You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airavata.apache.org by sm...@apache.org on 2017/12/06 03:13:44 UTC

[airavata-sandbox] branch master updated (4b072af -> c38e09e)

This is an automated email from the ASF dual-hosted git repository.

smarru pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-sandbox.git.


    from 4b072af  Updated read me  and added changes to the client file (#22)
     new 7f9341b  Initial workflow composer implementation
     new e20636a  Integrated workflow creation flow
     new 7ee67aa  Updated tasks to accept TaskContexts as inputs
     new 23b761e  Initial implementation of task execution engine on top of helix
     new ec218c2  Fixing broken features for web console
     new a1c4115  UI improvements
     new d6e055c  Refactoring
     new e83184a  Optimizing maven imports and adding docker build plugins to tasks
     new 0739614  Adding CI/CD support
     new cfbd298  Adding parent directory to Jenkinsfile
     new f8cc315  Updating Jenkinsfile to build parent pom
     new 28eba20  Removing unused imports
     new 930722c  Refactoring workflow scheduler
     new 6a046a9  Fixing minor bugs
     new c0ade6c  Fixing minor bugs
     new c43e46c  Fixing minor bugs
     new 7ee4d13  Fixing minor bugs
     new e42d3f6  Initial agent implementation
     new c38e09e  Adding async monitor component

The 19 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../modules/agents/agent-core/pom.xml              |    40 +
 .../airavata/agents/core/AsyncCommandStatus.java   |    19 +
 .../airavata/agents/core/AsyncOperation.java       |    24 +
 .../airavata/agents/core/StatusPublisher.java      |    49 +
 .../modules/agents/thrift-agent/pom.xml            |    47 +
 .../agents/thrift/handler/OperationHandler.java    |    36 +
 .../thrift/operation/ThriftAgentOperation.java     |    46 +
 .../agents/thrift/server/OperationServer.java      |    51 +
 .../agents/thrift/stubs/OperationException.java    |   476 +
 .../agents/thrift/stubs/OperationService.java      |  1072 +
 .../src/main/resources/application.properties      |     2 +
 .../thrift-agent/src/main/resources/schema.thrift  |    11 +
 .../process/ProcessBootstrapDataResource.java      |    40 +
 .../k8s/api/resources/process/ProcessResource.java |    40 +
 .../resources/process/ProcessStatusResource.java   |    62 +-
 .../k8s/api/resources/task/TaskDagResource.java    |    40 +
 .../k8s/api/resources/task/TaskInputResource.java  |    61 +
 .../api/resources/task/TaskOutPortResource.java    |    50 +
 .../k8s/api/resources/task/TaskOutputResource.java |    61 +
 .../k8s/api/resources/task/TaskResource.java       |    76 +-
 .../resources/task/type/TaskInputTypeResource.java |    51 +
 .../task/type/TaskOutPortTypeResource.java         |    41 +
 .../task/type/TaskOutputTypeResource.java          |    41 +
 .../api/resources/task/type/TaskTypeResource.java  |    84 +
 .../api/resources/workflow/WorkflowResource.java   |    54 +
 .../k8s/compute/impl/SSHComputeOperations.java     |   154 +-
 airavata-kubernetes/modules/helix-task-api/pom.xml |    55 +
 .../apache/airavata/helix/api/AbstractTask.java    |   154 +
 .../airavata/helix/api/HelixParticipant.java       |   145 +
 .../airavata/helix/api/PropertyResolver.java       |    38 +
 .../api/server/controller/DataStoreController.java |     9 +-
 .../api/server/controller/ProcessController.java   |     2 +
 .../k8s/api/server/controller/TaskController.java  |     9 +-
 .../api/server/controller/TaskTypeController.java  |    33 +
 .../api/server/controller/WorkflowController.java  |    52 +
 .../k8s/api/server/model/data/DataStoreModel.java  |    11 +-
 .../server/model/process/ProcessBootstrapData.java |    63 +
 .../k8s/api/server/model/process/ProcessModel.java |    78 +-
 .../k8s/api/server/model/task/TaskDAG.java         |    50 +
 .../k8s/api/server/model/task/TaskInput.java       |    86 +
 .../k8s/api/server/model/task/TaskModel.java       |   117 +-
 .../k8s/api/server/model/task/TaskOutPort.java     |    57 +
 .../k8s/api/server/model/task/TaskOutput.java      |    86 +
 .../k8s/api/server/model/task/TaskParam.java       |    82 -
 .../api/server/model/task/type/TaskInputType.java  |    69 +
 .../api/server/model/task/type/TaskModelType.java  |    95 +
 .../server/model/task/type/TaskOutPortType.java    |    61 +
 .../api/server/model/task/type/TaskOutputType.java |    59 +
 .../k8s/api/server/model/workflow/Workflow.java    |    69 +
 .../api/server/repository/TaskParamRepository.java |    32 -
 .../ApplicationDeploymentRepository.java           |     2 +-
 .../ApplicationIfaceRepository.java                |     2 +-
 .../ApplicationInputRepository.java                |     2 +-
 .../ApplicationModuleRepository.java               |     2 +-
 .../ApplicationOutputRepository.java               |     2 +-
 .../{ => compute}/ComputeRepository.java           |     2 +-
 .../repository/{ => data}/DataStoreRepository.java |     2 +-
 .../ExperimentInputDataRepository.java             |     2 +-
 .../ExperimentOutputDataRepository.java            |     2 +-
 .../{ => experiment}/ExperimentRepository.java     |     2 +-
 .../ExperimentStatusRepository.java                |     2 +-
 .../process/ProcessBootstrapDataRepository.java    |    13 +
 .../{ => process}/ProcessRepository.java           |     2 +-
 .../{ => process}/ProcessStatusRepository.java     |     2 +-
 .../server/repository/task/TaskDAGRepository.java  |    17 +
 .../repository/task/TaskInputRepository.java       |    13 +
 .../repository/task/TaskOutPortRepository.java     |    17 +
 .../repository/task/TaskOutputRepository.java      |    13 +
 .../repository/{ => task}/TaskRepository.java      |     2 +-
 .../{ => task}/TaskStatusRepository.java           |     2 +-
 .../task/type/TaskInputTypeRepository.java         |    13 +
 .../task/type/TaskOutPortTypeRepository.java       |    13 +
 .../task/type/TaskOutputTypeRepository.java        |    13 +
 .../repository/task/type/TaskTypeRepository.java   |    17 +
 .../repository/workflow/WorkflowRepository.java    |    16 +
 .../service/ApplicationDeploymentService.java      |     6 +-
 .../server/service/ApplicationIfaceService.java    |     8 +-
 .../server/service/ApplicationModuleService.java   |     2 +-
 .../api/server/service/ComputeResourceService.java |     2 +-
 .../k8s/api/server/service/ExperimentService.java  |     7 +-
 .../k8s/api/server/service/ProcessService.java     |    43 +-
 .../k8s/api/server/service/TaskService.java        |   109 -
 .../k8s/api/server/service/WorkflowService.java    |   150 +
 .../api/server/service/data/DataStoreService.java  |    13 +-
 .../k8s/api/server/service/task/TaskService.java   |   153 +
 .../server/service/task/type/TaskTypeService.java  |    94 +
 .../k8s/api/server/service/util/GraphParser.java   |   442 +
 .../api/server/service/util/ToResourceUtil.java    |   206 +-
 .../src/main/resources/application.properties      |     3 +-
 .../pom.xml                                        |    26 +-
 .../airavata/async/event/listener/Application.java |    29 +
 .../event/listener}/messaging/KafkaReceiver.java   |    25 +-
 .../event/listener}/messaging/ReceiverConfig.java  |    13 +-
 .../event/listener/service/ListenerService.java    |    35 +
 .../src/main/resources/application.properties      |     4 +
 .../src/main/resources/application.yml             |     0
 .../modules/microservices/helix-controller/pom.xml |   106 +
 .../org/apache/airavata/helix/HelixController.java |    94 +
 .../src/main/resources/application.properties      |     3 +
 .../src/main/resources/log4j.properties            |     9 +
 .../k8s/gfac/core/ProcessLifeCycleManager.java     |   124 -
 .../airavata/k8s/gfac/messaging/KafkaSender.java   |    39 -
 .../k8s/gfac/messaging/ReceiverConfig.java         |   109 -
 .../airavata/k8s/gfac/messaging/SenderConfig.java  |    69 -
 .../airavata/k8s/gfac/service/WorkerService.java   |    88 -
 .../src/main/resources/application.properties      |     5 -
 .../src/main/resources/application.yml             |     4 -
 .../tasks/async-command-monitor/pom.xml            |   113 +
 .../async/command/monitor/AsyncCommandMonitor.java |   111 +
 .../task/async/command/monitor/Participant.java    |    54 +
 .../src/main/resources/application.properties      |     7 +
 .../src/main/resources/log4j.properties            |     9 +
 .../microservices/tasks/async-command-task/pom.xml |   118 +
 .../helix/task/async/command/AsyncCommandTask.java |   127 +
 .../helix/task/async/command/Participant.java      |    54 +
 .../src/main/resources/application.properties      |     8 +
 .../src/main/resources/log4j.properties            |     9 +
 .../microservices/tasks/command-task/pom.xml       |   102 +
 .../airavata/helix/task/command/CommandTask.java   |   138 +
 .../airavata/helix/task/command/Participant.java   |    55 +
 .../src/main/resources/application.properties      |     7 +
 .../src/main/resources/log4j.properties            |     9 +
 .../microservices/tasks/data-in-task/pom.xml       |   101 +
 .../airavata/helix/task/datain/DataInputTask.java  |   124 +
 .../airavata/helix/task/datain/Participant.java    |    55 +
 .../src/main/resources/application.properties      |     7 +
 .../src/main/resources/log4j.properties            |     9 +
 .../microservices/tasks/data-out-task/pom.xml      |   102 +
 .../helix/task/dataout/DataOutputTask.java         |   131 +
 .../airavata/helix/task/dataout/Participant.java   |    54 +
 .../src/main/resources/application.properties      |     7 +
 .../src/main/resources/log4j.properties            |     9 +
 .../airavata/k8s/task/egress/Application.java      |    49 -
 .../k8s/task/egress/messaging/KafkaSender.java     |    43 -
 .../k8s/task/egress/messaging/ReceiverConfig.java  |    85 -
 .../k8s/task/egress/messaging/SenderConfig.java    |    69 -
 .../task/egress/service/TaskExecutionService.java  |   183 -
 .../src/main/resources/application.properties      |     5 -
 .../src/main/resources/application.yml             |     4 -
 .../microservices/tasks/env-cleanup-task/pom.xml   |   156 -
 .../airavata/k8s/task/cleanup/Application.java     |    49 -
 .../k8s/task/cleanup/messaging/KafkaReceiver.java  |    45 -
 .../k8s/task/cleanup/messaging/KafkaSender.java    |    43 -
 .../k8s/task/cleanup/messaging/SenderConfig.java   |    69 -
 .../task/cleanup/service/TaskExecutionService.java |   153 -
 .../src/main/resources/application.properties      |     5 -
 .../src/main/resources/application.yml             |     4 -
 .../microservices/tasks/env-setup-task/pom.xml     |   157 -
 .../airavata/k8s/task/env/setup/Application.java   |    49 -
 .../task/env/setup/messaging/KafkaReceiver.java    |    45 -
 .../k8s/task/env/setup/messaging/KafkaSender.java  |    43 -
 .../task/env/setup/messaging/ReceiverConfig.java   |    85 -
 .../k8s/task/env/setup/messaging/SenderConfig.java |    69 -
 .../env/setup/service/TaskExecutionService.java    |   153 -
 .../src/main/resources/application.properties      |     5 -
 .../src/main/resources/application.yml             |     4 -
 .../tasks/ingress-staging-task/pom.xml             |   156 -
 .../airavata/k8s/task/ingress/Application.java     |    49 -
 .../k8s/task/ingress/messaging/KafkaReceiver.java  |    45 -
 .../k8s/task/ingress/messaging/KafkaSender.java    |    43 -
 .../k8s/task/ingress/messaging/ReceiverConfig.java |    85 -
 .../k8s/task/ingress/messaging/SenderConfig.java   |    69 -
 .../task/ingress/service/TaskExecutionService.java |   126 -
 .../src/main/resources/application.properties      |     5 -
 .../tasks/job-submission-task/pom.xml              |   156 -
 .../apache/airavata/k8s/task/job/Application.java  |    49 -
 .../k8s/task/job/messaging/KafkaReceiver.java      |    45 -
 .../k8s/task/job/messaging/KafkaSender.java        |    43 -
 .../k8s/task/job/messaging/SenderConfig.java       |    69 -
 .../k8s/task/job/service/TaskExecutionService.java |   160 -
 .../src/main/resources/application.properties      |     5 -
 .../service/ExperimentLaunchService.java           |    20 +-
 .../pom.xml                                        |    42 +-
 .../org/apache/airavata/k8s/gfac/Application.java  |     0
 .../k8s/gfac/core/HelixWorkflowManager.java        |   201 +
 .../airavata/k8s/gfac/messaging/KafkaReceiver.java |    16 +-
 .../k8s/gfac}/messaging/ReceiverConfig.java        |    13 +-
 .../airavata/k8s/gfac/service/WorkerService.java   |   100 +
 .../src/main/resources/application.properties      |     9 +
 .../src/main/resources/application.yml             |     0
 .../src/main/resources/log4j.properties            |     9 +
 airavata-kubernetes/pom.xml                        |    44 +-
 airavata-kubernetes/readme.txt                     |     6 +-
 .../docker/jenkins-docker-kubectl-mvn/Dockerfile   |     4 +
 .../scripts/k8s/api-server/api-server-dep.yml      |     4 +-
 airavata-kubernetes/scripts/k8s/ci-cd/Jenkinsfile  |    79 +
 airavata-kubernetes/scripts/k8s/ci-cd/jenkins.yml  |    76 +
 .../k8s/helix-controller/helix-controller-dep.yml  |    24 +
 ...k-secheduler-dep.yml => task-scheduler-dep.yml} |     3 +
 .../k8s/tasks/command-task/command-task-dep.yml    |    28 +
 .../k8s/tasks/data-in-task/data-in-task-dep.yml    |    28 +
 .../k8s/tasks/data-out-task/data-out-task-dep.yml  |    28 +
 .../egress-staging-task-dep.yml                    |    21 -
 .../env-cleanup-task/env-cleanup-task-dep.yml      |    21 -
 .../tasks/env-setup-task/env-setup-task-dep.yml    |    21 -
 .../ingress-staging-task-dep.yml                   |    21 -
 .../job-submission-task-dep.yml                    |    21 -
 .../workflow-generator/workflow-generator-dep.yml  |    21 -
 .../scripts/k8s/zookeeper-service.yml              |    24 +
 .../web-console/src/app/app.module.ts              |    11 +-
 .../src/app/components/dashboard/dashboard.html    |     3 +
 .../app/components/dashboard/dashboard.routes.ts   |    15 +
 .../src/app/components/process/detail/detail.html  |    12 +-
 .../process/detail/process.detail.component.ts     |     5 +-
 .../components/workflow/create/create.component.ts |   436 +
 .../src/app/components/workflow/create/create.html |    33 +
 .../src/app/components/workflow/detail/detail.html |    56 +
 .../components/workflow/detail/workflow.detail.ts  |    85 +
 .../src/app/components/workflow/list/list.html     |    23 +
 .../workflow/list/workflow.list.component.ts       |    49 +
 .../type/operation/operation.inport.type.model.ts  |     9 +
 .../type/operation/operation.outport.type.model.ts |     9 +
 .../task/type/operation/operation.type.model.ts    |    29 +
 .../app/models/task/type/task.input.type.model.ts  |    14 +
 .../src/app/models/task/type/task.outport.model.ts |    12 +
 .../app/models/task/type/task.output.type.model.ts |    12 +
 .../src/app/models/task/type/task.type.model.ts    |    39 +
 .../src/app/models/workflow/workflow.model.ts      |     8 +
 .../web-console/src/app/services/task.service.ts   |     4 +
 .../web-console/src/app/services/util.service.ts   |    13 +
 .../src/app/services/workflow.service.ts           |    25 +
 .../web-console/src/assets/css/common.css          |   160 +
 .../web-console/src/assets/css/explorer.css        |    18 +
 .../web-console/src/assets/icons/copy.png          |   Bin 0 -> 3575 bytes
 .../web-console/src/assets/icons/datain.png        |   Bin 0 -> 4095 bytes
 .../web-console/src/assets/icons/dataout.png       |   Bin 0 -> 4110 bytes
 .../web-console/src/assets/icons/http.png          |   Bin 0 -> 3218 bytes
 .../web-console/src/assets/icons/parallel.png      |   Bin 0 -> 3150 bytes
 .../web-console/src/assets/icons/s3.png            |   Bin 0 -> 3988 bytes
 .../web-console/src/assets/icons/ssh.png           |   Bin 0 -> 3815 bytes
 .../web-console/src/assets/icons/start.png         |   Bin 0 -> 3303 bytes
 .../web-console/src/assets/icons/stop.png          |   Bin 0 -> 3525 bytes
 .../web-console/src/assets/images/button.gif       |   Bin 0 -> 137 bytes
 .../web-console/src/assets/images/close.gif        |   Bin 0 -> 70 bytes
 .../web-console/src/assets/images/collapsed.gif    |   Bin 0 -> 877 bytes
 .../web-console/src/assets/images/error.gif        |   Bin 0 -> 907 bytes
 .../web-console/src/assets/images/expanded.gif     |   Bin 0 -> 878 bytes
 .../web-console/src/assets/images/maximize.gif     |   Bin 0 -> 843 bytes
 .../web-console/src/assets/images/minimize.gif     |   Bin 0 -> 64 bytes
 .../web-console/src/assets/images/normalize.gif    |   Bin 0 -> 845 bytes
 .../web-console/src/assets/images/point.gif        |   Bin 0 -> 55 bytes
 .../web-console/src/assets/images/resize.gif       |   Bin 0 -> 74 bytes
 .../web-console/src/assets/images/separator.gif    |   Bin 0 -> 146 bytes
 .../web-console/src/assets/images/submenu.gif      |   Bin 0 -> 56 bytes
 .../web-console/src/assets/images/transparent.gif  |   Bin 0 -> 90 bytes
 .../web-console/src/assets/images/warning.gif      |   Bin 0 -> 276 bytes
 .../web-console/src/assets/images/warning.png      |   Bin 0 -> 425 bytes
 .../web-console/src/assets/images/window-title.gif |   Bin 0 -> 275 bytes
 .../web-console/src/assets/images/window.gif       |   Bin 0 -> 75 bytes
 .../web-console/src/assets/js/components.js        |    53 +
 .../src/assets/js/editor/mxDefaultKeyHandler.js    |   126 +
 .../src/assets/js/editor/mxDefaultPopupMenu.js     |   306 +
 .../src/assets/js/editor/mxDefaultToolbar.js       |   564 +
 .../web-console/src/assets/js/editor/mxEditor.js   |  3114 +
 .../src/assets/js/handler/mxCellHighlight.js       |   314 +
 .../src/assets/js/handler/mxCellMarker.js          |   430 +
 .../src/assets/js/handler/mxCellTracker.js         |   145 +
 .../src/assets/js/handler/mxConnectionHandler.js   |  2204 +
 .../src/assets/js/handler/mxConstraintHandler.js   |   520 +
 .../src/assets/js/handler/mxEdgeHandler.js         |  2409 +
 .../src/assets/js/handler/mxEdgeSegmentHandler.js  |   401 +
 .../src/assets/js/handler/mxElbowEdgeHandler.js    |   229 +
 .../src/assets/js/handler/mxGraphHandler.js        |  1074 +
 .../web-console/src/assets/js/handler/mxHandle.js  |   353 +
 .../src/assets/js/handler/mxKeyHandler.js          |   428 +
 .../src/assets/js/handler/mxPanningHandler.js      |   462 +
 .../src/assets/js/handler/mxPopupMenuHandler.js    |   218 +
 .../src/assets/js/handler/mxRubberband.js          |   401 +
 .../assets/js/handler/mxSelectionCellsHandler.js   |   287 +
 .../src/assets/js/handler/mxTooltipHandler.js      |   337 +
 .../src/assets/js/handler/mxVertexHandler.js       |  1950 +
 .../web-console/src/assets/js/index.txt            |   316 +
 .../web-console/src/assets/js/io/mxCellCodec.js    |   189 +
 .../src/assets/js/io/mxChildChangeCodec.js         |   149 +
 .../web-console/src/assets/js/io/mxCodec.js        |   596 +
 .../src/assets/js/io/mxCodecRegistry.js            |   137 +
 .../src/assets/js/io/mxDefaultKeyHandlerCodec.js   |    88 +
 .../src/assets/js/io/mxDefaultPopupMenuCodec.js    |    54 +
 .../src/assets/js/io/mxDefaultToolbarCodec.js      |   312 +
 .../web-console/src/assets/js/io/mxEditorCodec.js  |   245 +
 .../src/assets/js/io/mxGenericChangeCodec.js       |    64 +
 .../web-console/src/assets/js/io/mxGraphCodec.js   |    28 +
 .../src/assets/js/io/mxGraphViewCodec.js           |   197 +
 .../web-console/src/assets/js/io/mxModelCodec.js   |    80 +
 .../web-console/src/assets/js/io/mxObjectCodec.js  |  1077 +
 .../src/assets/js/io/mxRootChangeCodec.js          |    83 +
 .../src/assets/js/io/mxStylesheetCodec.js          |   217 +
 .../src/assets/js/io/mxTerminalChangeCodec.js      |    42 +
 .../model/mxGraphAbstractHierarchyCell.js          |   206 +
 .../hierarchical/model/mxGraphHierarchyEdge.js     |   187 +
 .../hierarchical/model/mxGraphHierarchyModel.js    |   681 +
 .../hierarchical/model/mxGraphHierarchyNode.js     |   220 +
 .../layout/hierarchical/model/mxSwimlaneModel.js   |   801 +
 .../js/layout/hierarchical/mxHierarchicalLayout.js |   846 +
 .../js/layout/hierarchical/mxSwimlaneLayout.js     |   937 +
 .../hierarchical/stage/mxCoordinateAssignment.js   |  1830 +
 .../stage/mxHierarchicalLayoutStage.js             |    25 +
 .../stage/mxMedianHybridCrossingReduction.js       |   675 +
 .../hierarchical/stage/mxMinimumCycleRemover.js    |   108 +
 .../hierarchical/stage/mxSwimlaneOrdering.js       |    96 +
 .../src/assets/js/layout/mxCircleLayout.js         |   203 +
 .../src/assets/js/layout/mxCompactTreeLayout.js    |  1203 +
 .../src/assets/js/layout/mxCompositeLayout.js      |   101 +
 .../src/assets/js/layout/mxEdgeLabelLayout.js      |   165 +
 .../src/assets/js/layout/mxFastOrganicLayout.js    |   591 +
 .../src/assets/js/layout/mxGraphLayout.js          |   461 +
 .../src/assets/js/layout/mxParallelEdgeLayout.js   |   225 +
 .../src/assets/js/layout/mxPartitionLayout.js      |   240 +
 .../src/assets/js/layout/mxRadialTreeLayout.js     |   317 +
 .../src/assets/js/layout/mxStackLayout.js          |   515 +
 .../web-console/src/assets/js/model/mxCell.js      |   825 +
 .../web-console/src/assets/js/model/mxCellPath.js  |   163 +
 .../web-console/src/assets/js/model/mxGeometry.js  |   415 +
 .../src/assets/js/model/mxGraphModel.js            |  2667 +
 .../web-console/src/assets/js/mxClient.js          |   769 +
 .../web-console/src/assets/js/shape/mxActor.js     |    86 +
 .../web-console/src/assets/js/shape/mxArrow.js     |   115 +
 .../src/assets/js/shape/mxArrowConnector.js        |   485 +
 .../web-console/src/assets/js/shape/mxCloud.js     |    55 +
 .../web-console/src/assets/js/shape/mxConnector.js |   149 +
 .../web-console/src/assets/js/shape/mxCylinder.js  |   105 +
 .../src/assets/js/shape/mxDoubleEllipse.js         |   114 +
 .../web-console/src/assets/js/shape/mxEllipse.js   |    48 +
 .../web-console/src/assets/js/shape/mxHexagon.js   |    34 +
 .../src/assets/js/shape/mxImageShape.js            |   233 +
 .../web-console/src/assets/js/shape/mxLabel.js     |   275 +
 .../web-console/src/assets/js/shape/mxLine.js      |    51 +
 .../web-console/src/assets/js/shape/mxMarker.js    |   208 +
 .../web-console/src/assets/js/shape/mxPolyline.js  |   127 +
 .../src/assets/js/shape/mxRectangleShape.js        |   117 +
 .../web-console/src/assets/js/shape/mxRhombus.js   |    54 +
 .../web-console/src/assets/js/shape/mxShape.js     |  1604 +
 .../web-console/src/assets/js/shape/mxStencil.js   |   761 +
 .../src/assets/js/shape/mxStencilRegistry.js       |    53 +
 .../web-console/src/assets/js/shape/mxSwimlane.js  |   410 +
 .../web-console/src/assets/js/shape/mxText.js      |  1263 +
 .../web-console/src/assets/js/shape/mxTriangle.js  |    33 +
 .../src/assets/js/util/mxAbstractCanvas2D.js       |   642 +
 .../web-console/src/assets/js/util/mxAnimation.js  |    92 +
 .../src/assets/js/util/mxAutoSaveManager.js        |   213 +
 .../web-console/src/assets/js/util/mxClipboard.js  |   221 +
 .../web-console/src/assets/js/util/mxConstants.js  |  2275 +
 .../web-console/src/assets/js/util/mxDictionary.js |   130 +
 .../web-console/src/assets/js/util/mxDivResizer.js |   151 +
 .../web-console/src/assets/js/util/mxDragSource.js |   679 +
 .../web-console/src/assets/js/util/mxEffects.js    |   211 +
 .../web-console/src/assets/js/util/mxEvent.js      |  1406 +
 .../src/assets/js/util/mxEventObject.js            |   111 +
 .../src/assets/js/util/mxEventSource.js            |   189 +
 .../web-console/src/assets/js/util/mxForm.js       |   202 +
 .../web-console/src/assets/js/util/mxGuide.js      |   401 +
 .../web-console/src/assets/js/util/mxImage.js      |    40 +
 .../src/assets/js/util/mxImageBundle.js            |   103 +
 .../src/assets/js/util/mxImageExport.js            |   175 +
 .../web-console/src/assets/js/util/mxLog.js        |   413 +
 .../web-console/src/assets/js/util/mxMorphing.js   |   248 +
 .../web-console/src/assets/js/util/mxMouseEvent.js |   244 +
 .../src/assets/js/util/mxObjectIdentity.js         |    72 +
 .../src/assets/js/util/mxPanningManager.js         |   262 +
 .../web-console/src/assets/js/util/mxPoint.js      |    54 +
 .../web-console/src/assets/js/util/mxPopupMenu.js  |   613 +
 .../web-console/src/assets/js/util/mxRectangle.js  |   179 +
 .../web-console/src/assets/js/util/mxResources.js  |   450 +
 .../src/assets/js/util/mxSvgCanvas2D.js            |  2179 +
 .../web-console/src/assets/js/util/mxToolbar.js    |   527 +
 .../src/assets/js/util/mxUndoManager.js            |   229 +
 .../src/assets/js/util/mxUndoableEdit.js           |   213 +
 .../src/assets/js/util/mxUrlConverter.js           |   151 +
 .../web-console/src/assets/js/util/mxUtils.js      |  4353 +
 .../src/assets/js/util/mxVmlCanvas2D.js            |  1102 +
 .../web-console/src/assets/js/util/mxWindow.js     |  1130 +
 .../src/assets/js/util/mxXmlCanvas2D.js            |  1217 +
 .../web-console/src/assets/js/util/mxXmlRequest.js |   463 +
 .../web-console/src/assets/js/view/mxCellEditor.js |  1069 +
 .../src/assets/js/view/mxCellOverlay.js            |   233 +
 .../src/assets/js/view/mxCellRenderer.js           |  1553 +
 .../web-console/src/assets/js/view/mxCellState.js  |   431 +
 .../src/assets/js/view/mxCellStatePreview.js       |   203 +
 .../src/assets/js/view/mxConnectionConstraint.js   |    50 +
 .../web-console/src/assets/js/view/mxEdgeStyle.js  |  1569 +
 .../web-console/src/assets/js/view/mxGraph.js      | 12768 +++
 .../src/assets/js/view/mxGraphSelectionModel.js    |   436 +
 .../web-console/src/assets/js/view/mxGraphView.js  |  3001 +
 .../src/assets/js/view/mxLayoutManager.js          |   409 +
 .../src/assets/js/view/mxMultiplicity.js           |   257 +
 .../web-console/src/assets/js/view/mxOutline.js    |   761 +
 .../web-console/src/assets/js/view/mxPerimeter.js  |   921 +
 .../src/assets/js/view/mxPrintPreview.js           |  1175 +
 .../src/assets/js/view/mxStyleRegistry.js          |    71 +
 .../web-console/src/assets/js/view/mxStylesheet.js |   266 +
 .../src/assets/js/view/mxSwimlaneManager.js        |   450 +
 .../src/assets/js/view/mxTemporaryCellStates.js    |   108 +
 .../web-console/src/assets/resources/editor.txt    |     5 +
 .../web-console/src/assets/resources/editor_de.txt |     5 +
 .../web-console/src/assets/resources/editor_zh.txt |     5 +
 .../web-console/src/assets/resources/graph.txt     |    11 +
 .../web-console/src/assets/resources/graph_de.txt  |    11 +
 .../web-console/src/assets/resources/graph_zh.txt  |    11 +
 airavata-kubernetes/web-console/src/index.html     |     6 +
 airavata-kubernetes/workflow-composer/index.html   |   374 +
 airavata-kubernetes/workflow-composer/mxClient.js  | 88604 +++++++++++++++++++
 .../workflow-composer/mxClient.min.js              |  1797 +
 .../workflow-composer/src/css/common.css           |   160 +
 .../workflow-composer/src/css/explorer.css         |    18 +
 .../workflow-composer/src/icons/copy.png           |   Bin 0 -> 3575 bytes
 .../workflow-composer/src/icons/http.png           |   Bin 0 -> 3218 bytes
 .../workflow-composer/src/icons/parallel.png       |   Bin 0 -> 3150 bytes
 .../workflow-composer/src/icons/s3.png             |   Bin 0 -> 3988 bytes
 .../workflow-composer/src/icons/ssh.png            |   Bin 0 -> 3815 bytes
 .../workflow-composer/src/icons/start.png          |   Bin 0 -> 3303 bytes
 .../workflow-composer/src/icons/stop.png           |   Bin 0 -> 3525 bytes
 .../workflow-composer/src/images/button.gif        |   Bin 0 -> 137 bytes
 .../workflow-composer/src/images/close.gif         |   Bin 0 -> 70 bytes
 .../workflow-composer/src/images/collapsed.gif     |   Bin 0 -> 877 bytes
 .../workflow-composer/src/images/error.gif         |   Bin 0 -> 907 bytes
 .../workflow-composer/src/images/expanded.gif      |   Bin 0 -> 878 bytes
 .../workflow-composer/src/images/maximize.gif      |   Bin 0 -> 843 bytes
 .../workflow-composer/src/images/minimize.gif      |   Bin 0 -> 64 bytes
 .../workflow-composer/src/images/normalize.gif     |   Bin 0 -> 845 bytes
 .../workflow-composer/src/images/point.gif         |   Bin 0 -> 55 bytes
 .../workflow-composer/src/images/resize.gif        |   Bin 0 -> 74 bytes
 .../workflow-composer/src/images/separator.gif     |   Bin 0 -> 146 bytes
 .../workflow-composer/src/images/submenu.gif       |   Bin 0 -> 56 bytes
 .../workflow-composer/src/images/transparent.gif   |   Bin 0 -> 90 bytes
 .../workflow-composer/src/images/warning.gif       |   Bin 0 -> 276 bytes
 .../workflow-composer/src/images/warning.png       |   Bin 0 -> 425 bytes
 .../workflow-composer/src/images/window-title.gif  |   Bin 0 -> 275 bytes
 .../workflow-composer/src/images/window.gif        |   Bin 0 -> 75 bytes
 .../workflow-composer/src/js/components.js         |    53 +
 .../src/js/editor/mxDefaultKeyHandler.js           |   126 +
 .../src/js/editor/mxDefaultPopupMenu.js            |   306 +
 .../src/js/editor/mxDefaultToolbar.js              |   564 +
 .../workflow-composer/src/js/editor/mxEditor.js    |  3114 +
 .../src/js/handler/mxCellHighlight.js              |   314 +
 .../src/js/handler/mxCellMarker.js                 |   430 +
 .../src/js/handler/mxCellTracker.js                |   145 +
 .../src/js/handler/mxConnectionHandler.js          |  2204 +
 .../src/js/handler/mxConstraintHandler.js          |   520 +
 .../src/js/handler/mxEdgeHandler.js                |  2409 +
 .../src/js/handler/mxEdgeSegmentHandler.js         |   401 +
 .../src/js/handler/mxElbowEdgeHandler.js           |   229 +
 .../src/js/handler/mxGraphHandler.js               |  1074 +
 .../workflow-composer/src/js/handler/mxHandle.js   |   353 +
 .../src/js/handler/mxKeyHandler.js                 |   428 +
 .../src/js/handler/mxPanningHandler.js             |   462 +
 .../src/js/handler/mxPopupMenuHandler.js           |   218 +
 .../src/js/handler/mxRubberband.js                 |   401 +
 .../src/js/handler/mxSelectionCellsHandler.js      |   287 +
 .../src/js/handler/mxTooltipHandler.js             |   337 +
 .../src/js/handler/mxVertexHandler.js              |  1950 +
 .../workflow-composer/src/js/index.txt             |   316 +
 .../workflow-composer/src/js/io/mxCellCodec.js     |   189 +
 .../src/js/io/mxChildChangeCodec.js                |   149 +
 .../workflow-composer/src/js/io/mxCodec.js         |   596 +
 .../workflow-composer/src/js/io/mxCodecRegistry.js |   137 +
 .../src/js/io/mxDefaultKeyHandlerCodec.js          |    88 +
 .../src/js/io/mxDefaultPopupMenuCodec.js           |    54 +
 .../src/js/io/mxDefaultToolbarCodec.js             |   312 +
 .../workflow-composer/src/js/io/mxEditorCodec.js   |   245 +
 .../src/js/io/mxGenericChangeCodec.js              |    64 +
 .../workflow-composer/src/js/io/mxGraphCodec.js    |    28 +
 .../src/js/io/mxGraphViewCodec.js                  |   197 +
 .../workflow-composer/src/js/io/mxModelCodec.js    |    80 +
 .../workflow-composer/src/js/io/mxObjectCodec.js   |  1077 +
 .../src/js/io/mxRootChangeCodec.js                 |    83 +
 .../src/js/io/mxStylesheetCodec.js                 |   217 +
 .../src/js/io/mxTerminalChangeCodec.js             |    42 +
 .../model/mxGraphAbstractHierarchyCell.js          |   206 +
 .../hierarchical/model/mxGraphHierarchyEdge.js     |   187 +
 .../hierarchical/model/mxGraphHierarchyModel.js    |   681 +
 .../hierarchical/model/mxGraphHierarchyNode.js     |   220 +
 .../layout/hierarchical/model/mxSwimlaneModel.js   |   801 +
 .../js/layout/hierarchical/mxHierarchicalLayout.js |   846 +
 .../src/js/layout/hierarchical/mxSwimlaneLayout.js |   937 +
 .../hierarchical/stage/mxCoordinateAssignment.js   |  1830 +
 .../stage/mxHierarchicalLayoutStage.js             |    25 +
 .../stage/mxMedianHybridCrossingReduction.js       |   675 +
 .../hierarchical/stage/mxMinimumCycleRemover.js    |   108 +
 .../hierarchical/stage/mxSwimlaneOrdering.js       |    96 +
 .../src/js/layout/mxCircleLayout.js                |   203 +
 .../src/js/layout/mxCompactTreeLayout.js           |  1203 +
 .../src/js/layout/mxCompositeLayout.js             |   101 +
 .../src/js/layout/mxEdgeLabelLayout.js             |   165 +
 .../src/js/layout/mxFastOrganicLayout.js           |   591 +
 .../src/js/layout/mxGraphLayout.js                 |   461 +
 .../src/js/layout/mxParallelEdgeLayout.js          |   225 +
 .../src/js/layout/mxPartitionLayout.js             |   240 +
 .../src/js/layout/mxRadialTreeLayout.js            |   317 +
 .../src/js/layout/mxStackLayout.js                 |   515 +
 .../workflow-composer/src/js/model/mxCell.js       |   825 +
 .../workflow-composer/src/js/model/mxCellPath.js   |   163 +
 .../workflow-composer/src/js/model/mxGeometry.js   |   415 +
 .../workflow-composer/src/js/model/mxGraphModel.js |  2667 +
 .../workflow-composer/src/js/mxClient.js           |   769 +
 .../workflow-composer/src/js/shape/mxActor.js      |    86 +
 .../workflow-composer/src/js/shape/mxArrow.js      |   115 +
 .../src/js/shape/mxArrowConnector.js               |   485 +
 .../workflow-composer/src/js/shape/mxCloud.js      |    55 +
 .../workflow-composer/src/js/shape/mxConnector.js  |   149 +
 .../workflow-composer/src/js/shape/mxCylinder.js   |   105 +
 .../src/js/shape/mxDoubleEllipse.js                |   114 +
 .../workflow-composer/src/js/shape/mxEllipse.js    |    48 +
 .../workflow-composer/src/js/shape/mxHexagon.js    |    34 +
 .../workflow-composer/src/js/shape/mxImageShape.js |   233 +
 .../workflow-composer/src/js/shape/mxLabel.js      |   275 +
 .../workflow-composer/src/js/shape/mxLine.js       |    51 +
 .../workflow-composer/src/js/shape/mxMarker.js     |   208 +
 .../workflow-composer/src/js/shape/mxPolyline.js   |   127 +
 .../src/js/shape/mxRectangleShape.js               |   117 +
 .../workflow-composer/src/js/shape/mxRhombus.js    |    54 +
 .../workflow-composer/src/js/shape/mxShape.js      |  1604 +
 .../workflow-composer/src/js/shape/mxStencil.js    |   761 +
 .../src/js/shape/mxStencilRegistry.js              |    53 +
 .../workflow-composer/src/js/shape/mxSwimlane.js   |   410 +
 .../workflow-composer/src/js/shape/mxText.js       |  1263 +
 .../workflow-composer/src/js/shape/mxTriangle.js   |    33 +
 .../src/js/util/mxAbstractCanvas2D.js              |   642 +
 .../workflow-composer/src/js/util/mxAnimation.js   |    92 +
 .../src/js/util/mxAutoSaveManager.js               |   213 +
 .../workflow-composer/src/js/util/mxClipboard.js   |   221 +
 .../workflow-composer/src/js/util/mxConstants.js   |  2275 +
 .../workflow-composer/src/js/util/mxDictionary.js  |   130 +
 .../workflow-composer/src/js/util/mxDivResizer.js  |   151 +
 .../workflow-composer/src/js/util/mxDragSource.js  |   679 +
 .../workflow-composer/src/js/util/mxEffects.js     |   211 +
 .../workflow-composer/src/js/util/mxEvent.js       |  1406 +
 .../workflow-composer/src/js/util/mxEventObject.js |   111 +
 .../workflow-composer/src/js/util/mxEventSource.js |   189 +
 .../workflow-composer/src/js/util/mxForm.js        |   202 +
 .../workflow-composer/src/js/util/mxGuide.js       |   401 +
 .../workflow-composer/src/js/util/mxImage.js       |    40 +
 .../workflow-composer/src/js/util/mxImageBundle.js |   103 +
 .../workflow-composer/src/js/util/mxImageExport.js |   175 +
 .../workflow-composer/src/js/util/mxLog.js         |   413 +
 .../workflow-composer/src/js/util/mxMorphing.js    |   248 +
 .../workflow-composer/src/js/util/mxMouseEvent.js  |   244 +
 .../src/js/util/mxObjectIdentity.js                |    72 +
 .../src/js/util/mxPanningManager.js                |   262 +
 .../workflow-composer/src/js/util/mxPoint.js       |    54 +
 .../workflow-composer/src/js/util/mxPopupMenu.js   |   613 +
 .../workflow-composer/src/js/util/mxRectangle.js   |   179 +
 .../workflow-composer/src/js/util/mxResources.js   |   450 +
 .../workflow-composer/src/js/util/mxSvgCanvas2D.js |  2179 +
 .../workflow-composer/src/js/util/mxToolbar.js     |   527 +
 .../workflow-composer/src/js/util/mxUndoManager.js |   229 +
 .../src/js/util/mxUndoableEdit.js                  |   213 +
 .../src/js/util/mxUrlConverter.js                  |   151 +
 .../workflow-composer/src/js/util/mxUtils.js       |  4353 +
 .../workflow-composer/src/js/util/mxVmlCanvas2D.js |  1102 +
 .../workflow-composer/src/js/util/mxWindow.js      |  1130 +
 .../workflow-composer/src/js/util/mxXmlCanvas2D.js |  1217 +
 .../workflow-composer/src/js/util/mxXmlRequest.js  |   463 +
 .../workflow-composer/src/js/view/mxCellEditor.js  |  1069 +
 .../workflow-composer/src/js/view/mxCellOverlay.js |   233 +
 .../src/js/view/mxCellRenderer.js                  |  1553 +
 .../workflow-composer/src/js/view/mxCellState.js   |   431 +
 .../src/js/view/mxCellStatePreview.js              |   203 +
 .../src/js/view/mxConnectionConstraint.js          |    50 +
 .../workflow-composer/src/js/view/mxEdgeStyle.js   |  1569 +
 .../workflow-composer/src/js/view/mxGraph.js       | 12768 +++
 .../src/js/view/mxGraphSelectionModel.js           |   436 +
 .../workflow-composer/src/js/view/mxGraphView.js   |  3001 +
 .../src/js/view/mxLayoutManager.js                 |   409 +
 .../src/js/view/mxMultiplicity.js                  |   257 +
 .../workflow-composer/src/js/view/mxOutline.js     |   761 +
 .../workflow-composer/src/js/view/mxPerimeter.js   |   921 +
 .../src/js/view/mxPrintPreview.js                  |  1175 +
 .../src/js/view/mxStyleRegistry.js                 |    71 +
 .../workflow-composer/src/js/view/mxStylesheet.js  |   266 +
 .../src/js/view/mxSwimlaneManager.js               |   450 +
 .../src/js/view/mxTemporaryCellStates.js           |   108 +
 .../workflow-composer/src/resources/editor.txt     |     5 +
 .../workflow-composer/src/resources/editor_de.txt  |     5 +
 .../workflow-composer/src/resources/editor_zh.txt  |     5 +
 .../workflow-composer/src/resources/graph.txt      |    11 +
 .../workflow-composer/src/resources/graph_de.txt   |    11 +
 .../workflow-composer/src/resources/graph_zh.txt   |    11 +
 577 files changed, 278138 insertions(+), 3757 deletions(-)
 create mode 100644 airavata-kubernetes/modules/agents/agent-core/pom.xml
 create mode 100644 airavata-kubernetes/modules/agents/agent-core/src/main/java/org/apache/airavata/agents/core/AsyncCommandStatus.java
 create mode 100644 airavata-kubernetes/modules/agents/agent-core/src/main/java/org/apache/airavata/agents/core/AsyncOperation.java
 create mode 100644 airavata-kubernetes/modules/agents/agent-core/src/main/java/org/apache/airavata/agents/core/StatusPublisher.java
 create mode 100644 airavata-kubernetes/modules/agents/thrift-agent/pom.xml
 create mode 100644 airavata-kubernetes/modules/agents/thrift-agent/src/main/java/org/apache/airavata/agents/thrift/handler/OperationHandler.java
 create mode 100644 airavata-kubernetes/modules/agents/thrift-agent/src/main/java/org/apache/airavata/agents/thrift/operation/ThriftAgentOperation.java
 create mode 100644 airavata-kubernetes/modules/agents/thrift-agent/src/main/java/org/apache/airavata/agents/thrift/server/OperationServer.java
 create mode 100644 airavata-kubernetes/modules/agents/thrift-agent/src/main/java/org/apache/airavata/agents/thrift/stubs/OperationException.java
 create mode 100644 airavata-kubernetes/modules/agents/thrift-agent/src/main/java/org/apache/airavata/agents/thrift/stubs/OperationService.java
 create mode 100644 airavata-kubernetes/modules/agents/thrift-agent/src/main/resources/application.properties
 create mode 100644 airavata-kubernetes/modules/agents/thrift-agent/src/main/resources/schema.thrift
 create mode 100644 airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/process/ProcessBootstrapDataResource.java
 create mode 100644 airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/task/TaskDagResource.java
 create mode 100644 airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/task/TaskInputResource.java
 create mode 100644 airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/task/TaskOutPortResource.java
 create mode 100644 airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/task/TaskOutputResource.java
 create mode 100644 airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/task/type/TaskInputTypeResource.java
 create mode 100644 airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/task/type/TaskOutPortTypeResource.java
 create mode 100644 airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/task/type/TaskOutputTypeResource.java
 create mode 100644 airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/task/type/TaskTypeResource.java
 create mode 100644 airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/workflow/WorkflowResource.java
 create mode 100644 airavata-kubernetes/modules/helix-task-api/pom.xml
 create mode 100644 airavata-kubernetes/modules/helix-task-api/src/main/java/org/apache/airavata/helix/api/AbstractTask.java
 create mode 100644 airavata-kubernetes/modules/helix-task-api/src/main/java/org/apache/airavata/helix/api/HelixParticipant.java
 create mode 100644 airavata-kubernetes/modules/helix-task-api/src/main/java/org/apache/airavata/helix/api/PropertyResolver.java
 create mode 100644 airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/controller/TaskTypeController.java
 create mode 100644 airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/controller/WorkflowController.java
 create mode 100644 airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/process/ProcessBootstrapData.java
 create mode 100644 airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/TaskDAG.java
 create mode 100644 airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/TaskInput.java
 create mode 100644 airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/TaskOutPort.java
 create mode 100644 airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/TaskOutput.java
 delete mode 100644 airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/TaskParam.java
 create mode 100644 airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/type/TaskInputType.java
 create mode 100644 airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/type/TaskModelType.java
 create mode 100644 airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/type/TaskOutPortType.java
 create mode 100644 airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/type/TaskOutputType.java
 create mode 100644 airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/workflow/Workflow.java
 delete mode 100644 airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/TaskParamRepository.java
 rename airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/{ => application}/ApplicationDeploymentRepository.java (94%)
 rename airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/{ => application}/ApplicationIfaceRepository.java (94%)
 rename airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/{ => application}/ApplicationInputRepository.java (94%)
 rename airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/{ => application}/ApplicationModuleRepository.java (94%)
 rename airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/{ => application}/ApplicationOutputRepository.java (94%)
 rename airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/{ => compute}/ComputeRepository.java (95%)
 rename airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/{ => data}/DataStoreRepository.java (95%)
 rename airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/{ => experiment}/ExperimentInputDataRepository.java (94%)
 rename airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/{ => experiment}/ExperimentOutputDataRepository.java (94%)
 rename airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/{ => experiment}/ExperimentRepository.java (94%)
 rename airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/{ => experiment}/ExperimentStatusRepository.java (94%)
 create mode 100644 airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/process/ProcessBootstrapDataRepository.java
 rename airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/{ => process}/ProcessRepository.java (94%)
 rename airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/{ => process}/ProcessStatusRepository.java (94%)
 create mode 100644 airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/task/TaskDAGRepository.java
 create mode 100644 airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/task/TaskInputRepository.java
 create mode 100644 airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/task/TaskOutPortRepository.java
 create mode 100644 airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/task/TaskOutputRepository.java
 rename airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/{ => task}/TaskRepository.java (95%)
 rename airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/{ => task}/TaskStatusRepository.java (95%)
 create mode 100644 airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/task/type/TaskInputTypeRepository.java
 create mode 100644 airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/task/type/TaskOutPortTypeRepository.java
 create mode 100644 airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/task/type/TaskOutputTypeRepository.java
 create mode 100644 airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/task/type/TaskTypeRepository.java
 create mode 100644 airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/workflow/WorkflowRepository.java
 delete mode 100644 airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/TaskService.java
 create mode 100644 airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/WorkflowService.java
 create mode 100644 airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/task/TaskService.java
 create mode 100644 airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/task/type/TaskTypeService.java
 create mode 100644 airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/util/GraphParser.java
 rename airavata-kubernetes/modules/microservices/{task-scheduler => async-event-listener}/pom.xml (85%)
 create mode 100644 airavata-kubernetes/modules/microservices/async-event-listener/src/main/java/org/apache/airavata/async/event/listener/Application.java
 rename airavata-kubernetes/modules/microservices/{tasks/egress-staging-task/src/main/java/org/apacher/airavata/k8s/task/egress => async-event-listener/src/main/java/org/apache/airavata/async/event/listener}/messaging/KafkaReceiver.java (52%)
 rename airavata-kubernetes/modules/microservices/{tasks/job-submission-task/src/main/java/org/apache/airavata/k8s/task/job => async-event-listener/src/main/java/org/apache/airavata/async/event/listener}/messaging/ReceiverConfig.java (87%)
 create mode 100644 airavata-kubernetes/modules/microservices/async-event-listener/src/main/java/org/apache/airavata/async/event/listener/service/ListenerService.java
 create mode 100644 airavata-kubernetes/modules/microservices/async-event-listener/src/main/resources/application.properties
 rename airavata-kubernetes/modules/microservices/{tasks/job-submission-task => async-event-listener}/src/main/resources/application.yml (100%)
 create mode 100644 airavata-kubernetes/modules/microservices/helix-controller/pom.xml
 create mode 100644 airavata-kubernetes/modules/microservices/helix-controller/src/main/java/org/apache/airavata/helix/HelixController.java
 create mode 100644 airavata-kubernetes/modules/microservices/helix-controller/src/main/resources/application.properties
 create mode 100644 airavata-kubernetes/modules/microservices/helix-controller/src/main/resources/log4j.properties
 delete mode 100644 airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/core/ProcessLifeCycleManager.java
 delete mode 100644 airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/KafkaSender.java
 delete mode 100644 airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/ReceiverConfig.java
 delete mode 100644 airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/SenderConfig.java
 delete mode 100644 airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/service/WorkerService.java
 delete mode 100644 airavata-kubernetes/modules/microservices/task-scheduler/src/main/resources/application.properties
 delete mode 100644 airavata-kubernetes/modules/microservices/task-scheduler/src/main/resources/application.yml
 create mode 100644 airavata-kubernetes/modules/microservices/tasks/async-command-monitor/pom.xml
 create mode 100644 airavata-kubernetes/modules/microservices/tasks/async-command-monitor/src/main/java/org/apache/airavata/task/async/command/monitor/AsyncCommandMonitor.java
 create mode 100644 airavata-kubernetes/modules/microservices/tasks/async-command-monitor/src/main/java/org/apache/airavata/task/async/command/monitor/Participant.java
 create mode 100644 airavata-kubernetes/modules/microservices/tasks/async-command-monitor/src/main/resources/application.properties
 create mode 100644 airavata-kubernetes/modules/microservices/tasks/async-command-monitor/src/main/resources/log4j.properties
 create mode 100644 airavata-kubernetes/modules/microservices/tasks/async-command-task/pom.xml
 create mode 100644 airavata-kubernetes/modules/microservices/tasks/async-command-task/src/main/java/org/apache/airavata/helix/task/async/command/AsyncCommandTask.java
 create mode 100644 airavata-kubernetes/modules/microservices/tasks/async-command-task/src/main/java/org/apache/airavata/helix/task/async/command/Participant.java
 create mode 100644 airavata-kubernetes/modules/microservices/tasks/async-command-task/src/main/resources/application.properties
 create mode 100644 airavata-kubernetes/modules/microservices/tasks/async-command-task/src/main/resources/log4j.properties
 create mode 100644 airavata-kubernetes/modules/microservices/tasks/command-task/pom.xml
 create mode 100644 airavata-kubernetes/modules/microservices/tasks/command-task/src/main/java/org/apache/airavata/helix/task/command/CommandTask.java
 create mode 100644 airavata-kubernetes/modules/microservices/tasks/command-task/src/main/java/org/apache/airavata/helix/task/command/Participant.java
 create mode 100644 airavata-kubernetes/modules/microservices/tasks/command-task/src/main/resources/application.properties
 create mode 100644 airavata-kubernetes/modules/microservices/tasks/command-task/src/main/resources/log4j.properties
 create mode 100644 airavata-kubernetes/modules/microservices/tasks/data-in-task/pom.xml
 create mode 100644 airavata-kubernetes/modules/microservices/tasks/data-in-task/src/main/java/org/apache/airavata/helix/task/datain/DataInputTask.java
 create mode 100644 airavata-kubernetes/modules/microservices/tasks/data-in-task/src/main/java/org/apache/airavata/helix/task/datain/Participant.java
 create mode 100644 airavata-kubernetes/modules/microservices/tasks/data-in-task/src/main/resources/application.properties
 create mode 100644 airavata-kubernetes/modules/microservices/tasks/data-in-task/src/main/resources/log4j.properties
 create mode 100644 airavata-kubernetes/modules/microservices/tasks/data-out-task/pom.xml
 create mode 100644 airavata-kubernetes/modules/microservices/tasks/data-out-task/src/main/java/org/apache/airavata/helix/task/dataout/DataOutputTask.java
 create mode 100644 airavata-kubernetes/modules/microservices/tasks/data-out-task/src/main/java/org/apache/airavata/helix/task/dataout/Participant.java
 create mode 100644 airavata-kubernetes/modules/microservices/tasks/data-out-task/src/main/resources/application.properties
 create mode 100644 airavata-kubernetes/modules/microservices/tasks/data-out-task/src/main/resources/log4j.properties
 delete mode 100644 airavata-kubernetes/modules/microservices/tasks/egress-staging-task/src/main/java/org/apacher/airavata/k8s/task/egress/Application.java
 delete mode 100644 airavata-kubernetes/modules/microservices/tasks/egress-staging-task/src/main/java/org/apacher/airavata/k8s/task/egress/messaging/KafkaSender.java
 delete mode 100644 airavata-kubernetes/modules/microservices/tasks/egress-staging-task/src/main/java/org/apacher/airavata/k8s/task/egress/messaging/ReceiverConfig.java
 delete mode 100644 airavata-kubernetes/modules/microservices/tasks/egress-staging-task/src/main/java/org/apacher/airavata/k8s/task/egress/messaging/SenderConfig.java
 delete mode 100644 airavata-kubernetes/modules/microservices/tasks/egress-staging-task/src/main/java/org/apacher/airavata/k8s/task/egress/service/TaskExecutionService.java
 delete mode 100644 airavata-kubernetes/modules/microservices/tasks/egress-staging-task/src/main/resources/application.properties
 delete mode 100644 airavata-kubernetes/modules/microservices/tasks/egress-staging-task/src/main/resources/application.yml
 delete mode 100644 airavata-kubernetes/modules/microservices/tasks/env-cleanup-task/pom.xml
 delete mode 100644 airavata-kubernetes/modules/microservices/tasks/env-cleanup-task/src/main/java/org/apache/airavata/k8s/task/cleanup/Application.java
 delete mode 100644 airavata-kubernetes/modules/microservices/tasks/env-cleanup-task/src/main/java/org/apache/airavata/k8s/task/cleanup/messaging/KafkaReceiver.java
 delete mode 100644 airavata-kubernetes/modules/microservices/tasks/env-cleanup-task/src/main/java/org/apache/airavata/k8s/task/cleanup/messaging/KafkaSender.java
 delete mode 100644 airavata-kubernetes/modules/microservices/tasks/env-cleanup-task/src/main/java/org/apache/airavata/k8s/task/cleanup/messaging/SenderConfig.java
 delete mode 100644 airavata-kubernetes/modules/microservices/tasks/env-cleanup-task/src/main/java/org/apache/airavata/k8s/task/cleanup/service/TaskExecutionService.java
 delete mode 100644 airavata-kubernetes/modules/microservices/tasks/env-cleanup-task/src/main/resources/application.properties
 delete mode 100644 airavata-kubernetes/modules/microservices/tasks/env-cleanup-task/src/main/resources/application.yml
 delete mode 100644 airavata-kubernetes/modules/microservices/tasks/env-setup-task/pom.xml
 delete mode 100644 airavata-kubernetes/modules/microservices/tasks/env-setup-task/src/main/java/org/apache/airavata/k8s/task/env/setup/Application.java
 delete mode 100644 airavata-kubernetes/modules/microservices/tasks/env-setup-task/src/main/java/org/apache/airavata/k8s/task/env/setup/messaging/KafkaReceiver.java
 delete mode 100644 airavata-kubernetes/modules/microservices/tasks/env-setup-task/src/main/java/org/apache/airavata/k8s/task/env/setup/messaging/KafkaSender.java
 delete mode 100644 airavata-kubernetes/modules/microservices/tasks/env-setup-task/src/main/java/org/apache/airavata/k8s/task/env/setup/messaging/ReceiverConfig.java
 delete mode 100644 airavata-kubernetes/modules/microservices/tasks/env-setup-task/src/main/java/org/apache/airavata/k8s/task/env/setup/messaging/SenderConfig.java
 delete mode 100644 airavata-kubernetes/modules/microservices/tasks/env-setup-task/src/main/java/org/apache/airavata/k8s/task/env/setup/service/TaskExecutionService.java
 delete mode 100644 airavata-kubernetes/modules/microservices/tasks/env-setup-task/src/main/resources/application.properties
 delete mode 100644 airavata-kubernetes/modules/microservices/tasks/env-setup-task/src/main/resources/application.yml
 delete mode 100644 airavata-kubernetes/modules/microservices/tasks/ingress-staging-task/pom.xml
 delete mode 100644 airavata-kubernetes/modules/microservices/tasks/ingress-staging-task/src/main/java/org/apache/airavata/k8s/task/ingress/Application.java
 delete mode 100644 airavata-kubernetes/modules/microservices/tasks/ingress-staging-task/src/main/java/org/apache/airavata/k8s/task/ingress/messaging/KafkaReceiver.java
 delete mode 100644 airavata-kubernetes/modules/microservices/tasks/ingress-staging-task/src/main/java/org/apache/airavata/k8s/task/ingress/messaging/KafkaSender.java
 delete mode 100644 airavata-kubernetes/modules/microservices/tasks/ingress-staging-task/src/main/java/org/apache/airavata/k8s/task/ingress/messaging/ReceiverConfig.java
 delete mode 100644 airavata-kubernetes/modules/microservices/tasks/ingress-staging-task/src/main/java/org/apache/airavata/k8s/task/ingress/messaging/SenderConfig.java
 delete mode 100644 airavata-kubernetes/modules/microservices/tasks/ingress-staging-task/src/main/java/org/apache/airavata/k8s/task/ingress/service/TaskExecutionService.java
 delete mode 100644 airavata-kubernetes/modules/microservices/tasks/ingress-staging-task/src/main/resources/application.properties
 delete mode 100644 airavata-kubernetes/modules/microservices/tasks/job-submission-task/pom.xml
 delete mode 100644 airavata-kubernetes/modules/microservices/tasks/job-submission-task/src/main/java/org/apache/airavata/k8s/task/job/Application.java
 delete mode 100644 airavata-kubernetes/modules/microservices/tasks/job-submission-task/src/main/java/org/apache/airavata/k8s/task/job/messaging/KafkaReceiver.java
 delete mode 100644 airavata-kubernetes/modules/microservices/tasks/job-submission-task/src/main/java/org/apache/airavata/k8s/task/job/messaging/KafkaSender.java
 delete mode 100644 airavata-kubernetes/modules/microservices/tasks/job-submission-task/src/main/java/org/apache/airavata/k8s/task/job/messaging/SenderConfig.java
 delete mode 100644 airavata-kubernetes/modules/microservices/tasks/job-submission-task/src/main/java/org/apache/airavata/k8s/task/job/service/TaskExecutionService.java
 delete mode 100644 airavata-kubernetes/modules/microservices/tasks/job-submission-task/src/main/resources/application.properties
 rename airavata-kubernetes/modules/microservices/{tasks/egress-staging-task => workflow-scheduler}/pom.xml (82%)
 rename airavata-kubernetes/modules/microservices/{task-scheduler => workflow-scheduler}/src/main/java/org/apache/airavata/k8s/gfac/Application.java (100%)
 create mode 100644 airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/core/HelixWorkflowManager.java
 rename airavata-kubernetes/modules/microservices/{task-scheduler => workflow-scheduler}/src/main/java/org/apache/airavata/k8s/gfac/messaging/KafkaReceiver.java (74%)
 rename airavata-kubernetes/modules/microservices/{tasks/env-cleanup-task/src/main/java/org/apache/airavata/k8s/task/cleanup => workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac}/messaging/ReceiverConfig.java (87%)
 create mode 100644 airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/service/WorkerService.java
 create mode 100644 airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/resources/application.properties
 rename airavata-kubernetes/modules/microservices/{tasks/ingress-staging-task => workflow-scheduler}/src/main/resources/application.yml (100%)
 create mode 100644 airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/resources/log4j.properties
 create mode 100644 airavata-kubernetes/scripts/docker/jenkins-docker-kubectl-mvn/Dockerfile
 create mode 100644 airavata-kubernetes/scripts/k8s/ci-cd/Jenkinsfile
 create mode 100644 airavata-kubernetes/scripts/k8s/ci-cd/jenkins.yml
 create mode 100644 airavata-kubernetes/scripts/k8s/helix-controller/helix-controller-dep.yml
 rename airavata-kubernetes/scripts/k8s/task-scheduler/{task-secheduler-dep.yml => task-scheduler-dep.yml} (75%)
 create mode 100644 airavata-kubernetes/scripts/k8s/tasks/command-task/command-task-dep.yml
 create mode 100644 airavata-kubernetes/scripts/k8s/tasks/data-in-task/data-in-task-dep.yml
 create mode 100644 airavata-kubernetes/scripts/k8s/tasks/data-out-task/data-out-task-dep.yml
 delete mode 100644 airavata-kubernetes/scripts/k8s/tasks/egress-staging-task/egress-staging-task-dep.yml
 delete mode 100644 airavata-kubernetes/scripts/k8s/tasks/env-cleanup-task/env-cleanup-task-dep.yml
 delete mode 100644 airavata-kubernetes/scripts/k8s/tasks/env-setup-task/env-setup-task-dep.yml
 delete mode 100644 airavata-kubernetes/scripts/k8s/tasks/ingress-staging-task/ingress-staging-task-dep.yml
 delete mode 100644 airavata-kubernetes/scripts/k8s/tasks/job-submission-task/job-submission-task-dep.yml
 delete mode 100644 airavata-kubernetes/scripts/k8s/workflow-generator/workflow-generator-dep.yml
 create mode 100644 airavata-kubernetes/scripts/k8s/zookeeper-service.yml
 create mode 100644 airavata-kubernetes/web-console/src/app/components/workflow/create/create.component.ts
 create mode 100644 airavata-kubernetes/web-console/src/app/components/workflow/create/create.html
 create mode 100644 airavata-kubernetes/web-console/src/app/components/workflow/detail/detail.html
 create mode 100644 airavata-kubernetes/web-console/src/app/components/workflow/detail/workflow.detail.ts
 create mode 100644 airavata-kubernetes/web-console/src/app/components/workflow/list/list.html
 create mode 100644 airavata-kubernetes/web-console/src/app/components/workflow/list/workflow.list.component.ts
 create mode 100644 airavata-kubernetes/web-console/src/app/models/task/type/operation/operation.inport.type.model.ts
 create mode 100644 airavata-kubernetes/web-console/src/app/models/task/type/operation/operation.outport.type.model.ts
 create mode 100644 airavata-kubernetes/web-console/src/app/models/task/type/operation/operation.type.model.ts
 create mode 100644 airavata-kubernetes/web-console/src/app/models/task/type/task.input.type.model.ts
 create mode 100644 airavata-kubernetes/web-console/src/app/models/task/type/task.outport.model.ts
 create mode 100644 airavata-kubernetes/web-console/src/app/models/task/type/task.output.type.model.ts
 create mode 100644 airavata-kubernetes/web-console/src/app/models/task/type/task.type.model.ts
 create mode 100644 airavata-kubernetes/web-console/src/app/models/workflow/workflow.model.ts
 create mode 100644 airavata-kubernetes/web-console/src/app/services/util.service.ts
 create mode 100644 airavata-kubernetes/web-console/src/app/services/workflow.service.ts
 create mode 100644 airavata-kubernetes/web-console/src/assets/css/common.css
 create mode 100644 airavata-kubernetes/web-console/src/assets/css/explorer.css
 create mode 100755 airavata-kubernetes/web-console/src/assets/icons/copy.png
 create mode 100755 airavata-kubernetes/web-console/src/assets/icons/datain.png
 create mode 100755 airavata-kubernetes/web-console/src/assets/icons/dataout.png
 create mode 100755 airavata-kubernetes/web-console/src/assets/icons/http.png
 create mode 100755 airavata-kubernetes/web-console/src/assets/icons/parallel.png
 create mode 100755 airavata-kubernetes/web-console/src/assets/icons/s3.png
 create mode 100755 airavata-kubernetes/web-console/src/assets/icons/ssh.png
 create mode 100755 airavata-kubernetes/web-console/src/assets/icons/start.png
 create mode 100755 airavata-kubernetes/web-console/src/assets/icons/stop.png
 create mode 100644 airavata-kubernetes/web-console/src/assets/images/button.gif
 create mode 100644 airavata-kubernetes/web-console/src/assets/images/close.gif
 create mode 100644 airavata-kubernetes/web-console/src/assets/images/collapsed.gif
 create mode 100644 airavata-kubernetes/web-console/src/assets/images/error.gif
 create mode 100644 airavata-kubernetes/web-console/src/assets/images/expanded.gif
 create mode 100644 airavata-kubernetes/web-console/src/assets/images/maximize.gif
 create mode 100644 airavata-kubernetes/web-console/src/assets/images/minimize.gif
 create mode 100644 airavata-kubernetes/web-console/src/assets/images/normalize.gif
 create mode 100644 airavata-kubernetes/web-console/src/assets/images/point.gif
 create mode 100644 airavata-kubernetes/web-console/src/assets/images/resize.gif
 create mode 100644 airavata-kubernetes/web-console/src/assets/images/separator.gif
 create mode 100644 airavata-kubernetes/web-console/src/assets/images/submenu.gif
 create mode 100644 airavata-kubernetes/web-console/src/assets/images/transparent.gif
 create mode 100644 airavata-kubernetes/web-console/src/assets/images/warning.gif
 create mode 100644 airavata-kubernetes/web-console/src/assets/images/warning.png
 create mode 100644 airavata-kubernetes/web-console/src/assets/images/window-title.gif
 create mode 100644 airavata-kubernetes/web-console/src/assets/images/window.gif
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/components.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/editor/mxDefaultKeyHandler.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/editor/mxDefaultPopupMenu.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/editor/mxDefaultToolbar.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/editor/mxEditor.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/handler/mxCellHighlight.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/handler/mxCellMarker.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/handler/mxCellTracker.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/handler/mxConnectionHandler.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/handler/mxConstraintHandler.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/handler/mxEdgeHandler.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/handler/mxEdgeSegmentHandler.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/handler/mxElbowEdgeHandler.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/handler/mxGraphHandler.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/handler/mxHandle.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/handler/mxKeyHandler.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/handler/mxPanningHandler.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/handler/mxPopupMenuHandler.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/handler/mxRubberband.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/handler/mxSelectionCellsHandler.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/handler/mxTooltipHandler.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/handler/mxVertexHandler.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/index.txt
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/io/mxCellCodec.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/io/mxChildChangeCodec.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/io/mxCodec.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/io/mxCodecRegistry.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/io/mxDefaultKeyHandlerCodec.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/io/mxDefaultPopupMenuCodec.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/io/mxDefaultToolbarCodec.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/io/mxEditorCodec.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/io/mxGenericChangeCodec.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/io/mxGraphCodec.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/io/mxGraphViewCodec.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/io/mxModelCodec.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/io/mxObjectCodec.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/io/mxRootChangeCodec.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/io/mxStylesheetCodec.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/io/mxTerminalChangeCodec.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/model/mxGraphAbstractHierarchyCell.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/model/mxGraphHierarchyEdge.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/model/mxGraphHierarchyModel.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/model/mxGraphHierarchyNode.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/model/mxSwimlaneModel.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/mxHierarchicalLayout.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/mxSwimlaneLayout.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/stage/mxCoordinateAssignment.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/stage/mxHierarchicalLayoutStage.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/stage/mxMedianHybridCrossingReduction.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/stage/mxMinimumCycleRemover.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/stage/mxSwimlaneOrdering.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/layout/mxCircleLayout.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/layout/mxCompactTreeLayout.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/layout/mxCompositeLayout.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/layout/mxEdgeLabelLayout.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/layout/mxFastOrganicLayout.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/layout/mxGraphLayout.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/layout/mxParallelEdgeLayout.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/layout/mxPartitionLayout.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/layout/mxRadialTreeLayout.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/layout/mxStackLayout.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/model/mxCell.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/model/mxCellPath.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/model/mxGeometry.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/model/mxGraphModel.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/mxClient.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/shape/mxActor.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/shape/mxArrow.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/shape/mxArrowConnector.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/shape/mxCloud.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/shape/mxConnector.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/shape/mxCylinder.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/shape/mxDoubleEllipse.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/shape/mxEllipse.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/shape/mxHexagon.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/shape/mxImageShape.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/shape/mxLabel.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/shape/mxLine.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/shape/mxMarker.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/shape/mxPolyline.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/shape/mxRectangleShape.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/shape/mxRhombus.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/shape/mxShape.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/shape/mxStencil.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/shape/mxStencilRegistry.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/shape/mxSwimlane.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/shape/mxText.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/shape/mxTriangle.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/util/mxAbstractCanvas2D.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/util/mxAnimation.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/util/mxAutoSaveManager.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/util/mxClipboard.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/util/mxConstants.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/util/mxDictionary.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/util/mxDivResizer.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/util/mxDragSource.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/util/mxEffects.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/util/mxEvent.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/util/mxEventObject.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/util/mxEventSource.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/util/mxForm.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/util/mxGuide.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/util/mxImage.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/util/mxImageBundle.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/util/mxImageExport.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/util/mxLog.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/util/mxMorphing.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/util/mxMouseEvent.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/util/mxObjectIdentity.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/util/mxPanningManager.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/util/mxPoint.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/util/mxPopupMenu.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/util/mxRectangle.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/util/mxResources.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/util/mxSvgCanvas2D.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/util/mxToolbar.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/util/mxUndoManager.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/util/mxUndoableEdit.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/util/mxUrlConverter.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/util/mxUtils.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/util/mxVmlCanvas2D.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/util/mxWindow.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/util/mxXmlCanvas2D.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/util/mxXmlRequest.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/view/mxCellEditor.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/view/mxCellOverlay.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/view/mxCellRenderer.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/view/mxCellState.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/view/mxCellStatePreview.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/view/mxConnectionConstraint.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/view/mxEdgeStyle.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/view/mxGraph.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/view/mxGraphSelectionModel.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/view/mxGraphView.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/view/mxLayoutManager.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/view/mxMultiplicity.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/view/mxOutline.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/view/mxPerimeter.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/view/mxPrintPreview.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/view/mxStyleRegistry.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/view/mxStylesheet.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/view/mxSwimlaneManager.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/js/view/mxTemporaryCellStates.js
 create mode 100644 airavata-kubernetes/web-console/src/assets/resources/editor.txt
 create mode 100644 airavata-kubernetes/web-console/src/assets/resources/editor_de.txt
 create mode 100644 airavata-kubernetes/web-console/src/assets/resources/editor_zh.txt
 create mode 100644 airavata-kubernetes/web-console/src/assets/resources/graph.txt
 create mode 100644 airavata-kubernetes/web-console/src/assets/resources/graph_de.txt
 create mode 100644 airavata-kubernetes/web-console/src/assets/resources/graph_zh.txt
 create mode 100644 airavata-kubernetes/workflow-composer/index.html
 create mode 100644 airavata-kubernetes/workflow-composer/mxClient.js
 create mode 100644 airavata-kubernetes/workflow-composer/mxClient.min.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/css/common.css
 create mode 100644 airavata-kubernetes/workflow-composer/src/css/explorer.css
 create mode 100755 airavata-kubernetes/workflow-composer/src/icons/copy.png
 create mode 100755 airavata-kubernetes/workflow-composer/src/icons/http.png
 create mode 100755 airavata-kubernetes/workflow-composer/src/icons/parallel.png
 create mode 100755 airavata-kubernetes/workflow-composer/src/icons/s3.png
 create mode 100755 airavata-kubernetes/workflow-composer/src/icons/ssh.png
 create mode 100755 airavata-kubernetes/workflow-composer/src/icons/start.png
 create mode 100755 airavata-kubernetes/workflow-composer/src/icons/stop.png
 create mode 100644 airavata-kubernetes/workflow-composer/src/images/button.gif
 create mode 100644 airavata-kubernetes/workflow-composer/src/images/close.gif
 create mode 100644 airavata-kubernetes/workflow-composer/src/images/collapsed.gif
 create mode 100644 airavata-kubernetes/workflow-composer/src/images/error.gif
 create mode 100644 airavata-kubernetes/workflow-composer/src/images/expanded.gif
 create mode 100644 airavata-kubernetes/workflow-composer/src/images/maximize.gif
 create mode 100644 airavata-kubernetes/workflow-composer/src/images/minimize.gif
 create mode 100644 airavata-kubernetes/workflow-composer/src/images/normalize.gif
 create mode 100644 airavata-kubernetes/workflow-composer/src/images/point.gif
 create mode 100644 airavata-kubernetes/workflow-composer/src/images/resize.gif
 create mode 100644 airavata-kubernetes/workflow-composer/src/images/separator.gif
 create mode 100644 airavata-kubernetes/workflow-composer/src/images/submenu.gif
 create mode 100644 airavata-kubernetes/workflow-composer/src/images/transparent.gif
 create mode 100644 airavata-kubernetes/workflow-composer/src/images/warning.gif
 create mode 100644 airavata-kubernetes/workflow-composer/src/images/warning.png
 create mode 100644 airavata-kubernetes/workflow-composer/src/images/window-title.gif
 create mode 100644 airavata-kubernetes/workflow-composer/src/images/window.gif
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/components.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/editor/mxDefaultKeyHandler.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/editor/mxDefaultPopupMenu.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/editor/mxDefaultToolbar.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/editor/mxEditor.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/handler/mxCellHighlight.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/handler/mxCellMarker.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/handler/mxCellTracker.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/handler/mxConnectionHandler.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/handler/mxConstraintHandler.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/handler/mxEdgeHandler.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/handler/mxEdgeSegmentHandler.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/handler/mxElbowEdgeHandler.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/handler/mxGraphHandler.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/handler/mxHandle.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/handler/mxKeyHandler.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/handler/mxPanningHandler.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/handler/mxPopupMenuHandler.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/handler/mxRubberband.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/handler/mxSelectionCellsHandler.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/handler/mxTooltipHandler.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/handler/mxVertexHandler.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/index.txt
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/io/mxCellCodec.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/io/mxChildChangeCodec.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/io/mxCodec.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/io/mxCodecRegistry.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/io/mxDefaultKeyHandlerCodec.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/io/mxDefaultPopupMenuCodec.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/io/mxDefaultToolbarCodec.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/io/mxEditorCodec.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/io/mxGenericChangeCodec.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/io/mxGraphCodec.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/io/mxGraphViewCodec.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/io/mxModelCodec.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/io/mxObjectCodec.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/io/mxRootChangeCodec.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/io/mxStylesheetCodec.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/io/mxTerminalChangeCodec.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/model/mxGraphAbstractHierarchyCell.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/model/mxGraphHierarchyEdge.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/model/mxGraphHierarchyModel.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/model/mxGraphHierarchyNode.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/model/mxSwimlaneModel.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/mxHierarchicalLayout.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/mxSwimlaneLayout.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/stage/mxCoordinateAssignment.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/stage/mxHierarchicalLayoutStage.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/stage/mxMedianHybridCrossingReduction.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/stage/mxMinimumCycleRemover.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/stage/mxSwimlaneOrdering.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/layout/mxCircleLayout.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/layout/mxCompactTreeLayout.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/layout/mxCompositeLayout.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/layout/mxEdgeLabelLayout.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/layout/mxFastOrganicLayout.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/layout/mxGraphLayout.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/layout/mxParallelEdgeLayout.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/layout/mxPartitionLayout.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/layout/mxRadialTreeLayout.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/layout/mxStackLayout.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/model/mxCell.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/model/mxCellPath.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/model/mxGeometry.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/model/mxGraphModel.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/mxClient.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/shape/mxActor.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/shape/mxArrow.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/shape/mxArrowConnector.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/shape/mxCloud.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/shape/mxConnector.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/shape/mxCylinder.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/shape/mxDoubleEllipse.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/shape/mxEllipse.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/shape/mxHexagon.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/shape/mxImageShape.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/shape/mxLabel.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/shape/mxLine.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/shape/mxMarker.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/shape/mxPolyline.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/shape/mxRectangleShape.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/shape/mxRhombus.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/shape/mxShape.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/shape/mxStencil.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/shape/mxStencilRegistry.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/shape/mxSwimlane.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/shape/mxText.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/shape/mxTriangle.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/util/mxAbstractCanvas2D.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/util/mxAnimation.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/util/mxAutoSaveManager.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/util/mxClipboard.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/util/mxConstants.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/util/mxDictionary.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/util/mxDivResizer.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/util/mxDragSource.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/util/mxEffects.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/util/mxEvent.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/util/mxEventObject.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/util/mxEventSource.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/util/mxForm.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/util/mxGuide.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/util/mxImage.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/util/mxImageBundle.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/util/mxImageExport.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/util/mxLog.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/util/mxMorphing.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/util/mxMouseEvent.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/util/mxObjectIdentity.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/util/mxPanningManager.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/util/mxPoint.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/util/mxPopupMenu.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/util/mxRectangle.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/util/mxResources.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/util/mxSvgCanvas2D.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/util/mxToolbar.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/util/mxUndoManager.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/util/mxUndoableEdit.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/util/mxUrlConverter.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/util/mxUtils.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/util/mxVmlCanvas2D.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/util/mxWindow.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/util/mxXmlCanvas2D.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/util/mxXmlRequest.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/view/mxCellEditor.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/view/mxCellOverlay.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/view/mxCellRenderer.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/view/mxCellState.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/view/mxCellStatePreview.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/view/mxConnectionConstraint.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/view/mxEdgeStyle.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/view/mxGraph.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/view/mxGraphSelectionModel.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/view/mxGraphView.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/view/mxLayoutManager.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/view/mxMultiplicity.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/view/mxOutline.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/view/mxPerimeter.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/view/mxPrintPreview.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/view/mxStyleRegistry.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/view/mxStylesheet.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/view/mxSwimlaneManager.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/js/view/mxTemporaryCellStates.js
 create mode 100644 airavata-kubernetes/workflow-composer/src/resources/editor.txt
 create mode 100644 airavata-kubernetes/workflow-composer/src/resources/editor_de.txt
 create mode 100644 airavata-kubernetes/workflow-composer/src/resources/editor_zh.txt
 create mode 100644 airavata-kubernetes/workflow-composer/src/resources/graph.txt
 create mode 100644 airavata-kubernetes/workflow-composer/src/resources/graph_de.txt
 create mode 100644 airavata-kubernetes/workflow-composer/src/resources/graph_zh.txt

-- 
To stop receiving notification emails like this one, please contact
['"commits@airavata.apache.org" <co...@airavata.apache.org>'].

[airavata-sandbox] 09/19: Adding CI/CD support

Posted by sm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

smarru pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-sandbox.git

commit 0739614ae1daa4b4c2a7249627c405c79daab590
Author: dimuthu.upeksha2@gmail.com <Di...@1234>
AuthorDate: Sun Nov 26 06:20:47 2017 +0530

    Adding CI/CD support
---
 .../airavata/helix/api/HelixParticipant.java       | 23 ++++---
 .../airavata/helix/api/PropertyResolver.java       | 38 +++++++++++
 .../modules/microservices/helix-controller/pom.xml | 72 +++++++++++++++++++-
 .../org/apache/airavata/helix/HelixController.java | 23 +++++--
 .../src/main/resources/application.properties      |  3 +
 .../src/main/resources/log4j.properties            |  9 +++
 .../microservices/tasks/command-task/pom.xml       |  1 -
 .../airavata/helix/task/command/CommandTask.java   |  2 +-
 .../airavata/helix/task/command/Participant.java   | 18 ++---
 .../src/main/resources/application.properties      |  5 ++
 .../microservices/tasks/data-in-task/pom.xml       |  2 +-
 .../airavata/helix/task/datain/Participant.java    | 20 +++---
 .../src/main/resources/application.properties      |  5 ++
 .../microservices/tasks/data-out-task/pom.xml      |  2 +-
 .../task/{datain => }/dataout/DataOutputTask.java  |  2 +-
 .../task/{datain => }/dataout/Participant.java     | 20 +++---
 .../src/main/resources/application.properties      |  5 ++
 .../microservices/workflow-scheduler/pom.xml       | 13 ++++
 .../k8s/gfac/core/HelixWorkflowManager.java        | 14 +++-
 .../airavata/k8s/gfac/service/WorkerService.java   | 13 +++-
 .../src/main/resources/application.properties      |  6 +-
 .../docker/jenkins-docker-kubectl-mvn/Dockerfile   |  2 +
 .../scripts/k8s/api-server/api-server-dep.yml      |  4 +-
 airavata-kubernetes/scripts/k8s/ci-cd/Jenkinsfile  | 78 ++++++++++++++++++++++
 airavata-kubernetes/scripts/k8s/ci-cd/jenkins.yml  | 76 +++++++++++++++++++++
 .../k8s/helix-controller/helix-controller-dep.yml  | 24 +++++++
 .../k8s/task-scheduler/task-secheduler-dep.yml     |  3 +
 .../k8s/tasks/command-task/command-task-dep.yml    | 28 ++++++++
 .../k8s/tasks/data-in-task/data-in-task-dep.yml    | 28 ++++++++
 .../k8s/tasks/data-out-task/data-out-task-dep.yml  | 28 ++++++++
 .../egress-staging-task-dep.yml                    | 21 ------
 .../env-cleanup-task/env-cleanup-task-dep.yml      | 21 ------
 .../tasks/env-setup-task/env-setup-task-dep.yml    | 21 ------
 .../ingress-staging-task-dep.yml                   | 21 ------
 .../job-submission-task-dep.yml                    | 21 ------
 .../workflow-generator/workflow-generator-dep.yml  | 21 ------
 .../scripts/k8s/zookeeper-service.yml              | 24 +++++++
 37 files changed, 534 insertions(+), 183 deletions(-)

diff --git a/airavata-kubernetes/modules/helix-task-api/src/main/java/org/apache/airavata/helix/api/HelixParticipant.java b/airavata-kubernetes/modules/helix-task-api/src/main/java/org/apache/airavata/helix/api/HelixParticipant.java
index 77073ff..a2a56ca 100644
--- a/airavata-kubernetes/modules/helix-task-api/src/main/java/org/apache/airavata/helix/api/HelixParticipant.java
+++ b/airavata-kubernetes/modules/helix-task-api/src/main/java/org/apache/airavata/helix/api/HelixParticipant.java
@@ -16,6 +16,8 @@ import org.apache.log4j.LogManager;
 import org.apache.log4j.Logger;
 import org.springframework.web.client.RestTemplate;
 
+import java.io.File;
+import java.io.IOException;
 import java.util.List;
 import java.util.Map;
 
@@ -37,18 +39,19 @@ public abstract class HelixParticipant implements Runnable {
     private String apiServerUrl;
     private RestTemplate restTemplate;
 
-    public HelixParticipant(String zkAddress,
-                            String clusterName,
-                            String participantName,
-                            String taskTypeName,
-                            String apiServerUrl) {
+    public HelixParticipant(String propertyFile) throws IOException {
 
         logger.debug("Initializing Participant Node");
-        this.zkAddress = zkAddress;
-        this.clusterName = clusterName;
-        this.participantName = participantName;
-        this.taskTypeName = taskTypeName;
-        this.apiServerUrl = apiServerUrl;
+
+        PropertyResolver propertyResolver = new PropertyResolver();
+        propertyResolver.loadInputStream(this.getClass().getClassLoader().getResourceAsStream(propertyFile));
+
+        this.zkAddress = propertyResolver.get("zookeeper.connection.url");
+        this.clusterName = propertyResolver.get("helix.cluster.name");
+        this.participantName = propertyResolver.get("participant.name");
+        this.taskTypeName = propertyResolver.get("task.type.name");
+        this.apiServerUrl = propertyResolver.get("api.server.url");
+
         this.restTemplate = new RestTemplate();
     }
 
diff --git a/airavata-kubernetes/modules/helix-task-api/src/main/java/org/apache/airavata/helix/api/PropertyResolver.java b/airavata-kubernetes/modules/helix-task-api/src/main/java/org/apache/airavata/helix/api/PropertyResolver.java
new file mode 100644
index 0000000..b1f2e00
--- /dev/null
+++ b/airavata-kubernetes/modules/helix-task-api/src/main/java/org/apache/airavata/helix/api/PropertyResolver.java
@@ -0,0 +1,38 @@
+package org.apache.airavata.helix.api;
+
+import java.io.*;
+import java.util.Properties;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public class PropertyResolver {
+
+    private Properties properties = new Properties();
+
+    public void loadFromFile(File propertyFile) throws IOException {
+        properties = new Properties();
+        properties.load(new FileInputStream(propertyFile));
+    }
+
+    public void loadInputStream(InputStream inputStream) throws IOException {
+        properties = new Properties();
+        properties.load(inputStream);
+    }
+
+    public String get(String key) {
+        if (properties.containsKey(key)) {
+            if (System.getenv(key.replace(".", "_")) != null) {
+                return System.getenv(key.replace(".", "_"));
+            } else {
+                return properties.getProperty(key);
+            }
+        } else {
+            return null;
+        }
+    }
+
+}
diff --git a/airavata-kubernetes/modules/microservices/helix-controller/pom.xml b/airavata-kubernetes/modules/microservices/helix-controller/pom.xml
index 1613030..27afea4 100644
--- a/airavata-kubernetes/modules/microservices/helix-controller/pom.xml
+++ b/airavata-kubernetes/modules/microservices/helix-controller/pom.xml
@@ -12,8 +12,73 @@
 
     <artifactId>helix-controller</artifactId>
 
-    <dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.5.1</version>
+                <configuration>
+                    <source>${java.version}</source>
+                    <target>${java.version}</target>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <configuration>
+                    <archive>
+                        <manifest>
+                            <mainClass>org.apache.airavata.helix.HelixController</mainClass>
+                        </manifest>
+                    </archive>
+                    <descriptorRefs>
+                        <descriptorRef>jar-with-dependencies</descriptorRef>
+                    </descriptorRefs>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>make-assembly</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+
+            <!-- Create a docker image that runs the executable jar-->
+            <plugin>
+                <groupId>com.spotify</groupId>
+                <artifactId>docker-maven-plugin</artifactId>
+                <version>1.0.0</version>
+                <configuration>
+                    <imageName>${docker.image.prefix}/helix-controller</imageName>
+                    <baseImage>java:openjdk-8-jdk-alpine</baseImage>
+                    <exposes>
+                        <expose>8080</expose>
+                    </exposes>
+                    <entryPoint>["java","-jar","/${project.build.finalName}-jar-with-dependencies.jar"]</entryPoint>
+                    <resources>
+                        <resource>
+                            <targetPath>/</targetPath>
+                            <directory>${project.build.directory}</directory>
+                            <include>${project.build.finalName}-jar-with-dependencies.jar</include>
+                        </resource>
+                    </resources>
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>build</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
 
+    <dependencies>
         <dependency>
             <groupId>org.apache.helix</groupId>
             <artifactId>helix-core</artifactId>
@@ -31,6 +96,11 @@
             <groupId>org.apache.curator</groupId>
             <artifactId>curator-framework</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.apache.airavata</groupId>
+            <artifactId>helix-task-api</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
     </dependencies>
 
 </project>
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/microservices/helix-controller/src/main/java/org/apache/airavata/helix/HelixController.java b/airavata-kubernetes/modules/microservices/helix-controller/src/main/java/org/apache/airavata/helix/HelixController.java
index 50fd82b..1b08e56 100644
--- a/airavata-kubernetes/modules/microservices/helix-controller/src/main/java/org/apache/airavata/helix/HelixController.java
+++ b/airavata-kubernetes/modules/microservices/helix-controller/src/main/java/org/apache/airavata/helix/HelixController.java
@@ -1,9 +1,12 @@
 package org.apache.airavata.helix;
 
+import org.apache.airavata.helix.api.PropertyResolver;
 import org.apache.helix.controller.HelixControllerMain;
 import org.apache.log4j.LogManager;
 import org.apache.log4j.Logger;
 
+import java.io.File;
+import java.io.IOException;
 import java.util.concurrent.CountDownLatch;
 
 /**
@@ -24,10 +27,14 @@ public class HelixController implements Runnable {
     private CountDownLatch startLatch = new CountDownLatch(1);
     private CountDownLatch stopLatch = new CountDownLatch(1);
 
-    public HelixController(String zkAddress, String clusterName, String controllerName) {
-        this.clusterName = clusterName;
-        this.controllerName = controllerName;
-        this.zkAddress = zkAddress;
+    public HelixController(String propertyFile) throws IOException {
+
+        PropertyResolver propertyResolver = new PropertyResolver();
+        propertyResolver.loadInputStream(this.getClass().getClassLoader().getResourceAsStream(propertyFile));
+
+        this.clusterName = propertyResolver.get("helix.cluster.name");
+        this.controllerName = propertyResolver.get("helix.controller.name");
+        this.zkAddress = propertyResolver.get("zookeeper.connection.url");
     }
 
     public void run() {
@@ -77,7 +84,11 @@ public class HelixController implements Runnable {
     }
 
     public static void main(String args[]) {
-        HelixController helixController = new HelixController("localhost:2199", "AiravataDemoCluster", "AiravataController");
-        helixController.start();
+        try {
+            HelixController helixController = new HelixController("application.properties");
+            helixController.start();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
     }
 }
diff --git a/airavata-kubernetes/modules/microservices/helix-controller/src/main/resources/application.properties b/airavata-kubernetes/modules/microservices/helix-controller/src/main/resources/application.properties
new file mode 100644
index 0000000..bc3c9db
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/helix-controller/src/main/resources/application.properties
@@ -0,0 +1,3 @@
+zookeeper.connection.url=localhost:2199
+helix.cluster.name=AiravataDemoCluster
+helix.controller.name=AiravataController
diff --git a/airavata-kubernetes/modules/microservices/helix-controller/src/main/resources/log4j.properties b/airavata-kubernetes/modules/microservices/helix-controller/src/main/resources/log4j.properties
new file mode 100644
index 0000000..5e31e3c
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/helix-controller/src/main/resources/log4j.properties
@@ -0,0 +1,9 @@
+# Set root logger level to DEBUG and its only appender to A1.
+log4j.rootLogger=INFO, A1
+
+# A1 is set to be a ConsoleAppender.
+log4j.appender.A1=org.apache.log4j.ConsoleAppender
+
+# A1 uses PatternLayout.
+log4j.appender.A1.layout=org.apache.log4j.PatternLayout
+log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/microservices/tasks/command-task/pom.xml b/airavata-kubernetes/modules/microservices/tasks/command-task/pom.xml
index a67fefc..bafde96 100644
--- a/airavata-kubernetes/modules/microservices/tasks/command-task/pom.xml
+++ b/airavata-kubernetes/modules/microservices/tasks/command-task/pom.xml
@@ -84,7 +84,6 @@
             <artifactId>helix-task-api</artifactId>
             <version>1.0-SNAPSHOT</version>
         </dependency>
-
         <dependency>
             <groupId>org.apache.helix</groupId>
             <artifactId>helix-core</artifactId>
diff --git a/airavata-kubernetes/modules/microservices/tasks/command-task/src/main/java/org/apache/airavata/helix/task/command/CommandTask.java b/airavata-kubernetes/modules/microservices/tasks/command-task/src/main/java/org/apache/airavata/helix/task/command/CommandTask.java
index 16c600e..866c2c7 100644
--- a/airavata-kubernetes/modules/microservices/tasks/command-task/src/main/java/org/apache/airavata/helix/task/command/CommandTask.java
+++ b/airavata-kubernetes/modules/microservices/tasks/command-task/src/main/java/org/apache/airavata/helix/task/command/CommandTask.java
@@ -55,7 +55,7 @@ public class CommandTask extends AbstractTask {
             String finalCommand = command + (arguments != null ? arguments : "") + stdOutSuffix;
 
             System.out.println("Executing command " + finalCommand);
-
+            Thread.sleep(200000);
             ExecutionResult executionResult = fetchComputeResourceOperation(computeResource).executeCommand(finalCommand);
 
             if (executionResult.getExitStatus() == 0) {
diff --git a/airavata-kubernetes/modules/microservices/tasks/command-task/src/main/java/org/apache/airavata/helix/task/command/Participant.java b/airavata-kubernetes/modules/microservices/tasks/command-task/src/main/java/org/apache/airavata/helix/task/command/Participant.java
index 742ed88..3b7e55e 100644
--- a/airavata-kubernetes/modules/microservices/tasks/command-task/src/main/java/org/apache/airavata/helix/task/command/Participant.java
+++ b/airavata-kubernetes/modules/microservices/tasks/command-task/src/main/java/org/apache/airavata/helix/task/command/Participant.java
@@ -6,6 +6,7 @@ import org.apache.helix.task.Task;
 import org.apache.helix.task.TaskCallbackContext;
 import org.apache.helix.task.TaskFactory;
 
+import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -17,8 +18,9 @@ import java.util.Map;
  */
 public class Participant extends HelixParticipant {
 
-    public Participant(String zkAddress, String clusterName, String participantName, String taskTypeName, String apiServerUrl) {
-        super(zkAddress, clusterName, participantName, taskTypeName, apiServerUrl);
+
+    public Participant(String propertyFile) throws IOException {
+        super(propertyFile);
     }
 
     @Override
@@ -43,11 +45,11 @@ public class Participant extends HelixParticipant {
     }
 
     public static void main(String args[]) {
-        HelixParticipant participant = new Participant(
-                "localhost:2199",
-                "AiravataDemoCluster",
-                "command-p1", CommandTask.NAME,
-                "localhost:8080");
-        new Thread(participant).start();
+        try {
+            HelixParticipant participant = new Participant("application.properties");
+            new Thread(participant).start();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
     }
 }
diff --git a/airavata-kubernetes/modules/microservices/tasks/command-task/src/main/resources/application.properties b/airavata-kubernetes/modules/microservices/tasks/command-task/src/main/resources/application.properties
new file mode 100644
index 0000000..92292ab
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/tasks/command-task/src/main/resources/application.properties
@@ -0,0 +1,5 @@
+api.server.url=api-server.default.svc.cluster.local:8080
+zookeeper.connection.url=localhost:2199
+helix.cluster.name=AiravataDemoCluster
+participant.name=command-p2
+task.type.name=COMMAND
diff --git a/airavata-kubernetes/modules/microservices/tasks/data-in-task/pom.xml b/airavata-kubernetes/modules/microservices/tasks/data-in-task/pom.xml
index d6c2fe6..a3503ab 100644
--- a/airavata-kubernetes/modules/microservices/tasks/data-in-task/pom.xml
+++ b/airavata-kubernetes/modules/microservices/tasks/data-in-task/pom.xml
@@ -29,7 +29,7 @@
                 <configuration>
                     <archive>
                         <manifest>
-                            <mainClass>org.apache.airavata.helix.task.command.Participant</mainClass>
+                            <mainClass>org.apache.airavata.helix.task.datain.Participant</mainClass>
                         </manifest>
                     </archive>
                     <descriptorRefs>
diff --git a/airavata-kubernetes/modules/microservices/tasks/data-in-task/src/main/java/org/apache/airavata/helix/task/datain/Participant.java b/airavata-kubernetes/modules/microservices/tasks/data-in-task/src/main/java/org/apache/airavata/helix/task/datain/Participant.java
index cfbe86a..d804352 100644
--- a/airavata-kubernetes/modules/microservices/tasks/data-in-task/src/main/java/org/apache/airavata/helix/task/datain/Participant.java
+++ b/airavata-kubernetes/modules/microservices/tasks/data-in-task/src/main/java/org/apache/airavata/helix/task/datain/Participant.java
@@ -6,6 +6,7 @@ import org.apache.helix.task.Task;
 import org.apache.helix.task.TaskCallbackContext;
 import org.apache.helix.task.TaskFactory;
 
+import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -17,10 +18,9 @@ import java.util.Map;
  */
 public class Participant extends HelixParticipant {
 
-    public Participant(String zkAddress, String clusterName, String participantName,
-                       String taskTypeName,
-                       String apiServerUrl) {
-        super(zkAddress, clusterName, participantName, taskTypeName, apiServerUrl);
+
+    public Participant(String propertyFile) throws IOException {
+        super(propertyFile);
     }
 
     @Override
@@ -45,11 +45,11 @@ public class Participant extends HelixParticipant {
     }
 
     public static void main(String args[]) {
-        HelixParticipant participant = new Participant(
-                "localhost:2199",
-                "AiravataDemoCluster",
-                "data-in-p1", DataInputTask.NAME,
-                "localhost:8080");
-        new Thread(participant).start();
+        try {
+            HelixParticipant participant = new Participant("application.properties");
+            new Thread(participant).start();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
     }
 }
diff --git a/airavata-kubernetes/modules/microservices/tasks/data-in-task/src/main/resources/application.properties b/airavata-kubernetes/modules/microservices/tasks/data-in-task/src/main/resources/application.properties
new file mode 100644
index 0000000..3f98946
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/tasks/data-in-task/src/main/resources/application.properties
@@ -0,0 +1,5 @@
+api.server.url=api-server.default.svc.cluster.local:8080
+zookeeper.connection.url=localhost:2199
+helix.cluster.name=AiravataDemoCluster
+participant.name=data-in-p1
+task.type.name=DATA_INPUT
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/microservices/tasks/data-out-task/pom.xml b/airavata-kubernetes/modules/microservices/tasks/data-out-task/pom.xml
index 80c9e87..4f27e24 100644
--- a/airavata-kubernetes/modules/microservices/tasks/data-out-task/pom.xml
+++ b/airavata-kubernetes/modules/microservices/tasks/data-out-task/pom.xml
@@ -29,7 +29,7 @@
                 <configuration>
                     <archive>
                         <manifest>
-                            <mainClass>org.apache.airavata.helix.task.command.Participant</mainClass>
+                            <mainClass>org.apache.airavata.helix.task.dataout.Participant</mainClass>
                         </manifest>
                     </archive>
                     <descriptorRefs>
diff --git a/airavata-kubernetes/modules/microservices/tasks/data-out-task/src/main/java/org/apache/airavata/helix/task/datain/dataout/DataOutputTask.java b/airavata-kubernetes/modules/microservices/tasks/data-out-task/src/main/java/org/apache/airavata/helix/task/dataout/DataOutputTask.java
similarity index 98%
rename from airavata-kubernetes/modules/microservices/tasks/data-out-task/src/main/java/org/apache/airavata/helix/task/datain/dataout/DataOutputTask.java
rename to airavata-kubernetes/modules/microservices/tasks/data-out-task/src/main/java/org/apache/airavata/helix/task/dataout/DataOutputTask.java
index 8426c90..ad6123f 100644
--- a/airavata-kubernetes/modules/microservices/tasks/data-out-task/src/main/java/org/apache/airavata/helix/task/datain/dataout/DataOutputTask.java
+++ b/airavata-kubernetes/modules/microservices/tasks/data-out-task/src/main/java/org/apache/airavata/helix/task/dataout/DataOutputTask.java
@@ -1,4 +1,4 @@
-package org.apache.airavata.helix.task.datain.dataout;
+package org.apache.airavata.helix.task.dataout;
 
 import org.apache.airavata.helix.api.AbstractTask;
 import org.apache.airavata.k8s.api.resources.compute.ComputeResource;
diff --git a/airavata-kubernetes/modules/microservices/tasks/data-out-task/src/main/java/org/apache/airavata/helix/task/datain/dataout/Participant.java b/airavata-kubernetes/modules/microservices/tasks/data-out-task/src/main/java/org/apache/airavata/helix/task/dataout/Participant.java
similarity index 66%
rename from airavata-kubernetes/modules/microservices/tasks/data-out-task/src/main/java/org/apache/airavata/helix/task/datain/dataout/Participant.java
rename to airavata-kubernetes/modules/microservices/tasks/data-out-task/src/main/java/org/apache/airavata/helix/task/dataout/Participant.java
index 8177eb4..a26c654 100644
--- a/airavata-kubernetes/modules/microservices/tasks/data-out-task/src/main/java/org/apache/airavata/helix/task/datain/dataout/Participant.java
+++ b/airavata-kubernetes/modules/microservices/tasks/data-out-task/src/main/java/org/apache/airavata/helix/task/dataout/Participant.java
@@ -1,4 +1,4 @@
-package org.apache.airavata.helix.task.datain.dataout;
+package org.apache.airavata.helix.task.dataout;
 
 import org.apache.airavata.helix.api.HelixParticipant;
 import org.apache.airavata.k8s.api.resources.task.type.TaskTypeResource;
@@ -6,6 +6,7 @@ import org.apache.helix.task.Task;
 import org.apache.helix.task.TaskCallbackContext;
 import org.apache.helix.task.TaskFactory;
 
+import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -17,9 +18,8 @@ import java.util.Map;
  */
 public class Participant extends HelixParticipant {
 
-    public Participant(String zkAddress, String clusterName, String participantName,
-                       String taskTypeName, String apiServerUrl) {
-        super(zkAddress, clusterName, participantName, taskTypeName, apiServerUrl);
+    public Participant(String propertyFile) throws IOException {
+        super(propertyFile);
     }
 
     @Override
@@ -44,11 +44,11 @@ public class Participant extends HelixParticipant {
     }
 
     public static void main(String args[]) {
-        HelixParticipant participant = new Participant(
-                "localhost:2199",
-                "AiravataDemoCluster",
-                "data-out-p1", DataOutputTask.NAME,
-                "localhost:8080");
-        new Thread(participant).start();
+        try {
+            HelixParticipant participant = new Participant("application.properties");
+            new Thread(participant).start();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
     }
 }
diff --git a/airavata-kubernetes/modules/microservices/tasks/data-out-task/src/main/resources/application.properties b/airavata-kubernetes/modules/microservices/tasks/data-out-task/src/main/resources/application.properties
new file mode 100644
index 0000000..37109d6
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/tasks/data-out-task/src/main/resources/application.properties
@@ -0,0 +1,5 @@
+api.server.url=api-server.default.svc.cluster.local:8080
+zookeeper.connection.url=localhost:2199
+helix.cluster.name=AiravataDemoCluster
+participant.name=data-out-p1
+task.type.name=DATA_OUTPUT
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/microservices/workflow-scheduler/pom.xml b/airavata-kubernetes/modules/microservices/workflow-scheduler/pom.xml
index 245716a..66974fb 100644
--- a/airavata-kubernetes/modules/microservices/workflow-scheduler/pom.xml
+++ b/airavata-kubernetes/modules/microservices/workflow-scheduler/pom.xml
@@ -45,9 +45,17 @@
             <version>1.0-SNAPSHOT</version>
         </dependency>
 
+
+
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.springframework.boot</groupId>
+                    <artifactId>spring-boot-starter-logging</artifactId>
+                </exclusion>
+            </exclusions>
         </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
@@ -59,6 +67,11 @@
         </dependency>
 
         <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>jcl-over-slf4j</artifactId>
+            <version>1.7.25</version>
+        </dependency>
+        <dependency>
             <groupId>org.apache.helix</groupId>
             <artifactId>helix-core</artifactId>
             <version>0.6.7</version>
diff --git a/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/core/HelixWorkflowManager.java b/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/core/HelixWorkflowManager.java
index 36f66b4..53778ac 100644
--- a/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/core/HelixWorkflowManager.java
+++ b/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/core/HelixWorkflowManager.java
@@ -38,20 +38,28 @@ public class HelixWorkflowManager {
     private final RestTemplate restTemplate;
     private String apiServerUrl;
 
+    private String zkConnectionString;
+    private String helixClusterName;
+    private String instanceName;
+
     public HelixWorkflowManager(long processId, List<TaskResource> tasks, Map<Long, Long> edgeMap,
                                 KafkaSender kafkaSender,
-                                RestTemplate restTemplate, String apiServerUrl) {
+                                RestTemplate restTemplate, String apiServerUrl, String zkConnectionString,
+                                String helixClusterName, String instanceName) {
         this.processId = processId;
         this.tasks = tasks;
         this.edgeMap = edgeMap;
         this.kafkaSender = kafkaSender;
         this.restTemplate = restTemplate;
         this.apiServerUrl = apiServerUrl;
+        this.zkConnectionString = zkConnectionString;
+        this.helixClusterName = helixClusterName;
+        this.instanceName = instanceName;
     }
 
     public void launchWorkflow() {
-        org.apache.helix.HelixManager helixManager = HelixManagerFactory.getZKHelixManager("AiravataDemoCluster", "Admin",
-                InstanceType.SPECTATOR, "localhost:2199");
+        org.apache.helix.HelixManager helixManager = HelixManagerFactory.getZKHelixManager(helixClusterName, instanceName,
+                InstanceType.SPECTATOR, zkConnectionString);
 
         try {
             updateProcessStatus(ProcessStatusResource.State.CREATED);
diff --git a/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/service/WorkerService.java b/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/service/WorkerService.java
index 1ccf1d1..606c20a 100644
--- a/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/service/WorkerService.java
+++ b/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/service/WorkerService.java
@@ -54,6 +54,15 @@ public class WorkerService {
     @Value("${api.server.url}")
     private String apiServerUrl;
 
+    @Value("${zookeeper.connection.url}")
+    private String zkConnectionString;
+
+    @Value("${helix.cluster.name}")
+    private String helixClusterName;
+
+    @Value("${instance.name}")
+    private String instanceName;
+
     public WorkerService(RestTemplate restTemplate, KafkaSender kafkaSender) {
         this.restTemplate = restTemplate;
         this.kafkaSender = kafkaSender;
@@ -83,7 +92,9 @@ public class WorkerService {
 
         //processLifecycleStore.put(processId, manager);
 
-        final HelixWorkflowManager helixWorkflowManager = new HelixWorkflowManager(processId, taskResources, edgeMap, kafkaSender, restTemplate, apiServerUrl);
+        final HelixWorkflowManager helixWorkflowManager = new HelixWorkflowManager(processId, taskResources, edgeMap,
+                kafkaSender, restTemplate, apiServerUrl,
+                zkConnectionString, helixClusterName, instanceName);
 
         executorService.execute(new Runnable() {
             @Override
diff --git a/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/resources/application.properties b/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/resources/application.properties
index c23a904..41b2847 100644
--- a/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/resources/application.properties
+++ b/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/resources/application.properties
@@ -2,4 +2,8 @@ server.port = 8195
 api.server.url = api-server.default.svc.cluster.local:8080
 scheduler.topic.name = airavata-scheduler
 scheduler.group.name = task-scheduler
-task.event.topic.name = airavata-task-event
\ No newline at end of file
+task.event.topic.name = airavata-task-event
+
+zookeeper.connection.url=localhost:2199
+helix.cluster.name=AiravataDemoCluster
+instance.name=Admin
\ No newline at end of file
diff --git a/airavata-kubernetes/scripts/docker/jenkins-docker-kubectl-mvn/Dockerfile b/airavata-kubernetes/scripts/docker/jenkins-docker-kubectl-mvn/Dockerfile
new file mode 100644
index 0000000..544a9d3
--- /dev/null
+++ b/airavata-kubernetes/scripts/docker/jenkins-docker-kubectl-mvn/Dockerfile
@@ -0,0 +1,2 @@
+FROM chadmoon/jenkins-docker-kubectl:latest
+ADD apache-maven-3.5.2 /opt/maven
\ No newline at end of file
diff --git a/airavata-kubernetes/scripts/k8s/api-server/api-server-dep.yml b/airavata-kubernetes/scripts/k8s/api-server/api-server-dep.yml
index 95c6502..8f61723 100644
--- a/airavata-kubernetes/scripts/k8s/api-server/api-server-dep.yml
+++ b/airavata-kubernetes/scripts/k8s/api-server/api-server-dep.yml
@@ -18,9 +18,9 @@ spec:
       - name: api-server
         env:
         - name: spring_datasource_username
-          value: airavata-user
+          value: root
         - name: spring_datasource_password
-          value: password
+          value: fun123
         image: dimuthuupe/api-server:v1.0
         ports:
         - containerPort: 8080
\ No newline at end of file
diff --git a/airavata-kubernetes/scripts/k8s/ci-cd/Jenkinsfile b/airavata-kubernetes/scripts/k8s/ci-cd/Jenkinsfile
new file mode 100644
index 0000000..aaf3a04
--- /dev/null
+++ b/airavata-kubernetes/scripts/k8s/ci-cd/Jenkinsfile
@@ -0,0 +1,78 @@
+node {
+
+    checkout scm
+
+    env.DOCKER_API_VERSION="1.23"
+
+    sh "git rev-parse --short HEAD > commit-id"
+
+    tag = readFile('commit-id').replace("\n", "").replace("\r", "")
+    registryHost = "192.168.1.114:5000"
+
+    api_server_imageName = "${registryHost}/api-server:${tag}"
+    env.BUILDIMG_API_SERVER=api_server_imageName
+
+    event_sink_imageName = "${registryHost}/event-sink:${tag}"
+    env.BUILDIMG_EVENT_SINK=event_sink_imageName
+
+    helix_controller_imageName = "${registryHost}/helix-controller:${tag}"
+    env.BUILDIMG_HELIX_CONTROLLER=helix_controller_imageName
+
+    task_scheduler_imageName = "${registryHost}/task-scheduler:${tag}"
+    env.BUILDIMG_TASK_SCHEDULER=task_scheduler_imageName
+
+    command_task_imageName = "${registryHost}/command-task:${tag}"
+    env.BUILDIMG_COMMAND_TASK=command_task_imageName
+
+    data_in_task_imageName = "${registryHost}/data-in-task:${tag}"
+    env.BUILDIMG_DATA_IN_TASK=data_in_task_imageName
+
+    data_out_task_imageName = "${registryHost}/data-out-task:${tag}"
+    env.BUILDIMG_DATA_OUT_TASK=data_out_task_imageName
+
+    stage "Build"
+        sh "/opt/maven/bin/mvn -f modules/api-resource/ clean install"
+        sh "/opt/maven/bin/mvn -f modules/compute-resource-api/ clean install"
+        sh "/opt/maven/bin/mvn -f modules/helix-task-api/ clean install"
+        sh "/opt/maven/bin/mvn -f modules/microservices/api-server/ clean install docker:build -DdockerImageTags=${tag} -Ddocker.image.prefix=${registryHost}"
+        sh "/opt/maven/bin/mvn -f modules/microservices/event-sink/ clean install docker:build -DdockerImageTags=${tag} -Ddocker.image.prefix=${registryHost}"
+        sh "/opt/maven/bin/mvn -f modules/microservices/helix-controller/ clean install docker:build -DdockerImageTags=${tag} -Ddocker.image.prefix=${registryHost}"
+        sh "/opt/maven/bin/mvn -f modules/microservices/workflow-scheduler/ clean install docker:build -DdockerImageTags=${tag} -Ddocker.image.prefix=${registryHost}"
+        sh "/opt/maven/bin/mvn -f modules/microservices/tasks/command-task/ clean install docker:build -DdockerImageTags=${tag} -Ddocker.image.prefix=${registryHost}"
+        sh "/opt/maven/bin/mvn -f modules/microservices/tasks/data-in-task/ clean install docker:build -DdockerImageTags=${tag} -Ddocker.image.prefix=${registryHost}"
+        sh "/opt/maven/bin/mvn -f modules/microservices/tasks/data-out-task/ clean install docker:build -DdockerImageTags=${tag} -Ddocker.image.prefix=${registryHost}"
+
+    stage "Push"
+
+        sh "docker push ${api_server_imageName}"
+        sh "docker push ${event_sink_imageName}"
+        sh "docker push ${helix_controller_imageName}"
+        sh "docker push ${task_scheduler_imageName}"
+        sh "docker push ${command_task_imageName}"
+        sh "docker push ${data_in_task_imageName}"
+        sh "docker push ${data_out_task_imageName}"
+
+    stage "Deploy"
+
+        sh "sed 's#dimuthuupe/api-server:v1.0#'$BUILDIMG_API_SERVER'#' scripts/k8s/api-server/api-server-dep.yaml | kubectl apply -f -"
+        sh "kubectl rollout status deployment/api-server"
+
+        sh "sed 's#dimuthuupe/event-sink:v1.0#'BUILDIMG_EVENT_SINK'#' scripts/k8s/event-sink/event-sink-dep.yaml | kubectl apply -f -"
+        sh "kubectl rollout status deployment/event-sink"
+
+        sh "sed 's#dimuthuupe/helix-controller:v1.0#'BUILDIMG_HELIX_CONTROLLER'#' scripts/k8s/helix-controller/helix-controller-dep.yaml | kubectl apply -f -"
+        sh "kubectl rollout status deployment/helix-controller"
+
+        sh "sed 's#dimuthuupe/task-scheduler:v1.0#'BUILDIMG_TASK_SCHEDULER'#' scripts/k8s/task-scheduler/task-scheduler-dep.yaml | kubectl apply -f -"
+        sh "kubectl rollout status deployment/task-scheduler"
+
+        sh "sed 's#dimuthuupe/command-task:v1.0#'BUILDIMG_COMMAND_TASK'#' scripts/k8s/command-task/command-task-dep.yaml | kubectl apply -f -"
+        sh "kubectl rollout status deployment/command-task"
+
+        sh "sed 's#dimuthuupe/data-in-task:v1.0#'BUILDIMG_DATA_IN_TASK'#' scripts/k8s/data-in-task/data-in-task-dep.yaml | kubectl apply -f -"
+        sh "kubectl rollout status deployment/data-in-task"
+
+        sh "sed 's#dimuthuupe/data-out-task:v1.0#'BUILDIMG_DATA_OUT_TASK'#' scripts/k8s/data-out-task/data-out-task-dep.yaml | kubectl apply -f -"
+        sh "kubectl rollout status deployment/data-out-task"
+
+}
\ No newline at end of file
diff --git a/airavata-kubernetes/scripts/k8s/ci-cd/jenkins.yml b/airavata-kubernetes/scripts/k8s/ci-cd/jenkins.yml
new file mode 100644
index 0000000..6f98fc4
--- /dev/null
+++ b/airavata-kubernetes/scripts/k8s/ci-cd/jenkins.yml
@@ -0,0 +1,76 @@
+kind: PersistentVolume
+apiVersion: v1
+metadata:
+  name: jenkins
+  labels:
+    type: local
+spec:
+  capacity:
+    storage: 2Gi
+  accessModes:
+    - ReadWriteOnce
+  hostPath:
+    path: "/data/jenkins/"
+
+---
+kind: PersistentVolumeClaim
+apiVersion: v1
+metadata:
+  name: jenkins-claim
+spec:
+  accessModes:
+    - ReadWriteOnce
+  resources:
+    requests:
+      storage: 2Gi
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: jenkins
+  labels:
+    app: jenkins
+spec:
+  ports:
+    - port: 80
+      targetPort: 8080
+  selector:
+    app: jenkins
+    tier: jenkins
+  type: NodePort
+---
+apiVersion: extensions/v1beta1
+kind: Deployment
+metadata:
+  name: jenkins
+  labels:
+    app: jenkins
+spec:
+  strategy:
+    type: Recreate
+  template:
+    metadata:
+      labels:
+        app: jenkins
+        tier: jenkins
+    spec:
+      containers:
+      - image: dimuthuupe/jenkins-docker-kubectl:v1
+        name: jenkins
+        securityContext:
+          privileged: true
+        ports:
+        - containerPort: 8080
+          name: jenkins
+        volumeMounts:
+        - name: jenkins-persistent-storage
+          mountPath: /root/.jenkins
+        - name: docker
+          mountPath: /var/run/docker.sock
+      volumes:
+      - name: docker
+        hostPath:
+          path: /var/run/docker.sock
+      - name: jenkins-persistent-storage
+        persistentVolumeClaim:
+          claimName: jenkins-claim
\ No newline at end of file
diff --git a/airavata-kubernetes/scripts/k8s/helix-controller/helix-controller-dep.yml b/airavata-kubernetes/scripts/k8s/helix-controller/helix-controller-dep.yml
new file mode 100644
index 0000000..6dfad3e
--- /dev/null
+++ b/airavata-kubernetes/scripts/k8s/helix-controller/helix-controller-dep.yml
@@ -0,0 +1,24 @@
+apiVersion: apps/v1beta2 # for versions before 1.7.0 use apps/v1beta1
+kind: Deployment
+metadata:
+  name: helix-controller
+  labels:
+    app: helix-controller
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: helix-controller
+  template:
+    metadata:
+      labels:
+        app: helix-controller
+    spec:
+      containers:
+      - name: helix-controller
+        image: dimuthuupe/helix-controller:v1.0
+        env:
+        - name: zookeeper_connection_url
+          value: zk.default.svc.cluster.local:2199
+        ports:
+        - containerPort: 8080
\ No newline at end of file
diff --git a/airavata-kubernetes/scripts/k8s/task-scheduler/task-secheduler-dep.yml b/airavata-kubernetes/scripts/k8s/task-scheduler/task-secheduler-dep.yml
index 283a40b..c731bfb 100644
--- a/airavata-kubernetes/scripts/k8s/task-scheduler/task-secheduler-dep.yml
+++ b/airavata-kubernetes/scripts/k8s/task-scheduler/task-secheduler-dep.yml
@@ -17,5 +17,8 @@ spec:
       containers:
       - name: task-scheduler
         image: dimuthuupe/task-scheduler:v1.0
+        env:
+        - name: zookeeper_connection_url
+          value: zk.default.svc.cluster.local:2199
         ports:
         - containerPort: 8080
\ No newline at end of file
diff --git a/airavata-kubernetes/scripts/k8s/tasks/command-task/command-task-dep.yml b/airavata-kubernetes/scripts/k8s/tasks/command-task/command-task-dep.yml
new file mode 100644
index 0000000..04bc4a9
--- /dev/null
+++ b/airavata-kubernetes/scripts/k8s/tasks/command-task/command-task-dep.yml
@@ -0,0 +1,28 @@
+apiVersion: apps/v1beta2 # for versions before 1.7.0 use apps/v1beta1
+kind: Deployment
+metadata:
+  name: command-task
+  labels:
+    app: command-task
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: command-task
+  template:
+    metadata:
+      labels:
+        app: command-task
+    spec:
+      containers:
+      - name: command-task
+        image: dimuthuupe/command-task:v1.0
+        env:
+        - name: zookeeper_connection_url
+          value: zk.default.svc.cluster.local:2199
+        - name: participant_name
+          valueFrom:
+            fieldRef:
+              fieldPath: metadata.name
+        ports:
+        - containerPort: 8080
\ No newline at end of file
diff --git a/airavata-kubernetes/scripts/k8s/tasks/data-in-task/data-in-task-dep.yml b/airavata-kubernetes/scripts/k8s/tasks/data-in-task/data-in-task-dep.yml
new file mode 100644
index 0000000..ca5dfd9
--- /dev/null
+++ b/airavata-kubernetes/scripts/k8s/tasks/data-in-task/data-in-task-dep.yml
@@ -0,0 +1,28 @@
+apiVersion: apps/v1beta2 # for versions before 1.7.0 use apps/v1beta1
+kind: Deployment
+metadata:
+  name: data-in-task
+  labels:
+    app: data-in-task
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: data-in-task
+  template:
+    metadata:
+      labels:
+        app: data-in-task
+    spec:
+      containers:
+      - name: data-in-task
+        image: dimuthuupe/data-in-task:v1.0
+        env:
+        - name: zookeeper_connection_url
+          value: zk.default.svc.cluster.local:2199
+        - name: participant_name
+          valueFrom:
+            fieldRef:
+              fieldPath: metadata.name
+        ports:
+        - containerPort: 8080
\ No newline at end of file
diff --git a/airavata-kubernetes/scripts/k8s/tasks/data-out-task/data-out-task-dep.yml b/airavata-kubernetes/scripts/k8s/tasks/data-out-task/data-out-task-dep.yml
new file mode 100644
index 0000000..8ab80fb
--- /dev/null
+++ b/airavata-kubernetes/scripts/k8s/tasks/data-out-task/data-out-task-dep.yml
@@ -0,0 +1,28 @@
+apiVersion: apps/v1beta2 # for versions before 1.7.0 use apps/v1beta1
+kind: Deployment
+metadata:
+  name: data-out-task
+  labels:
+    app: data-out-task
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: data-out-task
+  template:
+    metadata:
+      labels:
+        app: data-out-task
+    spec:
+      containers:
+      - name: data-out-task
+        image: dimuthuupe/data-out-task:v1.0
+        env:
+        - name: zookeeper_connection_url
+          value: zk.default.svc.cluster.local:2199
+        - name: participant_name
+          valueFrom:
+            fieldRef:
+              fieldPath: metadata.name
+        ports:
+        - containerPort: 8080
\ No newline at end of file
diff --git a/airavata-kubernetes/scripts/k8s/tasks/egress-staging-task/egress-staging-task-dep.yml b/airavata-kubernetes/scripts/k8s/tasks/egress-staging-task/egress-staging-task-dep.yml
deleted file mode 100644
index a1588a0..0000000
--- a/airavata-kubernetes/scripts/k8s/tasks/egress-staging-task/egress-staging-task-dep.yml
+++ /dev/null
@@ -1,21 +0,0 @@
-apiVersion: apps/v1beta2 # for versions before 1.7.0 use apps/v1beta1
-kind: Deployment
-metadata:
-  name: egress-staging-task
-  labels:
-    app: egress-staging-task
-spec:
-  replicas: 1
-  selector:
-    matchLabels:
-      app: egress-staging-task
-  template:
-    metadata:
-      labels:
-        app: egress-staging-task
-    spec:
-      containers:
-      - name: egress-staging-task
-        image: dimuthuupe/egress-staging-task:v1.0
-        ports:
-        - containerPort: 8080
\ No newline at end of file
diff --git a/airavata-kubernetes/scripts/k8s/tasks/env-cleanup-task/env-cleanup-task-dep.yml b/airavata-kubernetes/scripts/k8s/tasks/env-cleanup-task/env-cleanup-task-dep.yml
deleted file mode 100644
index 85b1c20..0000000
--- a/airavata-kubernetes/scripts/k8s/tasks/env-cleanup-task/env-cleanup-task-dep.yml
+++ /dev/null
@@ -1,21 +0,0 @@
-apiVersion: apps/v1beta2 # for versions before 1.7.0 use apps/v1beta1
-kind: Deployment
-metadata:
-  name: env-cleanup-task
-  labels:
-    app: env-cleanup-task
-spec:
-  replicas: 1
-  selector:
-    matchLabels:
-      app: env-cleanup-task
-  template:
-    metadata:
-      labels:
-        app: env-cleanup-task
-    spec:
-      containers:
-      - name: env-cleanup-task
-        image: dimuthuupe/env-cleanup-task:v1.0
-        ports:
-        - containerPort: 8080
\ No newline at end of file
diff --git a/airavata-kubernetes/scripts/k8s/tasks/env-setup-task/env-setup-task-dep.yml b/airavata-kubernetes/scripts/k8s/tasks/env-setup-task/env-setup-task-dep.yml
deleted file mode 100644
index 4e92893..0000000
--- a/airavata-kubernetes/scripts/k8s/tasks/env-setup-task/env-setup-task-dep.yml
+++ /dev/null
@@ -1,21 +0,0 @@
-apiVersion: apps/v1beta2 # for versions before 1.7.0 use apps/v1beta1
-kind: Deployment
-metadata:
-  name: env-setup-task
-  labels:
-    app: env-setup-task
-spec:
-  replicas: 1
-  selector:
-    matchLabels:
-      app: env-setup-task
-  template:
-    metadata:
-      labels:
-        app: env-setup-task
-    spec:
-      containers:
-      - name: env-setup-task
-        image: dimuthuupe/env-setup-task:v1.0
-        ports:
-        - containerPort: 8080
\ No newline at end of file
diff --git a/airavata-kubernetes/scripts/k8s/tasks/ingress-staging-task/ingress-staging-task-dep.yml b/airavata-kubernetes/scripts/k8s/tasks/ingress-staging-task/ingress-staging-task-dep.yml
deleted file mode 100644
index 7e1b6ad..0000000
--- a/airavata-kubernetes/scripts/k8s/tasks/ingress-staging-task/ingress-staging-task-dep.yml
+++ /dev/null
@@ -1,21 +0,0 @@
-apiVersion: apps/v1beta2 # for versions before 1.7.0 use apps/v1beta1
-kind: Deployment
-metadata:
-  name: ingress-staging-task
-  labels:
-    app: ingress-staging-task
-spec:
-  replicas: 1
-  selector:
-    matchLabels:
-      app: ingress-staging-task
-  template:
-    metadata:
-      labels:
-        app: ingress-staging-task
-    spec:
-      containers:
-      - name: ingress-staging-task
-        image: dimuthuupe/ingress-staging-task:v1.0
-        ports:
-        - containerPort: 8080
\ No newline at end of file
diff --git a/airavata-kubernetes/scripts/k8s/tasks/job-submission-task/job-submission-task-dep.yml b/airavata-kubernetes/scripts/k8s/tasks/job-submission-task/job-submission-task-dep.yml
deleted file mode 100644
index 354edc4..0000000
--- a/airavata-kubernetes/scripts/k8s/tasks/job-submission-task/job-submission-task-dep.yml
+++ /dev/null
@@ -1,21 +0,0 @@
-apiVersion: apps/v1beta2 # for versions before 1.7.0 use apps/v1beta1
-kind: Deployment
-metadata:
-  name: job-submission-task
-  labels:
-    app: job-submission-task
-spec:
-  replicas: 1
-  selector:
-    matchLabels:
-      app: job-submission-task
-  template:
-    metadata:
-      labels:
-        app: job-submission-task
-    spec:
-      containers:
-      - name: job-submission-task
-        image: dimuthuupe/job-submission-task:v1.0
-        ports:
-        - containerPort: 8080
\ No newline at end of file
diff --git a/airavata-kubernetes/scripts/k8s/workflow-generator/workflow-generator-dep.yml b/airavata-kubernetes/scripts/k8s/workflow-generator/workflow-generator-dep.yml
deleted file mode 100644
index 91a08b6..0000000
--- a/airavata-kubernetes/scripts/k8s/workflow-generator/workflow-generator-dep.yml
+++ /dev/null
@@ -1,21 +0,0 @@
-apiVersion: apps/v1beta2 # for versions before 1.7.0 use apps/v1beta1
-kind: Deployment
-metadata:
-  name: workflow-generator
-  labels:
-    app: workflow-generator
-spec:
-  replicas: 1
-  selector:
-    matchLabels:
-      app: workflow-generator
-  template:
-    metadata:
-      labels:
-        app: workflow-generator
-    spec:
-      containers:
-      - name: workflow-generator
-        image: dimuthuupe/workflow-generator:v1.0
-        ports:
-        - containerPort: 8080
\ No newline at end of file
diff --git a/airavata-kubernetes/scripts/k8s/zookeeper-service.yml b/airavata-kubernetes/scripts/k8s/zookeeper-service.yml
new file mode 100644
index 0000000..19fbf94
--- /dev/null
+++ b/airavata-kubernetes/scripts/k8s/zookeeper-service.yml
@@ -0,0 +1,24 @@
+apiVersion: v1
+kind: Service
+metadata:
+  name: zk
+  labels:
+    name: zk
+spec:
+  ports:
+    - port: 2199
+      targetPort: 2199
+      protocol: TCP
+      name: zk
+---
+
+kind: Endpoints
+apiVersion: v1
+metadata:
+  name: zk
+subsets:
+  - addresses:
+      - ip: 192.168.1.114
+    ports:
+      - port: 2199
+        name: zk
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
"commits@airavata.apache.org" <co...@airavata.apache.org>.

[airavata-sandbox] 10/19: Adding parent directory to Jenkinsfile

Posted by sm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

smarru pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-sandbox.git

commit cfbd29868af421106cb52b0433916a84736e3311
Author: dimuthu.upeksha2@gmail.com <Di...@1234>
AuthorDate: Sun Nov 26 06:30:55 2017 +0530

    Adding parent directory to Jenkinsfile
---
 airavata-kubernetes/scripts/k8s/ci-cd/Jenkinsfile | 34 +++++++++++------------
 1 file changed, 17 insertions(+), 17 deletions(-)

diff --git a/airavata-kubernetes/scripts/k8s/ci-cd/Jenkinsfile b/airavata-kubernetes/scripts/k8s/ci-cd/Jenkinsfile
index aaf3a04..b7a5b32 100644
--- a/airavata-kubernetes/scripts/k8s/ci-cd/Jenkinsfile
+++ b/airavata-kubernetes/scripts/k8s/ci-cd/Jenkinsfile
@@ -31,16 +31,16 @@ node {
     env.BUILDIMG_DATA_OUT_TASK=data_out_task_imageName
 
     stage "Build"
-        sh "/opt/maven/bin/mvn -f modules/api-resource/ clean install"
-        sh "/opt/maven/bin/mvn -f modules/compute-resource-api/ clean install"
-        sh "/opt/maven/bin/mvn -f modules/helix-task-api/ clean install"
-        sh "/opt/maven/bin/mvn -f modules/microservices/api-server/ clean install docker:build -DdockerImageTags=${tag} -Ddocker.image.prefix=${registryHost}"
-        sh "/opt/maven/bin/mvn -f modules/microservices/event-sink/ clean install docker:build -DdockerImageTags=${tag} -Ddocker.image.prefix=${registryHost}"
-        sh "/opt/maven/bin/mvn -f modules/microservices/helix-controller/ clean install docker:build -DdockerImageTags=${tag} -Ddocker.image.prefix=${registryHost}"
-        sh "/opt/maven/bin/mvn -f modules/microservices/workflow-scheduler/ clean install docker:build -DdockerImageTags=${tag} -Ddocker.image.prefix=${registryHost}"
-        sh "/opt/maven/bin/mvn -f modules/microservices/tasks/command-task/ clean install docker:build -DdockerImageTags=${tag} -Ddocker.image.prefix=${registryHost}"
-        sh "/opt/maven/bin/mvn -f modules/microservices/tasks/data-in-task/ clean install docker:build -DdockerImageTags=${tag} -Ddocker.image.prefix=${registryHost}"
-        sh "/opt/maven/bin/mvn -f modules/microservices/tasks/data-out-task/ clean install docker:build -DdockerImageTags=${tag} -Ddocker.image.prefix=${registryHost}"
+        sh "/opt/maven/bin/mvn -f airavata-kubernetes/modules/api-resource/ clean install"
+        sh "/opt/maven/bin/mvn -f airavata-kubernetes/modules/compute-resource-api/ clean install"
+        sh "/opt/maven/bin/mvn -f airavata-kubernetes/modules/helix-task-api/ clean install"
+        sh "/opt/maven/bin/mvn -f airavata-kubernetes/modules/microservices/api-server/ clean install docker:build -DdockerImageTags=${tag} -Ddocker.image.prefix=${registryHost}"
+        sh "/opt/maven/bin/mvn -f airavata-kubernetes/modules/microservices/event-sink/ clean install docker:build -DdockerImageTags=${tag} -Ddocker.image.prefix=${registryHost}"
+        sh "/opt/maven/bin/mvn -f airavata-kubernetes/modules/microservices/helix-controller/ clean install docker:build -DdockerImageTags=${tag} -Ddocker.image.prefix=${registryHost}"
+        sh "/opt/maven/bin/mvn -f airavata-kubernetes/modules/microservices/workflow-scheduler/ clean install docker:build -DdockerImageTags=${tag} -Ddocker.image.prefix=${registryHost}"
+        sh "/opt/maven/bin/mvn -f airavata-kubernetes/modules/microservices/tasks/command-task/ clean install docker:build -DdockerImageTags=${tag} -Ddocker.image.prefix=${registryHost}"
+        sh "/opt/maven/bin/mvn -f airavata-kubernetes/modules/microservices/tasks/data-in-task/ clean install docker:build -DdockerImageTags=${tag} -Ddocker.image.prefix=${registryHost}"
+        sh "/opt/maven/bin/mvn -f airavata-kubernetes/modules/microservices/tasks/data-out-task/ clean install docker:build -DdockerImageTags=${tag} -Ddocker.image.prefix=${registryHost}"
 
     stage "Push"
 
@@ -54,25 +54,25 @@ node {
 
     stage "Deploy"
 
-        sh "sed 's#dimuthuupe/api-server:v1.0#'$BUILDIMG_API_SERVER'#' scripts/k8s/api-server/api-server-dep.yaml | kubectl apply -f -"
+        sh "sed 's#dimuthuupe/api-server:v1.0#'$BUILDIMG_API_SERVER'#' airavata-kubernetes/scripts/k8s/api-server/api-server-dep.yaml | kubectl apply -f -"
         sh "kubectl rollout status deployment/api-server"
 
-        sh "sed 's#dimuthuupe/event-sink:v1.0#'BUILDIMG_EVENT_SINK'#' scripts/k8s/event-sink/event-sink-dep.yaml | kubectl apply -f -"
+        sh "sed 's#dimuthuupe/event-sink:v1.0#'BUILDIMG_EVENT_SINK'#' airavata-kubernetes/scripts/k8s/event-sink/event-sink-dep.yaml | kubectl apply -f -"
         sh "kubectl rollout status deployment/event-sink"
 
-        sh "sed 's#dimuthuupe/helix-controller:v1.0#'BUILDIMG_HELIX_CONTROLLER'#' scripts/k8s/helix-controller/helix-controller-dep.yaml | kubectl apply -f -"
+        sh "sed 's#dimuthuupe/helix-controller:v1.0#'BUILDIMG_HELIX_CONTROLLER'#' airavata-kubernetes/scripts/k8s/helix-controller/helix-controller-dep.yaml | kubectl apply -f -"
         sh "kubectl rollout status deployment/helix-controller"
 
-        sh "sed 's#dimuthuupe/task-scheduler:v1.0#'BUILDIMG_TASK_SCHEDULER'#' scripts/k8s/task-scheduler/task-scheduler-dep.yaml | kubectl apply -f -"
+        sh "sed 's#dimuthuupe/task-scheduler:v1.0#'BUILDIMG_TASK_SCHEDULER'#' airavata-kubernetes/scripts/k8s/task-scheduler/task-scheduler-dep.yaml | kubectl apply -f -"
         sh "kubectl rollout status deployment/task-scheduler"
 
-        sh "sed 's#dimuthuupe/command-task:v1.0#'BUILDIMG_COMMAND_TASK'#' scripts/k8s/command-task/command-task-dep.yaml | kubectl apply -f -"
+        sh "sed 's#dimuthuupe/command-task:v1.0#'BUILDIMG_COMMAND_TASK'#' airavata-kubernetes/scripts/k8s/command-task/command-task-dep.yaml | kubectl apply -f -"
         sh "kubectl rollout status deployment/command-task"
 
-        sh "sed 's#dimuthuupe/data-in-task:v1.0#'BUILDIMG_DATA_IN_TASK'#' scripts/k8s/data-in-task/data-in-task-dep.yaml | kubectl apply -f -"
+        sh "sed 's#dimuthuupe/data-in-task:v1.0#'BUILDIMG_DATA_IN_TASK'#' airavata-kubernetes/scripts/k8s/data-in-task/data-in-task-dep.yaml | kubectl apply -f -"
         sh "kubectl rollout status deployment/data-in-task"
 
-        sh "sed 's#dimuthuupe/data-out-task:v1.0#'BUILDIMG_DATA_OUT_TASK'#' scripts/k8s/data-out-task/data-out-task-dep.yaml | kubectl apply -f -"
+        sh "sed 's#dimuthuupe/data-out-task:v1.0#'BUILDIMG_DATA_OUT_TASK'#' airavata-kubernetes/scripts/k8s/data-out-task/data-out-task-dep.yaml | kubectl apply -f -"
         sh "kubectl rollout status deployment/data-out-task"
 
 }
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
"commits@airavata.apache.org" <co...@airavata.apache.org>.

[airavata-sandbox] 08/19: Optimizing maven imports and adding docker build plugins to tasks

Posted by sm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

smarru pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-sandbox.git

commit e83184a0a26e7a00144b0c078b7bc4508233bc80
Author: dimuthu.upeksha2@gmail.com <Di...@1234>
AuthorDate: Mon Nov 20 20:40:02 2017 +0530

    Optimizing maven imports and adding docker build plugins to tasks
---
 airavata-kubernetes/modules/helix-task-api/pom.xml |  2 -
 .../apache/airavata/helix/api/AbstractTask.java    |  1 -
 .../modules/microservices/helix-controller/pom.xml | 41 +-------------
 .../microservices/tasks/command-task/pom.xml       | 63 +++++++++++++++++++++-
 .../src/main/resources/log4j.properties            |  9 ++++
 .../microservices/tasks/data-in-task/pom.xml       | 62 ++++++++++++++++++++-
 .../src/main/resources/log4j.properties            |  9 ++++
 .../microservices/tasks/data-out-task/pom.xml      | 62 ++++++++++++++++++++-
 .../src/main/resources/log4j.properties            |  9 ++++
 airavata-kubernetes/pom.xml                        | 25 +++++++++
 10 files changed, 237 insertions(+), 46 deletions(-)

diff --git a/airavata-kubernetes/modules/helix-task-api/pom.xml b/airavata-kubernetes/modules/helix-task-api/pom.xml
index e2363e6..6116e4f 100644
--- a/airavata-kubernetes/modules/helix-task-api/pom.xml
+++ b/airavata-kubernetes/modules/helix-task-api/pom.xml
@@ -30,7 +30,6 @@
         <dependency>
             <groupId>org.apache.helix</groupId>
             <artifactId>helix-core</artifactId>
-            <version>0.6.7</version>
         </dependency>
         <dependency>
             <groupId>org.springframework</groupId>
@@ -50,7 +49,6 @@
         <dependency>
             <groupId>org.apache.kafka</groupId>
             <artifactId>kafka-clients</artifactId>
-            <version>0.10.1.1</version>
         </dependency>
     </dependencies>
 
diff --git a/airavata-kubernetes/modules/helix-task-api/src/main/java/org/apache/airavata/helix/api/AbstractTask.java b/airavata-kubernetes/modules/helix-task-api/src/main/java/org/apache/airavata/helix/api/AbstractTask.java
index e7290c2..03589e7 100644
--- a/airavata-kubernetes/modules/helix-task-api/src/main/java/org/apache/airavata/helix/api/AbstractTask.java
+++ b/airavata-kubernetes/modules/helix-task-api/src/main/java/org/apache/airavata/helix/api/AbstractTask.java
@@ -1,7 +1,6 @@
 package org.apache.airavata.helix.api;
 
 import org.apache.airavata.k8s.api.resources.compute.ComputeResource;
-import org.apache.airavata.k8s.api.resources.task.type.TaskTypeResource;
 import org.apache.airavata.k8s.compute.api.ComputeOperations;
 import org.apache.airavata.k8s.compute.impl.MockComputeOperation;
 import org.apache.airavata.k8s.compute.impl.SSHComputeOperations;
diff --git a/airavata-kubernetes/modules/microservices/helix-controller/pom.xml b/airavata-kubernetes/modules/microservices/helix-controller/pom.xml
index 9fe9f06..1613030 100644
--- a/airavata-kubernetes/modules/microservices/helix-controller/pom.xml
+++ b/airavata-kubernetes/modules/microservices/helix-controller/pom.xml
@@ -13,62 +13,23 @@
     <artifactId>helix-controller</artifactId>
 
     <dependencies>
-        <dependency>
-            <groupId>org.springframework</groupId>
-            <artifactId>spring-web</artifactId>
-            <version>3.0.2.RELEASE</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.airavata</groupId>
-            <artifactId>api-resource</artifactId>
-            <version>1.0-SNAPSHOT</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.airavata</groupId>
-            <artifactId>compute-resource-api</artifactId>
-            <version>1.0-SNAPSHOT</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.kafka</groupId>
-            <artifactId>kafka-clients</artifactId>
-            <version>0.10.1.1</version>
-        </dependency>
-        <dependency>
-            <groupId>commons-io</groupId>
-            <artifactId>commons-io</artifactId>
-            <version>2.5</version>
-        </dependency>
+
         <dependency>
             <groupId>org.apache.helix</groupId>
             <artifactId>helix-core</artifactId>
-            <version>0.6.7</version>
         </dependency>
         <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
-            <version>1.7.5</version>
             <scope>compile</scope>
         </dependency>
-
         <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-log4j12</artifactId>
-            <version>1.7.5</version>
-        </dependency>
-        <dependency>
-            <groupId>org.testng</groupId>
-            <artifactId>testng</artifactId>
-            <version>6.0.1</version>
-        </dependency>
-        <dependency>
-            <groupId>com.jcraft</groupId>
-            <artifactId>jsch</artifactId>
-            <version>0.1.53</version>
         </dependency>
         <dependency>
             <groupId>org.apache.curator</groupId>
             <artifactId>curator-framework</artifactId>
-            <version>2.8.0</version>
         </dependency>
     </dependencies>
 
diff --git a/airavata-kubernetes/modules/microservices/tasks/command-task/pom.xml b/airavata-kubernetes/modules/microservices/tasks/command-task/pom.xml
index a08c014..a67fefc 100644
--- a/airavata-kubernetes/modules/microservices/tasks/command-task/pom.xml
+++ b/airavata-kubernetes/modules/microservices/tasks/command-task/pom.xml
@@ -23,6 +23,58 @@
                     <target>${java.version}</target>
                 </configuration>
             </plugin>
+
+            <plugin>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <configuration>
+                    <archive>
+                        <manifest>
+                            <mainClass>org.apache.airavata.helix.task.command.Participant</mainClass>
+                        </manifest>
+                    </archive>
+                    <descriptorRefs>
+                        <descriptorRef>jar-with-dependencies</descriptorRef>
+                    </descriptorRefs>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>make-assembly</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+
+            <!-- Create a docker image that runs the executable jar-->
+            <plugin>
+                <groupId>com.spotify</groupId>
+                <artifactId>docker-maven-plugin</artifactId>
+                <version>1.0.0</version>
+                <configuration>
+                    <imageName>${docker.image.prefix}/command-task</imageName>
+                    <baseImage>java:openjdk-8-jdk-alpine</baseImage>
+                    <exposes>
+                        <expose>8080</expose>
+                    </exposes>
+                    <entryPoint>["java","-jar","/${project.build.finalName}-jar-with-dependencies.jar"]</entryPoint>
+                    <resources>
+                        <resource>
+                            <targetPath>/</targetPath>
+                            <directory>${project.build.directory}</directory>
+                            <include>${project.build.finalName}-jar-with-dependencies.jar</include>
+                        </resource>
+                    </resources>
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>build</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
         </plugins>
     </build>
 
@@ -32,10 +84,19 @@
             <artifactId>helix-task-api</artifactId>
             <version>1.0-SNAPSHOT</version>
         </dependency>
+
         <dependency>
             <groupId>org.apache.helix</groupId>
             <artifactId>helix-core</artifactId>
-            <version>0.6.7</version>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-log4j12</artifactId>
         </dependency>
     </dependencies>
 
diff --git a/airavata-kubernetes/modules/microservices/tasks/command-task/src/main/resources/log4j.properties b/airavata-kubernetes/modules/microservices/tasks/command-task/src/main/resources/log4j.properties
new file mode 100644
index 0000000..5e31e3c
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/tasks/command-task/src/main/resources/log4j.properties
@@ -0,0 +1,9 @@
+# Set root logger level to DEBUG and its only appender to A1.
+log4j.rootLogger=INFO, A1
+
+# A1 is set to be a ConsoleAppender.
+log4j.appender.A1=org.apache.log4j.ConsoleAppender
+
+# A1 uses PatternLayout.
+log4j.appender.A1.layout=org.apache.log4j.PatternLayout
+log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/microservices/tasks/data-in-task/pom.xml b/airavata-kubernetes/modules/microservices/tasks/data-in-task/pom.xml
index 866ef14..d6c2fe6 100644
--- a/airavata-kubernetes/modules/microservices/tasks/data-in-task/pom.xml
+++ b/airavata-kubernetes/modules/microservices/tasks/data-in-task/pom.xml
@@ -23,6 +23,58 @@
                     <target>${java.version}</target>
                 </configuration>
             </plugin>
+
+            <plugin>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <configuration>
+                    <archive>
+                        <manifest>
+                            <mainClass>org.apache.airavata.helix.task.command.Participant</mainClass>
+                        </manifest>
+                    </archive>
+                    <descriptorRefs>
+                        <descriptorRef>jar-with-dependencies</descriptorRef>
+                    </descriptorRefs>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>make-assembly</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+
+            <!-- Create a docker image that runs the executable jar-->
+            <plugin>
+                <groupId>com.spotify</groupId>
+                <artifactId>docker-maven-plugin</artifactId>
+                <version>1.0.0</version>
+                <configuration>
+                    <imageName>${docker.image.prefix}/data-in-task</imageName>
+                    <baseImage>java:openjdk-8-jdk-alpine</baseImage>
+                    <exposes>
+                        <expose>8080</expose>
+                    </exposes>
+                    <entryPoint>["java","-jar","/${project.build.finalName}-jar-with-dependencies.jar"]</entryPoint>
+                    <resources>
+                        <resource>
+                            <targetPath>/</targetPath>
+                            <directory>${project.build.directory}</directory>
+                            <include>${project.build.finalName}-jar-with-dependencies.jar</include>
+                        </resource>
+                    </resources>
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>build</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
         </plugins>
     </build>
 
@@ -35,7 +87,15 @@
         <dependency>
             <groupId>org.apache.helix</groupId>
             <artifactId>helix-core</artifactId>
-            <version>0.6.7</version>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-log4j12</artifactId>
         </dependency>
     </dependencies>
 </project>
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/microservices/tasks/data-in-task/src/main/resources/log4j.properties b/airavata-kubernetes/modules/microservices/tasks/data-in-task/src/main/resources/log4j.properties
new file mode 100644
index 0000000..5e31e3c
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/tasks/data-in-task/src/main/resources/log4j.properties
@@ -0,0 +1,9 @@
+# Set root logger level to DEBUG and its only appender to A1.
+log4j.rootLogger=INFO, A1
+
+# A1 is set to be a ConsoleAppender.
+log4j.appender.A1=org.apache.log4j.ConsoleAppender
+
+# A1 uses PatternLayout.
+log4j.appender.A1.layout=org.apache.log4j.PatternLayout
+log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/microservices/tasks/data-out-task/pom.xml b/airavata-kubernetes/modules/microservices/tasks/data-out-task/pom.xml
index e88a3a0..80c9e87 100644
--- a/airavata-kubernetes/modules/microservices/tasks/data-out-task/pom.xml
+++ b/airavata-kubernetes/modules/microservices/tasks/data-out-task/pom.xml
@@ -23,6 +23,58 @@
                     <target>${java.version}</target>
                 </configuration>
             </plugin>
+
+            <plugin>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <configuration>
+                    <archive>
+                        <manifest>
+                            <mainClass>org.apache.airavata.helix.task.command.Participant</mainClass>
+                        </manifest>
+                    </archive>
+                    <descriptorRefs>
+                        <descriptorRef>jar-with-dependencies</descriptorRef>
+                    </descriptorRefs>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>make-assembly</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+
+            <!-- Create a docker image that runs the executable jar-->
+            <plugin>
+                <groupId>com.spotify</groupId>
+                <artifactId>docker-maven-plugin</artifactId>
+                <version>1.0.0</version>
+                <configuration>
+                    <imageName>${docker.image.prefix}/data-out-task</imageName>
+                    <baseImage>java:openjdk-8-jdk-alpine</baseImage>
+                    <exposes>
+                        <expose>8080</expose>
+                    </exposes>
+                    <entryPoint>["java","-jar","/${project.build.finalName}-jar-with-dependencies.jar"]</entryPoint>
+                    <resources>
+                        <resource>
+                            <targetPath>/</targetPath>
+                            <directory>${project.build.directory}</directory>
+                            <include>${project.build.finalName}-jar-with-dependencies.jar</include>
+                        </resource>
+                    </resources>
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>build</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
         </plugins>
     </build>
 
@@ -35,7 +87,15 @@
         <dependency>
             <groupId>org.apache.helix</groupId>
             <artifactId>helix-core</artifactId>
-            <version>0.6.7</version>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-log4j12</artifactId>
         </dependency>
     </dependencies>
 
diff --git a/airavata-kubernetes/modules/microservices/tasks/data-out-task/src/main/resources/log4j.properties b/airavata-kubernetes/modules/microservices/tasks/data-out-task/src/main/resources/log4j.properties
new file mode 100644
index 0000000..5e31e3c
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/tasks/data-out-task/src/main/resources/log4j.properties
@@ -0,0 +1,9 @@
+# Set root logger level to DEBUG and its only appender to A1.
+log4j.rootLogger=INFO, A1
+
+# A1 is set to be a ConsoleAppender.
+log4j.appender.A1=org.apache.log4j.ConsoleAppender
+
+# A1 uses PatternLayout.
+log4j.appender.A1.layout=org.apache.log4j.PatternLayout
+log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
\ No newline at end of file
diff --git a/airavata-kubernetes/pom.xml b/airavata-kubernetes/pom.xml
index 30439d3..30b9226 100644
--- a/airavata-kubernetes/pom.xml
+++ b/airavata-kubernetes/pom.xml
@@ -75,6 +75,31 @@
                 <artifactId>jsch</artifactId>
                 <version>0.1.53</version>
             </dependency>
+            <dependency>
+                <groupId>org.apache.helix</groupId>
+                <artifactId>helix-core</artifactId>
+                <version>0.6.7</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.kafka</groupId>
+                <artifactId>kafka-clients</artifactId>
+                <version>0.10.1.1</version>
+            </dependency>
+            <dependency>
+                <groupId>org.slf4j</groupId>
+                <artifactId>slf4j-api</artifactId>
+                <version>1.7.5</version>
+            </dependency>
+            <dependency>
+                <groupId>org.slf4j</groupId>
+                <artifactId>slf4j-log4j12</artifactId>
+                <version>1.7.5</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.curator</groupId>
+                <artifactId>curator-framework</artifactId>
+                <version>2.8.0</version>
+            </dependency>
         </dependencies>
     </dependencyManagement>
 

-- 
To stop receiving notification emails like this one, please contact
"commits@airavata.apache.org" <co...@airavata.apache.org>.

[airavata-sandbox] 16/19: Fixing minor bugs

Posted by sm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

smarru pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-sandbox.git

commit c43e46ca264426220770b9b5fc0ac97768323a1d
Author: dimuthu.upeksha2@gmail.com <Di...@1234>
AuthorDate: Mon Nov 27 00:57:13 2017 +0530

    Fixing minor bugs
---
 .../task-scheduler/{task-secheduler-dep.yml => task-scheduler-dep.yml}    | 0
 1 file changed, 0 insertions(+), 0 deletions(-)

diff --git a/airavata-kubernetes/scripts/k8s/task-scheduler/task-secheduler-dep.yml b/airavata-kubernetes/scripts/k8s/task-scheduler/task-scheduler-dep.yml
similarity index 100%
rename from airavata-kubernetes/scripts/k8s/task-scheduler/task-secheduler-dep.yml
rename to airavata-kubernetes/scripts/k8s/task-scheduler/task-scheduler-dep.yml

-- 
To stop receiving notification emails like this one, please contact
"commits@airavata.apache.org" <co...@airavata.apache.org>.

[airavata-sandbox] 05/19: Fixing broken features for web console

Posted by sm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

smarru pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-sandbox.git

commit ec218c223feecebd81fbe454258cc6f1f466028a
Author: dimuthu.upeksha2@gmail.com <Di...@1234>
AuthorDate: Sun Nov 19 17:46:09 2017 +0530

    Fixing broken features for web console
---
 .../resources/process/ProcessStatusResource.java   |  5 +-
 .../k8s/compute/impl/SSHComputeOperations.java     |  4 +-
 .../helix/tasks/dataout/DataOutputTask.java        |  6 +-
 .../api/server/controller/WorkflowController.java  |  7 +++
 .../k8s/api/server/model/workflow/Workflow.java    |  2 +-
 .../k8s/api/server/service/ProcessService.java     |  9 ++-
 .../k8s/api/server/service/WorkflowService.java    |  8 ++-
 .../api/server/service/util/ToResourceUtil.java    |  1 +
 .../k8s/gfac/core/HelixWorkflowManager.java        | 31 +++++++++-
 .../web-console/src/app/app.module.ts              |  7 ++-
 .../app/components/dashboard/dashboard.routes.ts   |  5 ++
 .../src/app/components/process/detail/detail.html  | 12 ++--
 .../process/detail/process.detail.component.ts     |  5 +-
 .../src/app/components/workflow/detail/detail.html | 56 ++++++++++++++++++
 .../components/workflow/detail/workflow.detail.ts  | 68 ++++++++++++++++++++++
 .../workflow/list/workflow.list.component.ts       |  2 +-
 .../web-console/src/app/services/util.service.ts   | 13 +++++
 .../src/app/services/workflow.service.ts           |  8 +++
 18 files changed, 228 insertions(+), 21 deletions(-)

diff --git a/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/process/ProcessStatusResource.java b/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/process/ProcessStatusResource.java
index 40fc9b1..dc7e2f5 100644
--- a/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/process/ProcessStatusResource.java
+++ b/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/process/ProcessStatusResource.java
@@ -106,7 +106,10 @@ public class ProcessStatusResource {
         COMPLETED(10),
         FAILED(11),
         CANCELLING(12),
-        CANCELED(13);
+        CANCELED(13),
+        ABORTED(14),
+        STOPPED(15),
+        NOT_STARTED(16);
 
         private final int value;
 
diff --git a/airavata-kubernetes/modules/compute-resource-api/src/main/java/org/apache/airavata/k8s/compute/impl/SSHComputeOperations.java b/airavata-kubernetes/modules/compute-resource-api/src/main/java/org/apache/airavata/k8s/compute/impl/SSHComputeOperations.java
index fb77981..df711b8 100644
--- a/airavata-kubernetes/modules/compute-resource-api/src/main/java/org/apache/airavata/k8s/compute/impl/SSHComputeOperations.java
+++ b/airavata-kubernetes/modules/compute-resource-api/src/main/java/org/apache/airavata/k8s/compute/impl/SSHComputeOperations.java
@@ -169,7 +169,7 @@ public class SSHComputeOperations implements ComputeOperations {
         File _lfile = new File(source);
 
         if (ptimestamp) {
-            command = "T " + (_lfile.lastModified() / 1000) + " 0";
+            command = "T" + (_lfile.lastModified() / 1000) + " 0";
             // The access time should be sent here,
             // but it is not accessible with JavaAPI ;-<
             command += (" " + (_lfile.lastModified() / 1000) + " 0\n");
@@ -341,6 +341,6 @@ public class SSHComputeOperations implements ComputeOperations {
         //ExecutionResult result = operations.executeCommand("sh /opt/sample.sh > /tmp/stdout.txt 2> /tmp/stderr.txt");
         //System.out.println(result.getStdOut());
         //System.out.println(result.getStdErr());
-        operations.transferDataOut("/tmp/stdout.txt", "/tmp/b.txt", "SCP");
+        operations.transferDataIn("/tmp/a.txt", "/tmp/b.txt", "SCP");
     }
 }
diff --git a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/dataout/DataOutputTask.java b/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/dataout/DataOutputTask.java
index 314e912..62a6712 100644
--- a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/dataout/DataOutputTask.java
+++ b/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/dataout/DataOutputTask.java
@@ -107,10 +107,12 @@ public class DataOutputTask extends AbstractTask {
                                 .setDefaultValue(""),
                         new TaskInputTypeResource()
                                 .setName(PARAMS.IDENTIFIER)
-                                .setType("String"),
+                                .setType("String")
+                                .setDefaultValue(""),
                         new TaskInputTypeResource()
                                 .setName(PARAMS.COMPUTE_RESOURCE)
-                                .setType("Long")));
+                                .setType("Long")
+                                .setDefaultValue("")));
 
         taskTypeResource.getOutPorts().addAll(
                 Arrays.asList(
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/controller/WorkflowController.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/controller/WorkflowController.java
index 136f839..273a59f 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/controller/WorkflowController.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/controller/WorkflowController.java
@@ -2,6 +2,7 @@ package org.apache.airavata.k8s.api.server.controller;
 
 import org.apache.airavata.k8s.api.resources.experiment.ExperimentResource;
 import org.apache.airavata.k8s.api.resources.workflow.WorkflowResource;
+import org.apache.airavata.k8s.api.server.ServerRuntimeException;
 import org.apache.airavata.k8s.api.server.service.WorkflowService;
 import org.springframework.http.MediaType;
 import org.springframework.web.bind.annotation.*;
@@ -32,6 +33,12 @@ public class WorkflowController {
         return this.workflowService.getAll();
     }
 
+    @GetMapping(path = "{id}", produces = MediaType.APPLICATION_JSON_VALUE)
+    public WorkflowResource findExperimentById(@PathVariable("id") long id) {
+        return this.workflowService.findById(id)
+                .orElseThrow(() -> new ServerRuntimeException("Workflow with id " + id + " not found"));
+    }
+
     @GetMapping(path = "{id}/launch", produces = MediaType.APPLICATION_JSON_VALUE)
     public long launchExperiment(@PathVariable("id") long id) {
         return this.workflowService.launchWorkflow(id);
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/workflow/Workflow.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/workflow/Workflow.java
index 82d6bf5..c2bb059 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/workflow/Workflow.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/workflow/Workflow.java
@@ -27,7 +27,7 @@ public class Workflow {
     private byte[] workFlowGraph;
 
 
-    @OneToMany(mappedBy = "experiment", cascade = CascadeType.ALL)
+    @OneToMany(mappedBy = "workflow", cascade = CascadeType.ALL)
     private List<ProcessModel> processes = new ArrayList<>();
 
     public long getId() {
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/ProcessService.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/ProcessService.java
index d2b93aa..500e83f 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/ProcessService.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/ProcessService.java
@@ -27,6 +27,7 @@ import org.apache.airavata.k8s.api.server.model.task.TaskModel;
 import org.apache.airavata.k8s.api.server.repository.process.ProcessRepository;
 import org.apache.airavata.k8s.api.resources.process.ProcessResource;
 import org.apache.airavata.k8s.api.server.repository.process.ProcessStatusRepository;
+import org.apache.airavata.k8s.api.server.repository.workflow.WorkflowRepository;
 import org.apache.airavata.k8s.api.server.service.task.TaskService;
 import org.apache.airavata.k8s.api.server.service.util.ToResourceUtil;
 import org.springframework.stereotype.Service;
@@ -48,17 +49,19 @@ public class ProcessService {
     private ExperimentService experimentService;
     private TaskService taskService;
 
-    private WorkflowService workflowService;
+    private WorkflowRepository workflowRepository;
 
     public ProcessService(ProcessRepository processRepository,
                           ProcessStatusRepository processStatusRepository,
                           ExperimentService experimentService,
-                          TaskService taskService) {
+                          TaskService taskService,
+                          WorkflowRepository workflowRepository) {
 
         this.processRepository = processRepository;
         this.processStatusRepository = processStatusRepository;
         this.experimentService = experimentService;
         this.taskService = taskService;
+        this.workflowRepository = workflowRepository;
     }
 
     public long create(ProcessResource resource) {
@@ -77,7 +80,7 @@ public class ProcessService {
         }
 
         if (resource.getWorkflowId() != 0) {
-            processModel.setWorkflow(workflowService.findEntityById(resource.getWorkflowId())
+            processModel.setWorkflow(workflowRepository.findById(resource.getWorkflowId())
                     .orElseThrow(() -> new ServerRuntimeException("Can not find workflow with id " +
                             resource.getWorkflowId())));
         }
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/WorkflowService.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/WorkflowService.java
index f4492fe..42be414 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/WorkflowService.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/WorkflowService.java
@@ -77,7 +77,9 @@ public class WorkflowService {
 
         long processId = processService.create(new ProcessResource()
                 .setName("Workflow Process : " + workflow.getName() + "-" + UUID.randomUUID().toString())
-                .setCreationTime(System.currentTimeMillis()).setProcessType("WORKFLOW"));
+                .setCreationTime(System.currentTimeMillis())
+                .setProcessType("WORKFLOW")
+                .setWorkflowId(id));
 
         try {
             GraphParser.ParseResult parseResult = GraphParser.parse(new String(workflow.getWorkFlowGraph()));
@@ -132,6 +134,10 @@ public class WorkflowService {
         return workflowResources;
     }
 
+    public Optional<WorkflowResource> findById(long id) {
+        return ToResourceUtil.toResource(findEntityById(id).get());
+    }
+
     @SuppressWarnings("WeakerAccess")
     public Optional<Workflow> findEntityById(long id) {
         return this.workflowRepository.findById(id);
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/util/ToResourceUtil.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/util/ToResourceUtil.java
index 22b970f..35c7fde 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/util/ToResourceUtil.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/util/ToResourceUtil.java
@@ -229,6 +229,7 @@ public class ToResourceUtil {
             resource.setCreationTime(taskModel.getCreationTime());
             resource.setParentProcessId(taskModel.getParentProcess().getId());
             resource.setTaskType(toResource(taskModel.getTaskType()).get());
+            resource.setTaskTypeStr(taskModel.getTaskType().getName());
             resource.setTaskDetail(taskModel.getTaskDetail());
             resource.setStartingTask(taskModel.isStartingTask());
             resource.setStoppingTask(taskModel.isStoppingTask());
diff --git a/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/core/HelixWorkflowManager.java b/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/core/HelixWorkflowManager.java
index e9dead8..36f66b4 100644
--- a/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/core/HelixWorkflowManager.java
+++ b/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/core/HelixWorkflowManager.java
@@ -54,7 +54,7 @@ public class HelixWorkflowManager {
                 InstanceType.SPECTATOR, "localhost:2199");
 
         try {
-
+            updateProcessStatus(ProcessStatusResource.State.CREATED);
             Workflow.Builder workflowBuilder = createWorkflow();
             WorkflowConfig.Builder config = new WorkflowConfig.Builder().setFailureThreshold(0);
             workflowBuilder.setWorkflowConfig(config.build());
@@ -64,6 +64,8 @@ public class HelixWorkflowManager {
 
             Workflow workflow = workflowBuilder.build();
 
+            updateProcessStatus(ProcessStatusResource.State.VALIDATED);
+
             helixManager.connect();
             TaskDriver taskDriver = new TaskDriver(helixManager);
 
@@ -77,8 +79,12 @@ public class HelixWorkflowManager {
             );
 
             taskDriver.start(workflow);
+
+            updateProcessStatus(ProcessStatusResource.State.STARTED);
+
             logger.info("Started workflow");
             TaskState taskState = taskDriver.pollForWorkflowState(workflow.getName(), TaskState.COMPLETED, TaskState.FAILED, TaskState.STOPPED, TaskState.ABORTED);
+            updateProcessStatus(taskState);
             System.out.println("Workflow state " + taskState.name());
 
         } catch (Exception ex) {
@@ -146,6 +152,29 @@ public class HelixWorkflowManager {
         }));
     }
 
+    private void updateProcessStatus(TaskState taskState) {
+        switch (taskState) {
+            case ABORTED:
+                updateProcessStatus(ProcessStatusResource.State.ABORTED);
+                break;
+            case COMPLETED:
+                updateProcessStatus(ProcessStatusResource.State.COMPLETED);
+                break;
+            case STOPPED:
+                updateProcessStatus(ProcessStatusResource.State.STOPPED);
+                break;
+            case NOT_STARTED:
+                updateProcessStatus(ProcessStatusResource.State.NOT_STARTED);
+                break;
+            case FAILED:
+                updateProcessStatus(ProcessStatusResource.State.FAILED);
+                break;
+            case IN_PROGRESS:
+                updateProcessStatus(ProcessStatusResource.State.EXECUTING);
+                break;
+        }
+    }
+
     private void updateProcessStatus(ProcessStatusResource.State state) {
         updateProcessStatus(state, "");
     }
diff --git a/airavata-kubernetes/web-console/src/app/app.module.ts b/airavata-kubernetes/web-console/src/app/app.module.ts
index 6e4d842..36d3047 100644
--- a/airavata-kubernetes/web-console/src/app/app.module.ts
+++ b/airavata-kubernetes/web-console/src/app/app.module.ts
@@ -17,6 +17,8 @@ import {ProcessDetailComponent} from "./components/process/detail/process.detail
 import {SetupComponent} from "./components/setup/setup.component";
 import {WorkflowCreateComponent} from "./components/workflow/create/create.component";
 import {WorkflowListComponent} from "./components/workflow/list/workflow.list.component";
+import {WorkflowDetailComponent} from "./components/workflow/detail/workflow.detail";
+import {UtilService} from "./services/util.service";
 
 @NgModule({
   declarations: [
@@ -31,7 +33,8 @@ import {WorkflowListComponent} from "./components/workflow/list/workflow.list.co
     ProcessDetailComponent,
     SetupComponent,
     WorkflowCreateComponent,
-    WorkflowListComponent
+    WorkflowListComponent,
+    WorkflowDetailComponent
   ],
   imports: [
     NgbModule.forRoot(),
@@ -40,7 +43,7 @@ import {WorkflowListComponent} from "./components/workflow/list/workflow.list.co
     HttpModule,
     FormsModule
   ],
-  providers: [],
+  providers: [UtilService],
   bootstrap: [AppComponent]
 })
 export class AppModule { }
diff --git a/airavata-kubernetes/web-console/src/app/components/dashboard/dashboard.routes.ts b/airavata-kubernetes/web-console/src/app/components/dashboard/dashboard.routes.ts
index c211503..7030474 100644
--- a/airavata-kubernetes/web-console/src/app/components/dashboard/dashboard.routes.ts
+++ b/airavata-kubernetes/web-console/src/app/components/dashboard/dashboard.routes.ts
@@ -10,6 +10,7 @@ import {ProcessDetailComponent} from "../process/detail/process.detail.component
 import {SetupComponent} from "../setup/setup.component";
 import {WorkflowCreateComponent} from "../workflow/create/create.component";
 import {WorkflowListComponent} from "../workflow/list/workflow.list.component";
+import {WorkflowDetailComponent} from "../workflow/detail/workflow.detail";
 
 /**
  * Created by dimuthu on 10/29/17.
@@ -56,5 +57,9 @@ export const DASHBOARD_ROUTES: Routes = [
   {
     path: 'workflow/create',
     component: WorkflowCreateComponent,
+  },
+  {
+    path: 'workflow/detail/:id',
+    component: WorkflowDetailComponent,
   }
 ];
diff --git a/airavata-kubernetes/web-console/src/app/components/process/detail/detail.html b/airavata-kubernetes/web-console/src/app/components/process/detail/detail.html
index f89265c..76bbe1f 100644
--- a/airavata-kubernetes/web-console/src/app/components/process/detail/detail.html
+++ b/airavata-kubernetes/web-console/src/app/components/process/detail/detail.html
@@ -3,7 +3,7 @@
   <div class="form-group row">
     <label for="name" class="col-sm-2 col-form-label">Creation Time</label>
     <div class="col-sm-10">
-      <input type="text" [(ngModel)]="selectedProcess.creationTime" readonly class="form-control-plaintext" id="name" name="name" value="email@example.com">
+      <input type="text" readonly class="form-control-plaintext" id="name" name="name" value="{{this.utilService.toDateString(selectedProcess.creationTime)}}">
     </div>
   </div>
 </form>
@@ -14,16 +14,16 @@
   <thead>
   <tr>
     <th scope="col">Id</th>
+    <th scope="col">Name</th>
     <th scope="col">Type</th>
-    <th scope="col">Detail</th>
     <th scope="col">Current Status</th>
   </tr>
   </thead>
   <tbody>
   <tr *ngFor="let task of this.taskDagFroProcess">
     <th>{{task.id}}</th>
+    <th>{{task.name}}</th>
     <th>{{task.taskTypeStr}}</th>
-    <th>{{task.taskDetail}}</th>
     <th>{{task.taskStatus.length > 0 ? task.taskStatus[task.taskStatus.length -1].stateStr : "NO STATUS"}}</th>
   </tr>
   </tbody>
@@ -46,8 +46,8 @@
       <thead>
       <tr>
         <th scope="col">Task Id</th>
+        <th scope="col">Task Name</th>
         <th scope="col">Task Type</th>
-        <th scope="col">Task Detail</th>
         <th scope="col">Status</th>
         <th scope="col">Occurred Time</th>
         <th scope="col">Reason</th>
@@ -57,10 +57,10 @@
       <template ngFor let-task [ngForOf]="this.taskDagFroProcess">
         <tr *ngFor="let status of task.taskStatus">
           <td>{{task.id}}</td>
+          <td>{{task.name}}</td>
           <td>{{task.taskTypeStr}}</td>
-          <td>{{task.taskDetail}}</td>
           <td>{{status.stateStr}}</td>
-          <td>{{status.timeOfStateChange}}</td>
+          <td>{{this.utilService.toDateString(status.timeOfStateChange)}}</td>
           <td>{{status.reason}}</td>
         </tr>
       </template>
diff --git a/airavata-kubernetes/web-console/src/app/components/process/detail/process.detail.component.ts b/airavata-kubernetes/web-console/src/app/components/process/detail/process.detail.component.ts
index c9e554d..6207383 100644
--- a/airavata-kubernetes/web-console/src/app/components/process/detail/process.detail.component.ts
+++ b/airavata-kubernetes/web-console/src/app/components/process/detail/process.detail.component.ts
@@ -7,6 +7,7 @@ import {Process} from "../../../models/process/process.model";
 import {Task} from "../../../models/task/task.model";
 import {TaskStatus} from "../../../models/task/task.status.model";
 import {DataEntry} from "../../../models/data/data.entry.model";
+import {UtilService} from "../../../services/util.service";
 
 @Component({
   templateUrl: './detail.html',
@@ -21,12 +22,14 @@ export class ProcessDetailComponent {
   outputs: Array<DataEntry> = [];
 
   constructor(private modalService: NgbModal, private activatedRoute: ActivatedRoute,
-              private processService: ProcessService, private taskService: TaskService){
+              private processService: ProcessService, private taskService: TaskService,
+              private utilService: UtilService){
 
     let processId = this.activatedRoute.snapshot.params["id"];
     processService.getProcessById(processId).subscribe(data => {
       this.selectedProcess = data;
       this.taskDagFroProcess = this.selectedProcess.tasks;
+      this.taskDagFroProcess.sort((t1, t2) => {return t1.id - t2.id})
     }, err => console.log(err));
   }
 
diff --git a/airavata-kubernetes/web-console/src/app/components/workflow/detail/detail.html b/airavata-kubernetes/web-console/src/app/components/workflow/detail/detail.html
new file mode 100644
index 0000000..011b36a
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/app/components/workflow/detail/detail.html
@@ -0,0 +1,56 @@
+<div class="">
+  <div class="">
+  <form>
+    <div class="form-group row">
+      <label for="name" class="col-sm-2 col-form-label">Name</label>
+      <div class="col-sm-10">
+        <input type="text" [(ngModel)]="selectedWorkflow.name" readonly class="form-control-plaintext" id="name" name="name" value="email@example.com">
+      </div>
+    </div>
+
+    <div class="form-group row">
+      <label for="appProcess" class="col-sm-2 col-form-label">Application Processes</label>
+      <div class="col-sm-10">
+        <button type="button" id="appProcess" name="appProcess" class="btn" (click)="openProcessesAsModel(processContent)">Processes</button>
+      </div>
+    </div>
+  </form>
+  <button type="button" class="btn" (click)="launchWorkflow()">Launch</button>
+  </div>
+</div>
+
+
+<ng-template #processContent let-c="close" let-d="dismiss">
+
+  <div class="modal-header">
+    <h4 class="modal-title">Experiment Processes</h4>
+    <button type="button" class="close" aria-label="Close" (click)="d('Cross click')">
+      <span aria-hidden="true">&times;</span>
+    </button>
+  </div>
+
+  <div class="modal-body">
+    <table class="table">
+      <thead>
+      <tr>
+        <th scope="col">Id</th>
+        <th scope="col">Creation Time</th>
+        <th scope="col">Last State</th>
+        <th scope="col"></th>
+      </tr>
+      </thead>
+      <tbody>
+      <tr *ngFor="let process of this.processes">
+        <th>{{process.id}}</th>
+        <th>{{process.creationTime}}</th>
+        <th>{{process.processStatuses.length > 0 ? process.processStatuses[process.processStatuses.length -1].stateStr : "" }}</th>
+        <td><button (click)="routeToProcessPage(process.id)">Detail</button></td>
+      </tr>
+      </tbody>
+    </table>
+  </div>
+
+  <div class="modal-footer">
+    <button type="button" class="btn btn-outline-dark" (click)="c('Close click')">Close</button>
+  </div>
+</ng-template>
diff --git a/airavata-kubernetes/web-console/src/app/components/workflow/detail/workflow.detail.ts b/airavata-kubernetes/web-console/src/app/components/workflow/detail/workflow.detail.ts
new file mode 100644
index 0000000..99482f7
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/app/components/workflow/detail/workflow.detail.ts
@@ -0,0 +1,68 @@
+import {ViewEncapsulation, Component} from "@angular/core";
+import {ActivatedRoute, Router} from "@angular/router";
+import {NgbModal, NgbModalRef} from "@ng-bootstrap/ng-bootstrap";
+import {Process} from "../../../models/process/process.model";
+import {ProcessService} from "../../../services/process.service";
+import {ProcessStatus} from "../../../models/process/process.status.model";
+import {WorkFlow} from "../../../models/workflow/workflow.model";
+import {WorkflowService} from "../../../services/workflow.service";
+
+/**
+ * Created by dimuthu on 10/29/17.
+ */
+
+@Component({
+  templateUrl: './detail.html',
+  encapsulation: ViewEncapsulation.None,
+  providers: [WorkflowService, ProcessService]
+})
+export class WorkflowDetailComponent {
+
+  selectedWorkflow: WorkFlow = new WorkFlow();
+  processes: Array<Process> = [];
+  processLastState: ProcessStatus = new ProcessStatus();
+  processListModel: NgbModalRef;
+
+  constructor(private modalService: NgbModal,private activatedRoute: ActivatedRoute,
+              private workflowService: WorkflowService, private processService: ProcessService,
+              private router: Router) {
+
+    let workflowId = this.activatedRoute.snapshot.params["id"];
+    this.workflowService.getWorkflowById(workflowId)
+      .subscribe(data => {this.selectedWorkflow = data}, err => {console.log(err)});
+  }
+
+  launchWorkflow() {
+    this.workflowService.launchWorkflow(this.selectedWorkflow.id).subscribe(data => {
+      alert("Workflow launching started");
+    },
+      err => {
+        console.log(err);
+        alert("Workflow launch failed");
+      }
+    )
+  }
+
+  routeToProcessPage(id: number) {
+    this.processListModel.close();
+    this.router.navigateByUrl("/process/detail/" + id);
+  }
+
+  openAsModel(content) {
+    this.modalService.open(content, {size: "lg"}).result.then((result) => {}, (reason) => {});
+  }
+
+  openProcessesAsModel(content) {
+    this.processes = [];
+    this.selectedWorkflow.processIds.forEach(id => {
+      this.processService.getProcessById(id).subscribe(data => {
+        this.processes.push(data);
+        this.processes.sort((p1, p2) => {return p1.id - p2.id;})
+      }, err => {
+        console.log(err);
+      });
+    });
+    this.processListModel = this.modalService.open(content, {size: "lg"});
+    this.processListModel.result.then((result) => {}, (reason) => {});
+  }
+}
diff --git a/airavata-kubernetes/web-console/src/app/components/workflow/list/workflow.list.component.ts b/airavata-kubernetes/web-console/src/app/components/workflow/list/workflow.list.component.ts
index fc8b9ad..3838ae4 100644
--- a/airavata-kubernetes/web-console/src/app/components/workflow/list/workflow.list.component.ts
+++ b/airavata-kubernetes/web-console/src/app/components/workflow/list/workflow.list.component.ts
@@ -33,7 +33,7 @@ export class WorkflowListComponent {
   }
 
   routeToDetailPage(id: number) {
-    this.router.navigateByUrl("/experiment/detail/"+id);
+    this.router.navigateByUrl("/workflow/detail/"+id);
   }
 
   routeToCreatePage() {
diff --git a/airavata-kubernetes/web-console/src/app/services/util.service.ts b/airavata-kubernetes/web-console/src/app/services/util.service.ts
new file mode 100644
index 0000000..fafda55
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/app/services/util.service.ts
@@ -0,0 +1,13 @@
+import {Injectable} from "@angular/core";
+
+@Injectable()
+export class UtilService {
+
+  toDateString(timestamp: number) {
+    return new Date(timestamp).toString();
+  }
+
+  toTimeString(timestamp: number) {
+    return new Date(timestamp).toLocaleTimeString();
+  }
+}
diff --git a/airavata-kubernetes/web-console/src/app/services/workflow.service.ts b/airavata-kubernetes/web-console/src/app/services/workflow.service.ts
index f652bd9..bc345b4 100644
--- a/airavata-kubernetes/web-console/src/app/services/workflow.service.ts
+++ b/airavata-kubernetes/web-console/src/app/services/workflow.service.ts
@@ -14,4 +14,12 @@ export class WorkflowService {
   getAllWorkflows() {
     return this.apiService.get("workflow").map(res => res.json());
   }
+
+  getWorkflowById(id: number) {
+    return this.apiService.get("workflow/" + id).map(res => res.json());
+  }
+
+  launchWorkflow(id: number) {
+    return this.apiService.get("workflow/" + id + "/launch");
+  }
 }

-- 
To stop receiving notification emails like this one, please contact
"commits@airavata.apache.org" <co...@airavata.apache.org>.

[airavata-sandbox] 13/19: Refactoring workflow scheduler

Posted by sm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

smarru pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-sandbox.git

commit 930722cd5ae35995278ecf31b590c2661273ef7b
Author: dimuthu.upeksha2@gmail.com <Di...@1234>
AuthorDate: Sun Nov 26 13:17:50 2017 +0530

    Refactoring workflow scheduler
---
 .../microservices/workflow-scheduler/pom.xml       |   7 -
 .../k8s/gfac/core/HelixWorkflowManager.java        |   6 -
 .../k8s/gfac/core/ProcessLifeCycleManager.java     | 168 ---------------------
 .../airavata/k8s/gfac/messaging/KafkaReceiver.java |   1 -
 .../airavata/k8s/gfac/messaging/KafkaSender.java   |  41 -----
 .../k8s/gfac/messaging/ReceiverConfig.java         |  27 ----
 .../airavata/k8s/gfac/messaging/SenderConfig.java  |  71 ---------
 .../k8s/gfac/service/HelixWorkflowService.java     |  17 ---
 .../airavata/k8s/gfac/service/WorkerService.java   |  16 +-
 9 files changed, 2 insertions(+), 352 deletions(-)

diff --git a/airavata-kubernetes/modules/microservices/workflow-scheduler/pom.xml b/airavata-kubernetes/modules/microservices/workflow-scheduler/pom.xml
index 66974fb..d8f1a40 100644
--- a/airavata-kubernetes/modules/microservices/workflow-scheduler/pom.xml
+++ b/airavata-kubernetes/modules/microservices/workflow-scheduler/pom.xml
@@ -39,13 +39,6 @@
             <artifactId>api-resource</artifactId>
             <version>1.0-SNAPSHOT</version>
         </dependency>
-        <dependency>
-            <groupId>org.apache.airavata</groupId>
-            <artifactId>task-api</artifactId>
-            <version>1.0-SNAPSHOT</version>
-        </dependency>
-
-
 
         <dependency>
             <groupId>org.springframework.boot</groupId>
diff --git a/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/core/HelixWorkflowManager.java b/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/core/HelixWorkflowManager.java
index 53778ac..4fe8579 100644
--- a/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/core/HelixWorkflowManager.java
+++ b/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/core/HelixWorkflowManager.java
@@ -2,13 +2,11 @@ package org.apache.airavata.k8s.gfac.core;
 
 import org.apache.airavata.k8s.api.resources.process.ProcessStatusResource;
 import org.apache.airavata.k8s.api.resources.task.TaskResource;
-import org.apache.airavata.k8s.gfac.messaging.KafkaSender;
 import org.apache.helix.HelixManagerFactory;
 import org.apache.helix.InstanceType;
 import org.apache.helix.task.*;
 import org.apache.log4j.LogManager;
 import org.apache.log4j.Logger;
-import org.apache.zookeeper.Op;
 import org.springframework.web.client.RestTemplate;
 
 import java.util.ArrayList;
@@ -32,8 +30,6 @@ public class HelixWorkflowManager {
     // out port id, next task id
     private Map<Long, Long> edgeMap;
 
-    private KafkaSender kafkaSender;
-
     // Todo abstract out these parameters to reusable class
     private final RestTemplate restTemplate;
     private String apiServerUrl;
@@ -43,13 +39,11 @@ public class HelixWorkflowManager {
     private String instanceName;
 
     public HelixWorkflowManager(long processId, List<TaskResource> tasks, Map<Long, Long> edgeMap,
-                                KafkaSender kafkaSender,
                                 RestTemplate restTemplate, String apiServerUrl, String zkConnectionString,
                                 String helixClusterName, String instanceName) {
         this.processId = processId;
         this.tasks = tasks;
         this.edgeMap = edgeMap;
-        this.kafkaSender = kafkaSender;
         this.restTemplate = restTemplate;
         this.apiServerUrl = apiServerUrl;
         this.zkConnectionString = zkConnectionString;
diff --git a/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/core/ProcessLifeCycleManager.java b/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/core/ProcessLifeCycleManager.java
deleted file mode 100644
index 8508f92..0000000
--- a/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/core/ProcessLifeCycleManager.java
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.airavata.k8s.gfac.core;
-
-import org.apache.airavata.k8s.api.resources.process.ProcessStatusResource;
-import org.apache.airavata.k8s.api.resources.task.TaskOutPortResource;
-import org.apache.airavata.k8s.api.resources.task.TaskResource;
-import org.apache.airavata.k8s.api.resources.task.TaskStatusResource;
-import org.apache.airavata.k8s.gfac.messaging.KafkaSender;
-import org.apache.airavata.k8s.task.api.TaskContext;
-import org.springframework.web.client.RestTemplate;
-
-import java.util.*;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-public class ProcessLifeCycleManager {
-
-    private long processId;
-    private List<TaskResource> tasks;
-    private TaskResource currentTask;
-    private Map<Long, Long> edgeMap;
-
-    private KafkaSender kafkaSender;
-
-    // Todo abstract out these parameters to reusable class
-    private final RestTemplate restTemplate;
-    private String apiServerUrl;
-
-    public ProcessLifeCycleManager(long processId, List<TaskResource> tasks, Map<Long, Long> edgeMap,
-                                   KafkaSender kafkaSender,
-                                   RestTemplate restTemplate, String apiServerUrl) {
-        this.processId = processId;
-        this.tasks = tasks;
-        this.edgeMap = edgeMap;
-        this.kafkaSender = kafkaSender;
-        this.restTemplate = restTemplate;
-        this.apiServerUrl = apiServerUrl;
-    }
-
-    public void init() {
-
-        Optional<TaskResource> startingTask = tasks.stream().filter(TaskResource::isStartingTask).findFirst();
-        if (startingTask.isPresent()) {
-            this.currentTask = startingTask.get();
-        } else {
-            System.out.println("No starting task for this process " + processId);
-            updateProcessStatus(ProcessStatusResource.State.CANCELED, "No starting task for this process");
-        }
-
-    }
-
-    public void start() {
-        updateProcessStatus(ProcessStatusResource.State.EXECUTING);
-        System.out.println("Starting process " + processId + " with task " + currentTask.getName());
-
-        TaskContext startContext = new TaskContext();
-        startContext.assignTask(currentTask);
-
-        submitTaskToQueue(currentTask.getTaskType().getTopicName(), startContext);
-    }
-
-    public synchronized void onTaskStateChanged(TaskContext taskContext) {
-
-        updateProcessStatus(ProcessStatusResource.State.MONITORING, "Task moved to state "
-                + ProcessStatusResource.State.valueOf(taskContext.getStatus()).name());
-
-        if (taskContext.getTaskId() != currentTask.getId()) {
-            System.out.println("Incompatible task status received. " +
-                    "Currently running task id " + currentTask.getId() + " received task id " + taskContext.getTaskId());
-            updateProcessStatus(ProcessStatusResource.State.FAILED, "Incompatible task status received. " +
-                    "Currently running task id " + currentTask.getId() + " received task id " + taskContext.getTaskId());
-            return;
-        } else {
-            System.out.println("Compatible task status received");
-        }
-
-        switch (taskContext.getStatus()) {
-            case TaskStatusResource.State.COMPLETED:
-
-                if (currentTask.isStoppingTask()) {
-                    System.out.println("Process completed with last task " + currentTask.getName());
-                    updateProcessStatus(ProcessStatusResource.State.COMPLETED, "Process completed with last task " + currentTask.getName());
-
-                } else {
-                    Optional<TaskOutPortResource> nextOutPort = currentTask.getOutPorts().stream()
-                            .filter(port -> port.getId() == taskContext.getOutPortId()).findFirst();
-                    if (nextOutPort.isPresent()) {
-
-                        if (edgeMap.containsKey(nextOutPort.get().getId())) {
-                            Long nextTaskId = edgeMap.get(nextOutPort.get().getId());
-                            Optional<TaskResource> nextTask = tasks.stream().filter(task -> task.getId() == nextTaskId).findFirst();
-
-                            if (nextTask.isPresent()) {
-
-                                this.currentTask = nextTask.get();
-                                taskContext.assignTask(this.currentTask);
-                                System.out.println("Submitting next task " + this.currentTask.getName() + " of process " + processId);
-                                submitTaskToQueue(this.currentTask.getTaskType().getTopicName(), taskContext);
-
-                            } else {
-                                System.out.println("Next task with id " + nextTaskId + " can not be found");
-                                updateProcessStatus(ProcessStatusResource.State.FAILED, "Next task with id "
-                                        + nextTaskId + " can not be found");
-                                return;
-                            }
-
-                        } else {
-                            System.out.println("Incomplete graph. Next outport " + nextOutPort.get().getName()
-                                    + " of task " + currentTask.getName() + " ends with a no endpoint");
-                            updateProcessStatus(ProcessStatusResource.State.FAILED, "Incomplete graph. Next outport "
-                                    + nextOutPort.get().getName() + " of task " + currentTask.getName()
-                                    + " ends with a no endpoint");
-                            return;
-                        }
-                    } else {
-                        System.out.println("Invalid out port " + taskContext.getOutPortId() + " for task " + taskContext.getTaskId());
-                        updateProcessStatus(ProcessStatusResource.State.FAILED,
-                                "Invalid out port " + taskContext.getOutPortId() + " for task " + taskContext.getTaskId());
-                    }
-                }
-                break;
-            case TaskStatusResource.State.FAILED:
-                updateProcessStatus(ProcessStatusResource.State.FAILED);
-                break;
-        }
-    }
-
-    private void submitTaskToQueue(String topicName, TaskContext taskContext) {
-        updateProcessStatus(ProcessStatusResource.State.MONITORING, "Submitting task " + taskContext.getTaskId() + " to queue");
-        kafkaSender.send(topicName, taskContext);
-    }
-
-    private void updateProcessStatus(ProcessStatusResource.State state) {
-        updateProcessStatus(state, "");
-    }
-
-    private void updateProcessStatus(ProcessStatusResource.State state, String reason) {
-        this.restTemplate.postForObject("http://" + apiServerUrl + "/process/" + this.processId + "/status",
-                new ProcessStatusResource()
-                        .setState(state.getValue())
-                        .setReason(reason)
-                        .setTimeOfStateChange(System.currentTimeMillis()),
-                Long.class);
-    }
-
-}
diff --git a/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/KafkaReceiver.java b/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/KafkaReceiver.java
index 6a02975..a3659c8 100644
--- a/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/KafkaReceiver.java
+++ b/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/KafkaReceiver.java
@@ -20,7 +20,6 @@
 package org.apache.airavata.k8s.gfac.messaging;
 
 import org.apache.airavata.k8s.gfac.service.WorkerService;
-import org.apache.airavata.k8s.task.api.TaskContext;
 import org.springframework.kafka.annotation.KafkaListener;
 
 import javax.annotation.Resource;
diff --git a/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/KafkaSender.java b/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/KafkaSender.java
deleted file mode 100644
index c4df008..0000000
--- a/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/KafkaSender.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/**
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.airavata.k8s.gfac.messaging;
-
-import org.apache.airavata.k8s.task.api.TaskContext;
-import org.apache.airavata.k8s.task.api.TaskContextSerializer;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.kafka.core.KafkaTemplate;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-public class KafkaSender {
-
-    @Autowired
-    private KafkaTemplate<String, TaskContext> kafkaTemplate;
-
-    public void send(String topic, TaskContext taskContext) {
-        kafkaTemplate.send(topic, taskContext);
-    }
-}
diff --git a/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/ReceiverConfig.java b/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/ReceiverConfig.java
index 0b23bdd..58fea3f 100644
--- a/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/ReceiverConfig.java
+++ b/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/ReceiverConfig.java
@@ -19,8 +19,6 @@
  */
 package org.apache.airavata.k8s.gfac.messaging;
 
-import org.apache.airavata.k8s.task.api.TaskContext;
-import org.apache.airavata.k8s.task.api.TaskContextDeserializer;
 import org.apache.kafka.clients.consumer.ConsumerConfig;
 import org.apache.kafka.common.serialization.StringDeserializer;
 import org.springframework.beans.factory.annotation.Value;
@@ -66,28 +64,11 @@ public class ReceiverConfig {
     }
 
     @Bean
-    public Map<String, Object> consumerConfigsForEvents() {
-        Map<String, Object> props = new HashMap<>();
-        // list of host:port pairs used for establishing the initial connections to the Kakfa cluster
-        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
-        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
-        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, TaskContextDeserializer.class);
-        // create a random group for each consumer in order to read all events form all consumers
-        props.put(ConsumerConfig.GROUP_ID_CONFIG, "event-group-" + UUID.randomUUID().toString());
-        return props;
-    }
-
-    @Bean
     public ConsumerFactory<String, String> consumerFactory() {
         return new DefaultKafkaConsumerFactory<String, String>(consumerConfigs());
     }
 
     @Bean
-    public ConsumerFactory<String, TaskContext> consumerFactoryForEvents() {
-        return new DefaultKafkaConsumerFactory<String, TaskContext>(consumerConfigsForEvents());
-    }
-
-    @Bean
     public KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<String, String>> kafkaListenerContainerFactory() {
         ConcurrentKafkaListenerContainerFactory<String, String> factory =
                 new ConcurrentKafkaListenerContainerFactory<>();
@@ -97,14 +78,6 @@ public class ReceiverConfig {
     }
 
     @Bean
-    public KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<String, TaskContext>> kafkaEventListenerContainerFactory() {
-        ConcurrentKafkaListenerContainerFactory<String, TaskContext> factory =
-                new ConcurrentKafkaListenerContainerFactory<>();
-        factory.setConsumerFactory(consumerFactoryForEvents());
-        return factory;
-    }
-
-    @Bean
     public KafkaReceiver receiver() {
         return new KafkaReceiver();
     }
diff --git a/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/SenderConfig.java b/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/SenderConfig.java
deleted file mode 100644
index 4c6bf1e..0000000
--- a/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/SenderConfig.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/**
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.airavata.k8s.gfac.messaging;
-
-import org.apache.airavata.k8s.task.api.TaskContext;
-import org.apache.airavata.k8s.task.api.TaskContextSerializer;
-import org.apache.kafka.clients.producer.ProducerConfig;
-import org.apache.kafka.common.serialization.StringSerializer;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.kafka.core.DefaultKafkaProducerFactory;
-import org.springframework.kafka.core.KafkaTemplate;
-import org.springframework.kafka.core.ProducerFactory;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-@Configuration
-public class SenderConfig {
-    @Value("${kafka.bootstrap-servers}")
-    private String bootstrapServers;
-
-    @Bean
-    public Map<String, Object> producerConfigs() {
-        Map<String, Object> props = new HashMap<>();
-        // list of host:port pairs used for establishing the initial connections to the Kakfa cluster
-        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
-        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
-        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, TaskContextSerializer.class);
-        return props;
-    }
-
-    @Bean
-    public ProducerFactory<String, TaskContext> producerFactory() {
-        return new DefaultKafkaProducerFactory<String, TaskContext>(producerConfigs());
-    }
-
-    @Bean
-    public KafkaTemplate<String, TaskContext> kafkaTemplate() {
-        return new KafkaTemplate<>(producerFactory());
-    }
-
-    @Bean
-    public KafkaSender kafkaSender() {
-        return new KafkaSender();
-    }
-}
diff --git a/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/service/HelixWorkflowService.java b/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/service/HelixWorkflowService.java
deleted file mode 100644
index 05a1c55..0000000
--- a/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/service/HelixWorkflowService.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package org.apache.airavata.k8s.gfac.service;
-
-import org.springframework.stereotype.Service;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-@Service
-public class HelixWorkflowService {
-
-    public void launchProcess(long processId) {
-
-    }
-}
diff --git a/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/service/WorkerService.java b/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/service/WorkerService.java
index 606c20a..888f469 100644
--- a/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/service/WorkerService.java
+++ b/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/service/WorkerService.java
@@ -22,11 +22,7 @@ package org.apache.airavata.k8s.gfac.service;
 import org.apache.airavata.k8s.api.resources.process.ProcessResource;
 import org.apache.airavata.k8s.api.resources.task.TaskDagResource;
 import org.apache.airavata.k8s.api.resources.task.TaskResource;
-import org.apache.airavata.k8s.api.resources.task.TaskStatusResource;
 import org.apache.airavata.k8s.gfac.core.HelixWorkflowManager;
-import org.apache.airavata.k8s.gfac.core.ProcessLifeCycleManager;
-import org.apache.airavata.k8s.gfac.messaging.KafkaSender;
-import org.apache.airavata.k8s.task.api.TaskContext;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.core.ParameterizedTypeReference;
 import org.springframework.http.HttpMethod;
@@ -47,8 +43,6 @@ import java.util.concurrent.Executors;
 public class WorkerService {
 
     private final RestTemplate restTemplate;
-    private final KafkaSender kafkaSender;
-    private Map<Long, ProcessLifeCycleManager> processLifecycleStore = new HashMap<>();
     ExecutorService executorService = Executors.newFixedThreadPool(10);
 
     @Value("${api.server.url}")
@@ -63,9 +57,8 @@ public class WorkerService {
     @Value("${instance.name}")
     private String instanceName;
 
-    public WorkerService(RestTemplate restTemplate, KafkaSender kafkaSender) {
+    public WorkerService(RestTemplate restTemplate) {
         this.restTemplate = restTemplate;
-        this.kafkaSender = kafkaSender;
     }
 
     public void launchProcess(long processId) {
@@ -93,7 +86,7 @@ public class WorkerService {
         //processLifecycleStore.put(processId, manager);
 
         final HelixWorkflowManager helixWorkflowManager = new HelixWorkflowManager(processId, taskResources, edgeMap,
-                kafkaSender, restTemplate, apiServerUrl,
+                restTemplate, apiServerUrl,
                 zkConnectionString, helixClusterName, instanceName);
 
         executorService.execute(new Runnable() {
@@ -103,9 +96,4 @@ public class WorkerService {
             }
         });
     }
-
-    public void onTaskStateEvent(TaskContext taskContext) {
-        Optional.ofNullable(processLifecycleStore.get(taskContext.getProcessId()))
-                .ifPresent(manager -> manager.onTaskStateChanged(taskContext));
-    }
 }

-- 
To stop receiving notification emails like this one, please contact
"commits@airavata.apache.org" <co...@airavata.apache.org>.

[airavata-sandbox] 15/19: Fixing minor bugs

Posted by sm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

smarru pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-sandbox.git

commit c0ade6cfa8daf5bfb050811cc5022d5c546a6b75
Author: dimuthu.upeksha2@gmail.com <Di...@1234>
AuthorDate: Mon Nov 27 00:43:25 2017 +0530

    Fixing minor bugs
---
 .../scripts/docker/jenkins-docker-kubectl-mvn/Dockerfile     |  4 +++-
 airavata-kubernetes/scripts/k8s/ci-cd/Jenkinsfile            | 12 ++++++------
 2 files changed, 9 insertions(+), 7 deletions(-)

diff --git a/airavata-kubernetes/scripts/docker/jenkins-docker-kubectl-mvn/Dockerfile b/airavata-kubernetes/scripts/docker/jenkins-docker-kubectl-mvn/Dockerfile
index 544a9d3..ac9d947 100644
--- a/airavata-kubernetes/scripts/docker/jenkins-docker-kubectl-mvn/Dockerfile
+++ b/airavata-kubernetes/scripts/docker/jenkins-docker-kubectl-mvn/Dockerfile
@@ -1,2 +1,4 @@
 FROM chadmoon/jenkins-docker-kubectl:latest
-ADD apache-maven-3.5.2 /opt/maven
\ No newline at end of file
+ADD apache-maven-3.5.2 /opt/maven
+ADD kubectl /usr/local/bin/kubectl
+RUN chmod +x /usr/local/bin/kubectl
\ No newline at end of file
diff --git a/airavata-kubernetes/scripts/k8s/ci-cd/Jenkinsfile b/airavata-kubernetes/scripts/k8s/ci-cd/Jenkinsfile
index 521de80..98624ce 100644
--- a/airavata-kubernetes/scripts/k8s/ci-cd/Jenkinsfile
+++ b/airavata-kubernetes/scripts/k8s/ci-cd/Jenkinsfile
@@ -58,22 +58,22 @@ node {
         sh "sed 's#dimuthuupe/api-server:v1.0#'$BUILDIMG_API_SERVER'#' airavata-kubernetes/scripts/k8s/api-server/api-server-dep.yml | kubectl apply -f -"
         sh "kubectl rollout status deployment/api-server"
 
-        sh "sed 's#dimuthuupe/event-sink:v1.0#'BUILDIMG_EVENT_SINK'#' airavata-kubernetes/scripts/k8s/event-sink/event-sink-dep.yml | kubectl apply -f -"
+        sh "sed 's#dimuthuupe/event-sink:v1.0#'$BUILDIMG_EVENT_SINK'#' airavata-kubernetes/scripts/k8s/event-sink/event-sink-dep.yml | kubectl apply -f -"
         sh "kubectl rollout status deployment/event-sink"
 
-        sh "sed 's#dimuthuupe/helix-controller:v1.0#'BUILDIMG_HELIX_CONTROLLER'#' airavata-kubernetes/scripts/k8s/helix-controller/helix-controller-dep.yml | kubectl apply -f -"
+        sh "sed 's#dimuthuupe/helix-controller:v1.0#'$BUILDIMG_HELIX_CONTROLLER'#' airavata-kubernetes/scripts/k8s/helix-controller/helix-controller-dep.yml | kubectl apply -f -"
         sh "kubectl rollout status deployment/helix-controller"
 
-        sh "sed 's#dimuthuupe/task-scheduler:v1.0#'BUILDIMG_TASK_SCHEDULER'#' airavata-kubernetes/scripts/k8s/task-scheduler/task-scheduler-dep.yml | kubectl apply -f -"
+        sh "sed 's#dimuthuupe/task-scheduler:v1.0#'$BUILDIMG_TASK_SCHEDULER'#' airavata-kubernetes/scripts/k8s/task-scheduler/task-scheduler-dep.yml | kubectl apply -f -"
         sh "kubectl rollout status deployment/task-scheduler"
 
-        sh "sed 's#dimuthuupe/command-task:v1.0#'BUILDIMG_COMMAND_TASK'#' airavata-kubernetes/scripts/k8s/command-task/command-task-dep.yml | kubectl apply -f -"
+        sh "sed 's#dimuthuupe/command-task:v1.0#'$BUILDIMG_COMMAND_TASK'#' airavata-kubernetes/scripts/k8s/command-task/command-task-dep.yml | kubectl apply -f -"
         sh "kubectl rollout status deployment/command-task"
 
-        sh "sed 's#dimuthuupe/data-in-task:v1.0#'BUILDIMG_DATA_IN_TASK'#' airavata-kubernetes/scripts/k8s/data-in-task/data-in-task-dep.yml | kubectl apply -f -"
+        sh "sed 's#dimuthuupe/data-in-task:v1.0#'$BUILDIMG_DATA_IN_TASK'#' airavata-kubernetes/scripts/k8s/data-in-task/data-in-task-dep.yml | kubectl apply -f -"
         sh "kubectl rollout status deployment/data-in-task"
 
-        sh "sed 's#dimuthuupe/data-out-task:v1.0#'BUILDIMG_DATA_OUT_TASK'#' airavata-kubernetes/scripts/k8s/data-out-task/data-out-task-dep.yml | kubectl apply -f -"
+        sh "sed 's#dimuthuupe/data-out-task:v1.0#'$BUILDIMG_DATA_OUT_TASK'#' airavata-kubernetes/scripts/k8s/data-out-task/data-out-task-dep.yml | kubectl apply -f -"
         sh "kubectl rollout status deployment/data-out-task"
 
 }
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
"commits@airavata.apache.org" <co...@airavata.apache.org>.

[airavata-sandbox] 11/19: Updating Jenkinsfile to build parent pom

Posted by sm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

smarru pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-sandbox.git

commit f8cc315043f2415ad451682c82e323877fa0a75c
Author: dimuthu.upeksha2@gmail.com <Di...@1234>
AuthorDate: Sun Nov 26 07:10:48 2017 +0530

    Updating Jenkinsfile to build parent pom
---
 airavata-kubernetes/scripts/k8s/ci-cd/Jenkinsfile | 1 +
 1 file changed, 1 insertion(+)

diff --git a/airavata-kubernetes/scripts/k8s/ci-cd/Jenkinsfile b/airavata-kubernetes/scripts/k8s/ci-cd/Jenkinsfile
index b7a5b32..a8ea07f 100644
--- a/airavata-kubernetes/scripts/k8s/ci-cd/Jenkinsfile
+++ b/airavata-kubernetes/scripts/k8s/ci-cd/Jenkinsfile
@@ -31,6 +31,7 @@ node {
     env.BUILDIMG_DATA_OUT_TASK=data_out_task_imageName
 
     stage "Build"
+        sh "/opt/maven/bin/mvn -N -f airavata-kubernetes clean install"
         sh "/opt/maven/bin/mvn -f airavata-kubernetes/modules/api-resource/ clean install"
         sh "/opt/maven/bin/mvn -f airavata-kubernetes/modules/compute-resource-api/ clean install"
         sh "/opt/maven/bin/mvn -f airavata-kubernetes/modules/helix-task-api/ clean install"

-- 
To stop receiving notification emails like this one, please contact
"commits@airavata.apache.org" <co...@airavata.apache.org>.

[airavata-sandbox] 04/19: Initial implementation of task execution engine on top of helix

Posted by sm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

smarru pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-sandbox.git

commit 23b761ec3813f6db12ad2d851924d40021017fd9
Author: dimuthu.upeksha2@gmail.com <Di...@1234>
AuthorDate: Sun Nov 19 01:19:04 2017 +0530

    Initial implementation of task execution engine on top of helix
---
 .../k8s/compute/impl/SSHComputeOperations.java     | 152 ++++++++++++-------
 airavata-kubernetes/modules/helix-tasks/pom.xml    |  89 +++++++++++
 .../org/apache/airavata/helix/HelixCluster.java    |  48 ++++++
 .../org/apache/airavata/helix/HelixController.java |  85 +++++++++++
 .../org/apache/airavata/helix/HelixManager.java    |  14 ++
 .../apache/airavata/helix/HelixParticipant.java    | 143 ++++++++++++++++++
 .../org/apache/airavata/helix/WorkflowManager.java |  86 +++++++++++
 .../apache/airavata/helix/tasks/AbstractTask.java  | 139 ++++++++++++++++++
 .../airavata/helix/tasks/DataCollectingTask.java   |  32 ++++
 .../airavata/helix/tasks/DataPushingTask.java      |  39 +++++
 .../airavata/helix/tasks/command/CommandTask.java  | 137 +++++++++++++++++
 .../airavata/helix/tasks/command/Participant.java  |  55 +++++++
 .../airavata/helix/tasks/datain/DataInputTask.java | 123 ++++++++++++++++
 .../airavata/helix/tasks/datain/Participant.java   |  56 +++++++
 .../helix/tasks/dataout/DataOutputTask.java        | 128 ++++++++++++++++
 .../airavata/helix/tasks/dataout/Participant.java  |  54 +++++++
 .../src/main/resources/log4j.properties            |   9 ++
 .../api/server/controller/DataStoreController.java |   9 +-
 .../k8s/api/server/model/data/DataStoreModel.java  |  11 +-
 .../k8s/api/server/service/WorkflowService.java    |   6 +-
 .../api/server/service/data/DataStoreService.java  |   7 +-
 .../api/server/service/util/ToResourceUtil.java    |   6 +-
 .../modules/microservices/task-scheduler/pom.xml   |  18 +++
 .../k8s/gfac/core/HelixWorkflowManager.java        | 162 +++++++++++++++++++++
 .../airavata/k8s/gfac/messaging/KafkaReceiver.java |  12 +-
 .../k8s/gfac/service/HelixWorkflowService.java     |  17 +++
 .../airavata/k8s/gfac/service/WorkerService.java   |  31 +++-
 .../src/main/resources/log4j.properties            |   9 ++
 airavata-kubernetes/pom.xml                        |   1 +
 29 files changed, 1593 insertions(+), 85 deletions(-)

diff --git a/airavata-kubernetes/modules/compute-resource-api/src/main/java/org/apache/airavata/k8s/compute/impl/SSHComputeOperations.java b/airavata-kubernetes/modules/compute-resource-api/src/main/java/org/apache/airavata/k8s/compute/impl/SSHComputeOperations.java
index 140c5b6..fb77981 100644
--- a/airavata-kubernetes/modules/compute-resource-api/src/main/java/org/apache/airavata/k8s/compute/impl/SSHComputeOperations.java
+++ b/airavata-kubernetes/modules/compute-resource-api/src/main/java/org/apache/airavata/k8s/compute/impl/SSHComputeOperations.java
@@ -1,5 +1,4 @@
 /**
- *
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
  * distributed with this work for additional information
@@ -7,9 +6,9 @@
  * to you under the Apache License, Version 2.0 (the
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -51,46 +50,11 @@ public class SSHComputeOperations implements ComputeOperations {
         this.port = port;
     }
 
-    public ExecutionResult executeCommand(String command) throws JSchException, IOException {
-        JSch jsch = new JSch();
-        Session session = jsch.getSession(userName, this.computeHost, port);
-        session.setConfig("StrictHostKeyChecking", "no");
+    public ExecutionResult executeCommand(String command) throws Exception {
+        Session session = getConnectedSession(this.userName, this.password, this.computeHost, this.port);
 
-        session.setUserInfo(new UserInfo() {
-            @Override
-            public String getPassphrase() {
-                return password;
-            }
-
-            @Override
-            public String getPassword() {
-                return password;
-            }
-
-            @Override
-            public boolean promptPassword(String s) {
-                return true;
-            }
-
-            @Override
-            public boolean promptPassphrase(String s) {
-                return false;
-            }
-
-            @Override
-            public boolean promptYesNo(String s) {
-                return false;
-            }
-
-            @Override
-            public void showMessage(String s) {
-
-            }
-        });
-
-        session.connect();
-        Channel channel=session.openChannel("exec");
-        ((ChannelExec)channel).setCommand(command);
+        Channel channel = session.openChannel("exec");
+        ((ChannelExec) channel).setCommand(command);
 
         ByteArrayOutputStream sysOut = new ByteArrayOutputStream();
         channel.setOutputStream(sysOut);
@@ -104,9 +68,9 @@ public class SSHComputeOperations implements ComputeOperations {
         ExecutionResult result = new ExecutionResult();
         byte[] tmp = new byte[1024];
         while (true) {
-            while (in.available()>0) {
+            while (in.available() > 0) {
                 int i = in.read(tmp, 0, 1024);
-                if (i<0) break;
+                if (i < 0) break;
                 System.out.print(new String(tmp, 0, i));
             }
             if (channel.isClosed()) {
@@ -117,7 +81,8 @@ public class SSHComputeOperations implements ComputeOperations {
             }
             try {
                 Thread.sleep(1000);
-            } catch(Exception e){}
+            } catch (Exception e) {
+            }
         }
 
         channel.disconnect();
@@ -128,16 +93,27 @@ public class SSHComputeOperations implements ComputeOperations {
         return result;
     }
 
-    public void transferDataIn(String source, String target, String protocol) {
-
+    public void transferDataIn(String source, String target, String protocol) throws Exception {
+        Session session = getConnectedSession(this.userName, this.password, this.computeHost, this.port);
+        copyLocalToRemote(session, source, target);
     }
 
     public void transferDataOut(String source, String target, String protocol) throws Exception {
+        Session session = getConnectedSession(this.userName, this.password, this.computeHost, this.port);
+        copyRemoteToLocal(session, source, target);
+    }
+
+    private static Session getConnectedSession(String userName, String password, String computeHost, int port) throws Exception {
         JSch jsch = new JSch();
-        Session session = jsch.getSession(userName, this.computeHost, port);
+        Session session = jsch.getSession(userName, computeHost, port);
         session.setConfig("StrictHostKeyChecking", "no");
+        session.setUserInfo(getUserInfo(password));
+        session.connect();
+        return session;
+    }
 
-        session.setUserInfo(new UserInfo() {
+    private static UserInfo getUserInfo(String password) {
+        return new UserInfo() {
             @Override
             public String getPassphrase() {
                 return password;
@@ -167,11 +143,79 @@ public class SSHComputeOperations implements ComputeOperations {
             public void showMessage(String s) {
 
             }
-        });
+        };
+    }
 
-        session.connect();
+    private static void copyLocalToRemote(Session session, String source, String target) throws Exception {
 
-        copyRemoteToLocal(session, source, target);
+        FileInputStream fis = null;
+        boolean ptimestamp = true;
+
+        // exec 'scp -t rfile' remotely
+        String command = "scp " + (ptimestamp ? "-p" : "") + " -t " + target;
+        Channel channel = session.openChannel("exec");
+        ((ChannelExec) channel).setCommand(command);
+
+        // get I/O streams for remote scp
+        OutputStream out = channel.getOutputStream();
+        InputStream in = channel.getInputStream();
+
+        channel.connect();
+
+        if (checkAck(in) != 0) {
+            return;
+        }
+
+        File _lfile = new File(source);
+
+        if (ptimestamp) {
+            command = "T " + (_lfile.lastModified() / 1000) + " 0";
+            // The access time should be sent here,
+            // but it is not accessible with JavaAPI ;-<
+            command += (" " + (_lfile.lastModified() / 1000) + " 0\n");
+            out.write(command.getBytes());
+            out.flush();
+            if (checkAck(in) != 0) {
+                return;
+            }
+        }
+
+        // send "C0644 filesize filename", where filename should not include '/'
+        long filesize = _lfile.length();
+        command = "C0644 " + filesize + " ";
+        if (source.lastIndexOf('/') > 0) {
+            command += source.substring(source.lastIndexOf('/') + 1);
+        } else {
+            command += source;
+        }
+        command += "\n";
+        out.write(command.getBytes());
+        out.flush();
+        if (checkAck(in) != 0) {
+            return;
+        }
+
+        // send a content of lfile
+        fis = new FileInputStream(source);
+        byte[] buf = new byte[1024];
+        while (true) {
+            int len = fis.read(buf, 0, buf.length);
+            if (len <= 0) break;
+            out.write(buf, 0, len); //out.flush();
+        }
+        fis.close();
+        fis = null;
+        // send '\0'
+        buf[0] = 0;
+        out.write(buf, 0, 1);
+        out.flush();
+        if (checkAck(in) != 0) {
+            return;
+        }
+        out.close();
+
+        channel.disconnect();
+        session.disconnect();
     }
 
     private static void copyRemoteToLocal(Session session, String source, String target) throws JSchException, IOException {
@@ -246,7 +290,7 @@ public class SSHComputeOperations implements ComputeOperations {
             }
 
             if (checkAck(in) != 0) {
-                System.exit(0);
+                return;
             }
 
             // send '\0'
diff --git a/airavata-kubernetes/modules/helix-tasks/pom.xml b/airavata-kubernetes/modules/helix-tasks/pom.xml
new file mode 100644
index 0000000..568c922
--- /dev/null
+++ b/airavata-kubernetes/modules/helix-tasks/pom.xml
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>airavata-kubernetes</artifactId>
+        <groupId>org.apache.airavata</groupId>
+        <version>1.0-SNAPSHOT</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>helix-tasks</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-web</artifactId>
+            <version>3.0.2.RELEASE</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.airavata</groupId>
+            <artifactId>api-resource</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.airavata</groupId>
+            <artifactId>compute-resource-api</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.kafka</groupId>
+            <artifactId>kafka-clients</artifactId>
+            <version>0.10.1.1</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>2.5</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.helix</groupId>
+            <artifactId>helix-core</artifactId>
+            <version>0.6.7</version>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <version>1.7.5</version>
+            <scope>compile</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-log4j12</artifactId>
+            <version>1.7.5</version>
+        </dependency>
+        <dependency>
+            <groupId>org.testng</groupId>
+            <artifactId>testng</artifactId>
+            <version>6.0.1</version>
+        </dependency>
+        <dependency>
+            <groupId>com.jcraft</groupId>
+            <artifactId>jsch</artifactId>
+            <version>0.1.53</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.curator</groupId>
+            <artifactId>curator-framework</artifactId>
+            <version>2.8.0</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.5.1</version>
+                <configuration>
+                    <source>${java.version}</source>
+                    <target>${java.version}</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/HelixCluster.java b/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/HelixCluster.java
new file mode 100644
index 0000000..2b96328
--- /dev/null
+++ b/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/HelixCluster.java
@@ -0,0 +1,48 @@
+package org.apache.airavata.helix;
+
+import org.apache.helix.manager.zk.ZKHelixAdmin;
+import org.apache.helix.manager.zk.ZNRecordSerializer;
+import org.apache.helix.manager.zk.ZkClient;
+import org.apache.helix.model.OnlineOfflineSMD;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public class HelixCluster {
+
+    private static final Logger logger = LogManager.getLogger(HelixCluster.class);
+
+    private String zkAddress;
+    private String clusterName;
+    private int numPartitions;
+
+    private ZkClient zkClient;
+    private ZKHelixAdmin zkHelixAdmin;
+
+    public HelixCluster(String zkAddress, String clusterName, int numPartitions) {
+        this.zkAddress = zkAddress;
+        this.clusterName = clusterName;
+        this.numPartitions = numPartitions;
+
+        zkClient = new ZkClient(this.zkAddress, ZkClient.DEFAULT_SESSION_TIMEOUT,
+                ZkClient.DEFAULT_CONNECTION_TIMEOUT, new ZNRecordSerializer());
+        zkHelixAdmin = new ZKHelixAdmin(zkClient);
+    }
+
+    public void setup() {
+        zkHelixAdmin.addCluster(clusterName, true);
+        zkHelixAdmin.addStateModelDef(clusterName, OnlineOfflineSMD.name, OnlineOfflineSMD.build());
+        logger.info("Cluster: " +  clusterName + ", has been added.");
+    }
+
+    public void disconnect() {
+        if (zkClient != null) {
+            zkClient.close();
+        }
+    }
+}
diff --git a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/HelixController.java b/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/HelixController.java
new file mode 100644
index 0000000..8237d43
--- /dev/null
+++ b/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/HelixController.java
@@ -0,0 +1,85 @@
+package org.apache.airavata.helix;
+
+import org.apache.helix.*;
+import org.apache.helix.controller.HelixControllerMain;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public class HelixController implements Runnable {
+
+    private static final Logger logger = LogManager.getLogger(HelixController.class);
+
+    private String clusterName;
+    private String controllerName;
+    private String zkAddress;
+    private org.apache.helix.HelixManager zkHelixManager;
+
+    private CountDownLatch startLatch = new CountDownLatch(1);
+    private CountDownLatch stopLatch = new CountDownLatch(1);
+
+    public HelixController(String zkAddress, String clusterName, String controllerName) {
+        this.clusterName = clusterName;
+        this.controllerName = controllerName;
+        this.zkAddress = zkAddress;
+    }
+
+    public void run() {
+        try {
+            zkHelixManager = HelixControllerMain.startHelixController(zkAddress, clusterName,
+                    controllerName, HelixControllerMain.STANDALONE);
+            startLatch.countDown();
+            stopLatch.await();
+        } catch (Exception ex) {
+            logger.error("Error in run() for Controller: " + controllerName + ", reason: " + ex, ex);
+        } finally {
+            disconnect();
+        }
+
+    }
+
+    public void start() {
+        new Thread(this).start();
+        try {
+            startLatch.await();
+            logger.info("Controller: " + controllerName + ", has connected to cluster: " + clusterName);
+
+            Runtime.getRuntime().addShutdownHook(
+                    new Thread() {
+                        @Override
+                        public void run() {
+                            disconnect();
+                        }
+                    }
+            );
+
+        } catch (InterruptedException ex) {
+            logger.error("Controller: " + controllerName + ", is interrupted! reason: " + ex, ex);
+        }
+
+    }
+
+    public void stop() {
+        stopLatch.countDown();
+    }
+
+    private void disconnect() {
+        if (zkHelixManager != null) {
+            logger.info("Controller: " + controllerName + ", has disconnected from cluster: " + clusterName);
+            zkHelixManager.disconnect();
+        }
+    }
+
+    public static void main(String args[]) {
+        HelixController helixController = new HelixController("localhost:2199", "AiravataDemoCluster", "AiravataController");
+        helixController.start();
+    }
+
+}
diff --git a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/HelixManager.java b/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/HelixManager.java
new file mode 100644
index 0000000..e09f307
--- /dev/null
+++ b/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/HelixManager.java
@@ -0,0 +1,14 @@
+package org.apache.airavata.helix;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public class HelixManager {
+    public static void main(String args[]) {
+        HelixCluster helixCluster = new HelixCluster("localhost:2199", "AiravataDemoCluster", 1);
+        helixCluster.setup();
+    }
+}
diff --git a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/HelixParticipant.java b/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/HelixParticipant.java
new file mode 100644
index 0000000..cbbc300
--- /dev/null
+++ b/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/HelixParticipant.java
@@ -0,0 +1,143 @@
+package org.apache.airavata.helix;
+
+import org.apache.airavata.helix.tasks.command.CommandTask;
+import org.apache.airavata.helix.tasks.DataCollectingTask;
+import org.apache.airavata.helix.tasks.DataPushingTask;
+import org.apache.airavata.k8s.api.resources.task.type.TaskTypeResource;
+import org.apache.helix.InstanceType;
+import org.apache.helix.examples.OnlineOfflineStateModelFactory;
+import org.apache.helix.manager.zk.ZKHelixAdmin;
+import org.apache.helix.manager.zk.ZKHelixManager;
+import org.apache.helix.manager.zk.ZNRecordSerializer;
+import org.apache.helix.manager.zk.ZkClient;
+import org.apache.helix.model.BuiltInStateModelDefinitions;
+import org.apache.helix.model.InstanceConfig;
+import org.apache.helix.participant.StateMachineEngine;
+import org.apache.helix.task.Task;
+import org.apache.helix.task.TaskCallbackContext;
+import org.apache.helix.task.TaskFactory;
+import org.apache.helix.task.TaskStateModelFactory;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public abstract class HelixParticipant implements Runnable {
+
+    private static final Logger logger = LogManager.getLogger(HelixParticipant.class);
+
+    private String zkAddress;
+    private String clusterName;
+    private String participantName;
+    private ZKHelixManager zkHelixManager;
+    private String taskTypeName;
+    private String apiServerUrl;
+    private RestTemplate restTemplate;
+
+    public HelixParticipant(String zkAddress,
+                            String clusterName,
+                            String participantName,
+                            String taskTypeName,
+                            String apiServerUrl) {
+
+        logger.debug("Initializing Participant Node");
+        this.zkAddress = zkAddress;
+        this.clusterName = clusterName;
+        this.participantName = participantName;
+        this.taskTypeName = taskTypeName;
+        this.apiServerUrl = apiServerUrl;
+        this.restTemplate = new RestTemplate();
+    }
+
+    public abstract Map<String, TaskFactory> getTaskFactory();
+
+    public abstract TaskTypeResource getTaskType();
+
+    public void run() {
+        ZkClient zkClient = null;
+        try {
+            zkClient = new ZkClient(zkAddress, ZkClient.DEFAULT_SESSION_TIMEOUT,
+                    ZkClient.DEFAULT_CONNECTION_TIMEOUT, new ZNRecordSerializer());
+            ZKHelixAdmin zkHelixAdmin = new ZKHelixAdmin(zkClient);
+
+            List<String> nodesInCluster = zkHelixAdmin.getInstancesInCluster(clusterName);
+
+            if (!nodesInCluster.contains(participantName)) {
+                InstanceConfig instanceConfig = new InstanceConfig(participantName);
+                instanceConfig.setHostName("localhost");
+                instanceConfig.setInstanceEnabled(true);
+                instanceConfig.addTag(taskTypeName);
+                zkHelixAdmin.addInstance(clusterName, instanceConfig);
+                logger.debug("Instance: " + participantName + ", has been added to cluster: " + clusterName);
+            } else {
+                zkHelixAdmin.addInstanceTag(clusterName, participantName, taskTypeName);
+            }
+
+            Runtime.getRuntime().addShutdownHook(
+                    new Thread() {
+                        @Override
+                        public void run() {
+                            logger.debug("Participant: " + participantName + ", shutdown hook called.");
+                            disconnect();
+                        }
+                    }
+            );
+
+            // connect the participant manager
+            register();
+            connect();
+        } catch (Exception ex) {
+            logger.error("Error in run() for Participant: " + participantName + ", reason: " + ex, ex);
+        } finally {
+            if (zkClient != null) {
+                zkClient.close();
+            }
+        }
+    }
+
+    private void register() {
+        this.restTemplate.postForObject("http://" + apiServerUrl + "/taskType", getTaskType(), Long.class);
+    }
+
+    private void connect() {
+        try {
+            zkHelixManager = new ZKHelixManager(clusterName, participantName, InstanceType.PARTICIPANT, zkAddress);
+            // register online-offline model
+            StateMachineEngine machineEngine = zkHelixManager.getStateMachineEngine();
+            OnlineOfflineStateModelFactory factory = new OnlineOfflineStateModelFactory(participantName);
+            machineEngine.registerStateModelFactory(BuiltInStateModelDefinitions.OnlineOffline.name(), factory);
+
+            // register task model
+            machineEngine.registerStateModelFactory("Task", new TaskStateModelFactory(zkHelixManager, getTaskFactory()));
+            logger.debug("Participant: " + participantName + ", registered state model factories.");
+
+            zkHelixManager.connect();
+            logger.info("Participant: " + participantName + ", has connected to cluster: " + clusterName);
+
+            Thread.currentThread().join();
+        } catch (InterruptedException ex) {
+            logger.error("Participant: " + participantName + ", is interrupted! reason: " + ex, ex);
+        }
+        catch (Exception ex) {
+            logger.error("Error in connect() for Participant: " + participantName + ", reason: " + ex, ex);
+        } finally {
+            disconnect();
+        }
+    }
+
+    private void disconnect() {
+        if (zkHelixManager != null) {
+            logger.info("Participant: " + participantName + ", has disconnected from cluster: " + clusterName);
+            zkHelixManager.disconnect();
+        }
+    }
+}
diff --git a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/WorkflowManager.java b/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/WorkflowManager.java
new file mode 100644
index 0000000..6866af3
--- /dev/null
+++ b/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/WorkflowManager.java
@@ -0,0 +1,86 @@
+package org.apache.airavata.helix;
+
+import org.apache.airavata.helix.tasks.command.CommandTask;
+import org.apache.airavata.helix.tasks.DataCollectingTask;
+import org.apache.airavata.helix.tasks.DataPushingTask;
+import org.apache.helix.*;
+import org.apache.helix.task.*;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public class WorkflowManager {
+
+    private static final Logger logger = LogManager.getLogger(WorkflowManager.class);
+
+
+    public static void main(String args[]) {
+        Workflow workflow = createWorkflow().build();
+
+        org.apache.helix.HelixManager helixManager = HelixManagerFactory.getZKHelixManager("AiravataDemoCluster", "Admin",
+                InstanceType.SPECTATOR, "localhost:2199");
+
+        try {
+            helixManager.connect();
+            TaskDriver taskDriver = new TaskDriver(helixManager);
+
+            Runtime.getRuntime().addShutdownHook(
+                    new Thread() {
+                        @Override
+                        public void run() {
+                            helixManager.disconnect();
+                        }
+                    }
+            );
+
+            taskDriver.start(workflow);
+            logger.info("Started workflow");
+            TaskState taskState = taskDriver.pollForWorkflowState(workflow.getName(), TaskState.COMPLETED, TaskState.FAILED, TaskState.STOPPED, TaskState.ABORTED);
+            System.out.println("Task state " + taskState.name());
+
+        } catch (Exception ex) {
+            logger.error("Error in connect() for Admin, reason: " + ex, ex);
+        }
+    }
+
+    private static Workflow.Builder createWorkflow() {
+        List<TaskConfig> downloadDataTasks = new ArrayList<>();
+        downloadDataTasks.add(new TaskConfig.Builder().setTaskId("Download_Task").setCommand(DataCollectingTask.NAME).build());
+
+        List<TaskConfig> commandExecuteTasks = new ArrayList<>();
+        commandExecuteTasks.add(new TaskConfig.Builder().setTaskId("Command_Task").setCommand(CommandTask.NAME).build());
+
+        List<TaskConfig> pushDataTasks = new ArrayList<>();
+        pushDataTasks.add(new TaskConfig.Builder().setTaskId("Push_Task").setCommand(DataPushingTask.NAME).build());
+
+        JobConfig.Builder downloadDataJob = new JobConfig.Builder()
+                .addTaskConfigs(downloadDataTasks)
+                .setMaxAttemptsPerTask(3).setInstanceGroupTag("p1");
+
+        JobConfig.Builder commandExecuteJob = new JobConfig.Builder()
+                .addTaskConfigs(commandExecuteTasks)
+                .setMaxAttemptsPerTask(3).setInstanceGroupTag("p2");
+
+        JobConfig.Builder dataPushJob = new JobConfig.Builder()
+                .addTaskConfigs(pushDataTasks)
+                .setMaxAttemptsPerTask(3).setInstanceGroupTag("p3");
+
+        Workflow.Builder workflow = new Workflow.Builder("Airavata_Workflow3").setExpiry(0);
+        workflow.addJob("downloadDataJob", downloadDataJob);
+        workflow.addJob("commandExecuteJob", commandExecuteJob);
+        workflow.addJob("dataPushJob", dataPushJob);
+
+        workflow.addParentChildDependency("downloadDataJob", "commandExecuteJob");
+        workflow.addParentChildDependency("downloadDataJob", "dataPushJob");
+
+        return workflow;
+    }
+}
diff --git a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/AbstractTask.java b/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/AbstractTask.java
new file mode 100644
index 0000000..8e581ea
--- /dev/null
+++ b/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/AbstractTask.java
@@ -0,0 +1,139 @@
+package org.apache.airavata.helix.tasks;
+
+import org.apache.airavata.k8s.api.resources.compute.ComputeResource;
+import org.apache.airavata.k8s.api.resources.task.type.TaskTypeResource;
+import org.apache.airavata.k8s.compute.api.ComputeOperations;
+import org.apache.airavata.k8s.compute.impl.MockComputeOperation;
+import org.apache.airavata.k8s.compute.impl.SSHComputeOperations;
+import org.apache.helix.task.Task;
+import org.apache.helix.task.TaskCallbackContext;
+import org.apache.helix.task.TaskResult;
+import org.apache.helix.task.UserContentStore;
+import org.apache.kafka.clients.producer.KafkaProducer;
+import org.apache.kafka.clients.producer.Producer;
+import org.apache.kafka.clients.producer.ProducerRecord;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.Properties;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public abstract class AbstractTask extends UserContentStore implements Task {
+
+    public static final String NEXT_JOB = "next-job";
+    public static final String WORKFLOW_STARTED = "workflow-started";
+    public static final String TASK_ID = "task_id";
+    public static final String PROCESS_ID = "process_id";
+
+    //Configurable values
+    private String apiServerUrl = "localhost:8080";
+    private String kafkaBootstrapUrl = "localhost:9092";
+    private String eventTopic = "airavata-task-event";
+
+    private TaskCallbackContext callbackContext;
+    private RestTemplate restTemplate;
+    private Producer<String, String> eventProducer;
+    private long processId;
+    private long taskId;
+
+    public AbstractTask(TaskCallbackContext callbackContext) {
+        this.callbackContext = callbackContext;
+        this.taskId = Long.parseLong(this.callbackContext.getTaskConfig().getConfigMap().get(TASK_ID));
+        this.processId = Long.parseLong(this.callbackContext.getTaskConfig().getConfigMap().get(PROCESS_ID));
+        this.restTemplate = new RestTemplate();
+        initializeKafkaEventProducer();
+        init();
+    }
+
+    public TaskCallbackContext getCallbackContext() {
+        return callbackContext;
+    }
+
+    @Override
+    public final TaskResult run() {
+        boolean isThisNextJob = getUserContent(WORKFLOW_STARTED, Scope.WORKFLOW) == null ||
+                this.callbackContext.getJobConfig().getJobId()
+                        .equals(this.callbackContext.getJobConfig().getWorkflow() + "_" + getUserContent(NEXT_JOB, Scope.WORKFLOW));
+        if (isThisNextJob) {
+            return onRun();
+        } else {
+            return new TaskResult(TaskResult.Status.COMPLETED, "Not a target job");
+        }
+    }
+
+    @Override
+    public final void cancel() {
+        onCancel();
+    }
+
+    public void init() {
+
+    }
+
+    public abstract TaskResult onRun();
+
+    public abstract void onCancel();
+
+    public void sendToOutPort(String port) {
+        putUserContent(WORKFLOW_STARTED, "TRUE", Scope.WORKFLOW);
+        String outJob = getCallbackContext().getTaskConfig().getConfigMap().get("OUT_" + port);
+        if (outJob != null) {
+            putUserContent(NEXT_JOB, outJob, Scope.WORKFLOW);
+        }
+    }
+
+    public RestTemplate getRestTemplate() {
+        return restTemplate;
+    }
+
+    public String getApiServerUrl() {
+        return apiServerUrl;
+    }
+
+    public ComputeOperations fetchComputeResourceOperation(ComputeResource computeResource) throws Exception {
+        ComputeOperations operations;
+        if ("SSH".equals(computeResource.getCommunicationType())) {
+            operations = new SSHComputeOperations(computeResource.getHost(), computeResource.getUserName(), computeResource.getPassword());
+        } else if ("Mock".equals(computeResource.getCommunicationType())) {
+            operations = new MockComputeOperation(computeResource.getHost());
+        } else {
+            throw new Exception("No compatible communication method {" + computeResource.getCommunicationType() + "} not found for compute resource " + computeResource.getName());
+        }
+        return operations;
+    }
+
+    public void initializeKafkaEventProducer() {
+        Properties props = new Properties();
+
+        props.put("bootstrap.servers", this.kafkaBootstrapUrl);
+
+        props.put("acks", "all");
+        props.put("retries", 0);
+        props.put("batch.size", 16384);
+        props.put("linger.ms", 1);
+        props.put("buffer.memory", 33554432);
+        props.put("key.serializer",
+                "org.apache.kafka.common.serialization.StringSerializer");
+        props.put("value.serializer",
+                "org.apache.kafka.common.serialization.StringSerializer");
+
+        eventProducer = new KafkaProducer<String, String>(props);
+    }
+
+    public void publishTaskStatus(long status, String reason) {
+        eventProducer.send(new ProducerRecord<String, String>(
+                this.eventTopic, String.join(",", this.processId + "", this.taskId + "", status + "", reason)));
+    }
+
+    public long getProcessId() {
+        return processId;
+    }
+
+    public long getTaskId() {
+        return taskId;
+    }
+}
diff --git a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/DataCollectingTask.java b/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/DataCollectingTask.java
new file mode 100644
index 0000000..594ce35
--- /dev/null
+++ b/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/DataCollectingTask.java
@@ -0,0 +1,32 @@
+package org.apache.airavata.helix.tasks;
+
+import org.apache.helix.task.Task;
+import org.apache.helix.task.TaskCallbackContext;
+import org.apache.helix.task.TaskResult;
+import org.apache.helix.task.UserContentStore;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public class DataCollectingTask extends UserContentStore implements Task {
+
+    public static final String NAME = "DATA_COLLECTING";
+
+    public DataCollectingTask(TaskCallbackContext callbackContext) {
+    }
+
+    public TaskResult run() {
+        System.out.println("Executing data collecting");
+        putUserContent("Key", "Hooo", Scope.WORKFLOW);
+
+        return new TaskResult(TaskResult.Status.COMPLETED, "HelixTaskB completed!");
+
+    }
+
+    public void cancel() {
+
+    }
+}
diff --git a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/DataPushingTask.java b/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/DataPushingTask.java
new file mode 100644
index 0000000..fc7ee8b
--- /dev/null
+++ b/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/DataPushingTask.java
@@ -0,0 +1,39 @@
+package org.apache.airavata.helix.tasks;
+
+import org.apache.helix.task.Task;
+import org.apache.helix.task.TaskCallbackContext;
+import org.apache.helix.task.TaskResult;
+import org.apache.helix.task.UserContentStore;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public class DataPushingTask extends UserContentStore implements Task {
+
+    public static final String NAME = "DATA_PUSHING";
+
+    public DataPushingTask(TaskCallbackContext callbackContext) {
+    }
+
+    public TaskResult run() {
+        System.out.println("Executing data pushing");
+        try {
+            Thread.currentThread().sleep(5000);
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+        System.out.println("Continuing");
+        String key2 = getUserContent("Key2", Scope.WORKFLOW);
+
+        System.out.println(key2);
+        return new TaskResult(TaskResult.Status.COMPLETED, "HelixTaskB completed!");
+
+    }
+
+    public void cancel() {
+
+    }
+}
diff --git a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/command/CommandTask.java b/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/command/CommandTask.java
new file mode 100644
index 0000000..5aae5b7
--- /dev/null
+++ b/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/command/CommandTask.java
@@ -0,0 +1,137 @@
+package org.apache.airavata.helix.tasks.command;
+
+import org.apache.airavata.helix.tasks.AbstractTask;
+import org.apache.airavata.k8s.api.resources.compute.ComputeResource;
+import org.apache.airavata.k8s.api.resources.task.TaskStatusResource;
+import org.apache.airavata.k8s.api.resources.task.type.TaskInputTypeResource;
+import org.apache.airavata.k8s.api.resources.task.type.TaskOutPortTypeResource;
+import org.apache.airavata.k8s.api.resources.task.type.TaskTypeResource;
+import org.apache.airavata.k8s.compute.api.ExecutionResult;
+import org.apache.helix.task.TaskCallbackContext;
+import org.apache.helix.task.TaskResult;
+
+import java.util.Arrays;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public class CommandTask extends AbstractTask {
+
+    public static final String NAME = "COMMAND";
+
+    private String command;
+    private String arguments;
+    private String stdOutPath;
+    private String stdErrPath;
+    private String computeResourceId;
+    private ComputeResource computeResource;
+
+    public CommandTask(TaskCallbackContext callbackContext) {
+        super(callbackContext);
+    }
+
+    @Override
+    public void init() {
+        this.command = getCallbackContext().getTaskConfig().getConfigMap().get(PARAMS.COMMAND);
+        this.arguments = getCallbackContext().getTaskConfig().getConfigMap().get(PARAMS.ARGUMENTS);
+        this.stdOutPath = getCallbackContext().getTaskConfig().getConfigMap().get(PARAMS.STD_OUT_PATH);
+        this.stdErrPath = getCallbackContext().getTaskConfig().getConfigMap().get(PARAMS.STD_ERR_PATH);
+        this.computeResourceId = getCallbackContext().getTaskConfig().getConfigMap().get(PARAMS.COMPUTE_RESOURCE);
+        this.computeResource = this.getRestTemplate().getForObject("http://" + this.getApiServerUrl()
+                + "/compute/" + Long.parseLong(this.computeResourceId), ComputeResource.class);
+    }
+
+    public TaskResult onRun() {
+        System.out.println("Executing command " + command);
+        try {
+
+            String stdOutSuffix = " > " + stdOutPath + " 2> " + stdErrPath;
+
+            publishTaskStatus(TaskStatusResource.State.EXECUTING, "");
+
+            String finalCommand = command + (arguments != null ? arguments : "") + stdOutSuffix;
+
+            System.out.println("Executing command " + finalCommand);
+
+            ExecutionResult executionResult = fetchComputeResourceOperation(computeResource).executeCommand(finalCommand);
+
+            if (executionResult.getExitStatus() == 0) {
+                publishTaskStatus(TaskStatusResource.State.COMPLETED, "Task completed");
+                sendToOutPort("Out");
+                return new TaskResult(TaskResult.Status.COMPLETED, "Task completed");
+
+            } else if (executionResult.getExitStatus() == -1) {
+                publishTaskStatus(TaskStatusResource.State.FAILED, "Process didn't exit successfully");
+                sendToOutPort("Error");
+                return new TaskResult(TaskResult.Status.FATAL_FAILED, "Task failed");
+
+            } else {
+                publishTaskStatus(TaskStatusResource.State.FAILED, "Process exited with error status " + executionResult.getExitStatus());
+                sendToOutPort("Error");
+                return new TaskResult(TaskResult.Status.FATAL_FAILED, "Task failed");
+            }
+
+        } catch (Exception e) {
+
+            e.printStackTrace();
+            publishTaskStatus(TaskStatusResource.State.FAILED, e.getMessage());
+            return new TaskResult(TaskResult.Status.FATAL_FAILED, "Task failed");
+        }
+    }
+
+    public void onCancel() {
+
+    }
+
+    public static TaskTypeResource getTaskType() {
+        TaskTypeResource taskTypeResource = new TaskTypeResource();
+        taskTypeResource.setName(NAME);
+        taskTypeResource.setTopicName("airavata-command");
+        taskTypeResource.setIcon("assets/icons/ssh.png");
+        taskTypeResource.getInputTypes().addAll(
+                Arrays.asList(
+                        new TaskInputTypeResource()
+                                .setName(PARAMS.COMMAND)
+                                .setType("String")
+                                .setDefaultValue(""),
+                        new TaskInputTypeResource()
+                                .setName(PARAMS.ARGUMENTS)
+                                .setType("String")
+                                .setDefaultValue(""),
+                        new TaskInputTypeResource()
+                                .setName(PARAMS.COMPUTE_RESOURCE)
+                                .setType("Long")
+                                .setDefaultValue(""),
+                        new TaskInputTypeResource()
+                                .setName(PARAMS.STD_OUT_PATH)
+                                .setType("String")
+                                .setDefaultValue(""),
+                        new TaskInputTypeResource()
+                                .setName(PARAMS.STD_ERR_PATH)
+                                .setType("String")
+                                .setDefaultValue("")));
+
+        taskTypeResource.getOutPorts().addAll(
+                Arrays.asList(
+                        new TaskOutPortTypeResource()
+                                .setName("Out")
+                                .setOrder(0),
+                        new TaskOutPortTypeResource()
+                                .setName("Error")
+                                .setOrder(1))
+        );
+
+        return taskTypeResource;
+    }
+
+    public static final class PARAMS {
+        public static final String COMMAND = "command";
+        public static final String ARGUMENTS = "arguments";
+        public static final String STD_OUT_PATH = "std_out_path";
+        public static final String STD_ERR_PATH = "std_err_path";
+        public static final String COMPUTE_RESOURCE = "compute_resource";
+    }
+}
diff --git a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/command/Participant.java b/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/command/Participant.java
new file mode 100644
index 0000000..f852d8f
--- /dev/null
+++ b/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/command/Participant.java
@@ -0,0 +1,55 @@
+package org.apache.airavata.helix.tasks.command;
+
+import org.apache.airavata.helix.HelixParticipant;
+import org.apache.airavata.helix.tasks.DataCollectingTask;
+import org.apache.airavata.helix.tasks.DataPushingTask;
+import org.apache.airavata.k8s.api.resources.task.type.TaskTypeResource;
+import org.apache.helix.task.Task;
+import org.apache.helix.task.TaskCallbackContext;
+import org.apache.helix.task.TaskFactory;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public class Participant extends HelixParticipant {
+
+    public Participant(String zkAddress, String clusterName, String participantName, String taskTypeName, String apiServerUrl) {
+        super(zkAddress, clusterName, participantName, taskTypeName, apiServerUrl);
+    }
+
+    @Override
+    public Map<String, TaskFactory> getTaskFactory() {
+        Map<String, TaskFactory> taskRegistry = new HashMap<String, TaskFactory>();
+
+        TaskFactory commandTaskFac = new TaskFactory() {
+            @Override
+            public Task createNewTask(TaskCallbackContext context) {
+                return new CommandTask(context);
+            }
+        };
+
+        taskRegistry.put(CommandTask.NAME, commandTaskFac);
+
+        return taskRegistry;
+    }
+
+    @Override
+    public TaskTypeResource getTaskType() {
+        return CommandTask.getTaskType();
+    }
+
+    public static void main(String args[]) {
+        HelixParticipant participant = new Participant(
+                "localhost:2199",
+                "AiravataDemoCluster",
+                "command-p1", CommandTask.NAME,
+                "localhost:8080");
+        new Thread(participant).start();
+    }
+}
diff --git a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/datain/DataInputTask.java b/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/datain/DataInputTask.java
new file mode 100644
index 0000000..41d6aa4
--- /dev/null
+++ b/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/datain/DataInputTask.java
@@ -0,0 +1,123 @@
+package org.apache.airavata.helix.tasks.datain;
+
+import org.apache.airavata.helix.tasks.AbstractTask;
+import org.apache.airavata.k8s.api.resources.compute.ComputeResource;
+import org.apache.airavata.k8s.api.resources.task.TaskStatusResource;
+import org.apache.airavata.k8s.api.resources.task.type.TaskInputTypeResource;
+import org.apache.airavata.k8s.api.resources.task.type.TaskOutPortTypeResource;
+import org.apache.airavata.k8s.api.resources.task.type.TaskTypeResource;
+import org.apache.commons.io.FileUtils;
+import org.apache.helix.task.TaskCallbackContext;
+import org.apache.helix.task.TaskResult;
+
+import java.io.File;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.UUID;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public class DataInputTask extends AbstractTask {
+
+    public static final String NAME = "DATA_INPUT";
+
+    private String remoteSourcePath;
+    private String targetPath;
+    private String computeResourceId;
+    private ComputeResource computeResource;
+
+    public DataInputTask(TaskCallbackContext callbackContext) {
+        super(callbackContext);
+    }
+
+    @Override
+    public void init() {
+        this.remoteSourcePath = getCallbackContext().getTaskConfig().getConfigMap().get(PARAMS.REMOTE_SOURCE_PATH);
+        this.targetPath = getCallbackContext().getTaskConfig().getConfigMap().get(PARAMS.TARGET_PATH);
+        this.computeResourceId = getCallbackContext().getTaskConfig().getConfigMap().get(PARAMS.COMPUTE_RESOURCE);
+        this.computeResource = this.getRestTemplate().getForObject("http://" + this.getApiServerUrl()
+                + "/compute/" + Long.parseLong(this.computeResourceId), ComputeResource.class);
+    }
+
+    @Override
+    public TaskResult onRun() {
+        try {
+            String tempFilePath = "/tmp/" + UUID.randomUUID().toString();
+            System.out.println("Creating tmp file " + tempFilePath);
+            publishTaskStatus(TaskStatusResource.State.EXECUTING, "");
+
+            if (remoteSourcePath.startsWith("http")) {
+                System.out.println("Downloading text file " + remoteSourcePath);
+                FileUtils.copyURLToFile(new URL(remoteSourcePath), new File(tempFilePath));
+
+            } else {
+                publishTaskStatus(TaskStatusResource.State.FAILED, "Unsupported source type");
+                sendToOutPort("Error");
+                return new TaskResult(TaskResult.Status.FATAL_FAILED, "Task failed");
+
+            }
+
+            System.out.println("Transferring file to remote resource");
+            fetchComputeResourceOperation(computeResource).transferDataIn(tempFilePath, this.targetPath, "SCP");
+
+            publishTaskStatus(TaskStatusResource.State.COMPLETED, "Task completed");
+            sendToOutPort("Out");
+            return new TaskResult(TaskResult.Status.COMPLETED, "Task completed");
+
+        } catch (Exception e) {
+
+            e.printStackTrace();
+            publishTaskStatus(TaskStatusResource.State.FAILED, e.getMessage());
+            sendToOutPort("Error");
+            return new TaskResult(TaskResult.Status.FATAL_FAILED, "Task failed");
+        }
+    }
+
+    @Override
+    public void onCancel() {
+
+    }
+
+    public static TaskTypeResource getTaskType() {
+        TaskTypeResource taskTypeResource = new TaskTypeResource();
+        taskTypeResource.setName(NAME);
+        taskTypeResource.setTopicName("airavata-data-collect");
+        taskTypeResource.setIcon("assets/icons/copy.png");
+        taskTypeResource.getInputTypes().addAll(
+                Arrays.asList(
+                        new TaskInputTypeResource()
+                                .setName(PARAMS.REMOTE_SOURCE_PATH)
+                                .setType("String")
+                                .setDefaultValue(""),
+                        new TaskInputTypeResource()
+                                .setName(PARAMS.TARGET_PATH)
+                                .setType("String")
+                                .setDefaultValue(""),
+                        new TaskInputTypeResource()
+                                .setName(PARAMS.COMPUTE_RESOURCE)
+                                .setType("Long")
+                                .setDefaultValue("")));
+
+        taskTypeResource.getOutPorts().addAll(
+                Arrays.asList(
+                        new TaskOutPortTypeResource()
+                                .setName("Out")
+                                .setOrder(0),
+                        new TaskOutPortTypeResource()
+                                .setName("Error")
+                                .setOrder(1))
+        );
+
+        return taskTypeResource;
+    }
+
+    public static final class PARAMS {
+        public static final String REMOTE_SOURCE_PATH = "remote_source_path";
+        public static final String TARGET_PATH = "target_path";
+        public static final String COMPUTE_RESOURCE = "compute_resource";
+    }
+}
diff --git a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/datain/Participant.java b/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/datain/Participant.java
new file mode 100644
index 0000000..f06f56b
--- /dev/null
+++ b/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/datain/Participant.java
@@ -0,0 +1,56 @@
+package org.apache.airavata.helix.tasks.datain;
+
+import org.apache.airavata.helix.HelixParticipant;
+import org.apache.airavata.helix.tasks.command.CommandTask;
+import org.apache.airavata.k8s.api.resources.task.type.TaskTypeResource;
+import org.apache.helix.task.Task;
+import org.apache.helix.task.TaskCallbackContext;
+import org.apache.helix.task.TaskFactory;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public class Participant extends HelixParticipant {
+
+    public Participant(String zkAddress, String clusterName, String participantName,
+                       String taskTypeName,
+                       String apiServerUrl) {
+        super(zkAddress, clusterName, participantName, taskTypeName, apiServerUrl);
+    }
+
+    @Override
+    public Map<String, TaskFactory> getTaskFactory() {
+        Map<String, TaskFactory> taskRegistry = new HashMap<String, TaskFactory>();
+
+        TaskFactory dataInTask = new TaskFactory() {
+            @Override
+            public Task createNewTask(TaskCallbackContext context) {
+                return new DataInputTask(context);
+            }
+        };
+
+        taskRegistry.put(DataInputTask.NAME, dataInTask);
+
+        return taskRegistry;
+    }
+
+    @Override
+    public TaskTypeResource getTaskType() {
+        return DataInputTask.getTaskType();
+    }
+
+    public static void main(String args[]) {
+        HelixParticipant participant = new Participant(
+                "localhost:2199",
+                "AiravataDemoCluster",
+                "data-in-p1", DataInputTask.NAME,
+                "localhost:8080");
+        new Thread(participant).start();
+    }
+}
diff --git a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/dataout/DataOutputTask.java b/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/dataout/DataOutputTask.java
new file mode 100644
index 0000000..314e912
--- /dev/null
+++ b/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/dataout/DataOutputTask.java
@@ -0,0 +1,128 @@
+package org.apache.airavata.helix.tasks.dataout;
+
+import org.apache.airavata.helix.tasks.AbstractTask;
+import org.apache.airavata.k8s.api.resources.compute.ComputeResource;
+import org.apache.airavata.k8s.api.resources.task.TaskStatusResource;
+import org.apache.airavata.k8s.api.resources.task.type.TaskInputTypeResource;
+import org.apache.airavata.k8s.api.resources.task.type.TaskOutPortTypeResource;
+import org.apache.airavata.k8s.api.resources.task.type.TaskTypeResource;
+import org.apache.helix.task.TaskCallbackContext;
+import org.apache.helix.task.TaskResult;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.MediaType;
+import org.springframework.util.LinkedMultiValueMap;
+
+import java.util.Arrays;
+import java.util.UUID;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public class DataOutputTask extends AbstractTask {
+
+    public static final String NAME = "DATA_OUTPUT";
+
+    private String sourcePath;
+    private String identifier;
+    private String computeResourceId;
+    private ComputeResource computeResource;
+
+    public DataOutputTask(TaskCallbackContext callbackContext) {
+        super(callbackContext);
+    }
+
+    @Override
+    public void init() {
+        this.sourcePath = getCallbackContext().getTaskConfig().getConfigMap().get(PARAMS.SOURCE_PATH);
+        this.identifier = getCallbackContext().getTaskConfig().getConfigMap().get(PARAMS.IDENTIFIER);
+        this.computeResourceId = getCallbackContext().getTaskConfig().getConfigMap().get(PARAMS.COMPUTE_RESOURCE);
+        this.computeResource = this.getRestTemplate().getForObject("http://" + this.getApiServerUrl()
+                + "/compute/" + Long.parseLong(this.computeResourceId), ComputeResource.class);
+    }
+
+    @Override
+    public TaskResult onRun() {
+        try {
+            publishTaskStatus(TaskStatusResource.State.EXECUTING, "");
+
+            String temporaryFile = "/tmp/" + UUID.randomUUID().toString();
+            System.out.println("Downloading " + sourcePath + " to " + temporaryFile + " from compute resource "
+                    + computeResource.getName());
+
+            fetchComputeResourceOperation(computeResource).transferDataOut(sourcePath, temporaryFile, "SCP");
+
+            LinkedMultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
+            map.add("file", new FileSystemResource(temporaryFile));
+            HttpHeaders headers = new HttpHeaders();
+            headers.setContentType(MediaType.MULTIPART_FORM_DATA);
+
+            HttpEntity<LinkedMultiValueMap<String, Object>> requestEntity = new HttpEntity<>(map, headers);
+
+            System.out.println("Uploading data file with task id " + getTaskId() + " and identifier "
+                    + identifier + " to data store");
+
+            getRestTemplate().exchange("http://" + getApiServerUrl() + "/data/" + getTaskId() + "/"
+                    + identifier + "/upload", HttpMethod.POST, requestEntity, Long.class);
+
+            publishTaskStatus(TaskStatusResource.State.COMPLETED, "Task completed");
+            sendToOutPort("Out");
+            return new TaskResult(TaskResult.Status.COMPLETED, "Task completed");
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            publishTaskStatus(TaskStatusResource.State.FAILED, e.getMessage());
+            sendToOutPort("Error");
+            return new TaskResult(TaskResult.Status.FATAL_FAILED, "Task completed");
+
+        }
+    }
+
+    @Override
+    public void onCancel() {
+
+    }
+
+    public static final class PARAMS {
+        public static final String SOURCE_PATH = "source_path";
+        public static final String COMPUTE_RESOURCE = "compute_resource";
+        public static final String IDENTIFIER = "identifier";
+    }
+
+    public static TaskTypeResource getTaskType() {
+        TaskTypeResource taskTypeResource = new TaskTypeResource();
+        taskTypeResource.setName(NAME);
+        taskTypeResource.setTopicName("airavata-data-collect");
+        taskTypeResource.setIcon("assets/icons/copy.png");
+        taskTypeResource.getInputTypes().addAll(
+                Arrays.asList(
+                        new TaskInputTypeResource()
+                                .setName(PARAMS.SOURCE_PATH)
+                                .setType("String")
+                                .setDefaultValue(""),
+                        new TaskInputTypeResource()
+                                .setName(PARAMS.IDENTIFIER)
+                                .setType("String"),
+                        new TaskInputTypeResource()
+                                .setName(PARAMS.COMPUTE_RESOURCE)
+                                .setType("Long")));
+
+        taskTypeResource.getOutPorts().addAll(
+                Arrays.asList(
+                        new TaskOutPortTypeResource()
+                                .setName("Out")
+                                .setOrder(0),
+                        new TaskOutPortTypeResource()
+                                .setName("Error")
+                                .setOrder(1))
+        );
+
+        return taskTypeResource;
+
+    }
+}
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/dataout/Participant.java b/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/dataout/Participant.java
new file mode 100644
index 0000000..b226511
--- /dev/null
+++ b/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/dataout/Participant.java
@@ -0,0 +1,54 @@
+package org.apache.airavata.helix.tasks.dataout;
+
+import org.apache.airavata.helix.HelixParticipant;
+import org.apache.airavata.k8s.api.resources.task.type.TaskTypeResource;
+import org.apache.helix.task.Task;
+import org.apache.helix.task.TaskCallbackContext;
+import org.apache.helix.task.TaskFactory;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public class Participant extends HelixParticipant {
+
+    public Participant(String zkAddress, String clusterName, String participantName,
+                       String taskTypeName, String apiServerUrl) {
+        super(zkAddress, clusterName, participantName, taskTypeName, apiServerUrl);
+    }
+
+    @Override
+    public Map<String, TaskFactory> getTaskFactory() {
+        Map<String, TaskFactory> taskRegistry = new HashMap<String, TaskFactory>();
+
+        TaskFactory dataInTask = new TaskFactory() {
+            @Override
+            public Task createNewTask(TaskCallbackContext context) {
+                return new DataOutputTask(context);
+            }
+        };
+
+        taskRegistry.put(DataOutputTask.NAME, dataInTask);
+
+        return taskRegistry;
+    }
+
+    @Override
+    public TaskTypeResource getTaskType() {
+        return DataOutputTask.getTaskType();
+    }
+
+    public static void main(String args[]) {
+        HelixParticipant participant = new Participant(
+                "localhost:2199",
+                "AiravataDemoCluster",
+                "data-out-p1", DataOutputTask.NAME,
+                "localhost:8080");
+        new Thread(participant).start();
+    }
+}
diff --git a/airavata-kubernetes/modules/helix-tasks/src/main/resources/log4j.properties b/airavata-kubernetes/modules/helix-tasks/src/main/resources/log4j.properties
new file mode 100644
index 0000000..5e31e3c
--- /dev/null
+++ b/airavata-kubernetes/modules/helix-tasks/src/main/resources/log4j.properties
@@ -0,0 +1,9 @@
+# Set root logger level to DEBUG and its only appender to A1.
+log4j.rootLogger=INFO, A1
+
+# A1 is set to be a ConsoleAppender.
+log4j.appender.A1=org.apache.log4j.ConsoleAppender
+
+# A1 uses PatternLayout.
+log4j.appender.A1.layout=org.apache.log4j.PatternLayout
+log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/controller/DataStoreController.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/controller/DataStoreController.java
index 016011d..2a7f073 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/controller/DataStoreController.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/controller/DataStoreController.java
@@ -46,18 +46,19 @@ public class DataStoreController {
     @Resource
     private DataStoreService dataStoreService;
 
-    @PostMapping("{taskId}/{expOutputId}/upload")
+    @PostMapping("{taskId}/{identifier}/upload")
     public long uploadData(@RequestParam("file") MultipartFile file, @PathVariable("taskId") long taskId,
-                           @PathVariable("expOutputId") long expOutputId, RedirectAttributes redirectAttributes) {
+                           @PathVariable("identifier") String identifier, RedirectAttributes redirectAttributes) {
 
-        System.out.println("Received data for task id " + taskId + " and experiment output id " + expOutputId);
+        System.out.println("Received data for task id " + taskId + " and identifier " + identifier);
         if (file.isEmpty()) {
             throw new ServerRuntimeException("Data file is empty");
         }
         try {
             // Get the file and save it somewhere
             byte[] bytes = file.getBytes();
-            return this.dataStoreService.createEntry(taskId, expOutputId, bytes);
+            return this.dataStoreService.createEntry(taskId, identifier, bytes);
+
         } catch (IOException e) {
             e.printStackTrace();
             throw new ServerRuntimeException("Failed to store file", e);
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/data/DataStoreModel.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/data/DataStoreModel.java
index 71df7c4..6b42313 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/data/DataStoreModel.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/data/DataStoreModel.java
@@ -44,8 +44,7 @@ public class DataStoreModel {
     @Basic(fetch = FetchType.LAZY)
     private byte[] content;
 
-    @ManyToOne
-    private ExperimentOutputData experimentOutputData;
+    private String identifier;
 
     @ManyToOne
     private TaskModel taskModel;
@@ -68,12 +67,12 @@ public class DataStoreModel {
         return this;
     }
 
-    public ExperimentOutputData getExperimentOutputData() {
-        return experimentOutputData;
+    public String getIdentifier() {
+        return identifier;
     }
 
-    public DataStoreModel setExperimentOutputData(ExperimentOutputData experimentOutputData) {
-        this.experimentOutputData = experimentOutputData;
+    public DataStoreModel setIdentifier(String identifier) {
+        this.identifier = identifier;
         return this;
     }
 
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/WorkflowService.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/WorkflowService.java
index 68abab4..f4492fe 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/WorkflowService.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/WorkflowService.java
@@ -105,10 +105,10 @@ public class WorkflowService {
             });
 
             parseResult.getEdgeCache().forEach(((outPort, inPort) -> {
-                if (outPort.getParentOperation() != null && outPort.getNextPort().getParentOperation() != null) {
+                if (outPort.getParentTask() != null && outPort.getNextPort().getParentTask() != null) {
                     Optional<TaskOutPort> sourceOutPort = taskOutPortRepository
-                            .findByReferenceIdAndTaskModel_Id(outPort.getId(), outPort.getParentTask().getId());
-                    Optional<TaskModel> targetTask = taskRepository.findById(inPort.getParentTask().getId());
+                            .findByReferenceIdAndTaskModel_Id(outPort.getId(), outPort.getParentTask().getTaskResource().getId());
+                    Optional<TaskModel> targetTask = taskRepository.findById(inPort.getParentTask().getTaskResource().getId());
 
                     taskDAGRepository.save(new TaskDAG()
                             .setSourceOutPort(sourceOutPort.get())
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/data/DataStoreService.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/data/DataStoreService.java
index 272ab33..e775acc 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/data/DataStoreService.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/data/DataStoreService.java
@@ -51,10 +51,10 @@ public class DataStoreService {
         this.experimentOutputDataRepository = experimentOutputDataRepository;
     }
 
-    public long createEntry(long taskId, long expOutId, byte[] content) {
+    public long createEntry(long taskId, String identifier, byte[] content) {
         DataStoreModel model = new DataStoreModel();
         model.setTaskModel(taskRepository.findById(taskId).get())
-                .setExperimentOutputData(experimentOutputDataRepository.findById(expOutId).get())
+                .setIdentifier(identifier)
                 .setContent(content);
         return dataStoreRepository.save(model).getId();
     }
@@ -64,8 +64,7 @@ public class DataStoreService {
         List<DataStoreModel> dataStoreModels = this.dataStoreRepository.findByTaskModel_ParentProcess_Id(processId);
         Optional.ofNullable(dataStoreModels).ifPresent(models -> models.forEach(model -> entries.add(new DataEntryResource()
                 .setId(model.getId())
-                .setName(model.getExperimentOutputData().getName())
-                .setDataType(model.getExperimentOutputData().getType().name()))));
+                .setName(model.getIdentifier()))));
         return entries;
     }
 }
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/util/ToResourceUtil.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/util/ToResourceUtil.java
index acd94c9..22b970f 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/util/ToResourceUtil.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/util/ToResourceUtil.java
@@ -249,6 +249,10 @@ public class ToResourceUtil {
                             taskStatuses.forEach(taskStatus -> resource.getTaskStatus()
                                     .add(toResource(taskStatus).get())));
 
+            Optional.ofNullable(taskModel.getTaskOutPorts())
+                    .ifPresent(outPorts -> outPorts.forEach(outPort -> resource.getOutPorts()
+                            .add(toResource(outPort).get())));
+
             resource.setOrder(taskModel.getOrderIndex());
             return Optional.of(resource);
         } else {
@@ -442,7 +446,7 @@ public class ToResourceUtil {
             resource.setId(outPort.getId());
             resource.setReferenceId(outPort.getReferenceId());
             resource.setName(outPort.getName());
-            resource.setTaskResource(toResource(outPort.getTaskModel()).get());
+            //resource.setTaskResource(toResource(outPort.getTaskModel()).get());
             return Optional.of(resource);
         } else {
             return Optional.empty();
diff --git a/airavata-kubernetes/modules/microservices/task-scheduler/pom.xml b/airavata-kubernetes/modules/microservices/task-scheduler/pom.xml
index 6372cb1..8f502c8 100644
--- a/airavata-kubernetes/modules/microservices/task-scheduler/pom.xml
+++ b/airavata-kubernetes/modules/microservices/task-scheduler/pom.xml
@@ -57,6 +57,24 @@
             <groupId>org.springframework.kafka</groupId>
             <artifactId>spring-kafka</artifactId>
         </dependency>
+
+        <dependency>
+            <groupId>org.apache.helix</groupId>
+            <artifactId>helix-core</artifactId>
+            <version>0.6.7</version>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <version>1.7.5</version>
+            <scope>compile</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-log4j12</artifactId>
+            <version>1.7.5</version>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/core/HelixWorkflowManager.java b/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/core/HelixWorkflowManager.java
new file mode 100644
index 0000000..e9dead8
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/core/HelixWorkflowManager.java
@@ -0,0 +1,162 @@
+package org.apache.airavata.k8s.gfac.core;
+
+import org.apache.airavata.k8s.api.resources.process.ProcessStatusResource;
+import org.apache.airavata.k8s.api.resources.task.TaskResource;
+import org.apache.airavata.k8s.gfac.messaging.KafkaSender;
+import org.apache.helix.HelixManagerFactory;
+import org.apache.helix.InstanceType;
+import org.apache.helix.task.*;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+import org.apache.zookeeper.Op;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public class HelixWorkflowManager {
+
+    private static final Logger logger = LogManager.getLogger(HelixWorkflowManager.class);
+
+    private long processId;
+    private List<TaskResource> tasks;
+
+    // out port id, next task id
+    private Map<Long, Long> edgeMap;
+
+    private KafkaSender kafkaSender;
+
+    // Todo abstract out these parameters to reusable class
+    private final RestTemplate restTemplate;
+    private String apiServerUrl;
+
+    public HelixWorkflowManager(long processId, List<TaskResource> tasks, Map<Long, Long> edgeMap,
+                                KafkaSender kafkaSender,
+                                RestTemplate restTemplate, String apiServerUrl) {
+        this.processId = processId;
+        this.tasks = tasks;
+        this.edgeMap = edgeMap;
+        this.kafkaSender = kafkaSender;
+        this.restTemplate = restTemplate;
+        this.apiServerUrl = apiServerUrl;
+    }
+
+    public void launchWorkflow() {
+        org.apache.helix.HelixManager helixManager = HelixManagerFactory.getZKHelixManager("AiravataDemoCluster", "Admin",
+                InstanceType.SPECTATOR, "localhost:2199");
+
+        try {
+
+            Workflow.Builder workflowBuilder = createWorkflow();
+            WorkflowConfig.Builder config = new WorkflowConfig.Builder().setFailureThreshold(0);
+            workflowBuilder.setWorkflowConfig(config.build());
+            if (workflowBuilder == null) {
+                throw new Exception("Failed to create a workflow for process id " + processId);
+            }
+
+            Workflow workflow = workflowBuilder.build();
+
+            helixManager.connect();
+            TaskDriver taskDriver = new TaskDriver(helixManager);
+
+            Runtime.getRuntime().addShutdownHook(
+                    new Thread() {
+                        @Override
+                        public void run() {
+                            helixManager.disconnect();
+                        }
+                    }
+            );
+
+            taskDriver.start(workflow);
+            logger.info("Started workflow");
+            TaskState taskState = taskDriver.pollForWorkflowState(workflow.getName(), TaskState.COMPLETED, TaskState.FAILED, TaskState.STOPPED, TaskState.ABORTED);
+            System.out.println("Workflow state " + taskState.name());
+
+        } catch (Exception ex) {
+            logger.error("Error in connect() for Admin, reason: " + ex, ex);
+        }
+    }
+
+    private Workflow.Builder createWorkflow() {
+        Optional<TaskResource> startingTask = tasks.stream().filter(TaskResource::isStartingTask).findFirst();
+        if (startingTask.isPresent()) {
+            Workflow.Builder workflow = new Workflow.Builder("Airavata_Process_" + processId).setExpiry(0);
+            createWorkflowRecursively(startingTask.get(), workflow, null);
+            return workflow;
+        } else {
+            System.out.println("No starting task for this process " + processId);
+            updateProcessStatus(ProcessStatusResource.State.CANCELED, "No starting task for this process");
+            return null;
+        }
+    }
+
+    private void createWorkflowRecursively(TaskResource taskResource, Workflow.Builder workflow, Long parentTaskId) {
+
+        TaskConfig.Builder taskBuilder = new TaskConfig.Builder().setTaskId("Task_" + taskResource.getId())
+                .setCommand(taskResource.getTaskType().getName());
+
+        Optional.ofNullable(taskResource.getInputs()).ifPresent(inputs -> inputs.forEach(input -> {
+            taskBuilder.addConfig(input.getName(), input.getValue());
+        }));
+
+        taskBuilder.addConfig("task_id", taskResource.getId() + "");
+        taskBuilder.addConfig("process_id", taskResource.getParentProcessId() + "");
+
+        Optional.ofNullable(taskResource.getOutPorts()).ifPresent(outPorts -> outPorts.forEach(outPort -> {
+            Optional.ofNullable(edgeMap.get(outPort.getId())).ifPresent(nextTask -> {
+                Optional<TaskResource> nextTaskResource = tasks.stream().filter(task -> task.getId() == nextTask).findFirst();
+                nextTaskResource.ifPresent(t -> {
+                    taskBuilder.addConfig("OUT_"+ outPort.getName(), "JOB_" + t.getId());
+                });
+            });
+        }));
+
+
+        List<TaskConfig> taskBuilds = new ArrayList<>();
+        taskBuilds.add(taskBuilder.build());
+
+        JobConfig.Builder job = new JobConfig.Builder()
+                .addTaskConfigs(taskBuilds)
+                .setFailureThreshold(0)
+                .setMaxAttemptsPerTask(3)
+                .setInstanceGroupTag(taskResource.getTaskType().getName());
+
+        workflow.addJob(("JOB_" + taskResource.getId()), job);
+        if (parentTaskId != null) {
+            workflow.addParentChildDependency("JOB_" + parentTaskId, "JOB_" + taskResource.getId());
+        }
+
+        Optional.ofNullable(taskResource.getOutPorts()).ifPresent(outPorts -> outPorts.forEach(outPort -> {
+            Optional.ofNullable(edgeMap.get(outPort.getId())).ifPresent(nextTask -> {
+                Optional<TaskResource> nextTaskResource = tasks.stream().filter(task -> task.getId() == nextTask).findFirst();
+                nextTaskResource.ifPresent(t -> {
+
+                    createWorkflowRecursively(t, workflow, taskResource.getId());
+                });
+            });
+        }));
+    }
+
+    private void updateProcessStatus(ProcessStatusResource.State state) {
+        updateProcessStatus(state, "");
+    }
+
+    private void updateProcessStatus(ProcessStatusResource.State state, String reason) {
+        this.restTemplate.postForObject("http://" + apiServerUrl + "/process/" + this.processId + "/status",
+                new ProcessStatusResource()
+                        .setState(state.getValue())
+                        .setReason(reason)
+                        .setTimeOfStateChange(System.currentTimeMillis()),
+                Long.class);
+    }
+
+}
diff --git a/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/KafkaReceiver.java b/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/KafkaReceiver.java
index 42aa43d..6a02975 100644
--- a/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/KafkaReceiver.java
+++ b/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/KafkaReceiver.java
@@ -41,10 +41,10 @@ public class KafkaReceiver {
         System.out.println("received process=" + payload);
         workerService.launchProcess(Long.parseLong(payload));
     }
-
-    @KafkaListener(topics = "${task.event.topic.name}", containerFactory = "kafkaEventListenerContainerFactory")
-    public void receiveTaskEvent(TaskContext taskContext) {
-        System.out.println("received event for task id =" + taskContext.getTaskId());
-        workerService.onTaskStateEvent(taskContext);
-    }
+//
+//    @KafkaListener(topics = "${task.event.topic.name}", containerFactory = "kafkaEventListenerContainerFactory")
+//    public void receiveTaskEvent(TaskContext taskContext) {
+//        System.out.println("received event for task id =" + taskContext.getTaskId());
+//        workerService.onTaskStateEvent(taskContext);
+//    }
 }
diff --git a/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/service/HelixWorkflowService.java b/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/service/HelixWorkflowService.java
new file mode 100644
index 0000000..05a1c55
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/service/HelixWorkflowService.java
@@ -0,0 +1,17 @@
+package org.apache.airavata.k8s.gfac.service;
+
+import org.springframework.stereotype.Service;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+@Service
+public class HelixWorkflowService {
+
+    public void launchProcess(long processId) {
+
+    }
+}
diff --git a/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/service/WorkerService.java b/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/service/WorkerService.java
index 5449951..1ccf1d1 100644
--- a/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/service/WorkerService.java
+++ b/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/service/WorkerService.java
@@ -23,14 +23,19 @@ import org.apache.airavata.k8s.api.resources.process.ProcessResource;
 import org.apache.airavata.k8s.api.resources.task.TaskDagResource;
 import org.apache.airavata.k8s.api.resources.task.TaskResource;
 import org.apache.airavata.k8s.api.resources.task.TaskStatusResource;
+import org.apache.airavata.k8s.gfac.core.HelixWorkflowManager;
 import org.apache.airavata.k8s.gfac.core.ProcessLifeCycleManager;
 import org.apache.airavata.k8s.gfac.messaging.KafkaSender;
 import org.apache.airavata.k8s.task.api.TaskContext;
 import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpMethod;
 import org.springframework.stereotype.Service;
 import org.springframework.web.client.RestTemplate;
 
 import java.util.*;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 
 /**
  * TODO: Class level comments please
@@ -44,6 +49,7 @@ public class WorkerService {
     private final RestTemplate restTemplate;
     private final KafkaSender kafkaSender;
     private Map<Long, ProcessLifeCycleManager> processLifecycleStore = new HashMap<>();
+    ExecutorService executorService = Executors.newFixedThreadPool(10);
 
     @Value("${api.server.url}")
     private String apiServerUrl;
@@ -59,8 +65,9 @@ public class WorkerService {
                 ProcessResource.class);
         List<TaskResource> taskResources = processResource.getTasks();
 
-        Set<TaskDagResource> takDagSet = this.restTemplate.getForObject("http://" + apiServerUrl + "/task/dag/"
-                + processId, Set.class);
+        Set<TaskDagResource> takDagSet = this.restTemplate.exchange("http://" + apiServerUrl + "/task/dag/"
+                + processId, HttpMethod.GET, null, new ParameterizedTypeReference<Set<TaskDagResource>>() {})
+                .getBody();
 
         final Map<Long, Long> edgeMap = new HashMap<>();
         Optional.ofNullable(takDagSet)
@@ -68,12 +75,22 @@ public class WorkerService {
                         edgeMap.put(dag.getSourceOutPort().getId(), dag.getTargetTask().getId())));
 
         System.out.println("Starting to execute process " + processId);
-        ProcessLifeCycleManager manager =
-                new ProcessLifeCycleManager(processId, taskResources, edgeMap, kafkaSender, restTemplate, apiServerUrl);
+        //ProcessLifeCycleManager manager =
+        //        new ProcessLifeCycleManager(processId, taskResources, edgeMap, kafkaSender, restTemplate, apiServerUrl);
 
-        manager.init();
-        manager.start();
-        processLifecycleStore.put(processId, manager);
+        //manager.init();
+        //manager.start();
+
+        //processLifecycleStore.put(processId, manager);
+
+        final HelixWorkflowManager helixWorkflowManager = new HelixWorkflowManager(processId, taskResources, edgeMap, kafkaSender, restTemplate, apiServerUrl);
+
+        executorService.execute(new Runnable() {
+            @Override
+            public void run() {
+                helixWorkflowManager.launchWorkflow();
+            }
+        });
     }
 
     public void onTaskStateEvent(TaskContext taskContext) {
diff --git a/airavata-kubernetes/modules/microservices/task-scheduler/src/main/resources/log4j.properties b/airavata-kubernetes/modules/microservices/task-scheduler/src/main/resources/log4j.properties
new file mode 100644
index 0000000..0a185ad
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/task-scheduler/src/main/resources/log4j.properties
@@ -0,0 +1,9 @@
+# Set root logger level to DEBUG and its only appender to A1.
+log4j.rootLogger=ERROR, A1
+
+# A1 is set to be a ConsoleAppender.
+log4j.appender.A1=org.apache.log4j.ConsoleAppender
+
+# A1 uses PatternLayout.
+log4j.appender.A1.layout=org.apache.log4j.PatternLayout
+log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
\ No newline at end of file
diff --git a/airavata-kubernetes/pom.xml b/airavata-kubernetes/pom.xml
index c758f70..222fffe 100644
--- a/airavata-kubernetes/pom.xml
+++ b/airavata-kubernetes/pom.xml
@@ -40,6 +40,7 @@
         <module>modules/microservices/tasks/data-pushing-task</module>
         <module>modules/microservices/tasks/data-collecting-task</module>
         <module>modules/task-api</module>
+        <module>modules/helix-tasks</module>
     </modules>
 
     <dependencyManagement>

-- 
To stop receiving notification emails like this one, please contact
"commits@airavata.apache.org" <co...@airavata.apache.org>.

[airavata-sandbox] 02/19: Integrated workflow creation flow

Posted by sm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

smarru pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-sandbox.git

commit e20636abffeee060ccd82fcc32153c6d0591158f
Author: dimuthu.upeksha2@gmail.com <Di...@1234>
AuthorDate: Wed Nov 8 06:36:48 2017 +0530

    Integrated workflow creation flow
---
 .../k8s/api/resources/process/ProcessResource.java |    30 +
 .../k8s/api/resources/task/TaskInputResource.java  |    61 +
 .../api/resources/task/TaskOutPortResource.java    |    40 +
 .../k8s/api/resources/task/TaskOutputResource.java |    61 +
 .../k8s/api/resources/task/TaskResource.java       |    68 +-
 .../resources/task/type/TaskInputTypeResource.java |    51 +
 .../task/type/TaskOutPortTypeResource.java         |    41 +
 .../task/type/TaskOutputTypeResource.java          |    41 +
 .../api/resources/task/type/TaskTypeResource.java  |    84 +
 .../api/resources/workflow/WorkflowResource.java   |    54 +
 .../k8s/api/server/controller/TaskController.java  |     2 +-
 .../api/server/controller/TaskTypeController.java  |    33 +
 .../api/server/controller/WorkflowController.java  |    39 +
 .../k8s/api/server/model/process/ProcessModel.java |    66 +-
 .../k8s/api/server/model/task/TaskDAG.java         |    52 +
 .../k8s/api/server/model/task/TaskInput.java       |    86 +
 .../k8s/api/server/model/task/TaskModel.java       |   110 +-
 .../k8s/api/server/model/task/TaskOutPort.java     |    57 +
 .../k8s/api/server/model/task/TaskOutput.java      |    86 +
 .../k8s/api/server/model/task/TaskParam.java       |    82 -
 .../api/server/model/task/type/TaskInputType.java  |    69 +
 .../api/server/model/task/type/TaskModelType.java  |    95 +
 .../server/model/task/type/TaskOutPortType.java    |    61 +
 .../api/server/model/task/type/TaskOutputType.java |    59 +
 .../k8s/api/server/model/workflow/Workflow.java    |    69 +
 .../api/server/repository/TaskParamRepository.java |    32 -
 .../ApplicationDeploymentRepository.java           |     2 +-
 .../ApplicationIfaceRepository.java                |     2 +-
 .../ApplicationInputRepository.java                |     2 +-
 .../ApplicationModuleRepository.java               |     2 +-
 .../ApplicationOutputRepository.java               |     2 +-
 .../{ => compute}/ComputeRepository.java           |     2 +-
 .../repository/{ => data}/DataStoreRepository.java |     2 +-
 .../ExperimentInputDataRepository.java             |     2 +-
 .../ExperimentOutputDataRepository.java            |     2 +-
 .../{ => experiment}/ExperimentRepository.java     |     2 +-
 .../ExperimentStatusRepository.java                |     2 +-
 .../{ => process}/ProcessRepository.java           |     2 +-
 .../{ => process}/ProcessStatusRepository.java     |     2 +-
 .../server/repository/task/TaskDAGRepository.java  |    13 +
 .../repository/task/TaskInputRepository.java       |    13 +
 .../repository/task/TaskOutPortRepository.java     |    17 +
 .../repository/task/TaskOutputRepository.java      |    13 +
 .../repository/{ => task}/TaskRepository.java      |     2 +-
 .../{ => task}/TaskStatusRepository.java           |     2 +-
 .../task/type/TaskInputTypeRepository.java         |    13 +
 .../task/type/TaskOutPortTypeRepository.java       |    13 +
 .../task/type/TaskOutputTypeRepository.java        |    13 +
 .../repository/task/type/TaskTypeRepository.java   |    17 +
 .../repository/workflow/WorkflowRepository.java    |    16 +
 .../service/ApplicationDeploymentService.java      |     6 +-
 .../server/service/ApplicationIfaceService.java    |     8 +-
 .../server/service/ApplicationModuleService.java   |     2 +-
 .../api/server/service/ComputeResourceService.java |     2 +-
 .../k8s/api/server/service/ExperimentService.java  |     7 +-
 .../k8s/api/server/service/ProcessService.java     |    25 +-
 .../k8s/api/server/service/WorkflowService.java    |   129 +
 .../api/server/service/data/DataStoreService.java  |     6 +-
 .../api/server/service/{ => task}/TaskService.java |    73 +-
 .../server/service/task/type/TaskTypeService.java  |    94 +
 .../k8s/api/server/service/util/GraphParser.java   |   440 +
 .../api/server/service/util/ToResourceUtil.java    |   148 +-
 .../k8s/gfac/core/ProcessLifeCycleManager.java     |    27 -
 .../pom.xml                                        |     7 +-
 .../apache/airavata/k8s/task/job/Application.java  |     8 +-
 .../airavata/k8s/task/job/CommandTaskInfo.java     |    59 +
 .../airavata/k8s/task/job/config/RestConfig.java   |    10 +
 .../k8s/task/job/service/TaskExecutionService.java |   100 +
 .../src/main/resources/application.properties      |     0
 .../src/main/resources/application.yml             |     0
 .../pom.xml                                        |     7 +-
 .../airavata/k8s/task/egress/Application.java      |     6 +
 .../k8s/task/egress/DataCollectingTaskInfo.java    |    50 +
 .../task/egress/service/TaskExecutionService.java  |   107 +
 .../src/main/resources/application.properties      |     0
 .../src/main/resources/application.yml             |     0
 .../pom.xml                                        |     7 +-
 .../airavata/k8s/task/ingress/Application.java     |     0
 .../task/ingress/service/TaskExecutionService.java |    83 +
 .../src/main/resources/application.properties      |     0
 .../src/main/resources/application.yml             |     0
 .../k8s/task/egress/messaging/KafkaReceiver.java   |    45 -
 .../k8s/task/egress/messaging/KafkaSender.java     |    43 -
 .../k8s/task/egress/messaging/SenderConfig.java    |    69 -
 .../task/egress/service/TaskExecutionService.java  |   183 -
 .../src/main/resources/application.yml             |     4 -
 .../microservices/tasks/env-cleanup-task/pom.xml   |   156 -
 .../airavata/k8s/task/cleanup/Application.java     |    49 -
 .../k8s/task/cleanup/messaging/KafkaReceiver.java  |    45 -
 .../k8s/task/cleanup/messaging/KafkaSender.java    |    43 -
 .../k8s/task/cleanup/messaging/ReceiverConfig.java |    85 -
 .../k8s/task/cleanup/messaging/SenderConfig.java   |    69 -
 .../task/cleanup/service/TaskExecutionService.java |   153 -
 .../src/main/resources/application.properties      |     5 -
 .../src/main/resources/application.yml             |     4 -
 .../microservices/tasks/env-setup-task/pom.xml     |   157 -
 .../airavata/k8s/task/env/setup/Application.java   |    49 -
 .../task/env/setup/messaging/KafkaReceiver.java    |    45 -
 .../k8s/task/env/setup/messaging/KafkaSender.java  |    43 -
 .../task/env/setup/messaging/ReceiverConfig.java   |    85 -
 .../k8s/task/env/setup/messaging/SenderConfig.java |    69 -
 .../env/setup/service/TaskExecutionService.java    |   153 -
 .../src/main/resources/application.properties      |     5 -
 .../k8s/task/ingress/messaging/KafkaReceiver.java  |    45 -
 .../k8s/task/ingress/messaging/KafkaSender.java    |    43 -
 .../k8s/task/ingress/messaging/ReceiverConfig.java |    85 -
 .../k8s/task/ingress/messaging/SenderConfig.java   |    69 -
 .../task/ingress/service/TaskExecutionService.java |   126 -
 .../k8s/task/job/messaging/ReceiverConfig.java     |    85 -
 .../k8s/task/job/service/TaskExecutionService.java |   160 -
 .../service/ExperimentLaunchService.java           |    20 +-
 airavata-kubernetes/modules/task-api/pom.xml       |    54 +
 .../k8s/task/api/AbstractTaskExecutionService.java |   123 +
 .../apache/airavata/k8s/task/api/TaskContext.java  |    54 +
 .../k8s/task/api/TaskContextDeserializer.java      |    29 +
 .../k8s/task/api/TaskContextSerializer.java        |    28 +
 .../k8s/task/api}/messaging/KafkaReceiver.java     |    13 +-
 .../k8s/task/api}/messaging/KafkaSender.java       |     2 +-
 .../k8s/task/api}/messaging/ReceiverConfig.java    |     4 +-
 .../k8s/task/api}/messaging/SenderConfig.java      |     2 +-
 airavata-kubernetes/pom.xml                        |     9 +-
 .../web-console/src/app/app.module.ts              |     6 +-
 .../src/app/components/dashboard/dashboard.html    |     3 +
 .../app/components/dashboard/dashboard.routes.ts   |    10 +
 .../components/workflow/create/create.component.ts |   435 +
 .../src/app/components/workflow/create/create.html |    27 +
 .../src/app/components/workflow/list/list.html     |    23 +
 .../workflow/list/workflow.list.component.ts       |    49 +
 .../type/operation/operation.inport.type.model.ts  |     9 +
 .../type/operation/operation.outport.type.model.ts |     9 +
 .../task/type/operation/operation.type.model.ts    |    29 +
 .../app/models/task/type/task.input.type.model.ts  |    14 +
 .../src/app/models/task/type/task.outport.model.ts |    12 +
 .../app/models/task/type/task.output.type.model.ts |    12 +
 .../src/app/models/task/type/task.type.model.ts    |    39 +
 .../src/app/models/workflow/workflow.model.ts      |     8 +
 .../web-console/src/app/services/task.service.ts   |     4 +
 .../src/app/services/workflow.service.ts           |    17 +
 .../web-console/src/assets/css/common.css          |   160 +
 .../web-console/src/assets/css/explorer.css        |    18 +
 .../web-console/src/assets/icons/copy.png          |   Bin 0 -> 3575 bytes
 .../web-console/src/assets/icons/http.png          |   Bin 0 -> 3218 bytes
 .../web-console/src/assets/icons/parallel.png      |   Bin 0 -> 3150 bytes
 .../web-console/src/assets/icons/s3.png            |   Bin 0 -> 3988 bytes
 .../web-console/src/assets/icons/ssh.png           |   Bin 0 -> 3815 bytes
 .../web-console/src/assets/icons/start.png         |   Bin 0 -> 3303 bytes
 .../web-console/src/assets/icons/stop.png          |   Bin 0 -> 3525 bytes
 .../web-console/src/assets/images/button.gif       |   Bin 0 -> 137 bytes
 .../web-console/src/assets/images/close.gif        |   Bin 0 -> 70 bytes
 .../web-console/src/assets/images/collapsed.gif    |   Bin 0 -> 877 bytes
 .../web-console/src/assets/images/error.gif        |   Bin 0 -> 907 bytes
 .../web-console/src/assets/images/expanded.gif     |   Bin 0 -> 878 bytes
 .../web-console/src/assets/images/maximize.gif     |   Bin 0 -> 843 bytes
 .../web-console/src/assets/images/minimize.gif     |   Bin 0 -> 64 bytes
 .../web-console/src/assets/images/normalize.gif    |   Bin 0 -> 845 bytes
 .../web-console/src/assets/images/point.gif        |   Bin 0 -> 55 bytes
 .../web-console/src/assets/images/resize.gif       |   Bin 0 -> 74 bytes
 .../web-console/src/assets/images/separator.gif    |   Bin 0 -> 146 bytes
 .../web-console/src/assets/images/submenu.gif      |   Bin 0 -> 56 bytes
 .../web-console/src/assets/images/transparent.gif  |   Bin 0 -> 90 bytes
 .../web-console/src/assets/images/warning.gif      |   Bin 0 -> 276 bytes
 .../web-console/src/assets/images/warning.png      |   Bin 0 -> 425 bytes
 .../web-console/src/assets/images/window-title.gif |   Bin 0 -> 275 bytes
 .../web-console/src/assets/images/window.gif       |   Bin 0 -> 75 bytes
 .../web-console/src/assets/js/components.js        |    53 +
 .../src/assets/js/editor/mxDefaultKeyHandler.js    |   126 +
 .../src/assets/js/editor/mxDefaultPopupMenu.js     |   306 +
 .../src/assets/js/editor/mxDefaultToolbar.js       |   564 +
 .../web-console/src/assets/js/editor/mxEditor.js   |  3114 +++++
 .../src/assets/js/handler/mxCellHighlight.js       |   314 +
 .../src/assets/js/handler/mxCellMarker.js          |   430 +
 .../src/assets/js/handler/mxCellTracker.js         |   145 +
 .../src/assets/js/handler/mxConnectionHandler.js   |  2204 ++++
 .../src/assets/js/handler/mxConstraintHandler.js   |   520 +
 .../src/assets/js/handler/mxEdgeHandler.js         |  2409 ++++
 .../src/assets/js/handler/mxEdgeSegmentHandler.js  |   401 +
 .../src/assets/js/handler/mxElbowEdgeHandler.js    |   229 +
 .../src/assets/js/handler/mxGraphHandler.js        |  1074 ++
 .../web-console/src/assets/js/handler/mxHandle.js  |   353 +
 .../src/assets/js/handler/mxKeyHandler.js          |   428 +
 .../src/assets/js/handler/mxPanningHandler.js      |   462 +
 .../src/assets/js/handler/mxPopupMenuHandler.js    |   218 +
 .../src/assets/js/handler/mxRubberband.js          |   401 +
 .../assets/js/handler/mxSelectionCellsHandler.js   |   287 +
 .../src/assets/js/handler/mxTooltipHandler.js      |   337 +
 .../src/assets/js/handler/mxVertexHandler.js       |  1950 +++
 .../src => web-console/src/assets}/js/index.txt    |    96 +-
 .../web-console/src/assets/js/io/mxCellCodec.js    |   189 +
 .../src/assets/js/io/mxChildChangeCodec.js         |   149 +
 .../web-console/src/assets/js/io/mxCodec.js        |   596 +
 .../src/assets/js/io/mxCodecRegistry.js            |   137 +
 .../src/assets/js/io/mxDefaultKeyHandlerCodec.js   |    88 +
 .../src/assets/js/io/mxDefaultPopupMenuCodec.js    |    54 +
 .../src/assets/js/io/mxDefaultToolbarCodec.js      |   312 +
 .../web-console/src/assets/js/io/mxEditorCodec.js  |   245 +
 .../src/assets/js/io/mxGenericChangeCodec.js       |    64 +
 .../web-console/src/assets/js/io/mxGraphCodec.js   |    28 +
 .../src/assets/js/io/mxGraphViewCodec.js           |   197 +
 .../web-console/src/assets/js/io/mxModelCodec.js   |    80 +
 .../web-console/src/assets/js/io/mxObjectCodec.js  |  1077 ++
 .../src/assets/js/io/mxRootChangeCodec.js          |    83 +
 .../src/assets/js/io/mxStylesheetCodec.js          |   217 +
 .../src/assets/js/io/mxTerminalChangeCodec.js      |    42 +
 .../model/mxGraphAbstractHierarchyCell.js          |   206 +
 .../hierarchical/model/mxGraphHierarchyEdge.js     |   187 +
 .../hierarchical/model/mxGraphHierarchyModel.js    |   681 +
 .../hierarchical/model/mxGraphHierarchyNode.js     |   220 +
 .../layout/hierarchical/model/mxSwimlaneModel.js   |   801 ++
 .../js/layout/hierarchical/mxHierarchicalLayout.js |   846 ++
 .../js/layout/hierarchical/mxSwimlaneLayout.js     |   937 ++
 .../hierarchical/stage/mxCoordinateAssignment.js   |  1830 +++
 .../stage/mxHierarchicalLayoutStage.js             |    25 +
 .../stage/mxMedianHybridCrossingReduction.js       |   675 +
 .../hierarchical/stage/mxMinimumCycleRemover.js    |   108 +
 .../hierarchical/stage/mxSwimlaneOrdering.js       |    96 +
 .../src/assets/js/layout/mxCircleLayout.js         |   203 +
 .../src/assets/js/layout/mxCompactTreeLayout.js    |  1203 ++
 .../src/assets/js/layout/mxCompositeLayout.js      |   101 +
 .../src/assets/js/layout/mxEdgeLabelLayout.js      |   165 +
 .../src/assets/js/layout/mxFastOrganicLayout.js    |   591 +
 .../src/assets/js/layout/mxGraphLayout.js          |   461 +
 .../src/assets/js/layout/mxParallelEdgeLayout.js   |   225 +
 .../src/assets/js/layout/mxPartitionLayout.js      |   240 +
 .../src/assets/js/layout/mxRadialTreeLayout.js     |   317 +
 .../src/assets/js/layout/mxStackLayout.js          |   515 +
 .../web-console/src/assets/js/model/mxCell.js      |   825 ++
 .../web-console/src/assets/js/model/mxCellPath.js  |   163 +
 .../web-console/src/assets/js/model/mxGeometry.js  |   415 +
 .../src/assets/js/model/mxGraphModel.js            |  2667 ++++
 .../web-console/src/assets/js/mxClient.js          |   769 ++
 .../web-console/src/assets/js/shape/mxActor.js     |    86 +
 .../web-console/src/assets/js/shape/mxArrow.js     |   115 +
 .../src/assets/js/shape/mxArrowConnector.js        |   485 +
 .../web-console/src/assets/js/shape/mxCloud.js     |    55 +
 .../web-console/src/assets/js/shape/mxConnector.js |   149 +
 .../web-console/src/assets/js/shape/mxCylinder.js  |   105 +
 .../src/assets/js/shape/mxDoubleEllipse.js         |   114 +
 .../web-console/src/assets/js/shape/mxEllipse.js   |    48 +
 .../web-console/src/assets/js/shape/mxHexagon.js   |    34 +
 .../src/assets/js/shape/mxImageShape.js            |   233 +
 .../web-console/src/assets/js/shape/mxLabel.js     |   275 +
 .../web-console/src/assets/js/shape/mxLine.js      |    51 +
 .../web-console/src/assets/js/shape/mxMarker.js    |   208 +
 .../web-console/src/assets/js/shape/mxPolyline.js  |   127 +
 .../src/assets/js/shape/mxRectangleShape.js        |   117 +
 .../web-console/src/assets/js/shape/mxRhombus.js   |    54 +
 .../web-console/src/assets/js/shape/mxShape.js     |  1604 +++
 .../web-console/src/assets/js/shape/mxStencil.js   |   761 ++
 .../src/assets/js/shape/mxStencilRegistry.js       |    53 +
 .../web-console/src/assets/js/shape/mxSwimlane.js  |   410 +
 .../web-console/src/assets/js/shape/mxText.js      |  1263 ++
 .../web-console/src/assets/js/shape/mxTriangle.js  |    33 +
 .../src/assets/js/util/mxAbstractCanvas2D.js       |   642 +
 .../web-console/src/assets/js/util/mxAnimation.js  |    92 +
 .../src/assets/js/util/mxAutoSaveManager.js        |   213 +
 .../web-console/src/assets/js/util/mxClipboard.js  |   221 +
 .../web-console/src/assets/js/util/mxConstants.js  |  2275 ++++
 .../web-console/src/assets/js/util/mxDictionary.js |   130 +
 .../web-console/src/assets/js/util/mxDivResizer.js |   151 +
 .../web-console/src/assets/js/util/mxDragSource.js |   679 +
 .../web-console/src/assets/js/util/mxEffects.js    |   211 +
 .../web-console/src/assets/js/util/mxEvent.js      |  1406 ++
 .../src/assets/js/util/mxEventObject.js            |   111 +
 .../src/assets/js/util/mxEventSource.js            |   189 +
 .../web-console/src/assets/js/util/mxForm.js       |   202 +
 .../web-console/src/assets/js/util/mxGuide.js      |   401 +
 .../web-console/src/assets/js/util/mxImage.js      |    40 +
 .../src/assets/js/util/mxImageBundle.js            |   103 +
 .../src/assets/js/util/mxImageExport.js            |   175 +
 .../web-console/src/assets/js/util/mxLog.js        |   413 +
 .../web-console/src/assets/js/util/mxMorphing.js   |   248 +
 .../web-console/src/assets/js/util/mxMouseEvent.js |   244 +
 .../src/assets/js/util/mxObjectIdentity.js         |    72 +
 .../src/assets/js/util/mxPanningManager.js         |   262 +
 .../web-console/src/assets/js/util/mxPoint.js      |    54 +
 .../web-console/src/assets/js/util/mxPopupMenu.js  |   613 +
 .../web-console/src/assets/js/util/mxRectangle.js  |   179 +
 .../web-console/src/assets/js/util/mxResources.js  |   450 +
 .../src/assets/js/util/mxSvgCanvas2D.js            |  2179 ++++
 .../web-console/src/assets/js/util/mxToolbar.js    |   527 +
 .../src/assets/js/util/mxUndoManager.js            |   229 +
 .../src/assets/js/util/mxUndoableEdit.js           |   213 +
 .../src/assets/js/util/mxUrlConverter.js           |   151 +
 .../web-console/src/assets/js/util/mxUtils.js      |  4353 +++++++
 .../src/assets/js/util/mxVmlCanvas2D.js            |  1102 ++
 .../web-console/src/assets/js/util/mxWindow.js     |  1130 ++
 .../src/assets/js/util/mxXmlCanvas2D.js            |  1217 ++
 .../web-console/src/assets/js/util/mxXmlRequest.js |   463 +
 .../web-console/src/assets/js/view/mxCellEditor.js |  1069 ++
 .../src/assets/js/view/mxCellOverlay.js            |   233 +
 .../src/assets/js/view/mxCellRenderer.js           |  1553 +++
 .../web-console/src/assets/js/view/mxCellState.js  |   431 +
 .../src/assets/js/view/mxCellStatePreview.js       |   203 +
 .../src/assets/js/view/mxConnectionConstraint.js   |    50 +
 .../web-console/src/assets/js/view/mxEdgeStyle.js  |  1569 +++
 .../web-console/src/assets/js/view/mxGraph.js      | 12768 +++++++++++++++++++
 .../src/assets/js/view/mxGraphSelectionModel.js    |   436 +
 .../web-console/src/assets/js/view/mxGraphView.js  |  3001 +++++
 .../src/assets/js/view/mxLayoutManager.js          |   409 +
 .../src/assets/js/view/mxMultiplicity.js           |   257 +
 .../web-console/src/assets/js/view/mxOutline.js    |   761 ++
 .../web-console/src/assets/js/view/mxPerimeter.js  |   921 ++
 .../src/assets/js/view/mxPrintPreview.js           |  1175 ++
 .../src/assets/js/view/mxStyleRegistry.js          |    71 +
 .../web-console/src/assets/js/view/mxStylesheet.js |   266 +
 .../src/assets/js/view/mxSwimlaneManager.js        |   450 +
 .../src/assets/js/view/mxTemporaryCellStates.js    |   108 +
 .../web-console/src/assets/resources/editor.txt    |     5 +
 .../web-console/src/assets/resources/editor_de.txt |     5 +
 .../web-console/src/assets/resources/editor_zh.txt |     5 +
 .../web-console/src/assets/resources/graph.txt     |    11 +
 .../web-console/src/assets/resources/graph_de.txt  |    11 +
 .../web-console/src/assets/resources/graph_zh.txt  |    11 +
 airavata-kubernetes/web-console/src/index.html     |     6 +
 airavata-kubernetes/workflow-composer/index.html   |     2 +-
 .../workflow-composer/src/js/index.txt             |     6 +-
 316 files changed, 92931 insertions(+), 2525 deletions(-)

diff --git a/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/process/ProcessResource.java b/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/process/ProcessResource.java
index 8805e74..b5081cf 100644
--- a/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/process/ProcessResource.java
+++ b/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/process/ProcessResource.java
@@ -33,7 +33,9 @@ import java.util.List;
 public class ProcessResource {
 
     private long id;
+    private String name;
     private long experimentId;
+    private long workflowId;
     private long creationTime;
     private long lastUpdateTime;
     private List<ProcessStatusResource> processStatuses = new ArrayList<>();
@@ -41,6 +43,7 @@ public class ProcessResource {
     private List<Long> processErrorIds = new ArrayList<>();
     private String taskDag;
     private String experimentDataDir;
+    private String processType;
 
     public long getId() {
         return id;
@@ -122,4 +125,31 @@ public class ProcessResource {
         this.experimentDataDir = experimentDataDir;
         return this;
     }
+
+    public String getProcessType() {
+        return processType;
+    }
+
+    public ProcessResource setProcessType(String processType) {
+        this.processType = processType;
+        return this;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public ProcessResource setName(String name) {
+        this.name = name;
+        return this;
+    }
+
+    public long getWorkflowId() {
+        return workflowId;
+    }
+
+    public ProcessResource setWorkflowId(long workflowId) {
+        this.workflowId = workflowId;
+        return this;
+    }
 }
diff --git a/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/task/TaskInputResource.java b/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/task/TaskInputResource.java
new file mode 100644
index 0000000..562f416
--- /dev/null
+++ b/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/task/TaskInputResource.java
@@ -0,0 +1,61 @@
+package org.apache.airavata.k8s.api.resources.task;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public class TaskInputResource {
+
+    private long id;
+    private String name;
+    private String value;
+    private String type;
+    private String importFrom;
+
+    public long getId() {
+        return id;
+    }
+
+    public TaskInputResource setId(long id) {
+        this.id = id;
+        return this;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public TaskInputResource setName(String name) {
+        this.name = name;
+        return this;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public TaskInputResource setValue(String value) {
+        this.value = value;
+        return this;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public TaskInputResource setType(String type) {
+        this.type = type;
+        return this;
+    }
+
+    public String getImportFrom() {
+        return importFrom;
+    }
+
+    public TaskInputResource setImportFrom(String importFrom) {
+        this.importFrom = importFrom;
+        return this;
+    }
+}
diff --git a/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/task/TaskOutPortResource.java b/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/task/TaskOutPortResource.java
new file mode 100644
index 0000000..de4ef1b
--- /dev/null
+++ b/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/task/TaskOutPortResource.java
@@ -0,0 +1,40 @@
+package org.apache.airavata.k8s.api.resources.task;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public class TaskOutPortResource {
+    private long id;
+    private String name;
+    private int referenceId = 0;
+
+    public long getId() {
+        return id;
+    }
+
+    public TaskOutPortResource setId(long id) {
+        this.id = id;
+        return this;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public TaskOutPortResource setName(String name) {
+        this.name = name;
+        return this;
+    }
+
+    public int getReferenceId() {
+        return referenceId;
+    }
+
+    public TaskOutPortResource setReferenceId(int referenceId) {
+        this.referenceId = referenceId;
+        return this;
+    }
+}
diff --git a/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/task/TaskOutputResource.java b/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/task/TaskOutputResource.java
new file mode 100644
index 0000000..aa677d8
--- /dev/null
+++ b/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/task/TaskOutputResource.java
@@ -0,0 +1,61 @@
+package org.apache.airavata.k8s.api.resources.task;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public class TaskOutputResource {
+
+    private long id;
+    private String name;
+    private String value;
+    private String type;
+    private String exportTo;
+
+    public long getId() {
+        return id;
+    }
+
+    public TaskOutputResource setId(long id) {
+        this.id = id;
+        return this;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public TaskOutputResource setName(String name) {
+        this.name = name;
+        return this;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public TaskOutputResource setValue(String value) {
+        this.value = value;
+        return this;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public TaskOutputResource setType(String type) {
+        this.type = type;
+        return this;
+    }
+
+    public String getExportTo() {
+        return exportTo;
+    }
+
+    public TaskOutputResource setExportTo(String exportTo) {
+        this.exportTo = exportTo;
+        return this;
+    }
+}
diff --git a/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/task/TaskResource.java b/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/task/TaskResource.java
index 10948f7..bdc34d7 100644
--- a/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/task/TaskResource.java
+++ b/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/task/TaskResource.java
@@ -31,7 +31,8 @@ import java.util.List;
 public class TaskResource {
 
     private long id;
-    private int taskType;
+    private String name;
+    private long taskTypeId;
     private String taskTypeStr;
     private long parentProcessId;
     private long creationTime;
@@ -39,9 +40,13 @@ public class TaskResource {
     private List<TaskStatusResource> taskStatus = new ArrayList<>();
     private String taskDetail;
     private List<Long> taskErrorIds = new ArrayList<>();
-    private List<TaskParamResource> taskParams = new ArrayList<>();
+    private List<TaskInputResource> inputs = new ArrayList<>();
+    private List<TaskOutputResource> outputs = new ArrayList<>();
+    private List<TaskOutPortResource> outPorts = new ArrayList<>();
     private List<Long> jobIds;
     private int order;
+    private boolean startingTask;
+    private boolean stoppingTask;
 
     public long getId() {
         return id;
@@ -52,12 +57,12 @@ public class TaskResource {
         return this;
     }
 
-    public int getTaskType() {
-        return taskType;
+    public long getTaskTypeId() {
+        return taskTypeId;
     }
 
-    public TaskResource setTaskType(int taskType) {
-        this.taskType = taskType;
+    public TaskResource setTaskTypeId(long taskTypeId) {
+        this.taskTypeId = taskTypeId;
         return this;
     }
 
@@ -124,12 +129,21 @@ public class TaskResource {
         return this;
     }
 
-    public List<TaskParamResource> getTaskParams() {
-        return taskParams;
+    public List<TaskInputResource> getInputs() {
+        return inputs;
     }
 
-    public TaskResource setTaskParams(List<TaskParamResource> taskParams) {
-        this.taskParams = taskParams;
+    public TaskResource setInputs(List<TaskInputResource> inputs) {
+        this.inputs = inputs;
+        return this;
+    }
+
+    public List<TaskOutputResource> getOutputs() {
+        return outputs;
+    }
+
+    public TaskResource setOutputs(List<TaskOutputResource> outputs) {
+        this.outputs = outputs;
         return this;
     }
 
@@ -151,6 +165,40 @@ public class TaskResource {
         return this;
     }
 
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public List<TaskOutPortResource> getOutPorts() {
+        return outPorts;
+    }
+
+    public void setOutPorts(List<TaskOutPortResource> outPorts) {
+        this.outPorts = outPorts;
+    }
+
+    public boolean isStartingTask() {
+        return startingTask;
+    }
+
+    public TaskResource setStartingTask(boolean startingTask) {
+        this.startingTask = startingTask;
+        return this;
+    }
+
+    public boolean isStoppingTask() {
+        return stoppingTask;
+    }
+
+    public TaskResource setStoppingTask(boolean stoppingTask) {
+        this.stoppingTask = stoppingTask;
+        return this;
+    }
+
     public static final class TaskTypes {
         public static final int ENV_SETUP = 0;
         public static final int INGRESS_DATA_STAGING = 1;
diff --git a/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/task/type/TaskInputTypeResource.java b/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/task/type/TaskInputTypeResource.java
new file mode 100644
index 0000000..bfab1bf
--- /dev/null
+++ b/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/task/type/TaskInputTypeResource.java
@@ -0,0 +1,51 @@
+package org.apache.airavata.k8s.api.resources.task.type;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public class TaskInputTypeResource {
+
+    private long id;
+    private String name;
+    private String type;
+    private String defaultValue;
+
+    public long getId() {
+        return id;
+    }
+
+    public TaskInputTypeResource setId(long id) {
+        this.id = id;
+        return this;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public TaskInputTypeResource setName(String name) {
+        this.name = name;
+        return this;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public TaskInputTypeResource setType(String type) {
+        this.type = type;
+        return this;
+    }
+
+    public String getDefaultValue() {
+        return defaultValue;
+    }
+
+    public TaskInputTypeResource setDefaultValue(String defaultValue) {
+        this.defaultValue = defaultValue;
+        return this;
+    }
+}
diff --git a/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/task/type/TaskOutPortTypeResource.java b/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/task/type/TaskOutPortTypeResource.java
new file mode 100644
index 0000000..ad274ca
--- /dev/null
+++ b/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/task/type/TaskOutPortTypeResource.java
@@ -0,0 +1,41 @@
+package org.apache.airavata.k8s.api.resources.task.type;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public class TaskOutPortTypeResource {
+
+    private long id;
+    private String name;
+    private int order = 0;
+
+    public long getId() {
+        return id;
+    }
+
+    public TaskOutPortTypeResource setId(long id) {
+        this.id = id;
+        return this;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public TaskOutPortTypeResource setName(String name) {
+        this.name = name;
+        return this;
+    }
+
+    public int getOrder() {
+        return order;
+    }
+
+    public TaskOutPortTypeResource setOrder(int order) {
+        this.order = order;
+        return this;
+    }
+}
diff --git a/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/task/type/TaskOutputTypeResource.java b/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/task/type/TaskOutputTypeResource.java
new file mode 100644
index 0000000..f4fd618
--- /dev/null
+++ b/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/task/type/TaskOutputTypeResource.java
@@ -0,0 +1,41 @@
+package org.apache.airavata.k8s.api.resources.task.type;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public class TaskOutputTypeResource {
+
+    private long id;
+    private String name;
+    private String type;
+
+    public long getId() {
+        return id;
+    }
+
+    public TaskOutputTypeResource setId(long id) {
+        this.id = id;
+        return this;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public TaskOutputTypeResource setName(String name) {
+        this.name = name;
+        return this;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public TaskOutputTypeResource setType(String type) {
+        this.type = type;
+        return this;
+    }
+}
diff --git a/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/task/type/TaskTypeResource.java b/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/task/type/TaskTypeResource.java
new file mode 100644
index 0000000..1818b47
--- /dev/null
+++ b/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/task/type/TaskTypeResource.java
@@ -0,0 +1,84 @@
+package org.apache.airavata.k8s.api.resources.task.type;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public class TaskTypeResource {
+
+    private long id;
+    private String name;
+    private String topicName;
+    private String icon;
+    private List<TaskInputTypeResource> inputTypes = new ArrayList<>();
+    private List<TaskOutputTypeResource> outputTypes = new ArrayList<>();
+    private List<TaskOutPortTypeResource> outPorts = new ArrayList<>();
+
+    public long getId() {
+        return id;
+    }
+
+    public TaskTypeResource setId(long id) {
+        this.id = id;
+        return this;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public TaskTypeResource setName(String name) {
+        this.name = name;
+        return this;
+    }
+
+    public String getTopicName() {
+        return topicName;
+    }
+
+    public TaskTypeResource setTopicName(String topicName) {
+        this.topicName = topicName;
+        return this;
+    }
+
+    public List<TaskInputTypeResource> getInputTypes() {
+        return inputTypes;
+    }
+
+    public TaskTypeResource setInputTypes(List<TaskInputTypeResource> inputTypes) {
+        this.inputTypes = inputTypes;
+        return this;
+    }
+
+    public List<TaskOutputTypeResource> getOutputTypes() {
+        return outputTypes;
+    }
+
+    public TaskTypeResource setOutputTypes(List<TaskOutputTypeResource> outputTypes) {
+        this.outputTypes = outputTypes;
+        return this;
+    }
+
+    public List<TaskOutPortTypeResource> getOutPorts() {
+        return outPorts;
+    }
+
+    public TaskTypeResource setOutPorts(List<TaskOutPortTypeResource> outPorts) {
+        this.outPorts = outPorts;
+        return this;
+    }
+
+    public String getIcon() {
+        return icon;
+    }
+
+    public TaskTypeResource setIcon(String icon) {
+        this.icon = icon;
+        return this;
+    }
+}
diff --git a/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/workflow/WorkflowResource.java b/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/workflow/WorkflowResource.java
new file mode 100644
index 0000000..5631e50
--- /dev/null
+++ b/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/workflow/WorkflowResource.java
@@ -0,0 +1,54 @@
+package org.apache.airavata.k8s.api.resources.workflow;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public class WorkflowResource {
+
+    private long id;
+    private String name;
+    private String workflowGraphXML;
+    private List<Long> processIds = new ArrayList<>();
+
+    public long getId() {
+        return id;
+    }
+
+    public WorkflowResource setId(long id) {
+        this.id = id;
+        return this;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public WorkflowResource setName(String name) {
+        this.name = name;
+        return this;
+    }
+
+    public String getWorkflowGraphXML() {
+        return workflowGraphXML;
+    }
+
+    public WorkflowResource setWorkflowGraphXML(String workflowGraphXML) {
+        this.workflowGraphXML = workflowGraphXML;
+        return this;
+    }
+
+    public List<Long> getProcessIds() {
+        return processIds;
+    }
+
+    public WorkflowResource setProcessIds(List<Long> processIds) {
+        this.processIds = processIds;
+        return this;
+    }
+}
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/controller/TaskController.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/controller/TaskController.java
index ef8c797..2fb2334 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/controller/TaskController.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/controller/TaskController.java
@@ -22,7 +22,7 @@ package org.apache.airavata.k8s.api.server.controller;
 import org.apache.airavata.k8s.api.resources.task.TaskResource;
 import org.apache.airavata.k8s.api.resources.task.TaskStatusResource;
 import org.apache.airavata.k8s.api.server.ServerRuntimeException;
-import org.apache.airavata.k8s.api.server.service.TaskService;
+import org.apache.airavata.k8s.api.server.service.task.TaskService;
 import org.springframework.http.MediaType;
 import org.springframework.web.bind.annotation.*;
 
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/controller/TaskTypeController.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/controller/TaskTypeController.java
new file mode 100644
index 0000000..5424b72
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/controller/TaskTypeController.java
@@ -0,0 +1,33 @@
+package org.apache.airavata.k8s.api.server.controller;
+
+import org.apache.airavata.k8s.api.resources.task.type.TaskTypeResource;
+import org.apache.airavata.k8s.api.server.service.task.type.TaskTypeService;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+@RestController
+@RequestMapping(path="/taskType")
+public class TaskTypeController {
+
+    @Resource
+    private TaskTypeService taskTypeService;
+
+    @GetMapping(path = "", produces = MediaType.APPLICATION_JSON_VALUE)
+    public List<TaskTypeResource> getAll() {
+        return taskTypeService.getAll();
+    }
+
+    @PostMapping( path = "", consumes = MediaType.APPLICATION_JSON_VALUE)
+    public long createTask(@RequestBody TaskTypeResource resource) {
+        return taskTypeService.create(resource);
+    }
+}
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/controller/WorkflowController.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/controller/WorkflowController.java
new file mode 100644
index 0000000..136f839
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/controller/WorkflowController.java
@@ -0,0 +1,39 @@
+package org.apache.airavata.k8s.api.server.controller;
+
+import org.apache.airavata.k8s.api.resources.experiment.ExperimentResource;
+import org.apache.airavata.k8s.api.resources.workflow.WorkflowResource;
+import org.apache.airavata.k8s.api.server.service.WorkflowService;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+@RestController
+@RequestMapping(path="/workflow")
+public class WorkflowController {
+
+    @Resource
+    private WorkflowService workflowService;
+
+    @PostMapping(path = "create/{name}")
+    public long createWorkflow(@PathVariable("name") String name, @RequestBody String workflowGraph) {
+        return this.workflowService.createWorkflow(new WorkflowResource().setName(name).setWorkflowGraphXML(workflowGraph));
+    }
+
+    @GetMapping(path = "", produces = MediaType.APPLICATION_JSON_VALUE)
+    public List<WorkflowResource> getAllWorkflows() {
+        return this.workflowService.getAll();
+    }
+
+    @GetMapping(path = "{id}/launch", produces = MediaType.APPLICATION_JSON_VALUE)
+    public long launchExperiment(@PathVariable("id") long id) {
+        return this.workflowService.launchWorkflow(id);
+    }
+}
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/process/ProcessModel.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/process/ProcessModel.java
index d3f8984..5a4054f 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/process/ProcessModel.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/process/ProcessModel.java
@@ -22,6 +22,7 @@ package org.apache.airavata.k8s.api.server.model.process;
 import org.apache.airavata.k8s.api.server.model.commons.ErrorModel;
 import org.apache.airavata.k8s.api.server.model.experiment.Experiment;
 import org.apache.airavata.k8s.api.server.model.task.TaskModel;
+import org.apache.airavata.k8s.api.server.model.workflow.Workflow;
 
 import javax.persistence.*;
 import java.util.ArrayList;
@@ -45,6 +46,11 @@ public class ProcessModel {
     @ManyToOne
     private Experiment experiment;
 
+    @ManyToOne
+    private Workflow workflow;
+
+    private String name;
+
     private long creationTime;
     private long lastUpdateTime;
 
@@ -61,75 +67,117 @@ public class ProcessModel {
 
     private String experimentDataDir;
 
+    private ProcessType processType = ProcessType.EXPERIMENT;
+
     public long getId() {
         return id;
     }
 
-    public void setId(long id) {
+    public ProcessModel setId(long id) {
         this.id = id;
+        return this;
     }
 
     public Experiment getExperiment() {
         return experiment;
     }
 
-    public void setExperiment(Experiment experiment) {
+    public ProcessModel setExperiment(Experiment experiment) {
         this.experiment = experiment;
+        return this;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public ProcessModel setName(String name) {
+        this.name = name;
+        return this;
     }
 
     public long getCreationTime() {
         return creationTime;
     }
 
-    public void setCreationTime(long creationTime) {
+    public ProcessModel setCreationTime(long creationTime) {
         this.creationTime = creationTime;
+        return this;
     }
 
     public long getLastUpdateTime() {
         return lastUpdateTime;
     }
 
-    public void setLastUpdateTime(long lastUpdateTime) {
+    public ProcessModel setLastUpdateTime(long lastUpdateTime) {
         this.lastUpdateTime = lastUpdateTime;
+        return this;
     }
 
     public List<ProcessStatus> getProcessStatuses() {
         return processStatuses;
     }
 
-    public void setProcessStatuses(List<ProcessStatus> processStatuses) {
+    public ProcessModel setProcessStatuses(List<ProcessStatus> processStatuses) {
         this.processStatuses = processStatuses;
+        return this;
     }
 
     public List<TaskModel> getTasks() {
         return tasks;
     }
 
-    public void setTasks(List<TaskModel> tasks) {
+    public ProcessModel setTasks(List<TaskModel> tasks) {
         this.tasks = tasks;
+        return this;
     }
 
     public String getTaskDag() {
         return taskDag;
     }
 
-    public void setTaskDag(String taskDag) {
+    public ProcessModel setTaskDag(String taskDag) {
         this.taskDag = taskDag;
+        return this;
     }
 
     public List<ErrorModel> getProcessErrors() {
         return processErrors;
     }
 
-    public void setProcessErrors(List<ErrorModel> processErrors) {
+    public ProcessModel setProcessErrors(List<ErrorModel> processErrors) {
         this.processErrors = processErrors;
+        return this;
     }
 
     public String getExperimentDataDir() {
         return experimentDataDir;
     }
 
-    public void setExperimentDataDir(String experimentDataDir) {
+    public ProcessModel setExperimentDataDir(String experimentDataDir) {
         this.experimentDataDir = experimentDataDir;
+        return this;
+    }
+
+    public ProcessType getProcessType() {
+        return processType;
+    }
+
+    public ProcessModel setProcessType(ProcessType processType) {
+        this.processType = processType;
+        return this;
+    }
+
+    public Workflow getWorkflow() {
+        return workflow;
+    }
+
+    public ProcessModel setWorkflow(Workflow workflow) {
+        this.workflow = workflow;
+        return this;
+    }
+
+    public enum ProcessType {
+        WORKFLOW, EXPERIMENT;
     }
 }
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/TaskDAG.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/TaskDAG.java
new file mode 100644
index 0000000..88ffef3
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/TaskDAG.java
@@ -0,0 +1,52 @@
+package org.apache.airavata.k8s.api.server.model.task;
+
+import com.sun.javafx.tk.Toolkit;
+
+import javax.persistence.*;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+@Entity
+public class TaskDAG {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.AUTO)
+    private long id;
+
+    @ManyToOne
+    private TaskOutPort sourceOutPort;
+
+    @ManyToOne
+    private TaskModel targetTask;
+
+    public long getId() {
+        return id;
+    }
+
+    public TaskDAG setId(long id) {
+        this.id = id;
+        return this;
+    }
+
+    public TaskOutPort getSourceOutPort() {
+        return sourceOutPort;
+    }
+
+    public TaskDAG setSourceOutPort(TaskOutPort sourceOutPort) {
+        this.sourceOutPort = sourceOutPort;
+        return this;
+    }
+
+    public TaskModel getTargetTask() {
+        return targetTask;
+    }
+
+    public TaskDAG setTargetTask(TaskModel targetTask) {
+        this.targetTask = targetTask;
+        return this;
+    }
+}
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/TaskInput.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/TaskInput.java
new file mode 100644
index 0000000..4a2d703
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/TaskInput.java
@@ -0,0 +1,86 @@
+package org.apache.airavata.k8s.api.server.model.task;
+
+import javax.persistence.*;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+@Entity
+public class TaskInput {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.AUTO)
+    private long id;
+
+    @Column(name = "TASK_INPUT_NAME")
+    private String name;
+
+    @Column(name = "TASK_INPUT_VALUE")
+    private String value;
+
+    @Column(name = "TASK_INPUT_TYPE")
+    private String type;
+
+    @Column(name = "IMPORT_FORM")
+    private String importFrom;
+
+    @ManyToOne
+    private TaskModel taskModel;
+
+    public long getId() {
+        return id;
+    }
+
+    public TaskInput setId(long id) {
+        this.id = id;
+        return this;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public TaskInput setName(String name) {
+        this.name = name;
+        return this;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public TaskInput setValue(String value) {
+        this.value = value;
+        return this;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public TaskInput setType(String type) {
+        this.type = type;
+        return this;
+    }
+
+    public TaskModel getTaskModel() {
+        return taskModel;
+    }
+
+    public TaskInput setTaskModel(TaskModel taskModel) {
+        this.taskModel = taskModel;
+        return this;
+    }
+
+    public String getImportFrom() {
+        return importFrom;
+    }
+
+    public TaskInput setImportFrom(String importFrom) {
+        this.importFrom = importFrom;
+        return this;
+    }
+}
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/TaskModel.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/TaskModel.java
index 6ce995d..247bae1 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/TaskModel.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/TaskModel.java
@@ -22,12 +22,11 @@ package org.apache.airavata.k8s.api.server.model.task;
 import org.apache.airavata.k8s.api.server.model.commons.ErrorModel;
 import org.apache.airavata.k8s.api.server.model.job.JobModel;
 import org.apache.airavata.k8s.api.server.model.process.ProcessModel;
+import org.apache.airavata.k8s.api.server.model.task.type.TaskModelType;
 
 import javax.persistence.*;
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
 /**
  * TODO: Class level comments please
@@ -43,7 +42,10 @@ public class TaskModel {
     @GeneratedValue(strategy = GenerationType.AUTO)
     private long id;
 
-    private TaskTypes taskType;
+    private String name;
+
+    @ManyToOne
+    private TaskModelType taskType;
 
     @ManyToOne
     private ProcessModel parentProcess;
@@ -52,6 +54,10 @@ public class TaskModel {
     private long lastUpdateTime;
     private int orderIndex;
 
+    private boolean startingTask;
+
+    private boolean stoppingTask;
+
     @OneToMany(mappedBy = "taskModel", cascade = CascadeType.ALL)
     private List<TaskStatus> taskStatuses = new ArrayList<>();
 
@@ -64,7 +70,13 @@ public class TaskModel {
     private List<JobModel> jobs = new ArrayList<>();
 
     @OneToMany(mappedBy = "taskModel", cascade = CascadeType.ALL)
-    private List<TaskParam> taskParams = new ArrayList<>();
+    private List<TaskInput> taskInputs = new ArrayList<>();
+
+    @OneToMany(mappedBy = "taskModel", cascade = CascadeType.ALL)
+    private List<TaskOutput> taskOutputs = new ArrayList<>();
+
+    @OneToMany(mappedBy = "taskModel", cascade = CascadeType.ALL)
+    private List<TaskOutPort> taskOutPorts = new ArrayList<>();
 
     public long getId() {
         return id;
@@ -74,14 +86,6 @@ public class TaskModel {
         this.id = id;
     }
 
-    public TaskTypes getTaskType() {
-        return taskType;
-    }
-
-    public void setTaskType(TaskTypes taskType) {
-        this.taskType = taskType;
-    }
-
     public ProcessModel getParentProcess() {
         return parentProcess;
     }
@@ -138,12 +142,48 @@ public class TaskModel {
         this.jobs = jobs;
     }
 
-    public List<TaskParam> getTaskParams() {
-        return taskParams;
+    public List<TaskInput> getTaskInputs() {
+        return taskInputs;
+    }
+
+    public TaskModel setTaskInputs(List<TaskInput> taskInputs) {
+        this.taskInputs = taskInputs;
+        return this;
+    }
+
+    public List<TaskOutput> getTaskOutputs() {
+        return taskOutputs;
+    }
+
+    public TaskModel setTaskOutputs(List<TaskOutput> taskOutputs) {
+        this.taskOutputs = taskOutputs;
+        return this;
+    }
+
+    public TaskModelType getTaskType() {
+        return taskType;
     }
 
-    public TaskModel setTaskParams(List<TaskParam> taskParams) {
-        this.taskParams = taskParams;
+    public TaskModel setTaskType(TaskModelType taskType) {
+        this.taskType = taskType;
+        return this;
+    }
+
+    public boolean isStartingTask() {
+        return startingTask;
+    }
+
+    public TaskModel setStartingTask(boolean startingTask) {
+        this.startingTask = startingTask;
+        return this;
+    }
+
+    public boolean isStoppingTask() {
+        return stoppingTask;
+    }
+
+    public TaskModel setStoppingTask(boolean stoppingTask) {
+        this.stoppingTask = stoppingTask;
         return this;
     }
 
@@ -156,35 +196,19 @@ public class TaskModel {
         return this;
     }
 
-    public enum TaskTypes {
-        ENV_SETUP(0),
-        INGRESS_DATA_STAGING(1),
-        EGRESS_DATA_STAGING(2),
-        JOB_SUBMISSION(3),
-        ENV_CLEANUP(4),
-        MONITORING(5),
-        OUTPUT_FETCHING(6);
-
-        private static Map<Integer, TaskTypes> map = new HashMap<>();
-
-        static {
-            for (TaskTypes taskType : TaskTypes.values()) {
-                map.put(taskType.value, taskType);
-            }
-        }
-        private final int value;
-
-        public static TaskTypes valueOf(int taskType) {
-            return map.get(taskType);
-        }
+    public String getName() {
+        return name;
+    }
 
-        private TaskTypes(int value) {
-            this.value = value;
-        }
+    public void setName(String name) {
+        this.name = name;
+    }
 
-        public int getValue() {
-            return value;
-        }
+    public List<TaskOutPort> getTaskOutPorts() {
+        return taskOutPorts;
     }
 
+    public void setTaskOutPorts(List<TaskOutPort> taskOutPorts) {
+        this.taskOutPorts = taskOutPorts;
+    }
 }
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/TaskOutPort.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/TaskOutPort.java
new file mode 100644
index 0000000..2f86638
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/TaskOutPort.java
@@ -0,0 +1,57 @@
+package org.apache.airavata.k8s.api.server.model.task;
+
+import javax.persistence.*;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+@Entity
+public class TaskOutPort {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.AUTO)
+    private long id;
+
+    private String name;
+
+    private int referenceId;
+
+    @ManyToOne
+    private TaskModel taskModel;
+
+    public long getId() {
+        return id;
+    }
+
+    public void setId(long id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public int getReferenceId() {
+        return referenceId;
+    }
+
+    public TaskOutPort setReferenceId(int referenceId) {
+        this.referenceId = referenceId;
+        return this;
+    }
+
+    public TaskModel getTaskModel() {
+        return taskModel;
+    }
+
+    public void setTaskModel(TaskModel taskModel) {
+        this.taskModel = taskModel;
+    }
+}
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/TaskOutput.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/TaskOutput.java
new file mode 100644
index 0000000..4265683
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/TaskOutput.java
@@ -0,0 +1,86 @@
+package org.apache.airavata.k8s.api.server.model.task;
+
+import javax.persistence.*;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+@Entity
+public class TaskOutput {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.AUTO)
+    private long id;
+
+    @Column(name = "TASK_OUTPUT_NAME")
+    private String name;
+
+    @Column(name = "TASK_OUTPUT_VALUE")
+    private String value;
+
+    @Column(name = "TASK_OUTPUT_TYPE")
+    private String type;
+
+    @Column(name = "EXPORT_TO")
+    private String exportTo;
+
+    @ManyToOne
+    private TaskModel taskModel;
+
+    public long getId() {
+        return id;
+    }
+
+    public TaskOutput setId(long id) {
+        this.id = id;
+        return this;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public TaskOutput setName(String name) {
+        this.name = name;
+        return this;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public TaskOutput setValue(String value) {
+        this.value = value;
+        return this;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public TaskOutput setType(String type) {
+        this.type = type;
+        return this;
+    }
+
+    public String getExportTo() {
+        return exportTo;
+    }
+
+    public TaskOutput setExportTo(String exportTo) {
+        this.exportTo = exportTo;
+        return this;
+    }
+
+    public TaskModel getTaskModel() {
+        return taskModel;
+    }
+
+    public TaskOutput setTaskModel(TaskModel taskModel) {
+        this.taskModel = taskModel;
+        return this;
+    }
+}
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/TaskParam.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/TaskParam.java
deleted file mode 100644
index cbeaace..0000000
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/TaskParam.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/**
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.airavata.k8s.api.server.model.task;
-
-import javax.persistence.*;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-@Entity
-@Table(name = "TASK_PARAM")
-public class TaskParam {
-
-    @Id
-    @GeneratedValue(strategy = GenerationType.AUTO)
-    private long id;
-
-    @Column(name = "PARAM_KEY")
-    private String key;
-
-    @Column(name = "PARAM_VALUE")
-    private String value;
-
-    @ManyToOne
-    private TaskModel taskModel;
-
-    public long getId() {
-        return id;
-    }
-
-    public TaskParam setId(long id) {
-        this.id = id;
-        return this;
-    }
-
-    public String getKey() {
-        return key;
-    }
-
-    public TaskParam setKey(String key) {
-        this.key = key;
-        return this;
-    }
-
-    public String getValue() {
-        return value;
-    }
-
-    public TaskParam setValue(String value) {
-        this.value = value;
-        return this;
-    }
-
-    public TaskModel getTaskModel() {
-        return taskModel;
-    }
-
-    public TaskParam setTaskModel(TaskModel taskModel) {
-        this.taskModel = taskModel;
-        return this;
-    }
-}
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/type/TaskInputType.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/type/TaskInputType.java
new file mode 100644
index 0000000..93d8dd6
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/type/TaskInputType.java
@@ -0,0 +1,69 @@
+package org.apache.airavata.k8s.api.server.model.task.type;
+
+import javax.persistence.*;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+@Entity
+public class TaskInputType {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.AUTO)
+    private long id;
+
+    private String name;
+    private String type;
+    private String defaultValue;
+
+    @ManyToOne
+    private TaskModelType taskModelType;
+
+    public long getId() {
+        return id;
+    }
+
+    public TaskInputType setId(long id) {
+        this.id = id;
+        return this;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public TaskInputType setName(String name) {
+        this.name = name;
+        return this;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public TaskInputType setType(String type) {
+        this.type = type;
+        return this;
+    }
+
+    public String getDefaultValue() {
+        return defaultValue;
+    }
+
+    public TaskInputType setDefaultValue(String defaultValue) {
+        this.defaultValue = defaultValue;
+        return this;
+    }
+
+    public TaskModelType getTaskModelType() {
+        return taskModelType;
+    }
+
+    public TaskInputType setTaskModelType(TaskModelType taskModelType) {
+        this.taskModelType = taskModelType;
+        return this;
+    }
+}
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/type/TaskModelType.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/type/TaskModelType.java
new file mode 100644
index 0000000..cce35c7
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/type/TaskModelType.java
@@ -0,0 +1,95 @@
+package org.apache.airavata.k8s.api.server.model.task.type;
+
+import javax.persistence.*;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+@Entity
+public class TaskModelType {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.AUTO)
+    private long id;
+
+    private String name;
+    private String topicName;
+    private String icon;
+
+    @OneToMany(mappedBy = "taskModelType", cascade = CascadeType.ALL)
+    private List<TaskInputType> inputTypes = new ArrayList<>();
+
+    @OneToMany(mappedBy = "taskModelType", cascade = CascadeType.ALL)
+    private List<TaskOutputType> outputTypes = new ArrayList<>();
+
+    @OneToMany(mappedBy = "taskModelType", cascade = CascadeType.ALL)
+    private List<TaskOutPortType> outPorts = new ArrayList<>();
+
+    public long getId() {
+        return id;
+    }
+
+    public TaskModelType setId(long id) {
+        this.id = id;
+        return this;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public TaskModelType setName(String name) {
+        this.name = name;
+        return this;
+    }
+
+    public String getTopicName() {
+        return topicName;
+    }
+
+    public TaskModelType setTopicName(String topicName) {
+        this.topicName = topicName;
+        return this;
+    }
+
+    public List<TaskInputType> getInputTypes() {
+        return inputTypes;
+    }
+
+    public TaskModelType setInputTypes(List<TaskInputType> inputTypes) {
+        this.inputTypes = inputTypes;
+        return this;
+    }
+
+    public List<TaskOutputType> getOutputTypes() {
+        return outputTypes;
+    }
+
+    public TaskModelType setOutputTypes(List<TaskOutputType> outputTypes) {
+        this.outputTypes = outputTypes;
+        return this;
+    }
+
+    public List<TaskOutPortType> getOutPorts() {
+        return outPorts;
+    }
+
+    public TaskModelType setOutPorts(List<TaskOutPortType> outPorts) {
+        this.outPorts = outPorts;
+        return this;
+    }
+
+    public String getIcon() {
+        return icon;
+    }
+
+    public TaskModelType setIcon(String icon) {
+        this.icon = icon;
+        return this;
+    }
+}
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/type/TaskOutPortType.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/type/TaskOutPortType.java
new file mode 100644
index 0000000..5d0a836
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/type/TaskOutPortType.java
@@ -0,0 +1,61 @@
+package org.apache.airavata.k8s.api.server.model.task.type;
+
+import javax.persistence.*;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+@Entity
+public class TaskOutPortType {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.AUTO)
+    private long id;
+
+    private String name;
+
+    @Column(name = "PORT_ORDER")
+    private int order = 0;
+
+    @ManyToOne
+    private TaskModelType taskModelType;
+
+    public long getId() {
+        return id;
+    }
+
+    public TaskOutPortType setId(long id) {
+        this.id = id;
+        return this;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public TaskOutPortType setName(String name) {
+        this.name = name;
+        return this;
+    }
+
+    public int getOrder() {
+        return order;
+    }
+
+    public TaskOutPortType setOrder(int order) {
+        this.order = order;
+        return this;
+    }
+
+    public TaskModelType getTaskModelType() {
+        return taskModelType;
+    }
+
+    public TaskOutPortType setTaskModelType(TaskModelType taskModelType) {
+        this.taskModelType = taskModelType;
+        return this;
+    }
+}
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/type/TaskOutputType.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/type/TaskOutputType.java
new file mode 100644
index 0000000..3c159a0
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/type/TaskOutputType.java
@@ -0,0 +1,59 @@
+package org.apache.airavata.k8s.api.server.model.task.type;
+
+import javax.persistence.*;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+@Entity
+public class TaskOutputType {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.AUTO)
+    private long id;
+
+    private String name;
+    private String type;
+
+    @ManyToOne
+    private TaskModelType taskModelType;
+
+    public long getId() {
+        return id;
+    }
+
+    public TaskOutputType setId(long id) {
+        this.id = id;
+        return this;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public TaskOutputType setName(String name) {
+        this.name = name;
+        return this;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public TaskOutputType setType(String type) {
+        this.type = type;
+        return this;
+    }
+
+    public TaskModelType getTaskModelType() {
+        return taskModelType;
+    }
+
+    public TaskOutputType setTaskModelType(TaskModelType taskModelType) {
+        this.taskModelType = taskModelType;
+        return this;
+    }
+}
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/workflow/Workflow.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/workflow/Workflow.java
new file mode 100644
index 0000000..82d6bf5
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/workflow/Workflow.java
@@ -0,0 +1,69 @@
+package org.apache.airavata.k8s.api.server.model.workflow;
+
+import org.apache.airavata.k8s.api.server.model.process.ProcessModel;
+
+import javax.persistence.*;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+@Entity
+public class Workflow {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.AUTO)
+    private long id;
+
+    private String name;
+
+    @Lob
+    @Column(length = 1000000, name = "CONTENT")
+    @Basic(fetch = FetchType.LAZY)
+    private byte[] workFlowGraph;
+
+
+    @OneToMany(mappedBy = "experiment", cascade = CascadeType.ALL)
+    private List<ProcessModel> processes = new ArrayList<>();
+
+    public long getId() {
+        return id;
+    }
+
+    public Workflow setId(long id) {
+        this.id = id;
+        return this;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public Workflow setName(String name) {
+        this.name = name;
+        return this;
+    }
+
+
+    public byte[] getWorkFlowGraph() {
+        return workFlowGraph;
+    }
+
+    public Workflow setWorkFlowGraph(byte[] workFlowGraph) {
+        this.workFlowGraph = workFlowGraph;
+        return this;
+    }
+
+    public List<ProcessModel> getProcesses() {
+        return processes;
+    }
+
+    public Workflow setProcesses(List<ProcessModel> processes) {
+        this.processes = processes;
+        return this;
+    }
+}
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/TaskParamRepository.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/TaskParamRepository.java
deleted file mode 100644
index 8d66c41..0000000
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/TaskParamRepository.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/**
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.airavata.k8s.api.server.repository;
-
-import org.apache.airavata.k8s.api.server.model.task.TaskParam;
-import org.springframework.data.repository.CrudRepository;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-public interface TaskParamRepository extends CrudRepository<TaskParam, Long> {
-}
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/ApplicationDeploymentRepository.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/application/ApplicationDeploymentRepository.java
similarity index 94%
rename from airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/ApplicationDeploymentRepository.java
rename to airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/application/ApplicationDeploymentRepository.java
index 8699bea..157e3a2 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/ApplicationDeploymentRepository.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/application/ApplicationDeploymentRepository.java
@@ -17,7 +17,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.airavata.k8s.api.server.repository;
+package org.apache.airavata.k8s.api.server.repository.application;
 
 import org.apache.airavata.k8s.api.server.model.application.ApplicationDeployment;
 import org.springframework.data.repository.CrudRepository;
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/ApplicationIfaceRepository.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/application/ApplicationIfaceRepository.java
similarity index 94%
rename from airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/ApplicationIfaceRepository.java
rename to airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/application/ApplicationIfaceRepository.java
index 57a35fb..1912528 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/ApplicationIfaceRepository.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/application/ApplicationIfaceRepository.java
@@ -17,7 +17,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.airavata.k8s.api.server.repository;
+package org.apache.airavata.k8s.api.server.repository.application;
 
 import org.apache.airavata.k8s.api.server.model.application.ApplicationInterface;
 import org.springframework.data.repository.CrudRepository;
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/ApplicationInputRepository.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/application/ApplicationInputRepository.java
similarity index 94%
rename from airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/ApplicationInputRepository.java
rename to airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/application/ApplicationInputRepository.java
index 624fdfe..0a55f1c 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/ApplicationInputRepository.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/application/ApplicationInputRepository.java
@@ -17,7 +17,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.airavata.k8s.api.server.repository;
+package org.apache.airavata.k8s.api.server.repository.application;
 
 import org.apache.airavata.k8s.api.server.model.application.ApplicationInput;
 import org.springframework.data.repository.CrudRepository;
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/ApplicationModuleRepository.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/application/ApplicationModuleRepository.java
similarity index 94%
rename from airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/ApplicationModuleRepository.java
rename to airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/application/ApplicationModuleRepository.java
index fd1a2ae..4fc8188 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/ApplicationModuleRepository.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/application/ApplicationModuleRepository.java
@@ -17,7 +17,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.airavata.k8s.api.server.repository;
+package org.apache.airavata.k8s.api.server.repository.application;
 
 import org.apache.airavata.k8s.api.server.model.application.ApplicationModule;
 import org.springframework.data.repository.CrudRepository;
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/ApplicationOutputRepository.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/application/ApplicationOutputRepository.java
similarity index 94%
rename from airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/ApplicationOutputRepository.java
rename to airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/application/ApplicationOutputRepository.java
index 852179f..cea9db1 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/ApplicationOutputRepository.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/application/ApplicationOutputRepository.java
@@ -17,7 +17,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.airavata.k8s.api.server.repository;
+package org.apache.airavata.k8s.api.server.repository.application;
 
 import org.apache.airavata.k8s.api.server.model.application.ApplicationOutput;
 import org.springframework.data.repository.CrudRepository;
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/ComputeRepository.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/compute/ComputeRepository.java
similarity index 95%
rename from airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/ComputeRepository.java
rename to airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/compute/ComputeRepository.java
index fb73dfb..c1f64c7 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/ComputeRepository.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/compute/ComputeRepository.java
@@ -17,7 +17,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.airavata.k8s.api.server.repository;
+package org.apache.airavata.k8s.api.server.repository.compute;
 
 import org.apache.airavata.k8s.api.server.model.compute.ComputeResourceModel;
 import org.springframework.data.repository.CrudRepository;
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/DataStoreRepository.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/data/DataStoreRepository.java
similarity index 95%
rename from airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/DataStoreRepository.java
rename to airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/data/DataStoreRepository.java
index ec584c3..85e8596 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/DataStoreRepository.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/data/DataStoreRepository.java
@@ -17,7 +17,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.airavata.k8s.api.server.repository;
+package org.apache.airavata.k8s.api.server.repository.data;
 
 import org.apache.airavata.k8s.api.server.model.data.DataStoreModel;
 import org.springframework.data.repository.CrudRepository;
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/ExperimentInputDataRepository.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/experiment/ExperimentInputDataRepository.java
similarity index 94%
rename from airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/ExperimentInputDataRepository.java
rename to airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/experiment/ExperimentInputDataRepository.java
index 9087da7..1d84e75 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/ExperimentInputDataRepository.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/experiment/ExperimentInputDataRepository.java
@@ -17,7 +17,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.airavata.k8s.api.server.repository;
+package org.apache.airavata.k8s.api.server.repository.experiment;
 
 import org.apache.airavata.k8s.api.server.model.experiment.ExperimentInputData;
 import org.springframework.data.repository.CrudRepository;
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/ExperimentOutputDataRepository.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/experiment/ExperimentOutputDataRepository.java
similarity index 94%
rename from airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/ExperimentOutputDataRepository.java
rename to airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/experiment/ExperimentOutputDataRepository.java
index b3b9392..8564cdb 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/ExperimentOutputDataRepository.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/experiment/ExperimentOutputDataRepository.java
@@ -17,7 +17,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.airavata.k8s.api.server.repository;
+package org.apache.airavata.k8s.api.server.repository.experiment;
 
 import org.apache.airavata.k8s.api.server.model.experiment.ExperimentOutputData;
 import org.springframework.data.repository.CrudRepository;
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/ExperimentRepository.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/experiment/ExperimentRepository.java
similarity index 94%
rename from airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/ExperimentRepository.java
rename to airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/experiment/ExperimentRepository.java
index ca98567..26078e5 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/ExperimentRepository.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/experiment/ExperimentRepository.java
@@ -17,7 +17,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.airavata.k8s.api.server.repository;
+package org.apache.airavata.k8s.api.server.repository.experiment;
 
 import org.apache.airavata.k8s.api.server.model.experiment.Experiment;
 import org.springframework.data.repository.CrudRepository;
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/ExperimentStatusRepository.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/experiment/ExperimentStatusRepository.java
similarity index 94%
rename from airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/ExperimentStatusRepository.java
rename to airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/experiment/ExperimentStatusRepository.java
index 0b795c1..63cd3f0 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/ExperimentStatusRepository.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/experiment/ExperimentStatusRepository.java
@@ -17,7 +17,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.airavata.k8s.api.server.repository;
+package org.apache.airavata.k8s.api.server.repository.experiment;
 
 import org.apache.airavata.k8s.api.server.model.experiment.ExperimentStatus;
 import org.springframework.data.repository.CrudRepository;
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/ProcessRepository.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/process/ProcessRepository.java
similarity index 94%
rename from airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/ProcessRepository.java
rename to airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/process/ProcessRepository.java
index d0dc311..62c0aaa 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/ProcessRepository.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/process/ProcessRepository.java
@@ -17,7 +17,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.airavata.k8s.api.server.repository;
+package org.apache.airavata.k8s.api.server.repository.process;
 
 import org.apache.airavata.k8s.api.server.model.process.ProcessModel;
 import org.springframework.data.repository.CrudRepository;
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/ProcessStatusRepository.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/process/ProcessStatusRepository.java
similarity index 94%
rename from airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/ProcessStatusRepository.java
rename to airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/process/ProcessStatusRepository.java
index bb11a9a..0c01b8a 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/ProcessStatusRepository.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/process/ProcessStatusRepository.java
@@ -17,7 +17,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.airavata.k8s.api.server.repository;
+package org.apache.airavata.k8s.api.server.repository.process;
 
 import org.apache.airavata.k8s.api.server.model.process.ProcessStatus;
 import org.springframework.data.repository.CrudRepository;
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/task/TaskDAGRepository.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/task/TaskDAGRepository.java
new file mode 100644
index 0000000..b2cb7fa
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/task/TaskDAGRepository.java
@@ -0,0 +1,13 @@
+package org.apache.airavata.k8s.api.server.repository.task;
+
+import org.apache.airavata.k8s.api.server.model.task.TaskDAG;
+import org.springframework.data.repository.CrudRepository;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public interface TaskDAGRepository extends CrudRepository<TaskDAG, Long> {
+}
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/task/TaskInputRepository.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/task/TaskInputRepository.java
new file mode 100644
index 0000000..0576245
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/task/TaskInputRepository.java
@@ -0,0 +1,13 @@
+package org.apache.airavata.k8s.api.server.repository.task;
+
+import org.apache.airavata.k8s.api.server.model.task.TaskInput;
+import org.springframework.data.repository.CrudRepository;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public interface TaskInputRepository extends CrudRepository<TaskInput, Long> {
+}
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/task/TaskOutPortRepository.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/task/TaskOutPortRepository.java
new file mode 100644
index 0000000..63ffd89
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/task/TaskOutPortRepository.java
@@ -0,0 +1,17 @@
+package org.apache.airavata.k8s.api.server.repository.task;
+
+import org.apache.airavata.k8s.api.server.model.task.TaskOutPort;
+import org.springframework.data.repository.CrudRepository;
+
+import java.util.Optional;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public interface TaskOutPortRepository extends CrudRepository<TaskOutPort, Long> {
+    Optional<TaskOutPort> findByName(String name);
+    Optional<TaskOutPort> findByReferenceIdAndTaskModel_Id(int referenceId, long taskId);
+}
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/task/TaskOutputRepository.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/task/TaskOutputRepository.java
new file mode 100644
index 0000000..62a87af
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/task/TaskOutputRepository.java
@@ -0,0 +1,13 @@
+package org.apache.airavata.k8s.api.server.repository.task;
+
+import org.apache.airavata.k8s.api.server.model.task.TaskOutput;
+import org.springframework.data.repository.CrudRepository;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public interface TaskOutputRepository extends CrudRepository<TaskOutput, Long> {
+}
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/TaskRepository.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/task/TaskRepository.java
similarity index 95%
rename from airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/TaskRepository.java
rename to airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/task/TaskRepository.java
index 2701c33..f20e936 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/TaskRepository.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/task/TaskRepository.java
@@ -17,7 +17,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.airavata.k8s.api.server.repository;
+package org.apache.airavata.k8s.api.server.repository.task;
 
 import org.apache.airavata.k8s.api.server.model.task.TaskModel;
 import org.springframework.data.repository.CrudRepository;
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/TaskStatusRepository.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/task/TaskStatusRepository.java
similarity index 95%
rename from airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/TaskStatusRepository.java
rename to airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/task/TaskStatusRepository.java
index b79ad2b..05096a9 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/TaskStatusRepository.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/task/TaskStatusRepository.java
@@ -17,7 +17,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.airavata.k8s.api.server.repository;
+package org.apache.airavata.k8s.api.server.repository.task;
 
 import org.apache.airavata.k8s.api.server.model.task.TaskStatus;
 import org.springframework.data.repository.CrudRepository;
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/task/type/TaskInputTypeRepository.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/task/type/TaskInputTypeRepository.java
new file mode 100644
index 0000000..fe66f66
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/task/type/TaskInputTypeRepository.java
@@ -0,0 +1,13 @@
+package org.apache.airavata.k8s.api.server.repository.task.type;
+
+import org.apache.airavata.k8s.api.server.model.task.type.TaskInputType;
+import org.springframework.data.repository.CrudRepository;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public interface TaskInputTypeRepository extends CrudRepository<TaskInputType, Long> {
+}
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/task/type/TaskOutPortTypeRepository.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/task/type/TaskOutPortTypeRepository.java
new file mode 100644
index 0000000..98d1715
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/task/type/TaskOutPortTypeRepository.java
@@ -0,0 +1,13 @@
+package org.apache.airavata.k8s.api.server.repository.task.type;
+
+import org.apache.airavata.k8s.api.server.model.task.type.TaskOutPortType;
+import org.springframework.data.repository.CrudRepository;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public interface TaskOutPortTypeRepository extends CrudRepository<TaskOutPortType, Long> {
+}
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/task/type/TaskOutputTypeRepository.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/task/type/TaskOutputTypeRepository.java
new file mode 100644
index 0000000..c28d6da
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/task/type/TaskOutputTypeRepository.java
@@ -0,0 +1,13 @@
+package org.apache.airavata.k8s.api.server.repository.task.type;
+
+import org.apache.airavata.k8s.api.server.model.task.type.TaskOutputType;
+import org.springframework.data.repository.CrudRepository;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public interface TaskOutputTypeRepository extends CrudRepository<TaskOutputType, Long> {
+}
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/task/type/TaskTypeRepository.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/task/type/TaskTypeRepository.java
new file mode 100644
index 0000000..a28e374
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/task/type/TaskTypeRepository.java
@@ -0,0 +1,17 @@
+package org.apache.airavata.k8s.api.server.repository.task.type;
+
+import org.apache.airavata.k8s.api.server.model.task.type.TaskModelType;
+import org.springframework.data.repository.CrudRepository;
+
+import java.util.Optional;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public interface TaskTypeRepository extends CrudRepository<TaskModelType, Long> {
+    Optional<TaskModelType> findById(long id);
+    Optional<TaskModelType> findByName(String name);
+}
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/workflow/WorkflowRepository.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/workflow/WorkflowRepository.java
new file mode 100644
index 0000000..259f41e
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/workflow/WorkflowRepository.java
@@ -0,0 +1,16 @@
+package org.apache.airavata.k8s.api.server.repository.workflow;
+
+import org.apache.airavata.k8s.api.server.model.workflow.Workflow;
+import org.springframework.data.repository.CrudRepository;
+
+import java.util.Optional;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public interface WorkflowRepository extends CrudRepository<Workflow, Long> {
+    public Optional<Workflow> findById(long id);
+}
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/ApplicationDeploymentService.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/ApplicationDeploymentService.java
index 941ee6e..93216bc 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/ApplicationDeploymentService.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/ApplicationDeploymentService.java
@@ -21,9 +21,9 @@ package org.apache.airavata.k8s.api.server.service;
 
 import org.apache.airavata.k8s.api.server.ServerRuntimeException;
 import org.apache.airavata.k8s.api.server.model.application.ApplicationDeployment;
-import org.apache.airavata.k8s.api.server.repository.ApplicationDeploymentRepository;
-import org.apache.airavata.k8s.api.server.repository.ApplicationModuleRepository;
-import org.apache.airavata.k8s.api.server.repository.ComputeRepository;
+import org.apache.airavata.k8s.api.server.repository.application.ApplicationDeploymentRepository;
+import org.apache.airavata.k8s.api.server.repository.application.ApplicationModuleRepository;
+import org.apache.airavata.k8s.api.server.repository.compute.ComputeRepository;
 import org.apache.airavata.k8s.api.resources.application.ApplicationDeploymentResource;
 import org.apache.airavata.k8s.api.server.service.util.ToResourceUtil;
 import org.springframework.beans.factory.annotation.Autowired;
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/ApplicationIfaceService.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/ApplicationIfaceService.java
index e95b651..7472c36 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/ApplicationIfaceService.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/ApplicationIfaceService.java
@@ -23,10 +23,10 @@ import org.apache.airavata.k8s.api.server.ServerRuntimeException;
 import org.apache.airavata.k8s.api.server.model.application.ApplicationInput;
 import org.apache.airavata.k8s.api.server.model.application.ApplicationInterface;
 import org.apache.airavata.k8s.api.server.model.application.ApplicationOutput;
-import org.apache.airavata.k8s.api.server.repository.ApplicationIfaceRepository;
-import org.apache.airavata.k8s.api.server.repository.ApplicationInputRepository;
-import org.apache.airavata.k8s.api.server.repository.ApplicationModuleRepository;
-import org.apache.airavata.k8s.api.server.repository.ApplicationOutputRepository;
+import org.apache.airavata.k8s.api.server.repository.application.ApplicationIfaceRepository;
+import org.apache.airavata.k8s.api.server.repository.application.ApplicationInputRepository;
+import org.apache.airavata.k8s.api.server.repository.application.ApplicationModuleRepository;
+import org.apache.airavata.k8s.api.server.repository.application.ApplicationOutputRepository;
 import org.apache.airavata.k8s.api.resources.application.ApplicationIfaceResource;
 import org.apache.airavata.k8s.api.server.service.util.ToResourceUtil;
 import org.springframework.beans.factory.annotation.Autowired;
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/ApplicationModuleService.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/ApplicationModuleService.java
index e658c42..4fd6543 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/ApplicationModuleService.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/ApplicationModuleService.java
@@ -20,7 +20,7 @@
 package org.apache.airavata.k8s.api.server.service;
 
 import org.apache.airavata.k8s.api.server.model.application.ApplicationModule;
-import org.apache.airavata.k8s.api.server.repository.ApplicationModuleRepository;
+import org.apache.airavata.k8s.api.server.repository.application.ApplicationModuleRepository;
 import org.apache.airavata.k8s.api.resources.application.ApplicationModuleResource;
 import org.apache.airavata.k8s.api.server.service.util.ToResourceUtil;
 import org.springframework.beans.factory.annotation.Autowired;
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/ComputeResourceService.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/ComputeResourceService.java
index bceda0b..a3d7525 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/ComputeResourceService.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/ComputeResourceService.java
@@ -20,7 +20,7 @@
 package org.apache.airavata.k8s.api.server.service;
 
 import org.apache.airavata.k8s.api.server.model.compute.ComputeResourceModel;
-import org.apache.airavata.k8s.api.server.repository.ComputeRepository;
+import org.apache.airavata.k8s.api.server.repository.compute.ComputeRepository;
 import org.apache.airavata.k8s.api.resources.compute.ComputeResource;
 import org.apache.airavata.k8s.api.server.service.util.ToResourceUtil;
 import org.springframework.beans.factory.annotation.Autowired;
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/ExperimentService.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/ExperimentService.java
index 1ded541..5f67efd 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/ExperimentService.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/ExperimentService.java
@@ -24,8 +24,13 @@ import org.apache.airavata.k8s.api.server.model.experiment.Experiment;
 import org.apache.airavata.k8s.api.server.model.experiment.ExperimentInputData;
 import org.apache.airavata.k8s.api.server.model.experiment.ExperimentOutputData;
 import org.apache.airavata.k8s.api.server.model.experiment.ExperimentStatus;
-import org.apache.airavata.k8s.api.server.repository.*;
 import org.apache.airavata.k8s.api.resources.experiment.ExperimentResource;
+import org.apache.airavata.k8s.api.server.repository.application.ApplicationDeploymentRepository;
+import org.apache.airavata.k8s.api.server.repository.application.ApplicationIfaceRepository;
+import org.apache.airavata.k8s.api.server.repository.experiment.ExperimentInputDataRepository;
+import org.apache.airavata.k8s.api.server.repository.experiment.ExperimentOutputDataRepository;
+import org.apache.airavata.k8s.api.server.repository.experiment.ExperimentRepository;
+import org.apache.airavata.k8s.api.server.repository.experiment.ExperimentStatusRepository;
 import org.apache.airavata.k8s.api.server.service.messaging.MessagingService;
 import org.apache.airavata.k8s.api.server.service.util.ToResourceUtil;
 import org.springframework.beans.factory.annotation.Autowired;
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/ProcessService.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/ProcessService.java
index 532a1d5..d2b93aa 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/ProcessService.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/ProcessService.java
@@ -24,9 +24,10 @@ import org.apache.airavata.k8s.api.server.ServerRuntimeException;
 import org.apache.airavata.k8s.api.server.model.process.ProcessModel;
 import org.apache.airavata.k8s.api.server.model.process.ProcessStatus;
 import org.apache.airavata.k8s.api.server.model.task.TaskModel;
-import org.apache.airavata.k8s.api.server.repository.ProcessRepository;
+import org.apache.airavata.k8s.api.server.repository.process.ProcessRepository;
 import org.apache.airavata.k8s.api.resources.process.ProcessResource;
-import org.apache.airavata.k8s.api.server.repository.ProcessStatusRepository;
+import org.apache.airavata.k8s.api.server.repository.process.ProcessStatusRepository;
+import org.apache.airavata.k8s.api.server.service.task.TaskService;
 import org.apache.airavata.k8s.api.server.service.util.ToResourceUtil;
 import org.springframework.stereotype.Service;
 
@@ -47,6 +48,8 @@ public class ProcessService {
     private ExperimentService experimentService;
     private TaskService taskService;
 
+    private WorkflowService workflowService;
+
     public ProcessService(ProcessRepository processRepository,
                           ProcessStatusRepository processStatusRepository,
                           ExperimentService experimentService,
@@ -64,9 +67,21 @@ public class ProcessService {
         processModel.setId(resource.getId());
         processModel.setCreationTime(resource.getCreationTime());
         processModel.setLastUpdateTime(resource.getLastUpdateTime());
-        processModel.setExperiment(experimentService.findEntityById(resource.getExperimentId())
-                .orElseThrow(() -> new ServerRuntimeException("Can not find experiment with id " +
-                        resource.getExperimentId())));
+        processModel.setName(resource.getName());
+        processModel.setProcessType(ProcessModel.ProcessType.valueOf(resource.getProcessType()));
+
+        if (resource.getExperimentId() != 0) {
+            processModel.setExperiment(experimentService.findEntityById(resource.getExperimentId())
+                    .orElseThrow(() -> new ServerRuntimeException("Can not find experiment with id " +
+                            resource.getExperimentId())));
+        }
+
+        if (resource.getWorkflowId() != 0) {
+            processModel.setWorkflow(workflowService.findEntityById(resource.getWorkflowId())
+                    .orElseThrow(() -> new ServerRuntimeException("Can not find workflow with id " +
+                            resource.getWorkflowId())));
+        }
+
         processModel.setExperimentDataDir(resource.getExperimentDataDir());
 
         ProcessModel saved = processRepository.save(processModel);
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/WorkflowService.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/WorkflowService.java
new file mode 100644
index 0000000..6090c90
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/WorkflowService.java
@@ -0,0 +1,129 @@
+package org.apache.airavata.k8s.api.server.service;
+
+import org.apache.airavata.k8s.api.resources.process.ProcessResource;
+import org.apache.airavata.k8s.api.resources.workflow.WorkflowResource;
+import org.apache.airavata.k8s.api.server.ServerRuntimeException;
+import org.apache.airavata.k8s.api.server.model.process.ProcessModel;
+import org.apache.airavata.k8s.api.server.model.task.TaskDAG;
+import org.apache.airavata.k8s.api.server.model.task.TaskModel;
+import org.apache.airavata.k8s.api.server.model.task.TaskOutPort;
+import org.apache.airavata.k8s.api.server.model.workflow.Workflow;
+import org.apache.airavata.k8s.api.server.repository.task.TaskDAGRepository;
+import org.apache.airavata.k8s.api.server.repository.task.TaskOutPortRepository;
+import org.apache.airavata.k8s.api.server.repository.task.TaskRepository;
+import org.apache.airavata.k8s.api.server.repository.workflow.WorkflowRepository;
+import org.apache.airavata.k8s.api.server.service.task.TaskService;
+import org.apache.airavata.k8s.api.server.service.util.GraphParser;
+import org.apache.airavata.k8s.api.server.service.util.ToResourceUtil;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+@Service
+public class WorkflowService {
+
+    private ProcessService processService;
+    private TaskService taskService;
+
+    private WorkflowRepository workflowRepository;
+    private TaskOutPortRepository taskOutPortRepository;
+    private TaskRepository taskRepository;
+    private TaskDAGRepository taskDAGRepository;
+
+    public WorkflowService(ProcessService processService,
+                           TaskService taskService,
+                           WorkflowRepository workflowRepository,
+                           TaskOutPortRepository taskOutPortRepository,
+                           TaskRepository taskRepository,
+                           TaskDAGRepository taskDAGRepository) {
+
+        this.processService = processService;
+        this.taskService = taskService;
+        this.workflowRepository = workflowRepository;
+        this.taskOutPortRepository = taskOutPortRepository;
+        this.taskRepository = taskRepository;
+        this.taskDAGRepository = taskDAGRepository;
+    }
+
+    public long createWorkflow(WorkflowResource resource) {
+        Workflow workflow = new Workflow();
+        workflow.setName(resource.getName());
+        workflow.setWorkFlowGraph(resource.getWorkflowGraphXML().getBytes());
+
+        Workflow saved = workflowRepository.save(workflow);
+        return saved.getId();
+    }
+
+    public long launchWorkflow(long id) {
+        Workflow workflow = this.workflowRepository.findById(id)
+                .orElseThrow(() -> new ServerRuntimeException("Workflow with id " + id + "can not be found"));
+
+
+        long processId = processService.create(new ProcessResource()
+                .setName("Workflow Process : " + workflow.getName() + "-" + UUID.randomUUID().toString())
+                .setCreationTime(System.currentTimeMillis()).setProcessType("WORKFLOW"));
+
+        try {
+            GraphParser.ParseResult parseResult = GraphParser.parse(new String(workflow.getWorkFlowGraph()));
+            parseResult.getTasks().forEach((mockId, task) -> {
+                task.getTaskResource().setParentProcessId(processId);
+
+                for (GraphParser.OutPort outPort : task.getOutPorts().values()) {
+                    if (outPort.getNextPort() != null && outPort.getNextPort().getParentOperation() != null) {
+                        if ("Stop".equals(outPort.getNextPort().getParentOperation().getName())) {
+                            task.getTaskResource().setStoppingTask(true);
+                        }
+                    }
+                }
+
+                for (GraphParser.InPort inPort : task.getInPorts().values()) {
+                    for (GraphParser.OutPort outPort : inPort.getPreviousPorts().values()) {
+                        if (outPort.getParentOperation() != null && "Start".equals(outPort.getParentOperation().getName())) {
+                            task.getTaskResource().setStartingTask(true);
+                        }
+                    }
+                }
+
+                long taskId = taskService.create(task.getTaskResource());
+                task.getTaskResource().setId(taskId);
+            });
+
+            parseResult.getEdgeCache().forEach(((outPort, inPort) -> {
+                if (outPort.getParentOperation() != null && outPort.getNextPort().getParentOperation() != null) {
+                    Optional<TaskOutPort> sourceOutPort = taskOutPortRepository
+                            .findByReferenceIdAndTaskModel_Id(outPort.getId(), outPort.getParentTask().getId());
+                    Optional<TaskModel> targetTask = taskRepository.findById(inPort.getParentTask().getId());
+
+                    taskDAGRepository.save(new TaskDAG()
+                            .setSourceOutPort(sourceOutPort.get())
+                            .setTargetTask(targetTask.get()));
+                }
+            }));
+
+        } catch (Exception e) {
+            throw new ServerRuntimeException("Failed to create workflow", e);
+        }
+        return 0;
+    }
+
+    public List<WorkflowResource> getAll() {
+        List<WorkflowResource> workflowResources = new ArrayList<>();
+        Iterable<Workflow> workFlows = this.workflowRepository.findAll();
+        Optional.ofNullable(workFlows)
+                .ifPresent(wfs -> wfs.forEach(wf -> workflowResources.add(ToResourceUtil.toResource(wf).get())));
+        return workflowResources;
+    }
+
+    public Optional<Workflow> findEntityById(long id) {
+        return this.workflowRepository.findById(id);
+    }
+}
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/data/DataStoreService.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/data/DataStoreService.java
index 27fceb1..272ab33 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/data/DataStoreService.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/data/DataStoreService.java
@@ -21,9 +21,9 @@ package org.apache.airavata.k8s.api.server.service.data;
 
 import org.apache.airavata.k8s.api.resources.data.DataEntryResource;
 import org.apache.airavata.k8s.api.server.model.data.DataStoreModel;
-import org.apache.airavata.k8s.api.server.repository.DataStoreRepository;
-import org.apache.airavata.k8s.api.server.repository.ExperimentOutputDataRepository;
-import org.apache.airavata.k8s.api.server.repository.TaskRepository;
+import org.apache.airavata.k8s.api.server.repository.data.DataStoreRepository;
+import org.apache.airavata.k8s.api.server.repository.experiment.ExperimentOutputDataRepository;
+import org.apache.airavata.k8s.api.server.repository.task.TaskRepository;
 import org.springframework.stereotype.Service;
 
 import java.util.ArrayList;
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/TaskService.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/task/TaskService.java
similarity index 53%
rename from airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/TaskService.java
rename to airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/task/TaskService.java
index 11a3b91..d1b3828 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/TaskService.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/task/TaskService.java
@@ -17,22 +17,20 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.airavata.k8s.api.server.service;
+package org.apache.airavata.k8s.api.server.service.task;
 
 import org.apache.airavata.k8s.api.resources.task.TaskResource;
 import org.apache.airavata.k8s.api.resources.task.TaskStatusResource;
 import org.apache.airavata.k8s.api.server.ServerRuntimeException;
-import org.apache.airavata.k8s.api.server.model.task.TaskModel;
-import org.apache.airavata.k8s.api.server.model.task.TaskParam;
-import org.apache.airavata.k8s.api.server.model.task.TaskStatus;
-import org.apache.airavata.k8s.api.server.repository.ProcessRepository;
-import org.apache.airavata.k8s.api.server.repository.TaskParamRepository;
-import org.apache.airavata.k8s.api.server.repository.TaskRepository;
-import org.apache.airavata.k8s.api.server.repository.TaskStatusRepository;
+import org.apache.airavata.k8s.api.server.model.task.*;
+import org.apache.airavata.k8s.api.server.repository.process.ProcessRepository;
+import org.apache.airavata.k8s.api.server.repository.task.*;
+import org.apache.airavata.k8s.api.server.repository.task.type.TaskTypeRepository;
 import org.apache.airavata.k8s.api.server.service.util.ToResourceUtil;
 import org.springframework.stereotype.Service;
 
 import java.util.Optional;
+import java.util.UUID;
 
 /**
  * TODO: Class level comments please
@@ -45,40 +43,77 @@ public class TaskService {
 
     private ProcessRepository processRepository;
     private TaskRepository taskRepository;
-    private TaskParamRepository taskParamRepository;
+    private TaskInputRepository taskInputRepository;
+    private TaskOutputRepository taskOutputRepository;
     private TaskStatusRepository taskStatusRepository;
+    private TaskTypeRepository taskTypeRepository;
+    private TaskOutPortRepository taskOutPortRepository;
+    private TaskDAGRepository taskDAGRepository;
+
 
     public TaskService(ProcessRepository processRepository,
                        TaskRepository taskRepository,
-                       TaskParamRepository taskParamRepository,
-                       TaskStatusRepository taskStatusRepository) {
+                       TaskInputRepository taskInputRepository,
+                       TaskOutputRepository taskOutputRepository,
+                       TaskStatusRepository taskStatusRepository,
+                       TaskTypeRepository taskTypeRepository,
+                       TaskOutPortRepository taskOutPortRepository,
+                       TaskDAGRepository taskDAGRepository) {
 
         this.processRepository = processRepository;
         this.taskRepository = taskRepository;
-        this.taskParamRepository = taskParamRepository;
+        this.taskInputRepository = taskInputRepository;
+        this.taskOutputRepository = taskOutputRepository;
         this.taskStatusRepository = taskStatusRepository;
+        this.taskTypeRepository = taskTypeRepository;
+        this.taskOutPortRepository = taskOutPortRepository;
+        this.taskDAGRepository = taskDAGRepository;
     }
 
     public long create(TaskResource resource) {
         TaskModel taskModel = new TaskModel();
+        taskModel.setName(resource.getName());
         taskModel.setCreationTime(resource.getCreationTime());
         taskModel.setLastUpdateTime(resource.getLastUpdateTime());
         taskModel.setOrderIndex(resource.getOrder());
+        taskModel.setStartingTask(resource.isStartingTask());
+        taskModel.setStoppingTask(resource.isStoppingTask());
         taskModel.setTaskDetail(resource.getTaskDetail());
         taskModel.setParentProcess(processRepository.findById(resource.getParentProcessId())
                 .orElseThrow(() -> new ServerRuntimeException("Can not find process with id " +
                         resource.getParentProcessId())));
-        taskModel.setTaskType(TaskModel.TaskTypes.valueOf(resource.getTaskType()));
+        taskModel.setTaskType(taskTypeRepository.findById(resource.getTaskTypeId())
+                .orElseThrow(() -> new ServerRuntimeException("Can not find task type with id " +
+                resource.getTaskTypeId())));
 
         TaskModel savedTask = taskRepository.save(taskModel);
 
-        Optional.ofNullable(resource.getTaskParams()).ifPresent(params -> params.forEach(param -> {
-            TaskParam taskParam = new TaskParam();
-            taskParam.setKey(param.getKey());
-            taskParam.setValue(param.getValue());
-            taskParam.setTaskModel(savedTask);
-            taskParamRepository.save(taskParam);
+        Optional.ofNullable(resource.getInputs()).ifPresent(inputs -> inputs.forEach(input -> {
+            TaskInput taskInput = new TaskInput();
+            taskInput.setName(input.getName());
+            taskInput.setValue(input.getValue());
+            taskInput.setType(input.getType());
+            taskInput.setImportFrom(input.getImportFrom());
+            taskInput.setTaskModel(savedTask);
+            taskInputRepository.save(taskInput);
+        }));
+
+        Optional.ofNullable(resource.getOutputs()).ifPresent(outputs -> outputs.forEach(output -> {
+            TaskOutput taskOutput = new TaskOutput();
+            taskOutput.setName(output.getName());
+            taskOutput.setValue(output.getValue());
+            taskOutput.setType(output.getType());
+            taskOutput.setExportTo(output.getExportTo());
+            taskOutput.setTaskModel(savedTask);
+            taskOutputRepository.save(taskOutput);
+        }));
 
+        Optional.ofNullable(resource.getOutPorts()).ifPresent(outPorts -> outPorts.forEach(outPort -> {
+            TaskOutPort taskOutPort = new TaskOutPort();
+            taskOutPort.setName(outPort.getName());
+            taskOutPort.setReferenceId(outPort.getReferenceId());
+            taskOutPort.setTaskModel(taskModel);
+            taskOutPortRepository.save(taskOutPort);
         }));
         return savedTask.getId();
     }
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/task/type/TaskTypeService.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/task/type/TaskTypeService.java
new file mode 100644
index 0000000..d2ae0f1
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/task/type/TaskTypeService.java
@@ -0,0 +1,94 @@
+package org.apache.airavata.k8s.api.server.service.task.type;
+
+import org.apache.airavata.k8s.api.resources.task.type.TaskTypeResource;
+import org.apache.airavata.k8s.api.server.model.task.type.TaskInputType;
+import org.apache.airavata.k8s.api.server.model.task.type.TaskModelType;
+import org.apache.airavata.k8s.api.server.model.task.type.TaskOutPortType;
+import org.apache.airavata.k8s.api.server.model.task.type.TaskOutputType;
+import org.apache.airavata.k8s.api.server.repository.task.type.TaskInputTypeRepository;
+import org.apache.airavata.k8s.api.server.repository.task.type.TaskOutPortTypeRepository;
+import org.apache.airavata.k8s.api.server.repository.task.type.TaskOutputTypeRepository;
+import org.apache.airavata.k8s.api.server.repository.task.type.TaskTypeRepository;
+import org.apache.airavata.k8s.api.server.service.util.ToResourceUtil;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+@Service
+public class TaskTypeService {
+
+    private TaskTypeRepository taskTypeRepository;
+    private TaskInputTypeRepository taskInputTypeRepository;
+    private TaskOutputTypeRepository taskOutputTypeRepository;
+    private TaskOutPortTypeRepository taskOutPortTypeRepository;
+
+    public TaskTypeService(TaskTypeRepository taskTypeRepository,
+                           TaskInputTypeRepository taskInputTypeRepository,
+                           TaskOutputTypeRepository taskOutputTypeRepository,
+                           TaskOutPortTypeRepository taskOutPortTypeRepository) {
+
+        this.taskTypeRepository = taskTypeRepository;
+        this.taskInputTypeRepository = taskInputTypeRepository;
+        this.taskOutputTypeRepository = taskOutputTypeRepository;
+        this.taskOutPortTypeRepository = taskOutPortTypeRepository;
+    }
+
+    public long create(TaskTypeResource resource) {
+
+        Optional<TaskModelType> taskType = taskTypeRepository.findByName(resource.getName());
+
+        if (taskType.isPresent()) {
+            return taskType.get().getId();
+        }
+
+        TaskModelType taskModelType = new TaskModelType();
+        taskModelType.setName(resource.getName());
+        taskModelType.setTopicName(resource.getTopicName());
+        taskModelType.setIcon(resource.getIcon());
+        TaskModelType savedTaskType = taskTypeRepository.save(taskModelType);
+
+        Optional.ofNullable(resource.getInputTypes()).ifPresent(inputs -> inputs.forEach(input -> {
+            TaskInputType inputType = new TaskInputType();
+            inputType.setName(input.getName());
+            inputType.setType(input.getType());
+            inputType.setDefaultValue(input.getDefaultValue());
+            inputType.setTaskModelType(savedTaskType);
+            taskInputTypeRepository.save(inputType);
+        }));
+
+        Optional.ofNullable(resource.getOutputTypes()).ifPresent(outputs -> outputs.forEach(output -> {
+            TaskOutputType outputType = new TaskOutputType();
+            outputType.setName(output.getName());
+            outputType.setType(output.getType());
+            outputType.setTaskModelType(savedTaskType);
+            taskOutputTypeRepository.save(outputType);
+        }));
+
+        Optional.ofNullable(resource.getOutPorts()).ifPresent(outPorts -> outPorts.forEach(outPort -> {
+            TaskOutPortType outPortType = new TaskOutPortType();
+            outPortType.setName(outPort.getName());
+            outPortType.setOrder(outPort.getOrder());
+            outPortType.setTaskModelType(savedTaskType);
+            taskOutPortTypeRepository.save(outPortType);
+        }));
+
+        return savedTaskType.getId();
+    }
+
+    public List<TaskTypeResource> getAll() {
+        List<TaskTypeResource> types = new ArrayList<>();
+        Optional.ofNullable(taskTypeRepository.findAll())
+                .ifPresent(taskModelTypes -> taskModelTypes.forEach(taskModelType -> {
+                    types.add(ToResourceUtil.toResource(taskModelType).get());
+                }));
+        return types;
+    }
+}
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/util/GraphParser.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/util/GraphParser.java
new file mode 100644
index 0000000..9cf1630
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/util/GraphParser.java
@@ -0,0 +1,440 @@
+package org.apache.airavata.k8s.api.server.service.util;
+
+import org.apache.airavata.k8s.api.resources.task.TaskInputResource;
+import org.apache.airavata.k8s.api.resources.task.TaskOutPortResource;
+import org.apache.airavata.k8s.api.resources.task.TaskOutputResource;
+import org.apache.airavata.k8s.api.resources.task.TaskResource;
+import org.w3c.dom.*;
+import org.xml.sax.InputSource;
+
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import java.io.File;
+import java.io.StringReader;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public class GraphParser {
+
+    public static ParseResult parse(String content) throws Exception {
+
+        DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
+        DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
+        Document doc = dBuilder.parse(new InputSource(new StringReader(content)));
+        doc.getDocumentElement().normalize();
+
+        NodeList nList = doc.getElementsByTagName("root");
+        Node rootNode = nList.item(0);
+
+        NodeList elementNodeList = rootNode.getChildNodes();
+
+        ParseResult result = new ParseResult();
+
+        for (int temp = 0; temp < elementNodeList.getLength(); temp++) {
+
+            Node nNode = elementNodeList.item(temp);
+
+            if (nNode.getNodeType() == Node.ELEMENT_NODE) {
+                Element eElement = (Element) nNode;
+
+                if ("ProcessingElement".equals(eElement.getTagName())) {
+                    TaskResource taskResource = new TaskResource();
+                    NamedNodeMap attributes = eElement.getAttributes();
+                    int id = -1;
+                    for (int i = 0; i < attributes.getLength(); i++) {
+                        Node attr = attributes.item(i);
+
+                        if ("name".equals(attr.getNodeName())) {
+                            taskResource.setName(attr.getNodeValue());
+
+                        } else if ("Type".equals(attr.getNodeName())) {
+                            taskResource.setTaskTypeId(Long.parseLong(attr.getNodeValue()));
+
+                        } else if (attr.getNodeName().startsWith("in-") || attr.getNodeName().startsWith("out-")) {
+
+                        } else if ("id".equals(attr.getNodeName())) {
+                            id = Integer.parseInt(attr.getNodeValue());
+
+                        } else if (attr.getNodeName().startsWith("output-")) {
+                            TaskOutputResource outputResource = new TaskOutputResource();
+                            outputResource.setName(attr.getNodeName().substring(7));
+                            outputResource.setValue(attr.getNodeValue());
+
+                        } else {
+                            TaskInputResource inputResource = new TaskInputResource();
+                            inputResource.setName(attr.getNodeName());
+                            inputResource.setValue(attr.getNodeValue());
+                            taskResource.getInputs().add(inputResource);
+                        }
+
+                    }
+                    Task task = new Task();
+                    task.id = id;
+                    task.taskResource = taskResource;
+                    result.tasks.put(id, task);
+
+                } else if ("Operation".equals(eElement.getTagName())) {
+                    NamedNodeMap attributes = eElement.getAttributes();
+                    int id = -1;
+                    String name = null;
+                    for (int i = 0; i < attributes.getLength(); i++) {
+                        Node attr = attributes.item(i);
+
+                        if ("name".equals(attr.getNodeName())) {
+                            name = attr.getNodeValue();
+                        } else if ("id".equals(attr.getNodeName())) {
+                            id = Integer.parseInt(attr.getNodeValue());
+                        }
+
+                    }
+
+                    Operation operation = new Operation();
+                    operation.id = id;
+                    operation.name = name;
+                    result.operations.put(id, operation);
+                } else if ("InPort".equals(eElement.getTagName())) {
+
+                    int id = Integer.parseInt(eElement.getAttribute("id"));
+                    String name = eElement.getAttribute("name");
+
+                    Element mxCell = null;
+
+                    NodeList childNodes = eElement.getChildNodes();
+                    for (int i = 0 ; i < childNodes.getLength(); i++) {
+                        Node tempInPort = childNodes.item(i);
+                        if (tempInPort.getNodeType() == Node.ELEMENT_NODE) {
+                            Element tmpInElement = (Element) tempInPort;
+                            if ("mxCell".equals(tmpInElement.getTagName())) {
+                                mxCell = tmpInElement;
+                                break;
+                            }
+                        }
+                    }
+
+                    int parentId = Integer.parseInt(mxCell.getAttribute("parent"));
+
+                    InPort inPort = new InPort();
+                    inPort.id = id;
+                    inPort.name = name;
+                    result.portCache.put(id, inPort);
+
+                    if (result.tasks.containsKey(parentId)) {
+                        inPort.parentTask = result.tasks.get(parentId);
+                        result.tasks.get(parentId).inPorts.put(id, inPort);
+                    } else if (result.operations.containsKey(parentId)) {
+                        inPort.parentOperation = result.operations.get(parentId);
+                        result.operations.get(parentId).inPorts.put(id, inPort);
+                    } else {
+                        throw new Exception("Failed to find parent id " + parentId + " for in port " + id);
+                    }
+
+                } else if ("OutPort".equals(eElement.getTagName())) {
+
+                    int id = Integer.parseInt(eElement.getAttribute("id"));
+                    String name = eElement.getAttribute("name");
+
+                    NamedNodeMap attributes = eElement.getAttributes();
+                    Element mxCell = null;
+
+                    NodeList childNodes = eElement.getChildNodes();
+                    for (int i = 0 ; i < childNodes.getLength(); i++) {
+                        Node tempOutPort = childNodes.item(i);
+                        if (tempOutPort.getNodeType() == Node.ELEMENT_NODE) {
+                            Element tmpOutElement = (Element) tempOutPort;
+                            if ("mxCell".equals(tmpOutElement.getTagName())) {
+                                mxCell = tmpOutElement;
+                                break;
+                            }
+                        }
+                    }
+
+                    int parentId = Integer.parseInt(mxCell.getAttribute("parent"));
+
+                    OutPort outPort = new OutPort();
+                    outPort.id = id;
+                    outPort.name = name;
+                    result.portCache.put(id, outPort);
+
+                    if (result.tasks.containsKey(parentId)) {
+                        outPort.parentTask = result.tasks.get(parentId);
+                        result.tasks.get(parentId).outPorts.put(id, outPort);
+                        result.tasks.get(parentId).getTaskResource().getOutPorts()
+                                .add(new TaskOutPortResource()
+                                        .setName(outPort.name)
+                                        .setReferenceId(outPort.id));
+
+                    } else if (result.operations.containsKey(parentId)) {
+                        outPort.parentOperation = result.operations.get(parentId);
+                        result.operations.get(parentId).outPorts.put(id, outPort);
+                    } else {
+                        throw new Exception("Failed to find parent id " + parentId + " for out port " + id);
+                    }
+
+                } else if ("mxCell".equals(eElement.getTagName())) {
+                    if (eElement.hasAttribute("edge") && eElement.hasAttribute("source") && eElement.hasAttribute("target")) {
+                        int sourceId = Integer.parseInt(eElement.getAttribute("source"));
+                        int targetId = Integer.parseInt(eElement.getAttribute("target"));
+
+                        if (result.portCache.containsKey(targetId) && result.portCache.containsKey(sourceId)) {
+                            InPort inPort = (InPort) result.portCache.get(targetId);
+                            OutPort outPort = (OutPort) result.portCache.get(sourceId);
+                            outPort.nextPort = inPort;
+                            inPort.previousPorts.put(outPort.id, outPort);
+                            result.edgeCache.put(outPort, inPort);
+                        } else {
+                            throw new Exception("Invalid connection with source id " + sourceId + " target id " + targetId);
+                        }
+                    }
+                }
+            }
+        }
+
+        return result;
+    }
+
+    public static class ParseResult {
+
+
+        private Map<Integer, Task> tasks = new HashMap<>();
+        private Map<Integer, Operation> operations = new HashMap<>();
+        private Map<Integer, Object> portCache = new HashMap<>();
+        private Map<OutPort, InPort> edgeCache = new HashMap<>();
+
+        public Map<Integer, Task> getTasks() {
+            return tasks;
+        }
+
+        public ParseResult setTasks(Map<Integer, Task> tasks) {
+            this.tasks = tasks;
+            return this;
+        }
+
+        public Map<Integer, Operation> getOperations() {
+            return operations;
+        }
+
+        public ParseResult setOperations(Map<Integer, Operation> operations) {
+            this.operations = operations;
+            return this;
+        }
+
+        public Map<Integer, Object> getPortCache() {
+            return portCache;
+        }
+
+        public ParseResult setPortCache(Map<Integer, Object> portCache) {
+            this.portCache = portCache;
+            return this;
+        }
+
+        public Map<OutPort, InPort> getEdgeCache() {
+            return edgeCache;
+        }
+
+        public ParseResult setEdgeCache(Map<OutPort, InPort> edgeCache) {
+            this.edgeCache = edgeCache;
+            return this;
+        }
+    }
+
+
+    public static class Task {
+        private int id;
+        private TaskResource taskResource;
+        private Map<Integer, InPort> inPorts = new HashMap<>();
+        private Map<Integer, OutPort> outPorts = new HashMap<>();
+
+        public int getId() {
+            return id;
+        }
+
+        public Task setId(int id) {
+            this.id = id;
+            return this;
+        }
+
+        public TaskResource getTaskResource() {
+            return taskResource;
+        }
+
+        public Task setTaskResource(TaskResource taskResource) {
+            this.taskResource = taskResource;
+            return this;
+        }
+
+        public Map<Integer, InPort> getInPorts() {
+            return inPorts;
+        }
+
+        public Task setInPorts(Map<Integer, InPort> inPorts) {
+            this.inPorts = inPorts;
+            return this;
+        }
+
+        public Map<Integer, OutPort> getOutPorts() {
+            return outPorts;
+        }
+
+        public Task setOutPorts(Map<Integer, OutPort> outPorts) {
+            this.outPorts = outPorts;
+            return this;
+        }
+    }
+
+    public static class OutPort {
+        private InPort nextPort;
+        private Task parentTask;
+        private Operation parentOperation;
+        private String name;
+        private int id;
+
+        public InPort getNextPort() {
+            return nextPort;
+        }
+
+        public OutPort setNextPort(InPort nextPort) {
+            this.nextPort = nextPort;
+            return this;
+        }
+
+        public Task getParentTask() {
+            return parentTask;
+        }
+
+        public OutPort setParentTask(Task parentTask) {
+            this.parentTask = parentTask;
+            return this;
+        }
+
+        public Operation getParentOperation() {
+            return parentOperation;
+        }
+
+        public OutPort setParentOperation(Operation parentOperation) {
+            this.parentOperation = parentOperation;
+            return this;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public OutPort setName(String name) {
+            this.name = name;
+            return this;
+        }
+
+        public int getId() {
+            return id;
+        }
+
+        public OutPort setId(int id) {
+            this.id = id;
+            return this;
+        }
+    }
+
+    public static class InPort {
+        private Map<Integer, OutPort> previousPorts = new HashMap<>();
+        private Task parentTask;
+        private Operation parentOperation;
+        private String name;
+        private int id;
+
+        public Map<Integer, OutPort> getPreviousPorts() {
+            return previousPorts;
+        }
+
+        public InPort setPreviousPorts(Map<Integer, OutPort> previousPorts) {
+            this.previousPorts = previousPorts;
+            return this;
+        }
+
+        public Task getParentTask() {
+            return parentTask;
+        }
+
+        public InPort setParentTask(Task parentTask) {
+            this.parentTask = parentTask;
+            return this;
+        }
+
+        public Operation getParentOperation() {
+            return parentOperation;
+        }
+
+        public InPort setParentOperation(Operation parentOperation) {
+            this.parentOperation = parentOperation;
+            return this;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public InPort setName(String name) {
+            this.name = name;
+            return this;
+        }
+
+        public int getId() {
+            return id;
+        }
+
+        public InPort setId(int id) {
+            this.id = id;
+            return this;
+        }
+    }
+
+    public static class Operation {
+        private int id;
+        private String name;
+        private Map<Integer, InPort> inPorts = new HashMap<>();
+        private Map<Integer, OutPort> outPorts = new HashMap<>();
+
+        public int getId() {
+            return id;
+        }
+
+        public Operation setId(int id) {
+            this.id = id;
+            return this;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public Operation setName(String name) {
+            this.name = name;
+            return this;
+        }
+
+        public Map<Integer, InPort> getInPorts() {
+            return inPorts;
+        }
+
+        public Operation setInPorts(Map<Integer, InPort> inPorts) {
+            this.inPorts = inPorts;
+            return this;
+        }
+
+        public Map<Integer, OutPort> getOutPorts() {
+            return outPorts;
+        }
+
+        public Operation setOutPorts(Map<Integer, OutPort> outPorts) {
+            this.outPorts = outPorts;
+            return this;
+        }
+    }
+
+}
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/util/ToResourceUtil.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/util/ToResourceUtil.java
index 0318978..7631b01 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/util/ToResourceUtil.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/util/ToResourceUtil.java
@@ -21,9 +21,12 @@ package org.apache.airavata.k8s.api.server.service.util;
 
 import org.apache.airavata.k8s.api.resources.experiment.ExperimentStatusResource;
 import org.apache.airavata.k8s.api.resources.process.ProcessStatusResource;
-import org.apache.airavata.k8s.api.resources.task.TaskParamResource;
-import org.apache.airavata.k8s.api.resources.task.TaskResource;
-import org.apache.airavata.k8s.api.resources.task.TaskStatusResource;
+import org.apache.airavata.k8s.api.resources.task.*;
+import org.apache.airavata.k8s.api.resources.task.type.TaskInputTypeResource;
+import org.apache.airavata.k8s.api.resources.task.type.TaskOutPortTypeResource;
+import org.apache.airavata.k8s.api.resources.task.type.TaskOutputTypeResource;
+import org.apache.airavata.k8s.api.resources.task.type.TaskTypeResource;
+import org.apache.airavata.k8s.api.resources.workflow.WorkflowResource;
 import org.apache.airavata.k8s.api.server.model.application.ApplicationDeployment;
 import org.apache.airavata.k8s.api.server.model.application.ApplicationInterface;
 import org.apache.airavata.k8s.api.server.model.application.ApplicationModule;
@@ -34,6 +37,7 @@ import org.apache.airavata.k8s.api.server.model.experiment.ExperimentOutputData;
 import org.apache.airavata.k8s.api.server.model.experiment.ExperimentStatus;
 import org.apache.airavata.k8s.api.server.model.process.ProcessModel;
 import org.apache.airavata.k8s.api.server.model.process.ProcessStatus;
+import org.apache.airavata.k8s.api.server.model.task.TaskInput;
 import org.apache.airavata.k8s.api.server.model.task.TaskModel;
 import org.apache.airavata.k8s.api.resources.application.*;
 import org.apache.airavata.k8s.api.resources.compute.ComputeResource;
@@ -41,8 +45,13 @@ import org.apache.airavata.k8s.api.resources.experiment.ExperimentInputResource;
 import org.apache.airavata.k8s.api.resources.experiment.ExperimentOutputResource;
 import org.apache.airavata.k8s.api.resources.experiment.ExperimentResource;
 import org.apache.airavata.k8s.api.resources.process.ProcessResource;
-import org.apache.airavata.k8s.api.server.model.task.TaskParam;
+import org.apache.airavata.k8s.api.server.model.task.TaskOutput;
 import org.apache.airavata.k8s.api.server.model.task.TaskStatus;
+import org.apache.airavata.k8s.api.server.model.task.type.TaskInputType;
+import org.apache.airavata.k8s.api.server.model.task.type.TaskModelType;
+import org.apache.airavata.k8s.api.server.model.task.type.TaskOutPortType;
+import org.apache.airavata.k8s.api.server.model.task.type.TaskOutputType;
+import org.apache.airavata.k8s.api.server.model.workflow.Workflow;
 
 import java.util.Optional;
 
@@ -217,17 +226,25 @@ public class ToResourceUtil {
     public static Optional<TaskResource> toResource(TaskModel taskModel) {
         if (taskModel != null) {
             TaskResource resource = new TaskResource();
+            resource.setName(taskModel.getName());
             resource.setId(taskModel.getId());
             resource.setLastUpdateTime(taskModel.getLastUpdateTime());
             resource.setCreationTime(taskModel.getCreationTime());
             resource.setParentProcessId(taskModel.getParentProcess().getId());
-            resource.setTaskType(taskModel.getTaskType().getValue());
-            resource.setTaskTypeStr(taskModel.getTaskType().name());
+            resource.setTaskTypeId(taskModel.getTaskType().getId());
             resource.setTaskDetail(taskModel.getTaskDetail());
-            Optional.ofNullable(taskModel.getTaskParams())
-                    .ifPresent(params ->
-                            params.forEach(param -> resource.getTaskParams()
-                                    .add(toResource(param).get())));
+            resource.setStartingTask(taskModel.isStartingTask());
+            resource.setStoppingTask(taskModel.isStoppingTask());
+            Optional.ofNullable(taskModel.getTaskInputs())
+                    .ifPresent(inputs ->
+                            inputs.forEach(input -> resource.getInputs()
+                                    .add(toResource(input).get())));
+
+            Optional.ofNullable(taskModel.getTaskOutputs())
+                    .ifPresent(outputs ->
+                            outputs.forEach(output -> resource.getOutputs()
+                                    .add(toResource(output).get())));
+
 
             Optional.ofNullable(taskModel.getTaskStatuses())
                     .ifPresent(taskStatuses ->
@@ -256,12 +273,28 @@ public class ToResourceUtil {
         }
     }
 
-    public static Optional<TaskParamResource> toResource(TaskParam taskParam) {
-        if (taskParam != null) {
-            TaskParamResource resource = new TaskParamResource();
-            resource.setId(taskParam.getId());
-            resource.setKey(taskParam.getKey());
-            resource.setValue(taskParam.getValue());
+    public static Optional<TaskInputResource> toResource(TaskInput taskInput) {
+        if (taskInput != null) {
+            TaskInputResource resource = new TaskInputResource();
+            resource.setId(taskInput.getId());
+            resource.setName(taskInput.getName());
+            resource.setValue(taskInput.getValue());
+            resource.setType(taskInput.getType());
+            resource.setImportFrom(taskInput.getImportFrom());
+            return Optional.of(resource);
+        } else {
+            return Optional.empty();
+        }
+    }
+
+    public static Optional<TaskOutputResource> toResource(TaskOutput taskOutput) {
+        if (taskOutput != null) {
+            TaskOutputResource resource = new TaskOutputResource();
+            resource.setId(taskOutput.getId());
+            resource.setName(taskOutput.getName());
+            resource.setValue(taskOutput.getValue());
+            resource.setType(taskOutput.getType());
+            resource.setExportTo(taskOutput.getExportTo());
             return Optional.of(resource);
         } else {
             return Optional.empty();
@@ -302,4 +335,87 @@ public class ToResourceUtil {
             return Optional.empty();
         }
     }
+
+    public static Optional<TaskTypeResource> toResource(TaskModelType taskModelType) {
+
+        if (taskModelType != null) {
+            TaskTypeResource resource = new TaskTypeResource();
+            resource.setName(taskModelType.getName());
+            resource.setId(taskModelType.getId());
+            resource.setTopicName(taskModelType.getTopicName());
+            resource.setIcon(taskModelType.getIcon());
+
+            Optional.ofNullable(taskModelType.getInputTypes())
+                    .ifPresent(taskInputTypes -> taskInputTypes.forEach(taskInputType -> {
+                        resource.getInputTypes().add(toResource(taskInputType).get());
+                    }));
+
+            Optional.ofNullable(taskModelType.getOutputTypes())
+                    .ifPresent(taskOutputTypes -> taskOutputTypes.forEach(taskOutputType -> {
+                        resource.getOutputTypes().add(toResource(taskOutputType).get());
+                    }));
+
+            Optional.ofNullable(taskModelType.getOutPorts())
+                    .ifPresent(taskOutPorts -> taskOutPorts.forEach(taskOutPort -> {
+                        resource.getOutPorts().add(toResource(taskOutPort).get());
+                    }));
+            return Optional.of(resource);
+        } else {
+            return Optional.empty();
+        }
+    }
+
+    public static Optional<TaskInputTypeResource> toResource(TaskInputType taskInputType) {
+        if (taskInputType != null) {
+            TaskInputTypeResource resource = new TaskInputTypeResource();
+            resource.setId(taskInputType.getId());
+            resource.setName(taskInputType.getName());
+            resource.setType(taskInputType.getType());
+            resource.setDefaultValue(taskInputType.getDefaultValue());
+            return Optional.of(resource);
+        } else {
+            return Optional.empty();
+        }
+    }
+
+    public static Optional<TaskOutputTypeResource> toResource(TaskOutputType taskOutputType) {
+        if (taskOutputType != null) {
+            TaskOutputTypeResource resource = new TaskOutputTypeResource();
+            resource.setId(taskOutputType.getId());
+            resource.setName(taskOutputType.getName());
+            resource.setType(taskOutputType.getType());
+            return Optional.of(resource);
+        } else {
+            return Optional.empty();
+        }
+    }
+
+    public static Optional<TaskOutPortTypeResource> toResource(TaskOutPortType taskOutPort) {
+        if (taskOutPort != null) {
+            TaskOutPortTypeResource resource = new TaskOutPortTypeResource();
+            resource.setId(taskOutPort.getId());
+            resource.setName(taskOutPort.getName());
+            resource.setOrder(taskOutPort.getOrder());
+            return Optional.of(resource);
+        } else {
+            return Optional.empty();
+        }
+    }
+
+    public static Optional<WorkflowResource> toResource(Workflow workflow) {
+        if (workflow != null) {
+            WorkflowResource resource = new WorkflowResource();
+            resource.setId(workflow.getId());
+            resource.setName(workflow.getName());
+            resource.setWorkflowGraphXML(new String(workflow.getWorkFlowGraph()));
+            Optional.ofNullable(workflow.getProcesses()).ifPresent(processModels -> {
+                processModels.forEach(processModel -> {
+                    resource.getProcessIds().add(processModel.getId());
+                });
+            });
+            return Optional.of(resource);
+        } else {
+            return Optional.empty();
+        }
+    }
 }
diff --git a/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/core/ProcessLifeCycleManager.java b/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/core/ProcessLifeCycleManager.java
index 01e53a2..751b2ec 100644
--- a/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/core/ProcessLifeCycleManager.java
+++ b/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/core/ProcessLifeCycleManager.java
@@ -84,33 +84,6 @@ public class ProcessLifeCycleManager {
 
     public void submitTaskToQueue(TaskResource taskResource) {
 
-        switch (taskResource.getTaskType()) {
-            case TaskResource.TaskTypes.EGRESS_DATA_STAGING :
-                System.out.println("Submitting task " + taskResource.getId() + " to egress data staging queue");
-                updateProcessStatus(ProcessStatusResource.State.OUTPUT_DATA_STAGING);
-                this.kafkaSender.send("airavata-task-egress-staging", taskResource.getId() + "");
-                break;
-            case TaskResource.TaskTypes.INGRESS_DATA_STAGING :
-                System.out.println("Submitting task " + taskResource.getId() + " to ingress data staging queue");
-                updateProcessStatus(ProcessStatusResource.State.INPUT_DATA_STAGING);
-                this.kafkaSender.send("airavata-task-ingress-staging", taskResource.getId() + "");
-                break;
-            case TaskResource.TaskTypes.ENV_SETUP :
-                System.out.println("Submitting task " + taskResource.getId() + " to env setup queue");
-                updateProcessStatus(ProcessStatusResource.State.PRE_PROCESSING);
-                this.kafkaSender.send("airavata-task-env-setup", taskResource.getId() + "");
-                break;
-            case TaskResource.TaskTypes.ENV_CLEANUP :
-                System.out.println("Submitting task " + taskResource.getId() + " to env cleanup queue");
-                updateProcessStatus(ProcessStatusResource.State.POST_PROCESSING);
-                this.kafkaSender.send("airavata-task-env-cleanup", taskResource.getId() + "");
-                break;
-            case TaskResource.TaskTypes.JOB_SUBMISSION :
-                System.out.println("Submitting task " + taskResource.getId() + " to job submission queue");
-                updateProcessStatus(ProcessStatusResource.State.EXECUTING);
-                this.kafkaSender.send("airavata-task-job-submission", taskResource.getId() + "");
-                break;
-        }
     }
 
     private void updateProcessStatus(int state) {
diff --git a/airavata-kubernetes/modules/microservices/tasks/job-submission-task/pom.xml b/airavata-kubernetes/modules/microservices/tasks/command-executing-task/pom.xml
similarity index 96%
rename from airavata-kubernetes/modules/microservices/tasks/job-submission-task/pom.xml
rename to airavata-kubernetes/modules/microservices/tasks/command-executing-task/pom.xml
index c046b87..eff73f5 100644
--- a/airavata-kubernetes/modules/microservices/tasks/job-submission-task/pom.xml
+++ b/airavata-kubernetes/modules/microservices/tasks/command-executing-task/pom.xml
@@ -31,7 +31,7 @@
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
-    <artifactId>job-submission-task</artifactId>
+    <artifactId>command-executing-task</artifactId>
 
     <dependencies>
         <dependency>
@@ -44,6 +44,11 @@
             <artifactId>api-resource</artifactId>
             <version>1.0-SNAPSHOT</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.airavata</groupId>
+            <artifactId>task-api</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
 
         <dependency>
             <groupId>org.springframework.boot</groupId>
diff --git a/airavata-kubernetes/modules/microservices/tasks/job-submission-task/src/main/java/org/apache/airavata/k8s/task/job/Application.java b/airavata-kubernetes/modules/microservices/tasks/command-executing-task/src/main/java/org/apache/airavata/k8s/task/job/Application.java
similarity index 81%
rename from airavata-kubernetes/modules/microservices/tasks/job-submission-task/src/main/java/org/apache/airavata/k8s/task/job/Application.java
rename to airavata-kubernetes/modules/microservices/tasks/command-executing-task/src/main/java/org/apache/airavata/k8s/task/job/Application.java
index 9955502..d11a7c5 100644
--- a/airavata-kubernetes/modules/microservices/tasks/job-submission-task/src/main/java/org/apache/airavata/k8s/task/job/Application.java
+++ b/airavata-kubernetes/modules/microservices/tasks/command-executing-task/src/main/java/org/apache/airavata/k8s/task/job/Application.java
@@ -19,12 +19,16 @@
  */
 package org.apache.airavata.k8s.task.job;
 
+import org.apache.airavata.k8s.task.api.messaging.ReceiverConfig;
+import org.apache.airavata.k8s.task.api.messaging.SenderConfig;
 import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.boot.web.client.RestTemplateBuilder;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.ComponentScan;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
 import org.springframework.web.client.RestTemplate;
 
 /**
@@ -35,7 +39,9 @@ import org.springframework.web.client.RestTemplate;
  */
 @SpringBootApplication
 @Configuration
-@ComponentScan
+@ComponentScan(basePackages = "org.apache.airavata.*")
+@EnableAutoConfiguration
+@Import({ReceiverConfig.class, SenderConfig.class})
 public class Application {
 
     public static void main(String[] args) {
diff --git a/airavata-kubernetes/modules/microservices/tasks/command-executing-task/src/main/java/org/apache/airavata/k8s/task/job/CommandTaskInfo.java b/airavata-kubernetes/modules/microservices/tasks/command-executing-task/src/main/java/org/apache/airavata/k8s/task/job/CommandTaskInfo.java
new file mode 100644
index 0000000..cbffce3
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/tasks/command-executing-task/src/main/java/org/apache/airavata/k8s/task/job/CommandTaskInfo.java
@@ -0,0 +1,59 @@
+package org.apache.airavata.k8s.task.job;
+
+import org.apache.airavata.k8s.api.resources.task.type.TaskInputTypeResource;
+import org.apache.airavata.k8s.api.resources.task.type.TaskOutPortTypeResource;
+import org.apache.airavata.k8s.api.resources.task.type.TaskTypeResource;
+
+import java.util.Arrays;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public class CommandTaskInfo {
+
+    public static final String COMMAND = "command";
+    public static final String ARGUMENTS = "arguments";
+    public static final String STD_OUT_PATH = "std_out_path";
+    public static final String STD_ERR_PATH = "std_err_path";
+    public static final String COMPUTE_RESOURCE = "compute_resource";
+
+    public static TaskTypeResource getTaskType() {
+        TaskTypeResource taskTypeResource = new TaskTypeResource();
+        taskTypeResource.setName("Command Execute");
+        taskTypeResource.setTopicName("airavata-command");
+        taskTypeResource.setIcon("assets/icons/ssh.png");
+        taskTypeResource.getInputTypes().addAll(
+                Arrays.asList(
+                        new TaskInputTypeResource()
+                                .setName(COMMAND)
+                                .setType("String")
+                                .setDefaultValue(""),
+                        new TaskInputTypeResource()
+                                .setName(ARGUMENTS)
+                                .setType("String"),
+                        new TaskInputTypeResource()
+                                .setName(COMPUTE_RESOURCE)
+                                .setType("Long"),
+                        new TaskInputTypeResource()
+                                .setName(STD_OUT_PATH)
+                                .setType("String"),
+                        new TaskInputTypeResource()
+                                .setName(STD_ERR_PATH)
+                                .setType("String")));
+
+        taskTypeResource.getOutPorts().addAll(
+                Arrays.asList(
+                        new TaskOutPortTypeResource()
+                                .setName("Out")
+                                .setOrder(0),
+                        new TaskOutPortTypeResource()
+                                .setName("Error")
+                                .setOrder(1))
+        );
+
+        return taskTypeResource;
+    }
+}
diff --git a/airavata-kubernetes/modules/microservices/tasks/command-executing-task/src/main/java/org/apache/airavata/k8s/task/job/config/RestConfig.java b/airavata-kubernetes/modules/microservices/tasks/command-executing-task/src/main/java/org/apache/airavata/k8s/task/job/config/RestConfig.java
new file mode 100644
index 0000000..acbf74f
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/tasks/command-executing-task/src/main/java/org/apache/airavata/k8s/task/job/config/RestConfig.java
@@ -0,0 +1,10 @@
+package org.apache.airavata.k8s.task.job.config;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public class RestConfig {
+}
diff --git a/airavata-kubernetes/modules/microservices/tasks/command-executing-task/src/main/java/org/apache/airavata/k8s/task/job/service/TaskExecutionService.java b/airavata-kubernetes/modules/microservices/tasks/command-executing-task/src/main/java/org/apache/airavata/k8s/task/job/service/TaskExecutionService.java
new file mode 100644
index 0000000..23c147e
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/tasks/command-executing-task/src/main/java/org/apache/airavata/k8s/task/job/service/TaskExecutionService.java
@@ -0,0 +1,100 @@
+/**
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.airavata.k8s.task.job.service;
+
+import org.apache.airavata.k8s.api.resources.compute.ComputeResource;
+import org.apache.airavata.k8s.api.resources.task.TaskResource;
+import org.apache.airavata.k8s.api.resources.task.TaskStatusResource;
+import org.apache.airavata.k8s.api.resources.task.type.TaskTypeResource;
+import org.apache.airavata.k8s.compute.api.ExecutionResult;
+import org.apache.airavata.k8s.task.api.AbstractTaskExecutionService;
+import org.apache.airavata.k8s.task.api.TaskContext;
+import org.apache.airavata.k8s.task.api.messaging.KafkaSender;
+import org.apache.airavata.k8s.task.job.CommandTaskInfo;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+@Service
+public class TaskExecutionService extends AbstractTaskExecutionService {
+
+    public TaskExecutionService(RestTemplate restTemplate, KafkaSender kafkaSender) {
+        super(restTemplate, null, 10);
+    }
+
+    @Override
+    public TaskTypeResource getType() {
+        return CommandTaskInfo.getTaskType();
+    }
+
+    @Override
+    public void initializeParameters(TaskResource taskResource, TaskContext taskContext) throws Exception {
+
+        taskContext.getLocalContext().put(CommandTaskInfo.COMMAND, findInput(taskResource, CommandTaskInfo.COMMAND, false));
+        taskContext.getLocalContext().put(CommandTaskInfo.ARGUMENTS, findInput(taskResource, CommandTaskInfo.ARGUMENTS, true));
+        taskContext.getLocalContext().put(CommandTaskInfo.STD_OUT_PATH, findInput(taskResource, CommandTaskInfo.STD_OUT_PATH, false));
+        taskContext.getLocalContext().put(CommandTaskInfo.STD_ERR_PATH, findInput(taskResource, CommandTaskInfo.STD_ERR_PATH, false));
+
+        String computeId = findInput(taskResource, CommandTaskInfo.COMPUTE_RESOURCE, false);
+        taskContext.getLocalContext().put(CommandTaskInfo.COMPUTE_RESOURCE, this.getRestTemplate().getForObject("http://" + this.getApiServerUrl()
+                + "/compute/" + Long.parseLong(computeId), ComputeResource.class));
+
+    }
+
+    @Override
+    public void executeTask(TaskResource taskResource, TaskContext taskContext) {
+
+        try {
+            String command = (String) taskContext.getLocalContext().get(CommandTaskInfo.COMMAND);
+            String arguments = (String) taskContext.getLocalContext().get(CommandTaskInfo.ARGUMENTS);
+            ComputeResource computeResource = (ComputeResource) taskContext.getLocalContext().get(CommandTaskInfo.COMPUTE_RESOURCE);
+            String stdOutPath = (String) taskContext.getLocalContext().get(CommandTaskInfo.STD_OUT_PATH);
+            String stdErrPath = (String) taskContext.getLocalContext().get(CommandTaskInfo.STD_ERR_PATH);
+            String stdOutSuffix = " > " + stdOutPath + " 2> " + stdErrPath;
+
+            publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.EXECUTING);
+
+            String finalCommand = command + (arguments != null ? arguments : "") + stdOutSuffix;
+
+            System.out.println("Executing command " + finalCommand);
+
+            ExecutionResult executionResult = fetchComputeResourceOperation(computeResource).executeCommand(finalCommand);
+
+            if (executionResult.getExitStatus() == 0) {
+                publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.COMPLETED);
+            } else if (executionResult.getExitStatus() == -1) {
+                publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.FAILED, "Process didn't exit successfully");
+            } else {
+                publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.FAILED, "Process exited with error status " + executionResult.getExitStatus());
+            }
+
+        } catch (Exception e) {
+
+            e.printStackTrace();
+            publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.FAILED, e.getMessage());
+        }
+    }
+}
diff --git a/airavata-kubernetes/modules/microservices/tasks/job-submission-task/src/main/resources/application.properties b/airavata-kubernetes/modules/microservices/tasks/command-executing-task/src/main/resources/application.properties
similarity index 100%
rename from airavata-kubernetes/modules/microservices/tasks/job-submission-task/src/main/resources/application.properties
rename to airavata-kubernetes/modules/microservices/tasks/command-executing-task/src/main/resources/application.properties
diff --git a/airavata-kubernetes/modules/microservices/tasks/job-submission-task/src/main/resources/application.yml b/airavata-kubernetes/modules/microservices/tasks/command-executing-task/src/main/resources/application.yml
similarity index 100%
rename from airavata-kubernetes/modules/microservices/tasks/job-submission-task/src/main/resources/application.yml
rename to airavata-kubernetes/modules/microservices/tasks/command-executing-task/src/main/resources/application.yml
diff --git a/airavata-kubernetes/modules/microservices/tasks/egress-staging-task/pom.xml b/airavata-kubernetes/modules/microservices/tasks/data-collecting-task/pom.xml
similarity index 96%
rename from airavata-kubernetes/modules/microservices/tasks/egress-staging-task/pom.xml
rename to airavata-kubernetes/modules/microservices/tasks/data-collecting-task/pom.xml
index 05852fe..fe50fe8 100644
--- a/airavata-kubernetes/modules/microservices/tasks/egress-staging-task/pom.xml
+++ b/airavata-kubernetes/modules/microservices/tasks/data-collecting-task/pom.xml
@@ -31,7 +31,7 @@
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
-    <artifactId>egress-staging-task</artifactId>
+    <artifactId>data-collecting-task</artifactId>
 
     <dependencies>
         <dependency>
@@ -44,6 +44,11 @@
             <artifactId>api-resource</artifactId>
             <version>1.0-SNAPSHOT</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.airavata</groupId>
+            <artifactId>task-api</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
 
         <dependency>
             <groupId>org.springframework.boot</groupId>
diff --git a/airavata-kubernetes/modules/microservices/tasks/egress-staging-task/src/main/java/org/apacher/airavata/k8s/task/egress/Application.java b/airavata-kubernetes/modules/microservices/tasks/data-collecting-task/src/main/java/org/apacher/airavata/k8s/task/egress/Application.java
similarity index 83%
rename from airavata-kubernetes/modules/microservices/tasks/egress-staging-task/src/main/java/org/apacher/airavata/k8s/task/egress/Application.java
rename to airavata-kubernetes/modules/microservices/tasks/data-collecting-task/src/main/java/org/apacher/airavata/k8s/task/egress/Application.java
index e2dbae4..1642d18 100644
--- a/airavata-kubernetes/modules/microservices/tasks/egress-staging-task/src/main/java/org/apacher/airavata/k8s/task/egress/Application.java
+++ b/airavata-kubernetes/modules/microservices/tasks/data-collecting-task/src/main/java/org/apacher/airavata/k8s/task/egress/Application.java
@@ -19,12 +19,16 @@
  */
 package org.apacher.airavata.k8s.task.egress;
 
+import org.apache.airavata.k8s.task.api.messaging.ReceiverConfig;
+import org.apache.airavata.k8s.task.api.messaging.SenderConfig;
 import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.boot.web.client.RestTemplateBuilder;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.ComponentScan;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
 import org.springframework.web.client.RestTemplate;
 
 /**
@@ -36,6 +40,8 @@ import org.springframework.web.client.RestTemplate;
 @SpringBootApplication
 @Configuration
 @ComponentScan
+@EnableAutoConfiguration
+@Import({ReceiverConfig.class, SenderConfig.class})
 public class Application {
 
     public static void main(String[] args) {
diff --git a/airavata-kubernetes/modules/microservices/tasks/data-collecting-task/src/main/java/org/apacher/airavata/k8s/task/egress/DataCollectingTaskInfo.java b/airavata-kubernetes/modules/microservices/tasks/data-collecting-task/src/main/java/org/apacher/airavata/k8s/task/egress/DataCollectingTaskInfo.java
new file mode 100644
index 0000000..73a354f
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/tasks/data-collecting-task/src/main/java/org/apacher/airavata/k8s/task/egress/DataCollectingTaskInfo.java
@@ -0,0 +1,50 @@
+package org.apacher.airavata.k8s.task.egress;
+
+import org.apache.airavata.k8s.api.resources.task.type.TaskInputTypeResource;
+import org.apache.airavata.k8s.api.resources.task.type.TaskOutPortTypeResource;
+import org.apache.airavata.k8s.api.resources.task.type.TaskTypeResource;
+
+import java.util.Arrays;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public class DataCollectingTaskInfo {
+    public static final String REMOTE_SOURCE_PATH = "remote_source_path";
+    public static final String COMPUTE_RESOURCE = "compute_resource";
+    public static final String IDENTIFIER = "identifier";
+
+    public static TaskTypeResource getTaskType() {
+        TaskTypeResource taskTypeResource = new TaskTypeResource();
+        taskTypeResource.setName("Data Collect");
+        taskTypeResource.setTopicName("airavata-data-collect");
+        taskTypeResource.setIcon("assets/icons/copy.png");
+        taskTypeResource.getInputTypes().addAll(
+                Arrays.asList(
+                        new TaskInputTypeResource()
+                                .setName(REMOTE_SOURCE_PATH)
+                                .setType("String")
+                                .setDefaultValue(""),
+                        new TaskInputTypeResource()
+                                .setName(IDENTIFIER)
+                                .setType("String"),
+                        new TaskInputTypeResource()
+                                .setName(COMPUTE_RESOURCE)
+                                .setType("Long")));
+
+        taskTypeResource.getOutPorts().addAll(
+                Arrays.asList(
+                        new TaskOutPortTypeResource()
+                                .setName("Out")
+                                .setOrder(0),
+                        new TaskOutPortTypeResource()
+                                .setName("Error")
+                                .setOrder(1))
+        );
+
+        return taskTypeResource;
+    }
+}
diff --git a/airavata-kubernetes/modules/microservices/tasks/data-collecting-task/src/main/java/org/apacher/airavata/k8s/task/egress/service/TaskExecutionService.java b/airavata-kubernetes/modules/microservices/tasks/data-collecting-task/src/main/java/org/apacher/airavata/k8s/task/egress/service/TaskExecutionService.java
new file mode 100644
index 0000000..8b83708
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/tasks/data-collecting-task/src/main/java/org/apacher/airavata/k8s/task/egress/service/TaskExecutionService.java
@@ -0,0 +1,107 @@
+/**
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apacher.airavata.k8s.task.egress.service;
+
+import org.apache.airavata.k8s.api.resources.compute.ComputeResource;
+import org.apache.airavata.k8s.api.resources.task.TaskResource;
+import org.apache.airavata.k8s.api.resources.task.TaskStatusResource;
+import org.apache.airavata.k8s.api.resources.task.type.TaskTypeResource;
+import org.apache.airavata.k8s.task.api.AbstractTaskExecutionService;
+import org.apache.airavata.k8s.task.api.TaskContext;
+import org.apache.airavata.k8s.task.api.messaging.KafkaSender;
+import org.apacher.airavata.k8s.task.egress.DataCollectingTaskInfo;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.http.*;
+import org.springframework.stereotype.Service;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.UUID;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+@Service
+public class TaskExecutionService extends AbstractTaskExecutionService {
+
+    public TaskExecutionService(RestTemplate restTemplate, KafkaSender kafkaSender) {
+        super(restTemplate, kafkaSender, 10);
+    }
+
+    @Override
+    public TaskTypeResource getType() {
+        return DataCollectingTaskInfo.getTaskType();
+    }
+
+    @Override
+    public void initializeParameters(TaskResource taskResource, TaskContext taskContext) throws Exception {
+
+        taskContext.getLocalContext().put(DataCollectingTaskInfo.REMOTE_SOURCE_PATH, findInput(taskResource, DataCollectingTaskInfo.REMOTE_SOURCE_PATH, false));
+        taskContext.getLocalContext().put(DataCollectingTaskInfo.IDENTIFIER, findInput(taskResource, DataCollectingTaskInfo.IDENTIFIER, false));
+
+        String computeId = findInput(taskResource, DataCollectingTaskInfo.COMPUTE_RESOURCE, false);
+        taskContext.getLocalContext().put(DataCollectingTaskInfo.COMPUTE_RESOURCE, this.getRestTemplate().getForObject("http://" + this.getApiServerUrl()
+                + "/compute/" + Long.parseLong(computeId), ComputeResource.class));
+
+    }
+
+    public void executeTask(TaskResource taskResource, TaskContext taskContext) {
+
+        try {
+
+            ComputeResource computeResource = (ComputeResource) taskContext.getLocalContext().get(DataCollectingTaskInfo.COMPUTE_RESOURCE);
+            String identifier = (String) taskContext.getLocalContext().get(DataCollectingTaskInfo.IDENTIFIER);
+            String remoteSourcePath = (String) taskContext.getLocalContext().get(DataCollectingTaskInfo.REMOTE_SOURCE_PATH);
+
+            publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.EXECUTING);
+
+            String temporaryFile = "/tmp/" + UUID.randomUUID().toString();
+            System.out.println("Downloading " + remoteSourcePath + " to " + temporaryFile + " from compute resource "
+                    + computeResource.getName());
+
+            fetchComputeResourceOperation(computeResource).transferDataOut(remoteSourcePath, temporaryFile, "SCP");
+
+            LinkedMultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
+            map.add("file", new FileSystemResource(temporaryFile));
+            HttpHeaders headers = new HttpHeaders();
+            headers.setContentType(MediaType.MULTIPART_FORM_DATA);
+
+            HttpEntity<LinkedMultiValueMap<String, Object>> requestEntity = new HttpEntity<>(map, headers);
+
+            System.out.println("Uploading data file with task id " + taskResource.getId() + " and identifier "
+                    + identifier + " to data store");
+
+            getRestTemplate().exchange("http://" + getApiServerUrl() + "/data/" + taskResource.getId()+ "/"
+                            + identifier + "/upload", HttpMethod.POST, requestEntity, Long.class);
+
+            publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(),
+                    TaskStatusResource.State.COMPLETED);
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.FAILED, e.getMessage());
+
+        }
+    }
+
+}
diff --git a/airavata-kubernetes/modules/microservices/tasks/egress-staging-task/src/main/resources/application.properties b/airavata-kubernetes/modules/microservices/tasks/data-collecting-task/src/main/resources/application.properties
similarity index 100%
rename from airavata-kubernetes/modules/microservices/tasks/egress-staging-task/src/main/resources/application.properties
rename to airavata-kubernetes/modules/microservices/tasks/data-collecting-task/src/main/resources/application.properties
diff --git a/airavata-kubernetes/modules/microservices/tasks/ingress-staging-task/src/main/resources/application.yml b/airavata-kubernetes/modules/microservices/tasks/data-collecting-task/src/main/resources/application.yml
similarity index 100%
rename from airavata-kubernetes/modules/microservices/tasks/ingress-staging-task/src/main/resources/application.yml
rename to airavata-kubernetes/modules/microservices/tasks/data-collecting-task/src/main/resources/application.yml
diff --git a/airavata-kubernetes/modules/microservices/tasks/ingress-staging-task/pom.xml b/airavata-kubernetes/modules/microservices/tasks/data-pushing-task/pom.xml
similarity index 96%
rename from airavata-kubernetes/modules/microservices/tasks/ingress-staging-task/pom.xml
rename to airavata-kubernetes/modules/microservices/tasks/data-pushing-task/pom.xml
index c1ae760..533914f 100644
--- a/airavata-kubernetes/modules/microservices/tasks/ingress-staging-task/pom.xml
+++ b/airavata-kubernetes/modules/microservices/tasks/data-pushing-task/pom.xml
@@ -31,7 +31,7 @@
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
-    <artifactId>ingress-staging-task</artifactId>
+    <artifactId>data-pushing-task</artifactId>
 
     <dependencies>
         <dependency>
@@ -44,6 +44,11 @@
             <artifactId>api-resource</artifactId>
             <version>1.0-SNAPSHOT</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.airavata</groupId>
+            <artifactId>task-api</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
 
         <dependency>
             <groupId>org.springframework.boot</groupId>
diff --git a/airavata-kubernetes/modules/microservices/tasks/ingress-staging-task/src/main/java/org/apache/airavata/k8s/task/ingress/Application.java b/airavata-kubernetes/modules/microservices/tasks/data-pushing-task/src/main/java/org/apache/airavata/k8s/task/ingress/Application.java
similarity index 100%
rename from airavata-kubernetes/modules/microservices/tasks/ingress-staging-task/src/main/java/org/apache/airavata/k8s/task/ingress/Application.java
rename to airavata-kubernetes/modules/microservices/tasks/data-pushing-task/src/main/java/org/apache/airavata/k8s/task/ingress/Application.java
diff --git a/airavata-kubernetes/modules/microservices/tasks/data-pushing-task/src/main/java/org/apache/airavata/k8s/task/ingress/service/TaskExecutionService.java b/airavata-kubernetes/modules/microservices/tasks/data-pushing-task/src/main/java/org/apache/airavata/k8s/task/ingress/service/TaskExecutionService.java
new file mode 100644
index 0000000..cb3fada
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/tasks/data-pushing-task/src/main/java/org/apache/airavata/k8s/task/ingress/service/TaskExecutionService.java
@@ -0,0 +1,83 @@
+/**
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.airavata.k8s.task.ingress.service;
+
+import org.apache.airavata.k8s.api.resources.compute.ComputeResource;
+import org.apache.airavata.k8s.api.resources.task.TaskResource;
+import org.apache.airavata.k8s.api.resources.task.TaskStatusResource;
+import org.apache.airavata.k8s.api.resources.task.type.TaskTypeResource;
+import org.apache.airavata.k8s.task.api.AbstractTaskExecutionService;
+import org.apache.airavata.k8s.task.api.TaskContext;
+import org.apache.airavata.k8s.task.api.messaging.KafkaSender;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+@Service
+public class TaskExecutionService extends AbstractTaskExecutionService {
+
+    private final String COMPUTE_RESOURCE =  "compute_resource";
+    private final String REMOTE_TARGET_PATH = "remote_target_path";
+    private final String DATA_LOCATION_ID = "data_location_id";
+
+    public TaskExecutionService(RestTemplate restTemplate, KafkaSender kafkaSender) {
+        super(restTemplate, kafkaSender, 10);
+    }
+
+    @Override
+    public TaskTypeResource getType() {
+        return null;
+    }
+
+    @Override
+    public void initializeParameters(TaskResource taskResource, TaskContext taskContext) throws Exception {
+
+        taskContext.getLocalContext().put(DATA_LOCATION_ID, findInput(taskResource, DATA_LOCATION_ID, false));
+        taskContext.getLocalContext().put(REMOTE_TARGET_PATH, findInput(taskResource, REMOTE_TARGET_PATH, false));
+
+        String computeId = findInput(taskResource, COMPUTE_RESOURCE, false);
+        taskContext.getLocalContext().put(COMPUTE_RESOURCE, this.getRestTemplate().getForObject("http://" + this.getApiServerUrl()
+                + "/compute/" + Long.parseLong(computeId), ComputeResource.class));
+    }
+
+    @Override
+    public void executeTask(TaskResource taskResource, TaskContext taskContext) {
+
+        String remoteTargetPath = (String) taskContext.getLocalContext().get(REMOTE_TARGET_PATH);
+        String dataLocationId = (String) taskContext.getLocalContext().get(DATA_LOCATION_ID);
+        ComputeResource computeResource = (ComputeResource) taskContext.getLocalContext().get(COMPUTE_RESOURCE);
+
+        try {
+            publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.EXECUTING);
+            fetchComputeResourceOperation(computeResource).transferDataIn(dataLocationId, remoteTargetPath, "SCP");
+            publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.COMPLETED);
+
+        } catch (Exception e) {
+
+            e.printStackTrace();
+            publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.FAILED);
+        }
+    }
+}
diff --git a/airavata-kubernetes/modules/microservices/tasks/ingress-staging-task/src/main/resources/application.properties b/airavata-kubernetes/modules/microservices/tasks/data-pushing-task/src/main/resources/application.properties
similarity index 100%
rename from airavata-kubernetes/modules/microservices/tasks/ingress-staging-task/src/main/resources/application.properties
rename to airavata-kubernetes/modules/microservices/tasks/data-pushing-task/src/main/resources/application.properties
diff --git a/airavata-kubernetes/modules/microservices/tasks/env-setup-task/src/main/resources/application.yml b/airavata-kubernetes/modules/microservices/tasks/data-pushing-task/src/main/resources/application.yml
similarity index 100%
rename from airavata-kubernetes/modules/microservices/tasks/env-setup-task/src/main/resources/application.yml
rename to airavata-kubernetes/modules/microservices/tasks/data-pushing-task/src/main/resources/application.yml
diff --git a/airavata-kubernetes/modules/microservices/tasks/egress-staging-task/src/main/java/org/apacher/airavata/k8s/task/egress/messaging/KafkaReceiver.java b/airavata-kubernetes/modules/microservices/tasks/egress-staging-task/src/main/java/org/apacher/airavata/k8s/task/egress/messaging/KafkaReceiver.java
deleted file mode 100644
index ade0aaf..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/egress-staging-task/src/main/java/org/apacher/airavata/k8s/task/egress/messaging/KafkaReceiver.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/**
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apacher.airavata.k8s.task.egress.messaging;
-
-import org.apacher.airavata.k8s.task.egress.service.TaskExecutionService;
-import org.springframework.kafka.annotation.KafkaListener;
-import org.springframework.kafka.support.Acknowledgment;
-
-import javax.annotation.Resource;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-public class KafkaReceiver {
-
-    @Resource
-    private TaskExecutionService taskExecutionService;
-
-    @KafkaListener(topics = "${task.read.topic.name}")
-    public void receiveTasks(String payload, Acknowledgment ack) {
-        System.out.println("received task=" + payload);
-        taskExecutionService.executeTaskAsync(Long.parseLong(payload));
-        ack.acknowledge();
-    }
-}
diff --git a/airavata-kubernetes/modules/microservices/tasks/egress-staging-task/src/main/java/org/apacher/airavata/k8s/task/egress/messaging/KafkaSender.java b/airavata-kubernetes/modules/microservices/tasks/egress-staging-task/src/main/java/org/apacher/airavata/k8s/task/egress/messaging/KafkaSender.java
deleted file mode 100644
index 253daa4..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/egress-staging-task/src/main/java/org/apacher/airavata/k8s/task/egress/messaging/KafkaSender.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/**
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apacher.airavata.k8s.task.egress.messaging;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.kafka.core.KafkaTemplate;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-public class KafkaSender {
-
-    @Autowired
-    private KafkaTemplate<String, String> kafkaTemplate;
-
-    public void send(String topic, String payload) {
-        kafkaTemplate.send(topic, payload);
-    }
-
-    public void send(String topic, String key, String payload) {
-        kafkaTemplate.send(topic, key, payload);
-    }
-}
diff --git a/airavata-kubernetes/modules/microservices/tasks/egress-staging-task/src/main/java/org/apacher/airavata/k8s/task/egress/messaging/SenderConfig.java b/airavata-kubernetes/modules/microservices/tasks/egress-staging-task/src/main/java/org/apacher/airavata/k8s/task/egress/messaging/SenderConfig.java
deleted file mode 100644
index 42674a3..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/egress-staging-task/src/main/java/org/apacher/airavata/k8s/task/egress/messaging/SenderConfig.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/**
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apacher.airavata.k8s.task.egress.messaging;
-
-import org.apache.kafka.clients.producer.ProducerConfig;
-import org.apache.kafka.common.serialization.StringSerializer;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.kafka.core.DefaultKafkaProducerFactory;
-import org.springframework.kafka.core.KafkaTemplate;
-import org.springframework.kafka.core.ProducerFactory;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-@Configuration
-public class SenderConfig {
-    @Value("${kafka.bootstrap-servers}")
-    private String bootstrapServers;
-
-    @Bean
-    public Map<String, Object> producerConfigs() {
-        Map<String, Object> props = new HashMap<>();
-        // list of host:port pairs used for establishing the initial connections to the Kakfa cluster
-        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
-        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
-        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
-        return props;
-    }
-
-    @Bean
-    public ProducerFactory<String, String> producerFactory() {
-        return new DefaultKafkaProducerFactory<String, String>(producerConfigs());
-    }
-
-    @Bean
-    public KafkaTemplate<String, String> kafkaTemplate() {
-        return new KafkaTemplate<>(producerFactory());
-    }
-
-    @Bean
-    public KafkaSender kafkaSender() {
-        return new KafkaSender();
-    }
-}
diff --git a/airavata-kubernetes/modules/microservices/tasks/egress-staging-task/src/main/java/org/apacher/airavata/k8s/task/egress/service/TaskExecutionService.java b/airavata-kubernetes/modules/microservices/tasks/egress-staging-task/src/main/java/org/apacher/airavata/k8s/task/egress/service/TaskExecutionService.java
deleted file mode 100644
index 30fca3b..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/egress-staging-task/src/main/java/org/apacher/airavata/k8s/task/egress/service/TaskExecutionService.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/**
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apacher.airavata.k8s.task.egress.service;
-
-import org.apache.airavata.k8s.api.resources.compute.ComputeResource;
-import org.apache.airavata.k8s.api.resources.task.TaskParamResource;
-import org.apache.airavata.k8s.api.resources.task.TaskResource;
-import org.apache.airavata.k8s.api.resources.task.TaskStatusResource;
-import org.apache.airavata.k8s.compute.api.ComputeOperations;
-import org.apache.airavata.k8s.compute.impl.MockComputeOperation;
-import org.apache.airavata.k8s.compute.impl.SSHComputeOperations;
-import org.apacher.airavata.k8s.task.egress.messaging.KafkaSender;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.core.io.FileSystemResource;
-import org.springframework.http.*;
-import org.springframework.stereotype.Service;
-import org.springframework.util.LinkedMultiValueMap;
-import org.springframework.web.client.RestTemplate;
-
-import java.util.Optional;
-import java.util.UUID;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-@Service
-public class TaskExecutionService {
-
-    private final ExecutorService executorService = Executors.newFixedThreadPool(10);
-
-    private final RestTemplate restTemplate;
-    private final KafkaSender kafkaSender;
-
-    @Value("${api.server.url}")
-    private String apiServerUrl;
-
-    @Value("${task.event.topic.name}")
-    private String taskEventPublishTopic;
-
-    public TaskExecutionService(RestTemplate restTemplate, KafkaSender kafkaSender) {
-        this.restTemplate = restTemplate;
-        this.kafkaSender = kafkaSender;
-    }
-
-    public void executeTaskAsync(long taskId) {
-
-        System.out.println("Executing task " + taskId + " as egress staging task");
-        TaskResource taskResource = this.restTemplate.getForObject("http://" + apiServerUrl + "/task/" + taskId, TaskResource.class);
-
-        publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.SCHEDULED);
-
-        this.executorService.execute(new Runnable() {
-            @Override
-            public void run() {
-                executeTask(taskResource);
-            }
-        });
-    }
-
-    public void executeTask(TaskResource taskResource) {
-        try {
-            Optional<TaskParamResource> sourceParam = taskResource.getTaskParams()
-                    .stream()
-                    .filter(taskParamResource -> "source".equals(taskParamResource.getKey()))
-                    .findFirst();
-
-            Optional<TaskParamResource> targetParam = taskResource.getTaskParams()
-                    .stream()
-                    .filter(taskParamResource -> "target".equals(taskParamResource.getKey()))
-                    .findFirst();
-
-            Optional<TaskParamResource> computeId = taskResource.getTaskParams()
-                    .stream()
-                    .filter(taskParamResource -> "compute-id".equals(taskParamResource.getKey()))
-                    .findFirst();
-
-            Optional<TaskParamResource> experimentDataDir = taskResource.getTaskParams()
-                    .stream()
-                    .filter(taskParamResource -> "exp-data-dir".equals(taskParamResource.getKey()))
-                    .findFirst();
-
-            String processDataDirectory = experimentDataDir
-                    .orElseThrow(() -> new Exception("exp-data-dir param can not be found in the params of task " +
-                            taskResource.getId())).getValue() + "/" + taskResource.getParentProcessId();
-
-
-            if (sourceParam.isPresent()) {
-                sourceParam.get().setValue(sourceParam.get().getValue().replace("{process-data-dir}", processDataDirectory));
-                if (targetParam.isPresent()) {
-                    publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.EXECUTING);
-
-                    ComputeResource computeResource = this.restTemplate.getForObject("http://" + this.apiServerUrl
-                            + "/compute/" + Long.parseLong(computeId.get().getValue()), ComputeResource.class);
-
-                    ComputeOperations operations;
-                    if ("SSH".equals(computeResource.getCommunicationType())) {
-                        operations = new SSHComputeOperations(computeResource.getHost(), computeResource.getUserName(), computeResource.getPassword());
-                    } else if ("Mock".equals(computeResource.getCommunicationType())) {
-                        operations = new MockComputeOperation(computeResource.getHost());
-                    } else {
-                        throw new Exception("No compatible communication method {" + computeResource.getCommunicationType() + "} not found for compute resource " + computeResource.getName());
-                    }
-
-                    try {
-
-                        String temporaryFile = "/tmp/" + UUID.randomUUID().toString();
-                        System.out.println("Downloading " + sourceParam.get().getValue() + " to " + temporaryFile +
-                                " from compute resource " + computeResource.getName());
-                        operations.transferDataOut(sourceParam.get().getValue(), temporaryFile, "SCP");
-
-                        RestTemplate template = new RestTemplate();
-                        LinkedMultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
-                        map.add("file", new FileSystemResource(temporaryFile));
-                        HttpHeaders headers = new HttpHeaders();
-                        headers.setContentType(MediaType.MULTIPART_FORM_DATA);
-
-                        HttpEntity<LinkedMultiValueMap<String, Object>> requestEntity = new HttpEntity<LinkedMultiValueMap<String, Object>>(
-                                map, headers);
-
-                        System.out.println("Uploading data file with task id " + taskResource.getId()
-                                + " and experiment output id " + targetParam.get().getValue() + " to data store");
-
-                        ResponseEntity<Long> result = template.exchange(
-                                "http://" + apiServerUrl + "/data/" + taskResource.getId()+ "/"
-                                        + targetParam.get().getValue() +"/upload", HttpMethod.POST, requestEntity, Long.class);
-
-                        publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(),
-                                TaskStatusResource.State.COMPLETED);
-
-                    } catch (Exception e) {
-
-                        e.printStackTrace();
-                        publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(),
-                                TaskStatusResource.State.FAILED, e.getMessage());
-                    }
-                } else {
-                    System.out.println("Target can not be null for task " + taskResource.getId());
-                    publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(),
-                            TaskStatusResource.State.FAILED, "Target can not be null for task " + taskResource.getId());
-                }
-            } else {
-                System.out.println("Source can not be null for task " + taskResource.getId());
-                publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(),
-                        TaskStatusResource.State.FAILED, "Source can not be null for task " + taskResource.getId());
-            }
-        } catch (Exception e) {
-            e.printStackTrace();
-            publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(),
-                    TaskStatusResource.State.FAILED, e.getMessage());
-        }
-    }
-
-    public void publishTaskStatus(long processId, long taskId, int status) {
-        publishTaskStatus(processId, taskId, status, "");
-    }
-
-    public void publishTaskStatus(long processId, long taskId, int status, String reason) {
-        this.kafkaSender.send(this.taskEventPublishTopic, processId + "-" + taskId,
-                processId + "," + taskId + "," + status + "," + reason);
-    }
-}
diff --git a/airavata-kubernetes/modules/microservices/tasks/egress-staging-task/src/main/resources/application.yml b/airavata-kubernetes/modules/microservices/tasks/egress-staging-task/src/main/resources/application.yml
deleted file mode 100644
index 069dd61..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/egress-staging-task/src/main/resources/application.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-kafka:
-  bootstrap-servers: kafka.default.svc.cluster.local:9092
-  topic:
-    helloworld: helloworld.t
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/microservices/tasks/env-cleanup-task/pom.xml b/airavata-kubernetes/modules/microservices/tasks/env-cleanup-task/pom.xml
deleted file mode 100644
index 43bca2c..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/env-cleanup-task/pom.xml
+++ /dev/null
@@ -1,156 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-
-
-    Licensed to the Apache Software Foundation (ASF) under one
-    or more contributor license agreements.  See the NOTICE file
-    distributed with this work for additional information
-    regarding copyright ownership.  The ASF licenses this file
-    to you under the Apache License, Version 2.0 (the
-    "License"); you may not use this file except in compliance
-    with the License.  You may obtain a copy of the License at
-
-      http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing,
-    software distributed under the License is distributed on an
-    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-    KIND, either express or implied.  See the License for the
-    specific language governing permissions and limitations
-    under the License.
-
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-    <parent>
-        <artifactId>airavata-kubernetes</artifactId>
-        <groupId>org.apache.airavata</groupId>
-        <version>1.0-SNAPSHOT</version>
-        <relativePath>../../../../pom.xml</relativePath>
-    </parent>
-    <modelVersion>4.0.0</modelVersion>
-
-    <artifactId>env-cleanup-task</artifactId>
-
-    <dependencies>
-        <dependency>
-            <groupId>org.apache.airavata</groupId>
-            <artifactId>compute-resource-api</artifactId>
-            <version>1.0-SNAPSHOT</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.airavata</groupId>
-            <artifactId>api-resource</artifactId>
-            <version>1.0-SNAPSHOT</version>
-        </dependency>
-
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-web</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-freemarker</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.kafka</groupId>
-            <artifactId>spring-kafka</artifactId>
-        </dependency>
-    </dependencies>
-
-    <build>
-        <plugins>
-            <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-compiler-plugin</artifactId>
-                <version>3.5.1</version>
-                <configuration>
-                    <source>${java.version}</source>
-                    <target>${java.version}</target>
-                </configuration>
-            </plugin>
-        </plugins>
-    </build>
-
-    <profiles>
-
-        <profile>
-            <id>jar</id>
-            <activation>
-                <activeByDefault>true</activeByDefault>
-            </activation>
-            <properties>
-                <artifact-packaging>jar</artifact-packaging>
-            </properties>
-            <build>
-                <plugins>
-                    <plugin>
-                        <groupId>org.springframework.boot</groupId>
-                        <artifactId>spring-boot-maven-plugin</artifactId>
-                        <version>1.4.3.RELEASE</version>
-                        <executions>
-                            <execution>
-                                <goals>
-                                    <goal>repackage</goal>
-                                </goals>
-                            </execution>
-                        </executions>
-                    </plugin>
-                    <!-- Create a docker image that runs the executable jar-->
-                    <plugin>
-                        <groupId>com.spotify</groupId>
-                        <artifactId>docker-maven-plugin</artifactId>
-                        <version>1.0.0</version>
-                        <configuration>
-                            <imageName>${docker.image.prefix}/env-cleanup-task</imageName>
-                            <baseImage>java:openjdk-8-jdk-alpine</baseImage>
-                            <entryPoint>["java","-jar","/${project.build.finalName}.jar"]</entryPoint>
-                            <resources>
-                                <resource>
-                                    <targetPath>/</targetPath>
-                                    <directory>${project.build.directory}</directory>
-                                    <include>${project.build.finalName}.jar</include>
-                                </resource>
-                            </resources>
-                        </configuration>
-                        <executions>
-                            <execution>
-                                <goals>
-                                    <goal>build</goal>
-                                </goals>
-                            </execution>
-                        </executions>
-                    </plugin>
-                </plugins>
-            </build>
-        </profile>
-
-        <profile>
-            <id>war</id>
-            <properties>
-                <artifact-packaging>war</artifact-packaging>
-            </properties>
-            <dependencies>
-                <dependency>
-                    <groupId>org.springframework.boot</groupId>
-                    <artifactId>spring-boot-starter-tomcat</artifactId>
-                    <scope>provided</scope>
-                </dependency>
-            </dependencies>
-            <build>
-                <plugins>
-                    <plugin>
-                        <groupId>org.springframework.boot</groupId>
-                        <artifactId>spring-boot-maven-plugin</artifactId>
-                        <version>1.4.3.RELEASE</version>
-                        <configuration>
-                            <!-- this will get rid of version info from war file name -->
-                            <finalName>env-cleanup</finalName>
-                        </configuration>
-                    </plugin>
-                </plugins>
-            </build>
-        </profile>
-    </profiles>
-</project>
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/microservices/tasks/env-cleanup-task/src/main/java/org/apache/airavata/k8s/task/cleanup/Application.java b/airavata-kubernetes/modules/microservices/tasks/env-cleanup-task/src/main/java/org/apache/airavata/k8s/task/cleanup/Application.java
deleted file mode 100644
index 1e5cf1f..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/env-cleanup-task/src/main/java/org/apache/airavata/k8s/task/cleanup/Application.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/**
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.airavata.k8s.task.cleanup;
-
-import org.springframework.boot.SpringApplication;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.boot.web.client.RestTemplateBuilder;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.ComponentScan;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.web.client.RestTemplate;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-@SpringBootApplication
-@Configuration
-@ComponentScan
-public class Application {
-
-    public static void main(String[] args) {
-        SpringApplication.run(Application.class, args);
-    }
-
-    @Bean
-    public RestTemplate restTemplate(RestTemplateBuilder builder) {
-        return builder.build();
-    }
-}
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/microservices/tasks/env-cleanup-task/src/main/java/org/apache/airavata/k8s/task/cleanup/messaging/KafkaReceiver.java b/airavata-kubernetes/modules/microservices/tasks/env-cleanup-task/src/main/java/org/apache/airavata/k8s/task/cleanup/messaging/KafkaReceiver.java
deleted file mode 100644
index 01aaa51..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/env-cleanup-task/src/main/java/org/apache/airavata/k8s/task/cleanup/messaging/KafkaReceiver.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/**
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.airavata.k8s.task.cleanup.messaging;
-
-import org.apache.airavata.k8s.task.cleanup.service.TaskExecutionService;
-import org.springframework.kafka.annotation.KafkaListener;
-import org.springframework.kafka.support.Acknowledgment;
-
-import javax.annotation.Resource;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-public class KafkaReceiver {
-
-    @Resource
-    private TaskExecutionService taskExecutionService;
-
-    @KafkaListener(topics = "${task.read.topic.name}")
-    public void receiveTasks(String payload, Acknowledgment ack) {
-        System.out.println("received task=" + payload);
-        taskExecutionService.executeTaskAsync(Long.parseLong(payload));
-        ack.acknowledge();
-    }
-}
diff --git a/airavata-kubernetes/modules/microservices/tasks/env-cleanup-task/src/main/java/org/apache/airavata/k8s/task/cleanup/messaging/KafkaSender.java b/airavata-kubernetes/modules/microservices/tasks/env-cleanup-task/src/main/java/org/apache/airavata/k8s/task/cleanup/messaging/KafkaSender.java
deleted file mode 100644
index 8293d31..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/env-cleanup-task/src/main/java/org/apache/airavata/k8s/task/cleanup/messaging/KafkaSender.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/**
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.airavata.k8s.task.cleanup.messaging;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.kafka.core.KafkaTemplate;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-public class KafkaSender {
-
-    @Autowired
-    private KafkaTemplate<String, String> kafkaTemplate;
-
-    public void send(String topic, String payload) {
-        kafkaTemplate.send(topic, payload);
-    }
-
-    public void send(String topic, String key, String payload) {
-        kafkaTemplate.send(topic, key, payload);
-    }
-}
diff --git a/airavata-kubernetes/modules/microservices/tasks/env-cleanup-task/src/main/java/org/apache/airavata/k8s/task/cleanup/messaging/ReceiverConfig.java b/airavata-kubernetes/modules/microservices/tasks/env-cleanup-task/src/main/java/org/apache/airavata/k8s/task/cleanup/messaging/ReceiverConfig.java
deleted file mode 100644
index 941ba11..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/env-cleanup-task/src/main/java/org/apache/airavata/k8s/task/cleanup/messaging/ReceiverConfig.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/**
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.airavata.k8s.task.cleanup.messaging;
-
-import org.apache.kafka.clients.consumer.ConsumerConfig;
-import org.apache.kafka.common.serialization.StringDeserializer;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.kafka.annotation.EnableKafka;
-import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
-import org.springframework.kafka.config.KafkaListenerContainerFactory;
-import org.springframework.kafka.core.ConsumerFactory;
-import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
-import org.springframework.kafka.listener.AbstractMessageListenerContainer;
-import org.springframework.kafka.listener.ConcurrentMessageListenerContainer;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-@Configuration
-@EnableKafka
-public class ReceiverConfig {
-
-    @Value("${kafka.bootstrap-servers}")
-    private String bootstrapServers;
-
-    @Value("${task.group.name}")
-    private String taskGroupName;
-
-    @Bean
-    public Map<String, Object> consumerConfigs() {
-        Map<String, Object> props = new HashMap<>();
-        // list of host:port pairs used for establishing the initial connections to the Kakfa cluster
-        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
-        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
-        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
-        // allows a pool of processes to divide the work of consuming and processing records
-        props.put(ConsumerConfig.GROUP_ID_CONFIG, taskGroupName);
-        props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
-        return props;
-    }
-
-    @Bean
-    public ConsumerFactory<String, String> consumerFactory() {
-        return new DefaultKafkaConsumerFactory<String, String>(consumerConfigs());
-    }
-
-    @Bean
-    public KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<String, String>> kafkaListenerContainerFactory() {
-        ConcurrentKafkaListenerContainerFactory<String, String> factory =
-                new ConcurrentKafkaListenerContainerFactory<>();
-        factory.setConsumerFactory(consumerFactory());
-        factory.getContainerProperties().setAckMode(AbstractMessageListenerContainer.AckMode.MANUAL_IMMEDIATE);
-        return factory;
-    }
-
-    @Bean
-    public KafkaReceiver receiver() {
-        return new KafkaReceiver();
-    }
-}
diff --git a/airavata-kubernetes/modules/microservices/tasks/env-cleanup-task/src/main/java/org/apache/airavata/k8s/task/cleanup/messaging/SenderConfig.java b/airavata-kubernetes/modules/microservices/tasks/env-cleanup-task/src/main/java/org/apache/airavata/k8s/task/cleanup/messaging/SenderConfig.java
deleted file mode 100644
index a84cecf..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/env-cleanup-task/src/main/java/org/apache/airavata/k8s/task/cleanup/messaging/SenderConfig.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/**
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.airavata.k8s.task.cleanup.messaging;
-
-import org.apache.kafka.clients.producer.ProducerConfig;
-import org.apache.kafka.common.serialization.StringSerializer;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.kafka.core.DefaultKafkaProducerFactory;
-import org.springframework.kafka.core.KafkaTemplate;
-import org.springframework.kafka.core.ProducerFactory;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-@Configuration
-public class SenderConfig {
-    @Value("${kafka.bootstrap-servers}")
-    private String bootstrapServers;
-
-    @Bean
-    public Map<String, Object> producerConfigs() {
-        Map<String, Object> props = new HashMap<>();
-        // list of host:port pairs used for establishing the initial connections to the Kakfa cluster
-        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
-        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
-        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
-        return props;
-    }
-
-    @Bean
-    public ProducerFactory<String, String> producerFactory() {
-        return new DefaultKafkaProducerFactory<String, String>(producerConfigs());
-    }
-
-    @Bean
-    public KafkaTemplate<String, String> kafkaTemplate() {
-        return new KafkaTemplate<>(producerFactory());
-    }
-
-    @Bean
-    public KafkaSender kafkaSender() {
-        return new KafkaSender();
-    }
-}
diff --git a/airavata-kubernetes/modules/microservices/tasks/env-cleanup-task/src/main/java/org/apache/airavata/k8s/task/cleanup/service/TaskExecutionService.java b/airavata-kubernetes/modules/microservices/tasks/env-cleanup-task/src/main/java/org/apache/airavata/k8s/task/cleanup/service/TaskExecutionService.java
deleted file mode 100644
index 4c9cc66..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/env-cleanup-task/src/main/java/org/apache/airavata/k8s/task/cleanup/service/TaskExecutionService.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/**
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.airavata.k8s.task.cleanup.service;
-
-import org.apache.airavata.k8s.api.resources.compute.ComputeResource;
-import org.apache.airavata.k8s.api.resources.task.TaskParamResource;
-import org.apache.airavata.k8s.api.resources.task.TaskResource;
-import org.apache.airavata.k8s.api.resources.task.TaskStatusResource;
-import org.apache.airavata.k8s.compute.api.ComputeOperations;
-import org.apache.airavata.k8s.compute.api.ExecutionResult;
-import org.apache.airavata.k8s.compute.impl.MockComputeOperation;
-import org.apache.airavata.k8s.compute.impl.SSHComputeOperations;
-import org.apache.airavata.k8s.task.cleanup.messaging.KafkaSender;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.stereotype.Service;
-import org.springframework.web.client.RestTemplate;
-
-import java.util.Optional;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-@Service
-public class TaskExecutionService {
-
-    private final ExecutorService executorService = Executors.newFixedThreadPool(10);
-
-    private final RestTemplate restTemplate;
-    private final KafkaSender kafkaSender;
-
-    @Value("${api.server.url}")
-    private String apiServerUrl;
-
-    @Value("${task.event.topic.name}")
-    private String taskEventPublishTopic;
-
-    public TaskExecutionService(RestTemplate restTemplate, KafkaSender kafkaSender) {
-        this.restTemplate = restTemplate;
-        this.kafkaSender = kafkaSender;
-    }
-
-    public void executeTaskAsync(long taskId) {
-
-        System.out.println("Executing task " + taskId + " as env cleanup task");
-        TaskResource taskResource = this.restTemplate.getForObject("http://" + apiServerUrl + "/task/" + taskId, TaskResource.class);
-
-        publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.SCHEDULED);
-
-        this.executorService.execute(new Runnable() {
-            @Override
-            public void run() {
-                executeTask(taskResource);
-            }
-        });
-    }
-
-    public void executeTask(TaskResource taskResource) {
-
-        try {
-            Optional<TaskParamResource> commandParam = taskResource.getTaskParams()
-                    .stream()
-                    .filter(taskParamResource -> "command".equals(taskParamResource.getKey()))
-                    .findFirst();
-
-            Optional<TaskParamResource> computeId = taskResource.getTaskParams()
-                    .stream()
-                    .filter(taskParamResource -> "compute-id".equals(taskParamResource.getKey()))
-                    .findFirst();
-
-            Optional<TaskParamResource> experimentDataDir = taskResource.getTaskParams()
-                    .stream()
-                    .filter(taskParamResource -> "exp-data-dir".equals(taskParamResource.getKey()))
-                    .findFirst();
-
-            String processDataDirectory = experimentDataDir
-                    .orElseThrow(() -> new Exception("exp-data-dir param can not be found in the params of task " +
-                            taskResource.getId())).getValue() + "/" + taskResource.getParentProcessId();
-
-            commandParam.ifPresent(taskParamResource -> {
-
-                try {
-                    String command = taskParamResource.getValue();
-                    command = command.replace("{process-data-dir}", processDataDirectory);
-
-                    System.out.println("Executing command " + command);
-
-                    publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.EXECUTING);
-
-                    // TODO fetch this from the catalog
-                    ComputeResource computeResource = this.restTemplate.getForObject("http://" + this.apiServerUrl
-                            + "/compute/" + Long.parseLong(computeId.get().getValue()), ComputeResource.class);
-
-                    // TODO fetch this from the catalog
-                    ComputeOperations operations;
-                    if ("SSH".equals(computeResource.getCommunicationType())) {
-                        operations = new SSHComputeOperations(computeResource.getHost(), computeResource.getUserName(), computeResource.getPassword());
-                    } else if ("Mock".equals(computeResource.getCommunicationType())) {
-                        operations = new MockComputeOperation(computeResource.getHost());
-                    } else {
-                        throw new Exception("No compatible communication method {" + computeResource.getCommunicationType() + "} not found for compute resource " + computeResource.getName());
-                    }
-
-                    ExecutionResult executionResult = operations.executeCommand(command);
-                    if (executionResult.getExitStatus() == 0) {
-                        publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.COMPLETED);
-                    } else if (executionResult.getExitStatus() == -1) {
-                        publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.FAILED, "Process didn't exit successfully");
-                    } else {
-                        publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.FAILED, "Process exited with error status " + executionResult.getExitStatus());
-                    }
-
-                } catch (Exception e) {
-                    e.printStackTrace();
-                    publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.FAILED, e.getMessage());
-                }
-            });
-        } catch (Exception e) {
-            e.printStackTrace();
-            publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.FAILED, e.getMessage());
-        }
-    }
-
-    public void publishTaskStatus(long processId, long taskId, int status) {
-        publishTaskStatus(processId, taskId, status, "");
-    }
-
-    public void publishTaskStatus(long processId, long taskId, int status, String reason) {
-        this.kafkaSender.send(this.taskEventPublishTopic, processId + "-" + taskId,
-                processId + "," + taskId + "," + status + "," + reason);
-    }
-}
diff --git a/airavata-kubernetes/modules/microservices/tasks/env-cleanup-task/src/main/resources/application.properties b/airavata-kubernetes/modules/microservices/tasks/env-cleanup-task/src/main/resources/application.properties
deleted file mode 100644
index f9d49a7..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/env-cleanup-task/src/main/resources/application.properties
+++ /dev/null
@@ -1,5 +0,0 @@
-server.port = 8591
-api.server.url = api-server.default.svc.cluster.local:8080
-task.group.name = env-cleanup
-task.event.topic.name = airavata-task-event
-task.read.topic.name = airavata-task-env-cleanup
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/microservices/tasks/env-cleanup-task/src/main/resources/application.yml b/airavata-kubernetes/modules/microservices/tasks/env-cleanup-task/src/main/resources/application.yml
deleted file mode 100644
index 069dd61..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/env-cleanup-task/src/main/resources/application.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-kafka:
-  bootstrap-servers: kafka.default.svc.cluster.local:9092
-  topic:
-    helloworld: helloworld.t
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/microservices/tasks/env-setup-task/pom.xml b/airavata-kubernetes/modules/microservices/tasks/env-setup-task/pom.xml
deleted file mode 100644
index d011d7b..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/env-setup-task/pom.xml
+++ /dev/null
@@ -1,157 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-
-
-    Licensed to the Apache Software Foundation (ASF) under one
-    or more contributor license agreements.  See the NOTICE file
-    distributed with this work for additional information
-    regarding copyright ownership.  The ASF licenses this file
-    to you under the Apache License, Version 2.0 (the
-    "License"); you may not use this file except in compliance
-    with the License.  You may obtain a copy of the License at
-
-      http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing,
-    software distributed under the License is distributed on an
-    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-    KIND, either express or implied.  See the License for the
-    specific language governing permissions and limitations
-    under the License.
-
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-    <parent>
-        <artifactId>airavata-kubernetes</artifactId>
-        <groupId>org.apache.airavata</groupId>
-        <version>1.0-SNAPSHOT</version>
-        <relativePath>../../../../pom.xml</relativePath>
-    </parent>
-    <modelVersion>4.0.0</modelVersion>
-
-    <artifactId>env-setup-task</artifactId>
-
-    <dependencies>
-        <dependency>
-            <groupId>org.apache.airavata</groupId>
-            <artifactId>compute-resource-api</artifactId>
-            <version>1.0-SNAPSHOT</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.airavata</groupId>
-            <artifactId>api-resource</artifactId>
-            <version>1.0-SNAPSHOT</version>
-        </dependency>
-
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-web</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-freemarker</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.kafka</groupId>
-            <artifactId>spring-kafka</artifactId>
-        </dependency>
-    </dependencies>
-
-    <build>
-        <plugins>
-            <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-compiler-plugin</artifactId>
-                <version>3.5.1</version>
-                <configuration>
-                    <source>${java.version}</source>
-                    <target>${java.version}</target>
-                </configuration>
-            </plugin>
-        </plugins>
-    </build>
-
-    <profiles>
-
-        <profile>
-            <id>jar</id>
-            <activation>
-                <activeByDefault>true</activeByDefault>
-            </activation>
-            <properties>
-                <artifact-packaging>jar</artifact-packaging>
-            </properties>
-            <build>
-                <plugins>
-                    <plugin>
-                        <groupId>org.springframework.boot</groupId>
-                        <artifactId>spring-boot-maven-plugin</artifactId>
-                        <version>1.4.3.RELEASE</version>
-                        <executions>
-                            <execution>
-                                <goals>
-                                    <goal>repackage</goal>
-                                </goals>
-                            </execution>
-                        </executions>
-                    </plugin>
-                    <!-- Create a docker image that runs the executable jar-->
-                    <plugin>
-                        <groupId>com.spotify</groupId>
-                        <artifactId>docker-maven-plugin</artifactId>
-                        <version>1.0.0</version>
-                        <configuration>
-                            <imageName>${docker.image.prefix}/env-setup-task</imageName>
-                            <baseImage>java:openjdk-8-jdk-alpine</baseImage>
-                            <entryPoint>["java","-jar","/${project.build.finalName}.jar"]</entryPoint>
-                            <resources>
-                                <resource>
-                                    <targetPath>/</targetPath>
-                                    <directory>${project.build.directory}</directory>
-                                    <include>${project.build.finalName}.jar</include>
-                                </resource>
-                            </resources>
-                        </configuration>
-                        <executions>
-                            <execution>
-                                <goals>
-                                    <goal>build</goal>
-                                </goals>
-                            </execution>
-                        </executions>
-                    </plugin>
-                </plugins>
-            </build>
-        </profile>
-
-        <profile>
-            <id>war</id>
-            <properties>
-                <artifact-packaging>war</artifact-packaging>
-            </properties>
-            <dependencies>
-                <dependency>
-                    <groupId>org.springframework.boot</groupId>
-                    <artifactId>spring-boot-starter-tomcat</artifactId>
-                    <scope>provided</scope>
-                </dependency>
-            </dependencies>
-            <build>
-                <plugins>
-                    <plugin>
-                        <groupId>org.springframework.boot</groupId>
-                        <artifactId>spring-boot-maven-plugin</artifactId>
-                        <version>1.4.3.RELEASE</version>
-                        <configuration>
-                            <!-- this will get rid of version info from war file name -->
-                            <finalName>env-setup</finalName>
-                        </configuration>
-                    </plugin>
-                </plugins>
-            </build>
-        </profile>
-    </profiles>
-
-</project>
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/microservices/tasks/env-setup-task/src/main/java/org/apache/airavata/k8s/task/env/setup/Application.java b/airavata-kubernetes/modules/microservices/tasks/env-setup-task/src/main/java/org/apache/airavata/k8s/task/env/setup/Application.java
deleted file mode 100644
index 43881fc..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/env-setup-task/src/main/java/org/apache/airavata/k8s/task/env/setup/Application.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/**
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.airavata.k8s.task.env.setup;
-
-import org.springframework.boot.SpringApplication;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.boot.web.client.RestTemplateBuilder;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.ComponentScan;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.web.client.RestTemplate;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-@SpringBootApplication
-@Configuration
-@ComponentScan
-public class Application {
-
-    public static void main(String[] args) {
-        SpringApplication.run(Application.class, args);
-    }
-
-    @Bean
-    public RestTemplate restTemplate(RestTemplateBuilder builder) {
-        return builder.build();
-    }
-}
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/microservices/tasks/env-setup-task/src/main/java/org/apache/airavata/k8s/task/env/setup/messaging/KafkaReceiver.java b/airavata-kubernetes/modules/microservices/tasks/env-setup-task/src/main/java/org/apache/airavata/k8s/task/env/setup/messaging/KafkaReceiver.java
deleted file mode 100644
index 38c7bf4..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/env-setup-task/src/main/java/org/apache/airavata/k8s/task/env/setup/messaging/KafkaReceiver.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/**
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.airavata.k8s.task.env.setup.messaging;
-
-import org.apache.airavata.k8s.task.env.setup.service.TaskExecutionService;
-import org.springframework.kafka.annotation.KafkaListener;
-import org.springframework.kafka.support.Acknowledgment;
-
-import javax.annotation.Resource;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-public class KafkaReceiver {
-
-    @Resource
-    private TaskExecutionService taskExecutionService;
-
-    @KafkaListener(topics = "${task.read.topic.name}")
-    public void receiveTasks(String payload, Acknowledgment ack) {
-        System.out.println("received task=" + payload);
-        taskExecutionService.executeTaskAsync(Long.parseLong(payload));
-        ack.acknowledge();
-    }
-}
diff --git a/airavata-kubernetes/modules/microservices/tasks/env-setup-task/src/main/java/org/apache/airavata/k8s/task/env/setup/messaging/KafkaSender.java b/airavata-kubernetes/modules/microservices/tasks/env-setup-task/src/main/java/org/apache/airavata/k8s/task/env/setup/messaging/KafkaSender.java
deleted file mode 100644
index 5cf6499..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/env-setup-task/src/main/java/org/apache/airavata/k8s/task/env/setup/messaging/KafkaSender.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/**
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.airavata.k8s.task.env.setup.messaging;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.kafka.core.KafkaTemplate;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-public class KafkaSender {
-
-    @Autowired
-    private KafkaTemplate<String, String> kafkaTemplate;
-
-    public void send(String topic, String payload) {
-        kafkaTemplate.send(topic, payload);
-    }
-
-    public void send(String topic, String key, String payload) {
-        kafkaTemplate.send(topic, key, payload);
-    }
-}
diff --git a/airavata-kubernetes/modules/microservices/tasks/env-setup-task/src/main/java/org/apache/airavata/k8s/task/env/setup/messaging/ReceiverConfig.java b/airavata-kubernetes/modules/microservices/tasks/env-setup-task/src/main/java/org/apache/airavata/k8s/task/env/setup/messaging/ReceiverConfig.java
deleted file mode 100644
index c26395e..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/env-setup-task/src/main/java/org/apache/airavata/k8s/task/env/setup/messaging/ReceiverConfig.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/**
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.airavata.k8s.task.env.setup.messaging;
-
-import org.apache.kafka.clients.consumer.ConsumerConfig;
-import org.apache.kafka.common.serialization.StringDeserializer;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.kafka.annotation.EnableKafka;
-import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
-import org.springframework.kafka.config.KafkaListenerContainerFactory;
-import org.springframework.kafka.core.ConsumerFactory;
-import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
-import org.springframework.kafka.listener.AbstractMessageListenerContainer;
-import org.springframework.kafka.listener.ConcurrentMessageListenerContainer;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-@Configuration
-@EnableKafka
-public class ReceiverConfig {
-
-    @Value("${kafka.bootstrap-servers}")
-    private String bootstrapServers;
-
-    @Value("${task.group.name}")
-    private String taskGroupName;
-
-    @Bean
-    public Map<String, Object> consumerConfigs() {
-        Map<String, Object> props = new HashMap<>();
-        // list of host:port pairs used for establishing the initial connections to the Kakfa cluster
-        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
-        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
-        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
-        // allows a pool of processes to divide the work of consuming and processing records
-        props.put(ConsumerConfig.GROUP_ID_CONFIG, taskGroupName);
-        props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
-        return props;
-    }
-
-    @Bean
-    public ConsumerFactory<String, String> consumerFactory() {
-        return new DefaultKafkaConsumerFactory<String, String>(consumerConfigs());
-    }
-
-    @Bean
-    public KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<String, String>> kafkaListenerContainerFactory() {
-        ConcurrentKafkaListenerContainerFactory<String, String> factory =
-                new ConcurrentKafkaListenerContainerFactory<>();
-        factory.setConsumerFactory(consumerFactory());
-        factory.getContainerProperties().setAckMode(AbstractMessageListenerContainer.AckMode.MANUAL_IMMEDIATE);
-        return factory;
-    }
-
-    @Bean
-    public KafkaReceiver receiver() {
-        return new KafkaReceiver();
-    }
-}
diff --git a/airavata-kubernetes/modules/microservices/tasks/env-setup-task/src/main/java/org/apache/airavata/k8s/task/env/setup/messaging/SenderConfig.java b/airavata-kubernetes/modules/microservices/tasks/env-setup-task/src/main/java/org/apache/airavata/k8s/task/env/setup/messaging/SenderConfig.java
deleted file mode 100644
index ce8b975..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/env-setup-task/src/main/java/org/apache/airavata/k8s/task/env/setup/messaging/SenderConfig.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/**
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.airavata.k8s.task.env.setup.messaging;
-
-import org.apache.kafka.clients.producer.ProducerConfig;
-import org.apache.kafka.common.serialization.StringSerializer;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.kafka.core.DefaultKafkaProducerFactory;
-import org.springframework.kafka.core.KafkaTemplate;
-import org.springframework.kafka.core.ProducerFactory;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-@Configuration
-public class SenderConfig {
-    @Value("${kafka.bootstrap-servers}")
-    private String bootstrapServers;
-
-    @Bean
-    public Map<String, Object> producerConfigs() {
-        Map<String, Object> props = new HashMap<>();
-        // list of host:port pairs used for establishing the initial connections to the Kakfa cluster
-        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
-        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
-        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
-        return props;
-    }
-
-    @Bean
-    public ProducerFactory<String, String> producerFactory() {
-        return new DefaultKafkaProducerFactory<String, String>(producerConfigs());
-    }
-
-    @Bean
-    public KafkaTemplate<String, String> kafkaTemplate() {
-        return new KafkaTemplate<>(producerFactory());
-    }
-
-    @Bean
-    public KafkaSender kafkaSender() {
-        return new KafkaSender();
-    }
-}
diff --git a/airavata-kubernetes/modules/microservices/tasks/env-setup-task/src/main/java/org/apache/airavata/k8s/task/env/setup/service/TaskExecutionService.java b/airavata-kubernetes/modules/microservices/tasks/env-setup-task/src/main/java/org/apache/airavata/k8s/task/env/setup/service/TaskExecutionService.java
deleted file mode 100644
index 7e2c2da..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/env-setup-task/src/main/java/org/apache/airavata/k8s/task/env/setup/service/TaskExecutionService.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/**
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.airavata.k8s.task.env.setup.service;
-
-import org.apache.airavata.k8s.api.resources.compute.ComputeResource;
-import org.apache.airavata.k8s.api.resources.task.TaskParamResource;
-import org.apache.airavata.k8s.api.resources.task.TaskResource;
-import org.apache.airavata.k8s.api.resources.task.TaskStatusResource;
-import org.apache.airavata.k8s.compute.api.ComputeOperations;
-import org.apache.airavata.k8s.compute.api.ExecutionResult;
-import org.apache.airavata.k8s.compute.impl.MockComputeOperation;
-import org.apache.airavata.k8s.compute.impl.SSHComputeOperations;
-import org.apache.airavata.k8s.task.env.setup.messaging.KafkaSender;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.stereotype.Service;
-import org.springframework.web.client.RestTemplate;
-
-import java.util.Optional;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-@Service
-public class TaskExecutionService {
-
-    private final ExecutorService executorService = Executors.newFixedThreadPool(10);
-
-    private final RestTemplate restTemplate;
-    private final KafkaSender kafkaSender;
-
-    @Value("${api.server.url}")
-    private String apiServerUrl;
-
-    @Value("${task.event.topic.name}")
-    private String taskEventPublishTopic;
-
-    public TaskExecutionService(RestTemplate restTemplate, KafkaSender kafkaSender) {
-        this.restTemplate = restTemplate;
-        this.kafkaSender = kafkaSender;
-    }
-
-    public void executeTaskAsync(long taskId) {
-
-        System.out.println("Executing task " + taskId + " as env setup task");
-        TaskResource taskResource = this.restTemplate.getForObject("http://" + apiServerUrl + "/task/" + taskId, TaskResource.class);
-
-        publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.SCHEDULED);
-
-        this.executorService.execute(new Runnable() {
-            @Override
-            public void run() {
-                executeTask(taskResource);
-            }
-        });
-    }
-
-    public void executeTask(TaskResource taskResource) {
-
-        try {
-
-            Optional<TaskParamResource> commandParam = taskResource.getTaskParams()
-                    .stream()
-                    .filter(taskParamResource -> "command".equals(taskParamResource.getKey()))
-                    .findFirst();
-
-            Optional<TaskParamResource> computeId = taskResource.getTaskParams()
-                    .stream()
-                    .filter(taskParamResource -> "compute-id".equals(taskParamResource.getKey()))
-                    .findFirst();
-
-            Optional<TaskParamResource> experimentDataDir = taskResource.getTaskParams()
-                    .stream()
-                    .filter(taskParamResource -> "exp-data-dir".equals(taskParamResource.getKey()))
-                    .findFirst();
-
-            String processDataDirectory = experimentDataDir
-                    .orElseThrow(() -> new Exception("exp-data-dir param can not be found in the params of task " +
-                            taskResource.getId())).getValue() + "/" + taskResource.getParentProcessId();
-
-            commandParam.ifPresent(taskParamResource -> {
-                try {
-
-                    String command = taskParamResource.getValue();
-                    command = command.replace("{process-data-dir}", processDataDirectory);
-                    System.out.println("Executing command " + command);
-
-                    publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.EXECUTING);
-
-                    ComputeResource computeResource = this.restTemplate.getForObject("http://" + this.apiServerUrl
-                            + "/compute/" + Long.parseLong(computeId.get().getValue()), ComputeResource.class);
-
-                    // TODO fetch this from the catalog
-                    ComputeOperations operations;
-                    if ("SSH".equals(computeResource.getCommunicationType())) {
-                        operations = new SSHComputeOperations(computeResource.getHost(), computeResource.getUserName(), computeResource.getPassword());
-                    } else if ("Mock".equals(computeResource.getCommunicationType())) {
-                        operations = new MockComputeOperation(computeResource.getHost());
-                    } else {
-                        throw new Exception("No compatible communication method {" + computeResource.getCommunicationType() + "} not found for compute resource " + computeResource.getName());
-                    }
-
-                    ExecutionResult executionResult = operations.executeCommand(command);
-                    if (executionResult.getExitStatus() == 0) {
-                        publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.COMPLETED);
-                    } else if (executionResult.getExitStatus() == -1) {
-                        publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.FAILED, "Process didn't exit successfully");
-                    } else {
-                        publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.FAILED, "Process exited with error status " + executionResult.getExitStatus());
-                    }
-
-                } catch (Exception e) {
-                    e.printStackTrace();
-                    publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.FAILED, e.getMessage());
-
-                }
-            });
-        } catch (Exception e) {
-            e.printStackTrace();
-            publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.FAILED, e.getMessage());
-        }
-    }
-
-    public void publishTaskStatus(long processId, long taskId, int status) {
-        publishTaskStatus(processId, taskId, status, "");
-    }
-
-    public void publishTaskStatus(long processId, long taskId, int status, String reason) {
-        this.kafkaSender.send(this.taskEventPublishTopic, processId + "-" + taskId,
-                processId + "," + taskId + "," + status + "," + reason);
-    }
-}
diff --git a/airavata-kubernetes/modules/microservices/tasks/env-setup-task/src/main/resources/application.properties b/airavata-kubernetes/modules/microservices/tasks/env-setup-task/src/main/resources/application.properties
deleted file mode 100644
index 0ac80de..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/env-setup-task/src/main/resources/application.properties
+++ /dev/null
@@ -1,5 +0,0 @@
-server.port = 8392
-api.server.url = api-server.default.svc.cluster.local:8080
-task.group.name = env-setup
-task.event.topic.name = airavata-task-event
-task.read.topic.name = airavata-task-env-setup
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/microservices/tasks/ingress-staging-task/src/main/java/org/apache/airavata/k8s/task/ingress/messaging/KafkaReceiver.java b/airavata-kubernetes/modules/microservices/tasks/ingress-staging-task/src/main/java/org/apache/airavata/k8s/task/ingress/messaging/KafkaReceiver.java
deleted file mode 100644
index 912add3..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/ingress-staging-task/src/main/java/org/apache/airavata/k8s/task/ingress/messaging/KafkaReceiver.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/**
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.airavata.k8s.task.ingress.messaging;
-
-import org.apache.airavata.k8s.task.ingress.service.TaskExecutionService;
-import org.springframework.kafka.annotation.KafkaListener;
-import org.springframework.kafka.support.Acknowledgment;
-
-import javax.annotation.Resource;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-public class KafkaReceiver {
-
-    @Resource
-    private TaskExecutionService taskExecutionService;
-
-    @KafkaListener(topics = "${task.read.topic.name}")
-    public void receiveTasks(String payload, Acknowledgment ack) {
-        System.out.println("received task=" + payload);
-        taskExecutionService.executeTaskAsync(Long.parseLong(payload));
-        ack.acknowledge();
-    }
-}
diff --git a/airavata-kubernetes/modules/microservices/tasks/ingress-staging-task/src/main/java/org/apache/airavata/k8s/task/ingress/messaging/KafkaSender.java b/airavata-kubernetes/modules/microservices/tasks/ingress-staging-task/src/main/java/org/apache/airavata/k8s/task/ingress/messaging/KafkaSender.java
deleted file mode 100644
index bf0b7c1..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/ingress-staging-task/src/main/java/org/apache/airavata/k8s/task/ingress/messaging/KafkaSender.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/**
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.airavata.k8s.task.ingress.messaging;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.kafka.core.KafkaTemplate;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-public class KafkaSender {
-
-    @Autowired
-    private KafkaTemplate<String, String> kafkaTemplate;
-
-    public void send(String topic, String payload) {
-        kafkaTemplate.send(topic, payload);
-    }
-
-    public void send(String topic, String key, String payload) {
-        kafkaTemplate.send(topic, key, payload);
-    }
-}
diff --git a/airavata-kubernetes/modules/microservices/tasks/ingress-staging-task/src/main/java/org/apache/airavata/k8s/task/ingress/messaging/ReceiverConfig.java b/airavata-kubernetes/modules/microservices/tasks/ingress-staging-task/src/main/java/org/apache/airavata/k8s/task/ingress/messaging/ReceiverConfig.java
deleted file mode 100644
index cfbb3f9..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/ingress-staging-task/src/main/java/org/apache/airavata/k8s/task/ingress/messaging/ReceiverConfig.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/**
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.airavata.k8s.task.ingress.messaging;
-
-import org.apache.kafka.clients.consumer.ConsumerConfig;
-import org.apache.kafka.common.serialization.StringDeserializer;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.kafka.annotation.EnableKafka;
-import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
-import org.springframework.kafka.config.KafkaListenerContainerFactory;
-import org.springframework.kafka.core.ConsumerFactory;
-import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
-import org.springframework.kafka.listener.AbstractMessageListenerContainer;
-import org.springframework.kafka.listener.ConcurrentMessageListenerContainer;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-@Configuration
-@EnableKafka
-public class ReceiverConfig {
-
-    @Value("${kafka.bootstrap-servers}")
-    private String bootstrapServers;
-
-    @Value("${task.group.name}")
-    private String taskGroupName;
-
-    @Bean
-    public Map<String, Object> consumerConfigs() {
-        Map<String, Object> props = new HashMap<>();
-        // list of host:port pairs used for establishing the initial connections to the Kakfa cluster
-        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
-        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
-        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
-        // allows a pool of processes to divide the work of consuming and processing records
-        props.put(ConsumerConfig.GROUP_ID_CONFIG, taskGroupName);
-        props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
-        return props;
-    }
-
-    @Bean
-    public ConsumerFactory<String, String> consumerFactory() {
-        return new DefaultKafkaConsumerFactory<String, String>(consumerConfigs());
-    }
-
-    @Bean
-    public KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<String, String>> kafkaListenerContainerFactory() {
-        ConcurrentKafkaListenerContainerFactory<String, String> factory =
-                new ConcurrentKafkaListenerContainerFactory<>();
-        factory.setConsumerFactory(consumerFactory());
-        factory.getContainerProperties().setAckMode(AbstractMessageListenerContainer.AckMode.MANUAL_IMMEDIATE);
-        return factory;
-    }
-
-    @Bean
-    public KafkaReceiver receiver() {
-        return new KafkaReceiver();
-    }
-}
diff --git a/airavata-kubernetes/modules/microservices/tasks/ingress-staging-task/src/main/java/org/apache/airavata/k8s/task/ingress/messaging/SenderConfig.java b/airavata-kubernetes/modules/microservices/tasks/ingress-staging-task/src/main/java/org/apache/airavata/k8s/task/ingress/messaging/SenderConfig.java
deleted file mode 100644
index 8a9efbb..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/ingress-staging-task/src/main/java/org/apache/airavata/k8s/task/ingress/messaging/SenderConfig.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/**
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.airavata.k8s.task.ingress.messaging;
-
-import org.apache.kafka.clients.producer.ProducerConfig;
-import org.apache.kafka.common.serialization.StringSerializer;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.kafka.core.DefaultKafkaProducerFactory;
-import org.springframework.kafka.core.KafkaTemplate;
-import org.springframework.kafka.core.ProducerFactory;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-@Configuration
-public class SenderConfig {
-    @Value("${kafka.bootstrap-servers}")
-    private String bootstrapServers;
-
-    @Bean
-    public Map<String, Object> producerConfigs() {
-        Map<String, Object> props = new HashMap<>();
-        // list of host:port pairs used for establishing the initial connections to the Kakfa cluster
-        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
-        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
-        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
-        return props;
-    }
-
-    @Bean
-    public ProducerFactory<String, String> producerFactory() {
-        return new DefaultKafkaProducerFactory<String, String>(producerConfigs());
-    }
-
-    @Bean
-    public KafkaTemplate<String, String> kafkaTemplate() {
-        return new KafkaTemplate<>(producerFactory());
-    }
-
-    @Bean
-    public KafkaSender kafkaSender() {
-        return new KafkaSender();
-    }
-}
diff --git a/airavata-kubernetes/modules/microservices/tasks/ingress-staging-task/src/main/java/org/apache/airavata/k8s/task/ingress/service/TaskExecutionService.java b/airavata-kubernetes/modules/microservices/tasks/ingress-staging-task/src/main/java/org/apache/airavata/k8s/task/ingress/service/TaskExecutionService.java
deleted file mode 100644
index 2fafa81..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/ingress-staging-task/src/main/java/org/apache/airavata/k8s/task/ingress/service/TaskExecutionService.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/**
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.airavata.k8s.task.ingress.service;
-
-import org.apache.airavata.k8s.api.resources.task.TaskParamResource;
-import org.apache.airavata.k8s.api.resources.task.TaskResource;
-import org.apache.airavata.k8s.api.resources.task.TaskStatusResource;
-import org.apache.airavata.k8s.compute.api.ComputeOperations;
-import org.apache.airavata.k8s.compute.impl.MockComputeOperation;
-import org.apache.airavata.k8s.task.ingress.messaging.KafkaSender;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.core.io.ClassPathResource;
-import org.springframework.core.io.FileSystemResource;
-import org.springframework.http.*;
-import org.springframework.stereotype.Service;
-import org.springframework.util.LinkedMultiValueMap;
-import org.springframework.web.client.RestTemplate;
-
-import java.io.File;
-import java.util.Optional;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-@Service
-public class TaskExecutionService {
-
-    private final ExecutorService executorService = Executors.newFixedThreadPool(10);
-
-    private final RestTemplate restTemplate;
-    private final KafkaSender kafkaSender;
-
-    @Value("${api.server.url}")
-    private String apiServerUrl;
-
-    @Value("${task.event.topic.name}")
-    private String taskEventPublishTopic;
-
-    public TaskExecutionService(RestTemplate restTemplate, KafkaSender kafkaSender) {
-        this.restTemplate = restTemplate;
-        this.kafkaSender = kafkaSender;
-    }
-
-    public void executeTaskAsync(long taskId) {
-
-        System.out.println("Executing task " + taskId + " as ingress task");
-        TaskResource taskResource = this.restTemplate.getForObject("http://" + apiServerUrl + "/task/" + taskId, TaskResource.class);
-
-        publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.SCHEDULED);
-
-        this.executorService.execute(new Runnable() {
-            @Override
-            public void run() {
-                executeTask(taskResource);
-            }
-        });
-    }
-
-    public void executeTask(TaskResource taskResource) {
-
-        Optional<TaskParamResource> sourceParam = taskResource.getTaskParams()
-                .stream()
-                .filter(taskParamResource -> "source".equals(taskParamResource.getKey()))
-                .findFirst();
-
-        Optional<TaskParamResource> targetParam = taskResource.getTaskParams()
-                .stream()
-                .filter(taskParamResource -> "target".equals(taskParamResource.getKey()))
-                .findFirst();
-
-        Optional<TaskParamResource> computeName = taskResource.getTaskParams()
-                .stream()
-                .filter(taskParamResource -> "compute-name".equals(taskParamResource.getKey()))
-                .findFirst();
-
-        if (sourceParam.isPresent()) {
-            if (targetParam.isPresent()) {
-                publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.EXECUTING);
-                ComputeOperations computeOperations = new MockComputeOperation(computeName.get().getValue());
-
-                try {
-                    computeOperations.transferDataIn(sourceParam.get().getValue(), targetParam.get().getValue(), "SCP");
-                    publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.COMPLETED);
-
-                } catch (Exception e) {
-
-                    e.printStackTrace();
-                    publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.FAILED);
-                }
-            } else {
-                System.out.println("Source can not be null for task " + taskResource.getId());
-                publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.FAILED);
-            }
-        } else {
-            System.out.println("Source can not be null for task " + taskResource.getId());
-            publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.FAILED);
-        }
-    }
-
-    public void publishTaskStatus(long processId, long taskId, int status) {
-        this.kafkaSender.send(this.taskEventPublishTopic, processId + "-" + taskId,
-                processId + "," + taskId + "," + status);
-    }
-}
diff --git a/airavata-kubernetes/modules/microservices/tasks/job-submission-task/src/main/java/org/apache/airavata/k8s/task/job/messaging/ReceiverConfig.java b/airavata-kubernetes/modules/microservices/tasks/job-submission-task/src/main/java/org/apache/airavata/k8s/task/job/messaging/ReceiverConfig.java
deleted file mode 100644
index 9a69ecf..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/job-submission-task/src/main/java/org/apache/airavata/k8s/task/job/messaging/ReceiverConfig.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/**
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.airavata.k8s.task.job.messaging;
-
-import org.apache.kafka.clients.consumer.ConsumerConfig;
-import org.apache.kafka.common.serialization.StringDeserializer;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.kafka.annotation.EnableKafka;
-import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
-import org.springframework.kafka.config.KafkaListenerContainerFactory;
-import org.springframework.kafka.core.ConsumerFactory;
-import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
-import org.springframework.kafka.listener.AbstractMessageListenerContainer;
-import org.springframework.kafka.listener.ConcurrentMessageListenerContainer;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-@Configuration
-@EnableKafka
-public class ReceiverConfig {
-
-    @Value("${kafka.bootstrap-servers}")
-    private String bootstrapServers;
-
-    @Value("${task.group.name}")
-    private String taskGroupName;
-
-    @Bean
-    public Map<String, Object> consumerConfigs() {
-        Map<String, Object> props = new HashMap<>();
-        // list of host:port pairs used for establishing the initial connections to the Kakfa cluster
-        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
-        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
-        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
-        // allows a pool of processes to divide the work of consuming and processing records
-        props.put(ConsumerConfig.GROUP_ID_CONFIG, taskGroupName);
-        props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
-        return props;
-    }
-
-    @Bean
-    public ConsumerFactory<String, String> consumerFactory() {
-        return new DefaultKafkaConsumerFactory<String, String>(consumerConfigs());
-    }
-
-    @Bean
-    public KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<String, String>> kafkaListenerContainerFactory() {
-        ConcurrentKafkaListenerContainerFactory<String, String> factory =
-                new ConcurrentKafkaListenerContainerFactory<>();
-        factory.setConsumerFactory(consumerFactory());
-        factory.getContainerProperties().setAckMode(AbstractMessageListenerContainer.AckMode.MANUAL_IMMEDIATE);
-        return factory;
-    }
-
-    @Bean
-    public KafkaReceiver receiver() {
-        return new KafkaReceiver();
-    }
-}
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/microservices/tasks/job-submission-task/src/main/java/org/apache/airavata/k8s/task/job/service/TaskExecutionService.java b/airavata-kubernetes/modules/microservices/tasks/job-submission-task/src/main/java/org/apache/airavata/k8s/task/job/service/TaskExecutionService.java
deleted file mode 100644
index dfedbd9..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/job-submission-task/src/main/java/org/apache/airavata/k8s/task/job/service/TaskExecutionService.java
+++ /dev/null
@@ -1,160 +0,0 @@
-/**
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.airavata.k8s.task.job.service;
-
-import org.apache.airavata.k8s.api.resources.compute.ComputeResource;
-import org.apache.airavata.k8s.api.resources.task.TaskParamResource;
-import org.apache.airavata.k8s.api.resources.task.TaskResource;
-import org.apache.airavata.k8s.api.resources.task.TaskStatusResource;
-import org.apache.airavata.k8s.compute.api.ComputeOperations;
-import org.apache.airavata.k8s.compute.api.ExecutionResult;
-import org.apache.airavata.k8s.compute.impl.MockComputeOperation;
-import org.apache.airavata.k8s.compute.impl.SSHComputeOperations;
-import org.apache.airavata.k8s.task.job.messaging.KafkaSender;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.stereotype.Service;
-import org.springframework.web.client.RestTemplate;
-
-import java.util.Optional;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-@Service
-public class TaskExecutionService {
-
-    private final ExecutorService executorService = Executors.newFixedThreadPool(10);
-
-    private final RestTemplate restTemplate;
-    private final KafkaSender kafkaSender;
-
-    @Value("${api.server.url}")
-    private String apiServerUrl;
-
-    @Value("${task.event.topic.name}")
-    private String taskEventPublishTopic;
-
-    public TaskExecutionService(RestTemplate restTemplate, KafkaSender kafkaSender) {
-        this.restTemplate = restTemplate;
-        this.kafkaSender = kafkaSender;
-    }
-
-    public void executeTaskAsync(long taskId) {
-
-        System.out.println("Executing task " + taskId + " as job submission task");
-        TaskResource taskResource = this.restTemplate.getForObject("http://" + apiServerUrl + "/task/" + taskId, TaskResource.class);
-
-        publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.SCHEDULED);
-
-        this.executorService.execute(new Runnable() {
-            @Override
-            public void run() {
-                executeTask(taskResource);
-            }
-        });
-    }
-
-    private void executeTask(TaskResource taskResource) {
-
-        try {
-            Optional<TaskParamResource> commandParam = taskResource.getTaskParams()
-                    .stream()
-                    .filter(taskParamResource -> "command".equals(taskParamResource.getKey()))
-                    .findFirst();
-            Optional<TaskParamResource> argumentsParam = taskResource.getTaskParams()
-                    .stream()
-                    .filter(taskParamResource -> "arguments".equals(taskParamResource.getKey()))
-                    .findFirst();
-            Optional<TaskParamResource> computeId = taskResource.getTaskParams()
-                    .stream()
-                    .filter(taskParamResource -> "compute-id".equals(taskParamResource.getKey()))
-                    .findFirst();
-            Optional<TaskParamResource> experimentDataDir = taskResource.getTaskParams()
-                    .stream()
-                    .filter(taskParamResource -> "exp-data-dir".equals(taskParamResource.getKey()))
-                    .findFirst();
-
-            String processDataDirectory = experimentDataDir
-                    .orElseThrow(() -> new Exception("exp-data-dir param can not be found the tas params of task " +
-                            taskResource.getId())).getValue() + "/" + taskResource.getParentProcessId();
-
-
-            commandParam.ifPresent(taskParamResource -> {
-                try {
-                    String command = taskParamResource.getValue();
-                    command = command.replace("{process-data-dir}", processDataDirectory);
-                    System.out.println("Executing command " + command);
-
-                    argumentsParam.ifPresent(taskArgParamResource -> {
-                        taskArgParamResource.setValue(taskArgParamResource.getValue()
-                                .replace("{process-data-dir}", processDataDirectory));
-                        System.out.println("With arguments " + taskArgParamResource.getValue());
-                    });
-
-                    publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.EXECUTING);
-
-                    ComputeResource computeResource = this.restTemplate.getForObject("http://" + this.apiServerUrl
-                            + "/compute/" + Long.parseLong(computeId.get().getValue()), ComputeResource.class);
-
-                    ComputeOperations operations;
-                    if ("SSH".equals(computeResource.getCommunicationType())) {
-                        operations = new SSHComputeOperations(computeResource.getHost(), computeResource.getUserName(), computeResource.getPassword());
-                    } else if ("Mock".equals(computeResource.getCommunicationType())) {
-                        operations = new MockComputeOperation(computeResource.getHost());
-                    } else {
-                        throw new Exception("No compatible communication method {" + computeResource.getCommunicationType() + "} not found for compute resource " + computeResource.getName());
-                    }
-
-                    ExecutionResult executionResult = operations.executeCommand(command +
-                            (argumentsParam.isPresent() ? argumentsParam.get().getValue() : ""));
-
-                    if (executionResult.getExitStatus() == 0) {
-                        publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.COMPLETED);
-                    } else if (executionResult.getExitStatus() == -1) {
-                        publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.FAILED, "Process didn't exit successfully");
-                    } else {
-                        publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.FAILED, "Process exited with error status " + executionResult.getExitStatus());
-                    }
-
-                } catch (Exception e) {
-                    e.printStackTrace();
-                    publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.FAILED, e.getMessage());
-                }
-            });
-        } catch (Exception e) {
-            e.printStackTrace();
-            publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.FAILED, e.getMessage());
-        }
-    }
-
-    public void publishTaskStatus(long processId, long taskId, int status) {
-        publishTaskStatus(processId, taskId, status, "");
-    }
-
-    public void publishTaskStatus(long processId, long taskId, int status, String reason) {
-        this.kafkaSender.send(this.taskEventPublishTopic, processId + "-" + taskId,
-                processId + "," + taskId + "," + status + "," + reason);
-    }
-}
diff --git a/airavata-kubernetes/modules/microservices/workflow-generator/src/main/java/org/apache/airavata/k8s/orchestrator/service/ExperimentLaunchService.java b/airavata-kubernetes/modules/microservices/workflow-generator/src/main/java/org/apache/airavata/k8s/orchestrator/service/ExperimentLaunchService.java
index b67eda8..ee5e591 100644
--- a/airavata-kubernetes/modules/microservices/workflow-generator/src/main/java/org/apache/airavata/k8s/orchestrator/service/ExperimentLaunchService.java
+++ b/airavata-kubernetes/modules/microservices/workflow-generator/src/main/java/org/apache/airavata/k8s/orchestrator/service/ExperimentLaunchService.java
@@ -102,7 +102,7 @@ public class ExperimentLaunchService {
 
         List<TaskResource> taskDag = new ArrayList<>();
 
-        AtomicInteger dagOrder = new AtomicInteger(0);
+        /*AtomicInteger dagOrder = new AtomicInteger(0);
 
         TaskResource dataDirTaskReasource = new TaskResource();
         dataDirTaskReasource.setTaskType(TaskResource.TaskTypes.ENV_SETUP);
@@ -114,7 +114,7 @@ public class ExperimentLaunchService {
                 new TaskParamResource().setKey("compute-id").setValue(computeResource.getId() + ""),
                 new TaskParamResource().setKey("compute-name").setValue(computeResource.getName() + "")));
 
-        dataDirTaskReasource.setOrder(dagOrder.incrementAndGet());
+        dataDirTaskReasource.setReferenceId(dagOrder.incrementAndGet());
         taskDag.add(dataDirTaskReasource);
 
         Optional.ofNullable(appDepRes.getPreJobCommand()).ifPresent(preJob -> {
@@ -127,7 +127,7 @@ public class ExperimentLaunchService {
                     new TaskParamResource().setKey("command").setValue(preJob),
                     new TaskParamResource().setKey("compute-id").setValue(computeResource.getId() + ""),
                     new TaskParamResource().setKey("compute-name").setValue(computeResource.getName() + "")));
-            resource.setOrder(dagOrder.incrementAndGet());
+            resource.setReferenceId(dagOrder.incrementAndGet());
             taskDag.add(resource);
         });
 
@@ -150,7 +150,7 @@ public class ExperimentLaunchService {
                             new TaskParamResource().setKey("target").setValue(localPath),
                             new TaskParamResource().setKey("compute-id").setValue(computeResource.getId() + ""),
                             new TaskParamResource().setKey("compute-name").setValue(computeResource.getName() + "")));
-                    resource.setOrder(dagOrder.incrementAndGet());
+                    resource.setReferenceId(dagOrder.incrementAndGet());
 
                     inputArgument.append(" ");
                     if (expInp.getArguments() != null && !expInp.getArguments().isEmpty()) {
@@ -189,7 +189,7 @@ public class ExperimentLaunchService {
                     new TaskParamResource().setKey("arguments").setValue(inputArgument.toString()),
                     new TaskParamResource().setKey("compute-id").setValue(computeResource.getId() + ""),
                     new TaskParamResource().setKey("compute-name").setValue(computeResource.getName() + "")));
-            resource.setOrder(dagOrder.incrementAndGet());
+            resource.setReferenceId(dagOrder.incrementAndGet());
             taskDag.add(resource);
         });
 
@@ -205,7 +205,7 @@ public class ExperimentLaunchService {
                         new TaskParamResource().setKey("target").setValue(expOut.getId() + ""),
                         new TaskParamResource().setKey("compute-id").setValue(computeResource.getId() + ""),
                         new TaskParamResource().setKey("compute-name").setValue(computeResource.getName() + "")));
-                resource.setOrder(dagOrder.incrementAndGet());
+                resource.setReferenceId(dagOrder.incrementAndGet());
                 taskDag.add(resource);
             }
 
@@ -220,7 +220,7 @@ public class ExperimentLaunchService {
                         new TaskParamResource().setKey("target").setValue(expOut.getId() + ""),
                         new TaskParamResource().setKey("compute-id").setValue(computeResource.getId() + ""),
                         new TaskParamResource().setKey("compute-name").setValue(computeResource.getName() + "")));
-                resource.setOrder(dagOrder.incrementAndGet());
+                resource.setReferenceId(dagOrder.incrementAndGet());
                 taskDag.add(resource);
             }
 
@@ -235,7 +235,7 @@ public class ExperimentLaunchService {
                         new TaskParamResource().setKey("target").setValue(expOut.getId() + ""),
                         new TaskParamResource().setKey("compute-id").setValue(computeResource.getId() + ""),
                         new TaskParamResource().setKey("compute-name").setValue(computeResource.getName() + "")));
-                resource.setOrder(dagOrder.incrementAndGet());
+                resource.setReferenceId(dagOrder.incrementAndGet());
                 taskDag.add(resource);
             }
         }));
@@ -250,9 +250,9 @@ public class ExperimentLaunchService {
                     new TaskParamResource().setKey("command").setValue(postJob),
                     new TaskParamResource().setKey("compute-id").setValue(computeResource.getId() + ""),
                     new TaskParamResource().setKey("compute-name").setValue(computeResource.getName() + "")));
-            resource.setOrder(dagOrder.incrementAndGet());
+            resource.setReferenceId(dagOrder.incrementAndGet());
             taskDag.add(resource);
-        });
+        });*/
 
         return taskDag;
     }
diff --git a/airavata-kubernetes/modules/task-api/pom.xml b/airavata-kubernetes/modules/task-api/pom.xml
new file mode 100644
index 0000000..d2621fc
--- /dev/null
+++ b/airavata-kubernetes/modules/task-api/pom.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>airavata-kubernetes</artifactId>
+        <groupId>org.apache.airavata</groupId>
+        <version>1.0-SNAPSHOT</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>task-api</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.airavata</groupId>
+            <artifactId>api-resource</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.airavata</groupId>
+            <artifactId>compute-resource-api</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-freemarker</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.kafka</groupId>
+            <artifactId>spring-kafka</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.5.1</version>
+                <configuration>
+                    <source>${java.version}</source>
+                    <target>${java.version}</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/AbstractTaskExecutionService.java b/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/AbstractTaskExecutionService.java
new file mode 100644
index 0000000..1f958a7
--- /dev/null
+++ b/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/AbstractTaskExecutionService.java
@@ -0,0 +1,123 @@
+package org.apache.airavata.k8s.task.api;
+
+import org.apache.airavata.k8s.api.resources.compute.ComputeResource;
+import org.apache.airavata.k8s.api.resources.task.TaskInputResource;
+import org.apache.airavata.k8s.api.resources.task.TaskResource;
+import org.apache.airavata.k8s.api.resources.task.TaskStatusResource;
+import org.apache.airavata.k8s.api.resources.task.type.TaskTypeResource;
+import org.apache.airavata.k8s.compute.api.ComputeOperations;
+import org.apache.airavata.k8s.compute.impl.MockComputeOperation;
+import org.apache.airavata.k8s.compute.impl.SSHComputeOperations;
+import org.apache.airavata.k8s.task.api.messaging.KafkaSender;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.web.client.RestTemplate;
+
+import javax.annotation.PostConstruct;
+import java.util.Optional;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public abstract class AbstractTaskExecutionService {
+
+    private final ExecutorService executorService;
+
+    private final RestTemplate restTemplate;
+    private final KafkaSender kafkaSender;
+
+    @Value("${api.server.url}")
+    private String apiServerUrl;
+
+    @Value("${task.event.topic.name}")
+    private String taskEventPublishTopic;
+
+    public AbstractTaskExecutionService(RestTemplate restTemplate, KafkaSender kafkaSender, int concurrentTasks) {
+        this.restTemplate = restTemplate;
+        this.kafkaSender = kafkaSender;
+        executorService = Executors.newFixedThreadPool(concurrentTasks);
+    }
+
+    @PostConstruct
+    public void init() {
+        getRestTemplate().postForObject("http://" + apiServerUrl + "/taskType", getType(), Long.class);
+    }
+
+    public abstract TaskTypeResource getType();
+
+    public void executeTaskAsync(TaskContext taskContext) {
+
+        System.out.println("Executing task " + taskContext.getTaskId());
+        TaskResource taskResource = this.restTemplate.getForObject("http://" + apiServerUrl + "/task/" + taskContext.getTaskId(), TaskResource.class);
+
+        publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.SCHEDULED);
+
+        this.executorService.execute(() -> {
+            try {
+                initializeParameters(taskResource, taskContext);
+                executeTask(taskResource, taskContext);
+            } catch (Exception e) {
+                e.printStackTrace();
+                // Ignore silently as this is already handled
+                // TODO add a new exception type
+            }
+        });
+    }
+
+    public ComputeOperations fetchComputeResourceOperation(ComputeResource computeResource) throws Exception {
+        ComputeOperations operations;
+        if ("SSH".equals(computeResource.getCommunicationType())) {
+            operations = new SSHComputeOperations(computeResource.getHost(), computeResource.getUserName(), computeResource.getPassword());
+        } else if ("Mock".equals(computeResource.getCommunicationType())) {
+            operations = new MockComputeOperation(computeResource.getHost());
+        } else {
+            throw new Exception("No compatible communication method {" + computeResource.getCommunicationType() + "} not found for compute resource " + computeResource.getName());
+        }
+        return operations;
+    }
+
+    public String findInput(TaskResource taskResource, String name, boolean optional) throws Exception {
+
+        Optional<TaskInputResource> inputResource = taskResource.getInputs()
+                .stream()
+                .filter(input -> name.equals(input.getValue()))
+                .findFirst();
+
+        if (inputResource.isPresent()) {
+            return inputResource.get().getValue();
+
+        } else {
+            if (!optional) {
+                publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.FAILED,
+                        name + " is not available in inputs");
+                throw new Exception(name + " is not available in inputs");
+            } else {
+                return null;
+            }
+        }
+    }
+
+    public abstract void initializeParameters(TaskResource taskResource, TaskContext taskContext) throws Exception;
+    public abstract void executeTask(TaskResource taskResource, TaskContext taskContext);
+
+    public void publishTaskStatus(long processId, long taskId, int status) {
+        publishTaskStatus(processId, taskId, status, "");
+    }
+
+    public void publishTaskStatus(long processId, long taskId, int status, String reason) {
+        this.kafkaSender.send(this.taskEventPublishTopic, processId + "-" + taskId,
+                processId + "," + taskId + "," + status + "," + reason);
+    }
+
+    public RestTemplate getRestTemplate() {
+        return restTemplate;
+    }
+
+    public String getApiServerUrl() {
+        return apiServerUrl;
+    }
+}
diff --git a/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/TaskContext.java b/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/TaskContext.java
new file mode 100644
index 0000000..2fdf8af
--- /dev/null
+++ b/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/TaskContext.java
@@ -0,0 +1,54 @@
+package org.apache.airavata.k8s.task.api;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public class TaskContext {
+
+    private long taskId;
+    private Map<String, String> contextVariableParams = new HashMap<>();
+    private Map<String, String> contextDataParams = new HashMap<>();
+    private Map<String, Object> localContext = new HashMap<>();
+
+    public long getTaskId() {
+        return taskId;
+    }
+
+    public TaskContext setTaskId(long taskId) {
+        this.taskId = taskId;
+        return this;
+    }
+
+    public Map<String, String> getContextVariableParams() {
+        return contextVariableParams;
+    }
+
+    public TaskContext setContextVariableParams(Map<String, String> contextVariableParams) {
+        this.contextVariableParams = contextVariableParams;
+        return this;
+    }
+
+    public Map<String, String> getContextDataParams() {
+        return contextDataParams;
+    }
+
+    public TaskContext setContextDataParams(Map<String, String> contextDataParams) {
+        this.contextDataParams = contextDataParams;
+        return this;
+    }
+
+    public Map<String, Object> getLocalContext() {
+        return localContext;
+    }
+
+    public TaskContext setLocalContext(Map<String, Object> localContext) {
+        this.localContext = localContext;
+        return this;
+    }
+}
diff --git a/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/TaskContextDeserializer.java b/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/TaskContextDeserializer.java
new file mode 100644
index 0000000..524ce2e
--- /dev/null
+++ b/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/TaskContextDeserializer.java
@@ -0,0 +1,29 @@
+package org.apache.airavata.k8s.task.api;
+
+import org.apache.kafka.common.serialization.Deserializer;
+
+import java.util.Map;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public class TaskContextDeserializer implements Deserializer<TaskContext> {
+
+    @Override
+    public void configure(Map<String, ?> configs, boolean isKey) {
+
+    }
+
+    @Override
+    public TaskContext deserialize(String topic, byte[] data) {
+        return null;
+    }
+
+    @Override
+    public void close() {
+
+    }
+}
diff --git a/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/TaskContextSerializer.java b/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/TaskContextSerializer.java
new file mode 100644
index 0000000..eb4c762
--- /dev/null
+++ b/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/TaskContextSerializer.java
@@ -0,0 +1,28 @@
+package org.apache.airavata.k8s.task.api;
+
+import org.apache.kafka.common.serialization.Serializer;
+
+import java.util.Map;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public class TaskContextSerializer implements Serializer<TaskContext> {
+    @Override
+    public void configure(Map<String, ?> configs, boolean isKey) {
+
+    }
+
+    @Override
+    public byte[] serialize(String topic, TaskContext data) {
+        return new byte[0];
+    }
+
+    @Override
+    public void close() {
+
+    }
+}
diff --git a/airavata-kubernetes/modules/microservices/tasks/job-submission-task/src/main/java/org/apache/airavata/k8s/task/job/messaging/KafkaReceiver.java b/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/messaging/KafkaReceiver.java
similarity index 73%
rename from airavata-kubernetes/modules/microservices/tasks/job-submission-task/src/main/java/org/apache/airavata/k8s/task/job/messaging/KafkaReceiver.java
rename to airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/messaging/KafkaReceiver.java
index 574c075..c3597dc 100644
--- a/airavata-kubernetes/modules/microservices/tasks/job-submission-task/src/main/java/org/apache/airavata/k8s/task/job/messaging/KafkaReceiver.java
+++ b/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/messaging/KafkaReceiver.java
@@ -17,9 +17,10 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.airavata.k8s.task.job.messaging;
+package org.apache.airavata.k8s.task.api.messaging;
 
-import org.apache.airavata.k8s.task.job.service.TaskExecutionService;
+import org.apache.airavata.k8s.task.api.AbstractTaskExecutionService;
+import org.apache.airavata.k8s.task.api.TaskContext;
 import org.springframework.kafka.annotation.KafkaListener;
 import org.springframework.kafka.support.Acknowledgment;
 
@@ -34,12 +35,12 @@ import javax.annotation.Resource;
 public class KafkaReceiver {
 
     @Resource
-    private TaskExecutionService taskExecutionService;
+    private AbstractTaskExecutionService taskExecutionService;
 
     @KafkaListener(topics = "${task.read.topic.name}")
-    public void receiveTasks(String payload, Acknowledgment ack) {
-        System.out.println("received task=" + payload);
-        taskExecutionService.executeTaskAsync(Long.parseLong(payload));
+    public void receiveTasks(TaskContext taskContext, Acknowledgment ack) {
+        System.out.println("received task=" + taskContext.toString());
+        taskExecutionService.executeTaskAsync(taskContext);
         ack.acknowledge();
     }
 }
diff --git a/airavata-kubernetes/modules/microservices/tasks/job-submission-task/src/main/java/org/apache/airavata/k8s/task/job/messaging/KafkaSender.java b/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/messaging/KafkaSender.java
similarity index 96%
rename from airavata-kubernetes/modules/microservices/tasks/job-submission-task/src/main/java/org/apache/airavata/k8s/task/job/messaging/KafkaSender.java
rename to airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/messaging/KafkaSender.java
index 395ada1..307215f 100644
--- a/airavata-kubernetes/modules/microservices/tasks/job-submission-task/src/main/java/org/apache/airavata/k8s/task/job/messaging/KafkaSender.java
+++ b/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/messaging/KafkaSender.java
@@ -17,7 +17,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.airavata.k8s.task.job.messaging;
+package org.apache.airavata.k8s.task.api.messaging;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.kafka.core.KafkaTemplate;
diff --git a/airavata-kubernetes/modules/microservices/tasks/egress-staging-task/src/main/java/org/apacher/airavata/k8s/task/egress/messaging/ReceiverConfig.java b/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/messaging/ReceiverConfig.java
similarity index 96%
rename from airavata-kubernetes/modules/microservices/tasks/egress-staging-task/src/main/java/org/apacher/airavata/k8s/task/egress/messaging/ReceiverConfig.java
rename to airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/messaging/ReceiverConfig.java
index bd8ba90..b078a79 100644
--- a/airavata-kubernetes/modules/microservices/tasks/egress-staging-task/src/main/java/org/apacher/airavata/k8s/task/egress/messaging/ReceiverConfig.java
+++ b/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/messaging/ReceiverConfig.java
@@ -17,12 +17,13 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apacher.airavata.k8s.task.egress.messaging;
+package org.apache.airavata.k8s.task.api.messaging;
 
 import org.apache.kafka.clients.consumer.ConsumerConfig;
 import org.apache.kafka.common.serialization.StringDeserializer;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.kafka.annotation.EnableKafka;
 import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
@@ -42,6 +43,7 @@ import java.util.Map;
  * @since 1.0.0-SNAPSHOT
  */
 @Configuration
+@ComponentScan
 @EnableKafka
 public class ReceiverConfig {
 
diff --git a/airavata-kubernetes/modules/microservices/tasks/job-submission-task/src/main/java/org/apache/airavata/k8s/task/job/messaging/SenderConfig.java b/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/messaging/SenderConfig.java
similarity index 97%
rename from airavata-kubernetes/modules/microservices/tasks/job-submission-task/src/main/java/org/apache/airavata/k8s/task/job/messaging/SenderConfig.java
rename to airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/messaging/SenderConfig.java
index 88a4890..cd8b54b 100644
--- a/airavata-kubernetes/modules/microservices/tasks/job-submission-task/src/main/java/org/apache/airavata/k8s/task/job/messaging/SenderConfig.java
+++ b/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/messaging/SenderConfig.java
@@ -17,7 +17,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.airavata.k8s.task.job.messaging;
+package org.apache.airavata.k8s.task.api.messaging;
 
 import org.apache.kafka.clients.producer.ProducerConfig;
 import org.apache.kafka.common.serialization.StringSerializer;
diff --git a/airavata-kubernetes/pom.xml b/airavata-kubernetes/pom.xml
index b0ee98f..c758f70 100644
--- a/airavata-kubernetes/pom.xml
+++ b/airavata-kubernetes/pom.xml
@@ -36,11 +36,10 @@
         <module>modules/microservices/workflow-generator</module>
         <module>modules/microservices/task-scheduler</module>
         <module>modules/microservices/event-sink</module>
-        <module>modules/microservices/tasks/ingress-staging-task</module>
-        <module>modules/microservices/tasks/env-setup-task</module>
-        <module>modules/microservices/tasks/job-submission-task</module>
-        <module>modules/microservices/tasks/env-cleanup-task</module>
-        <module>modules/microservices/tasks/egress-staging-task</module>
+        <module>modules/microservices/tasks/command-executing-task</module>
+        <module>modules/microservices/tasks/data-pushing-task</module>
+        <module>modules/microservices/tasks/data-collecting-task</module>
+        <module>modules/task-api</module>
     </modules>
 
     <dependencyManagement>
diff --git a/airavata-kubernetes/web-console/src/app/app.module.ts b/airavata-kubernetes/web-console/src/app/app.module.ts
index 76c7338..6e4d842 100644
--- a/airavata-kubernetes/web-console/src/app/app.module.ts
+++ b/airavata-kubernetes/web-console/src/app/app.module.ts
@@ -15,6 +15,8 @@ import {FormsModule} from "@angular/forms";
 import {ExperimentDetailComponent} from "./components/experiment/detail/experiment.detail";
 import {ProcessDetailComponent} from "./components/process/detail/process.detail.component";
 import {SetupComponent} from "./components/setup/setup.component";
+import {WorkflowCreateComponent} from "./components/workflow/create/create.component";
+import {WorkflowListComponent} from "./components/workflow/list/workflow.list.component";
 
 @NgModule({
   declarations: [
@@ -27,7 +29,9 @@ import {SetupComponent} from "./components/setup/setup.component";
     ExperimentListComponent,
     ExperimentDetailComponent,
     ProcessDetailComponent,
-    SetupComponent
+    SetupComponent,
+    WorkflowCreateComponent,
+    WorkflowListComponent
   ],
   imports: [
     NgbModule.forRoot(),
diff --git a/airavata-kubernetes/web-console/src/app/components/dashboard/dashboard.html b/airavata-kubernetes/web-console/src/app/components/dashboard/dashboard.html
index ba6ab8d..827ca51 100644
--- a/airavata-kubernetes/web-console/src/app/components/dashboard/dashboard.html
+++ b/airavata-kubernetes/web-console/src/app/components/dashboard/dashboard.html
@@ -34,6 +34,9 @@
       <li class="nav-item">
         <a class="nav-link" [routerLink]="['/setup']">Setup</a>
       </li>
+      <li class="nav-item">
+        <a class="nav-link" [routerLink]="['/workflow']">Workflow</a>
+      </li>
     </ul>
   </div>
 
diff --git a/airavata-kubernetes/web-console/src/app/components/dashboard/dashboard.routes.ts b/airavata-kubernetes/web-console/src/app/components/dashboard/dashboard.routes.ts
index 90b4991..c211503 100644
--- a/airavata-kubernetes/web-console/src/app/components/dashboard/dashboard.routes.ts
+++ b/airavata-kubernetes/web-console/src/app/components/dashboard/dashboard.routes.ts
@@ -8,6 +8,8 @@ import {Routes} from "@angular/router";
 import {ExperimentDetailComponent} from "../experiment/detail/experiment.detail";
 import {ProcessDetailComponent} from "../process/detail/process.detail.component";
 import {SetupComponent} from "../setup/setup.component";
+import {WorkflowCreateComponent} from "../workflow/create/create.component";
+import {WorkflowListComponent} from "../workflow/list/workflow.list.component";
 
 /**
  * Created by dimuthu on 10/29/17.
@@ -46,5 +48,13 @@ export const DASHBOARD_ROUTES: Routes = [
   {
     path: 'module',
     component: AppModuleListComponent,
+  },
+  {
+    path: 'workflow',
+    component: WorkflowListComponent,
+  },
+  {
+    path: 'workflow/create',
+    component: WorkflowCreateComponent,
   }
 ];
diff --git a/airavata-kubernetes/web-console/src/app/components/workflow/create/create.component.ts b/airavata-kubernetes/web-console/src/app/components/workflow/create/create.component.ts
new file mode 100644
index 0000000..e286c19
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/app/components/workflow/create/create.component.ts
@@ -0,0 +1,435 @@
+import {Component, ViewEncapsulation, AfterViewInit} from "@angular/core";
+import {TaskService} from "../../../services/task.service";
+import {TaskType} from "../../../models/task/type/task.type.model";
+import {OperationType} from "../../../models/task/type/operation/operation.type.model";
+import {OperationInPort} from "../../../models/task/type/operation/operation.inport.type.model";
+import {OperationOutPort} from "../../../models/task/type/operation/operation.outport.type.model";
+import {WorkflowService} from "../../../services/workflow.service";
+import {Router} from "@angular/router";
+
+declare let mxClient: any;
+declare let mxUtils: any;
+declare let mxToolbar: any;
+declare let mxDivResizer: any;
+declare let mxGraph: any;
+declare let mxDragSource: any;
+declare let mxRubberband: any;
+declare let mxKeyHandler: any;
+declare let mxCodec: any;
+declare let mxConstants: any;
+declare let mxEdgeStyle: any;
+declare let mxEvent: any;
+declare let mxForm: any;
+declare let mxCellAttributeChange: any;
+declare let mxRectangle: any;
+declare let mxPoint: any;
+
+@Component({
+  templateUrl: './create.html',
+  encapsulation: ViewEncapsulation.None,
+  providers: [TaskService, WorkflowService]
+})
+export class WorkflowCreateComponent implements AfterViewInit {
+
+  taskTypes: Array<TaskType> = [];
+  operationTypes: Array<OperationType> = [];
+  graphInstance: any;
+  workFlowName: string = "Sample Workflow";
+
+  constructor(private taskService: TaskService, private workflowService: WorkflowService, private router: Router) {
+
+    let startType: OperationType = new OperationType();
+    startType.id = 1;
+    startType.name = "Start";
+    startType.icon = "assets/icons/start.png";
+    startType.outPorts.push(new OperationOutPort(1, "Output"));
+
+    let endType: OperationType = new OperationType();
+    endType.id = 1;
+    endType.name = "Stop";
+    endType.icon = "assets/icons/stop.png";
+    endType.inPorts.push(new OperationInPort(1, "Input"));
+
+    this.operationTypes.push(startType, endType);
+  }
+
+  loadGraphData() {
+    this.taskService.getAllTaskTypes().subscribe(data => {
+      this.taskTypes = data;
+      this.loadGraph(document.getElementById('graphContainer'), document.getElementById('toolContainer'));
+    }, err => {
+
+    })
+  }
+
+  onShowWorkflowXMLClicked() {
+    let encoder = new mxCodec();
+    let node = encoder.encode(this.graphInstance.getModel());
+    mxUtils.popup(mxUtils.getPrettyXml(node), true);
+  }
+
+  onCreateWorkflowClicked() {
+    let encoder = new mxCodec();
+    let node = encoder.encode(this.graphInstance.getModel());
+    this.workflowService.createWorkflow(this.workFlowName, mxUtils.getPrettyXml(node)).subscribe(data => {
+      alert("Workflow created");
+      this.routeToListPage();
+    }, err => {
+      alert("An error occurred");
+    });
+  }
+
+  routeToListPage() {
+    this.router.navigateByUrl("/workflow");
+  }
+
+  ngAfterViewInit(): void {
+    this.loadGraphData();
+  }
+
+  loadGraph(container, tbContainer) {
+
+    if (!mxClient.isBrowserSupported()) {
+      // Displays an error message if the browser is not supported.
+      mxUtils.error('Browser is not supported!', 200, false);
+    } else {
+      let toolbar = new mxToolbar(tbContainer);
+      toolbar.enabled = false;
+
+      // Workaround for Internet Explorer ignoring certain styles
+      if (mxClient.IS_QUIRKS) {
+        document.body.style.overflow = 'hidden';
+        new mxDivResizer(tbContainer);
+        new mxDivResizer(container);
+      }
+
+      let doc = mxUtils.createXmlDocument();
+
+      let relation = doc.createElement('Edge');
+
+      let graph = new mxGraph(container);
+      this.graphInstance = graph;
+
+      graph.dropEnabled = true;
+
+      mxDragSource.prototype.getDropTarget = function (graph, x, y) {
+        let cell = graph.getCellAt(x, y);
+
+        if (!graph.isValidDropTarget(cell)) {
+          cell = null;
+        }
+
+        return cell;
+      };
+
+      // Enables new connections in the graph
+      graph.setConnectable(true);
+      graph.setMultigraph(false);
+
+      let addTaskType = function (icon, w, h, taskType: TaskType) {
+        addToolbarItem(graph, toolbar, createTaskItem(doc, taskType), icon, doc);
+      };
+
+      let addOperation = function (icon, w, h, operationType: OperationType) {
+        addToolbarItem(graph, toolbar, createOperation(doc, operationType), icon, doc);
+      };
+
+      this.taskTypes.forEach((taskType) => {
+        addTaskType(taskType.icon, 40, 40, taskType);
+      });
+
+      this.operationTypes.forEach((operationType) => {
+        addOperation(operationType.icon, 40, 40, operationType);
+      });
+
+      toolbar.addLine();
+
+      graph.setCellsResizable(false);
+      graph.setResizeContainer(true);
+      graph.minimumContainerSize = new mxRectangle(0, 0, 500, 380);
+      graph.setBorder(60);
+
+      new mxKeyHandler(graph);
+
+      // Overrides method to disallow edge label editing
+      graph.isCellEditable = function (cell) {
+        return !this.getModel().isEdge(cell);
+      };
+
+      // Overrides method to provide a cell label in the display
+      graph.convertValueToString = function (cell) {
+        if (mxUtils.isNode(cell.value)) {
+          if (cell.value.nodeName.toLowerCase() == 'processingelement') {
+            return cell.getAttribute('name', '');
+          }
+          if (cell.value.nodeName.toLowerCase() == 'operation') {
+            return cell.getAttribute('name', '');
+          }
+          else if (cell.value.nodeName.toLowerCase() == 'edge') {
+            return '';
+          }
+
+        }
+        return '';
+      };
+
+      // Overrides method to store a cell label in the model
+      let cellLabelChanged = graph.cellLabelChanged;
+      graph.cellLabelChanged = function (cell, newValue, autoSize) {
+        if (mxUtils.isNode(cell.value) &&
+          cell.value.nodeName.toLowerCase() == 'processingelement') {
+          // Clones the value for correct undo/redo
+          let elt = cell.value.cloneNode(true);
+
+          elt.setAttribute('name', newValue);
+          newValue = elt;
+          autoSize = true;
+        }
+
+        cellLabelChanged.apply(this, arguments);
+      };
+
+      // Overrides method to create the editing value
+      let getEditingValue = graph.getEditingValue;
+      graph.getEditingValue = function (cell) {
+        if (mxUtils.isNode(cell.value) &&
+          cell.value.nodeName.toLowerCase() == 'processingelement') {
+          return cell.getAttribute('name', '');
+        }
+      };
+
+      new mxRubberband(graph);
+
+      // Changes the style for match the markup
+      // Creates the default style for vertices
+      let style = graph.getStylesheet().getDefaultVertexStyle();
+      style[mxConstants.STYLE_STROKECOLOR] = 'gray';
+      style[mxConstants.STYLE_ROUNDED] = true;
+      style[mxConstants.STYLE_SHADOW] = true;
+      style[mxConstants.STYLE_FILLCOLOR] = '#DFDFDF';
+      style[mxConstants.STYLE_GRADIENTCOLOR] = 'white';
+      style[mxConstants.STYLE_FONTCOLOR] = 'black';
+      style[mxConstants.STYLE_FONTSIZE] = '12';
+      style[mxConstants.STYLE_SPACING] = 4;
+
+      // Creates the default style for edges
+      style = graph.getStylesheet().getDefaultEdgeStyle();
+      style[mxConstants.STYLE_STROKECOLOR] = '#0C0C0C';
+      style[mxConstants.STYLE_LABEL_BACKGROUNDCOLOR] = 'white';
+      style[mxConstants.STYLE_EDGE] = mxEdgeStyle.ElbowConnector;
+      style[mxConstants.STYLE_ROUNDED] = true;
+      style[mxConstants.STYLE_FONTCOLOR] = 'black';
+      style[mxConstants.STYLE_FONTSIZE] = '10';
+
+      let parent = graph.getDefaultParent();
+      graph.getModel().beginUpdate();
+      try {
+        //var v1 = graph.insertVertex(parent, null, pe1, 40, 40, 80, 30);
+        //var v2 = graph.insertVertex(parent, null, pe2, 200, 150, 80, 30);
+        //var e1 = graph.insertEdge(parent, null, relation, v1, v2);
+      } finally {
+        // Updates the display
+        graph.getModel().endUpdate();
+      }
+
+      // Implements a properties panel that uses
+      // mxCellAttributeChange to change properties
+      graph.getSelectionModel().addListener(mxEvent.CHANGE, function (sender, evt) {
+        selectionChanged(graph);
+      });
+
+      selectionChanged(graph);
+
+    }
+
+    /**
+     * Updates the properties panel
+     */
+    function selectionChanged(graph) {
+      let div = document.getElementById('properties');
+
+      // Forces focusout in IE
+      graph.container.focus();
+
+      // Clears the DIV the non-DOM way
+      div.innerHTML = '';
+
+      // Gets the selection cell
+      let cell = graph.getSelectionCell();
+
+      if (cell == null) {
+        mxUtils.writeln(div, 'Nothing selected.');
+      }
+      else if (cell.value) {
+        // Writes the title
+        let center = document.createElement('center');
+        mxUtils.writeln(center, cell.value.nodeName + ' (' + cell.id + ')');
+        div.appendChild(center);
+        mxUtils.br(div);
+
+        // Creates the form from the attributes of the user object
+        let form = new mxForm();
+
+        let attrs = cell.value.attributes;
+
+        for (let i = 0; i < attrs.length; i++) {
+          if (!attrs[i].nodeName.startsWith("in-") && !attrs[i].nodeName.startsWith("out-")) {
+            createTextField(graph, form, cell, attrs[i]);
+          }
+        }
+
+        div.appendChild(form.getTable());
+        mxUtils.br(div);
+      }
+    }
+
+    function createTaskItem(doc, taskType: TaskType) {
+
+      var pe = doc.createElement('ProcessingElement');
+      pe.setAttribute('name', taskType.name);
+      pe.setAttribute('Type', taskType.id);
+
+      taskType.inputTypes.forEach((input, index) => {
+        pe.setAttribute(input.name, input.defaultValue);
+      });
+
+      taskType.outputTypes.forEach((output, index) => {
+        pe.setAttribute('output-' + output.name, '');
+      });
+
+      taskType.outPorts.forEach((outPort, index) => {
+        pe.setAttribute("out-" + outPort.id, outPort.name);
+      });
+
+      pe.setAttribute("in-1" , "Input");
+
+      return pe;
+    }
+
+    function createOperation(doc, operation: OperationType) {
+      var op = doc.createElement('Operation');
+      op.setAttribute("name", operation.name);
+      operation.outPorts.forEach((outPort, index) => {
+        op.setAttribute("out-" + outPort.id, outPort.name);
+      });
+      operation.inPorts.forEach((inPort, index) => {
+        op.setAttribute("in-" + inPort.id, inPort.name);
+      });
+      return op;
+    }
+
+    function createTextField(graph, form, cell, attribute) {
+      let input = form.addText(attribute.nodeName + ':', attribute.nodeValue);
+
+      let applyHandler = function () {
+        let newValue = input.value || '';
+        let oldValue = cell.getAttribute(attribute.nodeName, '');
+
+        if (newValue != oldValue) {
+          graph.getModel().beginUpdate();
+
+          try {
+            let edit = new mxCellAttributeChange(
+              cell, attribute.nodeName,
+              newValue);
+            graph.getModel().execute(edit);
+            graph.updateCellSize(cell);
+          }
+          finally {
+            graph.getModel().endUpdate();
+          }
+        }
+      };
+
+      mxEvent.addListener(input, 'keypress', function (evt) {
+        // Needs to take shift into account for textareas
+        if (evt.keyCode == /*enter*/13 && !mxEvent.isShiftDown(evt)) {
+          input.blur();
+        }
+      });
+
+      if (mxClient.IS_IE) {
+        mxEvent.addListener(input, 'focusout', applyHandler);
+      }
+      else {
+        // Note: Known problem is the blurring of fields in
+        // Firefox by changing the selection, in which case
+        // no event is fired in FF and the change is lost.
+        // As a workaround you should use a local variable
+        // that stores the focused field and invoke blur
+        // explicitely where we do the graph.focus above.
+        mxEvent.addListener(input, 'blur', applyHandler);
+      }
+    }
+
+    function addToolbarItem(graph, toolbar, prototype, image, doc) {
+      // Function that is executed when the image is dropped on
+      // the graph. The cell argument points to the cell under
+      // the mousepointer if there is one.
+      let funct = function (graph, evt, cell, x, y) {
+
+        let parent = graph.getDefaultParent();
+        let model = graph.getModel();
+
+        let v1 = null;
+
+        model.beginUpdate();
+
+        try {
+          v1 = graph.insertVertex(parent, null, prototype.cloneNode(true), x, y, 80, 60);
+          v1.setConnectable(false);
+
+          let inputs = [];
+          let inputKeys = [];
+          let outputs = [];
+          let outputKeys = [];
+          for (let i = 0; i < prototype.attributes.length; i++) {
+            let attr = prototype.attributes[i];
+            if (attr.nodeName.startsWith("in-")) {
+              inputs.push(attr.nodeValue);
+              inputKeys.push(attr.nodeName)
+            }
+            if (attr.nodeName.startsWith("out-")) {
+              outputs.push(attr.nodeValue);
+              outputKeys.push(attr.nodeName);
+            }
+          }
+
+          let inputDivision = 1 / (inputs.length + 1);
+          let outputDivision = 1 / (outputs.length + 1);
+
+          inputs.forEach(function (input, index) {
+            let port = doc.createElement('InPort');
+            port.setAttribute('name', input);
+            port.setAttribute("port-id", inputKeys[index]);
+
+            let v11 = graph.insertVertex(v1, null, port, 0, (index * inputDivision + inputDivision), 10, 10);
+            v11.geometry.offset = new mxPoint(-5, -5);
+            v11.geometry.relative = true;
+          });
+
+          outputs.forEach(function (output, index) {
+            let port = doc.createElement('OutPort');
+            port.setAttribute('name', output);
+            port.setAttribute("port-id", outputKeys[index]);
+
+            let v11 = graph.insertVertex(v1, null, port, 1, (index * outputDivision + outputDivision), 10, 10);
+            v11.geometry.offset = new mxPoint(-5, -5);
+            v11.geometry.relative = true;
+          });
+        }
+        finally {
+          // Updates the display
+          graph.getModel().endUpdate();
+        }
+
+        graph.updateCellSize(v1);
+        graph.setSelectionCell(v1);
+      };
+
+      // Creates the image which is used as the drag icon (preview)
+      let img = toolbar.addMode(null, image, funct);
+      mxUtils.makeDraggable(img, graph, funct);
+    }
+  };
+}
diff --git a/airavata-kubernetes/web-console/src/app/components/workflow/create/create.html b/airavata-kubernetes/web-console/src/app/components/workflow/create/create.html
new file mode 100644
index 0000000..22723d1
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/app/components/workflow/create/create.html
@@ -0,0 +1,27 @@
+<label for="worklow-name">Workflow Name</label>
+<input [(ngModel)]="workFlowName" name="worklow-name" id="worklow-name"/>
+
+<table style="position:relative;">
+  <tr>
+    <td>
+      <div id="toolContainer" style="border: solid 1px black; width: 80px; cursor: default">
+
+      </div>
+    </td>
+    <td>
+      <div id="graphContainer"
+           style="border: solid 1px black;overflow:hidden;width:321px; cursor:default;">
+      </div>
+    </td>
+    <td valign="top">
+      <div id="properties"
+           style="border: solid 1px black; padding: 10px;">
+      </div>
+    </td>
+  </tr>
+  <tr>
+    <td></td>
+    <td><button (click)="onShowWorkflowXMLClicked()">Show XML</button></td>
+    <td><button (click)="onCreateWorkflowClicked()">Create Workflow</button></td>
+  </tr>
+</table>
diff --git a/airavata-kubernetes/web-console/src/app/components/workflow/list/list.html b/airavata-kubernetes/web-console/src/app/components/workflow/list/list.html
new file mode 100644
index 0000000..7c454dd
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/app/components/workflow/list/list.html
@@ -0,0 +1,23 @@
+<h4>Workflows</h4>
+
+<button class="btn btn-default-outline" (click)="routeToCreatePage()">Add Workflow</button>
+
+<table class="table">
+  <thead class="thead-light">
+
+  <tr>
+    <th scope="col">Id</th>
+    <th scope="col">Name</th>
+    <th scope="col"></th>
+  </tr>
+  </thead>
+
+  <tbody>
+  <tr *ngFor="let workflow of this.workFlows">
+    <th scope="row">{{ workflow.id }}</th>
+    <td>{{ workflow.name }}</td>
+    <td><button (click)="routeToDetailPage(workflow.id)">Detail</button></td>
+  </tr>
+  </tbody>
+
+</table>
diff --git a/airavata-kubernetes/web-console/src/app/components/workflow/list/workflow.list.component.ts b/airavata-kubernetes/web-console/src/app/components/workflow/list/workflow.list.component.ts
new file mode 100644
index 0000000..fc8b9ad
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/app/components/workflow/list/workflow.list.component.ts
@@ -0,0 +1,49 @@
+import {Component, ViewEncapsulation} from "@angular/core";
+import {NgbModal, ModalDismissReasons, NgbModalRef} from '@ng-bootstrap/ng-bootstrap';
+import {ApiService} from "../../../services/api.service";
+import {ExperimentService} from "../../../services/experiment.service";
+import {Experiment} from "../../../models/experiment/experiment.model";
+import {ApplicationIface} from "../../../models/application/application.iface.model";
+import {ApplicationIfaceService} from "../../../services/application.iface.service";
+import {ExperimentInput} from "../../../models/experiment/experiment.input.model";
+import {ExperimentOutput} from "../../../models/experiment/experiment.output.model";
+import {ApplicationDeployment} from "../../../models/application/application.deployment.model";
+import {AppDeploymentService} from "../../../services/deployment.service";
+import {ComputeResource} from "../../../models/compute/compute.resource.model";
+import {ComputeService} from "../../../services/compute.service";
+import {Router} from "@angular/router";
+import {WorkFlow} from "../../../models/workflow/workflow.model";
+import {WorkflowService} from "../../../services/workflow.service";
+
+/**
+ * Created by dimuthu on 10/29/17.
+ */
+
+@Component({
+  templateUrl: './list.html',
+  encapsulation: ViewEncapsulation.None,
+  providers: [WorkflowService]
+})
+export class WorkflowListComponent {
+
+  workFlows: Array<WorkFlow> = [];
+
+  constructor(private workflowService: WorkflowService, private router: Router) {
+    this.getAllWorkFlows();
+  }
+
+  routeToDetailPage(id: number) {
+    this.router.navigateByUrl("/experiment/detail/"+id);
+  }
+
+  routeToCreatePage() {
+    this.router.navigateByUrl("/workflow/create")
+  }
+
+  getAllWorkFlows() {
+    this.workflowService.getAllWorkflows().subscribe(
+      data => {this.workFlows = data},
+      err => {console.log(err);});
+  }
+
+}
diff --git a/airavata-kubernetes/web-console/src/app/models/task/type/operation/operation.inport.type.model.ts b/airavata-kubernetes/web-console/src/app/models/task/type/operation/operation.inport.type.model.ts
new file mode 100644
index 0000000..c52b0f2
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/app/models/task/type/operation/operation.inport.type.model.ts
@@ -0,0 +1,9 @@
+export class OperationInPort {
+  id: number;
+  name: string;
+
+  constructor(id: number, name: string) {
+    this.id = id;
+    this.name = name;
+  }
+}
diff --git a/airavata-kubernetes/web-console/src/app/models/task/type/operation/operation.outport.type.model.ts b/airavata-kubernetes/web-console/src/app/models/task/type/operation/operation.outport.type.model.ts
new file mode 100644
index 0000000..c4a4b38
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/app/models/task/type/operation/operation.outport.type.model.ts
@@ -0,0 +1,9 @@
+export class OperationOutPort {
+  id: number;
+  name: string;
+
+  constructor(id: number, name: string) {
+    this.id = id;
+    this.name = name;
+  }
+}
diff --git a/airavata-kubernetes/web-console/src/app/models/task/type/operation/operation.type.model.ts b/airavata-kubernetes/web-console/src/app/models/task/type/operation/operation.type.model.ts
new file mode 100644
index 0000000..07b7560
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/app/models/task/type/operation/operation.type.model.ts
@@ -0,0 +1,29 @@
+import {OperationInPort} from "./operation.inport.type.model";
+import {OperationOutPort} from "./operation.outport.type.model";
+
+export class OperationType {
+  id: number;
+  name: string;
+  icon: string = "assets/icons/ssh.png";
+  inPorts: Array<OperationInPort> = [];
+  outPorts: Array<OperationOutPort> = [];
+
+  clone(): OperationType {
+    let cloned: OperationType = new OperationType();
+    cloned.id = this.id;
+    cloned.name = this.name;
+    cloned.icon = this.icon;
+
+    this.inPorts.forEach((inPort) => {
+      let clonedInPort: OperationInPort = new OperationInPort(inPort.id, inPort.name);
+      cloned.inPorts.push(clonedInPort);
+    });
+
+    this.outPorts.forEach((outPort) => {
+      let clonedOutPort: OperationOutPort = new OperationOutPort(outPort.id, outPort.name);
+      cloned.outPorts.push(clonedOutPort);
+    });
+
+    return cloned;
+  }
+}
diff --git a/airavata-kubernetes/web-console/src/app/models/task/type/task.input.type.model.ts b/airavata-kubernetes/web-console/src/app/models/task/type/task.input.type.model.ts
new file mode 100644
index 0000000..0f70847
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/app/models/task/type/task.input.type.model.ts
@@ -0,0 +1,14 @@
+export class TaskInputType {
+  id: number;
+  name: String;
+  type: String;
+  defaultValue: string;
+
+
+  constructor(id: number = 0, name: String = null, type: String = null, defaultValue: string = null) {
+    this.id = id;
+    this.name = name;
+    this.type = type;
+    this.defaultValue = defaultValue;
+  }
+}
diff --git a/airavata-kubernetes/web-console/src/app/models/task/type/task.outport.model.ts b/airavata-kubernetes/web-console/src/app/models/task/type/task.outport.model.ts
new file mode 100644
index 0000000..468b0be
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/app/models/task/type/task.outport.model.ts
@@ -0,0 +1,12 @@
+export class TaskOutPort {
+  id: number;
+  name: string;
+  order: number;
+
+
+  constructor(id: number =0 , name: string = null, order: number = 0) {
+    this.id = id;
+    this.name = name;
+    this.order = order;
+  }
+}
diff --git a/airavata-kubernetes/web-console/src/app/models/task/type/task.output.type.model.ts b/airavata-kubernetes/web-console/src/app/models/task/type/task.output.type.model.ts
new file mode 100644
index 0000000..a995603
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/app/models/task/type/task.output.type.model.ts
@@ -0,0 +1,12 @@
+export class TaskOutputType {
+  id: number;
+  name: string;
+  type: string;
+
+
+  constructor(id: number = 0, name: string = null, type: string = null) {
+    this.id = id;
+    this.name = name;
+    this.type = type;
+  }
+}
diff --git a/airavata-kubernetes/web-console/src/app/models/task/type/task.type.model.ts b/airavata-kubernetes/web-console/src/app/models/task/type/task.type.model.ts
new file mode 100644
index 0000000..8572344
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/app/models/task/type/task.type.model.ts
@@ -0,0 +1,39 @@
+import {TaskInputType} from "./task.input.type.model";
+import {TaskOutputType} from "./task.output.type.model";
+import {TaskOutPort} from "./task.outport.model";
+export class TaskType {
+
+  id: number;
+  name: string;
+  topicName: string;
+  icon: string = "assets/icons/ssh.png";
+  inputTypes: Array<TaskInputType> = [];
+  outputTypes: Array<TaskOutputType> = [];
+  outPorts: Array<TaskOutPort> = [];
+
+  clone(): TaskType {
+    let cloned: TaskType = new TaskType();
+    cloned.id = this.id;
+    cloned.name = this.name;
+    cloned.icon = this.icon;
+    cloned.topicName = this.topicName;
+
+    this.inputTypes.forEach((input) => {
+      let clonedInput: TaskInputType = new TaskInputType(input.id, input.name, input.type, input.defaultValue);
+      cloned.inputTypes.push(clonedInput);
+    });
+
+    this.outputTypes.forEach((output) => {
+      let clonedOutput: TaskOutputType = new TaskOutputType(output.id, output.name, output.type);
+      cloned.outputTypes.push(clonedOutput);
+    });
+
+    this.outPorts.forEach((outPort) => {
+      let clonedOutPort: TaskOutPort = new TaskOutPort(outPort.id, outPort.name, outPort.order);
+      cloned.outPorts.push(clonedOutPort);
+    });
+
+    return cloned;
+  }
+
+}
diff --git a/airavata-kubernetes/web-console/src/app/models/workflow/workflow.model.ts b/airavata-kubernetes/web-console/src/app/models/workflow/workflow.model.ts
new file mode 100644
index 0000000..f437c26
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/app/models/workflow/workflow.model.ts
@@ -0,0 +1,8 @@
+export class WorkFlow {
+
+  id: number;
+  name: string;
+  workflowGraphXML: string;
+  processIds: Array<number> = [];
+
+}
diff --git a/airavata-kubernetes/web-console/src/app/services/task.service.ts b/airavata-kubernetes/web-console/src/app/services/task.service.ts
index d7b17ed..f944ab0 100644
--- a/airavata-kubernetes/web-console/src/app/services/task.service.ts
+++ b/airavata-kubernetes/web-console/src/app/services/task.service.ts
@@ -22,4 +22,8 @@ export class TaskService {
   addTask(task: Task) {
     return this.apiService.post("task", task);
   }
+
+  getAllTaskTypes() {
+    return this.apiService.get("taskType").map(res => res.json());
+  }
 }
diff --git a/airavata-kubernetes/web-console/src/app/services/workflow.service.ts b/airavata-kubernetes/web-console/src/app/services/workflow.service.ts
new file mode 100644
index 0000000..f652bd9
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/app/services/workflow.service.ts
@@ -0,0 +1,17 @@
+import {Inject, Injectable} from "@angular/core";
+import {ApiService} from "./api.service";
+
+@Injectable()
+export class WorkflowService {
+
+  constructor(@Inject(ApiService) private apiService:ApiService) {
+  }
+
+  createWorkflow(name: string, xml: any) {
+    return this.apiService.post("workflow/create/" + name, xml)
+  }
+
+  getAllWorkflows() {
+    return this.apiService.get("workflow").map(res => res.json());
+  }
+}
diff --git a/airavata-kubernetes/web-console/src/assets/css/common.css b/airavata-kubernetes/web-console/src/assets/css/common.css
new file mode 100644
index 0000000..1733f6f
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/css/common.css
@@ -0,0 +1,160 @@
+div.mxRubberband {
+	position: absolute;
+	overflow: hidden;
+	border-style: solid;
+	border-width: 1px;
+	border-color: #0000FF;
+	background: #0077FF;
+}
+.mxCellEditor {
+	background: url('../images/transparent.gif');
+	border-color: transparent;
+	border-style: solid;
+	position: absolute;
+	overflow: visible;
+	word-wrap: normal;
+	border-width: 0;
+	min-width: 1px;
+	resize: none;
+	padding: 0px;
+	margin: 0px;
+}
+.mxPlainTextEditor * {
+	padding: 0px;
+	margin: 0px;
+}
+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:1px solid #c3c3c3;
+	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;
+	position: relative;
+}
+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;
+}
+table.mxPopupMenu {
+	border-collapse: collapse;
+	margin-top: 1px;
+	margin-bottom: 1px;
+}
+tr.mxPopupMenuItem {
+	color: black;
+	cursor: pointer;
+}
+tr.mxPopupMenuItemHover {
+	background-color: #000066;
+	color: #FFFFFF;
+	cursor: pointer;
+}
+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;
+}
+.mxDisabled {
+	opacity: 0.2 !important;
+	cursor:default !important;
+}
diff --git a/airavata-kubernetes/web-console/src/assets/css/explorer.css b/airavata-kubernetes/web-console/src/assets/css/explorer.css
new file mode 100644
index 0000000..50e704f
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/css/explorer.css
@@ -0,0 +1,18 @@
+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;
+}
+.mxDisabled {
+	filter:alpha(opacity=20) !important;
+}
diff --git a/airavata-kubernetes/web-console/src/assets/icons/copy.png b/airavata-kubernetes/web-console/src/assets/icons/copy.png
new file mode 100755
index 0000000..b6efb58
Binary files /dev/null and b/airavata-kubernetes/web-console/src/assets/icons/copy.png differ
diff --git a/airavata-kubernetes/web-console/src/assets/icons/http.png b/airavata-kubernetes/web-console/src/assets/icons/http.png
new file mode 100755
index 0000000..8cbd863
Binary files /dev/null and b/airavata-kubernetes/web-console/src/assets/icons/http.png differ
diff --git a/airavata-kubernetes/web-console/src/assets/icons/parallel.png b/airavata-kubernetes/web-console/src/assets/icons/parallel.png
new file mode 100755
index 0000000..4e10b7c
Binary files /dev/null and b/airavata-kubernetes/web-console/src/assets/icons/parallel.png differ
diff --git a/airavata-kubernetes/web-console/src/assets/icons/s3.png b/airavata-kubernetes/web-console/src/assets/icons/s3.png
new file mode 100755
index 0000000..0845fff
Binary files /dev/null and b/airavata-kubernetes/web-console/src/assets/icons/s3.png differ
diff --git a/airavata-kubernetes/web-console/src/assets/icons/ssh.png b/airavata-kubernetes/web-console/src/assets/icons/ssh.png
new file mode 100755
index 0000000..5e9f91d
Binary files /dev/null and b/airavata-kubernetes/web-console/src/assets/icons/ssh.png differ
diff --git a/airavata-kubernetes/web-console/src/assets/icons/start.png b/airavata-kubernetes/web-console/src/assets/icons/start.png
new file mode 100755
index 0000000..871fe36
Binary files /dev/null and b/airavata-kubernetes/web-console/src/assets/icons/start.png differ
diff --git a/airavata-kubernetes/web-console/src/assets/icons/stop.png b/airavata-kubernetes/web-console/src/assets/icons/stop.png
new file mode 100755
index 0000000..6cc9ace
Binary files /dev/null and b/airavata-kubernetes/web-console/src/assets/icons/stop.png differ
diff --git a/airavata-kubernetes/web-console/src/assets/images/button.gif b/airavata-kubernetes/web-console/src/assets/images/button.gif
new file mode 100644
index 0000000..ad55cab
Binary files /dev/null and b/airavata-kubernetes/web-console/src/assets/images/button.gif differ
diff --git a/airavata-kubernetes/web-console/src/assets/images/close.gif b/airavata-kubernetes/web-console/src/assets/images/close.gif
new file mode 100644
index 0000000..1069e94
Binary files /dev/null and b/airavata-kubernetes/web-console/src/assets/images/close.gif differ
diff --git a/airavata-kubernetes/web-console/src/assets/images/collapsed.gif b/airavata-kubernetes/web-console/src/assets/images/collapsed.gif
new file mode 100644
index 0000000..0276444
Binary files /dev/null and b/airavata-kubernetes/web-console/src/assets/images/collapsed.gif differ
diff --git a/airavata-kubernetes/web-console/src/assets/images/error.gif b/airavata-kubernetes/web-console/src/assets/images/error.gif
new file mode 100644
index 0000000..14e1aee
Binary files /dev/null and b/airavata-kubernetes/web-console/src/assets/images/error.gif differ
diff --git a/airavata-kubernetes/web-console/src/assets/images/expanded.gif b/airavata-kubernetes/web-console/src/assets/images/expanded.gif
new file mode 100644
index 0000000..3767b0b
Binary files /dev/null and b/airavata-kubernetes/web-console/src/assets/images/expanded.gif differ
diff --git a/airavata-kubernetes/web-console/src/assets/images/maximize.gif b/airavata-kubernetes/web-console/src/assets/images/maximize.gif
new file mode 100644
index 0000000..e27cf3e
Binary files /dev/null and b/airavata-kubernetes/web-console/src/assets/images/maximize.gif differ
diff --git a/airavata-kubernetes/web-console/src/assets/images/minimize.gif b/airavata-kubernetes/web-console/src/assets/images/minimize.gif
new file mode 100644
index 0000000..1e95e7c
Binary files /dev/null and b/airavata-kubernetes/web-console/src/assets/images/minimize.gif differ
diff --git a/airavata-kubernetes/web-console/src/assets/images/normalize.gif b/airavata-kubernetes/web-console/src/assets/images/normalize.gif
new file mode 100644
index 0000000..34a8d30
Binary files /dev/null and b/airavata-kubernetes/web-console/src/assets/images/normalize.gif differ
diff --git a/airavata-kubernetes/web-console/src/assets/images/point.gif b/airavata-kubernetes/web-console/src/assets/images/point.gif
new file mode 100644
index 0000000..9074c39
Binary files /dev/null and b/airavata-kubernetes/web-console/src/assets/images/point.gif differ
diff --git a/airavata-kubernetes/web-console/src/assets/images/resize.gif b/airavata-kubernetes/web-console/src/assets/images/resize.gif
new file mode 100644
index 0000000..ff558db
Binary files /dev/null and b/airavata-kubernetes/web-console/src/assets/images/resize.gif differ
diff --git a/airavata-kubernetes/web-console/src/assets/images/separator.gif b/airavata-kubernetes/web-console/src/assets/images/separator.gif
new file mode 100644
index 0000000..5c1b895
Binary files /dev/null and b/airavata-kubernetes/web-console/src/assets/images/separator.gif differ
diff --git a/airavata-kubernetes/web-console/src/assets/images/submenu.gif b/airavata-kubernetes/web-console/src/assets/images/submenu.gif
new file mode 100644
index 0000000..ffe7617
Binary files /dev/null and b/airavata-kubernetes/web-console/src/assets/images/submenu.gif differ
diff --git a/airavata-kubernetes/web-console/src/assets/images/transparent.gif b/airavata-kubernetes/web-console/src/assets/images/transparent.gif
new file mode 100644
index 0000000..76040f2
Binary files /dev/null and b/airavata-kubernetes/web-console/src/assets/images/transparent.gif differ
diff --git a/airavata-kubernetes/web-console/src/assets/images/warning.gif b/airavata-kubernetes/web-console/src/assets/images/warning.gif
new file mode 100644
index 0000000..705235f
Binary files /dev/null and b/airavata-kubernetes/web-console/src/assets/images/warning.gif differ
diff --git a/airavata-kubernetes/web-console/src/assets/images/warning.png b/airavata-kubernetes/web-console/src/assets/images/warning.png
new file mode 100644
index 0000000..2f78789
Binary files /dev/null and b/airavata-kubernetes/web-console/src/assets/images/warning.png differ
diff --git a/airavata-kubernetes/web-console/src/assets/images/window-title.gif b/airavata-kubernetes/web-console/src/assets/images/window-title.gif
new file mode 100644
index 0000000..231def8
Binary files /dev/null and b/airavata-kubernetes/web-console/src/assets/images/window-title.gif differ
diff --git a/airavata-kubernetes/web-console/src/assets/images/window.gif b/airavata-kubernetes/web-console/src/assets/images/window.gif
new file mode 100644
index 0000000..6631c4f
Binary files /dev/null and b/airavata-kubernetes/web-console/src/assets/images/window.gif differ
diff --git a/airavata-kubernetes/web-console/src/assets/js/components.js b/airavata-kubernetes/web-console/src/assets/js/components.js
new file mode 100644
index 0000000..b3e186f
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/components.js
@@ -0,0 +1,53 @@
+function fetchComponent(name, doc) {
+    if (name == "SSH") {
+        var pe = doc.createElement('ProcessingElement');
+        pe.setAttribute('name', 'SSH Processing element');
+        pe.setAttribute('Type', 'PROCESSING_ELEMENT');
+        pe.setAttribute('Compute-host', '192.168.1.112');
+        pe.setAttribute('User', 'root');
+        pe.setAttribute('Password', 'password');
+        pe.setAttribute('Command', '');
+        pe.setAttribute('Arguments', '');
+        pe.setAttribute("out-1", "Output");
+        pe.setAttribute("out-2", "Error");
+        pe.setAttribute("in-1", "Input");
+        return pe;
+    } else if (name == "CP") {
+        var pe = doc.createElement('ProcessingElement');
+        pe.setAttribute('name', 'Copy Processing element');
+        pe.setAttribute('type', 'PROCESSING_ELEMENT');
+        pe.setAttribute("out-1", "Output");
+        pe.setAttribute("out-2", "Error");
+        pe.setAttribute("in-1", "Input");
+        return pe;
+    } else if (name == "S3") {
+        var pe = doc.createElement('ProcessingElement');
+        pe.setAttribute('name', 'S3 Processing element');
+        pe.setAttribute('type', 'PROCESSING_ELEMENT');
+        pe.setAttribute("out-1", "Output");
+        pe.setAttribute("out-2", "Error");
+        pe.setAttribute("in-1", "Input");
+        return pe;
+    } else if (name == "START") {
+        var pe = doc.createElement('ProcessingElement');
+        pe.setAttribute('name', 'Start Operation');
+        pe.setAttribute('type', 'OPERATION');
+        pe.setAttribute("out-1", "Output");
+        return pe;
+    } else if (name == "STOP") {
+        var pe = doc.createElement('ProcessingElement');
+        pe.setAttribute('name', 'Stop Operation');
+        pe.setAttribute('type', 'OPERATION');
+        pe.setAttribute("in-1", "Input");
+        return pe;
+    } else if (name = "PARALLEL") {
+        var pe = doc.createElement('ProcessingElement');
+        pe.setAttribute('name', 'Parallel Operation');
+        pe.setAttribute('type', 'OPERATION');
+        pe.setAttribute("out-1", "Output1");
+        pe.setAttribute("out-2", "Output2");
+        pe.setAttribute("in-1", "Input");
+        return pe;
+    }
+
+}
\ No newline at end of file
diff --git a/airavata-kubernetes/web-console/src/assets/js/editor/mxDefaultKeyHandler.js b/airavata-kubernetes/web-console/src/assets/js/editor/mxDefaultKeyHandler.js
new file mode 100644
index 0000000..237dea4
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/editor/mxDefaultKeyHandler.js
@@ -0,0 +1,126 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxDefaultKeyHandler
+ *
+ * Binds keycodes to actionnames in an editor. This aggregates an internal
+ * <handler> and extends the implementation of <mxKeyHandler.escape> to not
+ * only cancel the editing, but also hide the properties dialog and fire an
+ * <mxEditor.escape> event via <editor>. An instance of this class is created
+ * by <mxEditor> and stored in <mxEditor.keyHandler>.
+ * 
+ * Example:
+ * 
+ * Bind the delete key to the delete action in an existing editor.
+ * 
+ * (code)
+ * var keyHandler = new mxDefaultKeyHandler(editor);
+ * keyHandler.bindAction(46, 'delete');
+ * (end)
+ *
+ * Codec:
+ * 
+ * This class uses the <mxDefaultKeyHandlerCodec> to read configuration
+ * data into an existing instance. See <mxDefaultKeyHandlerCodec> for a
+ * description of the configuration format.
+ * 
+ * Keycodes:
+ * 
+ * See <mxKeyHandler>.
+ * 
+ * An <mxEvent.ESCAPE> event is fired via the editor if the escape key is
+ * pressed.
+ * 
+ * Constructor: mxDefaultKeyHandler
+ *
+ * Constructs a new default key handler for the <mxEditor.graph> in the
+ * given <mxEditor>. (The editor may be null if a prototypical instance for
+ * a <mxDefaultKeyHandlerCodec> is created.)
+ * 
+ * Parameters:
+ * 
+ * editor - Reference to the enclosing <mxEditor>.
+ */
+function mxDefaultKeyHandler(editor)
+{
+	if (editor != null)
+	{
+		this.editor = editor;
+		this.handler = new mxKeyHandler(editor.graph);
+		
+		// Extends the escape function of the internal key
+		// handle to hide the properties dialog and fire
+		// the escape event via the editor instance
+		var old = this.handler.escape;
+		
+		this.handler.escape = function(evt)
+		{
+			old.apply(this, arguments);
+			editor.hideProperties();
+			editor.fireEvent(new mxEventObject(mxEvent.ESCAPE, 'event', evt));
+		};
+	}
+};
+	
+/**
+ * Variable: editor
+ *
+ * Reference to the enclosing <mxEditor>.
+ */
+mxDefaultKeyHandler.prototype.editor = null;
+
+/**
+ * Variable: handler
+ *
+ * Holds the <mxKeyHandler> for key event handling.
+ */
+mxDefaultKeyHandler.prototype.handler = null;
+
+/**
+ * Function: bindAction
+ *
+ * Binds the specified keycode to the given action in <editor>. The
+ * optional control flag specifies if the control key must be pressed
+ * to trigger the action.
+ *
+ * Parameters:
+ *
+ * code - Integer that specifies the keycode.
+ * action - Name of the action to execute in <editor>.
+ * control - Optional boolean that specifies if control must be pressed.
+ * Default is false.
+ */
+mxDefaultKeyHandler.prototype.bindAction = function (code, action, control)
+{
+	var keyHandler = mxUtils.bind(this, function()
+	{
+		this.editor.execute(action);
+	});
+
+	// Binds the function to control-down keycode
+	if (control)
+	{
+		this.handler.bindControlKey(code, keyHandler);
+	}
+
+	// Binds the function to the normal keycode
+	else
+	{
+		this.handler.bindKey(code, keyHandler);				
+	}
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the <handler> associated with this object. This does normally
+ * not need to be called, the <handler> is destroyed automatically when the
+ * window unloads (in IE) by <mxEditor>.
+ */
+mxDefaultKeyHandler.prototype.destroy = function ()
+{
+	this.handler.destroy();
+	this.handler = null;
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/editor/mxDefaultPopupMenu.js b/airavata-kubernetes/web-console/src/assets/js/editor/mxDefaultPopupMenu.js
new file mode 100644
index 0000000..2f2e6e7
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/editor/mxDefaultPopupMenu.js
@@ -0,0 +1,306 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxDefaultPopupMenu
+ *
+ * Creates popupmenus for mouse events. This object holds an XML node
+ * which is a description of the popup menu to be created. In
+ * <createMenu>, the configuration is applied to the context and
+ * the resulting menu items are added to the menu dynamically. See
+ * <createMenu> for a description of the configuration format.
+ * 
+ * This class does not create the DOM nodes required for the popup menu, it
+ * only parses an XML description to invoke the respective methods on an
+ * <mxPopupMenu> each time the menu is displayed.
+ *
+ * Codec:
+ * 
+ * This class uses the <mxDefaultPopupMenuCodec> to read configuration
+ * data into an existing instance, however, the actual parsing is done
+ * by this class during program execution, so the format is described
+ * below.
+ * 
+ * Constructor: mxDefaultPopupMenu
+ *
+ * Constructs a new popupmenu-factory based on given configuration.
+ *
+ * Paramaters:
+ *
+ * config - XML node that contains the configuration data.
+ */
+function mxDefaultPopupMenu(config)
+{
+	this.config = config;
+};
+
+/**
+ * Variable: imageBasePath
+ *
+ * Base path for all icon attributes in the config. Default is null.
+ */
+mxDefaultPopupMenu.prototype.imageBasePath = null;
+
+/**
+ * Variable: config
+ *
+ * XML node used as the description of new menu items. This node is
+ * used in <createMenu> to dynamically create the menu items if their
+ * respective conditions evaluate to true for the given arguments.
+ */
+mxDefaultPopupMenu.prototype.config = null;
+
+/**
+ * Function: createMenu
+ *
+ * This function is called from <mxEditor> to add items to the
+ * given menu based on <config>. The config is a sequence of
+ * the following nodes and attributes.
+ *
+ * Child Nodes: 
+ *
+ * add - Adds a new menu item. See below for attributes.
+ * separator - Adds a separator. No attributes.
+ * condition - Adds a custom condition. Name attribute.
+ * 
+ * The add-node may have a child node that defines a function to be invoked
+ * before the action is executed (or instead of an action to be executed).
+ *
+ * Attributes:
+ *
+ * as - Resource key for the label (needs entry in property file).
+ * action - Name of the action to execute in enclosing editor.
+ * icon - Optional icon (relative/absolute URL).
+ * iconCls - Optional CSS class for the icon.
+ * if - Optional name of condition that must be true (see below).
+ * enabled-if - Optional name of condition that specifies if the menu item
+ * should be enabled.
+ * name - Name of custom condition. Only for condition nodes.
+ *
+ * Conditions:
+ *
+ * nocell - No cell under the mouse.
+ * ncells - More than one cell selected.
+ * notRoot - Drilling position is other than home.
+ * cell - Cell under the mouse.
+ * notEmpty - Exactly one cell with children under mouse.
+ * expandable - Exactly one expandable cell under mouse.
+ * collapsable - Exactly one collapsable cell under mouse.
+ * validRoot - Exactly one cell which is a possible root under mouse.
+ * swimlane - Exactly one cell which is a swimlane under mouse.
+ *
+ * Example:
+ *
+ * To add a new item for a given action to the popupmenu:
+ * 
+ * (code)
+ * <mxDefaultPopupMenu as="popupHandler">
+ *   <add as="delete" action="delete" icon="images/delete.gif" if="cell"/>
+ * </mxDefaultPopupMenu>
+ * (end)
+ * 
+ * To add a new item for a custom function:
+ * 
+ * (code)
+ * <mxDefaultPopupMenu as="popupHandler">
+ *   <add as="action1"><![CDATA[
+ *		function (editor, cell, evt)
+ *		{
+ *			editor.execute('action1', cell, 'myArg');
+ *		}
+ *   ]]></add>
+ * </mxDefaultPopupMenu>
+ * (end)
+ * 
+ * The above example invokes action1 with an additional third argument via
+ * the editor instance. The third argument is passed to the function that
+ * defines action1. If the add-node has no action-attribute, then only the
+ * function defined in the text content is executed, otherwise first the
+ * function and then the action defined in the action-attribute is
+ * executed. The function in the text content has 3 arguments, namely the
+ * <mxEditor> instance, the <mxCell> instance under the mouse, and the
+ * native mouse event.
+ *
+ * Custom Conditions:
+ *
+ * To add a new condition for popupmenu items:
+ *  
+ * (code)
+ * <condition name="condition1"><![CDATA[
+ *   function (editor, cell, evt)
+ *   {
+ *     return cell != null;
+ *   }
+ * ]]></condition>
+ * (end)
+ * 
+ * The new condition can then be used in any item as follows:
+ * 
+ * (code)
+ * <add as="action1" action="action1" icon="action1.gif" if="condition1"/>
+ * (end)
+ * 
+ * The order in which the items and conditions appear is not significant as
+ * all connditions are evaluated before any items are created.
+ * 
+ * Parameters:
+ *
+ * editor - Enclosing <mxEditor> instance.
+ * menu - <mxPopupMenu> that is used for adding items and separators.
+ * cell - Optional <mxCell> which is under the mousepointer.
+ * evt - Optional mouse event which triggered the menu. 
+ */
+mxDefaultPopupMenu.prototype.createMenu = function(editor, menu, cell, evt)
+{
+	if (this.config != null)
+	{
+		var conditions = this.createConditions(editor, cell, evt);
+		var item = this.config.firstChild;
+
+		this.addItems(editor, menu, cell, evt, conditions, item, null);
+	}
+};
+
+/**
+ * Function: addItems
+ * 
+ * Recursively adds the given items and all of its children into the given menu.
+ * 
+ * Parameters:
+ *
+ * editor - Enclosing <mxEditor> instance.
+ * menu - <mxPopupMenu> that is used for adding items and separators.
+ * cell - Optional <mxCell> which is under the mousepointer.
+ * evt - Optional mouse event which triggered the menu.
+ * conditions - Array of names boolean conditions.
+ * item - XML node that represents the current menu item.
+ * parent - DOM node that represents the parent menu item.
+ */
+mxDefaultPopupMenu.prototype.addItems = function(editor, menu, cell, evt, conditions, item, parent)
+{
+	var addSeparator = false;
+	
+	while (item != null)
+	{
+		if (item.nodeName == 'add')
+		{
+			var condition = item.getAttribute('if');
+			
+			if (condition == null || conditions[condition])
+			{
+				var as = item.getAttribute('as');
+				as = mxResources.get(as) || as;
+				var funct = mxUtils.eval(mxUtils.getTextContent(item));
+				var action = item.getAttribute('action');
+				var icon = item.getAttribute('icon');
+				var iconCls = item.getAttribute('iconCls');
+				var enabledCond = item.getAttribute('enabled-if');
+				var enabled = enabledCond == null || conditions[enabledCond];
+				
+				if (addSeparator)
+				{
+					menu.addSeparator(parent);
+					addSeparator = false;
+				}
+				
+				if (icon != null && this.imageBasePath)
+				{
+					icon = this.imageBasePath + icon;
+				}
+				
+				var row = this.addAction(menu, editor, as, icon, funct, action, cell, parent, iconCls, enabled);
+				this.addItems(editor, menu, cell, evt, conditions, item.firstChild, row);
+			}
+		}
+		else if (item.nodeName == 'separator')
+		{
+			addSeparator = true;
+		}
+		
+		item = item.nextSibling;
+	}
+};
+
+/**
+ * Function: addAction
+ *
+ * Helper method to bind an action to a new menu item.
+ * 
+ * Parameters:
+ *
+ * menu - <mxPopupMenu> that is used for adding items and separators.
+ * editor - Enclosing <mxEditor> instance.
+ * lab - String that represents the label of the menu item.
+ * icon - Optional URL that represents the icon of the menu item.
+ * action - Optional name of the action to execute in the given editor.
+ * funct - Optional function to execute before the optional action. The
+ * function takes an <mxEditor>, the <mxCell> under the mouse and the
+ * mouse event that triggered the call.
+ * cell - Optional <mxCell> to use as an argument for the action.
+ * parent - DOM node that represents the parent menu item.
+ * iconCls - Optional CSS class for the menu icon.
+ * enabled - Optional boolean that specifies if the menu item is enabled.
+ * Default is true.
+ */
+mxDefaultPopupMenu.prototype.addAction = function(menu, editor, lab, icon, funct, action, cell, parent, iconCls, enabled)
+{
+	var clickHandler = function(evt)
+	{
+		if (typeof(funct) == 'function')
+		{
+			funct.call(editor, editor, cell, evt);
+		}
+		
+		if (action != null)
+		{
+			editor.execute(action, cell, evt);
+		}
+	};
+	
+	return menu.addItem(lab, icon, clickHandler, parent, iconCls, enabled);
+};
+
+/**
+ * Function: createConditions
+ * 
+ * Evaluates the default conditions for the given context.
+ */
+mxDefaultPopupMenu.prototype.createConditions = function(editor, cell, evt)
+{
+	// Creates array with conditions
+	var model = editor.graph.getModel();
+	var childCount = model.getChildCount(cell);
+	
+	// Adds some frequently used conditions
+	var conditions = [];
+	conditions['nocell'] = cell == null;
+	conditions['ncells'] = editor.graph.getSelectionCount() > 1;
+	conditions['notRoot'] = model.getRoot() !=
+		model.getParent(editor.graph.getDefaultParent());
+	conditions['cell'] = cell != null;
+	
+	var isCell = cell != null && editor.graph.getSelectionCount() == 1;
+	conditions['nonEmpty'] = isCell && childCount > 0;
+	conditions['expandable'] = isCell && editor.graph.isCellFoldable(cell, false);
+	conditions['collapsable'] = isCell && editor.graph.isCellFoldable(cell, true);
+	conditions['validRoot'] = isCell && editor.graph.isValidRoot(cell);
+	conditions['emptyValidRoot'] = conditions['validRoot'] && childCount == 0;
+	conditions['swimlane'] = isCell && editor.graph.isSwimlane(cell);
+
+	// Evaluates dynamic conditions from config file
+	var condNodes = this.config.getElementsByTagName('condition');
+	
+	for (var i=0; i<condNodes.length; i++)
+	{
+		var funct = mxUtils.eval(mxUtils.getTextContent(condNodes[i]));
+		var name = condNodes[i].getAttribute('name');
+		
+		if (name != null && typeof(funct) == 'function')
+		{
+			conditions[name] = funct(editor, cell, evt);
+		}
+	}
+	
+	return conditions;
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/editor/mxDefaultToolbar.js b/airavata-kubernetes/web-console/src/assets/js/editor/mxDefaultToolbar.js
new file mode 100644
index 0000000..8a7f2b6
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/editor/mxDefaultToolbar.js
@@ -0,0 +1,564 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxDefaultToolbar
+ *
+ * Toolbar for the editor. This modifies the state of the graph
+ * or inserts new cells upon mouse clicks.
+ * 
+ * Example:
+ * 
+ * Create a toolbar with a button to copy the selection into the clipboard,
+ * and a combo box with one action to paste the selection from the clipboard
+ * into the graph.
+ * 
+ * (code)
+ * var toolbar = new mxDefaultToolbar(container, editor);
+ * toolbar.addItem('Copy', null, 'copy');
+ * 
+ * var combo = toolbar.addActionCombo('More actions...');
+ * toolbar.addActionOption(combo, 'Paste', 'paste');
+ * (end) 
+ *
+ * Codec:
+ * 
+ * This class uses the <mxDefaultToolbarCodec> to read configuration
+ * data into an existing instance. See <mxDefaultToolbarCodec> for a
+ * description of the configuration format.
+ * 
+ * Constructor: mxDefaultToolbar
+ *
+ * Constructs a new toolbar for the given container and editor. The
+ * container and editor may be null if a prototypical instance for a
+ * <mxDefaultKeyHandlerCodec> is created.
+ *
+ * Parameters:
+ *
+ * container - DOM node that contains the toolbar.
+ * editor - Reference to the enclosing <mxEditor>. 
+ */
+function mxDefaultToolbar(container, editor)
+{
+	this.editor = editor;
+
+	if (container != null && editor != null)
+	{
+		this.init(container);
+	}
+};
+	
+/**
+ * Variable: editor
+ *
+ * Reference to the enclosing <mxEditor>.
+ */
+mxDefaultToolbar.prototype.editor = null;
+
+/**
+ * Variable: toolbar
+ *
+ * Holds the internal <mxToolbar>.
+ */
+mxDefaultToolbar.prototype.toolbar = null;
+
+/**
+ * Variable: resetHandler
+ *
+ * Reference to the function used to reset the <toolbar>.
+ */
+mxDefaultToolbar.prototype.resetHandler = null;
+
+/**
+ * Variable: spacing
+ *
+ * Defines the spacing between existing and new vertices in
+ * gridSize units when a new vertex is dropped on an existing
+ * cell. Default is 4 (40 pixels).
+ */
+mxDefaultToolbar.prototype.spacing = 4;
+
+/**
+ * Variable: connectOnDrop
+ * 
+ * Specifies if elements should be connected if new cells are dropped onto
+ * connectable elements. Default is false.
+ */
+mxDefaultToolbar.prototype.connectOnDrop = false;
+
+/**
+ * Variable: init
+ * 
+ * Constructs the <toolbar> for the given container and installs a listener
+ * that updates the <mxEditor.insertFunction> on <editor> if an item is
+ * selected in the toolbar. This assumes that <editor> is not null.
+ *
+ * Parameters:
+ *
+ * container - DOM node that contains the toolbar.
+ */
+mxDefaultToolbar.prototype.init = function(container)
+{
+	if (container != null)
+	{
+		this.toolbar = new mxToolbar(container);
+		
+		// Installs the insert function in the editor if an item is
+		// selected in the toolbar
+		this.toolbar.addListener(mxEvent.SELECT, mxUtils.bind(this, function(sender, evt)
+		{
+			var funct = evt.getProperty('function');
+			
+			if (funct != null)
+			{
+				this.editor.insertFunction = mxUtils.bind(this, function()
+				{
+					funct.apply(this, arguments);
+					this.toolbar.resetMode();
+				});
+			}
+			else
+			{
+				this.editor.insertFunction = null;
+			}
+		}));
+		
+		// Resets the selected tool after a doubleclick or escape keystroke
+		this.resetHandler = mxUtils.bind(this, function()
+		{
+			if (this.toolbar != null)
+			{
+				this.toolbar.resetMode(true);
+			}
+		});
+
+		this.editor.graph.addListener(mxEvent.DOUBLE_CLICK, this.resetHandler);
+		this.editor.addListener(mxEvent.ESCAPE, this.resetHandler);
+	}
+};
+
+/**
+ * Function: addItem
+ *
+ * Adds a new item that executes the given action in <editor>. The title,
+ * icon and pressedIcon are used to display the toolbar item.
+ * 
+ * Parameters:
+ *
+ * title - String that represents the title (tooltip) for the item.
+ * icon - URL of the icon to be used for displaying the item.
+ * action - Name of the action to execute when the item is clicked.
+ * pressed - Optional URL of the icon for the pressed state.
+ */
+mxDefaultToolbar.prototype.addItem = function(title, icon, action, pressed)
+{
+	var clickHandler = mxUtils.bind(this, function()
+	{
+		if (action != null && action.length > 0)
+		{
+			this.editor.execute(action);
+		}
+	});
+	
+	return this.toolbar.addItem(title, icon, clickHandler, pressed);
+};
+
+/**
+ * Function: addSeparator
+ *
+ * Adds a vertical separator using the optional icon.
+ * 
+ * Parameters:
+ * 
+ * icon - Optional URL of the icon that represents the vertical separator.
+ * Default is <mxClient.imageBasePath> + '/separator.gif'.
+ */
+mxDefaultToolbar.prototype.addSeparator = function(icon)
+{
+	icon = icon || mxClient.imageBasePath + '/separator.gif';
+	this.toolbar.addSeparator(icon);
+};
+	
+/**
+ * Function: addCombo
+ *
+ * Helper method to invoke <mxToolbar.addCombo> on <toolbar> and return the
+ * resulting DOM node.
+ */
+mxDefaultToolbar.prototype.addCombo = function()
+{
+	return this.toolbar.addCombo();
+};
+		
+/**
+ * Function: addActionCombo
+ *
+ * Helper method to invoke <mxToolbar.addActionCombo> on <toolbar> using
+ * the given title and return the resulting DOM node.
+ * 
+ * Parameters:
+ * 
+ * title - String that represents the title of the combo.
+ */
+mxDefaultToolbar.prototype.addActionCombo = function(title)
+{
+	return this.toolbar.addActionCombo(title);
+};
+
+/**
+ * Function: addActionOption
+ *
+ * Binds the given action to a option with the specified label in the
+ * given combo. Combo is an object returned from an earlier call to
+ * <addCombo> or <addActionCombo>.
+ * 
+ * Parameters:
+ * 
+ * combo - DOM node that represents the combo box.
+ * title - String that represents the title of the combo.
+ * action - Name of the action to execute in <editor>.
+ */
+mxDefaultToolbar.prototype.addActionOption = function(combo, title, action)
+{
+	var clickHandler = mxUtils.bind(this, function()
+	{
+		this.editor.execute(action);
+	});
+	
+	this.addOption(combo, title, clickHandler);
+};
+
+/**
+ * Function: addOption
+ *
+ * Helper method to invoke <mxToolbar.addOption> on <toolbar> and return
+ * the resulting DOM node that represents the option.
+ * 
+ * Parameters:
+ * 
+ * combo - DOM node that represents the combo box.
+ * title - String that represents the title of the combo.
+ * value - Object that represents the value of the option.
+ */
+mxDefaultToolbar.prototype.addOption = function(combo, title, value)
+{
+	return this.toolbar.addOption(combo, title, value);
+};
+	
+/**
+ * Function: addMode
+ *
+ * Creates an item for selecting the given mode in the <editor>'s graph.
+ * Supported modenames are select, connect and pan.
+ * 
+ * Parameters:
+ * 
+ * title - String that represents the title of the item.
+ * icon - URL of the icon that represents the item.
+ * mode - String that represents the mode name to be used in
+ * <mxEditor.setMode>.
+ * pressed - Optional URL of the icon that represents the pressed state.
+ * funct - Optional JavaScript function that takes the <mxEditor> as the
+ * first and only argument that is executed after the mode has been
+ * selected.
+ */
+mxDefaultToolbar.prototype.addMode = function(title, icon, mode, pressed, funct)
+{
+	var clickHandler = mxUtils.bind(this, function()
+	{
+		this.editor.setMode(mode);
+		
+		if (funct != null)
+		{
+			funct(this.editor);
+		}
+	});
+	
+	return this.toolbar.addSwitchMode(title, icon, clickHandler, pressed);
+};
+
+/**
+ * Function: addPrototype
+ *
+ * Creates an item for inserting a clone of the specified prototype cell into
+ * the <editor>'s graph. The ptype may either be a cell or a function that
+ * returns a cell.
+ * 
+ * Parameters:
+ * 
+ * title - String that represents the title of the item.
+ * icon - URL of the icon that represents the item.
+ * ptype - Function or object that represents the prototype cell. If ptype
+ * is a function then it is invoked with no arguments to create new
+ * instances.
+ * pressed - Optional URL of the icon that represents the pressed state.
+ * insert - Optional JavaScript function that handles an insert of the new
+ * cell. This function takes the <mxEditor>, new cell to be inserted, mouse
+ * event and optional <mxCell> under the mouse pointer as arguments.
+ * toggle - Optional boolean that specifies if the item can be toggled.
+ * Default is true.
+ */
+mxDefaultToolbar.prototype.addPrototype = function(title, icon, ptype, pressed, insert, toggle)
+{
+	// Creates a wrapper function that is in charge of constructing
+	// the new cell instance to be inserted into the graph
+	var factory = mxUtils.bind(this, function()
+	{
+		if (typeof(ptype) == 'function')
+		{
+			return ptype();
+		}
+		else if (ptype != null)
+		{
+			return this.editor.graph.cloneCells([ptype])[0];
+		}
+		
+		return null;
+	});
+	
+	// Defines the function for a click event on the graph
+	// after this item has been selected in the toolbar
+	var clickHandler = mxUtils.bind(this, function(evt, cell)
+	{
+		if (typeof(insert) == 'function')
+		{
+			insert(this.editor, factory(), evt, cell);
+		}
+		else
+		{
+			this.drop(factory(), evt, cell);
+		}
+		
+		this.toolbar.resetMode();
+		mxEvent.consume(evt);
+	});
+	
+	var img = this.toolbar.addMode(title, icon, clickHandler, pressed, null, toggle);
+				
+	// Creates a wrapper function that calls the click handler without
+	// the graph argument
+	var dropHandler = function(graph, evt, cell)
+	{
+		clickHandler(evt, cell);
+	};
+	
+	this.installDropHandler(img, dropHandler);
+	
+	return img;
+};
+
+/**
+ * Function: drop
+ * 
+ * Handles a drop from a toolbar item to the graph. The given vertex
+ * represents the new cell to be inserted. This invokes <insert> or
+ * <connect> depending on the given target cell.
+ * 
+ * Parameters:
+ * 
+ * vertex - <mxCell> to be inserted.
+ * evt - Mouse event that represents the drop.
+ * target - Optional <mxCell> that represents the drop target.
+ */
+mxDefaultToolbar.prototype.drop = function(vertex, evt, target)
+{
+	var graph = this.editor.graph;
+	var model = graph.getModel();
+	
+	if (target == null ||
+		model.isEdge(target) ||
+		!this.connectOnDrop ||
+		!graph.isCellConnectable(target))
+	{
+		while (target != null &&
+			!graph.isValidDropTarget(target, [vertex], evt))
+		{
+			target = model.getParent(target);
+		}
+		
+		this.insert(vertex, evt, target);
+	}
+	else
+	{
+		this.connect(vertex, evt, target);
+	}
+};
+
+/**
+ * Function: insert
+ *
+ * Handles a drop by inserting the given vertex into the given parent cell
+ * or the default parent if no parent is specified.
+ * 
+ * Parameters:
+ * 
+ * vertex - <mxCell> to be inserted.
+ * evt - Mouse event that represents the drop.
+ * parent - Optional <mxCell> that represents the parent.
+ */
+mxDefaultToolbar.prototype.insert = function(vertex, evt, target)
+{
+	var graph = this.editor.graph;
+	
+	if (graph.canImportCell(vertex))
+	{
+		var x = mxEvent.getClientX(evt);
+		var y = mxEvent.getClientY(evt);
+		var pt = mxUtils.convertPoint(graph.container, x, y);
+		
+		// Splits the target edge or inserts into target group
+		if (graph.isSplitEnabled() &&
+			graph.isSplitTarget(target, [vertex], evt))
+		{
+			return graph.splitEdge(target, [vertex], null, pt.x, pt.y);
+		}
+		else
+		{
+			return this.editor.addVertex(target, vertex, pt.x, pt.y);
+		}
+	}
+	
+	return null;
+};
+
+/**
+ * Function: connect
+ * 
+ * Handles a drop by connecting the given vertex to the given source cell.
+ * 
+ * vertex - <mxCell> to be inserted.
+ * evt - Mouse event that represents the drop.
+ * source - Optional <mxCell> that represents the source terminal.
+ */
+mxDefaultToolbar.prototype.connect = function(vertex, evt, source)
+{
+	var graph = this.editor.graph;
+	var model = graph.getModel();
+	
+	if (source != null &&
+		graph.isCellConnectable(vertex) &&
+		graph.isEdgeValid(null, source, vertex))
+	{
+		var edge = null;
+
+		model.beginUpdate();
+		try
+		{
+			var geo = model.getGeometry(source);
+			var g = model.getGeometry(vertex).clone();
+			
+			// Moves the vertex away from the drop target that will
+			// be used as the source for the new connection
+			g.x = geo.x + (geo.width - g.width) / 2;
+			g.y = geo.y + (geo.height - g.height) / 2;
+			
+			var step = this.spacing * graph.gridSize;
+			var dist = model.getDirectedEdgeCount(source, true) * 20;
+			
+			if (this.editor.horizontalFlow)
+			{
+				g.x += (g.width + geo.width) / 2 + step + dist;
+			}
+			else
+			{
+				g.y += (g.height + geo.height) / 2 + step + dist;
+			}
+			
+			vertex.setGeometry(g);
+			
+			// Fires two add-events with the code below - should be fixed
+			// to only fire one add event for both inserts
+			var parent = model.getParent(source);
+			graph.addCell(vertex, parent);
+			graph.constrainChild(vertex);
+
+			// Creates the edge using the editor instance and calls
+			// the second function that fires an add event
+			edge = this.editor.createEdge(source, vertex);
+			
+			if (model.getGeometry(edge) == null)
+			{
+				var edgeGeometry = new mxGeometry();
+				edgeGeometry.relative = true;
+				
+				model.setGeometry(edge, edgeGeometry);
+			}
+			
+			graph.addEdge(edge, parent, source, vertex);
+		}
+		finally
+		{
+			model.endUpdate();
+		}
+		
+		graph.setSelectionCells([vertex, edge]);
+		graph.scrollCellToVisible(vertex);
+	}
+};
+
+/**
+ * Function: installDropHandler
+ * 
+ * Makes the given img draggable using the given function for handling a
+ * drop event.
+ * 
+ * Parameters:
+ * 
+ * img - DOM node that represents the image.
+ * dropHandler - Function that handles a drop of the image.
+ */
+mxDefaultToolbar.prototype.installDropHandler = function (img, dropHandler)
+{
+	var sprite = document.createElement('img');
+	sprite.setAttribute('src', img.getAttribute('src'));
+
+	// Handles delayed loading of the images
+	var loader = mxUtils.bind(this, function(evt)
+	{
+		// Preview uses the image node with double size. Later this can be
+		// changed to use a separate preview and guides, but for this the
+		// dropHandler must use the additional x- and y-arguments and the
+		// dragsource which makeDraggable returns much be configured to
+		// use guides via mxDragSource.isGuidesEnabled.
+		sprite.style.width = (2 * img.offsetWidth) + 'px';
+		sprite.style.height = (2 * img.offsetHeight) + 'px';
+
+		mxUtils.makeDraggable(img, this.editor.graph, dropHandler,
+			sprite);
+		mxEvent.removeListener(sprite, 'load', loader);
+	});
+
+	if (mxClient.IS_IE)
+	{
+		loader();
+	}
+	else
+	{
+		mxEvent.addListener(sprite, 'load', loader);
+	}	
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the <toolbar> associated with this object and removes all
+ * installed listeners. This does normally not need to be called, the
+ * <toolbar> is destroyed automatically when the window unloads (in IE) by
+ * <mxEditor>.
+ */
+mxDefaultToolbar.prototype.destroy = function ()
+{
+	if (this.resetHandler != null)
+	{
+		this.editor.graph.removeListener('dblclick', this.resetHandler);
+		this.editor.removeListener('escape', this.resetHandler);
+		this.resetHandler = null;
+	}
+	
+	if (this.toolbar != null)
+	{
+		this.toolbar.destroy();
+		this.toolbar = null;
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/editor/mxEditor.js b/airavata-kubernetes/web-console/src/assets/js/editor/mxEditor.js
new file mode 100644
index 0000000..3aec641
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/editor/mxEditor.js
@@ -0,0 +1,3114 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxEditor
+ *
+ * Extends <mxEventSource> to implement a application wrapper for a graph that
+ * adds <actions>, I/O using <mxCodec>, auto-layout using <mxLayoutManager>,
+ * command history using <undoManager>, and standard dialogs and widgets, eg.
+ * properties, help, outline, toolbar, and popupmenu. It also adds <templates>
+ * to be used as cells in toolbars, auto-validation using the <validation>
+ * flag, attribute cycling using <cycleAttributeValues>, higher-level events
+ * such as <root>, and backend integration using <urlPost> and <urlImage>. 
+ * 
+ * Actions:
+ * 
+ * Actions are functions stored in the <actions> array under their names. The
+ * functions take the <mxEditor> as the first, and an optional <mxCell> as the
+ * second argument and are invoked using <execute>. Any additional arguments
+ * passed to execute are passed on to the action as-is.
+ * 
+ * A list of built-in actions is available in the <addActions> description.
+ * 
+ * Read/write Diagrams:
+ * 
+ * To read a diagram from an XML string, for example from a textfield within the 
+ * page, the following code is used:
+ * 
+ * (code)
+ * var doc = mxUtils.parseXML(xmlString);
+ * var node = doc.documentElement;
+ * editor.readGraphModel(node);
+ * (end)
+ * 
+ * For reading a diagram from a remote location, use the <open> method.
+ * 
+ * To save diagrams in XML on a server, you can set the <urlPost> variable. 
+ * This variable will be used in <getUrlPost> to construct a URL for the post 
+ * request that is issued in the <save> method. The post request contains the 
+ * XML representation of the diagram as returned by <writeGraphModel> in the 
+ * xml parameter.
+ * 
+ * On the server side, the post request is processed using standard
+ * technologies such as Java Servlets, CGI, .NET or ASP.
+ * 
+ * Here are some examples of processing a post request in various languages.
+ * 
+ * - Java: URLDecoder.decode(request.getParameter("xml"), "UTF-8").replace("\n", "&#xa;")
+ * 
+ * Note that the linefeeds should only be replaced if the XML is
+ * processed in Java, for example when creating an image, but not
+ * if the XML is passed back to the client-side.
+ * 
+ * - .NET: HttpUtility.UrlDecode(context.Request.Params["xml"])
+ * - PHP: urldecode($_POST["xml"])
+ * 
+ * Creating images:
+ * 
+ * A backend (Java, PHP or C#) is required for creating images. The
+ * distribution contains an example for each backend (ImageHandler.java,
+ * ImageHandler.cs and graph.php). More information about using a backend
+ * to create images can be found in the readme.html files. Note that the
+ * preview is implemented using VML/SVG in the browser and does not require
+ * a backend. The backend is only required to creates images (bitmaps).
+ * 
+ * Special characters:
+ * 
+ * Note There are five characters that should always appear in XML content as
+ * escapes, so that they do not interact with the syntax of the markup. These
+ * are part of the language for all documents based on XML and for HTML.
+ * 
+ * - &lt; (<)
+ * - &gt; (>)
+ * - &amp; (&)
+ * - &quot; (")
+ * - &apos; (')
+ * 
+ * Although it is part of the XML language, &apos; is not defined in HTML.
+ * For this reason the XHTML specification recommends instead the use of
+ * &#39; if text may be passed to a HTML user agent.
+ * 
+ * If you are having problems with special characters on the server-side then
+ * you may want to try the <escapePostData> flag.
+ * 
+ * For converting decimal escape sequences inside strings, a user has provided
+ * us with the following function:
+ * 
+ * (code)
+ * function html2js(text)
+ * {
+ *   var entitySearch = /&#[0-9]+;/;
+ *   var entity;
+ *   
+ *   while (entity = entitySearch.exec(text))
+ *   {
+ *     var charCode = entity[0].substring(2, entity[0].length -1);
+ *     text = text.substring(0, entity.index)
+ *            + String.fromCharCode(charCode)
+ *            + text.substring(entity.index + entity[0].length);
+ *   }
+ *   
+ *   return text;
+ * }
+ * (end)
+ * 
+ * Otherwise try using hex escape sequences and the built-in unescape function
+ * for converting such strings.
+ * 
+ * Local Files:
+ * 
+ * For saving and opening local files, no standardized method exists that
+ * works across all browsers. The recommended way of dealing with local files
+ * is to create a backend that streams the XML data back to the browser (echo)
+ * as an attachment so that a Save-dialog is displayed on the client-side and
+ * the file can be saved to the local disk.
+ * 
+ * For example, in PHP the code that does this looks as follows.
+ * 
+ * (code)
+ * $xml = stripslashes($_POST["xml"]);
+ * header("Content-Disposition: attachment; filename=\"diagram.xml\"");
+ * echo($xml);
+ * (end)
+ * 
+ * To open a local file, the file should be uploaded via a form in the browser
+ * and then opened from the server in the editor.
+ * 
+ * Cell Properties:
+ * 
+ * The properties displayed in the properties dialog are the attributes and 
+ * values of the cell's user object, which is an XML node. The XML node is 
+ * defined in the templates section of the config file.
+ * 
+ * The templates are stored in <mxEditor.templates> and contain cells which
+ * are cloned at insertion time to create new vertices by use of drag and
+ * drop from the toolbar. Each entry in the toolbar for adding a new vertex
+ * must refer to an existing template.
+ * 
+ * In the following example, the task node is a business object and only the 
+ * mxCell node and its mxGeometry child contain graph information:
+ * 
+ * (code)
+ * <Task label="Task" description="">
+ *   <mxCell vertex="true">
+ *     <mxGeometry as="geometry" width="72" height="32"/>
+ *   </mxCell>
+ * </Task> 
+ * (end)
+ * 
+ * The idea is that the XML representation is inverse from the in-memory 
+ * representation: The outer XML node is the user object and the inner node is 
+ * the cell. This means the user object of the cell is the Task node with no 
+ * children for the above example:
+ * 
+ * (code)
+ * <Task label="Task" description=""/>
+ * (end)
+ * 
+ * The Task node can have any tag name, attributes and child nodes. The 
+ * <mxCodec> will use the XML hierarchy as the user object, while removing the 
+ * "known annotations", such as the mxCell node. At save-time the cell data 
+ * will be "merged" back into the user object. The user object is only modified 
+ * via the properties dialog during the lifecycle of the cell.
+ * 
+ * In the default implementation of <createProperties>, the user object's
+ * attributes are put into a form for editing. Attributes are changed using
+ * the <mxCellAttributeChange> action in the model. The dialog can be replaced 
+ * by overriding the <createProperties> hook or by replacing the showProperties
+ * action in <actions>. Alternatively, the entry in the config file's popupmenu
+ * section can be modified to invoke a different action.
+ * 
+ * If you want to displey the properties dialog on a doubleclick, you can set
+ * <mxEditor.dblClickAction> to showProperties as follows:
+ * 
+ * (code)
+ * editor.dblClickAction = 'showProperties';
+ * (end)
+ * 
+ * Popupmenu and Toolbar:
+ * 
+ * The toolbar and popupmenu are typically configured using the respective
+ * sections in the config file, that is, the popupmenu is defined as follows:
+ * 
+ * (code)
+ * <mxEditor>
+ *   <mxDefaultPopupMenu as="popupHandler">
+ * 		<add as="cut" action="cut" icon="images/cut.gif"/>
+ *      ...
+ * (end)
+ * 
+ * New entries can be added to the toolbar by inserting an add-node into the
+ * above configuration. Existing entries may be removed and changed by
+ * modifying or removing the respective entries in the configuration.
+ * The configuration is read by the <mxDefaultPopupMenuCodec>, the format of the
+ * configuration is explained in <mxDefaultPopupMenu.decode>.
+ * 
+ * The toolbar is defined in the mxDefaultToolbar section. Items can be added
+ * and removed in this section.
+ * 
+ * (code)
+ * <mxEditor>
+ *   <mxDefaultToolbar>
+ *     <add as="save" action="save" icon="images/save.gif"/>
+ *     <add as="Swimlane" template="swimlane" icon="images/swimlane.gif"/>
+ *     ...
+ * (end)
+ * 
+ * The format of the configuration is described in
+ * <mxDefaultToolbarCodec.decode>.
+ * 
+ * Ids:
+ * 
+ * For the IDs, there is an implicit behaviour in <mxCodec>: It moves the Id
+ * from the cell to the user object at encoding time and vice versa at decoding
+ * time. For example, if the Task node from above has an id attribute, then
+ * the <mxCell.id> of the corresponding cell will have this value. If there
+ * is no Id collision in the model, then the cell may be retrieved using this
+ * Id with the <mxGraphModel.getCell> function. If there is a collision, a new
+ * Id will be created for the cell using <mxGraphModel.createId>. At encoding
+ * time, this new Id will replace the value previously stored under the id
+ * attribute in the Task node.
+ * 
+ * See <mxEditorCodec>, <mxDefaultToolbarCodec> and <mxDefaultPopupMenuCodec>
+ * for information about configuring the editor and user interface.
+ * 
+ * Programmatically inserting cells:
+ * 
+ * For inserting a new cell, say, by clicking a button in the document,
+ * the following code can be used. This requires an reference to the editor.
+ * 
+ * (code)
+ * var userObject = new Object();
+ * var parent = editor.graph.getDefaultParent();
+ * var model = editor.graph.model;
+ * model.beginUpdate();
+ * try
+ * {
+ *   editor.graph.insertVertex(parent, null, userObject, 20, 20, 80, 30);
+ * }
+ * finally
+ * {
+ *   model.endUpdate();
+ * }
+ * (end)
+ * 
+ * If a template cell from the config file should be inserted, then a clone
+ * of the template can be created as follows. The clone is then inserted using
+ * the add function instead of addVertex.
+ * 
+ * (code)
+ * var template = editor.templates['task'];
+ * var clone = editor.graph.model.cloneCell(template);
+ * (end)
+ * 
+ * Resources:
+ *
+ * resources/editor - Language resources for mxEditor
+ *
+ * Callback: onInit
+ *
+ * Called from within the constructor. In the callback,
+ * "this" refers to the editor instance.
+ *
+ * Cookie: mxgraph=seen
+ *
+ * Set when the editor is started. Never expires. Use
+ * <resetFirstTime> to reset this cookie. This cookie
+ * only exists if <onInit> is implemented.
+ *
+ * Event: mxEvent.OPEN
+ *
+ * Fires after a file was opened in <open>. The <code>filename</code> property
+ * contains the filename that was used. The same value is also available in
+ * <filename>.
+ *
+ * Event: mxEvent.SAVE
+ *
+ * Fires after the current file was saved in <save>. The <code>url</code>
+ * property contains the URL that was used for saving.
+ *
+ * Event: mxEvent.POST
+ * 
+ * Fires if a successful response was received in <postDiagram>. The
+ * <code>request</code> property contains the <mxXmlRequest>, the
+ * <code>url</code> and <code>data</code> properties contain the URL and the
+ * data that were used in the post request. 
+ *
+ * Event: mxEvent.ROOT
+ *
+ * Fires when the current root has changed, or when the title of the current
+ * root has changed. This event has no properties.
+ *
+ * Event: mxEvent.BEFORE_ADD_VERTEX
+ * 
+ * Fires before a vertex is added in <addVertex>. The <code>vertex</code>
+ * property contains the new vertex and the <code>parent</code> property
+ * contains its parent.
+ * 
+ * Event: mxEvent.ADD_VERTEX
+ * 
+ * Fires between begin- and endUpdate in <addVertex>. The <code>vertex</code>
+ * property contains the vertex that is being inserted.
+ * 
+ * Event: mxEvent.AFTER_ADD_VERTEX
+ * 
+ * Fires after a vertex was inserted and selected in <addVertex>. The
+ * <code>vertex</code> property contains the new vertex.
+ * 
+ * Example:
+ * 
+ * For starting an in-place edit after a new vertex has been added to the
+ * graph, the following code can be used.
+ * 
+ * (code)
+ * editor.addListener(mxEvent.AFTER_ADD_VERTEX, function(sender, evt)
+ * {
+ *   var vertex = evt.getProperty('vertex');
+ * 
+ *   if (editor.graph.isCellEditable(vertex))
+ *   {
+ *   	editor.graph.startEditingAtCell(vertex);
+ *   }
+ * });
+ * (end)
+ * 
+ * Event: mxEvent.ESCAPE
+ * 
+ * Fires when the escape key is pressed. The <code>event</code> property
+ * contains the key event.
+ * 
+ * Constructor: mxEditor
+ *
+ * Constructs a new editor. This function invokes the <onInit> callback
+ * upon completion.
+ *
+ * Example:
+ *
+ * (code)
+ * var config = mxUtils.load('config/diagrameditor.xml').getDocumentElement();
+ * var editor = new mxEditor(config);
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * config - Optional XML node that contains the configuration.
+ */
+function mxEditor(config)
+{
+	this.actions = [];
+	this.addActions();
+
+	// Executes the following only if a document has been instanciated.
+	// That is, don't execute when the editorcodec is setup.
+	if (document.body != null)
+	{
+		// Defines instance fields
+		this.cycleAttributeValues = [];
+		this.popupHandler = new mxDefaultPopupMenu();
+		this.undoManager = new mxUndoManager();
+
+		// Creates the graph and toolbar without the containers
+		this.graph = this.createGraph();
+		this.toolbar = this.createToolbar();
+
+		// Creates the global keyhandler (requires graph instance)
+		this.keyHandler = new mxDefaultKeyHandler(this);
+
+		// Configures the editor using the URI
+		// which was passed to the ctor
+		this.configure(config);
+		
+		// Assigns the swimlaneIndicatorColorAttribute on the graph
+		this.graph.swimlaneIndicatorColorAttribute = this.cycleAttributeName;
+
+		// Checks if the <onInit> hook has been set
+		if (this.onInit != null)
+		{
+			// Invokes the <onInit> hook
+			this.onInit();
+		}
+		
+		// Automatic deallocation of memory
+		if (mxClient.IS_IE)
+		{
+			mxEvent.addListener(window, 'unload', mxUtils.bind(this, function()
+			{
+				this.destroy();
+			}));
+		}
+	}
+};
+
+/**
+ * Installs the required language resources at class
+ * loading time.
+ */
+if (mxLoadResources)
+{
+	mxResources.add(mxClient.basePath+'/resources/editor');
+}
+
+/**
+ * Extends mxEventSource.
+ */
+mxEditor.prototype = new mxEventSource();
+mxEditor.prototype.constructor = mxEditor;
+
+/**
+ * Group: Controls and Handlers
+ */
+	
+/**
+ * Variable: askZoomResource
+ * 
+ * Specifies the resource key for the zoom dialog. If the resource for this
+ * key does not exist then the value is used as the error message. Default
+ * is 'askZoom'.
+ */
+mxEditor.prototype.askZoomResource = (mxClient.language != 'none') ? 'askZoom' : '';
+	
+/**
+ * Variable: lastSavedResource
+ * 
+ * Specifies the resource key for the last saved info. If the resource for
+ * this key does not exist then the value is used as the error message.
+ * Default is 'lastSaved'.
+ */
+mxEditor.prototype.lastSavedResource = (mxClient.language != 'none') ? 'lastSaved' : '';
+	
+/**
+ * Variable: currentFileResource
+ * 
+ * Specifies the resource key for the current file info. If the resource for
+ * this key does not exist then the value is used as the error message.
+ * Default is 'lastSaved'.
+ */
+mxEditor.prototype.currentFileResource = (mxClient.language != 'none') ? 'currentFile' : '';
+	
+/**
+ * Variable: propertiesResource
+ * 
+ * Specifies the resource key for the properties window title. If the
+ * resource for this key does not exist then the value is used as the
+ * error message. Default is 'properties'.
+ */
+mxEditor.prototype.propertiesResource = (mxClient.language != 'none') ? 'properties' : '';
+	
+/**
+ * Variable: tasksResource
+ * 
+ * Specifies the resource key for the tasks window title. If the
+ * resource for this key does not exist then the value is used as the
+ * error message. Default is 'tasks'.
+ */
+mxEditor.prototype.tasksResource = (mxClient.language != 'none') ? 'tasks' : '';
+	
+/**
+ * Variable: helpResource
+ * 
+ * Specifies the resource key for the help window title. If the
+ * resource for this key does not exist then the value is used as the
+ * error message. Default is 'help'.
+ */
+mxEditor.prototype.helpResource = (mxClient.language != 'none') ? 'help' : '';
+	
+/**
+ * Variable: outlineResource
+ * 
+ * Specifies the resource key for the outline window title. If the
+ * resource for this key does not exist then the value is used as the
+ * error message. Default is 'outline'.
+ */
+mxEditor.prototype.outlineResource = (mxClient.language != 'none') ? 'outline' : '';
+	
+/**
+ * Variable: outline
+ * 
+ * Reference to the <mxWindow> that contains the outline. The <mxOutline>
+ * is stored in outline.outline.
+ */
+mxEditor.prototype.outline = null;
+
+/**
+ * Variable: graph
+ *
+ * Holds a <mxGraph> for displaying the diagram. The graph
+ * is created in <setGraphContainer>.
+ */
+mxEditor.prototype.graph = null;
+
+/**
+ * Variable: graphRenderHint
+ *
+ * Holds the render hint used for creating the
+ * graph in <setGraphContainer>. See <mxGraph>.
+ * Default is null.
+ */
+mxEditor.prototype.graphRenderHint = null;
+
+/**
+ * Variable: toolbar
+ *
+ * Holds a <mxDefaultToolbar> for displaying the toolbar. The
+ * toolbar is created in <setToolbarContainer>.
+ */
+mxEditor.prototype.toolbar = null;
+
+/**
+ * Variable: status
+ *
+ * DOM container that holds the statusbar. Default is null.
+ * Use <setStatusContainer> to set this value.
+ */
+mxEditor.prototype.status = null;
+
+/**
+ * Variable: popupHandler
+ *
+ * Holds a <mxDefaultPopupMenu> for displaying
+ * popupmenus.
+ */
+mxEditor.prototype.popupHandler = null;
+
+/**
+ * Variable: undoManager
+ *
+ * Holds an <mxUndoManager> for the command history.
+ */
+mxEditor.prototype.undoManager = null;
+
+/**
+ * Variable: keyHandler
+ *
+ * Holds a <mxDefaultKeyHandler> for handling keyboard events.
+ * The handler is created in <setGraphContainer>.
+ */
+mxEditor.prototype.keyHandler = null;
+
+/**
+ * Group: Actions and Options
+ */
+
+/**
+ * Variable: actions
+ *
+ * Maps from actionnames to actions, which are functions taking
+ * the editor and the cell as arguments. Use <addAction>
+ * to add or replace an action and <execute> to execute an action
+ * by name, passing the cell to be operated upon as the second
+ * argument.
+ */
+mxEditor.prototype.actions = null;
+
+/**
+ * Variable: dblClickAction
+ *
+ * Specifies the name of the action to be executed
+ * when a cell is double clicked. Default is edit.
+ * 
+ * To handle a singleclick, use the following code.
+ * 
+ * (code)
+ * editor.graph.addListener(mxEvent.CLICK, function(sender, evt)
+ * {
+ *   var e = evt.getProperty('event');
+ *   var cell = evt.getProperty('cell');
+ * 
+ *   if (cell != null && !e.isConsumed())
+ *   {
+ *     // Do something useful with cell...
+ *     e.consume();
+ *   }
+ * });
+ * (end)
+ */
+mxEditor.prototype.dblClickAction = 'edit';
+
+/**
+ * Variable: swimlaneRequired
+ * 
+ * Specifies if new cells must be inserted
+ * into an existing swimlane. Otherwise, cells
+ * that are not swimlanes can be inserted as
+ * top-level cells. Default is false.
+ */
+mxEditor.prototype.swimlaneRequired = false;
+
+/**
+ * Variable: disableContextMenu
+ *
+ * Specifies if the context menu should be disabled in the graph container.
+ * Default is true.
+ */
+mxEditor.prototype.disableContextMenu = true;
+
+/**
+ * Group: Templates
+ */
+
+/**
+ * Variable: insertFunction
+ *
+ * Specifies the function to be used for inserting new
+ * cells into the graph. This is assigned from the
+ * <mxDefaultToolbar> if a vertex-tool is clicked.
+ */
+mxEditor.prototype.insertFunction = null;
+
+/**
+ * Variable: forcedInserting
+ *
+ * Specifies if a new cell should be inserted on a single
+ * click even using <insertFunction> if there is a cell 
+ * under the mousepointer, otherwise the cell under the 
+ * mousepointer is selected. Default is false.
+ */
+mxEditor.prototype.forcedInserting = false;
+
+/**
+ * Variable: templates
+ * 
+ * Maps from names to protoype cells to be used
+ * in the toolbar for inserting new cells into
+ * the diagram.
+ */
+mxEditor.prototype.templates = null;
+
+/**
+ * Variable: defaultEdge
+ * 
+ * Prototype edge cell that is used for creating
+ * new edges.
+ */
+mxEditor.prototype.defaultEdge = null;
+
+/**
+ * Variable: defaultEdgeStyle
+ * 
+ * Specifies the edge style to be returned in <getEdgeStyle>.
+ * Default is null.
+ */
+mxEditor.prototype.defaultEdgeStyle = null;
+
+/**
+ * Variable: defaultGroup
+ * 
+ * Prototype group cell that is used for creating
+ * new groups.
+ */
+mxEditor.prototype.defaultGroup = null;
+
+/**
+ * Variable: graphRenderHint
+ *
+ * Default size for the border of new groups. If null,
+ * then then <mxGraph.gridSize> is used. Default is
+ * null.
+ */
+mxEditor.prototype.groupBorderSize = null;
+
+/**
+ * Group: Backend Integration
+ */
+
+/**
+ * Variable: filename
+ *
+ * Contains the URL of the last opened file as a string.
+ * Default is null.
+ */
+mxEditor.prototype.filename = null;
+
+/**
+ * Variable: lineFeed
+ *
+ * Character to be used for encoding linefeeds in <save>. Default is '&#xa;'.
+ */
+mxEditor.prototype.linefeed = '&#xa;';
+
+/**
+ * Variable: postParameterName
+ *
+ * Specifies if the name of the post parameter that contains the diagram
+ * data in a post request to the server. Default is xml.
+ */
+mxEditor.prototype.postParameterName = 'xml';
+
+/**
+ * Variable: escapePostData
+ *
+ * Specifies if the data in the post request for saving a diagram
+ * should be converted using encodeURIComponent. Default is true.
+ */
+mxEditor.prototype.escapePostData = true;
+
+/**
+ * Variable: urlPost
+ *
+ * Specifies the URL to be used for posting the diagram
+ * to a backend in <save>.
+ */
+mxEditor.prototype.urlPost = null;
+
+/**
+ * Variable: urlImage
+ *
+ * Specifies the URL to be used for creating a bitmap of
+ * the graph in the image action.
+ */
+mxEditor.prototype.urlImage = null;
+
+/**
+ * Group: Autolayout
+ */
+
+/**
+ * Variable: horizontalFlow
+ *
+ * Specifies the direction of the flow
+ * in the diagram. This is used in the
+ * layout algorithms. Default is false,
+ * ie. vertical flow.
+ */
+mxEditor.prototype.horizontalFlow = false;
+
+/**
+ * Variable: layoutDiagram
+ *
+ * Specifies if the top-level elements in the
+ * diagram should be layed out using a vertical
+ * or horizontal stack depending on the setting
+ * of <horizontalFlow>. The spacing between the
+ * swimlanes is specified by <swimlaneSpacing>.
+ * Default is false.
+ * 
+ * If the top-level elements are swimlanes, then
+ * the intra-swimlane layout is activated by
+ * the <layoutSwimlanes> switch.
+ */
+mxEditor.prototype.layoutDiagram = false;
+
+/**
+ * Variable: swimlaneSpacing
+ *
+ * Specifies the spacing between swimlanes if
+ * automatic layout is turned on in
+ * <layoutDiagram>. Default is 0.
+ */
+mxEditor.prototype.swimlaneSpacing = 0;
+
+/**
+ * Variable: maintainSwimlanes
+ * 
+ * Specifies if the swimlanes should be kept at the same
+ * width or height depending on the setting of
+ * <horizontalFlow>.  Default is false.
+ * 
+ * For horizontal flows, all swimlanes
+ * have the same height and for vertical flows, all swimlanes
+ * have the same width. Furthermore, the swimlanes are
+ * automatically "stacked" if <layoutDiagram> is true.
+ */
+mxEditor.prototype.maintainSwimlanes = false;
+
+/**
+ * Variable: layoutSwimlanes
+ *
+ * Specifies if the children of swimlanes should
+ * be layed out, either vertically or horizontally
+ * depending on <horizontalFlow>.
+ * Default is false.
+ */
+mxEditor.prototype.layoutSwimlanes = false;
+
+/**
+ * Group: Attribute Cycling
+ */
+ 
+/**
+ * Variable: cycleAttributeValues
+ * 
+ * Specifies the attribute values to be cycled when
+ * inserting new swimlanes. Default is an empty
+ * array.
+ */
+mxEditor.prototype.cycleAttributeValues = null;
+
+/**
+ * Variable: cycleAttributeIndex
+ * 
+ * Index of the last consumed attribute index. If a new
+ * swimlane is inserted, then the <cycleAttributeValues>
+ * at this index will be used as the value for
+ * <cycleAttributeName>. Default is 0.
+ */
+mxEditor.prototype.cycleAttributeIndex = 0;
+
+/**
+ * Variable: cycleAttributeName
+ * 
+ * Name of the attribute to be assigned a <cycleAttributeValues>
+ * when inserting new swimlanes. Default is fillColor.
+ */
+mxEditor.prototype.cycleAttributeName = 'fillColor';
+
+/**
+ * Group: Windows
+ */
+
+/**
+ * Variable: tasks
+ * 
+ * Holds the <mxWindow> created in <showTasks>.
+ */
+mxEditor.prototype.tasks = null;
+
+/**
+ * Variable: tasksWindowImage
+ *
+ * Icon for the tasks window.
+ */
+mxEditor.prototype.tasksWindowImage = null;
+
+/**
+ * Variable: tasksTop
+ * 
+ * Specifies the top coordinate of the tasks window in pixels.
+ * Default is 20.
+ */
+mxEditor.prototype.tasksTop = 20;
+
+/**
+ * Variable: help
+ * 
+ * Holds the <mxWindow> created in <showHelp>.
+ */
+mxEditor.prototype.help = null;
+
+/**
+ * Variable: helpWindowImage
+ *
+ * Icon for the help window.
+ */
+mxEditor.prototype.helpWindowImage = null;
+
+/**
+ * Variable: urlHelp
+ *
+ * Specifies the URL to be used for the contents of the
+ * Online Help window. This is usually specified in the
+ * resources file under urlHelp for language-specific
+ * online help support.
+ */
+mxEditor.prototype.urlHelp = null;
+
+/**
+ * Variable: helpWidth
+ * 
+ * Specifies the width of the help window in pixels.
+ * Default is 300.
+ */
+mxEditor.prototype.helpWidth = 300;
+	
+/**
+ * Variable: helpWidth
+ * 
+ * Specifies the width of the help window in pixels.
+ * Default is 260.
+ */
+mxEditor.prototype.helpHeight = 260;
+
+/**
+ * Variable: propertiesWidth
+ * 
+ * Specifies the width of the properties window in pixels.
+ * Default is 240.
+ */
+mxEditor.prototype.propertiesWidth = 240;
+		
+/**
+ * Variable: propertiesHeight
+ * 
+ * Specifies the height of the properties window in pixels.
+ * If no height is specified then the window will be automatically
+ * sized to fit its contents. Default is null.
+ */
+mxEditor.prototype.propertiesHeight = null;
+		
+/**
+ * Variable: movePropertiesDialog
+ *
+ * Specifies if the properties dialog should be automatically
+ * moved near the cell it is displayed for, otherwise the
+ * dialog is not moved. This value is only taken into 
+ * account if the dialog is already visible. Default is false.
+ */
+mxEditor.prototype.movePropertiesDialog = false;
+
+/**
+ * Variable: validating
+ *
+ * Specifies if <mxGraph.validateGraph> should automatically be invoked after
+ * each change. Default is false.
+ */
+mxEditor.prototype.validating = false;
+
+/**
+ * Variable: modified
+ *
+ * True if the graph has been modified since it was last saved.
+ */
+mxEditor.prototype.modified = false;
+
+/**
+ * Function: isModified
+ * 
+ * Returns <modified>.
+ */
+mxEditor.prototype.isModified = function ()
+{
+	return this.modified;
+};
+
+/**
+ * Function: setModified
+ * 
+ * Sets <modified> to the specified boolean value.
+ */
+mxEditor.prototype.setModified = function (value)
+{
+	this.modified = value;
+};
+
+/**
+ * Function: addActions
+ *
+ * Adds the built-in actions to the editor instance.
+ *
+ * save - Saves the graph using <urlPost>.
+ * print - Shows the graph in a new print preview window.
+ * show - Shows the graph in a new window.
+ * exportImage - Shows the graph as a bitmap image using <getUrlImage>.
+ * refresh - Refreshes the graph's display.
+ * cut - Copies the current selection into the clipboard
+ * and removes it from the graph.
+ * copy - Copies the current selection into the clipboard.
+ * paste - Pastes the clipboard into the graph.
+ * delete - Removes the current selection from the graph.
+ * group - Puts the current selection into a new group.
+ * ungroup - Removes the selected groups and selects the children.
+ * undo - Undoes the last change on the graph model.
+ * redo - Redoes the last change on the graph model.
+ * zoom - Sets the zoom via a dialog.
+ * zoomIn - Zooms into the graph.
+ * zoomOut - Zooms out of the graph
+ * actualSize - Resets the scale and translation on the graph.
+ * fit - Changes the scale so that the graph fits into the window.
+ * showProperties - Shows the properties dialog.
+ * selectAll - Selects all cells.
+ * selectNone - Clears the selection.
+ * selectVertices - Selects all vertices.
+ * selectEdges = Selects all edges.
+ * edit - Starts editing the current selection cell.
+ * enterGroup - Drills down into the current selection cell.
+ * exitGroup - Moves up in the drilling hierachy
+ * home - Moves to the topmost parent in the drilling hierarchy
+ * selectPrevious - Selects the previous cell.
+ * selectNext - Selects the next cell.
+ * selectParent - Selects the parent of the selection cell.
+ * selectChild - Selects the first child of the selection cell.
+ * collapse - Collapses the currently selected cells.
+ * expand - Expands the currently selected cells.
+ * bold - Toggle bold text style.
+ * italic - Toggle italic text style.
+ * underline - Toggle underline text style.
+ * alignCellsLeft - Aligns the selection cells at the left.
+ * alignCellsCenter - Aligns the selection cells in the center.
+ * alignCellsRight - Aligns the selection cells at the right.
+ * alignCellsTop - Aligns the selection cells at the top.
+ * alignCellsMiddle - Aligns the selection cells in the middle.
+ * alignCellsBottom - Aligns the selection cells at the bottom.
+ * alignFontLeft - Sets the horizontal text alignment to left.
+ * alignFontCenter - Sets the horizontal text alignment to center.
+ * alignFontRight - Sets the horizontal text alignment to right.
+ * alignFontTop - Sets the vertical text alignment to top.
+ * alignFontMiddle - Sets the vertical text alignment to middle.
+ * alignFontBottom - Sets the vertical text alignment to bottom.
+ * toggleTasks - Shows or hides the tasks window.
+ * toggleHelp - Shows or hides the help window.
+ * toggleOutline - Shows or hides the outline window.
+ * toggleConsole - Shows or hides the console window.
+ */
+mxEditor.prototype.addActions = function ()
+{
+	this.addAction('save', function(editor)
+	{
+		editor.save();
+	});
+	
+	this.addAction('print', function(editor)
+	{
+		var preview = new mxPrintPreview(editor.graph, 1);
+		preview.open();
+	});
+	
+	this.addAction('show', function(editor)
+	{
+		mxUtils.show(editor.graph, null, 10, 10);
+	});
+
+	this.addAction('exportImage', function(editor)
+	{
+		var url = editor.getUrlImage();
+		
+		if (url == null || mxClient.IS_LOCAL)
+		{
+			editor.execute('show');
+		}
+		else
+		{
+			var node = mxUtils.getViewXml(editor.graph, 1);
+			var xml = mxUtils.getXml(node, '\n');
+
+			mxUtils.submit(url, editor.postParameterName + '=' +
+				encodeURIComponent(xml), document, '_blank');
+		}
+	});
+	
+	this.addAction('refresh', function(editor)
+	{
+		editor.graph.refresh();
+	});
+	
+	this.addAction('cut', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			mxClipboard.cut(editor.graph);
+		}
+	});
+	
+	this.addAction('copy', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			mxClipboard.copy(editor.graph);
+		}
+	});
+	
+	this.addAction('paste', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			mxClipboard.paste(editor.graph);
+		}
+	});
+	
+	this.addAction('delete', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.removeCells();
+		}
+	});
+	
+	this.addAction('group', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.setSelectionCell(editor.groupCells());
+		}
+	});
+	
+	this.addAction('ungroup', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.setSelectionCells(editor.graph.ungroupCells());
+		}
+	});
+	
+	this.addAction('removeFromParent', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.removeCellsFromParent();
+		}
+	});
+	
+	this.addAction('undo', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.undo();
+		}
+	});
+	
+	this.addAction('redo', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.redo();
+		}
+	});
+	
+	this.addAction('zoomIn', function(editor)
+	{
+		editor.graph.zoomIn();
+	});
+	
+	this.addAction('zoomOut', function(editor)
+	{
+		editor.graph.zoomOut();
+	});
+	
+	this.addAction('actualSize', function(editor)
+	{
+		editor.graph.zoomActual();
+	});
+	
+	this.addAction('fit', function(editor)
+	{
+		editor.graph.fit();
+	});
+	
+	this.addAction('showProperties', function(editor, cell)
+	{
+		editor.showProperties(cell);
+	});
+	
+	this.addAction('selectAll', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.selectAll();
+		}
+	});
+	
+	this.addAction('selectNone', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.clearSelection();
+		}
+	});
+	
+	this.addAction('selectVertices', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.selectVertices();
+		}
+	});
+	
+	this.addAction('selectEdges', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.selectEdges();
+		}
+	});
+	
+	this.addAction('edit', function(editor, cell)
+	{
+		if (editor.graph.isEnabled() &&
+			editor.graph.isCellEditable(cell))
+		{
+			editor.graph.startEditingAtCell(cell);
+		}
+	});
+	
+	this.addAction('toBack', function(editor, cell)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.orderCells(true);
+		}
+	});
+	
+	this.addAction('toFront', function(editor, cell)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.orderCells(false);
+		}
+	});
+	
+	this.addAction('enterGroup', function(editor, cell)
+	{
+		editor.graph.enterGroup(cell);
+	});
+	
+	this.addAction('exitGroup', function(editor)
+	{
+		editor.graph.exitGroup();
+	});
+	
+	this.addAction('home', function(editor)
+	{
+		editor.graph.home();
+	});
+	
+	this.addAction('selectPrevious', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.selectPreviousCell();
+		}
+	});
+	
+	this.addAction('selectNext', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.selectNextCell();
+		}
+	});
+	
+	this.addAction('selectParent', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.selectParentCell();
+		}
+	});
+	
+	this.addAction('selectChild', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.selectChildCell();
+		}
+	});
+	
+	this.addAction('collapse', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.foldCells(true);
+		}
+	});
+	
+	this.addAction('collapseAll', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			var cells = editor.graph.getChildVertices();
+			editor.graph.foldCells(true, false, cells);
+		}
+	});
+	
+	this.addAction('expand', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.foldCells(false);
+		}
+	});
+	
+	this.addAction('expandAll', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			var cells = editor.graph.getChildVertices();
+			editor.graph.foldCells(false, false, cells);
+		}
+	});
+	
+	this.addAction('bold', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.toggleCellStyleFlags(
+				mxConstants.STYLE_FONTSTYLE,
+				mxConstants.FONT_BOLD);
+		}
+	});
+	
+	this.addAction('italic', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.toggleCellStyleFlags(
+				mxConstants.STYLE_FONTSTYLE,
+				mxConstants.FONT_ITALIC);
+		}
+	});
+	
+	this.addAction('underline', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.toggleCellStyleFlags(
+				mxConstants.STYLE_FONTSTYLE,
+				mxConstants.FONT_UNDERLINE);
+		}
+	});
+
+	this.addAction('alignCellsLeft', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.alignCells(mxConstants.ALIGN_LEFT);
+		}
+	});
+	
+	this.addAction('alignCellsCenter', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.alignCells(mxConstants.ALIGN_CENTER);
+		}
+	});
+	
+	this.addAction('alignCellsRight', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.alignCells(mxConstants.ALIGN_RIGHT);
+		}
+	});
+	
+	this.addAction('alignCellsTop', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.alignCells(mxConstants.ALIGN_TOP);
+		}
+	});
+	
+	this.addAction('alignCellsMiddle', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.alignCells(mxConstants.ALIGN_MIDDLE);
+		}
+	});
+	
+	this.addAction('alignCellsBottom', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.alignCells(mxConstants.ALIGN_BOTTOM);
+		}
+	});
+	
+	this.addAction('alignFontLeft', function(editor)
+	{
+		
+		editor.graph.setCellStyles(
+			mxConstants.STYLE_ALIGN,
+			mxConstants.ALIGN_LEFT);
+	});
+	
+	this.addAction('alignFontCenter', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.setCellStyles(
+				mxConstants.STYLE_ALIGN,
+				mxConstants.ALIGN_CENTER);
+		}
+	});
+	
+	this.addAction('alignFontRight', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.setCellStyles(
+				mxConstants.STYLE_ALIGN,
+				mxConstants.ALIGN_RIGHT);
+		}
+	});
+	
+	this.addAction('alignFontTop', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.setCellStyles(
+				mxConstants.STYLE_VERTICAL_ALIGN,
+				mxConstants.ALIGN_TOP);
+		}
+	});
+	
+	this.addAction('alignFontMiddle', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.setCellStyles(
+				mxConstants.STYLE_VERTICAL_ALIGN,
+				mxConstants.ALIGN_MIDDLE);
+		}
+	});
+	
+	this.addAction('alignFontBottom', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.setCellStyles(
+				mxConstants.STYLE_VERTICAL_ALIGN,
+				mxConstants.ALIGN_BOTTOM);
+		}
+	});
+	
+	this.addAction('zoom', function(editor)
+	{
+		var current = editor.graph.getView().scale*100;
+		var scale = parseFloat(mxUtils.prompt(
+			mxResources.get(editor.askZoomResource) ||
+			editor.askZoomResource,
+			current))/100;
+
+		if (!isNaN(scale))
+		{
+			editor.graph.getView().setScale(scale);
+		}
+	});
+	
+	this.addAction('toggleTasks', function(editor)
+	{
+		if (editor.tasks != null)
+		{
+			editor.tasks.setVisible(!editor.tasks.isVisible());
+		}
+		else
+		{
+			editor.showTasks();
+		}
+	});
+	
+	this.addAction('toggleHelp', function(editor)
+	{
+		if (editor.help != null)
+		{
+			editor.help.setVisible(!editor.help.isVisible());
+		}
+		else
+		{
+			editor.showHelp();
+		}
+	});
+	
+	this.addAction('toggleOutline', function(editor)
+	{
+		if (editor.outline == null)
+		{
+			editor.showOutline();
+		}
+		else
+		{
+			editor.outline.setVisible(!editor.outline.isVisible());
+		}
+	});
+	
+	this.addAction('toggleConsole', function(editor)
+	{
+		mxLog.setVisible(!mxLog.isVisible());
+	});
+};
+
+/**
+ * Function: configure
+ *
+ * Configures the editor using the specified node. To load the
+ * configuration from a given URL the following code can be used to obtain
+ * the XML node.
+ * 
+ * (code)
+ * var node = mxUtils.load(url).getDocumentElement();
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * node - XML node that contains the configuration.
+ */
+mxEditor.prototype.configure = function (node)
+{
+	if (node != null)
+	{
+		// Creates a decoder for the XML data
+		// and uses it to configure the editor
+		var dec = new mxCodec(node.ownerDocument);
+		dec.decode(node, this);
+		
+		// Resets the counters, modified state and
+		// command history
+		this.resetHistory();
+	}
+};
+
+/**
+ * Function: resetFirstTime
+ * 
+ * Resets the cookie that is used to remember if the editor has already
+ * been used.
+ */
+mxEditor.prototype.resetFirstTime = function ()
+{
+	document.cookie =
+		'mxgraph=seen; expires=Fri, 27 Jul 2001 02:47:11 UTC; path=/';
+};
+
+/**
+ * Function: resetHistory
+ * 
+ * Resets the command history, modified state and counters.
+ */
+mxEditor.prototype.resetHistory = function ()
+{
+	this.lastSnapshot = new Date().getTime();
+	this.undoManager.clear();
+	this.ignoredChanges = 0;
+	this.setModified(false);
+};
+
+/**
+ * Function: addAction
+ * 
+ * Binds the specified actionname to the specified function.
+ * 
+ * Parameters:
+ * 
+ * actionname - String that specifies the name of the action
+ * to be added.
+ * funct - Function that implements the new action. The first
+ * argument of the function is the editor it is used
+ * with, the second argument is the cell it operates
+ * upon.
+ * 
+ * Example:
+ * (code)
+ * editor.addAction('test', function(editor, cell)
+ * {
+ * 		mxUtils.alert("test "+cell);
+ * });
+ * (end)
+ */
+mxEditor.prototype.addAction = function (actionname, funct)
+{
+	this.actions[actionname] = funct;
+};
+
+/**
+ * Function: execute
+ * 
+ * Executes the function with the given name in <actions> passing the
+ * editor instance and given cell as the first and second argument. All
+ * additional arguments are passed to the action as well. This method
+ * contains a try-catch block and displays an error message if an action
+ * causes an exception. The exception is re-thrown after the error
+ * message was displayed.
+ * 
+ * Example:
+ * 
+ * (code)
+ * editor.execute("showProperties", cell);
+ * (end)
+ */
+mxEditor.prototype.execute = function (actionname, cell, evt)
+{
+	var action = this.actions[actionname];
+	
+	if (action != null)
+	{
+		try
+		{
+			// Creates the array of arguments by replacing the actionname
+			// with the editor instance in the args of this function
+			var args = arguments;
+			args[0] = this;
+			
+			// Invokes the function on the editor using the args
+			action.apply(this, args);
+		}
+		catch (e)
+		{
+			mxUtils.error('Cannot execute ' + actionname +
+				': ' + e.message, 280, true);
+			
+			throw e;
+		}
+	}
+	else
+	{
+		mxUtils.error('Cannot find action '+actionname, 280, true);
+	}
+};
+
+/**
+ * Function: addTemplate
+ * 
+ * Adds the specified template under the given name in <templates>.
+ */
+mxEditor.prototype.addTemplate = function (name, template)
+{
+	this.templates[name] = template;
+};
+
+/**
+ * Function: getTemplate
+ * 
+ * Returns the template for the given name.
+ */
+mxEditor.prototype.getTemplate = function (name)
+{
+	return this.templates[name];
+};
+
+/**
+ * Function: createGraph
+ * 
+ * Creates the <graph> for the editor. The graph is created with no
+ * container and is initialized from <setGraphContainer>.
+ */
+mxEditor.prototype.createGraph = function ()
+{
+	var graph = new mxGraph(null, null, this.graphRenderHint);
+	
+	// Enables rubberband, tooltips, panning
+	graph.setTooltips(true);
+	graph.setPanning(true);
+
+	// Overrides the dblclick method on the graph to
+	// invoke the dblClickAction for a cell and reset
+	// the selection tool in the toolbar
+	this.installDblClickHandler(graph);
+	
+	// Installs the command history
+	this.installUndoHandler(graph);
+
+	// Installs the handlers for the root event
+	this.installDrillHandler(graph);
+	
+	// Installs the handler for validation
+	this.installChangeHandler(graph);
+
+	// Installs the handler for calling the
+	// insert function and consume the
+	// event if an insert function is defined
+	this.installInsertHandler(graph);
+
+	// Redirects the function for creating the
+	// popupmenu items
+	graph.popupMenuHandler.factoryMethod =
+		mxUtils.bind(this, function(menu, cell, evt)
+		{
+			return this.createPopupMenu(menu, cell, evt);
+		});
+
+	// Redirects the function for creating
+	// new connections in the diagram
+	graph.connectionHandler.factoryMethod =
+		mxUtils.bind(this, function(source, target)
+		{
+			return this.createEdge(source, target);
+		});
+	
+	// Maintains swimlanes and installs autolayout
+	this.createSwimlaneManager(graph);
+	this.createLayoutManager(graph);
+	
+	return graph;
+};
+
+/**
+ * Function: createSwimlaneManager
+ * 
+ * Sets the graph's container using <mxGraph.init>.
+ */
+mxEditor.prototype.createSwimlaneManager = function (graph)
+{
+	var swimlaneMgr = new mxSwimlaneManager(graph, false);
+
+	swimlaneMgr.isHorizontal = mxUtils.bind(this, function()
+	{
+		return this.horizontalFlow;
+	});
+	
+	swimlaneMgr.isEnabled = mxUtils.bind(this, function()
+	{
+		return this.maintainSwimlanes;
+	});
+	
+	return swimlaneMgr;
+};
+
+/**
+ * Function: createLayoutManager
+ * 
+ * Creates a layout manager for the swimlane and diagram layouts, that
+ * is, the locally defined inter- and intraswimlane layouts.
+ */
+mxEditor.prototype.createLayoutManager = function (graph)
+{
+	var layoutMgr = new mxLayoutManager(graph);
+	
+	var self = this; // closure
+	layoutMgr.getLayout = function(cell)
+	{
+		var layout = null;
+		var model = self.graph.getModel();
+		
+		if (model.getParent(cell) != null)
+		{
+			// Executes the swimlane layout if a child of
+			// a swimlane has been changed. The layout is
+			// lazy created in createSwimlaneLayout.
+			if (self.layoutSwimlanes &&
+				graph.isSwimlane(cell))
+			{
+				if (self.swimlaneLayout == null)
+				{
+					self.swimlaneLayout = self.createSwimlaneLayout();
+				}
+				
+				layout = self.swimlaneLayout;
+			}
+			
+			// Executes the diagram layout if the modified
+			// cell is a top-level cell. The layout is
+			// lazy created in createDiagramLayout.
+			else if (self.layoutDiagram &&
+				(graph.isValidRoot(cell) ||
+				model.getParent(model.getParent(cell)) == null))
+			{
+				if (self.diagramLayout == null)
+				{
+					self.diagramLayout = self.createDiagramLayout();
+				}
+				
+				layout = self.diagramLayout;
+			}
+		}
+			
+		return layout;
+	};
+	
+	return layoutMgr;
+};
+
+/**
+ * Function: setGraphContainer
+ * 
+ * Sets the graph's container using <mxGraph.init>.
+ */
+mxEditor.prototype.setGraphContainer = function (container)
+{
+	if (this.graph.container == null)
+	{
+		// Creates the graph instance inside the given container and render hint
+		//this.graph = new mxGraph(container, null, this.graphRenderHint);
+		this.graph.init(container);
+
+		// Install rubberband selection as the last
+		// action handler in the chain
+		this.rubberband = new mxRubberband(this.graph);
+
+		// Disables the context menu
+		if (this.disableContextMenu)
+		{
+			mxEvent.disableContextMenu(container);
+		}
+
+		// Workaround for stylesheet directives in IE
+		if (mxClient.IS_QUIRKS)
+		{
+			new mxDivResizer(container);
+		}
+	}
+};
+
+/**
+ * Function: installDblClickHandler
+ * 
+ * Overrides <mxGraph.dblClick> to invoke <dblClickAction>
+ * on a cell and reset the selection tool in the toolbar.
+ */
+mxEditor.prototype.installDblClickHandler = function (graph)
+{
+	// Installs a listener for double click events
+	graph.addListener(mxEvent.DOUBLE_CLICK,
+		mxUtils.bind(this, function(sender, evt)
+		{
+			var cell = evt.getProperty('cell');
+			
+			if (cell != null &&
+				graph.isEnabled() &&
+				this.dblClickAction != null)
+			{
+				this.execute(this.dblClickAction, cell);
+				evt.consume();
+			}
+		})
+	);
+};
+		
+/**
+ * Function: installUndoHandler
+ * 
+ * Adds the <undoManager> to the graph model and the view.
+ */
+mxEditor.prototype.installUndoHandler = function (graph)
+{				
+	var listener = mxUtils.bind(this, function(sender, evt)
+	{
+		var edit = evt.getProperty('edit');
+		this.undoManager.undoableEditHappened(edit);
+	});
+	
+	graph.getModel().addListener(mxEvent.UNDO, listener);
+	graph.getView().addListener(mxEvent.UNDO, listener);
+
+	// Keeps the selection state in sync
+	var undoHandler = function(sender, evt)
+	{
+		var changes = evt.getProperty('edit').changes;
+		graph.setSelectionCells(graph.getSelectionCellsForChanges(changes));
+	};
+	
+	this.undoManager.addListener(mxEvent.UNDO, undoHandler);
+	this.undoManager.addListener(mxEvent.REDO, undoHandler);
+};
+		
+/**
+ * Function: installDrillHandler
+ * 
+ * Installs listeners for dispatching the <root> event.
+ */
+mxEditor.prototype.installDrillHandler = function (graph)
+{				
+	var listener = mxUtils.bind(this, function(sender)
+	{
+		this.fireEvent(new mxEventObject(mxEvent.ROOT));
+	});
+	
+	graph.getView().addListener(mxEvent.DOWN, listener);
+	graph.getView().addListener(mxEvent.UP, listener);
+};
+
+/**
+ * Function: installChangeHandler
+ * 
+ * Installs the listeners required to automatically validate
+ * the graph. On each change of the root, this implementation
+ * fires a <root> event.
+ */
+mxEditor.prototype.installChangeHandler = function (graph)
+{
+	var listener = mxUtils.bind(this, function(sender, evt)
+	{
+		// Updates the modified state
+		this.setModified(true);
+
+		// Automatically validates the graph
+		// after each change
+		if (this.validating == true)
+		{
+			graph.validateGraph();
+		}
+
+		// Checks if the root has been changed
+		var changes = evt.getProperty('edit').changes;
+		
+		for (var i = 0; i < changes.length; i++)
+		{
+			var change = changes[i];
+			
+			if (change instanceof mxRootChange ||
+				(change instanceof mxValueChange &&
+				change.cell == this.graph.model.root) ||
+				(change instanceof mxCellAttributeChange &&
+				change.cell == this.graph.model.root))
+			{
+				this.fireEvent(new mxEventObject(mxEvent.ROOT));
+				break;
+			}
+		}
+	});
+	
+	graph.getModel().addListener(mxEvent.CHANGE, listener);
+};
+
+/**
+ * Function: installInsertHandler
+ * 
+ * Installs the handler for invoking <insertFunction> if
+ * one is defined.
+ */
+mxEditor.prototype.installInsertHandler = function (graph)
+{
+	var self = this; // closure
+	var insertHandler =
+	{
+		mouseDown: function(sender, me)
+		{
+			if (self.insertFunction != null &&
+				!me.isPopupTrigger() &&
+				(self.forcedInserting ||
+				me.getState() == null))
+			{
+				self.graph.clearSelection();
+				self.insertFunction(me.getEvent(), me.getCell());
+
+				// Consumes the rest of the events
+				// for this gesture (down, move, up)
+				this.isActive = true;
+				me.consume();
+			}
+		},
+		
+		mouseMove: function(sender, me)
+		{
+			if (this.isActive)
+			{
+				me.consume();
+			}
+		},
+		
+		mouseUp: function(sender, me)
+		{
+			if (this.isActive)
+			{
+				this.isActive = false;
+				me.consume();
+			}
+		}
+	};
+	
+	graph.addMouseListener(insertHandler);
+};
+
+/**
+ * Function: createDiagramLayout
+ * 
+ * Creates the layout instance used to layout the
+ * swimlanes in the diagram.
+ */
+mxEditor.prototype.createDiagramLayout = function ()
+{
+	var gs = this.graph.gridSize;
+	var layout = new mxStackLayout(this.graph, !this.horizontalFlow,
+		 this.swimlaneSpacing, 2*gs, 2*gs);
+	
+	// Overrides isIgnored to only take into account swimlanes
+	layout.isVertexIgnored = function(cell)
+	{
+		return !layout.graph.isSwimlane(cell);
+	};
+	
+	return layout;
+};
+
+/**
+ * Function: createSwimlaneLayout
+ * 
+ * Creates the layout instance used to layout the
+ * children of each swimlane.
+ */
+mxEditor.prototype.createSwimlaneLayout = function ()
+{
+	return new mxCompactTreeLayout(this.graph, this.horizontalFlow);
+};
+
+/**
+ * Function: createToolbar
+ * 
+ * Creates the <toolbar> with no container.
+ */
+mxEditor.prototype.createToolbar = function ()
+{
+	return new mxDefaultToolbar(null, this);
+};
+
+/**
+ * Function: setToolbarContainer
+ * 
+ * Initializes the toolbar for the given container.
+ */
+mxEditor.prototype.setToolbarContainer = function (container)
+{
+	this.toolbar.init(container);
+	
+	// Workaround for stylesheet directives in IE
+	if (mxClient.IS_QUIRKS)
+	{
+		new mxDivResizer(container);
+	}
+};
+
+/**
+ * Function: setStatusContainer
+ * 
+ * Creates the <status> using the specified container.
+ * 
+ * This implementation adds listeners in the editor to 
+ * display the last saved time and the current filename 
+ * in the status bar.
+ * 
+ * Parameters:
+ * 
+ * container - DOM node that will contain the statusbar.
+ */
+mxEditor.prototype.setStatusContainer = function (container)
+{
+	if (this.status == null)
+	{
+		this.status = container;
+		
+		// Prints the last saved time in the status bar
+		// when files are saved
+		this.addListener(mxEvent.SAVE, mxUtils.bind(this, function()
+		{
+			var tstamp = new Date().toLocaleString();
+			this.setStatus((mxResources.get(this.lastSavedResource) ||
+				this.lastSavedResource)+': '+tstamp);
+		}));
+		
+		// Updates the statusbar to display the filename
+		// when new files are opened
+		this.addListener(mxEvent.OPEN, mxUtils.bind(this, function()
+		{
+			this.setStatus((mxResources.get(this.currentFileResource) ||
+				this.currentFileResource)+': '+this.filename);
+		}));
+		
+		// Workaround for stylesheet directives in IE
+		if (mxClient.IS_QUIRKS)
+		{
+			new mxDivResizer(container);
+		}
+	}
+};
+
+/**
+ * Function: setStatus
+ * 
+ * Display the specified message in the status bar.
+ * 
+ * Parameters:
+ * 
+ * message - String the specified the message to
+ * be displayed.
+ */
+mxEditor.prototype.setStatus = function (message)
+{
+	if (this.status != null && message != null)
+	{
+		this.status.innerHTML = message;
+	}
+};
+
+/**
+ * Function: setTitleContainer
+ * 
+ * Creates a listener to update the inner HTML of the
+ * specified DOM node with the value of <getTitle>.
+ * 
+ * Parameters:
+ * 
+ * container - DOM node that will contain the title.
+ */
+mxEditor.prototype.setTitleContainer = function (container)
+{
+	this.addListener(mxEvent.ROOT, mxUtils.bind(this, function(sender)
+	{
+		container.innerHTML = this.getTitle();
+	}));
+
+	// Workaround for stylesheet directives in IE
+	if (mxClient.IS_QUIRKS)
+	{
+		new mxDivResizer(container);
+	}
+};
+
+/**
+ * Function: treeLayout
+ * 
+ * Executes a vertical or horizontal compact tree layout
+ * using the specified cell as an argument. The cell may
+ * either be a group or the root of a tree.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to use in the compact tree layout.
+ * horizontal - Optional boolean to specify the tree's
+ * orientation. Default is true.
+ */
+mxEditor.prototype.treeLayout = function (cell, horizontal)
+{
+	if (cell != null)
+	{
+		var layout = new mxCompactTreeLayout(this.graph, horizontal);
+		layout.execute(cell);
+	}
+};
+
+/**
+ * Function: getTitle
+ * 
+ * Returns the string value for the current root of the
+ * diagram.
+ */
+mxEditor.prototype.getTitle = function ()
+{
+	var title = '';
+	var graph = this.graph;
+	var cell = graph.getCurrentRoot();
+	
+	while (cell != null &&
+		   graph.getModel().getParent(
+				graph.getModel().getParent(cell)) != null)
+	{
+		// Append each label of a valid root
+		if (graph.isValidRoot(cell))
+		{
+			title = ' > ' +
+			graph.convertValueToString(cell) + title;
+		}
+		
+		cell = graph.getModel().getParent(cell);
+	}
+	
+	var prefix = this.getRootTitle();
+	
+	return prefix + title;
+};
+
+/**
+ * Function: getRootTitle
+ * 
+ * Returns the string value of the root cell in
+ * <mxGraph.model>.
+ */
+mxEditor.prototype.getRootTitle = function ()
+{
+	var root = this.graph.getModel().getRoot();
+	return this.graph.convertValueToString(root);
+};
+
+/**
+ * Function: undo
+ * 
+ * Undo the last change in <graph>.
+ */
+mxEditor.prototype.undo = function ()
+{
+	this.undoManager.undo();
+};
+
+/**
+ * Function: redo
+ * 
+ * Redo the last change in <graph>.
+ */
+mxEditor.prototype.redo = function ()
+{
+	this.undoManager.redo();
+};
+
+/**
+ * Function: groupCells
+ * 
+ * Invokes <createGroup> to create a new group cell and the invokes
+ * <mxGraph.groupCells>, using the grid size of the graph as the spacing
+ * in the group's content area.
+ */
+mxEditor.prototype.groupCells = function ()
+{
+	var border = (this.groupBorderSize != null) ?
+		this.groupBorderSize :
+		this.graph.gridSize;
+	return this.graph.groupCells(this.createGroup(), border);
+};
+
+/**
+ * Function: createGroup
+ * 
+ * Creates and returns a clone of <defaultGroup> to be used
+ * as a new group cell in <group>.
+ */
+mxEditor.prototype.createGroup = function ()
+{
+	var model = this.graph.getModel();
+	
+	return model.cloneCell(this.defaultGroup);
+};
+
+/**
+ * Function: open
+ * 
+ * Opens the specified file synchronously and parses it using
+ * <readGraphModel>. It updates <filename> and fires an <open>-event after
+ * the file has been opened. Exceptions should be handled as follows:
+ * 
+ * (code)
+ * try
+ * {
+ *   editor.open(filename);
+ * }
+ * catch (e)
+ * {
+ *   mxUtils.error('Cannot open ' + filename +
+ *     ': ' + e.message, 280, true);
+ * }
+ * (end)
+ *
+ * Parameters:
+ * 
+ * filename - URL of the file to be opened.
+ */
+mxEditor.prototype.open = function (filename)
+{
+	if (filename != null)
+	{
+		var xml = mxUtils.load(filename).getXml();
+		this.readGraphModel(xml.documentElement);
+		this.filename = filename;
+		
+		this.fireEvent(new mxEventObject(mxEvent.OPEN, 'filename', filename));
+	}
+};
+
+/**
+ * Function: readGraphModel
+ * 
+ * Reads the specified XML node into the existing graph model and resets
+ * the command history and modified state.
+ */
+mxEditor.prototype.readGraphModel = function (node)
+{
+	var dec = new mxCodec(node.ownerDocument);
+	dec.decode(node, this.graph.getModel());
+	this.resetHistory();
+};
+
+/**
+ * Function: save
+ * 
+ * Posts the string returned by <writeGraphModel> to the given URL or the
+ * URL returned by <getUrlPost>. The actual posting is carried out by
+ * <postDiagram>. If the URL is null then the resulting XML will be
+ * displayed using <mxUtils.popup>. Exceptions should be handled as
+ * follows:
+ * 
+ * (code)
+ * try
+ * {
+ *   editor.save();
+ * }
+ * catch (e)
+ * {
+ *   mxUtils.error('Cannot save : ' + e.message, 280, true);
+ * }
+ * (end)
+ */
+mxEditor.prototype.save = function (url, linefeed)
+{
+	// Gets the URL to post the data to
+	url = url || this.getUrlPost();
+
+	// Posts the data if the URL is not empty
+	if (url != null && url.length > 0)
+	{
+		var data = this.writeGraphModel(linefeed);
+		this.postDiagram(url, data);
+		
+		// Resets the modified flag
+		this.setModified(false);
+	}
+	
+	// Dispatches a save event
+	this.fireEvent(new mxEventObject(mxEvent.SAVE, 'url', url));
+};
+
+/**
+ * Function: postDiagram
+ * 
+ * Hook for subclassers to override the posting of a diagram
+ * represented by the given node to the given URL. This fires
+ * an asynchronous <post> event if the diagram has been posted.
+ * 
+ * Example:
+ * 
+ * To replace the diagram with the diagram in the response, use the
+ * following code.
+ * 
+ * (code)
+ * editor.addListener(mxEvent.POST, function(sender, evt)
+ * {
+ *   // Process response (replace diagram)
+ *   var req = evt.getProperty('request');
+ *   var root = req.getDocumentElement();
+ *   editor.graph.readGraphModel(root)
+ * });
+ * (end)
+ */
+mxEditor.prototype.postDiagram = function (url, data)
+{
+	if (this.escapePostData)
+	{
+		data = encodeURIComponent(data);
+	}
+
+	mxUtils.post(url, this.postParameterName+'='+data,
+		mxUtils.bind(this, function(req)
+		{
+			this.fireEvent(new mxEventObject(mxEvent.POST,
+				'request', req, 'url', url, 'data', data));
+		})
+	);
+};
+
+/**
+ * Function: writeGraphModel
+ * 
+ * Hook to create the string representation of the diagram. The default
+ * implementation uses an <mxCodec> to encode the graph model as
+ * follows:
+ * 
+ * (code)
+ * var enc = new mxCodec();
+ * var node = enc.encode(this.graph.getModel());
+ * return mxUtils.getXml(node, this.linefeed);
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * linefeed - Optional character to be used as the linefeed. Default is
+ * <linefeed>.
+ */
+mxEditor.prototype.writeGraphModel = function (linefeed)
+{
+	linefeed = (linefeed != null) ? linefeed : this.linefeed;
+	var enc = new mxCodec();
+	var node = enc.encode(this.graph.getModel());
+
+	return mxUtils.getXml(node, linefeed);
+};
+
+/**
+ * Function: getUrlPost
+ * 
+ * Returns the URL to post the diagram to. This is used
+ * in <save>. The default implementation returns <urlPost>,
+ * adding <code>?draft=true</code>.
+ */
+mxEditor.prototype.getUrlPost = function ()
+{
+	return this.urlPost;
+};
+
+/**
+ * Function: getUrlImage
+ * 
+ * Returns the URL to create the image with. This is typically
+ * the URL of a backend which accepts an XML representation
+ * of a graph view to create an image. The function is used
+ * in the image action to create an image. This implementation
+ * returns <urlImage>.
+ */
+mxEditor.prototype.getUrlImage = function ()
+{
+	return this.urlImage;
+};
+
+/**
+ * Function: swapStyles
+ * 
+ * Swaps the styles for the given names in the graph's
+ * stylesheet and refreshes the graph.
+ */
+mxEditor.prototype.swapStyles = function (first, second)
+{
+	var style = this.graph.getStylesheet().styles[second];
+	this.graph.getView().getStylesheet().putCellStyle(
+		second, this.graph.getStylesheet().styles[first]);
+	this.graph.getStylesheet().putCellStyle(first, style);
+	this.graph.refresh();
+};
+
+/**
+ * Function: showProperties
+ * 
+ * Creates and shows the properties dialog for the given
+ * cell. The content area of the dialog is created using
+ * <createProperties>.
+ */
+mxEditor.prototype.showProperties = function (cell)
+{
+	cell = cell || this.graph.getSelectionCell();
+	
+	// Uses the root node for the properties dialog
+	// if not cell was passed in and no cell is
+	// selected
+	if (cell == null)
+	{
+		cell = this.graph.getCurrentRoot();
+		
+		if (cell == null)
+		{
+			cell = this.graph.getModel().getRoot();
+		}
+	}
+	
+	if (cell != null)
+	{
+		// Makes sure there is no in-place editor in the
+		// graph and computes the location of the dialog
+		this.graph.stopEditing(true);
+
+		var offset = mxUtils.getOffset(this.graph.container);
+		var x = offset.x+10;
+		var y = offset.y;
+		
+		// Avoids moving the dialog if it is alredy open
+		if (this.properties != null && !this.movePropertiesDialog)
+		{
+			x = this.properties.getX();
+			y = this.properties.getY();
+		}
+		
+		// Places the dialog near the cell for which it
+		// displays the properties
+		else
+		{
+			var bounds = this.graph.getCellBounds(cell);
+			
+			if (bounds != null)
+			{
+				x += bounds.x+Math.min(200, bounds.width);
+				y += bounds.y;				
+			}			
+		}
+		
+		// Hides the existing properties dialog and creates a new one with the
+		// contents created in the hook method
+		this.hideProperties();
+		var node = this.createProperties(cell);
+		
+		if (node != null)
+		{
+			// Displays the contents in a window and stores a reference to the
+			// window for later hiding of the window
+			this.properties = new mxWindow(mxResources.get(this.propertiesResource) ||
+				this.propertiesResource, node, x, y, this.propertiesWidth, this.propertiesHeight, false);
+			this.properties.setVisible(true);
+		}
+	}
+};
+
+/**
+ * Function: isPropertiesVisible
+ * 
+ * Returns true if the properties dialog is currently visible.
+ */
+mxEditor.prototype.isPropertiesVisible = function ()
+{
+	return this.properties != null;
+};
+
+/**
+ * Function: createProperties
+ * 
+ * Creates and returns the DOM node that represents the contents
+ * of the properties dialog for the given cell. This implementation
+ * works for user objects that are XML nodes and display all the
+ * node attributes in a form.
+ */
+mxEditor.prototype.createProperties = function (cell)
+{
+	var model = this.graph.getModel();
+	var value = model.getValue(cell);
+	
+	if (mxUtils.isNode(value))
+	{
+		// Creates a form for the user object inside
+		// the cell
+		var form = new mxForm('properties');
+		
+		// Adds a readonly field for the cell id
+		var id = form.addText('ID', cell.getId());
+		id.setAttribute('readonly', 'true');
+
+		var geo = null;
+		var yField = null;
+		var xField = null;
+		var widthField = null;
+		var heightField = null;
+
+		// Adds fields for the location and size
+		if (model.isVertex(cell))
+		{
+			geo = model.getGeometry(cell);
+			
+			if (geo != null)
+			{
+				yField = form.addText('top', geo.y);
+				xField = form.addText('left', geo.x);
+				widthField = form.addText('width', geo.width);
+				heightField = form.addText('height', geo.height);
+			}
+		}
+		
+		// Adds a field for the cell style			
+		var tmp = model.getStyle(cell);
+		var style = form.addText('Style', tmp || '');
+		
+		// Creates textareas for each attribute of the
+		// user object within the cell
+		var attrs = value.attributes;
+		var texts = [];
+		
+		for (var i = 0; i < attrs.length; i++)
+		{
+			// Creates a textarea with more lines for
+			// the cell label
+			var val = attrs[i].value;
+			texts[i] = form.addTextarea(attrs[i].nodeName, val,
+				(attrs[i].nodeName == 'label') ? 4 : 2);
+		}
+		
+		// Adds an OK and Cancel button to the dialog
+		// contents and implements the respective
+		// actions below
+		
+		// Defines the function to be executed when the
+		// OK button is pressed in the dialog
+		var okFunction = mxUtils.bind(this, function()
+		{
+			// Hides the dialog
+			this.hideProperties();
+			
+			// Supports undo for the changes on the underlying
+			// XML structure / XML node attribute changes.
+			model.beginUpdate();
+			try
+			{
+				if (geo != null)
+				{
+					geo = geo.clone();
+					
+					geo.x = parseFloat(xField.value);
+					geo.y = parseFloat(yField.value);
+					geo.width = parseFloat(widthField.value);
+					geo.height = parseFloat(heightField.value);
+					
+					model.setGeometry(cell, geo);
+				}
+				
+				// Applies the style
+				if (style.value.length > 0)
+				{
+					model.setStyle(cell, style.value);
+				}
+				else
+				{
+					model.setStyle(cell, null);
+				}
+				
+				// Creates an undoable change for each
+				// attribute and executes it using the
+				// model, which will also make the change
+				// part of the current transaction
+				for (var i=0; i<attrs.length; i++)
+				{
+					var edit = new mxCellAttributeChange(
+						cell, attrs[i].nodeName,
+						texts[i].value);
+					model.execute(edit);
+				}
+				
+				// Checks if the graph wants cells to 
+				// be automatically sized and updates
+				// the size as an undoable step if
+				// the feature is enabled
+				if (this.graph.isAutoSizeCell(cell))
+				{
+					this.graph.updateCellSize(cell);
+				}
+			}
+			finally
+			{
+				model.endUpdate();
+			}
+		});
+		
+		// Defines the function to be executed when the
+		// Cancel button is pressed in the dialog
+		var cancelFunction = mxUtils.bind(this, function()
+		{
+			// Hides the dialog
+			this.hideProperties();
+		});
+		
+		form.addButtons(okFunction, cancelFunction);
+		
+		return form.table;
+	}
+
+	return null;
+};
+
+/**
+ * Function: hideProperties
+ * 
+ * Hides the properties dialog.
+ */
+mxEditor.prototype.hideProperties = function ()
+{
+	if (this.properties != null)
+	{
+		this.properties.destroy();
+		this.properties = null;
+	}
+};
+
+/**
+ * Function: showTasks
+ * 
+ * Shows the tasks window. The tasks window is created using <createTasks>. The
+ * default width of the window is 200 pixels, the y-coordinate of the location
+ * can be specifies in <tasksTop> and the x-coordinate is right aligned with a
+ * 20 pixel offset from the right border. To change the location of the tasks
+ * window, the following code can be used:
+ * 
+ * (code)
+ * var oldShowTasks = mxEditor.prototype.showTasks;
+ * mxEditor.prototype.showTasks = function()
+ * {
+ *   oldShowTasks.apply(this, arguments); // "supercall"
+ *   
+ *   if (this.tasks != null)
+ *   {
+ *     this.tasks.setLocation(10, 10);
+ *   }
+ * };
+ * (end)
+ */
+mxEditor.prototype.showTasks = function ()
+{
+	if (this.tasks == null)
+	{
+		var div = document.createElement('div');
+		div.style.padding = '4px';
+		div.style.paddingLeft = '20px';
+		var w = document.body.clientWidth;
+		var wnd = new mxWindow(
+			mxResources.get(this.tasksResource) ||
+			this.tasksResource,
+			div, w - 220, this.tasksTop, 200);
+		wnd.setClosable(true);
+		wnd.destroyOnClose = false;
+		
+		// Installs a function to update the contents
+		// of the tasks window on every change of the
+		// model, selection or root.
+		var funct = mxUtils.bind(this, function(sender)
+		{
+			mxEvent.release(div);
+			div.innerHTML = '';
+			this.createTasks(div);
+		});
+		
+		this.graph.getModel().addListener(mxEvent.CHANGE, funct);
+		this.graph.getSelectionModel().addListener(mxEvent.CHANGE, funct);
+		this.graph.addListener(mxEvent.ROOT, funct);
+		
+		// Assigns the icon to the tasks window
+		if (this.tasksWindowImage != null)
+		{
+			wnd.setImage(this.tasksWindowImage);
+		}
+		
+		this.tasks = wnd;
+		this.createTasks(div);
+	}
+	
+	this.tasks.setVisible(true);
+};
+		
+/**
+ * Function: refreshTasks
+ * 
+ * Updates the contents of the tasks window using <createTasks>.
+ */
+mxEditor.prototype.refreshTasks = function (div)
+{
+	if (this.tasks != null)
+	{
+		var div = this.tasks.content;
+		mxEvent.release(div);
+		div.innerHTML = '';
+		this.createTasks(div);
+	}
+};
+		
+/**
+ * Function: createTasks
+ * 
+ * Updates the contents of the given DOM node to
+ * display the tasks associated with the current
+ * editor state. This is invoked whenever there
+ * is a possible change of state in the editor.
+ * Default implementation is empty.
+ */
+mxEditor.prototype.createTasks = function (div)
+{
+	// override
+};
+	
+/**
+ * Function: showHelp
+ * 
+ * Shows the help window. If the help window does not exist
+ * then it is created using an iframe pointing to the resource
+ * for the <code>urlHelp</code> key or <urlHelp> if the resource
+ * is undefined.
+ */
+mxEditor.prototype.showHelp = function (tasks)
+{
+	if (this.help == null)
+	{
+		var frame = document.createElement('iframe');
+		frame.setAttribute('src', mxResources.get('urlHelp') || this.urlHelp);
+		frame.setAttribute('height', '100%');
+		frame.setAttribute('width', '100%');
+		frame.setAttribute('frameBorder', '0');
+		frame.style.backgroundColor = 'white';
+	
+		var w = document.body.clientWidth;
+		var h = (document.body.clientHeight || document.documentElement.clientHeight);
+		
+		var wnd = new mxWindow(mxResources.get(this.helpResource) || this.helpResource,
+			frame, (w-this.helpWidth)/2, (h-this.helpHeight)/3, this.helpWidth, this.helpHeight);
+		wnd.setMaximizable(true);
+		wnd.setClosable(true);
+		wnd.destroyOnClose = false;
+		wnd.setResizable(true);
+
+		// Assigns the icon to the help window
+		if (this.helpWindowImage != null)
+		{
+			wnd.setImage(this.helpWindowImage);
+		}
+		
+		// Workaround for ignored iframe height 100% in FF
+		if (mxClient.IS_NS)
+		{
+			var handler = function(sender)
+			{
+				var h = wnd.div.offsetHeight;
+				frame.setAttribute('height', (h-26)+'px');
+			};
+			
+			wnd.addListener(mxEvent.RESIZE_END, handler);
+			wnd.addListener(mxEvent.MAXIMIZE, handler);
+			wnd.addListener(mxEvent.NORMALIZE, handler);
+			wnd.addListener(mxEvent.SHOW, handler);
+		}
+		
+		this.help = wnd;
+	}
+	
+	this.help.setVisible(true);
+};
+
+/**
+ * Function: showOutline
+ * 
+ * Shows the outline window. If the window does not exist, then it is
+ * created using an <mxOutline>.
+ */
+mxEditor.prototype.showOutline = function ()
+{
+	var create = this.outline == null;
+	
+	if (create)
+	{
+		var div = document.createElement('div');
+		
+		div.style.overflow = 'hidden';
+		div.style.position = 'relative';
+		div.style.width = '100%';
+		div.style.height = '100%';
+		div.style.background = 'white';
+		div.style.cursor = 'move';
+		
+		if (document.documentMode == 8)
+		{
+			div.style.filter = 'progid:DXImageTransform.Microsoft.alpha(opacity=100)';
+		}
+		
+		var wnd = new mxWindow(
+			mxResources.get(this.outlineResource) ||
+			this.outlineResource,
+			div, 600, 480, 200, 200, false);
+				
+		// Creates the outline in the specified div
+		// and links it to the existing graph
+		var outline = new mxOutline(this.graph, div);			
+		wnd.setClosable(true);
+		wnd.setResizable(true);
+		wnd.destroyOnClose = false;
+		
+		wnd.addListener(mxEvent.RESIZE_END, function()
+		{
+			outline.update();
+		});
+		
+		this.outline = wnd;
+		this.outline.outline = outline;
+	}
+	
+	// Finally shows the outline
+	this.outline.setVisible(true);
+	this.outline.outline.update(true);
+};
+		
+/**
+ * Function: setMode
+ *
+ * Puts the graph into the specified mode. The following modenames are
+ * supported:
+ * 
+ * select - Selects using the left mouse button, new connections
+ * are disabled.
+ * connect - Selects using the left mouse button or creates new
+ * connections if mouse over cell hotspot. See <mxConnectionHandler>.
+ * pan - Pans using the left mouse button, new connections are disabled.
+ */
+mxEditor.prototype.setMode = function(modename)
+{
+	if (modename == 'select')
+	{
+		this.graph.panningHandler.useLeftButtonForPanning = false;
+		this.graph.setConnectable(false);
+	}
+	else if (modename == 'connect')
+	{
+		this.graph.panningHandler.useLeftButtonForPanning = false;
+		this.graph.setConnectable(true);
+	}
+	else if (modename == 'pan')
+	{
+		this.graph.panningHandler.useLeftButtonForPanning = true;
+		this.graph.setConnectable(false);
+	}
+};
+
+/**
+ * Function: createPopupMenu
+ * 
+ * Uses <popupHandler> to create the menu in the graph's
+ * panning handler. The redirection is setup in
+ * <setToolbarContainer>.
+ */
+mxEditor.prototype.createPopupMenu = function (menu, cell, evt)
+{
+	this.popupHandler.createMenu(this, menu, cell, evt);
+};
+
+/**
+ * Function: createEdge
+ * 
+ * Uses <defaultEdge> as the prototype for creating new edges
+ * in the connection handler of the graph. The style of the
+ * edge will be overridden with the value returned by
+ * <getEdgeStyle>.
+ */
+mxEditor.prototype.createEdge = function (source, target)
+{
+	// Clones the defaultedge prototype
+	var e = null;
+	
+	if (this.defaultEdge != null)
+	{
+		var model = this.graph.getModel();
+		e = model.cloneCell(this.defaultEdge);
+	}
+	else
+	{
+		e = new mxCell('');
+		e.setEdge(true);
+		
+		var geo = new mxGeometry();
+		geo.relative = true;
+		e.setGeometry(geo);
+	}
+	
+	// Overrides the edge style
+	var style = this.getEdgeStyle();
+	
+	if (style != null)
+	{
+		e.setStyle(style);
+	}
+	
+	return e;
+};
+
+/**
+ * Function: getEdgeStyle
+ * 
+ * Returns a string identifying the style of new edges.
+ * The function is used in <createEdge> when new edges
+ * are created in the graph.
+ */
+mxEditor.prototype.getEdgeStyle = function ()
+{
+	return this.defaultEdgeStyle;
+};
+
+/**
+ * Function: consumeCycleAttribute
+ * 
+ * Returns the next attribute in <cycleAttributeValues>
+ * or null, if not attribute should be used in the
+ * specified cell.
+ */
+mxEditor.prototype.consumeCycleAttribute = function (cell)
+{
+	return (this.cycleAttributeValues != null &&
+		this.cycleAttributeValues.length > 0 &&
+		this.graph.isSwimlane(cell)) ?
+		this.cycleAttributeValues[this.cycleAttributeIndex++ %
+			this.cycleAttributeValues.length] : null;
+};
+
+/**
+ * Function: cycleAttribute
+ * 
+ * Uses the returned value from <consumeCycleAttribute>
+ * as the value for the <cycleAttributeName> key in
+ * the given cell's style.
+ */
+mxEditor.prototype.cycleAttribute = function (cell)
+{
+	if (this.cycleAttributeName != null)
+	{
+		var value = this.consumeCycleAttribute(cell);
+		
+		if (value != null)
+		{
+			cell.setStyle(cell.getStyle()+';'+
+				this.cycleAttributeName+'='+value);
+		}
+	}
+};
+
+/**
+ * Function: addVertex
+ * 
+ * Adds the given vertex as a child of parent at the specified
+ * x and y coordinate and fires an <addVertex> event.
+ */
+mxEditor.prototype.addVertex = function (parent, vertex, x, y)
+{
+	var model = this.graph.getModel();
+	
+	while (parent != null && !this.graph.isValidDropTarget(parent))
+	{
+		parent = model.getParent(parent);
+	}
+	
+	parent = (parent != null) ? parent : this.graph.getSwimlaneAt(x, y);
+	var scale = this.graph.getView().scale;
+	
+	var geo = model.getGeometry(vertex);
+	var pgeo = model.getGeometry(parent);
+	
+	if (this.graph.isSwimlane(vertex) &&
+		!this.graph.swimlaneNesting)
+	{
+		parent = null;
+	}
+	else if (parent == null && this.swimlaneRequired)
+	{
+		return null;
+	}
+	else if (parent != null && pgeo != null)
+	{
+		// Keeps vertex inside parent
+		var state = this.graph.getView().getState(parent);
+		
+		if (state != null)
+		{			
+			x -= state.origin.x * scale;
+			y -= state.origin.y * scale;
+			
+			if (this.graph.isConstrainedMoving)
+			{
+				var width = geo.width;
+				var height = geo.height;				
+				var tmp = state.x+state.width;
+				
+				if (x+width > tmp)
+				{
+					x -= x+width - tmp;
+				}
+				
+				tmp = state.y+state.height;
+				
+				if (y+height > tmp)
+				{
+					y -= y+height - tmp;
+				}
+			}
+		}
+		else if (pgeo != null)
+		{
+			x -= pgeo.x*scale;
+			y -= pgeo.y*scale;
+		}
+	}
+	
+	geo = geo.clone();
+	geo.x = this.graph.snap(x / scale -
+		this.graph.getView().translate.x -
+		this.graph.gridSize/2);
+	geo.y = this.graph.snap(y / scale -
+		this.graph.getView().translate.y -
+		this.graph.gridSize/2);
+	vertex.setGeometry(geo);
+	
+	if (parent == null)
+	{
+		parent = this.graph.getDefaultParent();
+	}
+
+	this.cycleAttribute(vertex);
+	this.fireEvent(new mxEventObject(mxEvent.BEFORE_ADD_VERTEX,
+			'vertex', vertex, 'parent', parent));
+
+	model.beginUpdate();
+	try
+	{
+		vertex = this.graph.addCell(vertex, parent);
+		
+		if (vertex != null)
+		{
+			this.graph.constrainChild(vertex);
+			
+			this.fireEvent(new mxEventObject(mxEvent.ADD_VERTEX, 'vertex', vertex));
+		}
+	}
+	finally
+	{
+		model.endUpdate();
+	}
+	
+	if (vertex != null)
+	{
+		this.graph.setSelectionCell(vertex);
+		this.graph.scrollCellToVisible(vertex);
+		this.fireEvent(new mxEventObject(mxEvent.AFTER_ADD_VERTEX, 'vertex', vertex));
+	}
+	
+	return vertex;
+};
+
+/**
+ * Function: destroy
+ * 
+ * Removes the editor and all its associated resources. This does not
+ * normally need to be called, it is called automatically when the window
+ * unloads.
+ */
+mxEditor.prototype.destroy = function ()
+{
+	if (!this.destroyed)
+	{
+		this.destroyed = true;
+
+		if (this.tasks != null)
+		{
+			this.tasks.destroy();
+		}
+		
+		if (this.outline != null)
+		{
+			this.outline.destroy();
+		}
+		
+		if (this.properties != null)
+		{
+			this.properties.destroy();
+		}
+		
+		if (this.keyHandler != null)
+		{
+			this.keyHandler.destroy();
+		}
+		
+		if (this.rubberband != null)
+		{
+			this.rubberband.destroy();
+		}
+		
+		if (this.toolbar != null)
+		{
+			this.toolbar.destroy();
+		}
+		
+		if (this.graph != null)
+		{
+			this.graph.destroy();
+		}
+	
+		this.status = null;
+		this.templates = null;
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/handler/mxCellHighlight.js b/airavata-kubernetes/web-console/src/assets/js/handler/mxCellHighlight.js
new file mode 100644
index 0000000..937386a
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/handler/mxCellHighlight.js
@@ -0,0 +1,314 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxCellHighlight
+ * 
+ * A helper class to highlight cells. Here is an example for a given cell.
+ * 
+ * (code)
+ * var highlight = new mxCellHighlight(graph, '#ff0000', 2);
+ * highlight.highlight(graph.view.getState(cell)));
+ * (end)
+ * 
+ * Constructor: mxCellHighlight
+ * 
+ * Constructs a cell highlight.
+ */
+function mxCellHighlight(graph, highlightColor, strokeWidth, dashed)
+{
+	if (graph != null)
+	{
+		this.graph = graph;
+		this.highlightColor = (highlightColor != null) ? highlightColor : mxConstants.DEFAULT_VALID_COLOR;
+		this.strokeWidth = (strokeWidth != null) ? strokeWidth : mxConstants.HIGHLIGHT_STROKEWIDTH;
+		this.dashed = (dashed != null) ? dashed : false;
+		this.opacity = mxConstants.HIGHLIGHT_OPACITY;
+
+		// Updates the marker if the graph changes
+		this.repaintHandler = mxUtils.bind(this, function()
+		{
+			// Updates reference to state
+			if (this.state != null)
+			{
+				var tmp = this.graph.view.getState(this.state.cell);
+				
+				if (tmp == null)
+				{
+					this.hide();
+				}
+				else
+				{
+					this.state = tmp;
+					this.repaint();
+				}
+			}
+		});
+
+		this.graph.getView().addListener(mxEvent.SCALE, this.repaintHandler);
+		this.graph.getView().addListener(mxEvent.TRANSLATE, this.repaintHandler);
+		this.graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE, this.repaintHandler);
+		this.graph.getModel().addListener(mxEvent.CHANGE, this.repaintHandler);
+		
+		// Hides the marker if the current root changes
+		this.resetHandler = mxUtils.bind(this, function()
+		{
+			this.hide();
+		});
+
+		this.graph.getView().addListener(mxEvent.DOWN, this.resetHandler);
+		this.graph.getView().addListener(mxEvent.UP, this.resetHandler);
+	}
+};
+
+/**
+ * Variable: keepOnTop
+ * 
+ * Specifies if the highlights should appear on top of everything
+ * else in the overlay pane. Default is false.
+ */
+mxCellHighlight.prototype.keepOnTop = false;
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxCellHighlight.prototype.graph = true;
+
+/**
+ * Variable: state
+ * 
+ * Reference to the <mxCellState>.
+ */
+mxCellHighlight.prototype.state = null;
+
+/**
+ * Variable: spacing
+ * 
+ * Specifies the spacing between the highlight for vertices and the vertex.
+ * Default is 2.
+ */
+mxCellHighlight.prototype.spacing = 2;
+
+/**
+ * Variable: resetHandler
+ * 
+ * Holds the handler that automatically invokes reset if the highlight
+ * should be hidden.
+ */
+mxCellHighlight.prototype.resetHandler = null;
+
+/**
+ * Function: setHighlightColor
+ * 
+ * Sets the color of the rectangle used to highlight drop targets.
+ * 
+ * Parameters:
+ * 
+ * color - String that represents the new highlight color.
+ */
+mxCellHighlight.prototype.setHighlightColor = function(color)
+{
+	this.highlightColor = color;
+	
+	if (this.shape != null)
+	{
+		this.shape.stroke = color;
+	}
+};
+
+/**
+ * Function: drawHighlight
+ * 
+ * Creates and returns the highlight shape for the given state.
+ */
+mxCellHighlight.prototype.drawHighlight = function()
+{
+	this.shape = this.createShape();
+	this.repaint();
+
+	if (!this.keepOnTop && this.shape.node.parentNode.firstChild != this.shape.node)
+	{
+		this.shape.node.parentNode.insertBefore(this.shape.node, this.shape.node.parentNode.firstChild);
+	}
+};
+
+/**
+ * Function: createShape
+ * 
+ * Creates and returns the highlight shape for the given state.
+ */
+mxCellHighlight.prototype.createShape = function()
+{
+	var shape = this.graph.cellRenderer.createShape(this.state);
+	
+	shape.svgStrokeTolerance = this.graph.tolerance;
+	shape.points = this.state.absolutePoints;
+	shape.apply(this.state);
+	shape.stroke = this.highlightColor;
+	shape.opacity = this.opacity;
+	shape.isDashed = this.dashed;
+	shape.isShadow = false;
+	
+	shape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+	shape.init(this.graph.getView().getOverlayPane());
+	mxEvent.redirectMouseEvents(shape.node, this.graph, this.state);
+	
+	if (this.graph.dialect != mxConstants.DIALECT_SVG)
+	{
+		shape.pointerEvents = false;
+	}
+	else
+	{
+		shape.svgPointerEvents = 'stroke';
+	}
+	
+	return shape;
+};
+
+/**
+ * Function: repaint
+ * 
+ * Updates the highlight after a change of the model or view.
+ */
+mxCellHighlight.prototype.getStrokeWidth = function(state)
+{
+	return this.strokeWidth;
+};
+
+/**
+ * Function: repaint
+ * 
+ * Updates the highlight after a change of the model or view.
+ */
+mxCellHighlight.prototype.repaint = function()
+{
+	if (this.state != null && this.shape != null)
+	{
+		this.shape.scale = this.state.view.scale;
+		
+		if (this.graph.model.isEdge(this.state.cell))
+		{
+			this.shape.strokewidth = this.getStrokeWidth();
+			this.shape.points = this.state.absolutePoints;
+			this.shape.outline = false;
+		}
+		else
+		{
+			this.shape.bounds = new mxRectangle(this.state.x - this.spacing, this.state.y - this.spacing,
+					this.state.width + 2 * this.spacing, this.state.height + 2 * this.spacing);
+			this.shape.rotation = Number(this.state.style[mxConstants.STYLE_ROTATION] || '0');
+			this.shape.strokewidth = this.getStrokeWidth() / this.state.view.scale;
+			this.shape.outline = true;
+		}
+
+		// Uses cursor from shape in highlight
+		if (this.state.shape != null)
+		{
+			this.shape.setCursor(this.state.shape.getCursor());
+		}
+		
+		// Workaround for event transparency in VML with transparent color
+		// is to use a non-transparent color with near zero opacity
+		if (mxClient.IS_QUIRKS || document.documentMode == 8)
+		{
+			if (this.shape.stroke == 'transparent')
+			{
+				// KNOWN: Quirks mode does not seem to catch events if
+				// we do not force an update of the DOM via a change such
+				// as mxLog.debug. Since IE6 is EOL we do not add a fix.
+				this.shape.stroke = 'white';
+				this.shape.opacity = 1;
+			}
+			else
+			{
+				this.shape.opacity = this.opacity;
+			}
+		}
+		
+		this.shape.redraw();
+	}
+};
+
+/**
+ * Function: hide
+ * 
+ * Resets the state of the cell marker.
+ */
+mxCellHighlight.prototype.hide = function()
+{
+	this.highlight(null);
+};
+
+/**
+ * Function: mark
+ * 
+ * Marks the <markedState> and fires a <mark> event.
+ */
+mxCellHighlight.prototype.highlight = function(state)
+{
+	if (this.state != state)
+	{
+		if (this.shape != null)
+		{
+			this.shape.destroy();
+			this.shape = null;
+		}
+
+		this.state = state;
+		
+		if (this.state != null)
+		{
+			this.drawHighlight();
+		}
+	}
+};
+
+/**
+ * Function: isHighlightAt
+ * 
+ * Returns true if this highlight is at the given position.
+ */
+mxCellHighlight.prototype.isHighlightAt = function(x, y)
+{
+	var hit = false;
+	
+	// Quirks mode is currently not supported as it used a different coordinate system
+	if (this.shape != null && document.elementFromPoint != null && !mxClient.IS_QUIRKS)
+	{
+		var elt = document.elementFromPoint(x, y);
+
+		while (elt != null)
+		{
+			if (elt == this.shape.node)
+			{
+				hit = true;
+				break;
+			}
+			
+			elt = elt.parentNode;
+		}
+	}
+	
+	return hit;
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the handler and all its resources and DOM nodes.
+ */
+mxCellHighlight.prototype.destroy = function()
+{
+	this.graph.getView().removeListener(this.resetHandler);
+	this.graph.getView().removeListener(this.repaintHandler);
+	this.graph.getModel().removeListener(this.repaintHandler);
+	
+	if (this.shape != null)
+	{
+		this.shape.destroy();
+		this.shape = null;
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/handler/mxCellMarker.js b/airavata-kubernetes/web-console/src/assets/js/handler/mxCellMarker.js
new file mode 100644
index 0000000..10037c8
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/handler/mxCellMarker.js
@@ -0,0 +1,430 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxCellMarker
+ * 
+ * A helper class to process mouse locations and highlight cells.
+ * 
+ * Helper class to highlight cells. To add a cell marker to an existing graph
+ * for highlighting all cells, the following code is used:
+ * 
+ * (code)
+ * var marker = new mxCellMarker(graph);
+ * graph.addMouseListener({
+ *   mouseDown: function() {},
+ *   mouseMove: function(sender, me)
+ *   {
+ *     marker.process(me);
+ *   },
+ *   mouseUp: function() {}
+ * });
+ * (end)
+ *
+ * Event: mxEvent.MARK
+ * 
+ * Fires after a cell has been marked or unmarked. The <code>state</code>
+ * property contains the marked <mxCellState> or null if no state is marked.
+ * 
+ * Constructor: mxCellMarker
+ * 
+ * Constructs a new cell marker.
+ * 
+ * Parameters:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ * validColor - Optional marker color for valid states. Default is
+ * <mxConstants.DEFAULT_VALID_COLOR>.
+ * invalidColor - Optional marker color for invalid states. Default is
+ * <mxConstants.DEFAULT_INVALID_COLOR>.
+ * hotspot - Portion of the width and hight where a state intersects a
+ * given coordinate pair. A value of 0 means always highlight. Default is
+ * <mxConstants.DEFAULT_HOTSPOT>.
+ */
+function mxCellMarker(graph, validColor, invalidColor, hotspot)
+{
+	mxEventSource.call(this);
+	
+	if (graph != null)
+	{
+		this.graph = graph;
+		this.validColor = (validColor != null) ? validColor : mxConstants.DEFAULT_VALID_COLOR;
+		this.invalidColor = (validColor != null) ? invalidColor : mxConstants.DEFAULT_INVALID_COLOR;
+		this.hotspot = (hotspot != null) ? hotspot : mxConstants.DEFAULT_HOTSPOT;
+		
+		this.highlight = new mxCellHighlight(graph);
+	}
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxUtils.extend(mxCellMarker, mxEventSource);
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxCellMarker.prototype.graph = null;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies if the marker is enabled. Default is true.
+ */
+mxCellMarker.prototype.enabled = true;
+
+/**
+ * Variable: hotspot
+ * 
+ * Specifies the portion of the width and height that should trigger
+ * a highlight. The area around the center of the cell to be marked is used
+ * as the hotspot. Possible values are between 0 and 1. Default is
+ * mxConstants.DEFAULT_HOTSPOT.
+ */
+mxCellMarker.prototype.hotspot = mxConstants.DEFAULT_HOTSPOT; 
+
+/**
+ * Variable: hotspotEnabled
+ * 
+ * Specifies if the hotspot is enabled. Default is false.
+ */
+mxCellMarker.prototype.hotspotEnabled = false;
+
+/**
+ * Variable: validColor
+ * 
+ * Holds the valid marker color.
+ */
+mxCellMarker.prototype.validColor = null;
+
+/**
+ * Variable: invalidColor
+ * 
+ * Holds the invalid marker color.
+ */
+mxCellMarker.prototype.invalidColor = null;
+
+/**
+ * Variable: currentColor
+ * 
+ * Holds the current marker color.
+ */
+mxCellMarker.prototype.currentColor = null;
+
+/**
+ * Variable: validState
+ * 
+ * Holds the marked <mxCellState> if it is valid.
+ */
+mxCellMarker.prototype.validState = null; 
+
+/**
+ * Variable: markedState
+ * 
+ * Holds the marked <mxCellState>.
+ */
+mxCellMarker.prototype.markedState = null;
+
+/**
+ * Function: setEnabled
+ * 
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ * 
+ * Parameters:
+ * 
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxCellMarker.prototype.setEnabled = function(enabled)
+{
+	this.enabled = enabled;
+};
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxCellMarker.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+
+/**
+ * Function: setHotspot
+ * 
+ * Sets the <hotspot>.
+ */
+mxCellMarker.prototype.setHotspot = function(hotspot)
+{
+	this.hotspot = hotspot;
+};
+
+/**
+ * Function: getHotspot
+ * 
+ * Returns the <hotspot>.
+ */
+mxCellMarker.prototype.getHotspot = function()
+{
+	return this.hotspot;
+};
+
+/**
+ * Function: setHotspotEnabled
+ * 
+ * Specifies whether the hotspot should be used in <intersects>.
+ */
+mxCellMarker.prototype.setHotspotEnabled = function(enabled)
+{
+	this.hotspotEnabled = enabled;
+};
+
+/**
+ * Function: isHotspotEnabled
+ * 
+ * Returns true if hotspot is used in <intersects>.
+ */
+mxCellMarker.prototype.isHotspotEnabled = function()
+{
+	return this.hotspotEnabled;
+};
+
+/**
+ * Function: hasValidState
+ * 
+ * Returns true if <validState> is not null.
+ */
+mxCellMarker.prototype.hasValidState = function()
+{
+	return this.validState != null;
+};
+
+/**
+ * Function: getValidState
+ * 
+ * Returns the <validState>.
+ */
+mxCellMarker.prototype.getValidState = function()
+{
+	return this.validState;
+};
+
+/**
+ * Function: getMarkedState
+ * 
+ * Returns the <markedState>.
+ */
+mxCellMarker.prototype.getMarkedState = function()
+{
+	return this.markedState;
+};
+
+/**
+ * Function: reset
+ * 
+ * Resets the state of the cell marker.
+ */
+mxCellMarker.prototype.reset = function()
+{
+	this.validState = null;
+	
+	if (this.markedState != null)
+	{
+		this.markedState = null;
+		this.unmark();
+	}
+};
+
+/**
+ * Function: process
+ * 
+ * Processes the given event and cell and marks the state returned by
+ * <getState> with the color returned by <getMarkerColor>. If the
+ * markerColor is not null, then the state is stored in <markedState>. If
+ * <isValidState> returns true, then the state is stored in <validState>
+ * regardless of the marker color. The state is returned regardless of the
+ * marker color and valid state. 
+ */
+mxCellMarker.prototype.process = function(me)
+{
+	var state = null;
+	
+	if (this.isEnabled())
+	{
+		state = this.getState(me);
+		this.setCurrentState(state, me);
+	}
+	
+	return state;
+};
+
+/**
+ * Function: setCurrentState
+ * 
+ * Sets and marks the current valid state.
+ */
+mxCellMarker.prototype.setCurrentState = function(state, me, color)
+{
+	var isValid = (state != null) ? this.isValidState(state) : false;
+	color = (color != null) ? color : this.getMarkerColor(me.getEvent(), state, isValid);
+	
+	if (isValid)
+	{
+		this.validState = state;
+	}
+	else
+	{
+		this.validState = null;
+	}
+	
+	if (state != this.markedState || color != this.currentColor)
+	{
+		this.currentColor = color;
+		
+		if (state != null && this.currentColor != null)
+		{
+			this.markedState = state;
+			this.mark();		
+		}
+		else if (this.markedState != null)
+		{
+			this.markedState = null;
+			this.unmark();
+		}
+	}
+};
+
+/**
+ * Function: markCell
+ * 
+ * Marks the given cell using the given color, or <validColor> if no color is specified.
+ */
+mxCellMarker.prototype.markCell = function(cell, color)
+{
+	var state = this.graph.getView().getState(cell);
+	
+	if (state != null)
+	{
+		this.currentColor = (color != null) ? color : this.validColor;
+		this.markedState = state;
+		this.mark();
+	}
+};
+
+/**
+ * Function: mark
+ * 
+ * Marks the <markedState> and fires a <mark> event.
+ */
+mxCellMarker.prototype.mark = function()
+{
+	this.highlight.setHighlightColor(this.currentColor);
+	this.highlight.highlight(this.markedState);
+	this.fireEvent(new mxEventObject(mxEvent.MARK, 'state', this.markedState));
+};
+
+/**
+ * Function: unmark
+ * 
+ * Hides the marker and fires a <mark> event.
+ */
+mxCellMarker.prototype.unmark = function()
+{
+	this.mark();
+};
+
+/**
+ * Function: isValidState
+ * 
+ * Returns true if the given <mxCellState> is a valid state. If this
+ * returns true, then the state is stored in <validState>. The return value
+ * of this method is used as the argument for <getMarkerColor>.
+ */
+mxCellMarker.prototype.isValidState = function(state)
+{
+	return true;
+};
+
+/**
+ * Function: getMarkerColor
+ * 
+ * Returns the valid- or invalidColor depending on the value of isValid.
+ * The given <mxCellState> is ignored by this implementation.
+ */
+mxCellMarker.prototype.getMarkerColor = function(evt, state, isValid)
+{
+	return (isValid) ? this.validColor : this.invalidColor;
+};
+
+/**
+ * Function: getState
+ * 
+ * Uses <getCell>, <getStateToMark> and <intersects> to return the
+ * <mxCellState> for the given <mxMouseEvent>.
+ */
+mxCellMarker.prototype.getState = function(me)
+{
+	var view = this.graph.getView();
+	var cell = this.getCell(me);
+	var state = this.getStateToMark(view.getState(cell));
+
+	return (state != null && this.intersects(state, me)) ? state : null;
+};
+
+/**
+ * Function: getCell
+ * 
+ * Returns the <mxCell> for the given event and cell. This returns the
+ * given cell.
+ */
+mxCellMarker.prototype.getCell = function(me)
+{
+	return me.getCell();
+};
+
+/**
+ * Function: getStateToMark
+ * 
+ * Returns the <mxCellState> to be marked for the given <mxCellState> under
+ * the mouse. This returns the given state.
+ */
+mxCellMarker.prototype.getStateToMark = function(state)
+{
+	return state;
+};
+
+/**
+ * Function: intersects
+ * 
+ * Returns true if the given coordinate pair intersects the given state.
+ * This returns true if the <hotspot> is 0 or the coordinates are inside
+ * the hotspot for the given cell state.
+ */
+mxCellMarker.prototype.intersects = function(state, me)
+{
+	if (this.hotspotEnabled)
+	{
+		return mxUtils.intersectsHotspot(state, me.getGraphX(), me.getGraphY(),
+			this.hotspot, mxConstants.MIN_HOTSPOT_SIZE,
+			mxConstants.MAX_HOTSPOT_SIZE);
+	}
+	
+	return true;
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the handler and all its resources and DOM nodes.
+ */
+mxCellMarker.prototype.destroy = function()
+{
+	this.graph.getView().removeListener(this.resetHandler);
+	this.graph.getModel().removeListener(this.resetHandler);
+	this.highlight.destroy();
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/handler/mxCellTracker.js b/airavata-kubernetes/web-console/src/assets/js/handler/mxCellTracker.js
new file mode 100644
index 0000000..9f0c8bb
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/handler/mxCellTracker.js
@@ -0,0 +1,145 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxCellTracker
+ * 
+ * Event handler that highlights cells. Inherits from <mxCellMarker>.
+ * 
+ * Example:
+ * 
+ * (code)
+ * new mxCellTracker(graph, '#00FF00');
+ * (end)
+ * 
+ * For detecting dragEnter, dragOver and dragLeave on cells, the following
+ * code can be used:
+ * 
+ * (code)
+ * graph.addMouseListener(
+ * {
+ *   cell: null,
+ *   mouseDown: function(sender, me) { },
+ *   mouseMove: function(sender, me)
+ *   {
+ *     var tmp = me.getCell();
+ *     
+ *     if (tmp != this.cell)
+ *     {
+ *       if (this.cell != null)
+ *       {
+ *         this.dragLeave(me.getEvent(), this.cell);
+ *       }
+ *       
+ *       this.cell = tmp;
+ *       
+ *       if (this.cell != null)
+ *       {
+ *         this.dragEnter(me.getEvent(), this.cell);
+ *       }
+ *     }
+ *     
+ *     if (this.cell != null)
+ *     {
+ *       this.dragOver(me.getEvent(), this.cell);
+ *     }
+ *   },
+ *   mouseUp: function(sender, me) { },
+ *   dragEnter: function(evt, cell)
+ *   {
+ *     mxLog.debug('dragEnter', cell.value);
+ *   },
+ *   dragOver: function(evt, cell)
+ *   {
+ *     mxLog.debug('dragOver', cell.value);
+ *   },
+ *   dragLeave: function(evt, cell)
+ *   {
+ *     mxLog.debug('dragLeave', cell.value);
+ *   }
+ * });
+ * (end)
+ * 
+ * Constructor: mxCellTracker
+ * 
+ * Constructs an event handler that highlights cells.
+ * 
+ * Parameters:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ * color - Color of the highlight. Default is blue.
+ * funct - Optional JavaScript function that is used to override
+ * <mxCellMarker.getCell>.
+ */
+function mxCellTracker(graph, color, funct)
+{
+	mxCellMarker.call(this, graph, color);
+
+	this.graph.addMouseListener(this);
+	
+	if (funct != null)
+	{
+		this.getCell = funct;
+	}
+	
+	// Automatic deallocation of memory
+	if (mxClient.IS_IE)
+	{
+		mxEvent.addListener(window, 'unload', mxUtils.bind(this, function()
+		{
+			this.destroy();
+		}));
+	}
+};
+
+/**
+ * Extends mxCellMarker.
+ */
+mxUtils.extend(mxCellTracker, mxCellMarker);
+
+/**
+ * Function: mouseDown
+ * 
+ * Ignores the event. The event is not consumed.
+ */
+mxCellTracker.prototype.mouseDown = function(sender, me) { };
+
+/**
+ * Function: mouseMove
+ * 
+ * Handles the event by highlighting the cell under the mousepointer if it
+ * is over the hotspot region of the cell.
+ */
+mxCellTracker.prototype.mouseMove = function(sender, me)
+{
+	if (this.isEnabled())
+	{
+		this.process(me);
+	}
+};
+
+/**
+ * Function: mouseUp
+ * 
+ * Handles the event by reseting the highlight.
+ */
+mxCellTracker.prototype.mouseUp = function(sender, me) { };
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the object and all its resources and DOM nodes. This doesn't
+ * normally need to be called. It is called automatically when the window
+ * unloads.
+ */
+mxCellTracker.prototype.destroy = function()
+{
+	if (!this.destroyed)
+	{
+		this.destroyed = true;
+
+		this.graph.removeMouseListener(this);
+		mxCellMarker.prototype.destroy.apply(this);
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/handler/mxConnectionHandler.js b/airavata-kubernetes/web-console/src/assets/js/handler/mxConnectionHandler.js
new file mode 100644
index 0000000..2eade4d
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/handler/mxConnectionHandler.js
@@ -0,0 +1,2204 @@
+/**
+ * Copyright (c) 2006-2016, JGraph Ltd
+ * Copyright (c) 2006-2016, Gaudenz Alder
+ */
+/**
+ * Class: mxConnectionHandler
+ *
+ * Graph event handler that creates new connections. Uses <mxTerminalMarker>
+ * for finding and highlighting the source and target vertices and
+ * <factoryMethod> to create the edge instance. This handler is built-into
+ * <mxGraph.connectionHandler> and enabled using <mxGraph.setConnectable>.
+ *
+ * Example:
+ * 
+ * (code)
+ * new mxConnectionHandler(graph, function(source, target, style)
+ * {
+ *   edge = new mxCell('', new mxGeometry());
+ *   edge.setEdge(true);
+ *   edge.setStyle(style);
+ *   edge.geometry.relative = true;
+ *   return edge;
+ * });
+ * (end)
+ * 
+ * Here is an alternative solution that just sets a specific user object for
+ * new edges by overriding <insertEdge>.
+ *
+ * (code)
+ * mxConnectionHandlerInsertEdge = mxConnectionHandler.prototype.insertEdge;
+ * mxConnectionHandler.prototype.insertEdge = function(parent, id, value, source, target, style)
+ * {
+ *   value = 'Test';
+ * 
+ *   return mxConnectionHandlerInsertEdge.apply(this, arguments);
+ * };
+ * (end)
+ * 
+ * Using images to trigger connections:
+ * 
+ * This handler uses mxTerminalMarker to find the source and target cell for
+ * the new connection and creates a new edge using <connect>. The new edge is
+ * created using <createEdge> which in turn uses <factoryMethod> or creates a
+ * new default edge.
+ * 
+ * The handler uses a "highlight-paradigm" for indicating if a cell is being
+ * used as a source or target terminal, as seen in other diagramming products.
+ * In order to allow both, moving and connecting cells at the same time,
+ * <mxConstants.DEFAULT_HOTSPOT> is used in the handler to determine the hotspot
+ * of a cell, that is, the region of the cell which is used to trigger a new
+ * connection. The constant is a value between 0 and 1 that specifies the
+ * amount of the width and height around the center to be used for the hotspot
+ * of a cell and its default value is 0.5. In addition,
+ * <mxConstants.MIN_HOTSPOT_SIZE> defines the minimum number of pixels for the
+ * width and height of the hotspot.
+ * 
+ * This solution, while standards compliant, may be somewhat confusing because
+ * there is no visual indicator for the hotspot and the highlight is seen to
+ * switch on and off while the mouse is being moved in and out. Furthermore,
+ * this paradigm does not allow to create different connections depending on
+ * the highlighted hotspot as there is only one hotspot per cell and it
+ * normally does not allow cells to be moved and connected at the same time as
+ * there is no clear indication of the connectable area of the cell.
+ * 
+ * To come across these issues, the handle has an additional <createIcons> hook
+ * with a default implementation that allows to create one icon to be used to
+ * trigger new connections. If this icon is specified, then new connections can
+ * only be created if the image is clicked while the cell is being highlighted.
+ * The <createIcons> hook may be overridden to create more than one
+ * <mxImageShape> for creating new connections, but the default implementation
+ * supports one image and is used as follows:
+ * 
+ * In order to display the "connect image" whenever the mouse is over the cell,
+ * an DEFAULT_HOTSPOT of 1 should be used:
+ * 
+ * (code)
+ * mxConstants.DEFAULT_HOTSPOT = 1;
+ * (end)
+ * 
+ * In order to avoid confusion with the highlighting, the highlight color
+ * should not be used with a connect image:
+ * 
+ * (code)
+ * mxConstants.HIGHLIGHT_COLOR = null;
+ * (end)
+ * 
+ * To install the image, the connectImage field of the mxConnectionHandler must
+ * be assigned a new <mxImage> instance:
+ * 
+ * (code)
+ * mxConnectionHandler.prototype.connectImage = new mxImage('images/green-dot.gif', 14, 14);
+ * (end)
+ * 
+ * This will use the green-dot.gif with a width and height of 14 pixels as the
+ * image to trigger new connections. In createIcons the icon field of the
+ * handler will be set in order to remember the icon that has been clicked for
+ * creating the new connection. This field will be available under selectedIcon
+ * in the connect method, which may be overridden to take the icon that
+ * triggered the new connection into account. This is useful if more than one
+ * icon may be used to create a connection.
+ *
+ * Group: Events
+ * 
+ * Event: mxEvent.START
+ * 
+ * Fires when a new connection is being created by the user. The <code>state</code>
+ * property contains the state of the source cell.
+ * 
+ * Event: mxEvent.CONNECT
+ * 
+ * Fires between begin- and endUpdate in <connect>. The <code>cell</code>
+ * property contains the inserted edge, the <code>event</code> and <code>target</code> 
+ * properties contain the respective arguments that were passed to <connect> (where
+ * target corresponds to the dropTarget argument). Finally, the <code>terminal</code>
+ * property corresponds to the target argument in <connect> or the clone of the source
+ * terminal if <createTarget> is enabled.
+ * 
+ * Note that the target is the cell under the mouse where the mouse button was released.
+ * Depending on the logic in the handler, this doesn't necessarily have to be the target
+ * of the inserted edge. To print the source, target or any optional ports IDs that the
+ * edge is connected to, the following code can be used. To get more details about the
+ * actual connection point, <mxGraph.getConnectionConstraint> can be used. To resolve
+ * the port IDs, use <mxGraphModel.getCell>.
+ * 
+ * (code)
+ * graph.connectionHandler.addListener(mxEvent.CONNECT, function(sender, evt)
+ * {
+ *   var edge = evt.getProperty('cell');
+ *   var source = graph.getModel().getTerminal(edge, true);
+ *   var target = graph.getModel().getTerminal(edge, false);
+ *   
+ *   var style = graph.getCellStyle(edge);
+ *   var sourcePortId = style[mxConstants.STYLE_SOURCE_PORT];
+ *   var targetPortId = style[mxConstants.STYLE_TARGET_PORT];
+ *   
+ *   mxLog.show();
+ *   mxLog.debug('connect', edge, source.id, target.id, sourcePortId, targetPortId);
+ * });
+ * (end)
+ *
+ * Event: mxEvent.RESET
+ * 
+ * Fires when the <reset> method is invoked.
+ *
+ * Constructor: mxConnectionHandler
+ *
+ * Constructs an event handler that connects vertices using the specified
+ * factory method to create the new edges. Modify
+ * <mxConstants.ACTIVE_REGION> to setup the region on a cell which triggers
+ * the creation of a new connection or use connect icons as explained
+ * above.
+ * 
+ * Parameters:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ * factoryMethod - Optional function to create the edge. The function takes
+ * the source and target <mxCell> as the first and second argument and an
+ * optional cell style from the preview as the third argument. It returns
+ * the <mxCell> that represents the new edge.
+ */
+function mxConnectionHandler(graph, factoryMethod)
+{
+	mxEventSource.call(this);
+	
+	if (graph != null)
+	{
+		this.graph = graph;
+		this.factoryMethod = factoryMethod;
+		this.init();
+		
+		// Handles escape keystrokes
+		this.escapeHandler = mxUtils.bind(this, function(sender, evt)
+		{
+			this.reset();
+		});
+		
+		this.graph.addListener(mxEvent.ESCAPE, this.escapeHandler);
+	}
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxUtils.extend(mxConnectionHandler, mxEventSource);
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxConnectionHandler.prototype.graph = null;
+
+/**
+ * Variable: factoryMethod
+ * 
+ * Function that is used for creating new edges. The function takes the
+ * source and target <mxCell> as the first and second argument and returns
+ * a new <mxCell> that represents the edge. This is used in <createEdge>.
+ */
+mxConnectionHandler.prototype.factoryMethod = true;
+
+/**
+ * Variable: moveIconFront
+ * 
+ * Specifies if icons should be displayed inside the graph container instead
+ * of the overlay pane. This is used for HTML labels on vertices which hide
+ * the connect icon. This has precendence over <moveIconBack> when set
+ * to true. Default is false.
+ */
+mxConnectionHandler.prototype.moveIconFront = false;
+
+/**
+ * Variable: moveIconBack
+ * 
+ * Specifies if icons should be moved to the back of the overlay pane. This can
+ * be set to true if the icons of the connection handler conflict with other
+ * handles, such as the vertex label move handle. Default is false.
+ */
+mxConnectionHandler.prototype.moveIconBack = false;
+
+/**
+ * Variable: connectImage
+ * 
+ * <mxImage> that is used to trigger the creation of a new connection. This
+ * is used in <createIcons>. Default is null.
+ */
+mxConnectionHandler.prototype.connectImage = null;
+
+/**
+ * Variable: targetConnectImage
+ * 
+ * Specifies if the connect icon should be centered on the target state
+ * while connections are being previewed. Default is false.
+ */
+mxConnectionHandler.prototype.targetConnectImage = false;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies if events are handled. Default is true.
+ */
+mxConnectionHandler.prototype.enabled = true;
+
+/**
+ * Variable: select
+ * 
+ * Specifies if new edges should be selected. Default is true.
+ */
+mxConnectionHandler.prototype.select = true;
+
+/**
+ * Variable: createTarget
+ * 
+ * Specifies if <createTargetVertex> should be called if no target was under the
+ * mouse for the new connection. Setting this to true means the connection
+ * will be drawn as valid if no target is under the mouse, and
+ * <createTargetVertex> will be called before the connection is created between
+ * the source cell and the newly created vertex in <createTargetVertex>, which
+ * can be overridden to create a new target. Default is false.
+ */
+mxConnectionHandler.prototype.createTarget = false;
+
+/**
+ * Variable: marker
+ * 
+ * Holds the <mxTerminalMarker> used for finding source and target cells.
+ */
+mxConnectionHandler.prototype.marker = null;
+
+/**
+ * Variable: constraintHandler
+ * 
+ * Holds the <mxConstraintHandler> used for drawing and highlighting
+ * constraints.
+ */
+mxConnectionHandler.prototype.constraintHandler = null;
+
+/**
+ * Variable: error
+ * 
+ * Holds the current validation error while connections are being created.
+ */
+mxConnectionHandler.prototype.error = null;
+
+/**
+ * Variable: waypointsEnabled
+ * 
+ * Specifies if single clicks should add waypoints on the new edge. Default is
+ * false.
+ */
+mxConnectionHandler.prototype.waypointsEnabled = false;
+
+/**
+ * Variable: ignoreMouseDown
+ * 
+ * Specifies if the connection handler should ignore the state of the mouse
+ * button when highlighting the source. Default is false, that is, the
+ * handler only highlights the source if no button is being pressed.
+ */
+mxConnectionHandler.prototype.ignoreMouseDown = false;
+
+/**
+ * Variable: first
+ * 
+ * Holds the <mxPoint> where the mouseDown took place while the handler is
+ * active.
+ */
+mxConnectionHandler.prototype.first = null;
+
+/**
+ * Variable: connectIconOffset
+ * 
+ * Holds the offset for connect icons during connection preview.
+ * Default is mxPoint(0, <mxConstants.TOOLTIP_VERTICAL_OFFSET>).
+ * Note that placing the icon under the mouse pointer with an
+ * offset of (0,0) will affect hit detection.
+ */
+mxConnectionHandler.prototype.connectIconOffset = new mxPoint(0, mxConstants.TOOLTIP_VERTICAL_OFFSET);
+
+/**
+ * Variable: edgeState
+ * 
+ * Optional <mxCellState> that represents the preview edge while the
+ * handler is active. This is created in <createEdgeState>.
+ */
+mxConnectionHandler.prototype.edgeState = null;
+
+/**
+ * Variable: changeHandler
+ * 
+ * Holds the change event listener for later removal.
+ */
+mxConnectionHandler.prototype.changeHandler = null;
+
+/**
+ * Variable: drillHandler
+ * 
+ * Holds the drill event listener for later removal.
+ */
+mxConnectionHandler.prototype.drillHandler = null;
+
+/**
+ * Variable: mouseDownCounter
+ * 
+ * Counts the number of mouseDown events since the start. The initial mouse
+ * down event counts as 1.
+ */
+mxConnectionHandler.prototype.mouseDownCounter = 0;
+
+/**
+ * Variable: movePreviewAway
+ * 
+ * Switch to enable moving the preview away from the mousepointer. This is required in browsers
+ * where the preview cannot be made transparent to events and if the built-in hit detection on
+ * the HTML elements in the page should be used. Default is the value of <mxClient.IS_VML>.
+ */
+mxConnectionHandler.prototype.movePreviewAway = mxClient.IS_VML;
+
+/**
+ * Variable: outlineConnect
+ * 
+ * Specifies if connections to the outline of a highlighted target should be
+ * enabled. This will allow to place the connection point along the outline of
+ * the highlighted target. Default is false.
+ */
+mxConnectionHandler.prototype.outlineConnect = false;
+
+/**
+ * Variable: livePreview
+ * 
+ * Specifies if the actual shape of the edge state should be used for the preview.
+ * Default is false. (Ignored if no edge state is created in <createEdgeState>.)
+ */
+mxConnectionHandler.prototype.livePreview = false;
+
+/**
+ * Variable: cursor
+ * 
+ * Specifies the cursor to be used while the handler is active. Default is null.
+ */
+mxConnectionHandler.prototype.cursor = null;
+
+/**
+ * Variable: insertBeforeSource
+ * 
+ * Specifies if new edges should be inserted before the source vertex in the
+ * cell hierarchy. Default is false for backwards compatibility.
+ */
+mxConnectionHandler.prototype.insertBeforeSource = false;
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxConnectionHandler.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+	
+/**
+ * Function: setEnabled
+ * 
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ * 
+ * Parameters:
+ * 
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxConnectionHandler.prototype.setEnabled = function(enabled)
+{
+	this.enabled = enabled;
+};
+
+/**
+ * Function: isInsertBefore
+ * 
+ * Returns <insertBeforeSource> for non-loops and false for loops.
+ *
+ * Parameters:
+ * 
+ * edge - <mxCell> that represents the edge to be inserted.
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ * evt - Mousedown event of the connect gesture.
+ * dropTarget - <mxCell> that represents the cell under the mouse when it was
+ * released.
+ */
+mxConnectionHandler.prototype.isInsertBefore = function(edge, source, target, evt, dropTarget)
+{
+	return this.insertBeforeSource && source != target;
+};
+
+/**
+ * Function: isCreateTarget
+ * 
+ * Returns <createTarget>.
+ *
+ * Parameters:
+ *
+ * evt - Current active native pointer event.
+ */
+mxConnectionHandler.prototype.isCreateTarget = function(evt)
+{
+	return this.createTarget;
+};
+
+/**
+ * Function: setCreateTarget
+ * 
+ * Sets <createTarget>.
+ */
+mxConnectionHandler.prototype.setCreateTarget = function(value)
+{
+	this.createTarget = value;
+};
+
+/**
+ * Function: createShape
+ * 
+ * Creates the preview shape for new connections.
+ */
+mxConnectionHandler.prototype.createShape = function()
+{
+	// Creates the edge preview
+	var shape = (this.livePreview && this.edgeState != null) ?
+		this.graph.cellRenderer.createShape(this.edgeState) :
+		new mxPolyline([], mxConstants.INVALID_COLOR);
+	shape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
+		mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+	shape.scale = this.graph.view.scale;
+	shape.pointerEvents = false;
+	shape.isDashed = true;
+	shape.init(this.graph.getView().getOverlayPane());
+	mxEvent.redirectMouseEvents(shape.node, this.graph, null);
+
+	return shape;
+};
+
+/**
+ * Function: init
+ * 
+ * Initializes the shapes required for this connection handler. This should
+ * be invoked if <mxGraph.container> is assigned after the connection
+ * handler has been created.
+ */
+mxConnectionHandler.prototype.init = function()
+{
+	this.graph.addMouseListener(this);
+	this.marker = this.createMarker();
+	this.constraintHandler = new mxConstraintHandler(this.graph);
+
+	// Redraws the icons if the graph changes
+	this.changeHandler = mxUtils.bind(this, function(sender)
+	{
+		if (this.iconState != null)
+		{
+			this.iconState = this.graph.getView().getState(this.iconState.cell);
+		}
+		
+		if (this.iconState != null)
+		{
+			this.redrawIcons(this.icons, this.iconState);
+			this.constraintHandler.reset();
+		}
+		else if (this.previous != null && this.graph.view.getState(this.previous.cell) == null)
+		{
+			this.reset();
+		}
+	});
+	
+	this.graph.getModel().addListener(mxEvent.CHANGE, this.changeHandler);
+	this.graph.getView().addListener(mxEvent.SCALE, this.changeHandler);
+	this.graph.getView().addListener(mxEvent.TRANSLATE, this.changeHandler);
+	this.graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE, this.changeHandler);
+	
+	// Removes the icon if we step into/up or start editing
+	this.drillHandler = mxUtils.bind(this, function(sender)
+	{
+		this.reset();
+	});
+	
+	this.graph.addListener(mxEvent.START_EDITING, this.drillHandler);
+	this.graph.getView().addListener(mxEvent.DOWN, this.drillHandler);
+	this.graph.getView().addListener(mxEvent.UP, this.drillHandler);
+};
+
+/**
+ * Function: isConnectableCell
+ * 
+ * Returns true if the given cell is connectable. This is a hook to
+ * disable floating connections. This implementation returns true.
+ */
+mxConnectionHandler.prototype.isConnectableCell = function(cell)
+{
+	return true;
+};
+
+/**
+ * Function: createMarker
+ * 
+ * Creates and returns the <mxCellMarker> used in <marker>.
+ */
+mxConnectionHandler.prototype.createMarker = function()
+{
+	var marker = new mxCellMarker(this.graph);
+	marker.hotspotEnabled = true;
+
+	// Overrides to return cell at location only if valid (so that
+	// there is no highlight for invalid cells)
+	marker.getCell = mxUtils.bind(this, function(me)
+	{
+		var cell = mxCellMarker.prototype.getCell.apply(marker, arguments);
+		this.error = null;
+		
+		// Checks for cell at preview point (with grid)
+		if (cell == null && this.currentPoint != null)
+		{
+			cell = this.graph.getCellAt(this.currentPoint.x, this.currentPoint.y);
+		}
+		
+		// Uses connectable parent vertex if one exists
+		if (cell != null && !this.graph.isCellConnectable(cell))
+		{
+			var parent = this.graph.getModel().getParent(cell);
+			
+			if (this.graph.getModel().isVertex(parent) && this.graph.isCellConnectable(parent))
+			{
+				cell = parent;
+			}
+		}
+		
+		if ((this.graph.isSwimlane(cell) && this.currentPoint != null &&
+			this.graph.hitsSwimlaneContent(cell, this.currentPoint.x, this.currentPoint.y)) ||
+			!this.isConnectableCell(cell))
+		{
+			cell = null;
+		}
+		
+		if (cell != null)
+		{
+			if (this.isConnecting())
+			{
+				if (this.previous != null)
+				{
+					this.error = this.validateConnection(this.previous.cell, cell);
+					
+					if (this.error != null && this.error.length == 0)
+					{
+						cell = null;
+						
+						// Enables create target inside groups
+						if (this.isCreateTarget(me.getEvent()))
+						{
+							this.error = null;
+						}
+					}
+				}
+			}
+			else if (!this.isValidSource(cell, me))
+			{
+				cell = null;
+			}
+		}
+		else if (this.isConnecting() && !this.isCreateTarget(me.getEvent()) &&
+				!this.graph.allowDanglingEdges)
+		{
+			this.error = '';
+		}
+
+		return cell;
+	});
+
+	// Sets the highlight color according to validateConnection
+	marker.isValidState = mxUtils.bind(this, function(state)
+	{
+		if (this.isConnecting())
+		{
+			return this.error == null;
+		}
+		else
+		{
+			return mxCellMarker.prototype.isValidState.apply(marker, arguments);
+		}
+	});
+
+	// Overrides to use marker color only in highlight mode or for
+	// target selection
+	marker.getMarkerColor = mxUtils.bind(this, function(evt, state, isValid)
+	{
+		return (this.connectImage == null || this.isConnecting()) ?
+			mxCellMarker.prototype.getMarkerColor.apply(marker, arguments) :
+			null;
+	});
+
+	// Overrides to use hotspot only for source selection otherwise
+	// intersects always returns true when over a cell
+	marker.intersects = mxUtils.bind(this, function(state, evt)
+	{
+		if (this.connectImage != null || this.isConnecting())
+		{
+			return true;
+		}
+		
+		return mxCellMarker.prototype.intersects.apply(marker, arguments);
+	});
+
+	return marker;
+};
+
+/**
+ * Function: start
+ * 
+ * Starts a new connection for the given state and coordinates.
+ */
+mxConnectionHandler.prototype.start = function(state, x, y, edgeState)
+{
+	this.previous = state;
+	this.first = new mxPoint(x, y);
+	this.edgeState = (edgeState != null) ? edgeState : this.createEdgeState(null);
+	
+	// Marks the source state
+	this.marker.currentColor = this.marker.validColor;
+	this.marker.markedState = state;
+	this.marker.mark();
+
+	this.fireEvent(new mxEventObject(mxEvent.START, 'state', this.previous));
+};
+
+/**
+ * Function: isConnecting
+ * 
+ * Returns true if the source terminal has been clicked and a new
+ * connection is currently being previewed.
+ */
+mxConnectionHandler.prototype.isConnecting = function()
+{
+	return this.first != null && this.shape != null;
+};
+
+/**
+ * Function: isValidSource
+ * 
+ * Returns <mxGraph.isValidSource> for the given source terminal.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the source terminal.
+ * me - <mxMouseEvent> that is associated with this call.
+ */
+mxConnectionHandler.prototype.isValidSource = function(cell, me)
+{
+	return this.graph.isValidSource(cell);
+};
+
+/**
+ * Function: isValidTarget
+ * 
+ * Returns true. The call to <mxGraph.isValidTarget> is implicit by calling
+ * <mxGraph.getEdgeValidationError> in <validateConnection>. This is an
+ * additional hook for disabling certain targets in this specific handler.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the target terminal.
+ */
+mxConnectionHandler.prototype.isValidTarget = function(cell)
+{
+	return true;
+};
+
+/**
+ * Function: validateConnection
+ * 
+ * Returns the error message or an empty string if the connection for the
+ * given source target pair is not valid. Otherwise it returns null. This
+ * implementation uses <mxGraph.getEdgeValidationError>.
+ * 
+ * Parameters:
+ * 
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ */
+mxConnectionHandler.prototype.validateConnection = function(source, target)
+{
+	if (!this.isValidTarget(target))
+	{
+		return '';
+	}
+	
+	return this.graph.getEdgeValidationError(null, source, target);
+};
+
+/**
+ * Function: getConnectImage
+ * 
+ * Hook to return the <mxImage> used for the connection icon of the given
+ * <mxCellState>. This implementation returns <connectImage>.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose connect image should be returned.
+ */
+mxConnectionHandler.prototype.getConnectImage = function(state)
+{
+	return this.connectImage;
+};
+
+/**
+ * Function: isMoveIconToFrontForState
+ * 
+ * Returns true if the state has a HTML label in the graph's container, otherwise
+ * it returns <moveIconFront>.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose connect icons should be returned.
+ */
+mxConnectionHandler.prototype.isMoveIconToFrontForState = function(state)
+{
+	if (state.text != null && state.text.node.parentNode == this.graph.container)
+	{
+		return true;
+	}
+	
+	return this.moveIconFront;
+};
+
+/**
+ * Function: createIcons
+ * 
+ * Creates the array <mxImageShapes> that represent the connect icons for
+ * the given <mxCellState>.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose connect icons should be returned.
+ */
+mxConnectionHandler.prototype.createIcons = function(state)
+{
+	var image = this.getConnectImage(state);
+	
+	if (image != null && state != null)
+	{
+		this.iconState = state;
+		var icons = [];
+
+		// Cannot use HTML for the connect icons because the icon receives all
+		// mouse move events in IE, must use VML and SVG instead even if the
+		// connect-icon appears behind the selection border and the selection
+		// border consumes the events before the icon gets a chance
+		var bounds = new mxRectangle(0, 0, image.width, image.height);
+		var icon = new mxImageShape(bounds, image.src, null, null, 0);
+		icon.preserveImageAspect = false;
+		
+		if (this.isMoveIconToFrontForState(state))
+		{
+			icon.dialect = mxConstants.DIALECT_STRICTHTML;
+			icon.init(this.graph.container);
+		}
+		else
+		{
+			icon.dialect = (this.graph.dialect == mxConstants.DIALECT_SVG) ?
+				mxConstants.DIALECT_SVG : mxConstants.DIALECT_VML;
+			icon.init(this.graph.getView().getOverlayPane());
+
+			// Move the icon back in the overlay pane
+			if (this.moveIconBack && icon.node.previousSibling != null)
+			{
+				icon.node.parentNode.insertBefore(icon.node, icon.node.parentNode.firstChild);
+			}
+		}
+
+		icon.node.style.cursor = mxConstants.CURSOR_CONNECT;
+
+		// Events transparency
+		var getState = mxUtils.bind(this, function()
+		{
+			return (this.currentState != null) ? this.currentState : state;
+		});
+		
+		// Updates the local icon before firing the mouse down event.
+		var mouseDown = mxUtils.bind(this, function(evt)
+		{
+			if (!mxEvent.isConsumed(evt))
+			{
+				this.icon = icon;
+				this.graph.fireMouseEvent(mxEvent.MOUSE_DOWN,
+					new mxMouseEvent(evt, getState()));
+			}
+		});
+
+		mxEvent.redirectMouseEvents(icon.node, this.graph, getState, mouseDown);
+		
+		icons.push(icon);
+		this.redrawIcons(icons, this.iconState);
+		
+		return icons;
+	}
+	
+	return null;
+};
+
+/**
+ * Function: redrawIcons
+ * 
+ * Redraws the given array of <mxImageShapes>.
+ * 
+ * Parameters:
+ * 
+ * icons - Optional array of <mxImageShapes> to be redrawn.
+ */
+mxConnectionHandler.prototype.redrawIcons = function(icons, state)
+{
+	if (icons != null && icons[0] != null && state != null)
+	{
+		var pos = this.getIconPosition(icons[0], state);
+		icons[0].bounds.x = pos.x;
+		icons[0].bounds.y = pos.y;
+		icons[0].redraw();
+	}
+};
+
+/**
+ * Function: redrawIcons
+ * 
+ * Redraws the given array of <mxImageShapes>.
+ * 
+ * Parameters:
+ * 
+ * icons - Optional array of <mxImageShapes> to be redrawn.
+ */
+mxConnectionHandler.prototype.getIconPosition = function(icon, state)
+{
+	var scale = this.graph.getView().scale;
+	var cx = state.getCenterX();
+	var cy = state.getCenterY();
+	
+	if (this.graph.isSwimlane(state.cell))
+	{
+		var size = this.graph.getStartSize(state.cell);
+		
+		cx = (size.width != 0) ? state.x + size.width * scale / 2 : cx;
+		cy = (size.height != 0) ? state.y + size.height * scale / 2 : cy;
+		
+		var alpha = mxUtils.toRadians(mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION) || 0);
+		
+		if (alpha != 0)
+		{
+			var cos = Math.cos(alpha);
+			var sin = Math.sin(alpha);
+			var ct = new mxPoint(state.getCenterX(), state.getCenterY());
+			var pt = mxUtils.getRotatedPoint(new mxPoint(cx, cy), cos, sin, ct);
+			cx = pt.x;
+			cy = pt.y;
+		}
+	}
+
+	return new mxPoint(cx - icon.bounds.width / 2,
+			cy - icon.bounds.height / 2);
+};
+
+/**
+ * Function: destroyIcons
+ * 
+ * Destroys the connect icons and resets the respective state.
+ */
+mxConnectionHandler.prototype.destroyIcons = function()
+{
+	if (this.icons != null)
+	{
+		for (var i = 0; i < this.icons.length; i++)
+		{
+			this.icons[i].destroy();
+		}
+		
+		this.icons = null;
+		this.icon = null;
+		this.selectedIcon = null;
+		this.iconState = null;
+	}
+};
+
+/**
+ * Function: isStartEvent
+ * 
+ * Returns true if the given mouse down event should start this handler. The
+ * This implementation returns true if the event does not force marquee
+ * selection, and the currentConstraint and currentFocus of the
+ * <constraintHandler> are not null, or <previous> and <error> are not null and
+ * <icons> is null or <icons> and <icon> are not null.
+ */
+mxConnectionHandler.prototype.isStartEvent = function(me)
+{
+	return ((this.constraintHandler.currentFocus != null && this.constraintHandler.currentConstraint != null) ||
+		(this.previous != null && this.error == null && (this.icons == null || (this.icons != null &&
+		this.icon != null))));
+};
+
+/**
+ * Function: mouseDown
+ * 
+ * Handles the event by initiating a new connection.
+ */
+mxConnectionHandler.prototype.mouseDown = function(sender, me)
+{
+	this.mouseDownCounter++;
+	
+	if (this.isEnabled() && this.graph.isEnabled() && !me.isConsumed() &&
+		!this.isConnecting() && this.isStartEvent(me))
+	{
+		if (this.constraintHandler.currentConstraint != null &&
+			this.constraintHandler.currentFocus != null &&
+			this.constraintHandler.currentPoint != null)
+		{
+			this.sourceConstraint = this.constraintHandler.currentConstraint;
+			this.previous = this.constraintHandler.currentFocus;
+			this.first = this.constraintHandler.currentPoint.clone();
+		}
+		else
+		{
+			// Stores the location of the initial mousedown
+			this.first = new mxPoint(me.getGraphX(), me.getGraphY());
+		}
+	
+		this.edgeState = this.createEdgeState(me);
+		this.mouseDownCounter = 1;
+		
+		if (this.waypointsEnabled && this.shape == null)
+		{
+			this.waypoints = null;
+			this.shape = this.createShape();
+			
+			if (this.edgeState != null)
+			{
+				this.shape.apply(this.edgeState);
+			}
+		}
+
+		// Stores the starting point in the geometry of the preview
+		if (this.previous == null && this.edgeState != null)
+		{
+			var pt = this.graph.getPointForEvent(me.getEvent());
+			this.edgeState.cell.geometry.setTerminalPoint(pt, true);
+		}
+		
+		this.fireEvent(new mxEventObject(mxEvent.START, 'state', this.previous));
+
+		me.consume();
+	}
+
+	this.selectedIcon = this.icon;
+	this.icon = null;
+};
+
+/**
+ * Function: isImmediateConnectSource
+ * 
+ * Returns true if a tap on the given source state should immediately start
+ * connecting. This implementation returns true if the state is not movable
+ * in the graph. 
+ */
+mxConnectionHandler.prototype.isImmediateConnectSource = function(state)
+{
+	return !this.graph.isCellMovable(state.cell);
+};
+
+/**
+ * Function: createEdgeState
+ * 
+ * Hook to return an <mxCellState> which may be used during the preview.
+ * This implementation returns null.
+ * 
+ * Use the following code to create a preview for an existing edge style:
+ * 
+ * (code)
+ * graph.connectionHandler.createEdgeState = function(me)
+ * {
+ *   var edge = graph.createEdge(null, null, null, null, null, 'edgeStyle=elbowEdgeStyle');
+ *   
+ *   return new mxCellState(this.graph.view, edge, this.graph.getCellStyle(edge));
+ * };
+ * (end)
+ */
+mxConnectionHandler.prototype.createEdgeState = function(me)
+{
+	return null;
+};
+
+/**
+ * Function: isOutlineConnectEvent
+ * 
+ * Returns true if <outlineConnect> is true and the source of the event is the outline shape
+ * or shift is pressed.
+ */
+mxConnectionHandler.prototype.isOutlineConnectEvent = function(me)
+{
+	var offset = mxUtils.getOffset(this.graph.container);
+	var evt = me.getEvent();
+	
+	var clientX = mxEvent.getClientX(evt);
+	var clientY = mxEvent.getClientY(evt);
+	
+	var doc = document.documentElement;
+	var left = (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0);
+	var top = (window.pageYOffset || doc.scrollTop)  - (doc.clientTop || 0);
+	
+	var gridX = this.currentPoint.x - this.graph.container.scrollLeft + offset.x - left;
+	var gridY = this.currentPoint.y - this.graph.container.scrollTop + offset.y - top;
+
+	return this.outlineConnect && !mxEvent.isShiftDown(me.getEvent()) &&
+		(me.isSource(this.marker.highlight.shape) ||
+		(mxEvent.isAltDown(me.getEvent()) && me.getState() != null) ||
+		this.marker.highlight.isHighlightAt(clientX, clientY) ||
+		((gridX != clientX || gridY != clientY) && me.getState() == null &&
+		this.marker.highlight.isHighlightAt(gridX, gridY)));
+};
+
+/**
+ * Function: updateCurrentState
+ * 
+ * Updates the current state for a given mouse move event by using
+ * the <marker>.
+ */
+mxConnectionHandler.prototype.updateCurrentState = function(me, point)
+{
+	this.constraintHandler.update(me, this.first == null, false, (this.first == null ||
+		me.isSource(this.marker.highlight.shape)) ? null : point);
+	
+	if (this.constraintHandler.currentFocus != null && this.constraintHandler.currentConstraint != null)
+	{
+		// Handles special case where grid is large and connection point is at actual point in which
+		// case the outline is not followed as long as we're < gridSize / 2 away from that point
+		if (this.marker.highlight != null && this.marker.highlight.state != null &&
+			this.marker.highlight.state.cell == this.constraintHandler.currentFocus.cell)
+		{
+			// Direct repaint needed if cell already highlighted
+			if (this.marker.highlight.shape.stroke != 'transparent')
+			{
+				this.marker.highlight.shape.stroke = 'transparent';
+				this.marker.highlight.repaint();
+			}
+		}
+		else
+		{
+			this.marker.markCell(this.constraintHandler.currentFocus.cell, 'transparent');
+		}
+
+		// Updates validation state
+		if (this.previous != null)
+		{
+			this.error = this.validateConnection(this.previous.cell, this.constraintHandler.currentFocus.cell);
+			
+			if (this.error == null)
+			{
+				this.currentState = this.constraintHandler.currentFocus;
+			}
+			else
+			{
+				this.constraintHandler.reset();
+			}
+		}
+	}
+	else
+	{
+		if (this.graph.isIgnoreTerminalEvent(me.getEvent()))
+		{
+			this.marker.reset();
+			this.currentState = null;
+		}
+		else
+		{
+			this.marker.process(me);
+			this.currentState = this.marker.getValidState();
+		}
+
+		var outline = this.isOutlineConnectEvent(me);
+		
+		if (this.currentState != null && outline)
+		{
+			// Handles special case where mouse is on outline away from actual end point
+			// in which case the grid is ignored and mouse point is used instead
+			if (me.isSource(this.marker.highlight.shape))
+			{
+				point = new mxPoint(me.getGraphX(), me.getGraphY());
+			}
+			
+			var constraint = this.graph.getOutlineConstraint(point, this.currentState, me);
+			this.constraintHandler.setFocus(me, this.currentState, false);
+			this.constraintHandler.currentConstraint = constraint;
+			this.constraintHandler.currentPoint = point;
+		}
+
+		if (this.outlineConnect)
+		{
+			if (this.marker.highlight != null && this.marker.highlight.shape != null)
+			{
+				var s = this.graph.view.scale;
+				
+				if (this.constraintHandler.currentConstraint != null &&
+					this.constraintHandler.currentFocus != null)
+				{
+					this.marker.highlight.shape.stroke = mxConstants.OUTLINE_HIGHLIGHT_COLOR;
+					this.marker.highlight.shape.strokewidth = mxConstants.OUTLINE_HIGHLIGHT_STROKEWIDTH / s / s;
+					this.marker.highlight.repaint();
+				} 
+				else if (this.marker.hasValidState())
+				{
+					// Handles special case where actual end point of edge and current mouse point
+					// are not equal (due to grid snapping) and there is no hit on shape or highlight
+					if (this.marker.getValidState() != me.getState())
+					{
+						this.marker.highlight.shape.stroke = 'transparent';
+						this.currentState = null;
+					}
+					else
+					{
+						this.marker.highlight.shape.stroke = mxConstants.DEFAULT_VALID_COLOR;
+					}
+	
+					this.marker.highlight.shape.strokewidth = mxConstants.HIGHLIGHT_STROKEWIDTH / s / s;
+					this.marker.highlight.repaint();
+				}
+			}
+		}
+	}
+};
+
+/**
+ * Function: convertWaypoint
+ * 
+ * Converts the given point from screen coordinates to model coordinates.
+ */
+mxConnectionHandler.prototype.convertWaypoint = function(point)
+{
+	var scale = this.graph.getView().getScale();
+	var tr = this.graph.getView().getTranslate();
+	
+	point.x = point.x / scale - tr.x;
+	point.y = point.y / scale - tr.y;
+};
+
+/**
+ * Function: snapToPreview
+ * 
+ * Called to snap the given point to the current preview. This snaps to the
+ * first point of the preview if alt is not pressed.
+ */
+mxConnectionHandler.prototype.snapToPreview = function(me, point)
+{
+	if (!mxEvent.isAltDown(me.getEvent()) && this.previous != null)
+	{
+		var tol = this.graph.gridSize * this.graph.view.scale / 2;	
+		var tmp = (this.sourceConstraint != null) ? this.first :
+			new mxPoint(this.previous.getCenterX(), this.previous.getCenterY());
+
+		if (Math.abs(tmp.x - me.getGraphX()) < tol)
+		{
+			point.x = tmp.x;
+		}
+		
+		if (Math.abs(tmp.y - me.getGraphY()) < tol)
+		{
+			point.y = tmp.y;
+		}
+	}	
+};
+
+/**
+ * Function: mouseMove
+ * 
+ * Handles the event by updating the preview edge or by highlighting
+ * a possible source or target terminal.
+ */
+mxConnectionHandler.prototype.mouseMove = function(sender, me)
+{
+	if (!me.isConsumed() && (this.ignoreMouseDown || this.first != null || !this.graph.isMouseDown))
+	{
+		// Handles special case when handler is disabled during highlight
+		if (!this.isEnabled() && this.currentState != null)
+		{
+			this.destroyIcons();
+			this.currentState = null;
+		}
+
+		var view = this.graph.getView();
+		var scale = view.scale;
+		var tr = view.translate;
+		var point = new mxPoint(me.getGraphX(), me.getGraphY());
+		this.error = null;
+
+		if (this.graph.isGridEnabledEvent(me.getEvent()))
+		{
+			point = new mxPoint((this.graph.snap(point.x / scale - tr.x) + tr.x) * scale,
+				(this.graph.snap(point.y / scale - tr.y) + tr.y) * scale);
+		}
+		
+		this.snapToPreview(me, point);
+		this.currentPoint = point;
+		
+		if (this.first != null || (this.isEnabled() && this.graph.isEnabled()))
+		{
+			this.updateCurrentState(me, point);
+		}
+
+		if (this.first != null)
+		{
+			var constraint = null;
+			var current = point;
+			
+			// Uses the current point from the constraint handler if available
+			if (this.constraintHandler.currentConstraint != null &&
+				this.constraintHandler.currentFocus != null &&
+				this.constraintHandler.currentPoint != null)
+			{
+				constraint = this.constraintHandler.currentConstraint;
+				current = this.constraintHandler.currentPoint.clone();
+			}
+			else if (this.previous != null && !this.graph.isIgnoreTerminalEvent(me.getEvent()) && mxEvent.isShiftDown(me.getEvent()))
+			{
+				if (Math.abs(this.previous.getCenterX() - point.x) < Math.abs(this.previous.getCenterY() - point.y))
+				{
+					point.x = this.previous.getCenterX();
+				}
+				else
+				{
+					point.y = this.previous.getCenterY();
+				}
+			}
+			
+			var pt2 = this.first;
+			
+			// Moves the connect icon with the mouse
+			if (this.selectedIcon != null)
+			{
+				var w = this.selectedIcon.bounds.width;
+				var h = this.selectedIcon.bounds.height;
+				
+				if (this.currentState != null && this.targetConnectImage)
+				{
+					var pos = this.getIconPosition(this.selectedIcon, this.currentState);
+					this.selectedIcon.bounds.x = pos.x;
+					this.selectedIcon.bounds.y = pos.y;
+				}
+				else
+				{
+					var bounds = new mxRectangle(me.getGraphX() + this.connectIconOffset.x,
+						me.getGraphY() + this.connectIconOffset.y, w, h);
+					this.selectedIcon.bounds = bounds;
+				}
+				
+				this.selectedIcon.redraw();
+			}
+
+			// Uses edge state to compute the terminal points
+			if (this.edgeState != null)
+			{
+				this.updateEdgeState(current, constraint);
+				current = this.edgeState.absolutePoints[this.edgeState.absolutePoints.length - 1];
+				pt2 = this.edgeState.absolutePoints[0];
+			}
+			else
+			{
+				if (this.currentState != null)
+				{
+					if (this.constraintHandler.currentConstraint == null)
+					{
+						var tmp = this.getTargetPerimeterPoint(this.currentState, me);
+						
+						if (tmp != null)
+						{
+							current = tmp;
+						}
+					}
+				}
+				
+				// Computes the source perimeter point
+				if (this.sourceConstraint == null && this.previous != null)
+				{
+					var next = (this.waypoints != null && this.waypoints.length > 0) ?
+							this.waypoints[0] : current;
+					var tmp = this.getSourcePerimeterPoint(this.previous, next, me);
+					
+					if (tmp != null)
+					{
+						pt2 = tmp;
+					}
+				}
+			}
+
+			// Makes sure the cell under the mousepointer can be detected
+			// by moving the preview shape away from the mouse. This
+			// makes sure the preview shape does not prevent the detection
+			// of the cell under the mousepointer even for slow gestures.
+			if (this.currentState == null && this.movePreviewAway)
+			{
+				var tmp = pt2; 
+				
+				if (this.edgeState != null && this.edgeState.absolutePoints.length >= 2)
+				{
+					var tmp2 = this.edgeState.absolutePoints[this.edgeState.absolutePoints.length - 2];
+					
+					if (tmp2 != null)
+					{
+						tmp = tmp2;
+					}
+				}
+				
+				var dx = current.x - tmp.x;
+				var dy = current.y - tmp.y;
+				
+				var len = Math.sqrt(dx * dx + dy * dy);
+				
+				if (len == 0)
+				{
+					return;
+				}
+
+				// Stores old point to reuse when creating edge
+				this.originalPoint = current.clone();
+				current.x -= dx * 4 / len;
+				current.y -= dy * 4 / len;
+			}
+			else
+			{
+				this.originalPoint = null;
+			}
+			
+			// Creates the preview shape (lazy)
+			if (this.shape == null)
+			{
+				var dx = Math.abs(point.x - this.first.x);
+				var dy = Math.abs(point.y - this.first.y);
+
+				if (dx > this.graph.tolerance || dy > this.graph.tolerance)
+				{
+					this.shape = this.createShape();
+
+					if (this.edgeState != null)
+					{
+						this.shape.apply(this.edgeState);
+					}
+					
+					// Revalidates current connection
+					this.updateCurrentState(me, point);
+				}
+			}
+
+			// Updates the points in the preview edge
+			if (this.shape != null)
+			{
+				if (this.edgeState != null)
+				{
+					this.shape.points = this.edgeState.absolutePoints;
+				}
+				else
+				{
+					var pts = [pt2];
+					
+					if (this.waypoints != null)
+					{
+						pts = pts.concat(this.waypoints);
+					}
+
+					pts.push(current);
+					this.shape.points = pts;
+				}
+				
+				this.drawPreview();
+			}
+			
+			// Makes sure endpoint of edge is visible during connect
+			if (this.cursor != null)
+			{
+				this.graph.container.style.cursor = this.cursor;
+			}
+			
+			mxEvent.consume(me.getEvent());
+			me.consume();
+		}
+		else if (!this.isEnabled() || !this.graph.isEnabled())
+		{
+			this.constraintHandler.reset();
+		}
+		else if (this.previous != this.currentState && this.edgeState == null)
+		{
+			this.destroyIcons();
+			
+			// Sets the cursor on the current shape				
+			if (this.currentState != null && this.error == null && this.constraintHandler.currentConstraint == null)
+			{
+				this.icons = this.createIcons(this.currentState);
+
+				if (this.icons == null)
+				{
+					this.currentState.setCursor(mxConstants.CURSOR_CONNECT);
+					me.consume();
+				}
+			}
+
+			this.previous = this.currentState;
+		}
+		else if (this.previous == this.currentState && this.currentState != null && this.icons == null &&
+			!this.graph.isMouseDown)
+		{
+			// Makes sure that no cursors are changed
+			me.consume();
+		}
+
+		if (!this.graph.isMouseDown && this.currentState != null && this.icons != null)
+		{
+			var hitsIcon = false;
+			var target = me.getSource();
+			
+			for (var i = 0; i < this.icons.length && !hitsIcon; i++)
+			{
+				hitsIcon = target == this.icons[i].node || target.parentNode == this.icons[i].node;
+			}
+
+			if (!hitsIcon)
+			{
+				this.updateIcons(this.currentState, this.icons, me);
+			}
+		}
+	}
+	else
+	{
+		this.constraintHandler.reset();
+	}
+};
+
+/**
+ * Function: updateEdgeState
+ * 
+ * Updates <edgeState>.
+ */
+mxConnectionHandler.prototype.updateEdgeState = function(current, constraint)
+{
+	// TODO: Use generic method for writing constraint to style
+	if (this.sourceConstraint != null && this.sourceConstraint.point != null)
+	{
+		this.edgeState.style[mxConstants.STYLE_EXIT_X] = this.sourceConstraint.point.x;
+		this.edgeState.style[mxConstants.STYLE_EXIT_Y] = this.sourceConstraint.point.y;
+	}
+
+	if (constraint != null && constraint.point != null)
+	{
+		this.edgeState.style[mxConstants.STYLE_ENTRY_X] = constraint.point.x;
+		this.edgeState.style[mxConstants.STYLE_ENTRY_Y] = constraint.point.y;
+	}
+	else
+	{
+		delete this.edgeState.style[mxConstants.STYLE_ENTRY_X];
+		delete this.edgeState.style[mxConstants.STYLE_ENTRY_Y];
+	}
+	
+	this.edgeState.absolutePoints = [null, (this.currentState != null) ? null : current];
+	this.graph.view.updateFixedTerminalPoint(this.edgeState, this.previous, true, this.sourceConstraint);
+	
+	if (this.currentState != null)
+	{
+		if (constraint == null)
+		{
+			constraint = this.graph.getConnectionConstraint(this.edgeState, this.previous, false);
+		}
+		
+		this.edgeState.setAbsoluteTerminalPoint(null, false);
+		this.graph.view.updateFixedTerminalPoint(this.edgeState, this.currentState, false, constraint);
+	}
+	
+	// Scales and translates the waypoints to the model
+	var realPoints = null;
+	
+	if (this.waypoints != null)
+	{
+		realPoints = [];
+		
+		for (var i = 0; i < this.waypoints.length; i++)
+		{
+			var pt = this.waypoints[i].clone();
+			this.convertWaypoint(pt);
+			realPoints[i] = pt;
+		}
+	}
+	
+	this.graph.view.updatePoints(this.edgeState, realPoints, this.previous, this.currentState);
+	this.graph.view.updateFloatingTerminalPoints(this.edgeState, this.previous, this.currentState);
+};
+
+/**
+ * Function: getTargetPerimeterPoint
+ * 
+ * Returns the perimeter point for the given target state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> that represents the target cell state.
+ * me - <mxMouseEvent> that represents the mouse move.
+ */
+mxConnectionHandler.prototype.getTargetPerimeterPoint = function(state, me)
+{
+	var result = null;
+	var view = state.view;
+	var targetPerimeter = view.getPerimeterFunction(state);
+	
+	if (targetPerimeter != null)
+	{
+		var next = (this.waypoints != null && this.waypoints.length > 0) ?
+				this.waypoints[this.waypoints.length - 1] :
+				new mxPoint(this.previous.getCenterX(), this.previous.getCenterY());
+		var tmp = targetPerimeter(view.getPerimeterBounds(state),
+			this.edgeState, next, false);
+			
+		if (tmp != null)
+		{
+			result = tmp;
+		}
+	}
+	else
+	{
+		result = new mxPoint(state.getCenterX(), state.getCenterY());
+	}
+	
+	return result;
+};
+
+/**
+ * Function: getSourcePerimeterPoint
+ * 
+ * Hook to update the icon position(s) based on a mouseOver event. This is
+ * an empty implementation.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> that represents the target cell state.
+ * next - <mxPoint> that represents the next point along the previewed edge.
+ * me - <mxMouseEvent> that represents the mouse move.
+ */
+mxConnectionHandler.prototype.getSourcePerimeterPoint = function(state, next, me)
+{
+	var result = null;
+	var view = state.view;
+	var sourcePerimeter = view.getPerimeterFunction(state);
+	var c = new mxPoint(state.getCenterX(), state.getCenterY());
+	
+	if (sourcePerimeter != null)
+	{
+		var theta = mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION, 0);
+		var rad = -theta * (Math.PI / 180);
+		
+		if (theta != 0)
+		{
+			next = mxUtils.getRotatedPoint(new mxPoint(next.x, next.y), Math.cos(rad), Math.sin(rad), c);
+		}
+		
+		var tmp = sourcePerimeter(view.getPerimeterBounds(state), state, next, false);
+			
+		if (tmp != null)
+		{
+			if (theta != 0)
+			{
+				tmp = mxUtils.getRotatedPoint(new mxPoint(tmp.x, tmp.y), Math.cos(-rad), Math.sin(-rad), c);
+			}
+			
+			result = tmp;
+		}
+	}
+	else
+	{
+		result = c;
+	}
+	
+	return result;
+};
+
+
+/**
+ * Function: updateIcons
+ * 
+ * Hook to update the icon position(s) based on a mouseOver event. This is
+ * an empty implementation.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> under the mouse.
+ * icons - Array of currently displayed icons.
+ * me - <mxMouseEvent> that contains the mouse event.
+ */
+mxConnectionHandler.prototype.updateIcons = function(state, icons, me)
+{
+	// empty
+};
+
+/**
+ * Function: isStopEvent
+ * 
+ * Returns true if the given mouse up event should stop this handler. The
+ * connection will be created if <error> is null. Note that this is only
+ * called if <waypointsEnabled> is true. This implemtation returns true
+ * if there is a cell state in the given event.
+ */
+mxConnectionHandler.prototype.isStopEvent = function(me)
+{
+	return me.getState() != null;
+};
+
+/**
+ * Function: addWaypoint
+ * 
+ * Adds the waypoint for the given event to <waypoints>.
+ */
+mxConnectionHandler.prototype.addWaypointForEvent = function(me)
+{
+	var point = mxUtils.convertPoint(this.graph.container, me.getX(), me.getY());
+	var dx = Math.abs(point.x - this.first.x);
+	var dy = Math.abs(point.y - this.first.y);
+	var addPoint = this.waypoints != null || (this.mouseDownCounter > 1 &&
+			(dx > this.graph.tolerance || dy > this.graph.tolerance));
+
+	if (addPoint)
+	{
+		if (this.waypoints == null)
+		{
+			this.waypoints = [];
+		}
+		
+		var scale = this.graph.view.scale;
+		var point = new mxPoint(this.graph.snap(me.getGraphX() / scale) * scale,
+				this.graph.snap(me.getGraphY() / scale) * scale);
+		this.waypoints.push(point);
+	}
+};
+
+/**
+ * Function: mouseUp
+ * 
+ * Handles the event by inserting the new connection.
+ */
+mxConnectionHandler.prototype.mouseUp = function(sender, me)
+{
+	if (!me.isConsumed() && this.isConnecting())
+	{
+		if (this.waypointsEnabled && !this.isStopEvent(me))
+		{
+			this.addWaypointForEvent(me);
+			me.consume();
+			
+			return;
+		}
+		
+		// Inserts the edge if no validation error exists
+		if (this.error == null)
+		{
+			var source = (this.previous != null) ? this.previous.cell : null;
+			var target = null;
+			
+			if (this.constraintHandler.currentConstraint != null &&
+				this.constraintHandler.currentFocus != null)
+			{
+				target = this.constraintHandler.currentFocus.cell;
+			}
+			
+			if (target == null && this.currentState != null)
+			{
+				target = this.currentState.cell;
+			}
+			
+			this.connect(source, target, me.getEvent(), me.getCell());
+		}
+		else
+		{
+			// Selects the source terminal for self-references
+			if (this.previous != null && this.marker.validState != null &&
+				this.previous.cell == this.marker.validState.cell)
+			{
+				this.graph.selectCellForEvent(this.marker.source, evt);
+			}
+			
+			// Displays the error message if it is not an empty string,
+			// for empty error messages, the event is silently dropped
+			if (this.error.length > 0)
+			{
+				this.graph.validationAlert(this.error);
+			}
+		}
+		
+		// Redraws the connect icons and resets the handler state
+		this.destroyIcons();
+		me.consume();
+	}
+
+	if (this.first != null)
+	{
+		this.reset();
+	}
+};
+
+/**
+ * Function: reset
+ * 
+ * Resets the state of this handler.
+ */
+mxConnectionHandler.prototype.reset = function()
+{
+	if (this.shape != null)
+	{
+		this.shape.destroy();
+		this.shape = null;
+	}
+	
+	// Resets the cursor on the container
+	if (this.cursor != null && this.graph.container != null)
+	{
+		this.graph.container.style.cursor = '';
+	}
+	
+	this.destroyIcons();
+	this.marker.reset();
+	this.constraintHandler.reset();
+	this.originalPoint = null;
+	this.currentPoint = null;
+	this.edgeState = null;
+	this.previous = null;
+	this.error = null;
+	this.sourceConstraint = null;
+	this.mouseDownCounter = 0;
+	this.first = null;
+
+	this.fireEvent(new mxEventObject(mxEvent.RESET));
+};
+
+/**
+ * Function: drawPreview
+ * 
+ * Redraws the preview edge using the color and width returned by
+ * <getEdgeColor> and <getEdgeWidth>.
+ */
+mxConnectionHandler.prototype.drawPreview = function()
+{
+	this.updatePreview(this.error == null);
+	this.shape.redraw();
+};
+
+/**
+ * Function: getEdgeColor
+ * 
+ * Returns the color used to draw the preview edge. This returns green if
+ * there is no edge validation error and red otherwise.
+ * 
+ * Parameters:
+ * 
+ * valid - Boolean indicating if the color for a valid edge should be
+ * returned.
+ */
+mxConnectionHandler.prototype.updatePreview = function(valid)
+{
+	this.shape.strokewidth = this.getEdgeWidth(valid);
+	this.shape.stroke = this.getEdgeColor(valid);
+};
+
+/**
+ * Function: getEdgeColor
+ * 
+ * Returns the color used to draw the preview edge. This returns green if
+ * there is no edge validation error and red otherwise.
+ * 
+ * Parameters:
+ * 
+ * valid - Boolean indicating if the color for a valid edge should be
+ * returned.
+ */
+mxConnectionHandler.prototype.getEdgeColor = function(valid)
+{
+	return (valid) ? mxConstants.VALID_COLOR : mxConstants.INVALID_COLOR;
+};
+	
+/**
+ * Function: getEdgeWidth
+ * 
+ * Returns the width used to draw the preview edge. This returns 3 if
+ * there is no edge validation error and 1 otherwise.
+ * 
+ * Parameters:
+ * 
+ * valid - Boolean indicating if the width for a valid edge should be
+ * returned.
+ */
+mxConnectionHandler.prototype.getEdgeWidth = function(valid)
+{
+	return (valid) ? 3 : 1;
+};
+
+/**
+ * Function: connect
+ * 
+ * Connects the given source and target using a new edge. This
+ * implementation uses <createEdge> to create the edge.
+ * 
+ * Parameters:
+ * 
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ * evt - Mousedown event of the connect gesture.
+ * dropTarget - <mxCell> that represents the cell under the mouse when it was
+ * released.
+ */
+mxConnectionHandler.prototype.connect = function(source, target, evt, dropTarget)
+{
+	if (target != null || this.isCreateTarget(evt) || this.graph.allowDanglingEdges)
+	{
+		// Uses the common parent of source and target or
+		// the default parent to insert the edge
+		var model = this.graph.getModel();
+		var terminalInserted = false;
+		var edge = null;
+
+		model.beginUpdate();
+		try
+		{
+			if (source != null && target == null && !this.graph.isIgnoreTerminalEvent(evt) && this.isCreateTarget(evt))
+			{
+				target = this.createTargetVertex(evt, source);
+				
+				if (target != null)
+				{
+					dropTarget = this.graph.getDropTarget([target], evt, dropTarget);
+					terminalInserted = true;
+					
+					// Disables edges as drop targets if the target cell was created
+					// FIXME: Should not shift if vertex was aligned (same in Java)
+					if (dropTarget == null || !this.graph.getModel().isEdge(dropTarget))
+					{
+						var pstate = this.graph.getView().getState(dropTarget);
+						
+						if (pstate != null)
+						{
+							var tmp = model.getGeometry(target);
+							tmp.x -= pstate.origin.x;
+							tmp.y -= pstate.origin.y;
+						}
+					}
+					else
+					{
+						dropTarget = this.graph.getDefaultParent();
+					}
+						
+					this.graph.addCell(target, dropTarget);
+				}
+			}
+
+			var parent = this.graph.getDefaultParent();
+
+			if (source != null && target != null &&
+				model.getParent(source) == model.getParent(target) &&
+				model.getParent(model.getParent(source)) != model.getRoot())
+			{
+				parent = model.getParent(source);
+
+				if ((source.geometry != null && source.geometry.relative) &&
+					(target.geometry != null && target.geometry.relative))
+				{
+					parent = model.getParent(parent);
+				}
+			}
+			
+			// Uses the value of the preview edge state for inserting
+			// the new edge into the graph
+			var value = null;
+			var style = null;
+			
+			if (this.edgeState != null)
+			{
+				value = this.edgeState.cell.value;
+				style = this.edgeState.cell.style;
+			}
+
+			edge = this.insertEdge(parent, null, value, source, target, style);
+			
+			if (edge != null)
+			{
+				// Updates the connection constraints
+				this.graph.setConnectionConstraint(edge, source, true, this.sourceConstraint);
+				this.graph.setConnectionConstraint(edge, target, false, this.constraintHandler.currentConstraint);
+				
+				// Uses geometry of the preview edge state
+				if (this.edgeState != null)
+				{
+					model.setGeometry(edge, this.edgeState.cell.geometry);
+				}
+				
+				var parent = model.getParent(source);
+				
+				// Inserts edge before source
+				if (this.isInsertBefore(edge, source, target, evt, dropTarget))
+				{
+					var index = null;
+					var tmp = source;
+
+					while (tmp.parent != null && tmp.geometry != null &&
+						tmp.geometry.relative && tmp.parent != edge.parent)
+					{
+						tmp = this.graph.model.getParent(tmp);
+					}
+
+					if (tmp != null && tmp.parent != null && tmp.parent == edge.parent)
+					{
+						var index = tmp.parent.getIndex(tmp);
+						tmp.parent.insert(edge, index);
+					}
+				}
+				
+				// Makes sure the edge has a non-null, relative geometry
+				var geo = model.getGeometry(edge);
+
+				if (geo == null)
+				{
+					geo = new mxGeometry();
+					geo.relative = true;
+					
+					model.setGeometry(edge, geo);
+				}
+				
+				// Uses scaled waypoints in geometry
+				if (this.waypoints != null && this.waypoints.length > 0)
+				{
+					var s = this.graph.view.scale;
+					var tr = this.graph.view.translate;
+					geo.points = [];
+					
+					for (var i = 0; i < this.waypoints.length; i++)
+					{
+						var pt = this.waypoints[i];
+						geo.points.push(new mxPoint(pt.x / s - tr.x, pt.y / s - tr.y));
+					}
+				}
+
+				if (target == null)
+				{
+					var t = this.graph.view.translate;
+					var s = this.graph.view.scale;
+					var pt = (this.originalPoint != null) ?
+							new mxPoint(this.originalPoint.x / s - t.x, this.originalPoint.y / s - t.y) :
+						new mxPoint(this.currentPoint.x / s - t.x, this.currentPoint.y / s - t.y);
+					pt.x -= this.graph.panDx / this.graph.view.scale;
+					pt.y -= this.graph.panDy / this.graph.view.scale;
+					geo.setTerminalPoint(pt, false);
+				}
+				
+				this.fireEvent(new mxEventObject(mxEvent.CONNECT, 'cell', edge, 'terminal', target,
+					'event', evt, 'target', dropTarget, 'terminalInserted', terminalInserted));
+			}
+		}
+		catch (e)
+		{
+			mxLog.show();
+			mxLog.debug(e.message);
+		}
+		finally
+		{
+			model.endUpdate();
+		}
+		
+		if (this.select)
+		{
+			this.selectCells(edge, (terminalInserted) ? target : null);
+		}
+	}
+};
+
+/**
+ * Function: selectCells
+ * 
+ * Selects the given edge after adding a new connection. The target argument
+ * contains the target vertex if one has been inserted.
+ */
+mxConnectionHandler.prototype.selectCells = function(edge, target)
+{
+	this.graph.setSelectionCell(edge);
+};
+
+/**
+ * Function: insertEdge
+ * 
+ * Creates, inserts and returns the new edge for the given parameters. This
+ * implementation does only use <createEdge> if <factoryMethod> is defined,
+ * otherwise <mxGraph.insertEdge> will be used.
+ */
+mxConnectionHandler.prototype.insertEdge = function(parent, id, value, source, target, style)
+{
+	if (this.factoryMethod == null)
+	{
+		return this.graph.insertEdge(parent, id, value, source, target, style);
+	}
+	else
+	{
+		var edge = this.createEdge(value, source, target, style);
+		edge = this.graph.addEdge(edge, parent, source, target);
+		
+		return edge;
+	}
+};
+
+/**
+ * Function: createTargetVertex
+ * 
+ * Hook method for creating new vertices on the fly if no target was
+ * under the mouse. This is only called if <createTarget> is true and
+ * returns null.
+ * 
+ * Parameters:
+ * 
+ * evt - Mousedown event of the connect gesture.
+ * source - <mxCell> that represents the source terminal.
+ */
+mxConnectionHandler.prototype.createTargetVertex = function(evt, source)
+{
+	// Uses the first non-relative source
+	var geo = this.graph.getCellGeometry(source);
+	
+	while (geo != null && geo.relative)
+	{
+		source = this.graph.getModel().getParent(source);
+		geo = this.graph.getCellGeometry(source);
+	}
+	
+	var clone = this.graph.cloneCells([source])[0];
+	var geo = this.graph.getModel().getGeometry(clone);
+	
+	if (geo != null)
+	{
+		var t = this.graph.view.translate;
+		var s = this.graph.view.scale;
+		var point = new mxPoint(this.currentPoint.x / s - t.x, this.currentPoint.y / s - t.y);
+		geo.x = Math.round(point.x - geo.width / 2 - this.graph.panDx / s);
+		geo.y = Math.round(point.y - geo.height / 2 - this.graph.panDy / s);
+
+		// Aligns with source if within certain tolerance
+		var tol = this.getAlignmentTolerance();
+		
+		if (tol > 0)
+		{
+			var sourceState = this.graph.view.getState(source);
+			
+			if (sourceState != null)
+			{
+				var x = sourceState.x / s - t.x;
+				var y = sourceState.y / s - t.y;
+				
+				if (Math.abs(x - geo.x) <= tol)
+				{
+					geo.x = Math.round(x);
+				}
+				
+				if (Math.abs(y - geo.y) <= tol)
+				{
+					geo.y = Math.round(y);
+				}
+			}
+		}
+	}
+
+	return clone;		
+};
+
+/**
+ * Function: getAlignmentTolerance
+ * 
+ * Returns the tolerance for aligning new targets to sources. This returns the grid size / 2.
+ */
+mxConnectionHandler.prototype.getAlignmentTolerance = function(evt)
+{
+	return (this.graph.isGridEnabled()) ? this.graph.gridSize / 2 : this.graph.tolerance;
+};
+
+/**
+ * Function: createEdge
+ * 
+ * Creates and returns a new edge using <factoryMethod> if one exists. If
+ * no factory method is defined, then a new default edge is returned. The
+ * source and target arguments are informal, the actual connection is
+ * setup later by the caller of this function.
+ * 
+ * Parameters:
+ * 
+ * value - Value to be used for creating the edge.
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ * style - Optional style from the preview edge.
+ */
+mxConnectionHandler.prototype.createEdge = function(value, source, target, style)
+{
+	var edge = null;
+	
+	// Creates a new edge using the factoryMethod
+	if (this.factoryMethod != null)
+	{
+		edge = this.factoryMethod(source, target, style);
+	}
+	
+	if (edge == null)
+	{
+		edge = new mxCell(value || '');
+		edge.setEdge(true);
+		edge.setStyle(style);
+		
+		var geo = new mxGeometry();
+		geo.relative = true;
+		edge.setGeometry(geo);
+	}
+
+	return edge;
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the handler and all its resources and DOM nodes. This should be
+ * called on all instances. It is called automatically for the built-in
+ * instance created for each <mxGraph>.
+ */
+mxConnectionHandler.prototype.destroy = function()
+{
+	this.graph.removeMouseListener(this);
+	
+	if (this.shape != null)
+	{
+		this.shape.destroy();
+		this.shape = null;
+	}
+	
+	if (this.marker != null)
+	{
+		this.marker.destroy();
+		this.marker = null;
+	}
+
+	if (this.constraintHandler != null)
+	{
+		this.constraintHandler.destroy();
+		this.constraintHandler = null;
+	}
+
+	if (this.changeHandler != null)
+	{
+		this.graph.getModel().removeListener(this.changeHandler);
+		this.graph.getView().removeListener(this.changeHandler);
+		this.changeHandler = null;
+	}
+	
+	if (this.drillHandler != null)
+	{
+		this.graph.removeListener(this.drillHandler);
+		this.graph.getView().removeListener(this.drillHandler);
+		this.drillHandler = null;
+	}
+	
+	if (this.escapeHandler != null)
+	{
+		this.graph.removeListener(this.escapeHandler);
+		this.escapeHandler = null;
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/handler/mxConstraintHandler.js b/airavata-kubernetes/web-console/src/assets/js/handler/mxConstraintHandler.js
new file mode 100644
index 0000000..2ea07a2
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/handler/mxConstraintHandler.js
@@ -0,0 +1,520 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxConstraintHandler
+ *
+ * Handles constraints on connection targets. This class is in charge of
+ * showing fixed points when the mouse is over a vertex and handles constraints
+ * to establish new connections.
+ *
+ * Constructor: mxConstraintHandler
+ *
+ * Constructs an new constraint handler.
+ * 
+ * Parameters:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ * factoryMethod - Optional function to create the edge. The function takes
+ * the source and target <mxCell> as the first and second argument and
+ * returns the <mxCell> that represents the new edge.
+ */
+function mxConstraintHandler(graph)
+{
+	this.graph = graph;
+	
+	// Adds a graph model listener to update the current focus on changes
+	this.resetHandler = mxUtils.bind(this, function(sender, evt)
+	{
+		if (this.currentFocus != null && this.graph.view.getState(this.currentFocus.cell) == null)
+		{
+			this.reset();
+		}
+		else
+		{
+			this.redraw();
+		}
+	});
+	
+	this.graph.model.addListener(mxEvent.CHANGE, this.resetHandler);
+	this.graph.view.addListener(mxEvent.SCALE, this.resetHandler);
+	this.graph.addListener(mxEvent.ROOT, this.resetHandler);
+};
+
+/**
+ * Variable: pointImage
+ * 
+ * <mxImage> to be used as the image for fixed connection points.
+ */
+mxConstraintHandler.prototype.pointImage = new mxImage(mxClient.imageBasePath + '/point.gif', 5, 5);
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxConstraintHandler.prototype.graph = null;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies if events are handled. Default is true.
+ */
+mxConstraintHandler.prototype.enabled = true;
+
+/**
+ * Variable: highlightColor
+ * 
+ * Specifies the color for the highlight. Default is <mxConstants.DEFAULT_VALID_COLOR>.
+ */
+mxConstraintHandler.prototype.highlightColor = mxConstants.DEFAULT_VALID_COLOR;
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxConstraintHandler.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+	
+/**
+ * Function: setEnabled
+ * 
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ * 
+ * Parameters:
+ * 
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxConstraintHandler.prototype.setEnabled = function(enabled)
+{
+	this.enabled = enabled;
+};
+
+/**
+ * Function: reset
+ * 
+ * Resets the state of this handler.
+ */
+mxConstraintHandler.prototype.reset = function()
+{
+	if (this.focusIcons != null)
+	{
+		for (var i = 0; i < this.focusIcons.length; i++)
+		{
+			this.focusIcons[i].destroy();
+		}
+		
+		this.focusIcons = null;
+	}
+	
+	if (this.focusHighlight != null)
+	{
+		this.focusHighlight.destroy();
+		this.focusHighlight = null;
+	}
+	
+	this.currentConstraint = null;
+	this.currentFocusArea = null;
+	this.currentPoint = null;
+	this.currentFocus = null;
+	this.focusPoints = null;
+};
+
+/**
+ * Function: getTolerance
+ * 
+ * Returns the tolerance to be used for intersecting connection points. This
+ * implementation returns <mxGraph.tolerance>.
+ * 
+ * Parameters:
+ * 
+ * me - <mxMouseEvent> whose tolerance should be returned.
+ */
+mxConstraintHandler.prototype.getTolerance = function(me)
+{
+	return this.graph.getTolerance();
+};
+
+/**
+ * Function: getImageForConstraint
+ * 
+ * Returns the tolerance to be used for intersecting connection points.
+ */
+mxConstraintHandler.prototype.getImageForConstraint = function(state, constraint, point)
+{
+	return this.pointImage;
+};
+
+/**
+ * Function: isEventIgnored
+ * 
+ * Returns true if the given <mxMouseEvent> should be ignored in <update>. This
+ * implementation always returns false.
+ */
+mxConstraintHandler.prototype.isEventIgnored = function(me, source)
+{
+	return false;
+};
+
+/**
+ * Function: isStateIgnored
+ * 
+ * Returns true if the given state should be ignored. This always returns false.
+ */
+mxConstraintHandler.prototype.isStateIgnored = function(state, source)
+{
+	return false;
+};
+
+/**
+ * Function: destroyIcons
+ * 
+ * Destroys the <focusIcons> if they exist.
+ */
+mxConstraintHandler.prototype.destroyIcons = function()
+{
+	if (this.focusIcons != null)
+	{
+		for (var i = 0; i < this.focusIcons.length; i++)
+		{
+			this.focusIcons[i].destroy();
+		}
+		
+		this.focusIcons = null;
+		this.focusPoints = null;
+	}
+};
+
+/**
+ * Function: destroyFocusHighlight
+ * 
+ * Destroys the <focusHighlight> if one exists.
+ */
+mxConstraintHandler.prototype.destroyFocusHighlight = function()
+{
+	if (this.focusHighlight != null)
+	{
+		this.focusHighlight.destroy();
+		this.focusHighlight = null;
+	}
+};
+
+/**
+ * Function: isKeepFocusEvent
+ * 
+ * Returns true if the current focused state should not be changed for the given event.
+ * This returns true if shift and alt are pressed.
+ */
+mxConstraintHandler.prototype.isKeepFocusEvent = function(me)
+{
+	return mxEvent.isShiftDown(me.getEvent());
+};
+
+/**
+ * Function: getCellForEvent
+ * 
+ * Returns the cell for the given event.
+ */
+mxConstraintHandler.prototype.getCellForEvent = function(me, point)
+{
+	var cell = me.getCell();
+	
+	// Gets cell under actual point if different from event location
+	if (cell == null && point != null && (me.getGraphX() != point.x || me.getGraphY() != point.y))
+	{
+		cell = this.graph.getCellAt(point.x, point.y);
+	}
+	
+	// Uses connectable parent vertex if one exists
+	if (cell != null && !this.graph.isCellConnectable(cell))
+	{
+		var parent = this.graph.getModel().getParent(cell);
+		
+		if (this.graph.getModel().isVertex(parent) && this.graph.isCellConnectable(parent))
+		{
+			cell = parent;
+		}
+	}
+	
+	return cell;
+};
+
+/**
+ * Function: update
+ * 
+ * Updates the state of this handler based on the given <mxMouseEvent>.
+ * Source is a boolean indicating if the cell is a source or target.
+ */
+mxConstraintHandler.prototype.update = function(me, source, existingEdge, point)
+{
+	if (this.isEnabled() && !this.isEventIgnored(me))
+	{
+		// Lazy installation of mouseleave handler
+		if (this.mouseleaveHandler == null && this.graph.container != null)
+		{
+			this.mouseleaveHandler = mxUtils.bind(this, function()
+			{
+				this.reset();
+			});
+
+			mxEvent.addListener(this.graph.container, 'mouseleave', this.resetHandler);	
+		}
+		
+		var tol = this.getTolerance(me);
+		var x = (point != null) ? point.x : me.getGraphX();
+		var y = (point != null) ? point.y : me.getGraphY();
+		var grid = new mxRectangle(x - tol, y - tol, 2 * tol, 2 * tol);
+		var mouse = new mxRectangle(me.getGraphX() - tol, me.getGraphY() - tol, 2 * tol, 2 * tol);
+		var state = this.graph.view.getState(this.getCellForEvent(me, point));
+
+		// Keeps focus icons visible while over vertex bounds and no other cell under mouse or shift is pressed
+		if (!this.isKeepFocusEvent(me) && (this.currentFocusArea == null || this.currentFocus == null ||
+			(state != null) || !this.graph.getModel().isVertex(this.currentFocus.cell) ||
+			!mxUtils.intersects(this.currentFocusArea, mouse)) && (state != this.currentFocus))
+		{
+			this.currentFocusArea = null;
+			this.currentFocus = null;
+			this.setFocus(me, state, source);
+		}
+
+		this.currentConstraint = null;
+		this.currentPoint = null;
+		var minDistSq = null;
+		
+		if (this.focusIcons != null && this.constraints != null &&
+			(state == null || this.currentFocus == state))
+		{
+			var cx = mouse.getCenterX();
+			var cy = mouse.getCenterY();
+			
+			for (var i = 0; i < this.focusIcons.length; i++)
+			{
+				var dx = cx - this.focusIcons[i].bounds.getCenterX();
+				var dy = cy - this.focusIcons[i].bounds.getCenterY();
+				var tmp = dx * dx + dy * dy;
+				
+				if ((this.intersects(this.focusIcons[i], mouse, source, existingEdge) || (point != null &&
+					this.intersects(this.focusIcons[i], grid, source, existingEdge))) &&
+					(minDistSq == null || tmp < minDistSq))
+				{
+					this.currentConstraint = this.constraints[i];
+					this.currentPoint = this.focusPoints[i];
+					minDistSq = tmp;
+					
+					var tmp = this.focusIcons[i].bounds.clone();
+					tmp.grow(mxConstants.HIGHLIGHT_SIZE);
+					
+					if (mxClient.IS_IE)
+					{
+						tmp.grow(1);
+						tmp.width -= 1;
+						tmp.height -= 1;
+					}
+					
+					if (this.focusHighlight == null)
+					{
+						var hl = this.createHighlightShape();
+						hl.dialect = (this.graph.dialect == mxConstants.DIALECT_SVG) ?
+								mxConstants.DIALECT_SVG : mxConstants.DIALECT_VML;
+						hl.pointerEvents = false;
+
+						hl.init(this.graph.getView().getOverlayPane());
+						this.focusHighlight = hl;
+						
+						var getState = mxUtils.bind(this, function()
+						{
+							return (this.currentFocus != null) ? this.currentFocus : state;
+						});
+	
+						mxEvent.redirectMouseEvents(hl.node, this.graph, getState);
+					}
+
+					this.focusHighlight.bounds = tmp;
+					this.focusHighlight.redraw();
+				}
+			}
+		}
+		
+		if (this.currentConstraint == null)
+		{
+			this.destroyFocusHighlight();
+		}
+	}
+	else
+	{
+		this.currentConstraint = null;
+		this.currentFocus = null;
+		this.currentPoint = null;
+	}
+};
+
+/**
+ * Function: redraw
+ * 
+ * Transfers the focus to the given state as a source or target terminal. If
+ * the handler is not enabled then the outline is painted, but the constraints
+ * are ignored.
+ */
+mxConstraintHandler.prototype.redraw = function()
+{
+	if (this.currentFocus != null && this.constraints != null && this.focusIcons != null)
+	{
+		var state = this.graph.view.getState(this.currentFocus.cell);
+		this.currentFocus = state;
+		this.currentFocusArea = new mxRectangle(state.x, state.y, state.width, state.height);
+		
+		for (var i = 0; i < this.constraints.length; i++)
+		{
+			var cp = this.graph.getConnectionPoint(state, this.constraints[i]);
+			var img = this.getImageForConstraint(state, this.constraints[i], cp);
+
+			var bounds = new mxRectangle(Math.round(cp.x - img.width / 2),
+				Math.round(cp.y - img.height / 2), img.width, img.height);
+			this.focusIcons[i].bounds = bounds;
+			this.focusIcons[i].redraw();
+			this.currentFocusArea.add(this.focusIcons[i].bounds);
+			this.focusPoints[i] = cp;
+		}
+	}	
+};
+
+/**
+ * Function: setFocus
+ * 
+ * Transfers the focus to the given state as a source or target terminal. If
+ * the handler is not enabled then the outline is painted, but the constraints
+ * are ignored.
+ */
+mxConstraintHandler.prototype.setFocus = function(me, state, source)
+{
+	this.constraints = (state != null && !this.isStateIgnored(state, source) &&
+		this.graph.isCellConnectable(state.cell)) ? ((this.isEnabled()) ?
+		(this.graph.getAllConnectionConstraints(state, source) || []) : []) : null;
+
+	// Only uses cells which have constraints
+	if (this.constraints != null)
+	{
+		this.currentFocus = state;
+		this.currentFocusArea = new mxRectangle(state.x, state.y, state.width, state.height);
+		
+		if (this.focusIcons != null)
+		{
+			for (var i = 0; i < this.focusIcons.length; i++)
+			{
+				this.focusIcons[i].destroy();
+			}
+			
+			this.focusIcons = null;
+			this.focusPoints = null;
+		}
+		
+		this.focusPoints = [];
+		this.focusIcons = [];
+		
+		for (var i = 0; i < this.constraints.length; i++)
+		{
+			var cp = this.graph.getConnectionPoint(state, this.constraints[i]);
+			var img = this.getImageForConstraint(state, this.constraints[i], cp);
+
+			var src = img.src;
+			var bounds = new mxRectangle(Math.round(cp.x - img.width / 2),
+				Math.round(cp.y - img.height / 2), img.width, img.height);
+			var icon = new mxImageShape(bounds, src);
+			icon.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
+					mxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG;
+			icon.preserveImageAspect = false;
+			icon.init(this.graph.getView().getDecoratorPane());
+			
+			// Fixes lost event tracking for images in quirks / IE8 standards
+			if (mxClient.IS_QUIRKS || document.documentMode == 8)
+			{
+				mxEvent.addListener(icon.node, 'dragstart', function(evt)
+				{
+					mxEvent.consume(evt);
+					
+					return false;
+				});
+			}
+			
+			// Move the icon behind all other overlays
+			if (icon.node.previousSibling != null)
+			{
+				icon.node.parentNode.insertBefore(icon.node, icon.node.parentNode.firstChild);
+			}
+
+			var getState = mxUtils.bind(this, function()
+			{
+				return (this.currentFocus != null) ? this.currentFocus : state;
+			});
+			
+			icon.redraw();
+
+			mxEvent.redirectMouseEvents(icon.node, this.graph, getState);
+			this.currentFocusArea.add(icon.bounds);
+			this.focusIcons.push(icon);
+			this.focusPoints.push(cp);
+		}
+		
+		this.currentFocusArea.grow(this.getTolerance(me));
+	}
+	else
+	{
+		this.destroyIcons();
+		this.destroyFocusHighlight();
+	}
+};
+
+/**
+ * Function: createHighlightShape
+ * 
+ * Create the shape used to paint the highlight.
+ * 
+ * Returns true if the given icon intersects the given point.
+ */
+mxConstraintHandler.prototype.createHighlightShape = function()
+{
+	var hl = new mxRectangleShape(null, this.highlightColor, this.highlightColor, mxConstants.HIGHLIGHT_STROKEWIDTH);
+	hl.opacity = mxConstants.HIGHLIGHT_OPACITY;
+	
+	return hl;
+};
+
+/**
+ * Function: intersects
+ * 
+ * Returns true if the given icon intersects the given rectangle.
+ */
+mxConstraintHandler.prototype.intersects = function(icon, mouse, source, existingEdge)
+{
+	return mxUtils.intersects(icon.bounds, mouse);
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroy this handler.
+ */
+mxConstraintHandler.prototype.destroy = function()
+{
+	this.reset();
+	
+	if (this.resetHandler != null)
+	{
+		this.graph.model.removeListener(this.resetHandler);
+		this.graph.view.removeListener(this.resetHandler);
+		this.graph.removeListener(this.resetHandler);
+		this.resetHandler = null;
+	}
+	
+	if (this.mouseleaveHandler != null && this.graph.container != null)
+	{
+		mxEvent.removeListener(this.graph.container, 'mouseleave', this.mouseleaveHandler);
+		this.mouseleaveHandler = null;
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/handler/mxEdgeHandler.js b/airavata-kubernetes/web-console/src/assets/js/handler/mxEdgeHandler.js
new file mode 100644
index 0000000..8dd7761
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/handler/mxEdgeHandler.js
@@ -0,0 +1,2409 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxEdgeHandler
+ *
+ * Graph event handler that reconnects edges and modifies control points and
+ * the edge label location. Uses <mxTerminalMarker> for finding and
+ * highlighting new source and target vertices. This handler is automatically
+ * created in <mxGraph.createHandler> for each selected edge.
+ * 
+ * To enable adding/removing control points, the following code can be used:
+ * 
+ * (code)
+ * mxEdgeHandler.prototype.addEnabled = true;
+ * mxEdgeHandler.prototype.removeEnabled = true;
+ * (end)
+ * 
+ * Note: This experimental feature is not recommended for production use.
+ * 
+ * Constructor: mxEdgeHandler
+ *
+ * Constructs an edge handler for the specified <mxCellState>.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> of the cell to be handled.
+ */
+function mxEdgeHandler(state)
+{
+	if (state != null)
+	{
+		this.state = state;
+		this.init();
+		
+		// Handles escape keystrokes
+		this.escapeHandler = mxUtils.bind(this, function(sender, evt)
+		{
+			this.reset();
+		});
+		
+		this.state.view.graph.addListener(mxEvent.ESCAPE, this.escapeHandler);
+	}
+};
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxEdgeHandler.prototype.graph = null;
+
+/**
+ * Variable: state
+ * 
+ * Reference to the <mxCellState> being modified.
+ */
+mxEdgeHandler.prototype.state = null;
+
+/**
+ * Variable: marker
+ * 
+ * Holds the <mxTerminalMarker> which is used for highlighting terminals.
+ */
+mxEdgeHandler.prototype.marker = null;
+
+/**
+ * Variable: constraintHandler
+ * 
+ * Holds the <mxConstraintHandler> used for drawing and highlighting
+ * constraints.
+ */
+mxEdgeHandler.prototype.constraintHandler = null;
+
+/**
+ * Variable: error
+ * 
+ * Holds the current validation error while a connection is being changed.
+ */
+mxEdgeHandler.prototype.error = null;
+
+/**
+ * Variable: shape
+ * 
+ * Holds the <mxShape> that represents the preview edge.
+ */
+mxEdgeHandler.prototype.shape = null;
+
+/**
+ * Variable: bends
+ * 
+ * Holds the <mxShapes> that represent the points.
+ */
+mxEdgeHandler.prototype.bends = null;
+
+/**
+ * Variable: labelShape
+ * 
+ * Holds the <mxShape> that represents the label position.
+ */
+mxEdgeHandler.prototype.labelShape = null;
+
+/**
+ * Variable: cloneEnabled
+ * 
+ * Specifies if cloning by control-drag is enabled. Default is true.
+ */
+mxEdgeHandler.prototype.cloneEnabled = true;
+
+/**
+ * Variable: addEnabled
+ * 
+ * Specifies if adding bends by shift-click is enabled. Default is false.
+ * Note: This experimental feature is not recommended for production use.
+ */
+mxEdgeHandler.prototype.addEnabled = false;
+
+/**
+ * Variable: removeEnabled
+ * 
+ * Specifies if removing bends by shift-click is enabled. Default is false.
+ * Note: This experimental feature is not recommended for production use.
+ */
+mxEdgeHandler.prototype.removeEnabled = false;
+
+/**
+ * Variable: dblClickRemoveEnabled
+ * 
+ * Specifies if removing bends by double click is enabled. Default is false.
+ */
+mxEdgeHandler.prototype.dblClickRemoveEnabled = false;
+
+/**
+ * Variable: mergeRemoveEnabled
+ * 
+ * Specifies if removing bends by dropping them on other bends is enabled.
+ * Default is false.
+ */
+mxEdgeHandler.prototype.mergeRemoveEnabled = false;
+
+/**
+ * Variable: straightRemoveEnabled
+ * 
+ * Specifies if removing bends by creating straight segments should be enabled.
+ * If enabled, this can be overridden by holding down the alt key while moving.
+ * Default is false.
+ */
+mxEdgeHandler.prototype.straightRemoveEnabled = false;
+
+/**
+ * Variable: virtualBendsEnabled
+ * 
+ * Specifies if virtual bends should be added in the center of each
+ * segments. These bends can then be used to add new waypoints.
+ * Default is false.
+ */
+mxEdgeHandler.prototype.virtualBendsEnabled = false;
+
+/**
+ * Variable: virtualBendOpacity
+ * 
+ * Opacity to be used for virtual bends (see <virtualBendsEnabled>).
+ * Default is 20.
+ */
+mxEdgeHandler.prototype.virtualBendOpacity = 20;
+
+/**
+ * Variable: parentHighlightEnabled
+ * 
+ * Specifies if the parent should be highlighted if a child cell is selected.
+ * Default is false.
+ */
+mxEdgeHandler.prototype.parentHighlightEnabled = false;
+
+/**
+ * Variable: preferHtml
+ * 
+ * Specifies if bends should be added to the graph container. This is updated
+ * in <init> based on whether the edge or one of its terminals has an HTML
+ * label in the container.
+ */
+mxEdgeHandler.prototype.preferHtml = false;
+
+/**
+ * Variable: allowHandleBoundsCheck
+ * 
+ * Specifies if the bounds of handles should be used for hit-detection in IE
+ * Default is true.
+ */
+mxEdgeHandler.prototype.allowHandleBoundsCheck = true;
+
+/**
+ * Variable: snapToTerminals
+ * 
+ * Specifies if waypoints should snap to the routing centers of terminals.
+ * Default is false.
+ */
+mxEdgeHandler.prototype.snapToTerminals = false;
+
+/**
+ * Variable: handleImage
+ * 
+ * Optional <mxImage> to be used as handles. Default is null.
+ */
+mxEdgeHandler.prototype.handleImage = null;
+
+/**
+ * Variable: tolerance
+ * 
+ * Optional tolerance for hit-detection in <getHandleForEvent>. Default is 0.
+ */
+mxEdgeHandler.prototype.tolerance = 0;
+
+/**
+ * Variable: outlineConnect
+ * 
+ * Specifies if connections to the outline of a highlighted target should be
+ * enabled. This will allow to place the connection point along the outline of
+ * the highlighted target. Default is false.
+ */
+mxEdgeHandler.prototype.outlineConnect = false;
+
+/**
+ * Variable: manageLabelHandle
+ * 
+ * Specifies if the label handle should be moved if it intersects with another
+ * handle. Uses <checkLabelHandle> for checking and moving. Default is false.
+ */
+mxEdgeHandler.prototype.manageLabelHandle = false;
+
+/**
+ * Function: init
+ * 
+ * Initializes the shapes required for this edge handler.
+ */
+mxEdgeHandler.prototype.init = function()
+{
+	this.graph = this.state.view.graph;
+	this.marker = this.createMarker();
+	this.constraintHandler = new mxConstraintHandler(this.graph);
+	
+	// Clones the original points from the cell
+	// and makes sure at least one point exists
+	this.points = [];
+	
+	// Uses the absolute points of the state
+	// for the initial configuration and preview
+	this.abspoints = this.getSelectionPoints(this.state);
+	this.shape = this.createSelectionShape(this.abspoints);
+	this.shape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
+		mxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG;
+	this.shape.init(this.graph.getView().getOverlayPane());
+	this.shape.pointerEvents = false;
+	this.shape.setCursor(mxConstants.CURSOR_MOVABLE_EDGE);
+	mxEvent.redirectMouseEvents(this.shape.node, this.graph, this.state);
+
+	// Updates preferHtml
+	this.preferHtml = this.state.text != null &&
+		this.state.text.node.parentNode == this.graph.container;
+	
+	if (!this.preferHtml)
+	{
+		// Checks source terminal
+		var sourceState = this.state.getVisibleTerminalState(true);
+		
+		if (sourceState != null)
+		{
+			this.preferHtml = sourceState.text != null &&
+				sourceState.text.node.parentNode == this.graph.container;
+		}
+		
+		if (!this.preferHtml)
+		{
+			// Checks target terminal
+			var targetState = this.state.getVisibleTerminalState(false);
+			
+			if (targetState != null)
+			{
+				this.preferHtml = targetState.text != null &&
+				targetState.text.node.parentNode == this.graph.container;
+			}
+		}
+	}
+	
+	// Adds highlight for parent group
+	if (this.parentHighlightEnabled)
+	{
+		var parent = this.graph.model.getParent(this.state.cell);
+		
+		if (this.graph.model.isVertex(parent))
+		{
+			var pstate = this.graph.view.getState(parent);
+			
+			if (pstate != null)
+			{
+				this.parentHighlight = this.createParentHighlightShape(pstate);
+				// VML dialect required here for event transparency in IE
+				this.parentHighlight.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+				this.parentHighlight.pointerEvents = false;
+				this.parentHighlight.rotation = Number(pstate.style[mxConstants.STYLE_ROTATION] || '0');
+				this.parentHighlight.init(this.graph.getView().getOverlayPane());
+			}
+		}
+	}
+	
+	// Creates bends for the non-routed absolute points
+	// or bends that don't correspond to points
+	if (this.graph.getSelectionCount() < mxGraphHandler.prototype.maxCells ||
+		mxGraphHandler.prototype.maxCells <= 0)
+	{
+		this.bends = this.createBends();
+
+		if (this.isVirtualBendsEnabled())
+		{
+			this.virtualBends = this.createVirtualBends();
+		}
+	}
+
+	// Adds a rectangular handle for the label position
+	this.label = new mxPoint(this.state.absoluteOffset.x, this.state.absoluteOffset.y);
+	this.labelShape = this.createLabelHandleShape();
+	this.initBend(this.labelShape);
+	this.labelShape.setCursor(mxConstants.CURSOR_LABEL_HANDLE);
+	
+	this.customHandles = this.createCustomHandles();
+	
+	this.redraw();
+};
+
+/**
+ * Function: createCustomHandles
+ * 
+ * Returns an array of custom handles. This implementation returns null.
+ */
+mxEdgeHandler.prototype.createCustomHandles = function()
+{
+	return null;
+};
+
+/**
+ * Function: isVirtualBendsEnabled
+ * 
+ * Returns true if virtual bends should be added. This returns true if
+ * <virtualBendsEnabled> is true and the current style allows and
+ * renders custom waypoints.
+ */
+mxEdgeHandler.prototype.isVirtualBendsEnabled = function(evt)
+{
+	return this.virtualBendsEnabled && (this.state.style[mxConstants.STYLE_EDGE] == null ||
+			this.state.style[mxConstants.STYLE_EDGE] == mxConstants.NONE ||
+			this.state.style[mxConstants.STYLE_NOEDGESTYLE] == 1)  &&
+			mxUtils.getValue(this.state.style, mxConstants.STYLE_SHAPE, null) != 'arrow';
+};
+
+/**
+ * Function: isAddPointEvent
+ * 
+ * Returns true if the given event is a trigger to add a new point. This
+ * implementation returns true if shift is pressed.
+ */
+mxEdgeHandler.prototype.isAddPointEvent = function(evt)
+{
+	return mxEvent.isShiftDown(evt);
+};
+
+/**
+ * Function: isRemovePointEvent
+ * 
+ * Returns true if the given event is a trigger to remove a point. This
+ * implementation returns true if shift is pressed.
+ */
+mxEdgeHandler.prototype.isRemovePointEvent = function(evt)
+{
+	return mxEvent.isShiftDown(evt);
+};
+
+/**
+ * Function: getSelectionPoints
+ * 
+ * Returns the list of points that defines the selection stroke.
+ */
+mxEdgeHandler.prototype.getSelectionPoints = function(state)
+{
+	return state.absolutePoints;
+};
+
+/**
+ * Function: createSelectionShape
+ * 
+ * Creates the shape used to draw the selection border.
+ */
+mxEdgeHandler.prototype.createParentHighlightShape = function(bounds)
+{
+	var shape = new mxRectangleShape(bounds, null, this.getSelectionColor());
+	shape.strokewidth = this.getSelectionStrokeWidth();
+	shape.isDashed = this.isSelectionDashed();
+	
+	return shape;
+};
+
+/**
+ * Function: createSelectionShape
+ * 
+ * Creates the shape used to draw the selection border.
+ */
+mxEdgeHandler.prototype.createSelectionShape = function(points)
+{
+	var shape = new this.state.shape.constructor();
+	shape.outline = true;
+	shape.apply(this.state);
+	
+	shape.isDashed = this.isSelectionDashed();
+	shape.stroke = this.getSelectionColor();
+	shape.isShadow = false;
+	
+	return shape;
+};
+
+/**
+ * Function: getSelectionColor
+ * 
+ * Returns <mxConstants.EDGE_SELECTION_COLOR>.
+ */
+mxEdgeHandler.prototype.getSelectionColor = function()
+{
+	return mxConstants.EDGE_SELECTION_COLOR;
+};
+
+/**
+ * Function: getSelectionStrokeWidth
+ * 
+ * Returns <mxConstants.EDGE_SELECTION_STROKEWIDTH>.
+ */
+mxEdgeHandler.prototype.getSelectionStrokeWidth = function()
+{
+	return mxConstants.EDGE_SELECTION_STROKEWIDTH;
+};
+
+/**
+ * Function: isSelectionDashed
+ * 
+ * Returns <mxConstants.EDGE_SELECTION_DASHED>.
+ */
+mxEdgeHandler.prototype.isSelectionDashed = function()
+{
+	return mxConstants.EDGE_SELECTION_DASHED;
+};
+
+/**
+ * Function: isConnectableCell
+ * 
+ * Returns true if the given cell is connectable. This is a hook to
+ * disable floating connections. This implementation returns true.
+ */
+mxEdgeHandler.prototype.isConnectableCell = function(cell)
+{
+	return true;
+};
+
+/**
+ * Function: getCellAt
+ * 
+ * Creates and returns the <mxCellMarker> used in <marker>.
+ */
+mxEdgeHandler.prototype.getCellAt = function(x, y)
+{
+	return (!this.outlineConnect) ? this.graph.getCellAt(x, y) : null;
+};
+
+/**
+ * Function: createMarker
+ * 
+ * Creates and returns the <mxCellMarker> used in <marker>.
+ */
+mxEdgeHandler.prototype.createMarker = function()
+{
+	var marker = new mxCellMarker(this.graph);
+	var self = this; // closure
+
+	// Only returns edges if they are connectable and never returns
+	// the edge that is currently being modified
+	marker.getCell = function(me)
+	{
+		var cell = mxCellMarker.prototype.getCell.apply(this, arguments);
+
+		// Checks for cell at preview point (with grid)
+		if ((cell == self.state.cell || cell == null) && self.currentPoint != null)
+		{
+			cell = self.graph.getCellAt(self.currentPoint.x, self.currentPoint.y);
+		}
+		
+		// Uses connectable parent vertex if one exists
+		if (cell != null && !this.graph.isCellConnectable(cell))
+		{
+			var parent = this.graph.getModel().getParent(cell);
+			
+			if (this.graph.getModel().isVertex(parent) && this.graph.isCellConnectable(parent))
+			{
+				cell = parent;
+			}
+		}
+		
+		var model = self.graph.getModel();
+		
+		if ((this.graph.isSwimlane(cell) && self.currentPoint != null &&
+			this.graph.hitsSwimlaneContent(cell, self.currentPoint.x, self.currentPoint.y)) ||
+			(!self.isConnectableCell(cell)) || (cell == self.state.cell ||
+			(cell != null && !self.graph.connectableEdges && model.isEdge(cell))) ||
+			model.isAncestor(self.state.cell, cell))
+		{
+			cell = null;
+		}
+		
+		if (!this.graph.isCellConnectable(cell))
+		{
+			cell = null;
+		}
+		
+		return cell;
+	};
+
+	// Sets the highlight color according to validateConnection
+	marker.isValidState = function(state)
+	{
+		var model = self.graph.getModel();
+		var other = self.graph.view.getTerminalPort(state,
+			self.graph.view.getState(model.getTerminal(self.state.cell,
+			!self.isSource)), !self.isSource);
+		var otherCell = (other != null) ? other.cell : null;
+		var source = (self.isSource) ? state.cell : otherCell;
+		var target = (self.isSource) ? otherCell : state.cell;
+		
+		// Updates the error message of the handler
+		self.error = self.validateConnection(source, target);
+
+		return self.error == null;
+	};
+	
+	return marker;
+};
+
+/**
+ * Function: validateConnection
+ * 
+ * Returns the error message or an empty string if the connection for the
+ * given source, target pair is not valid. Otherwise it returns null. This
+ * implementation uses <mxGraph.getEdgeValidationError>.
+ * 
+ * Parameters:
+ * 
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ */
+mxEdgeHandler.prototype.validateConnection = function(source, target)
+{
+	return this.graph.getEdgeValidationError(this.state.cell, source, target);
+};
+
+/**
+ * Function: createBends
+ * 
+ * Creates and returns the bends used for modifying the edge. This is
+ * typically an array of <mxRectangleShapes>.
+ */
+ mxEdgeHandler.prototype.createBends = function()
+ {
+	var cell = this.state.cell;
+	var bends = [];
+
+	for (var i = 0; i < this.abspoints.length; i++)
+	{
+		if (this.isHandleVisible(i))
+		{
+			var source = i == 0;
+			var target = i == this.abspoints.length - 1;
+			var terminal = source || target;
+
+			if (terminal || this.graph.isCellBendable(cell))
+			{
+				(mxUtils.bind(this, function(index)
+				{
+					var bend = this.createHandleShape(index);
+					this.initBend(bend, mxUtils.bind(this, mxUtils.bind(this, function()
+					{
+						if (this.dblClickRemoveEnabled)
+						{
+							this.removePoint(this.state, index);
+						}
+					})));
+	
+					if (this.isHandleEnabled(i))
+					{
+						bend.setCursor((terminal) ? mxConstants.CURSOR_TERMINAL_HANDLE : mxConstants.CURSOR_BEND_HANDLE);
+					}
+					
+					bends.push(bend);
+				
+					if (!terminal)
+					{
+						this.points.push(new mxPoint(0,0));
+						bend.node.style.visibility = 'hidden';
+					}
+				}))(i);
+			}
+		}
+	}
+
+	return bends;
+};
+
+/**
+ * Function: createVirtualBends
+ * 
+ * Creates and returns the bends used for modifying the edge. This is
+ * typically an array of <mxRectangleShapes>.
+ */
+ mxEdgeHandler.prototype.createVirtualBends = function()
+ {
+	var cell = this.state.cell;
+	var last = this.abspoints[0];
+	var bends = [];
+
+	if (this.graph.isCellBendable(cell))
+	{
+		for (var i = 1; i < this.abspoints.length; i++)
+		{
+			(mxUtils.bind(this, function(bend)
+			{
+				this.initBend(bend);
+				bend.setCursor(mxConstants.CURSOR_VIRTUAL_BEND_HANDLE);
+				bends.push(bend);
+			}))(this.createHandleShape());
+		}
+	}
+
+	return bends;
+};
+
+/**
+ * Function: isHandleEnabled
+ * 
+ * Creates the shape used to display the given bend.
+ */
+mxEdgeHandler.prototype.isHandleEnabled = function(index)
+{
+	return true;
+};
+
+/**
+ * Function: isHandleVisible
+ * 
+ * Returns true if the handle at the given index is visible.
+ */
+mxEdgeHandler.prototype.isHandleVisible = function(index)
+{
+	var source = this.state.getVisibleTerminalState(true);
+	var target = this.state.getVisibleTerminalState(false);
+	var geo = this.graph.getCellGeometry(this.state.cell);
+	var edgeStyle = (geo != null) ? this.graph.view.getEdgeStyle(this.state, geo.points, source, target) : null;
+
+	return edgeStyle != mxEdgeStyle.EntityRelation || index == 0 || index == this.abspoints.length - 1;
+};
+
+/**
+ * Function: createHandleShape
+ * 
+ * Creates the shape used to display the given bend. Note that the index may be
+ * null for special cases, such as when called from
+ * <mxElbowEdgeHandler.createVirtualBend>. Only images and rectangles should be
+ * returned if support for HTML labels with not foreign objects is required.
+ * Index if null for virtual handles.
+ */
+mxEdgeHandler.prototype.createHandleShape = function(index)
+{
+	if (this.handleImage != null)
+	{
+		var shape = new mxImageShape(new mxRectangle(0, 0, this.handleImage.width, this.handleImage.height), this.handleImage.src);
+		
+		// Allows HTML rendering of the images
+		shape.preserveImageAspect = false;
+
+		return shape;
+	}
+	else
+	{
+		var s = mxConstants.HANDLE_SIZE;
+		
+		if (this.preferHtml)
+		{
+			s -= 1;
+		}
+		
+		return new mxRectangleShape(new mxRectangle(0, 0, s, s), mxConstants.HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR);
+	}
+};
+
+/**
+ * Function: createLabelHandleShape
+ * 
+ * Creates the shape used to display the the label handle.
+ */
+mxEdgeHandler.prototype.createLabelHandleShape = function()
+{
+	if (this.labelHandleImage != null)
+	{
+		var shape = new mxImageShape(new mxRectangle(0, 0, this.labelHandleImage.width, this.labelHandleImage.height), this.labelHandleImage.src);
+		
+		// Allows HTML rendering of the images
+		shape.preserveImageAspect = false;
+
+		return shape;
+	}
+	else
+	{
+		var s = mxConstants.LABEL_HANDLE_SIZE;
+		return new mxRectangleShape(new mxRectangle(0, 0, s, s), mxConstants.LABEL_HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR);
+	}
+};
+
+/**
+ * Function: initBend
+ * 
+ * Helper method to initialize the given bend.
+ * 
+ * Parameters:
+ * 
+ * bend - <mxShape> that represents the bend to be initialized.
+ */
+mxEdgeHandler.prototype.initBend = function(bend, dblClick)
+{
+	if (this.preferHtml)
+	{
+		bend.dialect = mxConstants.DIALECT_STRICTHTML;
+		bend.init(this.graph.container);
+	}
+	else
+	{
+		bend.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
+			mxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG;
+		bend.init(this.graph.getView().getOverlayPane());
+	}
+
+	mxEvent.redirectMouseEvents(bend.node, this.graph, this.state,
+			null, null, null, dblClick);
+	
+	// Fixes lost event tracking for images in quirks / IE8 standards
+	if (mxClient.IS_QUIRKS || document.documentMode == 8)
+	{
+		mxEvent.addListener(bend.node, 'dragstart', function(evt)
+		{
+			mxEvent.consume(evt);
+			
+			return false;
+		});
+	}
+	
+	if (mxClient.IS_TOUCH)
+	{
+		bend.node.setAttribute('pointer-events', 'none');
+	}
+};
+
+/**
+ * Function: getHandleForEvent
+ * 
+ * Returns the index of the handle for the given event.
+ */
+mxEdgeHandler.prototype.getHandleForEvent = function(me)
+{
+	// Connection highlight may consume events before they reach sizer handle
+	var tol = (!mxEvent.isMouseEvent(me.getEvent())) ? this.tolerance : 1;
+	var hit = (this.allowHandleBoundsCheck && (mxClient.IS_IE || tol > 0)) ?
+		new mxRectangle(me.getGraphX() - tol, me.getGraphY() - tol, 2 * tol, 2 * tol) : null;
+	var minDistSq = null;
+	var result = null;
+
+	function checkShape(shape)
+	{
+		if (shape != null && shape.node.style.display != 'none' && shape.node.style.visibility != 'hidden' &&
+			(me.isSource(shape) || (hit != null && mxUtils.intersects(shape.bounds, hit))))
+		{
+			var dx = me.getGraphX() - shape.bounds.getCenterX();
+			var dy = me.getGraphY() - shape.bounds.getCenterY();
+			var tmp = dx * dx + dy * dy;
+			
+			if (minDistSq == null || tmp <= minDistSq)
+			{
+				minDistSq = tmp;
+			
+				return true;
+			}
+		}
+		
+		return false;
+	}
+	
+	if (this.customHandles != null && this.isCustomHandleEvent(me))
+	{
+		// Inverse loop order to match display order
+		for (var i = this.customHandles.length - 1; i >= 0; i--)
+		{
+			if (checkShape(this.customHandles[i].shape))
+			{
+				// LATER: Return reference to active shape
+				return mxEvent.CUSTOM_HANDLE - i;
+			}
+		}
+	}
+
+	if (me.isSource(this.state.text) || checkShape(this.labelShape))
+	{
+		result = mxEvent.LABEL_HANDLE;
+	}
+	
+	if (this.bends != null)
+	{
+		for (var i = 0; i < this.bends.length; i++)
+		{
+			if (checkShape(this.bends[i]))
+			{
+				result = i;
+			}
+		}
+	}
+	
+	if (this.virtualBends != null && this.isAddVirtualBendEvent(me))
+	{
+		for (var i = 0; i < this.virtualBends.length; i++)
+		{
+			if (checkShape(this.virtualBends[i]))
+			{
+				result = mxEvent.VIRTUAL_HANDLE - i;
+			}
+		}
+	}
+
+	return result;
+};
+
+/**
+ * Function: isAddVirtualBendEvent
+ * 
+ * Returns true if the given event allows virtual bends to be added. This
+ * implementation returns true.
+ */
+mxEdgeHandler.prototype.isAddVirtualBendEvent = function(me)
+{
+	return true;
+};
+
+/**
+ * Function: isCustomHandleEvent
+ * 
+ * Returns true if the given event allows custom handles to be changed. This
+ * implementation returns true.
+ */
+mxEdgeHandler.prototype.isCustomHandleEvent = function(me)
+{
+	return true;
+};
+
+/**
+ * Function: mouseDown
+ * 
+ * Handles the event by checking if a special element of the handler
+ * was clicked, in which case the index parameter is non-null. The
+ * indices may be one of <LABEL_HANDLE> or the number of the respective
+ * control point. The source and target points are used for reconnecting
+ * the edge.
+ */
+mxEdgeHandler.prototype.mouseDown = function(sender, me)
+{
+	var handle = this.getHandleForEvent(me);
+	
+	if (this.bends != null && this.bends[handle] != null)
+	{
+		var b = this.bends[handle].bounds;
+		this.snapPoint = new mxPoint(b.getCenterX(), b.getCenterY());
+	}
+	
+	if (this.addEnabled && handle == null && this.isAddPointEvent(me.getEvent()))
+	{
+		this.addPoint(this.state, me.getEvent());
+		me.consume();
+	}
+	else if (handle != null && !me.isConsumed() && this.graph.isEnabled())
+	{
+		if (this.removeEnabled && this.isRemovePointEvent(me.getEvent()))
+		{
+			this.removePoint(this.state, handle);
+		}
+		else if (handle != mxEvent.LABEL_HANDLE || this.graph.isLabelMovable(me.getCell()))
+		{
+			if (handle <= mxEvent.VIRTUAL_HANDLE)
+			{
+				mxUtils.setOpacity(this.virtualBends[mxEvent.VIRTUAL_HANDLE - handle].node, 100);
+			}
+			
+			this.start(me.getX(), me.getY(), handle);
+		}
+		
+		me.consume();
+	}
+};
+
+/**
+ * Function: start
+ * 
+ * Starts the handling of the mouse gesture.
+ */
+mxEdgeHandler.prototype.start = function(x, y, index)
+{
+	this.startX = x;
+	this.startY = y;
+
+	this.isSource = (this.bends == null) ? false : index == 0;
+	this.isTarget = (this.bends == null) ? false : index == this.bends.length - 1;
+	this.isLabel = index == mxEvent.LABEL_HANDLE;
+
+	if (this.isSource || this.isTarget)
+	{
+		var cell = this.state.cell;
+		var terminal = this.graph.model.getTerminal(cell, this.isSource);
+
+		if ((terminal == null && this.graph.isTerminalPointMovable(cell, this.isSource)) ||
+			(terminal != null && this.graph.isCellDisconnectable(cell, terminal, this.isSource)))
+		{
+			this.index = index;
+		}
+	}
+	else
+	{
+		this.index = index;
+	}
+	
+	// Hides other custom handles
+	if (this.index <= mxEvent.CUSTOM_HANDLE && this.index > mxEvent.VIRTUAL_HANDLE)
+	{
+		if (this.customHandles != null)
+		{
+			for (var i = 0; i < this.customHandles.length; i++)
+			{
+				if (i != mxEvent.CUSTOM_HANDLE - this.index)
+				{
+					this.customHandles[i].setVisible(false);
+				}
+			}
+		}
+	}
+};
+
+/**
+ * Function: clonePreviewState
+ * 
+ * Returns a clone of the current preview state for the given point and terminal.
+ */
+mxEdgeHandler.prototype.clonePreviewState = function(point, terminal)
+{
+	return this.state.clone();
+};
+
+/**
+ * Function: getSnapToTerminalTolerance
+ * 
+ * Returns the tolerance for the guides. Default value is
+ * gridSize * scale / 2.
+ */
+mxEdgeHandler.prototype.getSnapToTerminalTolerance = function()
+{
+	return this.graph.gridSize * this.graph.view.scale / 2;
+};
+
+/**
+ * Function: updateHint
+ * 
+ * Hook for subclassers do show details while the handler is active.
+ */
+mxEdgeHandler.prototype.updateHint = function(me, point) { };
+
+/**
+ * Function: removeHint
+ * 
+ * Hooks for subclassers to hide details when the handler gets inactive.
+ */
+mxEdgeHandler.prototype.removeHint = function() { };
+
+/**
+ * Function: roundLength
+ * 
+ * Hook for rounding the unscaled width or height. This uses Math.round.
+ */
+mxEdgeHandler.prototype.roundLength = function(length)
+{
+	return Math.round(length);
+};
+
+/**
+ * Function: isSnapToTerminalsEvent
+ * 
+ * Returns true if <snapToTerminals> is true and if alt is not pressed.
+ */
+mxEdgeHandler.prototype.isSnapToTerminalsEvent = function(me)
+{
+	return this.snapToTerminals && !mxEvent.isAltDown(me.getEvent());
+};
+
+/**
+ * Function: getPointForEvent
+ * 
+ * Returns the point for the given event.
+ */
+mxEdgeHandler.prototype.getPointForEvent = function(me)
+{
+	var view = this.graph.getView();
+	var scale = view.scale;
+	var point = new mxPoint(this.roundLength(me.getGraphX() / scale) * scale,
+		this.roundLength(me.getGraphY() / scale) * scale);
+	
+	var tt = this.getSnapToTerminalTolerance();
+	var overrideX = false;
+	var overrideY = false;		
+	
+	if (tt > 0 && this.isSnapToTerminalsEvent(me))
+	{
+		function snapToPoint(pt)
+		{
+			if (pt != null)
+			{
+				var x = pt.x;
+
+				if (Math.abs(point.x - x) < tt)
+				{
+					point.x = x;
+					overrideX = true;
+				}
+				
+				var y = pt.y;
+
+				if (Math.abs(point.y - y) < tt)
+				{
+					point.y = y;
+					overrideY = true;
+				}
+			}
+		}
+		
+		// Temporary function
+		function snapToTerminal(terminal)
+		{
+			if (terminal != null)
+			{
+				snapToPoint.call(this, new mxPoint(view.getRoutingCenterX(terminal),
+						view.getRoutingCenterY(terminal)));
+			}
+		};
+
+		snapToTerminal.call(this, this.state.getVisibleTerminalState(true));
+		snapToTerminal.call(this, this.state.getVisibleTerminalState(false));
+
+		if (this.state.absolutePoints != null)
+		{
+			for (var i = 0; i < this.state.absolutePoints.length; i++)
+			{
+				snapToPoint.call(this, this.state.absolutePoints[i]);
+			}
+		}
+	}
+
+	if (this.graph.isGridEnabledEvent(me.getEvent()))
+	{
+		var tr = view.translate;
+		
+		if (!overrideX)
+		{
+			point.x = (this.graph.snap(point.x / scale - tr.x) + tr.x) * scale;
+		}
+		
+		if (!overrideY)
+		{
+			point.y = (this.graph.snap(point.y / scale - tr.y) + tr.y) * scale;
+		}
+	}
+	
+	return point;
+};
+
+/**
+ * Function: getPreviewTerminalState
+ * 
+ * Updates the given preview state taking into account the state of the constraint handler.
+ */
+mxEdgeHandler.prototype.getPreviewTerminalState = function(me)
+{
+	this.constraintHandler.update(me, this.isSource, true, me.isSource(this.marker.highlight.shape) ? null : this.currentPoint);
+	
+	if (this.constraintHandler.currentFocus != null && this.constraintHandler.currentConstraint != null)
+	{
+		// Handles special case where grid is large and connection point is at actual point in which
+		// case the outline is not followed as long as we're < gridSize / 2 away from that point
+		if (this.marker.highlight != null && this.marker.highlight.state != null &&
+			this.marker.highlight.state.cell == this.constraintHandler.currentFocus.cell)
+		{
+			// Direct repaint needed if cell already highlighted
+			if (this.marker.highlight.shape.stroke != 'transparent')
+			{
+				this.marker.highlight.shape.stroke = 'transparent';
+				this.marker.highlight.repaint();
+			}
+		}
+		else
+		{
+			this.marker.markCell(this.constraintHandler.currentFocus.cell, 'transparent');
+		}
+		
+		var model = this.graph.getModel();
+		var other = this.graph.view.getTerminalPort(this.state,
+				this.graph.view.getState(model.getTerminal(this.state.cell,
+			!this.isSource)), !this.isSource);
+		var otherCell = (other != null) ? other.cell : null;
+		var source = (this.isSource) ? this.constraintHandler.currentFocus.cell : otherCell;
+		var target = (this.isSource) ? otherCell : this.constraintHandler.currentFocus.cell;
+		
+		// Updates the error message of the handler
+		this.error = this.validateConnection(source, target);
+		var result = null;
+		
+		if (this.error == null)
+		{
+			result = this.constraintHandler.currentFocus;
+		}
+		else
+		{
+			this.constraintHandler.reset();
+		}
+		
+		return result;
+	}
+	else if (!this.graph.isIgnoreTerminalEvent(me.getEvent()))
+	{
+		this.marker.process(me);
+
+		return this.marker.getValidState();
+	}
+	else
+	{
+		this.marker.reset();
+		
+		return null;
+	}
+};
+
+/**
+ * Function: getPreviewPoints
+ * 
+ * Updates the given preview state taking into account the state of the constraint handler.
+ * 
+ * Parameters:
+ * 
+ * pt - <mxPoint> that contains the current pointer position.
+ * me - Optional <mxMouseEvent> that contains the current event.
+ */
+mxEdgeHandler.prototype.getPreviewPoints = function(pt, me)
+{
+	var geometry = this.graph.getCellGeometry(this.state.cell);
+	var points = (geometry.points != null) ? geometry.points.slice() : null;
+	var point = new mxPoint(pt.x, pt.y);
+	var result = null;
+	
+	if (!this.isSource && !this.isTarget)
+	{
+		this.convertPoint(point, false);
+		
+		if (points == null)
+		{
+			points = [point];
+		}
+		else
+		{
+			// Adds point from virtual bend
+			if (this.index <= mxEvent.VIRTUAL_HANDLE)
+			{
+				points.splice(mxEvent.VIRTUAL_HANDLE - this.index, 0, point);
+			}
+
+			// Removes point if dragged on terminal point
+			if (!this.isSource && !this.isTarget)
+			{
+				for (var i = 0; i < this.bends.length; i++)
+				{
+					if (i != this.index)
+					{
+						var bend = this.bends[i];
+						
+						if (bend != null && mxUtils.contains(bend.bounds, pt.x, pt.y))
+						{
+							if (this.index <= mxEvent.VIRTUAL_HANDLE)
+							{
+								points.splice(mxEvent.VIRTUAL_HANDLE - this.index, 1);
+							}
+							else
+							{
+								points.splice(this.index - 1, 1);
+							}
+							
+							result = points;
+						}
+					}
+				}
+				
+				// Removes point if user tries to straighten a segment
+				if (result == null && this.straightRemoveEnabled && (me == null || !mxEvent.isAltDown(me.getEvent())))
+				{
+					var tol = this.graph.tolerance * this.graph.tolerance;
+					var abs = this.state.absolutePoints.slice();
+					abs[this.index] = pt;
+					
+					// Handes special case where removing waypoint affects tolerance (flickering)
+					var src = this.state.getVisibleTerminalState(true);
+					
+					if (src != null)
+					{
+						var c = this.graph.getConnectionConstraint(this.state, src, true);
+						
+						// Checks if point is not fixed
+						if (c == null || this.graph.getConnectionPoint(src, c) == null)
+						{
+							abs[0] = new mxPoint(src.view.getRoutingCenterX(src), src.view.getRoutingCenterY(src));
+						}
+					}
+					
+					var trg = this.state.getVisibleTerminalState(false);
+					
+					if (trg != null)
+					{
+						var c = this.graph.getConnectionConstraint(this.state, trg, false);
+						
+						// Checks if point is not fixed
+						if (c == null || this.graph.getConnectionPoint(trg, c) == null)
+						{
+							abs[abs.length - 1] = new mxPoint(trg.view.getRoutingCenterX(trg), trg.view.getRoutingCenterY(trg));
+						}
+					}
+
+					function checkRemove(idx, tmp)
+					{
+						if (idx > 0 && idx < abs.length - 1 &&
+							mxUtils.ptSegDistSq(abs[idx - 1].x, abs[idx - 1].y,
+								abs[idx + 1].x, abs[idx + 1].y, tmp.x, tmp.y) < tol)
+						{
+							points.splice(idx - 1, 1);
+							result = points;
+						}
+					};
+					
+					// LATER: Check if other points can be removed if a segment is made straight
+					checkRemove(this.index, pt);
+				}
+			}
+			
+			// Updates existing point
+			if (result == null && this.index > mxEvent.VIRTUAL_HANDLE)
+			{
+				points[this.index - 1] = point;
+			}
+		}
+	}
+	else if (this.graph.resetEdgesOnConnect)
+	{
+		points = null;
+	}
+	
+	return (result != null) ? result : points;
+};
+
+/**
+ * Function: isOutlineConnectEvent
+ * 
+ * Returns true if <outlineConnect> is true and the source of the event is the outline shape
+ * or shift is pressed.
+ */
+mxEdgeHandler.prototype.isOutlineConnectEvent = function(me)
+{
+	var offset = mxUtils.getOffset(this.graph.container);
+	var evt = me.getEvent();
+	
+	var clientX = mxEvent.getClientX(evt);
+	var clientY = mxEvent.getClientY(evt);
+	
+	var doc = document.documentElement;
+	var left = (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0);
+	var top = (window.pageYOffset || doc.scrollTop)  - (doc.clientTop || 0);
+	
+	var gridX = this.currentPoint.x - this.graph.container.scrollLeft + offset.x - left;
+	var gridY = this.currentPoint.y - this.graph.container.scrollTop + offset.y - top;
+
+	return this.outlineConnect && !mxEvent.isShiftDown(me.getEvent()) &&
+		(me.isSource(this.marker.highlight.shape) ||
+		(mxEvent.isAltDown(me.getEvent()) && me.getState() != null) ||
+		this.marker.highlight.isHighlightAt(clientX, clientY) ||
+		((gridX != clientX || gridY != clientY) && me.getState() == null &&
+		this.marker.highlight.isHighlightAt(gridX, gridY)));
+};
+
+/**
+ * Function: updatePreviewState
+ * 
+ * Updates the given preview state taking into account the state of the constraint handler.
+ */
+mxEdgeHandler.prototype.updatePreviewState = function(edge, point, terminalState, me, outline)
+{
+	// Computes the points for the edge style and terminals
+	var sourceState = (this.isSource) ? terminalState : this.state.getVisibleTerminalState(true);
+	var targetState = (this.isTarget) ? terminalState : this.state.getVisibleTerminalState(false);
+	
+	var sourceConstraint = this.graph.getConnectionConstraint(edge, sourceState, true);
+	var targetConstraint = this.graph.getConnectionConstraint(edge, targetState, false);
+
+	var constraint = this.constraintHandler.currentConstraint;
+
+	if (constraint == null && outline)
+	{
+		if (terminalState != null)
+		{
+			// Handles special case where mouse is on outline away from actual end point
+			// in which case the grid is ignored and mouse point is used instead
+			if (me.isSource(this.marker.highlight.shape))
+			{
+				point = new mxPoint(me.getGraphX(), me.getGraphY());
+			}
+			
+			constraint = this.graph.getOutlineConstraint(point, terminalState, me);
+			this.constraintHandler.setFocus(me, terminalState, this.isSource);
+			this.constraintHandler.currentConstraint = constraint;
+			this.constraintHandler.currentPoint = point;
+		}
+		else
+		{
+			constraint = new mxConnectionConstraint();
+		}
+	}
+	
+	if (this.outlineConnect && this.marker.highlight != null && this.marker.highlight.shape != null)
+	{
+		var s = this.graph.view.scale;
+		
+		if (this.constraintHandler.currentConstraint != null &&
+			this.constraintHandler.currentFocus != null)
+		{
+			this.marker.highlight.shape.stroke = (outline) ? mxConstants.OUTLINE_HIGHLIGHT_COLOR : 'transparent';
+			this.marker.highlight.shape.strokewidth = mxConstants.OUTLINE_HIGHLIGHT_STROKEWIDTH / s / s;
+			this.marker.highlight.repaint();
+		}
+		else if (this.marker.hasValidState())
+		{
+			this.marker.highlight.shape.stroke = (this.marker.getValidState() == me.getState()) ?
+				mxConstants.DEFAULT_VALID_COLOR : 'transparent';
+			this.marker.highlight.shape.strokewidth = mxConstants.HIGHLIGHT_STROKEWIDTH / s / s;
+			this.marker.highlight.repaint();
+		}
+	}
+	
+	if (this.isSource)
+	{
+		sourceConstraint = constraint;
+	}
+	else if (this.isTarget)
+	{
+		targetConstraint = constraint;
+	}
+	
+	if (this.isSource || this.isTarget)
+	{
+		if (constraint != null && constraint.point != null)
+		{
+			edge.style[(this.isSource) ? mxConstants.STYLE_EXIT_X : mxConstants.STYLE_ENTRY_X] = constraint.point.x;
+			edge.style[(this.isSource) ? mxConstants.STYLE_EXIT_Y : mxConstants.STYLE_ENTRY_Y] = constraint.point.y;
+		}
+		else
+		{
+			delete edge.style[(this.isSource) ? mxConstants.STYLE_EXIT_X : mxConstants.STYLE_ENTRY_X];
+			delete edge.style[(this.isSource) ? mxConstants.STYLE_EXIT_Y : mxConstants.STYLE_ENTRY_Y];
+		}
+	}
+	
+	edge.setVisibleTerminalState(sourceState, true);
+	edge.setVisibleTerminalState(targetState, false);
+	
+	if (!this.isSource || sourceState != null)
+	{
+		edge.view.updateFixedTerminalPoint(edge, sourceState, true, sourceConstraint);
+	}
+	
+	if (!this.isTarget || targetState != null)
+	{
+		edge.view.updateFixedTerminalPoint(edge, targetState, false, targetConstraint);
+	}
+	
+	if ((this.isSource || this.isTarget) && terminalState == null)
+	{
+		edge.setAbsoluteTerminalPoint(point, this.isSource);
+
+		if (this.marker.getMarkedState() == null)
+		{
+			this.error = (this.graph.allowDanglingEdges) ? null : '';
+		}
+	}
+	
+	edge.view.updatePoints(edge, this.points, sourceState, targetState);
+	edge.view.updateFloatingTerminalPoints(edge, sourceState, targetState);
+};
+
+/**
+ * Function: mouseMove
+ * 
+ * Handles the event by updating the preview.
+ */
+mxEdgeHandler.prototype.mouseMove = function(sender, me)
+{
+	if (this.index != null && this.marker != null)
+	{
+		this.currentPoint = this.getPointForEvent(me);
+		this.error = null;
+		
+		// Uses the current point from the constraint handler if available
+		if (!this.graph.isIgnoreTerminalEvent(me.getEvent()) && mxEvent.isShiftDown(me.getEvent()) && this.snapPoint != null)
+		{
+			if (Math.abs(this.snapPoint.x - this.currentPoint.x) < Math.abs(this.snapPoint.y - this.currentPoint.y))
+			{
+				this.currentPoint.x = this.snapPoint.x;
+			}
+			else
+			{
+				this.currentPoint.y = this.snapPoint.y;
+			}
+		}
+		
+		if (this.index <= mxEvent.CUSTOM_HANDLE && this.index > mxEvent.VIRTUAL_HANDLE)
+		{
+			if (this.customHandles != null)
+			{
+				this.customHandles[mxEvent.CUSTOM_HANDLE - this.index].processEvent(me);
+			}
+		}
+		else if (this.isLabel)
+		{
+			this.label.x = this.currentPoint.x;
+			this.label.y = this.currentPoint.y;
+		}
+		else
+		{
+			this.points = this.getPreviewPoints(this.currentPoint, me);
+			var terminalState = (this.isSource || this.isTarget) ? this.getPreviewTerminalState(me) : null;
+
+			if (this.constraintHandler.currentConstraint != null &&
+				this.constraintHandler.currentFocus != null &&
+				this.constraintHandler.currentPoint != null)
+			{
+				this.currentPoint = this.constraintHandler.currentPoint.clone();
+			}
+			else if (this.outlineConnect)
+			{
+				// Need to check outline before cloning terminal state
+				var outline = (this.isSource || this.isTarget) ? this.isOutlineConnectEvent(me) : false
+						
+				if (outline)
+				{
+					terminalState = this.marker.highlight.state;
+				}
+				else if (terminalState != null && terminalState != me.getState() && this.marker.highlight.shape != null)
+				{
+					this.marker.highlight.shape.stroke = 'transparent';
+					this.marker.highlight.repaint();
+					terminalState = null;
+				}
+			}
+			
+			var clone = this.clonePreviewState(this.currentPoint, (terminalState != null) ? terminalState.cell : null);
+			this.updatePreviewState(clone, this.currentPoint, terminalState, me, outline);
+
+			// Sets the color of the preview to valid or invalid, updates the
+			// points of the preview and redraws
+			var color = (this.error == null) ? this.marker.validColor : this.marker.invalidColor;
+			this.setPreviewColor(color);
+			this.abspoints = clone.absolutePoints;
+			this.active = true;
+		}
+
+		// This should go before calling isOutlineConnectEvent above. As a workaround
+		// we add an offset of gridSize to the hint to avoid problem with hit detection
+		// in highlight.isHighlightAt (which uses comonentFromPoint)
+		this.updateHint(me, this.currentPoint);
+		this.drawPreview();
+		mxEvent.consume(me.getEvent());
+		me.consume();
+	}
+	// Workaround for disabling the connect highlight when over handle
+	else if (mxClient.IS_IE && this.getHandleForEvent(me) != null)
+	{
+		me.consume(false);
+	}
+};
+
+/**
+ * Function: mouseUp
+ * 
+ * Handles the event to applying the previewed changes on the edge by
+ * using <moveLabel>, <connect> or <changePoints>.
+ */
+mxEdgeHandler.prototype.mouseUp = function(sender, me)
+{
+	// Workaround for wrong event source in Webkit
+	if (this.index != null && this.marker != null)
+	{
+		var edge = this.state.cell;
+		
+		// Ignores event if mouse has not been moved
+		if (me.getX() != this.startX || me.getY() != this.startY)
+		{
+			var clone = !this.graph.isIgnoreTerminalEvent(me.getEvent()) && this.graph.isCloneEvent(me.getEvent()) &&
+				this.cloneEnabled && this.graph.isCellsCloneable();
+			
+			// Displays the reason for not carriying out the change
+			// if there is an error message with non-zero length
+			if (this.error != null)
+			{
+				if (this.error.length > 0)
+				{
+					this.graph.validationAlert(this.error);
+				}
+			}
+			else if (this.index <= mxEvent.CUSTOM_HANDLE && this.index > mxEvent.VIRTUAL_HANDLE)
+			{
+				if (this.customHandles != null)
+				{
+					var model = this.graph.getModel();
+					
+					model.beginUpdate();
+					try
+					{
+						this.customHandles[mxEvent.CUSTOM_HANDLE - this.index].execute();
+					}
+					finally
+					{
+						model.endUpdate();
+					}
+				}
+			}
+			else if (this.isLabel)
+			{
+				this.moveLabel(this.state, this.label.x, this.label.y);
+			}
+			else if (this.isSource || this.isTarget)
+			{
+				var terminal = null;
+				
+				if (this.constraintHandler.currentConstraint != null &&
+					this.constraintHandler.currentFocus != null)
+				{
+					terminal = this.constraintHandler.currentFocus.cell;
+				}
+				
+				if (terminal == null && this.marker.hasValidState() && this.marker.highlight != null &&
+					this.marker.highlight.shape != null &&
+					this.marker.highlight.shape.stroke != 'transparent' &&
+					this.marker.highlight.shape.stroke != 'white')
+				{
+					terminal = this.marker.validState.cell;
+				}
+				
+				if (terminal != null)
+				{
+					edge = this.connect(edge, terminal, this.isSource, clone, me);
+				}
+				else if (this.graph.isAllowDanglingEdges())
+				{
+					var pt = this.abspoints[(this.isSource) ? 0 : this.abspoints.length - 1];
+					pt.x = this.roundLength(pt.x / this.graph.view.scale - this.graph.view.translate.x);
+					pt.y = this.roundLength(pt.y / this.graph.view.scale - this.graph.view.translate.y);
+
+					var pstate = this.graph.getView().getState(
+							this.graph.getModel().getParent(edge));
+							
+					if (pstate != null)
+					{
+						pt.x -= pstate.origin.x;
+						pt.y -= pstate.origin.y;
+					}
+					
+					pt.x -= this.graph.panDx / this.graph.view.scale;
+					pt.y -= this.graph.panDy / this.graph.view.scale;
+										
+					// Destroys and recreates this handler
+					edge = this.changeTerminalPoint(edge, pt, this.isSource, clone);
+				}
+			}
+			else if (this.active)
+			{
+				edge = this.changePoints(edge, this.points, clone);
+			}
+			else
+			{
+				this.graph.getView().invalidate(this.state.cell);
+				this.graph.getView().validate(this.state.cell);						
+			}
+		}
+		
+		// Resets the preview color the state of the handler if this
+		// handler has not been recreated
+		if (this.marker != null)
+		{
+			this.reset();
+
+			// Updates the selection if the edge has been cloned
+			if (edge != this.state.cell)
+			{
+				this.graph.setSelectionCell(edge);
+			}
+		}
+
+		me.consume();
+	}
+};
+
+/**
+ * Function: reset
+ * 
+ * Resets the state of this handler.
+ */
+mxEdgeHandler.prototype.reset = function()
+{
+	this.error = null;
+	this.index = null;
+	this.label = null;
+	this.points = null;
+	this.snapPoint = null;
+	this.active = false;
+	this.isLabel = false;
+	this.isSource = false;
+	this.isTarget = false;
+	
+	if (this.livePreview && this.sizers != null)
+	{
+		for (var i = 0; i < this.sizers.length; i++)
+		{
+			if (this.sizers[i] != null)
+			{
+				this.sizers[i].node.style.display = '';
+			}
+		}
+	}
+
+	if (this.marker != null)
+	{
+		this.marker.reset();
+	}
+	
+	if (this.constraintHandler != null)
+	{
+		this.constraintHandler.reset();
+	}
+	
+	if (this.customHandles != null)
+	{
+		for (var i = 0; i < this.customHandles.length; i++)
+		{
+			this.customHandles[i].reset();
+		}
+	}
+
+	this.setPreviewColor(mxConstants.EDGE_SELECTION_COLOR);
+	this.removeHint();
+	this.redraw();
+};
+
+/**
+ * Function: setPreviewColor
+ * 
+ * Sets the color of the preview to the given value.
+ */
+mxEdgeHandler.prototype.setPreviewColor = function(color)
+{
+	if (this.shape != null)
+	{
+		this.shape.stroke = color;
+	}
+};
+
+
+/**
+ * Function: convertPoint
+ * 
+ * Converts the given point in-place from screen to unscaled, untranslated
+ * graph coordinates and applies the grid. Returns the given, modified
+ * point instance.
+ * 
+ * Parameters:
+ * 
+ * point - <mxPoint> to be converted.
+ * gridEnabled - Boolean that specifies if the grid should be applied.
+ */
+mxEdgeHandler.prototype.convertPoint = function(point, gridEnabled)
+{
+	var scale = this.graph.getView().getScale();
+	var tr = this.graph.getView().getTranslate();
+		
+	if (gridEnabled)
+	{
+		point.x = this.graph.snap(point.x);
+		point.y = this.graph.snap(point.y);
+	}
+	
+	point.x = Math.round(point.x / scale - tr.x);
+	point.y = Math.round(point.y / scale - tr.y);
+
+	var pstate = this.graph.getView().getState(
+		this.graph.getModel().getParent(this.state.cell));
+
+	if (pstate != null)
+	{
+		point.x -= pstate.origin.x;
+		point.y -= pstate.origin.y;
+	}
+
+	return point;
+};
+
+/**
+ * Function: moveLabel
+ * 
+ * Changes the coordinates for the label of the given edge.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> that represents the edge.
+ * x - Integer that specifies the x-coordinate of the new location.
+ * y - Integer that specifies the y-coordinate of the new location.
+ */
+mxEdgeHandler.prototype.moveLabel = function(edgeState, x, y)
+{
+	var model = this.graph.getModel();
+	var geometry = model.getGeometry(edgeState.cell);
+	
+	if (geometry != null)
+	{
+		var scale = this.graph.getView().scale;
+		geometry = geometry.clone();
+		
+		if (geometry.relative)
+		{
+			// Resets the relative location stored inside the geometry
+			var pt = this.graph.getView().getRelativePoint(edgeState, x, y);
+			geometry.x = Math.round(pt.x * 10000) / 10000;
+			geometry.y = Math.round(pt.y);
+			
+			// Resets the offset inside the geometry to find the offset
+			// from the resulting point
+			geometry.offset = new mxPoint(0, 0);
+			var pt = this.graph.view.getPoint(edgeState, geometry);
+			geometry.offset = new mxPoint(Math.round((x - pt.x) / scale), Math.round((y - pt.y) / scale));
+		}
+		else
+		{
+			var points = edgeState.absolutePoints;
+			var p0 = points[0];
+			var pe = points[points.length - 1];
+			
+			if (p0 != null && pe != null)
+			{
+				var cx = p0.x + (pe.x - p0.x) / 2;
+				var cy = p0.y + (pe.y - p0.y) / 2;
+				
+				geometry.offset = new mxPoint(Math.round((x - cx) / scale), Math.round((y - cy) / scale));
+				geometry.x = 0;
+				geometry.y = 0;
+			}
+		}
+
+		model.setGeometry(edgeState.cell, geometry);
+	}
+};
+
+/**
+ * Function: connect
+ * 
+ * Changes the terminal or terminal point of the given edge in the graph
+ * model.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> that represents the edge to be reconnected.
+ * terminal - <mxCell> that represents the new terminal.
+ * isSource - Boolean indicating if the new terminal is the source or
+ * target terminal.
+ * isClone - Boolean indicating if the new connection should be a clone of
+ * the old edge.
+ * me - <mxMouseEvent> that contains the mouse up event.
+ */
+mxEdgeHandler.prototype.connect = function(edge, terminal, isSource, isClone, me)
+{
+	var model = this.graph.getModel();
+	var parent = model.getParent(edge);
+	
+	model.beginUpdate();
+	try
+	{
+		// Clones and adds the cell
+		if (isClone)
+		{
+			var clone = this.graph.cloneCells([edge])[0];
+			model.add(parent, clone, model.getChildCount(parent));
+			
+			var other = model.getTerminal(edge, !isSource);
+			this.graph.connectCell(clone, other, !isSource);
+			
+			edge = clone;
+		}
+
+		var constraint = this.constraintHandler.currentConstraint;
+		
+		if (constraint == null)
+		{
+			constraint = new mxConnectionConstraint();
+		}
+
+		this.graph.connectCell(edge, terminal, isSource, constraint);
+	}
+	finally
+	{
+		model.endUpdate();
+	}
+	
+	return edge;
+};
+
+/**
+ * Function: changeTerminalPoint
+ * 
+ * Changes the terminal point of the given edge.
+ */
+mxEdgeHandler.prototype.changeTerminalPoint = function(edge, point, isSource, clone)
+{
+	var model = this.graph.getModel();
+
+	model.beginUpdate();
+	try
+	{
+		if (clone)
+		{
+			var parent = model.getParent(edge);
+			var terminal = model.getTerminal(edge, !isSource);
+			edge = this.graph.cloneCells([edge])[0];
+			model.add(parent, edge, model.getChildCount(parent));
+			model.setTerminal(edge, terminal, !isSource);
+		}
+
+		var geo = model.getGeometry(edge);
+		
+		if (geo != null)
+		{
+			geo = geo.clone();
+			geo.setTerminalPoint(point, isSource);
+			model.setGeometry(edge, geo);
+			this.graph.connectCell(edge, null, isSource, new mxConnectionConstraint());
+		}
+	}
+	finally
+	{
+		model.endUpdate();
+	}
+	
+	return edge;
+};
+
+/**
+ * Function: changePoints
+ * 
+ * Changes the control points of the given edge in the graph model.
+ */
+mxEdgeHandler.prototype.changePoints = function(edge, points, clone)
+{
+	var model = this.graph.getModel();
+	model.beginUpdate();
+	try
+	{
+		if (clone)
+		{
+			var parent = model.getParent(edge);
+			var source = model.getTerminal(edge, true);
+			var target = model.getTerminal(edge, false);
+			edge = this.graph.cloneCells([edge])[0];
+			model.add(parent, edge, model.getChildCount(parent));
+			model.setTerminal(edge, source, true);
+			model.setTerminal(edge, target, false);
+		}
+		
+		var geo = model.getGeometry(edge);
+		
+		if (geo != null)
+		{
+			geo = geo.clone();
+			geo.points = points;
+			
+			model.setGeometry(edge, geo);
+		}
+	}
+	finally
+	{
+		model.endUpdate();
+	}
+	
+	return edge;
+};
+
+/**
+ * Function: addPoint
+ * 
+ * Adds a control point for the given state and event.
+ */
+mxEdgeHandler.prototype.addPoint = function(state, evt)
+{
+	var pt = mxUtils.convertPoint(this.graph.container, mxEvent.getClientX(evt),
+			mxEvent.getClientY(evt));
+	var gridEnabled = this.graph.isGridEnabledEvent(evt);
+	this.convertPoint(pt, gridEnabled);
+	this.addPointAt(state, pt.x, pt.y);
+	mxEvent.consume(evt);
+};
+
+/**
+ * Function: addPointAt
+ * 
+ * Adds a control point at the given point.
+ */
+mxEdgeHandler.prototype.addPointAt = function(state, x, y)
+{
+	var geo = this.graph.getCellGeometry(state.cell);
+	var pt = new mxPoint(x, y);
+	
+	if (geo != null)
+	{
+		geo = geo.clone();
+		var t = this.graph.view.translate;
+		var s = this.graph.view.scale;
+		var offset = new mxPoint(t.x * s, t.y * s);
+		
+		var parent = this.graph.model.getParent(this.state.cell);
+		
+		if (this.graph.model.isVertex(parent))
+		{
+			var pState = this.graph.view.getState(parent);
+			offset = new mxPoint(pState.x, pState.y);
+		}
+		
+		var index = mxUtils.findNearestSegment(state, pt.x * s + offset.x, pt.y * s + offset.y);
+
+		if (geo.points == null)
+		{
+			geo.points = [pt];
+		}
+		else
+		{
+			geo.points.splice(index, 0, pt);
+		}
+		
+		this.graph.getModel().setGeometry(state.cell, geo);
+		this.refresh();	
+		this.redraw();
+	}
+};
+
+/**
+ * Function: removePoint
+ * 
+ * Removes the control point at the given index from the given state.
+ */
+mxEdgeHandler.prototype.removePoint = function(state, index)
+{
+	if (index > 0 && index < this.abspoints.length - 1)
+	{
+		var geo = this.graph.getCellGeometry(this.state.cell);
+		
+		if (geo != null && geo.points != null)
+		{
+			geo = geo.clone();
+			geo.points.splice(index - 1, 1);
+			this.graph.getModel().setGeometry(state.cell, geo);
+			this.refresh();
+			this.redraw();
+		}
+	}
+};
+
+/**
+ * Function: getHandleFillColor
+ * 
+ * Returns the fillcolor for the handle at the given index.
+ */
+mxEdgeHandler.prototype.getHandleFillColor = function(index)
+{
+	var isSource = index == 0;
+	var cell = this.state.cell;
+	var terminal = this.graph.getModel().getTerminal(cell, isSource);
+	var color = mxConstants.HANDLE_FILLCOLOR;
+	
+	if ((terminal != null && !this.graph.isCellDisconnectable(cell, terminal, isSource)) ||
+		(terminal == null && !this.graph.isTerminalPointMovable(cell, isSource)))
+	{
+		color = mxConstants.LOCKED_HANDLE_FILLCOLOR;
+	}
+	else if (terminal != null && this.graph.isCellDisconnectable(cell, terminal, isSource))
+	{
+		color = mxConstants.CONNECT_HANDLE_FILLCOLOR;
+	}
+	
+	return color;
+};
+
+/**
+ * Function: redraw
+ * 
+ * Redraws the preview, and the bends- and label control points.
+ */
+mxEdgeHandler.prototype.redraw = function()
+{
+	this.abspoints = this.state.absolutePoints.slice();
+	this.redrawHandles();
+	
+	var g = this.graph.getModel().getGeometry(this.state.cell);
+	var pts = g.points;
+
+	if (this.bends != null && this.bends.length > 0)
+	{
+		if (pts != null)
+		{
+			if (this.points == null)
+			{
+				this.points = [];
+			}
+			
+			for (var i = 1; i < this.bends.length - 1; i++)
+			{
+				if (this.bends[i] != null && this.abspoints[i] != null)
+				{
+					this.points[i - 1] = pts[i - 1];
+				}
+			}
+		}
+	}
+
+	this.drawPreview();
+};
+
+/**
+ * Function: redrawHandles
+ * 
+ * Redraws the handles.
+ */
+mxEdgeHandler.prototype.redrawHandles = function()
+{
+	var cell = this.state.cell;
+
+	// Updates the handle for the label position
+	var b = this.labelShape.bounds;
+	this.label = new mxPoint(this.state.absoluteOffset.x, this.state.absoluteOffset.y);
+	this.labelShape.bounds = new mxRectangle(Math.round(this.label.x - b.width / 2),
+		Math.round(this.label.y - b.height / 2), b.width, b.height);
+
+	// Shows or hides the label handle depending on the label
+	var lab = this.graph.getLabel(cell);
+	this.labelShape.visible = (lab != null && lab.length > 0 && this.graph.isLabelMovable(cell));
+	
+	if (this.bends != null && this.bends.length > 0)
+	{
+		var n = this.abspoints.length - 1;
+		
+		var p0 = this.abspoints[0];
+		var x0 = p0.x;
+		var y0 = p0.y;
+		
+		b = this.bends[0].bounds;
+		this.bends[0].bounds = new mxRectangle(Math.floor(x0 - b.width / 2),
+				Math.floor(y0 - b.height / 2), b.width, b.height);
+		this.bends[0].fill = this.getHandleFillColor(0);
+		this.bends[0].redraw();
+		
+		if (this.manageLabelHandle)
+		{
+			this.checkLabelHandle(this.bends[0].bounds);
+		}
+				
+		var pe = this.abspoints[n];
+		var xn = pe.x;
+		var yn = pe.y;
+		
+		var bn = this.bends.length - 1;
+		b = this.bends[bn].bounds;
+		this.bends[bn].bounds = new mxRectangle(Math.floor(xn - b.width / 2),
+				Math.floor(yn - b.height / 2), b.width, b.height);
+		this.bends[bn].fill = this.getHandleFillColor(bn);
+		this.bends[bn].redraw();
+				
+		if (this.manageLabelHandle)
+		{
+			this.checkLabelHandle(this.bends[bn].bounds);
+		}
+		
+		this.redrawInnerBends(p0, pe);
+	}
+
+	if (this.abspoints != null && this.virtualBends != null && this.virtualBends.length > 0)
+	{
+		var last = this.abspoints[0];
+		
+		for (var i = 0; i < this.virtualBends.length; i++)
+		{
+			if (this.virtualBends[i] != null && this.abspoints[i + 1] != null)
+			{
+				var pt = this.abspoints[i + 1];
+				var b = this.virtualBends[i];
+				var x = last.x + (pt.x - last.x) / 2;
+				var y = last.y + (pt.y - last.y) / 2;
+				b.bounds = new mxRectangle(Math.floor(x - b.bounds.width / 2),
+						Math.floor(y - b.bounds.height / 2), b.bounds.width, b.bounds.height);
+				b.redraw();
+				mxUtils.setOpacity(b.node, this.virtualBendOpacity);
+				last = pt;
+				
+				if (this.manageLabelHandle)
+				{
+					this.checkLabelHandle(b.bounds);
+				}
+			}
+		}
+	}
+	
+	if (this.labelShape != null)
+	{
+		this.labelShape.redraw();
+	}
+	
+	if (this.customHandles != null)
+	{
+		for (var i = 0; i < this.customHandles.length; i++)
+		{
+			this.customHandles[i].redraw();
+		}
+	}
+};
+
+/**
+ * Function: hideHandles
+ * 
+ * Shortcut to <hideSizers>.
+ */
+mxEdgeHandler.prototype.setHandlesVisible = function(visible)
+{
+	if (this.bends != null)
+	{
+		for (var i = 0; i < this.bends.length; i++)
+		{
+			this.bends[i].node.style.display = (visible) ? '' : 'none';
+		}
+	}
+	
+	if (this.virtualBends != null)
+	{
+		for (var i = 0; i < this.virtualBends.length; i++)
+		{
+			this.virtualBends[i].node.style.display = (visible) ? '' : 'none';
+		}
+	}
+
+	if (this.labelShape != null)
+	{
+		this.labelShape.node.style.display = (visible) ? '' : 'none';
+	}
+	
+	if (this.customHandles != null)
+	{
+		for (var i = 0; i < this.customHandles.length; i++)
+		{
+			this.customHandles[i].setVisible(visible);
+		}
+	}
+};
+
+/**
+ * Function: redrawInnerBends
+ * 
+ * Updates and redraws the inner bends.
+ * 
+ * Parameters:
+ * 
+ * p0 - <mxPoint> that represents the location of the first point.
+ * pe - <mxPoint> that represents the location of the last point.
+ */
+mxEdgeHandler.prototype.redrawInnerBends = function(p0, pe)
+{
+	for (var i = 1; i < this.bends.length - 1; i++)
+	{
+		if (this.bends[i] != null)
+		{
+			if (this.abspoints[i] != null)
+			{
+				var x = this.abspoints[i].x;
+				var y = this.abspoints[i].y;
+				
+				var b = this.bends[i].bounds;
+				this.bends[i].node.style.visibility = 'visible';
+				this.bends[i].bounds = new mxRectangle(Math.round(x - b.width / 2),
+						Math.round(y - b.height / 2), b.width, b.height);
+				
+				if (this.manageLabelHandle)
+				{
+					this.checkLabelHandle(this.bends[i].bounds);
+				}
+				else if (this.handleImage == null && this.labelShape.visible && mxUtils.intersects(this.bends[i].bounds, this.labelShape.bounds))
+				{
+					w = mxConstants.HANDLE_SIZE + 3;
+					h = mxConstants.HANDLE_SIZE + 3;
+					this.bends[i].bounds = new mxRectangle(Math.round(x - w / 2), Math.round(y - h / 2), w, h);
+				}
+				
+				this.bends[i].redraw();
+			}
+			else
+			{
+				this.bends[i].destroy();
+				this.bends[i] = null;
+			}
+		}
+	}
+};
+
+/**
+ * Function: checkLabelHandle
+ * 
+ * Checks if the label handle intersects the given bounds and moves it if it
+ * intersects.
+ */
+mxEdgeHandler.prototype.checkLabelHandle = function(b)
+{
+	if (this.labelShape != null)
+	{
+		var b2 = this.labelShape.bounds;
+		
+		if (mxUtils.intersects(b, b2))
+		{
+			if (b.getCenterY() < b2.getCenterY())
+			{
+				b2.y = b.y + b.height;
+			}
+			else
+			{
+				b2.y = b.y - b2.height;
+			}
+		}
+	}
+};
+
+/**
+ * Function: drawPreview
+ * 
+ * Redraws the preview.
+ */
+mxEdgeHandler.prototype.drawPreview = function()
+{
+	if (this.isLabel)
+	{
+		var b = this.labelShape.bounds;
+		var bounds = new mxRectangle(Math.round(this.label.x - b.width / 2),
+				Math.round(this.label.y - b.height / 2), b.width, b.height);
+		this.labelShape.bounds = bounds;
+		this.labelShape.redraw();
+	}
+	else if (this.shape != null)
+	{
+		this.shape.apply(this.state);
+		this.shape.points = this.abspoints;
+		this.shape.scale = this.state.view.scale;
+		this.shape.isDashed = this.isSelectionDashed();
+		this.shape.stroke = this.getSelectionColor();
+		this.shape.strokewidth = this.getSelectionStrokeWidth() / this.shape.scale / this.shape.scale;
+		this.shape.isShadow = false;
+		this.shape.redraw();
+	}
+	
+	if (this.parentHighlight != null)
+	{
+		this.parentHighlight.redraw();
+	}
+};
+
+/**
+ * Function: refresh
+ * 
+ * Refreshes the bends of this handler.
+ */
+mxEdgeHandler.prototype.refresh = function()
+{
+	this.abspoints = this.getSelectionPoints(this.state);
+	this.points = [];
+
+	if (this.shape != null)
+	{
+		this.shape.points = this.abspoints;
+	}
+	
+	if (this.bends != null)
+	{
+		this.destroyBends(this.bends);
+		this.bends = this.createBends();
+	}
+	
+	if (this.virtualBends != null)
+	{
+		this.destroyBends(this.virtualBends);
+		this.virtualBends = this.createVirtualBends();
+	}
+	
+	if (this.customHandles != null)
+	{
+		this.destroyBends(this.customHandles);
+		this.customHandles = this.createCustomHandles();
+	}
+	
+	// Puts label node on top of bends
+	if (this.labelShape != null && this.labelShape.node != null && this.labelShape.node.parentNode != null)
+	{
+		this.labelShape.node.parentNode.appendChild(this.labelShape.node);
+	}
+};
+
+/**
+ * Function: destroyBends
+ * 
+ * Destroys all elements in <bends>.
+ */
+mxEdgeHandler.prototype.destroyBends = function(bends)
+{
+	if (bends != null)
+	{
+		for (var i = 0; i < bends.length; i++)
+		{
+			if (bends[i] != null)
+			{
+				bends[i].destroy();
+			}
+		}
+	}
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the handler and all its resources and DOM nodes. This does
+ * normally not need to be called as handlers are destroyed automatically
+ * when the corresponding cell is deselected.
+ */
+mxEdgeHandler.prototype.destroy = function()
+{
+	if (this.escapeHandler != null)
+	{
+		this.state.view.graph.removeListener(this.escapeHandler);
+		this.escapeHandler = null;
+	}
+	
+	if (this.marker != null)
+	{
+		this.marker.destroy();
+		this.marker = null;
+	}
+	
+	if (this.shape != null)
+	{
+		this.shape.destroy();
+		this.shape = null;
+	}
+	
+	if (this.parentHighlight != null)
+	{
+		this.parentHighlight.destroy();
+		this.parentHighlight = null;
+	}
+	
+	if (this.labelShape != null)
+	{
+		this.labelShape.destroy();
+		this.labelShape = null;
+	}
+
+	if (this.constraintHandler != null)
+	{
+		this.constraintHandler.destroy();
+		this.constraintHandler = null;
+	}
+	
+	this.destroyBends(this.virtualBends);
+	this.virtualBends = null;
+	
+	this.destroyBends(this.customHandles);
+	this.customHandles = null;
+
+	this.destroyBends(this.bends);
+	this.bends = null;
+	
+	this.removeHint();
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/handler/mxEdgeSegmentHandler.js b/airavata-kubernetes/web-console/src/assets/js/handler/mxEdgeSegmentHandler.js
new file mode 100644
index 0000000..513344e
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/handler/mxEdgeSegmentHandler.js
@@ -0,0 +1,401 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+function mxEdgeSegmentHandler(state)
+{
+	mxEdgeHandler.call(this, state);
+};
+
+/**
+ * Extends mxEdgeHandler.
+ */
+mxUtils.extend(mxEdgeSegmentHandler, mxElbowEdgeHandler);
+
+/**
+ * Function: getCurrentPoints
+ * 
+ * Returns the current absolute points.
+ */
+mxEdgeSegmentHandler.prototype.getCurrentPoints = function()
+{
+	var pts = this.state.absolutePoints;
+	
+	if (pts != null)
+	{
+		// Special case for straight edges where we add a virtual middle handle for moving the edge
+		if (pts.length == 2 || (pts.length == 3 && (pts[0].x == pts[1].x && pts[1].x == pts[2].x ||
+				pts[0].y == pts[1].y && pts[1].y == pts[2].y)))
+		{
+			var cx = pts[0].x + (pts[pts.length - 1].x - pts[0].x) / 2;
+			var cy = pts[0].y + (pts[pts.length - 1].y - pts[0].y) / 2;
+			
+			pts = [pts[0], new mxPoint(cx, cy), new mxPoint(cx, cy), pts[pts.length - 1]];	
+		}
+	}
+
+	return pts;
+};
+
+/**
+ * Function: getPreviewPoints
+ * 
+ * Updates the given preview state taking into account the state of the constraint handler.
+ */
+mxEdgeSegmentHandler.prototype.getPreviewPoints = function(point)
+{
+	if (this.isSource || this.isTarget)
+	{
+		return mxElbowEdgeHandler.prototype.getPreviewPoints.apply(this, arguments);
+	}
+	else
+	{
+		var pts = this.getCurrentPoints();
+		var last = this.convertPoint(pts[0].clone(), false);
+		point = this.convertPoint(point.clone(), false);
+		var result = [];
+
+		for (var i = 1; i < pts.length; i++)
+		{
+			var pt = this.convertPoint(pts[i].clone(), false);
+			
+			if (i == this.index)
+			{
+				if (Math.round(last.x - pt.x) == 0)
+		 		{
+					last.x = point.x;
+					pt.x = point.x;
+		 		}
+		 		
+				if (Math.round(last.y - pt.y) == 0)
+		 		{
+		 			last.y = point.y;
+		 			pt.y = point.y;
+		 		}
+			}
+
+			if (i < pts.length - 1)
+			{
+				result.push(pt);
+			}
+
+			last = pt;
+		}
+		
+		// Replaces single point that intersects with source or target
+		if (result.length == 1)
+		{
+			var source = this.state.getVisibleTerminalState(true);
+			var target = this.state.getVisibleTerminalState(false);
+			var scale = this.state.view.getScale();
+			var tr = this.state.view.getTranslate();
+			
+			var x = result[0].x * scale + tr.x;
+			var y = result[0].y * scale + tr.y;
+			
+			if ((source != null && mxUtils.contains(source, x, y)) ||
+				(target != null && mxUtils.contains(target, x, y)))
+			{
+				result = [point, point];
+			}
+		}
+
+		return result;
+	}
+};
+
+/**
+ * Function: updatePreviewState
+ * 
+ * Overridden to perform optimization of the edge style result.
+ */
+mxEdgeSegmentHandler.prototype.updatePreviewState = function(edge, point, terminalState, me)
+{
+	mxEdgeHandler.prototype.updatePreviewState.apply(this, arguments);
+
+	// Checks and corrects preview by running edge style again
+	if (!this.isSource && !this.isTarget)
+	{
+		point = this.convertPoint(point.clone(), false);
+		var pts = edge.absolutePoints;
+		var pt0 = pts[0];
+		var pt1 = pts[1];
+
+		var result = [];
+		
+		for (var i = 2; i < pts.length; i++)
+		{
+			var pt2 = pts[i];
+		
+			// Merges adjacent segments only if more than 2 to allow for straight edges
+			if ((Math.round(pt0.x - pt1.x) != 0 || Math.round(pt1.x - pt2.x) != 0) &&
+				(Math.round(pt0.y - pt1.y) != 0 || Math.round(pt1.y - pt2.y) != 0))
+			{
+				result.push(this.convertPoint(pt1.clone(), false));
+			}
+
+			pt0 = pt1;
+			pt1 = pt2;
+		}
+		
+		var source = this.state.getVisibleTerminalState(true);
+		var target = this.state.getVisibleTerminalState(false);
+		var rpts = this.state.absolutePoints;
+		
+		// A straight line is represented by 3 handles
+		if (result.length == 0 && (Math.round(pts[0].x - pts[pts.length - 1].x) == 0 ||
+			Math.round(pts[0].y - pts[pts.length - 1].y) == 0))
+		{
+			result = [point, point];
+		}
+		// Handles special case of transitions from straight vertical to routed
+		else if (pts.length == 5 && result.length == 2 && source != null && target != null &&
+				rpts != null && Math.round(rpts[0].x - rpts[rpts.length - 1].x) == 0)
+		{
+			var view = this.graph.getView();
+			var scale = view.getScale();
+			var tr = view.getTranslate();
+			
+			var y0 = view.getRoutingCenterY(source) / scale - tr.y;
+			
+			// Use fixed connection point y-coordinate if one exists
+			var sc = this.graph.getConnectionConstraint(edge, source, true);
+			
+			if (sc != null)
+			{
+				var pt = this.graph.getConnectionPoint(source, sc);
+				
+				if (pt != null)
+				{
+					this.convertPoint(pt, false);
+					y0 = pt.y;
+				}
+			}
+			
+			var ye = view.getRoutingCenterY(target) / scale - tr.y;
+			
+			// Use fixed connection point y-coordinate if one exists
+			var tc = this.graph.getConnectionConstraint(edge, target, false);
+			
+			if (tc)
+			{
+				var pt = this.graph.getConnectionPoint(target, tc);
+				
+				if (pt != null)
+				{
+					this.convertPoint(pt, false);
+					ye = pt.y;
+				}
+			}
+			
+			result = [new mxPoint(point.x, y0), new mxPoint(point.x, ye)];
+		}
+
+		this.points = result;
+
+		// LATER: Check if points and result are different
+		edge.view.updateFixedTerminalPoints(edge, source, target);
+		edge.view.updatePoints(edge, this.points, source, target);
+		edge.view.updateFloatingTerminalPoints(edge, source, target);
+	}
+};
+
+/**
+ * Overriden to merge edge segments.
+ */
+mxEdgeSegmentHandler.prototype.connect = function(edge, terminal, isSource, isClone, me)
+{
+	// Merges adjacent edge segments
+	var pts = this.abspoints;
+	var pt0 = pts[0];
+	var pt1 = pts[1];
+	var result = [];
+	
+	for (var i = 2; i < pts.length; i++)
+	{
+		var pt2 = pts[i];
+	
+		// Merges adjacent segments only if more than 2 to allow for straight edges
+		if ((Math.round(pt0.x - pt1.x) != 0 || Math.round(pt1.x - pt2.x) != 0) &&
+			(Math.round(pt0.y - pt1.y) != 0 || Math.round(pt1.y - pt2.y) != 0))
+		{
+			result.push(this.convertPoint(pt1.clone(), false));
+		}
+
+		pt0 = pt1;
+		pt1 = pt2;
+	}
+	
+	var model = this.graph.getModel();
+	
+	model.beginUpdate();
+	try
+	{
+		var geo = model.getGeometry(edge);
+		
+		if (geo != null)
+		{
+			geo = geo.clone();
+			geo.points = result;
+			
+			model.setGeometry(edge, geo);
+		}
+		
+		edge = mxEdgeHandler.prototype.connect.apply(this, arguments);
+	}
+	finally
+	{
+		model.endUpdate();
+	}
+	
+	return edge;
+};
+
+/**
+ * Function: getTooltipForNode
+ * 
+ * Returns no tooltips.
+ */
+mxEdgeSegmentHandler.prototype.getTooltipForNode = function(node)
+{
+	return null;
+};
+
+/**
+ * Function: createBends
+ * 
+ * Adds custom bends for the center of each segment.
+ */
+mxEdgeSegmentHandler.prototype.start = function(x, y, index)
+{
+	mxEdgeHandler.prototype.start.apply(this, arguments);
+	
+	if (this.bends[index] != null && !this.isSource && !this.isTarget)
+	{
+		mxUtils.setOpacity(this.bends[index].node, 100);
+	}
+};
+
+/**
+ * Function: createBends
+ * 
+ * Adds custom bends for the center of each segment.
+ */
+mxEdgeSegmentHandler.prototype.createBends = function()
+{
+	var bends = [];
+	
+	// Source
+	var bend = this.createHandleShape(0);
+	this.initBend(bend);
+	bend.setCursor(mxConstants.CURSOR_TERMINAL_HANDLE);
+	bends.push(bend);
+
+	var pts = this.getCurrentPoints();
+
+	// Waypoints (segment handles)
+	if (this.graph.isCellBendable(this.state.cell))
+	{
+		if (this.points == null)
+		{
+			this.points = [];
+		}
+
+		for (var i = 0; i < pts.length - 1; i++)
+		{
+			bend = this.createVirtualBend();
+			bends.push(bend);
+			var horizontal = Math.round(pts[i].x - pts[i + 1].x) == 0;
+			
+			// Special case where dy is 0 as well
+			if (Math.round(pts[i].y - pts[i + 1].y) == 0 && i < pts.length - 2)
+			{
+				horizontal = Math.round(pts[i].x - pts[i + 2].x) == 0;
+			}
+			
+			bend.setCursor((horizontal) ? 'col-resize' : 'row-resize');
+			this.points.push(new mxPoint(0,0));
+		}
+	}
+
+	// Target
+	var bend = this.createHandleShape(pts.length);
+	this.initBend(bend);
+	bend.setCursor(mxConstants.CURSOR_TERMINAL_HANDLE);
+	bends.push(bend);
+
+	return bends;
+};
+
+/**
+ * Function: redraw
+ * 
+ * Overridden to invoke <refresh> before the redraw.
+ */
+mxEdgeSegmentHandler.prototype.redraw = function()
+{
+	this.refresh();
+	mxEdgeHandler.prototype.redraw.apply(this, arguments);
+};
+
+/**
+ * Function: redrawInnerBends
+ * 
+ * Updates the position of the custom bends.
+ */
+mxEdgeSegmentHandler.prototype.redrawInnerBends = function(p0, pe)
+{
+	if (this.graph.isCellBendable(this.state.cell))
+	{
+		var pts = this.getCurrentPoints();
+		
+		if (pts != null && pts.length > 1)
+		{
+			var straight = false;
+			
+			// Puts handle in the center of straight edges
+			if (pts.length == 4 && Math.round(pts[1].x - pts[2].x) == 0 && Math.round(pts[1].y - pts[2].y) == 0)
+			{
+				straight = true;
+				
+				if (Math.round(pts[0].y - pts[pts.length - 1].y) == 0)
+				{
+					var cx = pts[0].x + (pts[pts.length - 1].x - pts[0].x) / 2;
+					pts[1] = new mxPoint(cx, pts[1].y);
+					pts[2] = new mxPoint(cx, pts[2].y);
+				}
+				else
+				{
+					var cy = pts[0].y + (pts[pts.length - 1].y - pts[0].y) / 2;
+					pts[1] = new mxPoint(pts[1].x, cy);
+					pts[2] = new mxPoint(pts[2].x, cy);
+				}
+			}
+			
+			for (var i = 0; i < pts.length - 1; i++)
+			{
+				if (this.bends[i + 1] != null)
+				{
+		 			var p0 = pts[i];
+	 				var pe = pts[i + 1];
+			 		var pt = new mxPoint(p0.x + (pe.x - p0.x) / 2, p0.y + (pe.y - p0.y) / 2);
+			 		var b = this.bends[i + 1].bounds;
+			 		this.bends[i + 1].bounds = new mxRectangle(Math.floor(pt.x - b.width / 2),
+			 				Math.floor(pt.y - b.height / 2), b.width, b.height);
+				 	this.bends[i + 1].redraw();
+				 	
+				 	if (this.manageLabelHandle)
+					{
+						this.checkLabelHandle(this.bends[i + 1].bounds);
+					}
+				}
+			}
+			
+			if (straight)
+			{
+				mxUtils.setOpacity(this.bends[1].node, this.virtualBendOpacity);
+				mxUtils.setOpacity(this.bends[3].node, this.virtualBendOpacity);
+			}
+		}
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/handler/mxElbowEdgeHandler.js b/airavata-kubernetes/web-console/src/assets/js/handler/mxElbowEdgeHandler.js
new file mode 100644
index 0000000..e408f04
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/handler/mxElbowEdgeHandler.js
@@ -0,0 +1,229 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxElbowEdgeHandler
+ *
+ * Graph event handler that reconnects edges and modifies control points and
+ * the edge label location. Uses <mxTerminalMarker> for finding and
+ * highlighting new source and target vertices. This handler is automatically
+ * created in <mxGraph.createHandler>. It extends <mxEdgeHandler>.
+ * 
+ * Constructor: mxEdgeHandler
+ *
+ * Constructs an edge handler for the specified <mxCellState>.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> of the cell to be modified.
+ */
+function mxElbowEdgeHandler(state)
+{
+	mxEdgeHandler.call(this, state);
+};
+
+/**
+ * Extends mxEdgeHandler.
+ */
+mxUtils.extend(mxElbowEdgeHandler, mxEdgeHandler);
+
+/**
+ * Specifies if a double click on the middle handle should call
+ * <mxGraph.flipEdge>. Default is true.
+ */
+mxElbowEdgeHandler.prototype.flipEnabled = true;
+
+/**
+ * Variable: doubleClickOrientationResource
+ * 
+ * Specifies the resource key for the tooltip to be displayed on the single
+ * control point for routed edges. If the resource for this key does not
+ * exist then the value is used as the error message. Default is
+ * 'doubleClickOrientation'.
+ */
+mxElbowEdgeHandler.prototype.doubleClickOrientationResource =
+	(mxClient.language != 'none') ? 'doubleClickOrientation' : '';
+
+/**
+ * Function: createBends
+ * 
+ * Overrides <mxEdgeHandler.createBends> to create custom bends.
+ */
+ mxElbowEdgeHandler.prototype.createBends = function()
+ {
+	var bends = [];
+	
+	// Source
+	var bend = this.createHandleShape(0);
+	this.initBend(bend);
+	bend.setCursor(mxConstants.CURSOR_TERMINAL_HANDLE);
+	bends.push(bend);
+
+	// Virtual
+	bends.push(this.createVirtualBend(mxUtils.bind(this, function(evt)
+	{
+		if (!mxEvent.isConsumed(evt) && this.flipEnabled)
+		{
+			this.graph.flipEdge(this.state.cell, evt);
+			mxEvent.consume(evt);
+		}
+	})));
+	this.points.push(new mxPoint(0,0));
+
+	// Target
+	bend = this.createHandleShape(2);
+	this.initBend(bend);
+	bend.setCursor(mxConstants.CURSOR_TERMINAL_HANDLE);
+	bends.push(bend);
+	
+	return bends;
+ };
+
+/**
+ * Function: createVirtualBend
+ * 
+ * Creates a virtual bend that supports double clicking and calls
+ * <mxGraph.flipEdge>.
+ */
+mxElbowEdgeHandler.prototype.createVirtualBend = function(dblClickHandler)
+{
+	var bend = this.createHandleShape();
+	this.initBend(bend, dblClickHandler);
+
+	bend.setCursor(this.getCursorForBend());
+
+	if (!this.graph.isCellBendable(this.state.cell))
+	{
+		bend.node.style.display = 'none';
+	}
+
+	return bend;
+};
+
+/**
+ * Function: getCursorForBend
+ * 
+ * Returns the cursor to be used for the bend.
+ */
+mxElbowEdgeHandler.prototype.getCursorForBend = function()
+{
+	return (this.state.style[mxConstants.STYLE_EDGE] == mxEdgeStyle.TopToBottom ||
+		this.state.style[mxConstants.STYLE_EDGE] == mxConstants.EDGESTYLE_TOPTOBOTTOM ||
+		((this.state.style[mxConstants.STYLE_EDGE] == mxEdgeStyle.ElbowConnector ||
+		this.state.style[mxConstants.STYLE_EDGE] == mxConstants.EDGESTYLE_ELBOW)&&
+		this.state.style[mxConstants.STYLE_ELBOW] == mxConstants.ELBOW_VERTICAL)) ? 
+		'row-resize' : 'col-resize';
+};
+
+/**
+ * Function: getTooltipForNode
+ * 
+ * Returns the tooltip for the given node.
+ */
+mxElbowEdgeHandler.prototype.getTooltipForNode = function(node)
+{
+	var tip = null;
+	
+	if (this.bends != null && this.bends[1] != null && (node == this.bends[1].node ||
+		node.parentNode == this.bends[1].node))
+	{
+		tip = this.doubleClickOrientationResource;
+		tip = mxResources.get(tip) || tip; // translate
+	}
+
+	return tip;
+};
+
+/**
+ * Function: convertPoint
+ * 
+ * Converts the given point in-place from screen to unscaled, untranslated
+ * graph coordinates and applies the grid.
+ * 
+ * Parameters:
+ * 
+ * point - <mxPoint> to be converted.
+ * gridEnabled - Boolean that specifies if the grid should be applied.
+ */
+mxElbowEdgeHandler.prototype.convertPoint = function(point, gridEnabled)
+{
+	var scale = this.graph.getView().getScale();
+	var tr = this.graph.getView().getTranslate();
+	var origin = this.state.origin;
+	
+	if (gridEnabled)
+	{
+		point.x = this.graph.snap(point.x);
+		point.y = this.graph.snap(point.y);
+	}
+	
+	point.x = Math.round(point.x / scale - tr.x - origin.x);
+	point.y = Math.round(point.y / scale - tr.y - origin.y);
+	
+	return point;
+};
+
+/**
+ * Function: redrawInnerBends
+ * 
+ * Updates and redraws the inner bends.
+ * 
+ * Parameters:
+ * 
+ * p0 - <mxPoint> that represents the location of the first point.
+ * pe - <mxPoint> that represents the location of the last point.
+ */
+mxElbowEdgeHandler.prototype.redrawInnerBends = function(p0, pe)
+{
+	var g = this.graph.getModel().getGeometry(this.state.cell);
+	var pts = this.state.absolutePoints;
+	var pt = null;
+
+	// Keeps the virtual bend on the edge shape
+	if (pts.length > 1)
+	{
+		p0 = pts[1];
+		pe = pts[pts.length - 2];
+	}
+	else if (g.points != null && g.points.length > 0)
+	{
+		pt = pts[0];
+	}
+	
+	if (pt == null)
+	{
+		pt = new mxPoint(p0.x + (pe.x - p0.x) / 2, p0.y + (pe.y - p0.y) / 2);
+	}
+	else
+	{
+		pt = new mxPoint(this.graph.getView().scale * (pt.x + this.graph.getView().translate.x + this.state.origin.x),
+				this.graph.getView().scale * (pt.y + this.graph.getView().translate.y + this.state.origin.y));
+	}
+
+	// Makes handle slightly bigger if the yellow  label handle
+	// exists and intersects this green handle
+	var b = this.bends[1].bounds;
+	var w = b.width;
+	var h = b.height;
+	var bounds = new mxRectangle(Math.round(pt.x - w / 2), Math.round(pt.y - h / 2), w, h);
+
+	if (this.manageLabelHandle)
+	{
+		this.checkLabelHandle(bounds);
+	}
+	else if (this.handleImage == null && this.labelShape.visible && mxUtils.intersects(bounds, this.labelShape.bounds))
+	{
+		w = mxConstants.HANDLE_SIZE + 3;
+		h = mxConstants.HANDLE_SIZE + 3;
+		bounds = new mxRectangle(Math.floor(pt.x - w / 2), Math.floor(pt.y - h / 2), w, h);
+	}
+
+	this.bends[1].bounds = bounds;
+	this.bends[1].redraw();
+	
+	if (this.manageLabelHandle)
+	{
+		this.checkLabelHandle(this.bends[1].bounds);
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/handler/mxGraphHandler.js b/airavata-kubernetes/web-console/src/assets/js/handler/mxGraphHandler.js
new file mode 100644
index 0000000..0619d67
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/handler/mxGraphHandler.js
@@ -0,0 +1,1074 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxGraphHandler
+ * 
+ * Graph event handler that handles selection. Individual cells are handled
+ * separately using <mxVertexHandler> or one of the edge handlers. These
+ * handlers are created using <mxGraph.createHandler> in
+ * <mxGraphSelectionModel.cellAdded>.
+ * 
+ * To avoid the container to scroll a moved cell into view, set
+ * <scrollAfterMove> to false.
+ * 
+ * Constructor: mxGraphHandler
+ * 
+ * Constructs an event handler that creates handles for the
+ * selection cells.
+ * 
+ * Parameters:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ */
+function mxGraphHandler(graph)
+{
+	this.graph = graph;
+	this.graph.addMouseListener(this);
+	
+	// Repaints the handler after autoscroll
+	this.panHandler = mxUtils.bind(this, function()
+	{
+		this.updatePreviewShape();
+		this.updateHint();
+	});
+	
+	this.graph.addListener(mxEvent.PAN, this.panHandler);
+	
+	// Handles escape keystrokes
+	this.escapeHandler = mxUtils.bind(this, function(sender, evt)
+	{
+		this.reset();
+	});
+	
+	this.graph.addListener(mxEvent.ESCAPE, this.escapeHandler);
+};
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxGraphHandler.prototype.graph = null;
+
+/**
+ * Variable: maxCells
+ * 
+ * Defines the maximum number of cells to paint subhandles
+ * for. Default is 50 for Firefox and 20 for IE. Set this
+ * to 0 if you want an unlimited number of handles to be
+ * displayed. This is only recommended if the number of
+ * cells in the graph is limited to a small number, eg.
+ * 500.
+ */
+mxGraphHandler.prototype.maxCells = (mxClient.IS_IE) ? 20 : 50;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies if events are handled. Default is true.
+ */
+mxGraphHandler.prototype.enabled = true;
+
+/**
+ * Variable: highlightEnabled
+ * 
+ * Specifies if drop targets under the mouse should be enabled. Default is
+ * true.
+ */
+mxGraphHandler.prototype.highlightEnabled = true;
+
+/**
+ * Variable: cloneEnabled
+ * 
+ * Specifies if cloning by control-drag is enabled. Default is true.
+ */
+mxGraphHandler.prototype.cloneEnabled = true;
+
+/**
+ * Variable: moveEnabled
+ * 
+ * Specifies if moving is enabled. Default is true.
+ */
+mxGraphHandler.prototype.moveEnabled = true;
+
+/**
+ * Variable: guidesEnabled
+ * 
+ * Specifies if other cells should be used for snapping the right, center or
+ * left side of the current selection. Default is false.
+ */
+mxGraphHandler.prototype.guidesEnabled = false;
+
+/**
+ * Variable: guide
+ * 
+ * Holds the <mxGuide> instance that is used for alignment.
+ */
+mxGraphHandler.prototype.guide = null;
+
+/**
+ * Variable: currentDx
+ * 
+ * Stores the x-coordinate of the current mouse move.
+ */
+mxGraphHandler.prototype.currentDx = null;
+
+/**
+ * Variable: currentDy
+ * 
+ * Stores the y-coordinate of the current mouse move.
+ */
+mxGraphHandler.prototype.currentDy = null;
+
+/**
+ * Variable: updateCursor
+ * 
+ * Specifies if a move cursor should be shown if the mouse is over a movable
+ * cell. Default is true.
+ */
+mxGraphHandler.prototype.updateCursor = true;
+
+/**
+ * Variable: selectEnabled
+ * 
+ * Specifies if selecting is enabled. Default is true.
+ */
+mxGraphHandler.prototype.selectEnabled = true;
+
+/**
+ * Variable: removeCellsFromParent
+ * 
+ * Specifies if cells may be moved out of their parents. Default is true.
+ */
+mxGraphHandler.prototype.removeCellsFromParent = true;
+
+/**
+ * Variable: connectOnDrop
+ * 
+ * Specifies if drop events are interpreted as new connections if no other
+ * drop action is defined. Default is false.
+ */
+mxGraphHandler.prototype.connectOnDrop = false;
+
+/**
+ * Variable: scrollOnMove
+ * 
+ * Specifies if the view should be scrolled so that a moved cell is
+ * visible. Default is true.
+ */
+mxGraphHandler.prototype.scrollOnMove = true;
+
+/**
+ * Variable: minimumSize
+ * 
+ * Specifies the minimum number of pixels for the width and height of a
+ * selection border. Default is 6.
+ */
+mxGraphHandler.prototype.minimumSize = 6;
+
+/**
+ * Variable: previewColor
+ * 
+ * Specifies the color of the preview shape. Default is black.
+ */
+mxGraphHandler.prototype.previewColor = 'black';
+
+/**
+ * Variable: htmlPreview
+ * 
+ * Specifies if the graph container should be used for preview. If this is used
+ * then drop target detection relies entirely on <mxGraph.getCellAt> because
+ * the HTML preview does not "let events through". Default is false.
+ */
+mxGraphHandler.prototype.htmlPreview = false;
+
+/**
+ * Variable: shape
+ * 
+ * Reference to the <mxShape> that represents the preview.
+ */
+mxGraphHandler.prototype.shape = null;
+
+/**
+ * Variable: scaleGrid
+ * 
+ * Specifies if the grid should be scaled. Default is false.
+ */
+mxGraphHandler.prototype.scaleGrid = false;
+
+/**
+ * Variable: rotationEnabled
+ * 
+ * Specifies if the bounding box should allow for rotation. Default is true.
+ */
+mxGraphHandler.prototype.rotationEnabled = true;
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns <enabled>.
+ */
+mxGraphHandler.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ * 
+ * Sets <enabled>.
+ */
+mxGraphHandler.prototype.setEnabled = function(value)
+{
+	this.enabled = value;
+};
+
+/**
+ * Function: isCloneEnabled
+ * 
+ * Returns <cloneEnabled>.
+ */
+mxGraphHandler.prototype.isCloneEnabled = function()
+{
+	return this.cloneEnabled;
+};
+
+/**
+ * Function: setCloneEnabled
+ * 
+ * Sets <cloneEnabled>.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean that specifies the new clone enabled state.
+ */
+mxGraphHandler.prototype.setCloneEnabled = function(value)
+{
+	this.cloneEnabled = value;
+};
+
+/**
+ * Function: isMoveEnabled
+ * 
+ * Returns <moveEnabled>.
+ */
+mxGraphHandler.prototype.isMoveEnabled = function()
+{
+	return this.moveEnabled;
+};
+
+/**
+ * Function: setMoveEnabled
+ * 
+ * Sets <moveEnabled>.
+ */
+mxGraphHandler.prototype.setMoveEnabled = function(value)
+{
+	this.moveEnabled = value;
+};
+
+/**
+ * Function: isSelectEnabled
+ * 
+ * Returns <selectEnabled>.
+ */
+mxGraphHandler.prototype.isSelectEnabled = function()
+{
+	return this.selectEnabled;
+};
+
+/**
+ * Function: setSelectEnabled
+ * 
+ * Sets <selectEnabled>.
+ */
+mxGraphHandler.prototype.setSelectEnabled = function(value)
+{
+	this.selectEnabled = value;
+};
+
+/**
+ * Function: isRemoveCellsFromParent
+ * 
+ * Returns <removeCellsFromParent>.
+ */
+mxGraphHandler.prototype.isRemoveCellsFromParent = function()
+{
+	return this.removeCellsFromParent;
+};
+
+/**
+ * Function: setRemoveCellsFromParent
+ * 
+ * Sets <removeCellsFromParent>.
+ */
+mxGraphHandler.prototype.setRemoveCellsFromParent = function(value)
+{
+	this.removeCellsFromParent = value;
+};
+
+/**
+ * Function: getInitialCellForEvent
+ * 
+ * Hook to return initial cell for the given event.
+ */
+mxGraphHandler.prototype.getInitialCellForEvent = function(me)
+{
+	return me.getCell();
+};
+
+/**
+ * Function: isDelayedSelection
+ * 
+ * Hook to return true for delayed selections.
+ */
+mxGraphHandler.prototype.isDelayedSelection = function(cell, me)
+{
+	return this.graph.isCellSelected(cell);
+};
+
+/**
+ * Function: consumeMouseEvent
+ * 
+ * Consumes the given mouse event. NOTE: This may be used to enable click
+ * events for links in labels on iOS as follows as consuming the initial
+ * touchStart disables firing the subsequent click evnent on the link.
+ * 
+ * <code>
+ * mxGraphHandler.prototype.consumeMouseEvent = function(evtName, me)
+ * {
+ *   var source = mxEvent.getSource(me.getEvent());
+ *   
+ *   if (!mxEvent.isTouchEvent(me.getEvent()) || source.nodeName != 'A')
+ *   {
+ *     me.consume();
+ *   }
+ * }
+ * </code>
+ */
+mxGraphHandler.prototype.consumeMouseEvent = function(evtName, me)
+{
+	me.consume();
+};
+
+/**
+ * Function: mouseDown
+ * 
+ * Handles the event by selecing the given cell and creating a handle for
+ * it. By consuming the event all subsequent events of the gesture are
+ * redirected to this handler.
+ */
+mxGraphHandler.prototype.mouseDown = function(sender, me)
+{
+	if (!me.isConsumed() && this.isEnabled() && this.graph.isEnabled() &&
+		me.getState() != null && !mxEvent.isMultiTouchEvent(me.getEvent()))
+	{
+		var cell = this.getInitialCellForEvent(me);
+		this.delayedSelection = this.isDelayedSelection(cell, me);
+		this.cell = null;
+		
+		if (this.isSelectEnabled() && !this.delayedSelection)
+		{
+			this.graph.selectCellForEvent(cell, me.getEvent());
+		}
+
+		if (this.isMoveEnabled())
+		{
+			var model = this.graph.model;
+			var geo = model.getGeometry(cell);
+
+			if (this.graph.isCellMovable(cell) && ((!model.isEdge(cell) || this.graph.getSelectionCount() > 1 ||
+				(geo.points != null && geo.points.length > 0) || model.getTerminal(cell, true) == null ||
+				model.getTerminal(cell, false) == null) || this.graph.allowDanglingEdges || 
+				(this.graph.isCloneEvent(me.getEvent()) && this.graph.isCellsCloneable())))
+			{
+				this.start(cell, me.getX(), me.getY());
+			}
+			else if (this.delayedSelection)
+			{
+				this.cell = cell;
+			}
+
+			this.cellWasClicked = true;
+			this.consumeMouseEvent(mxEvent.MOUSE_DOWN, me);
+		}
+	}
+};
+
+/**
+ * Function: getGuideStates
+ * 
+ * Creates an array of cell states which should be used as guides.
+ */
+mxGraphHandler.prototype.getGuideStates = function()
+{
+	var parent = this.graph.getDefaultParent();
+	var model = this.graph.getModel();
+	
+	var filter = mxUtils.bind(this, function(cell)
+	{
+		return this.graph.view.getState(cell) != null &&
+			model.isVertex(cell) &&
+			model.getGeometry(cell) != null &&
+			!model.getGeometry(cell).relative;
+	});
+	
+	return this.graph.view.getCellStates(model.filterDescendants(filter, parent));
+};
+
+/**
+ * Function: getCells
+ * 
+ * Returns the cells to be modified by this handler. This implementation
+ * returns all selection cells that are movable, or the given initial cell if
+ * the given cell is not selected and movable. This handles the case of moving
+ * unselectable or unselected cells.
+ * 
+ * Parameters:
+ * 
+ * initialCell - <mxCell> that triggered this handler.
+ */
+mxGraphHandler.prototype.getCells = function(initialCell)
+{
+	if (!this.delayedSelection && this.graph.isCellMovable(initialCell))
+	{
+		return [initialCell];
+	}
+	else
+	{
+		return this.graph.getMovableCells(this.graph.getSelectionCells());
+	}
+};
+
+/**
+ * Function: getPreviewBounds
+ * 
+ * Returns the <mxRectangle> used as the preview bounds for
+ * moving the given cells.
+ */
+mxGraphHandler.prototype.getPreviewBounds = function(cells)
+{
+	var bounds = this.getBoundingBox(cells);
+	
+	if (bounds != null)
+	{
+		// Corrects width and height
+		bounds.width = Math.max(0, bounds.width - 1);
+		bounds.height = Math.max(0, bounds.height - 1);
+		
+		if (bounds.width < this.minimumSize)
+		{
+			var dx = this.minimumSize - bounds.width;
+			bounds.x -= dx / 2;
+			bounds.width = this.minimumSize;
+		}
+		else
+		{
+			bounds.x = Math.round(bounds.x);
+			bounds.width = Math.ceil(bounds.width);
+		}
+		
+		var tr = this.graph.view.translate;
+		var s = this.graph.view.scale;
+		
+		if (bounds.height < this.minimumSize)
+		{
+			var dy = this.minimumSize - bounds.height;
+			bounds.y -= dy / 2;
+			bounds.height = this.minimumSize;
+		}
+		else
+		{
+			bounds.y = Math.round(bounds.y);
+			bounds.height = Math.ceil(bounds.height);
+		}
+	}
+	
+	return bounds;
+};
+
+/**
+ * Function: getBoundingBox
+ * 
+ * Returns the union of the <mxCellStates> for the given array of <mxCells>.
+ * For vertices, this method uses the bounding box of the corresponding shape
+ * if one exists. The bounding box of the corresponding text label and all
+ * controls and overlays are ignored. See also: <mxGraphView.getBounds> and
+ * <mxGraph.getBoundingBox>.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> whose bounding box should be returned.
+ */
+mxGraphHandler.prototype.getBoundingBox = function(cells)
+{
+	var result = null;
+	
+	if (cells != null && cells.length > 0)
+	{
+		var model = this.graph.getModel();
+		
+		for (var i = 0; i < cells.length; i++)
+		{
+			if (model.isVertex(cells[i]) || model.isEdge(cells[i]))
+			{
+				var state = this.graph.view.getState(cells[i]);
+			
+				if (state != null)
+				{
+					var bbox = state;
+					
+					if (model.isVertex(cells[i]) && state.shape != null && state.shape.boundingBox != null)
+					{
+						bbox = state.shape.boundingBox;
+					}
+					
+					if (result == null)
+					{
+						result = mxRectangle.fromRectangle(bbox);
+					}
+					else
+					{
+						result.add(bbox);
+					}
+				}
+			}
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: createPreviewShape
+ * 
+ * Creates the shape used to draw the preview for the given bounds.
+ */
+mxGraphHandler.prototype.createPreviewShape = function(bounds)
+{
+	var shape = new mxRectangleShape(bounds, null, this.previewColor);
+	shape.isDashed = true;
+	
+	if (this.htmlPreview)
+	{
+		shape.dialect = mxConstants.DIALECT_STRICTHTML;
+		shape.init(this.graph.container);
+	}
+	else
+	{
+		// Makes sure to use either VML or SVG shapes in order to implement
+		// event-transparency on the background area of the rectangle since
+		// HTML shapes do not let mouseevents through even when transparent
+		shape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
+			mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+		shape.init(this.graph.getView().getOverlayPane());
+		shape.pointerEvents = false;
+		
+		// Workaround for artifacts on iOS
+		if (mxClient.IS_IOS)
+		{
+			shape.getSvgScreenOffset = function()
+			{
+				return 0;
+			};
+		}
+	}
+	
+	return shape;
+};
+
+/**
+ * Function: start
+ * 
+ * Starts the handling of the mouse gesture.
+ */
+mxGraphHandler.prototype.start = function(cell, x, y)
+{
+	this.cell = cell;
+	this.first = mxUtils.convertPoint(this.graph.container, x, y);
+	this.cells = this.getCells(this.cell);
+	this.bounds = this.graph.getView().getBounds(this.cells);
+	this.pBounds = this.getPreviewBounds(this.cells);
+
+	if (this.guidesEnabled)
+	{
+		this.guide = new mxGuide(this.graph, this.getGuideStates());
+	}
+};
+
+/**
+ * Function: useGuidesForEvent
+ * 
+ * Returns true if the guides should be used for the given <mxMouseEvent>.
+ * This implementation returns <mxGuide.isEnabledForEvent>.
+ */
+mxGraphHandler.prototype.useGuidesForEvent = function(me)
+{
+	return (this.guide != null) ? this.guide.isEnabledForEvent(me.getEvent()) : true;
+};
+
+
+/**
+ * Function: snap
+ * 
+ * Snaps the given vector to the grid and returns the given mxPoint instance.
+ */
+mxGraphHandler.prototype.snap = function(vector)
+{
+	var scale = (this.scaleGrid) ? this.graph.view.scale : 1;
+	
+	vector.x = this.graph.snap(vector.x / scale) * scale;
+	vector.y = this.graph.snap(vector.y / scale) * scale;
+	
+	return vector;
+};
+
+/**
+ * Function: getDelta
+ * 
+ * Returns an <mxPoint> that represents the vector for moving the cells
+ * for the given <mxMouseEvent>.
+ */
+mxGraphHandler.prototype.getDelta = function(me)
+{
+	var point = mxUtils.convertPoint(this.graph.container, me.getX(), me.getY());
+	var s = this.graph.view.scale;
+	
+	return new mxPoint(this.roundLength((point.x - this.first.x) / s) * s,
+		this.roundLength((point.y - this.first.y) / s) * s);
+};
+
+/**
+ * Function: updateHint
+ * 
+ * Hook for subclassers do show details while the handler is active.
+ */
+mxGraphHandler.prototype.updateHint = function(me) { };
+
+/**
+ * Function: removeHint
+ * 
+ * Hooks for subclassers to hide details when the handler gets inactive.
+ */
+mxGraphHandler.prototype.removeHint = function() { };
+
+/**
+ * Function: roundLength
+ * 
+ * Hook for rounding the unscaled vector. This uses Math.round.
+ */
+mxGraphHandler.prototype.roundLength = function(length)
+{
+	return Math.round(length);
+};
+
+/**
+ * Function: mouseMove
+ * 
+ * Handles the event by highlighting possible drop targets and updating the
+ * preview.
+ */
+mxGraphHandler.prototype.mouseMove = function(sender, me)
+{
+	var graph = this.graph;
+
+	if (!me.isConsumed() && graph.isMouseDown && this.cell != null &&
+		this.first != null && this.bounds != null)
+	{
+		// Stops moving if a multi touch event is received
+		if (mxEvent.isMultiTouchEvent(me.getEvent()))
+		{
+			this.reset();
+			return;
+		}
+		
+		var delta = this.getDelta(me);
+		var dx = delta.x;
+		var dy = delta.y;
+		var tol = graph.tolerance;
+
+		if (this.shape != null || Math.abs(dx) > tol || Math.abs(dy) > tol)
+		{
+			// Highlight is used for highlighting drop targets
+			if (this.highlight == null)
+			{
+				this.highlight = new mxCellHighlight(this.graph,
+					mxConstants.DROP_TARGET_COLOR, 3);
+			}
+			
+			if (this.shape == null)
+			{
+				this.shape = this.createPreviewShape(this.bounds);
+			}
+			
+			var gridEnabled = graph.isGridEnabledEvent(me.getEvent());
+			var hideGuide = true;
+			
+			if (this.guide != null && this.useGuidesForEvent(me))
+			{
+				delta = this.guide.move(this.bounds, new mxPoint(dx, dy), gridEnabled);
+				hideGuide = false;
+				dx = delta.x;
+				dy = delta.y;
+			}
+			else if (gridEnabled)
+			{
+				var trx = graph.getView().translate;
+				var scale = graph.getView().scale;				
+				
+				var tx = this.bounds.x - (graph.snap(this.bounds.x / scale - trx.x) + trx.x) * scale;
+				var ty = this.bounds.y - (graph.snap(this.bounds.y / scale - trx.y) + trx.y) * scale;
+				var v = this.snap(new mxPoint(dx, dy));
+			
+				dx = v.x - tx;
+				dy = v.y - ty;
+			}
+			
+			if (this.guide != null && hideGuide)
+			{
+				this.guide.hide();
+			}
+
+			// Constrained movement if shift key is pressed
+			if (graph.isConstrainedEvent(me.getEvent()))
+			{
+				if (Math.abs(dx) > Math.abs(dy))
+				{
+					dy = 0;
+				}
+				else
+				{
+					dx = 0;
+				}
+			}
+
+			this.currentDx = dx;
+			this.currentDy = dy;
+			this.updatePreviewShape();
+
+			var target = null;
+			var cell = me.getCell();
+
+			var clone = graph.isCloneEvent(me.getEvent()) && graph.isCellsCloneable() && this.isCloneEnabled();
+			
+			if (graph.isDropEnabled() && this.highlightEnabled)
+			{
+				// Contains a call to getCellAt to find the cell under the mouse
+				target = graph.getDropTarget(this.cells, me.getEvent(), cell, clone);
+			}
+
+			var state = graph.getView().getState(target);
+			var highlight = false;
+			
+			if (state != null && (graph.model.getParent(this.cell) != target || clone))
+			{
+			    if (this.target != target)
+			    {
+				    this.target = target;
+				    this.setHighlightColor(mxConstants.DROP_TARGET_COLOR);
+				}
+			    
+			    highlight = true;
+			}
+			else
+			{
+				this.target = null;
+
+				if (this.connectOnDrop && cell != null && this.cells.length == 1 &&
+					graph.getModel().isVertex(cell) && graph.isCellConnectable(cell))
+				{
+					state = graph.getView().getState(cell);
+					
+					if (state != null)
+					{
+						var error = graph.getEdgeValidationError(null, this.cell, cell);
+						var color = (error == null) ?
+							mxConstants.VALID_COLOR :
+							mxConstants.INVALID_CONNECT_TARGET_COLOR;
+						this.setHighlightColor(color);
+						highlight = true;
+					}
+				}
+			}
+			
+			if (state != null && highlight)
+			{
+				this.highlight.highlight(state);
+			}
+			else
+			{
+				this.highlight.hide();
+			}
+		}
+
+		this.updateHint(me);
+		this.consumeMouseEvent(mxEvent.MOUSE_MOVE, me);
+		
+		// Cancels the bubbling of events to the container so
+		// that the droptarget is not reset due to an mouseMove
+		// fired on the container with no associated state.
+		mxEvent.consume(me.getEvent());
+	}
+	else if ((this.isMoveEnabled() || this.isCloneEnabled()) && this.updateCursor &&
+		!me.isConsumed() && me.getState() != null && !graph.isMouseDown)
+	{
+		var cursor = graph.getCursorForMouseEvent(me);
+		
+		if (cursor == null && graph.isEnabled() && graph.isCellMovable(me.getCell()))
+		{
+			if (graph.getModel().isEdge(me.getCell()))
+			{
+				cursor = mxConstants.CURSOR_MOVABLE_EDGE;
+			}
+			else
+			{
+				cursor = mxConstants.CURSOR_MOVABLE_VERTEX;
+			}
+		}
+
+		// Sets the cursor on the original source state under the mouse
+		// instead of the event source state which can be the parent
+		if (me.sourceState != null)
+		{
+			me.sourceState.setCursor(cursor);
+		}
+	}
+};
+
+/**
+ * Function: updatePreviewShape
+ * 
+ * Updates the bounds of the preview shape.
+ */
+mxGraphHandler.prototype.updatePreviewShape = function()
+{
+	if (this.shape != null)
+	{
+		this.shape.bounds = new mxRectangle(Math.round(this.pBounds.x + this.currentDx - this.graph.panDx),
+				Math.round(this.pBounds.y + this.currentDy - this.graph.panDy), this.pBounds.width, this.pBounds.height);
+		this.shape.redraw();
+	}
+};
+
+/**
+ * Function: setHighlightColor
+ * 
+ * Sets the color of the rectangle used to highlight drop targets.
+ * 
+ * Parameters:
+ * 
+ * color - String that represents the new highlight color.
+ */
+mxGraphHandler.prototype.setHighlightColor = function(color)
+{
+	if (this.highlight != null)
+	{
+		this.highlight.setHighlightColor(color);
+	}
+};
+
+/**
+ * Function: mouseUp
+ * 
+ * Handles the event by applying the changes to the selection cells.
+ */
+mxGraphHandler.prototype.mouseUp = function(sender, me)
+{
+	if (!me.isConsumed())
+	{
+		var graph = this.graph;
+		
+		if (this.cell != null && this.first != null && this.shape != null &&
+			this.currentDx != null && this.currentDy != null)
+		{
+			var cell = me.getCell();
+			
+			if (this.connectOnDrop && this.target == null && cell != null && graph.getModel().isVertex(cell) &&
+				graph.isCellConnectable(cell) && graph.isEdgeValid(null, this.cell, cell))
+			{
+				graph.connectionHandler.connect(this.cell, cell, me.getEvent());
+			}
+			else
+			{
+				var clone = graph.isCloneEvent(me.getEvent()) && graph.isCellsCloneable() && this.isCloneEnabled();
+				var scale = graph.getView().scale;
+				var dx = this.roundLength(this.currentDx / scale);
+				var dy = this.roundLength(this.currentDy / scale);
+				var target = this.target;
+				
+				if (graph.isSplitEnabled() && graph.isSplitTarget(target, this.cells, me.getEvent()))
+				{
+					graph.splitEdge(target, this.cells, null, dx, dy);
+				}
+				else
+				{
+					this.moveCells(this.cells, dx, dy, clone, this.target, me.getEvent());
+				}
+			}
+		}
+		else if (this.isSelectEnabled() && this.delayedSelection && this.cell != null)
+		{
+			this.selectDelayed(me);
+		}
+	}
+
+	// Consumes the event if a cell was initially clicked
+	if (this.cellWasClicked)
+	{
+		this.consumeMouseEvent(mxEvent.MOUSE_UP, me);
+	}
+
+	this.reset();
+};
+
+/**
+ * Function: selectDelayed
+ * 
+ * Implements the delayed selection for the given mouse event.
+ */
+mxGraphHandler.prototype.selectDelayed = function(me)
+{
+	if (!this.graph.isCellSelected(this.cell) || !this.graph.popupMenuHandler.isPopupTrigger(me))
+	{
+		this.graph.selectCellForEvent(this.cell, me.getEvent());
+	}
+};
+
+/**
+ * Function: reset
+ * 
+ * Resets the state of this handler.
+ */
+mxGraphHandler.prototype.reset = function()
+{
+	this.destroyShapes();
+	this.removeHint();
+	
+	this.cellWasClicked = false;
+	this.delayedSelection = false;
+	this.currentDx = null;
+	this.currentDy = null;
+	this.guides = null;
+	this.first = null;
+	this.cell = null;
+	this.target = null;
+};
+
+/**
+ * Function: shouldRemoveCellsFromParent
+ * 
+ * Returns true if the given cells should be removed from the parent for the specified
+ * mousereleased event.
+ */
+mxGraphHandler.prototype.shouldRemoveCellsFromParent = function(parent, cells, evt)
+{
+	if (this.graph.getModel().isVertex(parent))
+	{
+		var pState = this.graph.getView().getState(parent);
+		
+		if (pState != null)
+		{
+			var pt = mxUtils.convertPoint(this.graph.container,
+				mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+			var alpha = mxUtils.toRadians(mxUtils.getValue(pState.style, mxConstants.STYLE_ROTATION) || 0);
+			
+			if (alpha != 0)
+			{
+				var cos = Math.cos(-alpha);
+				var sin = Math.sin(-alpha);
+				var cx = new mxPoint(pState.getCenterX(), pState.getCenterY());
+				pt = mxUtils.getRotatedPoint(pt, cos, sin, cx);
+			}
+		
+			return !mxUtils.contains(pState, pt.x, pt.y);
+		}
+	}
+	
+	return false;
+};
+
+/**
+ * Function: moveCells
+ * 
+ * Moves the given cells by the specified amount.
+ */
+mxGraphHandler.prototype.moveCells = function(cells, dx, dy, clone, target, evt)
+{
+	if (clone)
+	{
+		cells = this.graph.getCloneableCells(cells);
+	}
+	
+	// Removes cells from parent
+	if (target == null && this.isRemoveCellsFromParent() &&
+		this.shouldRemoveCellsFromParent(this.graph.getModel().getParent(this.cell), cells, evt))
+	{
+		target = this.graph.getDefaultParent();
+	}
+	
+	// Passes all selected cells in order to correctly clone or move into
+	// the target cell. The method checks for each cell if its movable.
+	cells = this.graph.moveCells(cells, dx - this.graph.panDx / this.graph.view.scale,
+			dy - this.graph.panDy / this.graph.view.scale, clone, target, evt);
+	
+	if (this.isSelectEnabled() && this.scrollOnMove)
+	{
+		this.graph.scrollCellToVisible(cells[0]);
+	}
+			
+	// Selects the new cells if cells have been cloned
+	if (clone)
+	{
+		this.graph.setSelectionCells(cells);
+	}
+};
+
+/**
+ * Function: destroyShapes
+ * 
+ * Destroy the preview and highlight shapes.
+ */
+mxGraphHandler.prototype.destroyShapes = function()
+{
+	// Destroys the preview dashed rectangle
+	if (this.shape != null)
+	{
+		this.shape.destroy();
+		this.shape = null;
+	}
+	
+	if (this.guide != null)
+	{
+		this.guide.destroy();
+		this.guide = null;
+	}
+	
+	// Destroys the drop target highlight
+	if (this.highlight != null)
+	{
+		this.highlight.destroy();
+		this.highlight = null;
+	}
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the handler and all its resources and DOM nodes.
+ */
+mxGraphHandler.prototype.destroy = function()
+{
+	this.graph.removeMouseListener(this);
+	this.graph.removeListener(this.panHandler);
+	
+	if (this.escapeHandler != null)
+	{
+		this.graph.removeListener(this.escapeHandler);
+		this.escapeHandler = null;
+	}
+	
+	this.destroyShapes();
+	this.removeHint();
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/handler/mxHandle.js b/airavata-kubernetes/web-console/src/assets/js/handler/mxHandle.js
new file mode 100644
index 0000000..5564925
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/handler/mxHandle.js
@@ -0,0 +1,353 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxHandle
+ * 
+ * Implements a single custom handle for vertices.
+ * 
+ * Constructor: mxHandle
+ * 
+ * Constructs a new handle for the given state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> of the cell to be handled.
+ */
+function mxHandle(state, cursor, image)
+{
+	this.graph = state.view.graph;
+	this.state = state;
+	this.cursor = (cursor != null) ? cursor : this.cursor;
+	this.image = (image != null) ? image : this.image;
+	this.init();
+};
+
+/**
+ * Variable: cursor
+ * 
+ * Specifies the cursor to be used for this handle. Default is 'default'.
+ */
+mxHandle.prototype.cursor = 'default';
+
+/**
+ * Variable: image
+ * 
+ * Specifies the <mxImage> to be used to render the handle. Default is null.
+ */
+mxHandle.prototype.image = null;
+
+/**
+ * Variable: image
+ * 
+ * Specifies the <mxImage> to be used to render the handle. Default is null.
+ */
+mxHandle.prototype.ignoreGrid = false;
+
+/**
+ * Function: getPosition
+ * 
+ * Hook for subclassers to return the current position of the handle.
+ */
+mxHandle.prototype.getPosition = function(bounds) { };
+
+/**
+ * Function: setPosition
+ * 
+ * Hooks for subclassers to update the style in the <state>.
+ */
+mxHandle.prototype.setPosition = function(bounds, pt, me) { };
+
+/**
+ * Function: execute
+ * 
+ * Hook for subclassers to execute the handle.
+ */
+mxHandle.prototype.execute = function() { };
+
+/**
+ * Function: copyStyle
+ * 
+ * Sets the cell style with the given name to the corresponding value in <state>.
+ */
+mxHandle.prototype.copyStyle = function(key)
+{
+	this.graph.setCellStyles(key, this.state.style[key], [this.state.cell]);
+};
+
+/**
+ * Function: processEvent
+ * 
+ * Processes the given <mxMouseEvent> and invokes <setPosition>.
+ */
+mxHandle.prototype.processEvent = function(me)
+{
+	var scale = this.graph.view.scale;
+	var tr = this.graph.view.translate;
+	var pt = new mxPoint(me.getGraphX() / scale - tr.x, me.getGraphY() / scale - tr.y);
+	
+	// Center shape on mouse cursor
+	if (this.shape != null && this.shape.bounds != null)
+	{
+		pt.x -= this.shape.bounds.width / scale / 4;
+		pt.y -= this.shape.bounds.height / scale / 4;
+	}
+
+	// Snaps to grid for the rotated position then applies the rotation for the direction after that
+	var alpha1 = -mxUtils.toRadians(this.getRotation());
+	var alpha2 = -mxUtils.toRadians(this.getTotalRotation()) - alpha1;
+	pt = this.flipPoint(this.rotatePoint(this.snapPoint(this.rotatePoint(pt, alpha1),
+			this.ignoreGrid || !this.graph.isGridEnabledEvent(me.getEvent())), alpha2));
+	this.setPosition(this.state.getPaintBounds(), pt, me);
+	this.positionChanged();
+	this.redraw();
+};
+
+/**
+ * Function: positionChanged
+ * 
+ * Called after <setPosition> has been called in <processEvent>. This repaints
+ * the state using <mxCellRenderer>.
+ */
+mxHandle.prototype.positionChanged = function()
+{
+	if (this.state.text != null)
+	{
+		this.state.text.apply(this.state);
+	}
+	
+	if (this.state.shape != null)
+	{
+		this.state.shape.apply(this.state);
+	}
+	
+	// Needed to force update of text bounds
+	this.state.unscaledWidth = null;
+	this.graph.cellRenderer.redraw(this.state, true);
+};
+
+/**
+ * Function: getRotation
+ * 
+ * Returns the rotation defined in the style of the cell.
+ */
+mxHandle.prototype.getRotation = function()
+{
+	if (this.state.shape != null)
+	{
+		return this.state.shape.getRotation();
+	}
+	
+	return 0;
+};
+
+/**
+ * Function: getTotalRotation
+ * 
+ * Returns the rotation from the style and the rotation from the direction of
+ * the cell.
+ */
+mxHandle.prototype.getTotalRotation = function()
+{
+	if (this.state.shape != null)
+	{
+		return this.state.shape.getShapeRotation();
+	}
+	
+	return 0;
+};
+
+/**
+ * Function: init
+ * 
+ * Creates and initializes the shapes required for this handle.
+ */
+mxHandle.prototype.init = function()
+{
+	var html = this.isHtmlRequired();
+	
+	if (this.image != null)
+	{
+		this.shape = new mxImageShape(new mxRectangle(0, 0, this.image.width, this.image.height), this.image.src);
+		this.shape.preserveImageAspect = false;
+	}
+	else
+	{
+		this.shape = this.createShape(html);
+	}
+	
+	this.initShape(html);
+};
+
+/**
+ * Function: createShape
+ * 
+ * Creates and returns the shape for this handle.
+ */
+mxHandle.prototype.createShape = function(html)
+{
+	var bounds = new mxRectangle(0, 0, mxConstants.HANDLE_SIZE, mxConstants.HANDLE_SIZE);
+	
+	return new mxRectangleShape(bounds, mxConstants.HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR);
+};
+
+/**
+ * Function: initShape
+ * 
+ * Initializes <shape> and sets its cursor.
+ */
+mxHandle.prototype.initShape = function(html)
+{
+	if (html && this.shape.isHtmlAllowed())
+	{
+		this.shape.dialect = mxConstants.DIALECT_STRICTHTML;
+		this.shape.init(this.graph.container);
+	}
+	else
+	{
+		this.shape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? mxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG;
+		
+		if (this.cursor != null)
+		{
+			this.shape.init(this.graph.getView().getOverlayPane());
+		}
+	}
+
+	mxEvent.redirectMouseEvents(this.shape.node, this.graph, this.state);
+	this.shape.node.style.cursor = this.cursor;
+};
+
+/**
+ * Function: redraw
+ * 
+ * Renders the shape for this handle.
+ */
+mxHandle.prototype.redraw = function()
+{
+	if (this.shape != null && this.state.shape != null)
+	{
+		var pt = this.getPosition(this.state.getPaintBounds());
+		
+		if (pt != null)
+		{
+			var alpha = mxUtils.toRadians(this.getTotalRotation());
+			pt = this.rotatePoint(this.flipPoint(pt), alpha);
+	
+			var scale = this.graph.view.scale;
+			var tr = this.graph.view.translate;
+			this.shape.bounds.x = Math.floor((pt.x + tr.x) * scale - this.shape.bounds.width / 2);
+			this.shape.bounds.y = Math.floor((pt.y + tr.y) * scale - this.shape.bounds.height / 2);
+			
+			// Needed to force update of text bounds
+			this.shape.redraw();
+		}
+	}
+};
+
+/**
+ * Function: isHtmlRequired
+ * 
+ * Returns true if this handle should be rendered in HTML. This returns true if
+ * the text node is in the graph container.
+ */
+mxHandle.prototype.isHtmlRequired = function()
+{
+	return this.state.text != null && this.state.text.node.parentNode == this.graph.container;
+};
+
+/**
+ * Function: rotatePoint
+ * 
+ * Rotates the point by the given angle.
+ */
+mxHandle.prototype.rotatePoint = function(pt, alpha)
+{
+	var bounds = this.state.getCellBounds();
+	var cx = new mxPoint(bounds.getCenterX(), bounds.getCenterY());
+	var cos = Math.cos(alpha);
+	var sin = Math.sin(alpha); 
+
+	return mxUtils.getRotatedPoint(pt, cos, sin, cx);
+};
+
+/**
+ * Function: flipPoint
+ * 
+ * Flips the given point vertically and/or horizontally.
+ */
+mxHandle.prototype.flipPoint = function(pt)
+{
+	if (this.state.shape != null)
+	{
+		var bounds = this.state.getCellBounds();
+		
+		if (this.state.shape.flipH)
+		{
+			pt.x = 2 * bounds.x + bounds.width - pt.x;
+		}
+		
+		if (this.state.shape.flipV)
+		{
+			pt.y = 2 * bounds.y + bounds.height - pt.y;
+		}
+	}
+	
+	return pt;
+};
+
+/**
+ * Function: snapPoint
+ * 
+ * Snaps the given point to the grid if ignore is false. This modifies
+ * the given point in-place and also returns it.
+ */
+mxHandle.prototype.snapPoint = function(pt, ignore)
+{
+	if (!ignore)
+	{
+		pt.x = this.graph.snap(pt.x);
+		pt.y = this.graph.snap(pt.y);
+	}
+	
+	return pt;
+};
+
+/**
+ * Function: setVisible
+ * 
+ * Shows or hides this handle.
+ */
+mxHandle.prototype.setVisible = function(visible)
+{
+	if (this.shape != null && this.shape.node != null)
+	{
+		this.shape.node.style.display = (visible) ? '' : 'none';
+	}
+};
+
+/**
+ * Function: reset
+ * 
+ * Resets the state of this handle by setting its visibility to true.
+ */
+mxHandle.prototype.reset = function()
+{
+	this.setVisible(true);
+	this.state.style = this.graph.getCellStyle(this.state.cell);
+	this.positionChanged();
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys this handle.
+ */
+mxHandle.prototype.destroy = function()
+{
+	if (this.shape != null)
+	{
+		this.shape.destroy();
+		this.shape = null;
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/handler/mxKeyHandler.js b/airavata-kubernetes/web-console/src/assets/js/handler/mxKeyHandler.js
new file mode 100644
index 0000000..6a391f0
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/handler/mxKeyHandler.js
@@ -0,0 +1,428 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxKeyHandler
+ *
+ * Event handler that listens to keystroke events. This is not a singleton,
+ * however, it is normally only required once if the target is the document
+ * element (default).
+ * 
+ * This handler installs a key event listener in the topmost DOM node and
+ * processes all events that originate from descandants of <mxGraph.container>
+ * or from the topmost DOM node. The latter means that all unhandled keystrokes
+ * are handled by this object regardless of the focused state of the <graph>.
+ * 
+ * Example:
+ * 
+ * The following example creates a key handler that listens to the delete key
+ * (46) and deletes the selection cells if the graph is enabled.
+ * 
+ * (code)
+ * var keyHandler = new mxKeyHandler(graph);
+ * keyHandler.bindKey(46, function(evt)
+ * {
+ *   if (graph.isEnabled())
+ *   {
+ *     graph.removeCells();
+ *   }
+ * });
+ * (end)
+ * 
+ * Keycodes:
+ * 
+ * See http://tinyurl.com/yp8jgl or http://tinyurl.com/229yqw for a list of
+ * keycodes or install a key event listener into the document element and print
+ * the key codes of the respective events to the console.
+ * 
+ * To support the Command key and the Control key on the Mac, the following
+ * code can be used.
+ *
+ * (code)
+ * keyHandler.getFunction = function(evt)
+ * {
+ *   if (evt != null)
+ *   {
+ *     return (mxEvent.isControlDown(evt) || (mxClient.IS_MAC && evt.metaKey)) ? this.controlKeys[evt.keyCode] : this.normalKeys[evt.keyCode];
+ *   }
+ *   
+ *   return null;
+ * };
+ * (end)
+ * 
+ * Constructor: mxKeyHandler
+ *
+ * Constructs an event handler that executes functions bound to specific
+ * keystrokes.
+ * 
+ * Parameters:
+ * 
+ * graph - Reference to the associated <mxGraph>.
+ * target - Optional reference to the event target. If null, the document
+ * element is used as the event target, that is, the object where the key
+ * event listener is installed.
+ */
+function mxKeyHandler(graph, target)
+{
+	if (graph != null)
+	{
+		this.graph = graph;
+		this.target = target || document.documentElement;
+		
+		// Creates the arrays to map from keycodes to functions
+		this.normalKeys = [];
+		this.shiftKeys = [];
+		this.controlKeys = [];
+		this.controlShiftKeys = [];
+		
+		this.keydownHandler = mxUtils.bind(this, function(evt)
+		{
+			this.keyDown(evt);
+		});
+
+		// Installs the keystroke listener in the target
+		mxEvent.addListener(this.target, 'keydown', this.keydownHandler);
+		
+		// Automatically deallocates memory in IE
+		if (mxClient.IS_IE)
+		{
+			mxEvent.addListener(window, 'unload',
+				mxUtils.bind(this, function()
+				{
+					this.destroy();
+				})
+			);
+		}
+	}
+};
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the <mxGraph> associated with this handler.
+ */
+mxKeyHandler.prototype.graph = null;
+
+/**
+ * Variable: target
+ * 
+ * Reference to the target DOM, that is, the DOM node where the key event
+ * listeners are installed.
+ */
+mxKeyHandler.prototype.target = null;
+
+/**
+ * Variable: normalKeys
+ * 
+ * Maps from keycodes to functions for non-pressed control keys.
+ */
+mxKeyHandler.prototype.normalKeys = null;
+
+/**
+ * Variable: shiftKeys
+ * 
+ * Maps from keycodes to functions for pressed shift keys.
+ */
+mxKeyHandler.prototype.shiftKeys = null;
+
+/**
+ * Variable: controlKeys
+ * 
+ * Maps from keycodes to functions for pressed control keys.
+ */
+mxKeyHandler.prototype.controlKeys = null;
+
+/**
+ * Variable: controlShiftKeys
+ * 
+ * Maps from keycodes to functions for pressed control and shift keys.
+ */
+mxKeyHandler.prototype.controlShiftKeys = null;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies if events are handled. Default is true.
+ */
+mxKeyHandler.prototype.enabled = true;
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns true if events are handled. This implementation returns
+ * <enabled>.
+ */
+mxKeyHandler.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ * 
+ * Enables or disables event handling by updating <enabled>.
+ * 
+ * Parameters:
+ * 
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxKeyHandler.prototype.setEnabled = function(enabled)
+{
+	this.enabled = enabled;
+};
+
+/**
+ * Function: bindKey
+ * 
+ * Binds the specified keycode to the given function. This binding is used
+ * if the control key is not pressed.
+ * 
+ * Parameters:
+ *
+ * code - Integer that specifies the keycode.
+ * funct - JavaScript function that takes the key event as an argument.
+ */
+mxKeyHandler.prototype.bindKey = function(code, funct)
+{
+	this.normalKeys[code] = funct;
+};
+
+/**
+ * Function: bindShiftKey
+ * 
+ * Binds the specified keycode to the given function. This binding is used
+ * if the shift key is pressed.
+ * 
+ * Parameters:
+ *
+ * code - Integer that specifies the keycode.
+ * funct - JavaScript function that takes the key event as an argument.
+ */
+mxKeyHandler.prototype.bindShiftKey = function(code, funct)
+{
+	this.shiftKeys[code] = funct;
+};
+
+/**
+ * Function: bindControlKey
+ * 
+ * Binds the specified keycode to the given function. This binding is used
+ * if the control key is pressed.
+ * 
+ * Parameters:
+ *
+ * code - Integer that specifies the keycode.
+ * funct - JavaScript function that takes the key event as an argument.
+ */
+mxKeyHandler.prototype.bindControlKey = function(code, funct)
+{
+	this.controlKeys[code] = funct;
+};
+
+/**
+ * Function: bindControlShiftKey
+ * 
+ * Binds the specified keycode to the given function. This binding is used
+ * if the control and shift key are pressed.
+ * 
+ * Parameters:
+ *
+ * code - Integer that specifies the keycode.
+ * funct - JavaScript function that takes the key event as an argument.
+ */
+mxKeyHandler.prototype.bindControlShiftKey = function(code, funct)
+{
+	this.controlShiftKeys[code] = funct;
+};
+
+/**
+ * Function: isControlDown
+ * 
+ * Returns true if the control key is pressed. This uses <mxEvent.isControlDown>.
+ * 
+ * Parameters:
+ * 
+ * evt - Key event whose control key pressed state should be returned.
+ */
+mxKeyHandler.prototype.isControlDown = function(evt)
+{
+	return mxEvent.isControlDown(evt);
+};
+
+/**
+ * Function: getFunction
+ * 
+ * Returns the function associated with the given key event or null if no
+ * function is associated with the given event.
+ * 
+ * Parameters:
+ * 
+ * evt - Key event whose associated function should be returned.
+ */
+mxKeyHandler.prototype.getFunction = function(evt)
+{
+	if (evt != null && !mxEvent.isAltDown(evt))
+	{
+		if (this.isControlDown(evt))
+		{
+			if (mxEvent.isShiftDown(evt))
+			{
+				return this.controlShiftKeys[evt.keyCode];
+			}
+			else
+			{
+				return this.controlKeys[evt.keyCode];
+			}
+		}
+		else
+		{
+			if (mxEvent.isShiftDown(evt))
+			{
+				return this.shiftKeys[evt.keyCode];
+			}
+			else
+			{
+				return this.normalKeys[evt.keyCode];
+			}
+		}
+	}
+	
+	return null;
+};
+	
+/**
+ * Function: isGraphEvent
+ * 
+ * Returns true if the event should be processed by this handler, that is,
+ * if the event source is either the target, one of its direct children, a
+ * descendant of the <mxGraph.container>, or the <mxGraph.cellEditor> of the
+ * <graph>.
+ * 
+ * Parameters:
+ * 
+ * evt - Key event that represents the keystroke.
+ */
+mxKeyHandler.prototype.isGraphEvent = function(evt)
+{
+	var source = mxEvent.getSource(evt);
+	
+	// Accepts events from the target object or
+	// in-place editing inside graph
+	if ((source == this.target || source.parentNode == this.target) ||
+		(this.graph.cellEditor != null && this.graph.cellEditor.isEventSource(evt)))
+	{
+		return true;
+	}
+	
+	// Accepts events from inside the container
+	return mxUtils.isAncestorNode(this.graph.container, source);
+};
+
+/**
+ * Function: keyDown
+ * 
+ * Handles the event by invoking the function bound to the respective keystroke
+ * if <isEnabledForEvent> returns true for the given event and if
+ * <isEventIgnored> returns false, except for escape for which
+ * <isEventIgnored> is not invoked.
+ * 
+ * Parameters:
+ * 
+ * evt - Key event that represents the keystroke.
+ */
+mxKeyHandler.prototype.keyDown = function(evt)
+{
+	if (this.isEnabledForEvent(evt))
+	{
+		// Cancels the editing if escape is pressed
+		if (evt.keyCode == 27 /* Escape */)
+		{
+			this.escape(evt);
+		}
+		
+		// Invokes the function for the keystroke
+		else if (!this.isEventIgnored(evt))
+		{
+			var boundFunction = this.getFunction(evt);
+			
+			if (boundFunction != null)
+			{
+				boundFunction(evt);
+				mxEvent.consume(evt);
+			}
+		}
+	}
+};
+
+/**
+ * Function: isEnabledForEvent
+ * 
+ * Returns true if the given event should be handled. <isEventIgnored> is
+ * called later if the event is not an escape key stroke, in which case
+ * <escape> is called. This implementation returns true if <isEnabled>
+ * returns true for both, this handler and <graph>, if the event is not
+ * consumed and if <isGraphEvent> returns true.
+ * 
+ * Parameters:
+ * 
+ * evt - Key event that represents the keystroke.
+ */
+mxKeyHandler.prototype.isEnabledForEvent = function(evt)
+{
+	return (this.graph.isEnabled() && !mxEvent.isConsumed(evt) &&
+		this.isGraphEvent(evt) && this.isEnabled());
+};
+
+/**
+ * Function: isEventIgnored
+ * 
+ * Returns true if the given keystroke should be ignored. This returns
+ * graph.isEditing().
+ * 
+ * Parameters:
+ * 
+ * evt - Key event that represents the keystroke.
+ */
+mxKeyHandler.prototype.isEventIgnored = function(evt)
+{
+	return this.graph.isEditing();
+};
+
+/**
+ * Function: escape
+ * 
+ * Hook to process ESCAPE keystrokes. This implementation invokes
+ * <mxGraph.stopEditing> to cancel the current editing, connecting
+ * and/or other ongoing modifications.
+ * 
+ * Parameters:
+ * 
+ * evt - Key event that represents the keystroke. Possible keycode in this
+ * case is 27 (ESCAPE).
+ */
+mxKeyHandler.prototype.escape = function(evt)
+{
+	if (this.graph.isEscapeEnabled())
+	{
+		this.graph.escape(evt);
+	}
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the handler and all its references into the DOM. This does
+ * normally not need to be called, it is called automatically when the
+ * window unloads (in IE).
+ */
+mxKeyHandler.prototype.destroy = function()
+{
+	if (this.target != null && this.keydownHandler != null)
+	{
+		mxEvent.removeListener(this.target, 'keydown', this.keydownHandler);
+		this.keydownHandler = null;
+	}
+	
+	this.target = null;
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/handler/mxPanningHandler.js b/airavata-kubernetes/web-console/src/assets/js/handler/mxPanningHandler.js
new file mode 100644
index 0000000..189e676
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/handler/mxPanningHandler.js
@@ -0,0 +1,462 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxPanningHandler
+ * 
+ * Event handler that pans and creates popupmenus. To use the left
+ * mousebutton for panning without interfering with cell moving and
+ * resizing, use <isUseLeftButton> and <isIgnoreCell>. For grid size
+ * steps while panning, use <useGrid>. This handler is built-into
+ * <mxGraph.panningHandler> and enabled using <mxGraph.setPanning>.
+ * 
+ * Constructor: mxPanningHandler
+ * 
+ * Constructs an event handler that creates a <mxPopupMenu>
+ * and pans the graph.
+ *
+ * Event: mxEvent.PAN_START
+ *
+ * Fires when the panning handler changes its <active> state to true. The
+ * <code>event</code> property contains the corresponding <mxMouseEvent>.
+ *
+ * Event: mxEvent.PAN
+ *
+ * Fires while handle is processing events. The <code>event</code> property contains
+ * the corresponding <mxMouseEvent>.
+ *
+ * Event: mxEvent.PAN_END
+ *
+ * Fires when the panning handler changes its <active> state to false. The
+ * <code>event</code> property contains the corresponding <mxMouseEvent>.
+ */
+function mxPanningHandler(graph)
+{
+	if (graph != null)
+	{
+		this.graph = graph;
+		this.graph.addMouseListener(this);
+
+		// Handles force panning event
+		this.forcePanningHandler = mxUtils.bind(this, function(sender, evt)
+		{
+			var evtName = evt.getProperty('eventName');
+			var me = evt.getProperty('event');
+			
+			if (evtName == mxEvent.MOUSE_DOWN && this.isForcePanningEvent(me))
+			{
+				this.start(me);
+				this.active = true;
+				this.fireEvent(new mxEventObject(mxEvent.PAN_START, 'event', me));
+				me.consume();
+			}
+		});
+
+		this.graph.addListener(mxEvent.FIRE_MOUSE_EVENT, this.forcePanningHandler);
+		
+		// Handles pinch gestures
+		this.gestureHandler = mxUtils.bind(this, function(sender, eo)
+		{
+			if (this.isPinchEnabled())
+			{
+				var evt = eo.getProperty('event');
+				
+				if (!mxEvent.isConsumed(evt) && evt.type == 'gesturestart')
+				{
+					this.initialScale = this.graph.view.scale;
+				
+					// Forces start of panning when pinch gesture starts
+					if (!this.active && this.mouseDownEvent != null)
+					{
+						this.start(this.mouseDownEvent);
+						this.mouseDownEvent = null;
+					}
+				}
+				else if (evt.type == 'gestureend' && this.initialScale != null)
+				{
+					this.initialScale = null;
+				}
+				
+				if (this.initialScale != null)
+				{
+					var value = Math.round(this.initialScale * evt.scale * 100) / 100;
+					
+					if (this.minScale != null)
+					{
+						value = Math.max(this.minScale, value);
+					}
+					
+					if (this.maxScale != null)
+					{
+						value = Math.min(this.maxScale, value);
+					}
+	
+					if (this.graph.view.scale != value)
+					{
+						this.graph.zoomTo(value);
+						mxEvent.consume(evt);
+					}
+				}
+			}
+		});
+		
+		this.graph.addListener(mxEvent.GESTURE, this.gestureHandler);
+	}
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxPanningHandler.prototype = new mxEventSource();
+mxPanningHandler.prototype.constructor = mxPanningHandler;
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxPanningHandler.prototype.graph = null;
+
+/**
+ * Variable: useLeftButtonForPanning
+ * 
+ * Specifies if panning should be active for the left mouse button.
+ * Setting this to true may conflict with <mxRubberband>. Default is false.
+ */
+mxPanningHandler.prototype.useLeftButtonForPanning = false;
+
+/**
+ * Variable: usePopupTrigger
+ * 
+ * Specifies if <mxEvent.isPopupTrigger> should also be used for panning.
+ */
+mxPanningHandler.prototype.usePopupTrigger = true;
+
+/**
+ * Variable: ignoreCell
+ * 
+ * Specifies if panning should be active even if there is a cell under the
+ * mousepointer. Default is false.
+ */
+mxPanningHandler.prototype.ignoreCell = false;
+
+/**
+ * Variable: previewEnabled
+ * 
+ * Specifies if the panning should be previewed. Default is true.
+ */
+mxPanningHandler.prototype.previewEnabled = true;
+
+/**
+ * Variable: useGrid
+ * 
+ * Specifies if the panning steps should be aligned to the grid size.
+ * Default is false.
+ */
+mxPanningHandler.prototype.useGrid = false;
+
+/**
+ * Variable: panningEnabled
+ * 
+ * Specifies if panning should be enabled. Default is true.
+ */
+mxPanningHandler.prototype.panningEnabled = true;
+
+/**
+ * Variable: pinchEnabled
+ * 
+ * Specifies if pinch gestures should be handled as zoom. Default is true.
+ */
+mxPanningHandler.prototype.pinchEnabled = true;
+
+/**
+ * Variable: maxScale
+ * 
+ * Specifies the maximum scale. Default is 8.
+ */
+mxPanningHandler.prototype.maxScale = 8;
+
+/**
+ * Variable: minScale
+ * 
+ * Specifies the minimum scale. Default is 0.01.
+ */
+mxPanningHandler.prototype.minScale = 0.01;
+
+/**
+ * Variable: dx
+ * 
+ * Holds the current horizontal offset.
+ */
+mxPanningHandler.prototype.dx = null;
+
+/**
+ * Variable: dy
+ * 
+ * Holds the current vertical offset.
+ */
+mxPanningHandler.prototype.dy = null;
+
+/**
+ * Variable: startX
+ * 
+ * Holds the x-coordinate of the start point.
+ */
+mxPanningHandler.prototype.startX = 0;
+
+/**
+ * Variable: startY
+ * 
+ * Holds the y-coordinate of the start point.
+ */
+mxPanningHandler.prototype.startY = 0;
+
+/**
+ * Function: isActive
+ * 
+ * Returns true if the handler is currently active.
+ */
+mxPanningHandler.prototype.isActive = function()
+{
+	return this.active || this.initialScale != null;
+};
+
+/**
+ * Function: isPanningEnabled
+ * 
+ * Returns <panningEnabled>.
+ */
+mxPanningHandler.prototype.isPanningEnabled = function()
+{
+	return this.panningEnabled;
+};
+
+/**
+ * Function: setPanningEnabled
+ * 
+ * Sets <panningEnabled>.
+ */
+mxPanningHandler.prototype.setPanningEnabled = function(value)
+{
+	this.panningEnabled = value;
+};
+
+/**
+ * Function: isPinchEnabled
+ * 
+ * Returns <pinchEnabled>.
+ */
+mxPanningHandler.prototype.isPinchEnabled = function()
+{
+	return this.pinchEnabled;
+};
+
+/**
+ * Function: setPinchEnabled
+ * 
+ * Sets <pinchEnabled>.
+ */
+mxPanningHandler.prototype.setPinchEnabled = function(value)
+{
+	this.pinchEnabled = value;
+};
+
+/**
+ * Function: isPanningTrigger
+ * 
+ * Returns true if the given event is a panning trigger for the optional
+ * given cell. This returns true if control-shift is pressed or if
+ * <usePopupTrigger> is true and the event is a popup trigger.
+ */
+mxPanningHandler.prototype.isPanningTrigger = function(me)
+{
+	var evt = me.getEvent();
+	
+	return (this.useLeftButtonForPanning && me.getState() == null &&
+			mxEvent.isLeftMouseButton(evt)) || (mxEvent.isControlDown(evt) &&
+			mxEvent.isShiftDown(evt)) || (this.usePopupTrigger && mxEvent.isPopupTrigger(evt));
+};
+
+/**
+ * Function: isForcePanningEvent
+ * 
+ * Returns true if the given <mxMouseEvent> should start panning. This
+ * implementation always returns true if <ignoreCell> is true or for
+ * multi touch events.
+ */
+mxPanningHandler.prototype.isForcePanningEvent = function(me)
+{
+	return this.ignoreCell || mxEvent.isMultiTouchEvent(me.getEvent());
+};
+
+/**
+ * Function: mouseDown
+ * 
+ * Handles the event by initiating the panning. By consuming the event all
+ * subsequent events of the gesture are redirected to this handler.
+ */
+mxPanningHandler.prototype.mouseDown = function(sender, me)
+{
+	this.mouseDownEvent = me;
+	
+	if (!me.isConsumed() && this.isPanningEnabled() && !this.active && this.isPanningTrigger(me))
+	{
+		this.start(me);
+		this.consumePanningTrigger(me);
+	}
+};
+
+/**
+ * Function: start
+ * 
+ * Starts panning at the given event.
+ */
+mxPanningHandler.prototype.start = function(me)
+{
+	this.dx0 = -this.graph.container.scrollLeft;
+	this.dy0 = -this.graph.container.scrollTop;
+
+	// Stores the location of the trigger event
+	this.startX = me.getX();
+	this.startY = me.getY();
+	this.dx = null;
+	this.dy = null;
+	
+	this.panningTrigger = true;
+};
+
+/**
+ * Function: consumePanningTrigger
+ * 
+ * Consumes the given <mxMouseEvent> if it was a panning trigger in
+ * <mouseDown>. The default is to invoke <mxMouseEvent.consume>. Note that this
+ * will block any further event processing. If you haven't disabled built-in
+ * context menus and require immediate selection of the cell on mouseDown in
+ * Safari and/or on the Mac, then use the following code:
+ * 
+ * (code)
+ * mxPanningHandler.prototype.consumePanningTrigger = function(me)
+ * {
+ *   if (me.evt.preventDefault)
+ *   {
+ *     me.evt.preventDefault();
+ *   }
+ *   
+ *   // Stops event processing in IE
+ *   me.evt.returnValue = false;
+ *   
+ *   // Sets local consumed state
+ *   if (!mxClient.IS_SF && !mxClient.IS_MAC)
+ *   {
+ *     me.consumed = true;
+ *   }
+ * };
+ * (end)
+ */
+mxPanningHandler.prototype.consumePanningTrigger = function(me)
+{
+	me.consume();
+};
+
+/**
+ * Function: mouseMove
+ * 
+ * Handles the event by updating the panning on the graph.
+ */
+mxPanningHandler.prototype.mouseMove = function(sender, me)
+{
+	this.dx = me.getX() - this.startX;
+	this.dy = me.getY() - this.startY;
+	
+	if (this.active)
+	{
+		if (this.previewEnabled)
+		{
+			// Applies the grid to the panning steps
+			if (this.useGrid)
+			{
+				this.dx = this.graph.snap(this.dx);
+				this.dy = this.graph.snap(this.dy);
+			}
+			
+			this.graph.panGraph(this.dx + this.dx0, this.dy + this.dy0);
+		}
+
+		this.fireEvent(new mxEventObject(mxEvent.PAN, 'event', me));
+	}
+	else if (this.panningTrigger)
+	{
+		var tmp = this.active;
+
+		// Panning is activated only if the mouse is moved
+		// beyond the graph tolerance
+		this.active = Math.abs(this.dx) > this.graph.tolerance || Math.abs(this.dy) > this.graph.tolerance;
+
+		if (!tmp && this.active)
+		{
+			this.fireEvent(new mxEventObject(mxEvent.PAN_START, 'event', me));
+		}
+	}
+	
+	if (this.active || this.panningTrigger)
+	{
+		me.consume();
+	}
+};
+
+/**
+ * Function: mouseUp
+ * 
+ * Handles the event by setting the translation on the view or showing the
+ * popupmenu.
+ */
+mxPanningHandler.prototype.mouseUp = function(sender, me)
+{
+	if (this.active)
+	{
+		if (this.dx != null && this.dy != null)
+		{
+			// Ignores if scrollbars have been used for panning
+			if (!this.graph.useScrollbarsForPanning || !mxUtils.hasScrollbars(this.graph.container))
+			{
+				var scale = this.graph.getView().scale;
+				var t = this.graph.getView().translate;
+				this.graph.panGraph(0, 0);
+				this.panGraph(t.x + this.dx / scale, t.y + this.dy / scale);
+			}
+			
+			me.consume();
+		}
+		
+		this.fireEvent(new mxEventObject(mxEvent.PAN_END, 'event', me));
+	}
+	
+	this.panningTrigger = false;
+	this.mouseDownEvent = null;
+	this.active = false;
+	this.dx = null;
+	this.dy = null;
+};
+
+/**
+ * Function: panGraph
+ * 
+ * Pans <graph> by the given amount.
+ */
+mxPanningHandler.prototype.panGraph = function(dx, dy)
+{
+	this.graph.getView().setTranslate(dx, dy);
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the handler and all its resources and DOM nodes.
+ */
+mxPanningHandler.prototype.destroy = function()
+{
+	this.graph.removeMouseListener(this);
+	this.graph.removeListener(this.forcePanningHandler);
+	this.graph.removeListener(this.gestureHandler);
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/handler/mxPopupMenuHandler.js b/airavata-kubernetes/web-console/src/assets/js/handler/mxPopupMenuHandler.js
new file mode 100644
index 0000000..2388319
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/handler/mxPopupMenuHandler.js
@@ -0,0 +1,218 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxPopupMenuHandler
+ * 
+ * Event handler that creates popupmenus.
+ * 
+ * Constructor: mxPopupMenuHandler
+ * 
+ * Constructs an event handler that creates a <mxPopupMenu>.
+ */
+function mxPopupMenuHandler(graph, factoryMethod)
+{
+	if (graph != null)
+	{
+		this.graph = graph;
+		this.factoryMethod = factoryMethod;
+		this.graph.addMouseListener(this);
+		
+		// Does not show menu if any touch gestures take place after the trigger
+		this.gestureHandler = mxUtils.bind(this, function(sender, eo)
+		{
+			this.inTolerance = false;
+		});
+		
+		this.graph.addListener(mxEvent.GESTURE, this.gestureHandler);
+		
+		this.init();
+	}
+};
+
+/**
+ * Extends mxPopupMenu.
+ */
+mxPopupMenuHandler.prototype = new mxPopupMenu();
+mxPopupMenuHandler.prototype.constructor = mxPopupMenuHandler;
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxPopupMenuHandler.prototype.graph = null;
+
+/**
+ * Variable: selectOnPopup
+ * 
+ * Specifies if cells should be selected if a popupmenu is displayed for
+ * them. Default is true.
+ */
+mxPopupMenuHandler.prototype.selectOnPopup = true;
+
+/**
+ * Variable: clearSelectionOnBackground
+ * 
+ * Specifies if cells should be deselected if a popupmenu is displayed for
+ * the diagram background. Default is true.
+ */
+mxPopupMenuHandler.prototype.clearSelectionOnBackground = true;
+
+/**
+ * Variable: triggerX
+ * 
+ * X-coordinate of the mouse down event.
+ */
+mxPopupMenuHandler.prototype.triggerX = null;
+
+/**
+ * Variable: triggerY
+ * 
+ * Y-coordinate of the mouse down event.
+ */
+mxPopupMenuHandler.prototype.triggerY = null;
+
+/**
+ * Variable: screenX
+ * 
+ * Screen X-coordinate of the mouse down event.
+ */
+mxPopupMenuHandler.prototype.screenX = null;
+
+/**
+ * Variable: screenY
+ * 
+ * Screen Y-coordinate of the mouse down event.
+ */
+mxPopupMenuHandler.prototype.screenY = null;
+
+/**
+ * Function: init
+ * 
+ * Initializes the shapes required for this vertex handler.
+ */
+mxPopupMenuHandler.prototype.init = function()
+{
+	// Supercall
+	mxPopupMenu.prototype.init.apply(this);
+
+	// Hides the tooltip if the mouse is over
+	// the context menu
+	mxEvent.addGestureListeners(this.div, mxUtils.bind(this, function(evt)
+	{
+		this.graph.tooltipHandler.hide();
+	}));
+};
+
+/**
+ * Function: isSelectOnPopup
+ * 
+ * Hook for returning if a cell should be selected for a given <mxMouseEvent>.
+ * This implementation returns <selectOnPopup>.
+ */
+mxPopupMenuHandler.prototype.isSelectOnPopup = function(me)
+{
+	return this.selectOnPopup;
+};
+
+/**
+ * Function: mouseDown
+ * 
+ * Handles the event by initiating the panning. By consuming the event all
+ * subsequent events of the gesture are redirected to this handler.
+ */
+mxPopupMenuHandler.prototype.mouseDown = function(sender, me)
+{
+	if (this.isEnabled() && !mxEvent.isMultiTouchEvent(me.getEvent()))
+	{
+		// Hides the popupmenu if is is being displayed
+		this.hideMenu();
+		this.triggerX = me.getGraphX();
+		this.triggerY = me.getGraphY();
+		this.screenX = mxEvent.getMainEvent(me.getEvent()).screenX;
+		this.screenY = mxEvent.getMainEvent(me.getEvent()).screenY;
+		this.popupTrigger = this.isPopupTrigger(me);
+		this.inTolerance = true;
+	}
+};
+
+/**
+ * Function: mouseMove
+ * 
+ * Handles the event by updating the panning on the graph.
+ */
+mxPopupMenuHandler.prototype.mouseMove = function(sender, me)
+{
+	// Popup trigger may change on mouseUp so ignore it
+	if (this.inTolerance && this.screenX != null && this.screenY != null)
+	{
+		if (Math.abs(mxEvent.getMainEvent(me.getEvent()).screenX - this.screenX) > this.graph.tolerance ||
+			Math.abs(mxEvent.getMainEvent(me.getEvent()).screenY - this.screenY) > this.graph.tolerance)
+		{
+			this.inTolerance = false;
+		}
+	}
+};
+
+/**
+ * Function: mouseUp
+ * 
+ * Handles the event by setting the translation on the view or showing the
+ * popupmenu.
+ */
+mxPopupMenuHandler.prototype.mouseUp = function(sender, me)
+{
+	if (this.popupTrigger && this.inTolerance && this.triggerX != null && this.triggerY != null)
+	{
+		var cell = this.getCellForPopupEvent(me);
+
+		// Selects the cell for which the context menu is being displayed
+		if (this.graph.isEnabled() && this.isSelectOnPopup(me) &&
+			cell != null && !this.graph.isCellSelected(cell))
+		{
+			this.graph.setSelectionCell(cell);
+		}
+		else if (this.clearSelectionOnBackground && cell == null)
+		{
+			this.graph.clearSelection();
+		}
+		
+		// Hides the tooltip if there is one
+		this.graph.tooltipHandler.hide();
+
+		// Menu is shifted by 1 pixel so that the mouse up event
+		// is routed via the underlying shape instead of the DIV
+		var origin = mxUtils.getScrollOrigin();
+		this.popup(me.getX() + origin.x + 1, me.getY() + origin.y + 1, cell, me.getEvent());
+		me.consume();
+	}
+	
+	this.popupTrigger = false;
+	this.inTolerance = false;
+};
+
+/**
+ * Function: getCellForPopupEvent
+ * 
+ * Hook to return the cell for the mouse up popup trigger handling.
+ */
+mxPopupMenuHandler.prototype.getCellForPopupEvent = function(me)
+{
+	return me.getCell();
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the handler and all its resources and DOM nodes.
+ */
+mxPopupMenuHandler.prototype.destroy = function()
+{
+	this.graph.removeMouseListener(this);
+	this.graph.removeListener(this.gestureHandler);
+	
+	// Supercall
+	mxPopupMenu.prototype.destroy.apply(this);
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/handler/mxRubberband.js b/airavata-kubernetes/web-console/src/assets/js/handler/mxRubberband.js
new file mode 100644
index 0000000..8d29fdb
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/handler/mxRubberband.js
@@ -0,0 +1,401 @@
+/**
+ * Copyright (c) 2006-2016, JGraph Ltd
+ * Copyright (c) 2006-2016, Gaudenz Alder
+ */
+/**
+ * Class: mxRubberband
+ * 
+ * Event handler that selects rectangular regions. This is not built-into
+ * <mxGraph>. To enable rubberband selection in a graph, use the following code.
+ * 
+ * Example:
+ * 
+ * (code)
+ * var rubberband = new mxRubberband(graph);
+ * (end)
+ * 
+ * Constructor: mxRubberband
+ * 
+ * Constructs an event handler that selects rectangular regions in the graph
+ * using rubberband selection.
+ */
+function mxRubberband(graph)
+{
+	if (graph != null)
+	{
+		this.graph = graph;
+		this.graph.addMouseListener(this);
+
+		// Handles force rubberband event
+		this.forceRubberbandHandler = mxUtils.bind(this, function(sender, evt)
+		{
+			var evtName = evt.getProperty('eventName');
+			var me = evt.getProperty('event');
+			
+			if (evtName == mxEvent.MOUSE_DOWN && this.isForceRubberbandEvent(me))
+			{
+				var offset = mxUtils.getOffset(this.graph.container);
+				var origin = mxUtils.getScrollOrigin(this.graph.container);
+				origin.x -= offset.x;
+				origin.y -= offset.y;
+				this.start(me.getX() + origin.x, me.getY() + origin.y);
+				me.consume(false);
+			}
+		});
+		
+		this.graph.addListener(mxEvent.FIRE_MOUSE_EVENT, this.forceRubberbandHandler);
+		
+		// Repaints the marquee after autoscroll
+		this.panHandler = mxUtils.bind(this, function()
+		{
+			this.repaint();
+		});
+		
+		this.graph.addListener(mxEvent.PAN, this.panHandler);
+		
+		// Does not show menu if any touch gestures take place after the trigger
+		this.gestureHandler = mxUtils.bind(this, function(sender, eo)
+		{
+			if (this.first != null)
+			{
+				this.reset();
+			}
+		});
+		
+		this.graph.addListener(mxEvent.GESTURE, this.gestureHandler);
+		
+		// Automatic deallocation of memory
+		if (mxClient.IS_IE)
+		{
+			mxEvent.addListener(window, 'unload',
+				mxUtils.bind(this, function()
+				{
+					this.destroy();
+				})
+			);
+		}
+	}
+};
+
+/**
+ * Variable: defaultOpacity
+ * 
+ * Specifies the default opacity to be used for the rubberband div. Default
+ * is 20.
+ */
+mxRubberband.prototype.defaultOpacity = 20;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies if events are handled. Default is true.
+ */
+mxRubberband.prototype.enabled = true;
+
+/**
+ * Variable: div
+ * 
+ * Holds the DIV element which is currently visible.
+ */
+mxRubberband.prototype.div = null;
+
+/**
+ * Variable: sharedDiv
+ * 
+ * Holds the DIV element which is used to display the rubberband.
+ */
+mxRubberband.prototype.sharedDiv = null;
+
+/**
+ * Variable: currentX
+ * 
+ * Holds the value of the x argument in the last call to <update>.
+ */
+mxRubberband.prototype.currentX = 0;
+
+/**
+ * Variable: currentY
+ * 
+ * Holds the value of the y argument in the last call to <update>.
+ */
+mxRubberband.prototype.currentY = 0;
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns true if events are handled. This implementation returns
+ * <enabled>.
+ */
+mxRubberband.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+		
+/**
+ * Function: setEnabled
+ * 
+ * Enables or disables event handling. This implementation updates
+ * <enabled>.
+ */
+mxRubberband.prototype.setEnabled = function(enabled)
+{
+	this.enabled = enabled;
+};
+
+/**
+ * Function: isForceRubberbandEvent
+ * 
+ * Returns true if the given <mxMouseEvent> should start rubberband selection.
+ * This implementation returns true if the alt key is pressed.
+ */
+mxRubberband.prototype.isForceRubberbandEvent = function(me)
+{
+	return mxEvent.isAltDown(me.getEvent());
+};
+
+/**
+ * Function: mouseDown
+ * 
+ * Handles the event by initiating a rubberband selection. By consuming the
+ * event all subsequent events of the gesture are redirected to this
+ * handler.
+ */
+mxRubberband.prototype.mouseDown = function(sender, me)
+{
+	if (!me.isConsumed() && this.isEnabled() && this.graph.isEnabled() &&
+		me.getState() == null && !mxEvent.isMultiTouchEvent(me.getEvent()))
+	{
+		var offset = mxUtils.getOffset(this.graph.container);
+		var origin = mxUtils.getScrollOrigin(this.graph.container);
+		origin.x -= offset.x;
+		origin.y -= offset.y;
+		this.start(me.getX() + origin.x, me.getY() + origin.y);
+
+		// Does not prevent the default for this event so that the
+		// event processing chain is still executed even if we start
+		// rubberbanding. This is required eg. in ExtJs to hide the
+		// current context menu. In mouseMove we'll make sure we're
+		// not selecting anything while we're rubberbanding.
+		me.consume(false);
+	}
+};
+
+/**
+ * Function: start
+ * 
+ * Sets the start point for the rubberband selection.
+ */
+mxRubberband.prototype.start = function(x, y)
+{
+	this.first = new mxPoint(x, y);
+
+	var container = this.graph.container;
+	
+	function createMouseEvent(evt)
+	{
+		var me = new mxMouseEvent(evt);
+		var pt = mxUtils.convertPoint(container, me.getX(), me.getY());
+		
+		me.graphX = pt.x;
+		me.graphY = pt.y;
+		
+		return me;
+	};
+
+	this.dragHandler = mxUtils.bind(this, function(evt)
+	{
+		this.mouseMove(this.graph, createMouseEvent(evt));
+	});
+
+	this.dropHandler = mxUtils.bind(this, function(evt)
+	{
+		this.mouseUp(this.graph, createMouseEvent(evt));
+	});
+
+	// Workaround for rubberband stopping if the mouse leaves the container in Firefox
+	if (mxClient.IS_FF)
+	{
+		mxEvent.addGestureListeners(document, null, this.dragHandler, this.dropHandler);
+	}
+};
+
+/**
+ * Function: mouseMove
+ * 
+ * Handles the event by updating therubberband selection.
+ */
+mxRubberband.prototype.mouseMove = function(sender, me)
+{
+	if (!me.isConsumed() && this.first != null)
+	{
+		var origin = mxUtils.getScrollOrigin(this.graph.container);
+		var offset = mxUtils.getOffset(this.graph.container);
+		origin.x -= offset.x;
+		origin.y -= offset.y;
+		var x = me.getX() + origin.x;
+		var y = me.getY() + origin.y;
+		var dx = this.first.x - x;
+		var dy = this.first.y - y;
+		var tol = this.graph.tolerance;
+		
+		if (this.div != null || Math.abs(dx) > tol ||  Math.abs(dy) > tol)
+		{
+			if (this.div == null)
+			{
+				this.div = this.createShape();
+			}
+			
+			// Clears selection while rubberbanding. This is required because
+			// the event is not consumed in mouseDown.
+			mxUtils.clearSelection();
+			
+			this.update(x, y);
+			me.consume();
+		}
+	}
+};
+
+/**
+ * Function: createShape
+ * 
+ * Creates the rubberband selection shape.
+ */
+mxRubberband.prototype.createShape = function()
+{
+	if (this.sharedDiv == null)
+	{
+		this.sharedDiv = document.createElement('div');
+		this.sharedDiv.className = 'mxRubberband';
+		mxUtils.setOpacity(this.sharedDiv, this.defaultOpacity);
+	}
+
+	this.graph.container.appendChild(this.sharedDiv);
+		
+	return this.sharedDiv;
+};
+
+/**
+ * Function: isActive
+ * 
+ * Returns true if this handler is active.
+ */
+mxRubberband.prototype.isActive = function(sender, me)
+{
+	return this.div != null && this.div.style.display != 'none';
+};
+
+/**
+ * Function: mouseUp
+ * 
+ * Handles the event by selecting the region of the rubberband using
+ * <mxGraph.selectRegion>.
+ */
+mxRubberband.prototype.mouseUp = function(sender, me)
+{
+	var active = this.isActive();
+	this.reset();
+	
+	if (active)
+	{
+		this.execute(me.getEvent());
+		me.consume();
+	}
+};
+
+/**
+ * Function: execute
+ * 
+ * Resets the state of this handler and selects the current region
+ * for the given event.
+ */
+mxRubberband.prototype.execute = function(evt)
+{
+	var rect = new mxRectangle(this.x, this.y, this.width, this.height);
+	this.graph.selectRegion(rect, evt);
+};
+
+/**
+ * Function: reset
+ * 
+ * Resets the state of the rubberband selection.
+ */
+mxRubberband.prototype.reset = function()
+{
+	if (this.div != null)
+	{
+		this.div.parentNode.removeChild(this.div);
+	}
+
+	mxEvent.removeGestureListeners(document, null, this.dragHandler, this.dropHandler);
+	this.dragHandler = null;
+	this.dropHandler = null;
+	
+	this.currentX = 0;
+	this.currentY = 0;
+	this.first = null;
+	this.div = null;
+};
+
+/**
+ * Function: update
+ * 
+ * Sets <currentX> and <currentY> and calls <repaint>.
+ */
+mxRubberband.prototype.update = function(x, y)
+{
+	this.currentX = x;
+	this.currentY = y;
+	
+	this.repaint();
+};
+
+/**
+ * Function: repaint
+ * 
+ * Computes the bounding box and updates the style of the <div>.
+ */
+mxRubberband.prototype.repaint = function()
+{
+	if (this.div != null)
+	{
+		var x = this.currentX - this.graph.panDx;
+		var y = this.currentY - this.graph.panDy;
+		
+		this.x = Math.min(this.first.x, x);
+		this.y = Math.min(this.first.y, y);
+		this.width = Math.max(this.first.x, x) - this.x;
+		this.height =  Math.max(this.first.y, y) - this.y;
+
+		var dx = (mxClient.IS_VML) ? this.graph.panDx : 0;
+		var dy = (mxClient.IS_VML) ? this.graph.panDy : 0;
+		
+		this.div.style.left = (this.x + dx) + 'px';
+		this.div.style.top = (this.y + dy) + 'px';
+		this.div.style.width = Math.max(1, this.width) + 'px';
+		this.div.style.height = Math.max(1, this.height) + 'px';
+	}
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the handler and all its resources and DOM nodes. This does
+ * normally not need to be called, it is called automatically when the
+ * window unloads.
+ */
+mxRubberband.prototype.destroy = function()
+{
+	if (!this.destroyed)
+	{
+		this.destroyed = true;
+		this.graph.removeMouseListener(this);
+		this.graph.removeListener(this.forceRubberbandHandler);
+		this.graph.removeListener(this.panHandler);
+		this.reset();
+		
+		if (this.sharedDiv != null)
+		{
+			this.sharedDiv = null;
+		}
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/handler/mxSelectionCellsHandler.js b/airavata-kubernetes/web-console/src/assets/js/handler/mxSelectionCellsHandler.js
new file mode 100644
index 0000000..432e237
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/handler/mxSelectionCellsHandler.js
@@ -0,0 +1,287 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxSelectionCellsHandler
+ * 
+ * An event handler that manages cell handlers and invokes their mouse event
+ * processing functions.
+ * 
+ * Group: Events
+ * 
+ * Event: mxEvent.ADD
+ * 
+ * Fires if a cell has been added to the selection. The <code>state</code>
+ * property contains the <mxCellState> that has been added.
+ * 
+ * Event: mxEvent.REMOVE
+ * 
+ * Fires if a cell has been remove from the selection. The <code>state</code>
+ * property contains the <mxCellState> that has been removed.
+ * 
+ * Parameters:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ */
+function mxSelectionCellsHandler(graph)
+{
+	mxEventSource.call(this);
+	
+	this.graph = graph;
+	this.handlers = new mxDictionary();
+	this.graph.addMouseListener(this);
+	
+	this.refreshHandler = mxUtils.bind(this, function(sender, evt)
+	{
+		if (this.isEnabled())
+		{
+			this.refresh();
+		}
+	});
+	
+	this.graph.getSelectionModel().addListener(mxEvent.CHANGE, this.refreshHandler);
+	this.graph.getModel().addListener(mxEvent.CHANGE, this.refreshHandler);
+	this.graph.getView().addListener(mxEvent.SCALE, this.refreshHandler);
+	this.graph.getView().addListener(mxEvent.TRANSLATE, this.refreshHandler);
+	this.graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE, this.refreshHandler);
+	this.graph.getView().addListener(mxEvent.DOWN, this.refreshHandler);
+	this.graph.getView().addListener(mxEvent.UP, this.refreshHandler);
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxUtils.extend(mxSelectionCellsHandler, mxEventSource);
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxSelectionCellsHandler.prototype.graph = null;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies if events are handled. Default is true.
+ */
+mxSelectionCellsHandler.prototype.enabled = true;
+
+/**
+ * Variable: refreshHandler
+ * 
+ * Keeps a reference to an event listener for later removal.
+ */
+mxSelectionCellsHandler.prototype.refreshHandler = null;
+
+/**
+ * Variable: maxHandlers
+ * 
+ * Defines the maximum number of handlers to paint individually. Default is 100.
+ */
+mxSelectionCellsHandler.prototype.maxHandlers = 100;
+
+/**
+ * Variable: handlers
+ * 
+ * <mxDictionary> that maps from cells to handlers.
+ */
+mxSelectionCellsHandler.prototype.handlers = null;
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns <enabled>.
+ */
+mxSelectionCellsHandler.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ * 
+ * Sets <enabled>.
+ */
+mxSelectionCellsHandler.prototype.setEnabled = function(value)
+{
+	this.enabled = value;
+};
+
+/**
+ * Function: getHandler
+ * 
+ * Returns the handler for the given cell.
+ */
+mxSelectionCellsHandler.prototype.getHandler = function(cell)
+{
+	return this.handlers.get(cell);
+};
+
+/**
+ * Function: reset
+ * 
+ * Resets all handlers.
+ */
+mxSelectionCellsHandler.prototype.reset = function()
+{
+	this.handlers.visit(function(key, handler)
+	{
+		handler.reset.apply(handler);
+	});
+};
+
+/**
+ * Function: refresh
+ * 
+ * Reloads or updates all handlers.
+ */
+mxSelectionCellsHandler.prototype.refresh = function()
+{
+	// Removes all existing handlers
+	var oldHandlers = this.handlers;
+	this.handlers = new mxDictionary();
+	
+	// Creates handles for all selection cells
+	var tmp = this.graph.getSelectionCells();
+
+	for (var i = 0; i < tmp.length; i++)
+	{
+		var state = this.graph.view.getState(tmp[i]);
+
+		if (state != null)
+		{
+			var handler = oldHandlers.remove(tmp[i]);
+
+			if (handler != null)
+			{
+				if (handler.state != state)
+				{
+					handler.destroy();
+					handler = null;
+				}
+				else
+				{
+					if (handler.refresh != null)
+					{
+						handler.refresh();
+					}
+					
+					handler.redraw();
+				}
+			}
+			
+			if (handler == null)
+			{
+				handler = this.graph.createHandler(state);
+				this.fireEvent(new mxEventObject(mxEvent.ADD, 'state', state));
+			}
+			
+			if (handler != null)
+			{
+				this.handlers.put(tmp[i], handler);
+			}
+		}
+	}
+	
+	// Destroys all unused handlers
+	oldHandlers.visit(mxUtils.bind(this, function(key, handler)
+	{
+		this.fireEvent(new mxEventObject(mxEvent.REMOVE, 'state', handler.state));
+		handler.destroy();
+	}));
+};
+
+/**
+ * Function: updateHandler
+ * 
+ * Updates the handler for the given shape if one exists.
+ */
+mxSelectionCellsHandler.prototype.updateHandler = function(state)
+{
+	var handler = this.handlers.remove(state.cell);
+	
+	if (handler != null)
+	{
+		handler.destroy();
+		handler = this.graph.createHandler(state);
+		
+		if (handler != null)
+		{
+			this.handlers.put(state.cell, handler);
+		}
+	}
+};
+
+/**
+ * Function: mouseDown
+ * 
+ * Redirects the given event to the handlers.
+ */
+mxSelectionCellsHandler.prototype.mouseDown = function(sender, me)
+{
+	if (this.graph.isEnabled() && this.isEnabled())
+	{
+		var args = [sender, me];
+
+		this.handlers.visit(function(key, handler)
+		{
+			handler.mouseDown.apply(handler, args);
+		});
+	}
+};
+
+/**
+ * Function: mouseMove
+ * 
+ * Redirects the given event to the handlers.
+ */
+mxSelectionCellsHandler.prototype.mouseMove = function(sender, me)
+{
+	if (this.graph.isEnabled() && this.isEnabled())
+	{
+		var args = [sender, me];
+
+		this.handlers.visit(function(key, handler)
+		{
+			handler.mouseMove.apply(handler, args);
+		});
+	}
+};
+
+/**
+ * Function: mouseUp
+ * 
+ * Redirects the given event to the handlers.
+ */
+mxSelectionCellsHandler.prototype.mouseUp = function(sender, me)
+{
+	if (this.graph.isEnabled() && this.isEnabled())
+	{
+		var args = [sender, me];
+
+		this.handlers.visit(function(key, handler)
+		{
+			handler.mouseUp.apply(handler, args);
+		});
+	}
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the handler and all its resources and DOM nodes.
+ */
+mxSelectionCellsHandler.prototype.destroy = function()
+{
+	this.graph.removeMouseListener(this);
+	
+	if (this.refreshHandler != null)
+	{
+		this.graph.getSelectionModel().removeListener(this.refreshHandler);
+		this.graph.getModel().removeListener(this.refreshHandler);
+		this.graph.getView().removeListener(this.refreshHandler);
+		this.refreshHandler = null;
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/handler/mxTooltipHandler.js b/airavata-kubernetes/web-console/src/assets/js/handler/mxTooltipHandler.js
new file mode 100644
index 0000000..4237e0c
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/handler/mxTooltipHandler.js
@@ -0,0 +1,337 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxTooltipHandler
+ * 
+ * Graph event handler that displays tooltips. <mxGraph.getTooltip> is used to
+ * get the tooltip for a cell or handle. This handler is built-into
+ * <mxGraph.tooltipHandler> and enabled using <mxGraph.setTooltips>.
+ *
+ * Example:
+ * 
+ * (code>
+ * new mxTooltipHandler(graph);
+ * (end)
+ * 
+ * Constructor: mxTooltipHandler
+ * 
+ * Constructs an event handler that displays tooltips with the specified
+ * delay (in milliseconds). If no delay is specified then a default delay
+ * of 500 ms (0.5 sec) is used.
+ * 
+ * Parameters:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ * delay - Optional delay in milliseconds.
+ */
+function mxTooltipHandler(graph, delay)
+{
+	if (graph != null)
+	{
+		this.graph = graph;
+		this.delay = delay || 500;
+		this.graph.addMouseListener(this);
+	}
+};
+
+/**
+ * Variable: zIndex
+ * 
+ * Specifies the zIndex for the tooltip and its shadow. Default is 10005.
+ */
+mxTooltipHandler.prototype.zIndex = 10005;
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxTooltipHandler.prototype.graph = null;
+
+/**
+ * Variable: delay
+ * 
+ * Delay to show the tooltip in milliseconds. Default is 500.
+ */
+mxTooltipHandler.prototype.delay = null;
+
+/**
+ * Variable: ignoreTouchEvents
+ * 
+ * Specifies if touch and pen events should be ignored. Default is true.
+ */
+mxTooltipHandler.prototype.ignoreTouchEvents = true;
+
+/**
+ * Variable: hideOnHover
+ * 
+ * Specifies if the tooltip should be hidden if the mouse is moved over the
+ * current cell. Default is false.
+ */
+mxTooltipHandler.prototype.hideOnHover = false;
+
+/**
+ * Variable: destroyed
+ * 
+ * True if this handler was destroyed using <destroy>.
+ */
+mxTooltipHandler.prototype.destroyed = false;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies if events are handled. Default is true.
+ */
+mxTooltipHandler.prototype.enabled = true;
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxTooltipHandler.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ * 
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ */
+mxTooltipHandler.prototype.setEnabled = function(enabled)
+{
+	this.enabled = enabled;
+};
+
+/**
+ * Function: isHideOnHover
+ * 
+ * Returns <hideOnHover>.
+ */
+mxTooltipHandler.prototype.isHideOnHover = function()
+{
+	return this.hideOnHover;
+};
+
+/**
+ * Function: setHideOnHover
+ * 
+ * Sets <hideOnHover>.
+ */
+mxTooltipHandler.prototype.setHideOnHover = function(value)
+{
+	this.hideOnHover = value;
+};
+
+/**
+ * Function: init
+ * 
+ * Initializes the DOM nodes required for this tooltip handler.
+ */
+mxTooltipHandler.prototype.init = function()
+{
+	if (document.body != null)
+	{
+		this.div = document.createElement('div');
+		this.div.className = 'mxTooltip';
+		this.div.style.visibility = 'hidden';
+
+		document.body.appendChild(this.div);
+
+		mxEvent.addGestureListeners(this.div, mxUtils.bind(this, function(evt)
+		{
+			this.hideTooltip();
+		}));
+	}
+};
+
+/**
+ * Function: mouseDown
+ * 
+ * Handles the event by initiating a rubberband selection. By consuming the
+ * event all subsequent events of the gesture are redirected to this
+ * handler.
+ */
+mxTooltipHandler.prototype.mouseDown = function(sender, me)
+{
+	this.reset(me, false);
+	this.hideTooltip();
+};
+
+/**
+ * Function: mouseMove
+ * 
+ * Handles the event by updating the rubberband selection.
+ */
+mxTooltipHandler.prototype.mouseMove = function(sender, me)
+{
+	if (me.getX() != this.lastX || me.getY() != this.lastY)
+	{
+		this.reset(me, true);
+		
+		if (this.isHideOnHover() || me.getState() != this.state || (me.getSource() != this.node &&
+			(!this.stateSource || (me.getState() != null && this.stateSource ==
+			(me.isSource(me.getState().shape) || !me.isSource(me.getState().text))))))
+		{
+			this.hideTooltip();
+		}
+	}
+	
+	this.lastX = me.getX();
+	this.lastY = me.getY();
+};
+
+/**
+ * Function: mouseUp
+ * 
+ * Handles the event by resetting the tooltip timer or hiding the existing
+ * tooltip.
+ */
+mxTooltipHandler.prototype.mouseUp = function(sender, me)
+{
+	this.reset(me, true);
+	this.hideTooltip();
+};
+
+
+/**
+ * Function: resetTimer
+ * 
+ * Resets the timer.
+ */
+mxTooltipHandler.prototype.resetTimer = function()
+{
+	if (this.thread != null)
+	{
+		window.clearTimeout(this.thread);
+		this.thread = null;
+	}
+};
+
+/**
+ * Function: reset
+ * 
+ * Resets and/or restarts the timer to trigger the display of the tooltip.
+ */
+mxTooltipHandler.prototype.reset = function(me, restart)
+{
+	if (!this.ignoreTouchEvents || mxEvent.isMouseEvent(me.getEvent()))
+	{
+		this.resetTimer();
+		
+		if (restart && this.isEnabled() && me.getState() != null && (this.div == null ||
+			this.div.style.visibility == 'hidden'))
+		{
+			var state = me.getState();
+			var node = me.getSource();
+			var x = me.getX();
+			var y = me.getY();
+			var stateSource = me.isSource(state.shape) || me.isSource(state.text);
+	
+			this.thread = window.setTimeout(mxUtils.bind(this, function()
+			{
+				if (!this.graph.isEditing() && !this.graph.popupMenuHandler.isMenuShowing() && !this.graph.isMouseDown)
+				{
+					// Uses information from inside event cause using the event at
+					// this (delayed) point in time is not possible in IE as it no
+					// longer contains the required information (member not found)
+					var tip = this.graph.getTooltip(state, node, x, y);
+					this.show(tip, x, y);
+					this.state = state;
+					this.node = node;
+					this.stateSource = stateSource;
+				}
+			}), this.delay);
+		}
+	}
+};
+
+/**
+ * Function: hide
+ * 
+ * Hides the tooltip and resets the timer.
+ */
+mxTooltipHandler.prototype.hide = function()
+{
+	this.resetTimer();
+	this.hideTooltip();
+};
+
+/**
+ * Function: hideTooltip
+ * 
+ * Hides the tooltip.
+ */
+mxTooltipHandler.prototype.hideTooltip = function()
+{
+	if (this.div != null)
+	{
+		this.div.style.visibility = 'hidden';
+		this.div.innerHTML = '';
+	}
+};
+
+/**
+ * Function: show
+ * 
+ * Shows the tooltip for the specified cell and optional index at the
+ * specified location (with a vertical offset of 10 pixels).
+ */
+mxTooltipHandler.prototype.show = function(tip, x, y)
+{
+	if (!this.destroyed && tip != null && tip.length > 0)
+	{
+		// Initializes the DOM nodes if required
+		if (this.div == null)
+		{
+			this.init();
+		}
+		
+		var origin = mxUtils.getScrollOrigin();
+
+		this.div.style.zIndex = this.zIndex;
+		this.div.style.left = (x + origin.x) + 'px';
+		this.div.style.top = (y + mxConstants.TOOLTIP_VERTICAL_OFFSET +
+			origin.y) + 'px';
+
+		if (!mxUtils.isNode(tip))
+		{	
+			this.div.innerHTML = tip.replace(/\n/g, '<br>');
+		}
+		else
+		{
+			this.div.innerHTML = '';
+			this.div.appendChild(tip);
+		}
+		
+		this.div.style.visibility = '';
+		mxUtils.fit(this.div);
+	}
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the handler and all its resources and DOM nodes.
+ */
+mxTooltipHandler.prototype.destroy = function()
+{
+	if (!this.destroyed)
+	{
+		this.graph.removeMouseListener(this);
+		mxEvent.release(this.div);
+		
+		if (this.div != null && this.div.parentNode != null)
+		{
+			this.div.parentNode.removeChild(this.div);
+		}
+		
+		this.destroyed = true;
+		this.div = null;
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/handler/mxVertexHandler.js b/airavata-kubernetes/web-console/src/assets/js/handler/mxVertexHandler.js
new file mode 100644
index 0000000..7669d8c
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/handler/mxVertexHandler.js
@@ -0,0 +1,1950 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxVertexHandler
+ * 
+ * Event handler for resizing cells. This handler is automatically created in
+ * <mxGraph.createHandler>.
+ * 
+ * Constructor: mxVertexHandler
+ * 
+ * Constructs an event handler that allows to resize vertices
+ * and groups.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> of the cell to be resized.
+ */
+function mxVertexHandler(state)
+{
+	if (state != null)
+	{
+		this.state = state;
+		this.init();
+		
+		// Handles escape keystrokes
+		this.escapeHandler = mxUtils.bind(this, function(sender, evt)
+		{
+			if (this.livePreview && this.index != null)
+			{
+				// Redraws the live preview
+				this.state.view.graph.cellRenderer.redraw(this.state, true);
+				
+				// Redraws connected edges
+				this.state.view.invalidate(this.state.cell);
+				this.state.invalid = false;
+				this.state.view.validate();
+			}
+			
+			this.reset();
+		});
+		
+		this.state.view.graph.addListener(mxEvent.ESCAPE, this.escapeHandler);
+	}
+};
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxVertexHandler.prototype.graph = null;
+
+/**
+ * Variable: state
+ * 
+ * Reference to the <mxCellState> being modified.
+ */
+mxVertexHandler.prototype.state = null;
+
+/**
+ * Variable: singleSizer
+ * 
+ * Specifies if only one sizer handle at the bottom, right corner should be
+ * used. Default is false.
+ */
+mxVertexHandler.prototype.singleSizer = false;
+
+/**
+ * Variable: index
+ * 
+ * Holds the index of the current handle.
+ */
+mxVertexHandler.prototype.index = null;
+
+/**
+ * Variable: allowHandleBoundsCheck
+ * 
+ * Specifies if the bounds of handles should be used for hit-detection in IE or
+ * if <tolerance> > 0. Default is true.
+ */
+mxVertexHandler.prototype.allowHandleBoundsCheck = true;
+
+/**
+ * Variable: handleImage
+ * 
+ * Optional <mxImage> to be used as handles. Default is null.
+ */
+mxVertexHandler.prototype.handleImage = null;
+
+/**
+ * Variable: tolerance
+ * 
+ * Optional tolerance for hit-detection in <getHandleForEvent>. Default is 0.
+ */
+mxVertexHandler.prototype.tolerance = 0;
+
+/**
+ * Variable: rotationEnabled
+ * 
+ * Specifies if a rotation handle should be visible. Default is false.
+ */
+mxVertexHandler.prototype.rotationEnabled = false;
+
+/**
+ * Variable: parentHighlightEnabled
+ * 
+ * Specifies if the parent should be highlighted if a child cell is selected.
+ * Default is false.
+ */
+mxVertexHandler.prototype.parentHighlightEnabled = false;
+
+/**
+ * Variable: rotationRaster
+ * 
+ * Specifies if rotation steps should be "rasterized" depening on the distance
+ * to the handle. Default is true.
+ */
+mxVertexHandler.prototype.rotationRaster = true;
+
+/**
+ * Variable: rotationCursor
+ * 
+ * Specifies the cursor for the rotation handle. Default is 'crosshair'.
+ */
+mxVertexHandler.prototype.rotationCursor = 'crosshair';
+
+/**
+ * Variable: livePreview
+ * 
+ * Specifies if resize should change the cell in-place. This is an experimental
+ * feature for non-touch devices. Default is false.
+ */
+mxVertexHandler.prototype.livePreview = false;
+
+/**
+ * Variable: manageSizers
+ * 
+ * Specifies if sizers should be hidden and spaced if the vertex is small.
+ * Default is false.
+ */
+mxVertexHandler.prototype.manageSizers = false;
+
+/**
+ * Variable: constrainGroupByChildren
+ * 
+ * Specifies if the size of groups should be constrained by the children.
+ * Default is false.
+ */
+mxVertexHandler.prototype.constrainGroupByChildren = false;
+
+/**
+ * Variable: rotationHandleVSpacing
+ * 
+ * Vertical spacing for rotation icon. Default is -16.
+ */
+mxVertexHandler.prototype.rotationHandleVSpacing = -16;
+
+/**
+ * Variable: horizontalOffset
+ * 
+ * The horizontal offset for the handles. This is updated in <redrawHandles>
+ * if <manageSizers> is true and the sizers are offset horizontally.
+ */
+mxVertexHandler.prototype.horizontalOffset = 0;
+
+/**
+ * Variable: verticalOffset
+ * 
+ * The horizontal offset for the handles. This is updated in <redrawHandles>
+ * if <manageSizers> is true and the sizers are offset vertically.
+ */
+mxVertexHandler.prototype.verticalOffset = 0;
+
+/**
+ * Function: init
+ * 
+ * Initializes the shapes required for this vertex handler.
+ */
+mxVertexHandler.prototype.init = function()
+{
+	this.graph = this.state.view.graph;
+	this.selectionBounds = this.getSelectionBounds(this.state);
+	this.bounds = new mxRectangle(this.selectionBounds.x, this.selectionBounds.y, this.selectionBounds.width, this.selectionBounds.height);
+	this.selectionBorder = this.createSelectionShape(this.bounds);
+	// VML dialect required here for event transparency in IE
+	this.selectionBorder.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+	this.selectionBorder.pointerEvents = false;
+	this.selectionBorder.rotation = Number(this.state.style[mxConstants.STYLE_ROTATION] || '0');
+	this.selectionBorder.init(this.graph.getView().getOverlayPane());
+	mxEvent.redirectMouseEvents(this.selectionBorder.node, this.graph, this.state);
+	
+	if (this.graph.isCellMovable(this.state.cell))
+	{
+		this.selectionBorder.setCursor(mxConstants.CURSOR_MOVABLE_VERTEX);
+	}
+
+	// Adds the sizer handles
+	if (mxGraphHandler.prototype.maxCells <= 0 || this.graph.getSelectionCount() < mxGraphHandler.prototype.maxCells)
+	{
+		var resizable = this.graph.isCellResizable(this.state.cell);
+		this.sizers = [];
+
+		if (resizable || (this.graph.isLabelMovable(this.state.cell) &&
+			this.state.width >= 2 && this.state.height >= 2))
+		{
+			var i = 0;
+
+			if (resizable)
+			{
+				if (!this.singleSizer)
+				{
+					this.sizers.push(this.createSizer('nw-resize', i++));
+					this.sizers.push(this.createSizer('n-resize', i++));
+					this.sizers.push(this.createSizer('ne-resize', i++));
+					this.sizers.push(this.createSizer('w-resize', i++));
+					this.sizers.push(this.createSizer('e-resize', i++));
+					this.sizers.push(this.createSizer('sw-resize', i++));
+					this.sizers.push(this.createSizer('s-resize', i++));
+				}
+				
+				this.sizers.push(this.createSizer('se-resize', i++));
+			}
+			
+			var geo = this.graph.model.getGeometry(this.state.cell);
+			
+			if (geo != null && !geo.relative && !this.graph.isSwimlane(this.state.cell) &&
+				this.graph.isLabelMovable(this.state.cell))
+			{
+				// Marks this as the label handle for getHandleForEvent
+				this.labelShape = this.createSizer(mxConstants.CURSOR_LABEL_HANDLE, mxEvent.LABEL_HANDLE, mxConstants.LABEL_HANDLE_SIZE, mxConstants.LABEL_HANDLE_FILLCOLOR);
+				this.sizers.push(this.labelShape);
+			}
+		}
+		else if (this.graph.isCellMovable(this.state.cell) && !this.graph.isCellResizable(this.state.cell) &&
+			this.state.width < 2 && this.state.height < 2)
+		{
+			this.labelShape = this.createSizer(mxConstants.CURSOR_MOVABLE_VERTEX,
+				mxEvent.LABEL_HANDLE, null, mxConstants.LABEL_HANDLE_FILLCOLOR);
+			this.sizers.push(this.labelShape);
+		}
+	}
+	
+	// Adds the rotation handler
+	if (this.isRotationHandleVisible())
+	{
+		this.rotationShape = this.createSizer(this.rotationCursor, mxEvent.ROTATION_HANDLE,
+			mxConstants.HANDLE_SIZE + 3, mxConstants.HANDLE_FILLCOLOR);
+		this.sizers.push(this.rotationShape);
+	}
+
+	this.customHandles = this.createCustomHandles();
+	this.redraw();
+	
+	if (this.constrainGroupByChildren)
+	{
+		this.updateMinBounds();
+	}
+};
+
+/**
+ * Function: isRotationHandleVisible
+ * 
+ * Returns true if the rotation handle should be showing.
+ */
+mxVertexHandler.prototype.isRotationHandleVisible = function()
+{
+	return this.graph.isEnabled() && this.rotationEnabled && this.graph.isCellRotatable(this.state.cell) &&
+		(mxGraphHandler.prototype.maxCells <= 0 || this.graph.getSelectionCount() < mxGraphHandler.prototype.maxCells) &&
+		this.state.width >= 2 && this.state.height >= 2;
+};
+
+/**
+ * Function: isConstrainedEvent
+ * 
+ * Returns true if the aspect ratio if the cell should be maintained.
+ */
+mxVertexHandler.prototype.isConstrainedEvent = function(me)
+{
+	return mxEvent.isShiftDown(me.getEvent()) || this.state.style[mxConstants.STYLE_ASPECT] == 'fixed';
+};
+
+/**
+ * Function: isCenteredEvent
+ * 
+ * Returns true if the center of the vertex should be maintained during the resize.
+ */
+mxVertexHandler.prototype.isCenteredEvent = function(state, me)
+{
+	return false;
+};
+
+/**
+ * Function: createCustomHandles
+ * 
+ * Returns an array of custom handles. This implementation returns null.
+ */
+mxVertexHandler.prototype.createCustomHandles = function()
+{
+	return null;
+};
+
+/**
+ * Function: updateMinBounds
+ * 
+ * Initializes the shapes required for this vertex handler.
+ */
+mxVertexHandler.prototype.updateMinBounds = function()
+{
+	var children = this.graph.getChildCells(this.state.cell);
+	
+	if (children.length > 0)
+	{
+		this.minBounds = this.graph.view.getBounds(children);
+		
+		if (this.minBounds != null)
+		{
+			var s = this.state.view.scale;
+			var t = this.state.view.translate;
+
+			this.minBounds.x -= this.state.x;
+			this.minBounds.y -= this.state.y;
+			this.minBounds.x /= s;
+			this.minBounds.y /= s;
+			this.minBounds.width /= s;
+			this.minBounds.height /= s;
+			this.x0 = this.state.x / s - t.x;
+			this.y0 = this.state.y / s - t.y;
+		}
+	}
+};
+
+/**
+ * Function: getSelectionBounds
+ * 
+ * Returns the mxRectangle that defines the bounds of the selection
+ * border.
+ */
+mxVertexHandler.prototype.getSelectionBounds = function(state)
+{
+	return new mxRectangle(Math.round(state.x), Math.round(state.y), Math.round(state.width), Math.round(state.height));
+};
+
+/**
+ * Function: createParentHighlightShape
+ * 
+ * Creates the shape used to draw the selection border.
+ */
+mxVertexHandler.prototype.createParentHighlightShape = function(bounds)
+{
+	return this.createSelectionShape(bounds);
+};
+
+/**
+ * Function: createSelectionShape
+ * 
+ * Creates the shape used to draw the selection border.
+ */
+mxVertexHandler.prototype.createSelectionShape = function(bounds)
+{
+	var shape = new mxRectangleShape(bounds, null, this.getSelectionColor());
+	shape.strokewidth = this.getSelectionStrokeWidth();
+	shape.isDashed = this.isSelectionDashed();
+	
+	return shape;
+};
+
+/**
+ * Function: getSelectionColor
+ * 
+ * Returns <mxConstants.VERTEX_SELECTION_COLOR>.
+ */
+mxVertexHandler.prototype.getSelectionColor = function()
+{
+	return mxConstants.VERTEX_SELECTION_COLOR;
+};
+
+/**
+ * Function: getSelectionStrokeWidth
+ * 
+ * Returns <mxConstants.VERTEX_SELECTION_STROKEWIDTH>.
+ */
+mxVertexHandler.prototype.getSelectionStrokeWidth = function()
+{
+	return mxConstants.VERTEX_SELECTION_STROKEWIDTH;
+};
+
+/**
+ * Function: isSelectionDashed
+ * 
+ * Returns <mxConstants.VERTEX_SELECTION_DASHED>.
+ */
+mxVertexHandler.prototype.isSelectionDashed = function()
+{
+	return mxConstants.VERTEX_SELECTION_DASHED;
+};
+
+/**
+ * Function: createSizer
+ * 
+ * Creates a sizer handle for the specified cursor and index and returns
+ * the new <mxRectangleShape> that represents the handle.
+ */
+mxVertexHandler.prototype.createSizer = function(cursor, index, size, fillColor)
+{
+	size = size || mxConstants.HANDLE_SIZE;
+	
+	var bounds = new mxRectangle(0, 0, size, size);
+	var sizer = this.createSizerShape(bounds, index, fillColor);
+
+	if (sizer.isHtmlAllowed() && this.state.text != null && this.state.text.node.parentNode == this.graph.container)
+	{
+		sizer.bounds.height -= 1;
+		sizer.bounds.width -= 1;
+		sizer.dialect = mxConstants.DIALECT_STRICTHTML;
+		sizer.init(this.graph.container);
+	}
+	else
+	{
+		sizer.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
+				mxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG;
+		sizer.init(this.graph.getView().getOverlayPane());
+	}
+
+	mxEvent.redirectMouseEvents(sizer.node, this.graph, this.state);
+	
+	if (this.graph.isEnabled())
+	{
+		sizer.setCursor(cursor);
+	}
+	
+	if (!this.isSizerVisible(index))
+	{
+		sizer.visible = false;
+	}
+	
+	return sizer;
+};
+
+/**
+ * Function: isSizerVisible
+ * 
+ * Returns true if the sizer for the given index is visible.
+ * This returns true for all given indices.
+ */
+mxVertexHandler.prototype.isSizerVisible = function(index)
+{
+	return true;
+};
+
+/**
+ * Function: createSizerShape
+ * 
+ * Creates the shape used for the sizer handle for the specified bounds an
+ * index. Only images and rectangles should be returned if support for HTML
+ * labels with not foreign objects is required.
+ */
+mxVertexHandler.prototype.createSizerShape = function(bounds, index, fillColor)
+{
+	if (this.handleImage != null)
+	{
+		bounds = new mxRectangle(bounds.x, bounds.y, this.handleImage.width, this.handleImage.height);
+		var shape = new mxImageShape(bounds, this.handleImage.src);
+		
+		// Allows HTML rendering of the images
+		shape.preserveImageAspect = false;
+
+		return shape;
+	}
+	else if (index == mxEvent.ROTATION_HANDLE)
+	{
+		return new mxEllipse(bounds, fillColor || mxConstants.HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR);
+	}
+	else
+	{
+		return new mxRectangleShape(bounds, fillColor || mxConstants.HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR);
+	}
+};
+
+/**
+ * Function: createBounds
+ * 
+ * Helper method to create an <mxRectangle> around the given centerpoint
+ * with a width and height of 2*s or 6, if no s is given.
+ */
+mxVertexHandler.prototype.moveSizerTo = function(shape, x, y)
+{
+	if (shape != null)
+	{
+		shape.bounds.x = Math.floor(x - shape.bounds.width / 2);
+		shape.bounds.y = Math.floor(y - shape.bounds.height / 2);
+		
+		// Fixes visible inactive handles in VML
+		if (shape.node != null && shape.node.style.display != 'none')
+		{
+			shape.redraw();
+		}
+	}
+};
+
+/**
+ * Function: getHandleForEvent
+ * 
+ * Returns the index of the handle for the given event. This returns the index
+ * of the sizer from where the event originated or <mxEvent.LABEL_INDEX>.
+ */
+mxVertexHandler.prototype.getHandleForEvent = function(me)
+{
+	// Connection highlight may consume events before they reach sizer handle
+	var tol = (!mxEvent.isMouseEvent(me.getEvent())) ? this.tolerance : 1;
+	var hit = (this.allowHandleBoundsCheck && (mxClient.IS_IE || tol > 0)) ?
+		new mxRectangle(me.getGraphX() - tol, me.getGraphY() - tol, 2 * tol, 2 * tol) : null;
+	
+	function checkShape(shape)
+	{
+		return shape != null && (me.isSource(shape) || (hit != null && mxUtils.intersects(shape.bounds, hit) &&
+			shape.node.style.display != 'none' && shape.node.style.visibility != 'hidden'));
+	}
+
+	if (this.customHandles != null && this.isCustomHandleEvent(me))
+	{
+		// Inverse loop order to match display order
+		for (var i = this.customHandles.length - 1; i >= 0; i--)
+		{
+			if (checkShape(this.customHandles[i].shape))
+			{
+				// LATER: Return reference to active shape
+				return mxEvent.CUSTOM_HANDLE - i;
+			}
+		}
+	}
+
+	if (checkShape(this.rotationShape))
+	{
+		return mxEvent.ROTATION_HANDLE;
+	}
+	else if (checkShape(this.labelShape))
+	{
+		return mxEvent.LABEL_HANDLE;
+	}
+	
+	if (this.sizers != null)
+	{
+		for (var i = 0; i < this.sizers.length; i++)
+		{
+			if (checkShape(this.sizers[i]))
+			{
+				return i;
+			}
+		}
+	}
+
+	return null;
+};
+
+/**
+ * Function: isCustomHandleEvent
+ * 
+ * Returns true if the given event allows custom handles to be changed. This
+ * implementation returns true.
+ */
+mxVertexHandler.prototype.isCustomHandleEvent = function(me)
+{
+	return true;
+};
+
+/**
+ * Function: mouseDown
+ * 
+ * Handles the event if a handle has been clicked. By consuming the
+ * event all subsequent events of the gesture are redirected to this
+ * handler.
+ */
+mxVertexHandler.prototype.mouseDown = function(sender, me)
+{
+	var tol = (!mxEvent.isMouseEvent(me.getEvent())) ? this.tolerance : 0;
+	
+	if (!me.isConsumed() && this.graph.isEnabled() && (tol > 0 || me.getState() == this.state))
+	{
+		var handle = this.getHandleForEvent(me);
+
+		if (handle != null)
+		{
+			this.start(me.getGraphX(), me.getGraphY(), handle);
+			me.consume();
+		}
+	}
+};
+
+/**
+ * Function: isLivePreviewBorder
+ * 
+ * Called if <livePreview> is enabled to check if a border should be painted.
+ * This implementation returns true if the shape is transparent.
+ */
+mxVertexHandler.prototype.isLivePreviewBorder = function()
+{
+	return this.state.shape != null && this.state.shape.fill == null && this.state.shape.stroke == null;
+};
+
+/**
+ * Function: start
+ * 
+ * Starts the handling of the mouse gesture.
+ */
+mxVertexHandler.prototype.start = function(x, y, index)
+{
+	this.inTolerance = true;
+	this.childOffsetX = 0;
+	this.childOffsetY = 0;
+	this.index = index;
+	this.startX = x;
+	this.startY = y;
+	
+	// Saves reference to parent state
+	var model = this.state.view.graph.model;
+	var parent = model.getParent(this.state.cell);
+	
+	if (this.state.view.currentRoot != parent && (model.isVertex(parent) || model.isEdge(parent)))
+	{
+		this.parentState = this.state.view.graph.view.getState(parent);
+	}
+	
+	// Creates a preview that can be on top of any HTML label
+	this.selectionBorder.node.style.display = (index == mxEvent.ROTATION_HANDLE) ? 'inline' : 'none';
+	
+	// Creates the border that represents the new bounds
+	if (!this.livePreview || this.isLivePreviewBorder())
+	{
+		this.preview = this.createSelectionShape(this.bounds);
+		
+		if (!(mxClient.IS_SVG && Number(this.state.style[mxConstants.STYLE_ROTATION] || '0') != 0) &&
+			this.state.text != null && this.state.text.node.parentNode == this.graph.container)
+		{
+			this.preview.dialect = mxConstants.DIALECT_STRICTHTML;
+			this.preview.init(this.graph.container);
+		}
+		else
+		{
+			this.preview.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
+					mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+			this.preview.init(this.graph.view.getOverlayPane());
+		}
+	}
+	
+	// Prepares the handles for live preview
+	if (this.livePreview)
+	{
+		this.hideSizers();
+		
+		if (index == mxEvent.ROTATION_HANDLE)
+		{
+			this.rotationShape.node.style.display = '';
+		}
+		else if (index == mxEvent.LABEL_HANDLE)
+		{
+			this.labelShape.node.style.display = '';
+		}
+		else if (this.sizers != null && this.sizers[index] != null)
+		{
+			this.sizers[index].node.style.display = '';
+		}
+		else if (index <= mxEvent.CUSTOM_HANDLE && this.customHandles != null)
+		{
+			this.customHandles[mxEvent.CUSTOM_HANDLE - index].setVisible(true);
+		}
+		
+		// Gets the array of connected edge handlers for redrawing
+		var edges = this.graph.getEdges(this.state.cell);
+		this.edgeHandlers = [];
+		
+		for (var i = 0; i < edges.length; i++)
+		{
+			var handler = this.graph.selectionCellsHandler.getHandler(edges[i]);
+			
+			if (handler != null)
+			{
+				this.edgeHandlers.push(handler);
+			}
+		}
+	}
+};
+
+/**
+ * Function: hideHandles
+ * 
+ * Shortcut to <hideSizers>.
+ */
+mxVertexHandler.prototype.setHandlesVisible = function(visible)
+{
+	if (this.sizers != null)
+	{
+		for (var i = 0; i < this.sizers.length; i++)
+		{
+			this.sizers[i].node.style.display = (visible) ? '' : 'none';
+		}
+	}
+
+	if (this.customHandles != null)
+	{
+		for (var i = 0; i < this.customHandles.length; i++)
+		{
+			this.customHandles[i].setVisible(visible);
+		}
+	}
+};
+
+/**
+ * Function: hideSizers
+ * 
+ * Hides all sizers except.
+ * 
+ * Starts the handling of the mouse gesture.
+ */
+mxVertexHandler.prototype.hideSizers = function()
+{
+	this.setHandlesVisible(false);
+};
+
+/**
+ * Function: checkTolerance
+ * 
+ * Checks if the coordinates for the given event are within the
+ * <mxGraph.tolerance>. If the event is a mouse event then the tolerance is
+ * ignored.
+ */
+mxVertexHandler.prototype.checkTolerance = function(me)
+{
+	if (this.inTolerance && this.startX != null && this.startY != null)
+	{
+		if (mxEvent.isMouseEvent(me.getEvent()) ||
+			Math.abs(me.getGraphX() - this.startX) > this.graph.tolerance ||
+			Math.abs(me.getGraphY() - this.startY) > this.graph.tolerance)
+		{
+			this.inTolerance = false;
+		}
+	}
+};
+
+/**
+ * Function: updateHint
+ * 
+ * Hook for subclassers do show details while the handler is active.
+ */
+mxVertexHandler.prototype.updateHint = function(me) { };
+
+/**
+ * Function: removeHint
+ * 
+ * Hooks for subclassers to hide details when the handler gets inactive.
+ */
+mxVertexHandler.prototype.removeHint = function() { };
+
+/**
+ * Function: roundAngle
+ * 
+ * Hook for rounding the angle. This uses Math.round.
+ */
+mxVertexHandler.prototype.roundAngle = function(angle)
+{
+	return Math.round(angle * 10) / 10;
+};
+
+/**
+ * Function: roundLength
+ * 
+ * Hook for rounding the unscaled width or height. This uses Math.round.
+ */
+mxVertexHandler.prototype.roundLength = function(length)
+{
+	return Math.round(length);
+};
+
+/**
+ * Function: mouseMove
+ * 
+ * Handles the event by updating the preview.
+ */
+mxVertexHandler.prototype.mouseMove = function(sender, me)
+{
+	if (!me.isConsumed() && this.index != null)
+	{
+		// Checks tolerance for ignoring single clicks
+		this.checkTolerance(me);
+
+		if (!this.inTolerance)
+		{
+			if (this.index <= mxEvent.CUSTOM_HANDLE)
+			{
+				if (this.customHandles != null)
+				{
+					this.customHandles[mxEvent.CUSTOM_HANDLE - this.index].processEvent(me);
+					this.customHandles[mxEvent.CUSTOM_HANDLE - this.index].active = true;
+				}
+			}
+			else if (this.index == mxEvent.LABEL_HANDLE)
+			{
+				this.moveLabel(me);
+			}
+			else if (this.index == mxEvent.ROTATION_HANDLE)
+			{
+				this.rotateVertex(me);
+			}
+			else
+			{
+				this.resizeVertex(me);
+			}
+
+			this.updateHint(me);
+		}
+		
+		me.consume();
+	}
+	// Workaround for disabling the connect highlight when over handle
+	else if (!this.graph.isMouseDown && this.getHandleForEvent(me) != null)
+	{
+		me.consume(false);
+	}
+};
+
+/**
+ * Function: rotateVertex
+ * 
+ * Rotates the vertex.
+ */
+mxVertexHandler.prototype.moveLabel = function(me)
+{
+	var point = new mxPoint(me.getGraphX(), me.getGraphY());
+	var tr = this.graph.view.translate;
+	var scale = this.graph.view.scale;
+	
+	if (this.graph.isGridEnabledEvent(me.getEvent()))
+	{
+		point.x = (this.graph.snap(point.x / scale - tr.x) + tr.x) * scale;
+		point.y = (this.graph.snap(point.y / scale - tr.y) + tr.y) * scale;
+	}
+
+	var index = (this.rotationShape != null) ? this.sizers.length - 2 : this.sizers.length - 1;
+	this.moveSizerTo(this.sizers[index], point.x, point.y);
+};
+
+/**
+ * Function: rotateVertex
+ * 
+ * Rotates the vertex.
+ */
+mxVertexHandler.prototype.rotateVertex = function(me)
+{
+	var point = new mxPoint(me.getGraphX(), me.getGraphY());
+	var dx = this.state.x + this.state.width / 2 - point.x;
+	var dy = this.state.y + this.state.height / 2 - point.y;
+	this.currentAlpha = (dx != 0) ? Math.atan(dy / dx) * 180 / Math.PI + 90 : ((dy < 0) ? 180 : 0);
+	
+	if (dx > 0)
+	{
+		this.currentAlpha -= 180;
+	}
+
+	// Rotation raster
+	if (this.rotationRaster && this.graph.isGridEnabledEvent(me.getEvent()))
+	{
+		var dx = point.x - this.state.getCenterX();
+		var dy = point.y - this.state.getCenterY();
+		var dist = Math.abs(Math.sqrt(dx * dx + dy * dy) - 20) * 3;
+		var raster = Math.max(1, 5 * Math.min(3, Math.max(0, Math.round(80 / Math.abs(dist)))));
+		
+		this.currentAlpha = Math.round(this.currentAlpha / raster) * raster;
+	}
+	else
+	{
+		this.currentAlpha = this.roundAngle(this.currentAlpha);
+	}
+
+	this.selectionBorder.rotation = this.currentAlpha;
+	this.selectionBorder.redraw();
+					
+	if (this.livePreview)
+	{
+		this.redrawHandles();
+	}
+};
+
+/**
+ * Function: rotateVertex
+ * 
+ * Rotates the vertex.
+ */
+mxVertexHandler.prototype.resizeVertex = function(me)
+{
+	var ct = new mxPoint(this.state.getCenterX(), this.state.getCenterY());
+	var alpha = mxUtils.toRadians(this.state.style[mxConstants.STYLE_ROTATION] || '0');
+	var point = new mxPoint(me.getGraphX(), me.getGraphY());
+	var tr = this.graph.view.translate;
+	var scale = this.graph.view.scale;
+	var cos = Math.cos(-alpha);
+	var sin = Math.sin(-alpha);
+	
+	var dx = point.x - this.startX;
+	var dy = point.y - this.startY;
+
+	// Rotates vector for mouse gesture
+	var tx = cos * dx - sin * dy;
+	var ty = sin * dx + cos * dy;
+	
+	dx = tx;
+	dy = ty;
+
+	var geo = this.graph.getCellGeometry(this.state.cell);
+	this.unscaledBounds = this.union(geo, dx / scale, dy / scale, this.index,
+		this.graph.isGridEnabledEvent(me.getEvent()), 1,
+		new mxPoint(0, 0), this.isConstrainedEvent(me),
+		this.isCenteredEvent(this.state, me));
+	
+	// Keeps vertex within maximum graph or parent bounds
+	if (!geo.relative)
+	{
+		var max = this.graph.getMaximumGraphBounds();
+		
+		// Handles child cells
+		if (max != null && this.parentState != null)
+		{
+			max = mxRectangle.fromRectangle(max);
+			
+			max.x -= (this.parentState.x - tr.x * scale) / scale;
+			max.y -= (this.parentState.y - tr.y * scale) / scale;
+		}
+		
+		if (this.graph.isConstrainChild(this.state.cell))
+		{
+			var tmp = this.graph.getCellContainmentArea(this.state.cell);
+			
+			if (tmp != null)
+			{
+				var overlap = this.graph.getOverlap(this.state.cell);
+				
+				if (overlap > 0)
+				{
+					tmp = mxRectangle.fromRectangle(tmp);
+					
+					tmp.x -= tmp.width * overlap;
+					tmp.y -= tmp.height * overlap;
+					tmp.width += 2 * tmp.width * overlap;
+					tmp.height += 2 * tmp.height * overlap;
+				}
+				
+				if (max == null)
+				{
+					max = tmp;
+				}
+				else
+				{
+					max = mxRectangle.fromRectangle(max);
+					max.intersect(tmp);
+				}
+			}
+		}
+	
+		if (max != null)
+		{
+			if (this.unscaledBounds.x < max.x)
+			{
+				this.unscaledBounds.width -= max.x - this.unscaledBounds.x;
+				this.unscaledBounds.x = max.x;
+			}
+			
+			if (this.unscaledBounds.y < max.y)
+			{
+				this.unscaledBounds.height -= max.y - this.unscaledBounds.y;
+				this.unscaledBounds.y = max.y;
+			}
+			
+			if (this.unscaledBounds.x + this.unscaledBounds.width > max.x + max.width)
+			{
+				this.unscaledBounds.width -= this.unscaledBounds.x +
+					this.unscaledBounds.width - max.x - max.width;
+			}
+			
+			if (this.unscaledBounds.y + this.unscaledBounds.height > max.y + max.height)
+			{
+				this.unscaledBounds.height -= this.unscaledBounds.y +
+					this.unscaledBounds.height - max.y - max.height;
+			}
+		}
+	}
+	
+	this.bounds = new mxRectangle(((this.parentState != null) ? this.parentState.x : tr.x * scale) +
+		(this.unscaledBounds.x) * scale, ((this.parentState != null) ? this.parentState.y : tr.y * scale) +
+		(this.unscaledBounds.y) * scale, this.unscaledBounds.width * scale, this.unscaledBounds.height * scale);
+
+	if (geo.relative && this.parentState != null)
+	{
+		this.bounds.x += this.state.x - this.parentState.x;
+		this.bounds.y += this.state.y - this.parentState.y;
+	}
+
+	cos = Math.cos(alpha);
+	sin = Math.sin(alpha);
+	
+	var c2 = new mxPoint(this.bounds.getCenterX(), this.bounds.getCenterY());
+
+	var dx = c2.x - ct.x;
+	var dy = c2.y - ct.y;
+	
+	var dx2 = cos * dx - sin * dy;
+	var dy2 = sin * dx + cos * dy;
+	
+	var dx3 = dx2 - dx;
+	var dy3 = dy2 - dy;
+	
+	var dx4 = this.bounds.x - this.state.x;
+	var dy4 = this.bounds.y - this.state.y;
+	
+	var dx5 = cos * dx4 - sin * dy4;
+	var dy5 = sin * dx4 + cos * dy4;
+	
+	this.bounds.x += dx3;
+	this.bounds.y += dy3;
+	
+	// Rounds unscaled bounds to int
+	this.unscaledBounds.x = this.roundLength(this.unscaledBounds.x + dx3 / scale);
+	this.unscaledBounds.y = this.roundLength(this.unscaledBounds.y + dy3 / scale);
+	this.unscaledBounds.width = this.roundLength(this.unscaledBounds.width);
+	this.unscaledBounds.height = this.roundLength(this.unscaledBounds.height);
+	
+	// Shifts the children according to parent offset
+	if (!this.graph.isCellCollapsed(this.state.cell) && (dx3 != 0 || dy3 != 0))
+	{
+		this.childOffsetX = this.state.x - this.bounds.x + dx5;
+		this.childOffsetY = this.state.y - this.bounds.y + dy5;
+	}
+	else
+	{
+		this.childOffsetX = 0;
+		this.childOffsetY = 0;
+	}
+	
+	if (this.livePreview)
+	{
+		this.updateLivePreview(me);
+	}
+	
+	if (this.preview != null)
+	{
+		this.drawPreview();
+	}
+};
+
+/**
+ * Function: updateLivePreview
+ * 
+ * Repaints the live preview.
+ */
+mxVertexHandler.prototype.updateLivePreview = function(me)
+{
+	// TODO: Apply child offset to children in live preview
+	var scale = this.graph.view.scale;
+	var tr = this.graph.view.translate;
+	
+	// Saves current state
+	var tempState = this.state.clone();
+
+	// Temporarily changes size and origin
+	this.state.x = this.bounds.x;
+	this.state.y = this.bounds.y;
+	this.state.origin = new mxPoint(this.state.x / scale - tr.x, this.state.y / scale - tr.y);
+	this.state.width = this.bounds.width;
+	this.state.height = this.bounds.height;
+	
+	// Needed to force update of text bounds
+	this.state.unscaledWidth = null;
+	
+	// Redraws cell and handles
+	var off = this.state.absoluteOffset;
+	off = new mxPoint(off.x, off.y);
+
+	// Required to store and reset absolute offset for updating label position
+	this.state.absoluteOffset.x = 0;
+	this.state.absoluteOffset.y = 0;
+	var geo = this.graph.getCellGeometry(this.state.cell);				
+
+	if (geo != null)
+	{
+		var offset = geo.offset || this.EMPTY_POINT;
+
+		if (offset != null && !geo.relative)
+		{
+			this.state.absoluteOffset.x = this.state.view.scale * offset.x;
+			this.state.absoluteOffset.y = this.state.view.scale * offset.y;
+		}
+		
+		this.state.view.updateVertexLabelOffset(this.state);
+	}
+	
+	// Draws the live preview
+	this.state.view.graph.cellRenderer.redraw(this.state, true);
+	
+	// Redraws connected edges TODO: Include child edges
+	this.state.view.invalidate(this.state.cell);
+	this.state.invalid = false;
+	this.state.view.validate();
+	this.redrawHandles();
+	
+	// Restores current state
+	this.state.setState(tempState);
+};
+
+/**
+ * Function: mouseUp
+ * 
+ * Handles the event by applying the changes to the geometry.
+ */
+mxVertexHandler.prototype.mouseUp = function(sender, me)
+{
+	if (this.index != null && this.state != null)
+	{
+		var point = new mxPoint(me.getGraphX(), me.getGraphY());
+
+		this.graph.getModel().beginUpdate();
+		try
+		{
+			if (this.index <= mxEvent.CUSTOM_HANDLE)
+			{
+				if (this.customHandles != null)
+				{
+					this.customHandles[mxEvent.CUSTOM_HANDLE - this.index].active = false;
+					this.customHandles[mxEvent.CUSTOM_HANDLE - this.index].execute();
+				}
+			}
+			else if (this.index == mxEvent.ROTATION_HANDLE)
+			{
+				if (this.currentAlpha != null)
+				{
+					var delta = this.currentAlpha - (this.state.style[mxConstants.STYLE_ROTATION] || 0);
+					
+					if (delta != 0)
+					{
+						this.rotateCell(this.state.cell, delta);
+					}
+				}
+				else
+				{
+					this.rotateClick();
+				}
+			}
+			else
+			{
+				var gridEnabled = this.graph.isGridEnabledEvent(me.getEvent());
+				var alpha = mxUtils.toRadians(this.state.style[mxConstants.STYLE_ROTATION] || '0');
+				var cos = Math.cos(-alpha);
+				var sin = Math.sin(-alpha);
+				
+				var dx = point.x - this.startX;
+				var dy = point.y - this.startY;
+				
+				// Rotates vector for mouse gesture
+				var tx = cos * dx - sin * dy;
+				var ty = sin * dx + cos * dy;
+				
+				dx = tx;
+				dy = ty;
+				
+				var s = this.graph.view.scale;
+				var recurse = this.isRecursiveResize(this.state, me);
+				this.resizeCell(this.state.cell, this.roundLength(dx / s), this.roundLength(dy / s),
+					this.index, gridEnabled, this.isConstrainedEvent(me), recurse);
+			}
+		}
+		finally
+		{
+			this.graph.getModel().endUpdate();
+		}
+
+		me.consume();
+		this.reset();
+	}
+};
+
+/**
+ * Function: rotateCell
+ * 
+ * Rotates the given cell to the given rotation.
+ */
+mxVertexHandler.prototype.isRecursiveResize = function(state, me)
+{
+	return this.graph.isRecursiveResize(this.state);
+};
+
+/**
+ * Function: rotateClick
+ * 
+ * Hook for subclassers to implement a single click on the rotation handle.
+ * This code is executed as part of the model transaction. This implementation
+ * is empty.
+ */
+mxVertexHandler.prototype.rotateClick = function() { };
+
+/**
+ * Function: rotateCell
+ * 
+ * Rotates the given cell and its children by the given angle in degrees.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to be rotated.
+ * angle - Angle in degrees.
+ */
+mxVertexHandler.prototype.rotateCell = function(cell, angle, parent)
+{
+	if (angle != 0)
+	{
+		var model = this.graph.getModel();
+
+		if (model.isVertex(cell) || model.isEdge(cell))
+		{
+			if (!model.isEdge(cell))
+			{
+				var state = this.graph.view.getState(cell);
+				var style = (state != null) ? state.style : this.graph.getCellStyle(cell);
+		
+				if (style != null)
+				{
+					var total = (style[mxConstants.STYLE_ROTATION] || 0) + angle;
+					this.graph.setCellStyles(mxConstants.STYLE_ROTATION, total, [cell]);
+				}
+			}
+			
+			var geo = this.graph.getCellGeometry(cell);
+			
+			if (geo != null)
+			{
+				var pgeo = this.graph.getCellGeometry(parent);
+				
+				if (pgeo != null && !model.isEdge(parent))
+				{
+					geo = geo.clone();
+					geo.rotate(angle, new mxPoint(pgeo.width / 2, pgeo.height / 2));
+					model.setGeometry(cell, geo);
+				}
+				
+				if ((model.isVertex(cell) && !geo.relative) || model.isEdge(cell))
+				{
+					// Recursive rotation
+					var childCount = model.getChildCount(cell);
+					
+					for (var i = 0; i < childCount; i++)
+					{
+						this.rotateCell(model.getChildAt(cell, i), angle, cell);
+					}
+				}
+			}
+		}
+	}
+};
+
+/**
+ * Function: reset
+ * 
+ * Resets the state of this handler.
+ */
+mxVertexHandler.prototype.reset = function()
+{
+	if (this.sizers != null && this.index != null && this.sizers[this.index] != null &&
+		this.sizers[this.index].node.style.display == 'none')
+	{
+		this.sizers[this.index].node.style.display = '';
+	}
+
+	this.currentAlpha = null;
+	this.inTolerance = null;
+	this.index = null;
+
+	// TODO: Reset and redraw cell states for live preview
+	if (this.preview != null)
+	{
+		this.preview.destroy();
+		this.preview = null;
+	}
+
+	if (this.livePreview && this.sizers != null)
+	{
+		for (var i = 0; i < this.sizers.length; i++)
+		{
+			if (this.sizers[i] != null)
+			{
+				this.sizers[i].node.style.display = '';
+			}
+		}
+	}
+
+	if (this.customHandles != null)
+	{
+		for (var i = 0; i < this.customHandles.length; i++)
+		{
+			if (this.customHandles[i].active)
+			{
+				this.customHandles[i].active = false;
+				this.customHandles[i].reset();
+			}
+			else
+			{
+				this.customHandles[i].setVisible(true);
+			}
+		}
+	}
+	
+	// Checks if handler has been destroyed
+	if (this.selectionBorder != null)
+	{
+		this.selectionBorder.node.style.display = 'inline';
+		this.selectionBounds = this.getSelectionBounds(this.state);
+		this.bounds = new mxRectangle(this.selectionBounds.x, this.selectionBounds.y,
+			this.selectionBounds.width, this.selectionBounds.height);
+		this.drawPreview();
+	}
+
+	this.removeHint();
+	this.redrawHandles();
+	this.edgeHandlers = null;
+	this.unscaledBounds = null;
+};
+
+/**
+ * Function: resizeCell
+ * 
+ * Uses the given vector to change the bounds of the given cell
+ * in the graph using <mxGraph.resizeCell>.
+ */
+mxVertexHandler.prototype.resizeCell = function(cell, dx, dy, index, gridEnabled, constrained, recurse)
+{
+	var geo = this.graph.model.getGeometry(cell);
+	
+	if (geo != null)
+	{
+		if (index == mxEvent.LABEL_HANDLE)
+		{
+			var scale = this.graph.view.scale;
+			dx = Math.round((this.labelShape.bounds.getCenterX() - this.startX) / scale);
+			dy = Math.round((this.labelShape.bounds.getCenterY() - this.startY) / scale);
+			
+			geo = geo.clone();
+			
+			if (geo.offset == null)
+			{
+				geo.offset = new mxPoint(dx, dy);
+			}
+			else
+			{
+				geo.offset.x += dx;
+				geo.offset.y += dy;
+			}
+			
+			this.graph.model.setGeometry(cell, geo);
+		}
+		else if (this.unscaledBounds != null)
+		{
+			var scale = this.graph.view.scale;
+
+			if (this.childOffsetX != 0 || this.childOffsetY != 0)
+			{
+				this.moveChildren(cell, Math.round(this.childOffsetX / scale), Math.round(this.childOffsetY / scale));
+			}
+
+			this.graph.resizeCell(cell, this.unscaledBounds, recurse);
+		}
+	}
+};
+
+/**
+ * Function: moveChildren
+ * 
+ * Moves the children of the given cell by the given vector.
+ */
+mxVertexHandler.prototype.moveChildren = function(cell, dx, dy)
+{
+	var model = this.graph.getModel();
+	var childCount = model.getChildCount(cell);
+	
+	for (var i = 0; i < childCount; i++)
+	{
+		var child = model.getChildAt(cell, i);
+		var geo = this.graph.getCellGeometry(child);
+		
+		if (geo != null)
+		{
+			geo = geo.clone();
+			geo.translate(dx, dy);
+			model.setGeometry(child, geo);
+		}
+	}
+};
+/**
+ * Function: union
+ * 
+ * Returns the union of the given bounds and location for the specified
+ * handle index.
+ * 
+ * To override this to limit the size of vertex via a minWidth/-Height style,
+ * the following code can be used.
+ * 
+ * (code)
+ * var vertexHandlerUnion = mxVertexHandler.prototype.union;
+ * mxVertexHandler.prototype.union = function(bounds, dx, dy, index, gridEnabled, scale, tr, constrained)
+ * {
+ *   var result = vertexHandlerUnion.apply(this, arguments);
+ *   
+ *   result.width = Math.max(result.width, mxUtils.getNumber(this.state.style, 'minWidth', 0));
+ *   result.height = Math.max(result.height, mxUtils.getNumber(this.state.style, 'minHeight', 0));
+ *   
+ *   return result;
+ * };
+ * (end)
+ * 
+ * The minWidth/-Height style can then be used as follows:
+ * 
+ * (code)
+ * graph.insertVertex(parent, null, 'Hello,', 20, 20, 80, 30, 'minWidth=100;minHeight=100;');
+ * (end)
+ * 
+ * To override this to update the height for a wrapped text if the width of a vertex is
+ * changed, the following can be used.
+ * 
+ * (code)
+ * var mxVertexHandlerUnion = mxVertexHandler.prototype.union;
+ * mxVertexHandler.prototype.union = function(bounds, dx, dy, index, gridEnabled, scale, tr, constrained)
+ * {
+ *   var result = mxVertexHandlerUnion.apply(this, arguments);
+ *   var s = this.state;
+ *   
+ *   if (this.graph.isHtmlLabel(s.cell) && (index == 3 || index == 4) &&
+ *       s.text != null && s.style[mxConstants.STYLE_WHITE_SPACE] == 'wrap')
+ *   {
+ *     var label = this.graph.getLabel(s.cell);
+ *     var fontSize = mxUtils.getNumber(s.style, mxConstants.STYLE_FONTSIZE, mxConstants.DEFAULT_FONTSIZE);
+ *     var ww = result.width / s.view.scale - s.text.spacingRight - s.text.spacingLeft
+ *     
+ *     result.height = mxUtils.getSizeForString(label, fontSize, s.style[mxConstants.STYLE_FONTFAMILY], ww).height;
+ *   }
+ *   
+ *   return result;
+ * };
+ * (end)
+ */
+mxVertexHandler.prototype.union = function(bounds, dx, dy, index, gridEnabled, scale, tr, constrained, centered)
+{
+	if (this.singleSizer)
+	{
+		var x = bounds.x + bounds.width + dx;
+		var y = bounds.y + bounds.height + dy;
+		
+		if (gridEnabled)
+		{
+			x = this.graph.snap(x / scale) * scale;
+			y = this.graph.snap(y / scale) * scale;
+		}
+		
+		var rect = new mxRectangle(bounds.x, bounds.y, 0, 0);
+		rect.add(new mxRectangle(x, y, 0, 0));
+		
+		return rect;
+	}
+	else
+	{
+		var w0 = bounds.width;
+		var h0 = bounds.height;
+		var left = bounds.x - tr.x * scale;
+		var right = left + w0;
+		var top = bounds.y - tr.y * scale;
+		var bottom = top + h0;
+		
+		var cx = left + w0 / 2;
+		var cy = top + h0 / 2;
+		
+		if (index > 4 /* Bottom Row */)
+		{
+			bottom = bottom + dy;
+			
+			if (gridEnabled)
+			{
+				bottom = this.graph.snap(bottom / scale) * scale;
+			}
+		}
+		else if (index < 3 /* Top Row */)
+		{
+			top = top + dy;
+			
+			if (gridEnabled)
+			{
+				top = this.graph.snap(top / scale) * scale;
+			}
+		}
+		
+		if (index == 0 || index == 3 || index == 5 /* Left */)
+		{
+			left += dx;
+			
+			if (gridEnabled)
+			{
+				left = this.graph.snap(left / scale) * scale;
+			}
+		}
+		else if (index == 2 || index == 4 || index == 7 /* Right */)
+		{
+			right += dx;
+			
+			if (gridEnabled)
+			{
+				right = this.graph.snap(right / scale) * scale;
+			}
+		}
+		
+		var width = right - left;
+		var height = bottom - top;
+
+		if (constrained)
+		{
+			var geo = this.graph.getCellGeometry(this.state.cell);
+
+			if (geo != null)
+			{
+				var aspect = geo.width / geo.height;
+				
+				if (index== 1 || index== 2 || index == 7 || index == 6)
+				{
+					width = height * aspect;
+				}
+				else
+				{
+					height = width / aspect;
+				}
+				
+				if (index == 0)
+				{
+					left = right - width;
+					top = bottom - height;
+				}
+			}
+		}
+
+		if (centered)
+		{
+			width += (width - w0);
+			height += (height - h0);
+			
+			var cdx = cx - (left + width / 2);
+			var cdy = cy - (top + height / 2);
+
+			left += cdx;
+			top += cdy;
+			right += cdx;
+			bottom += cdy;
+		}
+
+		// Flips over left side
+		if (width < 0)
+		{
+			left += width;
+			width = Math.abs(width);
+		}
+		
+		// Flips over top side
+		if (height < 0)
+		{
+			top += height;
+			height = Math.abs(height);
+		}
+
+		var result = new mxRectangle(left + tr.x * scale, top + tr.y * scale, width, height);
+		
+		if (this.minBounds != null)
+		{
+			result.width = Math.max(result.width, this.minBounds.x * scale + this.minBounds.width * scale +
+				Math.max(0, this.x0 * scale - result.x));
+			result.height = Math.max(result.height, this.minBounds.y * scale + this.minBounds.height * scale +
+				Math.max(0, this.y0 * scale - result.y));
+		}
+		
+		return result;
+	}
+};
+
+/**
+ * Function: redraw
+ * 
+ * Redraws the handles and the preview.
+ */
+mxVertexHandler.prototype.redraw = function()
+{
+	this.selectionBounds = this.getSelectionBounds(this.state);
+	this.bounds = new mxRectangle(this.selectionBounds.x, this.selectionBounds.y, this.selectionBounds.width, this.selectionBounds.height);
+	
+	this.redrawHandles();
+	this.drawPreview();
+};
+
+/**
+ * Returns the padding to be used for drawing handles for the current <bounds>.
+ */
+mxVertexHandler.prototype.getHandlePadding = function()
+{
+	// KNOWN: Tolerance depends on event type (eg. 0 for mouse events)
+	var result = new mxPoint(0, 0);
+	var tol = this.tolerance;
+
+	if (this.sizers != null && this.sizers.length > 0 && this.sizers[0] != null &&
+		(this.bounds.width < 2 * this.sizers[0].bounds.width + 2 * tol ||
+		this.bounds.height < 2 * this.sizers[0].bounds.height + 2 * tol))
+	{
+		tol /= 2;
+		
+		result.x = this.sizers[0].bounds.width + tol;
+		result.y = this.sizers[0].bounds.height + tol;
+	}
+	
+	return result;
+};
+
+/**
+ * Function: redrawHandles
+ * 
+ * Redraws the handles. To hide certain handles the following code can be used.
+ * 
+ * (code)
+ * mxVertexHandler.prototype.redrawHandles = function()
+ * {
+ *   mxVertexHandlerRedrawHandles.apply(this, arguments);
+ *   
+ *   if (this.sizers != null && this.sizers.length > 7)
+ *   {
+ *     this.sizers[1].node.style.display = 'none';
+ *     this.sizers[6].node.style.display = 'none';
+ *   }
+ * };
+ * (end)
+ */
+mxVertexHandler.prototype.redrawHandles = function()
+{
+	var tol = this.tolerance;
+	this.horizontalOffset = 0;
+	this.verticalOffset = 0;
+	var s = this.bounds;
+
+	if (this.sizers != null && this.sizers.length > 0 && this.sizers[0] != null)
+	{
+		if (this.index == null && this.manageSizers && this.sizers.length >= 8)
+		{
+			// KNOWN: Tolerance depends on event type (eg. 0 for mouse events)
+			var padding = this.getHandlePadding();
+			this.horizontalOffset = padding.x;
+			this.verticalOffset = padding.y;
+			
+			if (this.horizontalOffset != 0 || this.verticalOffset != 0)
+			{
+				s = new mxRectangle(s.x, s.y, s.width, s.height);
+
+				s.x -= this.horizontalOffset / 2;
+				s.width += this.horizontalOffset;
+				s.y -= this.verticalOffset / 2;
+				s.height += this.verticalOffset;
+			}
+			
+			if (this.sizers.length >= 8)
+			{
+				if ((s.width < 2 * this.sizers[0].bounds.width + 2 * tol) ||
+					(s.height < 2 * this.sizers[0].bounds.height + 2 * tol))
+				{
+					this.sizers[0].node.style.display = 'none';
+					this.sizers[2].node.style.display = 'none';
+					this.sizers[5].node.style.display = 'none';
+					this.sizers[7].node.style.display = 'none';
+				}
+				else
+				{
+					this.sizers[0].node.style.display = '';
+					this.sizers[2].node.style.display = '';
+					this.sizers[5].node.style.display = '';
+					this.sizers[7].node.style.display = '';
+				}
+			}
+		}
+
+		var r = s.x + s.width;
+		var b = s.y + s.height;
+		
+		if (this.singleSizer)
+		{
+			this.moveSizerTo(this.sizers[0], r, b);
+		}
+		else
+		{
+			var cx = s.x + s.width / 2;
+			var cy = s.y + s.height / 2;
+			
+			if (this.sizers.length >= 8)
+			{
+				var crs = ['nw-resize', 'n-resize', 'ne-resize', 'e-resize', 'se-resize', 's-resize', 'sw-resize', 'w-resize'];
+				
+				var alpha = mxUtils.toRadians(this.state.style[mxConstants.STYLE_ROTATION] || '0');
+				var cos = Math.cos(alpha);
+				var sin = Math.sin(alpha);
+				
+				var da = Math.round(alpha * 4 / Math.PI);
+				
+				var ct = new mxPoint(s.getCenterX(), s.getCenterY());
+				var pt = mxUtils.getRotatedPoint(new mxPoint(s.x, s.y), cos, sin, ct);
+				
+				this.moveSizerTo(this.sizers[0], pt.x, pt.y);
+				this.sizers[0].setCursor(crs[mxUtils.mod(0 + da, crs.length)]);
+				
+				pt.x = cx;
+				pt.y = s.y;
+				pt = mxUtils.getRotatedPoint(pt, cos, sin, ct);
+				
+				this.moveSizerTo(this.sizers[1], pt.x, pt.y);
+				this.sizers[1].setCursor(crs[mxUtils.mod(1 + da, crs.length)]);
+				
+				pt.x = r;
+				pt.y = s.y;
+				pt = mxUtils.getRotatedPoint(pt, cos, sin, ct);
+				
+				this.moveSizerTo(this.sizers[2], pt.x, pt.y);
+				this.sizers[2].setCursor(crs[mxUtils.mod(2 + da, crs.length)]);
+				
+				pt.x = s.x;
+				pt.y = cy;
+				pt = mxUtils.getRotatedPoint(pt, cos, sin, ct);
+				
+				this.moveSizerTo(this.sizers[3], pt.x, pt.y);
+				this.sizers[3].setCursor(crs[mxUtils.mod(7 + da, crs.length)]);
+
+				pt.x = r;
+				pt.y = cy;
+				pt = mxUtils.getRotatedPoint(pt, cos, sin, ct);
+				
+				this.moveSizerTo(this.sizers[4], pt.x, pt.y);
+				this.sizers[4].setCursor(crs[mxUtils.mod(3 + da, crs.length)]);
+
+				pt.x = s.x;
+				pt.y = b;
+				pt = mxUtils.getRotatedPoint(pt, cos, sin, ct);
+				
+				this.moveSizerTo(this.sizers[5], pt.x, pt.y);
+				this.sizers[5].setCursor(crs[mxUtils.mod(6 + da, crs.length)]);
+
+				pt.x = cx;
+				pt.y = b;
+				pt = mxUtils.getRotatedPoint(pt, cos, sin, ct);
+				
+				this.moveSizerTo(this.sizers[6], pt.x, pt.y);
+				this.sizers[6].setCursor(crs[mxUtils.mod(5 + da, crs.length)]);
+
+				pt.x = r;
+				pt.y = b;
+				pt = mxUtils.getRotatedPoint(pt, cos, sin, ct);
+				
+				this.moveSizerTo(this.sizers[7], pt.x, pt.y);
+				this.sizers[7].setCursor(crs[mxUtils.mod(4 + da, crs.length)]);
+				
+				this.moveSizerTo(this.sizers[8], cx + this.state.absoluteOffset.x, cy + this.state.absoluteOffset.y);
+			}
+			else if (this.state.width >= 2 && this.state.height >= 2)
+			{
+				this.moveSizerTo(this.sizers[0], cx + this.state.absoluteOffset.x, cy + this.state.absoluteOffset.y);
+			}
+			else
+			{
+				this.moveSizerTo(this.sizers[0], this.state.x, this.state.y);
+			}
+		}
+	}
+
+	if (this.rotationShape != null)
+	{
+		var alpha = mxUtils.toRadians((this.currentAlpha != null) ? this.currentAlpha : this.state.style[mxConstants.STYLE_ROTATION] || '0');
+		var cos = Math.cos(alpha);
+		var sin = Math.sin(alpha);
+		
+		var ct = new mxPoint(this.state.getCenterX(), this.state.getCenterY());
+		var pt = mxUtils.getRotatedPoint(new mxPoint(s.x + s.width / 2, s.y + this.rotationHandleVSpacing), cos, sin, ct);
+
+		if (this.rotationShape.node != null)
+		{
+			this.moveSizerTo(this.rotationShape, pt.x, pt.y);
+		}
+	}
+	
+	if (this.selectionBorder != null)
+	{
+		this.selectionBorder.rotation = Number(this.state.style[mxConstants.STYLE_ROTATION] || '0');
+	}
+	
+	if (this.edgeHandlers != null)
+	{		
+		for (var i = 0; i < this.edgeHandlers.length; i++)
+		{
+			this.edgeHandlers[i].redraw();
+		}
+	}
+
+	if (this.customHandles != null)
+	{
+		for (var i = 0; i < this.customHandles.length; i++)
+		{
+			var temp = this.customHandles[i].shape.node.style.display;
+			this.customHandles[i].redraw();
+			this.customHandles[i].shape.node.style.display = temp;
+		}
+	}
+
+	this.updateParentHighlight();
+};
+
+/**
+ * Function: updateParentHighlight
+ * 
+ * Updates the highlight of the parent if <parentHighlightEnabled> is true.
+ */
+mxVertexHandler.prototype.updateParentHighlight = function()
+{
+	// If not destroyed
+	if (this.selectionBorder != null)
+	{
+		if (this.parentHighlight != null)
+		{
+			var parent = this.graph.model.getParent(this.state.cell);
+	
+			if (this.graph.model.isVertex(parent))
+			{
+				var pstate = this.graph.view.getState(parent);
+				var b = this.parentHighlight.bounds;
+				
+				if (pstate != null && (b.x != pstate.x || b.y != pstate.y ||
+					b.width != pstate.width || b.height != pstate.height))
+				{
+					this.parentHighlight.bounds = pstate;
+					this.parentHighlight.redraw();
+				}
+			}
+			else
+			{
+				this.parentHighlight.destroy();
+				this.parentHighlight = null;
+			}
+		}
+		else if (this.parentHighlightEnabled)
+		{
+			var parent = this.graph.model.getParent(this.state.cell);
+			
+			if (this.graph.model.isVertex(parent))
+			{
+				var pstate = this.graph.view.getState(parent);
+				
+				if (pstate != null)
+				{
+					this.parentHighlight = this.createParentHighlightShape(pstate);
+					// VML dialect required here for event transparency in IE
+					this.parentHighlight.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+					this.parentHighlight.pointerEvents = false;
+					this.parentHighlight.rotation = Number(pstate.style[mxConstants.STYLE_ROTATION] || '0');
+					this.parentHighlight.init(this.graph.getView().getOverlayPane());
+				}
+			}
+		}
+	}
+};
+
+/**
+ * Function: drawPreview
+ * 
+ * Redraws the preview.
+ */
+mxVertexHandler.prototype.drawPreview = function()
+{
+	if (this.preview != null)
+	{
+		this.preview.bounds = this.bounds;
+		
+		if (this.preview.node.parentNode == this.graph.container)
+		{
+			this.preview.bounds.width = Math.max(0, this.preview.bounds.width - 1);
+			this.preview.bounds.height = Math.max(0, this.preview.bounds.height - 1);
+		}
+	
+		this.preview.rotation = Number(this.state.style[mxConstants.STYLE_ROTATION] || '0');
+		this.preview.redraw();
+	}
+	
+	this.selectionBorder.bounds = this.bounds;
+	this.selectionBorder.redraw();
+	
+	if (this.parentHighlight != null)
+	{
+		this.parentHighlight.redraw();
+	}
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the handler and all its resources and DOM nodes.
+ */
+mxVertexHandler.prototype.destroy = function()
+{
+	if (this.escapeHandler != null)
+	{
+		this.state.view.graph.removeListener(this.escapeHandler);
+		this.escapeHandler = null;
+	}
+	
+	if (this.preview != null)
+	{
+		this.preview.destroy();
+		this.preview = null;
+	}
+	
+	if (this.parentHighlight != null)
+	{
+		this.parentHighlight.destroy();
+		this.parentHighlight = null;
+	}
+	
+	if (this.selectionBorder != null)
+	{
+		this.selectionBorder.destroy();
+		this.selectionBorder = null;
+	}
+	
+	this.labelShape = null;
+	this.removeHint();
+
+	if (this.sizers != null)
+	{
+		for (var i = 0; i < this.sizers.length; i++)
+		{
+			this.sizers[i].destroy();
+		}
+		
+		this.sizers = null;
+	}
+
+	if (this.customHandles != null)
+	{
+		for (var i = 0; i < this.customHandles.length; i++)
+		{
+			this.customHandles[i].destroy();
+		}
+		
+		this.customHandles = null;
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/index.txt b/airavata-kubernetes/web-console/src/assets/js/index.txt
similarity index 95%
copy from airavata-kubernetes/workflow-composer/src/js/index.txt
copy to airavata-kubernetes/web-console/src/assets/js/index.txt
index f3631d6..084fbb8 100644
--- a/airavata-kubernetes/workflow-composer/src/js/index.txt
+++ b/airavata-kubernetes/web-console/src/assets/js/index.txt
@@ -8,7 +8,7 @@ Overview:
 
   The *editor* package provides the classes required to implement a diagram
   editor. The main class in this package is <mxEditor>.
-  
+
   The *view* and *model* packages implement the graph component, represented
   by <mxGraph>. It refers to a <mxGraphModel> which contains <mxCell>s and
   caches the state of the cells in a <mxGraphView>. The cells are painted
@@ -16,15 +16,15 @@ Overview:
   Undo history is implemented in <mxUndoManager>. To display an icon on the
   graph, <mxCellOverlay> may be used. Validation rules are defined with
   <mxMultiplicity>.
-  
+
   The *handler*, *layout* and *shape* packages contain event listeners,
   layout algorithms and shapes, respectively. The graph event listeners
   include <mxRubberband> for rubberband selection, <mxTooltipHandler>
   for tooltips and <mxGraphHandler> for  basic cell modifications.
-  <mxCompactTreeLayout> implements a tree layout algorithm, and the 
+  <mxCompactTreeLayout> implements a tree layout algorithm, and the
   shape package provides various shapes, which are subclasses of
   <mxShape>.
-  
+
   The *util* package provides utility classes including <mxClipboard> for
   copy-paste, <mxDatatransfer> for drag-and-drop, <mxConstants> for keys and
   values of stylesheets, <mxEvent> and <mxUtils> for cross-browser
@@ -34,7 +34,7 @@ Overview:
   The *io* package implements a generic <mxObjectCodec> for turning
   JavaScript objects into XML. The main class is <mxCodec>.
   <mxCodecRegistry> is the global registry for custom codecs.
-  
+
 Events:
 
   There are three different types of events, namely native DOM events,
@@ -44,36 +44,36 @@ Events:
   Some helper methods for handling native events are provided in <mxEvent>. It
   also takes care of resolving cycles between DOM nodes and JavaScript event
   handlers, which can lead to memory leaks in IE6.
-  
+
   Most custom events in mxGraph are implemented using <mxEventSource>. Its
   listeners are functions that take a sender and <mxEventObject>. Additionally,
   the <mxGraph> class fires special <mxMouseEvents> which are handled using
   mouse listeners, which are objects that provide a mousedown, mousemove and
   mouseup method.
-  
+
   Events in <mxEventSource> are fired using <mxEventSource.fireEvent>.
   Listeners are added and removed using <mxEventSource.addListener> and
   <mxEventSource.removeListener>. <mxMouseEvents> in <mxGraph> are fired using
   <mxGraph.fireMouseEvent>. Listeners are added and removed using
   <mxGraph.addMouseListener> and <mxGraph.removeMouseListener>, respectively.
-  
+
 Key bindings:
-  
+
   The following key bindings are defined for mouse events in the client across
   all browsers and platforms:
-  
+
   - Control-Drag: Duplicates (clones) selected cells
   - Shift-Rightlick: Shows the context menu
   - Alt-Click: Forces rubberband (aka. marquee)
   - Control-Select: Toggles the selection state
   - Shift-Drag: Constrains the offset to one direction
   - Shift-Control-Drag: Panning (also Shift-Rightdrag)
-  
+
 Configuration:
 
   The following global variables may be defined before the client is loaded to
   specify its language or base path, respectively.
-  
+
   - mxBasePath: Specifies the path in <mxClient.basePath>.
   - mxImageBasePath: Specifies the path in <mxClient.imageBasePath>.
   - mxLanguage: Specifies the language for resources in <mxClient.language>.
@@ -86,7 +86,7 @@ Reserved Words:
   The mx prefix is used for all classes and objects in mxGraph. The mx prefix
   can be seen as the global namespace for all JavaScript code in mxGraph. The
   following fieldnames should not be used in objects.
-  
+
   - *mxObjectId*: If the object is used with mxObjectIdentity
   - *as*: If the object is a field of another object
   - *id*: If the object is an idref in a codec
@@ -100,13 +100,13 @@ Files:
 
   The library contains these relative filenames. All filenames are relative
   to <mxClient.basePath>.
-  
+
 Built-in Images:
-  
-  All images are loaded from the <mxClient.imageBasePath>, 
-  which you can change to reflect your environment. The image variables can 
+
+  All images are loaded from the <mxClient.imageBasePath>,
+  which you can change to reflect your environment. The image variables can
   also be changed individually.
-  
+
   - mxGraph.prototype.collapsedImage
   - mxGraph.prototype.expandedImage
   - mxGraph.prototype.warningImage
@@ -119,17 +119,17 @@ Built-in Images:
   - mxUtils.errorImage
   - mxConstraintHandler.prototype.pointImage
 
-  The basename of the warning image (images/warning without extension) used in 
+  The basename of the warning image (images/warning without extension) used in
   <mxGraph.setCellWarning> is defined in <mxGraph.warningImage>.
 
 Resources:
-  
+
   The <mxEditor> and <mxGraph> classes add the following resources to
   <mxResources> at class loading time:
 
   - resources/editor*.properties
   - resources/graph*.properties
-  
+
   By default, the library ships with English and German resource files.
 
 Images:
@@ -137,16 +137,16 @@ Images:
   Recommendations for using images. Use GIF images (256 color palette) in HTML
   elements (such as the toolbar and context menu), and PNG images (24 bit) for
   all images which appear inside the graph component.
-  
-  - For PNG images inside HTML elements, Internet Explorer will ignore any 
+
+  - For PNG images inside HTML elements, Internet Explorer will ignore any
     transparency information.
-  - For GIF images inside the graph, Firefox on the Mac will display strange 
-    colors. Furthermore, only the first image for animated GIFs is displayed 
+  - For GIF images inside the graph, Firefox on the Mac will display strange
+    colors. Furthermore, only the first image for animated GIFs is displayed
     on the Mac.
-    
+
   For faster image rendering during application runtime, images can be
   prefetched using the following code:
-  
+
   (code)
   var image = new Image();
   image.src = url_to_image;
@@ -164,27 +164,27 @@ Deployment:
   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 
+  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
@@ -205,7 +205,7 @@ Subclassing:
   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
+  applied so that the type of an object can be retrieved via the name of it�s
   constructor using mxUtils.getFunctionName(obj.constructor).
 
 Constructor:
@@ -222,7 +222,7 @@ Constructor:
     mxGraph.call(this, container);
   }
   (end)
-  
+
   The prototype of MyGraph inherits from mxGraph as follows. As usual, the
   constructor is redefined after extending the superclass:
 
@@ -230,7 +230,7 @@ Constructor:
   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.
@@ -240,12 +240,12 @@ Constructor:
   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)
   {
@@ -255,12 +255,12 @@ Functions:
     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.
+  superclass as follows, which is another way of �subclassing� in JavaScript.
 
   (code)
   mxGraph.prototype.isCellSelectable = function(cell)
@@ -274,8 +274,8 @@ Functions:
 
   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
+
+  In referenceId 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:
 
@@ -286,7 +286,7 @@ Functions:
     return enc.encode(this.getModel());
   }
   (end)
-  
+
 Variables:
 
   Likewise, a new field is declared and defined as follows.
@@ -294,7 +294,7 @@ Variables:
   (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.
@@ -303,7 +303,7 @@ Variables:
   function MyGraph(container)
   {
     mxGraph.call(this, container);
-    
+
     this.myField = new Array();
   }
   (end)
diff --git a/airavata-kubernetes/web-console/src/assets/js/io/mxCellCodec.js b/airavata-kubernetes/web-console/src/assets/js/io/mxCellCodec.js
new file mode 100644
index 0000000..253c96f
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/io/mxCellCodec.js
@@ -0,0 +1,189 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+mxCodecRegistry.register(function()
+{
+	/**
+	 * Class: mxCellCodec
+	 *
+	 * Codec for <mxCell>s. This class is created and registered
+	 * dynamically at load time and used implicitely via <mxCodec>
+	 * and the <mxCodecRegistry>.
+	 *
+	 * Transient Fields:
+	 *
+	 * - children
+	 * - edges
+	 * - overlays
+	 * - mxTransient
+	 *
+	 * Reference Fields:
+	 *
+	 * - parent
+	 * - source
+	 * - target
+	 * 
+	 * Transient fields can be added using the following code:
+	 * 
+	 * mxCodecRegistry.getCodec(mxCell).exclude.push('name_of_field');
+	 * 
+	 * To subclass <mxCell>, replace the template and add an alias as
+	 * follows.
+	 * 
+	 * (code)
+	 * function CustomCell(value, geometry, style)
+	 * {
+	 *   mxCell.apply(this, arguments);
+	 * }
+	 * 
+	 * mxUtils.extend(CustomCell, mxCell);
+	 * 
+	 * mxCodecRegistry.getCodec(mxCell).template = new CustomCell();
+	 * mxCodecRegistry.addAlias('CustomCell', 'mxCell');
+	 * (end)
+	 */
+	var codec = new mxObjectCodec(new mxCell(),
+		['children', 'edges', 'overlays', 'mxTransient'],
+		['parent', 'source', 'target']);
+
+	/**
+	 * Function: isCellCodec
+	 *
+	 * Returns true since this is a cell codec.
+	 */
+	codec.isCellCodec = function()
+	{
+		return true;
+	};
+
+	/**
+	 * Overidden to disable conversion of value to number.
+	 */
+	codec.isNumericAttribute = function(dec, attr, obj)
+	{
+		return attr.nodeName !== 'value' && mxObjectCodec.prototype.isNumericAttribute.apply(this, arguments);
+	};
+	
+	/**
+	 * Function: isExcluded
+	 *
+	 * Excludes user objects that are XML nodes.
+	 */ 
+	codec.isExcluded = function(obj, attr, value, isWrite)
+	{
+		return mxObjectCodec.prototype.isExcluded.apply(this, arguments) ||
+			(isWrite && attr == 'value' &&
+			value.nodeType == mxConstants.NODETYPE_ELEMENT);
+	};
+	
+	/**
+	 * Function: afterEncode
+	 *
+	 * Encodes an <mxCell> and wraps the XML up inside the
+	 * XML of the user object (inversion).
+	 */
+	codec.afterEncode = function(enc, obj, node)
+	{
+		if (obj.value != null && obj.value.nodeType == mxConstants.NODETYPE_ELEMENT)
+		{
+			// Wraps the graphical annotation up in the user object (inversion)
+			// by putting the result of the default encoding into a clone of the
+			// user object (node type 1) and returning this cloned user object.
+			var tmp = node;
+			node = mxUtils.importNode(enc.document, obj.value, true);
+			node.appendChild(tmp);
+			
+			// Moves the id attribute to the outermost XML node, namely the
+			// node which denotes the object boundaries in the file.
+			var id = tmp.getAttribute('id');
+			node.setAttribute('id', id);
+			tmp.removeAttribute('id');
+		}
+
+		return node;
+	};
+
+	/**
+	 * Function: beforeDecode
+	 *
+	 * Decodes an <mxCell> and uses the enclosing XML node as
+	 * the user object for the cell (inversion).
+	 */
+	codec.beforeDecode = function(dec, node, obj)
+	{
+		var inner = node.cloneNode(true);
+		var classname = this.getName();
+		
+		if (node.nodeName != classname)
+		{
+			// Passes the inner graphical annotation node to the
+			// object codec for further processing of the cell.
+			var tmp = node.getElementsByTagName(classname)[0];
+			
+			if (tmp != null && tmp.parentNode == node)
+			{
+				mxUtils.removeWhitespace(tmp, true);
+				mxUtils.removeWhitespace(tmp, false);
+				tmp.parentNode.removeChild(tmp);
+				inner = tmp;
+			}
+			else
+			{
+				inner = null;
+			}
+			
+			// Creates the user object out of the XML node
+			obj.value = node.cloneNode(true);
+			var id = obj.value.getAttribute('id');
+			
+			if (id != null)
+			{
+				obj.setId(id);
+				obj.value.removeAttribute('id');
+			}
+		}
+		else
+		{
+			// Uses ID from XML file as ID for cell in model
+			obj.setId(node.getAttribute('id'));
+		}
+			
+		// Preprocesses and removes all Id-references in order to use the
+		// correct encoder (this) for the known references to cells (all).
+		if (inner != null)
+		{
+			for (var i = 0; i < this.idrefs.length; i++)
+			{
+				var attr = this.idrefs[i];
+				var ref = inner.getAttribute(attr);
+				
+				if (ref != null)
+				{
+					inner.removeAttribute(attr);
+					var object = dec.objects[ref] || dec.lookup(ref);
+					
+					if (object == null)
+					{
+						// Needs to decode forward reference
+						var element = dec.getElementById(ref);
+						
+						if (element != null)
+						{
+							var decoder = mxCodecRegistry.codecs[element.nodeName] || this;
+							object = decoder.decode(dec, element);
+						}
+					}
+					
+					obj[attr] = object;
+				}
+			}
+		}
+		
+		return inner;
+	};
+
+	// Returns the codec into the registry
+	return codec;
+
+}());
diff --git a/airavata-kubernetes/web-console/src/assets/js/io/mxChildChangeCodec.js b/airavata-kubernetes/web-console/src/assets/js/io/mxChildChangeCodec.js
new file mode 100644
index 0000000..92d0c76
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/io/mxChildChangeCodec.js
@@ -0,0 +1,149 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+mxCodecRegistry.register(function()
+{
+	/**
+	 * Class: mxChildChangeCodec
+	 *
+	 * Codec for <mxChildChange>s. This class is created and registered
+	 * dynamically at load time and used implicitely via <mxCodec> and
+	 * the <mxCodecRegistry>.
+	 *
+	 * Transient Fields:
+	 *
+	 * - model
+	 * - previous
+	 * - previousIndex
+	 * - child
+	 *
+	 * Reference Fields:
+	 *
+	 * - parent
+	 */
+	var codec = new mxObjectCodec(new mxChildChange(),
+		['model', 'child', 'previousIndex'],
+		['parent', 'previous']);
+
+	/**
+	 * Function: isReference
+	 *
+	 * Returns true for the child attribute if the child
+	 * cell had a previous parent or if we're reading the
+	 * child as an attribute rather than a child node, in
+	 * which case it's always a reference.
+	 */
+	codec.isReference = function(obj, attr, value, isWrite)
+	{
+		if (attr == 'child' &&
+			(obj.previous != null ||
+			!isWrite))
+		{
+			return true;
+		}
+		
+		return mxUtils.indexOf(this.idrefs, attr) >= 0;
+	};
+
+	/**
+	 * Function: afterEncode
+	 *
+	 * Encodes the child recusively and adds the result
+	 * to the given node.
+	 */
+	codec.afterEncode = function(enc, obj, node)
+	{
+		if (this.isReference(obj, 'child',  obj.child, true))
+		{
+			// Encodes as reference (id)
+			node.setAttribute('child', enc.getId(obj.child));
+		}
+		else
+		{
+			// At this point, the encoder is no longer able to know which cells
+			// are new, so we have to encode the complete cell hierarchy and
+			// ignore the ones that are already there at decoding time. Note:
+			// This can only be resolved by moving the notify event into the
+			// execute of the edit.
+			enc.encodeCell(obj.child, node);
+		}
+		
+		return node;
+	};
+
+	/**
+	 * Function: beforeDecode
+	 *
+	 * Decodes the any child nodes as using the respective
+	 * codec from the registry.
+	 */
+	codec.beforeDecode = function(dec, node, obj)
+	{
+		if (node.firstChild != null &&
+			node.firstChild.nodeType == mxConstants.NODETYPE_ELEMENT)
+		{
+			// Makes sure the original node isn't modified
+			node = node.cloneNode(true);
+			
+			var tmp = node.firstChild;
+			obj.child = dec.decodeCell(tmp, false);
+
+			var tmp2 = tmp.nextSibling;
+			tmp.parentNode.removeChild(tmp);
+			tmp = tmp2;
+			
+			while (tmp != null)
+			{
+				tmp2 = tmp.nextSibling;
+				
+				if (tmp.nodeType == mxConstants.NODETYPE_ELEMENT)
+				{
+					// Ignores all existing cells because those do not need to
+					// be re-inserted into the model. Since the encoded version
+					// of these cells contains the new parent, this would leave
+					// to an inconsistent state on the model (ie. a parent
+					// change without a call to parentForCellChanged).
+					var id = tmp.getAttribute('id');
+					
+					if (dec.lookup(id) == null)
+					{
+						dec.decodeCell(tmp);
+					}
+				}
+				
+				tmp.parentNode.removeChild(tmp);
+				tmp = tmp2;
+			}
+		}
+		else
+		{
+			var childRef = node.getAttribute('child');
+			obj.child = dec.getObject(childRef);
+		}
+		
+		return node;
+	};
+	
+	/**
+	 * Function: afterDecode
+	 *
+	 * Restores object state in the child change.
+	 */
+	codec.afterDecode = function(dec, node, obj)
+	{
+		// Cells are encoded here after a complete transaction so the previous
+		// parent must be restored on the cell for the case where the cell was
+		// added. This is needed for the local model to identify the cell as a
+		// new cell and register the ID.
+		obj.child.parent = obj.previous;
+		obj.previous = obj.parent;
+		obj.previousIndex = obj.index;
+
+		return obj;
+	};
+
+	// Returns the codec into the registry
+	return codec;
+
+}());
diff --git a/airavata-kubernetes/web-console/src/assets/js/io/mxCodec.js b/airavata-kubernetes/web-console/src/assets/js/io/mxCodec.js
new file mode 100644
index 0000000..b308387
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/io/mxCodec.js
@@ -0,0 +1,596 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxCodec
+ *
+ * XML codec for JavaScript object graphs. See <mxObjectCodec> for a
+ * description of the general encoding/decoding scheme. This class uses the
+ * codecs registered in <mxCodecRegistry> for encoding/decoding each object.
+ * 
+ * References:
+ * 
+ * In order to resolve references, especially forward references, the mxCodec
+ * constructor must be given the document that contains the referenced
+ * elements.
+ *
+ * Examples:
+ *
+ * The following code is used to encode a graph model.
+ *
+ * (code)
+ * var encoder = new mxCodec();
+ * var result = encoder.encode(graph.getModel());
+ * var xml = mxUtils.getXml(result);
+ * (end)
+ * 
+ * Example:
+ * 
+ * Using the code below, an XML document is decoded into an existing model. The
+ * document may be obtained using one of the functions in mxUtils for loading
+ * an XML file, eg. <mxUtils.get>, or using <mxUtils.parseXml> for parsing an
+ * XML string.
+ * 
+ * (code)
+ * var doc = mxUtils.parseXml(xmlString);
+ * var codec = new mxCodec(doc);
+ * codec.decode(doc.documentElement, graph.getModel());
+ * (end)
+ * 
+ * Example:
+ * 
+ * This example demonstrates parsing a list of isolated cells into an existing
+ * graph model. Note that the cells do not have a parent reference so they can
+ * be added anywhere in the cell hierarchy after parsing.
+ * 
+ * (code)
+ * var xml = '<root><mxCell id="2" value="Hello," vertex="1"><mxGeometry x="20" y="20" width="80" height="30" as="geometry"/></mxCell><mxCell id="3" value="World!" vertex="1"><mxGeometry x="200" y="150" width="80" height="30" as="geometry"/></mxCell><mxCell id="4" value="" edge="1" source="2" target="3"><mxGeometry relative="1" as="geometry"/></mxCell></root>';
+ * var doc = mxUtils.parseXml(xml);
+ * var codec = new mxCodec(doc);
+ * var elt = doc.documentElement.firstChild;
+ * var cells = [];
+ * 
+ * while (elt != null)
+ * {
+ *   cells.push(codec.decode(elt));
+ *   elt = elt.nextSibling;
+ * }
+ * 
+ * graph.addCells(cells);
+ * (end)
+ * 
+ * Example:
+ * 
+ * Using the following code, the selection cells of a graph are encoded and the
+ * output is displayed in a dialog box.
+ * 
+ * (code)
+ * var enc = new mxCodec();
+ * var cells = graph.getSelectionCells();
+ * mxUtils.alert(mxUtils.getPrettyXml(enc.encode(cells)));
+ * (end)
+ * 
+ * Newlines in the XML can be converted to <br>, in which case a '<br>' argument
+ * must be passed to <mxUtils.getXml> as the second argument.
+ * 
+ * Debugging:
+ * 
+ * For debugging I/O you can use the following code to get the sequence of
+ * encoded objects:
+ * 
+ * (code)
+ * var oldEncode = mxCodec.prototype.encode;
+ * mxCodec.prototype.encode = function(obj)
+ * {
+ *   mxLog.show();
+ *   mxLog.debug('mxCodec.encode: obj='+mxUtils.getFunctionName(obj.constructor));
+ *   
+ *   return oldEncode.apply(this, arguments);
+ * };
+ * (end)
+ * 
+ * Note that the I/O system adds object codecs for new object automatically. For
+ * decoding those objects, the constructor should be written as follows:
+ * 
+ * (code)
+ * var MyObj = function(name)
+ * {
+ *   // ...
+ * };
+ * (end)
+ * 
+ * Constructor: mxCodec
+ *
+ * Constructs an XML encoder/decoder for the specified
+ * owner document.
+ *
+ * Parameters:
+ *
+ * document - Optional XML document that contains the data.
+ * If no document is specified then a new document is created
+ * using <mxUtils.createXmlDocument>.
+ */
+function mxCodec(document)
+{
+	this.document = document || mxUtils.createXmlDocument();
+	this.objects = [];
+};
+
+/**
+ * Variable: document
+ *
+ * The owner document of the codec.
+ */
+mxCodec.prototype.document = null;
+
+/**
+ * Variable: objects
+ *
+ * Maps from IDs to objects.
+ */
+mxCodec.prototype.objects = null;
+
+/**
+ * Variable: elements
+ * 
+ * Lookup table for resolving IDs to elements.
+ */
+mxCodec.prototype.elements = null;
+
+/**
+ * Variable: encodeDefaults
+ *
+ * Specifies if default values should be encoded. Default is false.
+ */
+mxCodec.prototype.encodeDefaults = false;
+
+
+/**
+ * Function: putObject
+ * 
+ * Assoiates the given object with the given ID and returns the given object.
+ * 
+ * Parameters
+ * 
+ * id - ID for the object to be associated with.
+ * obj - Object to be associated with the ID.
+ */
+mxCodec.prototype.putObject = function(id, obj)
+{
+	this.objects[id] = obj;
+	
+	return obj;
+};
+
+/**
+ * Function: getObject
+ *
+ * Returns the decoded object for the element with the specified ID in
+ * <document>. If the object is not known then <lookup> is used to find an
+ * object. If no object is found, then the element with the respective ID
+ * from the document is parsed using <decode>.
+ */
+mxCodec.prototype.getObject = function(id)
+{
+	var obj = null;
+
+	if (id != null)
+	{
+		obj = this.objects[id];
+		
+		if (obj == null)
+		{
+			obj = this.lookup(id);
+			
+			if (obj == null)
+			{
+				var node = this.getElementById(id);
+				
+				if (node != null)
+				{
+					obj = this.decode(node);
+				}
+			}
+		}
+	}
+	
+	return obj;
+};
+
+/**
+ * Function: lookup
+ *
+ * Hook for subclassers to implement a custom lookup mechanism for cell IDs.
+ * This implementation always returns null.
+ *
+ * Example:
+ *
+ * (code)
+ * var codec = new mxCodec();
+ * codec.lookup = function(id)
+ * {
+ *   return model.getCell(id);
+ * };
+ * (end)
+ *
+ * Parameters:
+ *
+ * id - ID of the object to be returned.
+ */
+mxCodec.prototype.lookup = function(id)
+{
+	return null;
+};
+
+/**
+ * Function: getElementById
+ *
+ * Returns the element with the given ID from <document>.
+ *
+ * Parameters:
+ *
+ * id - String that contains the ID.
+ */
+mxCodec.prototype.getElementById = function(id)
+{
+	if (this.elements == null)
+	{
+		// Throws custom error for cases where a reference should be resolved
+		// in an empty document. This happens if an XML node is decoded without
+		// passing the owner document to the codec constructor.
+		if (this.document.documentElement == null)
+		{
+			throw new Error('mxCodec constructor needs document parameter');
+		}
+		
+		this.elements = new Object();
+		this.addElement(this.document.documentElement);
+	}
+	
+	return this.elements[id];
+};
+
+/**
+ * Function: addElement
+ *
+ * Adds the given element to <elements> if it has an ID.
+ */
+mxCodec.prototype.addElement = function(node)
+{
+	if (node.nodeType == mxConstants.NODETYPE_ELEMENT)
+	{
+		var id = node.getAttribute('id');
+		
+		if (id != null && this.elements[id] == null)
+		{
+			this.elements[id] = node;
+		}
+	}
+	
+	node = node.firstChild;
+	
+	while (node != null)
+	{
+		this.addElement(node);
+		node = node.nextSibling;
+	}
+};
+
+/**
+ * Function: getId
+ *
+ * Returns the ID of the specified object. This implementation
+ * calls <reference> first and if that returns null handles
+ * the object as an <mxCell> by returning their IDs using
+ * <mxCell.getId>. If no ID exists for the given cell, then
+ * an on-the-fly ID is generated using <mxCellPath.create>.
+ *
+ * Parameters:
+ *
+ * obj - Object to return the ID for.
+ */
+mxCodec.prototype.getId = function(obj)
+{
+	var id = null;
+	
+	if (obj != null)
+	{
+		id = this.reference(obj);
+		
+		if (id == null && obj instanceof mxCell)
+		{
+			id = obj.getId();
+			
+			if (id == null)
+			{
+				// Uses an on-the-fly Id
+				id = mxCellPath.create(obj);
+				
+				if (id.length == 0)
+				{
+					id = 'root';
+				}
+			}
+		}
+	}
+	
+	return id;
+};
+
+/**
+ * Function: reference
+ *
+ * Hook for subclassers to implement a custom method
+ * for retrieving IDs from objects. This implementation
+ * always returns null.
+ *
+ * Example:
+ *
+ * (code)
+ * var codec = new mxCodec();
+ * codec.reference = function(obj)
+ * {
+ *   return obj.getCustomId();
+ * };
+ * (end)
+ *
+ * Parameters:
+ *
+ * obj - Object whose ID should be returned.
+ */
+mxCodec.prototype.reference = function(obj)
+{
+	return null;
+};
+
+/**
+ * Function: encode
+ *
+ * Encodes the specified object and returns the resulting
+ * XML node.
+ *
+ * Parameters:
+ *
+ * obj - Object to be encoded. 
+ */
+mxCodec.prototype.encode = function(obj)
+{
+	var node = null;
+	
+	if (obj != null && obj.constructor != null)
+	{
+		var enc = mxCodecRegistry.getCodec(obj.constructor);
+		
+		if (enc != null)
+		{
+			node = enc.encode(this, obj);
+		}
+		else
+		{
+			if (mxUtils.isNode(obj))
+			{
+				node = mxUtils.importNode(this.document, obj, true);
+			}
+			else
+			{
+	    		mxLog.warn('mxCodec.encode: No codec for ' + mxUtils.getFunctionName(obj.constructor));
+			}
+		}
+	}
+	
+	return node;
+};
+
+/**
+ * Function: decode
+ *
+ * Decodes the given XML node. The optional "into"
+ * argument specifies an existing object to be
+ * used. If no object is given, then a new instance
+ * is created using the constructor from the codec.
+ *
+ * The function returns the passed in object or
+ * the new instance if no object was given.
+ *
+ * Parameters:
+ *
+ * node - XML node to be decoded.
+ * into - Optional object to be decodec into.
+ */
+mxCodec.prototype.decode = function(node, into)
+{
+	var obj = null;
+	
+	if (node != null && node.nodeType == mxConstants.NODETYPE_ELEMENT)
+	{
+		var ctor = null;
+		
+		try
+		{
+			ctor = window[node.nodeName];
+		}
+		catch (err)
+		{
+			// ignore
+		}
+		
+		var dec = mxCodecRegistry.getCodec(ctor);
+		
+		if (dec != null)
+		{
+			obj = dec.decode(this, node, into);
+		}
+		else
+		{
+			obj = node.cloneNode(true);
+			obj.removeAttribute('as');
+		}
+	}
+	
+	return obj;
+};
+
+/**
+ * Function: encodeCell
+ *
+ * Encoding of cell hierarchies is built-into the core, but
+ * is a higher-level function that needs to be explicitely
+ * used by the respective object encoders (eg. <mxModelCodec>,
+ * <mxChildChangeCodec> and <mxRootChangeCodec>). This
+ * implementation writes the given cell and its children as a
+ * (flat) sequence into the given node. The children are not
+ * encoded if the optional includeChildren is false. The
+ * function is in charge of adding the result into the
+ * given node and has no return value.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to be encoded.
+ * node - Parent XML node to add the encoded cell into.
+ * includeChildren - Optional boolean indicating if the
+ * function should include all descendents. Default is true. 
+ */
+mxCodec.prototype.encodeCell = function(cell, node, includeChildren)
+{
+	node.appendChild(this.encode(cell));
+	
+	if (includeChildren == null || includeChildren)
+	{
+		var childCount = cell.getChildCount();
+		
+		for (var i = 0; i < childCount; i++)
+		{
+			this.encodeCell(cell.getChildAt(i), node);
+		}
+	}
+};
+
+/**
+ * Function: isCellCodec
+ * 
+ * Returns true if the given codec is a cell codec. This uses
+ * <mxCellCodec.isCellCodec> to check if the codec is of the
+ * given type.
+ */
+mxCodec.prototype.isCellCodec = function(codec)
+{
+	if (codec != null && typeof(codec.isCellCodec) == 'function')
+	{
+		return codec.isCellCodec();
+	}
+	
+	return false;
+};
+
+/**
+ * Function: decodeCell
+ *
+ * Decodes cells that have been encoded using inversion, ie.
+ * where the user object is the enclosing node in the XML,
+ * and restores the group and graph structure in the cells.
+ * Returns a new <mxCell> instance that represents the
+ * given node.
+ *
+ * Parameters:
+ *
+ * node - XML node that contains the cell data.
+ * restoreStructures - Optional boolean indicating whether
+ * the graph structure should be restored by calling insert
+ * and insertEdge on the parent and terminals, respectively.
+ * Default is true.
+ */
+mxCodec.prototype.decodeCell = function(node, restoreStructures)
+{
+	restoreStructures = (restoreStructures != null) ? restoreStructures : true;
+	var cell = null;
+	
+	if (node != null && node.nodeType == mxConstants.NODETYPE_ELEMENT)
+	{
+		// Tries to find a codec for the given node name. If that does
+		// not return a codec then the node is the user object (an XML node
+		// that contains the mxCell, aka inversion).
+		var decoder = mxCodecRegistry.getCodec(node.nodeName);
+		
+		// Tries to find the codec for the cell inside the user object.
+		// This assumes all node names inside the user object are either
+		// not registered or they correspond to a class for cells.
+		if (!this.isCellCodec(decoder))
+		{
+			var child = node.firstChild;
+			
+			while (child != null && !this.isCellCodec(decoder))
+			{
+				decoder = mxCodecRegistry.getCodec(child.nodeName);
+				child = child.nextSibling;
+			}
+		}
+		
+		if (!this.isCellCodec(decoder))
+		{
+			decoder = mxCodecRegistry.getCodec(mxCell);
+		}
+
+		cell = decoder.decode(this, node);
+		
+		if (restoreStructures)
+		{
+			this.insertIntoGraph(cell);
+		}
+	}
+	
+	return cell;
+};
+
+/**
+ * Function: insertIntoGraph
+ *
+ * Inserts the given cell into its parent and terminal cells.
+ */
+mxCodec.prototype.insertIntoGraph = function(cell)
+{
+	var parent = cell.parent;
+	var source = cell.getTerminal(true);
+	var target = cell.getTerminal(false);
+
+	// Fixes possible inconsistencies during insert into graph
+	cell.setTerminal(null, false);
+	cell.setTerminal(null, true);
+	cell.parent = null;
+	
+	if (parent != null)
+	{
+		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/airavata-kubernetes/web-console/src/assets/js/io/mxCodecRegistry.js b/airavata-kubernetes/web-console/src/assets/js/io/mxCodecRegistry.js
new file mode 100644
index 0000000..42ebcd7
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/io/mxCodecRegistry.js
@@ -0,0 +1,137 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+var mxCodecRegistry =
+{
+	/**
+	 * Class: mxCodecRegistry
+	 *
+	 * Singleton class that acts as a global registry for codecs.
+	 *
+	 * Adding an <mxCodec>:
+	 *
+	 * 1. Define a default codec with a new instance of the 
+	 * object to be handled.
+	 *
+	 * (code)
+	 * var codec = new mxObjectCodec(new mxGraphModel());
+	 * (end)
+	 *
+	 * 2. Define the functions required for encoding and decoding
+	 * objects.
+	 *
+	 * (code)
+	 * codec.encode = function(enc, obj) { ... }
+	 * codec.decode = function(dec, node, into) { ... }
+	 * (end)
+	 *
+	 * 3. Register the codec in the <mxCodecRegistry>.
+	 *
+	 * (code)
+	 * mxCodecRegistry.register(codec);
+	 * (end)
+	 *
+	 * <mxObjectCodec.decode> may be used to either create a new 
+	 * instance of an object or to configure an existing instance, 
+	 * in which case the into argument points to the existing
+	 * object. In this case, we say the codec "configures" the
+	 * object.
+	 * 
+	 * Variable: codecs
+	 *
+	 * Maps from constructor names to codecs.
+	 */
+	codecs: [],
+	
+	/**
+	 * Variable: aliases
+	 *
+	 * Maps from classnames to codecnames.
+	 */
+	aliases: [],
+
+	/**
+	 * Function: register
+	 *
+	 * Registers a new codec and associates the name of the template
+	 * constructor in the codec with the codec object.
+	 *
+	 * Parameters:
+	 *
+	 * codec - <mxObjectCodec> to be registered.
+	 */
+	register: function(codec)
+	{
+		if (codec != null)
+		{
+			var name = codec.getName();
+			mxCodecRegistry.codecs[name] = codec;
+			
+			var classname = mxUtils.getFunctionName(codec.template.constructor);
+
+			if (classname != name)
+			{
+				mxCodecRegistry.addAlias(classname, name);
+			}
+		}
+
+		return codec;
+	},
+
+	/**
+	 * Function: addAlias
+	 *
+	 * Adds an alias for mapping a classname to a codecname.
+	 */
+	addAlias: function(classname, codecname)
+	{
+		mxCodecRegistry.aliases[classname] = codecname;
+	},
+
+	/**
+	 * Function: getCodec
+	 *
+	 * Returns a codec that handles objects that are constructed
+	 * using the given constructor.
+	 *
+	 * Parameters:
+	 *
+	 * ctor - JavaScript constructor function. 
+	 */
+	getCodec: function(ctor)
+	{
+		var codec = null;
+		
+		if (ctor != null)
+		{
+			var name = mxUtils.getFunctionName(ctor);
+			var tmp = mxCodecRegistry.aliases[name];
+			
+			if (tmp != null)
+			{
+				name = tmp;
+			}
+			
+			codec = mxCodecRegistry.codecs[name];
+			
+			// Registers a new default codec for the given constructor
+			// if no codec has been previously defined.
+			if (codec == null)
+			{
+				try
+				{
+					codec = new mxObjectCodec(new ctor());
+					mxCodecRegistry.register(codec);
+				}
+				catch (e)
+				{
+					// ignore
+				}
+			}
+		}
+		
+		return codec;
+	}
+
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/io/mxDefaultKeyHandlerCodec.js b/airavata-kubernetes/web-console/src/assets/js/io/mxDefaultKeyHandlerCodec.js
new file mode 100644
index 0000000..9a18579
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/io/mxDefaultKeyHandlerCodec.js
@@ -0,0 +1,88 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+mxCodecRegistry.register(function()
+{
+	/**
+	 * Class: mxDefaultKeyHandlerCodec
+	 *
+	 * Custom codec for configuring <mxDefaultKeyHandler>s. This class is created
+	 * and registered dynamically at load time and used implicitely via
+	 * <mxCodec> and the <mxCodecRegistry>. This codec only reads configuration
+	 * data for existing key handlers, it does not encode or create key handlers.
+	 */
+	var codec = new mxObjectCodec(new mxDefaultKeyHandler());
+
+	/**
+	 * Function: encode
+	 *
+	 * Returns null.
+	 */
+	codec.encode = function(enc, obj)
+	{
+		return null;
+	};
+	
+	/**
+	 * Function: decode
+	 *
+	 * Reads a sequence of the following child nodes
+	 * and attributes:
+	 *
+	 * Child Nodes:
+	 *
+	 * add - Binds a keystroke to an actionname.
+	 *
+	 * Attributes:
+	 *
+	 * as - Keycode.
+	 * action - Actionname to execute in editor.
+	 * control - Optional boolean indicating if
+	 * 		the control key must be pressed.
+	 *
+	 * Example:
+	 *
+	 * (code)
+	 * <mxDefaultKeyHandler as="keyHandler">
+	 *   <add as="88" control="true" action="cut"/>
+	 *   <add as="67" control="true" action="copy"/>
+	 *   <add as="86" control="true" action="paste"/>
+	 * </mxDefaultKeyHandler>
+	 * (end)
+	 *
+	 * The keycodes are for the x, c and v keys.
+	 *
+	 * See also: <mxDefaultKeyHandler.bindAction>,
+	 * http://www.js-examples.com/page/tutorials__key_codes.html
+	 */
+	codec.decode = function(dec, node, into)
+	{
+		if (into != null)
+		{
+			var editor = into.editor;
+			node = node.firstChild;
+			
+			while (node != null)
+			{
+				if (!this.processInclude(dec, node, into) &&
+					node.nodeName == 'add')
+				{
+					var as = node.getAttribute('as');
+					var action = node.getAttribute('action');
+					var control = node.getAttribute('control');
+					
+					into.bindAction(as, action, control);
+				}
+				
+				node = node.nextSibling;
+			}
+		}
+		
+		return into;
+	};
+
+	// Returns the codec into the registry
+	return codec;
+
+}());
diff --git a/airavata-kubernetes/web-console/src/assets/js/io/mxDefaultPopupMenuCodec.js b/airavata-kubernetes/web-console/src/assets/js/io/mxDefaultPopupMenuCodec.js
new file mode 100644
index 0000000..7a62ac2
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/io/mxDefaultPopupMenuCodec.js
@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+mxCodecRegistry.register(function()
+{
+	/**
+	 * Class: mxDefaultPopupMenuCodec
+	 *
+	 * Custom codec for configuring <mxDefaultPopupMenu>s. This class is created
+	 * and registered dynamically at load time and used implicitely via
+	 * <mxCodec> and the <mxCodecRegistry>. This codec only reads configuration
+	 * data for existing popup menus, it does not encode or create menus. Note
+	 * that this codec only passes the configuration node to the popup menu,
+	 * which uses the config to dynamically create menus. See
+	 * <mxDefaultPopupMenu.createMenu>.
+	 */
+	var codec = new mxObjectCodec(new mxDefaultPopupMenu());
+
+	/**
+	 * Function: encode
+	 *
+	 * Returns null.
+	 */
+	codec.encode = function(enc, obj)
+	{
+		return null;
+	};
+	
+	/**
+	 * Function: decode
+	 *
+	 * Uses the given node as the config for <mxDefaultPopupMenu>.
+	 */
+	codec.decode = function(dec, node, into)
+	{
+		var inc = node.getElementsByTagName('include')[0];
+		
+		if (inc != null)
+		{
+			this.processInclude(dec, inc, into);
+		}
+		else if (into != null)
+		{
+			into.config = node;
+		}
+		
+		return into;
+	};
+	
+	// Returns the codec into the registry
+	return codec;
+
+}());
diff --git a/airavata-kubernetes/web-console/src/assets/js/io/mxDefaultToolbarCodec.js b/airavata-kubernetes/web-console/src/assets/js/io/mxDefaultToolbarCodec.js
new file mode 100644
index 0000000..6157fd3
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/io/mxDefaultToolbarCodec.js
@@ -0,0 +1,312 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxDefaultToolbarCodec
+ *
+ * Custom codec for configuring <mxDefaultToolbar>s. This class is created
+ * and registered dynamically at load time and used implicitely via
+ * <mxCodec> and the <mxCodecRegistry>. This codec only reads configuration
+ * data for existing toolbars handlers, it does not encode or create toolbars.
+ */
+var mxDefaultToolbarCodec = mxCodecRegistry.register(function()
+{
+	var codec = new mxObjectCodec(new mxDefaultToolbar());
+
+	/**
+	 * Function: encode
+	 *
+	 * Returns null.
+	 */
+	codec.encode = function(enc, obj)
+	{
+		return null;
+	};
+	
+	/**
+	 * Function: decode
+	 *
+	 * Reads a sequence of the following child nodes
+	 * and attributes:
+	 *
+	 * Child Nodes:
+	 *
+	 * add - Adds a new item to the toolbar. See below for attributes.
+	 * separator - Adds a vertical separator. No attributes.
+	 * hr - Adds a horizontal separator. No attributes.
+	 * br - Adds a linefeed. No attributes. 
+	 *
+	 * Attributes:
+	 *
+	 * as - Resource key for the label.
+	 * action - Name of the action to execute in enclosing editor.
+	 * mode - Modename (see below).
+	 * template - Template name for cell insertion.
+	 * style - Optional style to override the template style.
+	 * icon - Icon (relative/absolute URL).
+	 * pressedIcon - Optional icon for pressed state (relative/absolute URL).
+	 * id - Optional ID to be used for the created DOM element.
+	 * toggle - Optional 0 or 1 to disable toggling of the element. Default is
+	 * 1 (true).
+	 *
+	 * The action, mode and template attributes are mutually exclusive. The
+	 * style can only be used with the template attribute. The add node may
+	 * contain another sequence of add nodes with as and action attributes
+	 * to create a combo box in the toolbar. If the icon is specified then
+	 * a list of the child node is expected to have its template attribute
+	 * set and the action is ignored instead.
+	 * 
+	 * Nodes with a specified template may define a function to be used for
+	 * inserting the cloned template into the graph. Here is an example of such
+	 * a node:
+	 * 
+	 * (code)
+	 * <add as="Swimlane" template="swimlane" icon="images/swimlane.gif"><![CDATA[
+	 *   function (editor, cell, evt, targetCell)
+	 *   {
+	 *     var pt = mxUtils.convertPoint(
+	 *       editor.graph.container, mxEvent.getClientX(evt),
+	 *         mxEvent.getClientY(evt));
+	 *     return editor.addVertex(targetCell, cell, pt.x, pt.y);
+	 *   }
+	 * ]]></add>
+	 * (end)
+	 * 
+	 * In the above function, editor is the enclosing <mxEditor> instance, cell
+	 * is the clone of the template, evt is the mouse event that represents the
+	 * drop and targetCell is the cell under the mousepointer where the drop
+	 * occurred. The targetCell is retrieved using <mxGraph.getCellAt>.
+	 *
+	 * Futhermore, nodes with the mode attribute may define a function to
+	 * be executed upon selection of the respective toolbar icon. In the
+	 * example below, the default edge style is set when this specific
+	 * connect-mode is activated:
+	 *
+	 * (code)
+	 * <add as="connect" mode="connect"><![CDATA[
+	 *   function (editor)
+	 *   {
+	 *     if (editor.defaultEdge != null)
+	 *     {
+	 *       editor.defaultEdge.style = 'straightEdge';
+	 *     }
+	 *   }
+	 * ]]></add>
+	 * (end)
+	 * 
+	 * Both functions require <mxDefaultToolbarCodec.allowEval> to be set to true.
+	 *
+	 * Modes:
+	 *
+	 * select - Left mouse button used for rubberband- & cell-selection.
+	 * connect - Allows connecting vertices by inserting new edges.
+	 * pan - Disables selection and switches to panning on the left button.
+	 *
+	 * Example:
+	 *
+	 * To add items to the toolbar:
+	 * 
+	 * (code)
+	 * <mxDefaultToolbar as="toolbar">
+	 *   <add as="save" action="save" icon="images/save.gif"/>
+	 *   <br/><hr/>
+	 *   <add as="select" mode="select" icon="images/select.gif"/>
+	 *   <add as="connect" mode="connect" icon="images/connect.gif"/>
+	 * </mxDefaultToolbar>
+	 * (end)
+	 */
+	codec.decode = function(dec, node, into)
+	{
+		if (into != null)
+		{
+			var editor = into.editor;
+			node = node.firstChild;
+			
+			while (node != null)
+			{
+				if (node.nodeType == mxConstants.NODETYPE_ELEMENT)
+				{
+					if (!this.processInclude(dec, node, into))
+					{
+						if (node.nodeName == 'separator')
+						{
+							into.addSeparator();
+						}
+						else if (node.nodeName == 'br')
+						{
+							into.toolbar.addBreak();
+						}
+						else if (node.nodeName == 'hr')
+						{
+							into.toolbar.addLine();
+						}
+						else if (node.nodeName == 'add')
+						{
+							var as = node.getAttribute('as');
+							as = mxResources.get(as) || as;
+							var icon = node.getAttribute('icon');
+							var pressedIcon = node.getAttribute('pressedIcon');
+							var action = node.getAttribute('action');
+							var mode = node.getAttribute('mode');
+							var template = node.getAttribute('template');
+							var toggle = node.getAttribute('toggle') != '0';
+							var text = mxUtils.getTextContent(node);
+							var elt = null;
+
+							if (action != null)
+							{
+								elt = into.addItem(as, icon, action, pressedIcon);
+							}
+							else if (mode != null)
+							{
+								var funct = (mxDefaultToolbarCodec.allowEval) ? mxUtils.eval(text) : null;
+								elt = into.addMode(as, icon, mode, pressedIcon, funct);
+							}
+							else if (template != null || (text != null && text.length > 0))
+							{
+								var cell = editor.templates[template];
+								var style = node.getAttribute('style');
+								
+								if (cell != null && style != null)
+								{
+									cell = editor.graph.cloneCells([cell])[0];
+									cell.setStyle(style);
+								}
+								
+								var insertFunction = null;
+								
+								if (text != null && text.length > 0 && mxDefaultToolbarCodec.allowEval)
+								{
+									insertFunction = mxUtils.eval(text);
+								}
+								
+								elt = into.addPrototype(as, icon, cell, pressedIcon, insertFunction, toggle);
+							}
+							else
+							{
+								var children = mxUtils.getChildNodes(node);
+								
+								if (children.length > 0)
+								{
+									if (icon == null)
+									{
+										var combo = into.addActionCombo(as);
+										
+										for (var i=0; i<children.length; i++)
+										{
+											var child = children[i];
+											
+											if (child.nodeName == 'separator')
+											{
+												into.addOption(combo, '---');
+											}
+											else if (child.nodeName == 'add')
+											{
+												var lab = child.getAttribute('as');
+												var act = child.getAttribute('action');
+												into.addActionOption(combo, lab, act);
+											}
+										}
+									}
+									else
+									{
+										var select = null;
+										var create = function()
+										{
+											var template = editor.templates[select.value];
+											
+											if (template != null)
+											{
+												var clone = template.clone();
+												var style = select.options[select.selectedIndex].cellStyle;
+												
+												if (style != null)
+												{
+													clone.setStyle(style);
+												}
+												
+												return clone;
+											}
+											else
+											{
+												mxLog.warn('Template '+template+' not found');
+											}
+											
+											return null;
+										};
+										
+										var img = into.addPrototype(as, icon, create, null, null, toggle);
+										select = into.addCombo();
+										
+										// Selects the toolbar icon if a selection change
+										// is made in the corresponding combobox.
+										mxEvent.addListener(select, 'change', function()
+										{
+											into.toolbar.selectMode(img, function(evt)
+											{
+												var pt = mxUtils.convertPoint(editor.graph.container,
+													mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+												
+												return editor.addVertex(null, funct(), pt.x, pt.y);
+											});
+											
+											into.toolbar.noReset = false;
+										});
+										
+										// Adds the entries to the combobox
+										for (var i=0; i<children.length; i++)
+										{
+											var child = children[i];
+											
+											if (child.nodeName == 'separator')
+											{
+												into.addOption(select, '---');
+											}
+											else if (child.nodeName == 'add')
+											{
+												var lab = child.getAttribute('as');
+												var tmp = child.getAttribute('template');
+												var option = into.addOption(select, lab, tmp || template);
+												option.cellStyle = child.getAttribute('style');
+											}
+										}
+										
+									}
+								}
+							}
+							
+							// Assigns an ID to the created element to access it later.
+							if (elt != null)
+							{
+								var id = node.getAttribute('id');
+								
+								if (id != null && id.length > 0)
+								{
+									elt.setAttribute('id', id);
+								}
+							}
+						}
+					}
+				}
+				
+				node = node.nextSibling;
+			}
+		}
+		
+		return into;
+	};
+	
+	// Returns the codec into the registry
+	return codec;
+
+}());
+
+/**
+ * Variable: allowEval
+ * 
+ * Static global switch that specifies if the use of eval is allowed for
+ * evaluating text content. Default is true. Set this to false if stylesheets
+ * may contain user input
+ */
+mxDefaultToolbarCodec.allowEval = true;
diff --git a/airavata-kubernetes/web-console/src/assets/js/io/mxEditorCodec.js b/airavata-kubernetes/web-console/src/assets/js/io/mxEditorCodec.js
new file mode 100644
index 0000000..47ce585
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/io/mxEditorCodec.js
@@ -0,0 +1,245 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+mxCodecRegistry.register(function()
+{
+	/**
+	 * Class: mxEditorCodec
+	 *
+	 * Codec for <mxEditor>s. This class is created and registered
+	 * dynamically at load time and used implicitely via <mxCodec>
+	 * and the <mxCodecRegistry>.
+	 *
+	 * Transient Fields:
+	 *
+	 * - modified
+	 * - lastSnapshot
+	 * - ignoredChanges
+	 * - undoManager
+	 * - graphContainer
+	 * - toolbarContainer
+	 */
+	var codec = new mxObjectCodec(new mxEditor(),
+		['modified', 'lastSnapshot', 'ignoredChanges',
+		'undoManager', 'graphContainer', 'toolbarContainer']);
+
+	/**
+	 * Function: beforeDecode
+	 *
+	 * Decodes the ui-part of the configuration node by reading
+	 * a sequence of the following child nodes and attributes
+	 * and passes the control to the default decoding mechanism:
+	 *
+	 * Child Nodes:
+	 *
+	 * stylesheet - Adds a CSS stylesheet to the document.
+	 * resource - Adds the basename of a resource bundle.
+	 * add - Creates or configures a known UI element.
+	 *
+	 * These elements may appear in any order given that the
+	 * graph UI element is added before the toolbar element
+	 * (see Known Keys).
+	 *
+	 * Attributes:
+	 *
+	 * as - Key for the UI element (see below).
+	 * element - ID for the element in the document.
+	 * style - CSS style to be used for the element or window.
+	 * x - X coordinate for the new window.
+	 * y - Y coordinate for the new window.
+	 * width - Width for the new window.
+	 * height - Optional height for the new window.
+	 * name - Name of the stylesheet (absolute/relative URL).
+	 * basename - Basename of the resource bundle (see <mxResources>).
+	 *
+	 * The x, y, width and height attributes are used to create a new
+	 * <mxWindow> if the element attribute is not specified in an add
+	 * node. The name and basename are only used in the stylesheet and
+	 * resource nodes, respectively.
+	 *
+	 * Known Keys:
+	 *
+	 * graph - Main graph element (see <mxEditor.setGraphContainer>).
+	 * title - Title element (see <mxEditor.setTitleContainer>).
+	 * toolbar - Toolbar element (see <mxEditor.setToolbarContainer>).
+	 * status - Status bar element (see <mxEditor.setStatusContainer>).
+	 *
+	 * Example:
+	 *
+	 * (code)
+	 * <ui>
+	 *   <stylesheet name="css/process.css"/>
+	 *   <resource basename="resources/app"/>
+	 *   <add as="graph" element="graph"
+	 *     style="left:70px;right:20px;top:20px;bottom:40px"/>
+	 *   <add as="status" element="status"/>
+	 *   <add as="toolbar" x="10" y="20" width="54"/>
+	 * </ui>
+	 * (end)
+	 */
+	codec.afterDecode = function(dec, node, obj)
+	{
+		// Assigns the specified templates for edges
+		var defaultEdge = node.getAttribute('defaultEdge');
+		
+		if (defaultEdge != null)
+		{
+			node.removeAttribute('defaultEdge');
+			obj.defaultEdge = obj.templates[defaultEdge];
+		}
+
+		// Assigns the specified templates for groups
+		var defaultGroup = node.getAttribute('defaultGroup');
+		
+		if (defaultGroup != null)
+		{
+			node.removeAttribute('defaultGroup');
+			obj.defaultGroup = obj.templates[defaultGroup];
+		}
+
+		return obj;
+	};
+	
+	/**
+	 * Function: decodeChild
+	 * 
+	 * Overrides decode child to handle special child nodes.
+	 */	
+	codec.decodeChild = function(dec, child, obj)
+	{
+		if (child.nodeName == 'Array')
+		{
+			var role = child.getAttribute('as');
+			
+			if (role == 'templates')
+			{
+				this.decodeTemplates(dec, child, obj);
+				return;
+			}
+		}
+		else if (child.nodeName == 'ui')
+		{
+			this.decodeUi(dec, child, obj);
+			return;
+		}
+		
+		mxObjectCodec.prototype.decodeChild.apply(this, arguments);
+	};
+		
+	/**
+	 * Function: decodeTemplates
+	 *
+	 * Decodes the cells from the given node as templates.
+	 */
+	codec.decodeUi = function(dec, node, editor)
+	{
+		var tmp = node.firstChild;
+		while (tmp != null)
+		{
+			if (tmp.nodeName == 'add')
+			{
+				var as = tmp.getAttribute('as');
+				var elt = tmp.getAttribute('element');
+				var style = tmp.getAttribute('style');
+				var element = null;
+
+				if (elt != null)
+				{
+					element = document.getElementById(elt);
+					
+					if (element != null && style != null)
+					{
+						element.style.cssText += ';' + style;
+					}
+				}
+				else
+				{
+					var x = parseInt(tmp.getAttribute('x'));
+					var y = parseInt(tmp.getAttribute('y'));
+					var width = tmp.getAttribute('width');
+					var height = tmp.getAttribute('height');
+
+					// Creates a new window around the element
+					element = document.createElement('div');
+					element.style.cssText = style;
+					
+					var wnd = new mxWindow(mxResources.get(as) || as,
+						element, x, y, width, height, false, true);
+					wnd.setVisible(true);
+				}
+				
+				// TODO: Make more generic
+				if (as == 'graph')
+				{
+					editor.setGraphContainer(element);
+				}
+				else if (as == 'toolbar')
+				{
+					editor.setToolbarContainer(element);
+				}
+				else if (as == 'title')
+				{
+					editor.setTitleContainer(element);
+				}
+				else if (as == 'status')
+				{
+					editor.setStatusContainer(element);
+				}
+				else if (as == 'map')
+				{
+					editor.setMapContainer(element);
+				}
+			}
+			else if (tmp.nodeName == 'resource')
+			{
+				mxResources.add(tmp.getAttribute('basename'));
+			}
+			else if (tmp.nodeName == 'stylesheet')
+			{
+				mxClient.link('stylesheet', tmp.getAttribute('name'));
+			}
+			
+			tmp = tmp.nextSibling;
+		}	
+	};
+	
+	/**
+	 * Function: decodeTemplates
+	 *
+	 * Decodes the cells from the given node as templates.
+	 */
+	codec.decodeTemplates = function(dec, node, editor)
+	{
+		if (editor.templates == null)
+		{
+			editor.templates = [];
+		}
+		
+		var children = mxUtils.getChildNodes(node);
+		for (var j=0; j<children.length; j++)
+		{
+			var name = children[j].getAttribute('as');
+			var child = children[j].firstChild;
+			
+			while (child != null && child.nodeType != 1)
+			{
+				child = child.nextSibling;
+			}
+			
+			if (child != null)
+			{
+				// LATER: Only single cells means you need
+				// to group multiple cells within another
+				// cell. This should be changed to support
+				// arrays of cells, or the wrapper must
+				// be automatically handled in this class.
+				editor.templates[name] = dec.decodeCell(child);
+			}
+		}
+	};
+	
+	// Returns the codec into the registry
+	return codec;
+
+}());
diff --git a/airavata-kubernetes/web-console/src/assets/js/io/mxGenericChangeCodec.js b/airavata-kubernetes/web-console/src/assets/js/io/mxGenericChangeCodec.js
new file mode 100644
index 0000000..8ecc82a
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/io/mxGenericChangeCodec.js
@@ -0,0 +1,64 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxGenericChangeCodec
+ *
+ * Codec for <mxValueChange>s, <mxStyleChange>s, <mxGeometryChange>s,
+ * <mxCollapseChange>s and <mxVisibleChange>s. This class is created
+ * and registered dynamically at load time and used implicitely
+ * via <mxCodec> and the <mxCodecRegistry>.
+ *
+ * Transient Fields:
+ *
+ * - model
+ * - previous
+ *
+ * Reference Fields:
+ *
+ * - cell
+ * 
+ * Constructor: mxGenericChangeCodec
+ *
+ * Factory function that creates a <mxObjectCodec> for
+ * the specified change and fieldname.
+ *
+ * Parameters:
+ *
+ * obj - An instance of the change object.
+ * variable - The fieldname for the change data.
+ */
+var mxGenericChangeCodec = function(obj, variable)
+{
+	var codec = new mxObjectCodec(obj,  ['model', 'previous'], ['cell']);
+
+	/**
+	 * Function: afterDecode
+	 *
+	 * Restores the state by assigning the previous value.
+	 */
+	codec.afterDecode = function(dec, node, obj)
+	{
+		// Allows forward references in sessions. This is a workaround
+		// for the sequence of edits in mxGraph.moveCells and cellsAdded.
+		if (mxUtils.isNode(obj.cell))
+		{
+			obj.cell = dec.decodeCell(obj.cell, false);
+		}
+
+		obj.previous = obj[variable];
+
+		return obj;
+	};
+	
+	return codec;
+};
+
+// Registers the codecs
+mxCodecRegistry.register(mxGenericChangeCodec(new mxValueChange(), 'value'));
+mxCodecRegistry.register(mxGenericChangeCodec(new mxStyleChange(), 'style'));
+mxCodecRegistry.register(mxGenericChangeCodec(new mxGeometryChange(), 'geometry'));
+mxCodecRegistry.register(mxGenericChangeCodec(new mxCollapseChange(), 'collapsed'));
+mxCodecRegistry.register(mxGenericChangeCodec(new mxVisibleChange(), 'visible'));
+mxCodecRegistry.register(mxGenericChangeCodec(new mxCellAttributeChange(), 'value'));
diff --git a/airavata-kubernetes/web-console/src/assets/js/io/mxGraphCodec.js b/airavata-kubernetes/web-console/src/assets/js/io/mxGraphCodec.js
new file mode 100644
index 0000000..f3e7a56
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/io/mxGraphCodec.js
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+mxCodecRegistry.register(function()
+{
+	/**
+	 * Class: mxGraphCodec
+	 *
+	 * Codec for <mxGraph>s. This class is created and registered
+	 * dynamically at load time and used implicitely via <mxCodec>
+	 * and the <mxCodecRegistry>.
+	 *
+	 * Transient Fields:
+	 *
+	 * - graphListeners
+	 * - eventListeners
+	 * - view
+	 * - container
+	 * - cellRenderer
+	 * - editor
+	 * - selection
+	 */
+	return new mxObjectCodec(new mxGraph(),
+		['graphListeners', 'eventListeners', 'view', 'container',
+		'cellRenderer', 'editor', 'selection']);
+
+}());
diff --git a/airavata-kubernetes/web-console/src/assets/js/io/mxGraphViewCodec.js b/airavata-kubernetes/web-console/src/assets/js/io/mxGraphViewCodec.js
new file mode 100644
index 0000000..c3023de
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/io/mxGraphViewCodec.js
@@ -0,0 +1,197 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+mxCodecRegistry.register(function()
+{
+	/**
+	 * Class: mxGraphViewCodec
+	 *
+	 * Custom encoder for <mxGraphView>s. This class is created
+	 * and registered dynamically at load time and used implicitely via
+	 * <mxCodec> and the <mxCodecRegistry>. This codec only writes views
+	 * into a XML format that can be used to create an image for
+	 * the graph, that is, it contains absolute coordinates with
+	 * computed perimeters, edge styles and cell styles.
+	 */
+	var codec = new mxObjectCodec(new mxGraphView());
+
+	/**
+	 * Function: encode
+	 *
+	 * Encodes the given <mxGraphView> using <encodeCell>
+	 * starting at the model's root. This returns the
+	 * top-level graph node of the recursive encoding.
+	 */
+	codec.encode = function(enc, view)
+	{
+		return this.encodeCell(enc, view,
+			view.graph.getModel().getRoot());
+	};
+
+	/**
+	 * Function: encodeCell
+	 *
+	 * Recursively encodes the specifed cell. Uses layer
+	 * as the default nodename. If the cell's parent is
+	 * null, then graph is used for the nodename. If
+	 * <mxGraphModel.isEdge> returns true for the cell,
+	 * then edge is used for the nodename, else if
+	 * <mxGraphModel.isVertex> returns true for the cell,
+	 * then vertex is used for the nodename.
+	 *
+	 * <mxGraph.getLabel> is used to create the label
+	 * attribute for the cell. For graph nodes and vertices
+	 * the bounds are encoded into x, y, width and height.
+	 * For edges the points are encoded into a points
+	 * attribute as a space-separated list of comma-separated
+	 * coordinate pairs (eg. x0,y0 x1,y1 ... xn,yn). All
+	 * values from the cell style are added as attribute
+	 * values to the node. 
+	 */
+	codec.encodeCell = function(enc, view, cell)
+	{
+		var model = view.graph.getModel();
+		var state = view.getState(cell);
+		var parent = model.getParent(cell);
+		
+		if (parent == null || state != null)
+		{
+			var childCount = model.getChildCount(cell);
+			var geo = view.graph.getCellGeometry(cell);
+			var name = null;
+			
+			if (parent == model.getRoot())
+			{
+				name = 'layer';
+			}
+			else if (parent == null)
+			{
+				name = 'graph';
+			}
+			else if (model.isEdge(cell))
+			{
+				name = 'edge';
+			}
+			else if (childCount > 0 && geo != null)
+			{
+				name = 'group';
+			}
+			else if (model.isVertex(cell))
+			{
+				name = 'vertex';
+			}
+			
+			if (name != null)
+			{
+				var node = enc.document.createElement(name);
+				var lab = view.graph.getLabel(cell);
+				
+				if (lab != null)
+				{
+					node.setAttribute('label', view.graph.getLabel(cell));
+					
+					if (view.graph.isHtmlLabel(cell))
+					{
+						node.setAttribute('html', true);
+					}
+				}
+		
+				if (parent == null)
+				{
+					var bounds = view.getGraphBounds();
+					
+					if (bounds != null)
+					{
+						node.setAttribute('x', Math.round(bounds.x));
+						node.setAttribute('y', Math.round(bounds.y));
+						node.setAttribute('width', Math.round(bounds.width));
+						node.setAttribute('height', Math.round(bounds.height));
+					}
+					
+					node.setAttribute('scale', view.scale);
+				}
+				else if (state != null && geo != null)
+				{
+					// Writes each key, value in the style pair to an attribute
+				    for (var i in state.style)
+				    {
+				    	var value = state.style[i];
+		
+				    	// Tries to turn objects and functions into strings
+					    if (typeof(value) == 'function' &&
+							typeof(value) == 'object')
+						{
+					    	value = mxStyleRegistry.getName(value);
+				        }
+				    	
+				    	if (value != null &&
+				    		typeof(value) != 'function' &&
+							typeof(value) != 'object')
+						{
+							node.setAttribute(i, value);
+				        }
+				    }
+				    
+					var abs = state.absolutePoints;
+					
+					// Writes the list of points into one attribute
+					if (abs != null && abs.length > 0)
+					{
+						var pts = Math.round(abs[0].x) + ',' + Math.round(abs[0].y);
+		
+						for (var i=1; i<abs.length; i++)
+						{
+							pts += ' ' + Math.round(abs[i].x) + ',' +
+								Math.round(abs[i].y);
+						}
+		
+						node.setAttribute('points', pts);
+					}
+					
+					// Writes the bounds into 4 attributes
+					else
+					{
+						node.setAttribute('x', Math.round(state.x));
+						node.setAttribute('y', Math.round(state.y));
+						node.setAttribute('width', Math.round(state.width));
+						node.setAttribute('height', Math.round(state.height));				
+					}
+		
+					var offset = state.absoluteOffset;
+					
+					// Writes the offset into 2 attributes
+					if (offset != null)
+					{
+						if (offset.x != 0)
+						{
+							node.setAttribute('dx', Math.round(offset.x));
+						}
+						
+						if (offset.y != 0)
+						{
+							node.setAttribute('dy', Math.round(offset.y));
+						}
+					}
+				}
+		
+				for (var i=0; i<childCount; i++)
+				{
+					var childNode = this.encodeCell(enc,
+							view, model.getChildAt(cell, i));
+					
+					if (childNode != null)
+					{
+						node.appendChild(childNode);
+					}
+				}
+			}
+		}
+		
+		return node;
+	};
+
+	// Returns the codec into the registry
+	return codec;
+
+}());
diff --git a/airavata-kubernetes/web-console/src/assets/js/io/mxModelCodec.js b/airavata-kubernetes/web-console/src/assets/js/io/mxModelCodec.js
new file mode 100644
index 0000000..85ec4ce
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/io/mxModelCodec.js
@@ -0,0 +1,80 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+mxCodecRegistry.register(function()
+{
+	/**
+	 * Class: mxModelCodec
+	 *
+	 * Codec for <mxGraphModel>s. This class is created and registered
+	 * dynamically at load time and used implicitely via <mxCodec>
+	 * and the <mxCodecRegistry>.
+	 */
+	var codec = new mxObjectCodec(new mxGraphModel());
+
+	/**
+	 * Function: encodeObject
+	 *
+	 * Encodes the given <mxGraphModel> by writing a (flat) XML sequence of
+	 * cell nodes as produced by the <mxCellCodec>. The sequence is
+	 * wrapped-up in a node with the name root.
+	 */
+	codec.encodeObject = function(enc, obj, node)
+	{
+		var rootNode = enc.document.createElement('root');
+		enc.encodeCell(obj.getRoot(), rootNode);
+		node.appendChild(rootNode);
+	};
+
+	/**
+	 * Function: decodeChild
+	 * 
+	 * Overrides decode child to handle special child nodes.
+	 */	
+	codec.decodeChild = function(dec, child, obj)
+	{
+		if (child.nodeName == 'root')
+		{
+			this.decodeRoot(dec, child, obj);
+		}
+		else
+		{
+			mxObjectCodec.prototype.decodeChild.apply(this, arguments);
+		}
+	};
+
+	/**
+	 * Function: decodeRoot
+	 *
+	 * Reads the cells into the graph model. All cells
+	 * are children of the root element in the node.
+	 */
+	codec.decodeRoot = function(dec, root, model)
+	{
+		var rootCell = null;
+		var tmp = root.firstChild;
+		
+		while (tmp != null)
+		{
+			var cell = dec.decodeCell(tmp);
+			
+			if (cell != null && cell.getParent() == null)
+			{
+				rootCell = cell;
+			}
+			
+			tmp = tmp.nextSibling;
+		}
+
+		// Sets the root on the model if one has been decoded
+		if (rootCell != null)
+		{
+			model.setRoot(rootCell);
+		}
+	};
+
+	// Returns the codec into the registry
+	return codec;
+
+}());
diff --git a/airavata-kubernetes/web-console/src/assets/js/io/mxObjectCodec.js b/airavata-kubernetes/web-console/src/assets/js/io/mxObjectCodec.js
new file mode 100644
index 0000000..ac69318
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/io/mxObjectCodec.js
@@ -0,0 +1,1077 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxObjectCodec
+ *
+ * Generic codec for JavaScript objects that implements a mapping between
+ * JavaScript objects and XML nodes that maps each field or element to an
+ * attribute or child node, and vice versa.
+ * 
+ * Atomic Values:
+ * 
+ * Consider the following example.
+ * 
+ * (code)
+ * var obj = new Object();
+ * obj.foo = "Foo";
+ * obj.bar = "Bar";
+ * (end)
+ * 
+ * This object is encoded into an XML node using the following.
+ * 
+ * (code)
+ * var enc = new mxCodec();
+ * var node = enc.encode(obj);
+ * (end)
+ * 
+ * The output of the encoding may be viewed using <mxLog> as follows.
+ * 
+ * (code)
+ * mxLog.show();
+ * mxLog.debug(mxUtils.getPrettyXml(node));
+ * (end)
+ * 
+ * Finally, the result of the encoding looks as follows.
+ * 
+ * (code)
+ * <Object foo="Foo" bar="Bar"/>
+ * (end)
+ * 
+ * In the above output, the foo and bar fields have been mapped to attributes
+ * with the same names, and the name of the constructor was used for the
+ * nodename.
+ * 
+ * Booleans:
+ *
+ * Since booleans are numbers in JavaScript, all boolean values are encoded
+ * into 1 for true and 0 for false. The decoder also accepts the string true
+ * and false for boolean values.
+ * 
+ * Objects:
+ * 
+ * The above scheme is applied to all atomic fields, that is, to all non-object
+ * fields of an object. For object fields, a child node is created with a
+ * special attribute that contains the fieldname. This special attribute is
+ * called "as" and hence, as is a reserved word that should not be used for a
+ * fieldname.
+ * 
+ * Consider the following example where foo is an object and bar is an atomic
+ * property of foo.
+ * 
+ * (code)
+ * var obj = {foo: {bar: "Bar"}};
+ * (end)
+ * 
+ * This will be mapped to the following XML structure by mxObjectCodec.
+ * 
+ * (code)
+ * <Object>
+ *   <Object bar="Bar" as="foo"/>
+ * </Object>
+ * (end)
+ * 
+ * In the above output, the inner Object node contains the as-attribute that
+ * specifies the fieldname in the enclosing object. That is, the field foo was
+ * mapped to a child node with an as-attribute that has the value foo.
+ * 
+ * Arrays:
+ * 
+ * Arrays are special objects that are either associative, in which case each
+ * key, value pair is treated like a field where the key is the fieldname, or
+ * they are a sequence of atomic values and objects, which is mapped to a
+ * sequence of child nodes. For object elements, the above scheme is applied
+ * without the use of the special as-attribute for creating each child. For
+ * atomic elements, a special add-node is created with the value stored in the
+ * value-attribute.
+ * 
+ * For example, the following array contains one atomic value and one object
+ * with a field called bar. Furthermore it contains two associative entries
+ * called bar with an atomic value, and foo with an object value.
+ * 
+ * (code)
+ * var obj = ["Bar", {bar: "Bar"}];
+ * obj["bar"] = "Bar";
+ * obj["foo"] = {bar: "Bar"};
+ * (end)
+ * 
+ * This array is represented by the following XML nodes.
+ * 
+ * (code)
+ * <Array bar="Bar">
+ *   <add value="Bar"/>
+ *   <Object bar="Bar"/>
+ *   <Object bar="Bar" as="foo"/>
+ * </Array>
+ * (end)
+ * 
+ * The Array node name is the name of the constructor. The additional
+ * as-attribute in the last child contains the key of the associative entry,
+ * whereas the second last child is part of the array sequence and does not
+ * have an as-attribute.
+ * 
+ * References:
+ * 
+ * Objects may be represented as child nodes or attributes with ID values,
+ * which are used to lookup the object in a table within <mxCodec>. The
+ * <isReference> function is in charge of deciding if a specific field should
+ * be encoded as a reference or not. Its default implementation returns true if
+ * the fieldname is in <idrefs>, an array of strings that is used to configure
+ * the <mxObjectCodec>.
+ * 
+ * Using this approach, the mapping does not guarantee that the referenced
+ * object itself exists in the document. The fields that are encoded as
+ * references must be carefully chosen to make sure all referenced objects
+ * exist in the document, or may be resolved by some other means if necessary.
+ * 
+ * For example, in the case of the graph model all cells are stored in a tree
+ * whose root is referenced by the model's root field. A tree is a structure
+ * that is well suited for an XML representation, however, the additional edges
+ * in the graph model have a reference to a source and target cell, which are
+ * also contained in the tree. To handle this case, the source and target cell
+ * of an edge are treated as references, whereas the children are treated as
+ * objects. Since all cells are contained in the tree and no edge references a
+ * source or target outside the tree, this setup makes sure all referenced
+ * objects are contained in the document.
+ * 
+ * In the case of a tree structure we must further avoid infinite recursion by
+ * ignoring the parent reference of each child. This is done by returning true
+ * in <isExcluded>, whose default implementation uses the array of excluded
+ * fieldnames passed to the mxObjectCodec constructor.
+ * 
+ * References are only used for cells in mxGraph. For defining other
+ * referencable object types, the codec must be able to work out the ID of an
+ * object. This is done by implementing <mxCodec.reference>. For decoding a
+ * reference, the XML node with the respective id-attribute is fetched from the
+ * document, decoded, and stored in a lookup table for later reference. For
+ * looking up external objects, <mxCodec.lookup> may be implemented.
+ * 
+ * Expressions:
+ * 
+ * For decoding JavaScript expressions, the add-node may be used with a text
+ * content that contains the JavaScript expression. For example, the following
+ * creates a field called foo in the enclosing object and assigns it the value
+ * of <mxConstants.ALIGN_LEFT>.
+ * 
+ * (code)
+ * <Object>
+ *   <add as="foo">mxConstants.ALIGN_LEFT</add>
+ * </Object>
+ * (end)
+ * 
+ * The resulting object has a field called foo with the value "left". Its XML
+ * representation looks as follows.
+ * 
+ * (code)
+ * <Object foo="left"/>
+ * (end)
+ * 
+ * This means the expression is evaluated at decoding time and the result of
+ * the evaluation is stored in the respective field. Valid expressions are all
+ * JavaScript expressions, including function definitions, which are mapped to
+ * functions on the resulting object.
+ * 
+ * Expressions are only evaluated if <allowEval> is true.
+ * 
+ * Constructor: mxObjectCodec
+ *
+ * Constructs a new codec for the specified template object.
+ * The variables in the optional exclude array are ignored by
+ * the codec. Variables in the optional idrefs array are
+ * turned into references in the XML. The optional mapping
+ * may be used to map from variable names to XML attributes.
+ * The argument is created as follows:
+ *
+ * (code)
+ * var mapping = new Object();
+ * mapping['variableName'] = 'attribute-name';
+ * (end)
+ *
+ * Parameters:
+ *
+ * template - Prototypical instance of the object to be
+ * encoded/decoded.
+ * exclude - Optional array of fieldnames to be ignored.
+ * idrefs - Optional array of fieldnames to be converted to/from
+ * references.
+ * mapping - Optional mapping from field- to attributenames.
+ */
+function mxObjectCodec(template, exclude, idrefs, mapping)
+{
+	this.template = template;
+	
+	this.exclude = (exclude != null) ? exclude : [];
+	this.idrefs = (idrefs != null) ? idrefs : [];
+	this.mapping = (mapping != null) ? mapping : [];
+	
+	this.reverse = new Object();
+	
+	for (var i in this.mapping)
+	{
+		this.reverse[this.mapping[i]] = i;
+	}
+};
+
+/**
+ * Variable: allowEval
+ *
+ * Static global switch that specifies if expressions in arrays are allowed.
+ * Default is false. NOTE: Enabling this carries a possible security risk.
+ */
+mxObjectCodec.allowEval = false;
+
+/**
+ * Variable: template
+ *
+ * Holds the template object associated with this codec.
+ */
+mxObjectCodec.prototype.template = null;
+
+/**
+ * Variable: exclude
+ *
+ * Array containing the variable names that should be
+ * ignored by the codec.
+ */
+mxObjectCodec.prototype.exclude = null;
+
+/**
+ * Variable: idrefs
+ *
+ * Array containing the variable names that should be
+ * turned into or converted from references. See
+ * <mxCodec.getId> and <mxCodec.getObject>.
+ */
+mxObjectCodec.prototype.idrefs = null;
+
+/**
+ * Variable: mapping
+ *
+ * Maps from from fieldnames to XML attribute names.
+ */
+mxObjectCodec.prototype.mapping = null;
+
+/**
+ * Variable: reverse
+ *
+ * Maps from from XML attribute names to fieldnames.
+ */
+mxObjectCodec.prototype.reverse = null;
+
+/**
+ * Function: getName
+ * 
+ * Returns the name used for the nodenames and lookup of the codec when
+ * classes are encoded and nodes are decoded. For classes to work with
+ * this the codec registry automatically adds an alias for the classname
+ * if that is different than what this returns. The default implementation
+ * returns the classname of the template class.
+ */
+mxObjectCodec.prototype.getName = function()
+{
+	return mxUtils.getFunctionName(this.template.constructor);
+};
+
+/**
+ * Function: cloneTemplate
+ * 
+ * Returns a new instance of the template for this codec.
+ */
+mxObjectCodec.prototype.cloneTemplate = function()
+{
+	return new this.template.constructor();
+};
+
+/**
+ * Function: getFieldName
+ * 
+ * Returns the fieldname for the given attributename.
+ * Looks up the value in the <reverse> mapping or returns
+ * the input if there is no reverse mapping for the
+ * given name.
+ */
+mxObjectCodec.prototype.getFieldName = function(attributename)
+{
+	if (attributename != null)
+	{
+		var mapped = this.reverse[attributename];
+		
+		if (mapped != null)
+		{
+			attributename = mapped;
+		}
+	}
+	
+	return attributename;
+};
+
+/**
+ * Function: getAttributeName
+ * 
+ * Returns the attributename for the given fieldname.
+ * Looks up the value in the <mapping> or returns
+ * the input if there is no mapping for the
+ * given name.
+ */
+mxObjectCodec.prototype.getAttributeName = function(fieldname)
+{
+	if (fieldname != null)
+	{
+		var mapped = this.mapping[fieldname];
+		
+		if (mapped != null)
+		{
+			fieldname = mapped;
+		}
+	}
+	
+	return fieldname;
+};
+
+/**
+ * Function: isExcluded
+ *
+ * Returns true if the given attribute is to be ignored by the codec. This
+ * implementation returns true if the given fieldname is in <exclude> or
+ * if the fieldname equals <mxObjectIdentity.FIELD_NAME>.
+ *
+ * Parameters:
+ *
+ * obj - Object instance that contains the field.
+ * attr - Fieldname of the field.
+ * value - Value of the field.
+ * write - Boolean indicating if the field is being encoded or decoded.
+ * Write is true if the field is being encoded, else it is being decoded.
+ */
+mxObjectCodec.prototype.isExcluded = function(obj, attr, value, write)
+{
+	return attr == mxObjectIdentity.FIELD_NAME ||
+		mxUtils.indexOf(this.exclude, attr) >= 0;
+};
+
+/**
+ * Function: isReference
+ *
+ * Returns true if the given fieldname is to be treated
+ * as a textual reference (ID). This implementation returns
+ * true if the given fieldname is in <idrefs>.
+ *
+ * Parameters:
+ *
+ * obj - Object instance that contains the field.
+ * attr - Fieldname of the field.
+ * value - Value of the field. 
+ * write - Boolean indicating if the field is being encoded or decoded.
+ * Write is true if the field is being encoded, else it is being decoded.
+ */
+mxObjectCodec.prototype.isReference = function(obj, attr, value, write)
+{
+	return mxUtils.indexOf(this.idrefs, attr) >= 0;
+};
+
+/**
+ * Function: encode
+ *
+ * Encodes the specified object and returns a node
+ * representing then given object. Calls <beforeEncode>
+ * after creating the node and <afterEncode> with the 
+ * resulting node after processing.
+ *
+ * Enc is a reference to the calling encoder. It is used
+ * to encode complex objects and create references.
+ *
+ * This implementation encodes all variables of an
+ * object according to the following rules:
+ *
+ * - If the variable name is in <exclude> then it is ignored.
+ * - If the variable name is in <idrefs> then <mxCodec.getId>
+ * is used to replace the object with its ID.
+ * - The variable name is mapped using <mapping>.
+ * - If obj is an array and the variable name is numeric
+ * (ie. an index) then it is not encoded.
+ * - If the value is an object, then the codec is used to
+ * create a child node with the variable name encoded into
+ * the "as" attribute.
+ * - Else, if <encodeDefaults> is true or the value differs
+ * from the template value, then ...
+ * - ... if obj is not an array, then the value is mapped to
+ * an attribute.
+ * - ... else if obj is an array, the value is mapped to an
+ * add child with a value attribute or a text child node,
+ * if the value is a function.
+ *
+ * If no ID exists for a variable in <idrefs> or if an object
+ * cannot be encoded, a warning is issued using <mxLog.warn>.
+ *
+ * Returns the resulting XML node that represents the given
+ * object.
+ *
+ * Parameters:
+ *
+ * enc - <mxCodec> that controls the encoding process.
+ * obj - Object to be encoded.
+ */
+mxObjectCodec.prototype.encode = function(enc, obj)
+{
+	var node = enc.document.createElement(this.getName());
+	
+	obj = this.beforeEncode(enc, obj, node);
+	this.encodeObject(enc, obj, node);
+	
+	return this.afterEncode(enc, obj, node);
+};
+	
+/**
+ * Function: encodeObject
+ *
+ * Encodes the value of each member in then given obj into the given node using
+ * <encodeValue>.
+ * 
+ * Parameters:
+ *
+ * enc - <mxCodec> that controls the encoding process.
+ * obj - Object to be encoded.
+ * node - XML node that contains the encoded object.
+ */
+mxObjectCodec.prototype.encodeObject = function(enc, obj, node)
+{
+	enc.setAttribute(node, 'id', enc.getId(obj));
+	
+    for (var i in obj)
+    {
+		var name = i;
+		var value = obj[name];
+		
+    	if (value != null && !this.isExcluded(obj, name, value, true))
+    	{
+    		if (mxUtils.isInteger(name))
+    		{
+    			name = null;
+    		}
+    		
+    		this.encodeValue(enc, obj, name, value, node);
+    	}
+    }
+};
+
+/**
+ * Function: encodeValue
+ * 
+ * Converts the given value according to the mappings
+ * and id-refs in this codec and uses <writeAttribute>
+ * to write the attribute into the given node.
+ * 
+ * Parameters:
+ *
+ * enc - <mxCodec> that controls the encoding process.
+ * obj - Object whose property is going to be encoded.
+ * name - XML node that contains the encoded object.
+ * value - Value of the property to be encoded.
+ * node - XML node that contains the encoded object.
+ */
+mxObjectCodec.prototype.encodeValue = function(enc, obj, name, value, node)
+{
+	if (value != null)
+	{
+		if (this.isReference(obj, name, value, true))
+		{
+			var tmp = enc.getId(value);
+			
+			if (tmp == null)
+			{
+		    	mxLog.warn('mxObjectCodec.encode: No ID for ' +
+		    		this.getName() + '.' + name + '=' + value);
+		    	return; // exit
+		    }
+		    
+		    value = tmp;
+		}
+
+		var defaultValue = this.template[name];
+		
+		// Checks if the value is a default value and
+		// the name is correct
+		if (name == null || enc.encodeDefaults || defaultValue != value)
+		{
+			name = this.getAttributeName(name);
+			this.writeAttribute(enc, obj, name, value, node);	
+		}
+	}
+};
+
+/**
+ * Function: writeAttribute
+ * 
+ * Writes the given value into node using <writePrimitiveAttribute>
+ * or <writeComplexAttribute> depending on the type of the value.
+ */
+mxObjectCodec.prototype.writeAttribute = function(enc, obj, name, value, node)
+{
+	if (typeof(value) != 'object' /* primitive type */)
+	{
+		this.writePrimitiveAttribute(enc, obj, name, value, node);
+	}
+	else /* complex type */
+	{
+		this.writeComplexAttribute(enc, obj, name, value, node);
+	}
+};
+
+/**
+ * Function: writePrimitiveAttribute
+ * 
+ * Writes the given value as an attribute of the given node.
+ */
+mxObjectCodec.prototype.writePrimitiveAttribute = function(enc, obj, name, value, node)
+{
+	value = this.convertAttributeToXml(enc, obj, name, value, node);
+	
+	if (name == null)
+	{
+		var child = enc.document.createElement('add');
+		
+		if (typeof(value) == 'function')
+		{
+    		child.appendChild(enc.document.createTextNode(value));
+    	}
+    	else
+    	{
+    		enc.setAttribute(child, 'value', value);
+    	}
+    	
+		node.appendChild(child);
+	}
+	else if (typeof(value) != 'function')
+	{
+    	enc.setAttribute(node, name, value);
+	}		
+};
+	
+/**
+ * Function: writeComplexAttribute
+ * 
+ * Writes the given value as a child node of the given node.
+ */
+mxObjectCodec.prototype.writeComplexAttribute = function(enc, obj, name, value, node)
+{
+	var child = enc.encode(value);
+	
+	if (child != null)
+	{
+		if (name != null)
+		{
+    		child.setAttribute('as', name);
+    	}
+    	
+    	node.appendChild(child);
+	}
+	else
+	{
+		mxLog.warn('mxObjectCodec.encode: No node for ' + this.getName() + '.' + name + ': ' + value);
+	}
+};
+
+/**
+ * Function: convertAttributeToXml
+ * 
+ * Converts true to "1" and false to "0" is <isBooleanAttribute> returns true.
+ * All other values are not converted.
+ * 
+ * Parameters:
+ *
+ * enc - <mxCodec> that controls the encoding process.
+ * obj - Objec to convert the attribute for.
+ * name - Name of the attribute to be converted.
+ * value - Value to be converted.
+ */
+mxObjectCodec.prototype.convertAttributeToXml = function(enc, obj, name, value)
+{
+	// Makes sure to encode boolean values as numeric values
+	if (this.isBooleanAttribute(enc, obj, name, value))
+	{	
+		// Checks if the value is true (do not use the value as is, because
+		// this would check if the value is not null, so 0 would be true)
+		value = (value == true) ? '1' : '0';
+	}
+	
+	return value;
+};
+
+/**
+ * Function: isBooleanAttribute
+ * 
+ * Returns true if the given object attribute is a boolean value.
+ * 
+ * Parameters:
+ *
+ * enc - <mxCodec> that controls the encoding process.
+ * obj - Objec to convert the attribute for.
+ * name - Name of the attribute to be converted.
+ * value - Value of the attribute to be converted.
+ */
+mxObjectCodec.prototype.isBooleanAttribute = function(enc, obj, name, value)
+{
+	return (typeof(value.length) == 'undefined' && (value == true || value == false));
+};
+
+/**
+ * Function: convertAttributeFromXml
+ * 
+ * Converts booleans and numeric values to the respective types. Values are
+ * numeric if <isNumericAttribute> returns true.
+ * 
+ * Parameters:
+ *
+ * dec - <mxCodec> that controls the decoding process.
+ * attr - XML attribute to be converted.
+ * obj - Objec to convert the attribute for.
+ */
+mxObjectCodec.prototype.convertAttributeFromXml = function(dec, attr, obj)
+{
+	var value = attr.value;
+	
+	if (this.isNumericAttribute(dec, attr, obj))
+	{
+		value = parseFloat(value);
+	}
+	
+	return value;
+};
+
+/**
+ * Function: isNumericAttribute
+ * 
+ * Returns true if the given XML attribute is a numeric value.
+ * 
+ * Parameters:
+ *
+ * dec - <mxCodec> that controls the decoding process.
+ * attr - XML attribute to be converted.
+ * obj - Objec to convert the attribute for.
+ */
+mxObjectCodec.prototype.isNumericAttribute = function(dec, attr, obj)
+{
+	return mxUtils.isNumeric(attr.value);
+};
+
+/**
+ * Function: beforeEncode
+ *
+ * Hook for subclassers to pre-process the object before
+ * encoding. This returns the input object. The return
+ * value of this function is used in <encode> to perform
+ * the default encoding into the given node.
+ *
+ * Parameters:
+ *
+ * enc - <mxCodec> that controls the encoding process.
+ * obj - Object to be encoded.
+ * node - XML node to encode the object into.
+ */
+mxObjectCodec.prototype.beforeEncode = function(enc, obj, node)
+{
+	return obj;
+};
+
+/**
+ * Function: afterEncode
+ *
+ * Hook for subclassers to post-process the node
+ * for the given object after encoding and return the
+ * post-processed node. This implementation returns 
+ * the input node. The return value of this method
+ * is returned to the encoder from <encode>.
+ *
+ * Parameters:
+ *
+ * enc - <mxCodec> that controls the encoding process.
+ * obj - Object to be encoded.
+ * node - XML node that represents the default encoding.
+ */
+mxObjectCodec.prototype.afterEncode = function(enc, obj, node)
+{
+	return node;
+};
+
+/**
+ * Function: decode
+ *
+ * Parses the given node into the object or returns a new object
+ * representing the given node.
+ *
+ * Dec is a reference to the calling decoder. It is used to decode
+ * complex objects and resolve references.
+ *
+ * If a node has an id attribute then the object cache is checked for the
+ * object. If the object is not yet in the cache then it is constructed
+ * using the constructor of <template> and cached in <mxCodec.objects>.
+ *
+ * This implementation decodes all attributes and childs of a node
+ * according to the following rules:
+ *
+ * - If the variable name is in <exclude> or if the attribute name is "id"
+ * or "as" then it is ignored.
+ * - If the variable name is in <idrefs> then <mxCodec.getObject> is used
+ * to replace the reference with an object.
+ * - The variable name is mapped using a reverse <mapping>.
+ * - If the value has a child node, then the codec is used to create a
+ * child object with the variable name taken from the "as" attribute.
+ * - If the object is an array and the variable name is empty then the
+ * value or child object is appended to the array.
+ * - If an add child has no value or the object is not an array then
+ * the child text content is evaluated using <mxUtils.eval>.
+ *
+ * For add nodes where the object is not an array and the variable name
+ * is defined, the default mechanism is used, allowing to override/add
+ * methods as follows:
+ *
+ * (code)
+ * <Object>
+ *   <add as="hello"><![CDATA[
+ *     function(arg1) {
+ *       mxUtils.alert('Hello '+arg1);
+ *     }
+ *   ]]></add>
+ * </Object>
+ * (end) 
+ *
+ * If no object exists for an ID in <idrefs> a warning is issued
+ * using <mxLog.warn>.
+ *
+ * Returns the resulting object that represents the given XML node
+ * or the object given to the method as the into parameter.
+ *
+ * Parameters:
+ *
+ * dec - <mxCodec> that controls the decoding process.
+ * node - XML node to be decoded.
+ * into - Optional objec to encode the node into.
+ */
+mxObjectCodec.prototype.decode = function(dec, node, into)
+{
+	var id = node.getAttribute('id');
+	var obj = dec.objects[id];
+	
+	if (obj == null)
+	{
+		obj = into || this.cloneTemplate();
+		
+		if (id != null)
+		{
+			dec.putObject(id, obj);
+		}
+	}
+	
+	node = this.beforeDecode(dec, node, obj);
+	this.decodeNode(dec, node, obj);
+	
+    return this.afterDecode(dec, node, obj);
+};	
+
+/**
+ * Function: decodeNode
+ * 
+ * Calls <decodeAttributes> and <decodeChildren> for the given node.
+ * 
+ * Parameters:
+ *
+ * dec - <mxCodec> that controls the decoding process.
+ * node - XML node to be decoded.
+ * obj - Objec to encode the node into.
+ */	
+mxObjectCodec.prototype.decodeNode = function(dec, node, obj)
+{
+	if (node != null)
+	{
+		this.decodeAttributes(dec, node, obj);
+		this.decodeChildren(dec, node, obj);
+	}
+};
+
+/**
+ * Function: decodeAttributes
+ * 
+ * Decodes all attributes of the given node using <decodeAttribute>.
+ * 
+ * Parameters:
+ *
+ * dec - <mxCodec> that controls the decoding process.
+ * node - XML node to be decoded.
+ * obj - Objec to encode the node into.
+ */	
+mxObjectCodec.prototype.decodeAttributes = function(dec, node, obj)
+{
+	var attrs = node.attributes;
+	
+	if (attrs != null)
+	{
+		for (var i = 0; i < attrs.length; i++)
+		{
+			this.decodeAttribute(dec, attrs[i], obj);
+		}
+	}
+};
+
+/**
+ * Function: isIgnoredAttribute
+ * 
+ * Returns true if the given attribute should be ignored. This implementation
+ * returns true if the attribute name is "as" or "id".
+ * 
+ * Parameters:
+ *
+ * dec - <mxCodec> that controls the decoding process.
+ * attr - XML attribute to be decoded.
+ * obj - Objec to encode the attribute into.
+ */	
+mxObjectCodec.prototype.isIgnoredAttribute = function(dec, attr, obj)
+{
+	return attr.nodeName == 'as' || attr.nodeName == 'id';
+};
+
+/**
+ * Function: decodeAttribute
+ * 
+ * Reads the given attribute into the specified object.
+ * 
+ * Parameters:
+ *
+ * dec - <mxCodec> that controls the decoding process.
+ * attr - XML attribute to be decoded.
+ * obj - Objec to encode the attribute into.
+ */	
+mxObjectCodec.prototype.decodeAttribute = function(dec, attr, obj)
+{
+	if (!this.isIgnoredAttribute(dec, attr, obj))
+	{
+		var name = attr.nodeName;
+		
+		// Converts the string true and false to their boolean values.
+		// This may require an additional check on the obj to see if
+		// the existing field is a boolean value or uninitialized, in
+		// which case we may want to convert true and false to a string.
+		var value = this.convertAttributeFromXml(dec, attr, obj);
+		var fieldname = this.getFieldName(name);
+		
+		if (this.isReference(obj, fieldname, value, false))
+		{
+			var tmp = dec.getObject(value);
+			
+			if (tmp == null)
+			{
+		    	mxLog.warn('mxObjectCodec.decode: No object for ' +
+		    		this.getName() + '.' + name + '=' + value);
+		    	return; // exit
+		    }
+		    
+		    value = tmp;
+		}
+
+		if (!this.isExcluded(obj, name, value, false))
+		{
+			//mxLog.debug(mxUtils.getFunctionName(obj.constructor)+'.'+name+'='+value);
+			obj[name] = value;
+		}
+	}
+};
+
+/**
+ * Function: decodeChildren
+ * 
+ * Decodes all children of the given node using <decodeChild>.
+ * 
+ * Parameters:
+ *
+ * dec - <mxCodec> that controls the decoding process.
+ * node - XML node to be decoded.
+ * obj - Objec to encode the node into.
+ */	
+mxObjectCodec.prototype.decodeChildren = function(dec, node, obj)
+{
+	var child = node.firstChild;
+	
+	while (child != null)
+	{
+		var tmp = child.nextSibling;
+		
+		if (child.nodeType == mxConstants.NODETYPE_ELEMENT &&
+			!this.processInclude(dec, child, obj))
+		{
+			this.decodeChild(dec, child, obj);
+		}
+		
+		child = tmp;
+	}
+};
+
+/**
+ * Function: decodeChild
+ * 
+ * Reads the specified child into the given object.
+ * 
+ * Parameters:
+ *
+ * dec - <mxCodec> that controls the decoding process.
+ * child - XML child element to be decoded.
+ * obj - Objec to encode the node into.
+ */	
+mxObjectCodec.prototype.decodeChild = function(dec, child, obj)
+{
+	var fieldname = this.getFieldName(child.getAttribute('as'));
+	
+	if (fieldname == null || !this.isExcluded(obj, fieldname, child, false))
+	{
+		var template = this.getFieldTemplate(obj, fieldname, child);
+		var value = null;
+		
+		if (child.nodeName == 'add')
+		{
+			value = child.getAttribute('value');
+			
+			if (value == null && mxObjectCodec.allowEval)
+			{
+				value = mxUtils.eval(mxUtils.getTextContent(child));
+			}
+		}
+		else
+		{
+			value = dec.decode(child, template);
+		}
+
+		this.addObjectValue(obj, fieldname, value, template);
+	}
+};
+
+/**
+ * Function: getFieldTemplate
+ * 
+ * Returns the template instance for the given field. This returns the
+ * value of the field, null if the value is an array or an empty collection
+ * if the value is a collection. The value is then used to populate the
+ * field for a new instance. For strongly typed languages it may be
+ * required to override this to return the correct collection instance
+ * based on the encoded child.
+ */	
+mxObjectCodec.prototype.getFieldTemplate = function(obj, fieldname, child)
+{
+	var template = obj[fieldname];
+	
+	// Non-empty arrays are replaced completely
+    if (template instanceof Array && template.length > 0)
+    {
+        template = null;
+    }
+    
+    return template;
+};
+
+/**
+ * Function: addObjectValue
+ * 
+ * Sets the decoded child node as a value of the given object. If the
+ * object is a map, then the value is added with the given fieldname as a
+ * key. If the fieldname is not empty, then setFieldValue is called or
+ * else, if the object is a collection, the value is added to the
+ * collection. For strongly typed languages it may be required to
+ * override this with the correct code to add an entry to an object.
+ */	
+mxObjectCodec.prototype.addObjectValue = function(obj, fieldname, value, template)
+{
+	if (value != null && value != template)
+	{
+		if (fieldname != null && fieldname.length > 0)
+		{
+			obj[fieldname] = value;
+		}
+		else
+		{
+			obj.push(value);
+		}
+		//mxLog.debug('Decoded '+mxUtils.getFunctionName(obj.constructor)+'.'+fieldname+': '+value);
+	}
+};
+
+/**
+ * Function: processInclude
+ *
+ * Returns true if the given node is an include directive and
+ * executes the include by decoding the XML document. Returns
+ * false if the given node is not an include directive.
+ *
+ * Parameters:
+ *
+ * dec - <mxCodec> that controls the encoding/decoding process.
+ * node - XML node to be checked.
+ * into - Optional object to pass-thru to the codec.
+ */
+mxObjectCodec.prototype.processInclude = function(dec, node, into)
+{
+	if (node.nodeName == 'include')
+	{
+		var name = node.getAttribute('name');
+		
+		if (name != null)
+		{
+			try
+			{
+				var xml = mxUtils.load(name).getDocumentElement();
+				
+				if (xml != null)
+				{
+					dec.decode(xml, into);
+				}
+			}
+			catch (e)
+			{
+				// ignore
+			}
+		}
+		
+		return true;
+	}
+	
+	return false;
+};
+
+/**
+ * Function: beforeDecode
+ *
+ * Hook for subclassers to pre-process the node for
+ * the specified object and return the node to be
+ * used for further processing by <decode>.
+ * The object is created based on the template in the 
+ * calling method and is never null. This implementation
+ * returns the input node. The return value of this
+ * function is used in <decode> to perform
+ * the default decoding into the given object.
+ *
+ * Parameters:
+ *
+ * dec - <mxCodec> that controls the decoding process.
+ * node - XML node to be decoded.
+ * obj - Object to encode the node into.
+ */
+mxObjectCodec.prototype.beforeDecode = function(dec, node, obj)
+{
+	return node;
+};
+
+/**
+ * Function: afterDecode
+ *
+ * Hook for subclassers to post-process the object after
+ * decoding. This implementation returns the given object
+ * without any changes. The return value of this method
+ * is returned to the decoder from <decode>.
+ *
+ * Parameters:
+ *
+ * enc - <mxCodec> that controls the encoding process.
+ * node - XML node to be decoded.
+ * obj - Object that represents the default decoding.
+ */
+mxObjectCodec.prototype.afterDecode = function(dec, node, obj)
+{
+	return obj;
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/io/mxRootChangeCodec.js b/airavata-kubernetes/web-console/src/assets/js/io/mxRootChangeCodec.js
new file mode 100644
index 0000000..bf8c47d
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/io/mxRootChangeCodec.js
@@ -0,0 +1,83 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+mxCodecRegistry.register(function()
+{
+	/**
+	 * Class: mxRootChangeCodec
+	 *
+	 * Codec for <mxRootChange>s. This class is created and registered
+	 * dynamically at load time and used implicitely via <mxCodec> and
+	 * the <mxCodecRegistry>.
+	 *
+	 * Transient Fields:
+	 *
+	 * - model
+	 * - previous
+	 * - root
+	 */
+	var codec = new mxObjectCodec(new mxRootChange(),
+		['model', 'previous', 'root']);
+
+	/**
+	 * Function: onEncode
+	 *
+	 * Encodes the child recursively.
+	 */
+	codec.afterEncode = function(enc, obj, node)
+	{
+		enc.encodeCell(obj.root, node);
+		
+		return node;
+	};
+
+	/**
+	 * Function: beforeDecode
+	 *
+	 * Decodes the optional children as cells
+	 * using the respective decoder.
+	 */
+	codec.beforeDecode = function(dec, node, obj)
+	{
+		if (node.firstChild != null &&
+			node.firstChild.nodeType == mxConstants.NODETYPE_ELEMENT)
+		{
+			// Makes sure the original node isn't modified
+			node = node.cloneNode(true);
+			
+			var tmp = node.firstChild;
+			obj.root = dec.decodeCell(tmp, false);
+
+			var tmp2 = tmp.nextSibling;
+			tmp.parentNode.removeChild(tmp);
+			tmp = tmp2;
+		
+			while (tmp != null)
+			{
+				tmp2 = tmp.nextSibling;
+				dec.decodeCell(tmp);
+				tmp.parentNode.removeChild(tmp);
+				tmp = tmp2;
+			}
+		}
+		
+		return node;
+	};
+	
+	/**
+	 * Function: afterDecode
+	 *
+	 * Restores the state by assigning the previous value.
+	 */
+	codec.afterDecode = function(dec, node, obj)
+	{
+		obj.previous = obj.root;
+		
+		return obj;
+	};
+
+	// Returns the codec into the registry
+	return codec;
+
+}());
diff --git a/airavata-kubernetes/web-console/src/assets/js/io/mxStylesheetCodec.js b/airavata-kubernetes/web-console/src/assets/js/io/mxStylesheetCodec.js
new file mode 100644
index 0000000..8899116
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/io/mxStylesheetCodec.js
@@ -0,0 +1,217 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxStylesheetCodec
+ *
+ * Codec for <mxStylesheet>s. This class is created and registered
+ * dynamically at load time and used implicitely via <mxCodec>
+ * and the <mxCodecRegistry>.
+ */
+var mxStylesheetCodec = mxCodecRegistry.register(function()
+{
+	var codec = new mxObjectCodec(new mxStylesheet());
+
+	/**
+	 * Function: encode
+	 *
+	 * Encodes a stylesheet. See <decode> for a description of the
+	 * format.
+	 */
+	codec.encode = function(enc, obj)
+	{
+		var node = enc.document.createElement(this.getName());
+		
+		for (var i in obj.styles)
+		{
+			var style = obj.styles[i];
+			var styleNode = enc.document.createElement('add');
+			
+			if (i != null)
+			{
+				styleNode.setAttribute('as', i);
+				
+				for (var j in style)
+				{
+					var value = this.getStringValue(j, style[j]);
+					
+					if (value != null)
+					{
+						var entry = enc.document.createElement('add');
+						entry.setAttribute('value', value);
+						entry.setAttribute('as', j);
+						styleNode.appendChild(entry);
+					}
+				}
+				
+				if (styleNode.childNodes.length > 0)
+				{
+					node.appendChild(styleNode);
+				}
+			}
+		}
+		
+	    return node;
+	};
+
+	/**
+	 * Function: getStringValue
+	 *
+	 * Returns the string for encoding the given value.
+	 */
+	codec.getStringValue = function(key, value)
+	{
+		var type = typeof(value);
+		
+		if (type == 'function')
+		{
+			value = mxStyleRegistry.getName(style[j]);
+		}
+		else if (type == 'object')
+		{
+			value = null;
+		}
+		
+		return value;
+	};
+	
+	/**
+	 * Function: decode
+	 *
+	 * Reads a sequence of the following child nodes
+	 * and attributes:
+	 *
+	 * Child Nodes:
+	 *
+	 * add - Adds a new style.
+	 *
+	 * Attributes:
+	 *
+	 * as - Name of the style.
+	 * extend - Name of the style to inherit from.
+	 *
+	 * Each node contains another sequence of add and remove nodes with the following
+	 * attributes:
+	 *
+	 * as - Name of the style (see <mxConstants>).
+	 * value - Value for the style.
+	 *
+	 * Instead of the value-attribute, one can put Javascript expressions into
+	 * the node as follows if <mxStylesheetCodec.allowEval> is true:
+	 * <add as="perimeter">mxPerimeter.RectanglePerimeter</add>
+	 *
+	 * A remove node will remove the entry with the name given in the as-attribute
+	 * from the style.
+	 * 
+	 * Example:
+	 *
+	 * (code)
+	 * <mxStylesheet as="stylesheet">
+	 *   <add as="text">
+	 *     <add as="fontSize" value="12"/>
+	 *   </add>
+	 *   <add as="defaultVertex" extend="text">
+	 *     <add as="shape" value="rectangle"/>
+	 *   </add>
+	 * </mxStylesheet>
+	 * (end)
+	 */
+	codec.decode = function(dec, node, into)
+	{
+		var obj = into || new this.template.constructor();
+		var id = node.getAttribute('id');
+		
+		if (id != null)
+		{
+			dec.objects[id] = obj;
+		}
+		
+		node = node.firstChild;
+		
+		while (node != null)
+		{
+			if (!this.processInclude(dec, node, obj) && node.nodeName == 'add')
+			{
+				var as = node.getAttribute('as');
+				
+				if (as != null)
+				{
+					var extend = node.getAttribute('extend');
+					var style = (extend != null) ? mxUtils.clone(obj.styles[extend]) : null;
+					
+					if (style == null)
+					{
+						if (extend != null)
+						{
+							mxLog.warn('mxStylesheetCodec.decode: stylesheet ' +
+								extend + ' not found to extend');
+						}
+						
+						style = new Object();
+					}
+					
+					var entry = node.firstChild;
+					
+					while (entry != null)
+					{
+						if (entry.nodeType == mxConstants.NODETYPE_ELEMENT)
+						{
+						 	var key = entry.getAttribute('as');
+						 	
+						 	if (entry.nodeName == 'add')
+						 	{
+							 	var text = mxUtils.getTextContent(entry);
+							 	var value = null;
+							 	
+							 	if (text != null && text.length > 0 && mxStylesheetCodec.allowEval)
+							 	{
+							 		value = mxUtils.eval(text);
+							 	}
+							 	else
+							 	{
+							 		value = entry.getAttribute('value');
+							 		
+							 		if (mxUtils.isNumeric(value))
+							 		{
+										value = parseFloat(value);
+									}
+							 	}
+
+							 	if (value != null)
+							 	{
+							 		style[key] = value;
+							 	}
+						 	}
+						 	else if (entry.nodeName == 'remove')
+						 	{
+						 		delete style[key];
+						 	}
+						}
+						
+						entry = entry.nextSibling;
+					}
+					
+					obj.putCellStyle(as, style);
+				}
+			}
+			
+			node = node.nextSibling;
+		}
+		
+		return obj;
+	};
+
+	// Returns the codec into the registry
+	return codec;
+
+}());
+
+/**
+ * Variable: allowEval
+ * 
+ * Static global switch that specifies if the use of eval is allowed for
+ * evaluating text content. Default is true. Set this to false if stylesheets
+ * may contain user input.
+ */
+mxStylesheetCodec.allowEval = true;
diff --git a/airavata-kubernetes/web-console/src/assets/js/io/mxTerminalChangeCodec.js b/airavata-kubernetes/web-console/src/assets/js/io/mxTerminalChangeCodec.js
new file mode 100644
index 0000000..b65f47f
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/io/mxTerminalChangeCodec.js
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+mxCodecRegistry.register(function()
+{
+	/**
+	 * Class: mxTerminalChangeCodec
+	 *
+	 * Codec for <mxTerminalChange>s. This class is created and registered
+	 * dynamically at load time and used implicitely via <mxCodec> and
+	 * the <mxCodecRegistry>.
+	 *
+	 * Transient Fields:
+	 *
+	 * - model
+	 * - previous
+	 *
+	 * Reference Fields:
+	 *
+	 * - cell
+	 * - terminal
+	 */
+	var codec = new mxObjectCodec(new mxTerminalChange(),
+		['model', 'previous'], ['cell', 'terminal']);
+
+	/**
+	 * Function: afterDecode
+	 *
+	 * Restores the state by assigning the previous value.
+	 */
+	codec.afterDecode = function(dec, node, obj)
+	{
+		obj.previous = obj.terminal;
+		
+		return obj;
+	};
+
+	// Returns the codec into the registry
+	return codec;
+
+}());
diff --git a/airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/model/mxGraphAbstractHierarchyCell.js b/airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/model/mxGraphAbstractHierarchyCell.js
new file mode 100644
index 0000000..fd81b0b
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/model/mxGraphAbstractHierarchyCell.js
@@ -0,0 +1,206 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxGraphAbstractHierarchyCell
+ * 
+ * An abstraction of an internal hierarchy node or edge
+ * 
+ * Constructor: mxGraphAbstractHierarchyCell
+ *
+ * Constructs a new hierarchical layout algorithm.
+ *
+ * Arguments:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ * deterministic - Optional boolean that specifies if this layout should be
+ * deterministic. Default is true.
+ */
+function mxGraphAbstractHierarchyCell()
+{
+	this.x = [];
+	this.y = [];
+	this.temp = [];
+};
+
+/**
+ * Variable: maxRank
+ * 
+ * The maximum rank this cell occupies. Default is -1.
+ */
+mxGraphAbstractHierarchyCell.prototype.maxRank = -1;
+
+/**
+ * Variable: minRank
+ * 
+ * The minimum rank this cell occupies. Default is -1.
+ */
+mxGraphAbstractHierarchyCell.prototype.minRank = -1;
+
+/**
+ * Variable: x
+ * 
+ * The x position of this cell for each layer it occupies
+ */
+mxGraphAbstractHierarchyCell.prototype.x = null;
+
+/**
+ * Variable: y
+ * 
+ * The y position of this cell for each layer it occupies
+ */
+mxGraphAbstractHierarchyCell.prototype.y = null;
+
+/**
+ * Variable: width
+ * 
+ * The width of this cell
+ */
+mxGraphAbstractHierarchyCell.prototype.width = 0;
+
+/**
+ * Variable: height
+ * 
+ * The height of this cell
+ */
+mxGraphAbstractHierarchyCell.prototype.height = 0;
+
+/**
+ * Variable: nextLayerConnectedCells
+ * 
+ * A cached version of the cells this cell connects to on the next layer up
+ */
+mxGraphAbstractHierarchyCell.prototype.nextLayerConnectedCells = null;
+
+/**
+ * Variable: previousLayerConnectedCells
+ * 
+ * A cached version of the cells this cell connects to on the next layer down
+ */
+mxGraphAbstractHierarchyCell.prototype.previousLayerConnectedCells = null;
+
+/**
+ * Variable: temp
+ * 
+ * Temporary variable for general use. Generally, try to avoid
+ * carrying information between stages. Currently, the longest
+ * path layering sets temp to the rank position in fixRanks()
+ * and the crossing reduction uses this. This meant temp couldn't
+ * be used for hashing the nodes in the model dfs and so hashCode
+ * was created
+ */
+mxGraphAbstractHierarchyCell.prototype.temp = null;
+
+/**
+ * Function: getNextLayerConnectedCells
+ * 
+ * Returns the cells this cell connects to on the next layer up
+ */
+mxGraphAbstractHierarchyCell.prototype.getNextLayerConnectedCells = function(layer)
+{
+	return null;
+};
+
+/**
+ * Function: getPreviousLayerConnectedCells
+ * 
+ * Returns the cells this cell connects to on the next layer down
+ */
+mxGraphAbstractHierarchyCell.prototype.getPreviousLayerConnectedCells = function(layer)
+{
+	return null;
+};
+
+/**
+ * Function: isEdge
+ * 
+ * Returns whether or not this cell is an edge
+ */
+mxGraphAbstractHierarchyCell.prototype.isEdge = function()
+{
+	return false;
+};
+
+/**
+ * Function: isVertex
+ * 
+ * Returns whether or not this cell is a node
+ */
+mxGraphAbstractHierarchyCell.prototype.isVertex = function()
+{
+	return false;
+};
+
+/**
+ * Function: getGeneralPurposeVariable
+ * 
+ * Gets the value of temp for the specified layer
+ */
+mxGraphAbstractHierarchyCell.prototype.getGeneralPurposeVariable = function(layer)
+{
+	return null;
+};
+
+/**
+ * Function: setGeneralPurposeVariable
+ * 
+ * Set the value of temp for the specified layer
+ */
+mxGraphAbstractHierarchyCell.prototype.setGeneralPurposeVariable = function(layer, value)
+{
+	return null;
+};
+
+/**
+ * Function: setX
+ * 
+ * Set the value of x for the specified layer
+ */
+mxGraphAbstractHierarchyCell.prototype.setX = function(layer, value)
+{
+	if (this.isVertex())
+	{
+		this.x[0] = value;
+	}
+	else if (this.isEdge())
+	{
+		this.x[layer - this.minRank - 1] = value;
+	}
+};
+
+/**
+ * Function: getX
+ * 
+ * Gets the value of x on the specified layer
+ */
+mxGraphAbstractHierarchyCell.prototype.getX = function(layer)
+{
+	if (this.isVertex())
+	{
+		return this.x[0];
+	}
+	else if (this.isEdge())
+	{
+		return this.x[layer - this.minRank - 1];
+	}
+
+	return 0.0;
+};
+
+/**
+ * Function: setY
+ * 
+ * Set the value of y for the specified layer
+ */
+mxGraphAbstractHierarchyCell.prototype.setY = function(layer, value)
+{
+	if (this.isVertex())
+	{
+		this.y[0] = value;
+	}
+	else if (this.isEdge())
+	{
+		this.y[layer -this. minRank - 1] = value;
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/model/mxGraphHierarchyEdge.js b/airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/model/mxGraphHierarchyEdge.js
new file mode 100644
index 0000000..0f81cbb
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/model/mxGraphHierarchyEdge.js
@@ -0,0 +1,187 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxGraphHierarchyEdge
+ * 
+ * An abstraction of a hierarchical edge for the hierarchy layout
+ * 
+ * Constructor: mxGraphHierarchyEdge
+ *
+ * Constructs a hierarchy edge
+ *
+ * Arguments:
+ * 
+ * edges - a list of real graph edges this abstraction represents
+ */
+function mxGraphHierarchyEdge(edges)
+{
+	mxGraphAbstractHierarchyCell.apply(this, arguments);
+	this.edges = edges;
+	this.ids = [];
+	
+	for (var i = 0; i < edges.length; i++)
+	{
+		this.ids.push(mxObjectIdentity.get(edges[i]));
+	}
+};
+
+/**
+ * Extends mxGraphAbstractHierarchyCell.
+ */
+mxGraphHierarchyEdge.prototype = new mxGraphAbstractHierarchyCell();
+mxGraphHierarchyEdge.prototype.constructor = mxGraphHierarchyEdge;
+
+/**
+ * Variable: edges
+ * 
+ * The graph edge(s) this object represents. Parallel edges are all grouped
+ * together within one hierarchy edge.
+ */
+mxGraphHierarchyEdge.prototype.edges = null;
+
+/**
+ * Variable: ids
+ * 
+ * The object identities of the wrapped cells
+ */
+mxGraphHierarchyEdge.prototype.ids = null;
+
+/**
+ * Variable: source
+ * 
+ * The node this edge is sourced at
+ */
+mxGraphHierarchyEdge.prototype.source = null;
+
+/**
+ * Variable: target
+ * 
+ * The node this edge targets
+ */
+mxGraphHierarchyEdge.prototype.target = null;
+
+/**
+ * Variable: isReversed
+ * 
+ * Whether or not the direction of this edge has been reversed
+ * internally to create a DAG for the hierarchical layout
+ */
+mxGraphHierarchyEdge.prototype.isReversed = false;
+
+/**
+ * Function: invert
+ * 
+ * Inverts the direction of this internal edge(s)
+ */
+mxGraphHierarchyEdge.prototype.invert = function(layer)
+{
+	var temp = this.source;
+	this.source = this.target;
+	this.target = temp;
+	this.isReversed = !this.isReversed;
+};
+
+/**
+ * Function: getNextLayerConnectedCells
+ * 
+ * Returns the cells this cell connects to on the next layer up
+ */
+mxGraphHierarchyEdge.prototype.getNextLayerConnectedCells = function(layer)
+{
+	if (this.nextLayerConnectedCells == null)
+	{
+		this.nextLayerConnectedCells = [];
+		
+		for (var i = 0; i < this.temp.length; i++)
+		{
+			this.nextLayerConnectedCells[i] = [];
+			
+			if (i == this.temp.length - 1)
+			{
+				this.nextLayerConnectedCells[i].push(this.source);
+			}
+			else
+			{
+				this.nextLayerConnectedCells[i].push(this);
+			}
+		}
+	}
+	
+	return this.nextLayerConnectedCells[layer - this.minRank - 1];
+};
+
+/**
+ * Function: getPreviousLayerConnectedCells
+ * 
+ * Returns the cells this cell connects to on the next layer down
+ */
+mxGraphHierarchyEdge.prototype.getPreviousLayerConnectedCells = function(layer)
+{
+	if (this.previousLayerConnectedCells == null)
+	{
+		this.previousLayerConnectedCells = [];
+
+		for (var i = 0; i < this.temp.length; i++)
+		{
+			this.previousLayerConnectedCells[i] = [];
+			
+			if (i == 0)
+			{
+				this.previousLayerConnectedCells[i].push(this.target);
+			}
+			else
+			{
+				this.previousLayerConnectedCells[i].push(this);
+			}
+		}
+	}
+
+	return this.previousLayerConnectedCells[layer - this.minRank - 1];
+};
+
+/**
+ * Function: isEdge
+ * 
+ * Returns true.
+ */
+mxGraphHierarchyEdge.prototype.isEdge = function()
+{
+	return true;
+};
+
+/**
+ * Function: getGeneralPurposeVariable
+ * 
+ * Gets the value of temp for the specified layer
+ */
+mxGraphHierarchyEdge.prototype.getGeneralPurposeVariable = function(layer)
+{
+	return this.temp[layer - this.minRank - 1];
+};
+
+/**
+ * Function: setGeneralPurposeVariable
+ * 
+ * Set the value of temp for the specified layer
+ */
+mxGraphHierarchyEdge.prototype.setGeneralPurposeVariable = function(layer, value)
+{
+	this.temp[layer - this.minRank - 1] = value;
+};
+
+/**
+ * Function: getCoreCell
+ * 
+ * Gets the first core edge associated with this wrapper
+ */
+mxGraphHierarchyEdge.prototype.getCoreCell = function()
+{
+	if (this.edges != null && this.edges.length > 0)
+	{
+		return this.edges[0];
+	}
+	
+	return null;
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/model/mxGraphHierarchyModel.js b/airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/model/mxGraphHierarchyModel.js
new file mode 100644
index 0000000..de37e09
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/model/mxGraphHierarchyModel.js
@@ -0,0 +1,681 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxGraphHierarchyModel
+ *
+ * Internal model of a hierarchical graph. This model stores nodes and edges
+ * equivalent to the real graph nodes and edges, but also stores the rank of the
+ * cells, the order within the ranks and the new candidate locations of cells.
+ * The internal model also reverses edge direction were appropriate , ignores
+ * self-loop and groups parallels together under one edge object.
+ *
+ * Constructor: mxGraphHierarchyModel
+ *
+ * Creates an internal ordered graph model using the vertices passed in. If
+ * there are any, leftward edge need to be inverted in the internal model
+ *
+ * Arguments:
+ *
+ * graph - the facade describing the graph to be operated on
+ * vertices - the vertices for this hierarchy
+ * ordered - whether or not the vertices are already ordered
+ * deterministic - whether or not this layout should be deterministic on each
+ * tightenToSource - whether or not to tighten vertices towards the sources
+ * scanRanksFromSinks - Whether rank assignment is from the sinks or sources.
+ * usage
+ */
+function mxGraphHierarchyModel(layout, vertices, roots, parent, tightenToSource)
+{
+	var graph = layout.getGraph();
+	this.tightenToSource = tightenToSource;
+	this.roots = roots;
+	this.parent = parent;
+
+	// map of cells to internal cell needed for second run through
+	// to setup the sink of edges correctly
+	this.vertexMapper = new mxDictionary();
+	this.edgeMapper = new mxDictionary();
+	this.maxRank = 0;
+	var internalVertices = [];
+
+	if (vertices == null)
+	{
+		vertices = this.graph.getChildVertices(parent);
+	}
+
+	this.maxRank = this.SOURCESCANSTARTRANK;
+	// map of cells to internal cell needed for second run through
+	// to setup the sink of edges correctly. Guess size by number
+	// of edges is roughly same as number of vertices.
+	this.createInternalCells(layout, vertices, internalVertices);
+
+	// Go through edges set their sink values. Also check the
+	// ordering if and invert edges if necessary
+	for (var i = 0; i < vertices.length; i++)
+	{
+		var edges = internalVertices[i].connectsAsSource;
+
+		for (var j = 0; j < edges.length; j++)
+		{
+			var internalEdge = edges[j];
+			var realEdges = internalEdge.edges;
+
+			// Only need to process the first real edge, since
+			// all the edges connect to the same other vertex
+			if (realEdges != null && realEdges.length > 0)
+			{
+				var realEdge = realEdges[0];
+				var targetCell = layout.getVisibleTerminal(
+						realEdge, false);
+				var internalTargetCell = this.vertexMapper.get(targetCell);
+
+				if (internalVertices[i] == internalTargetCell)
+				{
+					// If there are parallel edges going between two vertices and not all are in the same direction
+					// you can have navigated across one direction when doing the cycle reversal that isn't the same
+					// direction as the first real edge in the array above. When that happens the if above catches
+					// that and we correct the target cell before continuing.
+					// This branch only detects this single case
+					targetCell = layout.getVisibleTerminal(
+							realEdge, true);
+					internalTargetCell = this.vertexMapper.get(targetCell);
+				}
+				
+				if (internalTargetCell != null
+						&& internalVertices[i] != internalTargetCell)
+				{
+					internalEdge.target = internalTargetCell;
+
+					if (internalTargetCell.connectsAsTarget.length == 0)
+					{
+						internalTargetCell.connectsAsTarget = [];
+					}
+
+					if (mxUtils.indexOf(internalTargetCell.connectsAsTarget, internalEdge) < 0)
+					{
+						internalTargetCell.connectsAsTarget.push(internalEdge);
+					}
+				}
+			}
+		}
+
+		// Use the temp variable in the internal nodes to mark this
+		// internal vertex as having been visited.
+		internalVertices[i].temp[0] = 1;
+	}
+};
+
+/**
+ * Variable: maxRank
+ *
+ * Stores the largest rank number allocated
+ */
+mxGraphHierarchyModel.prototype.maxRank = null;
+
+/**
+ * Variable: vertexMapper
+ *
+ * Map from graph vertices to internal model nodes.
+ */
+mxGraphHierarchyModel.prototype.vertexMapper = null;
+
+/**
+ * Variable: edgeMapper
+ *
+ * Map from graph edges to internal model edges
+ */
+mxGraphHierarchyModel.prototype.edgeMapper = null;
+
+/**
+ * Variable: ranks
+ *
+ * Mapping from rank number to actual rank
+ */
+mxGraphHierarchyModel.prototype.ranks = null;
+
+/**
+ * Variable: roots
+ *
+ * Store of roots of this hierarchy model, these are real graph cells, not
+ * internal cells
+ */
+mxGraphHierarchyModel.prototype.roots = null;
+
+/**
+ * Variable: parent
+ *
+ * The parent cell whose children are being laid out
+ */
+mxGraphHierarchyModel.prototype.parent = null;
+
+/**
+ * Variable: dfsCount
+ *
+ * Count of the number of times the ancestor dfs has been used.
+ */
+mxGraphHierarchyModel.prototype.dfsCount = 0;
+
+/**
+ * Variable: SOURCESCANSTARTRANK
+ *
+ * High value to start source layering scan rank value from.
+ */
+mxGraphHierarchyModel.prototype.SOURCESCANSTARTRANK = 100000000;
+
+/**
+ * Variable: tightenToSource
+ *
+ * Whether or not to tighten the assigned ranks of vertices up towards
+ * the source cells.
+ */
+mxGraphHierarchyModel.prototype.tightenToSource = false;
+
+/**
+ * Function: createInternalCells
+ *
+ * Creates all edges in the internal model
+ *
+ * Parameters:
+ *
+ * layout - Reference to the <mxHierarchicalLayout> algorithm.
+ * vertices - Array of <mxCells> that represent the vertices whom are to
+ * have an internal representation created.
+ * internalVertices - The array of <mxGraphHierarchyNodes> to have their
+ * information filled in using the real vertices.
+ */
+mxGraphHierarchyModel.prototype.createInternalCells = function(layout, vertices, internalVertices)
+{
+	var graph = layout.getGraph();
+
+	// Create internal edges
+	for (var i = 0; i < vertices.length; i++)
+	{
+		internalVertices[i] = new mxGraphHierarchyNode(vertices[i]);
+		this.vertexMapper.put(vertices[i], internalVertices[i]);
+
+		// If the layout is deterministic, order the cells
+		//List outgoingCells = graph.getNeighbours(vertices[i], deterministic);
+		var conns = layout.getEdges(vertices[i]);
+		internalVertices[i].connectsAsSource = [];
+
+		// Create internal edges, but don't do any rank assignment yet
+		// First use the information from the greedy cycle remover to
+		// invert the leftward edges internally
+		for (var j = 0; j < conns.length; j++)
+		{
+			var cell = layout.getVisibleTerminal(conns[j], false);
+
+			// Looking for outgoing edges only
+			if (cell != vertices[i] && layout.graph.model.isVertex(cell) &&
+					!layout.isVertexIgnored(cell))
+			{
+				// We process all edge between this source and its targets
+				// If there are edges going both ways, we need to collect
+				// them all into one internal edges to avoid looping problems
+				// later. We assume this direction (source -> target) is the 
+				// natural direction if at least half the edges are going in
+				// that direction.
+
+				// The check below for edges[0] being in the vertex mapper is
+				// in case we've processed this the other way around
+				// (target -> source) and the number of edges in each direction
+				// are the same. All the graph edges will have been assigned to
+				// an internal edge going the other way, so we don't want to 
+				// process them again
+				var undirectedEdges = layout.getEdgesBetween(vertices[i],
+						cell, false);
+				var directedEdges = layout.getEdgesBetween(vertices[i],
+						cell, true);
+				
+				if (undirectedEdges != null &&
+						undirectedEdges.length > 0 &&
+						this.edgeMapper.get(undirectedEdges[0]) == null &&
+						directedEdges.length * 2 >= undirectedEdges.length)
+				{
+					var internalEdge = new mxGraphHierarchyEdge(undirectedEdges);
+
+					for (var k = 0; k < undirectedEdges.length; k++)
+					{
+						var edge = undirectedEdges[k];
+						this.edgeMapper.put(edge, internalEdge);
+
+						// Resets all point on the edge and disables the edge style
+						// without deleting it from the cell style
+						graph.resetEdge(edge);
+
+					    if (layout.disableEdgeStyle)
+					    {
+					    	layout.setEdgeStyleEnabled(edge, false);
+					    	layout.setOrthogonalEdge(edge,true);
+					    }
+					}
+
+					internalEdge.source = internalVertices[i];
+
+					if (mxUtils.indexOf(internalVertices[i].connectsAsSource, internalEdge) < 0)
+					{
+						internalVertices[i].connectsAsSource.push(internalEdge);
+					}
+				}
+			}
+		}
+
+		// Ensure temp variable is cleared from any previous use
+		internalVertices[i].temp[0] = 0;
+	}
+};
+
+/**
+ * Function: initialRank
+ *
+ * Basic determination of minimum layer ranking by working from from sources
+ * or sinks and working through each node in the relevant edge direction.
+ * Starting at the sinks is basically a longest path layering algorithm.
+*/
+mxGraphHierarchyModel.prototype.initialRank = function()
+{
+	var startNodes = [];
+
+	if (this.roots != null)
+	{
+		for (var i = 0; i < this.roots.length; i++)
+		{
+			var internalNode = this.vertexMapper.get(this.roots[i]);
+
+			if (internalNode != null)
+			{
+				startNodes.push(internalNode);
+			}
+		}
+	}
+
+	var internalNodes = this.vertexMapper.getValues();
+	
+	for (var i=0; i < internalNodes.length; i++)
+	{
+		// Mark the node as not having had a layer assigned
+		internalNodes[i].temp[0] = -1;
+	}
+
+	var startNodesCopy = startNodes.slice();
+
+	while (startNodes.length > 0)
+	{
+		var internalNode = startNodes[0];
+		var layerDeterminingEdges;
+		var edgesToBeMarked;
+
+		layerDeterminingEdges = internalNode.connectsAsTarget;
+		edgesToBeMarked = internalNode.connectsAsSource;
+
+		// flag to keep track of whether or not all layer determining
+		// edges have been scanned
+		var allEdgesScanned = true;
+
+		// Work out the layer of this node from the layer determining
+		// edges. The minimum layer number of any node connected by one of
+		// the layer determining edges variable
+		var minimumLayer = this.SOURCESCANSTARTRANK;
+
+		for (var i = 0; i < layerDeterminingEdges.length; i++)
+		{
+			var internalEdge = layerDeterminingEdges[i];
+
+			if (internalEdge.temp[0] == 5270620)
+			{
+				// This edge has been scanned, get the layer of the
+				// node on the other end
+				var otherNode = internalEdge.source;
+				minimumLayer = Math.min(minimumLayer, otherNode.temp[0] - 1);
+			}
+			else
+			{
+				allEdgesScanned = false;
+
+				break;
+			}
+		}
+
+		// If all edge have been scanned, assign the layer, mark all
+		// edges in the other direction and remove from the nodes list
+		if (allEdgesScanned)
+		{
+			internalNode.temp[0] = minimumLayer;
+			this.maxRank = Math.min(this.maxRank, minimumLayer);
+
+			if (edgesToBeMarked != null)
+			{
+				for (var i = 0; i < edgesToBeMarked.length; i++)
+				{
+					var internalEdge = edgesToBeMarked[i];
+
+					// Assign unique stamp ( y/m/d/h )
+					internalEdge.temp[0] = 5270620;
+
+					// Add node on other end of edge to LinkedList of
+					// nodes to be analysed
+					var otherNode = internalEdge.target;
+
+					// Only add node if it hasn't been assigned a layer
+					if (otherNode.temp[0] == -1)
+					{
+						startNodes.push(otherNode);
+
+						// Mark this other node as neither being
+						// unassigned nor assigned so it isn't
+						// added to this list again, but it's
+						// layer isn't used in any calculation.
+						otherNode.temp[0] = -2;
+					}
+				}
+			}
+
+			startNodes.shift();
+		}
+		else
+		{
+			// Not all the edges have been scanned, get to the back of
+			// the class and put the dunces cap on
+			var removedCell = startNodes.shift();
+			startNodes.push(internalNode);
+
+			if (removedCell == internalNode && startNodes.length == 1)
+			{
+				// This is an error condition, we can't get out of
+				// this loop. It could happen for more than one node
+				// but that's a lot harder to detect. Log the error
+				// TODO make log comment
+				break;
+			}
+		}
+	}
+
+	// Normalize the ranks down from their large starting value to place
+	// at least 1 sink on layer 0
+	for (var i=0; i < internalNodes.length; i++)
+	{
+		// Mark the node as not having had a layer assigned
+		internalNodes[i].temp[0] -= this.maxRank;
+	}
+	
+	// Tighten the rank 0 nodes as far as possible
+	for ( var i = 0; i < startNodesCopy.length; i++)
+	{
+		var internalNode = startNodesCopy[i];
+		var currentMaxLayer = 0;
+		var layerDeterminingEdges = internalNode.connectsAsSource;
+
+		for ( var j = 0; j < layerDeterminingEdges.length; j++)
+		{
+			var internalEdge = layerDeterminingEdges[j];
+			var otherNode = internalEdge.target;
+			internalNode.temp[0] = Math.max(currentMaxLayer,
+					otherNode.temp[0] + 1);
+			currentMaxLayer = internalNode.temp[0];
+		}
+	}
+	
+	// Reset the maxRank to that which would be expected for a from-sink
+	// scan
+	this.maxRank = this.SOURCESCANSTARTRANK - this.maxRank;
+};
+
+/**
+ * Function: fixRanks
+ *
+ * Fixes the layer assignments to the values stored in the nodes. Also needs
+ * to create dummy nodes for edges that cross layers.
+ */
+mxGraphHierarchyModel.prototype.fixRanks = function()
+{
+	var rankList = [];
+	this.ranks = [];
+
+	for (var i = 0; i < this.maxRank + 1; i++)
+	{
+		rankList[i] = [];
+		this.ranks[i] = rankList[i];
+	}
+
+	// Perform a DFS to obtain an initial ordering for each rank.
+	// Without doing this you would end up having to process
+	// crossings for a standard tree.
+	var rootsArray = null;
+
+	if (this.roots != null)
+	{
+		var oldRootsArray = this.roots;
+		rootsArray = [];
+
+		for (var i = 0; i < oldRootsArray.length; i++)
+		{
+			var cell = oldRootsArray[i];
+			var internalNode = this.vertexMapper.get(cell);
+			rootsArray[i] = internalNode;
+		}
+	}
+
+	this.visit(function(parent, node, edge, layer, seen)
+	{
+		if (seen == 0 && node.maxRank < 0 && node.minRank < 0)
+		{
+			rankList[node.temp[0]].push(node);
+			node.maxRank = node.temp[0];
+			node.minRank = node.temp[0];
+
+			// Set temp[0] to the nodes position in the rank
+			node.temp[0] = rankList[node.maxRank].length - 1;
+		}
+
+		if (parent != null && edge != null)
+		{
+			var parentToCellRankDifference = parent.maxRank - node.maxRank;
+
+			if (parentToCellRankDifference > 1)
+			{
+				// There are ranks in between the parent and current cell
+				edge.maxRank = parent.maxRank;
+				edge.minRank = node.maxRank;
+				edge.temp = [];
+				edge.x = [];
+				edge.y = [];
+
+				for (var i = edge.minRank + 1; i < edge.maxRank; i++)
+				{
+					// The connecting edge must be added to the
+					// appropriate ranks
+					rankList[i].push(edge);
+					edge.setGeneralPurposeVariable(i, rankList[i]
+							.length - 1);
+				}
+			}
+		}
+	}, rootsArray, false, null);
+};
+
+/**
+ * Function: visit
+ *
+ * A depth first search through the internal heirarchy model.
+ *
+ * Parameters:
+ *
+ * visitor - The visitor function pattern to be called for each node.
+ * trackAncestors - Whether or not the search is to keep track all nodes
+ * directly above this one in the search path.
+ */
+mxGraphHierarchyModel.prototype.visit = function(visitor, dfsRoots, trackAncestors, seenNodes)
+{
+	// Run dfs through on all roots
+	if (dfsRoots != null)
+	{
+		for (var i = 0; i < dfsRoots.length; i++)
+		{
+			var internalNode = dfsRoots[i];
+
+			if (internalNode != null)
+			{
+				if (seenNodes == null)
+				{
+					seenNodes = new Object();
+				}
+
+				if (trackAncestors)
+				{
+					// Set up hash code for root
+					internalNode.hashCode = [];
+					internalNode.hashCode[0] = this.dfsCount;
+					internalNode.hashCode[1] = i;
+					this.extendedDfs(null, internalNode, null, visitor, seenNodes,
+							internalNode.hashCode, i, 0);
+				}
+				else
+				{
+					this.dfs(null, internalNode, null, visitor, seenNodes, 0);
+				}
+			}
+		}
+
+		this.dfsCount++;
+	}
+};
+
+/**
+ * Function: dfs
+ *
+ * Performs a depth first search on the internal hierarchy model
+ *
+ * Parameters:
+ *
+ * parent - the parent internal node of the current internal node
+ * root - the current internal node
+ * connectingEdge - the internal edge connecting the internal node and the parent
+ * internal node, if any
+ * visitor - the visitor pattern to be called for each node
+ * seen - a set of all nodes seen by this dfs a set of all of the
+ * ancestor node of the current node
+ * layer - the layer on the dfs tree ( not the same as the model ranks )
+ */
+mxGraphHierarchyModel.prototype.dfs = function(parent, root, connectingEdge, visitor, seen, layer)
+{
+	if (root != null)
+	{
+		var rootId = root.id;
+
+		if (seen[rootId] == null)
+		{
+			seen[rootId] = root;
+			visitor(parent, root, connectingEdge, layer, 0);
+
+			// Copy the connects as source list so that visitors
+			// can change the original for edge direction inversions
+			var outgoingEdges = root.connectsAsSource.slice();
+			
+			for (var i = 0; i< outgoingEdges.length; i++)
+			{
+				var internalEdge = outgoingEdges[i];
+				var targetNode = internalEdge.target;
+
+				// Root check is O(|roots|)
+				this.dfs(root, targetNode, internalEdge, visitor, seen,
+						layer + 1);
+			}
+		}
+		else
+		{
+			// Use the int field to indicate this node has been seen
+			visitor(parent, root, connectingEdge, layer, 1);
+		}
+	}
+};
+
+/**
+ * Function: extendedDfs
+ *
+ * Performs a depth first search on the internal hierarchy model. This dfs
+ * extends the default version by keeping track of cells ancestors, but it
+ * should be only used when necessary because of it can be computationally
+ * intensive for deep searches.
+ *
+ * Parameters:
+ *
+ * parent - the parent internal node of the current internal node
+ * root - the current internal node
+ * connectingEdge - the internal edge connecting the internal node and the parent
+ * internal node, if any
+ * visitor - the visitor pattern to be called for each node
+ * seen - a set of all nodes seen by this dfs
+ * ancestors - the parent hash code
+ * childHash - the new hash code for this node
+ * layer - the layer on the dfs tree ( not the same as the model ranks )
+ */
+mxGraphHierarchyModel.prototype.extendedDfs = function(parent, root, connectingEdge, visitor, seen, ancestors, childHash, layer)
+{
+	// Explanation of custom hash set. Previously, the ancestors variable
+	// was passed through the dfs as a HashSet. The ancestors were copied
+	// into a new HashSet and when the new child was processed it was also
+	// added to the set. If the current node was in its ancestor list it
+	// meant there is a cycle in the graph and this information is passed
+	// to the visitor.visit() in the seen parameter. The HashSet clone was
+	// very expensive on CPU so a custom hash was developed using primitive
+	// types. temp[] couldn't be used so hashCode[] was added to each node.
+	// Each new child adds another int to the array, copying the prefix
+	// from its parent. Child of the same parent add different ints (the
+	// limit is therefore 2^32 children per parent...). If a node has a
+	// child with the hashCode already set then the child code is compared
+	// to the same portion of the current nodes array. If they match there
+	// is a loop.
+	// Note that the basic mechanism would only allow for 1 use of this
+	// functionality, so the root nodes have two ints. The second int is
+	// incremented through each node root and the first is incremented
+	// through each run of the dfs algorithm (therefore the dfs is not
+	// thread safe). The hash code of each node is set if not already set,
+	// or if the first int does not match that of the current run.
+	if (root != null)
+	{
+		if (parent != null)
+		{
+			// Form this nodes hash code if necessary, that is, if the
+			// hashCode variable has not been initialized or if the
+			// start of the parent hash code does not equal the start of
+			// this nodes hash code, indicating the code was set on a
+			// previous run of this dfs.
+			if (root.hashCode == null ||
+				root.hashCode[0] != parent.hashCode[0])
+			{
+				var hashCodeLength = parent.hashCode.length + 1;
+				root.hashCode = parent.hashCode.slice();
+				root.hashCode[hashCodeLength - 1] = childHash;
+			}
+		}
+
+		var rootId = root.id;
+
+		if (seen[rootId] == null)
+		{
+			seen[rootId] = root;
+			visitor(parent, root, connectingEdge, layer, 0);
+
+			// Copy the connects as source list so that visitors
+			// can change the original for edge direction inversions
+			var outgoingEdges = root.connectsAsSource.slice();
+
+			for (var i = 0; i < outgoingEdges.length; i++)
+			{
+				var internalEdge = outgoingEdges[i];
+				var targetNode = internalEdge.target;
+
+				// Root check is O(|roots|)
+				this.extendedDfs(root, targetNode, internalEdge, visitor, seen,
+						root.hashCode, i, layer + 1);
+			}
+		}
+		else
+		{
+			// Use the int field to indicate this node has been seen
+			visitor(parent, root, connectingEdge, layer, 1);
+		}
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/model/mxGraphHierarchyNode.js b/airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/model/mxGraphHierarchyNode.js
new file mode 100644
index 0000000..1dbb8be
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/model/mxGraphHierarchyNode.js
@@ -0,0 +1,220 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxGraphHierarchyNode
+ * 
+ * An abstraction of a hierarchical edge for the hierarchy layout
+ * 
+ * Constructor: mxGraphHierarchyNode
+ *
+ * Constructs an internal node to represent the specified real graph cell
+ *
+ * Arguments:
+ * 
+ * cell - the real graph cell this node represents
+ */
+function mxGraphHierarchyNode(cell)
+{
+	mxGraphAbstractHierarchyCell.apply(this, arguments);
+	this.cell = cell;
+	this.id = mxObjectIdentity.get(cell);
+	this.connectsAsTarget = [];
+	this.connectsAsSource = [];
+};
+
+/**
+ * Extends mxGraphAbstractHierarchyCell.
+ */
+mxGraphHierarchyNode.prototype = new mxGraphAbstractHierarchyCell();
+mxGraphHierarchyNode.prototype.constructor = mxGraphHierarchyNode;
+
+/**
+ * Variable: cell
+ * 
+ * The graph cell this object represents.
+ */
+mxGraphHierarchyNode.prototype.cell = null;
+
+/**
+ * Variable: id
+ * 
+ * The object identity of the wrapped cell
+ */
+mxGraphHierarchyNode.prototype.id = null;
+
+/**
+ * Variable: connectsAsTarget
+ * 
+ * Collection of hierarchy edges that have this node as a target
+ */
+mxGraphHierarchyNode.prototype.connectsAsTarget = null;
+
+/**
+ * Variable: connectsAsSource
+ * 
+ * Collection of hierarchy edges that have this node as a source
+ */
+mxGraphHierarchyNode.prototype.connectsAsSource = null;
+
+/**
+ * Variable: hashCode
+ * 
+ * Assigns a unique hashcode for each node. Used by the model dfs instead
+ * of copying HashSets
+ */
+mxGraphHierarchyNode.prototype.hashCode = false;
+
+/**
+ * Function: getRankValue
+ * 
+ * Returns the integer value of the layer that this node resides in
+ */
+mxGraphHierarchyNode.prototype.getRankValue = function(layer)
+{
+	return this.maxRank;
+};
+
+/**
+ * Function: getNextLayerConnectedCells
+ * 
+ * Returns the cells this cell connects to on the next layer up
+ */
+mxGraphHierarchyNode.prototype.getNextLayerConnectedCells = function(layer)
+{
+	if (this.nextLayerConnectedCells == null)
+	{
+		this.nextLayerConnectedCells = [];
+		this.nextLayerConnectedCells[0] = [];
+		
+		for (var i = 0; i < this.connectsAsTarget.length; i++)
+		{
+			var edge = this.connectsAsTarget[i];
+
+			if (edge.maxRank == -1 || edge.maxRank == layer + 1)
+			{
+				// Either edge is not in any rank or
+				// no dummy nodes in edge, add node of other side of edge
+				this.nextLayerConnectedCells[0].push(edge.source);
+			}
+			else
+			{
+				// Edge spans at least two layers, add edge
+				this.nextLayerConnectedCells[0].push(edge);
+			}
+		}
+	}
+
+	return this.nextLayerConnectedCells[0];
+};
+
+/**
+ * Function: getPreviousLayerConnectedCells
+ * 
+ * Returns the cells this cell connects to on the next layer down
+ */
+mxGraphHierarchyNode.prototype.getPreviousLayerConnectedCells = function(layer)
+{
+	if (this.previousLayerConnectedCells == null)
+	{
+		this.previousLayerConnectedCells = [];
+		this.previousLayerConnectedCells[0] = [];
+		
+		for (var i = 0; i < this.connectsAsSource.length; i++)
+		{
+			var edge = this.connectsAsSource[i];
+
+			if (edge.minRank == -1 || edge.minRank == layer - 1)
+			{
+				// No dummy nodes in edge, add node of other side of edge
+				this.previousLayerConnectedCells[0].push(edge.target);
+			}
+			else
+			{
+				// Edge spans at least two layers, add edge
+				this.previousLayerConnectedCells[0].push(edge);
+			}
+		}
+	}
+
+	return this.previousLayerConnectedCells[0];
+};
+
+/**
+ * Function: isVertex
+ * 
+ * Returns true.
+ */
+mxGraphHierarchyNode.prototype.isVertex = function()
+{
+	return true;
+};
+
+/**
+ * Function: getGeneralPurposeVariable
+ * 
+ * Gets the value of temp for the specified layer
+ */
+mxGraphHierarchyNode.prototype.getGeneralPurposeVariable = function(layer)
+{
+	return this.temp[0];
+};
+
+/**
+ * Function: setGeneralPurposeVariable
+ * 
+ * Set the value of temp for the specified layer
+ */
+mxGraphHierarchyNode.prototype.setGeneralPurposeVariable = function(layer, value)
+{
+	this.temp[0] = value;
+};
+
+/**
+ * Function: isAncestor
+ */
+mxGraphHierarchyNode.prototype.isAncestor = function(otherNode)
+{
+	// Firstly, the hash code of this node needs to be shorter than the
+	// other node
+	if (otherNode != null && this.hashCode != null && otherNode.hashCode != null
+			&& this.hashCode.length < otherNode.hashCode.length)
+	{
+		if (this.hashCode == otherNode.hashCode)
+		{
+			return true;
+		}
+		
+		if (this.hashCode == null || this.hashCode == null)
+		{
+			return false;
+		}
+		
+		// Secondly, this hash code must match the start of the other
+		// node's hash code. Arrays.equals cannot be used here since
+		// the arrays are different length, and we do not want to
+		// perform another array copy.
+		for (var i = 0; i < this.hashCode.length; i++)
+		{
+			if (this.hashCode[i] != otherNode.hashCode[i])
+			{
+				return false;
+			}
+		}
+
+		return true;
+	}
+
+	return false;
+};
+
+/**
+ * Function: getCoreCell
+ * 
+ * Gets the core vertex associated with this wrapper
+ */
+mxGraphHierarchyNode.prototype.getCoreCell = function()
+{
+	return this.cell;
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/model/mxSwimlaneModel.js b/airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/model/mxSwimlaneModel.js
new file mode 100644
index 0000000..52873c6
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/model/mxSwimlaneModel.js
@@ -0,0 +1,801 @@
+/**
+ * Copyright (c) 2006-2016, JGraph Ltd
+ * Copyright (c) 2006-2016, Gaudenz Alder
+ */
+/**
+ * Class: mxSwimlaneModel
+ *
+ * Internal model of a hierarchical graph. This model stores nodes and edges
+ * equivalent to the real graph nodes and edges, but also stores the rank of the
+ * cells, the order within the ranks and the new candidate locations of cells.
+ * The internal model also reverses edge direction were appropriate , ignores
+ * self-loop and groups parallels together under one edge object.
+ *
+ * Constructor: mxSwimlaneModel
+ *
+ * Creates an internal ordered graph model using the vertices passed in. If
+ * there are any, leftward edge need to be inverted in the internal model
+ *
+ * Arguments:
+ *
+ * graph - the facade describing the graph to be operated on
+ * vertices - the vertices for this hierarchy
+ * ordered - whether or not the vertices are already ordered
+ * deterministic - whether or not this layout should be deterministic on each
+ * tightenToSource - whether or not to tighten vertices towards the sources
+ * scanRanksFromSinks - Whether rank assignment is from the sinks or sources.
+ * usage
+ */
+function mxSwimlaneModel(layout, vertices, roots, parent, tightenToSource)
+{
+	var graph = layout.getGraph();
+	this.tightenToSource = tightenToSource;
+	this.roots = roots;
+	this.parent = parent;
+
+	// map of cells to internal cell needed for second run through
+	// to setup the sink of edges correctly
+	this.vertexMapper = new mxDictionary();
+	this.edgeMapper = new mxDictionary();
+	this.maxRank = 0;
+	var internalVertices = [];
+
+	if (vertices == null)
+	{
+		vertices = this.graph.getChildVertices(parent);
+	}
+
+	this.maxRank = this.SOURCESCANSTARTRANK;
+	// map of cells to internal cell needed for second run through
+	// to setup the sink of edges correctly. Guess size by number
+	// of edges is roughly same as number of vertices.
+	this.createInternalCells(layout, vertices, internalVertices);
+
+	// Go through edges set their sink values. Also check the
+	// ordering if and invert edges if necessary
+	for (var i = 0; i < vertices.length; i++)
+	{
+		var edges = internalVertices[i].connectsAsSource;
+
+		for (var j = 0; j < edges.length; j++)
+		{
+			var internalEdge = edges[j];
+			var realEdges = internalEdge.edges;
+
+			// Only need to process the first real edge, since
+			// all the edges connect to the same other vertex
+			if (realEdges != null && realEdges.length > 0)
+			{
+				var realEdge = realEdges[0];
+				var targetCell = layout.getVisibleTerminal(
+						realEdge, false);
+				var internalTargetCell = this.vertexMapper.get(targetCell);
+
+				if (internalVertices[i] == internalTargetCell)
+				{
+					// If there are parallel edges going between two vertices and not all are in the same direction
+					// you can have navigated across one direction when doing the cycle reversal that isn't the same
+					// direction as the first real edge in the array above. When that happens the if above catches
+					// that and we correct the target cell before continuing.
+					// This branch only detects this single case
+					targetCell = layout.getVisibleTerminal(
+							realEdge, true);
+					internalTargetCell = this.vertexMapper.get(targetCell);
+				}
+
+				if (internalTargetCell != null
+						&& internalVertices[i] != internalTargetCell)
+				{
+					internalEdge.target = internalTargetCell;
+
+					if (internalTargetCell.connectsAsTarget.length == 0)
+					{
+						internalTargetCell.connectsAsTarget = [];
+					}
+
+					if (mxUtils.indexOf(internalTargetCell.connectsAsTarget, internalEdge) < 0)
+					{
+						internalTargetCell.connectsAsTarget.push(internalEdge);
+					}
+				}
+			}
+		}
+
+		// Use the temp variable in the internal nodes to mark this
+		// internal vertex as having been visited.
+		internalVertices[i].temp[0] = 1;
+	}
+};
+
+/**
+ * Variable: maxRank
+ *
+ * Stores the largest rank number allocated
+ */
+mxSwimlaneModel.prototype.maxRank = null;
+
+/**
+ * Variable: vertexMapper
+ *
+ * Map from graph vertices to internal model nodes.
+ */
+mxSwimlaneModel.prototype.vertexMapper = null;
+
+/**
+ * Variable: edgeMapper
+ *
+ * Map from graph edges to internal model edges
+ */
+mxSwimlaneModel.prototype.edgeMapper = null;
+
+/**
+ * Variable: ranks
+ *
+ * Mapping from rank number to actual rank
+ */
+mxSwimlaneModel.prototype.ranks = null;
+
+/**
+ * Variable: roots
+ *
+ * Store of roots of this hierarchy model, these are real graph cells, not
+ * internal cells
+ */
+mxSwimlaneModel.prototype.roots = null;
+
+/**
+ * Variable: parent
+ *
+ * The parent cell whose children are being laid out
+ */
+mxSwimlaneModel.prototype.parent = null;
+
+/**
+ * Variable: dfsCount
+ *
+ * Count of the number of times the ancestor dfs has been used.
+ */
+mxSwimlaneModel.prototype.dfsCount = 0;
+
+/**
+ * Variable: SOURCESCANSTARTRANK
+ *
+ * High value to start source layering scan rank value from.
+ */
+mxSwimlaneModel.prototype.SOURCESCANSTARTRANK = 100000000;
+
+/**
+ * Variable: tightenToSource
+ *
+ * Whether or not to tighten the assigned ranks of vertices up towards
+ * the source cells.
+ */
+mxGraphHierarchyModel.prototype.tightenToSource = false;
+
+/**
+ * Variable: ranksPerGroup
+ *
+ * An array of the number of ranks within each swimlane
+ */
+mxSwimlaneModel.prototype.ranksPerGroup = null;
+
+/**
+ * Function: createInternalCells
+ *
+ * Creates all edges in the internal model
+ *
+ * Parameters:
+ *
+ * layout - Reference to the <mxHierarchicalLayout> algorithm.
+ * vertices - Array of <mxCells> that represent the vertices whom are to
+ * have an internal representation created.
+ * internalVertices - The array of <mxGraphHierarchyNodes> to have their
+ * information filled in using the real vertices.
+ */
+mxSwimlaneModel.prototype.createInternalCells = function(layout, vertices, internalVertices)
+{
+	var graph = layout.getGraph();
+	var swimlanes = layout.swimlanes;
+
+	// Create internal edges
+	for (var i = 0; i < vertices.length; i++)
+	{
+		internalVertices[i] = new mxGraphHierarchyNode(vertices[i]);
+		this.vertexMapper.put(vertices[i], internalVertices[i]);
+		internalVertices[i].swimlaneIndex = -1;
+
+		for (var ii = 0; ii < swimlanes.length; ii++)
+		{
+			if (graph.model.getParent(vertices[i]) == swimlanes[ii])
+			{
+				internalVertices[i].swimlaneIndex = ii;
+				break;
+			}
+		}
+
+		// If the layout is deterministic, order the cells
+		//List outgoingCells = graph.getNeighbours(vertices[i], deterministic);
+		var conns = layout.getEdges(vertices[i]);
+		internalVertices[i].connectsAsSource = [];
+
+		// Create internal edges, but don't do any rank assignment yet
+		// First use the information from the greedy cycle remover to
+		// invert the leftward edges internally
+		for (var j = 0; j < conns.length; j++)
+		{
+			var cell = layout.getVisibleTerminal(conns[j], false);
+
+			// Looking for outgoing edges only
+			if (cell != vertices[i] && layout.graph.model.isVertex(cell) &&
+					!layout.isVertexIgnored(cell))
+			{
+				// We process all edge between this source and its targets
+				// If there are edges going both ways, we need to collect
+				// them all into one internal edges to avoid looping problems
+				// later. We assume this direction (source -> target) is the 
+				// natural direction if at least half the edges are going in
+				// that direction.
+
+				// The check below for edges[0] being in the vertex mapper is
+				// in case we've processed this the other way around
+				// (target -> source) and the number of edges in each direction
+				// are the same. All the graph edges will have been assigned to
+				// an internal edge going the other way, so we don't want to 
+				// process them again
+				var undirectedEdges = layout.getEdgesBetween(vertices[i],
+						cell, false);
+				var directedEdges = layout.getEdgesBetween(vertices[i],
+						cell, true);
+				
+				if (undirectedEdges != null &&
+						undirectedEdges.length > 0 &&
+						this.edgeMapper.get(undirectedEdges[0]) == null &&
+						directedEdges.length * 2 >= undirectedEdges.length)
+				{
+					var internalEdge = new mxGraphHierarchyEdge(undirectedEdges);
+
+					for (var k = 0; k < undirectedEdges.length; k++)
+					{
+						var edge = undirectedEdges[k];
+						this.edgeMapper.put(edge, internalEdge);
+
+						// Resets all point on the edge and disables the edge style
+						// without deleting it from the cell style
+						graph.resetEdge(edge);
+
+					    if (layout.disableEdgeStyle)
+					    {
+					    	layout.setEdgeStyleEnabled(edge, false);
+					    	layout.setOrthogonalEdge(edge,true);
+					    }
+					}
+
+					internalEdge.source = internalVertices[i];
+
+					if (mxUtils.indexOf(internalVertices[i].connectsAsSource, internalEdge) < 0)
+					{
+						internalVertices[i].connectsAsSource.push(internalEdge);
+					}
+				}
+			}
+		}
+
+		// Ensure temp variable is cleared from any previous use
+		internalVertices[i].temp[0] = 0;
+	}
+};
+
+/**
+ * Function: initialRank
+ *
+ * Basic determination of minimum layer ranking by working from from sources
+ * or sinks and working through each node in the relevant edge direction.
+ * Starting at the sinks is basically a longest path layering algorithm.
+*/
+mxSwimlaneModel.prototype.initialRank = function()
+{
+	this.ranksPerGroup = [];
+	
+	var startNodes = [];
+	var seen = new Object();
+
+	if (this.roots != null)
+	{
+		for (var i = 0; i < this.roots.length; i++)
+		{
+			var internalNode = this.vertexMapper.get(this.roots[i]);
+			this.maxChainDfs(null, internalNode, null, seen, 0);
+
+			if (internalNode != null)
+			{
+				startNodes.push(internalNode);
+			}
+		}
+	}
+
+	// Calculate the lower and upper rank bounds of each swimlane
+	var lowerRank = [];
+	var upperRank = [];
+	
+	for (var i = this.ranksPerGroup.length - 1; i >= 0; i--)
+	{
+		if (i == this.ranksPerGroup.length - 1)
+		{
+			lowerRank[i] = 0;
+		}
+		else
+		{
+			lowerRank[i] = upperRank[i+1] + 1;
+		}
+		
+		upperRank[i] = lowerRank[i] + this.ranksPerGroup[i];
+	}
+	
+	this.maxRank = upperRank[0];
+
+	var internalNodes = this.vertexMapper.getValues();
+	
+	for (var i=0; i < internalNodes.length; i++)
+	{
+		// Mark the node as not having had a layer assigned
+		internalNodes[i].temp[0] = -1;
+	}
+
+	var startNodesCopy = startNodes.slice();
+	
+	while (startNodes.length > 0)
+	{
+		var internalNode = startNodes[0];
+		var layerDeterminingEdges;
+		var edgesToBeMarked;
+
+		layerDeterminingEdges = internalNode.connectsAsTarget;
+		edgesToBeMarked = internalNode.connectsAsSource;
+
+		// flag to keep track of whether or not all layer determining
+		// edges have been scanned
+		var allEdgesScanned = true;
+
+		// Work out the layer of this node from the layer determining
+		// edges. The minimum layer number of any node connected by one of
+		// the layer determining edges variable
+		var minimumLayer = upperRank[0];
+
+		for (var i = 0; i < layerDeterminingEdges.length; i++)
+		{
+			var internalEdge = layerDeterminingEdges[i];
+
+			if (internalEdge.temp[0] == 5270620)
+			{
+				// This edge has been scanned, get the layer of the
+				// node on the other end
+				var otherNode = internalEdge.source;
+				minimumLayer = Math.min(minimumLayer, otherNode.temp[0] - 1);
+			}
+			else
+			{
+				allEdgesScanned = false;
+
+				break;
+			}
+		}
+
+		// If all edge have been scanned, assign the layer, mark all
+		// edges in the other direction and remove from the nodes list
+		if (allEdgesScanned)
+		{
+			if (minimumLayer > upperRank[internalNode.swimlaneIndex])
+			{
+				minimumLayer = upperRank[internalNode.swimlaneIndex];
+			}
+
+			internalNode.temp[0] = minimumLayer;
+
+			if (edgesToBeMarked != null)
+			{
+				for (var i = 0; i < edgesToBeMarked.length; i++)
+				{
+					var internalEdge = edgesToBeMarked[i];
+
+					// Assign unique stamp ( y/m/d/h )
+					internalEdge.temp[0] = 5270620;
+
+					// Add node on other end of edge to LinkedList of
+					// nodes to be analysed
+					var otherNode = internalEdge.target;
+
+					// Only add node if it hasn't been assigned a layer
+					if (otherNode.temp[0] == -1)
+					{
+						startNodes.push(otherNode);
+
+						// Mark this other node as neither being
+						// unassigned nor assigned so it isn't
+						// added to this list again, but it's
+						// layer isn't used in any calculation.
+						otherNode.temp[0] = -2;
+					}
+				}
+			}
+
+			startNodes.shift();
+		}
+		else
+		{
+			// Not all the edges have been scanned, get to the back of
+			// the class and put the dunces cap on
+			var removedCell = startNodes.shift();
+			startNodes.push(internalNode);
+
+			if (removedCell == internalNode && startNodes.length == 1)
+			{
+				// This is an error condition, we can't get out of
+				// this loop. It could happen for more than one node
+				// but that's a lot harder to detect. Log the error
+				// TODO make log comment
+				break;
+			}
+		}
+	}
+
+	// Normalize the ranks down from their large starting value to place
+	// at least 1 sink on layer 0
+//	for (var key in this.vertexMapper)
+//	{
+//		var internalNode = this.vertexMapper[key];
+//		// Mark the node as not having had a layer assigned
+//		internalNode.temp[0] -= this.maxRank;
+//	}
+	
+	// Tighten the rank 0 nodes as far as possible
+//	for ( var i = 0; i < startNodesCopy.length; i++)
+//	{
+//		var internalNode = startNodesCopy[i];
+//		var currentMaxLayer = 0;
+//		var layerDeterminingEdges = internalNode.connectsAsSource;
+//
+//		for ( var j = 0; j < layerDeterminingEdges.length; j++)
+//		{
+//			var internalEdge = layerDeterminingEdges[j];
+//			var otherNode = internalEdge.target;
+//			internalNode.temp[0] = Math.max(currentMaxLayer,
+//					otherNode.temp[0] + 1);
+//			currentMaxLayer = internalNode.temp[0];
+//		}
+//	}
+};
+
+/**
+ * Function: maxChainDfs
+ *
+ * Performs a depth first search on the internal hierarchy model. This dfs
+ * extends the default version by keeping track of chains within groups.
+ * Any cycles should be removed prior to running, but previously seen cells
+ * are ignored.
+ *
+ * Parameters:
+ *
+ * parent - the parent internal node of the current internal node
+ * root - the current internal node
+ * connectingEdge - the internal edge connecting the internal node and the parent
+ * internal node, if any
+ * seen - a set of all nodes seen by this dfs
+ * chainCount - the number of edges in the chain of vertices going through
+ * the current swimlane
+ */
+mxSwimlaneModel.prototype.maxChainDfs = function(parent, root, connectingEdge, seen, chainCount)
+{
+	if (root != null)
+	{
+		var rootId = mxCellPath.create(root.cell);
+
+		if (seen[rootId] == null)
+		{
+			seen[rootId] = root;
+			var slIndex = root.swimlaneIndex;
+			
+			if (this.ranksPerGroup[slIndex] == null || this.ranksPerGroup[slIndex] < chainCount)
+			{
+				this.ranksPerGroup[slIndex] = chainCount;
+			}
+
+			// Copy the connects as source list so that visitors
+			// can change the original for edge direction inversions
+			var outgoingEdges = root.connectsAsSource.slice();
+
+			for (var i = 0; i < outgoingEdges.length; i++)
+			{
+				var internalEdge = outgoingEdges[i];
+				var targetNode = internalEdge.target;
+
+				// Only navigate in source->target direction within the same
+				// swimlane, or from a lower index swimlane to a higher one
+				if (root.swimlaneIndex < targetNode.swimlaneIndex)
+				{
+					this.maxChainDfs(root, targetNode, internalEdge, mxUtils.clone(seen, null , true), 0);
+				}
+				else if (root.swimlaneIndex == targetNode.swimlaneIndex)
+				{
+					this.maxChainDfs(root, targetNode, internalEdge, mxUtils.clone(seen, null , true), chainCount + 1);
+				}
+			}
+		}
+	}
+};
+
+/**
+ * Function: fixRanks
+ *
+ * Fixes the layer assignments to the values stored in the nodes. Also needs
+ * to create dummy nodes for edges that cross layers.
+ */
+mxSwimlaneModel.prototype.fixRanks = function()
+{
+	var rankList = [];
+	this.ranks = [];
+
+	for (var i = 0; i < this.maxRank + 1; i++)
+	{
+		rankList[i] = [];
+		this.ranks[i] = rankList[i];
+	}
+
+	// Perform a DFS to obtain an initial ordering for each rank.
+	// Without doing this you would end up having to process
+	// crossings for a standard tree.
+	var rootsArray = null;
+
+	if (this.roots != null)
+	{
+		var oldRootsArray = this.roots;
+		rootsArray = [];
+
+		for (var i = 0; i < oldRootsArray.length; i++)
+		{
+			var cell = oldRootsArray[i];
+			var internalNode = this.vertexMapper.get(cell);
+			rootsArray[i] = internalNode;
+		}
+	}
+
+	this.visit(function(parent, node, edge, layer, seen)
+	{
+		if (seen == 0 && node.maxRank < 0 && node.minRank < 0)
+		{
+			rankList[node.temp[0]].push(node);
+			node.maxRank = node.temp[0];
+			node.minRank = node.temp[0];
+
+			// Set temp[0] to the nodes position in the rank
+			node.temp[0] = rankList[node.maxRank].length - 1;
+		}
+
+		if (parent != null && edge != null)
+		{
+			var parentToCellRankDifference = parent.maxRank - node.maxRank;
+
+			if (parentToCellRankDifference > 1)
+			{
+				// There are ranks in between the parent and current cell
+				edge.maxRank = parent.maxRank;
+				edge.minRank = node.maxRank;
+				edge.temp = [];
+				edge.x = [];
+				edge.y = [];
+
+				for (var i = edge.minRank + 1; i < edge.maxRank; i++)
+				{
+					// The connecting edge must be added to the
+					// appropriate ranks
+					rankList[i].push(edge);
+					edge.setGeneralPurposeVariable(i, rankList[i]
+							.length - 1);
+				}
+			}
+		}
+	}, rootsArray, false, null);
+};
+
+/**
+ * Function: visit
+ *
+ * A depth first search through the internal heirarchy model.
+ *
+ * Parameters:
+ *
+ * visitor - The visitor function pattern to be called for each node.
+ * trackAncestors - Whether or not the search is to keep track all nodes
+ * directly above this one in the search path.
+ */
+mxSwimlaneModel.prototype.visit = function(visitor, dfsRoots, trackAncestors, seenNodes)
+{
+	// Run dfs through on all roots
+	if (dfsRoots != null)
+	{
+		for (var i = 0; i < dfsRoots.length; i++)
+		{
+			var internalNode = dfsRoots[i];
+
+			if (internalNode != null)
+			{
+				if (seenNodes == null)
+				{
+					seenNodes = new Object();
+				}
+
+				if (trackAncestors)
+				{
+					// Set up hash code for root
+					internalNode.hashCode = [];
+					internalNode.hashCode[0] = this.dfsCount;
+					internalNode.hashCode[1] = i;
+					this.extendedDfs(null, internalNode, null, visitor, seenNodes,
+							internalNode.hashCode, i, 0);
+				}
+				else
+				{
+					this.dfs(null, internalNode, null, visitor, seenNodes, 0);
+				}
+			}
+		}
+
+		this.dfsCount++;
+	}
+};
+
+/**
+ * Function: dfs
+ *
+ * Performs a depth first search on the internal hierarchy model
+ *
+ * Parameters:
+ *
+ * parent - the parent internal node of the current internal node
+ * root - the current internal node
+ * connectingEdge - the internal edge connecting the internal node and the parent
+ * internal node, if any
+ * visitor - the visitor pattern to be called for each node
+ * seen - a set of all nodes seen by this dfs a set of all of the
+ * ancestor node of the current node
+ * layer - the layer on the dfs tree ( not the same as the model ranks )
+ */
+mxSwimlaneModel.prototype.dfs = function(parent, root, connectingEdge, visitor, seen, layer)
+{
+	if (root != null)
+	{
+		var rootId = root.id;
+
+		if (seen[rootId] == null)
+		{
+			seen[rootId] = root;
+			visitor(parent, root, connectingEdge, layer, 0);
+
+			// Copy the connects as source list so that visitors
+			// can change the original for edge direction inversions
+			var outgoingEdges = root.connectsAsSource.slice();
+			
+			for (var i = 0; i< outgoingEdges.length; i++)
+			{
+				var internalEdge = outgoingEdges[i];
+				var targetNode = internalEdge.target;
+
+				// Root check is O(|roots|)
+				this.dfs(root, targetNode, internalEdge, visitor, seen,
+						layer + 1);
+			}
+		}
+		else
+		{
+			// Use the int field to indicate this node has been seen
+			visitor(parent, root, connectingEdge, layer, 1);
+		}
+	}
+};
+
+/**
+ * Function: extendedDfs
+ *
+ * Performs a depth first search on the internal hierarchy model. This dfs
+ * extends the default version by keeping track of cells ancestors, but it
+ * should be only used when necessary because of it can be computationally
+ * intensive for deep searches.
+ *
+ * Parameters:
+ *
+ * parent - the parent internal node of the current internal node
+ * root - the current internal node
+ * connectingEdge - the internal edge connecting the internal node and the parent
+ * internal node, if any
+ * visitor - the visitor pattern to be called for each node
+ * seen - a set of all nodes seen by this dfs
+ * ancestors - the parent hash code
+ * childHash - the new hash code for this node
+ * layer - the layer on the dfs tree ( not the same as the model ranks )
+ */
+mxSwimlaneModel.prototype.extendedDfs = function(parent, root, connectingEdge, visitor, seen, ancestors, childHash, layer)
+{
+	// Explanation of custom hash set. Previously, the ancestors variable
+	// was passed through the dfs as a HashSet. The ancestors were copied
+	// into a new HashSet and when the new child was processed it was also
+	// added to the set. If the current node was in its ancestor list it
+	// meant there is a cycle in the graph and this information is passed
+	// to the visitor.visit() in the seen parameter. The HashSet clone was
+	// very expensive on CPU so a custom hash was developed using primitive
+	// types. temp[] couldn't be used so hashCode[] was added to each node.
+	// Each new child adds another int to the array, copying the prefix
+	// from its parent. Child of the same parent add different ints (the
+	// limit is therefore 2^32 children per parent...). If a node has a
+	// child with the hashCode already set then the child code is compared
+	// to the same portion of the current nodes array. If they match there
+	// is a loop.
+	// Note that the basic mechanism would only allow for 1 use of this
+	// functionality, so the root nodes have two ints. The second int is
+	// incremented through each node root and the first is incremented
+	// through each run of the dfs algorithm (therefore the dfs is not
+	// thread safe). The hash code of each node is set if not already set,
+	// or if the first int does not match that of the current run.
+	if (root != null)
+	{
+		if (parent != null)
+		{
+			// Form this nodes hash code if necessary, that is, if the
+			// hashCode variable has not been initialized or if the
+			// start of the parent hash code does not equal the start of
+			// this nodes hash code, indicating the code was set on a
+			// previous run of this dfs.
+			if (root.hashCode == null ||
+				root.hashCode[0] != parent.hashCode[0])
+			{
+				var hashCodeLength = parent.hashCode.length + 1;
+				root.hashCode = parent.hashCode.slice();
+				root.hashCode[hashCodeLength - 1] = childHash;
+			}
+		}
+
+		var rootId = root.id;
+
+		if (seen[rootId] == null)
+		{
+			seen[rootId] = root;
+			visitor(parent, root, connectingEdge, layer, 0);
+
+			// Copy the connects as source list so that visitors
+			// can change the original for edge direction inversions
+			var outgoingEdges = root.connectsAsSource.slice();
+			var incomingEdges = root.connectsAsTarget.slice();
+
+			for (var i = 0; i < outgoingEdges.length; i++)
+			{
+				var internalEdge = outgoingEdges[i];
+				var targetNode = internalEdge.target;
+				
+				// Only navigate in source->target direction within the same
+				// swimlane, or from a lower index swimlane to a higher one
+				if (root.swimlaneIndex <= targetNode.swimlaneIndex)
+				{
+					this.extendedDfs(root, targetNode, internalEdge, visitor, seen,
+							root.hashCode, i, layer + 1);
+				}
+			}
+			
+			for (var i = 0; i < incomingEdges.length; i++)
+			{
+				var internalEdge = incomingEdges[i];
+				var targetNode = internalEdge.source;
+
+				// Only navigate in target->source direction from a lower index 
+				// swimlane to a higher one
+				if (root.swimlaneIndex < targetNode.swimlaneIndex)
+				{
+					this.extendedDfs(root, targetNode, internalEdge, visitor, seen,
+							root.hashCode, i, layer + 1);
+				}
+			}
+		}
+		else
+		{
+			// Use the int field to indicate this node has been seen
+			visitor(parent, root, connectingEdge, layer, 1);
+		}
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/mxHierarchicalLayout.js b/airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/mxHierarchicalLayout.js
new file mode 100644
index 0000000..deb60b5
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/mxHierarchicalLayout.js
@@ -0,0 +1,846 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxHierarchicalLayout
+ * 
+ * A hierarchical layout algorithm.
+ * 
+ * Constructor: mxHierarchicalLayout
+ *
+ * Constructs a new hierarchical layout algorithm.
+ *
+ * Arguments:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ * orientation - Optional constant that defines the orientation of this
+ * layout.
+ * deterministic - Optional boolean that specifies if this layout should be
+ * deterministic. Default is true.
+ */
+function mxHierarchicalLayout(graph, orientation, deterministic)
+{
+	mxGraphLayout.call(this, graph);
+	this.orientation = (orientation != null) ? orientation : mxConstants.DIRECTION_NORTH;
+	this.deterministic = (deterministic != null) ? deterministic : true;
+};
+
+var mxHierarchicalEdgeStyle =
+{
+	ORTHOGONAL: 1,
+	POLYLINE: 2,
+	STRAIGHT: 3,
+	CURVE: 4
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxHierarchicalLayout.prototype = new mxGraphLayout();
+mxHierarchicalLayout.prototype.constructor = mxHierarchicalLayout;
+
+/**
+ * Variable: roots
+ * 
+ * Holds the array of <mxCell> that this layout contains.
+ */
+mxHierarchicalLayout.prototype.roots = null;
+
+/**
+ * Variable: resizeParent
+ * 
+ * Specifies if the parent should be resized after the layout so that it
+ * contains all the child cells. Default is false. See also <parentBorder>.
+ */
+mxHierarchicalLayout.prototype.resizeParent = false;
+
+/**
+ * Variable: maintainParentLocation
+ * 
+ * Specifies if the parent location should be maintained, so that the
+ * top, left corner stays the same before and after execution of
+ * the layout. Default is false for backwards compatibility.
+ */
+mxHierarchicalLayout.prototype.maintainParentLocation = false;
+
+/**
+ * Variable: moveParent
+ * 
+ * Specifies if the parent should be moved if <resizeParent> is enabled.
+ * Default is false.
+ */
+mxHierarchicalLayout.prototype.moveParent = false;
+
+/**
+ * Variable: parentBorder
+ * 
+ * The border to be added around the children if the parent is to be
+ * resized using <resizeParent>. Default is 0.
+ */
+mxHierarchicalLayout.prototype.parentBorder = 0;
+
+/**
+ * Variable: intraCellSpacing
+ * 
+ * The spacing buffer added between cells on the same layer. Default is 30.
+ */
+mxHierarchicalLayout.prototype.intraCellSpacing = 30;
+
+/**
+ * Variable: interRankCellSpacing
+ * 
+ * The spacing buffer added between cell on adjacent layers. Default is 50.
+ */
+mxHierarchicalLayout.prototype.interRankCellSpacing = 100;
+
+/**
+ * Variable: interHierarchySpacing
+ * 
+ * The spacing buffer between unconnected hierarchies. Default is 60.
+ */
+mxHierarchicalLayout.prototype.interHierarchySpacing = 60;
+
+/**
+ * Variable: parallelEdgeSpacing
+ * 
+ * The distance between each parallel edge on each ranks for long edges
+ */
+mxHierarchicalLayout.prototype.parallelEdgeSpacing = 10;
+
+/**
+ * Variable: orientation
+ * 
+ * The position of the root node(s) relative to the laid out graph in.
+ * Default is <mxConstants.DIRECTION_NORTH>.
+ */
+mxHierarchicalLayout.prototype.orientation = mxConstants.DIRECTION_NORTH;
+
+/**
+ * Variable: fineTuning
+ * 
+ * Whether or not to perform local optimisations and iterate multiple times
+ * through the algorithm. Default is true.
+ */
+mxHierarchicalLayout.prototype.fineTuning = true;
+
+/**
+ * 
+ * Variable: tightenToSource
+ * 
+ * Whether or not to tighten the assigned ranks of vertices up towards
+ * the source cells.
+ */
+mxHierarchicalLayout.prototype.tightenToSource = true;
+
+/**
+ * Variable: disableEdgeStyle
+ * 
+ * Specifies if the STYLE_NOEDGESTYLE flag should be set on edges that are
+ * modified by the result. Default is true.
+ */
+mxHierarchicalLayout.prototype.disableEdgeStyle = true;
+
+/**
+ * Variable: traverseAncestors
+ * 
+ * Whether or not to drill into child cells and layout in reverse
+ * group order. This also cause the layout to navigate edges whose 
+ * terminal vertices  * have different parents but are in the same 
+ * ancestry chain
+ */
+mxHierarchicalLayout.prototype.traverseAncestors = true;
+
+/**
+ * Variable: model
+ * 
+ * The internal <mxGraphHierarchyModel> formed of the layout.
+ */
+mxHierarchicalLayout.prototype.model = null;
+
+/**
+ * Variable: edgesSet
+ * 
+ * A cache of edges whose source terminal is the key
+ */
+mxHierarchicalLayout.prototype.edgesCache = null;
+
+/**
+ * Variable: edgesSet
+ * 
+ * A cache of edges whose source terminal is the key
+ */
+mxHierarchicalLayout.prototype.edgeSourceTermCache = null;
+
+/**
+ * Variable: edgesSet
+ * 
+ * A cache of edges whose source terminal is the key
+ */
+mxHierarchicalLayout.prototype.edgesTargetTermCache = null;
+
+/**
+ * Variable: edgeStyle
+ * 
+ * The style to apply between cell layers to edge segments
+ */
+mxHierarchicalLayout.prototype.edgeStyle = mxHierarchicalEdgeStyle.POLYLINE;
+
+/**
+ * Function: getModel
+ * 
+ * Returns the internal <mxGraphHierarchyModel> for this layout algorithm.
+ */
+mxHierarchicalLayout.prototype.getModel = function()
+{
+	return this.model;
+};
+
+/**
+ * Function: execute
+ * 
+ * Executes the layout for the children of the specified parent.
+ * 
+ * Parameters:
+ * 
+ * parent - Parent <mxCell> that contains the children to be laid out.
+ * roots - Optional starting roots of the layout.
+ */
+mxHierarchicalLayout.prototype.execute = function(parent, roots)
+{
+	this.parent = parent;
+	var model = this.graph.model;
+	this.edgesCache = new mxDictionary();
+	this.edgeSourceTermCache = new mxDictionary();
+	this.edgesTargetTermCache = new mxDictionary();
+
+	if (roots != null && !(roots instanceof Array))
+	{
+		roots = [roots];
+	}
+	
+	// If the roots are set and the parent is set, only
+	// use the roots that are some dependent of the that
+	// parent.
+	// If just the root are set, use them as-is
+	// If just the parent is set use it's immediate
+	// children as the initial set
+
+	if (roots == null && parent == null)
+	{
+		// TODO indicate the problem
+		return;
+	}
+	
+	//  Maintaining parent location
+	this.parentX = null;
+	this.parentY = null;
+	
+	if (parent != this.root && model.isVertex(parent) != null && this.maintainParentLocation)
+	{
+		var geo = this.graph.getCellGeometry(parent);
+		
+		if (geo != null)
+		{
+			this.parentX = geo.x;
+			this.parentY = geo.y;
+		}
+	}
+	
+	if (roots != null)
+	{
+		var rootsCopy = [];
+
+		for (var i = 0; i < roots.length; i++)
+		{
+			var ancestor = parent != null ? model.isAncestor(parent, roots[i]) : true;
+			
+			if (ancestor && model.isVertex(roots[i]))
+			{
+				rootsCopy.push(roots[i]);
+			}
+		}
+
+		this.roots = rootsCopy;
+	}
+	
+	model.beginUpdate();
+	try
+	{
+		this.run(parent);
+		
+		if (this.resizeParent && !this.graph.isCellCollapsed(parent))
+		{
+			this.graph.updateGroupBounds([parent], this.parentBorder, this.moveParent);
+		}
+		
+		// Maintaining parent location
+		if (this.parentX != null && this.parentY != null)
+		{
+			var geo = this.graph.getCellGeometry(parent);
+			
+			if (geo != null)
+			{
+				geo = geo.clone();
+				geo.x = this.parentX;
+				geo.y = this.parentY;
+				model.setGeometry(parent, geo);
+			}
+		}
+	}
+	finally
+	{
+		model.endUpdate();
+	}
+};
+
+/**
+ * Function: findRoots
+ * 
+ * Returns all visible children in the given parent which do not have
+ * incoming edges. If the result is empty then the children with the
+ * maximum difference between incoming and outgoing edges are returned.
+ * This takes into account edges that are being promoted to the given
+ * root due to invisible children or collapsed cells.
+ * 
+ * Parameters:
+ * 
+ * parent - <mxCell> whose children should be checked.
+ * vertices - array of vertices to limit search to
+ */
+mxHierarchicalLayout.prototype.findRoots = function(parent, vertices)
+{
+	var roots = [];
+	
+	if (parent != null && vertices != null)
+	{
+		var model = this.graph.model;
+		var best = null;
+		var maxDiff = -100000;
+		
+		for (var i in vertices)
+		{
+			var cell = vertices[i];
+
+			if (model.isVertex(cell) && this.graph.isCellVisible(cell))
+			{
+				var conns = this.getEdges(cell);
+				var fanOut = 0;
+				var fanIn = 0;
+
+				for (var k = 0; k < conns.length; k++)
+				{
+					var src = this.getVisibleTerminal(conns[k], true);
+
+					if (src == cell)
+					{
+						fanOut++;
+					}
+					else
+					{
+						fanIn++;
+					}
+				}
+
+				if (fanIn == 0 && fanOut > 0)
+				{
+					roots.push(cell);
+				}
+
+				var diff = fanOut - fanIn;
+
+				if (diff > maxDiff)
+				{
+					maxDiff = diff;
+					best = cell;
+				}
+			}
+		}
+		
+		if (roots.length == 0 && best != null)
+		{
+			roots.push(best);
+		}
+	}
+	
+	return roots;
+};
+
+/**
+ * Function: getEdges
+ * 
+ * Returns the connected edges for the given cell.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose edges should be returned.
+ */
+mxHierarchicalLayout.prototype.getEdges = function(cell)
+{
+	var cachedEdges = this.edgesCache.get(cell);
+	
+	if (cachedEdges != null)
+	{
+		return cachedEdges;
+	}
+
+	var model = this.graph.model;
+	var edges = [];
+	var isCollapsed = this.graph.isCellCollapsed(cell);
+	var childCount = model.getChildCount(cell);
+
+	for (var i = 0; i < childCount; i++)
+	{
+		var child = model.getChildAt(cell, i);
+
+		if (this.isPort(child))
+		{
+			edges = edges.concat(model.getEdges(child, true, true));
+		}
+		else if (isCollapsed || !this.graph.isCellVisible(child))
+		{
+			edges = edges.concat(model.getEdges(child, true, true));
+		}
+	}
+
+	edges = edges.concat(model.getEdges(cell, true, true));
+	var result = [];
+	
+	for (var i = 0; i < edges.length; i++)
+	{
+		var source = this.getVisibleTerminal(edges[i], true);
+		var target = this.getVisibleTerminal(edges[i], false);
+		
+		if ((source == target) || ((source != target) && ((target == cell && (this.parent == null || this.graph.isValidAncestor(source, this.parent, this.traverseAncestors))) ||
+			(source == cell && (this.parent == null ||
+					this.graph.isValidAncestor(target, this.parent, this.traverseAncestors))))))
+		{
+			result.push(edges[i]);
+		}
+	}
+
+	this.edgesCache.put(cell, result);
+
+	return result;
+};
+
+/**
+ * Function: getVisibleTerminal
+ * 
+ * Helper function to return visible terminal for edge allowing for ports
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> whose edges should be returned.
+ * source - Boolean that specifies whether the source or target terminal is to be returned
+ */
+mxHierarchicalLayout.prototype.getVisibleTerminal = function(edge, source)
+{
+	var terminalCache = this.edgesTargetTermCache;
+	
+	if (source)
+	{
+		terminalCache = this.edgeSourceTermCache;
+	}
+
+	var term = terminalCache.get(edge);
+
+	if (term != null)
+	{
+		return term;
+	}
+
+	var state = this.graph.view.getState(edge);
+	
+	var terminal = (state != null) ? state.getVisibleTerminal(source) : this.graph.view.getVisibleTerminal(edge, source);
+	
+	if (terminal == null)
+	{
+		terminal = (state != null) ? state.getVisibleTerminal(source) : this.graph.view.getVisibleTerminal(edge, source);
+	}
+
+	if (terminal != null)
+	{
+		if (this.isPort(terminal))
+		{
+			terminal = this.graph.model.getParent(terminal);
+		}
+		
+		terminalCache.put(edge, terminal);
+	}
+
+	return terminal;
+};
+
+/**
+ * Function: run
+ * 
+ * The API method used to exercise the layout upon the graph description
+ * and produce a separate description of the vertex position and edge
+ * routing changes made. It runs each stage of the layout that has been
+ * created.
+ */
+mxHierarchicalLayout.prototype.run = function(parent)
+{
+	// Separate out unconnected hierarchies
+	var hierarchyVertices = [];
+	var allVertexSet = [];
+
+	if (this.roots == null && parent != null)
+	{
+		var filledVertexSet = Object();
+		this.filterDescendants(parent, filledVertexSet);
+
+		this.roots = [];
+		var filledVertexSetEmpty = true;
+
+		// Poor man's isSetEmpty
+		for (var key in filledVertexSet)
+		{
+			if (filledVertexSet[key] != null)
+			{
+				filledVertexSetEmpty = false;
+				break;
+			}
+		}
+
+		while (!filledVertexSetEmpty)
+		{
+			var candidateRoots = this.findRoots(parent, filledVertexSet);
+			
+			// If the candidate root is an unconnected group cell, remove it from
+			// the layout. We may need a custom set that holds such groups and forces
+			// them to be processed for resizing and/or moving.
+			
+
+			for (var i = 0; i < candidateRoots.length; i++)
+			{
+				var vertexSet = Object();
+				hierarchyVertices.push(vertexSet);
+
+				this.traverse(candidateRoots[i], true, null, allVertexSet, vertexSet,
+						hierarchyVertices, filledVertexSet);
+			}
+
+			for (var i = 0; i < candidateRoots.length; i++)
+			{
+				this.roots.push(candidateRoots[i]);
+			}
+			
+			filledVertexSetEmpty = true;
+			
+			// Poor man's isSetEmpty
+			for (var key in filledVertexSet)
+			{
+				if (filledVertexSet[key] != null)
+				{
+					filledVertexSetEmpty = false;
+					break;
+				}
+			}
+		}
+	}
+	else
+	{
+		// Find vertex set as directed traversal from roots
+
+		for (var i = 0; i < this.roots.length; i++)
+		{
+			var vertexSet = Object();
+			hierarchyVertices.push(vertexSet);
+
+			this.traverse(this.roots[i], true, null, allVertexSet, vertexSet,
+					hierarchyVertices, null);
+		}
+	}
+
+	// Iterate through the result removing parents who have children in this layout
+	
+	// Perform a layout for each seperate hierarchy
+	// Track initial coordinate x-positioning
+	var initialX = 0;
+
+	for (var i = 0; i < hierarchyVertices.length; i++)
+	{
+		var vertexSet = hierarchyVertices[i];
+		var tmp = [];
+		
+		for (var key in vertexSet)
+		{
+			tmp.push(vertexSet[key]);
+		}
+		
+		this.model = new mxGraphHierarchyModel(this, tmp, this.roots,
+			parent, this.tightenToSource);
+
+		this.cycleStage(parent);
+		this.layeringStage();
+		
+		this.crossingStage(parent);
+		initialX = this.placementStage(initialX, parent);
+	}
+};
+
+/**
+ * Function: filterDescendants
+ * 
+ * Creates an array of descendant cells
+ */
+mxHierarchicalLayout.prototype.filterDescendants = function(cell, result)
+{
+	var model = this.graph.model;
+
+	if (model.isVertex(cell) && cell != this.parent && this.graph.isCellVisible(cell))
+	{
+		result[mxObjectIdentity.get(cell)] = cell;
+	}
+
+	if (this.traverseAncestors || cell == this.parent
+			&& this.graph.isCellVisible(cell))
+	{
+		var childCount = model.getChildCount(cell);
+
+		for (var i = 0; i < childCount; i++)
+		{
+			var child = model.getChildAt(cell, i);
+			
+			// Ignore ports in the layout vertex list, they are dealt with
+			// in the traversal mechanisms
+			if (!this.isPort(child))
+			{
+				this.filterDescendants(child, result);
+			}
+		}
+	}
+};
+
+/**
+ * Function: isPort
+ * 
+ * Returns true if the given cell is a "port", that is, when connecting to
+ * it, its parent is the connecting vertex in terms of graph traversal
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the port.
+ */
+mxHierarchicalLayout.prototype.isPort = function(cell)
+{
+	if (cell.geometry.relative)
+	{
+		return true;
+	}
+	
+	return false;
+};
+
+/**
+ * Function: getEdgesBetween
+ * 
+ * Returns the edges between the given source and target. This takes into
+ * account collapsed and invisible cells and ports.
+ * 
+ * Parameters:
+ * 
+ * source -
+ * target -
+ * directed -
+ */
+mxHierarchicalLayout.prototype.getEdgesBetween = function(source, target, directed)
+{
+	directed = (directed != null) ? directed : false;
+	var edges = this.getEdges(source);
+	var result = [];
+
+	// Checks if the edge is connected to the correct
+	// cell and returns the first match
+	for (var i = 0; i < edges.length; i++)
+	{
+		var src = this.getVisibleTerminal(edges[i], true);
+		var trg = this.getVisibleTerminal(edges[i], false);
+
+		if ((src == source && trg == target) || (!directed && src == target && trg == source))
+		{
+			result.push(edges[i]);
+		}
+	}
+
+	return result;
+};
+
+/**
+ * Traverses the (directed) graph invoking the given function for each
+ * visited vertex and edge. The function is invoked with the current vertex
+ * and the incoming edge as a parameter. This implementation makes sure
+ * each vertex is only visited once. The function may return false if the
+ * traversal should stop at the given vertex.
+ * 
+ * Parameters:
+ * 
+ * vertex - <mxCell> that represents the vertex where the traversal starts.
+ * directed - boolean indicating if edges should only be traversed
+ * from source to target. Default is true.
+ * edge - Optional <mxCell> that represents the incoming edge. This is
+ * null for the first step of the traversal.
+ * allVertices - Array of cell paths for the visited cells.
+ */
+mxHierarchicalLayout.prototype.traverse = function(vertex, directed, edge, allVertices, currentComp,
+											hierarchyVertices, filledVertexSet)
+{
+	if (vertex != null && allVertices != null)
+	{
+		// Has this vertex been seen before in any traversal
+		// And if the filled vertex set is populated, only 
+		// process vertices in that it contains
+		var vertexID = mxObjectIdentity.get(vertex);
+		
+		if ((allVertices[vertexID] == null)
+				&& (filledVertexSet == null ? true : filledVertexSet[vertexID] != null))
+		{
+			if (currentComp[vertexID] == null)
+			{
+				currentComp[vertexID] = vertex;
+			}
+			if (allVertices[vertexID] == null)
+			{
+				allVertices[vertexID] = vertex;
+			}
+
+			if (filledVertexSet !== null)
+			{
+				delete filledVertexSet[vertexID];
+			}
+
+			var edges = this.getEdges(vertex);
+			var edgeIsSource = [];
+
+			for (var i = 0; i < edges.length; i++)
+			{
+				edgeIsSource[i] = (this.getVisibleTerminal(edges[i], true) == vertex);
+			}
+
+			for (var i = 0; i < edges.length; i++)
+			{
+				if (!directed || edgeIsSource[i])
+				{
+					var next = this.getVisibleTerminal(edges[i], !edgeIsSource[i]);
+					
+					// Check whether there are more edges incoming from the target vertex than outgoing
+					// The hierarchical model treats bi-directional parallel edges as being sourced
+					// from the more "sourced" terminal. If the directions are equal in number, the direction
+					// is that of the natural direction from the roots of the layout.
+					// The checks below are slightly more verbose than need be for performance reasons
+					var netCount = 1;
+
+					for (var j = 0; j < edges.length; j++)
+					{
+						if (j == i)
+						{
+							continue;
+						}
+						else
+						{
+							var isSource2 = edgeIsSource[j];
+							var otherTerm = this.getVisibleTerminal(edges[j], !isSource2);
+							
+							if (otherTerm == next)
+							{
+								if (isSource2)
+								{
+									netCount++;
+								}
+								else
+								{
+									netCount--;
+								}
+							}
+						}
+					}
+
+					if (netCount >= 0)
+					{
+						currentComp = this.traverse(next, directed, edges[i], allVertices,
+							currentComp, hierarchyVertices,
+							filledVertexSet);
+					}
+				}
+			}
+		}
+		else
+		{
+			if (currentComp[vertexID] == null)
+			{
+				// We've seen this vertex before, but not in the current component
+				// This component and the one it's in need to be merged
+
+				for (var i = 0; i < hierarchyVertices.length; i++)
+				{
+					var comp = hierarchyVertices[i];
+
+					if (comp[vertexID] != null)
+					{
+						for (var key in comp)
+						{
+							currentComp[key] = comp[key];
+						}
+						
+						// Remove the current component from the hierarchy set
+						hierarchyVertices.splice(i, 1);
+						return currentComp;
+					}
+				}
+			}
+		}
+	}
+	
+	return currentComp;
+};
+
+/**
+ * Function: cycleStage
+ * 
+ * Executes the cycle stage using mxMinimumCycleRemover.
+ */
+mxHierarchicalLayout.prototype.cycleStage = function(parent)
+{
+	var cycleStage = new mxMinimumCycleRemover(this);
+	cycleStage.execute(parent);
+};
+
+/**
+ * Function: layeringStage
+ * 
+ * Implements first stage of a Sugiyama layout.
+ */
+mxHierarchicalLayout.prototype.layeringStage = function()
+{
+	this.model.initialRank();
+	this.model.fixRanks();
+};
+
+/**
+ * Function: crossingStage
+ * 
+ * Executes the crossing stage using mxMedianHybridCrossingReduction.
+ */
+mxHierarchicalLayout.prototype.crossingStage = function(parent)
+{
+	var crossingStage = new mxMedianHybridCrossingReduction(this);
+	crossingStage.execute(parent);
+};
+
+/**
+ * Function: placementStage
+ * 
+ * Executes the placement stage using mxCoordinateAssignment.
+ */
+mxHierarchicalLayout.prototype.placementStage = function(initialX, parent)
+{
+	var placementStage = new mxCoordinateAssignment(this, this.intraCellSpacing,
+			this.interRankCellSpacing, this.orientation, initialX,
+			this.parallelEdgeSpacing);
+	placementStage.fineTuning = this.fineTuning;
+	placementStage.execute(parent);
+	
+	return placementStage.limitX + this.interHierarchySpacing;
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/mxSwimlaneLayout.js b/airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/mxSwimlaneLayout.js
new file mode 100644
index 0000000..669beef
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/mxSwimlaneLayout.js
@@ -0,0 +1,937 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxSwimlaneLayout
+ * 
+ * A hierarchical layout algorithm.
+ * 
+ * Constructor: mxSwimlaneLayout
+ *
+ * Constructs a new hierarchical layout algorithm.
+ *
+ * Arguments:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ * orientation - Optional constant that defines the orientation of this
+ * layout.
+ * deterministic - Optional boolean that specifies if this layout should be
+ * deterministic. Default is true.
+ */
+function mxSwimlaneLayout(graph, orientation, deterministic)
+{
+	mxGraphLayout.call(this, graph);
+	this.orientation = (orientation != null) ? orientation : mxConstants.DIRECTION_NORTH;
+	this.deterministic = (deterministic != null) ? deterministic : true;
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxSwimlaneLayout.prototype = new mxGraphLayout();
+mxSwimlaneLayout.prototype.constructor = mxSwimlaneLayout;
+
+/**
+ * Variable: roots
+ * 
+ * Holds the array of <mxCell> that this layout contains.
+ */
+mxSwimlaneLayout.prototype.roots = null;
+
+/**
+ * Variable: swimlanes
+ * 
+ * Holds the array of <mxCell> of the ordered swimlanes to lay out
+ */
+mxSwimlaneLayout.prototype.swimlanes = null;
+
+/**
+ * Variable: dummyVertices
+ * 
+ * Holds an array of <mxCell> of dummy vertices inserted during the layout
+ * to pad out empty swimlanes
+ */
+mxSwimlaneLayout.prototype.dummyVertices = null;
+
+/**
+ * Variable: dummyVertexWidth
+ * 
+ * The cell width of any dummy vertices inserted
+ */
+mxSwimlaneLayout.prototype.dummyVertexWidth = 50;
+
+/**
+ * Variable: resizeParent
+ * 
+ * Specifies if the parent should be resized after the layout so that it
+ * contains all the child cells. Default is false. See also <parentBorder>.
+ */
+mxSwimlaneLayout.prototype.resizeParent = false;
+
+/**
+ * Variable: maintainParentLocation
+ * 
+ * Specifies if the parent location should be maintained, so that the
+ * top, left corner stays the same before and after execution of
+ * the layout. Default is false for backwards compatibility.
+ */
+mxSwimlaneLayout.prototype.maintainParentLocation = false;
+
+/**
+ * Variable: moveParent
+ * 
+ * Specifies if the parent should be moved if <resizeParent> is enabled.
+ * Default is false.
+ */
+mxSwimlaneLayout.prototype.moveParent = false;
+
+/**
+ * Variable: parentBorder
+ * 
+ * The border to be added around the children if the parent is to be
+ * resized using <resizeParent>. Default is 0.
+ */
+mxSwimlaneLayout.prototype.parentBorder = 30;
+
+/**
+ * Variable: intraCellSpacing
+ * 
+ * The spacing buffer added between cells on the same layer. Default is 30.
+ */
+mxSwimlaneLayout.prototype.intraCellSpacing = 30;
+
+/**
+ * Variable: interRankCellSpacing
+ * 
+ * The spacing buffer added between cell on adjacent layers. Default is 50.
+ */
+mxSwimlaneLayout.prototype.interRankCellSpacing = 100;
+
+/**
+ * Variable: interHierarchySpacing
+ * 
+ * The spacing buffer between unconnected hierarchies. Default is 60.
+ */
+mxSwimlaneLayout.prototype.interHierarchySpacing = 60;
+
+/**
+ * Variable: parallelEdgeSpacing
+ * 
+ * The distance between each parallel edge on each ranks for long edges
+ */
+mxSwimlaneLayout.prototype.parallelEdgeSpacing = 10;
+
+/**
+ * Variable: orientation
+ * 
+ * The position of the root node(s) relative to the laid out graph in.
+ * Default is <mxConstants.DIRECTION_NORTH>.
+ */
+mxSwimlaneLayout.prototype.orientation = mxConstants.DIRECTION_NORTH;
+
+/**
+ * Variable: fineTuning
+ * 
+ * Whether or not to perform local optimisations and iterate multiple times
+ * through the algorithm. Default is true.
+ */
+mxSwimlaneLayout.prototype.fineTuning = true;
+
+/**
+ * 
+ * Variable: tightenToSource
+ * 
+ * Whether or not to tighten the assigned ranks of vertices up towards
+ * the source cells.
+ */
+mxSwimlaneLayout.prototype.tightenToSource = true;
+
+/**
+ * Variable: disableEdgeStyle
+ * 
+ * Specifies if the STYLE_NOEDGESTYLE flag should be set on edges that are
+ * modified by the result. Default is true.
+ */
+mxSwimlaneLayout.prototype.disableEdgeStyle = true;
+
+/**
+ * Variable: traverseAncestors
+ * 
+ * Whether or not to drill into child cells and layout in reverse
+ * group order. This also cause the layout to navigate edges whose 
+ * terminal vertices  * have different parents but are in the same 
+ * ancestry chain
+ */
+mxSwimlaneLayout.prototype.traverseAncestors = true;
+
+/**
+ * Variable: model
+ * 
+ * The internal <mxSwimlaneModel> formed of the layout.
+ */
+mxSwimlaneLayout.prototype.model = null;
+
+/**
+ * Variable: edgesSet
+ * 
+ * A cache of edges whose source terminal is the key
+ */
+mxSwimlaneLayout.prototype.edgesCache = null;
+
+/**
+ * Variable: edgesSet
+ * 
+ * A cache of edges whose source terminal is the key
+ */
+mxHierarchicalLayout.prototype.edgeSourceTermCache = null;
+
+/**
+ * Variable: edgesSet
+ * 
+ * A cache of edges whose source terminal is the key
+ */
+mxHierarchicalLayout.prototype.edgesTargetTermCache = null;
+
+/**
+ * Variable: edgeStyle
+ * 
+ * The style to apply between cell layers to edge segments
+ */
+mxHierarchicalLayout.prototype.edgeStyle = mxHierarchicalEdgeStyle.POLYLINE;
+
+/**
+ * Function: getModel
+ * 
+ * Returns the internal <mxSwimlaneModel> for this layout algorithm.
+ */
+mxSwimlaneLayout.prototype.getModel = function()
+{
+	return this.model;
+};
+
+/**
+ * Function: execute
+ * 
+ * Executes the layout for the children of the specified parent.
+ * 
+ * Parameters:
+ * 
+ * parent - Parent <mxCell> that contains the children to be laid out.
+ * swimlanes - Ordered array of swimlanes to be laid out
+ */
+mxSwimlaneLayout.prototype.execute = function(parent, swimlanes)
+{
+	this.parent = parent;
+	var model = this.graph.model;
+	this.edgesCache = new mxDictionary();
+	this.edgeSourceTermCache = new mxDictionary();
+	this.edgesTargetTermCache = new mxDictionary();
+
+	// If the roots are set and the parent is set, only
+	// use the roots that are some dependent of the that
+	// parent.
+	// If just the root are set, use them as-is
+	// If just the parent is set use it's immediate
+	// children as the initial set
+
+	if (swimlanes == null || swimlanes.length < 1)
+	{
+		// TODO indicate the problem
+		return;
+	}
+
+	if (parent == null)
+	{
+		parent = model.getParent(swimlanes[0]);
+	}
+
+	//  Maintaining parent location
+	this.parentX = null;
+	this.parentY = null;
+	
+	if (parent != this.root && model.isVertex(parent) != null && this.maintainParentLocation)
+	{
+		var geo = this.graph.getCellGeometry(parent);
+		
+		if (geo != null)
+		{
+			this.parentX = geo.x;
+			this.parentY = geo.y;
+		}
+	}
+
+	this.swimlanes = swimlanes;
+	this.dummyVertices = [];
+	// Check the swimlanes all have vertices
+	// in them
+	for (var i = 0; i < swimlanes.length; i++)
+	{
+		var children = this.graph.getChildCells(swimlanes[i]);
+		
+		if (children == null || children.length == 0)
+		{
+			var vertex = this.graph.insertVertex(swimlanes[i], null, null, 0, 0, this.dummyVertexWidth, 0);
+			this.dummyVertices.push(vertex);
+		}
+	}
+	
+	model.beginUpdate();
+	try
+	{
+		this.run(parent);
+		
+		if (this.resizeParent && !this.graph.isCellCollapsed(parent))
+		{
+			this.graph.updateGroupBounds([parent], this.parentBorder, this.moveParent);
+		}
+		
+		// Maintaining parent location
+		if (this.parentX != null && this.parentY != null)
+		{
+			var geo = this.graph.getCellGeometry(parent);
+			
+			if (geo != null)
+			{
+				geo = geo.clone();
+				geo.x = this.parentX;
+				geo.y = this.parentY;
+				model.setGeometry(parent, geo);
+			}
+		}
+
+		this.graph.removeCells(this.dummyVertices);
+	}
+	finally
+	{
+		model.endUpdate();
+	}
+};
+
+/**
+ * Function: updateGroupBounds
+ * 
+ * Updates the bounds of the given array of groups so that it includes
+ * all child vertices.
+ * 
+ */
+mxSwimlaneLayout.prototype.updateGroupBounds = function()
+{
+	// Get all vertices and edge in the layout
+	var cells = [];
+	var model = this.model;
+	
+	for (var key in model.edgeMapper)
+	{
+		var edge = model.edgeMapper[key];
+		
+		for (var i = 0; i < edge.edges.length; i++)
+		{
+			cells.push(edge.edges[i]);
+		}
+	}
+	
+	var layoutBounds = this.graph.getBoundingBoxFromGeometry(cells, true);
+	var childBounds = [];
+
+	for (var i = 0; i < this.swimlanes.length; i++)
+	{
+		var lane = this.swimlanes[i];
+		var geo = this.graph.getCellGeometry(lane);
+		
+		if (geo != null)
+		{
+			var children = this.graph.getChildCells(lane);
+			
+			var size = (this.graph.isSwimlane(lane)) ?
+					this.graph.getStartSize(lane) : new mxRectangle();
+
+			var bounds = this.graph.getBoundingBoxFromGeometry(children);
+			childBounds[i] = bounds;
+			var childrenY = bounds.y + geo.y - size.height - this.parentBorder;
+			var maxChildrenY = bounds.y + geo.y + bounds.height;
+
+			if (layoutBounds == null)
+			{
+				layoutBounds = new mxRectangle(0, childrenY, 0, maxChildrenY - childrenY);
+			}
+			else
+			{
+				layoutBounds.y = Math.min(layoutBounds.y, childrenY);
+				var maxY = Math.max(layoutBounds.y + layoutBounds.height, maxChildrenY);
+				layoutBounds.height = maxY - layoutBounds.y;
+			}
+		}
+	}
+
+	
+	for (var i = 0; i < this.swimlanes.length; i++)
+	{
+		var lane = this.swimlanes[i];
+		var geo = this.graph.getCellGeometry(lane);
+		
+		if (geo != null)
+		{
+			var children = this.graph.getChildCells(lane);
+			
+			var size = (this.graph.isSwimlane(lane)) ?
+					this.graph.getStartSize(lane) : new mxRectangle();
+
+			var newGeo = geo.clone();
+			
+			var leftGroupBorder = (i == 0) ? this.parentBorder : this.interRankCellSpacing/2;
+			newGeo.x += childBounds[i].x - size.width - leftGroupBorder;
+			newGeo.y = newGeo.y + layoutBounds.y - geo.y - this.parentBorder;
+			
+			newGeo.width = childBounds[i].width + size.width + this.interRankCellSpacing/2 + leftGroupBorder;
+			newGeo.height = layoutBounds.height + size.height + 2 * this.parentBorder;
+			
+			this.graph.model.setGeometry(lane, newGeo);
+			this.graph.moveCells(children, -childBounds[i].x + size.width + leftGroupBorder, 
+					geo.y - layoutBounds.y + this.parentBorder);
+		}
+	}
+};
+
+/**
+ * Function: findRoots
+ * 
+ * Returns all visible children in the given parent which do not have
+ * incoming edges. If the result is empty then the children with the
+ * maximum difference between incoming and outgoing edges are returned.
+ * This takes into account edges that are being promoted to the given
+ * root due to invisible children or collapsed cells.
+ * 
+ * Parameters:
+ * 
+ * parent - <mxCell> whose children should be checked.
+ * vertices - array of vertices to limit search to
+ */
+mxSwimlaneLayout.prototype.findRoots = function(parent, vertices)
+{
+	var roots = [];
+	
+	if (parent != null && vertices != null)
+	{
+		var model = this.graph.model;
+		var best = null;
+		var maxDiff = -100000;
+		
+		for (var i in vertices)
+		{
+			var cell = vertices[i];
+
+			if (cell != null && model.isVertex(cell) && this.graph.isCellVisible(cell) && model.isAncestor(parent, cell))
+			{
+				var conns = this.getEdges(cell);
+				var fanOut = 0;
+				var fanIn = 0;
+
+				for (var k = 0; k < conns.length; k++)
+				{
+					var src = this.getVisibleTerminal(conns[k], true);
+
+					if (src == cell)
+					{
+						// Only count connection within this swimlane
+						var other = this.getVisibleTerminal(conns[k], false);
+						
+						if (model.isAncestor(parent, other))
+						{
+							fanOut++;
+						}
+					}
+					else if (model.isAncestor(parent, src))
+					{
+						fanIn++;
+					}
+				}
+
+				if (fanIn == 0 && fanOut > 0)
+				{
+					roots.push(cell);
+				}
+
+				var diff = fanOut - fanIn;
+
+				if (diff > maxDiff)
+				{
+					maxDiff = diff;
+					best = cell;
+				}
+			}
+		}
+		
+		if (roots.length == 0 && best != null)
+		{
+			roots.push(best);
+		}
+	}
+	
+	return roots;
+};
+
+/**
+ * Function: getEdges
+ * 
+ * Returns the connected edges for the given cell.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose edges should be returned.
+ */
+mxSwimlaneLayout.prototype.getEdges = function(cell)
+{
+	var cachedEdges = this.edgesCache.get(cell);
+	
+	if (cachedEdges != null)
+	{
+		return cachedEdges;
+	}
+
+	var model = this.graph.model;
+	var edges = [];
+	var isCollapsed = this.graph.isCellCollapsed(cell);
+	var childCount = model.getChildCount(cell);
+
+	for (var i = 0; i < childCount; i++)
+	{
+		var child = model.getChildAt(cell, i);
+
+		if (this.isPort(child))
+		{
+			edges = edges.concat(model.getEdges(child, true, true));
+		}
+		else if (isCollapsed || !this.graph.isCellVisible(child))
+		{
+			edges = edges.concat(model.getEdges(child, true, true));
+		}
+	}
+
+	edges = edges.concat(model.getEdges(cell, true, true));
+	var result = [];
+	
+	for (var i = 0; i < edges.length; i++)
+	{
+		var source = this.getVisibleTerminal(edges[i], true);
+		var target = this.getVisibleTerminal(edges[i], false);
+		
+		if ((source == target) || ((source != target) && ((target == cell && (this.parent == null || this.graph.isValidAncestor(source, this.parent, this.traverseAncestors))) ||
+			(source == cell && (this.parent == null ||
+					this.graph.isValidAncestor(target, this.parent, this.traverseAncestors))))))
+		{
+			result.push(edges[i]);
+		}
+	}
+
+	this.edgesCache.put(cell, result);
+
+	return result;
+};
+
+/**
+ * Function: getVisibleTerminal
+ * 
+ * Helper function to return visible terminal for edge allowing for ports
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> whose edges should be returned.
+ * source - Boolean that specifies whether the source or target terminal is to be returned
+ */
+mxSwimlaneLayout.prototype.getVisibleTerminal = function(edge, source)
+{
+	var terminalCache = this.edgesTargetTermCache;
+	
+	if (source)
+	{
+		terminalCache = this.edgeSourceTermCache;
+	}
+
+	var term = terminalCache.get(edge);
+
+	if (term != null)
+	{
+		return term;
+	}
+
+	var state = this.graph.view.getState(edge);
+	
+	var terminal = (state != null) ? state.getVisibleTerminal(source) : this.graph.view.getVisibleTerminal(edge, source);
+	
+	if (terminal == null)
+	{
+		terminal = (state != null) ? state.getVisibleTerminal(source) : this.graph.view.getVisibleTerminal(edge, source);
+	}
+
+	if (terminal != null)
+	{
+		if (this.isPort(terminal))
+		{
+			terminal = this.graph.model.getParent(terminal);
+		}
+		
+		terminalCache.put(edge, terminal);
+	}
+
+	return terminal;
+};
+
+/**
+ * Function: run
+ * 
+ * The API method used to exercise the layout upon the graph description
+ * and produce a separate description of the vertex position and edge
+ * routing changes made. It runs each stage of the layout that has been
+ * created.
+ */
+mxSwimlaneLayout.prototype.run = function(parent)
+{
+	// Separate out unconnected hierarchies
+	var hierarchyVertices = [];
+	var allVertexSet = [];
+
+	if (this.swimlanes != null && this.swimlanes.length > 0 && parent != null)
+	{
+		var filledVertexSet = Object();
+		
+		for (var i = 0; i < this.swimlanes.length; i++)
+		{
+			this.filterDescendants(this.swimlanes[i], filledVertexSet);
+		}
+
+		this.roots = [];
+		var filledVertexSetEmpty = true;
+
+		// Poor man's isSetEmpty
+		for (var key in filledVertexSet)
+		{
+			if (filledVertexSet[key] != null)
+			{
+				filledVertexSetEmpty = false;
+				break;
+			}
+		}
+
+		// Only test for candidates in each swimlane in order
+		var laneCounter = 0;
+
+		while (!filledVertexSetEmpty && laneCounter < this.swimlanes.length)
+		{
+			var candidateRoots = this.findRoots(this.swimlanes[laneCounter], filledVertexSet);
+			
+			if (candidateRoots.length == 0)
+			{
+				laneCounter++;
+				continue;
+			}
+			
+			// If the candidate root is an unconnected group cell, remove it from
+			// the layout. We may need a custom set that holds such groups and forces
+			// them to be processed for resizing and/or moving.
+			for (var i = 0; i < candidateRoots.length; i++)
+			{
+				var vertexSet = Object();
+				hierarchyVertices.push(vertexSet);
+
+				this.traverse(candidateRoots[i], true, null, allVertexSet, vertexSet,
+						hierarchyVertices, filledVertexSet, laneCounter);
+			}
+
+			for (var i = 0; i < candidateRoots.length; i++)
+			{
+				this.roots.push(candidateRoots[i]);
+			}
+			
+			filledVertexSetEmpty = true;
+			
+			// Poor man's isSetEmpty
+			for (var key in filledVertexSet)
+			{
+				if (filledVertexSet[key] != null)
+				{
+					filledVertexSetEmpty = false;
+					break;
+				}
+			}
+		}
+	}
+	else
+	{
+		// Find vertex set as directed traversal from roots
+
+		for (var i = 0; i < this.roots.length; i++)
+		{
+			var vertexSet = Object();
+			hierarchyVertices.push(vertexSet);
+
+			this.traverse(this.roots[i], true, null, allVertexSet, vertexSet,
+					hierarchyVertices, null);
+		}
+	}
+
+	var tmp = [];
+	
+	for (var key in allVertexSet)
+	{
+		tmp.push(allVertexSet[key]);
+	}
+	
+	this.model = new mxSwimlaneModel(this, tmp, this.roots,
+		parent, this.tightenToSource);
+
+	this.cycleStage(parent);
+	this.layeringStage();
+	
+	this.crossingStage(parent);
+	initialX = this.placementStage(0, parent);
+};
+
+/**
+ * Function: filterDescendants
+ * 
+ * Creates an array of descendant cells
+ */
+mxSwimlaneLayout.prototype.filterDescendants = function(cell, result)
+{
+	var model = this.graph.model;
+
+	if (model.isVertex(cell) && cell != this.parent && model.getParent(cell) != this.parent && this.graph.isCellVisible(cell))
+	{
+		result[mxObjectIdentity.get(cell)] = cell;
+	}
+
+	if (this.traverseAncestors || cell == this.parent
+			&& this.graph.isCellVisible(cell))
+	{
+		var childCount = model.getChildCount(cell);
+
+		for (var i = 0; i < childCount; i++)
+		{
+			var child = model.getChildAt(cell, i);
+			
+			// Ignore ports in the layout vertex list, they are dealt with
+			// in the traversal mechanisms
+			if (!this.isPort(child))
+			{
+				this.filterDescendants(child, result);
+			}
+		}
+	}
+};
+
+/**
+ * Function: isPort
+ * 
+ * Returns true if the given cell is a "port", that is, when connecting to
+ * it, its parent is the connecting vertex in terms of graph traversal
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the port.
+ */
+mxSwimlaneLayout.prototype.isPort = function(cell)
+{
+	if (cell.geometry.relative)
+	{
+		return true;
+	}
+	
+	return false;
+};
+
+/**
+ * Function: getEdgesBetween
+ * 
+ * Returns the edges between the given source and target. This takes into
+ * account collapsed and invisible cells and ports.
+ * 
+ * Parameters:
+ * 
+ * source -
+ * target -
+ * directed -
+ */
+mxSwimlaneLayout.prototype.getEdgesBetween = function(source, target, directed)
+{
+	directed = (directed != null) ? directed : false;
+	var edges = this.getEdges(source);
+	var result = [];
+
+	// Checks if the edge is connected to the correct
+	// cell and returns the first match
+	for (var i = 0; i < edges.length; i++)
+	{
+		var src = this.getVisibleTerminal(edges[i], true);
+		var trg = this.getVisibleTerminal(edges[i], false);
+
+		if ((src == source && trg == target) || (!directed && src == target && trg == source))
+		{
+			result.push(edges[i]);
+		}
+	}
+
+	return result;
+};
+
+/**
+ * Traverses the (directed) graph invoking the given function for each
+ * visited vertex and edge. The function is invoked with the current vertex
+ * and the incoming edge as a parameter. This implementation makes sure
+ * each vertex is only visited once. The function may return false if the
+ * traversal should stop at the given vertex.
+ * 
+ * Parameters:
+ * 
+ * vertex - <mxCell> that represents the vertex where the traversal starts.
+ * directed - boolean indicating if edges should only be traversed
+ * from source to target. Default is true.
+ * edge - Optional <mxCell> that represents the incoming edge. This is
+ * null for the first step of the traversal.
+ * allVertices - Array of cell paths for the visited cells.
+ * swimlaneIndex - the laid out order index of the swimlane vertex is contained in
+ */
+mxSwimlaneLayout.prototype.traverse = function(vertex, directed, edge, allVertices, currentComp,
+											hierarchyVertices, filledVertexSet, swimlaneIndex)
+{
+	if (vertex != null && allVertices != null)
+	{
+		// Has this vertex been seen before in any traversal
+		// And if the filled vertex set is populated, only 
+		// process vertices in that it contains
+		var vertexID = mxObjectIdentity.get(vertex);
+		
+		if ((allVertices[vertexID] == null)
+				&& (filledVertexSet == null ? true : filledVertexSet[vertexID] != null))
+		{
+			if (currentComp[vertexID] == null)
+			{
+				currentComp[vertexID] = vertex;
+			}
+			if (allVertices[vertexID] == null)
+			{
+				allVertices[vertexID] = vertex;
+			}
+
+			if (filledVertexSet !== null)
+			{
+				delete filledVertexSet[vertexID];
+			}
+
+			var edges = this.getEdges(vertex);
+			var model = this.graph.model;
+
+			for (var i = 0; i < edges.length; i++)
+			{
+				var otherVertex = this.getVisibleTerminal(edges[i], true);
+				var isSource = otherVertex == vertex;
+				
+				if (isSource)
+				{
+					otherVertex = this.getVisibleTerminal(edges[i], false);
+				}
+
+				var otherIndex = 0;
+				// Get the swimlane index of the other terminal
+				for (otherIndex = 0; otherIndex < this.swimlanes.length; otherIndex++)
+				{
+					if (model.isAncestor(this.swimlanes[otherIndex], otherVertex))
+					{
+						break;
+					}
+				}
+				
+				if (otherIndex >= this.swimlanes.length)
+				{
+					continue;
+				}
+
+				// Traverse if the other vertex is within the same swimlane as
+				// as the current vertex, or if the swimlane index of the other
+				// vertex is greater than that of this vertex
+				if ((otherIndex > swimlaneIndex) ||
+						((!directed || isSource) && otherIndex == swimlaneIndex))
+				{
+					currentComp = this.traverse(otherVertex, directed, edges[i], allVertices,
+							currentComp, hierarchyVertices,
+							filledVertexSet, otherIndex);
+				}
+			}
+		}
+		else
+		{
+			if (currentComp[vertexID] == null)
+			{
+				// We've seen this vertex before, but not in the current component
+				// This component and the one it's in need to be merged
+				for (var i = 0; i < hierarchyVertices.length; i++)
+				{
+					var comp = hierarchyVertices[i];
+
+					if (comp[vertexID] != null)
+					{
+						for (var key in comp)
+						{
+							currentComp[key] = comp[key];
+						}
+						
+						// Remove the current component from the hierarchy set
+						hierarchyVertices.splice(i, 1);
+						return currentComp;
+					}
+				}
+			}
+		}
+	}
+	
+	return currentComp;
+};
+
+/**
+ * Function: cycleStage
+ * 
+ * Executes the cycle stage using mxMinimumCycleRemover.
+ */
+mxSwimlaneLayout.prototype.cycleStage = function(parent)
+{
+	var cycleStage = new mxSwimlaneOrdering(this);
+	cycleStage.execute(parent);
+};
+
+/**
+ * Function: layeringStage
+ * 
+ * Implements first stage of a Sugiyama layout.
+ */
+mxSwimlaneLayout.prototype.layeringStage = function()
+{
+	this.model.initialRank();
+	this.model.fixRanks();
+};
+
+/**
+ * Function: crossingStage
+ * 
+ * Executes the crossing stage using mxMedianHybridCrossingReduction.
+ */
+mxSwimlaneLayout.prototype.crossingStage = function(parent)
+{
+	var crossingStage = new mxMedianHybridCrossingReduction(this);
+	crossingStage.execute(parent);
+};
+
+/**
+ * Function: placementStage
+ * 
+ * Executes the placement stage using mxCoordinateAssignment.
+ */
+mxSwimlaneLayout.prototype.placementStage = function(initialX, parent)
+{
+	var placementStage = new mxCoordinateAssignment(this, this.intraCellSpacing,
+			this.interRankCellSpacing, this.orientation, initialX,
+			this.parallelEdgeSpacing);
+	placementStage.fineTuning = this.fineTuning;
+	placementStage.execute(parent);
+	
+	return placementStage.limitX + this.interHierarchySpacing;
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/stage/mxCoordinateAssignment.js b/airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/stage/mxCoordinateAssignment.js
new file mode 100644
index 0000000..70c9bbd
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/stage/mxCoordinateAssignment.js
@@ -0,0 +1,1830 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxCoordinateAssignment
+ * 
+ * Sets the horizontal locations of node and edge dummy nodes on each layer.
+ * Uses median down and up weighings as well as heuristics to straighten edges as
+ * far as possible.
+ * 
+ * Constructor: mxCoordinateAssignment
+ *
+ * Creates a coordinate assignment.
+ * 
+ * Arguments:
+ * 
+ * intraCellSpacing - the minimum buffer between cells on the same rank
+ * interRankCellSpacing - the minimum distance between cells on adjacent ranks
+ * orientation - the position of the root node(s) relative to the graph
+ * initialX - the leftmost coordinate node placement starts at
+ */
+function mxCoordinateAssignment(layout, intraCellSpacing, interRankCellSpacing,
+	orientation, initialX, parallelEdgeSpacing)
+{
+	this.layout = layout;
+	this.intraCellSpacing = intraCellSpacing;
+	this.interRankCellSpacing = interRankCellSpacing;
+	this.orientation = orientation;
+	this.initialX = initialX;
+	this.parallelEdgeSpacing = parallelEdgeSpacing;
+};
+
+/**
+ * Extends mxHierarchicalLayoutStage.
+ */
+mxCoordinateAssignment.prototype = new mxHierarchicalLayoutStage();
+mxCoordinateAssignment.prototype.constructor = mxCoordinateAssignment;
+
+/**
+ * Variable: layout
+ * 
+ * Reference to the enclosing <mxHierarchicalLayout>.
+ */
+mxCoordinateAssignment.prototype.layout = null;
+
+/**
+ * Variable: intraCellSpacing
+ * 
+ * The minimum buffer between cells on the same rank. Default is 30.
+ */
+mxCoordinateAssignment.prototype.intraCellSpacing = 30;
+
+/**
+ * Variable: interRankCellSpacing
+ * 
+ * The minimum distance between cells on adjacent ranks. Default is 10.
+ */
+mxCoordinateAssignment.prototype.interRankCellSpacing = 100;
+
+/**
+ * Variable: parallelEdgeSpacing
+ * 
+ * The distance between each parallel edge on each ranks for long edges.
+ * Default is 10.
+ */
+mxCoordinateAssignment.prototype.parallelEdgeSpacing = 10;
+
+/**
+ * Variable: maxIterations
+ * 
+ * The number of heuristic iterations to run. Default is 8.
+ */
+mxCoordinateAssignment.prototype.maxIterations = 8;
+
+/**
+ * Variable: prefHozEdgeSep
+ * 
+ * The preferred horizontal distance between edges exiting a vertex
+ */
+mxCoordinateAssignment.prototype.prefHozEdgeSep = 5;
+
+/**
+ * Variable: prefVertEdgeOff
+ * 
+ * The preferred vertical offset between edges exiting a vertex
+ */
+mxCoordinateAssignment.prototype.prefVertEdgeOff = 2;
+
+/**
+ * Variable: minEdgeJetty
+ * 
+ * The minimum distance for an edge jetty from a vertex
+ */
+mxCoordinateAssignment.prototype.minEdgeJetty = 12;
+
+/**
+ * Variable: channelBuffer
+ * 
+ * The size of the vertical buffer in the center of inter-rank channels
+ * where edge control points should not be placed
+ */
+mxCoordinateAssignment.prototype.channelBuffer = 4;
+
+/**
+ * Variable: jettyPositions
+ * 
+ * Map of internal edges and (x,y) pair of positions of the start and end jetty
+ * for that edge where it connects to the source and target vertices.
+ * Note this should technically be a WeakHashMap, but since JS does not
+ * have an equivalent, housekeeping must be performed before using.
+ * i.e. check all edges are still in the model and clear the values.
+ * Note that the y co-ord is the offset of the jetty, not the
+ * absolute point
+ */
+mxCoordinateAssignment.prototype.jettyPositions = null;
+
+/**
+ * Variable: orientation
+ * 
+ * The position of the root ( start ) node(s) relative to the rest of the
+ * laid out graph. Default is <mxConstants.DIRECTION_NORTH>.
+ */
+mxCoordinateAssignment.prototype.orientation = mxConstants.DIRECTION_NORTH;
+
+/**
+ * Variable: initialX
+ * 
+ * The minimum x position node placement starts at
+ */
+mxCoordinateAssignment.prototype.initialX = null;
+
+/**
+ * Variable: limitX
+ * 
+ * The maximum x value this positioning lays up to
+ */
+mxCoordinateAssignment.prototype.limitX = null;
+
+/**
+ * Variable: currentXDelta
+ * 
+ * The sum of x-displacements for the current iteration
+ */
+mxCoordinateAssignment.prototype.currentXDelta = null;
+
+/**
+ * Variable: widestRank
+ * 
+ * The rank that has the widest x position
+ */
+mxCoordinateAssignment.prototype.widestRank = null;
+
+/**
+ * Variable: rankTopY
+ * 
+ * Internal cache of top-most values of Y for each rank
+ */
+mxCoordinateAssignment.prototype.rankTopY = null;
+
+/**
+ * Variable: rankBottomY
+ * 
+ * Internal cache of bottom-most value of Y for each rank
+ */
+mxCoordinateAssignment.prototype.rankBottomY = null;
+
+/**
+ * Variable: widestRankValue
+ * 
+ * The X-coordinate of the edge of the widest rank
+ */
+mxCoordinateAssignment.prototype.widestRankValue = null;
+
+/**
+ * Variable: rankWidths
+ * 
+ * The width of all the ranks
+ */
+mxCoordinateAssignment.prototype.rankWidths = null;
+
+/**
+ * Variable: rankY
+ * 
+ * The Y-coordinate of all the ranks
+ */
+mxCoordinateAssignment.prototype.rankY = null;
+
+/**
+ * Variable: fineTuning
+ * 
+ * Whether or not to perform local optimisations and iterate multiple times
+ * through the algorithm. Default is true.
+ */
+mxCoordinateAssignment.prototype.fineTuning = true;
+
+/**
+ * Variable: 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 = Object();
+	var model = this.layout.getModel();
+	this.currentXDelta = 0.0;
+
+	this.initialCoords(this.layout.getGraph(), model);
+	
+//	this.printStatus();
+	
+	if (this.fineTuning)
+	{
+		this.minNode(model);
+	}
+	
+	var bestXDelta = 100000000.0;
+	
+	if (this.fineTuning)
+	{
+		for (var i = 0; i < this.maxIterations; i++)
+		{
+//			this.printStatus();
+		
+			// Median Heuristic
+			if (i != 0)
+			{
+				this.medianPos(i, model);
+				this.minNode(model);
+			}
+			
+			// if the total offset is less for the current positioning,
+			// there are less heavily angled edges and so the current
+			// positioning is used
+			if (this.currentXDelta < bestXDelta)
+			{
+				for (var j = 0; j < model.ranks.length; j++)
+				{
+					var rank = model.ranks[j];
+					
+					for (var k = 0; k < rank.length; k++)
+					{
+						var cell = rank[k];
+						cell.setX(j, cell.getGeneralPurposeVariable(j));
+					}
+				}
+				
+				bestXDelta = this.currentXDelta;
+			}
+			else
+			{
+				// Restore the best positions
+				for (var j = 0; j < model.ranks.length; j++)
+				{
+					var rank = model.ranks[j];
+					
+					for (var k = 0; k < rank.length; k++)
+					{
+						var cell = rank[k];
+						cell.setGeneralPurposeVariable(j, cell.getX(j));
+					}
+				}
+			}
+			
+			this.minPath(this.layout.getGraph(), model);
+			
+			this.currentXDelta = 0;
+		}
+	}
+	
+	this.setCellLocations(this.layout.getGraph(), model);
+};
+
+/**
+ * Function: minNode
+ * 
+ * Performs one median positioning sweep in both directions
+ */
+mxCoordinateAssignment.prototype.minNode = function(model)
+{
+	// Queue all nodes
+	var nodeList = [];
+	
+	// Need to be able to map from cell to cellWrapper
+	var map = new mxDictionary();
+	var rank = [];
+	
+	for (var i = 0; i <= model.maxRank; i++)
+	{
+		rank[i] = model.ranks[i];
+		
+		for (var j = 0; j < rank[i].length; j++)
+		{
+			// Use the weight to store the rank and visited to store whether
+			// or not the cell is in the list
+			var node = rank[i][j];
+			var nodeWrapper = new WeightedCellSorter(node, i);
+			nodeWrapper.rankIndex = j;
+			nodeWrapper.visited = true;
+			nodeList.push(nodeWrapper);
+			
+			map.put(node, nodeWrapper);
+		}
+	}
+	
+	// Set a limit of the maximum number of times we will access the queue
+	// in case a loop appears
+	var maxTries = nodeList.length * 10;
+	var count = 0;
+	
+	// Don't move cell within this value of their median
+	var tolerance = 1;
+	
+	while (nodeList.length > 0 && count <= maxTries)
+	{
+		var cellWrapper = nodeList.shift();
+		var cell = cellWrapper.cell;
+		
+		var rankValue = cellWrapper.weightedValue;
+		var rankIndex = parseInt(cellWrapper.rankIndex);
+		
+		var nextLayerConnectedCells = cell.getNextLayerConnectedCells(rankValue);
+		var previousLayerConnectedCells = cell.getPreviousLayerConnectedCells(rankValue);
+		
+		var numNextLayerConnected = nextLayerConnectedCells.length;
+		var numPreviousLayerConnected = previousLayerConnectedCells.length;
+
+		var medianNextLevel = this.medianXValue(nextLayerConnectedCells,
+				rankValue + 1);
+		var medianPreviousLevel = this.medianXValue(previousLayerConnectedCells,
+				rankValue - 1);
+
+		var numConnectedNeighbours = numNextLayerConnected
+				+ numPreviousLayerConnected;
+		var currentPosition = cell.getGeneralPurposeVariable(rankValue);
+		var cellMedian = currentPosition;
+		
+		if (numConnectedNeighbours > 0)
+		{
+			cellMedian = (medianNextLevel * numNextLayerConnected + medianPreviousLevel
+					* numPreviousLayerConnected)
+					/ numConnectedNeighbours;
+		}
+
+		// Flag storing whether or not position has changed
+		var positionChanged = false;
+		
+		if (cellMedian < currentPosition - tolerance)
+		{
+			if (rankIndex == 0)
+			{
+				cell.setGeneralPurposeVariable(rankValue, cellMedian);
+				positionChanged = true;
+			}
+			else
+			{
+				var leftCell = rank[rankValue][rankIndex - 1];
+				var leftLimit = leftCell
+						.getGeneralPurposeVariable(rankValue);
+				leftLimit = leftLimit + leftCell.width / 2
+						+ this.intraCellSpacing + cell.width / 2;
+
+				if (leftLimit < cellMedian)
+				{
+					cell.setGeneralPurposeVariable(rankValue, cellMedian);
+					positionChanged = true;
+				}
+				else if (leftLimit < cell
+						.getGeneralPurposeVariable(rankValue)
+						- tolerance)
+				{
+					cell.setGeneralPurposeVariable(rankValue, leftLimit);
+					positionChanged = true;
+				}
+			}
+		}
+		else if (cellMedian > currentPosition + tolerance)
+		{
+			var rankSize = rank[rankValue].length;
+			
+			if (rankIndex == rankSize - 1)
+			{
+				cell.setGeneralPurposeVariable(rankValue, cellMedian);
+				positionChanged = true;
+			}
+			else
+			{
+				var rightCell = rank[rankValue][rankIndex + 1];
+				var rightLimit = rightCell
+						.getGeneralPurposeVariable(rankValue);
+				rightLimit = rightLimit - rightCell.width / 2
+						- this.intraCellSpacing - cell.width / 2;
+				
+				if (rightLimit > cellMedian)
+				{
+					cell.setGeneralPurposeVariable(rankValue, cellMedian);
+					positionChanged = true;
+				}
+				else if (rightLimit > cell
+						.getGeneralPurposeVariable(rankValue)
+						+ tolerance)
+				{
+					cell.setGeneralPurposeVariable(rankValue, rightLimit);
+					positionChanged = true;
+				}
+			}
+		}
+		
+		if (positionChanged)
+		{
+			// Add connected nodes to map and list
+			for (var i = 0; i < nextLayerConnectedCells.length; i++)
+			{
+				var connectedCell = nextLayerConnectedCells[i];
+				var connectedCellWrapper = map.get(connectedCell);
+				
+				if (connectedCellWrapper != null)
+				{
+					if (connectedCellWrapper.visited == false)
+					{
+						connectedCellWrapper.visited = true;
+						nodeList.push(connectedCellWrapper);
+					}
+				}
+			}
+
+			// Add connected nodes to map and list
+			for (var i = 0; i < previousLayerConnectedCells.length; i++)
+			{
+				var connectedCell = previousLayerConnectedCells[i];
+				var connectedCellWrapper = map.get(connectedCell);
+
+				if (connectedCellWrapper != null)
+				{
+					if (connectedCellWrapper.visited == false)
+					{
+						connectedCellWrapper.visited = true;
+						nodeList.push(connectedCellWrapper);
+					}
+				}
+			}
+		}
+		
+		cellWrapper.visited = false;
+		count++;
+	}
+};
+
+/**
+ * Function: medianPos
+ * 
+ * Performs one median positioning sweep in one direction
+ * 
+ * Parameters:
+ * 
+ * i - the iteration of the whole process
+ * model - an internal model of the hierarchical layout
+ */
+mxCoordinateAssignment.prototype.medianPos = function(i, model)
+{
+	// Reverse sweep direction each time through this method
+	var downwardSweep = (i % 2 == 0);
+	
+	if (downwardSweep)
+	{
+		for (var j = model.maxRank; j > 0; j--)
+		{
+			this.rankMedianPosition(j - 1, model, j);
+		}
+	}
+	else
+	{
+		for (var j = 0; j < model.maxRank - 1; j++)
+		{
+			this.rankMedianPosition(j + 1, model, j);
+		}
+	}
+};
+
+/**
+ * Function: rankMedianPosition
+ * 
+ * Performs median minimisation over one rank.
+ * 
+ * Parameters:
+ * 
+ * rankValue - the layer number of this rank
+ * model - an internal model of the hierarchical layout
+ * nextRankValue - the layer number whose connected cels are to be laid out
+ * relative to
+ */
+mxCoordinateAssignment.prototype.rankMedianPosition = function(rankValue, model, nextRankValue)
+{
+	var rank = model.ranks[rankValue];
+
+	// Form an array of the order in which the cell are to be processed
+	// , the order is given by the weighted sum of the in or out edges,
+	// depending on whether we're traveling up or down the hierarchy.
+	var weightedValues = [];
+	var cellMap = new Object();
+
+	for (var i = 0; i < rank.length; i++)
+	{
+		var currentCell = rank[i];
+		weightedValues[i] = new WeightedCellSorter();
+		weightedValues[i].cell = currentCell;
+		weightedValues[i].rankIndex = i;
+		cellMap[currentCell.id] = weightedValues[i];
+		var nextLayerConnectedCells = null;
+		
+		if (nextRankValue < rankValue)
+		{
+			nextLayerConnectedCells = currentCell
+					.getPreviousLayerConnectedCells(rankValue);
+		}
+		else
+		{
+			nextLayerConnectedCells = currentCell
+					.getNextLayerConnectedCells(rankValue);
+		}
+
+		// Calculate the weighing based on this node type and those this
+		// node is connected to on the next layer
+		weightedValues[i].weightedValue = this.calculatedWeightedValue(
+				currentCell, nextLayerConnectedCells);
+	}
+
+	weightedValues.sort(WeightedCellSorter.prototype.compare);
+
+	// Set the new position of each node within the rank using
+	// its temp variable
+	
+	for (var i = 0; i < weightedValues.length; i++)
+	{
+		var numConnectionsNextLevel = 0;
+		var cell = weightedValues[i].cell;
+		var nextLayerConnectedCells = null;
+		var medianNextLevel = 0;
+
+		if (nextRankValue < rankValue)
+		{
+			nextLayerConnectedCells = cell.getPreviousLayerConnectedCells(
+					rankValue).slice();
+		}
+		else
+		{
+			nextLayerConnectedCells = cell.getNextLayerConnectedCells(
+					rankValue).slice();
+		}
+
+		if (nextLayerConnectedCells != null)
+		{
+			numConnectionsNextLevel = nextLayerConnectedCells.length;
+			
+			if (numConnectionsNextLevel > 0)
+			{
+				medianNextLevel = this.medianXValue(nextLayerConnectedCells,
+						nextRankValue);
+			}
+			else
+			{
+				// For case of no connections on the next level set the
+				// median to be the current position and try to be
+				// positioned there
+				medianNextLevel = cell.getGeneralPurposeVariable(rankValue);
+			}
+		}
+
+		var leftBuffer = 0.0;
+		var leftLimit = -100000000.0;
+		
+		for (var j = weightedValues[i].rankIndex - 1; j >= 0;)
+		{
+			var weightedValue = cellMap[rank[j].id];
+			
+			if (weightedValue != null)
+			{
+				var leftCell = weightedValue.cell;
+				
+				if (weightedValue.visited)
+				{
+					// The left limit is the right hand limit of that
+					// cell plus any allowance for unallocated cells
+					// in-between
+					leftLimit = leftCell
+							.getGeneralPurposeVariable(rankValue)
+							+ leftCell.width
+							/ 2.0
+							+ this.intraCellSpacing
+							+ leftBuffer + cell.width / 2.0;
+					j = -1;
+				}
+				else
+				{
+					leftBuffer += leftCell.width + this.intraCellSpacing;
+					j--;
+				}
+			}
+		}
+
+		var rightBuffer = 0.0;
+		var rightLimit = 100000000.0;
+		
+		for (var j = weightedValues[i].rankIndex + 1; j < weightedValues.length;)
+		{
+			var weightedValue = cellMap[rank[j].id];
+			
+			if (weightedValue != null)
+			{
+				var rightCell = weightedValue.cell;
+				
+				if (weightedValue.visited)
+				{
+					// The left limit is the right hand limit of that
+					// cell plus any allowance for unallocated cells
+					// in-between
+					rightLimit = rightCell
+							.getGeneralPurposeVariable(rankValue)
+							- rightCell.width
+							/ 2.0
+							- this.intraCellSpacing
+							- rightBuffer - cell.width / 2.0;
+					j = weightedValues.length;
+				}
+				else
+				{
+					rightBuffer += rightCell.width + this.intraCellSpacing;
+					j++;
+				}
+			}
+		}
+		
+		if (medianNextLevel >= leftLimit && medianNextLevel <= rightLimit)
+		{
+			cell.setGeneralPurposeVariable(rankValue, medianNextLevel);
+		}
+		else if (medianNextLevel < leftLimit)
+		{
+			// Couldn't place at median value, place as close to that
+			// value as possible
+			cell.setGeneralPurposeVariable(rankValue, leftLimit);
+			this.currentXDelta += leftLimit - medianNextLevel;
+		}
+		else if (medianNextLevel > rightLimit)
+		{
+			// Couldn't place at median value, place as close to that
+			// value as possible
+			cell.setGeneralPurposeVariable(rankValue, rightLimit);
+			this.currentXDelta += medianNextLevel - rightLimit;
+		}
+
+		weightedValues[i].visited = true;
+	}
+};
+
+/**
+ * Function: calculatedWeightedValue
+ * 
+ * Calculates the priority the specified cell has based on the type of its
+ * cell and the cells it is connected to on the next layer
+ * 
+ * Parameters:
+ * 
+ * currentCell - the cell whose weight is to be calculated
+ * collection - the cells the specified cell is connected to
+ */
+mxCoordinateAssignment.prototype.calculatedWeightedValue = function(currentCell, collection)
+{
+	var totalWeight = 0;
+	
+	for (var i = 0; i < collection.length; i++)
+	{
+		var cell = collection[i];
+
+		if (currentCell.isVertex() && cell.isVertex())
+		{
+			totalWeight++;
+		}
+		else if (currentCell.isEdge() && cell.isEdge())
+		{
+			totalWeight += 8;
+		}
+		else
+		{
+			totalWeight += 2;
+		}
+	}
+
+	return totalWeight;
+};
+
+/**
+ * Function: medianXValue
+ * 
+ * Calculates the median position of the connected cell on the specified
+ * rank
+ * 
+ * Parameters:
+ * 
+ * connectedCells - the cells the candidate connects to on this level
+ * rankValue - the layer number of this rank
+ */
+mxCoordinateAssignment.prototype.medianXValue = function(connectedCells, rankValue)
+{
+	if (connectedCells.length == 0)
+	{
+		return 0;
+	}
+
+	var medianValues = [];
+
+	for (var i = 0; i < connectedCells.length; i++)
+	{
+		medianValues[i] = connectedCells[i].getGeneralPurposeVariable(rankValue);
+	}
+
+	medianValues.sort(function(a,b){return a - b;});
+	
+	if (connectedCells.length % 2 == 1)
+	{
+		// For odd numbers of adjacent vertices return the median
+		return medianValues[Math.floor(connectedCells.length / 2)];
+	}
+	else
+	{
+		var medianPoint = connectedCells.length / 2;
+		var leftMedian = medianValues[medianPoint - 1];
+		var rightMedian = medianValues[medianPoint];
+
+		return ((leftMedian + rightMedian) / 2);
+	}
+};
+
+/**
+ * Function: initialCoords
+ * 
+ * Sets up the layout in an initial positioning. The ranks are all centered
+ * as much as possible along the middle vertex in each rank. The other cells
+ * are then placed as close as possible on either side.
+ * 
+ * Parameters:
+ * 
+ * facade - the facade describing the input graph
+ * model - an internal model of the hierarchical layout
+ */
+mxCoordinateAssignment.prototype.initialCoords = function(facade, model)
+{
+	this.calculateWidestRank(facade, model);
+
+	// Sweep up and down from the widest rank
+	for (var i = this.widestRank; i >= 0; i--)
+	{
+		if (i < model.maxRank)
+		{
+			this.rankCoordinates(i, facade, model);
+		}
+	}
+
+	for (var i = this.widestRank+1; i <= model.maxRank; i++)
+	{
+		if (i > 0)
+		{
+			this.rankCoordinates(i, facade, model);
+		}
+	}
+};
+
+/**
+ * Function: rankCoordinates
+ * 
+ * Sets up the layout in an initial positioning. All the first cells in each
+ * rank are moved to the left and the rest of the rank inserted as close
+ * together as their size and buffering permits. This method works on just
+ * the specified rank.
+ * 
+ * Parameters:
+ * 
+ * rankValue - the current rank being processed
+ * graph - the facade describing the input graph
+ * model - an internal model of the hierarchical layout
+ */
+mxCoordinateAssignment.prototype.rankCoordinates = function(rankValue, graph, model)
+{
+	var rank = model.ranks[rankValue];
+	var maxY = 0.0;
+	var localX = this.initialX + (this.widestRankValue - this.rankWidths[rankValue])
+			/ 2;
+
+	// Store whether or not any of the cells' bounds were unavailable so
+	// to only issue the warning once for all cells
+	var boundsWarning = false;
+	
+	for (var i = 0; i < rank.length; i++)
+	{
+		var node = rank[i];
+		
+		if (node.isVertex())
+		{
+			var bounds = this.layout.getVertexBounds(node.cell);
+
+			if (bounds != null)
+			{
+				if (this.orientation == mxConstants.DIRECTION_NORTH ||
+					this.orientation == mxConstants.DIRECTION_SOUTH)
+				{
+					node.width = bounds.width;
+					node.height = bounds.height;
+				}
+				else
+				{
+					node.width = bounds.height;
+					node.height = bounds.width;
+				}
+			}
+			else
+			{
+				boundsWarning = true;
+			}
+
+			maxY = Math.max(maxY, node.height);
+		}
+		else if (node.isEdge())
+		{
+			// The width is the number of additional parallel edges
+			// time the parallel edge spacing
+			var numEdges = 1;
+
+			if (node.edges != null)
+			{
+				numEdges = node.edges.length;
+			}
+			else
+			{
+				mxLog.warn('edge.edges is null');
+			}
+
+			node.width = (numEdges - 1) * this.parallelEdgeSpacing;
+		}
+
+		// Set the initial x-value as being the best result so far
+		localX += node.width / 2.0;
+		node.setX(rankValue, localX);
+		node.setGeneralPurposeVariable(rankValue, localX);
+		localX += node.width / 2.0;
+		localX += this.intraCellSpacing;
+	}
+
+	if (boundsWarning == true)
+	{
+		mxLog.warn('At least one cell has no bounds');
+	}
+};
+
+/**
+ * Function: calculateWidestRank
+ * 
+ * Calculates the width rank in the hierarchy. Also set the y value of each
+ * rank whilst performing the calculation
+ * 
+ * Parameters:
+ * 
+ * graph - the facade describing the input graph
+ * model - an internal model of the hierarchical layout
+ */
+mxCoordinateAssignment.prototype.calculateWidestRank = function(graph, model)
+{
+	// Starting y co-ordinate
+	var y = -this.interRankCellSpacing;
+	
+	// Track the widest cell on the last rank since the y
+	// difference depends on it
+	var lastRankMaxCellHeight = 0.0;
+	this.rankWidths = [];
+	this.rankY = [];
+
+	for (var rankValue = model.maxRank; rankValue >= 0; rankValue--)
+	{
+		// Keep track of the widest cell on this rank
+		var maxCellHeight = 0.0;
+		var rank = model.ranks[rankValue];
+		var localX = this.initialX;
+
+		// Store whether or not any of the cells' bounds were unavailable so
+		// to only issue the warning once for all cells
+		var boundsWarning = false;
+		
+		for (var i = 0; i < rank.length; i++)
+		{
+			var node = rank[i];
+
+			if (node.isVertex())
+			{
+				var bounds = this.layout.getVertexBounds(node.cell);
+
+				if (bounds != null)
+				{
+					if (this.orientation == mxConstants.DIRECTION_NORTH ||
+						this.orientation == mxConstants.DIRECTION_SOUTH)
+					{
+						node.width = bounds.width;
+						node.height = bounds.height;
+					}
+					else
+					{
+						node.width = bounds.height;
+						node.height = bounds.width;
+					}
+				}
+				else
+				{
+					boundsWarning = true;
+				}
+
+				maxCellHeight = Math.max(maxCellHeight, node.height);
+			}
+			else if (node.isEdge())
+			{
+				// The width is the number of additional parallel edges
+				// time the parallel edge spacing
+				var numEdges = 1;
+
+				if (node.edges != null)
+				{
+					numEdges = node.edges.length;
+				}
+				else
+				{
+					mxLog.warn('edge.edges is null');
+				}
+
+				node.width = (numEdges - 1) * this.parallelEdgeSpacing;
+			}
+
+			// Set the initial x-value as being the best result so far
+			localX += node.width / 2.0;
+			node.setX(rankValue, localX);
+			node.setGeneralPurposeVariable(rankValue, localX);
+			localX += node.width / 2.0;
+			localX += this.intraCellSpacing;
+
+			if (localX > this.widestRankValue)
+			{
+				this.widestRankValue = localX;
+				this.widestRank = rankValue;
+			}
+
+			this.rankWidths[rankValue] = localX;
+		}
+
+		if (boundsWarning == true)
+		{
+			mxLog.warn('At least one cell has no bounds');
+		}
+
+		this.rankY[rankValue] = y;
+		var distanceToNextRank = maxCellHeight / 2.0
+				+ lastRankMaxCellHeight / 2.0 + this.interRankCellSpacing;
+		lastRankMaxCellHeight = maxCellHeight;
+
+		if (this.orientation == mxConstants.DIRECTION_NORTH ||
+			this.orientation == mxConstants.DIRECTION_WEST)
+		{
+			y += distanceToNextRank;
+		}
+		else
+		{
+			y -= distanceToNextRank;
+		}
+
+		for (var i = 0; i < rank.length; i++)
+		{
+			var cell = rank[i];
+			cell.setY(rankValue, y);
+		}
+	}
+};
+
+/**
+ * Function: minPath
+ * 
+ * Straightens out chains of virtual nodes where possibleacade to those stored after this layout
+ * processing step has completed.
+ * 
+ * Parameters:
+ *
+ * graph - the facade describing the input graph
+ * model - an internal model of the hierarchical layout
+ */
+mxCoordinateAssignment.prototype.minPath = function(graph, model)
+{
+	// Work down and up each edge with at least 2 control points
+	// trying to straighten each one out. If the same number of
+	// straight segments are formed in both directions, the 
+	// preferred direction used is the one where the final
+	// control points have the least offset from the connectable 
+	// region of the terminating vertices
+	var edges = model.edgeMapper.getValues();
+	
+	for (var j = 0; j < edges.length; j++)
+	{
+		var cell = edges[j];
+		
+		if (cell.maxRank - cell.minRank - 1 < 1)
+		{
+			continue;
+		}
+
+		// At least two virtual nodes in the edge
+		// Check first whether the edge is already straight
+		var referenceX = cell
+				.getGeneralPurposeVariable(cell.minRank + 1);
+		var edgeStraight = true;
+		var refSegCount = 0;
+		
+		for (var i = cell.minRank + 2; i < cell.maxRank; i++)
+		{
+			var x = cell.getGeneralPurposeVariable(i);
+
+			if (referenceX != x)
+			{
+				edgeStraight = false;
+				referenceX = x;
+			}
+			else
+			{
+				refSegCount++;
+			}
+		}
+
+		if (!edgeStraight)
+		{
+			var upSegCount = 0;
+			var downSegCount = 0;
+			var upXPositions = [];
+			var downXPositions = [];
+
+			var currentX = cell.getGeneralPurposeVariable(cell.minRank + 1);
+
+			for (var i = cell.minRank + 1; i < cell.maxRank - 1; i++)
+			{
+				// Attempt to straight out the control point on the
+				// next segment up with the current control point.
+				var nextX = cell.getX(i + 1);
+
+				if (currentX == nextX)
+				{
+					upXPositions[i - cell.minRank - 1] = currentX;
+					upSegCount++;
+				}
+				else if (this.repositionValid(model, cell, i + 1, currentX))
+				{
+					upXPositions[i - cell.minRank - 1] = currentX;
+					upSegCount++;
+					// Leave currentX at same value
+				}
+				else
+				{
+					upXPositions[i - cell.minRank - 1] = nextX;
+					currentX = nextX;
+				}				
+			}
+
+			currentX = cell.getX(i);
+
+			for (var i = cell.maxRank - 1; i > cell.minRank + 1; i--)
+			{
+				// Attempt to straight out the control point on the
+				// next segment down with the current control point.
+				var nextX = cell.getX(i - 1);
+
+				if (currentX == nextX)
+				{
+					downXPositions[i - cell.minRank - 2] = currentX;
+					downSegCount++;
+				}
+				else if (this.repositionValid(model, cell, i - 1, currentX))
+				{
+					downXPositions[i - cell.minRank - 2] = currentX;
+					downSegCount++;
+					// Leave currentX at same value
+				}
+				else
+				{
+					downXPositions[i - cell.minRank - 2] = cell.getX(i-1);
+					currentX = nextX;
+				}
+			}
+
+			if (downSegCount > refSegCount || upSegCount > refSegCount)
+			{
+				if (downSegCount >= upSegCount)
+				{
+					// Apply down calculation values
+					for (var i = cell.maxRank - 2; i > cell.minRank; i--)
+					{
+						cell.setX(i, downXPositions[i - cell.minRank - 1]);
+					}
+				}
+				else if (upSegCount > downSegCount)
+				{
+					// Apply up calculation values
+					for (var i = cell.minRank + 2; i < cell.maxRank; i++)
+					{
+						cell.setX(i, upXPositions[i - cell.minRank - 2]);
+					}
+				}
+				else
+				{
+					// Neither direction provided a favourable result
+					// But both calculations are better than the
+					// existing solution, so apply the one with minimal
+					// offset to attached vertices at either end.
+				}
+			}
+		}
+	}
+};
+
+/**
+ * Function: repositionValid
+ * 
+ * Determines whether or not a node may be moved to the specified x 
+ * position on the specified rank
+ * 
+ * Parameters:
+ *
+ * model - the layout model
+ * cell - the cell being analysed
+ * rank - the layer of the cell
+ * position - the x position being sought
+ */
+mxCoordinateAssignment.prototype.repositionValid = function(model, cell, rank, position)
+{
+	var rankArray = model.ranks[rank];
+	var rankIndex = -1;
+
+	for (var i = 0; i < rankArray.length; i++)
+	{
+		if (cell == rankArray[i])
+		{
+			rankIndex = i;
+			break;
+		}
+	}
+
+	if (rankIndex < 0)
+	{
+		return false;
+	}
+
+	var currentX = cell.getGeneralPurposeVariable(rank);
+
+	if (position < currentX)
+	{
+		// Trying to move node to the left.
+		if (rankIndex == 0)
+		{
+			// Left-most node, can move anywhere
+			return true;
+		}
+
+		var leftCell = rankArray[rankIndex - 1];
+		var leftLimit = leftCell.getGeneralPurposeVariable(rank);
+		leftLimit = leftLimit + leftCell.width / 2
+				+ this.intraCellSpacing + cell.width / 2;
+
+		if (leftLimit <= position)
+		{
+			return true;
+		}
+		else
+		{
+			return false;
+		}
+	}
+	else if (position > currentX)
+	{
+		// Trying to move node to the right.
+		if (rankIndex == rankArray.length - 1)
+		{
+			// Right-most node, can move anywhere
+			return true;
+		}
+
+		var rightCell = rankArray[rankIndex + 1];
+		var rightLimit = rightCell.getGeneralPurposeVariable(rank);
+		rightLimit = rightLimit - rightCell.width / 2
+				- this.intraCellSpacing - cell.width / 2;
+
+		if (rightLimit >= position)
+		{
+			return true;
+		}
+		else
+		{
+			return false;
+		}
+	}
+
+	return true;
+};
+
+/**
+ * Function: setCellLocations
+ * 
+ * Sets the cell locations in the facade to those stored after this layout
+ * processing step has completed.
+ * 
+ * Parameters:
+ *
+ * graph - the input graph
+ * model - the layout model
+ */
+mxCoordinateAssignment.prototype.setCellLocations = function(graph, model)
+{
+	this.rankTopY = [];
+	this.rankBottomY = [];
+
+	for (var i = 0; i < model.ranks.length; i++)
+	{
+		this.rankTopY[i] = Number.MAX_VALUE;
+		this.rankBottomY[i] = -Number.MAX_VALUE;
+	}
+	
+	var vertices = model.vertexMapper.getValues();
+
+	// Process vertices all first, since they define the lower and 
+	// limits of each rank. Between these limits lie the channels
+	// where the edges can be routed across the graph
+
+	for (var i = 0; i < vertices.length; i++)
+	{
+		this.setVertexLocation(vertices[i]);
+	}
+	
+	// Post process edge styles. Needs the vertex locations set for initial
+	// values of the top and bottoms of each rank
+	if (this.layout.edgeStyle == mxHierarchicalEdgeStyle.ORTHOGONAL
+			|| this.layout.edgeStyle == mxHierarchicalEdgeStyle.POLYLINE
+			|| this.layout.edgeStyle == mxHierarchicalEdgeStyle.CURVE)
+	{
+		this.localEdgeProcessing(model);
+	}
+
+	var edges = model.edgeMapper.getValues();
+
+	for (var i = 0; i < edges.length; i++)
+	{
+		this.setEdgePosition(edges[i]);
+	}
+};
+
+/**
+ * Function: localEdgeProcessing
+ * 
+ * Separates the x position of edges as they connect to vertices
+ * 
+ * Parameters:
+ *
+ * model - the layout model
+ */
+mxCoordinateAssignment.prototype.localEdgeProcessing = function(model)
+{
+	// Iterate through each vertex, look at the edges connected in
+	// both directions.
+	for (var rankIndex = 0; rankIndex < model.ranks.length; rankIndex++)
+	{
+		var rank = model.ranks[rankIndex];
+
+		for (var cellIndex = 0; cellIndex < rank.length; cellIndex++)
+		{
+			var cell = rank[cellIndex];
+
+			if (cell.isVertex())
+			{
+				var currentCells = cell.getPreviousLayerConnectedCells(rankIndex);
+
+				var currentRank = rankIndex - 1;
+
+				// Two loops, last connected cells, and next
+				for (var k = 0; k < 2; k++)
+				{
+					if (currentRank > -1
+							&& currentRank < model.ranks.length
+							&& currentCells != null
+							&& currentCells.length > 0)
+					{
+						var sortedCells = [];
+
+						for (var j = 0; j < currentCells.length; j++)
+						{
+							var sorter = new WeightedCellSorter(
+									currentCells[j], currentCells[j].getX(currentRank));
+							sortedCells.push(sorter);
+						}
+
+						sortedCells.sort(WeightedCellSorter.prototype.compare);
+
+						var leftLimit = cell.x[0] - cell.width / 2;
+						var rightLimit = leftLimit + cell.width;
+
+						// Connected edge count starts at 1 to allow for buffer
+						// with edge of vertex
+						var connectedEdgeCount = 0;
+						var connectedEdgeGroupCount = 0;
+						var connectedEdges = [];
+						// Calculate width requirements for all connected edges
+						for (var j = 0; j < sortedCells.length; j++)
+						{
+							var innerCell = sortedCells[j].cell;
+							var connections;
+
+							if (innerCell.isVertex())
+							{
+								// Get the connecting edge
+								if (k == 0)
+								{
+									connections = cell.connectsAsSource;
+
+								}
+								else
+								{
+									connections = cell.connectsAsTarget;
+								}
+
+								for (var connIndex = 0; connIndex < connections.length; connIndex++)
+								{
+									if (connections[connIndex].source == innerCell
+											|| connections[connIndex].target == innerCell)
+									{
+										connectedEdgeCount += connections[connIndex].edges
+												.length;
+										connectedEdgeGroupCount++;
+
+										connectedEdges.push(connections[connIndex]);
+									}
+								}
+							}
+							else
+							{
+								connectedEdgeCount += innerCell.edges.length;
+								connectedEdgeGroupCount++;
+								connectedEdges.push(innerCell);
+							}
+						}
+
+						var requiredWidth = (connectedEdgeCount + 1)
+								* this.prefHozEdgeSep;
+
+						// Add a buffer on the edges of the vertex if the edge count allows
+						if (cell.width > requiredWidth
+								+ (2 * this.prefHozEdgeSep))
+						{
+							leftLimit += this.prefHozEdgeSep;
+							rightLimit -= this.prefHozEdgeSep;
+						}
+
+						var availableWidth = rightLimit - leftLimit;
+						var edgeSpacing = availableWidth / connectedEdgeCount;
+
+						var currentX = leftLimit + edgeSpacing / 2.0;
+						var currentYOffset = this.minEdgeJetty - this.prefVertEdgeOff;
+						var maxYOffset = 0;
+
+						for (var j = 0; j < connectedEdges.length; j++)
+						{
+							var numActualEdges = connectedEdges[j].edges
+									.length;
+							var pos = this.jettyPositions[connectedEdges[j].ids[0]];
+							
+							if (pos == null)
+							{
+								pos = [];
+								this.jettyPositions[connectedEdges[j].ids[0]] = pos;
+							}
+
+							if (j < connectedEdgeCount / 2)
+							{
+								currentYOffset += this.prefVertEdgeOff;
+							}
+							else if (j > connectedEdgeCount / 2)
+							{
+								currentYOffset -= this.prefVertEdgeOff;
+							}
+							// Ignore the case if equals, this means the second of 2
+							// jettys with the same y (even number of edges)
+
+							for (var m = 0; m < numActualEdges; m++)
+							{
+								pos[m * 4 + k * 2] = currentX;
+								currentX += edgeSpacing;
+								pos[m * 4 + k * 2 + 1] = currentYOffset;
+							}
+							
+							maxYOffset = Math.max(maxYOffset,
+									currentYOffset);
+						}
+					}
+
+					currentCells = cell.getNextLayerConnectedCells(rankIndex);
+
+					currentRank = rankIndex + 1;
+				}
+			}
+		}
+	}
+};
+
+/**
+ * Function: setEdgePosition
+ * 
+ * Fixes the control points
+ */
+mxCoordinateAssignment.prototype.setEdgePosition = function(cell)
+{
+	// For parallel edges we need to seperate out the points a
+	// little
+	var offsetX = 0;
+	// Only set the edge control points once
+
+	if (cell.temp[0] != 101207)
+	{
+		var maxRank = cell.maxRank;
+		var minRank = cell.minRank;
+		
+		if (maxRank == minRank)
+		{
+			maxRank = cell.source.maxRank;
+			minRank = cell.target.minRank;
+		}
+		
+		var parallelEdgeCount = 0;
+		var jettys = this.jettyPositions[cell.ids[0]];
+
+		var source = cell.isReversed ? cell.target.cell : cell.source.cell;
+		var graph = this.layout.graph;
+		var layoutReversed = this.orientation == mxConstants.DIRECTION_EAST
+				|| this.orientation == mxConstants.DIRECTION_SOUTH;
+
+		for (var i = 0; i < cell.edges.length; i++)
+		{
+			var realEdge = cell.edges[i];
+			var realSource = this.layout.getVisibleTerminal(realEdge, true);
+
+			//List oldPoints = graph.getPoints(realEdge);
+			var newPoints = [];
+
+			// Single length reversed edges end up with the jettys in the wrong
+			// places. Since single length edges only have jettys, not segment
+			// control points, we just say the edge isn't reversed in this section
+			var reversed = cell.isReversed;
+			
+			if (realSource != source)
+			{
+				// The real edges include all core model edges and these can go
+				// in both directions. If the source of the hierarchical model edge
+				// isn't the source of the specific real edge in this iteration
+				// treat if as reversed
+				reversed = !reversed;
+			}
+
+			// First jetty of edge
+			if (jettys != null)
+			{
+				var arrayOffset = reversed ? 2 : 0;
+				var y = reversed ?
+						(layoutReversed ? this.rankBottomY[minRank] : this.rankTopY[minRank]) :
+							(layoutReversed ? this.rankTopY[maxRank] : this.rankBottomY[maxRank]);
+				var jetty = jettys[parallelEdgeCount * 4 + 1 + arrayOffset];
+				
+				if (reversed != layoutReversed)
+				{
+					jetty = -jetty;
+				}
+				
+				y += jetty;
+				var x = jettys[parallelEdgeCount * 4 + arrayOffset];
+				
+				var modelSource = graph.model.getTerminal(realEdge, true);
+
+				if (this.layout.isPort(modelSource) && graph.model.getParent(modelSource) == realSource)
+				{
+					var state = graph.view.getState(modelSource);
+					
+					if (state != null)
+					{
+						x = state.x;
+					}
+					else
+					{
+						x = realSource.geometry.x + cell.source.width * modelSource.geometry.x;
+					}
+				}
+
+				if (this.orientation == mxConstants.DIRECTION_NORTH
+						|| this.orientation == mxConstants.DIRECTION_SOUTH)
+				{
+					newPoints.push(new mxPoint(x, y));
+					
+					if (this.layout.edgeStyle == mxHierarchicalEdgeStyle.CURVE)
+					{
+						newPoints.push(new mxPoint(x, y + jetty));
+					}
+				}
+				else
+				{
+					newPoints.push(new mxPoint(y, x));
+					
+					if (this.layout.edgeStyle == mxHierarchicalEdgeStyle.CURVE)
+					{
+						newPoints.push(new mxPoint(y + jetty, x));
+					}
+				}
+			}
+
+			// Declare variables to define loop through edge points and 
+			// change direction if edge is reversed
+
+			var loopStart = cell.x.length - 1;
+			var loopLimit = -1;
+			var loopDelta = -1;
+			var currentRank = cell.maxRank - 1;
+
+			if (reversed)
+			{
+				loopStart = 0;
+				loopLimit = cell.x.length;
+				loopDelta = 1;
+				currentRank = cell.minRank + 1;
+			}
+			// Reversed edges need the points inserted in
+			// reverse order
+			for (var j = loopStart; (cell.maxRank != cell.minRank) && j != loopLimit; j += loopDelta)
+			{
+				// The horizontal position in a vertical layout
+				var positionX = cell.x[j] + offsetX;
+
+				// Work out the vertical positions in a vertical layout
+				// in the edge buffer channels above and below this rank
+				var topChannelY = (this.rankTopY[currentRank] + this.rankBottomY[currentRank + 1]) / 2.0;
+				var bottomChannelY = (this.rankTopY[currentRank - 1] + this.rankBottomY[currentRank]) / 2.0;
+
+				if (reversed)
+				{
+					var tmp = topChannelY;
+					topChannelY = bottomChannelY;
+					bottomChannelY = tmp;
+				}
+
+				if (this.orientation == mxConstants.DIRECTION_NORTH ||
+					this.orientation == mxConstants.DIRECTION_SOUTH)
+				{
+					newPoints.push(new mxPoint(positionX, topChannelY));
+					newPoints.push(new mxPoint(positionX, bottomChannelY));
+				}
+				else
+				{
+					newPoints.push(new mxPoint(topChannelY, positionX));
+					newPoints.push(new mxPoint(bottomChannelY, positionX));
+				}
+
+				this.limitX = Math.max(this.limitX, positionX);
+				currentRank += loopDelta;
+			}
+
+			// Second jetty of edge
+			if (jettys != null)
+			{
+				var arrayOffset = reversed ? 2 : 0;
+				var rankY = reversed ?
+						(layoutReversed ? this.rankTopY[maxRank] : this.rankBottomY[maxRank]) :
+							(layoutReversed ? this.rankBottomY[minRank] : this.rankTopY[minRank]);
+				var jetty = jettys[parallelEdgeCount * 4 + 3 - arrayOffset];
+				
+				if (reversed != layoutReversed)
+				{
+					jetty = -jetty;
+				}
+				var y = rankY - jetty;
+				var x = jettys[parallelEdgeCount * 4 + 2 - arrayOffset];
+				
+				var modelTarget = graph.model.getTerminal(realEdge, false);
+				var realTarget = this.layout.getVisibleTerminal(realEdge, false);
+
+				if (this.layout.isPort(modelTarget) && graph.model.getParent(modelTarget) == realTarget)
+				{
+					var state = graph.view.getState(modelTarget);
+					
+					if (state != null)
+					{
+						x = state.x;
+					}
+					else
+					{
+						x = realTarget.geometry.x + cell.target.width * modelTarget.geometry.x;
+					}
+				}
+
+				if (this.orientation == mxConstants.DIRECTION_NORTH ||
+						this.orientation == mxConstants.DIRECTION_SOUTH)
+				{
+					if (this.layout.edgeStyle == mxHierarchicalEdgeStyle.CURVE)
+					{
+						newPoints.push(new mxPoint(x, y - jetty));
+					}
+
+					newPoints.push(new mxPoint(x, y));
+				}
+				else
+				{
+					if (this.layout.edgeStyle == mxHierarchicalEdgeStyle.CURVE)
+					{
+						newPoints.push(new mxPoint(y - jetty, x));
+					}
+
+					newPoints.push(new mxPoint(y, x));
+				}
+			}
+
+			if (cell.isReversed)
+			{
+				this.processReversedEdge(cell, realEdge);
+			}
+
+			this.layout.setEdgePoints(realEdge, newPoints);
+
+			// Increase offset so next edge is drawn next to
+			// this one
+			if (offsetX == 0.0)
+			{
+				offsetX = this.parallelEdgeSpacing;
+			}
+			else if (offsetX > 0)
+			{
+				offsetX = -offsetX;
+			}
+			else
+			{
+				offsetX = -offsetX + this.parallelEdgeSpacing;
+			}
+			
+			parallelEdgeCount++;
+		}
+
+		cell.temp[0] = 101207;
+	}
+};
+
+
+/**
+ * Function: setVertexLocation
+ * 
+ * Fixes the position of the specified vertex.
+ * 
+ * Parameters:
+ * 
+ * cell - the vertex to position
+ */
+mxCoordinateAssignment.prototype.setVertexLocation = function(cell)
+{
+	var realCell = cell.cell;
+	var positionX = cell.x[0] - cell.width / 2;
+	var positionY = cell.y[0] - cell.height / 2;
+
+	this.rankTopY[cell.minRank] = Math.min(this.rankTopY[cell.minRank], positionY);
+	this.rankBottomY[cell.minRank] = Math.max(this.rankBottomY[cell.minRank],
+			positionY + cell.height);
+
+	if (this.orientation == mxConstants.DIRECTION_NORTH ||
+		this.orientation == mxConstants.DIRECTION_SOUTH)
+	{
+		this.layout.setVertexLocation(realCell, positionX, positionY);
+	}
+	else
+	{
+		this.layout.setVertexLocation(realCell, positionY, positionX);
+	}
+
+	this.limitX = Math.max(this.limitX, positionX + cell.width);
+};
+
+/**
+ * Function: processReversedEdge
+ * 
+ * Hook to add additional processing
+ * 
+ * Parameters:
+ * 
+ * edge - the hierarchical model edge
+ * realEdge - the real edge in the graph
+ */
+mxCoordinateAssignment.prototype.processReversedEdge = function(graph, model)
+{
+	// hook for subclassers
+};
+
+/**
+ * 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/airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/stage/mxHierarchicalLayoutStage.js b/airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/stage/mxHierarchicalLayoutStage.js
new file mode 100644
index 0000000..605846b
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/stage/mxHierarchicalLayoutStage.js
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxHierarchicalLayoutStage
+ * 
+ * The specific layout interface for hierarchical layouts. It adds a
+ * <code>run</code> method with a parameter for the hierarchical layout model
+ * that is shared between the layout stages.
+ * 
+ * Constructor: mxHierarchicalLayoutStage
+ *
+ * Constructs a new hierarchical layout stage.
+ */
+function mxHierarchicalLayoutStage() { };
+
+/**
+ * Function: execute
+ * 
+ * Takes the graph detail and configuration information within the facade
+ * and creates the resulting laid out graph within that facade for further
+ * use.
+ */
+mxHierarchicalLayoutStage.prototype.execute = function(parent) { };
diff --git a/airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/stage/mxMedianHybridCrossingReduction.js b/airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/stage/mxMedianHybridCrossingReduction.js
new file mode 100644
index 0000000..139079d
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/stage/mxMedianHybridCrossingReduction.js
@@ -0,0 +1,675 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxMedianHybridCrossingReduction
+ * 
+ * Sets the horizontal locations of node and edge dummy nodes on each layer.
+ * Uses median down and up weighings as well heuristic to straighten edges as
+ * far as possible.
+ * 
+ * Constructor: mxMedianHybridCrossingReduction
+ *
+ * Creates a coordinate assignment.
+ * 
+ * Arguments:
+ * 
+ * intraCellSpacing - the minimum buffer between cells on the same rank
+ * interRankCellSpacing - the minimum distance between cells on adjacent ranks
+ * orientation - the position of the root node(s) relative to the graph
+ * initialX - the leftmost coordinate node placement starts at
+ */
+function mxMedianHybridCrossingReduction(layout)
+{
+	this.layout = layout;
+};
+
+/**
+ * Extends mxMedianHybridCrossingReduction.
+ */
+mxMedianHybridCrossingReduction.prototype = new mxHierarchicalLayoutStage();
+mxMedianHybridCrossingReduction.prototype.constructor = mxMedianHybridCrossingReduction;
+
+/**
+ * Variable: layout
+ * 
+ * Reference to the enclosing <mxHierarchicalLayout>.
+ */
+mxMedianHybridCrossingReduction.prototype.layout = null;
+
+/**
+ * Variable: maxIterations
+ * 
+ * The maximum number of iterations to perform whilst reducing edge
+ * crossings. Default is 24.
+ */
+mxMedianHybridCrossingReduction.prototype.maxIterations = 24;
+
+/**
+ * Variable: nestedBestRanks
+ * 
+ * Stores each rank as a collection of cells in the best order found for
+ * each layer so far
+ */
+mxMedianHybridCrossingReduction.prototype.nestedBestRanks = null;
+
+/**
+ * Variable: currentBestCrossings
+ * 
+ * The total number of crossings found in the best configuration so far
+ */
+mxMedianHybridCrossingReduction.prototype.currentBestCrossings = 0;
+
+/**
+ * Variable: iterationsWithoutImprovement
+ * 
+ * The total number of crossings found in the best configuration so far
+ */
+mxMedianHybridCrossingReduction.prototype.iterationsWithoutImprovement = 0;
+
+/**
+ * Variable: maxNoImprovementIterations
+ * 
+ * The total number of crossings found in the best configuration so far
+ */
+mxMedianHybridCrossingReduction.prototype.maxNoImprovementIterations = 2;
+
+/**
+ * Function: execute
+ * 
+ * Performs a vertex ordering within ranks as described by Gansner et al
+ * 1993
+ */
+mxMedianHybridCrossingReduction.prototype.execute = function(parent)
+{
+	var model = this.layout.getModel();
+
+	// Stores initial ordering as being the best one found so far
+	this.nestedBestRanks = [];
+	
+	for (var i = 0; i < model.ranks.length; i++)
+	{
+		this.nestedBestRanks[i] = model.ranks[i].slice();
+	}
+
+	var iterationsWithoutImprovement = 0;
+	var currentBestCrossings = this.calculateCrossings(model);
+
+	for (var i = 0; i < this.maxIterations &&
+		iterationsWithoutImprovement < this.maxNoImprovementIterations; i++)
+	{
+		this.weightedMedian(i, model);
+		this.transpose(i, model);
+		var candidateCrossings = this.calculateCrossings(model);
+
+		if (candidateCrossings < currentBestCrossings)
+		{
+			currentBestCrossings = candidateCrossings;
+			iterationsWithoutImprovement = 0;
+
+			// Store the current rankings as the best ones
+			for (var j = 0; j < this.nestedBestRanks.length; j++)
+			{
+				var rank = model.ranks[j];
+
+				for (var k = 0; k < rank.length; k++)
+				{
+					var cell = rank[k];
+					this.nestedBestRanks[j][cell.getGeneralPurposeVariable(j)] = cell;
+				}
+			}
+		}
+		else
+		{
+			// Increase count of iterations where we haven't improved the
+			// layout
+			iterationsWithoutImprovement++;
+
+			// Restore the best values to the cells
+			for (var j = 0; j < this.nestedBestRanks.length; j++)
+			{
+				var rank = model.ranks[j];
+				
+				for (var k = 0; k < rank.length; k++)
+				{
+					var cell = rank[k];
+					cell.setGeneralPurposeVariable(j, k);
+				}
+			}
+		}
+		
+		if (currentBestCrossings == 0)
+		{
+			// Do nothing further
+			break;
+		}
+	}
+
+	// Store the best rankings but in the model
+	var ranks = [];
+	var rankList = [];
+
+	for (var i = 0; i < model.maxRank + 1; i++)
+	{
+		rankList[i] = [];
+		ranks[i] = rankList[i];
+	}
+
+	for (var i = 0; i < this.nestedBestRanks.length; i++)
+	{
+		for (var j = 0; j < this.nestedBestRanks[i].length; j++)
+		{
+			rankList[i].push(this.nestedBestRanks[i][j]);
+		}
+	}
+
+	model.ranks = ranks;
+};
+
+
+/**
+ * Function: calculateCrossings
+ * 
+ * Calculates the total number of edge crossing in the current graph.
+ * Returns the current number of edge crossings in the hierarchy graph
+ * model in the current candidate layout
+ * 
+ * Parameters:
+ * 
+ * model - the internal model describing the hierarchy
+ */
+mxMedianHybridCrossingReduction.prototype.calculateCrossings = function(model)
+{
+	var numRanks = model.ranks.length;
+	var totalCrossings = 0;
+
+	for (var i = 1; i < numRanks; i++)
+	{
+		totalCrossings += this.calculateRankCrossing(i, model);
+	}
+	
+	return totalCrossings;
+};
+
+/**
+ * Function: calculateRankCrossing
+ * 
+ * Calculates the number of edges crossings between the specified rank and
+ * the rank below it. Returns the number of edges crossings with the rank
+ * beneath
+ * 
+ * Parameters:
+ * 
+ * i -  the topmost rank of the pair ( higher rank value )
+ * model - the internal model describing the hierarchy
+ */
+mxMedianHybridCrossingReduction.prototype.calculateRankCrossing = function(i, model)
+{
+	var totalCrossings = 0;
+	var rank = model.ranks[i];
+	var previousRank = model.ranks[i - 1];
+
+	var tmpIndices = [];
+
+	// Iterate over the top rank and fill in the connection information
+	for (var j = 0; j < rank.length; j++)
+	{
+		var node = rank[j];
+		var rankPosition = node.getGeneralPurposeVariable(i);
+		var connectedCells = node.getPreviousLayerConnectedCells(i);
+		var nodeIndices = [];
+
+		for (var k = 0; k < connectedCells.length; k++)
+		{
+			var connectedNode = connectedCells[k];
+			var otherCellRankPosition = connectedNode.getGeneralPurposeVariable(i - 1);
+			nodeIndices.push(otherCellRankPosition);
+		}
+		
+		nodeIndices.sort(function(x, y) { return x - y; });
+		tmpIndices[rankPosition] = nodeIndices;
+	}
+	
+	var indices = [];
+
+	for (var j = 0; j < tmpIndices.length; j++)
+	{
+		indices = indices.concat(tmpIndices[j]);
+	}
+
+	var firstIndex = 1;
+	
+	while (firstIndex < previousRank.length)
+	{
+		firstIndex <<= 1;
+	}
+
+	var treeSize = 2 * firstIndex - 1;
+	firstIndex -= 1;
+
+	var tree = [];
+	
+	for (var j = 0; j < treeSize; ++j)
+	{
+		tree[j] = 0;
+	}
+
+	for (var j = 0; j < indices.length; j++)
+	{
+		var index = indices[j];
+	    var treeIndex = index + firstIndex;
+	    ++tree[treeIndex];
+	    
+	    while (treeIndex > 0)
+	    {
+	    	if (treeIndex % 2)
+	    	{
+	    		totalCrossings += tree[treeIndex + 1];
+	    	}
+	      
+	    	treeIndex = (treeIndex - 1) >> 1;
+	    	++tree[treeIndex];
+	    }
+	}
+
+	return totalCrossings;
+};
+
+/**
+ * Function: transpose
+ * 
+ * Takes each possible adjacent cell pair on each rank and checks if
+ * swapping them around reduces the number of crossing
+ * 
+ * Parameters:
+ * 
+ * mainLoopIteration - the iteration number of the main loop
+ * model - the internal model describing the hierarchy
+ */
+mxMedianHybridCrossingReduction.prototype.transpose = function(mainLoopIteration, model)
+{
+	var improved = true;
+
+	// Track the number of iterations in case of looping
+	var count = 0;
+	var maxCount = 10;
+	while (improved && count++ < maxCount)
+	{
+		// On certain iterations allow allow swapping of cell pairs with
+		// equal edge crossings switched or not switched. This help to
+		// nudge a stuck layout into a lower crossing total.
+		var nudge = mainLoopIteration % 2 == 1 && count % 2 == 1;
+		improved = false;
+		
+		for (var i = 0; i < model.ranks.length; i++)
+		{
+			var rank = model.ranks[i];
+			var orderedCells = [];
+			
+			for (var j = 0; j < rank.length; j++)
+			{
+				var cell = rank[j];
+				var tempRank = cell.getGeneralPurposeVariable(i);
+				
+				// FIXME: Workaround to avoid negative tempRanks
+				if (tempRank < 0)
+				{
+					tempRank = j;
+				}
+				orderedCells[tempRank] = cell;
+			}
+			
+			var leftCellAboveConnections = null;
+			var leftCellBelowConnections = null;
+			var rightCellAboveConnections = null;
+			var rightCellBelowConnections = null;
+			
+			var leftAbovePositions = null;
+			var leftBelowPositions = null;
+			var rightAbovePositions = null;
+			var rightBelowPositions = null;
+			
+			var leftCell = null;
+			var rightCell = null;
+
+			for (var j = 0; j < (rank.length - 1); j++)
+			{
+				// For each intra-rank adjacent pair of cells
+				// see if swapping them around would reduce the
+				// number of edges crossing they cause in total
+				// On every cell pair except the first on each rank, we
+				// can save processing using the previous values for the
+				// right cell on the new left cell
+				if (j == 0)
+				{
+					leftCell = orderedCells[j];
+					leftCellAboveConnections = leftCell
+							.getNextLayerConnectedCells(i);
+					leftCellBelowConnections = leftCell
+							.getPreviousLayerConnectedCells(i);
+					leftAbovePositions = [];
+					leftBelowPositions = [];
+					
+					for (var k = 0; k < leftCellAboveConnections.length; k++)
+					{
+						leftAbovePositions[k] = leftCellAboveConnections[k].getGeneralPurposeVariable(i + 1);
+					}
+					
+					for (var k = 0; k < leftCellBelowConnections.length; k++)
+					{
+						leftBelowPositions[k] = leftCellBelowConnections[k].getGeneralPurposeVariable(i - 1);
+					}
+				}
+				else
+				{
+					leftCellAboveConnections = rightCellAboveConnections;
+					leftCellBelowConnections = rightCellBelowConnections;
+					leftAbovePositions = rightAbovePositions;
+					leftBelowPositions = rightBelowPositions;
+					leftCell = rightCell;
+				}
+				
+				rightCell = orderedCells[j + 1];
+				rightCellAboveConnections = rightCell
+						.getNextLayerConnectedCells(i);
+				rightCellBelowConnections = rightCell
+						.getPreviousLayerConnectedCells(i);
+
+				rightAbovePositions = [];
+				rightBelowPositions = [];
+
+				for (var k = 0; k < rightCellAboveConnections.length; k++)
+				{
+					rightAbovePositions[k] = rightCellAboveConnections[k].getGeneralPurposeVariable(i + 1);
+				}
+				
+				for (var k = 0; k < rightCellBelowConnections.length; k++)
+				{
+					rightBelowPositions[k] = rightCellBelowConnections[k].getGeneralPurposeVariable(i - 1);
+				}
+
+				var totalCurrentCrossings = 0;
+				var totalSwitchedCrossings = 0;
+				
+				for (var k = 0; k < leftAbovePositions.length; k++)
+				{
+					for (var ik = 0; ik < rightAbovePositions.length; ik++)
+					{
+						if (leftAbovePositions[k] > rightAbovePositions[ik])
+						{
+							totalCurrentCrossings++;
+						}
+
+						if (leftAbovePositions[k] < rightAbovePositions[ik])
+						{
+							totalSwitchedCrossings++;
+						}
+					}
+				}
+				
+				for (var k = 0; k < leftBelowPositions.length; k++)
+				{
+					for (var ik = 0; ik < rightBelowPositions.length; ik++)
+					{
+						if (leftBelowPositions[k] > rightBelowPositions[ik])
+						{
+							totalCurrentCrossings++;
+						}
+
+						if (leftBelowPositions[k] < rightBelowPositions[ik])
+						{
+							totalSwitchedCrossings++;
+						}
+					}
+				}
+				
+				if ((totalSwitchedCrossings < totalCurrentCrossings) ||
+					(totalSwitchedCrossings == totalCurrentCrossings &&
+					nudge))
+				{
+					var temp = leftCell.getGeneralPurposeVariable(i);
+					leftCell.setGeneralPurposeVariable(i, rightCell
+							.getGeneralPurposeVariable(i));
+					rightCell.setGeneralPurposeVariable(i, temp);
+
+					// With this pair exchanged we have to switch all of
+					// values for the left cell to the right cell so the
+					// next iteration for this rank uses it as the left
+					// cell again
+					rightCellAboveConnections = leftCellAboveConnections;
+					rightCellBelowConnections = leftCellBelowConnections;
+					rightAbovePositions = leftAbovePositions;
+					rightBelowPositions = leftBelowPositions;
+					rightCell = leftCell;
+					
+					if (!nudge)
+					{
+						// Don't count nudges as improvement or we'll end
+						// up stuck in two combinations and not finishing
+						// as early as we should
+						improved = true;
+					}
+				}
+			}
+		}
+	}
+};
+
+/**
+ * Function: weightedMedian
+ * 
+ * Sweeps up or down the layout attempting to minimise the median placement
+ * of connected cells on adjacent ranks
+ * 
+ * Parameters:
+ * 
+ * iteration - the iteration number of the main loop
+ * model - the internal model describing the hierarchy
+ */
+mxMedianHybridCrossingReduction.prototype.weightedMedian = function(iteration, model)
+{
+	// Reverse sweep direction each time through this method
+	var downwardSweep = (iteration % 2 == 0);
+	if (downwardSweep)
+	{
+		for (var j = model.maxRank - 1; j >= 0; j--)
+		{
+			this.medianRank(j, downwardSweep);
+		}
+	}
+	else
+	{
+		for (var j = 1; j < model.maxRank; j++)
+		{
+			this.medianRank(j, downwardSweep);
+		}
+	}
+};
+
+/**
+ * Function: medianRank
+ * 
+ * Attempts to minimise the median placement of connected cells on this rank
+ * and one of the adjacent ranks
+ * 
+ * Parameters:
+ * 
+ * rankValue - the layer number of this rank
+ * downwardSweep - whether or not this is a downward sweep through the graph
+ */
+mxMedianHybridCrossingReduction.prototype.medianRank = function(rankValue, downwardSweep)
+{
+	var numCellsForRank = this.nestedBestRanks[rankValue].length;
+	var medianValues = [];
+	var reservedPositions = [];
+
+	for (var i = 0; i < numCellsForRank; i++)
+	{
+		var cell = this.nestedBestRanks[rankValue][i];
+		var sorterEntry = new MedianCellSorter();
+		sorterEntry.cell = cell;
+
+		// Flip whether or not equal medians are flipped on up and down
+		// sweeps
+		// TODO re-implement some kind of nudge
+		// medianValues[i].nudge = !downwardSweep;
+		var nextLevelConnectedCells;
+		
+		if (downwardSweep)
+		{
+			nextLevelConnectedCells = cell
+					.getNextLayerConnectedCells(rankValue);
+		}
+		else
+		{
+			nextLevelConnectedCells = cell
+					.getPreviousLayerConnectedCells(rankValue);
+		}
+		
+		var nextRankValue;
+		
+		if (downwardSweep)
+		{
+			nextRankValue = rankValue + 1;
+		}
+		else
+		{
+			nextRankValue = rankValue - 1;
+		}
+
+		if (nextLevelConnectedCells != null
+				&& nextLevelConnectedCells.length != 0)
+		{
+			sorterEntry.medianValue = this.medianValue(
+					nextLevelConnectedCells, nextRankValue);
+			medianValues.push(sorterEntry);
+		}
+		else
+		{
+			// Nodes with no adjacent vertices are flagged in the reserved array
+			// to indicate they should be left in their current position.
+			reservedPositions[cell.getGeneralPurposeVariable(rankValue)] = true;
+		}
+	}
+	
+	medianValues.sort(MedianCellSorter.prototype.compare);
+	
+	// Set the new position of each node within the rank using
+	// its temp variable
+	for (var i = 0; i < numCellsForRank; i++)
+	{
+		if (reservedPositions[i] == null)
+		{
+			var cell = medianValues.shift().cell;
+			cell.setGeneralPurposeVariable(rankValue, i);
+		}
+	}
+};
+
+/**
+ * Function: medianValue
+ * 
+ * Calculates the median rank order positioning for the specified cell using
+ * the connected cells on the specified rank. Returns the median rank
+ * ordering value of the connected cells
+ * 
+ * Parameters:
+ * 
+ * connectedCells - the cells on the specified rank connected to the
+ * specified cell
+ * rankValue - the rank that the connected cell lie upon
+ */
+mxMedianHybridCrossingReduction.prototype.medianValue = function(connectedCells, rankValue)
+{
+	var medianValues = [];
+	var arrayCount = 0;
+	
+	for (var i = 0; i < connectedCells.length; i++)
+	{
+		var cell = connectedCells[i];
+		medianValues[arrayCount++] = cell.getGeneralPurposeVariable(rankValue);
+	}
+
+	// Sort() sorts lexicographically by default (i.e. 11 before 9) so force
+	// numerical order sort
+	medianValues.sort(function(a,b){return a - b;});
+	
+	if (arrayCount % 2 == 1)
+	{
+		// For odd numbers of adjacent vertices return the median
+		return medianValues[Math.floor(arrayCount / 2)];
+	}
+	else if (arrayCount == 2)
+	{
+		return ((medianValues[0] + medianValues[1]) / 2.0);
+	}
+	else
+	{
+		var medianPoint = arrayCount / 2;
+		var leftMedian = medianValues[medianPoint - 1] - medianValues[0];
+		var rightMedian = medianValues[arrayCount - 1]
+				- medianValues[medianPoint];
+
+		return (medianValues[medianPoint - 1] * rightMedian + medianValues[medianPoint]
+				* leftMedian)
+				/ (leftMedian + rightMedian);
+	}
+};
+
+/**
+ * Class: MedianCellSorter
+ * 
+ * A utility class used to track cells whilst sorting occurs on the median
+ * values. Does not violate (x.compareTo(y)==0) == (x.equals(y))
+ *
+ * Constructor: MedianCellSorter
+ * 
+ * Constructs a new median cell sorter.
+ */
+function MedianCellSorter()
+{
+	// empty
+};
+
+/**
+ * Variable: medianValue
+ * 
+ * The weighted value of the cell stored.
+ */
+MedianCellSorter.prototype.medianValue = 0;
+
+/**
+ * Variable: cell
+ * 
+ * The cell whose median value is being calculated
+ */
+MedianCellSorter.prototype.cell = false;
+
+/**
+ * Function: compare
+ * 
+ * Compares two MedianCellSorters.
+ */
+MedianCellSorter.prototype.compare = function(a, b)
+{
+	if (a != null && b != null)
+	{
+		if (b.medianValue > a.medianValue)
+		{
+			return -1;
+		}
+		else if (b.medianValue < a.medianValue)
+		{
+			return 1;
+		}
+		else
+		{
+			return 0;
+		}
+	}
+	else
+	{
+		return 0;
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/stage/mxMinimumCycleRemover.js b/airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/stage/mxMinimumCycleRemover.js
new file mode 100644
index 0000000..7046755
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/stage/mxMinimumCycleRemover.js
@@ -0,0 +1,108 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxMinimumCycleRemover
+ * 
+ * An implementation of the first stage of the Sugiyama layout. Straightforward
+ * longest path calculation of layer assignment
+ * 
+ * Constructor: mxMinimumCycleRemover
+ *
+ * Creates a cycle remover for the given internal model.
+ */
+function mxMinimumCycleRemover(layout)
+{
+	this.layout = layout;
+};
+
+/**
+ * Extends mxHierarchicalLayoutStage.
+ */
+mxMinimumCycleRemover.prototype = new mxHierarchicalLayoutStage();
+mxMinimumCycleRemover.prototype.constructor = mxMinimumCycleRemover;
+
+/**
+ * Variable: layout
+ * 
+ * Reference to the enclosing <mxHierarchicalLayout>.
+ */
+mxMinimumCycleRemover.prototype.layout = null;
+
+/**
+ * Function: execute
+ * 
+ * Takes the graph detail and configuration information within the facade
+ * and creates the resulting laid out graph within that facade for further
+ * use.
+ */
+mxMinimumCycleRemover.prototype.execute = function(parent)
+{
+	var model = this.layout.getModel();
+	var seenNodes = new Object();
+	var unseenNodesArray = model.vertexMapper.getValues();
+	var unseenNodes = new Object();
+	
+	for (var i = 0; i < unseenNodesArray.length; i++)
+	{
+		unseenNodes[unseenNodesArray[i].id] = unseenNodesArray[i];
+	}
+	
+	// Perform a dfs through the internal model. If a cycle is found,
+	// reverse it.
+	var rootsArray = null;
+	
+	if (model.roots != null)
+	{
+		var modelRoots = model.roots;
+		rootsArray = [];
+		
+		for (var i = 0; i < modelRoots.length; i++)
+		{
+			rootsArray[i] = model.vertexMapper.get(modelRoots[i]);
+		}
+	}
+
+	model.visit(function(parent, node, connectingEdge, layer, seen)
+	{
+		// Check if the cell is in it's own ancestor list, if so
+		// invert the connecting edge and reverse the target/source
+		// relationship to that edge in the parent and the cell
+		if (node.isAncestor(parent))
+		{
+			connectingEdge.invert();
+			mxUtils.remove(connectingEdge, parent.connectsAsSource);
+			parent.connectsAsTarget.push(connectingEdge);
+			mxUtils.remove(connectingEdge, node.connectsAsTarget);
+			node.connectsAsSource.push(connectingEdge);
+		}
+		
+		seenNodes[node.id] = node;
+		delete unseenNodes[node.id];
+	}, rootsArray, true, null);
+
+	// If there are any nodes that should be nodes that the dfs can miss
+	// these need to be processed with the dfs and the roots assigned
+	// correctly to form a correct internal model
+	var seenNodesCopy = mxUtils.clone(seenNodes, null, true);
+
+	// Pick a random cell and dfs from it
+	model.visit(function(parent, node, connectingEdge, layer, seen)
+	{
+		// Check if the cell is in it's own ancestor list, if so
+		// invert the connecting edge and reverse the target/source
+		// relationship to that edge in the parent and the cell
+		if (node.isAncestor(parent))
+		{
+			connectingEdge.invert();
+			mxUtils.remove(connectingEdge, parent.connectsAsSource);
+			node.connectsAsSource.push(connectingEdge);
+			parent.connectsAsTarget.push(connectingEdge);
+			mxUtils.remove(connectingEdge, node.connectsAsTarget);
+		}
+		
+		seenNodes[node.id] = node;
+		delete unseenNodes[node.id];
+	}, unseenNodes, true, seenNodesCopy);
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/stage/mxSwimlaneOrdering.js b/airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/stage/mxSwimlaneOrdering.js
new file mode 100644
index 0000000..5c71f40
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/layout/hierarchical/stage/mxSwimlaneOrdering.js
@@ -0,0 +1,96 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxSwimlaneOrdering
+ * 
+ * An implementation of the first stage of the Sugiyama layout. Straightforward
+ * longest path calculation of layer assignment
+ * 
+ * Constructor: mxSwimlaneOrdering
+ *
+ * Creates a cycle remover for the given internal model.
+ */
+function mxSwimlaneOrdering(layout)
+{
+	this.layout = layout;
+};
+
+/**
+ * Extends mxHierarchicalLayoutStage.
+ */
+mxSwimlaneOrdering.prototype = new mxHierarchicalLayoutStage();
+mxSwimlaneOrdering.prototype.constructor = mxSwimlaneOrdering;
+
+/**
+ * Variable: layout
+ * 
+ * Reference to the enclosing <mxHierarchicalLayout>.
+ */
+mxSwimlaneOrdering.prototype.layout = null;
+
+/**
+ * Function: execute
+ * 
+ * Takes the graph detail and configuration information within the facade
+ * and creates the resulting laid out graph within that facade for further
+ * use.
+ */
+mxSwimlaneOrdering.prototype.execute = function(parent)
+{
+	var model = this.layout.getModel();
+	var seenNodes = new Object();
+	var unseenNodes = mxUtils.clone(model.vertexMapper, null, true);
+	
+	// Perform a dfs through the internal model. If a cycle is found,
+	// reverse it.
+	var rootsArray = null;
+	
+	if (model.roots != null)
+	{
+		var modelRoots = model.roots;
+		rootsArray = [];
+		
+		for (var i = 0; i < modelRoots.length; i++)
+		{
+			var nodeId = mxCellPath.create(modelRoots[i]);
+			rootsArray[i] = model.vertexMapper.get(modelRoots[i]);
+		}
+	}
+
+	model.visit(function(parent, node, connectingEdge, layer, seen)
+	{
+		// Check if the cell is in it's own ancestor list, if so
+		// invert the connecting edge and reverse the target/source
+		// relationship to that edge in the parent and the cell
+		// Ancestor hashes only line up within a swimlane
+		var isAncestor = parent != null && parent.swimlaneIndex == node.swimlaneIndex && node.isAncestor(parent);
+
+		// If the source->target swimlane indices go from higher to
+		// lower, the edge is reverse
+		var reversedOverSwimlane = parent != null && connectingEdge != null &&
+						parent.swimlaneIndex < node.swimlaneIndex && connectingEdge.source == node;
+
+		if (isAncestor)
+		{
+			connectingEdge.invert();
+			mxUtils.remove(connectingEdge, parent.connectsAsSource);
+			node.connectsAsSource.push(connectingEdge);
+			parent.connectsAsTarget.push(connectingEdge);
+			mxUtils.remove(connectingEdge, node.connectsAsTarget);
+		}
+		else if (reversedOverSwimlane)
+		{
+			connectingEdge.invert();
+			mxUtils.remove(connectingEdge, parent.connectsAsTarget);
+			node.connectsAsTarget.push(connectingEdge);
+			parent.connectsAsSource.push(connectingEdge);
+			mxUtils.remove(connectingEdge, node.connectsAsSource);
+		}
+		
+		var cellId = mxCellPath.create(node.cell);
+		seenNodes[cellId] = node;
+		delete unseenNodes[cellId];
+	}, rootsArray, true, null);
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/layout/mxCircleLayout.js b/airavata-kubernetes/web-console/src/assets/js/layout/mxCircleLayout.js
new file mode 100644
index 0000000..de82c53
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/layout/mxCircleLayout.js
@@ -0,0 +1,203 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxCircleLayout
+ * 
+ * Extends <mxGraphLayout> to implement a circluar layout for a given radius.
+ * The vertices do not need to be connected for this layout to work and all
+ * connections between vertices are not taken into account.
+ * 
+ * Example:
+ * 
+ * (code)
+ * var layout = new mxCircleLayout(graph);
+ * layout.execute(graph.getDefaultParent());
+ * (end)
+ * 
+ * Constructor: mxCircleLayout
+ *
+ * Constructs a new circular layout for the specified radius.
+ *
+ * Arguments:
+ * 
+ * graph - <mxGraph> that contains the cells.
+ * radius - Optional radius as an int. Default is 100.
+ */
+function mxCircleLayout(graph, radius)
+{
+	mxGraphLayout.call(this, graph);
+	this.radius = (radius != null) ? radius : 100;
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxCircleLayout.prototype = new mxGraphLayout();
+mxCircleLayout.prototype.constructor = mxCircleLayout;
+
+/**
+ * Variable: radius
+ * 
+ * Integer specifying the size of the radius. Default is 100.
+ */
+mxCircleLayout.prototype.radius = null;
+
+/**
+ * Variable: moveCircle
+ * 
+ * Boolean specifying if the circle should be moved to the top,
+ * left corner specified by <x0> and <y0>. Default is false.
+ */
+mxCircleLayout.prototype.moveCircle = false;
+
+/**
+ * Variable: x0
+ * 
+ * Integer specifying the left coordinate of the circle.
+ * Default is 0.
+ */
+mxCircleLayout.prototype.x0 = 0;
+
+/**
+ * Variable: y0
+ * 
+ * Integer specifying the top coordinate of the circle.
+ * Default is 0.
+ */
+mxCircleLayout.prototype.y0 = 0;
+
+/**
+ * Variable: resetEdges
+ * 
+ * Specifies if all edge points of traversed edges should be removed.
+ * Default is true.
+ */
+mxCircleLayout.prototype.resetEdges = true;
+
+/**
+ * Variable: disableEdgeStyle
+ * 
+ * Specifies if the STYLE_NOEDGESTYLE flag should be set on edges that are
+ * modified by the result. Default is true.
+ */
+mxCircleLayout.prototype.disableEdgeStyle = true;
+
+/**
+ * Function: execute
+ * 
+ * Implements <mxGraphLayout.execute>.
+ */
+mxCircleLayout.prototype.execute = function(parent)
+{
+	var model = this.graph.getModel();
+
+	// Moves the vertices to build a circle. Makes sure the
+	// radius is large enough for the vertices to not
+	// overlap
+	model.beginUpdate();
+	try
+	{
+		// Gets all vertices inside the parent and finds
+		// the maximum dimension of the largest vertex
+		var max = 0;
+		var top = null;
+		var left = null;
+		var vertices = [];
+		var childCount = model.getChildCount(parent);
+		
+		for (var i = 0; i < childCount; i++)
+		{
+			var cell = model.getChildAt(parent, i);
+			
+			if (!this.isVertexIgnored(cell))
+			{
+				vertices.push(cell);
+				var bounds = this.getVertexBounds(cell);
+				
+				if (top == null)
+				{
+					top = bounds.y;
+				}
+				else
+				{
+					top = Math.min(top, bounds.y);
+				}
+				
+				if (left == null)
+				{
+					left = bounds.x;
+				}
+				else
+				{
+					left = Math.min(left, bounds.x);
+				}
+				
+				max = Math.max(max, Math.max(bounds.width, bounds.height));
+			}
+			else if (!this.isEdgeIgnored(cell))
+			{
+				// Resets the points on the traversed edge
+				if (this.resetEdges)
+				{
+					this.graph.resetEdge(cell);
+				}
+
+			    if (this.disableEdgeStyle)
+			    {
+			    	this.setEdgeStyleEnabled(cell, false);
+			    }
+			}
+		}
+		
+		var r = this.getRadius(vertices.length, max);
+
+		// Moves the circle to the specified origin
+		if (this.moveCircle)
+		{
+			left = this.x0;
+			top = this.y0;
+		}
+		
+		this.circle(vertices, r, left, top);
+	}
+	finally
+	{
+		model.endUpdate();
+	}
+};
+
+/**
+ * Function: getRadius
+ * 
+ * Returns the radius to be used for the given vertex count. Max is the maximum
+ * width or height of all vertices in the layout.
+ */
+mxCircleLayout.prototype.getRadius = function(count, max)
+{
+	return Math.max(count * max / Math.PI, this.radius);
+};
+
+/**
+ * Function: circle
+ * 
+ * Executes the circular layout for the specified array
+ * of vertices and the given radius. This is called from
+ * <execute>.
+ */
+mxCircleLayout.prototype.circle = function(vertices, r, left, top)
+{
+	var vertexCount = vertices.length;
+	var phi = 2 * Math.PI / vertexCount;
+	
+	for (var i = 0; i < vertexCount; i++)
+	{
+		if (this.isVertexMovable(vertices[i]))
+		{
+			this.setVertexLocation(vertices[i],
+				left + r + r * Math.sin(i*phi),
+				top + r + r * Math.cos(i*phi));
+		}
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/layout/mxCompactTreeLayout.js b/airavata-kubernetes/web-console/src/assets/js/layout/mxCompactTreeLayout.js
new file mode 100644
index 0000000..2bda6ed
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/layout/mxCompactTreeLayout.js
@@ -0,0 +1,1203 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxCompactTreeLayout
+ * 
+ * Extends <mxGraphLayout> to implement a compact tree (Moen) algorithm. This
+ * layout is suitable for graphs that have no cycles (trees). Vertices that are
+ * not connected to the tree will be ignored by this layout.
+ * 
+ * Example:
+ * 
+ * (code)
+ * var layout = new mxCompactTreeLayout(graph);
+ * layout.execute(graph.getDefaultParent());
+ * (end)
+ * 
+ * Constructor: mxCompactTreeLayout
+ * 
+ * Constructs a new compact tree layout for the specified graph
+ * and orientation.
+ */
+function mxCompactTreeLayout(graph, horizontal, invert)
+{
+	mxGraphLayout.call(this, graph);
+	this.horizontal = (horizontal != null) ? horizontal : true;
+	this.invert = (invert != null) ? invert : false;
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxCompactTreeLayout.prototype = new mxGraphLayout();
+mxCompactTreeLayout.prototype.constructor = mxCompactTreeLayout;
+
+/**
+ * Variable: horizontal
+ *
+ * Specifies the orientation of the layout. Default is true.
+ */
+mxCompactTreeLayout.prototype.horizontal = null;	 
+
+/**
+ * Variable: invert
+ *
+ * Specifies if edge directions should be inverted. Default is false.
+ */
+mxCompactTreeLayout.prototype.invert = null;	 
+
+/**
+ * Variable: resizeParent
+ * 
+ * If the parents should be resized to match the width/height of the
+ * children. Default is true.
+ */
+mxCompactTreeLayout.prototype.resizeParent = true;
+
+/**
+ * Variable: maintainParentLocation
+ * 
+ * Specifies if the parent location should be maintained, so that the
+ * top, left corner stays the same before and after execution of
+ * the layout. Default is false for backwards compatibility.
+ */
+mxCompactTreeLayout.prototype.maintainParentLocation = false;
+
+/**
+ * Variable: groupPadding
+ * 
+ * Padding added to resized parents. Default is 10.
+ */
+mxCompactTreeLayout.prototype.groupPadding = 10;
+
+/**
+ * Variable: groupPaddingTop
+ * 
+ * Top padding added to resized parents. Default is 0.
+ */
+mxCompactTreeLayout.prototype.groupPaddingTop = 0;
+
+/**
+ * Variable: groupPaddingRight
+ * 
+ * Right padding added to resized parents. Default is 0.
+ */
+mxCompactTreeLayout.prototype.groupPaddingRight = 0;
+
+/**
+ * Variable: groupPaddingBottom
+ * 
+ * Bottom padding added to resized parents. Default is 0.
+ */
+mxCompactTreeLayout.prototype.groupPaddingBottom = 0;
+
+/**
+ * Variable: groupPaddingLeft
+ * 
+ * Left padding added to resized parents. Default is 0.
+ */
+mxCompactTreeLayout.prototype.groupPaddingLeft = 0;
+
+/**
+ * Variable: parentsChanged
+ *
+ * A set of the parents that need updating based on children
+ * process as part of the layout.
+ */
+mxCompactTreeLayout.prototype.parentsChanged = null;
+
+/**
+ * Variable: moveTree
+ * 
+ * Specifies if the tree should be moved to the top, left corner
+ * if it is inside a top-level layer. Default is false.
+ */
+mxCompactTreeLayout.prototype.moveTree = false;
+
+/**
+ * Variable: visited
+ * 
+ * Specifies if the tree should be moved to the top, left corner
+ * if it is inside a top-level layer. Default is false.
+ */
+mxCompactTreeLayout.prototype.visited = null;
+
+/**
+ * Variable: levelDistance
+ *
+ * Holds the levelDistance. Default is 10.
+ */
+mxCompactTreeLayout.prototype.levelDistance = 10;
+
+/**
+ * Variable: nodeDistance
+ *
+ * Holds the nodeDistance. Default is 20.
+ */
+mxCompactTreeLayout.prototype.nodeDistance = 20;
+
+/**
+ * Variable: resetEdges
+ * 
+ * Specifies if all edge points of traversed edges should be removed.
+ * Default is true.
+ */
+mxCompactTreeLayout.prototype.resetEdges = true;
+
+/**
+ * Variable: prefHozEdgeSep
+ * 
+ * The preferred horizontal distance between edges exiting a vertex.
+ */
+mxCompactTreeLayout.prototype.prefHozEdgeSep = 5;
+
+/**
+ * Variable: prefVertEdgeOff
+ * 
+ * The preferred vertical offset between edges exiting a vertex.
+ */
+mxCompactTreeLayout.prototype.prefVertEdgeOff = 4;
+
+/**
+ * Variable: minEdgeJetty
+ * 
+ * The minimum distance for an edge jetty from a vertex.
+ */
+mxCompactTreeLayout.prototype.minEdgeJetty = 8;
+
+/**
+ * Variable: channelBuffer
+ * 
+ * The size of the vertical buffer in the center of inter-rank channels
+ * where edge control points should not be placed.
+ */
+mxCompactTreeLayout.prototype.channelBuffer = 4;
+
+/**
+ * Variable: edgeRouting
+ * 
+ * Whether or not to apply the internal tree edge routing.
+ */
+mxCompactTreeLayout.prototype.edgeRouting = true;
+
+/**
+ * Variable: sortEdges
+ * 
+ * Specifies if edges should be sorted according to the order of their
+ * opposite terminal cell in the model.
+ */
+mxCompactTreeLayout.prototype.sortEdges = false;
+
+/**
+ * Variable: alignRanks
+ * 
+ * Whether or not the tops of cells in each rank should be aligned
+ * across the rank
+ */
+mxCompactTreeLayout.prototype.alignRanks = false;
+
+/**
+ * Variable: maxRankHeight
+ * 
+ * An array of the maximum height of cells (relative to the layout direction)
+ * per rank
+ */
+mxCompactTreeLayout.prototype.maxRankHeight = null;
+
+/**
+ * Variable: root
+ * 
+ * The cell to use as the root of the tree
+ */
+mxCompactTreeLayout.prototype.root = null;
+
+/**
+ * Variable: node
+ * 
+ * The internal node representation of the root cell. Do not set directly
+ * , this value is only exposed to assist with post-processing functionality
+ */
+mxCompactTreeLayout.prototype.node = null;
+
+/**
+ * Function: isVertexIgnored
+ * 
+ * Returns a boolean indicating if the given <mxCell> should be ignored as a
+ * vertex. This returns true if the cell has no connections.
+ * 
+ * Parameters:
+ * 
+ * vertex - <mxCell> whose ignored state should be returned.
+ */
+mxCompactTreeLayout.prototype.isVertexIgnored = function(vertex)
+{
+	return mxGraphLayout.prototype.isVertexIgnored.apply(this, arguments) ||
+		this.graph.getConnections(vertex).length == 0;
+};
+
+/**
+ * Function: isHorizontal
+ * 
+ * Returns <horizontal>.
+ */
+mxCompactTreeLayout.prototype.isHorizontal = function()
+{
+	return this.horizontal;
+};
+
+/**
+ * Function: execute
+ * 
+ * Implements <mxGraphLayout.execute>.
+ * 
+ * If the parent has any connected edges, then it is used as the root of
+ * the tree. Else, <mxGraph.findTreeRoots> will be used to find a suitable
+ * root node within the set of children of the given parent.
+ * 
+ * Parameters:
+ * 
+ * parent - <mxCell> whose children should be laid out.
+ * root - Optional <mxCell> that will be used as the root of the tree.
+ * Overrides <root> if specified.
+ */
+mxCompactTreeLayout.prototype.execute = function(parent, root)
+{
+	this.parent = parent;
+	var model = this.graph.getModel();
+
+	if (root == null)
+	{
+		// Takes the parent as the root if it has outgoing edges
+		if (this.graph.getEdges(parent, model.getParent(parent),
+			this.invert, !this.invert, false).length > 0)
+		{
+			this.root = parent;
+		}
+		
+		// Tries to find a suitable root in the parent's
+		// children
+		else
+		{
+			var roots = this.graph.findTreeRoots(parent, true, this.invert);
+			
+			if (roots.length > 0)
+			{
+				for (var i = 0; i < roots.length; i++)
+				{
+					if (!this.isVertexIgnored(roots[i]) &&
+						this.graph.getEdges(roots[i], null,
+							this.invert, !this.invert, false).length > 0)
+					{
+						this.root = roots[i];
+						break;
+					}
+				}
+			}
+		}
+	}
+	else
+	{
+		this.root = root;
+	}
+	
+	if (this.root != null)
+	{
+		if (this.resizeParent)
+		{
+			this.parentsChanged = new Object();
+		}
+		else
+		{
+			this.parentsChanged = null;
+		}
+
+		//  Maintaining parent location
+		this.parentX = null;
+		this.parentY = null;
+		
+		if (parent != this.root && model.isVertex(parent) != null && this.maintainParentLocation)
+		{
+			var geo = this.graph.getCellGeometry(parent);
+			
+			if (geo != null)
+			{
+				this.parentX = geo.x;
+				this.parentY = geo.y;
+			}
+		}
+		
+		model.beginUpdate();
+		
+		try
+		{
+			this.visited = new Object();
+			this.node = this.dfs(this.root, parent);
+			
+			if (this.alignRanks)
+			{
+				this.maxRankHeight = [];
+				this.findRankHeights(this.node, 0);
+				this.setCellHeights(this.node, 0);
+			}
+			
+			if (this.node != null)
+			{
+				this.layout(this.node);
+				var x0 = this.graph.gridSize;
+				var y0 = x0;
+				
+				if (!this.moveTree)
+				{
+					var g = this.getVertexBounds(this.root);
+					
+					if (g != null)
+					{
+						x0 = g.x;
+						y0 = g.y;
+					}
+				}
+				
+				var bounds = null;
+				
+				if (this.isHorizontal())
+				{
+					bounds = this.horizontalLayout(this.node, x0, y0);
+				}
+				else
+				{
+					bounds = this.verticalLayout(this.node, null, x0, y0);
+				}
+
+				if (bounds != null)
+				{
+					var dx = 0;
+					var dy = 0;
+
+					if (bounds.x < 0)
+					{
+						dx = Math.abs(x0 - bounds.x);
+					}
+
+					if (bounds.y < 0)
+					{
+						dy = Math.abs(y0 - bounds.y);	
+					}
+
+					if (dx != 0 || dy != 0)
+					{
+						this.moveNode(this.node, dx, dy);
+					}
+					
+					if (this.resizeParent)
+					{
+						this.adjustParents();
+					}
+
+					if (this.edgeRouting)
+					{
+						// Iterate through all edges setting their positions
+						this.localEdgeProcessing(this.node);
+					}
+				}
+				
+				// Maintaining parent location
+				if (this.parentX != null && this.parentY != null)
+				{
+					var geo = this.graph.getCellGeometry(parent);
+					
+					if (geo != null)
+					{
+						geo = geo.clone();
+						geo.x = this.parentX;
+						geo.y = this.parentY;
+						model.setGeometry(parent, geo);
+					}
+				}
+			}
+		}
+		finally
+		{
+			model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: moveNode
+ * 
+ * Moves the specified node and all of its children by the given amount.
+ */
+mxCompactTreeLayout.prototype.moveNode = function(node, dx, dy)
+{
+	node.x += dx;
+	node.y += dy;
+	this.apply(node);
+	
+	var child = node.child;
+	
+	while (child != null)
+	{
+		this.moveNode(child, dx, dy);
+		child = child.next;
+	}
+};
+
+
+/**
+ * Function: sortOutgoingEdges
+ * 
+ * Called if <sortEdges> is true to sort the array of outgoing edges in place.
+ */
+mxCompactTreeLayout.prototype.sortOutgoingEdges = function(source, edges)
+{
+	var lookup = new mxDictionary();
+	
+	edges.sort(function(e1, e2)
+	{
+		var end1 = e1.getTerminal(e1.getTerminal(false) == source);
+		var p1 = lookup.get(end1);
+		
+		if (p1 == null)
+		{
+			p1 = mxCellPath.create(end1).split(mxCellPath.PATH_SEPARATOR);
+			lookup.put(end1, p1);
+		}
+
+		var end2 = e2.getTerminal(e2.getTerminal(false) == source);
+		var p2 = lookup.get(end2);
+		
+		if (p2 == null)
+		{
+			p2 = mxCellPath.create(end2).split(mxCellPath.PATH_SEPARATOR);
+			lookup.put(end2, p2);
+		}
+
+		return mxCellPath.compare(p1, p2);
+	});
+};
+
+/**
+ * Function: findRankHeights
+ * 
+ * Stores the maximum height (relative to the layout
+ * direction) of cells in each rank
+ */
+mxCompactTreeLayout.prototype.findRankHeights = function(node, rank)
+{
+	if (this.maxRankHeight[rank] == null || this.maxRankHeight[rank] < node.height)
+	{
+		this.maxRankHeight[rank] = node.height;
+	}
+
+	var child = node.child;
+	
+	while (child != null)
+	{
+		this.findRankHeights(child, rank + 1);
+		child = child.next;
+	}
+};
+
+/**
+ * Function: setCellHeights
+ * 
+ * Set the cells heights (relative to the layout
+ * direction) when the tops of each rank are to be aligned
+ */
+mxCompactTreeLayout.prototype.setCellHeights = function(node, rank)
+{
+	if (this.maxRankHeight[rank] != null && this.maxRankHeight[rank] > node.height)
+	{
+		node.height = this.maxRankHeight[rank];
+	}
+
+	var child = node.child;
+	
+	while (child != null)
+	{
+		this.setCellHeights(child, rank + 1);
+		child = child.next;
+	}
+};
+
+/**
+ * Function: dfs
+ * 
+ * Does a depth first search starting at the specified cell.
+ * Makes sure the specified parent is never left by the
+ * algorithm.
+ */
+mxCompactTreeLayout.prototype.dfs = function(cell, parent)
+{
+	var id = mxCellPath.create(cell);
+	var node = null;
+	
+	if (cell != null && this.visited[id] == null && !this.isVertexIgnored(cell))
+	{
+		this.visited[id] = cell;
+		node = this.createNode(cell);
+
+		var model = this.graph.getModel();
+		var prev = null;
+		var out = this.graph.getEdges(cell, parent, this.invert, !this.invert, false, true);
+		var view = this.graph.getView();
+		
+		if (this.sortEdges)
+		{
+			this.sortOutgoingEdges(cell, out);
+		}
+
+		for (var i = 0; i < out.length; i++)
+		{
+			var edge = out[i];
+			
+			if (!this.isEdgeIgnored(edge))
+			{
+				// Resets the points on the traversed edge
+				if (this.resetEdges)
+				{
+					this.setEdgePoints(edge, null);
+				}
+				
+				if (this.edgeRouting)
+				{
+					this.setEdgeStyleEnabled(edge, false);
+					this.setEdgePoints(edge, null);
+				}
+				
+				// Checks if terminal in same swimlane
+				var state = view.getState(edge);
+				var target = (state != null) ? state.getVisibleTerminal(this.invert) : view.getVisibleTerminal(edge, this.invert);
+				var tmp = this.dfs(target, parent);
+				
+				if (tmp != null && model.getGeometry(target) != null)
+				{
+					if (prev == null)
+					{
+						node.child = tmp;
+					}
+					else
+					{
+						prev.next = tmp;
+					}
+					
+					prev = tmp;
+				}
+			}
+		}
+	}
+	
+	return node;
+};
+
+/**
+ * Function: layout
+ * 
+ * Starts the actual compact tree layout algorithm
+ * at the given node.
+ */
+mxCompactTreeLayout.prototype.layout = function(node)
+{
+	if (node != null)
+	{
+		var child = node.child;
+		
+		while (child != null)
+		{
+			this.layout(child);
+			child = child.next;
+		}
+		
+		if (node.child != null)
+		{
+			this.attachParent(node, this.join(node));
+		}
+		else
+		{
+			this.layoutLeaf(node);
+		}
+	}
+};
+
+/**
+ * Function: horizontalLayout
+ */
+mxCompactTreeLayout.prototype.horizontalLayout = function(node, x0, y0, bounds)
+{
+	node.x += x0 + node.offsetX;
+	node.y += y0 + node.offsetY;
+	bounds = this.apply(node, bounds);
+	var child = node.child;
+	
+	if (child != null)
+	{
+		bounds = this.horizontalLayout(child, node.x, node.y, bounds);
+		var siblingOffset = node.y + child.offsetY;
+		var s = child.next;
+		
+		while (s != null)
+		{
+			bounds = this.horizontalLayout(s, node.x + child.offsetX, siblingOffset, bounds);
+			siblingOffset += s.offsetY;
+			s = s.next;
+		}
+	}
+	
+	return bounds;
+};
+	
+/**
+ * Function: verticalLayout
+ */
+mxCompactTreeLayout.prototype.verticalLayout = function(node, parent, x0, y0, bounds)
+{
+	node.x += x0 + node.offsetY;
+	node.y += y0 + node.offsetX;
+	bounds = this.apply(node, bounds);
+	var child = node.child;
+	
+	if (child != null)
+	{
+		bounds = this.verticalLayout(child, node, node.x, node.y, bounds);
+		var siblingOffset = node.x + child.offsetY;
+		var s = child.next;
+		
+		while (s != null)
+		{
+			bounds = this.verticalLayout(s, node, siblingOffset, node.y + child.offsetX, bounds);
+			siblingOffset += s.offsetY;
+			s = s.next;
+		}
+	}
+	
+	return bounds;
+};
+
+/**
+ * Function: attachParent
+ */
+mxCompactTreeLayout.prototype.attachParent = function(node, height)
+{
+	var x = this.nodeDistance + this.levelDistance;
+	var y2 = (height - node.width) / 2 - this.nodeDistance;
+	var y1 = y2 + node.width + 2 * this.nodeDistance - height;
+	
+	node.child.offsetX = x + node.height;
+	node.child.offsetY = y1;
+	
+	node.contour.upperHead = this.createLine(node.height, 0,
+		this.createLine(x, y1, node.contour.upperHead));
+	node.contour.lowerHead = this.createLine(node.height, 0,
+		this.createLine(x, y2, node.contour.lowerHead));
+};
+
+/**
+ * Function: layoutLeaf
+ */
+mxCompactTreeLayout.prototype.layoutLeaf = function(node)
+{
+	var dist = 2 * this.nodeDistance;
+	
+	node.contour.upperTail = this.createLine(
+		node.height + dist, 0);
+	node.contour.upperHead = node.contour.upperTail;
+	node.contour.lowerTail = this.createLine(
+		0, -node.width - dist);
+	node.contour.lowerHead = this.createLine(
+		node.height + dist, 0, node.contour.lowerTail);
+};
+
+/**
+ * Function: join
+ */
+mxCompactTreeLayout.prototype.join = function(node)
+{
+	var dist = 2 * this.nodeDistance;
+	
+	var child = node.child;
+	node.contour = child.contour;
+	var h = child.width + dist;
+	var sum = h;
+	child = child.next;
+	
+	while (child != null)
+	{
+		var d = this.merge(node.contour, child.contour);
+		child.offsetY = d + h;
+		child.offsetX = 0;
+		h = child.width + dist;
+		sum += d + h;
+		child = child.next;
+	}
+	
+	return sum;
+};
+
+/**
+ * Function: merge
+ */
+mxCompactTreeLayout.prototype.merge = function(p1, p2)
+{
+	var x = 0;
+	var y = 0;
+	var total = 0;
+	
+	var upper = p1.lowerHead;
+	var lower = p2.upperHead;
+	
+	while (lower != null && upper != null)
+	{
+		var d = this.offset(x, y, lower.dx, lower.dy,
+			upper.dx, upper.dy);
+		y += d;
+		total += d;
+		
+		if (x + lower.dx <= upper.dx)
+		{
+			x += lower.dx;
+			y += lower.dy;
+			lower = lower.next;
+		}
+		else
+		{				
+			x -= upper.dx;
+			y -= upper.dy;
+			upper = upper.next;
+		}
+	}
+	
+	if (lower != null)
+	{
+		var b = this.bridge(p1.upperTail, 0, 0, lower, x, y);
+		p1.upperTail = (b.next != null) ? p2.upperTail : b;
+		p1.lowerTail = p2.lowerTail;
+	}
+	else
+	{
+		var b = this.bridge(p2.lowerTail, x, y, upper, 0, 0);
+		
+		if (b.next == null)
+		{
+			p1.lowerTail = b;
+		}
+	}
+	
+	p1.lowerHead = p2.lowerHead;
+	
+	return total;
+};
+
+/**
+ * Function: offset
+ */
+mxCompactTreeLayout.prototype.offset = function(p1, p2, a1, a2, b1, b2)
+{
+	var d = 0;
+	
+	if (b1 <= p1 || p1 + a1 <= 0)
+	{
+		return 0;
+	}
+
+	var t = b1 * a2 - a1 * b2;
+	
+	if (t > 0)
+	{
+		if (p1 < 0)
+		{
+			var s = p1 * a2;
+			d = s / a1 - p2;
+		}
+		else if (p1 > 0)
+		{
+			var s = p1 * b2;
+			d = s / b1 - p2;
+		}
+		else
+		{
+			d = -p2;
+		}
+	}
+	else if (b1 < p1 + a1)
+	{
+		var s = (b1 - p1) * a2;
+		d = b2 - (p2 + s / a1);
+	}
+	else if (b1 > p1 + a1)
+	{
+		var s = (a1 + p1) * b2;
+		d = s / b1 - (p2 + a2);
+	}
+	else
+	{
+		d = b2 - (p2 + a2);
+	}
+
+	if (d > 0)
+	{
+		return d;
+	}
+	else
+	{
+		return 0;
+	}
+};
+
+/**
+ * Function: bridge
+ */
+mxCompactTreeLayout.prototype.bridge = function(line1, x1, y1, line2, x2, y2)
+{
+	var dx = x2 + line2.dx - x1;
+	var dy = 0;
+	var s = 0;
+	
+	if (line2.dx == 0)
+	{
+		dy = line2.dy;
+	}
+	else
+	{
+		s = dx * line2.dy;
+		dy = s / line2.dx;
+	}
+	
+	var r = this.createLine(dx, dy, line2.next);
+	line1.next = this.createLine(0, y2 + line2.dy - dy - y1, r);
+	
+	return r;
+};
+
+/**
+ * Function: createNode
+ */
+mxCompactTreeLayout.prototype.createNode = function(cell)
+{
+	var node = new Object();
+	node.cell = cell;
+	node.x = 0;
+	node.y = 0;
+	node.width = 0;
+	node.height = 0;
+	
+	var geo = this.getVertexBounds(cell);
+	
+	if (geo != null)
+	{
+		if (this.isHorizontal())
+		{
+			node.width = geo.height;
+			node.height = geo.width;			
+		}
+		else
+		{
+			node.width = geo.width;
+			node.height = geo.height;
+		}
+	}
+	
+	node.offsetX = 0;
+	node.offsetY = 0;
+	node.contour = new Object();
+	
+	return node;
+};
+
+/**
+ * Function: apply
+ */
+mxCompactTreeLayout.prototype.apply = function(node, bounds)
+{
+	var model = this.graph.getModel();
+	var cell = node.cell;
+	var g = model.getGeometry(cell);
+
+	if (cell != null && g != null)
+	{
+		if (this.isVertexMovable(cell))
+		{
+			g = this.setVertexLocation(cell, node.x, node.y);
+			
+			if (this.resizeParent)
+			{
+				var parent = model.getParent(cell);
+				var id = mxCellPath.create(parent);
+				
+				// Implements set semantic
+				if (this.parentsChanged[id] == null)
+				{
+					this.parentsChanged[id] = parent;					
+				}
+			}
+		}
+		
+		if (bounds == null)
+		{
+			bounds = new mxRectangle(g.x, g.y, g.width, g.height);
+		}
+		else
+		{
+			bounds = new mxRectangle(Math.min(bounds.x, g.x),
+				Math.min(bounds.y, g.y),
+				Math.max(bounds.x + bounds.width, g.x + g.width),
+				Math.max(bounds.y + bounds.height, g.y + g.height));
+		}
+	}
+	
+	return bounds;
+};
+
+/**
+ * Function: createLine
+ */
+mxCompactTreeLayout.prototype.createLine = function(dx, dy, next)
+{
+	var line = new Object();
+	line.dx = dx;
+	line.dy = dy;
+	line.next = next;
+	
+	return line;
+};
+
+/**
+ * Function: adjustParents
+ * 
+ * Adjust parent cells whose child geometries have changed. The default 
+ * implementation adjusts the group to just fit around the children with 
+ * a padding.
+ */
+mxCompactTreeLayout.prototype.adjustParents = function()
+{
+	var tmp = [];
+	
+	for (var id in this.parentsChanged)
+	{
+		tmp.push(this.parentsChanged[id]);
+	}
+	
+	this.arrangeGroups(mxUtils.sortCells(tmp, true), this.groupPadding, this.groupPaddingTop,
+		this.groupPaddingRight, this.groupPaddingBottom, this.groupPaddingLeft);
+};
+
+/**
+ * Function: localEdgeProcessing
+ *
+ * Moves the specified node and all of its children by the given amount.
+ */
+mxCompactTreeLayout.prototype.localEdgeProcessing = function(node)
+{
+	this.processNodeOutgoing(node);
+	var child = node.child;
+
+	while (child != null)
+	{
+		this.localEdgeProcessing(child);
+		child = child.next;
+	}
+};
+
+/**
+ * Function: 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;
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/layout/mxCompositeLayout.js b/airavata-kubernetes/web-console/src/assets/js/layout/mxCompositeLayout.js
new file mode 100644
index 0000000..8e3e116
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/layout/mxCompositeLayout.js
@@ -0,0 +1,101 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxCompositeLayout
+ * 
+ * Allows to compose multiple layouts into a single layout. The master layout
+ * is the layout that handles move operations if another layout than the first
+ * element in <layouts> should be used. The <master> layout is not executed as
+ * the code assumes that it is part of <layouts>.
+ * 
+ * Example:
+ * (code)
+ * var first = new mxFastOrganicLayout(graph);
+ * var second = new mxParallelEdgeLayout(graph);
+ * var layout = new mxCompositeLayout(graph, [first, second], first);
+ * layout.execute(graph.getDefaultParent());
+ * (end)
+ * 
+ * Constructor: mxCompositeLayout
+ *
+ * Constructs a new layout using the given layouts. The graph instance is
+ * required for creating the transaction that contains all layouts.
+ *
+ * Arguments:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ * layouts - Array of <mxGraphLayouts>.
+ * master - Optional layout that handles moves. If no layout is given then
+ * the first layout of the above array is used to handle moves.
+ */
+function mxCompositeLayout(graph, layouts, master)
+{
+	mxGraphLayout.call(this, graph);
+	this.layouts = layouts;
+	this.master = master;
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxCompositeLayout.prototype = new mxGraphLayout();
+mxCompositeLayout.prototype.constructor = mxCompositeLayout;
+	
+/**
+ * Variable: layouts
+ * 
+ * Holds the array of <mxGraphLayouts> that this layout contains.
+ */
+mxCompositeLayout.prototype.layouts = null;
+
+/**
+ * Variable: layouts
+ * 
+ * Reference to the <mxGraphLayouts> that handles moves. If this is null
+ * then the first layout in <layouts> is used.
+ */
+mxCompositeLayout.prototype.master = null;
+
+/**
+ * Function: moveCell
+ * 
+ * Implements <mxGraphLayout.moveCell> by calling move on <master> or the first
+ * layout in <layouts>.
+ */
+mxCompositeLayout.prototype.moveCell = function(cell, x, y)
+{
+	if (this.master != null)
+	{
+		this.master.move.apply(this.master, arguments);
+	}
+	else
+	{
+		this.layouts[0].move.apply(this.layouts[0], arguments);
+	}
+};
+
+/**
+ * Function: execute
+ * 
+ * Implements <mxGraphLayout.execute> by executing all <layouts> in a
+ * single transaction.
+ */
+mxCompositeLayout.prototype.execute = function(parent)
+{
+	var model = this.graph.getModel();
+	
+	model.beginUpdate();
+	try
+	{
+		for (var i = 0; i < this.layouts.length; i++)
+		{
+			this.layouts[i].execute.apply(this.layouts[i], arguments);
+		}
+	}
+	finally
+	{
+		model.endUpdate();
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/layout/mxEdgeLabelLayout.js b/airavata-kubernetes/web-console/src/assets/js/layout/mxEdgeLabelLayout.js
new file mode 100644
index 0000000..bfba27e
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/layout/mxEdgeLabelLayout.js
@@ -0,0 +1,165 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxEdgeLabelLayout
+ * 
+ * Extends <mxGraphLayout> to implement an edge label layout. This layout
+ * makes use of cell states, which means the graph must be validated in
+ * a graph view (so that the label bounds are available) before this layout
+ * can be executed.
+ * 
+ * Example:
+ * 
+ * (code)
+ * var layout = new mxEdgeLabelLayout(graph);
+ * layout.execute(graph.getDefaultParent());
+ * (end)
+ * 
+ * Constructor: mxEdgeLabelLayout
+ *
+ * Constructs a new edge label layout.
+ *
+ * Arguments:
+ * 
+ * graph - <mxGraph> that contains the cells.
+ */
+function mxEdgeLabelLayout(graph, radius)
+{
+	mxGraphLayout.call(this, graph);
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxEdgeLabelLayout.prototype = new mxGraphLayout();
+mxEdgeLabelLayout.prototype.constructor = mxEdgeLabelLayout;
+
+/**
+ * Function: execute
+ * 
+ * Implements <mxGraphLayout.execute>.
+ */
+mxEdgeLabelLayout.prototype.execute = function(parent)
+{
+	var view = this.graph.view;
+	var model = this.graph.getModel();
+	
+	// Gets all vertices and edges inside the parent
+	var edges = [];
+	var vertices = [];
+	var childCount = model.getChildCount(parent);
+	
+	for (var i = 0; i < childCount; i++)
+	{
+		var cell = model.getChildAt(parent, i);
+		var state = view.getState(cell);
+		
+		if (state != null)
+		{
+			if (!this.isVertexIgnored(cell))
+			{
+				vertices.push(state);
+			}
+			else if (!this.isEdgeIgnored(cell))
+			{
+				edges.push(state);
+			}
+		}
+	}
+	
+	this.placeLabels(vertices, edges);
+};
+
+/**
+ * Function: placeLabels
+ * 
+ * Places the labels of the given edges.
+ */
+mxEdgeLabelLayout.prototype.placeLabels = function(v, e)
+{
+	var model = this.graph.getModel();
+	
+	// Moves the vertices to build a circle. Makes sure the
+	// radius is large enough for the vertices to not
+	// overlap
+	model.beginUpdate();
+	try
+	{
+		for (var i = 0; i < e.length; i++)
+		{
+			var edge = e[i];
+			
+			if (edge != null && edge.text != null &&
+				edge.text.boundingBox != null)
+			{
+				for (var j = 0; j < v.length; j++)
+				{
+					var vertex = v[j];
+					
+					if (vertex != null)
+					{
+						this.avoid(edge, vertex);
+					}
+				}
+			}
+		}
+	}
+	finally
+	{
+		model.endUpdate();
+	}
+};
+
+/**
+ * Function: avoid
+ * 
+ * Places the labels of the given edges.
+ */
+mxEdgeLabelLayout.prototype.avoid = function(edge, vertex)
+{
+	var model = this.graph.getModel();
+	var labRect = edge.text.boundingBox;
+	
+	if (mxUtils.intersects(labRect, vertex))
+	{
+		var dy1 = -labRect.y - labRect.height + vertex.y;
+		var dy2 = -labRect.y + vertex.y + vertex.height;
+		
+		var dy = (Math.abs(dy1) < Math.abs(dy2)) ? dy1 : dy2;
+		
+		var dx1 = -labRect.x - labRect.width + vertex.x;
+		var dx2 = -labRect.x + vertex.x + vertex.width;
+	
+		var dx = (Math.abs(dx1) < Math.abs(dx2)) ? dx1 : dx2;
+		
+		if (Math.abs(dx) < Math.abs(dy))
+		{
+			dy = 0;
+		}
+		else
+		{
+			dx = 0;
+		}
+	
+		var g = model.getGeometry(edge.cell);
+		
+		if (g != null)
+		{
+			g = g.clone();
+			
+			if (g.offset != null)
+			{
+				g.offset.x += dx;
+				g.offset.y += dy;
+			}
+			else
+			{
+				g.offset = new mxPoint(dx, dy);
+			}
+			
+			model.setGeometry(edge.cell, g);
+		}
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/layout/mxFastOrganicLayout.js b/airavata-kubernetes/web-console/src/assets/js/layout/mxFastOrganicLayout.js
new file mode 100644
index 0000000..779bf69
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/layout/mxFastOrganicLayout.js
@@ -0,0 +1,591 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxFastOrganicLayout
+ * 
+ * Extends <mxGraphLayout> to implement a fast organic layout algorithm.
+ * The vertices need to be connected for this layout to work, vertices
+ * with no connections are ignored.
+ * 
+ * Example:
+ * 
+ * (code)
+ * var layout = new mxFastOrganicLayout(graph);
+ * layout.execute(graph.getDefaultParent());
+ * (end)
+ * 
+ * Constructor: mxCompactTreeLayout
+ * 
+ * Constructs a new fast organic layout for the specified graph.
+ */
+function mxFastOrganicLayout(graph)
+{
+	mxGraphLayout.call(this, graph);
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxFastOrganicLayout.prototype = new mxGraphLayout();
+mxFastOrganicLayout.prototype.constructor = mxFastOrganicLayout;
+
+/**
+ * Variable: useInputOrigin
+ * 
+ * Specifies if the top left corner of the input cells should be the origin
+ * of the layout result. Default is true.
+ */
+mxFastOrganicLayout.prototype.useInputOrigin = true;
+
+/**
+ * Variable: resetEdges
+ * 
+ * Specifies if all edge points of traversed edges should be removed.
+ * Default is true.
+ */
+mxFastOrganicLayout.prototype.resetEdges = true;
+
+/**
+ * Variable: disableEdgeStyle
+ * 
+ * Specifies if the STYLE_NOEDGESTYLE flag should be set on edges that are
+ * modified by the result. Default is true.
+ */
+mxFastOrganicLayout.prototype.disableEdgeStyle = true;
+
+/**
+ * Variable: forceConstant
+ * 
+ * The force constant by which the attractive forces are divided and the
+ * replusive forces are multiple by the square of. The value equates to the
+ * average radius there is of free space around each node. Default is 50.
+ */
+mxFastOrganicLayout.prototype.forceConstant = 50;
+
+/**
+ * Variable: forceConstantSquared
+ * 
+ * Cache of <forceConstant>^2 for performance.
+ */
+mxFastOrganicLayout.prototype.forceConstantSquared = 0;
+
+/**
+ * Variable: minDistanceLimit
+ * 
+ * Minimal distance limit. Default is 2. Prevents of
+ * dividing by zero.
+ */
+mxFastOrganicLayout.prototype.minDistanceLimit = 2;
+
+/**
+ * Variable: minDistanceLimit
+ * 
+ * Minimal distance limit. Default is 2. Prevents of
+ * dividing by zero.
+ */
+mxFastOrganicLayout.prototype.maxDistanceLimit = 500;
+
+/**
+ * Variable: minDistanceLimitSquared
+ * 
+ * Cached version of <minDistanceLimit> squared.
+ */
+mxFastOrganicLayout.prototype.minDistanceLimitSquared = 4;
+
+/**
+ * Variable: initialTemp
+ * 
+ * Start value of temperature. Default is 200.
+ */
+mxFastOrganicLayout.prototype.initialTemp = 200;
+
+/**
+ * Variable: temperature
+ * 
+ * Temperature to limit displacement at later stages of layout.
+ */
+mxFastOrganicLayout.prototype.temperature = 0;
+
+/**
+ * Variable: maxIterations
+ * 
+ * Total number of iterations to run the layout though.
+ */
+mxFastOrganicLayout.prototype.maxIterations = 0;
+
+/**
+ * Variable: iteration
+ * 
+ * Current iteration count.
+ */
+mxFastOrganicLayout.prototype.iteration = 0;
+
+/**
+ * Variable: vertexArray
+ * 
+ * An array of all vertices to be laid out.
+ */
+mxFastOrganicLayout.prototype.vertexArray;
+
+/**
+ * Variable: dispX
+ * 
+ * An array of locally stored X co-ordinate displacements for the vertices.
+ */
+mxFastOrganicLayout.prototype.dispX;
+
+/**
+ * Variable: dispY
+ * 
+ * An array of locally stored Y co-ordinate displacements for the vertices.
+ */
+mxFastOrganicLayout.prototype.dispY;
+
+/**
+ * Variable: cellLocation
+ * 
+ * An array of locally stored co-ordinate positions for the vertices.
+ */
+mxFastOrganicLayout.prototype.cellLocation;
+
+/**
+ * Variable: radius
+ * 
+ * The approximate radius of each cell, nodes only.
+ */
+mxFastOrganicLayout.prototype.radius;
+
+/**
+ * Variable: radiusSquared
+ * 
+ * The approximate radius squared of each cell, nodes only.
+ */
+mxFastOrganicLayout.prototype.radiusSquared;
+
+/**
+ * Variable: isMoveable
+ * 
+ * Array of booleans representing the movable states of the vertices.
+ */
+mxFastOrganicLayout.prototype.isMoveable;
+
+/**
+ * Variable: neighbours
+ * 
+ * Local copy of cell neighbours.
+ */
+mxFastOrganicLayout.prototype.neighbours;
+
+/**
+ * Variable: indices
+ * 
+ * Hashtable from cells to local indices.
+ */
+mxFastOrganicLayout.prototype.indices;
+
+/**
+ * Variable: allowedToRun
+ * 
+ * Boolean flag that specifies if the layout is allowed to run. If this is
+ * set to false, then the layout exits in the following iteration.
+ */
+mxFastOrganicLayout.prototype.allowedToRun = true;
+
+/**
+ * Function: isVertexIgnored
+ * 
+ * Returns a boolean indicating if the given <mxCell> should be ignored as a
+ * vertex. This returns true if the cell has no connections.
+ * 
+ * Parameters:
+ * 
+ * vertex - <mxCell> whose ignored state should be returned.
+ */
+mxFastOrganicLayout.prototype.isVertexIgnored = function(vertex)
+{
+	return mxGraphLayout.prototype.isVertexIgnored.apply(this, arguments) ||
+		this.graph.getConnections(vertex).length == 0;
+};
+
+/**
+ * Function: execute
+ * 
+ * Implements <mxGraphLayout.execute>. This operates on all children of the
+ * given parent where <isVertexIgnored> returns false.
+ */
+mxFastOrganicLayout.prototype.execute = function(parent)
+{
+	var model = this.graph.getModel();
+	this.vertexArray = [];
+	var cells = this.graph.getChildVertices(parent);
+	
+	for (var i = 0; i < cells.length; i++)
+	{
+		if (!this.isVertexIgnored(cells[i]))
+		{
+			this.vertexArray.push(cells[i]);
+		}
+	}
+	
+	var initialBounds = (this.useInputOrigin) ?
+			this.graph.getBoundingBoxFromGeometry(this.vertexArray) :
+				null;
+	var n = this.vertexArray.length;
+
+	this.indices = [];
+	this.dispX = [];
+	this.dispY = [];
+	this.cellLocation = [];
+	this.isMoveable = [];
+	this.neighbours = [];
+	this.radius = [];
+	this.radiusSquared = [];
+
+	if (this.forceConstant < 0.001)
+	{
+		this.forceConstant = 0.001;
+	}
+
+	this.forceConstantSquared = this.forceConstant * this.forceConstant;
+
+	// Create a map of vertices first. This is required for the array of
+	// arrays called neighbours which holds, for each vertex, a list of
+	// ints which represents the neighbours cells to that vertex as
+	// the indices into vertexArray
+	for (var i = 0; i < this.vertexArray.length; i++)
+	{
+		var vertex = this.vertexArray[i];
+		this.cellLocation[i] = [];
+		
+		// Set up the mapping from array indices to cells
+		var id = mxObjectIdentity.get(vertex);
+		this.indices[id] = i;
+		var bounds = this.getVertexBounds(vertex);
+
+		// Set the X,Y value of the internal version of the cell to
+		// the center point of the vertex for better positioning
+		var width = bounds.width;
+		var height = bounds.height;
+		
+		// Randomize (0, 0) locations
+		var x = bounds.x;
+		var y = bounds.y;
+		
+		this.cellLocation[i][0] = x + width / 2.0;
+		this.cellLocation[i][1] = y + height / 2.0;
+		this.radius[i] = Math.min(width, height);
+		this.radiusSquared[i] = this.radius[i] * this.radius[i];
+	}
+
+	// Moves cell location back to top-left from center locations used in
+	// algorithm, resetting the edge points is part of the transaction
+	model.beginUpdate();
+	try
+	{
+		for (var i = 0; i < n; i++)
+		{
+			this.dispX[i] = 0;
+			this.dispY[i] = 0;
+			this.isMoveable[i] = this.isVertexMovable(this.vertexArray[i]);
+
+			// Get lists of neighbours to all vertices, translate the cells
+			// obtained in indices into vertexArray and store as an array
+			// against the orginial cell index
+			var edges = this.graph.getConnections(this.vertexArray[i], parent);
+			var cells = this.graph.getOpposites(edges, this.vertexArray[i]);
+			this.neighbours[i] = [];
+
+			for (var j = 0; j < cells.length; j++)
+			{
+				// Resets the points on the traversed edge
+				if (this.resetEdges)
+				{
+					this.graph.resetEdge(edges[j]);
+				}
+
+			    if (this.disableEdgeStyle)
+			    {
+			    	this.setEdgeStyleEnabled(edges[j], false);
+			    }
+
+				// Looks the cell up in the indices dictionary
+				var id = mxObjectIdentity.get(cells[j]);
+				var index = this.indices[id];
+
+				// Check the connected cell in part of the vertex list to be
+				// acted on by this layout
+				if (index != null)
+				{
+					this.neighbours[i][j] = index;
+				}
+
+				// Else if index of the other cell doesn't correspond to
+				// any cell listed to be acted upon in this layout. Set
+				// the index to the value of this vertex (a dummy self-loop)
+				// so the attraction force of the edge is not calculated
+				else
+				{
+					this.neighbours[i][j] = i;
+				}
+			}
+		}
+		this.temperature = this.initialTemp;
+
+		// If max number of iterations has not been set, guess it
+		if (this.maxIterations == 0)
+		{
+			this.maxIterations = 20 * Math.sqrt(n);
+		}
+		
+		// Main iteration loop
+		for (this.iteration = 0; this.iteration < this.maxIterations; this.iteration++)
+		{
+			if (!this.allowedToRun)
+			{
+				return;
+			}
+			
+			// Calculate repulsive forces on all vertices
+			this.calcRepulsion();
+
+			// Calculate attractive forces through edges
+			this.calcAttraction();
+
+			this.calcPositions();
+			this.reduceTemperature();
+		}
+
+		var minx = null;
+		var miny = null;
+		
+		for (var i = 0; i < this.vertexArray.length; i++)
+		{
+			var vertex = this.vertexArray[i];
+			
+			if (this.isVertexMovable(vertex))
+			{
+				var bounds = this.getVertexBounds(vertex);
+				
+				if (bounds != null)
+				{
+					this.cellLocation[i][0] -= bounds.width / 2.0;
+					this.cellLocation[i][1] -= bounds.height / 2.0;
+					
+					var x = this.graph.snap(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/airavata-kubernetes/web-console/src/assets/js/layout/mxGraphLayout.js b/airavata-kubernetes/web-console/src/assets/js/layout/mxGraphLayout.js
new file mode 100644
index 0000000..7a59a3e
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/layout/mxGraphLayout.js
@@ -0,0 +1,461 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxGraphLayout
+ * 
+ * Base class for all layout algorithms in mxGraph. Main public functions are
+ * <move> for handling a moved cell within a layouted parent, and <execute> for
+ * running the layout on a given parent cell.
+ *
+ * Known Subclasses:
+ *
+ * <mxCircleLayout>, <mxCompactTreeLayout>, <mxCompositeLayout>,
+ * <mxFastOrganicLayout>, <mxParallelEdgeLayout>, <mxPartitionLayout>,
+ * <mxStackLayout>
+ * 
+ * Constructor: mxGraphLayout
+ *
+ * Constructs a new layout using the given layouts.
+ *
+ * Arguments:
+ * 
+ * graph - Enclosing 
+ */
+function mxGraphLayout(graph)
+{
+	this.graph = graph;
+};
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxGraphLayout.prototype.graph = null;
+
+/**
+ * Variable: useBoundingBox
+ *
+ * Boolean indicating if the bounding box of the label should be used if
+ * its available. Default is true.
+ */
+mxGraphLayout.prototype.useBoundingBox = true;
+
+/**
+ * Variable: parent
+ *
+ * The parent cell of the layout, if any
+ */
+mxGraphLayout.prototype.parent = null;
+
+/**
+ * Function: moveCell
+ * 
+ * Notified when a cell is being moved in a parent that has automatic
+ * layout to update the cell state (eg. index) so that the outcome of the
+ * layout will position the vertex as close to the point (x, y) as
+ * possible.
+ * 
+ * Empty implementation.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> which has been moved.
+ * x - X-coordinate of the new cell location.
+ * y - Y-coordinate of the new cell location.
+ */
+mxGraphLayout.prototype.moveCell = function(cell, x, y) { };
+
+/**
+ * Function: execute
+ * 
+ * Executes the layout algorithm for the children of the given parent.
+ * 
+ * Parameters:
+ * 
+ * parent - <mxCell> whose children should be layed out.
+ */
+mxGraphLayout.prototype.execute = function(parent) { };
+
+/**
+ * Function: getGraph
+ * 
+ * Returns the graph that this layout operates on.
+ */
+mxGraphLayout.prototype.getGraph = function()
+{
+	return this.graph;
+};
+
+/**
+ * Function: getConstraint
+ * 
+ * Returns the constraint for the given key and cell. The optional edge and
+ * source arguments are used to return inbound and outgoing routing-
+ * constraints for the given edge and vertex. This implementation always
+ * returns the value for the given key in the style of the given cell.
+ * 
+ * Parameters:
+ * 
+ * key - Key of the constraint to be returned.
+ * cell - <mxCell> whose constraint should be returned.
+ * edge - Optional <mxCell> that represents the connection whose constraint
+ * should be returned. Default is null.
+ * source - Optional boolean that specifies if the connection is incoming
+ * or outgoing. Default is null.
+ */
+mxGraphLayout.prototype.getConstraint = function(key, cell, edge, source)
+{
+	var state = this.graph.view.getState(cell);
+	var style = (state != null) ? state.style : this.graph.getCellStyle(cell);
+	
+	return (style != null) ? style[key] : null;
+};
+
+/**
+ * Function: traverse
+ * 
+ * Traverses the (directed) graph invoking the given function for each
+ * visited vertex and edge. The function is invoked with the current vertex
+ * and the incoming edge as a parameter. This implementation makes sure
+ * each vertex is only visited once. The function may return false if the
+ * traversal should stop at the given vertex.
+ * 
+ * Example:
+ * 
+ * (code)
+ * mxLog.show();
+ * var cell = graph.getSelectionCell();
+ * graph.traverse(cell, false, function(vertex, edge)
+ * {
+ *   mxLog.debug(graph.getLabel(vertex));
+ * });
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * vertex - <mxCell> that represents the vertex where the traversal starts.
+ * directed - Optional boolean indicating if edges should only be traversed
+ * from source to target. Default is true.
+ * func - Visitor function that takes the current vertex and the incoming
+ * edge as arguments. The traversal stops if the function returns false.
+ * edge - Optional <mxCell> that represents the incoming edge. This is
+ * null for the first step of the traversal.
+ * visited - Optional <mxDictionary> of cell paths for the visited cells.
+ */
+mxGraphLayout.traverse = function(vertex, directed, func, edge, visited)
+{
+	if (func != null && vertex != null)
+	{
+		directed = (directed != null) ? directed : true;
+		visited = visited || new mxDictionary();
+		
+		if (!visited.get(vertex))
+		{
+			visited.put(vertex, true);
+			var result = func(vertex, edge);
+			
+			if (result == null || result)
+			{
+				var edgeCount = this.graph.model.getEdgeCount(vertex);
+				
+				if (edgeCount > 0)
+				{
+					for (var i = 0; i < edgeCount; i++)
+					{
+						var e = this.graph.model.getEdgeAt(vertex, i);
+						var isSource = this.graph.model.getTerminal(e, true) == vertex;
+												
+						if (!directed || isSource)
+						{
+							var next = this.graph.view.getVisibleTerminal(e, !isSource);
+							this.traverse(next, directed, func, e, visited);
+						}
+					}
+				}
+			}
+		}
+	}
+};
+
+/**
+ * Function: isVertexMovable
+ * 
+ * Returns a boolean indicating if the given <mxCell> is movable or
+ * bendable by the algorithm. This implementation returns true if the given
+ * cell is movable in the graph.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose movable state should be returned.
+ */
+mxGraphLayout.prototype.isVertexMovable = function(cell)
+{
+	return this.graph.isCellMovable(cell);
+};
+
+/**
+ * Function: isVertexIgnored
+ * 
+ * Returns a boolean indicating if the given <mxCell> should be ignored by
+ * the algorithm. This implementation returns false for all vertices.
+ * 
+ * Parameters:
+ * 
+ * vertex - <mxCell> whose ignored state should be returned.
+ */
+mxGraphLayout.prototype.isVertexIgnored = function(vertex)
+{
+	return !this.graph.getModel().isVertex(vertex) ||
+		!this.graph.isCellVisible(vertex);
+};
+
+/**
+ * Function: isEdgeIgnored
+ * 
+ * Returns a boolean indicating if the given <mxCell> should be ignored by
+ * the algorithm. This implementation returns false for all vertices.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose ignored state should be returned.
+ */
+mxGraphLayout.prototype.isEdgeIgnored = function(edge)
+{
+	var model = this.graph.getModel();
+	
+	return !model.isEdge(edge) ||
+		!this.graph.isCellVisible(edge) ||
+		model.getTerminal(edge, true) == null ||
+		model.getTerminal(edge, false) == null;
+};
+
+/**
+ * Function: setEdgeStyleEnabled
+ * 
+ * Disables or enables the edge style of the given edge.
+ */
+mxGraphLayout.prototype.setEdgeStyleEnabled = function(edge, value)
+{
+	this.graph.setCellStyles(mxConstants.STYLE_NOEDGESTYLE,
+			(value) ? '0' : '1', [edge]);
+};
+
+/**
+ * Function: setOrthogonalEdge
+ * 
+ * Disables or enables orthogonal end segments of the given edge.
+ */
+mxGraphLayout.prototype.setOrthogonalEdge = function(edge, value)
+{
+	this.graph.setCellStyles(mxConstants.STYLE_ORTHOGONAL,
+			(value) ? '1' : '0', [edge]);
+};
+
+/**
+ * Function: getParentOffset
+ * 
+ * Determines the offset of the given parent to the parent
+ * of the layout
+ */
+mxGraphLayout.prototype.getParentOffset = function(parent)
+{
+	var result = new mxPoint();
+
+	if (parent != null && parent != this.parent)
+	{
+		var model = this.graph.getModel();
+
+		if (model.isAncestor(this.parent, parent))
+		{
+			var parentGeo = model.getGeometry(parent);
+
+			while (parent != this.parent)
+			{
+				result.x = result.x + parentGeo.x;
+				result.y = result.y + parentGeo.y;
+
+				parent = model.getParent(parent);;
+				parentGeo = model.getGeometry(parent);
+			}
+		}
+	}
+
+	return result;
+};
+
+/**
+ * Function: setEdgePoints
+ * 
+ * Replaces the array of mxPoints in the geometry of the given edge
+ * with the given array of mxPoints.
+ */
+mxGraphLayout.prototype.setEdgePoints = function(edge, points)
+{
+	if (edge != null)
+	{
+		var model = this.graph.model;
+		var geometry = model.getGeometry(edge);
+
+		if (geometry == null)
+		{
+			geometry = new mxGeometry();
+			geometry.setRelative(true);
+		}
+		else
+		{
+			geometry = geometry.clone();
+		}
+
+		if (this.parent != null && points != null)
+		{
+			var parent = model.getParent(edge);
+
+			var parentOffset = this.getParentOffset(parent);
+
+			for (var i = 0; i < points.length; i++)
+			{
+				points[i].x = points[i].x - parentOffset.x;
+				points[i].y = points[i].y - parentOffset.y;
+			}
+		}
+
+		geometry.points = points;
+		model.setGeometry(edge, geometry);
+	}
+};
+
+/**
+ * Function: setVertexLocation
+ * 
+ * Sets the new position of the given cell taking into account the size of
+ * the bounding box if <useBoundingBox> is true. The change is only carried
+ * out if the new location is not equal to the existing location, otherwise
+ * the geometry is not replaced with an updated instance. The new or old
+ * bounds are returned (including overlapping labels).
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose geometry is to be set.
+ * x - Integer that defines the x-coordinate of the new location.
+ * y - Integer that defines the y-coordinate of the new location.
+ */
+mxGraphLayout.prototype.setVertexLocation = function(cell, x, y)
+{
+	var model = this.graph.getModel();
+	var geometry = model.getGeometry(cell);
+	var result = null;
+	
+	if (geometry != null)
+	{
+		result = new mxRectangle(x, y, geometry.width, geometry.height);
+		
+		// Checks for oversize labels and shifts the result
+		// TODO: Use mxUtils.getStringSize for label bounds
+		if (this.useBoundingBox)
+		{
+			var state = this.graph.getView().getState(cell);
+			
+			if (state != null && state.text != null && state.text.boundingBox != null)
+			{
+				var scale = this.graph.getView().scale;
+				var box = state.text.boundingBox;
+				
+				if (state.text.boundingBox.x < state.x)
+				{
+					x += (state.x - box.x) / scale;
+					result.width = box.width;
+				}
+				
+				if (state.text.boundingBox.y < state.y)
+				{
+					y += (state.y - box.y) / scale;
+					result.height = box.height;
+				}
+			}
+		}
+
+		if (this.parent != null)
+		{
+			var parent = model.getParent(cell);
+
+			if (parent != null && parent != this.parent)
+			{
+				var parentOffset = this.getParentOffset(parent);
+
+				x = x - parentOffset.x;
+				y = y - parentOffset.y;
+			}
+		}
+
+		if (geometry.x != x || geometry.y != y)
+		{
+			geometry = geometry.clone();
+			geometry.x = x;
+			geometry.y = y;
+			
+			model.setGeometry(cell, geometry);
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: getVertexBounds
+ * 
+ * Returns an <mxRectangle> that defines the bounds of the given cell or
+ * the bounding box if <useBoundingBox> is true.
+ */
+mxGraphLayout.prototype.getVertexBounds = function(cell)
+{
+	var geo = this.graph.getModel().getGeometry(cell);
+
+	// Checks for oversize label bounding box and corrects
+	// the return value accordingly
+	// TODO: Use mxUtils.getStringSize for label bounds
+	if (this.useBoundingBox)
+	{
+		var state = this.graph.getView().getState(cell);
+
+		if (state != null && state.text != null && state.text.boundingBox != null)
+		{
+			var scale = this.graph.getView().scale;
+			var tmp = state.text.boundingBox;
+
+			var dx0 = Math.max(state.x - tmp.x, 0) / scale;
+			var dy0 = Math.max(state.y - tmp.y, 0) / scale;
+			var dx1 = Math.max((tmp.x + tmp.width) - (state.x + state.width), 0) / scale;
+  			var dy1 = Math.max((tmp.y + tmp.height) - (state.y + state.height), 0) / scale;
+
+			geo = new mxRectangle(geo.x - dx0, geo.y - dy0, geo.width + dx0 + dx1, geo.height + dy0 + dy1);
+		}
+	}
+
+	if (this.parent != null)
+	{
+		var parent = this.graph.getModel().getParent(cell);
+		geo = geo.clone();
+
+		if (parent != null && parent != this.parent)
+		{
+			var parentOffset = this.getParentOffset(parent);
+			geo.x = geo.x + parentOffset.x;
+			geo.y = geo.y + parentOffset.y;
+		}
+	}
+
+	return new mxRectangle(geo.x, geo.y, geo.width, geo.height);
+};
+
+/**
+ * Function: arrangeGroups
+ * 
+ * Shortcut to <mxGraph.updateGroupBounds> with moveGroup set to true.
+ */
+mxGraphLayout.prototype.arrangeGroups = function(cells, border, topBorder, rightBorder, bottomBorder, leftBorder)
+{
+	return this.graph.updateGroupBounds(cells, border, true, topBorder, rightBorder, bottomBorder, leftBorder);
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/layout/mxParallelEdgeLayout.js b/airavata-kubernetes/web-console/src/assets/js/layout/mxParallelEdgeLayout.js
new file mode 100644
index 0000000..73d436a
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/layout/mxParallelEdgeLayout.js
@@ -0,0 +1,225 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxParallelEdgeLayout
+ * 
+ * Extends <mxGraphLayout> for arranging parallel edges. This layout works
+ * on edges for all pairs of vertices where there is more than one edge
+ * connecting the latter.
+ * 
+ * Example:
+ * 
+ * (code)
+ * var layout = new mxParallelEdgeLayout(graph);
+ * layout.execute(graph.getDefaultParent());
+ * (end)
+ * 
+ * To run the layout for the parallel edges of a changed edge only, the
+ * following code can be used.
+ * 
+ * (code)
+ * var layout = new mxParallelEdgeLayout(graph);
+ * 
+ * graph.addListener(mxEvent.CELL_CONNECTED, function(sender, evt)
+ * {
+ *   var model = graph.getModel();
+ *   var edge = evt.getProperty('edge');
+ *   var src = model.getTerminal(edge, true);
+ *   var trg = model.getTerminal(edge, false);
+ *   
+ *   layout.isEdgeIgnored = function(edge2)
+ *   {
+ *     var src2 = model.getTerminal(edge2, true);
+ *     var trg2 = model.getTerminal(edge2, false);
+ *     
+ *     return !(model.isEdge(edge2) && ((src == src2 && trg == trg2) || (src == trg2 && trg == src2)));
+ *   };
+ *   
+ *   layout.execute(graph.getDefaultParent());
+ * });
+ * (end)
+ * 
+ * Constructor: mxCompactTreeLayout
+ * 
+ * Constructs a new fast organic layout for the specified graph.
+ */
+function mxParallelEdgeLayout(graph)
+{
+	mxGraphLayout.call(this, graph);
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxParallelEdgeLayout.prototype = new mxGraphLayout();
+mxParallelEdgeLayout.prototype.constructor = mxParallelEdgeLayout;
+
+/**
+ * Variable: spacing
+ * 
+ * Defines the spacing between the parallels. Default is 20.
+ */
+mxParallelEdgeLayout.prototype.spacing = 20;
+
+/**
+ * Function: execute
+ * 
+ * Implements <mxGraphLayout.execute>.
+ */
+mxParallelEdgeLayout.prototype.execute = function(parent)
+{
+	var lookup = this.findParallels(parent);
+	
+	this.graph.model.beginUpdate();	
+	try
+	{
+		for (var i in lookup)
+		{
+			var parallels = lookup[i];
+
+			if (parallels.length > 1)
+			{
+				this.layout(parallels);
+			}
+		}
+	}
+	finally
+	{
+		this.graph.model.endUpdate();
+	}
+};
+
+/**
+ * Function: findParallels
+ * 
+ * Finds the parallel edges in the given parent.
+ */
+mxParallelEdgeLayout.prototype.findParallels = function(parent)
+{
+	var model = this.graph.getModel();
+	var lookup = [];
+	var childCount = model.getChildCount(parent);
+	
+	for (var i = 0; i < childCount; i++)
+	{
+		var child = model.getChildAt(parent, i);
+		
+		if (!this.isEdgeIgnored(child))
+		{
+			var id = this.getEdgeId(child);
+			
+			if (id != null)
+			{
+				if (lookup[id] == null)
+				{
+					lookup[id] = [];
+				}
+				
+				lookup[id].push(child);
+			}
+		}
+	}
+	
+	return lookup;
+};
+
+/**
+ * Function: getEdgeId
+ * 
+ * Returns a unique ID for the given edge. The id is independent of the
+ * edge direction and is built using the visible terminal of the given
+ * edge.
+ */
+mxParallelEdgeLayout.prototype.getEdgeId = function(edge)
+{
+	var view = this.graph.getView();
+	
+	// Cannot used cached visible terminal because this could be triggered in BEFORE_UNDO
+	var src = view.getVisibleTerminal(edge, true);
+	var trg = view.getVisibleTerminal(edge, false);
+
+	if (src != null && trg != null)
+	{
+		src = mxObjectIdentity.get(src);
+		trg = mxObjectIdentity.get(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 view = this.graph.getView();
+	var model = this.graph.getModel();
+	var src = model.getGeometry(view.getVisibleTerminal(edge, true));
+	var trg = model.getGeometry(view.getVisibleTerminal(edge, false));
+	
+	// Routes multiple loops
+	if (src == trg)
+	{
+		var x0 = src.x + src.width + this.spacing;
+		var y0 = src.y + src.height / 2;
+
+		for (var i = 0; i < parallels.length; i++)
+		{
+			this.route(parallels[i], x0, y0);
+			x0 += this.spacing;
+		}
+	}
+	else if (src != null && trg != null)
+	{
+		// Routes parallel edges
+		var scx = src.x + src.width / 2;
+		var scy = src.y + src.height / 2;
+		
+		var tcx = trg.x + trg.width / 2;
+		var tcy = trg.y + trg.height / 2;
+		
+		var dx = tcx - scx;
+		var dy = tcy - scy;
+
+		var len = Math.sqrt(dx * dx + dy * dy);
+		
+		if (len > 0)
+		{
+			var x0 = scx + dx / 2;
+			var y0 = scy + dy / 2;
+			
+			var nx = dy * this.spacing / len;
+			var ny = dx * this.spacing / len;
+			
+			x0 += nx * (parallels.length - 1) / 2;
+			y0 -= ny * (parallels.length - 1) / 2;
+	
+			for (var i = 0; i < parallels.length; i++)
+			{
+				this.route(parallels[i], x0, y0);
+				x0 -= nx;
+				y0 += ny;
+			}
+		}
+	}
+};
+
+/**
+ * Function: route
+ * 
+ * Routes the given edge via the given point.
+ */
+mxParallelEdgeLayout.prototype.route = function(edge, x, y)
+{
+	if (this.graph.isCellMovable(edge))
+	{
+		this.setEdgePoints(edge, [new mxPoint(x, y)]);
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/layout/mxPartitionLayout.js b/airavata-kubernetes/web-console/src/assets/js/layout/mxPartitionLayout.js
new file mode 100644
index 0000000..ad66f54
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/layout/mxPartitionLayout.js
@@ -0,0 +1,240 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxPartitionLayout
+ * 
+ * Extends <mxGraphLayout> for partitioning the parent cell vertically or
+ * horizontally by filling the complete area with the child cells. A horizontal
+ * layout partitions the height of the given parent whereas a a non-horizontal
+ * layout partitions the width. If the parent is a layer (that is, a child of
+ * the root node), then the current graph size is partitioned. The children do
+ * not need to be connected for this layout to work.
+ * 
+ * Example:
+ * 
+ * (code)
+ * var layout = new mxPartitionLayout(graph, true, 10, 20);
+ * layout.execute(graph.getDefaultParent());
+ * (end)
+ * 
+ * Constructor: mxPartitionLayout
+ * 
+ * Constructs a new stack layout layout for the specified graph,
+ * spacing, orientation and offset.
+ */
+function mxPartitionLayout(graph, horizontal, spacing, border)
+{
+	mxGraphLayout.call(this, graph);
+	this.horizontal = (horizontal != null) ? horizontal : true;
+	this.spacing = spacing || 0;
+	this.border = border || 0;
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxPartitionLayout.prototype = new mxGraphLayout();
+mxPartitionLayout.prototype.constructor = mxPartitionLayout;
+
+/**
+ * Variable: horizontal
+ * 
+ * Boolean indicating the direction in which the space is partitioned.
+ * Default is true.
+ */
+mxPartitionLayout.prototype.horizontal = null;
+
+/**
+ * Variable: spacing
+ * 
+ * Integer that specifies the absolute spacing in pixels between the
+ * children. Default is 0.
+ */
+mxPartitionLayout.prototype.spacing = null;
+
+/**
+ * Variable: border
+ * 
+ * Integer that specifies the absolute inset in pixels for the parent that
+ * contains the children. Default is 0.
+ */
+mxPartitionLayout.prototype.border = null;
+
+/**
+ * Variable: resizeVertices
+ * 
+ * Boolean that specifies if vertices should be resized. Default is true.
+ */
+mxPartitionLayout.prototype.resizeVertices = true;
+
+/**
+ * Function: isHorizontal
+ * 
+ * Returns <horizontal>.
+ */
+mxPartitionLayout.prototype.isHorizontal = function()
+{
+	return this.horizontal;
+};
+
+/**
+ * Function: moveCell
+ * 
+ * Implements <mxGraphLayout.moveCell>.
+ */
+mxPartitionLayout.prototype.moveCell = function(cell, x, y)
+{
+	var model = this.graph.getModel();
+	var parent = model.getParent(cell);
+	
+	if (cell != null &&
+		parent != null)
+	{
+		var i = 0;
+		var last = 0;
+		var childCount = model.getChildCount(parent);
+		
+		// Finds index of the closest swimlane
+		// TODO: Take into account the orientation
+		for (i = 0; i < childCount; i++)
+		{
+			var child = model.getChildAt(parent, i);
+			var bounds = this.getVertexBounds(child);
+			
+			if (bounds != null)
+			{
+				var tmp = bounds.x + bounds.width / 2;
+				
+				if (last < x && tmp > x)
+				{
+					break;
+				}
+				
+				last = tmp;
+			}
+		}
+		
+		// Changes child order in parent
+		var idx = parent.getIndex(cell);
+		idx = Math.max(0, i - ((i > idx) ? 1 : 0));
+		
+		model.add(parent, cell, idx);
+	}
+};
+
+/**
+ * Function: execute
+ * 
+ * Implements <mxGraphLayout.execute>. All children where <isVertexIgnored>
+ * returns false and <isVertexMovable> returns true are modified.
+ */
+mxPartitionLayout.prototype.execute = function(parent)
+{
+	var horizontal = this.isHorizontal();
+	var model = this.graph.getModel();
+	var pgeo = model.getGeometry(parent);
+	
+	// Handles special case where the parent is either a layer with no
+	// geometry or the current root of the view in which case the size
+	// of the graph's container will be used.
+	if (this.graph.container != null &&
+		((pgeo == null &&
+		model.isLayer(parent)) ||
+		parent == this.graph.getView().currentRoot))
+	{
+		var width = this.graph.container.offsetWidth - 1;
+		var height = this.graph.container.offsetHeight - 1;
+		pgeo = new mxRectangle(0, 0, width, height);
+	}
+
+	if (pgeo != null)
+	{
+		var children = [];
+		var childCount = model.getChildCount(parent);
+		
+		for (var i = 0; i < childCount; i++)
+		{
+			var child = model.getChildAt(parent, i);
+			
+			if (!this.isVertexIgnored(child) &&
+				this.isVertexMovable(child))
+			{
+				children.push(child);
+			}
+		}
+		
+		var n = children.length;
+
+		if (n > 0)
+		{
+			var x0 = this.border;
+			var y0 = this.border;
+			var other = (horizontal) ? pgeo.height : pgeo.width;
+			other -= 2 * this.border;
+
+			var size = (this.graph.isSwimlane(parent)) ?
+				this.graph.getStartSize(parent) :
+				new mxRectangle();
+
+			other -= (horizontal) ? size.height : size.width;
+			x0 = x0 + size.width;
+			y0 = y0 + size.height;
+
+			var tmp = this.border + (n - 1) * this.spacing;
+			var value = (horizontal) ?
+				((pgeo.width - x0 - tmp) / n) :
+				((pgeo.height - y0 - tmp) / n);
+			
+			// Avoids negative values, that is values where the sum of the
+			// spacing plus the border is larger then the available space
+			if (value > 0)
+			{
+				model.beginUpdate();
+				try
+				{
+					for (var i = 0; i < n; i++)
+					{
+						var child = children[i];
+						var geo = model.getGeometry(child);
+					
+						if (geo != null)
+						{
+							geo = geo.clone();
+							geo.x = x0;
+							geo.y = y0;
+
+							if (horizontal)
+							{
+								if (this.resizeVertices)
+								{
+									geo.width = value;
+									geo.height = other;
+								}
+								
+								x0 += value + this.spacing;
+							}
+							else
+							{
+								if (this.resizeVertices)
+								{
+									geo.height = value;
+									geo.width = other;
+								}
+								
+								y0 += value + this.spacing;
+							}
+
+							model.setGeometry(child, geo);
+						}
+					}
+				}
+				finally
+				{
+					model.endUpdate();
+				}
+			}
+		}
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/layout/mxRadialTreeLayout.js b/airavata-kubernetes/web-console/src/assets/js/layout/mxRadialTreeLayout.js
new file mode 100644
index 0000000..427c366
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/layout/mxRadialTreeLayout.js
@@ -0,0 +1,317 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxRadialTreeLayout
+ * 
+ * Extends <mxGraphLayout> to implement a radial tree algorithm. This
+ * layout is suitable for graphs that have no cycles (trees). Vertices that are
+ * not connected to the tree will be ignored by this layout.
+ * 
+ * Example:
+ * 
+ * (code)
+ * var layout = new mxRadialTreeLayout(graph);
+ * layout.execute(graph.getDefaultParent());
+ * (end)
+ * 
+ * Constructor: mxRadialTreeLayout
+ * 
+ * Constructs a new radial tree layout for the specified graph
+ */
+function mxRadialTreeLayout(graph)
+{
+	mxCompactTreeLayout.call(this, graph , false);
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxUtils.extend(mxRadialTreeLayout, mxCompactTreeLayout);
+
+/**
+ * Variable: angleOffset
+ *
+ * The initial offset to compute the angle position.
+ */
+mxRadialTreeLayout.prototype.angleOffset = 0.5;
+
+/**
+ * Variable: rootx
+ *
+ * The X co-ordinate of the root cell
+ */
+mxRadialTreeLayout.prototype.rootx = 0;
+
+/**
+ * Variable: rooty
+ *
+ * The Y co-ordinate of the root cell
+ */
+mxRadialTreeLayout.prototype.rooty = 0;
+
+/**
+ * Variable: levelDistance
+ *
+ * Holds the levelDistance. Default is 120.
+ */
+mxRadialTreeLayout.prototype.levelDistance = 120;
+
+/**
+ * Variable: nodeDistance
+ *
+ * Holds the nodeDistance. Default is 10.
+ */
+mxRadialTreeLayout.prototype.nodeDistance = 10;
+
+/**
+ * Variable: autoRadius
+ * 
+ * Specifies if the radios should be computed automatically
+ */
+mxRadialTreeLayout.prototype.autoRadius = false;
+
+/**
+ * Variable: sortEdges
+ * 
+ * Specifies if edges should be sorted according to the order of their
+ * opposite terminal cell in the model.
+ */
+mxRadialTreeLayout.prototype.sortEdges = false;
+
+/**
+ * Variable: rowMinX
+ * 
+ * Array of leftmost x coordinate of each row
+ */
+mxRadialTreeLayout.prototype.rowMinX = [];
+
+/**
+ * Variable: rowMaxX
+ * 
+ * Array of rightmost x coordinate of each row
+ */
+mxRadialTreeLayout.prototype.rowMaxX = [];
+
+/**
+ * Variable: rowMinCenX
+ * 
+ * Array of x coordinate of leftmost vertex of each row
+ */
+mxRadialTreeLayout.prototype.rowMinCenX = [];
+
+/**
+ * Variable: rowMaxCenX
+ * 
+ * Array of x coordinate of rightmost vertex of each row
+ */
+mxRadialTreeLayout.prototype.rowMaxCenX = [];
+
+/**
+ * Variable: rowRadi
+ * 
+ * Array of y deltas of each row behind root vertex, also the radius in the tree
+ */
+mxRadialTreeLayout.prototype.rowRadi = [];
+
+/**
+ * Variable: row
+ * 
+ * Array of vertices on each row
+ */
+mxRadialTreeLayout.prototype.row = [];
+
+/**
+ * Function: isVertexIgnored
+ * 
+ * Returns a boolean indicating if the given <mxCell> should be ignored as a
+ * vertex. This returns true if the cell has no connections.
+ * 
+ * Parameters:
+ * 
+ * vertex - <mxCell> whose ignored state should be returned.
+ */
+mxRadialTreeLayout.prototype.isVertexIgnored = function(vertex)
+{
+	return mxGraphLayout.prototype.isVertexIgnored.apply(this, arguments) ||
+		this.graph.getConnections(vertex).length == 0;
+};
+
+/**
+ * Function: execute
+ * 
+ * Implements <mxGraphLayout.execute>.
+ * 
+ * If the parent has any connected edges, then it is used as the root of
+ * the tree. Else, <mxGraph.findTreeRoots> will be used to find a suitable
+ * root node within the set of children of the given parent.
+ * 
+ * Parameters:
+ * 
+ * parent - <mxCell> whose children should be laid out.
+ * root - Optional <mxCell> that will be used as the root of the tree.
+ */
+mxRadialTreeLayout.prototype.execute = function(parent, root)
+{
+	this.parent = parent;
+	
+	this.useBoundingBox = false;
+	this.edgeRouting = false;
+	//this.horizontal = false;
+
+	mxCompactTreeLayout.prototype.execute.apply(this, arguments);
+	
+	var bounds = null;
+	var rootBounds = this.getVertexBounds(this.root);
+	this.centerX = rootBounds.x + rootBounds.width / 2;
+	this.centerY = rootBounds.y + rootBounds.height / 2;
+
+	// Calculate the bounds of the involved vertices directly from the values set in the compact tree
+	for (var vertex in this.visited)
+	{
+		var vertexBounds = this.getVertexBounds(this.visited[vertex]);
+		bounds = (bounds != null) ? bounds : vertexBounds.clone();
+		bounds.add(vertexBounds);
+	}
+	
+	this.calcRowDims([this.node], 0);
+	
+	var maxLeftGrad = 0;
+	var maxRightGrad = 0;
+
+	// Find the steepest left and right gradients
+	for (var i = 0; i < this.row.length; i++)
+	{
+		var leftGrad = (this.centerX - this.rowMinX[i] - this.nodeDistance) / this.rowRadi[i];
+		var rightGrad = (this.rowMaxX[i] - this.centerX - this.nodeDistance) / this.rowRadi[i];
+		
+		maxLeftGrad = Math.max (maxLeftGrad, leftGrad);
+		maxRightGrad = Math.max (maxRightGrad, rightGrad);
+	}
+	
+	// Extend out row so they meet the maximum gradient and convert to polar co-ords
+	for (var i = 0; i < this.row.length; i++)
+	{
+		var xLeftLimit = this.centerX - this.nodeDistance - maxLeftGrad * this.rowRadi[i];
+		var xRightLimit = this.centerX + this.nodeDistance + maxRightGrad * this.rowRadi[i];
+		var fullWidth = xRightLimit - xLeftLimit;
+		
+		for (var j = 0; j < this.row[i].length; j ++)
+		{
+			var row = this.row[i];
+			var node = row[j];
+			var vertexBounds = this.getVertexBounds(node.cell);
+			var xProportion = (vertexBounds.x + vertexBounds.width / 2 - xLeftLimit) / (fullWidth);
+			var theta =  2 * Math.PI * xProportion;
+			node.theta = theta;
+		}
+	}
+
+	// Post-process from outside inwards to try to align parents with children
+	for (var i = this.row.length - 2; i >= 0; i--)
+	{
+		var row = this.row[i];
+		
+		for (var j = 0; j < row.length; j++)
+		{
+			var node = row[j];
+			var child = node.child;
+			var counter = 0;
+			var totalTheta = 0;
+			
+			while (child != null)
+			{
+				totalTheta += child.theta;
+				counter++;
+				child = child.next;
+			}
+			
+			if (counter > 0)
+			{
+				var averTheta = totalTheta / counter;
+				
+				if (averTheta > node.theta && j < row.length - 1)
+				{
+					var nextTheta = row[j+1].theta;
+					node.theta = Math.min (averTheta, nextTheta - Math.PI/10);
+				}
+				else if (averTheta < node.theta && j > 0 )
+				{
+					var lastTheta = row[j-1].theta;
+					node.theta = Math.max (averTheta, lastTheta + Math.PI/10);
+				}
+			}
+		}
+	}
+	
+	// Set locations
+	for (var i = 0; i < this.row.length; i++)
+	{
+		for (var j = 0; j < this.row[i].length; j ++)
+		{
+			var row = this.row[i];
+			var node = row[j];
+			var vertexBounds = this.getVertexBounds(node.cell);
+			this.setVertexLocation(node.cell,
+									this.centerX - vertexBounds.width / 2 + this.rowRadi[i] * Math.cos(node.theta),
+									this.centerY - vertexBounds.height / 2 + this.rowRadi[i] * Math.sin(node.theta));
+		}
+	}
+};
+
+/**
+ * Function: calcRowDims
+ * 
+ * Recursive function to calculate the dimensions of each row
+ * 
+ * Parameters:
+ * 
+ * row - Array of internal nodes, the children of which are to be processed.
+ * rowNum - Integer indicating which row is being processed.
+ */
+mxRadialTreeLayout.prototype.calcRowDims = function(row, rowNum)
+{
+	if (row == null || row.length == 0)
+	{
+		return;
+	}
+
+	// Place root's children proportionally around the first level
+	this.rowMinX[rowNum] = this.centerX;
+	this.rowMaxX[rowNum] = this.centerX;
+	this.rowMinCenX[rowNum] = this.centerX;
+	this.rowMaxCenX[rowNum] = this.centerX;
+	this.row[rowNum] = [];
+
+	var rowHasChildren = false;
+
+	for (var i = 0; i < row.length; i++)
+	{
+		var child = row[i] != null ? row[i].child : null;
+
+		while (child != null)
+		{
+			var cell = child.cell;
+			vertexBounds = this.getVertexBounds(cell);
+			
+			this.rowMinX[rowNum] = Math.min(vertexBounds.x, this.rowMinX[rowNum]);
+			this.rowMaxX[rowNum] = Math.max(vertexBounds.x + vertexBounds.width, this.rowMaxX[rowNum]);
+			this.rowMinCenX[rowNum] = Math.min(vertexBounds.x + vertexBounds.width / 2, this.rowMinCenX[rowNum]);
+			this.rowMaxCenX[rowNum] = Math.max(vertexBounds.x + vertexBounds.width / 2, this.rowMaxCenX[rowNum]);
+			this.rowRadi[rowNum] = vertexBounds.y - this.getVertexBounds(this.root).y;
+	
+			if (child.child != null)
+			{
+				rowHasChildren = true;
+			}
+			this.row[rowNum].push(child);
+			child = child.next;
+		}
+	}
+	
+	if (rowHasChildren)
+	{
+		this.calcRowDims(this.row[rowNum], rowNum + 1);
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/layout/mxStackLayout.js b/airavata-kubernetes/web-console/src/assets/js/layout/mxStackLayout.js
new file mode 100644
index 0000000..ed46a5b
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/layout/mxStackLayout.js
@@ -0,0 +1,515 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxStackLayout
+ * 
+ * Extends <mxGraphLayout> to create a horizontal or vertical stack of the
+ * child vertices. The children do not need to be connected for this layout
+ * to work.
+ * 
+ * Example:
+ * 
+ * (code)
+ * var layout = new mxStackLayout(graph, true);
+ * layout.execute(graph.getDefaultParent());
+ * (end)
+ * 
+ * Constructor: mxStackLayout
+ * 
+ * Constructs a new stack layout layout for the specified graph,
+ * spacing, orientation and offset.
+ */
+function mxStackLayout(graph, horizontal, spacing, x0, y0, border)
+{
+	mxGraphLayout.call(this, graph);
+	this.horizontal = (horizontal != null) ? horizontal : true;
+	this.spacing = (spacing != null) ? spacing : 0;
+	this.x0 = (x0 != null) ? x0 : 0;
+	this.y0 = (y0 != null) ? y0 : 0;
+	this.border = (border != null) ? border : 0;
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxStackLayout.prototype = new mxGraphLayout();
+mxStackLayout.prototype.constructor = mxStackLayout;
+
+/**
+ * Variable: horizontal
+ *
+ * Specifies the orientation of the layout. Default is true.
+ */
+mxStackLayout.prototype.horizontal = null;
+
+/**
+ * Variable: spacing
+ *
+ * Specifies the spacing between the cells. Default is 0.
+ */
+mxStackLayout.prototype.spacing = null;
+
+/**
+ * Variable: x0
+ *
+ * Specifies the horizontal origin of the layout. Default is 0.
+ */
+mxStackLayout.prototype.x0 = null;
+
+/**
+ * Variable: y0
+ *
+ * Specifies the vertical origin of the layout. Default is 0.
+ */
+mxStackLayout.prototype.y0 = null;
+
+/**
+ * Variable: border
+ *
+ * Border to be added if fill is true. Default is 0.
+ */
+mxStackLayout.prototype.border = 0;
+
+/**
+ * Variable: marginTop
+ * 
+ * Top margin for the child area. Default is 0.
+ */
+mxStackLayout.prototype.marginTop = 0;
+
+/**
+ * Variable: marginLeft
+ * 
+ * Top margin for the child area. Default is 0.
+ */
+mxStackLayout.prototype.marginLeft = 0;
+
+/**
+ * Variable: marginRight
+ * 
+ * Top margin for the child area. Default is 0.
+ */
+mxStackLayout.prototype.marginRight = 0;
+
+/**
+ * Variable: marginBottom
+ * 
+ * Top margin for the child area. Default is 0.
+ */
+mxStackLayout.prototype.marginBottom = 0;
+
+/**
+ * Variable: keepFirstLocation
+ * 
+ * Boolean indicating if the location of the first cell should be
+ * kept, that is, it will not be moved to x0 or y0.
+ */
+mxStackLayout.prototype.keepFirstLocation = false;
+
+/**
+ * Variable: fill
+ * 
+ * Boolean indicating if dimension should be changed to fill out the parent
+ * cell. Default is false.
+ */
+mxStackLayout.prototype.fill = false;
+	
+/**
+ * Variable: resizeParent
+ * 
+ * If the parent should be resized to match the width/height of the
+ * stack. Default is false.
+ */
+mxStackLayout.prototype.resizeParent = false;
+
+/**
+ * Variable: resizeParentMax
+ * 
+ * Use maximum of existing value and new value for resize of parent.
+ * Default is false.
+ */
+mxStackLayout.prototype.resizeParentMax = false;
+
+/**
+ * Variable: resizeLast
+ * 
+ * If the last element should be resized to fill out the parent. Default is
+ * false. If <resizeParent> is true then this is ignored.
+ */
+mxStackLayout.prototype.resizeLast = false;
+
+/**
+ * Variable: wrap
+ * 
+ * Value at which a new column or row should be created. Default is null.
+ */
+mxStackLayout.prototype.wrap = null;
+
+/**
+ * Variable: borderCollapse
+ * 
+ * If the strokeWidth should be ignored. Default is true.
+ */
+mxStackLayout.prototype.borderCollapse = true;
+
+/**
+ * Function: isHorizontal
+ * 
+ * Returns <horizontal>.
+ */
+mxStackLayout.prototype.isHorizontal = function()
+{
+	return this.horizontal;
+};
+
+/**
+ * Function: moveCell
+ * 
+ * Implements <mxGraphLayout.moveCell>.
+ */
+mxStackLayout.prototype.moveCell = function(cell, x, y)
+{
+	var model = this.graph.getModel();
+	var parent = model.getParent(cell);
+	var horizontal = this.isHorizontal();
+	
+	if (cell != null && parent != null)
+	{
+		var i = 0;
+		var last = 0;
+		var childCount = model.getChildCount(parent);
+		var value = (horizontal) ? x : y;
+		var pstate = this.graph.getView().getState(parent);
+
+		if (pstate != null)
+		{
+			value -= (horizontal) ? pstate.x : pstate.y;
+		}
+		
+		value /= this.graph.view.scale;
+		
+		for (i = 0; i < childCount; i++)
+		{
+			var child = model.getChildAt(parent, i);
+			
+			if (child != cell)
+			{
+				var bounds = model.getGeometry(child);
+				
+				if (bounds != null)
+				{
+					var tmp = (horizontal) ?
+						bounds.x + bounds.width / 2 :
+						bounds.y + bounds.height / 2;
+					
+					if (last <= value && tmp > value)
+					{
+						break;
+					}
+					
+					last = tmp;
+				}
+			}
+		}
+
+		// Changes child order in parent
+		var idx = parent.getIndex(cell);
+		idx = Math.max(0, i - ((i > idx) ? 1 : 0));
+
+		model.add(parent, cell, idx);
+	}
+};
+
+/**
+ * Function: getParentSize
+ * 
+ * Returns the size for the parent container or the size of the graph
+ * container if the parent is a layer or the root of the model.
+ */
+mxStackLayout.prototype.getParentSize = function(parent)
+{
+	var model = this.graph.getModel();			
+	var pgeo = model.getGeometry(parent);
+	
+	// Handles special case where the parent is either a layer with no
+	// geometry or the current root of the view in which case the size
+	// of the graph's container will be used.
+	if (this.graph.container != null && ((pgeo == null &&
+		model.isLayer(parent)) || parent == this.graph.getView().currentRoot))
+	{
+		var width = this.graph.container.offsetWidth - 1;
+		var height = this.graph.container.offsetHeight - 1;
+		pgeo = new mxRectangle(0, 0, width, height);
+	}
+	
+	return pgeo;
+};
+
+/**
+ * Function: execute
+ * 
+ * Implements <mxGraphLayout.execute>.
+ * 
+ * Only children where <isVertexIgnored> returns false are taken into
+ * account.
+ */
+mxStackLayout.prototype.execute = function(parent)
+{
+	if (parent != null)
+	{
+		var pgeo = this.getParentSize(parent);
+		var horizontal = this.isHorizontal();
+		var model = this.graph.getModel();	
+		var fillValue = null;
+		
+		if (pgeo != null)
+		{
+			fillValue = (horizontal) ? pgeo.height - this.marginTop - this.marginBottom :
+				pgeo.width - this.marginLeft - this.marginRight;
+		}
+		
+		fillValue -= 2 * this.spacing + 2 * this.border;
+		var x0 = this.x0 + this.border + this.marginLeft;
+		var y0 = this.y0 + this.border + this.marginTop;
+		
+		// Handles swimlane start size
+		if (this.graph.isSwimlane(parent))
+		{
+			// Uses computed style to get latest 
+			var style = this.graph.getCellStyle(parent);
+			var start = mxUtils.getNumber(style, mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE);
+			var horz = mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, true) == 1;
+
+			if (pgeo != null)
+			{
+				if (horz)
+				{
+					start = Math.min(start, pgeo.height);
+				}
+				else
+				{
+					start = Math.min(start, pgeo.width);
+				}
+			}
+			
+			if (horizontal == horz)
+			{
+				fillValue -= start;
+			}
+
+			if (horz)
+			{
+				y0 += start;
+			}
+			else
+			{
+				x0 += start;
+			}
+		}
+
+		model.beginUpdate();
+		try
+		{
+			var tmp = 0;
+			var last = null;
+			var lastValue = 0;
+			var lastChild = null;
+			var 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);
+						var sw = 0;
+						
+						if (!this.borderCollapse)
+						{
+							var childStyle = this.graph.getCellStyle(child);
+							sw = mxUtils.getNumber(childStyle, mxConstants.STYLE_STROKEWIDTH, 1);
+						}
+						
+						if (last != null)
+						{
+							if (horizontal)
+							{
+								geo.x = lastValue + this.spacing + Math.floor(sw / 2);
+							}
+							else
+							{
+								geo.y = lastValue + this.spacing + Math.floor(sw / 2);
+							}
+						}
+						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 != null)
+						{
+							if (horizontal)
+							{
+								geo.height = fillValue;
+							}
+							else
+							{
+								geo.width = fillValue;									
+							}
+						}
+						
+						this.setChildGeometry(child, geo);
+						lastChild = child;
+						last = geo;
+						
+						if (horizontal)
+						{
+							lastValue = last.x + last.width + Math.floor(sw / 2);
+						}
+						else
+						{
+							lastValue = last.y + last.height + Math.floor(sw / 2);
+						}
+					}
+				}
+			}
+
+			if (this.resizeParent && pgeo != null && last != null && !this.graph.isCellCollapsed(parent))
+			{
+				this.updateParentGeometry(parent, pgeo, last);
+			}
+			else if (this.resizeLast && pgeo != null && last != null && lastChild != null)
+			{
+				if (horizontal)
+				{
+					last.width = pgeo.width - last.x - this.spacing - this.marginRight - this.marginLeft;
+				}
+				else
+				{
+					last.height = pgeo.height - last.y - this.spacing - this.marginBottom;
+				}
+				
+				this.setChildGeometry(lastChild, last);
+			}
+		}
+		finally
+		{
+			model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: execute
+ * 
+ * Implements <mxGraphLayout.execute>.
+ * 
+ * Only children where <isVertexIgnored> returns false are taken into
+ * account.
+ */
+mxStackLayout.prototype.setChildGeometry = function(child, geo)
+{
+	var geo2 = this.graph.getCellGeometry(child);
+	
+	if (geo2 == null || geo.x != geo2.x || geo.y != geo2.y ||
+		geo.width != geo2.width || geo.height != geo2.height)
+	{
+		this.graph.getModel().setGeometry(child, geo);
+	}
+};
+
+/**
+ * Function: execute
+ * 
+ * Implements <mxGraphLayout.execute>.
+ * 
+ * Only children where <isVertexIgnored> returns false are taken into
+ * account.
+ */
+mxStackLayout.prototype.updateParentGeometry = function(parent, pgeo, last)
+{
+	var horizontal = this.isHorizontal();
+	var model = this.graph.getModel();	
+
+	var pgeo2 = pgeo.clone();
+	
+	if (horizontal)
+	{
+		var tmp = last.x + last.width + this.spacing + this.marginRight;
+		
+		if (this.resizeParentMax)
+		{
+			pgeo2.width = Math.max(pgeo2.width, tmp);
+		}
+		else
+		{
+			pgeo2.width = tmp;
+		}
+	}
+	else
+	{
+		var tmp = last.y + last.height + this.spacing + this.marginBottom;
+		
+		if (this.resizeParentMax)
+		{
+			pgeo2.height = Math.max(pgeo2.height, tmp);
+		}
+		else
+		{
+			pgeo2.height = tmp;
+		}
+	}
+	
+	if (pgeo.x != pgeo2.x || pgeo.y != pgeo2.y ||
+		pgeo.width != pgeo2.width || pgeo.height != pgeo2.height)
+	{
+		model.setGeometry(parent, pgeo2);
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/model/mxCell.js b/airavata-kubernetes/web-console/src/assets/js/model/mxCell.js
new file mode 100644
index 0000000..73e94ba
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/model/mxCell.js
@@ -0,0 +1,825 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxCell
+ *
+ * Cells are the elements of the graph model. They represent the state
+ * of the groups, vertices and edges in a graph.
+ * 
+ * Custom attributes:
+ * 
+ * For custom attributes we recommend using an XML node as the value of a cell.
+ * The following code can be used to create a cell with an XML node as the
+ * value:
+ * 
+ * (code)
+ * var doc = mxUtils.createXmlDocument();
+ * var node = doc.createElement('MyNode')
+ * node.setAttribute('label', 'MyLabel');
+ * node.setAttribute('attribute1', 'value1');
+ * graph.insertVertex(graph.getDefaultParent(), null, node, 40, 40, 80, 30);
+ * (end)
+ * 
+ * For the label to work, <mxGraph.convertValueToString> and
+ * <mxGraph.cellLabelChanged> should be overridden as follows:
+ * 
+ * (code)
+ * graph.convertValueToString = function(cell)
+ * {
+ *   if (mxUtils.isNode(cell.value))
+ *   {
+ *     return cell.getAttribute('label', '')
+ *   }
+ * };
+ * 
+ * var cellLabelChanged = graph.cellLabelChanged;
+ * graph.cellLabelChanged = function(cell, newValue, autoSize)
+ * {
+ *   if (mxUtils.isNode(cell.value))
+ *   {
+ *     // Clones the value for correct undo/redo
+ *     var elt = cell.value.cloneNode(true);
+ *     elt.setAttribute('label', newValue);
+ *     newValue = elt;
+ *   }
+ *   
+ *   cellLabelChanged.apply(this, arguments);
+ * };
+ * (end)
+ * 
+ * Callback: onInit
+ *
+ * Called from within the constructor.
+ * 
+ * Constructor: mxCell
+ *
+ * Constructs a new cell to be used in a graph model.
+ * This method invokes <onInit> upon completion.
+ * 
+ * Parameters:
+ * 
+ * value - Optional object that represents the cell value.
+ * geometry - Optional <mxGeometry> that specifies the geometry.
+ * style - Optional formatted string that defines the style.
+ */
+function mxCell(value, geometry, style)
+{
+	this.value = value;
+	this.setGeometry(geometry);
+	this.setStyle(style);
+	
+	if (this.onInit != null)
+	{
+		this.onInit();
+	}
+};
+
+/**
+ * Variable: id
+ *
+ * Holds the Id. Default is null.
+ */
+mxCell.prototype.id = null;
+
+/**
+ * Variable: value
+ *
+ * Holds the user object. Default is null.
+ */
+mxCell.prototype.value = null;
+
+/**
+ * Variable: geometry
+ *
+ * Holds the <mxGeometry>. Default is null.
+ */
+mxCell.prototype.geometry = null;
+
+/**
+ * Variable: style
+ *
+ * Holds the style as a string of the form [(stylename|key=value);]. Default is
+ * null.
+ */
+mxCell.prototype.style = null;
+
+/**
+ * Variable: vertex
+ *
+ * Specifies whether the cell is a vertex. Default is false.
+ */
+mxCell.prototype.vertex = false;
+
+/**
+ * Variable: edge
+ *
+ * Specifies whether the cell is an edge. Default is false.
+ */
+mxCell.prototype.edge = false;
+
+/**
+ * Variable: connectable
+ *
+ * Specifies whether the cell is connectable. Default is true.
+ */
+mxCell.prototype.connectable = true;
+
+/**
+ * Variable: visible
+ *
+ * Specifies whether the cell is visible. Default is true.
+ */
+mxCell.prototype.visible = true;
+
+/**
+ * Variable: collapsed
+ *
+ * Specifies whether the cell is collapsed. Default is false.
+ */
+mxCell.prototype.collapsed = false;
+
+/**
+ * Variable: parent
+ *
+ * Reference to the parent cell.
+ */
+mxCell.prototype.parent = null;
+
+/**
+ * Variable: source
+ *
+ * Reference to the source terminal.
+ */
+mxCell.prototype.source = null;
+
+/**
+ * Variable: target
+ *
+ * Reference to the target terminal.
+ */
+mxCell.prototype.target = null;
+
+/**
+ * Variable: children
+ *
+ * Holds the child cells.
+ */
+mxCell.prototype.children = null;
+
+/**
+ * Variable: edges
+ *
+ * Holds the edges.
+ */
+mxCell.prototype.edges = null;
+
+/**
+ * Variable: mxTransient
+ *
+ * List of members that should not be cloned inside <clone>. This field is
+ * passed to <mxUtils.clone> and is not made persistent in <mxCellCodec>.
+ * This is not a convention for all classes, it is only used in this class
+ * to mark transient fields since transient modifiers are not supported by
+ * the language.
+ */
+mxCell.prototype.mxTransient = ['id', 'value', 'parent', 'source',
+                                'target', 'children', 'edges'];
+
+/**
+ * Function: getId
+ *
+ * Returns the Id of the cell as a string.
+ */
+mxCell.prototype.getId = function()
+{
+	return this.id;
+};
+		
+/**
+ * Function: setId
+ *
+ * Sets the Id of the cell to the given string.
+ */
+mxCell.prototype.setId = function(id)
+{
+	this.id = id;
+};
+
+/**
+ * Function: getValue
+ *
+ * Returns the user object of the cell. The user
+ * object is stored in <value>.
+ */
+mxCell.prototype.getValue = function()
+{
+	return this.value;
+};
+		
+/**
+ * Function: setValue
+ *
+ * Sets the user object of the cell. The user object
+ * is stored in <value>.
+ */
+mxCell.prototype.setValue = function(value)
+{
+	this.value = value;
+};
+
+/**
+ * Function: valueChanged
+ *
+ * Changes the user object after an in-place edit
+ * and returns the previous value. This implementation
+ * replaces the user object with the given value and
+ * returns the old user object.
+ */
+mxCell.prototype.valueChanged = function(newValue)
+{
+	var previous = this.getValue();
+	this.setValue(newValue);
+	
+	return previous;
+};
+
+/**
+ * Function: getGeometry
+ *
+ * Returns the <mxGeometry> that describes the <geometry>.
+ */
+mxCell.prototype.getGeometry = function()
+{
+	return this.geometry;
+};
+
+/**
+ * Function: setGeometry
+ *
+ * Sets the <mxGeometry> to be used as the <geometry>.
+ */
+mxCell.prototype.setGeometry = function(geometry)
+{
+	this.geometry = geometry;
+};
+
+/**
+ * Function: getStyle
+ *
+ * Returns a string that describes the <style>.
+ */
+mxCell.prototype.getStyle = function()
+{
+	return this.style;
+};
+
+/**
+ * Function: setStyle
+ *
+ * Sets the string to be used as the <style>.
+ */
+mxCell.prototype.setStyle = function(style)
+{
+	this.style = style;
+};
+
+/**
+ * Function: isVertex
+ *
+ * Returns true if the cell is a vertex.
+ */
+mxCell.prototype.isVertex = function()
+{
+	return this.vertex != 0;
+};
+
+/**
+ * Function: setVertex
+ *
+ * Specifies if the cell is a vertex. This should only be assigned at
+ * construction of the cell and not be changed during its lifecycle.
+ * 
+ * Parameters:
+ * 
+ * vertex - Boolean that specifies if the cell is a vertex.
+ */
+mxCell.prototype.setVertex = function(vertex)
+{
+	this.vertex = vertex;
+};
+
+/**
+ * Function: isEdge
+ *
+ * Returns true if the cell is an edge.
+ */
+mxCell.prototype.isEdge = function()
+{
+	return this.edge != 0;
+};
+	
+/**
+ * Function: setEdge
+ * 
+ * Specifies if the cell is an edge. This should only be assigned at
+ * construction of the cell and not be changed during its lifecycle.
+ * 
+ * Parameters:
+ * 
+ * edge - Boolean that specifies if the cell is an edge.
+ */
+mxCell.prototype.setEdge = function(edge)
+{
+	this.edge = edge;
+};
+
+/**
+ * Function: isConnectable
+ *
+ * Returns true if the cell is connectable.
+ */
+mxCell.prototype.isConnectable = function()
+{
+	return this.connectable != 0;
+};
+
+/**
+ * Function: setConnectable
+ *
+ * Sets the connectable state.
+ * 
+ * Parameters:
+ * 
+ * connectable - Boolean that specifies the new connectable state.
+ */
+mxCell.prototype.setConnectable = function(connectable)
+{
+	this.connectable = connectable;
+};
+
+/**
+ * Function: isVisible
+ *
+ * Returns true if the cell is visibile.
+ */
+mxCell.prototype.isVisible = function()
+{
+	return this.visible != 0;
+};
+
+/**
+ * Function: setVisible
+ *
+ * Specifies if the cell is visible.
+ * 
+ * Parameters:
+ * 
+ * visible - Boolean that specifies the new visible state.
+ */
+mxCell.prototype.setVisible = function(visible)
+{
+	this.visible = visible;
+};
+
+/**
+ * Function: isCollapsed
+ *
+ * Returns true if the cell is collapsed.
+ */
+mxCell.prototype.isCollapsed = function()
+{
+	return this.collapsed != 0;
+};
+
+/**
+ * Function: setCollapsed
+ *
+ * Sets the collapsed state.
+ * 
+ * Parameters:
+ * 
+ * collapsed - Boolean that specifies the new collapsed state.
+ */
+mxCell.prototype.setCollapsed = function(collapsed)
+{
+	this.collapsed = collapsed;
+};
+
+/**
+ * Function: getParent
+ *
+ * Returns the cell's parent.
+ */
+mxCell.prototype.getParent = function()
+{
+	return this.parent;
+};
+
+/**
+ * Function: setParent
+ *
+ * Sets the parent cell.
+ * 
+ * Parameters:
+ * 
+ * parent - <mxCell> that represents the new parent.
+ */
+mxCell.prototype.setParent = function(parent)
+{
+	this.parent = parent;
+};
+
+/**
+ * Function: getTerminal
+ *
+ * Returns the source or target terminal.
+ * 
+ * Parameters:
+ * 
+ * source - Boolean that specifies if the source terminal should be
+ * returned.
+ */
+mxCell.prototype.getTerminal = function(source)
+{
+	return (source) ? this.source : this.target;
+};
+
+/**
+ * Function: setTerminal
+ *
+ * Sets the source or target terminal and returns the new terminal.
+ * 
+ * Parameters:
+ * 
+ * terminal - <mxCell> that represents the new source or target terminal.
+ * isSource - Boolean that specifies if the source or target terminal
+ * should be set.
+ */
+mxCell.prototype.setTerminal = function(terminal, isSource)
+{
+	if (isSource)
+	{
+		this.source = terminal;
+	}
+	else
+	{
+		this.target = terminal;
+	}
+	
+	return terminal;
+};
+
+/**
+ * Function: getChildCount
+ *
+ * Returns the number of child cells.
+ */
+mxCell.prototype.getChildCount = function()
+{
+	return (this.children == null) ? 0 : this.children.length;
+};
+
+/**
+ * Function: getIndex
+ *
+ * Returns the index of the specified child in the child array.
+ * 
+ * Parameters:
+ * 
+ * child - Child whose index should be returned.
+ */
+mxCell.prototype.getIndex = function(child)
+{
+	return mxUtils.indexOf(this.children, child);
+};
+
+/**
+ * Function: getChildAt
+ *
+ * Returns the child at the specified index.
+ * 
+ * Parameters:
+ * 
+ * index - Integer that specifies the child to be returned.
+ */
+mxCell.prototype.getChildAt = function(index)
+{
+	return (this.children == null) ? null : this.children[index];
+};
+
+/**
+ * Function: insert
+ *
+ * Inserts the specified child into the child array at the specified index
+ * and updates the parent reference of the child. If not childIndex is
+ * specified then the child is appended to the child array. Returns the
+ * inserted child.
+ * 
+ * Parameters:
+ * 
+ * child - <mxCell> to be inserted or appended to the child array.
+ * index - Optional integer that specifies the index at which the child
+ * should be inserted into the child array.
+ */
+mxCell.prototype.insert = function(child, index)
+{
+	if (child != null)
+	{
+		if (index == null)
+		{
+			index = this.getChildCount();
+			
+			if (child.getParent() == this)
+			{
+				index--;
+			}
+		}
+
+		child.removeFromParent();
+		child.setParent(this);
+		
+		if (this.children == null)
+		{
+			this.children = [];
+			this.children.push(child);
+		}
+		else
+		{
+			this.children.splice(index, 0, child);
+		}
+	}
+	
+	return child;
+};
+
+/**
+ * Function: remove
+ *
+ * Removes the child at the specified index from the child array and
+ * returns the child that was removed. Will remove the parent reference of
+ * the child.
+ * 
+ * Parameters:
+ * 
+ * index - Integer that specifies the index of the child to be
+ * removed.
+ */
+mxCell.prototype.remove = function(index)
+{
+	var child = null;
+	
+	if (this.children != null && index >= 0)
+	{
+		child = this.getChildAt(index);
+		
+		if (child != null)
+		{
+			this.children.splice(index, 1);
+			child.setParent(null);
+		}
+	}
+	
+	return child;
+};
+
+/**
+ * Function: removeFromParent
+ *
+ * Removes the cell from its parent.
+ */
+mxCell.prototype.removeFromParent = function()
+{
+	if (this.parent != null)
+	{
+		var index = this.parent.getIndex(this);
+		this.parent.remove(index);
+	}
+};
+
+/**
+ * Function: getEdgeCount
+ *
+ * Returns the number of edges in the edge array.
+ */
+mxCell.prototype.getEdgeCount = function()
+{
+	return (this.edges == null) ? 0 : this.edges.length;
+};
+
+/**
+ * Function: getEdgeIndex
+ *
+ * Returns the index of the specified edge in <edges>.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> whose index in <edges> should be returned.
+ */
+mxCell.prototype.getEdgeIndex = function(edge)
+{
+	return mxUtils.indexOf(this.edges, edge);
+};
+
+/**
+ * Function: getEdgeAt
+ *
+ * Returns the edge at the specified index in <edges>.
+ * 
+ * Parameters:
+ * 
+ * index - Integer that specifies the index of the edge to be returned.
+ */
+mxCell.prototype.getEdgeAt = function(index)
+{
+	return (this.edges == null) ? null : this.edges[index];
+};
+
+/**
+ * Function: insertEdge
+ *
+ * Inserts the specified edge into the edge array and returns the edge.
+ * Will update the respective terminal reference of the edge.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> to be inserted into the edge array.
+ * isOutgoing - Boolean that specifies if the edge is outgoing.
+ */
+mxCell.prototype.insertEdge = function(edge, isOutgoing)
+{
+	if (edge != null)
+	{
+		edge.removeFromTerminal(isOutgoing);
+		edge.setTerminal(this, isOutgoing);
+		
+		if (this.edges == null ||
+			edge.getTerminal(!isOutgoing) != this ||
+			mxUtils.indexOf(this.edges, edge) < 0)
+		{
+			if (this.edges == null)
+			{
+				this.edges = [];
+			}
+			
+			this.edges.push(edge);
+		}
+	}
+	
+	return edge;
+};
+
+/**
+ * Function: removeEdge
+ *
+ * Removes the specified edge from the edge array and returns the edge.
+ * Will remove the respective terminal reference from the edge.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> to be removed from the edge array.
+ * isOutgoing - Boolean that specifies if the edge is outgoing.
+ */
+mxCell.prototype.removeEdge = function(edge, isOutgoing)
+{
+	if (edge != null)
+	{
+		if (edge.getTerminal(!isOutgoing) != this &&
+			this.edges != null)
+		{
+			var index = this.getEdgeIndex(edge);
+			
+			if (index >= 0)
+			{
+				this.edges.splice(index, 1);
+			}
+		}
+		
+		edge.setTerminal(null, isOutgoing);
+	}
+	
+	return edge;
+};
+
+/**
+ * Function: removeFromTerminal
+ *
+ * Removes the edge from its source or target terminal.
+ * 
+ * Parameters:
+ * 
+ * isSource - Boolean that specifies if the edge should be removed from its
+ * source or target terminal.
+ */
+mxCell.prototype.removeFromTerminal = function(isSource)
+{
+	var terminal = this.getTerminal(isSource);
+	
+	if (terminal != null)
+	{
+		terminal.removeEdge(this, isSource);
+	}
+};
+
+/**
+ * Function: hasAttribute
+ * 
+ * Returns true if the user object is an XML node that contains the given
+ * attribute.
+ * 
+ * Parameters:
+ * 
+ * name - Name of the attribute.
+ */
+mxCell.prototype.hasAttribute = function(name)
+{
+	var userObject = this.getValue();
+	
+	return (userObject != null &&
+		userObject.nodeType == mxConstants.NODETYPE_ELEMENT && userObject.hasAttribute) ?
+		userObject.hasAttribute(name) : userObject.getAttribute(name) != null;
+};
+
+/**
+ * Function: getAttribute
+ *
+ * Returns the specified attribute from the user object if it is an XML
+ * node.
+ * 
+ * Parameters:
+ * 
+ * name - Name of the attribute whose value should be returned.
+ * defaultValue - Optional default value to use if the attribute has no
+ * value.
+ */
+mxCell.prototype.getAttribute = function(name, defaultValue)
+{
+	var userObject = this.getValue();
+	
+	var val = (userObject != null &&
+		userObject.nodeType == mxConstants.NODETYPE_ELEMENT) ?
+		userObject.getAttribute(name) : null;
+		
+	return val || defaultValue;
+};
+
+/**
+ * Function: setAttribute
+ *
+ * Sets the specified attribute on the user object if it is an XML node.
+ * 
+ * Parameters:
+ * 
+ * name - Name of the attribute whose value should be set.
+ * value - New value of the attribute.
+ */
+mxCell.prototype.setAttribute = function(name, value)
+{
+	var userObject = this.getValue();
+	
+	if (userObject != null &&
+		userObject.nodeType == mxConstants.NODETYPE_ELEMENT)
+	{
+		userObject.setAttribute(name, value);
+	}
+};
+
+/**
+ * Function: clone
+ *
+ * Returns a clone of the cell. Uses <cloneValue> to clone
+ * the user object. All fields in <mxTransient> are ignored
+ * during the cloning.
+ */
+mxCell.prototype.clone = function()
+{
+	var clone = mxUtils.clone(this, this.mxTransient);
+	clone.setValue(this.cloneValue());
+	
+	return clone;
+};
+
+/**
+ * Function: cloneValue
+ *
+ * Returns a clone of the cell's user object.
+ */
+mxCell.prototype.cloneValue = function()
+{
+	var value = this.getValue();
+	
+	if (value != null)
+	{
+		if (typeof(value.clone) == 'function')
+		{
+			value = value.clone();
+		}
+		else if (!isNaN(value.nodeType))
+		{
+			value = value.cloneNode(true);
+		}
+	}
+	
+	return value;
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/model/mxCellPath.js b/airavata-kubernetes/web-console/src/assets/js/model/mxCellPath.js
new file mode 100644
index 0000000..c51a823
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/model/mxCellPath.js
@@ -0,0 +1,163 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+var mxCellPath =
+{
+
+	/**
+	 * Class: mxCellPath
+	 * 
+	 * Implements a mechanism for temporary cell Ids.
+	 * 
+	 * Variable: PATH_SEPARATOR
+	 * 
+	 * Defines the separator between the path components. Default is ".".
+	 */
+	PATH_SEPARATOR: '.',
+	
+	/**
+	 * Function: create
+	 * 
+	 * Creates the cell path for the given cell. The cell path is a
+	 * concatenation of the indices of all ancestors on the (finite) path to
+	 * the root, eg. "0.0.0.1".
+	 * 
+	 * Parameters:
+	 * 
+	 * cell - Cell whose path should be returned.
+	 */
+	create: function(cell)
+	{
+		var result = '';
+		
+		if (cell != null)
+		{
+			var parent = cell.getParent();
+			
+			while (parent != null)
+			{
+				var index = parent.getIndex(cell);
+				result = index + mxCellPath.PATH_SEPARATOR + result;
+				
+				cell = parent;
+				parent = cell.getParent();
+			}
+		}
+		
+		// Removes trailing separator
+		var n = result.length;
+		
+		if (n > 1)
+		{
+			result = result.substring(0, n - 1);
+		}
+		
+		return result;
+	},
+	
+	/**
+	 * Function: getParentPath
+	 * 
+	 * Returns the path for the parent of the cell represented by the given
+	 * path. Returns null if the given path has no parent.
+	 * 
+	 * Parameters:
+	 * 
+	 * path - Path whose parent path should be returned.
+	 */
+	getParentPath: function(path)
+	{
+		if (path != null)
+		{
+			var index = path.lastIndexOf(mxCellPath.PATH_SEPARATOR);
+
+			if (index >= 0)
+			{
+				return path.substring(0, index);
+			}
+			else if (path.length > 0)
+			{
+				return '';
+			}
+		}
+
+		return null;
+	},
+
+	/**
+	 * Function: resolve
+	 * 
+	 * Returns the cell for the specified cell path using the given root as the
+	 * root of the path.
+	 * 
+	 * Parameters:
+	 * 
+	 * root - Root cell of the path to be resolved.
+	 * path - String that defines the path.
+	 */
+	resolve: function(root, path)
+	{
+		var parent = root;
+		
+		if (path != null)
+		{
+			var tokens = path.split(mxCellPath.PATH_SEPARATOR);
+			
+			for (var i=0; i<tokens.length; i++)
+			{
+				parent = parent.getChildAt(parseInt(tokens[i]));
+			}
+		}
+		
+		return parent;
+	},
+	
+	/**
+	 * Function: compare
+	 * 
+	 * Compares the given cell paths and returns -1 if p1 is smaller, 0 if
+	 * p1 is equal and 1 if p1 is greater than p2.
+	 */
+	compare: function(p1, p2)
+	{
+		var min = Math.min(p1.length, p2.length);
+		var comp = 0;
+		
+		for (var i = 0; i < min; i++)
+		{
+			if (p1[i] != p2[i])
+			{
+				if (p1[i].length == 0 ||
+					p2[i].length == 0)
+				{
+					comp = (p1[i] == p2[i]) ? 0 : ((p1[i] > p2[i]) ? 1 : -1);
+				}
+				else
+				{
+					var t1 = parseInt(p1[i]);
+					var t2 = parseInt(p2[i]);
+					
+					comp = (t1 == t2) ? 0 : ((t1 > t2) ? 1 : -1);
+				}
+				
+				break;
+			}
+		}
+		
+		// Compares path length if both paths are equal to this point
+		if (comp == 0)
+		{
+			var t1 = p1.length;
+			var t2 = p2.length;
+			
+			if (t1 != t2)
+			{
+				comp = (t1 > t2) ? 1 : -1;
+			}
+		}
+		
+		return comp;
+	}
+
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/model/mxGeometry.js b/airavata-kubernetes/web-console/src/assets/js/model/mxGeometry.js
new file mode 100644
index 0000000..8d9278a
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/model/mxGeometry.js
@@ -0,0 +1,415 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxGeometry
+ * 
+ * Extends <mxRectangle> to represent the geometry of a cell.
+ * 
+ * For vertices, the geometry consists of the x- and y-location, and the width
+ * and height. For edges, the geometry consists of the optional terminal- and
+ * control points. The terminal points are only required if an edge is
+ * unconnected, and are stored in the sourcePoint> and <targetPoint>
+ * variables, respectively.
+ * 
+ * Example:
+ * 
+ * If an edge is unconnected, that is, it has no source or target terminal,
+ * then a geometry with terminal points for a new edge can be defined as
+ * follows.
+ * 
+ * (code)
+ * geometry.setTerminalPoint(new mxPoint(x1, y1), true);
+ * geometry.points = [new mxPoint(x2, y2)];
+ * geometry.setTerminalPoint(new mxPoint(x3, y3), false);
+ * (end)
+ * 
+ * Control points are used regardless of the connected state of an edge and may
+ * be ignored or interpreted differently depending on the edge's <mxEdgeStyle>.
+ * 
+ * To disable automatic reset of control points after a cell has been moved or
+ * resized, the the <mxGraph.resizeEdgesOnMove> and
+ * <mxGraph.resetEdgesOnResize> may be used.
+ *
+ * Edge Labels:
+ * 
+ * Using the x- and y-coordinates of a cell's geometry, it is possible to
+ * position the label on edges on a specific location on the actual edge shape
+ * as it appears on the screen. The x-coordinate of an edge's geometry is used
+ * to describe the distance from the center of the edge from -1 to 1 with 0
+ * being the center of the edge and the default value. The y-coordinate of an
+ * edge's geometry is used to describe the absolute, orthogonal distance in
+ * pixels from that point. In addition, the <mxGeometry.offset> is used as an
+ * absolute offset vector from the resulting point.
+ * 
+ * This coordinate system is applied if <relative> is true, otherwise the
+ * offset defines the absolute vector from the edge's center point to the
+ * label and the values for <x> and <y> are ignored.
+ * 
+ * The width and height parameter for edge geometries can be used to set the
+ * label width and height (eg. for word wrapping).
+ * 
+ * Ports:
+ * 
+ * The term "port" refers to a relatively positioned, connectable child cell,
+ * which is used to specify the connection between the parent and another cell
+ * in the graph. Ports are typically modeled as vertices with relative
+ * geometries.
+ * 
+ * Offsets:
+ * 
+ * The <offset> field is interpreted in 3 different ways, depending on the cell
+ * and the geometry. For edges, the offset defines the absolute offset for the
+ * edge label. For relative geometries, the offset defines the absolute offset
+ * for the origin (top, left corner) of the vertex, otherwise the offset
+ * defines the absolute offset for the label inside the vertex or group.
+ * 
+ * Constructor: mxGeometry
+ *
+ * Constructs a new object to describe the size and location of a vertex or
+ * the control points of an edge.
+ */
+function mxGeometry(x, y, width, height)
+{
+	mxRectangle.call(this, x, y, width, height);
+};
+
+/**
+ * Extends mxRectangle.
+ */
+mxGeometry.prototype = new mxRectangle();
+mxGeometry.prototype.constructor = mxGeometry;
+
+/**
+ * Variable: TRANSLATE_CONTROL_POINTS
+ * 
+ * Global switch to translate the points in translate. Default is true.
+ */
+mxGeometry.prototype.TRANSLATE_CONTROL_POINTS = true;
+
+/**
+ * Variable: alternateBounds
+ *
+ * Stores alternate values for x, y, width and height in a rectangle. See
+ * <swap> to exchange the values. Default is null.
+ */
+mxGeometry.prototype.alternateBounds = null;
+
+/**
+ * Variable: sourcePoint
+ *
+ * Defines the source <mxPoint> of the edge. This is used if the
+ * corresponding edge does not have a source vertex. Otherwise it is
+ * ignored. Default is  null.
+ */
+mxGeometry.prototype.sourcePoint = null;
+
+/**
+ * Variable: targetPoint
+ *
+ * Defines the target <mxPoint> of the edge. This is used if the
+ * corresponding edge does not have a target vertex. Otherwise it is
+ * ignored. Default is null.
+ */
+mxGeometry.prototype.targetPoint = null;
+
+/**
+ * Variable: points
+ *
+ * Array of <mxPoints> which specifies the control points along the edge.
+ * These points are the intermediate points on the edge, for the endpoints
+ * use <targetPoint> and <sourcePoint> or set the terminals of the edge to
+ * a non-null value. Default is null.
+ */
+mxGeometry.prototype.points = null;
+
+/**
+ * Variable: offset
+ *
+ * For edges, this holds the offset (in pixels) from the position defined
+ * by <x> and <y> on the edge. For relative geometries (for vertices), this
+ * defines the absolute offset from the point defined by the relative
+ * coordinates. For absolute geometries (for vertices), this defines the
+ * offset for the label. Default is null.
+ */
+mxGeometry.prototype.offset = null;
+
+/**
+ * Variable: relative
+ *
+ * Specifies if the coordinates in the geometry are to be interpreted as
+ * relative coordinates. For edges, this is used to define the location of
+ * the edge label relative to the edge as rendered on the display. For
+ * vertices, this specifies the relative location inside the bounds of the
+ * parent cell.
+ * 
+ * If this is false, then the coordinates are relative to the origin of the
+ * parent cell or, for edges, the edge label position is relative to the
+ * center of the edge as rendered on screen.
+ * 
+ * Default is false.
+ */
+mxGeometry.prototype.relative = false;
+
+/**
+ * Function: swap
+ * 
+ * Swaps the x, y, width and height with the values stored in
+ * <alternateBounds> and puts the previous values into <alternateBounds> as
+ * a rectangle. This operation is carried-out in-place, that is, using the
+ * existing geometry instance. If this operation is called during a graph
+ * model transactional change, then the geometry should be cloned before
+ * calling this method and setting the geometry of the cell using
+ * <mxGraphModel.setGeometry>.
+ */
+mxGeometry.prototype.swap = function()
+{
+	if (this.alternateBounds != null)
+	{
+		var old = new mxRectangle(
+			this.x, this.y, this.width, this.height);
+
+		this.x = this.alternateBounds.x;
+		this.y = this.alternateBounds.y;
+		this.width = this.alternateBounds.width;
+		this.height = this.alternateBounds.height;
+
+		this.alternateBounds = old;
+	}
+};
+
+/**
+ * Function: getTerminalPoint
+ * 
+ * Returns the <mxPoint> representing the source or target point of this
+ * edge. This is only used if the edge has no source or target vertex.
+ * 
+ * Parameters:
+ * 
+ * isSource - Boolean that specifies if the source or target point
+ * should be returned.
+ */
+mxGeometry.prototype.getTerminalPoint = function(isSource)
+{
+	return (isSource) ? this.sourcePoint : this.targetPoint;
+};
+
+/**
+ * Function: setTerminalPoint
+ * 
+ * Sets the <sourcePoint> or <targetPoint> to the given <mxPoint> and
+ * returns the new point.
+ * 
+ * Parameters:
+ * 
+ * point - Point to be used as the new source or target point.
+ * isSource - Boolean that specifies if the source or target point
+ * should be set.
+ */
+mxGeometry.prototype.setTerminalPoint = function(point, isSource)
+{
+	if (isSource)
+	{
+		this.sourcePoint = point;
+	}
+	else
+	{
+		this.targetPoint = point;
+	}
+	
+	return point;
+};
+
+/**
+ * Function: rotate
+ * 
+ * Rotates the geometry by the given angle around the given center. That is,
+ * <x> and <y> of the geometry, the <sourcePoint>, <targetPoint> and all
+ * <points> are translated by the given amount. <x> and <y> are only
+ * translated if <relative> is false.
+ * 
+ * Parameters:
+ * 
+ * angle - Number that specifies the rotation angle in degrees.
+ * cx - <mxPoint> that specifies the center of the rotation.
+ */
+mxGeometry.prototype.rotate = function(angle, cx)
+{
+	var rad = mxUtils.toRadians(angle);
+	var cos = Math.cos(rad);
+	var sin = Math.sin(rad);
+	
+	// Rotates the geometry
+	if (!this.relative)
+	{
+		var ct = new mxPoint(this.getCenterX(), this.getCenterY());
+		var pt = mxUtils.getRotatedPoint(ct, cos, sin, cx);
+		
+		this.x = Math.round(pt.x - this.width / 2);
+		this.y = Math.round(pt.y - this.height / 2);
+	}
+
+	// Rotates the source point
+	if (this.sourcePoint != null)
+	{
+		var pt = mxUtils.getRotatedPoint(this.sourcePoint, cos, sin, cx);
+		this.sourcePoint.x = Math.round(pt.x);
+		this.sourcePoint.y = Math.round(pt.y);
+	}
+	
+	// Translates the target point
+	if (this.targetPoint != null)
+	{
+		var pt = mxUtils.getRotatedPoint(this.targetPoint, cos, sin, cx);
+		this.targetPoint.x = Math.round(pt.x);
+		this.targetPoint.y = Math.round(pt.y);	
+	}
+	
+	// Translate the control points
+	if (this.points != null)
+	{
+		for (var i = 0; i < this.points.length; i++)
+		{
+			if (this.points[i] != null)
+			{
+				var pt = mxUtils.getRotatedPoint(this.points[i], cos, sin, cx);
+				this.points[i].x = Math.round(pt.x);
+				this.points[i].y = Math.round(pt.y);
+			}
+		}
+	}
+};
+
+/**
+ * Function: translate
+ * 
+ * Translates the geometry by the specified amount. That is, <x> and <y> of the
+ * geometry, the <sourcePoint>, <targetPoint> and all <points> are translated
+ * by the given amount. <x> and <y> are only translated if <relative> is false.
+ * If <TRANSLATE_CONTROL_POINTS> is false, then <points> are not modified by
+ * this function.
+ * 
+ * Parameters:
+ * 
+ * dx - Number that specifies the x-coordinate of the translation.
+ * dy - Number that specifies the y-coordinate of the translation.
+ */
+mxGeometry.prototype.translate = function(dx, dy)
+{
+	dx = parseFloat(dx);
+	dy = parseFloat(dy);
+	
+	// Translates the geometry
+	if (!this.relative)
+	{
+		this.x = parseFloat(this.x) + dx;
+		this.y = parseFloat(this.y) + dy;
+	}
+
+	// Translates the source point
+	if (this.sourcePoint != null)
+	{
+		this.sourcePoint.x = parseFloat(this.sourcePoint.x) + dx;
+		this.sourcePoint.y = parseFloat(this.sourcePoint.y) + dy;
+	}
+	
+	// Translates the target point
+	if (this.targetPoint != null)
+	{
+		this.targetPoint.x = parseFloat(this.targetPoint.x) + dx;
+		this.targetPoint.y = parseFloat(this.targetPoint.y) + dy;		
+	}
+
+	// Translate the control points
+	if (this.TRANSLATE_CONTROL_POINTS && this.points != null)
+	{
+		for (var i = 0; i < this.points.length; i++)
+		{
+			if (this.points[i] != null)
+			{
+				this.points[i].x = parseFloat(this.points[i].x) + dx;
+				this.points[i].y = parseFloat(this.points[i].y) + dy;
+			}
+		}
+	}
+};
+
+/**
+ * Function: scale
+ * 
+ * Scales the geometry by the given amount. That is, <x> and <y> of the
+ * geometry, the <sourcePoint>, <targetPoint> and all <points> are scaled
+ * by the given amount. <x>, <y>, <width> and <height> are only scaled if
+ * <relative> is false. If <fixedAspect> is true, then the smaller value
+ * is used to scale the width and the height.
+ * 
+ * Parameters:
+ * 
+ * sx - Number that specifies the horizontal scale factor.
+ * sy - Number that specifies the vertical scale factor.
+ * fixedAspect - Optional boolean to keep the aspect ratio fixed.
+ */
+mxGeometry.prototype.scale = function(sx, sy, fixedAspect)
+{
+	sx = parseFloat(sx);
+	sy = parseFloat(sy);
+
+	// Translates the source point
+	if (this.sourcePoint != null)
+	{
+		this.sourcePoint.x = parseFloat(this.sourcePoint.x) * sx;
+		this.sourcePoint.y = parseFloat(this.sourcePoint.y) * sy;
+	}
+	
+	// Translates the target point
+	if (this.targetPoint != null)
+	{
+		this.targetPoint.x = parseFloat(this.targetPoint.x) * sx;
+		this.targetPoint.y = parseFloat(this.targetPoint.y) * sy;		
+	}
+
+	// Translate the control points
+	if (this.points != null)
+	{
+		for (var i = 0; i < this.points.length; i++)
+		{
+			if (this.points[i] != null)
+			{
+				this.points[i].x = parseFloat(this.points[i].x) * sx;
+				this.points[i].y = parseFloat(this.points[i].y) * sy;
+			}
+		}
+	}
+	
+	// Translates the geometry
+	if (!this.relative)
+	{
+		this.x = parseFloat(this.x) * sx;
+		this.y = parseFloat(this.y) * sy;
+
+		if (fixedAspect)
+		{
+			sy = sx = Math.min(sx, sy);
+		}
+		
+		this.width = parseFloat(this.width) * sx;
+		this.height = parseFloat(this.height) * sy;
+	}
+};
+
+/**
+ * Function: equals
+ * 
+ * Returns true if the given object equals this geometry.
+ */
+mxGeometry.prototype.equals = function(obj)
+{
+	return mxRectangle.prototype.equals.apply(this, arguments) &&
+		this.relative == obj.relative &&
+		((this.sourcePoint == null && obj.sourcePoint == null) || (this.sourcePoint != null && this.sourcePoint.equals(obj.sourcePoint))) &&
+		((this.targetPoint == null && obj.targetPoint == null) || (this.targetPoint != null && this.targetPoint.equals(obj.targetPoint))) &&
+		((this.points == null && obj.points == null) || (this.points != null && mxUtils.equalPoints(this.points, obj.points))) &&
+		((this.alternateBounds == null && obj.alternateBounds == null) || (this.alternateBounds != null && this.alternateBounds.equals(obj.alternateBounds))) &&
+		((this.offset == null && obj.offset == null) || (this.offset != null && this.offset.equals(obj.offset)));
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/model/mxGraphModel.js b/airavata-kubernetes/web-console/src/assets/js/model/mxGraphModel.js
new file mode 100644
index 0000000..1401bac
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/model/mxGraphModel.js
@@ -0,0 +1,2667 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxGraphModel
+ * 
+ * Extends <mxEventSource> to implement a graph model. The graph model acts as
+ * a wrapper around the cells which are in charge of storing the actual graph
+ * datastructure. The model acts as a transactional wrapper with event
+ * notification for all changes, whereas the cells contain the atomic
+ * operations for updating the actual datastructure.
+ * 
+ * Layers:
+ * 
+ * The cell hierarchy in the model must have a top-level root cell which
+ * contains the layers (typically one default layer), which in turn contain the
+ * top-level cells of the layers. This means each cell is contained in a layer.
+ * If no layers are required, then all new cells should be added to the default
+ * layer.
+ * 
+ * Layers are useful for hiding and showing groups of cells, or for placing
+ * groups of cells on top of other cells in the display. To identify a layer,
+ * the <isLayer> function is used. It returns true if the parent of the given
+ * cell is the root of the model.
+ * 
+ * Events:
+ * 
+ * See events section for more details. There is a new set of events for
+ * tracking transactional changes as they happen. The events are called
+ * startEdit for the initial beginUpdate, executed for each executed change
+ * and endEdit for the terminal endUpdate. The executed event contains a
+ * property called change which represents the change after execution.
+ * 
+ * Encoding the model:
+ * 
+ * To encode a graph model, use the following code:
+ * 
+ * (code)
+ * var enc = new mxCodec();
+ * var node = enc.encode(graph.getModel());
+ * (end)
+ * 
+ * This will create an XML node that contains all the model information.
+ * 
+ * Encoding and decoding changes:
+ * 
+ * For the encoding of changes, a graph model listener is required that encodes
+ * each change from the given array of changes.
+ * 
+ * (code)
+ * model.addListener(mxEvent.CHANGE, function(sender, evt)
+ * {
+ *   var changes = evt.getProperty('edit').changes;
+ *   var nodes = [];
+ *   var codec = new mxCodec();
+ * 
+ *   for (var i = 0; i < changes.length; i++)
+ *   {
+ *     nodes.push(codec.encode(changes[i]));
+ *   }
+ *   // do something with the nodes
+ * });
+ * (end)
+ * 
+ * For the decoding and execution of changes, the codec needs a lookup function
+ * that allows it to resolve cell IDs as follows:
+ * 
+ * (code)
+ * var codec = new mxCodec();
+ * codec.lookup = function(id)
+ * {
+ *   return model.getCell(id);
+ * }
+ * (end)
+ * 
+ * For each encoded change (represented by a node), the following code can be
+ * used to carry out the decoding and create a change object.
+ * 
+ * (code)
+ * var changes = [];
+ * var change = codec.decode(node);
+ * change.model = model;
+ * change.execute();
+ * changes.push(change);
+ * (end)
+ * 
+ * The changes can then be dispatched using the model as follows.
+ * 
+ * (code)
+ * var edit = new mxUndoableEdit(model, false);
+ * edit.changes = changes;
+ * 
+ * edit.notify = function()
+ * {
+ *   edit.source.fireEvent(new mxEventObject(mxEvent.CHANGE,
+ *   	'edit', edit, 'changes', edit.changes));
+ *   edit.source.fireEvent(new mxEventObject(mxEvent.NOTIFY,
+ *   	'edit', edit, 'changes', edit.changes));
+ * }
+ * 
+ * model.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit));
+ * model.fireEvent(new mxEventObject(mxEvent.CHANGE,
+ * 		'edit', edit, 'changes', changes));
+ * (end)
+ *
+ * Event: mxEvent.CHANGE
+ *
+ * Fires when an undoable edit is dispatched. The <code>edit</code> property
+ * contains the <mxUndoableEdit>. The <code>changes</code> property contains
+ * the array of atomic changes inside the undoable edit. The changes property
+ * is <strong>deprecated</strong>, please use edit.changes instead.
+ *
+ * Example:
+ * 
+ * For finding newly inserted cells, the following code can be used:
+ * 
+ * (code)
+ * graph.model.addListener(mxEvent.CHANGE, function(sender, evt)
+ * {
+ *   var changes = evt.getProperty('edit').changes;
+ * 
+ *   for (var i = 0; i < changes.length; i++)
+ *   {
+ *     var change = changes[i];
+ *     
+ *     if (change instanceof mxChildChange &&
+ *       change.change.previous == null)
+ *     {
+ *       graph.startEditingAtCell(change.child);
+ *       break;
+ *     }
+ *   }
+ * });
+ * (end)
+ * 
+ * 
+ * Event: mxEvent.NOTIFY
+ *
+ * Same as <mxEvent.CHANGE>, this event can be used for classes that need to
+ * implement a sync mechanism between this model and, say, a remote model. In
+ * such a setup, only local changes should trigger a notify event and all
+ * changes should trigger a change event.
+ * 
+ * Event: mxEvent.EXECUTE
+ * 
+ * Fires between begin- and endUpdate and after an atomic change was executed
+ * in the model. The <code>change</code> property contains the atomic change
+ * that was executed.
+ * 
+ * Event: mxEvent.EXECUTED
+ * 
+ * Fires between START_EDIT and END_EDIT after an atomic change was executed.
+ * The <code>change</code> property contains the change that was executed.
+ *
+ * Event: mxEvent.BEGIN_UPDATE
+ *
+ * Fires after the <updateLevel> was incremented in <beginUpdate>. This event
+ * contains no properties.
+ * 
+ * Event: mxEvent.START_EDIT
+ *
+ * Fires after the <updateLevel> was changed from 0 to 1. This event
+ * contains no properties.
+ * 
+ * Event: mxEvent.END_UPDATE
+ * 
+ * Fires after the <updateLevel> was decreased in <endUpdate> but before any
+ * notification or change dispatching. The <code>edit</code> property contains
+ * the <currentEdit>.
+ * 
+ * Event: mxEvent.END_EDIT
+ *
+ * Fires after the <updateLevel> was changed from 1 to 0. This event
+ * contains no properties.
+ * 
+ * Event: mxEvent.BEFORE_UNDO
+ * 
+ * Fires before the change is dispatched after the update level has reached 0
+ * in <endUpdate>. The <code>edit</code> property contains the <curreneEdit>.
+ * 
+ * Event: mxEvent.UNDO
+ * 
+ * Fires after the change was dispatched in <endUpdate>. The <code>edit</code>
+ * property contains the <currentEdit>.
+ * 
+ * Constructor: mxGraphModel
+ * 
+ * Constructs a new graph model. If no root is specified then a new root
+ * <mxCell> with a default layer is created.
+ * 
+ * Parameters:
+ * 
+ * root - <mxCell> that represents the root cell.
+ */
+function mxGraphModel(root)
+{
+	this.currentEdit = this.createUndoableEdit();
+	
+	if (root != null)
+	{
+		this.setRoot(root);
+	}
+	else
+	{
+		this.clear();
+	}
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxGraphModel.prototype = new mxEventSource();
+mxGraphModel.prototype.constructor = mxGraphModel;
+
+/**
+ * Variable: root
+ * 
+ * Holds the root cell, which in turn contains the cells that represent the
+ * layers of the diagram as child cells. That is, the actual elements of the
+ * diagram are supposed to live in the third generation of cells and below.
+ */
+mxGraphModel.prototype.root = null;
+
+/**
+ * Variable: cells
+ * 
+ * Maps from Ids to cells.
+ */
+mxGraphModel.prototype.cells = null;
+
+/**
+ * Variable: maintainEdgeParent
+ * 
+ * Specifies if edges should automatically be moved into the nearest common
+ * ancestor of their terminals. Default is true.
+ */
+mxGraphModel.prototype.maintainEdgeParent = true;
+
+/**
+ * Variable: ignoreRelativeEdgeParent
+ * 
+ * Specifies if relative edge parents should be ignored for finding the nearest
+ * common ancestors of an edge's terminals. Default is true.
+ */
+mxGraphModel.prototype.ignoreRelativeEdgeParent = true;
+
+/**
+ * Variable: createIds
+ * 
+ * Specifies if the model should automatically create Ids for new cells.
+ * Default is true.
+ */
+mxGraphModel.prototype.createIds = true;
+
+/**
+ * Variable: prefix
+ * 
+ * Defines the prefix of new Ids. Default is an empty string.
+ */
+mxGraphModel.prototype.prefix = '';
+
+/**
+ * Variable: postfix
+ * 
+ * Defines the postfix of new Ids. Default is an empty string.
+ */
+mxGraphModel.prototype.postfix = '';
+
+/**
+ * Variable: nextId
+ * 
+ * Specifies the next Id to be created. Initial value is 0.
+ */
+mxGraphModel.prototype.nextId = 0;
+
+/**
+ * Variable: currentEdit
+ * 
+ * Holds the changes for the current transaction. If the transaction is
+ * closed then a new object is created for this variable using
+ * <createUndoableEdit>.
+ */
+mxGraphModel.prototype.currentEdit = null;
+
+/**
+ * Variable: updateLevel
+ * 
+ * Counter for the depth of nested transactions. Each call to <beginUpdate>
+ * will increment this number and each call to <endUpdate> will decrement
+ * it. When the counter reaches 0, the transaction is closed and the
+ * respective events are fired. Initial value is 0.
+ */
+mxGraphModel.prototype.updateLevel = 0;
+
+/**
+ * Variable: endingUpdate
+ * 
+ * True if the program flow is currently inside endUpdate.
+ */
+mxGraphModel.prototype.endingUpdate = false;
+
+/**
+ * Function: clear
+ *
+ * Sets a new root using <createRoot>.
+ */
+mxGraphModel.prototype.clear = function()
+{
+	this.setRoot(this.createRoot());
+};
+
+/**
+ * Function: isCreateIds
+ *
+ * Returns <createIds>.
+ */
+mxGraphModel.prototype.isCreateIds = function()
+{
+	return this.createIds;
+};
+
+/**
+ * Function: setCreateIds
+ *
+ * Sets <createIds>.
+ */
+mxGraphModel.prototype.setCreateIds = function(value)
+{
+	this.createIds = value;
+};
+
+/**
+ * Function: createRoot
+ *
+ * Creates a new root cell with a default layer (child 0).
+ */
+mxGraphModel.prototype.createRoot = function()
+{
+	var cell = new mxCell();
+	cell.insert(new mxCell());
+	
+	return cell;
+};
+
+/**
+ * Function: getCell
+ *
+ * Returns the <mxCell> for the specified Id or null if no cell can be
+ * found for the given Id.
+ *
+ * Parameters:
+ * 
+ * id - A string representing the Id of the cell.
+ */
+mxGraphModel.prototype.getCell = function(id)
+{
+	return (this.cells != null) ? this.cells[id] : null;
+};
+
+/**
+ * Function: filterCells
+ * 
+ * Returns the cells from the given array where the given filter function
+ * returns true.
+ */
+mxGraphModel.prototype.filterCells = function(cells, filter)
+{
+	var result = null;
+	
+	if (cells != null)
+	{
+		result = [];
+		
+		for (var i = 0; i < cells.length; i++)
+		{
+			if (filter(cells[i]))
+			{
+				result.push(cells[i]);
+			}
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: getDescendants
+ * 
+ * Returns all descendants of the given cell and the cell itself in an array.
+ * 
+ * Parameters:
+ * 
+ * parent - <mxCell> whose descendants should be returned.
+ */
+mxGraphModel.prototype.getDescendants = function(parent)
+{
+	return this.filterDescendants(null, parent);
+};
+
+/**
+ * Function: filterDescendants
+ * 
+ * Visits all cells recursively and applies the specified filter function
+ * to each cell. If the function returns true then the cell is added
+ * to the resulting array. The parent and result paramters are optional.
+ * If parent is not specified then the recursion starts at <root>.
+ * 
+ * Example:
+ * The following example extracts all vertices from a given model:
+ * (code)
+ * var filter = function(cell)
+ * {
+ * 	return model.isVertex(cell);
+ * }
+ * var vertices = model.filterDescendants(filter);
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * filter - JavaScript function that takes an <mxCell> as an argument
+ * and returns a boolean.
+ * parent - Optional <mxCell> that is used as the root of the recursion.
+ */
+mxGraphModel.prototype.filterDescendants = function(filter, parent)
+{
+	// Creates a new array for storing the result
+	var result = [];
+
+	// Recursion starts at the root of the model
+	parent = parent || this.getRoot();
+	
+	// Checks if the filter returns true for the cell
+	// and adds it to the result array
+	if (filter == null || filter(parent))
+	{
+		result.push(parent);
+	}
+	
+	// Visits the children of the cell
+	var childCount = this.getChildCount(parent);
+	
+	for (var i = 0; i < childCount; i++)
+	{
+		var child = this.getChildAt(parent, i);
+		result = result.concat(this.filterDescendants(filter, child));
+	}
+
+	return result;
+};
+
+/**
+ * Function: getRoot
+ * 
+ * Returns the root of the model or the topmost parent of the given cell.
+ *
+ * Parameters:
+ * 
+ * cell - Optional <mxCell> that specifies the child.
+ */
+mxGraphModel.prototype.getRoot = function(cell)
+{
+	var root = cell || this.root;
+	
+	if (cell != null)
+	{
+		while (cell != null)
+		{
+			root = cell;
+			cell = this.getParent(cell);
+		}
+	}
+	
+	return root;
+};
+
+/**
+ * Function: setRoot
+ * 
+ * Sets the <root> of the model using <mxRootChange> and adds the change to
+ * the current transaction. This resets all datastructures in the model and
+ * is the preferred way of clearing an existing model. Returns the new
+ * root.
+ * 
+ * Example:
+ * 
+ * (code)
+ * var root = new mxCell();
+ * root.insert(new mxCell());
+ * model.setRoot(root);
+ * (end)
+ *
+ * Parameters:
+ * 
+ * root - <mxCell> that specifies the new root.
+ */
+mxGraphModel.prototype.setRoot = function(root)
+{
+	this.execute(new mxRootChange(this, root));
+	
+	return root;
+};
+
+/**
+ * Function: rootChanged
+ * 
+ * Inner callback to change the root of the model and update the internal
+ * datastructures, such as <cells> and <nextId>. Returns the previous root.
+ *
+ * Parameters:
+ * 
+ * root - <mxCell> that specifies the new root.
+ */
+mxGraphModel.prototype.rootChanged = function(root)
+{
+	var oldRoot = this.root;
+	this.root = root;
+	
+	// Resets counters and datastructures
+	this.nextId = 0;
+	this.cells = null;
+	this.cellAdded(root);
+	
+	return oldRoot;
+};
+
+/**
+ * Function: isRoot
+ * 
+ * Returns true if the given cell is the root of the model and a non-null
+ * value.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the possible root.
+ */
+mxGraphModel.prototype.isRoot = function(cell)
+{
+	return cell != null && this.root == cell;
+};
+
+/**
+ * Function: isLayer
+ * 
+ * Returns true if <isRoot> returns true for the parent of the given cell.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the possible layer.
+ */
+mxGraphModel.prototype.isLayer = function(cell)
+{
+	return this.isRoot(this.getParent(cell));
+};
+
+/**
+ * Function: isAncestor
+ * 
+ * Returns true if the given parent is an ancestor of the given child.
+ *
+ * Parameters:
+ * 
+ * parent - <mxCell> that specifies the parent.
+ * child - <mxCell> that specifies the child.
+ */
+mxGraphModel.prototype.isAncestor = function(parent, child)
+{
+	while (child != null && child != parent)
+	{
+		child = this.getParent(child);
+	}
+	
+	return child == parent;
+};
+
+/**
+ * Function: contains
+ * 
+ * Returns true if the model contains the given <mxCell>.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> that specifies the cell.
+ */
+mxGraphModel.prototype.contains = function(cell)
+{
+	return this.isAncestor(this.root, cell);
+};
+
+/**
+ * Function: getParent
+ * 
+ * Returns the parent of the given cell.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> whose parent should be returned.
+ */
+mxGraphModel.prototype.getParent = function(cell)
+{
+	return (cell != null) ? cell.getParent() : null;
+};
+
+/**
+ * Function: add
+ * 
+ * Adds the specified child to the parent at the given index using
+ * <mxChildChange> and adds the change to the current transaction. If no
+ * index is specified then the child is appended to the parent's array of
+ * children. Returns the inserted child.
+ * 
+ * Parameters:
+ * 
+ * parent - <mxCell> that specifies the parent to contain the child.
+ * child - <mxCell> that specifies the child to be inserted.
+ * index - Optional integer that specifies the index of the child.
+ */
+mxGraphModel.prototype.add = function(parent, child, index)
+{
+	if (child != parent && parent != null && child != null)
+	{	
+		// Appends the child if no index was specified
+		if (index == null)
+		{
+			index = this.getChildCount(parent);
+		}
+		
+		var parentChanged = parent != this.getParent(child);
+		this.execute(new mxChildChange(this, parent, child, index));
+
+		// Maintains the edges parents by moving the edges
+		// into the nearest common ancestor of its
+		// terminals
+		if (this.maintainEdgeParent && parentChanged)
+		{
+			this.updateEdgeParents(child);
+		}
+	}
+	
+	return child;
+};
+
+/**
+ * Function: cellAdded
+ * 
+ * Inner callback to update <cells> when a cell has been added. This
+ * implementation resolves collisions by creating new Ids. To change the
+ * ID of a cell after it was inserted into the model, use the following
+ * code:
+ * 
+ * (code
+ * delete model.cells[cell.getId()];
+ * cell.setId(newId);
+ * model.cells[cell.getId()] = cell;
+ * (end)
+ *
+ * If the change of the ID should be part of the command history, then the
+ * cell should be removed from the model and a clone with the new ID should
+ * be reinserted into the model instead.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> that specifies the cell that has been added.
+ */
+mxGraphModel.prototype.cellAdded = function(cell)
+{
+	if (cell != null)
+	{
+		// Creates an Id for the cell if not Id exists
+		if (cell.getId() == null && this.createIds)
+		{
+			cell.setId(this.createId(cell));
+		}
+		
+		if (cell.getId() != null)
+		{
+			var collision = this.getCell(cell.getId());
+			
+			if (collision != cell)
+			{	
+				// Creates new Id for the cell
+				// as long as there is a collision
+				while (collision != null)
+				{
+					cell.setId(this.createId(cell));
+					collision = this.getCell(cell.getId());
+				}
+				
+				// Lazily creates the cells dictionary
+				if (this.cells == null)
+				{
+					this.cells = new Object();
+				}
+				
+				this.cells[cell.getId()] = cell;
+			}
+		}
+		
+		// Makes sure IDs of deleted cells are not reused
+		if (mxUtils.isNumeric(cell.getId()))
+		{
+			this.nextId = Math.max(this.nextId, cell.getId());
+		}
+		
+		// Recursively processes child cells
+		var childCount = this.getChildCount(cell);
+		
+		for (var i=0; i<childCount; i++)
+		{
+			this.cellAdded(this.getChildAt(cell, i));
+		}
+	}
+};
+
+/**
+ * Function: createId
+ * 
+ * Hook method to create an Id for the specified cell. This implementation
+ * concatenates <prefix>, id and <postfix> to create the Id and increments
+ * <nextId>. The cell is ignored by this implementation, but can be used in
+ * overridden methods to prefix the Ids with eg. the cell type.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to create the Id for.
+ */
+mxGraphModel.prototype.createId = function(cell)
+{
+	var id = this.nextId;
+	this.nextId++;
+	
+	return this.prefix + id + this.postfix;
+};
+
+/**
+ * Function: updateEdgeParents
+ * 
+ * Updates the parent for all edges that are connected to cell or one of
+ * its descendants using <updateEdgeParent>.
+ */
+mxGraphModel.prototype.updateEdgeParents = function(cell, root)
+{
+	// Gets the topmost node of the hierarchy
+	root = root || this.getRoot(cell);
+	
+	// Updates edges on children first
+	var childCount = this.getChildCount(cell);
+	
+	for (var i = 0; i < childCount; i++)
+	{
+		var child = this.getChildAt(cell, i);
+		this.updateEdgeParents(child, root);
+	}
+	
+	// Updates the parents of all connected edges
+	var edgeCount = this.getEdgeCount(cell);
+	var edges = [];
+
+	for (var i = 0; i < edgeCount; i++)
+	{
+		edges.push(this.getEdgeAt(cell, i));
+	}
+	
+	for (var i = 0; i < edges.length; i++)
+	{
+		var edge = edges[i];
+		
+		// Updates edge parent if edge and child have
+		// a common root node (does not need to be the
+		// model root node)
+		if (this.isAncestor(root, edge))
+		{
+			this.updateEdgeParent(edge, root);
+		}
+	}
+};
+
+/**
+ * Function: updateEdgeParent
+ *
+ * Inner callback to update the parent of the specified <mxCell> to the
+ * nearest-common-ancestor of its two terminals.
+ *
+ * Parameters:
+ * 
+ * edge - <mxCell> that specifies the edge.
+ * root - <mxCell> that represents the current root of the model.
+ */
+mxGraphModel.prototype.updateEdgeParent = function(edge, root)
+{
+	var source = this.getTerminal(edge, true);
+	var target = this.getTerminal(edge, false);
+	var cell = null;
+	
+	// Uses the first non-relative descendants of the source terminal
+	while (source != null && !this.isEdge(source) &&
+		source.geometry != null && source.geometry.relative)
+	{
+		source = this.getParent(source);
+	}
+	
+	// Uses the first non-relative descendants of the target terminal
+	while (target != null && this.ignoreRelativeEdgeParent &&
+		!this.isEdge(target) && target.geometry != null && 
+		target.geometry.relative)
+	{
+		target = this.getParent(target);
+	}
+	
+	if (this.isAncestor(root, source) && this.isAncestor(root, target))
+	{
+		if (source == target)
+		{
+			cell = this.getParent(source);
+		}
+		else
+		{
+			cell = this.getNearestCommonAncestor(source, target);
+		}
+
+		if (cell != null && (this.getParent(cell) != this.root ||
+			this.isAncestor(cell, edge)) && this.getParent(edge) != cell)
+		{
+			var geo = this.getGeometry(edge);
+			
+			if (geo != null)
+			{
+				var origin1 = this.getOrigin(this.getParent(edge));
+				var origin2 = this.getOrigin(cell);
+				
+				var dx = origin2.x - origin1.x;
+				var dy = origin2.y - origin1.y;
+				
+				geo = geo.clone();
+				geo.translate(-dx, -dy);
+				this.setGeometry(edge, geo);
+			}
+
+			this.add(cell, edge, this.getChildCount(cell));
+		}
+	}
+};
+
+/**
+ * Function: getOrigin
+ * 
+ * Returns the absolute, accumulated origin for the children inside the
+ * given parent as an <mxPoint>.
+ */
+mxGraphModel.prototype.getOrigin = function(cell)
+{
+	var result = null;
+	
+	if (cell != null)
+	{
+		result = this.getOrigin(this.getParent(cell));
+		
+		if (!this.isEdge(cell))
+		{
+			var geo = this.getGeometry(cell);
+			
+			if (geo != null)
+			{
+				result.x += geo.x;
+				result.y += geo.y;
+			}
+		}
+	}
+	else
+	{
+		result = new mxPoint();
+	}
+	
+	return result;
+};
+
+/**
+ * Function: getNearestCommonAncestor
+ * 
+ * Returns the nearest common ancestor for the specified cells.
+ *
+ * Parameters:
+ * 
+ * cell1 - <mxCell> that specifies the first cell in the tree.
+ * cell2 - <mxCell> that specifies the second cell in the tree.
+ */
+mxGraphModel.prototype.getNearestCommonAncestor = function(cell1, cell2)
+{
+	if (cell1 != null && cell2 != null)
+	{		
+		// Creates the cell path for the second cell
+		var path = mxCellPath.create(cell2);
+
+		if (path != null && path.length > 0)
+		{
+			// Bubbles through the ancestors of the first
+			// cell to find the nearest common ancestor.
+			var cell = cell1;
+			var current = mxCellPath.create(cell);
+			
+			// Inverts arguments
+			if (path.length < current.length)
+			{
+				cell = cell2;
+				var tmp = current;
+				current = path;
+				path = tmp;
+			}
+			
+			while (cell != null)
+			{
+				var parent = this.getParent(cell);
+				
+				// Checks if the cell path is equal to the beginning of the given cell path
+				if (path.indexOf(current + mxCellPath.PATH_SEPARATOR) == 0 && parent != null)
+				{
+					return cell;
+				}
+				
+				current = mxCellPath.getParentPath(current);
+				cell = parent;
+			}
+		}
+	}
+	
+	return null;
+};
+
+/**
+ * Function: remove
+ * 
+ * Removes the specified cell from the model using <mxChildChange> and adds
+ * the change to the current transaction. This operation will remove the
+ * cell and all of its children from the model. Returns the removed cell.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> that should be removed.
+ */
+mxGraphModel.prototype.remove = function(cell)
+{
+	if (cell == this.root)
+	{
+		this.setRoot(null);
+	}
+	else if (this.getParent(cell) != null)
+	{
+		this.execute(new mxChildChange(this, null, cell));
+	}
+	
+	return cell;
+};
+
+/**
+ * Function: cellRemoved
+ * 
+ * Inner callback to update <cells> when a cell has been removed.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> that specifies the cell that has been removed.
+ */
+mxGraphModel.prototype.cellRemoved = function(cell)
+{
+	if (cell != null && this.cells != null)
+	{
+		// Recursively processes child cells
+		var childCount = this.getChildCount(cell);
+		
+		for (var i = childCount - 1; i >= 0; i--)
+		{
+			this.cellRemoved(this.getChildAt(cell, i));
+		}
+		
+		// Removes the dictionary entry for the cell
+		if (this.cells != null && cell.getId() != null)
+		{
+			delete this.cells[cell.getId()];
+		}
+	}
+};
+
+/**
+ * Function: parentForCellChanged
+ * 
+ * Inner callback to update the parent of a cell using <mxCell.insert>
+ * on the parent and return the previous parent.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> to update the parent for.
+ * parent - <mxCell> that specifies the new parent of the cell.
+ * index - Optional integer that defines the index of the child
+ * in the parent's child array.
+ */
+mxGraphModel.prototype.parentForCellChanged = function(cell, parent, index)
+{
+	var previous = this.getParent(cell);
+	
+	if (parent != null)
+	{
+		if (parent != previous || previous.getIndex(cell) != index)
+		{
+			parent.insert(cell, index);
+		}
+	}
+	else if (previous != null)
+	{
+		var oldIndex = previous.getIndex(cell);
+		previous.remove(oldIndex);
+	}
+	
+	// Checks if the previous parent was already in the
+	// model and avoids calling cellAdded if it was.
+	if (!this.contains(previous) && parent != null)
+	{
+		this.cellAdded(cell);
+	}
+	else if (parent == null)
+	{
+		this.cellRemoved(cell);
+	}
+	
+	return previous;
+};
+
+/**
+ * Function: getChildCount
+ *
+ * Returns the number of children in the given cell.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> whose number of children should be returned.
+ */
+mxGraphModel.prototype.getChildCount = function(cell)
+{
+	return (cell != null) ? cell.getChildCount() : 0;
+};
+
+/**
+ * Function: getChildAt
+ *
+ * Returns the child of the given <mxCell> at the given index.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the parent.
+ * index - Integer that specifies the index of the child to be returned.
+ */
+mxGraphModel.prototype.getChildAt = function(cell, index)
+{
+	return (cell != null) ? cell.getChildAt(index) : null;
+};
+
+/**
+ * Function: getChildren
+ * 
+ * Returns all children of the given <mxCell> as an array of <mxCells>. The
+ * return value should be only be read.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> the represents the parent.
+ */
+mxGraphModel.prototype.getChildren = function(cell)
+{
+	return (cell != null) ? cell.children : null;
+};
+	
+/**
+ * Function: getChildVertices
+ * 
+ * Returns the child vertices of the given parent.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> whose child vertices should be returned.
+ */
+mxGraphModel.prototype.getChildVertices = function(parent)
+{
+	return this.getChildCells(parent, true, false);
+};
+		
+/**
+ * Function: getChildEdges
+ * 
+ * Returns the child edges of the given parent.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> whose child edges should be returned.
+ */
+mxGraphModel.prototype.getChildEdges = function(parent)
+{
+	return this.getChildCells(parent, false, true);
+};
+
+/**
+ * Function: getChildCells
+ * 
+ * Returns the children of the given cell that are vertices and/or edges
+ * depending on the arguments.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> the represents the parent.
+ * vertices - Boolean indicating if child vertices should be returned.
+ * Default is false.
+ * edges - Boolean indicating if child edges should be returned.
+ * Default is false.
+ */
+mxGraphModel.prototype.getChildCells = function(parent, vertices, edges)
+{
+	vertices = (vertices != null) ? vertices : false;
+	edges = (edges != null) ? edges : false;
+	
+	var childCount = this.getChildCount(parent);
+	var result = [];
+
+	for (var i = 0; i < childCount; i++)
+	{
+		var child = this.getChildAt(parent, i);
+
+		if ((!edges && !vertices) || (edges && this.isEdge(child)) ||
+			(vertices && this.isVertex(child)))
+		{
+			result.push(child);
+		}
+	}
+
+	return result;
+};
+		
+/**
+ * Function: getTerminal
+ * 
+ * Returns the source or target <mxCell> of the given edge depending on the
+ * value of the boolean parameter.
+ *
+ * Parameters:
+ * 
+ * edge - <mxCell> that specifies the edge.
+ * isSource - Boolean indicating which end of the edge should be returned.
+ */
+mxGraphModel.prototype.getTerminal = function(edge, isSource)
+{
+	return (edge != null) ? edge.getTerminal(isSource) : null;
+};
+
+/**
+ * Function: setTerminal
+ * 
+ * Sets the source or target terminal of the given <mxCell> using
+ * <mxTerminalChange> and adds the change to the current transaction.
+ * This implementation updates the parent of the edge using <updateEdgeParent>
+ * if required.
+ *
+ * Parameters:
+ * 
+ * edge - <mxCell> that specifies the edge.
+ * terminal - <mxCell> that specifies the new terminal.
+ * isSource - Boolean indicating if the terminal is the new source or
+ * target terminal of the edge.
+ */
+mxGraphModel.prototype.setTerminal = function(edge, terminal, isSource)
+{
+	var terminalChanged = terminal != this.getTerminal(edge, isSource);
+	this.execute(new mxTerminalChange(this, edge, terminal, isSource));
+	
+	if (this.maintainEdgeParent && terminalChanged)
+	{
+		this.updateEdgeParent(edge, this.getRoot());
+	}
+	
+	return terminal;
+};
+	
+/**
+ * Function: setTerminals
+ * 
+ * Sets the source and target <mxCell> of the given <mxCell> in a single
+ * transaction using <setTerminal> for each end of the edge.
+ *
+ * Parameters:
+ * 
+ * edge - <mxCell> that specifies the edge.
+ * source - <mxCell> that specifies the new source terminal.
+ * target - <mxCell> that specifies the new target terminal.
+ */
+mxGraphModel.prototype.setTerminals = function(edge, source, target)
+{
+	this.beginUpdate();
+	try
+	{
+		this.setTerminal(edge, source, true);
+		this.setTerminal(edge, target, false);
+	}
+	finally
+	{
+		this.endUpdate();
+	}
+};
+
+/**
+ * Function: terminalForCellChanged
+ * 
+ * Inner helper function to update the terminal of the edge using
+ * <mxCell.insertEdge> and return the previous terminal.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> that specifies the edge to be updated.
+ * terminal - <mxCell> that specifies the new terminal.
+ * isSource - Boolean indicating if the terminal is the new source or
+ * target terminal of the edge.
+ */
+mxGraphModel.prototype.terminalForCellChanged = function(edge, terminal, isSource)
+{
+	var previous = this.getTerminal(edge, isSource);
+	
+	if (terminal != null)
+	{
+		terminal.insertEdge(edge, isSource);
+	}
+	else if (previous != null)
+	{
+		previous.removeEdge(edge, isSource);
+	}
+	
+	return previous;
+};
+
+/**
+ * Function: getEdgeCount
+ * 
+ * Returns the number of distinct edges connected to the given cell.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the vertex.
+ */
+mxGraphModel.prototype.getEdgeCount = function(cell)
+{
+	return (cell != null) ? cell.getEdgeCount() : 0;
+};
+
+/**
+ * Function: getEdgeAt
+ * 
+ * Returns the edge of cell at the given index.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> that specifies the vertex.
+ * index - Integer that specifies the index of the edge
+ * to return.
+ */
+mxGraphModel.prototype.getEdgeAt = function(cell, index)
+{
+	return (cell != null) ? cell.getEdgeAt(index) : null;
+};
+	
+/**
+ * Function: getDirectedEdgeCount
+ * 
+ * Returns the number of incoming or outgoing edges, ignoring the given
+ * edge.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose edge count should be returned.
+ * outgoing - Boolean that specifies if the number of outgoing or
+ * incoming edges should be returned.
+ * ignoredEdge - <mxCell> that represents an edge to be ignored.
+ */
+mxGraphModel.prototype.getDirectedEdgeCount = function(cell, outgoing, ignoredEdge)
+{
+	var count = 0;
+	var edgeCount = this.getEdgeCount(cell);
+
+	for (var i = 0; i < edgeCount; i++)
+	{
+		var edge = this.getEdgeAt(cell, i);
+
+		if (edge != ignoredEdge && this.getTerminal(edge, outgoing) == cell)
+		{
+			count++;
+		}
+	}
+
+	return count;
+};
+
+/**
+ * Function: getConnections
+ * 
+ * Returns all edges of the given cell without loops.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose edges should be returned.
+ * 
+ */
+mxGraphModel.prototype.getConnections = function(cell)
+{
+	return this.getEdges(cell, true, true, false);
+};
+
+/**
+ * Function: getIncomingEdges
+ * 
+ * Returns the incoming edges of the given cell without loops.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose incoming edges should be returned.
+ * 
+ */
+mxGraphModel.prototype.getIncomingEdges = function(cell)
+{
+	return this.getEdges(cell, true, false, false);
+};
+
+/**
+ * Function: getOutgoingEdges
+ * 
+ * Returns the outgoing edges of the given cell without loops.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose outgoing edges should be returned.
+ * 
+ */
+mxGraphModel.prototype.getOutgoingEdges = function(cell)
+{
+	return this.getEdges(cell, false, true, false);
+};
+
+/**
+ * Function: getEdges
+ * 
+ * Returns all distinct edges connected to this cell as a new array of
+ * <mxCells>. If at least one of incoming or outgoing is true, then loops
+ * are ignored, otherwise if both are false, then all edges connected to
+ * the given cell are returned including loops.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that specifies the cell.
+ * incoming - Optional boolean that specifies if incoming edges should be
+ * returned. Default is true.
+ * outgoing - Optional boolean that specifies if outgoing edges should be
+ * returned. Default is true.
+ * includeLoops - Optional boolean that specifies if loops should be returned.
+ * Default is true. 
+ */
+mxGraphModel.prototype.getEdges = function(cell, incoming, outgoing, includeLoops)
+{
+	incoming = (incoming != null) ? incoming : true;
+	outgoing = (outgoing != null) ? outgoing : true;
+	includeLoops = (includeLoops != null) ? includeLoops : true;
+	
+	var edgeCount = this.getEdgeCount(cell);
+	var result = [];
+
+	for (var i = 0; i < edgeCount; i++)
+	{
+		var edge = this.getEdgeAt(cell, i);
+		var source = this.getTerminal(edge, true);
+		var target = this.getTerminal(edge, false);
+
+		if ((includeLoops && source == target) || ((source != target) && ((incoming && target == cell) ||
+			(outgoing && source == cell))))
+		{
+			result.push(edge);
+		}
+	}
+
+	return result;
+};
+
+/**
+ * Function: getEdgesBetween
+ * 
+ * Returns all edges between the given source and target pair. If directed
+ * is true, then only edges from the source to the target are returned,
+ * otherwise, all edges between the two cells are returned.
+ * 
+ * Parameters:
+ * 
+ * source - <mxCell> that defines the source terminal of the edge to be
+ * returned.
+ * target - <mxCell> that defines the target terminal of the edge to be
+ * returned.
+ * directed - Optional boolean that specifies if the direction of the
+ * edge should be taken into account. Default is false.
+ */
+mxGraphModel.prototype.getEdgesBetween = function(source, target, directed)
+{
+	directed = (directed != null) ? directed : false;
+	
+	var tmp1 = this.getEdgeCount(source);
+	var tmp2 = this.getEdgeCount(target);
+	
+	// Assumes the source has less connected edges
+	var terminal = source;
+	var edgeCount = tmp1;
+	
+	// Uses the smaller array of connected edges
+	// for searching the edge
+	if (tmp2 < tmp1)
+	{
+		edgeCount = tmp2;
+		terminal = target;
+	}
+	
+	var result = [];
+	
+	// Checks if the edge is connected to the correct
+	// cell and returns the first match
+	for (var i = 0; i < edgeCount; i++)
+	{
+		var edge = this.getEdgeAt(terminal, i);
+		var src = this.getTerminal(edge, true);
+		var trg = this.getTerminal(edge, false);
+		var directedMatch = (src == source) && (trg == target);
+		var oppositeMatch = (trg == source) && (src == target);
+
+		if (directedMatch || (!directed && oppositeMatch))
+		{
+			result.push(edge);
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: getOpposites
+ * 
+ * Returns all opposite vertices wrt terminal for the given edges, only
+ * returning sources and/or targets as specified. The result is returned
+ * as an array of <mxCells>.
+ * 
+ * Parameters:
+ * 
+ * edges - Array of <mxCells> that contain the edges to be examined.
+ * terminal - <mxCell> that specifies the known end of the edges.
+ * sources - Boolean that specifies if source terminals should be contained
+ * in the result. Default is true.
+ * targets - Boolean that specifies if target terminals should be contained
+ * in the result. Default is true.
+ */
+mxGraphModel.prototype.getOpposites = function(edges, terminal, sources, targets)
+{
+	sources = (sources != null) ? sources : true;
+	targets = (targets != null) ? targets : true;
+	
+	var terminals = [];
+	
+	if (edges != null)
+	{
+		for (var i = 0; i < edges.length; i++)
+		{
+			var source = this.getTerminal(edges[i], true);
+			var target = this.getTerminal(edges[i], false);
+			
+			// Checks if the terminal is the source of
+			// the edge and if the target should be
+			// stored in the result
+			if (source == terminal && target != null && target != terminal && targets)
+			{
+				terminals.push(target);
+			}
+			
+			// Checks if the terminal is the taget of
+			// the edge and if the source should be
+			// stored in the result
+			else if (target == terminal && source != null && source != terminal && sources)
+			{
+				terminals.push(source);
+			}
+		}
+	}
+	
+	return terminals;
+};
+
+/**
+ * Function: getTopmostCells
+ * 
+ * Returns the topmost cells of the hierarchy in an array that contains no
+ * descendants for each <mxCell> that it contains. Duplicates should be
+ * removed in the cells array to improve performance.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> whose topmost ancestors should be returned.
+ */
+mxGraphModel.prototype.getTopmostCells = function(cells)
+{
+	var dict = new mxDictionary();
+	var tmp = [];
+	
+	for (var i = 0; i < cells.length; i++)
+	{
+		dict.put(cells[i], true);
+	}
+	
+	for (var i = 0; i < cells.length; i++)
+	{
+		var cell = cells[i];
+		var topmost = true;
+		var parent = this.getParent(cell);
+		
+		while (parent != null)
+		{
+			if (dict.get(parent))
+			{
+				topmost = false;
+				break;
+			}
+			
+			parent = this.getParent(parent);
+		}
+		
+		if (topmost)
+		{
+			tmp.push(cell);
+		}
+	}
+	
+	return tmp;
+};
+
+/**
+ * Function: isVertex
+ * 
+ * Returns true if the given cell is a vertex.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the possible vertex.
+ */
+mxGraphModel.prototype.isVertex = function(cell)
+{
+	return (cell != null) ? cell.isVertex() : false;
+};
+
+/**
+ * Function: isEdge
+ * 
+ * Returns true if the given cell is an edge.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the possible edge.
+ */
+mxGraphModel.prototype.isEdge = function(cell)
+{
+	return (cell != null) ? cell.isEdge() : false;
+};
+
+/**
+ * Function: isConnectable
+ * 
+ * Returns true if the given <mxCell> is connectable. If <edgesConnectable>
+ * is false, then this function returns false for all edges else it returns
+ * the return value of <mxCell.isConnectable>.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> whose connectable state should be returned.
+ */
+mxGraphModel.prototype.isConnectable = function(cell)
+{
+	return (cell != null) ? cell.isConnectable() : false;
+};
+
+/**
+ * Function: getValue
+ * 
+ * Returns the user object of the given <mxCell> using <mxCell.getValue>.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> whose user object should be returned.
+ */
+mxGraphModel.prototype.getValue = function(cell)
+{
+	return (cell != null) ? cell.getValue() : null;
+};
+
+/**
+ * Function: setValue
+ * 
+ * Sets the user object of then given <mxCell> using <mxValueChange>
+ * and adds the change to the current transaction.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> whose user object should be changed.
+ * value - Object that defines the new user object.
+ */
+mxGraphModel.prototype.setValue = function(cell, value)
+{
+	this.execute(new mxValueChange(this, cell, value));
+	
+	return value;
+};
+
+/**
+ * Function: valueForCellChanged
+ * 
+ * Inner callback to update the user object of the given <mxCell>
+ * using <mxCell.valueChanged> and return the previous value,
+ * that is, the return value of <mxCell.valueChanged>.
+ * 
+ * To change a specific attribute in an XML node, the following code can be
+ * used.
+ * 
+ * (code)
+ * graph.getModel().valueForCellChanged = function(cell, value)
+ * {
+ *   var previous = cell.value.getAttribute('label');
+ *   cell.value.setAttribute('label', value);
+ *   
+ *   return previous;
+ * };
+ * (end) 
+ */
+mxGraphModel.prototype.valueForCellChanged = function(cell, value)
+{
+	return cell.valueChanged(value);
+};
+
+/**
+ * Function: getGeometry
+ * 
+ * Returns the <mxGeometry> of the given <mxCell>.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> whose geometry should be returned.
+ */
+mxGraphModel.prototype.getGeometry = function(cell)
+{
+	return (cell != null) ? cell.getGeometry() : null;
+};
+
+/**
+ * Function: setGeometry
+ * 
+ * Sets the <mxGeometry> of the given <mxCell>. The actual update
+ * of the cell is carried out in <geometryForCellChanged>. The
+ * <mxGeometryChange> action is used to encapsulate the change.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose geometry should be changed.
+ * geometry - <mxGeometry> that defines the new geometry.
+ */
+mxGraphModel.prototype.setGeometry = function(cell, geometry)
+{
+	if (geometry != this.getGeometry(cell))
+	{
+		this.execute(new mxGeometryChange(this, cell, geometry));
+	}
+	
+	return geometry;
+};
+
+/**
+ * Function: geometryForCellChanged
+ * 
+ * Inner callback to update the <mxGeometry> of the given <mxCell> using
+ * <mxCell.setGeometry> and return the previous <mxGeometry>.
+ */
+mxGraphModel.prototype.geometryForCellChanged = function(cell, geometry)
+{
+	var previous = this.getGeometry(cell);
+	cell.setGeometry(geometry);
+	
+	return previous;
+};
+
+/**
+ * Function: getStyle
+ * 
+ * Returns the style of the given <mxCell>.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> whose style should be returned.
+ */
+mxGraphModel.prototype.getStyle = function(cell)
+{
+	return (cell != null) ? cell.getStyle() : null;
+};
+
+/**
+ * Function: setStyle
+ * 
+ * Sets the style of the given <mxCell> using <mxStyleChange> and
+ * adds the change to the current transaction.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> whose style should be changed.
+ * style - String of the form [stylename;|key=value;] to specify
+ * the new cell style.
+ */
+mxGraphModel.prototype.setStyle = function(cell, style)
+{
+	if (style != this.getStyle(cell))
+	{
+		this.execute(new mxStyleChange(this, cell, style));
+	}
+	
+	return style;
+};
+
+/**
+ * Function: styleForCellChanged
+ * 
+ * Inner callback to update the style of the given <mxCell>
+ * using <mxCell.setStyle> and return the previous style.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> that specifies the cell to be updated.
+ * style - String of the form [stylename;|key=value;] to specify
+ * the new cell style.
+ */
+mxGraphModel.prototype.styleForCellChanged = function(cell, style)
+{
+	var previous = this.getStyle(cell);
+	cell.setStyle(style);
+	
+	return previous;
+};
+
+/**
+ * Function: isCollapsed
+ * 
+ * Returns true if the given <mxCell> is collapsed.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> whose collapsed state should be returned.
+ */
+mxGraphModel.prototype.isCollapsed = function(cell)
+{
+	return (cell != null) ? cell.isCollapsed() : false;
+};
+
+/**
+ * Function: setCollapsed
+ * 
+ * Sets the collapsed state of the given <mxCell> using <mxCollapseChange>
+ * and adds the change to the current transaction.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> whose collapsed state should be changed.
+ * collapsed - Boolean that specifies the new collpased state.
+ */
+mxGraphModel.prototype.setCollapsed = function(cell, collapsed)
+{
+	if (collapsed != this.isCollapsed(cell))
+	{
+		this.execute(new mxCollapseChange(this, cell, collapsed));
+	}
+	
+	return collapsed;
+};
+	
+/**
+ * Function: collapsedStateForCellChanged
+ *
+ * Inner callback to update the collapsed state of the
+ * given <mxCell> using <mxCell.setCollapsed> and return
+ * the previous collapsed state.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> that specifies the cell to be updated.
+ * collapsed - Boolean that specifies the new collpased state.
+ */
+mxGraphModel.prototype.collapsedStateForCellChanged = function(cell, collapsed)
+{
+	var previous = this.isCollapsed(cell);
+	cell.setCollapsed(collapsed);
+	
+	return previous;
+};
+
+/**
+ * Function: isVisible
+ * 
+ * Returns true if the given <mxCell> is visible.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose visible state should be returned.
+ */
+mxGraphModel.prototype.isVisible = function(cell)
+{
+	return (cell != null) ? cell.isVisible() : false;
+};
+
+/**
+ * Function: setVisible
+ * 
+ * Sets the visible state of the given <mxCell> using <mxVisibleChange> and
+ * adds the change to the current transaction.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> whose visible state should be changed.
+ * visible - Boolean that specifies the new visible state.
+ */
+mxGraphModel.prototype.setVisible = function(cell, visible)
+{
+	if (visible != this.isVisible(cell))
+	{
+		this.execute(new mxVisibleChange(this, cell, visible));
+	}
+	
+	return visible;
+};
+	
+/**
+ * Function: visibleStateForCellChanged
+ *
+ * Inner callback to update the visible state of the
+ * given <mxCell> using <mxCell.setCollapsed> and return
+ * the previous visible state.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> that specifies the cell to be updated.
+ * visible - Boolean that specifies the new visible state.
+ */
+mxGraphModel.prototype.visibleStateForCellChanged = function(cell, visible)
+{
+	var previous = this.isVisible(cell);
+	cell.setVisible(visible);
+	
+	return previous;
+};
+
+/**
+ * Function: execute
+ * 
+ * Executes the given edit and fires events if required. The edit object
+ * requires an execute function which is invoked. The edit is added to the
+ * <currentEdit> between <beginUpdate> and <endUpdate> calls, so that
+ * events will be fired if this execute is an individual transaction, that
+ * is, if no previous <beginUpdate> calls have been made without calling
+ * <endUpdate>. This implementation fires an <execute> event before
+ * executing the given change.
+ * 
+ * Parameters:
+ * 
+ * change - Object that described the change.
+ */
+mxGraphModel.prototype.execute = function(change)
+{
+	change.execute();
+	this.beginUpdate();
+	this.currentEdit.add(change);
+	this.fireEvent(new mxEventObject(mxEvent.EXECUTE, 'change', change));
+	// New global executed event
+	this.fireEvent(new mxEventObject(mxEvent.EXECUTED, 'change', change));
+	this.endUpdate();
+};
+
+/**
+ * Function: beginUpdate
+ * 
+ * Increments the <updateLevel> by one. The event notification
+ * is queued until <updateLevel> reaches 0 by use of
+ * <endUpdate>.
+ *
+ * All changes on <mxGraphModel> are transactional,
+ * that is, they are executed in a single undoable change
+ * on the model (without transaction isolation).
+ * Therefore, if you want to combine any
+ * number of changes into a single undoable change,
+ * you should group any two or more API calls that
+ * modify the graph model between <beginUpdate>
+ * and <endUpdate> calls as shown here:
+ * 
+ * (code)
+ * var model = graph.getModel();
+ * var parent = graph.getDefaultParent();
+ * var index = model.getChildCount(parent);
+ * model.beginUpdate();
+ * try
+ * {
+ *   model.add(parent, v1, index);
+ *   model.add(parent, v2, index+1);
+ * }
+ * finally
+ * {
+ *   model.endUpdate();
+ * }
+ * (end)
+ * 
+ * Of course there is a shortcut for appending a
+ * sequence of cells into the default parent:
+ * 
+ * (code)
+ * graph.addCells([v1, v2]).
+ * (end)
+ */
+mxGraphModel.prototype.beginUpdate = function()
+{
+	this.updateLevel++;
+	this.fireEvent(new mxEventObject(mxEvent.BEGIN_UPDATE));
+	
+	if (this.updateLevel == 1)
+	{
+		this.fireEvent(new mxEventObject(mxEvent.START_EDIT));
+	}
+};
+
+/**
+ * Function: endUpdate
+ * 
+ * Decrements the <updateLevel> by one and fires an <undo>
+ * event if the <updateLevel> reaches 0. This function
+ * indirectly fires a <change> event by invoking the notify
+ * function on the <currentEdit> und then creates a new
+ * <currentEdit> using <createUndoableEdit>.
+ *
+ * The <undo> event is fired only once per edit, whereas
+ * the <change> event is fired whenever the notify
+ * function is invoked, that is, on undo and redo of
+ * the edit.
+ */
+mxGraphModel.prototype.endUpdate = function()
+{
+	this.updateLevel--;
+	
+	if (this.updateLevel == 0)
+	{
+		this.fireEvent(new mxEventObject(mxEvent.END_EDIT));
+	}
+	
+	if (!this.endingUpdate)
+	{
+		this.endingUpdate = this.updateLevel == 0;
+		this.fireEvent(new mxEventObject(mxEvent.END_UPDATE, 'edit', this.currentEdit));
+
+		try
+		{		
+			if (this.endingUpdate && !this.currentEdit.isEmpty())
+			{
+				this.fireEvent(new mxEventObject(mxEvent.BEFORE_UNDO, 'edit', this.currentEdit));
+				var tmp = this.currentEdit;
+				this.currentEdit = this.createUndoableEdit();
+				tmp.notify();
+				this.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', tmp));
+			}
+		}
+		finally
+		{
+			this.endingUpdate = false;
+		}
+	}
+};
+
+/**
+ * Function: createUndoableEdit
+ * 
+ * Creates a new <mxUndoableEdit> that implements the
+ * notify function to fire a <change> and <notify> event
+ * through the <mxUndoableEdit>'s source.
+ */
+mxGraphModel.prototype.createUndoableEdit = function()
+{
+	var edit = new mxUndoableEdit(this, true);
+	
+	edit.notify = function()
+	{
+		// LATER: Remove changes property (deprecated)
+		edit.source.fireEvent(new mxEventObject(mxEvent.CHANGE,
+			'edit', edit, 'changes', edit.changes));
+		edit.source.fireEvent(new mxEventObject(mxEvent.NOTIFY,
+			'edit', edit, 'changes', edit.changes));
+	};
+	
+	return edit;
+};
+
+/**
+ * Function: mergeChildren
+ * 
+ * Merges the children of the given cell into the given target cell inside
+ * this model. All cells are cloned unless there is a corresponding cell in
+ * the model with the same id, in which case the source cell is ignored and
+ * all edges are connected to the corresponding cell in this model. Edges
+ * are considered to have no identity and are always cloned unless the
+ * cloneAllEdges flag is set to false, in which case edges with the same
+ * id in the target model are reconnected to reflect the terminals of the
+ * source edges.
+ */
+mxGraphModel.prototype.mergeChildren = function(from, to, cloneAllEdges)
+{
+	cloneAllEdges = (cloneAllEdges != null) ? cloneAllEdges : true;
+	
+	this.beginUpdate();
+	try
+	{
+		var mapping = new Object();
+		this.mergeChildrenImpl(from, to, cloneAllEdges, mapping);
+		
+		// Post-processes all edges in the mapping and
+		// reconnects the terminals to the corresponding
+		// cells in the target model
+		for (var key in mapping)
+		{
+			var cell = mapping[key];
+			var terminal = this.getTerminal(cell, true);
+
+			if (terminal != null)
+			{
+				terminal = mapping[mxCellPath.create(terminal)];
+				this.setTerminal(cell, terminal, true);
+			}
+			
+			terminal = this.getTerminal(cell, false);
+			
+			if (terminal != null)
+			{
+				terminal = mapping[mxCellPath.create(terminal)];
+				this.setTerminal(cell, terminal, false);
+			}
+		}
+	}
+	finally
+	{
+		this.endUpdate();
+	}
+};
+
+/**
+ * Function: mergeChildren
+ * 
+ * Clones the children of the source cell into the given target cell in
+ * this model and adds an entry to the mapping that maps from the source
+ * cell to the target cell with the same id or the clone of the source cell
+ * that was inserted into this model.
+ */
+mxGraphModel.prototype.mergeChildrenImpl = function(from, to, cloneAllEdges, mapping)
+{
+	this.beginUpdate();
+	try
+	{
+		var childCount = from.getChildCount();
+		
+		for (var i = 0; i < childCount; i++)
+		{
+			var cell = from.getChildAt(i);
+			
+			if (typeof(cell.getId) == 'function')
+			{
+				var id = cell.getId();
+				var target = (id != null && (!this.isEdge(cell) || !cloneAllEdges)) ?
+						this.getCell(id) : null;
+				
+				// Clones and adds the child if no cell exists for the id
+				if (target == null)
+				{
+					var clone = cell.clone();
+					clone.setId(id);
+					
+					// Sets the terminals from the original cell to the clone
+					// because the lookup uses strings not cells in JS
+					clone.setTerminal(cell.getTerminal(true), true);
+					clone.setTerminal(cell.getTerminal(false), false);
+					
+					// Do *NOT* use model.add as this will move the edge away
+					// from the parent in updateEdgeParent if maintainEdgeParent
+					// is enabled in the target model
+					target = to.insert(clone);
+					this.cellAdded(target);
+				}
+				
+				// Stores the mapping for later reconnecting edges
+				mapping[mxCellPath.create(cell)] = target;
+				
+				// Recurses
+				this.mergeChildrenImpl(cell, target, cloneAllEdges, mapping);
+			}
+		}
+	}
+	finally
+	{
+		this.endUpdate();
+	}
+};
+
+/**
+ * Function: getParents
+ * 
+ * Returns an array that represents the set (no duplicates) of all parents
+ * for the given array of cells.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of cells whose parents should be returned.
+ */
+mxGraphModel.prototype.getParents = function(cells)
+{
+	var parents = [];
+	
+	if (cells != null)
+	{
+		var dict = new mxDictionary();
+		
+		for (var i = 0; i < cells.length; i++)
+		{
+			var parent = this.getParent(cells[i]);
+			
+			if (parent != null && !dict.get(parent))
+			{
+				dict.put(parent, true);
+				parents.push(parent);
+			}
+		}
+	}
+	
+	return parents;
+};
+
+//
+// Cell Cloning
+//
+
+/**
+ * Function: cloneCell
+ * 
+ * Returns a deep clone of the given <mxCell> (including
+ * the children) which is created using <cloneCells>.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> to be cloned.
+ */
+mxGraphModel.prototype.cloneCell = function(cell)
+{
+	if (cell != null)
+	{
+		return this.cloneCells([cell], true)[0];
+	}
+	
+	return null;
+};
+
+/**
+ * Function: cloneCells
+ * 
+ * Returns an array of clones for the given array of <mxCells>.
+ * Depending on the value of includeChildren, a deep clone is created for
+ * each cell. Connections are restored based if the corresponding
+ * cell is contained in the passed in array.
+ *
+ * Parameters:
+ * 
+ * cells - Array of <mxCell> to be cloned.
+ * includeChildren - Boolean indicating if the cells should be cloned
+ * with all descendants.
+ * mapping - Optional mapping for existing clones.
+ */
+mxGraphModel.prototype.cloneCells = function(cells, includeChildren, mapping)
+{
+	mapping = (mapping != null) ? mapping : new Object();
+	var clones = [];
+	
+	for (var i = 0; i < cells.length; i++)
+	{
+		if (cells[i] != null)
+		{
+			clones.push(this.cloneCellImpl(cells[i], mapping, includeChildren));
+		}
+		else
+		{
+			clones.push(null);
+		}
+	}
+	
+	for (var i = 0; i < clones.length; i++)
+	{
+		if (clones[i] != null)
+		{
+			this.restoreClone(clones[i], cells[i], mapping);
+		}
+	}
+	
+	return clones;
+};
+			
+/**
+ * Function: cloneCellImpl
+ * 
+ * Inner helper method for cloning cells recursively.
+ */
+mxGraphModel.prototype.cloneCellImpl = function(cell, mapping, includeChildren)
+{
+	var clone = this.cellCloned(cell);
+	
+	// Stores the clone in the lookup table
+	mapping[mxObjectIdentity.get(cell)] = clone;
+	
+	if (includeChildren)
+	{
+		var childCount = this.getChildCount(cell);
+		
+		for (var i = 0; i < childCount; i++)
+		{
+			var cloneChild = this.cloneCellImpl(
+				this.getChildAt(cell, i), mapping, true);
+			clone.insert(cloneChild);
+		}
+	}
+	
+	return clone;
+};
+
+/**
+ * Function: cellCloned
+ * 
+ * Hook for cloning the cell. This returns cell.clone() or
+ * any possible exceptions.
+ */
+mxGraphModel.prototype.cellCloned = function(cell)
+{
+	return cell.clone();
+};
+
+/**
+ * Function: restoreClone
+ * 
+ * Inner helper method for restoring the connections in
+ * a network of cloned cells.
+ */
+mxGraphModel.prototype.restoreClone = function(clone, cell, mapping)
+{
+	var source = this.getTerminal(cell, true);
+	
+	if (source != null)
+	{
+		var tmp = mapping[mxObjectIdentity.get(source)];
+		
+		if (tmp != null)
+		{
+			tmp.insertEdge(clone, true);
+		}
+	}
+	
+	var target = this.getTerminal(cell, false);
+	
+	if (target != null)
+	{
+		var tmp = mapping[mxObjectIdentity.get(target)];
+		
+		if (tmp != null)
+		{	
+			tmp.insertEdge(clone, false);
+		}
+	}
+	
+	var childCount = this.getChildCount(clone);
+	
+	for (var i = 0; i < childCount; i++)
+	{
+		this.restoreClone(this.getChildAt(clone, i),
+			this.getChildAt(cell, i), mapping);
+	}
+};
+
+//
+// Atomic changes
+//
+
+/**
+ * Class: mxRootChange
+ * 
+ * Action to change the root in a model.
+ *
+ * Constructor: mxRootChange
+ * 
+ * Constructs a change of the root in the
+ * specified model.
+ */
+function mxRootChange(model, root)
+{
+	this.model = model;
+	this.root = root;
+	this.previous = root;
+};
+
+/**
+ * Function: execute
+ * 
+ * Carries out a change of the root using
+ * <mxGraphModel.rootChanged>.
+ */
+mxRootChange.prototype.execute = function()
+{
+	this.root = this.previous;
+	this.previous = this.model.rootChanged(this.previous);
+};
+
+/**
+ * Class: mxChildChange
+ * 
+ * Action to add or remove a child in a model.
+ *
+ * Constructor: mxChildChange
+ * 
+ * Constructs a change of a child in the
+ * specified model.
+ */
+function mxChildChange(model, parent, child, index)
+{
+	this.model = model;
+	this.parent = parent;
+	this.previous = parent;
+	this.child = child;
+	this.index = index;
+	this.previousIndex = index;
+};
+
+/**
+ * Function: execute
+ * 
+ * Changes the parent of <child> using
+ * <mxGraphModel.parentForCellChanged> and
+ * removes or restores the cell's
+ * connections.
+ */
+mxChildChange.prototype.execute = function()
+{
+	var tmp = this.model.getParent(this.child);
+	var tmp2 = (tmp != null) ? tmp.getIndex(this.child) : 0;
+	
+	if (this.previous == null)
+	{
+		this.connect(this.child, false);
+	}
+	
+	tmp = this.model.parentForCellChanged(
+		this.child, this.previous, this.previousIndex);
+		
+	if (this.previous != null)
+	{
+		this.connect(this.child, true);
+	}
+	
+	this.parent = this.previous;
+	this.previous = tmp;
+	this.index = this.previousIndex;
+	this.previousIndex = tmp2;
+};
+
+/**
+ * Function: disconnect
+ * 
+ * Disconnects the given cell recursively from its
+ * terminals and stores the previous terminal in the
+ * cell's terminals.
+ */
+mxChildChange.prototype.connect = function(cell, isConnect)
+{
+	isConnect = (isConnect != null) ? isConnect : true;
+	
+	var source = cell.getTerminal(true);
+	var target = cell.getTerminal(false);
+	
+	if (source != null)
+	{
+		if (isConnect)
+		{
+			this.model.terminalForCellChanged(cell, source, true);
+		}
+		else
+		{
+			this.model.terminalForCellChanged(cell, null, true);
+		}
+	}
+	
+	if (target != null)
+	{
+		if (isConnect)
+		{
+			this.model.terminalForCellChanged(cell, target, false);
+		}
+		else
+		{
+			this.model.terminalForCellChanged(cell, null, false);
+		}
+	}
+	
+	cell.setTerminal(source, true);
+	cell.setTerminal(target, false);
+	
+	var childCount = this.model.getChildCount(cell);
+	
+	for (var i=0; i<childCount; i++)
+	{
+		this.connect(this.model.getChildAt(cell, i), isConnect);
+	}
+};
+
+/**
+ * Class: mxTerminalChange
+ * 
+ * Action to change a terminal in a model.
+ *
+ * Constructor: mxTerminalChange
+ * 
+ * Constructs a change of a terminal in the 
+ * specified model.
+ */
+function mxTerminalChange(model, cell, terminal, source)
+{
+	this.model = model;
+	this.cell = cell;
+	this.terminal = terminal;
+	this.previous = terminal;
+	this.source = source;
+};
+
+/**
+ * Function: execute
+ * 
+ * Changes the terminal of <cell> to <previous> using
+ * <mxGraphModel.terminalForCellChanged>.
+ */
+mxTerminalChange.prototype.execute = function()
+{
+	this.terminal = this.previous;
+	this.previous = this.model.terminalForCellChanged(
+		this.cell, this.previous, this.source);
+};
+
+/**
+ * Class: mxValueChange
+ * 
+ * Action to change a user object in a model.
+ *
+ * Constructor: mxValueChange
+ * 
+ * Constructs a change of a user object in the 
+ * specified model.
+ */
+function mxValueChange(model, cell, value)
+{
+	this.model = model;
+	this.cell = cell;
+	this.value = value;
+	this.previous = value;
+};
+
+/**
+ * Function: execute
+ * 
+ * Changes the value of <cell> to <previous> using
+ * <mxGraphModel.valueForCellChanged>.
+ */
+mxValueChange.prototype.execute = function()
+{
+	this.value = this.previous;
+	this.previous = this.model.valueForCellChanged(
+		this.cell, this.previous);
+};
+
+/**
+ * Class: mxStyleChange
+ * 
+ * Action to change a cell's style in a model.
+ *
+ * Constructor: mxStyleChange
+ * 
+ * Constructs a change of a style in the
+ * specified model.
+ */
+function mxStyleChange(model, cell, style)
+{
+	this.model = model;
+	this.cell = cell;
+	this.style = style;
+	this.previous = style;
+};
+
+/**
+ * Function: execute
+ * 
+ * Changes the style of <cell> to <previous> using
+ * <mxGraphModel.styleForCellChanged>.
+ */
+mxStyleChange.prototype.execute = function()
+{
+	this.style = this.previous;
+	this.previous = this.model.styleForCellChanged(
+		this.cell, this.previous);
+};
+
+/**
+ * Class: mxGeometryChange
+ * 
+ * Action to change a cell's geometry in a model.
+ *
+ * Constructor: mxGeometryChange
+ * 
+ * Constructs a change of a geometry in the
+ * specified model.
+ */
+function mxGeometryChange(model, cell, geometry)
+{
+	this.model = model;
+	this.cell = cell;
+	this.geometry = geometry;
+	this.previous = geometry;
+};
+
+/**
+ * Function: execute
+ * 
+ * Changes the geometry of <cell> ro <previous> using
+ * <mxGraphModel.geometryForCellChanged>.
+ */
+mxGeometryChange.prototype.execute = function()
+{
+	this.geometry = this.previous;
+	this.previous = this.model.geometryForCellChanged(
+		this.cell, this.previous);
+};
+
+/**
+ * Class: mxCollapseChange
+ * 
+ * Action to change a cell's collapsed state in a model.
+ *
+ * Constructor: mxCollapseChange
+ * 
+ * Constructs a change of a collapsed state in the
+ * specified model.
+ */
+function mxCollapseChange(model, cell, collapsed)
+{
+	this.model = model;
+	this.cell = cell;
+	this.collapsed = collapsed;
+	this.previous = collapsed;
+};
+
+/**
+ * Function: execute
+ * 
+ * Changes the collapsed state of <cell> to <previous> using
+ * <mxGraphModel.collapsedStateForCellChanged>.
+ */
+mxCollapseChange.prototype.execute = function()
+{
+	this.collapsed = this.previous;
+	this.previous = this.model.collapsedStateForCellChanged(
+		this.cell, this.previous);
+};
+
+/**
+ * Class: mxVisibleChange
+ * 
+ * Action to change a cell's visible state in a model.
+ *
+ * Constructor: mxVisibleChange
+ * 
+ * Constructs a change of a visible state in the
+ * specified model.
+ */
+function mxVisibleChange(model, cell, visible)
+{
+	this.model = model;
+	this.cell = cell;
+	this.visible = visible;
+	this.previous = visible;
+};
+
+/**
+ * Function: execute
+ * 
+ * Changes the visible state of <cell> to <previous> using
+ * <mxGraphModel.visibleStateForCellChanged>.
+ */
+mxVisibleChange.prototype.execute = function()
+{
+	this.visible = this.previous;
+	this.previous = this.model.visibleStateForCellChanged(
+		this.cell, this.previous);
+};
+
+/**
+ * Class: mxCellAttributeChange
+ * 
+ * Action to change the attribute of a cell's user object.
+ * There is no method on the graph model that uses this
+ * action. To use the action, you can use the code shown
+ * in the example below.
+ * 
+ * Example:
+ * 
+ * To change the attributeName in the cell's user object
+ * to attributeValue, use the following code:
+ * 
+ * (code)
+ * model.beginUpdate();
+ * try
+ * {
+ *   var edit = new mxCellAttributeChange(
+ *     cell, attributeName, attributeValue);
+ *   model.execute(edit);
+ * }
+ * finally
+ * {
+ *   model.endUpdate();
+ * } 
+ * (end)
+ *
+ * Constructor: mxCellAttributeChange
+ * 
+ * Constructs a change of a attribute of the DOM node
+ * stored as the value of the given <mxCell>.
+ */
+function mxCellAttributeChange(cell, attribute, value)
+{
+	this.cell = cell;
+	this.attribute = attribute;
+	this.value = value;
+	this.previous = value;
+};
+
+/**
+ * Function: execute
+ * 
+ * Changes the attribute of the cell's user object by
+ * using <mxCell.setAttribute>.
+ */
+mxCellAttributeChange.prototype.execute = function()
+{
+	var tmp = this.cell.getAttribute(this.attribute);
+	
+	if (this.previous == null)
+	{
+		this.cell.value.removeAttribute(this.attribute);
+	}
+	else
+	{
+		this.cell.setAttribute(this.attribute, this.previous);
+	}
+	
+	this.previous = tmp;
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/mxClient.js b/airavata-kubernetes/web-console/src/assets/js/mxClient.js
new file mode 100644
index 0000000..2e9e892
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/mxClient.js
@@ -0,0 +1,769 @@
+/**
+ * Copyright (c) 2006-2017, JGraph Ltd
+ * Copyright (c) 2006-2017, Gaudenz Alder
+ */
+var mxClient =
+{
+	/**
+	 * Class: mxClient
+	 *
+	 * Bootstrapping mechanism for the mxGraph thin client. The production version
+	 * of this file contains all code required to run the mxGraph thin client, as
+	 * well as global constants to identify the browser and operating system in
+	 * use. You may have to load chrome://global/content/contentAreaUtils.js in
+	 * your page to disable certain security restrictions in Mozilla.
+	 * 
+	 * Variable: VERSION
+	 *
+	 * Contains the current version of the mxGraph library. The strings that
+	 * communicate versions of mxGraph use the following format.
+	 * 
+	 * versionMajor.versionMinor.buildNumber.revisionNumber
+	 * 
+	 * Current version is 3.7.5.
+	 */
+	VERSION: '3.7.5',
+
+	/**
+	 * Variable: IS_IE
+	 *
+	 * True if the current browser is Internet Explorer 10 or below. Use <mxClient.IS_IE11>
+	 * to detect IE 11.
+	 */
+	IS_IE: navigator.userAgent.indexOf('MSIE') >= 0,
+
+	/**
+	 * Variable: IS_IE6
+	 *
+	 * True if the current browser is Internet Explorer 6.x.
+	 */
+	IS_IE6: navigator.userAgent.indexOf('MSIE 6') >= 0,
+
+	/**
+	 * Variable: IS_IE11
+	 *
+	 * True if the current browser is Internet Explorer 11.x.
+	 */
+	IS_IE11: !!navigator.userAgent.match(/Trident\/7\./),
+
+	/**
+	 * Variable: IS_EDGE
+	 *
+	 * True if the current browser is Microsoft Edge.
+	 */
+	IS_EDGE: !!navigator.userAgent.match(/Edge\//),
+
+	/**
+	 * Variable: IS_QUIRKS
+	 *
+	 * True if the current browser is Internet Explorer and it is in quirks mode.
+	 */
+	IS_QUIRKS: navigator.userAgent.indexOf('MSIE') >= 0 && (document.documentMode == null || document.documentMode == 5),
+
+	/**
+	 * Variable: IS_EM
+	 * 
+	 * True if the browser is IE11 in enterprise mode (IE8 standards mode).
+	 */
+	IS_EM: 'spellcheck' in document.createElement('textarea') && document.documentMode == 8,
+
+	/**
+	 * Variable: VML_PREFIX
+	 * 
+	 * Prefix for VML namespace in node names. Default is 'v'.
+	 */
+	VML_PREFIX: 'v',
+
+	/**
+	 * Variable: OFFICE_PREFIX
+	 * 
+	 * Prefix for VML office namespace in node names. Default is 'o'.
+	 */
+	OFFICE_PREFIX: 'o',
+
+	/**
+	 * Variable: IS_NS
+	 *
+	 * True if the current browser is Netscape (including Firefox).
+	 */
+  	IS_NS: navigator.userAgent.indexOf('Mozilla/') >= 0 &&
+  		navigator.userAgent.indexOf('MSIE') < 0 &&
+  		navigator.userAgent.indexOf('Edge/') < 0,
+
+	/**
+	 * Variable: IS_OP
+	 *
+	 * True if the current browser is Opera.
+	 */
+  	IS_OP: navigator.userAgent.indexOf('Opera/') >= 0 ||
+  		navigator.userAgent.indexOf('OPR/') >= 0,
+
+	/**
+	 * Variable: IS_OT
+	 *
+	 * True if -o-transform is available as a CSS style, ie for Opera browsers
+	 * based on a Presto engine with version 2.5 or later.
+	 */
+  	IS_OT: navigator.userAgent.indexOf('Presto/') >= 0 &&
+  		navigator.userAgent.indexOf('Presto/2.4.') < 0 &&
+  		navigator.userAgent.indexOf('Presto/2.3.') < 0 &&
+  		navigator.userAgent.indexOf('Presto/2.2.') < 0 &&
+  		navigator.userAgent.indexOf('Presto/2.1.') < 0 &&
+  		navigator.userAgent.indexOf('Presto/2.0.') < 0 &&
+  		navigator.userAgent.indexOf('Presto/1.') < 0,
+  	
+	/**
+	 * Variable: IS_SF
+	 *
+	 * True if the current browser is Safari.
+	 */
+  	IS_SF: navigator.userAgent.indexOf('AppleWebKit/') >= 0 &&
+  		navigator.userAgent.indexOf('Chrome/') < 0 &&
+  		navigator.userAgent.indexOf('Edge/') < 0,
+  	
+	/**
+	 * Variable: IS_IOS
+	 * 
+	 * Returns true if the user agent is an iPad, iPhone or iPod.
+	 */
+  	IS_IOS: (navigator.userAgent.match(/(iPad|iPhone|iPod)/g) ? true : false),
+  		
+	/**
+	 * Variable: IS_GC
+	 *
+	 * True if the current browser is Google Chrome.
+	 */
+  	IS_GC: navigator.userAgent.indexOf('Chrome/') >= 0 &&
+		navigator.userAgent.indexOf('Edge/') < 0,
+	
+	/**
+	 * Variable: IS_CHROMEAPP
+	 *
+	 * True if the this is running inside a Chrome App.
+	 */
+  	IS_CHROMEAPP: window.chrome != null && chrome.app != null && chrome.app.runtime != null,
+		
+	/**
+	 * Variable: IS_FF
+	 *
+	 * True if the current browser is Firefox.
+	 */
+  	IS_FF: navigator.userAgent.indexOf('Firefox/') >= 0,
+  	
+	/**
+	 * Variable: IS_MT
+	 *
+	 * True if -moz-transform is available as a CSS style. This is the case
+	 * for all Firefox-based browsers newer than or equal 3, such as Camino,
+	 * Iceweasel, Seamonkey and Iceape.
+	 */
+  	IS_MT: (navigator.userAgent.indexOf('Firefox/') >= 0 &&
+		navigator.userAgent.indexOf('Firefox/1.') < 0 &&
+  		navigator.userAgent.indexOf('Firefox/2.') < 0) ||
+  		(navigator.userAgent.indexOf('Iceweasel/') >= 0 &&
+  		navigator.userAgent.indexOf('Iceweasel/1.') < 0 &&
+  		navigator.userAgent.indexOf('Iceweasel/2.') < 0) ||
+  		(navigator.userAgent.indexOf('SeaMonkey/') >= 0 &&
+  		navigator.userAgent.indexOf('SeaMonkey/1.') < 0) ||
+  		(navigator.userAgent.indexOf('Iceape/') >= 0 &&
+  		navigator.userAgent.indexOf('Iceape/1.') < 0),
+
+	/**
+	 * Variable: IS_SVG
+	 *
+	 * True if the browser supports SVG.
+	 */
+  	IS_SVG: navigator.userAgent.indexOf('Firefox/') >= 0 || // FF and Camino
+	  	navigator.userAgent.indexOf('Iceweasel/') >= 0 || // Firefox on Debian
+	  	navigator.userAgent.indexOf('Seamonkey/') >= 0 || // Firefox-based
+	  	navigator.userAgent.indexOf('Iceape/') >= 0 || // Seamonkey on Debian
+	  	navigator.userAgent.indexOf('Galeon/') >= 0 || // Gnome Browser (old)
+	  	navigator.userAgent.indexOf('Epiphany/') >= 0 || // Gnome Browser (new)
+	  	navigator.userAgent.indexOf('AppleWebKit/') >= 0 || // Safari/Google Chrome
+	  	navigator.userAgent.indexOf('Gecko/') >= 0 || // Netscape/Gecko
+	  	navigator.userAgent.indexOf('Opera/') >= 0 || // Opera
+	  	(document.documentMode != null && document.documentMode >= 9), // IE9+
+
+	/**
+	 * Variable: NO_FO
+	 *
+	 * True if foreignObject support is not available. This is the case for
+	 * Opera, older SVG-based browsers and all versions of IE.
+	 */
+  	NO_FO: !document.createElementNS || document.createElementNS('http://www.w3.org/2000/svg',
+  		'foreignObject') != '[object SVGForeignObjectElement]' || navigator.userAgent.indexOf('Opera/') >= 0,
+
+	/**
+	 * Variable: IS_VML
+	 *
+	 * True if the browser supports VML.
+	 */
+  	IS_VML: navigator.appName.toUpperCase() == 'MICROSOFT INTERNET EXPLORER',
+
+	/**
+	 * Variable: IS_WIN
+	 *
+	 * True if the client is a Windows.
+	 */
+  	IS_WIN: navigator.appVersion.indexOf('Win') > 0,
+
+	/**
+	 * Variable: IS_MAC
+	 *
+	 * True if the client is a Mac.
+	 */
+  	IS_MAC: navigator.appVersion.indexOf('Mac') > 0,
+
+	/**
+	 * Variable: IS_TOUCH
+	 * 
+	 * True if this device supports touchstart/-move/-end events (Apple iOS,
+	 * Android, Chromebook and Chrome Browser on touch-enabled devices).
+	 */
+  	IS_TOUCH: 'ontouchstart' in document.documentElement,
+
+	/**
+	 * Variable: IS_POINTER
+	 * 
+	 * True if this device supports Microsoft pointer events (always false on Macs).
+	 */
+  	IS_POINTER: window.PointerEvent != null && !(navigator.appVersion.indexOf('Mac') > 0),
+
+	/**
+	 * Variable: IS_LOCAL
+	 *
+	 * True if the documents location does not start with http:// or https://.
+	 */
+  	IS_LOCAL: document.location.href.indexOf('http://') < 0 &&
+  			  document.location.href.indexOf('https://') < 0,
+
+	/**
+	 * Function: isBrowserSupported
+	 *
+	 * Returns true if the current browser is supported, that is, if
+	 * <mxClient.IS_VML> or <mxClient.IS_SVG> is true.
+	 * 
+	 * Example:
+	 * 
+	 * (code)
+	 * if (!mxClient.isBrowserSupported())
+	 * {
+	 *   mxUtils.error('Browser is not supported!', 200, false);
+	 * }
+	 * (end)
+	 */
+	isBrowserSupported: function()
+	{
+		return mxClient.IS_VML || mxClient.IS_SVG;
+	},
+
+	/**
+	 * Function: link
+	 *
+	 * Adds a link node to the head of the document. Use this
+	 * to add a stylesheet to the page as follows:
+	 *
+	 * (code)
+	 * mxClient.link('stylesheet', filename);
+	 * (end)
+	 *
+	 * where filename is the (relative) URL of the stylesheet. The charset
+	 * is hardcoded to ISO-8859-1 and the type is text/css.
+	 * 
+	 * Parameters:
+	 * 
+	 * rel - String that represents the rel attribute of the link node.
+	 * href - String that represents the href attribute of the link node.
+	 * doc - Optional parent document of the link node.
+	 */
+	link: function(rel, href, doc)
+	{
+		doc = doc || document;
+
+		// Workaround for Operation Aborted in IE6 if base tag is used in head
+		if (mxClient.IS_IE6)
+		{
+			doc.write('<link rel="' + rel + '" href="' + href + '" charset="UTF-8" type="text/css"/>');
+		}
+		else
+		{	
+			var link = doc.createElement('link');
+			
+			link.setAttribute('rel', rel);
+			link.setAttribute('href', href);
+			link.setAttribute('charset', 'UTF-8');
+			link.setAttribute('type', 'text/css');
+			
+			var head = doc.getElementsByTagName('head')[0];
+	   		head.appendChild(link);
+		}
+	},
+	
+	/**
+	 * Function: include
+	 *
+	 * Dynamically adds a script node to the document header.
+	 * 
+	 * In production environments, the includes are resolved in the mxClient.js
+	 * file to reduce the number of requests required for client startup. This
+	 * function should only be used in development environments, but not in
+	 * production systems.
+	 */
+	include: function(src)
+	{
+		document.write('<script src="'+src+'"></script>');
+	},
+	
+	/**
+	 * Function: dispose
+	 * 
+	 * Frees up memory in IE by resolving cyclic dependencies between the DOM
+	 * and the JavaScript objects.
+	 */
+	dispose: function()
+	{
+		// Cleans all objects where listeners have been added
+		for (var i = 0; i < mxEvent.objects.length; i++)
+		{
+			if (mxEvent.objects[i].mxListenerList != null)
+			{
+				mxEvent.removeAllListeners(mxEvent.objects[i]);
+			}
+		}
+	}
+
+};
+
+/**
+ * Variable: mxLoadResources
+ * 
+ * Optional global config variable to toggle loading of the two resource files
+ * in <mxGraph> and <mxEditor>. Default is true. NOTE: This is a global variable,
+ * not a variable of mxClient.
+ *
+ * (code)
+ * <script type="text/javascript">
+ * 		var mxLoadResources = false;
+ * </script>
+ * <script type="text/javascript" src="/path/to/core/directory/js/mxClient.js"></script>
+ * (end)
+ */
+if (typeof(mxLoadResources) == 'undefined')
+{
+	mxLoadResources = true;
+}
+
+/**
+ * Variable: mxForceIncludes
+ * 
+ * Optional global config variable to force loading the JavaScript files in
+ * development mode. Default is undefined. NOTE: This is a global variable,
+ * not a variable of mxClient.
+ *
+ * (code)
+ * <script type="text/javascript">
+ * 		var mxLoadResources = true;
+ * </script>
+ * <script type="text/javascript" src="/path/to/core/directory/js/mxClient.js"></script>
+ * (end)
+ */
+if (typeof(mxForceIncludes) == 'undefined')
+{
+	mxForceIncludes = false;
+}
+
+/**
+ * Variable: mxResourceExtension
+ * 
+ * Optional global config variable to specify the extension of resource files.
+ * Default is true. NOTE: This is a global variable, not a variable of mxClient.
+ *
+ * (code)
+ * <script type="text/javascript">
+ * 		var mxResourceExtension = '.txt';
+ * </script>
+ * <script type="text/javascript" src="/path/to/core/directory/js/mxClient.js"></script>
+ * (end)
+ */
+if (typeof(mxResourceExtension) == 'undefined')
+{
+	mxResourceExtension = '.txt';
+}
+
+/**
+ * Variable: mxLoadStylesheets
+ * 
+ * Optional global config variable to toggle loading of the CSS files when
+ * the library is initialized. Default is true. NOTE: This is a global variable,
+ * not a variable of mxClient.
+ *
+ * (code)
+ * <script type="text/javascript">
+ * 		var mxLoadStylesheets = false;
+ * </script>
+ * <script type="text/javascript" src="/path/to/core/directory/js/mxClient.js"></script>
+ * (end)
+ */
+if (typeof(mxLoadStylesheets) == 'undefined')
+{
+	mxLoadStylesheets = true;
+}
+
+/**
+ * Variable: basePath
+ *
+ * Basepath for all URLs in the core without trailing slash. Default is '.'.
+ * Set mxBasePath prior to loading the mxClient library as follows to override
+ * this setting:
+ *
+ * (code)
+ * <script type="text/javascript">
+ * 		mxBasePath = '/path/to/core/directory';
+ * </script>
+ * <script type="text/javascript" src="/path/to/core/directory/js/mxClient.js"></script>
+ * (end)
+ * 
+ * When using a relative path, the path is relative to the URL of the page that
+ * contains the assignment. Trailing slashes are automatically removed.
+ */
+if (typeof(mxBasePath) != 'undefined' && mxBasePath.length > 0)
+{
+	// Adds a trailing slash if required
+	if (mxBasePath.substring(mxBasePath.length - 1) == '/')
+	{
+		mxBasePath = mxBasePath.substring(0, mxBasePath.length - 1);
+	}
+
+	mxClient.basePath = mxBasePath;
+}
+else
+{
+	mxClient.basePath = '.';
+}
+
+/**
+ * Variable: imageBasePath
+ *
+ * Basepath for all images URLs in the core without trailing slash. Default is
+ * <mxClient.basePath> + '/images'. Set mxImageBasePath prior to loading the
+ * mxClient library as follows to override this setting:
+ *
+ * (code)
+ * <script type="text/javascript">
+ * 		mxImageBasePath = '/path/to/image/directory';
+ * </script>
+ * <script type="text/javascript" src="/path/to/core/directory/js/mxClient.js"></script>
+ * (end)
+ * 
+ * When using a relative path, the path is relative to the URL of the page that
+ * contains the assignment. Trailing slashes are automatically removed.
+ */
+if (typeof(mxImageBasePath) != 'undefined' && mxImageBasePath.length > 0)
+{
+	// Adds a trailing slash if required
+	if (mxImageBasePath.substring(mxImageBasePath.length - 1) == '/')
+	{
+		mxImageBasePath = mxImageBasePath.substring(0, mxImageBasePath.length - 1);
+	}
+
+	mxClient.imageBasePath = mxImageBasePath;
+}
+else
+{
+	mxClient.imageBasePath = mxClient.basePath + '/images';	
+}
+
+/**
+ * Variable: language
+ *
+ * Defines the language of the client, eg. en for english, de for german etc.
+ * The special value 'none' will disable all built-in internationalization and
+ * resource loading. See <mxResources.getSpecialBundle> for handling identifiers
+ * with and without a dash.
+ * 
+ * Set mxLanguage prior to loading the mxClient library as follows to override
+ * this setting:
+ *
+ * (code)
+ * <script type="text/javascript">
+ * 		mxLanguage = 'en';
+ * </script>
+ * <script type="text/javascript" src="js/mxClient.js"></script>
+ * (end)
+ * 
+ * If internationalization is disabled, then the following variables should be
+ * overridden to reflect the current language of the system. These variables are
+ * cleared when i18n is disabled.
+ * <mxEditor.askZoomResource>, <mxEditor.lastSavedResource>,
+ * <mxEditor.currentFileResource>, <mxEditor.propertiesResource>,
+ * <mxEditor.tasksResource>, <mxEditor.helpResource>, <mxEditor.outlineResource>,
+ * <mxElbowEdgeHandler.doubleClickOrientationResource>, <mxUtils.errorResource>,
+ * <mxUtils.closeResource>, <mxGraphSelectionModel.doneResource>,
+ * <mxGraphSelectionModel.updatingSelectionResource>, <mxGraphView.doneResource>,
+ * <mxGraphView.updatingDocumentResource>, <mxCellRenderer.collapseExpandResource>,
+ * <mxGraph.containsValidationErrorsResource> and
+ * <mxGraph.alreadyConnectedResource>.
+ */
+if (typeof(mxLanguage) != 'undefined' && mxLanguage != null)
+{
+	mxClient.language = mxLanguage;
+}
+else
+{
+	mxClient.language = (mxClient.IS_IE) ? navigator.userLanguage : navigator.language;
+}
+
+/**
+ * Variable: defaultLanguage
+ * 
+ * Defines the default language which is used in the common resource files. Any
+ * resources for this language will only load the common resource file, but not
+ * the language-specific resource file. Default is 'en'.
+ * 
+ * Set mxDefaultLanguage prior to loading the mxClient library as follows to override
+ * this setting:
+ *
+ * (code)
+ * <script type="text/javascript">
+ * 		mxDefaultLanguage = 'de';
+ * </script>
+ * <script type="text/javascript" src="js/mxClient.js"></script>
+ * (end)
+ */
+if (typeof(mxDefaultLanguage) != 'undefined' && mxDefaultLanguage != null)
+{
+	mxClient.defaultLanguage = mxDefaultLanguage;
+}
+else
+{
+	mxClient.defaultLanguage = 'en';
+}
+
+// Adds all required stylesheets and namespaces
+if (mxLoadStylesheets)
+{
+	mxClient.link('stylesheet', mxClient.basePath + '/css/common.css');
+}
+
+/**
+ * Variable: languages
+ *
+ * Defines the optional array of all supported language extensions. The default
+ * language does not have to be part of this list. See
+ * <mxResources.isLanguageSupported>.
+ *
+ * (code)
+ * <script type="text/javascript">
+ * 		mxLanguages = ['de', 'it', 'fr'];
+ * </script>
+ * <script type="text/javascript" src="js/mxClient.js"></script>
+ * (end)
+ * 
+ * This is used to avoid unnecessary requests to language files, ie. if a 404
+ * will be returned.
+ */
+if (typeof(mxLanguages) != 'undefined' && mxLanguages != null)
+{
+	mxClient.languages = mxLanguages;
+}
+
+// Adds required namespaces, stylesheets and memory handling for older IE browsers
+if (mxClient.IS_VML)
+{
+	if (mxClient.IS_SVG)
+	{
+		mxClient.IS_VML = false;
+	}
+	else
+	{
+		// Enables support for IE8 standards mode. Note that this requires all attributes for VML
+		// elements to be set using direct notation, ie. node.attr = value. The use of setAttribute
+		// is not possible.
+		if (document.documentMode == 8)
+		{
+			document.namespaces.add(mxClient.VML_PREFIX, 'urn:schemas-microsoft-com:vml', '#default#VML');
+			document.namespaces.add(mxClient.OFFICE_PREFIX, 'urn:schemas-microsoft-com:office:office', '#default#VML');
+		}
+		else
+		{
+			document.namespaces.add(mxClient.VML_PREFIX, 'urn:schemas-microsoft-com:vml');
+			document.namespaces.add(mxClient.OFFICE_PREFIX, 'urn:schemas-microsoft-com:office:office');
+		}
+
+		// Workaround for limited number of stylesheets in IE (does not work in standards mode)
+		if (mxClient.IS_QUIRKS && document.styleSheets.length >= 30)
+		{
+			(function()
+			{
+				var node = document.createElement('style');
+				node.type = 'text/css';
+				node.styleSheet.cssText = mxClient.VML_PREFIX + '\\:*{behavior:url(#default#VML)}' +
+		        	mxClient.OFFICE_PREFIX + '\\:*{behavior:url(#default#VML)}';
+		        document.getElementsByTagName('head')[0].appendChild(node);
+			})();
+		}
+		else
+		{
+			document.createStyleSheet().cssText = mxClient.VML_PREFIX + '\\:*{behavior:url(#default#VML)}' +
+		    	mxClient.OFFICE_PREFIX + '\\:*{behavior:url(#default#VML)}';
+		}
+	    
+	    if (mxLoadStylesheets)
+	    {
+	    	mxClient.link('stylesheet', mxClient.basePath + '/css/explorer.css');
+	    }
+	
+		// Cleans up resources when the application terminates
+		window.attachEvent('onunload', mxClient.dispose);
+	}
+}
+
+// PREPROCESSOR-REMOVE-START
+// If script is loaded via CommonJS, do not write <script> tags to the page
+// for dependencies. These are already included in the build.
+if (mxForceIncludes || !(typeof module === 'object' && module.exports != null))
+{
+// PREPROCESSOR-REMOVE-END
+	mxClient.include(mxClient.basePath+'/js/util/mxLog.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxObjectIdentity.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxDictionary.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxResources.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxPoint.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxRectangle.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxEffects.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxUtils.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxConstants.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxEventObject.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxMouseEvent.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxEventSource.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxEvent.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxXmlRequest.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxClipboard.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxWindow.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxForm.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxImage.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxDivResizer.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxDragSource.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxToolbar.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxUndoableEdit.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxUndoManager.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxUrlConverter.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxPanningManager.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxPopupMenu.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxAutoSaveManager.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxAnimation.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxMorphing.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxImageBundle.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxImageExport.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxAbstractCanvas2D.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxXmlCanvas2D.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxSvgCanvas2D.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxVmlCanvas2D.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxGuide.js');
+	mxClient.include(mxClient.basePath+'/js/shape/mxStencil.js');
+	mxClient.include(mxClient.basePath+'/js/shape/mxShape.js');
+	mxClient.include(mxClient.basePath+'/js/shape/mxStencilRegistry.js');
+	mxClient.include(mxClient.basePath+'/js/shape/mxMarker.js');
+	mxClient.include(mxClient.basePath+'/js/shape/mxActor.js');
+	mxClient.include(mxClient.basePath+'/js/shape/mxCloud.js');
+	mxClient.include(mxClient.basePath+'/js/shape/mxRectangleShape.js');
+	mxClient.include(mxClient.basePath+'/js/shape/mxEllipse.js');
+	mxClient.include(mxClient.basePath+'/js/shape/mxDoubleEllipse.js');
+	mxClient.include(mxClient.basePath+'/js/shape/mxRhombus.js');
+	mxClient.include(mxClient.basePath+'/js/shape/mxPolyline.js');
+	mxClient.include(mxClient.basePath+'/js/shape/mxArrow.js');
+	mxClient.include(mxClient.basePath+'/js/shape/mxArrowConnector.js');
+	mxClient.include(mxClient.basePath+'/js/shape/mxText.js');
+	mxClient.include(mxClient.basePath+'/js/shape/mxTriangle.js');
+	mxClient.include(mxClient.basePath+'/js/shape/mxHexagon.js');
+	mxClient.include(mxClient.basePath+'/js/shape/mxLine.js');
+	mxClient.include(mxClient.basePath+'/js/shape/mxImageShape.js');
+	mxClient.include(mxClient.basePath+'/js/shape/mxLabel.js');
+	mxClient.include(mxClient.basePath+'/js/shape/mxCylinder.js');
+	mxClient.include(mxClient.basePath+'/js/shape/mxConnector.js');
+	mxClient.include(mxClient.basePath+'/js/shape/mxSwimlane.js');
+	mxClient.include(mxClient.basePath+'/js/layout/mxGraphLayout.js');
+	mxClient.include(mxClient.basePath+'/js/layout/mxStackLayout.js');
+	mxClient.include(mxClient.basePath+'/js/layout/mxPartitionLayout.js');
+	mxClient.include(mxClient.basePath+'/js/layout/mxCompactTreeLayout.js');
+	mxClient.include(mxClient.basePath+'/js/layout/mxRadialTreeLayout.js');
+	mxClient.include(mxClient.basePath+'/js/layout/mxFastOrganicLayout.js');
+	mxClient.include(mxClient.basePath+'/js/layout/mxCircleLayout.js');
+	mxClient.include(mxClient.basePath+'/js/layout/mxParallelEdgeLayout.js');
+	mxClient.include(mxClient.basePath+'/js/layout/mxCompositeLayout.js');
+	mxClient.include(mxClient.basePath+'/js/layout/mxEdgeLabelLayout.js');
+	mxClient.include(mxClient.basePath+'/js/layout/hierarchical/model/mxGraphAbstractHierarchyCell.js');
+	mxClient.include(mxClient.basePath+'/js/layout/hierarchical/model/mxGraphHierarchyNode.js');
+	mxClient.include(mxClient.basePath+'/js/layout/hierarchical/model/mxGraphHierarchyEdge.js');
+	mxClient.include(mxClient.basePath+'/js/layout/hierarchical/model/mxGraphHierarchyModel.js');
+	mxClient.include(mxClient.basePath+'/js/layout/hierarchical/model/mxSwimlaneModel.js');
+	mxClient.include(mxClient.basePath+'/js/layout/hierarchical/stage/mxHierarchicalLayoutStage.js');
+	mxClient.include(mxClient.basePath+'/js/layout/hierarchical/stage/mxMedianHybridCrossingReduction.js');
+	mxClient.include(mxClient.basePath+'/js/layout/hierarchical/stage/mxMinimumCycleRemover.js');
+	mxClient.include(mxClient.basePath+'/js/layout/hierarchical/stage/mxCoordinateAssignment.js');
+	mxClient.include(mxClient.basePath+'/js/layout/hierarchical/stage/mxSwimlaneOrdering.js');
+	mxClient.include(mxClient.basePath+'/js/layout/hierarchical/mxHierarchicalLayout.js');
+	mxClient.include(mxClient.basePath+'/js/layout/hierarchical/mxSwimlaneLayout.js');
+	mxClient.include(mxClient.basePath+'/js/model/mxGraphModel.js');
+	mxClient.include(mxClient.basePath+'/js/model/mxCell.js');
+	mxClient.include(mxClient.basePath+'/js/model/mxGeometry.js');
+	mxClient.include(mxClient.basePath+'/js/model/mxCellPath.js');
+	mxClient.include(mxClient.basePath+'/js/view/mxPerimeter.js');
+	mxClient.include(mxClient.basePath+'/js/view/mxPrintPreview.js');
+	mxClient.include(mxClient.basePath+'/js/view/mxStylesheet.js');
+	mxClient.include(mxClient.basePath+'/js/view/mxCellState.js');
+	mxClient.include(mxClient.basePath+'/js/view/mxGraphSelectionModel.js');
+	mxClient.include(mxClient.basePath+'/js/view/mxCellEditor.js');
+	mxClient.include(mxClient.basePath+'/js/view/mxCellRenderer.js');
+	mxClient.include(mxClient.basePath+'/js/view/mxEdgeStyle.js');
+	mxClient.include(mxClient.basePath+'/js/view/mxStyleRegistry.js');
+	mxClient.include(mxClient.basePath+'/js/view/mxGraphView.js');
+	mxClient.include(mxClient.basePath+'/js/view/mxGraph.js');
+	mxClient.include(mxClient.basePath+'/js/view/mxCellOverlay.js');
+	mxClient.include(mxClient.basePath+'/js/view/mxOutline.js');
+	mxClient.include(mxClient.basePath+'/js/view/mxMultiplicity.js');
+	mxClient.include(mxClient.basePath+'/js/view/mxLayoutManager.js');
+	mxClient.include(mxClient.basePath+'/js/view/mxSwimlaneManager.js');
+	mxClient.include(mxClient.basePath+'/js/view/mxTemporaryCellStates.js');
+	mxClient.include(mxClient.basePath+'/js/view/mxCellStatePreview.js');
+	mxClient.include(mxClient.basePath+'/js/view/mxConnectionConstraint.js');
+	mxClient.include(mxClient.basePath+'/js/handler/mxGraphHandler.js');
+	mxClient.include(mxClient.basePath+'/js/handler/mxPanningHandler.js');
+	mxClient.include(mxClient.basePath+'/js/handler/mxPopupMenuHandler.js');
+	mxClient.include(mxClient.basePath+'/js/handler/mxCellMarker.js');
+	mxClient.include(mxClient.basePath+'/js/handler/mxSelectionCellsHandler.js');
+	mxClient.include(mxClient.basePath+'/js/handler/mxConnectionHandler.js');
+	mxClient.include(mxClient.basePath+'/js/handler/mxConstraintHandler.js');
+	mxClient.include(mxClient.basePath+'/js/handler/mxRubberband.js');
+	mxClient.include(mxClient.basePath+'/js/handler/mxHandle.js');
+	mxClient.include(mxClient.basePath+'/js/handler/mxVertexHandler.js');
+	mxClient.include(mxClient.basePath+'/js/handler/mxEdgeHandler.js');
+	mxClient.include(mxClient.basePath+'/js/handler/mxElbowEdgeHandler.js');
+	mxClient.include(mxClient.basePath+'/js/handler/mxEdgeSegmentHandler.js');
+	mxClient.include(mxClient.basePath+'/js/handler/mxKeyHandler.js');
+	mxClient.include(mxClient.basePath+'/js/handler/mxTooltipHandler.js');
+	mxClient.include(mxClient.basePath+'/js/handler/mxCellTracker.js');
+	mxClient.include(mxClient.basePath+'/js/handler/mxCellHighlight.js');
+	mxClient.include(mxClient.basePath+'/js/editor/mxDefaultKeyHandler.js');
+	mxClient.include(mxClient.basePath+'/js/editor/mxDefaultPopupMenu.js');
+	mxClient.include(mxClient.basePath+'/js/editor/mxDefaultToolbar.js');
+	mxClient.include(mxClient.basePath+'/js/editor/mxEditor.js');
+	mxClient.include(mxClient.basePath+'/js/io/mxCodecRegistry.js');
+	mxClient.include(mxClient.basePath+'/js/io/mxCodec.js');
+	mxClient.include(mxClient.basePath+'/js/io/mxObjectCodec.js');
+	mxClient.include(mxClient.basePath+'/js/io/mxCellCodec.js');
+	mxClient.include(mxClient.basePath+'/js/io/mxModelCodec.js');
+	mxClient.include(mxClient.basePath+'/js/io/mxRootChangeCodec.js');
+	mxClient.include(mxClient.basePath+'/js/io/mxChildChangeCodec.js');
+	mxClient.include(mxClient.basePath+'/js/io/mxTerminalChangeCodec.js');
+	mxClient.include(mxClient.basePath+'/js/io/mxGenericChangeCodec.js');
+	mxClient.include(mxClient.basePath+'/js/io/mxGraphCodec.js');
+	mxClient.include(mxClient.basePath+'/js/io/mxGraphViewCodec.js');
+	mxClient.include(mxClient.basePath+'/js/io/mxStylesheetCodec.js');
+	mxClient.include(mxClient.basePath+'/js/io/mxDefaultKeyHandlerCodec.js');
+	mxClient.include(mxClient.basePath+'/js/io/mxDefaultToolbarCodec.js');
+	mxClient.include(mxClient.basePath+'/js/io/mxDefaultPopupMenuCodec.js');
+	mxClient.include(mxClient.basePath+'/js/io/mxEditorCodec.js');
+// PREPROCESSOR-REMOVE-START
+}
+// PREPROCESSOR-REMOVE-END
diff --git a/airavata-kubernetes/web-console/src/assets/js/shape/mxActor.js b/airavata-kubernetes/web-console/src/assets/js/shape/mxActor.js
new file mode 100644
index 0000000..19a8080
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/shape/mxActor.js
@@ -0,0 +1,86 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxActor
+ *
+ * Extends <mxShape> to implement an actor shape. If a custom shape with one
+ * filled area is needed, then this shape's <redrawPath> should be overridden.
+ * 
+ * Example:
+ * 
+ * (code)
+ * function SampleShape() { }
+ * 
+ * SampleShape.prototype = new mxActor();
+ * SampleShape.prototype.constructor = vsAseShape;
+ * 
+ * mxCellRenderer.prototype.defaultShapes['sample'] = SampleShape;
+ * SampleShape.prototype.redrawPath = function(path, x, y, w, h)
+ * {
+ *   path.moveTo(0, 0);
+ *   path.lineTo(w, h);
+ *   // ...
+ *   path.close();
+ * }
+ * (end)
+ * 
+ * This shape is registered under <mxConstants.SHAPE_ACTOR> in
+ * <mxCellRenderer>.
+ * 
+ * Constructor: mxActor
+ *
+ * Constructs a new actor shape.
+ * 
+ * Parameters:
+ * 
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxActor(bounds, fill, stroke, strokewidth)
+{
+	mxShape.call(this);
+	this.bounds = bounds;
+	this.fill = fill;
+	this.stroke = stroke;
+	this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxUtils.extend(mxActor, mxShape);
+
+/**
+ * Function: paintVertexShape
+ * 
+ * Redirects to redrawPath for subclasses to work.
+ */
+mxActor.prototype.paintVertexShape = function(c, x, y, w, h)
+{
+	c.translate(x, y);
+	c.begin();
+	this.redrawPath(c, x, y, w, h);
+	c.fillAndStroke();
+};
+
+/**
+ * Function: redrawPath
+ *
+ * Draws the path for this shape.
+ */
+mxActor.prototype.redrawPath = function(c, x, y, w, h)
+{
+	var width = w/3;
+	c.moveTo(0, h);
+	c.curveTo(0, 3 * h / 5, 0, 2 * h / 5, w / 2, 2 * h / 5);
+	c.curveTo(w / 2 - width, 2 * h / 5, w / 2 - width, 0, w / 2, 0);
+	c.curveTo(w / 2 + width, 0, w / 2 + width, 2 * h / 5, w / 2, 2 * h / 5);
+	c.curveTo(w, 2 * h / 5, w, 3 * h / 5, w, h);
+	c.close();
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/shape/mxArrow.js b/airavata-kubernetes/web-console/src/assets/js/shape/mxArrow.js
new file mode 100644
index 0000000..c5c4402
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/shape/mxArrow.js
@@ -0,0 +1,115 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxArrow
+ *
+ * Extends <mxShape> to implement an arrow shape. (The shape
+ * is used to represent edges, not vertices.)
+ * This shape is registered under <mxConstants.SHAPE_ARROW>
+ * in <mxCellRenderer>.
+ * 
+ * Constructor: mxArrow
+ *
+ * Constructs a new arrow shape.
+ * 
+ * Parameters:
+ * 
+ * points - Array of <mxPoints> that define the points. This is stored in
+ * <mxShape.points>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ * arrowWidth - Optional integer that defines the arrow width. Default is
+ * <mxConstants.ARROW_WIDTH>. This is stored in <arrowWidth>.
+ * spacing - Optional integer that defines the spacing between the arrow shape
+ * and its endpoints. Default is <mxConstants.ARROW_SPACING>. This is stored in
+ * <spacing>.
+ * endSize - Optional integer that defines the size of the arrowhead. Default
+ * is <mxConstants.ARROW_SIZE>. This is stored in <endSize>.
+ */
+function mxArrow(points, fill, stroke, strokewidth, arrowWidth, spacing, endSize)
+{
+	mxShape.call(this);
+	this.points = points;
+	this.fill = fill;
+	this.stroke = stroke;
+	this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+	this.arrowWidth = (arrowWidth != null) ? arrowWidth : mxConstants.ARROW_WIDTH;
+	this.spacing = (spacing != null) ? spacing : mxConstants.ARROW_SPACING;
+	this.endSize = (endSize != null) ? endSize : mxConstants.ARROW_SIZE;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxUtils.extend(mxArrow, mxShape);
+
+/**
+ * Function: augmentBoundingBox
+ *
+ * Augments the bounding box with the edge width and markers.
+ */
+mxArrow.prototype.augmentBoundingBox = function(bbox)
+{
+	mxShape.prototype.augmentBoundingBox.apply(this, arguments);
+	
+	var w = Math.max(this.arrowWidth, this.endSize);
+	bbox.grow((w / 2 + this.strokewidth) * this.scale);
+};
+
+/**
+ * Function: paintEdgeShape
+ * 
+ * Paints the line shape.
+ */
+mxArrow.prototype.paintEdgeShape = function(c, pts)
+{
+	// Geometry of arrow
+	var spacing =  mxConstants.ARROW_SPACING;
+	var width = mxConstants.ARROW_WIDTH;
+	var arrow = mxConstants.ARROW_SIZE;
+
+	// Base vector (between end points)
+	var p0 = pts[0];
+	var pe = pts[pts.length - 1];
+	var dx = pe.x - p0.x;
+	var dy = pe.y - p0.y;
+	var dist = Math.sqrt(dx * dx + dy * dy);
+	var length = dist - 2 * spacing - arrow;
+	
+	// Computes the norm and the inverse norm
+	var nx = dx / dist;
+	var ny = dy / dist;
+	var basex = length * nx;
+	var basey = length * ny;
+	var floorx = width * ny/3;
+	var floory = -width * nx/3;
+	
+	// Computes points
+	var p0x = p0.x - floorx / 2 + spacing * nx;
+	var p0y = p0.y - floory / 2 + spacing * ny;
+	var p1x = p0x + floorx;
+	var p1y = p0y + floory;
+	var p2x = p1x + basex;
+	var p2y = p1y + basey;
+	var p3x = p2x + floorx;
+	var p3y = p2y + floory;
+	// p4 not necessary
+	var p5x = p3x - 3 * floorx;
+	var p5y = p3y - 3 * floory;
+	
+	c.begin();
+	c.moveTo(p0x, p0y);
+	c.lineTo(p1x, p1y);
+	c.lineTo(p2x, p2y);
+	c.lineTo(p3x, p3y);
+	c.lineTo(pe.x - spacing * nx, pe.y - spacing * ny);
+	c.lineTo(p5x, p5y);
+	c.lineTo(p5x + floorx, p5y + floory);
+	c.close();
+
+	c.fillAndStroke();
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/shape/mxArrowConnector.js b/airavata-kubernetes/web-console/src/assets/js/shape/mxArrowConnector.js
new file mode 100644
index 0000000..2abae89
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/shape/mxArrowConnector.js
@@ -0,0 +1,485 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxArrowConnector
+ *
+ * Extends <mxShape> to implement an new rounded arrow shape with support for
+ * waypoints and double arrows. (The shape is used to represent edges, not
+ * vertices.) This shape is registered under <mxConstants.SHAPE_ARROW_CONNECTOR>
+ * in <mxCellRenderer>.
+ * 
+ * Constructor: mxArrowConnector
+ *
+ * Constructs a new arrow shape.
+ * 
+ * Parameters:
+ * 
+ * points - Array of <mxPoints> that define the points. This is stored in
+ * <mxShape.points>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ * arrowWidth - Optional integer that defines the arrow width. Default is
+ * <mxConstants.ARROW_WIDTH>. This is stored in <arrowWidth>.
+ * spacing - Optional integer that defines the spacing between the arrow shape
+ * and its endpoints. Default is <mxConstants.ARROW_SPACING>. This is stored in
+ * <spacing>.
+ * endSize - Optional integer that defines the size of the arrowhead. Default
+ * is <mxConstants.ARROW_SIZE>. This is stored in <endSize>.
+ */
+function mxArrowConnector(points, fill, stroke, strokewidth, arrowWidth, spacing, endSize)
+{
+	mxShape.call(this);
+	this.points = points;
+	this.fill = fill;
+	this.stroke = stroke;
+	this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+	this.arrowWidth = (arrowWidth != null) ? arrowWidth : mxConstants.ARROW_WIDTH;
+	this.arrowSpacing = (spacing != null) ? spacing : mxConstants.ARROW_SPACING;
+	this.startSize = mxConstants.ARROW_SIZE / 5;
+	this.endSize = mxConstants.ARROW_SIZE / 5;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxUtils.extend(mxArrowConnector, mxShape);
+
+/**
+ * Variable: useSvgBoundingBox
+ * 
+ * Allows to use the SVG bounding box in SVG. Default is false for performance
+ * reasons.
+ */
+mxArrowConnector.prototype.useSvgBoundingBox = true;
+
+/**
+ * Variable: resetStyles
+ * 
+ * Overrides mxShape to reset spacing.
+ */
+mxArrowConnector.prototype.resetStyles = function()
+{
+	mxShape.prototype.resetStyles.apply(this, arguments);
+	
+	this.arrowSpacing = mxConstants.ARROW_SPACING;
+};
+
+/**
+ * Overrides apply to get smooth transition from default start- and endsize.
+ */
+mxArrowConnector.prototype.apply = function(state)
+{
+	mxShape.prototype.apply.apply(this, arguments);
+
+	if (this.style != null)
+	{
+		this.startSize = mxUtils.getNumber(this.style, mxConstants.STYLE_STARTSIZE, mxConstants.ARROW_SIZE / 5) * 3;
+		this.endSize = mxUtils.getNumber(this.style, mxConstants.STYLE_ENDSIZE, mxConstants.ARROW_SIZE / 5) * 3;
+	}
+};
+
+/**
+ * Function: augmentBoundingBox
+ *
+ * Augments the bounding box with the edge width and markers.
+ */
+mxArrowConnector.prototype.augmentBoundingBox = function(bbox)
+{
+	mxShape.prototype.augmentBoundingBox.apply(this, arguments);
+	
+	var w = this.getEdgeWidth();
+	
+	if (this.isMarkerStart())
+	{
+		w = Math.max(w, this.getStartArrowWidth());
+	}
+	
+	if (this.isMarkerEnd())
+	{
+		w = Math.max(w, this.getEndArrowWidth());
+	}
+	
+	bbox.grow((w / 2 + this.strokewidth) * this.scale);
+};
+
+/**
+ * Function: paintEdgeShape
+ * 
+ * Paints the line shape.
+ */
+mxArrowConnector.prototype.paintEdgeShape = function(c, pts)
+{
+	// Geometry of arrow
+	var strokeWidth = this.strokewidth;
+	
+	if (this.outline)
+	{
+		strokeWidth = Math.max(1, mxUtils.getNumber(this.style, mxConstants.STYLE_STROKEWIDTH, this.strokewidth));
+	}
+	
+	var startWidth = this.getStartArrowWidth() + strokeWidth;
+	var endWidth = this.getEndArrowWidth() + strokeWidth;
+	var edgeWidth = this.outline ? this.getEdgeWidth() + strokeWidth : this.getEdgeWidth();
+	var openEnded = this.isOpenEnded();
+	var markerStart = this.isMarkerStart();
+	var markerEnd = this.isMarkerEnd();
+	var spacing = (openEnded) ? 0 : this.arrowSpacing + strokeWidth / 2;
+	var startSize = this.startSize + strokeWidth;
+	var endSize = this.endSize + strokeWidth;
+	var isRounded = this.isArrowRounded();
+	
+	// Base vector (between first points)
+	var pe = pts[pts.length - 1];
+
+	// Finds first non-overlapping point
+	var i0 = 1;
+	
+	while (i0 < pts.length - 1 && pts[i0].x == pts[0].x && pts[i0].y == pts[0].y)
+	{
+		i0++;
+	}
+	
+	var dx = pts[i0].x - pts[0].x;
+	var dy = pts[i0].y - pts[0].y;
+	var dist = Math.sqrt(dx * dx + dy * dy);
+	
+	if (dist == 0)
+	{
+		return;
+	}
+	
+	// Computes the norm and the inverse norm
+	var nx = dx / dist;
+	var nx2, nx1 = nx;
+	var ny = dy / dist;
+	var ny2, ny1 = ny;
+	var orthx = edgeWidth * ny;
+	var orthy = -edgeWidth * nx;
+	
+	// Stores the inbound function calls in reverse order in fns
+	var fns = [];
+	
+	if (isRounded)
+	{
+		c.setLineJoin('round');
+	}
+	else if (pts.length > 2)
+	{
+		// Only mitre if there are waypoints
+		c.setMiterLimit(1.42);
+	}
+
+	c.begin();
+
+	var startNx = nx;
+	var startNy = ny;
+
+	if (markerStart && !openEnded)
+	{
+		this.paintMarker(c, pts[0].x, pts[0].y, nx, ny, startSize, startWidth, edgeWidth, spacing, true);
+	}
+	else
+	{
+		var outStartX = pts[0].x + orthx / 2 + spacing * nx;
+		var outStartY = pts[0].y + orthy / 2 + spacing * ny;
+		var inEndX = pts[0].x - orthx / 2 + spacing * nx;
+		var inEndY = pts[0].y - orthy / 2 + spacing * ny;
+		
+		if (openEnded)
+		{
+			c.moveTo(outStartX, outStartY);
+			
+			fns.push(function()
+			{
+				c.lineTo(inEndX, inEndY);
+			});
+		}
+		else
+		{
+			c.moveTo(inEndX, inEndY);
+			c.lineTo(outStartX, outStartY);
+		}
+	}
+	
+	var dx1 = 0;
+	var dy1 = 0;
+	var dist1 = 0;
+
+	for (var i = 0; i < pts.length - 2; i++)
+	{
+		// Work out in which direction the line is bending
+		var pos = mxUtils.relativeCcw(pts[i].x, pts[i].y, pts[i+1].x, pts[i+1].y, pts[i+2].x, pts[i+2].y);
+
+		dx1 = pts[i+2].x - pts[i+1].x;
+		dy1 = pts[i+2].y - pts[i+1].y;
+
+		dist1 = Math.sqrt(dx1 * dx1 + dy1 * dy1);
+		
+		if (dist1 != 0)
+		{
+			nx1 = dx1 / dist1;
+			ny1 = dy1 / dist1;
+			
+			var tmp1 = nx * nx1 + ny * ny1;
+			tmp = Math.max(Math.sqrt((tmp1 + 1) / 2), 0.04);
+			
+			// Work out the normal orthogonal to the line through the control point and the edge sides intersection
+			nx2 = (nx + nx1);
+			ny2 = (ny + ny1);
+	
+			var dist2 = Math.sqrt(nx2 * nx2 + ny2 * ny2);
+			
+			if (dist2 != 0)
+			{
+				nx2 = nx2 / dist2;
+				ny2 = ny2 / dist2;
+				
+				// Higher strokewidths require a larger minimum bend, 0.35 covers all but the most extreme cases
+				var strokeWidthFactor = Math.max(tmp, Math.min(this.strokewidth / 200 + 0.04, 0.35));
+				var angleFactor = (pos != 0 && isRounded) ? Math.max(0.1, strokeWidthFactor) : Math.max(tmp, 0.06);
+
+				var outX = pts[i+1].x + ny2 * edgeWidth / 2 / angleFactor;
+				var outY = pts[i+1].y - nx2 * edgeWidth / 2 / angleFactor;
+				var inX = pts[i+1].x - ny2 * edgeWidth / 2 / angleFactor;
+				var inY = pts[i+1].y + nx2 * edgeWidth / 2 / angleFactor;
+				
+				if (pos == 0 || !isRounded)
+				{
+					// If the two segments are aligned, or if we're not drawing curved sections between segments
+					// just draw straight to the intersection point
+					c.lineTo(outX, outY);
+					
+					(function(x, y)
+					{
+						fns.push(function()
+						{
+							c.lineTo(x, y);
+						});
+					})(inX, inY);
+				}
+				else if (pos == -1)
+				{
+					var c1x = inX + ny * edgeWidth;
+					var c1y = inY - nx * edgeWidth;
+					var c2x = inX + ny1 * edgeWidth;
+					var c2y = inY - nx1 * edgeWidth;
+					c.lineTo(c1x, c1y);
+					c.quadTo(outX, outY, c2x, c2y);
+					
+					(function(x, y)
+					{
+						fns.push(function()
+						{
+							c.lineTo(x, y);
+						});
+					})(inX, inY);
+				}
+				else
+				{
+					c.lineTo(outX, outY);
+					
+					(function(x, y)
+					{
+						var c1x = outX - ny * edgeWidth;
+						var c1y = outY + nx * edgeWidth;
+						var c2x = outX - ny1 * edgeWidth;
+						var c2y = outY + nx1 * edgeWidth;
+						
+						fns.push(function()
+						{
+							c.quadTo(x, y, c1x, c1y);
+						});
+						fns.push(function()
+						{
+							c.lineTo(c2x, c2y);
+						});
+					})(inX, inY);
+				}
+				
+				nx = nx1;
+				ny = ny1;
+			}
+		}
+	}
+	
+	orthx = edgeWidth * ny1;
+	orthy = - edgeWidth * nx1;
+
+	if (markerEnd && !openEnded)
+	{
+		this.paintMarker(c, pe.x, pe.y, -nx, -ny, endSize, endWidth, edgeWidth, spacing, false);
+	}
+	else
+	{
+		c.lineTo(pe.x - spacing * nx1 + orthx / 2, pe.y - spacing * ny1 + orthy / 2);
+		
+		var inStartX = pe.x - spacing * nx1 - orthx / 2;
+		var inStartY = pe.y - spacing * ny1 - orthy / 2;
+
+		if (!openEnded)
+		{
+			c.lineTo(inStartX, inStartY);
+		}
+		else
+		{
+			c.moveTo(inStartX, inStartY);
+			
+			fns.splice(0, 0, function()
+			{
+				c.moveTo(inStartX, inStartY);
+			});
+		}
+	}
+	
+	for (var i = fns.length - 1; i >= 0; i--)
+	{
+		fns[i]();
+	}
+
+	if (openEnded)
+	{
+		c.end();
+		c.stroke();
+	}
+	else
+	{
+		c.close();
+		c.fillAndStroke();
+	}
+	
+	// Workaround for shadow on top of base arrow
+	c.setShadow(false);
+	
+	// Need to redraw the markers without the low miter limit
+	c.setMiterLimit(4);
+	
+	if (isRounded)
+	{
+		c.setLineJoin('flat');
+	}
+
+	if (pts.length > 2)
+	{
+		// Only to repaint markers if no waypoints
+		// Need to redraw the markers without the low miter limit
+		c.setMiterLimit(4);
+		if (markerStart && !openEnded)
+		{
+			c.begin();
+			this.paintMarker(c, pts[0].x, pts[0].y, startNx, startNy, startSize, startWidth, edgeWidth, spacing, true);
+			c.stroke();
+			c.end();
+		}
+		
+		if (markerEnd && !openEnded)
+		{
+			c.begin();
+			this.paintMarker(c, pe.x, pe.y, -nx, -ny, endSize, endWidth, edgeWidth, spacing, true);
+			c.stroke();
+			c.end();
+		}
+	}
+};
+
+/**
+ * Function: paintEdgeShape
+ * 
+ * Paints the line shape.
+ */
+mxArrowConnector.prototype.paintMarker = function(c, ptX, ptY, nx, ny, size, arrowWidth, edgeWidth, spacing, initialMove)
+{
+	var widthArrowRatio = edgeWidth / arrowWidth;
+	var orthx = edgeWidth * ny / 2;
+	var orthy = -edgeWidth * nx / 2;
+
+	var spaceX = (spacing + size) * nx;
+	var spaceY = (spacing + size) * ny;
+
+	if (initialMove)
+	{
+		c.moveTo(ptX - orthx + spaceX, ptY - orthy + spaceY);
+	}
+	else
+	{
+		c.lineTo(ptX - orthx + spaceX, ptY - orthy + spaceY);
+	}
+
+	c.lineTo(ptX - orthx / widthArrowRatio + spaceX, ptY - orthy / widthArrowRatio + spaceY);
+	c.lineTo(ptX + spacing * nx, ptY + spacing * ny);
+	c.lineTo(ptX + orthx / widthArrowRatio + spaceX, ptY + orthy / widthArrowRatio + spaceY);
+	c.lineTo(ptX + orthx + spaceX, ptY + orthy + spaceY);
+}
+
+/**
+ * Function: isArrowRounded
+ * 
+ * Returns wether the arrow is rounded
+ */
+mxArrowConnector.prototype.isArrowRounded = function()
+{
+	return this.isRounded;
+};
+
+/**
+ * Function: getStartArrowWidth
+ * 
+ * Returns the width of the start arrow
+ */
+mxArrowConnector.prototype.getStartArrowWidth = function()
+{
+	return mxConstants.ARROW_WIDTH;
+};
+
+/**
+ * Function: getEndArrowWidth
+ * 
+ * Returns the width of the end arrow
+ */
+mxArrowConnector.prototype.getEndArrowWidth = function()
+{
+	return mxConstants.ARROW_WIDTH;
+};
+
+/**
+ * Function: getEdgeWidth
+ * 
+ * Returns the width of the body of the edge
+ */
+mxArrowConnector.prototype.getEdgeWidth = function()
+{
+	return mxConstants.ARROW_WIDTH / 3;
+};
+
+/**
+ * Function: isOpenEnded
+ * 
+ * Returns whether the ends of the shape are drawn
+ */
+mxArrowConnector.prototype.isOpenEnded = function()
+{
+	return false;
+};
+
+/**
+ * Function: isMarkerStart
+ * 
+ * Returns whether the start marker is drawn
+ */
+mxArrowConnector.prototype.isMarkerStart = function()
+{
+	return (mxUtils.getValue(this.style, mxConstants.STYLE_STARTARROW, mxConstants.NONE) != mxConstants.NONE);
+};
+
+/**
+ * Function: isMarkerEnd
+ * 
+ * Returns whether the end marker is drawn
+ */
+mxArrowConnector.prototype.isMarkerEnd = function()
+{
+	return (mxUtils.getValue(this.style, mxConstants.STYLE_ENDARROW, mxConstants.NONE) != mxConstants.NONE);
+};
\ No newline at end of file
diff --git a/airavata-kubernetes/web-console/src/assets/js/shape/mxCloud.js b/airavata-kubernetes/web-console/src/assets/js/shape/mxCloud.js
new file mode 100644
index 0000000..fb1f931
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/shape/mxCloud.js
@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxCloud
+ *
+ * Extends <mxActor> to implement a cloud shape.
+ * 
+ * This shape is registered under <mxConstants.SHAPE_CLOUD> in
+ * <mxCellRenderer>.
+ * 
+ * Constructor: mxCloud
+ *
+ * Constructs a new cloud shape.
+ * 
+ * Parameters:
+ * 
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxCloud(bounds, fill, stroke, strokewidth)
+{
+	mxActor.call(this);
+	this.bounds = bounds;
+	this.fill = fill;
+	this.stroke = stroke;
+	this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxActor.
+ */
+mxUtils.extend(mxCloud, mxActor);
+
+/**
+ * Function: redrawPath
+ *
+ * Draws the path for this shape.
+ */
+mxCloud.prototype.redrawPath = function(c, x, y, w, h)
+{
+	c.moveTo(0.25 * w, 0.25 * h);
+	c.curveTo(0.05 * w, 0.25 * h, 0, 0.5 * h, 0.16 * w, 0.55 * h);
+	c.curveTo(0, 0.66 * h, 0.18 * w, 0.9 * h, 0.31 * w, 0.8 * h);
+	c.curveTo(0.4 * w, h, 0.7 * w, h, 0.8 * w, 0.8 * h);
+	c.curveTo(w, 0.8 * h, w, 0.6 * h, 0.875 * w, 0.5 * h);
+	c.curveTo(w, 0.3 * h, 0.8 * w, 0.1 * h, 0.625 * w, 0.2 * h);
+	c.curveTo(0.5 * w, 0.05 * h, 0.3 * w, 0.05 * h, 0.25 * w, 0.25 * h);
+	c.close();
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/shape/mxConnector.js b/airavata-kubernetes/web-console/src/assets/js/shape/mxConnector.js
new file mode 100644
index 0000000..42968a0
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/shape/mxConnector.js
@@ -0,0 +1,149 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxConnector
+ * 
+ * Extends <mxShape> to implement a connector shape. The connector
+ * shape allows for arrow heads on either side.
+ * 
+ * This shape is registered under <mxConstants.SHAPE_CONNECTOR> in
+ * <mxCellRenderer>.
+ * 
+ * Constructor: mxConnector
+ * 
+ * Constructs a new connector shape.
+ * 
+ * Parameters:
+ * 
+ * points - Array of <mxPoints> that define the points. This is stored in
+ * <mxShape.points>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * Default is 'black'.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxConnector(points, stroke, strokewidth)
+{
+	mxPolyline.call(this, points, stroke, strokewidth);
+};
+
+/**
+ * Extends mxPolyline.
+ */
+mxUtils.extend(mxConnector, mxPolyline);
+
+/**
+ * Function: updateBoundingBox
+ *
+ * Updates the <boundingBox> for this shape using <createBoundingBox> and
+ * <augmentBoundingBox> and stores the result in <boundingBox>.
+ */
+mxConnector.prototype.updateBoundingBox = function()
+{
+	this.useSvgBoundingBox = this.style != null && this.style[mxConstants.STYLE_CURVED] == 1;
+	mxShape.prototype.updateBoundingBox.apply(this, arguments);
+};
+
+/**
+ * Function: paintEdgeShape
+ * 
+ * Paints the line shape.
+ */
+mxConnector.prototype.paintEdgeShape = function(c, pts)
+{
+	// The indirection via functions for markers is needed in
+	// order to apply the offsets before painting the line and
+	// paint the markers after painting the line.
+	var sourceMarker = this.createMarker(c, pts, true);
+	var targetMarker = this.createMarker(c, pts, false);
+
+	mxPolyline.prototype.paintEdgeShape.apply(this, arguments);
+	
+	// Disables shadows, dashed styles and fixes fill color for markers
+	c.setFillColor(this.stroke);
+	c.setShadow(false);
+	c.setDashed(false);
+	
+	if (sourceMarker != null)
+	{
+		sourceMarker();
+	}
+	
+	if (targetMarker != null)
+	{
+		targetMarker();
+	}
+};
+
+/**
+ * Function: createMarker
+ * 
+ * Prepares the marker by adding offsets in pts and returning a function to
+ * paint the marker.
+ */
+mxConnector.prototype.createMarker = function(c, pts, source)
+{
+	var result = null;
+	var n = pts.length;
+	var type = mxUtils.getValue(this.style, (source) ? mxConstants.STYLE_STARTARROW : mxConstants.STYLE_ENDARROW);
+	var p0 = (source) ? pts[1] : pts[n - 2];
+	var pe = (source) ? pts[0] : pts[n - 1];
+	
+	if (type != null && p0 != null && pe != null)
+	{
+		var count = 1;
+		
+		// Uses next non-overlapping point
+		while (count < n - 1 && Math.round(p0.x - pe.x) == 0 && Math.round(p0.y - pe.y) == 0)
+		{
+			p0 = (source) ? pts[1 + count] : pts[n - 2 - count];
+			count++;
+		}
+	
+		// Computes the norm and the inverse norm
+		var dx = pe.x - p0.x;
+		var dy = pe.y - p0.y;
+	
+		var dist = Math.max(1, Math.sqrt(dx * dx + dy * dy));
+		
+		var unitX = dx / dist;
+		var unitY = dy / dist;
+	
+		var size = mxUtils.getNumber(this.style, (source) ? mxConstants.STYLE_STARTSIZE : mxConstants.STYLE_ENDSIZE, mxConstants.DEFAULT_MARKERSIZE);
+		
+		// Allow for stroke width in the end point used and the 
+		// orthogonal vectors describing the direction of the marker
+		var filled = this.style[(source) ? mxConstants.STYLE_STARTFILL : mxConstants.STYLE_ENDFILL] != 0;
+		
+		result = mxMarker.createMarker(c, this, type, pe, unitX, unitY, size, source, this.strokewidth, filled);
+	}
+	
+	return result;
+};
+
+/**
+ * Function: augmentBoundingBox
+ *
+ * Augments the bounding box with the strokewidth and shadow offsets.
+ */
+mxConnector.prototype.augmentBoundingBox = function(bbox)
+{
+	mxShape.prototype.augmentBoundingBox.apply(this, arguments);
+	
+	// Adds marker sizes
+	var size = 0;
+	
+	if (mxUtils.getValue(this.style, mxConstants.STYLE_STARTARROW, mxConstants.NONE) != mxConstants.NONE)
+	{
+		size = mxUtils.getNumber(this.style, mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_MARKERSIZE) + 1;
+	}
+	
+	if (mxUtils.getValue(this.style, mxConstants.STYLE_ENDARROW, mxConstants.NONE) != mxConstants.NONE)
+	{
+		size = Math.max(size, mxUtils.getNumber(this.style, mxConstants.STYLE_ENDSIZE, mxConstants.DEFAULT_MARKERSIZE)) + 1;
+	}
+	
+	bbox.grow(size * this.scale);
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/shape/mxCylinder.js b/airavata-kubernetes/web-console/src/assets/js/shape/mxCylinder.js
new file mode 100644
index 0000000..3eef14b
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/shape/mxCylinder.js
@@ -0,0 +1,105 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxCylinder
+ *
+ * Extends <mxShape> to implement an cylinder shape. If a
+ * custom shape with one filled area and an overlay path is
+ * needed, then this shape's <redrawPath> should be overridden.
+ * This shape is registered under <mxConstants.SHAPE_CYLINDER>
+ * in <mxCellRenderer>.
+ * 
+ * Constructor: mxCylinder
+ *
+ * Constructs a new cylinder shape.
+ * 
+ * Parameters:
+ * 
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxCylinder(bounds, fill, stroke, strokewidth)
+{
+	mxShape.call(this);
+	this.bounds = bounds;
+	this.fill = fill;
+	this.stroke = stroke;
+	this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxUtils.extend(mxCylinder, mxShape);
+
+/**
+ * Variable: maxHeight
+ *
+ * Defines the maximum height of the top and bottom part
+ * of the cylinder shape.
+ */
+mxCylinder.prototype.maxHeight = 40;
+
+/**
+ * Variable: svgStrokeTolerance
+ *
+ * Sets stroke tolerance to 0 for SVG.
+ */
+mxCylinder.prototype.svgStrokeTolerance = 0;
+
+/**
+ * Function: paintVertexShape
+ * 
+ * Redirects to redrawPath for subclasses to work.
+ */
+mxCylinder.prototype.paintVertexShape = function(c, x, y, w, h)
+{
+	c.translate(x, y);
+	c.begin();
+	this.redrawPath(c, x, y, w, h, false);
+	c.fillAndStroke();
+	
+	c.setShadow(false);
+	
+	c.begin();
+	this.redrawPath(c, x, y, w, h, true);
+	c.stroke();
+};
+
+/**
+ * Function: redrawPath
+ *
+ * Draws the path for this shape.
+ */
+mxCylinder.prototype.redrawPath = function(c, x, y, w, h, isForeground)
+{
+	var dy = Math.min(this.maxHeight, Math.round(h / 5));
+	
+	if ((isForeground && this.fill != null) || (!isForeground && this.fill == null))
+	{
+		c.moveTo(0, dy);
+		c.curveTo(0, 2 * dy, w, 2 * dy, w, dy);
+		
+		// Needs separate shapes for correct hit-detection
+		if (!isForeground)
+		{
+			c.stroke();
+			c.begin();
+		}
+	}
+	
+	if (!isForeground)
+	{
+		c.moveTo(0, dy);
+		c.curveTo(0, -dy / 3, w, -dy / 3, w, dy);
+		c.lineTo(w, h - dy);
+		c.curveTo(w, h + dy / 3, 0, h + dy / 3, 0, h - dy);
+		c.close();
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/shape/mxDoubleEllipse.js b/airavata-kubernetes/web-console/src/assets/js/shape/mxDoubleEllipse.js
new file mode 100644
index 0000000..7ac95bb
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/shape/mxDoubleEllipse.js
@@ -0,0 +1,114 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxDoubleEllipse
+ *
+ * Extends <mxShape> to implement a double ellipse shape. This shape is
+ * registered under <mxConstants.SHAPE_DOUBLE_ELLIPSE> in <mxCellRenderer>.
+ * Use the following override to only fill the inner ellipse in this shape:
+ * 
+ * (code)
+ * mxDoubleEllipse.prototype.paintVertexShape = function(c, x, y, w, h)
+ * {
+ *   c.ellipse(x, y, w, h);
+ *   c.stroke();
+ *   
+ *   var inset = mxUtils.getValue(this.style, mxConstants.STYLE_MARGIN, Math.min(3 + this.strokewidth, Math.min(w / 5, h / 5)));
+ *   x += inset;
+ *   y += inset;
+ *   w -= 2 * inset;
+ *   h -= 2 * inset;
+ *   
+ *   if (w > 0 && h > 0)
+ *   {
+ *     c.ellipse(x, y, w, h);
+ *   }
+ *   
+ *   c.fillAndStroke();
+ * };
+ * (end)
+ * 
+ * Constructor: mxDoubleEllipse
+ *
+ * Constructs a new ellipse shape.
+ *
+ * Parameters:
+ *
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxDoubleEllipse(bounds, fill, stroke, strokewidth)
+{
+	mxShape.call(this);
+	this.bounds = bounds;
+	this.fill = fill;
+	this.stroke = stroke;
+	this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxUtils.extend(mxDoubleEllipse, mxShape);
+
+/**
+ * Variable: vmlScale
+ * 
+ * Scale for improving the precision of VML rendering. Default is 10.
+ */
+mxDoubleEllipse.prototype.vmlScale = 10;
+
+/**
+ * Function: paintBackground
+ * 
+ * Paints the background.
+ */
+mxDoubleEllipse.prototype.paintBackground = function(c, x, y, w, h)
+{
+	c.ellipse(x, y, w, h);
+	c.fillAndStroke();
+};
+
+/**
+ * Function: paintForeground
+ * 
+ * Paints the foreground.
+ */
+mxDoubleEllipse.prototype.paintForeground = function(c, x, y, w, h)
+{
+	if (!this.outline)
+	{
+		var margin = mxUtils.getValue(this.style, mxConstants.STYLE_MARGIN, Math.min(3 + this.strokewidth, Math.min(w / 5, h / 5)));
+		x += margin;
+		y += margin;
+		w -= 2 * margin;
+		h -= 2 * margin;
+		
+		// FIXME: Rounding issues in IE8 standards mode (not in 1.x)
+		if (w > 0 && h > 0)
+		{
+			c.ellipse(x, y, w, h);
+		}
+		
+		c.stroke();
+	}
+};
+
+/**
+ * Function: getLabelBounds
+ * 
+ * Returns the bounds for the label.
+ */
+mxDoubleEllipse.prototype.getLabelBounds = function(rect)
+{
+	var margin = (mxUtils.getValue(this.style, mxConstants.STYLE_MARGIN, Math.min(3 + this.strokewidth,
+			Math.min(rect.width / 5 / this.scale, rect.height / 5 / this.scale)))) * this.scale;
+
+	return new mxRectangle(rect.x + margin, rect.y + margin, rect.width - 2 * margin, rect.height - 2 * margin);
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/shape/mxEllipse.js b/airavata-kubernetes/web-console/src/assets/js/shape/mxEllipse.js
new file mode 100644
index 0000000..aef8df7
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/shape/mxEllipse.js
@@ -0,0 +1,48 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxEllipse
+ *
+ * Extends <mxShape> to implement an ellipse shape.
+ * This shape is registered under <mxConstants.SHAPE_ELLIPSE>
+ * in <mxCellRenderer>.
+ * 
+ * Constructor: mxEllipse
+ *
+ * Constructs a new ellipse shape.
+ * 
+ * Parameters:
+ * 
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxEllipse(bounds, fill, stroke, strokewidth)
+{
+	mxShape.call(this);
+	this.bounds = bounds;
+	this.fill = fill;
+	this.stroke = stroke;
+	this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxUtils.extend(mxEllipse, mxShape);
+
+/**
+ * Function: paintVertexShape
+ * 
+ * Paints the ellipse shape.
+ */
+mxEllipse.prototype.paintVertexShape = function(c, x, y, w, h)
+{
+	c.ellipse(x, y, w, h);
+	c.fillAndStroke();
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/shape/mxHexagon.js b/airavata-kubernetes/web-console/src/assets/js/shape/mxHexagon.js
new file mode 100644
index 0000000..83f4fd6
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/shape/mxHexagon.js
@@ -0,0 +1,34 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxHexagon
+ * 
+ * Implementation of the hexagon shape.
+ * 
+ * Constructor: mxHexagon
+ *
+ * Constructs a new hexagon shape.
+ */
+function mxHexagon()
+{
+	mxActor.call(this);
+};
+
+/**
+ * Extends mxActor.
+ */
+mxUtils.extend(mxHexagon, mxActor);
+
+/**
+ * Function: redrawPath
+ *
+ * Draws the path for this shape.
+ */
+mxHexagon.prototype.redrawPath = function(c, x, y, w, h)
+{
+	var arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
+	this.addPoints(c, [new mxPoint(0.25 * w, 0), new mxPoint(0.75 * w, 0), new mxPoint(w, 0.5 * h), new mxPoint(0.75 * w, h),
+	                   new mxPoint(0.25 * w, h), new mxPoint(0, 0.5 * h)], this.isRounded, arcSize, true);
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/shape/mxImageShape.js b/airavata-kubernetes/web-console/src/assets/js/shape/mxImageShape.js
new file mode 100644
index 0000000..123b64f
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/shape/mxImageShape.js
@@ -0,0 +1,233 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxImageShape
+ *
+ * Extends <mxShape> to implement an image shape. This shape is registered
+ * under <mxConstants.SHAPE_IMAGE> in <mxCellRenderer>.
+ * 
+ * Constructor: mxImageShape
+ * 
+ * Constructs a new image shape.
+ * 
+ * Parameters:
+ * 
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * image - String that specifies the URL of the image. This is stored in
+ * <image>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 0. This is stored in <strokewidth>.
+ */
+function mxImageShape(bounds, image, fill, stroke, strokewidth)
+{
+	mxShape.call(this);
+	this.bounds = bounds;
+	this.image = image;
+	this.fill = fill;
+	this.stroke = stroke;
+	this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+	this.shadow = false;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxUtils.extend(mxImageShape, mxRectangleShape);
+
+/**
+ * Variable: preserveImageAspect
+ *
+ * Switch to preserve image aspect. Default is true.
+ */
+mxImageShape.prototype.preserveImageAspect = true;
+
+/**
+ * Function: getSvgScreenOffset
+ * 
+ * Disables offset in IE9 for crisper image output.
+ */
+mxImageShape.prototype.getSvgScreenOffset = function()
+{
+	return 0;
+};
+
+/**
+ * Function: apply
+ * 
+ * Overrides <mxShape.apply> to replace the fill and stroke colors with the
+ * respective values from <mxConstants.STYLE_IMAGE_BACKGROUND> and
+ * <mxConstants.STYLE_IMAGE_BORDER>.
+ * 
+ * Applies the style of the given <mxCellState> to the shape. This
+ * implementation assigns the following styles to local fields:
+ * 
+ * - <mxConstants.STYLE_IMAGE_BACKGROUND> => fill
+ * - <mxConstants.STYLE_IMAGE_BORDER> => stroke
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> of the corresponding cell.
+ */
+mxImageShape.prototype.apply = function(state)
+{
+	mxShape.prototype.apply.apply(this, arguments);
+	
+	this.fill = null;
+	this.stroke = null;
+	this.gradient = null;
+	
+	if (this.style != null)
+	{
+		this.preserveImageAspect = mxUtils.getNumber(this.style, mxConstants.STYLE_IMAGE_ASPECT, 1) == 1;
+		
+		// Legacy support for imageFlipH/V
+		this.flipH = this.flipH || mxUtils.getValue(this.style, 'imageFlipH', 0) == 1;
+		this.flipV = this.flipV || mxUtils.getValue(this.style, 'imageFlipV', 0) == 1;
+	}
+};
+
+/**
+ * Function: isHtmlAllowed
+ * 
+ * Returns true if HTML is allowed for this shape. This implementation always
+ * returns false.
+ */
+mxImageShape.prototype.isHtmlAllowed = function()
+{
+	return !this.preserveImageAspect;
+};
+
+/**
+ * Function: createHtml
+ *
+ * Creates and returns the HTML DOM node(s) to represent
+ * this shape. This implementation falls back to <createVml>
+ * so that the HTML creation is optional.
+ */
+mxImageShape.prototype.createHtml = function()
+{
+	var node = document.createElement('div');
+	node.style.position = 'absolute';
+
+	return node;
+};
+
+/**
+ * Function: paintVertexShape
+ * 
+ * Generic background painting implementation.
+ */
+mxImageShape.prototype.paintVertexShape = function(c, x, y, w, h)
+{
+	if (this.image != null)
+	{
+		var fill = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_BACKGROUND, null);
+		var stroke = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_BORDER, null);
+		
+		if (fill != null)
+		{
+			// Stroke rendering required for shadow
+			c.setFillColor(fill);
+			c.setStrokeColor(stroke);
+			c.rect(x, y, w, h);
+			c.fillAndStroke();
+		}
+
+		// FlipH/V are implicit via mxShape.updateTransform
+		c.image(x, y, w, h, this.image, this.preserveImageAspect, false, false);
+		
+		var stroke = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_BORDER, null);
+		
+		if (stroke != null)
+		{
+			c.setShadow(false);
+			c.setStrokeColor(stroke);
+			c.rect(x, y, w, h);
+			c.stroke();
+		}
+	}
+	else
+	{
+		mxRectangleShape.prototype.paintBackground.apply(this, arguments);
+	}
+};
+
+/**
+ * Function: redraw
+ * 
+ * Overrides <mxShape.redraw> to preserve the aspect ratio of images.
+ */
+mxImageShape.prototype.redrawHtmlShape = function()
+{
+	this.node.style.left = Math.round(this.bounds.x) + 'px';
+	this.node.style.top = Math.round(this.bounds.y) + 'px';
+	this.node.style.width = Math.max(0, Math.round(this.bounds.width)) + 'px';
+	this.node.style.height = Math.max(0, Math.round(this.bounds.height)) + 'px';
+	this.node.innerHTML = '';
+
+	if (this.image != null)
+	{
+		var fill = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_BACKGROUND, '');
+		var stroke = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_BORDER, '');
+		this.node.style.backgroundColor = fill;
+		this.node.style.borderColor = stroke;
+		
+		// VML image supports PNG in IE6
+		var useVml = mxClient.IS_IE6 || ((document.documentMode == null || document.documentMode <= 8) && this.rotation != 0);
+		var img = document.createElement((useVml) ? mxClient.VML_PREFIX + ':image' : 'img');
+		img.setAttribute('border', '0');
+		img.style.position = 'absolute';
+		img.src = this.image;
+
+		var filter = (this.opacity < 100) ? 'alpha(opacity=' + this.opacity + ')' : '';
+		this.node.style.filter = filter;
+		
+		if (this.flipH && this.flipV)
+		{
+			filter += 'progid:DXImageTransform.Microsoft.BasicImage(rotation=2)';
+		}
+		else if (this.flipH)
+		{
+			filter += 'progid:DXImageTransform.Microsoft.BasicImage(mirror=1)';
+		}
+		else if (this.flipV)
+		{
+			filter += 'progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)';
+		}
+
+		if (img.style.filter != filter)
+		{
+			img.style.filter = filter;
+		}
+
+		if (img.nodeName == 'image')
+		{
+			img.style.rotation = this.rotation;
+		}
+		else if (this.rotation != 0)
+		{
+			// LATER: Add flipV/H support
+			mxUtils.setPrefixedStyle(img.style, 'transform', 'rotate(' + this.rotation + 'deg)');
+		}
+		else
+		{
+			mxUtils.setPrefixedStyle(img.style, 'transform', '');
+		}
+
+		// Known problem: IE clips top line of image for certain angles
+		img.style.width = this.node.style.width;
+		img.style.height = this.node.style.height;
+		
+		this.node.style.backgroundImage = '';
+		this.node.appendChild(img);
+	}
+	else
+	{
+		this.setTransparentBackgroundImage(this.node);
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/shape/mxLabel.js b/airavata-kubernetes/web-console/src/assets/js/shape/mxLabel.js
new file mode 100644
index 0000000..f9305b1
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/shape/mxLabel.js
@@ -0,0 +1,275 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxLabel
+ *
+ * Extends <mxShape> to implement an image shape with a label.
+ * This shape is registered under <mxConstants.SHAPE_LABEL> in
+ * <mxCellRenderer>.
+ * 
+ * Constructor: mxLabel
+ *
+ * Constructs a new label shape.
+ * 
+ * Parameters:
+ * 
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxLabel(bounds, fill, stroke, strokewidth)
+{
+	mxRectangleShape.call(this, bounds, fill, stroke, strokewidth);
+};
+
+/**
+ * Extends mxShape.
+ */
+mxUtils.extend(mxLabel, mxRectangleShape);
+
+/**
+ * Variable: imageSize
+ *
+ * Default width and height for the image. Default is
+ * <mxConstants.DEFAULT_IMAGESIZE>.
+ */
+mxLabel.prototype.imageSize = mxConstants.DEFAULT_IMAGESIZE;
+
+/**
+ * Variable: spacing
+ *
+ * Default value for image spacing. Default is 2.
+ */
+mxLabel.prototype.spacing = 2;
+
+/**
+ * Variable: indicatorSize
+ *
+ * Default width and height for the indicicator. Default is 10.
+ */
+mxLabel.prototype.indicatorSize = 10;
+
+/**
+ * Variable: indicatorSpacing
+ *
+ * Default spacing between image and indicator. Default is 2.
+ */
+mxLabel.prototype.indicatorSpacing = 2;
+
+/**
+ * Function: init
+ *
+ * Initializes the shape and the <indicator>.
+ */
+mxLabel.prototype.init = function(container)
+{
+	mxShape.prototype.init.apply(this, arguments);
+
+	if (this.indicatorShape != null)
+	{
+		this.indicator = new this.indicatorShape();
+		this.indicator.dialect = this.dialect;
+		this.indicator.init(this.node);
+	}
+};
+
+/**
+ * Function: redraw
+ *
+ * Reconfigures this shape. This will update the colors of the indicator
+ * and reconfigure it if required.
+ */
+mxLabel.prototype.redraw = function()
+{
+	if (this.indicator != null)
+	{
+		this.indicator.fill = this.indicatorColor;
+		this.indicator.stroke = this.indicatorStrokeColor;
+		this.indicator.gradient = this.indicatorGradientColor;
+		this.indicator.direction = this.indicatorDirection;
+	}
+	
+	mxShape.prototype.redraw.apply(this, arguments);
+};
+
+/**
+ * Function: isHtmlAllowed
+ *
+ * Returns true for non-rounded, non-rotated shapes with no glass gradient and
+ * no indicator shape.
+ */
+mxLabel.prototype.isHtmlAllowed = function()
+{
+	return mxRectangleShape.prototype.isHtmlAllowed.apply(this, arguments) &&
+		this.indicatorColor == null && this.indicatorShape == null;
+};
+
+/**
+ * Function: paintForeground
+ * 
+ * Generic background painting implementation.
+ */
+mxLabel.prototype.paintForeground = function(c, x, y, w, h)
+{
+	this.paintImage(c, x, y, w, h);
+	this.paintIndicator(c, x, y, w, h);
+	
+	mxRectangleShape.prototype.paintForeground.apply(this, arguments);
+};
+
+/**
+ * Function: paintImage
+ * 
+ * Generic background painting implementation.
+ */
+mxLabel.prototype.paintImage = function(c, x, y, w, h)
+{
+	if (this.image != null)
+	{
+		var bounds = this.getImageBounds(x, y, w, h);
+		c.image(bounds.x, bounds.y, bounds.width, bounds.height, this.image, false, false, false);
+	}
+};
+
+/**
+ * Function: getImageBounds
+ * 
+ * Generic background painting implementation.
+ */
+mxLabel.prototype.getImageBounds = function(x, y, w, h)
+{
+	var align = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_ALIGN, mxConstants.ALIGN_LEFT);
+	var valign = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_VERTICAL_ALIGN, mxConstants.ALIGN_MIDDLE);
+	var width = mxUtils.getNumber(this.style, mxConstants.STYLE_IMAGE_WIDTH, mxConstants.DEFAULT_IMAGESIZE);
+	var height = mxUtils.getNumber(this.style, mxConstants.STYLE_IMAGE_HEIGHT, mxConstants.DEFAULT_IMAGESIZE);
+	var spacing = mxUtils.getNumber(this.style, mxConstants.STYLE_SPACING, this.spacing) + 5;
+
+	if (align == mxConstants.ALIGN_CENTER)
+	{
+		x += (w - width) / 2;
+	}
+	else if (align == mxConstants.ALIGN_RIGHT)
+	{
+		x += w - width - spacing;
+	}
+	else // default is left
+	{
+		x += spacing;
+	}
+
+	if (valign == mxConstants.ALIGN_TOP)
+	{
+		y += spacing;
+	}
+	else if (valign == mxConstants.ALIGN_BOTTOM)
+	{
+		y += h - height - spacing;
+	}
+	else // default is middle
+	{
+		y += (h - height) / 2;
+	}
+	
+	return new mxRectangle(x, y, width, height);
+};
+
+/**
+ * Function: paintIndicator
+ * 
+ * Generic background painting implementation.
+ */
+mxLabel.prototype.paintIndicator = function(c, x, y, w, h)
+{
+	if (this.indicator != null)
+	{
+		this.indicator.bounds = this.getIndicatorBounds(x, y, w, h);
+		this.indicator.paint(c);
+	}
+	else if (this.indicatorImage != null)
+	{
+		var bounds = this.getIndicatorBounds(x, y, w, h);
+		c.image(bounds.x, bounds.y, bounds.width, bounds.height, this.indicatorImage, false, false, false);
+	}
+};
+
+/**
+ * Function: getIndicatorBounds
+ * 
+ * Generic background painting implementation.
+ */
+mxLabel.prototype.getIndicatorBounds = function(x, y, w, h)
+{
+	var align = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_ALIGN, mxConstants.ALIGN_LEFT);
+	var valign = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_VERTICAL_ALIGN, mxConstants.ALIGN_MIDDLE);
+	var width = mxUtils.getNumber(this.style, mxConstants.STYLE_INDICATOR_WIDTH, this.indicatorSize);
+	var height = mxUtils.getNumber(this.style, mxConstants.STYLE_INDICATOR_HEIGHT, this.indicatorSize);
+	var spacing = this.spacing + 5;		
+	
+	if (align == mxConstants.ALIGN_RIGHT)
+	{
+		x += w - width - spacing;
+	}
+	else if (align == mxConstants.ALIGN_CENTER)
+	{
+		x += (w - width) / 2;
+	}
+	else // default is left
+	{
+		x += spacing;
+	}
+	
+	if (valign == mxConstants.ALIGN_BOTTOM)
+	{
+		y += h - height - spacing;
+	}
+	else if (valign == mxConstants.ALIGN_TOP)
+	{
+		y += spacing;
+	}
+	else // default is middle
+	{
+		y += (h - height) / 2;
+	}
+	
+	return new mxRectangle(x, y, width, height);
+};
+/**
+ * Function: redrawHtmlShape
+ * 
+ * Generic background painting implementation.
+ */
+mxLabel.prototype.redrawHtmlShape = function()
+{
+	mxRectangleShape.prototype.redrawHtmlShape.apply(this, arguments);
+	
+	// Removes all children
+	while(this.node.hasChildNodes())
+	{
+		this.node.removeChild(this.node.lastChild);
+	}
+	
+	if (this.image != null)
+	{
+		var node = document.createElement('img');
+		node.style.position = 'relative';
+		node.setAttribute('border', '0');
+		
+		var bounds = this.getImageBounds(this.bounds.x, this.bounds.y, this.bounds.width, this.bounds.height);
+		bounds.x -= this.bounds.x;
+		bounds.y -= this.bounds.y;
+
+		node.style.left = Math.round(bounds.x) + 'px';
+		node.style.top = Math.round(bounds.y) + 'px';
+		node.style.width = Math.round(bounds.width) + 'px';
+		node.style.height = Math.round(bounds.height) + 'px';
+		
+		node.src = this.image;
+		
+		this.node.appendChild(node);
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/shape/mxLine.js b/airavata-kubernetes/web-console/src/assets/js/shape/mxLine.js
new file mode 100644
index 0000000..a154cb3
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/shape/mxLine.js
@@ -0,0 +1,51 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxLine
+ *
+ * Extends <mxShape> to implement a horizontal line shape.
+ * This shape is registered under <mxConstants.SHAPE_LINE> in
+ * <mxCellRenderer>.
+ * 
+ * Constructor: mxLine
+ *
+ * Constructs a new line shape.
+ * 
+ * Parameters:
+ * 
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * stroke - String that defines the stroke color. Default is 'black'. This is
+ * stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxLine(bounds, stroke, strokewidth)
+{
+	mxShape.call(this);
+	this.bounds = bounds;
+	this.stroke = stroke;
+	this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxUtils.extend(mxLine, mxShape);
+
+/**
+ * Function: paintVertexShape
+ * 
+ * Redirects to redrawPath for subclasses to work.
+ */
+mxLine.prototype.paintVertexShape = function(c, x, y, w, h)
+{
+	var mid = y + h / 2;
+
+	c.begin();
+	c.moveTo(x, mid);
+	c.lineTo(x + w, mid);
+	c.stroke();
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/shape/mxMarker.js b/airavata-kubernetes/web-console/src/assets/js/shape/mxMarker.js
new file mode 100644
index 0000000..1004eb5
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/shape/mxMarker.js
@@ -0,0 +1,208 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+var mxMarker =
+{
+	/**
+	 * Class: mxMarker
+	 * 
+	 * A static class that implements all markers for VML and SVG using a
+	 * registry. NOTE: The signatures in this class will change.
+	 * 
+	 * Variable: markers
+	 * 
+	 * Maps from markers names to functions to paint the markers.
+	 */
+	markers: [],
+	
+	/**
+	 * Function: addMarker
+	 * 
+	 * Adds a factory method that updates a given endpoint and returns a
+	 * function to paint the marker onto the given canvas.
+	 */
+	addMarker: function(type, funct)
+	{
+		mxMarker.markers[type] = funct;
+	},
+	
+	/**
+	 * Function: createMarker
+	 * 
+	 * Returns a function to paint the given marker.
+	 */
+	createMarker: function(canvas, shape, type, pe, unitX, unitY, size, source, sw, filled)
+	{
+		var funct = mxMarker.markers[type];
+		
+		return (funct != null) ? funct(canvas, shape, type, pe, unitX, unitY, size, source, sw, filled) : null;
+	}
+
+};
+
+/**
+ * Adds the classic and block marker factory method.
+ */
+(function()
+{
+	function createArrow(widthFactor)
+	{
+		widthFactor = (widthFactor != null) ? widthFactor : 2;
+		
+		return function(canvas, shape, type, pe, unitX, unitY, size, source, sw, filled)
+		{
+			// The angle of the forward facing arrow sides against the x axis is
+			// 26.565 degrees, 1/sin(26.565) = 2.236 / 2 = 1.118 ( / 2 allows for
+			// only half the strokewidth is processed ).
+			var endOffsetX = unitX * sw * 1.118;
+			var endOffsetY = unitY * sw * 1.118;
+			
+			unitX = unitX * (size + sw);
+			unitY = unitY * (size + sw);
+	
+			var pt = pe.clone();
+			pt.x -= endOffsetX;
+			pt.y -= endOffsetY;
+			
+			var f = (type != mxConstants.ARROW_CLASSIC && type != mxConstants.ARROW_CLASSIC_THIN) ? 1 : 3 / 4;
+			pe.x += -unitX * f - endOffsetX;
+			pe.y += -unitY * f - endOffsetY;
+			
+			return function()
+			{
+				canvas.begin();
+				canvas.moveTo(pt.x, pt.y);
+				canvas.lineTo(pt.x - unitX - unitY / widthFactor, pt.y - unitY + unitX / widthFactor);
+			
+				if (type == mxConstants.ARROW_CLASSIC || type == mxConstants.ARROW_CLASSIC_THIN)
+				{
+					canvas.lineTo(pt.x - unitX * 3 / 4, pt.y - unitY * 3 / 4);
+				}
+			
+				canvas.lineTo(pt.x + unitY / widthFactor - unitX, pt.y - unitY - unitX / widthFactor);
+				canvas.close();
+	
+				if (filled)
+				{
+					canvas.fillAndStroke();
+				}
+				else
+				{
+					canvas.stroke();
+				}
+			};
+		}
+	};
+	
+	mxMarker.addMarker('classic', createArrow(2));
+	mxMarker.addMarker('classicThin', createArrow(3));
+	mxMarker.addMarker('block', createArrow(2));
+	mxMarker.addMarker('blockThin', createArrow(3));
+	
+	function createOpenArrow(widthFactor)
+	{
+		widthFactor = (widthFactor != null) ? widthFactor : 2;
+		
+		return function(canvas, shape, type, pe, unitX, unitY, size, source, sw, filled)
+		{
+			// The angle of the forward facing arrow sides against the x axis is
+			// 26.565 degrees, 1/sin(26.565) = 2.236 / 2 = 1.118 ( / 2 allows for
+			// only half the strokewidth is processed ).
+			var endOffsetX = unitX * sw * 1.118;
+			var endOffsetY = unitY * sw * 1.118;
+			
+			unitX = unitX * (size + sw);
+			unitY = unitY * (size + sw);
+			
+			var pt = pe.clone();
+			pt.x -= endOffsetX;
+			pt.y -= endOffsetY;
+			
+			pe.x += -endOffsetX * 2;
+			pe.y += -endOffsetY * 2;
+
+			return function()
+			{
+				canvas.begin();
+				canvas.moveTo(pt.x - unitX - unitY / widthFactor, pt.y - unitY + unitX / widthFactor);
+				canvas.lineTo(pt.x, pt.y);
+				canvas.lineTo(pt.x + unitY / widthFactor - unitX, pt.y - unitY - unitX / widthFactor);
+				canvas.stroke();
+			};
+		}
+	};
+	
+	mxMarker.addMarker('open', createOpenArrow(2));
+	mxMarker.addMarker('openThin', createOpenArrow(3));
+	
+	mxMarker.addMarker('oval', function(canvas, shape, type, pe, unitX, unitY, size, source, sw, filled)
+	{
+		var a = size / 2;
+		
+		var pt = pe.clone();
+		pe.x -= unitX * a;
+		pe.y -= unitY * a;
+
+		return function()
+		{
+			canvas.ellipse(pt.x - a, pt.y - a, size, size);
+						
+			if (filled)
+			{
+				canvas.fillAndStroke();
+			}
+			else
+			{
+				canvas.stroke();
+			}
+		};
+	});
+
+	function diamond(canvas, shape, type, pe, unitX, unitY, size, source, sw, filled)
+	{
+		// The angle of the forward facing arrow sides against the x axis is
+		// 45 degrees, 1/sin(45) = 1.4142 / 2 = 0.7071 ( / 2 allows for
+		// only half the strokewidth is processed ). Or 0.9862 for thin diamond.
+		// Note these values and the tk variable below are dependent, update
+		// both together (saves trig hard coding it).
+		var swFactor = (type == mxConstants.ARROW_DIAMOND) ?  0.7071 : 0.9862;
+		var endOffsetX = unitX * sw * swFactor;
+		var endOffsetY = unitY * sw * swFactor;
+		
+		unitX = unitX * (size + sw);
+		unitY = unitY * (size + sw);
+		
+		var pt = pe.clone();
+		pt.x -= endOffsetX;
+		pt.y -= endOffsetY;
+		
+		pe.x += -unitX - endOffsetX;
+		pe.y += -unitY - endOffsetY;
+		
+		// thickness factor for diamond
+		var tk = ((type == mxConstants.ARROW_DIAMOND) ?  2 : 3.4);
+		
+		return function()
+		{
+			canvas.begin();
+			canvas.moveTo(pt.x, pt.y);
+			canvas.lineTo(pt.x - unitX / 2 - unitY / tk, pt.y + unitX / tk - unitY / 2);
+			canvas.lineTo(pt.x - unitX, pt.y - unitY);
+			canvas.lineTo(pt.x - unitX / 2 + unitY / tk, pt.y - unitY / 2 - unitX / tk);
+			canvas.close();
+			
+			if (filled)
+			{
+				canvas.fillAndStroke();
+			}
+			else
+			{
+				canvas.stroke();
+			}
+		};
+	};
+
+	mxMarker.addMarker('diamond', diamond);
+	mxMarker.addMarker('diamondThin', diamond);
+})();
diff --git a/airavata-kubernetes/web-console/src/assets/js/shape/mxPolyline.js b/airavata-kubernetes/web-console/src/assets/js/shape/mxPolyline.js
new file mode 100644
index 0000000..794e896
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/shape/mxPolyline.js
@@ -0,0 +1,127 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxPolyline
+ *
+ * Extends <mxShape> to implement a polyline (a line with multiple points).
+ * This shape is registered under <mxConstants.SHAPE_POLYLINE> in
+ * <mxCellRenderer>.
+ * 
+ * Constructor: mxPolyline
+ *
+ * Constructs a new polyline shape.
+ * 
+ * Parameters:
+ * 
+ * points - Array of <mxPoints> that define the points. This is stored in
+ * <mxShape.points>.
+ * stroke - String that defines the stroke color. Default is 'black'. This is
+ * stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxPolyline(points, stroke, strokewidth)
+{
+	mxShape.call(this);
+	this.points = points;
+	this.stroke = stroke;
+	this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxUtils.extend(mxPolyline, mxShape);
+
+/**
+ * Function: getRotation
+ * 
+ * Returns 0.
+ */
+mxPolyline.prototype.getRotation = function()
+{
+	return 0;
+};
+
+/**
+ * Function: getShapeRotation
+ * 
+ * Returns 0.
+ */
+mxPolyline.prototype.getShapeRotation = function()
+{
+	return 0;
+};
+
+/**
+ * Function: isPaintBoundsInverted
+ * 
+ * Returns false.
+ */
+mxPolyline.prototype.isPaintBoundsInverted = function()
+{
+	return false;
+};
+
+/**
+ * Function: paintEdgeShape
+ * 
+ * Paints the line shape.
+ */
+mxPolyline.prototype.paintEdgeShape = function(c, pts)
+{
+	if (this.style == null || this.style[mxConstants.STYLE_CURVED] != 1)
+	{
+		this.paintLine(c, pts, this.isRounded);
+	}
+	else
+	{
+		this.paintCurvedLine(c, pts);
+	}
+};
+
+/**
+ * Function: paintLine
+ * 
+ * Paints the line shape.
+ */
+mxPolyline.prototype.paintLine = function(c, pts, rounded)
+{
+	var arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
+	c.begin();
+	this.addPoints(c, pts, rounded, arcSize, false);
+	c.stroke();
+};
+
+/**
+ * Function: paintLine
+ * 
+ * Paints the line shape.
+ */
+mxPolyline.prototype.paintCurvedLine = function(c, pts)
+{
+	c.begin();
+	
+	var pt = pts[0];
+	var n = pts.length;
+	
+	c.moveTo(pt.x, pt.y);
+	
+	for (var i = 1; i < n - 2; i++)
+	{
+		var p0 = pts[i];
+		var p1 = pts[i + 1];
+		var ix = (p0.x + p1.x) / 2;
+		var iy = (p0.y + p1.y) / 2;
+		
+		c.quadTo(p0.x, p0.y, ix, iy);
+	}
+	
+	var p0 = pts[n - 2];
+	var p1 = pts[n - 1];
+	
+	c.quadTo(p0.x, p0.y, p1.x, p1.y);
+	c.stroke();
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/shape/mxRectangleShape.js b/airavata-kubernetes/web-console/src/assets/js/shape/mxRectangleShape.js
new file mode 100644
index 0000000..da61e4b
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/shape/mxRectangleShape.js
@@ -0,0 +1,117 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxRectangleShape
+ *
+ * Extends <mxShape> to implement a rectangle shape.
+ * This shape is registered under <mxConstants.SHAPE_RECTANGLE>
+ * in <mxCellRenderer>.
+ * 
+ * Constructor: mxRectangleShape
+ *
+ * Constructs a new rectangle shape.
+ * 
+ * Parameters:
+ * 
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxRectangleShape(bounds, fill, stroke, strokewidth)
+{
+	mxShape.call(this);
+	this.bounds = bounds;
+	this.fill = fill;
+	this.stroke = stroke;
+	this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxUtils.extend(mxRectangleShape, mxShape);
+
+/**
+ * Function: isHtmlAllowed
+ *
+ * Returns true for non-rounded, non-rotated shapes with no glass gradient.
+ */
+mxRectangleShape.prototype.isHtmlAllowed = function()
+{
+	var events = true;
+	
+	if (this.style != null)
+	{
+		events = mxUtils.getValue(this.style, mxConstants.STYLE_POINTER_EVENTS, '1') == '1';		
+	}
+	
+	return !this.isRounded && !this.glass && this.rotation == 0 && (events ||
+		(this.fill != null && this.fill != mxConstants.NONE));
+};
+
+/**
+ * Function: paintBackground
+ * 
+ * Generic background painting implementation.
+ */
+mxRectangleShape.prototype.paintBackground = function(c, x, y, w, h)
+{
+	var events = true;
+	
+	if (this.style != null)
+	{
+		events = mxUtils.getValue(this.style, mxConstants.STYLE_POINTER_EVENTS, '1') == '1';		
+	}
+	
+	if (events || (this.fill != null && this.fill != mxConstants.NONE) ||
+		(this.stroke != null && this.stroke != mxConstants.NONE))
+	{
+		if (!events && (this.fill == null || this.fill == mxConstants.NONE))
+		{
+			c.pointerEvents = false;
+		}
+		
+		if (this.isRounded)
+		{
+			var r = 0;
+			
+			if (mxUtils.getValue(this.style, mxConstants.STYLE_ABSOLUTE_ARCSIZE, 0) == '1')
+			{
+				r = Math.min(w / 2, Math.min(h / 2, mxUtils.getValue(this.style,
+					mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2));
+			}
+			else
+			{
+				var f = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE,
+					mxConstants.RECTANGLE_ROUNDING_FACTOR * 100) / 100;
+				r = Math.min(w * f, h * f);
+			}
+			
+			c.roundrect(x, y, w, h, r, r);
+		}
+		else
+		{
+			c.rect(x, y, w, h);
+		}
+			
+		c.fillAndStroke();
+	}
+};
+
+/**
+ * Function: paintForeground
+ * 
+ * Generic background painting implementation.
+ */
+mxRectangleShape.prototype.paintForeground = function(c, x, y, w, h)
+{
+	if (this.glass && !this.outline && this.fill != null && this.fill != mxConstants.NONE)
+	{
+		this.paintGlassEffect(c, x, y, w, h, this.getArcSize(w + this.strokewidth, h + this.strokewidth));
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/shape/mxRhombus.js b/airavata-kubernetes/web-console/src/assets/js/shape/mxRhombus.js
new file mode 100644
index 0000000..4bc2962
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/shape/mxRhombus.js
@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxRhombus
+ *
+ * Extends <mxShape> to implement a rhombus (aka diamond) shape.
+ * This shape is registered under <mxConstants.SHAPE_RHOMBUS>
+ * in <mxCellRenderer>.
+ * 
+ * Constructor: mxRhombus
+ *
+ * Constructs a new rhombus shape.
+ * 
+ * Parameters:
+ * 
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxRhombus(bounds, fill, stroke, strokewidth)
+{
+	mxShape.call(this);
+	this.bounds = bounds;
+	this.fill = fill;
+	this.stroke = stroke;
+	this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxUtils.extend(mxRhombus, mxShape);
+
+/**
+ * Function: paintVertexShape
+ * 
+ * Generic painting implementation.
+ */
+mxRhombus.prototype.paintVertexShape = function(c, x, y, w, h)
+{
+	var hw = w / 2;
+	var hh = h / 2;
+	
+	var arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
+	c.begin();
+	this.addPoints(c, [new mxPoint(x + hw, y), new mxPoint(x + w, y + hh), new mxPoint(x + hw, y + h),
+	     new mxPoint(x, y + hh)], this.isRounded, arcSize, true);
+	c.fillAndStroke();
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/shape/mxShape.js b/airavata-kubernetes/web-console/src/assets/js/shape/mxShape.js
new file mode 100644
index 0000000..506d6d8
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/shape/mxShape.js
@@ -0,0 +1,1604 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxShape
+ *
+ * Base class for all shapes. A shape in mxGraph is a
+ * separate implementation for SVG, VML and HTML. Which
+ * implementation to use is controlled by the <dialect>
+ * property which is assigned from within the <mxCellRenderer>
+ * when the shape is created. The dialect must be assigned
+ * for a shape, and it does normally depend on the browser and
+ * the confiuration of the graph (see <mxGraph> rendering hint).
+ *
+ * For each supported shape in SVG and VML, a corresponding
+ * shape exists in mxGraph, namely for text, image, rectangle,
+ * rhombus, ellipse and polyline. The other shapes are a
+ * combination of these shapes (eg. label and swimlane)
+ * or they consist of one or more (filled) path objects
+ * (eg. actor and cylinder). The HTML implementation is
+ * optional but may be required for a HTML-only view of
+ * the graph.
+ *
+ * Custom Shapes:
+ *
+ * To extend from this class, the basic code looks as follows.
+ * In the special case where the custom shape consists only of
+ * one filled region or one filled region and an additional stroke
+ * the <mxActor> and <mxCylinder> should be subclassed,
+ * respectively.
+ *
+ * (code)
+ * function CustomShape() { }
+ * 
+ * CustomShape.prototype = new mxShape();
+ * CustomShape.prototype.constructor = CustomShape; 
+ * (end)
+ *
+ * To register a custom shape in an existing graph instance,
+ * one must register the shape under a new name in the graph's
+ * cell renderer as follows:
+ *
+ * (code)
+ * mxCellRenderer.registerShape('customShape', CustomShape);
+ * (end)
+ *
+ * The second argument is the name of the constructor.
+ *
+ * In order to use the shape you can refer to the given name above
+ * in a stylesheet. For example, to change the shape for the default
+ * vertex style, the following code is used:
+ *
+ * (code)
+ * var style = graph.getStylesheet().getDefaultVertexStyle();
+ * style[mxConstants.STYLE_SHAPE] = 'customShape';
+ * (end)
+ * 
+ * Constructor: mxShape
+ *
+ * Constructs a new shape.
+ */
+function mxShape(stencil)
+{
+	this.stencil = stencil;
+	this.initStyles();
+};
+
+/**
+ * Variable: dialect
+ *
+ * Holds the dialect in which the shape is to be painted.
+ * This can be one of the DIALECT constants in <mxConstants>.
+ */
+mxShape.prototype.dialect = null;
+
+/**
+ * Variable: scale
+ *
+ * Holds the scale in which the shape is being painted.
+ */
+mxShape.prototype.scale = 1;
+
+/**
+ * Variable: antiAlias
+ * 
+ * Rendering hint for configuring the canvas.
+ */
+mxShape.prototype.antiAlias = true;
+
+/**
+ * Variable: bounds
+ *
+ * Holds the <mxRectangle> that specifies the bounds of this shape.
+ */
+mxShape.prototype.bounds = null;
+
+/**
+ * Variable: points
+ *
+ * Holds the array of <mxPoints> that specify the points of this shape.
+ */
+mxShape.prototype.points = null;
+
+/**
+ * Variable: node
+ *
+ * Holds the outermost DOM node that represents this shape.
+ */
+mxShape.prototype.node = null;
+ 
+/**
+ * Variable: state
+ * 
+ * Optional reference to the corresponding <mxCellState>.
+ */
+mxShape.prototype.state = null;
+
+/**
+ * Variable: style
+ *
+ * Optional reference to the style of the corresponding <mxCellState>.
+ */
+mxShape.prototype.style = null;
+
+/**
+ * Variable: boundingBox
+ *
+ * Contains the bounding box of the shape, that is, the smallest rectangle
+ * that includes all pixels of the shape.
+ */
+mxShape.prototype.boundingBox = null;
+
+/**
+ * Variable: stencil
+ *
+ * Holds the <mxStencil> that defines the shape.
+ */
+mxShape.prototype.stencil = null;
+
+/**
+ * Variable: svgStrokeTolerance
+ *
+ * Event-tolerance for SVG strokes (in px). Default is 8. This is only passed
+ * to the canvas in <createSvgCanvas> if <pointerEvents> is true.
+ */
+mxShape.prototype.svgStrokeTolerance = 8;
+
+/**
+ * Variable: pointerEvents
+ * 
+ * Specifies if pointer events should be handled. Default is true.
+ */
+mxShape.prototype.pointerEvents = true;
+
+/**
+ * Variable: svgPointerEvents
+ * 
+ * Specifies if pointer events should be handled. Default is true.
+ */
+mxShape.prototype.svgPointerEvents = 'all';
+
+/**
+ * Variable: shapePointerEvents
+ * 
+ * Specifies if pointer events outside of shape should be handled. Default
+ * is false.
+ */
+mxShape.prototype.shapePointerEvents = false;
+
+/**
+ * Variable: stencilPointerEvents
+ * 
+ * Specifies if pointer events outside of stencils should be handled. Default
+ * is false. Set this to true for backwards compatibility with the 1.x branch.
+ */
+mxShape.prototype.stencilPointerEvents = false;
+
+/**
+ * Variable: vmlScale
+ * 
+ * Scale for improving the precision of VML rendering. Default is 1.
+ */
+mxShape.prototype.vmlScale = 1;
+
+/**
+ * Variable: outline
+ * 
+ * Specifies if the shape should be drawn as an outline. This disables all
+ * fill colors and can be used to disable other drawing states that should
+ * not be painted for outlines. Default is false. This should be set before
+ * calling <apply>.
+ */
+mxShape.prototype.outline = false;
+
+/**
+ * Variable: visible
+ * 
+ * Specifies if the shape is visible. Default is true.
+ */
+mxShape.prototype.visible = true;
+
+/**
+ * Variable: useSvgBoundingBox
+ * 
+ * Allows to use the SVG bounding box in SVG. Default is false for performance
+ * reasons.
+ */
+mxShape.prototype.useSvgBoundingBox = false;
+
+/**
+ * Function: init
+ *
+ * Initializes the shape by creaing the DOM node using <create>
+ * and adding it into the given container.
+ *
+ * Parameters:
+ *
+ * container - DOM node that will contain the shape.
+ */
+mxShape.prototype.init = function(container)
+{
+	if (this.node == null)
+	{
+		this.node = this.create(container);
+		
+		if (container != null)
+		{
+			container.appendChild(this.node);
+		}
+	}
+};
+
+/**
+ * Function: initStyles
+ *
+ * Sets the styles to their default values.
+ */
+mxShape.prototype.initStyles = function(container)
+{
+	this.strokewidth = 1;
+	this.rotation = 0;
+	this.opacity = 100;
+	this.fillOpacity = 100;
+	this.strokeOpacity = 100;
+	this.flipH = false;
+	this.flipV = false;
+};
+
+/**
+ * Function: isParseVml
+ * 
+ * Specifies if any VML should be added via insertAdjacentHtml to the DOM. This
+ * is only needed in IE8 and only if the shape contains VML markup. This method
+ * returns true.
+ */
+mxShape.prototype.isParseVml = function()
+{
+	return true;
+};
+
+/**
+ * Function: isHtmlAllowed
+ * 
+ * Returns true if HTML is allowed for this shape. This implementation always
+ * returns false.
+ */
+mxShape.prototype.isHtmlAllowed = function()
+{
+	return false;
+};
+
+/**
+ * Function: getSvgScreenOffset
+ * 
+ * Returns 0, or 0.5 if <strokewidth> % 2 == 1.
+ */
+mxShape.prototype.getSvgScreenOffset = function()
+{
+	var sw = this.stencil && this.stencil.strokewidth != 'inherit' ? Number(this.stencil.strokewidth) : this.strokewidth;
+	
+	return (mxUtils.mod(Math.max(1, Math.round(sw * this.scale)), 2) == 1) ? 0.5 : 0;
+};
+
+/**
+ * Function: create
+ *
+ * Creates and returns the DOM node(s) for the shape in
+ * the given container. This implementation invokes
+ * <createSvg>, <createHtml> or <createVml> depending
+ * on the <dialect> and style settings.
+ *
+ * Parameters:
+ *
+ * container - DOM node that will contain the shape.
+ */
+mxShape.prototype.create = function(container)
+{
+	var node = null;
+	
+	if (container != null && container.ownerSVGElement != null)
+	{
+		node = this.createSvg(container);
+	}
+	else if (document.documentMode == 8 || !mxClient.IS_VML ||
+		(this.dialect != mxConstants.DIALECT_VML && this.isHtmlAllowed()))
+	{
+		node = this.createHtml(container);
+	}
+	else
+	{
+		node = this.createVml(container);
+	}
+	
+	return node;
+};
+
+/**
+ * Function: createSvg
+ *
+ * Creates and returns the SVG node(s) to represent this shape.
+ */
+mxShape.prototype.createSvg = function()
+{
+	return document.createElementNS(mxConstants.NS_SVG, 'g');
+};
+
+/**
+ * Function: createVml
+ *
+ * Creates and returns the VML node to represent this shape.
+ */
+mxShape.prototype.createVml = function()
+{
+	var node = document.createElement(mxClient.VML_PREFIX + ':group');
+	node.style.position = 'absolute';
+	
+	return node;
+};
+
+/**
+ * Function: createHtml
+ *
+ * Creates and returns the HTML DOM node(s) to represent
+ * this shape. This implementation falls back to <createVml>
+ * so that the HTML creation is optional.
+ */
+mxShape.prototype.createHtml = function()
+{
+	var node = document.createElement('div');
+	node.style.position = 'absolute';
+	
+	return node;
+};
+
+/**
+ * Function: reconfigure
+ *
+ * Reconfigures this shape. This will update the colors etc in
+ * addition to the bounds or points.
+ */
+mxShape.prototype.reconfigure = function()
+{
+	this.redraw();
+};
+
+/**
+ * Function: redraw
+ *
+ * Creates and returns the SVG node(s) to represent this shape.
+ */
+mxShape.prototype.redraw = function()
+{
+	this.updateBoundsFromPoints();
+	
+	if (this.visible && this.checkBounds())
+	{
+		this.node.style.visibility = 'visible';
+		this.clear();
+		
+		if (this.node.nodeName == 'DIV' && (this.isHtmlAllowed() || !mxClient.IS_VML))
+		{
+			this.redrawHtmlShape();
+		}
+		else
+		{	
+			this.redrawShape();
+		}
+
+		this.updateBoundingBox();
+	}
+	else
+	{
+		this.node.style.visibility = 'hidden';
+		this.boundingBox = null;
+	}
+};
+
+/**
+ * Function: clear
+ * 
+ * Removes all child nodes and resets all CSS.
+ */
+mxShape.prototype.clear = function()
+{
+	if (this.node.ownerSVGElement != null)
+	{
+		while (this.node.lastChild != null)
+		{
+			this.node.removeChild(this.node.lastChild);
+		}
+	}
+	else
+	{
+		this.node.style.cssText = 'position:absolute;' + ((this.cursor != null) ?
+			('cursor:' + this.cursor + ';') : '');
+		this.node.innerHTML = '';
+	}
+};
+
+/**
+ * Function: updateBoundsFromPoints
+ * 
+ * Updates the bounds based on the points.
+ */
+mxShape.prototype.updateBoundsFromPoints = function()
+{
+	var pts = this.points;
+	
+	if (pts != null && pts.length > 0 && pts[0] != null)
+	{
+		this.bounds = new mxRectangle(Number(pts[0].x), Number(pts[0].y), 1, 1);
+		
+		for (var i = 1; i < this.points.length; i++)
+		{
+			if (pts[i] != null)
+			{
+				this.bounds.add(new mxRectangle(Number(pts[i].x), Number(pts[i].y), 1, 1));
+			}
+		}
+	}
+};
+
+/**
+ * Function: getLabelBounds
+ * 
+ * Returns the <mxRectangle> for the label bounds of this shape, based on the
+ * given scaled and translated bounds of the shape. This method should not
+ * change the rectangle in-place. This implementation returns the given rect.
+ */
+mxShape.prototype.getLabelBounds = function(rect)
+{
+	var d = mxUtils.getValue(this.style, mxConstants.STYLE_DIRECTION, mxConstants.DIRECTION_EAST);
+	var bounds = rect;
+	
+	// Normalizes argument for getLabelMargins hook
+	if (d != mxConstants.DIRECTION_SOUTH && d != mxConstants.DIRECTION_NORTH &&
+		this.state != null && this.state.text != null &&
+		this.state.text.isPaintBoundsInverted())
+	{
+		bounds = bounds.clone();
+		var tmp = bounds.width;
+		bounds.width = bounds.height;
+		bounds.height = tmp;
+	}
+		
+	var m = this.getLabelMargins(bounds);
+	
+	if (m != null)
+	{
+		var flipH = mxUtils.getValue(this.style, mxConstants.STYLE_FLIPH, false) == '1';
+		var flipV = mxUtils.getValue(this.style, mxConstants.STYLE_FLIPV, false) == '1';
+		
+		// Handles special case for vertical labels
+		if (this.state != null && this.state.text != null &&
+			this.state.text.isPaintBoundsInverted())
+		{
+			var tmp = m.x;
+			m.x = m.height;
+			m.height = m.width;
+			m.width = m.y;
+			m.y = tmp;
+
+			tmp = flipH;
+			flipH = flipV;
+			flipV = tmp;
+		}
+		
+		return mxUtils.getDirectedBounds(rect, m, this.style, flipH, flipV);
+	}
+	
+	return rect;
+};
+
+/**
+ * Function: getLabelMargins
+ * 
+ * Returns the scaled top, left, bottom and right margin to be used for
+ * computing the label bounds as an <mxRectangle>, where the bottom and right
+ * margin are defined in the width and height of the rectangle, respectively.
+ */
+mxShape.prototype.getLabelMargins= function(rect)
+{
+	return null;
+};
+
+/**
+ * Function: checkBounds
+ * 
+ * Returns true if the bounds are not null and all of its variables are numeric.
+ */
+mxShape.prototype.checkBounds = function()
+{
+	return (!isNaN(this.scale) && isFinite(this.scale) && this.scale > 0 &&
+			this.bounds != null && !isNaN(this.bounds.x) && !isNaN(this.bounds.y) &&
+			!isNaN(this.bounds.width) && !isNaN(this.bounds.height) &&
+			this.bounds.width > 0 && this.bounds.height > 0);
+};
+
+/**
+ * Function: createVmlGroup
+ *
+ * Returns the temporary element used for rendering in IE8 standards mode.
+ */
+mxShape.prototype.createVmlGroup = function()
+{
+	var node = document.createElement(mxClient.VML_PREFIX + ':group');
+	node.style.position = 'absolute';
+	node.style.width = this.node.style.width;
+	node.style.height = this.node.style.height;
+	
+	return node;
+};
+
+/**
+ * Function: redrawShape
+ *
+ * Updates the SVG or VML shape.
+ */
+mxShape.prototype.redrawShape = function()
+{
+	var canvas = this.createCanvas();
+	
+	if (canvas != null)
+	{
+		// Specifies if events should be handled
+		canvas.pointerEvents = this.pointerEvents;
+	
+		this.paint(canvas);
+	
+		if (this.node != canvas.root)
+		{
+			// Forces parsing in IE8 standards mode - slow! avoid
+			this.node.insertAdjacentHTML('beforeend', canvas.root.outerHTML);
+		}
+	
+		if (this.node.nodeName == 'DIV' && document.documentMode == 8)
+		{
+			// Makes DIV transparent to events for IE8 in IE8 standards
+			// mode (Note: Does not work for IE9 in IE8 standards mode
+			// and not for IE11 in enterprise mode)
+			this.node.style.filter = '';
+			
+			// Adds event transparency in IE8 standards
+			mxUtils.addTransparentBackgroundFilter(this.node);
+		}
+		
+		this.destroyCanvas(canvas);
+	}
+};
+
+/**
+ * Function: createCanvas
+ * 
+ * Creates a new canvas for drawing this shape. May return null.
+ */
+mxShape.prototype.createCanvas = function()
+{
+	var canvas = null;
+	
+	// LATER: Check if reusing existing DOM nodes improves performance
+	if (this.node.ownerSVGElement != null)
+	{
+		canvas = this.createSvgCanvas();
+	}
+	else if (mxClient.IS_VML)
+	{
+		this.updateVmlContainer();
+		canvas = this.createVmlCanvas();
+	}
+	
+	if (canvas != null && this.outline)
+	{
+		canvas.setStrokeWidth(this.strokewidth);
+		canvas.setStrokeColor(this.stroke);
+		
+		if (this.isDashed != null)
+		{
+			canvas.setDashed(this.isDashed);
+		}
+		
+		canvas.setStrokeWidth = function() {};
+		canvas.setStrokeColor = function() {};
+		canvas.setFillColor = function() {};
+		canvas.setGradient = function() {};
+		canvas.setDashed = function() {};
+	}
+
+	return canvas;
+};
+
+/**
+ * Function: createSvgCanvas
+ * 
+ * Creates and returns an <mxSvgCanvas2D> for rendering this shape.
+ */
+mxShape.prototype.createSvgCanvas = function()
+{
+	var canvas = new mxSvgCanvas2D(this.node, false);
+	canvas.strokeTolerance = (this.pointerEvents) ? this.svgStrokeTolerance : 0;
+	canvas.pointerEventsValue = this.svgPointerEvents;
+	canvas.blockImagePointerEvents = mxClient.IS_FF;
+	var off = this.getSvgScreenOffset();
+
+	if (off != 0)
+	{
+		this.node.setAttribute('transform', 'translate(' + off + ',' + off + ')');
+	}
+	else
+	{
+		this.node.removeAttribute('transform');
+	}
+	
+	if (!this.antiAlias)
+	{
+		// Rounds all numbers in the SVG output to integers
+		canvas.format = function(value)
+		{
+			return Math.round(parseFloat(value));
+		};
+	}
+	
+	return canvas;
+};
+
+/**
+ * Function: createVmlCanvas
+ * 
+ * Creates and returns an <mxVmlCanvas2D> for rendering this shape.
+ */
+mxShape.prototype.createVmlCanvas = function()
+{
+	// Workaround for VML rendering bug in IE8 standards mode
+	var node = (document.documentMode == 8 && this.isParseVml()) ? this.createVmlGroup() : this.node;
+	var canvas = new mxVmlCanvas2D(node, false);
+	
+	if (node.tagUrn != '')
+	{
+		var w = Math.max(1, Math.round(this.bounds.width));
+		var h = Math.max(1, Math.round(this.bounds.height));
+		node.coordsize = (w * this.vmlScale) + ',' + (h * this.vmlScale);
+		canvas.scale(this.vmlScale);
+		canvas.vmlScale = this.vmlScale;
+	}
+
+	// Painting relative to top, left shape corner
+	var s = this.scale;
+	canvas.translate(-Math.round(this.bounds.x / s), -Math.round(this.bounds.y / s));
+	
+	return canvas;
+};
+
+/**
+ * Function: updateVmlContainer
+ * 
+ * Updates the bounds of the VML container.
+ */
+mxShape.prototype.updateVmlContainer = function()
+{
+	this.node.style.left = Math.round(this.bounds.x) + 'px';
+	this.node.style.top = Math.round(this.bounds.y) + 'px';
+	var w = Math.max(1, Math.round(this.bounds.width));
+	var h = Math.max(1, Math.round(this.bounds.height));
+	this.node.style.width = w + 'px';
+	this.node.style.height = h + 'px';
+	this.node.style.overflow = 'visible';
+};
+
+/**
+ * Function: redrawHtml
+ *
+ * Allow optimization by replacing VML with HTML.
+ */
+mxShape.prototype.redrawHtmlShape = function()
+{
+	// LATER: Refactor methods
+	this.updateHtmlBounds(this.node);
+	this.updateHtmlFilters(this.node);
+	this.updateHtmlColors(this.node);
+};
+
+/**
+ * Function: updateHtmlFilters
+ *
+ * Allow optimization by replacing VML with HTML.
+ */
+mxShape.prototype.updateHtmlFilters = function(node)
+{
+	var f = '';
+	
+	if (this.opacity < 100)
+	{
+		f += 'alpha(opacity=' + (this.opacity) + ')';
+	}
+	
+	if (this.isShadow)
+	{
+		// FIXME: Cannot implement shadow transparency with filter
+		f += 'progid:DXImageTransform.Microsoft.dropShadow (' +
+			'OffX=\'' + Math.round(mxConstants.SHADOW_OFFSET_X * this.scale) + '\', ' +
+			'OffY=\'' + Math.round(mxConstants.SHADOW_OFFSET_Y * this.scale) + '\', ' +
+			'Color=\'' + mxConstants.VML_SHADOWCOLOR + '\')';
+	}
+	
+	if (this.fill != null && this.fill != mxConstants.NONE && this.gradient && this.gradient != mxConstants.NONE)
+	{
+		var start = this.fill;
+		var end = this.gradient;
+		var type = '0';
+		
+		var lookup = {east:0,south:1,west:2,north:3};
+		var dir = (this.direction != null) ? lookup[this.direction] : 0;
+		
+		if (this.gradientDirection != null)
+		{
+			dir = mxUtils.mod(dir + lookup[this.gradientDirection] - 1, 4);
+		}
+
+		if (dir == 1)
+		{
+			type = '1';
+			var tmp = start;
+			start = end;
+			end = tmp;
+		}
+		else if (dir == 2)
+		{
+			var tmp = start;
+			start = end;
+			end = tmp;
+		}
+		else if (dir == 3)
+		{
+			type = '1';
+		}
+		
+		f += 'progid:DXImageTransform.Microsoft.gradient(' +
+			'startColorStr=\'' + start + '\', endColorStr=\'' + end +
+			'\', gradientType=\'' + type + '\')';
+	}
+
+	node.style.filter = f;
+};
+
+/**
+ * Function: mixedModeHtml
+ *
+ * Allow optimization by replacing VML with HTML.
+ */
+mxShape.prototype.updateHtmlColors = function(node)
+{
+	var color = this.stroke;
+	
+	if (color != null && color != mxConstants.NONE)
+	{
+		node.style.borderColor = color;
+
+		if (this.isDashed)
+		{
+			node.style.borderStyle = 'dashed';
+		}
+		else if (this.strokewidth > 0)
+		{
+			node.style.borderStyle = 'solid';
+		}
+
+		node.style.borderWidth = Math.max(1, Math.ceil(this.strokewidth * this.scale)) + 'px';
+	}
+	else
+	{
+		node.style.borderWidth = '0px';
+	}
+
+	color = (this.outline) ? null : this.fill;
+	
+	if (color != null && color != mxConstants.NONE)
+	{
+		node.style.backgroundColor = color;
+		node.style.backgroundImage = 'none';
+	}
+	else if (this.pointerEvents)
+	{
+		 node.style.backgroundColor = 'transparent';
+	}
+	else if (document.documentMode == 8)
+	{
+		mxUtils.addTransparentBackgroundFilter(node);
+	}
+	else
+	{
+		this.setTransparentBackgroundImage(node);
+	}
+};
+
+/**
+ * Function: mixedModeHtml
+ *
+ * Allow optimization by replacing VML with HTML.
+ */
+mxShape.prototype.updateHtmlBounds = function(node)
+{
+	var sw = (document.documentMode >= 9) ? 0 : Math.ceil(this.strokewidth * this.scale);
+	node.style.borderWidth = Math.max(1, sw) + 'px';
+	node.style.overflow = 'hidden';
+	
+	node.style.left = Math.round(this.bounds.x - sw / 2) + 'px';
+	node.style.top = Math.round(this.bounds.y - sw / 2) + 'px';
+
+	if (document.compatMode == 'CSS1Compat')
+	{
+		sw = -sw;
+	}
+	
+	node.style.width = Math.round(Math.max(0, this.bounds.width + sw)) + 'px';
+	node.style.height = Math.round(Math.max(0, this.bounds.height + sw)) + 'px';
+};
+
+/**
+ * Function: destroyCanvas
+ * 
+ * Destroys the given canvas which was used for drawing. This implementation
+ * increments the reference counts on all shared gradients used in the canvas.
+ */
+mxShape.prototype.destroyCanvas = function(canvas)
+{
+	// Manages reference counts
+	if (canvas instanceof mxSvgCanvas2D)
+	{
+		// Increments ref counts
+		for (var key in canvas.gradients)
+		{
+			var gradient = canvas.gradients[key];
+			
+			if (gradient != null)
+			{
+				gradient.mxRefCount = (gradient.mxRefCount || 0) + 1;
+			}
+		}
+		
+		this.releaseSvgGradients(this.oldGradients);
+		this.oldGradients = canvas.gradients;
+	}
+};
+
+/**
+ * Function: paint
+ * 
+ * Generic rendering code.
+ */
+mxShape.prototype.paint = function(c)
+{
+	// Scale is passed-through to canvas
+	var s = this.scale;
+	var x = this.bounds.x / s;
+	var y = this.bounds.y / s;
+	var w = this.bounds.width / s;
+	var h = this.bounds.height / s;
+
+	if (this.isPaintBoundsInverted())
+	{
+		var t = (w - h) / 2;
+		x += t;
+		y -= t;
+		var tmp = w;
+		w = h;
+		h = tmp;
+	}
+	
+	this.updateTransform(c, x, y, w, h);
+	this.configureCanvas(c, x, y, w, h);
+
+	// Adds background rectangle to capture events
+	var bg = null;
+	
+	if ((this.stencil == null && this.points == null && this.shapePointerEvents) ||
+		(this.stencil != null && this.stencilPointerEvents))
+	{
+		var bb = this.createBoundingBox();
+		
+		if (this.dialect == mxConstants.DIALECT_SVG)
+		{
+			bg = this.createTransparentSvgRectangle(bb.x, bb.y, bb.width, bb.height);
+			this.node.appendChild(bg);
+		}
+		else
+		{
+			var rect = c.createRect('rect', bb.x / s, bb.y / s, bb.width / s, bb.height / s);
+			rect.appendChild(c.createTransparentFill());
+			rect.stroked = 'false';
+			c.root.appendChild(rect);
+		}
+	}
+
+	if (this.stencil != null)
+	{
+		this.stencil.drawShape(c, this, x, y, w, h);
+	}
+	else
+	{
+		// Stencils have separate strokewidth
+		c.setStrokeWidth(this.strokewidth);
+		
+		if (this.points != null)
+		{
+			// Paints edge shape
+			var pts = [];
+			
+			for (var i = 0; i < this.points.length; i++)
+			{
+				if (this.points[i] != null)
+				{
+					pts.push(new mxPoint(this.points[i].x / s, this.points[i].y / s));
+				}
+			}
+
+			this.paintEdgeShape(c, pts);
+		}
+		else
+		{
+			// Paints vertex shape
+			this.paintVertexShape(c, x, y, w, h);
+		}
+	}
+	
+	if (bg != null && c.state != null && c.state.transform != null)
+	{
+		bg.setAttribute('transform', c.state.transform);
+	}
+};
+
+/**
+ * Function: configureCanvas
+ * 
+ * Sets the state of the canvas for drawing the shape.
+ */
+mxShape.prototype.configureCanvas = function(c, x, y, w, h)
+{
+	var dash = null;
+	
+	if (this.style != null)
+	{
+		dash = this.style['dashPattern'];		
+	}
+
+	c.setAlpha(this.opacity / 100);
+	c.setFillAlpha(this.fillOpacity / 100);
+	c.setStrokeAlpha(this.strokeOpacity / 100);
+
+	// Sets alpha, colors and gradients
+	if (this.isShadow != null)
+	{
+		c.setShadow(this.isShadow);
+	}
+	
+	// Dash pattern
+	if (this.isDashed != null)
+	{
+		c.setDashed(this.isDashed, (this.style != null) ?
+			mxUtils.getValue(this.style, mxConstants.STYLE_FIX_DASH, false) == 1 : false);
+	}
+
+	if (dash != null)
+	{
+		c.setDashPattern(dash);
+	}
+
+	if (this.fill != null && this.fill != mxConstants.NONE && this.gradient && this.gradient != mxConstants.NONE)
+	{
+		var b = this.getGradientBounds(c, x, y, w, h);
+		c.setGradient(this.fill, this.gradient, b.x, b.y, b.width, b.height, this.gradientDirection);
+	}
+	else
+	{
+		c.setFillColor(this.fill);
+	}
+
+	c.setStrokeColor(this.stroke);
+};
+
+/**
+ * Function: getGradientBounds
+ * 
+ * Returns the bounding box for the gradient box for this shape.
+ */
+mxShape.prototype.getGradientBounds = function(c, x, y, w, h)
+{
+	return new mxRectangle(x, y, w, h);
+};
+
+/**
+ * Function: updateTransform
+ * 
+ * Sets the scale and rotation on the given canvas.
+ */
+mxShape.prototype.updateTransform = function(c, x, y, w, h)
+{
+	// NOTE: Currently, scale is implemented in state and canvas. This will
+	// move to canvas in a later version, so that the states are unscaled
+	// and untranslated and do not need an update after zooming or panning.
+	c.scale(this.scale);
+	c.rotate(this.getShapeRotation(), this.flipH, this.flipV, x + w / 2, y + h / 2);
+};
+
+/**
+ * Function: paintVertexShape
+ * 
+ * Paints the vertex shape.
+ */
+mxShape.prototype.paintVertexShape = function(c, x, y, w, h)
+{
+	this.paintBackground(c, x, y, w, h);
+	c.setShadow(false);
+	this.paintForeground(c, x, y, w, h);
+};
+
+/**
+ * Function: paintBackground
+ * 
+ * Hook for subclassers. This implementation is empty.
+ */
+mxShape.prototype.paintBackground = function(c, x, y, w, h) { };
+
+/**
+ * Function: paintForeground
+ * 
+ * Hook for subclassers. This implementation is empty.
+ */
+mxShape.prototype.paintForeground = function(c, x, y, w, h) { };
+
+/**
+ * Function: paintEdgeShape
+ * 
+ * Hook for subclassers. This implementation is empty.
+ */
+mxShape.prototype.paintEdgeShape = function(c, pts) { };
+
+/**
+ * Function: getArcSize
+ * 
+ * Returns the arc size for the given dimension.
+ */
+mxShape.prototype.getArcSize = function(w, h)
+{
+	var r = 0;
+	
+	if (mxUtils.getValue(this.style, mxConstants.STYLE_ABSOLUTE_ARCSIZE, 0) == '1')
+	{
+		r = Math.min(w / 2, Math.min(h / 2, mxUtils.getValue(this.style,
+			mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2));
+	}
+	else
+	{
+		var f = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE,
+			mxConstants.RECTANGLE_ROUNDING_FACTOR * 100) / 100;
+		r = Math.min(w * f, h * f);
+	}
+	
+	return r;
+};
+
+/**
+ * Function: paintGlassEffect
+ * 
+ * Paints the glass gradient effect.
+ */
+mxShape.prototype.paintGlassEffect = function(c, x, y, w, h, arc)
+{
+	var sw = Math.ceil(this.strokewidth / 2);
+	var size = 0.4;
+	
+	c.setGradient('#ffffff', '#ffffff', x, y, w, h * 0.6, 'south', 0.9, 0.1);
+	c.begin();
+	arc += 2 * sw;
+		
+	if (this.isRounded)
+	{
+		c.moveTo(x - sw + arc, y - sw);
+		c.quadTo(x - sw, y - sw, x - sw, y - sw + arc);
+		c.lineTo(x - sw, y + h * size);
+		c.quadTo(x + w * 0.5, y + h * 0.7, x + w + sw, y + h * size);
+		c.lineTo(x + w + sw, y - sw + arc);
+		c.quadTo(x + w + sw, y - sw, x + w + sw - arc, y - sw);
+	}
+	else
+	{
+		c.moveTo(x - sw, y - sw);
+		c.lineTo(x - sw, y + h * size);
+		c.quadTo(x + w * 0.5, y + h * 0.7, x + w + sw, y + h * size);
+		c.lineTo(x + w + sw, y - sw);
+	}
+	
+	c.close();
+	c.fill();
+};
+
+/**
+ * Function: addPoints
+ * 
+ * Paints the given points with rounded corners.
+ */
+mxShape.prototype.addPoints = function(c, pts, rounded, arcSize, close, exclude, initialMove)
+{
+	if (pts != null && pts.length > 0)
+	{
+		initialMove = (initialMove != null) ? initialMove : true;
+		var pe = pts[pts.length - 1];
+		
+		// Adds virtual waypoint in the center between start and end point
+		if (close && rounded)
+		{
+			pts = pts.slice();
+			var p0 = pts[0];
+			var wp = new mxPoint(pe.x + (p0.x - pe.x) / 2, pe.y + (p0.y - pe.y) / 2);
+			pts.splice(0, 0, wp);
+		}
+	
+		var pt = pts[0];
+		var i = 1;
+	
+		// Draws the line segments
+		if (initialMove)
+		{
+			c.moveTo(pt.x, pt.y);
+		}
+		else
+		{
+			c.lineTo(pt.x, pt.y);
+		}
+		
+		while (i < ((close) ? pts.length : pts.length - 1))
+		{
+			var tmp = pts[mxUtils.mod(i, pts.length)];
+			var dx = pt.x - tmp.x;
+			var dy = pt.y - tmp.y;
+	
+			if (rounded && (dx != 0 || dy != 0) && (exclude == null || mxUtils.indexOf(exclude, i - 1) < 0))
+			{
+				// Draws a line from the last point to the current
+				// point with a spacing of size off the current point
+				// into direction of the last point
+				var dist = Math.sqrt(dx * dx + dy * dy);
+				var nx1 = dx * Math.min(arcSize, dist / 2) / dist;
+				var ny1 = dy * Math.min(arcSize, dist / 2) / dist;
+	
+				var x1 = tmp.x + nx1;
+				var y1 = tmp.y + ny1;
+				c.lineTo(x1, y1);
+	
+				// Draws a curve from the last point to the current
+				// point with a spacing of size off the current point
+				// into direction of the next point
+				var next = pts[mxUtils.mod(i + 1, pts.length)];
+				
+				// Uses next non-overlapping point
+				while (i < pts.length - 2 && Math.round(next.x - tmp.x) == 0 && Math.round(next.y - tmp.y) == 0)
+				{
+					next = pts[mxUtils.mod(i + 2, pts.length)];
+					i++;
+				}
+				
+				dx = next.x - tmp.x;
+				dy = next.y - tmp.y;
+	
+				dist = Math.max(1, Math.sqrt(dx * dx + dy * dy));
+				var nx2 = dx * Math.min(arcSize, dist / 2) / dist;
+				var ny2 = dy * Math.min(arcSize, dist / 2) / dist;
+	
+				var x2 = tmp.x + nx2;
+				var y2 = tmp.y + ny2;
+	
+				c.quadTo(tmp.x, tmp.y, x2, y2);
+				tmp = new mxPoint(x2, y2);
+			}
+			else
+			{
+				c.lineTo(tmp.x, tmp.y);
+			}
+	
+			pt = tmp;
+			i++;
+		}
+	
+		if (close)
+		{
+			c.close();
+		}
+		else
+		{
+			c.lineTo(pe.x, pe.y);
+		}
+	}
+};
+
+/**
+ * Function: resetStyles
+ * 
+ * Resets all styles.
+ */
+mxShape.prototype.resetStyles = function()
+{
+	this.initStyles();
+
+	this.spacing = 0;
+	
+	delete this.fill;
+	delete this.gradient;
+	delete this.gradientDirection;
+	delete this.stroke;
+	delete this.startSize;
+	delete this.endSize;
+	delete this.startArrow;
+	delete this.endArrow;
+	delete this.direction;
+	delete this.isShadow;
+	delete this.isDashed;
+	delete this.isRounded;
+	delete this.glass;
+};
+
+/**
+ * Function: apply
+ * 
+ * Applies the style of the given <mxCellState> to the shape. This
+ * implementation assigns the following styles to local fields:
+ * 
+ * - <mxConstants.STYLE_FILLCOLOR> => fill
+ * - <mxConstants.STYLE_GRADIENTCOLOR> => gradient
+ * - <mxConstants.STYLE_GRADIENT_DIRECTION> => gradientDirection
+ * - <mxConstants.STYLE_OPACITY> => opacity
+ * - <mxConstants.STYLE_FILL_OPACITY> => fillOpacity
+ * - <mxConstants.STYLE_STROKE_OPACITY> => strokeOpacity
+ * - <mxConstants.STYLE_STROKECOLOR> => stroke
+ * - <mxConstants.STYLE_STROKEWIDTH> => strokewidth
+ * - <mxConstants.STYLE_SHADOW> => isShadow
+ * - <mxConstants.STYLE_DASHED> => isDashed
+ * - <mxConstants.STYLE_SPACING> => spacing
+ * - <mxConstants.STYLE_STARTSIZE> => startSize
+ * - <mxConstants.STYLE_ENDSIZE> => endSize
+ * - <mxConstants.STYLE_ROUNDED> => isRounded
+ * - <mxConstants.STYLE_STARTARROW> => startArrow
+ * - <mxConstants.STYLE_ENDARROW> => endArrow
+ * - <mxConstants.STYLE_ROTATION> => rotation
+ * - <mxConstants.STYLE_DIRECTION> => direction
+ * - <mxConstants.STYLE_GLASS> => glass
+ *
+ * This keeps a reference to the <style>. If you need to keep a reference to
+ * the cell, you can override this method and store a local reference to
+ * state.cell or the <mxCellState> itself. If <outline> should be true, make
+ * sure to set it before calling this method.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> of the corresponding cell.
+ */
+mxShape.prototype.apply = function(state)
+{
+	this.state = state;
+	this.style = state.style;
+
+	if (this.style != null)
+	{
+		this.fill = mxUtils.getValue(this.style, mxConstants.STYLE_FILLCOLOR, this.fill);
+		this.gradient = mxUtils.getValue(this.style, mxConstants.STYLE_GRADIENTCOLOR, this.gradient);
+		this.gradientDirection = mxUtils.getValue(this.style, mxConstants.STYLE_GRADIENT_DIRECTION, this.gradientDirection);
+		this.opacity = mxUtils.getValue(this.style, mxConstants.STYLE_OPACITY, this.opacity);
+		this.fillOpacity = mxUtils.getValue(this.style, mxConstants.STYLE_FILL_OPACITY, this.fillOpacity);
+		this.strokeOpacity = mxUtils.getValue(this.style, mxConstants.STYLE_STROKE_OPACITY, this.strokeOpacity);
+		this.stroke = mxUtils.getValue(this.style, mxConstants.STYLE_STROKECOLOR, this.stroke);
+		this.strokewidth = mxUtils.getNumber(this.style, mxConstants.STYLE_STROKEWIDTH, this.strokewidth);
+		this.spacing = mxUtils.getValue(this.style, mxConstants.STYLE_SPACING, this.spacing);
+		this.startSize = mxUtils.getNumber(this.style, mxConstants.STYLE_STARTSIZE, this.startSize);
+		this.endSize = mxUtils.getNumber(this.style, mxConstants.STYLE_ENDSIZE, this.endSize);
+		this.startArrow = mxUtils.getValue(this.style, mxConstants.STYLE_STARTARROW, this.startArrow);
+		this.endArrow = mxUtils.getValue(this.style, mxConstants.STYLE_ENDARROW, this.endArrow);
+		this.rotation = mxUtils.getValue(this.style, mxConstants.STYLE_ROTATION, this.rotation);
+		this.direction = mxUtils.getValue(this.style, mxConstants.STYLE_DIRECTION, this.direction);
+		this.flipH = mxUtils.getValue(this.style, mxConstants.STYLE_FLIPH, 0) == 1;
+		this.flipV = mxUtils.getValue(this.style, mxConstants.STYLE_FLIPV, 0) == 1;	
+		
+		// Legacy support for stencilFlipH/V
+		if (this.stencil != null)
+		{
+			this.flipH = mxUtils.getValue(this.style, 'stencilFlipH', 0) == 1 || this.flipH;
+			this.flipV = mxUtils.getValue(this.style, 'stencilFlipV', 0) == 1 || this.flipV;
+		}
+		
+		if (this.direction == mxConstants.DIRECTION_NORTH || this.direction == mxConstants.DIRECTION_SOUTH)
+		{
+			var tmp = this.flipH;
+			this.flipH = this.flipV;
+			this.flipV = tmp;
+		}
+
+		this.isShadow = mxUtils.getValue(this.style, mxConstants.STYLE_SHADOW, this.isShadow) == 1;
+		this.isDashed = mxUtils.getValue(this.style, mxConstants.STYLE_DASHED, this.isDashed) == 1;
+		this.isRounded = mxUtils.getValue(this.style, mxConstants.STYLE_ROUNDED, this.isRounded) == 1;
+		this.glass = mxUtils.getValue(this.style, mxConstants.STYLE_GLASS, this.glass) == 1;
+		
+		if (this.fill == mxConstants.NONE)
+		{
+			this.fill = null;
+		}
+
+		if (this.gradient == mxConstants.NONE)
+		{
+			this.gradient = null;
+		}
+
+		if (this.stroke == mxConstants.NONE)
+		{
+			this.stroke = null;
+		}
+	}
+};
+
+/**
+ * Function: setCursor
+ * 
+ * Sets the cursor on the given shape.
+ *
+ * Parameters:
+ *
+ * cursor - The cursor to be used.
+ */
+mxShape.prototype.setCursor = function(cursor)
+{
+	if (cursor == null)
+	{
+		cursor = '';
+	}
+	
+	this.cursor = cursor;
+
+	if (this.node != null)
+	{
+		this.node.style.cursor = cursor;
+	}
+};
+
+/**
+ * Function: getCursor
+ * 
+ * Returns the current cursor.
+ */
+mxShape.prototype.getCursor = function()
+{
+	return this.cursor;
+};
+
+/**
+ * Function: updateBoundingBox
+ *
+ * Updates the <boundingBox> for this shape using <createBoundingBox> and
+ * <augmentBoundingBox> and stores the result in <boundingBox>.
+ */
+mxShape.prototype.updateBoundingBox = function()
+{
+	// Tries to get bounding box from SVG subsystem
+	// LATER: Use getBoundingClientRect for fallback in VML
+	if (this.useSvgBoundingBox && this.node != null && this.node.ownerSVGElement != null)
+	{
+		try
+		{
+			var b = this.node.getBBox();
+	
+			if (b.width > 0 && b.height > 0)
+			{
+				this.boundingBox = new mxRectangle(b.x, b.y, b.width, b.height);
+				
+				// Adds strokeWidth
+				this.boundingBox.grow(this.strokewidth * this.scale / 2);
+				
+				return;
+			}
+		}
+		catch(e)
+		{
+			// fallback to code below
+		}
+	}
+
+	if (this.bounds != null)
+	{
+		var bbox = this.createBoundingBox();
+		
+		if (bbox != null)
+		{
+			this.augmentBoundingBox(bbox);
+			var rot = this.getShapeRotation();
+			
+			if (rot != 0)
+			{
+				bbox = mxUtils.getBoundingBox(bbox, rot);
+			}
+		}
+
+		this.boundingBox = bbox;
+	}
+};
+
+/**
+ * Function: createBoundingBox
+ *
+ * Returns a new rectangle that represents the bounding box of the bare shape
+ * with no shadows or strokewidths.
+ */
+mxShape.prototype.createBoundingBox = function()
+{
+	var bb = this.bounds.clone();
+
+	if ((this.stencil != null && (this.direction == mxConstants.DIRECTION_NORTH ||
+		this.direction == mxConstants.DIRECTION_SOUTH)) || this.isPaintBoundsInverted())
+	{
+		bb.rotate90();
+	}
+	
+	return bb;
+};
+
+/**
+ * Function: augmentBoundingBox
+ *
+ * Augments the bounding box with the strokewidth and shadow offsets.
+ */
+mxShape.prototype.augmentBoundingBox = function(bbox)
+{
+	if (this.isShadow)
+	{
+		bbox.width += Math.ceil(mxConstants.SHADOW_OFFSET_X * this.scale);
+		bbox.height += Math.ceil(mxConstants.SHADOW_OFFSET_Y * this.scale);
+	}
+	
+	// Adds strokeWidth
+	bbox.grow(this.strokewidth * this.scale / 2);
+};
+
+/**
+ * Function: isPaintBoundsInverted
+ * 
+ * Returns true if the bounds should be inverted.
+ */
+mxShape.prototype.isPaintBoundsInverted = function()
+{
+	// Stencil implements inversion via aspect
+	return this.stencil == null && (this.direction == mxConstants.DIRECTION_NORTH ||
+			this.direction == mxConstants.DIRECTION_SOUTH);
+};
+
+/**
+ * Function: getRotation
+ * 
+ * Returns the rotation from the style.
+ */
+mxShape.prototype.getRotation = function()
+{
+	return (this.rotation != null) ? this.rotation : 0;
+};
+
+/**
+ * Function: getTextRotation
+ * 
+ * Returns the rotation for the text label.
+ */
+mxShape.prototype.getTextRotation = function()
+{
+	var rot = this.getRotation();
+	
+	if (mxUtils.getValue(this.style, mxConstants.STYLE_HORIZONTAL, 1) != 1)
+	{
+		rot += mxText.prototype.verticalTextRotation;
+	}
+	
+	return rot;
+};
+
+/**
+ * Function: getShapeRotation
+ * 
+ * Returns the actual rotation of the shape.
+ */
+mxShape.prototype.getShapeRotation = function()
+{
+	var rot = this.getRotation();
+	
+	if (this.direction != null)
+	{
+		if (this.direction == mxConstants.DIRECTION_NORTH)
+		{
+			rot += 270;
+		}
+		else if (this.direction == mxConstants.DIRECTION_WEST)
+		{
+			rot += 180;
+		}
+		else if (this.direction == mxConstants.DIRECTION_SOUTH)
+		{
+			rot += 90;
+		}
+	}
+	
+	return rot;
+};
+
+/**
+ * Function: createTransparentSvgRectangle
+ * 
+ * Adds a transparent rectangle that catches all events.
+ */
+mxShape.prototype.createTransparentSvgRectangle = function(x, y, w, h)
+{
+	var rect = document.createElementNS(mxConstants.NS_SVG, 'rect');
+	rect.setAttribute('x', x);
+	rect.setAttribute('y', y);
+	rect.setAttribute('width', w);
+	rect.setAttribute('height', h);
+	rect.setAttribute('fill', 'none');
+	rect.setAttribute('stroke', 'none');
+	rect.setAttribute('pointer-events', 'all');
+	
+	return rect;
+};
+
+/**
+ * Function: setTransparentBackgroundImage
+ * 
+ * Sets a transparent background CSS style to catch all events.
+ * 
+ * Paints the line shape.
+ */
+mxShape.prototype.setTransparentBackgroundImage = function(node)
+{
+	node.style.backgroundImage = 'url(\'' + mxClient.imageBasePath + '/transparent.gif\')';
+};
+
+/**
+ * Function: releaseSvgGradients
+ * 
+ * Paints the line shape.
+ */
+mxShape.prototype.releaseSvgGradients = function(grads)
+{
+	if (grads != null)
+	{
+		for (var key in grads)
+		{
+			var gradient = grads[key];
+			
+			if (gradient != null)
+			{
+				gradient.mxRefCount = (gradient.mxRefCount || 0) - 1;
+				
+				if (gradient.mxRefCount == 0 && gradient.parentNode != null)
+				{
+					gradient.parentNode.removeChild(gradient);
+				}
+			}
+		}
+	}
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the shape by removing it from the DOM and releasing the DOM
+ * node associated with the shape using <mxEvent.release>.
+ */
+mxShape.prototype.destroy = function()
+{
+	if (this.node != null)
+	{
+		mxEvent.release(this.node);
+		
+		if (this.node.parentNode != null)
+		{
+			this.node.parentNode.removeChild(this.node);
+		}
+		
+		this.node = null;
+	}
+	
+	// Decrements refCount and removes unused
+	this.releaseSvgGradients(this.oldGradients);
+	this.oldGradients = null;
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/shape/mxStencil.js b/airavata-kubernetes/web-console/src/assets/js/shape/mxStencil.js
new file mode 100644
index 0000000..3622cd8
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/shape/mxStencil.js
@@ -0,0 +1,761 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxStencil
+ *
+ * Implements a generic shape which is based on a XML node as a description.
+ * 
+ * shape:
+ * 
+ * The outer element is *shape*, that has attributes:
+ * 
+ * - "name", string, required. The stencil name that uniquely identifies the shape.
+ * - "w" and "h" are optional decimal view bounds. This defines your co-ordinate
+ * system for the graphics operations in the shape. The default is 100,100.
+ * - "aspect", optional string. Either "variable", the default, or "fixed". Fixed
+ * means always render the shape with the aspect ratio defined by the ratio w/h.
+ * Variable causes the ratio to match that of the geometry of the current vertex.
+ * - "strokewidth", optional string. Either an integer or the string "inherit".
+ * "inherit" indicates that the strokeWidth of the cell is only changed on scaling,
+ * not on resizing. Default is "1".
+ * If numeric values are used, the strokeWidth of the cell is changed on both
+ * scaling and resizing and the value defines the multiple that is applied to
+ * the width.
+ * 
+ * connections:
+ * 
+ * If you want to define specific fixed connection points on the shape use the
+ * *connections* element. Each *constraint* element within connections defines
+ * a fixed connection point on the shape. Constraints have attributes:
+ * 
+ * - "perimeter", required. 1 or 0. 0 sets the connection point where specified
+ * by x,y. 1 Causes the position of the connection point to be extrapolated from
+ * the center of the shape, through x,y to the point of intersection with the
+ * perimeter of the shape.
+ * - "x" and "y" are the position of the fixed point relative to the bounds of
+ * the shape. They can be automatically adjusted if perimeter=1. So, (0,0) is top
+ * left, (0.5,0.5) the center, (1,0.5) the center of the right hand edge of the
+ * bounds, etc. Values may be less than 0 or greater than 1 to be positioned
+ * outside of the shape.
+ * - "name", optional string. A unique identifier for the port on the shape.
+ * 
+ * background and foreground:
+ * 
+ * The path of the graphics drawing is split into two elements, *foreground* and
+ * *background*. The split is to define which part any shadow applied to the shape
+ * is derived from (the background). This, generally, means the background is the
+ * line tracing of the outside of the shape, but not always.
+ * 
+ * Any stroke, fill or fillstroke of a background must be the first element of the
+ * foreground element, they must not be used within *background*. If the background
+ * is empty, this is not required.
+ * 
+ * Because the background cannot have any fill or stroke, it can contain only one
+ * *path*, *rect*, *roundrect* or *ellipse* element (or none). It can also not
+ * include *image*, *text* or *include-shape*.
+ * 
+ * Note that the state, styling and drawing in mxGraph stencils is very close in
+ * design to that of HTML 5 canvas. Tutorials on this subject, if you're not
+ * familiar with the topic, will give a good high-level introduction to the
+ * concepts used.
+ * 
+ * State:
+ * 
+ * Rendering within the foreground and background elements has the concept of
+ * state. There are two types of operations other than state save/load, styling
+ * and drawing. The styling operations change the current state, so you can save
+ * the current state with <save/> and pull the last saved state from the state
+ * stack using <restore/>.
+ * 
+ * Styling:
+ * 
+ * The elements that change colors within the current state all take a hash
+ * prefixed hex color code ("#FFEA80").
+ * 
+ * - *strokecolor*, this sets the color that drawing paths will be rendered in
+ * when a stroke or fillstroke command is issued.
+ * - *fillcolor*, this sets the color that the inside of closed paths will be
+ * rendered in when a fill or fillstroke command is issued.
+ * - *fontcolor*, this sets the color that fonts are rendered in when text is drawn.
+ * 
+ * *alpha* defines the degree of transparency used between 1.0 for fully opaque
+ * and 0.0 for fully transparent.
+ * 
+ * *strokewidth* defines the integer thickness of drawing elements rendered by
+ * stroking. Use fixed="1" to apply the value as-is, without scaling.
+ * 
+ * *dashed* is "1" for dashing enabled and "0" for disabled.
+ * 
+ * When *dashed* is enabled the current dash pattern, defined by *dashpattern*,
+ * is used on strokes. dashpattern is a sequence of space separated "on, off"
+ * lengths that define what distance to paint the stroke for, then what distance
+ * to paint nothing for, repeat... The default is "3 3". You could define a more
+ * complex pattern with "5 3 2 6", for example. Generally, it makes sense to have
+ * an even number of elements in the dashpattern, but that's not required.
+ * 
+ * *linejoin*, *linecap* and *miterlimit* are best explained by the Mozilla page
+ * on Canvas styling (about halfway down). The values are all the same except we
+ * use "flat" for linecap, instead of Canvas' "butt".
+ * 
+ * For font styling there are.
+ * 
+ * - *fontsize*, an integer,
+ * - *fontstyle*, an ORed bit pattern of bold (1), italic (2) and underline (4),
+ * i.e bold underline is "5".
+ * - *fontfamily*, is a string defining the typeface to be used.
+ * 
+ * Drawing:
+ * 
+ * Most drawing is contained within a *path* element. Again, the graphic
+ * primitives are very similar to that of HTML 5 canvas.
+ * 
+ * - *move* to attributes required decimals (x,y).
+ * - *line* to attributes required decimals (x,y).
+ * - *quad* to required decimals (x2,y2) via control point required decimals
+ * (x1,y1).
+ * - *curve* to required decimals (x3,y3), via control points required decimals
+ * (x1,y1) and (x2,y2).
+ * - *arc*, this doesn't follow the HTML Canvas signatures, instead it's a copy
+ * of the SVG arc command. The SVG specification documentation gives the best
+ * description of its behaviors. The attributes are named identically, they are
+ * decimals and all required.
+ * - *close* ends the current subpath and causes an automatic straight line to
+ * be drawn from the current point to the initial point of the current subpath.
+ * 
+ * Complex drawing:
+ * 
+ * In addition to the graphics primitive operations there are non-primitive
+ * operations. These provide an easy method to draw some basic shapes.
+ * 
+ * - *rect*, attributes "x", "y", "w", "h", all required decimals
+ * - *roundrect*, attributes "x", "y", "w", "h", all required decimals. Also
+ * "arcsize" an optional decimal attribute defining how large, the corner curves
+ * are.
+ * - *ellipse*, attributes "x", "y", "w", "h", all required decimals.
+ * 
+ * Note that these 3 shapes and all paths must be followed by either a fill,
+ * stroke, or fillstroke.
+ * 
+ * Text:
+ * 
+ * *text* elements have the following attributes.
+ * 
+ * - "str", the text string to display, required.
+ * - "x" and "y", the decimal location (x,y) of the text element, required.
+ * - "align", the horizontal alignment of the text element, either "left",
+ * "center" or "right". Optional, default is "left".
+ * - "valign", the vertical alignment of the text element, either "top", "middle"
+ * or "bottom". Optional, default is "top".
+ * - "localized", 0 or 1, if 1 then the "str" actually contains a key to use to
+ * fetch the value out of mxResources. Optional, default is
+ * <mxStencil.defaultLocalized>.
+ * - "vertical", 0 or 1, if 1 the label is rendered vertically (rotated by 90
+ * degrees). Optional, default is 0.
+ * - "rotation", angle in degrees (0 to 360). The angle to rotate the text by.
+ * Optional, default is 0.
+ * - "align-shape", 0 or 1, if 0 ignore the rotation of the shape when setting
+ * the text rotation. Optional, default is 1.
+ * 
+ * If <allowEval> is true, then the text content of the this element can define
+ * a function which is invoked with the shape as the only argument and returns
+ * the value for the text element (ignored if the str attribute is not null).
+ * 
+ * Images:
+ * 
+ * *image* elements can either be external URLs, or data URIs, where supported
+ * (not in IE 7-). Attributes are:
+ * 
+ * - "src", required string. Either a data URI or URL.
+ * - "x", "y", required decimals. The (x,y) position of the image.
+ * - "w", "h", required decimals. The width and height of the image.
+ * - "flipH" and "flipV", optional 0 or 1. Whether to flip the image along the
+ * horizontal/vertical axis. Default is 0 for both.
+ * 
+ * If <allowEval> is true, then the text content of the this element can define
+ * a function which is invoked with the shape as the only argument and returns
+ * the value for the image source (ignored if the src attribute is not null).
+ * 
+ * Sub-shapes:
+ * 
+ * *include-shape* allow stencils to be rendered within the current stencil by
+ * referencing the sub-stencil by name. Attributes are:
+ * 
+ * - "name", required string. The unique shape name of the stencil.
+ * - "x", "y", "w", "h", required decimals. The (x,y) position of the sub-shape
+ * and its width and height.
+ * 
+ * Constructor: mxStencil
+ * 
+ * Constructs a new generic shape by setting <desc> to the given XML node and
+ * invoking <parseDescription> and <parseConstraints>.
+ * 
+ * Parameters:
+ * 
+ * desc - XML node that contains the stencil description.
+ */
+function mxStencil(desc)
+{
+	this.desc = desc;
+	this.parseDescription();
+	this.parseConstraints();
+};
+
+/**
+ * Variable: defaultLocalized
+ * 
+ * Static global variable that specifies the default value for the localized
+ * attribute of the text element. Default is false.
+ */
+mxStencil.defaultLocalized = false;
+
+/**
+ * Function: allowEval
+ * 
+ * Static global switch that specifies if the use of eval is allowed for
+ * evaluating text content and images. Default is false. Set this to true
+ * if stencils can not contain user input.
+ */
+mxStencil.allowEval = false;
+
+/**
+ * Variable: desc
+ *
+ * Holds the XML node with the stencil description.
+ */
+mxStencil.prototype.desc = null;
+
+/**
+ * Variable: constraints
+ * 
+ * Holds an array of <mxConnectionConstraints> as defined in the shape.
+ */
+mxStencil.prototype.constraints = null;
+
+/**
+ * Variable: aspect
+ *
+ * Holds the aspect of the shape. Default is 'auto'.
+ */
+mxStencil.prototype.aspect = null;
+
+/**
+ * Variable: w0
+ *
+ * Holds the width of the shape. Default is 100.
+ */
+mxStencil.prototype.w0 = null;
+
+/**
+ * Variable: h0
+ *
+ * Holds the height of the shape. Default is 100.
+ */
+mxStencil.prototype.h0 = null;
+
+/**
+ * Variable: bgNodes
+ *
+ * Holds the XML node with the stencil description.
+ */
+mxStencil.prototype.bgNode = null;
+
+/**
+ * Variable: fgNodes
+ *
+ * Holds the XML node with the stencil description.
+ */
+mxStencil.prototype.fgNode = null;
+
+/**
+ * Variable: strokewidth
+ *
+ * Holds the strokewidth direction from the description.
+ */
+mxStencil.prototype.strokewidth = null;
+
+/**
+ * Function: parseDescription
+ *
+ * Reads <w0>, <h0>, <aspect>, <bgNodes> and <fgNodes> from <desc>.
+ */
+mxStencil.prototype.parseDescription = function()
+{
+	// LATER: Preprocess nodes for faster painting
+	this.fgNode = this.desc.getElementsByTagName('foreground')[0];
+	this.bgNode = this.desc.getElementsByTagName('background')[0];
+	this.w0 = Number(this.desc.getAttribute('w') || 100);
+	this.h0 = Number(this.desc.getAttribute('h') || 100);
+	
+	// Possible values for aspect are: variable and fixed where
+	// variable means fill the available space and fixed means
+	// use w0 and h0 to compute the aspect.
+	var aspect = this.desc.getAttribute('aspect');
+	this.aspect = (aspect != null) ? aspect : 'variable';
+	
+	// Possible values for strokewidth are all numbers and "inherit"
+	// where the inherit means take the value from the style (ie. the
+	// user-defined stroke-width). Note that the strokewidth is scaled
+	// by the minimum scaling that is used to draw the shape (sx, sy).
+	var sw = this.desc.getAttribute('strokewidth');
+	this.strokewidth = (sw != null) ? sw : '1';
+};
+
+/**
+ * Function: parseConstraints
+ *
+ * Reads the constraints from <desc> into <constraints> using
+ * <parseConstraint>.
+ */
+mxStencil.prototype.parseConstraints = function()
+{
+	var conns = this.desc.getElementsByTagName('connections')[0];
+	
+	if (conns != null)
+	{
+		var tmp = mxUtils.getChildNodes(conns);
+		
+		if (tmp != null && tmp.length > 0)
+		{
+			this.constraints = [];
+			
+			for (var i = 0; i < tmp.length; i++)
+			{
+				this.constraints.push(this.parseConstraint(tmp[i]));
+			}
+		}
+	}
+};
+
+/**
+ * Function: parseConstraint
+ *
+ * Parses the given XML node and returns its <mxConnectionConstraint>.
+ */
+mxStencil.prototype.parseConstraint = function(node)
+{
+	var x = Number(node.getAttribute('x'));
+	var y = Number(node.getAttribute('y'));
+	var perimeter = node.getAttribute('perimeter') == '1';
+	var name = node.getAttribute('name');
+	
+	return new mxConnectionConstraint(new mxPoint(x, y), perimeter, name);
+};
+
+/**
+ * Function: evaluateTextAttribute
+ * 
+ * Gets the given attribute as a text. The return value from <evaluateAttribute>
+ * is used as a key to <mxResources.get> if the localized attribute in the text
+ * node is 1 or if <defaultLocalized> is true.
+ */
+mxStencil.prototype.evaluateTextAttribute = function(node, attribute, shape)
+{
+	var result = this.evaluateAttribute(node, attribute, shape);
+	var loc = node.getAttribute('localized');
+	
+	if ((mxStencil.defaultLocalized && loc == null) || loc == '1')
+	{
+		result = mxResources.get(result);
+	}
+
+	return result;
+};
+
+/**
+ * Function: evaluateAttribute
+ *
+ * Gets the attribute for the given name from the given node. If the attribute
+ * does not exist then the text content of the node is evaluated and if it is
+ * a function it is invoked with <shape> as the only argument and the return
+ * value is used as the attribute value to be returned.
+ */
+mxStencil.prototype.evaluateAttribute = function(node, attribute, shape)
+{
+	var result = node.getAttribute(attribute);
+	
+	if (result == null)
+	{
+		var text = mxUtils.getTextContent(node);
+		
+		if (text != null && mxStencil.allowEval)
+		{
+			var funct = mxUtils.eval(text);
+			
+			if (typeof(funct) == 'function')
+			{
+				result = funct(shape);
+			}
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: drawShape
+ *
+ * Draws this stencil inside the given bounds.
+ */
+mxStencil.prototype.drawShape = function(canvas, shape, x, y, w, h)
+{
+	// TODO: Internal structure (array of special structs?), relative and absolute
+	// coordinates (eg. note shape, process vs star, actor etc.), text rendering
+	// and non-proportional scaling, how to implement pluggable edge shapes
+	// (start, segment, end blocks), pluggable markers, how to implement
+	// swimlanes (title area) with this API, add icon, horizontal/vertical
+	// label, indicator for all shapes, rotation
+	var direction = mxUtils.getValue(shape.style, mxConstants.STYLE_DIRECTION, null);
+	var aspect = this.computeAspect(shape.style, x, y, w, h, direction);
+	var minScale = Math.min(aspect.width, aspect.height);
+	var sw = (this.strokewidth == 'inherit') ?
+			Number(mxUtils.getNumber(shape.style, mxConstants.STYLE_STROKEWIDTH, 1)) :
+			Number(this.strokewidth) * minScale;
+	canvas.setStrokeWidth(sw);
+
+	this.drawChildren(canvas, shape, x, y, w, h, this.bgNode, aspect, false);
+	this.drawChildren(canvas, shape, x, y, w, h, this.fgNode, aspect, true);
+};
+
+/**
+ * Function: drawChildren
+ *
+ * Draws this stencil inside the given bounds.
+ */
+mxStencil.prototype.drawChildren = function(canvas, shape, x, y, w, h, node, aspect, disableShadow)
+{
+	if (node != null && w > 0 && h > 0)
+	{
+		var tmp = node.firstChild;
+		
+		while (tmp != null)
+		{
+			if (tmp.nodeType == mxConstants.NODETYPE_ELEMENT)
+			{
+				this.drawNode(canvas, shape, tmp, aspect, disableShadow);
+			}
+			
+			tmp = tmp.nextSibling;
+		}
+	}
+};
+
+/**
+ * Function: computeAspect
+ *
+ * Returns a rectangle that contains the offset in x and y and the horizontal
+ * and vertical scale in width and height used to draw this shape inside the
+ * given <mxRectangle>.
+ * 
+ * Parameters:
+ * 
+ * shape - <mxShape> to be drawn.
+ * bounds - <mxRectangle> that should contain the stencil.
+ * direction - Optional direction of the shape to be darwn.
+ */
+mxStencil.prototype.computeAspect = function(shape, x, y, w, h, direction)
+{
+	var x0 = x;
+	var y0 = y;
+	var sx = w / this.w0;
+	var sy = h / this.h0;
+	
+	var inverse = (direction == mxConstants.DIRECTION_NORTH || direction == mxConstants.DIRECTION_SOUTH);
+
+	if (inverse)
+	{
+		sy = w / this.h0;
+		sx = h / this.w0;
+		
+		var delta = (w - h) / 2;
+
+		x0 += delta;
+		y0 -= delta;
+	}
+
+	if (this.aspect == 'fixed')
+	{
+		sy = Math.min(sx, sy);
+		sx = sy;
+		
+		// Centers the shape inside the available space
+		if (inverse)
+		{
+			x0 += (h - this.w0 * sx) / 2;
+			y0 += (w - this.h0 * sy) / 2;
+		}
+		else
+		{
+			x0 += (w - this.w0 * sx) / 2;
+			y0 += (h - this.h0 * sy) / 2;
+		}
+	}
+
+	return new mxRectangle(x0, y0, sx, sy);
+};
+
+/**
+ * Function: drawNode
+ *
+ * Draws this stencil inside the given bounds.
+ */
+mxStencil.prototype.drawNode = function(canvas, shape, node, aspect, disableShadow)
+{
+	var name = node.nodeName;
+	var x0 = aspect.x;
+	var y0 = aspect.y;
+	var sx = aspect.width;
+	var sy = aspect.height;
+	var minScale = Math.min(sx, sy);
+
+	if (name == 'save')
+	{
+		canvas.save();
+	}
+	else if (name == 'restore')
+	{
+		canvas.restore();
+	}
+	else if (name == 'path')
+	{
+		canvas.begin();
+
+		// Renders the elements inside the given path
+		var childNode = node.firstChild;
+		
+		while (childNode != null)
+		{
+			if (childNode.nodeType == mxConstants.NODETYPE_ELEMENT)
+			{
+				this.drawNode(canvas, shape, childNode, aspect, disableShadow);
+			}
+			
+			childNode = childNode.nextSibling;
+		}
+	}
+	else if (name == 'close')
+	{
+		canvas.close();
+	}
+	else if (name == 'move')
+	{
+		canvas.moveTo(x0 + Number(node.getAttribute('x')) * sx, y0 + Number(node.getAttribute('y')) * sy);
+	}
+	else if (name == 'line')
+	{
+		canvas.lineTo(x0 + Number(node.getAttribute('x')) * sx, y0 + Number(node.getAttribute('y')) * sy);
+	}
+	else if (name == 'quad')
+	{
+		canvas.quadTo(x0 + Number(node.getAttribute('x1')) * sx,
+				y0 + Number(node.getAttribute('y1')) * sy,
+				x0 + Number(node.getAttribute('x2')) * sx,
+				y0 + Number(node.getAttribute('y2')) * sy);
+	}
+	else if (name == 'curve')
+	{
+		canvas.curveTo(x0 + Number(node.getAttribute('x1')) * sx,
+				y0 + Number(node.getAttribute('y1')) * sy,
+				x0 + Number(node.getAttribute('x2')) * sx,
+				y0 + Number(node.getAttribute('y2')) * sy,
+				x0 + Number(node.getAttribute('x3')) * sx,
+				y0 + Number(node.getAttribute('y3')) * sy);
+	}
+	else if (name == 'arc')
+	{
+		canvas.arcTo(Number(node.getAttribute('rx')) * sx,
+				Number(node.getAttribute('ry')) * sy,
+				Number(node.getAttribute('x-axis-rotation')),
+				Number(node.getAttribute('large-arc-flag')),
+				Number(node.getAttribute('sweep-flag')),
+				x0 + Number(node.getAttribute('x')) * sx,
+				y0 + Number(node.getAttribute('y')) * sy);
+	}
+	else if (name == 'rect')
+	{
+		canvas.rect(x0 + Number(node.getAttribute('x')) * sx,
+				y0 + Number(node.getAttribute('y')) * sy,
+				Number(node.getAttribute('w')) * sx,
+				Number(node.getAttribute('h')) * sy);
+	}
+	else if (name == 'roundrect')
+	{
+		var arcsize = Number(node.getAttribute('arcsize'));
+
+		if (arcsize == 0)
+		{
+			arcsize = mxConstants.RECTANGLE_ROUNDING_FACTOR * 100;
+		}
+		
+		var w = Number(node.getAttribute('w')) * sx;
+		var h = Number(node.getAttribute('h')) * sy;
+		var factor = Number(arcsize) / 100;
+		var r = Math.min(w * factor, h * factor);
+		
+		canvas.roundrect(x0 + Number(node.getAttribute('x')) * sx,
+				y0 + Number(node.getAttribute('y')) * sy,
+				w, h, r, r);
+	}
+	else if (name == 'ellipse')
+	{
+		canvas.ellipse(x0 + Number(node.getAttribute('x')) * sx,
+			y0 + Number(node.getAttribute('y')) * sy,
+			Number(node.getAttribute('w')) * sx,
+			Number(node.getAttribute('h')) * sy);
+	}
+	else if (name == 'image')
+	{
+		if (!shape.outline)
+		{
+			var src = this.evaluateAttribute(node, 'src', shape);
+			
+			canvas.image(x0 + Number(node.getAttribute('x')) * sx,
+				y0 + Number(node.getAttribute('y')) * sy,
+				Number(node.getAttribute('w')) * sx,
+				Number(node.getAttribute('h')) * sy,
+				src, false, node.getAttribute('flipH') == '1',
+				node.getAttribute('flipV') == '1');
+		}
+	}
+	else if (name == 'text')
+	{
+		if (!shape.outline)
+		{
+			var str = this.evaluateTextAttribute(node, 'str', shape);
+			var rotation = node.getAttribute('vertical') == '1' ? -90 : 0;
+			
+			if (node.getAttribute('align-shape') == '0')
+			{
+				var dr = shape.rotation;
+	
+				// Depends on flipping
+				var flipH = mxUtils.getValue(shape.style, mxConstants.STYLE_FLIPH, 0) == 1;
+				var flipV = mxUtils.getValue(shape.style, mxConstants.STYLE_FLIPV, 0) == 1;
+				
+				if (flipH && flipV)
+				{
+					rotation -= dr;
+				}
+				else if (flipH || flipV)
+				{
+					rotation += dr;
+				}
+				else
+				{
+					rotation -= dr;
+				}
+			}
+	
+			rotation -= node.getAttribute('rotation');
+	
+			canvas.text(x0 + Number(node.getAttribute('x')) * sx,
+					y0 + Number(node.getAttribute('y')) * sy,
+					0, 0, str, node.getAttribute('align') || 'left',
+					node.getAttribute('valign') || 'top', false, '',
+					null, false, rotation);
+		}
+	}
+	else if (name == 'include-shape')
+	{
+		var stencil = mxStencilRegistry.getStencil(node.getAttribute('name'));
+		
+		if (stencil != null)
+		{
+			var x = x0 + Number(node.getAttribute('x')) * sx;
+			var y = y0 + Number(node.getAttribute('y')) * sy;
+			var w = Number(node.getAttribute('w')) * sx;
+			var h = Number(node.getAttribute('h')) * sy;
+			
+			stencil.drawShape(canvas, shape, x, y, w, h);
+		}
+	}
+	else if (name == 'fillstroke')
+	{
+		canvas.fillAndStroke();
+	}
+	else if (name == 'fill')
+	{
+		canvas.fill();
+	}
+	else if (name == 'stroke')
+	{
+		canvas.stroke();
+	}
+	else if (name == 'strokewidth')
+	{
+		var s = (node.getAttribute('fixed') == '1') ? 1 : minScale;
+		canvas.setStrokeWidth(Number(node.getAttribute('width')) * s);
+	}
+	else if (name == 'dashed')
+	{
+		canvas.setDashed(node.getAttribute('dashed') == '1');
+	}
+	else if (name == 'dashpattern')
+	{
+		var value = node.getAttribute('pattern');
+		
+		if (value != null)
+		{
+			var tmp = value.split(' ');
+			var pat = [];
+			
+			for (var i = 0; i < tmp.length; i++)
+			{
+				if (tmp[i].length > 0)
+				{
+					pat.push(Number(tmp[i]) * minScale);
+				}
+			}
+			
+			value = pat.join(' ');
+			canvas.setDashPattern(value);
+		}
+	}
+	else if (name == 'strokecolor')
+	{
+		canvas.setStrokeColor(node.getAttribute('color'));
+	}
+	else if (name == 'linecap')
+	{
+		canvas.setLineCap(node.getAttribute('cap'));
+	}
+	else if (name == 'linejoin')
+	{
+		canvas.setLineJoin(node.getAttribute('join'));
+	}
+	else if (name == 'miterlimit')
+	{
+		canvas.setMiterLimit(Number(node.getAttribute('limit')));
+	}
+	else if (name == 'fillcolor')
+	{
+		canvas.setFillColor(node.getAttribute('color'));
+	}
+	else if (name == 'alpha')
+	{
+		canvas.setAlpha(node.getAttribute('alpha'));
+	}
+	else if (name == 'fontcolor')
+	{
+		canvas.setFontColor(node.getAttribute('color'));
+	}
+	else if (name == 'fontstyle')
+	{
+		canvas.setFontStyle(node.getAttribute('style'));
+	}
+	else if (name == 'fontfamily')
+	{
+		canvas.setFontFamily(node.getAttribute('family'));
+	}
+	else if (name == 'fontsize')
+	{
+		canvas.setFontSize(Number(node.getAttribute('size')) * minScale);
+	}
+	
+	if (disableShadow && (name == 'fillstroke' || name == 'fill' || name == 'stroke'))
+	{
+		disableShadow = false;
+		canvas.setShadow(false);
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/shape/mxStencilRegistry.js b/airavata-kubernetes/web-console/src/assets/js/shape/mxStencilRegistry.js
new file mode 100644
index 0000000..744275f
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/shape/mxStencilRegistry.js
@@ -0,0 +1,53 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ * 
+ * Code to add stencils.
+ * 
+ * (code)
+ * var req = mxUtils.load('test/stencils.xml');
+ * var root = req.getDocumentElement();
+ * var shape = root.firstChild;
+ * 
+ * while (shape != null)
+ * {
+ * 	 if (shape.nodeType == mxConstants.NODETYPE_ELEMENT)
+ *   {
+ *     mxStencilRegistry.addStencil(shape.getAttribute('name'), new mxStencil(shape));
+ *   }
+ *   
+ *   shape = shape.nextSibling;
+ * }
+ * (end)
+ */
+var mxStencilRegistry =
+{
+	/**
+	 * Class: mxStencilRegistry
+	 * 
+	 * A singleton class that provides a registry for stencils and the methods
+	 * for painting those stencils onto a canvas or into a DOM.
+	 */
+	stencils: {},
+	
+	/**
+	 * Function: addStencil
+	 * 
+	 * Adds the given <mxStencil>.
+	 */
+	addStencil: function(name, stencil)
+	{
+		mxStencilRegistry.stencils[name] = stencil;
+	},
+	
+	/**
+	 * Function: getStencil
+	 * 
+	 * Returns the <mxStencil> for the given name.
+	 */
+	getStencil: function(name)
+	{
+		return mxStencilRegistry.stencils[name];
+	}
+
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/shape/mxSwimlane.js b/airavata-kubernetes/web-console/src/assets/js/shape/mxSwimlane.js
new file mode 100644
index 0000000..b2abf82
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/shape/mxSwimlane.js
@@ -0,0 +1,410 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxSwimlane
+ *
+ * Extends <mxShape> to implement a swimlane shape. This shape is registered
+ * under <mxConstants.SHAPE_SWIMLANE> in <mxCellRenderer>. Use the
+ * <mxConstants.STYLE_STYLE_STARTSIZE> to define the size of the title
+ * region, <mxConstants.STYLE_SWIMLANE_FILLCOLOR> for the content area fill,
+ * <mxConstants.STYLE_SEPARATORCOLOR> to draw an additional vertical separator
+ * and <mxConstants.STYLE_SWIMLANE_LINE> to hide the line between the title
+ * region and the content area. The <mxConstants.STYLE_HORIZONTAL> affects
+ * the orientation of this shape, not only its label.
+ * 
+ * Constructor: mxSwimlane
+ *
+ * Constructs a new swimlane shape.
+ * 
+ * Parameters:
+ * 
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxSwimlane(bounds, fill, stroke, strokewidth)
+{
+	mxShape.call(this);
+	this.bounds = bounds;
+	this.fill = fill;
+	this.stroke = stroke;
+	this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxUtils.extend(mxSwimlane, mxShape);
+
+/**
+ * Variable: imageSize
+ *
+ * Default imagewidth and imageheight if an image but no imagewidth
+ * and imageheight are defined in the style. Value is 16.
+ */
+mxSwimlane.prototype.imageSize = 16;
+
+/**
+ * Function: getGradientBounds
+ * 
+ * Returns the bounding box for the gradient box for this shape.
+ */
+mxSwimlane.prototype.getTitleSize = function()
+{
+	return Math.max(0, mxUtils.getValue(this.style, mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE));
+};
+
+/**
+ * Function: getGradientBounds
+ * 
+ * Returns the bounding box for the gradient box for this shape.
+ */
+mxSwimlane.prototype.getLabelBounds = function(rect)
+{
+	var start = this.getTitleSize();
+	var bounds = new mxRectangle(rect.x, rect.y, rect.width, rect.height);
+	var horizontal = this.isHorizontal();
+	
+	var flipH = mxUtils.getValue(this.style, mxConstants.STYLE_FLIPH, 0) == 1;
+	var flipV = mxUtils.getValue(this.style, mxConstants.STYLE_FLIPV, 0) == 1;	
+	
+	// East is default
+	var shapeVertical = (this.direction == mxConstants.DIRECTION_NORTH ||
+			this.direction == mxConstants.DIRECTION_SOUTH);
+	var realHorizontal = horizontal == !shapeVertical;
+	
+	var realFlipH = !realHorizontal && flipH != (this.direction == mxConstants.DIRECTION_SOUTH ||
+			this.direction == mxConstants.DIRECTION_WEST);
+	var realFlipV = realHorizontal && flipV != (this.direction == mxConstants.DIRECTION_SOUTH ||
+			this.direction == mxConstants.DIRECTION_WEST);
+
+	// Shape is horizontal
+	if (!shapeVertical)
+	{
+		var tmp = Math.min(bounds.height, start * this.scale);
+
+		if (realFlipH || realFlipV)
+		{
+			bounds.y += bounds.height - tmp;
+		}
+
+		bounds.height = tmp;
+	}
+	else
+	{
+		var tmp = Math.min(bounds.width, start * this.scale);
+		
+		if (realFlipH || realFlipV)
+		{
+			bounds.x += bounds.width - tmp;	
+		}
+
+		bounds.width = tmp;
+	}
+	
+	return bounds;
+};
+
+/**
+ * Function: getGradientBounds
+ * 
+ * Returns the bounding box for the gradient box for this shape.
+ */
+mxSwimlane.prototype.getGradientBounds = function(c, x, y, w, h)
+{
+	var start = this.getTitleSize();
+	
+	if (this.isHorizontal())
+	{
+		start = Math.min(start, h);
+		return new mxRectangle(x, y, w, start);
+	}
+	else
+	{
+		start = Math.min(start, w);
+		return new mxRectangle(x, y, start, h);
+	}
+};
+
+/**
+ * Function: getArcSize
+ * 
+ * Returns the arcsize for the swimlane.
+ */
+mxSwimlane.prototype.getArcSize = function(w, h, start)
+{
+	var f = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.RECTANGLE_ROUNDING_FACTOR * 100) / 100;
+
+	return start * f * 3; 
+};
+
+/**
+ * Function: paintVertexShape
+ *
+ * Paints the swimlane vertex shape.
+ */
+mxSwimlane.prototype.isHorizontal = function()
+{
+	return mxUtils.getValue(this.style, mxConstants.STYLE_HORIZONTAL, 1) == 1;
+};
+
+/**
+ * Function: paintVertexShape
+ *
+ * Paints the swimlane vertex shape.
+ */
+mxSwimlane.prototype.paintVertexShape = function(c, x, y, w, h)
+{
+	var start = this.getTitleSize();
+	var fill = mxUtils.getValue(this.style, mxConstants.STYLE_SWIMLANE_FILLCOLOR, mxConstants.NONE);
+	var swimlaneLine = mxUtils.getValue(this.style, mxConstants.STYLE_SWIMLANE_LINE, 1) == 1;
+	var r = 0;
+	
+	if (this.isHorizontal())
+	{
+		start = Math.min(start, h);
+	}
+	else
+	{
+		start = Math.min(start, w);
+	}
+	
+	c.translate(x, y);
+	
+	if (!this.isRounded)
+	{
+		this.paintSwimlane(c, x, y, w, h, start, fill, swimlaneLine);
+	}
+	else
+	{
+		r = this.getArcSize(w, h, start);
+		this.paintRoundedSwimlane(c, x, y, w, h, start, r, fill, swimlaneLine);
+	}
+	
+	var sep = mxUtils.getValue(this.style, mxConstants.STYLE_SEPARATORCOLOR, mxConstants.NONE);
+	this.paintSeparator(c, x, y, w, h, start, sep);
+
+	if (this.image != null)
+	{
+		var bounds = this.getImageBounds(x, y, w, h);
+		c.image(bounds.x - x, bounds.y - y, bounds.width, bounds.height,
+				this.image, false, false, false);
+	}
+	
+	if (this.glass)
+	{
+		c.setShadow(false);
+		this.paintGlassEffect(c, 0, 0, w, start, r);
+	}
+};
+
+/**
+ * Function: paintSwimlane
+ *
+ * Paints the swimlane vertex shape.
+ */
+mxSwimlane.prototype.paintSwimlane = function(c, x, y, w, h, start, fill, swimlaneLine)
+{
+	if (fill != mxConstants.NONE)
+	{
+		c.save();
+		c.setFillColor(fill);
+		c.rect(0, 0, w, h);
+		c.fillAndStroke();
+		c.restore();
+		c.setShadow(false);
+	}
+
+	c.begin();
+	
+	if (this.isHorizontal())
+	{
+		c.moveTo(0, start);
+		c.lineTo(0, 0);
+		c.lineTo(w, 0);
+		c.lineTo(w, start);
+		
+		if (swimlaneLine || start >= h)
+		{
+			c.close();
+		}
+		
+		c.fillAndStroke();
+		
+		// Transparent content area
+		if (start < h && fill == mxConstants.NONE)
+		{
+			c.pointerEvents = false;
+			
+			c.begin();
+			c.moveTo(0, start);
+			c.lineTo(0, h);
+			c.lineTo(w, h);
+			c.lineTo(w, start);
+			c.stroke();
+		}
+	}
+	else
+	{
+		c.moveTo(start, 0);
+		c.lineTo(0, 0);
+		c.lineTo(0, h);
+		c.lineTo(start, h);
+		
+		if (swimlaneLine || start >= w)
+		{
+			c.close();
+		}
+		
+		c.fillAndStroke();
+		
+		// Transparent content area
+		if (start < w && fill == mxConstants.NONE)
+		{
+			c.pointerEvents = false;
+			
+			c.begin();
+			c.moveTo(start, 0);
+			c.lineTo(w, 0);
+			c.lineTo(w, h);
+			c.lineTo(start, h);
+			c.stroke();
+		}
+	}
+};
+
+/**
+ * Function: paintRoundedSwimlane
+ *
+ * Paints the swimlane vertex shape.
+ */
+mxSwimlane.prototype.paintRoundedSwimlane = function(c, x, y, w, h, start, r, fill, swimlaneLine)
+{
+	r = Math.min(h - start, Math.min(start, r));
+	
+	if (fill != mxConstants.NONE)
+	{
+		c.save();
+		c.setFillColor(fill);
+		c.roundrect(0, 0, w, h, r, r);
+		c.fillAndStroke();
+		c.restore();
+		c.setShadow(false);
+	}
+	
+	c.begin();
+	
+	if (this.isHorizontal())
+	{
+		c.moveTo(w, start);
+		c.lineTo(w, r);
+		c.quadTo(w, 0, w - Math.min(w / 2, r), 0);
+		c.lineTo(Math.min(w / 2, r), 0);
+		c.quadTo(0, 0, 0, r);
+		c.lineTo(0, start);
+		
+		if (swimlaneLine || start >= h)
+		{
+			c.close();
+		}
+	
+		c.fillAndStroke();
+		
+		// Transparent content area
+		if (start < h && fill == mxConstants.NONE)
+		{
+			c.pointerEvents = false;
+			
+			c.begin();
+			c.moveTo(0, start);
+			c.lineTo(0, h - r);
+			c.quadTo(0, h, Math.min(w / 2, r), h);
+			c.lineTo(w - Math.min(w / 2, r), h);
+			c.quadTo(w, h, w, h - r);
+			c.lineTo(w, start);
+			c.stroke();
+		}
+	}
+	else
+	{
+		c.moveTo(start, 0);
+		c.lineTo(r, 0);
+		c.quadTo(0, 0, 0, Math.min(h / 2, r));
+		c.lineTo(0, h - Math.min(h / 2, r));
+		c.quadTo(0, h, r, h);
+		c.lineTo(start, h);
+		
+		if (swimlaneLine || start >= w)
+		{
+			c.close();
+		}
+	
+		c.fillAndStroke();
+		
+		// Transparent content area
+		if (start < w && fill == mxConstants.NONE)
+		{
+			c.pointerEvents = false;
+			
+			c.begin();
+			c.moveTo(start, h);
+			c.lineTo(w - r, h);
+			c.quadTo(w, h, w, h - Math.min(h / 2, r));
+			c.lineTo(w, Math.min(h / 2, r));
+			c.quadTo(w, 0, w - r, 0);
+			c.lineTo(start, 0);
+			c.stroke();
+		}
+	}
+};
+
+/**
+ * Function: paintSwimlane
+ *
+ * Paints the swimlane vertex shape.
+ */
+mxSwimlane.prototype.paintSeparator = function(c, x, y, w, h, start, color)
+{
+	if (color != mxConstants.NONE)
+	{
+		c.setStrokeColor(color);
+		c.setDashed(true);
+		c.begin();
+		
+		if (this.isHorizontal())
+		{
+			c.moveTo(w, start);
+			c.lineTo(w, h);
+		}
+		else
+		{
+			c.moveTo(start, 0);
+			c.lineTo(w, 0);
+		}
+		
+		c.stroke();
+		c.setDashed(false);
+	}
+};
+
+/**
+ * Function: getImageBounds
+ *
+ * Paints the swimlane vertex shape.
+ */
+mxSwimlane.prototype.getImageBounds = function(x, y, w, h)
+{
+	if (this.isHorizontal())
+	{
+		return new mxRectangle(x + w - this.imageSize, y, this.imageSize, this.imageSize);
+	}
+	else
+	{
+		return new mxRectangle(x, y, this.imageSize, this.imageSize);
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/shape/mxText.js b/airavata-kubernetes/web-console/src/assets/js/shape/mxText.js
new file mode 100644
index 0000000..a574998
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/shape/mxText.js
@@ -0,0 +1,1263 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxText
+ *
+ * Extends <mxShape> to implement a text shape. To change vertical text from
+ * bottom to top to top to bottom, the following code can be used:
+ * 
+ * (code)
+ * mxText.prototype.verticalTextRotation = 90;
+ * (end)
+ * 
+ * Constructor: mxText
+ *
+ * Constructs a new text shape.
+ * 
+ * Parameters:
+ * 
+ * value - String that represents the text to be displayed. This is stored in
+ * <value>.
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * align - Specifies the horizontal alignment. Default is ''. This is stored in
+ * <align>.
+ * valign - Specifies the vertical alignment. Default is ''. This is stored in
+ * <valign>.
+ * color - String that specifies the text color. Default is 'black'. This is
+ * stored in <color>.
+ * family - String that specifies the font family. Default is
+ * <mxConstants.DEFAULT_FONTFAMILY>. This is stored in <family>.
+ * size - Integer that specifies the font size. Default is
+ * <mxConstants.DEFAULT_FONTSIZE>. This is stored in <size>.
+ * fontStyle - Specifies the font style. Default is 0. This is stored in
+ * <fontStyle>.
+ * spacing - Integer that specifies the global spacing. Default is 2. This is
+ * stored in <spacing>.
+ * spacingTop - Integer that specifies the top spacing. Default is 0. The
+ * sum of the spacing and this is stored in <spacingTop>.
+ * spacingRight - Integer that specifies the right spacing. Default is 0. The
+ * sum of the spacing and this is stored in <spacingRight>.
+ * spacingBottom - Integer that specifies the bottom spacing. Default is 0.The
+ * sum of the spacing and this is stored in <spacingBottom>.
+ * spacingLeft - Integer that specifies the left spacing. Default is 0. The
+ * sum of the spacing and this is stored in <spacingLeft>.
+ * horizontal - Boolean that specifies if the label is horizontal. Default is
+ * true. This is stored in <horizontal>.
+ * background - String that specifies the background color. Default is null.
+ * This is stored in <background>.
+ * border - String that specifies the label border color. Default is null.
+ * This is stored in <border>.
+ * wrap - Specifies if word-wrapping should be enabled. Default is false.
+ * This is stored in <wrap>.
+ * clipped - Specifies if the label should be clipped. Default is false.
+ * This is stored in <clipped>.
+ * overflow - Value of the overflow style. Default is 'visible'.
+ */
+function mxText(value, bounds, align, valign, color,
+	family,	size, fontStyle, spacing, spacingTop, spacingRight,
+	spacingBottom, spacingLeft, horizontal, background, border,
+	wrap, clipped, overflow, labelPadding, textDirection)
+{
+	mxShape.call(this);
+	this.value = value;
+	this.bounds = bounds;
+	this.color = (color != null) ? color : 'black';
+	this.align = (align != null) ? align : mxConstants.ALIGN_CENTER;
+	this.valign = (valign != null) ? valign : mxConstants.ALIGN_MIDDLE;
+	this.family = (family != null) ? family : mxConstants.DEFAULT_FONTFAMILY;
+	this.size = (size != null) ? size : mxConstants.DEFAULT_FONTSIZE;
+	this.fontStyle = (fontStyle != null) ? fontStyle : mxConstants.DEFAULT_FONTSTYLE;
+	this.spacing = parseInt(spacing || 2);
+	this.spacingTop = this.spacing + parseInt(spacingTop || 0);
+	this.spacingRight = this.spacing + parseInt(spacingRight || 0);
+	this.spacingBottom = this.spacing + parseInt(spacingBottom || 0);
+	this.spacingLeft = this.spacing + parseInt(spacingLeft || 0);
+	this.horizontal = (horizontal != null) ? horizontal : true;
+	this.background = background;
+	this.border = border;
+	this.wrap = (wrap != null) ? wrap : false;
+	this.clipped = (clipped != null) ? clipped : false;
+	this.overflow = (overflow != null) ? overflow : 'visible';
+	this.labelPadding = (labelPadding != null) ? labelPadding : 0;
+	this.textDirection = textDirection;
+	this.rotation = 0;
+	this.updateMargin();
+};
+
+/**
+ * Extends mxShape.
+ */
+mxUtils.extend(mxText, mxShape);
+
+/**
+ * Variable: baseSpacingTop
+ * 
+ * Specifies the spacing to be added to the top spacing. Default is 0. Use the
+ * value 5 here to get the same label positions as in mxGraph 1.x.
+ */
+mxText.prototype.baseSpacingTop = 0;
+
+/**
+ * Variable: baseSpacingBottom
+ * 
+ * Specifies the spacing to be added to the bottom spacing. Default is 0. Use the
+ * value 1 here to get the same label positions as in mxGraph 1.x.
+ */
+mxText.prototype.baseSpacingBottom = 0;
+
+/**
+ * Variable: baseSpacingLeft
+ * 
+ * Specifies the spacing to be added to the left spacing. Default is 0.
+ */
+mxText.prototype.baseSpacingLeft = 0;
+
+/**
+ * Variable: baseSpacingRight
+ * 
+ * Specifies the spacing to be added to the right spacing. Default is 0.
+ */
+mxText.prototype.baseSpacingRight = 0;
+
+/**
+ * Variable: replaceLinefeeds
+ * 
+ * Specifies if linefeeds in HTML labels should be replaced with BR tags.
+ * Default is true.
+ */
+mxText.prototype.replaceLinefeeds = true;
+
+/**
+ * Variable: verticalTextRotation
+ * 
+ * Rotation for vertical text. Default is -90 (bottom to top).
+ */
+mxText.prototype.verticalTextRotation = -90;
+
+/**
+ * Variable: ignoreClippedStringSize
+ * 
+ * Specifies if the string size should be measured in <updateBoundingBox> if
+ * the label is clipped and the label position is center and middle. If this is
+ * true, then the bounding box will be set to <bounds>. Default is true.
+ * <ignoreStringSize> has precedence over this switch.
+ */
+mxText.prototype.ignoreClippedStringSize = true;
+
+/**
+ * Variable: ignoreStringSize
+ * 
+ * Specifies if the actual string size should be measured. If disabled the
+ * boundingBox will not ignore the actual size of the string, otherwise
+ * <bounds> will be used instead. Default is false.
+ */
+mxText.prototype.ignoreStringSize = false;
+
+/**
+ * Variable: textWidthPadding
+ * 
+ * Specifies the padding to be added to the text width for the bounding box.
+ * This is needed to make sure no clipping is applied to borders. Default is 4
+ * for IE 8 standards mode and 3 for all others.
+ */
+mxText.prototype.textWidthPadding = (document.documentMode == 8 && !mxClient.IS_EM) ? 4 : 3;
+
+/**
+ * Variable: lastValue
+ * 
+ * Contains the last rendered text value. Used for caching.
+ */
+mxText.prototype.lastValue = null;
+
+/**
+ * Variable: cacheEnabled
+ * 
+ * Specifies if caching for HTML labels should be enabled. Default is true.
+ */
+mxText.prototype.cacheEnabled = true;
+
+/**
+ * Function: isParseVml
+ * 
+ * Text shapes do not contain VML markup and do not need to be parsed. This
+ * method returns false to speed up rendering in IE8.
+ */
+mxText.prototype.isParseVml = function()
+{
+	return false;
+};
+
+/**
+ * Function: isHtmlAllowed
+ * 
+ * Returns true if HTML is allowed for this shape. This implementation returns
+ * true if the browser is not in IE8 standards mode.
+ */
+mxText.prototype.isHtmlAllowed = function()
+{
+	return document.documentMode != 8 || mxClient.IS_EM;
+};
+
+/**
+ * Function: getSvgScreenOffset
+ * 
+ * Disables offset in IE9 for crisper image output.
+ */
+mxText.prototype.getSvgScreenOffset = function()
+{
+	return 0;
+};
+
+/**
+ * Function: checkBounds
+ * 
+ * Returns true if the bounds are not null and all of its variables are numeric.
+ */
+mxText.prototype.checkBounds = function()
+{
+	return (!isNaN(this.scale) && isFinite(this.scale) && this.scale > 0 &&
+			this.bounds != null && !isNaN(this.bounds.x) && !isNaN(this.bounds.y) &&
+			!isNaN(this.bounds.width) && !isNaN(this.bounds.height));
+};
+
+/**
+ * Function: paint
+ * 
+ * Generic rendering code.
+ */
+mxText.prototype.paint = function(c, update)
+{
+	// Scale is passed-through to canvas
+	var s = this.scale;
+	var x = this.bounds.x / s;
+	var y = this.bounds.y / s;
+	var w = this.bounds.width / s;
+	var h = this.bounds.height / s;
+	
+	this.updateTransform(c, x, y, w, h);
+	this.configureCanvas(c, x, y, w, h);
+
+	var unscaledWidth = (this.state != null) ? this.state.unscaledWidth : null;
+
+	if (update)
+	{
+		if (this.node.firstChild != null && (unscaledWidth == null ||
+			this.lastUnscaledWidth != unscaledWidth))
+		{
+			c.invalidateCachedOffsetSize(this.node);
+		}
+
+		c.updateText(x, y, w, h, this.align, this.valign, this.wrap, this.overflow,
+				this.clipped, this.getTextRotation(), this.node);
+	}
+	else
+	{
+		// Checks if text contains HTML markup
+		var realHtml = mxUtils.isNode(this.value) || this.dialect == mxConstants.DIALECT_STRICTHTML;
+		
+		// Always renders labels as HTML in VML
+		var fmt = (realHtml || c instanceof mxVmlCanvas2D) ? 'html' : '';
+		var val = this.value;
+		
+		if (!realHtml && fmt == 'html')
+		{
+			val =  mxUtils.htmlEntities(val, false);
+		}
+		
+		if (fmt == 'html' && !mxUtils.isNode(this.value))
+		{
+			val = mxUtils.replaceTrailingNewlines(val, '<div><br></div>');			
+		}
+		
+		// Handles trailing newlines to make sure they are visible in rendering output
+		val = (!mxUtils.isNode(this.value) && this.replaceLinefeeds && fmt == 'html') ?
+			val.replace(/\n/g, '<br/>') : val;
+			
+		var dir = this.textDirection;
+	
+		if (dir == mxConstants.TEXT_DIRECTION_AUTO && !realHtml)
+		{
+			dir = this.getAutoDirection();
+		}
+		
+		if (dir != mxConstants.TEXT_DIRECTION_LTR && dir != mxConstants.TEXT_DIRECTION_RTL)
+		{
+			dir = null;
+		}
+	
+		c.text(x, y, w, h, val, this.align, this.valign, this.wrap, fmt, this.overflow,
+			this.clipped, this.getTextRotation(), dir);
+	}
+	
+	// Needs to invalidate the cached offset widths if the geometry changes
+	this.lastUnscaledWidth = unscaledWidth;
+};
+
+/**
+ * Function: redraw
+ * 
+ * Renders the text using the given DOM nodes.
+ */
+mxText.prototype.redraw = function()
+{
+	if (this.visible && this.checkBounds() && this.cacheEnabled && this.lastValue == this.value &&
+		(mxUtils.isNode(this.value) || this.dialect == mxConstants.DIALECT_STRICTHTML))
+	{
+		if (this.node.nodeName == 'DIV' && (this.isHtmlAllowed() || !mxClient.IS_VML))
+		{
+			this.updateSize(this.node, (this.state == null || this.state.view.textDiv == null));
+
+			if (mxClient.IS_IE && (document.documentMode == null || document.documentMode <= 8))
+			{
+				this.updateHtmlFilter();
+			}
+			else
+			{
+				this.updateHtmlTransform();
+			}
+			
+			this.updateBoundingBox();
+		}
+		else
+		{
+			var canvas = this.createCanvas();
+
+			if (canvas != null && canvas.updateText != null &&
+				canvas.invalidateCachedOffsetSize != null)
+			{
+				this.paint(canvas, true);
+				this.destroyCanvas(canvas);
+				this.updateBoundingBox();
+			}
+			else
+			{
+				// Fallback if canvas does not support updateText (VML)
+				mxShape.prototype.redraw.apply(this, arguments);
+			}
+		}
+	}
+	else
+	{
+		mxShape.prototype.redraw.apply(this, arguments);
+		
+		if (mxUtils.isNode(this.value) || this.dialect == mxConstants.DIALECT_STRICTHTML)
+		{
+			this.lastValue = this.value;
+		}
+		else
+		{
+			this.lastValue = null;
+		}
+	}
+};
+
+/**
+ * Function: resetStyles
+ * 
+ * Resets all styles.
+ */
+mxText.prototype.resetStyles = function()
+{
+	mxShape.prototype.resetStyles.apply(this, arguments);
+	
+	this.color = 'black';
+	this.align = mxConstants.ALIGN_CENTER;
+	this.valign = mxConstants.ALIGN_MIDDLE;
+	this.family = mxConstants.DEFAULT_FONTFAMILY;
+	this.size = mxConstants.DEFAULT_FONTSIZE;
+	this.fontStyle = mxConstants.DEFAULT_FONTSTYLE;
+	this.spacing = 2;
+	this.spacingTop = 2;
+	this.spacingRight = 2;
+	this.spacingBottom = 2;
+	this.spacingLeft = 2;
+	this.horizontal = true;
+	delete this.background;
+	delete this.border;
+	this.textDirection = mxConstants.DEFAULT_TEXT_DIRECTION;
+	delete this.margin;
+};
+
+/**
+ * Function: apply
+ * 
+ * Extends mxShape to update the text styles.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> of the corresponding cell.
+ */
+mxText.prototype.apply = function(state)
+{
+	var old = this.spacing;
+	mxShape.prototype.apply.apply(this, arguments);
+	
+	if (this.style != null)
+	{
+		this.fontStyle = mxUtils.getValue(this.style, mxConstants.STYLE_FONTSTYLE, this.fontStyle);
+		this.family = mxUtils.getValue(this.style, mxConstants.STYLE_FONTFAMILY, this.family);
+		this.size = mxUtils.getValue(this.style, mxConstants.STYLE_FONTSIZE, this.size);
+		this.color = mxUtils.getValue(this.style, mxConstants.STYLE_FONTCOLOR, this.color);
+		this.align = mxUtils.getValue(this.style, mxConstants.STYLE_ALIGN, this.align);
+		this.valign = mxUtils.getValue(this.style, mxConstants.STYLE_VERTICAL_ALIGN, this.valign);
+		this.spacing = parseInt(mxUtils.getValue(this.style, mxConstants.STYLE_SPACING, this.spacing));
+		this.spacingTop = parseInt(mxUtils.getValue(this.style, mxConstants.STYLE_SPACING_TOP, this.spacingTop - old)) + this.spacing;
+		this.spacingRight = parseInt(mxUtils.getValue(this.style, mxConstants.STYLE_SPACING_RIGHT, this.spacingRight - old)) + this.spacing;
+		this.spacingBottom = parseInt(mxUtils.getValue(this.style, mxConstants.STYLE_SPACING_BOTTOM, this.spacingBottom - old)) + this.spacing;
+		this.spacingLeft = parseInt(mxUtils.getValue(this.style, mxConstants.STYLE_SPACING_LEFT, this.spacingLeft - old)) + this.spacing;
+		this.horizontal = mxUtils.getValue(this.style, mxConstants.STYLE_HORIZONTAL, this.horizontal);
+		this.background = mxUtils.getValue(this.style, mxConstants.STYLE_LABEL_BACKGROUNDCOLOR, this.background);
+		this.border = mxUtils.getValue(this.style, mxConstants.STYLE_LABEL_BORDERCOLOR, this.border);
+		this.textDirection = mxUtils.getValue(this.style, mxConstants.STYLE_TEXT_DIRECTION, mxConstants.DEFAULT_TEXT_DIRECTION);
+		this.opacity = mxUtils.getValue(this.style, mxConstants.STYLE_TEXT_OPACITY, 100);
+		this.updateMargin();
+	}
+	
+	this.flipV = null;
+	this.flipH = null;
+};
+
+/**
+ * Function: getAutoDirection
+ * 
+ * Used to determine the automatic text direction. Returns
+ * <mxConstants.TEXT_DIRECTION_LTR> or <mxConstants.TEXT_DIRECTION_RTL>
+ * depending on the contents of <value>. This is not invoked for HTML, wrapped
+ * content or if <value> is a DOM node.
+ */
+mxText.prototype.getAutoDirection = function()
+{
+	// Looks for strong (directional) characters
+	var tmp = /[A-Za-z\u05d0-\u065f\u066a-\u06ef\u06fa-\u07ff\ufb1d-\ufdff\ufe70-\ufefc]/.exec(this.value);
+	
+	// Returns the direction defined by the character
+	return (tmp != null && tmp.length > 0 && tmp[0] > 'z') ?
+		mxConstants.TEXT_DIRECTION_RTL : mxConstants.TEXT_DIRECTION_LTR;
+};
+
+/**
+ * Function: updateBoundingBox
+ *
+ * Updates the <boundingBox> for this shape using the given node and position.
+ */
+mxText.prototype.updateBoundingBox = function()
+{
+	var node = this.node;
+	this.boundingBox = this.bounds.clone();
+	var rot = this.getTextRotation();
+	
+	var h = (this.style != null) ? mxUtils.getValue(this.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER) : null;
+	var v = (this.style != null) ? mxUtils.getValue(this.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE) : null;
+
+	if (!this.ignoreStringSize && node != null && this.overflow != 'fill' && (!this.clipped ||
+		!this.ignoreClippedStringSize || h != mxConstants.ALIGN_CENTER || v != mxConstants.ALIGN_MIDDLE))
+	{
+		var ow = null;
+		var oh = null;
+		
+		if (node.ownerSVGElement != null)
+		{
+			if (node.firstChild != null && node.firstChild.firstChild != null &&
+				node.firstChild.firstChild.nodeName == 'foreignObject')
+			{
+				node = node.firstChild.firstChild;
+				ow = parseInt(node.getAttribute('width')) * this.scale;
+				oh = parseInt(node.getAttribute('height')) * this.scale;
+			}
+			else
+			{
+				try
+				{
+					var b = node.getBBox();
+					
+					// Workaround for bounding box of empty string
+					if (typeof(this.value) == 'string' && mxUtils.trim(this.value) == 0)
+					{
+						this.boundingBox = null;
+					}
+					else if (b.width == 0 && b.height == 0)
+					{
+						this.boundingBox = null;
+					}
+					else
+					{
+						this.boundingBox = new mxRectangle(b.x, b.y, b.width, b.height);
+					}
+					
+					return;
+				}
+				catch (e)
+				{
+					// Ignores NS_ERROR_FAILURE in FF if container display is none.
+				}
+			}
+		}
+		else
+		{
+			var td = (this.state != null) ? this.state.view.textDiv : null;
+
+			// Use cached offset size
+			if (this.offsetWidth != null && this.offsetHeight != null)
+			{
+				ow = this.offsetWidth * this.scale;
+				oh = this.offsetHeight * this.scale;
+			}
+			else
+			{
+				// Cannot get node size while container hidden so a
+				// shared temporary DIV is used for text measuring
+				if (td != null)
+				{
+					this.updateFont(td);
+					this.updateSize(td, false);
+					this.updateInnerHtml(td);
+
+					node = td;
+				}
+				
+				var sizeDiv = node;
+
+				if (document.documentMode == 8 && !mxClient.IS_EM)
+				{
+					var w = Math.round(this.bounds.width / this.scale);
+	
+					if (this.wrap && w > 0)
+					{
+						node.style.wordWrap = mxConstants.WORD_WRAP;
+						node.style.whiteSpace = 'normal';
+
+						if (node.style.wordWrap != 'break-word')
+						{
+							// Innermost DIV is used for measuring text
+							var divs = sizeDiv.getElementsByTagName('div');
+							
+							if (divs.length > 0)
+							{
+								sizeDiv = divs[divs.length - 1];
+							}
+							
+							ow = sizeDiv.offsetWidth + 2;
+							divs = this.node.getElementsByTagName('div');
+							
+							if (this.clipped)
+							{
+								ow = Math.min(w, ow);
+							}
+							
+							// Second last DIV width must be updated in DOM tree
+							if (divs.length > 1)
+							{
+								divs[divs.length - 2].style.width = ow + 'px';
+							}
+						}
+					}
+					else
+					{
+						node.style.whiteSpace = 'nowrap';
+					}
+				}
+				else if (sizeDiv.firstChild != null && sizeDiv.firstChild.nodeName == 'DIV')
+				{
+					sizeDiv = sizeDiv.firstChild;
+				}
+
+				this.offsetWidth = sizeDiv.offsetWidth + this.textWidthPadding;
+				this.offsetHeight = sizeDiv.offsetHeight;
+				
+				ow = this.offsetWidth * this.scale;
+				oh = this.offsetHeight * this.scale;
+			}
+		}
+
+		if (ow != null && oh != null)
+		{	
+			this.boundingBox = new mxRectangle(this.bounds.x,
+				this.bounds.y, ow, oh);
+		}
+	}
+
+	if (this.boundingBox != null)
+	{
+		if (rot != 0)
+		{
+			// Accounts for pre-rotated x and y
+			var bbox = mxUtils.getBoundingBox(new mxRectangle(
+				this.margin.x * this.boundingBox.width,
+				this.margin.y * this.boundingBox.height,
+				this.boundingBox.width, this.boundingBox.height),
+				rot, new mxPoint(0, 0));
+			
+			this.unrotatedBoundingBox = mxRectangle.fromRectangle(this.boundingBox);
+			this.unrotatedBoundingBox.x += this.margin.x * this.unrotatedBoundingBox.width;
+			this.unrotatedBoundingBox.y += this.margin.y * this.unrotatedBoundingBox.height;
+			
+			this.boundingBox.x += bbox.x;
+			this.boundingBox.y += bbox.y;
+			this.boundingBox.width = bbox.width;
+			this.boundingBox.height = bbox.height;
+		}
+		else
+		{
+			this.boundingBox.x += this.margin.x * this.boundingBox.width;
+			this.boundingBox.y += this.margin.y * this.boundingBox.height;
+			this.unrotatedBoundingBox = null;
+		}
+	}
+};
+
+/**
+ * Function: getShapeRotation
+ * 
+ * Returns 0 to avoid using rotation in the canvas via updateTransform.
+ */
+mxText.prototype.getShapeRotation = function()
+{
+	return 0;
+};
+
+/**
+ * Function: getTextRotation
+ * 
+ * Returns the rotation for the text label of the corresponding shape.
+ */
+mxText.prototype.getTextRotation = function()
+{
+	return (this.state != null && this.state.shape != null) ? this.state.shape.getTextRotation() : 0;
+};
+
+/**
+ * Function: isPaintBoundsInverted
+ * 
+ * Inverts the bounds if <mxShape.isBoundsInverted> returns true or if the
+ * horizontal style is false.
+ */
+mxText.prototype.isPaintBoundsInverted = function()
+{
+	return !this.horizontal && this.state != null && this.state.view.graph.model.isVertex(this.state.cell);
+};
+
+/**
+ * Function: configureCanvas
+ * 
+ * Sets the state of the canvas for drawing the shape.
+ */
+mxText.prototype.configureCanvas = function(c, x, y, w, h)
+{
+	mxShape.prototype.configureCanvas.apply(this, arguments);
+	
+	c.setFontColor(this.color);
+	c.setFontBackgroundColor(this.background);
+	c.setFontBorderColor(this.border);
+	c.setFontFamily(this.family);
+	c.setFontSize(this.size);
+	c.setFontStyle(this.fontStyle);
+};
+
+/**
+ * Function: updateVmlContainer
+ * 
+ * Sets the width and height of the container to 1px.
+ */
+mxText.prototype.updateVmlContainer = function()
+{
+	this.node.style.left = Math.round(this.bounds.x) + 'px';
+	this.node.style.top = Math.round(this.bounds.y) + 'px';
+	this.node.style.width = '1px';
+	this.node.style.height = '1px';
+	this.node.style.overflow = 'visible';
+};
+
+/**
+ * Function: redrawHtmlShape
+ *
+ * Updates the HTML node(s) to reflect the latest bounds and scale.
+ */
+mxText.prototype.redrawHtmlShape = function()
+{
+	var style = this.node.style;
+
+	// Resets CSS styles
+	style.whiteSpace = 'normal';
+	style.overflow = '';
+	style.width = '';
+	style.height = '';
+	
+	this.updateValue();
+	this.updateFont(this.node);
+	this.updateSize(this.node, (this.state == null || this.state.view.textDiv == null));
+	
+	this.offsetWidth = null;
+	this.offsetHeight = null;
+
+	if (mxClient.IS_IE && (document.documentMode == null || document.documentMode <= 8))
+	{
+		this.updateHtmlFilter();
+	}
+	else
+	{
+		this.updateHtmlTransform();
+	}
+};
+
+/**
+ * Function: updateHtmlTransform
+ *
+ * Returns the spacing as an <mxPoint>.
+ */
+mxText.prototype.updateHtmlTransform = function()
+{
+	var theta = this.getTextRotation();
+	var style = this.node.style;
+	var dx = this.margin.x;
+	var dy = this.margin.y;
+	
+	if (theta != 0)
+	{
+		mxUtils.setPrefixedStyle(style, 'transformOrigin', (-dx * 100) + '%' + ' ' + (-dy * 100) + '%');
+		mxUtils.setPrefixedStyle(style, 'transform', 'translate(' + (dx * 100) + '%' + ',' + (dy * 100) + '%)' +
+			'scale(' + this.scale + ') rotate(' + theta + 'deg)');
+	}
+	else
+	{
+		mxUtils.setPrefixedStyle(style, 'transformOrigin', '0% 0%');
+		mxUtils.setPrefixedStyle(style, 'transform', 'scale(' + this.scale + ')' +
+			'translate(' + (dx * 100) + '%' + ',' + (dy * 100) + '%)');
+	}
+
+	style.left = Math.round(this.bounds.x - Math.ceil(dx * ((this.overflow != 'fill' &&
+		this.overflow != 'width') ? 3 : 1))) + 'px';
+	style.top = Math.round(this.bounds.y - dy * ((this.overflow != 'fill') ? 3 : 1)) + 'px';
+	
+	if (this.opacity < 100)
+	{
+		style.opacity = this.opacity / 100;
+	}
+	else
+	{
+		style.opacity = '';
+	}
+};
+
+/**
+ * Function: setInnerHtml
+ * 
+ * Sets the inner HTML of the given element to the <value>.
+ */
+mxText.prototype.updateInnerHtml = function(elt)
+{
+	if (mxUtils.isNode(this.value))
+	{
+		elt.innerHTML = this.value.outerHTML;
+	}
+	else
+	{
+		var val = this.value;
+		
+		if (this.dialect != mxConstants.DIALECT_STRICTHTML)
+		{
+			// LATER: Can be cached in updateValue
+			val = mxUtils.htmlEntities(val, false);
+		}
+		
+		// Handles trailing newlines to make sure they are visible in rendering output
+		val = mxUtils.replaceTrailingNewlines(val, '<div>&nbsp;</div>');
+		val = (this.replaceLinefeeds) ? val.replace(/\n/g, '<br/>') : val;
+		val = '<div style="display:inline-block;_display:inline;">' + val + '</div>';
+		
+		elt.innerHTML = val;
+	}
+};
+
+/**
+ * Function: updateHtmlFilter
+ *
+ * Rotated text rendering quality is bad for IE9 quirks/IE8 standards
+ */
+mxText.prototype.updateHtmlFilter = function()
+{
+	var style = this.node.style;
+	var dx = this.margin.x;
+	var dy = this.margin.y;
+	var s = this.scale;
+	
+	// Resets filter before getting offsetWidth
+	mxUtils.setOpacity(this.node, this.opacity);
+	
+	// Adds 1 to match table height in 1.x
+	var ow = 0;
+	var oh = 0;
+	var td = (this.state != null) ? this.state.view.textDiv : null;
+	var sizeDiv = this.node;
+	
+	// Fallback for hidden text rendering in IE quirks mode
+	if (td != null)
+	{
+		td.style.overflow = '';
+		td.style.height = '';
+		td.style.width = '';
+		
+		this.updateFont(td);
+		this.updateSize(td, false);
+		this.updateInnerHtml(td);
+		
+		var w = Math.round(this.bounds.width / this.scale);
+
+		if (this.wrap && w > 0)
+		{
+			td.style.whiteSpace = 'normal';
+			td.style.wordWrap = mxConstants.WORD_WRAP;
+			ow = w;
+			
+			if (this.clipped)
+			{
+				ow = Math.min(ow, this.bounds.width);
+			}
+
+			td.style.width = ow + 'px';
+		}
+		else
+		{
+			td.style.whiteSpace = 'nowrap';
+		}
+		
+		sizeDiv = td;
+		
+		if (sizeDiv.firstChild != null && sizeDiv.firstChild.nodeName == 'DIV')
+		{
+			sizeDiv = sizeDiv.firstChild;
+			
+			if (this.wrap && td.style.wordWrap == 'break-word')
+			{
+				sizeDiv.style.width = '100%';
+			}
+		}
+
+		// Required to update the height of the text box after wrapping width is known 
+		if (!this.clipped && this.wrap && w > 0)
+		{
+			ow = sizeDiv.offsetWidth + this.textWidthPadding;
+			td.style.width = ow + 'px';
+		}
+		
+		oh = sizeDiv.offsetHeight + 2;
+		
+		if (mxClient.IS_QUIRKS && this.border != null && this.border != mxConstants.NONE)
+		{
+			oh += 3;
+		}
+	}
+	else if (sizeDiv.firstChild != null && sizeDiv.firstChild.nodeName == 'DIV')
+	{
+		sizeDiv = sizeDiv.firstChild;
+		oh = sizeDiv.offsetHeight;
+	}
+
+	ow = sizeDiv.offsetWidth + this.textWidthPadding;
+	
+	if (this.clipped)
+	{
+		oh = Math.min(oh, this.bounds.height);
+	}
+
+	var w = this.bounds.width / s;
+	var h = this.bounds.height / s;
+
+	// Handles special case for live preview with no wrapper DIV and no textDiv
+	if (this.overflow == 'fill')
+	{
+		oh = h;
+		ow = w;
+	}
+	else if (this.overflow == 'width')
+	{
+		oh = sizeDiv.scrollHeight;
+		ow = w;
+	}
+	
+	// Stores for later use
+	this.offsetWidth = ow;
+	this.offsetHeight = oh;
+	
+	// Simulates max-height CSS in quirks mode
+	if (mxClient.IS_QUIRKS && (this.clipped || (this.overflow == 'width' && h > 0)))
+	{
+		h = Math.min(h, oh);
+		style.height = Math.round(h) + 'px';
+	}
+	else
+	{
+		h = oh;
+	}
+
+	if (this.overflow != 'fill' && this.overflow != 'width')
+	{
+		if (this.clipped)
+		{
+			ow = Math.min(w, ow);
+		}
+		
+		w = ow;
+
+		// Simulates max-width CSS in quirks mode
+		if ((mxClient.IS_QUIRKS && this.clipped) || this.wrap)
+		{
+			style.width = Math.round(w) + 'px';
+		}
+	}
+
+	h *= s;
+	w *= s;
+	
+	// Rotation case is handled via VML canvas
+	var rad = this.getTextRotation() * (Math.PI / 180);
+	
+	// Precalculate cos and sin for the rotation
+	var real_cos = parseFloat(parseFloat(Math.cos(rad)).toFixed(8));
+	var real_sin = parseFloat(parseFloat(Math.sin(-rad)).toFixed(8));
+
+	rad %= 2 * Math.PI;
+	
+	if (rad < 0)
+	{
+		rad += 2 * Math.PI;
+	}
+	
+	rad %= Math.PI;
+	
+	if (rad > Math.PI / 2)
+	{
+		rad = Math.PI - rad;
+	}
+	
+	var cos = Math.cos(rad);
+	var sin = Math.sin(-rad);
+
+	var tx = w * -(dx + 0.5);
+	var ty = h * -(dy + 0.5);
+
+	var top_fix = (h - h * cos + w * sin) / 2 + real_sin * tx - real_cos * ty;
+	var left_fix = (w - w * cos + h * sin) / 2 - real_cos * tx - real_sin * ty;
+	
+	if (rad != 0)
+	{
+		var f = 'progid:DXImageTransform.Microsoft.Matrix(M11=' + real_cos + ', M12='+
+			real_sin + ', M21=' + (-real_sin) + ', M22=' + real_cos + ', sizingMethod=\'auto expand\')';
+		
+		if (style.filter != null && style.filter.length > 0)
+		{
+			style.filter += ' ' + f;
+		}
+		else
+		{
+			style.filter = f;
+		}
+	}
+	
+	// Workaround for rendering offsets
+	var dy = 0;
+	
+	if (this.overflow != 'fill' && mxClient.IS_QUIRKS)
+	{
+		if (this.valign == mxConstants.ALIGN_TOP)
+		{
+			dy -= 1;
+		}
+		else if (this.valign == mxConstants.ALIGN_BOTTOM)
+		{
+			dy += 2;
+		}
+		else
+		{
+			dy += 1;
+		}
+	}
+
+	style.zoom = s;
+	style.left = Math.round(this.bounds.x + left_fix - w / 2) + 'px';
+	style.top = Math.round(this.bounds.y + top_fix - h / 2 + dy) + 'px';
+};
+
+/**
+ * Function: updateValue
+ *
+ * Updates the HTML node(s) to reflect the latest bounds and scale.
+ */
+mxText.prototype.updateValue = function()
+{
+	if (mxUtils.isNode(this.value))
+	{
+		this.node.innerHTML = '';
+		this.node.appendChild(this.value);
+	}
+	else
+	{
+		var val = this.value;
+		
+		if (this.dialect != mxConstants.DIALECT_STRICTHTML)
+		{
+			val = mxUtils.htmlEntities(val, false);
+		}
+		
+		// Handles trailing newlines to make sure they are visible in rendering output
+		val = mxUtils.replaceTrailingNewlines(val, '<div><br></div>');
+		val = (this.replaceLinefeeds) ? val.replace(/\n/g, '<br/>') : val;
+		var bg = (this.background != null && this.background != mxConstants.NONE) ? this.background : null;
+		var bd = (this.border != null && this.border != mxConstants.NONE) ? this.border : null;
+
+		if (this.overflow == 'fill' || this.overflow == 'width')
+		{
+			if (bg != null)
+			{
+				this.node.style.backgroundColor = bg;
+			}
+			
+			if (bd != null)
+			{
+				this.node.style.border = '1px solid ' + bd;
+			}
+		}
+		else
+		{
+			var css = '';
+			
+			if (bg != null)
+			{
+				css += 'background-color:' + bg + ';';
+			}
+			
+			if (bd != null)
+			{
+				css += 'border:1px solid ' + bd + ';';
+			}
+			
+			// Wrapper DIV for background, zoom needed for inline in quirks
+			// and to measure wrapped font sizes in all browsers
+			// FIXME: Background size in quirks mode for wrapped text
+			var lh = (mxConstants.ABSOLUTE_LINE_HEIGHT) ? (this.size * mxConstants.LINE_HEIGHT) + 'px' :
+				mxConstants.LINE_HEIGHT;
+			val = '<div style="zoom:1;' + css + 'display:inline-block;_display:inline;text-decoration:inherit;' +
+				'padding-bottom:1px;padding-right:1px;line-height:' + lh + '">' + val + '</div>';
+		}
+
+		this.node.innerHTML = val;
+		
+		// Sets text direction
+		var divs = this.node.getElementsByTagName('div');
+		
+		if (divs.length > 0)
+		{
+			var dir = this.textDirection;
+
+			if (dir == mxConstants.TEXT_DIRECTION_AUTO && this.dialect != mxConstants.DIALECT_STRICTHTML)
+			{
+				dir = this.getAutoDirection();
+			}
+			
+			if (dir == mxConstants.TEXT_DIRECTION_LTR || dir == mxConstants.TEXT_DIRECTION_RTL)
+			{
+				divs[divs.length - 1].setAttribute('dir', dir);
+			}
+			else
+			{
+				divs[divs.length - 1].removeAttribute('dir');
+			}
+		}
+	}
+};
+
+/**
+ * Function: updateFont
+ *
+ * Updates the HTML node(s) to reflect the latest bounds and scale.
+ */
+mxText.prototype.updateFont = function(node)
+{
+	var style = node.style;
+	
+	style.lineHeight = (mxConstants.ABSOLUTE_LINE_HEIGHT) ? (this.size * mxConstants.LINE_HEIGHT) + 'px' : mxConstants.LINE_HEIGHT;
+	style.fontSize = this.size + 'px';
+	style.fontFamily = this.family;
+	style.verticalAlign = 'top';
+	style.color = this.color;
+	
+	if ((this.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
+	{
+		style.fontWeight = 'bold';
+	}
+	else
+	{
+		style.fontWeight = '';
+	}
+
+	if ((this.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
+	{
+		style.fontStyle = 'italic';
+	}
+	else
+	{
+		style.fontStyle = '';
+	}
+	
+	if ((this.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)
+	{
+		style.textDecoration = 'underline';
+	}
+	else
+	{
+		style.textDecoration = '';
+	}
+	
+	if (this.align == mxConstants.ALIGN_CENTER)
+	{
+		style.textAlign = 'center';
+	}
+	else if (this.align == mxConstants.ALIGN_RIGHT)
+	{
+		style.textAlign = 'right';
+	}
+	else
+	{
+		style.textAlign = 'left';
+	}
+};
+
+/**
+ * Function: updateSize
+ *
+ * Updates the HTML node(s) to reflect the latest bounds and scale.
+ */
+mxText.prototype.updateSize = function(node, enableWrap)
+{
+	var w = Math.max(0, Math.round(this.bounds.width / this.scale));
+	var h = Math.max(0, Math.round(this.bounds.height / this.scale));
+	var style = node.style;
+	
+	// NOTE: Do not use maxWidth here because wrapping will
+	// go wrong if the cell is outside of the viewable area
+	if (this.clipped)
+	{
+		style.overflow = 'hidden';
+		
+		if (!mxClient.IS_QUIRKS)
+		{
+			style.maxHeight = h + 'px';
+			style.maxWidth = w + 'px';
+		}
+		else
+		{
+			style.width = w + 'px';
+		}
+	}
+	else if (this.overflow == 'fill')
+	{
+		style.width = (w + 1) + 'px';
+		style.height = (h + 1) + 'px';
+		style.overflow = 'hidden';
+	}
+	else if (this.overflow == 'width')
+	{
+		style.width = (w + 1) + 'px';
+		style.maxHeight = (h + 1) + 'px';
+		style.overflow = 'hidden';
+	}
+	
+	if (this.wrap && w > 0)
+	{
+		style.wordWrap = mxConstants.WORD_WRAP;
+		style.whiteSpace = 'normal';
+		style.width = w + 'px';
+
+		if (enableWrap && this.overflow != 'fill' && this.overflow != 'width')
+		{
+			var sizeDiv = node;
+			
+			if (sizeDiv.firstChild != null && sizeDiv.firstChild.nodeName == 'DIV')
+			{
+				sizeDiv = sizeDiv.firstChild;
+				
+				if (node.style.wordWrap == 'break-word')
+				{
+					sizeDiv.style.width = '100%';
+				}
+			}
+			
+			var tmp = sizeDiv.offsetWidth;
+			
+			// Workaround for text measuring in hidden containers
+			if (tmp == 0)
+			{
+				var prev = node.parentNode;
+				node.style.visibility = 'hidden';
+				document.body.appendChild(node);
+				tmp = sizeDiv.offsetWidth;
+				node.style.visibility = '';
+				prev.appendChild(node);
+			}
+
+			tmp += 3;
+			
+			if (this.clipped)
+			{
+				tmp = Math.min(tmp, w);
+			}
+			
+			style.width = tmp + 'px';
+		}
+	}
+	else
+	{
+		style.whiteSpace = 'nowrap';
+	}
+};
+
+/**
+ * Function: getMargin
+ *
+ * Returns the spacing as an <mxPoint>.
+ */
+mxText.prototype.updateMargin = function()
+{
+	this.margin = mxUtils.getAlignmentAsPoint(this.align, this.valign);
+};
+
+/**
+ * Function: getSpacing
+ *
+ * Returns the spacing as an <mxPoint>.
+ */
+mxText.prototype.getSpacing = function()
+{
+	var dx = 0;
+	var dy = 0;
+
+	if (this.align == mxConstants.ALIGN_CENTER)
+	{
+		dx = (this.spacingLeft - this.spacingRight) / 2;
+	}
+	else if (this.align == mxConstants.ALIGN_RIGHT)
+	{
+		dx = -this.spacingRight - this.baseSpacingRight;
+	}
+	else
+	{
+		dx = this.spacingLeft + this.baseSpacingLeft;
+	}
+
+	if (this.valign == mxConstants.ALIGN_MIDDLE)
+	{
+		dy = (this.spacingTop - this.spacingBottom) / 2;
+	}
+	else if (this.valign == mxConstants.ALIGN_BOTTOM)
+	{
+		dy = -this.spacingBottom - this.baseSpacingBottom;;
+	}
+	else
+	{
+		dy = this.spacingTop + this.baseSpacingTop;
+	}
+	
+	return new mxPoint(dx, dy);
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/shape/mxTriangle.js b/airavata-kubernetes/web-console/src/assets/js/shape/mxTriangle.js
new file mode 100644
index 0000000..4c345e6
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/shape/mxTriangle.js
@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxTriangle
+ * 
+ * Implementation of the triangle shape.
+ * 
+ * Constructor: mxTriangle
+ *
+ * Constructs a new triangle shape.
+ */
+function mxTriangle()
+{
+	mxActor.call(this);
+};
+
+/**
+ * Extends mxActor.
+ */
+mxUtils.extend(mxTriangle, mxActor);
+
+/**
+ * Function: redrawPath
+ *
+ * Draws the path for this shape.
+ */
+mxTriangle.prototype.redrawPath = function(c, x, y, w, h)
+{
+	var arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
+	this.addPoints(c, [new mxPoint(0, 0), new mxPoint(w, 0.5 * h), new mxPoint(0, h)], this.isRounded, arcSize, true);
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/util/mxAbstractCanvas2D.js b/airavata-kubernetes/web-console/src/assets/js/util/mxAbstractCanvas2D.js
new file mode 100644
index 0000000..e9447f3
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/util/mxAbstractCanvas2D.js
@@ -0,0 +1,642 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxAbstractCanvas2D
+ *
+ * Base class for all canvases. A description of the public API is available in <mxXmlCanvas2D>.
+ * All color values of <mxConstants.NONE> will be converted to null in the state.
+ * 
+ * Constructor: mxAbstractCanvas2D
+ *
+ * Constructs a new abstract canvas.
+ */
+function mxAbstractCanvas2D()
+{
+	/**
+	 * Variable: converter
+	 * 
+	 * Holds the <mxUrlConverter> to convert image URLs.
+	 */
+	this.converter = this.createUrlConverter();
+	
+	this.reset();
+};
+
+/**
+ * Variable: state
+ * 
+ * Holds the current state.
+ */
+mxAbstractCanvas2D.prototype.state = null;
+
+/**
+ * Variable: states
+ * 
+ * Stack of states.
+ */
+mxAbstractCanvas2D.prototype.states = null;
+
+/**
+ * Variable: path
+ * 
+ * Holds the current path as an array.
+ */
+mxAbstractCanvas2D.prototype.path = null;
+
+/**
+ * Variable: rotateHtml
+ * 
+ * Switch for rotation of HTML. Default is false.
+ */
+mxAbstractCanvas2D.prototype.rotateHtml = true;
+
+/**
+ * Variable: lastX
+ * 
+ * Holds the last x coordinate.
+ */
+mxAbstractCanvas2D.prototype.lastX = 0;
+
+/**
+ * Variable: lastY
+ * 
+ * Holds the last y coordinate.
+ */
+mxAbstractCanvas2D.prototype.lastY = 0;
+
+/**
+ * Variable: moveOp
+ * 
+ * Contains the string used for moving in paths. Default is 'M'.
+ */
+mxAbstractCanvas2D.prototype.moveOp = 'M';
+
+/**
+ * Variable: lineOp
+ * 
+ * Contains the string used for moving in paths. Default is 'L'.
+ */
+mxAbstractCanvas2D.prototype.lineOp = 'L';
+
+/**
+ * Variable: quadOp
+ * 
+ * Contains the string used for quadratic paths. Default is 'Q'.
+ */
+mxAbstractCanvas2D.prototype.quadOp = 'Q';
+
+/**
+ * Variable: curveOp
+ * 
+ * Contains the string used for bezier curves. Default is 'C'.
+ */
+mxAbstractCanvas2D.prototype.curveOp = 'C';
+
+/**
+ * Variable: closeOp
+ * 
+ * Holds the operator for closing curves. Default is 'Z'.
+ */
+mxAbstractCanvas2D.prototype.closeOp = 'Z';
+
+/**
+ * Variable: pointerEvents
+ * 
+ * Boolean value that specifies if events should be handled. Default is false.
+ */
+mxAbstractCanvas2D.prototype.pointerEvents = false;
+
+/**
+ * Function: createUrlConverter
+ * 
+ * Create a new <mxUrlConverter> and returns it.
+ */
+mxAbstractCanvas2D.prototype.createUrlConverter = function()
+{
+	return new mxUrlConverter();
+};
+
+/**
+ * Function: reset
+ * 
+ * Resets the state of this canvas.
+ */
+mxAbstractCanvas2D.prototype.reset = function()
+{
+	this.state = this.createState();
+	this.states = [];
+};
+
+/**
+ * Function: createState
+ * 
+ * Creates the state of the this canvas.
+ */
+mxAbstractCanvas2D.prototype.createState = function()
+{
+	return {
+		dx: 0,
+		dy: 0,
+		scale: 1,
+		alpha: 1,
+		fillAlpha: 1,
+		strokeAlpha: 1,
+		fillColor: null,
+		gradientFillAlpha: 1,
+		gradientColor: null,
+		gradientAlpha: 1,
+		gradientDirection: null,
+		strokeColor: null,
+		strokeWidth: 1,
+		dashed: false,
+		dashPattern: '3 3',
+		fixDash: false,
+		lineCap: 'flat',
+		lineJoin: 'miter',
+		miterLimit: 10,
+		fontColor: '#000000',
+		fontBackgroundColor: null,
+		fontBorderColor: null,
+		fontSize: mxConstants.DEFAULT_FONTSIZE,
+		fontFamily: mxConstants.DEFAULT_FONTFAMILY,
+		fontStyle: 0,
+		shadow: false,
+		shadowColor: mxConstants.SHADOWCOLOR,
+		shadowAlpha: mxConstants.SHADOW_OPACITY,
+		shadowDx: mxConstants.SHADOW_OFFSET_X,
+		shadowDy: mxConstants.SHADOW_OFFSET_Y,
+		rotation: 0,
+		rotationCx: 0,
+		rotationCy: 0
+	};
+};
+
+/**
+ * Function: format
+ * 
+ * Rounds all numbers to integers.
+ */
+mxAbstractCanvas2D.prototype.format = function(value)
+{
+	return Math.round(parseFloat(value));
+};
+
+/**
+ * Function: addOp
+ * 
+ * Adds the given operation to the path.
+ */
+mxAbstractCanvas2D.prototype.addOp = function()
+{
+	if (this.path != null)
+	{
+		this.path.push(arguments[0]);
+		
+		if (arguments.length > 2)
+		{
+			var s = this.state;
+
+			for (var i = 2; i < arguments.length; i += 2)
+			{
+				this.lastX = arguments[i - 1];
+				this.lastY = arguments[i];
+				
+				this.path.push(this.format((this.lastX + s.dx) * s.scale));
+				this.path.push(this.format((this.lastY + s.dy) * s.scale));
+			}
+		}
+	}
+};
+
+/**
+ * Function: rotatePoint
+ * 
+ * Rotates the given point and returns the result as an <mxPoint>.
+ */
+mxAbstractCanvas2D.prototype.rotatePoint = function(x, y, theta, cx, cy)
+{
+	var rad = theta * (Math.PI / 180);
+	
+	return mxUtils.getRotatedPoint(new mxPoint(x, y), Math.cos(rad),
+		Math.sin(rad), new mxPoint(cx, cy));
+};
+
+/**
+ * Function: save
+ * 
+ * Saves the current state.
+ */
+mxAbstractCanvas2D.prototype.save = function()
+{
+	this.states.push(this.state);
+	this.state = mxUtils.clone(this.state);
+};
+
+/**
+ * Function: restore
+ * 
+ * Restores the current state.
+ */
+mxAbstractCanvas2D.prototype.restore = function()
+{
+	if (this.states.length > 0)
+	{
+		this.state = this.states.pop();
+	}
+};
+
+/**
+ * Function: setLink
+ * 
+ * Sets the current link. Hook for subclassers.
+ */
+mxAbstractCanvas2D.prototype.setLink = function(link)
+{
+	// nop
+};
+
+/**
+ * Function: scale
+ * 
+ * Scales the current state.
+ */
+mxAbstractCanvas2D.prototype.scale = function(value)
+{
+	this.state.scale *= value;
+	this.state.strokeWidth *= value;
+};
+
+/**
+ * Function: translate
+ * 
+ * Translates the current state.
+ */
+mxAbstractCanvas2D.prototype.translate = function(dx, dy)
+{
+	this.state.dx += dx;
+	this.state.dy += dy;
+};
+
+/**
+ * Function: rotate
+ * 
+ * Rotates the current state.
+ */
+mxAbstractCanvas2D.prototype.rotate = function(theta, flipH, flipV, cx, cy)
+{
+	// nop
+};
+
+/**
+ * Function: setAlpha
+ * 
+ * Sets the current alpha.
+ */
+mxAbstractCanvas2D.prototype.setAlpha = function(value)
+{
+	this.state.alpha = value;
+};
+
+/**
+ * Function: setFillAlpha
+ * 
+ * Sets the current solid fill alpha.
+ */
+mxAbstractCanvas2D.prototype.setFillAlpha = function(value)
+{
+	this.state.fillAlpha = value;
+};
+
+/**
+ * Function: setStrokeAlpha
+ * 
+ * Sets the current stroke alpha.
+ */
+mxAbstractCanvas2D.prototype.setStrokeAlpha = function(value)
+{
+	this.state.strokeAlpha = value;
+};
+
+/**
+ * Function: setFillColor
+ * 
+ * Sets the current fill color.
+ */
+mxAbstractCanvas2D.prototype.setFillColor = function(value)
+{
+	if (value == mxConstants.NONE)
+	{
+		value = null;
+	}
+	
+	this.state.fillColor = value;
+	this.state.gradientColor = null;
+};
+
+/**
+ * Function: setGradient
+ * 
+ * Sets the current gradient.
+ */
+mxAbstractCanvas2D.prototype.setGradient = function(color1, color2, x, y, w, h, direction, alpha1, alpha2)
+{
+	var s = this.state;
+	s.fillColor = color1;
+	s.gradientFillAlpha = (alpha1 != null) ? alpha1 : 1;
+	s.gradientColor = color2;
+	s.gradientAlpha = (alpha2 != null) ? alpha2 : 1;
+	s.gradientDirection = direction;
+};
+
+/**
+ * Function: setStrokeColor
+ * 
+ * Sets the current stroke color.
+ */
+mxAbstractCanvas2D.prototype.setStrokeColor = function(value)
+{
+	if (value == mxConstants.NONE)
+	{
+		value = null;
+	}
+	
+	this.state.strokeColor = value;
+};
+
+/**
+ * Function: setStrokeWidth
+ * 
+ * Sets the current stroke width.
+ */
+mxAbstractCanvas2D.prototype.setStrokeWidth = function(value)
+{
+	this.state.strokeWidth = value;
+};
+
+/**
+ * Function: setDashed
+ * 
+ * Enables or disables dashed lines.
+ */
+mxAbstractCanvas2D.prototype.setDashed = function(value, fixDash)
+{
+	this.state.dashed = value;
+	this.state.fixDash = fixDash;
+};
+
+/**
+ * Function: setDashPattern
+ * 
+ * Sets the current dash pattern.
+ */
+mxAbstractCanvas2D.prototype.setDashPattern = function(value)
+{
+	this.state.dashPattern = value;
+};
+
+/**
+ * Function: setLineCap
+ * 
+ * Sets the current line cap.
+ */
+mxAbstractCanvas2D.prototype.setLineCap = function(value)
+{
+	this.state.lineCap = value;
+};
+
+/**
+ * Function: setLineJoin
+ * 
+ * Sets the current line join.
+ */
+mxAbstractCanvas2D.prototype.setLineJoin = function(value)
+{
+	this.state.lineJoin = value;
+};
+
+/**
+ * Function: setMiterLimit
+ * 
+ * Sets the current miter limit.
+ */
+mxAbstractCanvas2D.prototype.setMiterLimit = function(value)
+{
+	this.state.miterLimit = value;
+};
+
+/**
+ * Function: setFontColor
+ * 
+ * Sets the current font color.
+ */
+mxAbstractCanvas2D.prototype.setFontColor = function(value)
+{
+	if (value == mxConstants.NONE)
+	{
+		value = null;
+	}
+	
+	this.state.fontColor = value;
+};
+
+/**
+ * Function: setFontColor
+ * 
+ * Sets the current font color.
+ */
+mxAbstractCanvas2D.prototype.setFontBackgroundColor = function(value)
+{
+	if (value == mxConstants.NONE)
+	{
+		value = null;
+	}
+	
+	this.state.fontBackgroundColor = value;
+};
+
+/**
+ * Function: setFontColor
+ * 
+ * Sets the current font color.
+ */
+mxAbstractCanvas2D.prototype.setFontBorderColor = function(value)
+{
+	if (value == mxConstants.NONE)
+	{
+		value = null;
+	}
+	
+	this.state.fontBorderColor = value;
+};
+
+/**
+ * Function: setFontSize
+ * 
+ * Sets the current font size.
+ */
+mxAbstractCanvas2D.prototype.setFontSize = function(value)
+{
+	this.state.fontSize = parseFloat(value);
+};
+
+/**
+ * Function: setFontFamily
+ * 
+ * Sets the current font family.
+ */
+mxAbstractCanvas2D.prototype.setFontFamily = function(value)
+{
+	this.state.fontFamily = value;
+};
+
+/**
+ * Function: setFontStyle
+ * 
+ * Sets the current font style.
+ */
+mxAbstractCanvas2D.prototype.setFontStyle = function(value)
+{
+	if (value == null)
+	{
+		value = 0;
+	}
+	
+	this.state.fontStyle = value;
+};
+
+/**
+ * Function: setShadow
+ * 
+ * Enables or disables and configures the current shadow.
+ */
+mxAbstractCanvas2D.prototype.setShadow = function(enabled)
+{
+	this.state.shadow = enabled;
+};
+
+/**
+ * Function: setShadowColor
+ * 
+ * Enables or disables and configures the current shadow.
+ */
+mxAbstractCanvas2D.prototype.setShadowColor = function(value)
+{
+	if (value == mxConstants.NONE)
+	{
+		value = null;
+	}
+	
+	this.state.shadowColor = value;
+};
+
+/**
+ * Function: setShadowAlpha
+ * 
+ * Enables or disables and configures the current shadow.
+ */
+mxAbstractCanvas2D.prototype.setShadowAlpha = function(value)
+{
+	this.state.shadowAlpha = value;
+};
+
+/**
+ * Function: setShadowOffset
+ * 
+ * Enables or disables and configures the current shadow.
+ */
+mxAbstractCanvas2D.prototype.setShadowOffset = function(dx, dy)
+{
+	this.state.shadowDx = dx;
+	this.state.shadowDy = dy;
+};
+
+/**
+ * Function: begin
+ * 
+ * Starts a new path.
+ */
+mxAbstractCanvas2D.prototype.begin = function()
+{
+	this.lastX = 0;
+	this.lastY = 0;
+	this.path = [];
+};
+
+/**
+ * Function: moveTo
+ * 
+ *  Moves the current path the given coordinates.
+ */
+mxAbstractCanvas2D.prototype.moveTo = function(x, y)
+{
+	this.addOp(this.moveOp, x, y);
+};
+
+/**
+ * Function: lineTo
+ * 
+ * Draws a line to the given coordinates. Uses moveTo with the op argument.
+ */
+mxAbstractCanvas2D.prototype.lineTo = function(x, y)
+{
+	this.addOp(this.lineOp, x, y);
+};
+
+/**
+ * Function: quadTo
+ * 
+ * Adds a quadratic curve to the current path.
+ */
+mxAbstractCanvas2D.prototype.quadTo = function(x1, y1, x2, y2)
+{
+	this.addOp(this.quadOp, x1, y1, x2, y2);
+};
+
+/**
+ * Function: curveTo
+ * 
+ * Adds a bezier curve to the current path.
+ */
+mxAbstractCanvas2D.prototype.curveTo = function(x1, y1, x2, y2, x3, y3)
+{
+	this.addOp(this.curveOp, x1, y1, x2, y2, x3, y3);
+};
+
+/**
+ * Function: arcTo
+ * 
+ * Adds the given arc to the current path. This is a synthetic operation that
+ * is broken down into curves.
+ */
+mxAbstractCanvas2D.prototype.arcTo = function(rx, ry, angle, largeArcFlag, sweepFlag, x, y)
+{
+	var curves = mxUtils.arcToCurves(this.lastX, this.lastY, rx, ry, angle, largeArcFlag, sweepFlag, x, y);
+	
+	if (curves != null)
+	{
+		for (var i = 0; i < curves.length; i += 6) 
+		{
+			this.curveTo(curves[i], curves[i + 1], curves[i + 2],
+				curves[i + 3], curves[i + 4], curves[i + 5]);
+		}
+	}
+};
+
+/**
+ * Function: close
+ * 
+ * Closes the current path.
+ */
+mxAbstractCanvas2D.prototype.close = function(x1, y1, x2, y2, x3, y3)
+{
+	this.addOp(this.closeOp);
+};
+
+/**
+ * Function: end
+ * 
+ * Empty implementation for backwards compatibility. This will be removed.
+ */
+mxAbstractCanvas2D.prototype.end = function() { };
diff --git a/airavata-kubernetes/web-console/src/assets/js/util/mxAnimation.js b/airavata-kubernetes/web-console/src/assets/js/util/mxAnimation.js
new file mode 100644
index 0000000..eabd9c3
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/util/mxAnimation.js
@@ -0,0 +1,92 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ *
+ * Class: mxAnimation
+ * 
+ * Implements a basic animation in JavaScript.
+ * 
+ * Constructor: mxAnimation
+ * 
+ * Constructs an animation.
+ * 
+ * Parameters:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ */
+function mxAnimation(delay)
+{
+	this.delay = (delay != null) ? delay : 20;
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxAnimation.prototype = new mxEventSource();
+mxAnimation.prototype.constructor = mxAnimation;
+
+/**
+ * Variable: delay
+ * 
+ * Specifies the delay between the animation steps. Defaul is 30ms.
+ */
+mxAnimation.prototype.delay = null;
+
+/**
+ * Variable: thread
+ * 
+ * Reference to the thread while the animation is running.
+ */
+mxAnimation.prototype.thread = null;
+
+/**
+ * Function: isRunning
+ * 
+ * Returns true if the animation is running.
+ */
+mxAnimation.prototype.isRunning = function()
+{
+	return this.thread != null;
+};
+
+/**
+ * Function: startAnimation
+ *
+ * Starts the animation by repeatedly invoking updateAnimation.
+ */
+mxAnimation.prototype.startAnimation = function()
+{
+	if (this.thread == null)
+	{
+		this.thread = window.setInterval(mxUtils.bind(this, this.updateAnimation), this.delay);
+	}
+};
+
+/**
+ * Function: updateAnimation
+ *
+ * Hook for subclassers to implement the animation. Invoke stopAnimation
+ * when finished, startAnimation to resume. This is called whenever the
+ * timer fires and fires an mxEvent.EXECUTE event with no properties.
+ */
+mxAnimation.prototype.updateAnimation = function()
+{
+	this.fireEvent(new mxEventObject(mxEvent.EXECUTE));
+};
+
+/**
+ * Function: stopAnimation
+ *
+ * Stops the animation by deleting the timer and fires an <mxEvent.DONE>.
+ */
+mxAnimation.prototype.stopAnimation = function()
+{
+	if (this.thread != null)
+	{
+		window.clearInterval(this.thread);
+		this.thread = null;
+		this.fireEvent(new mxEventObject(mxEvent.DONE));
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/util/mxAutoSaveManager.js b/airavata-kubernetes/web-console/src/assets/js/util/mxAutoSaveManager.js
new file mode 100644
index 0000000..ba9a41f
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/util/mxAutoSaveManager.js
@@ -0,0 +1,213 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxAutoSaveManager
+ * 
+ * Manager for automatically saving diagrams. The <save> hook must be
+ * implemented.
+ * 
+ * Example:
+ * 
+ * (code)
+ * var mgr = new mxAutoSaveManager(editor.graph);
+ * mgr.save = function()
+ * {
+ *   mxLog.show();
+ *   mxLog.debug('save');
+ * };
+ * (end)
+ * 
+ * Constructor: mxAutoSaveManager
+ *
+ * Constructs a new automatic layout for the given graph.
+ *
+ * Arguments:
+ * 
+ * graph - Reference to the enclosing graph. 
+ */
+function mxAutoSaveManager(graph)
+{
+	// Notifies the manager of a change
+	this.changeHandler = mxUtils.bind(this, function(sender, evt)
+	{
+		if (this.isEnabled())
+		{
+			this.graphModelChanged(evt.getProperty('edit').changes);
+		}
+	});
+
+	this.setGraph(graph);
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxAutoSaveManager.prototype = new mxEventSource();
+mxAutoSaveManager.prototype.constructor = mxAutoSaveManager;
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxAutoSaveManager.prototype.graph = null;
+
+/**
+ * Variable: autoSaveDelay
+ * 
+ * Minimum amount of seconds between two consecutive autosaves. Eg. a
+ * value of 1 (s) means the graph is not stored more than once per second.
+ * Default is 10.
+ */
+mxAutoSaveManager.prototype.autoSaveDelay = 10;
+
+/**
+ * Variable: autoSaveThrottle
+ * 
+ * Minimum amount of seconds between two consecutive autosaves triggered by
+ * more than <autoSaveThreshhold> changes within a timespan of less than
+ * <autoSaveDelay> seconds. Eg. a value of 1 (s) means the graph is not
+ * stored more than once per second even if there are more than
+ * <autoSaveThreshold> changes within that timespan. Default is 2.
+ */
+mxAutoSaveManager.prototype.autoSaveThrottle = 2;
+
+/**
+ * Variable: autoSaveThreshold
+ * 
+ * Minimum amount of ignored changes before an autosave. Eg. a value of 2
+ * means after 2 change of the graph model the autosave will trigger if the
+ * condition below is true. Default is 5.
+ */
+mxAutoSaveManager.prototype.autoSaveThreshold = 5;
+
+/**
+ * Variable: ignoredChanges
+ * 
+ * Counter for ignored changes in autosave.
+ */
+mxAutoSaveManager.prototype.ignoredChanges = 0;
+
+/**
+ * Variable: lastSnapshot
+ * 
+ * Used for autosaving. See <autosave>.
+ */
+mxAutoSaveManager.prototype.lastSnapshot = 0;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies if event handling is enabled. Default is true.
+ */
+mxAutoSaveManager.prototype.enabled = true;
+
+/**
+ * Variable: changeHandler
+ * 
+ * Holds the function that handles graph model changes.
+ */
+mxAutoSaveManager.prototype.changeHandler = null;
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxAutoSaveManager.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ * 
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ * 
+ * Parameters:
+ * 
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxAutoSaveManager.prototype.setEnabled = function(value)
+{
+	this.enabled = value;
+};
+
+/**
+ * Function: setGraph
+ * 
+ * Sets the graph that the layouts operate on.
+ */
+mxAutoSaveManager.prototype.setGraph = function(graph)
+{
+	if (this.graph != null)
+	{
+		this.graph.getModel().removeListener(this.changeHandler);
+	}
+	
+	this.graph = graph;
+	
+	if (this.graph != null)
+	{
+		this.graph.getModel().addListener(mxEvent.CHANGE, this.changeHandler);
+	}
+};
+
+/**
+ * Function: save
+ * 
+ * Empty hook that is called if the graph should be saved.
+ */
+mxAutoSaveManager.prototype.save = function()
+{
+	// empty
+};
+
+/**
+ * Function: graphModelChanged
+ * 
+ * Invoked when the graph model has changed.
+ */
+mxAutoSaveManager.prototype.graphModelChanged = function(changes)
+{
+	var now = new Date().getTime();
+	var dt = (now - this.lastSnapshot) / 1000;
+	
+	if (dt > this.autoSaveDelay ||
+		(this.ignoredChanges >= this.autoSaveThreshold &&
+		 dt > this.autoSaveThrottle))
+	{
+		this.save();
+		this.reset();
+	}
+	else
+	{
+		// Increments the number of ignored changes
+		this.ignoredChanges++;
+	}
+};
+
+/**
+ * Function: reset
+ * 
+ * Resets all counters.
+ */
+mxAutoSaveManager.prototype.reset = function()
+{
+	this.lastSnapshot = new Date().getTime();
+	this.ignoredChanges = 0;
+};
+
+/**
+ * Function: destroy
+ * 
+ * Removes all handlers from the <graph> and deletes the reference to it.
+ */
+mxAutoSaveManager.prototype.destroy = function()
+{
+	this.setGraph(null);
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/util/mxClipboard.js b/airavata-kubernetes/web-console/src/assets/js/util/mxClipboard.js
new file mode 100644
index 0000000..454e2e7
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/util/mxClipboard.js
@@ -0,0 +1,221 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+var mxClipboard =
+{
+	/**
+	 * Class: mxClipboard
+	 * 
+	 * Singleton that implements a clipboard for graph cells.
+	 *
+	 * Example:
+	 * 
+	 * (code)
+	 * mxClipboard.copy(graph);
+	 * mxClipboard.paste(graph2);
+	 * (end)
+	 *
+	 * This copies the selection cells from the graph to the clipboard and
+	 * pastes them into graph2.
+	 * 
+	 * For fine-grained control of the clipboard data the <mxGraph.canExportCell>
+	 * and <mxGraph.canImportCell> functions can be overridden.
+	 * 
+	 * To restore previous parents for pasted cells, the implementation for
+	 * <copy> and <paste> can be changed as follows.
+	 * 
+	 * (code)
+	 * mxClipboard.copy = function(graph, cells)
+	 * {
+	 *   cells = cells || graph.getSelectionCells();
+	 *   var result = graph.getExportableCells(cells);
+	 *   
+	 *   mxClipboard.parents = new Object();
+	 *   
+	 *   for (var i = 0; i < result.length; i++)
+	 *   {
+	 *     mxClipboard.parents[i] = graph.model.getParent(cells[i]);
+	 *   }
+	 *   
+	 *   mxClipboard.insertCount = 1;
+	 *   mxClipboard.setCells(graph.cloneCells(result));
+	 *   
+	 *   return result;
+	 * };
+	 * 
+	 * mxClipboard.paste = function(graph)
+	 * {
+	 *   if (!mxClipboard.isEmpty())
+	 *   {
+	 *     var cells = graph.getImportableCells(mxClipboard.getCells());
+	 *     var delta = mxClipboard.insertCount * mxClipboard.STEPSIZE;
+	 *     var parent = graph.getDefaultParent();
+	 *     
+	 *     graph.model.beginUpdate();
+	 *     try
+	 *     {
+	 *       for (var i = 0; i < cells.length; i++)
+	 *       {
+	 *         var tmp = (mxClipboard.parents != null && graph.model.contains(mxClipboard.parents[i])) ?
+	 *              mxClipboard.parents[i] : parent;
+	 *         cells[i] = graph.importCells([cells[i]], delta, delta, tmp)[0];
+	 *       }
+	 *     }
+	 *     finally
+	 *     {
+	 *       graph.model.endUpdate();
+	 *     }
+	 *     
+	 *     // Increments the counter and selects the inserted cells
+	 *     mxClipboard.insertCount++;
+	 *     graph.setSelectionCells(cells);
+	 *   }
+	 * };
+	 * (end)
+	 * 
+	 * Variable: STEPSIZE
+	 * 
+	 * Defines the step size to offset the cells after each paste operation.
+	 * Default is 10.
+	 */
+	STEPSIZE: 10,
+
+	/**
+	 * Variable: insertCount
+	 * 
+	 * Counts the number of times the clipboard data has been inserted.
+	 */
+	insertCount: 1,
+
+	/**
+	 * Variable: cells
+	 * 
+	 * Holds the array of <mxCells> currently in the clipboard.
+	 */
+	cells: null,
+
+	/**
+	 * Function: setCells
+	 * 
+	 * Sets the cells in the clipboard. Fires a <mxEvent.CHANGE> event.
+	 */
+	setCells: function(cells)
+	{
+		mxClipboard.cells = cells;
+	},
+
+	/**
+	 * Function: getCells
+	 * 
+	 * Returns  the cells in the clipboard.
+	 */
+	getCells: function()
+	{
+		return mxClipboard.cells;
+	},
+	
+	/**
+	 * Function: isEmpty
+	 * 
+	 * Returns true if the clipboard currently has not data stored.
+	 */
+	isEmpty: function()
+	{
+		return mxClipboard.getCells() == null;
+	},
+	
+	/**
+	 * Function: cut
+	 * 
+	 * Cuts the given array of <mxCells> from the specified graph.
+	 * If cells is null then the selection cells of the graph will
+	 * be used. Returns the cells that have been cut from the graph.
+	 *
+	 * Parameters:
+	 * 
+	 * graph - <mxGraph> that contains the cells to be cut.
+	 * cells - Optional array of <mxCells> to be cut.
+	 */
+	cut: function(graph, cells)
+	{
+		cells = mxClipboard.copy(graph, cells);
+		mxClipboard.insertCount = 0;
+		mxClipboard.removeCells(graph, cells);
+		
+		return cells;
+	},
+
+	/**
+	 * Function: removeCells
+	 * 
+	 * Hook to remove the given cells from the given graph after
+	 * a cut operation.
+	 *
+	 * Parameters:
+	 * 
+	 * graph - <mxGraph> that contains the cells to be cut.
+	 * cells - Array of <mxCells> to be cut.
+	 */
+	removeCells: function(graph, cells)
+	{
+		graph.removeCells(cells);
+	},
+
+	/**
+	 * Function: copy
+	 * 
+	 * Copies the given array of <mxCells> from the specified
+	 * graph to <cells>. Returns the original array of cells that has
+	 * been cloned. Descendants of cells in the array are ignored.
+	 * 
+	 * Parameters:
+	 * 
+	 * graph - <mxGraph> that contains the cells to be copied.
+	 * cells - Optional array of <mxCells> to be copied.
+	 */
+	copy: function(graph, cells)
+	{
+		cells = cells || graph.getSelectionCells();
+		var result = graph.getExportableCells(graph.model.getTopmostCells(cells));
+		mxClipboard.insertCount = 1;
+		mxClipboard.setCells(graph.cloneCells(result));
+
+		return result;
+	},
+
+	/**
+	 * Function: paste
+	 * 
+	 * Pastes the <cells> into the specified graph restoring
+	 * the relation to <parents>, if possible. If the parents
+	 * are no longer in the graph or invisible then the
+	 * cells are added to the graph's default or into the
+	 * swimlane under the cell's new location if one exists.
+	 * The cells are added to the graph using <mxGraph.importCells>
+	 * and returned.
+	 * 
+	 * Parameters:
+	 * 
+	 * graph - <mxGraph> to paste the <cells> into.
+	 */
+	paste: function(graph)
+	{
+		var cells = null;
+		
+		if (!mxClipboard.isEmpty())
+		{
+			cells = graph.getImportableCells(mxClipboard.getCells());
+			var delta = mxClipboard.insertCount * mxClipboard.STEPSIZE;
+			var parent = graph.getDefaultParent();
+			cells = graph.importCells(cells, delta, delta, parent);
+			
+			// Increments the counter and selects the inserted cells
+			mxClipboard.insertCount++;
+			graph.setSelectionCells(cells);
+		}
+		
+		return cells;
+	}
+
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/util/mxConstants.js b/airavata-kubernetes/web-console/src/assets/js/util/mxConstants.js
new file mode 100644
index 0000000..c448644
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/util/mxConstants.js
@@ -0,0 +1,2275 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+ var mxConstants =
+ {
+	/**
+	 * Class: mxConstants
+	 * 
+	 * Defines various global constants.
+	 * 
+	 * Variable: DEFAULT_HOTSPOT
+	 * 
+	 * Defines the portion of the cell which is to be used as a connectable
+	 * region. Default is 0.3. Possible values are 0 < x <= 1. 
+	 */
+	DEFAULT_HOTSPOT: 0.3,
+
+	/**
+	 * Variable: MIN_HOTSPOT_SIZE
+	 * 
+	 * Defines the minimum size in pixels of the portion of the cell which is
+	 * to be used as a connectable region. Default is 8.
+	 */
+	MIN_HOTSPOT_SIZE: 8,
+
+	/**
+	 * Variable: MAX_HOTSPOT_SIZE
+	 * 
+	 * Defines the maximum size in pixels of the portion of the cell which is
+	 * to be used as a connectable region. Use 0 for no maximum. Default is 0.
+	 */
+	MAX_HOTSPOT_SIZE: 0,
+
+	/**
+	 * Variable: RENDERING_HINT_EXACT
+	 * 
+	 * Defines the exact rendering hint.
+	 */
+	RENDERING_HINT_EXACT: 'exact',
+
+	/**
+	 * Variable: RENDERING_HINT_FASTER
+	 * 
+	 * Defines the faster rendering hint.
+	 */
+	RENDERING_HINT_FASTER: 'faster',
+
+	/**
+	 * Variable: RENDERING_HINT_FASTEST
+	 * 
+	 * Defines the fastest rendering hint.
+	 */
+	RENDERING_HINT_FASTEST: 'fastest',
+
+	/**
+	 * Variable: DIALECT_SVG
+	 * 
+	 * Defines the SVG display dialect name.
+	 */
+	DIALECT_SVG: 'svg',
+
+	/**
+	 * Variable: DIALECT_VML
+	 * 
+	 * Defines the VML display dialect name.
+	 */
+	DIALECT_VML: 'vml',
+
+	/**
+	 * Variable: DIALECT_MIXEDHTML
+	 * 
+	 * Defines the mixed HTML display dialect name.
+	 */
+	DIALECT_MIXEDHTML: 'mixedHtml',
+
+	/**
+	 * Variable: DIALECT_PREFERHTML
+	 * 
+	 * Defines the preferred HTML display dialect name.
+	 */
+	DIALECT_PREFERHTML: 'preferHtml',
+
+	/**
+	 * Variable: DIALECT_STRICTHTML
+	 * 
+	 * Defines the strict HTML display dialect.
+	 */
+	DIALECT_STRICTHTML: 'strictHtml',
+
+	/**
+	 * Variable: NS_SVG
+	 * 
+	 * Defines the SVG namespace.
+	 */
+	NS_SVG: 'http://www.w3.org/2000/svg',
+
+	/**
+	 * Variable: NS_XHTML
+	 * 
+	 * Defines the XHTML namespace.
+	 */
+	NS_XHTML: 'http://www.w3.org/1999/xhtml',
+
+	/**
+	 * Variable: NS_XLINK
+	 * 
+	 * Defines the XLink namespace.
+	 */
+	NS_XLINK: 'http://www.w3.org/1999/xlink',
+
+	/**
+	 * Variable: SHADOWCOLOR
+	 * 
+	 * Defines the color to be used to draw shadows in shapes and windows.
+	 * Default is gray.
+	 */
+	SHADOWCOLOR: 'gray',
+
+	/**
+	 * Variable: VML_SHADOWCOLOR
+	 * 
+	 * Used for shadow color in filters where transparency is not supported
+	 * (Microsoft Internet Explorer). Default is gray.
+	 */
+	VML_SHADOWCOLOR: 'gray',
+
+	/**
+	 * Variable: SHADOW_OFFSET_X
+	 * 
+	 * Specifies the x-offset of the shadow. Default is 2.
+	 */
+	SHADOW_OFFSET_X: 2,
+
+	/**
+	 * Variable: SHADOW_OFFSET_Y
+	 * 
+	 * Specifies the y-offset of the shadow. Default is 3.
+	 */
+	SHADOW_OFFSET_Y: 3,
+	
+	/**
+	 * Variable: SHADOW_OPACITY
+	 * 
+	 * Defines the opacity for shadows. Default is 1.
+	 */
+	SHADOW_OPACITY: 1,
+ 
+	/**
+	 * Variable: NODETYPE_ELEMENT
+	 * 
+	 * DOM node of type ELEMENT.
+	 */
+	NODETYPE_ELEMENT: 1,
+
+	/**
+	 * Variable: NODETYPE_ATTRIBUTE
+	 * 
+	 * DOM node of type ATTRIBUTE.
+	 */
+	NODETYPE_ATTRIBUTE: 2,
+
+	/**
+	 * Variable: NODETYPE_TEXT
+	 * 
+	 * DOM node of type TEXT.
+	 */
+	NODETYPE_TEXT: 3,
+
+	/**
+	 * Variable: NODETYPE_CDATA
+	 * 
+	 * DOM node of type CDATA.
+	 */
+	NODETYPE_CDATA: 4,
+	
+	/**
+	 * Variable: NODETYPE_ENTITY_REFERENCE
+	 * 
+	 * DOM node of type ENTITY_REFERENCE.
+	 */
+	NODETYPE_ENTITY_REFERENCE: 5,
+
+	/**
+	 * Variable: NODETYPE_ENTITY
+	 * 
+	 * DOM node of type ENTITY.
+	 */
+	NODETYPE_ENTITY: 6,
+
+	/**
+	 * Variable: NODETYPE_PROCESSING_INSTRUCTION
+	 * 
+	 * DOM node of type PROCESSING_INSTRUCTION.
+	 */
+	NODETYPE_PROCESSING_INSTRUCTION: 7,
+
+	/**
+	 * Variable: NODETYPE_COMMENT
+	 * 
+	 * DOM node of type COMMENT.
+	 */
+	NODETYPE_COMMENT: 8,
+		
+	/**
+	 * Variable: NODETYPE_DOCUMENT
+	 * 
+	 * DOM node of type DOCUMENT.
+	 */
+	NODETYPE_DOCUMENT: 9,
+
+	/**
+	 * Variable: NODETYPE_DOCUMENTTYPE
+	 * 
+	 * DOM node of type DOCUMENTTYPE.
+	 */
+	NODETYPE_DOCUMENTTYPE: 10,
+
+	/**
+	 * Variable: NODETYPE_DOCUMENT_FRAGMENT
+	 * 
+	 * DOM node of type DOCUMENT_FRAGMENT.
+	 */
+	NODETYPE_DOCUMENT_FRAGMENT: 11,
+
+	/**
+	 * Variable: NODETYPE_NOTATION
+	 * 
+	 * DOM node of type NOTATION.
+	 */
+	NODETYPE_NOTATION: 12,
+	
+	/**
+	 * Variable: TOOLTIP_VERTICAL_OFFSET
+	 * 
+	 * Defines the vertical offset for the tooltip.
+	 * Default is 16.
+	 */
+	TOOLTIP_VERTICAL_OFFSET: 16,
+
+	/**
+	 * Variable: DEFAULT_VALID_COLOR
+	 * 
+	 * Specifies the default valid color. Default is #0000FF.
+	 */
+	DEFAULT_VALID_COLOR: '#00FF00',
+
+	/**
+	 * Variable: DEFAULT_INVALID_COLOR
+	 * 
+	 * Specifies the default invalid color. Default is #FF0000.
+	 */
+	DEFAULT_INVALID_COLOR: '#FF0000',
+
+	/**
+	 * Variable: OUTLINE_HIGHLIGHT_COLOR
+	 * 
+	 * Specifies the default highlight color for shape outlines.
+	 * Default is #0000FF. This is used in <mxEdgeHandler>.
+	 */
+	OUTLINE_HIGHLIGHT_COLOR: '#00FF00',
+
+	/**
+	 * Variable: OUTLINE_HIGHLIGHT_COLOR
+	 * 
+	 * Defines the strokewidth to be used for shape outlines.
+	 * Default is 5. This is used in <mxEdgeHandler>.
+	 */
+	OUTLINE_HIGHLIGHT_STROKEWIDTH: 5,
+
+	/**
+	 * Variable: HIGHLIGHT_STROKEWIDTH
+	 * 
+	 * Defines the strokewidth to be used for the highlights.
+	 * Default is 3.
+	 */
+	HIGHLIGHT_STROKEWIDTH: 3,
+
+	/**
+	 * Variable: CONSTRAINT_HIGHLIGHT_SIZE
+	 * 
+	 * Size of the constraint highlight (in px). Default is 2.
+	 */
+	HIGHLIGHT_SIZE: 2,
+	
+	/**
+	 * Variable: HIGHLIGHT_OPACITY
+	 * 
+	 * Opacity (in %) used for the highlights (including outline).
+	 * Default is 100.
+	 */
+	HIGHLIGHT_OPACITY: 100,
+	
+	/**
+	 * Variable: CURSOR_MOVABLE_VERTEX
+	 * 
+	 * Defines the cursor for a movable vertex. Default is 'move'.
+	 */
+	CURSOR_MOVABLE_VERTEX: 'move',
+	
+	/**
+	 * Variable: CURSOR_MOVABLE_EDGE
+	 * 
+	 * Defines the cursor for a movable edge. Default is 'move'.
+	 */
+	CURSOR_MOVABLE_EDGE: 'move',
+	
+	/**
+	 * Variable: CURSOR_LABEL_HANDLE
+	 * 
+	 * Defines the cursor for a movable label. Default is 'default'.
+	 */
+	CURSOR_LABEL_HANDLE: 'default',
+	
+	/**
+	 * Variable: CURSOR_TERMINAL_HANDLE
+	 * 
+	 * Defines the cursor for a terminal handle. Default is 'pointer'.
+	 */
+	CURSOR_TERMINAL_HANDLE: 'pointer',
+	
+	/**
+	 * Variable: CURSOR_BEND_HANDLE
+	 * 
+	 * Defines the cursor for a movable bend. Default is 'crosshair'.
+	 */
+	CURSOR_BEND_HANDLE: 'crosshair',
+
+	/**
+	 * Variable: CURSOR_VIRTUAL_BEND_HANDLE
+	 * 
+	 * Defines the cursor for a movable bend. Default is 'crosshair'.
+	 */
+	CURSOR_VIRTUAL_BEND_HANDLE: 'crosshair',
+	
+	/**
+	 * Variable: CURSOR_CONNECT
+	 * 
+	 * Defines the cursor for a connectable state. Default is 'pointer'.
+	 */
+	CURSOR_CONNECT: 'pointer',
+
+	/**
+	 * Variable: HIGHLIGHT_COLOR
+	 * 
+	 * Defines the color to be used for the cell highlighting.
+	 * Use 'none' for no color. Default is #00FF00.
+	 */
+	HIGHLIGHT_COLOR: '#00FF00',
+
+	/**
+	 * Variable: TARGET_HIGHLIGHT_COLOR
+	 * 
+	 * Defines the color to be used for highlighting a target cell for a new
+	 * or changed connection. Note that this may be either a source or
+	 * target terminal in the graph. Use 'none' for no color.
+	 * Default is #0000FF.
+	 */
+	CONNECT_TARGET_COLOR: '#0000FF',
+
+	/**
+	 * Variable: INVALID_CONNECT_TARGET_COLOR
+	 * 
+	 * Defines the color to be used for highlighting a invalid target cells
+	 * for a new or changed connections. Note that this may be either a source
+	 * or target terminal in the graph. Use 'none' for no color. Default is
+	 * #FF0000.
+	 */
+	INVALID_CONNECT_TARGET_COLOR: '#FF0000',
+
+	/**
+	 * Variable: DROP_TARGET_COLOR
+	 * 
+	 * Defines the color to be used for the highlighting target parent cells
+	 * (for drag and drop). Use 'none' for no color. Default is #0000FF.
+	 */
+	DROP_TARGET_COLOR: '#0000FF',
+
+	/**
+	 * Variable: VALID_COLOR
+	 * 
+	 * Defines the color to be used for the coloring valid connection
+	 * previews. Use 'none' for no color. Default is #FF0000.
+	 */
+	VALID_COLOR: '#00FF00',
+
+	/**
+	 * Variable: INVALID_COLOR
+	 * 
+	 * Defines the color to be used for the coloring invalid connection
+	 * previews. Use 'none' for no color. Default is #FF0000.
+	 */
+	INVALID_COLOR: '#FF0000',
+
+	/**
+	 * Variable: EDGE_SELECTION_COLOR
+	 * 
+	 * Defines the color to be used for the selection border of edges. Use
+	 * 'none' for no color. Default is #00FF00.
+	 */
+	EDGE_SELECTION_COLOR: '#00FF00',
+
+	/**
+	 * Variable: VERTEX_SELECTION_COLOR
+	 * 
+	 * Defines the color to be used for the selection border of vertices. Use
+	 * 'none' for no color. Default is #00FF00.
+	 */
+	VERTEX_SELECTION_COLOR: '#00FF00',
+
+	/**
+	 * Variable: VERTEX_SELECTION_STROKEWIDTH
+	 * 
+	 * Defines the strokewidth to be used for vertex selections.
+	 * Default is 1.
+	 */
+	VERTEX_SELECTION_STROKEWIDTH: 1,
+
+	/**
+	 * Variable: EDGE_SELECTION_STROKEWIDTH
+	 * 
+	 * Defines the strokewidth to be used for edge selections.
+	 * Default is 1.
+	 */
+	EDGE_SELECTION_STROKEWIDTH: 1,
+
+	/**
+	 * Variable: SELECTION_DASHED
+	 * 
+	 * Defines the dashed state to be used for the vertex selection
+	 * border. Default is true.
+	 */
+	VERTEX_SELECTION_DASHED: true,
+
+	/**
+	 * Variable: SELECTION_DASHED
+	 * 
+	 * Defines the dashed state to be used for the edge selection
+	 * border. Default is true.
+	 */
+	EDGE_SELECTION_DASHED: true,
+
+	/**
+	 * Variable: GUIDE_COLOR
+	 * 
+	 * Defines the color to be used for the guidelines in mxGraphHandler.
+	 * Default is #FF0000.
+	 */
+	GUIDE_COLOR: '#FF0000',
+
+	/**
+	 * Variable: GUIDE_STROKEWIDTH
+	 * 
+	 * Defines the strokewidth to be used for the guidelines in mxGraphHandler.
+	 * Default is 1.
+	 */
+	GUIDE_STROKEWIDTH: 1,
+
+	/**
+	 * Variable: OUTLINE_COLOR
+	 * 
+	 * Defines the color to be used for the outline rectangle
+	 * border.  Use 'none' for no color. Default is #0099FF.
+	 */
+	OUTLINE_COLOR: '#0099FF',
+
+	/**
+	 * Variable: OUTLINE_STROKEWIDTH
+	 * 
+	 * Defines the strokewidth to be used for the outline rectangle
+	 * stroke width. Default is 3.
+	 */
+	OUTLINE_STROKEWIDTH: (mxClient.IS_IE) ? 2 : 3,
+
+	/**
+	 * Variable: HANDLE_SIZE
+	 * 
+	 * Defines the default size for handles. Default is 6.
+	 */
+	HANDLE_SIZE: 6,
+
+	/**
+	 * Variable: LABEL_HANDLE_SIZE
+	 * 
+	 * Defines the default size for label handles. Default is 4.
+	 */
+	LABEL_HANDLE_SIZE: 4,
+
+	/**
+	 * Variable: HANDLE_FILLCOLOR
+	 * 
+	 * Defines the color to be used for the handle fill color. Use 'none' for
+	 * no color. Default is #00FF00 (green).
+	 */
+	HANDLE_FILLCOLOR: '#00FF00',
+
+	/**
+	 * Variable: HANDLE_STROKECOLOR
+	 * 
+	 * Defines the color to be used for the handle stroke color. Use 'none' for
+	 * no color. Default is black.
+	 */
+	HANDLE_STROKECOLOR: 'black',
+
+	/**
+	 * Variable: LABEL_HANDLE_FILLCOLOR
+	 * 
+	 * Defines the color to be used for the label handle fill color. Use 'none'
+	 * for no color. Default is yellow.
+	 */
+	LABEL_HANDLE_FILLCOLOR: 'yellow',
+
+	/**
+	 * Variable: CONNECT_HANDLE_FILLCOLOR
+	 * 
+	 * Defines the color to be used for the connect handle fill color. Use
+	 * 'none' for no color. Default is #0000FF (blue).
+	 */
+	CONNECT_HANDLE_FILLCOLOR: '#0000FF',
+
+	/**
+	 * Variable: LOCKED_HANDLE_FILLCOLOR
+	 * 
+	 * Defines the color to be used for the locked handle fill color. Use
+	 * 'none' for no color. Default is #FF0000 (red).
+	 */
+	LOCKED_HANDLE_FILLCOLOR: '#FF0000',
+
+	/**
+	 * Variable: OUTLINE_HANDLE_FILLCOLOR
+	 * 
+	 * Defines the color to be used for the outline sizer fill color. Use
+	 * 'none' for no color. Default is #00FFFF.
+	 */
+	OUTLINE_HANDLE_FILLCOLOR: '#00FFFF',
+
+	/**
+	 * Variable: OUTLINE_HANDLE_STROKECOLOR
+	 * 
+	 * Defines the color to be used for the outline sizer stroke color. Use
+	 * 'none' for no color. Default is #0033FF.
+	 */
+	OUTLINE_HANDLE_STROKECOLOR: '#0033FF',
+
+	/**
+	 * Variable: DEFAULT_FONTFAMILY
+	 * 
+	 * Defines the default family for all fonts. Default is Arial,Helvetica.
+	 */
+	DEFAULT_FONTFAMILY: 'Arial,Helvetica',
+
+	/**
+	 * Variable: DEFAULT_FONTSIZE
+	 * 
+	 * Defines the default size (in px). Default is 11.
+	 */
+	DEFAULT_FONTSIZE: 11,
+
+	/**
+	 * Variable: DEFAULT_TEXT_DIRECTION
+	 * 
+	 * Defines the default value for the <STYLE_TEXT_DIRECTION> if no value is
+	 * defined for it in the style. Default value is an empty string which means
+	 * the default system setting is used and no direction is set.
+	 */
+	DEFAULT_TEXT_DIRECTION: '',
+
+	/**
+	 * Variable: LINE_HEIGHT
+	 * 
+	 * Defines the default line height for text labels. Default is 1.2.
+	 */
+	LINE_HEIGHT: 1.2,
+
+	/**
+	 * Variable: WORD_WRAP
+	 * 
+	 * Defines the CSS value for the word-wrap property. Default is "normal".
+	 * Change this to "break-word" to allow long words to be able to be broken
+	 * and wrap onto the next line.
+	 */
+	WORD_WRAP: 'normal',
+
+	/**
+	 * Variable: ABSOLUTE_LINE_HEIGHT
+	 * 
+	 * Specifies if absolute line heights should be used (px) in CSS. Default
+	 * is false. Set this to true for backwards compatibility.
+	 */
+	ABSOLUTE_LINE_HEIGHT: false,
+
+	/**
+	 * Variable: DEFAULT_FONTSTYLE
+	 * 
+	 * Defines the default style for all fonts. Default is 0. This can be set
+	 * to any combination of font styles as follows.
+	 * 
+	 * (code)
+	 * mxConstants.DEFAULT_FONTSTYLE = mxConstants.FONT_BOLD | mxConstants.FONT_ITALIC;
+	 * (end)
+	 */
+	DEFAULT_FONTSTYLE: 0,
+
+	/**
+	 * Variable: DEFAULT_STARTSIZE
+	 * 
+	 * Defines the default start size for swimlanes. Default is 40.
+	 */
+	DEFAULT_STARTSIZE: 40,
+
+	/**
+	 * Variable: DEFAULT_MARKERSIZE
+	 * 
+	 * Defines the default size for all markers. Default is 6.
+	 */
+	DEFAULT_MARKERSIZE: 6,
+
+	/**
+	 * Variable: DEFAULT_IMAGESIZE
+	 * 
+	 * Defines the default width and height for images used in the
+	 * label shape. Default is 24.
+	 */
+	DEFAULT_IMAGESIZE: 24,
+
+	/**
+	 * Variable: ENTITY_SEGMENT
+	 * 
+	 * Defines the length of the horizontal segment of an Entity Relation.
+	 * This can be overridden using <mxConstants.STYLE_SEGMENT> style.
+	 * Default is 30.
+	 */
+	ENTITY_SEGMENT: 30,
+
+	/**
+	 * Variable: RECTANGLE_ROUNDING_FACTOR
+	 * 
+	 * Defines the rounding factor for rounded rectangles in percent between
+	 * 0 and 1. Values should be smaller than 0.5. Default is 0.15.
+	 */
+	RECTANGLE_ROUNDING_FACTOR: 0.15,
+
+	/**
+	 * Variable: LINE_ARCSIZE
+	 * 
+	 * Defines the size of the arcs for rounded edges. Default is 20.
+	 */
+	LINE_ARCSIZE: 20,
+
+	/**
+	 * Variable: ARROW_SPACING
+	 * 
+	 * Defines the spacing between the arrow shape and its terminals. Default is 0.
+	 */
+	ARROW_SPACING: 0,
+
+	/**
+	 * Variable: ARROW_WIDTH
+	 * 
+	 * Defines the width of the arrow shape. Default is 30.
+	 */
+	ARROW_WIDTH: 30,
+
+	/**
+	 * Variable: ARROW_SIZE
+	 * 
+	 * Defines the size of the arrowhead in the arrow shape. Default is 30.
+	 */
+	ARROW_SIZE: 30,
+
+	/**
+	 * Variable: PAGE_FORMAT_A4_PORTRAIT
+	 * 
+	 * Defines the rectangle for the A4 portrait page format. The dimensions
+	 * of this page format are 826x1169 pixels.
+	 */
+	PAGE_FORMAT_A4_PORTRAIT: new mxRectangle(0, 0, 827, 1169),
+
+	/**
+	 * Variable: PAGE_FORMAT_A4_PORTRAIT
+	 * 
+	 * Defines the rectangle for the A4 portrait page format. The dimensions
+	 * of this page format are 826x1169 pixels.
+	 */
+	PAGE_FORMAT_A4_LANDSCAPE: new mxRectangle(0, 0, 1169, 827),
+
+	/**
+	 * Variable: PAGE_FORMAT_LETTER_PORTRAIT
+	 * 
+	 * Defines the rectangle for the Letter portrait page format. The
+	 * dimensions of this page format are 850x1100 pixels.
+	 */
+	PAGE_FORMAT_LETTER_PORTRAIT: new mxRectangle(0, 0, 850, 1100),
+
+	/**
+	 * Variable: PAGE_FORMAT_LETTER_PORTRAIT
+	 * 
+	 * Defines the rectangle for the Letter portrait page format. The dimensions
+	 * of this page format are 850x1100 pixels.
+	 */
+	PAGE_FORMAT_LETTER_LANDSCAPE: new mxRectangle(0, 0, 1100, 850),
+
+	/**
+	 * Variable: NONE
+	 * 
+	 * Defines the value for none. Default is "none".
+	 */
+	NONE: 'none',
+
+	/**
+	 * Variable: STYLE_PERIMETER
+	 * 
+	 * Defines the key for the perimeter style. This is a function that defines
+	 * the perimeter around a particular shape. Possible values are the
+	 * functions defined in <mxPerimeter>. Alternatively, the constants in this
+	 * class that start with "PERIMETER_" may be used to access
+	 * perimeter styles in <mxStyleRegistry>. Value is "perimeter".
+	 */
+	STYLE_PERIMETER: 'perimeter',
+	
+	/**
+	 * Variable: STYLE_SOURCE_PORT
+	 * 
+	 * Defines the ID of the cell that should be used for computing the
+	 * perimeter point of the source for an edge. This allows for graphically
+	 * connecting to a cell while keeping the actual terminal of the edge.
+	 * Value is "sourcePort".
+	 */
+	STYLE_SOURCE_PORT: 'sourcePort',
+	
+	/**
+	 * Variable: STYLE_TARGET_PORT
+	 * 
+	 * Defines the ID of the cell that should be used for computing the
+	 * perimeter point of the target for an edge. This allows for graphically
+	 * connecting to a cell while keeping the actual terminal of the edge.
+	 * Value is "targetPort".
+	 */
+	STYLE_TARGET_PORT: 'targetPort',
+
+	/**
+	 * Variable: STYLE_PORT_CONSTRAINT
+	 * 
+	 * Defines the direction(s) that edges are allowed to connect to cells in.
+	 * Possible values are "DIRECTION_NORTH, DIRECTION_SOUTH, 
+	 * DIRECTION_EAST" and "DIRECTION_WEST". Value is
+	 * "portConstraint".
+	 */
+	STYLE_PORT_CONSTRAINT: 'portConstraint',
+
+	/**
+	 * Variable: STYLE_PORT_CONSTRAINT_ROTATION
+	 * 
+	 * Define whether port constraint directions are rotated with vertex
+	 * rotation. 0 (default) causes port constraints to remain absolute, 
+	 * relative to the graph, 1 causes the constraints to rotate with
+	 * the vertex. Value is "portConstraintRotation".
+	 */
+	STYLE_PORT_CONSTRAINT_ROTATION: 'portConstraintRotation',
+
+	/**
+	 * Variable: STYLE_SOURCE_PORT_CONSTRAINT
+	 * 
+	 * Defines the direction(s) that edges are allowed to connect to sources in.
+	 * Possible values are "DIRECTION_NORTH, DIRECTION_SOUTH, DIRECTION_EAST"
+	 * and "DIRECTION_WEST". Value is "sourcePortConstraint".
+	 */
+	STYLE_SOURCE_PORT_CONSTRAINT: 'sourcePortConstraint',
+
+	/**
+	 * Variable: STYLE_TARGET_PORT_CONSTRAINT
+	 * 
+	 * Defines the direction(s) that edges are allowed to connect to targets in.
+	 * Possible values are "DIRECTION_NORTH, DIRECTION_SOUTH, DIRECTION_EAST"
+	 * and "DIRECTION_WEST". Value is "targetPortConstraint".
+	 */
+	STYLE_TARGET_PORT_CONSTRAINT: 'targetPortConstraint',
+
+	/**
+	 * Variable: STYLE_OPACITY
+	 * 
+	 * Defines the key for the opacity style. The type of the value is 
+	 * numeric and the possible range is 0-100. Value is "opacity".
+	 */
+	STYLE_OPACITY: 'opacity',
+
+	/**
+	 * Variable: STYLE_FILL_OPACITY
+	 * 
+	 * Defines the key for the fill opacity style. The type of the value is 
+	 * numeric and the possible range is 0-100. Value is "fillOpacity".
+	 */
+	STYLE_FILL_OPACITY: 'fillOpacity',
+
+	/**
+	 * Variable: STYLE_STROKE_OPACITY
+	 * 
+	 * Defines the key for the stroke opacity style. The type of the value is 
+	 * numeric and the possible range is 0-100. Value is "strokeOpacity".
+	 */
+	STYLE_STROKE_OPACITY: 'strokeOpacity',
+
+	/**
+	 * Variable: STYLE_TEXT_OPACITY
+	 * 
+	 * Defines the key for the text opacity style. The type of the value is 
+	 * numeric and the possible range is 0-100. Value is "textOpacity".
+	 */
+	STYLE_TEXT_OPACITY: 'textOpacity',
+
+	/**
+	 * Variable: STYLE_TEXT_DIRECTION
+	 * 
+	 * Defines the key for the text direction style. Possible values are
+	 * "TEXT_DIRECTION_DEFAULT, TEXT_DIRECTION_AUTO, TEXT_DIRECTION_LTR"
+	 * and "TEXT_DIRECTION_RTL". Value is "textDirection".
+	 * The default value for the style is defined in <DEFAULT_TEXT_DIRECTION>.
+	 * It is used is no value is defined for this key in a given style. This is
+	 * an experimental style that is currently ignored in the backends.
+	 */
+	STYLE_TEXT_DIRECTION: 'textDirection',
+
+	/**
+	 * Variable: STYLE_OVERFLOW
+	 * 
+	 * Defines the key for the overflow style. Possible values are 'visible',
+	 * 'hidden', 'fill' and 'width'. The default value is 'visible'. This value
+	 * specifies how overlapping vertex labels are handled. A value of
+	 * 'visible' will show the complete label. A value of 'hidden' will clip
+	 * the label so that it does not overlap the vertex bounds. A value of
+	 * 'fill' will use the vertex bounds and a value of 'width' will use the
+	 * the vertex width for the label. See <mxGraph.isLabelClipped>. Note that
+	 * the vertical alignment is ignored for overflow fill and for horizontal
+	 * alignment, left should be used to avoid pixel offsets in Internet Explorer
+	 * 11 and earlier or if foreignObjects are disabled. Value is "overflow".
+	 */
+	STYLE_OVERFLOW: 'overflow',
+
+	/**
+	 * Variable: STYLE_ORTHOGONAL
+	 * 
+	 * Defines if the connection points on either end of the edge should be
+	 * computed so that the edge is vertical or horizontal if possible and
+	 * if the point is not at a fixed location. Default is false. This is
+	 * used in <mxGraph.isOrthogonal>, which also returns true if the edgeStyle
+	 * of the edge is an elbow or entity. Value is "orthogonal".
+	 */
+	STYLE_ORTHOGONAL: 'orthogonal',
+
+	/**
+	 * Variable: STYLE_EXIT_X
+	 * 
+	 * Defines the key for the horizontal relative coordinate connection point
+	 * of an edge with its source terminal. Value is "exitX".
+	 */
+	STYLE_EXIT_X: 'exitX',
+
+	/**
+	 * Variable: STYLE_EXIT_Y
+	 * 
+	 * Defines the key for the vertical relative coordinate connection point
+	 * of an edge with its source terminal. Value is "exitY".
+	 */
+	STYLE_EXIT_Y: 'exitY',
+
+	/**
+	 * Variable: STYLE_EXIT_PERIMETER
+	 * 
+	 * Defines if the perimeter should be used to find the exact entry point
+	 * along the perimeter of the source. Possible values are 0 (false) and
+	 * 1 (true). Default is 1 (true). Value is "exitPerimeter".
+	 */
+	STYLE_EXIT_PERIMETER: 'exitPerimeter',
+
+	/**
+	 * Variable: STYLE_ENTRY_X
+	 * 
+	 * Defines the key for the horizontal relative coordinate connection point
+	 * of an edge with its target terminal. Value is "entryX".
+	 */
+	STYLE_ENTRY_X: 'entryX',
+
+	/**
+	 * Variable: STYLE_ENTRY_Y
+	 * 
+	 * Defines the key for the vertical relative coordinate connection point
+	 * of an edge with its target terminal. Value is "entryY".
+	 */
+	STYLE_ENTRY_Y: 'entryY',
+
+	/**
+	 * Variable: STYLE_ENTRY_PERIMETER
+	 * 
+	 * Defines if the perimeter should be used to find the exact entry point
+	 * along the perimeter of the target. Possible values are 0 (false) and
+	 * 1 (true). Default is 1 (true). Value is "entryPerimeter".
+	 */
+	STYLE_ENTRY_PERIMETER: 'entryPerimeter',
+
+	/**
+	 * Variable: STYLE_WHITE_SPACE
+	 * 
+	 * Defines the key for the white-space style. Possible values are 'nowrap'
+	 * and 'wrap'. The default value is 'nowrap'. This value specifies how
+	 * white-space inside a HTML vertex label should be handled. A value of
+	 * 'nowrap' means the text will never wrap to the next line until a
+	 * linefeed is encountered. A value of 'wrap' means text will wrap when
+	 * necessary. This style is only used for HTML labels.
+	 * See <mxGraph.isWrapping>. Value is "whiteSpace".
+	 */
+	STYLE_WHITE_SPACE: 'whiteSpace',
+
+	/**
+	 * Variable: STYLE_ROTATION
+	 * 
+	 * Defines the key for the rotation style. The type of the value is 
+	 * numeric and the possible range is 0-360. Value is "rotation".
+	 */
+	STYLE_ROTATION: 'rotation',
+
+	/**
+	 * Variable: STYLE_FILLCOLOR
+	 * 
+	 * Defines the key for the fill color. Possible values are all HTML color
+	 * names or HEX codes, as well as special keywords such as 'swimlane,
+	 * 'inherit' or 'indicated' to use the color code of a related cell or the
+	 * indicator shape. Value is "fillColor".
+	 */
+	STYLE_FILLCOLOR: 'fillColor',
+
+	/**
+	 * Variable: STYLE_POINTER_EVENTS
+	 * 
+	 * Specifies if pointer events should be fired on transparent backgrounds.
+	 * This style is currently only supported in <mxRectangleShape>. Default
+	 * is true. Value is "pointerEvents". This is typically set to
+	 * false in groups where the transparent part should allow any underlying
+	 * cells to be clickable.
+	 */
+	STYLE_POINTER_EVENTS: 'pointerEvents',
+
+	/**
+	 * Variable: STYLE_SWIMLANE_FILLCOLOR
+	 * 
+	 * Defines the key for the fill color of the swimlane background. Possible
+	 * values are all HTML color names or HEX codes. Default is no background.
+	 * Value is "swimlaneFillColor".
+	 */
+	STYLE_SWIMLANE_FILLCOLOR: 'swimlaneFillColor',
+
+	/**
+	 * Variable: STYLE_MARGIN
+	 * 
+	 * Defines the key for the margin between the ellipses in the double ellipse shape.
+	 * Possible values are all positive numbers. Value is "margin".
+	 */
+	STYLE_MARGIN: 'margin',
+
+	/**
+	 * Variable: STYLE_GRADIENTCOLOR
+	 * 
+	 * Defines the key for the gradient color. Possible values are all HTML color
+	 * names or HEX codes, as well as special keywords such as 'swimlane,
+	 * 'inherit' or 'indicated' to use the color code of a related cell or the
+	 * indicator shape. This is ignored if no fill color is defined. Value is
+	 * "gradientColor".
+	 */
+	STYLE_GRADIENTCOLOR: 'gradientColor',
+
+	/**
+	 * Variable: STYLE_GRADIENT_DIRECTION
+	 * 
+	 * Defines the key for the gradient direction. Possible values are
+	 * <DIRECTION_EAST>, <DIRECTION_WEST>, <DIRECTION_NORTH> and
+	 * <DIRECTION_SOUTH>. Default is <DIRECTION_SOUTH>. Generally, and by
+	 * default in mxGraph, gradient painting is done from the value of
+	 * <STYLE_FILLCOLOR> to the value of <STYLE_GRADIENTCOLOR>. Taking the
+	 * example of <DIRECTION_NORTH>, this means <STYLE_FILLCOLOR> color at the 
+	 * bottom of paint pattern and <STYLE_GRADIENTCOLOR> at top, with a
+	 * gradient in-between. Value is "gradientDirection".
+	 */
+	STYLE_GRADIENT_DIRECTION: 'gradientDirection',
+
+	/**
+	 * Variable: STYLE_STROKECOLOR
+	 * 
+	 * Defines the key for the strokeColor style. Possible values are all HTML
+	 * color names or HEX codes, as well as special keywords such as 'swimlane,
+	 * 'inherit', 'indicated' to use the color code of a related cell or the
+	 * indicator shape or 'none' for no color. Value is "strokeColor".
+	 */
+	STYLE_STROKECOLOR: 'strokeColor',
+
+	/**
+	 * Variable: STYLE_SEPARATORCOLOR
+	 * 
+	 * Defines the key for the separatorColor style. Possible values are all
+	 * HTML color names or HEX codes. This style is only used for
+	 * <SHAPE_SWIMLANE> shapes. Value is "separatorColor".
+	 */
+	STYLE_SEPARATORCOLOR: 'separatorColor',
+
+	/**
+	 * Variable: STYLE_STROKEWIDTH
+	 * 
+	 * Defines the key for the strokeWidth style. The type of the value is 
+	 * numeric and the possible range is any non-negative value larger or equal
+	 * to 1. The value defines the stroke width in pixels. Note: To hide a
+	 * stroke use strokeColor none. Value is "strokeWidth".
+	 */
+	STYLE_STROKEWIDTH: 'strokeWidth',
+
+	/**
+	 * Variable: STYLE_ALIGN
+	 * 
+	 * Defines the key for the align style. Possible values are <ALIGN_LEFT>,
+	 * <ALIGN_CENTER> and <ALIGN_RIGHT>. This value defines how the lines of
+	 * the label are horizontally aligned. <ALIGN_LEFT> mean label text lines
+	 * are aligned to left of the label bounds, <ALIGN_RIGHT> to the right of
+	 * the label bounds and <ALIGN_CENTER> means the center of the text lines
+	 * are aligned in the center of the label bounds. Note this value doesn't
+	 * affect the positioning of the overall label bounds relative to the
+	 * vertex, to move the label bounds horizontally, use
+	 * <STYLE_LABEL_POSITION>. Value is "align".
+	 */
+	STYLE_ALIGN: 'align',
+
+	/**
+	 * Variable: STYLE_VERTICAL_ALIGN
+	 * 
+	 * Defines the key for the verticalAlign style. Possible values are
+	 * <ALIGN_TOP>, <ALIGN_MIDDLE> and <ALIGN_BOTTOM>. This value defines how
+	 * the lines of the label are vertically aligned. <ALIGN_TOP> means the
+	 * topmost label text line is aligned against the top of the label bounds,
+	 * <ALIGN_BOTTOM> means the bottom-most label text line is aligned against
+	 * the bottom of the label bounds and <ALIGN_MIDDLE> means there is equal
+	 * spacing between the topmost text label line and the top of the label
+	 * bounds and the bottom-most text label line and the bottom of the label
+	 * bounds. Note this value doesn't affect the positioning of the overall
+	 * label bounds relative to the vertex, to move the label bounds
+	 * vertically, use <STYLE_VERTICAL_LABEL_POSITION>. Value is "verticalAlign".
+	 */
+	STYLE_VERTICAL_ALIGN: 'verticalAlign',
+
+	/**
+	 * Variable: STYLE_LABEL_WIDTH
+	 * 
+	 * Defines the key for the width of the label if the label position is not
+	 * center. Value is "labelWidth".
+	 */
+	STYLE_LABEL_WIDTH: 'labelWidth',
+
+	/**
+	 * Variable: STYLE_LABEL_POSITION
+	 * 
+	 * Defines the key for the horizontal label position of vertices. Possible
+	 * values are <ALIGN_LEFT>, <ALIGN_CENTER> and <ALIGN_RIGHT>. Default is
+	 * <ALIGN_CENTER>. The label align defines the position of the label
+	 * relative to the cell. <ALIGN_LEFT> means the entire label bounds is
+	 * placed completely just to the left of the vertex, <ALIGN_RIGHT> means
+	 * adjust to the right and <ALIGN_CENTER> means the label bounds are
+	 * vertically aligned with the bounds of the vertex. Note this value
+	 * doesn't affect the positioning of label within the label bounds, to move
+	 * the label horizontally within the label bounds, use <STYLE_ALIGN>.
+	 * Value is "labelPosition".
+	 */
+	STYLE_LABEL_POSITION: 'labelPosition',
+
+	/**
+	 * Variable: STYLE_VERTICAL_LABEL_POSITION
+	 * 
+	 * Defines the key for the vertical label position of vertices. Possible
+	 * values are <ALIGN_TOP>, <ALIGN_BOTTOM> and <ALIGN_MIDDLE>. Default is
+	 * <ALIGN_MIDDLE>. The label align defines the position of the label
+	 * relative to the cell. <ALIGN_TOP> means the entire label bounds is
+	 * placed completely just on the top of the vertex, <ALIGN_BOTTOM> means
+	 * adjust on the bottom and <ALIGN_MIDDLE> means the label bounds are
+	 * horizontally aligned with the bounds of the vertex. Note this value
+	 * doesn't affect the positioning of label within the label bounds, to move
+	 * the label vertically within the label bounds, use
+	 * <STYLE_VERTICAL_ALIGN>. Value is "verticalLabelPosition".
+	 */
+	STYLE_VERTICAL_LABEL_POSITION: 'verticalLabelPosition',
+	
+	/**
+	 * Variable: STYLE_IMAGE_ASPECT
+	 * 
+	 * Defines the key for the image aspect style. Possible values are 0 (do
+	 * not preserve aspect) or 1 (keep aspect). This is only used in
+	 * <mxImageShape>. Default is 1. Value is "imageAspect".
+	 */
+	STYLE_IMAGE_ASPECT: 'imageAspect',
+
+	/**
+	 * Variable: STYLE_IMAGE_ALIGN
+	 * 
+	 * Defines the key for the align style. Possible values are <ALIGN_LEFT>,
+	 * <ALIGN_CENTER> and <ALIGN_RIGHT>. The value defines how any image in the
+	 * vertex label is aligned horizontally within the label bounds of a
+	 * <SHAPE_LABEL> shape. Value is "imageAlign".
+	 */
+	STYLE_IMAGE_ALIGN: 'imageAlign',
+
+	/**
+	 * Variable: STYLE_IMAGE_VERTICAL_ALIGN
+	 * 
+	 * Defines the key for the verticalAlign style. Possible values are
+	 * <ALIGN_TOP>, <ALIGN_MIDDLE> and <ALIGN_BOTTOM>. The value defines how
+	 * any image in the vertex label is aligned vertically within the label
+	 * bounds of a <SHAPE_LABEL> shape. Value is "imageVerticalAlign".
+	 */
+	STYLE_IMAGE_VERTICAL_ALIGN: 'imageVerticalAlign',
+
+	/**
+	 * Variable: STYLE_GLASS
+	 * 
+	 * Defines the key for the glass style. Possible values are 0 (disabled) and
+	 * 1(enabled). The default value is 0. This is used in <mxLabel>. Value is
+	 * "glass".
+	 */
+	STYLE_GLASS: 'glass',
+
+	/**
+	 * Variable: STYLE_IMAGE
+	 * 
+	 * Defines the key for the image style. Possible values are any image URL,
+	 * the type of the value is String. This is the path to the image that is
+	 * to be displayed within the label of a vertex. Data URLs should use the
+	 * following format: data:image/png,xyz where xyz is the base64 encoded
+	 * data (without the "base64"-prefix). Note that Data URLs are only
+	 * supported in modern browsers. Value is "image".
+	 */
+	STYLE_IMAGE: 'image',
+
+	/**
+	 * Variable: STYLE_IMAGE_WIDTH
+	 * 
+	 * Defines the key for the imageWidth style. The type of this value is
+	 * int, the value is the image width in pixels and must be greater than 0.
+	 * Value is "imageWidth".
+	 */
+	STYLE_IMAGE_WIDTH: 'imageWidth',
+
+	/**
+	 * Variable: STYLE_IMAGE_HEIGHT
+	 * 
+	 * Defines the key for the imageHeight style. The type of this value is
+	 * int, the value is the image height in pixels and must be greater than 0.
+	 * Value is "imageHeight".
+	 */
+	STYLE_IMAGE_HEIGHT: 'imageHeight',
+
+	/**
+	 * Variable: STYLE_IMAGE_BACKGROUND
+	 * 
+	 * Defines the key for the image background color. This style is only used
+	 * in <mxImageShape>. Possible values are all HTML color names or HEX
+	 * codes. Value is "imageBackground".
+	 */
+	STYLE_IMAGE_BACKGROUND: 'imageBackground',
+
+	/**
+	 * Variable: STYLE_IMAGE_BORDER
+	 * 
+	 * Defines the key for the image border color. This style is only used in
+	 * <mxImageShape>. Possible values are all HTML color names or HEX codes.
+	 * Value is "imageBorder".
+	 */
+	STYLE_IMAGE_BORDER: 'imageBorder',
+
+	/**
+	 * Variable: STYLE_FLIPH
+	 * 
+	 * Defines the key for the horizontal image flip. This style is only used
+	 * in <mxImageShape>. Possible values are 0 and 1. Default is 0. Value is
+	 * "flipH".
+	 */
+	STYLE_FLIPH: 'flipH',
+
+	/**
+	 * Variable: STYLE_FLIPV
+	 * 
+	 * Defines the key for the vertical flip. Possible values are 0 and 1.
+	 * Default is 0. Value is "flipV".
+	 */
+	STYLE_FLIPV: 'flipV',
+
+	/**
+	 * Variable: STYLE_NOLABEL
+	 * 
+	 * Defines the key for the noLabel style. If this is true then no label is
+	 * visible for a given cell. Possible values are true or false (1 or 0).
+	 * Default is false. Value is "noLabel".
+	 */
+	STYLE_NOLABEL: 'noLabel',
+
+	/**
+	 * Variable: STYLE_NOEDGESTYLE
+	 * 
+	 * Defines the key for the noEdgeStyle style. If this is true then no edge
+	 * style is applied for a given edge. Possible values are true or false
+	 * (1 or 0). Default is false. Value is "noEdgeStyle".
+	 */
+	STYLE_NOEDGESTYLE: 'noEdgeStyle',
+
+	/**
+	 * Variable: STYLE_LABEL_BACKGROUNDCOLOR
+	 * 
+	 * Defines the key for the label background color. Possible values are all
+	 * HTML color names or HEX codes. Value is "labelBackgroundColor".
+	 */
+	STYLE_LABEL_BACKGROUNDCOLOR: 'labelBackgroundColor',
+
+	/**
+	 * Variable: STYLE_LABEL_BORDERCOLOR
+	 * 
+	 * Defines the key for the label border color. Possible values are all
+	 * HTML color names or HEX codes. Value is "labelBorderColor".
+	 */
+	STYLE_LABEL_BORDERCOLOR: 'labelBorderColor',
+
+	/**
+	 * Variable: STYLE_LABEL_PADDING
+	 * 
+	 * Defines the key for the label padding, ie. the space between the label
+	 * border and the label. Value is "labelPadding".
+	 */
+	STYLE_LABEL_PADDING: 'labelPadding',
+
+	/**
+	 * Variable: STYLE_INDICATOR_SHAPE
+	 * 
+	 * Defines the key for the indicator shape used within an <mxLabel>.
+	 * Possible values are all SHAPE_* constants or the names of any new
+	 * shapes. The indicatorShape has precedence over the indicatorImage.
+	 * Value is "indicatorShape".
+	 */
+	STYLE_INDICATOR_SHAPE: 'indicatorShape',
+
+	/**
+	 * Variable: STYLE_INDICATOR_IMAGE
+	 * 
+	 * Defines the key for the indicator image used within an <mxLabel>.
+	 * Possible values are all image URLs. The indicatorShape has
+	 * precedence over the indicatorImage. Value is "indicatorImage".
+	 */
+	STYLE_INDICATOR_IMAGE: 'indicatorImage',
+
+	/**
+	 * Variable: STYLE_INDICATOR_COLOR
+	 * 
+	 * Defines the key for the indicatorColor style. Possible values are all
+	 * HTML color names or HEX codes, as well as the special 'swimlane' keyword
+	 * to refer to the color of the parent swimlane if one exists. Value is
+	 * "indicatorColor".
+	 */
+	STYLE_INDICATOR_COLOR: 'indicatorColor',
+
+	/**
+	 * Variable: STYLE_INDICATOR_STROKECOLOR
+	 * 
+	 * Defines the key for the indicator stroke color in <mxLabel>.
+	 * Possible values are all color codes. Value is "indicatorStrokeColor".
+	 */
+	STYLE_INDICATOR_STROKECOLOR: 'indicatorStrokeColor',
+
+	/**
+	 * Variable: STYLE_INDICATOR_GRADIENTCOLOR
+	 * 
+	 * Defines the key for the indicatorGradientColor style. Possible values
+	 * are all HTML color names or HEX codes. This style is only supported in
+	 * <SHAPE_LABEL> shapes. Value is "indicatorGradientColor".
+	 */
+	STYLE_INDICATOR_GRADIENTCOLOR: 'indicatorGradientColor',
+
+	/**
+	 * Variable: STYLE_INDICATOR_SPACING
+	 * 
+	 * The defines the key for the spacing between the label and the
+	 * indicator in <mxLabel>. Possible values are in pixels. Value is
+	 * "indicatorSpacing".
+	 */
+	STYLE_INDICATOR_SPACING: 'indicatorSpacing',
+
+	/**
+	 * Variable: STYLE_INDICATOR_WIDTH
+	 * 
+	 * Defines the key for the indicator width. Possible values start at 0 (in
+	 * pixels). Value is "indicatorWidth".
+	 */
+	STYLE_INDICATOR_WIDTH: 'indicatorWidth',
+
+	/**
+	 * Variable: STYLE_INDICATOR_HEIGHT
+	 * 
+	 * Defines the key for the indicator height. Possible values start at 0 (in
+	 * pixels). Value is "indicatorHeight".
+	 */
+	STYLE_INDICATOR_HEIGHT: 'indicatorHeight',
+
+	/**
+	 * Variable: STYLE_INDICATOR_DIRECTION
+	 * 
+	 * Defines the key for the indicatorDirection style. The direction style is
+	 * used to specify the direction of certain shapes (eg. <mxTriangle>).
+	 * Possible values are <DIRECTION_EAST> (default), <DIRECTION_WEST>,
+	 * <DIRECTION_NORTH> and <DIRECTION_SOUTH>. Value is "indicatorDirection".
+	 */
+	STYLE_INDICATOR_DIRECTION: 'indicatorDirection',
+
+	/**
+	 * Variable: STYLE_SHADOW
+	 * 
+	 * Defines the key for the shadow style. The type of the value is Boolean.
+	 * Value is "shadow".
+	 */
+	STYLE_SHADOW: 'shadow',
+	
+	/**
+	 * Variable: STYLE_SEGMENT
+	 * 
+	 * Defines the key for the segment style. The type of this value is float
+	 * and the value represents the size of the horizontal segment of the
+	 * entity relation style. Default is ENTITY_SEGMENT. Value is "segment".
+	 */
+	STYLE_SEGMENT: 'segment',
+	
+	/**
+	 * Variable: STYLE_ENDARROW
+	 *
+	 * Defines the key for the end arrow marker. Possible values are all
+	 * constants with an ARROW-prefix. This is only used in <mxConnector>.
+	 * Value is "endArrow".
+	 *
+	 * Example:
+	 * (code)
+	 * style[mxConstants.STYLE_ENDARROW] = mxConstants.ARROW_CLASSIC;
+	 * (end)
+	 */
+	STYLE_ENDARROW: 'endArrow',
+
+	/**
+	 * Variable: STYLE_STARTARROW
+	 * 
+	 * Defines the key for the start arrow marker. Possible values are all
+	 * constants with an ARROW-prefix. This is only used in <mxConnector>.
+	 * See <STYLE_ENDARROW>. Value is "startArrow".
+	 */
+	STYLE_STARTARROW: 'startArrow',
+
+	/**
+	 * Variable: STYLE_ENDSIZE
+	 * 
+	 * Defines the key for the endSize style. The type of this value is numeric
+	 * and the value represents the size of the end marker in pixels. Value is
+	 * "endSize".
+	 */
+	STYLE_ENDSIZE: 'endSize',
+
+	/**
+	 * Variable: STYLE_STARTSIZE
+	 * 
+	 * Defines the key for the startSize style. The type of this value is
+	 * numeric and the value represents the size of the start marker or the
+	 * size of the swimlane title region depending on the shape it is used for.
+	 * Value is "startSize".
+	 */
+	STYLE_STARTSIZE: 'startSize',
+
+	/**
+	 * Variable: STYLE_SWIMLANE_LINE
+	 * 
+	 * Defines the key for the swimlaneLine style. This style specifies whether
+	 * the line between the title regio of a swimlane should be visible. Use 0
+	 * for hidden or 1 (default) for visible. Value is "swimlaneLine".
+	 */
+	STYLE_SWIMLANE_LINE: 'swimlaneLine',
+
+	/**
+	 * Variable: STYLE_ENDFILL
+	 * 
+	 * Defines the key for the endFill style. Use 0 for no fill or 1 (default)
+	 * for fill. (This style is only exported via <mxImageExport>.) Value is
+	 * "endFill".
+	 */
+	STYLE_ENDFILL: 'endFill',
+
+	/**
+	 * Variable: STYLE_STARTFILL
+	 * 
+	 * Defines the key for the startFill style. Use 0 for no fill or 1 (default)
+	 * for fill. (This style is only exported via <mxImageExport>.) Value is
+	 * "startFill".
+	 */
+	STYLE_STARTFILL: 'startFill',
+
+	/**
+	 * Variable: STYLE_DASHED
+	 * 
+	 * Defines the key for the dashed style. Use 0 (default) for non-dashed or 1
+	 * for dashed. Value is "dashed".
+	 */
+	STYLE_DASHED: 'dashed',
+
+	/**
+	 * Defines the key for the dashed pattern style in SVG and image exports.
+	 * The type of this value is a space separated list of numbers that specify
+	 * a custom-defined dash pattern. Dash styles are defined in terms of the
+	 * length of the dash (the drawn part of the stroke) and the length of the
+	 * space between the dashes. The lengths are relative to the line width: a
+	 * length of "1" is equal to the line width. VML ignores this style and
+	 * uses dashStyle instead as defined in the VML specification. This style
+	 * is only used in the <mxConnector> shape. Value is "dashPattern".
+	 */
+	STYLE_DASH_PATTERN: 'dashPattern',
+
+	/**
+	 * Variable: STYLE_FIX_DASH
+	 * 
+	 * Defines the key for the fixDash style. Use 0 (default) for dash patterns
+	 * that depend on the linewidth and 1 for dash patterns that ignore the
+	 * line width. Value is "fixDash".
+	 */
+	STYLE_FIX_DASH: 'fixDash',
+
+	/**
+	 * Variable: STYLE_ROUNDED
+	 * 
+	 * Defines the key for the rounded style. The type of this value is
+	 * Boolean. For edges this determines whether or not joins between edges
+	 * segments are smoothed to a rounded finish. For vertices that have the
+	 * rectangle shape, this determines whether or not the rectangle is
+	 * rounded. Use 0 (default) for non-rounded or 1 for rounded. Value is
+	 * "rounded".
+	 */
+	STYLE_ROUNDED: 'rounded',
+
+	/**
+	 * Variable: STYLE_CURVED
+	 * 
+	 * Defines the key for the curved style. The type of this value is
+	 * Boolean. It is only applicable for connector shapes. Use 0 (default)
+	 * for non-curved or 1 for curved. Value is "curved".
+	 */
+	STYLE_CURVED: 'curved',
+
+	/**
+	 * Variable: STYLE_ARCSIZE
+	 * 
+	 * Defines the rounding factor for a rounded rectangle in percent (without
+	 * the percent sign). Possible values are between 0 and 100. If this value
+	 * is not specified then RECTANGLE_ROUNDING_FACTOR * 100 is used. For
+	 * edges, this defines the absolute size of rounded corners in pixels. If
+	 * this values is not specified then LINE_ARCSIZE is used.
+	 * (This style is only exported via <mxImageExport>.) Value is "arcSize".
+	 */
+	STYLE_ARCSIZE: 'arcSize',
+
+	/**
+	 * Variable: STYLE_ABSOLUTE_ARCSIZE
+	 * 
+	 * Defines the key for the absolute arc size style. This specifies if
+	 * arcSize for rectangles is abolute or relative. Possible values are 1
+	 * and 0 (default). Value is "absoluteArcSize".
+	 */
+	STYLE_ABSOLUTE_ARCSIZE: 'absoluteArcSize',
+
+	/**
+	 * Variable: STYLE_SOURCE_PERIMETER_SPACING
+	 * 
+	 * Defines the key for the source perimeter spacing. The type of this value
+	 * is numeric. This is the distance between the source connection point of
+	 * an edge and the perimeter of the source vertex in pixels. This style
+	 * only applies to edges. Value is "sourcePerimeterSpacing".
+	 */
+	STYLE_SOURCE_PERIMETER_SPACING: 'sourcePerimeterSpacing',
+
+	/**
+	 * Variable: STYLE_TARGET_PERIMETER_SPACING
+	 * 
+	 * Defines the key for the target perimeter spacing. The type of this value
+	 * is numeric. This is the distance between the target connection point of
+	 * an edge and the perimeter of the target vertex in pixels. This style
+	 * only applies to edges. Value is "targetPerimeterSpacing".
+	 */
+	STYLE_TARGET_PERIMETER_SPACING: 'targetPerimeterSpacing',
+
+	/**
+	 * Variable: STYLE_PERIMETER_SPACING
+	 * 
+	 * Defines the key for the perimeter spacing. This is the distance between
+	 * the connection point and the perimeter in pixels. When used in a vertex
+	 * style, this applies to all incoming edges to floating ports (edges that
+	 * terminate on the perimeter of the vertex). When used in an edge style,
+	 * this spacing applies to the source and target separately, if they
+	 * terminate in floating ports (on the perimeter of the vertex). Value is
+	 * "perimeterSpacing".
+	 */
+	STYLE_PERIMETER_SPACING: 'perimeterSpacing',
+
+	/**
+	 * Variable: STYLE_SPACING
+	 * 
+	 * Defines the key for the spacing. The value represents the spacing, in
+	 * pixels, added to each side of a label in a vertex (style applies to
+	 * vertices only). Value is "spacing".
+	 */
+	STYLE_SPACING: 'spacing',
+
+	/**
+	 * Variable: STYLE_SPACING_TOP
+	 * 
+	 * Defines the key for the spacingTop style. The value represents the
+	 * spacing, in pixels, added to the top side of a label in a vertex (style
+	 * applies to vertices only). Value is "spacingTop".
+	 */
+	STYLE_SPACING_TOP: 'spacingTop',
+
+	/**
+	 * Variable: STYLE_SPACING_LEFT
+	 * 
+	 * Defines the key for the spacingLeft style. The value represents the
+	 * spacing, in pixels, added to the left side of a label in a vertex (style
+	 * applies to vertices only). Value is "spacingLeft".
+	 */
+	STYLE_SPACING_LEFT: 'spacingLeft',
+
+	/**
+	 * Variable: STYLE_SPACING_BOTTOM
+	 * 
+	 * Defines the key for the spacingBottom style The value represents the
+	 * spacing, in pixels, added to the bottom side of a label in a vertex
+	 * (style applies to vertices only). Value is "spacingBottom".
+	 */
+	STYLE_SPACING_BOTTOM: 'spacingBottom',
+
+	/**
+	 * Variable: STYLE_SPACING_RIGHT
+	 * 
+	 * Defines the key for the spacingRight style The value represents the
+	 * spacing, in pixels, added to the right side of a label in a vertex (style
+	 * applies to vertices only). Value is "spacingRight".
+	 */
+	STYLE_SPACING_RIGHT: 'spacingRight',
+
+	/**
+	 * Variable: STYLE_HORIZONTAL
+	 * 
+	 * Defines the key for the horizontal style. Possible values are
+	 * true or false. This value only applies to vertices. If the <STYLE_SHAPE>
+	 * is "SHAPE_SWIMLANE" a value of false indicates that the
+	 * swimlane should be drawn vertically, true indicates to draw it
+	 * horizontally. If the shape style does not indicate that this vertex is a
+	 * swimlane, this value affects only whether the label is drawn
+	 * horizontally or vertically. Value is "horizontal".
+	 */
+	STYLE_HORIZONTAL: 'horizontal',
+
+	/**
+	 * Variable: STYLE_DIRECTION
+	 * 
+	 * Defines the key for the direction style. The direction style is used
+	 * to specify the direction of certain shapes (eg. <mxTriangle>).
+	 * Possible values are <DIRECTION_EAST> (default), <DIRECTION_WEST>,
+	 * <DIRECTION_NORTH> and <DIRECTION_SOUTH>. Value is "direction".
+	 */
+	STYLE_DIRECTION: 'direction',
+
+	/**
+	 * Variable: STYLE_ELBOW
+	 * 
+	 * Defines the key for the elbow style. Possible values are
+	 * <ELBOW_HORIZONTAL> and <ELBOW_VERTICAL>. Default is <ELBOW_HORIZONTAL>.
+	 * This defines how the three segment orthogonal edge style leaves its
+	 * terminal vertices. The vertical style leaves the terminal vertices at
+	 * the top and bottom sides. Value is "elbow".
+	 */
+	STYLE_ELBOW: 'elbow',
+
+	/**
+	 * Variable: STYLE_FONTCOLOR
+	 * 
+	 * Defines the key for the fontColor style. Possible values are all HTML
+	 * color names or HEX codes. Value is "fontColor".
+	 */
+	STYLE_FONTCOLOR: 'fontColor',
+
+	/**
+	 * Variable: STYLE_FONTFAMILY
+	 * 
+	 * Defines the key for the fontFamily style. Possible values are names such
+	 * as Arial; Dialog; Verdana; Times New Roman. The value is of type String.
+	 * Value is fontFamily.
+	 */
+	STYLE_FONTFAMILY: 'fontFamily',
+
+	/**
+	 * Variable: STYLE_FONTSIZE
+	 * 
+	 * Defines the key for the fontSize style (in px). The type of the value
+	 * is int. Value is "fontSize".
+	 */
+	STYLE_FONTSIZE: 'fontSize',
+
+	/**
+	 * Variable: STYLE_FONTSTYLE
+	 * 
+	 * Defines the key for the fontStyle style. Values may be any logical AND
+	 * (sum) of <FONT_BOLD>, <FONT_ITALIC> and <FONT_UNDERLINE>.
+	 * The type of the value is int. Value is "fontStyle".
+	 */
+	STYLE_FONTSTYLE: 'fontStyle',
+	
+	/**
+	 * Variable: STYLE_ASPECT
+	 * 
+	 * Defines the key for the aspect style. Possible values are empty or fixed.
+	 * If fixed is used then the aspect ratio of the cell will be maintained
+	 * when resizing. Default is empty. Value is "aspect".
+	 */
+	STYLE_ASPECT: 'aspect',
+
+	/**
+	 * Variable: STYLE_AUTOSIZE
+	 * 
+	 * Defines the key for the autosize style. This specifies if a cell should be
+	 * resized automatically if the value has changed. Possible values are 0 or 1.
+	 * Default is 0. See <mxGraph.isAutoSizeCell>. This is normally combined with
+	 * <STYLE_RESIZABLE> to disable manual sizing. Value is "autosize".
+	 */
+	STYLE_AUTOSIZE: 'autosize',
+
+	/**
+	 * Variable: STYLE_FOLDABLE
+	 * 
+	 * Defines the key for the foldable style. This specifies if a cell is foldable
+	 * using a folding icon. Possible values are 0 or 1. Default is 1. See
+	 * <mxGraph.isCellFoldable>. Value is "foldable".
+	 */
+	STYLE_FOLDABLE: 'foldable',
+
+	/**
+	 * Variable: STYLE_EDITABLE
+	 * 
+	 * Defines the key for the editable style. This specifies if the value of
+	 * a cell can be edited using the in-place editor. Possible values are 0 or
+	 * 1. Default is 1. See <mxGraph.isCellEditable>. Value is "editable".
+	 */
+	STYLE_EDITABLE: 'editable',
+
+	/**
+	 * Variable: STYLE_BENDABLE
+	 * 
+	 * Defines the key for the bendable style. This specifies if the control
+	 * points of an edge can be moved. Possible values are 0 or 1. Default is
+	 * 1. See <mxGraph.isCellBendable>. Value is "bendable".
+	 */
+	STYLE_BENDABLE: 'bendable',
+
+	/**
+	 * Variable: STYLE_MOVABLE
+	 * 
+	 * Defines the key for the movable style. This specifies if a cell can
+	 * be moved. Possible values are 0 or 1. Default is 1. See
+	 * <mxGraph.isCellMovable>. Value is "movable".
+	 */
+	STYLE_MOVABLE: 'movable',
+
+	/**
+	 * Variable: STYLE_RESIZABLE
+	 * 
+	 * Defines the key for the resizable style. This specifies if a cell can
+	 * be resized. Possible values are 0 or 1. Default is 1. See
+	 * <mxGraph.isCellResizable>. Value is "resizable".
+	 */
+	STYLE_RESIZABLE: 'resizable',
+
+	/**
+	 * Variable: STYLE_RESIZE_WIDTH
+	 * 
+	 * Defines the key for the resizeWidth style. This specifies if a cell's
+	 * width is resized if the parent is resized. If this is 1 then the width
+	 * will be resized even if the cell's geometry is relative. If this is 0
+	 * then the cell's width will not be resized. Default is not defined. Value
+	 * is "resizeWidth".
+	 */
+	STYLE_RESIZE_WIDTH: 'resizeWidth',
+
+	/**
+	 * Variable: STYLE_RESIZE_WIDTH
+	 * 
+	 * Defines the key for the resizeHeight style. This specifies if a cell's
+	 * height if resize if the parent is resized. If this is 1 then the height
+	 * will be resized even if the cell's geometry is relative. If this is 0
+	 * then the cell's height will not be resized. Default is not defined. Value
+	 * is "resizeHeight".
+	 */
+	STYLE_RESIZE_HEIGHT: 'resizeHeight',
+
+	/**
+	 * Variable: STYLE_ROTATABLE
+	 * 
+	 * Defines the key for the rotatable style. This specifies if a cell can
+	 * be rotated. Possible values are 0 or 1. Default is 1. See
+	 * <mxGraph.isCellRotatable>. Value is "rotatable".
+	 */
+	STYLE_ROTATABLE: 'rotatable',
+
+	/**
+	 * Variable: STYLE_CLONEABLE
+	 * 
+	 * Defines the key for the cloneable style. This specifies if a cell can
+	 * be cloned. Possible values are 0 or 1. Default is 1. See
+	 * <mxGraph.isCellCloneable>. Value is "cloneable".
+	 */
+	STYLE_CLONEABLE: 'cloneable',
+
+	/**
+	 * Variable: STYLE_DELETABLE
+	 * 
+	 * Defines the key for the deletable style. This specifies if a cell can be
+	 * deleted. Possible values are 0 or 1. Default is 1. See
+	 * <mxGraph.isCellDeletable>. Value is "deletable".
+	 */
+	STYLE_DELETABLE: 'deletable',
+
+	/**
+	 * Variable: STYLE_SHAPE
+	 * 
+	 * Defines the key for the shape. Possible values are all constants with
+	 * a SHAPE-prefix or any newly defined shape names. Value is "shape".
+	 */
+	STYLE_SHAPE: 'shape',
+
+	/**
+	 * Variable: STYLE_EDGE
+	 * 
+	 * Defines the key for the edge style. Possible values are the functions
+	 * defined in <mxEdgeStyle>. Value is "edgeStyle".
+	 */
+	STYLE_EDGE: 'edgeStyle',
+
+	/**
+	 * Variable: STYLE_JETTY_SIZE
+	 * 
+	 * Defines the key for the jetty size in <mxEdgeStyle.OrthConnector>.
+	 * Default is 10. Possible values are all numeric values or "auto".
+	 * Value is "jettySize".
+	 */
+	STYLE_JETTY_SIZE: 'jettySize',
+
+	/**
+	 * Variable: STYLE_SOURCE_JETTY_SIZE
+	 * 
+	 * Defines the key for the jetty size in <mxEdgeStyle.OrthConnector>.
+	 * Default is 10. Possible values are numeric values or "auto". This has
+	 * precedence over <STYLE_JETTY_SIZE>. Value is "sourceJettySize".
+	 */
+	STYLE_SOURCE_JETTY_SIZE: 'sourceJettySize',
+
+	/**
+	 * Variable: targetJettySize
+	 * 
+	 * Defines the key for the jetty size in <mxEdgeStyle.OrthConnector>.
+	 * Default is 10. Possible values are numeric values or "auto". This has
+	 * precedence over <STYLE_JETTY_SIZE>. Value is "targetJettySize".
+	 */
+	STYLE_TARGET_JETTY_SIZE: 'targetJettySize',
+
+	/**
+	 * Variable: STYLE_LOOP
+	 * 
+	 * Defines the key for the loop style. Possible values are the functions
+	 * defined in <mxEdgeStyle>. Value is "loopStyle".
+	 */
+	STYLE_LOOP: 'loopStyle',
+
+	/**
+	 * Variable: STYLE_ORTHOGONAL_LOOP
+	 * 
+	 * Defines the key for the orthogonal loop style. Possible values are 0 and
+	 * 1. Default is 0. Value is "orthogonalLoop". Use this style to specify
+	 * if loops should be routed using an orthogonal router. Currently, this
+	 * uses <mxEdgeStyle.OrthConnector> but will be replaced with a dedicated
+	 * orthogonal loop router in later releases.
+	 */
+	STYLE_ORTHOGONAL_LOOP: 'orthogonalLoop',
+
+	/**
+	 * Variable: STYLE_ROUTING_CENTER_X
+	 * 
+	 * Defines the key for the horizontal routing center. Possible values are
+	 * between -0.5 and 0.5. This is the relative offset from the center used
+	 * for connecting edges. The type of this value is numeric. Value is
+	 * "routingCenterX".
+	 */
+	STYLE_ROUTING_CENTER_X: 'routingCenterX',
+
+	/**
+	 * Variable: STYLE_ROUTING_CENTER_Y
+	 * 
+	 * Defines the key for the vertical routing center. Possible values are
+	 * between -0.5 and 0.5. This is the relative offset from the center used
+	 * for connecting edges. The type of this value is numeric. Value is
+	 * "routingCenterY".
+	 */
+	STYLE_ROUTING_CENTER_Y: 'routingCenterY',
+
+	/**
+	 * Variable: FONT_BOLD
+	 * 
+	 * Constant for bold fonts. Default is 1.
+	 */
+	FONT_BOLD: 1,
+
+	/**
+	 * Variable: FONT_ITALIC
+	 * 
+	 * Constant for italic fonts. Default is 2.
+	 */
+	FONT_ITALIC: 2,
+
+	/**
+	 * Variable: FONT_UNDERLINE
+	 * 
+	 * Constant for underlined fonts. Default is 4.
+	 */
+	FONT_UNDERLINE: 4,
+
+	/**
+	 * Variable: SHAPE_RECTANGLE
+	 * 
+	 * Name under which <mxRectangleShape> is registered in <mxCellRenderer>.
+	 * Default is rectangle.
+	 */
+	SHAPE_RECTANGLE: 'rectangle',
+
+	/**
+	 * Variable: SHAPE_ELLIPSE
+	 * 
+	 * Name under which <mxEllipse> is registered in <mxCellRenderer>.
+	 * Default is ellipse.
+	 */
+	SHAPE_ELLIPSE: 'ellipse',
+
+	/**
+	 * Variable: SHAPE_DOUBLE_ELLIPSE
+	 * 
+	 * Name under which <mxDoubleEllipse> is registered in <mxCellRenderer>.
+	 * Default is doubleEllipse.
+	 */
+	SHAPE_DOUBLE_ELLIPSE: 'doubleEllipse',
+
+	/**
+	 * Variable: SHAPE_RHOMBUS
+	 * 
+	 * Name under which <mxRhombus> is registered in <mxCellRenderer>.
+	 * Default is rhombus.
+	 */
+	SHAPE_RHOMBUS: 'rhombus',
+
+	/**
+	 * Variable: SHAPE_LINE
+	 * 
+	 * Name under which <mxLine> is registered in <mxCellRenderer>.
+	 * Default is line.
+	 */
+	SHAPE_LINE: 'line',
+
+	/**
+	 * Variable: SHAPE_IMAGE
+	 * 
+	 * Name under which <mxImageShape> is registered in <mxCellRenderer>.
+	 * Default is image.
+	 */
+	SHAPE_IMAGE: 'image',
+	
+	/**
+	 * Variable: SHAPE_ARROW
+	 * 
+	 * Name under which <mxArrow> is registered in <mxCellRenderer>.
+	 * Default is arrow.
+	 */
+	SHAPE_ARROW: 'arrow',
+	
+	/**
+	 * Variable: SHAPE_ARROW_CONNECTOR
+	 * 
+	 * Name under which <mxArrowConnector> is registered in <mxCellRenderer>.
+	 * Default is arrowConnector.
+	 */
+	SHAPE_ARROW_CONNECTOR: 'arrowConnector',
+	
+	/**
+	 * Variable: SHAPE_LABEL
+	 * 
+	 * Name under which <mxLabel> is registered in <mxCellRenderer>.
+	 * Default is label.
+	 */
+	SHAPE_LABEL: 'label',
+	
+	/**
+	 * Variable: SHAPE_CYLINDER
+	 * 
+	 * Name under which <mxCylinder> is registered in <mxCellRenderer>.
+	 * Default is cylinder.
+	 */
+	SHAPE_CYLINDER: 'cylinder',
+	
+	/**
+	 * Variable: SHAPE_SWIMLANE
+	 * 
+	 * Name under which <mxSwimlane> is registered in <mxCellRenderer>.
+	 * Default is swimlane.
+	 */
+	SHAPE_SWIMLANE: 'swimlane',
+		
+	/**
+	 * Variable: SHAPE_CONNECTOR
+	 * 
+	 * Name under which <mxConnector> is registered in <mxCellRenderer>.
+	 * Default is connector.
+	 */
+	SHAPE_CONNECTOR: 'connector',
+
+	/**
+	 * Variable: SHAPE_ACTOR
+	 * 
+	 * Name under which <mxActor> is registered in <mxCellRenderer>.
+	 * Default is actor.
+	 */
+	SHAPE_ACTOR: 'actor',
+		
+	/**
+	 * Variable: SHAPE_CLOUD
+	 * 
+	 * Name under which <mxCloud> is registered in <mxCellRenderer>.
+	 * Default is cloud.
+	 */
+	SHAPE_CLOUD: 'cloud',
+		
+	/**
+	 * Variable: SHAPE_TRIANGLE
+	 * 
+	 * Name under which <mxTriangle> is registered in <mxCellRenderer>.
+	 * Default is triangle.
+	 */
+	SHAPE_TRIANGLE: 'triangle',
+		
+	/**
+	 * Variable: SHAPE_HEXAGON
+	 * 
+	 * Name under which <mxHexagon> is registered in <mxCellRenderer>.
+	 * Default is hexagon.
+	 */
+	SHAPE_HEXAGON: 'hexagon',
+
+	/**
+	 * Variable: ARROW_CLASSIC
+	 * 
+	 * Constant for classic arrow markers.
+	 */
+	ARROW_CLASSIC: 'classic',
+
+	/**
+	 * Variable: ARROW_CLASSIC_THIN
+	 * 
+	 * Constant for thin classic arrow markers.
+	 */
+	ARROW_CLASSIC_THIN: 'classicThin',
+
+	/**
+	 * Variable: ARROW_BLOCK
+	 * 
+	 * Constant for block arrow markers.
+	 */
+	ARROW_BLOCK: 'block',
+
+	/**
+	 * Variable: ARROW_BLOCK_THIN
+	 * 
+	 * Constant for thin block arrow markers.
+	 */
+	ARROW_BLOCK_THIN: 'blockThin',
+
+	/**
+	 * Variable: ARROW_OPEN
+	 * 
+	 * Constant for open arrow markers.
+	 */
+	ARROW_OPEN: 'open',
+
+	/**
+	 * Variable: ARROW_OPEN_THIN
+	 * 
+	 * Constant for thin open arrow markers.
+	 */
+	ARROW_OPEN_THIN: 'openThin',
+
+	/**
+	 * Variable: ARROW_OVAL
+	 * 
+	 * Constant for oval arrow markers.
+	 */
+	ARROW_OVAL: 'oval',
+
+	/**
+	 * Variable: ARROW_DIAMOND
+	 * 
+	 * Constant for diamond arrow markers.
+	 */
+	ARROW_DIAMOND: 'diamond',
+
+	/**
+	 * Variable: ARROW_DIAMOND_THIN
+	 * 
+	 * Constant for thin diamond arrow markers.
+	 */
+	ARROW_DIAMOND_THIN: 'diamondThin',
+
+	/**
+	 * Variable: ALIGN_LEFT
+	 * 
+	 * Constant for left horizontal alignment. Default is left.
+	 */
+	ALIGN_LEFT: 'left',
+
+	/**
+	 * Variable: ALIGN_CENTER
+	 * 
+	 * Constant for center horizontal alignment. Default is center.
+	 */
+	ALIGN_CENTER: 'center',
+
+	/**
+	 * Variable: ALIGN_RIGHT
+	 * 
+	 * Constant for right horizontal alignment. Default is right.
+	 */
+	ALIGN_RIGHT: 'right',
+
+	/**
+	 * Variable: ALIGN_TOP
+	 * 
+	 * Constant for top vertical alignment. Default is top.
+	 */
+	ALIGN_TOP: 'top',
+
+	/**
+	 * Variable: ALIGN_MIDDLE
+	 * 
+	 * Constant for middle vertical alignment. Default is middle.
+	 */
+	ALIGN_MIDDLE: 'middle',
+
+	/**
+	 * Variable: ALIGN_BOTTOM
+	 * 
+	 * Constant for bottom vertical alignment. Default is bottom.
+	 */
+	ALIGN_BOTTOM: 'bottom',
+
+	/**
+	 * Variable: DIRECTION_NORTH
+	 * 
+	 * Constant for direction north. Default is north.
+	 */
+	DIRECTION_NORTH: 'north',
+
+	/**
+	 * Variable: DIRECTION_SOUTH
+	 * 
+	 * Constant for direction south. Default is south.
+	 */
+	DIRECTION_SOUTH: 'south',
+
+	/**
+	 * Variable: DIRECTION_EAST
+	 * 
+	 * Constant for direction east. Default is east.
+	 */
+	DIRECTION_EAST: 'east',
+
+	/**
+	 * Variable: DIRECTION_WEST
+	 * 
+	 * Constant for direction west. Default is west.
+	 */
+	DIRECTION_WEST: 'west',
+
+	/**
+	 * Variable: TEXT_DIRECTION_DEFAULT
+	 * 
+	 * Constant for text direction default. Default is an empty string. Use
+	 * this value to use the default text direction of the operating system. 
+	 */
+	TEXT_DIRECTION_DEFAULT: '',
+
+	/**
+	 * Variable: TEXT_DIRECTION_AUTO
+	 * 
+	 * Constant for text direction automatic. Default is auto. Use this value
+	 * to find the direction for a given text with <mxText.getAutoDirection>. 
+	 */
+	TEXT_DIRECTION_AUTO: 'auto',
+
+	/**
+	 * Variable: TEXT_DIRECTION_LTR
+	 * 
+	 * Constant for text direction left to right. Default is ltr. Use this
+	 * value for left to right text direction.
+	 */
+	TEXT_DIRECTION_LTR: 'ltr',
+
+	/**
+	 * Variable: TEXT_DIRECTION_RTL
+	 * 
+	 * Constant for text direction right to left. Default is rtl. Use this
+	 * value for right to left text direction.
+	 */
+	TEXT_DIRECTION_RTL: 'rtl',
+
+	/**
+	 * Variable: DIRECTION_MASK_NONE
+	 * 
+	 * Constant for no direction.
+	 */
+	DIRECTION_MASK_NONE: 0,
+
+	/**
+	 * Variable: DIRECTION_MASK_WEST
+	 * 
+	 * Bitwise mask for west direction.
+	 */
+	DIRECTION_MASK_WEST: 1,
+	
+	/**
+	 * Variable: DIRECTION_MASK_NORTH
+	 * 
+	 * Bitwise mask for north direction.
+	 */
+	DIRECTION_MASK_NORTH: 2,
+
+	/**
+	 * Variable: DIRECTION_MASK_SOUTH
+	 * 
+	 * Bitwise mask for south direction.
+	 */
+	DIRECTION_MASK_SOUTH: 4,
+
+	/**
+	 * Variable: DIRECTION_MASK_EAST
+	 * 
+	 * Bitwise mask for east direction.
+	 */
+	DIRECTION_MASK_EAST: 8,
+	
+	/**
+	 * Variable: DIRECTION_MASK_ALL
+	 * 
+	 * Bitwise mask for all directions.
+	 */
+	DIRECTION_MASK_ALL: 15,
+
+	/**
+	 * Variable: ELBOW_VERTICAL
+	 * 
+	 * Constant for elbow vertical. Default is horizontal.
+	 */
+	ELBOW_VERTICAL: 'vertical',
+
+	/**
+	 * Variable: ELBOW_HORIZONTAL
+	 * 
+	 * Constant for elbow horizontal. Default is horizontal.
+	 */
+	ELBOW_HORIZONTAL: 'horizontal',
+
+	/**
+	 * Variable: EDGESTYLE_ELBOW
+	 * 
+	 * Name of the elbow edge style. Can be used as a string value
+	 * for the STYLE_EDGE style.
+	 */
+	EDGESTYLE_ELBOW: 'elbowEdgeStyle',
+
+	/**
+	 * Variable: EDGESTYLE_ENTITY_RELATION
+	 * 
+	 * Name of the entity relation edge style. Can be used as a string value
+	 * for the STYLE_EDGE style.
+	 */
+	EDGESTYLE_ENTITY_RELATION: 'entityRelationEdgeStyle',
+
+	/**
+	 * Variable: EDGESTYLE_LOOP
+	 * 
+	 * Name of the loop edge style. Can be used as a string value
+	 * for the STYLE_EDGE style.
+	 */
+	EDGESTYLE_LOOP: 'loopEdgeStyle',
+
+	/**
+	 * Variable: EDGESTYLE_SIDETOSIDE
+	 * 
+	 * Name of the side to side edge style. Can be used as a string value
+	 * for the STYLE_EDGE style.
+	 */
+	EDGESTYLE_SIDETOSIDE: 'sideToSideEdgeStyle',
+
+	/**
+	 * Variable: EDGESTYLE_TOPTOBOTTOM
+	 * 
+	 * Name of the top to bottom edge style. Can be used as a string value
+	 * for the STYLE_EDGE style.
+	 */
+	EDGESTYLE_TOPTOBOTTOM: 'topToBottomEdgeStyle',
+
+	/**
+	 * Variable: EDGESTYLE_ORTHOGONAL
+	 * 
+	 * Name of the generic orthogonal edge style. Can be used as a string value
+	 * for the STYLE_EDGE style.
+	 */
+	EDGESTYLE_ORTHOGONAL: 'orthogonalEdgeStyle',
+
+	/**
+	 * Variable: EDGESTYLE_SEGMENT
+	 * 
+	 * Name of the generic segment edge style. Can be used as a string value
+	 * for the STYLE_EDGE style.
+	 */
+	EDGESTYLE_SEGMENT: 'segmentEdgeStyle',
+ 
+	/**
+	 * Variable: PERIMETER_ELLIPSE
+	 * 
+	 * Name of the ellipse perimeter. Can be used as a string value
+	 * for the STYLE_PERIMETER style.
+	 */
+	PERIMETER_ELLIPSE: 'ellipsePerimeter',
+
+	/**
+	 * Variable: PERIMETER_RECTANGLE
+	 *
+	 * Name of the rectangle perimeter. Can be used as a string value
+	 * for the STYLE_PERIMETER style.
+	 */
+	PERIMETER_RECTANGLE: 'rectanglePerimeter',
+
+	/**
+	 * Variable: PERIMETER_RHOMBUS
+	 * 
+	 * Name of the rhombus perimeter. Can be used as a string value
+	 * for the STYLE_PERIMETER style.
+	 */
+	PERIMETER_RHOMBUS: 'rhombusPerimeter',
+
+	/**
+	 * Variable: PERIMETER_HEXAGON
+	 * 
+	 * Name of the hexagon perimeter. Can be used as a string value 
+	 * for the STYLE_PERIMETER style.
+	 */
+	PERIMETER_HEXAGON: 'hexagonPerimeter',
+
+	/**
+	 * Variable: PERIMETER_TRIANGLE
+	 * 
+	 * Name of the triangle perimeter. Can be used as a string value
+	 * for the STYLE_PERIMETER style.
+	 */
+	PERIMETER_TRIANGLE: 'trianglePerimeter'
+
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/util/mxDictionary.js b/airavata-kubernetes/web-console/src/assets/js/util/mxDictionary.js
new file mode 100644
index 0000000..536dafa
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/util/mxDictionary.js
@@ -0,0 +1,130 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxDictionary
+ *
+ * A wrapper class for an associative array with object keys. Note: This
+ * implementation uses <mxObjectIdentitiy> to turn object keys into strings.
+ * 
+ * Constructor: mxEventSource
+ *
+ * Constructs a new dictionary which allows object to be used as keys.
+ */
+function mxDictionary()
+{
+	this.clear();
+};
+
+/**
+ * Function: map
+ *
+ * Stores the (key, value) pairs in this dictionary.
+ */
+mxDictionary.prototype.map = null;
+
+/**
+ * Function: clear
+ *
+ * Clears the dictionary.
+ */
+mxDictionary.prototype.clear = function()
+{
+	this.map = {};
+};
+
+/**
+ * Function: get
+ *
+ * Returns the value for the given key.
+ */
+mxDictionary.prototype.get = function(key)
+{
+	var id = mxObjectIdentity.get(key);
+	
+	return this.map[id];
+};
+
+/**
+ * Function: put
+ *
+ * Stores the value under the given key and returns the previous
+ * value for that key.
+ */
+mxDictionary.prototype.put = function(key, value)
+{
+	var id = mxObjectIdentity.get(key);
+	var previous = this.map[id];
+	this.map[id] = value;
+	
+	return previous;
+};
+
+/**
+ * Function: remove
+ *
+ * Removes the value for the given key and returns the value that
+ * has been removed.
+ */
+mxDictionary.prototype.remove = function(key)
+{
+	var id = mxObjectIdentity.get(key);
+	var previous = this.map[id];
+	delete this.map[id];
+	
+	return previous;
+};
+
+/**
+ * Function: getKeys
+ *
+ * Returns all keys as an array.
+ */
+mxDictionary.prototype.getKeys = function()
+{
+	var result = [];
+	
+	for (var key in this.map)
+	{
+		result.push(key);
+	}
+	
+	return result;
+};
+
+/**
+ * Function: getValues
+ *
+ * Returns all values as an array.
+ */
+mxDictionary.prototype.getValues = function()
+{
+	var result = [];
+	
+	for (var key in this.map)
+	{
+		result.push(this.map[key]);
+	}
+	
+	return result;
+};
+
+/**
+ * Function: visit
+ *
+ * Visits all entries in the dictionary using the given function with the
+ * following signature: function(key, value) where key is a string and
+ * value is an object.
+ * 
+ * Parameters:
+ * 
+ * visitor - A function that takes the key and value as arguments.
+ */
+mxDictionary.prototype.visit = function(visitor)
+{
+	for (var key in this.map)
+	{
+		visitor(key, this.map[key]);
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/util/mxDivResizer.js b/airavata-kubernetes/web-console/src/assets/js/util/mxDivResizer.js
new file mode 100644
index 0000000..0089796
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/util/mxDivResizer.js
@@ -0,0 +1,151 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxDivResizer
+ * 
+ * Maintains the size of a div element in Internet Explorer. This is a
+ * workaround for the right and bottom style being ignored in IE.
+ * 
+ * If you need a div to cover the scrollwidth and -height of a document,
+ * then you can use this class as follows:
+ * 
+ * (code)
+ * var resizer = new mxDivResizer(background);
+ * resizer.getDocumentHeight = function()
+ * {
+ *   return document.body.scrollHeight;
+ * }
+ * resizer.getDocumentWidth = function()
+ * {
+ *   return document.body.scrollWidth;
+ * }
+ * resizer.resize();
+ * (end)
+ * 
+ * Constructor: mxDivResizer
+ * 
+ * Constructs an object that maintains the size of a div
+ * element when the window is being resized. This is only
+ * required for Internet Explorer as it ignores the respective
+ * stylesheet information for DIV elements.
+ * 
+ * Parameters:
+ * 
+ * div - Reference to the DOM node whose size should be maintained.
+ * container - Optional Container that contains the div. Default is the
+ * window.
+ */
+function mxDivResizer(div, container)
+{
+	if (div.nodeName.toLowerCase() == 'div')
+	{
+		if (container == null)
+		{
+			container = window;
+		}
+
+		this.div = div;
+		var style = mxUtils.getCurrentStyle(div);
+		
+		if (style != null)
+		{
+			this.resizeWidth = style.width == 'auto';
+			this.resizeHeight = style.height == 'auto';
+		}
+		
+		mxEvent.addListener(container, 'resize',
+			mxUtils.bind(this, function(evt)
+			{
+				if (!this.handlingResize)
+				{
+					this.handlingResize = true;
+					this.resize();
+					this.handlingResize = false;
+				}
+			})
+		);
+		
+		this.resize();
+	}
+};
+
+/**
+ * Function: resizeWidth
+ * 
+ * Boolean specifying if the width should be updated.
+ */
+mxDivResizer.prototype.resizeWidth = true;
+
+/**
+ * Function: resizeHeight
+ * 
+ * Boolean specifying if the height should be updated.
+ */
+mxDivResizer.prototype.resizeHeight = true;
+
+/**
+ * Function: handlingResize
+ * 
+ * Boolean specifying if the width should be updated.
+ */
+mxDivResizer.prototype.handlingResize = false;
+
+/**
+ * Function: resize
+ * 
+ * Updates the style of the DIV after the window has been resized.
+ */
+mxDivResizer.prototype.resize = function()
+{
+	var w = this.getDocumentWidth();
+	var h = this.getDocumentHeight();
+
+	var l = parseInt(this.div.style.left);
+	var r = parseInt(this.div.style.right);
+	var t = parseInt(this.div.style.top);
+	var b = parseInt(this.div.style.bottom);
+	
+	if (this.resizeWidth &&
+		!isNaN(l) &&
+		!isNaN(r) &&
+		l >= 0 &&
+		r >= 0 &&
+		w - r - l > 0)
+	{
+		this.div.style.width = (w - r - l)+'px';
+	}
+	
+	if (this.resizeHeight &&
+		!isNaN(t) &&
+		!isNaN(b) &&
+		t >= 0 &&
+		b >= 0 &&
+		h - t - b > 0)
+	{
+		this.div.style.height = (h - t - b)+'px';
+	}
+};
+
+/**
+ * Function: getDocumentWidth
+ * 
+ * Hook for subclassers to return the width of the document (without
+ * scrollbars).
+ */
+mxDivResizer.prototype.getDocumentWidth = function()
+{
+	return document.body.clientWidth;
+};
+
+/**
+ * Function: getDocumentHeight
+ * 
+ * Hook for subclassers to return the height of the document (without
+ * scrollbars).
+ */
+mxDivResizer.prototype.getDocumentHeight = function()
+{
+	return document.body.clientHeight;
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/util/mxDragSource.js b/airavata-kubernetes/web-console/src/assets/js/util/mxDragSource.js
new file mode 100644
index 0000000..1670489
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/util/mxDragSource.js
@@ -0,0 +1,679 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxDragSource
+ * 
+ * Wrapper to create a drag source from a DOM element so that the element can
+ * be dragged over a graph and dropped into the graph as a new cell.
+ * 
+ * Problem is that in the dropHandler the current preview location is not
+ * available, so the preview and the dropHandler must match.
+ * 
+ * Constructor: mxDragSource
+ * 
+ * Constructs a new drag source for the given element.
+ */
+function mxDragSource(element, dropHandler)
+{
+	this.element = element;
+	this.dropHandler = dropHandler;
+	
+	// Handles a drag gesture on the element
+	mxEvent.addGestureListeners(element, mxUtils.bind(this, function(evt)
+	{
+		this.mouseDown(evt);
+	}));
+	
+	// Prevents native drag and drop
+	mxEvent.addListener(element, 'dragstart', function(evt)
+	{
+		mxEvent.consume(evt);
+	});
+	
+	this.eventConsumer = function(sender, evt)
+	{
+		var evtName = evt.getProperty('eventName');
+		var me = evt.getProperty('event');
+		
+		if (evtName != mxEvent.MOUSE_DOWN)
+		{
+			me.consume();
+		}
+	};
+};
+
+/**
+ * Variable: element
+ *
+ * Reference to the DOM node which was made draggable.
+ */
+mxDragSource.prototype.element = null;
+
+/**
+ * Variable: dropHandler
+ *
+ * Holds the DOM node that is used to represent the drag preview. If this is
+ * null then the source element will be cloned and used for the drag preview.
+ */
+mxDragSource.prototype.dropHandler = null;
+
+/**
+ * Variable: dragOffset
+ *
+ * <mxPoint> that specifies the offset of the <dragElement>. Default is null.
+ */
+mxDragSource.prototype.dragOffset = null;
+
+/**
+ * Variable: dragElement
+ *
+ * Holds the DOM node that is used to represent the drag preview. If this is
+ * null then the source element will be cloned and used for the drag preview.
+ */
+mxDragSource.prototype.dragElement = null;
+
+/**
+ * Variable: previewElement
+ *
+ * Optional <mxRectangle> that specifies the unscaled size of the preview.
+ */
+mxDragSource.prototype.previewElement = null;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies if this drag source is enabled. Default is true.
+ */
+mxDragSource.prototype.enabled = true;
+
+/**
+ * Variable: currentGraph
+ *
+ * Reference to the <mxGraph> that is the current drop target.
+ */
+mxDragSource.prototype.currentGraph = null;
+
+/**
+ * Variable: currentDropTarget
+ *
+ * Holds the current drop target under the mouse.
+ */
+mxDragSource.prototype.currentDropTarget = null;
+
+/**
+ * Variable: currentPoint
+ *
+ * Holds the current drop location.
+ */
+mxDragSource.prototype.currentPoint = null;
+
+/**
+ * Variable: currentGuide
+ *
+ * Holds an <mxGuide> for the <currentGraph> if <dragPreview> is not null.
+ */
+mxDragSource.prototype.currentGuide = null;
+
+/**
+ * Variable: currentGuide
+ *
+ * Holds an <mxGuide> for the <currentGraph> if <dragPreview> is not null.
+ */
+mxDragSource.prototype.currentHighlight = null;
+
+/**
+ * Variable: autoscroll
+ *
+ * Specifies if the graph should scroll automatically. Default is true.
+ */
+mxDragSource.prototype.autoscroll = true;
+
+/**
+ * Variable: guidesEnabled
+ *
+ * Specifies if <mxGuide> should be enabled. Default is true.
+ */
+mxDragSource.prototype.guidesEnabled = true;
+
+/**
+ * Variable: gridEnabled
+ *
+ * Specifies if the grid should be allowed. Default is true.
+ */
+mxDragSource.prototype.gridEnabled = true;
+
+/**
+ * Variable: highlightDropTargets
+ *
+ * Specifies if drop targets should be highlighted. Default is true.
+ */
+mxDragSource.prototype.highlightDropTargets = true;
+
+/**
+ * Variable: dragElementZIndex
+ * 
+ * ZIndex for the drag element. Default is 100.
+ */
+mxDragSource.prototype.dragElementZIndex = 100;
+
+/**
+ * Variable: dragElementOpacity
+ * 
+ * Opacity of the drag element in %. Default is 70.
+ */
+mxDragSource.prototype.dragElementOpacity = 70;
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns <enabled>.
+ */
+mxDragSource.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ * 
+ * Sets <enabled>.
+ */
+mxDragSource.prototype.setEnabled = function(value)
+{
+	this.enabled = value;
+};
+
+/**
+ * Function: isGuidesEnabled
+ * 
+ * Returns <guidesEnabled>.
+ */
+mxDragSource.prototype.isGuidesEnabled = function()
+{
+	return this.guidesEnabled;
+};
+
+/**
+ * Function: setGuidesEnabled
+ * 
+ * Sets <guidesEnabled>.
+ */
+mxDragSource.prototype.setGuidesEnabled = function(value)
+{
+	this.guidesEnabled = value;
+};
+
+/**
+ * Function: isGridEnabled
+ * 
+ * Returns <gridEnabled>.
+ */
+mxDragSource.prototype.isGridEnabled = function()
+{
+	return this.gridEnabled;
+};
+
+/**
+ * Function: setGridEnabled
+ * 
+ * Sets <gridEnabled>.
+ */
+mxDragSource.prototype.setGridEnabled = function(value)
+{
+	this.gridEnabled = value;
+};
+
+/**
+ * Function: getGraphForEvent
+ * 
+ * Returns the graph for the given mouse event. This implementation returns
+ * null.
+ */
+mxDragSource.prototype.getGraphForEvent = function(evt)
+{
+	return null;
+};
+
+/**
+ * Function: getDropTarget
+ * 
+ * Returns the drop target for the given graph and coordinates. This
+ * implementation uses <mxGraph.getCellAt>.
+ */
+mxDragSource.prototype.getDropTarget = function(graph, x, y, evt)
+{
+	return graph.getCellAt(x, y);
+};
+
+/**
+ * Function: createDragElement
+ * 
+ * Creates and returns a clone of the <dragElementPrototype> or the <element>
+ * if the former is not defined.
+ */
+mxDragSource.prototype.createDragElement = function(evt)
+{
+	return this.element.cloneNode(true);
+};
+
+/**
+ * Function: createPreviewElement
+ * 
+ * Creates and returns an element which can be used as a preview in the given
+ * graph.
+ */
+mxDragSource.prototype.createPreviewElement = function(graph)
+{
+	return null;
+};
+
+/**
+ * Function: isActive
+ * 
+ * Returns true if this drag source is active.
+ */
+mxDragSource.prototype.isActive = function()
+{
+	return this.mouseMoveHandler != null;
+};
+
+/**
+ * Function: reset
+ * 
+ * Stops and removes everything and restores the state of the object.
+ */
+mxDragSource.prototype.reset = function()
+{
+	if (this.currentGraph != null)
+	{
+		this.dragExit(this.currentGraph);
+		this.currentGraph = null;
+	}
+	
+	this.removeDragElement();
+	this.removeListeners();
+	this.stopDrag();
+};
+
+/**
+ * Function: mouseDown
+ * 
+ * Returns the drop target for the given graph and coordinates. This
+ * implementation uses <mxGraph.getCellAt>.
+ * 
+ * To ignore popup menu events for a drag source, this function can be
+ * overridden as follows.
+ * 
+ * (code)
+ * var mouseDown = dragSource.mouseDown;
+ * 
+ * dragSource.mouseDown = function(evt)
+ * {
+ *   if (!mxEvent.isPopupTrigger(evt))
+ *   {
+ *     mouseDown.apply(this, arguments);
+ *   }
+ * };
+ * (end)
+ */
+mxDragSource.prototype.mouseDown = function(evt)
+{
+	if (this.enabled && !mxEvent.isConsumed(evt) && this.mouseMoveHandler == null)
+	{
+		this.startDrag(evt);
+		this.mouseMoveHandler = mxUtils.bind(this, this.mouseMove);
+		this.mouseUpHandler = mxUtils.bind(this, this.mouseUp);		
+		mxEvent.addGestureListeners(document, null, this.mouseMoveHandler, this.mouseUpHandler);
+		
+		if (mxClient.IS_TOUCH && !mxEvent.isMouseEvent(evt))
+		{
+			this.eventSource = mxEvent.getSource(evt);
+			mxEvent.addGestureListeners(this.eventSource, null, this.mouseMoveHandler, this.mouseUpHandler);
+		}
+	}
+};
+
+/**
+ * Function: startDrag
+ * 
+ * Creates the <dragElement> using <createDragElement>.
+ */
+mxDragSource.prototype.startDrag = function(evt)
+{
+	this.dragElement = this.createDragElement(evt);
+	this.dragElement.style.position = 'absolute';
+	this.dragElement.style.zIndex = this.dragElementZIndex;
+	mxUtils.setOpacity(this.dragElement, this.dragElementOpacity);
+};
+
+/**
+ * Function: stopDrag
+ * 
+ * Invokes <removeDragElement>.
+ */
+mxDragSource.prototype.stopDrag = function()
+{
+	// LATER: This used to have a mouse event. If that is still needed we need to add another
+	// final call to the DnD protocol to add a cleanup step in the case of escape press, which
+	// is not associated with a mouse event and which currently calles this method.
+	this.removeDragElement();
+};
+
+/**
+ * Function: removeDragElement
+ * 
+ * Removes and destroys the <dragElement>.
+ */
+mxDragSource.prototype.removeDragElement = function()
+{
+	if (this.dragElement != null)
+	{
+		if (this.dragElement.parentNode != null)
+		{
+			this.dragElement.parentNode.removeChild(this.dragElement);
+		}
+		
+		this.dragElement = null;
+	}
+};
+
+/**
+ * Function: graphContainsEvent
+ * 
+ * Returns true if the given graph contains the given event.
+ */
+mxDragSource.prototype.graphContainsEvent = function(graph, evt)
+{
+	var x = mxEvent.getClientX(evt);
+	var y = mxEvent.getClientY(evt);
+	var offset = mxUtils.getOffset(graph.container);
+	var origin = mxUtils.getScrollOrigin();
+
+	// Checks if event is inside the bounds of the graph container
+	return x >= offset.x - origin.x && y >= offset.y - origin.y &&
+		x <= offset.x - origin.x + graph.container.offsetWidth &&
+		y <= offset.y - origin.y + graph.container.offsetHeight;
+};
+
+/**
+ * Function: mouseMove
+ * 
+ * Gets the graph for the given event using <getGraphForEvent>, updates the
+ * <currentGraph>, calling <dragEnter> and <dragExit> on the new and old graph,
+ * respectively, and invokes <dragOver> if <currentGraph> is not null.
+ */
+mxDragSource.prototype.mouseMove = function(evt)
+{
+	var graph = this.getGraphForEvent(evt);
+	
+	// Checks if event is inside the bounds of the graph container
+	if (graph != null && !this.graphContainsEvent(graph, evt))
+	{
+		graph = null;
+	}
+
+	if (graph != this.currentGraph)
+	{
+		if (this.currentGraph != null)
+		{
+			this.dragExit(this.currentGraph, evt);
+		}
+		
+		this.currentGraph = graph;
+		
+		if (this.currentGraph != null)
+		{
+			this.dragEnter(this.currentGraph, evt);
+		}
+	}
+	
+	if (this.currentGraph != null)
+	{
+		this.dragOver(this.currentGraph, evt);
+	}
+
+	if (this.dragElement != null && (this.previewElement == null || this.previewElement.style.visibility != 'visible'))
+	{
+		var x = mxEvent.getClientX(evt);
+		var y = mxEvent.getClientY(evt);
+		
+		if (this.dragElement.parentNode == null)
+		{
+			document.body.appendChild(this.dragElement);
+		}
+
+		this.dragElement.style.visibility = 'visible';
+		
+		if (this.dragOffset != null)
+		{
+			x += this.dragOffset.x;
+			y += this.dragOffset.y;
+		}
+		
+		var offset = mxUtils.getDocumentScrollOrigin(document);
+		
+		this.dragElement.style.left = (x + offset.x) + 'px';
+		this.dragElement.style.top = (y + offset.y) + 'px';
+	}
+	else if (this.dragElement != null)
+	{
+		this.dragElement.style.visibility = 'hidden';
+	}
+	
+	mxEvent.consume(evt);
+};
+
+/**
+ * Function: mouseUp
+ * 
+ * Processes the mouse up event and invokes <drop>, <dragExit> and <stopDrag>
+ * as required.
+ */
+mxDragSource.prototype.mouseUp = function(evt)
+{
+	if (this.currentGraph != null)
+	{
+		if (this.currentPoint != null && (this.previewElement == null ||
+			this.previewElement.style.visibility != 'hidden'))
+		{
+			var scale = this.currentGraph.view.scale;
+			var tr = this.currentGraph.view.translate;
+			var x = this.currentPoint.x / scale - tr.x;
+			var y = this.currentPoint.y / scale - tr.y;
+			
+			this.drop(this.currentGraph, evt, this.currentDropTarget, x, y);
+		}
+		
+		this.dragExit(this.currentGraph);
+		this.currentGraph = null;
+	}
+
+	this.stopDrag();
+	this.removeListeners();
+	
+	mxEvent.consume(evt);
+};
+
+/**
+ * Function: removeListeners
+ * 
+ * Actives the given graph as a drop target.
+ */
+mxDragSource.prototype.removeListeners = function()
+{
+	if (this.eventSource != null)
+	{
+		mxEvent.removeGestureListeners(this.eventSource, null, this.mouseMoveHandler, this.mouseUpHandler);
+		this.eventSource = null;
+	}
+	
+	mxEvent.removeGestureListeners(document, null, this.mouseMoveHandler, this.mouseUpHandler);
+	this.mouseMoveHandler = null;
+	this.mouseUpHandler = null;
+};
+
+/**
+ * Function: dragEnter
+ * 
+ * Actives the given graph as a drop target.
+ */
+mxDragSource.prototype.dragEnter = function(graph, evt)
+{
+	graph.isMouseDown = true;
+	graph.isMouseTrigger = mxEvent.isMouseEvent(evt);
+	this.previewElement = this.createPreviewElement(graph);
+	
+	// Guide is only needed if preview element is used
+	if (this.isGuidesEnabled() && this.previewElement != null)
+	{
+		this.currentGuide = new mxGuide(graph, graph.graphHandler.getGuideStates());
+	}
+	
+	if (this.highlightDropTargets)
+	{
+		this.currentHighlight = new mxCellHighlight(graph, mxConstants.DROP_TARGET_COLOR);
+	}
+	
+	// Consumes all events in the current graph before they are fired
+	graph.addListener(mxEvent.FIRE_MOUSE_EVENT, this.eventConsumer);
+};
+
+/**
+ * Function: dragExit
+ * 
+ * Deactivates the given graph as a drop target.
+ */
+mxDragSource.prototype.dragExit = function(graph, evt)
+{
+	this.currentDropTarget = null;
+	this.currentPoint = null;
+	graph.isMouseDown = false;
+	
+	// Consumes all events in the current graph before they are fired
+	graph.removeListener(this.eventConsumer);
+	
+	if (this.previewElement != null)
+	{
+		if (this.previewElement.parentNode != null)
+		{
+			this.previewElement.parentNode.removeChild(this.previewElement);
+		}
+		
+		this.previewElement = null;
+	}
+	
+	if (this.currentGuide != null)
+	{
+		this.currentGuide.destroy();
+		this.currentGuide = null;
+	}
+	
+	if (this.currentHighlight != null)
+	{
+		this.currentHighlight.destroy();
+		this.currentHighlight = null;
+	}
+};
+
+/**
+ * Function: dragOver
+ * 
+ * Implements autoscroll, updates the <currentPoint>, highlights any drop
+ * targets and updates the preview.
+ */
+mxDragSource.prototype.dragOver = function(graph, evt)
+{
+	var offset = mxUtils.getOffset(graph.container);
+	var origin = mxUtils.getScrollOrigin(graph.container);
+	var x = mxEvent.getClientX(evt) - offset.x + origin.x - graph.panDx;
+	var y = mxEvent.getClientY(evt) - offset.y + origin.y - graph.panDy;
+
+	if (graph.autoScroll && (this.autoscroll == null || this.autoscroll))
+	{
+		graph.scrollPointToVisible(x, y, graph.autoExtend);
+	}
+
+	// Highlights the drop target under the mouse
+	if (this.currentHighlight != null && graph.isDropEnabled())
+	{
+		this.currentDropTarget = this.getDropTarget(graph, x, y, evt);
+		var state = graph.getView().getState(this.currentDropTarget);
+		this.currentHighlight.highlight(state);
+	}
+
+	// Updates the location of the preview
+	if (this.previewElement != null)
+	{
+		if (this.previewElement.parentNode == null)
+		{
+			graph.container.appendChild(this.previewElement);
+			
+			this.previewElement.style.zIndex = '3';
+			this.previewElement.style.position = 'absolute';
+		}
+		
+		var gridEnabled = this.isGridEnabled() && graph.isGridEnabledEvent(evt);
+		var hideGuide = true;
+
+		// Grid and guides
+		if (this.currentGuide != null && this.currentGuide.isEnabledForEvent(evt))
+		{
+			// LATER: HTML preview appears smaller than SVG preview
+			var w = parseInt(this.previewElement.style.width);
+			var h = parseInt(this.previewElement.style.height);
+			var bounds = new mxRectangle(0, 0, w, h);
+			var delta = new mxPoint(x, y);
+			delta = this.currentGuide.move(bounds, delta, gridEnabled);
+			hideGuide = false;
+			x = delta.x;
+			y = delta.y;
+		}
+		else if (gridEnabled)
+		{
+			var scale = graph.view.scale;
+			var tr = graph.view.translate;
+			var off = graph.gridSize / 2;
+			x = (graph.snap(x / scale - tr.x - off) + tr.x) * scale;
+			y = (graph.snap(y / scale - tr.y - off) + tr.y) * scale;
+		}
+		
+		if (this.currentGuide != null && hideGuide)
+		{
+			this.currentGuide.hide();
+		}
+		
+		if (this.previewOffset != null)
+		{
+			x += this.previewOffset.x;
+			y += this.previewOffset.y;
+		}
+
+		this.previewElement.style.left = Math.round(x) + 'px';
+		this.previewElement.style.top = Math.round(y) + 'px';
+		this.previewElement.style.visibility = 'visible';
+	}
+	
+	this.currentPoint = new mxPoint(x, y);
+};
+
+/**
+ * Function: drop
+ * 
+ * Returns the drop target for the given graph and coordinates. This
+ * implementation uses <mxGraph.getCellAt>.
+ */
+mxDragSource.prototype.drop = function(graph, evt, dropTarget, x, y)
+{
+	this.dropHandler(graph, evt, dropTarget, x, y);
+	
+	// Had to move this to after the insert because it will
+	// affect the scrollbars of the window in IE to try and
+	// make the complete container visible.
+	// LATER: Should be made optional.
+	if (graph.container.style.visibility != 'hidden')
+	{
+		graph.container.focus();
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/util/mxEffects.js b/airavata-kubernetes/web-console/src/assets/js/util/mxEffects.js
new file mode 100644
index 0000000..f7a1891
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/util/mxEffects.js
@@ -0,0 +1,211 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+var mxEffects =
+{
+
+	/**
+	 * Class: mxEffects
+	 * 
+	 * Provides animation effects.
+	 */
+
+	/**
+	 * Function: animateChanges
+	 * 
+	 * Asynchronous animated move operation. See also: <mxMorphing>.
+	 * 
+	 * Example:
+	 * 
+	 * (code)
+	 * graph.model.addListener(mxEvent.CHANGE, function(sender, evt)
+	 * {
+	 *   var changes = evt.getProperty('edit').changes;
+	 * 
+	 *   if (changes.length < 10)
+	 *   {
+	 *     mxEffects.animateChanges(graph, changes);
+	 *   }
+	 * });
+	 * (end)
+	 * 
+	 * Parameters:
+	 * 
+	 * graph - <mxGraph> that received the changes.
+	 * changes - Array of changes to be animated.
+	 * done - Optional function argument that is invoked after the
+	 * last step of the animation.
+	 */
+	animateChanges: function(graph, changes, done)
+	{
+		var maxStep = 10;
+		var step = 0;
+
+		var animate = function() 
+		{
+			var isRequired = false;
+			
+			for (var i = 0; i < changes.length; i++)
+			{
+				var change = changes[i];
+				
+				if (change instanceof mxGeometryChange ||
+					change instanceof mxTerminalChange ||
+					change instanceof mxValueChange ||
+					change instanceof mxChildChange ||
+					change instanceof mxStyleChange)
+				{
+					var state = graph.getView().getState(change.cell || change.child, false);
+					
+					if (state != null)
+					{
+						isRequired = true;
+					
+						if (change.constructor != mxGeometryChange || graph.model.isEdge(change.cell))
+						{
+							mxUtils.setOpacity(state.shape.node, 100 * step / maxStep);
+						}
+						else
+						{
+							var scale = graph.getView().scale;					
+
+							var dx = (change.geometry.x - change.previous.x) * scale;
+							var dy = (change.geometry.y - change.previous.y) * scale;
+							
+							var sx = (change.geometry.width - change.previous.width) * scale;
+							var sy = (change.geometry.height - change.previous.height) * scale;
+							
+							if (step == 0)
+							{
+								state.x -= dx;
+								state.y -= dy;
+								state.width -= sx;
+								state.height -= sy;
+							}
+							else
+							{
+								state.x += dx / maxStep;
+								state.y += dy / maxStep;
+								state.width += sx / maxStep;
+								state.height += sy / maxStep;
+							}
+							
+							graph.cellRenderer.redraw(state);
+							
+							// Fades all connected edges and children
+							mxEffects.cascadeOpacity(graph, change.cell, 100 * step / maxStep);
+						}
+					}
+				}
+			}
+
+			if (step < maxStep && isRequired)
+			{
+				step++;
+				window.setTimeout(animate, delay);
+			}
+			else if (done != null)
+			{
+				done();
+			}
+		};
+		
+		var delay = 30;
+		animate();
+	},
+    
+	/**
+	 * Function: cascadeOpacity
+	 * 
+	 * Sets the opacity on the given cell and its descendants.
+	 * 
+	 * Parameters:
+	 * 
+	 * graph - <mxGraph> that contains the cells.
+	 * cell - <mxCell> to set the opacity for.
+	 * opacity - New value for the opacity in %.
+	 */
+    cascadeOpacity: function(graph, cell, opacity)
+	{
+		// Fades all children
+		var childCount = graph.model.getChildCount(cell);
+		
+		for (var i=0; i<childCount; i++)
+		{
+			var child = graph.model.getChildAt(cell, i);
+			var childState = graph.getView().getState(child);
+			
+			if (childState != null)
+			{
+				mxUtils.setOpacity(childState.shape.node, opacity);
+				mxEffects.cascadeOpacity(graph, child, opacity);
+			}
+		}
+		
+		// Fades all connected edges
+		var edges = graph.model.getEdges(cell);
+		
+		if (edges != null)
+		{
+			for (var i=0; i<edges.length; i++)
+			{
+				var edgeState = graph.getView().getState(edges[i]);
+				
+				if (edgeState != null)
+				{
+					mxUtils.setOpacity(edgeState.shape.node, opacity);
+				}
+			}
+		}
+	},
+
+	/**
+	 * Function: fadeOut
+	 * 
+	 * Asynchronous fade-out operation.
+	 */
+	fadeOut: function(node, from, remove, step, delay, isEnabled)
+	{
+		step = step || 40;
+		delay = delay || 30;
+		
+		var opacity = from || 100;
+		
+		mxUtils.setOpacity(node, opacity);
+		
+		if (isEnabled || isEnabled == null)
+		{
+			var f = function()
+			{
+			    opacity = Math.max(opacity-step, 0);
+				mxUtils.setOpacity(node, opacity);
+				
+				if (opacity > 0)
+				{
+					window.setTimeout(f, delay);
+				}
+				else
+				{
+					node.style.visibility = 'hidden';
+					
+					if (remove && node.parentNode)
+					{
+						node.parentNode.removeChild(node);
+					}
+				}
+			};
+			window.setTimeout(f, delay);
+		}
+		else
+		{
+			node.style.visibility = 'hidden';
+			
+			if (remove && node.parentNode)
+			{
+				node.parentNode.removeChild(node);
+			}
+		}
+	}
+
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/util/mxEvent.js b/airavata-kubernetes/web-console/src/assets/js/util/mxEvent.js
new file mode 100644
index 0000000..ae1143f
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/util/mxEvent.js
@@ -0,0 +1,1406 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+var mxEvent =
+{
+
+	/**
+	 * Class: mxEvent
+	 * 
+	 * Cross-browser DOM event support. For internal event handling,
+	 * <mxEventSource> and the graph event dispatch loop in <mxGraph> are used.
+	 * 
+	 * Memory Leaks:
+	 * 
+	 * Use this class for adding and removing listeners to/from DOM nodes. The
+	 * <removeAllListeners> function is provided to remove all listeners that
+	 * have been added using <addListener>. The function should be invoked when
+	 * the last reference is removed in the JavaScript code, typically when the
+	 * referenced DOM node is removed from the DOM, and helps to reduce memory
+	 * leaks in IE6.
+	 * 
+	 * Variable: objects
+	 * 
+	 * Contains all objects where any listener was added using <addListener>.
+	 * This is used to reduce memory leaks in IE, see <mxClient.dispose>.
+	 */
+	objects: [],
+
+	 /**
+	  * Function: addListener
+	  * 
+	  * Binds the function to the specified event on the given element. Use
+	  * <mxUtils.bind> in order to bind the "this" keyword inside the function
+	  * to a given execution scope.
+	  */
+	addListener: function()
+	{
+		var updateListenerList = function(element, eventName, funct)
+		{
+			if (element.mxListenerList == null)
+			{
+				element.mxListenerList = [];
+				mxEvent.objects.push(element);
+			}
+			
+			var entry = {name: eventName, f: funct};
+			element.mxListenerList.push(entry);
+		};
+		
+		if (window.addEventListener)
+		{
+			return function(element, eventName, funct)
+			{
+				element.addEventListener(eventName, funct, false);
+				updateListenerList(element, eventName, funct);
+			};
+		}
+		else
+		{
+			return function(element, eventName, funct)
+			{
+				element.attachEvent('on' + eventName, funct);
+				updateListenerList(element, eventName, funct);				
+			};
+		}
+	}(),
+
+	/**
+	 * Function: removeListener
+	 *
+	 * Removes the specified listener from the given element.
+	 */
+	removeListener: function()
+	{
+		var updateListener = function(element, eventName, funct)
+		{
+			if (element.mxListenerList != null)
+			{
+				var listenerCount = element.mxListenerList.length;
+				
+				for (var i = 0; i < listenerCount; i++)
+				{
+					var entry = element.mxListenerList[i];
+					
+					if (entry.f == funct)
+					{
+						element.mxListenerList.splice(i, 1);
+						break;
+					}
+				}
+				
+				if (element.mxListenerList.length == 0)
+				{
+					element.mxListenerList = null;
+					
+					var idx = mxUtils.indexOf(mxEvent.objects, element);
+					
+					if (idx >= 0)
+					{
+						mxEvent.objects.splice(idx, 1);
+					}
+				}
+			}
+		};
+		
+		if (window.removeEventListener)
+		{
+			return function(element, eventName, funct)
+			{
+				element.removeEventListener(eventName, funct, false);
+				updateListener(element, eventName, funct);
+			};
+		}
+		else
+		{
+			return function(element, eventName, funct)
+			{
+				element.detachEvent('on' + eventName, funct);
+				updateListener(element, eventName, funct);
+			};
+		}
+	}(),
+
+	/**
+	 * Function: removeAllListeners
+	 * 
+	 * Removes all listeners from the given element.
+	 */
+	removeAllListeners: function(element)
+	{
+		var list = element.mxListenerList;
+
+		if (list != null)
+		{
+			while (list.length > 0)
+			{
+				var entry = list[0];
+				mxEvent.removeListener(element, entry.name, entry.f);
+			}
+		}
+	},
+	
+	/**
+	 * Function: addGestureListeners
+	 * 
+	 * Adds the given listeners for touch, mouse and/or pointer events. If
+	 * <mxClient.IS_POINTER> is true then pointer events will be registered,
+	 * else the respective mouse events will be registered. If <mxClient.IS_POINTER>
+	 * is false and <mxClient.IS_TOUCH> is true then the respective touch events
+	 * will be registered as well as the mouse events.
+	 */
+	addGestureListeners: function(node, startListener, moveListener, endListener)
+	{
+		if (startListener != null)
+		{
+			mxEvent.addListener(node, (mxClient.IS_POINTER) ? 'pointerdown' : 'mousedown', startListener);
+		}
+		
+		if (moveListener != null)
+		{
+			mxEvent.addListener(node, (mxClient.IS_POINTER) ? 'pointermove' : 'mousemove', moveListener);
+		}
+		
+		if (endListener != null)
+		{
+			mxEvent.addListener(node, (mxClient.IS_POINTER) ? 'pointerup' : 'mouseup', endListener);
+		}
+		
+		if (!mxClient.IS_POINTER && mxClient.IS_TOUCH)
+		{
+			if (startListener != null)
+			{
+				mxEvent.addListener(node, 'touchstart', startListener);
+			}
+			
+			if (moveListener != null)
+			{
+				mxEvent.addListener(node, 'touchmove', moveListener);
+			}
+			
+			if (endListener != null)
+			{
+				mxEvent.addListener(node, 'touchend', endListener);
+			}
+		}
+	},
+	
+	/**
+	 * Function: removeGestureListeners
+	 * 
+	 * Removes the given listeners from mousedown, mousemove, mouseup and the
+	 * respective touch events if <mxClient.IS_TOUCH> is true.
+	 */
+	removeGestureListeners: function(node, startListener, moveListener, endListener)
+	{
+		if (startListener != null)
+		{
+			mxEvent.removeListener(node, (mxClient.IS_POINTER) ? 'pointerdown' : 'mousedown', startListener);
+		}
+		
+		if (moveListener != null)
+		{
+			mxEvent.removeListener(node, (mxClient.IS_POINTER) ? 'pointermove' : 'mousemove', moveListener);
+		}
+		
+		if (endListener != null)
+		{
+			mxEvent.removeListener(node, (mxClient.IS_POINTER) ? 'pointerup' : 'mouseup', endListener);
+		}
+		
+		if (!mxClient.IS_POINTER && mxClient.IS_TOUCH)
+		{
+			if (startListener != null)
+			{
+				mxEvent.removeListener(node, 'touchstart', startListener);
+			}
+			
+			if (moveListener != null)
+			{
+				mxEvent.removeListener(node, 'touchmove', moveListener);
+			}
+			
+			if (endListener != null)
+			{
+				mxEvent.removeListener(node, 'touchend', endListener);
+			}
+		}
+	},
+	
+	/**
+	 * Function: redirectMouseEvents
+	 *
+	 * Redirects the mouse events from the given DOM node to the graph dispatch
+	 * loop using the event and given state as event arguments. State can
+	 * either be an instance of <mxCellState> or a function that returns an
+	 * <mxCellState>. The down, move, up and dblClick arguments are optional
+	 * functions that take the trigger event as arguments and replace the
+	 * default behaviour.
+	 */
+	redirectMouseEvents: function(node, graph, state, down, move, up, dblClick)
+	{
+		var getState = function(evt)
+		{
+			return (typeof(state) == 'function') ? state(evt) : state;
+		};
+		
+		mxEvent.addGestureListeners(node, function (evt)
+		{
+			if (down != null)
+			{
+				down(evt);
+			}
+			else if (!mxEvent.isConsumed(evt))
+			{
+				graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt, getState(evt)));
+			}
+		},
+		function (evt)
+		{
+			if (move != null)
+			{
+				move(evt);
+			}
+			else if (!mxEvent.isConsumed(evt))
+			{
+				graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, getState(evt)));
+			}
+		},
+		function (evt)
+		{
+			if (up != null)
+			{
+				up(evt);
+			}
+			else if (!mxEvent.isConsumed(evt))
+			{
+				graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt, getState(evt)));
+			}
+		});
+
+		mxEvent.addListener(node, 'dblclick', function (evt)
+		{
+			if (dblClick != null)
+			{
+				dblClick(evt);
+			}
+			else if (!mxEvent.isConsumed(evt))
+			{
+				var tmp = getState(evt);
+				graph.dblClick(evt, (tmp != null) ? tmp.cell : null);
+			}
+		});
+	},
+
+	/**
+	 * Function: release
+	 * 
+	 * Removes the known listeners from the given DOM node and its descendants.
+	 * 
+	 * Parameters:
+	 * 
+	 * element - DOM node to remove the listeners from.
+	 */
+	release: function(element)
+	{
+		if (element != null)
+		{
+			mxEvent.removeAllListeners(element);
+			
+			var children = element.childNodes;
+			
+			if (children != null)
+			{
+		        var childCount = children.length;
+		        
+		        for (var i = 0; i < childCount; i += 1)
+		        {
+		        	mxEvent.release(children[i]);
+		        }
+		    }
+		}
+	},
+
+	/**
+	 * Function: addMouseWheelListener
+	 * 
+	 * Installs the given function as a handler for mouse wheel events. The
+	 * function has two arguments: the mouse event and a boolean that specifies
+	 * if the wheel was moved up or down.
+	 * 
+	 * This has been tested with IE 6 and 7, Firefox (all versions), Opera and
+	 * Safari. It does currently not work on Safari for Mac.
+	 * 
+	 * Example:
+	 * 
+	 * (code)
+	 * mxEvent.addMouseWheelListener(function (evt, up)
+	 * {
+	 *   mxLog.show();
+	 *   mxLog.debug('mouseWheel: up='+up);
+	 * });
+	 *(end)
+	 * 
+	 * Parameters:
+	 * 
+	 * funct - Handler function that takes the event argument and a boolean up
+	 * argument for the mousewheel direction.
+	 */
+	addMouseWheelListener: function(funct)
+	{
+		if (funct != null)
+		{
+			var wheelHandler = function(evt)
+			{
+				// IE does not give an event object but the
+				// global event object is the mousewheel event
+				// at this point in time.
+				if (evt == null)
+				{
+					evt = window.event;
+				}
+			
+				var delta = 0;
+				
+				if (mxClient.IS_FF)
+				{
+					delta = -evt.detail / 2;
+				}
+				else
+				{
+					delta = evt.wheelDelta / 120;
+				}
+				
+				// Handles the event using the given function
+				if (delta != 0)
+				{
+					funct(evt, delta > 0);
+				}
+			};
+	
+			// Webkit has NS event API, but IE event name and details 
+			if (mxClient.IS_NS && document.documentMode == null)
+			{
+				var eventName = (mxClient.IS_SF || 	mxClient.IS_GC) ? 'mousewheel' : 'DOMMouseScroll';
+				mxEvent.addListener(window, eventName, wheelHandler);
+			}
+			else
+			{
+				mxEvent.addListener(document, 'mousewheel', wheelHandler);
+			}
+		}
+	},
+	
+	/**
+	 * Function: disableContextMenu
+	 *
+	 * Disables the context menu for the given element.
+	 */
+	disableContextMenu: function()
+	{
+		if (mxClient.IS_IE && (typeof(document.documentMode) === 'undefined' || document.documentMode < 9))
+		{
+			return function(element)
+			{
+				mxEvent.addListener(element, 'contextmenu', function()
+				{
+					return false;
+				});
+			};
+		}
+		else
+		{
+			return function(element)
+			{
+				element.setAttribute('oncontextmenu', 'return false;');
+			};		
+		}
+	}(),
+	
+	/**
+	 * Function: getSource
+	 * 
+	 * Returns the event's target or srcElement depending on the browser.
+	 */
+	getSource: function(evt)
+	{
+		return (evt.srcElement != null) ? evt.srcElement : evt.target;
+	},
+
+	/**
+	 * Function: isConsumed
+	 * 
+	 * Returns true if the event has been consumed using <consume>.
+	 */
+	isConsumed: function(evt)
+	{
+		return evt.isConsumed != null && evt.isConsumed;
+	},
+
+	/**
+	 * Function: isTouchEvent
+	 * 
+	 * Returns true if the event was generated using a touch device (not a pen or mouse).
+	 */
+	isTouchEvent: function(evt)
+	{
+		return (evt.pointerType != null) ? (evt.pointerType == 'touch' || evt.pointerType ===
+			evt.MSPOINTER_TYPE_TOUCH) : ((evt.mozInputSource != null) ?
+					evt.mozInputSource == 5 : evt.type.indexOf('touch') == 0);
+	},
+
+	/**
+	 * Function: isPenEvent
+	 * 
+	 * Returns true if the event was generated using a pen (not a touch device or mouse).
+	 */
+	isPenEvent: function(evt)
+	{
+		return (evt.pointerType != null) ? (evt.pointerType == 'pen' || evt.pointerType ===
+			evt.MSPOINTER_TYPE_PEN) : ((evt.mozInputSource != null) ?
+					evt.mozInputSource == 2 : evt.type.indexOf('pen') == 0);
+	},
+
+	/**
+	 * Function: isMultiTouchEvent
+	 * 
+	 * Returns true if the event was generated using a touch device (not a pen or mouse).
+	 */
+	isMultiTouchEvent: function(evt)
+	{
+		return (evt.type != null && evt.type.indexOf('touch') == 0 && evt.touches != null && evt.touches.length > 1);
+	},
+
+	/**
+	 * Function: isMouseEvent
+	 * 
+	 * Returns true if the event was generated using a mouse (not a pen or touch device).
+	 */
+	isMouseEvent: function(evt)
+	{
+		return (evt.pointerType != null) ? (evt.pointerType == 'mouse' || evt.pointerType ===
+			evt.MSPOINTER_TYPE_MOUSE) : ((evt.mozInputSource != null) ?
+				evt.mozInputSource == 1 : evt.type.indexOf('mouse') == 0);
+	},
+	
+	/**
+	 * Function: isLeftMouseButton
+	 * 
+	 * Returns true if the left mouse button is pressed for the given event.
+	 * To check if a button is pressed during a mouseMove you should use the
+	 * <mxGraph.isMouseDown> property. Note that this returns true in Firefox
+	 * for control+left-click on the Mac.
+	 */
+	isLeftMouseButton: function(evt)
+	{
+		// Special case for mousemove and mousedown we check the buttons
+		// if it exists because which is 0 even if no button is pressed
+		if ('buttons' in evt && (evt.type == 'mousedown' || evt.type == 'mousemove'))
+		{
+			return evt.buttons == 1;
+		}
+		else if ('which' in evt)
+		{
+	        return evt.which === 1;
+	    }
+		else
+		{
+	        return evt.button === 1;
+	    }
+	},
+	
+	/**
+	 * Function: isMiddleMouseButton
+	 * 
+	 * Returns true if the middle mouse button is pressed for the given event.
+	 * To check if a button is pressed during a mouseMove you should use the
+	 * <mxGraph.isMouseDown> property.
+	 */
+	isMiddleMouseButton: function(evt)
+	{
+		if ('which' in evt)
+		{
+	        return evt.which === 2;
+	    }
+		else
+		{
+	        return evt.button === 4;
+	    }
+	},
+	
+	/**
+	 * Function: isRightMouseButton
+	 * 
+	 * Returns true if the right mouse button was pressed. Note that this
+	 * button might not be available on some systems. For handling a popup
+	 * trigger <isPopupTrigger> should be used.
+	 */
+	isRightMouseButton: function(evt)
+	{
+		if ('which' in evt)
+		{
+	        return evt.which === 3;
+	    }
+		else
+		{
+	        return evt.button === 2;
+	    }
+	},
+
+	/**
+	 * Function: isPopupTrigger
+	 * 
+	 * Returns true if the event is a popup trigger. This implementation
+	 * returns true if the right button or the left button and control was
+	 * pressed on a Mac.
+	 */
+	isPopupTrigger: function(evt)
+	{
+		return mxEvent.isRightMouseButton(evt) || (mxClient.IS_MAC && mxEvent.isControlDown(evt) &&
+			!mxEvent.isShiftDown(evt) && !mxEvent.isMetaDown(evt) && !mxEvent.isAltDown(evt));
+	},
+
+	/**
+	 * Function: isShiftDown
+	 * 
+	 * Returns true if the shift key is pressed for the given event.
+	 */
+	isShiftDown: function(evt)
+	{
+		return (evt != null) ? evt.shiftKey : false;
+	},
+
+	/**
+	 * Function: isAltDown
+	 * 
+	 * Returns true if the alt key is pressed for the given event.
+	 */
+	isAltDown: function(evt)
+	{
+		return (evt != null) ? evt.altKey : false;
+	},
+
+	/**
+	 * Function: isControlDown
+	 * 
+	 * Returns true if the control key is pressed for the given event.
+	 */
+	isControlDown: function(evt)
+	{
+		return (evt != null) ? evt.ctrlKey : false;
+	},
+
+	/**
+	 * Function: isMetaDown
+	 * 
+	 * Returns true if the meta key is pressed for the given event.
+	 */
+	isMetaDown: function(evt)
+	{
+		return (evt != null) ? evt.metaKey : false;
+	},
+
+	/**
+	 * Function: getMainEvent
+	 * 
+	 * Returns the touch or mouse event that contains the mouse coordinates.
+	 */
+	getMainEvent: function(e)
+	{
+		if ((e.type == 'touchstart' || e.type == 'touchmove') && e.touches != null && e.touches[0] != null)
+		{
+			e = e.touches[0];
+		}
+		else if (e.type == 'touchend' && e.changedTouches != null && e.changedTouches[0] != null)
+		{
+			e = e.changedTouches[0];
+		}
+		
+		return e;
+	},
+	
+	/**
+	 * Function: getClientX
+	 * 
+	 * Returns true if the meta key is pressed for the given event.
+	 */
+	getClientX: function(e)
+	{
+		return mxEvent.getMainEvent(e).clientX;
+	},
+
+	/**
+	 * Function: getClientY
+	 * 
+	 * Returns true if the meta key is pressed for the given event.
+	 */
+	getClientY: function(e)
+	{
+		return mxEvent.getMainEvent(e).clientY;
+	},
+
+	/**
+	 * Function: consume
+	 * 
+	 * Consumes the given event.
+	 * 
+	 * Parameters:
+	 * 
+	 * evt - Native event to be consumed.
+	 * preventDefault - Optional boolean to prevent the default for the event.
+	 * Default is true.
+	 * stopPropagation - Option boolean to stop event propagation. Default is
+	 * true.
+	 */
+	consume: function(evt, preventDefault, stopPropagation)
+	{
+		preventDefault = (preventDefault != null) ? preventDefault : true;
+		stopPropagation = (stopPropagation != null) ? stopPropagation : true;
+		
+		if (preventDefault)
+		{
+			if (evt.preventDefault)
+			{
+				if (stopPropagation)
+				{
+					evt.stopPropagation();
+				}
+				
+				evt.preventDefault();
+			}
+			else if (stopPropagation)
+			{
+				evt.cancelBubble = true;
+			}
+		}
+
+		// Opera
+		evt.isConsumed = true;
+
+		// Other browsers
+		if (!evt.preventDefault)
+		{
+			evt.returnValue = false;
+		}
+	},
+	
+	//
+	// Special handles in mouse events
+	//
+	
+	/**
+	 * Variable: LABEL_HANDLE
+	 * 
+	 * Index for the label handle in an mxMouseEvent. This should be a negative
+	 * value that does not interfere with any possible handle indices. Default
+	 * is -1.
+	 */
+	LABEL_HANDLE: -1,
+	
+	/**
+	 * Variable: ROTATION_HANDLE
+	 * 
+	 * Index for the rotation handle in an mxMouseEvent. This should be a
+	 * negative value that does not interfere with any possible handle indices.
+	 * Default is -2.
+	 */
+	ROTATION_HANDLE: -2,
+	
+	/**
+	 * Variable: CUSTOM_HANDLE
+	 * 
+	 * Start index for the custom handles in an mxMouseEvent. This should be a
+	 * negative value and is the start index which is decremented for each
+	 * custom handle. Default is -100.
+	 */
+	CUSTOM_HANDLE: -100,
+	
+	/**
+	 * Variable: VIRTUAL_HANDLE
+	 * 
+	 * Start index for the virtual handles in an mxMouseEvent. This should be a
+	 * negative value and is the start index which is decremented for each
+	 * virtual handle. Default is -100000. This assumes that there are no more
+	 * than VIRTUAL_HANDLE - CUSTOM_HANDLE custom handles.
+	 * 
+	 */
+	VIRTUAL_HANDLE: -100000,
+	
+	//
+	// Event names
+	//
+	
+	/**
+	 * Variable: MOUSE_DOWN
+	 *
+	 * Specifies the event name for mouseDown.
+	 */
+	MOUSE_DOWN: 'mouseDown',
+	
+	/**
+	 * Variable: MOUSE_MOVE
+	 *
+	 * Specifies the event name for mouseMove. 
+	 */
+	MOUSE_MOVE: 'mouseMove',
+	
+	/**
+	 * Variable: MOUSE_UP
+	 *
+	 * Specifies the event name for mouseUp. 
+	 */
+	MOUSE_UP: 'mouseUp',
+
+	/**
+	 * Variable: ACTIVATE
+	 *
+	 * Specifies the event name for activate.
+	 */
+	ACTIVATE: 'activate',
+
+	/**
+	 * Variable: RESIZE_START
+	 *
+	 * Specifies the event name for resizeStart.
+	 */
+	RESIZE_START: 'resizeStart',
+
+	/**
+	 * Variable: RESIZE
+	 *
+	 * Specifies the event name for resize.
+	 */
+	RESIZE: 'resize',
+
+	/**
+	 * Variable: RESIZE_END
+	 *
+	 * Specifies the event name for resizeEnd.
+	 */
+	RESIZE_END: 'resizeEnd',
+
+	/**
+	 * Variable: MOVE_START
+	 *
+	 * Specifies the event name for moveStart.
+	 */
+	MOVE_START: 'moveStart',
+
+	/**
+	 * Variable: MOVE
+	 *
+	 * Specifies the event name for move.
+	 */
+	MOVE: 'move',
+
+	/**
+	 * Variable: MOVE_END
+	 *
+	 * Specifies the event name for moveEnd.
+	 */
+	MOVE_END: 'moveEnd',
+
+	/**
+	 * Variable: PAN_START
+	 *
+	 * Specifies the event name for panStart.
+	 */
+	PAN_START: 'panStart',
+
+	/**
+	 * Variable: PAN
+	 *
+	 * Specifies the event name for pan.
+	 */
+	PAN: 'pan',
+
+	/**
+	 * Variable: PAN_END
+	 *
+	 * Specifies the event name for panEnd.
+	 */
+	PAN_END: 'panEnd',
+
+	/**
+	 * Variable: MINIMIZE
+	 *
+	 * Specifies the event name for minimize.
+	 */
+	MINIMIZE: 'minimize',
+
+	/**
+	 * Variable: NORMALIZE
+	 *
+	 * Specifies the event name for normalize.
+	 */
+	NORMALIZE: 'normalize',
+
+	/**
+	 * Variable: MAXIMIZE
+	 *
+	 * Specifies the event name for maximize.
+	 */
+	MAXIMIZE: 'maximize',
+
+	/**
+	 * Variable: HIDE
+	 *
+	 * Specifies the event name for hide.
+	 */
+	HIDE: 'hide',
+
+	/**
+	 * Variable: SHOW
+	 *
+	 * Specifies the event name for show.
+	 */
+	SHOW: 'show',
+
+	/**
+	 * Variable: CLOSE
+	 *
+	 * Specifies the event name for close.
+	 */
+	CLOSE: 'close',
+
+	/**
+	 * Variable: DESTROY
+	 *
+	 * Specifies the event name for destroy.
+	 */
+	DESTROY: 'destroy',
+
+	/**
+	 * Variable: REFRESH
+	 *
+	 * Specifies the event name for refresh.
+	 */
+	REFRESH: 'refresh',
+
+	/**
+	 * Variable: SIZE
+	 *
+	 * Specifies the event name for size.
+	 */
+	SIZE: 'size',
+	
+	/**
+	 * Variable: SELECT
+	 *
+	 * Specifies the event name for select.
+	 */
+	SELECT: 'select',
+
+	/**
+	 * Variable: FIRED
+	 *
+	 * Specifies the event name for fired.
+	 */
+	FIRED: 'fired',
+
+	/**
+	 * Variable: FIRE_MOUSE_EVENT
+	 *
+	 * Specifies the event name for fireMouseEvent.
+	 */
+	FIRE_MOUSE_EVENT: 'fireMouseEvent',
+
+	/**
+	 * Variable: GESTURE
+	 *
+	 * Specifies the event name for gesture.
+	 */
+	GESTURE: 'gesture',
+
+	/**
+	 * Variable: TAP_AND_HOLD
+	 *
+	 * Specifies the event name for tapAndHold.
+	 */
+	TAP_AND_HOLD: 'tapAndHold',
+
+	/**
+	 * Variable: GET
+	 *
+	 * Specifies the event name for get.
+	 */
+	GET: 'get',
+
+	/**
+	 * Variable: RECEIVE
+	 *
+	 * Specifies the event name for receive.
+	 */
+	RECEIVE: 'receive',
+
+	/**
+	 * Variable: CONNECT
+	 *
+	 * Specifies the event name for connect.
+	 */
+	CONNECT: 'connect',
+
+	/**
+	 * Variable: DISCONNECT
+	 *
+	 * Specifies the event name for disconnect.
+	 */
+	DISCONNECT: 'disconnect',
+
+	/**
+	 * Variable: SUSPEND
+	 *
+	 * Specifies the event name for suspend.
+	 */
+	SUSPEND: 'suspend',
+
+	/**
+	 * Variable: RESUME
+	 *
+	 * Specifies the event name for suspend.
+	 */
+	RESUME: 'resume',
+
+	/**
+	 * Variable: MARK
+	 *
+	 * Specifies the event name for mark.
+	 */
+	MARK: 'mark',
+
+	/**
+	 * Variable: ROOT
+	 *
+	 * Specifies the event name for root.
+	 */
+	ROOT: 'root',
+
+	/**
+	 * Variable: POST
+	 *
+	 * Specifies the event name for post.
+	 */
+	POST: 'post',
+
+	/**
+	 * Variable: OPEN
+	 *
+	 * Specifies the event name for open.
+	 */
+	OPEN: 'open',
+
+	/**
+	 * Variable: SAVE
+	 *
+	 * Specifies the event name for open.
+	 */
+	SAVE: 'save',
+
+	/**
+	 * Variable: BEFORE_ADD_VERTEX
+	 *
+	 * Specifies the event name for beforeAddVertex.
+	 */
+	BEFORE_ADD_VERTEX: 'beforeAddVertex',
+
+	/**
+	 * Variable: ADD_VERTEX
+	 *
+	 * Specifies the event name for addVertex.
+	 */
+	ADD_VERTEX: 'addVertex',
+
+	/**
+	 * Variable: AFTER_ADD_VERTEX
+	 *
+	 * Specifies the event name for afterAddVertex.
+	 */
+	AFTER_ADD_VERTEX: 'afterAddVertex',
+
+	/**
+	 * Variable: DONE
+	 *
+	 * Specifies the event name for done.
+	 */
+	DONE: 'done',
+
+	/**
+	 * Variable: EXECUTE
+	 *
+	 * Specifies the event name for execute.
+	 */
+	EXECUTE: 'execute',
+
+	/**
+	 * Variable: EXECUTED
+	 *
+	 * Specifies the event name for executed.
+	 */
+	EXECUTED: 'executed',
+
+	/**
+	 * Variable: BEGIN_UPDATE
+	 *
+	 * Specifies the event name for beginUpdate.
+	 */
+	BEGIN_UPDATE: 'beginUpdate',
+
+	/**
+	 * Variable: START_EDIT
+	 *
+	 * Specifies the event name for startEdit.
+	 */
+	START_EDIT: 'startEdit',
+
+	/**
+	 * Variable: END_UPDATE
+	 *
+	 * Specifies the event name for endUpdate.
+	 */
+	END_UPDATE: 'endUpdate',
+
+	/**
+	 * Variable: END_EDIT
+	 *
+	 * Specifies the event name for endEdit.
+	 */
+	END_EDIT: 'endEdit',
+
+	/**
+	 * Variable: BEFORE_UNDO
+	 *
+	 * Specifies the event name for beforeUndo.
+	 */
+	BEFORE_UNDO: 'beforeUndo',
+
+	/**
+	 * Variable: UNDO
+	 *
+	 * Specifies the event name for undo.
+	 */
+	UNDO: 'undo',
+
+	/**
+	 * Variable: REDO
+	 *
+	 * Specifies the event name for redo.
+	 */
+	REDO: 'redo',
+
+	/**
+	 * Variable: CHANGE
+	 *
+	 * Specifies the event name for change.
+	 */
+	CHANGE: 'change',
+
+	/**
+	 * Variable: NOTIFY
+	 *
+	 * Specifies the event name for notify.
+	 */
+	NOTIFY: 'notify',
+
+	/**
+	 * Variable: LAYOUT_CELLS
+	 *
+	 * Specifies the event name for layoutCells.
+	 */
+	LAYOUT_CELLS: 'layoutCells',
+
+	/**
+	 * Variable: CLICK
+	 *
+	 * Specifies the event name for click.
+	 */
+	CLICK: 'click',
+
+	/**
+	 * Variable: SCALE
+	 *
+	 * Specifies the event name for scale.
+	 */
+	SCALE: 'scale',
+
+	/**
+	 * Variable: TRANSLATE
+	 *
+	 * Specifies the event name for translate.
+	 */
+	TRANSLATE: 'translate',
+
+	/**
+	 * Variable: SCALE_AND_TRANSLATE
+	 *
+	 * Specifies the event name for scaleAndTranslate.
+	 */
+	SCALE_AND_TRANSLATE: 'scaleAndTranslate',
+
+	/**
+	 * Variable: UP
+	 *
+	 * Specifies the event name for up.
+	 */
+	UP: 'up',
+
+	/**
+	 * Variable: DOWN
+	 *
+	 * Specifies the event name for down.
+	 */
+	DOWN: 'down',
+
+	/**
+	 * Variable: ADD
+	 *
+	 * Specifies the event name for add.
+	 */
+	ADD: 'add',
+
+	/**
+	 * Variable: REMOVE
+	 *
+	 * Specifies the event name for remove.
+	 */
+	REMOVE: 'remove',
+	
+	/**
+	 * Variable: CLEAR
+	 *
+	 * Specifies the event name for clear.
+	 */
+	CLEAR: 'clear',
+
+	/**
+	 * Variable: ADD_CELLS
+	 *
+	 * Specifies the event name for addCells.
+	 */
+	ADD_CELLS: 'addCells',
+
+	/**
+	 * Variable: CELLS_ADDED
+	 *
+	 * Specifies the event name for cellsAdded.
+	 */
+	CELLS_ADDED: 'cellsAdded',
+
+	/**
+	 * Variable: MOVE_CELLS
+	 *
+	 * Specifies the event name for moveCells.
+	 */
+	MOVE_CELLS: 'moveCells',
+
+	/**
+	 * Variable: CELLS_MOVED
+	 *
+	 * Specifies the event name for cellsMoved.
+	 */
+	CELLS_MOVED: 'cellsMoved',
+
+	/**
+	 * Variable: RESIZE_CELLS
+	 *
+	 * Specifies the event name for resizeCells.
+	 */
+	RESIZE_CELLS: 'resizeCells',
+
+	/**
+	 * Variable: CELLS_RESIZED
+	 *
+	 * Specifies the event name for cellsResized.
+	 */
+	CELLS_RESIZED: 'cellsResized',
+
+	/**
+	 * Variable: TOGGLE_CELLS
+	 *
+	 * Specifies the event name for toggleCells.
+	 */
+	TOGGLE_CELLS: 'toggleCells',
+
+	/**
+	 * Variable: CELLS_TOGGLED
+	 *
+	 * Specifies the event name for cellsToggled.
+	 */
+	CELLS_TOGGLED: 'cellsToggled',
+
+	/**
+	 * Variable: ORDER_CELLS
+	 *
+	 * Specifies the event name for orderCells.
+	 */
+	ORDER_CELLS: 'orderCells',
+
+	/**
+	 * Variable: CELLS_ORDERED
+	 *
+	 * Specifies the event name for cellsOrdered.
+	 */
+	CELLS_ORDERED: 'cellsOrdered',
+
+	/**
+	 * Variable: REMOVE_CELLS
+	 *
+	 * Specifies the event name for removeCells.
+	 */
+	REMOVE_CELLS: 'removeCells',
+
+	/**
+	 * Variable: CELLS_REMOVED
+	 *
+	 * Specifies the event name for cellsRemoved.
+	 */
+	CELLS_REMOVED: 'cellsRemoved',
+
+	/**
+	 * Variable: GROUP_CELLS
+	 *
+	 * Specifies the event name for groupCells.
+	 */
+	GROUP_CELLS: 'groupCells',
+
+	/**
+	 * Variable: UNGROUP_CELLS
+	 *
+	 * Specifies the event name for ungroupCells.
+	 */
+	UNGROUP_CELLS: 'ungroupCells',
+
+	/**
+	 * Variable: REMOVE_CELLS_FROM_PARENT
+	 *
+	 * Specifies the event name for removeCellsFromParent.
+	 */
+	REMOVE_CELLS_FROM_PARENT: 'removeCellsFromParent',
+
+	/**
+	 * Variable: FOLD_CELLS
+	 *
+	 * Specifies the event name for foldCells.
+	 */
+	FOLD_CELLS: 'foldCells',
+
+	/**
+	 * Variable: CELLS_FOLDED
+	 *
+	 * Specifies the event name for cellsFolded.
+	 */
+	CELLS_FOLDED: 'cellsFolded',
+
+	/**
+	 * Variable: ALIGN_CELLS
+	 *
+	 * Specifies the event name for alignCells.
+	 */
+	ALIGN_CELLS: 'alignCells',
+
+	/**
+	 * Variable: LABEL_CHANGED
+	 *
+	 * Specifies the event name for labelChanged.
+	 */
+	LABEL_CHANGED: 'labelChanged',
+
+	/**
+	 * Variable: CONNECT_CELL
+	 *
+	 * Specifies the event name for connectCell.
+	 */
+	CONNECT_CELL: 'connectCell',
+
+	/**
+	 * Variable: CELL_CONNECTED
+	 *
+	 * Specifies the event name for cellConnected.
+	 */
+	CELL_CONNECTED: 'cellConnected',
+
+	/**
+	 * Variable: SPLIT_EDGE
+	 *
+	 * Specifies the event name for splitEdge.
+	 */
+	SPLIT_EDGE: 'splitEdge',
+
+	/**
+	 * Variable: FLIP_EDGE
+	 *
+	 * Specifies the event name for flipEdge.
+	 */
+	FLIP_EDGE: 'flipEdge',
+
+	/**
+	 * Variable: START_EDITING
+	 *
+	 * Specifies the event name for startEditing.
+	 */
+	START_EDITING: 'startEditing',
+
+	/**
+	 * Variable: EDITING_STARTED
+	 *
+	 * Specifies the event name for editingStarted.
+	 */
+	EDITING_STARTED: 'editingStarted',
+
+	/**
+	 * Variable: EDITING_STOPPED
+	 *
+	 * Specifies the event name for editingStopped.
+	 */
+	EDITING_STOPPED: 'editingStopped',
+
+	/**
+	 * Variable: ADD_OVERLAY
+	 *
+	 * Specifies the event name for addOverlay.
+	 */
+	ADD_OVERLAY: 'addOverlay',
+
+	/**
+	 * Variable: REMOVE_OVERLAY
+	 *
+	 * Specifies the event name for removeOverlay.
+	 */
+	REMOVE_OVERLAY: 'removeOverlay',
+
+	/**
+	 * Variable: UPDATE_CELL_SIZE
+	 *
+	 * Specifies the event name for updateCellSize.
+	 */
+	UPDATE_CELL_SIZE: 'updateCellSize',
+
+	/**
+	 * Variable: ESCAPE
+	 *
+	 * Specifies the event name for escape.
+	 */
+	ESCAPE: 'escape',
+
+	/**
+	 * Variable: DOUBLE_CLICK
+	 *
+	 * Specifies the event name for doubleClick.
+	 */
+	DOUBLE_CLICK: 'doubleClick',
+
+	/**
+	 * Variable: START
+	 *
+	 * Specifies the event name for start.
+	 */
+	START: 'start',
+
+	/**
+	 * Variable: RESET
+	 *
+	 * Specifies the event name for reset.
+	 */
+	RESET: 'reset'
+
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/util/mxEventObject.js b/airavata-kubernetes/web-console/src/assets/js/util/mxEventObject.js
new file mode 100644
index 0000000..fd7cb6c
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/util/mxEventObject.js
@@ -0,0 +1,111 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxEventObject
+ * 
+ * The mxEventObject is a wrapper for all properties of a single event.
+ * Additionally, it also offers functions to consume the event and check if it
+ * was consumed as follows:
+ * 
+ * (code)
+ * evt.consume();
+ * INV: evt.isConsumed() == true
+ * (end)
+ * 
+ * Constructor: mxEventObject
+ *
+ * Constructs a new event object with the specified name. An optional
+ * sequence of key, value pairs can be appended to define properties.
+ * 
+ * Example:
+ *
+ * (code)
+ * new mxEventObject("eventName", key1, val1, .., keyN, valN)
+ * (end)
+ */
+function mxEventObject(name)
+{
+	this.name = name;
+	this.properties = [];
+	
+	for (var i = 1; i < arguments.length; i += 2)
+	{
+		if (arguments[i + 1] != null)
+		{
+			this.properties[arguments[i]] = arguments[i + 1];
+		}
+	}
+};
+
+/**
+ * Variable: name
+ *
+ * Holds the name.
+ */
+mxEventObject.prototype.name = null;
+
+/**
+ * Variable: properties
+ *
+ * Holds the properties as an associative array.
+ */
+mxEventObject.prototype.properties = null;
+
+/**
+ * Variable: consumed
+ *
+ * Holds the consumed state. Default is false.
+ */
+mxEventObject.prototype.consumed = false;
+
+/**
+ * Function: getName
+ * 
+ * Returns <name>.
+ */
+mxEventObject.prototype.getName = function()
+{
+	return this.name;
+};
+
+/**
+ * Function: getProperties
+ * 
+ * Returns <properties>.
+ */
+mxEventObject.prototype.getProperties = function()
+{
+	return this.properties;
+};
+
+/**
+ * Function: getProperty
+ * 
+ * Returns the property for the given key.
+ */
+mxEventObject.prototype.getProperty = function(key)
+{
+	return this.properties[key];
+};
+
+/**
+ * Function: isConsumed
+ *
+ * Returns true if the event has been consumed.
+ */
+mxEventObject.prototype.isConsumed = function()
+{
+	return this.consumed;
+};
+
+/**
+ * Function: consume
+ *
+ * Consumes the event.
+ */
+mxEventObject.prototype.consume = function()
+{
+	this.consumed = true;
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/util/mxEventSource.js b/airavata-kubernetes/web-console/src/assets/js/util/mxEventSource.js
new file mode 100644
index 0000000..7646ff8
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/util/mxEventSource.js
@@ -0,0 +1,189 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxEventSource
+ *
+ * Base class for objects that dispatch named events. To create a subclass that
+ * inherits from mxEventSource, the following code is used.
+ *
+ * (code)
+ * function MyClass() { };
+ *
+ * MyClass.prototype = new mxEventSource();
+ * MyClass.prototype.constructor = MyClass;
+ * (end)
+ *
+ * Known Subclasses:
+ *
+ * <mxGraphModel>, <mxGraph>, <mxGraphView>, <mxEditor>, <mxCellOverlay>,
+ * <mxToolbar>, <mxWindow>
+ * 
+ * Constructor: mxEventSource
+ *
+ * Constructs a new event source.
+ */
+function mxEventSource(eventSource)
+{
+	this.setEventSource(eventSource);
+};
+
+/**
+ * Variable: eventListeners
+ *
+ * Holds the event names and associated listeners in an array. The array
+ * contains the event name followed by the respective listener for each
+ * registered listener.
+ */
+mxEventSource.prototype.eventListeners = null;
+
+/**
+ * Variable: eventsEnabled
+ *
+ * Specifies if events can be fired. Default is true.
+ */
+mxEventSource.prototype.eventsEnabled = true;
+
+/**
+ * Variable: eventSource
+ *
+ * Optional source for events. Default is null.
+ */
+mxEventSource.prototype.eventSource = null;
+
+/**
+ * Function: isEventsEnabled
+ * 
+ * Returns <eventsEnabled>.
+ */
+mxEventSource.prototype.isEventsEnabled = function()
+{
+	return this.eventsEnabled;
+};
+
+/**
+ * Function: setEventsEnabled
+ * 
+ * Sets <eventsEnabled>.
+ */
+mxEventSource.prototype.setEventsEnabled = function(value)
+{
+	this.eventsEnabled = value;
+};
+
+/**
+ * Function: getEventSource
+ * 
+ * Returns <eventSource>.
+ */
+mxEventSource.prototype.getEventSource = function()
+{
+	return this.eventSource;
+};
+
+/**
+ * Function: setEventSource
+ * 
+ * Sets <eventSource>.
+ */
+mxEventSource.prototype.setEventSource = function(value)
+{
+	this.eventSource = value;
+};
+
+/**
+ * Function: addListener
+ *
+ * Binds the specified function to the given event name. If no event name
+ * is given, then the listener is registered for all events.
+ * 
+ * The parameters of the listener are the sender and an <mxEventObject>.
+ */
+mxEventSource.prototype.addListener = function(name, funct)
+{
+	if (this.eventListeners == null)
+	{
+		this.eventListeners = [];
+	}
+	
+	this.eventListeners.push(name);
+	this.eventListeners.push(funct);
+};
+
+/**
+ * Function: removeListener
+ *
+ * Removes all occurrences of the given listener from <eventListeners>.
+ */
+mxEventSource.prototype.removeListener = function(funct)
+{
+	if (this.eventListeners != null)
+	{
+		var i = 0;
+		
+		while (i < this.eventListeners.length)
+		{
+			if (this.eventListeners[i+1] == funct)
+			{
+				this.eventListeners.splice(i, 2);
+			}
+			else
+			{
+				i += 2;
+			}
+		}
+	}
+};
+
+/**
+ * Function: fireEvent
+ *
+ * Dispatches the given event to the listeners which are registered for
+ * the event. The sender argument is optional. The current execution scope
+ * ("this") is used for the listener invocation (see <mxUtils.bind>).
+ *
+ * Example:
+ *
+ * (code)
+ * fireEvent(new mxEventObject("eventName", key1, val1, .., keyN, valN))
+ * (end)
+ * 
+ * Parameters:
+ *
+ * evt - <mxEventObject> that represents the event.
+ * sender - Optional sender to be passed to the listener. Default value is
+ * the return value of <getEventSource>.
+ */
+mxEventSource.prototype.fireEvent = function(evt, sender)
+{
+	if (this.eventListeners != null && this.isEventsEnabled())
+	{
+		if (evt == null)
+		{
+			evt = new mxEventObject();
+		}
+		
+		if (sender == null)
+		{
+			sender = this.getEventSource();
+		}
+
+		if (sender == null)
+		{
+			sender = this;
+		}
+
+		var args = [sender, evt];
+		
+		for (var i = 0; i < this.eventListeners.length; i += 2)
+		{
+			var listen = this.eventListeners[i];
+			
+			if (listen == null || listen == evt.getName())
+			{
+				this.eventListeners[i+1].apply(this, args);
+			}
+		}
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/util/mxForm.js b/airavata-kubernetes/web-console/src/assets/js/util/mxForm.js
new file mode 100644
index 0000000..afb0a6d
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/util/mxForm.js
@@ -0,0 +1,202 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxForm
+ * 
+ * A simple class for creating HTML forms.
+ * 
+ * Constructor: mxForm
+ * 
+ * Creates a HTML table using the specified classname.
+ */
+function mxForm(className)
+{
+	this.table = document.createElement('table');
+	this.table.className = className;
+	this.body = document.createElement('tbody');
+	
+	this.table.appendChild(this.body);
+};
+
+/**
+ * Variable: table
+ * 
+ * Holds the DOM node that represents the table.
+ */
+mxForm.prototype.table = null;
+
+/**
+ * Variable: body
+ * 
+ * Holds the DOM node that represents the tbody (table body). New rows
+ * can be added to this object using DOM API.
+ */
+mxForm.prototype.body = false;
+
+/**
+ * Function: getTable
+ * 
+ * Returns the table that contains this form.
+ */
+mxForm.prototype.getTable = function()
+{
+	return this.table;
+};
+
+/**
+ * Function: addButtons
+ * 
+ * Helper method to add an OK and Cancel button using the respective
+ * functions.
+ */
+mxForm.prototype.addButtons = function(okFunct, cancelFunct)
+{
+	var tr = document.createElement('tr');
+	var td = document.createElement('td');
+	tr.appendChild(td);
+	td = document.createElement('td');
+
+	// Adds the ok button
+	var button = document.createElement('button');
+	mxUtils.write(button, mxResources.get('ok') || 'OK');
+	td.appendChild(button);
+
+	mxEvent.addListener(button, 'click', function()
+	{
+		okFunct();
+	});
+	
+	// Adds the cancel button
+	button = document.createElement('button');
+	mxUtils.write(button, mxResources.get('cancel') || 'Cancel');
+	td.appendChild(button);
+	
+	mxEvent.addListener(button, 'click', function()
+	{
+		cancelFunct();
+	});
+	
+	tr.appendChild(td);
+	this.body.appendChild(tr);
+};
+
+/**
+ * Function: addText
+ * 
+ * Adds an input for the given name, type and value and returns it.
+ */
+mxForm.prototype.addText = function(name, value, type)
+{
+	var input = document.createElement('input');
+	
+	input.setAttribute('type', type || 'text');
+	input.value = value;
+	
+	return this.addField(name, input);
+};
+
+/**
+ * Function: addCheckbox
+ * 
+ * Adds a checkbox for the given name and value and returns the textfield.
+ */
+mxForm.prototype.addCheckbox = function(name, value)
+{
+	var input = document.createElement('input');
+	
+	input.setAttribute('type', 'checkbox');
+	this.addField(name, input);
+
+	// IE can only change the checked value if the input is inside the DOM
+	if (value)
+	{
+		input.checked = true;
+	}
+
+	return input;
+};
+
+/**
+ * Function: addTextarea
+ * 
+ * Adds a textarea for the given name and value and returns the textarea.
+ */
+mxForm.prototype.addTextarea = function(name, value, rows)
+{
+	var input = document.createElement('textarea');
+	
+	if (mxClient.IS_NS)
+	{
+		rows--;
+	}
+	
+	input.setAttribute('rows', rows || 2);
+	input.value = value;
+	
+	return this.addField(name, input);
+};
+
+/**
+ * Function: addCombo
+ * 
+ * Adds a combo for the given name and returns the combo.
+ */
+mxForm.prototype.addCombo = function(name, isMultiSelect, size)
+{
+	var select = document.createElement('select');
+	
+	if (size != null)
+	{
+		select.setAttribute('size', size);
+	}
+	
+	if (isMultiSelect)
+	{
+		select.setAttribute('multiple', 'true');
+	}
+	
+	return this.addField(name, select);
+};
+
+/**
+ * Function: addOption
+ * 
+ * Adds an option for the given label to the specified combo.
+ */
+mxForm.prototype.addOption = function(combo, label, value, isSelected)
+{
+	var option = document.createElement('option');
+	
+	mxUtils.writeln(option, label);
+	option.setAttribute('value', value);
+	
+	if (isSelected)
+	{
+		option.setAttribute('selected', isSelected);
+	}
+	
+	combo.appendChild(option);
+};
+
+/**
+ * Function: addField
+ * 
+ * Adds a new row with the name and the input field in two columns and
+ * returns the given input.
+ */
+mxForm.prototype.addField = function(name, input)
+{
+	var tr = document.createElement('tr');
+	var td = document.createElement('td');
+	mxUtils.write(td, name);
+	tr.appendChild(td);
+	
+	td = document.createElement('td');
+	td.appendChild(input);
+	tr.appendChild(td);
+	this.body.appendChild(tr);
+	
+	return input;
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/util/mxGuide.js b/airavata-kubernetes/web-console/src/assets/js/util/mxGuide.js
new file mode 100644
index 0000000..773affc
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/util/mxGuide.js
@@ -0,0 +1,401 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxGuide
+ *
+ * Implements the alignment of selection cells to other cells in the graph.
+ * 
+ * Constructor: mxGuide
+ * 
+ * Constructs a new guide object.
+ */
+function mxGuide(graph, states)
+{
+	this.graph = graph;
+	this.setStates(states);
+};
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph> instance.
+ */
+mxGuide.prototype.graph = null;
+
+/**
+ * Variable: states
+ * 
+ * Contains the <mxCellStates> that are used for alignment.
+ */
+mxGuide.prototype.states = null;
+
+/**
+ * Variable: horizontal
+ *
+ * Specifies if horizontal guides are enabled. Default is true.
+ */
+mxGuide.prototype.horizontal = true;
+
+/**
+ * Variable: vertical
+ *
+ * Specifies if vertical guides are enabled. Default is true.
+ */
+mxGuide.prototype.vertical = true;
+
+/**
+ * Variable: vertical
+ *
+ * Holds the <mxShape> for the horizontal guide.
+ */
+mxGuide.prototype.guideX = null;
+
+/**
+ * Variable: vertical
+ *
+ * Holds the <mxShape> for the vertical guide.
+ */
+mxGuide.prototype.guideY = null;
+
+/**
+ * Function: setStates
+ * 
+ * Sets the <mxCellStates> that should be used for alignment.
+ */
+mxGuide.prototype.setStates = function(states)
+{
+	this.states = states;
+};
+
+/**
+ * Function: isEnabledForEvent
+ * 
+ * Returns true if the guide should be enabled for the given native event. This
+ * implementation always returns true.
+ */
+mxGuide.prototype.isEnabledForEvent = function(evt)
+{
+	return true;
+};
+
+/**
+ * Function: getGuideTolerance
+ * 
+ * Returns the tolerance for the guides. Default value is gridSize / 2.
+ */
+mxGuide.prototype.getGuideTolerance = function()
+{
+	return this.graph.gridSize / 2;
+};
+
+/**
+ * Function: createGuideShape
+ * 
+ * Returns the mxShape to be used for painting the respective guide. This
+ * implementation returns a new, dashed and crisp <mxPolyline> using
+ * <mxConstants.GUIDE_COLOR> and <mxConstants.GUIDE_STROKEWIDTH> as the format.
+ * 
+ * Parameters:
+ * 
+ * horizontal - Boolean that specifies which guide should be created.
+ */
+mxGuide.prototype.createGuideShape = function(horizontal)
+{
+	var guide = new mxPolyline([], mxConstants.GUIDE_COLOR, mxConstants.GUIDE_STROKEWIDTH);
+	guide.isDashed = true;
+	
+	return guide;
+};
+
+/**
+ * Function: move
+ * 
+ * Moves the <bounds> by the given <mxPoint> and returnt the snapped point.
+ */
+mxGuide.prototype.move = function(bounds, delta, gridEnabled)
+{
+	if (this.states != null && (this.horizontal || this.vertical) && bounds != null && delta != null)
+	{
+		var trx = this.graph.getView().translate;
+		var scale = this.graph.getView().scale;
+		var dx = delta.x;
+		var dy = delta.y;
+		
+		var overrideX = false;
+		var stateX = null;
+		var valueX = null;
+		var overrideY = false;
+		var stateY = null;
+		var valueY = null;
+		
+		var tt = this.getGuideTolerance();
+		var ttX = tt;
+		var ttY = tt;
+		
+		var b = bounds.clone();
+		b.x += delta.x;
+		b.y += delta.y;
+		
+		var left = b.x;
+		var right = b.x + b.width;
+		var center = b.getCenterX();
+		var top = b.y;
+		var bottom = b.y + b.height;
+		var middle = b.getCenterY();
+	
+		// Snaps the left, center and right to the given x-coordinate
+		function snapX(x, state)
+		{
+			x += this.graph.panDx;
+			var override = false;
+			
+			if (Math.abs(x - center) < ttX)
+			{
+				dx = x - bounds.getCenterX();
+				ttX = Math.abs(x - center);
+				override = true;
+			}
+			else if (Math.abs(x - left) < ttX)
+			{
+				dx = x - bounds.x;
+				ttX = Math.abs(x - left);
+				override = true;
+			}
+			else if (Math.abs(x - right) < ttX)
+			{
+				dx = x - bounds.x - bounds.width;
+				ttX = Math.abs(x - right);
+				override = true;
+			}
+			
+			if (override)
+			{
+				stateX = state;
+				valueX = Math.round(x - this.graph.panDx);
+				
+				if (this.guideX == null)
+				{
+					this.guideX = this.createGuideShape(true);
+					
+					// Makes sure to use either VML or SVG shapes in order to implement
+					// event-transparency on the background area of the rectangle since
+					// HTML shapes do not let mouseevents through even when transparent
+					this.guideX.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
+						mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+					this.guideX.pointerEvents = false;
+					this.guideX.init(this.graph.getView().getOverlayPane());
+				}
+			}
+			
+			overrideX = overrideX || override;
+		};
+		
+		// Snaps the top, middle or bottom to the given y-coordinate
+		function snapY(y)
+		{
+			y += this.graph.panDy;
+			var override = false;
+			
+			if (Math.abs(y - middle) < ttY)
+			{
+				dy = y - bounds.getCenterY();
+				ttY = Math.abs(y -  middle);
+				override = true;
+			}
+			else if (Math.abs(y - top) < ttY)
+			{
+				dy = y - bounds.y;
+				ttY = Math.abs(y - top);
+				override = true;
+			}
+			else if (Math.abs(y - bottom) < ttY)
+			{
+				dy = y - bounds.y - bounds.height;
+				ttY = Math.abs(y - bottom);
+				override = true;
+			}
+			
+			if (override)
+			{
+				stateY = state;
+				valueY = Math.round(y - this.graph.panDy);
+				
+				if (this.guideY == null)
+				{
+					this.guideY = this.createGuideShape(false);
+					
+					// Makes sure to use either VML or SVG shapes in order to implement
+					// event-transparency on the background area of the rectangle since
+					// HTML shapes do not let mouseevents through even when transparent
+					this.guideY.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
+						mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+					this.guideY.pointerEvents = false;
+					this.guideY.init(this.graph.getView().getOverlayPane());
+				}
+			}
+			
+			overrideY = overrideY || override;
+		};
+		
+		for (var i = 0; i < this.states.length; i++)
+		{
+			var state =  this.states[i];
+			
+			if (state != null)
+			{
+				// Align x
+				if (this.horizontal)
+				{
+					snapX.call(this, state.getCenterX(), state);
+					snapX.call(this, state.x, state);
+					snapX.call(this, state.x + state.width, state);
+				}
+	
+				// Align y
+				if (this.vertical)
+				{
+					snapY.call(this, state.getCenterY(), state);
+					snapY.call(this, state.y, state);
+					snapY.call(this, state.y + state.height, state);
+				}
+			}
+		}
+
+		// Moves cells that are off-grid back to the grid on move
+		if (gridEnabled)
+		{
+			if (!overrideX)
+			{
+				var tx = bounds.x - (this.graph.snap(bounds.x /
+					scale - trx.x) + trx.x) * scale;
+				dx = this.graph.snap(dx / scale) * scale - tx;
+			}
+			
+			if (!overrideY)
+			{
+				var ty = bounds.y - (this.graph.snap(bounds.y /
+					scale - trx.y) + trx.y) * scale;
+				dy = this.graph.snap(dy / scale) * scale - ty;
+			}
+		}
+		
+		// Redraws the guides
+		var c = this.graph.container;
+		
+		if (!overrideX && this.guideX != null)
+		{
+			this.guideX.node.style.visibility = 'hidden';
+		}
+		else if (this.guideX != null)
+		{
+			if (stateX != null && bounds != null)
+			{
+				minY = Math.min(bounds.y + dy - this.graph.panDy, stateX.y);
+				maxY = Math.max(bounds.y + bounds.height + dy - this.graph.panDy, stateX.y + stateX.height);
+			}
+			
+			if (minY != null && maxY != null)
+			{
+				this.guideX.points = [new mxPoint(valueX, minY), new mxPoint(valueX, maxY)];
+			}
+			else
+			{
+				this.guideX.points = [new mxPoint(valueX, -this.graph.panDy), new mxPoint(valueX, c.scrollHeight - 3 - this.graph.panDy)];
+			}
+			
+			this.guideX.stroke = this.getGuideColor(stateX, true);
+			this.guideX.node.style.visibility = 'visible';
+			this.guideX.redraw();
+		}
+		
+		if (!overrideY && this.guideY != null)
+		{
+			this.guideY.node.style.visibility = 'hidden';
+		}
+		else if (this.guideY != null)
+		{
+			if (stateY != null && bounds != null)
+			{
+				minX = Math.min(bounds.x + dx - this.graph.panDx, stateY.x);
+				maxX = Math.max(bounds.x + bounds.width + dx - this.graph.panDx, stateY.x + stateY.width);
+			}
+			
+			if (minX != null && maxX != null)
+			{
+				this.guideY.points = [new mxPoint(minX, valueY), new mxPoint(maxX, valueY)];
+			}
+			else
+			{
+				this.guideY.points = [new mxPoint(-this.graph.panDx, valueY), new mxPoint(c.scrollWidth - 3 - this.graph.panDx, valueY)];
+			}
+			
+			this.guideY.stroke = this.getGuideColor(stateY, false);
+			this.guideY.node.style.visibility = 'visible';
+			this.guideY.redraw();
+		}
+		
+		delta = new mxPoint(dx, dy);
+	}
+	
+	return delta;
+};
+
+/**
+ * Function: hide
+ * 
+ * Hides all current guides.
+ */
+mxGuide.prototype.getGuideColor = function(state, horizontal)
+{
+	return mxConstants.GUIDE_COLOR;
+};
+
+/**
+ * Function: hide
+ * 
+ * Hides all current guides.
+ */
+mxGuide.prototype.hide = function()
+{
+	this.setVisible(false);
+};
+
+/**
+ * Function: setVisible
+ * 
+ * Shows or hides the current guides.
+ */
+mxGuide.prototype.setVisible = function(visible)
+{
+	if (this.guideX != null)
+	{
+		this.guideX.node.style.visibility = (visible) ? 'visible' : 'hidden';
+	}
+	
+	if (this.guideY != null)
+	{
+		this.guideY.node.style.visibility = (visible) ? 'visible' : 'hidden';
+	}
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys all resources that this object uses.
+ */
+mxGuide.prototype.destroy = function()
+{
+	if (this.guideX != null)
+	{
+		this.guideX.destroy();
+		this.guideX = null;
+	}
+	
+	if (this.guideY != null)
+	{
+		this.guideY.destroy();
+		this.guideY = null;
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/util/mxImage.js b/airavata-kubernetes/web-console/src/assets/js/util/mxImage.js
new file mode 100644
index 0000000..83bfc03
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/util/mxImage.js
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxImage
+ *
+ * Encapsulates the URL, width and height of an image.
+ * 
+ * Constructor: mxImage
+ * 
+ * Constructs a new image.
+ */
+function mxImage(src, width, height)
+{
+	this.src = src;
+	this.width = width;
+	this.height = height;
+};
+
+/**
+ * Variable: src
+ *
+ * String that specifies the URL of the image.
+ */
+mxImage.prototype.src = null;
+
+/**
+ * Variable: width
+ *
+ * Integer that specifies the width of the image.
+ */
+mxImage.prototype.width = null;
+
+/**
+ * Variable: height
+ *
+ * Integer that specifies the height of the image.
+ */
+mxImage.prototype.height = null;
diff --git a/airavata-kubernetes/web-console/src/assets/js/util/mxImageBundle.js b/airavata-kubernetes/web-console/src/assets/js/util/mxImageBundle.js
new file mode 100644
index 0000000..c9bb3a5
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/util/mxImageBundle.js
@@ -0,0 +1,103 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxImageBundle
+ *
+ * Maps from keys to base64 encoded images or file locations. All values must
+ * be URLs or use the format data:image/format followed by a comma and the base64
+ * encoded image data, eg. "data:image/gif,XYZ", where XYZ is the base64 encoded
+ * image data.
+ * 
+ * To add a new image bundle to an existing graph, the following code is used:
+ * 
+ * (code)
+ * var bundle = new mxImageBundle(alt);
+ * bundle.putImage('myImage', 'data:image/gif,R0lGODlhEAAQAMIGAAAAAICAAICAgP' +
+ *   '//AOzp2O3r2////////yH+FUNyZWF0ZWQgd2l0aCBUaGUgR0lNUAAh+QQBCgAHACwAAAAA' +
+ *   'EAAQAAADTXi63AowynnAMDfjPUDlnAAJhmeBFxAEloliKltWmiYCQvfVr6lBPB1ggxN1hi' +
+ *   'laSSASFQpIV5HJBDyHpqK2ejVRm2AAgZCdmCGO9CIBADs=', fallback);
+ * bundle.putImage('mySvgImage', 'data:image/svg+xml,' + encodeURIComponent(
+ *   '<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%">' +
+ *   '<linearGradient id="gradient"><stop offset="10%" stop-color="#F00"/>' +
+ *   '<stop offset="90%" stop-color="#fcc"/></linearGradient>' +
+ *   '<rect fill="url(#gradient)" width="100%" height="100%"/></svg>'), fallback);
+ * graph.addImageBundle(bundle);
+ * (end);
+ * 
+ * Alt is an optional boolean (default is false) that specifies if the value
+ * or the fallback should be returned in <getImage>.
+ * 
+ * The image can then be referenced in any cell style using image=myImage.
+ * If you are using mxOutline, you should use the same image bundles in the
+ * graph that renders the outline.
+ * 
+ * The keys for images are resolved in <mxGraph.postProcessCellStyle> and
+ * turned into a data URI if the returned value has a short data URI format
+ * as specified above.
+ * 
+ * A typical value for the fallback is a MTHML link as defined in RFC 2557.
+ * Note that this format requires a file to be dynamically created on the
+ * server-side, or the page that contains the graph to be modified to contain
+ * the resources, this can be done by adding a comment that contains the
+ * resource in the HEAD section of the page after the title tag.
+ * 
+ * This type of fallback mechanism should be used in IE6 and IE7. IE8 does
+ * support data URIs, but the maximum size is limited to 32 KB, which means
+ * all data URIs should be limited to 32 KB.
+ */
+function mxImageBundle(alt)
+{
+	this.images = [];
+	this.alt = (alt != null) ? alt : false;
+};
+
+/**
+ * Variable: images
+ * 
+ * Maps from keys to images.
+ */
+mxImageBundle.prototype.images = null;
+
+/**
+ * Variable: alt
+ * 
+ * Specifies if the fallback representation should be returned.
+ */
+mxImageBundle.prototype.images = null;
+
+/**
+ * Function: putImage
+ * 
+ * Adds the specified entry to the map. The entry is an object with a value and
+ * fallback property as specified in the arguments.
+ */
+mxImageBundle.prototype.putImage = function(key, value, fallback)
+{
+	this.images[key] = {value: value, fallback: fallback};
+};
+
+/**
+ * Function: getImage
+ * 
+ * Returns the value for the given key. This returns the value
+ * or fallback, depending on <alt>. The fallback is returned if
+ * <alt> is true, the value is returned otherwise.
+ */
+mxImageBundle.prototype.getImage = function(key)
+{
+	var result = null;
+	
+	if (key != null)
+	{
+		var img = this.images[key];
+		
+		if (img != null)
+		{
+			result = (this.alt) ? img.fallback : img.value;
+		}
+	}
+	
+	return result;
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/util/mxImageExport.js b/airavata-kubernetes/web-console/src/assets/js/util/mxImageExport.js
new file mode 100644
index 0000000..368f8c0
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/util/mxImageExport.js
@@ -0,0 +1,175 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxImageExport
+ * 
+ * Creates a new image export instance to be used with an export canvas. Here
+ * is an example that uses this class to create an image via a backend using
+ * <mxXmlExportCanvas>.
+ * 
+ * (code)
+ * var xmlDoc = mxUtils.createXmlDocument();
+ * var root = xmlDoc.createElement('output');
+ * xmlDoc.appendChild(root);
+ * 
+ * var xmlCanvas = new mxXmlCanvas2D(root);
+ * var imgExport = new mxImageExport();
+ * imgExport.drawState(graph.getView().getState(graph.model.root), xmlCanvas);
+ * 
+ * var bounds = graph.getGraphBounds();
+ * var w = Math.ceil(bounds.x + bounds.width);
+ * var h = Math.ceil(bounds.y + bounds.height);
+ * 
+ * var xml = mxUtils.getXml(root);
+ * new mxXmlRequest('export', 'format=png&w=' + w +
+ * 		'&h=' + h + '&bg=#F9F7ED&xml=' + encodeURIComponent(xml))
+ * 		.simulate(document, '_blank');
+ * (end)
+ * 
+ * Constructor: mxImageExport
+ * 
+ * Constructs a new image export.
+ */
+function mxImageExport() { };
+
+/**
+ * Variable: includeOverlays
+ * 
+ * Specifies if overlays should be included in the export. Default is false.
+ */
+mxImageExport.prototype.includeOverlays = false;
+
+/**
+ * Function: drawState
+ * 
+ * Draws the given state and all its descendants to the given canvas.
+ */
+mxImageExport.prototype.drawState = function(state, canvas)
+{
+	if (state != null)
+	{
+		this.visitStatesRecursive(state, canvas, mxUtils.bind(this, function()
+		{
+			this.drawCellState.apply(this, arguments);
+		}));
+				
+		// Paints the overlays
+		if (this.includeOverlays)
+		{
+			this.visitStatesRecursive(state, canvas, mxUtils.bind(this, function()
+			{
+				this.drawOverlays.apply(this, arguments);
+			}));
+		}
+	}
+};
+
+/**
+ * Function: drawState
+ * 
+ * Draws the given state and all its descendants to the given canvas.
+ */
+mxImageExport.prototype.visitStatesRecursive = function(state, canvas, visitor)
+{
+	if (state != null)
+	{
+		visitor(state, canvas);
+		
+		var graph = state.view.graph;
+		var childCount = graph.model.getChildCount(state.cell);
+		
+		for (var i = 0; i < childCount; i++)
+		{
+			var childState = graph.view.getState(graph.model.getChildAt(state.cell, i));
+			this.visitStatesRecursive(childState, canvas, visitor);
+		}
+	}
+};
+
+/**
+ * Function: getLinkForCellState
+ * 
+ * Returns the link for the given cell state and canvas. This returns null.
+ */
+mxImageExport.prototype.getLinkForCellState = function(state, canvas)
+{
+	return null;
+};
+
+/**
+ * Function: drawCellState
+ * 
+ * Draws the given state to the given canvas.
+ */
+mxImageExport.prototype.drawCellState = function(state, canvas)
+{
+	// Experimental feature
+	var link = this.getLinkForCellState(state, canvas);
+	
+	if (link != null)
+	{
+		canvas.setLink(link);
+	}
+	
+	// Paints the shape and text
+	this.drawShape(state, canvas);
+	this.drawText(state, canvas);
+
+	if (link != null)
+	{
+		canvas.setLink(null);
+	}
+};
+
+/**
+ * Function: drawShape
+ * 
+ * Draws the shape of the given state.
+ */
+mxImageExport.prototype.drawShape = function(state, canvas)
+{
+	if (state.shape instanceof mxShape && state.shape.checkBounds())
+	{
+		canvas.save();
+		state.shape.paint(canvas);
+		canvas.restore();
+	}
+};
+
+/**
+ * Function: drawText
+ * 
+ * Draws the text of the given state.
+ */
+mxImageExport.prototype.drawText = function(state, canvas)
+{
+	if (state.text != null && state.text.checkBounds())
+	{
+		canvas.save();
+		state.text.paint(canvas);
+		canvas.restore();
+	}
+};
+
+/**
+ * Function: drawOverlays
+ * 
+ * Draws the overlays for the given state. This is called if <includeOverlays>
+ * is true.
+ */
+mxImageExport.prototype.drawOverlays = function(state, canvas)
+{
+	if (state.overlays != null)
+	{
+		state.overlays.visit(function(id, shape)
+		{
+			if (shape instanceof mxShape)
+			{
+				shape.paint(canvas);
+			}
+		});
+	}
+};
+
diff --git a/airavata-kubernetes/web-console/src/assets/js/util/mxLog.js b/airavata-kubernetes/web-console/src/assets/js/util/mxLog.js
new file mode 100644
index 0000000..28595fd
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/util/mxLog.js
@@ -0,0 +1,413 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+var mxLog =
+{
+	/**
+	 * Class: mxLog
+	 * 
+	 * A singleton class that implements a simple console.
+	 * 
+	 * Variable: consoleName
+	 * 
+	 * Specifies the name of the console window. Default is 'Console'.
+	 */
+	consoleName: 'Console',
+	
+	/**
+	 * Variable: TRACE
+	 * 
+	 * Specified if the output for <enter> and <leave> should be visible in the
+	 * console. Default is false.
+	 */
+	TRACE: false,
+
+	/**
+	 * Variable: DEBUG
+	 * 
+	 * Specifies if the output for <debug> should be visible in the console.
+	 * Default is true.
+	 */
+	DEBUG: true,
+
+	/**
+	 * Variable: WARN
+	 * 
+	 * Specifies if the output for <warn> should be visible in the console.
+	 * Default is true.
+	 */
+	WARN: true,
+
+	/**
+	 * Variable: buffer
+	 * 
+	 * Buffer for pre-initialized content.
+	 */
+	buffer: '',
+	
+	/**
+	 * Function: init
+	 *
+	 * Initializes the DOM node for the console. This requires document.body to
+	 * point to a non-null value. This is called from within <setVisible> if the
+	 * log has not yet been initialized.
+	 */
+	init: function()
+	{
+		if (mxLog.window == null && document.body != null)
+		{
+			var title = mxLog.consoleName + ' - mxGraph ' + mxClient.VERSION;
+
+			// Creates a table that maintains the layout
+			var table = document.createElement('table');
+			table.setAttribute('width', '100%');
+			table.setAttribute('height', '100%');
+
+			var tbody = document.createElement('tbody');
+			var tr = document.createElement('tr');
+			var td = document.createElement('td');
+			td.style.verticalAlign = 'top';
+				
+			// Adds the actual console as a textarea
+			mxLog.textarea = document.createElement('textarea');
+			mxLog.textarea.setAttribute('wrap', 'off');
+			mxLog.textarea.setAttribute('readOnly', 'true');
+			mxLog.textarea.style.height = '100%';
+			mxLog.textarea.style.resize = 'none';
+			mxLog.textarea.value = mxLog.buffer;
+
+			// Workaround for wrong width in standards mode
+			if (mxClient.IS_NS && document.compatMode != 'BackCompat')
+			{
+				mxLog.textarea.style.width = '99%';
+			}
+			else
+			{
+				mxLog.textarea.style.width = '100%';
+			}
+			
+			td.appendChild(mxLog.textarea);
+			tr.appendChild(td);
+			tbody.appendChild(tr);
+
+			// Creates the container div
+			tr = document.createElement('tr');
+			mxLog.td = document.createElement('td');
+			mxLog.td.style.verticalAlign = 'top';
+			mxLog.td.setAttribute('height', '30px');
+			
+			tr.appendChild(mxLog.td);
+			tbody.appendChild(tr);
+			table.appendChild(tbody);
+
+			// Adds various debugging buttons
+			mxLog.addButton('Info', function (evt)
+			{
+				mxLog.info();
+			});
+		
+			mxLog.addButton('DOM', function (evt)
+			{
+				var content = mxUtils.getInnerHtml(document.body);
+				mxLog.debug(content);
+			});
+	
+			mxLog.addButton('Trace', function (evt)
+			{
+				mxLog.TRACE = !mxLog.TRACE;
+				
+				if (mxLog.TRACE)
+				{
+					mxLog.debug('Tracing enabled');
+				}
+				else
+				{
+					mxLog.debug('Tracing disabled');
+				}
+			});	
+
+			mxLog.addButton('Copy', function (evt)
+			{
+				try
+				{
+					mxUtils.copy(mxLog.textarea.value);
+				}
+				catch (err)
+				{
+					mxUtils.alert(err);
+				}
+			});			
+
+			mxLog.addButton('Show', function (evt)
+			{
+				try
+				{
+					mxUtils.popup(mxLog.textarea.value);
+				}
+				catch (err)
+				{
+					mxUtils.alert(err);
+				}
+			});	
+			
+			mxLog.addButton('Clear', function (evt)
+			{
+				mxLog.textarea.value = '';
+			});
+
+			// Cross-browser code to get window size
+			var h = 0;
+			var w = 0;
+			
+			if (typeof(window.innerWidth) === 'number')
+			{
+				h = window.innerHeight;
+				w = window.innerWidth;
+			}
+			else
+			{
+				h = (document.documentElement.clientHeight || document.body.clientHeight);
+				w = document.body.clientWidth;
+			}
+
+			mxLog.window = new mxWindow(title, table, Math.max(0, w - 320), Math.max(0, h - 210), 300, 160);
+			mxLog.window.setMaximizable(true);
+			mxLog.window.setScrollable(false);
+			mxLog.window.setResizable(true);
+			mxLog.window.setClosable(true);
+			mxLog.window.destroyOnClose = false;
+			
+			// Workaround for ignored textarea height in various setups
+			if (((mxClient.IS_NS || mxClient.IS_IE) && !mxClient.IS_GC &&
+				!mxClient.IS_SF && document.compatMode != 'BackCompat') ||
+				document.documentMode == 11)
+			{
+				var elt = mxLog.window.getElement();
+				
+				var resizeHandler = function(sender, evt)
+				{
+					mxLog.textarea.style.height = Math.max(0, elt.offsetHeight - 70) + 'px';
+				}; 
+				
+				mxLog.window.addListener(mxEvent.RESIZE_END, resizeHandler);
+				mxLog.window.addListener(mxEvent.MAXIMIZE, resizeHandler);
+				mxLog.window.addListener(mxEvent.NORMALIZE, resizeHandler);
+
+				mxLog.textarea.style.height = '92px';
+			}
+		}
+	},
+	
+	/**
+	 * Function: info
+	 * 
+	 * Writes the current navigator information to the console.
+	 */
+	info: function()
+	{
+		mxLog.writeln(mxUtils.toString(navigator));
+	},
+			
+	/**
+	 * Function: addButton
+	 * 
+	 * Adds a button to the console using the given label and function.
+	 */
+	addButton: function(lab, funct)
+	{
+		var button = document.createElement('button');
+		mxUtils.write(button, lab);
+		mxEvent.addListener(button, 'click', funct);
+		mxLog.td.appendChild(button);
+	},
+				
+	/**
+	 * Function: isVisible
+	 * 
+	 * Returns true if the console is visible.
+	 */
+	isVisible: function()
+	{
+		if (mxLog.window != null)
+		{
+			return mxLog.window.isVisible();
+		}
+		
+		return false;
+	},
+	
+
+	/**
+	 * Function: show
+	 * 
+	 * Shows the console.
+	 */
+	show: function()
+	{
+		mxLog.setVisible(true);
+	},
+
+	/**
+	 * Function: setVisible
+	 * 
+	 * Shows or hides the console.
+	 */
+	setVisible: function(visible)
+	{
+		if (mxLog.window == null)
+		{
+			mxLog.init();
+		}
+
+		if (mxLog.window != null)
+		{
+			mxLog.window.setVisible(visible);
+		}
+	},
+
+	/**
+	 * Function: enter
+	 * 
+	 * Writes the specified string to the console
+	 * if <TRACE> is true and returns the current 
+	 * time in milliseconds.
+	 *
+	 * Example:
+	 * 
+	 * (code)
+	 * mxLog.show();
+	 * var t0 = mxLog.enter('Hello');
+	 * // Do something
+	 * mxLog.leave('World!', t0);
+	 * (end)
+	 */
+	enter: function(string)
+	{
+		if (mxLog.TRACE)
+		{
+			mxLog.writeln('Entering '+string);
+			
+			return new Date().getTime();
+		}
+	},
+
+	/**
+	 * Function: leave
+	 * 
+	 * Writes the specified string to the console
+	 * if <TRACE> is true and computes the difference
+	 * between the current time and t0 in milliseconds.
+	 * See <enter> for an example.
+	 */
+	leave: function(string, t0)
+	{
+		if (mxLog.TRACE)
+		{
+			var dt = (t0 != 0) ? ' ('+(new Date().getTime() - t0)+' ms)' : '';
+			mxLog.writeln('Leaving '+string+dt);
+		}
+	},
+	
+	/**
+	 * Function: debug
+	 * 
+	 * Adds all arguments to the console if <DEBUG> is enabled.
+	 *
+	 * Example:
+	 * 
+	 * (code)
+	 * mxLog.show();
+	 * mxLog.debug('Hello, World!');
+	 * (end)
+	 */
+	debug: function()
+	{
+		if (mxLog.DEBUG)
+		{
+			mxLog.writeln.apply(this, arguments);
+		}
+	},
+	
+	/**
+	 * Function: warn
+	 * 
+	 * Adds all arguments to the console if <WARN> is enabled.
+	 *
+	 * Example:
+	 * 
+	 * (code)
+	 * mxLog.show();
+	 * mxLog.warn('Hello, World!');
+	 * (end)
+	 */
+	warn: function()
+	{
+		if (mxLog.WARN)
+		{
+			mxLog.writeln.apply(this, arguments);
+		}
+	},
+
+	/**
+	 * Function: write
+	 * 
+	 * Adds the specified strings to the console.
+	 */
+	write: function()
+	{
+		var string = '';
+		
+		for (var i = 0; i < arguments.length; i++)
+		{
+			string += arguments[i];
+			
+			if (i < arguments.length - 1)
+			{
+				string += ' ';
+			}
+		}
+		
+		if (mxLog.textarea != null)
+		{
+			mxLog.textarea.value = mxLog.textarea.value + string;
+
+			// Workaround for no update in Presto 2.5.22 (Opera 10.5)
+			if (navigator.userAgent.indexOf('Presto/2.5') >= 0)
+			{
+				mxLog.textarea.style.visibility = 'hidden';
+				mxLog.textarea.style.visibility = 'visible';
+			}
+			
+			mxLog.textarea.scrollTop = mxLog.textarea.scrollHeight;
+		}
+		else
+		{
+			mxLog.buffer += string;
+		}
+	},
+	
+	/**
+	 * Function: writeln
+	 * 
+	 * Adds the specified strings to the console, appending a linefeed at the
+	 * end of each string.
+	 */
+	writeln: function()
+	{
+		var string = '';
+		
+		for (var i = 0; i < arguments.length; i++)
+		{
+			string += arguments[i];
+			
+			if (i < arguments.length - 1)
+			{
+				string += ' ';
+			}
+		}
+
+		mxLog.write(string + '\n');
+	}
+	
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/util/mxMorphing.js b/airavata-kubernetes/web-console/src/assets/js/util/mxMorphing.js
new file mode 100644
index 0000000..4cbdc70
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/util/mxMorphing.js
@@ -0,0 +1,248 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ *
+ * Class: mxMorphing
+ * 
+ * Implements animation for morphing cells. Here is an example of
+ * using this class for animating the result of a layout algorithm:
+ * 
+ * (code)
+ * graph.getModel().beginUpdate();
+ * try
+ * {
+ *   var circleLayout = new mxCircleLayout(graph);
+ *   circleLayout.execute(graph.getDefaultParent());
+ * }
+ * finally
+ * {
+ *   var morph = new mxMorphing(graph);
+ *   morph.addListener(mxEvent.DONE, function()
+ *   {
+ *     graph.getModel().endUpdate();
+ *   });
+ *   
+ *   morph.startAnimation();
+ * }
+ * (end)
+ * 
+ * Constructor: mxMorphing
+ * 
+ * Constructs an animation.
+ * 
+ * Parameters:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ * steps - Optional number of steps in the morphing animation. Default is 6.
+ * ease - Optional easing constant for the animation. Default is 1.5.
+ * delay - Optional delay between the animation steps. Passed to <mxAnimation>.
+ */
+function mxMorphing(graph, steps, ease, delay)
+{
+	mxAnimation.call(this, delay);
+	this.graph = graph;
+	this.steps = (steps != null) ? steps : 6;
+	this.ease = (ease != null) ? ease : 1.5;
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxMorphing.prototype = new mxAnimation();
+mxMorphing.prototype.constructor = mxMorphing;
+
+/**
+ * Variable: graph
+ * 
+ * Specifies the delay between the animation steps. Defaul is 30ms.
+ */
+mxMorphing.prototype.graph = null;
+
+/**
+ * Variable: steps
+ * 
+ * Specifies the maximum number of steps for the morphing.
+ */
+mxMorphing.prototype.steps = null;
+
+/**
+ * Variable: step
+ * 
+ * Contains the current step.
+ */
+mxMorphing.prototype.step = 0;
+
+/**
+ * Variable: ease
+ * 
+ * Ease-off for movement towards the given vector. Larger values are
+ * slower and smoother. Default is 4.
+ */
+mxMorphing.prototype.ease = null;
+
+/**
+ * Variable: cells
+ * 
+ * Optional array of cells to be animated. If this is not specified
+ * then all cells are checked and animated if they have been moved
+ * in the current transaction.
+ */
+mxMorphing.prototype.cells = null;
+
+/**
+ * Function: updateAnimation
+ *
+ * Animation step.
+ */
+mxMorphing.prototype.updateAnimation = function()
+{
+	var move = new mxCellStatePreview(this.graph);
+
+	if (this.cells != null)
+	{
+		// Animates the given cells individually without recursion
+		for (var i = 0; i < this.cells.length; i++)
+		{
+			this.animateCell(this.cells[i], move, false);
+		}
+	}
+	else
+	{
+		// Animates all changed cells by using recursion to find
+		// the changed cells but not for the animation itself
+		this.animateCell(this.graph.getModel().getRoot(), move, true);
+	}
+	
+	this.show(move);
+	
+	if (move.isEmpty() || this.step++ >= this.steps)
+	{
+		this.stopAnimation();
+	}
+};
+
+/**
+ * Function: show
+ *
+ * Shows the changes in the given <mxCellStatePreview>.
+ */
+mxMorphing.prototype.show = function(move)
+{
+	move.show();
+};
+
+/**
+ * Function: animateCell
+ *
+ * Animates the given cell state using <mxCellStatePreview.moveState>.
+ */
+mxMorphing.prototype.animateCell = function(cell, move, recurse)
+{
+	var state = this.graph.getView().getState(cell);
+	var delta = null;
+
+	if (state != null)
+	{
+		// Moves the animated state from where it will be after the model
+		// change by subtracting the given delta vector from that location
+		delta = this.getDelta(state);
+
+		if (this.graph.getModel().isVertex(cell) && (delta.x != 0 || delta.y != 0))
+		{
+			var translate = this.graph.view.getTranslate();
+			var scale = this.graph.view.getScale();
+			
+			delta.x += translate.x * scale;
+			delta.y += translate.y * scale;
+			
+			move.moveState(state, -delta.x / this.ease, -delta.y / this.ease);
+		}
+	}
+	
+	if (recurse && !this.stopRecursion(state, delta))
+	{
+		var childCount = this.graph.getModel().getChildCount(cell);
+
+		for (var i = 0; i < childCount; i++)
+		{
+			this.animateCell(this.graph.getModel().getChildAt(cell, i), move, recurse);
+		}
+	}
+};
+
+/**
+ * Function: stopRecursion
+ *
+ * Returns true if the animation should not recursively find more
+ * deltas for children if the given parent state has been animated.
+ */
+mxMorphing.prototype.stopRecursion = function(state, delta)
+{
+	return delta != null && (delta.x != 0 || delta.y != 0);
+};
+
+/**
+ * Function: getDelta
+ *
+ * Returns the vector between the current rendered state and the future
+ * location of the state after the display will be updated.
+ */
+mxMorphing.prototype.getDelta = function(state)
+{
+	var origin = this.getOriginForCell(state.cell);
+	var translate = this.graph.getView().getTranslate();
+	var scale = this.graph.getView().getScale();
+	var x = state.x / scale - translate.x;
+	var y = state.y / scale - translate.y;
+
+	return new mxPoint((origin.x - x) * scale, (origin.y - y) * scale);
+};
+
+/**
+ * Function: getOriginForCell
+ *
+ * Returns the top, left corner of the given cell. TODO: Improve performance
+ * by using caching inside this method as the result per cell never changes
+ * during the lifecycle of this object.
+ */
+mxMorphing.prototype.getOriginForCell = function(cell)
+{
+	var result = null;
+	
+	if (cell != null)
+	{
+		var parent = this.graph.getModel().getParent(cell);
+		var geo = this.graph.getCellGeometry(cell);
+		result = this.getOriginForCell(parent);
+		
+		// TODO: Handle offsets
+		if (geo != null)
+		{
+			if (geo.relative)
+			{
+				var pgeo = this.graph.getCellGeometry(parent);
+				
+				if (pgeo != null)
+				{
+					result.x += geo.x * pgeo.width;
+					result.y += geo.y * pgeo.height;
+				}
+			}
+			else
+			{
+				result.x += geo.x;
+				result.y += geo.y;
+			}
+		}
+	}
+	
+	if (result == null)
+	{
+		var t = this.graph.view.getTranslate();
+		result = new mxPoint(-t.x, -t.y);
+	}
+	
+	return result;
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/util/mxMouseEvent.js b/airavata-kubernetes/web-console/src/assets/js/util/mxMouseEvent.js
new file mode 100644
index 0000000..875bac4
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/util/mxMouseEvent.js
@@ -0,0 +1,244 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxMouseEvent
+ * 
+ * Base class for all mouse events in mxGraph. A listener for this event should
+ * implement the following methods:
+ * 
+ * (code)
+ * graph.addMouseListener(
+ * {
+ *   mouseDown: function(sender, evt)
+ *   {
+ *     mxLog.debug('mouseDown');
+ *   },
+ *   mouseMove: function(sender, evt)
+ *   {
+ *     mxLog.debug('mouseMove');
+ *   },
+ *   mouseUp: function(sender, evt)
+ *   {
+ *     mxLog.debug('mouseUp');
+ *   }
+ * });
+ * (end)
+ * 
+ * Constructor: mxMouseEvent
+ *
+ * Constructs a new event object for the given arguments.
+ * 
+ * Parameters:
+ * 
+ * evt - Native mouse event.
+ * state - Optional <mxCellState> under the mouse.
+ * 
+ */
+function mxMouseEvent(evt, state)
+{
+	this.evt = evt;
+	this.state = state;
+	this.sourceState = state;
+};
+
+/**
+ * Variable: consumed
+ *
+ * Holds the consumed state of this event.
+ */
+mxMouseEvent.prototype.consumed = false;
+
+/**
+ * Variable: evt
+ *
+ * Holds the inner event object.
+ */
+mxMouseEvent.prototype.evt = null;
+
+/**
+ * Variable: graphX
+ *
+ * Holds the x-coordinate of the event in the graph. This value is set in
+ * <mxGraph.fireMouseEvent>.
+ */
+mxMouseEvent.prototype.graphX = null;
+
+/**
+ * Variable: graphY
+ *
+ * Holds the y-coordinate of the event in the graph. This value is set in
+ * <mxGraph.fireMouseEvent>.
+ */
+mxMouseEvent.prototype.graphY = null;
+
+/**
+ * Variable: state
+ *
+ * Holds the optional <mxCellState> associated with this event.
+ */
+mxMouseEvent.prototype.state = null;
+
+/**
+ * Variable: sourceState
+ * 
+ * Holds the <mxCellState> that was passed to the constructor. This can be
+ * different from <state> depending on the result of <mxGraph.getEventState>.
+ */
+mxMouseEvent.prototype.sourceState = null;
+
+/**
+ * Function: getEvent
+ * 
+ * Returns <evt>.
+ */
+mxMouseEvent.prototype.getEvent = function()
+{
+	return this.evt;
+};
+
+/**
+ * Function: getSource
+ * 
+ * Returns the target DOM element using <mxEvent.getSource> for <evt>.
+ */
+mxMouseEvent.prototype.getSource = function()
+{
+	return mxEvent.getSource(this.evt);
+};
+
+/**
+ * Function: isSource
+ * 
+ * Returns true if the given <mxShape> is the source of <evt>.
+ */
+mxMouseEvent.prototype.isSource = function(shape)
+{
+	if (shape != null)
+	{
+		return mxUtils.isAncestorNode(shape.node, this.getSource());
+	}
+	
+	return false;
+};
+
+/**
+ * Function: getX
+ * 
+ * Returns <evt.clientX>.
+ */
+mxMouseEvent.prototype.getX = function()
+{
+	return mxEvent.getClientX(this.getEvent());
+};
+
+/**
+ * Function: getY
+ * 
+ * Returns <evt.clientY>.
+ */
+mxMouseEvent.prototype.getY = function()
+{
+	return mxEvent.getClientY(this.getEvent());
+};
+
+/**
+ * Function: getGraphX
+ * 
+ * Returns <graphX>.
+ */
+mxMouseEvent.prototype.getGraphX = function()
+{
+	return this.graphX;
+};
+
+/**
+ * Function: getGraphY
+ * 
+ * Returns <graphY>.
+ */
+mxMouseEvent.prototype.getGraphY = function()
+{
+	return this.graphY;
+};
+
+/**
+ * Function: getState
+ * 
+ * Returns <state>.
+ */
+mxMouseEvent.prototype.getState = function()
+{
+	return this.state;
+};
+
+/**
+ * Function: getCell
+ * 
+ * Returns the <mxCell> in <state> is not null.
+ */
+mxMouseEvent.prototype.getCell = function()
+{
+	var state = this.getState();
+	
+	if (state != null)
+	{
+		return state.cell;
+	}
+	
+	return null;
+};
+
+/**
+ * Function: isPopupTrigger
+ *
+ * Returns true if the event is a popup trigger.
+ */
+mxMouseEvent.prototype.isPopupTrigger = function()
+{
+	return mxEvent.isPopupTrigger(this.getEvent());
+};
+
+/**
+ * Function: isConsumed
+ *
+ * Returns <consumed>.
+ */
+mxMouseEvent.prototype.isConsumed = function()
+{
+	return this.consumed;
+};
+
+/**
+ * Function: consume
+ *
+ * Sets <consumed> to true and invokes preventDefault on the native event
+ * if such a method is defined. This is used mainly to avoid the cursor from
+ * being changed to a text cursor in Webkit. You can use the preventDefault
+ * flag to disable this functionality.
+ * 
+ * Parameters:
+ * 
+ * preventDefault - Specifies if the native event should be canceled. Default
+ * is true.
+ */
+mxMouseEvent.prototype.consume = function(preventDefault)
+{
+	preventDefault = (preventDefault != null) ? preventDefault : true;
+	
+	if (preventDefault && this.evt.preventDefault)
+	{
+		this.evt.preventDefault();
+	}
+
+	// Workaround for images being dragged in IE
+	// Does not change returnValue in Opera
+	if (mxClient.IS_IE)
+	{
+		this.evt.returnValue = true;
+	}
+
+	// Sets local consumed state
+	this.consumed = true;
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/util/mxObjectIdentity.js b/airavata-kubernetes/web-console/src/assets/js/util/mxObjectIdentity.js
new file mode 100644
index 0000000..457eee8
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/util/mxObjectIdentity.js
@@ -0,0 +1,72 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+var mxObjectIdentity =
+{
+	/**
+	 * Class: mxObjectIdentity
+	 * 
+	 * Identity for JavaScript objects and functions. This is implemented using
+	 * a simple incrementing counter which is stored in each object under
+	 * <FIELD_NAME>.
+	 * 
+	 * The identity for an object does not change during its lifecycle.
+	 * 
+	 * Variable: FIELD_NAME
+	 * 
+	 * Name of the field to be used to store the object ID. Default is
+	 * <code>mxObjectId</code>.
+	 */
+	FIELD_NAME: 'mxObjectId',
+
+	/**
+	 * Variable: counter
+	 * 
+	 * Current counter.
+	 */
+	counter: 0,
+
+	/**
+	 * Function: get
+	 * 
+	 * Returns the ID for the given object or function or null if no object
+	 * is specified.
+	 */
+	get: function(obj)
+	{
+		if (obj != null)
+		{
+			if (obj[mxObjectIdentity.FIELD_NAME] == null)
+			{
+				if (typeof obj === 'object')
+				{
+					var ctor = mxUtils.getFunctionName(obj.constructor);
+					obj[mxObjectIdentity.FIELD_NAME] = ctor + '#' + mxObjectIdentity.counter++;
+				}
+				else if (typeof obj === 'function')
+				{
+					obj[mxObjectIdentity.FIELD_NAME] = 'Function#' + mxObjectIdentity.counter++;
+				}
+			}
+			
+			return obj[mxObjectIdentity.FIELD_NAME];
+		}
+		
+		return null;
+	},
+
+	/**
+	 * Function: clear
+	 * 
+	 * Deletes the ID from the given object or function.
+	 */
+	clear: function(obj)
+	{
+		if (typeof(obj) === 'object' || typeof obj === 'function')
+		{
+			delete obj[mxObjectIdentity.FIELD_NAME];
+		}
+	}
+
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/util/mxPanningManager.js b/airavata-kubernetes/web-console/src/assets/js/util/mxPanningManager.js
new file mode 100644
index 0000000..0a3f815
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/util/mxPanningManager.js
@@ -0,0 +1,262 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxPanningManager
+ *
+ * Implements a handler for panning.
+ */
+function mxPanningManager(graph)
+{
+	this.thread = null;
+	this.active = false;
+	this.tdx = 0;
+	this.tdy = 0;
+	this.t0x = 0;
+	this.t0y = 0;
+	this.dx = 0;
+	this.dy = 0;
+	this.scrollbars = false;
+	this.scrollLeft = 0;
+	this.scrollTop = 0;
+	
+	this.mouseListener =
+	{
+	    mouseDown: function(sender, me) { },
+	    mouseMove: function(sender, me) { },
+	    mouseUp: mxUtils.bind(this, function(sender, me)
+	    {
+	    	if (this.active)
+	    	{
+	    		this.stop();
+	    	}
+	    })
+	};
+	
+	graph.addMouseListener(this.mouseListener);
+	
+	// Stops scrolling on every mouseup anywhere in the document
+	mxEvent.addListener(document, 'mouseup', mxUtils.bind(this, function()
+	{
+    	if (this.active)
+    	{
+    		this.stop();
+    	}
+	}));
+	
+	var createThread = mxUtils.bind(this, function()
+	{
+    	this.scrollbars = mxUtils.hasScrollbars(graph.container);
+    	this.scrollLeft = graph.container.scrollLeft;
+    	this.scrollTop = graph.container.scrollTop;
+
+    	return window.setInterval(mxUtils.bind(this, function()
+		{
+			this.tdx -= this.dx;
+			this.tdy -= this.dy;
+
+			if (this.scrollbars)
+			{
+				var left = -graph.container.scrollLeft - Math.ceil(this.dx);
+				var top = -graph.container.scrollTop - Math.ceil(this.dy);
+				graph.panGraph(left, top);
+				graph.panDx = this.scrollLeft - graph.container.scrollLeft;
+				graph.panDy = this.scrollTop - graph.container.scrollTop;
+				graph.fireEvent(new mxEventObject(mxEvent.PAN));
+				// TODO: Implement graph.autoExtend
+			}
+			else
+			{
+				graph.panGraph(this.getDx(), this.getDy());
+			}
+		}), this.delay);
+	});
+	
+	this.isActive = function()
+	{
+		return active;
+	};
+	
+	this.getDx = function()
+	{
+		return Math.round(this.tdx);
+	};
+	
+	this.getDy = function()
+	{
+		return Math.round(this.tdy);
+	};
+	
+	this.start = function()
+	{
+		this.t0x = graph.view.translate.x;
+		this.t0y = graph.view.translate.y;
+		this.active = true;
+	};
+	
+	this.panTo = function(x, y, w, h)
+	{
+		if (!this.active)
+		{
+			this.start();
+		}
+		
+    	this.scrollLeft = graph.container.scrollLeft;
+    	this.scrollTop = graph.container.scrollTop;
+		
+		w = (w != null) ? w : 0;
+		h = (h != null) ? h : 0;
+		
+		var c = graph.container;
+		this.dx = x + w - c.scrollLeft - c.clientWidth;
+		
+		if (this.dx < 0 && Math.abs(this.dx) < this.border)
+		{
+			this.dx = this.border + this.dx;
+		}
+		else if (this.handleMouseOut)
+		{
+			this.dx = Math.max(this.dx, 0);
+		}
+		else
+		{
+			this.dx = 0;
+		}
+		
+		if (this.dx == 0)
+		{
+			this.dx = x - c.scrollLeft;
+			
+			if (this.dx > 0 && this.dx < this.border)
+			{
+				this.dx = this.dx - this.border;
+			}
+			else if (this.handleMouseOut)
+			{
+				this.dx = Math.min(0, this.dx);
+			}
+			else
+			{
+				this.dx = 0;
+			}
+		}
+		
+		this.dy = y + h - c.scrollTop - c.clientHeight;
+
+		if (this.dy < 0 && Math.abs(this.dy) < this.border)
+		{
+			this.dy = this.border + this.dy;
+		}
+		else if (this.handleMouseOut)
+		{
+			this.dy = Math.max(this.dy, 0);
+		}
+		else
+		{
+			this.dy = 0;
+		}
+		
+		if (this.dy == 0)
+		{
+			this.dy = y - c.scrollTop;
+			
+			if (this.dy > 0 && this.dy < this.border)
+			{
+				this.dy = this.dy - this.border;
+			}
+			else if (this.handleMouseOut)
+			{
+				this.dy = Math.min(0, this.dy);
+			} 
+			else
+			{
+				this.dy = 0;
+			}
+		}
+		
+		if (this.dx != 0 || this.dy != 0)
+		{
+			this.dx *= this.damper;
+			this.dy *= this.damper;
+			
+			if (this.thread == null)
+			{
+				this.thread = createThread();
+			}
+		}
+		else if (this.thread != null)
+		{
+			window.clearInterval(this.thread);
+			this.thread = null;
+		}
+	};
+	
+	this.stop = function()
+	{
+		if (this.active)
+		{
+			this.active = false;
+		
+			if (this.thread != null)
+	    	{
+				window.clearInterval(this.thread);
+				this.thread = null;
+	    	}
+			
+			this.tdx = 0;
+			this.tdy = 0;
+			
+			if (!this.scrollbars)
+			{
+				var px = graph.panDx;
+				var py = graph.panDy;
+		    	
+		    	if (px != 0 || py != 0)
+		    	{
+		    		graph.panGraph(0, 0);
+			    	graph.view.setTranslate(this.t0x + px / graph.view.scale, this.t0y + py / graph.view.scale);
+		    	}
+			}
+			else
+			{
+				graph.panDx = 0;
+				graph.panDy = 0;
+				graph.fireEvent(new mxEventObject(mxEvent.PAN));
+			}
+		}
+	};
+	
+	this.destroy = function()
+	{
+		graph.removeMouseListener(this.mouseListener);
+	};
+};
+
+/**
+ * Variable: damper
+ * 
+ * Damper value for the panning. Default is 1/6.
+ */
+mxPanningManager.prototype.damper = 1/6;
+
+/**
+ * Variable: delay
+ * 
+ * Delay in milliseconds for the panning. Default is 10.
+ */
+mxPanningManager.prototype.delay = 10;
+
+/**
+ * Variable: handleMouseOut
+ * 
+ * Specifies if mouse events outside of the component should be handled. Default is true. 
+ */
+mxPanningManager.prototype.handleMouseOut = true;
+
+/**
+ * Variable: border
+ * 
+ * Border to handle automatic panning inside the component. Default is 0 (disabled).
+ */
+mxPanningManager.prototype.border = 0;
diff --git a/airavata-kubernetes/web-console/src/assets/js/util/mxPoint.js b/airavata-kubernetes/web-console/src/assets/js/util/mxPoint.js
new file mode 100644
index 0000000..e7d300a
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/util/mxPoint.js
@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxPoint
+ *
+ * Implements a 2-dimensional vector with double precision coordinates.
+ * 
+ * Constructor: mxPoint
+ *
+ * Constructs a new point for the optional x and y coordinates. If no
+ * coordinates are given, then the default values for <x> and <y> are used.
+ */
+function mxPoint(x, y)
+{
+	this.x = (x != null) ? x : 0;
+	this.y = (y != null) ? y : 0;
+};
+
+/**
+ * Variable: x
+ *
+ * Holds the x-coordinate of the point. Default is 0.
+ */
+mxPoint.prototype.x = null;
+
+/**
+ * Variable: y
+ *
+ * Holds the y-coordinate of the point. Default is 0.
+ */
+mxPoint.prototype.y = null;
+
+/**
+ * Function: equals
+ * 
+ * Returns true if the given object equals this point.
+ */
+mxPoint.prototype.equals = function(obj)
+{
+	return obj != null && obj.x == this.x && obj.y == this.y;
+};
+
+/**
+ * Function: clone
+ *
+ * Returns a clone of this <mxPoint>.
+ */
+mxPoint.prototype.clone = function()
+{
+	// Handles subclasses as well
+	return mxUtils.clone(this);
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/util/mxPopupMenu.js b/airavata-kubernetes/web-console/src/assets/js/util/mxPopupMenu.js
new file mode 100644
index 0000000..82777a2
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/util/mxPopupMenu.js
@@ -0,0 +1,613 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxPopupMenu
+ * 
+ * Basic popup menu. To add a vertical scrollbar to a given submenu, the
+ * following code can be used.
+ * 
+ * (code)
+ * var mxPopupMenuShowMenu = mxPopupMenu.prototype.showMenu;
+ * mxPopupMenu.prototype.showMenu = function()
+ * {
+ *   mxPopupMenuShowMenu.apply(this, arguments);
+ *   
+ *   this.div.style.overflowY = 'auto';
+ *   this.div.style.overflowX = 'hidden';
+ *   this.div.style.maxHeight = '160px';
+ * };
+ * (end)
+ * 
+ * Constructor: mxPopupMenu
+ * 
+ * Constructs a popupmenu.
+ * 
+ * Event: mxEvent.SHOW
+ *
+ * Fires after the menu has been shown in <popup>.
+ */
+function mxPopupMenu(factoryMethod)
+{
+	this.factoryMethod = factoryMethod;
+	
+	if (factoryMethod != null)
+	{
+		this.init();
+	}
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxPopupMenu.prototype = new mxEventSource();
+mxPopupMenu.prototype.constructor = mxPopupMenu;
+
+/**
+ * Variable: submenuImage
+ * 
+ * URL of the image to be used for the submenu icon.
+ */
+mxPopupMenu.prototype.submenuImage = mxClient.imageBasePath + '/submenu.gif';
+
+/**
+ * Variable: zIndex
+ * 
+ * Specifies the zIndex for the popupmenu and its shadow. Default is 1006.
+ */
+mxPopupMenu.prototype.zIndex = 10006;
+
+/**
+ * Variable: factoryMethod
+ * 
+ * Function that is used to create the popup menu. The function takes the
+ * current panning handler, the <mxCell> under the mouse and the mouse
+ * event that triggered the call as arguments.
+ */
+mxPopupMenu.prototype.factoryMethod = null;
+
+/**
+ * Variable: useLeftButtonForPopup
+ * 
+ * Specifies if popupmenus should be activated by clicking the left mouse
+ * button. Default is false.
+ */
+mxPopupMenu.prototype.useLeftButtonForPopup = false;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies if events are handled. Default is true.
+ */
+mxPopupMenu.prototype.enabled = true;
+
+/**
+ * Variable: itemCount
+ * 
+ * Contains the number of times <addItem> has been called for a new menu.
+ */
+mxPopupMenu.prototype.itemCount = 0;
+
+/**
+ * Variable: autoExpand
+ * 
+ * Specifies if submenus should be expanded on mouseover. Default is false.
+ */
+mxPopupMenu.prototype.autoExpand = false;
+
+/**
+ * Variable: smartSeparators
+ * 
+ * Specifies if separators should only be added if a menu item follows them.
+ * Default is false.
+ */
+mxPopupMenu.prototype.smartSeparators = false;
+
+/**
+ * Variable: labels
+ * 
+ * Specifies if any labels should be visible. Default is true.
+ */
+mxPopupMenu.prototype.labels = true;
+
+/**
+ * Function: init
+ * 
+ * Initializes the shapes required for this vertex handler.
+ */
+mxPopupMenu.prototype.init = function()
+{
+	// Adds the inner table
+	this.table = document.createElement('table');
+	this.table.className = 'mxPopupMenu';
+	
+	this.tbody = document.createElement('tbody');
+	this.table.appendChild(this.tbody);
+
+	// Adds the outer div
+	this.div = document.createElement('div');
+	this.div.className = 'mxPopupMenu';
+	this.div.style.display = 'inline';
+	this.div.style.zIndex = this.zIndex;
+	this.div.appendChild(this.table);
+
+	// Disables the context menu on the outer div
+	mxEvent.disableContextMenu(this.div);
+};
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxPopupMenu.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+	
+/**
+ * Function: setEnabled
+ * 
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ */
+mxPopupMenu.prototype.setEnabled = function(enabled)
+{
+	this.enabled = enabled;
+};
+
+/**
+ * Function: isPopupTrigger
+ * 
+ * Returns true if the given event is a popupmenu trigger for the optional
+ * given cell.
+ * 
+ * Parameters:
+ * 
+ * me - <mxMouseEvent> that represents the mouse event.
+ */
+mxPopupMenu.prototype.isPopupTrigger = function(me)
+{
+	return me.isPopupTrigger() || (this.useLeftButtonForPopup && mxEvent.isLeftMouseButton(me.getEvent()));
+};
+
+/**
+ * Function: addItem
+ * 
+ * Adds the given item to the given parent item. If no parent item is specified
+ * then the item is added to the top-level menu. The return value may be used
+ * as the parent argument, ie. as a submenu item. The return value is the table
+ * row that represents the item.
+ * 
+ * Paramters:
+ * 
+ * title - String that represents the title of the menu item.
+ * image - Optional URL for the image icon.
+ * funct - Function associated that takes a mouseup or touchend event.
+ * parent - Optional item returned by <addItem>.
+ * iconCls - Optional string that represents the CSS class for the image icon.
+ * IconsCls is ignored if image is given.
+ * enabled - Optional boolean indicating if the item is enabled. Default is true.
+ * active - Optional boolean indicating if the menu should implement any event handling.
+ * Default is true.
+ */
+mxPopupMenu.prototype.addItem = function(title, image, funct, parent, iconCls, enabled, active)
+{
+	parent = parent || this;
+	this.itemCount++;
+	
+	// Smart separators only added if element contains items
+	if (parent.willAddSeparator)
+	{
+		if (parent.containsItems)
+		{
+			this.addSeparator(parent, true);
+		}
+
+		parent.willAddSeparator = false;
+	}
+
+	parent.containsItems = true;
+	var tr = document.createElement('tr');
+	tr.className = 'mxPopupMenuItem';
+	var col1 = document.createElement('td');
+	col1.className = 'mxPopupMenuIcon';
+
+	// Adds the given image into the first column
+	if (image != null)
+	{
+		var img = document.createElement('img');
+		img.src = image;
+		col1.appendChild(img);
+	}
+	else if (iconCls != null)
+	{
+		var div = document.createElement('div');
+		div.className = iconCls;
+		col1.appendChild(div);
+	}
+	
+	tr.appendChild(col1);
+	
+	if (this.labels)
+	{
+		var col2 = document.createElement('td');
+		col2.className = 'mxPopupMenuItem' +
+			((enabled != null && !enabled) ? ' mxDisabled' : '');
+		
+		mxUtils.write(col2, title);
+		col2.align = 'left';
+		tr.appendChild(col2);
+	
+		var col3 = document.createElement('td');
+		col3.className = 'mxPopupMenuItem' +
+			((enabled != null && !enabled) ? ' mxDisabled' : '');
+		col3.style.paddingRight = '6px';
+		col3.style.textAlign = 'right';
+		
+		tr.appendChild(col3);
+		
+		if (parent.div == null)
+		{
+			this.createSubmenu(parent);
+		}
+	}
+	
+	parent.tbody.appendChild(tr);
+
+	if (active != false && enabled != false)
+	{
+		var currentSelection = null;
+		
+		mxEvent.addGestureListeners(tr,
+			mxUtils.bind(this, function(evt)
+			{
+				this.eventReceiver = tr;
+				
+				if (parent.activeRow != tr && parent.activeRow != parent)
+				{
+					if (parent.activeRow != null && parent.activeRow.div.parentNode != null)
+					{
+						this.hideSubmenu(parent);
+					}
+					
+					if (tr.div != null)
+					{
+						this.showSubmenu(parent, tr);
+						parent.activeRow = tr;
+					}
+				}
+				
+				// Workaround for lost current selection in page because of focus in IE
+				if (mxClient.IS_QUIRKS || document.documentMode == 8)
+				{
+					currentSelection = document.selection.createRange();
+				}
+				
+				mxEvent.consume(evt);
+			}),
+			mxUtils.bind(this, function(evt)
+			{
+				if (parent.activeRow != tr && parent.activeRow != parent)
+				{
+					if (parent.activeRow != null && parent.activeRow.div.parentNode != null)
+					{
+						this.hideSubmenu(parent);
+					}
+					
+					if (this.autoExpand && tr.div != null)
+					{
+						this.showSubmenu(parent, tr);
+						parent.activeRow = tr;
+					}
+				}
+		
+				// Sets hover style because TR in IE doesn't have hover
+				tr.className = 'mxPopupMenuItemHover';
+			}),
+			mxUtils.bind(this, function(evt)
+			{
+				// EventReceiver avoids clicks on a submenu item
+				// which has just been shown in the mousedown
+				if (this.eventReceiver == tr)
+				{
+					if (parent.activeRow != tr)
+					{
+						this.hideMenu();
+					}
+					
+					// Workaround for lost current selection in page because of focus in IE
+					if (currentSelection != null)
+					{
+						// Workaround for "unspecified error" in IE8 standards
+						try
+						{
+							currentSelection.select();
+						}
+						catch (e)
+						{
+							// ignore
+						}
+
+						currentSelection = null;
+					}
+					
+					if (funct != null)
+					{
+						funct(evt);
+					}
+				}
+				
+				this.eventReceiver = null;
+				mxEvent.consume(evt);
+			})
+		);
+	
+		// Resets hover style because TR in IE doesn't have hover
+		mxEvent.addListener(tr, 'mouseout',
+			mxUtils.bind(this, function(evt)
+			{
+				tr.className = 'mxPopupMenuItem';
+			})
+		);
+	}
+	
+	return tr;
+};
+
+/**
+ * Adds a checkmark to the given menuitem.
+ */
+mxPopupMenu.prototype.addCheckmark = function(item, img)
+{
+	var td = item.firstChild.nextSibling;
+	td.style.backgroundImage = 'url(\'' + img + '\')';
+	td.style.backgroundRepeat = 'no-repeat';
+	td.style.backgroundPosition = '2px 50%';
+};
+
+/**
+ * Function: createSubmenu
+ * 
+ * Creates the nodes required to add submenu items inside the given parent
+ * item. This is called in <addItem> if a parent item is used for the first
+ * time. This adds various DOM nodes and a <submenuImage> to the parent.
+ * 
+ * Parameters:
+ * 
+ * parent - An item returned by <addItem>.
+ */
+mxPopupMenu.prototype.createSubmenu = function(parent)
+{
+	parent.table = document.createElement('table');
+	parent.table.className = 'mxPopupMenu';
+
+	parent.tbody = document.createElement('tbody');
+	parent.table.appendChild(parent.tbody);
+
+	parent.div = document.createElement('div');
+	parent.div.className = 'mxPopupMenu';
+
+	parent.div.style.position = 'absolute';
+	parent.div.style.display = 'inline';
+	parent.div.style.zIndex = this.zIndex;
+	
+	parent.div.appendChild(parent.table);
+	
+	var img = document.createElement('img');
+	img.setAttribute('src', this.submenuImage);
+	
+	// Last column of the submenu item in the parent menu
+	td = parent.firstChild.nextSibling.nextSibling;
+	td.appendChild(img);
+};
+
+/**
+ * Function: showSubmenu
+ * 
+ * Shows the submenu inside the given parent row.
+ */
+mxPopupMenu.prototype.showSubmenu = function(parent, row)
+{
+	if (row.div != null)
+	{
+		row.div.style.left = (parent.div.offsetLeft +
+			row.offsetLeft+row.offsetWidth - 1) + 'px';
+		row.div.style.top = (parent.div.offsetTop+row.offsetTop) + 'px';
+		document.body.appendChild(row.div);
+		
+		// Moves the submenu to the left side if there is no space
+		var left = parseInt(row.div.offsetLeft);
+		var width = parseInt(row.div.offsetWidth);
+		var offset = mxUtils.getDocumentScrollOrigin(document);
+		
+		var b = document.body;
+		var d = document.documentElement;
+		
+		var right = offset.x + (b.clientWidth || d.clientWidth);
+		
+		if (left + width > right)
+		{
+			row.div.style.left = Math.max(0, (parent.div.offsetLeft - width + ((mxClient.IS_IE) ? 6 : -6))) + 'px';
+		}
+		
+		mxUtils.fit(row.div);
+	}
+};
+
+/**
+ * Function: addSeparator
+ * 
+ * Adds a horizontal separator in the given parent item or the top-level menu
+ * if no parent is specified.
+ * 
+ * Parameters:
+ * 
+ * parent - Optional item returned by <addItem>.
+ * force - Optional boolean to ignore <smartSeparators>. Default is false.
+ */
+mxPopupMenu.prototype.addSeparator = function(parent, force)
+{
+	parent = parent || this;
+	
+	if (this.smartSeparators && !force)
+	{
+		parent.willAddSeparator = true;
+	}
+	else if (parent.tbody != null)
+	{
+		parent.willAddSeparator = false;
+		var tr = document.createElement('tr');
+		
+		var col1 = document.createElement('td');
+		col1.className = 'mxPopupMenuIcon';
+		col1.style.padding = '0 0 0 0px';
+		
+		tr.appendChild(col1);
+		
+		var col2 = document.createElement('td');
+		col2.style.padding = '0 0 0 0px';
+		col2.setAttribute('colSpan', '2');
+	
+		var hr = document.createElement('hr');
+		hr.setAttribute('size', '1');
+		col2.appendChild(hr);
+		
+		tr.appendChild(col2);
+		
+		parent.tbody.appendChild(tr);
+	}
+};
+
+/**
+ * Function: popup
+ * 
+ * Shows the popup menu for the given event and cell.
+ * 
+ * Example:
+ * 
+ * (code)
+ * graph.panningHandler.popup = function(x, y, cell, evt)
+ * {
+ *   mxUtils.alert('Hello, World!');
+ * }
+ * (end)
+ */
+mxPopupMenu.prototype.popup = function(x, y, cell, evt)
+{
+	if (this.div != null && this.tbody != null && this.factoryMethod != null)
+	{
+		this.div.style.left = x + 'px';
+		this.div.style.top = y + 'px';
+		
+		// Removes all child nodes from the existing menu
+		while (this.tbody.firstChild != null)
+		{
+			mxEvent.release(this.tbody.firstChild);
+			this.tbody.removeChild(this.tbody.firstChild);
+		}
+		
+		this.itemCount = 0;
+		this.factoryMethod(this, cell, evt);
+		
+		if (this.itemCount > 0)
+		{
+			this.showMenu();
+			this.fireEvent(new mxEventObject(mxEvent.SHOW));
+		}
+	}
+};
+
+/**
+ * Function: isMenuShowing
+ * 
+ * Returns true if the menu is showing.
+ */
+mxPopupMenu.prototype.isMenuShowing = function()
+{
+	return this.div != null && this.div.parentNode == document.body;
+};
+
+/**
+ * Function: showMenu
+ * 
+ * Shows the menu.
+ */
+mxPopupMenu.prototype.showMenu = function()
+{
+	// Disables filter-based shadow in IE9 standards mode
+	if (document.documentMode >= 9)
+	{
+		this.div.style.filter = 'none';
+	}
+	
+	// Fits the div inside the viewport
+	document.body.appendChild(this.div);
+	mxUtils.fit(this.div);
+};
+
+/**
+ * Function: hideMenu
+ * 
+ * Removes the menu and all submenus.
+ */
+mxPopupMenu.prototype.hideMenu = function()
+{
+	if (this.div != null)
+	{
+		if (this.div.parentNode != null)
+		{
+			this.div.parentNode.removeChild(this.div);
+		}
+		
+		this.hideSubmenu(this);
+		this.containsItems = false;
+		this.fireEvent(new mxEventObject(mxEvent.HIDE));
+	}
+};
+
+/**
+ * Function: hideSubmenu
+ * 
+ * Removes all submenus inside the given parent.
+ * 
+ * Parameters:
+ * 
+ * parent - An item returned by <addItem>.
+ */
+mxPopupMenu.prototype.hideSubmenu = function(parent)
+{
+	if (parent.activeRow != null)
+	{
+		this.hideSubmenu(parent.activeRow);
+		
+		if (parent.activeRow.div.parentNode != null)
+		{
+			parent.activeRow.div.parentNode.removeChild(parent.activeRow.div);
+		}
+		
+		parent.activeRow = null;
+	}
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the handler and all its resources and DOM nodes.
+ */
+mxPopupMenu.prototype.destroy = function()
+{
+	if (this.div != null)
+	{
+		mxEvent.release(this.div);
+		
+		if (this.div.parentNode != null)
+		{
+			this.div.parentNode.removeChild(this.div);
+		}
+		
+		this.div = null;
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/util/mxRectangle.js b/airavata-kubernetes/web-console/src/assets/js/util/mxRectangle.js
new file mode 100644
index 0000000..b935e5a
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/util/mxRectangle.js
@@ -0,0 +1,179 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxRectangle
+ *
+ * Extends <mxPoint> to implement a 2-dimensional rectangle with double
+ * precision coordinates.
+ * 
+ * Constructor: mxRectangle
+ *
+ * Constructs a new rectangle for the optional parameters. If no parameters
+ * are given then the respective default values are used.
+ */
+function mxRectangle(x, y, width, height)
+{
+	mxPoint.call(this, x, y);
+
+	this.width = (width != null) ? width : 0;
+	this.height = (height != null) ? height : 0;
+};
+
+/**
+ * Extends mxPoint.
+ */
+mxRectangle.prototype = new mxPoint();
+mxRectangle.prototype.constructor = mxRectangle;
+
+/**
+ * Variable: width
+ *
+ * Holds the width of the rectangle. Default is 0.
+ */
+mxRectangle.prototype.width = null;
+
+/**
+ * Variable: height
+ *
+ * Holds the height of the rectangle. Default is 0.
+ */
+mxRectangle.prototype.height = null;
+
+/**
+ * Function: setRect
+ * 
+ * Sets this rectangle to the specified values
+ */
+mxRectangle.prototype.setRect = function(x, y, w, h)
+{
+    this.x = x;
+    this.y = y;
+    this.width = w;
+    this.height = h;
+};
+
+/**
+ * Function: getCenterX
+ * 
+ * Returns the x-coordinate of the center point.
+ */
+mxRectangle.prototype.getCenterX = function ()
+{
+	return this.x + this.width/2;
+};
+
+/**
+ * Function: getCenterY
+ * 
+ * Returns the y-coordinate of the center point.
+ */
+mxRectangle.prototype.getCenterY = function ()
+{
+	return this.y + this.height/2;
+};
+
+/**
+ * Function: add
+ *
+ * Adds the given rectangle to this rectangle.
+ */
+mxRectangle.prototype.add = function(rect)
+{
+	if (rect != null)
+	{
+		var minX = Math.min(this.x, rect.x);
+		var minY = Math.min(this.y, rect.y);
+		var maxX = Math.max(this.x + this.width, rect.x + rect.width);
+		var maxY = Math.max(this.y + this.height, rect.y + rect.height);
+		
+		this.x = minX;
+		this.y = minY;
+		this.width = maxX - minX;
+		this.height = maxY - minY;
+	}
+};
+
+/**
+ * Function: intersect
+ * 
+ * Changes this rectangle to where it overlaps with the given rectangle.
+ */
+mxRectangle.prototype.intersect = function(rect)
+{
+	if (rect != null)
+	{
+		var r1 = this.x + this.width;
+		var r2 = rect.x + rect.width;
+		
+		var b1 = this.y + this.height;
+		var b2 = rect.y + rect.height;
+		
+		this.x = Math.max(this.x, rect.x);
+		this.y = Math.max(this.y, rect.y);
+		this.width = Math.min(r1, r2) - this.x;
+		this.height = Math.min(b1, b2) - this.y;
+	}
+};
+
+/**
+ * Function: grow
+ *
+ * Grows the rectangle by the given amount, that is, this method subtracts
+ * the given amount from the x- and y-coordinates and adds twice the amount
+ * to the width and height.
+ */
+mxRectangle.prototype.grow = function(amount)
+{
+	this.x -= amount;
+	this.y -= amount;
+	this.width += 2 * amount;
+	this.height += 2 * amount;
+};
+
+/**
+ * Function: getPoint
+ * 
+ * Returns the top, left corner as a new <mxPoint>.
+ */
+mxRectangle.prototype.getPoint = function()
+{
+	return new mxPoint(this.x, this.y);
+};
+
+/**
+ * Function: rotate90
+ * 
+ * Rotates this rectangle by 90 degree around its center point.
+ */
+mxRectangle.prototype.rotate90 = function()
+{
+	var t = (this.width - this.height) / 2;
+	this.x += t;
+	this.y -= t;
+	var tmp = this.width;
+	this.width = this.height;
+	this.height = tmp;
+};
+
+/**
+ * Function: equals
+ * 
+ * Returns true if the given object equals this rectangle.
+ */
+mxRectangle.prototype.equals = function(obj)
+{
+	return obj != null && obj.x == this.x && obj.y == this.y &&
+		obj.width == this.width && obj.height == this.height;
+};
+
+/**
+ * Function: fromRectangle
+ * 
+ * Returns a new <mxRectangle> which is a copy of the given rectangle.
+ */
+mxRectangle.fromRectangle = function(rect)
+{
+	return new mxRectangle(rect.x, rect.y, rect.width, rect.height);
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/util/mxResources.js b/airavata-kubernetes/web-console/src/assets/js/util/mxResources.js
new file mode 100644
index 0000000..54612b6
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/util/mxResources.js
@@ -0,0 +1,450 @@
+/**
+ * Copyright (c) 2006-2016, JGraph Ltd
+ * Copyright (c) 2006-2016, Gaudenz Alder
+ */
+var mxResources =
+{
+	/**
+	 * Class: mxResources
+	 * 
+	 * Implements internationalization. You can provide any number of 
+	 * resource files on the server using the following format for the 
+	 * filename: name[-en].properties. The en stands for any lowercase 
+	 * 2-character language shortcut (eg. de for german, fr for french).
+	 *
+	 * If the optional language extension is omitted, then the file is used as a 
+	 * default resource which is loaded in all cases. If a properties file for a 
+	 * specific language exists, then it is used to override the settings in the 
+	 * default resource. All entries in the file are of the form key=value. The
+	 * values may then be accessed in code via <get>. Lines without 
+	 * equal signs in the properties files are ignored.
+	 *
+	 * Resource files may either be added programmatically using
+	 * <add> or via a resource tag in the UI section of the 
+	 * editor configuration file, eg:
+	 * 
+	 * (code)
+	 * <mxEditor>
+	 *   <ui>
+	 *     <resource basename="examples/resources/mxWorkflow"/>
+	 * (end)
+	 * 
+	 * The above element will load examples/resources/mxWorkflow.properties as well
+	 * as the language specific file for the current language, if it exists.
+	 * 
+	 * Values may contain placeholders of the form {1}...{n} where each placeholder
+	 * is replaced with the value of the corresponding array element in the params
+	 * argument passed to <mxResources.get>. The placeholder {1} maps to the first
+	 * element in the array (at index 0).
+	 * 
+	 * See <mxClient.language> for more information on specifying the default
+	 * language or disabling all loading of resources.
+	 * 
+	 * Lines that start with a # sign will be ignored.
+	 * 
+	 * Special characters
+	 * 
+	 * To use unicode characters, use the standard notation (eg. \u8fd1) or %u as a
+	 * prefix (eg. %u20AC will display a Euro sign). For normal hex encoded strings,
+	 * use % as a prefix, eg. %F6 will display a "o umlaut" (&ouml;).
+	 * 
+	 * See <resourcesEncoded> to disable this. If you disable this, make sure that
+	 * your files are UTF-8 encoded.
+	 * 
+	 * Asynchronous loading
+	 * 
+	 * By default, the core adds two resource files synchronously at load time.
+	 * To load these files asynchronously, set <mxLoadResources> to false
+	 * before loading mxClient.js and use <mxResources.loadResources> instead.
+	 * 
+	 * Variable: resources
+	 * 
+	 * Associative array that maps from keys to values.
+	 */
+	resources: [],
+
+	/**
+	 * Variable: extension
+	 * 
+	 * Specifies the extension used for language files. Default is <mxResourceExtension>.
+	 */
+	extension: mxResourceExtension,
+
+	/**
+	 * Variable: resourcesEncoded
+	 * 
+	 * Specifies whether or not values in resource files are encoded with \u or
+	 * percentage. Default is false.
+	 */
+	resourcesEncoded: false,
+
+	/**
+	 * Variable: loadDefaultBundle
+	 * 
+	 * Specifies if the default file for a given basename should be loaded.
+	 * Default is true.
+	 */
+	loadDefaultBundle: true,
+
+	/**
+	 * Variable: loadDefaultBundle
+	 * 
+	 * Specifies if the specific language file file for a given basename should
+	 * be loaded. Default is true.
+	 */
+	loadSpecialBundle: true,
+
+	/**
+	 * Function: isLanguageSupported
+	 * 
+	 * Hook for subclassers to disable support for a given language. This
+	 * implementation returns true if lan is in <mxClient.languages>.
+	 * 
+	 * Parameters:
+	 *
+	 * lan - The current language.
+	 */
+	isLanguageSupported: function(lan)
+	{
+		if (mxClient.languages != null)
+		{
+			return mxUtils.indexOf(mxClient.languages, lan) >= 0;
+		}
+		
+		return true;
+	},
+
+	/**
+	 * Function: getDefaultBundle
+	 * 
+	 * Hook for subclassers to return the URL for the special bundle. This
+	 * implementation returns basename + <extension> or null if
+	 * <loadDefaultBundle> is false.
+	 * 
+	 * Parameters:
+	 * 
+	 * basename - The basename for which the file should be loaded.
+	 * lan - The current language.
+	 */
+	getDefaultBundle: function(basename, lan)
+	{
+		if (mxResources.loadDefaultBundle || !mxResources.isLanguageSupported(lan))
+		{
+			return basename + mxResources.extension;
+		}
+		else
+		{
+			return null;
+		}
+	},
+
+	/**
+	 * Function: getSpecialBundle
+	 * 
+	 * Hook for subclassers to return the URL for the special bundle. This
+	 * implementation returns basename + '_' + lan + <extension> or null if
+	 * <loadSpecialBundle> is false or lan equals <mxClient.defaultLanguage>.
+	 * 
+	 * If <mxResources.languages> is not null and <mxClient.language> contains
+	 * a dash, then this method checks if <isLanguageSupported> returns true
+	 * for the full language (including the dash). If that returns false the
+	 * first part of the language (up to the dash) will be tried as an extension.
+	 * 
+	 * If <mxResources.language> is null then the first part of the language is
+	 * used to maintain backwards compatibility.
+	 * 
+	 * Parameters:
+	 * 
+	 * basename - The basename for which the file should be loaded.
+	 * lan - The language for which the file should be loaded.
+	 */
+	getSpecialBundle: function(basename, lan)
+	{
+		if (mxClient.languages == null || !this.isLanguageSupported(lan))
+		{
+			var dash = lan.indexOf('-');
+			
+			if (dash > 0)
+			{
+				lan = lan.substring(0, dash);
+			}
+		}
+
+		if (mxResources.loadSpecialBundle && mxResources.isLanguageSupported(lan) && lan != mxClient.defaultLanguage)
+		{
+			return basename + '_' + lan + mxResources.extension;
+		}
+		else
+		{
+			return null;
+		}
+	},
+
+	/**
+	 * Function: add
+	 * 
+	 * Adds the default and current language properties file for the specified
+	 * basename. Existing keys are overridden as new files are added. If no
+	 * callback is used then the request is synchronous.
+	 *
+	 * Example:
+	 * 
+	 * At application startup, additional resources may be 
+	 * added using the following code:
+	 * 
+	 * (code)
+	 * mxResources.add('resources/editor');
+	 * (end)
+	 * 
+	 * Parameters:
+	 * 
+	 * basename - The basename for which the file should be loaded.
+	 * lan - The language for which the file should be loaded.
+	 * callback - Optional callback for asynchronous loading.
+	 */
+	add: function(basename, lan, callback)
+	{
+		lan = (lan != null) ? lan : ((mxClient.language != null) ?
+			mxClient.language.toLowerCase() : mxConstants.NONE);
+		
+		if (lan != mxConstants.NONE)
+		{
+			var defaultBundle = mxResources.getDefaultBundle(basename, lan);
+			var specialBundle = mxResources.getSpecialBundle(basename, lan);
+			
+			var loadSpecialBundle = function()
+			{
+				if (specialBundle != null)
+				{
+					if (callback)
+					{
+						mxUtils.get(specialBundle, function(req)
+						{
+							mxResources.parse(req.getText());
+							callback();
+						}, function()
+						{
+							callback();
+						});
+					}
+					else
+					{
+						try
+						{
+					   		var req = mxUtils.load(specialBundle);
+					   		
+					   		if (req.isReady())
+					   		{
+					 	   		mxResources.parse(req.getText());
+					   		}
+				   		}
+				   		catch (e)
+				   		{
+				   			// ignore
+					   	}
+					}
+				}
+				else if (callback != null)
+				{
+					callback();
+				}
+			}
+			
+			if (defaultBundle != null)
+			{
+				if (callback)
+				{
+					mxUtils.get(defaultBundle, function(req)
+					{
+						mxResources.parse(req.getText());
+						loadSpecialBundle();
+					}, function()
+					{
+						loadSpecialBundle();
+					});
+				}
+				else
+				{
+					try
+					{
+				   		var req = mxUtils.load(defaultBundle);
+				   		
+				   		if (req.isReady())
+				   		{
+				 	   		mxResources.parse(req.getText());
+				   		}
+				   		
+				   		loadSpecialBundle();
+				  	}
+				  	catch (e)
+				  	{
+				  		// ignore
+				  	}
+				}
+			}
+			else
+			{
+				// Overlays the language specific file (_lan-extension)
+				loadSpecialBundle();
+			}
+		}
+	},
+
+	/**
+	 * Function: parse
+	 * 
+	 * Parses the key, value pairs in the specified
+	 * text and stores them as local resources.
+	 */
+	parse: function(text)
+	{
+		if (text != null)
+		{
+			var lines = text.split('\n');
+			
+			for (var i = 0; i < lines.length; i++)
+			{
+				if (lines[i].charAt(0) != '#')
+				{
+					var index = lines[i].indexOf('=');
+					
+					if (index > 0)
+					{
+						var key = lines[i].substring(0, index);
+						var idx = lines[i].length;
+						
+						if (lines[i].charCodeAt(idx - 1) == 13)
+						{
+							idx--;
+						}
+						
+						var value = lines[i].substring(index + 1, idx);
+						
+						if (this.resourcesEncoded)
+						{
+							value = value.replace(/\\(?=u[a-fA-F\d]{4})/g,"%");
+							mxResources.resources[key] = unescape(value);
+						}
+						else
+						{
+							mxResources.resources[key] = value;
+						}
+					}
+				}
+			}
+		}
+	},
+
+	/**
+	 * Function: get
+	 * 
+	 * Returns the value for the specified resource key.
+	 *
+	 * Example:
+	 * To read the value for 'welomeMessage', use the following:
+	 * (code)
+	 * var result = mxResources.get('welcomeMessage') || '';
+	 * (end)
+	 *
+	 * This would require an entry of the following form in
+	 * one of the English language resource files:
+	 * (code)
+	 * welcomeMessage=Welcome to mxGraph!
+	 * (end)
+	 * 
+	 * The part behind the || is the string value to be used if the given
+	 * resource is not available.
+	 * 
+	 * Parameters:
+	 * 
+	 * key - String that represents the key of the resource to be returned.
+	 * params - Array of the values for the placeholders of the form {1}...{n}
+	 * to be replaced with in the resulting string.
+	 * defaultValue - Optional string that specifies the default return value.
+	 */
+	get: function(key, params, defaultValue)
+	{
+		var value = mxResources.resources[key];
+		
+		// Applies the default value if no resource was found
+		if (value == null)
+		{
+			value = defaultValue;
+		}
+		
+		// Replaces the placeholders with the values in the array
+		if (value != null && params != null)
+		{
+			value = mxResources.replacePlaceholders(value, params);
+		}
+		
+		return value;
+	},
+
+	/**
+	 * Function: replacePlaceholders
+	 * 
+	 * Replaces the given placeholders with the given parameters.
+	 * 
+	 * Parameters:
+	 * 
+	 * value - String that contains the placeholders.
+	 * params - Array of the values for the placeholders of the form {1}...{n}
+	 * to be replaced with in the resulting string.
+	 */
+	replacePlaceholders: function(value, params)
+	{
+		var result = [];
+		var index = null;
+		
+		for (var i = 0; i < value.length; i++)
+		{
+			var c = value.charAt(i);
+
+			if (c == '{')
+			{
+				index = '';
+			}
+			else if (index != null && 	c == '}')
+			{
+				index = parseInt(index)-1;
+				
+				if (index >= 0 && index < params.length)
+				{
+					result.push(params[index]);
+				}
+				
+				index = null;
+			}
+			else if (index != null)
+			{
+				index += c;
+			}
+			else
+			{
+				result.push(c);
+			}
+		}
+		
+		return result.join('');
+	},
+
+	/**
+	 * Function: loadResources
+	 * 
+	 * Loads all required resources asynchronously. Use this to load the graph and
+	 * editor resources if <mxLoadResources> is false.
+	 * 
+	 * Parameters:
+	 * 
+	 * callback - Callback function for asynchronous loading.
+	 */
+	loadResources: function(callback)
+	{
+		mxResources.add(mxClient.basePath+'/resources/editor', null, function()
+		{
+			mxResources.add(mxClient.basePath+'/resources/graph', null, callback);
+		});
+	}
+
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/util/mxSvgCanvas2D.js b/airavata-kubernetes/web-console/src/assets/js/util/mxSvgCanvas2D.js
new file mode 100644
index 0000000..28ae6d9
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/util/mxSvgCanvas2D.js
@@ -0,0 +1,2179 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxSvgCanvas2D
+ *
+ * Extends <mxAbstractCanvas2D> to implement a canvas for SVG. This canvas writes all
+ * calls as SVG output to the given SVG root node.
+ * 
+ * (code)
+ * var svgDoc = mxUtils.createXmlDocument();
+ * var root = (svgDoc.createElementNS != null) ?
+ * 		svgDoc.createElementNS(mxConstants.NS_SVG, 'svg') : svgDoc.createElement('svg');
+ * 
+ * if (svgDoc.createElementNS == null)
+ * {
+ *   root.setAttribute('xmlns', mxConstants.NS_SVG);
+ *   root.setAttribute('xmlns:xlink', mxConstants.NS_XLINK);
+ * }
+ * else
+ * {
+ *   root.setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xlink', mxConstants.NS_XLINK);
+ * }
+ * 
+ * var bounds = graph.getGraphBounds();
+ * root.setAttribute('width', (bounds.x + bounds.width + 4) + 'px');
+ * root.setAttribute('height', (bounds.y + bounds.height + 4) + 'px');
+ * root.setAttribute('version', '1.1');
+ * 
+ * svgDoc.appendChild(root);
+ * 
+ * var svgCanvas = new mxSvgCanvas2D(root);
+ * (end)
+ * 
+ * A description of the public API is available in <mxXmlCanvas2D>.
+ * 
+ * To disable anti-aliasing in the output, use the following code.
+ * 
+ * (code)
+ * graph.view.canvas.ownerSVGElement.setAttribute('shape-rendering', 'crispEdges');
+ * (end)
+ * 
+ * Or set the respective attribute in the SVG element directly.
+ * 
+ * Constructor: mxSvgCanvas2D
+ *
+ * Constructs a new SVG canvas.
+ * 
+ * Parameters:
+ * 
+ * root - SVG container for the output.
+ * styleEnabled - Optional boolean that specifies if a style section should be
+ * added. The style section sets the default font-size, font-family and
+ * stroke-miterlimit globally. Default is false.
+ */
+function mxSvgCanvas2D(root, styleEnabled)
+{
+	mxAbstractCanvas2D.call(this);
+
+	/**
+	 * Variable: root
+	 * 
+	 * Reference to the container for the SVG content.
+	 */
+	this.root = root;
+
+	/**
+	 * Variable: gradients
+	 * 
+	 * Local cache of gradients for quick lookups.
+	 */
+	this.gradients = [];
+
+	/**
+	 * Variable: defs
+	 * 
+	 * Reference to the defs section of the SVG document. Only for export.
+	 */
+	this.defs = null;
+	
+	/**
+	 * Variable: styleEnabled
+	 * 
+	 * Stores the value of styleEnabled passed to the constructor.
+	 */
+	this.styleEnabled = (styleEnabled != null) ? styleEnabled : false;
+	
+	var svg = null;
+	
+	// Adds optional defs section for export
+	if (root.ownerDocument != document)
+	{
+		var node = root;
+
+		// Finds owner SVG element in XML DOM
+		while (node != null && node.nodeName != 'svg')
+		{
+			node = node.parentNode;
+		}
+		
+		svg = node;
+	}
+
+	if (svg != null)
+	{
+		// Tries to get existing defs section
+		var tmp = svg.getElementsByTagName('defs');
+		
+		if (tmp.length > 0)
+		{
+			this.defs = svg.getElementsByTagName('defs')[0];
+		}
+		
+		// Adds defs section if none exists
+		if (this.defs == null)
+		{
+			this.defs = this.createElement('defs');
+			
+			if (svg.firstChild != null)
+			{
+				svg.insertBefore(this.defs, svg.firstChild);
+			}
+			else
+			{
+				svg.appendChild(this.defs);
+			}
+		}
+
+		// Adds stylesheet
+		if (this.styleEnabled)
+		{
+			this.defs.appendChild(this.createStyle());
+		}
+	}
+};
+
+/**
+ * Extends mxAbstractCanvas2D
+ */
+mxUtils.extend(mxSvgCanvas2D, mxAbstractCanvas2D);
+
+/**
+ * Capability check for DOM parser.
+ */
+(function()
+{
+	mxSvgCanvas2D.prototype.useDomParser = !mxClient.IS_IE && typeof DOMParser === 'function' && typeof XMLSerializer === 'function';
+	
+	if (mxSvgCanvas2D.prototype.useDomParser)
+	{
+		// Checks using a generic test text if the parsing actually works. This is a workaround
+		// for older browsers where the capability check returns true but the parsing fails.
+		try
+		{
+			var doc = new DOMParser().parseFromString('test text', 'text/html');
+			mxSvgCanvas2D.prototype.useDomParser = doc != null;
+		}
+		catch (e)
+		{
+			mxSvgCanvas2D.prototype.useDomParser = false;
+		}
+	}
+})();
+
+/**
+ * Variable: path
+ * 
+ * Holds the current DOM node.
+ */
+mxSvgCanvas2D.prototype.node = null;
+
+/**
+ * Variable: matchHtmlAlignment
+ * 
+ * Specifies if plain text output should match the vertical HTML alignment.
+ * Defaul is true.
+ */
+mxSvgCanvas2D.prototype.matchHtmlAlignment = true;
+
+/**
+ * Variable: textEnabled
+ * 
+ * Specifies if text output should be enabled. Default is true.
+ */
+mxSvgCanvas2D.prototype.textEnabled = true;
+
+/**
+ * Variable: foEnabled
+ * 
+ * Specifies if use of foreignObject for HTML markup is allowed. Default is true.
+ */
+mxSvgCanvas2D.prototype.foEnabled = true;
+
+/**
+ * Variable: foAltText
+ * 
+ * Specifies the fallback text for unsupported foreignObjects in exported
+ * documents. Default is '[Object]'. If this is set to null then no fallback
+ * text is added to the exported document.
+ */
+mxSvgCanvas2D.prototype.foAltText = '[Object]';
+
+/**
+ * Variable: foOffset
+ * 
+ * Offset to be used for foreignObjects.
+ */
+mxSvgCanvas2D.prototype.foOffset = 0;
+
+/**
+ * Variable: textOffset
+ * 
+ * Offset to be used for text elements.
+ */
+mxSvgCanvas2D.prototype.textOffset = 0;
+
+/**
+ * Variable: imageOffset
+ * 
+ * Offset to be used for image elements.
+ */
+mxSvgCanvas2D.prototype.imageOffset = 0;
+
+/**
+ * Variable: strokeTolerance
+ * 
+ * Adds transparent paths for strokes.
+ */
+mxSvgCanvas2D.prototype.strokeTolerance = 0;
+
+/**
+ * Variable: refCount
+ * 
+ * Local counter for references in SVG export.
+ */
+mxSvgCanvas2D.prototype.refCount = 0;
+
+/**
+ * Variable: blockImagePointerEvents
+ * 
+ * Specifies if a transparent rectangle should be added on top of images to absorb
+ * all pointer events. Default is false. This is only needed in Firefox to disable
+ * control-clicks on images.
+ */
+mxSvgCanvas2D.prototype.blockImagePointerEvents = false;
+
+/**
+ * Variable: lineHeightCorrection
+ * 
+ * Correction factor for <mxConstants.LINE_HEIGHT> in HTML output. Default is 1.
+ */
+mxSvgCanvas2D.prototype.lineHeightCorrection = 1;
+
+/**
+ * Variable: pointerEventsValue
+ * 
+ * Default value for active pointer events. Default is all.
+ */
+mxSvgCanvas2D.prototype.pointerEventsValue = 'all';
+
+/**
+ * Variable: fontMetricsPadding
+ * 
+ * Padding to be added for text that is not wrapped to account for differences
+ * in font metrics on different platforms in pixels. Default is 10.
+ */
+mxSvgCanvas2D.prototype.fontMetricsPadding = 10;
+
+/**
+ * Variable: cacheOffsetSize
+ * 
+ * Specifies if offsetWidth and offsetHeight should be cached. Default is true.
+ * This is used to speed up repaint of text in <updateText>.
+ */
+mxSvgCanvas2D.prototype.cacheOffsetSize = true;
+
+/**
+ * Function: format
+ * 
+ * Rounds all numbers to 2 decimal points.
+ */
+mxSvgCanvas2D.prototype.format = function(value)
+{
+	return parseFloat(parseFloat(value).toFixed(2));
+};
+
+/**
+ * Function: getBaseUrl
+ * 
+ * Returns the URL of the page without the hash part. This needs to use href to
+ * include any search part with no params (ie question mark alone). This is a
+ * workaround for the fact that window.location.search is empty if there is
+ * no search string behind the question mark.
+ */
+mxSvgCanvas2D.prototype.getBaseUrl = function()
+{
+	var href = window.location.href;
+	var hash = href.lastIndexOf('#');
+	
+	if (hash > 0)
+	{
+		href = href.substring(0, hash);
+	}
+	
+	return href;
+};
+
+/**
+ * Function: reset
+ * 
+ * Returns any offsets for rendering pixels.
+ */
+mxSvgCanvas2D.prototype.reset = function()
+{
+	mxAbstractCanvas2D.prototype.reset.apply(this, arguments);
+	this.gradients = [];
+};
+
+/**
+ * Function: createStyle
+ * 
+ * Creates the optional style section.
+ */
+mxSvgCanvas2D.prototype.createStyle = function(x)
+{
+	var style = this.createElement('style');
+	style.setAttribute('type', 'text/css');
+	mxUtils.write(style, 'svg{font-family:' + mxConstants.DEFAULT_FONTFAMILY +
+			';font-size:' + mxConstants.DEFAULT_FONTSIZE +
+			';fill:none;stroke-miterlimit:10}');
+	
+	return style;
+};
+
+/**
+ * Function: createElement
+ * 
+ * Private helper function to create SVG elements
+ */
+mxSvgCanvas2D.prototype.createElement = function(tagName, namespace)
+{
+	if (this.root.ownerDocument.createElementNS != null)
+	{
+		return this.root.ownerDocument.createElementNS(namespace || mxConstants.NS_SVG, tagName);
+	}
+	else
+	{
+		var elt = this.root.ownerDocument.createElement(tagName);
+		
+		if (namespace != null)
+		{
+			elt.setAttribute('xmlns', namespace);
+		}
+		
+		return elt;
+	}
+};
+
+/**
+ * Function: getAlternateContent
+ * 
+ * Returns the alternate content for the given foreignObject.
+ */
+mxSvgCanvas2D.prototype.createAlternateContent = function(fo, x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation)
+{
+	if (this.foAltText != null)
+	{
+		var s = this.state;
+		var alt = this.createElement('text');
+		alt.setAttribute('x', Math.round(w / 2));
+		alt.setAttribute('y', Math.round((h + s.fontSize) / 2));
+		alt.setAttribute('fill', s.fontColor || 'black');
+		alt.setAttribute('text-anchor', 'middle');
+		alt.setAttribute('font-size', s.fontSize + 'px');
+		alt.setAttribute('font-family', s.fontFamily);
+		
+		if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
+		{
+			alt.setAttribute('font-weight', 'bold');
+		}
+		
+		if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
+		{
+			alt.setAttribute('font-style', 'italic');
+		}
+		
+		if ((s.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)
+		{
+			alt.setAttribute('text-decoration', 'underline');
+		}
+		
+		mxUtils.write(alt, this.foAltText);
+		
+		return alt;
+	}
+	else
+	{
+		return null;
+	}
+};
+
+/**
+ * Function: createGradientId
+ * 
+ * Private helper function to create SVG elements
+ */
+mxSvgCanvas2D.prototype.createGradientId = function(start, end, alpha1, alpha2, direction)
+{
+	// Removes illegal characters from gradient ID
+	if (start.charAt(0) == '#')
+	{
+		start = start.substring(1);
+	}
+	
+	if (end.charAt(0) == '#')
+	{
+		end = end.substring(1);
+	}
+	
+	// Workaround for gradient IDs not working in Safari 5 / Chrome 6
+	// if they contain uppercase characters
+	start = start.toLowerCase() + '-' + alpha1;
+	end = end.toLowerCase() + '-' + alpha2;
+
+	// Wrong gradient directions possible?
+	var dir = null;
+	
+	if (direction == null || direction == mxConstants.DIRECTION_SOUTH)
+	{
+		dir = 's';
+	}
+	else if (direction == mxConstants.DIRECTION_EAST)
+	{
+		dir = 'e';
+	}
+	else
+	{
+		var tmp = start;
+		start = end;
+		end = tmp;
+		
+		if (direction == mxConstants.DIRECTION_NORTH)
+		{
+			dir = 's';
+		}
+		else if (direction == mxConstants.DIRECTION_WEST)
+		{
+			dir = 'e';
+		}
+	}
+	
+	return 'mx-gradient-' + start + '-' + end + '-' + dir;
+};
+
+/**
+ * Function: getSvgGradient
+ * 
+ * Private helper function to create SVG elements
+ */
+mxSvgCanvas2D.prototype.getSvgGradient = function(start, end, alpha1, alpha2, direction)
+{
+	var id = this.createGradientId(start, end, alpha1, alpha2, direction);
+	var gradient = this.gradients[id];
+	
+	if (gradient == null)
+	{
+		var svg = this.root.ownerSVGElement;
+
+		var counter = 0;
+		var tmpId = id + '-' + counter;
+
+		if (svg != null)
+		{
+			gradient = svg.ownerDocument.getElementById(tmpId);
+			
+			while (gradient != null && gradient.ownerSVGElement != svg)
+			{
+				tmpId = id + '-' + counter++;
+				gradient = svg.ownerDocument.getElementById(tmpId);
+			}
+		}
+		else
+		{
+			// Uses shorter IDs for export
+			tmpId = 'id' + (++this.refCount);
+		}
+		
+		if (gradient == null)
+		{
+			gradient = this.createSvgGradient(start, end, alpha1, alpha2, direction);
+			gradient.setAttribute('id', tmpId);
+			
+			if (this.defs != null)
+			{
+				this.defs.appendChild(gradient);
+			}
+			else
+			{
+				svg.appendChild(gradient);
+			}
+		}
+
+		this.gradients[id] = gradient;
+	}
+
+	return gradient.getAttribute('id');
+};
+
+/**
+ * Function: createSvgGradient
+ * 
+ * Creates the given SVG gradient.
+ */
+mxSvgCanvas2D.prototype.createSvgGradient = function(start, end, alpha1, alpha2, direction)
+{
+	var gradient = this.createElement('linearGradient');
+	gradient.setAttribute('x1', '0%');
+	gradient.setAttribute('y1', '0%');
+	gradient.setAttribute('x2', '0%');
+	gradient.setAttribute('y2', '0%');
+	
+	if (direction == null || direction == mxConstants.DIRECTION_SOUTH)
+	{
+		gradient.setAttribute('y2', '100%');
+	}
+	else if (direction == mxConstants.DIRECTION_EAST)
+	{
+		gradient.setAttribute('x2', '100%');
+	}
+	else if (direction == mxConstants.DIRECTION_NORTH)
+	{
+		gradient.setAttribute('y1', '100%');
+	}
+	else if (direction == mxConstants.DIRECTION_WEST)
+	{
+		gradient.setAttribute('x1', '100%');
+	}
+	
+	var op = (alpha1 < 1) ? ';stop-opacity:' + alpha1 : '';
+	
+	var stop = this.createElement('stop');
+	stop.setAttribute('offset', '0%');
+	stop.setAttribute('style', 'stop-color:' + start + op);
+	gradient.appendChild(stop);
+	
+	op = (alpha2 < 1) ? ';stop-opacity:' + alpha2 : '';
+	
+	stop = this.createElement('stop');
+	stop.setAttribute('offset', '100%');
+	stop.setAttribute('style', 'stop-color:' + end + op);
+	gradient.appendChild(stop);
+	
+	return gradient;
+};
+
+/**
+ * Function: addNode
+ * 
+ * Private helper function to create SVG elements
+ */
+mxSvgCanvas2D.prototype.addNode = function(filled, stroked)
+{
+	var node = this.node;
+	var s = this.state;
+
+	if (node != null)
+	{
+		if (node.nodeName == 'path')
+		{
+			// Checks if the path is not empty
+			if (this.path != null && this.path.length > 0)
+			{
+				node.setAttribute('d', this.path.join(' '));
+			}
+			else
+			{
+				return;
+			}
+		}
+
+		if (filled && s.fillColor != null)
+		{
+			this.updateFill();
+		}
+		else if (!this.styleEnabled)
+		{
+			// Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=814952
+			if (node.nodeName == 'ellipse' && mxClient.IS_FF)
+			{
+				node.setAttribute('fill', 'transparent');
+			}
+			else
+			{
+				node.setAttribute('fill', 'none');
+			}
+			
+			// Sets the actual filled state for stroke tolerance
+			filled = false;
+		}
+		
+		if (stroked && s.strokeColor != null)
+		{
+			this.updateStroke();
+		}
+		else if (!this.styleEnabled)
+		{
+			node.setAttribute('stroke', 'none');
+		}
+		
+		if (s.transform != null && s.transform.length > 0)
+		{
+			node.setAttribute('transform', s.transform);
+		}
+		
+		if (s.shadow)
+		{
+			this.root.appendChild(this.createShadow(node));
+		}
+	
+		// Adds stroke tolerance
+		if (this.strokeTolerance > 0 && !filled)
+		{
+			this.root.appendChild(this.createTolerance(node));
+		}
+
+		// Adds pointer events
+		if (this.pointerEvents && (node.nodeName != 'path' ||
+			this.path[this.path.length - 1] == this.closeOp))
+		{
+			node.setAttribute('pointer-events', this.pointerEventsValue);
+		}
+		// Enables clicks for nodes inside a link element
+		else if (!this.pointerEvents && this.originalRoot == null)
+		{
+			node.setAttribute('pointer-events', 'none');
+		}
+		
+		// Removes invisible nodes from output if they don't handle events
+		if ((node.nodeName != 'rect' && node.nodeName != 'path' && node.nodeName != 'ellipse') ||
+			(node.getAttribute('fill') != 'none' && node.getAttribute('fill') != 'transparent') ||
+			node.getAttribute('stroke') != 'none' || node.getAttribute('pointer-events') != 'none')
+		{
+			// LATER: Update existing DOM for performance		
+			this.root.appendChild(node);
+		}
+		
+		this.node = null;
+	}
+};
+
+/**
+ * Function: updateFill
+ * 
+ * Transfers the stroke attributes from <state> to <node>.
+ */
+mxSvgCanvas2D.prototype.updateFill = function()
+{
+	var s = this.state;
+	
+	if (s.alpha < 1 || s.fillAlpha < 1)
+	{
+		this.node.setAttribute('fill-opacity', s.alpha * s.fillAlpha);
+	}
+	
+	if (s.fillColor != null)
+	{
+		if (s.gradientColor != null)
+		{
+			var id = this.getSvgGradient(s.fillColor, s.gradientColor, s.gradientFillAlpha, s.gradientAlpha, s.gradientDirection);
+			
+			if (!mxClient.IS_CHROME_APP && !mxClient.IS_IE && !mxClient.IS_IE11 &&
+				!mxClient.IS_EDGE && this.root.ownerDocument == document)
+			{
+				// Workaround for potential base tag and brackets must be escaped
+				var base = this.getBaseUrl().replace(/([\(\)])/g, '\\$1');
+				this.node.setAttribute('fill', 'url(' + base + '#' + id + ')');
+			}
+			else
+			{
+				this.node.setAttribute('fill', 'url(#' + id + ')');
+			}
+		}
+		else
+		{
+			this.node.setAttribute('fill', s.fillColor.toLowerCase());
+		}
+	}
+};
+
+/**
+ * Function: getCurrentStrokeWidth
+ * 
+ * Returns the current stroke width (>= 1), ie. max(1, this.format(this.state.strokeWidth * this.state.scale)).
+ */
+mxSvgCanvas2D.prototype.getCurrentStrokeWidth = function()
+{
+	return Math.max(1, this.format(this.state.strokeWidth * this.state.scale));
+};
+
+/**
+ * Function: updateStroke
+ * 
+ * Transfers the stroke attributes from <state> to <node>.
+ */
+mxSvgCanvas2D.prototype.updateStroke = function()
+{
+	var s = this.state;
+
+	this.node.setAttribute('stroke', s.strokeColor.toLowerCase());
+	
+	if (s.alpha < 1 || s.strokeAlpha < 1)
+	{
+		this.node.setAttribute('stroke-opacity', s.alpha * s.strokeAlpha);
+	}
+	
+	var sw = this.getCurrentStrokeWidth();
+	
+	if (sw != 1)
+	{
+		this.node.setAttribute('stroke-width', sw);
+	}
+	
+	if (this.node.nodeName == 'path')
+	{
+		this.updateStrokeAttributes();
+	}
+	
+	if (s.dashed)
+	{
+		this.node.setAttribute('stroke-dasharray', this.createDashPattern(
+			((s.fixDash) ? 1 : s.strokeWidth) * s.scale));
+	}
+};
+
+/**
+ * Function: updateStrokeAttributes
+ * 
+ * Transfers the stroke attributes from <state> to <node>.
+ */
+mxSvgCanvas2D.prototype.updateStrokeAttributes = function()
+{
+	var s = this.state;
+	
+	// Linejoin miter is default in SVG
+	if (s.lineJoin != null && s.lineJoin != 'miter')
+	{
+		this.node.setAttribute('stroke-linejoin', s.lineJoin);
+	}
+	
+	if (s.lineCap != null)
+	{
+		// flat is called butt in SVG
+		var value = s.lineCap;
+		
+		if (value == 'flat')
+		{
+			value = 'butt';
+		}
+		
+		// Linecap butt is default in SVG
+		if (value != 'butt')
+		{
+			this.node.setAttribute('stroke-linecap', value);
+		}
+	}
+	
+	// Miterlimit 10 is default in our document
+	if (s.miterLimit != null && (!this.styleEnabled || s.miterLimit != 10))
+	{
+		this.node.setAttribute('stroke-miterlimit', s.miterLimit);
+	}
+};
+
+/**
+ * Function: createDashPattern
+ * 
+ * Creates the SVG dash pattern for the given state.
+ */
+mxSvgCanvas2D.prototype.createDashPattern = function(scale)
+{
+	var pat = [];
+	
+	if (typeof(this.state.dashPattern) === 'string')
+	{
+		var dash = this.state.dashPattern.split(' ');
+		
+		if (dash.length > 0)
+		{
+			for (var i = 0; i < dash.length; i++)
+			{
+				pat[i] = Number(dash[i]) * scale;
+			}
+		}
+	}
+	
+	return pat.join(' ');
+};
+
+/**
+ * Function: createTolerance
+ * 
+ * Creates a hit detection tolerance shape for the given node.
+ */
+mxSvgCanvas2D.prototype.createTolerance = function(node)
+{
+	var tol = node.cloneNode(true);
+	var sw = parseFloat(tol.getAttribute('stroke-width') || 1) + this.strokeTolerance;
+	tol.setAttribute('pointer-events', 'stroke');
+	tol.setAttribute('visibility', 'hidden');
+	tol.removeAttribute('stroke-dasharray');
+	tol.setAttribute('stroke-width', sw);
+	tol.setAttribute('fill', 'none');
+	
+	// Workaround for Opera ignoring the visiblity attribute above while
+	// other browsers need a stroke color to perform the hit-detection but
+	// do not ignore the visibility attribute. Side-effect is that Opera's
+	// hit detection for horizontal/vertical edges seems to ignore the tol.
+	tol.setAttribute('stroke', (mxClient.IS_OT) ? 'none' : 'white');
+	
+	return tol;
+};
+
+/**
+ * Function: createShadow
+ * 
+ * Creates a shadow for the given node.
+ */
+mxSvgCanvas2D.prototype.createShadow = function(node)
+{
+	var shadow = node.cloneNode(true);
+	var s = this.state;
+
+	// Firefox uses transparent for no fill in ellipses
+	if (shadow.getAttribute('fill') != 'none' && (!mxClient.IS_FF || shadow.getAttribute('fill') != 'transparent'))
+	{
+		shadow.setAttribute('fill', s.shadowColor);
+	}
+	
+	if (shadow.getAttribute('stroke') != 'none')
+	{
+		shadow.setAttribute('stroke', s.shadowColor);
+	}
+
+	shadow.setAttribute('transform', 'translate(' + this.format(s.shadowDx * s.scale) +
+		',' + this.format(s.shadowDy * s.scale) + ')' + (s.transform || ''));
+	shadow.setAttribute('opacity', s.shadowAlpha);
+	
+	return shadow;
+};
+
+/**
+ * Function: setLink
+ * 
+ * Experimental implementation for hyperlinks.
+ */
+mxSvgCanvas2D.prototype.setLink = function(link)
+{
+	if (link == null)
+	{
+		this.root = this.originalRoot;
+	}
+	else
+	{
+		this.originalRoot = this.root;
+		
+		var node = this.createElement('a');
+		
+		// Workaround for implicit namespace handling in HTML5 export, IE adds NS1 namespace so use code below
+		// in all IE versions except quirks mode. KNOWN: Adds xlink namespace to each image tag in output.
+		if (node.setAttributeNS == null || (this.root.ownerDocument != document && document.documentMode == null))
+		{
+			node.setAttribute('xlink:href', link);
+		}
+		else
+		{
+			node.setAttributeNS(mxConstants.NS_XLINK, 'xlink:href', link);
+		}
+		
+		this.root.appendChild(node);
+		this.root = node;
+	}
+};
+
+/**
+ * Function: rotate
+ * 
+ * Sets the rotation of the canvas. Note that rotation cannot be concatenated.
+ */
+mxSvgCanvas2D.prototype.rotate = function(theta, flipH, flipV, cx, cy)
+{
+	if (theta != 0 || flipH || flipV)
+	{
+		var s = this.state;
+		cx += s.dx;
+		cy += s.dy;
+	
+		cx *= s.scale;
+		cy *= s.scale;
+
+		s.transform = s.transform || '';
+		
+		// This implementation uses custom scale/translate and built-in rotation
+		// Rotation state is part of the AffineTransform in state.transform
+		if (flipH && flipV)
+		{
+			theta += 180;
+		}
+		else if (flipH != flipV)
+		{
+			var tx = (flipH) ? cx : 0;
+			var sx = (flipH) ? -1 : 1;
+	
+			var ty = (flipV) ? cy : 0;
+			var sy = (flipV) ? -1 : 1;
+
+			s.transform += 'translate(' + this.format(tx) + ',' + this.format(ty) + ')' +
+				'scale(' + this.format(sx) + ',' + this.format(sy) + ')' +
+				'translate(' + this.format(-tx) + ',' + this.format(-ty) + ')';
+		}
+		
+		if (flipH ? !flipV : flipV)
+		{
+			theta *= -1;
+		}
+		
+		if (theta != 0)
+		{
+			s.transform += 'rotate(' + this.format(theta) + ',' + this.format(cx) + ',' + this.format(cy) + ')';
+		}
+		
+		s.rotation = s.rotation + theta;
+		s.rotationCx = cx;
+		s.rotationCy = cy;
+	}
+};
+
+/**
+ * Function: begin
+ * 
+ * Extends superclass to create path.
+ */
+mxSvgCanvas2D.prototype.begin = function()
+{
+	mxAbstractCanvas2D.prototype.begin.apply(this, arguments);
+	this.node = this.createElement('path');
+};
+
+/**
+ * Function: rect
+ * 
+ * Private helper function to create SVG elements
+ */
+mxSvgCanvas2D.prototype.rect = function(x, y, w, h)
+{
+	var s = this.state;
+	var n = this.createElement('rect');
+	n.setAttribute('x', this.format((x + s.dx) * s.scale));
+	n.setAttribute('y', this.format((y + s.dy) * s.scale));
+	n.setAttribute('width', this.format(w * s.scale));
+	n.setAttribute('height', this.format(h * s.scale));
+	
+	this.node = n;
+};
+
+/**
+ * Function: roundrect
+ * 
+ * Private helper function to create SVG elements
+ */
+mxSvgCanvas2D.prototype.roundrect = function(x, y, w, h, dx, dy)
+{
+	this.rect(x, y, w, h);
+	
+	if (dx > 0)
+	{
+		this.node.setAttribute('rx', this.format(dx * this.state.scale));
+	}
+	
+	if (dy > 0)
+	{
+		this.node.setAttribute('ry', this.format(dy * this.state.scale));
+	}
+};
+
+/**
+ * Function: ellipse
+ * 
+ * Private helper function to create SVG elements
+ */
+mxSvgCanvas2D.prototype.ellipse = function(x, y, w, h)
+{
+	var s = this.state;
+	var n = this.createElement('ellipse');
+	// No rounding for consistent output with 1.x
+	n.setAttribute('cx', Math.round((x + w / 2 + s.dx) * s.scale));
+	n.setAttribute('cy', Math.round((y + h / 2 + s.dy) * s.scale));
+	n.setAttribute('rx', w / 2 * s.scale);
+	n.setAttribute('ry', h / 2 * s.scale);
+	this.node = n;
+};
+
+/**
+ * Function: image
+ * 
+ * Private helper function to create SVG elements
+ */
+mxSvgCanvas2D.prototype.image = function(x, y, w, h, src, aspect, flipH, flipV)
+{
+	src = this.converter.convert(src);
+	
+	// LATER: Add option for embedding images as base64.
+	aspect = (aspect != null) ? aspect : true;
+	flipH = (flipH != null) ? flipH : false;
+	flipV = (flipV != null) ? flipV : false;
+	
+	var s = this.state;
+	x += s.dx;
+	y += s.dy;
+	
+	var node = this.createElement('image');
+	node.setAttribute('x', this.format(x * s.scale) + this.imageOffset);
+	node.setAttribute('y', this.format(y * s.scale) + this.imageOffset);
+	node.setAttribute('width', this.format(w * s.scale));
+	node.setAttribute('height', this.format(h * s.scale));
+	
+	// Workaround for missing namespace support
+	if (node.setAttributeNS == null)
+	{
+		node.setAttribute('xlink:href', src);
+	}
+	else
+	{
+		node.setAttributeNS(mxConstants.NS_XLINK, 'xlink:href', src);
+	}
+	
+	if (!aspect)
+	{
+		node.setAttribute('preserveAspectRatio', 'none');
+	}
+
+	if (s.alpha < 1 || s.fillAlpha < 1)
+	{
+		node.setAttribute('opacity', s.alpha * s.fillAlpha);
+	}
+	
+	var tr = this.state.transform || '';
+	
+	if (flipH || flipV)
+	{
+		var sx = 1;
+		var sy = 1;
+		var dx = 0;
+		var dy = 0;
+		
+		if (flipH)
+		{
+			sx = -1;
+			dx = -w - 2 * x;
+		}
+		
+		if (flipV)
+		{
+			sy = -1;
+			dy = -h - 2 * y;
+		}
+		
+		// Adds image tansformation to existing transform
+		tr += 'scale(' + sx + ',' + sy + ')translate(' + (dx * s.scale) + ',' + (dy * s.scale) + ')';
+	}
+
+	if (tr.length > 0)
+	{
+		node.setAttribute('transform', tr);
+	}
+	
+	if (!this.pointerEvents)
+	{
+		node.setAttribute('pointer-events', 'none');
+	}
+	
+	this.root.appendChild(node);
+	
+	// Disables control-clicks on images in Firefox to open in new tab
+	// by putting a rect in the foreground that absorbs all events and
+	// disabling all pointer-events on the original image tag.
+	if (this.blockImagePointerEvents)
+	{
+		node.setAttribute('style', 'pointer-events:none');
+		
+		node = this.createElement('rect');
+		node.setAttribute('visibility', 'hidden');
+		node.setAttribute('pointer-events', 'fill');
+		node.setAttribute('x', this.format(x * s.scale));
+		node.setAttribute('y', this.format(y * s.scale));
+		node.setAttribute('width', this.format(w * s.scale));
+		node.setAttribute('height', this.format(h * s.scale));
+		this.root.appendChild(node);
+	}
+};
+
+/**
+ * Function: convertHtml
+ * 
+ * Converts the given HTML string to XHTML.
+ */
+mxSvgCanvas2D.prototype.convertHtml = function(val)
+{
+	if (this.useDomParser)
+	{
+		var doc = new DOMParser().parseFromString(val, 'text/html');
+
+		if (doc != null)
+		{
+			val = new XMLSerializer().serializeToString(doc.body);
+			
+			// Extracts body content from DOM
+			if (val.substring(0, 5) == '<body')
+			{
+				val = val.substring(val.indexOf('>', 5) + 1);
+			}
+			
+			if (val.substring(val.length - 7, val.length) == '</body>')
+			{
+				val = val.substring(0, val.length - 7);
+			}
+		}
+	}
+	else if (document.implementation != null && document.implementation.createDocument != null)
+	{
+		var xd = document.implementation.createDocument('http://www.w3.org/1999/xhtml', 'html', null);
+		var xb = xd.createElement('body');
+		xd.documentElement.appendChild(xb);
+		
+		var div = document.createElement('div');
+		div.innerHTML = val;
+		var child = div.firstChild;
+		
+		while (child != null)
+		{
+			var next = child.nextSibling;
+			xb.appendChild(xd.adoptNode(child));
+			child = next;
+		}
+		
+		return xb.innerHTML;
+	}
+	else
+	{
+		var ta = document.createElement('textarea');
+		
+		// Handles special HTML entities < and > and double escaping
+		// and converts unclosed br, hr and img tags to XHTML
+		// LATER: Convert all unclosed tags
+		ta.innerHTML = val.replace(/&amp;/g, '&amp;amp;').
+			replace(/&#60;/g, '&amp;lt;').replace(/&#62;/g, '&amp;gt;').
+			replace(/&lt;/g, '&amp;lt;').replace(/&gt;/g, '&amp;gt;').
+			replace(/</g, '&lt;').replace(/>/g, '&gt;');
+		val = ta.value.replace(/&/g, '&amp;').replace(/&amp;lt;/g, '&lt;').
+			replace(/&amp;gt;/g, '&gt;').replace(/&amp;amp;/g, '&amp;').
+			replace(/<br>/g, '<br />').replace(/<hr>/g, '<hr />').
+			replace(/(<img[^>]+)>/gm, "$1 />");
+	}
+	
+	return val;
+};
+
+/**
+ * Function: createDiv
+ * 
+ * Private helper function to create SVG elements
+ */
+mxSvgCanvas2D.prototype.createDiv = function(str, align, valign, style, overflow)
+{
+	var s = this.state;
+
+	// Inline block for rendering HTML background over SVG in Safari
+	var lh = (mxConstants.ABSOLUTE_LINE_HEIGHT) ? (s.fontSize * mxConstants.LINE_HEIGHT) + 'px' :
+		(mxConstants.LINE_HEIGHT * this.lineHeightCorrection);
+	
+	style = 'display:inline-block;font-size:' + s.fontSize + 'px;font-family:' + s.fontFamily +
+		';color:' + s.fontColor + ';line-height:' + lh + ';' + style;
+
+	if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
+	{
+		style += 'font-weight:bold;';
+	}
+
+	if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
+	{
+		style += 'font-style:italic;';
+	}
+	
+	if ((s.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)
+	{
+		style += 'text-decoration:underline;';
+	}
+	
+	if (align == mxConstants.ALIGN_CENTER)
+	{
+		style += 'text-align:center;';
+	}
+	else if (align == mxConstants.ALIGN_RIGHT)
+	{
+		style += 'text-align:right;';
+	}
+
+	var css = '';
+	
+	if (s.fontBackgroundColor != null)
+	{
+		css += 'background-color:' + s.fontBackgroundColor + ';';
+	}
+	
+	if (s.fontBorderColor != null)
+	{
+		css += 'border:1px solid ' + s.fontBorderColor + ';';
+	}
+	
+	var val = str;
+	
+	if (!mxUtils.isNode(val))
+	{
+		val = this.convertHtml(val);
+		
+		if (overflow != 'fill' && overflow != 'width')
+		{
+			// Inner div always needed to measure wrapped text
+			val = '<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;' + css + '">' + val + '</div>';
+		}
+		else
+		{
+			style += css;
+		}
+	}
+
+	// Uses DOM API where available. This cannot be used in IE to avoid
+	// an opening and two (!) closing TBODY tags being added to tables.
+	if (!mxClient.IS_IE && document.createElementNS)
+	{
+		var div = document.createElementNS('http://www.w3.org/1999/xhtml', 'div');
+		div.setAttribute('style', style);
+		
+		if (mxUtils.isNode(val))
+		{
+			// Creates a copy for export
+			if (this.root.ownerDocument != document)
+			{
+				div.appendChild(val.cloneNode(true));
+			}
+			else
+			{
+				div.appendChild(val);
+			}
+		}
+		else
+		{
+			div.innerHTML = val;
+		}
+		
+		return div;
+	}
+	else
+	{
+		// Serializes for export
+		if (mxUtils.isNode(val) && this.root.ownerDocument != document)
+		{
+			val = val.outerHTML;
+		}
+
+		// NOTE: FF 3.6 crashes if content CSS contains "height:100%"
+		return mxUtils.parseXml('<div xmlns="http://www.w3.org/1999/xhtml" style="' + style + 
+			'">' + val + '</div>').documentElement;
+	}
+};
+
+/**
+ * Invalidates the cached offset size for the given node.
+ */
+mxSvgCanvas2D.prototype.invalidateCachedOffsetSize = function(node)
+{
+	delete node.firstChild.mxCachedOffsetWidth;
+	delete node.firstChild.mxCachedFinalOffsetWidth;
+	delete node.firstChild.mxCachedFinalOffsetHeight;
+};
+
+/**
+ * Updates existing DOM nodes for text rendering. LATER: Merge common parts with text function below.
+ */
+mxSvgCanvas2D.prototype.updateText = function(x, y, w, h, align, valign, wrap, overflow, clip, rotation, node)
+{
+	if (node != null && node.firstChild != null && node.firstChild.firstChild != null &&
+		node.firstChild.firstChild.firstChild != null)
+	{
+		// Uses outer group for opacity and transforms to
+		// fix rendering order in Chrome
+		var group = node.firstChild;
+		var fo = group.firstChild;
+		var div = fo.firstChild;
+
+		rotation = (rotation != null) ? rotation : 0;
+		
+		var s = this.state;
+		x += s.dx;
+		y += s.dy;
+		
+		if (clip)
+		{
+			div.style.maxHeight = Math.round(h) + 'px';
+			div.style.maxWidth = Math.round(w) + 'px';
+		}
+		else if (overflow == 'fill')
+		{
+			div.style.width = Math.round(w + 1) + 'px';
+			div.style.height = Math.round(h + 1) + 'px';
+		}
+		else if (overflow == 'width')
+		{
+			div.style.width = Math.round(w + 1) + 'px';
+			
+			if (h > 0)
+			{
+				div.style.maxHeight = Math.round(h) + 'px';
+			}
+		}
+
+		if (wrap && w > 0)
+		{
+			div.style.width = Math.round(w + 1) + 'px';
+		}
+		
+		// Code that depends on the size which is computed after
+		// the element was added to the DOM.
+		var ow = 0;
+		var oh = 0;
+		
+		// Padding avoids clipping on border and wrapping for differing font metrics on platforms
+		var padX = 2;
+		var padY = 2;
+
+		var sizeDiv = div;
+		
+		if (sizeDiv.firstChild != null && sizeDiv.firstChild.nodeName == 'DIV')
+		{
+			sizeDiv = sizeDiv.firstChild;
+		}
+		
+		var tmp = (group.mxCachedOffsetWidth != null) ? group.mxCachedOffsetWidth : sizeDiv.offsetWidth;
+		ow = tmp + padX;
+
+		// Recomputes the height of the element for wrapped width
+		if (wrap && overflow != 'fill')
+		{
+			if (clip)
+			{
+				ow = Math.min(ow, w);
+			}
+			
+			div.style.width = ow + 'px';
+		}
+		
+		ow = ((group.mxCachedFinalOffsetWidth != null) ? group.mxCachedFinalOffsetWidth :
+			sizeDiv.offsetWidth) + padX;
+		oh = ((group.mxCachedFinalOffsetHeight != null) ? group.mxCachedFinalOffsetHeight :
+			sizeDiv.offsetHeight) - 2;
+
+		if (clip)
+		{
+			oh = Math.min(oh, h);
+			ow = Math.min(ow, w);
+		}
+
+		if (overflow == 'width')
+		{
+			h = oh;
+		}
+		else if (overflow != 'fill')
+		{
+			w = ow;
+			h = oh;
+		}
+
+		var dx = 0;
+		var dy = 0;
+
+		if (align == mxConstants.ALIGN_CENTER)
+		{
+			dx -= w / 2;
+		}
+		else if (align == mxConstants.ALIGN_RIGHT)
+		{
+			dx -= w;
+		}
+		
+		x += dx;
+		
+		// FIXME: LINE_HEIGHT not ideal for all text sizes, fix for export
+		if (valign == mxConstants.ALIGN_MIDDLE)
+		{
+			dy -= h / 2;
+		}
+		else if (valign == mxConstants.ALIGN_BOTTOM)
+		{
+			dy -= h;
+		}
+		
+		// Workaround for rendering offsets
+		// TODO: Check if export needs these fixes, too
+		if (overflow != 'fill' && mxClient.IS_FF && mxClient.IS_WIN)
+		{
+			dy -= 2;
+		}
+		
+		y += dy;
+
+		var tr = (s.scale != 1) ? 'scale(' + s.scale + ')' : '';
+
+		if (s.rotation != 0 && this.rotateHtml)
+		{
+			tr += 'rotate(' + (s.rotation) + ',' + (w / 2) + ',' + (h / 2) + ')';
+			var pt = this.rotatePoint((x + w / 2) * s.scale, (y + h / 2) * s.scale,
+				s.rotation, s.rotationCx, s.rotationCy);
+			x = pt.x - w * s.scale / 2;
+			y = pt.y - h * s.scale / 2;
+		}
+		else
+		{
+			x *= s.scale;
+			y *= s.scale;
+		}
+
+		if (rotation != 0)
+		{
+			tr += 'rotate(' + (rotation) + ',' + (-dx) + ',' + (-dy) + ')';
+		}
+
+		group.setAttribute('transform', 'translate(' + Math.round(x) + ',' + Math.round(y) + ')' + tr);
+		fo.setAttribute('width', Math.round(Math.max(1, w)));
+		fo.setAttribute('height', Math.round(Math.max(1, h)));
+	}
+};
+
+/**
+ * Function: text
+ * 
+ * Paints the given text. Possible values for format are empty string for plain
+ * text and html for HTML markup. Note that HTML markup is only supported if
+ * foreignObject is supported and <foEnabled> is true. (This means IE9 and later
+ * does currently not support HTML text as part of shapes.)
+ */
+mxSvgCanvas2D.prototype.text = function(x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation, dir)
+{
+	if (this.textEnabled && str != null)
+	{
+		rotation = (rotation != null) ? rotation : 0;
+		
+		var s = this.state;
+		x += s.dx;
+		y += s.dy;
+		
+		if (this.foEnabled && format == 'html')
+		{
+			var style = 'vertical-align:top;';
+			
+			if (clip)
+			{
+				style += 'overflow:hidden;max-height:' + Math.round(h) + 'px;max-width:' + Math.round(w) + 'px;';
+			}
+			else if (overflow == 'fill')
+			{
+				style += 'width:' + Math.round(w + 1) + 'px;height:' + Math.round(h + 1) + 'px;overflow:hidden;';
+			}
+			else if (overflow == 'width')
+			{
+				style += 'width:' + Math.round(w + 1) + 'px;';
+				
+				if (h > 0)
+				{
+					style += 'max-height:' + Math.round(h) + 'px;overflow:hidden;';
+				}
+			}
+
+			if (wrap && w > 0)
+			{
+				style += 'width:' + Math.round(w + 1) + 'px;white-space:normal;word-wrap:' +
+					mxConstants.WORD_WRAP + ';';
+			}
+			else
+			{
+				style += 'white-space:nowrap;';
+			}
+			
+			// Uses outer group for opacity and transforms to
+			// fix rendering order in Chrome
+			var group = this.createElement('g');
+			
+			if (s.alpha < 1)
+			{
+				group.setAttribute('opacity', s.alpha);
+			}
+
+			var fo = this.createElement('foreignObject');
+			fo.setAttribute('style', 'overflow:visible;');
+			fo.setAttribute('pointer-events', 'all');
+			
+			var div = this.createDiv(str, align, valign, style, overflow);
+			
+			// Ignores invalid XHTML labels
+			if (div == null)
+			{
+				return;
+			}
+			else if (dir != null)
+			{
+				div.setAttribute('dir', dir);
+			}
+
+			group.appendChild(fo);
+			this.root.appendChild(group);
+			
+			// Code that depends on the size which is computed after
+			// the element was added to the DOM.
+			var ow = 0;
+			var oh = 0;
+			
+			// Padding avoids clipping on border and wrapping for differing font metrics on platforms
+			var padX = 2;
+			var padY = 2;
+
+			// NOTE: IE is always export as it does not support foreign objects
+			if (mxClient.IS_IE && (document.documentMode == 9 || !mxClient.IS_SVG))
+			{
+				// Handles non-standard namespace for getting size in IE
+				var clone = document.createElement('div');
+				
+				clone.style.cssText = div.getAttribute('style');
+				clone.style.display = (mxClient.IS_QUIRKS) ? 'inline' : 'inline-block';
+				clone.style.position = 'absolute';
+				clone.style.visibility = 'hidden';
+
+				// Inner DIV is needed for text measuring
+				var div2 = document.createElement('div');
+				div2.style.display = (mxClient.IS_QUIRKS) ? 'inline' : 'inline-block';
+				div2.style.wordWrap = mxConstants.WORD_WRAP;
+				div2.innerHTML = (mxUtils.isNode(str)) ? str.outerHTML : str;
+				clone.appendChild(div2);
+
+				document.body.appendChild(clone);
+
+				// Workaround for different box models
+				if (document.documentMode != 8 && document.documentMode != 9 && s.fontBorderColor != null)
+				{
+					padX += 2;
+					padY += 2;
+				}
+
+				if (wrap && w > 0)
+				{
+					var tmp = div2.offsetWidth;
+					
+					// Workaround for adding padding twice in IE8/IE9 standards mode if label is wrapped
+					var padDx = 0;
+					
+					// For export, if no wrapping occurs, we add a large padding to make
+					// sure there is no wrapping even if the text metrics are different.
+					// This adds support for text metrics on different operating systems.
+					// Disables wrapping if text is not wrapped for given width
+					if (!clip && wrap && w > 0 && this.root.ownerDocument != document && overflow != 'fill')
+					{
+						var ws = clone.style.whiteSpace;
+						div2.style.whiteSpace = 'nowrap';
+						
+						if (tmp < div2.offsetWidth)
+						{
+							clone.style.whiteSpace = ws;
+						}
+					}
+					
+					if (clip)
+					{
+						tmp = Math.min(tmp, w);
+					}
+					
+					clone.style.width = tmp + 'px';
+	
+					// Padding avoids clipping on border
+					ow = div2.offsetWidth + padX + padDx;
+					oh = div2.offsetHeight + padY;
+					
+					// Overrides the width of the DIV via XML DOM by using the
+					// clone DOM style, getting the CSS text for that and
+					// then setting that on the DIV via setAttribute
+					clone.style.display = 'inline-block';
+					clone.style.position = '';
+					clone.style.visibility = '';
+					clone.style.width = ow + 'px';
+					
+					div.setAttribute('style', clone.style.cssText);
+				}
+				else
+				{
+					// Padding avoids clipping on border
+					ow = div2.offsetWidth + padX;
+					oh = div2.offsetHeight + padY;
+				}
+
+				clone.parentNode.removeChild(clone);
+				fo.appendChild(div);
+			}
+			else
+			{
+				// Uses document for text measuring during export
+				if (this.root.ownerDocument != document)
+				{
+					div.style.visibility = 'hidden';
+					document.body.appendChild(div);
+				}
+				else
+				{
+					fo.appendChild(div);
+				}
+
+				var sizeDiv = div;
+				
+				if (sizeDiv.firstChild != null && sizeDiv.firstChild.nodeName == 'DIV')
+				{
+					sizeDiv = sizeDiv.firstChild;
+					
+					if (wrap && div.style.wordWrap == 'break-word')
+					{
+						sizeDiv.style.width = '100%';
+					}
+				}
+				
+				var tmp = sizeDiv.offsetWidth;
+				
+				// Workaround for text measuring in hidden containers
+				if (tmp == 0 && div.parentNode == fo)
+				{
+					div.style.visibility = 'hidden';
+					document.body.appendChild(div);
+					
+					tmp = sizeDiv.offsetWidth;
+				}
+				
+				if (this.cacheOffsetSize)
+				{
+					group.mxCachedOffsetWidth = tmp;
+				}
+				
+				// Disables wrapping if text is not wrapped for given width
+				if (!clip && wrap && w > 0 && this.root.ownerDocument != document &&
+					overflow != 'fill' && overflow != 'width')
+				{
+					var ws = div.style.whiteSpace;
+					div.style.whiteSpace = 'nowrap';
+					
+					if (tmp < sizeDiv.offsetWidth)
+					{
+						div.style.whiteSpace = ws;
+					}
+				}
+
+				ow = tmp + padX - 1;
+
+				// Recomputes the height of the element for wrapped width
+				if (wrap && overflow != 'fill' && overflow != 'width')
+				{
+					if (clip)
+					{
+						ow = Math.min(ow, w);
+					}
+					
+					div.style.width = ow + 'px';
+				}
+
+				ow = sizeDiv.offsetWidth;
+				oh = sizeDiv.offsetHeight;
+				
+				if (this.cacheOffsetSize)
+				{
+					group.mxCachedFinalOffsetWidth = ow;
+					group.mxCachedFinalOffsetHeight = oh;
+				}
+
+				oh -= padY;
+				
+				if (div.parentNode != fo)
+				{
+					fo.appendChild(div);
+					div.style.visibility = '';
+				}
+			}
+
+			if (clip)
+			{
+				oh = Math.min(oh, h);
+				ow = Math.min(ow, w);
+			}
+
+			if (overflow == 'width')
+			{
+				h = oh;
+			}
+			else if (overflow != 'fill')
+			{
+				w = ow;
+				h = oh;
+			}
+
+			if (s.alpha < 1)
+			{
+				group.setAttribute('opacity', s.alpha);
+			}
+			
+			var dx = 0;
+			var dy = 0;
+
+			if (align == mxConstants.ALIGN_CENTER)
+			{
+				dx -= w / 2;
+			}
+			else if (align == mxConstants.ALIGN_RIGHT)
+			{
+				dx -= w;
+			}
+			
+			x += dx;
+			
+			// FIXME: LINE_HEIGHT not ideal for all text sizes, fix for export
+			if (valign == mxConstants.ALIGN_MIDDLE)
+			{
+				dy -= h / 2;
+			}
+			else if (valign == mxConstants.ALIGN_BOTTOM)
+			{
+				dy -= h;
+			}
+			
+			// Workaround for rendering offsets
+			// TODO: Check if export needs these fixes, too
+			//if (this.root.ownerDocument == document)
+			if (overflow != 'fill' && mxClient.IS_FF && mxClient.IS_WIN)
+			{
+				dy -= 2;
+			}
+			
+			y += dy;
+
+			var tr = (s.scale != 1) ? 'scale(' + s.scale + ')' : '';
+
+			if (s.rotation != 0 && this.rotateHtml)
+			{
+				tr += 'rotate(' + (s.rotation) + ',' + (w / 2) + ',' + (h / 2) + ')';
+				var pt = this.rotatePoint((x + w / 2) * s.scale, (y + h / 2) * s.scale,
+					s.rotation, s.rotationCx, s.rotationCy);
+				x = pt.x - w * s.scale / 2;
+				y = pt.y - h * s.scale / 2;
+			}
+			else
+			{
+				x *= s.scale;
+				y *= s.scale;
+			}
+
+			if (rotation != 0)
+			{
+				tr += 'rotate(' + (rotation) + ',' + (-dx) + ',' + (-dy) + ')';
+			}
+
+			group.setAttribute('transform', 'translate(' + (Math.round(x) + this.foOffset) + ',' +
+				(Math.round(y) + this.foOffset) + ')' + tr);
+			fo.setAttribute('width', Math.round(Math.max(1, w)));
+			fo.setAttribute('height', Math.round(Math.max(1, h)));
+			
+			// Adds alternate content if foreignObject not supported in viewer
+			if (this.root.ownerDocument != document)
+			{
+				var alt = this.createAlternateContent(fo, x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation);
+				
+				if (alt != null)
+				{
+					fo.setAttribute('requiredFeatures', 'http://www.w3.org/TR/SVG11/feature#Extensibility');
+					var sw = this.createElement('switch');
+					sw.appendChild(fo);
+					sw.appendChild(alt);
+					group.appendChild(sw);
+				}
+			}
+		}
+		else
+		{
+			this.plainText(x, y, w, h, str, align, valign, wrap, overflow, clip, rotation, dir);
+		}
+	}
+};
+
+/**
+ * Function: createClip
+ * 
+ * Creates a clip for the given coordinates.
+ */
+mxSvgCanvas2D.prototype.createClip = function(x, y, w, h)
+{
+	x = Math.round(x);
+	y = Math.round(y);
+	w = Math.round(w);
+	h = Math.round(h);
+	
+	var id = 'mx-clip-' + x + '-' + y + '-' + w + '-' + h;
+
+	var counter = 0;
+	var tmp = id + '-' + counter;
+	
+	// Resolves ID conflicts
+	while (document.getElementById(tmp) != null)
+	{
+		tmp = id + '-' + (++counter);
+	}
+	
+	clip = this.createElement('clipPath');
+	clip.setAttribute('id', tmp);
+	
+	var rect = this.createElement('rect');
+	rect.setAttribute('x', x);
+	rect.setAttribute('y', y);
+	rect.setAttribute('width', w);
+	rect.setAttribute('height', h);
+		
+	clip.appendChild(rect);
+	
+	return clip;
+};
+
+/**
+ * Function: text
+ * 
+ * Paints the given text. Possible values for format are empty string for
+ * plain text and html for HTML markup.
+ */
+mxSvgCanvas2D.prototype.plainText = function(x, y, w, h, str, align, valign, wrap, overflow, clip, rotation, dir)
+{
+	rotation = (rotation != null) ? rotation : 0;
+	var s = this.state;
+	var size = s.fontSize;
+	var node = this.createElement('g');
+	var tr = s.transform || '';
+	this.updateFont(node);
+	
+	// Non-rotated text
+	if (rotation != 0)
+	{
+		tr += 'rotate(' + rotation  + ',' + this.format(x * s.scale) + ',' + this.format(y * s.scale) + ')';
+	}
+	
+	if (dir != null)
+	{
+		node.setAttribute('direction', dir);
+	}
+
+	if (clip && w > 0 && h > 0)
+	{
+		var cx = x;
+		var cy = y;
+		
+		if (align == mxConstants.ALIGN_CENTER)
+		{
+			cx -= w / 2;
+		}
+		else if (align == mxConstants.ALIGN_RIGHT)
+		{
+			cx -= w;
+		}
+		
+		if (overflow != 'fill')
+		{
+			if (valign == mxConstants.ALIGN_MIDDLE)
+			{
+				cy -= h / 2;
+			}
+			else if (valign == mxConstants.ALIGN_BOTTOM)
+			{
+				cy -= h;
+			}
+		}
+		
+		// LATER: Remove spacing from clip rectangle
+		var c = this.createClip(cx * s.scale - 2, cy * s.scale - 2, w * s.scale + 4, h * s.scale + 4);
+		
+		if (this.defs != null)
+		{
+			this.defs.appendChild(c);
+		}
+		else
+		{
+			// Makes sure clip is removed with referencing node
+			this.root.appendChild(c);
+		}
+		
+		if (!mxClient.IS_CHROME_APP && !mxClient.IS_IE && !mxClient.IS_IE11 &&
+			!mxClient.IS_EDGE && this.root.ownerDocument == document)
+		{
+			// Workaround for potential base tag
+			var base = this.getBaseUrl().replace(/([\(\)])/g, '\\$1');
+			node.setAttribute('clip-path', 'url(' + base + '#' + c.getAttribute('id') + ')');
+		}
+		else
+		{
+			node.setAttribute('clip-path', 'url(#' + c.getAttribute('id') + ')');
+		}
+	}
+
+	// Default is left
+	var anchor = (align == mxConstants.ALIGN_RIGHT) ? 'end' :
+					(align == mxConstants.ALIGN_CENTER) ? 'middle' :
+					'start';
+
+	// Text-anchor start is default in SVG
+	if (anchor != 'start')
+	{
+		node.setAttribute('text-anchor', anchor);
+	}
+	
+	if (!this.styleEnabled || size != mxConstants.DEFAULT_FONTSIZE)
+	{
+		node.setAttribute('font-size', (size * s.scale) + 'px');
+	}
+	
+	if (tr.length > 0)
+	{
+		node.setAttribute('transform', tr);
+	}
+	
+	if (s.alpha < 1)
+	{
+		node.setAttribute('opacity', s.alpha);
+	}
+	
+	var lines = str.split('\n');
+	var lh = Math.round(size * mxConstants.LINE_HEIGHT);
+	var textHeight = size + (lines.length - 1) * lh;
+
+	var cy = y + size - 1;
+
+	if (valign == mxConstants.ALIGN_MIDDLE)
+	{
+		if (overflow == 'fill')
+		{
+			cy -= h / 2;
+		}
+		else
+		{
+			var dy = ((this.matchHtmlAlignment && clip && h > 0) ? Math.min(textHeight, h) : textHeight) / 2;
+			cy -= dy + 1;
+		}
+	}
+	else if (valign == mxConstants.ALIGN_BOTTOM)
+	{
+		if (overflow == 'fill')
+		{
+			cy -= h;
+		}
+		else
+		{
+			var dy = (this.matchHtmlAlignment && clip && h > 0) ? Math.min(textHeight, h) : textHeight;
+			cy -= dy + 2;
+		}
+	}
+
+	for (var i = 0; i < lines.length; i++)
+	{
+		// Workaround for bounding box of empty lines and spaces
+		if (lines[i].length > 0 && mxUtils.trim(lines[i]).length > 0)
+		{
+			var text = this.createElement('text');
+			// LATER: Match horizontal HTML alignment
+			text.setAttribute('x', this.format(x * s.scale) + this.textOffset);
+			text.setAttribute('y', this.format(cy * s.scale) + this.textOffset);
+			
+			mxUtils.write(text, lines[i]);
+			node.appendChild(text);
+		}
+
+		cy += lh;
+	}
+
+	this.root.appendChild(node);
+	this.addTextBackground(node, str, x, y, w, (overflow == 'fill') ? h : textHeight, align, valign, overflow);
+};
+
+/**
+ * Function: updateFont
+ * 
+ * Updates the text properties for the given node. (NOTE: For this to work in
+ * IE, the given node must be a text or tspan element.)
+ */
+mxSvgCanvas2D.prototype.updateFont = function(node)
+{
+	var s = this.state;
+
+	node.setAttribute('fill', s.fontColor);
+	
+	if (!this.styleEnabled || s.fontFamily != mxConstants.DEFAULT_FONTFAMILY)
+	{
+		node.setAttribute('font-family', s.fontFamily);
+	}
+
+	if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
+	{
+		node.setAttribute('font-weight', 'bold');
+	}
+
+	if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
+	{
+		node.setAttribute('font-style', 'italic');
+	}
+	
+	if ((s.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)
+	{
+		node.setAttribute('text-decoration', 'underline');
+	}
+};
+
+/**
+ * Function: addTextBackground
+ * 
+ * Background color and border
+ */
+mxSvgCanvas2D.prototype.addTextBackground = function(node, str, x, y, w, h, align, valign, overflow)
+{
+	var s = this.state;
+
+	if (s.fontBackgroundColor != null || s.fontBorderColor != null)
+	{
+		var bbox = null;
+		
+		if (overflow == 'fill' || overflow == 'width')
+		{
+			if (align == mxConstants.ALIGN_CENTER)
+			{
+				x -= w / 2;
+			}
+			else if (align == mxConstants.ALIGN_RIGHT)
+			{
+				x -= w;
+			}
+			
+			if (valign == mxConstants.ALIGN_MIDDLE)
+			{
+				y -= h / 2;
+			}
+			else if (valign == mxConstants.ALIGN_BOTTOM)
+			{
+				y -= h;
+			}
+			
+			bbox = new mxRectangle((x + 1) * s.scale, y * s.scale, (w - 2) * s.scale, (h + 2) * s.scale);
+		}
+		else if (node.getBBox != null && this.root.ownerDocument == document)
+		{
+			// Uses getBBox only if inside document for correct size
+			try
+			{
+				bbox = node.getBBox();
+				var ie = mxClient.IS_IE && mxClient.IS_SVG;
+				bbox = new mxRectangle(bbox.x, bbox.y + ((ie) ? 0 : 1), bbox.width, bbox.height + ((ie) ? 1 : 0));
+			}
+			catch (e)
+			{
+				// Ignores NS_ERROR_FAILURE in FF if container display is none.
+			}
+		}
+		else
+		{
+			// Computes size if not in document or no getBBox available
+			var div = document.createElement('div');
+
+			// Wrapping and clipping can be ignored here
+			div.style.lineHeight = (mxConstants.ABSOLUTE_LINE_HEIGHT) ? (s.fontSize * mxConstants.LINE_HEIGHT) + 'px' : mxConstants.LINE_HEIGHT;
+			div.style.fontSize = s.fontSize + 'px';
+			div.style.fontFamily = s.fontFamily;
+			div.style.whiteSpace = 'nowrap';
+			div.style.position = 'absolute';
+			div.style.visibility = 'hidden';
+			div.style.display = (mxClient.IS_QUIRKS) ? 'inline' : 'inline-block';
+			div.style.zoom = '1';
+			
+			if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
+			{
+				div.style.fontWeight = 'bold';
+			}
+
+			if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
+			{
+				div.style.fontStyle = 'italic';
+			}
+			
+			str = mxUtils.htmlEntities(str, false);
+			div.innerHTML = str.replace(/\n/g, '<br/>');
+			
+			document.body.appendChild(div);
+			var w = div.offsetWidth;
+			var h = div.offsetHeight;
+			div.parentNode.removeChild(div);
+			
+			if (align == mxConstants.ALIGN_CENTER)
+			{
+				x -= w / 2;
+			}
+			else if (align == mxConstants.ALIGN_RIGHT)
+			{
+				x -= w;
+			}
+			
+			if (valign == mxConstants.ALIGN_MIDDLE)
+			{
+				y -= h / 2;
+			}
+			else if (valign == mxConstants.ALIGN_BOTTOM)
+			{
+				y -= h;
+			}
+			
+			bbox = new mxRectangle((x + 1) * s.scale, (y + 2) * s.scale, w * s.scale, (h + 1) * s.scale);
+		}
+		
+		if (bbox != null)
+		{
+			var n = this.createElement('rect');
+			n.setAttribute('fill', s.fontBackgroundColor || 'none');
+			n.setAttribute('stroke', s.fontBorderColor || 'none');
+			n.setAttribute('x', Math.floor(bbox.x - 1));
+			n.setAttribute('y', Math.floor(bbox.y - 1));
+			n.setAttribute('width', Math.ceil(bbox.width + 2));
+			n.setAttribute('height', Math.ceil(bbox.height));
+
+			var sw = (s.fontBorderColor != null) ? Math.max(1, this.format(s.scale)) : 0;
+			n.setAttribute('stroke-width', sw);
+			
+			// Workaround for crisp rendering - only required if not exporting
+			if (this.root.ownerDocument == document && mxUtils.mod(sw, 2) == 1)
+			{
+				n.setAttribute('transform', 'translate(0.5, 0.5)');
+			}
+			
+			node.insertBefore(n, node.firstChild);
+		}
+	}
+};
+
+/**
+ * Function: stroke
+ * 
+ * Paints the outline of the current path.
+ */
+mxSvgCanvas2D.prototype.stroke = function()
+{
+	this.addNode(false, true);
+};
+
+/**
+ * Function: fill
+ * 
+ * Fills the current path.
+ */
+mxSvgCanvas2D.prototype.fill = function()
+{
+	this.addNode(true, false);
+};
+
+/**
+ * Function: fillAndStroke
+ * 
+ * Fills and paints the outline of the current path.
+ */
+mxSvgCanvas2D.prototype.fillAndStroke = function()
+{
+	this.addNode(true, true);
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/util/mxToolbar.js b/airavata-kubernetes/web-console/src/assets/js/util/mxToolbar.js
new file mode 100644
index 0000000..cb90ced
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/util/mxToolbar.js
@@ -0,0 +1,527 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxToolbar
+ * 
+ * Creates a toolbar inside a given DOM node. The toolbar may contain icons,
+ * buttons and combo boxes.
+ * 
+ * Event: mxEvent.SELECT
+ * 
+ * Fires when an item was selected in the toolbar. The <code>function</code>
+ * property contains the function that was selected in <selectMode>.
+ * 
+ * Constructor: mxToolbar
+ * 
+ * Constructs a toolbar in the specified container.
+ *
+ * Parameters:
+ *
+ * container - DOM node that contains the toolbar.
+ */
+function mxToolbar(container)
+{
+	this.container = container;
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxToolbar.prototype = new mxEventSource();
+mxToolbar.prototype.constructor = mxToolbar;
+
+/**
+ * Variable: container
+ * 
+ * Reference to the DOM nodes that contains the toolbar.
+ */
+mxToolbar.prototype.container = null;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies if events are handled. Default is true.
+ */
+mxToolbar.prototype.enabled = true;
+
+/**
+ * Variable: noReset
+ * 
+ * Specifies if <resetMode> requires a forced flag of true for resetting
+ * the current mode in the toolbar. Default is false. This is set to true
+ * if the toolbar item is double clicked to avoid a reset after a single
+ * use of the item.
+ */
+mxToolbar.prototype.noReset = false;
+
+/**
+ * Variable: updateDefaultMode
+ * 
+ * Boolean indicating if the default mode should be the last selected
+ * switch mode or the first inserted switch mode. Default is true, that
+ * is the last selected switch mode is the default mode. The default mode
+ * is the mode to be selected after a reset of the toolbar. If this is
+ * false, then the default mode is the first inserted mode item regardless
+ * of what was last selected. Otherwise, the selected item after a reset is
+ * the previously selected item.
+ */
+mxToolbar.prototype.updateDefaultMode = true;
+
+/**
+ * Function: addItem
+ * 
+ * Adds the given function as an image with the specified title and icon
+ * and returns the new image node.
+ * 
+ * Parameters:
+ * 
+ * title - Optional string that is used as the tooltip.
+ * icon - Optional URL of the image to be used. If no URL is given, then a
+ * button is created.
+ * funct - Function to execute on a mouse click.
+ * pressedIcon - Optional URL of the pressed image. Default is a gray
+ * background.
+ * style - Optional style classname. Default is mxToolbarItem.
+ * factoryMethod - Optional factory method for popup menu, eg.
+ * function(menu, evt, cell) { menu.addItem('Hello, World!'); }
+ */
+mxToolbar.prototype.addItem = function(title, icon, funct, pressedIcon, style, factoryMethod)
+{
+	var img = document.createElement((icon != null) ? 'img' : 'button');
+	var initialClassName = style || ((factoryMethod != null) ?
+			'mxToolbarMode' : 'mxToolbarItem');
+	img.className = initialClassName;
+	img.setAttribute('src', icon);
+	
+	if (title != null)
+	{
+		if (icon != null)
+		{
+			img.setAttribute('title', title);
+		}
+		else
+		{
+			mxUtils.write(img, title);
+		}
+	}
+	
+	this.container.appendChild(img);
+
+	// Invokes the function on a click on the toolbar item
+	if (funct != null)
+	{
+		mxEvent.addListener(img, 'click', funct);
+		
+		if (mxClient.IS_TOUCH)
+		{
+			mxEvent.addListener(img, 'touchend', funct);
+		}
+	}
+
+	var mouseHandler = mxUtils.bind(this, function(evt)
+	{
+		if (pressedIcon != null)
+		{
+			img.setAttribute('src', icon);
+		}
+		else
+		{
+			img.style.backgroundColor = '';
+		}
+	});
+
+	// Highlights the toolbar item with a gray background
+	// while it is being clicked with the mouse
+	mxEvent.addGestureListeners(img, mxUtils.bind(this, function(evt)
+	{
+		if (pressedIcon != null)
+		{
+			img.setAttribute('src', pressedIcon);
+		}
+		else
+		{
+			img.style.backgroundColor = 'gray';
+		}
+		
+		// Popup Menu
+		if (factoryMethod != null)
+		{
+			if (this.menu == null)
+			{
+				this.menu = new mxPopupMenu();
+				this.menu.init();
+			}
+			
+			var last = this.currentImg;
+			
+			if (this.menu.isMenuShowing())
+			{
+				this.menu.hideMenu();
+			}
+			
+			if (last != img)
+			{
+				// Redirects factory method to local factory method
+				this.currentImg = img;
+				this.menu.factoryMethod = factoryMethod;
+				
+				var point = new mxPoint(
+					img.offsetLeft,
+					img.offsetTop + img.offsetHeight);
+				this.menu.popup(point.x, point.y, null, evt);
+
+				// Sets and overrides to restore classname
+				if (this.menu.isMenuShowing())
+				{
+					img.className = initialClassName + 'Selected';
+					
+					this.menu.hideMenu = function()
+					{
+						mxPopupMenu.prototype.hideMenu.apply(this);
+						img.className = initialClassName;
+						this.currentImg = null;
+					};
+				}
+			}
+		}
+	}), null, mouseHandler);
+
+	mxEvent.addListener(img, 'mouseout', mouseHandler);
+	
+	return img;
+};
+
+/**
+ * Function: addCombo
+ * 
+ * Adds and returns a new SELECT element using the given style. The element
+ * is placed inside a DIV with the mxToolbarComboContainer style classname.
+ * 
+ * Parameters:
+ * 
+ * style - Optional style classname. Default is mxToolbarCombo.
+ */
+mxToolbar.prototype.addCombo = function(style)
+{
+	var div = document.createElement('div');
+	div.style.display = 'inline';
+	div.className = 'mxToolbarComboContainer';
+	
+	var select = document.createElement('select');
+	select.className = style || 'mxToolbarCombo';
+	div.appendChild(select);
+	
+	this.container.appendChild(div);
+	
+	return select;
+};
+
+/**
+ * Function: addCombo
+ * 
+ * Adds and returns a new SELECT element using the given title as the
+ * default element. The selection is reset to this element after each
+ * change.
+ * 
+ * Parameters:
+ * 
+ * title - String that specifies the title of the default element.
+ * style - Optional style classname. Default is mxToolbarCombo.
+ */
+mxToolbar.prototype.addActionCombo = function(title, style)
+{
+	var select = document.createElement('select');
+	select.className = style || 'mxToolbarCombo';
+	this.addOption(select, title, null);
+	
+	mxEvent.addListener(select, 'change', function(evt)
+	{
+		var value = select.options[select.selectedIndex];
+		select.selectedIndex = 0;
+		
+		if (value.funct != null)
+		{
+			value.funct(evt);
+		}
+	});
+	
+	this.container.appendChild(select);
+	
+	return select;
+};
+
+/**
+ * Function: addOption
+ * 
+ * Adds and returns a new OPTION element inside the given SELECT element.
+ * If the given value is a function then it is stored in the option's funct
+ * field.
+ * 
+ * Parameters:
+ * 
+ * combo - SELECT element that will contain the new entry.
+ * title - String that specifies the title of the option.
+ * value - Specifies the value associated with this option.
+ */
+mxToolbar.prototype.addOption = function(combo, title, value)
+{
+	var option = document.createElement('option');
+	mxUtils.writeln(option, title);
+	
+	if (typeof(value) == 'function')
+	{
+		option.funct = value;
+	}
+	else
+	{
+		option.setAttribute('value', value);
+	}
+	
+	combo.appendChild(option);
+	
+	return option;
+};
+
+/**
+ * Function: addSwitchMode
+ * 
+ * Adds a new selectable item to the toolbar. Only one switch mode item may
+ * be selected at a time. The currently selected item is the default item
+ * after a reset of the toolbar.
+ */
+mxToolbar.prototype.addSwitchMode = function(title, icon, funct, pressedIcon, style)
+{
+	var img = document.createElement('img');
+	img.initialClassName = style || 'mxToolbarMode';
+	img.className = img.initialClassName;
+	img.setAttribute('src', icon);
+	img.altIcon = pressedIcon;
+	
+	if (title != null)
+	{
+		img.setAttribute('title', title);
+	}
+	
+	mxEvent.addListener(img, 'click', mxUtils.bind(this, function(evt)
+	{
+		var tmp = this.selectedMode.altIcon;
+		
+		if (tmp != null)
+		{
+			this.selectedMode.altIcon = this.selectedMode.getAttribute('src');
+			this.selectedMode.setAttribute('src', tmp);
+		}
+		else
+		{
+			this.selectedMode.className = this.selectedMode.initialClassName;
+		}
+		
+		if (this.updateDefaultMode)
+		{
+			this.defaultMode = img;
+		}
+		
+		this.selectedMode = img;
+		
+		var tmp = img.altIcon;
+		
+		if (tmp != null)
+		{
+			img.altIcon = img.getAttribute('src');
+			img.setAttribute('src', tmp);
+		}
+		else
+		{
+			img.className = img.initialClassName+'Selected';
+		}
+		
+		this.fireEvent(new mxEventObject(mxEvent.SELECT));
+		funct();
+	}));
+	
+	this.container.appendChild(img);
+	
+	if (this.defaultMode == null)
+	{
+		this.defaultMode = img;
+		
+		// Function should fire only once so
+		// do not pass it with the select event
+		this.selectMode(img);
+		funct();
+	}
+	
+	return img;
+};
+
+/**
+ * Function: addMode
+ * 
+ * Adds a new item to the toolbar. The selection is typically reset after
+ * the item has been consumed, for example by adding a new vertex to the
+ * graph. The reset is not carried out if the item is double clicked.
+ * 
+ * The function argument uses the following signature: funct(evt, cell) where
+ * evt is the native mouse event and cell is the cell under the mouse.
+ */
+mxToolbar.prototype.addMode = function(title, icon, funct, pressedIcon, style, toggle)
+{
+	toggle = (toggle != null) ? toggle : true;
+	var img = document.createElement((icon != null) ? 'img' : 'button');
+	
+	img.initialClassName = style || 'mxToolbarMode';
+	img.className = img.initialClassName;
+	img.setAttribute('src', icon);
+	img.altIcon = pressedIcon;
+
+	if (title != null)
+	{
+		img.setAttribute('title', title);
+	}
+	
+	if (this.enabled && toggle)
+	{
+		mxEvent.addListener(img, 'click', mxUtils.bind(this, function(evt)
+		{
+			this.selectMode(img, funct);
+			this.noReset = false;
+		}));
+		
+		mxEvent.addListener(img, 'dblclick', mxUtils.bind(this, function(evt)
+		{
+			this.selectMode(img, funct);
+			this.noReset = true;
+		}));
+		
+		if (this.defaultMode == null)
+		{
+			this.defaultMode = img;
+			this.defaultFunction = funct;
+			this.selectMode(img, funct);
+		}
+	}
+
+	this.container.appendChild(img);					
+
+	return img;
+};
+
+/**
+ * Function: selectMode
+ * 
+ * Resets the state of the previously selected mode and displays the given
+ * DOM node as selected. This function fires a select event with the given
+ * function as a parameter.
+ */
+mxToolbar.prototype.selectMode = function(domNode, funct)
+{
+	if (this.selectedMode != domNode)
+	{
+		if (this.selectedMode != null)
+		{
+			var tmp = this.selectedMode.altIcon;
+			
+			if (tmp != null)
+			{
+				this.selectedMode.altIcon = this.selectedMode.getAttribute('src');
+				this.selectedMode.setAttribute('src', tmp);
+			}
+			else
+			{
+				this.selectedMode.className = this.selectedMode.initialClassName;
+			}
+		}
+		
+		this.selectedMode = domNode;
+		var tmp = this.selectedMode.altIcon;
+		
+		if (tmp != null)
+		{
+			this.selectedMode.altIcon = this.selectedMode.getAttribute('src');
+			this.selectedMode.setAttribute('src', tmp);
+		}
+		else
+		{
+			this.selectedMode.className = this.selectedMode.initialClassName+'Selected';
+		}
+		
+		this.fireEvent(new mxEventObject(mxEvent.SELECT, "function", funct));
+	}
+};
+
+/**
+ * Function: resetMode
+ * 
+ * Selects the default mode and resets the state of the previously selected
+ * mode.
+ */
+mxToolbar.prototype.resetMode = function(forced)
+{
+	if ((forced || !this.noReset) && this.selectedMode != this.defaultMode)
+	{
+		// The last selected switch mode will be activated
+		// so the function was already executed and is
+		// no longer required here
+		this.selectMode(this.defaultMode, this.defaultFunction);
+	}
+};
+
+/**
+ * Function: addSeparator
+ * 
+ * Adds the specifies image as a separator.
+ * 
+ * Parameters:
+ * 
+ * icon - URL of the separator icon.
+ */
+mxToolbar.prototype.addSeparator = function(icon)
+{
+	return this.addItem(null, icon, null);
+};
+
+/**
+ * Function: addBreak
+ * 
+ * Adds a break to the container.
+ */
+mxToolbar.prototype.addBreak = function()
+{
+	mxUtils.br(this.container);
+};
+
+/**
+ * Function: addLine
+ * 
+ * Adds a horizontal line to the container.
+ */
+mxToolbar.prototype.addLine = function()
+{
+	var hr = document.createElement('hr');
+	
+	hr.style.marginRight = '6px';
+	hr.setAttribute('size', '1');
+	
+	this.container.appendChild(hr);
+};
+
+/**
+ * Function: destroy
+ * 
+ * Removes the toolbar and all its associated resources.
+ */
+mxToolbar.prototype.destroy = function ()
+{
+	mxEvent.release(this.container);
+	this.container = null;
+	this.defaultMode = null;
+	this.defaultFunction = null;
+	this.selectedMode = null;
+	
+	if (this.menu != null)
+	{
+		this.menu.destroy();
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/util/mxUndoManager.js b/airavata-kubernetes/web-console/src/assets/js/util/mxUndoManager.js
new file mode 100644
index 0000000..749561c
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/util/mxUndoManager.js
@@ -0,0 +1,229 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxUndoManager
+ *
+ * Implements a command history. When changing the graph model, an
+ * <mxUndoableChange> object is created at the start of the transaction (when
+ * model.beginUpdate is called). All atomic changes are then added to this
+ * object until the last model.endUpdate call, at which point the
+ * <mxUndoableEdit> is dispatched in an event, and added to the history inside
+ * <mxUndoManager>. This is done by an event listener in
+ * <mxEditor.installUndoHandler>.
+ * 
+ * Each atomic change of the model is represented by an object (eg.
+ * <mxRootChange>, <mxChildChange>, <mxTerminalChange> etc) which contains the
+ * complete undo information. The <mxUndoManager> also listens to the
+ * <mxGraphView> and stores it's changes to the current root as insignificant
+ * undoable changes, so that drilling (step into, step up) is undone.
+ * 
+ * This means when you execute an atomic change on the model, then change the
+ * current root on the view and click undo, the change of the root will be
+ * undone together with the change of the model so that the display represents
+ * the state at which the model was changed. However, these changes are not
+ * transmitted for sharing as they do not represent a state change.
+ *
+ * Example:
+ * 
+ * When adding an undo manager to a graph, make sure to add it
+ * to the model and the view as well to maintain a consistent
+ * display across multiple undo/redo steps.
+ *
+ * (code)
+ * var undoManager = new mxUndoManager();
+ * var listener = function(sender, evt)
+ * {
+ *   undoManager.undoableEditHappened(evt.getProperty('edit'));
+ * };
+ * graph.getModel().addListener(mxEvent.UNDO, listener);
+ * graph.getView().addListener(mxEvent.UNDO, listener);
+ * (end)
+ * 
+ * The code creates a function that informs the undoManager
+ * of an undoable edit and binds it to the undo event of
+ * <mxGraphModel> and <mxGraphView> using
+ * <mxEventSource.addListener>.
+ * 
+ * Event: mxEvent.CLEAR
+ * 
+ * Fires after <clear> was invoked. This event has no properties.
+ * 
+ * Event: mxEvent.UNDO
+ * 
+ * Fires afer a significant edit was undone in <undo>. The <code>edit</code>
+ * property contains the <mxUndoableEdit> that was undone.
+ * 
+ * Event: mxEvent.REDO
+ * 
+ * Fires afer a significant edit was redone in <redo>. The <code>edit</code>
+ * property contains the <mxUndoableEdit> that was redone.
+ * 
+ * Event: mxEvent.ADD
+ * 
+ * Fires after an undoable edit was added to the history. The <code>edit</code>
+ * property contains the <mxUndoableEdit> that was added.
+ * 
+ * Constructor: mxUndoManager
+ *
+ * Constructs a new undo manager with the given history size. If no history
+ * size is given, then a default size of 100 steps is used.
+ */
+function mxUndoManager(size)
+{
+	this.size = (size != null) ? size : 100;
+	this.clear();
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxUndoManager.prototype = new mxEventSource();
+mxUndoManager.prototype.constructor = mxUndoManager;
+
+/**
+ * Variable: size
+ * 
+ * Maximum command history size. 0 means unlimited history. Default is
+ * 100.
+ */
+mxUndoManager.prototype.size = null;
+
+/**
+ * Variable: history
+ * 
+ * Array that contains the steps of the command history.
+ */
+mxUndoManager.prototype.history = null;
+
+/**
+ * Variable: indexOfNextAdd
+ * 
+ * Index of the element to be added next.
+ */
+mxUndoManager.prototype.indexOfNextAdd = 0;
+
+/**
+ * Function: isEmpty
+ * 
+ * Returns true if the history is empty.
+ */
+mxUndoManager.prototype.isEmpty = function()
+{
+	return this.history.length == 0;
+};
+
+/**
+ * Function: clear
+ * 
+ * Clears the command history.
+ */
+mxUndoManager.prototype.clear = function()
+{
+	this.history = [];
+	this.indexOfNextAdd = 0;
+	this.fireEvent(new mxEventObject(mxEvent.CLEAR));
+};
+
+/**
+ * Function: canUndo
+ * 
+ * Returns true if an undo is possible.
+ */
+mxUndoManager.prototype.canUndo = function()
+{
+	return this.indexOfNextAdd > 0;
+};
+
+/**
+ * Function: undo
+ * 
+ * Undoes the last change.
+ */
+mxUndoManager.prototype.undo = function()
+{
+    while (this.indexOfNextAdd > 0)
+    {
+        var edit = this.history[--this.indexOfNextAdd];
+        edit.undo();
+
+		if (edit.isSignificant())
+        {
+        	this.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit));
+            break;
+        }
+    }
+};
+
+/**
+ * Function: canRedo
+ * 
+ * Returns true if a redo is possible.
+ */
+mxUndoManager.prototype.canRedo = function()
+{
+	return this.indexOfNextAdd < this.history.length;
+};
+
+/**
+ * Function: redo
+ * 
+ * Redoes the last change.
+ */
+mxUndoManager.prototype.redo = function()
+{
+    var n = this.history.length;
+    
+    while (this.indexOfNextAdd < n)
+    {
+        var edit =  this.history[this.indexOfNextAdd++];
+        edit.redo();
+        
+        if (edit.isSignificant())
+        {
+        	this.fireEvent(new mxEventObject(mxEvent.REDO, 'edit', edit));
+            break;
+        }
+    }
+};
+
+/**
+ * Function: undoableEditHappened
+ * 
+ * Method to be called to add new undoable edits to the <history>.
+ */
+mxUndoManager.prototype.undoableEditHappened = function(undoableEdit)
+{
+	this.trim();
+	
+	if (this.size > 0 &&
+		this.size == this.history.length)
+	{
+		this.history.shift();
+	}
+	
+	this.history.push(undoableEdit);
+	this.indexOfNextAdd = this.history.length;
+	this.fireEvent(new mxEventObject(mxEvent.ADD, 'edit', undoableEdit));
+};
+
+/**
+ * Function: trim
+ * 
+ * Removes all pending steps after <indexOfNextAdd> from the history,
+ * invoking die on each edit. This is called from <undoableEditHappened>.
+ */
+mxUndoManager.prototype.trim = function()
+{
+	if (this.history.length > this.indexOfNextAdd)
+	{
+		var edits = this.history.splice(this.indexOfNextAdd,
+			this.history.length - this.indexOfNextAdd);
+			
+		for (var i = 0; i < edits.length; i++)
+		{
+			edits[i].die();
+		}
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/util/mxUndoableEdit.js b/airavata-kubernetes/web-console/src/assets/js/util/mxUndoableEdit.js
new file mode 100644
index 0000000..b12f830
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/util/mxUndoableEdit.js
@@ -0,0 +1,213 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxUndoableEdit
+ * 
+ * Implements a composite undoable edit. Here is an example for a custom change
+ * which gets executed via the model:
+ * 
+ * (code)
+ * function CustomChange(model, name)
+ * {
+ *   this.model = model;
+ *   this.name = name;
+ *   this.previous = name;
+ * };
+ * 
+ * CustomChange.prototype.execute = function()
+ * {
+ *   var tmp = this.model.name;
+ *   this.model.name = this.previous;
+ *   this.previous = tmp;
+ * };
+ * 
+ * var name = prompt('Enter name');
+ * graph.model.execute(new CustomChange(graph.model, name));
+ * (end)
+ * 
+ * Event: mxEvent.EXECUTED
+ * 
+ * Fires between START_EDIT and END_EDIT after an atomic change was executed.
+ * The <code>change</code> property contains the change that was executed.
+ * 
+ * Event: mxEvent.START_EDIT
+ * 
+ * Fires before a set of changes will be executed in <undo> or <redo>.
+ * This event contains no properties.
+ * 
+ * Event: mxEvent.END_EDIT
+ *
+ * Fires after a set of changeswas executed in <undo> or <redo>.
+ * This event contains no properties.
+ * 
+ * Constructor: mxUndoableEdit
+ * 
+ * Constructs a new undoable edit for the given source.
+ */
+function mxUndoableEdit(source, significant)
+{
+	this.source = source;
+	this.changes = [];
+	this.significant = (significant != null) ? significant : true;
+};
+
+/**
+ * Variable: source
+ * 
+ * Specifies the source of the edit.
+ */
+mxUndoableEdit.prototype.source = null;
+
+/**
+ * Variable: changes
+ * 
+ * Array that contains the changes that make up this edit. The changes are
+ * expected to either have an undo and redo function, or an execute
+ * function. Default is an empty array.
+ */
+mxUndoableEdit.prototype.changes = null;
+
+/**
+ * Variable: significant
+ * 
+ * Specifies if the undoable change is significant.
+ * Default is true.
+ */
+mxUndoableEdit.prototype.significant = null;
+
+/**
+ * Variable: undone
+ * 
+ * Specifies if this edit has been undone. Default is false.
+ */
+mxUndoableEdit.prototype.undone = false;
+
+/**
+ * Variable: redone
+ * 
+ * Specifies if this edit has been redone. Default is false.
+ */
+mxUndoableEdit.prototype.redone = false;
+
+/**
+ * Function: isEmpty
+ * 
+ * Returns true if the this edit contains no changes.
+ */
+mxUndoableEdit.prototype.isEmpty = function()
+{
+	return this.changes.length == 0;
+};
+
+/**
+ * Function: isSignificant
+ * 
+ * Returns <significant>.
+ */
+mxUndoableEdit.prototype.isSignificant = function()
+{
+	return this.significant;
+};
+
+/**
+ * Function: add
+ * 
+ * Adds the specified change to this edit. The change is an object that is
+ * expected to either have an undo and redo, or an execute function.
+ */
+mxUndoableEdit.prototype.add = function(change)
+{
+	this.changes.push(change);
+};
+
+/**
+ * Function: notify
+ * 
+ * Hook to notify any listeners of the changes after an <undo> or <redo>
+ * has been carried out. This implementation is empty.
+ */
+mxUndoableEdit.prototype.notify = function() { };
+
+/**
+ * Function: die
+ * 
+ * Hook to free resources after the edit has been removed from the command
+ * history. This implementation is empty.
+ */
+mxUndoableEdit.prototype.die = function() { };
+
+/**
+ * Function: undo
+ * 
+ * Undoes all changes in this edit.
+ */
+mxUndoableEdit.prototype.undo = function()
+{
+	if (!this.undone)
+	{
+		this.source.fireEvent(new mxEventObject(mxEvent.START_EDIT));
+		var count = this.changes.length;
+		
+		for (var i = count - 1; i >= 0; i--)
+		{
+			var change = this.changes[i];
+			
+			if (change.execute != null)
+			{
+				change.execute();
+			}
+			else if (change.undo != null)
+			{
+				change.undo();
+			}
+			
+			// New global executed event
+			this.source.fireEvent(new mxEventObject(mxEvent.EXECUTED, 'change', change));
+		}
+		
+		this.undone = true;
+		this.redone = false;
+		this.source.fireEvent(new mxEventObject(mxEvent.END_EDIT));
+	}
+	
+	this.notify();
+};
+
+/**
+ * Function: redo
+ * 
+ * Redoes all changes in this edit.
+ */
+mxUndoableEdit.prototype.redo = function()
+{
+	if (!this.redone)
+	{
+		this.source.fireEvent(new mxEventObject(mxEvent.START_EDIT));
+		var count = this.changes.length;
+		
+		for (var i = 0; i < count; i++)
+		{
+			var change = this.changes[i];
+			
+			if (change.execute != null)
+			{
+				change.execute();
+			}
+			else if (change.redo != null)
+			{
+				change.redo();
+			}
+			
+			// New global executed event
+			this.source.fireEvent(new mxEventObject(mxEvent.EXECUTED, 'change', change));
+		}
+		
+		this.undone = false;
+		this.redone = true;
+		this.source.fireEvent(new mxEventObject(mxEvent.END_EDIT));
+	}
+	
+	this.notify();
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/util/mxUrlConverter.js b/airavata-kubernetes/web-console/src/assets/js/util/mxUrlConverter.js
new file mode 100644
index 0000000..6aae50e
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/util/mxUrlConverter.js
@@ -0,0 +1,151 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ *
+ * Class: mxUrlConverter
+ * 
+ * Converts relative and absolute URLs to absolute URLs with protocol and domain.
+ */
+var mxUrlConverter = function()
+{
+	// Empty constructor
+};
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies if the converter is enabled. Default is true.
+ */
+mxUrlConverter.prototype.enabled = true;
+
+/**
+ * Variable: baseUrl
+ * 
+ * Specifies the base URL to be used as a prefix for relative URLs.
+ */
+mxUrlConverter.prototype.baseUrl = null;
+
+/**
+ * Variable: baseDomain
+ * 
+ * Specifies the base domain to be used as a prefix for absolute URLs.
+ */
+mxUrlConverter.prototype.baseDomain = null;
+
+/**
+ * Function: updateBaseUrl
+ * 
+ * Private helper function to update the base URL.
+ */
+mxUrlConverter.prototype.updateBaseUrl = function()
+{
+	this.baseDomain = location.protocol + '//' + location.host;
+	this.baseUrl = this.baseDomain + location.pathname;
+	var tmp = this.baseUrl.lastIndexOf('/');
+	
+	// Strips filename etc
+	if (tmp > 0)
+	{
+		this.baseUrl = this.baseUrl.substring(0, tmp + 1);
+	}
+};
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns <enabled>.
+ */
+mxUrlConverter.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ * 
+ * Sets <enabled>.
+ */
+mxUrlConverter.prototype.setEnabled = function(value)
+{
+	this.enabled = value;
+};
+
+/**
+ * Function: getBaseUrl
+ * 
+ * Returns <baseUrl>.
+ */
+mxUrlConverter.prototype.getBaseUrl = function()
+{
+	return this.baseUrl;
+};
+
+/**
+ * Function: setBaseUrl
+ * 
+ * Sets <baseUrl>.
+ */
+mxUrlConverter.prototype.setBaseUrl = function(value)
+{
+	this.baseUrl = value;
+};
+
+/**
+ * Function: getBaseDomain
+ * 
+ * Returns <baseDomain>.
+ */
+mxUrlConverter.prototype.getBaseDomain = function()
+{
+	return this.baseDomain;
+},
+
+/**
+ * Function: setBaseDomain
+ * 
+ * Sets <baseDomain>.
+ */
+mxUrlConverter.prototype.setBaseDomain = function(value)
+{
+	this.baseDomain = value;
+},
+
+/**
+ * Function: isRelativeUrl
+ * 
+ * Returns true if the given URL is relative.
+ */
+mxUrlConverter.prototype.isRelativeUrl = function(url)
+{
+	return url.substring(0, 2) != '//' && url.substring(0, 7) != 'http://' && url.substring(0, 8) != 'https://' && url.substring(0, 10) != 'data:image';
+};
+
+/**
+ * Function: convert
+ * 
+ * Converts the given URL to an absolute URL with protol and domain.
+ * Relative URLs are first converted to absolute URLs.
+ */
+mxUrlConverter.prototype.convert = function(url)
+{
+	if (this.isEnabled() && this.isRelativeUrl(url))
+	{
+		if (this.getBaseUrl() == null)
+		{
+			this.updateBaseUrl();
+		}
+		
+		if (url.charAt(0) == '/')
+		{
+			url = this.getBaseDomain() + url;
+		}
+		else
+		{
+			url = this.getBaseUrl() + url;
+		}
+	}
+	
+	return url;
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/util/mxUtils.js b/airavata-kubernetes/web-console/src/assets/js/util/mxUtils.js
new file mode 100644
index 0000000..ef5a268
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/util/mxUtils.js
@@ -0,0 +1,4353 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+var mxUtils =
+{
+	/**
+	 * Class: mxUtils
+	 * 
+	 * A singleton class that provides cross-browser helper methods.
+	 * This is a global functionality. To access the functions in this
+	 * class, use the global classname appended by the functionname.
+	 * You may have to load chrome://global/content/contentAreaUtils.js
+	 * to disable certain security restrictions in Mozilla for the <open>,
+	 * <save>, <saveAs> and <copy> function.
+	 * 
+	 * For example, the following code displays an error message:
+	 * 
+	 * (code)
+	 * mxUtils.error('Browser is not supported!', 200, false);
+	 * (end)
+	 * 
+	 * Variable: errorResource
+	 * 
+	 * Specifies the resource key for the title of the error window. If the
+	 * resource for this key does not exist then the value is used as
+	 * the title. Default is 'error'.
+	 */
+	errorResource: (mxClient.language != 'none') ? 'error' : '',
+	
+	/**
+	 * Variable: closeResource
+	 * 
+	 * Specifies the resource key for the label of the close button. If the
+	 * resource for this key does not exist then the value is used as
+	 * the label. Default is 'close'.
+	 */
+	closeResource: (mxClient.language != 'none') ? 'close' : '',
+
+	/**
+	 * Variable: errorImage
+	 * 
+	 * Defines the image used for error dialogs.
+	 */
+	errorImage: mxClient.imageBasePath + '/error.gif',
+	
+	/**
+	 * Function: removeCursors
+	 * 
+	 * Removes the cursors from the style of the given DOM node and its
+	 * descendants.
+	 * 
+	 * Parameters:
+	 * 
+	 * element - DOM node to remove the cursor style from.
+	 */
+	removeCursors: function(element)
+	{
+		if (element.style != null)
+		{
+			element.style.cursor = '';
+		}
+		
+		var children = element.childNodes;
+		
+		if (children != null)
+		{
+	        var childCount = children.length;
+	        
+	        for (var i = 0; i < childCount; i += 1)
+	        {
+	            mxUtils.removeCursors(children[i]);
+	        }
+	    }
+	},
+
+	/**
+	 * Function: getCurrentStyle
+	 * 
+	 * Returns the current style of the specified element.
+	 * 
+	 * Parameters:
+	 * 
+	 * element - DOM node whose current style should be returned.
+	 */
+	getCurrentStyle: function()
+	{
+		if (mxClient.IS_IE)
+		{
+			return function(element)
+			{
+				return (element != null) ? element.currentStyle : null;
+			};
+		}
+		else
+		{
+			return function(element)
+			{
+				return (element != null) ?
+					window.getComputedStyle(element, '') :
+					null;
+			};
+		}
+	}(),
+	
+	/**
+	 * Function: parseCssNumber
+	 * 
+	 * Parses the given CSS numeric value adding handling for the values thin,
+	 * medium and thick (2, 4 and 6).
+	 */
+	parseCssNumber: function(value)
+	{
+		if (value == 'thin')
+		{
+			value = '2';
+		}
+		else if (value == 'medium')
+		{
+			value = '4';
+		}
+		else if (value == 'thick')
+		{
+			value = '6';
+		}
+		
+		value = parseFloat(value);
+		
+		if (isNaN(value))
+		{
+			value = 0;
+		}
+		
+		return value;
+	},
+
+	/**
+	 * Function: setPrefixedStyle
+	 * 
+	 * Adds the given style with the standard name and an optional vendor prefix for the current
+	 * browser.
+	 * 
+	 * (code)
+	 * mxUtils.setPrefixedStyle(node.style, 'transformOrigin', '0% 0%');
+	 * (end)
+	 */
+	setPrefixedStyle: function()
+	{
+		var prefix = null;
+		
+		if (mxClient.IS_OT)
+		{
+			prefix = 'O';
+		}
+		else if (mxClient.IS_SF || mxClient.IS_GC)
+		{
+			prefix = 'Webkit';
+		}
+		else if (mxClient.IS_MT)
+		{
+			prefix = 'Moz';
+		}
+		else if (mxClient.IS_IE && document.documentMode >= 9 && document.documentMode < 10)
+		{
+			prefix = 'ms';
+		}
+
+		return function(style, name, value)
+		{
+			style[name] = value;
+			
+			if (prefix != null && name.length > 0)
+			{
+				name = prefix + name.substring(0, 1).toUpperCase() + name.substring(1);
+				style[name] = value;
+			}
+		};
+	}(),
+	
+	/**
+	 * Function: hasScrollbars
+	 * 
+	 * Returns true if the overflow CSS property of the given node is either
+	 * scroll or auto.
+	 * 
+	 * Parameters:
+	 * 
+	 * node - DOM node whose style should be checked for scrollbars.
+	 */
+	hasScrollbars: function(node)
+	{
+		var style = mxUtils.getCurrentStyle(node);
+
+		return style != null && (style.overflow == 'scroll' || style.overflow == 'auto');
+	},
+	
+	/**
+	 * Function: bind
+	 * 
+	 * Returns a wrapper function that locks the execution scope of the given
+	 * function to the specified scope. Inside funct, the "this" keyword
+	 * becomes a reference to that scope.
+	 */
+	bind: function(scope, funct)
+	{
+		return function()
+		{
+			return funct.apply(scope, arguments);
+		};
+	},
+	
+	/**
+	 * Function: eval
+	 * 
+	 * Evaluates the given expression using eval and returns the JavaScript
+	 * object that represents the expression result. Supports evaluation of
+	 * expressions that define functions and returns the function object for
+	 * these expressions.
+	 * 
+	 * Parameters:
+	 * 
+	 * expr - A string that represents a JavaScript expression.
+	 */
+	eval: function(expr)
+	{
+		var result = null;
+
+		if (expr.indexOf('function') >= 0)
+		{
+			try
+			{
+				eval('var _mxJavaScriptExpression='+expr);
+				result = _mxJavaScriptExpression;
+				// TODO: Use delete here?
+				_mxJavaScriptExpression = null;
+			}
+			catch (e)
+			{
+				mxLog.warn(e.message + ' while evaluating ' + expr);
+			}
+		}
+		else
+		{
+			try
+			{
+				result = eval(expr);
+			}
+			catch (e)
+			{
+				mxLog.warn(e.message + ' while evaluating ' + expr);
+			}
+		}
+		
+		return result;
+	},
+	
+	/**
+	 * Function: findNode
+	 * 
+	 * Returns the first node where attr equals value.
+	 * This implementation does not use XPath.
+	 */
+	findNode: function(node, attr, value)
+	{
+		if (node.nodeType == mxConstants.NODETYPE_ELEMENT)
+		{
+			var tmp = node.getAttribute(attr);
+	
+			if (tmp != null && tmp == value)
+			{
+				return node;
+			}
+		}
+		
+		node = node.firstChild;
+		
+		while (node != null)
+		{
+			var result = mxUtils.findNode(node, attr, value);
+			
+			if (result != null)
+			{
+				return result;
+			}
+			
+			node = node.nextSibling;
+		}
+		
+		return null;
+	},
+
+	/**
+	 * Function: getFunctionName
+	 * 
+	 * Returns the name for the given function.
+	 * 
+	 * Parameters:
+	 * 
+	 * f - JavaScript object that represents a function.
+	 */
+	getFunctionName: function(f)
+	{
+		var str = null;
+
+		if (f != null)
+		{
+			if (f.name != null)
+			{
+				str = f.name;
+			}
+			else
+			{
+				str = mxUtils.trim(f.toString());
+				
+				if (/^function\s/.test(str))
+				{
+					str = mxUtils.ltrim(str.substring(9));
+					var idx2 = str.indexOf('(');
+					
+					if (idx2 > 0)
+					{
+						str = str.substring(0, idx2);
+					}
+				}
+			}
+		}
+		
+		return str;
+	},
+
+	/**
+	 * Function: indexOf
+	 * 
+	 * Returns the index of obj in array or -1 if the array does not contain
+	 * the given object.
+	 * 
+	 * Parameters:
+	 * 
+	 * array - Array to check for the given obj.
+	 * obj - Object to find in the given array.
+	 */
+	indexOf: function(array, obj)
+	{
+		if (array != null && obj != null)
+		{
+			for (var i = 0; i < array.length; i++)
+			{
+				if (array[i] == obj)
+				{
+					return i;
+				}
+			}
+		}
+		
+		return -1;
+	},
+
+	/**
+	 * Function: forEach
+	 * 
+	 * Calls the given function for each element of the given array and returns
+	 * the array.
+	 * 
+	 * Parameters:
+	 * 
+	 * array - Array that contains the elements.
+	 * fn - Function to be called for each object.
+	 */
+	forEach: function(array, fn)
+	{
+		if (array != null && fn != null)
+		{
+			for (var i = 0; i < array.length; i++)
+			{
+				fn(array[i]);
+			}
+		}
+		
+		return array;
+	},
+
+	/**
+	 * Function: remove
+	 * 
+	 * Removes all occurrences of the given object in the given array or
+	 * object. If there are multiple occurrences of the object, be they
+	 * associative or as an array entry, all occurrences are removed from
+	 * the array or deleted from the object. By removing the object from
+	 * the array, all elements following the removed element are shifted
+	 * by one step towards the beginning of the array.
+	 * 
+	 * The length of arrays is not modified inside this function.
+	 * 
+	 * Parameters:
+	 * 
+	 * obj - Object to find in the given array.
+	 * array - Array to check for the given obj.
+	 */
+	remove: function(obj, array)
+	{
+		var result = null;
+		
+		if (typeof(array) == 'object')
+		{
+			var index = mxUtils.indexOf(array, obj);
+			
+			while (index >= 0)
+			{
+				array.splice(index, 1);
+				result = obj;
+				index = mxUtils.indexOf(array, obj);
+			}
+		}
+
+		for (var key in array)
+		{
+			if (array[key] == obj)
+			{
+				delete array[key];
+				result = obj;
+			}
+		}
+		
+		return result;
+	},
+	
+	/**
+	 * Function: isNode
+	 * 
+	 * Returns true if the given value is an XML node with the node name
+	 * and if the optional attribute has the specified value.
+	 * 
+	 * This implementation assumes that the given value is a DOM node if the
+	 * nodeType property is numeric, that is, if isNaN returns false for
+	 * value.nodeType.
+	 * 
+	 * Parameters:
+	 * 
+	 * value - Object that should be examined as a node.
+	 * nodeName - String that specifies the node name.
+	 * attributeName - Optional attribute name to check.
+	 * attributeValue - Optional attribute value to check.
+	 */
+	 isNode: function(value, nodeName, attributeName, attributeValue)
+	 {
+	 	if (value != null && !isNaN(value.nodeType) && (nodeName == null ||
+	 		value.nodeName.toLowerCase() == nodeName.toLowerCase()))
+ 		{
+ 			return attributeName == null ||
+ 				value.getAttribute(attributeName) == attributeValue;
+ 		}
+	 	
+	 	return false;
+	 },
+	
+	/**
+	 * Function: isAncestorNode
+	 * 
+	 * Returns true if the given ancestor is an ancestor of the
+	 * given DOM node in the DOM. This also returns true if the
+	 * child is the ancestor.
+	 * 
+	 * Parameters:
+	 * 
+	 * ancestor - DOM node that represents the ancestor.
+	 * child - DOM node that represents the child.
+	 */
+	 isAncestorNode: function(ancestor, child)
+	 {
+	 	var parent = child;
+	 	
+	 	while (parent != null)
+	 	{
+	 		if (parent == ancestor)
+	 		{
+	 			return true;
+	 		}
+
+	 		parent = parent.parentNode;
+	 	}
+	 	
+	 	return false;
+	 },
+
+	/**
+	 * Function: getChildNodes
+	 * 
+	 * Returns an array of child nodes that are of the given node type.
+	 * 
+	 * Parameters:
+	 * 
+	 * node - Parent DOM node to return the children from.
+	 * nodeType - Optional node type to return. Default is
+	 * <mxConstants.NODETYPE_ELEMENT>.
+	 */
+	getChildNodes: function(node, nodeType)
+	{
+		nodeType = nodeType || mxConstants.NODETYPE_ELEMENT;
+		
+		var children = [];
+		var tmp = node.firstChild;
+		
+		while (tmp != null)
+		{
+			if (tmp.nodeType == nodeType)
+			{
+				children.push(tmp);
+			}
+			
+			tmp = tmp.nextSibling;
+		}
+		
+		return children;
+	},
+
+	/**
+	 * Function: importNode
+	 * 
+	 * Cross browser implementation for document.importNode. Uses document.importNode
+	 * in all browsers but IE, where the node is cloned by creating a new node and
+	 * copying all attributes and children into it using importNode, recursively.
+	 * 
+	 * Parameters:
+	 * 
+	 * doc - Document to import the node into.
+	 * node - Node to be imported.
+	 * allChildren - If all children should be imported.
+	 */
+	importNode: function(doc, node, allChildren)
+	{
+		if (mxClient.IS_IE && (document.documentMode == null || document.documentMode < 10))
+		{
+			switch (node.nodeType)
+			{
+				case 1: /* element */
+				{
+					var newNode = doc.createElement(node.nodeName);
+					
+					if (node.attributes && node.attributes.length > 0)
+					{
+						for (var i = 0; i < node.attributes.length; i++)
+						{
+							newNode.setAttribute(node.attributes[i].nodeName,
+								node.getAttribute(node.attributes[i].nodeName));
+						}
+						
+						if (allChildren && node.childNodes && node.childNodes.length > 0)
+						{
+							for (var i = 0; i < node.childNodes.length; i++)
+							{
+								newNode.appendChild(mxUtils.importNode(doc, node.childNodes[i], allChildren));
+							}
+						}
+					}
+					
+					return newNode;
+					break;
+				}
+				case 3: /* text */
+			    case 4: /* cdata-section */
+			    case 8: /* comment */
+			    {
+			      return doc.createTextNode(node.value);
+			      break;
+			    }
+			};
+		}
+		else
+		{
+			return doc.importNode(node, allChildren);
+		}
+	},
+
+	/**
+	 * Function: createXmlDocument
+	 * 
+	 * Returns a new, empty XML document.
+	 */
+	createXmlDocument: function()
+	{
+		var doc = null;
+		
+		if (document.implementation && document.implementation.createDocument)
+		{
+			doc = document.implementation.createDocument('', '', null);
+		}
+		else if (window.ActiveXObject)
+		{
+			doc = new ActiveXObject('Microsoft.XMLDOM');
+	 	}
+	 	
+	 	return doc;
+	},
+
+	/**
+	 * Function: parseXml
+	 * 
+	 * Parses the specified XML string into a new XML document and returns the
+	 * new document.
+	 * 
+	 * Example:
+	 * 
+	 * (code)
+	 * var doc = mxUtils.parseXml(
+	 *   '<mxGraphModel><root><MyDiagram id="0"><mxCell/></MyDiagram>'+
+	 *   '<MyLayer id="1"><mxCell parent="0" /></MyLayer><MyObject id="2">'+
+	 *   '<mxCell style="strokeColor=blue;fillColor=red" parent="1" vertex="1">'+
+	 *   '<mxGeometry x="10" y="10" width="80" height="30" as="geometry"/>'+
+	 *   '</mxCell></MyObject></root></mxGraphModel>');
+	 * (end)
+	 * 
+	 * Parameters:
+	 * 
+	 * xml - String that contains the XML data.
+	 */
+	parseXml: function()
+	{
+		if (window.DOMParser)
+		{
+			return function(xml)
+			{
+				var parser = new DOMParser();
+				
+				return parser.parseFromString(xml, 'text/xml');
+			};
+		}
+		else // IE<=9
+		{
+			return function(xml)
+			{
+				var result = mxUtils.createXmlDocument();
+				result.async = false;
+				// Workaround for parsing errors with SVG DTD
+				result.validateOnParse = false;
+				result.resolveExternals = false;
+				result.loadXML(xml);
+				
+				return result;
+			};
+		}
+	}(),
+
+	/**
+	 * Function: clearSelection
+	 * 
+	 * Clears the current selection in the page.
+	 */
+	clearSelection: function()
+	{
+		if (document.selection)
+		{
+			return function()
+			{
+				document.selection.empty();
+			};
+		}
+		else if (window.getSelection)
+		{
+			return function()
+			{
+				window.getSelection().removeAllRanges();
+			};
+		}
+		else
+		{
+			return function() { };
+		}
+	}(),
+
+	/**
+	 * Function: getPrettyXML
+	 * 
+	 * Returns a pretty printed string that represents the XML tree for the
+	 * given node. This method should only be used to print XML for reading,
+	 * use <getXml> instead to obtain a string for processing.
+	 * 
+	 * Parameters:
+	 * 
+	 * node - DOM node to return the XML for.
+	 * tab - Optional string that specifies the indentation for one level.
+	 * Default is two spaces.
+	 * indent - Optional string that represents the current indentation.
+	 * Default is an empty string.
+	 */
+	getPrettyXml: function(node, tab, indent)
+	{
+		var result = [];
+		
+		if (node != null)
+		{
+			tab = tab || '  ';
+			indent = indent || '';
+			
+			if (node.nodeType == mxConstants.NODETYPE_TEXT)
+			{
+				result.push(node.value);
+			}
+			else
+			{
+				result.push(indent + '<' + node.nodeName);
+				
+				// Creates the string with the node attributes
+				// and converts all HTML entities in the values
+				var attrs = node.attributes;
+				
+				if (attrs != null)
+				{
+					for (var i = 0; i < attrs.length; i++)
+					{
+						var val = mxUtils.htmlEntities(attrs[i].value);
+						result.push(' ' + attrs[i].nodeName + '="' + val + '"');
+					}
+				}
+
+				// Recursively creates the XML string for each
+				// child nodes and appends it here with an
+				// indentation
+				var tmp = node.firstChild;
+				
+				if (tmp != null)
+				{
+					result.push('>\n');
+					
+					while (tmp != null)
+					{
+						result.push(mxUtils.getPrettyXml(tmp, tab, indent + tab));
+						tmp = tmp.nextSibling;
+					}
+					
+					result.push(indent + '</'+node.nodeName + '>\n');
+				}
+				else
+				{
+					result.push('/>\n');
+				}
+			}
+		}
+		
+		return result.join('');
+	},
+	
+	/**
+	 * Function: removeWhitespace
+	 * 
+	 * Removes the sibling text nodes for the given node that only consists
+	 * of tabs, newlines and spaces.
+	 * 
+	 * Parameters:
+	 * 
+	 * node - DOM node whose siblings should be removed.
+	 * before - Optional boolean that specifies the direction of the traversal.
+	 */
+	removeWhitespace: function(node, before)
+	{
+		var tmp = (before) ? node.previousSibling : node.nextSibling;
+		
+		while (tmp != null && tmp.nodeType == mxConstants.NODETYPE_TEXT)
+		{
+			var next = (before) ? tmp.previousSibling : tmp.nextSibling;
+			var text = mxUtils.getTextContent(tmp);
+			
+			if (mxUtils.trim(text).length == 0)
+			{
+				tmp.parentNode.removeChild(tmp);
+			}
+			
+			tmp = next;
+		}
+	},
+	
+	/**
+	 * Function: htmlEntities
+	 * 
+	 * Replaces characters (less than, greater than, newlines and quotes) with
+	 * their HTML entities in the given string and returns the result.
+	 * 
+	 * Parameters:
+	 * 
+	 * s - String that contains the characters to be converted.
+	 * newline - If newlines should be replaced. Default is true.
+	 */
+	htmlEntities: function(s, newline)
+	{
+		s = String(s || '');
+		
+		s = s.replace(/&/g,'&amp;'); // 38 26
+		s = s.replace(/"/g,'&quot;'); // 34 22
+		s = s.replace(/\'/g,'&#39;'); // 39 27
+		s = s.replace(/</g,'&lt;'); // 60 3C
+		s = s.replace(/>/g,'&gt;'); // 62 3E
+
+		if (newline == null || newline)
+		{
+			s = s.replace(/\n/g, '&#xa;');
+		}
+		
+		return s;
+	},
+	
+	/**
+	 * Function: isVml
+	 * 
+	 * Returns true if the given node is in the VML namespace.
+	 * 
+	 * Parameters:
+	 * 
+	 * node - DOM node whose tag urn should be checked.
+	 */
+	isVml: function(node)
+	{
+		return node != null && node.tagUrn == 'urn:schemas-microsoft-com:vml';
+	},
+
+	/**
+	 * Function: getXml
+	 * 
+	 * Returns the XML content of the specified node. For Internet Explorer,
+	 * all \r\n\t[\t]* are removed from the XML string and the remaining \r\n
+	 * are replaced by \n. All \n are then replaced with linefeed, or &#xa; if
+	 * no linefeed is defined.
+	 * 
+	 * Parameters:
+	 * 
+	 * node - DOM node to return the XML for.
+	 * linefeed - Optional string that linefeeds are converted into. Default is
+	 * &#xa;
+	 */
+	getXml: function(node, linefeed)
+	{
+		var xml = '';
+
+		if (window.XMLSerializer != null)
+		{
+			var xmlSerializer = new XMLSerializer();
+			xml = xmlSerializer.serializeToString(node);     
+		}
+		else if (node.xml != null)
+		{
+			xml = node.xml.replace(/\r\n\t[\t]*/g, '').
+				replace(/>\r\n/g, '>').
+				replace(/\r\n/g, '\n');
+		}
+
+		// Replaces linefeeds with HTML Entities.
+		linefeed = linefeed || '&#xa;';
+		xml = xml.replace(/\n/g, linefeed);
+		  
+		return xml;
+	},
+	
+	/**
+	 * Function: extractTextWithWhitespace
+	 * 
+	 * Returns the text content of the specified node.
+	 * 
+	 * Parameters:
+	 * 
+	 * elems - DOM nodes to return the text for.
+	 */
+	extractTextWithWhitespace: function(elems)
+	{
+	    // Known block elements for handling linefeeds (list is not complete)
+		var blocks = ['BLOCKQUOTE', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'OL', 'P', 'PRE', 'TABLE', 'UL'];
+		var ret = [];
+		
+		function doExtract(elts)
+		{
+			// Single break should be ignored
+			if (elts.length == 1 && (elts[0].nodeName == 'BR' ||
+				elts[0].innerHTML == '\n'))
+			{
+				return;
+			}
+			
+		    for (var i = 0; i < elts.length; i++)
+		    {
+		        var elem = elts[i];
+
+				// DIV with a br or linefeed forces a linefeed
+				if (elem.nodeName == 'BR' || elem.innerHTML == '\n' ||
+					((elts.length == 1 || i == 0) && (elem.nodeName == 'DIV' &&
+					elem.innerHTML.toLowerCase() == '<br>')))
+		    	{
+	    			ret.push('\n');
+		    	}
+				else
+				{
+			        if (elem.nodeType === 3 || elem.nodeType === 4)
+			        {
+			        	if (elem.nodeValue.length > 0)
+			        	{
+			        		ret.push(elem.nodeValue);
+			        	}
+			        }
+			        else if (elem.nodeType !== 8 && elem.childNodes.length > 0)
+					{
+						doExtract(elem.childNodes);
+					}
+			        
+	        		if (i < elts.length - 1 && mxUtils.indexOf(blocks, elts[i + 1].nodeName) >= 0)
+	        		{
+	        			ret.push('\n');		
+	        		}
+				}
+		    }
+		};
+		
+		doExtract(elems);
+	    
+	    return ret.join('');
+	},
+
+	/**
+	 * Function: replaceTrailingNewlines
+	 * 
+	 * Replaces each trailing newline with the given pattern.
+	 */
+	replaceTrailingNewlines: function(str, pattern)
+	{
+		// LATER: Check is this can be done with a regular expression
+		var postfix = '';
+		
+		while (str.length > 0 && str.charAt(str.length - 1) == '\n')
+		{
+			str = str.substring(0, str.length - 1);
+			postfix += pattern;
+		}
+		
+		return str + postfix;
+	},
+
+	/**
+	 * Function: getTextContent
+	 * 
+	 * Returns the text content of the specified node.
+	 * 
+	 * Parameters:
+	 * 
+	 * node - DOM node to return the text content for.
+	 */
+	getTextContent: function(node)
+	{
+		if (node.innerText !== undefined)
+		{
+			return node.innerText;
+		}
+		else
+		{
+			return (node != null) ? node[(node.textContent === undefined) ? 'text' : 'textContent'] : '';
+		}
+	},
+	
+	/**
+	 * Function: setTextContent
+	 * 
+	 * Sets the text content of the specified node.
+	 * 
+	 * Parameters:
+	 * 
+	 * node - DOM node to set the text content for.
+	 * text - String that represents the text content.
+	 */
+	setTextContent: function(node, text)
+	{
+		if (node.innerText !== undefined)
+		{
+			node.innerText = text;
+		}
+		else
+		{
+			node[(node.textContent === undefined) ? 'text' : 'textContent'] = text;
+		}
+	},
+	
+	/**
+	 * Function: getInnerHtml
+	 * 
+	 * Returns the inner HTML for the given node as a string or an empty string
+	 * if no node was specified. The inner HTML is the text representing all
+	 * children of the node, but not the node itself.
+	 * 
+	 * Parameters:
+	 * 
+	 * node - DOM node to return the inner HTML for.
+	 */
+	getInnerHtml: function()
+	{
+		if (mxClient.IS_IE)
+		{
+			return function(node)
+			{
+				if (node != null)
+				{
+					return node.innerHTML;
+				}
+				
+				return '';
+			};
+		}
+		else
+		{
+			return function(node)
+			{
+				if (node != null)
+				{
+					var serializer = new XMLSerializer();
+					return serializer.serializeToString(node);
+				}
+				
+				return '';
+			};
+		}
+	}(),
+
+	/**
+	 * Function: getOuterHtml
+	 * 
+	 * Returns the outer HTML for the given node as a string or an empty
+	 * string if no node was specified. The outer HTML is the text representing
+	 * all children of the node including the node itself.
+	 * 
+	 * Parameters:
+	 * 
+	 * node - DOM node to return the outer HTML for.
+	 */
+	getOuterHtml: function()
+	{
+		if (mxClient.IS_IE)
+		{
+			return function(node)
+			{
+				if (node != null)
+				{
+					if (node.outerHTML != null)
+					{
+						return node.outerHTML;
+					}
+					else
+					{
+						var tmp = [];
+						tmp.push('<'+node.nodeName);
+						
+						var attrs = node.attributes;
+						
+						if (attrs != null)
+						{
+							for (var i = 0; i < attrs.length; i++)
+							{
+								var value = attrs[i].value;
+								
+								if (value != null && value.length > 0)
+								{
+									tmp.push(' ');
+									tmp.push(attrs[i].nodeName);
+									tmp.push('="');
+									tmp.push(value);
+									tmp.push('"');
+								}
+							}
+						}
+						
+						if (node.innerHTML.length == 0)
+						{
+							tmp.push('/>');
+						}
+						else
+						{
+							tmp.push('>');
+							tmp.push(node.innerHTML);
+							tmp.push('</'+node.nodeName+'>');
+						}
+						
+						return tmp.join('');
+					}
+				}
+				
+				return '';
+			};
+		}
+		else
+		{
+			return function(node)
+			{
+				if (node != null)
+				{
+					var serializer = new XMLSerializer();
+					return serializer.serializeToString(node);
+				}
+				
+				return '';
+			};
+		}
+	}(),
+	
+	/**
+	 * Function: write
+	 * 
+	 * Creates a text node for the given string and appends it to the given
+	 * parent. Returns the text node.
+	 * 
+	 * Parameters:
+	 * 
+	 * parent - DOM node to append the text node to.
+	 * text - String representing the text to be added.
+	 */
+	write: function(parent, text)
+	{
+		var doc = parent.ownerDocument;
+		var node = doc.createTextNode(text);
+		
+		if (parent != null)
+		{
+			parent.appendChild(node);
+		}
+		
+		return node;
+	},
+	
+	/**
+	 * Function: writeln
+	 * 
+	 * Creates a text node for the given string and appends it to the given
+	 * parent with an additional linefeed. Returns the text node.
+	 * 
+	 * Parameters:
+	 * 
+	 * parent - DOM node to append the text node to.
+	 * text - String representing the text to be added.
+	 */
+	writeln: function(parent, text)
+	{
+		var doc = parent.ownerDocument;
+		var node = doc.createTextNode(text);
+		
+		if (parent != null)
+		{
+			parent.appendChild(node);
+			parent.appendChild(document.createElement('br'));
+		}
+		
+		return node;
+	},
+	
+	/**
+	 * Function: br
+	 * 
+	 * Appends a linebreak to the given parent and returns the linebreak.
+	 * 
+	 * Parameters:
+	 * 
+	 * parent - DOM node to append the linebreak to.
+	 */
+	br: function(parent, count)
+	{
+		count = count || 1;
+		var br = null;
+		
+		for (var i = 0; i < count; i++)
+		{
+			if (parent != null)
+			{
+				br = parent.ownerDocument.createElement('br');
+				parent.appendChild(br);
+			}
+		}
+		
+		return br;
+	},
+		
+	/**
+	 * Function: button
+	 * 
+	 * Returns a new button with the given level and function as an onclick
+	 * event handler.
+	 * 
+	 * (code)
+	 * document.body.appendChild(mxUtils.button('Test', function(evt)
+	 * {
+	 *   alert('Hello, World!');
+	 * }));
+	 * (end)
+	 * 
+	 * Parameters:
+	 * 
+	 * label - String that represents the label of the button.
+	 * funct - Function to be called if the button is pressed.
+	 * doc - Optional document to be used for creating the button. Default is the
+	 * current document.
+	 */
+	button: function(label, funct, doc)
+	{
+		doc = (doc != null) ? doc : document;
+		
+		var button = doc.createElement('button');
+		mxUtils.write(button, label);
+
+		mxEvent.addListener(button, 'click', function(evt)
+		{
+			funct(evt);
+		});
+		
+		return button;
+	},
+	
+	/**
+	 * Function: para
+	 * 
+	 * Appends a new paragraph with the given text to the specified parent and
+	 * returns the paragraph.
+	 * 
+	 * Parameters:
+	 * 
+	 * parent - DOM node to append the text node to.
+	 * text - String representing the text for the new paragraph.
+	 */
+	para: function(parent, text)
+	{
+		var p = document.createElement('p');
+		mxUtils.write(p, text);
+
+		if (parent != null)
+		{
+			parent.appendChild(p);
+		}
+		
+		return p;
+	},
+
+	/**
+	 * Function: addTransparentBackgroundFilter
+	 * 
+	 * Adds a transparent background to the filter of the given node. This
+	 * background can be used in IE8 standards mode (native IE8 only) to pass
+	 * events through the node.
+	 */
+	addTransparentBackgroundFilter: function(node)
+	{
+		node.style.filter += 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'' +
+			mxClient.imageBasePath + '/transparent.gif\', sizingMethod=\'scale\')';
+	},
+
+	/**
+	 * Function: linkAction
+	 * 
+	 * Adds a hyperlink to the specified parent that invokes action on the
+	 * specified editor.
+	 * 
+	 * Parameters:
+	 * 
+	 * parent - DOM node to contain the new link.
+	 * text - String that is used as the link label.
+	 * editor - <mxEditor> that will execute the action.
+	 * action - String that defines the name of the action to be executed.
+	 * pad - Optional left-padding for the link. Default is 0.
+	 */
+	linkAction: function(parent, text, editor, action, pad)
+	{
+		return mxUtils.link(parent, text, function()
+		{
+			editor.execute(action);
+		}, pad);
+	},
+
+	/**
+	 * Function: linkInvoke
+	 * 
+	 * Adds a hyperlink to the specified parent that invokes the specified
+	 * function on the editor passing along the specified argument. The
+	 * function name is the name of a function of the editor instance,
+	 * not an action name.
+	 * 
+	 * Parameters:
+	 * 
+	 * parent - DOM node to contain the new link.
+	 * text - String that is used as the link label.
+	 * editor - <mxEditor> instance to execute the function on.
+	 * functName - String that represents the name of the function.
+	 * arg - Object that represents the argument to the function.
+	 * pad - Optional left-padding for the link. Default is 0.
+	 */
+	linkInvoke: function(parent, text, editor, functName, arg, pad)
+	{
+		return mxUtils.link(parent, text, function()
+		{
+			editor[functName](arg);
+		}, pad);
+	},
+	
+	/**
+	 * Function: link
+	 * 
+	 * Adds a hyperlink to the specified parent and invokes the given function
+	 * when the link is clicked.
+	 * 
+	 * Parameters:
+	 * 
+	 * parent - DOM node to contain the new link.
+	 * text - String that is used as the link label.
+	 * funct - Function to execute when the link is clicked.
+	 * pad - Optional left-padding for the link. Default is 0.
+	 */
+	link: function(parent, text, funct, pad)
+	{
+		var a = document.createElement('span');
+		
+		a.style.color = 'blue';
+		a.style.textDecoration = 'underline';
+		a.style.cursor = 'pointer';
+		
+		if (pad != null)
+		{
+			a.style.paddingLeft = pad+'px';
+		}
+		
+		mxEvent.addListener(a, 'click', funct);
+		mxUtils.write(a, text);
+		
+		if (parent != null)
+		{
+			parent.appendChild(a);
+		}
+		
+		return a;
+	},
+
+	/**
+	 * Function: fit
+	 * 
+	 * Makes sure the given node is inside the visible area of the window. This
+	 * is done by setting the left and top in the style. 
+	 */
+	fit: function(node)
+	{
+		var left = parseInt(node.offsetLeft);
+		var width = parseInt(node.offsetWidth);
+			
+		var offset = mxUtils.getDocumentScrollOrigin(node.ownerDocument);
+		var sl = offset.x;
+		var st = offset.y;
+
+		var b = document.body;
+		var d = document.documentElement;
+		var right = (sl) + (b.clientWidth || d.clientWidth);
+		
+		if (left + width > right)
+		{
+			node.style.left = Math.max(sl, right - width) + 'px';
+		}
+		
+		var top = parseInt(node.offsetTop);
+		var height = parseInt(node.offsetHeight);
+		
+		var bottom = st + Math.max(b.clientHeight || 0, d.clientHeight);
+		
+		if (top + height > bottom)
+		{
+			node.style.top = Math.max(st, bottom - height) + 'px';
+		}
+	},
+
+	/**
+	 * Function: load
+	 * 
+	 * Loads the specified URL *synchronously* and returns the <mxXmlRequest>.
+	 * Throws an exception if the file cannot be loaded. See <mxUtils.get> for
+	 * an asynchronous implementation.
+	 *
+	 * Example:
+	 * 
+	 * (code)
+	 * try
+	 * {
+	 *   var req = mxUtils.load(filename);
+	 *   var root = req.getDocumentElement();
+	 *   // Process XML DOM...
+	 * }
+	 * catch (ex)
+	 * {
+	 *   mxUtils.alert('Cannot load '+filename+': '+ex);
+	 * }
+	 * (end)
+	 * 
+	 * Parameters:
+	 * 
+	 * url - URL to get the data from.
+	 */
+	load: function(url)
+	{
+		var req = new mxXmlRequest(url, null, 'GET', false);
+		req.send();
+		
+		return req;
+	},
+
+	/**
+	 * Function: get
+	 * 
+	 * Loads the specified URL *asynchronously* and invokes the given functions
+	 * depending on the request status. Returns the <mxXmlRequest> in use. Both
+	 * functions take the <mxXmlRequest> as the only parameter. See
+	 * <mxUtils.load> for a synchronous implementation.
+	 *
+	 * Example:
+	 * 
+	 * (code)
+	 * mxUtils.get(url, function(req)
+	 * {
+	 *    var node = req.getDocumentElement();
+	 *    // Process XML DOM...
+	 * });
+	 * (end)
+	 * 
+	 * So for example, to load a diagram into an existing graph model, the
+	 * following code is used.
+	 * 
+	 * (code)
+	 * mxUtils.get(url, function(req)
+	 * {
+	 *   var node = req.getDocumentElement();
+	 *   var dec = new mxCodec(node.ownerDocument);
+	 *   dec.decode(node, graph.getModel());
+	 * });
+	 * (end)
+	 * 
+	 * Parameters:
+	 * 
+	 * url - URL to get the data from.
+	 * onload - Optional function to execute for a successful response.
+	 * onerror - Optional function to execute on error.
+	 * binary - Optional boolean parameter that specifies if the request is
+	 * binary.
+	 * timeout - Optional timeout in ms before calling ontimeout.
+	 * ontimeout - Optional function to execute on timeout.
+	 */
+	get: function(url, onload, onerror, binary, timeout, ontimeout)
+	{
+		var req = new mxXmlRequest(url, null, 'GET');
+		
+		if (binary != null)
+		{
+			req.setBinary(binary);
+		}
+		
+		req.send(onload, onerror, timeout, ontimeout);
+		
+		return req;
+	},
+
+	/**
+	 * Function: getAll
+	 * 
+	 * Loads the URLs in the given array *asynchronously* and invokes the given function
+	 * if all requests returned with a valid 2xx status. The error handler is invoked
+	 * once on the first error or invalid response.
+	 *
+	 * Parameters:
+	 * 
+	 * urls - Array of URLs to be loaded.
+	 * onload - Callback with array of <mxXmlRequests>.
+	 * onerror - Optional function to execute on error.
+	 */
+	getAll: function(urls, onload, onerror)
+	{
+		var remain = urls.length;
+		var result = [];
+		var errors = 0;
+		var err = function()
+		{
+			if (errors == 0 && onerror != null)
+			{
+				onerror();
+			}
+
+			errors++;
+		};
+		
+		for (var i = 0; i < urls.length; i++)
+		{
+			(function(url, index)
+			{
+				mxUtils.get(url, function(req)
+				{
+					var status = req.getStatus();
+					
+					if (status < 200 || status > 299)
+					{
+						err();
+					}
+					else
+					{
+						result[index] = req;
+						remain--;
+						
+						if (remain == 0)
+						{
+							onload(result);
+						}
+					}
+				}, err);
+			})(urls[i], i);
+		}
+		
+		if (remain == 0)
+		{
+			onload(result);			
+		}
+	},
+	
+	/**
+	 * Function: post
+	 * 
+	 * Posts the specified params to the given URL *asynchronously* and invokes
+	 * the given functions depending on the request status. Returns the
+	 * <mxXmlRequest> in use. Both functions take the <mxXmlRequest> as the
+	 * only parameter. Make sure to use encodeURIComponent for the parameter
+	 * values.
+	 *
+	 * Example:
+	 * 
+	 * (code)
+	 * mxUtils.post(url, 'key=value', function(req)
+	 * {
+	 * 	mxUtils.alert('Ready: '+req.isReady()+' Status: '+req.getStatus());
+	 *  // Process req.getDocumentElement() using DOM API if OK...
+	 * });
+	 * (end)
+	 * 
+	 * Parameters:
+	 * 
+	 * url - URL to get the data from.
+	 * params - Parameters for the post request.
+	 * onload - Optional function to execute for a successful response.
+	 * onerror - Optional function to execute on error.
+	 */
+	post: function(url, params, onload, onerror)
+	{
+		return new mxXmlRequest(url, params).send(onload, onerror);
+	},
+	
+	/**
+	 * Function: submit
+	 * 
+	 * Submits the given parameters to the specified URL using
+	 * <mxXmlRequest.simulate> and returns the <mxXmlRequest>.
+	 * Make sure to use encodeURIComponent for the parameter
+	 * values.
+	 * 
+	 * Parameters:
+	 * 
+	 * url - URL to get the data from.
+	 * params - Parameters for the form.
+	 * doc - Document to create the form in.
+	 * target - Target to send the form result to.
+	 */
+	submit: function(url, params, doc, target)
+	{
+		return new mxXmlRequest(url, params).simulate(doc, target);
+	},
+	
+	/**
+	 * Function: loadInto
+	 * 
+	 * Loads the specified URL *asynchronously* into the specified document,
+	 * invoking onload after the document has been loaded. This implementation
+	 * does not use <mxXmlRequest>, but the document.load method.
+	 * 
+	 * Parameters:
+	 * 
+	 * url - URL to get the data from.
+	 * doc - The document to load the URL into.
+	 * onload - Function to execute when the URL has been loaded.
+	 */
+	loadInto: function(url, doc, onload)
+	{
+		if (mxClient.IS_IE)
+		{
+			doc.onreadystatechange = function ()
+			{
+				if (doc.readyState == 4)
+				{
+					onload();
+				}
+			};
+		}
+		else
+		{
+			doc.addEventListener('load', onload, false);
+		}
+		
+		doc.load(url);
+	},
+	
+	/**
+	 * Function: getValue
+	 * 
+	 * Returns the value for the given key in the given associative array or
+	 * the given default value if the value is null.
+	 * 
+	 * Parameters:
+	 * 
+	 * array - Associative array that contains the value for the key.
+	 * key - Key whose value should be returned.
+	 * defaultValue - Value to be returned if the value for the given
+	 * key is null.
+	 */
+	getValue: function(array, key, defaultValue)
+	{
+		var value = (array != null) ? array[key] : null;
+
+		if (value == null)
+		{
+			value = defaultValue;			
+		}
+		
+		return value;
+	},
+	
+	/**
+	 * Function: getNumber
+	 * 
+	 * Returns the numeric value for the given key in the given associative
+	 * array or the given default value (or 0) if the value is null. The value
+	 * is converted to a numeric value using the Number function.
+	 * 
+	 * Parameters:
+	 * 
+	 * array - Associative array that contains the value for the key.
+	 * key - Key whose value should be returned.
+	 * defaultValue - Value to be returned if the value for the given
+	 * key is null. Default is 0.
+	 */
+	getNumber: function(array, key, defaultValue)
+	{
+		var value = (array != null) ? array[key] : null;
+
+		if (value == null)
+		{
+			value = defaultValue || 0;			
+		}
+		
+		return Number(value);
+	},
+	
+	/**
+	 * Function: getColor
+	 * 
+	 * Returns the color value for the given key in the given associative
+	 * array or the given default value if the value is null. If the value
+	 * is <mxConstants.NONE> then null is returned.
+	 * 
+	 * Parameters:
+	 * 
+	 * array - Associative array that contains the value for the key.
+	 * key - Key whose value should be returned.
+	 * defaultValue - Value to be returned if the value for the given
+	 * key is null. Default is null.
+	 */
+	getColor: function(array, key, defaultValue)
+	{
+		var value = (array != null) ? array[key] : null;
+
+		if (value == null)
+		{
+			value = defaultValue;
+		}
+		else if (value == mxConstants.NONE)
+		{
+			value = null;
+		}
+		
+		return value;
+	},
+
+	/**
+	 * Function: clone
+	 * 
+	 * Recursively clones the specified object ignoring all fieldnames in the
+	 * given array of transient fields. <mxObjectIdentity.FIELD_NAME> is always
+	 * ignored by this function.
+	 * 
+	 * Parameters:
+	 * 
+	 * obj - Object to be cloned.
+	 * transients - Optional array of strings representing the fieldname to be
+	 * ignored.
+	 * shallow - Optional boolean argument to specify if a shallow clone should
+	 * be created, that is, one where all object references are not cloned or,
+	 * in other words, one where only atomic (strings, numbers) values are
+	 * cloned. Default is false.
+	 */
+	clone: function(obj, transients, shallow)
+	{
+		shallow = (shallow != null) ? shallow : false;
+		var clone = null;
+		
+		if (obj != null && typeof(obj.constructor) == 'function')
+		{
+			clone = new obj.constructor();
+			
+		    for (var i in obj)
+		    {
+		    	if (i != mxObjectIdentity.FIELD_NAME && (transients == null ||
+		    		mxUtils.indexOf(transients, i) < 0))
+		    	{
+			    	if (!shallow && typeof(obj[i]) == 'object')
+			    	{
+			            clone[i] = mxUtils.clone(obj[i]);
+			        }
+			        else
+			        {
+			            clone[i] = obj[i];
+			        }
+				}
+		    }
+		}
+		
+	    return clone;
+	},
+
+	/**
+	 * Function: equalPoints
+	 * 
+	 * Compares all mxPoints in the given lists.
+	 * 
+	 * Parameters:
+	 * 
+	 * a - Array of <mxPoints> to be compared.
+	 * b - Array of <mxPoints> to be compared.
+	 */
+	equalPoints: function(a, b)
+	{
+		if ((a == null && b != null) || (a != null && b == null) ||
+			(a != null && b != null && a.length != b.length))
+		{
+			return false;
+		}
+		else if (a != null && b != null)
+		{
+			for (var i = 0; i < a.length; i++)
+			{
+				if (a[i] == b[i] || (a[i] != null && !a[i].equals(b[i])))
+				{
+					return false;
+				}
+			}
+		}
+		
+		return true;
+	},
+
+	/**
+	 * Function: equalEntries
+	 * 
+	 * Returns true if all properties of the given objects are equal. Values
+	 * with NaN are equal to NaN and unequal to any other value.
+	 * 
+	 * Parameters:
+	 * 
+	 * a - First object to be compared.
+	 * b - Second object to be compared.
+	 */
+	equalEntries: function(a, b)
+	{
+		if ((a == null && b != null) || (a != null && b == null) ||
+			(a != null && b != null && a.length != b.length))
+		{
+			return false;
+		}
+		else if (a != null && b != null)
+		{
+			// Counts keys in b to check if all values have been compared
+			var count = 0;
+			
+			for (var key in b)
+			{
+				count++;
+			}
+			
+			for (var key in a)
+			{
+				count--
+				
+				if ((!mxUtils.isNaN(a[key]) || !mxUtils.isNaN(b[key])) && a[key] != b[key])
+				{
+					return false;
+				}
+			}
+		}
+		
+		return count == 0;
+	},
+	
+	/**
+	 * Function: removeDuplicates
+	 * 
+	 * Removes all duplicates from the given array.
+	 */
+	removeDuplicates: function(arr)
+	{
+		var dict = new mxDictionary();
+		var result = [];
+		
+		for (var i = 0; i < arr.length; i++)
+		{
+			if (!dict.get(arr[i]))
+			{
+				result.push(arr[i]);
+				dict.put(arr[i], true);
+			}
+		}
+
+		return result;
+	},
+	
+	/**
+	 * Function: isNaN
+	 *
+	 * Returns true if the given value is of type number and isNaN returns true.
+	 */
+	isNaN: function(value)
+	{
+		return typeof(value) == 'number' && isNaN(value);
+	},
+	
+	/**
+	 * Function: extend
+	 *
+	 * Assigns a copy of the superclass prototype to the subclass prototype.
+	 * Note that this does not call the constructor of the superclass at this
+	 * point, the superclass constructor should be called explicitely in the
+	 * subclass constructor. Below is an example.
+	 * 
+	 * (code)
+	 * MyGraph = function(container, model, renderHint, stylesheet)
+	 * {
+	 *   mxGraph.call(this, container, model, renderHint, stylesheet);
+	 * }
+	 * 
+	 * mxUtils.extend(MyGraph, mxGraph);
+	 * (end)
+	 * 
+	 * Parameters:
+	 * 
+	 * ctor - Constructor of the subclass.
+	 * superCtor - Constructor of the superclass.
+	 */
+	extend: function(ctor, superCtor)
+	{
+		var f = function() {};
+		f.prototype = superCtor.prototype;
+		
+		ctor.prototype = new f();
+		ctor.prototype.constructor = ctor;
+	},
+
+	/**
+	 * Function: toString
+	 * 
+	 * Returns a textual representation of the specified object.
+	 * 
+	 * Parameters:
+	 * 
+	 * obj - Object to return the string representation for.
+	 */
+	toString: function(obj)
+	{
+	    var output = '';
+	    
+	    for (var i in obj)
+	    {
+	    	try
+	    	{
+			    if (obj[i] == null)
+			    {
+		            output += i + ' = [null]\n';
+			    }
+			    else if (typeof(obj[i]) == 'function')
+			    {
+		            output += i + ' => [Function]\n';
+		        }
+		        else if (typeof(obj[i]) == 'object')
+		        {
+		        	var ctor = mxUtils.getFunctionName(obj[i].constructor); 
+		            output += i + ' => [' + ctor + ']\n';
+		        }
+		        else
+		        {
+		            output += i + ' = ' + obj[i] + '\n';
+		        }
+	    	}
+	    	catch (e)
+	    	{
+	    		output += i + '=' + e.message;
+	    	}
+	    }
+	    
+	    return output;
+	},
+
+	/**
+	 * Function: toRadians
+	 * 
+	 * Converts the given degree to radians.
+	 */
+	toRadians: function(deg)
+	{
+		return Math.PI * deg / 180;
+	},
+
+	/**
+	 * Function: toDegree
+	 * 
+	 * Converts the given radians to degree.
+	 */
+	toDegree: function(rad)
+	{
+		return rad * 180 / Math.PI;
+	},
+	
+	/**
+	 * Function: arcToCurves
+	 * 
+	 * Converts the given arc to a series of curves.
+	 */
+	arcToCurves: function(x0, y0, r1, r2, angle, largeArcFlag, sweepFlag, x, y)
+	{
+		x -= x0;
+		y -= y0;
+		
+        if (r1 === 0 || r2 === 0) 
+        {
+        	return result;
+        }
+        
+        var fS = sweepFlag;
+        var psai = angle;
+        r1 = Math.abs(r1);
+        r2 = Math.abs(r2);
+        var ctx = -x / 2;
+        var cty = -y / 2;
+        var cpsi = Math.cos(psai * Math.PI / 180);
+        var spsi = Math.sin(psai * Math.PI / 180);
+        var rxd = cpsi * ctx + spsi * cty;
+        var ryd = -1 * spsi * ctx + cpsi * cty;
+        var rxdd = rxd * rxd;
+        var rydd = ryd * ryd;
+        var r1x = r1 * r1;
+        var r2y = r2 * r2;
+        var lamda = rxdd / r1x + rydd / r2y;
+        var sds;
+        
+        if (lamda > 1) 
+        {
+        	r1 = Math.sqrt(lamda) * r1;
+        	r2 = Math.sqrt(lamda) * r2;
+        	sds = 0;
+        }  
+        else
+        {
+        	var seif = 1;
+            
+        	if (largeArcFlag === fS) 
+        	{
+        		seif = -1;
+        	}
+            
+        	sds = seif * Math.sqrt((r1x * r2y - r1x * rydd - r2y * rxdd) / (r1x * rydd + r2y * rxdd));
+        }
+        
+        var txd = sds * r1 * ryd / r2;
+        var tyd = -1 * sds * r2 * rxd / r1;
+        var tx = cpsi * txd - spsi * tyd + x / 2;
+        var ty = spsi * txd + cpsi * tyd + y / 2;
+        var rad = Math.atan2((ryd - tyd) / r2, (rxd - txd) / r1) - Math.atan2(0, 1);
+        var s1 = (rad >= 0) ? rad : 2 * Math.PI + rad;
+        rad = Math.atan2((-ryd - tyd) / r2, (-rxd - txd) / r1) - Math.atan2((ryd - tyd) / r2, (rxd - txd) / r1);
+        var dr = (rad >= 0) ? rad : 2 * Math.PI + rad;
+        
+        if (fS == 0 && dr > 0) 
+        {
+        	dr -= 2 * Math.PI;
+        }
+        else if (fS != 0 && dr < 0) 
+        {
+        	dr += 2 * Math.PI;
+        }
+        
+        var sse = dr * 2 / Math.PI;
+        var seg = Math.ceil(sse < 0 ? -1 * sse : sse);
+        var segr = dr / seg;
+        var t = 8/3 * Math.sin(segr / 4) * Math.sin(segr / 4) / Math.sin(segr / 2);
+        var cpsir1 = cpsi * r1;
+        var cpsir2 = cpsi * r2;
+        var spsir1 = spsi * r1;
+        var spsir2 = spsi * r2;
+        var mc = Math.cos(s1);
+        var ms = Math.sin(s1);
+        var x2 = -t * (cpsir1 * ms + spsir2 * mc);
+        var y2 = -t * (spsir1 * ms - cpsir2 * mc);
+        var x3 = 0;
+        var y3 = 0;
+
+		var result = [];
+        
+        for (var n = 0; n < seg; ++n) 
+        {
+            s1 += segr;
+            mc = Math.cos(s1);
+            ms = Math.sin(s1);
+            
+            x3 = cpsir1 * mc - spsir2 * ms + tx;
+            y3 = spsir1 * mc + cpsir2 * ms + ty;
+            var dx = -t * (cpsir1 * ms + spsir2 * mc);
+            var dy = -t * (spsir1 * ms - cpsir2 * mc);
+            
+            // CurveTo updates x0, y0 so need to restore it
+            var index = n * 6;
+            result[index] = Number(x2 + x0);
+            result[index + 1] = Number(y2 + y0);
+            result[index + 2] = Number(x3 - dx + x0);
+            result[index + 3] = Number(y3 - dy + y0);
+            result[index + 4] = Number(x3 + x0);
+            result[index + 5] = Number(y3 + y0);
+            
+			x2 = x3 + dx;
+            y2 = y3 + dy;
+        }
+        
+        return result;
+	},
+
+	/**
+	 * Function: getBoundingBox
+	 * 
+	 * Returns the bounding box for the rotated rectangle.
+	 * 
+	 * Parameters:
+	 * 
+	 * rect - <mxRectangle> to be rotated.
+	 * angle - Number that represents the angle (in degrees).
+	 * cx - Optional <mxPoint> that represents the rotation center. If no
+	 * rotation center is given then the center of rect is used.
+	 */
+	getBoundingBox: function(rect, rotation, cx)
+	{
+        var result = null;
+
+        if (rect != null && rotation != null && rotation != 0)
+        {
+            var rad = mxUtils.toRadians(rotation);
+            var cos = Math.cos(rad);
+            var sin = Math.sin(rad);
+
+            cx = (cx != null) ? cx : new mxPoint(rect.x + rect.width / 2, rect.y  + rect.height / 2);
+
+            var p1 = new mxPoint(rect.x, rect.y);
+            var p2 = new mxPoint(rect.x + rect.width, rect.y);
+            var p3 = new mxPoint(p2.x, rect.y + rect.height);
+            var p4 = new mxPoint(rect.x, p3.y);
+
+            p1 = mxUtils.getRotatedPoint(p1, cos, sin, cx);
+            p2 = mxUtils.getRotatedPoint(p2, cos, sin, cx);
+            p3 = mxUtils.getRotatedPoint(p3, cos, sin, cx);
+            p4 = mxUtils.getRotatedPoint(p4, cos, sin, cx);
+
+            result = new mxRectangle(p1.x, p1.y, 0, 0);
+            result.add(new mxRectangle(p2.x, p2.y, 0, 0));
+            result.add(new mxRectangle(p3.x, p3.y, 0, 0));
+            result.add(new mxRectangle(p4.x, p4.y, 0, 0));
+        }
+
+        return result;
+	},
+
+	/**
+	 * Function: getRotatedPoint
+	 * 
+	 * Rotates the given point by the given cos and sin.
+	 */
+	getRotatedPoint: function(pt, cos, sin, c)
+	{
+		c = (c != null) ? c : new mxPoint();
+		var x = pt.x - c.x;
+		var y = pt.y - c.y;
+
+		var x1 = x * cos - y * sin;
+		var y1 = y * cos + x * sin;
+
+		return new mxPoint(x1 + c.x, y1 + c.y);
+	},
+	
+	/**
+	 * Returns an integer mask of the port constraints of the given map
+	 * @param dict the style map to determine the port constraints for
+	 * @param defaultValue Default value to return if the key is undefined.
+	 * @return the mask of port constraint directions
+	 * 
+	 * Parameters:
+	 * 
+	 * terminal - <mxCelState> that represents the terminal.
+	 * edge - <mxCellState> that represents the edge.
+	 * source - Boolean that specifies if the terminal is the source terminal.
+	 * defaultValue - Default value to be returned.
+	 */
+	getPortConstraints: function(terminal, edge, source, defaultValue)
+	{
+		var value = mxUtils.getValue(terminal.style, mxConstants.STYLE_PORT_CONSTRAINT,
+			mxUtils.getValue(edge.style, (source) ? mxConstants.STYLE_SOURCE_PORT_CONSTRAINT :
+				mxConstants.STYLE_TARGET_PORT_CONSTRAINT, null));
+		
+		if (value == null)
+		{
+			return defaultValue;
+		}
+		else
+		{
+			var directions = value.toString();
+			var returnValue = mxConstants.DIRECTION_MASK_NONE;
+			var constraintRotationEnabled = mxUtils.getValue(terminal.style, mxConstants.STYLE_PORT_CONSTRAINT_ROTATION, 0);
+			var rotation = 0;
+			
+			if (constraintRotationEnabled == 1)
+			{
+				rotation = mxUtils.getValue(terminal.style, mxConstants.STYLE_ROTATION, 0);
+			}
+			
+			var quad = 0;
+
+			if (rotation > 45)
+			{
+				quad = 1;
+				
+				if (rotation >= 135)
+				{
+					quad = 2;
+				}
+			}
+			else if (rotation < -45)
+			{
+				quad = 3;
+				
+				if (rotation <= -135)
+				{
+					quad = 2;
+				}
+			}
+
+			if (directions.indexOf(mxConstants.DIRECTION_NORTH) >= 0)
+			{
+				switch (quad)
+				{
+					case 0:
+						returnValue |= mxConstants.DIRECTION_MASK_NORTH;
+						break;
+					case 1:
+						returnValue |= mxConstants.DIRECTION_MASK_EAST;
+						break;
+					case 2:
+						returnValue |= mxConstants.DIRECTION_MASK_SOUTH;
+						break;
+					case 3:
+						returnValue |= mxConstants.DIRECTION_MASK_WEST;
+						break;
+				}
+			}
+			if (directions.indexOf(mxConstants.DIRECTION_WEST) >= 0)
+			{
+				switch (quad)
+				{
+					case 0:
+						returnValue |= mxConstants.DIRECTION_MASK_WEST;
+						break;
+					case 1:
+						returnValue |= mxConstants.DIRECTION_MASK_NORTH;
+						break;
+					case 2:
+						returnValue |= mxConstants.DIRECTION_MASK_EAST;
+						break;
+					case 3:
+						returnValue |= mxConstants.DIRECTION_MASK_SOUTH;
+						break;
+				}
+			}
+			if (directions.indexOf(mxConstants.DIRECTION_SOUTH) >= 0)
+			{
+				switch (quad)
+				{
+					case 0:
+						returnValue |= mxConstants.DIRECTION_MASK_SOUTH;
+						break;
+					case 1:
+						returnValue |= mxConstants.DIRECTION_MASK_WEST;
+						break;
+					case 2:
+						returnValue |= mxConstants.DIRECTION_MASK_NORTH;
+						break;
+					case 3:
+						returnValue |= mxConstants.DIRECTION_MASK_EAST;
+						break;
+				}
+			}
+			if (directions.indexOf(mxConstants.DIRECTION_EAST) >= 0)
+			{
+				switch (quad)
+				{
+					case 0:
+						returnValue |= mxConstants.DIRECTION_MASK_EAST;
+						break;
+					case 1:
+						returnValue |= mxConstants.DIRECTION_MASK_SOUTH;
+						break;
+					case 2:
+						returnValue |= mxConstants.DIRECTION_MASK_WEST;
+						break;
+					case 3:
+						returnValue |= mxConstants.DIRECTION_MASK_NORTH;
+						break;
+				}
+			}
+
+			return returnValue;
+		}
+	},
+	
+	/**
+	 * Function: reversePortConstraints
+	 * 
+	 * Reverse the port constraint bitmask. For example, north | east
+	 * becomes south | west
+	 */
+	reversePortConstraints: function(constraint)
+	{
+		var result = 0;
+		
+		result = (constraint & mxConstants.DIRECTION_MASK_WEST) << 3;
+		result |= (constraint & mxConstants.DIRECTION_MASK_NORTH) << 1;
+		result |= (constraint & mxConstants.DIRECTION_MASK_SOUTH) >> 1;
+		result |= (constraint & mxConstants.DIRECTION_MASK_EAST) >> 3;
+		
+		return result;
+	},
+	
+	/**
+	 * Function: findNearestSegment
+	 * 
+	 * Finds the index of the nearest segment on the given cell state for
+	 * the specified coordinate pair.
+	 */
+	findNearestSegment: function(state, x, y)
+	{
+		var index = -1;
+		
+		if (state.absolutePoints.length > 0)
+		{
+			var last = state.absolutePoints[0];
+			var min = null;
+			
+			for (var i = 1; i < state.absolutePoints.length; i++)
+			{
+				var current = state.absolutePoints[i];
+				var dist = mxUtils.ptSegDistSq(last.x, last.y,
+					current.x, current.y, x, y);
+				
+				if (min == null || dist < min)
+				{
+					min = dist;
+					index = i - 1;
+				}
+
+				last = current;
+			}
+		}
+		
+		return index;
+	},
+
+	/**
+	 * Function: getDirectedBounds
+	 * 
+	 * Adds the given margins to the given rectangle and rotates and flips the
+	 * rectangle according to the respective styles in style.
+	 */
+	getDirectedBounds: function (rect, m, style, flipH, flipV)
+	{
+		var d = mxUtils.getValue(style, mxConstants.STYLE_DIRECTION, mxConstants.DIRECTION_EAST);
+		flipH = (flipH != null) ? flipH : mxUtils.getValue(style, mxConstants.STYLE_FLIPH, false);
+		flipV = (flipV != null) ? flipV : mxUtils.getValue(style, mxConstants.STYLE_FLIPV, false);
+
+		m.x = Math.round(Math.max(0, Math.min(rect.width, m.x)));
+		m.y = Math.round(Math.max(0, Math.min(rect.height, m.y)));
+		m.width = Math.round(Math.max(0, Math.min(rect.width, m.width)));
+		m.height = Math.round(Math.max(0, Math.min(rect.height, m.height)));
+		
+		if ((flipV && (d == mxConstants.DIRECTION_SOUTH || d == mxConstants.DIRECTION_NORTH)) ||
+			(flipH && (d == mxConstants.DIRECTION_EAST || d == mxConstants.DIRECTION_WEST)))
+		{
+			var tmp = m.x;
+			m.x = m.width;
+			m.width = tmp;
+		}
+			
+		if ((flipH && (d == mxConstants.DIRECTION_SOUTH || d == mxConstants.DIRECTION_NORTH)) ||
+			(flipV && (d == mxConstants.DIRECTION_EAST || d == mxConstants.DIRECTION_WEST)))
+		{
+			var tmp = m.y;
+			m.y = m.height;
+			m.height = tmp;
+		}
+		
+		var m2 = mxRectangle.fromRectangle(m);
+		
+		if (d == mxConstants.DIRECTION_SOUTH)
+		{
+			m2.y = m.x;
+			m2.x = m.height;
+			m2.width = m.y;
+			m2.height = m.width;
+		}
+		else if (d == mxConstants.DIRECTION_WEST)
+		{
+			m2.y = m.height;
+			m2.x = m.width;
+			m2.width = m.x;
+			m2.height = m.y;
+		}
+		else if (d == mxConstants.DIRECTION_NORTH)
+		{
+			m2.y = m.width;
+			m2.x = m.y;
+			m2.width = m.height;
+			m2.height = m.x;
+		}
+		
+		return new mxRectangle(rect.x + m2.x, rect.y + m2.y, rect.width - m2.width - m2.x, rect.height - m2.height - m2.y);
+	},
+
+	/**
+	 * Function: getPerimeterPoint
+	 * 
+	 * Returns the intersection between the polygon defined by the array of
+	 * points and the line between center and point.
+	 */
+	getPerimeterPoint: function (pts, center, point)
+	{
+		var min = null;
+		
+		for (var i = 0; i < pts.length - 1; i++)
+		{
+			var pt = mxUtils.intersection(pts[i].x, pts[i].y, pts[i + 1].x, pts[i + 1].y,
+				center.x, center.y, point.x, point.y);
+			
+			if (pt != null)
+			{
+				var dx = point.x - pt.x;
+				var dy = point.y - pt.y;
+				var ip = {p: pt, distSq: dy * dy + dx * dx};
+				
+				if (ip != null && (min == null || min.distSq > ip.distSq))
+				{
+					min = ip;
+				}
+			}
+		}
+		
+		return (min != null) ? min.p : null;
+	},
+
+	/**
+	 * Function: rectangleIntersectsSegment
+	 * 
+	 * Returns true if the given rectangle intersects the given segment.
+	 * 
+	 * Parameters:
+	 * 
+	 * bounds - <mxRectangle> that represents the rectangle.
+	 * p1 - <mxPoint> that represents the first point of the segment.
+	 * p2 - <mxPoint> that represents the second point of the segment.
+	 */
+	rectangleIntersectsSegment: function(bounds, p1, p2)
+	{
+		var top = bounds.y;
+		var left = bounds.x;
+		var bottom = top + bounds.height;
+		var right = left + bounds.width;
+			
+		// Find min and max X for the segment
+		var minX = p1.x;
+		var maxX = p2.x;
+		
+		if (p1.x > p2.x)
+		{
+		  minX = p2.x;
+		  maxX = p1.x;
+		}
+		
+		// Find the intersection of the segment's and rectangle's x-projections
+		if (maxX > right)
+		{
+		  maxX = right;
+		}
+		
+		if (minX < left)
+		{
+		  minX = left;
+		}
+		
+		if (minX > maxX) // If their projections do not intersect return false
+		{
+		  return false;
+		}
+		
+		// Find corresponding min and max Y for min and max X we found before
+		var minY = p1.y;
+		var maxY = p2.y;
+		var dx = p2.x - p1.x;
+		
+		if (Math.abs(dx) > 0.0000001)
+		{
+		  var a = (p2.y - p1.y) / dx;
+		  var b = p1.y - a * p1.x;
+		  minY = a * minX + b;
+		  maxY = a * maxX + b;
+		}
+		
+		if (minY > maxY)
+		{
+		  var tmp = maxY;
+		  maxY = minY;
+		  minY = tmp;
+		}
+		
+		// Find the intersection of the segment's and rectangle's y-projections
+		if (maxY > bottom)
+		{
+		  maxY = bottom;
+		}
+		
+		if (minY < top)
+		{
+		  minY = top;
+		}
+		
+		if (minY > maxY) // If Y-projections do not intersect return false
+		{
+		  return false;
+		}
+		
+		return true;
+	},
+	
+	/**
+	 * Function: contains
+	 * 
+	 * Returns true if the specified point (x, y) is contained in the given rectangle.
+	 * 
+	 * Parameters:
+	 * 
+	 * bounds - <mxRectangle> that represents the area.
+	 * x - X-coordinate of the point.
+	 * y - Y-coordinate of the point.
+	 */
+	contains: function(bounds, x, y)
+	{
+		return (bounds.x <= x && bounds.x + bounds.width >= x &&
+				bounds.y <= y && bounds.y + bounds.height >= y);
+	},
+
+	/**
+	 * Function: intersects
+	 * 
+	 * Returns true if the two rectangles intersect.
+	 * 
+	 * Parameters:
+	 * 
+	 * a - <mxRectangle> to be checked for intersection.
+	 * b - <mxRectangle> to be checked for intersection.
+	 */
+	intersects: function(a, b)
+	{
+		var tw = a.width;
+		var th = a.height;
+		var rw = b.width;
+		var rh = b.height;
+		
+		if (rw <= 0 || rh <= 0 || tw <= 0 || th <= 0)
+		{
+		    return false;
+		}
+		
+		var tx = a.x;
+		var ty = a.y;
+		var rx = b.x;
+		var ry = b.y;
+		
+		rw += rx;
+		rh += ry;
+		tw += tx;
+		th += ty;
+
+		return ((rw < rx || rw > tx) &&
+			(rh < ry || rh > ty) &&
+			(tw < tx || tw > rx) &&
+			(th < ty || th > ry));
+	},
+
+	/**
+	 * Function: intersects
+	 * 
+	 * Returns true if the two rectangles intersect.
+	 * 
+	 * Parameters:
+	 * 
+	 * a - <mxRectangle> to be checked for intersection.
+	 * b - <mxRectangle> to be checked for intersection.
+	 */
+	intersectsHotspot: function(state, x, y, hotspot, min, max)
+	{
+		hotspot = (hotspot != null) ? hotspot : 1;
+		min = (min != null) ? min : 0;
+		max = (max != null) ? max : 0;
+		
+		if (hotspot > 0)
+		{
+			var cx = state.getCenterX();
+			var cy = state.getCenterY();
+			var w = state.width;
+			var h = state.height;
+			
+			var start = mxUtils.getValue(state.style, mxConstants.STYLE_STARTSIZE) * state.view.scale;
+
+			if (start > 0)
+			{
+				if (mxUtils.getValue(state.style, mxConstants.STYLE_HORIZONTAL, true))
+				{
+					cy = state.y + start / 2;
+					h = start;
+				}
+				else
+				{
+					cx = state.x + start / 2;
+					w = start;
+				}
+			}
+
+			w = Math.max(min, w * hotspot);
+			h = Math.max(min, h * hotspot);
+			
+			if (max > 0)
+			{
+				w = Math.min(w, max);
+				h = Math.min(h, max);
+			}
+			
+			var rect = new mxRectangle(cx - w / 2, cy - h / 2, w, h);
+			var alpha = mxUtils.toRadians(mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION) || 0);
+			
+			if (alpha != 0)
+			{
+				var cos = Math.cos(-alpha);
+				var sin = Math.sin(-alpha);
+				var cx = new mxPoint(state.getCenterX(), state.getCenterY());
+				var pt = mxUtils.getRotatedPoint(new mxPoint(x, y), cos, sin, cx);
+				x = pt.x;
+				y = pt.y;
+			}
+			
+			return mxUtils.contains(rect, x, y);			
+		}
+		
+		return true;
+	},
+
+	/**
+	 * Function: getOffset
+	 * 
+	 * Returns the offset for the specified container as an <mxPoint>. The
+	 * offset is the distance from the top left corner of the container to the
+	 * top left corner of the document.
+	 * 
+	 * Parameters:
+	 * 
+	 * container - DOM node to return the offset for.
+	 * scollOffset - Optional boolean to add the scroll offset of the document.
+	 * Default is false.
+	 */
+	getOffset: function(container, scrollOffset)
+	{
+		var offsetLeft = 0;
+		var offsetTop = 0;
+		
+		// Ignores document scroll origin for fixed elements
+		var fixed = false;
+		var node = container;
+		var b = document.body;
+		var d = document.documentElement;
+
+		while (node != null && node != b && node != d && !fixed)
+		{
+			var style = mxUtils.getCurrentStyle(node);
+			
+			if (style != null)
+			{
+				fixed = fixed || style.position == 'fixed';
+			}
+			
+			node = node.parentNode;
+		}
+		
+		if (!scrollOffset && !fixed)
+		{
+			var offset = mxUtils.getDocumentScrollOrigin(container.ownerDocument);
+			offsetLeft += offset.x;
+			offsetTop += offset.y;
+		}
+		
+		var r = container.getBoundingClientRect();
+		
+		if (r != null)
+		{
+			offsetLeft += r.left;
+			offsetTop += r.top;
+		}
+		
+		return new mxPoint(offsetLeft, offsetTop);
+	},
+
+	/**
+	 * Function: getDocumentScrollOrigin
+	 * 
+	 * Returns the scroll origin of the given document or the current document
+	 * if no document is given.
+	 */
+	getDocumentScrollOrigin: function(doc)
+	{
+		if (mxClient.IS_QUIRKS)
+		{
+			return new mxPoint(doc.body.scrollLeft, doc.body.scrollTop);
+		}
+		else
+		{
+			var wnd = doc.defaultView || doc.parentWindow;
+			
+			var x = (wnd != null && window.pageXOffset !== undefined) ? window.pageXOffset : (document.documentElement || document.body.parentNode || document.body).scrollLeft;
+			var y = (wnd != null && window.pageYOffset !== undefined) ? window.pageYOffset : (document.documentElement || document.body.parentNode || document.body).scrollTop;
+			
+			return new mxPoint(x, y);
+		}
+	},
+	
+	/**
+	 * Function: getScrollOrigin
+	 * 
+	 * Returns the top, left corner of the viewrect as an <mxPoint>.
+	 * 
+	 * Parameters:
+	 * 
+	 * node - DOM node whose scroll origin should be returned.
+	 * includeAncestors - Whether the scroll origin of the ancestors should be
+	 * included. Default is false.
+	 * includeDocument - Whether the scroll origin of the document should be
+	 * included. Default is true.
+	 */
+	getScrollOrigin: function(node, includeAncestors, includeDocument)
+	{
+		includeAncestors = (includeAncestors != null) ? includeAncestors : false;
+		includeDocument = (includeDocument != null) ? includeDocument : true;
+		
+		var doc = (node != null) ? node.ownerDocument : document;
+		var b = doc.body;
+		var d = doc.documentElement;
+		var result = new mxPoint();
+		var fixed = false;
+
+		while (node != null && node != b && node != d)
+		{
+			if (!isNaN(node.scrollLeft) && !isNaN(node.scrollTop))
+			{
+				result.x += node.scrollLeft;
+				result.y += node.scrollTop;
+			}
+			
+			var style = mxUtils.getCurrentStyle(node);
+			
+			if (style != null)
+			{
+				fixed = fixed || style.position == 'fixed';
+			}
+
+			node = (includeAncestors) ? node.parentNode : null;
+		}
+
+		if (!fixed && includeDocument)
+		{
+			var origin = mxUtils.getDocumentScrollOrigin(doc);
+
+			result.x += origin.x;
+			result.y += origin.y;
+		}
+		
+		return result;
+	},
+
+	/**
+	 * Function: convertPoint
+	 * 
+	 * Converts the specified point (x, y) using the offset of the specified
+	 * container and returns a new <mxPoint> with the result.
+	 * 
+	 * (code)
+	 * var pt = mxUtils.convertPoint(graph.container,
+	 *   mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+	 * (end)
+	 * 
+	 * Parameters:
+	 * 
+	 * container - DOM node to use for the offset.
+	 * x - X-coordinate of the point to be converted.
+	 * y - Y-coordinate of the point to be converted.
+	 */
+	convertPoint: function(container, x, y)
+	{
+		var origin = mxUtils.getScrollOrigin(container, false);
+		var offset = mxUtils.getOffset(container);
+
+		offset.x -= origin.x;
+		offset.y -= origin.y;
+		
+		return new mxPoint(x - offset.x, y - offset.y);
+	},
+	
+	/**
+	 * Function: ltrim
+	 * 
+	 * Strips all whitespaces from the beginning of the string. Without the
+	 * second parameter, this will trim these characters:
+	 * 
+	 * - " " (ASCII 32 (0x20)), an ordinary space
+	 * - "\t" (ASCII 9 (0x09)), a tab
+	 * - "\n" (ASCII 10 (0x0A)), a new line (line feed)
+	 * - "\r" (ASCII 13 (0x0D)), a carriage return
+	 * - "\0" (ASCII 0 (0x00)), the NUL-byte
+	 * - "\x0B" (ASCII 11 (0x0B)), a vertical tab
+	 */
+	ltrim: function(str, chars)
+	{
+		chars = chars || "\\s";
+		
+		return (str != null) ? str.replace(new RegExp("^[" + chars + "]+", "g"), "") : null;
+	},
+	
+	/**
+	 * Function: rtrim
+	 * 
+	 * Strips all whitespaces from the end of the string. Without the second
+	 * parameter, this will trim these characters:
+	 * 
+	 * - " " (ASCII 32 (0x20)), an ordinary space
+	 * - "\t" (ASCII 9 (0x09)), a tab
+	 * - "\n" (ASCII 10 (0x0A)), a new line (line feed)
+	 * - "\r" (ASCII 13 (0x0D)), a carriage return
+	 * - "\0" (ASCII 0 (0x00)), the NUL-byte
+	 * - "\x0B" (ASCII 11 (0x0B)), a vertical tab
+	 */
+	rtrim: function(str, chars)
+	{
+		chars = chars || "\\s";
+		
+		return (str != null) ? str.replace(new RegExp("[" + chars + "]+$", "g"), "") : null;
+	},
+	
+	/**
+	 * Function: trim
+	 * 
+	 * Strips all whitespaces from both end of the string.
+	 * Without the second parameter, Javascript function will trim these
+	 * characters:
+	 * 
+	 * - " " (ASCII 32 (0x20)), an ordinary space
+	 * - "\t" (ASCII 9 (0x09)), a tab
+	 * - "\n" (ASCII 10 (0x0A)), a new line (line feed)
+	 * - "\r" (ASCII 13 (0x0D)), a carriage return
+	 * - "\0" (ASCII 0 (0x00)), the NUL-byte
+	 * - "\x0B" (ASCII 11 (0x0B)), a vertical tab
+	 */
+	trim: function(str, chars)
+	{
+		return mxUtils.ltrim(mxUtils.rtrim(str, chars), chars);
+	},
+	
+	/**
+	 * Function: isNumeric
+	 * 
+	 * Returns true if the specified value is numeric, that is, if it is not
+	 * null, not an empty string, not a HEX number and isNaN returns false.
+	 * 
+	 * Parameters:
+	 * 
+	 * n - String representing the possibly numeric value.
+	 */
+	isNumeric: function(n)
+	{
+		return !isNaN(parseFloat(n)) && isFinite(n) && (typeof(n) != 'string' || n.toLowerCase().indexOf('0x') < 0);
+	},
+
+	/**
+	 * Function: isInteger
+	 * 
+	 * Returns true if the given value is an valid integer number.
+	 * 
+	 * Parameters:
+	 * 
+	 * n - String representing the possibly numeric value.
+	 */
+	isInteger: function(n)
+	{
+		return String(parseInt(n)) === String(n);
+	},
+
+	/**
+	 * Function: mod
+	 * 
+	 * Returns the remainder of division of n by m. You should use this instead
+	 * of the built-in operation as the built-in operation does not properly
+	 * handle negative numbers.
+	 */
+	mod: function(n, m)
+	{
+		return ((n % m) + m) % m;
+	},
+
+	/**
+	 * Function: intersection
+	 * 
+	 * Returns the intersection of two lines as an <mxPoint>.
+	 * 
+	 * Parameters:
+	 * 
+	 * x0 - X-coordinate of the first line's startpoint.
+	 * y0 - X-coordinate of the first line's startpoint.
+	 * x1 - X-coordinate of the first line's endpoint.
+	 * y1 - Y-coordinate of the first line's endpoint.
+	 * x2 - X-coordinate of the second line's startpoint.
+	 * y2 - Y-coordinate of the second line's startpoint.
+	 * x3 - X-coordinate of the second line's endpoint.
+	 * y3 - Y-coordinate of the second line's endpoint.
+	 */
+	intersection: function (x0, y0, x1, y1, x2, y2, x3, y3)
+	{
+		var denom = ((y3 - y2) * (x1 - x0)) - ((x3 - x2) * (y1 - y0));
+		var nume_a = ((x3 - x2) * (y0 - y2)) - ((y3 - y2) * (x0 - x2));
+		var nume_b = ((x1 - x0) * (y0 - y2)) - ((y1 - y0) * (x0 - x2));
+
+		var ua = nume_a / denom;
+		var ub = nume_b / denom;
+		
+		if(ua >= 0.0 && ua <= 1.0 && ub >= 0.0 && ub <= 1.0)
+		{
+			// Get the intersection point
+			var x = x0 + ua * (x1 - x0);
+			var y = y0 + ua * (y1 - y0);
+			
+			return new mxPoint(x, y);
+		}
+		
+		// No intersection
+		return null;
+	},
+	
+	/**
+	 * Function: ptSegDistSq
+	 * 
+	 * Returns the square distance between a segment and a point. To get the
+	 * distance between a point and a line (with infinite length) use
+	 * <mxUtils.ptLineDist>.
+	 * 
+	 * Parameters:
+	 * 
+	 * x1 - X-coordinate of the startpoint of the segment.
+	 * y1 - Y-coordinate of the startpoint of the segment.
+	 * x2 - X-coordinate of the endpoint of the segment.
+	 * y2 - Y-coordinate of the endpoint of the segment.
+	 * px - X-coordinate of the point.
+	 * py - Y-coordinate of the point.
+	 */
+	ptSegDistSq: function(x1, y1, x2, y2, px, py)
+    {
+		x2 -= x1;
+		y2 -= y1;
+
+		px -= x1;
+		py -= y1;
+
+		var dotprod = px * x2 + py * y2;
+		var projlenSq;
+
+		if (dotprod <= 0.0)
+		{
+		    projlenSq = 0.0;
+		}
+		else
+		{
+		    px = x2 - px;
+		    py = y2 - py;
+		    dotprod = px * x2 + py * y2;
+
+		    if (dotprod <= 0.0)
+		    {
+				projlenSq = 0.0;
+		    }
+		    else
+		    {
+				projlenSq = dotprod * dotprod / (x2 * x2 + y2 * y2);
+		    }
+		}
+
+		var lenSq = px * px + py * py - projlenSq;
+		
+		if (lenSq < 0)
+		{
+		    lenSq = 0;
+		}
+		
+		return lenSq;
+    },
+	
+	/**
+	 * Function: ptLineDist
+	 * 
+	 * Returns the distance between a line defined by two points and a point.
+	 * To get the distance between a point and a segment (with a specific
+	 * length) use <mxUtils.ptSeqDistSq>.
+	 * 
+	 * Parameters:
+	 * 
+	 * x1 - X-coordinate of point 1 of the line.
+	 * y1 - Y-coordinate of point 1 of the line.
+	 * x2 - X-coordinate of point 1 of the line.
+	 * y2 - Y-coordinate of point 1 of the line.
+	 * px - X-coordinate of the point.
+	 * py - Y-coordinate of the point.
+	 */
+    ptLineDist: function(x1, y1, x2, y2, px, py)
+    {
+		return Math.abs((y2 - y1) * px - (x2 - x1) * py + x2 * y1 - y2 * x1) /
+			Math.sqrt((y2 - y1) * (y2 - y1) + (x2 - x1) * (x2 - x1));
+    },
+    	
+	/**
+	 * Function: relativeCcw
+	 * 
+	 * Returns 1 if the given point on the right side of the segment, 0 if its
+	 * on the segment, and -1 if the point is on the left side of the segment.
+	 * 
+	 * Parameters:
+	 * 
+	 * x1 - X-coordinate of the startpoint of the segment.
+	 * y1 - Y-coordinate of the startpoint of the segment.
+	 * x2 - X-coordinate of the endpoint of the segment.
+	 * y2 - Y-coordinate of the endpoint of the segment.
+	 * px - X-coordinate of the point.
+	 * py - Y-coordinate of the point.
+	 */
+	relativeCcw: function(x1, y1, x2, y2, px, py)
+    {
+		x2 -= x1;
+		y2 -= y1;
+		px -= x1;
+		py -= y1;
+		var ccw = px * y2 - py * x2;
+		
+		if (ccw == 0.0)
+		{
+		    ccw = px * x2 + py * y2;
+		    
+		    if (ccw > 0.0)
+		    {
+				px -= x2;
+				py -= y2;
+				ccw = px * x2 + py * y2;
+				
+				if (ccw < 0.0)
+				{
+				    ccw = 0.0;
+				}
+		    }
+		}
+		
+		return (ccw < 0.0) ? -1 : ((ccw > 0.0) ? 1 : 0);
+    },
+    
+	/**
+	 * Function: animateChanges
+	 * 
+	 * See <mxEffects.animateChanges>. This is for backwards compatibility and
+	 * will be removed later.
+	 */
+	animateChanges: function(graph, changes)
+	{
+		// LATER: Deprecated, remove this function
+    	mxEffects.animateChanges.apply(this, arguments);
+	},
+    
+	/**
+	 * Function: cascadeOpacity
+	 * 
+	 * See <mxEffects.cascadeOpacity>. This is for backwards compatibility and
+	 * will be removed later.
+	 */
+    cascadeOpacity: function(graph, cell, opacity)
+	{
+		mxEffects.cascadeOpacity.apply(this, arguments);
+	},
+
+	/**
+	 * Function: fadeOut
+	 * 
+	 * See <mxEffects.fadeOut>. This is for backwards compatibility and
+	 * will be removed later.
+	 */
+	fadeOut: function(node, from, remove, step, delay, isEnabled)
+	{
+		mxEffects.fadeOut.apply(this, arguments);
+	},
+	
+	/**
+	 * Function: setOpacity
+	 * 
+	 * Sets the opacity of the specified DOM node to the given value in %.
+	 * 
+	 * Parameters:
+	 * 
+	 * node - DOM node to set the opacity for.
+	 * value - Opacity in %. Possible values are between 0 and 100.
+	 */
+	setOpacity: function(node, value)
+	{
+		if (mxUtils.isVml(node))
+		{
+	    	if (value >= 100)
+	    	{
+	    		node.style.filter = '';
+	    	}
+	    	else
+	    	{
+	    		// TODO: Why is the division by 5 needed in VML?
+			    node.style.filter = 'alpha(opacity=' + (value/5) + ')';
+	    	}
+		}
+		else if (mxClient.IS_IE && (typeof(document.documentMode) === 'undefined' || document.documentMode < 9))
+	    {
+	    	if (value >= 100)
+	    	{
+	    		node.style.filter = '';
+	    	}
+	    	else
+	    	{
+			    node.style.filter = 'alpha(opacity=' + value + ')';
+	    	}
+		}
+		else
+		{
+		    node.style.opacity = (value / 100);
+		}
+	},
+
+	/**
+	 * Function: createImage
+	 * 
+	 * Creates and returns an image (IMG node) or VML image (v:image) in IE6 in
+	 * quirks mode.
+	 * 
+	 * Parameters:
+	 * 
+	 * src - URL that points to the image to be displayed.
+	 */
+	createImage: function(src)
+	{
+        var imageNode = null;
+        
+		if (mxClient.IS_IE6 && document.compatMode != 'CSS1Compat')
+		{
+        	imageNode = document.createElement(mxClient.VML_PREFIX + ':image');
+        	imageNode.setAttribute('src', src);
+        	imageNode.style.borderStyle = 'none';
+        }
+		else
+		{
+			imageNode = document.createElement('img');
+			imageNode.setAttribute('src', src);
+			imageNode.setAttribute('border', '0');
+		}
+		
+		return imageNode;
+	},
+
+	/**
+	 * Function: sortCells
+	 * 
+	 * Sorts the given cells according to the order in the cell hierarchy.
+	 * Ascending is optional and defaults to true.
+	 */
+	sortCells: function(cells, ascending)
+	{
+		ascending = (ascending != null) ? ascending : true;
+		var lookup = new mxDictionary();
+		cells.sort(function(o1, o2)
+		{
+			var p1 = lookup.get(o1);
+			
+			if (p1 == null)
+			{
+				p1 = mxCellPath.create(o1).split(mxCellPath.PATH_SEPARATOR);
+				lookup.put(o1, p1);
+			}
+			
+			var p2 = lookup.get(o2);
+			
+			if (p2 == null)
+			{
+				p2 = mxCellPath.create(o2).split(mxCellPath.PATH_SEPARATOR);
+				lookup.put(o2, p2);
+			}
+			
+			var comp = mxCellPath.compare(p1, p2);
+			
+			return (comp == 0) ? 0 : (((comp > 0) == ascending) ? 1 : -1);
+		});
+		
+		return cells;
+	},
+
+	/**
+	 * Function: getStylename
+	 * 
+	 * Returns the stylename in a style of the form [(stylename|key=value);] or
+	 * an empty string if the given style does not contain a stylename.
+	 * 
+	 * Parameters:
+	 * 
+	 * style - String of the form [(stylename|key=value);].
+	 */
+	getStylename: function(style)
+	{
+		if (style != null)
+		{
+			var pairs = style.split(';');
+			var stylename = pairs[0];
+			
+			if (stylename.indexOf('=') < 0)
+			{
+				return stylename;
+			}
+		}
+				
+		return '';
+	},
+
+	/**
+	 * Function: getStylenames
+	 * 
+	 * Returns the stylenames in a style of the form [(stylename|key=value);]
+	 * or an empty array if the given style does not contain any stylenames.
+	 * 
+	 * Parameters:
+	 * 
+	 * style - String of the form [(stylename|key=value);].
+	 */
+	getStylenames: function(style)
+	{
+		var result = [];
+		
+		if (style != null)
+		{
+			var pairs = style.split(';');
+			
+			for (var i = 0; i < pairs.length; i++)
+			{
+				if (pairs[i].indexOf('=') < 0)
+				{
+					result.push(pairs[i]);
+				}
+			}
+		}
+				
+		return result;
+	},
+
+	/**
+	 * Function: indexOfStylename
+	 * 
+	 * Returns the index of the given stylename in the given style. This
+	 * returns -1 if the given stylename does not occur (as a stylename) in the
+	 * given style, otherwise it returns the index of the first character.
+	 */
+	indexOfStylename: function(style, stylename)
+	{
+		if (style != null && stylename != null)
+		{
+			var tokens = style.split(';');
+			var pos = 0;
+			
+			for (var i = 0; i < tokens.length; i++)
+			{
+				if (tokens[i] == stylename)
+				{
+					return pos;
+				}
+				
+				pos += tokens[i].length + 1;
+			}
+		}
+
+		return -1;
+	},
+	
+	/**
+	 * Function: addStylename
+	 * 
+	 * Adds the specified stylename to the given style if it does not already
+	 * contain the stylename.
+	 */
+	addStylename: function(style, stylename)
+	{
+		if (mxUtils.indexOfStylename(style, stylename) < 0)
+		{
+			if (style == null)
+			{
+				style = '';
+			}
+			else if (style.length > 0 && style.charAt(style.length - 1) != ';')
+			{
+				style += ';';
+			}
+			
+			style += stylename;
+		}
+		
+		return style;
+	},
+	
+	/**
+	 * Function: removeStylename
+	 * 
+	 * Removes all occurrences of the specified stylename in the given style
+	 * and returns the updated style. Trailing semicolons are not preserved.
+	 */
+	removeStylename: function(style, stylename)
+	{
+		var result = [];
+		
+		if (style != null)
+		{
+			var tokens = style.split(';');
+			
+			for (var i = 0; i < tokens.length; i++)
+			{
+				if (tokens[i] != stylename)
+				{
+					result.push(tokens[i]);
+				}
+			}
+		}
+		
+		return result.join(';');
+	},
+	
+	/**
+	 * Function: removeAllStylenames
+	 * 
+	 * Removes all stylenames from the given style and returns the updated
+	 * style.
+	 */
+	removeAllStylenames: function(style)
+	{
+		var result = [];
+		
+		if (style != null)
+		{
+			var tokens = style.split(';');
+			
+			for (var i = 0; i < tokens.length; i++)
+			{
+				// Keeps the key, value assignments
+				if (tokens[i].indexOf('=') >= 0)
+				{
+					result.push(tokens[i]);
+				}
+			}
+		}
+		
+		return result.join(';');
+	},
+
+	/**
+	 * Function: setCellStyles
+	 * 
+	 * Assigns the value for the given key in the styles of the given cells, or
+	 * removes the key from the styles if the value is null.
+	 * 
+	 * Parameters:
+	 * 
+	 * model - <mxGraphModel> to execute the transaction in.
+	 * cells - Array of <mxCells> to be updated.
+	 * key - Key of the style to be changed.
+	 * value - New value for the given key.
+	 */
+	setCellStyles: function(model, cells, key, value)
+	{
+		if (cells != null && cells.length > 0)
+		{
+			model.beginUpdate();
+			try
+			{
+				for (var i = 0; i < cells.length; i++)
+				{
+					if (cells[i] != null)
+					{
+						var style = mxUtils.setStyle(model.getStyle(cells[i]), key, value);
+						model.setStyle(cells[i], style);
+					}
+				}
+			}
+			finally
+			{
+				model.endUpdate();
+			}
+		}
+	},
+	
+	/**
+	 * Function: setStyle
+	 * 
+	 * Adds or removes the given key, value pair to the style and returns the
+	 * new style. If value is null or zero length then the key is removed from
+	 * the style. This is for cell styles, not for CSS styles.
+	 * 
+	 * Parameters:
+	 * 
+	 * style - String of the form [(stylename|key=value);].
+	 * key - Key of the style to be changed.
+	 * value - New value for the given key.
+	 */
+	setStyle: function(style, key, value)
+	{
+		var isValue = value != null && (typeof(value.length) == 'undefined' || value.length > 0);
+		
+		if (style == null || style.length == 0)
+		{
+			if (isValue)
+			{
+				style = key + '=' + value + ';';
+			}
+		}
+		else
+		{
+			if (style.substring(0, key.length + 1) == key + '=')
+			{
+				var next = style.indexOf(';');
+				
+				if (isValue)
+				{
+					style = key + '=' + value + ((next < 0) ? ';' : style.substring(next));
+				}
+				else
+				{
+					style = (next < 0 || next == style.length - 1) ? '' : style.substring(next + 1);
+				}
+			}
+			else
+			{
+				var index = style.indexOf(';' + key + '=');
+				
+				if (index < 0)
+				{
+					if (isValue)
+					{
+						var sep = (style.charAt(style.length - 1) == ';') ? '' : ';';
+						style = style + sep + key + '=' + value + ';';
+					}
+				}
+				else
+				{
+					var next = style.indexOf(';', index + 1);
+					
+					if (isValue)
+					{
+						style = style.substring(0, index + 1) + key + '=' + value + ((next < 0) ? ';' : style.substring(next));
+					}
+					else
+					{
+						style = style.substring(0, index) + ((next < 0) ? ';' : style.substring(next));
+					}
+				}
+			}
+		}
+		
+		return style;
+	},
+
+	/**
+	 * Function: setCellStyleFlags
+	 * 
+	 * Sets or toggles the flag bit for the given key in the cell's styles.
+	 * If value is null then the flag is toggled.
+	 * 
+	 * Example:
+	 * 
+	 * (code)
+	 * var cells = graph.getSelectionCells();
+	 * mxUtils.setCellStyleFlags(graph.model,
+	 * 			cells,
+	 * 			mxConstants.STYLE_FONTSTYLE,
+	 * 			mxConstants.FONT_BOLD);
+	 * (end)
+	 * 
+	 * Toggles the bold font style.
+	 * 
+	 * Parameters:
+	 * 
+	 * model - <mxGraphModel> that contains the cells.
+	 * cells - Array of <mxCells> to change the style for.
+	 * key - Key of the style to be changed.
+	 * flag - Integer for the bit to be changed.
+	 * value - Optional boolean value for the flag.
+	 */
+	setCellStyleFlags: function(model, cells, key, flag, value)
+	{
+		if (cells != null && cells.length > 0)
+		{
+			model.beginUpdate();
+			try
+			{
+				for (var i = 0; i < cells.length; i++)
+				{
+					if (cells[i] != null)
+					{
+						var style = mxUtils.setStyleFlag(
+							model.getStyle(cells[i]),
+							key, flag, value);
+						model.setStyle(cells[i], style);
+					}
+				}
+			}
+			finally
+			{
+				model.endUpdate();
+			}
+		}
+	},
+	
+	/**
+	 * Function: setStyleFlag
+	 * 
+	 * Sets or removes the given key from the specified style and returns the
+	 * new style. If value is null then the flag is toggled.
+	 * 
+	 * Parameters:
+	 * 
+	 * style - String of the form [(stylename|key=value);].
+	 * key - Key of the style to be changed.
+	 * flag - Integer for the bit to be changed.
+	 * value - Optional boolean value for the given flag.
+	 */
+	setStyleFlag: function(style, key, flag, value)
+	{
+		if (style == null || style.length == 0)
+		{
+			if (value || value == null)
+			{
+				style = key+'='+flag;
+			}
+			else
+			{
+				style = key+'=0';
+			}
+		}
+		else
+		{
+			var index = style.indexOf(key+'=');
+			
+			if (index < 0)
+			{
+				var sep = (style.charAt(style.length-1) == ';') ? '' : ';';
+
+				if (value || value == null)
+				{
+					style = style + sep + key + '=' + flag;
+				}
+				else
+				{
+					style = style + sep + key + '=0';
+				}
+			}
+			else
+			{
+				var cont = style.indexOf(';', index);
+				var tmp = '';
+				
+				if (cont < 0)
+				{
+					tmp  = style.substring(index+key.length+1);
+				}
+				else
+				{
+					tmp = style.substring(index+key.length+1, cont);
+				}
+				
+				if (value == null)
+				{
+					tmp = parseInt(tmp) ^ flag;
+				}
+				else if (value)
+				{
+					tmp = parseInt(tmp) | flag;
+				}
+				else
+				{
+					tmp = parseInt(tmp) & ~flag;
+				}
+				
+				style = style.substring(0, index) + key + '=' + tmp +
+					((cont >= 0) ? style.substring(cont) : '');
+			}
+		}
+		
+		return style;
+	},
+	
+	/**
+	 * Function: getAlignmentAsPoint
+	 * 
+	 * Returns an <mxPoint> that represents the horizontal and vertical alignment
+	 * for numeric computations. X is -0.5 for center, -1 for right and 0 for
+	 * left alignment. Y is -0.5 for middle, -1 for bottom and 0 for top
+	 * alignment. Default values for missing arguments is top, left.
+	 */
+	getAlignmentAsPoint: function(align, valign)
+	{
+		var dx = 0;
+		var dy = 0;
+		
+		// Horizontal alignment
+		if (align == mxConstants.ALIGN_CENTER)
+		{
+			dx = -0.5;
+		}
+		else if (align == mxConstants.ALIGN_RIGHT)
+		{
+			dx = -1;
+		}
+
+		// Vertical alignment
+		if (valign == mxConstants.ALIGN_MIDDLE)
+		{
+			dy = -0.5;
+		}
+		else if (valign == mxConstants.ALIGN_BOTTOM)
+		{
+			dy = -1;
+		}
+		
+		return new mxPoint(dx, dy);
+	},
+	
+	/**
+	 * Function: getSizeForString
+	 * 
+	 * Returns an <mxRectangle> with the size (width and height in pixels) of
+	 * the given string. The string may contain HTML markup. Newlines should be
+	 * converted to <br> before calling this method. The caller is responsible
+	 * for sanitizing the HTML markup.
+	 * 
+	 * Example:
+	 * 
+	 * (code)
+	 * var label = graph.getLabel(cell).replace(/\n/g, "<br>");
+	 * var size = graph.getSizeForString(label);
+	 * (end)
+	 * 
+	 * Parameters:
+	 * 
+	 * text - String whose size should be returned.
+	 * fontSize - Integer that specifies the font size in pixels. Default is
+	 * <mxConstants.DEFAULT_FONTSIZE>.
+	 * fontFamily - String that specifies the name of the font family. Default
+	 * is <mxConstants.DEFAULT_FONTFAMILY>.
+	 * textWidth - Optional width for text wrapping.
+	 */
+	getSizeForString: function(text, fontSize, fontFamily, textWidth)
+	{
+		fontSize = (fontSize != null) ? fontSize : mxConstants.DEFAULT_FONTSIZE;
+		fontFamily = (fontFamily != null) ? fontFamily : mxConstants.DEFAULT_FONTFAMILY;
+		var div = document.createElement('div');
+		
+		// Sets the font size and family
+		div.style.fontFamily = fontFamily;
+		div.style.fontSize = Math.round(fontSize) + 'px';
+		div.style.lineHeight = Math.round(fontSize * mxConstants.LINE_HEIGHT) + 'px';
+		
+		// Disables block layout and outside wrapping and hides the div
+		div.style.position = 'absolute';
+		div.style.visibility = 'hidden';
+		div.style.display = (mxClient.IS_QUIRKS) ? 'inline' : 'inline-block';
+		div.style.zoom = '1';
+		
+		if (textWidth != null)
+		{
+			div.style.width = textWidth + 'px';
+			div.style.whiteSpace = 'normal';
+		}
+		else
+		{
+			div.style.whiteSpace = 'nowrap';
+		}
+		
+		// Adds the text and inserts into DOM for updating of size
+		div.innerHTML = text;
+		document.body.appendChild(div);
+		
+		// Gets the size and removes from DOM
+		var size = new mxRectangle(0, 0, div.offsetWidth, div.offsetHeight);
+		document.body.removeChild(div);
+		
+		return size;
+	},
+	
+	/**
+	 * Function: getViewXml
+	 */
+	getViewXml: function(graph, scale, cells, x0, y0)
+	{
+		x0 = (x0 != null) ? x0 : 0;
+		y0 = (y0 != null) ? y0 : 0;
+		scale = (scale != null) ? scale : 1;
+
+		if (cells == null)
+		{
+			var model = graph.getModel();
+			cells = [model.getRoot()];
+		}
+		
+		var view = graph.getView();
+		var result = null;
+
+		// Disables events on the view
+		var eventsEnabled = view.isEventsEnabled();
+		view.setEventsEnabled(false);
+
+		// Workaround for label bounds not taken into account for image export.
+		// Creates a temporary draw pane which is used for rendering the text.
+		// Text rendering is required for finding the bounds of the labels.
+		var drawPane = view.drawPane;
+		var overlayPane = view.overlayPane;
+
+		if (graph.dialect == mxConstants.DIALECT_SVG)
+		{
+			view.drawPane = document.createElementNS(mxConstants.NS_SVG, 'g');
+			view.canvas.appendChild(view.drawPane);
+
+			// Redirects cell overlays into temporary container
+			view.overlayPane = document.createElementNS(mxConstants.NS_SVG, 'g');
+			view.canvas.appendChild(view.overlayPane);
+		}
+		else
+		{
+			view.drawPane = view.drawPane.cloneNode(false);
+			view.canvas.appendChild(view.drawPane);
+			
+			// Redirects cell overlays into temporary container
+			view.overlayPane = view.overlayPane.cloneNode(false);
+			view.canvas.appendChild(view.overlayPane);
+		}
+
+		// Resets the translation
+		var translate = view.getTranslate();
+		view.translate = new mxPoint(x0, y0);
+
+		// Creates the temporary cell states in the view
+		var temp = new mxTemporaryCellStates(graph.getView(), scale, cells);
+
+		try
+		{
+			var enc = new mxCodec();
+			result = enc.encode(graph.getView());
+		}
+		finally
+		{
+			temp.destroy();
+			view.translate = translate;
+			view.canvas.removeChild(view.drawPane);
+			view.canvas.removeChild(view.overlayPane);
+			view.drawPane = drawPane;
+			view.overlayPane = overlayPane;
+			view.setEventsEnabled(eventsEnabled);
+		}
+
+		return result;
+	},
+	
+	/**
+	 * Function: getScaleForPageCount
+	 * 
+	 * Returns the scale to be used for printing the graph with the given
+	 * bounds across the specifies number of pages with the given format. The
+	 * scale is always computed such that it given the given amount or fewer
+	 * pages in the print output. See <mxPrintPreview> for an example.
+	 * 
+	 * Parameters:
+	 * 
+	 * pageCount - Specifies the number of pages in the print output.
+	 * graph - <mxGraph> that should be printed.
+	 * pageFormat - Optional <mxRectangle> that specifies the page format.
+	 * Default is <mxConstants.PAGE_FORMAT_A4_PORTRAIT>.
+	 * border - The border along each side of every page.
+	 */
+	getScaleForPageCount: function(pageCount, graph, pageFormat, border)
+	{
+		if (pageCount < 1)
+		{
+			// We can't work with less than 1 page, return no scale
+			// change
+			return 1;
+		}
+		
+		pageFormat = (pageFormat != null) ? pageFormat : mxConstants.PAGE_FORMAT_A4_PORTRAIT;
+		border = (border != null) ? border : 0;
+		
+		var availablePageWidth = pageFormat.width - (border * 2);
+		var availablePageHeight = pageFormat.height - (border * 2);
+
+		// Work out the number of pages required if the
+		// graph is not scaled.
+		var graphBounds = graph.getGraphBounds().clone();
+		var sc = graph.getView().getScale();
+		graphBounds.width /= sc;
+		graphBounds.height /= sc;
+		var graphWidth = graphBounds.width;
+		var graphHeight = graphBounds.height;
+
+		var scale = 1;
+		
+		// The ratio of the width/height for each printer page
+		var pageFormatAspectRatio = availablePageWidth / availablePageHeight;
+		// The ratio of the width/height for the graph to be printer
+		var graphAspectRatio = graphWidth / graphHeight;
+		
+		// The ratio of horizontal pages / vertical pages for this 
+		// graph to maintain its aspect ratio on this page format
+		var pagesAspectRatio = graphAspectRatio / pageFormatAspectRatio;
+		
+		// Factor the square root of the page count up and down 
+		// by the pages aspect ratio to obtain a horizontal and 
+		// vertical page count that adds up to the page count
+		// and has the correct aspect ratio
+		var pageRoot = Math.sqrt(pageCount);
+		var pagesAspectRatioSqrt = Math.sqrt(pagesAspectRatio);
+		var numRowPages = pageRoot * pagesAspectRatioSqrt;
+		var numColumnPages = pageRoot / pagesAspectRatioSqrt;
+
+		// These value are rarely more than 2 rounding downs away from
+		// a total that meets the page count. In cases of one being less 
+		// than 1 page, the other value can be too high and take more iterations 
+		// In this case, just change that value to be the page count, since 
+		// we know the other value is 1
+		if (numRowPages < 1 && numColumnPages > pageCount)
+		{
+			var scaleChange = numColumnPages / pageCount;
+			numColumnPages = pageCount;
+			numRowPages /= scaleChange;
+		}
+		
+		if (numColumnPages < 1 && numRowPages > pageCount)
+		{
+			var scaleChange = numRowPages / pageCount;
+			numRowPages = pageCount;
+			numColumnPages /= scaleChange;
+		}		
+
+		var currentTotalPages = Math.ceil(numRowPages) * Math.ceil(numColumnPages);
+
+		var numLoops = 0;
+		
+		// Iterate through while the rounded up number of pages comes to
+		// a total greater than the required number
+		while (currentTotalPages > pageCount)
+		{
+			// Round down the page count (rows or columns) that is
+			// closest to its next integer down in percentage terms.
+			// i.e. Reduce the page total by reducing the total
+			// page area by the least possible amount
+
+			var roundRowDownProportion = Math.floor(numRowPages) / numRowPages;
+			var roundColumnDownProportion = Math.floor(numColumnPages) / numColumnPages;
+			
+			// If the round down proportion is, work out the proportion to
+			// round down to 1 page less
+			if (roundRowDownProportion == 1)
+			{
+				roundRowDownProportion = Math.floor(numRowPages-1) / numRowPages;
+			}
+			if (roundColumnDownProportion == 1)
+			{
+				roundColumnDownProportion = Math.floor(numColumnPages-1) / numColumnPages;
+			}
+			
+			// Check which rounding down is smaller, but in the case of very small roundings
+			// try the other dimension instead
+			var scaleChange = 1;
+			
+			// Use the higher of the two values
+			if (roundRowDownProportion > roundColumnDownProportion)
+			{
+				scaleChange = roundRowDownProportion;
+			}
+			else
+			{
+				scaleChange = roundColumnDownProportion;
+			}
+
+			numRowPages = numRowPages * scaleChange;
+			numColumnPages = numColumnPages * scaleChange;
+			currentTotalPages = Math.ceil(numRowPages) * Math.ceil(numColumnPages);
+			
+			numLoops++;
+			
+			if (numLoops > 10)
+			{
+				break;
+			}
+		}
+
+		// Work out the scale from the number of row pages required
+		// The column pages will give the same value
+		var posterWidth = availablePageWidth * numRowPages;
+		scale = posterWidth / graphWidth;
+		
+		// Allow for rounding errors
+		return scale * 0.99999;
+	},
+	
+	/**
+	 * Function: show
+	 * 
+	 * Copies the styles and the markup from the graph's container into the
+	 * given document and removes all cursor styles. The document is returned.
+	 * 
+	 * This function should be called from within the document with the graph.
+	 * If you experience problems with missing stylesheets in IE then try adding
+	 * the domain to the trusted sites.
+	 * 
+	 * Parameters:
+	 * 
+	 * graph - <mxGraph> to be copied.
+	 * doc - Document where the new graph is created.
+	 * x0 - X-coordinate of the graph view origin. Default is 0.
+	 * y0 - Y-coordinate of the graph view origin. Default is 0.
+	 * w - Optional width of the graph view.
+	 * h - Optional height of the graph view.
+	 */
+	show: function(graph, doc, x0, y0, w, h)
+	{
+		x0 = (x0 != null) ? x0 : 0;
+		y0 = (y0 != null) ? y0 : 0;
+		
+		if (doc == null)
+		{
+			var wnd = window.open();
+			doc = wnd.document;
+		}
+		else
+		{
+			doc.open();
+		}
+
+		// Workaround for missing print output in IE9 standards
+		if (document.documentMode == 9)
+		{
+			doc.writeln('<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=9"><![endif]-->');
+		}
+		
+		var bounds = graph.getGraphBounds();
+		var dx = Math.ceil(x0 - bounds.x);
+		var dy = Math.ceil(y0 - bounds.y);
+		
+		if (w == null)
+		{
+			w = Math.ceil(bounds.width + x0) + Math.ceil(Math.ceil(bounds.x) - bounds.x);
+		}
+		
+		if (h == null)
+		{
+			h = Math.ceil(bounds.height + y0) + Math.ceil(Math.ceil(bounds.y) - bounds.y);
+		}
+		
+		// Needs a special way of creating the page so that no click is required
+		// to refresh the contents after the external CSS styles have been loaded.
+		// To avoid a click or programmatic refresh, the styleSheets[].cssText
+		// property is copied over from the original document.
+		if (mxClient.IS_IE || document.documentMode == 11)
+		{
+			var html = '<html><head>';
+
+			var base = document.getElementsByTagName('base');
+			
+			for (var i = 0; i < base.length; i++)
+			{
+				html += base[i].outerHTML;
+			}
+
+			html += '<style>';
+
+			// Copies the stylesheets without having to load them again
+			for (var i = 0; i < document.styleSheets.length; i++)
+			{
+				try
+				{
+					html += document.styleSheets[i].cssText;
+				}
+				catch (e)
+				{
+					// ignore security exception
+				}
+			}
+
+			html += '</style></head><body style="margin:0px;">';
+			
+			// Copies the contents of the graph container
+			html += '<div style="position:absolute;overflow:hidden;width:' + w + 'px;height:' + h + 'px;"><div style="position:relative;left:' + dx + 'px;top:' + dy + 'px;">';
+			html += graph.container.innerHTML;
+			html += '</div></div></body><html>';
+
+			doc.writeln(html);
+			doc.close();
+		}
+		else
+		{
+			doc.writeln('<html><head>');
+			
+			var base = document.getElementsByTagName('base');
+			
+			for (var i = 0; i < base.length; i++)
+			{
+				doc.writeln(mxUtils.getOuterHtml(base[i]));
+			}
+			
+			var links = document.getElementsByTagName('link');
+			
+			for (var i = 0; i < links.length; i++)
+			{
+				doc.writeln(mxUtils.getOuterHtml(links[i]));
+			}
+	
+			var styles = document.getElementsByTagName('style');
+			
+			for (var i = 0; i < styles.length; i++)
+			{
+				doc.writeln(mxUtils.getOuterHtml(styles[i]));
+			}
+
+			doc.writeln('</head><body style="margin:0px;"></body></html>');
+			doc.close();
+
+			var outer = doc.createElement('div');
+			outer.position = 'absolute';
+			outer.overflow = 'hidden';
+			outer.style.width = w + 'px';
+			outer.style.height = h + 'px';
+
+			// Required for HTML labels if foreignObjects are disabled
+			var div = doc.createElement('div');
+			div.style.position = 'absolute';
+			div.style.left = dx + 'px';
+			div.style.top = dy + 'px';
+
+			var node = graph.container.firstChild;
+			var svg = null;
+			
+			while (node != null)
+			{
+				var clone = node.cloneNode(true);
+				
+				if (node == graph.view.drawPane.ownerSVGElement)
+				{
+					outer.appendChild(clone);
+					svg = clone;
+				}
+				else
+				{
+					div.appendChild(clone);
+				}
+				
+				node = node.nextSibling;
+			}
+
+			doc.body.appendChild(outer);
+			
+			if (div.firstChild != null)
+			{
+				doc.body.appendChild(div);
+			}
+						
+			if (svg != null)
+			{
+				svg.style.minWidth = '';
+				svg.style.minHeight = '';
+				svg.firstChild.setAttribute('transform', 'translate(' + dx + ',' + dy + ')');
+			}
+		}
+		
+		mxUtils.removeCursors(doc.body);
+	
+		return doc;
+	},
+	
+	/**
+	 * Function: printScreen
+	 * 
+	 * Prints the specified graph using a new window and the built-in print
+	 * dialog.
+	 * 
+	 * This function should be called from within the document with the graph.
+	 * 
+	 * Parameters:
+	 * 
+	 * graph - <mxGraph> to be printed.
+	 */
+	printScreen: function(graph)
+	{
+		var wnd = window.open();
+		var bounds = graph.getGraphBounds();
+		mxUtils.show(graph, wnd.document);
+		
+		var print = function()
+		{
+			wnd.focus();
+			wnd.print();
+			wnd.close();
+		};
+		
+		// Workaround for Google Chrome which needs a bit of a
+		// delay in order to render the SVG contents
+		if (mxClient.IS_GC)
+		{
+			wnd.setTimeout(print, 500);
+		}
+		else
+		{
+			print();
+		}
+	},
+	
+	/**
+	 * Function: popup
+	 * 
+	 * Shows the specified text content in a new <mxWindow> or a new browser
+	 * window if isInternalWindow is false.
+	 * 
+	 * Parameters:
+	 * 
+	 * content - String that specifies the text to be displayed.
+	 * isInternalWindow - Optional boolean indicating if an mxWindow should be
+	 * used instead of a new browser window. Default is false.
+	 */
+	popup: function(content, isInternalWindow)
+	{
+	   	if (isInternalWindow)
+	   	{
+			var div = document.createElement('div');
+			
+			div.style.overflow = 'scroll';
+			div.style.width = '636px';
+			div.style.height = '460px';
+			
+			var pre = document.createElement('pre');
+		    pre.innerHTML = mxUtils.htmlEntities(content, false).
+		    	replace(/\n/g,'<br>').replace(/ /g, '&nbsp;');
+			
+			div.appendChild(pre);
+			
+			var w = document.body.clientWidth;
+			var h = Math.max(document.body.clientHeight || 0, document.documentElement.clientHeight)
+			var wnd = new mxWindow('Popup Window', div,
+				w/2-320, h/2-240, 640, 480, false, true);
+
+			wnd.setClosable(true);
+			wnd.setVisible(true);
+		}
+		else
+		{
+			// Wraps up the XML content in a textarea
+			if (mxClient.IS_NS)
+			{
+			    var wnd = window.open();
+				wnd.document.writeln('<pre>'+mxUtils.htmlEntities(content)+'</pre');
+			   	wnd.document.close();
+			}
+			else
+			{
+			    var wnd = window.open();
+			    var pre = wnd.document.createElement('pre');
+			    pre.innerHTML = mxUtils.htmlEntities(content, false).
+			    	replace(/\n/g,'<br>').replace(/ /g, '&nbsp;');
+			   	wnd.document.body.appendChild(pre);
+			}
+	   	}
+	},
+	
+	/**
+	 * Function: alert
+	 * 
+	 * Displayss the given alert in a new dialog. This implementation uses the
+	 * built-in alert function. This is used to display validation errors when
+	 * connections cannot be changed or created.
+	 * 
+	 * Parameters:
+	 * 
+	 * message - String specifying the message to be displayed.
+	 */
+	alert: function(message)
+	{
+		alert(message);
+	},
+	
+	/**
+	 * Function: prompt
+	 * 
+	 * Displays the given message in a prompt dialog. This implementation uses
+	 * the built-in prompt function.
+	 * 
+	 * Parameters:
+	 * 
+	 * message - String specifying the message to be displayed.
+	 * defaultValue - Optional string specifying the default value.
+	 */
+	prompt: function(message, defaultValue)
+	{
+		return prompt(message, (defaultValue != null) ? defaultValue : '');
+	},
+	
+	/**
+	 * Function: confirm
+	 * 
+	 * Displays the given message in a confirm dialog. This implementation uses
+	 * the built-in confirm function.
+	 * 
+	 * Parameters:
+	 * 
+	 * message - String specifying the message to be displayed.
+	 */
+	confirm: function(message)
+	{
+		return confirm(message);
+	},
+
+	/**
+	 * Function: error
+	 * 
+	 * Displays the given error message in a new <mxWindow> of the given width.
+	 * If close is true then an additional close button is added to the window.
+	 * The optional icon specifies the icon to be used for the window. Default
+	 * is <mxUtils.errorImage>.
+	 * 
+	 * Parameters:
+	 * 
+	 * message - String specifying the message to be displayed.
+	 * width - Integer specifying the width of the window.
+	 * close - Optional boolean indicating whether to add a close button.
+	 * icon - Optional icon for the window decoration.
+	 */
+	error: function(message, width, close, icon)
+	{
+		var div = document.createElement('div');
+		div.style.padding = '20px';
+
+		var img = document.createElement('img');
+		img.setAttribute('src', icon || mxUtils.errorImage);
+		img.setAttribute('valign', 'bottom');
+		img.style.verticalAlign = 'middle';
+		div.appendChild(img);
+
+		div.appendChild(document.createTextNode('\u00a0')); // &nbsp;
+		div.appendChild(document.createTextNode('\u00a0')); // &nbsp;
+		div.appendChild(document.createTextNode('\u00a0')); // &nbsp;
+		mxUtils.write(div, message);
+
+		var w = document.body.clientWidth;
+		var h = (document.body.clientHeight || document.documentElement.clientHeight);
+		var warn = new mxWindow(mxResources.get(mxUtils.errorResource) ||
+			mxUtils.errorResource, div, (w-width)/2, h/4, width, null,
+			false, true);
+
+		if (close)
+		{
+			mxUtils.br(div);
+			
+			var tmp = document.createElement('p');
+			var button = document.createElement('button');
+
+			if (mxClient.IS_IE)
+			{
+				button.style.cssText = 'float:right';
+			}
+			else
+			{
+				button.setAttribute('style', 'float:right');
+			}
+
+			mxEvent.addListener(button, 'click', function(evt)
+			{
+				warn.destroy();
+			});
+
+			mxUtils.write(button, mxResources.get(mxUtils.closeResource) ||
+				mxUtils.closeResource);
+			
+			tmp.appendChild(button);
+			div.appendChild(tmp);
+			
+			mxUtils.br(div);
+			
+			warn.setClosable(true);
+		}
+		
+		warn.setVisible(true);
+		
+		return warn;
+	},
+
+	/**
+	 * Function: makeDraggable
+	 * 
+	 * Configures the given DOM element to act as a drag source for the
+	 * specified graph. Returns a a new <mxDragSource>. If
+	 * <mxDragSource.guideEnabled> is enabled then the x and y arguments must
+	 * be used in funct to match the preview location.
+	 * 
+	 * Example:
+	 * 
+	 * (code)
+	 * var funct = function(graph, evt, cell, x, y)
+	 * {
+	 *   if (graph.canImportCell(cell))
+	 *   {
+	 *     var parent = graph.getDefaultParent();
+	 *     var vertex = null;
+	 *     
+	 *     graph.getModel().beginUpdate();
+	 *     try
+	 *     {
+	 * 	     vertex = graph.insertVertex(parent, null, 'Hello', x, y, 80, 30);
+	 *     }
+	 *     finally
+	 *     {
+	 *       graph.getModel().endUpdate();
+	 *     }
+	 *
+	 *     graph.setSelectionCell(vertex);
+	 *   }
+	 * }
+	 * 
+	 * var img = document.createElement('img');
+	 * img.setAttribute('src', 'editors/images/rectangle.gif');
+	 * img.style.position = 'absolute';
+	 * img.style.left = '0px';
+	 * img.style.top = '0px';
+	 * img.style.width = '16px';
+	 * img.style.height = '16px';
+	 * 
+	 * var dragImage = img.cloneNode(true);
+	 * dragImage.style.width = '32px';
+	 * dragImage.style.height = '32px';
+	 * mxUtils.makeDraggable(img, graph, funct, dragImage);
+	 * document.body.appendChild(img);
+	 * (end)
+	 * 
+	 * Parameters:
+	 * 
+	 * element - DOM element to make draggable.
+	 * graphF - <mxGraph> that acts as the drop target or a function that takes a
+	 * mouse event and returns the current <mxGraph>.
+	 * funct - Function to execute on a successful drop.
+	 * dragElement - Optional DOM node to be used for the drag preview.
+	 * dx - Optional horizontal offset between the cursor and the drag
+	 * preview.
+	 * dy - Optional vertical offset between the cursor and the drag
+	 * preview.
+	 * autoscroll - Optional boolean that specifies if autoscroll should be
+	 * used. Default is mxGraph.autoscroll.
+	 * scalePreview - Optional boolean that specifies if the preview element
+	 * should be scaled according to the graph scale. If this is true, then
+	 * the offsets will also be scaled. Default is false.
+	 * highlightDropTargets - Optional boolean that specifies if dropTargets
+	 * should be highlighted. Default is true.
+	 * getDropTarget - Optional function to return the drop target for a given
+	 * location (x, y). Default is mxGraph.getCellAt.
+	 */
+	makeDraggable: function(element, graphF, funct, dragElement, dx, dy, autoscroll,
+			scalePreview, highlightDropTargets, getDropTarget)
+	{
+		var dragSource = new mxDragSource(element, funct);
+		dragSource.dragOffset = new mxPoint((dx != null) ? dx : 0,
+			(dy != null) ? dy : mxConstants.TOOLTIP_VERTICAL_OFFSET);
+		dragSource.autoscroll = autoscroll;
+		
+		// Cannot enable this by default. This needs to be enabled in the caller
+		// if the funct argument uses the new x- and y-arguments.
+		dragSource.setGuidesEnabled(false);
+		
+		if (highlightDropTargets != null)
+		{
+			dragSource.highlightDropTargets = highlightDropTargets;
+		}
+		
+		// Overrides function to find drop target cell
+		if (getDropTarget != null)
+		{
+			dragSource.getDropTarget = getDropTarget;
+		}
+		
+		// Overrides function to get current graph
+		dragSource.getGraphForEvent = function(evt)
+		{
+			return (typeof(graphF) == 'function') ? graphF(evt) : graphF;
+		};
+		
+		// Translates switches into dragSource customizations
+		if (dragElement != null)
+		{
+			dragSource.createDragElement = function()
+			{
+				return dragElement.cloneNode(true);
+			};
+			
+			if (scalePreview)
+			{
+				dragSource.createPreviewElement = function(graph)
+				{
+					var elt = dragElement.cloneNode(true);
+
+					var w = parseInt(elt.style.width);
+					var h = parseInt(elt.style.height);
+					elt.style.width = Math.round(w * graph.view.scale) + 'px';
+					elt.style.height = Math.round(h * graph.view.scale) + 'px';
+					
+					return elt;
+				};
+			}
+		}
+		
+		return dragSource;
+	}
+
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/util/mxVmlCanvas2D.js b/airavata-kubernetes/web-console/src/assets/js/util/mxVmlCanvas2D.js
new file mode 100644
index 0000000..2b3d600
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/util/mxVmlCanvas2D.js
@@ -0,0 +1,1102 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ *
+ * Class: mxVmlCanvas2D
+ * 
+ * Implements a canvas to be used for rendering VML. Here is an example of implementing a
+ * fallback for SVG images which are not supported in VML-based browsers.
+ * 
+ * (code)
+ * var mxVmlCanvas2DImage = mxVmlCanvas2D.prototype.image;
+ * mxVmlCanvas2D.prototype.image = function(x, y, w, h, src, aspect, flipH, flipV)
+ * {
+ *   if (src.substring(src.length - 4, src.length) == '.svg')
+ *   {
+ *     src = 'http://www.jgraph.com/images/mxgraph.gif';
+ *   }
+ *   
+ *   mxVmlCanvas2DImage.apply(this, arguments);
+ * };
+ * (end)
+ * 
+ * To disable anti-aliasing in the output, use the following code.
+ * 
+ * (code)
+ * document.createStyleSheet().cssText = mxClient.VML_PREFIX + '\\:*{antialias:false;)}';
+ * (end)
+ * 
+ * A description of the public API is available in <mxXmlCanvas2D>. Note that
+ * there is a known issue in VML where gradients are painted using the outer
+ * bounding box of rotated shapes, not the actual bounds of the shape. See
+ * also <text> for plain text label restrictions in shapes for VML.
+ */
+var mxVmlCanvas2D = function(root)
+{
+	mxAbstractCanvas2D.call(this);
+
+	/**
+	 * Variable: root
+	 * 
+	 * Reference to the container for the SVG content.
+	 */
+	this.root = root;
+};
+
+/**
+ * Extends mxAbstractCanvas2D
+ */
+mxUtils.extend(mxVmlCanvas2D, mxAbstractCanvas2D);
+
+/**
+ * Variable: path
+ * 
+ * Holds the current DOM node.
+ */
+mxVmlCanvas2D.prototype.node = null;
+
+/**
+ * Variable: textEnabled
+ * 
+ * Specifies if text output should be enabledetB. Default is true.
+ */
+mxVmlCanvas2D.prototype.textEnabled = true;
+
+/**
+ * Variable: moveOp
+ * 
+ * Contains the string used for moving in paths. Default is 'm'.
+ */
+mxVmlCanvas2D.prototype.moveOp = 'm';
+
+/**
+ * Variable: lineOp
+ * 
+ * Contains the string used for moving in paths. Default is 'l'.
+ */
+mxVmlCanvas2D.prototype.lineOp = 'l';
+
+/**
+ * Variable: curveOp
+ * 
+ * Contains the string used for bezier curves. Default is 'c'.
+ */
+mxVmlCanvas2D.prototype.curveOp = 'c';
+
+/**
+ * Variable: closeOp
+ * 
+ * Holds the operator for closing curves. Default is 'x e'.
+ */
+mxVmlCanvas2D.prototype.closeOp = 'x';
+
+/**
+ * Variable: rotatedHtmlBackground
+ * 
+ * Background color for rotated HTML. Default is ''. This can be set to eg.
+ * white to improve rendering of rotated text in VML for IE9.
+ */
+mxVmlCanvas2D.prototype.rotatedHtmlBackground = '';
+
+/**
+ * Variable: vmlScale
+ * 
+ * Specifies the scale used to draw VML shapes.
+ */
+mxVmlCanvas2D.prototype.vmlScale = 1;
+
+/**
+ * Function: createElement
+ * 
+ * Creates the given element using the document.
+ */
+mxVmlCanvas2D.prototype.createElement = function(name)
+{
+	return document.createElement(name);
+};
+
+/**
+ * Function: createVmlElement
+ * 
+ * Creates a new element using <createElement> and prefixes the given name with
+ * <mxClient.VML_PREFIX>.
+ */
+mxVmlCanvas2D.prototype.createVmlElement = function(name)
+{
+	return this.createElement(mxClient.VML_PREFIX + ':' + name);
+};
+
+/**
+ * Function: addNode
+ * 
+ * Adds the current node to the <root>.
+ */
+mxVmlCanvas2D.prototype.addNode = function(filled, stroked)
+{
+	var node = this.node;
+	var s = this.state;
+	
+	if (node != null)
+	{
+		if (node.nodeName == 'shape')
+		{
+			// Checks if the path is not empty
+			if (this.path != null && this.path.length > 0)
+			{
+				node.path = this.path.join(' ') + ' e';
+				node.style.width = this.root.style.width;
+				node.style.height = this.root.style.height;
+				node.coordsize = parseInt(node.style.width) + ' ' + parseInt(node.style.height);
+			}
+			else
+			{
+				return;
+			}
+		}
+
+		node.strokeweight = this.format(Math.max(1, s.strokeWidth * s.scale / this.vmlScale)) + 'px';
+		
+		if (s.shadow)
+		{
+			this.root.appendChild(this.createShadow(node,
+				filled && s.fillColor != null,
+				stroked && s.strokeColor != null));
+		}
+		
+		if (stroked && s.strokeColor != null)
+		{
+			node.stroked = 'true';
+			node.strokecolor = s.strokeColor;
+		}
+		else
+		{
+			node.stroked = 'false';
+		}
+
+		node.appendChild(this.createStroke());
+
+		if (filled && s.fillColor != null)
+		{
+			node.appendChild(this.createFill());
+		}
+		else if (this.pointerEvents && (node.nodeName != 'shape' ||
+			this.path[this.path.length - 1] == this.closeOp))
+		{
+			node.appendChild(this.createTransparentFill());
+		}
+		else
+		{
+			node.filled = 'false';
+		}
+
+		// LATER: Update existing DOM for performance
+		this.root.appendChild(node);
+	}
+};
+
+/**
+ * Function: createTransparentFill
+ * 
+ * Creates a transparent fill.
+ */
+mxVmlCanvas2D.prototype.createTransparentFill = function()
+{
+	var fill = this.createVmlElement('fill');
+	fill.src = mxClient.imageBasePath + '/transparent.gif';
+	fill.type = 'tile';
+	
+	return fill;
+};
+
+/**
+ * Function: createFill
+ * 
+ * Creates a fill for the current state.
+ */
+mxVmlCanvas2D.prototype.createFill = function()
+{
+	var s = this.state;
+	
+	// Gradients in foregrounds not supported because special gradients
+	// with bounds must be created for each element in graphics-canvases
+	var fill = this.createVmlElement('fill');
+	fill.color = s.fillColor;
+
+	if (s.gradientColor != null)
+	{
+		fill.type = 'gradient';
+		fill.method = 'none';
+		fill.color2 = s.gradientColor;
+		var angle = 180 - s.rotation;
+		
+		if (s.gradientDirection == mxConstants.DIRECTION_WEST)
+		{
+			angle -= 90 + ((this.root.style.flip == 'x') ? 180 : 0);
+		}
+		else if (s.gradientDirection == mxConstants.DIRECTION_EAST)
+		{
+			angle += 90 + ((this.root.style.flip == 'x') ? 180 : 0);
+		}
+		else if (s.gradientDirection == mxConstants.DIRECTION_NORTH)
+		{
+			angle -= 180 + ((this.root.style.flip == 'y') ? -180 : 0);
+		}
+		else
+		{
+			 angle += ((this.root.style.flip == 'y') ? -180 : 0);
+		}
+		
+		if (this.root.style.flip == 'x' || this.root.style.flip == 'y')
+		{
+			angle *= -1;
+		}
+
+		// LATER: Fix outer bounding box for rotated shapes used in VML.
+		fill.angle = mxUtils.mod(angle, 360);
+		fill.opacity = (s.alpha * s.gradientFillAlpha * 100) + '%';
+		fill.setAttribute(mxClient.OFFICE_PREFIX + ':opacity2', (s.alpha * s.gradientAlpha * 100) + '%');
+	}
+	else if (s.alpha < 1 || s.fillAlpha < 1)
+	{
+		fill.opacity = (s.alpha * s.fillAlpha * 100) + '%';			
+	}
+	
+	return fill;
+};
+/**
+ * Function: createStroke
+ * 
+ * Creates a fill for the current state.
+ */
+mxVmlCanvas2D.prototype.createStroke = function()
+{
+	var s = this.state;
+	var stroke = this.createVmlElement('stroke');
+	stroke.endcap = s.lineCap || 'flat';
+	stroke.joinstyle = s.lineJoin || 'miter';
+	stroke.miterlimit = s.miterLimit || '10';
+	
+	if (s.alpha < 1 || s.strokeAlpha < 1)
+	{
+		stroke.opacity = (s.alpha * s.strokeAlpha * 100) + '%';
+	}
+	
+	if (s.dashed)
+	{
+		stroke.dashstyle = this.getVmlDashStyle();
+	}
+	
+	return stroke;
+};
+
+/**
+ * Function: getVmlDashPattern
+ * 
+ * Returns a VML dash pattern for the current dashPattern.
+ * See http://msdn.microsoft.com/en-us/library/bb264085(v=vs.85).aspx
+ */
+mxVmlCanvas2D.prototype.getVmlDashStyle = function()
+{
+	var result = 'dash';
+	
+	if (typeof(this.state.dashPattern) === 'string')
+	{
+		var tok = this.state.dashPattern.split(' ');
+		
+		if (tok.length > 0 && tok[0] == 1)
+		{
+			result = '0 2';
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: createShadow
+ * 
+ * Creates a shadow for the given node.
+ */
+mxVmlCanvas2D.prototype.createShadow = function(node, filled, stroked)
+{
+	var s = this.state;
+	var rad = -s.rotation * (Math.PI / 180);
+	var cos = Math.cos(rad);
+	var sin = Math.sin(rad);
+
+	var dx = s.shadowDx * s.scale;
+	var dy = s.shadowDy * s.scale;
+
+	if (this.root.style.flip == 'x')
+	{
+		dx *= -1;
+	}
+	else if (this.root.style.flip == 'y')
+	{
+		dy *= -1;
+	}
+	
+	var shadow = node.cloneNode(true);
+	shadow.style.marginLeft = Math.round(dx * cos - dy * sin) + 'px';
+	shadow.style.marginTop = Math.round(dx * sin + dy * cos) + 'px';
+
+	// Workaround for wrong cloning in IE8 standards mode
+	if (document.documentMode == 8)
+	{
+		shadow.strokeweight = node.strokeweight;
+		
+		if (node.nodeName == 'shape')
+		{
+			shadow.path = this.path.join(' ') + ' e';
+			shadow.style.width = this.root.style.width;
+			shadow.style.height = this.root.style.height;
+			shadow.coordsize = parseInt(node.style.width) + ' ' + parseInt(node.style.height);
+		}
+	}
+	
+	if (stroked)
+	{
+		shadow.strokecolor = s.shadowColor;
+		shadow.appendChild(this.createShadowStroke());
+	}
+	else
+	{
+		shadow.stroked = 'false';
+	}
+	
+	if (filled)
+	{
+		shadow.appendChild(this.createShadowFill());
+	}
+	else
+	{
+		shadow.filled = 'false';
+	}
+	
+	return shadow;
+};
+
+/**
+ * Function: createShadowFill
+ * 
+ * Creates the fill for the shadow.
+ */
+mxVmlCanvas2D.prototype.createShadowFill = function()
+{
+	var fill = this.createVmlElement('fill');
+	fill.color = this.state.shadowColor;
+	fill.opacity = (this.state.alpha * this.state.shadowAlpha * 100) + '%';
+	
+	return fill;
+};
+
+/**
+ * Function: createShadowStroke
+ * 
+ * Creates the stroke for the shadow.
+ */
+mxVmlCanvas2D.prototype.createShadowStroke = function()
+{
+	var stroke = this.createStroke();
+	stroke.opacity = (this.state.alpha * this.state.shadowAlpha * 100) + '%';
+	
+	return stroke;
+};
+
+/**
+ * Function: rotate
+ * 
+ * Sets the rotation of the canvas. Note that rotation cannot be concatenated.
+ */
+mxVmlCanvas2D.prototype.rotate = function(theta, flipH, flipV, cx, cy)
+{
+	if (flipH && flipV)
+	{
+		theta += 180;
+	}
+	else if (flipH)
+	{
+		this.root.style.flip = 'x';
+	}
+	else if (flipV)
+	{
+		this.root.style.flip = 'y';
+	}
+
+	if (flipH ? !flipV : flipV)
+	{
+		theta *= -1;
+	}
+
+	this.root.style.rotation = theta;
+	this.state.rotation = this.state.rotation + theta;
+	this.state.rotationCx = cx;
+	this.state.rotationCy = cy;
+};
+
+/**
+ * Function: begin
+ * 
+ * Extends superclass to create path.
+ */
+mxVmlCanvas2D.prototype.begin = function()
+{
+	mxAbstractCanvas2D.prototype.begin.apply(this, arguments);
+	this.node = this.createVmlElement('shape');
+	this.node.style.position = 'absolute';
+};
+
+/**
+ * Function: quadTo
+ * 
+ * Replaces quadratic curve with bezier curve in VML.
+ */
+mxVmlCanvas2D.prototype.quadTo = function(x1, y1, x2, y2)
+{
+	var s = this.state;
+
+	var cpx0 = (this.lastX + s.dx) * s.scale;
+	var cpy0 = (this.lastY + s.dy) * s.scale;
+	var qpx1 = (x1 + s.dx) * s.scale;
+	var qpy1 = (y1 + s.dy) * s.scale;
+	var cpx3 = (x2 + s.dx) * s.scale;
+	var cpy3 = (y2 + s.dy) * s.scale;
+	
+	var cpx1 = cpx0 + 2/3 * (qpx1 - cpx0);
+	var cpy1 = cpy0 + 2/3 * (qpy1 - cpy0);
+	
+	var cpx2 = cpx3 + 2/3 * (qpx1 - cpx3);
+	var cpy2 = cpy3 + 2/3 * (qpy1 - cpy3);
+	
+	this.path.push('c ' + this.format(cpx1) + ' ' + this.format(cpy1) +
+			' ' + this.format(cpx2) + ' ' + this.format(cpy2) +
+			' ' + this.format(cpx3) + ' ' + this.format(cpy3));
+	this.lastX = (cpx3 / s.scale) - s.dx;
+	this.lastY = (cpy3 / s.scale) - s.dy;
+	
+};
+
+/**
+ * Function: createRect
+ * 
+ * Sets the glass gradient.
+ */
+mxVmlCanvas2D.prototype.createRect = function(nodeName, x, y, w, h)
+{
+	var s = this.state;
+	var n = this.createVmlElement(nodeName);
+	n.style.position = 'absolute';
+	n.style.left = this.format((x + s.dx) * s.scale) + 'px';
+	n.style.top = this.format((y + s.dy) * s.scale) + 'px';
+	n.style.width = this.format(w * s.scale) + 'px';
+	n.style.height = this.format(h * s.scale) + 'px';
+	
+	return n;
+};
+
+/**
+ * Function: rect
+ * 
+ * Sets the current path to a rectangle.
+ */
+mxVmlCanvas2D.prototype.rect = function(x, y, w, h)
+{
+	this.node = this.createRect('rect', x, y, w, h);
+};
+
+/**
+ * Function: roundrect
+ * 
+ * Sets the current path to a rounded rectangle.
+ */
+mxVmlCanvas2D.prototype.roundrect = function(x, y, w, h, dx, dy)
+{
+	this.node = this.createRect('roundrect', x, y, w, h);
+	// SetAttribute needed here for IE8
+	this.node.setAttribute('arcsize', Math.max(dx * 100 / w, dy * 100 / h) + '%');
+};
+
+/**
+ * Function: ellipse
+ * 
+ * Sets the current path to an ellipse.
+ */
+mxVmlCanvas2D.prototype.ellipse = function(x, y, w, h)
+{
+	this.node = this.createRect('oval', x, y, w, h);
+};
+
+/**
+ * Function: image
+ * 
+ * Paints an image.
+ */
+mxVmlCanvas2D.prototype.image = function(x, y, w, h, src, aspect, flipH, flipV)
+{
+	var node = null;
+	
+	if (!aspect)
+	{
+		node = this.createRect('image', x, y, w, h);
+		node.src = src;
+	}
+	else
+	{
+		// Uses fill with aspect to avoid asynchronous update of size
+		node = this.createRect('rect', x, y, w, h);
+		node.stroked = 'false';
+		
+		// Handles image aspect via fill
+		var fill = this.createVmlElement('fill');
+		fill.aspect = (aspect) ? 'atmost' : 'ignore';
+		fill.rotate = 'true';
+		fill.type = 'frame';
+		fill.src = src;
+
+		node.appendChild(fill);
+	}
+	
+	if (flipH && flipV)
+	{
+		node.style.rotation = '180';
+	}
+	else if (flipH)
+	{
+		node.style.flip = 'x';
+	}
+	else if (flipV)
+	{
+		node.style.flip = 'y';
+	}
+	
+	if (this.state.alpha < 1 || this.state.fillAlpha < 1)
+	{
+		// KNOWN: Borders around transparent images in IE<9. Using fill.opacity
+		// fixes this problem by adding a white background in all IE versions.
+		node.style.filter += 'alpha(opacity=' + (this.state.alpha * this.state.fillAlpha * 100) + ')';
+	}
+
+	this.root.appendChild(node);
+};
+
+/**
+ * Function: createText
+ * 
+ * Creates the innermost element that contains the HTML text.
+ */
+mxVmlCanvas2D.prototype.createDiv = function(str, align, valign, overflow)
+{
+	var div = this.createElement('div');
+	var state = this.state;
+
+	var css = '';
+	
+	if (state.fontBackgroundColor != null)
+	{
+		css += 'background-color:' + state.fontBackgroundColor + ';';
+	}
+	
+	if (state.fontBorderColor != null)
+	{
+		css += 'border:1px solid ' + state.fontBorderColor + ';';
+	}
+	
+	if (mxUtils.isNode(str))
+	{
+		div.appendChild(str);
+	}
+	else
+	{
+		if (overflow != 'fill' && overflow != 'width')
+		{
+			var div2 = this.createElement('div');
+			div2.style.cssText = css;
+			div2.style.display = (mxClient.IS_QUIRKS) ? 'inline' : 'inline-block';
+			div2.style.zoom = '1';
+			div2.style.textDecoration = 'inherit';
+			div2.innerHTML = str;
+			div.appendChild(div2);
+		}
+		else
+		{
+			div.style.cssText = css;
+			div.innerHTML = str;
+		}
+	}
+	
+	var style = div.style;
+
+	style.fontSize = (state.fontSize / this.vmlScale) + 'px';
+	style.fontFamily = state.fontFamily;
+	style.color = state.fontColor;
+	style.verticalAlign = 'top';
+	style.textAlign = align || 'left';
+	style.lineHeight = (mxConstants.ABSOLUTE_LINE_HEIGHT) ? (state.fontSize * mxConstants.LINE_HEIGHT / this.vmlScale) + 'px' : mxConstants.LINE_HEIGHT;
+
+	if ((state.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
+	{
+		style.fontWeight = 'bold';
+	}
+
+	if ((state.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
+	{
+		style.fontStyle = 'italic';
+	}
+	
+	if ((state.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)
+	{
+		style.textDecoration = 'underline';
+	}
+	
+	return div;
+};
+
+/**
+ * Function: text
+ * 
+ * Paints the given text. Possible values for format are empty string for plain
+ * text and html for HTML markup. Clipping, text background and border are not
+ * supported for plain text in VML.
+ */
+mxVmlCanvas2D.prototype.text = function(x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation, dir)
+{
+	if (this.textEnabled && str != null)
+	{
+		var s = this.state;
+		
+		if (format == 'html')
+		{
+			if (s.rotation != null)
+			{
+				var pt = this.rotatePoint(x, y, s.rotation, s.rotationCx, s.rotationCy);
+				
+				x = pt.x;
+				y = pt.y;
+			}
+
+			if (document.documentMode == 8 && !mxClient.IS_EM)
+			{
+				x += s.dx;
+				y += s.dy;
+				
+				// Workaround for rendering offsets
+				if (overflow != 'fill' && valign == mxConstants.ALIGN_TOP)
+				{
+					y -= 1;
+				}
+			}
+			else
+			{
+				x *= s.scale;
+				y *= s.scale;
+			}
+
+			// Adds event transparency in IE8 standards without the transparent background
+			// filter which cannot be used due to bugs in the zoomed bounding box (too slow)
+			// FIXME: No event transparency if inside v:rect (ie part of shape)
+			// KNOWN: Offset wrong for rotated text with word that are longer than the wrapping
+			// width in IE8 because real width of text cannot be determined here.
+			// This should be fixed in mxText.updateBoundingBox by calling before this and
+			// passing the real width to this method if not clipped and wrapped.
+			var abs = (document.documentMode == 8 && !mxClient.IS_EM) ? this.createVmlElement('group') : this.createElement('div');
+			abs.style.position = 'absolute';
+			abs.style.display = 'inline';
+			abs.style.left = this.format(x) + 'px';
+			abs.style.top = this.format(y) + 'px';
+			abs.style.zoom = s.scale;
+
+			var box = this.createElement('div');
+			box.style.position = 'relative';
+			box.style.display = 'inline';
+			
+			var margin = mxUtils.getAlignmentAsPoint(align, valign);
+			var dx = margin.x;
+			var dy = margin.y;
+
+			var div = this.createDiv(str, align, valign, overflow);
+			var inner = this.createElement('div');
+			
+			if (dir != null)
+			{
+				div.setAttribute('dir', dir);
+			}
+
+			if (wrap && w > 0)
+			{
+				if (!clip)
+				{
+					div.style.width = Math.round(w) + 'px';
+				}
+				
+				div.style.wordWrap = mxConstants.WORD_WRAP;
+				div.style.whiteSpace = 'normal';
+				
+				// LATER: Check if other cases need to be handled
+				if (div.style.wordWrap == 'break-word')
+				{
+					var tmp = div;
+					
+					if (tmp.firstChild != null && tmp.firstChild.nodeName == 'DIV')
+					{
+						tmp.firstChild.style.width = '100%';
+					}
+				}
+			}
+			else
+			{
+				div.style.whiteSpace = 'nowrap';
+			}
+			
+			var rot = s.rotation + (rotation || 0);
+			
+			if (this.rotateHtml && rot != 0)
+			{
+				inner.style.display = 'inline';
+				inner.style.zoom = '1';
+				inner.appendChild(div);
+
+				// Box not needed for rendering in IE8 standards
+				if (document.documentMode == 8 && !mxClient.IS_EM && this.root.nodeName != 'DIV')
+				{
+					box.appendChild(inner);
+					abs.appendChild(box);
+				}
+				else
+				{
+					abs.appendChild(inner);
+				}
+			}
+			else if (document.documentMode == 8 && !mxClient.IS_EM)
+			{
+				box.appendChild(div);
+				abs.appendChild(box);
+			}
+			else
+			{
+				div.style.display = 'inline';
+				abs.appendChild(div);
+			}
+			
+			// Inserts the node into the DOM
+			if (this.root.nodeName != 'DIV')
+			{
+				// Rectangle to fix position in group
+				var rect = this.createVmlElement('rect');
+				rect.stroked = 'false';
+				rect.filled = 'false';
+
+				rect.appendChild(abs);
+				this.root.appendChild(rect);
+			}
+			else
+			{
+				this.root.appendChild(abs);
+			}
+			
+			if (clip)
+			{
+				div.style.overflow = 'hidden';
+				div.style.width = Math.round(w) + 'px';
+				
+				if (!mxClient.IS_QUIRKS)
+				{
+					div.style.maxHeight = Math.round(h) + 'px';
+				}
+			}
+			else if (overflow == 'fill')
+			{
+				// KNOWN: Affects horizontal alignment in quirks
+				// but fill should only be used with align=left
+				div.style.overflow = 'hidden';
+				div.style.width = (Math.max(0, w) + 1) + 'px';
+				div.style.height = (Math.max(0, h) + 1) + 'px';
+			}
+			else if (overflow == 'width')
+			{
+				// KNOWN: Affects horizontal alignment in quirks
+				// but fill should only be used with align=left
+				div.style.overflow = 'hidden';
+				div.style.width = (Math.max(0, w) + 1) + 'px';
+				div.style.maxHeight = (Math.max(0, h) + 1) + 'px';
+			}
+			
+			if (this.rotateHtml && rot != 0)
+			{
+				var rad = rot * (Math.PI / 180);
+				
+				// Precalculate cos and sin for the rotation
+				var real_cos = parseFloat(parseFloat(Math.cos(rad)).toFixed(8));
+				var real_sin = parseFloat(parseFloat(Math.sin(-rad)).toFixed(8));
+
+				rad %= 2 * Math.PI;
+				if (rad < 0) rad += 2 * Math.PI;
+				rad %= Math.PI;
+				if (rad > Math.PI / 2) rad = Math.PI - rad;
+				
+				var cos = Math.cos(rad);
+				var sin = Math.sin(rad);
+
+				// Adds div to document to measure size
+				if (document.documentMode == 8 && !mxClient.IS_EM)
+				{
+					div.style.display = 'inline-block';
+					inner.style.display = 'inline-block';
+					box.style.display = 'inline-block';
+				}
+				
+				div.style.visibility = 'hidden';
+				div.style.position = 'absolute';
+				document.body.appendChild(div);
+				
+				var sizeDiv = div;
+				
+				if (sizeDiv.firstChild != null && sizeDiv.firstChild.nodeName == 'DIV')
+				{
+					sizeDiv = sizeDiv.firstChild;
+				}
+				
+				var tmp = sizeDiv.offsetWidth + 3;
+				var oh = sizeDiv.offsetHeight;
+				
+				if (clip)
+				{
+					w = Math.min(w, tmp);
+					oh = Math.min(oh, h);
+				}
+				else
+				{
+					w = tmp;
+				}
+
+				// Handles words that are longer than the given wrapping width
+				if (wrap)
+				{
+					div.style.width = w + 'px';
+				}
+				
+				// Simulates max-height in quirks
+				if (mxClient.IS_QUIRKS && (clip || overflow == 'width') && oh > h)
+				{
+					oh = h;
+					
+					// Quirks does not support maxHeight
+					div.style.height = oh + 'px';
+				}
+				
+				h = oh;
+
+				var top_fix = (h - h * cos + w * -sin) / 2 - real_sin * w * (dx + 0.5) + real_cos * h * (dy + 0.5);
+				var left_fix = (w - w * cos + h * -sin) / 2 + real_cos * w * (dx + 0.5) + real_sin * h * (dy + 0.5);
+
+				if (abs.nodeName == 'group' && this.root.nodeName == 'DIV')
+				{
+					// Workaround for bug where group gets moved away if left and top are non-zero in IE8 standards
+					var pos = this.createElement('div');
+					pos.style.display = 'inline-block';
+					pos.style.position = 'absolute';
+					pos.style.left = this.format(x + (left_fix - w / 2) * s.scale) + 'px';
+					pos.style.top = this.format(y + (top_fix - h / 2) * s.scale) + 'px';
+					
+					abs.parentNode.appendChild(pos);
+					pos.appendChild(abs);
+				}
+				else
+				{
+					var sc = (document.documentMode == 8 && !mxClient.IS_EM) ? 1 : s.scale;
+					
+					abs.style.left = this.format(x + (left_fix - w / 2) * sc) + 'px';
+					abs.style.top = this.format(y + (top_fix - h / 2) * sc) + 'px';
+				}
+				
+				// KNOWN: Rotated text rendering quality is bad for IE9 quirks
+				inner.style.filter = "progid:DXImageTransform.Microsoft.Matrix(M11="+real_cos+", M12="+
+					real_sin+", M21="+(-real_sin)+", M22="+real_cos+", sizingMethod='auto expand')";
+				inner.style.backgroundColor = this.rotatedHtmlBackground;
+				
+				if (this.state.alpha < 1)
+				{
+					inner.style.filter += 'alpha(opacity=' + (this.state.alpha * 100) + ')';
+				}
+
+				// Restore parent node for DIV
+				inner.appendChild(div);
+				div.style.position = '';
+				div.style.visibility = '';
+			}
+			else if (document.documentMode != 8 || mxClient.IS_EM)
+			{
+				div.style.verticalAlign = 'top';
+				
+				if (this.state.alpha < 1)
+				{
+					abs.style.filter = 'alpha(opacity=' + (this.state.alpha * 100) + ')';
+				}
+				
+				// Adds div to document to measure size
+				var divParent = div.parentNode;
+				div.style.visibility = 'hidden';
+				document.body.appendChild(div);
+				
+				w = div.offsetWidth;
+				var oh = div.offsetHeight;
+				
+				// Simulates max-height in quirks
+				if (mxClient.IS_QUIRKS && clip && oh > h)
+				{
+					oh = h;
+					
+					// Quirks does not support maxHeight
+					div.style.height = oh + 'px';
+				}
+				
+				h = oh;
+				
+				div.style.visibility = '';
+				divParent.appendChild(div);
+				
+				abs.style.left = this.format(x + w * dx * this.state.scale) + 'px';
+				abs.style.top = this.format(y + h * dy * this.state.scale) + 'px';
+			}
+			else
+			{
+				if (this.state.alpha < 1)
+				{
+					div.style.filter = 'alpha(opacity=' + (this.state.alpha * 100) + ')';
+				}
+				
+				// Faster rendering in IE8 without offsetWidth/Height
+				box.style.left = (dx * 100) + '%';
+				box.style.top = (dy * 100) + '%';
+			}
+		}
+		else
+		{
+			this.plainText(x, y, w, h, mxUtils.htmlEntities(str, false), align, valign, wrap, format, overflow, clip, rotation, dir);
+		}
+	}
+};
+
+/**
+ * Function: plainText
+ * 
+ * Paints the outline of the current path.
+ */
+mxVmlCanvas2D.prototype.plainText = function(x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation, dir)
+{
+	// TextDirection is ignored since this code is not used (format is always HTML in the text function)
+	var s = this.state;
+	x = (x + s.dx) * s.scale;
+	y = (y + s.dy) * s.scale;
+	
+	var node = this.createVmlElement('shape');
+	node.style.width = '1px';
+	node.style.height = '1px';
+	node.stroked = 'false';
+
+	var fill = this.createVmlElement('fill');
+	fill.color = s.fontColor;
+	fill.opacity = (s.alpha * 100) + '%';
+	node.appendChild(fill);
+	
+	var path = this.createVmlElement('path');
+	path.textpathok = 'true';
+	path.v = 'm ' + this.format(0) + ' ' + this.format(0) + ' l ' + this.format(1) + ' ' + this.format(0);
+	
+	node.appendChild(path);
+	
+	// KNOWN: Font family and text decoration ignored
+	var tp = this.createVmlElement('textpath');
+	tp.style.cssText = 'v-text-align:' + align;
+	tp.style.align = align;
+	tp.style.fontFamily = s.fontFamily;
+	tp.string = str;
+	tp.on = 'true';
+	
+	// Scale via fontsize instead of node.style.zoom for correct offsets in IE8
+	var size = s.fontSize * s.scale / this.vmlScale;
+	tp.style.fontSize = size + 'px';
+	
+	// Bold
+	if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
+	{
+		tp.style.fontWeight = 'bold';
+	}
+	
+	// Italic
+	if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
+	{
+		tp.style.fontStyle = 'italic';
+	}
+
+	// Underline
+	if ((s.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)
+	{
+		tp.style.textDecoration = 'underline';
+	}
+
+	var lines = str.split('\n');
+	var textHeight = size + (lines.length - 1) * size * mxConstants.LINE_HEIGHT;
+	var dx = 0;
+	var dy = 0;
+
+	if (valign == mxConstants.ALIGN_BOTTOM)
+	{
+		dy = - textHeight / 2;
+	}
+	else if (valign != mxConstants.ALIGN_MIDDLE) // top
+	{
+		dy = textHeight / 2;
+	}
+
+	if (rotation != null)
+	{
+		node.style.rotation = rotation;
+		var rad = rotation * (Math.PI / 180);
+		dx = Math.sin(rad) * dy;
+		dy = Math.cos(rad) * dy;
+	}
+
+	// FIXME: Clipping is relative to bounding box
+	/*if (clip)
+	{
+		node.style.clip = 'rect(0px ' + this.format(w) + 'px ' + this.format(h) + 'px 0px)';
+	}*/
+	
+	node.appendChild(tp);
+	node.style.left = this.format(x - dx) + 'px';
+	node.style.top = this.format(y + dy) + 'px';
+	
+	this.root.appendChild(node);
+};
+
+/**
+ * Function: stroke
+ * 
+ * Paints the outline of the current path.
+ */
+mxVmlCanvas2D.prototype.stroke = function()
+{
+	this.addNode(false, true);
+};
+
+/**
+ * Function: fill
+ * 
+ * Fills the current path.
+ */
+mxVmlCanvas2D.prototype.fill = function()
+{
+	this.addNode(true, false);
+};
+
+/**
+ * Function: fillAndStroke
+ * 
+ * Fills and paints the outline of the current path.
+ */
+mxVmlCanvas2D.prototype.fillAndStroke = function()
+{
+	this.addNode(true, true);
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/util/mxWindow.js b/airavata-kubernetes/web-console/src/assets/js/util/mxWindow.js
new file mode 100644
index 0000000..7d95f49
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/util/mxWindow.js
@@ -0,0 +1,1130 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxWindow
+ * 
+ * Basic window inside a document.
+ * 
+ * Examples:
+ * 
+ * Creating a simple window.
+ *
+ * (code)
+ * var tb = document.createElement('div');
+ * var wnd = new mxWindow('Title', tb, 100, 100, 200, 200, true, true);
+ * wnd.setVisible(true); 
+ * (end)
+ *
+ * Creating a window that contains an iframe. 
+ * 
+ * (code)
+ * var frame = document.createElement('iframe');
+ * frame.setAttribute('width', '192px');
+ * frame.setAttribute('height', '172px');
+ * frame.setAttribute('src', 'http://www.example.com/');
+ * frame.style.backgroundColor = 'white';
+ * 
+ * var w = document.body.clientWidth;
+ * var h = (document.body.clientHeight || document.documentElement.clientHeight);
+ * var wnd = new mxWindow('Title', frame, (w-200)/2, (h-200)/3, 200, 200);
+ * wnd.setVisible(true);
+ * (end)
+ * 
+ * To limit the movement of a window, eg. to keep it from being moved beyond
+ * the top, left corner the following method can be overridden (recommended):
+ * 
+ * (code)
+ * wnd.setLocation = function(x, y)
+ * {
+ *   x = Math.max(0, x);
+ *   y = Math.max(0, y);
+ *   mxWindow.prototype.setLocation.apply(this, arguments);
+ * };
+ * (end)
+ * 
+ * Or the following event handler can be used:
+ * 
+ * (code)
+ * wnd.addListener(mxEvent.MOVE, function(e)
+ * {
+ *   wnd.setLocation(Math.max(0, wnd.getX()), Math.max(0, wnd.getY()));
+ * });
+ * (end)
+ * 
+ * To keep a window inside the current window:
+ * 
+ * (code)
+ * mxEvent.addListener(window, 'resize', mxUtils.bind(this, function()
+ * {
+ *   var iw = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
+ *   var ih = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
+ *   
+ *   var x = this.window.getX();
+ *   var y = this.window.getY();
+ *   
+ *   if (x + this.window.table.clientWidth > iw)
+ *   {
+ *     x = Math.max(0, iw - this.window.table.clientWidth);
+ *   }
+ *   
+ *   if (y + this.window.table.clientHeight > ih)
+ *   {
+ *     y = Math.max(0, ih - this.window.table.clientHeight);
+ *   }
+ *   
+ *   if (this.window.getX() != x || this.window.getY() != y)
+ *   {
+ *     this.window.setLocation(x, y);
+ *   }
+ * }));
+ * (end)
+ *
+ * Event: mxEvent.MOVE_START
+ *
+ * Fires before the window is moved. The <code>event</code> property contains
+ * the corresponding mouse event.
+ *
+ * Event: mxEvent.MOVE
+ *
+ * Fires while the window is being moved. The <code>event</code> property
+ * contains the corresponding mouse event.
+ *
+ * Event: mxEvent.MOVE_END
+ *
+ * Fires after the window is moved. The <code>event</code> property contains
+ * the corresponding mouse event.
+ *
+ * Event: mxEvent.RESIZE_START
+ *
+ * Fires before the window is resized. The <code>event</code> property contains
+ * the corresponding mouse event.
+ *
+ * Event: mxEvent.RESIZE
+ *
+ * Fires while the window is being resized. The <code>event</code> property
+ * contains the corresponding mouse event.
+ *
+ * Event: mxEvent.RESIZE_END
+ *
+ * Fires after the window is resized. The <code>event</code> property contains
+ * the corresponding mouse event.
+ *
+ * Event: mxEvent.MAXIMIZE
+ * 
+ * Fires after the window is maximized. The <code>event</code> property
+ * contains the corresponding mouse event.
+ * 
+ * Event: mxEvent.MINIMIZE
+ * 
+ * Fires after the window is minimized. The <code>event</code> property
+ * contains the corresponding mouse event.
+ * 
+ * Event: mxEvent.NORMALIZE
+ * 
+ * Fires after the window is normalized, that is, it returned from
+ * maximized or minimized state. The <code>event</code> property contains the
+ * corresponding mouse event.
+ *  
+ * Event: mxEvent.ACTIVATE
+ * 
+ * Fires after a window is activated. The <code>previousWindow</code> property
+ * contains the previous window. The event sender is the active window.
+ * 
+ * Event: mxEvent.SHOW
+ * 
+ * Fires after the window is shown. This event has no properties.
+ * 
+ * Event: mxEvent.HIDE
+ * 
+ * Fires after the window is hidden. This event has no properties.
+ * 
+ * Event: mxEvent.CLOSE
+ * 
+ * Fires before the window is closed. The <code>event</code> property contains
+ * the corresponding mouse event.
+ * 
+ * Event: mxEvent.DESTROY
+ * 
+ * Fires before the window is destroyed. This event has no properties.
+ * 
+ * Constructor: mxWindow
+ * 
+ * Constructs a new window with the given dimension and title to display
+ * the specified content. The window elements use the given style as a
+ * prefix for the classnames of the respective window elements, namely,
+ * the window title and window pane. The respective postfixes are appended
+ * to the given stylename as follows:
+ * 
+ *   style - Base style for the window.
+ *   style+Title - Style for the window title.
+ *   style+Pane - Style for the window pane.
+ * 
+ * The default value for style is mxWindow, resulting in the following
+ * classnames for the window elements: mxWindow, mxWindowTitle and
+ * mxWindowPane.
+ * 
+ * If replaceNode is given then the window replaces the given DOM node in
+ * the document.
+ * 
+ * Parameters:
+ * 
+ * title - String that represents the title of the new window.
+ * content - DOM node that is used as the window content.
+ * x - X-coordinate of the window location.
+ * y - Y-coordinate of the window location.
+ * width - Width of the window.
+ * height - Optional height of the window. Default is to match the height
+ * of the content at the specified width.
+ * minimizable - Optional boolean indicating if the window is minimizable.
+ * Default is true.
+ * movable - Optional boolean indicating if the window is movable. Default
+ * is true.
+ * replaceNode - Optional DOM node that the window should replace.
+ * style - Optional base classname for the window elements. Default is
+ * mxWindow.
+ */
+function mxWindow(title, content, x, y, width, height, minimizable, movable, replaceNode, style)
+{
+	if (content != null)
+	{
+		minimizable = (minimizable != null) ? minimizable : true;
+		this.content = content;
+		this.init(x, y, width, height, style);
+		
+		this.installMaximizeHandler();
+		this.installMinimizeHandler();
+		this.installCloseHandler();
+		this.setMinimizable(minimizable);
+		this.setTitle(title);
+		
+		if (movable == null || movable)
+		{
+			this.installMoveHandler();
+		}
+
+		if (replaceNode != null && replaceNode.parentNode != null)
+		{
+			replaceNode.parentNode.replaceChild(this.div, replaceNode);
+		}
+		else
+		{
+			document.body.appendChild(this.div);
+		}
+	}
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxWindow.prototype = new mxEventSource();
+mxWindow.prototype.constructor = mxWindow;
+
+/**
+ * Variable: closeImage
+ * 
+ * URL of the image to be used for the close icon in the titlebar.
+ */
+mxWindow.prototype.closeImage = mxClient.imageBasePath + '/close.gif';
+
+/**
+ * Variable: minimizeImage
+ * 
+ * URL of the image to be used for the minimize icon in the titlebar.
+ */
+mxWindow.prototype.minimizeImage = mxClient.imageBasePath + '/minimize.gif';
+	
+/**
+ * Variable: normalizeImage
+ * 
+ * URL of the image to be used for the normalize icon in the titlebar.
+ */
+mxWindow.prototype.normalizeImage = mxClient.imageBasePath + '/normalize.gif';
+	
+/**
+ * Variable: maximizeImage
+ * 
+ * URL of the image to be used for the maximize icon in the titlebar.
+ */
+mxWindow.prototype.maximizeImage = mxClient.imageBasePath + '/maximize.gif';
+
+/**
+ * Variable: normalizeImage
+ * 
+ * URL of the image to be used for the resize icon.
+ */
+mxWindow.prototype.resizeImage = mxClient.imageBasePath + '/resize.gif';
+
+/**
+ * Variable: visible
+ * 
+ * Boolean flag that represents the visible state of the window.
+ */
+mxWindow.prototype.visible = false;
+
+/**
+ * Variable: minimumSize
+ * 
+ * <mxRectangle> that specifies the minimum width and height of the window.
+ * Default is (50, 40).
+ */
+mxWindow.prototype.minimumSize = new mxRectangle(0, 0, 50, 40);
+
+/**
+ * Variable: destroyOnClose
+ * 
+ * Specifies if the window should be destroyed when it is closed. If this
+ * is false then the window is hidden using <setVisible>. Default is true.
+ */
+mxWindow.prototype.destroyOnClose = true;
+
+/**
+ * Variable: contentHeightCorrection
+ * 
+ * Defines the correction factor for computing the height of the contentWrapper.
+ * Default is 6 for IE 7/8 standards mode and 2 for all other browsers and modes.
+ */
+mxWindow.prototype.contentHeightCorrection = (document.documentMode == 8 || document.documentMode == 7) ? 6 : 2;
+
+/**
+ * Variable: title
+ * 
+ * Reference to the DOM node (TD) that contains the title.
+ */
+mxWindow.prototype.title = null;
+
+/**
+ * Variable: content
+ * 
+ * Reference to the DOM node that represents the window content.
+ */
+mxWindow.prototype.content = null;
+
+/**
+ * Function: init
+ * 
+ * Initializes the DOM tree that represents the window.
+ */
+mxWindow.prototype.init = function(x, y, width, height, style)
+{
+	style = (style != null) ? style : 'mxWindow';
+	
+	this.div = document.createElement('div');
+	this.div.className = style;
+
+	this.div.style.left = x + 'px';
+	this.div.style.top = y + 'px';
+	this.table = document.createElement('table');
+	this.table.className = style;
+
+	// Disables built-in pan and zoom in IE10 and later
+	if (mxClient.IS_POINTER)
+	{
+		this.div.style.touchAction = 'none';
+	}
+	
+	// Workaround for table size problems in FF
+	if (width != null)
+	{
+		if (!mxClient.IS_QUIRKS)
+		{
+			this.div.style.width = width + 'px'; 
+		}
+		
+		this.table.style.width = width + 'px';
+	} 
+	
+	if (height != null)
+	{
+		if (!mxClient.IS_QUIRKS)
+		{
+			this.div.style.height = height + 'px';
+		}
+		
+		this.table.style.height = height + 'px';
+	}		
+	
+	// Creates title row
+	var tbody = document.createElement('tbody');
+	var tr = document.createElement('tr');
+	
+	this.title = document.createElement('td');
+	this.title.className = style + 'Title';
+	
+	this.buttons = document.createElement('div');
+	this.buttons.style.position = 'absolute';
+	this.buttons.style.display = 'inline-block';
+	this.buttons.style.right = '4px';
+	this.buttons.style.top = '5px';
+	this.title.appendChild(this.buttons);
+	
+	tr.appendChild(this.title);
+	tbody.appendChild(tr);
+	
+	// Creates content row and table cell
+	tr = document.createElement('tr');
+	this.td = document.createElement('td');
+	this.td.className = style + 'Pane';
+	
+	if (document.documentMode == 7)
+	{
+		this.td.style.height = '100%';
+	}
+
+	this.contentWrapper = document.createElement('div');
+	this.contentWrapper.className = style + 'Pane';
+	this.contentWrapper.style.width = '100%';
+	this.contentWrapper.appendChild(this.content);
+
+	// Workaround for div around div restricts height
+	// of inner div if outerdiv has hidden overflow
+	if (mxClient.IS_QUIRKS || this.content.nodeName.toUpperCase() != 'DIV')
+	{
+		this.contentWrapper.style.height = '100%';
+	}
+
+	// Puts all content into the DOM
+	this.td.appendChild(this.contentWrapper);
+	tr.appendChild(this.td);
+	tbody.appendChild(tr);
+	this.table.appendChild(tbody);
+	this.div.appendChild(this.table);
+	
+	// Puts the window on top of other windows when clicked
+	var activator = mxUtils.bind(this, function(evt)
+	{
+		this.activate();
+	});
+	
+	mxEvent.addGestureListeners(this.title, activator);
+	mxEvent.addGestureListeners(this.table, activator);
+
+	this.hide();
+};
+
+/**
+ * Function: setTitle
+ * 
+ * Sets the window title to the given string. HTML markup inside the title
+ * will be escaped.
+ */
+mxWindow.prototype.setTitle = function(title)
+{
+	// Removes all text content nodes (normally just one)
+	var child = this.title.firstChild;
+	
+	while (child != null)
+	{
+		var next = child.nextSibling;
+		
+		if (child.nodeType == mxConstants.NODETYPE_TEXT)
+		{
+			child.parentNode.removeChild(child);
+		}
+		
+		child = next;
+	}
+	
+	mxUtils.write(this.title, title || '');
+	this.title.appendChild(this.buttons);
+};
+
+/**
+ * Function: setScrollable
+ * 
+ * Sets if the window contents should be scrollable.
+ */
+mxWindow.prototype.setScrollable = function(scrollable)
+{
+	// Workaround for hang in Presto 2.5.22 (Opera 10.5)
+	if (navigator.userAgent.indexOf('Presto/2.5') < 0)
+	{
+		if (scrollable)
+		{
+			this.contentWrapper.style.overflow = 'auto';
+		}
+		else
+		{
+			this.contentWrapper.style.overflow = 'hidden';
+		}
+	}
+};
+
+/**
+ * Function: activate
+ * 
+ * Puts the window on top of all other windows.
+ */
+mxWindow.prototype.activate = function()
+{
+	if (mxWindow.activeWindow != this)
+	{
+		var style = mxUtils.getCurrentStyle(this.getElement());
+		var index = (style != null) ? style.zIndex : 3;
+
+		if (mxWindow.activeWindow)
+		{
+			var elt = mxWindow.activeWindow.getElement();
+			
+			if (elt != null && elt.style != null)
+			{
+				elt.style.zIndex = index;
+			}
+		}
+		
+		var previousWindow = mxWindow.activeWindow;
+		this.getElement().style.zIndex = parseInt(index) + 1;
+		mxWindow.activeWindow = this;
+		
+		this.fireEvent(new mxEventObject(mxEvent.ACTIVATE, 'previousWindow', previousWindow));
+	}
+};
+
+/**
+ * Function: getElement
+ * 
+ * Returuns the outermost DOM node that makes up the window.
+ */
+mxWindow.prototype.getElement = function()
+{
+	return this.div;
+};
+
+/**
+ * Function: fit
+ * 
+ * Makes sure the window is inside the client area of the window.
+ */
+mxWindow.prototype.fit = function()
+{
+	mxUtils.fit(this.div);
+};
+
+/**
+ * Function: isResizable
+ * 
+ * Returns true if the window is resizable.
+ */
+mxWindow.prototype.isResizable = function()
+{
+	if (this.resize != null)
+	{
+		return this.resize.style.display != 'none';
+	}
+	
+	return false;
+};
+
+/**
+ * Function: setResizable
+ * 
+ * Sets if the window should be resizable. To avoid interference with some
+ * built-in features of IE10 and later, the use of the following code is
+ * recommended if there are resizable <mxWindow>s in the page:
+ * 
+ * (code)
+ * if (mxClient.IS_POINTER)
+ * {
+ *   document.body.style.msTouchAction = 'none';
+ * }
+ * (end)
+ */
+mxWindow.prototype.setResizable = function(resizable)
+{
+	if (resizable)
+	{
+		if (this.resize == null)
+		{
+			this.resize = document.createElement('img');
+			this.resize.style.position = 'absolute';
+			this.resize.style.bottom = '2px';
+			this.resize.style.right = '2px';
+
+			this.resize.setAttribute('src', mxClient.imageBasePath + '/resize.gif');
+			this.resize.style.cursor = 'nw-resize';
+			
+			var startX = null;
+			var startY = null;
+			var width = null;
+			var height = null;
+			
+			var start = mxUtils.bind(this, function(evt)
+			{
+				// LATER: pointerdown starting on border of resize does start
+				// the drag operation but does not fire consecutive events via
+				// one of the listeners below (does pan instead).
+				// Workaround: document.body.style.msTouchAction = 'none'
+				this.activate();
+				startX = mxEvent.getClientX(evt);
+				startY = mxEvent.getClientY(evt);
+				width = this.div.offsetWidth;
+				height = this.div.offsetHeight;
+				
+				mxEvent.addGestureListeners(document, null, dragHandler, dropHandler);
+				this.fireEvent(new mxEventObject(mxEvent.RESIZE_START, 'event', evt));
+				mxEvent.consume(evt);
+			});
+
+			// Adds a temporary pair of listeners to intercept
+			// the gesture event in the document
+			var dragHandler = mxUtils.bind(this, function(evt)
+			{
+				if (startX != null && startY != null)
+				{
+					var dx = mxEvent.getClientX(evt) - startX;
+					var dy = mxEvent.getClientY(evt) - startY;
+	
+					this.setSize(width + dx, height + dy);
+	
+					this.fireEvent(new mxEventObject(mxEvent.RESIZE, 'event', evt));
+					mxEvent.consume(evt);
+				}
+			});
+			
+			var dropHandler = mxUtils.bind(this, function(evt)
+			{
+				if (startX != null && startY != null)
+				{
+					startX = null;
+					startY = null;
+					mxEvent.removeGestureListeners(document, null, dragHandler, dropHandler);
+					this.fireEvent(new mxEventObject(mxEvent.RESIZE_END, 'event', evt));
+					mxEvent.consume(evt);
+				}
+			});
+			
+			mxEvent.addGestureListeners(this.resize, start, dragHandler, dropHandler);
+			this.div.appendChild(this.resize);
+		}
+		else 
+		{
+			this.resize.style.display = 'inline';
+		}
+	}
+	else if (this.resize != null)
+	{
+		this.resize.style.display = 'none';
+	}
+};
+	
+/**
+ * Function: setSize
+ * 
+ * Sets the size of the window.
+ */
+mxWindow.prototype.setSize = function(width, height)
+{
+	width = Math.max(this.minimumSize.width, width);
+	height = Math.max(this.minimumSize.height, height);
+
+	// Workaround for table size problems in FF
+	if (!mxClient.IS_QUIRKS)
+	{
+		this.div.style.width =  width + 'px';
+		this.div.style.height = height + 'px';
+	}
+	
+	this.table.style.width =  width + 'px';
+	this.table.style.height = height + 'px';
+
+	if (!mxClient.IS_QUIRKS)
+	{
+		this.contentWrapper.style.height = (this.div.offsetHeight -
+			this.title.offsetHeight - this.contentHeightCorrection) + 'px';
+	}
+};
+	
+/**
+ * Function: setMinimizable
+ * 
+ * Sets if the window is minimizable.
+ */
+mxWindow.prototype.setMinimizable = function(minimizable)
+{
+	this.minimize.style.display = (minimizable) ? '' : 'none';
+};
+
+/**
+ * Function: getMinimumSize
+ * 
+ * Returns an <mxRectangle> that specifies the size for the minimized window.
+ * A width or height of 0 means keep the existing width or height. This
+ * implementation returns the height of the window title and keeps the width.
+ */
+mxWindow.prototype.getMinimumSize = function()
+{
+	return new mxRectangle(0, 0, 0, this.title.offsetHeight);
+};
+
+/**
+ * Function: installMinimizeHandler
+ * 
+ * Installs the event listeners required for minimizing the window.
+ */
+mxWindow.prototype.installMinimizeHandler = function()
+{
+	this.minimize = document.createElement('img');
+	
+	this.minimize.setAttribute('src', this.minimizeImage);
+	this.minimize.setAttribute('title', 'Minimize');
+	this.minimize.style.cursor = 'pointer';
+	this.minimize.style.marginLeft = '2px';
+	this.minimize.style.display = 'none';
+	
+	this.buttons.appendChild(this.minimize);
+	
+	var minimized = false;
+	var maxDisplay = null;
+	var height = null;
+
+	var funct = mxUtils.bind(this, function(evt)
+	{
+		this.activate();
+		
+		if (!minimized)
+		{
+			minimized = true;
+			
+			this.minimize.setAttribute('src', this.normalizeImage);
+			this.minimize.setAttribute('title', 'Normalize');
+			this.contentWrapper.style.display = 'none';
+			maxDisplay = this.maximize.style.display;
+			
+			this.maximize.style.display = 'none';
+			height = this.table.style.height;
+			
+			var minSize = this.getMinimumSize();
+			
+			if (minSize.height > 0)
+			{
+				if (!mxClient.IS_QUIRKS)
+				{
+					this.div.style.height = minSize.height + 'px';
+				}
+				
+				this.table.style.height = minSize.height + 'px';
+			}
+			
+			if (minSize.width > 0)
+			{
+				if (!mxClient.IS_QUIRKS)
+				{
+					this.div.style.width = minSize.width + 'px';
+				}
+				
+				this.table.style.width = minSize.width + 'px';
+			}
+			
+			if (this.resize != null)
+			{
+				this.resize.style.visibility = 'hidden';
+			}
+			
+			this.fireEvent(new mxEventObject(mxEvent.MINIMIZE, 'event', evt));
+		}
+		else
+		{
+			minimized = false;
+			
+			this.minimize.setAttribute('src', this.minimizeImage);
+			this.minimize.setAttribute('title', 'Minimize');
+			this.contentWrapper.style.display = ''; // default
+			this.maximize.style.display = maxDisplay;
+			
+			if (!mxClient.IS_QUIRKS)
+			{
+				this.div.style.height = height;
+			}
+			
+			this.table.style.height = height;
+
+			if (this.resize != null)
+			{
+				this.resize.style.visibility = '';
+			}
+			
+			this.fireEvent(new mxEventObject(mxEvent.NORMALIZE, 'event', evt));
+		}
+		
+		mxEvent.consume(evt);
+	});
+	
+	mxEvent.addGestureListeners(this.minimize, funct);
+};
+	
+/**
+ * Function: setMaximizable
+ * 
+ * Sets if the window is maximizable.
+ */
+mxWindow.prototype.setMaximizable = function(maximizable)
+{
+	this.maximize.style.display = (maximizable) ? '' : 'none';
+};
+
+/**
+ * Function: installMaximizeHandler
+ * 
+ * Installs the event listeners required for maximizing the window.
+ */
+mxWindow.prototype.installMaximizeHandler = function()
+{
+	this.maximize = document.createElement('img');
+	
+	this.maximize.setAttribute('src', this.maximizeImage);
+	this.maximize.setAttribute('title', 'Maximize');
+	this.maximize.style.cursor = 'default';
+	this.maximize.style.marginLeft = '2px';
+	this.maximize.style.cursor = 'pointer';
+	this.maximize.style.display = 'none';
+	
+	this.buttons.appendChild(this.maximize);
+	
+	var maximized = false;
+	var x = null;
+	var y = null;
+	var height = null;
+	var width = null;
+	var minDisplay = null;
+
+	var funct = mxUtils.bind(this, function(evt)
+	{
+		this.activate();
+		
+		if (this.maximize.style.display != 'none')
+		{
+			if (!maximized)
+			{
+				maximized = true;
+				
+				this.maximize.setAttribute('src', this.normalizeImage);
+				this.maximize.setAttribute('title', 'Normalize');
+				this.contentWrapper.style.display = '';
+				minDisplay = this.minimize.style.display;
+				this.minimize.style.display = 'none';
+				
+				// Saves window state
+				x = parseInt(this.div.style.left);
+				y = parseInt(this.div.style.top);
+				height = this.table.style.height;
+				width = this.table.style.width;
+
+				this.div.style.left = '0px';
+				this.div.style.top = '0px';
+				var docHeight = Math.max(document.body.clientHeight || 0, document.documentElement.clientHeight || 0);
+
+				if (!mxClient.IS_QUIRKS)
+				{
+					this.div.style.width = (document.body.clientWidth - 2) + 'px';
+					this.div.style.height = (docHeight - 2) + 'px';
+				}
+
+				this.table.style.width = (document.body.clientWidth - 2) + 'px';
+				this.table.style.height = (docHeight - 2) + 'px';
+				
+				if (this.resize != null)
+				{
+					this.resize.style.visibility = 'hidden';
+				}
+
+				if (!mxClient.IS_QUIRKS)
+				{
+					var style = mxUtils.getCurrentStyle(this.contentWrapper);
+		
+					if (style.overflow == 'auto' || this.resize != null)
+					{
+						this.contentWrapper.style.height = (this.div.offsetHeight -
+							this.title.offsetHeight - this.contentHeightCorrection) + 'px';
+					}
+				}
+
+				this.fireEvent(new mxEventObject(mxEvent.MAXIMIZE, 'event', evt));
+			}
+			else
+			{
+				maximized = false;
+				
+				this.maximize.setAttribute('src', this.maximizeImage);
+				this.maximize.setAttribute('title', 'Maximize');
+				this.contentWrapper.style.display = '';
+				this.minimize.style.display = minDisplay;
+
+				// Restores window state
+				this.div.style.left = x+'px';
+				this.div.style.top = y+'px';
+				
+				if (!mxClient.IS_QUIRKS)
+				{
+					this.div.style.height = height;
+					this.div.style.width = width;
+
+					var style = mxUtils.getCurrentStyle(this.contentWrapper);
+		
+					if (style.overflow == 'auto' || this.resize != null)
+					{
+						this.contentWrapper.style.height = (this.div.offsetHeight -
+							this.title.offsetHeight - this.contentHeightCorrection) + 'px';
+					}
+				}
+				
+				this.table.style.height = height;
+				this.table.style.width = width;
+
+				if (this.resize != null)
+				{
+					this.resize.style.visibility = '';
+				}
+				
+				this.fireEvent(new mxEventObject(mxEvent.NORMALIZE, 'event', evt));
+			}
+			
+			mxEvent.consume(evt);
+		}
+	});
+	
+	mxEvent.addGestureListeners(this.maximize, funct);
+	mxEvent.addListener(this.title, 'dblclick', funct);
+};
+	
+/**
+ * Function: installMoveHandler
+ * 
+ * Installs the event listeners required for moving the window.
+ */
+mxWindow.prototype.installMoveHandler = function()
+{
+	this.title.style.cursor = 'move';
+	
+	mxEvent.addGestureListeners(this.title,
+		mxUtils.bind(this, function(evt)
+		{
+			var startX = mxEvent.getClientX(evt);
+			var startY = mxEvent.getClientY(evt);
+			var x = this.getX();
+			var y = this.getY();
+						
+			// Adds a temporary pair of listeners to intercept
+			// the gesture event in the document
+			var dragHandler = mxUtils.bind(this, function(evt)
+			{
+				var dx = mxEvent.getClientX(evt) - startX;
+				var dy = mxEvent.getClientY(evt) - startY;
+				this.setLocation(x + dx, y + dy);
+				this.fireEvent(new mxEventObject(mxEvent.MOVE, 'event', evt));
+				mxEvent.consume(evt);
+			});
+			
+			var dropHandler = mxUtils.bind(this, function(evt)
+			{
+				mxEvent.removeGestureListeners(document, null, dragHandler, dropHandler);
+				this.fireEvent(new mxEventObject(mxEvent.MOVE_END, 'event', evt));
+				mxEvent.consume(evt);
+			});
+			
+			mxEvent.addGestureListeners(document, null, dragHandler, dropHandler);
+			this.fireEvent(new mxEventObject(mxEvent.MOVE_START, 'event', evt));
+			mxEvent.consume(evt);
+		}));
+	
+	// Disables built-in pan and zoom in IE10 and later
+	if (mxClient.IS_POINTER)
+	{
+		this.title.style.touchAction = 'none';
+	}
+};
+
+/**
+ * Function: setLocation
+ * 
+ * Sets the upper, left corner of the window.
+ */
+ mxWindow.prototype.setLocation = function(x, y)
+ {
+	this.div.style.left = x + 'px';
+	this.div.style.top = y + 'px';
+ };
+
+/**
+ * Function: getX
+ *
+ * Returns the current position on the x-axis.
+ */
+mxWindow.prototype.getX = function()
+{
+	return parseInt(this.div.style.left);
+};
+
+/**
+ * Function: getY
+ *
+ * Returns the current position on the y-axis.
+ */
+mxWindow.prototype.getY = function()
+{
+	return parseInt(this.div.style.top);
+};
+
+/**
+ * Function: installCloseHandler
+ *
+ * Adds the <closeImage> as a new image node in <closeImg> and installs the
+ * <close> event.
+ */
+mxWindow.prototype.installCloseHandler = function()
+{
+	this.closeImg = document.createElement('img');
+	
+	this.closeImg.setAttribute('src', this.closeImage);
+	this.closeImg.setAttribute('title', 'Close');
+	this.closeImg.style.marginLeft = '2px';
+	this.closeImg.style.cursor = 'pointer';
+	this.closeImg.style.display = 'none';
+	
+	this.buttons.appendChild(this.closeImg);
+
+	mxEvent.addGestureListeners(this.closeImg,
+		mxUtils.bind(this, function(evt)
+		{
+			this.fireEvent(new mxEventObject(mxEvent.CLOSE, 'event', evt));
+			
+			if (this.destroyOnClose)
+			{
+				this.destroy();
+			}
+			else
+			{
+				this.setVisible(false);
+			}
+			
+			mxEvent.consume(evt);
+		}));
+};
+
+/**
+ * Function: setImage
+ * 
+ * Sets the image associated with the window.
+ * 
+ * Parameters:
+ * 
+ * image - URL of the image to be used.
+ */
+mxWindow.prototype.setImage = function(image)
+{
+	this.image = document.createElement('img');
+	this.image.setAttribute('src', image);
+	this.image.setAttribute('align', 'left');
+	this.image.style.marginRight = '4px';
+	this.image.style.marginLeft = '0px';
+	this.image.style.marginTop = '-2px';
+	
+	this.title.insertBefore(this.image, this.title.firstChild);
+};
+
+/**
+ * Function: setClosable
+ * 
+ * Sets the image associated with the window.
+ * 
+ * Parameters:
+ * 
+ * closable - Boolean specifying if the window should be closable.
+ */
+mxWindow.prototype.setClosable = function(closable)
+{
+	this.closeImg.style.display = (closable) ? '' : 'none';
+};
+
+/**
+ * Function: isVisible
+ * 
+ * Returns true if the window is visible.
+ */
+mxWindow.prototype.isVisible = function()
+{
+	if (this.div != null)
+	{
+		return this.div.style.display != 'none';
+	}
+	
+	return false;
+};
+
+/**
+ * Function: setVisible
+ *
+ * Shows or hides the window depending on the given flag.
+ * 
+ * Parameters:
+ * 
+ * visible - Boolean indicating if the window should be made visible.
+ */
+mxWindow.prototype.setVisible = function(visible)
+{
+	if (this.div != null && this.isVisible() != visible)
+	{
+		if (visible)
+		{
+			this.show();
+		}
+		else
+		{
+			this.hide();
+		}
+	}
+};
+
+/**
+ * Function: show
+ *
+ * Shows the window.
+ */
+mxWindow.prototype.show = function()
+{
+	this.div.style.display = '';
+	this.activate();
+	
+	var style = mxUtils.getCurrentStyle(this.contentWrapper);
+	
+	if (!mxClient.IS_QUIRKS && (style.overflow == 'auto' || this.resize != null))
+	{
+		this.contentWrapper.style.height = (this.div.offsetHeight -
+				this.title.offsetHeight - this.contentHeightCorrection) + 'px';
+	}
+	
+	this.fireEvent(new mxEventObject(mxEvent.SHOW));
+};
+
+/**
+ * Function: hide
+ *
+ * Hides the window.
+ */
+mxWindow.prototype.hide = function()
+{
+	this.div.style.display = 'none';
+	this.fireEvent(new mxEventObject(mxEvent.HIDE));
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the window and removes all associated resources. Fires a
+ * <destroy> event prior to destroying the window.
+ */
+mxWindow.prototype.destroy = function()
+{
+	this.fireEvent(new mxEventObject(mxEvent.DESTROY));
+	
+	if (this.div != null)
+	{
+		mxEvent.release(this.div);
+		this.div.parentNode.removeChild(this.div);
+		this.div = null;
+	}
+	
+	this.title = null;
+	this.content = null;
+	this.contentWrapper = null;
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/util/mxXmlCanvas2D.js b/airavata-kubernetes/web-console/src/assets/js/util/mxXmlCanvas2D.js
new file mode 100644
index 0000000..493ead6
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/util/mxXmlCanvas2D.js
@@ -0,0 +1,1217 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxXmlCanvas2D
+ *
+ * Base class for all canvases. The following methods make up the public
+ * interface of the canvas 2D for all painting in mxGraph:
+ * 
+ * - <save>, <restore>
+ * - <scale>, <translate>, <rotate>
+ * - <setAlpha>, <setFillAlpha>, <setStrokeAlpha>, <setFillColor>, <setGradient>,
+ *   <setStrokeColor>, <setStrokeWidth>, <setDashed>, <setDashPattern>, <setLineCap>, 
+ *   <setLineJoin>, <setMiterLimit>
+ * - <setFontColor>, <setFontBackgroundColor>, <setFontBorderColor>, <setFontSize>,
+ *   <setFontFamily>, <setFontStyle>
+ * - <setShadow>, <setShadowColor>, <setShadowAlpha>, <setShadowOffset>
+ * - <rect>, <roundrect>, <ellipse>, <image>, <text>
+ * - <begin>, <moveTo>, <lineTo>, <quadTo>, <curveTo>
+ * - <stroke>, <fill>, <fillAndStroke>
+ * 
+ * <mxAbstractCanvas2D.arcTo> is an additional method for drawing paths. This is
+ * a synthetic method, meaning that it is turned into a sequence of curves by
+ * default. Subclassers may add native support for arcs.
+ * 
+ * Constructor: mxXmlCanvas2D
+ *
+ * Constructs a new abstract canvas.
+ */
+function mxXmlCanvas2D(root)
+{
+	mxAbstractCanvas2D.call(this);
+
+	/**
+	 * Variable: root
+	 * 
+	 * Reference to the container for the SVG content.
+	 */
+	this.root = root;
+
+	// Writes default settings;
+	this.writeDefaults();
+};
+
+/**
+ * Extends mxAbstractCanvas2D
+ */
+mxUtils.extend(mxXmlCanvas2D, mxAbstractCanvas2D);
+
+/**
+ * Variable: textEnabled
+ * 
+ * Specifies if text output should be enabled. Default is true.
+ */
+mxXmlCanvas2D.prototype.textEnabled = true;
+
+/**
+ * Variable: compressed
+ * 
+ * Specifies if the output should be compressed by removing redundant calls.
+ * Default is true.
+ */
+mxXmlCanvas2D.prototype.compressed = true;
+
+/**
+ * Function: writeDefaults
+ * 
+ * Writes the rendering defaults to <root>:
+ */
+mxXmlCanvas2D.prototype.writeDefaults = function()
+{
+	var elem;
+	
+	// Writes font defaults
+	elem = this.createElement('fontfamily');
+	elem.setAttribute('family', mxConstants.DEFAULT_FONTFAMILY);
+	this.root.appendChild(elem);
+	
+	elem = this.createElement('fontsize');
+	elem.setAttribute('size', mxConstants.DEFAULT_FONTSIZE);
+	this.root.appendChild(elem);
+	
+	// Writes shadow defaults
+	elem = this.createElement('shadowcolor');
+	elem.setAttribute('color', mxConstants.SHADOWCOLOR);
+	this.root.appendChild(elem);
+	
+	elem = this.createElement('shadowalpha');
+	elem.setAttribute('alpha', mxConstants.SHADOW_OPACITY);
+	this.root.appendChild(elem);
+	
+	elem = this.createElement('shadowoffset');
+	elem.setAttribute('dx', mxConstants.SHADOW_OFFSET_X);
+	elem.setAttribute('dy', mxConstants.SHADOW_OFFSET_Y);
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: format
+ * 
+ * Returns a formatted number with 2 decimal places.
+ */
+mxXmlCanvas2D.prototype.format = function(value)
+{
+	return parseFloat(parseFloat(value).toFixed(2));
+};
+
+/**
+ * Function: createElement
+ * 
+ * Creates the given element using the owner document of <root>.
+ */
+mxXmlCanvas2D.prototype.createElement = function(name)
+{
+	return this.root.ownerDocument.createElement(name);
+};
+
+/**
+ * Function: save
+ * 
+ * Saves the drawing state.
+ */
+mxXmlCanvas2D.prototype.save = function()
+{
+	if (this.compressed)
+	{
+		mxAbstractCanvas2D.prototype.save.apply(this, arguments);
+	}
+	
+	this.root.appendChild(this.createElement('save'));
+};
+
+/**
+ * Function: restore
+ * 
+ * Restores the drawing state.
+ */
+mxXmlCanvas2D.prototype.restore = function()
+{
+	if (this.compressed)
+	{
+		mxAbstractCanvas2D.prototype.restore.apply(this, arguments);
+	}
+	
+	this.root.appendChild(this.createElement('restore'));
+};
+
+/**
+ * Function: scale
+ * 
+ * Scales the output.
+ * 
+ * Parameters:
+ * 
+ * scale - Number that represents the scale where 1 is equal to 100%.
+ */
+mxXmlCanvas2D.prototype.scale = function(value)
+{
+        var elem = this.createElement('scale');
+        elem.setAttribute('scale', value);
+        this.root.appendChild(elem);
+};
+
+/**
+ * Function: translate
+ * 
+ * Translates the output.
+ * 
+ * Parameters:
+ * 
+ * dx - Number that specifies the horizontal translation.
+ * dy - Number that specifies the vertical translation.
+ */
+mxXmlCanvas2D.prototype.translate = function(dx, dy)
+{
+	var elem = this.createElement('translate');
+	elem.setAttribute('dx', this.format(dx));
+	elem.setAttribute('dy', this.format(dy));
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: rotate
+ * 
+ * Rotates and/or flips the output around a given center. (Note: Due to
+ * limitations in VML, the rotation cannot be concatenated.)
+ * 
+ * Parameters:
+ * 
+ * theta - Number that represents the angle of the rotation (in degrees).
+ * flipH - Boolean indicating if the output should be flipped horizontally.
+ * flipV - Boolean indicating if the output should be flipped vertically.
+ * cx - Number that represents the x-coordinate of the rotation center.
+ * cy - Number that represents the y-coordinate of the rotation center.
+ */
+mxXmlCanvas2D.prototype.rotate = function(theta, flipH, flipV, cx, cy)
+{
+	var elem = this.createElement('rotate');
+	
+	if (theta != 0 || flipH || flipV)
+	{
+		elem.setAttribute('theta', this.format(theta));
+		elem.setAttribute('flipH', (flipH) ? '1' : '0');
+		elem.setAttribute('flipV', (flipV) ? '1' : '0');
+		elem.setAttribute('cx', this.format(cx));
+		elem.setAttribute('cy', this.format(cy));
+		this.root.appendChild(elem);
+	}
+};
+
+/**
+ * Function: setAlpha
+ * 
+ * Sets the current alpha.
+ * 
+ * Parameters:
+ * 
+ * value - Number that represents the new alpha. Possible values are between
+ * 1 (opaque) and 0 (transparent).
+ */
+mxXmlCanvas2D.prototype.setAlpha = function(value)
+{
+	if (this.compressed)
+	{
+		if (this.state.alpha == value)
+		{
+			return;
+		}
+		
+		mxAbstractCanvas2D.prototype.setAlpha.apply(this, arguments);
+	}
+	
+	var elem = this.createElement('alpha');
+	elem.setAttribute('alpha', this.format(value));
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: setFillAlpha
+ * 
+ * Sets the current fill alpha.
+ * 
+ * Parameters:
+ * 
+ * value - Number that represents the new fill alpha. Possible values are between
+ * 1 (opaque) and 0 (transparent).
+ */
+mxXmlCanvas2D.prototype.setFillAlpha = function(value)
+{
+	if (this.compressed)
+	{
+		if (this.state.fillAlpha == value)
+		{
+			return;
+		}
+		
+		mxAbstractCanvas2D.prototype.setFillAlpha.apply(this, arguments);
+	}
+	
+	var elem = this.createElement('fillalpha');
+	elem.setAttribute('alpha', this.format(value));
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: setStrokeAlpha
+ * 
+ * Sets the current stroke alpha.
+ * 
+ * Parameters:
+ * 
+ * value - Number that represents the new stroke alpha. Possible values are between
+ * 1 (opaque) and 0 (transparent).
+ */
+mxXmlCanvas2D.prototype.setStrokeAlpha = function(value)
+{
+	if (this.compressed)
+	{
+		if (this.state.strokeAlpha == value)
+		{
+			return;
+		}
+		
+		mxAbstractCanvas2D.prototype.setStrokeAlpha.apply(this, arguments);
+	}
+	
+	var elem = this.createElement('strokealpha');
+	elem.setAttribute('alpha', this.format(value));
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: setFillColor
+ * 
+ * Sets the current fill color.
+ * 
+ * Parameters:
+ * 
+ * value - Hexadecimal representation of the color or 'none'.
+ */
+mxXmlCanvas2D.prototype.setFillColor = function(value)
+{
+	if (value == mxConstants.NONE)
+	{
+		value = null;
+	}
+	
+	if (this.compressed)
+	{
+		if (this.state.fillColor == value)
+		{
+			return;
+		}
+		
+		mxAbstractCanvas2D.prototype.setFillColor.apply(this, arguments);
+	}
+	
+	var elem = this.createElement('fillcolor');
+	elem.setAttribute('color', (value != null) ? value : mxConstants.NONE);
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: setGradient
+ * 
+ * Sets the gradient. Note that the coordinates may be ignored by some implementations.
+ * 
+ * Parameters:
+ * 
+ * color1 - Hexadecimal representation of the start color.
+ * color2 - Hexadecimal representation of the end color.
+ * x - X-coordinate of the gradient region.
+ * y - y-coordinate of the gradient region.
+ * w - Width of the gradient region.
+ * h - Height of the gradient region.
+ * direction - One of <mxConstants.DIRECTION_NORTH>, <mxConstants.DIRECTION_EAST>,
+ * <mxConstants.DIRECTION_SOUTH> or <mxConstants.DIRECTION_WEST>.
+ * alpha1 - Optional alpha of the start color. Default is 1. Possible values
+ * are between 1 (opaque) and 0 (transparent).
+ * alpha2 - Optional alpha of the end color. Default is 1. Possible values
+ * are between 1 (opaque) and 0 (transparent).
+ */
+mxXmlCanvas2D.prototype.setGradient = function(color1, color2, x, y, w, h, direction, alpha1, alpha2)
+{
+	if (color1 != null && color2 != null)
+	{
+		mxAbstractCanvas2D.prototype.setGradient.apply(this, arguments);
+		
+		var elem = this.createElement('gradient');
+		elem.setAttribute('c1', color1);
+		elem.setAttribute('c2', color2);
+		elem.setAttribute('x', this.format(x));
+		elem.setAttribute('y', this.format(y));
+		elem.setAttribute('w', this.format(w));
+		elem.setAttribute('h', this.format(h));
+		
+		// Default direction is south
+		if (direction != null)
+		{
+			elem.setAttribute('direction', direction);
+		}
+		
+		if (alpha1 != null)
+		{
+			elem.setAttribute('alpha1', alpha1);
+		}
+		
+		if (alpha2 != null)
+		{
+			elem.setAttribute('alpha2', alpha2);
+		}
+		
+		this.root.appendChild(elem);
+	}
+};
+
+/**
+ * Function: setStrokeColor
+ * 
+ * Sets the current stroke color.
+ * 
+ * Parameters:
+ * 
+ * value - Hexadecimal representation of the color or 'none'.
+ */
+mxXmlCanvas2D.prototype.setStrokeColor = function(value)
+{
+	if (value == mxConstants.NONE)
+	{
+		value = null;
+	}
+	
+	if (this.compressed)
+	{
+		if (this.state.strokeColor == value)
+		{
+			return;
+		}
+		
+		mxAbstractCanvas2D.prototype.setStrokeColor.apply(this, arguments);
+	}
+	
+	var elem = this.createElement('strokecolor');
+	elem.setAttribute('color', (value != null) ? value : mxConstants.NONE);
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: setStrokeWidth
+ * 
+ * Sets the current stroke width.
+ * 
+ * Parameters:
+ * 
+ * value - Numeric representation of the stroke width.
+ */
+mxXmlCanvas2D.prototype.setStrokeWidth = function(value)
+{
+	if (this.compressed)
+	{
+		if (this.state.strokeWidth == value)
+		{
+			return;
+		}
+		
+		mxAbstractCanvas2D.prototype.setStrokeWidth.apply(this, arguments);
+	}
+	
+	var elem = this.createElement('strokewidth');
+	elem.setAttribute('width', this.format(value));
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: setDashed
+ * 
+ * Enables or disables dashed lines.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean that specifies if dashed lines should be enabled.
+ * value - Boolean that specifies if the stroke width should be ignored
+ * for the dash pattern. Default is false.
+ */
+mxXmlCanvas2D.prototype.setDashed = function(value, fixDash)
+{
+	if (this.compressed)
+	{
+		if (this.state.dashed == value)
+		{
+			return;
+		}
+		
+		mxAbstractCanvas2D.prototype.setDashed.apply(this, arguments);
+	}
+	
+	var elem = this.createElement('dashed');
+	elem.setAttribute('dashed', (value) ? '1' : '0');
+	
+	if (fixDash != null)
+	{
+		elem.setAttribute('fixDash', (fixDash) ? '1' : '0');
+	}
+	
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: setDashPattern
+ * 
+ * Sets the current dash pattern. Default is '3 3'.
+ * 
+ * Parameters:
+ * 
+ * value - String that represents the dash pattern, which is a sequence of
+ * numbers defining the length of the dashes and the length of the spaces
+ * between the dashes. The lengths are relative to the line width - a length
+ * of 1 is equals to the line width.
+ */
+mxXmlCanvas2D.prototype.setDashPattern = function(value)
+{
+	if (this.compressed)
+	{
+		if (this.state.dashPattern == value)
+		{
+			return;
+		}
+		
+		mxAbstractCanvas2D.prototype.setDashPattern.apply(this, arguments);
+	}
+	
+	var elem = this.createElement('dashpattern');
+	elem.setAttribute('pattern', value);
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: setLineCap
+ * 
+ * Sets the line cap. Default is 'flat' which corresponds to 'butt' in SVG.
+ * 
+ * Parameters:
+ * 
+ * value - String that represents the line cap. Possible values are flat, round
+ * and square.
+ */
+mxXmlCanvas2D.prototype.setLineCap = function(value)
+{
+	if (this.compressed)
+	{
+		if (this.state.lineCap == value)
+		{
+			return;
+		}
+		
+		mxAbstractCanvas2D.prototype.setLineCap.apply(this, arguments);
+	}
+	
+	var elem = this.createElement('linecap');
+	elem.setAttribute('cap', value);
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: setLineJoin
+ * 
+ * Sets the line join. Default is 'miter'.
+ * 
+ * Parameters:
+ * 
+ * value - String that represents the line join. Possible values are miter,
+ * round and bevel.
+ */
+mxXmlCanvas2D.prototype.setLineJoin = function(value)
+{
+	if (this.compressed)
+	{
+		if (this.state.lineJoin == value)
+		{
+			return;
+		}
+		
+		mxAbstractCanvas2D.prototype.setLineJoin.apply(this, arguments);
+	}
+	
+	var elem = this.createElement('linejoin');
+	elem.setAttribute('join', value);
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: setMiterLimit
+ * 
+ * Sets the miter limit. Default is 10.
+ * 
+ * Parameters:
+ * 
+ * value - Number that represents the miter limit.
+ */
+mxXmlCanvas2D.prototype.setMiterLimit = function(value)
+{
+	if (this.compressed)
+	{
+		if (this.state.miterLimit == value)
+		{
+			return;
+		}
+		
+		mxAbstractCanvas2D.prototype.setMiterLimit.apply(this, arguments);
+	}
+	
+	var elem = this.createElement('miterlimit');
+	elem.setAttribute('limit', value);
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: setFontColor
+ * 
+ * Sets the current font color. Default is '#000000'.
+ * 
+ * Parameters:
+ * 
+ * value - Hexadecimal representation of the color or 'none'.
+ */
+mxXmlCanvas2D.prototype.setFontColor = function(value)
+{
+	if (this.textEnabled)
+	{
+		if (value == mxConstants.NONE)
+		{
+			value = null;
+		}
+		
+		if (this.compressed)
+		{
+			if (this.state.fontColor == value)
+			{
+				return;
+			}
+			
+			mxAbstractCanvas2D.prototype.setFontColor.apply(this, arguments);
+		}
+		
+		var elem = this.createElement('fontcolor');
+		elem.setAttribute('color', (value != null) ? value : mxConstants.NONE);
+		this.root.appendChild(elem);
+	}
+};
+
+/**
+ * Function: setFontBackgroundColor
+ * 
+ * Sets the current font background color.
+ * 
+ * Parameters:
+ * 
+ * value - Hexadecimal representation of the color or 'none'.
+ */
+mxXmlCanvas2D.prototype.setFontBackgroundColor = function(value)
+{
+	if (this.textEnabled)
+	{
+		if (value == mxConstants.NONE)
+		{
+			value = null;
+		}
+		
+		if (this.compressed)
+		{
+			if (this.state.fontBackgroundColor == value)
+			{
+				return;
+			}
+			
+			mxAbstractCanvas2D.prototype.setFontBackgroundColor.apply(this, arguments);
+		}
+
+		var elem = this.createElement('fontbackgroundcolor');
+		elem.setAttribute('color', (value != null) ? value : mxConstants.NONE);
+		this.root.appendChild(elem);
+	}
+};
+
+/**
+ * Function: setFontBorderColor
+ * 
+ * Sets the current font border color.
+ * 
+ * Parameters:
+ * 
+ * value - Hexadecimal representation of the color or 'none'.
+ */
+mxXmlCanvas2D.prototype.setFontBorderColor = function(value)
+{
+	if (this.textEnabled)
+	{
+		if (value == mxConstants.NONE)
+		{
+			value = null;
+		}
+		
+		if (this.compressed)
+		{
+			if (this.state.fontBorderColor == value)
+			{
+				return;
+			}
+			
+			mxAbstractCanvas2D.prototype.setFontBorderColor.apply(this, arguments);
+		}
+		
+		var elem = this.createElement('fontbordercolor');
+		elem.setAttribute('color', (value != null) ? value : mxConstants.NONE);
+		this.root.appendChild(elem);
+	}
+};
+
+/**
+ * Function: setFontSize
+ * 
+ * Sets the current font size. Default is <mxConstants.DEFAULT_FONTSIZE>.
+ * 
+ * Parameters:
+ * 
+ * value - Numeric representation of the font size.
+ */
+mxXmlCanvas2D.prototype.setFontSize = function(value)
+{
+	if (this.textEnabled)
+	{
+		if (this.compressed)
+		{
+			if (this.state.fontSize == value)
+			{
+				return;
+			}
+			
+			mxAbstractCanvas2D.prototype.setFontSize.apply(this, arguments);
+		}
+		
+		var elem = this.createElement('fontsize');
+		elem.setAttribute('size', value);
+		this.root.appendChild(elem);
+	}
+};
+
+/**
+ * Function: setFontFamily
+ * 
+ * Sets the current font family. Default is <mxConstants.DEFAULT_FONTFAMILY>.
+ * 
+ * Parameters:
+ * 
+ * value - String representation of the font family. This handles the same
+ * values as the CSS font-family property.
+ */
+mxXmlCanvas2D.prototype.setFontFamily = function(value)
+{
+	if (this.textEnabled)
+	{
+		if (this.compressed)
+		{
+			if (this.state.fontFamily == value)
+			{
+				return;
+			}
+			
+			mxAbstractCanvas2D.prototype.setFontFamily.apply(this, arguments);
+		}
+		
+		var elem = this.createElement('fontfamily');
+		elem.setAttribute('family', value);
+		this.root.appendChild(elem);
+	}
+};
+
+/**
+ * Function: setFontStyle
+ * 
+ * Sets the current font style.
+ * 
+ * Parameters:
+ * 
+ * value - Numeric representation of the font family. This is the sum of the
+ * font styles from <mxConstants>.
+ */
+mxXmlCanvas2D.prototype.setFontStyle = function(value)
+{
+	if (this.textEnabled)
+	{
+		if (value == null)
+		{
+			value = 0;
+		}
+		
+		if (this.compressed)
+		{
+			if (this.state.fontStyle == value)
+			{
+				return;
+			}
+			
+			mxAbstractCanvas2D.prototype.setFontStyle.apply(this, arguments);
+		}
+		
+		var elem = this.createElement('fontstyle');
+		elem.setAttribute('style', value);
+		this.root.appendChild(elem);
+	}
+};
+
+/**
+ * Function: setShadow
+ * 
+ * Enables or disables shadows.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean that specifies if shadows should be enabled.
+ */
+mxXmlCanvas2D.prototype.setShadow = function(value)
+{
+	if (this.compressed)
+	{
+		if (this.state.shadow == value)
+		{
+			return;
+		}
+		
+		mxAbstractCanvas2D.prototype.setShadow.apply(this, arguments);
+	}
+	
+	var elem = this.createElement('shadow');
+	elem.setAttribute('enabled', (value) ? '1' : '0');
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: setShadowColor
+ * 
+ * Sets the current shadow color. Default is <mxConstants.SHADOWCOLOR>.
+ * 
+ * Parameters:
+ * 
+ * value - Hexadecimal representation of the color or 'none'.
+ */
+mxXmlCanvas2D.prototype.setShadowColor = function(value)
+{
+	if (this.compressed)
+	{
+		if (value == mxConstants.NONE)
+		{
+			value = null;
+		}
+		
+		if (this.state.shadowColor == value)
+		{
+			return;
+		}
+		
+		mxAbstractCanvas2D.prototype.setShadowColor.apply(this, arguments);
+	}
+	
+	var elem = this.createElement('shadowcolor');
+	elem.setAttribute('color', (value != null) ? value : mxConstants.NONE);
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: setShadowAlpha
+ * 
+ * Sets the current shadows alpha. Default is <mxConstants.SHADOW_OPACITY>.
+ * 
+ * Parameters:
+ * 
+ * value - Number that represents the new alpha. Possible values are between
+ * 1 (opaque) and 0 (transparent).
+ */
+mxXmlCanvas2D.prototype.setShadowAlpha = function(value)
+{
+	if (this.compressed)
+	{
+		if (this.state.shadowAlpha == value)
+		{
+			return;
+		}
+		
+		mxAbstractCanvas2D.prototype.setShadowAlpha.apply(this, arguments);
+	}
+	
+	var elem = this.createElement('shadowalpha');
+	elem.setAttribute('alpha', value);
+	this.root.appendChild(elem);
+	
+};
+
+/**
+ * Function: setShadowOffset
+ * 
+ * Sets the current shadow offset.
+ * 
+ * Parameters:
+ * 
+ * dx - Number that represents the horizontal offset of the shadow.
+ * dy - Number that represents the vertical offset of the shadow.
+ */
+mxXmlCanvas2D.prototype.setShadowOffset = function(dx, dy)
+{
+	if (this.compressed)
+	{
+		if (this.state.shadowDx == dx && this.state.shadowDy == dy)
+		{
+			return;
+		}
+		
+		mxAbstractCanvas2D.prototype.setShadowOffset.apply(this, arguments);
+	}
+	
+	var elem = this.createElement('shadowoffset');
+	elem.setAttribute('dx', dx);
+	elem.setAttribute('dy', dy);
+	this.root.appendChild(elem);
+	
+};
+
+/**
+ * Function: rect
+ * 
+ * Puts a rectangle into the drawing buffer.
+ * 
+ * Parameters:
+ * 
+ * x - Number that represents the x-coordinate of the rectangle.
+ * y - Number that represents the y-coordinate of the rectangle.
+ * w - Number that represents the width of the rectangle.
+ * h - Number that represents the height of the rectangle.
+ */
+mxXmlCanvas2D.prototype.rect = function(x, y, w, h)
+{
+	var elem = this.createElement('rect');
+	elem.setAttribute('x', this.format(x));
+	elem.setAttribute('y', this.format(y));
+	elem.setAttribute('w', this.format(w));
+	elem.setAttribute('h', this.format(h));
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: roundrect
+ * 
+ * Puts a rounded rectangle into the drawing buffer.
+ * 
+ * Parameters:
+ * 
+ * x - Number that represents the x-coordinate of the rectangle.
+ * y - Number that represents the y-coordinate of the rectangle.
+ * w - Number that represents the width of the rectangle.
+ * h - Number that represents the height of the rectangle.
+ * dx - Number that represents the horizontal rounding.
+ * dy - Number that represents the vertical rounding.
+ */
+mxXmlCanvas2D.prototype.roundrect = function(x, y, w, h, dx, dy)
+{
+	var elem = this.createElement('roundrect');
+	elem.setAttribute('x', this.format(x));
+	elem.setAttribute('y', this.format(y));
+	elem.setAttribute('w', this.format(w));
+	elem.setAttribute('h', this.format(h));
+	elem.setAttribute('dx', this.format(dx));
+	elem.setAttribute('dy', this.format(dy));
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: ellipse
+ * 
+ * Puts an ellipse into the drawing buffer.
+ * 
+ * Parameters:
+ * 
+ * x - Number that represents the x-coordinate of the ellipse.
+ * y - Number that represents the y-coordinate of the ellipse.
+ * w - Number that represents the width of the ellipse.
+ * h - Number that represents the height of the ellipse.
+ */
+mxXmlCanvas2D.prototype.ellipse = function(x, y, w, h)
+{
+	var elem = this.createElement('ellipse');
+	elem.setAttribute('x', this.format(x));
+	elem.setAttribute('y', this.format(y));
+	elem.setAttribute('w', this.format(w));
+	elem.setAttribute('h', this.format(h));
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: image
+ * 
+ * Paints an image.
+ * 
+ * Parameters:
+ * 
+ * x - Number that represents the x-coordinate of the image.
+ * y - Number that represents the y-coordinate of the image.
+ * w - Number that represents the width of the image.
+ * h - Number that represents the height of the image.
+ * src - String that specifies the URL of the image.
+ * aspect - Boolean indicating if the aspect of the image should be preserved.
+ * flipH - Boolean indicating if the image should be flipped horizontally.
+ * flipV - Boolean indicating if the image should be flipped vertically.
+ */
+mxXmlCanvas2D.prototype.image = function(x, y, w, h, src, aspect, flipH, flipV)
+{
+	src = this.converter.convert(src);
+	
+	// LATER: Add option for embedding images as base64.
+	var elem = this.createElement('image');
+	elem.setAttribute('x', this.format(x));
+	elem.setAttribute('y', this.format(y));
+	elem.setAttribute('w', this.format(w));
+	elem.setAttribute('h', this.format(h));
+	elem.setAttribute('src', src);
+	elem.setAttribute('aspect', (aspect) ? '1' : '0');
+	elem.setAttribute('flipH', (flipH) ? '1' : '0');
+	elem.setAttribute('flipV', (flipV) ? '1' : '0');
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: begin
+ * 
+ * Starts a new path and puts it into the drawing buffer.
+ */
+mxXmlCanvas2D.prototype.begin = function()
+{
+	this.root.appendChild(this.createElement('begin'));
+	this.lastX = 0;
+	this.lastY = 0;
+};
+
+/**
+ * Function: moveTo
+ * 
+ * Moves the current path the given point.
+ * 
+ * Parameters:
+ * 
+ * x - Number that represents the x-coordinate of the point.
+ * y - Number that represents the y-coordinate of the point.
+ */
+mxXmlCanvas2D.prototype.moveTo = function(x, y)
+{
+	var elem = this.createElement('move');
+	elem.setAttribute('x', this.format(x));
+	elem.setAttribute('y', this.format(y));
+	this.root.appendChild(elem);
+	this.lastX = x;
+	this.lastY = y;
+};
+
+/**
+ * Function: lineTo
+ * 
+ * Draws a line to the given coordinates.
+ * 
+ * Parameters:
+ * 
+ * x - Number that represents the x-coordinate of the endpoint.
+ * y - Number that represents the y-coordinate of the endpoint.
+ */
+mxXmlCanvas2D.prototype.lineTo = function(x, y)
+{
+	var elem = this.createElement('line');
+	elem.setAttribute('x', this.format(x));
+	elem.setAttribute('y', this.format(y));
+	this.root.appendChild(elem);
+	this.lastX = x;
+	this.lastY = y;
+};
+
+/**
+ * Function: quadTo
+ * 
+ * Adds a quadratic curve to the current path.
+ * 
+ * Parameters:
+ * 
+ * x1 - Number that represents the x-coordinate of the control point.
+ * y1 - Number that represents the y-coordinate of the control point.
+ * x2 - Number that represents the x-coordinate of the endpoint.
+ * y2 - Number that represents the y-coordinate of the endpoint.
+ */
+mxXmlCanvas2D.prototype.quadTo = function(x1, y1, x2, y2)
+{
+	var elem = this.createElement('quad');
+	elem.setAttribute('x1', this.format(x1));
+	elem.setAttribute('y1', this.format(y1));
+	elem.setAttribute('x2', this.format(x2));
+	elem.setAttribute('y2', this.format(y2));
+	this.root.appendChild(elem);
+	this.lastX = x2;
+	this.lastY = y2;
+};
+
+/**
+ * Function: curveTo
+ * 
+ * Adds a bezier curve to the current path.
+ * 
+ * Parameters:
+ * 
+ * x1 - Number that represents the x-coordinate of the first control point.
+ * y1 - Number that represents the y-coordinate of the first control point.
+ * x2 - Number that represents the x-coordinate of the second control point.
+ * y2 - Number that represents the y-coordinate of the second control point.
+ * x3 - Number that represents the x-coordinate of the endpoint.
+ * y3 - Number that represents the y-coordinate of the endpoint.
+ */
+mxXmlCanvas2D.prototype.curveTo = function(x1, y1, x2, y2, x3, y3)
+{
+	var elem = this.createElement('curve');
+	elem.setAttribute('x1', this.format(x1));
+	elem.setAttribute('y1', this.format(y1));
+	elem.setAttribute('x2', this.format(x2));
+	elem.setAttribute('y2', this.format(y2));
+	elem.setAttribute('x3', this.format(x3));
+	elem.setAttribute('y3', this.format(y3));
+	this.root.appendChild(elem);
+	this.lastX = x3;
+	this.lastY = y3;
+};
+
+/**
+ * Function: close
+ * 
+ * Closes the current path.
+ */
+mxXmlCanvas2D.prototype.close = function()
+{
+	this.root.appendChild(this.createElement('close'));
+};
+
+/**
+ * Function: text
+ * 
+ * Paints the given text. Possible values for format are empty string for
+ * plain text and html for HTML markup. Background and border color as well
+ * as clipping is not available in plain text labels for VML. HTML labels
+ * are not available as part of shapes with no foreignObject support in SVG
+ * (eg. IE9, IE10).
+ * 
+ * Parameters:
+ * 
+ * x - Number that represents the x-coordinate of the text.
+ * y - Number that represents the y-coordinate of the text.
+ * w - Number that represents the available width for the text or 0 for automatic width.
+ * h - Number that represents the available height for the text or 0 for automatic height.
+ * str - String that specifies the text to be painted.
+ * align - String that represents the horizontal alignment.
+ * valign - String that represents the vertical alignment.
+ * wrap - Boolean that specifies if word-wrapping is enabled. Requires w > 0.
+ * format - Empty string for plain text or 'html' for HTML markup.
+ * overflow - Specifies the overflow behaviour of the label. Requires w > 0 and/or h > 0.
+ * clip - Boolean that specifies if the label should be clipped. Requires w > 0 and/or h > 0.
+ * rotation - Number that specifies the angle of the rotation around the anchor point of the text.
+ * dir - Optional string that specifies the text direction. Possible values are rtl and lrt.
+ */
+mxXmlCanvas2D.prototype.text = function(x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation, dir)
+{
+	if (this.textEnabled && str != null)
+	{
+		if (mxUtils.isNode(str))
+		{
+			str = mxUtils.getOuterHtml(str);
+		}
+		
+		var elem = this.createElement('text');
+		elem.setAttribute('x', this.format(x));
+		elem.setAttribute('y', this.format(y));
+		elem.setAttribute('w', this.format(w));
+		elem.setAttribute('h', this.format(h));
+		elem.setAttribute('str', str);
+		
+		if (align != null)
+		{
+			elem.setAttribute('align', align);
+		}
+		
+		if (valign != null)
+		{
+			elem.setAttribute('valign', valign);
+		}
+		
+		elem.setAttribute('wrap', (wrap) ? '1' : '0');
+		
+		if (format == null)
+		{
+			format = '';
+		}
+		
+		elem.setAttribute('format', format);
+		
+		if (overflow != null)
+		{
+			elem.setAttribute('overflow', overflow);
+		}
+		
+		if (clip != null)
+		{
+			elem.setAttribute('clip', (clip) ? '1' : '0');
+		}
+		
+		if (rotation != null)
+		{
+			elem.setAttribute('rotation', rotation);
+		}
+		
+		if (dir != null)
+		{
+			elem.setAttribute('dir', dir);
+		}
+		
+		this.root.appendChild(elem);
+	}
+};
+
+/**
+ * Function: stroke
+ * 
+ * Paints the outline of the current drawing buffer.
+ */
+mxXmlCanvas2D.prototype.stroke = function()
+{
+	this.root.appendChild(this.createElement('stroke'));
+};
+
+/**
+ * Function: fill
+ * 
+ * Fills the current drawing buffer.
+ */
+mxXmlCanvas2D.prototype.fill = function()
+{
+	this.root.appendChild(this.createElement('fill'));
+};
+
+/**
+ * Function: fillAndStroke
+ * 
+ * Fills the current drawing buffer and its outline.
+ */
+mxXmlCanvas2D.prototype.fillAndStroke = function()
+{
+	this.root.appendChild(this.createElement('fillstroke'));
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/util/mxXmlRequest.js b/airavata-kubernetes/web-console/src/assets/js/util/mxXmlRequest.js
new file mode 100644
index 0000000..4dccf1f
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/util/mxXmlRequest.js
@@ -0,0 +1,463 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxXmlRequest
+ * 
+ * XML HTTP request wrapper. See also: <mxUtils.get>, <mxUtils.post> and
+ * <mxUtils.load>. This class provides a cross-browser abstraction for Ajax
+ * requests.
+ * 
+ * Encoding:
+ * 
+ * For encoding parameter values, the built-in encodeURIComponent JavaScript
+ * method must be used. For automatic encoding of post data in <mxEditor> the
+ * <mxEditor.escapePostData> switch can be set to true (default). The encoding
+ * will be carried out using the conte type of the page. That is, the page
+ * containting the editor should contain a meta tag in the header, eg.
+ * <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ * 
+ * Example:
+ * 
+ * (code)
+ * var onload = function(req)
+ * {
+ *   mxUtils.alert(req.getDocumentElement());
+ * }
+ * 
+ * var onerror = function(req)
+ * {
+ *   mxUtils.alert('Error');
+ * }
+ * new mxXmlRequest(url, 'key=value').send(onload, onerror);
+ * (end)
+ * 
+ * Sends an asynchronous POST request to the specified URL.
+ * 
+ * Example:
+ * 
+ * (code)
+ * var req = new mxXmlRequest(url, 'key=value', 'POST', false);
+ * req.send();
+ * mxUtils.alert(req.getDocumentElement());
+ * (end)
+ * 
+ * Sends a synchronous POST request to the specified URL.
+ * 
+ * Example:
+ * 
+ * (code)
+ * var encoder = new mxCodec();
+ * var result = encoder.encode(graph.getModel());
+ * var xml = encodeURIComponent(mxUtils.getXml(result));
+ * new mxXmlRequest(url, 'xml='+xml).send();
+ * (end)
+ * 
+ * Sends an encoded graph model to the specified URL using xml as the
+ * parameter name. The parameter can then be retrieved in C# as follows:
+ * 
+ * (code)
+ * string xml = HttpUtility.UrlDecode(context.Request.Params["xml"]);
+ * (end)
+ * 
+ * Or in Java as follows:
+ * 
+ * (code)
+ * String xml = URLDecoder.decode(request.getParameter("xml"), "UTF-8").replace("\n", "&#xa;");
+ * (end)
+ *
+ * Note that the linefeeds should only be replaced if the XML is
+ * processed in Java, for example when creating an image.
+ * 
+ * Constructor: mxXmlRequest
+ * 
+ * Constructs an XML HTTP request.
+ * 
+ * Parameters:
+ * 
+ * url - Target URL of the request.
+ * params - Form encoded parameters to send with a POST request.
+ * method - String that specifies the request method. Possible values are
+ * POST and GET. Default is POST.
+ * async - Boolean specifying if an asynchronous request should be used.
+ * Default is true.
+ * username - String specifying the username to be used for the request.
+ * password - String specifying the password to be used for the request.
+ */
+function mxXmlRequest(url, params, method, async, username, password)
+{
+	this.url = url;
+	this.params = params;
+	this.method = method || 'POST';
+	this.async = (async != null) ? async : true;
+	this.username = username;
+	this.password = password;
+};
+
+/**
+ * Variable: url
+ * 
+ * Holds the target URL of the request.
+ */
+mxXmlRequest.prototype.url = null;
+
+/**
+ * Variable: params
+ * 
+ * Holds the form encoded data for the POST request.
+ */
+mxXmlRequest.prototype.params = null;
+
+/**
+ * Variable: method
+ * 
+ * Specifies the request method. Possible values are POST and GET. Default
+ * is POST.
+ */
+mxXmlRequest.prototype.method = null;
+
+/**
+ * Variable: async
+ * 
+ * Boolean indicating if the request is asynchronous.
+ */
+mxXmlRequest.prototype.async = null;
+
+/**
+ * Variable: binary
+ * 
+ * Boolean indicating if the request is binary. This option is ignored in IE.
+ * In all other browsers the requested mime type is set to
+ * text/plain; charset=x-user-defined. Default is false.
+ */
+mxXmlRequest.prototype.binary = false;
+
+/**
+ * Variable: withCredentials
+ * 
+ * Specifies if withCredentials should be used in HTML5-compliant browsers. Default is
+ * false.
+ */
+mxXmlRequest.prototype.withCredentials = false;
+
+/**
+ * Variable: username
+ * 
+ * Specifies the username to be used for authentication.
+ */
+mxXmlRequest.prototype.username = null;
+
+/**
+ * Variable: password
+ * 
+ * Specifies the password to be used for authentication.
+ */
+mxXmlRequest.prototype.password = null;
+
+/**
+ * Variable: request
+ * 
+ * Holds the inner, browser-specific request object.
+ */
+mxXmlRequest.prototype.request = null;
+
+/**
+ * Variable: decodeSimulateValues
+ * 
+ * Specifies if request values should be decoded as URIs before setting the
+ * textarea value in <simulate>. Defaults to false for backwards compatibility,
+ * to avoid another decode on the server this should be set to true.
+ */
+mxXmlRequest.prototype.decodeSimulateValues = false;
+
+/**
+ * Function: isBinary
+ * 
+ * Returns <binary>.
+ */
+mxXmlRequest.prototype.isBinary = function()
+{
+	return this.binary;
+};
+
+/**
+ * Function: setBinary
+ * 
+ * Sets <binary>.
+ */
+mxXmlRequest.prototype.setBinary = function(value)
+{
+	this.binary = value;
+};
+
+/**
+ * Function: getText
+ * 
+ * Returns the response as a string.
+ */
+mxXmlRequest.prototype.getText = function()
+{
+	return this.request.responseText;
+};
+
+/**
+ * Function: isReady
+ * 
+ * Returns true if the response is ready.
+ */
+mxXmlRequest.prototype.isReady = function()
+{
+	return this.request.readyState == 4;
+};
+
+/**
+ * Function: getDocumentElement
+ * 
+ * Returns the document element of the response XML document.
+ */
+mxXmlRequest.prototype.getDocumentElement = function()
+{
+	var doc = this.getXml();
+	
+	if (doc != null)
+	{
+		return doc.documentElement;
+	}
+	
+	return null;
+};
+
+/**
+ * Function: getXml
+ * 
+ * Returns the response as an XML document. Use <getDocumentElement> to get
+ * the document element of the XML document.
+ */
+mxXmlRequest.prototype.getXml = function()
+{
+	var xml = this.request.responseXML;
+	
+	// Handles missing response headers in IE, the first condition handles
+	// the case where responseXML is there, but using its nodes leads to
+	// type errors in the mxCellCodec when putting the nodes into a new
+	// document. This happens in IE9 standards mode and with XML user
+	// objects only, as they are used directly as values in cells.
+	if (document.documentMode >= 9 || xml == null || xml.documentElement == null)
+	{
+		xml = mxUtils.parseXml(this.request.responseText);
+	}
+	
+	return xml;
+};
+
+/**
+ * Function: getText
+ * 
+ * Returns the response as a string.
+ */
+mxXmlRequest.prototype.getText = function()
+{
+	return this.request.responseText;
+};
+
+/**
+ * Function: getStatus
+ * 
+ * Returns the status as a number, eg. 404 for "Not found" or 200 for "OK".
+ * Note: The NS_ERROR_NOT_AVAILABLE for invalid responses cannot be cought.
+ */
+mxXmlRequest.prototype.getStatus = function()
+{
+	return this.request.status;
+};
+
+/**
+ * Function: create
+ * 
+ * Creates and returns the inner <request> object.
+ */
+mxXmlRequest.prototype.create = function()
+{
+	if (window.XMLHttpRequest)
+	{
+		return function()
+		{
+			var req = new XMLHttpRequest();
+			
+			// TODO: Check for overrideMimeType required here?
+			if (this.isBinary() && req.overrideMimeType)
+			{
+				req.overrideMimeType('text/plain; charset=x-user-defined');
+			}
+
+			return req;
+		};
+	}
+	else if (typeof(ActiveXObject) != 'undefined')
+	{
+		return function()
+		{
+			// TODO: Implement binary option
+			return new ActiveXObject('Microsoft.XMLHTTP');
+		};
+	}
+}();
+
+/**
+ * Function: send
+ * 
+ * Send the <request> to the target URL using the specified functions to
+ * process the response asychronously.
+ * 
+ * Parameters:
+ * 
+ * onload - Function to be invoked if a successful response was received.
+ * onerror - Function to be called on any error.
+ * timeout - Optional timeout in ms before calling ontimeout.
+ * ontimeout - Optional function to execute on timeout.
+ */
+mxXmlRequest.prototype.send = function(onload, onerror, timeout, ontimeout)
+{
+	this.request = this.create();
+	
+	if (this.request != null)
+	{
+		if (onload != null)
+		{
+			this.request.onreadystatechange = mxUtils.bind(this, function()
+			{
+				if (this.isReady())
+				{
+					onload(this);
+					this.onreadystatechaange = null;
+				}
+			});
+		}
+
+		this.request.open(this.method, this.url, this.async,
+			this.username, this.password);
+		this.setRequestHeaders(this.request, this.params);
+		
+		if (window.XMLHttpRequest && this.withCredentials)
+		{
+			this.request.withCredentials = 'true';
+		}
+		
+		if (!mxClient.IS_QUIRKS && (document.documentMode == null || document.documentMode > 9) &&
+			window.XMLHttpRequest && timeout != null && ontimeout != null)
+		{
+			this.request.timeout = timeout;
+			this.request.ontimeout = ontimeout;
+		}
+				
+		this.request.send(this.params);
+	}
+};
+
+/**
+ * Function: setRequestHeaders
+ * 
+ * Sets the headers for the given request and parameters. This sets the
+ * content-type to application/x-www-form-urlencoded if any params exist.
+ * 
+ * Example:
+ * 
+ * (code)
+ * request.setRequestHeaders = function(request, params)
+ * {
+ *   if (params != null)
+ *   {
+ *     request.setRequestHeader('Content-Type',
+ *             'multipart/form-data');
+ *     request.setRequestHeader('Content-Length',
+ *             params.length);
+ *   }
+ * };
+ * (end)
+ * 
+ * Use the code above before calling <send> if you require a
+ * multipart/form-data request.   
+ */
+mxXmlRequest.prototype.setRequestHeaders = function(request, params)
+{
+	if (params != null)
+	{
+		request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+	}
+};
+
+/**
+ * Function: simulate
+ * 
+ * Creates and posts a request to the given target URL using a dynamically
+ * created form inside the given document.
+ * 
+ * Parameters:
+ * 
+ * docs - Document that contains the form element.
+ * target - Target to send the form result to.
+ */
+mxXmlRequest.prototype.simulate = function(doc, target)
+{
+	doc = doc || document;
+	var old = null;
+
+	if (doc == document)
+	{
+		old = window.onbeforeunload;		
+		window.onbeforeunload = null;
+	}
+			
+	var form = doc.createElement('form');
+	form.setAttribute('method', this.method);
+	form.setAttribute('action', this.url);
+
+	if (target != null)
+	{
+		form.setAttribute('target', target);
+	}
+
+	form.style.display = 'none';
+	form.style.visibility = 'hidden';
+	
+	var pars = (this.params.indexOf('&') > 0) ?
+		this.params.split('&') :
+		this.params.split();
+
+	// Adds the parameters as textareas to the form
+	for (var i=0; i<pars.length; i++)
+	{
+		var pos = pars[i].indexOf('=');
+		
+		if (pos > 0)
+		{
+			var name = pars[i].substring(0, pos);
+			var value = pars[i].substring(pos+1);
+			
+			if (this.decodeSimulateValues)
+			{
+				value = decodeURIComponent(value);
+			}
+			
+			var textarea = doc.createElement('textarea');
+			textarea.setAttribute('wrap', 'off');
+			textarea.setAttribute('name', name);
+			mxUtils.write(textarea, value);
+			form.appendChild(textarea);
+		}
+	}
+	
+	doc.body.appendChild(form);
+	form.submit();
+	
+	if (form.parentNode != null)
+	{
+		form.parentNode.removeChild(form);
+	}
+
+	if (old != null)
+	{		
+		window.onbeforeunload = old;
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/view/mxCellEditor.js b/airavata-kubernetes/web-console/src/assets/js/view/mxCellEditor.js
new file mode 100644
index 0000000..7db84a7
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/view/mxCellEditor.js
@@ -0,0 +1,1069 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxCellEditor
+ *
+ * In-place editor for the graph. To control this editor, use
+ * <mxGraph.invokesStopCellEditing>, <mxGraph.enterStopsCellEditing> and
+ * <mxGraph.escapeEnabled>. If <mxGraph.enterStopsCellEditing> is true then
+ * ctrl-enter or shift-enter can be used to create a linefeed. The F2 and
+ * escape keys can always be used to stop editing.
+ * 
+ * To customize the location of the textbox in the graph, override
+ * <getEditorBounds> as follows:
+ * 
+ * (code)
+ * graph.cellEditor.getEditorBounds = function(state)
+ * {
+ *   var result = mxCellEditor.prototype.getEditorBounds.apply(this, arguments);
+ *   
+ *   if (this.graph.getModel().isEdge(state.cell))
+ *   {
+ *     result.x = state.getCenterX() - result.width / 2;
+ *     result.y = state.getCenterY() - result.height / 2;
+ *   }
+ *   
+ *   return result;
+ * };
+ * (end)
+ * 
+ * Note that this hook is only called if <autoSize> is false. If <autoSize> is true,
+ * then <mxShape.getLabelBounds> is used to compute the current bounds of the textbox.
+ * 
+ * The textarea uses the mxCellEditor CSS class. You can modify this class in
+ * your custom CSS. Note: You should modify the CSS after loading the client
+ * in the page.
+ *
+ * Example:
+ * 
+ * To only allow numeric input in the in-place editor, use the following code.
+ *
+ * (code)
+ * var text = graph.cellEditor.textarea;
+ * 
+ * mxEvent.addListener(text, 'keydown', function (evt)
+ * {
+ *   if (!(evt.keyCode >= 48 && evt.keyCode <= 57) &&
+ *       !(evt.keyCode >= 96 && evt.keyCode <= 105))
+ *   {
+ *     mxEvent.consume(evt);
+ *   }
+ * }); 
+ * (end)
+ * 
+ * Placeholder:
+ * 
+ * To implement a placeholder for cells without a label, use the
+ * <emptyLabelText> variable.
+ * 
+ * Resize in Chrome:
+ * 
+ * Resize of the textarea is disabled by default. If you want to enable
+ * this feature extend <init> and set this.textarea.style.resize = ''.
+ * 
+ * To start editing on a key press event, the container of the graph
+ * should have focus or a focusable parent should be used to add the
+ * key press handler as follows.
+ * 
+ * (code)
+ * mxEvent.addListener(graph.container, 'keypress', mxUtils.bind(this, function(evt)
+ * {
+ *   if (!graph.isEditing() && !graph.isSelectionEmpty() && evt.which !== 0 &&
+ *       !mxEvent.isAltDown(evt) && !mxEvent.isControlDown(evt) && !mxEvent.isMetaDown(evt))
+ *   {
+ *     graph.startEditing();
+ *     
+ *     if (mxClient.IS_FF)
+ *     {
+ *       graph.cellEditor.textarea.value = String.fromCharCode(evt.which);
+ *     }
+ *   }
+ * }));
+ * (end)
+ * 
+ * To allow focus for a DIV, and hence to receive key press events, some browsers
+ * require it to have a valid tabindex attribute. In this case the following
+ * code may be used to keep the container focused.
+ * 
+ * (code)
+ * var graphFireMouseEvent = graph.fireMouseEvent;
+ * graph.fireMouseEvent = function(evtName, me, sender)
+ * {
+ *   if (evtName == mxEvent.MOUSE_DOWN)
+ *   {
+ *     this.container.focus();
+ *   }
+ *   
+ *   graphFireMouseEvent.apply(this, arguments);
+ * };
+ * (end)
+ *
+ * Constructor: mxCellEditor
+ *
+ * Constructs a new in-place editor for the specified graph.
+ * 
+ * Parameters:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ */
+function mxCellEditor(graph)
+{
+	this.graph = graph;
+	
+	// Stops editing after zoom changes
+	this.zoomHandler = mxUtils.bind(this, function()
+	{
+		if (this.graph.isEditing())
+		{
+			this.resize();
+		}
+	});
+	
+	this.graph.view.addListener(mxEvent.SCALE, this.zoomHandler);
+	this.graph.view.addListener(mxEvent.SCALE_AND_TRANSLATE, this.zoomHandler);
+	
+	// Adds handling of deleted cells while editing
+	this.changeHandler = mxUtils.bind(this, function(sender)
+	{
+		if (this.editingCell != null && this.graph.getView().getState(this.editingCell) == null)
+		{
+			this.stopEditing(true);
+		}
+	});
+
+	this.graph.getModel().addListener(mxEvent.CHANGE, this.changeHandler);
+};
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxCellEditor.prototype.graph = null;
+
+/**
+ * Variable: textarea
+ *
+ * Holds the DIV that is used for text editing. Note that this may be null before the first
+ * edit. Instantiated in <init>.
+ */
+mxCellEditor.prototype.textarea = null;
+
+/**
+ * Variable: editingCell
+ * 
+ * Reference to the <mxCell> that is currently being edited.
+ */
+mxCellEditor.prototype.editingCell = null;
+
+/**
+ * Variable: trigger
+ * 
+ * Reference to the event that was used to start editing.
+ */
+mxCellEditor.prototype.trigger = null;
+
+/**
+ * Variable: modified
+ * 
+ * Specifies if the label has been modified.
+ */
+mxCellEditor.prototype.modified = false;
+
+/**
+ * Variable: autoSize
+ * 
+ * Specifies if the textarea should be resized while the text is being edited.
+ * Default is true.
+ */
+mxCellEditor.prototype.autoSize = true;
+
+/**
+ * Variable: selectText
+ * 
+ * Specifies if the text should be selected when editing starts. Default is
+ * true.
+ */
+mxCellEditor.prototype.selectText = true;
+
+/**
+ * Variable: emptyLabelText
+ * 
+ * Text to be displayed for empty labels. Default is '' or '<br>' in Firefox as
+ * a workaround for the missing cursor bug for empty content editable. This can
+ * be set to eg. "[Type Here]" to easier visualize editing of empty labels. The
+ * value is only displayed before the first keystroke and is never used as the
+ * actual editing value.
+ */
+mxCellEditor.prototype.emptyLabelText = (mxClient.IS_FF) ? '<br>' : '';
+
+/**
+ * Variable: escapeCancelsEditing
+ * 
+ * If true, pressing the escape key will stop editing and not accept the new
+ * value. Change this to false to accept the new value on escape, and cancel
+ * editing on Shift+Escape instead. Default is true.
+ */
+mxCellEditor.prototype.escapeCancelsEditing = true;
+
+/**
+ * Variable: textNode
+ * 
+ * Reference to the label DOM node that has been hidden.
+ */
+mxCellEditor.prototype.textNode = '';
+
+/**
+ * Variable: zIndex
+ * 
+ * Specifies the zIndex for the textarea. Default is 5.
+ */
+mxCellEditor.prototype.zIndex = 5;
+
+/**
+ * Variable: minResize
+ * 
+ * Defines the minimum width and height to be used in <resize>. Default is 0x20px.
+ */
+mxCellEditor.prototype.minResize = new mxRectangle(0, 20);
+
+/**
+ * Variable: wordWrapPadding
+ * 
+ * Correction factor for word wrapping width. Default is 2 in quirks, 0 in IE
+ * 11 and 1 in all other browsers and modes.
+ */
+mxCellEditor.prototype.wordWrapPadding = (mxClient.IS_QUIRKS) ? 2 : (!mxClient.IS_IE11) ? 1 : 0;
+
+/**
+ * Variable: blurEnabled
+ *
+ * If <focusLost> should be called if <textarea> loses the focus. Default is false.
+ */
+mxCellEditor.prototype.blurEnabled = false;
+
+/**
+ * Variable: initialValue
+ * 
+ * Holds the initial editing value to check if the current value was modified.
+ */
+mxCellEditor.prototype.initialValue = null;
+
+/**
+ * Function: init
+ *
+ * Creates the <textarea> and installs the event listeners. The key handler
+ * updates the <modified> state.
+ */
+mxCellEditor.prototype.init = function ()
+{
+	this.textarea = document.createElement('div');
+	this.textarea.className = 'mxCellEditor mxPlainTextEditor';
+	this.textarea.contentEditable = true;
+	
+	// Workaround for selection outside of DIV if height is 0
+	if (mxClient.IS_GC)
+	{
+		this.textarea.style.minHeight = '1em';
+	}
+	
+	this.installListeners(this.textarea);
+};
+
+/**
+ * Function: applyValue
+ * 
+ * Called in <stopEditing> if cancel is false to invoke <mxGraph.labelChanged>.
+ */
+mxCellEditor.prototype.applyValue = function(state, value)
+{
+	this.graph.labelChanged(state.cell, value, this.trigger);
+};
+
+/**
+ * Function: getInitialValue
+ * 
+ * Gets the initial editing value for the given cell.
+ */
+mxCellEditor.prototype.getInitialValue = function(state, trigger)
+{
+	var result = mxUtils.htmlEntities(this.graph.getEditingValue(state.cell, trigger), false);
+	
+    // Workaround for trailing line breaks being ignored in the editor
+	if (!mxClient.IS_QUIRKS && document.documentMode != 8 && document.documentMode != 9 &&
+		document.documentMode != 10)
+	{
+		result = mxUtils.replaceTrailingNewlines(result, '<div><br></div>');
+	}
+    
+    return result.replace(/\n/g, '<br>');
+};
+
+/**
+ * Function: getCurrentValue
+ * 
+ * Returns the current editing value.
+ */
+mxCellEditor.prototype.getCurrentValue = function(state)
+{
+	return mxUtils.extractTextWithWhitespace(this.textarea.childNodes);
+};
+
+/**
+ * Function: installListeners
+ * 
+ * Installs listeners for focus, change and standard key event handling.
+ */
+mxCellEditor.prototype.installListeners = function(elt)
+{
+	// Applies value if focus is lost
+	mxEvent.addListener(elt, 'blur', mxUtils.bind(this, function(evt)
+	{
+		if (this.blurEnabled)
+		{
+			this.focusLost(evt);
+		}
+	}));
+
+	// Updates modified state and handles placeholder text
+	mxEvent.addListener(elt, 'keydown', mxUtils.bind(this, function(evt)
+	{
+		if (!mxEvent.isConsumed(evt))
+		{
+			if (this.isStopEditingEvent(evt))
+			{
+				this.graph.stopEditing(false);
+				mxEvent.consume(evt);
+			}
+			else if (evt.keyCode == 27 /* Escape */)
+			{
+				this.graph.stopEditing(this.escapeCancelsEditing || mxEvent.isShiftDown(evt));
+				mxEvent.consume(evt);
+			}
+		}
+	}));
+
+	// Keypress only fires if printable key was pressed and handles removing the empty placeholder
+	var keypressHandler = mxUtils.bind(this, function(evt)
+	{
+		if (this.editingCell != null)
+		{
+			// Clears the initial empty label on the first keystroke
+			// and workaround for FF which fires keypress for delete and backspace
+			if (this.clearOnChange && elt.innerHTML == this.getEmptyLabelText() &&
+				(!mxClient.IS_FF || (evt.keyCode != 8 /* Backspace */ && evt.keyCode != 46 /* Delete */)))
+			{
+				this.clearOnChange = false;
+				elt.innerHTML = '';
+			}
+		}
+	});
+
+	mxEvent.addListener(elt, 'keypress', keypressHandler);
+	mxEvent.addListener(elt, 'paste', keypressHandler);
+	
+	// Handler for updating the empty label text value after a change
+	var keyupHandler = mxUtils.bind(this, function(evt)
+	{
+		if (this.editingCell != null)
+		{
+			// Uses an optional text value for sempty labels which is cleared
+			// when the first keystroke appears. This makes it easier to see
+			// that a label is being edited even if the label is empty.
+			// In Safari and FF, an empty text is represented by <BR> which isn't enough to force a valid size
+			if (this.textarea.innerHTML.length == 0 || this.textarea.innerHTML == '<br>')
+			{
+				this.textarea.innerHTML = this.getEmptyLabelText();
+				this.clearOnChange = this.textarea.innerHTML.length > 0;
+			}
+			else
+			{
+				this.clearOnChange = false;
+			}
+		}
+	});
+
+	mxEvent.addListener(elt, (!mxClient.IS_IE11 && !mxClient.IS_IE) ? 'input' : 'keyup', keyupHandler);
+	mxEvent.addListener(elt, 'cut', keyupHandler);
+	mxEvent.addListener(elt, 'paste', keyupHandler);
+
+	// Adds automatic resizing of the textbox while typing using input, keyup and/or DOM change events
+	var evtName = (!mxClient.IS_IE11 && !mxClient.IS_IE) ? 'input' : 'keydown';
+	
+	var resizeHandler = mxUtils.bind(this, function(evt)
+	{
+		if (this.editingCell != null && this.autoSize && !mxEvent.isConsumed(evt))
+		{
+			// Asynchronous is needed for keydown and shows better results for input events overall
+			// (ie non-blocking and cases where the offsetWidth/-Height was wrong at this time)
+			if (this.resizeThread != null)
+			{
+				window.clearTimeout(this.resizeThread);
+			}
+			
+			this.resizeThread = window.setTimeout(mxUtils.bind(this, function()
+			{
+				this.resizeThread = null;
+				this.resize();
+			}), 0);
+		}
+	});
+	
+	mxEvent.addListener(elt, evtName, resizeHandler);
+
+	if (document.documentMode >= 9)
+	{
+		mxEvent.addListener(elt, 'DOMNodeRemoved', resizeHandler);
+		mxEvent.addListener(elt, 'DOMNodeInserted', resizeHandler);
+	}
+	else
+	{
+		mxEvent.addListener(elt, 'cut', resizeHandler);
+		mxEvent.addListener(elt, 'paste', resizeHandler);
+	}
+};
+
+/**
+ * Function: isStopEditingEvent
+ * 
+ * Returns true if the given keydown event should stop cell editing. This
+ * returns true if F2 is pressed of if <mxGraph.enterStopsCellEditing> is true
+ * and enter is pressed without control or shift.
+ */
+mxCellEditor.prototype.isStopEditingEvent = function(evt)
+{
+	return evt.keyCode == 113 /* F2 */ || (this.graph.isEnterStopsCellEditing() &&
+		evt.keyCode == 13 /* Enter */ && !mxEvent.isControlDown(evt) &&
+		!mxEvent.isShiftDown(evt));
+};
+
+/**
+ * Function: isEventSource
+ * 
+ * Returns true if this editor is the source for the given native event.
+ */
+mxCellEditor.prototype.isEventSource = function(evt)
+{
+	return mxEvent.getSource(evt) == this.textarea;
+};
+
+/**
+ * Function: resize
+ * 
+ * Returns <modified>.
+ */
+mxCellEditor.prototype.resize = function()
+{
+	var state = this.graph.getView().getState(this.editingCell);
+	
+	if (state == null)
+	{
+		this.stopEditing(true);
+	}
+	else if (this.textarea != null)
+	{
+		var isEdge = this.graph.getModel().isEdge(state.cell);
+ 		var scale = this.graph.getView().scale;
+ 		var m = null;
+		
+		if (!this.autoSize || (state.style[mxConstants.STYLE_OVERFLOW] == 'fill'))
+		{
+			// Specifies the bounds of the editor box
+			this.bounds = this.getEditorBounds(state);
+			this.textarea.style.width = Math.round(this.bounds.width / scale) + 'px';
+			this.textarea.style.height = Math.round(this.bounds.height / scale) + 'px';
+			
+			// FIXME: Offset when scaled
+			if (document.documentMode == 8 || mxClient.IS_QUIRKS)
+			{
+				this.textarea.style.left = Math.round(this.bounds.x) + 'px';
+				this.textarea.style.top = Math.round(this.bounds.y) + 'px';
+			}
+			else
+			{
+				this.textarea.style.left = Math.max(0, Math.round(this.bounds.x + 1)) + 'px';
+				this.textarea.style.top = Math.max(0, Math.round(this.bounds.y + 1)) + 'px';
+			}
+			
+			// Installs native word wrapping and avoids word wrap for empty label placeholder
+			if (this.graph.isWrapping(state.cell) && (this.bounds.width >= 2 || this.bounds.height >= 2) &&
+				this.textarea.innerHTML != this.getEmptyLabelText())
+			{
+				this.textarea.style.wordWrap = mxConstants.WORD_WRAP;
+				this.textarea.style.whiteSpace = 'normal';
+				
+				if (state.style[mxConstants.STYLE_OVERFLOW] != 'fill')
+				{
+					this.textarea.style.width = Math.round(this.bounds.width / scale) + this.wordWrapPadding + 'px';
+				}
+			}
+			else
+			{
+				this.textarea.style.whiteSpace = 'nowrap';
+				
+				if (state.style[mxConstants.STYLE_OVERFLOW] != 'fill')
+				{
+					this.textarea.style.width = '';
+				}
+			}
+		}
+		else
+	 	{
+	 		var lw = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_WIDTH, null);
+			m = (state.text != null) ? state.text.margin : null;
+			
+			if (m == null)
+			{
+				m = mxUtils.getAlignmentAsPoint(mxUtils.getValue(state.style, mxConstants.STYLE_ALIGN, mxConstants.ALIGN_CENTER),
+						mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_ALIGN, mxConstants.ALIGN_MIDDLE));
+			}
+			
+	 		if (isEdge)
+			{
+				this.bounds = new mxRectangle(state.absoluteOffset.x, state.absoluteOffset.y, 0, 0);
+				
+				if (lw != null)
+			 	{
+					var tmp = (parseFloat(lw) + 2) * scale;
+					this.bounds.width = tmp;
+					this.bounds.x += m.x * tmp;
+			 	}
+			}
+			else
+			{
+				var bds = mxRectangle.fromRectangle(state);
+				var hpos = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);
+				var vpos = mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);
+
+				bds = (state.shape != null && hpos == mxConstants.ALIGN_CENTER && vpos == mxConstants.ALIGN_MIDDLE) ? state.shape.getLabelBounds(bds) : bds;
+			 	
+			 	if (lw != null)
+			 	{
+			 		bds.width = parseFloat(lw) * scale;
+			 	}
+			 	
+			 	if (!state.view.graph.cellRenderer.legacySpacing || state.style[mxConstants.STYLE_OVERFLOW] != 'width')
+			 	{
+					var spacing = parseInt(state.style[mxConstants.STYLE_SPACING] || 2) * scale;
+					var spacingTop = (parseInt(state.style[mxConstants.STYLE_SPACING_TOP] || 0) + mxText.prototype.baseSpacingTop) * scale + spacing;
+					var spacingRight = (parseInt(state.style[mxConstants.STYLE_SPACING_RIGHT] || 0) + mxText.prototype.baseSpacingRight) * scale + spacing;
+					var spacingBottom = (parseInt(state.style[mxConstants.STYLE_SPACING_BOTTOM] || 0) + mxText.prototype.baseSpacingBottom) * scale + spacing;
+					var spacingLeft = (parseInt(state.style[mxConstants.STYLE_SPACING_LEFT] || 0) + mxText.prototype.baseSpacingLeft) * scale + spacing;
+					
+					var hpos = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);
+					var vpos = mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);
+
+					bds = new mxRectangle(bds.x + spacingLeft, bds.y + spacingTop,
+						bds.width - ((hpos == mxConstants.ALIGN_CENTER && lw == null) ? (spacingLeft + spacingRight) : 0),
+						bds.height - ((vpos == mxConstants.ALIGN_MIDDLE) ? (spacingTop + spacingBottom) : 0));
+			 	}
+
+				this.bounds = new mxRectangle(bds.x + state.absoluteOffset.x, bds.y + state.absoluteOffset.y, bds.width, bds.height);
+			}
+
+			// Needed for word wrap inside text blocks with oversize lines to match the final result where
+	 		// the width of the longest line is used as the reference for text alignment in the cell
+	 		// TODO: Fix word wrapping preview for edge labels in helloworld.html
+			if (this.graph.isWrapping(state.cell) && (this.bounds.width >= 2 || this.bounds.height >= 2) &&
+				this.textarea.innerHTML != this.getEmptyLabelText())
+			{
+				this.textarea.style.wordWrap = mxConstants.WORD_WRAP;
+				this.textarea.style.whiteSpace = 'normal';
+				
+		 		// Forces automatic reflow if text is removed from an oversize label and normal word wrap
+				var tmp = Math.round(this.bounds.width / ((document.documentMode == 8) ? scale : scale)) + this.wordWrapPadding;
+				this.textarea.style.width = tmp + 'px';
+				
+				if (this.textarea.scrollWidth > tmp)
+				{
+					this.textarea.style.width = this.textarea.scrollWidth + 'px';
+				}
+			}
+			else
+			{
+				// KNOWN: Trailing cursor in IE9 quirks mode is not visible
+				this.textarea.style.whiteSpace = 'nowrap';
+				this.textarea.style.width = '';
+			}
+			
+			// LATER: Keep in visible area, add fine tuning for pixel precision
+			// Workaround for wrong measuring in IE8 standards
+			if (document.documentMode == 8)
+			{
+				this.textarea.style.zoom = '1';
+				this.textarea.style.height = 'auto';
+			}
+			
+			var ow = this.textarea.scrollWidth;
+			var oh = this.textarea.scrollHeight;
+			
+			// TODO: Update CSS width and height if smaller than minResize or remove minResize
+			//if (this.minResize != null)
+			//{
+			//	ow = Math.max(ow, this.minResize.width);
+			//	oh = Math.max(oh, this.minResize.height);
+			//}
+			
+			// LATER: Keep in visible area, add fine tuning for pixel precision
+			if (document.documentMode == 8)
+			{
+				// LATER: Scaled wrapping and position is wrong in IE8
+				this.textarea.style.left = Math.max(0, Math.ceil((this.bounds.x - m.x * (this.bounds.width - (ow + 1) * scale) + ow * (scale - 1) * 0 + (m.x + 0.5) * 2) / scale)) + 'px';
+				this.textarea.style.top = Math.max(0, Math.ceil((this.bounds.y - m.y * (this.bounds.height - (oh + 0.5) * scale) + oh * (scale - 1) * 0 + Math.abs(m.y + 0.5) * 1) / scale)) + 'px';
+				// Workaround for wrong event handling width and height
+				this.textarea.style.width = Math.round(ow * scale) + 'px';
+				this.textarea.style.height = Math.round(oh * scale) + 'px';
+			}
+			else if (mxClient.IS_QUIRKS)
+			{			
+				this.textarea.style.left = Math.max(0, Math.ceil(this.bounds.x - m.x * (this.bounds.width - (ow + 1) * scale) + ow * (scale - 1) * 0 + (m.x + 0.5) * 2)) + 'px';
+				this.textarea.style.top = Math.max(0, Math.ceil(this.bounds.y - m.y * (this.bounds.height - (oh + 0.5) * scale) + oh * (scale - 1) * 0 + Math.abs(m.y + 0.5) * 1)) + 'px';
+			}
+			else
+			{
+				this.textarea.style.left = Math.max(0, Math.round(this.bounds.x - m.x * (this.bounds.width - 2)) + 1) + 'px';
+				this.textarea.style.top = Math.max(0, Math.round(this.bounds.y - m.y * (this.bounds.height - 4) + ((m.y == -1) ? 3 : 0)) + 1) + 'px';
+			}
+	 	}
+
+		if (mxClient.IS_VML)
+		{
+			this.textarea.style.zoom = scale;
+		}
+		else
+		{
+			mxUtils.setPrefixedStyle(this.textarea.style, 'transformOrigin', '0px 0px');
+			mxUtils.setPrefixedStyle(this.textarea.style, 'transform',
+				'scale(' + scale + ',' + scale + ')' + ((m == null) ? '' :
+				' translate(' + (m.x * 100) + '%,' + (m.y * 100) + '%)'));
+		}
+	}
+};
+
+/**
+ * Function: focusLost
+ *
+ * Called if the textarea has lost focus.
+ */
+mxCellEditor.prototype.focusLost = function()
+{
+	this.stopEditing(!this.graph.isInvokesStopCellEditing());
+};
+
+/**
+ * Function: getBackgroundColor
+ * 
+ * Returns the background color for the in-place editor. This implementation
+ * always returns null.
+ */
+mxCellEditor.prototype.getBackgroundColor = function(state)
+{
+	return null;
+};
+
+/**
+ * Function: startEditing
+ *
+ * Starts the editor for the given cell.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to start editing.
+ * trigger - Optional mouse event that triggered the editor.
+ */
+mxCellEditor.prototype.startEditing = function(cell, trigger)
+{
+	this.stopEditing(true);
+	
+	// Creates new textarea instance
+	if (this.textarea == null)
+	{
+		this.init();
+	}
+	
+	if (this.graph.tooltipHandler != null)
+	{
+		this.graph.tooltipHandler.hideTooltip();
+	}
+	
+	var state = this.graph.getView().getState(cell);
+	
+	if (state != null)
+	{
+		// Configures the style of the in-place editor
+		var scale = this.graph.getView().scale;
+		var size = mxUtils.getValue(state.style, mxConstants.STYLE_FONTSIZE, mxConstants.DEFAULT_FONTSIZE);
+		var family = mxUtils.getValue(state.style, mxConstants.STYLE_FONTFAMILY, mxConstants.DEFAULT_FONTFAMILY);
+		var color = mxUtils.getValue(state.style, mxConstants.STYLE_FONTCOLOR, 'black');
+		var align = mxUtils.getValue(state.style, mxConstants.STYLE_ALIGN, mxConstants.ALIGN_LEFT);
+		var bold = (mxUtils.getValue(state.style, mxConstants.STYLE_FONTSTYLE, 0) &
+				mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD;
+		var italic = (mxUtils.getValue(state.style, mxConstants.STYLE_FONTSTYLE, 0) &
+				mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC;
+		var uline = (mxUtils.getValue(state.style, mxConstants.STYLE_FONTSTYLE, 0) &
+				mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE;
+		
+		this.textarea.style.lineHeight = (mxConstants.ABSOLUTE_LINE_HEIGHT) ? Math.round(size * mxConstants.LINE_HEIGHT) + 'px' : mxConstants.LINE_HEIGHT;
+		this.textarea.style.backgroundColor = this.getBackgroundColor(state);
+		this.textarea.style.textDecoration = (uline) ? 'underline' : '';
+		this.textarea.style.fontWeight = (bold) ? 'bold' : 'normal';
+		this.textarea.style.fontStyle = (italic) ? 'italic' : '';
+		this.textarea.style.fontSize = Math.round(size) + 'px';
+		this.textarea.style.zIndex = this.zIndex;
+		this.textarea.style.fontFamily = family;
+		this.textarea.style.textAlign = align;
+		this.textarea.style.outline = 'none';
+		this.textarea.style.color = color;
+		
+		var dir = this.textDirection = mxUtils.getValue(state.style, mxConstants.STYLE_TEXT_DIRECTION, mxConstants.DEFAULT_TEXT_DIRECTION);
+		
+		if (dir == mxConstants.TEXT_DIRECTION_AUTO)
+		{
+			if (state != null && state.text != null && state.text.dialect != mxConstants.DIALECT_STRICTHTML &&
+				!mxUtils.isNode(state.text.value))
+			{
+				dir = state.text.getAutoDirection();
+			}
+		}
+		
+		if (dir == mxConstants.TEXT_DIRECTION_LTR || dir == mxConstants.TEXT_DIRECTION_RTL)
+		{
+			this.textarea.setAttribute('dir', dir);
+		}
+		else
+		{
+			this.textarea.removeAttribute('dir');
+		}
+
+		// Sets the initial editing value
+		this.textarea.innerHTML = this.getInitialValue(state, trigger) || '';
+		this.initialValue = this.textarea.innerHTML;
+
+		// Uses an optional text value for empty labels which is cleared
+		// when the first keystroke appears. This makes it easier to see
+		// that a label is being edited even if the label is empty.
+		if (this.textarea.innerHTML.length == 0 || this.textarea.innerHTML == '<br>')
+		{
+			this.textarea.innerHTML = this.getEmptyLabelText();
+			this.clearOnChange = true;
+		}
+		else
+		{
+			this.clearOnChange = this.textarea.innerHTML == this.getEmptyLabelText();
+		}
+
+		this.graph.container.appendChild(this.textarea);
+		
+		// Update this after firing all potential events that could update the cleanOnChange flag
+		this.editingCell = cell;
+		this.trigger = trigger;
+		this.textNode = null;
+
+		if (state.text != null && this.isHideLabel(state))
+		{
+			this.textNode = state.text.node;
+			this.textNode.style.visibility = 'hidden';
+		}
+
+		// Workaround for initial offsetHeight not ready for heading in markup
+		if (this.autoSize && (this.graph.model.isEdge(state.cell) || state.style[mxConstants.STYLE_OVERFLOW] != 'fill'))
+		{
+			window.setTimeout(mxUtils.bind(this, function()
+			{
+				this.resize();
+			}), 0);
+		}
+		
+		this.resize();
+		
+		// Workaround for NS_ERROR_FAILURE in FF
+		try
+		{
+			// Prefers blinking cursor over no selected text if empty
+			this.textarea.focus();
+			
+			if (this.isSelectText() && this.textarea.innerHTML.length > 0 &&
+				(this.textarea.innerHTML != this.getEmptyLabelText() || !this.clearOnChange))
+			{
+				document.execCommand('selectAll', false, null);
+			}
+		}
+		catch (e)
+		{
+			// ignore
+		}
+	}
+};
+
+/**
+ * Function: isSelectText
+ * 
+ * Returns <selectText>.
+ */
+mxCellEditor.prototype.isSelectText = function()
+{
+	return this.selectText;
+};
+
+/**
+ * Function: stopEditing
+ *
+ * Stops the editor and applies the value if cancel is false.
+ */
+mxCellEditor.prototype.stopEditing = function(cancel)
+{
+	cancel = cancel || false;
+	
+	if (this.editingCell != null)
+	{
+		if (this.textNode != null)
+		{
+			this.textNode.style.visibility = 'visible';
+			this.textNode = null;
+		}
+
+		var state = (!cancel) ? this.graph.view.getState(this.editingCell) : null;
+
+		var initial = this.initialValue;
+		this.initialValue = null;
+		this.editingCell = null;
+		this.trigger = null;
+		this.bounds = null;
+		this.textarea.blur();
+		
+		if (this.textarea.parentNode != null)
+		{
+			this.textarea.parentNode.removeChild(this.textarea);
+		}
+		
+		if (this.clearOnChange && this.textarea.innerHTML == this.getEmptyLabelText())
+		{
+			this.textarea.innerHTML = '';
+			this.clearOnChange = false;
+		}
+		
+		if (state != null && this.textarea.innerHTML != initial)
+		{
+			this.prepareTextarea();
+			var value = this.getCurrentValue(state);
+			
+			if (value != null)
+			{
+				this.applyValue(state, value);
+			}
+		}
+		
+		// Forces new instance on next edit for undo history reset
+		mxEvent.release(this.textarea);
+		this.textarea = null;
+	}
+};
+
+/**
+ * Function: prepareTextarea
+ * 
+ * Prepares the textarea for getting its value in <stopEditing>.
+ * This implementation removes the extra trailing linefeed in Firefox.
+ */
+mxCellEditor.prototype.prepareTextarea = function()
+{
+	if (mxClient.IS_FF && this.textarea.lastChild != null &&
+		this.textarea.lastChild.nodeName == 'BR')
+	{
+		this.textarea.removeChild(this.textarea.lastChild);
+	}
+};
+
+/**
+ * Function: isHideLabel
+ * 
+ * Returns true if the label should be hidden while the cell is being
+ * edited.
+ */
+mxCellEditor.prototype.isHideLabel = function(state)
+{
+	return true;
+};
+
+/**
+ * Function: getMinimumSize
+ * 
+ * Returns the minimum width and height for editing the given state.
+ */
+mxCellEditor.prototype.getMinimumSize = function(state)
+{
+	var scale = this.graph.getView().scale;
+	
+	return new mxRectangle(0, 0, (state.text == null) ? 30 : state.text.size * scale + 20,
+			(this.textarea.style.textAlign == 'left') ? 120 : 40);
+};
+
+/**
+ * Function: getEditorBounds
+ * 
+ * Returns the <mxRectangle> that defines the bounds of the editor.
+ */
+mxCellEditor.prototype.getEditorBounds = function(state)
+{
+	var isEdge = this.graph.getModel().isEdge(state.cell);
+	var scale = this.graph.getView().scale;
+	var minSize = this.getMinimumSize(state);
+	var minWidth = minSize.width;
+ 	var minHeight = minSize.height;
+ 	var result = null;
+ 	
+ 	if (!isEdge && state.view.graph.cellRenderer.legacySpacing && state.style[mxConstants.STYLE_OVERFLOW] == 'fill')
+ 	{
+ 		result = state.shape.getLabelBounds(mxRectangle.fromRectangle(state));
+ 	}
+ 	else
+ 	{
+		var spacing = parseInt(state.style[mxConstants.STYLE_SPACING] || 0) * scale;
+		var spacingTop = (parseInt(state.style[mxConstants.STYLE_SPACING_TOP] || 0) + mxText.prototype.baseSpacingTop) * scale + spacing;
+		var spacingRight = (parseInt(state.style[mxConstants.STYLE_SPACING_RIGHT] || 0) + mxText.prototype.baseSpacingRight) * scale + spacing;
+		var spacingBottom = (parseInt(state.style[mxConstants.STYLE_SPACING_BOTTOM] || 0) + mxText.prototype.baseSpacingBottom) * scale + spacing;
+		var spacingLeft = (parseInt(state.style[mxConstants.STYLE_SPACING_LEFT] || 0) + mxText.prototype.baseSpacingLeft) * scale + spacing;
+	
+	 	result = new mxRectangle(state.x, state.y,
+	 		 Math.max(minWidth, state.width - spacingLeft - spacingRight),
+	 		 Math.max(minHeight, state.height - spacingTop - spacingBottom));
+		var hpos = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);
+		var vpos = mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);
+		
+		result = (state.shape != null && hpos == mxConstants.ALIGN_CENTER && vpos == mxConstants.ALIGN_MIDDLE) ? state.shape.getLabelBounds(result) : result;
+	
+		if (isEdge)
+		{
+			result.x = state.absoluteOffset.x;
+			result.y = state.absoluteOffset.y;
+	
+			if (state.text != null && state.text.boundingBox != null)
+			{
+				// Workaround for label containing just spaces in which case
+				// the bounding box location contains negative numbers 
+				if (state.text.boundingBox.x > 0)
+				{
+					result.x = state.text.boundingBox.x;
+				}
+				
+				if (state.text.boundingBox.y > 0)
+				{
+					result.y = state.text.boundingBox.y;
+				}
+			}
+		}
+		else if (state.text != null && state.text.boundingBox != null)
+		{
+			result.x = Math.min(result.x, state.text.boundingBox.x);
+			result.y = Math.min(result.y, state.text.boundingBox.y);
+		}
+	
+		result.x += spacingLeft;
+		result.y += spacingTop;
+	
+		if (state.text != null && state.text.boundingBox != null)
+		{
+			if (!isEdge)
+			{
+				result.width = Math.max(result.width, state.text.boundingBox.width);
+				result.height = Math.max(result.height, state.text.boundingBox.height);
+			}
+			else
+			{
+				result.width = Math.max(minWidth, state.text.boundingBox.width);
+				result.height = Math.max(minHeight, state.text.boundingBox.height);
+			}
+		}
+		
+		// Applies the horizontal and vertical label positions
+		if (this.graph.getModel().isVertex(state.cell))
+		{
+			var horizontal = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);
+	
+			if (horizontal == mxConstants.ALIGN_LEFT)
+			{
+				result.x -= state.width;
+			}
+			else if (horizontal == mxConstants.ALIGN_RIGHT)
+			{
+				result.x += state.width;
+			}
+	
+			var vertical = mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);
+	
+			if (vertical == mxConstants.ALIGN_TOP)
+			{
+				result.y -= state.height;
+			}
+			else if (vertical == mxConstants.ALIGN_BOTTOM)
+			{
+				result.y += state.height;
+			}
+		}
+ 	}
+ 	
+ 	return new mxRectangle(Math.round(result.x), Math.round(result.y), Math.round(result.width), Math.round(result.height));
+};
+
+/**
+ * Function: getEmptyLabelText
+ *
+ * Returns the initial label value to be used of the label of the given
+ * cell is empty. This label is displayed and cleared on the first keystroke.
+ * This implementation returns <emptyLabelText>.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> for which a text for an empty editing box should be
+ * returned.
+ */
+mxCellEditor.prototype.getEmptyLabelText = function (cell)
+{
+	return this.emptyLabelText;
+};
+
+/**
+ * Function: getEditingCell
+ *
+ * Returns the cell that is currently being edited or null if no cell is
+ * being edited.
+ */
+mxCellEditor.prototype.getEditingCell = function ()
+{
+	return this.editingCell;
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the editor and removes all associated resources.
+ */
+mxCellEditor.prototype.destroy = function ()
+{
+	if (this.textarea != null)
+	{
+		mxEvent.release(this.textarea);
+		
+		if (this.textarea.parentNode != null)
+		{
+			this.textarea.parentNode.removeChild(this.textarea);
+		}
+		
+		this.textarea = null;
+
+	}
+			
+	if (this.changeHandler != null)
+	{
+		this.graph.getModel().removeListener(this.changeHandler);
+		this.changeHandler = null;
+	}
+
+	if (this.zoomHandler)
+	{
+		this.graph.view.removeListener(this.zoomHandler);
+		this.zoomHandler = null;
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/view/mxCellOverlay.js b/airavata-kubernetes/web-console/src/assets/js/view/mxCellOverlay.js
new file mode 100644
index 0000000..42debbb
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/view/mxCellOverlay.js
@@ -0,0 +1,233 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxCellOverlay
+ *
+ * Extends <mxEventSource> to implement a graph overlay, represented by an icon
+ * and a tooltip. Overlays can handle and fire <click> events and are added to
+ * the graph using <mxGraph.addCellOverlay>, and removed using
+ * <mxGraph.removeCellOverlay>, or <mxGraph.removeCellOverlays> to remove all overlays.
+ * The <mxGraph.getCellOverlays> function returns the array of overlays for a given
+ * cell in a graph. If multiple overlays exist for the same cell, then
+ * <getBounds> should be overridden in at least one of the overlays.
+ * 
+ * Overlays appear on top of all cells in a special layer. If this is not
+ * desirable, then the image must be rendered as part of the shape or label of
+ * the cell instead.
+ *
+ * Example:
+ * 
+ * The following adds a new overlays for a given vertex and selects the cell
+ * if the overlay is clicked.
+ *
+ * (code)
+ * var overlay = new mxCellOverlay(img, html);
+ * graph.addCellOverlay(vertex, overlay);
+ * overlay.addListener(mxEvent.CLICK, function(sender, evt)
+ * {
+ *   var cell = evt.getProperty('cell');
+ *   graph.setSelectionCell(cell);
+ * });
+ * (end)
+ * 
+ * For cell overlays to be printed use <mxPrintPreview.printOverlays>.
+ *
+ * Event: mxEvent.CLICK
+ *
+ * Fires when the user clicks on the overlay. The <code>event</code> property
+ * contains the corresponding mouse event and the <code>cell</code> property
+ * contains the cell. For touch devices this is fired if the element receives
+ * a touchend event.
+ * 
+ * Constructor: mxCellOverlay
+ *
+ * Constructs a new overlay using the given image and tooltip.
+ * 
+ * Parameters:
+ * 
+ * image - <mxImage> that represents the icon to be displayed.
+ * tooltip - Optional string that specifies the tooltip.
+ * align - Optional horizontal alignment for the overlay. Possible
+ * values are <ALIGN_LEFT>, <ALIGN_CENTER> and <ALIGN_RIGHT>
+ * (default).
+ * verticalAlign - Vertical alignment for the overlay. Possible
+ * values are <ALIGN_TOP>, <ALIGN_MIDDLE> and <ALIGN_BOTTOM>
+ * (default).
+ */
+function mxCellOverlay(image, tooltip, align, verticalAlign, offset, cursor)
+{
+	this.image = image;
+	this.tooltip = tooltip;
+	this.align = (align != null) ? align : this.align;
+	this.verticalAlign = (verticalAlign != null) ? verticalAlign : this.verticalAlign;
+	this.offset = (offset != null) ? offset : new mxPoint();
+	this.cursor = (cursor != null) ? cursor : 'help';
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxCellOverlay.prototype = new mxEventSource();
+mxCellOverlay.prototype.constructor = mxCellOverlay;
+
+/**
+ * Variable: image
+ *
+ * Holds the <mxImage> to be used as the icon.
+ */
+mxCellOverlay.prototype.image = null;
+
+/**
+ * Variable: tooltip
+ * 
+ * Holds the optional string to be used as the tooltip.
+ */
+mxCellOverlay.prototype.tooltip = null;
+
+/**
+ * Variable: align
+ * 
+ * Holds the horizontal alignment for the overlay. Default is
+ * <mxConstants.ALIGN_RIGHT>. For edges, the overlay always appears in the
+ * center of the edge.
+ */
+mxCellOverlay.prototype.align = mxConstants.ALIGN_RIGHT;
+
+/**
+ * Variable: verticalAlign
+ * 
+ * Holds the vertical alignment for the overlay. Default is
+ * <mxConstants.ALIGN_BOTTOM>. For edges, the overlay always appears in the
+ * center of the edge.
+ */
+mxCellOverlay.prototype.verticalAlign = mxConstants.ALIGN_BOTTOM;
+
+/**
+ * Variable: offset
+ * 
+ * Holds the offset as an <mxPoint>. The offset will be scaled according to the
+ * current scale.
+ */
+mxCellOverlay.prototype.offset = null;
+
+/**
+ * Variable: cursor
+ * 
+ * Holds the cursor for the overlay. Default is 'help'.
+ */
+mxCellOverlay.prototype.cursor = null;
+
+/**
+ * Variable: defaultOverlap
+ * 
+ * Defines the overlapping for the overlay, that is, the proportional distance
+ * from the origin to the point defined by the alignment. Default is 0.5.
+ */
+mxCellOverlay.prototype.defaultOverlap = 0.5;
+
+/**
+ * Function: getBounds
+ * 
+ * Returns the bounds of the overlay for the given <mxCellState> as an
+ * <mxRectangle>. This should be overridden when using multiple overlays
+ * per cell so that the overlays do not overlap.
+ * 
+ * The following example will place the overlay along an edge (where
+ * x=[-1..1] from the start to the end of the edge and y is the
+ * orthogonal offset in px).
+ * 
+ * (code)
+ * overlay.getBounds = function(state)
+ * {
+ *   var bounds = mxCellOverlay.prototype.getBounds.apply(this, arguments);
+ *   
+ *   if (state.view.graph.getModel().isEdge(state.cell))
+ *   {
+ *     var pt = state.view.getPoint(state, {x: 0, y: 0, relative: true});
+ *     
+ *     bounds.x = pt.x - bounds.width / 2;
+ *     bounds.y = pt.y - bounds.height / 2;
+ *   }
+ *   
+ *   return bounds;
+ * };
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> that represents the current state of the
+ * associated cell.
+ */
+mxCellOverlay.prototype.getBounds = function(state)
+{
+	var isEdge = state.view.graph.getModel().isEdge(state.cell);
+	var s = state.view.scale;
+	var pt = null;
+
+	var w = this.image.width;
+	var h = this.image.height;
+	
+	if (isEdge)
+	{
+		var pts = state.absolutePoints;
+		
+		if (pts.length % 2 == 1)
+		{
+			pt = pts[Math.floor(pts.length / 2)];
+		}
+		else
+		{
+			var idx = pts.length / 2;
+			var p0 = pts[idx-1];
+			var p1 = pts[idx];
+			pt = new mxPoint(p0.x + (p1.x - p0.x) / 2,
+				p0.y + (p1.y - p0.y) / 2);
+		}
+	}
+	else
+	{
+		pt = new mxPoint();
+		
+		if (this.align == mxConstants.ALIGN_LEFT)
+		{
+			pt.x = state.x;
+		}
+		else if (this.align == mxConstants.ALIGN_CENTER)
+		{
+			pt.x = state.x + state.width / 2;
+		}
+		else
+		{
+			pt.x = state.x + state.width;
+		}
+		
+		if (this.verticalAlign == mxConstants.ALIGN_TOP)
+		{
+			pt.y = state.y;
+		}
+		else if (this.verticalAlign == mxConstants.ALIGN_MIDDLE)
+		{
+			pt.y = state.y + state.height / 2;
+		}
+		else
+		{
+			pt.y = state.y + state.height;
+		}
+	}
+
+	return new mxRectangle(Math.round(pt.x - (w * this.defaultOverlap - this.offset.x) * s),
+		Math.round(pt.y - (h * this.defaultOverlap - this.offset.y) * s), w * s, h * s);
+};
+
+/**
+ * Function: toString
+ * 
+ * Returns the textual representation of the overlay to be used as the
+ * tooltip. This implementation returns <tooltip>.
+ */
+mxCellOverlay.prototype.toString = function()
+{
+	return this.tooltip;
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/view/mxCellRenderer.js b/airavata-kubernetes/web-console/src/assets/js/view/mxCellRenderer.js
new file mode 100644
index 0000000..d36b303
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/view/mxCellRenderer.js
@@ -0,0 +1,1553 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxCellRenderer
+ * 
+ * Renders cells into a document object model. The <defaultShapes> is a global
+ * map of shapename, constructor pairs that is used in all instances. You can
+ * get a list of all available shape names using the following code.
+ * 
+ * In general the cell renderer is in charge of creating, redrawing and
+ * destroying the shape and label associated with a cell state, as well as
+ * some other graphical objects, namely controls and overlays. The shape
+ * hieararchy in the display (ie. the hierarchy in which the DOM nodes
+ * appear in the document) does not reflect the cell hierarchy. The shapes
+ * are a (flat) sequence of shapes and labels inside the draw pane of the
+ * graph view, with some exceptions, namely the HTML labels being placed
+ * directly inside the graph container for certain browsers.
+ * 
+ * (code)
+ * mxLog.show();
+ * for (var i in mxCellRenderer.prototype.defaultShapes)
+ * {
+ *   mxLog.debug(i);
+ * }
+ * (end)
+ *
+ * Constructor: mxCellRenderer
+ * 
+ * Constructs a new cell renderer with the following built-in shapes:
+ * arrow, rectangle, ellipse, rhombus, image, line, label, cylinder,
+ * swimlane, connector, actor and cloud.
+ */
+function mxCellRenderer() { };
+
+/**
+ * Variable: defaultEdgeShape
+ * 
+ * Defines the default shape for edges. Default is <mxConnector>.
+ */
+mxCellRenderer.prototype.defaultEdgeShape = mxConnector;
+
+/**
+ * Variable: defaultVertexShape
+ * 
+ * Defines the default shape for vertices. Default is <mxRectangleShape>.
+ */
+mxCellRenderer.prototype.defaultVertexShape = mxRectangleShape;
+
+/**
+ * Variable: defaultTextShape
+ * 
+ * Defines the default shape for labels. Default is <mxText>.
+ */
+mxCellRenderer.prototype.defaultTextShape = mxText;
+
+/**
+ * Variable: legacyControlPosition
+ * 
+ * Specifies if the folding icon should ignore the horizontal
+ * orientation of a swimlane. Default is true.
+ */
+mxCellRenderer.prototype.legacyControlPosition = true;
+
+/**
+ * Variable: legacySpacing
+ * 
+ * Specifies if spacing and label position should be ignored if overflow is
+ * fill or width. Default is true for backwards compatiblity.
+ */
+mxCellRenderer.prototype.legacySpacing = true;
+
+/**
+ * Variable: defaultShapes
+ * 
+ * Static array that contains the globally registered shapes which are
+ * known to all instances of this class. For adding new shapes you should
+ * use the static <mxCellRenderer.registerShape> function.
+ */
+mxCellRenderer.prototype.defaultShapes = new Object();
+
+/**
+ * Variable: antiAlias
+ * 
+ * Anti-aliasing option for new shapes. Default is true.
+ */
+mxCellRenderer.prototype.antiAlias = true;
+
+/**
+ * Variable: forceControlClickHandler
+ * 
+ * Specifies if the enabled state of the graph should be ignored in the control
+ * click handler (to allow folding in disabled graphs). Default is false.
+ */
+mxCellRenderer.prototype.forceControlClickHandler = false;
+
+/**
+ * Function: registerShape
+ * 
+ * Registers the given constructor under the specified key in this instance
+ * of the renderer.
+ * 
+ * Example:
+ * 
+ * (code)
+ * mxCellRenderer.registerShape(mxConstants.SHAPE_RECTANGLE, mxRectangleShape);
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * key - String representing the shape name.
+ * shape - Constructor of the <mxShape> subclass.
+ */
+mxCellRenderer.registerShape = function(key, shape)
+{
+	mxCellRenderer.prototype.defaultShapes[key] = shape;
+};
+
+// Adds default shapes into the default shapes array
+mxCellRenderer.registerShape(mxConstants.SHAPE_RECTANGLE, mxRectangleShape);
+mxCellRenderer.registerShape(mxConstants.SHAPE_ELLIPSE, mxEllipse);
+mxCellRenderer.registerShape(mxConstants.SHAPE_RHOMBUS, mxRhombus);
+mxCellRenderer.registerShape(mxConstants.SHAPE_CYLINDER, mxCylinder);
+mxCellRenderer.registerShape(mxConstants.SHAPE_CONNECTOR, mxConnector);
+mxCellRenderer.registerShape(mxConstants.SHAPE_ACTOR, mxActor);
+mxCellRenderer.registerShape(mxConstants.SHAPE_TRIANGLE, mxTriangle);
+mxCellRenderer.registerShape(mxConstants.SHAPE_HEXAGON, mxHexagon);
+mxCellRenderer.registerShape(mxConstants.SHAPE_CLOUD, mxCloud);
+mxCellRenderer.registerShape(mxConstants.SHAPE_LINE, mxLine);
+mxCellRenderer.registerShape(mxConstants.SHAPE_ARROW, mxArrow);
+mxCellRenderer.registerShape(mxConstants.SHAPE_ARROW_CONNECTOR, mxArrowConnector);
+mxCellRenderer.registerShape(mxConstants.SHAPE_DOUBLE_ELLIPSE, mxDoubleEllipse);
+mxCellRenderer.registerShape(mxConstants.SHAPE_SWIMLANE, mxSwimlane);
+mxCellRenderer.registerShape(mxConstants.SHAPE_IMAGE, mxImageShape);
+mxCellRenderer.registerShape(mxConstants.SHAPE_LABEL, mxLabel);
+
+/**
+ * Function: initializeShape
+ * 
+ * Initializes the shape in the given state by calling its init method with
+ * the correct container after configuring it using <configureShape>.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> for which the shape should be initialized.
+ */
+mxCellRenderer.prototype.initializeShape = function(state)
+{
+	state.shape.dialect = state.view.graph.dialect;
+	this.configureShape(state);
+	state.shape.init(state.view.getDrawPane());
+};
+
+/**
+ * Function: createShape
+ * 
+ * Creates and returns the shape for the given cell state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> for which the shape should be created.
+ */
+mxCellRenderer.prototype.createShape = function(state)
+{
+	var shape = null;
+	
+	if (state.style != null)
+	{
+		// Checks if there is a stencil for the name and creates
+		// a shape instance for the stencil if one exists
+		var stencil = mxStencilRegistry.getStencil(state.style[mxConstants.STYLE_SHAPE]);
+		
+		if (stencil != null)
+		{
+			shape = new mxShape(stencil);
+		}
+		else
+		{
+			var ctor = this.getShapeConstructor(state);
+			shape = new ctor();
+		}
+	}
+	
+	return shape;
+};
+
+/**
+ * Function: createIndicatorShape
+ * 
+ * Creates the indicator shape for the given cell state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> for which the indicator shape should be created.
+ */
+mxCellRenderer.prototype.createIndicatorShape = function(state)
+{
+	state.shape.indicatorShape = this.getShape(state.view.graph.getIndicatorShape(state));
+};
+
+/**
+ * Function: getShape
+ * 
+ * Returns the shape for the given name from <defaultShapes>.
+ */
+mxCellRenderer.prototype.getShape = function(name)
+{
+	return (name != null) ? mxCellRenderer.prototype.defaultShapes[name] : null;
+};
+
+/**
+ * Function: getShapeConstructor
+ * 
+ * Returns the constructor to be used for creating the shape.
+ */
+mxCellRenderer.prototype.getShapeConstructor = function(state)
+{
+	var ctor = this.getShape(state.style[mxConstants.STYLE_SHAPE]);
+	
+	if (ctor == null)
+	{
+		ctor = (state.view.graph.getModel().isEdge(state.cell)) ?
+			this.defaultEdgeShape : this.defaultVertexShape;
+	}
+	
+	return ctor;
+};
+
+/**
+ * Function: configureShape
+ * 
+ * Configures the shape for the given cell state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> for which the shape should be configured.
+ */
+mxCellRenderer.prototype.configureShape = function(state)
+{
+	state.shape.apply(state);
+	state.shape.image = state.view.graph.getImage(state);
+	state.shape.indicatorColor = state.view.graph.getIndicatorColor(state);
+	state.shape.indicatorStrokeColor = state.style[mxConstants.STYLE_INDICATOR_STROKECOLOR];
+	state.shape.indicatorGradientColor = state.view.graph.getIndicatorGradientColor(state);
+	state.shape.indicatorDirection = state.style[mxConstants.STYLE_INDICATOR_DIRECTION];
+	state.shape.indicatorImage = state.view.graph.getIndicatorImage(state);
+
+	this.postConfigureShape(state);
+};
+
+/**
+ * Function: postConfigureShape
+ * 
+ * Replaces any reserved words used for attributes, eg. inherit,
+ * indicated or swimlane for colors in the shape for the given state.
+ * This implementation resolves these keywords on the fill, stroke
+ * and gradient color keys.
+ */
+mxCellRenderer.prototype.postConfigureShape = function(state)
+{
+	if (state.shape != null)
+	{
+		this.resolveColor(state, 'indicatorColor', mxConstants.STYLE_FILLCOLOR);
+		this.resolveColor(state, 'indicatorGradientColor', mxConstants.STYLE_GRADIENTCOLOR);
+		this.resolveColor(state, 'fill', mxConstants.STYLE_FILLCOLOR);
+		this.resolveColor(state, 'stroke', mxConstants.STYLE_STROKECOLOR);
+		this.resolveColor(state, 'gradient', mxConstants.STYLE_GRADIENTCOLOR);
+	}
+};
+
+/**
+ * Function: resolveColor
+ * 
+ * Resolves special keywords 'inherit', 'indicated' and 'swimlane' and sets
+ * the respective color on the shape.
+ */
+mxCellRenderer.prototype.resolveColor = function(state, field, key)
+{
+	var value = state.shape[field];
+	var graph = state.view.graph;
+	var referenced = null;
+	
+	if (value == 'inherit')
+	{
+		referenced = graph.model.getParent(state.cell);
+	}
+	else if (value == 'swimlane')
+	{
+		if (graph.model.getTerminal(state.cell, false) != null)
+		{
+			referenced = graph.model.getTerminal(state.cell, false);
+		}
+		else
+		{
+			referenced = state.cell;
+		}
+		
+		referenced = graph.getSwimlane(referenced);
+		key = graph.swimlaneIndicatorColorAttribute;
+	}
+	else if (value == 'indicated')
+	{
+		state.shape[field] = state.shape.indicatorColor;
+	}
+	
+	if (referenced != null)
+	{
+		var rstate = graph.getView().getState(referenced);
+		state.shape[field] = null;
+
+		if (rstate != null)
+		{
+			if (rstate.shape != null && field != 'indicatorColor')
+			{
+				state.shape[field] = rstate.shape[field];
+			}
+			else
+			{
+				state.shape[field] = rstate.style[key];
+			}
+		}
+	}
+};
+
+/**
+ * Function: getLabelValue
+ * 
+ * Returns the value to be used for the label.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> for which the label should be created.
+ */
+mxCellRenderer.prototype.getLabelValue = function(state)
+{
+	return state.view.graph.getLabel(state.cell);
+};
+
+/**
+ * Function: createLabel
+ * 
+ * Creates the label for the given cell state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> for which the label should be created.
+ */
+mxCellRenderer.prototype.createLabel = function(state, value)
+{
+	var graph = state.view.graph;
+	var isEdge = graph.getModel().isEdge(state.cell);
+	
+	if (state.style[mxConstants.STYLE_FONTSIZE] > 0 || state.style[mxConstants.STYLE_FONTSIZE] == null)
+	{
+		// Avoids using DOM node for empty labels
+		var isForceHtml = (graph.isHtmlLabel(state.cell) || (value != null && mxUtils.isNode(value)));
+
+		state.text = new this.defaultTextShape(value, new mxRectangle(),
+				(state.style[mxConstants.STYLE_ALIGN] || mxConstants.ALIGN_CENTER),
+				graph.getVerticalAlign(state),
+				state.style[mxConstants.STYLE_FONTCOLOR],
+				state.style[mxConstants.STYLE_FONTFAMILY],
+				state.style[mxConstants.STYLE_FONTSIZE],
+				state.style[mxConstants.STYLE_FONTSTYLE],
+				state.style[mxConstants.STYLE_SPACING],
+				state.style[mxConstants.STYLE_SPACING_TOP],
+				state.style[mxConstants.STYLE_SPACING_RIGHT],
+				state.style[mxConstants.STYLE_SPACING_BOTTOM],
+				state.style[mxConstants.STYLE_SPACING_LEFT],
+				state.style[mxConstants.STYLE_HORIZONTAL],
+				state.style[mxConstants.STYLE_LABEL_BACKGROUNDCOLOR],
+				state.style[mxConstants.STYLE_LABEL_BORDERCOLOR],
+				graph.isWrapping(state.cell) && graph.isHtmlLabel(state.cell),
+				graph.isLabelClipped(state.cell),
+				state.style[mxConstants.STYLE_OVERFLOW],
+				state.style[mxConstants.STYLE_LABEL_PADDING],
+				mxUtils.getValue(state.style, mxConstants.STYLE_TEXT_DIRECTION, mxConstants.DEFAULT_TEXT_DIRECTION));
+		state.text.opacity = mxUtils.getValue(state.style, mxConstants.STYLE_TEXT_OPACITY, 100);
+		state.text.dialect = (isForceHtml) ? mxConstants.DIALECT_STRICTHTML : state.view.graph.dialect;
+		state.text.style = state.style;
+		state.text.state = state;
+		this.initializeLabel(state, state.text);
+		
+		// Workaround for touch devices routing all events for a mouse gesture
+		// (down, move, up) via the initial DOM node. IE additionally redirects
+		// the event via the initial DOM node but the event source is the node
+		// under the mouse, so we need to check if this is the case and force
+		// getCellAt for the subsequent mouseMoves and the final mouseUp.
+		var forceGetCell = false;
+		
+		var getState = function(evt)
+		{
+			var result = state;
+
+			if (mxClient.IS_TOUCH || forceGetCell)
+			{
+				var x = mxEvent.getClientX(evt);
+				var y = mxEvent.getClientY(evt);
+				
+				// Dispatches the drop event to the graph which
+				// consumes and executes the source function
+				var pt = mxUtils.convertPoint(graph.container, x, y);
+				result = graph.view.getState(graph.getCellAt(pt.x, pt.y));
+			}
+			
+			return result;
+		};
+		
+		// TODO: Add handling for special touch device gestures
+		mxEvent.addGestureListeners(state.text.node,
+			mxUtils.bind(this, function(evt)
+			{
+				if (this.isLabelEvent(state, evt))
+				{
+					graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt, state));
+					forceGetCell = graph.dialect != mxConstants.DIALECT_SVG &&
+						mxEvent.getSource(evt).nodeName == 'IMG';
+				}
+			}),
+			mxUtils.bind(this, function(evt)
+			{
+				if (this.isLabelEvent(state, evt))
+				{
+					graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, getState(evt)));
+				}
+			}),
+			mxUtils.bind(this, function(evt)
+			{
+				if (this.isLabelEvent(state, evt))
+				{
+					graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt, getState(evt)));
+					forceGetCell = false;
+				}
+			})
+		);
+
+		// Uses double click timeout in mxGraph for quirks mode
+		if (graph.nativeDblClickEnabled)
+		{
+			mxEvent.addListener(state.text.node, 'dblclick',
+				mxUtils.bind(this, function(evt)
+				{
+					if (this.isLabelEvent(state, evt))
+					{
+						graph.dblClick(evt, state.cell);
+						mxEvent.consume(evt);
+					}
+				})
+			);
+		}
+	}
+};
+
+/**
+ * Function: initializeLabel
+ * 
+ * Initiailzes the label with a suitable container.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose label should be initialized.
+ */
+mxCellRenderer.prototype.initializeLabel = function(state, shape)
+{
+	if (mxClient.IS_SVG && mxClient.NO_FO && shape.dialect != mxConstants.DIALECT_SVG)
+	{
+		shape.init(state.view.graph.container);
+	}
+	else
+	{
+		shape.init(state.view.getDrawPane());
+	}
+};
+
+/**
+ * Function: createCellOverlays
+ * 
+ * Creates the actual shape for showing the overlay for the given cell state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> for which the overlay should be created.
+ */
+mxCellRenderer.prototype.createCellOverlays = function(state)
+{
+	var graph = state.view.graph;
+	var overlays = graph.getCellOverlays(state.cell);
+	var dict = null;
+	
+	if (overlays != null)
+	{
+		dict = new mxDictionary();
+		
+		for (var i = 0; i < overlays.length; i++)
+		{
+			var shape = (state.overlays != null) ? state.overlays.remove(overlays[i]) : null;
+			
+			if (shape == null)
+			{
+				var tmp = new mxImageShape(new mxRectangle(), overlays[i].image.src);
+				tmp.dialect = state.view.graph.dialect;
+				tmp.preserveImageAspect = false;
+				tmp.overlay = overlays[i];
+				this.initializeOverlay(state, tmp);
+				this.installCellOverlayListeners(state, overlays[i], tmp);
+	
+				if (overlays[i].cursor != null)
+				{
+					tmp.node.style.cursor = overlays[i].cursor;
+				}
+				
+				dict.put(overlays[i], tmp);
+			}
+			else
+			{
+				dict.put(overlays[i], shape);
+			}
+		}
+	}
+	
+	// Removes unused
+	if (state.overlays != null)
+	{
+		state.overlays.visit(function(id, shape)
+		{
+			shape.destroy();
+		});
+	}
+	
+	state.overlays = dict;
+};
+
+/**
+ * Function: initializeOverlay
+ * 
+ * Initializes the given overlay.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> for which the overlay should be created.
+ * overlay - <mxImageShape> that represents the overlay.
+ */
+mxCellRenderer.prototype.initializeOverlay = function(state, overlay)
+{
+	overlay.init(state.view.getOverlayPane());
+};
+
+/**
+ * Function: installOverlayListeners
+ * 
+ * Installs the listeners for the given <mxCellState>, <mxCellOverlay> and
+ * <mxShape> that represents the overlay.
+ */
+mxCellRenderer.prototype.installCellOverlayListeners = function(state, overlay, shape)
+{
+	var graph  = state.view.graph;
+	
+	mxEvent.addListener(shape.node, 'click', function (evt)
+	{
+		if (graph.isEditing())
+		{
+			graph.stopEditing(!graph.isInvokesStopCellEditing());
+		}
+		
+		overlay.fireEvent(new mxEventObject(mxEvent.CLICK,
+				'event', evt, 'cell', state.cell));
+	});
+	
+	mxEvent.addGestureListeners(shape.node,
+		function (evt)
+		{
+			mxEvent.consume(evt);
+		},
+		function (evt)
+		{
+			graph.fireMouseEvent(mxEvent.MOUSE_MOVE,
+				new mxMouseEvent(evt, state));
+		});
+	
+	if (mxClient.IS_TOUCH)
+	{
+		mxEvent.addListener(shape.node, 'touchend', function (evt)
+		{
+			overlay.fireEvent(new mxEventObject(mxEvent.CLICK,
+					'event', evt, 'cell', state.cell));
+		});
+	}
+};
+
+/**
+ * Function: createControl
+ * 
+ * Creates the control for the given cell state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> for which the control should be created.
+ */
+mxCellRenderer.prototype.createControl = function(state)
+{
+	var graph = state.view.graph;
+	var image = graph.getFoldingImage(state);
+	
+	if (graph.foldingEnabled && image != null)
+	{
+		if (state.control == null)
+		{
+			var b = new mxRectangle(0, 0, image.width, image.height);
+			state.control = new mxImageShape(b, image.src);
+			state.control.preserveImageAspect = false;
+			state.control.dialect = graph.dialect;
+
+			this.initControl(state, state.control, true, this.createControlClickHandler(state));
+		}
+	}
+	else if (state.control != null)
+	{
+		state.control.destroy();
+		state.control = null;
+	}
+};
+
+/**
+ * Function: createControlClickHandler
+ * 
+ * Hook for creating the click handler for the folding icon.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose control click handler should be returned.
+ */
+mxCellRenderer.prototype.createControlClickHandler = function(state)
+{
+	var graph = state.view.graph;
+	
+	return mxUtils.bind(this, function (evt)
+	{
+		if (this.forceControlClickHandler || graph.isEnabled())
+		{
+			var collapse = !graph.isCellCollapsed(state.cell);
+			graph.foldCells(collapse, false, [state.cell], null, evt);
+			mxEvent.consume(evt);
+		}
+	});
+};
+
+/**
+ * Function: initControl
+ * 
+ * Initializes the given control and returns the corresponding DOM node.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> for which the control should be initialized.
+ * control - <mxShape> to be initialized.
+ * handleEvents - Boolean indicating if mousedown and mousemove should fire events via the graph.
+ * clickHandler - Optional function to implement clicks on the control.
+ */
+mxCellRenderer.prototype.initControl = function(state, control, handleEvents, clickHandler)
+{
+	var graph = state.view.graph;
+	
+	// In the special case where the label is in HTML and the display is SVG the image
+	// should go into the graph container directly in order to be clickable. Otherwise
+	// it is obscured by the HTML label that overlaps the cell.
+	var isForceHtml = graph.isHtmlLabel(state.cell) && mxClient.NO_FO &&
+		graph.dialect == mxConstants.DIALECT_SVG;
+
+	if (isForceHtml)
+	{
+		control.dialect = mxConstants.DIALECT_PREFERHTML;
+		control.init(graph.container);
+		control.node.style.zIndex = 1;
+	}
+	else
+	{
+		control.init(state.view.getOverlayPane());
+	}
+
+	var node = control.innerNode || control.node;
+	
+	// Workaround for missing click event on iOS is to check tolerance below
+	if (clickHandler != null && !mxClient.IS_IOS)
+	{
+		if (graph.isEnabled())
+		{
+			node.style.cursor = 'pointer';
+		}
+		
+		mxEvent.addListener(node, 'click', clickHandler);
+	}
+	
+	if (handleEvents)
+	{
+		var first = null;
+
+		mxEvent.addGestureListeners(node,
+			function (evt)
+			{
+				first = new mxPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+				graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt, state));
+				mxEvent.consume(evt);
+			},
+			function (evt)
+			{
+				graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, state));
+			},
+			function (evt)
+			{
+				graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt, state));
+				mxEvent.consume(evt);
+			});
+		
+		// Uses capture phase for event interception to stop bubble phase
+		if (clickHandler != null && mxClient.IS_IOS)
+		{
+			node.addEventListener('touchend', function(evt)
+			{
+				if (first != null)
+				{
+					var tol = graph.tolerance;
+					
+					if (Math.abs(first.x - mxEvent.getClientX(evt)) < tol &&
+						Math.abs(first.y - mxEvent.getClientY(evt)) < tol)
+					{
+						clickHandler.call(clickHandler, evt);
+						mxEvent.consume(evt);
+					}
+				}
+			}, true);
+		}
+	}
+	
+	return node;
+};
+
+/**
+ * Function: isShapeEvent
+ * 
+ * Returns true if the event is for the shape of the given state. This
+ * implementation always returns true.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose shape fired the event.
+ * evt - Mouse event which was fired.
+ */
+mxCellRenderer.prototype.isShapeEvent = function(state, evt)
+{
+	return true;
+};
+
+/**
+ * Function: isLabelEvent
+ * 
+ * Returns true if the event is for the label of the given state. This
+ * implementation always returns true.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose label fired the event.
+ * evt - Mouse event which was fired.
+ */
+mxCellRenderer.prototype.isLabelEvent = function(state, evt)
+{
+	return true;
+};
+
+/**
+ * Function: installListeners
+ * 
+ * Installs the event listeners for the given cell state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> for which the event listeners should be isntalled.
+ */
+mxCellRenderer.prototype.installListeners = function(state)
+{
+	var graph = state.view.graph;
+
+	// Workaround for touch devices routing all events for a mouse
+	// gesture (down, move, up) via the initial DOM node. Same for
+	// HTML images in all IE versions (VML images are working).
+	var getState = function(evt)
+	{
+		var result = state;
+		
+		if ((graph.dialect != mxConstants.DIALECT_SVG && mxEvent.getSource(evt).nodeName == 'IMG') || mxClient.IS_TOUCH)
+		{
+			var x = mxEvent.getClientX(evt);
+			var y = mxEvent.getClientY(evt);
+			
+			// Dispatches the drop event to the graph which
+			// consumes and executes the source function
+			var pt = mxUtils.convertPoint(graph.container, x, y);
+			result = graph.view.getState(graph.getCellAt(pt.x, pt.y));
+		}
+		
+		return result;
+	};
+
+	mxEvent.addGestureListeners(state.shape.node,
+		mxUtils.bind(this, function(evt)
+		{
+			if (this.isShapeEvent(state, evt))
+			{
+				graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt, state));
+			}
+		}),
+		mxUtils.bind(this, function(evt)
+		{
+			if (this.isShapeEvent(state, evt))
+			{
+				graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, getState(evt)));
+			}
+		}),
+		mxUtils.bind(this, function(evt)
+		{
+			if (this.isShapeEvent(state, evt))
+			{
+				graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt, getState(evt)));
+			}
+		})
+	);
+	
+	// Uses double click timeout in mxGraph for quirks mode
+	if (graph.nativeDblClickEnabled)
+	{
+		mxEvent.addListener(state.shape.node, 'dblclick',
+			mxUtils.bind(this, function(evt)
+			{
+				if (this.isShapeEvent(state, evt))
+				{
+					graph.dblClick(evt, state.cell);
+					mxEvent.consume(evt);
+				}
+			})
+		);
+	}
+};
+
+/**
+ * Function: redrawLabel
+ * 
+ * Redraws the label for the given cell state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose label should be redrawn.
+ */
+mxCellRenderer.prototype.redrawLabel = function(state, forced)
+{
+	var value = this.getLabelValue(state);
+	
+	if (state.text == null && value != null && (mxUtils.isNode(value) || value.length > 0))
+	{
+		this.createLabel(state, value);
+	}
+	else if (state.text != null && (value == null || value.length == 0))
+	{
+		state.text.destroy();
+		state.text = null;
+	}
+
+	if (state.text != null)
+	{
+		var graph = state.view.graph;
+
+		// Forced is true if the style has changed, so to get the updated
+		// result in getLabelBounds we apply the new style to the shape
+		if (forced)
+		{
+
+			// Checks if a full repaint is needed
+			if (state.text.lastValue != null && this.isTextShapeInvalid(state, state.text))
+			{
+				// Forces a full repaint
+				state.text.lastValue = null;
+			}
+			
+			state.text.resetStyles();
+			state.text.apply(state);
+			
+			// Special case where value is obtained via hook in graph
+			state.text.valign = graph.getVerticalAlign(state);
+		}
+		
+		var bounds = this.getLabelBounds(state);
+		var wrapping = graph.isWrapping(state.cell);
+		var clipping = graph.isLabelClipped(state.cell);
+		var isForceHtml = (state.view.graph.isHtmlLabel(state.cell) || (value != null && mxUtils.isNode(value)));
+		var dialect = (isForceHtml) ? mxConstants.DIALECT_STRICTHTML : state.view.graph.dialect;
+
+		// Text is a special case where change of dialect is possible at runtime
+		var overflow = state.style[mxConstants.STYLE_OVERFLOW] || 'visible';
+		
+		if (forced || state.text.value != value || state.text.isWrapping != wrapping ||
+			state.text.overflow != overflow || state.text.isClipping != clipping ||
+			state.text.scale != this.getTextScale(state) || state.text.dialect != dialect ||
+			!state.text.bounds.equals(bounds))
+		{
+			state.text.dialect = dialect;
+			state.text.value = value;
+			state.text.bounds = bounds;
+			state.text.scale = this.getTextScale(state);
+			state.text.wrap = wrapping;
+			state.text.clipped = clipping;
+			state.text.overflow = overflow;
+			
+			// Preserves visible state
+			var vis = state.text.node.style.visibility;
+			this.redrawLabelShape(state.text);
+			state.text.node.style.visibility = vis;
+		}
+	}
+};
+
+/**
+ * Function: isTextShapeInvalid
+ * 
+ * Returns true if the style for the text shape has changed.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose label should be checked.
+ * shape - <mxText> shape to be checked.
+ */
+mxCellRenderer.prototype.isTextShapeInvalid = function(state, shape)
+{
+	function check(property, stylename, defaultValue)
+	{
+		// Workaround for spacing added to directional spacing
+		if (stylename == 'spacingTop' || stylename == 'spacingRight' ||
+			stylename == 'spacingBottom' || stylename == 'spacingLeft')
+		{
+			result = parseFloat(shape[property]) - parseFloat(shape.spacing) !=
+				(state.style[stylename] || defaultValue);
+		}
+		else
+		{
+			result = shape[property] != (state.style[stylename] || defaultValue);
+		}
+		
+		return result;
+	};
+
+	return check('fontStyle', mxConstants.STYLE_FONTSTYLE, mxConstants.DEFAULT_FONTSTYLE) ||
+		check('family', mxConstants.STYLE_FONTFAMILY, mxConstants.DEFAULT_FONTFAMILY) ||
+		check('size', mxConstants.STYLE_FONTSIZE, mxConstants.DEFAULT_FONTSIZE) ||
+		check('color', mxConstants.STYLE_FONTCOLOR, 'black') ||
+		check('align', mxConstants.STYLE_ALIGN, '') ||
+		check('valign', mxConstants.STYLE_VERTICAL_ALIGN, '') ||
+		check('spacing', mxConstants.STYLE_SPACING, 2) ||
+		check('spacingTop', mxConstants.STYLE_SPACING_TOP, 0) ||
+		check('spacingRight', mxConstants.STYLE_SPACING_RIGHT, 0) ||
+		check('spacingBottom', mxConstants.STYLE_SPACING_BOTTOM, 0) ||
+		check('spacingLeft', mxConstants.STYLE_SPACING_LEFT, 0) ||
+		check('horizontal', mxConstants.STYLE_HORIZONTAL, true) ||
+		check('background', mxConstants.STYLE_LABEL_BACKGROUNDCOLOR) ||
+		check('border', mxConstants.STYLE_LABEL_BORDERCOLOR) ||
+		check('opacity', mxConstants.STYLE_TEXT_OPACITY, 100) ||
+		check('textDirection', mxConstants.STYLE_TEXT_DIRECTION, mxConstants.DEFAULT_TEXT_DIRECTION);
+};
+
+/**
+ * Function: redrawLabelShape
+ * 
+ * Called to invoked redraw on the given text shape.
+ * 
+ * Parameters:
+ * 
+ * shape - <mxText> shape to be redrawn.
+ */
+mxCellRenderer.prototype.redrawLabelShape = function(shape)
+{
+	shape.redraw();
+};
+
+/**
+ * Function: getTextScale
+ * 
+ * Returns the scaling used for the label of the given state
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose label scale should be returned.
+ */
+mxCellRenderer.prototype.getTextScale = function(state)
+{
+	return state.view.scale;
+};
+
+/**
+ * Function: getLabelBounds
+ * 
+ * Returns the bounds to be used to draw the label of the given state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose label bounds should be returned.
+ */
+mxCellRenderer.prototype.getLabelBounds = function(state)
+{
+	var graph = state.view.graph;
+	var scale = state.view.scale;
+	var isEdge = graph.getModel().isEdge(state.cell);
+	var bounds = new mxRectangle(state.absoluteOffset.x, state.absoluteOffset.y);
+
+	if (isEdge)
+	{
+		var spacing = state.text.getSpacing();
+		bounds.x += spacing.x * scale;
+		bounds.y += spacing.y * scale;
+		
+		var geo = graph.getCellGeometry(state.cell);
+		
+		if (geo != null)
+		{
+			bounds.width = Math.max(0, geo.width * scale);
+			bounds.height = Math.max(0, geo.height * scale);
+		}
+	}
+	else
+	{
+		// Inverts label position
+		if (state.text.isPaintBoundsInverted())
+		{
+			var tmp = bounds.x;
+			bounds.x = bounds.y;
+			bounds.y = tmp;
+		}
+		
+		bounds.x += state.x;
+		bounds.y += state.y;
+		
+		// Minimum of 1 fixes alignment bug in HTML labels
+		bounds.width = Math.max(1, state.width);
+		bounds.height = Math.max(1, state.height);
+
+		var sc = mxUtils.getValue(state.style, mxConstants.STYLE_STROKECOLOR, mxConstants.NONE);
+		
+		if (sc != mxConstants.NONE && sc != '')
+		{
+			var s = parseFloat(mxUtils.getValue(state.style, mxConstants.STYLE_STROKEWIDTH, 1)) * scale;
+			var dx = 1 + Math.floor((s - 1) / 2);
+			var dh = Math.floor(s + 1);
+			
+			bounds.x += dx;
+			bounds.y += dx;
+			bounds.width -= dh;
+			bounds.height -= dh;
+		}
+	}
+
+	if (state.text.isPaintBoundsInverted())
+	{
+		// Rotates around center of state
+		var t = (state.width - state.height) / 2;
+		bounds.x += t;
+		bounds.y -= t;
+		var tmp = bounds.width;
+		bounds.width = bounds.height;
+		bounds.height = tmp;
+	}
+	
+	// Shape can modify its label bounds
+	if (state.shape != null)
+	{
+		var hpos = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);
+		var vpos = mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);
+		
+		if (hpos == mxConstants.ALIGN_CENTER && vpos == mxConstants.ALIGN_MIDDLE)
+		{
+			bounds = state.shape.getLabelBounds(bounds);
+		}
+	}
+	
+	// Label width style overrides actual label width
+	var lw = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_WIDTH, null);
+	
+	if (lw != null)
+	{
+		bounds.width = parseFloat(lw) * scale;
+	}
+	
+	if (!isEdge)
+	{
+		this.rotateLabelBounds(state, bounds);
+	}
+	
+	return bounds;
+};
+
+/**
+ * Function: rotateLabelBounds
+ * 
+ * Adds the shape rotation to the given label bounds and
+ * applies the alignment and offsets.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose label bounds should be rotated.
+ * bounds - <mxRectangle> the rectangle to be rotated.
+ */
+mxCellRenderer.prototype.rotateLabelBounds = function(state, bounds)
+{
+	bounds.y -= state.text.margin.y * bounds.height;
+	bounds.x -= state.text.margin.x * bounds.width;
+	
+	if (!this.legacySpacing || (state.style[mxConstants.STYLE_OVERFLOW] != 'fill' && state.style[mxConstants.STYLE_OVERFLOW] != 'width'))
+	{
+		var s = state.view.scale;
+		var spacing = state.text.getSpacing();
+		bounds.x += spacing.x * s;
+		bounds.y += spacing.y * s;
+		
+		var hpos = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);
+		var vpos = mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);
+		var lw = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_WIDTH, null);
+		
+		bounds.width = Math.max(0, bounds.width - ((hpos == mxConstants.ALIGN_CENTER && lw == null) ? (state.text.spacingLeft * s + state.text.spacingRight * s) : 0));
+		bounds.height = Math.max(0, bounds.height - ((vpos == mxConstants.ALIGN_MIDDLE) ? (state.text.spacingTop * s + state.text.spacingBottom * s) : 0));
+	}
+
+	var theta = state.text.getTextRotation();
+
+	// Only needed if rotated around another center
+	if (theta != 0 && state != null && state.view.graph.model.isVertex(state.cell))
+	{
+		var cx = state.getCenterX();
+		var cy = state.getCenterY();
+		
+		if (bounds.x != cx || bounds.y != cy)
+		{
+			var rad = theta * (Math.PI / 180);
+			pt = mxUtils.getRotatedPoint(new mxPoint(bounds.x, bounds.y),
+					Math.cos(rad), Math.sin(rad), new mxPoint(cx, cy));
+			
+			bounds.x = pt.x;
+			bounds.y = pt.y;
+		}
+	}
+};
+
+/**
+ * Function: redrawCellOverlays
+ * 
+ * Redraws the overlays for the given cell state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose overlays should be redrawn.
+ */
+mxCellRenderer.prototype.redrawCellOverlays = function(state, forced)
+{
+	this.createCellOverlays(state);
+
+	if (state.overlays != null)
+	{
+		var rot = mxUtils.mod(mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION, 0), 90);
+        var rad = mxUtils.toRadians(rot);
+        var cos = Math.cos(rad);
+        var sin = Math.sin(rad);
+		
+		state.overlays.visit(function(id, shape)
+		{
+			var bounds = shape.overlay.getBounds(state);
+		
+			if (!state.view.graph.getModel().isEdge(state.cell))
+			{
+				if (state.shape != null && rot != 0)
+				{
+					var cx = bounds.getCenterX();
+					var cy = bounds.getCenterY();
+
+					var point = mxUtils.getRotatedPoint(new mxPoint(cx, cy), cos, sin,
+			        		new mxPoint(state.getCenterX(), state.getCenterY()));
+
+			        cx = point.x;
+			        cy = point.y;
+			        bounds.x = Math.round(cx - bounds.width / 2);
+			        bounds.y = Math.round(cy - bounds.height / 2);
+				}
+			}
+			
+			if (forced || shape.bounds == null || shape.scale != state.view.scale ||
+				!shape.bounds.equals(bounds))
+			{
+				shape.bounds = bounds;
+				shape.scale = state.view.scale;
+				shape.redraw();
+			}
+		});
+	}
+};
+
+/**
+ * Function: redrawControl
+ * 
+ * Redraws the control for the given cell state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose control should be redrawn.
+ */
+mxCellRenderer.prototype.redrawControl = function(state, forced)
+{
+	var image = state.view.graph.getFoldingImage(state);
+	
+	if (state.control != null && image != null)
+	{
+		var bounds = this.getControlBounds(state, image.width, image.height);
+		var r = (this.legacyControlPosition) ?
+				mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION, 0) :
+				state.shape.getTextRotation();
+		var s = state.view.scale;
+		
+		if (forced || state.control.scale != s || !state.control.bounds.equals(bounds) ||
+			state.control.rotation != r)
+		{
+			state.control.rotation = r;
+			state.control.bounds = bounds;
+			state.control.scale = s;
+			
+			state.control.redraw();
+		}
+	}
+};
+
+/**
+ * Function: getControlBounds
+ * 
+ * Returns the bounds to be used to draw the control (folding icon) of the
+ * given state.
+ */
+mxCellRenderer.prototype.getControlBounds = function(state, w, h)
+{
+	if (state.control != null)
+	{
+		var s = state.view.scale;
+		var cx = state.getCenterX();
+		var cy = state.getCenterY();
+	
+		if (!state.view.graph.getModel().isEdge(state.cell))
+		{
+			cx = state.x + w * s;
+			cy = state.y + h * s;
+			
+			if (state.shape != null)
+			{
+				// TODO: Factor out common code
+				var rot = state.shape.getShapeRotation();
+				
+				if (this.legacyControlPosition)
+				{
+					rot = mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION, 0);
+				}
+				else
+				{
+					if (state.shape.isPaintBoundsInverted())
+					{
+						var t = (state.width - state.height) / 2;
+						cx += t;
+						cy -= t;
+					}
+				}
+				
+				if (rot != 0)
+				{
+			        var rad = mxUtils.toRadians(rot);
+			        var cos = Math.cos(rad);
+			        var sin = Math.sin(rad);
+			        
+			        var point = mxUtils.getRotatedPoint(new mxPoint(cx, cy), cos, sin,
+			        		new mxPoint(state.getCenterX(), state.getCenterY()));
+			        cx = point.x;
+			        cy = point.y;
+				}
+			}
+		}
+		
+		return (state.view.graph.getModel().isEdge(state.cell)) ? 
+			new mxRectangle(Math.round(cx - w / 2 * s), Math.round(cy - h / 2 * s), Math.round(w * s), Math.round(h * s))
+			: new mxRectangle(Math.round(cx - w / 2 * s), Math.round(cy - h / 2 * s), Math.round(w * s), Math.round(h * s));
+	}
+	
+	return null;
+};
+
+/**
+ * Function: insertStateAfter
+ * 
+ * Inserts the given array of <mxShapes> after the given nodes in the DOM.
+ * 
+ * Parameters:
+ * 
+ * shapes - Array of <mxShapes> to be inserted.
+ * node - Node in <drawPane> after which the shapes should be inserted.
+ * htmlNode - Node in the graph container after which the shapes should be inserted that
+ * will not go into the <drawPane> (eg. HTML labels without foreignObjects).
+ */
+mxCellRenderer.prototype.insertStateAfter = function(state, node, htmlNode)
+{
+	var shapes = this.getShapesForState(state);
+	
+	for (var i = 0; i < shapes.length; i++)
+	{
+		if (shapes[i] != null && shapes[i].node != null)
+		{
+			var html = shapes[i].node.parentNode != state.view.getDrawPane() &&
+				shapes[i].node.parentNode != state.view.getOverlayPane();
+			var temp = (html) ? htmlNode : node;
+			
+			if (temp != null && temp.nextSibling != shapes[i].node)
+			{
+				if (temp.nextSibling == null)
+				{
+					temp.parentNode.appendChild(shapes[i].node);
+				}
+				else
+				{
+					temp.parentNode.insertBefore(shapes[i].node, temp.nextSibling);
+				}
+			}
+			else if (temp == null)
+			{
+				// Special case: First HTML node should be first sibling after canvas
+				if (shapes[i].node.parentNode == state.view.graph.container)
+				{
+					var canvas = state.view.canvas;
+					
+					while (canvas != null && canvas.parentNode != state.view.graph.container)
+					{
+						canvas = canvas.parentNode;
+					}
+					
+					if (canvas != null && canvas.nextSibling != null)
+					{
+						if (canvas.nextSibling != shapes[i].node)
+						{
+							shapes[i].node.parentNode.insertBefore(shapes[i].node, canvas.nextSibling);
+						}
+					}
+					else
+					{
+						shapes[i].node.parentNode.appendChild(shapes[i].node);
+					}
+				}
+				else if (shapes[i].node.parentNode.firstChild != null && shapes[i].node.parentNode.firstChild != shapes[i].node)
+				{
+					// Inserts the node as the first child of the parent to implement the order
+					shapes[i].node.parentNode.insertBefore(shapes[i].node, shapes[i].node.parentNode.firstChild);
+				}
+			}
+			
+			if (html)
+			{
+				htmlNode = shapes[i].node;
+			}
+			else
+			{
+				node = shapes[i].node;
+			}
+		}
+	}
+
+	return [node, htmlNode];
+};
+
+/**
+ * Function: getShapesForState
+ * 
+ * Returns the <mxShapes> for the given cell state in the order in which they should
+ * appear in the DOM.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose shapes should be returned.
+ */
+mxCellRenderer.prototype.getShapesForState = function(state)
+{
+	return [state.shape, state.text, state.control];
+};
+
+/**
+ * Function: redraw
+ * 
+ * Updates the bounds or points and scale of the shapes for the given cell
+ * state. This is called in mxGraphView.validatePoints as the last step of
+ * updating all cells.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> for which the shapes should be updated.
+ * force - Optional boolean that specifies if the cell should be reconfiured
+ * and redrawn without any additional checks.
+ * rendering - Optional boolean that specifies if the cell should actually
+ * be drawn into the DOM. If this is false then redraw and/or reconfigure
+ * will not be called on the shape.
+ */
+mxCellRenderer.prototype.redraw = function(state, force, rendering)
+{
+	var shapeChanged = this.redrawShape(state, force, rendering);
+	
+	if (state.shape != null && (rendering == null || rendering))
+	{
+		this.redrawLabel(state, shapeChanged);
+		this.redrawCellOverlays(state, shapeChanged);
+		this.redrawControl(state, shapeChanged);
+	}
+};
+
+/**
+ * Function: redrawShape
+ * 
+ * Redraws the shape for the given cell state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose label should be redrawn.
+ */
+mxCellRenderer.prototype.redrawShape = function(state, force, rendering)
+{
+	var model = state.view.graph.model;
+	var shapeChanged = false;
+
+	// Forces creation of new shape if shape style has changed
+	if (state.shape != null && state.shape.style != null && state.style != null &&
+		state.shape.style[mxConstants.STYLE_SHAPE] != state.style[mxConstants.STYLE_SHAPE])
+	{
+		state.shape.destroy();
+		state.shape = null;
+	}
+	
+	if (state.shape == null && state.view.graph.container != null &&
+		state.cell != state.view.currentRoot &&
+		(model.isVertex(state.cell) || model.isEdge(state.cell)))
+	{
+		state.shape = this.createShape(state);
+		
+		if (state.shape != null)
+		{
+			state.shape.antiAlias = this.antiAlias;
+	
+			this.createIndicatorShape(state);
+			this.initializeShape(state);
+			this.createCellOverlays(state);
+			this.installListeners(state);
+			
+			// Forces a refresh of the handler of one exists
+			state.view.graph.selectionCellsHandler.updateHandler(state);
+		}
+	}
+	else if (state.shape != null && !mxUtils.equalEntries(state.shape.style, state.style))
+	{
+		state.shape.resetStyles();
+		this.configureShape(state);
+		// LATER: Ignore update for realtime to fix reset of current gesture
+		state.view.graph.selectionCellsHandler.updateHandler(state);
+		force = true;
+	}
+
+	if (state.shape != null)
+	{
+		// Handles changes of the collapse icon
+		this.createControl(state);
+		
+		// Redraws the cell if required, ignores changes to bounds if points are
+		// defined as the bounds are updated for the given points inside the shape
+		if (force || this.isShapeInvalid(state, state.shape))
+		{
+			if (state.absolutePoints != null)
+			{
+				state.shape.points = state.absolutePoints.slice();
+				state.shape.bounds = null;
+			}
+			else
+			{
+				state.shape.points = null;
+				state.shape.bounds = new mxRectangle(state.x, state.y, state.width, state.height);
+			}
+
+			state.shape.scale = state.view.scale;
+			
+			if (rendering == null || rendering)
+			{
+				state.shape.redraw();
+			}
+			else
+			{
+				state.shape.updateBoundingBox();
+			}
+			
+			shapeChanged = true;
+		}
+	}
+
+	return shapeChanged;
+};
+
+/**
+ * Function: isShapeInvalid
+ * 
+ * Returns true if the given shape must be repainted.
+ */
+mxCellRenderer.prototype.isShapeInvalid = function(state, shape)
+{
+	return shape.bounds == null || shape.scale != state.view.scale ||
+		(state.absolutePoints == null && !shape.bounds.equals(state)) ||
+		(state.absolutePoints != null && !mxUtils.equalPoints(shape.points, state.absolutePoints))
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the shapes associated with the given cell state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> for which the shapes should be destroyed.
+ */
+mxCellRenderer.prototype.destroy = function(state)
+{
+	if (state.shape != null)
+	{
+		if (state.text != null)
+		{		
+			state.text.destroy();
+			state.text = null;
+		}
+		
+		if (state.overlays != null)
+		{
+			state.overlays.visit(function(id, shape)
+			{
+				shape.destroy();
+			});
+			
+			state.overlays = null;
+		}
+
+		if (state.control != null)
+		{
+			state.control.destroy();
+			state.control = null;
+		}
+		
+		state.shape.destroy();
+		state.shape = null;
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/view/mxCellState.js b/airavata-kubernetes/web-console/src/assets/js/view/mxCellState.js
new file mode 100644
index 0000000..8f563a7
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/view/mxCellState.js
@@ -0,0 +1,431 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxCellState
+ * 
+ * Represents the current state of a cell in a given <mxGraphView>.
+ * 
+ * For edges, the edge label position is stored in <absoluteOffset>.
+ * 
+ * The size for oversize labels can be retrieved using the boundingBox property
+ * of the <text> field as shown below.
+ * 
+ * (code)
+ * var bbox = (state.text != null) ? state.text.boundingBox : null;
+ * (end)
+ * 
+ * Constructor: mxCellState
+ * 
+ * Constructs a new object that represents the current state of the given
+ * cell in the specified view.
+ * 
+ * Parameters:
+ * 
+ * view - <mxGraphView> that contains the state.
+ * cell - <mxCell> that this state represents.
+ * style - Array of key, value pairs that constitute the style.
+ */
+function mxCellState(view, cell, style)
+{
+	this.view = view;
+	this.cell = cell;
+	this.style = style;
+	
+	this.origin = new mxPoint();
+	this.absoluteOffset = new mxPoint();
+};
+
+/**
+ * Extends mxRectangle.
+ */
+mxCellState.prototype = new mxRectangle();
+mxCellState.prototype.constructor = mxCellState;
+
+/**
+ * Variable: view
+ * 
+ * Reference to the enclosing <mxGraphView>.
+ */
+mxCellState.prototype.view = null;
+
+/**
+ * Variable: cell
+ *
+ * Reference to the <mxCell> that is represented by this state.
+ */
+mxCellState.prototype.cell = null;
+
+/**
+ * Variable: style
+ * 
+ * Contains an array of key, value pairs that represent the style of the
+ * cell.
+ */
+mxCellState.prototype.style = null;
+
+/**
+ * Variable: invalid
+ * 
+ * Specifies if the state is invalid. Default is true.
+ */
+mxCellState.prototype.invalid = true;
+
+/**
+ * Variable: origin
+ *
+ * <mxPoint> that holds the origin for all child cells. Default is a new
+ * empty <mxPoint>.
+ */
+mxCellState.prototype.origin = null;
+
+/**
+ * Variable: absolutePoints
+ * 
+ * Holds an array of <mxPoints> that represent the absolute points of an
+ * edge.
+ */
+mxCellState.prototype.absolutePoints = null;
+
+/**
+ * Variable: absoluteOffset
+ *
+ * <mxPoint> that holds the absolute offset. For edges, this is the
+ * absolute coordinates of the label position. For vertices, this is the
+ * offset of the label relative to the top, left corner of the vertex. 
+ */
+mxCellState.prototype.absoluteOffset = null;
+
+/**
+ * Variable: visibleSourceState
+ * 
+ * Caches the visible source terminal state.
+ */
+mxCellState.prototype.visibleSourceState = null;
+
+/**
+ * Variable: visibleTargetState
+ * 
+ * Caches the visible target terminal state.
+ */
+mxCellState.prototype.visibleTargetState = null;
+
+/**
+ * Variable: terminalDistance
+ * 
+ * Caches the distance between the end points for an edge.
+ */
+mxCellState.prototype.terminalDistance = 0;
+
+/**
+ * Variable: length
+ *
+ * Caches the length of an edge.
+ */
+mxCellState.prototype.length = 0;
+
+/**
+ * Variable: segments
+ * 
+ * Array of numbers that represent the cached length of each segment of the
+ * edge.
+ */
+mxCellState.prototype.segments = null;
+
+/**
+ * Variable: shape
+ * 
+ * Holds the <mxShape> that represents the cell graphically.
+ */
+mxCellState.prototype.shape = null;
+
+/**
+ * Variable: text
+ * 
+ * Holds the <mxText> that represents the label of the cell. Thi smay be
+ * null if the cell has no label.
+ */
+mxCellState.prototype.text = null;
+
+/**
+ * Variable: unscaledWidth
+ * 
+ * Holds the unscaled width of the state.
+ */
+mxCellState.prototype.unscaledWidth = null;
+
+/**
+ * Function: getPerimeterBounds
+ * 
+ * Returns the <mxRectangle> that should be used as the perimeter of the
+ * cell.
+ * 
+ * Parameters:
+ * 
+ * border - Optional border to be added around the perimeter bounds.
+ * bounds - Optional <mxRectangle> to be used as the initial bounds.
+ */
+mxCellState.prototype.getPerimeterBounds = function(border, bounds)
+{
+	border = border || 0;
+	bounds = (bounds != null) ? bounds : new mxRectangle(this.x, this.y, this.width, this.height);
+	
+	if (this.shape != null && this.shape.stencil != null && this.shape.stencil.aspect == 'fixed')
+	{
+		var aspect = this.shape.stencil.computeAspect(this.style, bounds.x, bounds.y, bounds.width, bounds.height);
+		
+		bounds.x = aspect.x;
+		bounds.y = aspect.y;
+		bounds.width = this.shape.stencil.w0 * aspect.width;
+		bounds.height = this.shape.stencil.h0 * aspect.height;
+	}
+	
+	if (border != 0)
+	{
+		bounds.grow(border);
+	}
+	
+	return bounds;
+};
+
+/**
+ * Function: setAbsoluteTerminalPoint
+ * 
+ * Sets the first or last point in <absolutePoints> depending on isSource.
+ * 
+ * Parameters:
+ * 
+ * point - <mxPoint> that represents the terminal point.
+ * isSource - Boolean that specifies if the first or last point should
+ * be assigned.
+ */
+mxCellState.prototype.setAbsoluteTerminalPoint = function(point, isSource)
+{
+	if (isSource)
+	{
+		if (this.absolutePoints == null)
+		{
+			this.absolutePoints = [];
+		}
+		
+		if (this.absolutePoints.length == 0)
+		{
+			this.absolutePoints.push(point);
+		}
+		else
+		{
+			this.absolutePoints[0] = point;
+		}
+	}
+	else
+	{
+		if (this.absolutePoints == null)
+		{
+			this.absolutePoints = [];
+			this.absolutePoints.push(null);
+			this.absolutePoints.push(point);
+		}
+		else if (this.absolutePoints.length == 1)
+		{
+			this.absolutePoints.push(point);
+		}
+		else
+		{
+			this.absolutePoints[this.absolutePoints.length - 1] = point;
+		}
+	}
+};
+
+/**
+ * Function: setCursor
+ * 
+ * Sets the given cursor on the shape and text shape.
+ */
+mxCellState.prototype.setCursor = function(cursor)
+{
+	if (this.shape != null)
+	{
+		this.shape.setCursor(cursor);
+	}
+	
+	if (this.text != null)
+	{
+		this.text.setCursor(cursor);
+	}
+};
+
+/**
+ * Function: getVisibleTerminal
+ * 
+ * Returns the visible source or target terminal cell.
+ * 
+ * Parameters:
+ * 
+ * source - Boolean that specifies if the source or target cell should be
+ * returned.
+ */
+mxCellState.prototype.getVisibleTerminal = function(source)
+{
+	var tmp = this.getVisibleTerminalState(source);
+	
+	return (tmp != null) ? tmp.cell : null;
+};
+
+/**
+ * Function: getVisibleTerminalState
+ * 
+ * Returns the visible source or target terminal state.
+ * 
+ * Parameters:
+ * 
+ * source - Boolean that specifies if the source or target state should be
+ * returned.
+ */
+mxCellState.prototype.getVisibleTerminalState = function(source)
+{
+	return (source) ? this.visibleSourceState : this.visibleTargetState;
+};
+
+/**
+ * Function: setVisibleTerminalState
+ * 
+ * Sets the visible source or target terminal state.
+ * 
+ * Parameters:
+ * 
+ * terminalState - <mxCellState> that represents the terminal.
+ * source - Boolean that specifies if the source or target state should be set.
+ */
+mxCellState.prototype.setVisibleTerminalState = function(terminalState, source)
+{
+	if (source)
+	{
+		this.visibleSourceState = terminalState;
+	}
+	else
+	{
+		this.visibleTargetState = terminalState;
+	}
+};
+
+/**
+ * Function: getCellBounds
+ * 
+ * Returns the unscaled, untranslated bounds.
+ */
+mxCellState.prototype.getCellBounds = function()
+{
+	return this.cellBounds;
+};
+
+/**
+ * Function: getPaintBounds
+ * 
+ * Returns the unscaled, untranslated paint bounds. This is the same as
+ * <getCellBounds> but with a 90 degree rotation if the shape's
+ * isPaintBoundsInverted returns true.
+ */
+mxCellState.prototype.getPaintBounds = function()
+{
+	return this.paintBounds;
+};
+
+/**
+ * Function: updateCachedBounds
+ * 
+ * Updates the cellBounds and paintBounds.
+ */
+mxCellState.prototype.updateCachedBounds = function()
+{
+	var tr = this.view.translate;
+	var s = this.view.scale;
+	this.cellBounds = new mxRectangle(this.x / s - tr.x, this.y / s - tr.y, this.width / s, this.height / s);
+	this.paintBounds = mxRectangle.fromRectangle(this.cellBounds);
+	
+	if (this.shape != null && this.shape.isPaintBoundsInverted())
+	{
+		this.paintBounds.rotate90();
+	}
+};
+
+/**
+ * Destructor: setState
+ * 
+ * Copies all fields from the given state to this state.
+ */
+mxCellState.prototype.setState = function(state)
+{
+	this.view = state.view;
+	this.cell = state.cell;
+	this.style = state.style;
+	this.absolutePoints = state.absolutePoints;
+	this.origin = state.origin;
+	this.absoluteOffset = state.absoluteOffset;
+	this.boundingBox = state.boundingBox;
+	this.terminalDistance = state.terminalDistance;
+	this.segments = state.segments;
+	this.length = state.length;
+	this.x = state.x;
+	this.y = state.y;
+	this.width = state.width;
+	this.height = state.height;
+	this.unscaledWidth = state.unscaledWidth;
+};
+
+/**
+ * Function: clone
+ *
+ * Returns a clone of this <mxPoint>.
+ */
+mxCellState.prototype.clone = function()
+{
+ 	var clone = new mxCellState(this.view, this.cell, this.style);
+
+	// Clones the absolute points
+	if (this.absolutePoints != null)
+	{
+		clone.absolutePoints = [];
+		
+		for (var i = 0; i < this.absolutePoints.length; i++)
+		{
+			clone.absolutePoints[i] = this.absolutePoints[i].clone();
+		}
+	}
+
+	if (this.origin != null)
+	{
+		clone.origin = this.origin.clone();
+	}
+
+	if (this.absoluteOffset != null)
+	{
+		clone.absoluteOffset = this.absoluteOffset.clone();
+	}
+
+	if (this.boundingBox != null)
+	{
+		clone.boundingBox = this.boundingBox.clone();
+	}
+
+	clone.terminalDistance = this.terminalDistance;
+	clone.segments = this.segments;
+	clone.length = this.length;
+	clone.x = this.x;
+	clone.y = this.y;
+	clone.width = this.width;
+	clone.height = this.height;
+	clone.unscaledWidth = this.unscaledWidth;
+	
+	return clone;
+};
+
+/**
+ * Destructor: destroy
+ * 
+ * Destroys the state and all associated resources.
+ */
+mxCellState.prototype.destroy = function()
+{
+	this.view.graph.cellRenderer.destroy(this);
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/view/mxCellStatePreview.js b/airavata-kubernetes/web-console/src/assets/js/view/mxCellStatePreview.js
new file mode 100644
index 0000000..7b0924d
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/view/mxCellStatePreview.js
@@ -0,0 +1,203 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ *
+ * Class: mxCellStatePreview
+ * 
+ * Implements a live preview for moving cells.
+ * 
+ * Constructor: mxCellStatePreview
+ * 
+ * Constructs a move preview for the given graph.
+ * 
+ * Parameters:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ */
+function mxCellStatePreview(graph)
+{
+	this.deltas = new mxDictionary();
+	this.graph = graph;
+};
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxCellStatePreview.prototype.graph = null;
+
+/**
+ * Variable: deltas
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxCellStatePreview.prototype.deltas = null;
+
+/**
+ * Variable: count
+ * 
+ * Contains the number of entries in the map.
+ */
+mxCellStatePreview.prototype.count = 0;
+
+/**
+ * Function: isEmpty
+ * 
+ * Returns true if this contains no entries.
+ */
+mxCellStatePreview.prototype.isEmpty = function()
+{
+	return this.count == 0;
+};
+
+/**
+ * Function: moveState
+ */
+mxCellStatePreview.prototype.moveState = function(state, dx, dy, add, includeEdges)
+{
+	add = (add != null) ? add : true;
+	includeEdges = (includeEdges != null) ? includeEdges : true;
+	
+	var delta = this.deltas.get(state.cell);
+
+	if (delta == null)
+	{
+		// Note: Deltas stores the point and the state since the key is a string.
+		delta = {point: new mxPoint(dx, dy), state: state};
+		this.deltas.put(state.cell, delta);
+		this.count++;
+	}
+	else if (add)
+	{
+		delta.point.x += dx;
+		delta.point.y += dy;
+	}
+	else
+	{
+		delta.point.x = dx;
+		delta.point.y = dy;
+	}
+	
+	if (includeEdges)
+	{
+		this.addEdges(state);
+	}
+	
+	return delta.point;
+};
+
+/**
+ * Function: show
+ */
+mxCellStatePreview.prototype.show = function(visitor)
+{
+	this.deltas.visit(mxUtils.bind(this, function(key, delta)
+	{
+		this.translateState(delta.state, delta.point.x, delta.point.y);
+	}));
+	
+	this.deltas.visit(mxUtils.bind(this, function(key, delta)
+	{
+		this.revalidateState(delta.state, delta.point.x, delta.point.y, visitor);
+	}));
+};
+
+/**
+ * Function: translateState
+ */
+mxCellStatePreview.prototype.translateState = function(state, dx, dy)
+{
+	if (state != null)
+	{
+		var model = this.graph.getModel();
+		
+		if (model.isVertex(state.cell))
+		{
+			state.view.updateCellState(state);
+			var geo = model.getGeometry(state.cell);
+			
+			// Moves selection cells and non-relative vertices in
+			// the first phase so that edge terminal points will
+			// be updated in the second phase
+			if ((dx != 0 || dy != 0) && geo != null && (!geo.relative || this.deltas.get(state.cell) != null))
+			{
+				state.x += dx;
+				state.y += dy;
+			}
+		}
+	    
+	    var childCount = model.getChildCount(state.cell);
+	    
+	    for (var i = 0; i < childCount; i++)
+	    {
+	    	this.translateState(state.view.getState(model.getChildAt(state.cell, i)), dx, dy);
+	    }
+	}
+};
+
+/**
+ * Function: revalidateState
+ */
+mxCellStatePreview.prototype.revalidateState = function(state, dx, dy, visitor)
+{
+	if (state != null)
+	{
+		var model = this.graph.getModel();
+		
+		// Updates the edge terminal points and restores the
+		// (relative) positions of any (relative) children
+		if (model.isEdge(state.cell))
+		{
+			state.view.updateCellState(state);
+		}
+
+		var geo = this.graph.getCellGeometry(state.cell);
+		var pState = state.view.getState(model.getParent(state.cell));
+		
+		// Moves selection vertices which are relative
+		if ((dx != 0 || dy != 0) && geo != null && geo.relative &&
+			model.isVertex(state.cell) && (pState == null ||
+			model.isVertex(pState.cell) || this.deltas.get(state.cell) != null))
+		{
+			state.x += dx;
+			state.y += dy;
+		}
+		
+		this.graph.cellRenderer.redraw(state);
+	
+		// Invokes the visitor on the given state
+		if (visitor != null)
+		{
+			visitor(state);
+		}
+						
+	    var childCount = model.getChildCount(state.cell);
+	    
+	    for (var i = 0; i < childCount; i++)
+	    {
+	    	this.revalidateState(this.graph.view.getState(model.getChildAt(state.cell, i)), dx, dy, visitor);
+	    }
+	}
+};
+
+/**
+ * Function: addEdges
+ */
+mxCellStatePreview.prototype.addEdges = function(state)
+{
+	var model = this.graph.getModel();
+	var edgeCount = model.getEdgeCount(state.cell);
+
+	for (var i = 0; i < edgeCount; i++)
+	{
+		var s = state.view.getState(model.getEdgeAt(state.cell, i));
+
+		if (s != null)
+		{
+			this.moveState(s, 0, 0);
+		}
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/view/mxConnectionConstraint.js b/airavata-kubernetes/web-console/src/assets/js/view/mxConnectionConstraint.js
new file mode 100644
index 0000000..53d7ab8
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/view/mxConnectionConstraint.js
@@ -0,0 +1,50 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxConnectionConstraint
+ * 
+ * Defines an object that contains the constraints about how to connect one
+ * side of an edge to its terminal.
+ * 
+ * Constructor: mxConnectionConstraint
+ * 
+ * Constructs a new connection constraint for the given point and boolean
+ * arguments.
+ * 
+ * Parameters:
+ * 
+ * point - Optional <mxPoint> that specifies the fixed location of the point
+ * in relative coordinates. Default is null.
+ * perimeter - Optional boolean that specifies if the fixed point should be
+ * projected onto the perimeter of the terminal. Default is true.
+ */
+function mxConnectionConstraint(point, perimeter, name)
+{
+	this.point = point;
+	this.perimeter = (perimeter != null) ? perimeter : true;
+	this.name = name;
+};
+
+/**
+ * Variable: point
+ * 
+ * <mxPoint> that specifies the fixed location of the connection point.
+ */
+mxConnectionConstraint.prototype.point = null;
+
+/**
+ * Variable: perimeter
+ * 
+ * Boolean that specifies if the point should be projected onto the perimeter
+ * of the terminal.
+ */
+mxConnectionConstraint.prototype.perimeter = null;
+
+/**
+ * Variable: name
+ * 
+ * Optional string that specifies the name of the constraint.
+ */
+mxConnectionConstraint.prototype.name = null;
diff --git a/airavata-kubernetes/web-console/src/assets/js/view/mxEdgeStyle.js b/airavata-kubernetes/web-console/src/assets/js/view/mxEdgeStyle.js
new file mode 100644
index 0000000..38a75b8
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/view/mxEdgeStyle.js
@@ -0,0 +1,1569 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+var mxEdgeStyle =
+{
+	/**
+	 * Class: mxEdgeStyle
+	 * 
+	 * Provides various edge styles to be used as the values for
+	 * <mxConstants.STYLE_EDGE> in a cell style.
+	 *
+	 * Example:
+	 * 
+	 * (code)
+	 * var style = stylesheet.getDefaultEdgeStyle();
+	 * style[mxConstants.STYLE_EDGE] = mxEdgeStyle.ElbowConnector;
+	 * (end)
+	 * 
+	 * Sets the default edge style to <ElbowConnector>.
+	 * 
+	 * Custom edge style:
+	 * 
+	 * To write a custom edge style, a function must be added to the mxEdgeStyle
+	 * object as follows:
+	 * 
+	 * (code)
+	 * mxEdgeStyle.MyStyle = function(state, source, target, points, result)
+	 * {
+	 *   if (source != null && target != null)
+	 *   {
+	 *     var pt = new mxPoint(target.getCenterX(), source.getCenterY());
+	 * 
+	 *     if (mxUtils.contains(source, pt.x, pt.y))
+	 *     {
+	 *       pt.y = source.y + source.height;
+	 *     }
+	 * 
+	 *     result.push(pt);
+	 *   }
+	 * };
+	 * (end)
+	 * 
+	 * In the above example, a right angle is created using a point on the
+	 * horizontal center of the target vertex and the vertical center of the source
+	 * vertex. The code checks if that point intersects the source vertex and makes
+	 * the edge straight if it does. The point is then added into the result array,
+	 * which acts as the return value of the function.
+	 *
+	 * The new edge style should then be registered in the <mxStyleRegistry> as follows:
+	 * (code)
+	 * mxStyleRegistry.putValue('myEdgeStyle', mxEdgeStyle.MyStyle);
+	 * (end)
+	 * 
+	 * The custom edge style above can now be used in a specific edge as follows:
+	 * 
+	 * (code)
+	 * model.setStyle(edge, 'edgeStyle=myEdgeStyle');
+	 * (end)
+	 * 
+	 * Note that the key of the <mxStyleRegistry> entry for the function should
+	 * be used in string values, unless <mxGraphView.allowEval> is true, in
+	 * which case you can also use mxEdgeStyle.MyStyle for the value in the
+	 * cell style above.
+	 * 
+	 * Or it can be used for all edges in the graph as follows:
+	 * 
+	 * (code)
+	 * var style = graph.getStylesheet().getDefaultEdgeStyle();
+	 * style[mxConstants.STYLE_EDGE] = mxEdgeStyle.MyStyle;
+	 * (end)
+	 * 
+	 * Note that the object can be used directly when programmatically setting
+	 * the value, but the key in the <mxStyleRegistry> should be used when
+	 * setting the value via a key, value pair in a cell style.
+	 * 
+	 * Function: EntityRelation
+	 * 
+	 * Implements an entity relation style for edges (as used in database
+	 * schema diagrams). At the time the function is called, the result
+	 * array contains a placeholder (null) for the first absolute point,
+	 * that is, the point where the edge and source terminal are connected.
+	 * The implementation of the style then adds all intermediate waypoints
+	 * except for the last point, that is, the connection point between the
+	 * edge and the target terminal. The first ant the last point in the
+	 * result array are then replaced with mxPoints that take into account
+	 * the terminal's perimeter and next point on the edge.
+	 *
+	 * Parameters:
+	 * 
+	 * state - <mxCellState> that represents the edge to be updated.
+	 * source - <mxCellState> that represents the source terminal.
+	 * target - <mxCellState> that represents the target terminal.
+	 * points - List of relative control points.
+	 * result - Array of <mxPoints> that represent the actual points of the
+	 * edge.
+	 */
+	 EntityRelation: function (state, source, target, points, result)
+	 {
+		var view = state.view;
+	 	var graph = view.graph;
+	 	var segment = mxUtils.getValue(state.style,
+	 			mxConstants.STYLE_SEGMENT,
+	 			mxConstants.ENTITY_SEGMENT) * view.scale;
+	 	
+		var pts = state.absolutePoints;
+		var p0 = pts[0];
+		var pe = pts[pts.length-1];
+
+	 	var isSourceLeft = false;
+
+		if (p0 != null)
+		{
+			source = new mxCellState();
+			source.x = p0.x;
+			source.y = p0.y;
+		}
+		else if (source != null)
+		{
+			var constraint = mxUtils.getPortConstraints(source, state, true, mxConstants.DIRECTION_MASK_NONE);
+			
+			if (constraint != mxConstants.DIRECTION_MASK_NONE && constraint != mxConstants.DIRECTION_MASK_WEST +
+				mxConstants.DIRECTION_MASK_EAST)
+			{
+				isSourceLeft = constraint == mxConstants.DIRECTION_MASK_WEST;
+			}
+			else
+			{
+			 	var sourceGeometry = graph.getCellGeometry(source.cell);
+		
+			 	if (sourceGeometry.relative)
+			 	{
+			 		isSourceLeft = sourceGeometry.x <= 0.5;
+			 	}
+			 	else if (target != null)
+			 	{
+			 		isSourceLeft = target.x + target.width < source.x;
+			 	}
+			}
+		}
+		else
+		{
+			return;
+		}
+	 	
+	 	var isTargetLeft = true;
+
+		if (pe != null)
+		{
+			target = new mxCellState();
+			target.x = pe.x;
+			target.y = pe.y;
+		}
+		else if (target != null)
+	 	{
+			var constraint = mxUtils.getPortConstraints(target, state, false, mxConstants.DIRECTION_MASK_NONE);
+
+			if (constraint != mxConstants.DIRECTION_MASK_NONE && constraint != mxConstants.DIRECTION_MASK_WEST +
+				mxConstants.DIRECTION_MASK_EAST)
+			{
+				isTargetLeft = constraint == mxConstants.DIRECTION_MASK_WEST;
+			}
+			else
+			{
+			 	var targetGeometry = graph.getCellGeometry(target.cell);
+	
+			 	if (targetGeometry.relative)
+			 	{
+			 		isTargetLeft = targetGeometry.x <= 0.5;
+			 	}
+			 	else if (source != null)
+			 	{
+			 		isTargetLeft = source.x + source.width < target.x;
+			 	}
+			}
+	 	}
+		
+		if (source != null && target != null)
+		{
+			var x0 = (isSourceLeft) ? source.x : source.x + source.width;
+			var y0 = view.getRoutingCenterY(source);
+			
+			var xe = (isTargetLeft) ? target.x : target.x + target.width;
+			var ye = view.getRoutingCenterY(target);
+	
+			var seg = segment;
+	
+			var dx = (isSourceLeft) ? -seg : seg;
+			var dep = new mxPoint(x0 + dx, y0);
+					
+			dx = (isTargetLeft) ? -seg : seg;
+			var arr = new mxPoint(xe + dx, ye);
+	
+			// Adds intermediate points if both go out on same side
+			if (isSourceLeft == isTargetLeft)
+			{
+				var x = (isSourceLeft) ?
+					Math.min(x0, xe)-segment :
+					Math.max(x0, xe)+segment;
+	
+				result.push(new mxPoint(x, y0));
+				result.push(new mxPoint(x, ye));
+			}
+			else if ((dep.x < arr.x) == isSourceLeft)
+			{
+				var midY = y0 + (ye - y0) / 2;
+	
+				result.push(dep);
+				result.push(new mxPoint(dep.x, midY));
+				result.push(new mxPoint(arr.x, midY));
+				result.push(arr);
+			}
+			else
+			{
+				result.push(dep);
+				result.push(arr);
+			}
+		}
+	 },
+
+	 /**
+	 * Function: Loop
+	 * 
+	 * Implements a self-reference, aka. loop.
+	 */
+	Loop: function (state, source, target, points, result)
+	{
+		var pts = state.absolutePoints;
+		
+		var p0 = pts[0];
+		var pe = pts[pts.length-1];
+
+		if (p0 != null && pe != null)
+		{
+			if (points != null && points.length > 0)
+			{
+				for (var i = 0; i < points.length; i++)
+				{
+					var pt = points[i];
+					pt = state.view.transformControlPoint(state, pt);
+					result.push(new mxPoint(pt.x, pt.y));
+				}
+			}
+
+			return;
+		}
+		
+		if (source != null)
+		{
+			var view = state.view;
+			var graph = view.graph;
+			var pt = (points != null && points.length > 0) ? points[0] : null;
+
+			if (pt != null)
+			{
+				pt = view.transformControlPoint(state, pt);
+					
+				if (mxUtils.contains(source, pt.x, pt.y))
+				{
+					pt = null;
+				}
+			}
+			
+			var x = 0;
+			var dx = 0;
+			var y = 0;
+			var dy = 0;
+			
+		 	var seg = mxUtils.getValue(state.style, mxConstants.STYLE_SEGMENT,
+		 		graph.gridSize) * view.scale;
+			var dir = mxUtils.getValue(state.style, mxConstants.STYLE_DIRECTION,
+				mxConstants.DIRECTION_WEST);
+			
+			if (dir == mxConstants.DIRECTION_NORTH ||
+				dir == mxConstants.DIRECTION_SOUTH)
+			{
+				x = view.getRoutingCenterX(source);
+				dx = seg;
+			}
+			else
+			{
+				y = view.getRoutingCenterY(source);
+				dy = seg;
+			}
+			
+			if (pt == null ||
+				pt.x < source.x ||
+				pt.x > source.x + source.width)
+			{
+				if (pt != null)
+				{
+					x = pt.x;
+					dy = Math.max(Math.abs(y - pt.y), dy);
+				}
+				else
+				{
+					if (dir == mxConstants.DIRECTION_NORTH)
+					{
+						y = source.y - 2 * dx;
+					}
+					else if (dir == mxConstants.DIRECTION_SOUTH)
+					{
+						y = source.y + source.height + 2 * dx;
+					}
+					else if (dir == mxConstants.DIRECTION_EAST)
+					{
+						x = source.x - 2 * dy;
+					}
+					else
+					{
+						x = source.x + source.width + 2 * dy;
+					}
+				}
+			}
+			else if (pt != null)
+			{
+				x = view.getRoutingCenterX(source);
+				dx = Math.max(Math.abs(x - pt.x), dy);
+				y = pt.y;
+				dy = 0;
+			}
+			
+			result.push(new mxPoint(x - dx, y - dy));
+			result.push(new mxPoint(x + dx, y + dy));
+		}
+	},
+	
+	/**
+	 * Function: ElbowConnector
+	 * 
+	 * Uses either <SideToSide> or <TopToBottom> depending on the horizontal
+	 * flag in the cell style. <SideToSide> is used if horizontal is true or
+	 * unspecified. See <EntityRelation> for a description of the
+	 * parameters.
+	 */
+	ElbowConnector: function (state, source, target, points, result)
+	{
+		var pt = (points != null && points.length > 0) ? points[0] : null;
+
+		var vertical = false;
+		var horizontal = false;
+		
+		if (source != null && target != null)
+		{
+			if (pt != null)
+			{
+				var left = Math.min(source.x, target.x);
+				var right = Math.max(source.x + source.width,
+					target.x + target.width);
+	
+				var top = Math.min(source.y, target.y);
+				var bottom = Math.max(source.y + source.height,
+					target.y + target.height);
+
+				pt = state.view.transformControlPoint(state, pt);
+					
+				vertical = pt.y < top || pt.y > bottom;
+				horizontal = pt.x < left || pt.x > right;
+			}
+			else
+			{
+				var left = Math.max(source.x, target.x);
+				var right = Math.min(source.x + source.width,
+					target.x + target.width);
+					
+				vertical = left == right;
+				
+				if (!vertical)
+				{
+					var top = Math.max(source.y, target.y);
+					var bottom = Math.min(source.y + source.height,
+						target.y + target.height);
+						
+					horizontal = top == bottom;
+				}
+			}
+		}
+
+		if (!horizontal && (vertical ||
+			state.style[mxConstants.STYLE_ELBOW] == mxConstants.ELBOW_VERTICAL))
+		{
+			mxEdgeStyle.TopToBottom(state, source, target, points, result);
+		}
+		else
+		{
+			mxEdgeStyle.SideToSide(state, source, target, points, result);
+		}
+	},
+
+	/**
+	 * Function: SideToSide
+	 * 
+	 * Implements a vertical elbow edge. See <EntityRelation> for a description
+	 * of the parameters.
+	 */
+	SideToSide: function (state, source, target, points, result)
+	{
+		var view = state.view;
+		var pt = (points != null && points.length > 0) ? points[0] : null;
+		var pts = state.absolutePoints;
+		var p0 = pts[0];
+		var pe = pts[pts.length-1];
+		
+		if (pt != null)
+		{
+			pt = view.transformControlPoint(state, pt);
+		}
+		
+		if (p0 != null)
+		{
+			source = new mxCellState();
+			source.x = p0.x;
+			source.y = p0.y;
+		}
+		
+		if (pe != null)
+		{
+			target = new mxCellState();
+			target.x = pe.x;
+			target.y = pe.y;
+		}
+		
+		if (source != null && target != null)
+		{
+			var l = Math.max(source.x, target.x);
+			var r = Math.min(source.x + source.width,
+							 target.x + target.width);
+	
+			var x = (pt != null) ? pt.x : Math.round(r + (l - r) / 2);
+	
+			var y1 = view.getRoutingCenterY(source);
+			var y2 = view.getRoutingCenterY(target);
+	
+			if (pt != null)
+			{
+				if (pt.y >= source.y && pt.y <= source.y + source.height)
+				{
+					y1 = pt.y;
+				}
+				
+				if (pt.y >= target.y && pt.y <= target.y + target.height)
+				{
+					y2 = pt.y;
+				}
+			}
+			
+			if (!mxUtils.contains(target, x, y1) &&
+				!mxUtils.contains(source, x, y1))
+			{
+				result.push(new mxPoint(x,  y1));
+			}
+	
+			if (!mxUtils.contains(target, x, y2) &&
+				!mxUtils.contains(source, x, y2))
+			{
+				result.push(new mxPoint(x, y2));
+			}
+	
+			if (result.length == 1)
+			{
+				if (pt != null)
+				{
+					if (!mxUtils.contains(target, x, pt.y) &&
+						!mxUtils.contains(source, x, pt.y))
+					{
+						result.push(new mxPoint(x, pt.y));
+					}
+				}
+				else
+				{	
+					var t = Math.max(source.y, target.y);
+					var b = Math.min(source.y + source.height,
+							 target.y + target.height);
+						 
+					result.push(new mxPoint(x, t + (b - t) / 2));
+				}
+			}
+		}
+	},
+
+	/**
+	 * Function: TopToBottom
+	 * 
+	 * Implements a horizontal elbow edge. See <EntityRelation> for a
+	 * description of the parameters.
+	 */
+	TopToBottom: function(state, source, target, points, result)
+	{
+		var view = state.view;
+		var pt = (points != null && points.length > 0) ? points[0] : null;
+		var pts = state.absolutePoints;
+		var p0 = pts[0];
+		var pe = pts[pts.length-1];
+		
+		if (pt != null)
+		{
+			pt = view.transformControlPoint(state, pt);
+		}
+		
+		if (p0 != null)
+		{
+			source = new mxCellState();
+			source.x = p0.x;
+			source.y = p0.y;
+		}
+		
+		if (pe != null)
+		{
+			target = new mxCellState();
+			target.x = pe.x;
+			target.y = pe.y;
+		}
+
+		if (source != null && target != null)
+		{
+			var t = Math.max(source.y, target.y);
+			var b = Math.min(source.y + source.height,
+							 target.y + target.height);
+	
+			var x = view.getRoutingCenterX(source);
+			
+			if (pt != null &&
+				pt.x >= source.x &&
+				pt.x <= source.x + source.width)
+			{
+				x = pt.x;
+			}
+			
+			var y = (pt != null) ? pt.y : Math.round(b + (t - b) / 2);
+			
+			if (!mxUtils.contains(target, x, y) &&
+				!mxUtils.contains(source, x, y))
+			{
+				result.push(new mxPoint(x, y));						
+			}
+			
+			if (pt != null &&
+				pt.x >= target.x &&
+				pt.x <= target.x + target.width)
+			{
+				x = pt.x;
+			}
+			else
+			{
+				x = view.getRoutingCenterX(target);
+			}
+			
+			if (!mxUtils.contains(target, x, y) &&
+				!mxUtils.contains(source, x, y))
+			{
+				result.push(new mxPoint(x, y));						
+			}
+			
+			if (result.length == 1)
+			{
+				if (pt != null && result.length == 1)
+				{
+					if (!mxUtils.contains(target, pt.x, y) &&
+						!mxUtils.contains(source, pt.x, y))
+					{
+						result.push(new mxPoint(pt.x, y));
+					}
+				}
+				else
+				{
+					var l = Math.max(source.x, target.x);
+					var r = Math.min(source.x + source.width,
+							 target.x + target.width);
+						 
+					result.push(new mxPoint(l + (r - l) / 2, y));
+				}
+			}
+		}
+	},
+
+	/**
+	 * Function: SegmentConnector
+	 * 
+	 * Implements an orthogonal edge style. Use <mxEdgeSegmentHandler>
+	 * as an interactive handler for this style.
+	 */
+	SegmentConnector: function(state, source, target, hints, result)
+	{
+		// Creates array of all way- and terminalpoints
+		var pts = state.absolutePoints;
+		var tol = Math.max(1, state.view.scale);
+		
+		// Whether the first segment outgoing from the source end is horizontal
+		var lastPushed = (result.length > 0) ? result[0] : null;
+		var horizontal = true;
+		var hint = null;
+		
+		// Adds waypoints only if outside of tolerance
+		function pushPoint(pt)
+		{
+			if (lastPushed == null || Math.abs(lastPushed.x - pt.x) >= tol || Math.abs(lastPushed.y - pt.y) >= tol)
+			{
+				result.push(pt);
+				lastPushed = pt;
+			}
+			
+			return lastPushed;
+		};
+
+		// Adds the first point
+		var pt = pts[0];
+		
+		if (pt == null && source != null)
+		{
+			pt = new mxPoint(state.view.getRoutingCenterX(source), state.view.getRoutingCenterY(source));
+		}
+		else if (pt != null)
+		{
+			pt = pt.clone();
+		}
+		
+		pt.x = Math.round(pt.x);
+		pt.y = Math.round(pt.y);
+		
+		var lastInx = pts.length - 1;
+
+		// Adds the waypoints
+		if (hints != null && hints.length > 0)
+		{
+			// Converts all hints and removes nulls
+			var newHints = [];
+			
+			for (var i = 0; i < hints.length; i++)
+			{
+				var tmp = state.view.transformControlPoint(state, hints[i]);
+				
+				if (tmp != null)
+				{
+					tmp.x = Math.round(tmp.x);
+					tmp.y = Math.round(tmp.y);
+					newHints.push(tmp);
+				}
+			}
+			
+			if (newHints.length == 0)
+			{
+				return;
+			}
+			
+			hints = newHints;
+			
+			// Aligns source and target hint to fixed points
+			if (pt != null && hints[0] != null)
+			{
+				if (Math.abs(hints[0].x - pt.x) < tol)
+				{
+					hints[0].x = pt.x;
+				}
+				
+				if (Math.abs(hints[0].y - pt.y) < tol)
+				{
+					hints[0].y = pt.y;
+				}
+			}
+			
+			var pe = pts[lastInx];
+			
+			if (pe != null && hints[hints.length - 1] != null)
+			{
+				if (Math.abs(hints[hints.length - 1].x - pe.x) < tol)
+				{
+					hints[hints.length - 1].x = pe.x;
+				}
+				
+				if (Math.abs(hints[hints.length - 1].y - pe.y) < tol)
+				{
+					hints[hints.length - 1].y = pe.y;
+				}
+			}
+			
+			hint = hints[0];
+
+			var currentTerm = source;
+			var currentPt = pts[0];
+			var hozChan = false;
+			var vertChan = false;
+			var currentHint = hint;
+			
+			if (currentPt != null)
+			{
+				currentPt.x = Math.round(currentPt.x);
+				currentPt.y = Math.round(currentPt.y);
+				currentTerm = null;
+			}
+			
+			// Check for alignment with fixed points and with channels
+			// at source and target segments only
+			for (var i = 0; i < 2; i++)
+			{
+				var fixedVertAlign = currentPt != null && currentPt.x == currentHint.x;
+				var fixedHozAlign = currentPt != null && currentPt.y == currentHint.y;
+				
+				var inHozChan = currentTerm != null && (currentHint.y >= currentTerm.y &&
+						currentHint.y <= currentTerm.y + currentTerm.height);
+				var inVertChan = currentTerm != null && (currentHint.x >= currentTerm.x &&
+						currentHint.x <= currentTerm.x + currentTerm.width);
+
+				hozChan = fixedHozAlign || (currentPt == null && inHozChan);
+				vertChan = fixedVertAlign || (currentPt == null && inVertChan);
+				
+				// If the current hint falls in both the hor and vert channels in the case
+				// of a floating port, or if the hint is exactly co-incident with a 
+				// fixed point, ignore the source and try to work out the orientation
+				// from the target end
+				if (i==0 && ((hozChan && vertChan) || (fixedVertAlign && fixedHozAlign)))
+				{
+				}
+				else
+				{
+					if (currentPt != null && (!fixedHozAlign && !fixedVertAlign) && (inHozChan || inVertChan)) 
+					{
+						horizontal = inHozChan ? false : true;
+						break;
+					}
+			
+					if (vertChan || hozChan)
+					{
+						horizontal = hozChan;
+						
+						if (i == 1)
+						{
+							// Work back from target end
+							horizontal = hints.length % 2 == 0 ? hozChan : vertChan;
+						}
+	
+						break;
+					}
+				}
+				
+				currentTerm = target;
+				currentPt = pts[lastInx];
+				
+				if (currentPt != null)
+				{
+					currentPt.x = Math.round(currentPt.x);
+					currentPt.y = Math.round(currentPt.y);
+					currentTerm = null;
+				}
+				
+				currentHint = hints[hints.length - 1];
+				
+				if (fixedVertAlign && fixedHozAlign)
+				{
+					hints = hints.slice(1);
+				}
+			}
+
+			if (horizontal && ((pts[0] != null && pts[0].y != hint.y) ||
+				(pts[0] == null && source != null &&
+				(hint.y < source.y || hint.y > source.y + source.height))))
+			{
+				pushPoint(new mxPoint(pt.x, hint.y));
+			}
+			else if (!horizontal && ((pts[0] != null && pts[0].x != hint.x) ||
+					(pts[0] == null && source != null &&
+					(hint.x < source.x || hint.x > source.x + source.width))))
+			{
+				pushPoint(new mxPoint(hint.x, pt.y));
+			}
+			
+			if (horizontal)
+			{
+				pt.y = hint.y;
+			}
+			else
+			{
+				pt.x = hint.x;
+			}
+		
+			for (var i = 0; i < hints.length; i++)
+			{
+				horizontal = !horizontal;
+				hint = hints[i];
+				
+//				mxLog.show();
+//				mxLog.debug('hint', i, hint.x, hint.y);
+				
+				if (horizontal)
+				{
+					pt.y = hint.y;
+				}
+				else
+				{
+					pt.x = hint.x;
+				}
+		
+				pushPoint(pt.clone());
+			}
+		}
+		else
+		{
+			hint = pt;
+			// FIXME: First click in connect preview toggles orientation
+			horizontal = true;
+		}
+
+		// Adds the last point
+		pt = pts[lastInx];
+
+		if (pt == null && target != null)
+		{
+			pt = new mxPoint(state.view.getRoutingCenterX(target), state.view.getRoutingCenterY(target));
+		}
+		
+		if (pt != null)
+		{
+			pt.x = Math.round(pt.x);
+			pt.y = Math.round(pt.y);
+			
+			if (hint != null)
+			{
+				if (horizontal && ((pts[lastInx] != null && pts[lastInx].y != hint.y) ||
+					(pts[lastInx] == null && target != null &&
+					(hint.y < target.y || hint.y > target.y + target.height))))
+				{
+					pushPoint(new mxPoint(pt.x, hint.y));
+				}
+				else if (!horizontal && ((pts[lastInx] != null && pts[lastInx].x != hint.x) ||
+						(pts[lastInx] == null && target != null &&
+						(hint.x < target.x || hint.x > target.x + target.width))))
+				{
+					pushPoint(new mxPoint(hint.x, pt.y));
+				}
+			}
+		}
+		
+		// Removes bends inside the source terminal for floating ports
+		if (pts[0] == null && source != null)
+		{
+			while (result.length > 1 && result[1] != null &&
+				mxUtils.contains(source, result[1].x, result[1].y))
+			{
+				result.splice(1, 1);
+			}
+		}
+		
+		// Removes bends inside the target terminal
+		if (pts[lastInx] == null && target != null)
+		{
+			while (result.length > 1 && result[result.length - 1] != null &&
+				mxUtils.contains(target, result[result.length - 1].x, result[result.length - 1].y))
+			{
+				result.splice(result.length - 1, 1);
+			}
+		}
+		
+		// Removes last point if inside tolerance with end point
+		if (pe != null && result[result.length - 1] != null &&
+			Math.abs(pe.x - result[result.length - 1].x) < tol &&
+			Math.abs(pe.y - result[result.length - 1].y) < tol)
+		{
+			result.splice(result.length - 1, 1);
+			
+			// Lines up second last point in result with end point
+			if (result[result.length - 1] != null)
+			{
+				if (Math.abs(result[result.length - 1].x - pe.x) < tol)
+				{
+					result[result.length - 1].x = pe.x;
+				}
+				
+				if (Math.abs(result[result.length - 1].y - pe.y) < tol)
+				{
+					result[result.length - 1].y = pe.y;
+				}
+			}
+		}
+	},
+	
+	orthBuffer: 10,
+	
+	orthPointsFallback: true,
+
+	dirVectors: [ [ -1, 0 ],
+			[ 0, -1 ], [ 1, 0 ], [ 0, 1 ], [ -1, 0 ], [ 0, -1 ], [ 1, 0 ] ],
+
+	wayPoints1: [ [ 0, 0], [ 0, 0],  [ 0, 0], [ 0, 0], [ 0, 0],  [ 0, 0],
+	              [ 0, 0],  [ 0, 0], [ 0, 0],  [ 0, 0], [ 0, 0],  [ 0, 0] ],
+
+	routePatterns: [
+		[ [ 513, 2308, 2081, 2562 ], [ 513, 1090, 514, 2184, 2114, 2561 ],
+			[ 513, 1090, 514, 2564, 2184, 2562 ],
+			[ 513, 2308, 2561, 1090, 514, 2568, 2308 ] ],
+	[ [ 514, 1057, 513, 2308, 2081, 2562 ], [ 514, 2184, 2114, 2561 ],
+			[ 514, 2184, 2562, 1057, 513, 2564, 2184 ],
+			[ 514, 1057, 513, 2568, 2308, 2561 ] ],
+	[ [ 1090, 514, 1057, 513, 2308, 2081, 2562 ], [ 2114, 2561 ],
+			[ 1090, 2562, 1057, 513, 2564, 2184 ],
+			[ 1090, 514, 1057, 513, 2308, 2561, 2568 ] ],
+	[ [ 2081, 2562 ], [ 1057, 513, 1090, 514, 2184, 2114, 2561 ],
+			[ 1057, 513, 1090, 514, 2184, 2562, 2564 ],
+			[ 1057, 2561, 1090, 514, 2568, 2308 ] ] ],
+	
+	inlineRoutePatterns: [
+			[ null, [ 2114, 2568 ], null, null ],
+			[ null, [ 514, 2081, 2114, 2568 ] , null, null ],
+			[ null, [ 2114, 2561 ], null, null ],
+			[ [ 2081, 2562 ], [ 1057, 2114, 2568 ],
+					[ 2184, 2562 ],
+					null ] ],
+	vertexSeperations: [],
+
+	limits: [
+	       [ 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
+	       [ 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ],
+
+	LEFT_MASK: 32,
+
+	TOP_MASK: 64,
+
+	RIGHT_MASK: 128,
+
+	BOTTOM_MASK: 256,
+
+	LEFT: 1,
+
+	TOP: 2,
+
+	RIGHT: 4,
+
+	BOTTOM: 8,
+
+	// TODO remove magic numbers
+	SIDE_MASK: 480,
+	//mxEdgeStyle.LEFT_MASK | mxEdgeStyle.TOP_MASK | mxEdgeStyle.RIGHT_MASK
+	//| mxEdgeStyle.BOTTOM_MASK,
+
+	CENTER_MASK: 512,
+
+	SOURCE_MASK: 1024,
+
+	TARGET_MASK: 2048,
+
+	VERTEX_MASK: 3072,
+	// mxEdgeStyle.SOURCE_MASK | mxEdgeStyle.TARGET_MASK,
+	
+	getJettySize: function(state, source, target, points, isSource)
+	{
+		var value = mxUtils.getValue(state.style, (isSource) ? mxConstants.STYLE_SOURCE_JETTY_SIZE :
+			mxConstants.STYLE_TARGET_JETTY_SIZE, mxUtils.getValue(state.style,
+					mxConstants.STYLE_JETTY_SIZE, mxEdgeStyle.orthBuffer));
+		
+		if (value == 'auto')
+		{
+			// Computes the automatic jetty size
+			var type = mxUtils.getValue(state.style, (isSource) ? mxConstants.STYLE_STARTARROW : mxConstants.STYLE_ENDARROW, mxConstants.NONE);
+			
+			if (type != mxConstants.NONE)
+			{
+				var size = mxUtils.getNumber(state.style, (isSource) ? mxConstants.STYLE_STARTSIZE : mxConstants.STYLE_ENDSIZE, mxConstants.DEFAULT_MARKERSIZE);
+				value = Math.max(2, Math.ceil((size + mxEdgeStyle.orthBuffer) / mxEdgeStyle.orthBuffer)) * mxEdgeStyle.orthBuffer;
+			}
+			else
+			{
+				value = 2 * mxEdgeStyle.orthBuffer;
+			}
+		}
+		
+		return value;
+	},
+
+	/**
+	 * Function: OrthConnector
+	 * 
+	 * Implements a local orthogonal router between the given
+	 * cells.
+	 * 
+	 * Parameters:
+	 * 
+	 * state - <mxCellState> that represents the edge to be updated.
+	 * source - <mxCellState> that represents the source terminal.
+	 * target - <mxCellState> that represents the target terminal.
+	 * points - List of relative control points.
+	 * result - Array of <mxPoints> that represent the actual points of the
+	 * edge.
+	 * 
+	 */
+	OrthConnector: function(state, source, target, points, result)
+	{
+		var graph = state.view.graph;
+		var sourceEdge = source == null ? false : graph.getModel().isEdge(source.cell);
+		var targetEdge = target == null ? false : graph.getModel().isEdge(target.cell);
+
+		var pts = state.absolutePoints;
+		var p0 = pts[0];
+		var pe = pts[pts.length-1];
+
+		var sourceX = source != null ? source.x : p0.x;
+		var sourceY = source != null ? source.y : p0.y;
+		var sourceWidth = source != null ? source.width : 0;
+		var sourceHeight = source != null ? source.height : 0;
+		
+		var targetX = target != null ? target.x : pe.x;
+		var targetY = target != null ? target.y : pe.y;
+		var targetWidth = target != null ? target.width : 0;
+		var targetHeight = target != null ? target.height : 0;
+
+		var scaledSourceBuffer = state.view.scale * mxEdgeStyle.getJettySize(state, source, target, points, true);
+		var scaledTargetBuffer = state.view.scale * mxEdgeStyle.getJettySize(state, source, target, points, false);
+		
+		// Workaround for loop routing within buffer zone
+		if (source != null && target == source)
+		{
+			scaledTargetBuffer = Math.max(scaledSourceBuffer, scaledTargetBuffer);
+			scaledSourceBuffer = scaledTargetBuffer;
+		}
+		
+		var totalBuffer = scaledTargetBuffer + scaledSourceBuffer;
+		var tooShort = false;
+		
+		// Checks minimum distance for fixed points and falls back to segment connector
+		if (p0 != null && pe != null)
+		{
+			var dx = pe.x - p0.x;
+			var dy = pe.y - p0.y;
+			
+			tooShort = dx * dx + dy * dy < totalBuffer * totalBuffer;
+		}
+
+		if (tooShort || (mxEdgeStyle.orthPointsFallback && (points != null &&
+			points.length > 0)) || sourceEdge || targetEdge)
+		{
+			mxEdgeStyle.SegmentConnector(state, source, target, points, result);
+			
+			return;
+		}
+
+		// Determine the side(s) of the source and target vertices
+		// that the edge may connect to
+		// portConstraint [source, target]
+		var portConstraint = [mxConstants.DIRECTION_MASK_ALL, mxConstants.DIRECTION_MASK_ALL];
+		var rotation = 0;
+		
+		if (source != null)
+		{
+			portConstraint[0] = mxUtils.getPortConstraints(source, state, true, 
+					mxConstants.DIRECTION_MASK_ALL);
+			rotation = mxUtils.getValue(source.style, mxConstants.STYLE_ROTATION, 0);
+			
+			if (rotation != 0)
+			{
+				var newRect = mxUtils.getBoundingBox(new mxRectangle(sourceX, sourceY, sourceWidth, sourceHeight), rotation);
+				sourceX = newRect.x; 
+				sourceY = newRect.y;
+				sourceWidth = newRect.width;
+				sourceHeight = newRect.height;
+			}
+		}
+
+		if (target != null)
+		{
+			portConstraint[1] = mxUtils.getPortConstraints(target, state, false,
+				mxConstants.DIRECTION_MASK_ALL);
+			rotation = mxUtils.getValue(target.style, mxConstants.STYLE_ROTATION, 0);
+
+			if (rotation != 0)
+			{
+				var newRect = mxUtils.getBoundingBox(new mxRectangle(targetX, targetY, targetWidth, targetHeight), rotation);
+				targetX = newRect.x;
+				targetY = newRect.y;
+				targetWidth = newRect.width;
+				targetHeight = newRect.height;
+			}
+		}
+
+		// Avoids floating point number errors
+		sourceX = Math.round(sourceX * 10) / 10;
+		sourceY = Math.round(sourceY * 10) / 10;
+		sourceWidth = Math.round(sourceWidth * 10) / 10;
+		sourceHeight = Math.round(sourceHeight * 10) / 10;
+		
+		targetX = Math.round(targetX * 10) / 10;
+		targetY = Math.round(targetY * 10) / 10;
+		targetWidth = Math.round(targetWidth * 10) / 10;
+		targetHeight = Math.round(targetHeight * 10) / 10;
+		
+		var dir = [0, 0];
+
+		// Work out which faces of the vertices present against each other
+		// in a way that would allow a 3-segment connection if port constraints
+		// permitted.
+		// geo -> [source, target] [x, y, width, height]
+		var geo = [ [sourceX, sourceY, sourceWidth, sourceHeight] ,
+		            [targetX, targetY, targetWidth, targetHeight] ];
+		var buffer = [scaledSourceBuffer, scaledTargetBuffer];
+
+		for (var i = 0; i < 2; i++)
+		{
+			mxEdgeStyle.limits[i][1] = geo[i][0] - buffer[i];
+			mxEdgeStyle.limits[i][2] = geo[i][1] - buffer[i];
+			mxEdgeStyle.limits[i][4] = geo[i][0] + geo[i][2] + buffer[i];
+			mxEdgeStyle.limits[i][8] = geo[i][1] + geo[i][3] + buffer[i];
+		}
+		
+		// Work out which quad the target is in
+		var sourceCenX = geo[0][0] + geo[0][2] / 2.0;
+		var sourceCenY = geo[0][1] + geo[0][3] / 2.0;
+		var targetCenX = geo[1][0] + geo[1][2] / 2.0;
+		var targetCenY = geo[1][1] + geo[1][3] / 2.0;
+		
+		var dx = sourceCenX - targetCenX;
+		var dy = sourceCenY - targetCenY;
+
+		var quad = 0;
+
+		if (dx < 0)
+		{
+			if (dy < 0)
+			{
+				quad = 2;
+			}
+			else
+			{
+				quad = 1;
+			}
+		}
+		else
+		{
+			if (dy <= 0)
+			{
+				quad = 3;
+				
+				// Special case on x = 0 and negative y
+				if (dx == 0)
+				{
+					quad = 2;
+				}
+			}
+		}
+
+		// Check for connection constraints
+		var currentTerm = null;
+		
+		if (source != null)
+		{
+			currentTerm = p0;
+		}
+
+		var constraint = [ [0.5, 0.5] , [0.5, 0.5] ];
+
+		for (var i = 0; i < 2; i++)
+		{
+			if (currentTerm != null)
+			{
+				constraint[i][0] = (currentTerm.x - geo[i][0]) / geo[i][2];
+				
+				if (Math.abs(currentTerm.x - geo[i][0]) <= 1)
+				{
+					dir[i] = mxConstants.DIRECTION_MASK_WEST;
+				}
+				else if (Math.abs(currentTerm.x - geo[i][0] - geo[i][2]) <= 1)
+				{
+					dir[i] = mxConstants.DIRECTION_MASK_EAST;
+				}
+
+				constraint[i][1] = (currentTerm.y - geo[i][1]) / geo[i][3];
+
+				if (Math.abs(currentTerm.y - geo[i][1]) <= 1)
+				{
+					dir[i] = mxConstants.DIRECTION_MASK_NORTH;
+				}
+				else if (Math.abs(currentTerm.y - geo[i][1] - geo[i][3]) <= 1)
+				{
+					dir[i] = mxConstants.DIRECTION_MASK_SOUTH;
+				}
+			}
+
+			currentTerm = null;
+			
+			if (target != null)
+			{
+				currentTerm = pe;
+			}
+		}
+
+		var sourceTopDist = geo[0][1] - (geo[1][1] + geo[1][3]);
+		var sourceLeftDist = geo[0][0] - (geo[1][0] + geo[1][2]);
+		var sourceBottomDist = geo[1][1] - (geo[0][1] + geo[0][3]);
+		var sourceRightDist = geo[1][0] - (geo[0][0] + geo[0][2]);
+
+		mxEdgeStyle.vertexSeperations[1] = Math.max(sourceLeftDist - totalBuffer, 0);
+		mxEdgeStyle.vertexSeperations[2] = Math.max(sourceTopDist - totalBuffer, 0);
+		mxEdgeStyle.vertexSeperations[4] = Math.max(sourceBottomDist - totalBuffer, 0);
+		mxEdgeStyle.vertexSeperations[3] = Math.max(sourceRightDist - totalBuffer, 0);
+				
+		//==============================================================
+		// Start of source and target direction determination
+
+		// Work through the preferred orientations by relative positioning
+		// of the vertices and list them in preferred and available order
+		
+		var dirPref = [];
+		var horPref = [];
+		var vertPref = [];
+
+		horPref[0] = (sourceLeftDist >= sourceRightDist) ? mxConstants.DIRECTION_MASK_WEST
+				: mxConstants.DIRECTION_MASK_EAST;
+		vertPref[0] = (sourceTopDist >= sourceBottomDist) ? mxConstants.DIRECTION_MASK_NORTH
+				: mxConstants.DIRECTION_MASK_SOUTH;
+
+		horPref[1] = mxUtils.reversePortConstraints(horPref[0]);
+		vertPref[1] = mxUtils.reversePortConstraints(vertPref[0]);
+		
+		var preferredHorizDist = sourceLeftDist >= sourceRightDist ? sourceLeftDist
+				: sourceRightDist;
+		var preferredVertDist = sourceTopDist >= sourceBottomDist ? sourceTopDist
+				: sourceBottomDist;
+
+		var prefOrdering = [ [0, 0] , [0, 0] ];
+		var preferredOrderSet = false;
+
+		// If the preferred port isn't available, switch it
+		for (var i = 0; i < 2; i++)
+		{
+			if (dir[i] != 0x0)
+			{
+				continue;
+			}
+
+			if ((horPref[i] & portConstraint[i]) == 0)
+			{
+				horPref[i] = mxUtils.reversePortConstraints(horPref[i]);
+			}
+
+			if ((vertPref[i] & portConstraint[i]) == 0)
+			{
+				vertPref[i] = mxUtils
+						.reversePortConstraints(vertPref[i]);
+			}
+
+			prefOrdering[i][0] = vertPref[i];
+			prefOrdering[i][1] = horPref[i];
+		}
+
+		if (preferredVertDist > 0
+				&& preferredHorizDist > 0)
+		{
+			// Possibility of two segment edge connection
+			if (((horPref[0] & portConstraint[0]) > 0)
+					&& ((vertPref[1] & portConstraint[1]) > 0))
+			{
+				prefOrdering[0][0] = horPref[0];
+				prefOrdering[0][1] = vertPref[0];
+				prefOrdering[1][0] = vertPref[1];
+				prefOrdering[1][1] = horPref[1];
+				preferredOrderSet = true;
+			}
+			else if (((vertPref[0] & portConstraint[0]) > 0)
+					&& ((horPref[1] & portConstraint[1]) > 0))
+			{
+				prefOrdering[0][0] = vertPref[0];
+				prefOrdering[0][1] = horPref[0];
+				prefOrdering[1][0] = horPref[1];
+				prefOrdering[1][1] = vertPref[1];
+				preferredOrderSet = true;
+			}
+		}
+		
+		if (preferredVertDist > 0 && !preferredOrderSet)
+		{
+			prefOrdering[0][0] = vertPref[0];
+			prefOrdering[0][1] = horPref[0];
+			prefOrdering[1][0] = vertPref[1];
+			prefOrdering[1][1] = horPref[1];
+			preferredOrderSet = true;
+
+		}
+		
+		if (preferredHorizDist > 0 && !preferredOrderSet)
+		{
+			prefOrdering[0][0] = horPref[0];
+			prefOrdering[0][1] = vertPref[0];
+			prefOrdering[1][0] = horPref[1];
+			prefOrdering[1][1] = vertPref[1];
+			preferredOrderSet = true;
+		}
+
+		// The source and target prefs are now an ordered list of
+		// the preferred port selections
+		// It the list can contain gaps, compact it
+
+		for (var i = 0; i < 2; i++)
+		{
+			if (dir[i] != 0x0)
+			{
+				continue;
+			}
+
+			if ((prefOrdering[i][0] & portConstraint[i]) == 0)
+			{
+				prefOrdering[i][0] = prefOrdering[i][1];
+			}
+
+			dirPref[i] = prefOrdering[i][0] & portConstraint[i];
+			dirPref[i] |= (prefOrdering[i][1] & portConstraint[i]) << 8;
+			dirPref[i] |= (prefOrdering[1 - i][i] & portConstraint[i]) << 16;
+			dirPref[i] |= (prefOrdering[1 - i][1 - i] & portConstraint[i]) << 24;
+
+			if ((dirPref[i] & 0xF) == 0)
+			{
+				dirPref[i] = dirPref[i] << 8;
+			}
+			
+			if ((dirPref[i] & 0xF00) == 0)
+			{
+				dirPref[i] = (dirPref[i] & 0xF) | dirPref[i] >> 8;
+			}
+			
+			if ((dirPref[i] & 0xF0000) == 0)
+			{
+				dirPref[i] = (dirPref[i] & 0xFFFF)
+						| ((dirPref[i] & 0xF000000) >> 8);
+			}
+
+			dir[i] = dirPref[i] & 0xF;
+
+			if (portConstraint[i] == mxConstants.DIRECTION_MASK_WEST
+					|| portConstraint[i] == mxConstants.DIRECTION_MASK_NORTH
+					|| portConstraint[i] == mxConstants.DIRECTION_MASK_EAST
+					|| portConstraint[i] == mxConstants.DIRECTION_MASK_SOUTH)
+			{
+				dir[i] = portConstraint[i];
+			}
+		}
+
+		//==============================================================
+		// End of source and target direction determination
+
+		var sourceIndex = dir[0] == mxConstants.DIRECTION_MASK_EAST ? 3
+				: dir[0];
+		var targetIndex = dir[1] == mxConstants.DIRECTION_MASK_EAST ? 3
+				: dir[1];
+
+		sourceIndex -= quad;
+		targetIndex -= quad;
+
+		if (sourceIndex < 1)
+		{
+			sourceIndex += 4;
+		}
+		
+		if (targetIndex < 1)
+		{
+			targetIndex += 4;
+		}
+
+		var routePattern = mxEdgeStyle.routePatterns[sourceIndex - 1][targetIndex - 1];
+
+		mxEdgeStyle.wayPoints1[0][0] = geo[0][0];
+		mxEdgeStyle.wayPoints1[0][1] = geo[0][1];
+
+		switch (dir[0])
+		{
+			case mxConstants.DIRECTION_MASK_WEST:
+				mxEdgeStyle.wayPoints1[0][0] -= scaledSourceBuffer;
+				mxEdgeStyle.wayPoints1[0][1] += constraint[0][1] * geo[0][3];
+				break;
+			case mxConstants.DIRECTION_MASK_SOUTH:
+				mxEdgeStyle.wayPoints1[0][0] += constraint[0][0] * geo[0][2];
+				mxEdgeStyle.wayPoints1[0][1] += geo[0][3] + scaledSourceBuffer;
+				break;
+			case mxConstants.DIRECTION_MASK_EAST:
+				mxEdgeStyle.wayPoints1[0][0] += geo[0][2] + scaledSourceBuffer;
+				mxEdgeStyle.wayPoints1[0][1] += constraint[0][1] * geo[0][3];
+				break;
+			case mxConstants.DIRECTION_MASK_NORTH:
+				mxEdgeStyle.wayPoints1[0][0] += constraint[0][0] * geo[0][2];
+				mxEdgeStyle.wayPoints1[0][1] -= scaledSourceBuffer;
+				break;
+		}
+
+		var currentIndex = 0;
+
+		// Orientation, 0 horizontal, 1 vertical
+		var lastOrientation = (dir[0] & (mxConstants.DIRECTION_MASK_EAST | mxConstants.DIRECTION_MASK_WEST)) > 0 ? 0
+				: 1;
+		var initialOrientation = lastOrientation;
+		var currentOrientation = 0;
+
+		for (var i = 0; i < routePattern.length; i++)
+		{
+			var nextDirection = routePattern[i] & 0xF;
+
+			// Rotate the index of this direction by the quad
+			// to get the real direction
+			var directionIndex = nextDirection == mxConstants.DIRECTION_MASK_EAST ? 3
+					: nextDirection;
+
+			directionIndex += quad;
+
+			if (directionIndex > 4)
+			{
+				directionIndex -= 4;
+			}
+
+			var direction = mxEdgeStyle.dirVectors[directionIndex - 1];
+
+			currentOrientation = (directionIndex % 2 > 0) ? 0 : 1;
+			// Only update the current index if the point moved
+			// in the direction of the current segment move,
+			// otherwise the same point is moved until there is 
+			// a segment direction change
+			if (currentOrientation != lastOrientation)
+			{
+				currentIndex++;
+				// Copy the previous way point into the new one
+				// We can't base the new position on index - 1
+				// because sometime elbows turn out not to exist,
+				// then we'd have to rewind.
+				mxEdgeStyle.wayPoints1[currentIndex][0] = mxEdgeStyle.wayPoints1[currentIndex - 1][0];
+				mxEdgeStyle.wayPoints1[currentIndex][1] = mxEdgeStyle.wayPoints1[currentIndex - 1][1];
+			}
+
+			var tar = (routePattern[i] & mxEdgeStyle.TARGET_MASK) > 0;
+			var sou = (routePattern[i] & mxEdgeStyle.SOURCE_MASK) > 0;
+			var side = (routePattern[i] & mxEdgeStyle.SIDE_MASK) >> 5;
+			side = side << quad;
+
+			if (side > 0xF)
+			{
+				side = side >> 4;
+			}
+
+			var center = (routePattern[i] & mxEdgeStyle.CENTER_MASK) > 0;
+
+			if ((sou || tar) && side < 9)
+			{
+				var limit = 0;
+				var souTar = sou ? 0 : 1;
+
+				if (center && currentOrientation == 0)
+				{
+					limit = geo[souTar][0] + constraint[souTar][0] * geo[souTar][2];
+				}
+				else if (center)
+				{
+					limit = geo[souTar][1] + constraint[souTar][1] * geo[souTar][3];
+				}
+				else
+				{
+					limit = mxEdgeStyle.limits[souTar][side];
+				}
+				
+				if (currentOrientation == 0)
+				{
+					var lastX = mxEdgeStyle.wayPoints1[currentIndex][0];
+					var deltaX = (limit - lastX) * direction[0];
+
+					if (deltaX > 0)
+					{
+						mxEdgeStyle.wayPoints1[currentIndex][0] += direction[0]
+								* deltaX;
+					}
+				}
+				else
+				{
+					var lastY = mxEdgeStyle.wayPoints1[currentIndex][1];
+					var deltaY = (limit - lastY) * direction[1];
+
+					if (deltaY > 0)
+					{
+						mxEdgeStyle.wayPoints1[currentIndex][1] += direction[1]
+								* deltaY;
+					}
+				}
+			}
+
+			else if (center)
+			{
+				// Which center we're travelling to depend on the current direction
+				mxEdgeStyle.wayPoints1[currentIndex][0] += direction[0]
+						* Math.abs(mxEdgeStyle.vertexSeperations[directionIndex] / 2);
+				mxEdgeStyle.wayPoints1[currentIndex][1] += direction[1]
+						* Math.abs(mxEdgeStyle.vertexSeperations[directionIndex] / 2);
+			}
+
+			if (currentIndex > 0
+					&& mxEdgeStyle.wayPoints1[currentIndex][currentOrientation] == mxEdgeStyle.wayPoints1[currentIndex - 1][currentOrientation])
+			{
+				currentIndex--;
+			}
+			else
+			{
+				lastOrientation = currentOrientation;
+			}
+		}
+
+		for (var i = 0; i <= currentIndex; i++)
+		{
+			if (i == currentIndex)
+			{
+				// Last point can cause last segment to be in
+				// same direction as jetty/approach. If so,
+				// check the number of points is consistent
+				// with the relative orientation of source and target
+				// jx. Same orientation requires an even
+				// number of turns (points), different requires
+				// odd.
+				var targetOrientation = (dir[1] & (mxConstants.DIRECTION_MASK_EAST | mxConstants.DIRECTION_MASK_WEST)) > 0 ? 0
+						: 1;
+				var sameOrient = targetOrientation == initialOrientation ? 0 : 1;
+
+				// (currentIndex + 1) % 2 is 0 for even number of points,
+				// 1 for odd
+				if (sameOrient != (currentIndex + 1) % 2)
+				{
+					// The last point isn't required
+					break;
+				}
+			}
+			
+			result.push(new mxPoint(Math.round(mxEdgeStyle.wayPoints1[i][0]), Math.round(mxEdgeStyle.wayPoints1[i][1])));
+		}
+		
+		// Removes duplicates
+		var index = 1;
+		
+		while (index < result.length)
+		{
+			if (result[index - 1] == null || result[index] == null ||
+				result[index - 1].x != result[index].x ||
+				result[index - 1].y != result[index].y)
+			{
+				index++;
+			}
+			else
+			{
+				result.splice(index, 1);
+			}
+		}
+	},
+	
+	getRoutePattern: function(dir, quad, dx, dy)
+	{
+		var sourceIndex = dir[0] == mxConstants.DIRECTION_MASK_EAST ? 3
+				: dir[0];
+		var targetIndex = dir[1] == mxConstants.DIRECTION_MASK_EAST ? 3
+				: dir[1];
+
+		sourceIndex -= quad;
+		targetIndex -= quad;
+
+		if (sourceIndex < 1)
+		{
+			sourceIndex += 4;
+		}
+		if (targetIndex < 1)
+		{
+			targetIndex += 4;
+		}
+
+		var result = routePatterns[sourceIndex - 1][targetIndex - 1];
+
+		if (dx == 0 || dy == 0)
+		{
+			if (inlineRoutePatterns[sourceIndex - 1][targetIndex - 1] != null)
+			{
+				result = inlineRoutePatterns[sourceIndex - 1][targetIndex - 1];
+			}
+		}
+
+		return result;
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/view/mxGraph.js b/airavata-kubernetes/web-console/src/assets/js/view/mxGraph.js
new file mode 100644
index 0000000..6d2ab86
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/view/mxGraph.js
@@ -0,0 +1,12768 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxGraph
+ *
+ * Extends <mxEventSource> to implement a graph component for
+ * the browser. This is the main class of the package. To activate
+ * panning and connections use <setPanning> and <setConnectable>.
+ * For rubberband selection you must create a new instance of
+ * <mxRubberband>. The following listeners are added to
+ * <mouseListeners> by default:
+ * 
+ * - <tooltipHandler>: <mxTooltipHandler> that displays tooltips
+ * - <panningHandler>: <mxPanningHandler> for panning and popup menus
+ * - <connectionHandler>: <mxConnectionHandler> for creating connections
+ * - <graphHandler>: <mxGraphHandler> for moving and cloning cells
+ * 
+ * These listeners will be called in the above order if they are enabled.
+ *
+ * Background Images:
+ * 
+ * To display a background image, set the image, image width and
+ * image height using <setBackgroundImage>. If one of the
+ * above values has changed then the <view>'s <mxGraphView.validate>
+ * should be invoked.
+ * 
+ * Cell Images:
+ * 
+ * To use images in cells, a shape must be specified in the default
+ * vertex style (or any named style). Possible shapes are
+ * <mxConstants.SHAPE_IMAGE> and <mxConstants.SHAPE_LABEL>.
+ * The code to change the shape used in the default vertex style,
+ * the following code is used:
+ * 
+ * (code)
+ * var style = graph.getStylesheet().getDefaultVertexStyle();
+ * style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_IMAGE;
+ * (end)
+ * 
+ * For the default vertex style, the image to be displayed can be
+ * specified in a cell's style using the <mxConstants.STYLE_IMAGE>
+ * key and the image URL as a value, for example:
+ * 
+ * (code)
+ * image=http://www.example.com/image.gif
+ * (end)
+ * 
+ * For a named style, the the stylename must be the first element
+ * of the cell style:
+ * 
+ * (code)
+ * stylename;image=http://www.example.com/image.gif
+ * (end)
+ * 
+ * A cell style can have any number of key=value pairs added, divided
+ * by a semicolon as follows:
+ * 
+ * (code)
+ * [stylename;|key=value;]
+ * (end)
+ *
+ * Labels:
+ * 
+ * The cell labels are defined by <getLabel> which uses <convertValueToString>
+ * if <labelsVisible> is true. If a label must be rendered as HTML markup, then
+ * <isHtmlLabel> should return true for the respective cell. If all labels
+ * contain HTML markup, <htmlLabels> can be set to true. NOTE: Enabling HTML
+ * labels carries a possible security risk (see the section on security in
+ * the manual).
+ * 
+ * If wrapping is needed for a label, then <isHtmlLabel> and <isWrapping> must
+ * return true for the cell whose label should be wrapped. See <isWrapping> for
+ * an example.
+ * 
+ * If clipping is needed to keep the rendering of a HTML label inside the
+ * bounds of its vertex, then <isClipping> should return true for the
+ * respective cell.
+ * 
+ * By default, edge labels are movable and vertex labels are fixed. This can be
+ * changed by setting <edgeLabelsMovable> and <vertexLabelsMovable>, or by
+ * overriding <isLabelMovable>.
+ *
+ * In-place Editing:
+ * 
+ * In-place editing is started with a doubleclick or by typing F2.
+ * Programmatically, <edit> is used to check if the cell is editable
+ * (<isCellEditable>) and call <startEditingAtCell>, which invokes
+ * <mxCellEditor.startEditing>. The editor uses the value returned
+ * by <getEditingValue> as the editing value.
+ * 
+ * After in-place editing, <labelChanged> is called, which invokes
+ * <mxGraphModel.setValue>, which in turn calls
+ * <mxGraphModel.valueForCellChanged> via <mxValueChange>.
+ * 
+ * The event that triggers in-place editing is passed through to the
+ * <cellEditor>, which may take special actions depending on the type of the
+ * event or mouse location, and is also passed to <getEditingValue>. The event
+ * is then passed back to the event processing functions which can perform
+ * specific actions based on the trigger event.
+ * 
+ * Tooltips:
+ * 
+ * Tooltips are implemented by <getTooltip>, which calls <getTooltipForCell>
+ * if a cell is under the mousepointer. The default implementation checks if
+ * the cell has a getTooltip function and calls it if it exists. Hence, in order
+ * to provide custom tooltips, the cell must provide a getTooltip function, or 
+ * one of the two above functions must be overridden.
+ * 
+ * Typically, for custom cell tooltips, the latter function is overridden as
+ * follows:
+ * 
+ * (code)
+ * graph.getTooltipForCell = function(cell)
+ * {
+ *   var label = this.convertValueToString(cell);
+ *   return 'Tooltip for '+label;
+ * }
+ * (end)
+ * 
+ * When using a config file, the function is overridden in the mxGraph section
+ * using the following entry:
+ * 
+ * (code)
+ * <add as="getTooltipForCell"><![CDATA[
+ *   function(cell)
+ *   {
+ *     var label = this.convertValueToString(cell);
+ *     return 'Tooltip for '+label;
+ *   }
+ * ]]></add>
+ * (end)
+ * 
+ * "this" refers to the graph in the implementation, so for example to check if 
+ * a cell is an edge, you use this.getModel().isEdge(cell)
+ *
+ * For replacing the default implementation of <getTooltipForCell> (rather than 
+ * replacing the function on a specific instance), the following code should be 
+ * used after loading the JavaScript files, but before creating a new mxGraph 
+ * instance using <mxGraph>:
+ * 
+ * (code)
+ * mxGraph.prototype.getTooltipForCell = function(cell)
+ * {
+ *   var label = this.convertValueToString(cell);
+ *   return 'Tooltip for '+label;
+ * }
+ * (end)
+ * 
+ * Shapes & Styles:
+ * 
+ * The implementation of new shapes is demonstrated in the examples. We'll assume
+ * that we have implemented a custom shape with the name BoxShape which we want
+ * to use for drawing vertices. To use this shape, it must first be registered in
+ * the cell renderer as follows:
+ * 
+ * (code)
+ * mxCellRenderer.registerShape('box', BoxShape);
+ * (end)
+ * 
+ * The code registers the BoxShape constructor under the name box in the cell
+ * renderer of the graph. The shape can now be referenced using the shape-key in
+ * a style definition. (The cell renderer contains a set of additional shapes,
+ * namely one for each constant with a SHAPE-prefix in <mxConstants>.)
+ *
+ * Styles are a collection of key, value pairs and a stylesheet is a collection
+ * of named styles. The names are referenced by the cellstyle, which is stored
+ * in <mxCell.style> with the following format: [stylename;|key=value;]. The
+ * string is resolved to a collection of key, value pairs, where the keys are
+ * overridden with the values in the string.
+ *
+ * When introducing a new shape, the name under which the shape is registered
+ * must be used in the stylesheet. There are three ways of doing this:
+ * 
+ *   - By changing the default style, so that all vertices will use the new
+ * 		shape
+ *   - By defining a new style, so that only vertices with the respective
+ * 		cellstyle will use the new shape
+ *   - By using shape=box in the cellstyle's optional list of key, value pairs
+ * 		to be overridden
+ *
+ * In the first case, the code to fetch and modify the default style for
+ * vertices is as follows:
+ * 
+ * (code)
+ * var style = graph.getStylesheet().getDefaultVertexStyle();
+ * style[mxConstants.STYLE_SHAPE] = 'box';
+ * (end)
+ * 
+ * The code takes the default vertex style, which is used for all vertices that
+ * do not have a specific cellstyle, and modifies the value for the shape-key
+ * in-place to use the new BoxShape for drawing vertices. This is done by
+ * assigning the box value in the second line, which refers to the name of the
+ * BoxShape in the cell renderer.
+ * 
+ * In the second case, a collection of key, value pairs is created and then
+ * added to the stylesheet under a new name. In order to distinguish the
+ * shapename and the stylename we'll use boxstyle for the stylename:
+ * 
+ * (code)
+ * var style = new Object();
+ * style[mxConstants.STYLE_SHAPE] = 'box';
+ * style[mxConstants.STYLE_STROKECOLOR] = '#000000';
+ * style[mxConstants.STYLE_FONTCOLOR] = '#000000';
+ * graph.getStylesheet().putCellStyle('boxstyle', style);
+ * (end)
+ * 
+ * The code adds a new style with the name boxstyle to the stylesheet. To use
+ * this style with a cell, it must be referenced from the cellstyle as follows:
+ * 
+ * (code)
+ * var vertex = graph.insertVertex(parent, null, 'Hello, World!', 20, 20, 80, 20,
+ * 				'boxstyle');
+ * (end)
+ * 
+ * To summarize, each new shape must be registered in the <mxCellRenderer> with
+ * a unique name. That name is then used as the value of the shape-key in a
+ * default or custom style. If there are multiple custom shapes, then there
+ * should be a separate style for each shape.
+ * 
+ * Inheriting Styles:
+ * 
+ * For fill-, stroke-, gradient- and indicatorColors special keywords can be
+ * used. The inherit keyword for one of these colors will inherit the color
+ * for the same key from the parent cell. The swimlane keyword does the same,
+ * but inherits from the nearest swimlane in the ancestor hierarchy. Finally,
+ * the indicated keyword will use the color of the indicator as the color for
+ * the given key.
+ * 
+ * Scrollbars:
+ * 
+ * The <containers> overflow CSS property defines if scrollbars are used to
+ * display the graph. For values of 'auto' or 'scroll', the scrollbars will
+ * be shown. Note that the <resizeContainer> flag is normally not used
+ * together with scrollbars, as it will resize the container to match the
+ * size of the graph after each change.
+ * 
+ * Multiplicities and Validation:
+ * 
+ * To control the possible connections in mxGraph, <getEdgeValidationError> is
+ * used. The default implementation of the function uses <multiplicities>,
+ * which is an array of <mxMultiplicity>. Using this class allows to establish
+ * simple multiplicities, which are enforced by the graph.
+ * 
+ * The <mxMultiplicity> uses <mxCell.is> to determine for which terminals it
+ * applies. The default implementation of <mxCell.is> works with DOM nodes (XML
+ * nodes) and checks if the given type parameter matches the nodeName of the
+ * node (case insensitive). Optionally, an attributename and value can be
+ * specified which are also checked.
+ * 
+ * <getEdgeValidationError> is called whenever the connectivity of an edge
+ * changes. It returns an empty string or an error message if the edge is
+ * invalid or null if the edge is valid. If the returned string is not empty
+ * then it is displayed as an error message.
+ * 
+ * <mxMultiplicity> allows to specify the multiplicity between a terminal and
+ * its possible neighbors. For example, if any rectangle may only be connected
+ * to, say, a maximum of two circles you can add the following rule to
+ * <multiplicities>:
+ * 
+ * (code)
+ * graph.multiplicities.push(new mxMultiplicity(
+ *   true, 'rectangle', null, null, 0, 2, ['circle'],
+ *   'Only 2 targets allowed',
+ *   'Only shape targets allowed'));
+ * (end)
+ * 
+ * This will display the first error message whenever a rectangle is connected
+ * to more than two circles and the second error message if a rectangle is
+ * connected to anything but a circle.
+ * 
+ * For certain multiplicities, such as a minimum of 1 connection, which cannot
+ * be enforced at cell creation time (unless the cell is created together with
+ * the connection), mxGraph offers <validate> which checks all multiplicities
+ * for all cells and displays the respective error messages in an overlay icon
+ * on the cells.
+ * 
+ * If a cell is collapsed and contains validation errors, a respective warning
+ * icon is attached to the collapsed cell.
+ * 
+ * Auto-Layout:
+ * 
+ * For automatic layout, the <getLayout> hook is provided in <mxLayoutManager>.
+ * It can be overridden to return a layout algorithm for the children of a
+ * given cell.
+ * 
+ * Unconnected edges:
+ * 
+ * The default values for all switches are designed to meet the requirements of
+ * general diagram drawing applications. A very typical set of settings to
+ * avoid edges that are not connected is the following:
+ * 
+ * (code)
+ * graph.setAllowDanglingEdges(false);
+ * graph.setDisconnectOnMove(false);
+ * (end)
+ * 
+ * Setting the <cloneInvalidEdges> switch to true is optional. This switch
+ * controls if edges are inserted after a copy, paste or clone-drag if they are
+ * invalid. For example, edges are invalid if copied or control-dragged without 
+ * having selected the corresponding terminals and allowDanglingEdges is
+ * false, in which case the edges will not be cloned if the switch is false.
+ * 
+ * Output:
+ * 
+ * To produce an XML representation for a diagram, the following code can be
+ * used.
+ * 
+ * (code)
+ * var enc = new mxCodec(mxUtils.createXmlDocument());
+ * var node = enc.encode(graph.getModel());
+ * (end)
+ * 
+ * This will produce an XML node than can be handled using the DOM API or
+ * turned into a string representation using the following code:
+ * 
+ * (code)
+ * var xml = mxUtils.getXml(node);
+ * (end)
+ * 
+ * To obtain a formatted string, mxUtils.getPrettyXml can be used instead.
+ * 
+ * This string can now be stored in a local persistent storage (for example
+ * using Google Gears) or it can be passed to a backend using mxUtils.post as
+ * follows. The url variable is the URL of the Java servlet, PHP page or HTTP
+ * handler, depending on the server.
+ * 
+ * (code)
+ * var xmlString = encodeURIComponent(mxUtils.getXml(node));
+ * mxUtils.post(url, 'xml='+xmlString, function(req)
+ * {
+ *   // Process server response using req of type mxXmlRequest
+ * });
+ * (end)
+ * 
+ * Input:
+ * 
+ * To load an XML representation of a diagram into an existing graph object
+ * mxUtils.load can be used as follows. The url variable is the URL of the Java
+ * servlet, PHP page or HTTP handler that produces the XML string.
+ * 
+ * (code)
+ * var xmlDoc = mxUtils.load(url).getXml();
+ * var node = xmlDoc.documentElement;
+ * var dec = new mxCodec(node.ownerDocument);
+ * dec.decode(node, graph.getModel());
+ * (end)
+ * 
+ * For creating a page that loads the client and a diagram using a single
+ * request please refer to the deployment examples in the backends.
+ * 
+ * Functional dependencies:
+ * 
+ * (see images/callgraph.png)
+ * 
+ * Resources:
+ *
+ * resources/graph - Language resources for mxGraph
+ *
+ * Group: Events
+ * 
+ * Event: mxEvent.ROOT
+ * 
+ * Fires if the root in the model has changed. This event has no properties.
+ * 
+ * Event: mxEvent.ALIGN_CELLS
+ * 
+ * Fires between begin- and endUpdate in <alignCells>. The <code>cells</code>
+ * and <code>align</code> properties contain the respective arguments that were
+ * passed to <alignCells>.
+ *
+ * Event: mxEvent.FLIP_EDGE
+ *
+ * Fires between begin- and endUpdate in <flipEdge>. The <code>edge</code>
+ * property contains the edge passed to <flipEdge>.
+ * 
+ * Event: mxEvent.ORDER_CELLS
+ * 
+ * Fires between begin- and endUpdate in <orderCells>. The <code>cells</code>
+ * and <code>back</code> properties contain the respective arguments that were
+ * passed to <orderCells>.
+ *
+ * Event: mxEvent.CELLS_ORDERED
+ *
+ * Fires between begin- and endUpdate in <cellsOrdered>. The <code>cells</code>
+ * and <code>back</code> arguments contain the respective arguments that were
+ * passed to <cellsOrdered>.
+ * 
+ * Event: mxEvent.GROUP_CELLS
+ * 
+ * Fires between begin- and endUpdate in <groupCells>. The <code>group</code>,
+ * <code>cells</code> and <code>border</code> arguments contain the respective
+ * arguments that were passed to <groupCells>.
+ * 
+ * Event: mxEvent.UNGROUP_CELLS
+ * 
+ * Fires between begin- and endUpdate in <ungroupCells>. The <code>cells</code>
+ * property contains the array of cells that was passed to <ungroupCells>.
+ * 
+ * Event: mxEvent.REMOVE_CELLS_FROM_PARENT
+ * 
+ * Fires between begin- and endUpdate in <removeCellsFromParent>. The
+ * <code>cells</code> property contains the array of cells that was passed to
+ * <removeCellsFromParent>.
+ * 
+ * Event: mxEvent.ADD_CELLS
+ * 
+ * Fires between begin- and endUpdate in <addCells>. The <code>cells</code>,
+ * <code>parent</code>, <code>index</code>, <code>source</code> and
+ * <code>target</code> properties contain the respective arguments that were
+ * passed to <addCells>.
+ * 
+ * Event: mxEvent.CELLS_ADDED
+ * 
+ * Fires between begin- and endUpdate in <cellsAdded>. The <code>cells</code>,
+ * <code>parent</code>, <code>index</code>, <code>source</code>,
+ * <code>target</code> and <code>absolute</code> properties contain the
+ * respective arguments that were passed to <cellsAdded>.
+ * 
+ * Event: mxEvent.REMOVE_CELLS
+ * 
+ * Fires between begin- and endUpdate in <removeCells>. The <code>cells</code>
+ * and <code>includeEdges</code> arguments contain the respective arguments
+ * that were passed to <removeCells>.
+ * 
+ * Event: mxEvent.CELLS_REMOVED
+ * 
+ * Fires between begin- and endUpdate in <cellsRemoved>. The <code>cells</code>
+ * argument contains the array of cells that was removed.
+ * 
+ * Event: mxEvent.SPLIT_EDGE
+ * 
+ * Fires between begin- and endUpdate in <splitEdge>. The <code>edge</code>
+ * property contains the edge to be splitted, the <code>cells</code>,
+ * <code>newEdge</code>, <code>dx</code> and <code>dy</code> properties contain
+ * the respective arguments that were passed to <splitEdge>.
+ * 
+ * Event: mxEvent.TOGGLE_CELLS
+ * 
+ * Fires between begin- and endUpdate in <toggleCells>. The <code>show</code>,
+ * <code>cells</code> and <code>includeEdges</code> properties contain the
+ * respective arguments that were passed to <toggleCells>.
+ * 
+ * Event: mxEvent.FOLD_CELLS
+ * 
+ * Fires between begin- and endUpdate in <foldCells>. The
+ * <code>collapse</code>, <code>cells</code> and <code>recurse</code>
+ * properties contain the respective arguments that were passed to <foldCells>.
+ * 
+ * Event: mxEvent.CELLS_FOLDED
+ * 
+ * Fires between begin- and endUpdate in cellsFolded. The
+ * <code>collapse</code>, <code>cells</code> and <code>recurse</code>
+ * properties contain the respective arguments that were passed to
+ * <cellsFolded>.
+ * 
+ * Event: mxEvent.UPDATE_CELL_SIZE
+ * 
+ * Fires between begin- and endUpdate in <updateCellSize>. The
+ * <code>cell</code> and <code>ignoreChildren</code> properties contain the
+ * respective arguments that were passed to <updateCellSize>.
+ * 
+ * Event: mxEvent.RESIZE_CELLS
+ * 
+ * Fires between begin- and endUpdate in <resizeCells>. The <code>cells</code>
+ * and <code>bounds</code> properties contain the respective arguments that
+ * were passed to <resizeCells>.
+ * 
+ * Event: mxEvent.CELLS_RESIZED
+ * 
+ * Fires between begin- and endUpdate in <cellsResized>. The <code>cells</code>
+ * and <code>bounds</code> properties contain the respective arguments that
+ * were passed to <cellsResized>.
+ * 
+ * Event: mxEvent.MOVE_CELLS
+ * 
+ * Fires between begin- and endUpdate in <moveCells>. The <code>cells</code>,
+ * <code>dx</code>, <code>dy</code>, <code>clone</code>, <code>target</code>
+ * and <code>event</code> properties contain the respective arguments that
+ * were passed to <moveCells>.
+ * 
+ * Event: mxEvent.CELLS_MOVED
+ * 
+ * Fires between begin- and endUpdate in <cellsMoved>. The <code>cells</code>,
+ * <code>dx</code>, <code>dy</code> and <code>disconnect</code> properties
+ * contain the respective arguments that were passed to <cellsMoved>.
+ * 
+ * Event: mxEvent.CONNECT_CELL
+ * 
+ * Fires between begin- and endUpdate in <connectCell>. The <code>edge</code>,
+ * <code>terminal</code> and <code>source</code> properties contain the
+ * respective arguments that were passed to <connectCell>.
+ * 
+ * Event: mxEvent.CELL_CONNECTED
+ * 
+ * Fires between begin- and endUpdate in <cellConnected>. The
+ * <code>edge</code>, <code>terminal</code> and <code>source</code> properties
+ * contain the respective arguments that were passed to <cellConnected>.
+ * 
+ * Event: mxEvent.REFRESH
+ * 
+ * Fires after <refresh> was executed. This event has no properties.
+ *
+ * Event: mxEvent.CLICK
+ * 
+ * Fires in <click> after a click event. The <code>event</code> property
+ * contains the original mouse event and <code>cell</code> property contains
+ * the cell under the mouse or null if the background was clicked.
+ * 
+ * Event: mxEvent.DOUBLE_CLICK
+ *
+ * Fires in <dblClick> after a double click. The <code>event</code> property
+ * contains the original mouse event and the <code>cell</code> property
+ * contains the cell under the mouse or null if the background was clicked.
+ * 
+ * Event: mxEvent.GESTURE
+ *
+ * Fires in <fireGestureEvent> after a touch gesture. The <code>event</code>
+ * property contains the original gesture end event and the <code>cell</code>
+ * property contains the optional cell associated with the gesture.
+ *
+ * Event: mxEvent.TAP_AND_HOLD
+ *
+ * Fires in <tapAndHold> if a tap and hold event was detected. The <code>event</code>
+ * property contains the initial touch event and the <code>cell</code> property
+ * contains the cell under the mouse or null if the background was clicked.
+ *
+ * Event: mxEvent.FIRE_MOUSE_EVENT
+ *
+ * Fires in <fireMouseEvent> before the mouse listeners are invoked. The
+ * <code>eventName</code> property contains the event name and the
+ * <code>event</code> property contains the <mxMouseEvent>.
+ *
+ * Event: mxEvent.SIZE
+ *
+ * Fires after <sizeDidChange> was executed. The <code>bounds</code> property
+ * contains the new graph bounds.
+ *
+ * Event: mxEvent.START_EDITING
+ *
+ * Fires before the in-place editor starts in <startEditingAtCell>. The
+ * <code>cell</code> property contains the cell that is being edited and the
+ * <code>event</code> property contains the optional event argument that was
+ * passed to <startEditingAtCell>.
+ * 
+ * Event: mxEvent.EDITING_STARTED
+ *
+ * Fires after the in-place editor starts in <startEditingAtCell>. The
+ * <code>cell</code> property contains the cell that is being edited and the
+ * <code>event</code> property contains the optional event argument that was
+ * passed to <startEditingAtCell>.
+ * 
+ * Event: mxEvent.EDITING_STOPPED
+ *
+ * Fires after the in-place editor stops in <stopEditing>.
+ *
+ * Event: mxEvent.LABEL_CHANGED
+ *
+ * Fires between begin- and endUpdate in <cellLabelChanged>. The
+ * <code>cell</code> property contains the cell, the <code>value</code>
+ * property contains the new value for the cell, the <code>old</code> property
+ * contains the old value and the optional <code>event</code> property contains
+ * the mouse event that started the edit.
+ * 
+ * Event: mxEvent.ADD_OVERLAY
+ *
+ * Fires after an overlay is added in <addCellOverlay>. The <code>cell</code>
+ * property contains the cell and the <code>overlay</code> property contains
+ * the <mxCellOverlay> that was added.
+ *
+ * Event: mxEvent.REMOVE_OVERLAY
+ *
+ * Fires after an overlay is removed in <removeCellOverlay> and
+ * <removeCellOverlays>. The <code>cell</code> property contains the cell and
+ * the <code>overlay</code> property contains the <mxCellOverlay> that was
+ * removed.
+ * 
+ * Constructor: mxGraph
+ * 
+ * Constructs a new mxGraph in the specified container. Model is an optional
+ * mxGraphModel. If no model is provided, a new mxGraphModel instance is 
+ * used as the model. The container must have a valid owner document prior 
+ * to calling this function in Internet Explorer. RenderHint is a string to
+ * affect the display performance and rendering in IE, but not in SVG-based 
+ * browsers. The parameter is mapped to <dialect>, which may 
+ * be one of <mxConstants.DIALECT_SVG> for SVG-based browsers, 
+ * <mxConstants.DIALECT_STRICTHTML> for fastest display mode,
+ * <mxConstants.DIALECT_PREFERHTML> for faster display mode,
+ * <mxConstants.DIALECT_MIXEDHTML> for fast and <mxConstants.DIALECT_VML> 
+ * for exact display mode (slowest). The dialects are defined in mxConstants.
+ * The default values are DIALECT_SVG for SVG-based browsers and
+ * DIALECT_MIXED for IE.
+ *
+ * The possible values for the renderingHint parameter are explained below:
+ * 
+ * fast - The parameter is based on the fact that the display performance is 
+ * highly improved in IE if the VML is not contained within a VML group 
+ * element. The lack of a group element only slightly affects the display while 
+ * panning, but improves the performance by almost a factor of 2, while keeping 
+ * the display sufficiently accurate. This also allows to render certain shapes as HTML 
+ * if the display accuracy is not affected, which is implemented by 
+ * <mxShape.isMixedModeHtml>. This is the default setting and is mapped to
+ * DIALECT_MIXEDHTML.
+ * faster - Same as fast, but more expensive shapes are avoided. This is 
+ * controlled by <mxShape.preferModeHtml>. The default implementation will 
+ * avoid gradients and rounded rectangles, but more significant shapes, such 
+ * as rhombus, ellipse, actor and cylinder will be rendered accurately. This 
+ * setting is mapped to DIALECT_PREFERHTML.
+ * fastest - Almost anything will be rendered in Html. This allows for 
+ * rectangles, labels and images. This setting is mapped to
+ * DIALECT_STRICTHTML.
+ * exact - If accurate panning is required and if the diagram is small (up
+ * to 100 cells), then this value should be used. In this mode, a group is 
+ * created that contains the VML. This allows for accurate panning and is 
+ * mapped to DIALECT_VML.
+ *
+ * Example:
+ * 
+ * To create a graph inside a DOM node with an id of graph:
+ * (code)
+ * var container = document.getElementById('graph');
+ * var graph = new mxGraph(container);
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * container - Optional DOM node that acts as a container for the graph.
+ * If this is null then the container can be initialized later using
+ * <init>.
+ * model - Optional <mxGraphModel> that constitutes the graph data.
+ * renderHint - Optional string that specifies the display accuracy and
+ * performance. Default is mxConstants.DIALECT_MIXEDHTML (for IE).
+ * stylesheet - Optional <mxStylesheet> to be used in the graph.
+ */
+function mxGraph(container, model, renderHint, stylesheet)
+{
+	// Initializes the variable in case the prototype has been
+	// modified to hold some listeners (which is possible because
+	// the createHandlers call is executed regardless of the
+	// arguments passed into the ctor).
+	this.mouseListeners = null;
+	
+	// Converts the renderHint into a dialect
+	this.renderHint = renderHint;
+
+	if (mxClient.IS_SVG)
+	{
+		this.dialect = mxConstants.DIALECT_SVG;
+	}
+	else if (renderHint == mxConstants.RENDERING_HINT_EXACT && mxClient.IS_VML)
+	{
+		this.dialect = mxConstants.DIALECT_VML;
+	}
+	else if (renderHint == mxConstants.RENDERING_HINT_FASTEST)
+	{
+		this.dialect = mxConstants.DIALECT_STRICTHTML;
+	}
+	else if (renderHint == mxConstants.RENDERING_HINT_FASTER)
+	{
+		this.dialect = mxConstants.DIALECT_PREFERHTML;
+	}
+	else // default for VML
+	{
+		this.dialect = mxConstants.DIALECT_MIXEDHTML;
+	}
+	
+	// Initializes the main members that do not require a container
+	this.model = (model != null) ? model : new mxGraphModel();
+	this.multiplicities = [];
+	this.imageBundles = [];
+	this.cellRenderer = this.createCellRenderer();
+	this.setSelectionModel(this.createSelectionModel());
+	this.setStylesheet((stylesheet != null) ? stylesheet : this.createStylesheet());
+	this.view = this.createGraphView();
+	
+	// Adds a graph model listener to update the view
+	this.graphModelChangeListener = mxUtils.bind(this, function(sender, evt)
+	{
+		this.graphModelChanged(evt.getProperty('edit').changes);
+	});
+	
+	this.model.addListener(mxEvent.CHANGE, this.graphModelChangeListener);
+
+	// Installs basic event handlers with disabled default settings.
+	this.createHandlers();
+	
+	// Initializes the display if a container was specified
+	if (container != null)
+	{
+		this.init(container);
+	}
+	
+	this.view.revalidate();
+};
+
+/**
+ * Installs the required language resources at class
+ * loading time.
+ */
+if (mxLoadResources)
+{
+	mxResources.add(mxClient.basePath+'/resources/graph');
+}
+
+/**
+ * Extends mxEventSource.
+ */
+mxGraph.prototype = new mxEventSource();
+mxGraph.prototype.constructor = mxGraph;
+
+/**
+ * Variable: EMPTY_ARRAY
+ *
+ * Immutable empty array instance.
+ */
+mxGraph.prototype.EMPTY_ARRAY = [];
+
+/**
+ * Group: Variables
+ */
+
+/**
+ * Variable: mouseListeners
+ * 
+ * Holds the mouse event listeners. See <fireMouseEvent>.
+ */
+mxGraph.prototype.mouseListeners = null;
+
+/**
+ * Variable: isMouseDown
+ * 
+ * Holds the state of the mouse button.
+ */
+mxGraph.prototype.isMouseDown = false;
+
+/**
+ * Variable: model
+ * 
+ * Holds the <mxGraphModel> that contains the cells to be displayed.
+ */
+mxGraph.prototype.model = null;
+
+/**
+ * Variable: view
+ * 
+ * Holds the <mxGraphView> that caches the <mxCellStates> for the cells.
+ */
+mxGraph.prototype.view = null;
+
+/**
+ * Variable: stylesheet
+ * 
+ * Holds the <mxStylesheet> that defines the appearance of the cells.
+ * 
+ * 
+ * Example:
+ * 
+ * Use the following code to read a stylesheet into an existing graph.
+ * 
+ * (code)
+ * var req = mxUtils.load('stylesheet.xml');
+ * var root = req.getDocumentElement();
+ * var dec = new mxCodec(root.ownerDocument);
+ * dec.decode(root, graph.stylesheet);
+ * (end)
+ */
+mxGraph.prototype.stylesheet = null;
+	
+/**
+ * Variable: selectionModel
+ * 
+ * Holds the <mxGraphSelectionModel> that models the current selection.
+ */
+mxGraph.prototype.selectionModel = null;
+
+/**
+ * Variable: cellEditor
+ * 
+ * Holds the <mxCellEditor> that is used as the in-place editing.
+ */
+mxGraph.prototype.cellEditor = null;
+
+/**
+ * Variable: cellRenderer
+ * 
+ * Holds the <mxCellRenderer> for rendering the cells in the graph.
+ */
+mxGraph.prototype.cellRenderer = null;
+
+/**
+ * Variable: multiplicities
+ * 
+ * An array of <mxMultiplicities> describing the allowed
+ * connections in a graph.
+ */
+mxGraph.prototype.multiplicities = null;
+
+/**
+ * Variable: renderHint
+ * 
+ * RenderHint as it was passed to the constructor.
+ */
+mxGraph.prototype.renderHint = null;
+
+/**
+ * Variable: dialect
+ * 
+ * Dialect to be used for drawing the graph. Possible values are all
+ * constants in <mxConstants> with a DIALECT-prefix.
+ */
+mxGraph.prototype.dialect = null;
+
+/**
+ * Variable: gridSize
+ * 
+ * Specifies the grid size. Default is 10.
+ */
+mxGraph.prototype.gridSize = 10;
+	
+/**
+ * Variable: gridEnabled
+ * 
+ * Specifies if the grid is enabled. This is used in <snap>. Default is
+ * true.
+ */
+mxGraph.prototype.gridEnabled = true;
+
+/**
+ * Variable: portsEnabled
+ * 
+ * Specifies if ports are enabled. This is used in <cellConnected> to update
+ * the respective style. Default is true.
+ */
+mxGraph.prototype.portsEnabled = true;
+
+/**
+ * Variable: nativeDoubleClickEnabled
+ * 
+ * Specifies if native double click events should be detected. Default is true.
+ */
+mxGraph.prototype.nativeDblClickEnabled = true;
+
+/**
+ * Variable: doubleTapEnabled
+ * 
+ * Specifies if double taps on touch-based devices should be handled as a
+ * double click. Default is true.
+ */
+mxGraph.prototype.doubleTapEnabled = true;
+
+/**
+ * Variable: doubleTapTimeout
+ * 
+ * Specifies the timeout for double taps and non-native double clicks. Default
+ * is 500 ms.
+ */
+mxGraph.prototype.doubleTapTimeout = 500;
+
+/**
+ * Variable: doubleTapTolerance
+ * 
+ * Specifies the tolerance for double taps and double clicks in quirks mode.
+ * Default is 25 pixels.
+ */
+mxGraph.prototype.doubleTapTolerance = 25;
+
+/**
+ * Variable: lastTouchX
+ * 
+ * Holds the x-coordinate of the last touch event for double tap detection.
+ */
+mxGraph.prototype.lastTouchY = 0;
+
+/**
+ * Variable: lastTouchX
+ * 
+ * Holds the y-coordinate of the last touch event for double tap detection.
+ */
+mxGraph.prototype.lastTouchY = 0;
+
+/**
+ * Variable: lastTouchTime
+ * 
+ * Holds the time of the last touch event for double click detection.
+ */
+mxGraph.prototype.lastTouchTime = 0;
+
+/**
+ * Variable: tapAndHoldEnabled
+ * 
+ * Specifies if tap and hold should be used for starting connections on touch-based
+ * devices. Default is true.
+ */
+mxGraph.prototype.tapAndHoldEnabled = true;
+
+/**
+ * Variable: tapAndHoldDelay
+ * 
+ * Specifies the time for a tap and hold. Default is 500 ms.
+ */
+mxGraph.prototype.tapAndHoldDelay = 500;
+
+/**
+ * Variable: tapAndHoldInProgress
+ * 
+ * True if the timer for tap and hold events is running.
+ */
+mxGraph.prototype.tapAndHoldInProgress = false;
+
+/**
+ * Variable: tapAndHoldValid
+ * 
+ * True as long as the timer is running and the touch events
+ * stay within the given <tapAndHoldTolerance>.
+ */
+mxGraph.prototype.tapAndHoldValid = false;
+
+/**
+ * Variable: initialTouchX
+ * 
+ * Holds the x-coordinate of the intial touch event for tap and hold.
+ */
+mxGraph.prototype.initialTouchX = 0;
+
+/**
+ * Variable: initialTouchY
+ * 
+ * Holds the y-coordinate of the intial touch event for tap and hold.
+ */
+mxGraph.prototype.initialTouchY = 0;
+
+/**
+ * Variable: tolerance
+ * 
+ * Tolerance for a move to be handled as a single click.
+ * Default is 4 pixels.
+ */
+mxGraph.prototype.tolerance = 4;
+
+/**
+ * Variable: defaultOverlap
+ * 
+ * Value returned by <getOverlap> if <isAllowOverlapParent> returns
+ * true for the given cell. <getOverlap> is used in <constrainChild> if
+ * <isConstrainChild> returns true. The value specifies the
+ * portion of the child which is allowed to overlap the parent.
+ */
+mxGraph.prototype.defaultOverlap = 0.5;
+
+/**
+ * Variable: defaultParent
+ * 
+ * Specifies the default parent to be used to insert new cells.
+ * This is used in <getDefaultParent>. Default is null.
+ */
+mxGraph.prototype.defaultParent = null;
+
+/**
+ * Variable: alternateEdgeStyle
+ * 
+ * Specifies the alternate edge style to be used if the main control point
+ * on an edge is being doubleclicked. Default is null.
+ */
+mxGraph.prototype.alternateEdgeStyle = null;
+
+/**
+ * Variable: backgroundImage
+ *
+ * Specifies the <mxImage> to be returned by <getBackgroundImage>. Default
+ * is null.
+ * 
+ * Example:
+ *
+ * (code)
+ * var img = new mxImage('http://www.example.com/maps/examplemap.jpg', 1024, 768);
+ * graph.setBackgroundImage(img);
+ * graph.view.validate();
+ * (end)
+ */
+mxGraph.prototype.backgroundImage = null;
+
+/**
+ * Variable: pageVisible
+ *
+ * Specifies if the background page should be visible. Default is false.
+ * Not yet implemented.
+ */
+mxGraph.prototype.pageVisible = false;
+
+/**
+ * Variable: pageBreaksVisible
+ * 
+ * Specifies if a dashed line should be drawn between multiple pages. Default
+ * is false. If you change this value while a graph is being displayed then you
+ * should call <sizeDidChange> to force an update of the display.
+ */
+mxGraph.prototype.pageBreaksVisible = false;
+
+/**
+ * Variable: pageBreakColor
+ * 
+ * Specifies the color for page breaks. Default is 'gray'.
+ */
+mxGraph.prototype.pageBreakColor = 'gray';
+
+/**
+ * Variable: pageBreakDashed
+ * 
+ * Specifies the page breaks should be dashed. Default is true.
+ */
+mxGraph.prototype.pageBreakDashed = true;
+
+/**
+ * Variable: minPageBreakDist
+ * 
+ * Specifies the minimum distance for page breaks to be visible. Default is
+ * 20 (in pixels).
+ */
+mxGraph.prototype.minPageBreakDist = 20;
+
+/**
+ * Variable: preferPageSize
+ * 
+ * Specifies if the graph size should be rounded to the next page number in
+ * <sizeDidChange>. This is only used if the graph container has scrollbars.
+ * Default is false.
+ */
+mxGraph.prototype.preferPageSize = false;
+
+/**
+ * Variable: pageFormat
+ *
+ * Specifies the page format for the background page. Default is
+ * <mxConstants.PAGE_FORMAT_A4_PORTRAIT>. This is used as the default in
+ * <mxPrintPreview> and for painting the background page if <pageVisible> is
+ * true and the pagebreaks if <pageBreaksVisible> is true.
+ */
+mxGraph.prototype.pageFormat = mxConstants.PAGE_FORMAT_A4_PORTRAIT;
+
+/**
+ * Variable: pageScale
+ *
+ * Specifies the scale of the background page. Default is 1.5.
+ * Not yet implemented.
+ */
+mxGraph.prototype.pageScale = 1.5;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies the return value for <isEnabled>. Default is true.
+ */
+mxGraph.prototype.enabled = true;
+
+/**
+ * Variable: escapeEnabled
+ * 
+ * Specifies if <mxKeyHandler> should invoke <escape> when the escape key
+ * is pressed. Default is true.
+ */
+mxGraph.prototype.escapeEnabled = true;
+
+/**
+ * Variable: invokesStopCellEditing
+ * 
+ * If true, when editing is to be stopped by way of selection changing,
+ * data in diagram changing or other means stopCellEditing is invoked, and
+ * changes are saved. This is implemented in a focus handler in
+ * <mxCellEditor>. Default is true.
+ */
+mxGraph.prototype.invokesStopCellEditing = true;
+
+/**
+ * Variable: enterStopsCellEditing
+ * 
+ * If true, pressing the enter key without pressing control or shift will stop
+ * editing and accept the new value. This is used in <mxCellEditor> to stop
+ * cell editing. Note: You can always use F2 and escape to stop editing.
+ * Default is false.
+ */
+mxGraph.prototype.enterStopsCellEditing = false;
+
+/**
+ * Variable: useScrollbarsForPanning
+ * 
+ * Specifies if scrollbars should be used for panning in <panGraph> if
+ * any scrollbars are available. If scrollbars are enabled in CSS, but no
+ * scrollbars appear because the graph is smaller than the container size,
+ * then no panning occurs if this is true. Default is true.
+ */
+mxGraph.prototype.useScrollbarsForPanning = true;
+
+/**
+ * Variable: exportEnabled
+ * 
+ * Specifies the return value for <canExportCell>. Default is true.
+ */
+mxGraph.prototype.exportEnabled = true;
+
+/**
+ * Variable: importEnabled
+ * 
+ * Specifies the return value for <canImportCell>. Default is true.
+ */
+mxGraph.prototype.importEnabled = true;
+
+/**
+ * Variable: cellsLocked
+ * 
+ * Specifies the return value for <isCellLocked>. Default is false.
+ */
+mxGraph.prototype.cellsLocked = false;
+
+/**
+ * Variable: cellsCloneable
+ * 
+ * Specifies the return value for <isCellCloneable>. Default is true.
+ */
+mxGraph.prototype.cellsCloneable = true;
+
+/**
+ * Variable: foldingEnabled
+ * 
+ * Specifies if folding (collapse and expand via an image icon in the graph
+ * should be enabled). Default is true.
+ */
+mxGraph.prototype.foldingEnabled = true;
+
+/**
+ * Variable: cellsEditable
+ * 
+ * Specifies the return value for <isCellEditable>. Default is true.
+ */
+mxGraph.prototype.cellsEditable = true;
+		
+/**
+ * Variable: cellsDeletable
+ * 
+ * Specifies the return value for <isCellDeletable>. Default is true.
+ */
+mxGraph.prototype.cellsDeletable = true;
+
+/**
+ * Variable: cellsMovable
+ * 
+ * Specifies the return value for <isCellMovable>. Default is true.
+ */
+mxGraph.prototype.cellsMovable = true;
+	
+/**
+ * Variable: edgeLabelsMovable
+ * 
+ * Specifies the return value for edges in <isLabelMovable>. Default is true.
+ */
+mxGraph.prototype.edgeLabelsMovable = true;
+	
+/**
+ * Variable: vertexLabelsMovable
+ * 
+ * Specifies the return value for vertices in <isLabelMovable>. Default is false.
+ */
+mxGraph.prototype.vertexLabelsMovable = false;
+
+/**
+ * Variable: dropEnabled
+ * 
+ * Specifies the return value for <isDropEnabled>. Default is false.
+ */
+mxGraph.prototype.dropEnabled = false;
+
+/**
+ * Variable: splitEnabled
+ * 
+ * Specifies if dropping onto edges should be enabled. This is ignored if
+ * <dropEnabled> is false. If enabled, it will call <splitEdge> to carry
+ * out the drop operation. Default is true.
+ */
+mxGraph.prototype.splitEnabled = true;
+
+/**
+ * Variable: cellsResizable
+ * 
+ * Specifies the return value for <isCellResizable>. Default is true.
+ */
+mxGraph.prototype.cellsResizable = true;
+
+/**
+ * Variable: cellsBendable
+ * 
+ * Specifies the return value for <isCellsBendable>. Default is true.
+ */
+mxGraph.prototype.cellsBendable = true;
+
+/**
+ * Variable: cellsSelectable
+ * 
+ * Specifies the return value for <isCellSelectable>. Default is true.
+ */
+mxGraph.prototype.cellsSelectable = true;
+
+/**
+ * Variable: cellsDisconnectable
+ * 
+ * Specifies the return value for <isCellDisconntable>. Default is true.
+ */
+mxGraph.prototype.cellsDisconnectable = true;
+
+/**
+ * Variable: autoSizeCells
+ * 
+ * Specifies if the graph should automatically update the cell size after an
+ * edit. This is used in <isAutoSizeCell>. Default is false.
+ */
+mxGraph.prototype.autoSizeCells = false;
+
+/**
+ * Variable: autoSizeCellsOnAdd
+ * 
+ * Specifies if autoSize style should be applied when cells are added. Default is false.
+ */
+mxGraph.prototype.autoSizeCellsOnAdd = false;
+
+/**
+ * Variable: autoScroll
+ * 
+ * Specifies if the graph should automatically scroll if the mouse goes near
+ * the container edge while dragging. This is only taken into account if the
+ * container has scrollbars. Default is true.
+ * 
+ * If you need this to work without scrollbars then set <ignoreScrollbars> to
+ * true. Please consult the <ignoreScrollbars> for details. In general, with
+ * no scrollbars, the use of <allowAutoPanning> is recommended.
+ */
+mxGraph.prototype.autoScroll = true;
+
+/**
+ * Variable: ignoreScrollbars
+ * 
+ * Specifies if the graph should automatically scroll regardless of the
+ * scrollbars. This will scroll the container using positive values for
+ * scroll positions (ie usually only rightwards and downwards). To avoid
+ * possible conflicts with panning, set <translateToScrollPosition> to true.
+ */
+mxGraph.prototype.ignoreScrollbars = false;
+
+/**
+ * Variable: translateToScrollPosition
+ * 
+ * Specifies if the graph should automatically convert the current scroll
+ * position to a translate in the graph view when a mouseUp event is received.
+ * This can be used to avoid conflicts when using <autoScroll> and
+ * <ignoreScrollbars> with no scrollbars in the container.
+ */
+mxGraph.prototype.translateToScrollPosition = false;
+
+/**
+ * Variable: timerAutoScroll
+ * 
+ * Specifies if autoscrolling should be carried out via mxPanningManager even
+ * if the container has scrollbars. This disables <scrollPointToVisible> and
+ * uses <mxPanningManager> instead. If this is true then <autoExtend> is
+ * disabled. It should only be used with a scroll buffer or when scollbars
+ * are visible and scrollable in all directions. Default is false.
+ */
+mxGraph.prototype.timerAutoScroll = false;
+
+/**
+ * Variable: allowAutoPanning
+ * 
+ * Specifies if panning via <panGraph> should be allowed to implement autoscroll
+ * if no scrollbars are available in <scrollPointToVisible>. To enable panning
+ * inside the container, near the edge, set <mxPanningManager.border> to a
+ * positive value. Default is false.
+ */
+mxGraph.prototype.allowAutoPanning = false;
+
+/**
+ * Variable: autoExtend
+ * 
+ * Specifies if the size of the graph should be automatically extended if the
+ * mouse goes near the container edge while dragging. This is only taken into
+ * account if the container has scrollbars. Default is true. See <autoScroll>.
+ */
+mxGraph.prototype.autoExtend = true;
+
+/**
+ * Variable: maximumGraphBounds
+ * 
+ * <mxRectangle> that specifies the area in which all cells in the diagram
+ * should be placed. Uses in <getMaximumGraphBounds>. Use a width or height of
+ * 0 if you only want to give a upper, left corner.
+ */
+mxGraph.prototype.maximumGraphBounds = null;
+
+/**
+ * Variable: minimumGraphSize
+ * 
+ * <mxRectangle> that specifies the minimum size of the graph. This is ignored
+ * if the graph container has no scrollbars. Default is null.
+ */
+mxGraph.prototype.minimumGraphSize = null;
+
+/**
+ * Variable: minimumContainerSize
+ * 
+ * <mxRectangle> that specifies the minimum size of the <container> if
+ * <resizeContainer> is true.
+ */
+mxGraph.prototype.minimumContainerSize = null;
+		
+/**
+ * Variable: maximumContainerSize
+ * 
+ * <mxRectangle> that specifies the maximum size of the container if
+ * <resizeContainer> is true.
+ */
+mxGraph.prototype.maximumContainerSize = null;
+
+/**
+ * Variable: resizeContainer
+ * 
+ * Specifies if the container should be resized to the graph size when
+ * the graph size has changed. Default is false.
+ */
+mxGraph.prototype.resizeContainer = false;
+
+/**
+ * Variable: border
+ * 
+ * Border to be added to the bottom and right side when the container is
+ * being resized after the graph has been changed. Default is 0.
+ */
+mxGraph.prototype.border = 0;
+		
+/**
+ * Variable: keepEdgesInForeground
+ * 
+ * Specifies if edges should appear in the foreground regardless of their order
+ * in the model. If <keepEdgesInForeground> and <keepEdgesInBackground> are
+ * both true then the normal order is applied. Default is false.
+ */
+mxGraph.prototype.keepEdgesInForeground = false;
+
+/**
+ * Variable: keepEdgesInBackground
+ * 
+ * Specifies if edges should appear in the background regardless of their order
+ * in the model. If <keepEdgesInForeground> and <keepEdgesInBackground> are
+ * both true then the normal order is applied. Default is false.
+ */
+mxGraph.prototype.keepEdgesInBackground = false;
+
+/**
+ * Variable: allowNegativeCoordinates
+ * 
+ * Specifies if negative coordinates for vertices are allowed. Default is true.
+ */
+mxGraph.prototype.allowNegativeCoordinates = true;
+
+/**
+ * Variable: constrainChildren
+ * 
+ * Specifies if a child should be constrained inside the parent bounds after a
+ * move or resize of the child. Default is true.
+ */
+mxGraph.prototype.constrainChildren = true;
+
+/**
+ * Variable: constrainRelativeChildren
+ * 
+ * Specifies if child cells with relative geometries should be constrained
+ * inside the parent bounds, if <constrainChildren> is true, and/or the
+ * <maximumGraphBounds>. Default is false.
+ */
+mxGraph.prototype.constrainRelativeChildren = false;
+
+/**
+ * Variable: extendParents
+ * 
+ * Specifies if a parent should contain the child bounds after a resize of
+ * the child. Default is true. This has precedence over <constrainChildren>.
+ */
+mxGraph.prototype.extendParents = true;
+
+/**
+ * Variable: extendParentsOnAdd
+ * 
+ * Specifies if parents should be extended according to the <extendParents>
+ * switch if cells are added. Default is true.
+ */
+mxGraph.prototype.extendParentsOnAdd = true;
+
+/**
+ * Variable: extendParentsOnAdd
+ * 
+ * Specifies if parents should be extended according to the <extendParents>
+ * switch if cells are added. Default is false for backwards compatiblity.
+ */
+mxGraph.prototype.extendParentsOnMove = false;
+
+/**
+ * Variable: recursiveResize
+ * 
+ * Specifies the return value for <isRecursiveResize>. Default is
+ * false for backwards compatiblity.
+ */
+mxGraph.prototype.recursiveResize = false;
+
+/**
+ * Variable: collapseToPreferredSize
+ * 
+ * Specifies if the cell size should be changed to the preferred size when
+ * a cell is first collapsed. Default is true.
+ */
+mxGraph.prototype.collapseToPreferredSize = true;
+
+/**
+ * Variable: zoomFactor
+ * 
+ * Specifies the factor used for <zoomIn> and <zoomOut>. Default is 1.2
+ * (120%).
+ */
+mxGraph.prototype.zoomFactor = 1.2;
+
+/**
+ * Variable: keepSelectionVisibleOnZoom
+ * 
+ * Specifies if the viewport should automatically contain the selection cells
+ * after a zoom operation. Default is false.
+ */
+mxGraph.prototype.keepSelectionVisibleOnZoom = false;
+
+/**
+ * Variable: centerZoom
+ * 
+ * Specifies if the zoom operations should go into the center of the actual
+ * diagram rather than going from top, left. Default is true.
+ */
+mxGraph.prototype.centerZoom = true;
+
+/**
+ * Variable: resetViewOnRootChange
+ * 
+ * Specifies if the scale and translate should be reset if the root changes in
+ * the model. Default is true.
+ */
+mxGraph.prototype.resetViewOnRootChange = true;
+
+/**
+ * Variable: resetEdgesOnResize
+ * 
+ * Specifies if edge control points should be reset after the resize of a
+ * connected cell. Default is false.
+ */
+mxGraph.prototype.resetEdgesOnResize = false;
+
+/**
+ * Variable: resetEdgesOnMove
+ * 
+ * Specifies if edge control points should be reset after the move of a
+ * connected cell. Default is false.
+ */
+mxGraph.prototype.resetEdgesOnMove = false;
+
+/**
+ * Variable: resetEdgesOnConnect
+ * 
+ * Specifies if edge control points should be reset after the the edge has been
+ * reconnected. Default is true.
+ */
+mxGraph.prototype.resetEdgesOnConnect = true;
+
+/**
+ * Variable: allowLoops
+ * 
+ * Specifies if loops (aka self-references) are allowed. Default is false.
+ */
+mxGraph.prototype.allowLoops = false;
+	
+/**
+ * Variable: defaultLoopStyle
+ * 
+ * <mxEdgeStyle> to be used for loops. This is a fallback for loops if the
+ * <mxConstants.STYLE_LOOP> is undefined. Default is <mxEdgeStyle.Loop>.
+ */
+mxGraph.prototype.defaultLoopStyle = mxEdgeStyle.Loop;
+
+/**
+ * Variable: multigraph
+ * 
+ * Specifies if multiple edges in the same direction between the same pair of
+ * vertices are allowed. Default is true.
+ */
+mxGraph.prototype.multigraph = true;
+
+/**
+ * Variable: connectableEdges
+ * 
+ * Specifies if edges are connectable. Default is false. This overrides the
+ * connectable field in edges.
+ */
+mxGraph.prototype.connectableEdges = false;
+
+/**
+ * Variable: allowDanglingEdges
+ * 
+ * Specifies if edges with disconnected terminals are allowed in the graph.
+ * Default is true.
+ */
+mxGraph.prototype.allowDanglingEdges = true;
+
+/**
+ * Variable: cloneInvalidEdges
+ * 
+ * Specifies if edges that are cloned should be validated and only inserted
+ * if they are valid. Default is true.
+ */
+mxGraph.prototype.cloneInvalidEdges = false;
+
+/**
+ * Variable: disconnectOnMove
+ * 
+ * Specifies if edges should be disconnected from their terminals when they
+ * are moved. Default is true.
+ */
+mxGraph.prototype.disconnectOnMove = true;
+
+/**
+ * Variable: labelsVisible
+ * 
+ * Specifies if labels should be visible. This is used in <getLabel>. Default
+ * is true.
+ */
+mxGraph.prototype.labelsVisible = true;
+	
+/**
+ * Variable: htmlLabels
+ * 
+ * Specifies the return value for <isHtmlLabel>. Default is false.
+ */
+mxGraph.prototype.htmlLabels = false;
+
+/**
+ * Variable: swimlaneSelectionEnabled
+ * 
+ * Specifies if swimlanes should be selectable via the content if the
+ * mouse is released. Default is true.
+ */
+mxGraph.prototype.swimlaneSelectionEnabled = true;
+
+/**
+ * Variable: swimlaneNesting
+ * 
+ * Specifies if nesting of swimlanes is allowed. Default is true.
+ */
+mxGraph.prototype.swimlaneNesting = true;
+	
+/**
+ * Variable: swimlaneIndicatorColorAttribute
+ * 
+ * The attribute used to find the color for the indicator if the indicator
+ * color is set to 'swimlane'. Default is <mxConstants.STYLE_FILLCOLOR>.
+ */
+mxGraph.prototype.swimlaneIndicatorColorAttribute = mxConstants.STYLE_FILLCOLOR;
+
+/**
+ * Variable: imageBundles
+ * 
+ * Holds the list of image bundles.
+ */
+mxGraph.prototype.imageBundles = null;
+
+/**
+ * Variable: minFitScale
+ * 
+ * Specifies the minimum scale to be applied in <fit>. Default is 0.1. Set this
+ * to null to allow any value.
+ */
+mxGraph.prototype.minFitScale = 0.1;
+
+/**
+ * Variable: maxFitScale
+ * 
+ * Specifies the maximum scale to be applied in <fit>. Default is 8. Set this
+ * to null to allow any value.
+ */
+mxGraph.prototype.maxFitScale = 8;
+
+/**
+ * Variable: panDx
+ * 
+ * Current horizontal panning value. Default is 0.
+ */
+mxGraph.prototype.panDx = 0;
+
+/**
+ * Variable: panDy
+ * 
+ * Current vertical panning value. Default is 0.
+ */
+mxGraph.prototype.panDy = 0;
+
+/**
+ * Variable: collapsedImage
+ * 
+ * Specifies the <mxImage> to indicate a collapsed state.
+ * Default value is mxClient.imageBasePath + '/collapsed.gif'
+ */
+mxGraph.prototype.collapsedImage = new mxImage(mxClient.imageBasePath + '/collapsed.gif', 9, 9);
+
+/**
+ * Variable: expandedImage
+ * 
+ * Specifies the <mxImage> to indicate a expanded state.
+ * Default value is mxClient.imageBasePath + '/expanded.gif'
+ */
+mxGraph.prototype.expandedImage = new mxImage(mxClient.imageBasePath + '/expanded.gif', 9, 9);
+
+/**
+ * Variable: warningImage
+ * 
+ * Specifies the <mxImage> for the image to be used to display a warning
+ * overlay. See <setCellWarning>. Default value is mxClient.imageBasePath +
+ * '/warning'.  The extension for the image depends on the platform. It is
+ * '.png' on the Mac and '.gif' on all other platforms.
+ */
+mxGraph.prototype.warningImage = new mxImage(mxClient.imageBasePath + '/warning'+
+	((mxClient.IS_MAC) ? '.png' : '.gif'), 16, 16);
+
+/**
+ * Variable: alreadyConnectedResource
+ * 
+ * Specifies the resource key for the error message to be displayed in
+ * non-multigraphs when two vertices are already connected. If the resource
+ * for this key does not exist then the value is used as the error message.
+ * Default is 'alreadyConnected'.
+ */
+mxGraph.prototype.alreadyConnectedResource = (mxClient.language != 'none') ? 'alreadyConnected' : '';
+
+/**
+ * Variable: containsValidationErrorsResource
+ * 
+ * Specifies the resource key for the warning message to be displayed when
+ * a collapsed cell contains validation errors. If the resource for this
+ * key does not exist then the value is used as the warning message.
+ * Default is 'containsValidationErrors'.
+ */
+mxGraph.prototype.containsValidationErrorsResource = (mxClient.language != 'none') ? 'containsValidationErrors' : '';
+
+/**
+ * Variable: collapseExpandResource
+ * 
+ * Specifies the resource key for the tooltip on the collapse/expand icon.
+ * If the resource for this key does not exist then the value is used as
+ * the tooltip. Default is 'collapse-expand'.
+ */
+mxGraph.prototype.collapseExpandResource = (mxClient.language != 'none') ? 'collapse-expand' : '';
+
+/**
+ * Function: init
+ * 
+ * Initializes the <container> and creates the respective datastructures.
+ * 
+ * Parameters:
+ * 
+ * container - DOM node that will contain the graph display.
+ */
+mxGraph.prototype.init = function(container)
+{
+	this.container = container;
+	
+	// Initializes the in-place editor
+	this.cellEditor = this.createCellEditor();	
+
+	// Initializes the container using the view
+	this.view.init();
+	
+	// Updates the size of the container for the current graph
+	this.sizeDidChange();
+	
+	// Hides tooltips and resets tooltip timer if mouse leaves container
+	mxEvent.addListener(container, 'mouseleave', mxUtils.bind(this, function()
+	{
+		if (this.tooltipHandler != null)
+		{
+			this.tooltipHandler.hide();
+		}
+	}));
+
+	// Automatic deallocation of memory
+	if (mxClient.IS_IE)
+	{
+		mxEvent.addListener(window, 'unload', mxUtils.bind(this, function()
+		{
+			this.destroy();
+		}));
+		
+		// Disable shift-click for text
+		mxEvent.addListener(container, 'selectstart',
+			mxUtils.bind(this, function(evt)
+			{
+				return this.isEditing() || (!this.isMouseDown && !mxEvent.isShiftDown(evt));
+			})
+		);
+	}
+	
+	// Workaround for missing last shape and connect preview in IE8 standards
+	// mode if no initial graph displayed or no label for shape defined
+	if (document.documentMode == 8)
+	{
+		container.insertAdjacentHTML('beforeend', '<' + mxClient.VML_PREFIX + ':group' +
+			' style="DISPLAY: none;"></' + mxClient.VML_PREFIX + ':group>');
+	}
+};
+
+/**
+ * Function: createHandlers
+ * 
+ * Creates the tooltip-, panning-, connection- and graph-handler (in this
+ * order). This is called in the constructor before <init> is called.
+ */
+mxGraph.prototype.createHandlers = function()
+{
+	this.tooltipHandler = this.createTooltipHandler();
+	this.tooltipHandler.setEnabled(false);
+	this.selectionCellsHandler = this.createSelectionCellsHandler();
+	this.connectionHandler = this.createConnectionHandler();
+	this.connectionHandler.setEnabled(false);
+	this.graphHandler = this.createGraphHandler();
+	this.panningHandler = this.createPanningHandler();
+	this.panningHandler.panningEnabled = false;
+	this.popupMenuHandler = this.createPopupMenuHandler();
+};
+
+/**
+ * Function: createTooltipHandler
+ * 
+ * Creates and returns a new <mxTooltipHandler> to be used in this graph.
+ */
+mxGraph.prototype.createTooltipHandler = function()
+{
+	return new mxTooltipHandler(this);
+};
+
+/**
+ * Function: createSelectionCellsHandler
+ * 
+ * Creates and returns a new <mxTooltipHandler> to be used in this graph.
+ */
+mxGraph.prototype.createSelectionCellsHandler = function()
+{
+	return new mxSelectionCellsHandler(this);
+};
+
+/**
+ * Function: createConnectionHandler
+ * 
+ * Creates and returns a new <mxConnectionHandler> to be used in this graph.
+ */
+mxGraph.prototype.createConnectionHandler = function()
+{
+	return new mxConnectionHandler(this);
+};
+
+/**
+ * Function: createGraphHandler
+ * 
+ * Creates and returns a new <mxGraphHandler> to be used in this graph.
+ */
+mxGraph.prototype.createGraphHandler = function()
+{
+	return new mxGraphHandler(this);
+};
+
+/**
+ * Function: createPanningHandler
+ * 
+ * Creates and returns a new <mxPanningHandler> to be used in this graph.
+ */
+mxGraph.prototype.createPanningHandler = function()
+{
+	return new mxPanningHandler(this);
+};
+
+/**
+ * Function: createPopupMenuHandler
+ * 
+ * Creates and returns a new <mxPopupMenuHandler> to be used in this graph.
+ */
+mxGraph.prototype.createPopupMenuHandler = function()
+{
+	return new mxPopupMenuHandler(this);
+};
+
+/**
+ * Function: createSelectionModel
+ * 
+ * Creates a new <mxGraphSelectionModel> to be used in this graph.
+ */
+mxGraph.prototype.createSelectionModel = function()
+{
+	return new mxGraphSelectionModel(this);
+};
+
+/**
+ * Function: createStylesheet
+ * 
+ * Creates a new <mxGraphSelectionModel> to be used in this graph.
+ */
+mxGraph.prototype.createStylesheet = function()
+{
+	return new mxStylesheet();
+};
+
+/**
+ * Function: createGraphView
+ * 
+ * Creates a new <mxGraphView> to be used in this graph.
+ */
+mxGraph.prototype.createGraphView = function()
+{
+	return new mxGraphView(this);
+};
+ 
+/**
+ * Function: createCellRenderer
+ * 
+ * Creates a new <mxCellRenderer> to be used in this graph.
+ */
+mxGraph.prototype.createCellRenderer = function()
+{
+	return new mxCellRenderer();
+};
+
+/**
+ * Function: createCellEditor
+ * 
+ * Creates a new <mxCellEditor> to be used in this graph.
+ */
+mxGraph.prototype.createCellEditor = function()
+{
+	return new mxCellEditor(this);
+};
+
+/**
+ * Function: getModel
+ * 
+ * Returns the <mxGraphModel> that contains the cells.
+ */
+mxGraph.prototype.getModel = function()
+{
+	return this.model;
+};
+
+/**
+ * Function: getView
+ * 
+ * Returns the <mxGraphView> that contains the <mxCellStates>.
+ */
+mxGraph.prototype.getView = function()
+{
+	return this.view;
+};
+
+/**
+ * Function: getStylesheet
+ * 
+ * Returns the <mxStylesheet> that defines the style.
+ */
+mxGraph.prototype.getStylesheet = function()
+{
+	return this.stylesheet;
+};
+
+/**
+ * Function: setStylesheet
+ * 
+ * Sets the <mxStylesheet> that defines the style.
+ */
+mxGraph.prototype.setStylesheet = function(stylesheet)
+{
+	this.stylesheet = stylesheet;
+};
+
+/**
+ * Function: getSelectionModel
+ * 
+ * Returns the <mxGraphSelectionModel> that contains the selection.
+ */
+mxGraph.prototype.getSelectionModel = function()
+{
+	return this.selectionModel;
+};
+
+/**
+ * Function: setSelectionModel
+ * 
+ * Sets the <mxSelectionModel> that contains the selection.
+ */
+mxGraph.prototype.setSelectionModel = function(selectionModel)
+{
+	this.selectionModel = selectionModel;
+};
+
+/**
+ * Function: getSelectionCellsForChanges
+ * 
+ * Returns the cells to be selected for the given array of changes.
+ */
+mxGraph.prototype.getSelectionCellsForChanges = function(changes)
+{
+	var cells = [];
+	
+	for (var i = 0; i < changes.length; i++)
+	{
+		var change = changes[i];
+		
+		if (change.constructor != mxRootChange)
+		{
+			var cell = null;
+
+			if (change instanceof mxChildChange && change.previous == null)
+			{
+				cell = change.child;
+			}
+			else if (change.cell != null && change.cell instanceof mxCell)
+			{
+				cell = change.cell;
+			}
+			
+			if (cell != null && mxUtils.indexOf(cells, cell) < 0)
+			{
+				cells.push(cell);
+			}
+		}
+	}
+	
+	return this.getModel().getTopmostCells(cells);
+};
+
+/**
+ * Function: graphModelChanged
+ * 
+ * Called when the graph model changes. Invokes <processChange> on each
+ * item of the given array to update the view accordingly.
+ * 
+ * Parameters:
+ * 
+ * changes - Array that contains the individual changes.
+ */
+mxGraph.prototype.graphModelChanged = function(changes)
+{
+	for (var i = 0; i < changes.length; i++)
+	{
+		this.processChange(changes[i]);
+	}
+	
+	this.removeSelectionCells(this.getRemovedCellsForChanges(changes));
+	
+	this.view.validate();
+	this.sizeDidChange();
+};
+
+/**
+ * Function: getRemovedCellsForChanges
+ * 
+ * Returns the cells that have been removed from the model.
+ */
+mxGraph.prototype.getRemovedCellsForChanges = function(changes)
+{
+	var result = [];
+	
+	for (var i = 0; i < changes.length; i++)
+	{
+		var change = changes[i];
+		
+		// Resets the view settings, removes all cells and clears
+		// the selection if the root changes.
+		if (change instanceof mxRootChange)
+		{
+			break;
+		}
+		else if (change instanceof mxChildChange)
+		{
+			if (change.previous != null && change.parent == null)
+			{
+				result = result.concat(this.model.getDescendants(change.child));
+			}
+		}
+		else if (change instanceof mxVisibleChange)
+		{
+			result = result.concat(this.model.getDescendants(change.cell));
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: processChange
+ * 
+ * Processes the given change and invalidates the respective cached data
+ * in <view>. This fires a <root> event if the root has changed in the
+ * model.
+ * 
+ * Parameters:
+ * 
+ * change - Object that represents the change on the model.
+ */
+mxGraph.prototype.processChange = function(change)
+{
+	// Resets the view settings, removes all cells and clears
+	// the selection if the root changes.
+	if (change instanceof mxRootChange)
+	{
+		this.clearSelection();
+		this.setDefaultParent(null);
+		this.removeStateForCell(change.previous);
+		
+		if (this.resetViewOnRootChange)
+		{
+			this.view.scale = 1;
+			this.view.translate.x = 0;
+			this.view.translate.y = 0;
+		}
+
+		this.fireEvent(new mxEventObject(mxEvent.ROOT));
+	}
+	
+	// Adds or removes a child to the view by online invaliding
+	// the minimal required portions of the cache, namely, the
+	// old and new parent and the child.
+	else if (change instanceof mxChildChange)
+	{
+		var newParent = this.model.getParent(change.child);
+		this.view.invalidate(change.child, true, true);
+
+		if (newParent == null || this.isCellCollapsed(newParent))
+		{
+			this.view.invalidate(change.child, true, true);
+			this.removeStateForCell(change.child);
+			
+			// Handles special case of current root of view being removed
+			if (this.view.currentRoot == change.child)
+			{
+				this.home();
+			}
+		}
+ 
+		if (newParent != change.previous)
+		{
+			// Refreshes the collapse/expand icons on the parents
+			if (newParent != null)
+			{
+				this.view.invalidate(newParent, false, false);
+			}
+			
+			if (change.previous != null)
+			{
+				this.view.invalidate(change.previous, false, false);
+			}
+		}
+	}
+
+	// Handles two special cases where the shape does not need to be
+	// recreated from scratch, it only needs to be invalidated.
+	else if (change instanceof mxTerminalChange || change instanceof mxGeometryChange)
+	{
+		// Checks if the geometry has changed to avoid unnessecary revalidation
+		if (change instanceof mxTerminalChange || ((change.previous == null && change.geometry != null) ||
+			(change.previous != null && !change.previous.equals(change.geometry))))
+		{
+			this.view.invalidate(change.cell);
+		}
+	}
+
+	// Handles two special cases where only the shape, but no
+	// descendants need to be recreated
+	else if (change instanceof mxValueChange)
+	{
+		this.view.invalidate(change.cell, false, false);
+	}
+	
+	// Requires a new mxShape in JavaScript
+	else if (change instanceof mxStyleChange)
+	{
+		this.view.invalidate(change.cell, true, true);
+		var state = this.view.getState(change.cell);
+		
+		if (state != null)
+		{
+			state.style = null;
+		}
+	}
+	
+	// Removes the state from the cache by default
+	else if (change.cell != null && change.cell instanceof mxCell)
+	{
+		this.removeStateForCell(change.cell);
+	}
+};
+
+/**
+ * Function: removeStateForCell
+ * 
+ * Removes all cached information for the given cell and its descendants.
+ * This is called when a cell was removed from the model.
+ * 
+ * Paramters:
+ * 
+ * cell - <mxCell> that was removed from the model.
+ */
+mxGraph.prototype.removeStateForCell = function(cell)
+{
+	var childCount = this.model.getChildCount(cell);
+	
+	for (var i = 0; i < childCount; i++)
+	{
+		this.removeStateForCell(this.model.getChildAt(cell, i));
+	}
+
+	this.view.invalidate(cell, false, true);
+	this.view.removeState(cell);
+};
+
+/**
+ * Group: Overlays
+ */
+
+/**
+ * Function: addCellOverlay
+ * 
+ * Adds an <mxCellOverlay> for the specified cell. This method fires an
+ * <addoverlay> event and returns the new <mxCellOverlay>.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to add the overlay for.
+ * overlay - <mxCellOverlay> to be added for the cell.
+ */
+mxGraph.prototype.addCellOverlay = function(cell, overlay)
+{
+	if (cell.overlays == null)
+	{
+		cell.overlays = [];
+	}
+	
+	cell.overlays.push(overlay);
+
+	var state = this.view.getState(cell);
+
+	// Immediately updates the cell display if the state exists
+	if (state != null)
+	{
+		this.cellRenderer.redraw(state);
+	}
+	
+	this.fireEvent(new mxEventObject(mxEvent.ADD_OVERLAY,
+			'cell', cell, 'overlay', overlay));
+	
+	return overlay;
+};
+
+/**
+ * Function: getCellOverlays
+ * 
+ * Returns the array of <mxCellOverlays> for the given cell or null, if
+ * no overlays are defined.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose overlays should be returned.
+ */
+mxGraph.prototype.getCellOverlays = function(cell)
+{
+	return cell.overlays;
+};
+
+/**
+ * Function: removeCellOverlay
+ * 
+ * Removes and returns the given <mxCellOverlay> from the given cell. This
+ * method fires a <removeoverlay> event. If no overlay is given, then all
+ * overlays are removed using <removeOverlays>.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose overlay should be removed.
+ * overlay - Optional <mxCellOverlay> to be removed.
+ */
+mxGraph.prototype.removeCellOverlay = function(cell, overlay)
+{
+	if (overlay == null)
+	{
+		this.removeCellOverlays(cell);
+	}
+	else
+	{
+		var index = mxUtils.indexOf(cell.overlays, overlay);
+		
+		if (index >= 0)
+		{
+			cell.overlays.splice(index, 1);
+			
+			if (cell.overlays.length == 0)
+			{
+				cell.overlays = null;
+			}
+			
+			// Immediately updates the cell display if the state exists
+			var state = this.view.getState(cell);
+			
+			if (state != null)
+			{
+				this.cellRenderer.redraw(state);
+			}
+			
+			this.fireEvent(new mxEventObject(mxEvent.REMOVE_OVERLAY,
+					'cell', cell, 'overlay', overlay));	
+		}
+		else
+		{
+			overlay = null;
+		}
+	}
+	
+	return overlay;
+};
+
+/**
+ * Function: removeCellOverlays
+ * 
+ * Removes all <mxCellOverlays> from the given cell. This method
+ * fires a <removeoverlay> event for each <mxCellOverlay> and returns
+ * the array of <mxCellOverlays> that was removed from the cell.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose overlays should be removed
+ */
+mxGraph.prototype.removeCellOverlays = function(cell)
+{
+	var overlays = cell.overlays;
+	
+	if (overlays != null)
+	{
+		cell.overlays = null;
+		
+		// Immediately updates the cell display if the state exists
+		var state = this.view.getState(cell);
+		
+		if (state != null)
+		{
+			this.cellRenderer.redraw(state);
+		}
+		
+		for (var i = 0; i < overlays.length; i++)
+		{
+			this.fireEvent(new mxEventObject(mxEvent.REMOVE_OVERLAY,
+					'cell', cell, 'overlay', overlays[i]));
+		}
+	}
+	
+	return overlays;
+};
+
+/**
+ * Function: clearCellOverlays
+ * 
+ * Removes all <mxCellOverlays> in the graph for the given cell and all its
+ * descendants. If no cell is specified then all overlays are removed from
+ * the graph. This implementation uses <removeCellOverlays> to remove the
+ * overlays from the individual cells.
+ * 
+ * Parameters:
+ * 
+ * cell - Optional <mxCell> that represents the root of the subtree to
+ * remove the overlays from. Default is the root in the model.
+ */
+mxGraph.prototype.clearCellOverlays = function(cell)
+{
+	cell = (cell != null) ? cell : this.model.getRoot();
+	this.removeCellOverlays(cell);
+	
+	// Recursively removes all overlays from the children
+	var childCount = this.model.getChildCount(cell);
+	
+	for (var i = 0; i < childCount; i++)
+	{
+		var child = this.model.getChildAt(cell, i);
+		this.clearCellOverlays(child); // recurse
+	}
+};
+
+/**
+ * Function: setCellWarning
+ * 
+ * Creates an overlay for the given cell using the warning and image or
+ * <warningImage> and returns the new <mxCellOverlay>. The warning is
+ * displayed as a tooltip in a red font and may contain HTML markup. If
+ * the warning is null or a zero length string, then all overlays are
+ * removed from the cell.
+ * 
+ * Example:
+ * 
+ * (code)
+ * graph.setCellWarning(cell, '<b>Warning:</b>: Hello, World!');
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose warning should be set.
+ * warning - String that represents the warning to be displayed.
+ * img - Optional <mxImage> to be used for the overlay. Default is
+ * <warningImage>.
+ * isSelect - Optional boolean indicating if a click on the overlay
+ * should select the corresponding cell. Default is false.
+ */
+mxGraph.prototype.setCellWarning = function(cell, warning, img, isSelect)
+{
+	if (warning != null && warning.length > 0)
+	{
+		img = (img != null) ? img : this.warningImage;
+		
+		// Creates the overlay with the image and warning
+		var overlay = new mxCellOverlay(img,
+			'<font color=red>'+warning+'</font>');
+		
+		// Adds a handler for single mouseclicks to select the cell
+		if (isSelect)
+		{
+			overlay.addListener(mxEvent.CLICK,
+				mxUtils.bind(this, function(sender, evt)
+				{
+					if (this.isEnabled())
+					{
+						this.setSelectionCell(cell);
+					}
+				})
+			);
+		}
+		
+		// Sets and returns the overlay in the graph
+		return this.addCellOverlay(cell, overlay);
+	}
+	else
+	{
+		this.removeCellOverlays(cell);
+	}
+	
+	return null;
+};
+
+/**
+ * Group: In-place editing
+ */
+
+/**
+ * Function: startEditing
+ * 
+ * Calls <startEditingAtCell> using the given cell or the first selection
+ * cell.
+ * 
+ * Parameters:
+ * 
+ * evt - Optional mouse event that triggered the editing.
+ */
+mxGraph.prototype.startEditing = function(evt)
+{
+	this.startEditingAtCell(null, evt);
+};
+
+/**
+ * Function: startEditingAtCell
+ * 
+ * Fires a <startEditing> event and invokes <mxCellEditor.startEditing>
+ * on <editor>. After editing was started, a <editingStarted> event is
+ * fired.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to start the in-place editor for.
+ * evt - Optional mouse event that triggered the editing.
+ */
+mxGraph.prototype.startEditingAtCell = function(cell, evt)
+{
+	if (evt == null || !mxEvent.isMultiTouchEvent(evt))
+	{
+		if (cell == null)
+		{
+			cell = this.getSelectionCell();
+			
+			if (cell != null && !this.isCellEditable(cell))
+			{
+				cell = null;
+			}
+		}
+	
+		if (cell != null)
+		{
+			this.fireEvent(new mxEventObject(mxEvent.START_EDITING,
+					'cell', cell, 'event', evt));
+			this.cellEditor.startEditing(cell, evt);
+			this.fireEvent(new mxEventObject(mxEvent.EDITING_STARTED,
+					'cell', cell, 'event', evt));
+		}
+	}
+};
+
+/**
+ * Function: getEditingValue
+ * 
+ * Returns the initial value for in-place editing. This implementation
+ * returns <convertValueToString> for the given cell. If this function is
+ * overridden, then <mxGraphModel.valueForCellChanged> should take care
+ * of correctly storing the actual new value inside the user object.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> for which the initial editing value should be returned.
+ * evt - Optional mouse event that triggered the editor.
+ */
+mxGraph.prototype.getEditingValue = function(cell, evt)
+{
+	return this.convertValueToString(cell);
+};
+
+/**
+ * Function: stopEditing
+ * 
+ * Stops the current editing  and fires a <editingStopped> event.
+ * 
+ * Parameters:
+ * 
+ * cancel - Boolean that specifies if the current editing value
+ * should be stored.
+ */
+mxGraph.prototype.stopEditing = function(cancel)
+{
+	this.cellEditor.stopEditing(cancel);
+	this.fireEvent(new mxEventObject(mxEvent.EDITING_STOPPED, 'cancel', cancel));
+};
+
+/**
+ * Function: labelChanged
+ * 
+ * Sets the label of the specified cell to the given value using
+ * <cellLabelChanged> and fires <mxEvent.LABEL_CHANGED> while the
+ * transaction is in progress. Returns the cell whose label was changed.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose label should be changed.
+ * value - New label to be assigned.
+ * evt - Optional event that triggered the change.
+ */
+mxGraph.prototype.labelChanged = function(cell, value, evt)
+{
+	this.model.beginUpdate();
+	try
+	{
+		var old = cell.value;
+		this.cellLabelChanged(cell, value, this.isAutoSizeCell(cell));
+		this.fireEvent(new mxEventObject(mxEvent.LABEL_CHANGED,
+			'cell', cell, 'value', value, 'old', old, 'event', evt));
+	}
+	finally
+	{
+		this.model.endUpdate();
+	}
+	
+	return cell;
+};
+
+/**
+ * Function: cellLabelChanged
+ * 
+ * Sets the new label for a cell. If autoSize is true then
+ * <cellSizeUpdated> will be called.
+ * 
+ * In the following example, the function is extended to map changes to
+ * attributes in an XML node, as shown in <convertValueToString>.
+ * Alternatively, the handling of this can be implemented as shown in
+ * <mxGraphModel.valueForCellChanged> without the need to clone the
+ * user object.
+ * 
+ * (code)
+ * var graphCellLabelChanged = graph.cellLabelChanged;
+ * graph.cellLabelChanged = function(cell, newValue, autoSize)
+ * {
+ * 	// Cloned for correct undo/redo
+ * 	var elt = cell.value.cloneNode(true);
+ *  elt.setAttribute('label', newValue);
+ *  
+ *  newValue = elt;
+ *  graphCellLabelChanged.apply(this, arguments);
+ * };
+ * (end) 
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose label should be changed.
+ * value - New label to be assigned.
+ * autoSize - Boolean that specifies if <cellSizeUpdated> should be called.
+ */
+mxGraph.prototype.cellLabelChanged = function(cell, value, autoSize)
+{
+	this.model.beginUpdate();
+	try
+	{
+		this.model.setValue(cell, value);
+		
+		if (autoSize)
+		{
+			this.cellSizeUpdated(cell, false);
+		}
+	}
+	finally
+	{
+		this.model.endUpdate();
+	}
+};
+
+/**
+ * Group: Event processing
+ */
+
+/**
+ * Function: escape
+ * 
+ * Processes an escape keystroke.
+ * 
+ * Parameters:
+ * 
+ * evt - Mouseevent that represents the keystroke.
+ */
+mxGraph.prototype.escape = function(evt)
+{
+	this.fireEvent(new mxEventObject(mxEvent.ESCAPE, 'event', evt));
+};
+
+/**
+ * Function: click
+ * 
+ * Processes a singleclick on an optional cell and fires a <click> event.
+ * The click event is fired initially. If the graph is enabled and the
+ * event has not been consumed, then the cell is selected using
+ * <selectCellForEvent> or the selection is cleared using
+ * <clearSelection>. The events consumed state is set to true if the
+ * corresponding <mxMouseEvent> has been consumed.
+ *
+ * To handle a click event, use the following code.
+ * 
+ * (code)
+ * graph.addListener(mxEvent.CLICK, function(sender, evt)
+ * {
+ *   var e = evt.getProperty('event'); // mouse event
+ *   var cell = evt.getProperty('cell'); // cell may be null
+ *   
+ *   if (cell != null)
+ *   {
+ *     // Do something useful with cell and consume the event
+ *     evt.consume();
+ *   }
+ * });
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * me - <mxMouseEvent> that represents the single click.
+ */
+mxGraph.prototype.click = function(me)
+{
+	var evt = me.getEvent();
+	var cell = me.getCell();
+	var mxe = new mxEventObject(mxEvent.CLICK, 'event', evt, 'cell', cell);
+	
+	if (me.isConsumed())
+	{
+		mxe.consume();
+	}
+	
+	this.fireEvent(mxe);
+	
+	// Handles the event if it has not been consumed
+	if (this.isEnabled() && !mxEvent.isConsumed(evt) && !mxe.isConsumed())
+	{
+		if (cell != null)
+		{
+			if (this.isTransparentClickEvent(evt))
+			{
+				var active = false;
+				
+				var tmp = this.getCellAt(me.graphX, me.graphY, null, null, null, mxUtils.bind(this, function(state)
+				{
+					var selected = this.isCellSelected(state.cell);
+					active = active || selected;
+					
+					return !active || selected;
+				}));
+				
+				if (tmp != null)
+				{
+					cell = tmp;
+				}
+			}
+			
+			this.selectCellForEvent(cell, evt);
+		}
+		else
+		{
+			var swimlane = null;
+			
+			if (this.isSwimlaneSelectionEnabled())
+			{
+				// Gets the swimlane at the location (includes
+				// content area of swimlanes)
+				swimlane = this.getSwimlaneAt(me.getGraphX(), me.getGraphY());
+			}
+
+			// Selects the swimlane and consumes the event
+			if (swimlane != null)
+			{
+				this.selectCellForEvent(swimlane, evt);
+			}
+			
+			// Ignores the event if the control key is pressed
+			else if (!this.isToggleEvent(evt))
+			{
+				this.clearSelection();
+			}
+		}
+	}
+};
+
+/**
+ * Function: dblClick
+ * 
+ * Processes a doubleclick on an optional cell and fires a <dblclick>
+ * event. The event is fired initially. If the graph is enabled and the
+ * event has not been consumed, then <edit> is called with the given
+ * cell. The event is ignored if no cell was specified.
+ *
+ * Example for overriding this method.
+ *
+ * (code)
+ * graph.dblClick = function(evt, cell)
+ * {
+ *   var mxe = new mxEventObject(mxEvent.DOUBLE_CLICK, 'event', evt, 'cell', cell);
+ *   this.fireEvent(mxe);
+ *   
+ *   if (this.isEnabled() && !mxEvent.isConsumed(evt) && !mxe.isConsumed())
+ *   {
+ * 	   mxUtils.alert('Hello, World!');
+ *     mxe.consume();
+ *   }
+ * }
+ * (end)
+ * 
+ * Example listener for this event.
+ * 
+ * (code)
+ * graph.addListener(mxEvent.DOUBLE_CLICK, function(sender, evt)
+ * {
+ *   var cell = evt.getProperty('cell');
+ *   // do something with the cell and consume the
+ *   // event to prevent in-place editing from start
+ * });
+ * (end) 
+ * 
+ * Parameters:
+ * 
+ * evt - Mouseevent that represents the doubleclick.
+ * cell - Optional <mxCell> under the mousepointer.
+ */
+mxGraph.prototype.dblClick = function(evt, cell)
+{
+	var mxe = new mxEventObject(mxEvent.DOUBLE_CLICK, 'event', evt, 'cell', cell);
+	this.fireEvent(mxe);
+	
+	// Handles the event if it has not been consumed
+	if (this.isEnabled() && !mxEvent.isConsumed(evt) && !mxe.isConsumed() &&
+		cell != null && this.isCellEditable(cell) && !this.isEditing(cell))
+	{
+		this.startEditingAtCell(cell, evt);
+		mxEvent.consume(evt);
+	}
+};
+
+/**
+ * Function: tapAndHold
+ * 
+ * Handles the <mxMouseEvent> by highlighting the <mxCellState>.
+ * 
+ * Parameters:
+ * 
+ * me - <mxMouseEvent> that represents the touch event.
+ * state - Optional <mxCellState> that is associated with the event.
+ */
+mxGraph.prototype.tapAndHold = function(me)
+{
+	var evt = me.getEvent();
+	var mxe = new mxEventObject(mxEvent.TAP_AND_HOLD, 'event', evt, 'cell', me.getCell());
+
+	// LATER: Check if event should be consumed if me is consumed
+	this.fireEvent(mxe);
+
+	if (mxe.isConsumed())
+	{
+		// Resets the state of the panning handler
+		this.panningHandler.panningTrigger = false;
+	}
+	
+	// Handles the event if it has not been consumed
+	if (this.isEnabled() && !mxEvent.isConsumed(evt) && !mxe.isConsumed() && this.connectionHandler.isEnabled())
+	{
+		var state = this.view.getState(this.connectionHandler.marker.getCell(me));
+
+		if (state != null)
+		{
+			this.connectionHandler.marker.currentColor = this.connectionHandler.marker.validColor;
+			this.connectionHandler.marker.markedState = state;
+			this.connectionHandler.marker.mark();
+			
+			this.connectionHandler.first = new mxPoint(me.getGraphX(), me.getGraphY());
+			this.connectionHandler.edgeState = this.connectionHandler.createEdgeState(me);
+			this.connectionHandler.previous = state;
+			this.connectionHandler.fireEvent(new mxEventObject(mxEvent.START, 'state', this.connectionHandler.previous));
+		}
+	}
+};
+
+/**
+ * Function: scrollPointToVisible
+ * 
+ * Scrolls the graph to the given point, extending the graph container if
+ * specified.
+ */
+mxGraph.prototype.scrollPointToVisible = function(x, y, extend, border)
+{
+	if (!this.timerAutoScroll && (this.ignoreScrollbars || mxUtils.hasScrollbars(this.container)))
+	{
+		var c = this.container;
+		border = (border != null) ? border : 20;
+		
+		if (x >= c.scrollLeft && y >= c.scrollTop && x <= c.scrollLeft + c.clientWidth &&
+			y <= c.scrollTop + c.clientHeight)
+		{
+			var dx = c.scrollLeft + c.clientWidth - x;
+			
+			if (dx < border)
+			{
+				var old = c.scrollLeft;
+				c.scrollLeft += border - dx;
+
+				// Automatically extends the canvas size to the bottom, right
+				// if the event is outside of the canvas and the edge of the
+				// canvas has been reached. Notes: Needs fix for IE.
+				if (extend && old == c.scrollLeft)
+				{
+					if (this.dialect == mxConstants.DIALECT_SVG)
+					{
+						var root = this.view.getDrawPane().ownerSVGElement;
+						var width = this.container.scrollWidth + border - dx;
+						
+						// Updates the clipping region. This is an expensive
+						// operation that should not be executed too often.
+						root.style.width = width + 'px';
+					}
+					else
+					{
+						var width = Math.max(c.clientWidth, c.scrollWidth) + border - dx;
+						var canvas = this.view.getCanvas();
+						canvas.style.width = width + 'px';
+					}
+					
+					c.scrollLeft += border - dx;
+				}
+			}
+			else
+			{
+				dx = x - c.scrollLeft;
+				
+				if (dx < border)
+				{
+					c.scrollLeft -= border - dx;
+				}
+			}
+			
+			var dy = c.scrollTop + c.clientHeight - y;
+			
+			if (dy < border)
+			{
+				var old = c.scrollTop;
+				c.scrollTop += border - dy;
+
+				if (old == c.scrollTop && extend)
+				{
+					if (this.dialect == mxConstants.DIALECT_SVG)
+					{
+						var root = this.view.getDrawPane().ownerSVGElement;
+						var height = this.container.scrollHeight + border - dy;
+						
+						// Updates the clipping region. This is an expensive
+						// operation that should not be executed too often.
+						root.style.height = height + 'px';
+					}
+					else
+					{
+						var height = Math.max(c.clientHeight, c.scrollHeight) + border - dy;
+						var canvas = this.view.getCanvas();
+						canvas.style.height = height + 'px';
+					}
+					
+					c.scrollTop += border - dy;
+				}
+			}
+			else
+			{
+				dy = y - c.scrollTop;
+				
+				if (dy < border)
+				{
+					c.scrollTop -= border - dy;
+				}
+			}
+		}
+	}
+	else if (this.allowAutoPanning && !this.panningHandler.isActive())
+	{
+		if (this.panningManager == null)
+		{
+			this.panningManager = this.createPanningManager();
+		}
+
+		this.panningManager.panTo(x + this.panDx, y + this.panDy);
+	}
+};
+
+
+/**
+ * Function: createPanningManager
+ * 
+ * Creates and returns an <mxPanningManager>.
+ */
+mxGraph.prototype.createPanningManager = function()
+{
+	return new mxPanningManager(this);
+};
+
+/**
+ * Function: getBorderSizes
+ * 
+ * Returns the size of the border and padding on all four sides of the
+ * container. The left, top, right and bottom borders are stored in the x, y,
+ * width and height of the returned <mxRectangle>, respectively.
+ */
+mxGraph.prototype.getBorderSizes = function()
+{
+	var css = mxUtils.getCurrentStyle(this.container);
+	
+	return new mxRectangle(mxUtils.parseCssNumber(css.paddingLeft) +
+			((css.borderLeftStyle != 'none') ? mxUtils.parseCssNumber(css.borderLeftWidth) : 0),
+		mxUtils.parseCssNumber(css.paddingTop) +
+			((css.borderTopStyle != 'none') ? mxUtils.parseCssNumber(css.borderTopWidth) : 0),
+		mxUtils.parseCssNumber(css.paddingRight) +
+			((css.borderRightStyle != 'none') ? mxUtils.parseCssNumber(css.borderRightWidth) : 0),
+		mxUtils.parseCssNumber(css.paddingBottom) +
+			((css.borderBottomStyle != 'none') ? mxUtils.parseCssNumber(css.borderBottomWidth) : 0));
+};
+
+/**
+ * Function: getPreferredPageSize
+ * 
+ * Returns the preferred size of the background page if <preferPageSize> is true.
+ */
+mxGraph.prototype.getPreferredPageSize = function(bounds, width, height)
+{
+	var scale = this.view.scale;
+	var tr = this.view.translate;
+	var fmt = this.pageFormat;
+	var ps = this.pageScale;
+	var page = new mxRectangle(0, 0, Math.ceil(fmt.width * ps), Math.ceil(fmt.height * ps));
+	
+	var hCount = (this.pageBreaksVisible) ? Math.ceil(width / page.width) : 1;
+	var vCount = (this.pageBreaksVisible) ? Math.ceil(height / page.height) : 1;
+	
+	return new mxRectangle(0, 0, hCount * page.width + 2 + tr.x, vCount * page.height + 2 + tr.y);
+};
+
+/**
+ * Function: fit
+ *
+ * Scales the graph such that the complete diagram fits into <container> and
+ * returns the current scale in the view. To fit an initial graph prior to
+ * rendering, set <mxGraphView.rendering> to false prior to changing the model
+ * and execute the following after changing the model.
+ * 
+ * (code)
+ * graph.fit();
+ * graph.view.rendering = true;
+ * graph.refresh();
+ * (end)
+ * 
+ * To fit and center the graph, the following code can be used.
+ * 
+ * (code)
+ * var margin = 2;
+ * var max = 3;
+ * 
+ * var bounds = graph.getGraphBounds();
+ * var cw = graph.container.clientWidth - margin;
+ * var ch = graph.container.clientHeight - margin;
+ * var w = bounds.width / graph.view.scale;
+ * var h = bounds.height / graph.view.scale;
+ * var s = Math.min(max, Math.min(cw / w, ch / h));
+ * 
+ * graph.view.scaleAndTranslate(s,
+ *   (margin + cw - w * s) / (2 * s) - bounds.x / graph.view.scale,
+ *   (margin + ch - h * s) / (2 * s) - bounds.y / graph.view.scale);
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * border - Optional number that specifies the border. Default is <border>.
+ * keepOrigin - Optional boolean that specifies if the translate should be
+ * changed. Default is false.
+ * margin - Optional margin in pixels. Default is 0.
+ * enabled - Optional boolean that specifies if the scale should be set or
+ * just returned. Default is true.
+ * ignoreWidth - Optional boolean that specifies if the width should be
+ * ignored. Default is false.
+ * ignoreHeight - Optional boolean that specifies if the height should be
+ * ignored. Default is false.
+ */
+mxGraph.prototype.fit = function(border, keepOrigin, margin, enabled, ignoreWidth, ignoreHeight)
+{
+	if (this.container != null)
+	{
+		border = (border != null) ? border : this.getBorder();
+		keepOrigin = (keepOrigin != null) ? keepOrigin : false;
+		margin = (margin != null) ? margin : 0;
+		enabled = (enabled != null) ? enabled : true;
+		ignoreWidth = (ignoreWidth != null) ? ignoreWidth : false;
+		ignoreHeight = (ignoreHeight != null) ? ignoreHeight : false;
+		
+		// Adds spacing and border from css
+		var cssBorder = this.getBorderSizes();
+		var w1 = this.container.offsetWidth - cssBorder.x - cssBorder.width - 1;
+		var h1 = this.container.offsetHeight - cssBorder.y - cssBorder.height - 1;
+		var bounds = this.view.getGraphBounds();
+		
+		if (bounds.width > 0 && bounds.height > 0)
+		{
+			if (keepOrigin && bounds.x != null && bounds.y != null)
+			{
+				bounds = bounds.clone();
+				bounds.width += bounds.x;
+				bounds.height += bounds.y;
+				bounds.x = 0;
+				bounds.y = 0;
+			}
+			
+			// LATER: Use unscaled bounding boxes to fix rounding errors
+			var s = this.view.scale;
+			var w2 = bounds.width / s;
+			var h2 = bounds.height / s;
+			
+			// Fits to the size of the background image if required
+			if (this.backgroundImage != null)
+			{
+				w2 = Math.max(w2, this.backgroundImage.width - bounds.x / s);
+				h2 = Math.max(h2, this.backgroundImage.height - bounds.y / s);
+			}
+			
+			var b = ((keepOrigin) ? border : 2 * border) + margin;
+
+			w1 -= b;
+			h1 -= b;
+			
+			var s2 = (((ignoreWidth) ? h1 / h2 : (ignoreHeight) ? w1 / w2 :
+				Math.min(w1 / w2, h1 / h2)));
+			
+			if (this.minFitScale != null)
+			{
+				s2 = Math.max(s2, this.minFitScale);
+			}
+			
+			if (this.maxFitScale != null)
+			{
+				s2 = Math.min(s2, this.maxFitScale);
+			}
+	
+			if (enabled)
+			{
+				if (!keepOrigin)
+				{
+					if (!mxUtils.hasScrollbars(this.container))
+					{
+						var x0 = (bounds.x != null) ? Math.floor(this.view.translate.x - bounds.x / s + border / s2 + margin / 2) : border;
+						var y0 = (bounds.y != null) ? Math.floor(this.view.translate.y - bounds.y / s + border / s2 + margin / 2) : border;
+
+						this.view.scaleAndTranslate(s2, x0, y0);
+					}
+					else
+					{
+						this.view.setScale(s2);
+						var b2 = this.getGraphBounds();
+						
+						if (b2.x != null)
+						{
+							this.container.scrollLeft = b2.x;
+						}
+						
+						if (b2.y != null)
+						{
+							this.container.scrollTop = b2.y;
+						}
+					}
+				}
+				else if (this.view.scale != s2)
+				{
+					this.view.setScale(s2);
+				}
+			}
+			else
+			{
+				return s2;
+			}
+		}
+	}
+
+	return this.view.scale;
+};
+
+/**
+ * Function: sizeDidChange
+ * 
+ * Called when the size of the graph has changed. This implementation fires
+ * a <size> event after updating the clipping region of the SVG element in
+ * SVG-bases browsers.
+ */
+mxGraph.prototype.sizeDidChange = function()
+{
+	var bounds = this.getGraphBounds();
+	
+	if (this.container != null)
+	{
+		var border = this.getBorder();
+		
+		var width = Math.max(0, bounds.x + bounds.width + border);
+		var height = Math.max(0, bounds.y + bounds.height + border);
+		
+		if (this.minimumContainerSize != null)
+		{
+			width = Math.max(width, this.minimumContainerSize.width);
+			height = Math.max(height, this.minimumContainerSize.height);
+		}
+
+		if (this.resizeContainer)
+		{
+			this.doResizeContainer(width, height);
+		}
+
+		if (this.preferPageSize || (!mxClient.IS_IE && this.pageVisible))
+		{
+			var size = this.getPreferredPageSize(bounds, Math.max(1, width), Math.max(1, height));
+			
+			if (size != null)
+			{
+				width = size.width * this.view.scale;
+				height = size.height * this.view.scale;
+			}
+		}
+		
+		if (this.minimumGraphSize != null)
+		{
+			width = Math.max(width, this.minimumGraphSize.width * this.view.scale);
+			height = Math.max(height, this.minimumGraphSize.height * this.view.scale);
+		}
+
+		width = Math.ceil(width);
+		height = Math.ceil(height);
+
+		if (this.dialect == mxConstants.DIALECT_SVG)
+		{
+			var root = this.view.getDrawPane().ownerSVGElement;
+			
+			root.style.minWidth = Math.max(1, width) + 'px';
+			root.style.minHeight = Math.max(1, height) + 'px';
+			root.style.width = '100%';
+			root.style.height = '100%';
+		}
+		else
+		{
+			if (mxClient.IS_QUIRKS)
+			{
+				// Quirks mode does not support minWidth/-Height
+				this.view.updateHtmlCanvasSize(Math.max(1, width), Math.max(1, height));
+			}
+			else
+			{
+				this.view.canvas.style.minWidth = Math.max(1, width) + 'px';
+				this.view.canvas.style.minHeight = Math.max(1, height) + 'px';
+			}
+		}
+		
+		this.updatePageBreaks(this.pageBreaksVisible, width, height);
+	}
+
+	this.fireEvent(new mxEventObject(mxEvent.SIZE, 'bounds', bounds));
+};
+
+/**
+ * Function: doResizeContainer
+ * 
+ * Resizes the container for the given graph width and height.
+ */
+mxGraph.prototype.doResizeContainer = function(width, height)
+{
+	if (this.maximumContainerSize != null)
+	{
+		width = Math.min(this.maximumContainerSize.width, width);
+		height = Math.min(this.maximumContainerSize.height, height);
+	}
+
+	this.container.style.width = Math.ceil(width) + 'px';
+	this.container.style.height = Math.ceil(height) + 'px';
+};
+
+/**
+ * Function: updatePageBreaks
+ * 
+ * Invokes from <sizeDidChange> to redraw the page breaks.
+ * 
+ * Parameters:
+ * 
+ * visible - Boolean that specifies if page breaks should be shown.
+ * width - Specifies the width of the container in pixels.
+ * height - Specifies the height of the container in pixels.
+ */
+mxGraph.prototype.updatePageBreaks = function(visible, width, height)
+{
+	var scale = this.view.scale;
+	var tr = this.view.translate;
+	var fmt = this.pageFormat;
+	var ps = scale * this.pageScale;
+	var bounds = new mxRectangle(0, 0, fmt.width * ps, fmt.height * ps);
+
+	var gb = mxRectangle.fromRectangle(this.getGraphBounds());
+	gb.width = Math.max(1, gb.width);
+	gb.height = Math.max(1, gb.height);
+	
+	bounds.x = Math.floor((gb.x - tr.x * scale) / bounds.width) * bounds.width + tr.x * scale;
+	bounds.y = Math.floor((gb.y - tr.y * scale) / bounds.height) * bounds.height + tr.y * scale;
+	
+	gb.width = Math.ceil((gb.width + (gb.x - bounds.x)) / bounds.width) * bounds.width;
+	gb.height = Math.ceil((gb.height + (gb.y - bounds.y)) / bounds.height) * bounds.height;
+	
+	// Does not show page breaks if the scale is too small
+	visible = visible && Math.min(bounds.width, bounds.height) > this.minPageBreakDist;
+
+	var horizontalCount = (visible) ? Math.ceil(gb.height / bounds.height) + 1 : 0;
+	var verticalCount = (visible) ? Math.ceil(gb.width / bounds.width) + 1 : 0;
+	var right = (verticalCount - 1) * bounds.width;
+	var bottom = (horizontalCount - 1) * bounds.height;
+	
+	if (this.horizontalPageBreaks == null && horizontalCount > 0)
+	{
+		this.horizontalPageBreaks = [];
+	}
+
+	if (this.verticalPageBreaks == null && verticalCount > 0)
+	{
+		this.verticalPageBreaks = [];
+	}
+	
+	var drawPageBreaks = mxUtils.bind(this, function(breaks)
+	{
+		if (breaks != null)
+		{
+			var count = (breaks == this.horizontalPageBreaks) ? horizontalCount : verticalCount; 
+			
+			for (var i = 0; i <= count; i++)
+			{
+				var pts = (breaks == this.horizontalPageBreaks) ?
+					[new mxPoint(Math.round(bounds.x), Math.round(bounds.y + i * bounds.height)),
+			         new mxPoint(Math.round(bounds.x + right), Math.round(bounds.y + i * bounds.height))] :
+			        [new mxPoint(Math.round(bounds.x + i * bounds.width), Math.round(bounds.y)),
+			         new mxPoint(Math.round(bounds.x + i * bounds.width), Math.round(bounds.y + bottom))];
+
+				if (breaks[i] != null)
+				{
+					breaks[i].points = pts;
+					breaks[i].redraw();
+				}
+				else
+				{
+					var pageBreak = new mxPolyline(pts, this.pageBreakColor);
+					pageBreak.dialect = this.dialect;
+					pageBreak.pointerEvents = false;
+					pageBreak.isDashed = this.pageBreakDashed;
+					pageBreak.init(this.view.backgroundPane);
+					pageBreak.redraw();
+					
+					breaks[i] = pageBreak;
+				}
+			}
+			
+			for (var i = count; i < breaks.length; i++)
+			{
+				breaks[i].destroy();
+			}
+			
+			breaks.splice(count, breaks.length - count);
+		}
+	});
+	
+	drawPageBreaks(this.horizontalPageBreaks);
+	drawPageBreaks(this.verticalPageBreaks);
+};
+
+/**
+ * Group: Cell styles
+ */
+
+/**
+ * Function: getCellStyle
+ * 
+ * Returns an array of key, value pairs representing the cell style for the
+ * given cell. If no string is defined in the model that specifies the
+ * style, then the default style for the cell is returned or <EMPTY_ARRAY>,
+ * if not style can be found. Note: You should try and get the cell state
+ * for the given cell and use the cached style in the state before using
+ * this method.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose style should be returned as an array.
+ */
+mxGraph.prototype.getCellStyle = function(cell)
+{
+	var stylename = this.model.getStyle(cell);
+	var style = null;
+	
+	// Gets the default style for the cell
+	if (this.model.isEdge(cell))
+	{
+		style = this.stylesheet.getDefaultEdgeStyle();
+	}
+	else
+	{
+		style = this.stylesheet.getDefaultVertexStyle();
+	}
+	
+	// Resolves the stylename using the above as the default
+	if (stylename != null)
+	{
+		style = this.postProcessCellStyle(this.stylesheet.getCellStyle(stylename, style));
+	}
+	
+	// Returns a non-null value if no style can be found
+	if (style == null)
+	{
+		style = mxGraph.prototype.EMPTY_ARRAY;
+	}
+	
+	return style;
+};
+
+/**
+ * Function: postProcessCellStyle
+ * 
+ * Tries to resolve the value for the image style in the image bundles and
+ * turns short data URIs as defined in mxImageBundle to data URIs as
+ * defined in RFC 2397 of the IETF.
+ */
+mxGraph.prototype.postProcessCellStyle = function(style)
+{
+	if (style != null)
+	{
+		var key = style[mxConstants.STYLE_IMAGE];
+		var image = this.getImageFromBundles(key);
+
+		if (image != null)
+		{
+			style[mxConstants.STYLE_IMAGE] = image;
+		}
+		else
+		{
+			image = key;
+		}
+		
+		// Converts short data uris to normal data uris
+		if (image != null && image.substring(0, 11) == 'data:image/')
+		{
+			if (image.substring(0, 20) == 'data:image/svg+xml,<')
+			{
+				// Required for FF and IE11
+				image = image.substring(0, 19) + encodeURIComponent(image.substring(19));
+			}
+			else if (image.substring(0, 22) != 'data:image/svg+xml,%3C')
+			{
+				var comma = image.indexOf(',');
+				
+				// Adds base64 encoding prefix if needed
+				if (comma > 0 && image.substring(comma - 7, comma + 1) != ';base64,')
+				{
+					image = image.substring(0, comma) + ';base64,'
+						+ image.substring(comma + 1);
+				}
+			}
+			
+			style[mxConstants.STYLE_IMAGE] = image;
+		}
+	}
+
+	return style;
+};
+
+/**
+ * Function: setCellStyle
+ * 
+ * Sets the style of the specified cells. If no cells are given, then the
+ * selection cells are changed.
+ * 
+ * Parameters:
+ * 
+ * style - String representing the new style of the cells.
+ * cells - Optional array of <mxCells> to set the style for. Default is the
+ * selection cells.
+ */
+mxGraph.prototype.setCellStyle = function(style, cells)
+{
+	cells = cells || this.getSelectionCells();
+	
+	if (cells != null)
+	{
+		this.model.beginUpdate();
+		try
+		{
+			for (var i = 0; i < cells.length; i++)
+			{
+				this.model.setStyle(cells[i], style);
+			}
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: toggleCellStyle
+ * 
+ * Toggles the boolean value for the given key in the style of the given cell
+ * and returns the new value as 0 or 1. If no cell is specified then the
+ * selection cell is used.
+ * 
+ * Parameter:
+ * 
+ * key - String representing the key for the boolean value to be toggled.
+ * defaultValue - Optional boolean default value if no value is defined.
+ * Default is false.
+ * cell - Optional <mxCell> whose style should be modified. Default is
+ * the selection cell.
+ */
+mxGraph.prototype.toggleCellStyle = function(key, defaultValue, cell)
+{
+	cell = cell || this.getSelectionCell();
+	
+	return this.toggleCellStyles(key, defaultValue, [cell]);
+};
+
+/**
+ * Function: toggleCellStyles
+ * 
+ * Toggles the boolean value for the given key in the style of the given cells
+ * and returns the new value as 0 or 1. If no cells are specified, then the
+ * selection cells are used. For example, this can be used to toggle
+ * <mxConstants.STYLE_ROUNDED> or any other style with a boolean value.
+ * 
+ * Parameter:
+ * 
+ * key - String representing the key for the boolean value to be toggled.
+ * defaultValue - Optional boolean default value if no value is defined.
+ * Default is false.
+ * cells - Optional array of <mxCells> whose styles should be modified.
+ * Default is the selection cells.
+ */
+mxGraph.prototype.toggleCellStyles = function(key, defaultValue, cells)
+{
+	defaultValue = (defaultValue != null) ? defaultValue : false;
+	cells = cells || this.getSelectionCells();
+	var value = null;
+	
+	if (cells != null && cells.length > 0)
+	{
+		var state = this.view.getState(cells[0]);
+		var style = (state != null) ? state.style : this.getCellStyle(cells[0]);
+		
+		if (style != null)
+		{
+			value = (mxUtils.getValue(style, key, defaultValue)) ? 0 : 1;
+			this.setCellStyles(key, value, cells);
+		}
+	}
+	
+	return value;
+};
+
+/**
+ * Function: setCellStyles
+ * 
+ * Sets the key to value in the styles of the given cells. This will modify
+ * the existing cell styles in-place and override any existing assignment
+ * for the given key. If no cells are specified, then the selection cells
+ * are changed. If no value is specified, then the respective key is
+ * removed from the styles.
+ * 
+ * Parameters:
+ * 
+ * key - String representing the key to be assigned.
+ * value - String representing the new value for the key.
+ * cells - Optional array of <mxCells> to change the style for. Default is
+ * the selection cells.
+ */
+mxGraph.prototype.setCellStyles = function(key, value, cells)
+{
+	cells = cells || this.getSelectionCells();
+	mxUtils.setCellStyles(this.model, cells, key, value);
+};
+
+/**
+ * Function: toggleCellStyleFlags
+ * 
+ * Toggles the given bit for the given key in the styles of the specified
+ * cells.
+ * 
+ * Parameters:
+ * 
+ * key - String representing the key to toggle the flag in.
+ * flag - Integer that represents the bit to be toggled.
+ * cells - Optional array of <mxCells> to change the style for. Default is
+ * the selection cells.
+ */
+mxGraph.prototype.toggleCellStyleFlags = function(key, flag, cells)
+{
+	this.setCellStyleFlags(key, flag, null, cells);
+};
+
+/**
+ * Function: setCellStyleFlags
+ * 
+ * Sets or toggles the given bit for the given key in the styles of the
+ * specified cells.
+ * 
+ * Parameters:
+ * 
+ * key - String representing the key to toggle the flag in.
+ * flag - Integer that represents the bit to be toggled.
+ * value - Boolean value to be used or null if the value should be toggled.
+ * cells - Optional array of <mxCells> to change the style for. Default is
+ * the selection cells.
+ */
+mxGraph.prototype.setCellStyleFlags = function(key, flag, value, cells)
+{
+	cells = cells || this.getSelectionCells();
+	
+	if (cells != null && cells.length > 0)
+	{
+		if (value == null)
+		{
+			var state = this.view.getState(cells[0]);
+			var style = (state != null) ? state.style : this.getCellStyle(cells[0]);
+			
+			if (style != null)
+			{
+				var current = parseInt(style[key] || 0);
+				value = !((current & flag) == flag);
+			}
+		}
+
+		mxUtils.setCellStyleFlags(this.model, cells, key, flag, value);
+	}
+};
+
+/**
+ * Group: Cell alignment and orientation
+ */
+
+/**
+ * Function: alignCells
+ * 
+ * Aligns the given cells vertically or horizontally according to the given
+ * alignment using the optional parameter as the coordinate.
+ * 
+ * Parameters:
+ * 
+ * align - Specifies the alignment. Possible values are all constants in
+ * mxConstants with an ALIGN prefix.
+ * cells - Array of <mxCells> to be aligned.
+ * param - Optional coordinate for the alignment.
+ */
+mxGraph.prototype.alignCells = function(align, cells, param)
+{
+	if (cells == null)
+	{
+		cells = this.getSelectionCells();
+	}
+	
+	if (cells != null && cells.length > 1)
+	{
+		// Finds the required coordinate for the alignment
+		if (param == null)
+		{
+			for (var i = 0; i < cells.length; i++)
+			{
+				var state = this.view.getState(cells[i]);
+				
+				if (state != null && !this.model.isEdge(cells[i]))
+				{
+					if (param == null)
+					{
+						if (align == mxConstants.ALIGN_CENTER)
+						{
+							param = state.x + state.width / 2;
+							break;
+						}
+						else if (align == mxConstants.ALIGN_RIGHT)
+						{
+							param = state.x + state.width;
+						}
+						else if (align == mxConstants.ALIGN_TOP)
+						{
+							param = state.y;
+						}
+						else if (align == mxConstants.ALIGN_MIDDLE)
+						{
+							param = state.y + state.height / 2;
+							break;
+						}
+						else if (align == mxConstants.ALIGN_BOTTOM)
+						{
+							param = state.y + state.height;
+						}
+						else
+						{
+							param = state.x;
+						}
+					}
+					else
+					{
+						if (align == mxConstants.ALIGN_RIGHT)
+						{
+							param = Math.max(param, state.x + state.width);
+						}
+						else if (align == mxConstants.ALIGN_TOP)
+						{
+							param = Math.min(param, state.y);
+						}
+						else if (align == mxConstants.ALIGN_BOTTOM)
+						{
+							param = Math.max(param, state.y + state.height);
+						}
+						else
+						{
+							param = Math.min(param, state.x);
+						}
+					}
+				}
+			}
+		}
+
+		// Aligns the cells to the coordinate
+		if (param != null)
+		{
+			var s = this.view.scale;
+
+			this.model.beginUpdate();
+			try
+			{
+				for (var i = 0; i < cells.length; i++)
+				{
+					var state = this.view.getState(cells[i]);
+					
+					if (state != null)
+					{
+						var geo = this.getCellGeometry(cells[i]);
+						
+						if (geo != null && !this.model.isEdge(cells[i]))
+						{
+							geo = geo.clone();
+							
+							if (align == mxConstants.ALIGN_CENTER)
+							{
+								geo.x += (param - state.x - state.width / 2) / s;
+							}
+							else if (align == mxConstants.ALIGN_RIGHT)
+							{
+								geo.x += (param - state.x - state.width) / s;
+							}
+							else if (align == mxConstants.ALIGN_TOP)
+							{
+								geo.y += (param - state.y) / s;
+							}
+							else if (align == mxConstants.ALIGN_MIDDLE)
+							{
+								geo.y += (param - state.y - state.height / 2) / s;
+							}
+							else if (align == mxConstants.ALIGN_BOTTOM)
+							{
+								geo.y += (param - state.y - state.height) / s;
+							}
+							else
+							{
+								geo.x += (param - state.x) / s;
+							}
+							
+							this.resizeCell(cells[i], geo);
+						}
+					}
+				}
+				
+				this.fireEvent(new mxEventObject(mxEvent.ALIGN_CELLS,
+						'align', align, 'cells', cells));
+			}
+			finally
+			{
+				this.model.endUpdate();
+			}
+		}
+	}
+	
+	return cells;
+};
+
+/**
+ * Function: flipEdge
+ * 
+ * Toggles the style of the given edge between null (or empty) and
+ * <alternateEdgeStyle>. This method fires <mxEvent.FLIP_EDGE> while the
+ * transaction is in progress. Returns the edge that was flipped.
+ * 
+ * Here is an example that overrides this implementation to invert the
+ * value of <mxConstants.STYLE_ELBOW> without removing any existing styles.
+ * 
+ * (code)
+ * graph.flipEdge = function(edge)
+ * {
+ *   if (edge != null)
+ *   {
+ *     var state = this.view.getState(edge);
+ *     var style = (state != null) ? state.style : this.getCellStyle(edge);
+ *     
+ *     if (style != null)
+ *     {
+ *       var elbow = mxUtils.getValue(style, mxConstants.STYLE_ELBOW,
+ *           mxConstants.ELBOW_HORIZONTAL);
+ *       var value = (elbow == mxConstants.ELBOW_HORIZONTAL) ?
+ *           mxConstants.ELBOW_VERTICAL : mxConstants.ELBOW_HORIZONTAL;
+ *       this.setCellStyles(mxConstants.STYLE_ELBOW, value, [edge]);
+ *     }
+ *   }
+ * };
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> whose style should be changed.
+ */
+mxGraph.prototype.flipEdge = function(edge)
+{
+	if (edge != null &&
+		this.alternateEdgeStyle != null)
+	{
+		this.model.beginUpdate();
+		try
+		{
+			var style = this.model.getStyle(edge);
+
+			if (style == null || style.length == 0)
+			{
+				this.model.setStyle(edge, this.alternateEdgeStyle);
+			}
+			else
+			{
+				this.model.setStyle(edge, null);
+			}
+
+			// Removes all existing control points
+			this.resetEdge(edge);
+			this.fireEvent(new mxEventObject(mxEvent.FLIP_EDGE, 'edge', edge));
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+
+	return edge;
+};
+
+/**
+ * Function: addImageBundle
+ *
+ * Adds the specified <mxImageBundle>.
+ */
+mxGraph.prototype.addImageBundle = function(bundle)
+{
+	this.imageBundles.push(bundle);
+};
+
+/**
+ * Function: removeImageBundle
+ * 
+ * Removes the specified <mxImageBundle>.
+ */
+mxGraph.prototype.removeImageBundle = function(bundle)
+{
+	var tmp = [];
+	
+	for (var i = 0; i < this.imageBundles.length; i++)
+	{
+		if (this.imageBundles[i] != bundle)
+		{
+			tmp.push(this.imageBundles[i]);
+		}
+	}
+	
+	this.imageBundles = tmp;
+};
+
+/**
+ * Function: getImageFromBundles
+ *
+ * Searches all <imageBundles> for the specified key and returns the value
+ * for the first match or null if the key is not found.
+ */
+mxGraph.prototype.getImageFromBundles = function(key)
+{
+	if (key != null)
+	{
+		for (var i = 0; i < this.imageBundles.length; i++)
+		{
+			var image = this.imageBundles[i].getImage(key);
+			
+			if (image != null)
+			{
+				return image;
+			}
+		}
+	}
+	
+	return null;
+};
+
+/**
+ * Group: Order
+ */
+
+/**
+ * Function: orderCells
+ * 
+ * Moves the given cells to the front or back. The change is carried out
+ * using <cellsOrdered>. This method fires <mxEvent.ORDER_CELLS> while the
+ * transaction is in progress.
+ * 
+ * Parameters:
+ * 
+ * back - Boolean that specifies if the cells should be moved to back.
+ * cells - Array of <mxCells> to move to the background. If null is
+ * specified then the selection cells are used.
+ */
+mxGraph.prototype.orderCells = function(back, cells)
+{
+	if (cells == null)
+	{
+		cells = mxUtils.sortCells(this.getSelectionCells(), true);
+	}
+
+	this.model.beginUpdate();
+	try
+	{
+		this.cellsOrdered(cells, back);
+		this.fireEvent(new mxEventObject(mxEvent.ORDER_CELLS,
+				'back', back, 'cells', cells));
+	}
+	finally
+	{
+		this.model.endUpdate();
+	}
+
+	return cells;
+};
+
+/**
+ * Function: cellsOrdered
+ * 
+ * Moves the given cells to the front or back. This method fires
+ * <mxEvent.CELLS_ORDERED> while the transaction is in progress.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> whose order should be changed.
+ * back - Boolean that specifies if the cells should be moved to back.
+ */
+mxGraph.prototype.cellsOrdered = function(cells, back)
+{
+	if (cells != null)
+	{
+		this.model.beginUpdate();
+		try
+		{
+			for (var i = 0; i < cells.length; i++)
+			{
+				var parent = this.model.getParent(cells[i]);
+
+				if (back)
+				{
+					this.model.add(parent, cells[i], i);
+				}
+				else
+				{
+					this.model.add(parent, cells[i],
+							this.model.getChildCount(parent) - 1);
+				}
+			}
+			
+			this.fireEvent(new mxEventObject(mxEvent.CELLS_ORDERED,
+					'back', back, 'cells', cells));
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Group: Grouping
+ */
+
+/**
+ * Function: groupCells
+ * 
+ * Adds the cells into the given group. The change is carried out using
+ * <cellsAdded>, <cellsMoved> and <cellsResized>. This method fires
+ * <mxEvent.GROUP_CELLS> while the transaction is in progress. Returns the
+ * new group. A group is only created if there is at least one entry in the
+ * given array of cells.
+ * 
+ * Parameters:
+ * 
+ * group - <mxCell> that represents the target group. If null is specified
+ * then a new group is created using <createGroupCell>.
+ * border - Optional integer that specifies the border between the child
+ * area and the group bounds. Default is 0.
+ * cells - Optional array of <mxCells> to be grouped. If null is specified
+ * then the selection cells are used.
+ */
+mxGraph.prototype.groupCells = function(group, border, cells)
+{
+	if (cells == null)
+	{
+		cells = mxUtils.sortCells(this.getSelectionCells(), true);
+	}
+
+	cells = this.getCellsForGroup(cells);
+
+	if (group == null)
+	{
+		group = this.createGroupCell(cells);
+	}
+
+	var bounds = this.getBoundsForGroup(group, cells, border);
+
+	if (cells.length > 0 && bounds != null)
+	{
+		// Uses parent of group or previous parent of first child
+		var parent = this.model.getParent(group);
+		
+		if (parent == null)
+		{
+			parent = this.model.getParent(cells[0]);
+		}
+
+		this.model.beginUpdate();
+		try
+		{
+			// Checks if the group has a geometry and
+			// creates one if one does not exist
+			if (this.getCellGeometry(group) == null)
+			{
+				this.model.setGeometry(group, new mxGeometry());
+			}
+
+			// Adds the group into the parent
+			var index = this.model.getChildCount(parent);
+			this.cellsAdded([group], parent, index, null, null, false, false, false);
+
+			// Adds the children into the group and moves
+			index = this.model.getChildCount(group);
+			this.cellsAdded(cells, group, index, null, null, false, false, false);
+			this.cellsMoved(cells, -bounds.x, -bounds.y, false, false, false);
+
+			// Resizes the group
+			this.cellsResized([group], [bounds], false);
+
+			this.fireEvent(new mxEventObject(mxEvent.GROUP_CELLS,
+					'group', group, 'border', border, 'cells', cells));
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+
+	return group;
+};
+
+/**
+ * Function: getCellsForGroup
+ * 
+ * Returns the cells with the same parent as the first cell
+ * in the given array.
+ */
+mxGraph.prototype.getCellsForGroup = function(cells)
+{
+	var result = [];
+
+	if (cells != null && cells.length > 0)
+	{
+		var parent = this.model.getParent(cells[0]);
+		result.push(cells[0]);
+
+		// Filters selection cells with the same parent
+		for (var i = 1; i < cells.length; i++)
+		{
+			if (this.model.getParent(cells[i]) == parent)
+			{
+				result.push(cells[i]);
+			}
+		}
+	}
+
+	return result;
+};
+
+/**
+ * Function: getBoundsForGroup
+ * 
+ * Returns the bounds to be used for the given group and children.
+ */
+mxGraph.prototype.getBoundsForGroup = function(group, children, border)
+{
+	var result = this.getBoundingBoxFromGeometry(children, true);
+	
+	if (result != null)
+	{
+		if (this.isSwimlane(group))
+		{
+			var size = this.getStartSize(group);
+			
+			result.x -= size.width;
+			result.y -= size.height;
+			result.width += size.width;
+			result.height += size.height;
+		}
+		
+		// Adds the border
+		if (border != null)
+		{
+			result.x -= border;
+			result.y -= border;
+			result.width += 2 * border;
+			result.height += 2 * border;
+		}
+	}			
+	
+	return result;
+};
+
+/**
+ * Function: createGroupCell
+ * 
+ * Hook for creating the group cell to hold the given array of <mxCells> if
+ * no group cell was given to the <group> function.
+ * 
+ * The following code can be used to set the style of new group cells.
+ * 
+ * (code)
+ * var graphCreateGroupCell = graph.createGroupCell;
+ * graph.createGroupCell = function(cells)
+ * {
+ *   var group = graphCreateGroupCell.apply(this, arguments);
+ *   group.setStyle('group');
+ *   
+ *   return group;
+ * };
+ */
+mxGraph.prototype.createGroupCell = function(cells)
+{
+	var group = new mxCell('');
+	group.setVertex(true);
+	group.setConnectable(false);
+	
+	return group;
+};
+
+/**
+ * Function: ungroupCells
+ * 
+ * Ungroups the given cells by moving the children the children to their
+ * parents parent and removing the empty groups. Returns the children that
+ * have been removed from the groups.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of cells to be ungrouped. If null is specified then the
+ * selection cells are used.
+ */
+mxGraph.prototype.ungroupCells = function(cells)
+{
+	var result = [];
+	
+	if (cells == null)
+	{
+		cells = this.getSelectionCells();
+
+		// Finds the cells with children
+		var tmp = [];
+		
+		for (var i = 0; i < cells.length; i++)
+		{
+			if (this.model.getChildCount(cells[i]) > 0)
+			{
+				tmp.push(cells[i]);
+			}
+		}
+
+		cells = tmp;
+	}
+	
+	if (cells != null && cells.length > 0)
+	{
+		this.model.beginUpdate();
+		try
+		{
+			for (var i = 0; i < cells.length; i++)
+			{
+				var children = this.model.getChildren(cells[i]);
+				
+				if (children != null && children.length > 0)
+				{
+					children = children.slice();
+					var parent = this.model.getParent(cells[i]);
+					var index = this.model.getChildCount(parent);
+
+					this.cellsAdded(children, parent, index, null, null, true);
+					result = result.concat(children);
+				}
+			}
+
+			this.removeCellsAfterUngroup(cells);
+			this.fireEvent(new mxEventObject(mxEvent.UNGROUP_CELLS, 'cells', cells));
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: removeCellsAfterUngroup
+ * 
+ * Hook to remove the groups after <ungroupCells>.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> that were ungrouped.
+ */
+mxGraph.prototype.removeCellsAfterUngroup = function(cells)
+{
+	this.cellsRemoved(this.addAllEdges(cells));
+};
+
+/**
+ * Function: removeCellsFromParent
+ * 
+ * Removes the specified cells from their parents and adds them to the
+ * default parent. Returns the cells that were removed from their parents.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> to be removed from their parents.
+ */
+mxGraph.prototype.removeCellsFromParent = function(cells)
+{
+	if (cells == null)
+	{
+		cells = this.getSelectionCells();
+	}
+	
+	this.model.beginUpdate();
+	try
+	{
+		var parent = this.getDefaultParent();
+		var index = this.model.getChildCount(parent);
+
+		this.cellsAdded(cells, parent, index, null, null, true);
+		this.fireEvent(new mxEventObject(mxEvent.REMOVE_CELLS_FROM_PARENT, 'cells', cells));
+	}
+	finally
+	{
+		this.model.endUpdate();
+	}
+
+	return cells;
+};
+
+/**
+ * Function: updateGroupBounds
+ * 
+ * Updates the bounds of the given groups to include all children and returns
+ * the passed-in cells. Call this with the groups in parent to child order,
+ * top-most group first, the cells are processed in reverse order and cells
+ * with no children are ignored.
+ * 
+ * Parameters:
+ * 
+ * cells - The groups whose bounds should be updated. If this is null, then
+ * the selection cells are used.
+ * border - Optional border to be added in the group. Default is 0.
+ * moveGroup - Optional boolean that allows the group to be moved. Default
+ * is false.
+ * topBorder - Optional top border to be added in the group. Default is 0.
+ * rightBorder - Optional top border to be added in the group. Default is 0.
+ * bottomBorder - Optional top border to be added in the group. Default is 0.
+ * leftBorder - Optional top border to be added in the group. Default is 0.
+ */
+mxGraph.prototype.updateGroupBounds = function(cells, border, moveGroup, topBorder, rightBorder, bottomBorder, leftBorder)
+{
+	if (cells == null)
+	{
+		cells = this.getSelectionCells();
+	}
+	
+	border = (border != null) ? border : 0;
+	moveGroup = (moveGroup != null) ? moveGroup : false;
+	topBorder = (topBorder != null) ? topBorder : 0;
+	rightBorder = (rightBorder != null) ? rightBorder : 0;
+	bottomBorder = (bottomBorder != null) ? bottomBorder : 0;
+	leftBorder = (leftBorder != null) ? leftBorder : 0;
+
+	this.model.beginUpdate();
+	try
+	{
+		for (var i = cells.length - 1; i >= 0; i--)
+		{
+			var geo = this.getCellGeometry(cells[i]);
+			
+			if (geo != null)
+			{
+				var children = this.getChildCells(cells[i]);
+				
+				if (children != null && children.length > 0)
+				{
+					var bounds = this.getBoundingBoxFromGeometry(children, true);
+					
+					if (bounds != null && bounds.width > 0 && bounds.height > 0)
+					{
+						var left = 0;
+						var top = 0;
+						
+						// Adds the size of the title area for swimlanes
+						if (this.isSwimlane(cells[i]))
+						{
+							var size = this.getStartSize(cells[i]);
+							left = size.width;
+							top = size.height;
+						}
+						
+						geo = geo.clone();
+						
+						if (moveGroup)
+						{
+							geo.x = Math.round(geo.x + bounds.x - border - left - leftBorder);
+							geo.y = Math.round(geo.y + bounds.y - border - top - topBorder);
+						}
+						
+						geo.width = Math.round(bounds.width + 2 * border + left + leftBorder + rightBorder);
+						geo.height = Math.round(bounds.height + 2 * border + top + topBorder + bottomBorder);
+						
+						this.model.setGeometry(cells[i], geo);
+						this.moveCells(children, border + left - bounds.x + leftBorder,
+								border + top - bounds.y + topBorder);
+					}
+				}
+			}
+		}
+	}
+	finally
+	{
+		this.model.endUpdate();
+	}
+
+	return cells;
+};
+
+/**
+ * Function: getBoundingBox
+ * 
+ * Returns the bounding box for the given array of <mxCells>. The bounding box for
+ * each cell and its descendants is computed using <mxGraphView.getBoundingBox>.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> whose bounding box should be returned.
+ */
+mxGraph.prototype.getBoundingBox = function(cells)
+{
+	var result = null;
+	
+	if (cells != null && cells.length > 0)
+	{
+		for (var i = 0; i < cells.length; i++)
+		{
+			if (this.model.isVertex(cells[i]) || this.model.isEdge(cells[i]))
+			{
+				var bbox = this.view.getBoundingBox(this.view.getState(cells[i]), true);
+			
+				if (bbox != null)
+				{
+					if (result == null)
+					{
+						result = mxRectangle.fromRectangle(bbox);
+					}
+					else
+					{
+						result.add(bbox);
+					}
+				}
+			}
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Group: Cell cloning, insertion and removal
+ */
+
+/**
+ * Function: cloneCells
+ * 
+ * Returns the clones for the given cells. The clones are created recursively
+ * using <mxGraphModel.cloneCells>. If the terminal of an edge is not in the
+ * given array, then the respective end is assigned a terminal point and the
+ * terminal is removed.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> to be cloned.
+ * allowInvalidEdges - Optional boolean that specifies if invalid edges
+ * should be cloned. Default is true.
+ * mapping - Optional mapping for existing clones.
+ */
+mxGraph.prototype.cloneCells = function(cells, allowInvalidEdges, mapping)
+{
+	allowInvalidEdges = (allowInvalidEdges != null) ? allowInvalidEdges : true;
+	var clones = null;
+	
+	if (cells != null)
+	{
+		// Creates a dictionary for fast lookups
+		var dict = new mxDictionary();
+		var tmp = [];
+		
+		for (var i = 0; i < cells.length; i++)
+		{
+			dict.put(cells[i], true);
+			tmp.push(cells[i]);
+		}
+		
+		if (tmp.length > 0)
+		{
+			var scale = this.view.scale;
+			var trans = this.view.translate;
+			clones = this.model.cloneCells(cells, true, mapping);
+		
+			for (var i = 0; i < cells.length; i++)
+			{
+				if (!allowInvalidEdges && this.model.isEdge(clones[i]) &&
+					this.getEdgeValidationError(clones[i],
+						this.model.getTerminal(clones[i], true),
+						this.model.getTerminal(clones[i], false)) != null)
+				{
+					clones[i] = null;
+				}
+				else
+				{
+					var g = this.model.getGeometry(clones[i]);
+					
+					if (g != null)
+					{
+						var state = this.view.getState(cells[i]);
+						var pstate = this.view.getState(this.model.getParent(cells[i]));
+						
+						if (state != null && pstate != null)
+						{
+							var dx = pstate.origin.x;
+							var dy = pstate.origin.y;
+							
+							if (this.model.isEdge(clones[i]))
+							{
+								var pts = state.absolutePoints;
+								
+								// Checks if the source is cloned or sets the terminal point
+								var src = this.model.getTerminal(cells[i], true);
+								
+								while (src != null && !dict.get(src))
+								{
+									src = this.model.getParent(src);
+								}
+								
+								if (src == null)
+								{
+									g.setTerminalPoint(
+										new mxPoint(pts[0].x / scale - trans.x,
+											pts[0].y / scale - trans.y), true);
+								}
+								
+								// Checks if the target is cloned or sets the terminal point
+								var trg = this.model.getTerminal(cells[i], false);
+								
+								while (trg != null && !dict.get(trg))
+								{
+									trg = this.model.getParent(trg);
+								}
+								
+								if (trg == null)
+								{
+									var n = pts.length - 1;
+									g.setTerminalPoint(
+										new mxPoint(pts[n].x / scale - trans.x,
+											pts[n].y / scale - trans.y), false);
+								}
+								
+								// Translates the control points
+								var points = g.points;
+								
+								if (points != null)
+								{
+									for (var j = 0; j < points.length; j++)
+									{
+										points[j].x += dx;
+										points[j].y += dy;
+									}
+								}
+							}
+							else
+							{
+								g.translate(dx, dy);
+							}
+						}
+					}
+				}
+			}
+		}
+		else
+		{
+			clones = [];
+		}
+	}
+	
+	return clones;
+};
+
+/**
+ * Function: insertVertex
+ * 
+ * Adds a new vertex into the given parent <mxCell> using value as the user
+ * object and the given coordinates as the <mxGeometry> of the new vertex.
+ * The id and style are used for the respective properties of the new
+ * <mxCell>, which is returned.
+ *
+ * When adding new vertices from a mouse event, one should take into
+ * account the offset of the graph container and the scale and translation
+ * of the view in order to find the correct unscaled, untranslated
+ * coordinates using <mxGraph.getPointForEvent> as follows:
+ * 
+ * (code)
+ * var pt = graph.getPointForEvent(evt);
+ * var parent = graph.getDefaultParent();
+ * graph.insertVertex(parent, null,
+ * 			'Hello, World!', x, y, 220, 30);
+ * (end)
+ * 
+ * For adding image cells, the style parameter can be assigned as
+ * 
+ * (code)
+ * stylename;image=imageUrl
+ * (end)
+ * 
+ * See <mxGraph> for more information on using images.
+ *
+ * Parameters:
+ * 
+ * parent - <mxCell> that specifies the parent of the new vertex.
+ * id - Optional string that defines the Id of the new vertex.
+ * value - Object to be used as the user object.
+ * x - Integer that defines the x coordinate of the vertex.
+ * y - Integer that defines the y coordinate of the vertex.
+ * width - Integer that defines the width of the vertex.
+ * height - Integer that defines the height of the vertex.
+ * style - Optional string that defines the cell style.
+ * relative - Optional boolean that specifies if the geometry is relative.
+ * Default is false.
+ */
+mxGraph.prototype.insertVertex = function(parent, id, value,
+	x, y, width, height, style, relative)
+{
+	var vertex = this.createVertex(parent, id, value, x, y, width, height, style, relative);
+
+	return this.addCell(vertex, parent);
+};
+
+/**
+ * Function: createVertex
+ * 
+ * Hook method that creates the new vertex for <insertVertex>.
+ */
+mxGraph.prototype.createVertex = function(parent, id, value,
+		x, y, width, height, style, relative)
+{
+	// Creates the geometry for the vertex
+	var geometry = new mxGeometry(x, y, width, height);
+	geometry.relative = (relative != null) ? relative : false;
+	
+	// Creates the vertex
+	var vertex = new mxCell(value, geometry, style);
+	vertex.setId(id);
+	vertex.setVertex(true);
+	vertex.setConnectable(true);
+	
+	return vertex;
+};
+	
+/**
+ * Function: insertEdge
+ * 
+ * Adds a new edge into the given parent <mxCell> using value as the user
+ * object and the given source and target as the terminals of the new edge.
+ * The id and style are used for the respective properties of the new
+ * <mxCell>, which is returned.
+ *
+ * Parameters:
+ * 
+ * parent - <mxCell> that specifies the parent of the new edge.
+ * id - Optional string that defines the Id of the new edge.
+ * value - JavaScript object to be used as the user object.
+ * source - <mxCell> that defines the source of the edge.
+ * target - <mxCell> that defines the target of the edge.
+ * style - Optional string that defines the cell style.
+ */
+mxGraph.prototype.insertEdge = function(parent, id, value, source, target, style)
+{
+	var edge = this.createEdge(parent, id, value, source, target, style);
+	
+	return this.addEdge(edge, parent, source, target);
+};
+
+/**
+ * Function: createEdge
+ * 
+ * Hook method that creates the new edge for <insertEdge>. This
+ * implementation does not set the source and target of the edge, these
+ * are set when the edge is added to the model.
+ * 
+ */
+mxGraph.prototype.createEdge = function(parent, id, value, source, target, style)
+{
+	// Creates the edge
+	var edge = new mxCell(value, new mxGeometry(), style);
+	edge.setId(id);
+	edge.setEdge(true);
+	edge.geometry.relative = true;
+	
+	return edge;
+};
+
+/**
+ * Function: addEdge
+ * 
+ * Adds the edge to the parent and connects it to the given source and
+ * target terminals. This is a shortcut method. Returns the edge that was
+ * added.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> to be inserted into the given parent.
+ * parent - <mxCell> that represents the new parent. If no parent is
+ * given then the default parent is used.
+ * source - Optional <mxCell> that represents the source terminal.
+ * target - Optional <mxCell> that represents the target terminal.
+ * index - Optional index to insert the cells at. Default is to append.
+ */
+mxGraph.prototype.addEdge = function(edge, parent, source, target, index)
+{
+	return this.addCell(edge, parent, index, source, target);
+};
+
+/**
+ * Function: addCell
+ * 
+ * Adds the cell to the parent and connects it to the given source and
+ * target terminals. This is a shortcut method. Returns the cell that was
+ * added.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to be inserted into the given parent.
+ * parent - <mxCell> that represents the new parent. If no parent is
+ * given then the default parent is used.
+ * index - Optional index to insert the cells at. Default is to append.
+ * source - Optional <mxCell> that represents the source terminal.
+ * target - Optional <mxCell> that represents the target terminal.
+ */
+mxGraph.prototype.addCell = function(cell, parent, index, source, target)
+{
+	return this.addCells([cell], parent, index, source, target)[0];
+};
+
+/**
+ * Function: addCells
+ * 
+ * Adds the cells to the parent at the given index, connecting each cell to
+ * the optional source and target terminal. The change is carried out using
+ * <cellsAdded>. This method fires <mxEvent.ADD_CELLS> while the
+ * transaction is in progress. Returns the cells that were added.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> to be inserted.
+ * parent - <mxCell> that represents the new parent. If no parent is
+ * given then the default parent is used.
+ * index - Optional index to insert the cells at. Default is to append.
+ * source - Optional source <mxCell> for all inserted cells.
+ * target - Optional target <mxCell> for all inserted cells.
+ */
+mxGraph.prototype.addCells = function(cells, parent, index, source, target)
+{
+	if (parent == null)
+	{
+		parent = this.getDefaultParent();
+	}
+	
+	if (index == null)
+	{
+		index = this.model.getChildCount(parent);
+	}
+	
+	this.model.beginUpdate();
+	try
+	{
+		this.cellsAdded(cells, parent, index, source, target, false, true);
+		this.fireEvent(new mxEventObject(mxEvent.ADD_CELLS, 'cells', cells,
+				'parent', parent, 'index', index, 'source', source, 'target', target));
+	}
+	finally
+	{
+		this.model.endUpdate();
+	}
+
+	return cells;
+};
+
+/**
+ * Function: cellsAdded
+ * 
+ * Adds the specified cells to the given parent. This method fires
+ * <mxEvent.CELLS_ADDED> while the transaction is in progress.
+ */
+mxGraph.prototype.cellsAdded = function(cells, parent, index, source, target, absolute, constrain, extend)
+{
+	if (cells != null && parent != null && index != null)
+	{
+		this.model.beginUpdate();
+		try
+		{
+			var parentState = (absolute) ? this.view.getState(parent) : null;
+			var o1 = (parentState != null) ? parentState.origin : null;
+			var zero = new mxPoint(0, 0);
+
+			for (var i = 0; i < cells.length; i++)
+			{
+				if (cells[i] == null)
+				{
+					index--;
+				}
+				else
+				{
+					var previous = this.model.getParent(cells[i]);
+	
+					// Keeps the cell at its absolute location
+					if (o1 != null && cells[i] != parent && parent != previous)
+					{
+						var oldState = this.view.getState(previous);
+						var o2 = (oldState != null) ? oldState.origin : zero;
+						var geo = this.model.getGeometry(cells[i]);
+	
+						if (geo != null)
+						{
+							var dx = o2.x - o1.x;
+							var dy = o2.y - o1.y;
+	
+							// FIXME: Cells should always be inserted first before any other edit
+							// to avoid forward references in sessions.
+							geo = geo.clone();
+							geo.translate(dx, dy);
+							
+							if (!geo.relative && this.model.isVertex(cells[i]) &&
+								!this.isAllowNegativeCoordinates())
+							{
+								geo.x = Math.max(0, geo.x);
+								geo.y = Math.max(0, geo.y);
+							}
+							
+							this.model.setGeometry(cells[i], geo);
+						}
+					}
+	
+					// Decrements all following indices
+					// if cell is already in parent
+					if (parent == previous && index + i > this.model.getChildCount(parent))
+					{
+						index--;
+					}
+
+					this.model.add(parent, cells[i], index + i);
+					
+					if (this.autoSizeCellsOnAdd)
+					{
+						this.autoSizeCell(cells[i], true);
+					}
+
+					// Extends the parent or constrains the child
+					if ((extend == null || extend) &&
+						this.isExtendParentsOnAdd(cells[i]) && this.isExtendParent(cells[i]))
+					{
+						this.extendParent(cells[i]);
+					}
+					
+					// Additionally constrains the child after extending the parent
+					if (constrain == null || constrain)
+					{
+						this.constrainChild(cells[i]);
+					}
+					
+					// Sets the source terminal
+					if (source != null)
+					{
+						this.cellConnected(cells[i], source, true);
+					}
+					
+					// Sets the target terminal
+					if (target != null)
+					{
+						this.cellConnected(cells[i], target, false);
+					}
+				}
+			}
+			
+			this.fireEvent(new mxEventObject(mxEvent.CELLS_ADDED, 'cells', cells,
+				'parent', parent, 'index', index, 'source', source, 'target', target,
+				'absolute', absolute));
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: autoSizeCell
+ * 
+ * Resizes the specified cell to just fit around the its label and/or children
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCells> to be resized.
+ * recurse - Optional boolean which specifies if all descendants should be
+ * autosized. Default is true.
+ */
+mxGraph.prototype.autoSizeCell = function(cell, recurse)
+{
+	recurse = (recurse != null) ? recurse : true;
+	
+	if (recurse)
+	{
+		var childCount = this.model.getChildCount(cell);
+		
+		for (var i = 0; i < childCount; i++)
+		{
+			this.autoSizeCell(this.model.getChildAt(cell, i));
+		}
+	}
+
+	if (this.getModel().isVertex(cell) && this.isAutoSizeCell(cell))
+	{
+		this.updateCellSize(cell);
+	}
+};
+
+/**
+ * Function: removeCells
+ * 
+ * Removes the given cells from the graph including all connected edges if
+ * includeEdges is true. The change is carried out using <cellsRemoved>.
+ * This method fires <mxEvent.REMOVE_CELLS> while the transaction is in
+ * progress. The removed cells are returned as an array.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> to remove. If null is specified then the
+ * selection cells which are deletable are used.
+ * includeEdges - Optional boolean which specifies if all connected edges
+ * should be removed as well. Default is true.
+ */
+mxGraph.prototype.removeCells = function(cells, includeEdges)
+{
+	includeEdges = (includeEdges != null) ? includeEdges : true;
+	
+	if (cells == null)
+	{
+		cells = this.getDeletableCells(this.getSelectionCells());
+	}
+
+	// Adds all edges to the cells
+	if (includeEdges)
+	{
+		// FIXME: Remove duplicate cells in result or do not add if
+		// in cells or descendant of cells
+		cells = this.getDeletableCells(this.addAllEdges(cells));
+	}
+
+	this.model.beginUpdate();
+	try
+	{
+		this.cellsRemoved(cells);
+		this.fireEvent(new mxEventObject(mxEvent.REMOVE_CELLS, 
+				'cells', cells, 'includeEdges', includeEdges));
+	}
+	finally
+	{
+		this.model.endUpdate();
+	}
+	
+	return cells;
+};
+
+/**
+ * Function: cellsRemoved
+ * 
+ * Removes the given cells from the model. This method fires
+ * <mxEvent.CELLS_REMOVED> while the transaction is in progress.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> to remove.
+ */
+mxGraph.prototype.cellsRemoved = function(cells)
+{
+	if (cells != null && cells.length > 0)
+	{
+		var scale = this.view.scale;
+		var tr = this.view.translate;
+		
+		this.model.beginUpdate();
+		try
+		{
+			// Creates hashtable for faster lookup
+			var dict = new mxDictionary();
+			
+			for (var i = 0; i < cells.length; i++)
+			{
+				dict.put(cells[i], true);
+			}
+			
+			for (var i = 0; i < cells.length; i++)
+			{
+				// Disconnects edges which are not in cells
+				var edges = this.getAllEdges([cells[i]]);
+				
+				var disconnectTerminal = mxUtils.bind(this, function(edge, source)
+				{
+					var geo = this.model.getGeometry(edge);
+
+					if (geo != null)
+					{
+						var state = this.view.getState(edge);
+								
+						if (state != null)
+						{
+							// Checks which side of the edge is being disconnected
+							var tmp = state.getVisibleTerminal(source);
+							var connected = false;
+							
+							while (tmp != null)
+							{
+								if (cells[i] == tmp)
+								{
+									connected = true;
+									break;
+								}
+								
+								tmp = this.model.getParent(tmp);
+							}
+							
+							if (connected)
+							{
+								var dx = tr.x;
+								var dy = tr.y;
+								var parentState = this.view.getState(this.model.getParent(edge));
+								
+								if (parentState != null && this.model.isVertex(parentState.cell))
+								{
+									dx = parentState.x / scale;
+									dy = parentState.y / scale;
+								}
+								
+								geo = geo.clone();
+								var pts = state.absolutePoints;
+								var n = (source) ? 0 : pts.length - 1;
+								geo.setTerminalPoint(new mxPoint(pts[n].x / scale - dx, pts[n].y / scale - dy), source);
+								this.model.setTerminal(edges[j], null, source);
+								this.model.setGeometry(edges[j], geo);
+							}
+						}
+					}
+				});
+				
+				for (var j = 0; j < edges.length; j++)
+				{
+					if (!dict.get(edges[j]))
+					{
+						disconnectTerminal(edges[j], true);
+						disconnectTerminal(edges[j], false);
+					}
+				}
+
+				this.model.remove(cells[i]);
+			}
+			
+			this.fireEvent(new mxEventObject(mxEvent.CELLS_REMOVED, 'cells', cells));
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: splitEdge
+ * 
+ * Splits the given edge by adding the newEdge between the previous source
+ * and the given cell and reconnecting the source of the given edge to the
+ * given cell. This method fires <mxEvent.SPLIT_EDGE> while the transaction
+ * is in progress. Returns the new edge that was inserted.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> that represents the edge to be splitted.
+ * cells - <mxCells> that represents the cells to insert into the edge.
+ * newEdge - <mxCell> that represents the edge to be inserted.
+ * dx - Optional integer that specifies the vector to move the cells.
+ * dy - Optional integer that specifies the vector to move the cells.
+ */
+mxGraph.prototype.splitEdge = function(edge, cells, newEdge, dx, dy)
+{
+	dx = dx || 0;
+	dy = dy || 0;
+
+	var parent = this.model.getParent(edge);
+	var source = this.model.getTerminal(edge, true);
+
+	this.model.beginUpdate();
+	try
+	{
+		if (newEdge == null)
+		{
+			newEdge = this.cloneCells([edge])[0];
+			
+			// Removes waypoints before/after new cell
+			var state = this.view.getState(edge);
+			var geo = this.getCellGeometry(newEdge);
+			
+			if (geo != null && geo.points != null && state != null)
+			{
+				var t = this.view.translate;
+				var s = this.view.scale;
+				var idx = mxUtils.findNearestSegment(state, (dx + t.x) * s, (dy + t.y) * s);
+				geo.points = geo.points.slice(0, idx);
+								
+				geo = this.getCellGeometry(edge);
+				
+				if (geo != null && geo.points != null)
+				{
+					geo = geo.clone();
+					geo.points = geo.points.slice(idx);
+					this.model.setGeometry(edge, geo);
+				}
+			}
+		}
+		
+		this.cellsMoved(cells, dx, dy, false, false);
+		this.cellsAdded(cells, parent, this.model.getChildCount(parent), null, null,
+				true);
+		this.cellsAdded([newEdge], parent, this.model.getChildCount(parent),
+				source, cells[0], false);
+		this.cellConnected(edge, cells[0], true);
+		this.fireEvent(new mxEventObject(mxEvent.SPLIT_EDGE, 'edge', edge,
+				'cells', cells, 'newEdge', newEdge, 'dx', dx, 'dy', dy));
+	}
+	finally
+	{
+		this.model.endUpdate();
+	}
+
+	return newEdge;
+};
+
+/**
+ * Group: Cell visibility
+ */
+
+/**
+ * Function: toggleCells
+ * 
+ * Sets the visible state of the specified cells and all connected edges
+ * if includeEdges is true. The change is carried out using <cellsToggled>.
+ * This method fires <mxEvent.TOGGLE_CELLS> while the transaction is in
+ * progress. Returns the cells whose visible state was changed.
+ * 
+ * Parameters:
+ * 
+ * show - Boolean that specifies the visible state to be assigned.
+ * cells - Array of <mxCells> whose visible state should be changed. If
+ * null is specified then the selection cells are used.
+ * includeEdges - Optional boolean indicating if the visible state of all
+ * connected edges should be changed as well. Default is true.
+ */
+mxGraph.prototype.toggleCells = function(show, cells, includeEdges)
+{
+	if (cells == null)
+	{
+		cells = this.getSelectionCells();
+	}
+
+	// Adds all connected edges recursively
+	if (includeEdges)
+	{
+		cells = this.addAllEdges(cells);
+	}
+
+	this.model.beginUpdate();
+	try
+	{
+		this.cellsToggled(cells, show);
+		this.fireEvent(new mxEventObject(mxEvent.TOGGLE_CELLS,
+			'show', show, 'cells', cells, 'includeEdges', includeEdges));
+	}
+	finally
+	{
+		this.model.endUpdate();
+	}
+
+	return cells;
+};
+
+/**
+ * Function: cellsToggled
+ * 
+ * Sets the visible state of the specified cells.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> whose visible state should be changed.
+ * show - Boolean that specifies the visible state to be assigned.
+ */
+mxGraph.prototype.cellsToggled = function(cells, show)
+{
+	if (cells != null && cells.length > 0)
+	{
+		this.model.beginUpdate();
+		try
+		{
+			for (var i = 0; i < cells.length; i++)
+			{
+				this.model.setVisible(cells[i], show);
+			}
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Group: Folding
+ */
+
+/**
+ * Function: foldCells
+ * 
+ * Sets the collapsed state of the specified cells and all descendants
+ * if recurse is true. The change is carried out using <cellsFolded>.
+ * This method fires <mxEvent.FOLD_CELLS> while the transaction is in
+ * progress. Returns the cells whose collapsed state was changed.
+ * 
+ * Parameters:
+ * 
+ * collapsed - Boolean indicating the collapsed state to be assigned.
+ * recurse - Optional boolean indicating if the collapsed state of all
+ * descendants should be set. Default is false.
+ * cells - Array of <mxCells> whose collapsed state should be set. If
+ * null is specified then the foldable selection cells are used.
+ * checkFoldable - Optional boolean indicating of isCellFoldable should be
+ * checked. Default is false.
+ * evt - Optional native event that triggered the invocation.
+ */
+mxGraph.prototype.foldCells = function(collapse, recurse, cells, checkFoldable, evt)
+{
+	recurse = (recurse != null) ? recurse : false;
+	
+	if (cells == null)
+	{
+		cells = this.getFoldableCells(this.getSelectionCells(), collapse);
+	}
+
+	this.stopEditing(false);
+
+	this.model.beginUpdate();
+	try
+	{
+		this.cellsFolded(cells, collapse, recurse, checkFoldable);
+		this.fireEvent(new mxEventObject(mxEvent.FOLD_CELLS,
+			'collapse', collapse, 'recurse', recurse, 'cells', cells));
+	}
+	finally
+	{
+		this.model.endUpdate();
+	}
+
+	return cells;
+};
+
+/**
+ * Function: cellsFolded
+ * 
+ * Sets the collapsed state of the specified cells. This method fires
+ * <mxEvent.CELLS_FOLDED> while the transaction is in progress. Returns the
+ * cells whose collapsed state was changed.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> whose collapsed state should be set.
+ * collapsed - Boolean indicating the collapsed state to be assigned.
+ * recurse - Boolean indicating if the collapsed state of all descendants
+ * should be set.
+ * checkFoldable - Optional boolean indicating of isCellFoldable should be
+ * checked. Default is false.
+ */
+mxGraph.prototype.cellsFolded = function(cells, collapse, recurse, checkFoldable)
+{
+	if (cells != null && cells.length > 0)
+	{
+		this.model.beginUpdate();
+		try
+		{
+			for (var i = 0; i < cells.length; i++)
+			{
+				if ((!checkFoldable || this.isCellFoldable(cells[i], collapse)) &&
+					collapse != this.isCellCollapsed(cells[i]))
+				{
+					this.model.setCollapsed(cells[i], collapse);
+					this.swapBounds(cells[i], collapse);
+
+					if (this.isExtendParent(cells[i]))
+					{
+						this.extendParent(cells[i]);
+					}
+
+					if (recurse)
+					{
+						var children = this.model.getChildren(cells[i]);
+						this.foldCells(children, collapse, recurse);
+					}
+					
+					this.constrainChild(cells[i]);
+				}
+			}
+			
+			this.fireEvent(new mxEventObject(mxEvent.CELLS_FOLDED,
+				'cells', cells, 'collapse', collapse, 'recurse', recurse));
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: swapBounds
+ * 
+ * Swaps the alternate and the actual bounds in the geometry of the given
+ * cell invoking <updateAlternateBounds> before carrying out the swap.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> for which the bounds should be swapped.
+ * willCollapse - Boolean indicating if the cell is going to be collapsed.
+ */
+mxGraph.prototype.swapBounds = function(cell, willCollapse)
+{
+	if (cell != null)
+	{
+		var geo = this.model.getGeometry(cell);
+		
+		if (geo != null)
+		{
+			geo = geo.clone();
+			
+			this.updateAlternateBounds(cell, geo, willCollapse);
+			geo.swap();
+			
+			this.model.setGeometry(cell, geo);
+		}
+	}
+};
+
+/**
+ * Function: updateAlternateBounds
+ * 
+ * Updates or sets the alternate bounds in the given geometry for the given
+ * cell depending on whether the cell is going to be collapsed. If no
+ * alternate bounds are defined in the geometry and
+ * <collapseToPreferredSize> is true, then the preferred size is used for
+ * the alternate bounds. The top, left corner is always kept at the same
+ * location.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> for which the geometry is being udpated.
+ * g - <mxGeometry> for which the alternate bounds should be updated.
+ * willCollapse - Boolean indicating if the cell is going to be collapsed.
+ */
+mxGraph.prototype.updateAlternateBounds = function(cell, geo, willCollapse)
+{
+	if (cell != null && geo != null)
+	{
+		var state = this.view.getState(cell);
+		var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+		if (geo.alternateBounds == null)
+		{
+			var bounds = geo;
+			
+			if (this.collapseToPreferredSize)
+			{
+				var tmp = this.getPreferredSizeForCell(cell);
+				
+				if (tmp != null)
+				{
+					bounds = tmp;
+
+					var startSize = mxUtils.getValue(style, mxConstants.STYLE_STARTSIZE);
+
+					if (startSize > 0)
+					{
+						bounds.height = Math.max(bounds.height, startSize);
+					}
+				}
+			}
+			
+			geo.alternateBounds = new mxRectangle(0, 0, bounds.width, bounds.height);
+		}
+		
+		if (geo.alternateBounds != null)
+		{
+			geo.alternateBounds.x = geo.x;
+			geo.alternateBounds.y = geo.y;
+			
+			var alpha = mxUtils.toRadians(style[mxConstants.STYLE_ROTATION] || 0);
+			
+			if (alpha != 0)
+			{
+				var dx = geo.alternateBounds.getCenterX() - geo.getCenterX();
+				var dy = geo.alternateBounds.getCenterY() - geo.getCenterY();
+	
+				var cos = Math.cos(alpha);
+				var sin = Math.sin(alpha);
+	
+				var dx2 = cos * dx - sin * dy;
+				var dy2 = sin * dx + cos * dy;
+				
+				geo.alternateBounds.x += dx2 - dx;
+				geo.alternateBounds.y += dy2 - dy;
+			}
+		}
+	}
+};
+
+/**
+ * Function: addAllEdges
+ * 
+ * Returns an array with the given cells and all edges that are connected
+ * to a cell or one of its descendants.
+ */
+mxGraph.prototype.addAllEdges = function(cells)
+{
+	var allCells = cells.slice();
+	
+	return mxUtils.removeDuplicates(allCells.concat(this.getAllEdges(cells)));
+};
+
+/**
+ * Function: getAllEdges
+ * 
+ * Returns all edges connected to the given cells or its descendants.
+ */
+mxGraph.prototype.getAllEdges = function(cells)
+{
+	var edges = [];
+	
+	if (cells != null)
+	{
+		for (var i = 0; i < cells.length; i++)
+		{
+			var edgeCount = this.model.getEdgeCount(cells[i]);
+			
+			for (var j = 0; j < edgeCount; j++)
+			{
+				edges.push(this.model.getEdgeAt(cells[i], j));
+			}
+
+			// Recurses
+			var children = this.model.getChildren(cells[i]);
+			edges = edges.concat(this.getAllEdges(children));
+		}
+	}
+	
+	return edges;
+};
+
+/**
+ * Group: Cell sizing
+ */
+
+/**
+ * Function: updateCellSize
+ * 
+ * Updates the size of the given cell in the model using <cellSizeUpdated>.
+ * This method fires <mxEvent.UPDATE_CELL_SIZE> while the transaction is in
+ * progress. Returns the cell whose size was updated.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose size should be updated.
+ */
+mxGraph.prototype.updateCellSize = function(cell, ignoreChildren)
+{
+	ignoreChildren = (ignoreChildren != null) ? ignoreChildren : false;
+	
+	this.model.beginUpdate();				
+	try
+	{
+		this.cellSizeUpdated(cell, ignoreChildren);
+		this.fireEvent(new mxEventObject(mxEvent.UPDATE_CELL_SIZE,
+				'cell', cell, 'ignoreChildren', ignoreChildren));
+	}
+	finally
+	{
+		this.model.endUpdate();
+	}
+	
+	return cell;
+};
+
+/**
+ * Function: cellSizeUpdated
+ * 
+ * Updates the size of the given cell in the model using
+ * <getPreferredSizeForCell> to get the new size.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> for which the size should be changed.
+ */
+mxGraph.prototype.cellSizeUpdated = function(cell, ignoreChildren)
+{
+	if (cell != null)
+	{
+		this.model.beginUpdate();				
+		try
+		{
+			var size = this.getPreferredSizeForCell(cell);
+			var geo = this.model.getGeometry(cell);
+			
+			if (size != null && geo != null)
+			{
+				var collapsed = this.isCellCollapsed(cell);
+				geo = geo.clone();
+
+				if (this.isSwimlane(cell))
+				{
+					var state = this.view.getState(cell);
+					var style = (state != null) ? state.style : this.getCellStyle(cell);
+					var cellStyle = this.model.getStyle(cell);
+
+					if (cellStyle == null)
+					{
+						cellStyle = '';
+					}
+
+					if (mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, true))
+					{
+						cellStyle = mxUtils.setStyle(cellStyle,
+								mxConstants.STYLE_STARTSIZE, size.height + 8);
+
+						if (collapsed)
+						{
+							geo.height = size.height + 8;
+						}
+
+						geo.width = size.width;
+					}
+					else
+					{
+						cellStyle = mxUtils.setStyle(cellStyle,
+								mxConstants.STYLE_STARTSIZE, size.width + 8);
+
+						if (collapsed)
+						{
+							geo.width = size.width + 8;
+						}
+
+						geo.height = size.height;
+					}
+
+					this.model.setStyle(cell, cellStyle);
+				}
+				else
+				{
+					geo.width = size.width;
+					geo.height = size.height;
+				}
+
+				if (!ignoreChildren && !collapsed)
+				{
+					var bounds = this.view.getBounds(this.model.getChildren(cell));
+
+					if (bounds != null)
+					{
+						var tr = this.view.translate;
+						var scale = this.view.scale;
+
+						var width = (bounds.x + bounds.width) / scale - geo.x - tr.x;
+						var height = (bounds.y + bounds.height) / scale - geo.y - tr.y;
+
+						geo.width = Math.max(geo.width, width);
+						geo.height = Math.max(geo.height, height);
+					}
+				}
+
+				this.cellsResized([cell], [geo], false);
+			}
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: getPreferredSizeForCell
+ * 
+ * Returns the preferred width and height of the given <mxCell> as an
+ * <mxRectangle>. To implement a minimum width, add a new style eg.
+ * minWidth in the vertex and override this method as follows.
+ * 
+ * (code)
+ * var graphGetPreferredSizeForCell = graph.getPreferredSizeForCell;
+ * graph.getPreferredSizeForCell = function(cell)
+ * {
+ *   var result = graphGetPreferredSizeForCell.apply(this, arguments);
+ *   var style = this.getCellStyle(cell);
+ *   
+ *   if (style['minWidth'] > 0)
+ *   {
+ *     result.width = Math.max(style['minWidth'], result.width);
+ *   }
+ * 
+ *   return result;
+ * };
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> for which the preferred size should be returned.
+ */
+mxGraph.prototype.getPreferredSizeForCell = function(cell)
+{
+	var result = null;
+	
+	if (cell != null)
+	{
+		var state = this.view.getState(cell) || this.view.createState(cell);
+		var style = state.style;
+
+		if (!this.model.isEdge(cell))
+		{
+			var fontSize = style[mxConstants.STYLE_FONTSIZE] || mxConstants.DEFAULT_FONTSIZE;
+			var dx = 0;
+			var dy = 0;
+			
+			// Adds dimension of image if shape is a label
+			if (this.getImage(state) != null || style[mxConstants.STYLE_IMAGE] != null)
+			{
+				if (style[mxConstants.STYLE_SHAPE] == mxConstants.SHAPE_LABEL)
+				{
+					if (style[mxConstants.STYLE_VERTICAL_ALIGN] == mxConstants.ALIGN_MIDDLE)
+					{
+						dx += parseFloat(style[mxConstants.STYLE_IMAGE_WIDTH]) || mxLabel.prototype.imageSize;
+					}
+					
+					if (style[mxConstants.STYLE_ALIGN] != mxConstants.ALIGN_CENTER)
+					{
+						dy += parseFloat(style[mxConstants.STYLE_IMAGE_HEIGHT]) || mxLabel.prototype.imageSize;
+					}
+				}
+			}
+
+			// Adds spacings
+			dx += 2 * (style[mxConstants.STYLE_SPACING] || 0);
+			dx += style[mxConstants.STYLE_SPACING_LEFT] || 0;
+			dx += style[mxConstants.STYLE_SPACING_RIGHT] || 0;
+
+			dy += 2 * (style[mxConstants.STYLE_SPACING] || 0);
+			dy += style[mxConstants.STYLE_SPACING_TOP] || 0;
+			dy += style[mxConstants.STYLE_SPACING_BOTTOM] || 0;
+			
+			// Add spacing for collapse/expand icon
+			// LATER: Check alignment and use constants
+			// for image spacing
+			var image = this.getFoldingImage(state);
+			
+			if (image != null)
+			{
+				dx += image.width + 8;
+			}
+
+			// Adds space for label
+			var value = this.cellRenderer.getLabelValue(state);
+
+			if (value != null && value.length > 0)
+			{
+				if (!this.isHtmlLabel(state.cell))
+				{
+					value = mxUtils.htmlEntities(value);
+				}
+				
+				value = value.replace(/\n/g, '<br>');
+				
+				var size = mxUtils.getSizeForString(value, fontSize, style[mxConstants.STYLE_FONTFAMILY]);
+				var width = size.width + dx;
+				var height = size.height + dy;
+				
+				if (!mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, true))
+				{
+					var tmp = height;
+					
+					height = width;
+					width = tmp;
+				}
+			
+				if (this.gridEnabled)
+				{
+					width = this.snap(width + this.gridSize / 2);
+					height = this.snap(height + this.gridSize / 2);
+				}
+
+				result = new mxRectangle(0, 0, width, height);
+			}
+			else
+			{
+				var gs2 = 4 * this.gridSize;
+				result = new mxRectangle(0, 0, gs2, gs2);
+			}
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: resizeCell
+ * 
+ * Sets the bounds of the given cell using <resizeCells>. Returns the
+ * cell which was passed to the function.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose bounds should be changed.
+ * bounds - <mxRectangle> that represents the new bounds.
+ */
+mxGraph.prototype.resizeCell = function(cell, bounds, recurse)
+{
+	return this.resizeCells([cell], [bounds], recurse)[0];
+};
+
+/**
+ * Function: resizeCells
+ * 
+ * Sets the bounds of the given cells and fires a <mxEvent.RESIZE_CELLS>
+ * event while the transaction is in progress. Returns the cells which
+ * have been passed to the function.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> whose bounds should be changed.
+ * bounds - Array of <mxRectangles> that represent the new bounds.
+ */
+mxGraph.prototype.resizeCells = function(cells, bounds, recurse)
+{
+	recurse = (recurse != null) ? recurse : this.isRecursiveResize();
+	
+	this.model.beginUpdate();
+	try
+	{
+		this.cellsResized(cells, bounds, recurse);
+		this.fireEvent(new mxEventObject(mxEvent.RESIZE_CELLS,
+				'cells', cells, 'bounds', bounds));
+	}
+	finally
+	{
+		this.model.endUpdate();
+	}
+
+	return cells;
+};
+
+/**
+ * Function: cellsResized
+ * 
+ * Sets the bounds of the given cells and fires a <mxEvent.CELLS_RESIZED>
+ * event. If <extendParents> is true, then the parent is extended if a
+ * child size is changed so that it overlaps with the parent.
+ * 
+ * The following example shows how to control group resizes to make sure
+ * that all child cells stay within the group.
+ * 
+ * (code)
+ * graph.addListener(mxEvent.CELLS_RESIZED, function(sender, evt)
+ * {
+ *   var cells = evt.getProperty('cells');
+ *   
+ *   if (cells != null)
+ *   {
+ *     for (var i = 0; i < cells.length; i++)
+ *     {
+ *       if (graph.getModel().getChildCount(cells[i]) > 0)
+ *       {
+ *         var geo = graph.getCellGeometry(cells[i]);
+ *         
+ *         if (geo != null)
+ *         {
+ *           var children = graph.getChildCells(cells[i], true, true);
+ *           var bounds = graph.getBoundingBoxFromGeometry(children, true);
+ *           
+ *           geo = geo.clone();
+ *           geo.width = Math.max(geo.width, bounds.width);
+ *           geo.height = Math.max(geo.height, bounds.height);
+ *           
+ *           graph.getModel().setGeometry(cells[i], geo);
+ *         }
+ *       }
+ *     }
+ *   }
+ * });
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> whose bounds should be changed.
+ * bounds - Array of <mxRectangles> that represent the new bounds.
+ * recurse - Optional boolean that specifies if the children should be resized.
+ */
+mxGraph.prototype.cellsResized = function(cells, bounds, recurse)
+{
+	recurse = (recurse != null) ? recurse : false;
+	
+	if (cells != null && bounds != null && cells.length == bounds.length)
+	{
+		this.model.beginUpdate();
+		try
+		{
+			for (var i = 0; i < cells.length; i++)
+			{
+				this.cellResized(cells[i], bounds[i], false, recurse);
+
+				if (this.isExtendParent(cells[i]))
+				{
+					this.extendParent(cells[i]);
+				}
+				
+				this.constrainChild(cells[i]);
+			}
+
+			if (this.resetEdgesOnResize)
+			{
+				this.resetEdges(cells);
+			}
+			
+			this.fireEvent(new mxEventObject(mxEvent.CELLS_RESIZED,
+					'cells', cells, 'bounds', bounds));
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: cellResized
+ * 
+ * Resizes the parents recursively so that they contain the complete area
+ * of the resized child cell.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose bounds should be changed.
+ * bounds - <mxRectangles> that represent the new bounds.
+ * ignoreRelative - Boolean that indicates if relative cells should be ignored.
+ * recurse - Optional boolean that specifies if the children should be resized.
+ */
+mxGraph.prototype.cellResized = function(cell, bounds, ignoreRelative, recurse)
+{
+	var geo = this.model.getGeometry(cell);
+
+	if (geo != null && (geo.x != bounds.x || geo.y != bounds.y ||
+		geo.width != bounds.width || geo.height != bounds.height))
+	{
+		geo = geo.clone();
+
+		if (!ignoreRelative && geo.relative)
+		{
+			var offset = geo.offset;
+
+			if (offset != null)
+			{
+				offset.x += bounds.x - geo.x;
+				offset.y += bounds.y - geo.y;
+			}
+		}
+		else
+		{
+			geo.x = bounds.x;
+			geo.y = bounds.y;
+		}
+
+		geo.width = bounds.width;
+		geo.height = bounds.height;
+
+		if (!geo.relative && this.model.isVertex(cell) && !this.isAllowNegativeCoordinates())
+		{
+			geo.x = Math.max(0, geo.x);
+			geo.y = Math.max(0, geo.y);
+		}
+
+		this.model.beginUpdate();
+		try
+		{
+			if (recurse)
+			{
+				this.resizeChildCells(cell, geo);
+			}
+						
+			this.model.setGeometry(cell, geo);
+			this.constrainChildCells(cell);
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: resizeChildCells
+ * 
+ * Resizes the child cells of the given cell for the given new geometry with
+ * respect to the current geometry of the cell.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that has been resized.
+ * newGeo - <mxGeometry> that represents the new bounds.
+ */
+mxGraph.prototype.resizeChildCells = function(cell, newGeo)
+{
+	var geo = this.model.getGeometry(cell);
+	var dx = newGeo.width / geo.width;
+	var dy = newGeo.height / geo.height;
+	var childCount = this.model.getChildCount(cell);
+	
+	for (var i = 0; i < childCount; i++)
+	{
+		this.scaleCell(this.model.getChildAt(cell, i), dx, dy, true);
+	}
+};
+
+/**
+ * Function: constrainChildCells
+ * 
+ * Constrains the children of the given cell using <constrainChild>.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that has been resized.
+ */
+mxGraph.prototype.constrainChildCells = function(cell)
+{
+	var childCount = this.model.getChildCount(cell);
+	
+	for (var i = 0; i < childCount; i++)
+	{
+		this.constrainChild(this.model.getChildAt(cell, i));
+	}
+};
+
+/**
+ * Function: scaleCell
+ * 
+ * Scales the points, position and size of the given cell according to the
+ * given vertical and horizontal scaling factors.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose geometry should be scaled.
+ * dx - Horizontal scaling factor.
+ * dy - Vertical scaling factor.
+ * recurse - Boolean indicating if the child cells should be scaled.
+ */
+mxGraph.prototype.scaleCell = function(cell, dx, dy, recurse)
+{
+	var geo = this.model.getGeometry(cell);
+	
+	if (geo != null)
+	{
+		var state = this.view.getState(cell);
+		var style = (state != null) ? state.style : this.getCellStyle(cell);
+		
+		geo = geo.clone();
+		
+		// Stores values for restoring based on style
+		var x = geo.x;
+		var y = geo.y
+		var w = geo.width;
+		var h = geo.height;
+		
+		geo.scale(dx, dy, style[mxConstants.STYLE_ASPECT] == 'fixed');
+		
+		if (style[mxConstants.STYLE_RESIZE_WIDTH] == '1')
+		{
+			geo.width = w * dx;
+		}
+		else if (style[mxConstants.STYLE_RESIZE_WIDTH] == '0')
+		{
+			geo.width = w;
+		}
+		
+		if (style[mxConstants.STYLE_RESIZE_HEIGHT] == '1')
+		{
+			geo.height = h * dy;
+		}
+		else if (style[mxConstants.STYLE_RESIZE_HEIGHT] == '0')
+		{
+			geo.height = h;
+		}
+		
+		if (!this.isCellMovable(cell))
+		{
+			geo.x = x;
+			geo.y = y;
+		}
+		
+		if (!this.isCellResizable(cell))
+		{
+			geo.width = w;
+			geo.height = h;
+		}
+
+		if (this.model.isVertex(cell))
+		{
+			this.cellResized(cell, geo, true, recurse);
+		}
+		else
+		{
+			this.model.setGeometry(cell, geo);
+		}
+	}
+};
+
+/**
+ * Function: extendParent
+ * 
+ * Resizes the parents recursively so that they contain the complete area
+ * of the resized child cell.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that has been resized.
+ */
+mxGraph.prototype.extendParent = function(cell)
+{
+	if (cell != null)
+	{
+		var parent = this.model.getParent(cell);
+		var p = this.getCellGeometry(parent);
+		
+		if (parent != null && p != null && !this.isCellCollapsed(parent))
+		{
+			var geo = this.getCellGeometry(cell);
+			
+			if (geo != null && !geo.relative &&
+				(p.width < geo.x + geo.width ||
+				p.height < geo.y + geo.height))
+			{
+				p = p.clone();
+				
+				p.width = Math.max(p.width, geo.x + geo.width);
+				p.height = Math.max(p.height, geo.y + geo.height);
+				
+				this.cellsResized([parent], [p], false);
+			}
+		}
+	}
+};
+
+/**
+ * Group: Cell moving
+ */
+
+/**
+ * Function: importCells
+ * 
+ * Clones and inserts the given cells into the graph using the move
+ * method and returns the inserted cells. This shortcut is used if
+ * cells are inserted via datatransfer.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> to be imported.
+ * dx - Integer that specifies the x-coordinate of the vector. Default is 0.
+ * dy - Integer that specifies the y-coordinate of the vector. Default is 0.
+ * target - <mxCell> that represents the new parent of the cells.
+ * evt - Mouseevent that triggered the invocation.
+ * mapping - Optional mapping for existing clones.
+ */
+mxGraph.prototype.importCells = function(cells, dx, dy, target, evt, mapping)
+{	
+	return this.moveCells(cells, dx, dy, true, target, evt, mapping);
+};
+
+/**
+ * Function: moveCells
+ * 
+ * Moves or clones the specified cells and moves the cells or clones by the
+ * given amount, adding them to the optional target cell. The evt is the
+ * mouse event as the mouse was released. The change is carried out using
+ * <cellsMoved>. This method fires <mxEvent.MOVE_CELLS> while the
+ * transaction is in progress. Returns the cells that were moved.
+ * 
+ * Use the following code to move all cells in the graph.
+ * 
+ * (code)
+ * graph.moveCells(graph.getChildCells(null, true, true), 10, 10);
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> to be moved, cloned or added to the target.
+ * dx - Integer that specifies the x-coordinate of the vector. Default is 0.
+ * dy - Integer that specifies the y-coordinate of the vector. Default is 0.
+ * clone - Boolean indicating if the cells should be cloned. Default is false.
+ * target - <mxCell> that represents the new parent of the cells.
+ * evt - Mouseevent that triggered the invocation.
+ * mapping - Optional mapping for existing clones.
+ */
+mxGraph.prototype.moveCells = function(cells, dx, dy, clone, target, evt, mapping)
+{
+	dx = (dx != null) ? dx : 0;
+	dy = (dy != null) ? dy : 0;
+	clone = (clone != null) ? clone : false;
+	
+	if (cells != null && (dx != 0 || dy != 0 || clone || target != null))
+	{
+		// Removes descandants with ancestors in cells to avoid multiple moving
+		cells = this.model.getTopmostCells(cells);
+
+		this.model.beginUpdate();
+		try
+		{
+			// Faster cell lookups to remove relative edge labels with selected
+			// terminals to avoid explicit and implicit move at same time
+			var dict = new mxDictionary();
+			
+			for (var i = 0; i < cells.length; i++)
+			{
+				dict.put(cells[i], true);
+			}
+			
+			var isSelected = mxUtils.bind(this, function(cell)
+			{
+				while (cell != null)
+				{
+					if (dict.get(cell))
+					{
+						return true;
+					}
+					
+					cell = this.model.getParent(cell);
+				}
+				
+				return false;
+			});
+			
+			// Removes relative edge labels with selected terminals
+			var checked = [];
+			
+			for (var i = 0; i < cells.length; i++)
+			{
+				var geo = this.getCellGeometry(cells[i]);
+				var parent = this.model.getParent(cells[i]);
+		
+				if ((geo == null || !geo.relative) || !this.model.isEdge(parent) ||
+					(!isSelected(this.model.getTerminal(parent, true)) &&
+					!isSelected(this.model.getTerminal(parent, false))))
+				{
+					checked.push(cells[i]);
+				}
+			}
+
+			cells = checked;
+			
+			if (clone)
+			{
+				cells = this.cloneCells(cells, this.isCloneInvalidEdges(), mapping);
+
+				if (target == null)
+				{
+					target = this.getDefaultParent();
+				}
+			}
+
+			// FIXME: Cells should always be inserted first before any other edit
+			// to avoid forward references in sessions.
+			// Need to disable allowNegativeCoordinates if target not null to
+			// allow for temporary negative numbers until cellsAdded is called.
+			var previous = this.isAllowNegativeCoordinates();
+			
+			if (target != null)
+			{
+				this.setAllowNegativeCoordinates(true);
+			}
+			
+			this.cellsMoved(cells, dx, dy, !clone && this.isDisconnectOnMove()
+					&& this.isAllowDanglingEdges(), target == null,
+					this.isExtendParentsOnMove() && target == null);
+			
+			this.setAllowNegativeCoordinates(previous);
+
+			if (target != null)
+			{
+				var index = this.model.getChildCount(target);
+				this.cellsAdded(cells, target, index, null, null, true);
+			}
+
+			// Dispatches a move event
+			this.fireEvent(new mxEventObject(mxEvent.MOVE_CELLS, 'cells', cells,
+				'dx', dx, 'dy', dy, 'clone', clone, 'target', target, 'event', evt));
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+
+	return cells;
+};
+
+/**
+ * Function: cellsMoved
+ * 
+ * Moves the specified cells by the given vector, disconnecting the cells
+ * using disconnectGraph is disconnect is true. This method fires
+ * <mxEvent.CELLS_MOVED> while the transaction is in progress.
+ */
+mxGraph.prototype.cellsMoved = function(cells, dx, dy, disconnect, constrain, extend)
+{
+	if (cells != null && (dx != 0 || dy != 0))
+	{
+		extend = (extend != null) ? extend : false;
+
+		this.model.beginUpdate();
+		try
+		{
+			if (disconnect)
+			{
+				this.disconnectGraph(cells);
+			}
+
+			for (var i = 0; i < cells.length; i++)
+			{
+				this.translateCell(cells[i], dx, dy);
+				
+				if (extend && this.isExtendParent(cells[i]))
+				{
+					this.extendParent(cells[i]);
+				}
+				else if (constrain)
+				{
+					this.constrainChild(cells[i]);
+				}
+			}
+
+			if (this.resetEdgesOnMove)
+			{
+				this.resetEdges(cells);
+			}
+			
+			this.fireEvent(new mxEventObject(mxEvent.CELLS_MOVED,
+				'cells', cells, 'dx', dx, 'dy', dy, 'disconnect', disconnect));
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: translateCell
+ * 
+ * Translates the geometry of the given cell and stores the new,
+ * translated geometry in the model as an atomic change.
+ */
+mxGraph.prototype.translateCell = function(cell, dx, dy)
+{
+	var geo = this.model.getGeometry(cell);
+
+	if (geo != null)
+	{
+		dx = parseFloat(dx);
+		dy = parseFloat(dy);
+		geo = geo.clone();
+		geo.translate(dx, dy);
+
+		if (!geo.relative && this.model.isVertex(cell) && !this.isAllowNegativeCoordinates())
+		{
+			geo.x = Math.max(0, parseFloat(geo.x));
+			geo.y = Math.max(0, parseFloat(geo.y));
+		}
+		
+		if (geo.relative && !this.model.isEdge(cell))
+		{
+			var parent = this.model.getParent(cell);
+			var angle = 0;
+			
+			if (this.model.isVertex(parent))
+			{
+				var state = this.view.getState(parent);
+				var style = (state != null) ? state.style : this.getCellStyle(parent);
+				
+				angle = mxUtils.getValue(style, mxConstants.STYLE_ROTATION, 0);
+			}
+			
+			if (angle != 0)
+			{
+				var rad = mxUtils.toRadians(-angle);
+				var cos = Math.cos(rad);
+				var sin = Math.sin(rad);
+				var pt = mxUtils.getRotatedPoint(new mxPoint(dx, dy), cos, sin, new mxPoint(0, 0));
+				dx = pt.x;
+				dy = pt.y;
+			}
+			
+			if (geo.offset == null)
+			{
+				geo.offset = new mxPoint(dx, dy);
+			}
+			else
+			{
+				geo.offset.x = parseFloat(geo.offset.x) + dx;
+				geo.offset.y = parseFloat(geo.offset.y) + dy;
+			}
+		}
+
+		this.model.setGeometry(cell, geo);
+	}
+};
+
+/**
+ * Function: getCellContainmentArea
+ * 
+ * Returns the <mxRectangle> inside which a cell is to be kept.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> for which the area should be returned.
+ */
+mxGraph.prototype.getCellContainmentArea = function(cell)
+{
+	if (cell != null && !this.model.isEdge(cell))
+	{
+		var parent = this.model.getParent(cell);
+		
+		if (parent != null && parent != this.getDefaultParent())
+		{
+			var g = this.model.getGeometry(parent);
+			
+			if (g != null)
+			{
+				var x = 0;
+				var y = 0;
+				var w = g.width;
+				var h = g.height;
+				
+				if (this.isSwimlane(parent))
+				{
+					var size = this.getStartSize(parent);
+					
+					var state = this.view.getState(parent);
+					var style = (state != null) ? state.style : this.getCellStyle(parent);
+					var dir = mxUtils.getValue(style, mxConstants.STYLE_DIRECTION, mxConstants.DIRECTION_EAST);
+					var flipH = mxUtils.getValue(style, mxConstants.STYLE_FLIPH, 0) == 1;
+					var flipV = mxUtils.getValue(style, mxConstants.STYLE_FLIPV, 0) == 1;
+					
+					if (dir == mxConstants.DIRECTION_SOUTH || dir == mxConstants.DIRECTION_NORTH)
+					{
+						var tmp = size.width;
+						size.width = size.height;
+						size.height = tmp;
+					}
+					
+					if ((dir == mxConstants.DIRECTION_EAST && !flipV) || (dir == mxConstants.DIRECTION_NORTH && !flipH) ||
+						(dir == mxConstants.DIRECTION_WEST && flipV) || (dir == mxConstants.DIRECTION_SOUTH && flipH))
+					{
+						x = size.width;
+						y = size.height;
+					}
+
+					w -= size.width;
+					h -= size.height;
+				}
+				
+				return new mxRectangle(x, y, w, h);
+			}
+		}
+	}
+	
+	return null;
+};
+
+/**
+ * Function: getMaximumGraphBounds
+ * 
+ * Returns the bounds inside which the diagram should be kept as an
+ * <mxRectangle>.
+ */
+mxGraph.prototype.getMaximumGraphBounds = function()
+{
+	return this.maximumGraphBounds;
+};
+
+/**
+ * Function: constrainChild
+ * 
+ * Keeps the given cell inside the bounds returned by
+ * <getCellContainmentArea> for its parent, according to the rules defined by
+ * <getOverlap> and <isConstrainChild>. This modifies the cell's geometry
+ * in-place and does not clone it.
+ * 
+ * Parameters:
+ * 
+ * cells - <mxCell> which should be constrained.
+ * sizeFirst - Specifies if the size should be changed first. Default is true.
+ */
+mxGraph.prototype.constrainChild = function(cell, sizeFirst)
+{
+	sizeFirst = (sizeFirst != null) ? sizeFirst : true;
+	
+	if (cell != null)
+	{
+		var geo = this.getCellGeometry(cell);
+		
+		if (geo != null && (this.isConstrainRelativeChildren() || !geo.relative))
+		{
+			var parent = this.model.getParent(cell);
+			var pgeo = this.getCellGeometry(parent);
+			var max = this.getMaximumGraphBounds();
+			
+			// Finds parent offset
+			if (max != null)
+			{
+				var off = this.getBoundingBoxFromGeometry([parent], false);
+				
+				if (off != null)
+				{
+					max = mxRectangle.fromRectangle(max);
+					
+					max.x -= off.x;
+					max.y -= off.y;
+				}
+			}
+			
+			if (this.isConstrainChild(cell))
+			{
+				var tmp = this.getCellContainmentArea(cell);
+				
+				if (tmp != null)
+				{
+					var overlap = this.getOverlap(cell);
+	
+					if (overlap > 0)
+					{
+						tmp = mxRectangle.fromRectangle(tmp);
+						
+						tmp.x -= tmp.width * overlap;
+						tmp.y -= tmp.height * overlap;
+						tmp.width += 2 * tmp.width * overlap;
+						tmp.height += 2 * tmp.height * overlap;
+					}
+					
+					// Find the intersection between max and tmp
+					if (max == null)
+					{
+						max = tmp;
+					}
+					else
+					{
+						max = mxRectangle.fromRectangle(max);
+						max.intersect(tmp);
+					}
+				}
+			}
+			
+			if (max != null)
+			{
+				var cells = [cell];
+				
+				if (!this.isCellCollapsed(cell))
+				{
+					var desc = this.model.getDescendants(cell);
+					
+					for (var i = 0; i < desc.length; i++)
+					{
+						if (this.isCellVisible(desc[i]))
+						{
+							cells.push(desc[i]);
+						}
+					}
+				}
+				
+				var bbox = this.getBoundingBoxFromGeometry(cells, false);
+				
+				if (bbox != null)
+				{
+					geo = geo.clone();
+					
+					// Cumulative horizontal movement
+					var dx = 0;
+					
+					if (geo.width > max.width)
+					{
+						dx = geo.width - max.width;
+						geo.width -= dx;
+					}
+					
+					if (bbox.x + bbox.width > max.x + max.width)
+					{
+						dx -= bbox.x + bbox.width - max.x - max.width - dx;
+					}
+					
+					// Cumulative vertical movement
+					var dy = 0;
+					
+					if (geo.height > max.height)
+					{
+						dy = geo.height - max.height;
+						geo.height -= dy;
+					}
+					
+					if (bbox.y + bbox.height > max.y + max.height)
+					{
+						dy -= bbox.y + bbox.height - max.y - max.height - dy;
+					}
+					
+					if (bbox.x < max.x)
+					{
+						dx -= bbox.x - max.x;
+					}
+					
+					if (bbox.y < max.y)
+					{
+						dy -= bbox.y - max.y;
+					}
+					
+					if (dx != 0 || dy != 0)
+					{
+						if (geo.relative)
+						{
+							// Relative geometries are moved via absolute offset
+							if (geo.offset == null)
+							{
+								geo.offset = new mxPoint();
+							}
+						
+							geo.offset.x += dx;
+							geo.offset.y += dy;
+						}
+						else
+						{
+							geo.x += dx;
+							geo.y += dy;
+						}
+					}
+					
+					this.model.setGeometry(cell, geo);
+				}
+			}
+		}
+	}
+};
+
+/**
+ * Function: resetEdges
+ * 
+ * Resets the control points of the edges that are connected to the given
+ * cells if not both ends of the edge are in the given cells array.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> for which the connected edges should be
+ * reset.
+ */
+mxGraph.prototype.resetEdges = function(cells)
+{
+	if (cells != null)
+	{
+		// Prepares faster cells lookup
+		var dict = new mxDictionary();
+		
+		for (var i = 0; i < cells.length; i++)
+		{
+			dict.put(cells[i], true);
+		}
+		
+		this.model.beginUpdate();
+		try
+		{
+			for (var i = 0; i < cells.length; i++)
+			{
+				var edges = this.model.getEdges(cells[i]);
+				
+				if (edges != null)
+				{
+					for (var j = 0; j < edges.length; j++)
+					{
+						var state = this.view.getState(edges[j]);
+						
+						var source = (state != null) ? state.getVisibleTerminal(true) : this.view.getVisibleTerminal(edges[j], true);
+						var target = (state != null) ? state.getVisibleTerminal(false) : this.view.getVisibleTerminal(edges[j], false);
+						
+						// Checks if one of the terminals is not in the given array
+						if (!dict.get(source) || !dict.get(target))
+						{
+							this.resetEdge(edges[j]);
+						}
+					}
+				}
+				
+				this.resetEdges(this.model.getChildren(cells[i]));
+			}
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: resetEdge
+ * 
+ * Resets the control points of the given edge.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> whose points should be reset.
+ */
+mxGraph.prototype.resetEdge = function(edge)
+{
+	var geo = this.model.getGeometry(edge);
+	
+	// Resets the control points
+	if (geo != null && geo.points != null && geo.points.length > 0)
+	{
+		geo = geo.clone();
+		geo.points = [];
+		this.model.setGeometry(edge, geo);
+	}
+	
+	return edge;
+};
+
+/**
+ * Group: Cell connecting and connection constraints
+ */
+
+/**
+ * Function: getOutlineConstraint
+ * 
+ * Returns the constraint used to connect to the outline of the given state.
+ */
+mxGraph.prototype.getOutlineConstraint = function(point, terminalState, me)
+{
+	if (terminalState.shape != null)
+	{
+		var bounds = this.view.getPerimeterBounds(terminalState);
+		var direction = terminalState.style[mxConstants.STYLE_DIRECTION];
+		
+		if (direction == mxConstants.DIRECTION_NORTH || direction == mxConstants.DIRECTION_SOUTH)
+		{
+			bounds.x += bounds.width / 2 - bounds.height / 2;
+			bounds.y += bounds.height / 2 - bounds.width / 2;
+			var tmp = bounds.width;
+			bounds.width = bounds.height;
+			bounds.height = tmp;
+		}
+	
+		var alpha = mxUtils.toRadians(terminalState.shape.getShapeRotation());
+		
+		if (alpha != 0)
+		{
+			var cos = Math.cos(-alpha);
+			var sin = Math.sin(-alpha);
+	
+			var ct = new mxPoint(bounds.getCenterX(), bounds.getCenterY());
+			point = mxUtils.getRotatedPoint(point, cos, sin, ct);
+		}
+
+		var sx = 1;
+		var sy = 1;
+		var dx = 0;
+		var dy = 0;
+		
+		// LATER: Add flipping support for image shapes
+		if (this.getModel().isVertex(terminalState.cell))
+		{
+			var flipH = terminalState.style[mxConstants.STYLE_FLIPH];
+			var flipV = terminalState.style[mxConstants.STYLE_FLIPV];
+			
+			// Legacy support for stencilFlipH/V
+			if (terminalState.shape != null && terminalState.shape.stencil != null)
+			{
+				flipH = mxUtils.getValue(terminalState.style, 'stencilFlipH', 0) == 1 || flipH;
+				flipV = mxUtils.getValue(terminalState.style, 'stencilFlipV', 0) == 1 || flipV;
+			}
+			
+			if (direction == mxConstants.DIRECTION_NORTH || direction == mxConstants.DIRECTION_SOUTH)
+			{
+				var tmp = flipH;
+				flipH = flipV;
+				flipV = tmp;
+			}
+			
+			if (flipH)
+			{
+				sx = -1;
+				dx = -bounds.width;
+			}
+			
+			if (flipV)
+			{
+				sy = -1;
+				dy = -bounds.height ;
+			}
+		}
+		
+		point = new mxPoint((point.x - bounds.x) * sx - dx + bounds.x, (point.y - bounds.y) * sy - dy + bounds.y);
+		
+		var x = Math.round((point.x - bounds.x) * 1000 / bounds.width) / 1000;
+		var y = Math.round((point.y - bounds.y) * 1000 / bounds.height) / 1000;
+		
+		return new mxConnectionConstraint(new mxPoint(x, y), false);
+	}
+	
+	return null;
+};
+
+/**
+ * Function: getAllConnectionConstraints
+ * 
+ * Returns an array of all <mxConnectionConstraints> for the given terminal. If
+ * the shape of the given terminal is a <mxStencilShape> then the constraints
+ * of the corresponding <mxStencil> are returned.
+ * 
+ * Parameters:
+ * 
+ * terminal - <mxCellState> that represents the terminal.
+ * source - Boolean that specifies if the terminal is the source or target.
+ */
+mxGraph.prototype.getAllConnectionConstraints = function(terminal, source)
+{
+	if (terminal != null && terminal.shape != null && terminal.shape.stencil != null)
+	{
+		return terminal.shape.stencil.constraints;
+	}
+
+	return null;
+};
+
+/**
+ * Function: getConnectionConstraint
+ * 
+ * Returns an <mxConnectionConstraint> that describes the given connection
+ * point. This result can then be passed to <getConnectionPoint>.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCellState> that represents the edge.
+ * terminal - <mxCellState> that represents the terminal.
+ * source - Boolean indicating if the terminal is the source or target.
+ */
+mxGraph.prototype.getConnectionConstraint = function(edge, terminal, source)
+{
+	var point = null;
+	var x = edge.style[(source) ? mxConstants.STYLE_EXIT_X : mxConstants.STYLE_ENTRY_X];
+
+	if (x != null)
+	{
+		var y = edge.style[(source) ? mxConstants.STYLE_EXIT_Y : mxConstants.STYLE_ENTRY_Y];
+		
+		if (y != null)
+		{
+			point = new mxPoint(parseFloat(x), parseFloat(y));
+		}
+	}
+	
+	var perimeter = false;
+	
+	if (point != null)
+	{
+		perimeter = mxUtils.getValue(edge.style, (source) ? mxConstants.STYLE_EXIT_PERIMETER :
+			mxConstants.STYLE_ENTRY_PERIMETER, true);
+	}
+	
+	return new mxConnectionConstraint(point, perimeter);
+};
+
+/**
+ * Function: setConnectionConstraint
+ * 
+ * Sets the <mxConnectionConstraint> that describes the given connection point.
+ * If no constraint is given then nothing is changed. To remove an existing
+ * constraint from the given edge, use an empty constraint instead.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> that represents the edge.
+ * terminal - <mxCell> that represents the terminal.
+ * source - Boolean indicating if the terminal is the source or target.
+ * constraint - Optional <mxConnectionConstraint> to be used for this
+ * connection.
+ */
+mxGraph.prototype.setConnectionConstraint = function(edge, terminal, source, constraint)
+{
+	if (constraint != null)
+	{
+		this.model.beginUpdate();
+		
+		try
+		{
+			if (constraint == null || constraint.point == null)
+			{
+				this.setCellStyles((source) ? mxConstants.STYLE_EXIT_X :
+					mxConstants.STYLE_ENTRY_X, null, [edge]);
+				this.setCellStyles((source) ? mxConstants.STYLE_EXIT_Y :
+					mxConstants.STYLE_ENTRY_Y, null, [edge]);
+				this.setCellStyles((source) ? mxConstants.STYLE_EXIT_PERIMETER :
+					mxConstants.STYLE_ENTRY_PERIMETER, null, [edge]);
+			}
+			else if (constraint.point != null)
+			{
+				this.setCellStyles((source) ? mxConstants.STYLE_EXIT_X :
+					mxConstants.STYLE_ENTRY_X, constraint.point.x, [edge]);
+				this.setCellStyles((source) ? mxConstants.STYLE_EXIT_Y :
+					mxConstants.STYLE_ENTRY_Y, constraint.point.y, [edge]);
+				
+				// Only writes 0 since 1 is default
+				if (!constraint.perimeter)
+				{
+					this.setCellStyles((source) ? mxConstants.STYLE_EXIT_PERIMETER :
+						mxConstants.STYLE_ENTRY_PERIMETER, '0', [edge]);
+				}
+				else
+				{
+					this.setCellStyles((source) ? mxConstants.STYLE_EXIT_PERIMETER :
+						mxConstants.STYLE_ENTRY_PERIMETER, null, [edge]);
+				}
+			}
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: getConnectionPoint
+ *
+ * Returns the nearest point in the list of absolute points or the center
+ * of the opposite terminal.
+ * 
+ * Parameters:
+ * 
+ * vertex - <mxCellState> that represents the vertex.
+ * constraint - <mxConnectionConstraint> that represents the connection point
+ * constraint as returned by <getConnectionConstraint>.
+ */
+mxGraph.prototype.getConnectionPoint = function(vertex, constraint)
+{
+	var point = null;
+	
+	if (vertex != null && constraint.point != null)
+	{
+		var bounds = this.view.getPerimeterBounds(vertex);
+        var cx = new mxPoint(bounds.getCenterX(), bounds.getCenterY());
+		var direction = vertex.style[mxConstants.STYLE_DIRECTION];
+		var r1 = 0;
+		
+		// Bounds need to be rotated by 90 degrees for further computation
+		if (direction != null)
+		{
+			if (direction == mxConstants.DIRECTION_NORTH)
+			{
+				r1 += 270;
+			}
+			else if (direction == mxConstants.DIRECTION_WEST)
+			{
+				r1 += 180;
+			}
+			else if (direction == mxConstants.DIRECTION_SOUTH)
+			{
+				r1 += 90;
+			}
+
+			// Bounds need to be rotated by 90 degrees for further computation
+			if (direction == mxConstants.DIRECTION_NORTH || direction == mxConstants.DIRECTION_SOUTH)
+			{
+				bounds.rotate90();
+			}
+		}
+
+		point = new mxPoint(bounds.x + constraint.point.x * bounds.width,
+				bounds.y + constraint.point.y * bounds.height);
+		
+		// Rotation for direction before projection on perimeter
+		var r2 = vertex.style[mxConstants.STYLE_ROTATION] || 0;
+		
+		if (constraint.perimeter)
+		{
+			if (r1 != 0)
+			{
+				// Only 90 degrees steps possible here so no trig needed
+				var cos = 0;
+				var sin = 0;
+				
+				if (r1 == 90)
+				{
+					sin = 1;
+				}
+				else if (r1 == 180)
+				{
+					cos = -1;
+				}
+				else if (r1 == 270)
+				{
+					sin = -1;
+				}
+				
+		        point = mxUtils.getRotatedPoint(point, cos, sin, cx);
+			}
+	
+			point = this.view.getPerimeterPoint(vertex, point, false);
+		}
+		else
+		{
+			r2 += r1;
+			
+			if (this.getModel().isVertex(vertex.cell))
+			{
+				var flipH = vertex.style[mxConstants.STYLE_FLIPH] == 1;
+				var flipV = vertex.style[mxConstants.STYLE_FLIPV] == 1;
+				
+				// Legacy support for stencilFlipH/V
+				if (vertex.shape != null && vertex.shape.stencil != null)
+				{
+					flipH = (mxUtils.getValue(vertex.style, 'stencilFlipH', 0) == 1) || flipH;
+					flipV = (mxUtils.getValue(vertex.style, 'stencilFlipV', 0) == 1) || flipV;
+				}
+				
+				if (flipH)
+				{
+					point.x = 2 * bounds.getCenterX() - point.x;
+				}
+				
+				if (flipV)
+				{
+					point.y = 2 * bounds.getCenterY() - point.y;
+				}
+			}
+		}
+
+		// Generic rotation after projection on perimeter
+		if (r2 != 0 && point != null)
+		{
+	        var rad = mxUtils.toRadians(r2);
+	        var cos = Math.cos(rad);
+	        var sin = Math.sin(rad);
+	        
+	        point = mxUtils.getRotatedPoint(point, cos, sin, cx);
+		}
+	}
+	
+	if (point != null)
+	{
+		point.x = Math.round(point.x);
+		point.y = Math.round(point.y);
+	}
+
+	return point;
+};
+
+/**
+ * Function: connectCell
+ * 
+ * Connects the specified end of the given edge to the given terminal
+ * using <cellConnected> and fires <mxEvent.CONNECT_CELL> while the
+ * transaction is in progress. Returns the updated edge.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> whose terminal should be updated.
+ * terminal - <mxCell> that represents the new terminal to be used.
+ * source - Boolean indicating if the new terminal is the source or target.
+ * constraint - Optional <mxConnectionConstraint> to be used for this
+ * connection.
+ */
+mxGraph.prototype.connectCell = function(edge, terminal, source, constraint)
+{
+	this.model.beginUpdate();
+	try
+	{
+		var previous = this.model.getTerminal(edge, source);
+		this.cellConnected(edge, terminal, source, constraint);
+		this.fireEvent(new mxEventObject(mxEvent.CONNECT_CELL,
+			'edge', edge, 'terminal', terminal, 'source', source,
+			'previous', previous));
+	}
+	finally
+	{
+		this.model.endUpdate();
+	}
+
+	return edge;
+};
+
+/**
+ * Function: cellConnected
+ * 
+ * Sets the new terminal for the given edge and resets the edge points if
+ * <resetEdgesOnConnect> is true. This method fires
+ * <mxEvent.CELL_CONNECTED> while the transaction is in progress.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> whose terminal should be updated.
+ * terminal - <mxCell> that represents the new terminal to be used.
+ * source - Boolean indicating if the new terminal is the source or target.
+ * constraint - <mxConnectionConstraint> to be used for this connection.
+ */
+mxGraph.prototype.cellConnected = function(edge, terminal, source, constraint)
+{
+	if (edge != null)
+	{
+		this.model.beginUpdate();
+		try
+		{
+			var previous = this.model.getTerminal(edge, source);
+
+			// Updates the constraint
+			this.setConnectionConstraint(edge, terminal, source, constraint);
+			
+			// Checks if the new terminal is a port, uses the ID of the port in the
+			// style and the parent of the port as the actual terminal of the edge.
+			if (this.isPortsEnabled())
+			{
+				var id = null;
+	
+				if (this.isPort(terminal))
+				{
+					id = terminal.getId();
+					terminal = this.getTerminalForPort(terminal, source);
+				}
+				
+				// Sets or resets all previous information for connecting to a child port
+				var key = (source) ? mxConstants.STYLE_SOURCE_PORT :
+					mxConstants.STYLE_TARGET_PORT;
+				this.setCellStyles(key, id, [edge]);
+			}
+			
+			this.model.setTerminal(edge, terminal, source);
+			
+			if (this.resetEdgesOnConnect)
+			{
+				this.resetEdge(edge);
+			}
+
+			this.fireEvent(new mxEventObject(mxEvent.CELL_CONNECTED,
+				'edge', edge, 'terminal', terminal, 'source', source,
+				'previous', previous));
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: disconnectGraph
+ * 
+ * Disconnects the given edges from the terminals which are not in the
+ * given array.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> to be disconnected.
+ */
+mxGraph.prototype.disconnectGraph = function(cells)
+{
+	if (cells != null)
+	{
+		this.model.beginUpdate();
+		try
+		{							
+			var scale = this.view.scale;
+			var tr = this.view.translate;
+			
+			// Fast lookup for finding cells in array
+			var dict = new mxDictionary();
+			
+			for (var i = 0; i < cells.length; i++)
+			{
+				dict.put(cells[i], true);
+			}
+			
+			for (var i = 0; i < cells.length; i++)
+			{
+				if (this.model.isEdge(cells[i]))
+				{
+					var geo = this.model.getGeometry(cells[i]);
+					
+					if (geo != null)
+					{
+						var state = this.view.getState(cells[i]);
+						var pstate = this.view.getState(
+							this.model.getParent(cells[i]));
+						
+						if (state != null &&
+							pstate != null)
+						{
+							geo = geo.clone();
+							
+							var dx = -pstate.origin.x;
+							var dy = -pstate.origin.y;
+							var pts = state.absolutePoints;
+
+							var src = this.model.getTerminal(cells[i], true);
+							
+							if (src != null && this.isCellDisconnectable(cells[i], src, true))
+							{
+								while (src != null && !dict.get(src))
+								{
+									src = this.model.getParent(src);
+								}
+								
+								if (src == null)
+								{
+									geo.setTerminalPoint(
+										new mxPoint(pts[0].x / scale - tr.x + dx,
+											pts[0].y / scale - tr.y + dy), true);
+									this.model.setTerminal(cells[i], null, true);
+								}
+							}
+							
+							var trg = this.model.getTerminal(cells[i], false);
+							
+							if (trg != null && this.isCellDisconnectable(cells[i], trg, false))
+							{
+								while (trg != null && !dict.get(trg))
+								{
+									trg = this.model.getParent(trg);
+								}
+								
+								if (trg == null)
+								{
+									var n = pts.length - 1;
+									geo.setTerminalPoint(
+										new mxPoint(pts[n].x / scale - tr.x + dx,
+											pts[n].y / scale - tr.y + dy), false);
+									this.model.setTerminal(cells[i], null, false);
+								}
+							}
+
+							this.model.setGeometry(cells[i], geo);
+						}
+					}
+				}
+			}
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Group: Drilldown
+ */
+
+/**
+ * Function: getCurrentRoot
+ * 
+ * Returns the current root of the displayed cell hierarchy. This is a
+ * shortcut to <mxGraphView.currentRoot> in <view>.
+ */
+mxGraph.prototype.getCurrentRoot = function()
+{
+	return this.view.currentRoot;
+};
+ 
+/**
+ * Function: getTranslateForRoot
+ * 
+ * Returns the translation to be used if the given cell is the root cell as
+ * an <mxPoint>. This implementation returns null.
+ * 
+ * Example:
+ * 
+ * To keep the children at their absolute position while stepping into groups,
+ * this function can be overridden as follows.
+ * 
+ * (code)
+ * var offset = new mxPoint(0, 0);
+ * 
+ * while (cell != null)
+ * {
+ *   var geo = this.model.getGeometry(cell);
+ * 
+ *   if (geo != null)
+ *   {
+ *     offset.x -= geo.x;
+ *     offset.y -= geo.y;
+ *   }
+ * 
+ *   cell = this.model.getParent(cell);
+ * }
+ * 
+ * return offset;
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the root.
+ */
+mxGraph.prototype.getTranslateForRoot = function(cell)
+{
+	return null;
+};
+
+/**
+ * Function: isPort
+ * 
+ * Returns true if the given cell is a "port", that is, when connecting to
+ * it, the cell returned by getTerminalForPort should be used as the
+ * terminal and the port should be referenced by the ID in either the
+ * mxConstants.STYLE_SOURCE_PORT or the or the
+ * mxConstants.STYLE_TARGET_PORT. Note that a port should not be movable.
+ * This implementation always returns false.
+ * 
+ * A typical implementation is the following:
+ * 
+ * (code)
+ * graph.isPort = function(cell)
+ * {
+ *   var geo = this.getCellGeometry(cell);
+ *   
+ *   return (geo != null) ? geo.relative : false;
+ * };
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the port.
+ */
+mxGraph.prototype.isPort = function(cell)
+{
+	return false;
+};
+
+/**
+ * Function: getTerminalForPort
+ * 
+ * Returns the terminal to be used for a given port. This implementation
+ * always returns the parent cell.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the port.
+ * source - If the cell is the source or target port.
+ */
+mxGraph.prototype.getTerminalForPort = function(cell, source)
+{
+	return this.model.getParent(cell);
+};
+
+/**
+ * Function: getChildOffsetForCell
+ * 
+ * Returns the offset to be used for the cells inside the given cell. The
+ * root and layer cells may be identified using <mxGraphModel.isRoot> and
+ * <mxGraphModel.isLayer>. For all other current roots, the
+ * <mxGraphView.currentRoot> field points to the respective cell, so that
+ * the following holds: cell == this.view.currentRoot. This implementation
+ * returns null.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose offset should be returned.
+ */
+mxGraph.prototype.getChildOffsetForCell = function(cell)
+{
+	return null;
+};
+
+/**
+ * Function: enterGroup
+ * 
+ * Uses the given cell as the root of the displayed cell hierarchy. If no
+ * cell is specified then the selection cell is used. The cell is only used
+ * if <isValidRoot> returns true.
+ * 
+ * Parameters:
+ * 
+ * cell - Optional <mxCell> to be used as the new root. Default is the
+ * selection cell.
+ */
+mxGraph.prototype.enterGroup = function(cell)
+{
+	cell = cell || this.getSelectionCell();
+	
+	if (cell != null && this.isValidRoot(cell))
+	{
+		this.view.setCurrentRoot(cell);
+		this.clearSelection();
+	}
+};
+
+/**
+ * Function: exitGroup
+ * 
+ * Changes the current root to the next valid root in the displayed cell
+ * hierarchy.
+ */
+mxGraph.prototype.exitGroup = function()
+{
+	var root = this.model.getRoot();
+	var current = this.getCurrentRoot();
+	
+	if (current != null)
+	{
+		var next = this.model.getParent(current);
+		
+		// Finds the next valid root in the hierarchy
+		while (next != root && !this.isValidRoot(next) &&
+				this.model.getParent(next) != root)
+		{
+			next = this.model.getParent(next);
+		}
+		
+		// Clears the current root if the new root is
+		// the model's root or one of the layers.
+		if (next == root || this.model.getParent(next) == root)
+		{
+			this.view.setCurrentRoot(null);
+		}
+		else
+		{
+			this.view.setCurrentRoot(next);
+		}
+		
+		var state = this.view.getState(current);
+		
+		// Selects the previous root in the graph
+		if (state != null)
+		{
+			this.setSelectionCell(current);
+		}
+	}
+};
+
+/**
+ * Function: home
+ * 
+ * Uses the root of the model as the root of the displayed cell hierarchy
+ * and selects the previous root.
+ */
+mxGraph.prototype.home = function()
+{
+	var current = this.getCurrentRoot();
+	
+	if (current != null)
+	{
+		this.view.setCurrentRoot(null);
+		var state = this.view.getState(current);
+		
+		if (state != null)
+		{
+			this.setSelectionCell(current);
+		}
+	}
+};
+
+/**
+ * Function: isValidRoot
+ * 
+ * Returns true if the given cell is a valid root for the cell display
+ * hierarchy. This implementation returns true for all non-null values.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> which should be checked as a possible root.
+ */
+mxGraph.prototype.isValidRoot = function(cell)
+{
+	return (cell != null);
+};
+
+/**
+ * Group: Graph display
+ */
+ 
+/**
+ * Function: getGraphBounds
+ * 
+ * Returns the bounds of the visible graph. Shortcut to
+ * <mxGraphView.getGraphBounds>. See also: <getBoundingBoxFromGeometry>.
+ */
+ mxGraph.prototype.getGraphBounds = function()
+ {
+ 	return this.view.getGraphBounds();
+ };
+
+/**
+ * Function: getCellBounds
+ * 
+ * Returns the scaled, translated bounds for the given cell. See
+ * <mxGraphView.getBounds> for arrays.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose bounds should be returned.
+ * includeEdge - Optional boolean that specifies if the bounds of
+ * the connected edges should be included. Default is false.
+ * includeDescendants - Optional boolean that specifies if the bounds
+ * of all descendants should be included. Default is false.
+ */
+mxGraph.prototype.getCellBounds = function(cell, includeEdges, includeDescendants)
+{
+	var cells = [cell];
+	
+	// Includes all connected edges
+	if (includeEdges)
+	{
+		cells = cells.concat(this.model.getEdges(cell));
+	}
+	
+	var result = this.view.getBounds(cells);
+	
+	// Recursively includes the bounds of the children
+	if (includeDescendants)
+	{
+		var childCount = this.model.getChildCount(cell);
+		
+		for (var i = 0; i < childCount; i++)
+		{
+			var tmp = this.getCellBounds(this.model.getChildAt(cell, i),
+				includeEdges, true);
+
+			if (result != null)
+			{
+				result.add(tmp);
+			}
+			else
+			{
+				result = tmp;
+			}
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: getBoundingBoxFromGeometry
+ * 
+ * Returns the bounding box for the geometries of the vertices in the
+ * given array of cells. This can be used to find the graph bounds during
+ * a layout operation (ie. before the last endUpdate) as follows:
+ * 
+ * (code)
+ * var cells = graph.getChildCells(graph.getDefaultParent(), true, true);
+ * var bounds = graph.getBoundingBoxFromGeometry(cells, true);
+ * (end)
+ * 
+ * This can then be used to move cells to the origin:
+ * 
+ * (code)
+ * if (bounds.x < 0 || bounds.y < 0)
+ * {
+ *   graph.moveCells(cells, -Math.min(bounds.x, 0), -Math.min(bounds.y, 0))
+ * }
+ * (end)
+ * 
+ * Or to translate the graph view:
+ * 
+ * (code)
+ * if (bounds.x < 0 || bounds.y < 0)
+ * {
+ *   graph.view.setTranslate(-Math.min(bounds.x, 0), -Math.min(bounds.y, 0));
+ * }
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> whose bounds should be returned.
+ * includeEdges - Specifies if edge bounds should be included by computing
+ * the bounding box for all points in geometry. Default is false.
+ */
+mxGraph.prototype.getBoundingBoxFromGeometry = function(cells, includeEdges)
+{
+	includeEdges = (includeEdges != null) ? includeEdges : false;
+	var result = null;
+	
+	if (cells != null)
+	{
+		for (var i = 0; i < cells.length; i++)
+		{
+			if (includeEdges || this.model.isVertex(cells[i]))
+			{
+				// Computes the bounding box for the points in the geometry
+				var geo = this.getCellGeometry(cells[i]);
+				
+				if (geo != null)
+				{
+					var bbox = null;
+					
+					if (this.model.isEdge(cells[i]))
+					{
+						var addPoint = function(pt)
+						{
+							if (pt != null)
+							{
+								if (tmp == null)
+								{
+									tmp = new mxRectangle(pt.x, pt.y, 0, 0);
+								}
+								else
+								{
+									tmp.add(new mxRectangle(pt.x, pt.y, 0, 0));
+								}
+							}
+						};
+						
+						if (this.model.getTerminal(cells[i], true) == null)
+						{
+							addPoint(geo.getTerminalPoint(true));
+						}
+						
+						if (this.model.getTerminal(cells[i], false) == null)
+						{
+							addPoint(geo.getTerminalPoint(false));
+						}
+												
+						var pts = geo.points;
+						
+						if (pts != null && pts.length > 0)
+						{
+							var tmp = new mxRectangle(pts[0].x, pts[0].y, 0, 0);
+
+							for (var j = 1; j < pts.length; j++)
+							{
+								addPoint(pts[j]);
+							}
+						}
+						
+						bbox = tmp;
+					}
+					else
+					{
+						var parent = this.model.getParent(cells[i]);
+						
+						if (geo.relative)
+						{
+							if (this.model.isVertex(parent) && parent != this.view.currentRoot)
+							{
+								var tmp = this.getBoundingBoxFromGeometry([parent], false);
+								
+								if (tmp != null)
+								{
+									bbox = new mxRectangle(geo.x * tmp.width, geo.y * tmp.height, geo.width, geo.height);
+									
+									if (mxUtils.indexOf(cells, parent) >= 0)
+									{
+										bbox.x += tmp.x;
+										bbox.y += tmp.y;
+									}
+								}
+							}
+						}
+						else
+						{
+							bbox = mxRectangle.fromRectangle(geo);
+							
+							if (this.model.isVertex(parent) && mxUtils.indexOf(cells, parent) >= 0)
+							{
+								var tmp = this.getBoundingBoxFromGeometry([parent], false);
+
+								if (tmp != null)
+								{
+									bbox.x += tmp.x;
+									bbox.y += tmp.y;
+								}
+							}
+						}
+						
+						if (bbox != null && geo.offset != null)
+						{
+							bbox.x += geo.offset.x;
+							bbox.y += geo.offset.y;
+						}
+					}
+					
+					if (bbox != null)
+					{
+						if (result == null)
+						{
+							result = mxRectangle.fromRectangle(bbox);
+						}
+						else
+						{
+							result.add(bbox);
+						}
+					}
+				}
+			}
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: refresh
+ * 
+ * Clears all cell states or the states for the hierarchy starting at the
+ * given cell and validates the graph. This fires a refresh event as the
+ * last step.
+ * 
+ * Parameters:
+ * 
+ * cell - Optional <mxCell> for which the cell states should be cleared.
+ */
+mxGraph.prototype.refresh = function(cell)
+{
+	this.view.clear(cell, cell == null);
+	this.view.validate();
+	this.sizeDidChange();
+	this.fireEvent(new mxEventObject(mxEvent.REFRESH));
+};
+
+/**
+ * Function: snap
+ * 
+ * Snaps the given numeric value to the grid if <gridEnabled> is true.
+ * 
+ * Parameters:
+ * 
+ * value - Numeric value to be snapped to the grid.
+ */
+mxGraph.prototype.snap = function(value)
+{
+	if (this.gridEnabled)
+	{
+		value = Math.round(value / this.gridSize ) * this.gridSize;
+	}
+	
+	return value;
+};
+
+/**
+ * Function: panGraph
+ * 
+ * Shifts the graph display by the given amount. This is used to preview
+ * panning operations, use <mxGraphView.setTranslate> to set a persistent
+ * translation of the view. Fires <mxEvent.PAN>.
+ * 
+ * Parameters:
+ * 
+ * dx - Amount to shift the graph along the x-axis.
+ * dy - Amount to shift the graph along the y-axis.
+ */
+mxGraph.prototype.panGraph = function(dx, dy)
+{
+	if (this.useScrollbarsForPanning && mxUtils.hasScrollbars(this.container))
+	{
+		this.container.scrollLeft = -dx;
+		this.container.scrollTop = -dy;
+	}
+	else
+	{
+		var canvas = this.view.getCanvas();
+		
+		if (this.dialect == mxConstants.DIALECT_SVG)
+		{
+			// Puts everything inside the container in a DIV so that it
+			// can be moved without changing the state of the container
+			if (dx == 0 && dy == 0)
+			{
+				// Workaround for ignored removeAttribute on SVG element in IE9 standards
+				if (mxClient.IS_IE)
+				{
+					canvas.setAttribute('transform', 'translate(' + dx + ',' + dy + ')');
+				}
+				else
+				{
+					canvas.removeAttribute('transform');
+				}
+				
+				if (this.shiftPreview1 != null)
+				{
+					var child = this.shiftPreview1.firstChild;
+					
+					while (child != null)
+					{
+						var next = child.nextSibling;
+						this.container.appendChild(child);
+						child = next;
+					}
+
+					if (this.shiftPreview1.parentNode != null)
+					{
+						this.shiftPreview1.parentNode.removeChild(this.shiftPreview1);
+					}
+					
+					this.shiftPreview1 = null;
+					
+					this.container.appendChild(canvas.parentNode);
+					
+					child = this.shiftPreview2.firstChild;
+					
+					while (child != null)
+					{
+						var next = child.nextSibling;
+						this.container.appendChild(child);
+						child = next;
+					}
+
+					if (this.shiftPreview2.parentNode != null)
+					{
+						this.shiftPreview2.parentNode.removeChild(this.shiftPreview2);
+					}
+					
+					this.shiftPreview2 = null;
+				}
+			}
+			else
+			{
+				canvas.setAttribute('transform', 'translate(' + dx + ',' + dy + ')');
+				
+				if (this.shiftPreview1 == null)
+				{
+					// Needs two divs for stuff before and after the SVG element
+					this.shiftPreview1 = document.createElement('div');
+					this.shiftPreview1.style.position = 'absolute';
+					this.shiftPreview1.style.overflow = 'visible';
+					
+					this.shiftPreview2 = document.createElement('div');
+					this.shiftPreview2.style.position = 'absolute';
+					this.shiftPreview2.style.overflow = 'visible';
+
+					var current = this.shiftPreview1;
+					var child = this.container.firstChild;
+					
+					while (child != null)
+					{
+						var next = child.nextSibling;
+						
+						// SVG element is moved via transform attribute
+						if (child != canvas.parentNode)
+						{
+							current.appendChild(child);
+						}
+						else
+						{
+							current = this.shiftPreview2;
+						}
+						
+						child = next;
+					}
+					
+					// Inserts elements only if not empty
+					if (this.shiftPreview1.firstChild != null)
+					{
+						this.container.insertBefore(this.shiftPreview1, canvas.parentNode);
+					}
+					
+					if (this.shiftPreview2.firstChild != null)
+					{
+						this.container.appendChild(this.shiftPreview2);
+					}
+				}
+				
+				this.shiftPreview1.style.left = dx + 'px';
+				this.shiftPreview1.style.top = dy + 'px';
+				this.shiftPreview2.style.left = dx + 'px';
+				this.shiftPreview2.style.top = dy + 'px';
+			}
+		}
+		else
+		{
+			canvas.style.left = dx + 'px';
+			canvas.style.top = dy + 'px';
+		}
+		
+		this.panDx = dx;
+		this.panDy = dy;
+
+		this.fireEvent(new mxEventObject(mxEvent.PAN));
+	}
+};
+
+/**
+ * Function: zoomIn
+ * 
+ * Zooms into the graph by <zoomFactor>.
+ */
+mxGraph.prototype.zoomIn = function()
+{
+	this.zoom(this.zoomFactor);
+};
+
+/**
+ * Function: zoomOut
+ * 
+ * Zooms out of the graph by <zoomFactor>.
+ */
+mxGraph.prototype.zoomOut = function()
+{
+	this.zoom(1 / this.zoomFactor);
+};
+
+/**
+ * Function: zoomActual
+ * 
+ * Resets the zoom and panning in the view.
+ */
+mxGraph.prototype.zoomActual = function()
+{
+	if (this.view.scale == 1)
+	{
+		this.view.setTranslate(0, 0);
+	}
+	else
+	{
+		this.view.translate.x = 0;
+		this.view.translate.y = 0;
+
+		this.view.setScale(1);
+	}
+};
+
+/**
+ * Function: zoomTo
+ * 
+ * Zooms the graph to the given scale with an optional boolean center
+ * argument, which is passd to <zoom>.
+ */
+mxGraph.prototype.zoomTo = function(scale, center)
+{
+	this.zoom(scale / this.view.scale, center);
+};
+
+/**
+ * Function: center
+ * 
+ * Centers the graph in the container.
+ * 
+ * Parameters:
+ * 
+ * horizontal - Optional boolean that specifies if the graph should be centered
+ * horizontally. Default is true.
+ * vertical - Optional boolean that specifies if the graph should be centered
+ * vertically. Default is true.
+ * cx - Optional float that specifies the horizontal center. Default is 0.5.
+ * cy - Optional float that specifies the vertical center. Default is 0.5.
+ */
+mxGraph.prototype.center = function(horizontal, vertical, cx, cy)
+{
+	horizontal = (horizontal != null) ? horizontal : true;
+	vertical = (vertical != null) ? vertical : true;
+	cx = (cx != null) ? cx : 0.5;
+	cy = (cy != null) ? cy : 0.5;
+	
+	var hasScrollbars = mxUtils.hasScrollbars(this.container);
+	var cw = this.container.clientWidth;
+	var ch = this.container.clientHeight;
+	var bounds = this.getGraphBounds();
+
+	var t = this.view.translate;
+	var s = this.view.scale;
+
+	var dx = (horizontal) ? cw - bounds.width : 0;
+	var dy = (vertical) ? ch - bounds.height : 0;
+	
+	if (!hasScrollbars)
+	{
+		this.view.setTranslate((horizontal) ? Math.floor(t.x - bounds.x * s + dx * cx / s) : t.x,
+			(vertical) ? Math.floor(t.y - bounds.y * s + dy * cy / s) : t.y);
+	}
+	else
+	{
+		bounds.x -= t.x;
+		bounds.y -= t.y;
+	
+		var sw = this.container.scrollWidth;
+		var sh = this.container.scrollHeight;
+		
+		if (sw > cw)
+		{
+			dx = 0;
+		}
+		
+		if (sh > ch)
+		{
+			dy = 0;
+		}
+
+		this.view.setTranslate(Math.floor(dx / 2 - bounds.x), Math.floor(dy / 2 - bounds.y));
+		this.container.scrollLeft = (sw - cw) / 2;
+		this.container.scrollTop = (sh - ch) / 2;
+	}
+};
+
+/**
+ * Function: zoom
+ * 
+ * Zooms the graph using the given factor. Center is an optional boolean
+ * argument that keeps the graph scrolled to the center. If the center argument
+ * is omitted, then <centerZoom> will be used as its value.
+ */
+mxGraph.prototype.zoom = function(factor, center)
+{
+	center = (center != null) ? center : this.centerZoom;
+	var scale = Math.round(this.view.scale * factor * 100) / 100;
+	var state = this.view.getState(this.getSelectionCell());
+	factor = scale / this.view.scale;
+	
+	if (this.keepSelectionVisibleOnZoom && state != null)
+	{
+		var rect = new mxRectangle(state.x * factor, state.y * factor,
+			state.width * factor, state.height * factor);
+		
+		// Refreshes the display only once if a scroll is carried out
+		this.view.scale = scale;
+		
+		if (!this.scrollRectToVisible(rect))
+		{
+			this.view.revalidate();
+			
+			// Forces an event to be fired but does not revalidate again
+			this.view.setScale(scale);
+		}
+	}
+	else
+	{
+		var hasScrollbars = mxUtils.hasScrollbars(this.container);
+		
+		if (center && !hasScrollbars)
+		{
+			var dx = this.container.offsetWidth;
+			var dy = this.container.offsetHeight;
+			
+			if (factor > 1)
+			{
+				var f = (factor - 1) / (scale * 2);
+				dx *= -f;
+				dy *= -f;
+			}
+			else
+			{
+				var f = (1 / factor - 1) / (this.view.scale * 2);
+				dx *= f;
+				dy *= f;
+			}
+
+			this.view.scaleAndTranslate(scale,
+				this.view.translate.x + dx,
+				this.view.translate.y + dy);
+		}
+		else
+		{
+			// Allows for changes of translate and scrollbars during setscale
+			var tx = this.view.translate.x;
+			var ty = this.view.translate.y;
+			var sl = this.container.scrollLeft;
+			var st = this.container.scrollTop;
+			
+			this.view.setScale(scale);
+			
+			if (hasScrollbars)
+			{
+				var dx = 0;
+				var dy = 0;
+				
+				if (center)
+				{
+					dx = this.container.offsetWidth * (factor - 1) / 2;
+					dy = this.container.offsetHeight * (factor - 1) / 2;
+				}
+				
+				this.container.scrollLeft = (this.view.translate.x - tx) * this.view.scale + Math.round(sl * factor + dx);
+				this.container.scrollTop = (this.view.translate.y - ty) * this.view.scale + Math.round(st * factor + dy);
+			}
+		}
+	}
+};
+
+/**
+ * Function: zoomToRect
+ * 
+ * Zooms the graph to the specified rectangle. If the rectangle does not have same aspect
+ * ratio as the display container, it is increased in the smaller relative dimension only
+ * until the aspect match. The original rectangle is centralised within this expanded one.
+ * 
+ * Note that the input rectangular must be un-scaled and un-translated.
+ * 
+ * Parameters:
+ * 
+ * rect - The un-scaled and un-translated rectangluar region that should be just visible 
+ * after the operation
+ */
+mxGraph.prototype.zoomToRect = function(rect)
+{
+	var scaleX = this.container.clientWidth / rect.width;
+	var scaleY = this.container.clientHeight / rect.height;
+	var aspectFactor = scaleX / scaleY;
+
+	// Remove any overlap of the rect outside the client area
+	rect.x = Math.max(0, rect.x);
+	rect.y = Math.max(0, rect.y);
+	var rectRight = Math.min(this.container.scrollWidth, rect.x + rect.width);
+	var rectBottom = Math.min(this.container.scrollHeight, rect.y + rect.height);
+	rect.width = rectRight - rect.x;
+	rect.height = rectBottom - rect.y;
+
+	// The selection area has to be increased to the same aspect
+	// ratio as the container, centred around the centre point of the 
+	// original rect passed in.
+	if (aspectFactor < 1.0)
+	{
+		// Height needs increasing
+		var newHeight = rect.height / aspectFactor;
+		var deltaHeightBuffer = (newHeight - rect.height) / 2.0;
+		rect.height = newHeight;
+		
+		// Assign up to half the buffer to the upper part of the rect, not crossing 0
+		// put the rest on the bottom
+		var upperBuffer = Math.min(rect.y , deltaHeightBuffer);
+		rect.y = rect.y - upperBuffer;
+		
+		// Check if the bottom has extended too far
+		rectBottom = Math.min(this.container.scrollHeight, rect.y + rect.height);
+		rect.height = rectBottom - rect.y;
+	}
+	else
+	{
+		// Width needs increasing
+		var newWidth = rect.width * aspectFactor;
+		var deltaWidthBuffer = (newWidth - rect.width) / 2.0;
+		rect.width = newWidth;
+		
+		// Assign up to half the buffer to the upper part of the rect, not crossing 0
+		// put the rest on the bottom
+		var leftBuffer = Math.min(rect.x , deltaWidthBuffer);
+		rect.x = rect.x - leftBuffer;
+		
+		// Check if the right hand side has extended too far
+		rectRight = Math.min(this.container.scrollWidth, rect.x + rect.width);
+		rect.width = rectRight - rect.x;
+	}
+
+	var scale = this.container.clientWidth / rect.width;
+	var newScale = this.view.scale * scale;
+
+	if (!mxUtils.hasScrollbars(this.container))
+	{
+		this.view.scaleAndTranslate(newScale, (this.view.translate.x - rect.x / this.view.scale), (this.view.translate.y - rect.y / this.view.scale));
+	}
+	else
+	{
+		this.view.setScale(newScale);
+		this.container.scrollLeft = Math.round(rect.x * scale);
+		this.container.scrollTop = Math.round(rect.y * scale);
+	}
+};
+
+/**
+ * Function: scrollCellToVisible
+ * 
+ * Pans the graph so that it shows the given cell. Optionally the cell may
+ * be centered in the container.
+ * 
+ * To center a given graph if the <container> has no scrollbars, use the following code.
+ * 
+ * [code]
+ * var bounds = graph.getGraphBounds();
+ * graph.view.setTranslate(-bounds.x - (bounds.width - container.clientWidth) / 2,
+ * 						   -bounds.y - (bounds.height - container.clientHeight) / 2);
+ * [/code]
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to be made visible.
+ * center - Optional boolean flag. Default is false.
+ */
+mxGraph.prototype.scrollCellToVisible = function(cell, center)
+{
+	var x = -this.view.translate.x;
+	var y = -this.view.translate.y;
+
+	var state = this.view.getState(cell);
+
+	if (state != null)
+	{
+		var bounds = new mxRectangle(x + state.x, y + state.y, state.width,
+			state.height);
+
+		if (center && this.container != null)
+		{
+			var w = this.container.clientWidth;
+			var h = this.container.clientHeight;
+
+			bounds.x = bounds.getCenterX() - w / 2;
+			bounds.width = w;
+			bounds.y = bounds.getCenterY() - h / 2;
+			bounds.height = h;
+		}
+		
+		var tr = new mxPoint(this.view.translate.x, this.view.translate.y);
+
+		if (this.scrollRectToVisible(bounds))
+		{
+			// Triggers an update via the view's event source
+			var tr2 = new mxPoint(this.view.translate.x, this.view.translate.y);
+			this.view.translate.x = tr.x;
+			this.view.translate.y = tr.y;
+			this.view.setTranslate(tr2.x, tr2.y);
+		}
+	}
+};
+
+/**
+ * Function: scrollRectToVisible
+ * 
+ * Pans the graph so that it shows the given rectangle.
+ * 
+ * Parameters:
+ * 
+ * rect - <mxRectangle> to be made visible.
+ */
+mxGraph.prototype.scrollRectToVisible = function(rect)
+{
+	var isChanged = false;
+	
+	if (rect != null)
+	{
+		var w = this.container.offsetWidth;
+		var h = this.container.offsetHeight;
+
+        var widthLimit = Math.min(w, rect.width);
+        var heightLimit = Math.min(h, rect.height);
+
+		if (mxUtils.hasScrollbars(this.container))
+		{
+			var c = this.container;
+			rect.x += this.view.translate.x;
+			rect.y += this.view.translate.y;
+			var dx = c.scrollLeft - rect.x;
+			var ddx = Math.max(dx - c.scrollLeft, 0);
+
+			if (dx > 0)
+			{
+				c.scrollLeft -= dx + 2;
+			}
+			else
+			{
+				dx = rect.x + widthLimit - c.scrollLeft - c.clientWidth;
+
+				if (dx > 0)
+				{
+					c.scrollLeft += dx + 2;
+				}
+			}
+
+			var dy = c.scrollTop - rect.y;
+			var ddy = Math.max(0, dy - c.scrollTop);
+
+			if (dy > 0)
+			{
+				c.scrollTop -= dy + 2;
+			}
+			else
+			{
+				dy = rect.y + heightLimit - c.scrollTop - c.clientHeight;
+
+				if (dy > 0)
+				{
+					c.scrollTop += dy + 2;
+				}
+			}
+
+			if (!this.useScrollbarsForPanning && (ddx != 0 || ddy != 0))
+			{
+				this.view.setTranslate(ddx, ddy);
+			}
+		}
+		else
+		{
+			var x = -this.view.translate.x;
+			var y = -this.view.translate.y;
+
+			var s = this.view.scale;
+
+			if (rect.x + widthLimit > x + w)
+			{
+				this.view.translate.x -= (rect.x + widthLimit - w - x) / s;
+				isChanged = true;
+			}
+
+			if (rect.y + heightLimit > y + h)
+			{
+				this.view.translate.y -= (rect.y + heightLimit - h - y) / s;
+				isChanged = true;
+			}
+
+			if (rect.x < x)
+			{
+				this.view.translate.x += (x - rect.x) / s;
+				isChanged = true;
+			}
+
+			if (rect.y  < y)
+			{
+				this.view.translate.y += (y - rect.y) / s;
+				isChanged = true;
+			}
+
+			if (isChanged)
+			{
+				this.view.refresh();
+				
+				// Repaints selection marker (ticket 18)
+				if (this.selectionCellsHandler != null)
+				{
+					this.selectionCellsHandler.refresh();
+				}
+			}
+		}
+	}
+
+	return isChanged;
+};
+
+/**
+ * Function: getCellGeometry
+ * 
+ * Returns the <mxGeometry> for the given cell. This implementation uses
+ * <mxGraphModel.getGeometry>. Subclasses can override this to implement
+ * specific geometries for cells in only one graph, that is, it can return
+ * geometries that depend on the current state of the view.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose geometry should be returned.
+ */
+mxGraph.prototype.getCellGeometry = function(cell)
+{
+	return this.model.getGeometry(cell);
+};
+
+/**
+ * Function: isCellVisible
+ * 
+ * Returns true if the given cell is visible in this graph. This
+ * implementation uses <mxGraphModel.isVisible>. Subclassers can override
+ * this to implement specific visibility for cells in only one graph, that
+ * is, without affecting the visible state of the cell.
+ * 
+ * When using dynamic filter expressions for cell visibility, then the
+ * graph should be revalidated after the filter expression has changed.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose visible state should be returned.
+ */
+mxGraph.prototype.isCellVisible = function(cell)
+{
+	return this.model.isVisible(cell);
+};
+
+/**
+ * Function: isCellCollapsed
+ * 
+ * Returns true if the given cell is collapsed in this graph. This
+ * implementation uses <mxGraphModel.isCollapsed>. Subclassers can override
+ * this to implement specific collapsed states for cells in only one graph,
+ * that is, without affecting the collapsed state of the cell.
+ * 
+ * When using dynamic filter expressions for the collapsed state, then the
+ * graph should be revalidated after the filter expression has changed.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose collapsed state should be returned.
+ */
+mxGraph.prototype.isCellCollapsed = function(cell)
+{
+	return this.model.isCollapsed(cell);
+};
+
+/**
+ * Function: isCellConnectable
+ * 
+ * Returns true if the given cell is connectable in this graph. This
+ * implementation uses <mxGraphModel.isConnectable>. Subclassers can override
+ * this to implement specific connectable states for cells in only one graph,
+ * that is, without affecting the connectable state of the cell in the model.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose connectable state should be returned.
+ */
+mxGraph.prototype.isCellConnectable = function(cell)
+{
+	return this.model.isConnectable(cell);
+};
+
+/**
+ * Function: isOrthogonal
+ * 
+ * Returns true if perimeter points should be computed such that the
+ * resulting edge has only horizontal or vertical segments.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCellState> that represents the edge.
+ */
+mxGraph.prototype.isOrthogonal = function(edge)
+{
+	var orthogonal = edge.style[mxConstants.STYLE_ORTHOGONAL];
+	
+	if (orthogonal != null)
+	{
+		return orthogonal;
+	}
+	
+	var tmp = this.view.getEdgeStyle(edge);
+	
+	return tmp == mxEdgeStyle.SegmentConnector ||
+		tmp == mxEdgeStyle.ElbowConnector ||
+		tmp == mxEdgeStyle.SideToSide ||
+		tmp == mxEdgeStyle.TopToBottom ||
+		tmp == mxEdgeStyle.EntityRelation ||
+		tmp == mxEdgeStyle.OrthConnector;
+};
+
+/**
+ * Function: isLoop
+ * 
+ * Returns true if the given cell state is a loop.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> that represents a potential loop.
+ */
+mxGraph.prototype.isLoop = function(state)
+{
+	var src = state.getVisibleTerminalState(true);
+	var trg = state.getVisibleTerminalState(false);
+	
+	return (src != null && src == trg);
+};
+
+/**
+ * Function: isCloneEvent
+ * 
+ * Returns true if the given event is a clone event. This implementation
+ * returns true if control is pressed.
+ */
+mxGraph.prototype.isCloneEvent = function(evt)
+{
+	return mxEvent.isControlDown(evt);
+};
+
+/**
+ * Function: isTransparentClickEvent
+ * 
+ * Hook for implementing click-through behaviour on selected cells. If this
+ * returns true the cell behind the selected cell will be selected. This
+ * implementation returns false;
+ */
+mxGraph.prototype.isTransparentClickEvent = function(evt)
+{
+	return false;
+};
+
+/**
+ * Function: isToggleEvent
+ * 
+ * Returns true if the given event is a toggle event. This implementation
+ * returns true if the meta key (Cmd) is pressed on Macs or if control is
+ * pressed on any other platform.
+ */
+mxGraph.prototype.isToggleEvent = function(evt)
+{
+	return (mxClient.IS_MAC) ? mxEvent.isMetaDown(evt) : mxEvent.isControlDown(evt);
+};
+
+/**
+ * Function: isGridEnabledEvent
+ * 
+ * Returns true if the given mouse event should be aligned to the grid.
+ */
+mxGraph.prototype.isGridEnabledEvent = function(evt)
+{
+	return evt != null && !mxEvent.isAltDown(evt);
+};
+
+/**
+ * Function: isConstrainedEvent
+ * 
+ * Returns true if the given mouse event should be aligned to the grid.
+ */
+mxGraph.prototype.isConstrainedEvent = function(evt)
+{
+	return mxEvent.isShiftDown(evt);
+};
+
+/**
+ * Function: isIgnoreTerminalEvent
+ * 
+ * Returns true if the given mouse event should not allow any connections to be
+ * made. This implementation returns false.
+ */
+mxGraph.prototype.isIgnoreTerminalEvent = function(evt)
+{
+	return false;
+};
+
+/**
+ * Group: Validation
+ */
+
+/**
+ * Function: validationAlert
+ * 
+ * Displays the given validation error in a dialog. This implementation uses
+ * mxUtils.alert.
+ */
+mxGraph.prototype.validationAlert = function(message)
+{
+	mxUtils.alert(message);
+};
+
+/**
+ * Function: isEdgeValid
+ * 
+ * Checks if the return value of <getEdgeValidationError> for the given
+ * arguments is null.
+ *  
+ * Parameters:
+ * 
+ * edge - <mxCell> that represents the edge to validate.
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ */
+mxGraph.prototype.isEdgeValid = function(edge, source, target)
+{
+	return this.getEdgeValidationError(edge, source, target) == null;
+};
+
+/**
+ * Function: getEdgeValidationError
+ * 
+ * Returns the validation error message to be displayed when inserting or
+ * changing an edges' connectivity. A return value of null means the edge
+ * is valid, a return value of '' means it's not valid, but do not display
+ * an error message. Any other (non-empty) string returned from this method
+ * is displayed as an error message when trying to connect an edge to a
+ * source and target. This implementation uses the <multiplicities>, and
+ * checks <multigraph>, <allowDanglingEdges> and <allowLoops> to generate
+ * validation errors.
+ * 
+ * For extending this method with specific checks for source/target cells,
+ * the method can be extended as follows. Returning an empty string means
+ * the edge is invalid with no error message, a non-null string specifies
+ * the error message, and null means the edge is valid.
+ * 
+ * (code)
+ * graph.getEdgeValidationError = function(edge, source, target)
+ * {
+ *   if (source != null && target != null &&
+ *     this.model.getValue(source) != null &&
+ *     this.model.getValue(target) != null)
+ *   {
+ *     if (target is not valid for source)
+ *     {
+ *       return 'Invalid Target';
+ *     }
+ *   }
+ *   
+ *   // "Supercall"
+ *   return mxGraph.prototype.getEdgeValidationError.apply(this, arguments);
+ * }
+ * (end)
+ *  
+ * Parameters:
+ * 
+ * edge - <mxCell> that represents the edge to validate.
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ */
+mxGraph.prototype.getEdgeValidationError = function(edge, source, target)
+{
+	if (edge != null && !this.isAllowDanglingEdges() && (source == null || target == null))
+	{
+		return '';
+	}
+	
+	if (edge != null && this.model.getTerminal(edge, true) == null &&
+		this.model.getTerminal(edge, false) == null)	
+	{
+		return null;
+	}
+	
+	// Checks if we're dealing with a loop
+	if (!this.allowLoops && source == target && source != null)
+	{
+		return '';
+	}
+	
+	// Checks if the connection is generally allowed
+	if (!this.isValidConnection(source, target))
+	{
+		return '';
+	}
+
+	if (source != null && target != null)
+	{
+		var error = '';
+
+		// Checks if the cells are already connected
+		// and adds an error message if required			
+		if (!this.multigraph)
+		{
+			var tmp = this.model.getEdgesBetween(source, target, true);
+			
+			// Checks if the source and target are not connected by another edge
+			if (tmp.length > 1 || (tmp.length == 1 && tmp[0] != edge))
+			{
+				error += (mxResources.get(this.alreadyConnectedResource) ||
+					this.alreadyConnectedResource)+'\n';
+			}
+		}
+
+		// Gets the number of outgoing edges from the source
+		// and the number of incoming edges from the target
+		// without counting the edge being currently changed.
+		var sourceOut = this.model.getDirectedEdgeCount(source, true, edge);
+		var targetIn = this.model.getDirectedEdgeCount(target, false, edge);
+
+		// Checks the change against each multiplicity rule
+		if (this.multiplicities != null)
+		{
+			for (var i = 0; i < this.multiplicities.length; i++)
+			{
+				var err = this.multiplicities[i].check(this, edge, source,
+					target, sourceOut, targetIn);
+				
+				if (err != null)
+				{
+					error += err;
+				}
+			}
+		}
+
+		// Validates the source and target terminals independently
+		var err = this.validateEdge(edge, source, target);
+		
+		if (err != null)
+		{
+			error += err;
+		}
+		
+		return (error.length > 0) ? error : null;
+	}
+	
+	return (this.allowDanglingEdges) ? null : '';
+};
+
+/**
+ * Function: validateEdge
+ * 
+ * Hook method for subclassers to return an error message for the given
+ * edge and terminals. This implementation returns null.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> that represents the edge to validate.
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ */
+mxGraph.prototype.validateEdge = function(edge, source, target)
+{
+	return null;
+};
+
+/**
+ * Function: validateGraph
+ * 
+ * Validates the graph by validating each descendant of the given cell or
+ * the root of the model. Context is an object that contains the validation
+ * state for the complete validation run. The validation errors are
+ * attached to their cells using <setCellWarning>. Returns null in the case of
+ * successful validation or an array of strings (warnings) in the case of
+ * failed validations.
+ * 
+ * Paramters:
+ * 
+ * cell - Optional <mxCell> to start the validation recursion. Default is
+ * the graph root.
+ * context - Object that represents the global validation state.
+ */
+mxGraph.prototype.validateGraph = function(cell, context)
+{
+	cell = (cell != null) ? cell : this.model.getRoot();
+	context = (context != null) ? context : new Object();
+	
+	var isValid = true;
+	var childCount = this.model.getChildCount(cell);
+	
+	for (var i = 0; i < childCount; i++)
+	{
+		var tmp = this.model.getChildAt(cell, i);
+		var ctx = context;
+		
+		if (this.isValidRoot(tmp))
+		{
+			ctx = new Object();
+		}
+		
+		var warn = this.validateGraph(tmp, ctx);
+		
+		if (warn != null)
+		{
+			this.setCellWarning(tmp, warn.replace(/\n/g, '<br>'));
+		}
+		else
+		{
+			this.setCellWarning(tmp, null);
+		}
+		
+		isValid = isValid && warn == null;
+	}
+	
+	var warning = '';
+	
+	// Adds error for invalid children if collapsed (children invisible)
+	if (this.isCellCollapsed(cell) && !isValid)
+	{
+		warning += (mxResources.get(this.containsValidationErrorsResource) ||
+			this.containsValidationErrorsResource) + '\n';
+	}
+	
+	// Checks edges and cells using the defined multiplicities
+	if (this.model.isEdge(cell))
+	{
+		warning += this.getEdgeValidationError(cell,
+		this.model.getTerminal(cell, true),
+		this.model.getTerminal(cell, false)) || '';
+	}
+	else
+	{
+		warning += this.getCellValidationError(cell) || '';
+	}
+	
+	// Checks custom validation rules
+	var err = this.validateCell(cell, context);
+	
+	if (err != null)
+	{
+		warning += err;
+	}
+	
+	// Updates the display with the warning icons
+	// before any potential alerts are displayed.
+	// LATER: Move this into addCellOverlay. Redraw
+	// should check if overlay was added or removed.
+	if (this.model.getParent(cell) == null)
+	{
+		this.view.validate();
+	}
+
+	return (warning.length > 0 || !isValid) ? warning : null;
+};
+
+/**
+ * Function: getCellValidationError
+ * 
+ * Checks all <multiplicities> that cannot be enforced while the graph is
+ * being modified, namely, all multiplicities that require a minimum of
+ * 1 edge.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> for which the multiplicities should be checked.
+ */
+mxGraph.prototype.getCellValidationError = function(cell)
+{
+	var outCount = this.model.getDirectedEdgeCount(cell, true);
+	var inCount = this.model.getDirectedEdgeCount(cell, false);
+	var value = this.model.getValue(cell);
+	var error = '';
+
+	if (this.multiplicities != null)
+	{
+		for (var i = 0; i < this.multiplicities.length; i++)
+		{
+			var rule = this.multiplicities[i];
+			
+			if (rule.source && mxUtils.isNode(value, rule.type,
+				rule.attr, rule.value) && (outCount > rule.max ||
+				outCount < rule.min))
+			{
+				error += rule.countError + '\n';
+			}
+			else if (!rule.source && mxUtils.isNode(value, rule.type,
+					rule.attr, rule.value) && (inCount > rule.max ||
+					inCount < rule.min))
+			{
+				error += rule.countError + '\n';
+			}
+		}
+	}
+
+	return (error.length > 0) ? error : null;
+};
+
+/**
+ * Function: validateCell
+ * 
+ * Hook method for subclassers to return an error message for the given
+ * cell and validation context. This implementation returns null. Any HTML
+ * breaks will be converted to linefeeds in the calling method.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the cell to validate.
+ * context - Object that represents the global validation state.
+ */
+mxGraph.prototype.validateCell = function(cell, context)
+{
+	return null;
+};
+
+/**
+ * Group: Graph appearance
+ */
+
+/**
+ * Function: getBackgroundImage
+ * 
+ * Returns the <backgroundImage> as an <mxImage>.
+ */
+mxGraph.prototype.getBackgroundImage = function()
+{
+	return this.backgroundImage;
+};
+
+/**
+ * Function: setBackgroundImage
+ * 
+ * Sets the new <backgroundImage>.
+ * 
+ * Parameters:
+ * 
+ * image - New <mxImage> to be used for the background.
+ */
+mxGraph.prototype.setBackgroundImage = function(image)
+{
+	this.backgroundImage = image;
+};
+
+/**
+ * Function: getFoldingImage
+ * 
+ * Returns the <mxImage> used to display the collapsed state of
+ * the specified cell state. This returns null for all edges.
+ */
+mxGraph.prototype.getFoldingImage = function(state)
+{
+	if (state != null && this.foldingEnabled && !this.getModel().isEdge(state.cell))
+	{
+		var tmp = this.isCellCollapsed(state.cell);
+		
+		if (this.isCellFoldable(state.cell, !tmp))
+		{
+			return (tmp) ? this.collapsedImage : this.expandedImage;
+		}
+	}
+	
+	return null;
+};
+
+/**
+ * Function: convertValueToString
+ * 
+ * Returns the textual representation for the given cell. This
+ * implementation returns the nodename or string-representation of the user
+ * object.
+ *
+ * Example:
+ * 
+ * The following returns the label attribute from the cells user
+ * object if it is an XML node.
+ * 
+ * (code)
+ * graph.convertValueToString = function(cell)
+ * {
+ * 	return cell.getAttribute('label');
+ * }
+ * (end)
+ * 
+ * See also: <cellLabelChanged>.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose textual representation should be returned.
+ */
+mxGraph.prototype.convertValueToString = function(cell)
+{
+	var value = this.model.getValue(cell);
+	
+	if (value != null)
+	{
+		if (mxUtils.isNode(value))
+		{
+			return value.nodeName;
+		}
+		else if (typeof(value.toString) == 'function')
+		{
+			return value.toString();
+		}
+	}
+	
+	return '';
+};
+
+/**
+ * Function: getLabel
+ * 
+ * Returns a string or DOM node that represents the label for the given
+ * cell. This implementation uses <convertValueToString> if <labelsVisible>
+ * is true. Otherwise it returns an empty string.
+ * 
+ * To truncate a label to match the size of the cell, the following code
+ * can be used.
+ * 
+ * (code)
+ * graph.getLabel = function(cell)
+ * {
+ *   var label = mxGraph.prototype.getLabel.apply(this, arguments);
+ * 
+ *   if (label != null && this.model.isVertex(cell))
+ *   {
+ *     var geo = this.getCellGeometry(cell);
+ * 
+ *     if (geo != null)
+ *     {
+ *       var max = parseInt(geo.width / 8);
+ * 
+ *       if (label.length > max)
+ *       {
+ *         label = label.substring(0, max)+'...';
+ *       }
+ *     }
+ *   } 
+ *   return mxUtils.htmlEntities(label);
+ * }
+ * (end)
+ * 
+ * A resize listener is needed in the graph to force a repaint of the label
+ * after a resize.
+ * 
+ * (code)
+ * graph.addListener(mxEvent.RESIZE_CELLS, function(sender, evt)
+ * {
+ *   var cells = evt.getProperty('cells');
+ * 
+ *   for (var i = 0; i < cells.length; i++)
+ *   {
+ *     this.view.removeState(cells[i]);
+ *   }
+ * });
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose label should be returned.
+ */
+mxGraph.prototype.getLabel = function(cell)
+{
+	var result = '';
+	
+	if (this.labelsVisible && cell != null)
+	{
+		var state = this.view.getState(cell);
+		var style = (state != null) ? state.style : this.getCellStyle(cell);
+		
+		if (!mxUtils.getValue(style, mxConstants.STYLE_NOLABEL, false))
+		{
+			result = this.convertValueToString(cell);
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: isHtmlLabel
+ * 
+ * Returns true if the label must be rendered as HTML markup. The default
+ * implementation returns <htmlLabels>.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose label should be displayed as HTML markup.
+ */
+mxGraph.prototype.isHtmlLabel = function(cell)
+{
+	return this.isHtmlLabels();
+};
+ 
+/**
+ * Function: isHtmlLabels
+ * 
+ * Returns <htmlLabels>.
+ */
+mxGraph.prototype.isHtmlLabels = function()
+{
+	return this.htmlLabels;
+};
+ 
+/**
+ * Function: setHtmlLabels
+ * 
+ * Sets <htmlLabels>.
+ */
+mxGraph.prototype.setHtmlLabels = function(value)
+{
+	this.htmlLabels = value;
+};
+
+/**
+ * Function: isWrapping
+ * 
+ * This enables wrapping for HTML labels.
+ * 
+ * Returns true if no white-space CSS style directive should be used for
+ * displaying the given cells label. This implementation returns true if
+ * <mxConstants.STYLE_WHITE_SPACE> in the style of the given cell is 'wrap'.
+ * 
+ * This is used as a workaround for IE ignoring the white-space directive
+ * of child elements if the directive appears in a parent element. It
+ * should be overridden to return true if a white-space directive is used
+ * in the HTML markup that represents the given cells label. In order for
+ * HTML markup to work in labels, <isHtmlLabel> must also return true
+ * for the given cell.
+ * 
+ * Example:
+ * 
+ * (code)
+ * graph.getLabel = function(cell)
+ * {
+ *   var tmp = mxGraph.prototype.getLabel.apply(this, arguments); // "supercall"
+ *   
+ *   if (this.model.isEdge(cell))
+ *   {
+ *     tmp = '<div style="width: 150px; white-space:normal;">'+tmp+'</div>';
+ *   }
+ *   
+ *   return tmp;
+ * }
+ * 
+ * graph.isWrapping = function(state)
+ * {
+ * 	 return this.model.isEdge(state.cell);
+ * }
+ * (end)
+ * 
+ * Makes sure no edge label is wider than 150 pixels, otherwise the content
+ * is wrapped. Note: No width must be specified for wrapped vertex labels as
+ * the vertex defines the width in its geometry.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCell> whose label should be wrapped.
+ */
+mxGraph.prototype.isWrapping = function(cell)
+{
+	var state = this.view.getState(cell);
+	var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+	return (style != null) ? style[mxConstants.STYLE_WHITE_SPACE] == 'wrap' : false;
+};
+
+/**
+ * Function: isLabelClipped
+ * 
+ * Returns true if the overflow portion of labels should be hidden. If this
+ * returns true then vertex labels will be clipped to the size of the vertices.
+ * This implementation returns true if <mxConstants.STYLE_OVERFLOW> in the
+ * style of the given cell is 'hidden'.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCell> whose label should be clipped.
+ */
+mxGraph.prototype.isLabelClipped = function(cell)
+{
+	var state = this.view.getState(cell);
+	var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+	return (style != null) ? style[mxConstants.STYLE_OVERFLOW] == 'hidden' : false;
+};
+
+/**
+ * Function: getTooltip
+ * 
+ * Returns the string or DOM node that represents the tooltip for the given
+ * state, node and coordinate pair. This implementation checks if the given
+ * node is a folding icon or overlay and returns the respective tooltip. If
+ * this does not result in a tooltip, the handler for the cell is retrieved
+ * from <selectionCellsHandler> and the optional getTooltipForNode method is
+ * called. If no special tooltip exists here then <getTooltipForCell> is used
+ * with the cell in the given state as the argument to return a tooltip for the
+ * given state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose tooltip should be returned.
+ * node - DOM node that is currently under the mouse.
+ * x - X-coordinate of the mouse.
+ * y - Y-coordinate of the mouse.
+ */
+mxGraph.prototype.getTooltip = function(state, node, x, y)
+{
+	var tip = null;
+	
+	if (state != null)
+	{
+		// Checks if the mouse is over the folding icon
+		if (state.control != null && (node == state.control.node ||
+			node.parentNode == state.control.node))
+		{
+			tip = this.collapseExpandResource;
+			tip = mxUtils.htmlEntities(mxResources.get(tip) || tip).replace(/\\n/g, '<br>');
+		}
+
+		if (tip == null && state.overlays != null)
+		{
+			state.overlays.visit(function(id, shape)
+			{
+				// LATER: Exit loop if tip is not null
+				if (tip == null && (node == shape.node || node.parentNode == shape.node))
+				{
+					tip = shape.overlay.toString();
+				}
+			});
+		}
+		
+		if (tip == null)
+		{
+			var handler = this.selectionCellsHandler.getHandler(state.cell);
+			
+			if (handler != null && typeof(handler.getTooltipForNode) == 'function')
+			{
+				tip = handler.getTooltipForNode(node);
+			}
+		}
+		
+		if (tip == null)
+		{
+			tip = this.getTooltipForCell(state.cell);
+		}
+	}
+	
+	return tip;
+};
+
+/**
+ * Function: getTooltipForCell
+ * 
+ * Returns the string or DOM node to be used as the tooltip for the given
+ * cell. This implementation uses the cells getTooltip function if it
+ * exists, or else it returns <convertValueToString> for the cell.
+ * 
+ * Example:
+ * 
+ * (code)
+ * graph.getTooltipForCell = function(cell)
+ * {
+ *   return 'Hello, World!';
+ * }
+ * (end)
+ * 
+ * Replaces all tooltips with the string Hello, World!
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose tooltip should be returned.
+ */
+mxGraph.prototype.getTooltipForCell = function(cell)
+{
+	var tip = null;
+	
+	if (cell != null && cell.getTooltip != null)
+	{
+		tip = cell.getTooltip();
+	}
+	else
+	{
+		tip = this.convertValueToString(cell);
+	}
+	
+	return tip;
+};
+
+/**
+ * Function: getCursorForMouseEvent
+ * 
+ * Returns the cursor value to be used for the CSS of the shape for the
+ * given event. This implementation calls <getCursorForCell>.
+ * 
+ * Parameters:
+ * 
+ * me - <mxMouseEvent> whose cursor should be returned.
+ */
+mxGraph.prototype.getCursorForMouseEvent = function(me)
+{
+	return this.getCursorForCell(me.getCell());
+};
+
+/**
+ * Function: getCursorForCell
+ * 
+ * Returns the cursor value to be used for the CSS of the shape for the
+ * given cell. This implementation returns null.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose cursor should be returned.
+ */
+mxGraph.prototype.getCursorForCell = function(cell)
+{
+	return null;
+};
+
+/**
+ * Function: getStartSize
+ * 
+ * Returns the start size of the given swimlane, that is, the width or
+ * height of the part that contains the title, depending on the
+ * horizontal style. The return value is an <mxRectangle> with either
+ * width or height set as appropriate.
+ * 
+ * Parameters:
+ * 
+ * swimlane - <mxCell> whose start size should be returned.
+ */
+mxGraph.prototype.getStartSize = function(swimlane)
+{
+	var result = new mxRectangle();
+	var state = this.view.getState(swimlane);
+	var style = (state != null) ? state.style : this.getCellStyle(swimlane);
+	
+	if (style != null)
+	{
+		var size = parseInt(mxUtils.getValue(style,
+			mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE));
+		
+		if (mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, true))
+		{
+			result.height = size;
+		}
+		else
+		{
+			result.width = size;
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: getImage
+ * 
+ * Returns the image URL for the given cell state. This implementation
+ * returns the value stored under <mxConstants.STYLE_IMAGE> in the cell
+ * style.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose image URL should be returned.
+ */
+mxGraph.prototype.getImage = function(state)
+{
+	return (state != null && state.style != null) ? state.style[mxConstants.STYLE_IMAGE] : null;
+};
+
+/**
+ * Function: getVerticalAlign
+ * 
+ * Returns the vertical alignment for the given cell state. This
+ * implementation returns the value stored under
+ * <mxConstants.STYLE_VERTICAL_ALIGN> in the cell style.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose vertical alignment should be
+ * returned.
+ */
+mxGraph.prototype.getVerticalAlign = function(state)
+{
+	return (state != null && state.style != null) ?
+		(state.style[mxConstants.STYLE_VERTICAL_ALIGN] ||
+		mxConstants.ALIGN_MIDDLE) : null;
+};
+
+/**
+ * Function: getIndicatorColor
+ * 
+ * Returns the indicator color for the given cell state. This
+ * implementation returns the value stored under
+ * <mxConstants.STYLE_INDICATOR_COLOR> in the cell style.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose indicator color should be
+ * returned.
+ */
+mxGraph.prototype.getIndicatorColor = function(state)
+{
+	return (state != null && state.style != null) ? state.style[mxConstants.STYLE_INDICATOR_COLOR] : null;
+};
+
+/**
+ * Function: getIndicatorGradientColor
+ * 
+ * Returns the indicator gradient color for the given cell state. This
+ * implementation returns the value stored under
+ * <mxConstants.STYLE_INDICATOR_GRADIENTCOLOR> in the cell style.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose indicator gradient color should be
+ * returned.
+ */
+mxGraph.prototype.getIndicatorGradientColor = function(state)
+{
+	return (state != null && state.style != null) ? state.style[mxConstants.STYLE_INDICATOR_GRADIENTCOLOR] : null;
+};
+
+/**
+ * Function: getIndicatorShape
+ * 
+ * Returns the indicator shape for the given cell state. This
+ * implementation returns the value stored under
+ * <mxConstants.STYLE_INDICATOR_SHAPE> in the cell style.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose indicator shape should be returned.
+ */
+mxGraph.prototype.getIndicatorShape = function(state)
+{
+	return (state != null && state.style != null) ? state.style[mxConstants.STYLE_INDICATOR_SHAPE] : null;
+};
+
+/**
+ * Function: getIndicatorImage
+ * 
+ * Returns the indicator image for the given cell state. This
+ * implementation returns the value stored under
+ * <mxConstants.STYLE_INDICATOR_IMAGE> in the cell style.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose indicator image should be returned.
+ */
+mxGraph.prototype.getIndicatorImage = function(state)
+{
+	return (state != null && state.style != null) ? state.style[mxConstants.STYLE_INDICATOR_IMAGE] : null;
+};
+
+/**
+ * Function: getBorder
+ * 
+ * Returns the value of <border>.
+ */
+mxGraph.prototype.getBorder = function()
+{
+	return this.border;
+};
+
+/**
+ * Function: setBorder
+ * 
+ * Sets the value of <border>.
+ * 
+ * Parameters:
+ * 
+ * value - Positive integer that represents the border to be used.
+ */
+mxGraph.prototype.setBorder = function(value)
+{
+	this.border = value;
+};
+
+/**
+ * Function: isSwimlane
+ * 
+ * Returns true if the given cell is a swimlane in the graph. A swimlane is
+ * a container cell with some specific behaviour. This implementation
+ * checks if the shape associated with the given cell is a <mxSwimlane>.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to be checked.
+ */
+mxGraph.prototype.isSwimlane = function (cell)
+{
+	if (cell != null)
+	{
+		if (this.model.getParent(cell) != this.model.getRoot())
+		{
+			var state = this.view.getState(cell);
+			var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+			if (style != null && !this.model.isEdge(cell))
+			{
+				return style[mxConstants.STYLE_SHAPE] == mxConstants.SHAPE_SWIMLANE;
+			}
+		}
+	}
+	
+	return false;
+};
+
+/**
+ * Group: Graph behaviour
+ */
+
+/**
+ * Function: isResizeContainer
+ * 
+ * Returns <resizeContainer>.
+ */
+mxGraph.prototype.isResizeContainer = function()
+{
+	return this.resizeContainer;
+};
+
+/**
+ * Function: setResizeContainer
+ * 
+ * Sets <resizeContainer>.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if the container should be resized.
+ */
+mxGraph.prototype.setResizeContainer = function(value)
+{
+	this.resizeContainer = value;
+};
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns true if the graph is <enabled>.
+ */
+mxGraph.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ * 
+ * Specifies if the graph should allow any interactions. This
+ * implementation updates <enabled>.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if the graph should be enabled.
+ */
+mxGraph.prototype.setEnabled = function(value)
+{
+	this.enabled = value;
+};
+
+/**
+ * Function: isEscapeEnabled
+ * 
+ * Returns <escapeEnabled>.
+ */
+mxGraph.prototype.isEscapeEnabled = function()
+{
+	return this.escapeEnabled;
+};
+
+/**
+ * Function: setEscapeEnabled
+ * 
+ * Sets <escapeEnabled>.
+ * 
+ * Parameters:
+ * 
+ * enabled - Boolean indicating if escape should be enabled.
+ */
+mxGraph.prototype.setEscapeEnabled = function(value)
+{
+	this.escapeEnabled = value;
+};
+
+/**
+ * Function: isInvokesStopCellEditing
+ * 
+ * Returns <invokesStopCellEditing>.
+ */
+mxGraph.prototype.isInvokesStopCellEditing = function()
+{
+	return this.invokesStopCellEditing;
+};
+
+/**
+ * Function: setInvokesStopCellEditing
+ * 
+ * Sets <invokesStopCellEditing>.
+ */
+mxGraph.prototype.setInvokesStopCellEditing = function(value)
+{
+	this.invokesStopCellEditing = value;
+};
+
+/**
+ * Function: isEnterStopsCellEditing
+ * 
+ * Returns <enterStopsCellEditing>.
+ */
+mxGraph.prototype.isEnterStopsCellEditing = function()
+{
+	return this.enterStopsCellEditing;
+};
+
+/**
+ * Function: setEnterStopsCellEditing
+ * 
+ * Sets <enterStopsCellEditing>.
+ */
+mxGraph.prototype.setEnterStopsCellEditing = function(value)
+{
+	this.enterStopsCellEditing = value;
+};
+
+/**
+ * Function: isCellLocked
+ * 
+ * Returns true if the given cell may not be moved, sized, bended,
+ * disconnected, edited or selected. This implementation returns true for
+ * all vertices with a relative geometry if <locked> is false.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose locked state should be returned.
+ */
+mxGraph.prototype.isCellLocked = function(cell)
+{
+	var geometry = this.model.getGeometry(cell);
+	
+	return this.isCellsLocked() || (geometry != null && this.model.isVertex(cell) && geometry.relative);
+};
+
+/**
+ * Function: isCellsLocked
+ * 
+ * Returns true if the given cell may not be moved, sized, bended,
+ * disconnected, edited or selected. This implementation returns true for
+ * all vertices with a relative geometry if <locked> is false.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose locked state should be returned.
+ */
+mxGraph.prototype.isCellsLocked = function()
+{
+	return this.cellsLocked;
+};
+
+/**
+ * Function: setLocked
+ * 
+ * Sets if any cell may be moved, sized, bended, disconnected, edited or
+ * selected.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean that defines the new value for <cellsLocked>.
+ */
+mxGraph.prototype.setCellsLocked = function(value)
+{
+	this.cellsLocked = value;
+};
+
+/**
+ * Function: getCloneableCells
+ * 
+ * Returns the cells which may be exported in the given array of cells.
+ */
+mxGraph.prototype.getCloneableCells = function(cells)
+{
+	return this.model.filterCells(cells, mxUtils.bind(this, function(cell)
+	{
+		return this.isCellCloneable(cell);
+	}));
+};
+
+/**
+ * Function: isCellCloneable
+ * 
+ * Returns true if the given cell is cloneable. This implementation returns
+ * <isCellsCloneable> for all cells unless a cell style specifies
+ * <mxConstants.STYLE_CLONEABLE> to be 0. 
+ * 
+ * Parameters:
+ * 
+ * cell - Optional <mxCell> whose cloneable state should be returned.
+ */
+mxGraph.prototype.isCellCloneable = function(cell)
+{
+	var state = this.view.getState(cell);
+	var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+	return this.isCellsCloneable() && style[mxConstants.STYLE_CLONEABLE] != 0;
+};
+
+/**
+ * Function: isCellsCloneable
+ * 
+ * Returns <cellsCloneable>, that is, if the graph allows cloning of cells
+ * by using control-drag.
+ */
+mxGraph.prototype.isCellsCloneable = function()
+{
+	return this.cellsCloneable;
+};
+
+/**
+ * Function: setCellsCloneable
+ * 
+ * Specifies if the graph should allow cloning of cells by holding down the
+ * control key while cells are being moved. This implementation updates
+ * <cellsCloneable>.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if the graph should be cloneable.
+ */
+mxGraph.prototype.setCellsCloneable = function(value)
+{
+	this.cellsCloneable = value;
+};
+
+/**
+ * Function: getExportableCells
+ * 
+ * Returns the cells which may be exported in the given array of cells.
+ */
+mxGraph.prototype.getExportableCells = function(cells)
+{
+	return this.model.filterCells(cells, mxUtils.bind(this, function(cell)
+	{
+		return this.canExportCell(cell);
+	}));
+};
+
+/**
+ * Function: canExportCell
+ * 
+ * Returns true if the given cell may be exported to the clipboard. This
+ * implementation returns <exportEnabled> for all cells.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the cell to be exported.
+ */
+mxGraph.prototype.canExportCell = function(cell)
+{
+	return this.exportEnabled;
+};
+
+/**
+ * Function: getImportableCells
+ * 
+ * Returns the cells which may be imported in the given array of cells.
+ */
+mxGraph.prototype.getImportableCells = function(cells)
+{
+	return this.model.filterCells(cells, mxUtils.bind(this, function(cell)
+	{
+		return this.canImportCell(cell);
+	}));
+};
+
+/**
+ * Function: canImportCell
+ * 
+ * Returns true if the given cell may be imported from the clipboard.
+ * This implementation returns <importEnabled> for all cells.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the cell to be imported.
+ */
+mxGraph.prototype.canImportCell = function(cell)
+{
+	return this.importEnabled;
+};
+
+/**
+ * Function: isCellSelectable
+ *
+ * Returns true if the given cell is selectable. This implementation
+ * returns <cellsSelectable>.
+ * 
+ * To add a new style for making cells (un)selectable, use the following code.
+ * 
+ * (code)
+ * mxGraph.prototype.isCellSelectable = function(cell)
+ * {
+ *   var state = this.view.getState(cell);
+ *   var style = (state != null) ? state.style : this.getCellStyle(cell);
+ *   
+ *   return this.isCellsSelectable() && !this.isCellLocked(cell) && style['selectable'] != 0;
+ * };
+ * (end)
+ * 
+ * You can then use the new style as shown in this example.
+ * 
+ * (code)
+ * graph.insertVertex(parent, null, 'Hello,', 20, 20, 80, 30, 'selectable=0');
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose selectable state should be returned.
+ */
+mxGraph.prototype.isCellSelectable = function(cell)
+{
+	return this.isCellsSelectable();
+};
+
+/**
+ * Function: isCellsSelectable
+ *
+ * Returns <cellsSelectable>.
+ */
+mxGraph.prototype.isCellsSelectable = function()
+{
+	return this.cellsSelectable;
+};
+
+/**
+ * Function: setCellsSelectable
+ *
+ * Sets <cellsSelectable>.
+ */
+mxGraph.prototype.setCellsSelectable = function(value)
+{
+	this.cellsSelectable = value;
+};
+
+/**
+ * Function: getDeletableCells
+ * 
+ * Returns the cells which may be exported in the given array of cells.
+ */
+mxGraph.prototype.getDeletableCells = function(cells)
+{
+	return this.model.filterCells(cells, mxUtils.bind(this, function(cell)
+	{
+		return this.isCellDeletable(cell);
+	}));
+};
+
+/**
+ * Function: isCellDeletable
+ *
+ * Returns true if the given cell is moveable. This returns
+ * <cellsDeletable> for all given cells if a cells style does not specify
+ * <mxConstants.STYLE_DELETABLE> to be 0.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose deletable state should be returned.
+ */
+mxGraph.prototype.isCellDeletable = function(cell)
+{
+	var state = this.view.getState(cell);
+	var style = (state != null) ? state.style : this.getCellStyle(cell);
+	
+	return this.isCellsDeletable() && style[mxConstants.STYLE_DELETABLE] != 0;
+};
+
+/**
+ * Function: isCellsDeletable
+ *
+ * Returns <cellsDeletable>.
+ */
+mxGraph.prototype.isCellsDeletable = function()
+{
+	return this.cellsDeletable;
+};
+
+/**
+ * Function: setCellsDeletable
+ * 
+ * Sets <cellsDeletable>.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if the graph should allow deletion of cells.
+ */
+mxGraph.prototype.setCellsDeletable = function(value)
+{
+	this.cellsDeletable = value;
+};
+
+/**
+ * Function: isLabelMovable
+ *
+ * Returns true if the given edges's label is moveable. This returns
+ * <movable> for all given cells if <isLocked> does not return true
+ * for the given cell.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose label should be moved.
+ */
+mxGraph.prototype.isLabelMovable = function(cell)
+{
+	return !this.isCellLocked(cell) &&
+		((this.model.isEdge(cell) && this.edgeLabelsMovable) ||
+		(this.model.isVertex(cell) && this.vertexLabelsMovable));
+};
+
+/**
+ * Function: isCellRotatable
+ *
+ * Returns true if the given cell is rotatable. This returns true for the given
+ * cell if its style does not specify <mxConstants.STYLE_ROTATABLE> to be 0.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose rotatable state should be returned.
+ */
+mxGraph.prototype.isCellRotatable = function(cell)
+{
+	var state = this.view.getState(cell);
+	var style = (state != null) ? state.style : this.getCellStyle(cell);
+	
+	return style[mxConstants.STYLE_ROTATABLE] != 0;
+};
+
+/**
+ * Function: getMovableCells
+ * 
+ * Returns the cells which are movable in the given array of cells.
+ */
+mxGraph.prototype.getMovableCells = function(cells)
+{
+	return this.model.filterCells(cells, mxUtils.bind(this, function(cell)
+	{
+		return this.isCellMovable(cell);
+	}));
+};
+
+/**
+ * Function: isCellMovable
+ *
+ * Returns true if the given cell is moveable. This returns <cellsMovable>
+ * for all given cells if <isCellLocked> does not return true for the given
+ * cell and its style does not specify <mxConstants.STYLE_MOVABLE> to be 0.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose movable state should be returned.
+ */
+mxGraph.prototype.isCellMovable = function(cell)
+{
+	var state = this.view.getState(cell);
+	var style = (state != null) ? state.style : this.getCellStyle(cell);
+	
+	return this.isCellsMovable() && !this.isCellLocked(cell) && style[mxConstants.STYLE_MOVABLE] != 0;
+};
+
+/**
+ * Function: isCellsMovable
+ *
+ * Returns <cellsMovable>.
+ */
+mxGraph.prototype.isCellsMovable = function()
+{
+	return this.cellsMovable;
+};
+
+/**
+ * Function: setCellsMovable
+ * 
+ * Specifies if the graph should allow moving of cells. This implementation
+ * updates <cellsMsovable>.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if the graph should allow moving of cells.
+ */
+mxGraph.prototype.setCellsMovable = function(value)
+{
+	this.cellsMovable = value;
+};
+
+/**
+ * Function: isGridEnabled
+ *
+ * Returns <gridEnabled> as a boolean.
+ */
+mxGraph.prototype.isGridEnabled = function()
+{
+	return this.gridEnabled;
+};
+
+/**
+ * Function: setGridEnabled
+ * 
+ * Specifies if the grid should be enabled.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if the grid should be enabled.
+ */
+mxGraph.prototype.setGridEnabled = function(value)
+{
+	this.gridEnabled = value;
+};
+
+/**
+ * Function: isPortsEnabled
+ *
+ * Returns <portsEnabled> as a boolean.
+ */
+mxGraph.prototype.isPortsEnabled = function()
+{
+	return this.portsEnabled;
+};
+
+/**
+ * Function: setPortsEnabled
+ * 
+ * Specifies if the ports should be enabled.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if the ports should be enabled.
+ */
+mxGraph.prototype.setPortsEnabled = function(value)
+{
+	this.portsEnabled = value;
+};
+
+/**
+ * Function: getGridSize
+ *
+ * Returns <gridSize>.
+ */
+mxGraph.prototype.getGridSize = function()
+{
+	return this.gridSize;
+};
+
+/**
+ * Function: setGridSize
+ * 
+ * Sets <gridSize>.
+ */
+mxGraph.prototype.setGridSize = function(value)
+{
+	this.gridSize = value;
+};
+
+/**
+ * Function: getTolerance
+ *
+ * Returns <tolerance>.
+ */
+mxGraph.prototype.getTolerance = function()
+{
+	return this.tolerance;
+};
+
+/**
+ * Function: setTolerance
+ * 
+ * Sets <tolerance>.
+ */
+mxGraph.prototype.setTolerance = function(value)
+{
+	this.tolerance = value;
+};
+
+/**
+ * Function: isVertexLabelsMovable
+ *
+ * Returns <vertexLabelsMovable>.
+ */
+mxGraph.prototype.isVertexLabelsMovable = function()
+{
+	return this.vertexLabelsMovable;
+};
+
+/**
+ * Function: setVertexLabelsMovable
+ * 
+ * Sets <vertexLabelsMovable>.
+ */
+mxGraph.prototype.setVertexLabelsMovable = function(value)
+{
+	this.vertexLabelsMovable = value;
+};
+
+/**
+ * Function: isEdgeLabelsMovable
+ *
+ * Returns <edgeLabelsMovable>.
+ */
+mxGraph.prototype.isEdgeLabelsMovable = function()
+{
+	return this.edgeLabelsMovable;
+};
+
+/**
+ * Function: isEdgeLabelsMovable
+ * 
+ * Sets <edgeLabelsMovable>.
+ */
+mxGraph.prototype.setEdgeLabelsMovable = function(value)
+{
+	this.edgeLabelsMovable = value;
+};
+
+/**
+ * Function: isSwimlaneNesting
+ *
+ * Returns <swimlaneNesting> as a boolean.
+ */
+mxGraph.prototype.isSwimlaneNesting = function()
+{
+	return this.swimlaneNesting;
+};
+
+/**
+ * Function: setSwimlaneNesting
+ * 
+ * Specifies if swimlanes can be nested by drag and drop. This is only
+ * taken into account if dropEnabled is true.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if swimlanes can be nested.
+ */
+mxGraph.prototype.setSwimlaneNesting = function(value)
+{
+	this.swimlaneNesting = value;
+};
+
+/**
+ * Function: isSwimlaneSelectionEnabled
+ *
+ * Returns <swimlaneSelectionEnabled> as a boolean.
+ */
+mxGraph.prototype.isSwimlaneSelectionEnabled = function()
+{
+	return this.swimlaneSelectionEnabled;
+};
+
+/**
+ * Function: setSwimlaneSelectionEnabled
+ * 
+ * Specifies if swimlanes should be selected if the mouse is released
+ * over their content area.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if swimlanes content areas
+ * should be selected when the mouse is released over them.
+ */
+mxGraph.prototype.setSwimlaneSelectionEnabled = function(value)
+{
+	this.swimlaneSelectionEnabled = value;
+};
+
+/**
+ * Function: isMultigraph
+ *
+ * Returns <multigraph> as a boolean.
+ */
+mxGraph.prototype.isMultigraph = function()
+{
+	return this.multigraph;
+};
+
+/**
+ * Function: setMultigraph
+ * 
+ * Specifies if the graph should allow multiple connections between the
+ * same pair of vertices.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if the graph allows multiple connections
+ * between the same pair of vertices.
+ */
+mxGraph.prototype.setMultigraph = function(value)
+{
+	this.multigraph = value;
+};
+
+/**
+ * Function: isAllowLoops
+ *
+ * Returns <allowLoops> as a boolean.
+ */
+mxGraph.prototype.isAllowLoops = function()
+{
+	return this.allowLoops;
+};
+
+/**
+ * Function: setAllowDanglingEdges
+ * 
+ * Specifies if dangling edges are allowed, that is, if edges are allowed
+ * that do not have a source and/or target terminal defined.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if dangling edges are allowed.
+ */
+mxGraph.prototype.setAllowDanglingEdges = function(value)
+{
+	this.allowDanglingEdges = value;
+};
+
+/**
+ * Function: isAllowDanglingEdges
+ *
+ * Returns <allowDanglingEdges> as a boolean.
+ */
+mxGraph.prototype.isAllowDanglingEdges = function()
+{
+	return this.allowDanglingEdges;
+};
+
+/**
+ * Function: setConnectableEdges
+ * 
+ * Specifies if edges should be connectable.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if edges should be connectable.
+ */
+mxGraph.prototype.setConnectableEdges = function(value)
+{
+	this.connectableEdges = value;
+};
+
+/**
+ * Function: isConnectableEdges
+ *
+ * Returns <connectableEdges> as a boolean.
+ */
+mxGraph.prototype.isConnectableEdges = function()
+{
+	return this.connectableEdges;
+};
+
+/**
+ * Function: setCloneInvalidEdges
+ * 
+ * Specifies if edges should be inserted when cloned but not valid wrt.
+ * <getEdgeValidationError>. If false such edges will be silently ignored.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if cloned invalid edges should be
+ * inserted into the graph or ignored.
+ */
+mxGraph.prototype.setCloneInvalidEdges = function(value)
+{
+	this.cloneInvalidEdges = value;
+};
+
+/**
+ * Function: isCloneInvalidEdges
+ *
+ * Returns <cloneInvalidEdges> as a boolean.
+ */
+mxGraph.prototype.isCloneInvalidEdges = function()
+{
+	return this.cloneInvalidEdges;
+};
+
+/**
+ * Function: setAllowLoops
+ * 
+ * Specifies if loops are allowed.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if loops are allowed.
+ */
+mxGraph.prototype.setAllowLoops = function(value)
+{
+	this.allowLoops = value;
+};
+
+/**
+ * Function: isDisconnectOnMove
+ *
+ * Returns <disconnectOnMove> as a boolean.
+ */
+mxGraph.prototype.isDisconnectOnMove = function()
+{
+	return this.disconnectOnMove;
+};
+
+/**
+ * Function: setDisconnectOnMove
+ * 
+ * Specifies if edges should be disconnected when moved. (Note: Cloned
+ * edges are always disconnected.)
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if edges should be disconnected
+ * when moved.
+ */
+mxGraph.prototype.setDisconnectOnMove = function(value)
+{
+	this.disconnectOnMove = value;
+};
+
+/**
+ * Function: isDropEnabled
+ *
+ * Returns <dropEnabled> as a boolean.
+ */
+mxGraph.prototype.isDropEnabled = function()
+{
+	return this.dropEnabled;
+};
+
+/**
+ * Function: setDropEnabled
+ * 
+ * Specifies if the graph should allow dropping of cells onto or into other
+ * cells.
+ * 
+ * Parameters:
+ * 
+ * dropEnabled - Boolean indicating if the graph should allow dropping
+ * of cells into other cells.
+ */
+mxGraph.prototype.setDropEnabled = function(value)
+{
+	this.dropEnabled = value;
+};
+
+/**
+ * Function: isSplitEnabled
+ *
+ * Returns <splitEnabled> as a boolean.
+ */
+mxGraph.prototype.isSplitEnabled = function()
+{
+	return this.splitEnabled;
+};
+
+/**
+ * Function: setSplitEnabled
+ * 
+ * Specifies if the graph should allow dropping of cells onto or into other
+ * cells.
+ * 
+ * Parameters:
+ * 
+ * dropEnabled - Boolean indicating if the graph should allow dropping
+ * of cells into other cells.
+ */
+mxGraph.prototype.setSplitEnabled = function(value)
+{
+	this.splitEnabled = value;
+};
+
+/**
+ * Function: isCellResizable
+ *
+ * Returns true if the given cell is resizable. This returns
+ * <cellsResizable> for all given cells if <isCellLocked> does not return
+ * true for the given cell and its style does not specify
+ * <mxConstants.STYLE_RESIZABLE> to be 0.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose resizable state should be returned.
+ */
+mxGraph.prototype.isCellResizable = function(cell)
+{
+	var state = this.view.getState(cell);
+	var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+	return this.isCellsResizable() && !this.isCellLocked(cell) &&
+		mxUtils.getValue(style, mxConstants.STYLE_RESIZABLE, '1') != '0';
+};
+
+/**
+ * Function: isCellsResizable
+ *
+ * Returns <cellsResizable>.
+ */
+mxGraph.prototype.isCellsResizable = function()
+{
+	return this.cellsResizable;
+};
+
+/**
+ * Function: setCellsResizable
+ * 
+ * Specifies if the graph should allow resizing of cells. This
+ * implementation updates <cellsResizable>.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if the graph should allow resizing of
+ * cells.
+ */
+mxGraph.prototype.setCellsResizable = function(value)
+{
+	this.cellsResizable = value;
+};
+
+/**
+ * Function: isTerminalPointMovable
+ *
+ * Returns true if the given terminal point is movable. This is independent
+ * from <isCellConnectable> and <isCellDisconnectable> and controls if terminal
+ * points can be moved in the graph if the edge is not connected. Note that it
+ * is required for this to return true to connect unconnected edges. This
+ * implementation returns true.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose terminal point should be moved.
+ * source - Boolean indicating if the source or target terminal should be moved.
+ */
+mxGraph.prototype.isTerminalPointMovable = function(cell, source)
+{
+	return true;
+};
+
+/**
+ * Function: isCellBendable
+ *
+ * Returns true if the given cell is bendable. This returns <cellsBendable>
+ * for all given cells if <isLocked> does not return true for the given
+ * cell and its style does not specify <mxConstants.STYLE_BENDABLE> to be 0.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose bendable state should be returned.
+ */
+mxGraph.prototype.isCellBendable = function(cell)
+{
+	var state = this.view.getState(cell);
+	var style = (state != null) ? state.style : this.getCellStyle(cell);
+	
+	return this.isCellsBendable() && !this.isCellLocked(cell) && style[mxConstants.STYLE_BENDABLE] != 0;
+};
+
+/**
+ * Function: isCellsBendable
+ *
+ * Returns <cellsBenadable>.
+ */
+mxGraph.prototype.isCellsBendable = function()
+{
+	return this.cellsBendable;
+};
+
+/**
+ * Function: setCellsBendable
+ * 
+ * Specifies if the graph should allow bending of edges. This
+ * implementation updates <bendable>.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if the graph should allow bending of
+ * edges.
+ */
+mxGraph.prototype.setCellsBendable = function(value)
+{
+	this.cellsBendable = value;
+};
+
+/**
+ * Function: isCellEditable
+ *
+ * Returns true if the given cell is editable. This returns <cellsEditable> for
+ * all given cells if <isCellLocked> does not return true for the given cell
+ * and its style does not specify <mxConstants.STYLE_EDITABLE> to be 0.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose editable state should be returned.
+ */
+mxGraph.prototype.isCellEditable = function(cell)
+{
+	var state = this.view.getState(cell);
+	var style = (state != null) ? state.style : this.getCellStyle(cell);
+	
+	return this.isCellsEditable() && !this.isCellLocked(cell) && style[mxConstants.STYLE_EDITABLE] != 0;
+};
+
+/**
+ * Function: isCellsEditable
+ *
+ * Returns <cellsEditable>.
+ */
+mxGraph.prototype.isCellsEditable = function()
+{
+	return this.cellsEditable;
+};
+
+/**
+ * Function: setCellsEditable
+ * 
+ * Specifies if the graph should allow in-place editing for cell labels.
+ * This implementation updates <cellsEditable>.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if the graph should allow in-place
+ * editing.
+ */
+mxGraph.prototype.setCellsEditable = function(value)
+{
+	this.cellsEditable = value;
+};
+
+/**
+ * Function: isCellDisconnectable
+ *
+ * Returns true if the given cell is disconnectable from the source or
+ * target terminal. This returns <isCellsDisconnectable> for all given
+ * cells if <isCellLocked> does not return true for the given cell.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose disconnectable state should be returned.
+ * terminal - <mxCell> that represents the source or target terminal.
+ * source - Boolean indicating if the source or target terminal is to be
+ * disconnected.
+ */
+mxGraph.prototype.isCellDisconnectable = function(cell, terminal, source)
+{
+	return this.isCellsDisconnectable() && !this.isCellLocked(cell);
+};
+
+/**
+ * Function: isCellsDisconnectable
+ *
+ * Returns <cellsDisconnectable>.
+ */
+mxGraph.prototype.isCellsDisconnectable = function()
+{
+	return this.cellsDisconnectable;
+};
+
+/**
+ * Function: setCellsDisconnectable
+ *
+ * Sets <cellsDisconnectable>.
+ */
+mxGraph.prototype.setCellsDisconnectable = function(value)
+{
+	this.cellsDisconnectable = value;
+};
+
+/**
+ * Function: isValidSource
+ * 
+ * Returns true if the given cell is a valid source for new connections.
+ * This implementation returns true for all non-null values and is
+ * called by is called by <isValidConnection>.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents a possible source or null.
+ */
+mxGraph.prototype.isValidSource = function(cell)
+{
+	return (cell == null && this.allowDanglingEdges) ||
+		(cell != null && (!this.model.isEdge(cell) ||
+		this.connectableEdges) && this.isCellConnectable(cell));
+};
+	
+/**
+ * Function: isValidTarget
+ * 
+ * Returns <isValidSource> for the given cell. This is called by
+ * <isValidConnection>.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents a possible target or null.
+ */
+mxGraph.prototype.isValidTarget = function(cell)
+{
+	return this.isValidSource(cell);
+};
+
+/**
+ * Function: isValidConnection
+ * 
+ * Returns true if the given target cell is a valid target for source.
+ * This is a boolean implementation for not allowing connections between
+ * certain pairs of vertices and is called by <getEdgeValidationError>.
+ * This implementation returns true if <isValidSource> returns true for
+ * the source and <isValidTarget> returns true for the target.
+ * 
+ * Parameters:
+ * 
+ * source - <mxCell> that represents the source cell.
+ * target - <mxCell> that represents the target cell.
+ */
+mxGraph.prototype.isValidConnection = function(source, target)
+{
+	return this.isValidSource(source) && this.isValidTarget(target);
+};
+
+/**
+ * Function: setConnectable
+ * 
+ * Specifies if the graph should allow new connections. This implementation
+ * updates <mxConnectionHandler.enabled> in <connectionHandler>.
+ * 
+ * Parameters:
+ * 
+ * connectable - Boolean indicating if new connections should be allowed.
+ */
+mxGraph.prototype.setConnectable = function(connectable)
+{
+	this.connectionHandler.setEnabled(connectable);
+};
+	
+/**
+ * Function: isConnectable
+ * 
+ * Returns true if the <connectionHandler> is enabled.
+ */
+mxGraph.prototype.isConnectable = function(connectable)
+{
+	return this.connectionHandler.isEnabled();
+};
+
+/**
+ * Function: setTooltips
+ * 
+ * Specifies if tooltips should be enabled. This implementation updates
+ * <mxTooltipHandler.enabled> in <tooltipHandler>.
+ * 
+ * Parameters:
+ * 
+ * enabled - Boolean indicating if tooltips should be enabled.
+ */
+mxGraph.prototype.setTooltips = function (enabled)
+{
+	this.tooltipHandler.setEnabled(enabled);
+};
+
+/**
+ * Function: setPanning
+ * 
+ * Specifies if panning should be enabled. This implementation updates
+ * <mxPanningHandler.panningEnabled> in <panningHandler>.
+ * 
+ * Parameters:
+ * 
+ * enabled - Boolean indicating if panning should be enabled.
+ */
+mxGraph.prototype.setPanning = function(enabled)
+{
+	this.panningHandler.panningEnabled = enabled;
+};
+
+/**
+ * Function: isEditing
+ * 
+ * Returns true if the given cell is currently being edited.
+ * If no cell is specified then this returns true if any
+ * cell is currently being edited.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> that should be checked.
+ */
+mxGraph.prototype.isEditing = function(cell)
+{
+	if (this.cellEditor != null)
+	{
+		var editingCell = this.cellEditor.getEditingCell();
+		
+		return (cell == null) ? editingCell != null : cell == editingCell;
+	}
+	
+	return false;
+};
+
+/**
+ * Function: isAutoSizeCell
+ * 
+ * Returns true if the size of the given cell should automatically be
+ * updated after a change of the label. This implementation returns
+ * <autoSizeCells> or checks if the cell style does specify
+ * <mxConstants.STYLE_AUTOSIZE> to be 1.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that should be resized.
+ */
+mxGraph.prototype.isAutoSizeCell = function(cell)
+{
+	var state = this.view.getState(cell);
+	var style = (state != null) ? state.style : this.getCellStyle(cell);
+	
+	return this.isAutoSizeCells() || style[mxConstants.STYLE_AUTOSIZE] == 1;
+};
+
+/**
+ * Function: isAutoSizeCells
+ * 
+ * Returns <autoSizeCells>.
+ */
+mxGraph.prototype.isAutoSizeCells = function()
+{
+	return this.autoSizeCells;
+};
+
+/**
+ * Function: setAutoSizeCells
+ * 
+ * Specifies if cell sizes should be automatically updated after a label
+ * change. This implementation sets <autoSizeCells> to the given parameter.
+ * To update the size of cells when the cells are added, set
+ * <autoSizeCellsOnAdd> to true.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if cells should be resized
+ * automatically.
+ */
+mxGraph.prototype.setAutoSizeCells = function(value)
+{
+	this.autoSizeCells = value;
+};
+
+/**
+ * Function: isExtendParent
+ * 
+ * Returns true if the parent of the given cell should be extended if the
+ * child has been resized so that it overlaps the parent. This
+ * implementation returns <isExtendParents> if the cell is not an edge.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that has been resized.
+ */
+mxGraph.prototype.isExtendParent = function(cell)
+{
+	return !this.getModel().isEdge(cell) && this.isExtendParents();
+};
+
+/**
+ * Function: isExtendParents
+ * 
+ * Returns <extendParents>.
+ */
+mxGraph.prototype.isExtendParents = function()
+{
+	return this.extendParents;
+};
+
+/**
+ * Function: setExtendParents
+ * 
+ * Sets <extendParents>.
+ * 
+ * Parameters:
+ * 
+ * value - New boolean value for <extendParents>.
+ */
+mxGraph.prototype.setExtendParents = function(value)
+{
+	this.extendParents = value;
+};
+
+/**
+ * Function: isExtendParentsOnAdd
+ * 
+ * Returns <extendParentsOnAdd>.
+ */
+mxGraph.prototype.isExtendParentsOnAdd = function(cell)
+{
+	return this.extendParentsOnAdd;
+};
+
+/**
+ * Function: setExtendParentsOnAdd
+ * 
+ * Sets <extendParentsOnAdd>.
+ * 
+ * Parameters:
+ * 
+ * value - New boolean value for <extendParentsOnAdd>.
+ */
+mxGraph.prototype.setExtendParentsOnAdd = function(value)
+{
+	this.extendParentsOnAdd = value;
+};
+
+/**
+ * Function: isExtendParentsOnMove
+ * 
+ * Returns <extendParentsOnMove>.
+ */
+mxGraph.prototype.isExtendParentsOnMove = function()
+{
+	return this.extendParentsOnMove;
+};
+
+/**
+ * Function: setExtendParentsOnMove
+ * 
+ * Sets <extendParentsOnMove>.
+ * 
+ * Parameters:
+ * 
+ * value - New boolean value for <extendParentsOnAdd>.
+ */
+mxGraph.prototype.setExtendParentsOnMove = function(value)
+{
+	this.extendParentsOnMove = value;
+};
+
+/**
+ * Function: isRecursiveResize
+ * 
+ * Returns <recursiveResize>.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> that is being resized.
+ */
+mxGraph.prototype.isRecursiveResize = function(state)
+{
+	return this.recursiveResize;
+};
+
+/**
+ * Function: setRecursiveResize
+ * 
+ * Sets <recursiveResize>.
+ * 
+ * Parameters:
+ * 
+ * value - New boolean value for <recursiveResize>.
+ */
+mxGraph.prototype.setRecursiveResize = function(value)
+{
+	this.recursiveResize = value;
+};
+
+/**
+ * Function: isConstrainChild
+ * 
+ * Returns true if the given cell should be kept inside the bounds of its
+ * parent according to the rules defined by <getOverlap> and
+ * <isAllowOverlapParent>. This implementation returns false for all children
+ * of edges and <isConstrainChildren> otherwise.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that should be constrained.
+ */
+mxGraph.prototype.isConstrainChild = function(cell)
+{
+	return this.isConstrainChildren() && !this.getModel().isEdge(this.getModel().getParent(cell));
+};
+
+/**
+ * Function: isConstrainChildren
+ * 
+ * Returns <constrainChildren>.
+ */
+mxGraph.prototype.isConstrainChildren = function()
+{
+	return this.constrainChildren;
+};
+
+/**
+ * Function: setConstrainChildren
+ * 
+ * Sets <constrainChildren>.
+ */
+mxGraph.prototype.setConstrainChildren = function(value)
+{
+	this.constrainChildren = value;
+};
+
+/**
+ * Function: isConstrainRelativeChildren
+ * 
+ * Returns <constrainRelativeChildren>.
+ */
+mxGraph.prototype.isConstrainRelativeChildren = function()
+{
+	return this.constrainRelativeChildren;
+};
+
+/**
+ * Function: setConstrainRelativeChildren
+ * 
+ * Sets <constrainRelativeChildren>.
+ */
+mxGraph.prototype.setConstrainRelativeChildren = function(value)
+{
+	this.constrainRelativeChildren = value;
+};
+
+/**
+ * Function: isConstrainChildren
+ * 
+ * Returns <allowNegativeCoordinates>.
+ */
+mxGraph.prototype.isAllowNegativeCoordinates = function()
+{
+	return this.allowNegativeCoordinates;
+};
+
+/**
+ * Function: setConstrainChildren
+ * 
+ * Sets <allowNegativeCoordinates>.
+ */
+mxGraph.prototype.setAllowNegativeCoordinates = function(value)
+{
+	this.allowNegativeCoordinates = value;
+};
+
+/**
+ * Function: getOverlap
+ * 
+ * Returns a decimal number representing the amount of the width and height
+ * of the given cell that is allowed to overlap its parent. A value of 0
+ * means all children must stay inside the parent, 1 means the child is
+ * allowed to be placed outside of the parent such that it touches one of
+ * the parents sides. If <isAllowOverlapParent> returns false for the given
+ * cell, then this method returns 0.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> for which the overlap ratio should be returned.
+ */
+mxGraph.prototype.getOverlap = function(cell)
+{
+	return (this.isAllowOverlapParent(cell)) ? this.defaultOverlap : 0;
+};
+	
+/**
+ * Function: isAllowOverlapParent
+ * 
+ * Returns true if the given cell is allowed to be placed outside of the
+ * parents area.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the child to be checked.
+ */
+mxGraph.prototype.isAllowOverlapParent = function(cell)
+{
+	return false;
+};
+
+/**
+ * Function: getFoldableCells
+ * 
+ * Returns the cells which are movable in the given array of cells.
+ */
+mxGraph.prototype.getFoldableCells = function(cells, collapse)
+{
+	return this.model.filterCells(cells, mxUtils.bind(this, function(cell)
+	{
+		return this.isCellFoldable(cell, collapse);
+	}));
+};
+
+/**
+ * Function: isCellFoldable
+ * 
+ * Returns true if the given cell is foldable. This implementation
+ * returns true if the cell has at least one child and its style
+ * does not specify <mxConstants.STYLE_FOLDABLE> to be 0.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose foldable state should be returned.
+ */
+mxGraph.prototype.isCellFoldable = function(cell, collapse)
+{
+	var state = this.view.getState(cell);
+	var style = (state != null) ? state.style : this.getCellStyle(cell);
+	
+	return this.model.getChildCount(cell) > 0 && style[mxConstants.STYLE_FOLDABLE] != 0;
+};
+
+/**
+ * Function: isValidDropTarget
+ *
+ * Returns true if the given cell is a valid drop target for the specified
+ * cells. If <splitEnabled> is true then this returns <isSplitTarget> for
+ * the given arguments else it returns true if the cell is not collapsed
+ * and its child count is greater than 0.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the possible drop target.
+ * cells - <mxCells> that should be dropped into the target.
+ * evt - Mouseevent that triggered the invocation.
+ */
+mxGraph.prototype.isValidDropTarget = function(cell, cells, evt)
+{
+	return cell != null && ((this.isSplitEnabled() &&
+		this.isSplitTarget(cell, cells, evt)) || (!this.model.isEdge(cell) &&
+		(this.isSwimlane(cell) || (this.model.getChildCount(cell) > 0 &&
+		!this.isCellCollapsed(cell)))));
+};
+
+/**
+ * Function: isSplitTarget
+ *
+ * Returns true if the given edge may be splitted into two edges with the
+ * given cell as a new terminal between the two.
+ * 
+ * Parameters:
+ * 
+ * target - <mxCell> that represents the edge to be splitted.
+ * cells - <mxCells> that should split the edge.
+ * evt - Mouseevent that triggered the invocation.
+ */
+mxGraph.prototype.isSplitTarget = function(target, cells, evt)
+{
+	if (this.model.isEdge(target) && cells != null && cells.length == 1 &&
+		this.isCellConnectable(cells[0]) && this.getEdgeValidationError(target,
+			this.model.getTerminal(target, true), cells[0]) == null)
+	{
+		var src = this.model.getTerminal(target, true);
+		var trg = this.model.getTerminal(target, false);
+
+		return (!this.model.isAncestor(cells[0], src) &&
+				!this.model.isAncestor(cells[0], trg));
+	}
+
+	return false;
+};
+
+/**
+ * Function: getDropTarget
+ * 
+ * Returns the given cell if it is a drop target for the given cells or the
+ * nearest ancestor that may be used as a drop target for the given cells.
+ * If the given array contains a swimlane and <swimlaneNesting> is false
+ * then this always returns null. If no cell is given, then the bottommost
+ * swimlane at the location of the given event is returned.
+ * 
+ * This function should only be used if <isDropEnabled> returns true.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> which are to be dropped onto the target.
+ * evt - Mouseevent for the drag and drop.
+ * cell - <mxCell> that is under the mousepointer.
+ * clone - Optional boolean to indicate of cells will be cloned.
+ */
+mxGraph.prototype.getDropTarget = function(cells, evt, cell, clone)
+{
+	if (!this.isSwimlaneNesting())
+	{
+		for (var i = 0; i < cells.length; i++)
+		{
+			if (this.isSwimlane(cells[i]))
+			{
+				return null;
+			}
+		}
+	}
+
+	var pt = mxUtils.convertPoint(this.container,
+		mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+	pt.x -= this.panDx;
+	pt.y -= this.panDy;
+	var swimlane = this.getSwimlaneAt(pt.x, pt.y);
+	
+	if (cell == null)
+	{
+		cell = swimlane;
+	}
+	else if (swimlane != null)
+	{
+		// Checks if the cell is an ancestor of the swimlane
+		// under the mouse and uses the swimlane in that case
+		var tmp = this.model.getParent(swimlane);
+		
+		while (tmp != null && this.isSwimlane(tmp) && tmp != cell)
+		{
+			tmp = this.model.getParent(tmp);
+		}
+		
+		if (tmp == cell)
+		{
+			cell = swimlane;
+		}
+	}
+	
+	while (cell != null && !this.isValidDropTarget(cell, cells, evt) &&
+		!this.model.isLayer(cell))
+	{
+		cell = this.model.getParent(cell);
+	}
+	
+	// Checks if parent is dropped into child if not cloning
+	if (clone == null || !clone)
+	{
+		var parent = cell;
+		
+		while (parent != null && mxUtils.indexOf(cells, parent) < 0)
+		{
+			parent = this.model.getParent(parent);
+		}
+	}
+
+	return (!this.model.isLayer(cell) && parent == null) ? cell : null;
+};
+
+/**
+ * Group: Cell retrieval
+ */
+
+/**
+ * Function: getDefaultParent
+ * 
+ * Returns <defaultParent> or <mxGraphView.currentRoot> or the first child
+ * child of <mxGraphModel.root> if both are null. The value returned by
+ * this function should be used as the parent for new cells (aka default
+ * layer).
+ */
+mxGraph.prototype.getDefaultParent = function()
+{
+	var parent = this.getCurrentRoot();
+	
+	if (parent == null)
+	{
+		parent = this.defaultParent;
+		
+		if (parent == null)
+		{
+			var root = this.model.getRoot();
+			parent = this.model.getChildAt(root, 0);
+		}
+	}
+	
+	return parent;
+};
+
+/**
+ * Function: setDefaultParent
+ * 
+ * Sets the <defaultParent> to the given cell. Set this to null to return
+ * the first child of the root in getDefaultParent.
+ */
+mxGraph.prototype.setDefaultParent = function(cell)
+{
+	this.defaultParent = cell;
+};
+
+/**
+ * Function: getSwimlane
+ * 
+ * Returns the nearest ancestor of the given cell which is a swimlane, or
+ * the given cell, if it is itself a swimlane.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> for which the ancestor swimlane should be returned.
+ */
+mxGraph.prototype.getSwimlane = function(cell)
+{
+	while (cell != null && !this.isSwimlane(cell))
+	{
+		cell = this.model.getParent(cell);
+	}
+	
+	return cell;
+};
+
+/**
+ * Function: getSwimlaneAt
+ * 
+ * Returns the bottom-most swimlane that intersects the given point (x, y)
+ * in the cell hierarchy that starts at the given parent.
+ * 
+ * Parameters:
+ * 
+ * x - X-coordinate of the location to be checked.
+ * y - Y-coordinate of the location to be checked.
+ * parent - <mxCell> that should be used as the root of the recursion.
+ * Default is <defaultParent>.
+ */
+mxGraph.prototype.getSwimlaneAt = function (x, y, parent)
+{
+	parent = parent || this.getDefaultParent();
+	
+	if (parent != null)
+	{
+		var childCount = this.model.getChildCount(parent);
+		
+		for (var i = 0; i < childCount; i++)
+		{
+			var child = this.model.getChildAt(parent, i);
+			var result = this.getSwimlaneAt(x, y, child);
+			
+			if (result != null)
+			{
+				return result;
+			}
+			else if (this.isSwimlane(child))
+			{
+				var state = this.view.getState(child);
+				
+				if (this.intersects(state, x, y))
+				{
+					return child;
+				}
+			}
+		}
+	}
+	
+	return null;
+};
+
+/**
+ * Function: getCellAt
+ * 
+ * Returns the bottom-most cell that intersects the given point (x, y) in
+ * the cell hierarchy starting at the given parent. This will also return
+ * swimlanes if the given location intersects the content area of the
+ * swimlane. If this is not desired, then the <hitsSwimlaneContent> may be
+ * used if the returned cell is a swimlane to determine if the location
+ * is inside the content area or on the actual title of the swimlane.
+ * 
+ * Parameters:
+ * 
+ * x - X-coordinate of the location to be checked.
+ * y - Y-coordinate of the location to be checked.
+ * parent - <mxCell> that should be used as the root of the recursion.
+ * Default is current root of the view or the root of the model.
+ * vertices - Optional boolean indicating if vertices should be returned.
+ * Default is true.
+ * edges - Optional boolean indicating if edges should be returned. Default
+ * is true.
+ * ignoreFn - Optional function that returns true if cell should be ignored.
+ * The function is passed the cell state and the x and y parameter.
+ */
+mxGraph.prototype.getCellAt = function(x, y, parent, vertices, edges, ignoreFn)
+{
+	vertices = (vertices != null) ? vertices : true;
+	edges = (edges != null) ? edges : true;
+
+	if (parent == null)
+	{
+		parent = this.getCurrentRoot();
+		
+		if (parent == null)
+		{
+			parent = this.getModel().getRoot();
+		}
+	}
+
+	if (parent != null)
+	{
+		var childCount = this.model.getChildCount(parent);
+		
+		for (var i = childCount - 1; i >= 0; i--)
+		{
+			var cell = this.model.getChildAt(parent, i);
+			var result = this.getCellAt(x, y, cell, vertices, edges, ignoreFn);
+			
+			if (result != null)
+			{
+				return result;
+			}
+			else if (this.isCellVisible(cell) && (edges && this.model.isEdge(cell) ||
+				vertices && this.model.isVertex(cell)))
+			{
+				var state = this.view.getState(cell);
+
+				if (state != null && (ignoreFn == null || !ignoreFn(state, x, y)) &&
+					this.intersects(state, x, y))
+				{
+					return cell;
+				}
+			}
+		}
+	}
+	
+	return null;
+};
+
+/**
+ * Function: intersects
+ * 
+ * Returns the bottom-most cell that intersects the given point (x, y) in
+ * the cell hierarchy that starts at the given parent.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> that represents the cell state.
+ * x - X-coordinate of the location to be checked.
+ * y - Y-coordinate of the location to be checked.
+ */
+mxGraph.prototype.intersects = function(state, x, y)
+{
+	if (state != null)
+	{
+		var pts = state.absolutePoints;
+
+		if (pts != null)
+		{
+			var t2 = this.tolerance * this.tolerance;
+			var pt = pts[0];
+			
+			for (var i = 1; i < pts.length; i++)
+			{
+				var next = pts[i];
+				var dist = mxUtils.ptSegDistSq(pt.x, pt.y, next.x, next.y, x, y);
+				
+				if (dist <= t2)
+				{
+					return true;
+				}
+				
+				pt = next;
+			}
+		}
+		else
+		{
+			var alpha = mxUtils.toRadians(mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION) || 0);
+			
+			if (alpha != 0)
+			{
+				var cos = Math.cos(-alpha);
+				var sin = Math.sin(-alpha);
+				var cx = new mxPoint(state.getCenterX(), state.getCenterY());
+				var pt = mxUtils.getRotatedPoint(new mxPoint(x, y), cos, sin, cx);
+				x = pt.x;
+				y = pt.y;
+			}
+			
+			if (mxUtils.contains(state, x, y))
+			{
+				return true;
+			}
+		}
+	}
+	
+	return false;
+};
+
+/**
+ * Function: hitsSwimlaneContent
+ * 
+ * Returns true if the given coordinate pair is inside the content
+ * are of the given swimlane.
+ * 
+ * Parameters:
+ * 
+ * swimlane - <mxCell> that specifies the swimlane.
+ * x - X-coordinate of the mouse event.
+ * y - Y-coordinate of the mouse event.
+ */
+mxGraph.prototype.hitsSwimlaneContent = function(swimlane, x, y)
+{
+	var state = this.getView().getState(swimlane);
+	var size = this.getStartSize(swimlane);
+	
+	if (state != null)
+	{
+		var scale = this.getView().getScale();
+		x -= state.x;
+		y -= state.y;
+		
+		if (size.width > 0 && x > 0 && x > size.width * scale)
+		{
+			return true;
+		}
+		else if (size.height > 0 && y > 0 && y > size.height * scale)
+		{
+			return true;
+		}
+	}
+	
+	return false;
+};
+
+/**
+ * Function: getChildVertices
+ * 
+ * Returns the visible child vertices of the given parent.
+ * 
+ * Parameters:
+ * 
+ * parent - <mxCell> whose children should be returned.
+ */
+mxGraph.prototype.getChildVertices = function(parent)
+{
+	return this.getChildCells(parent, true, false);
+};
+	
+/**
+ * Function: getChildEdges
+ * 
+ * Returns the visible child edges of the given parent.
+ * 
+ * Parameters:
+ * 
+ * parent - <mxCell> whose child vertices should be returned.
+ */
+mxGraph.prototype.getChildEdges = function(parent)
+{
+	return this.getChildCells(parent, false, true);
+};
+
+/**
+ * Function: getChildCells
+ * 
+ * Returns the visible child vertices or edges in the given parent. If
+ * vertices and edges is false, then all children are returned.
+ * 
+ * Parameters:
+ * 
+ * parent - <mxCell> whose children should be returned.
+ * vertices - Optional boolean that specifies if child vertices should
+ * be returned. Default is false.
+ * edges - Optional boolean that specifies if child edges should
+ * be returned. Default is false.
+ */
+mxGraph.prototype.getChildCells = function(parent, vertices, edges)
+{
+	parent = (parent != null) ? parent : this.getDefaultParent();
+	vertices = (vertices != null) ? vertices : false;
+	edges = (edges != null) ? edges : false;
+
+	var cells = this.model.getChildCells(parent, vertices, edges);
+	var result = [];
+
+	// Filters out the non-visible child cells
+	for (var i = 0; i < cells.length; i++)
+	{
+		if (this.isCellVisible(cells[i]))
+		{
+			result.push(cells[i]);
+		}
+	}
+
+	return result;
+};
+	
+/**
+ * Function: getConnections
+ * 
+ * Returns all visible edges connected to the given cell without loops.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose connections should be returned.
+ * parent - Optional parent of the opposite end for a connection to be
+ * returned.
+ */
+mxGraph.prototype.getConnections = function(cell, parent)
+{
+	return this.getEdges(cell, parent, true, true, false);
+};
+	
+/**
+ * Function: getIncomingEdges
+ * 
+ * Returns the visible incoming edges for the given cell. If the optional
+ * parent argument is specified, then only child edges of the given parent
+ * are returned.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose incoming edges should be returned.
+ * parent - Optional parent of the opposite end for an edge to be
+ * returned.
+ */
+mxGraph.prototype.getIncomingEdges = function(cell, parent)
+{
+	return this.getEdges(cell, parent, true, false, false);
+};
+	
+/**
+ * Function: getOutgoingEdges
+ * 
+ * Returns the visible outgoing edges for the given cell. If the optional
+ * parent argument is specified, then only child edges of the given parent
+ * are returned.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose outgoing edges should be returned.
+ * parent - Optional parent of the opposite end for an edge to be
+ * returned.
+ */
+mxGraph.prototype.getOutgoingEdges = function(cell, parent)
+{
+	return this.getEdges(cell, parent, false, true, false);
+};
+	
+/**
+ * Function: getEdges
+ * 
+ * Returns the incoming and/or outgoing edges for the given cell.
+ * If the optional parent argument is specified, then only edges are returned
+ * where the opposite is in the given parent cell. If at least one of incoming
+ * or outgoing is true, then loops are ignored, if both are false, then all
+ * edges connected to the given cell are returned including loops.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose edges should be returned.
+ * parent - Optional parent of the opposite end for an edge to be
+ * returned.
+ * incoming - Optional boolean that specifies if incoming edges should
+ * be included in the result. Default is true.
+ * outgoing - Optional boolean that specifies if outgoing edges should
+ * be included in the result. Default is true.
+ * includeLoops - Optional boolean that specifies if loops should be
+ * included in the result. Default is true.
+ * recurse - Optional boolean the specifies if the parent specified only 
+ * need be an ancestral parent, true, or the direct parent, false.
+ * Default is false
+ */
+mxGraph.prototype.getEdges = function(cell, parent, incoming, outgoing, includeLoops, recurse)
+{
+	incoming = (incoming != null) ? incoming : true;
+	outgoing = (outgoing != null) ? outgoing : true;
+	includeLoops = (includeLoops != null) ? includeLoops : true;
+	recurse = (recurse != null) ? recurse : false;
+	
+	var edges = [];
+	var isCollapsed = this.isCellCollapsed(cell);
+	var childCount = this.model.getChildCount(cell);
+
+	for (var i = 0; i < childCount; i++)
+	{
+		var child = this.model.getChildAt(cell, i);
+
+		if (isCollapsed || !this.isCellVisible(child))
+		{
+			edges = edges.concat(this.model.getEdges(child, incoming, outgoing));
+		}
+	}
+
+	edges = edges.concat(this.model.getEdges(cell, incoming, outgoing));
+	var result = [];
+	
+	for (var i = 0; i < edges.length; i++)
+	{
+		var state = this.view.getState(edges[i]);
+		
+		var source = (state != null) ? state.getVisibleTerminal(true) : this.view.getVisibleTerminal(edges[i], true);
+		var target = (state != null) ? state.getVisibleTerminal(false) : this.view.getVisibleTerminal(edges[i], false);
+
+		if ((includeLoops && source == target) || ((source != target) && ((incoming &&
+			target == cell && (parent == null || this.isValidAncestor(source, parent, recurse))) ||
+			(outgoing && source == cell && (parent == null ||
+					this.isValidAncestor(target, parent, recurse))))))
+		{
+			result.push(edges[i]);
+		}
+	}
+
+	return result;
+};
+
+/**
+ * Function: isValidAncestor
+ * 
+ * Returns whether or not the specified parent is a valid
+ * ancestor of the specified cell, either direct or indirectly
+ * based on whether ancestor recursion is enabled.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> the possible child cell
+ * parent - <mxCell> the possible parent cell
+ * recurse - boolean whether or not to recurse the child ancestors
+ */
+mxGraph.prototype.isValidAncestor = function(cell, parent, recurse)
+{
+	return (recurse ? this.model.isAncestor(parent, cell) : this.model
+			.getParent(cell) == parent);
+};
+
+/**
+ * Function: getOpposites
+ * 
+ * Returns all distinct visible opposite cells for the specified terminal
+ * on the given edges.
+ * 
+ * Parameters:
+ * 
+ * edges - Array of <mxCells> that contains the edges whose opposite
+ * terminals should be returned.
+ * terminal - Terminal that specifies the end whose opposite should be
+ * returned.
+ * source - Optional boolean that specifies if source terminals should be
+ * included in the result. Default is true.
+ * targets - Optional boolean that specifies if targer terminals should be
+ * included in the result. Default is true.
+ */
+mxGraph.prototype.getOpposites = function(edges, terminal, sources, targets)
+{
+	sources = (sources != null) ? sources : true;
+	targets = (targets != null) ? targets : true;
+	
+	var terminals = [];
+	
+	// Fast lookup to avoid duplicates in terminals array
+	var dict = new mxDictionary();
+	
+	if (edges != null)
+	{
+		for (var i = 0; i < edges.length; i++)
+		{
+			var state = this.view.getState(edges[i]);
+			
+			var source = (state != null) ? state.getVisibleTerminal(true) : this.view.getVisibleTerminal(edges[i], true);
+			var target = (state != null) ? state.getVisibleTerminal(false) : this.view.getVisibleTerminal(edges[i], false);
+			
+			// Checks if the terminal is the source of the edge and if the
+			// target should be stored in the result
+			if (source == terminal && target != null && target != terminal && targets)
+			{
+				if (!dict.get(target))
+				{
+					dict.put(target, true);
+					terminals.push(target);
+				}
+			}
+			
+			// Checks if the terminal is the taget of the edge and if the
+			// source should be stored in the result
+			else if (target == terminal && source != null && source != terminal && sources)
+			{
+				if (!dict.get(source))
+				{
+					dict.put(source, true);
+					terminals.push(source);
+				}
+			}
+		}
+	}
+	
+	return terminals;
+};
+
+/**
+ * Function: getEdgesBetween
+ * 
+ * Returns the edges between the given source and target. This takes into
+ * account collapsed and invisible cells and returns the connected edges
+ * as displayed on the screen.
+ * 
+ * Parameters:
+ * 
+ * source -
+ * target -
+ * directed -
+ */
+mxGraph.prototype.getEdgesBetween = function(source, target, directed)
+{
+	directed = (directed != null) ? directed : false;
+	var edges = this.getEdges(source);
+	var result = [];
+
+	// Checks if the edge is connected to the correct
+	// cell and returns the first match
+	for (var i = 0; i < edges.length; i++)
+	{
+		var state = this.view.getState(edges[i]);
+		
+		var src = (state != null) ? state.getVisibleTerminal(true) : this.view.getVisibleTerminal(edges[i], true);
+		var trg = (state != null) ? state.getVisibleTerminal(false) : this.view.getVisibleTerminal(edges[i], false);
+
+		if ((src == source && trg == target) || (!directed && src == target && trg == source))
+		{
+			result.push(edges[i]);
+		}
+	}
+
+	return result;
+};
+
+/**
+ * Function: getPointForEvent
+ * 
+ * Returns an <mxPoint> representing the given event in the unscaled,
+ * non-translated coordinate space of <container> and applies the grid.
+ * 
+ * Parameters:
+ * 
+ * evt - Mousevent that contains the mouse pointer location.
+ * addOffset - Optional boolean that specifies if the position should be
+ * offset by half of the <gridSize>. Default is true.
+ */
+ mxGraph.prototype.getPointForEvent = function(evt, addOffset)
+ {
+	var p = mxUtils.convertPoint(this.container,
+		mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+	
+	var s = this.view.scale;
+	var tr = this.view.translate;
+	var off = (addOffset != false) ? this.gridSize / 2 : 0;
+	
+	p.x = this.snap(p.x / s - tr.x - off);
+	p.y = this.snap(p.y / s - tr.y - off);
+	
+	return p;
+ };
+
+/**
+ * Function: getCells
+ * 
+ * Returns the child vertices and edges of the given parent that are contained
+ * in the given rectangle. The result is added to the optional result array,
+ * which is returned. If no result array is specified then a new array is
+ * created and returned.
+ * 
+ * Parameters:
+ * 
+ * x - X-coordinate of the rectangle.
+ * y - Y-coordinate of the rectangle.
+ * width - Width of the rectangle.
+ * height - Height of the rectangle.
+ * parent - <mxCell> that should be used as the root of the recursion.
+ * Default is current root of the view or the root of the model.
+ * result - Optional array to store the result in.
+ */
+mxGraph.prototype.getCells = function(x, y, width, height, parent, result)
+{
+	result = (result != null) ? result : [];
+	
+	if (width > 0 || height > 0)
+	{
+		var model = this.getModel();
+		var right = x + width;
+		var bottom = y + height;
+
+		if (parent == null)
+		{
+			parent = this.getCurrentRoot();
+			
+			if (parent == null)
+			{
+				parent = model.getRoot();
+			}
+		}
+		
+		if (parent != null)
+		{
+			var childCount = model.getChildCount(parent);
+			
+			for (var i = 0; i < childCount; i++)
+			{
+				var cell = model.getChildAt(parent, i);
+				var state = this.view.getState(cell);
+				
+				if (state != null && this.isCellVisible(cell))
+				{
+					var deg = mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION) || 0;
+					var box = state;
+					
+					if (deg != 0)
+					{
+						box = mxUtils.getBoundingBox(box, deg);
+					}
+					
+					if ((model.isEdge(cell) || model.isVertex(cell)) &&
+						box.x >= x && box.y + box.height <= bottom &&
+						box.y >= y && box.x + box.width <= right)
+					{
+						result.push(cell);
+					}
+					else
+					{
+						this.getCells(x, y, width, height, cell, result);
+					}
+				}
+			}
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: getCellsBeyond
+ * 
+ * Returns the children of the given parent that are contained in the
+ * halfpane from the given point (x0, y0) rightwards or downwards
+ * depending on rightHalfpane and bottomHalfpane.
+ * 
+ * Parameters:
+ * 
+ * x0 - X-coordinate of the origin.
+ * y0 - Y-coordinate of the origin.
+ * parent - Optional <mxCell> whose children should be checked. Default is
+ * <defaultParent>.
+ * rightHalfpane - Boolean indicating if the cells in the right halfpane
+ * from the origin should be returned.
+ * bottomHalfpane - Boolean indicating if the cells in the bottom halfpane
+ * from the origin should be returned.
+ */
+mxGraph.prototype.getCellsBeyond = function(x0, y0, parent, rightHalfpane, bottomHalfpane)
+{
+	var result = [];
+	
+	if (rightHalfpane || bottomHalfpane)
+	{
+		if (parent == null)
+		{
+			parent = this.getDefaultParent();
+		}
+		
+		if (parent != null)
+		{
+			var childCount = this.model.getChildCount(parent);
+			
+			for (var i = 0; i < childCount; i++)
+			{
+				var child = this.model.getChildAt(parent, i);
+				var state = this.view.getState(child);
+				
+				if (this.isCellVisible(child) && state != null)
+				{
+					if ((!rightHalfpane || state.x >= x0) &&
+						(!bottomHalfpane || state.y >= y0))
+					{
+						result.push(child);
+					}
+				}
+			}
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: findTreeRoots
+ * 
+ * Returns all children in the given parent which do not have incoming
+ * edges. If the result is empty then the with the greatest difference
+ * between incoming and outgoing edges is returned.
+ * 
+ * Parameters:
+ * 
+ * parent - <mxCell> whose children should be checked.
+ * isolate - Optional boolean that specifies if edges should be ignored if
+ * the opposite end is not a child of the given parent cell. Default is
+ * false.
+ * invert - Optional boolean that specifies if outgoing or incoming edges
+ * should be counted for a tree root. If false then outgoing edges will be
+ * counted. Default is false.
+ */
+mxGraph.prototype.findTreeRoots = function(parent, isolate, invert)
+{
+	isolate = (isolate != null) ? isolate : false;
+	invert = (invert != null) ? invert : false;
+	var roots = [];
+	
+	if (parent != null)
+	{
+		var model = this.getModel();
+		var childCount = model.getChildCount(parent);
+		var best = null;
+		var maxDiff = 0;
+		
+		for (var i=0; i<childCount; i++)
+		{
+			var cell = model.getChildAt(parent, i);
+			
+			if (this.model.isVertex(cell) && this.isCellVisible(cell))
+			{
+				var conns = this.getConnections(cell, (isolate) ? parent : null);
+				var fanOut = 0;
+				var fanIn = 0;
+				
+				for (var j = 0; j < conns.length; j++)
+				{
+					var src = this.view.getVisibleTerminal(conns[j], true);
+
+                    if (src == cell)
+                    {
+                        fanOut++;
+                    }
+                    else
+                    {
+                        fanIn++;
+                    }
+				}
+				
+				if ((invert && fanOut == 0 && fanIn > 0) ||
+					(!invert && fanIn == 0 && fanOut > 0))
+				{
+					roots.push(cell);
+				}
+				
+				var diff = (invert) ? fanIn - fanOut : fanOut - fanIn;
+				
+				if (diff > maxDiff)
+				{
+					maxDiff = diff;
+					best = cell;
+				}
+			}
+		}
+		
+		if (roots.length == 0 && best != null)
+		{
+			roots.push(best);
+		}
+	}
+	
+	return roots;
+};
+
+/**
+ * Function: traverse
+ * 
+ * Traverses the (directed) graph invoking the given function for each
+ * visited vertex and edge. The function is invoked with the current vertex
+ * and the incoming edge as a parameter. This implementation makes sure
+ * each vertex is only visited once. The function may return false if the
+ * traversal should stop at the given vertex.
+ * 
+ * Example:
+ * 
+ * (code)
+ * mxLog.show();
+ * var cell = graph.getSelectionCell();
+ * graph.traverse(cell, false, function(vertex, edge)
+ * {
+ *   mxLog.debug(graph.getLabel(vertex));
+ * });
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * vertex - <mxCell> that represents the vertex where the traversal starts.
+ * directed - Optional boolean indicating if edges should only be traversed
+ * from source to target. Default is true.
+ * func - Visitor function that takes the current vertex and the incoming
+ * edge as arguments. The traversal stops if the function returns false.
+ * edge - Optional <mxCell> that represents the incoming edge. This is
+ * null for the first step of the traversal.
+ * visited - Optional <mxDictionary> from cells to true for the visited cells.
+ * inverse - Optional boolean to traverse in inverse direction. Default is false.
+ * This is ignored if directed is false.
+ */
+mxGraph.prototype.traverse = function(vertex, directed, func, edge, visited, inverse)
+{
+	if (func != null && vertex != null)
+	{
+		directed = (directed != null) ? directed : true;
+		inverse = (inverse != null) ? inverse : false;
+		visited = visited || new mxDictionary();
+		
+		if (!visited.get(vertex))
+		{
+			visited.put(vertex, true);
+			var result = func(vertex, edge);
+			
+			if (result == null || result)
+			{
+				var edgeCount = this.model.getEdgeCount(vertex);
+				
+				if (edgeCount > 0)
+				{
+					for (var i = 0; i < edgeCount; i++)
+					{
+						var e = this.model.getEdgeAt(vertex, i);
+						var isSource = this.model.getTerminal(e, true) == vertex;
+						
+						if (!directed || (!inverse == isSource))
+						{
+							var next = this.model.getTerminal(e, !isSource);
+							this.traverse(next, directed, func, e, visited, inverse);
+						}
+					}
+				}
+			}
+		}
+	}
+};
+
+/**
+ * Group: Selection
+ */
+
+/**
+ * Function: isCellSelected
+ * 
+ * Returns true if the given cell is selected.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> for which the selection state should be returned.
+ */
+mxGraph.prototype.isCellSelected = function(cell)
+{
+	return this.getSelectionModel().isSelected(cell);
+};
+
+/**
+ * Function: isSelectionEmpty
+ * 
+ * Returns true if the selection is empty.
+ */
+mxGraph.prototype.isSelectionEmpty = function()
+{
+	return this.getSelectionModel().isEmpty();
+};
+
+/**
+ * Function: clearSelection
+ * 
+ * Clears the selection using <mxGraphSelectionModel.clear>.
+ */
+mxGraph.prototype.clearSelection = function()
+{
+	return this.getSelectionModel().clear();
+};
+
+/**
+ * Function: getSelectionCount
+ * 
+ * Returns the number of selected cells.
+ */
+mxGraph.prototype.getSelectionCount = function()
+{
+	return this.getSelectionModel().cells.length;
+};
+	
+/**
+ * Function: getSelectionCell
+ * 
+ * Returns the first cell from the array of selected <mxCells>.
+ */
+mxGraph.prototype.getSelectionCell = function()
+{
+	return this.getSelectionModel().cells[0];
+};
+
+/**
+ * Function: getSelectionCells
+ * 
+ * Returns the array of selected <mxCells>.
+ */
+mxGraph.prototype.getSelectionCells = function()
+{
+	return this.getSelectionModel().cells.slice();
+};
+
+/**
+ * Function: setSelectionCell
+ * 
+ * Sets the selection cell.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to be selected.
+ */
+mxGraph.prototype.setSelectionCell = function(cell)
+{
+	this.getSelectionModel().setCell(cell);
+};
+
+/**
+ * Function: setSelectionCells
+ * 
+ * Sets the selection cell.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> to be selected.
+ */
+mxGraph.prototype.setSelectionCells = function(cells)
+{
+	this.getSelectionModel().setCells(cells);
+};
+
+/**
+ * Function: addSelectionCell
+ * 
+ * Adds the given cell to the selection.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to be add to the selection.
+ */
+mxGraph.prototype.addSelectionCell = function(cell)
+{
+	this.getSelectionModel().addCell(cell);
+};
+
+/**
+ * Function: addSelectionCells
+ * 
+ * Adds the given cells to the selection.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> to be added to the selection.
+ */
+mxGraph.prototype.addSelectionCells = function(cells)
+{
+	this.getSelectionModel().addCells(cells);
+};
+
+/**
+ * Function: removeSelectionCell
+ * 
+ * Removes the given cell from the selection.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to be removed from the selection.
+ */
+mxGraph.prototype.removeSelectionCell = function(cell)
+{
+	this.getSelectionModel().removeCell(cell);
+};
+
+/**
+ * Function: removeSelectionCells
+ * 
+ * Removes the given cells from the selection.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> to be removed from the selection.
+ */
+mxGraph.prototype.removeSelectionCells = function(cells)
+{
+	this.getSelectionModel().removeCells(cells);
+};
+
+/**
+ * Function: selectRegion
+ * 
+ * Selects and returns the cells inside the given rectangle for the
+ * specified event.
+ * 
+ * Parameters:
+ * 
+ * rect - <mxRectangle> that represents the region to be selected.
+ * evt - Mouseevent that triggered the selection.
+ */
+mxGraph.prototype.selectRegion = function(rect, evt)
+{
+	var cells = this.getCells(rect.x, rect.y, rect.width, rect.height);
+	this.selectCellsForEvent(cells, evt);
+	
+	return cells;
+};
+
+/**
+ * Function: selectNextCell
+ * 
+ * Selects the next cell.
+ */
+mxGraph.prototype.selectNextCell = function()
+{
+	this.selectCell(true);
+};
+
+/**
+ * Function: selectPreviousCell
+ * 
+ * Selects the previous cell.
+ */
+mxGraph.prototype.selectPreviousCell = function()
+{
+	this.selectCell();
+};
+
+/**
+ * Function: selectParentCell
+ * 
+ * Selects the parent cell.
+ */
+mxGraph.prototype.selectParentCell = function()
+{
+	this.selectCell(false, true);
+};
+
+/**
+ * Function: selectChildCell
+ * 
+ * Selects the first child cell.
+ */
+mxGraph.prototype.selectChildCell = function()
+{
+	this.selectCell(false, false, true);
+};
+
+/**
+ * Function: selectCell
+ * 
+ * Selects the next, parent, first child or previous cell, if all arguments
+ * are false.
+ * 
+ * Parameters:
+ * 
+ * isNext - Boolean indicating if the next cell should be selected.
+ * isParent - Boolean indicating if the parent cell should be selected.
+ * isChild - Boolean indicating if the first child cell should be selected.
+ */
+mxGraph.prototype.selectCell = function(isNext, isParent, isChild)
+{
+	var sel = this.selectionModel;
+	var cell = (sel.cells.length > 0) ? sel.cells[0] : null;
+	
+	if (sel.cells.length > 1)
+	{
+		sel.clear();
+	}
+	
+	var parent = (cell != null) ?
+		this.model.getParent(cell) :
+		this.getDefaultParent();
+	
+	var childCount = this.model.getChildCount(parent);
+	
+	if (cell == null && childCount > 0)
+	{
+		var child = this.model.getChildAt(parent, 0);
+		this.setSelectionCell(child);
+	}
+	else if ((cell == null || isParent) &&
+		this.view.getState(parent) != null &&
+		this.model.getGeometry(parent) != null)
+	{
+		if (this.getCurrentRoot() != parent)
+		{
+			this.setSelectionCell(parent);
+		}
+	}
+	else if (cell != null && isChild)
+	{
+		var tmp = this.model.getChildCount(cell);
+		
+		if (tmp > 0)
+		{
+			var child = this.model.getChildAt(cell, 0);
+			this.setSelectionCell(child);
+		}
+	}
+	else if (childCount > 0)
+	{
+		var i = parent.getIndex(cell);
+		
+		if (isNext)
+		{
+			i++;
+			var child = this.model.getChildAt(parent, i % childCount);
+			this.setSelectionCell(child);
+		}
+		else
+		{
+			i--;
+			var index =  (i < 0) ? childCount - 1 : i;
+			var child = this.model.getChildAt(parent, index);
+			this.setSelectionCell(child);
+		}
+	}
+};
+
+/**
+ * Function: selectAll
+ * 
+ * Selects all children of the given parent cell or the children of the
+ * default parent if no parent is specified. To select leaf vertices and/or
+ * edges use <selectCells>.
+ * 
+ * Parameters:
+ * 
+ * parent - Optional <mxCell> whose children should be selected.
+ * Default is <defaultParent>.
+ * descendants - Optional boolean specifying whether all descendants should be
+ * selected. Default is false.
+ */
+mxGraph.prototype.selectAll = function(parent, descendants)
+{
+	parent = parent || this.getDefaultParent();
+	
+	var cells = (descendants) ? this.model.filterDescendants(function(cell)
+	{
+		return cell != parent;
+	}, parent) : this.model.getChildren(parent);
+	
+	if (cells != null)
+	{
+		this.setSelectionCells(cells);
+	}
+};
+
+/**
+ * Function: selectVertices
+ * 
+ * Select all vertices inside the given parent or the default parent.
+ */
+mxGraph.prototype.selectVertices = function(parent)
+{
+	this.selectCells(true, false, parent);
+};
+
+/**
+ * Function: selectVertices
+ * 
+ * Select all vertices inside the given parent or the default parent.
+ */
+mxGraph.prototype.selectEdges = function(parent)
+{
+	this.selectCells(false, true, parent);
+};
+
+/**
+ * Function: selectCells
+ * 
+ * Selects all vertices and/or edges depending on the given boolean
+ * arguments recursively, starting at the given parent or the default
+ * parent if no parent is specified. Use <selectAll> to select all cells.
+ * For vertices, only cells with no children are selected.
+ * 
+ * Parameters:
+ * 
+ * vertices - Boolean indicating if vertices should be selected.
+ * edges - Boolean indicating if edges should be selected.
+ * parent - Optional <mxCell> that acts as the root of the recursion.
+ * Default is <defaultParent>.
+ */
+mxGraph.prototype.selectCells = function(vertices, edges, parent)
+{
+	parent = parent || this.getDefaultParent();
+	
+	var filter = mxUtils.bind(this, function(cell)
+	{
+		return this.view.getState(cell) != null &&
+			((this.model.getChildCount(cell) == 0 && this.model.isVertex(cell) && vertices
+			&& !this.model.isEdge(this.model.getParent(cell))) ||
+			(this.model.isEdge(cell) && edges));
+	});
+	
+	var cells = this.model.filterDescendants(filter, parent);
+	this.setSelectionCells(cells);
+};
+
+/**
+ * Function: selectCellForEvent
+ * 
+ * Selects the given cell by either adding it to the selection or
+ * replacing the selection depending on whether the given mouse event is a
+ * toggle event.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to be selected.
+ * evt - Optional mouseevent that triggered the selection.
+ */
+mxGraph.prototype.selectCellForEvent = function(cell, evt)
+{
+	var isSelected = this.isCellSelected(cell);
+	
+	if (this.isToggleEvent(evt))
+	{
+		if (isSelected)
+		{
+			this.removeSelectionCell(cell);
+		}
+		else
+		{
+			this.addSelectionCell(cell);
+		}
+	}
+	else if (!isSelected || this.getSelectionCount() != 1)
+	{
+		this.setSelectionCell(cell);
+	}
+};
+
+/**
+ * Function: selectCellsForEvent
+ * 
+ * Selects the given cells by either adding them to the selection or
+ * replacing the selection depending on whether the given mouse event is a
+ * toggle event.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> to be selected.
+ * evt - Optional mouseevent that triggered the selection.
+ */
+mxGraph.prototype.selectCellsForEvent = function(cells, evt)
+{
+	if (this.isToggleEvent(evt))
+	{
+		this.addSelectionCells(cells);
+	}
+	else
+	{
+		this.setSelectionCells(cells);
+	}
+};
+
+/**
+ * Group: Selection state
+ */
+
+/**
+ * Function: createHandler
+ * 
+ * Creates a new handler for the given cell state. This implementation
+ * returns a new <mxEdgeHandler> of the corresponding cell is an edge,
+ * otherwise it returns an <mxVertexHandler>.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose handler should be created.
+ */
+mxGraph.prototype.createHandler = function(state)
+{
+	var result = null;
+	
+	if (state != null)
+	{
+		if (this.model.isEdge(state.cell))
+		{
+			var source = state.getVisibleTerminalState(true);
+			var target = state.getVisibleTerminalState(false);
+			var geo = this.getCellGeometry(state.cell);
+			
+			var edgeStyle = this.view.getEdgeStyle(state, (geo != null) ? geo.points : null, source, target);
+			result = this.createEdgeHandler(state, edgeStyle);
+		}
+		else
+		{
+			result = this.createVertexHandler(state);
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: createVertexHandler
+ * 
+ * Hooks to create a new <mxVertexHandler> for the given <mxCellState>.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> to create the handler for.
+ */
+mxGraph.prototype.createVertexHandler = function(state)
+{
+	return new mxVertexHandler(state);
+};
+
+/**
+ * Function: createEdgeHandler
+ * 
+ * Hooks to create a new <mxEdgeHandler> for the given <mxCellState>.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> to create the handler for.
+ */
+mxGraph.prototype.createEdgeHandler = function(state, edgeStyle)
+{
+	var result = null;
+	
+	if (edgeStyle == mxEdgeStyle.Loop ||
+		edgeStyle == mxEdgeStyle.ElbowConnector ||
+		edgeStyle == mxEdgeStyle.SideToSide ||
+		edgeStyle == mxEdgeStyle.TopToBottom)
+	{
+		result = this.createElbowEdgeHandler(state);
+	}
+	else if (edgeStyle == mxEdgeStyle.SegmentConnector || 
+			edgeStyle == mxEdgeStyle.OrthConnector)
+	{
+		result = this.createEdgeSegmentHandler(state);
+	}
+	else
+	{
+		result = new mxEdgeHandler(state);
+	}
+	
+	return result;
+};
+
+/**
+ * Function: createEdgeSegmentHandler
+ * 
+ * Hooks to create a new <mxEdgeSegmentHandler> for the given <mxCellState>.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> to create the handler for.
+ */
+mxGraph.prototype.createEdgeSegmentHandler = function(state)
+{
+	return new mxEdgeSegmentHandler(state);
+};
+
+/**
+ * Function: createElbowEdgeHandler
+ * 
+ * Hooks to create a new <mxElbowEdgeHandler> for the given <mxCellState>.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> to create the handler for.
+ */
+mxGraph.prototype.createElbowEdgeHandler = function(state)
+{
+	return new mxElbowEdgeHandler(state);
+};
+
+/**
+ * Group: Graph events
+ */
+
+/**
+ * Function: addMouseListener
+ * 
+ * Adds a listener to the graph event dispatch loop. The listener
+ * must implement the mouseDown, mouseMove and mouseUp methods
+ * as shown in the <mxMouseEvent> class.
+ * 
+ * Parameters:
+ * 
+ * listener - Listener to be added to the graph event listeners.
+ */
+mxGraph.prototype.addMouseListener = function(listener)
+{
+	if (this.mouseListeners == null)
+	{
+		this.mouseListeners = [];
+	}
+	
+	this.mouseListeners.push(listener);
+};
+
+/**
+ * Function: removeMouseListener
+ * 
+ * Removes the specified graph listener.
+ * 
+ * Parameters:
+ * 
+ * listener - Listener to be removed from the graph event listeners.
+ */
+mxGraph.prototype.removeMouseListener = function(listener)
+{
+	if (this.mouseListeners != null)
+	{
+		for (var i = 0; i < this.mouseListeners.length; i++)
+		{
+			if (this.mouseListeners[i] == listener)
+			{
+				this.mouseListeners.splice(i, 1);
+				break;
+			}
+		}
+	}
+};
+
+/**
+ * Function: updateMouseEvent
+ * 
+ * Sets the graphX and graphY properties if the given <mxMouseEvent> if
+ * required and returned the event.
+ * 
+ * Parameters:
+ * 
+ * me - <mxMouseEvent> to be updated.
+ * evtName - Name of the mouse event.
+ */
+mxGraph.prototype.updateMouseEvent = function(me, evtName)
+{
+	if (me.graphX == null || me.graphY == null)
+	{
+		var pt = mxUtils.convertPoint(this.container, me.getX(), me.getY());
+		
+		me.graphX = pt.x - this.panDx;
+		me.graphY = pt.y - this.panDy;
+		
+		// Searches for rectangles using method if native hit detection is disabled on shape
+		if (me.getCell() == null && this.isMouseDown && evtName == mxEvent.MOUSE_MOVE)
+		{
+			me.state = this.view.getState(this.getCellAt(pt.x, pt.y, null, null, null, function(state)
+			{
+				return state.shape == null || state.shape.paintBackground != mxRectangleShape.prototype.paintBackground ||
+					mxUtils.getValue(state.style, mxConstants.STYLE_POINTER_EVENTS, '1') == '1' ||
+					(state.shape.fill != null && state.shape.fill != mxConstants.NONE);
+			}));
+		}
+	}
+	
+	return me;
+};
+
+/**
+ * Function: getStateForEvent
+ * 
+ * Returns the state for the given touch event.
+ */
+mxGraph.prototype.getStateForTouchEvent = function(evt)
+{
+	var x = mxEvent.getClientX(evt);
+	var y = mxEvent.getClientY(evt);
+	
+	// Dispatches the drop event to the graph which
+	// consumes and executes the source function
+	var pt = mxUtils.convertPoint(this.container, x, y);
+
+	return this.view.getState(this.getCellAt(pt.x, pt.y));
+};
+
+/**
+ * Function: isEventIgnored
+ * 
+ * Returns true if the event should be ignored in <fireMouseEvent>.
+ */
+mxGraph.prototype.isEventIgnored = function(evtName, me, sender)
+{
+	var mouseEvent = mxEvent.isMouseEvent(me.getEvent());
+	var result = false;
+
+	// Drops events that are fired more than once
+	if (me.getEvent() == this.lastEvent)
+	{
+		result = true;
+	}
+	else
+	{
+		this.lastEvent = me.getEvent();
+	}
+
+	// Installs event listeners to capture the complete gesture from the event source
+	// for non-MS touch events as a workaround for all events for the same geture being
+	// fired from the event source even if that was removed from the DOM.
+	if (this.eventSource != null && evtName != mxEvent.MOUSE_MOVE)
+	{
+		mxEvent.removeGestureListeners(this.eventSource, null, this.mouseMoveRedirect, this.mouseUpRedirect);
+		this.mouseMoveRedirect = null;
+		this.mouseUpRedirect = null;
+		this.eventSource = null;
+	}
+	else if (this.eventSource != null && me.getSource() != this.eventSource)
+	{
+		result = true;
+	}
+	else if (mxClient.IS_TOUCH && evtName == mxEvent.MOUSE_DOWN && !mouseEvent && !mxEvent.isPenEvent(me.getEvent()))
+	{
+		this.eventSource = me.getSource();
+
+		this.mouseMoveRedirect = mxUtils.bind(this, function(evt)
+		{
+			this.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, this.getStateForTouchEvent(evt)));
+		});
+		this.mouseUpRedirect = mxUtils.bind(this, function(evt)
+		{
+			this.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt, this.getStateForTouchEvent(evt)));
+		});
+		
+		mxEvent.addGestureListeners(this.eventSource, null, this.mouseMoveRedirect, this.mouseUpRedirect);
+	}
+
+	// Factored out the workarounds for FF to make it easier to override/remove
+	// Note this method has side-effects!
+	if (this.isSyntheticEventIgnored(evtName, me, sender))
+	{
+		result = true;
+	}
+
+	// Never fires mouseUp/-Down for double clicks
+	if (!mxEvent.isPopupTrigger(this.lastEvent) && evtName != mxEvent.MOUSE_MOVE && this.lastEvent.detail == 2)
+	{
+		return true;
+	}
+	
+	// Filters out of sequence events or mixed event types during a gesture
+	if (evtName == mxEvent.MOUSE_UP && this.isMouseDown)
+	{
+		this.isMouseDown = false;
+	}
+	else if (evtName == mxEvent.MOUSE_DOWN && !this.isMouseDown)
+	{
+		this.isMouseDown = true;
+		this.isMouseTrigger = mouseEvent;
+	}
+	// Drops mouse events that are fired during touch gestures as a workaround for Webkit
+	// and mouse events that are not in sync with the current internal button state
+	else if (!result && (((!mxClient.IS_FF || evtName != mxEvent.MOUSE_MOVE) &&
+		this.isMouseDown && this.isMouseTrigger != mouseEvent) ||
+		(evtName == mxEvent.MOUSE_DOWN && this.isMouseDown) ||
+		(evtName == mxEvent.MOUSE_UP && !this.isMouseDown)))
+	{
+		result = true;
+	}
+	
+	if (!result && evtName == mxEvent.MOUSE_DOWN)
+	{
+		this.lastMouseX = me.getX();
+		this.lastMouseY = me.getY();
+	}
+
+	return result;
+};
+
+/**
+ * Function: isSyntheticEventIgnored
+ * 
+ * Hook for ignoring synthetic mouse events after touchend in Firefox.
+ */
+mxGraph.prototype.isSyntheticEventIgnored = function(evtName, me, sender)
+{
+	var result = false;
+	var mouseEvent = mxEvent.isMouseEvent(me.getEvent());
+	
+	// LATER: This does not cover all possible cases that can go wrong in FF
+	if (this.ignoreMouseEvents && mouseEvent && evtName != mxEvent.MOUSE_MOVE)
+	{
+		this.ignoreMouseEvents = evtName != mxEvent.MOUSE_UP;
+		result = true;
+	}
+	else if (mxClient.IS_FF && !mouseEvent && evtName == mxEvent.MOUSE_UP)
+	{
+		this.ignoreMouseEvents = true;
+	}
+	
+	return result;
+};
+
+/**
+ * Function: isEventSourceIgnored
+ * 
+ * Returns true if the event should be ignored in <fireMouseEvent>. This
+ * implementation returns true for select, option and input (if not of type
+ * checkbox, radio, button, submit or file) event sources if the event is not
+ * a mouse event or a left mouse button press event.
+ * 
+ * Parameters:
+ * 
+ * evtName - The name of the event.
+ * me - <mxMouseEvent> that should be ignored.
+ */
+mxGraph.prototype.isEventSourceIgnored = function(evtName, me)
+{
+	var source = me.getSource();
+	var name = (source.nodeName != null) ? source.nodeName.toLowerCase() : '';
+	var candidate = !mxEvent.isMouseEvent(me.getEvent()) || mxEvent.isLeftMouseButton(me.getEvent());
+	
+	return evtName == mxEvent.MOUSE_DOWN && candidate && (name == 'select' || name == 'option' ||
+		(name == 'input' && source.type != 'checkbox' && source.type != 'radio' &&
+		source.type != 'button' && source.type != 'submit' && source.type != 'file'));
+};
+
+/**
+ * Function: getEventState
+ * 
+ * Returns the <mxCellState> to be used when firing the mouse event for the
+ * given state. This implementation returns the given state.
+ * 
+ * Parameters:
+ * 
+ * <mxCellState> - State whose event source should be returned.
+ */
+mxGraph.prototype.getEventState = function(state)
+{
+	return state;
+};
+
+/**
+ * Function: fireMouseEvent
+ * 
+ * Dispatches the given event in the graph event dispatch loop. Possible
+ * event names are <mxEvent.MOUSE_DOWN>, <mxEvent.MOUSE_MOVE> and
+ * <mxEvent.MOUSE_UP>. All listeners are invoked for all events regardless
+ * of the consumed state of the event.
+ * 
+ * Parameters:
+ * 
+ * evtName - String that specifies the type of event to be dispatched.
+ * me - <mxMouseEvent> to be fired.
+ * sender - Optional sender argument. Default is this.
+ */
+mxGraph.prototype.fireMouseEvent = function(evtName, me, sender)
+{
+	if (this.isEventSourceIgnored(evtName, me))
+	{
+		if (this.tooltipHandler != null)
+		{
+			this.tooltipHandler.hide();
+		}
+		
+		return;
+	}
+	
+	if (sender == null)
+	{
+		sender = this;
+	}
+
+	// Updates the graph coordinates in the event
+	me = this.updateMouseEvent(me, evtName);
+
+	// Detects and processes double taps for touch-based devices which do not have native double click events
+	// or where detection of double click is not always possible (quirks, IE10+). Note that this can only handle
+	// double clicks on cells because the sequence of events in IE prevents detection on the background, it fires
+	// two mouse ups, one of which without a cell but no mousedown for the second click which means we cannot
+	// detect which mouseup(s) are part of the first click, ie we do not know when the first click ends.
+	if ((!this.nativeDblClickEnabled && !mxEvent.isPopupTrigger(me.getEvent())) || (this.doubleTapEnabled &&
+		mxClient.IS_TOUCH && (mxEvent.isTouchEvent(me.getEvent()) || mxEvent.isPenEvent(me.getEvent()))))
+	{
+		var currentTime = new Date().getTime();
+		
+		// NOTE: Second mouseDown for double click missing in quirks mode
+		if ((!mxClient.IS_QUIRKS && evtName == mxEvent.MOUSE_DOWN) || (mxClient.IS_QUIRKS && evtName == mxEvent.MOUSE_UP && !this.fireDoubleClick))
+		{
+			if (this.lastTouchEvent != null && this.lastTouchEvent != me.getEvent() &&
+				currentTime - this.lastTouchTime < this.doubleTapTimeout &&
+				Math.abs(this.lastTouchX - me.getX()) < this.doubleTapTolerance &&
+				Math.abs(this.lastTouchY - me.getY()) < this.doubleTapTolerance &&
+				this.doubleClickCounter < 2)
+			{
+				this.doubleClickCounter++;
+				var doubleClickFired = false;
+				
+				if (evtName == mxEvent.MOUSE_UP)
+				{
+					if (me.getCell() == this.lastTouchCell && this.lastTouchCell != null)
+					{
+						this.lastTouchTime = 0;
+						var cell = this.lastTouchCell;
+						this.lastTouchCell = null;
+
+						// Fires native dblclick event via event source
+						// NOTE: This fires two double click events on edges in quirks mode. While
+						// trying to fix this, we realized that nativeDoubleClick can be disabled for
+						// quirks and IE10+ (or we didn't find the case mentioned above where it
+						// would not work), ie. all double clicks seem to be working without this.
+						if (mxClient.IS_QUIRKS)
+						{
+							me.getSource().fireEvent('ondblclick');
+						}
+						
+						this.dblClick(me.getEvent(), cell);
+						doubleClickFired = true;
+					}
+				}
+				else
+				{
+					this.fireDoubleClick = true;
+					this.lastTouchTime = 0;
+				}
+
+				// Do not ignore mouse up in quirks in this case
+				if (!mxClient.IS_QUIRKS || doubleClickFired)
+				{
+					mxEvent.consume(me.getEvent());
+					return;
+				}
+			}
+			else if (this.lastTouchEvent == null || this.lastTouchEvent != me.getEvent())
+			{
+				this.lastTouchCell = me.getCell();
+				this.lastTouchX = me.getX();
+				this.lastTouchY = me.getY();
+				this.lastTouchTime = currentTime;
+				this.lastTouchEvent = me.getEvent();
+				this.doubleClickCounter = 0;
+			}
+		}
+		else if ((this.isMouseDown || evtName == mxEvent.MOUSE_UP) && this.fireDoubleClick)
+		{
+			this.fireDoubleClick = false;
+			var cell = this.lastTouchCell;
+			this.lastTouchCell = null;
+			this.isMouseDown = false;
+			
+			// Workaround for Chrome/Safari not firing native double click events for double touch on background
+			var valid = (cell != null) || ((mxEvent.isTouchEvent(me.getEvent()) || mxEvent.isPenEvent(me.getEvent())) &&
+				(mxClient.IS_GC || mxClient.IS_SF));
+			
+			if (valid && Math.abs(this.lastTouchX - me.getX()) < this.doubleTapTolerance &&
+				Math.abs(this.lastTouchY - me.getY()) < this.doubleTapTolerance)
+			{
+				this.dblClick(me.getEvent(), cell);
+			}
+			else
+			{
+				mxEvent.consume(me.getEvent());
+			}
+			
+			return;
+		}
+	}
+
+	if (!this.isEventIgnored(evtName, me, sender))
+	{
+		// Updates the event state via getEventState
+		me.state = this.getEventState(me.getState());
+		this.fireEvent(new mxEventObject(mxEvent.FIRE_MOUSE_EVENT, 'eventName', evtName, 'event', me));
+		
+		if ((mxClient.IS_OP || mxClient.IS_SF || mxClient.IS_GC || mxClient.IS_IE11 ||
+			(mxClient.IS_IE && mxClient.IS_SVG) || me.getEvent().target != this.container))
+		{
+			if (evtName == mxEvent.MOUSE_MOVE && this.isMouseDown && this.autoScroll && !mxEvent.isMultiTouchEvent(me.getEvent))
+			{
+				this.scrollPointToVisible(me.getGraphX(), me.getGraphY(), this.autoExtend);
+			}
+			else if (evtName == mxEvent.MOUSE_UP && this.ignoreScrollbars && this.translateToScrollPosition &&
+					(this.container.scrollLeft != 0 || this.container.scrollTop != 0))
+			{
+				var s = this.view.scale;
+				var tr = this.view.translate;
+				this.view.setTranslate(tr.x - this.container.scrollLeft / s, tr.y - this.container.scrollTop / s);
+				this.container.scrollLeft = 0;
+				this.container.scrollTop = 0;
+			}
+			
+			if (this.mouseListeners != null)
+			{
+				var args = [sender, me];
+	
+				// Does not change returnValue in Opera
+				if (!me.getEvent().preventDefault)
+				{
+					me.getEvent().returnValue = true;
+				}
+				
+				for (var i = 0; i < this.mouseListeners.length; i++)
+				{
+					var l = this.mouseListeners[i];
+					
+					if (evtName == mxEvent.MOUSE_DOWN)
+					{
+						l.mouseDown.apply(l, args);
+					}
+					else if (evtName == mxEvent.MOUSE_MOVE)
+					{
+						l.mouseMove.apply(l, args);
+					}
+					else if (evtName == mxEvent.MOUSE_UP)
+					{
+						l.mouseUp.apply(l, args);
+					}
+				}
+			}
+			
+			// Invokes the click handler
+			if (evtName == mxEvent.MOUSE_UP)
+			{
+				this.click(me);
+			}
+		}
+		
+		// Detects tapAndHold events using a timer
+		if ((mxEvent.isTouchEvent(me.getEvent()) || mxEvent.isPenEvent(me.getEvent())) &&
+			evtName == mxEvent.MOUSE_DOWN && this.tapAndHoldEnabled && !this.tapAndHoldInProgress)
+		{
+			this.tapAndHoldInProgress = true;
+			this.initialTouchX = me.getGraphX();
+			this.initialTouchY = me.getGraphY();
+			
+			var handler = function()
+			{
+				if (this.tapAndHoldValid)
+				{
+					this.tapAndHold(me);
+				}
+				
+				this.tapAndHoldInProgress = false;
+				this.tapAndHoldValid = false;
+			};
+			
+			if (this.tapAndHoldThread)
+			{
+				window.clearTimeout(this.tapAndHoldThread);
+			}
+	
+			this.tapAndHoldThread = window.setTimeout(mxUtils.bind(this, handler), this.tapAndHoldDelay);
+			this.tapAndHoldValid = true;
+		}
+		else if (evtName == mxEvent.MOUSE_UP)
+		{
+			this.tapAndHoldInProgress = false;
+			this.tapAndHoldValid = false;
+		}
+		else if (this.tapAndHoldValid)
+		{
+			this.tapAndHoldValid =
+				Math.abs(this.initialTouchX - me.getGraphX()) < this.tolerance &&
+				Math.abs(this.initialTouchY - me.getGraphY()) < this.tolerance;
+		}
+
+		// Stops editing for all events other than from cellEditor
+		if (evtName == mxEvent.MOUSE_DOWN && this.isEditing() && !this.cellEditor.isEventSource(me.getEvent()))
+		{
+			this.stopEditing(!this.isInvokesStopCellEditing());
+		}
+
+		this.consumeMouseEvent(evtName, me, sender);
+	}
+};
+
+/**
+ * Function: consumeMouseEvent
+ * 
+ * Consumes the given <mxMouseEvent> if it's a touchStart event.
+ */
+mxGraph.prototype.consumeMouseEvent = function(evtName, me, sender)
+{
+	// Workaround for duplicate click in Windows 8 with Chrome/FF/Opera with touch
+	if (evtName == mxEvent.MOUSE_DOWN && mxEvent.isTouchEvent(me.getEvent()))
+	{
+		me.consume(false);
+	}
+};
+
+/**
+ * Function: fireGestureEvent
+ * 
+ * Dispatches a <mxEvent.GESTURE> event. The following example will resize the
+ * cell under the mouse based on the scale property of the native touch event.
+ * 
+ * (code)
+ * graph.addListener(mxEvent.GESTURE, function(sender, eo)
+ * {
+ *   var evt = eo.getProperty('event');
+ *   var state = graph.view.getState(eo.getProperty('cell'));
+ *   
+ *   if (graph.isEnabled() && graph.isCellResizable(state.cell) && Math.abs(1 - evt.scale) > 0.2)
+ *   {
+ *     var scale = graph.view.scale;
+ *     var tr = graph.view.translate;
+ *     
+ *     var w = state.width * evt.scale;
+ *     var h = state.height * evt.scale;
+ *     var x = state.x - (w - state.width) / 2;
+ *     var y = state.y - (h - state.height) / 2;
+ *     
+ *     var bounds = new mxRectangle(graph.snap(x / scale) - tr.x,
+ *     		graph.snap(y / scale) - tr.y, graph.snap(w / scale), graph.snap(h / scale));
+ *     graph.resizeCell(state.cell, bounds);
+ *     eo.consume();
+ *   }
+ * });
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * evt - Gestureend event that represents the gesture.
+ * cell - Optional <mxCell> associated with the gesture.
+ */
+mxGraph.prototype.fireGestureEvent = function(evt, cell)
+{
+	// Resets double tap event handling when gestures take place
+	this.lastTouchTime = 0;
+	this.fireEvent(new mxEventObject(mxEvent.GESTURE, 'event', evt, 'cell', cell));
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the graph and all its resources.
+ */
+mxGraph.prototype.destroy = function()
+{
+	if (!this.destroyed)
+	{
+		this.destroyed = true;
+		
+		if (this.tooltipHandler != null)
+		{
+			this.tooltipHandler.destroy();
+		}
+		
+		if (this.selectionCellsHandler != null)
+		{
+			this.selectionCellsHandler.destroy();
+		}
+
+		if (this.panningHandler != null)
+		{
+			this.panningHandler.destroy();
+		}
+
+		if (this.popupMenuHandler != null)
+		{
+			this.popupMenuHandler.destroy();
+		}
+		
+		if (this.connectionHandler != null)
+		{
+			this.connectionHandler.destroy();
+		}
+		
+		if (this.graphHandler != null)
+		{
+			this.graphHandler.destroy();
+		}
+		
+		if (this.cellEditor != null)
+		{
+			this.cellEditor.destroy();
+		}
+		
+		if (this.view != null)
+		{
+			this.view.destroy();
+		}
+
+		if (this.model != null && this.graphModelChangeListener != null)
+		{
+			this.model.removeListener(this.graphModelChangeListener);
+			this.graphModelChangeListener = null;
+		}
+
+		this.container = null;
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/view/mxGraphSelectionModel.js b/airavata-kubernetes/web-console/src/assets/js/view/mxGraphSelectionModel.js
new file mode 100644
index 0000000..db55424
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/view/mxGraphSelectionModel.js
@@ -0,0 +1,436 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxGraphSelectionModel
+ *
+ * Implements the selection model for a graph. Here is a listener that handles
+ * all removed selection cells.
+ * 
+ * (code)
+ * graph.getSelectionModel().addListener(mxEvent.CHANGE, function(sender, evt)
+ * {
+ *   var cells = evt.getProperty('added');
+ *   
+ *   for (var i = 0; i < cells.length; i++)
+ *   {
+ *     // Handle cells[i]...
+ *   }
+ * });
+ * (end)
+ * 
+ * Event: mxEvent.UNDO
+ * 
+ * Fires after the selection was changed in <changeSelection>. The
+ * <code>edit</code> property contains the <mxUndoableEdit> which contains the
+ * <mxSelectionChange>.
+ * 
+ * Event: mxEvent.CHANGE
+ * 
+ * Fires after the selection changes by executing an <mxSelectionChange>. The
+ * <code>added</code> and <code>removed</code> properties contain arrays of
+ * cells that have been added to or removed from the selection, respectively.
+ * The names are inverted due to historic reasons. This cannot be changed.
+ * 
+ * Constructor: mxGraphSelectionModel
+ *
+ * Constructs a new graph selection model for the given <mxGraph>.
+ * 
+ * Parameters:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ */
+function mxGraphSelectionModel(graph)
+{
+	this.graph = graph;
+	this.cells = [];
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxGraphSelectionModel.prototype = new mxEventSource();
+mxGraphSelectionModel.prototype.constructor = mxGraphSelectionModel;
+
+/**
+ * Variable: doneResource
+ * 
+ * Specifies the resource key for the status message after a long operation.
+ * If the resource for this key does not exist then the value is used as
+ * the status message. Default is 'done'.
+ */
+mxGraphSelectionModel.prototype.doneResource = (mxClient.language != 'none') ? 'done' : '';
+
+/**
+ * Variable: updatingSelectionResource
+ *
+ * Specifies the resource key for the status message while the selection is
+ * being updated. If the resource for this key does not exist then the
+ * value is used as the status message. Default is 'updatingSelection'.
+ */
+mxGraphSelectionModel.prototype.updatingSelectionResource = (mxClient.language != 'none') ? 'updatingSelection' : '';
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxGraphSelectionModel.prototype.graph = null;
+
+/**
+ * Variable: singleSelection
+ *
+ * Specifies if only one selected item at a time is allowed.
+ * Default is false.
+ */
+mxGraphSelectionModel.prototype.singleSelection = false;
+
+/**
+ * Function: isSingleSelection
+ *
+ * Returns <singleSelection> as a boolean.
+ */
+mxGraphSelectionModel.prototype.isSingleSelection = function()
+{
+	return this.singleSelection;
+};
+
+/**
+ * Function: setSingleSelection
+ *
+ * Sets the <singleSelection> flag.
+ * 
+ * Parameters:
+ * 
+ * singleSelection - Boolean that specifies the new value for
+ * <singleSelection>.
+ */
+mxGraphSelectionModel.prototype.setSingleSelection = function(singleSelection)
+{
+	this.singleSelection = singleSelection;
+};
+
+/**
+ * Function: isSelected
+ *
+ * Returns true if the given <mxCell> is selected.
+ */
+mxGraphSelectionModel.prototype.isSelected = function(cell)
+{
+	if (cell != null)
+	{
+		return mxUtils.indexOf(this.cells, cell) >= 0;
+	}
+	
+	return false;
+};
+
+/**
+ * Function: isEmpty
+ *
+ * Returns true if no cells are currently selected.
+ */
+mxGraphSelectionModel.prototype.isEmpty = function()
+{
+	return this.cells.length == 0;
+};
+
+/**
+ * Function: clear
+ *
+ * Clears the selection and fires a <change> event if the selection was not
+ * empty.
+ */
+mxGraphSelectionModel.prototype.clear = function()
+{
+	this.changeSelection(null, this.cells);
+};
+
+/**
+ * Function: setCell
+ *
+ * Selects the specified <mxCell> using <setCells>.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to be selected.
+ */
+mxGraphSelectionModel.prototype.setCell = function(cell)
+{
+	if (cell != null)
+	{
+		this.setCells([cell]);
+	}
+};
+
+/**
+ * Function: setCells
+ *
+ * Selects the given array of <mxCells> and fires a <change> event.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> to be selected.
+ */
+mxGraphSelectionModel.prototype.setCells = function(cells)
+{
+	if (cells != null)
+	{
+		if (this.singleSelection)
+		{
+			cells = [this.getFirstSelectableCell(cells)];
+		}
+	
+		var tmp = [];
+		
+		for (var i = 0; i < cells.length; i++)
+		{
+			if (this.graph.isCellSelectable(cells[i]))
+			{
+				tmp.push(cells[i]);
+			}	
+		}
+
+		this.changeSelection(tmp, this.cells);
+	}
+};
+
+/**
+ * Function: getFirstSelectableCell
+ *
+ * Returns the first selectable cell in the given array of cells.
+ */
+mxGraphSelectionModel.prototype.getFirstSelectableCell = function(cells)
+{
+	if (cells != null)
+	{
+		for (var i = 0; i < cells.length; i++)
+		{
+			if (this.graph.isCellSelectable(cells[i]))
+			{
+				return cells[i];
+			}
+		}
+	}
+	
+	return null;
+};
+
+/**
+ * Function: addCell
+ * 
+ * Adds the given <mxCell> to the selection and fires a <select> event.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to add to the selection.
+ */
+mxGraphSelectionModel.prototype.addCell = function(cell)
+{
+	if (cell != null)
+	{
+		this.addCells([cell]);
+	}
+};
+
+/**
+ * Function: addCells
+ * 
+ * Adds the given array of <mxCells> to the selection and fires a <select>
+ * event.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> to add to the selection.
+ */
+mxGraphSelectionModel.prototype.addCells = function(cells)
+{
+	if (cells != null)
+	{
+		var remove = null;
+		
+		if (this.singleSelection)
+		{
+			remove = this.cells;
+			cells = [this.getFirstSelectableCell(cells)];
+		}
+
+		var tmp = [];
+		
+		for (var i = 0; i < cells.length; i++)
+		{
+			if (!this.isSelected(cells[i]) &&
+				this.graph.isCellSelectable(cells[i]))
+			{
+				tmp.push(cells[i]);
+			}	
+		}
+
+		this.changeSelection(tmp, remove);
+	}
+};
+
+/**
+ * Function: removeCell
+ *
+ * Removes the specified <mxCell> from the selection and fires a <select>
+ * event for the remaining cells.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to remove from the selection.
+ */
+mxGraphSelectionModel.prototype.removeCell = function(cell)
+{
+	if (cell != null)
+	{
+		this.removeCells([cell]);
+	}
+};
+
+/**
+ * Function: removeCells
+ */
+mxGraphSelectionModel.prototype.removeCells = function(cells)
+{
+	if (cells != null)
+	{
+		var tmp = [];
+		
+		for (var i = 0; i < cells.length; i++)
+		{
+			if (this.isSelected(cells[i]))
+			{
+				tmp.push(cells[i]);
+			}
+		}
+		
+		this.changeSelection(null, tmp);	
+	}
+};
+
+/**
+ * Function: changeSelection
+ *
+ * Inner callback to add the specified <mxCell> to the selection. No event
+ * is fired in this implementation.
+ * 
+ * Paramters:
+ * 
+ * cell - <mxCell> to add to the selection.
+ */
+mxGraphSelectionModel.prototype.changeSelection = function(added, removed)
+{
+	if ((added != null &&
+		added.length > 0 &&
+		added[0] != null) ||
+		(removed != null &&
+		removed.length > 0 &&
+		removed[0] != null))
+	{
+		var change = new mxSelectionChange(this, added, removed);
+		change.execute();
+		var edit = new mxUndoableEdit(this, false);
+		edit.add(change);
+		this.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit));
+	}
+};
+
+/**
+ * Function: cellAdded
+ *
+ * Inner callback to add the specified <mxCell> to the selection. No event
+ * is fired in this implementation.
+ * 
+ * Paramters:
+ * 
+ * cell - <mxCell> to add to the selection.
+ */
+mxGraphSelectionModel.prototype.cellAdded = function(cell)
+{
+	if (cell != null &&
+		!this.isSelected(cell))
+	{
+		this.cells.push(cell);
+	}
+};
+
+/**
+ * Function: cellRemoved
+ *
+ * Inner callback to remove the specified <mxCell> from the selection. No
+ * event is fired in this implementation.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to remove from the selection.
+ */
+mxGraphSelectionModel.prototype.cellRemoved = function(cell)
+{
+	if (cell != null)
+	{
+		var index = mxUtils.indexOf(this.cells, cell);
+		
+		if (index >= 0)
+		{
+			this.cells.splice(index, 1);
+		}
+	}
+};
+
+/**
+ * Class: mxSelectionChange
+ *
+ * Action to change the current root in a view.
+ *
+ * Constructor: mxCurrentRootChange
+ *
+ * Constructs a change of the current root in the given view.
+ */
+function mxSelectionChange(selectionModel, added, removed)
+{
+	this.selectionModel = selectionModel;
+	this.added = (added != null) ? added.slice() : null;
+	this.removed = (removed != null) ? removed.slice() : null;
+};
+
+/**
+ * Function: execute
+ *
+ * Changes the current root of the view.
+ */
+mxSelectionChange.prototype.execute = function()
+{
+	var t0 = mxLog.enter('mxSelectionChange.execute');
+	window.status = mxResources.get(
+		this.selectionModel.updatingSelectionResource) ||
+		this.selectionModel.updatingSelectionResource;
+
+	if (this.removed != null)
+	{
+		for (var i = 0; i < this.removed.length; i++)
+		{
+			this.selectionModel.cellRemoved(this.removed[i]);
+		}
+	}
+
+	if (this.added != null)
+	{
+		for (var i = 0; i < this.added.length; i++)
+		{
+			this.selectionModel.cellAdded(this.added[i]);
+		}
+	}
+	
+	var tmp = this.added;
+	this.added = this.removed;
+	this.removed = tmp;
+
+	window.status = mxResources.get(this.selectionModel.doneResource) ||
+		this.selectionModel.doneResource;
+	mxLog.leave('mxSelectionChange.execute', t0);
+	
+	this.selectionModel.fireEvent(new mxEventObject(mxEvent.CHANGE,
+			'added', this.added, 'removed', this.removed));
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/view/mxGraphView.js b/airavata-kubernetes/web-console/src/assets/js/view/mxGraphView.js
new file mode 100644
index 0000000..a81e5cd
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/view/mxGraphView.js
@@ -0,0 +1,3001 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxGraphView
+ *
+ * Extends <mxEventSource> to implement a view for a graph. This class is in
+ * charge of computing the absolute coordinates for the relative child
+ * geometries, the points for perimeters and edge styles and keeping them
+ * cached in <mxCellStates> for faster retrieval. The states are updated
+ * whenever the model or the view state (translate, scale) changes. The scale
+ * and translate are honoured in the bounds.
+ * 
+ * Event: mxEvent.UNDO
+ * 
+ * Fires after the root was changed in <setCurrentRoot>. The <code>edit</code>
+ * property contains the <mxUndoableEdit> which contains the
+ * <mxCurrentRootChange>.
+ * 
+ * Event: mxEvent.SCALE_AND_TRANSLATE
+ * 
+ * Fires after the scale and translate have been changed in <scaleAndTranslate>.
+ * The <code>scale</code>, <code>previousScale</code>, <code>translate</code>
+ * and <code>previousTranslate</code> properties contain the new and previous
+ * scale and translate, respectively.
+ * 
+ * Event: mxEvent.SCALE
+ * 
+ * Fires after the scale was changed in <setScale>. The <code>scale</code> and
+ * <code>previousScale</code> properties contain the new and previous scale.
+ * 
+ * Event: mxEvent.TRANSLATE
+ * 
+ * Fires after the translate was changed in <setTranslate>. The
+ * <code>translate</code> and <code>previousTranslate</code> properties contain
+ * the new and previous value for translate.
+ * 
+ * Event: mxEvent.DOWN and mxEvent.UP
+ * 
+ * Fire if the current root is changed by executing an <mxCurrentRootChange>.
+ * The event name depends on the location of the root in the cell hierarchy
+ * with respect to the current root. The <code>root</code> and
+ * <code>previous</code> properties contain the new and previous root,
+ * respectively.
+ * 
+ * Constructor: mxGraphView
+ *
+ * Constructs a new view for the given <mxGraph>.
+ * 
+ * Parameters:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ */
+function mxGraphView(graph)
+{
+	this.graph = graph;
+	this.translate = new mxPoint();
+	this.graphBounds = new mxRectangle();
+	this.states = new mxDictionary();
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxGraphView.prototype = new mxEventSource();
+mxGraphView.prototype.constructor = mxGraphView;
+
+/**
+ *
+ */
+mxGraphView.prototype.EMPTY_POINT = new mxPoint();
+
+/**
+ * Variable: doneResource
+ * 
+ * Specifies the resource key for the status message after a long operation.
+ * If the resource for this key does not exist then the value is used as
+ * the status message. Default is 'done'.
+ */
+mxGraphView.prototype.doneResource = (mxClient.language != 'none') ? 'done' : '';
+
+/**
+ * Function: updatingDocumentResource
+ *
+ * Specifies the resource key for the status message while the document is
+ * being updated. If the resource for this key does not exist then the
+ * value is used as the status message. Default is 'updatingDocument'.
+ */
+mxGraphView.prototype.updatingDocumentResource = (mxClient.language != 'none') ? 'updatingDocument' : '';
+
+/**
+ * Variable: allowEval
+ * 
+ * Specifies if string values in cell styles should be evaluated using
+ * <mxUtils.eval>. This will only be used if the string values can't be mapped
+ * to objects using <mxStyleRegistry>. Default is false. NOTE: Enabling this
+ * switch carries a possible security risk.
+ */
+mxGraphView.prototype.allowEval = false;
+
+/**
+ * Variable: captureDocumentGesture
+ * 
+ * Specifies if a gesture should be captured when it goes outside of the
+ * graph container. Default is true.
+ */
+mxGraphView.prototype.captureDocumentGesture = true;
+
+/**
+ * Variable: optimizeVmlReflows
+ * 
+ * Specifies if the <canvas> should be hidden while rendering in IE8 standards
+ * mode and quirks mode. This will significantly improve rendering performance.
+ * Default is true.
+ */
+mxGraphView.prototype.optimizeVmlReflows = true;
+
+/**
+ * Variable: rendering
+ * 
+ * Specifies if shapes should be created, updated and destroyed using the
+ * methods of <mxCellRenderer> in <graph>. Default is true.
+ */
+mxGraphView.prototype.rendering = true;
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxGraphView.prototype.graph = null;
+
+/**
+ * Variable: currentRoot
+ *
+ * <mxCell> that acts as the root of the displayed cell hierarchy.
+ */
+mxGraphView.prototype.currentRoot = null;
+
+/**
+ * Variable: graphBounds
+ *
+ * <mxRectangle> that caches the scales, translated bounds of the current view.
+ */
+mxGraphView.prototype.graphBounds = null;
+
+/**
+ * Variable: scale
+ * 
+ * Specifies the scale. Default is 1 (100%).
+ */
+mxGraphView.prototype.scale = 1;
+	
+/**
+ * Variable: translate
+ *
+ * <mxPoint> that specifies the current translation. Default is a new
+ * empty <mxPoint>.
+ */
+mxGraphView.prototype.translate = null;
+
+/**
+ * Variable: states
+ * 
+ * <mxDictionary> that maps from cell IDs to <mxCellStates>.
+ */
+mxGraphView.prototype.states = null;
+
+/**
+ * Variable: updateStyle
+ * 
+ * Specifies if the style should be updated in each validation step. If this
+ * is false then the style is only updated if the state is created or if the
+ * style of the cell was changed. Default is false.
+ */
+mxGraphView.prototype.updateStyle = false;
+
+/**
+ * Variable: lastNode
+ * 
+ * During validation, this contains the last DOM node that was processed.
+ */
+mxGraphView.prototype.lastNode = null;
+
+/**
+ * Variable: lastHtmlNode
+ * 
+ * During validation, this contains the last HTML DOM node that was processed.
+ */
+mxGraphView.prototype.lastHtmlNode = null;
+
+/**
+ * Variable: lastForegroundNode
+ * 
+ * During validation, this contains the last edge's DOM node that was processed.
+ */
+mxGraphView.prototype.lastForegroundNode = null;
+
+/**
+ * Variable: lastForegroundHtmlNode
+ * 
+ * During validation, this contains the last edge HTML DOM node that was processed.
+ */
+mxGraphView.prototype.lastForegroundHtmlNode = null;
+
+/**
+ * Function: getGraphBounds
+ *
+ * Returns <graphBounds>.
+ */
+mxGraphView.prototype.getGraphBounds = function()
+{
+	return this.graphBounds;
+};
+
+/**
+ * Function: setGraphBounds
+ *
+ * Sets <graphBounds>.
+ */
+mxGraphView.prototype.setGraphBounds = function(value)
+{
+	this.graphBounds = value;
+};
+
+/**
+ * Function: getBounds
+ * 
+ * Returns the union of all <mxCellStates> for the given array of <mxCells>.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> whose bounds should be returned.
+ */
+mxGraphView.prototype.getBounds = function(cells)
+{
+	var result = null;
+	
+	if (cells != null && cells.length > 0)
+	{
+		var model = this.graph.getModel();
+		
+		for (var i = 0; i < cells.length; i++)
+		{
+			if (model.isVertex(cells[i]) || model.isEdge(cells[i]))
+			{
+				var state = this.getState(cells[i]);
+			
+				if (state != null)
+				{
+					if (result == null)
+					{
+						result = mxRectangle.fromRectangle(state);
+					}
+					else
+					{
+						result.add(state);
+					}
+				}
+			}
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: setCurrentRoot
+ *
+ * Sets and returns the current root and fires an <undo> event before
+ * calling <mxGraph.sizeDidChange>.
+ *
+ * Parameters:
+ *
+ * root - <mxCell> that specifies the root of the displayed cell hierarchy.
+ */
+mxGraphView.prototype.setCurrentRoot = function(root)
+{
+	if (this.currentRoot != root)
+	{
+		var change = new mxCurrentRootChange(this, root);
+		change.execute();
+		var edit = new mxUndoableEdit(this, false);
+		edit.add(change);
+		this.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit));
+		this.graph.sizeDidChange();
+	}
+	
+	return root;
+};
+
+/**
+ * Function: scaleAndTranslate
+ *
+ * Sets the scale and translation and fires a <scale> and <translate> event
+ * before calling <revalidate> followed by <mxGraph.sizeDidChange>.
+ *
+ * Parameters:
+ *
+ * scale - Decimal value that specifies the new scale (1 is 100%).
+ * dx - X-coordinate of the translation.
+ * dy - Y-coordinate of the translation.
+ */
+mxGraphView.prototype.scaleAndTranslate = function(scale, dx, dy)
+{
+	var previousScale = this.scale;
+	var previousTranslate = new mxPoint(this.translate.x, this.translate.y);
+	
+	if (this.scale != scale || this.translate.x != dx || this.translate.y != dy)
+	{
+		this.scale = scale;
+		
+		this.translate.x = dx;
+		this.translate.y = dy;
+
+		if (this.isEventsEnabled())
+		{
+			this.revalidate();
+			this.graph.sizeDidChange();
+		}
+	}
+	
+	this.fireEvent(new mxEventObject(mxEvent.SCALE_AND_TRANSLATE,
+		'scale', scale, 'previousScale', previousScale,
+		'translate', this.translate, 'previousTranslate', previousTranslate));
+};
+
+/**
+ * Function: getScale
+ * 
+ * Returns the <scale>.
+ */
+mxGraphView.prototype.getScale = function()
+{
+	return this.scale;
+};
+
+/**
+ * Function: setScale
+ *
+ * Sets the scale and fires a <scale> event before calling <revalidate> followed
+ * by <mxGraph.sizeDidChange>.
+ *
+ * Parameters:
+ *
+ * value - Decimal value that specifies the new scale (1 is 100%).
+ */
+mxGraphView.prototype.setScale = function(value)
+{
+	var previousScale = this.scale;
+	
+	if (this.scale != value)
+	{
+		this.scale = value;
+
+		if (this.isEventsEnabled())
+		{
+			this.revalidate();
+			this.graph.sizeDidChange();
+		}
+	}
+	
+	this.fireEvent(new mxEventObject(mxEvent.SCALE,
+		'scale', value, 'previousScale', previousScale));
+};
+
+/**
+ * Function: getTranslate
+ * 
+ * Returns the <translate>.
+ */
+mxGraphView.prototype.getTranslate = function()
+{
+	return this.translate;
+};
+
+/**
+ * Function: setTranslate
+ *
+ * Sets the translation and fires a <translate> event before calling
+ * <revalidate> followed by <mxGraph.sizeDidChange>. The translation is the
+ * negative of the origin.
+ *
+ * Parameters:
+ *
+ * dx - X-coordinate of the translation.
+ * dy - Y-coordinate of the translation.
+ */
+mxGraphView.prototype.setTranslate = function(dx, dy)
+{
+	var previousTranslate = new mxPoint(this.translate.x, this.translate.y);
+	
+	if (this.translate.x != dx || this.translate.y != dy)
+	{
+		this.translate.x = dx;
+		this.translate.y = dy;
+
+		if (this.isEventsEnabled())
+		{
+			this.revalidate();
+			this.graph.sizeDidChange();
+		}
+	}
+	
+	this.fireEvent(new mxEventObject(mxEvent.TRANSLATE,
+		'translate', this.translate, 'previousTranslate', previousTranslate));
+};
+
+/**
+ * Function: refresh
+ *
+ * Clears the view if <currentRoot> is not null and revalidates.
+ */
+mxGraphView.prototype.refresh = function()
+{
+	if (this.currentRoot != null)
+	{
+		this.clear();
+	}
+	
+	this.revalidate();
+};
+
+/**
+ * Function: revalidate
+ *
+ * Revalidates the complete view with all cell states.
+ */
+mxGraphView.prototype.revalidate = function()
+{
+	this.invalidate();
+	this.validate();
+};
+
+/**
+ * Function: clear
+ *
+ * Removes the state of the given cell and all descendants if the given
+ * cell is not the current root.
+ * 
+ * Parameters:
+ * 
+ * cell - Optional <mxCell> for which the state should be removed. Default
+ * is the root of the model.
+ * force - Boolean indicating if the current root should be ignored for
+ * recursion.
+ */
+mxGraphView.prototype.clear = function(cell, force, recurse)
+{
+	var model = this.graph.getModel();
+	cell = cell || model.getRoot();
+	force = (force != null) ? force : false;
+	recurse = (recurse != null) ? recurse : true;
+	
+	this.removeState(cell);
+	
+	if (recurse && (force || cell != this.currentRoot))
+	{
+		var childCount = model.getChildCount(cell);
+		
+		for (var i = 0; i < childCount; i++)
+		{
+			this.clear(model.getChildAt(cell, i), force);
+		}
+	}
+	else
+	{
+		this.invalidate(cell);
+	}
+};
+
+/**
+ * Function: invalidate
+ * 
+ * Invalidates the state of the given cell, all its descendants and
+ * connected edges.
+ * 
+ * Parameters:
+ * 
+ * cell - Optional <mxCell> to be invalidated. Default is the root of the
+ * model.
+ */
+mxGraphView.prototype.invalidate = function(cell, recurse, includeEdges)
+{
+	var model = this.graph.getModel();
+	cell = cell || model.getRoot();
+	recurse = (recurse != null) ? recurse : true;
+	includeEdges = (includeEdges != null) ? includeEdges : true;
+	
+	var state = this.getState(cell);
+	
+	if (state != null)
+	{
+		state.invalid = true;
+	}
+	
+	// Avoids infinite loops for invalid graphs
+	if (!cell.invalidating)
+	{
+		cell.invalidating = true;
+		
+		// Recursively invalidates all descendants
+		if (recurse)
+		{
+			var childCount = model.getChildCount(cell);
+			
+			for (var i = 0; i < childCount; i++)
+			{
+				var child = model.getChildAt(cell, i);
+				this.invalidate(child, recurse, includeEdges);
+			}
+		}
+		
+		// Propagates invalidation to all connected edges
+		if (includeEdges)
+		{
+			var edgeCount = model.getEdgeCount(cell);
+			
+			for (var i = 0; i < edgeCount; i++)
+			{
+				this.invalidate(model.getEdgeAt(cell, i), recurse, includeEdges);
+			}
+		}
+		
+		delete cell.invalidating;
+	}
+};
+
+/**
+ * Function: validate
+ * 
+ * Calls <validateCell> and <validateCellState> and updates the <graphBounds>
+ * using <getBoundingBox>. Finally the background is validated using
+ * <validateBackground>.
+ * 
+ * Parameters:
+ * 
+ * cell - Optional <mxCell> to be used as the root of the validation.
+ * Default is <currentRoot> or the root of the model.
+ */
+mxGraphView.prototype.validate = function(cell)
+{
+	var t0 = mxLog.enter('mxGraphView.validate');
+	window.status = mxResources.get(this.updatingDocumentResource) ||
+		this.updatingDocumentResource;
+	
+	this.resetValidationState();
+	
+	// Improves IE rendering speed by minimizing reflows
+	var prevDisplay = null;
+	
+	if (this.optimizeVmlReflows && this.canvas != null && this.textDiv == null &&
+		((document.documentMode == 8 && !mxClient.IS_EM) || mxClient.IS_QUIRKS))
+	{
+		// Placeholder keeps scrollbar positions when canvas is hidden
+		this.placeholder = document.createElement('div');
+		this.placeholder.style.position = 'absolute';
+		this.placeholder.style.width = this.canvas.clientWidth + 'px';
+		this.placeholder.style.height = this.canvas.clientHeight + 'px';
+		this.canvas.parentNode.appendChild(this.placeholder);
+
+		prevDisplay = this.drawPane.style.display;
+		this.canvas.style.display = 'none';
+		
+		// Creates temporary DIV used for text measuring in mxText.updateBoundingBox
+		this.textDiv = document.createElement('div');
+		this.textDiv.style.position = 'absolute';
+		this.textDiv.style.whiteSpace = 'nowrap';
+		this.textDiv.style.visibility = 'hidden';
+		this.textDiv.style.display = (mxClient.IS_QUIRKS) ? 'inline' : 'inline-block';
+		this.textDiv.style.zoom = '1';
+		
+		document.body.appendChild(this.textDiv);
+	}
+	
+	var graphBounds = this.getBoundingBox(this.validateCellState(
+		this.validateCell(cell || ((this.currentRoot != null) ?
+			this.currentRoot : this.graph.getModel().getRoot()))));
+	this.setGraphBounds((graphBounds != null) ? graphBounds : this.getEmptyBounds());
+	this.validateBackground();
+	
+	if (prevDisplay != null)
+	{
+		this.canvas.style.display = prevDisplay;
+		this.textDiv.parentNode.removeChild(this.textDiv);
+		
+		if (this.placeholder != null)
+		{
+			this.placeholder.parentNode.removeChild(this.placeholder);
+		}
+				
+		// Textdiv cannot be reused
+		this.textDiv = null;
+	}
+	
+	this.resetValidationState();
+	
+	window.status = mxResources.get(this.doneResource) ||
+		this.doneResource;
+	mxLog.leave('mxGraphView.validate', t0);
+};
+
+/**
+ * Function: getEmptyBounds
+ * 
+ * Returns the bounds for an empty graph. This returns a rectangle at
+ * <translate> with the size of 0 x 0.
+ */
+mxGraphView.prototype.getEmptyBounds = function()
+{
+	return new mxRectangle(this.translate.x * this.scale, this.translate.y * this.scale);
+};
+
+/**
+ * Function: getBoundingBox
+ * 
+ * Returns the bounding box of the shape and the label for the given
+ * <mxCellState> and its children if recurse is true.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose bounding box should be returned.
+ * recurse - Optional boolean indicating if the children should be included.
+ * Default is true.
+ */
+mxGraphView.prototype.getBoundingBox = function(state, recurse)
+{
+	recurse = (recurse != null) ? recurse : true;
+	var bbox = null;
+	
+	if (state != null)
+	{
+		if (state.shape != null && state.shape.boundingBox != null)
+		{
+			bbox = state.shape.boundingBox.clone();
+		}
+		
+		// Adds label bounding box to graph bounds
+		if (state.text != null && state.text.boundingBox != null)
+		{
+			if (bbox != null)
+			{
+				bbox.add(state.text.boundingBox);
+			}
+			else
+			{
+				bbox = state.text.boundingBox.clone();
+			}
+		}
+		
+		if (recurse)
+		{
+			var model = this.graph.getModel();
+			var childCount = model.getChildCount(state.cell);
+			
+			for (var i = 0; i < childCount; i++)
+			{
+				var bounds = this.getBoundingBox(this.getState(model.getChildAt(state.cell, i)));
+				
+				if (bounds != null)
+				{
+					if (bbox == null)
+					{
+						bbox = bounds;
+					}
+					else
+					{
+						bbox.add(bounds);
+					}
+				}
+			}
+		}
+	}
+	
+	return bbox;
+};
+
+/**
+ * Function: createBackgroundPageShape
+ *
+ * Creates and returns the shape used as the background page.
+ * 
+ * Parameters:
+ * 
+ * bounds - <mxRectangle> that represents the bounds of the shape.
+ */
+mxGraphView.prototype.createBackgroundPageShape = function(bounds)
+{
+	return new mxRectangleShape(bounds, 'white', 'black');
+};
+
+/**
+ * Function: validateBackground
+ *
+ * Calls <validateBackgroundImage> and <validateBackgroundPage>.
+ */
+mxGraphView.prototype.validateBackground = function()
+{
+	this.validateBackgroundImage();
+	this.validateBackgroundPage();
+};
+
+/**
+ * Function: validateBackgroundImage
+ * 
+ * Validates the background image.
+ */
+mxGraphView.prototype.validateBackgroundImage = function()
+{
+	var bg = this.graph.getBackgroundImage();
+	
+	if (bg != null)
+	{
+		if (this.backgroundImage == null || this.backgroundImage.image != bg.src)
+		{
+			if (this.backgroundImage != null)
+			{
+				this.backgroundImage.destroy();
+			}
+			
+			var bounds = new mxRectangle(0, 0, 1, 1);
+			
+			this.backgroundImage = new mxImageShape(bounds, bg.src);
+			this.backgroundImage.dialect = this.graph.dialect;
+			this.backgroundImage.init(this.backgroundPane);
+			this.backgroundImage.redraw();
+
+			// Workaround for ignored event on background in IE8 standards mode
+			if (document.documentMode == 8 && !mxClient.IS_EM)
+			{
+				mxEvent.addGestureListeners(this.backgroundImage.node,
+					mxUtils.bind(this, function(evt)
+					{
+						this.graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt));
+					}),
+					mxUtils.bind(this, function(evt)
+					{
+						this.graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt));
+					}),
+					mxUtils.bind(this, function(evt)
+					{
+						this.graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt));
+					})
+				);
+			}
+		}
+		
+		this.redrawBackgroundImage(this.backgroundImage, bg);
+	}
+	else if (this.backgroundImage != null)
+	{
+		this.backgroundImage.destroy();
+		this.backgroundImage = null;
+	}
+};
+
+/**
+ * Function: validateBackgroundPage
+ * 
+ * Validates the background page.
+ */
+mxGraphView.prototype.validateBackgroundPage = function()
+{
+	if (this.graph.pageVisible)
+	{
+		var bounds = this.getBackgroundPageBounds();
+		
+		if (this.backgroundPageShape == null)
+		{
+			this.backgroundPageShape = this.createBackgroundPageShape(bounds);
+			this.backgroundPageShape.scale = this.scale;
+			this.backgroundPageShape.isShadow = true;
+			this.backgroundPageShape.dialect = this.graph.dialect;
+			this.backgroundPageShape.init(this.backgroundPane);
+			this.backgroundPageShape.redraw();
+			
+			// Adds listener for double click handling on background
+			if (this.graph.nativeDblClickEnabled)
+			{
+				mxEvent.addListener(this.backgroundPageShape.node, 'dblclick', mxUtils.bind(this, function(evt)
+				{
+					this.graph.dblClick(evt);
+				}));
+			}
+
+			// Adds basic listeners for graph event dispatching outside of the
+			// container and finishing the handling of a single gesture
+			mxEvent.addGestureListeners(this.backgroundPageShape.node,
+				mxUtils.bind(this, function(evt)
+				{
+					this.graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt));
+				}),
+				mxUtils.bind(this, function(evt)
+				{
+					// Hides the tooltip if mouse is outside container
+					if (this.graph.tooltipHandler != null && this.graph.tooltipHandler.isHideOnHover())
+					{
+						this.graph.tooltipHandler.hide();
+					}
+					
+					if (this.graph.isMouseDown && !mxEvent.isConsumed(evt))
+					{
+						this.graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt));
+					}
+				}),
+				mxUtils.bind(this, function(evt)
+				{
+					this.graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt));
+				})
+			);
+		}
+		else
+		{
+			this.backgroundPageShape.scale = this.scale;
+			this.backgroundPageShape.bounds = bounds;
+			this.backgroundPageShape.redraw();
+		}
+	}
+	else if (this.backgroundPageShape != null)
+	{
+		this.backgroundPageShape.destroy();
+		this.backgroundPageShape = null;
+	}
+};
+
+/**
+ * Function: getBackgroundPageBounds
+ * 
+ * Returns the bounds for the background page.
+ */
+mxGraphView.prototype.getBackgroundPageBounds = function()
+{
+	var fmt = this.graph.pageFormat;
+	var ps = this.scale * this.graph.pageScale;
+	var bounds = new mxRectangle(this.scale * this.translate.x, this.scale * this.translate.y,
+			fmt.width * ps, fmt.height * ps);
+	
+	return bounds;
+};
+
+/**
+ * Function: redrawBackgroundImage
+ *
+ * Updates the bounds and redraws the background image.
+ * 
+ * Example:
+ * 
+ * If the background image should not be scaled, this can be replaced with
+ * the following.
+ * 
+ * (code)
+ * mxGraphView.prototype.redrawBackground = function(backgroundImage, bg)
+ * {
+ *   backgroundImage.bounds.x = this.translate.x;
+ *   backgroundImage.bounds.y = this.translate.y;
+ *   backgroundImage.bounds.width = bg.width;
+ *   backgroundImage.bounds.height = bg.height;
+ *
+ *   backgroundImage.redraw();
+ * };
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * backgroundImage - <mxImageShape> that represents the background image.
+ * bg - <mxImage> that specifies the image and its dimensions.
+ */
+mxGraphView.prototype.redrawBackgroundImage = function(backgroundImage, bg)
+{
+	backgroundImage.scale = this.scale;
+	backgroundImage.bounds.x = this.scale * this.translate.x;
+	backgroundImage.bounds.y = this.scale * this.translate.y;
+	backgroundImage.bounds.width = this.scale * bg.width;
+	backgroundImage.bounds.height = this.scale * bg.height;
+
+	backgroundImage.redraw();
+};
+
+/**
+ * Function: validateCell
+ * 
+ * Recursively creates the cell state for the given cell if visible is true and
+ * the given cell is visible. If the cell is not visible but the state exists
+ * then it is removed using <removeState>.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose <mxCellState> should be created.
+ * visible - Optional boolean indicating if the cell should be visible. Default
+ * is true.
+ */
+mxGraphView.prototype.validateCell = function(cell, visible)
+{
+	visible = (visible != null) ? visible : true;
+	
+	if (cell != null)
+	{
+		visible = visible && this.graph.isCellVisible(cell);
+		var state = this.getState(cell, visible);
+		
+		if (state != null && !visible)
+		{
+			this.removeState(cell);
+		}
+		else
+		{
+			var model = this.graph.getModel();
+			var childCount = model.getChildCount(cell);
+			
+			for (var i = 0; i < childCount; i++)
+			{
+				this.validateCell(model.getChildAt(cell, i), visible &&
+					(!this.isCellCollapsed(cell) || cell == this.currentRoot));
+			}
+		}
+	}
+	
+	return cell;
+};
+
+/**
+ * Function: validateCellState
+ * 
+ * Validates and repaints the <mxCellState> for the given <mxCell>.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose <mxCellState> should be validated.
+ * recurse - Optional boolean indicating if the children of the cell should be
+ * validated. Default is true.
+ */
+mxGraphView.prototype.validateCellState = function(cell, recurse)
+{
+	recurse = (recurse != null) ? recurse : true;
+	var state = null;
+	
+	if (cell != null)
+	{
+		state = this.getState(cell);
+		
+		if (state != null)
+		{
+			var model = this.graph.getModel();
+			
+			if (state.invalid)
+			{
+				state.invalid = false;
+				
+				if (state.style == null)
+				{
+					state.style = this.graph.getCellStyle(state.cell);
+				}
+				
+				if (cell != this.currentRoot)
+				{
+					this.validateCellState(model.getParent(cell), false);
+				}
+
+				state.setVisibleTerminalState(this.validateCellState(this.getVisibleTerminal(cell, true), false), true);
+				state.setVisibleTerminalState(this.validateCellState(this.getVisibleTerminal(cell, false), false), false);
+				
+				this.updateCellState(state);
+				
+				// Repaint happens immediately after the cell is validated
+				if (cell != this.currentRoot && !state.invalid)
+				{
+					this.graph.cellRenderer.redraw(state, false, this.isRendering());
+
+					// Handles changes to invertex paintbounds after update of rendering shape
+					state.updateCachedBounds();
+				}
+			}
+
+			if (recurse && !state.invalid)
+			{
+				// Updates order in DOM if recursively traversing
+				if (state.shape != null)
+				{
+					this.stateValidated(state);
+				}
+			
+				var childCount = model.getChildCount(cell);
+				
+				for (var i = 0; i < childCount; i++)
+				{
+					this.validateCellState(model.getChildAt(cell, i));
+				}
+			}
+		}
+	}
+	
+	return state;
+};
+
+/**
+ * Function: updateCellState
+ * 
+ * Updates the given <mxCellState>.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> to be updated.
+ */
+mxGraphView.prototype.updateCellState = function(state)
+{
+	state.absoluteOffset.x = 0;
+	state.absoluteOffset.y = 0;
+	state.origin.x = 0;
+	state.origin.y = 0;
+	state.length = 0;
+	
+	if (state.cell != this.currentRoot)
+	{
+		var model = this.graph.getModel();
+		var pState = this.getState(model.getParent(state.cell)); 
+		
+		if (pState != null && pState.cell != this.currentRoot)
+		{
+			state.origin.x += pState.origin.x;
+			state.origin.y += pState.origin.y;
+		}
+		
+		var offset = this.graph.getChildOffsetForCell(state.cell);
+		
+		if (offset != null)
+		{
+			state.origin.x += offset.x;
+			state.origin.y += offset.y;
+		}
+		
+		var geo = this.graph.getCellGeometry(state.cell);				
+	
+		if (geo != null)
+		{
+			if (!model.isEdge(state.cell))
+			{
+				offset = geo.offset || this.EMPTY_POINT;
+	
+				if (geo.relative && pState != null)
+				{
+					if (model.isEdge(pState.cell))
+					{
+						var origin = this.getPoint(pState, geo);
+
+						if (origin != null)
+						{
+							state.origin.x += (origin.x / this.scale) - pState.origin.x - this.translate.x;
+							state.origin.y += (origin.y / this.scale) - pState.origin.y - this.translate.y;
+						}
+					}
+					else
+					{
+						state.origin.x += geo.x * pState.width / this.scale + offset.x;
+						state.origin.y += geo.y * pState.height / this.scale + offset.y;
+					}
+				}
+				else
+				{
+					state.absoluteOffset.x = this.scale * offset.x;
+					state.absoluteOffset.y = this.scale * offset.y;
+					state.origin.x += geo.x;
+					state.origin.y += geo.y;
+				}
+			}
+	
+			state.x = this.scale * (this.translate.x + state.origin.x);
+			state.y = this.scale * (this.translate.y + state.origin.y);
+			state.width = this.scale * geo.width;
+			state.unscaledWidth = geo.width;
+			state.height = this.scale * geo.height;
+			
+			if (model.isVertex(state.cell))
+			{
+				this.updateVertexState(state, geo);
+			}
+			
+			if (model.isEdge(state.cell))
+			{
+				this.updateEdgeState(state, geo);
+			}
+		}
+	}
+
+	state.updateCachedBounds();
+};
+
+/**
+ * Function: isCellCollapsed
+ * 
+ * Returns true if the children of the given cell should not be visible in the
+ * view. This implementation uses <mxGraph.isCellVisible> but it can be
+ * overidden to use a separate condition.
+ */
+mxGraphView.prototype.isCellCollapsed = function(cell)
+{
+	return this.graph.isCellCollapsed(cell);
+};
+
+/**
+ * Function: updateVertexState
+ * 
+ * Validates the given cell state.
+ */
+mxGraphView.prototype.updateVertexState = function(state, geo)
+{
+	var model = this.graph.getModel();
+	var pState = this.getState(model.getParent(state.cell));
+	
+	if (geo.relative && pState != null && !model.isEdge(pState.cell))
+	{
+		var alpha = mxUtils.toRadians(pState.style[mxConstants.STYLE_ROTATION] || '0');
+		
+		if (alpha != 0)
+		{
+			var cos = Math.cos(alpha);
+			var sin = Math.sin(alpha);
+
+			var ct = new mxPoint(state.getCenterX(), state.getCenterY());
+			var cx = new mxPoint(pState.getCenterX(), pState.getCenterY());
+			var pt = mxUtils.getRotatedPoint(ct, cos, sin, cx);
+			state.x = pt.x - state.width / 2;
+			state.y = pt.y - state.height / 2;
+		}
+	}
+	
+	this.updateVertexLabelOffset(state);
+};
+
+/**
+ * Function: updateEdgeState
+ * 
+ * Validates the given cell state.
+ */
+mxGraphView.prototype.updateEdgeState = function(state, geo)
+{
+	var source = state.getVisibleTerminalState(true);
+	var target = state.getVisibleTerminalState(false);
+	
+	// This will remove edges with no terminals and no terminal points
+	// as such edges are invalid and produce NPEs in the edge styles.
+	// Also removes connected edges that have no visible terminals.
+	if ((this.graph.model.getTerminal(state.cell, true) != null && source == null) ||
+		(source == null && geo.getTerminalPoint(true) == null) ||
+		(this.graph.model.getTerminal(state.cell, false) != null && target == null) ||
+		(target == null && geo.getTerminalPoint(false) == null))
+	{
+		this.clear(state.cell, true);
+	}
+	else
+	{
+		this.updateFixedTerminalPoints(state, source, target);
+		this.updatePoints(state, geo.points, source, target);
+		this.updateFloatingTerminalPoints(state, source, target);
+		
+		var pts = state.absolutePoints;
+		
+		if (state.cell != this.currentRoot && (pts == null || pts.length < 2 ||
+			pts[0] == null || pts[pts.length - 1] == null))
+		{
+			// This will remove edges with invalid points from the list of states in the view.
+			// Happens if the one of the terminals and the corresponding terminal point is null.
+			this.clear(state.cell, true);
+		}
+		else
+		{
+			this.updateEdgeBounds(state);
+			this.updateEdgeLabelOffset(state);
+		}
+	}
+};
+
+/**
+ * Function: updateVertexLabelOffset
+ * 
+ * Updates the absoluteOffset of the given vertex cell state. This takes
+ * into account the label position styles.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose absolute offset should be updated.
+ */
+mxGraphView.prototype.updateVertexLabelOffset = function(state)
+{
+	var h = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);
+
+	if (h == mxConstants.ALIGN_LEFT)
+	{
+		var lw = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_WIDTH, null);
+		
+		if (lw != null)
+		{
+			lw *= this.scale;
+		}
+		else
+		{
+			lw = state.width;
+		}
+		
+		state.absoluteOffset.x -= lw;
+	}
+	else if (h == mxConstants.ALIGN_RIGHT)
+	{
+		state.absoluteOffset.x += state.width;
+	}
+	else if (h == mxConstants.ALIGN_CENTER)
+	{
+		var lw = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_WIDTH, null);
+		
+		if (lw != null)
+		{
+			// Aligns text block with given width inside the vertex width
+			var align = mxUtils.getValue(state.style, mxConstants.STYLE_ALIGN, mxConstants.ALIGN_CENTER);
+			var dx = 0;
+			
+			if (align == mxConstants.ALIGN_CENTER)
+			{
+				dx = 0.5;
+			}
+			else if (align == mxConstants.ALIGN_RIGHT)
+			{
+				dx = 1;
+			}
+			
+			if (dx != 0)
+			{
+				state.absoluteOffset.x -= (lw * this.scale - state.width) * dx;
+			}
+		}
+	}
+	
+	var v = mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);
+	
+	if (v == mxConstants.ALIGN_TOP)
+	{
+		state.absoluteOffset.y -= state.height;
+	}
+	else if (v == mxConstants.ALIGN_BOTTOM)
+	{
+		state.absoluteOffset.y += state.height;
+	}
+};
+
+/**
+ * Function: resetValidationState
+ *
+ * Resets the current validation state.
+ */
+mxGraphView.prototype.resetValidationState = function()
+{
+	this.lastNode = null;
+	this.lastHtmlNode = null;
+	this.lastForegroundNode = null;
+	this.lastForegroundHtmlNode = null;
+};
+
+/**
+ * Function: stateValidated
+ * 
+ * Invoked when a state has been processed in <validatePoints>. This is used
+ * to update the order of the DOM nodes of the shape.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> that represents the cell state.
+ */
+mxGraphView.prototype.stateValidated = function(state)
+{
+	var fg = (this.graph.getModel().isEdge(state.cell) && this.graph.keepEdgesInForeground) ||
+		(this.graph.getModel().isVertex(state.cell) && this.graph.keepEdgesInBackground);
+	var htmlNode = (fg) ? this.lastForegroundHtmlNode || this.lastHtmlNode : this.lastHtmlNode;
+	var node = (fg) ? this.lastForegroundNode || this.lastNode : this.lastNode;
+	var result = this.graph.cellRenderer.insertStateAfter(state, node, htmlNode);
+
+	if (fg)
+	{
+		this.lastForegroundHtmlNode = result[1];
+		this.lastForegroundNode = result[0];
+	}
+	else
+	{
+		this.lastHtmlNode = result[1];
+		this.lastNode = result[0];
+	}
+};
+
+/**
+ * Function: updateFixedTerminalPoints
+ *
+ * Sets the initial absolute terminal points in the given state before the edge
+ * style is computed.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCellState> whose initial terminal points should be updated.
+ * source - <mxCellState> which represents the source terminal.
+ * target - <mxCellState> which represents the target terminal.
+ */
+mxGraphView.prototype.updateFixedTerminalPoints = function(edge, source, target)
+{
+	this.updateFixedTerminalPoint(edge, source, true,
+		this.graph.getConnectionConstraint(edge, source, true));
+	this.updateFixedTerminalPoint(edge, target, false,
+		this.graph.getConnectionConstraint(edge, target, false));
+};
+
+/**
+ * Function: updateFixedTerminalPoint
+ *
+ * Sets the fixed source or target terminal point on the given edge.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCellState> whose terminal point should be updated.
+ * terminal - <mxCellState> which represents the actual terminal.
+ * source - Boolean that specifies if the terminal is the source.
+ * constraint - <mxConnectionConstraint> that specifies the connection.
+ */
+mxGraphView.prototype.updateFixedTerminalPoint = function(edge, terminal, source, constraint)
+{
+	edge.setAbsoluteTerminalPoint(this.getFixedTerminalPoint(edge, terminal, source, constraint), source);
+};
+
+/**
+ * Function: getFixedTerminalPoint
+ *
+ * Returns the fixed source or target terminal point for the given edge.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCellState> whose terminal point should be returned.
+ * terminal - <mxCellState> which represents the actual terminal.
+ * source - Boolean that specifies if the terminal is the source.
+ * constraint - <mxConnectionConstraint> that specifies the connection.
+ */
+mxGraphView.prototype.getFixedTerminalPoint = function(edge, terminal, source, constraint)
+{
+	var pt = null;
+	
+	if (constraint != null)
+	{
+		pt = this.graph.getConnectionPoint(terminal, constraint);
+	}
+	
+	if (pt == null && terminal == null)
+	{
+		var s = this.scale;
+		var tr = this.translate;
+		var orig = edge.origin;
+		var geo = this.graph.getCellGeometry(edge.cell);
+		pt = geo.getTerminalPoint(source);
+		
+		if (pt != null)
+		{
+			pt = new mxPoint(s * (tr.x + pt.x + orig.x),
+							 s * (tr.y + pt.y + orig.y));
+		}
+	}
+	
+	return pt;
+};
+
+/**
+ * Function: updateBoundsFromStencil
+ * 
+ * Updates the bounds of the given cell state to reflect the bounds of the stencil
+ * if it has a fixed aspect and returns the previous bounds as an <mxRectangle> if
+ * the bounds have been modified or null otherwise.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCellState> whose bounds should be updated.
+ */
+mxGraphView.prototype.updateBoundsFromStencil = function(state)
+{
+	var previous = null;
+	
+	if (state != null && state.shape != null && state.shape.stencil != null && state.shape.stencil.aspect == 'fixed')
+	{
+		previous = mxRectangle.fromRectangle(state);
+		var asp = state.shape.stencil.computeAspect(state.style, state.x, state.y, state.width, state.height);
+		state.setRect(asp.x, asp.y, state.shape.stencil.w0 * asp.width, state.shape.stencil.h0 * asp.height);
+	}
+	
+	return previous;
+};
+
+/**
+ * Function: updatePoints
+ *
+ * Updates the absolute points in the given state using the specified array
+ * of <mxPoints> as the relative points.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCellState> whose absolute points should be updated.
+ * points - Array of <mxPoints> that constitute the relative points.
+ * source - <mxCellState> that represents the source terminal.
+ * target - <mxCellState> that represents the target terminal.
+ */
+mxGraphView.prototype.updatePoints = function(edge, points, source, target)
+{
+	if (edge != null)
+	{
+		var pts = [];
+		pts.push(edge.absolutePoints[0]);
+		var edgeStyle = this.getEdgeStyle(edge, points, source, target);
+		
+		if (edgeStyle != null)
+		{
+			var src = this.getTerminalPort(edge, source, true);
+			var trg = this.getTerminalPort(edge, target, false);
+			
+			// Uses the stencil bounds for routing and restores after routing
+			var srcBounds = this.updateBoundsFromStencil(src);
+			var trgBounds = this.updateBoundsFromStencil(trg);
+
+			edgeStyle(edge, src, trg, points, pts);
+			
+			// Restores previous bounds
+			if (srcBounds != null)
+			{
+				src.setRect(srcBounds.x, srcBounds.y, srcBounds.width, srcBounds.height);
+			}
+			
+			if (trgBounds != null)
+			{
+				trg.setRect(trgBounds.x, trgBounds.y, trgBounds.width, trgBounds.height);
+			}
+		}
+		else if (points != null)
+		{
+			for (var i = 0; i < points.length; i++)
+			{
+				if (points[i] != null)
+				{
+					var pt = mxUtils.clone(points[i]);
+					pts.push(this.transformControlPoint(edge, pt));
+				}
+			}
+		}
+		
+		var tmp = edge.absolutePoints;
+		pts.push(tmp[tmp.length-1]);
+
+		edge.absolutePoints = pts;
+	}
+};
+
+/**
+ * Function: transformControlPoint
+ *
+ * Transforms the given control point to an absolute point.
+ */
+mxGraphView.prototype.transformControlPoint = function(state, pt)
+{
+	if (state != null && pt != null)
+	{
+		var orig = state.origin;
+		
+	    return new mxPoint(this.scale * (pt.x + this.translate.x + orig.x),
+	    	this.scale * (pt.y + this.translate.y + orig.y));
+	}
+	
+	return null;
+};
+
+/**
+ * Function: isLoopStyleEnabled
+ * 
+ * Returns true if the given edge should be routed with <mxGraph.defaultLoopStyle>
+ * or the <mxConstants.STYLE_LOOP> defined for the given edge. This implementation
+ * returns true if the given edge is a loop and does not 
+ */
+mxGraphView.prototype.isLoopStyleEnabled = function(edge, points, source, target)
+{
+	var sc = this.graph.getConnectionConstraint(edge, source, true);
+	var tc = this.graph.getConnectionConstraint(edge, target, false);
+	
+	if (!mxUtils.getValue(edge.style, mxConstants.STYLE_ORTHOGONAL_LOOP, false) ||
+		((sc == null || sc.point == null) && (tc == null || tc.point == null)))
+	{
+		return source != null && source == target;
+	}
+	
+	return false;
+};
+
+/**
+ * Function: getEdgeStyle
+ * 
+ * Returns the edge style function to be used to render the given edge state.
+ */
+mxGraphView.prototype.getEdgeStyle = function(edge, points, source, target)
+{
+	var edgeStyle = this.isLoopStyleEnabled(edge, points, source, target) ?
+		mxUtils.getValue(edge.style, mxConstants.STYLE_LOOP, this.graph.defaultLoopStyle) :
+		(!mxUtils.getValue(edge.style, mxConstants.STYLE_NOEDGESTYLE, false) ?
+		edge.style[mxConstants.STYLE_EDGE] : null);
+
+	// Converts string values to objects
+	if (typeof(edgeStyle) == "string")
+	{
+		var tmp = mxStyleRegistry.getValue(edgeStyle);
+		
+		if (tmp == null && this.isAllowEval())
+		{
+ 			tmp = mxUtils.eval(edgeStyle);
+		}
+		
+		edgeStyle = tmp;
+	}
+	
+	if (typeof(edgeStyle) == "function")
+	{
+		return edgeStyle;
+	}
+	
+	return null;
+};
+
+/**
+ * Function: updateFloatingTerminalPoints
+ *
+ * Updates the terminal points in the given state after the edge style was
+ * computed for the edge.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose terminal points should be updated.
+ * source - <mxCellState> that represents the source terminal.
+ * target - <mxCellState> that represents the target terminal.
+ */
+mxGraphView.prototype.updateFloatingTerminalPoints = function(state, source, target)
+{
+	var pts = state.absolutePoints;
+	var p0 = pts[0];
+	var pe = pts[pts.length - 1];
+
+	if (pe == null && target != null)
+	{
+		this.updateFloatingTerminalPoint(state, target, source, false);
+	}
+	
+	if (p0 == null && source != null)
+	{
+		this.updateFloatingTerminalPoint(state, source, target, true);
+	}
+};
+
+/**
+ * Function: updateFloatingTerminalPoint
+ *
+ * Updates the absolute terminal point in the given state for the given
+ * start and end state, where start is the source if source is true.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCellState> whose terminal point should be updated.
+ * start - <mxCellState> for the terminal on "this" side of the edge.
+ * end - <mxCellState> for the terminal on the other side of the edge.
+ * source - Boolean indicating if start is the source terminal state.
+ */
+mxGraphView.prototype.updateFloatingTerminalPoint = function(edge, start, end, source)
+{
+	edge.setAbsoluteTerminalPoint(this.getFloatingTerminalPoint(edge, start, end, source), source);
+};
+
+/**
+ * Function: getFloatingTerminalPoint
+ * 
+ * Returns the floating terminal point for the given edge, start and end
+ * state, where start is the source if source is true.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCellState> whose terminal point should be returned.
+ * start - <mxCellState> for the terminal on "this" side of the edge.
+ * end - <mxCellState> for the terminal on the other side of the edge.
+ * source - Boolean indicating if start is the source terminal state.
+ */
+mxGraphView.prototype.getFloatingTerminalPoint = function(edge, start, end, source)
+{
+	start = this.getTerminalPort(edge, start, source);
+	var next = this.getNextPoint(edge, end, source);
+	
+	var orth = this.graph.isOrthogonal(edge);
+	var alpha = mxUtils.toRadians(Number(start.style[mxConstants.STYLE_ROTATION] || '0'));
+	var center = new mxPoint(start.getCenterX(), start.getCenterY());
+	
+	if (alpha != 0)
+	{
+		var cos = Math.cos(-alpha);
+		var sin = Math.sin(-alpha);
+		next = mxUtils.getRotatedPoint(next, cos, sin, center);
+	}
+	
+	var border = parseFloat(edge.style[mxConstants.STYLE_PERIMETER_SPACING] || 0);
+	border += parseFloat(edge.style[(source) ?
+		mxConstants.STYLE_SOURCE_PERIMETER_SPACING :
+		mxConstants.STYLE_TARGET_PERIMETER_SPACING] || 0);
+	var pt = this.getPerimeterPoint(start, next, alpha == 0 && orth, border);
+
+	if (alpha != 0)
+	{
+		var cos = Math.cos(alpha);
+		var sin = Math.sin(alpha);
+		pt = mxUtils.getRotatedPoint(pt, cos, sin, center);
+	}
+	
+	return pt;
+};
+
+/**
+ * Function: getTerminalPort
+ * 
+ * Returns an <mxCellState> that represents the source or target terminal or
+ * port for the given edge.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> that represents the state of the edge.
+ * terminal - <mxCellState> that represents the terminal.
+ * source - Boolean indicating if the given terminal is the source terminal.
+ */
+mxGraphView.prototype.getTerminalPort = function(state, terminal, source)
+{
+	var key = (source) ? mxConstants.STYLE_SOURCE_PORT :
+		mxConstants.STYLE_TARGET_PORT;
+	var id = mxUtils.getValue(state.style, key);
+	
+	if (id != null)
+	{
+		var tmp = this.getState(this.graph.getModel().getCell(id));
+		
+		// Only uses ports where a cell state exists
+		if (tmp != null)
+		{
+			terminal = tmp;
+		}
+	}
+	
+	return terminal;
+};
+
+/**
+ * Function: getPerimeterPoint
+ *
+ * Returns an <mxPoint> that defines the location of the intersection point between
+ * the perimeter and the line between the center of the shape and the given point.
+ * 
+ * Parameters:
+ * 
+ * terminal - <mxCellState> for the source or target terminal.
+ * next - <mxPoint> that lies outside of the given terminal.
+ * orthogonal - Boolean that specifies if the orthogonal projection onto
+ * the perimeter should be returned. If this is false then the intersection
+ * of the perimeter and the line between the next and the center point is
+ * returned.
+ * border - Optional border between the perimeter and the shape.
+ */
+mxGraphView.prototype.getPerimeterPoint = function(terminal, next, orthogonal, border)
+{
+	var point = null;
+	
+	if (terminal != null)
+	{
+		var perimeter = this.getPerimeterFunction(terminal);
+		
+		if (perimeter != null && next != null)
+		{
+			var bounds = this.getPerimeterBounds(terminal, border);
+
+			if (bounds.width > 0 || bounds.height > 0)
+			{
+				point = new mxPoint(next.x, next.y);
+				var flipH = false;
+				var flipV = false;	
+				
+				if (this.graph.model.isVertex(terminal.cell))
+				{
+					flipH = mxUtils.getValue(terminal.style, mxConstants.STYLE_FLIPH, 0) == 1;
+					flipV = mxUtils.getValue(terminal.style, mxConstants.STYLE_FLIPV, 0) == 1;	
+	
+					// Legacy support for stencilFlipH/V
+					if (terminal.shape != null && terminal.shape.stencil != null)
+					{
+						flipH = (mxUtils.getValue(terminal.style, 'stencilFlipH', 0) == 1) || flipH;
+						flipV = (mxUtils.getValue(terminal.style, 'stencilFlipV', 0) == 1) || flipV;
+					}
+	
+					if (flipH)
+					{
+						point.x = 2 * bounds.getCenterX() - point.x;
+					}
+					
+					if (flipV)
+					{
+						point.y = 2 * bounds.getCenterY() - point.y;
+					}
+				}
+				
+				point = perimeter(bounds, terminal, point, orthogonal);
+
+				if (point != null)
+				{
+					if (flipH)
+					{
+						point.x = 2 * bounds.getCenterX() - point.x;
+					}
+					
+					if (flipV)
+					{
+						point.y = 2 * bounds.getCenterY() - point.y;
+					}
+				}
+			}
+		}
+		
+		if (point == null)
+		{
+			point = this.getPoint(terminal);
+		}
+	}
+	
+	return point;
+};
+
+/**
+ * Function: getRoutingCenterX
+ * 
+ * Returns the x-coordinate of the center point for automatic routing.
+ */
+mxGraphView.prototype.getRoutingCenterX = function (state)
+{
+	var f = (state.style != null) ? parseFloat(state.style
+		[mxConstants.STYLE_ROUTING_CENTER_X]) || 0 : 0;
+
+	return state.getCenterX() + f * state.width;
+};
+
+/**
+ * Function: getRoutingCenterY
+ * 
+ * Returns the y-coordinate of the center point for automatic routing.
+ */
+mxGraphView.prototype.getRoutingCenterY = function (state)
+{
+	var f = (state.style != null) ? parseFloat(state.style
+		[mxConstants.STYLE_ROUTING_CENTER_Y]) || 0 : 0;
+
+	return state.getCenterY() + f * state.height;
+};
+
+/**
+ * Function: getPerimeterBounds
+ *
+ * Returns the perimeter bounds for the given terminal, edge pair as an
+ * <mxRectangle>.
+ * 
+ * If you have a model where each terminal has a relative child that should
+ * act as the graphical endpoint for a connection from/to the terminal, then
+ * this method can be replaced as follows:
+ * 
+ * (code)
+ * var oldGetPerimeterBounds = mxGraphView.prototype.getPerimeterBounds;
+ * mxGraphView.prototype.getPerimeterBounds = function(terminal, edge, isSource)
+ * {
+ *   var model = this.graph.getModel();
+ *   var childCount = model.getChildCount(terminal.cell);
+ * 
+ *   if (childCount > 0)
+ *   {
+ *     var child = model.getChildAt(terminal.cell, 0);
+ *     var geo = model.getGeometry(child);
+ *
+ *     if (geo != null &&
+ *         geo.relative)
+ *     {
+ *       var state = this.getState(child);
+ *       
+ *       if (state != null)
+ *       {
+ *         terminal = state;
+ *       }
+ *     }
+ *   }
+ *   
+ *   return oldGetPerimeterBounds.apply(this, arguments);
+ * };
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * terminal - <mxCellState> that represents the terminal.
+ * border - Number that adds a border between the shape and the perimeter.
+ */
+mxGraphView.prototype.getPerimeterBounds = function(terminal, border)
+{
+	border = (border != null) ? border : 0;
+
+	if (terminal != null)
+	{
+		border += parseFloat(terminal.style[mxConstants.STYLE_PERIMETER_SPACING] || 0);
+	}
+
+	return terminal.getPerimeterBounds(border * this.scale);
+};
+
+/**
+ * Function: getPerimeterFunction
+ *
+ * Returns the perimeter function for the given state.
+ */
+mxGraphView.prototype.getPerimeterFunction = function(state)
+{
+	var perimeter = state.style[mxConstants.STYLE_PERIMETER];
+
+	// Converts string values to objects
+	if (typeof(perimeter) == "string")
+	{
+		var tmp = mxStyleRegistry.getValue(perimeter);
+		
+		if (tmp == null && this.isAllowEval())
+		{
+ 			tmp = mxUtils.eval(perimeter);
+		}
+
+		perimeter = tmp;
+	}
+	
+	if (typeof(perimeter) == "function")
+	{
+		return perimeter;
+	}
+	
+	return null;
+};
+
+/**
+ * Function: getNextPoint
+ *
+ * Returns the nearest point in the list of absolute points or the center
+ * of the opposite terminal.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCellState> that represents the edge.
+ * opposite - <mxCellState> that represents the opposite terminal.
+ * source - Boolean indicating if the next point for the source or target
+ * should be returned.
+ */
+mxGraphView.prototype.getNextPoint = function(edge, opposite, source)
+{
+	var pts = edge.absolutePoints;
+	var point = null;
+	
+	if (pts != null && pts.length >= 2)
+	{
+		var count = pts.length;
+		point = pts[(source) ? Math.min(1, count - 1) : Math.max(0, count - 2)];
+	}
+	
+	if (point == null && opposite != null)
+	{
+		point = new mxPoint(opposite.getCenterX(), opposite.getCenterY());
+	}
+	
+	return point;
+};
+
+/**
+ * Function: getVisibleTerminal
+ *
+ * Returns the nearest ancestor terminal that is visible. The edge appears
+ * to be connected to this terminal on the display. The result of this method
+ * is cached in <mxCellState.getVisibleTerminalState>.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> whose visible terminal should be returned.
+ * source - Boolean that specifies if the source or target terminal
+ * should be returned.
+ */
+mxGraphView.prototype.getVisibleTerminal = function(edge, source)
+{
+	var model = this.graph.getModel();
+	var result = model.getTerminal(edge, source);
+	var best = result;
+	
+	while (result != null && result != this.currentRoot)
+	{
+		if (!this.graph.isCellVisible(best) || this.isCellCollapsed(result))
+		{
+			best = result;
+		}
+		
+		result = model.getParent(result);
+	}
+
+	// Checks if the result is not a layer
+	if (model.getParent(best) == model.getRoot())
+	{
+		best = null;
+	}
+	
+	return best;
+};
+
+/**
+ * Function: updateEdgeBounds
+ *
+ * Updates the given state using the bounding box of t
+ * he absolute points.
+ * Also updates <mxCellState.terminalDistance>, <mxCellState.length> and
+ * <mxCellState.segments>.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose bounds should be updated.
+ */
+mxGraphView.prototype.updateEdgeBounds = function(state)
+{
+	var points = state.absolutePoints;
+	var p0 = points[0];
+	var pe = points[points.length - 1];
+	
+	if (p0.x != pe.x || p0.y != pe.y)
+	{
+		var dx = pe.x - p0.x;
+		var dy = pe.y - p0.y;
+		state.terminalDistance = Math.sqrt(dx * dx + dy * dy);
+	}
+	else
+	{
+		state.terminalDistance = 0;
+	}
+	
+	var length = 0;
+	var segments = [];
+	var pt = p0;
+	
+	if (pt != null)
+	{
+		var minX = pt.x;
+		var minY = pt.y;
+		var maxX = minX;
+		var maxY = minY;
+		
+		for (var i = 1; i < points.length; i++)
+		{
+			var tmp = points[i];
+			
+			if (tmp != null)
+			{
+				var dx = pt.x - tmp.x;
+				var dy = pt.y - tmp.y;
+				
+				var segment = Math.sqrt(dx * dx + dy * dy);
+				segments.push(segment);
+				length += segment;
+				
+				pt = tmp;
+				
+				minX = Math.min(pt.x, minX);
+				minY = Math.min(pt.y, minY);
+				maxX = Math.max(pt.x, maxX);
+				maxY = Math.max(pt.y, maxY);
+			}
+		}
+		
+		state.length = length;
+		state.segments = segments;
+		
+		var markerSize = 1; // TODO: include marker size
+		
+		state.x = minX;
+		state.y = minY;
+		state.width = Math.max(markerSize, maxX - minX);
+		state.height = Math.max(markerSize, maxY - minY);
+	}
+};
+
+/**
+ * Function: getPoint
+ *
+ * Returns the absolute point on the edge for the given relative
+ * <mxGeometry> as an <mxPoint>. The edge is represented by the given
+ * <mxCellState>.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> that represents the state of the parent edge.
+ * geometry - <mxGeometry> that represents the relative location.
+ */
+mxGraphView.prototype.getPoint = function(state, geometry)
+{
+	var x = state.getCenterX();
+	var y = state.getCenterY();
+	
+	if (state.segments != null && (geometry == null || geometry.relative))
+	{
+		var gx = (geometry != null) ? geometry.x / 2 : 0;
+		var pointCount = state.absolutePoints.length;
+		var dist = Math.round((gx + 0.5) * state.length);
+		var segment = state.segments[0];
+		var length = 0;				
+		var index = 1;
+
+		while (dist >= Math.round(length + segment) && index < pointCount - 1)
+		{
+			length += segment;
+			segment = state.segments[index++];
+		}
+
+		var factor = (segment == 0) ? 0 : (dist - length) / segment;
+		var p0 = state.absolutePoints[index-1];
+		var pe = state.absolutePoints[index];
+
+		if (p0 != null && pe != null)
+		{
+			var gy = 0;
+			var offsetX = 0;
+			var offsetY = 0;
+
+			if (geometry != null)
+			{
+				gy = geometry.y;
+				var offset = geometry.offset;
+				
+				if (offset != null)
+				{
+					offsetX = offset.x;
+					offsetY = offset.y;
+				}
+			}
+
+			var dx = pe.x - p0.x;
+			var dy = pe.y - p0.y;
+			var nx = (segment == 0) ? 0 : dy / segment;
+			var ny = (segment == 0) ? 0 : dx / segment;
+			
+			x = p0.x + dx * factor + (nx * gy + offsetX) * this.scale;
+			y = p0.y + dy * factor - (ny * gy - offsetY) * this.scale;
+		}
+	}
+	else if (geometry != null)
+	{
+		var offset = geometry.offset;
+		
+		if (offset != null)
+		{
+			x += offset.x;
+			y += offset.y;
+		}
+	}
+	
+	return new mxPoint(x, y);		
+};
+
+/**
+ * Function: getRelativePoint
+ *
+ * Gets the relative point that describes the given, absolute label
+ * position for the given edge state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> that represents the state of the parent edge.
+ * x - Specifies the x-coordinate of the absolute label location.
+ * y - Specifies the y-coordinate of the absolute label location.
+ */
+mxGraphView.prototype.getRelativePoint = function(edgeState, x, y)
+{
+	var model = this.graph.getModel();
+	var geometry = model.getGeometry(edgeState.cell);
+	
+	if (geometry != null)
+	{
+		var pointCount = edgeState.absolutePoints.length;
+		
+		if (geometry.relative && pointCount > 1)
+		{
+			var totalLength = edgeState.length;
+			var segments = edgeState.segments;
+
+			// Works which line segment the point of the label is closest to
+			var p0 = edgeState.absolutePoints[0];
+			var pe = edgeState.absolutePoints[1];
+			var minDist = mxUtils.ptSegDistSq(p0.x, p0.y, pe.x, pe.y, x, y);
+
+			var index = 0;
+			var tmp = 0;
+			var length = 0;
+			
+			for (var i = 2; i < pointCount; i++)
+			{
+				tmp += segments[i - 2];
+				pe = edgeState.absolutePoints[i];
+				var dist = mxUtils.ptSegDistSq(p0.x, p0.y, pe.x, pe.y, x, y);
+
+				if (dist <= minDist)
+				{
+					minDist = dist;
+					index = i - 1;
+					length = tmp;
+				}
+				
+				p0 = pe;
+			}
+			
+			var seg = segments[index];
+			p0 = edgeState.absolutePoints[index];
+			pe = edgeState.absolutePoints[index + 1];
+			
+			var x2 = p0.x;
+			var y2 = p0.y;
+			
+			var x1 = pe.x;
+			var y1 = pe.y;
+			
+			var px = x;
+			var py = y;
+			
+			var xSegment = x2 - x1;
+			var ySegment = y2 - y1;
+			
+			px -= x1;
+			py -= y1;
+			var projlenSq = 0;
+			
+			px = xSegment - px;
+			py = ySegment - py;
+			var dotprod = px * xSegment + py * ySegment;
+
+			if (dotprod <= 0.0)
+			{
+				projlenSq = 0;
+			}
+			else
+			{
+				projlenSq = dotprod * dotprod
+						/ (xSegment * xSegment + ySegment * ySegment);
+			}
+
+			var projlen = Math.sqrt(projlenSq);
+
+			if (projlen > seg)
+			{
+				projlen = seg;
+			}
+
+			var yDistance = Math.sqrt(mxUtils.ptSegDistSq(p0.x, p0.y, pe
+					.x, pe.y, x, y));
+			var direction = mxUtils.relativeCcw(p0.x, p0.y, pe.x, pe.y, x, y);
+
+			if (direction == -1)
+			{
+				yDistance = -yDistance;
+			}
+
+			// Constructs the relative point for the label
+			return new mxPoint(((totalLength / 2 - length - projlen) / totalLength) * -2,
+						yDistance / this.scale);
+		}
+	}
+	
+	return new mxPoint();
+};
+
+/**
+ * Function: updateEdgeLabelOffset
+ *
+ * Updates <mxCellState.absoluteOffset> for the given state. The absolute
+ * offset is normally used for the position of the edge label. Is is
+ * calculated from the geometry as an absolute offset from the center
+ * between the two endpoints if the geometry is absolute, or as the
+ * relative distance between the center along the line and the absolute
+ * orthogonal distance if the geometry is relative.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose absolute offset should be updated.
+ */
+mxGraphView.prototype.updateEdgeLabelOffset = function(state)
+{
+	var points = state.absolutePoints;
+	
+	state.absoluteOffset.x = state.getCenterX();
+	state.absoluteOffset.y = state.getCenterY();
+
+	if (points != null && points.length > 0 && state.segments != null)
+	{
+		var geometry = this.graph.getCellGeometry(state.cell);
+		
+		if (geometry.relative)
+		{
+			var offset = this.getPoint(state, geometry);
+			
+			if (offset != null)
+			{
+				state.absoluteOffset = offset;
+			}
+		}
+		else
+		{
+			var p0 = points[0];
+			var pe = points[points.length - 1];
+			
+			if (p0 != null && pe != null)
+			{
+				var dx = pe.x - p0.x;
+				var dy = pe.y - p0.y;
+				var x0 = 0;
+				var y0 = 0;
+
+				var off = geometry.offset;
+				
+				if (off != null)
+				{
+					x0 = off.x;
+					y0 = off.y;
+				}
+				
+				var x = p0.x + dx / 2 + x0 * this.scale;
+				var y = p0.y + dy / 2 + y0 * this.scale;
+				
+				state.absoluteOffset.x = x;
+				state.absoluteOffset.y = y;
+			}
+		}
+	}
+};
+
+/**
+ * Function: getState
+ *
+ * Returns the <mxCellState> for the given cell. If create is true, then
+ * the state is created if it does not yet exist.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> for which the <mxCellState> should be returned.
+ * create - Optional boolean indicating if a new state should be created
+ * if it does not yet exist. Default is false.
+ */
+mxGraphView.prototype.getState = function(cell, create)
+{
+	create = create || false;
+	var state = null;
+	
+	if (cell != null)
+	{
+		state = this.states.get(cell);
+		
+		if (create && (state == null || this.updateStyle) && this.graph.isCellVisible(cell))
+		{
+			if (state == null)
+			{
+				state = this.createState(cell);
+				this.states.put(cell, state);
+			}
+			else
+			{
+				state.style = this.graph.getCellStyle(cell);
+			}
+		}
+	}
+
+	return state;
+};
+
+/**
+ * Function: isRendering
+ *
+ * Returns <rendering>.
+ */
+mxGraphView.prototype.isRendering = function()
+{
+	return this.rendering;
+};
+
+/**
+ * Function: setRendering
+ *
+ * Sets <rendering>.
+ */
+mxGraphView.prototype.setRendering = function(value)
+{
+	this.rendering = value;
+};
+
+/**
+ * Function: isAllowEval
+ *
+ * Returns <allowEval>.
+ */
+mxGraphView.prototype.isAllowEval = function()
+{
+	return this.allowEval;
+};
+
+/**
+ * Function: setAllowEval
+ *
+ * Sets <allowEval>.
+ */
+mxGraphView.prototype.setAllowEval = function(value)
+{
+	this.allowEval = value;
+};
+
+/**
+ * Function: getStates
+ *
+ * Returns <states>.
+ */
+mxGraphView.prototype.getStates = function()
+{
+	return this.states;
+};
+
+/**
+ * Function: setStates
+ *
+ * Sets <states>.
+ */
+mxGraphView.prototype.setStates = function(value)
+{
+	this.states = value;
+};
+
+/**
+ * Function: getCellStates
+ *
+ * Returns the <mxCellStates> for the given array of <mxCells>. The array
+ * contains all states that are not null, that is, the returned array may
+ * have less elements than the given array. If no argument is given, then
+ * this returns <states>.
+ */
+mxGraphView.prototype.getCellStates = function(cells)
+{
+	if (cells == null)
+	{
+		return this.states;
+	}
+	else
+	{
+		var result = [];
+		
+		for (var i = 0; i < cells.length; i++)
+		{
+			var state = this.getState(cells[i]);
+			
+			if (state != null)
+			{
+				result.push(state);
+			}
+		}
+		
+		return result;
+	}
+};
+
+/**
+ * Function: removeState
+ *
+ * Removes and returns the <mxCellState> for the given cell.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> for which the <mxCellState> should be removed.
+ */
+mxGraphView.prototype.removeState = function(cell)
+{
+	var state = null;
+	
+	if (cell != null)
+	{
+		state = this.states.remove(cell);
+		
+		if (state != null)
+		{
+			this.graph.cellRenderer.destroy(state);
+			state.invalid = true;
+			state.destroy();
+		}
+	}
+	
+	return state;
+};
+
+/**
+ * Function: createState
+ *
+ * Creates and returns an <mxCellState> for the given cell and initializes
+ * it using <mxCellRenderer.initialize>.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> for which a new <mxCellState> should be created.
+ */
+mxGraphView.prototype.createState = function(cell)
+{
+	return new mxCellState(this, cell, this.graph.getCellStyle(cell));
+};
+
+/**
+ * Function: getCanvas
+ *
+ * Returns the DOM node that contains the background-, draw- and
+ * overlay- and decoratorpanes.
+ */
+mxGraphView.prototype.getCanvas = function()
+{
+	return this.canvas;
+};
+
+/**
+ * Function: getBackgroundPane
+ *
+ * Returns the DOM node that represents the background layer.
+ */
+mxGraphView.prototype.getBackgroundPane = function()
+{
+	return this.backgroundPane;
+};
+
+/**
+ * Function: getDrawPane
+ *
+ * Returns the DOM node that represents the main drawing layer.
+ */
+mxGraphView.prototype.getDrawPane = function()
+{
+	return this.drawPane;
+};
+
+/**
+ * Function: getOverlayPane
+ *
+ * Returns the DOM node that represents the layer above the drawing layer.
+ */
+mxGraphView.prototype.getOverlayPane = function()
+{
+	return this.overlayPane;
+};
+
+/**
+ * Function: getDecoratorPane
+ *
+ * Returns the DOM node that represents the topmost drawing layer.
+ */
+mxGraphView.prototype.getDecoratorPane = function()
+{
+	return this.decoratorPane;
+};
+
+/**
+ * Function: isContainerEvent
+ * 
+ * Returns true if the event origin is one of the drawing panes or
+ * containers of the view.
+ */
+mxGraphView.prototype.isContainerEvent = function(evt)
+{
+	var source = mxEvent.getSource(evt);
+
+	return (source == this.graph.container ||
+		source.parentNode == this.backgroundPane ||
+		(source.parentNode != null &&
+		source.parentNode.parentNode == this.backgroundPane) ||
+		source == this.canvas.parentNode ||
+		source == this.canvas ||
+		source == this.backgroundPane ||
+		source == this.drawPane ||
+		source == this.overlayPane ||
+		source == this.decoratorPane);
+};
+
+/**
+ * Function: isScrollEvent
+ * 
+ * Returns true if the event origin is one of the scrollbars of the
+ * container in IE. Such events are ignored.
+ */
+ mxGraphView.prototype.isScrollEvent = function(evt)
+{
+	var offset = mxUtils.getOffset(this.graph.container);
+	var pt = new mxPoint(evt.clientX - offset.x, evt.clientY - offset.y);
+
+	var outWidth = this.graph.container.offsetWidth;
+	var inWidth = this.graph.container.clientWidth;
+
+	if (outWidth > inWidth && pt.x > inWidth + 2 && pt.x <= outWidth)
+	{
+		return true;
+	}
+
+	var outHeight = this.graph.container.offsetHeight;
+	var inHeight = this.graph.container.clientHeight;
+	
+	if (outHeight > inHeight && pt.y > inHeight + 2 && pt.y <= outHeight)
+	{
+		return true;
+	}
+	
+	return false;
+};
+
+/**
+ * Function: init
+ *
+ * Initializes the graph event dispatch loop for the specified container
+ * and invokes <create> to create the required DOM nodes for the display.
+ */
+mxGraphView.prototype.init = function()
+{
+	this.installListeners();
+	
+	// Creates the DOM nodes for the respective display dialect
+	var graph = this.graph;
+	
+	if (graph.dialect == mxConstants.DIALECT_SVG)
+	{
+		this.createSvg();
+	}
+	else if (graph.dialect == mxConstants.DIALECT_VML)
+	{
+		this.createVml();
+	}
+	else
+	{
+		this.createHtml();
+	}
+};
+
+/**
+ * Function: installListeners
+ *
+ * Installs the required listeners in the container.
+ */
+mxGraphView.prototype.installListeners = function()
+{
+	var graph = this.graph;
+	var container = graph.container;
+	
+	if (container != null)
+	{
+		// Support for touch device gestures (eg. pinch to zoom)
+		// Double-tap handling is implemented in mxGraph.fireMouseEvent
+		if (mxClient.IS_TOUCH)
+		{
+			mxEvent.addListener(container, 'gesturestart', mxUtils.bind(this, function(evt)
+			{
+				graph.fireGestureEvent(evt);
+				mxEvent.consume(evt);
+			}));
+			
+			mxEvent.addListener(container, 'gesturechange', mxUtils.bind(this, function(evt)
+			{
+				graph.fireGestureEvent(evt);
+				mxEvent.consume(evt);
+			}));
+
+			mxEvent.addListener(container, 'gestureend', mxUtils.bind(this, function(evt)
+			{
+				graph.fireGestureEvent(evt);
+				mxEvent.consume(evt);
+			}));
+		}
+		
+		// Adds basic listeners for graph event dispatching
+		mxEvent.addGestureListeners(container, mxUtils.bind(this, function(evt)
+		{
+			// Condition to avoid scrollbar events starting a rubberband selection
+			if (this.isContainerEvent(evt) && ((!mxClient.IS_IE && !mxClient.IS_IE11 && !mxClient.IS_GC &&
+				!mxClient.IS_OP && !mxClient.IS_SF) || !this.isScrollEvent(evt)))
+			{
+				graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt));
+			}
+		}),
+		mxUtils.bind(this, function(evt)
+		{
+			if (this.isContainerEvent(evt))
+			{
+				graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt));
+			}
+		}),
+		mxUtils.bind(this, function(evt)
+		{
+			if (this.isContainerEvent(evt))
+			{
+				graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt));
+			}
+		}));
+		
+		// Adds listener for double click handling on background, this does always
+		// use native event handler, we assume that the DOM of the background
+		// does not change during the double click
+		mxEvent.addListener(container, 'dblclick', mxUtils.bind(this, function(evt)
+		{
+			if (this.isContainerEvent(evt))
+			{
+				graph.dblClick(evt);
+			}
+		}));
+
+		// Workaround for touch events which started on some DOM node
+		// on top of the container, in which case the cells under the
+		// mouse for the move and up events are not detected.
+		var getState = function(evt)
+		{
+			var state = null;
+			
+			// Workaround for touch events which started on some DOM node
+			// on top of the container, in which case the cells under the
+			// mouse for the move and up events are not detected.
+			if (mxClient.IS_TOUCH)
+			{
+				var x = mxEvent.getClientX(evt);
+				var y = mxEvent.getClientY(evt);
+				
+				// Dispatches the drop event to the graph which
+				// consumes and executes the source function
+				var pt = mxUtils.convertPoint(container, x, y);
+				state = graph.view.getState(graph.getCellAt(pt.x, pt.y));
+			}
+			
+			return state;
+		};
+		
+		// Adds basic listeners for graph event dispatching outside of the
+		// container and finishing the handling of a single gesture
+		// Implemented via graph event dispatch loop to avoid duplicate events
+		// in Firefox and Chrome
+		graph.addMouseListener(
+		{
+			mouseDown: function(sender, me)
+			{
+				graph.popupMenuHandler.hideMenu();
+			},
+			mouseMove: function() { },
+			mouseUp: function() { }
+		});
+		
+		this.moveHandler = mxUtils.bind(this, function(evt)
+		{
+			// Hides the tooltip if mouse is outside container
+			if (graph.tooltipHandler != null && graph.tooltipHandler.isHideOnHover())
+			{
+				graph.tooltipHandler.hide();
+			}
+
+			if (this.captureDocumentGesture && graph.isMouseDown && graph.container != null &&
+				!this.isContainerEvent(evt) && graph.container.style.display != 'none' &&
+				graph.container.style.visibility != 'hidden' && !mxEvent.isConsumed(evt))
+			{
+				graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, getState(evt)));
+			}
+		});
+		
+		this.endHandler = mxUtils.bind(this, function(evt)
+		{
+			if (this.captureDocumentGesture && graph.isMouseDown && graph.container != null &&
+				!this.isContainerEvent(evt) && graph.container.style.display != 'none' &&
+				graph.container.style.visibility != 'hidden')
+			{
+				graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt));
+			}
+		});
+		
+		mxEvent.addGestureListeners(document, null, this.moveHandler, this.endHandler);
+	}
+};
+
+/**
+ * Function: create
+ *
+ * Creates the DOM nodes for the HTML display.
+ */
+mxGraphView.prototype.createHtml = function()
+{
+	var container = this.graph.container;
+	
+	if (container != null)
+	{
+		this.canvas = this.createHtmlPane('100%', '100%');
+		this.canvas.style.overflow = 'hidden';
+	
+		// Uses minimal size for inner DIVs on Canvas. This is required
+		// for correct event processing in IE. If we have an overlapping
+		// DIV then the events on the cells are only fired for labels.
+		this.backgroundPane = this.createHtmlPane('1px', '1px');
+		this.drawPane = this.createHtmlPane('1px', '1px');
+		this.overlayPane = this.createHtmlPane('1px', '1px');
+		this.decoratorPane = this.createHtmlPane('1px', '1px');
+		
+		this.canvas.appendChild(this.backgroundPane);
+		this.canvas.appendChild(this.drawPane);
+		this.canvas.appendChild(this.overlayPane);
+		this.canvas.appendChild(this.decoratorPane);
+
+		container.appendChild(this.canvas);
+		this.updateContainerStyle(container);
+		
+		// Implements minWidth/minHeight in quirks mode
+		if (mxClient.IS_QUIRKS)
+		{
+			var onResize = mxUtils.bind(this, function(evt)
+			{
+				var bounds = this.getGraphBounds();
+				var width = bounds.x + bounds.width + this.graph.border;
+				var height = bounds.y + bounds.height + this.graph.border;
+				
+				this.updateHtmlCanvasSize(width, height);
+			});
+			
+			mxEvent.addListener(window, 'resize', onResize);
+		}
+	}
+};
+
+/**
+ * Function: updateHtmlCanvasSize
+ * 
+ * Updates the size of the HTML canvas.
+ */
+mxGraphView.prototype.updateHtmlCanvasSize = function(width, height)
+{
+	if (this.graph.container != null)
+	{
+		var ow = this.graph.container.offsetWidth;
+		var oh = this.graph.container.offsetHeight;
+
+		if (ow < width)
+		{
+			this.canvas.style.width = width + 'px';
+		}
+		else
+		{
+			this.canvas.style.width = '100%';
+		}
+
+		if (oh < height)
+		{
+			this.canvas.style.height = height + 'px';
+		}
+		else
+		{
+			this.canvas.style.height = '100%';
+		}
+	}
+};
+
+/**
+ * Function: createHtmlPane
+ * 
+ * Creates and returns a drawing pane in HTML (DIV).
+ */
+mxGraphView.prototype.createHtmlPane = function(width, height)
+{
+	var pane = document.createElement('DIV');
+	
+	if (width != null && height != null)
+	{
+		pane.style.position = 'absolute';
+		pane.style.left = '0px';
+		pane.style.top = '0px';
+
+		pane.style.width = width;
+		pane.style.height = height;
+	}
+	else
+	{
+		pane.style.position = 'relative';
+	}
+	
+	return pane;
+};
+
+/**
+ * Function: create
+ *
+ * Creates the DOM nodes for the VML display.
+ */
+mxGraphView.prototype.createVml = function()
+{
+	var container = this.graph.container;
+
+	if (container != null)
+	{
+		var width = container.offsetWidth;
+		var height = container.offsetHeight;
+		this.canvas = this.createVmlPane(width, height);
+		this.canvas.style.overflow = 'hidden';
+		
+		this.backgroundPane = this.createVmlPane(width, height);
+		this.drawPane = this.createVmlPane(width, height);
+		this.overlayPane = this.createVmlPane(width, height);
+		this.decoratorPane = this.createVmlPane(width, height);
+		
+		this.canvas.appendChild(this.backgroundPane);
+		this.canvas.appendChild(this.drawPane);
+		this.canvas.appendChild(this.overlayPane);
+		this.canvas.appendChild(this.decoratorPane);
+		
+		container.appendChild(this.canvas);
+	}
+};
+
+/**
+ * Function: createVmlPane
+ * 
+ * Creates a drawing pane in VML (group).
+ */
+mxGraphView.prototype.createVmlPane = function(width, height)
+{
+	var pane = document.createElement(mxClient.VML_PREFIX + ':group');
+	
+	// At this point the width and height are potentially
+	// uninitialized. That's OK.
+	pane.style.position = 'absolute';
+	pane.style.left = '0px';
+	pane.style.top = '0px';
+
+	pane.style.width = width + 'px';
+	pane.style.height = height + 'px';
+
+	pane.setAttribute('coordsize', width + ',' + height);
+	pane.setAttribute('coordorigin', '0,0');
+	
+	return pane;
+};
+
+/**
+ * Function: create
+ *
+ * Creates and returns the DOM nodes for the SVG display.
+ */
+mxGraphView.prototype.createSvg = function()
+{
+	var container = this.graph.container;
+	this.canvas = document.createElementNS(mxConstants.NS_SVG, 'g');
+	
+	// For background image
+	this.backgroundPane = document.createElementNS(mxConstants.NS_SVG, 'g');
+	this.canvas.appendChild(this.backgroundPane);
+
+	// Adds two layers (background is early feature)
+	this.drawPane = document.createElementNS(mxConstants.NS_SVG, 'g');
+	this.canvas.appendChild(this.drawPane);
+
+	this.overlayPane = document.createElementNS(mxConstants.NS_SVG, 'g');
+	this.canvas.appendChild(this.overlayPane);
+	
+	this.decoratorPane = document.createElementNS(mxConstants.NS_SVG, 'g');
+	this.canvas.appendChild(this.decoratorPane);
+	
+	var root = document.createElementNS(mxConstants.NS_SVG, 'svg');
+	root.style.width = '100%';
+	root.style.height = '100%';
+	
+	// NOTE: In standards mode, the SVG must have block layout
+	// in order for the container DIV to not show scrollbars.
+	root.style.display = 'block';
+	root.appendChild(this.canvas);
+	
+	// Workaround for scrollbars in IE11 and below
+	if (mxClient.IS_IE || mxClient.IS_IE11)
+	{
+		root.style.overflow = 'hidden';
+	}
+
+	if (container != null)
+	{
+		container.appendChild(root);
+		this.updateContainerStyle(container);
+	}
+};
+
+/**
+ * Function: updateContainerStyle
+ * 
+ * Updates the style of the container after installing the SVG DOM elements.
+ */
+mxGraphView.prototype.updateContainerStyle = function(container)
+{
+	// Workaround for offset of container
+	var style = mxUtils.getCurrentStyle(container);
+	
+	if (style != null && style.position == 'static')
+	{
+		container.style.position = 'relative';
+	}
+	
+	// Disables built-in pan and zoom in IE10 and later
+	if (mxClient.IS_POINTER)
+	{
+		container.style.touchAction = 'none';
+	}
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the view and all its resources.
+ */
+mxGraphView.prototype.destroy = function()
+{
+	var root = (this.canvas != null) ? this.canvas.ownerSVGElement : null;
+	
+	if (root == null)
+	{
+		root = this.canvas;
+	}
+	
+	if (root != null && root.parentNode != null)
+	{
+		this.clear(this.currentRoot, true);
+		mxEvent.removeGestureListeners(document, null, this.moveHandler, this.endHandler);
+		mxEvent.release(this.graph.container);
+		root.parentNode.removeChild(root);
+		
+		this.moveHandler = null;
+		this.endHandler = null;
+		this.canvas = null;
+		this.backgroundPane = null;
+		this.drawPane = null;
+		this.overlayPane = null;
+		this.decoratorPane = null;
+	}
+};
+
+/**
+ * Class: mxCurrentRootChange
+ *
+ * Action to change the current root in a view.
+ *
+ * Constructor: mxCurrentRootChange
+ *
+ * Constructs a change of the current root in the given view.
+ */
+function mxCurrentRootChange(view, root)
+{
+	this.view = view;
+	this.root = root;
+	this.previous = root;
+	this.isUp = root == null;
+	
+	if (!this.isUp)
+	{
+		var tmp = this.view.currentRoot;
+		var model = this.view.graph.getModel();
+		
+		while (tmp != null)
+		{
+			if (tmp == root)
+			{
+				this.isUp = true;
+				break;
+			}
+			
+			tmp = model.getParent(tmp);
+		}
+	}
+};
+
+/**
+ * Function: execute
+ *
+ * Changes the current root of the view.
+ */
+mxCurrentRootChange.prototype.execute = function()
+{
+	var tmp = this.view.currentRoot;
+	this.view.currentRoot = this.previous;
+	this.previous = tmp;
+
+	var translate = this.view.graph.getTranslateForRoot(this.view.currentRoot);
+	
+	if (translate != null)
+	{
+		this.view.translate = new mxPoint(-translate.x, -translate.y);
+	}
+
+	if (this.isUp)
+	{
+		this.view.clear(this.view.currentRoot, true);
+		this.view.validate();
+	}
+	else
+	{
+		this.view.refresh();
+	}
+	
+	var name = (this.isUp) ? mxEvent.UP : mxEvent.DOWN;
+	this.view.fireEvent(new mxEventObject(name,
+		'root', this.view.currentRoot, 'previous', this.previous));
+	this.isUp = !this.isUp;
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/view/mxLayoutManager.js b/airavata-kubernetes/web-console/src/assets/js/view/mxLayoutManager.js
new file mode 100644
index 0000000..346af84
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/view/mxLayoutManager.js
@@ -0,0 +1,409 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxLayoutManager
+ * 
+ * Implements a layout manager that runs a given layout after any changes to the graph:
+ * 
+ * Example:
+ * 
+ * (code)
+ * var layoutMgr = new mxLayoutManager(graph);
+ * layoutMgr.getLayout = function(cell)
+ * {
+ *   return layout;
+ * };
+ * (end)
+ * 
+ * Event: mxEvent.LAYOUT_CELLS
+ * 
+ * Fires between begin- and endUpdate after all cells have been layouted in
+ * <layoutCells>. The <code>cells</code> property contains all cells that have
+ * been passed to <layoutCells>.
+ * 
+ * Constructor: mxLayoutManager
+ *
+ * Constructs a new automatic layout for the given graph.
+ *
+ * Arguments:
+ * 
+ * graph - Reference to the enclosing graph. 
+ */
+function mxLayoutManager(graph)
+{
+	// Executes the layout before the changes are dispatched
+	this.undoHandler = mxUtils.bind(this, function(sender, evt)
+	{
+		if (this.isEnabled())
+		{
+			this.beforeUndo(evt.getProperty('edit'));
+		}
+	});
+	
+	// Notifies the layout of a move operation inside a parent
+	this.moveHandler = mxUtils.bind(this, function(sender, evt)
+	{
+		if (this.isEnabled())
+		{
+			this.cellsMoved(evt.getProperty('cells'), evt.getProperty('event'));
+		}
+	});
+	
+	this.setGraph(graph);
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxLayoutManager.prototype = new mxEventSource();
+mxLayoutManager.prototype.constructor = mxLayoutManager;
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxLayoutManager.prototype.graph = null;
+
+/**
+ * Variable: bubbling
+ * 
+ * Specifies if the layout should bubble along
+ * the cell hierarchy. Default is true.
+ */
+mxLayoutManager.prototype.bubbling = true;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies if event handling is enabled. Default is true.
+ */
+mxLayoutManager.prototype.enabled = true;
+
+/**
+ * Variable: updateHandler
+ * 
+ * Holds the function that handles the endUpdate event.
+ */
+mxLayoutManager.prototype.updateHandler = null;
+
+/**
+ * Variable: moveHandler
+ * 
+ * Holds the function that handles the move event.
+ */
+mxLayoutManager.prototype.moveHandler = null;
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxLayoutManager.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ * 
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ * 
+ * Parameters:
+ * 
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxLayoutManager.prototype.setEnabled = function(enabled)
+{
+	this.enabled = enabled;
+};
+
+/**
+ * Function: isBubbling
+ * 
+ * Returns true if a layout should bubble, that is, if the parent layout
+ * should be executed whenever a cell layout (layout of the children of
+ * a cell) has been executed. This implementation returns <bubbling>.
+ */
+mxLayoutManager.prototype.isBubbling = function()
+{
+	return this.bubbling;
+};
+
+/**
+ * Function: setBubbling
+ * 
+ * Sets <bubbling>.
+ */
+mxLayoutManager.prototype.setBubbling = function(value)
+{
+	this.bubbling = value;
+};
+
+/**
+ * Function: getGraph
+ * 
+ * Returns the graph that this layout operates on.
+ */
+mxLayoutManager.prototype.getGraph = function()
+{
+	return this.graph;
+};
+
+/**
+ * Function: setGraph
+ * 
+ * Sets the graph that the layouts operate on.
+ */
+mxLayoutManager.prototype.setGraph = function(graph)
+{
+	if (this.graph != null)
+	{
+		var model = this.graph.getModel();		
+		model.removeListener(this.undoHandler);
+		this.graph.removeListener(this.moveHandler);
+	}
+	
+	this.graph = graph;
+	
+	if (this.graph != null)
+	{
+		var model = this.graph.getModel();	
+		model.addListener(mxEvent.BEFORE_UNDO, this.undoHandler);
+		this.graph.addListener(mxEvent.MOVE_CELLS, this.moveHandler);
+	}
+};
+
+/**
+ * Function: getLayout
+ * 
+ * Returns the layout to be executed for the given graph and parent.
+ */
+mxLayoutManager.prototype.getLayout = function(parent)
+{
+	return null;
+};
+
+/**
+ * Function: beforeUndo
+ * 
+ * Called from the undoHandler.
+ *
+ * Parameters:
+ * 
+ * cell - Array of <mxCells> that have been moved.
+ * evt - Mouse event that represents the mousedown.
+ */
+mxLayoutManager.prototype.beforeUndo = function(undoableEdit)
+{
+	var cells = this.getCellsForChanges(undoableEdit.changes);
+	var model = this.getGraph().getModel();
+
+	// Adds all descendants
+	var tmp = [];
+	
+	for (var i = 0; i < cells.length; i++)
+	{
+		tmp = tmp.concat(model.getDescendants(cells[i]));
+	}
+	
+	cells = tmp;
+	
+	// Adds all parent ancestors
+	if (this.isBubbling())
+	{
+		tmp = model.getParents(cells);
+		
+		while (tmp.length > 0)
+		{
+			cells = cells.concat(tmp);
+			tmp = model.getParents(tmp);
+		}
+	}
+	
+	this.executeLayoutForCells(cells);
+};
+
+/**
+ * Function: executeLayout
+ * 
+ * Executes the given layout on the given parent.
+ */
+mxLayoutManager.prototype.executeLayoutForCells = function(cells)
+{
+	// Adds reverse to this array to avoid duplicate execution of leafes
+	// Works like capture/bubble for events, first executes all layout
+	// from top to bottom and in reverse order and removes duplicates.
+	var sorted = mxUtils.sortCells(cells, true);
+	sorted = sorted.concat(sorted.slice().reverse());
+	this.layoutCells(sorted);
+};
+
+/**
+ * Function: cellsMoved
+ * 
+ * Called from the moveHandler.
+ *
+ * Parameters:
+ * 
+ * cell - Array of <mxCells> that have been moved.
+ * evt - Mouse event that represents the mousedown.
+ */
+mxLayoutManager.prototype.cellsMoved = function(cells, evt)
+{
+	if (cells != null && evt != null)
+	{
+		var point = mxUtils.convertPoint(this.getGraph().container,
+			mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+		var model = this.getGraph().getModel();
+		
+		// Checks if a layout exists to take care of the moving if the
+		// parent itself is not being moved
+		for (var i = 0; i < cells.length; i++)
+		{
+			var parent = model.getParent(cells[i]);
+			
+			if (mxUtils.indexOf(cells, parent) < 0)
+			{
+				var layout = this.getLayout(parent);
+	
+				if (layout != null)
+				{
+					layout.moveCell(cells[i], point.x, point.y);
+				}
+			}
+		}
+	}
+};
+
+/**
+ * Function: getCellsForEdit
+ * 
+ * Returns the cells to be layouted for the given sequence of changes.
+ */
+mxLayoutManager.prototype.getCellsForChanges = function(changes)
+{
+	var dict = new mxDictionary();
+	var result = [];
+	
+	for (var i = 0; i < changes.length; i++)
+	{
+		var change = changes[i];
+		
+		if (change instanceof mxRootChange)
+		{
+			return [];
+		}
+		else
+		{
+			var cells = this.getCellsForChange(change);
+			
+			for (var j = 0; j < cells.length; j++)
+			{
+				if (cells[j] != null && !dict.get(cells[j]))
+				{
+					dict.put(cells[j], true);
+					result.push(cells[j]);
+				}
+			}
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: getCellsForChange
+ * 
+ * Executes all layouts which have been scheduled during the
+ * changes.
+ */
+mxLayoutManager.prototype.getCellsForChange = function(change)
+{
+	var model = this.getGraph().getModel();
+	
+	if (change instanceof mxChildChange)
+	{
+		return [change.child, change.previous, model.getParent(change.child)];
+	}
+	else if (change instanceof mxTerminalChange || change instanceof mxGeometryChange)
+	{
+		return [change.cell, model.getParent(change.cell)];
+	}
+	else if (change instanceof mxVisibleChange || change instanceof mxStyleChange)
+	{
+		return [change.cell];
+	}
+	
+	return [];
+};
+
+/**
+ * Function: layoutCells
+ * 
+ * Executes all layouts which have been scheduled during the
+ * changes.
+ */
+mxLayoutManager.prototype.layoutCells = function(cells)
+{
+	if (cells.length > 0)
+	{
+		// Invokes the layouts while removing duplicates
+		var model = this.getGraph().getModel();
+		
+		model.beginUpdate();
+		try 
+		{
+			var last = null;
+			
+			for (var i = 0; i < cells.length; i++)
+			{
+				if (cells[i] != model.getRoot() && cells[i] != last)
+				{
+					if (this.executeLayout(this.getLayout(cells[i]), cells[i]))
+					{
+						last = cells[i];
+					}
+				}
+			}
+			
+			this.fireEvent(new mxEventObject(mxEvent.LAYOUT_CELLS, 'cells', cells));
+		}
+		finally
+		{
+			model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: executeLayout
+ * 
+ * Executes the given layout on the given parent.
+ */
+mxLayoutManager.prototype.executeLayout = function(layout, parent)
+{
+	var result = false;
+	
+	if (layout != null && parent != null)
+	{
+		layout.execute(parent);
+		result = true;
+	}
+	
+	return result;
+};
+
+/**
+ * Function: destroy
+ * 
+ * Removes all handlers from the <graph> and deletes the reference to it.
+ */
+mxLayoutManager.prototype.destroy = function()
+{
+	this.setGraph(null);
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/view/mxMultiplicity.js b/airavata-kubernetes/web-console/src/assets/js/view/mxMultiplicity.js
new file mode 100644
index 0000000..aa0e0b9
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/view/mxMultiplicity.js
@@ -0,0 +1,257 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxMultiplicity
+ * 
+ * Defines invalid connections along with the error messages that they produce.
+ * To add or remove rules on a graph, you must add/remove instances of this
+ * class to <mxGraph.multiplicities>.
+ * 
+ * Example:
+ * 
+ * (code)
+ * graph.multiplicities.push(new mxMultiplicity(
+ *   true, 'rectangle', null, null, 0, 2, ['circle'],
+ *   'Only 2 targets allowed',
+ *   'Only circle targets allowed'));
+ * (end)
+ * 
+ * Defines a rule where each rectangle must be connected to no more than 2
+ * circles and no other types of targets are allowed.
+ * 
+ * Constructor: mxMultiplicity
+ * 
+ * Instantiate class mxMultiplicity in order to describe allowed
+ * connections in a graph. Not all constraints can be enforced while
+ * editing, some must be checked at validation time. The <countError> and
+ * <typeError> are treated as resource keys in <mxResources>.
+ * 
+ * Parameters:
+ * 
+ * source - Boolean indicating if this rule applies to the source or target
+ * terminal.
+ * type - Type of the source or target terminal that this rule applies to.
+ * See <type> for more information.
+ * attr - Optional attribute name to match the source or target terminal.
+ * value - Optional attribute value to match the source or target terminal.
+ * min - Minimum number of edges for this rule. Default is 1.
+ * max - Maximum number of edges for this rule. n means infinite. Default
+ * is n.
+ * validNeighbors - Array of types of the opposite terminal for which this
+ * rule applies.
+ * countError - Error to be displayed for invalid number of edges.
+ * typeError - Error to be displayed for invalid opposite terminals.
+ * validNeighborsAllowed - Optional boolean indicating if the array of
+ * opposite types should be valid or invalid.
+ */
+function mxMultiplicity(source, type, attr, value, min, max,
+	validNeighbors, countError, typeError, validNeighborsAllowed)
+{
+	this.source = source;
+	this.type = type;
+	this.attr = attr;
+	this.value = value;
+	this.min = (min != null) ? min : 0;
+	this.max = (max != null) ? max : 'n';
+	this.validNeighbors = validNeighbors;
+	this.countError = mxResources.get(countError) || countError;
+	this.typeError = mxResources.get(typeError) || typeError;
+	this.validNeighborsAllowed = (validNeighborsAllowed != null) ?
+		validNeighborsAllowed : true;
+};
+
+/**
+ * Variable: type
+ * 
+ * Defines the type of the source or target terminal. The type is a string
+ * passed to <mxUtils.isNode> together with the source or target vertex
+ * value as the first argument.
+ */
+mxMultiplicity.prototype.type = null;
+
+/**
+ * Variable: attr
+ * 
+ * Optional string that specifies the attributename to be passed to
+ * <mxUtils.isNode> to check if the rule applies to a cell.
+ */
+mxMultiplicity.prototype.attr = null;
+
+/**
+ * Variable: value
+ * 
+ * Optional string that specifies the value of the attribute to be passed
+ * to <mxUtils.isNode> to check if the rule applies to a cell.
+ */
+mxMultiplicity.prototype.value = null;
+
+/**
+ * Variable: source
+ * 
+ * Boolean that specifies if the rule is applied to the source or target
+ * terminal of an edge.
+ */
+mxMultiplicity.prototype.source = null;
+
+/**
+ * Variable: min
+ * 
+ * Defines the minimum number of connections for which this rule applies.
+ * Default is 0.
+ */
+mxMultiplicity.prototype.min = null;
+
+/**
+ * Variable: max
+ * 
+ * Defines the maximum number of connections for which this rule applies.
+ * A value of 'n' means unlimited times. Default is 'n'. 
+ */
+mxMultiplicity.prototype.max = null;
+
+/**
+ * Variable: validNeighbors
+ * 
+ * Holds an array of strings that specify the type of neighbor for which
+ * this rule applies. The strings are used in <mxCell.is> on the opposite
+ * terminal to check if the rule applies to the connection.
+ */
+mxMultiplicity.prototype.validNeighbors = null;
+
+/**
+ * Variable: validNeighborsAllowed
+ * 
+ * Boolean indicating if the list of validNeighbors are those that are allowed
+ * for this rule or those that are not allowed for this rule.
+ */
+mxMultiplicity.prototype.validNeighborsAllowed = true;
+
+/**
+ * Variable: countError
+ * 
+ * Holds the localized error message to be displayed if the number of
+ * connections for which the rule applies is smaller than <min> or greater
+ * than <max>.
+ */
+mxMultiplicity.prototype.countError = null;
+
+/**
+ * Variable: typeError
+ * 
+ * Holds the localized error message to be displayed if the type of the
+ * neighbor for a connection does not match the rule.
+ */
+mxMultiplicity.prototype.typeError = null;
+
+/**
+ * Function: check
+ * 
+ * Checks the multiplicity for the given arguments and returns the error
+ * for the given connection or null if the multiplicity does not apply.
+ *  
+ * Parameters:
+ * 
+ * graph - Reference to the enclosing <mxGraph> instance.
+ * edge - <mxCell> that represents the edge to validate.
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ * sourceOut - Number of outgoing edges from the source terminal.
+ * targetIn - Number of incoming edges for the target terminal.
+ */
+mxMultiplicity.prototype.check = function(graph, edge, source, target, sourceOut, targetIn)
+{
+	var error = '';
+
+	if ((this.source && this.checkTerminal(graph, source, edge)) ||
+		(!this.source && this.checkTerminal(graph, target, edge)))
+	{
+		if (this.countError != null && 
+			((this.source && (this.max == 0 || (sourceOut >= this.max))) ||
+			(!this.source && (this.max == 0 || (targetIn >= this.max)))))
+		{
+			error += this.countError + '\n';
+		}
+
+		if (this.validNeighbors != null && this.typeError != null && this.validNeighbors.length > 0)
+		{
+			var isValid = this.checkNeighbors(graph, edge, source, target);
+
+			if (!isValid)
+			{
+				error += this.typeError + '\n';
+			}
+		}
+	}
+	
+	return (error.length > 0) ? error : null;
+};
+
+/**
+ * Function: checkNeighbors
+ * 
+ * Checks if there are any valid neighbours in <validNeighbors>. This is only
+ * called if <validNeighbors> is a non-empty array.
+ */
+mxMultiplicity.prototype.checkNeighbors = function(graph, edge, source, target)
+{
+	var sourceValue = graph.model.getValue(source);
+	var targetValue = graph.model.getValue(target);
+	var isValid = !this.validNeighborsAllowed;
+	var valid = this.validNeighbors;
+	
+	for (var j = 0; j < valid.length; j++)
+	{
+		if (this.source &&
+			this.checkType(graph, targetValue, valid[j]))
+		{
+			isValid = this.validNeighborsAllowed;
+			break;
+		}
+		else if (!this.source && 
+			this.checkType(graph, sourceValue, valid[j]))
+		{
+			isValid = this.validNeighborsAllowed;
+			break;
+		}
+	}
+	
+	return isValid;
+};
+
+/**
+ * Function: checkTerminal
+ * 
+ * Checks the given terminal cell and returns true if this rule applies. The
+ * given cell is the source or target of the given edge, depending on
+ * <source>. This implementation uses <checkType> on the terminal's value.
+ */
+mxMultiplicity.prototype.checkTerminal = function(graph, terminal, edge)
+{
+	var value = graph.model.getValue(terminal);
+	
+	return this.checkType(graph, value, this.type, this.attr, this.value);
+};
+
+/**
+ * Function: checkType
+ * 
+ * Checks the type of the given value.
+ */
+mxMultiplicity.prototype.checkType = function(graph, value, type, attr, attrValue)
+{
+	if (value != null)
+	{
+		if (!isNaN(value.nodeType)) // Checks if value is a DOM node
+		{
+			return mxUtils.isNode(value, type, attr, attrValue);
+		}
+		else
+		{
+			return value == type;
+		}
+	}
+	
+	return false;
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/view/mxOutline.js b/airavata-kubernetes/web-console/src/assets/js/view/mxOutline.js
new file mode 100644
index 0000000..9819cec
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/view/mxOutline.js
@@ -0,0 +1,761 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxOutline
+ *
+ * Implements an outline (aka overview) for a graph. Set <updateOnPan> to true
+ * to enable updates while the source graph is panning.
+ * 
+ * Example:
+ * 
+ * (code)
+ * var outline = new mxOutline(graph, div);
+ * (end)
+ * 
+ * If an outline is used in an <mxWindow> in IE8 standards mode, the following
+ * code makes sure that the shadow filter is not inherited and that any
+ * transparent elements in the graph do not show the page background, but the
+ * background of the graph container.
+ * 
+ * (code)
+ * if (document.documentMode == 8)
+ * {
+ *   container.style.filter = 'progid:DXImageTransform.Microsoft.alpha(opacity=100)';
+ * }
+ * (end)
+ * 
+ * To move the graph to the top, left corner the following code can be used.
+ * 
+ * (code)
+ * var scale = graph.view.scale;
+ * var bounds = graph.getGraphBounds();
+ * graph.view.setTranslate(-bounds.x / scale, -bounds.y / scale);
+ * (end)
+ * 
+ * To toggle the suspended mode, the following can be used.
+ * 
+ * (code)
+ * outline.suspended = !outln.suspended;
+ * if (!outline.suspended)
+ * {
+ *   outline.update(true);
+ * }
+ * (end)
+ * 
+ * Constructor: mxOutline
+ *
+ * Constructs a new outline for the specified graph inside the given
+ * container.
+ * 
+ * Parameters:
+ * 
+ * source - <mxGraph> to create the outline for.
+ * container - DOM node that will contain the outline.
+ */
+function mxOutline(source, container)
+{
+	this.source = source;
+
+	if (container != null)
+	{
+		this.init(container);
+	}
+};
+
+/**
+ * Function: source
+ * 
+ * Reference to the source <mxGraph>.
+ */
+mxOutline.prototype.source = null;
+
+/**
+ * Function: outline
+ * 
+ * Reference to the <mxGraph> that renders the outline.
+ */
+mxOutline.prototype.outline = null;
+
+/**
+ * Function: graphRenderHint
+ * 
+ * Renderhint to be used for the outline graph. Default is faster.
+ */
+mxOutline.prototype.graphRenderHint = mxConstants.RENDERING_HINT_FASTER;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies if events are handled. Default is true.
+ */
+mxOutline.prototype.enabled = true;
+
+/**
+ * Variable: showViewport
+ * 
+ * Specifies a viewport rectangle should be shown. Default is true.
+ */
+mxOutline.prototype.showViewport = true;
+
+/**
+ * Variable: border
+ * 
+ * Border to be added at the bottom and right. Default is 10.
+ */
+mxOutline.prototype.border = 10;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies the size of the sizer handler. Default is 8.
+ */
+mxOutline.prototype.sizerSize = 8;
+
+/**
+ * Variable: labelsVisible
+ * 
+ * Specifies if labels should be visible in the outline. Default is false.
+ */
+mxOutline.prototype.labelsVisible = false;
+
+/**
+ * Variable: updateOnPan
+ * 
+ * Specifies if <update> should be called for <mxEvent.PAN> in the source
+ * graph. Default is false.
+ */
+mxOutline.prototype.updateOnPan = false;
+
+/**
+ * Variable: sizerImage
+ * 
+ * Optional <mxImage> to be used for the sizer. Default is null.
+ */
+mxOutline.prototype.sizerImage = null;
+
+/**
+ * Variable: minScale
+ * 
+ * Minimum scale to be used. Default is 0.001.
+ */
+mxOutline.prototype.minScale = 0.0001;
+
+/**
+ * Variable: suspended
+ * 
+ * Optional boolean flag to suspend updates. Default is false. This flag will
+ * also suspend repaints of the outline. To toggle this switch, use the
+ * following code.
+ * 
+ * (code)
+ * nav.suspended = !nav.suspended;
+ * 
+ * if (!nav.suspended)
+ * {
+ *   nav.update(true);
+ * }
+ * (end)
+ */
+mxOutline.prototype.suspended = false;
+
+/**
+ * Variable: forceVmlHandles
+ * 
+ * Specifies if VML should be used to render the handles in this control. This
+ * is true for IE8 standards mode and false for all other browsers and modes.
+ * This is a workaround for rendering issues of HTML elements over elements
+ * with filters in IE 8 standards mode.
+ */
+mxOutline.prototype.forceVmlHandles = document.documentMode == 8;
+
+/**
+ * Function: createGraph
+ * 
+ * Creates the <mxGraph> used in the outline.
+ */
+mxOutline.prototype.createGraph = function(container)
+{
+	var graph = new mxGraph(container, this.source.getModel(), this.graphRenderHint, this.source.getStylesheet());
+	graph.foldingEnabled = false;
+	graph.autoScroll = false;
+	
+	return graph;
+};
+
+/**
+ * Function: init
+ * 
+ * Initializes the outline inside the given container.
+ */
+mxOutline.prototype.init = function(container)
+{
+	this.outline = this.createGraph(container);
+	
+	// Do not repaint when suspended
+	var outlineGraphModelChanged = this.outline.graphModelChanged;
+	this.outline.graphModelChanged = mxUtils.bind(this, function(changes)
+	{
+		if (!this.suspended && this.outline != null)
+		{
+			outlineGraphModelChanged.apply(this.outline, arguments);
+		}
+	});
+
+	// Enables faster painting in SVG
+	if (mxClient.IS_SVG)
+	{
+		var node = this.outline.getView().getCanvas().parentNode;
+		node.setAttribute('shape-rendering', 'optimizeSpeed');
+		node.setAttribute('image-rendering', 'optimizeSpeed');
+	}
+	
+	// Hides cursors and labels
+	this.outline.labelsVisible = this.labelsVisible;
+	this.outline.setEnabled(false);
+	
+	this.updateHandler = mxUtils.bind(this, function(sender, evt)
+	{
+		if (!this.suspended && !this.active)
+		{
+			this.update();
+		}
+	});
+	
+	// Updates the scale of the outline after a change of the main graph
+	this.source.getModel().addListener(mxEvent.CHANGE, this.updateHandler);
+	this.outline.addMouseListener(this);
+	
+	// Adds listeners to keep the outline in sync with the source graph
+	var view = this.source.getView();
+	view.addListener(mxEvent.SCALE, this.updateHandler);
+	view.addListener(mxEvent.TRANSLATE, this.updateHandler);
+	view.addListener(mxEvent.SCALE_AND_TRANSLATE, this.updateHandler);
+	view.addListener(mxEvent.DOWN, this.updateHandler);
+	view.addListener(mxEvent.UP, this.updateHandler);
+
+	// Updates blue rectangle on scroll
+	mxEvent.addListener(this.source.container, 'scroll', this.updateHandler);
+	
+	this.panHandler = mxUtils.bind(this, function(sender)
+	{
+		if (this.updateOnPan)
+		{
+			this.updateHandler.apply(this, arguments);
+		}
+	});
+	this.source.addListener(mxEvent.PAN, this.panHandler);
+	
+	// Refreshes the graph in the outline after a refresh of the main graph
+	this.refreshHandler = mxUtils.bind(this, function(sender)
+	{
+		this.outline.setStylesheet(this.source.getStylesheet());
+		this.outline.refresh();
+	});
+	this.source.addListener(mxEvent.REFRESH, this.refreshHandler);
+
+	// Creates the blue rectangle for the viewport
+	this.bounds = new mxRectangle(0, 0, 0, 0);
+	this.selectionBorder = new mxRectangleShape(this.bounds, null,
+		mxConstants.OUTLINE_COLOR, mxConstants.OUTLINE_STROKEWIDTH);
+	this.selectionBorder.dialect = this.outline.dialect;
+
+	if (this.forceVmlHandles)
+	{
+		this.selectionBorder.isHtmlAllowed = function()
+		{
+			return false;
+		};
+	}
+	
+	this.selectionBorder.init(this.outline.getView().getOverlayPane());
+
+	// Handles event by catching the initial pointer start and then listening to the
+	// complete gesture on the event target. This is needed because all the events
+	// are routed via the initial element even if that element is removed from the
+	// DOM, which happens when we repaint the selection border and zoom handles.
+	var handler = mxUtils.bind(this, function(evt)
+	{
+		var t = mxEvent.getSource(evt);
+		
+		var redirect = mxUtils.bind(this, function(evt)
+		{
+			this.outline.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt));
+		});
+		
+		var redirect2 = mxUtils.bind(this, function(evt)
+		{
+			mxEvent.removeGestureListeners(t, null, redirect, redirect2);
+			this.outline.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt));
+		});
+		
+		mxEvent.addGestureListeners(t, null, redirect, redirect2);
+		this.outline.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt));
+	});
+	
+	mxEvent.addGestureListeners(this.selectionBorder.node, handler);
+
+	// Creates a small blue rectangle for sizing (sizer handle)
+	this.sizer = this.createSizer();
+	
+	if (this.forceVmlHandles)
+	{
+		this.sizer.isHtmlAllowed = function()
+		{
+			return false;
+		};
+	}
+	
+	this.sizer.init(this.outline.getView().getOverlayPane());
+	
+	if (this.enabled)
+	{
+		this.sizer.node.style.cursor = 'nwse-resize';
+	}
+	
+	mxEvent.addGestureListeners(this.sizer.node, handler);
+
+	this.selectionBorder.node.style.display = (this.showViewport) ? '' : 'none';
+	this.sizer.node.style.display = this.selectionBorder.node.style.display;
+	this.selectionBorder.node.style.cursor = 'move';
+
+	this.update(false);
+};
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxOutline.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ * 
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean that specifies the new enabled state.
+ */
+mxOutline.prototype.setEnabled = function(value)
+{
+	this.enabled = value;
+};
+
+/**
+ * Function: setZoomEnabled
+ * 
+ * Enables or disables the zoom handling by showing or hiding the respective
+ * handle.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean that specifies the new enabled state.
+ */
+mxOutline.prototype.setZoomEnabled = function(value)
+{
+	this.sizer.node.style.visibility = (value) ? 'visible' : 'hidden';
+};
+
+/**
+ * Function: refresh
+ * 
+ * Invokes <update> and revalidate the outline. This method is deprecated.
+ */
+mxOutline.prototype.refresh = function()
+{
+	this.update(true);
+};
+
+/**
+ * Function: createSizer
+ * 
+ * Creates the shape used as the sizer.
+ */
+mxOutline.prototype.createSizer = function()
+{
+	if (this.sizerImage != null)
+	{
+		var sizer = new mxImageShape(new mxRectangle(0, 0, this.sizerImage.width, this.sizerImage.height), this.sizerImage.src);
+		sizer.dialect = this.outline.dialect;
+		
+		return sizer;
+	}
+	else
+	{
+		var sizer = new mxRectangleShape(new mxRectangle(0, 0, this.sizerSize, this.sizerSize),
+			mxConstants.OUTLINE_HANDLE_FILLCOLOR, mxConstants.OUTLINE_HANDLE_STROKECOLOR);
+		sizer.dialect = this.outline.dialect;
+	
+		return sizer;
+	}
+};
+
+/**
+ * Function: getSourceContainerSize
+ * 
+ * Returns the size of the source container.
+ */
+mxOutline.prototype.getSourceContainerSize = function()
+{
+	return new mxRectangle(0, 0, this.source.container.scrollWidth, this.source.container.scrollHeight);
+};
+
+/**
+ * Function: getOutlineOffset
+ * 
+ * Returns the offset for drawing the outline graph.
+ */
+mxOutline.prototype.getOutlineOffset = function(scale)
+{
+	return null;
+};
+
+/**
+ * Function: getOutlineOffset
+ * 
+ * Returns the offset for drawing the outline graph.
+ */
+mxOutline.prototype.getSourceGraphBounds = function()
+{
+	return this.source.getGraphBounds();
+};
+
+/**
+ * Function: update
+ * 
+ * Updates the outline.
+ */
+mxOutline.prototype.update = function(revalidate)
+{
+	if (this.source != null && this.outline != null)
+	{
+		var sourceScale = this.source.view.scale;
+		var scaledGraphBounds = this.getSourceGraphBounds();
+		var unscaledGraphBounds = new mxRectangle(scaledGraphBounds.x / sourceScale + this.source.panDx,
+				scaledGraphBounds.y / sourceScale + this.source.panDy, scaledGraphBounds.width / sourceScale,
+				scaledGraphBounds.height / sourceScale);
+
+		var unscaledFinderBounds = new mxRectangle(0, 0,
+			this.source.container.clientWidth / sourceScale,
+			this.source.container.clientHeight / sourceScale);
+		
+		var union = unscaledGraphBounds.clone();
+		union.add(unscaledFinderBounds);
+	
+		// Zooms to the scrollable area if that is bigger than the graph
+		var size = this.getSourceContainerSize();
+		var completeWidth = Math.max(size.width / sourceScale, union.width);
+		var completeHeight = Math.max(size.height / sourceScale, union.height);
+	
+		var availableWidth = Math.max(0, this.outline.container.clientWidth - this.border);
+		var availableHeight = Math.max(0, this.outline.container.clientHeight - this.border);
+		
+		var outlineScale = Math.min(availableWidth / completeWidth, availableHeight / completeHeight);
+		var scale = (isNaN(outlineScale)) ? this.minScale : Math.max(this.minScale, outlineScale);
+
+		if (scale > 0)
+		{
+			if (this.outline.getView().scale != scale)
+			{
+				this.outline.getView().scale = scale;
+				revalidate = true;
+			}
+		
+			var navView = this.outline.getView();
+			
+			if (navView.currentRoot != this.source.getView().currentRoot)
+			{
+				navView.setCurrentRoot(this.source.getView().currentRoot);
+			}
+
+			var t = this.source.view.translate;
+			var tx = t.x + this.source.panDx;
+			var ty = t.y + this.source.panDy;
+			
+			var off = this.getOutlineOffset(scale);
+			
+			if (off != null)
+			{
+				tx += off.x;
+				ty += off.y;
+			}
+			
+			if (unscaledGraphBounds.x < 0)
+			{
+				tx = tx - unscaledGraphBounds.x;
+			}
+			if (unscaledGraphBounds.y < 0)
+			{
+				ty = ty - unscaledGraphBounds.y;
+			}
+			
+			if (navView.translate.x != tx || navView.translate.y != ty)
+			{
+				navView.translate.x = tx;
+				navView.translate.y = ty;
+				revalidate = true;
+			}
+		
+			// Prepares local variables for computations
+			var t2 = navView.translate;
+			scale = this.source.getView().scale;
+			var scale2 = scale / navView.scale;
+			var scale3 = 1.0 / navView.scale;
+			var container = this.source.container;
+			
+			// Updates the bounds of the viewrect in the navigation
+			this.bounds = new mxRectangle(
+				(t2.x - t.x - this.source.panDx) / scale3,
+				(t2.y - t.y - this.source.panDy) / scale3,
+				(container.clientWidth / scale2),
+				(container.clientHeight / scale2));
+			
+			// Adds the scrollbar offset to the finder
+			this.bounds.x += this.source.container.scrollLeft * navView.scale / scale;
+			this.bounds.y += this.source.container.scrollTop * navView.scale / scale;
+			
+			var b = this.selectionBorder.bounds;
+			
+			if (b.x != this.bounds.x || b.y != this.bounds.y || b.width != this.bounds.width || b.height != this.bounds.height)
+			{
+				this.selectionBorder.bounds = this.bounds;
+				this.selectionBorder.redraw();
+			}
+		
+			// Updates the bounds of the zoom handle at the bottom right
+			var b = this.sizer.bounds;
+			var b2 = new mxRectangle(this.bounds.x + this.bounds.width - b.width / 2,
+					this.bounds.y + this.bounds.height - b.height / 2, b.width, b.height);
+
+			if (b.x != b2.x || b.y != b2.y || b.width != b2.width || b.height != b2.height)
+			{
+				this.sizer.bounds = b2;
+				
+				// Avoids update of visibility in redraw for VML
+				if (this.sizer.node.style.visibility != 'hidden')
+				{
+					this.sizer.redraw();
+				}
+			}
+
+			if (revalidate)
+			{
+				this.outline.view.revalidate();
+			}
+		}
+	}
+};
+
+/**
+ * Function: mouseDown
+ * 
+ * Handles the event by starting a translation or zoom.
+ */
+mxOutline.prototype.mouseDown = function(sender, me)
+{
+	if (this.enabled && this.showViewport)
+	{
+		var tol = (!mxEvent.isMouseEvent(me.getEvent())) ? this.source.tolerance : 0;
+		var hit = (this.source.allowHandleBoundsCheck && (mxClient.IS_IE || tol > 0)) ?
+				new mxRectangle(me.getGraphX() - tol, me.getGraphY() - tol, 2 * tol, 2 * tol) : null;
+		this.zoom = me.isSource(this.sizer) || (hit != null && mxUtils.intersects(shape.bounds, hit));
+		this.startX = me.getX();
+		this.startY = me.getY();
+		this.active = true;
+
+		if (this.source.useScrollbarsForPanning && mxUtils.hasScrollbars(this.source.container))
+		{
+			this.dx0 = this.source.container.scrollLeft;
+			this.dy0 = this.source.container.scrollTop;
+		}
+		else
+		{
+			this.dx0 = 0;
+			this.dy0 = 0;
+		}
+	}
+
+	me.consume();
+};
+
+/**
+ * Function: mouseMove
+ * 
+ * Handles the event by previewing the viewrect in <graph> and updating the
+ * rectangle that represents the viewrect in the outline.
+ */
+mxOutline.prototype.mouseMove = function(sender, me)
+{
+	if (this.active)
+	{
+		this.selectionBorder.node.style.display = (this.showViewport) ? '' : 'none';
+		this.sizer.node.style.display = this.selectionBorder.node.style.display; 
+
+		var delta = this.getTranslateForEvent(me);
+		var dx = delta.x;
+		var dy = delta.y;
+		var bounds = null;
+		
+		if (!this.zoom)
+		{
+			// Previews the panning on the source graph
+			var scale = this.outline.getView().scale;
+			bounds = new mxRectangle(this.bounds.x + dx,
+				this.bounds.y + dy, this.bounds.width, this.bounds.height);
+			this.selectionBorder.bounds = bounds;
+			this.selectionBorder.redraw();
+			dx /= scale;
+			dx *= this.source.getView().scale;
+			dy /= scale;
+			dy *= this.source.getView().scale;
+			this.source.panGraph(-dx - this.dx0, -dy - this.dy0);
+		}
+		else
+		{
+			// Does *not* preview zooming on the source graph
+			var container = this.source.container;
+			var viewRatio = container.clientWidth / container.clientHeight;
+			dy = dx / viewRatio;
+			bounds = new mxRectangle(this.bounds.x,
+				this.bounds.y,
+				Math.max(1, this.bounds.width + dx),
+				Math.max(1, this.bounds.height + dy));
+			this.selectionBorder.bounds = bounds;
+			this.selectionBorder.redraw();
+		}
+		
+		// Updates the zoom handle
+		var b = this.sizer.bounds;
+		this.sizer.bounds = new mxRectangle(
+			bounds.x + bounds.width - b.width / 2,
+			bounds.y + bounds.height - b.height / 2,
+			b.width, b.height);
+		
+		// Avoids update of visibility in redraw for VML
+		if (this.sizer.node.style.visibility != 'hidden')
+		{
+			this.sizer.redraw();
+		}
+		
+		me.consume();
+	}
+};
+
+/**
+ * Function: getTranslateForEvent
+ * 
+ * Gets the translate for the given mouse event. Here is an example to limit
+ * the outline to stay within positive coordinates:
+ * 
+ * (code)
+ * outline.getTranslateForEvent = function(me)
+ * {
+ *   var pt = new mxPoint(me.getX() - this.startX, me.getY() - this.startY);
+ *   
+ *   if (!this.zoom)
+ *   {
+ *     var tr = this.source.view.translate;
+ *     pt.x = Math.max(tr.x * this.outline.view.scale, pt.x);
+ *     pt.y = Math.max(tr.y * this.outline.view.scale, pt.y);
+ *   }
+ *   
+ *   return pt;
+ * };
+ * (end)
+ */
+mxOutline.prototype.getTranslateForEvent = function(me)
+{
+	return new mxPoint(me.getX() - this.startX, me.getY() - this.startY);
+};
+
+/**
+ * Function: mouseUp
+ * 
+ * Handles the event by applying the translation or zoom to <graph>.
+ */
+mxOutline.prototype.mouseUp = function(sender, me)
+{
+	if (this.active)
+	{
+		var delta = this.getTranslateForEvent(me);
+		var dx = delta.x;
+		var dy = delta.y;
+		
+		if (Math.abs(dx) > 0 || Math.abs(dy) > 0)
+		{
+			if (!this.zoom)
+			{
+				// Applies the new translation if the source
+				// has no scrollbars
+				if (!this.source.useScrollbarsForPanning ||
+					!mxUtils.hasScrollbars(this.source.container))
+				{
+					this.source.panGraph(0, 0);
+					dx /= this.outline.getView().scale;
+					dy /= this.outline.getView().scale;
+					var t = this.source.getView().translate;
+					this.source.getView().setTranslate(t.x - dx, t.y - dy);
+				}
+			}
+			else
+			{
+				// Applies the new zoom
+				var w = this.selectionBorder.bounds.width;
+				var scale = this.source.getView().scale;
+				this.source.zoomTo(Math.max(this.minScale, scale - (dx * scale) / w), false);
+			}
+
+			this.update();
+			me.consume();
+		}
+			
+		// Resets the state of the handler
+		this.index = null;
+		this.active = false;
+	}
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroy this outline and removes all listeners from <source>.
+ */
+mxOutline.prototype.destroy = function()
+{
+	if (this.source != null)
+	{
+		this.source.removeListener(this.panHandler);
+		this.source.removeListener(this.refreshHandler);
+		this.source.getModel().removeListener(this.updateHandler);
+		this.source.getView().removeListener(this.updateHandler);
+		mxEvent.addListener(this.source.container, 'scroll', this.updateHandler);
+		this.source = null;
+	}
+	
+	if (this.outline != null)
+	{
+		this.outline.removeMouseListener(this);
+		this.outline.destroy();
+		this.outline = null;
+	}
+
+	if (this.selectionBorder != null)
+	{
+		this.selectionBorder.destroy();
+		this.selectionBorder = null;
+	}
+	
+	if (this.sizer != null)
+	{
+		this.sizer.destroy();
+		this.sizer = null;
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/view/mxPerimeter.js b/airavata-kubernetes/web-console/src/assets/js/view/mxPerimeter.js
new file mode 100644
index 0000000..c1d5ffc
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/view/mxPerimeter.js
@@ -0,0 +1,921 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+var mxPerimeter =
+{
+	/**
+	 * Class: mxPerimeter
+	 * 
+	 * Provides various perimeter functions to be used in a style
+	 * as the value of <mxConstants.STYLE_PERIMETER>. Perimeters for
+	 * rectangle, circle, rhombus and triangle are available.
+	 *
+	 * Example:
+	 * 
+	 * (code)
+	 * <add as="perimeter">mxPerimeter.RectanglePerimeter</add>
+	 * (end)
+	 * 
+	 * Or programmatically:
+	 * 
+	 * (code)
+	 * style[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter;
+	 * (end)
+	 * 
+	 * When adding new perimeter functions, it is recommended to use the 
+	 * mxPerimeter-namespace as follows:
+	 * 
+	 * (code)
+	 * mxPerimeter.CustomPerimeter = function (bounds, vertex, next, orthogonal)
+	 * {
+	 *   var x = 0; // Calculate x-coordinate
+	 *   var y = 0; // Calculate y-coordainte
+	 *   
+	 *   return new mxPoint(x, y);
+	 * }
+	 * (end)
+	 * 
+	 * The new perimeter should then be registered in the <mxStyleRegistry> as follows:
+	 * (code)
+	 * mxStyleRegistry.putValue('customPerimeter', mxPerimeter.CustomPerimeter);
+	 * (end)
+	 * 
+	 * The custom perimeter above can now be used in a specific vertex as follows:
+	 * 
+	 * (code)
+	 * model.setStyle(vertex, 'perimeter=customPerimeter');
+	 * (end)
+	 * 
+	 * Note that the key of the <mxStyleRegistry> entry for the function should
+	 * be used in string values, unless <mxGraphView.allowEval> is true, in
+	 * which case you can also use mxPerimeter.CustomPerimeter for the value in
+	 * the cell style above.
+	 * 
+	 * Or it can be used for all vertices in the graph as follows:
+	 * 
+	 * (code)
+	 * var style = graph.getStylesheet().getDefaultVertexStyle();
+	 * style[mxConstants.STYLE_PERIMETER] = mxPerimeter.CustomPerimeter;
+	 * (end)
+	 * 
+	 * Note that the object can be used directly when programmatically setting
+	 * the value, but the key in the <mxStyleRegistry> should be used when
+	 * setting the value via a key, value pair in a cell style.
+	 * 
+	 * The parameters are explained in <RectanglePerimeter>.
+	 * 
+	 * Function: RectanglePerimeter
+	 * 
+	 * Describes a rectangular perimeter for the given bounds.
+	 *
+	 * Parameters:
+	 * 
+	 * bounds - <mxRectangle> that represents the absolute bounds of the
+	 * vertex.
+	 * vertex - <mxCellState> that represents the vertex.
+	 * next - <mxPoint> that represents the nearest neighbour point on the
+	 * given edge.
+	 * orthogonal - Boolean that specifies if the orthogonal projection onto
+	 * the perimeter should be returned. If this is false then the intersection
+	 * of the perimeter and the line between the next and the center point is
+	 * returned.
+	 */
+	RectanglePerimeter: function (bounds, vertex, next, orthogonal)
+	{
+		var cx = bounds.getCenterX();
+		var cy = bounds.getCenterY();
+		var dx = next.x - cx;
+		var dy = next.y - cy;
+		var alpha = Math.atan2(dy, dx);
+		var p = new mxPoint(0, 0);
+		var pi = Math.PI;
+		var pi2 = Math.PI/2;
+		var beta = pi2 - alpha;
+		var t = Math.atan2(bounds.height, bounds.width);
+		
+		if (alpha < -pi + t || alpha > pi - t)
+		{
+			// Left edge
+			p.x = bounds.x;
+			p.y = cy - bounds.width * Math.tan(alpha) / 2;
+		}
+		else if (alpha < -t)
+		{
+			// Top Edge
+			p.y = bounds.y;
+			p.x = cx - bounds.height * Math.tan(beta) / 2;
+		}
+		else if (alpha < t)
+		{
+			// Right Edge
+			p.x = bounds.x + bounds.width;
+			p.y = cy + bounds.width * Math.tan(alpha) / 2;
+		}
+		else
+		{
+			// Bottom Edge
+			p.y = bounds.y + bounds.height;
+			p.x = cx + bounds.height * Math.tan(beta) / 2;
+		}
+		
+		if (orthogonal)
+		{
+			if (next.x >= bounds.x &&
+				next.x <= bounds.x + bounds.width)
+			{
+				p.x = next.x;
+			}
+			else if (next.y >= bounds.y &&
+					   next.y <= bounds.y + bounds.height)
+			{
+				p.y = next.y;
+			}
+			if (next.x < bounds.x)
+			{
+				p.x = bounds.x;
+			}
+			else if (next.x > bounds.x + bounds.width)
+			{
+				p.x = bounds.x + bounds.width;
+			}
+			if (next.y < bounds.y)
+			{
+				p.y = bounds.y;
+			}
+			else if (next.y > bounds.y + bounds.height)
+			{
+				p.y = bounds.y + bounds.height;
+			}
+		}
+		
+		return p;
+	},
+
+	/**
+	 * Function: EllipsePerimeter
+	 * 
+	 * Describes an elliptic perimeter. See <RectanglePerimeter>
+	 * for a description of the parameters.
+	 */
+	EllipsePerimeter: function (bounds, vertex, next, orthogonal)
+	{
+		var x = bounds.x;
+		var y = bounds.y;
+		var a = bounds.width / 2;
+		var b = bounds.height / 2;
+		var cx = x + a;
+		var cy = y + b;
+		var px = next.x;
+		var py = next.y;
+		
+		// Calculates straight line equation through
+		// point and ellipse center y = d * x + h
+		var dx = parseInt(px - cx);
+		var dy = parseInt(py - cy);
+		
+		if (dx == 0 && dy != 0)
+		{
+			return new mxPoint(cx, cy + b * dy / Math.abs(dy));
+		}
+		else if (dx == 0 && dy == 0)
+		{
+			return new mxPoint(px, py);
+		}
+
+		if (orthogonal)
+		{
+			if (py >= y && py <= y + bounds.height)
+			{
+				var ty = py - cy;
+				var tx = Math.sqrt(a*a*(1-(ty*ty)/(b*b))) || 0;
+				
+				if (px <= x)
+				{
+					tx = -tx;
+				}
+				
+				return new mxPoint(cx+tx, py);
+			}
+			
+			if (px >= x && px <= x + bounds.width)
+			{
+				var tx = px - cx;
+				var ty = Math.sqrt(b*b*(1-(tx*tx)/(a*a))) || 0;
+				
+				if (py <= y)
+				{
+					ty = -ty;	
+				}
+				
+				return new mxPoint(px, cy+ty);
+			}
+		}
+		
+		// Calculates intersection
+		var d = dy / dx;
+		var h = cy - d * cx;
+		var e = a * a * d * d + b * b;
+		var f = -2 * cx * e;
+		var g = a * a * d * d * cx * cx +
+				b * b * cx * cx -
+				a * a * b * b;
+		var det = Math.sqrt(f * f - 4 * e * g);
+		
+		// Two solutions (perimeter points)
+		var xout1 = (-f + det) / (2 * e);
+		var xout2 = (-f - det) / (2 * e);
+		var yout1 = d * xout1 + h;
+		var yout2 = d * xout2 + h;
+		var dist1 = Math.sqrt(Math.pow((xout1 - px), 2)
+					+ Math.pow((yout1 - py), 2));
+		var dist2 = Math.sqrt(Math.pow((xout2 - px), 2)
+					+ Math.pow((yout2 - py), 2));
+					
+		// Correct solution
+		var xout = 0;
+		var yout = 0;
+		
+		if (dist1 < dist2)
+		{
+			xout = xout1;
+			yout = yout1;
+		}
+		else
+		{
+			xout = xout2;
+			yout = yout2;
+		}
+		
+		return new mxPoint(xout, yout);
+	},
+
+	/**
+	 * Function: RhombusPerimeter
+	 * 
+	 * Describes a rhombus (aka diamond) perimeter. See <RectanglePerimeter>
+	 * for a description of the parameters.
+	 */
+	RhombusPerimeter: function (bounds, vertex, next, orthogonal)
+	{
+		var x = bounds.x;
+		var y = bounds.y;
+		var w = bounds.width;
+		var h = bounds.height;
+		
+		var cx = x + w / 2;
+		var cy = y + h / 2;
+
+		var px = next.x;
+		var py = next.y;
+
+		// Special case for intersecting the diamond's corners
+		if (cx == px)
+		{
+			if (cy > py)
+			{
+				return new mxPoint(cx, y); // top
+			}
+			else
+			{
+				return new mxPoint(cx, y + h); // bottom
+			}
+		}
+		else if (cy == py)
+		{
+			if (cx > px)
+			{
+				return new mxPoint(x, cy); // left
+			}
+			else
+			{
+				return new mxPoint(x + w, cy); // right
+			}
+		}
+		
+		var tx = cx;
+		var ty = cy;
+		
+		if (orthogonal)
+		{
+			if (px >= x && px <= x + w)
+			{
+				tx = px;
+			}
+			else if (py >= y && py <= y + h)
+			{
+				ty = py;
+			}
+		}
+		
+		// In which quadrant will the intersection be?
+		// set the slope and offset of the border line accordingly
+		if (px < cx)
+		{
+			if (py < cy)
+			{
+				return mxUtils.intersection(px, py, tx, ty, cx, y, x, cy);
+			}
+			else
+			{
+				return mxUtils.intersection(px, py, tx, ty, cx, y + h, x, cy);
+			}
+		}
+		else if (py < cy)
+		{
+			return mxUtils.intersection(px, py, tx, ty, cx, y, x + w, cy);
+		}
+		else
+		{
+			return mxUtils.intersection(px, py, tx, ty, cx, y + h, x + w, cy);
+		}
+	},
+	
+	/**
+	 * Function: TrianglePerimeter
+	 * 
+	 * Describes a triangle perimeter. See <RectanglePerimeter>
+	 * for a description of the parameters.
+	 */
+	TrianglePerimeter: function (bounds, vertex, next, orthogonal)
+	{
+		var direction = (vertex != null) ?
+			vertex.style[mxConstants.STYLE_DIRECTION] : null;
+		var vertical = direction == mxConstants.DIRECTION_NORTH ||
+			direction == mxConstants.DIRECTION_SOUTH;
+
+		var x = bounds.x;
+		var y = bounds.y;
+		var w = bounds.width;
+		var h = bounds.height;
+		
+		var cx = x + w / 2;
+		var cy = y + h / 2;
+		
+		var start = new mxPoint(x, y);
+		var corner = new mxPoint(x + w, cy);
+		var end = new mxPoint(x, y + h);
+		
+		if (direction == mxConstants.DIRECTION_NORTH)
+		{
+			start = end;
+			corner = new mxPoint(cx, y);
+			end = new mxPoint(x + w, y + h);
+		}
+		else if (direction == mxConstants.DIRECTION_SOUTH)
+		{
+			corner = new mxPoint(cx, y + h);
+			end = new mxPoint(x + w, y);
+		}
+		else if (direction == mxConstants.DIRECTION_WEST)
+		{
+			start = new mxPoint(x + w, y);
+			corner = new mxPoint(x, cy);
+			end = new mxPoint(x + w, y + h);
+		}
+
+		var dx = next.x - cx;
+		var dy = next.y - cy;
+
+		var alpha = (vertical) ? Math.atan2(dx, dy) : Math.atan2(dy, dx);
+		var t = (vertical) ? Math.atan2(w, h) : Math.atan2(h, w);
+		
+		var base = false;
+		
+		if (direction == mxConstants.DIRECTION_NORTH ||
+			direction == mxConstants.DIRECTION_WEST)
+		{
+			base = alpha > -t && alpha < t;
+		}
+		else
+		{
+			base = alpha < -Math.PI + t || alpha > Math.PI - t;	
+		}
+
+		var result = null;			
+
+		if (base)
+		{
+			if (orthogonal && ((vertical && next.x >= start.x && next.x <= end.x) ||
+				(!vertical && next.y >= start.y && next.y <= end.y)))
+			{
+				if (vertical)
+				{
+					result = new mxPoint(next.x, start.y);
+				}
+				else
+				{
+					result = new mxPoint(start.x, next.y);
+				}
+			}
+			else
+			{
+				if (direction == mxConstants.DIRECTION_NORTH)
+				{
+					result = new mxPoint(x + w / 2 + h * Math.tan(alpha) / 2,
+						y + h);
+				}
+				else if (direction == mxConstants.DIRECTION_SOUTH)
+				{
+					result = new mxPoint(x + w / 2 - h * Math.tan(alpha) / 2,
+						y);
+				}
+				else if (direction == mxConstants.DIRECTION_WEST)
+				{
+					result = new mxPoint(x + w, y + h / 2 +
+						w * Math.tan(alpha) / 2);
+				}
+				else
+				{
+					result = new mxPoint(x, y + h / 2 -
+						w * Math.tan(alpha) / 2);
+				}
+			}
+		}
+		else
+		{
+			if (orthogonal)
+			{
+				var pt = new mxPoint(cx, cy);
+		
+				if (next.y >= y && next.y <= y + h)
+				{
+					pt.x = (vertical) ? cx : (
+						(direction == mxConstants.DIRECTION_WEST) ?
+							x + w : x);
+					pt.y = next.y;
+				}
+				else if (next.x >= x && next.x <= x + w)
+				{
+					pt.x = next.x;
+					pt.y = (!vertical) ? cy : (
+						(direction == mxConstants.DIRECTION_NORTH) ?
+							y + h : y);
+				}
+				
+				// Compute angle
+				dx = next.x - pt.x;
+				dy = next.y - pt.y;
+				
+				cx = pt.x;
+				cy = pt.y;
+			}
+
+			if ((vertical && next.x <= x + w / 2) ||
+				(!vertical && next.y <= y + h / 2))
+			{
+				result = mxUtils.intersection(next.x, next.y, cx, cy,
+					start.x, start.y, corner.x, corner.y);
+			}
+			else
+			{
+				result = mxUtils.intersection(next.x, next.y, cx, cy,
+					corner.x, corner.y, end.x, end.y);
+			}
+		}
+		
+		if (result == null)
+		{
+			result = new mxPoint(cx, cy);
+		}
+		
+		return result;
+	},
+	
+	/**
+	 * Function: HexagonPerimeter
+	 * 
+	 * Describes a hexagon perimeter. See <RectanglePerimeter>
+	 * for a description of the parameters.
+	 */
+	HexagonPerimeter: function (bounds, vertex, next, orthogonal)
+	{
+		var x = bounds.x;
+		var y = bounds.y;
+		var w = bounds.width;
+		var h = bounds.height;
+
+		var cx = bounds.getCenterX();
+		var cy = bounds.getCenterY();
+		var px = next.x;
+		var py = next.y;
+		var dx = px - cx;
+		var dy = py - cy;
+		var alpha = -Math.atan2(dy, dx);
+		var pi = Math.PI;
+		var pi2 = Math.PI / 2;
+
+		var result = new mxPoint(cx, cy);
+
+		var direction = (vertex != null) ? mxUtils.getValue(
+				vertex.style, mxConstants.STYLE_DIRECTION,
+				mxConstants.DIRECTION_EAST) : mxConstants.DIRECTION_EAST;
+		var vertical = direction == mxConstants.DIRECTION_NORTH
+				|| direction == mxConstants.DIRECTION_SOUTH;
+		var a = new mxPoint();
+		var b = new mxPoint();
+
+		//Only consider corrects quadrants for the orthogonal case.
+		if ((px < x) && (py < y) || (px < x) && (py > y + h)
+				|| (px > x + w) && (py < y) || (px > x + w) && (py > y + h))
+		{
+			orthogonal = false;
+		}
+
+		if (orthogonal)
+		{
+			if (vertical)
+			{
+				//Special cases where intersects with hexagon corners
+				if (px == cx)
+				{
+					if (py <= y)
+					{
+						return new mxPoint(cx, y);
+					}
+					else if (py >= y + h)
+					{
+						return new mxPoint(cx, y + h);
+					}
+				}
+				else if (px < x)
+				{
+					if (py == y + h / 4)
+					{
+						return new mxPoint(x, y + h / 4);
+					}
+					else if (py == y + 3 * h / 4)
+					{
+						return new mxPoint(x, y + 3 * h / 4);
+					}
+				}
+				else if (px > x + w)
+				{
+					if (py == y + h / 4)
+					{
+						return new mxPoint(x + w, y + h / 4);
+					}
+					else if (py == y + 3 * h / 4)
+					{
+						return new mxPoint(x + w, y + 3 * h / 4);
+					}
+				}
+				else if (px == x)
+				{
+					if (py < cy)
+					{
+						return new mxPoint(x, y + h / 4);
+					}
+					else if (py > cy)
+					{
+						return new mxPoint(x, y + 3 * h / 4);
+					}
+				}
+				else if (px == x + w)
+				{
+					if (py < cy)
+					{
+						return new mxPoint(x + w, y + h / 4);
+					}
+					else if (py > cy)
+					{
+						return new mxPoint(x + w, y + 3 * h / 4);
+					}
+				}
+				if (py == y)
+				{
+					return new mxPoint(cx, y);
+				}
+				else if (py == y + h)
+				{
+					return new mxPoint(cx, y + h);
+				}
+
+				if (px < cx)
+				{
+					if ((py > y + h / 4) && (py < y + 3 * h / 4))
+					{
+						a = new mxPoint(x, y);
+						b = new mxPoint(x, y + h);
+					}
+					else if (py < y + h / 4)
+					{
+						a = new mxPoint(x - Math.floor(0.5 * w), y
+								+ Math.floor(0.5 * h));
+						b = new mxPoint(x + w, y - Math.floor(0.25 * h));
+					}
+					else if (py > y + 3 * h / 4)
+					{
+						a = new mxPoint(x - Math.floor(0.5 * w), y
+								+ Math.floor(0.5 * h));
+						b = new mxPoint(x + w, y + Math.floor(1.25 * h));
+					}
+				}
+				else if (px > cx)
+				{
+					if ((py > y + h / 4) && (py < y + 3 * h / 4))
+					{
+						a = new mxPoint(x + w, y);
+						b = new mxPoint(x + w, y + h);
+					}
+					else if (py < y + h / 4)
+					{
+						a = new mxPoint(x, y - Math.floor(0.25 * h));
+						b = new mxPoint(x + Math.floor(1.5 * w), y
+								+ Math.floor(0.5 * h));
+					}
+					else if (py > y + 3 * h / 4)
+					{
+						a = new mxPoint(x + Math.floor(1.5 * w), y
+								+ Math.floor(0.5 * h));
+						b = new mxPoint(x, y + Math.floor(1.25 * h));
+					}
+				}
+
+			}
+			else
+			{
+				//Special cases where intersects with hexagon corners
+				if (py == cy)
+				{
+					if (px <= x)
+					{
+						return new mxPoint(x, y + h / 2);
+					}
+					else if (px >= x + w)
+					{
+						return new mxPoint(x + w, y + h / 2);
+					}
+				}
+				else if (py < y)
+				{
+					if (px == x + w / 4)
+					{
+						return new mxPoint(x + w / 4, y);
+					}
+					else if (px == x + 3 * w / 4)
+					{
+						return new mxPoint(x + 3 * w / 4, y);
+					}
+				}
+				else if (py > y + h)
+				{
+					if (px == x + w / 4)
+					{
+						return new mxPoint(x + w / 4, y + h);
+					}
+					else if (px == x + 3 * w / 4)
+					{
+						return new mxPoint(x + 3 * w / 4, y + h);
+					}
+				}
+				else if (py == y)
+				{
+					if (px < cx)
+					{
+						return new mxPoint(x + w / 4, y);
+					}
+					else if (px > cx)
+					{
+						return new mxPoint(x + 3 * w / 4, y);
+					}
+				}
+				else if (py == y + h)
+				{
+					if (px < cx)
+					{
+						return new mxPoint(x + w / 4, y + h);
+					}
+					else if (py > cy)
+					{
+						return new mxPoint(x + 3 * w / 4, y + h);
+					}
+				}
+				if (px == x)
+				{
+					return new mxPoint(x, cy);
+				}
+				else if (px == x + w)
+				{
+					return new mxPoint(x + w, cy);
+				}
+
+				if (py < cy)
+				{
+					if ((px > x + w / 4) && (px < x + 3 * w / 4))
+					{
+						a = new mxPoint(x, y);
+						b = new mxPoint(x + w, y);
+					}
+					else if (px < x + w / 4)
+					{
+						a = new mxPoint(x - Math.floor(0.25 * w), y + h);
+						b = new mxPoint(x + Math.floor(0.5 * w), y
+								- Math.floor(0.5 * h));
+					}
+					else if (px > x + 3 * w / 4)
+					{
+						a = new mxPoint(x + Math.floor(0.5 * w), y
+								- Math.floor(0.5 * h));
+						b = new mxPoint(x + Math.floor(1.25 * w), y + h);
+					}
+				}
+				else if (py > cy)
+				{
+					if ((px > x + w / 4) && (px < x + 3 * w / 4))
+					{
+						a = new mxPoint(x, y + h);
+						b = new mxPoint(x + w, y + h);
+					}
+					else if (px < x + w / 4)
+					{
+						a = new mxPoint(x - Math.floor(0.25 * w), y);
+						b = new mxPoint(x + Math.floor(0.5 * w), y
+								+ Math.floor(1.5 * h));
+					}
+					else if (px > x + 3 * w / 4)
+					{
+						a = new mxPoint(x + Math.floor(0.5 * w), y
+								+ Math.floor(1.5 * h));
+						b = new mxPoint(x + Math.floor(1.25 * w), y);
+					}
+				}
+			}
+
+			var tx = cx;
+			var ty = cy;
+
+			if (px >= x && px <= x + w)
+			{
+				tx = px;
+				
+				if (py < cy)
+				{
+					ty = y + h;
+				}
+				else
+				{
+					ty = y;
+				}
+			}
+			else if (py >= y && py <= y + h)
+			{
+				ty = py;
+				
+				if (px < cx)
+				{
+					tx = x + w;
+				}
+				else
+				{
+					tx = x;
+				}
+			}
+
+			result = mxUtils.intersection(tx, ty, next.x, next.y, a.x, a.y, b.x, b.y);
+		}
+		else
+		{
+			if (vertical)
+			{
+				var beta = Math.atan2(h / 4, w / 2);
+
+				//Special cases where intersects with hexagon corners
+				if (alpha == beta)
+				{
+					return new mxPoint(x + w, y + Math.floor(0.25 * h));
+				}
+				else if (alpha == pi2)
+				{
+					return new mxPoint(x + Math.floor(0.5 * w), y);
+				}
+				else if (alpha == (pi - beta))
+				{
+					return new mxPoint(x, y + Math.floor(0.25 * h));
+				}
+				else if (alpha == -beta)
+				{
+					return new mxPoint(x + w, y + Math.floor(0.75 * h));
+				}
+				else if (alpha == (-pi2))
+				{
+					return new mxPoint(x + Math.floor(0.5 * w), y + h);
+				}
+				else if (alpha == (-pi + beta))
+				{
+					return new mxPoint(x, y + Math.floor(0.75 * h));
+				}
+
+				if ((alpha < beta) && (alpha > -beta))
+				{
+					a = new mxPoint(x + w, y);
+					b = new mxPoint(x + w, y + h);
+				}
+				else if ((alpha > beta) && (alpha < pi2))
+				{
+					a = new mxPoint(x, y - Math.floor(0.25 * h));
+					b = new mxPoint(x + Math.floor(1.5 * w), y
+							+ Math.floor(0.5 * h));
+				}
+				else if ((alpha > pi2) && (alpha < (pi - beta)))
+				{
+					a = new mxPoint(x - Math.floor(0.5 * w), y
+							+ Math.floor(0.5 * h));
+					b = new mxPoint(x + w, y - Math.floor(0.25 * h));
+				}
+				else if (((alpha > (pi - beta)) && (alpha <= pi))
+						|| ((alpha < (-pi + beta)) && (alpha >= -pi)))
+				{
+					a = new mxPoint(x, y);
+					b = new mxPoint(x, y + h);
+				}
+				else if ((alpha < -beta) && (alpha > -pi2))
+				{
+					a = new mxPoint(x + Math.floor(1.5 * w), y
+							+ Math.floor(0.5 * h));
+					b = new mxPoint(x, y + Math.floor(1.25 * h));
+				}
+				else if ((alpha < -pi2) && (alpha > (-pi + beta)))
+				{
+					a = new mxPoint(x - Math.floor(0.5 * w), y
+							+ Math.floor(0.5 * h));
+					b = new mxPoint(x + w, y + Math.floor(1.25 * h));
+				}
+			}
+			else
+			{
+				var beta = Math.atan2(h / 2, w / 4);
+
+				//Special cases where intersects with hexagon corners
+				if (alpha == beta)
+				{
+					return new mxPoint(x + Math.floor(0.75 * w), y);
+				}
+				else if (alpha == (pi - beta))
+				{
+					return new mxPoint(x + Math.floor(0.25 * w), y);
+				}
+				else if ((alpha == pi) || (alpha == -pi))
+				{
+					return new mxPoint(x, y + Math.floor(0.5 * h));
+				}
+				else if (alpha == 0)
+				{
+					return new mxPoint(x + w, y + Math.floor(0.5 * h));
+				}
+				else if (alpha == -beta)
+				{
+					return new mxPoint(x + Math.floor(0.75 * w), y + h);
+				}
+				else if (alpha == (-pi + beta))
+				{
+					return new mxPoint(x + Math.floor(0.25 * w), y + h);
+				}
+
+				if ((alpha > 0) && (alpha < beta))
+				{
+					a = new mxPoint(x + Math.floor(0.5 * w), y
+							- Math.floor(0.5 * h));
+					b = new mxPoint(x + Math.floor(1.25 * w), y + h);
+				}
+				else if ((alpha > beta) && (alpha < (pi - beta)))
+				{
+					a = new mxPoint(x, y);
+					b = new mxPoint(x + w, y);
+				}
+				else if ((alpha > (pi - beta)) && (alpha < pi))
+				{
+					a = new mxPoint(x - Math.floor(0.25 * w), y + h);
+					b = new mxPoint(x + Math.floor(0.5 * w), y
+							- Math.floor(0.5 * h));
+				}
+				else if ((alpha < 0) && (alpha > -beta))
+				{
+					a = new mxPoint(x + Math.floor(0.5 * w), y
+							+ Math.floor(1.5 * h));
+					b = new mxPoint(x + Math.floor(1.25 * w), y);
+				}
+				else if ((alpha < -beta) && (alpha > (-pi + beta)))
+				{
+					a = new mxPoint(x, y + h);
+					b = new mxPoint(x + w, y + h);
+				}
+				else if ((alpha < (-pi + beta)) && (alpha > -pi))
+				{
+					a = new mxPoint(x - Math.floor(0.25 * w), y);
+					b = new mxPoint(x + Math.floor(0.5 * w), y
+							+ Math.floor(1.5 * h));
+				}
+			}
+
+			result = mxUtils.intersection(cx, cy, next.x, next.y, a.x, a.y, b.x, b.y);
+		}
+		
+		if (result == null)
+		{
+			return new mxPoint(cx, cy);
+		}
+		
+		return result;
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/view/mxPrintPreview.js b/airavata-kubernetes/web-console/src/assets/js/view/mxPrintPreview.js
new file mode 100644
index 0000000..14a6193
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/view/mxPrintPreview.js
@@ -0,0 +1,1175 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxPrintPreview
+ * 
+ * Implements printing of a diagram across multiple pages. The following opens
+ * a print preview for an existing graph:
+ * 
+ * (code)
+ * var preview = new mxPrintPreview(graph);
+ * preview.open();
+ * (end)
+ * 
+ * Use <mxUtils.getScaleForPageCount> as follows in order to print the graph
+ * across a given number of pages:
+ * 
+ * (code)
+ * var pageCount = mxUtils.prompt('Enter page count', '1');
+ * 
+ * if (pageCount != null)
+ * {
+ *   var scale = mxUtils.getScaleForPageCount(pageCount, graph);
+ *   var preview = new mxPrintPreview(graph, scale);
+ *   preview.open();
+ * }
+ * (end)
+ * 
+ * Additional pages:
+ * 
+ * To add additional pages before and after the output, <getCoverPages> and
+ * <getAppendices> can be used, respectively.
+ * 
+ * (code)
+ * var preview = new mxPrintPreview(graph, 1);
+ * 
+ * preview.getCoverPages = function(w, h)
+ * {
+ *   return [this.renderPage(w, h, 0, 0, mxUtils.bind(this, function(div)
+ *   {
+ *     div.innerHTML = '<div style="position:relative;margin:4px;">Cover Page</p>'
+ *   }))];
+ * };
+ * 
+ * preview.getAppendices = function(w, h)
+ * {
+ *   return [this.renderPage(w, h, 0, 0, mxUtils.bind(this, function(div)
+ *   {
+ *     div.innerHTML = '<div style="position:relative;margin:4px;">Appendix</p>'
+ *   }))];
+ * };
+ * 
+ * preview.open();
+ * (end)
+ * 
+ * CSS:
+ * 
+ * The CSS from the original page is not carried over to the print preview.
+ * To add CSS to the page, use the css argument in the <open> function or
+ * override <writeHead> to add the respective link tags as follows:
+ * 
+ * (code)
+ * var writeHead = preview.writeHead;
+ * preview.writeHead = function(doc, css)
+ * {
+ *   writeHead.apply(this, arguments);
+ *   doc.writeln('<link rel="stylesheet" type="text/css" href="style.css">');
+ * };
+ * (end)
+ * 
+ * Padding:
+ * 
+ * To add a padding to the page in the preview (but not the print output), use
+ * the following code:
+ * 
+ * (code)
+ * preview.writeHead = function(doc)
+ * {
+ *   writeHead.apply(this, arguments);
+ *   
+ *   doc.writeln('<style type="text/css">');
+ *   doc.writeln('@media screen {');
+ *   doc.writeln('  body > div { padding-top:30px;padding-left:40px;box-sizing:content-box; }');
+ *   doc.writeln('}');
+ *   doc.writeln('</style>');
+ * };
+ * (end)
+ * 
+ * Headers:
+ * 
+ * Apart from setting the title argument in the mxPrintPreview constructor you
+ * can override <renderPage> as follows to add a header to any page:
+ * 
+ * (code)
+ * var oldRenderPage = mxPrintPreview.prototype.renderPage;
+ * mxPrintPreview.prototype.renderPage = function(w, h, x, y, content, pageNumber)
+ * {
+ *   var div = oldRenderPage.apply(this, arguments);
+ *   
+ *   var header = document.createElement('div');
+ *   header.style.position = 'absolute';
+ *   header.style.top = '0px';
+ *   header.style.width = '100%';
+ *   header.style.textAlign = 'right';
+ *   mxUtils.write(header, 'Your header here');
+ *   div.firstChild.appendChild(header);
+ *   
+ *   return div;
+ * };
+ * (end)
+ * 
+ * The pageNumber argument contains the number of the current page, starting at
+ * 1. To display a header on the first page only, check pageNumber and add a
+ * vertical offset in the constructor call for the height of the header.
+ * 
+ * Page Format:
+ * 
+ * For landscape printing, use <mxConstants.PAGE_FORMAT_A4_LANDSCAPE> as
+ * the pageFormat in <mxUtils.getScaleForPageCount> and <mxPrintPreview>.
+ * Keep in mind that one can not set the defaults for the print dialog
+ * of the operating system from JavaScript so the user must manually choose
+ * a page format that matches this setting.
+ * 
+ * You can try passing the following CSS directive to <open> to set the
+ * page format in the print dialog to landscape. However, this CSS
+ * directive seems to be ignored in most major browsers, including IE.
+ * 
+ * (code)
+ * @page {
+ *   size: landscape;
+ * }
+ * (end)
+ * 
+ * Note that the print preview behaves differently in IE when used from the
+ * filesystem or via HTTP so printing should always be tested via HTTP.
+ * 
+ * If you are using a DOCTYPE in the source page you can override <getDoctype>
+ * and provide the same DOCTYPE for the print preview if required. Here is
+ * an example for IE8 standards mode.
+ * 
+ * (code)
+ * var preview = new mxPrintPreview(graph);
+ * preview.getDoctype = function()
+ * {
+ *   return '<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=5,IE=8" ><![endif]-->';
+ * };
+ * preview.open();
+ * (end)
+ * 
+ * Constructor: mxPrintPreview
+ *
+ * Constructs a new print preview for the given parameters.
+ * 
+ * Parameters:
+ * 
+ * graph - <mxGraph> to be previewed.
+ * scale - Optional scale of the output. Default is 1 / <mxGraph.pageScale>.
+ * border - Border in pixels along each side of every page. Note that the
+ * actual print function in the browser will add another border for
+ * printing.
+ * pageFormat - <mxRectangle> that specifies the page format (in pixels).
+ * This should match the page format of the printer. Default uses the
+ * <mxGraph.pageFormat> of the given graph.
+ * x0 - Optional left offset of the output. Default is 0.
+ * y0 - Optional top offset of the output. Default is 0.
+ * borderColor - Optional color of the page border. Default is no border.
+ * Note that a border is sometimes useful to highlight the printed page
+ * border in the print preview of the browser.
+ * title - Optional string that is used for the window title. Default
+ * is 'Printer-friendly version'.
+ * pageSelector - Optional boolean that specifies if the page selector
+ * should appear in the window with the print preview. Default is true.
+ */
+function mxPrintPreview(graph, scale, pageFormat, border, x0, y0, borderColor, title, pageSelector)
+{
+	this.graph = graph;
+	this.scale = (scale != null) ? scale : 1 / graph.pageScale;
+	this.border = (border != null) ? border : 0;
+	this.pageFormat = mxRectangle.fromRectangle((pageFormat != null) ? pageFormat : graph.pageFormat);
+	this.title = (title != null) ? title : 'Printer-friendly version';
+	this.x0 = (x0 != null) ? x0 : 0;
+	this.y0 = (y0 != null) ? y0 : 0;
+	this.borderColor = borderColor;
+	this.pageSelector = (pageSelector != null) ? pageSelector : true;
+};
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the <mxGraph> that should be previewed.
+ */
+mxPrintPreview.prototype.graph = null;
+
+/**
+ * Variable: pageFormat
+ *
+ * Holds the <mxRectangle> that defines the page format.
+ */
+mxPrintPreview.prototype.pageFormat = null;
+
+/**
+ * Variable: scale
+ * 
+ * Holds the scale of the print preview.
+ */
+mxPrintPreview.prototype.scale = null;
+
+/**
+ * Variable: border
+ * 
+ * The border inset around each side of every page in the preview. This is set
+ * to 0 if autoOrigin is false.
+ */
+mxPrintPreview.prototype.border = 0;
+
+/**
+ * Variable: marginTop
+ * 
+ * The margin at the top of the page (number). Default is 0.
+ */
+mxPrintPreview.prototype.marginTop = 0;
+
+/**
+ * Variable: marginBottom
+ * 
+ * The margin at the bottom of the page (number). Default is 0.
+ */
+mxPrintPreview.prototype.marginBottom = 0;
+
+/**
+ * Variable: x0
+ * 
+ * Holds the horizontal offset of the output.
+ */
+mxPrintPreview.prototype.x0 = 0;
+
+/**
+ * Variable: y0
+ *
+ * Holds the vertical offset of the output.
+ */
+mxPrintPreview.prototype.y0 = 0;
+
+/**
+ * Variable: autoOrigin
+ * 
+ * Specifies if the origin should be automatically computed based on the top,
+ * left corner of the actual diagram contents. The required offset will be added
+ * to <x0> and <y0> in <open>. Default is true.
+ */
+mxPrintPreview.prototype.autoOrigin = true;
+
+/**
+ * Variable: printOverlays
+ * 
+ * Specifies if overlays should be printed. Default is false.
+ */
+mxPrintPreview.prototype.printOverlays = false;
+
+/**
+ * Variable: printControls
+ * 
+ * Specifies if controls (such as folding icons) should be printed. Default is
+ * false.
+ */
+mxPrintPreview.prototype.printControls = false;
+
+/**
+ * Variable: printBackgroundImage
+ * 
+ * Specifies if the background image should be printed. Default is false.
+ */
+mxPrintPreview.prototype.printBackgroundImage = false;
+
+/**
+ * Variable: backgroundColor
+ * 
+ * Holds the color value for the page background color. Default is #ffffff.
+ */
+mxPrintPreview.prototype.backgroundColor = '#ffffff';
+
+/**
+ * Variable: borderColor
+ * 
+ * Holds the color value for the page border.
+ */
+mxPrintPreview.prototype.borderColor = null;
+
+/**
+ * Variable: title
+ * 
+ * Holds the title of the preview window.
+ */
+mxPrintPreview.prototype.title = null;
+
+/**
+ * Variable: pageSelector
+ * 
+ * Boolean that specifies if the page selector should be
+ * displayed. Default is true.
+ */
+mxPrintPreview.prototype.pageSelector = null;
+
+/**
+ * Variable: wnd
+ * 
+ * Reference to the preview window.
+ */
+mxPrintPreview.prototype.wnd = null;
+
+/**
+ * Variable: targetWindow
+ * 
+ * Assign any window here to redirect the rendering in <open>.
+ */
+mxPrintPreview.prototype.targetWindow = null;
+
+/**
+ * Variable: pageCount
+ * 
+ * Holds the actual number of pages in the preview.
+ */
+mxPrintPreview.prototype.pageCount = 0;
+
+/**
+ * Variable: clipping
+ * 
+ * Specifies is clipping should be used to avoid creating too many cell states
+ * in large diagrams. The bounding box of the cells in the original diagram is
+ * used if this is enabled. Default is true.
+ */
+mxPrintPreview.prototype.clipping = true;
+
+/**
+ * Function: getWindow
+ * 
+ * Returns <wnd>.
+ */
+mxPrintPreview.prototype.getWindow = function()
+{
+	return this.wnd;
+};
+
+/**
+ * Function: getDocType
+ * 
+ * Returns the string that should go before the HTML tag in the print preview
+ * page. This implementation returns an X-UA meta tag for IE5 in quirks mode,
+ * IE8 in IE8 standards mode and edge in IE9 standards mode.
+ */
+mxPrintPreview.prototype.getDoctype = function()
+{
+	var dt = '';
+	
+	if (document.documentMode == 5)
+	{
+		dt = '<meta http-equiv="X-UA-Compatible" content="IE=5">';
+	}
+	else if (document.documentMode == 8)
+	{
+		dt = '<meta http-equiv="X-UA-Compatible" content="IE=8">';
+	}
+	else if (document.documentMode > 8)
+	{
+		// Comment needed to make standards doctype apply in IE
+		dt = '<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=edge"><![endif]-->';
+	}
+	
+	return dt;
+};
+
+/**
+ * Function: appendGraph
+ * 
+ * Adds the given graph to the existing print preview.
+ * 
+ * Parameters:
+ * 
+ * css - Optional CSS string to be used in the head section.
+ * targetWindow - Optional window that should be used for rendering. If
+ * this is specified then no HEAD tag, CSS and BODY tag will be written.
+ */
+mxPrintPreview.prototype.appendGraph = function(graph, scale, x0, y0, forcePageBreaks, keepOpen)
+{
+	this.graph = graph;
+	this.scale = (scale != null) ? scale : 1 / graph.pageScale;
+	this.x0 = x0;
+	this.y0 = y0;
+	this.open(null, null, forcePageBreaks, keepOpen);
+};
+
+/**
+ * Function: open
+ * 
+ * Shows the print preview window. The window is created here if it does
+ * not exist.
+ * 
+ * Parameters:
+ * 
+ * css - Optional CSS string to be used in the head section.
+ * targetWindow - Optional window that should be used for rendering. If
+ * this is specified then no HEAD tag, CSS and BODY tag will be written.
+ */
+mxPrintPreview.prototype.open = function(css, targetWindow, forcePageBreaks, keepOpen)
+{
+	// Closing the window while the page is being rendered may cause an
+	// exception in IE. This and any other exceptions are simply ignored.
+	var previousInitializeOverlay = this.graph.cellRenderer.initializeOverlay;
+	var div = null;
+	
+	try
+	{
+		// Temporarily overrides the method to redirect rendering of overlays
+		// to the draw pane so that they are visible in the printout
+		if (this.printOverlays)
+		{
+			this.graph.cellRenderer.initializeOverlay = function(state, overlay)
+			{
+				overlay.init(state.view.getDrawPane());
+			};
+		}
+		
+		if (this.printControls)
+		{
+			this.graph.cellRenderer.initControl = function(state, control, handleEvents, clickHandler)
+			{
+				control.dialect = state.view.graph.dialect;
+				control.init(state.view.getDrawPane());
+			};
+		}
+		
+		this.wnd = (targetWindow != null) ? targetWindow : this.wnd;
+		var isNewWindow = false;
+		
+		if (this.wnd == null)
+		{
+			isNewWindow = true;
+			this.wnd = window.open();
+		}
+		
+		var doc = this.wnd.document;
+		
+		if (isNewWindow)
+		{
+			var dt = this.getDoctype();
+			
+			if (dt != null && dt.length > 0)
+			{
+				doc.writeln(dt);
+			}
+			
+			if (mxClient.IS_VML)
+			{
+				doc.writeln('<html xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">');
+			}
+			else
+			{
+				if (document.compatMode === 'CSS1Compat')
+				{
+					doc.writeln('<!DOCTYPE html>');
+				}
+				
+				doc.writeln('<html>');
+			}
+			
+			doc.writeln('<head>');
+			this.writeHead(doc, css);
+			doc.writeln('</head>');
+			doc.writeln('<body class="mxPage">');
+		}
+
+		// Computes the horizontal and vertical page count
+		var bounds = this.graph.getGraphBounds().clone();
+		var currentScale = this.graph.getView().getScale();
+		var sc = currentScale / this.scale;
+		var tr = this.graph.getView().getTranslate();
+		
+		// Uses the absolute origin with no offset for all printing
+		if (!this.autoOrigin)
+		{
+			this.x0 -= tr.x * this.scale;
+			this.y0 -= tr.y * this.scale;
+			bounds.width += bounds.x;
+			bounds.height += bounds.y;
+			bounds.x = 0;
+			bounds.y = 0;
+			this.border = 0;
+		}
+		
+		// Store the available page area
+		var availableWidth = this.pageFormat.width - (this.border * 2);
+		var availableHeight = this.pageFormat.height - (this.border * 2);
+	
+		// Adds margins to page format
+		this.pageFormat.height += this.marginTop + this.marginBottom;
+
+		// Compute the unscaled, untranslated bounds to find
+		// the number of vertical and horizontal pages
+		bounds.width /= sc;
+		bounds.height /= sc;
+
+		var hpages = Math.max(1, Math.ceil((bounds.width + this.x0) / availableWidth));
+		var vpages = Math.max(1, Math.ceil((bounds.height + this.y0) / availableHeight));
+		this.pageCount = hpages * vpages;
+		
+		var writePageSelector = mxUtils.bind(this, function()
+		{
+			if (this.pageSelector && (vpages > 1 || hpages > 1))
+			{
+				var table = this.createPageSelector(vpages, hpages);
+				doc.body.appendChild(table);
+				
+				// Implements position: fixed in IE quirks mode
+				if (mxClient.IS_IE && doc.documentMode == null || doc.documentMode == 5 || doc.documentMode == 8 || doc.documentMode == 7)
+				{
+					table.style.position = 'absolute';
+					
+					var update = function()
+					{
+						table.style.top = ((doc.body.scrollTop || doc.documentElement.scrollTop) + 10) + 'px';
+					};
+					
+					mxEvent.addListener(this.wnd, 'scroll', function(evt)
+					{
+						update();
+					});
+					
+					mxEvent.addListener(this.wnd, 'resize', function(evt)
+					{
+						update();
+					});
+				}
+			}
+		});
+		
+		var addPage = mxUtils.bind(this, function(div, addBreak)
+		{
+			// Border of the DIV (aka page) inside the document
+			if (this.borderColor != null)
+			{
+				div.style.borderColor = this.borderColor;
+				div.style.borderStyle = 'solid';
+				div.style.borderWidth = '1px';
+			}
+			
+			// Needs to be assigned directly because IE doesn't support
+			// child selectors, eg. body > div { background: white; }
+			div.style.background = this.backgroundColor;
+			
+			if (forcePageBreaks || addBreak)
+			{
+				div.style.pageBreakAfter = 'always';
+			}
+
+			// NOTE: We are dealing with cross-window DOM here, which
+			// is a problem in IE, so we copy the HTML markup instead.
+			// The underlying problem is that the graph display markup
+			// creation (in mxShape, mxGraphView) is hardwired to using
+			// document.createElement and hence we must use this document
+			// to create the complete page and then copy it over to the
+			// new window.document. This can be fixed later by using the
+			// ownerDocument of the container in mxShape and mxGraphView.
+			if (isNewWindow && (mxClient.IS_IE || document.documentMode >= 11 || mxClient.IS_EDGE))
+			{
+				// For some obscure reason, removing the DIV from the
+				// parent before fetching its outerHTML has missing
+				// fillcolor properties and fill children, so the div
+				// must be removed afterwards to keep the fillcolors.
+				doc.writeln(div.outerHTML);
+				div.parentNode.removeChild(div);
+			}
+			else
+			{
+				div.parentNode.removeChild(div);
+				doc.body.appendChild(div);
+			}
+
+			if (forcePageBreaks || addBreak)
+			{
+				this.addPageBreak(doc);
+			}
+		});
+		
+		var cov = this.getCoverPages(this.pageFormat.width, this.pageFormat.height);
+		
+		if (cov != null)
+		{
+			for (var i = 0; i < cov.length; i++)
+			{
+				addPage(cov[i], true);
+			}
+		}
+		
+		var apx = this.getAppendices(this.pageFormat.width, this.pageFormat.height);
+		
+		// Appends each page to the page output for printing, making
+		// sure there will be a page break after each page (ie. div)
+		for (var i = 0; i < vpages; i++)
+		{
+			var dy = i * availableHeight / this.scale - this.y0 / this.scale +
+					(bounds.y - tr.y * currentScale) / currentScale;
+			
+			for (var j = 0; j < hpages; j++)
+			{
+				if (this.wnd == null)
+				{
+					return null;
+				}
+				
+				var dx = j * availableWidth / this.scale - this.x0 / this.scale +
+						(bounds.x - tr.x * currentScale) / currentScale;
+				var pageNum = i * hpages + j + 1;
+				var clip = new mxRectangle(dx, dy, availableWidth, availableHeight);
+				div = this.renderPage(this.pageFormat.width, this.pageFormat.height, 0, 0, mxUtils.bind(this, function(div)
+				{
+					this.addGraphFragment(-dx, -dy, this.scale, pageNum, div, clip);
+					
+					if (this.printBackgroundImage)
+					{
+						this.insertBackgroundImage(div, -dx, -dy);
+					}
+				}), pageNum);
+
+				// Gives the page a unique ID for later accessing the page
+				div.setAttribute('id', 'mxPage-'+pageNum);
+
+				addPage(div, apx != null || i < vpages - 1 || j < hpages - 1);
+			}
+		}
+
+		if (apx != null)
+		{
+			for (var i = 0; i < apx.length; i++)
+			{
+				addPage(apx[i], i < apx.length - 1);
+			}
+		}
+
+		if (isNewWindow && !keepOpen)
+		{
+			this.closeDocument();
+			writePageSelector();
+		}
+		
+		this.wnd.focus();
+	}
+	catch (e)
+	{
+		// Removes the DIV from the document in case of an error
+		if (div != null && div.parentNode != null)
+		{
+			div.parentNode.removeChild(div);
+		}
+	}
+	finally
+	{
+		this.graph.cellRenderer.initializeOverlay = previousInitializeOverlay;
+	}
+
+	return this.wnd;
+};
+
+/**
+ * Function: addPageBreak
+ * 
+ * Adds a page break to the given document.
+ */
+mxPrintPreview.prototype.addPageBreak = function(doc)
+{
+	var hr = doc.createElement('hr');
+	hr.className = 'mxPageBreak';
+	doc.body.appendChild(hr);
+};
+
+/**
+ * Function: closeDocument
+ * 
+ * Writes the closing tags for body and page after calling <writePostfix>.
+ */
+mxPrintPreview.prototype.closeDocument = function()
+{
+	if (this.wnd != null && this.wnd.document != null)
+	{
+		var doc = this.wnd.document;
+		
+		this.writePostfix(doc);
+		doc.writeln('</body>');
+		doc.writeln('</html>');
+		doc.close();
+		
+		// Removes all event handlers in the print output
+		mxEvent.release(doc.body);
+	}
+};
+
+/**
+ * Function: writeHead
+ * 
+ * Writes the HEAD section into the given document, without the opening
+ * and closing HEAD tags.
+ */
+mxPrintPreview.prototype.writeHead = function(doc, css)
+{
+	if (this.title != null)
+	{
+		doc.writeln('<title>' + this.title + '</title>');
+	}
+	
+	// Adds required namespaces
+	if (mxClient.IS_VML)
+	{
+		doc.writeln('<style type="text/css">v\\:*{behavior:url(#default#VML)}o\\:*{behavior:url(#default#VML)}</style>');
+	}
+
+	// Adds all required stylesheets
+	mxClient.link('stylesheet', mxClient.basePath + '/css/common.css', doc);
+
+	// Removes horizontal rules and page selector from print output
+	doc.writeln('<style type="text/css">');
+	doc.writeln('@media print {');
+	doc.writeln('  table.mxPageSelector { display: none; }');
+	doc.writeln('  hr.mxPageBreak { display: none; }');
+	doc.writeln('}');
+	doc.writeln('@media screen {');
+	
+	// NOTE: position: fixed is not supported in IE, so the page selector
+	// position (absolute) needs to be updated in IE (see below)
+	doc.writeln('  table.mxPageSelector { position: fixed; right: 10px; top: 10px;' +
+			'font-family: Arial; font-size:10pt; border: solid 1px darkgray;' +
+			'background: white; border-collapse:collapse; }');
+	doc.writeln('  table.mxPageSelector td { border: solid 1px gray; padding:4px; }');
+	doc.writeln('  body.mxPage { background: gray; }');
+	doc.writeln('}');
+	
+	if (css != null)
+	{
+		doc.writeln(css);
+	}
+	
+	doc.writeln('</style>');
+};
+
+/**
+ * Function: writePostfix
+ * 
+ * Called before closing the body of the page. This implementation is empty.
+ */
+mxPrintPreview.prototype.writePostfix = function(doc)
+{
+	// empty
+};
+
+/**
+ * Function: createPageSelector
+ * 
+ * Creates the page selector table.
+ */
+mxPrintPreview.prototype.createPageSelector = function(vpages, hpages)
+{
+	var doc = this.wnd.document;
+	var table = doc.createElement('table');
+	table.className = 'mxPageSelector';
+	table.setAttribute('border', '0');
+
+	var tbody = doc.createElement('tbody');
+	
+	for (var i = 0; i < vpages; i++)
+	{
+		var row = doc.createElement('tr');
+		
+		for (var j = 0; j < hpages; j++)
+		{
+			var pageNum = i * hpages + j + 1;
+			var cell = doc.createElement('td');
+			var a = doc.createElement('a');
+			a.setAttribute('href', '#mxPage-' + pageNum);
+
+			// Workaround for FF where the anchor is appended to the URL of the original document
+			if (mxClient.IS_NS && !mxClient.IS_SF && !mxClient.IS_GC)
+			{
+				var js = 'var page = document.getElementById(\'mxPage-' + pageNum + '\');page.scrollIntoView(true);event.preventDefault();';
+				a.setAttribute('onclick', js);
+			}
+			
+			mxUtils.write(a, pageNum, doc);
+			cell.appendChild(a);
+			row.appendChild(cell);
+		}
+		
+		tbody.appendChild(row);
+	}
+	
+	table.appendChild(tbody);
+	
+	return table;
+};
+
+/**
+ * Function: renderPage
+ * 
+ * Creates a DIV that prints a single page of the given
+ * graph using the given scale and returns the DIV that
+ * represents the page.
+ * 
+ * Parameters:
+ * 
+ * w - Width of the page in pixels.
+ * h - Height of the page in pixels.
+ * dx - Optional horizontal page offset in pixels (used internally).
+ * dy - Optional vertical page offset in pixels (used internally).
+ * content - Callback that adds the HTML content to the inner div of a page.
+ * Takes the inner div as the argument.
+ * pageNumber - Integer representing the page number.
+ */
+mxPrintPreview.prototype.renderPage = function(w, h, dx, dy, content, pageNumber)
+{
+	var doc = this.wnd.document;
+	var div = document.createElement('div');
+	var arg = null;
+
+	try
+	{
+		// Workaround for ignored clipping in IE 9 standards
+		// when printing with page breaks and HTML labels.
+		if (dx != 0 || dy != 0)
+		{
+			div.style.position = 'relative';
+			div.style.width = w + 'px';
+			div.style.height = h + 'px';
+			div.style.pageBreakInside = 'avoid';
+			
+			var innerDiv = document.createElement('div');
+			innerDiv.style.position = 'relative';
+			innerDiv.style.top = this.border + 'px';
+			innerDiv.style.left = this.border + 'px';
+			innerDiv.style.width = (w - 2 * this.border) + 'px';
+			innerDiv.style.height = (h - 2 * this.border) + 'px';
+			innerDiv.style.overflow = 'hidden';
+			
+			var viewport = document.createElement('div');
+			viewport.style.position = 'relative';
+			viewport.style.marginLeft = dx + 'px';
+			viewport.style.marginTop = dy + 'px';
+
+			// FIXME: IE8 standards output problems
+			if (doc.documentMode == 8)
+			{
+				innerDiv.style.position = 'absolute';
+				viewport.style.position = 'absolute';
+			}
+		
+			if (doc.documentMode == 10)
+			{
+				viewport.style.width = '100%';
+				viewport.style.height = '100%';
+			}
+			
+			innerDiv.appendChild(viewport);
+			div.appendChild(innerDiv);
+			document.body.appendChild(div);
+			arg = viewport;
+		}
+		// FIXME: IE10/11 too many pages
+		else
+		{
+			div.style.width = w + 'px';
+			div.style.height = h + 'px';
+			div.style.overflow = 'hidden';
+			div.style.pageBreakInside = 'avoid';
+			
+			// IE8 uses above branch currently
+			if (doc.documentMode == 8)
+			{
+				div.style.position = 'relative';
+			}
+			
+			var innerDiv = document.createElement('div');
+			innerDiv.style.width = (w - 2 * this.border) + 'px';
+			innerDiv.style.height = (h - 2 * this.border) + 'px';
+			innerDiv.style.overflow = 'hidden';
+
+			if (mxClient.IS_IE && (doc.documentMode == null || doc.documentMode == 5 || doc.documentMode == 8 || doc.documentMode == 7))
+			{
+				innerDiv.style.marginTop = this.border + 'px';
+				innerDiv.style.marginLeft = this.border + 'px';	
+			}
+			else
+			{
+				innerDiv.style.top = this.border + 'px';
+				innerDiv.style.left = this.border + 'px';
+			}
+	
+			if (this.graph.dialect == mxConstants.DIALECT_VML)
+			{
+				innerDiv.style.position = 'absolute';
+			}
+
+			div.appendChild(innerDiv);
+			document.body.appendChild(div);
+			arg = innerDiv;
+		}
+	}
+	catch (e)
+	{
+		div.parentNode.removeChild(div);
+		div = null;
+		
+		throw e;
+	}
+
+	content(arg);
+	 
+	return div;
+};
+
+/**
+ * Function: getRoot
+ * 
+ * Returns the root cell for painting the graph.
+ */
+mxPrintPreview.prototype.getRoot = function()
+{
+	var root = this.graph.view.currentRoot;
+	
+	if (root == null)
+	{
+		root = this.graph.getModel().getRoot();
+	}
+	
+	return root;
+};
+
+/**
+ * Function: addGraphFragment
+ * 
+ * Adds a graph fragment to the given div.
+ * 
+ * Parameters:
+ * 
+ * dx - Horizontal translation for the diagram.
+ * dy - Vertical translation for the diagram.
+ * scale - Scale for the diagram.
+ * pageNumber - Number of the page to be rendered.
+ * div - Div that contains the output.
+ * clip - Contains the clipping rectangle as an <mxRectangle>.
+ */
+mxPrintPreview.prototype.addGraphFragment = function(dx, dy, scale, pageNumber, div, clip)
+{
+	var view = this.graph.getView();
+	var previousContainer = this.graph.container;
+	this.graph.container = div;
+	
+	var canvas = view.getCanvas();
+	var backgroundPane = view.getBackgroundPane();
+	var drawPane = view.getDrawPane();
+	var overlayPane = view.getOverlayPane();
+
+	if (this.graph.dialect == mxConstants.DIALECT_SVG)
+	{
+		view.createSvg();
+	}
+	else if (this.graph.dialect == mxConstants.DIALECT_VML)
+	{
+		view.createVml();
+	}
+	else
+	{
+		view.createHtml();
+	}
+	
+	// Disables events on the view
+	var eventsEnabled = view.isEventsEnabled();
+	view.setEventsEnabled(false);
+	
+	// Disables the graph to avoid cursors
+	var graphEnabled = this.graph.isEnabled();
+	this.graph.setEnabled(false);
+
+	// Resets the translation
+	var translate = view.getTranslate();
+	view.translate = new mxPoint(dx, dy);
+	
+	// Redraws only states that intersect the clip
+	var redraw = this.graph.cellRenderer.redraw;
+	var states = view.states;
+	var s = view.scale;
+	
+	// Gets the transformed clip for intersection check below
+	if (this.clipping)
+	{
+		var tempClip = new mxRectangle((clip.x + translate.x) * s, (clip.y + translate.y) * s,
+				clip.width * s / scale, clip.height * s / scale);
+		
+		// Checks clipping rectangle for speedup
+		// Must create terminal states for edge clipping even if terminal outside of clip
+		this.graph.cellRenderer.redraw = function(state, force, rendering)
+		{
+			if (state != null)
+			{
+				// Gets original state from graph to find bounding box
+				var orig = states.get(state.cell);
+				
+				if (orig != null)
+				{
+					var bbox = view.getBoundingBox(orig, false);
+					
+					// Stops rendering if outside clip for speedup
+					if (bbox != null && !mxUtils.intersects(tempClip, bbox))
+					{
+						return;
+					}
+				}
+			}
+			
+			redraw.apply(this, arguments);
+		};
+	}
+	
+	var temp = null;
+	
+	try
+	{
+		// Creates the temporary cell states in the view and
+		// draws them onto the temporary DOM nodes in the view
+		var cells = [this.getRoot()];
+		temp = new mxTemporaryCellStates(view, scale, cells);
+	}
+	finally
+	{
+		// Removes overlay pane with selection handles
+		// controls and icons from the print output
+		if (mxClient.IS_IE)
+		{
+			view.overlayPane.innerHTML = '';
+			view.canvas.style.overflow = 'hidden';
+			view.canvas.style.position = 'relative';
+			view.canvas.style.top = this.marginTop + 'px';
+			view.canvas.style.width = clip.width + 'px';
+			view.canvas.style.height = clip.height + 'px';
+		}
+		else
+		{
+			// Removes everything but the SVG node
+			var tmp = div.firstChild;
+
+			while (tmp != null)
+			{
+				var next = tmp.nextSibling;
+				var name = tmp.nodeName.toLowerCase();
+
+				// Note: Width and height are required in FF 11
+				if (name == 'svg')
+				{
+					tmp.style.overflow = 'hidden';
+					tmp.style.position = 'relative';
+					tmp.style.top = this.marginTop + 'px';
+					tmp.setAttribute('width', clip.width);
+					tmp.setAttribute('height', clip.height);
+					tmp.style.width = '';
+					tmp.style.height = '';
+				}
+				// Tries to fetch all text labels and only text labels
+				else if (tmp.style.cursor != 'default' && name != 'div')
+				{
+					tmp.parentNode.removeChild(tmp);
+				}
+				
+				tmp = next;
+			}
+		}
+		
+		// Puts background image behind SVG output
+		if (this.printBackgroundImage)
+		{
+			var svgs = div.getElementsByTagName('svg');
+			
+			if (svgs.length > 0)
+			{
+				svgs[0].style.position = 'absolute';
+			}
+		}
+		
+		// Completely removes the overlay pane to remove more handles
+		view.overlayPane.parentNode.removeChild(view.overlayPane);
+
+		// Restores the state of the view
+		this.graph.setEnabled(graphEnabled);
+		this.graph.container = previousContainer;
+		this.graph.cellRenderer.redraw = redraw;
+		view.canvas = canvas;
+		view.backgroundPane = backgroundPane;
+		view.drawPane = drawPane;
+		view.overlayPane = overlayPane;
+		view.translate = translate;
+		temp.destroy();
+		view.setEventsEnabled(eventsEnabled);
+	}
+};
+
+/**
+ * Function: insertBackgroundImage
+ * 
+ * Inserts the background image into the given div.
+ */
+mxPrintPreview.prototype.insertBackgroundImage = function(div, dx, dy)
+{
+	var bg = this.graph.backgroundImage;
+	
+	if (bg != null)
+	{
+		var img = document.createElement('img');
+		img.style.position = 'absolute';
+		img.style.marginLeft = Math.round(dx * this.scale) + 'px';
+		img.style.marginTop = Math.round(dy * this.scale) + 'px';
+		img.setAttribute('width', Math.round(this.scale * bg.width));
+		img.setAttribute('height', Math.round(this.scale * bg.height));
+		img.src = bg.src;
+		
+		div.insertBefore(img, div.firstChild);
+	}
+};
+
+/**
+ * Function: getCoverPages
+ * 
+ * Returns the pages to be added before the print output. This returns null.
+ */
+mxPrintPreview.prototype.getCoverPages = function()
+{
+	return null;
+};
+
+/**
+ * Function: getAppendices
+ * 
+ * Returns the pages to be added after the print output. This returns null.
+ */
+mxPrintPreview.prototype.getAppendices = function()
+{
+	return null;
+};
+
+/**
+ * Function: print
+ * 
+ * Opens the print preview and shows the print dialog.
+ * 
+ * Parameters:
+ * 
+ * css - Optional CSS string to be used in the head section.
+ */
+mxPrintPreview.prototype.print = function(css)
+{
+	var wnd = this.open(css);
+	
+	if (wnd != null)
+	{
+		wnd.print();
+	}
+};
+
+/**
+ * Function: close
+ * 
+ * Closes the print preview window.
+ */
+mxPrintPreview.prototype.close = function()
+{
+	if (this.wnd != null)
+	{
+		this.wnd.close();
+		this.wnd = null;
+	}
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/view/mxStyleRegistry.js b/airavata-kubernetes/web-console/src/assets/js/view/mxStyleRegistry.js
new file mode 100644
index 0000000..7d5d7af
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/view/mxStyleRegistry.js
@@ -0,0 +1,71 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+var mxStyleRegistry =
+{
+	/**
+	 * Class: mxStyleRegistry
+	 *
+	 * Singleton class that acts as a global converter from string to object values
+	 * in a style. This is currently only used to perimeters and edge styles.
+	 * 
+	 * Variable: values
+	 *
+	 * Maps from strings to objects.
+	 */
+	values: [],
+
+	/**
+	 * Function: putValue
+	 *
+	 * Puts the given object into the registry under the given name.
+	 */
+	putValue: function(name, obj)
+	{
+		mxStyleRegistry.values[name] = obj;
+	},
+
+	/**
+	 * Function: getValue
+	 *
+	 * Returns the value associated with the given name.
+	 */
+	getValue: function(name)
+	{
+		return mxStyleRegistry.values[name];
+	},
+	
+	/**
+	 * Function: getName
+	 * 
+	 * Returns the name for the given value.
+	 */
+	getName: function(value)
+	{
+		for (var key in mxStyleRegistry.values)
+		{
+			if (mxStyleRegistry.values[key] == value)
+			{
+				return key;
+			}
+		}
+		
+		return null;
+	}
+
+};
+
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_ELBOW, mxEdgeStyle.ElbowConnector);
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_ENTITY_RELATION, mxEdgeStyle.EntityRelation);
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_LOOP, mxEdgeStyle.Loop);
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_SIDETOSIDE, mxEdgeStyle.SideToSide);
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_TOPTOBOTTOM, mxEdgeStyle.TopToBottom);
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_ORTHOGONAL, mxEdgeStyle.OrthConnector);
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_SEGMENT, mxEdgeStyle.SegmentConnector);
+
+mxStyleRegistry.putValue(mxConstants.PERIMETER_ELLIPSE, mxPerimeter.EllipsePerimeter);
+mxStyleRegistry.putValue(mxConstants.PERIMETER_RECTANGLE, mxPerimeter.RectanglePerimeter);
+mxStyleRegistry.putValue(mxConstants.PERIMETER_RHOMBUS, mxPerimeter.RhombusPerimeter);
+mxStyleRegistry.putValue(mxConstants.PERIMETER_TRIANGLE, mxPerimeter.TrianglePerimeter);
+mxStyleRegistry.putValue(mxConstants.PERIMETER_HEXAGON, mxPerimeter.HexagonPerimeter);
diff --git a/airavata-kubernetes/web-console/src/assets/js/view/mxStylesheet.js b/airavata-kubernetes/web-console/src/assets/js/view/mxStylesheet.js
new file mode 100644
index 0000000..ed37cff
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/view/mxStylesheet.js
@@ -0,0 +1,266 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxStylesheet
+ * 
+ * Defines the appearance of the cells in a graph. See <putCellStyle> for an
+ * example of creating a new cell style. It is recommended to use objects, not
+ * arrays for holding cell styles. Existing styles can be cloned using
+ * <mxUtils.clone> and turned into a string for debugging using
+ * <mxUtils.toString>.
+ *
+ * Default Styles:
+ * 
+ * The stylesheet contains two built-in styles, which are used if no style is
+ * defined for a cell:
+ *
+ *   defaultVertex - Default style for vertices
+ *   defaultEdge - Default style for edges
+ * 
+ * Example:
+ * 
+ * (code)
+ * var vertexStyle = stylesheet.getDefaultVertexStyle();
+ * vertexStyle[mxConstants.ROUNDED] = true;
+ * var edgeStyle = stylesheet.getDefaultEdgeStyle();
+ * edgeStyle[mxConstants.STYLE_EDGE] = mxEdgeStyle.EntityRelation;
+ * (end)
+ * 
+ * Modifies the built-in default styles.
+ * 
+ * To avoid the default style for a cell, add a leading semicolon
+ * to the style definition, eg.
+ * 
+ * (code)
+ * ;shadow=1
+ * (end)
+ * 
+ * Removing keys:
+ * 
+ * For removing a key in a cell style of the form [stylename;|key=value;] the
+ * special value none can be used, eg. highlight;fillColor=none
+ * 
+ * See also the helper methods in mxUtils to modify strings of this format,
+ * namely <mxUtils.setStyle>, <mxUtils.indexOfStylename>,
+ * <mxUtils.addStylename>, <mxUtils.removeStylename>,
+ * <mxUtils.removeAllStylenames> and <mxUtils.setStyleFlag>.
+ * 
+ * Constructor: mxStylesheet
+ * 
+ * Constructs a new stylesheet and assigns default styles.
+ */
+function mxStylesheet()
+{
+	this.styles = new Object();
+	
+	this.putDefaultVertexStyle(this.createDefaultVertexStyle());
+	this.putDefaultEdgeStyle(this.createDefaultEdgeStyle());
+};
+
+/**
+ * Function: styles
+ * 
+ * Maps from names to cell styles. Each cell style is a map of key,
+ * value pairs.
+ */
+mxStylesheet.prototype.styles;
+
+/**
+ * Function: createDefaultVertexStyle
+ * 
+ * Creates and returns the default vertex style.
+ */
+mxStylesheet.prototype.createDefaultVertexStyle = function()
+{
+	var style = new Object();
+	
+	style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_RECTANGLE;
+	style[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter;
+	style[mxConstants.STYLE_VERTICAL_ALIGN] = mxConstants.ALIGN_MIDDLE;
+	style[mxConstants.STYLE_ALIGN] = mxConstants.ALIGN_CENTER;
+	style[mxConstants.STYLE_FILLCOLOR] = '#C3D9FF';
+	style[mxConstants.STYLE_STROKECOLOR] = '#6482B9';
+	style[mxConstants.STYLE_FONTCOLOR] = '#774400';
+	
+	return style;
+};
+
+/**
+ * Function: createDefaultEdgeStyle
+ * 
+ * Creates and returns the default edge style.
+ */
+mxStylesheet.prototype.createDefaultEdgeStyle = function()
+{
+	var style = new Object();
+	
+	style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_CONNECTOR;
+	style[mxConstants.STYLE_ENDARROW] = mxConstants.ARROW_CLASSIC;
+	style[mxConstants.STYLE_VERTICAL_ALIGN] = mxConstants.ALIGN_MIDDLE;
+	style[mxConstants.STYLE_ALIGN] = mxConstants.ALIGN_CENTER;
+	style[mxConstants.STYLE_STROKECOLOR] = '#6482B9';
+	style[mxConstants.STYLE_FONTCOLOR] = '#446299';
+	
+	return style;
+};
+
+/**
+ * Function: putDefaultVertexStyle
+ * 
+ * Sets the default style for vertices using defaultVertex as the
+ * stylename.
+ * 
+ * Parameters:
+ * style - Key, value pairs that define the style.
+ */
+mxStylesheet.prototype.putDefaultVertexStyle = function(style)
+{
+	this.putCellStyle('defaultVertex', style);
+};
+
+/**
+ * Function: putDefaultEdgeStyle
+ * 
+ * Sets the default style for edges using defaultEdge as the stylename.
+ */
+mxStylesheet.prototype.putDefaultEdgeStyle = function(style)
+{
+	this.putCellStyle('defaultEdge', style);
+};
+
+/**
+ * Function: getDefaultVertexStyle
+ * 
+ * Returns the default style for vertices.
+ */
+mxStylesheet.prototype.getDefaultVertexStyle = function()
+{
+	return this.styles['defaultVertex'];
+};
+
+/**
+ * Function: getDefaultEdgeStyle
+ * 
+ * Sets the default style for edges.
+ */
+mxStylesheet.prototype.getDefaultEdgeStyle = function()
+{
+	return this.styles['defaultEdge'];
+};
+
+/**
+ * Function: putCellStyle
+ * 
+ * Stores the given map of key, value pairs under the given name in
+ * <styles>.
+ *
+ * Example:
+ * 
+ * The following example adds a new style called 'rounded' into an
+ * existing stylesheet:
+ * 
+ * (code)
+ * var style = new Object();
+ * style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_RECTANGLE;
+ * style[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter;
+ * style[mxConstants.STYLE_ROUNDED] = true;
+ * graph.getStylesheet().putCellStyle('rounded', style);
+ * (end)
+ * 
+ * In the above example, the new style is an object. The possible keys of
+ * the object are all the constants in <mxConstants> that start with STYLE
+ * and the values are either JavaScript objects, such as
+ * <mxPerimeter.RightAngleRectanglePerimeter> (which is in fact a function)
+ * or expressions, such as true. Note that not all keys will be
+ * interpreted by all shapes (eg. the line shape ignores the fill color).
+ * The final call to this method associates the style with a name in the
+ * stylesheet. The style is used in a cell with the following code:
+ * 
+ * (code)
+ * model.setStyle(cell, 'rounded');
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * name - Name for the style to be stored.
+ * style - Key, value pairs that define the style.
+ */
+mxStylesheet.prototype.putCellStyle = function(name, style)
+{
+	this.styles[name] = style;
+};
+
+/**
+ * Function: getCellStyle
+ * 
+ * Returns the cell style for the specified stylename or the given
+ * defaultStyle if no style can be found for the given stylename.
+ * 
+ * Parameters:
+ * 
+ * name - String of the form [(stylename|key=value);] that represents the
+ * style.
+ * defaultStyle - Default style to be returned if no style can be found.
+ */
+mxStylesheet.prototype.getCellStyle = function(name, defaultStyle)
+{
+	var style = defaultStyle;
+	
+	if (name != null && name.length > 0)
+	{
+		var pairs = name.split(';');
+
+		if (style != null &&
+			name.charAt(0) != ';')
+		{
+			style = mxUtils.clone(style);
+		}
+		else
+		{
+			style = new Object();
+		}
+
+		// Parses each key, value pair into the existing style
+	 	for (var i = 0; i < pairs.length; i++)
+	 	{
+	 		var tmp = pairs[i];
+	 		var pos = tmp.indexOf('=');
+	 		
+	 		if (pos >= 0)
+	 		{
+		 		var key = tmp.substring(0, pos);
+		 		var value = tmp.substring(pos + 1);
+
+		 		if (value == mxConstants.NONE)
+		 		{
+		 			delete style[key];
+		 		}
+		 		else if (mxUtils.isNumeric(value))
+		 		{
+		 			style[key] = parseFloat(value);
+		 		}
+		 		else
+		 		{
+			 		style[key] = value;
+		 		}
+			}
+	 		else
+	 		{
+	 			// Merges the entries from a named style
+				var tmpStyle = this.styles[tmp];
+				
+				if (tmpStyle != null)
+				{
+					for (var key in tmpStyle)
+					{
+						style[key] = tmpStyle[key];
+					}
+				}
+	 		}
+		}
+	}
+	
+	return style;
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/view/mxSwimlaneManager.js b/airavata-kubernetes/web-console/src/assets/js/view/mxSwimlaneManager.js
new file mode 100644
index 0000000..f02214f
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/view/mxSwimlaneManager.js
@@ -0,0 +1,450 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxSwimlaneManager
+ * 
+ * Manager for swimlanes and nested swimlanes that sets the size of newly added
+ * swimlanes to that of their siblings, and propagates changes to the size of a
+ * swimlane to its siblings, if <siblings> is true, and its ancestors, if
+ * <bubbling> is true.
+ * 
+ * Constructor: mxSwimlaneManager
+ *
+ * Constructs a new swimlane manager for the given graph.
+ *
+ * Arguments:
+ * 
+ * graph - Reference to the enclosing graph. 
+ */
+function mxSwimlaneManager(graph, horizontal, addEnabled, resizeEnabled)
+{
+	this.horizontal = (horizontal != null) ? horizontal : true;
+	this.addEnabled = (addEnabled != null) ? addEnabled : true;
+	this.resizeEnabled = (resizeEnabled != null) ? resizeEnabled : true;
+
+	this.addHandler = mxUtils.bind(this, function(sender, evt)
+	{
+		if (this.isEnabled() && this.isAddEnabled())
+		{
+			this.cellsAdded(evt.getProperty('cells'));
+		}
+	});
+	
+	this.resizeHandler = mxUtils.bind(this, function(sender, evt)
+	{
+		if (this.isEnabled() && this.isResizeEnabled())
+		{
+			this.cellsResized(evt.getProperty('cells'));
+		}
+	});
+	
+	this.setGraph(graph);
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxSwimlaneManager.prototype = new mxEventSource();
+mxSwimlaneManager.prototype.constructor = mxSwimlaneManager;
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxSwimlaneManager.prototype.graph = null;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies if event handling is enabled. Default is true.
+ */
+mxSwimlaneManager.prototype.enabled = true;
+
+/**
+ * Variable: horizontal
+ * 
+ * Specifies the orientation of the swimlanes. Default is true.
+ */
+mxSwimlaneManager.prototype.horizontal = true;
+
+/**
+ * Variable: addEnabled
+ * 
+ * Specifies if newly added cells should be resized to match the size of their
+ * existing siblings. Default is true.
+ */
+mxSwimlaneManager.prototype.addEnabled = true;
+
+/**
+ * Variable: resizeEnabled
+ * 
+ * Specifies if resizing of swimlanes should be handled. Default is true.
+ */
+mxSwimlaneManager.prototype.resizeEnabled = true;
+
+/**
+ * Variable: moveHandler
+ * 
+ * Holds the function that handles the move event.
+ */
+mxSwimlaneManager.prototype.addHandler = null;
+
+/**
+ * Variable: moveHandler
+ * 
+ * Holds the function that handles the move event.
+ */
+mxSwimlaneManager.prototype.resizeHandler = null;
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxSwimlaneManager.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ * 
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ * 
+ * Parameters:
+ * 
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxSwimlaneManager.prototype.setEnabled = function(value)
+{
+	this.enabled = value;
+};
+
+/**
+ * Function: isHorizontal
+ * 
+ * Returns <horizontal>.
+ */
+mxSwimlaneManager.prototype.isHorizontal = function()
+{
+	return this.horizontal;
+};
+
+/**
+ * Function: setHorizontal
+ * 
+ * Sets <horizontal>.
+ */
+mxSwimlaneManager.prototype.setHorizontal = function(value)
+{
+	this.horizontal = value;
+};
+
+/**
+ * Function: isAddEnabled
+ * 
+ * Returns <addEnabled>.
+ */
+mxSwimlaneManager.prototype.isAddEnabled = function()
+{
+	return this.addEnabled;
+};
+
+/**
+ * Function: setAddEnabled
+ * 
+ * Sets <addEnabled>.
+ */
+mxSwimlaneManager.prototype.setAddEnabled = function(value)
+{
+	this.addEnabled = value;
+};
+
+/**
+ * Function: isResizeEnabled
+ * 
+ * Returns <resizeEnabled>.
+ */
+mxSwimlaneManager.prototype.isResizeEnabled = function()
+{
+	return this.resizeEnabled;
+};
+
+/**
+ * Function: setResizeEnabled
+ * 
+ * Sets <resizeEnabled>.
+ */
+mxSwimlaneManager.prototype.setResizeEnabled = function(value)
+{
+	this.resizeEnabled = value;
+};
+
+/**
+ * Function: getGraph
+ * 
+ * Returns the graph that this manager operates on.
+ */
+mxSwimlaneManager.prototype.getGraph = function()
+{
+	return this.graph;
+};
+
+/**
+ * Function: setGraph
+ * 
+ * Sets the graph that the manager operates on.
+ */
+mxSwimlaneManager.prototype.setGraph = function(graph)
+{
+	if (this.graph != null)
+	{
+		this.graph.removeListener(this.addHandler);
+		this.graph.removeListener(this.resizeHandler);
+	}
+	
+	this.graph = graph;
+	
+	if (this.graph != null)
+	{
+		this.graph.addListener(mxEvent.ADD_CELLS, this.addHandler);
+		this.graph.addListener(mxEvent.CELLS_RESIZED, this.resizeHandler);
+	}
+};
+
+/**
+ * Function: isSwimlaneIgnored
+ * 
+ * Returns true if the given swimlane should be ignored.
+ */
+mxSwimlaneManager.prototype.isSwimlaneIgnored = function(swimlane)
+{
+	return !this.getGraph().isSwimlane(swimlane);
+};
+
+/**
+ * Function: isCellHorizontal
+ * 
+ * Returns true if the given cell is horizontal. If the given cell is not a
+ * swimlane, then the global orientation is returned.
+ */
+mxSwimlaneManager.prototype.isCellHorizontal = function(cell)
+{
+	if (this.graph.isSwimlane(cell))
+	{
+		var style = this.graph.getCellStyle(cell);
+		
+		return mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, 1) == 1;
+	}
+	
+	return !this.isHorizontal();
+};
+
+/**
+ * Function: cellsAdded
+ * 
+ * Called if any cells have been added.
+ * 
+ * Parameters:
+ * 
+ * cell - Array of <mxCells> that have been added.
+ */
+mxSwimlaneManager.prototype.cellsAdded = function(cells)
+{
+	if (cells != null)
+	{
+		var model = this.getGraph().getModel();
+
+		model.beginUpdate();
+		try
+		{
+			for (var i = 0; i < cells.length; i++)
+			{
+				if (!this.isSwimlaneIgnored(cells[i]))
+				{
+					this.swimlaneAdded(cells[i]);
+				}
+			}
+		}
+		finally
+		{
+			model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: swimlaneAdded
+ * 
+ * Updates the size of the given swimlane to match that of any existing
+ * siblings swimlanes.
+ * 
+ * Parameters:
+ * 
+ * swimlane - <mxCell> that represents the new swimlane.
+ */
+mxSwimlaneManager.prototype.swimlaneAdded = function(swimlane)
+{
+	var model = this.getGraph().getModel();
+	var parent = model.getParent(swimlane);
+	var childCount = model.getChildCount(parent);
+	var geo = null;
+	
+	// Finds the first valid sibling swimlane as reference
+	for (var i = 0; i < childCount; i++)
+	{
+		var child = model.getChildAt(parent, i);
+		
+		if (child != swimlane && !this.isSwimlaneIgnored(child))
+		{
+			geo = model.getGeometry(child);
+			
+			if (geo != null)
+			{	
+				break;
+			}
+		}
+	}
+	
+	// Applies the size of the refernece to the newly added swimlane
+	if (geo != null)
+	{
+		var parentHorizontal = (parent != null) ? this.isCellHorizontal(parent) : this.horizontal;
+		this.resizeSwimlane(swimlane, geo.width, geo.height, parentHorizontal);
+	}
+};
+
+/**
+ * Function: cellsResized
+ * 
+ * Called if any cells have been resizes. Calls <swimlaneResized> for all
+ * swimlanes where <isSwimlaneIgnored> returns false.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> whose size was changed.
+ */
+mxSwimlaneManager.prototype.cellsResized = function(cells)
+{
+	if (cells != null)
+	{
+		var model = this.getGraph().getModel();
+		
+		model.beginUpdate();
+		try
+		{
+			// Finds the top-level swimlanes and adds offsets
+			for (var i = 0; i < cells.length; i++)
+			{
+				if (!this.isSwimlaneIgnored(cells[i]))
+				{
+					var geo = model.getGeometry(cells[i]);
+
+					if (geo != null)
+					{
+						var size = new mxRectangle(0, 0, geo.width, geo.height);
+						var top = cells[i];
+						var current = top;
+						
+						while (current != null)
+						{
+							top = current;
+							current = model.getParent(current);
+							var tmp = (this.graph.isSwimlane(current)) ?
+									this.graph.getStartSize(current) :
+									new mxRectangle();
+							size.width += tmp.width;
+							size.height += tmp.height;
+						}
+						
+						var parentHorizontal = (current != null) ? this.isCellHorizontal(current) : this.horizontal;
+						this.resizeSwimlane(top, size.width, size.height, parentHorizontal);
+					}
+				}
+			}
+		}
+		finally
+		{
+			model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: resizeSwimlane
+ * 
+ * Called from <cellsResized> for all swimlanes that are not ignored to update
+ * the size of the siblings and the size of the parent swimlanes, recursively,
+ * if <bubbling> is true.
+ * 
+ * Parameters:
+ * 
+ * swimlane - <mxCell> whose size has changed.
+ */
+mxSwimlaneManager.prototype.resizeSwimlane = function(swimlane, w, h, parentHorizontal)
+{
+	var model = this.getGraph().getModel();
+	
+	model.beginUpdate();
+	try
+	{
+		var horizontal = this.isCellHorizontal(swimlane);
+		
+		if (!this.isSwimlaneIgnored(swimlane))
+		{
+			var geo = model.getGeometry(swimlane);
+			
+			if (geo != null)
+			{
+				if ((parentHorizontal && geo.height != h) || (!parentHorizontal && geo.width != w))
+				{
+					geo = geo.clone();
+					
+					if (parentHorizontal)
+					{
+						geo.height = h;
+					}
+					else
+					{
+						geo.width = w;
+					}
+
+					model.setGeometry(swimlane, geo);
+				}
+			}
+		}
+
+		var tmp = (this.graph.isSwimlane(swimlane)) ?
+				this.graph.getStartSize(swimlane) :
+				new mxRectangle();
+		w -= tmp.width;
+		h -= tmp.height;
+		
+		var childCount = model.getChildCount(swimlane);
+		
+		for (var i = 0; i < childCount; i++)
+		{
+			var child = model.getChildAt(swimlane, i);
+			this.resizeSwimlane(child, w, h, horizontal);
+		}
+	}
+	finally
+	{
+		model.endUpdate();
+	}
+};
+
+/**
+ * Function: destroy
+ * 
+ * Removes all handlers from the <graph> and deletes the reference to it.
+ */
+mxSwimlaneManager.prototype.destroy = function()
+{
+	this.setGraph(null);
+};
diff --git a/airavata-kubernetes/web-console/src/assets/js/view/mxTemporaryCellStates.js b/airavata-kubernetes/web-console/src/assets/js/view/mxTemporaryCellStates.js
new file mode 100644
index 0000000..34bbfdd
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/js/view/mxTemporaryCellStates.js
@@ -0,0 +1,108 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxTemporaryCellStates
+ *
+ * Extends <mxPoint> to implement a 2-dimensional rectangle with double
+ * precision coordinates.
+ * 
+ * Constructor: mxRectangle
+ *
+ * Constructs a new rectangle for the optional parameters. If no parameters
+ * are given then the respective default values are used.
+ */
+function mxTemporaryCellStates(view, scale, cells, isCellVisibleFn)
+{
+	scale = (scale != null) ? scale : 1;
+	this.view = view;
+	
+	// Stores the previous state
+	this.oldValidateCellState = view.validateCellState;
+	this.oldBounds = view.getGraphBounds();
+	this.oldStates = view.getStates();
+	this.oldScale = view.getScale();
+	
+	// Overrides validateCellState to ignore invisible cells
+	var self = this;
+	
+	view.validateCellState = function(cell, resurse)
+	{
+		if (cell == null || isCellVisibleFn == null || isCellVisibleFn(cell))
+		{
+			return self.oldValidateCellState.apply(view, arguments);
+		}
+		
+		return null;
+	};
+	
+	// Creates space for new states
+	view.setStates(new mxDictionary());
+	view.setScale(scale);
+	
+	if (cells != null)
+	{
+		view.resetValidationState();
+		var bbox = null;
+
+		// Validates the vertices and edges without adding them to
+		// the model so that the original cells are not modified
+		for (var i = 0; i < cells.length; i++)
+		{
+			var bounds = view.getBoundingBox(view.validateCellState(view.validateCell(cells[i])));
+			
+			if (bbox == null)
+			{
+				bbox = bounds;
+			}
+			else
+			{
+				bbox.add(bounds);
+			}
+		}
+
+		view.setGraphBounds(bbox || new mxRectangle());
+	}
+};
+
+/**
+ * Variable: view
+ *
+ * Holds the width of the rectangle. Default is 0.
+ */
+mxTemporaryCellStates.prototype.view = null;
+
+/**
+ * Variable: oldStates
+ *
+ * Holds the height of the rectangle. Default is 0.
+ */
+mxTemporaryCellStates.prototype.oldStates = null;
+
+/**
+ * Variable: oldBounds
+ *
+ * Holds the height of the rectangle. Default is 0.
+ */
+mxTemporaryCellStates.prototype.oldBounds = null;
+
+/**
+ * Variable: oldScale
+ *
+ * Holds the height of the rectangle. Default is 0.
+ */
+mxTemporaryCellStates.prototype.oldScale = null;
+
+/**
+ * Function: destroy
+ * 
+ * Returns the top, left corner as a new <mxPoint>.
+ */
+mxTemporaryCellStates.prototype.destroy = function()
+{
+	this.view.setScale(this.oldScale);
+	this.view.setStates(this.oldStates);
+	this.view.setGraphBounds(this.oldBounds);
+	this.view.validateCellState = this.oldValidateCellState;
+};
diff --git a/airavata-kubernetes/web-console/src/assets/resources/editor.txt b/airavata-kubernetes/web-console/src/assets/resources/editor.txt
new file mode 100644
index 0000000..53e8712
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/resources/editor.txt
@@ -0,0 +1,5 @@
+askZoom=Enter zoom (%)
+properties=Properties
+outline=Outline
+tasks=Tasks
+help=Help
diff --git a/airavata-kubernetes/web-console/src/assets/resources/editor_de.txt b/airavata-kubernetes/web-console/src/assets/resources/editor_de.txt
new file mode 100644
index 0000000..542f387
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/resources/editor_de.txt
@@ -0,0 +1,5 @@
+askZoom=Zoom eingeben (%)
+properties=Eigenschaften
+outline=Uebersicht
+tasks=Aufgaben
+help=Hilfe
diff --git a/airavata-kubernetes/web-console/src/assets/resources/editor_zh.txt b/airavata-kubernetes/web-console/src/assets/resources/editor_zh.txt
new file mode 100644
index 0000000..b37848b
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/resources/editor_zh.txt
@@ -0,0 +1,5 @@
+askZoom=进入缩放(%25)
+properties=属性
+outline=轮廓
+tasks=任务
+help=帮助
\ No newline at end of file
diff --git a/airavata-kubernetes/web-console/src/assets/resources/graph.txt b/airavata-kubernetes/web-console/src/assets/resources/graph.txt
new file mode 100644
index 0000000..baf61f8
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/resources/graph.txt
@@ -0,0 +1,11 @@
+alreadyConnected=Nodes already connected
+containsValidationErrors=Contains validation errors
+updatingDocument=Updating Document. Please wait...
+updatingSelection=Updating Selection. Please wait...
+collapse-expand=Collapse/Expand
+doubleClickOrientation=Doubleclick to change orientation
+close=Close
+error=Error
+done=Done
+cancel=Cancel
+ok=OK
diff --git a/airavata-kubernetes/web-console/src/assets/resources/graph_de.txt b/airavata-kubernetes/web-console/src/assets/resources/graph_de.txt
new file mode 100644
index 0000000..2999934
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/resources/graph_de.txt
@@ -0,0 +1,11 @@
+alreadyConnected=Knoten schon verbunden
+containsValidationErrors=Enthält Validierungsfehler
+updatingDocument=Aktualisiere Dokument. Bitte warten...
+updatingSelection=Aktualisiere Markierung. Bitte warten...
+collapse-expand=Einklappen/Ausklappen
+doubleClickOrientation=Doppelklicken um Orientierung zu ändern
+close=Schliessen
+error=Fehler
+done=Fertig
+cancel=Abbrechen
+ok=OK
diff --git a/airavata-kubernetes/web-console/src/assets/resources/graph_zh.txt b/airavata-kubernetes/web-console/src/assets/resources/graph_zh.txt
new file mode 100644
index 0000000..4958593
--- /dev/null
+++ b/airavata-kubernetes/web-console/src/assets/resources/graph_zh.txt
@@ -0,0 +1,11 @@
+alreadyConnected=节点已经连接
+containsValidationErrors=包含效验错误
+updatingDocument=更新文档。请等候......
+updatingSelection=更新所选项。请等候......
+collapse-expand=折叠/展开
+doubleClickOrientation=双击以改变方向
+close=关闭
+error=错误
+done=完成
+cancel=取消
+ok=确定
\ No newline at end of file
diff --git a/airavata-kubernetes/web-console/src/index.html b/airavata-kubernetes/web-console/src/index.html
index 4e85744..931edb8 100644
--- a/airavata-kubernetes/web-console/src/index.html
+++ b/airavata-kubernetes/web-console/src/index.html
@@ -9,6 +9,12 @@
   <link rel="icon" type="image/x-icon" href="favicon.ico">
   <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css" integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb" crossorigin="anonymous">
 
+  <script type="text/javascript">
+    mxBasePath = 'assets';
+  </script>
+  <script type="text/javascript" src="assets/js/mxClient.js"></script>
+  <script type="text/javascript" src="assets/js/components.js"></script>
+
 </head>
 <body>
   <app-root></app-root>
diff --git a/airavata-kubernetes/workflow-composer/index.html b/airavata-kubernetes/workflow-composer/index.html
index 7db81ca..4d7f261 100644
--- a/airavata-kubernetes/workflow-composer/index.html
+++ b/airavata-kubernetes/workflow-composer/index.html
@@ -302,7 +302,7 @@
 
                     try
                     {
-                        v1 = graph.insertVertex(parent, null, prototype, x, y, 80, 60);
+                        v1 = graph.insertVertex(parent, null, prototype.cloneNode(true), x, y, 80, 60);
                         v1.setConnectable(false);
 
                         var inputs = [];
diff --git a/airavata-kubernetes/workflow-composer/src/js/index.txt b/airavata-kubernetes/workflow-composer/src/js/index.txt
index f3631d6..8faa8cb 100644
--- a/airavata-kubernetes/workflow-composer/src/js/index.txt
+++ b/airavata-kubernetes/workflow-composer/src/js/index.txt
@@ -205,7 +205,7 @@ Subclassing:
   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
+  applied so that the type of an object can be retrieved via the name of it�s
   constructor using mxUtils.getFunctionName(obj.constructor).
 
 Constructor:
@@ -260,7 +260,7 @@ Functions:
   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.
+  superclass as follows, which is another way of �subclassing� in JavaScript.
 
   (code)
   mxGraph.prototype.isCellSelectable = function(cell)
@@ -275,7 +275,7 @@ Functions:
   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
+  In referenceId 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:
 

-- 
To stop receiving notification emails like this one, please contact
"commits@airavata.apache.org" <co...@airavata.apache.org>.

[airavata-sandbox] 17/19: Fixing minor bugs

Posted by sm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

smarru pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-sandbox.git

commit 7ee4d13d11e19f4b376a8b55b5f63a68d79bbf34
Author: dimuthu.upeksha2@gmail.com <Di...@1234>
AuthorDate: Mon Nov 27 01:15:04 2017 +0530

    Fixing minor bugs
---
 airavata-kubernetes/scripts/k8s/ci-cd/Jenkinsfile | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/airavata-kubernetes/scripts/k8s/ci-cd/Jenkinsfile b/airavata-kubernetes/scripts/k8s/ci-cd/Jenkinsfile
index 98624ce..87d2f5b 100644
--- a/airavata-kubernetes/scripts/k8s/ci-cd/Jenkinsfile
+++ b/airavata-kubernetes/scripts/k8s/ci-cd/Jenkinsfile
@@ -67,13 +67,13 @@ node {
         sh "sed 's#dimuthuupe/task-scheduler:v1.0#'$BUILDIMG_TASK_SCHEDULER'#' airavata-kubernetes/scripts/k8s/task-scheduler/task-scheduler-dep.yml | kubectl apply -f -"
         sh "kubectl rollout status deployment/task-scheduler"
 
-        sh "sed 's#dimuthuupe/command-task:v1.0#'$BUILDIMG_COMMAND_TASK'#' airavata-kubernetes/scripts/k8s/command-task/command-task-dep.yml | kubectl apply -f -"
+        sh "sed 's#dimuthuupe/command-task:v1.0#'$BUILDIMG_COMMAND_TASK'#' airavata-kubernetes/scripts/k8s/tasks/command-task/command-task-dep.yml | kubectl apply -f -"
         sh "kubectl rollout status deployment/command-task"
 
-        sh "sed 's#dimuthuupe/data-in-task:v1.0#'$BUILDIMG_DATA_IN_TASK'#' airavata-kubernetes/scripts/k8s/data-in-task/data-in-task-dep.yml | kubectl apply -f -"
+        sh "sed 's#dimuthuupe/data-in-task:v1.0#'$BUILDIMG_DATA_IN_TASK'#' airavata-kubernetes/scripts/k8s/tasks/data-in-task/data-in-task-dep.yml | kubectl apply -f -"
         sh "kubectl rollout status deployment/data-in-task"
 
-        sh "sed 's#dimuthuupe/data-out-task:v1.0#'$BUILDIMG_DATA_OUT_TASK'#' airavata-kubernetes/scripts/k8s/data-out-task/data-out-task-dep.yml | kubectl apply -f -"
+        sh "sed 's#dimuthuupe/data-out-task:v1.0#'$BUILDIMG_DATA_OUT_TASK'#' airavata-kubernetes/scripts/k8s/tasks/data-out-task/data-out-task-dep.yml | kubectl apply -f -"
         sh "kubectl rollout status deployment/data-out-task"
 
 }
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
"commits@airavata.apache.org" <co...@airavata.apache.org>.

[airavata-sandbox] 12/19: Removing unused imports

Posted by sm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

smarru pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-sandbox.git

commit 28eba208aa2daa7501ecc4c01ea74e29498f04bc
Author: dimuthu.upeksha2@gmail.com <Di...@1234>
AuthorDate: Sun Nov 26 07:21:18 2017 +0530

    Removing unused imports
---
 .../java/org/apache/airavata/k8s/api/server/model/task/TaskDAG.java     | 2 --
 1 file changed, 2 deletions(-)

diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/TaskDAG.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/TaskDAG.java
index 88ffef3..e603a4d 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/TaskDAG.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/TaskDAG.java
@@ -1,7 +1,5 @@
 package org.apache.airavata.k8s.api.server.model.task;
 
-import com.sun.javafx.tk.Toolkit;
-
 import javax.persistence.*;
 
 /**

-- 
To stop receiving notification emails like this one, please contact
"commits@airavata.apache.org" <co...@airavata.apache.org>.

[airavata-sandbox] 06/19: UI improvements

Posted by sm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

smarru pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-sandbox.git

commit a1c411553ae8076f3b9cf27467a146ac55b75e68
Author: dimuthu.upeksha2@gmail.com <Di...@1234>
AuthorDate: Mon Nov 20 13:06:16 2017 +0530

    UI improvements
---
 .../airavata/helix/tasks/datain/DataInputTask.java |   2 +-
 .../helix/tasks/dataout/DataOutputTask.java        |   2 +-
 .../src/app/components/workflow/create/create.html |  50 ++++++++++++---------
 .../src/app/components/workflow/detail/detail.html |   2 +-
 .../components/workflow/detail/workflow.detail.ts  |  43 ++++++++++++------
 .../web-console/src/assets/icons/datain.png        | Bin 0 -> 4095 bytes
 .../web-console/src/assets/icons/dataout.png       | Bin 0 -> 4110 bytes
 7 files changed, 61 insertions(+), 38 deletions(-)

diff --git a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/datain/DataInputTask.java b/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/datain/DataInputTask.java
index 41d6aa4..9d4a27e 100644
--- a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/datain/DataInputTask.java
+++ b/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/datain/DataInputTask.java
@@ -86,7 +86,7 @@ public class DataInputTask extends AbstractTask {
         TaskTypeResource taskTypeResource = new TaskTypeResource();
         taskTypeResource.setName(NAME);
         taskTypeResource.setTopicName("airavata-data-collect");
-        taskTypeResource.setIcon("assets/icons/copy.png");
+        taskTypeResource.setIcon("assets/icons/datain.png");
         taskTypeResource.getInputTypes().addAll(
                 Arrays.asList(
                         new TaskInputTypeResource()
diff --git a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/dataout/DataOutputTask.java b/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/dataout/DataOutputTask.java
index 62a6712..fb696b2 100644
--- a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/dataout/DataOutputTask.java
+++ b/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/dataout/DataOutputTask.java
@@ -98,7 +98,7 @@ public class DataOutputTask extends AbstractTask {
         TaskTypeResource taskTypeResource = new TaskTypeResource();
         taskTypeResource.setName(NAME);
         taskTypeResource.setTopicName("airavata-data-collect");
-        taskTypeResource.setIcon("assets/icons/copy.png");
+        taskTypeResource.setIcon("assets/icons/dataout.png");
         taskTypeResource.getInputTypes().addAll(
                 Arrays.asList(
                         new TaskInputTypeResource()
diff --git a/airavata-kubernetes/web-console/src/app/components/workflow/create/create.html b/airavata-kubernetes/web-console/src/app/components/workflow/create/create.html
index 22723d1..1a3ba79 100644
--- a/airavata-kubernetes/web-console/src/app/components/workflow/create/create.html
+++ b/airavata-kubernetes/web-console/src/app/components/workflow/create/create.html
@@ -1,27 +1,33 @@
-<label for="worklow-name">Workflow Name</label>
-<input [(ngModel)]="workFlowName" name="worklow-name" id="worklow-name"/>
+<div class="container">
 
-<table style="position:relative;">
-  <tr>
-    <td>
-      <div id="toolContainer" style="border: solid 1px black; width: 80px; cursor: default">
+  <div class="row" style="margin-bottom: 20px; margin-top: 20px">
 
-      </div>
-    </td>
-    <td>
+    <div class="col-8">
+      <label for="worklow-name">Workflow Name</label>
+      <input [(ngModel)]="workFlowName" name="worklow-name" id="worklow-name"/>
+    </div>
+
+    <div class="col-4">
+      <button (click)="onShowWorkflowXMLClicked()" class="btn btn-info">Show XML</button>
+      <button (click)="onCreateWorkflowClicked()" class="btn btn-success">Create Workflow</button>
+    </div>
+
+  </div>
+
+  <div class="row">
+
+    <div class="col-1" id="toolContainer" style="border: solid 1px black; cursor: default"></div>
+
+    <div class="col-7">
       <div id="graphContainer"
            style="border: solid 1px black;overflow:hidden;width:321px; cursor:default;">
       </div>
-    </td>
-    <td valign="top">
-      <div id="properties"
-           style="border: solid 1px black; padding: 10px;">
-      </div>
-    </td>
-  </tr>
-  <tr>
-    <td></td>
-    <td><button (click)="onShowWorkflowXMLClicked()">Show XML</button></td>
-    <td><button (click)="onCreateWorkflowClicked()">Create Workflow</button></td>
-  </tr>
-</table>
+    </div>
+
+    <div class="col-4">
+      <div id="properties" style="border: solid 1px black; padding: 10px;"></div>
+    </div>
+
+  </div>
+
+</div>
diff --git a/airavata-kubernetes/web-console/src/app/components/workflow/detail/detail.html b/airavata-kubernetes/web-console/src/app/components/workflow/detail/detail.html
index 011b36a..a4add70 100644
--- a/airavata-kubernetes/web-console/src/app/components/workflow/detail/detail.html
+++ b/airavata-kubernetes/web-console/src/app/components/workflow/detail/detail.html
@@ -23,7 +23,7 @@
 <ng-template #processContent let-c="close" let-d="dismiss">
 
   <div class="modal-header">
-    <h4 class="modal-title">Experiment Processes</h4>
+    <h4 class="modal-title">Workflow Processes</h4>
     <button type="button" class="close" aria-label="Close" (click)="d('Cross click')">
       <span aria-hidden="true">&times;</span>
     </button>
diff --git a/airavata-kubernetes/web-console/src/app/components/workflow/detail/workflow.detail.ts b/airavata-kubernetes/web-console/src/app/components/workflow/detail/workflow.detail.ts
index 99482f7..6f184b6 100644
--- a/airavata-kubernetes/web-console/src/app/components/workflow/detail/workflow.detail.ts
+++ b/airavata-kubernetes/web-console/src/app/components/workflow/detail/workflow.detail.ts
@@ -22,14 +22,14 @@ export class WorkflowDetailComponent {
   processes: Array<Process> = [];
   processLastState: ProcessStatus = new ProcessStatus();
   processListModel: NgbModalRef;
+  workflowId: number;
 
   constructor(private modalService: NgbModal,private activatedRoute: ActivatedRoute,
               private workflowService: WorkflowService, private processService: ProcessService,
               private router: Router) {
 
-    let workflowId = this.activatedRoute.snapshot.params["id"];
-    this.workflowService.getWorkflowById(workflowId)
-      .subscribe(data => {this.selectedWorkflow = data}, err => {console.log(err)});
+    this.workflowId = this.activatedRoute.snapshot.params["id"];
+    this.fetchWorkflow(this.workflowId);
   }
 
   launchWorkflow() {
@@ -54,15 +54,32 @@ export class WorkflowDetailComponent {
 
   openProcessesAsModel(content) {
     this.processes = [];
-    this.selectedWorkflow.processIds.forEach(id => {
-      this.processService.getProcessById(id).subscribe(data => {
-        this.processes.push(data);
-        this.processes.sort((p1, p2) => {return p1.id - p2.id;})
-      }, err => {
-        console.log(err);
-      });
-    });
-    this.processListModel = this.modalService.open(content, {size: "lg"});
-    this.processListModel.result.then((result) => {}, (reason) => {});
+
+    this.workflowService.getWorkflowById(this.workflowId)
+      .subscribe(data => {
+
+        this.selectedWorkflow = data;
+        this.selectedWorkflow.processIds.forEach(id => {
+
+          this.processService.getProcessById(id).subscribe(data => {
+            this.processes.push(data);
+            this.processes.sort((p1, p2) => {return p1.id - p2.id;})
+
+          }, err => {
+            console.log(err);
+          });
+
+        });
+
+        this.processListModel = this.modalService.open(content, {size: "lg"});
+        this.processListModel.result.then((result) => {}, (reason) => {});
+
+      }, err => {console.log(err)});
+
+  }
+
+  fetchWorkflow(id: number) {
+    this.workflowService.getWorkflowById(id)
+      .subscribe(data => {this.selectedWorkflow = data}, err => {console.log(err)});
   }
 }
diff --git a/airavata-kubernetes/web-console/src/assets/icons/datain.png b/airavata-kubernetes/web-console/src/assets/icons/datain.png
new file mode 100755
index 0000000..102009a
Binary files /dev/null and b/airavata-kubernetes/web-console/src/assets/icons/datain.png differ
diff --git a/airavata-kubernetes/web-console/src/assets/icons/dataout.png b/airavata-kubernetes/web-console/src/assets/icons/dataout.png
new file mode 100755
index 0000000..6aae639
Binary files /dev/null and b/airavata-kubernetes/web-console/src/assets/icons/dataout.png differ

-- 
To stop receiving notification emails like this one, please contact
"commits@airavata.apache.org" <co...@airavata.apache.org>.

[airavata-sandbox] 14/19: Fixing minor bugs

Posted by sm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

smarru pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-sandbox.git

commit 6a046a90b66fbff9b437859782ef1b05372c4eaa
Author: dimuthu.upeksha2@gmail.com <Di...@1234>
AuthorDate: Sun Nov 26 22:12:38 2017 +0530

    Fixing minor bugs
---
 airavata-kubernetes/scripts/k8s/ci-cd/Jenkinsfile | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/airavata-kubernetes/scripts/k8s/ci-cd/Jenkinsfile b/airavata-kubernetes/scripts/k8s/ci-cd/Jenkinsfile
index a8ea07f..521de80 100644
--- a/airavata-kubernetes/scripts/k8s/ci-cd/Jenkinsfile
+++ b/airavata-kubernetes/scripts/k8s/ci-cd/Jenkinsfile
@@ -55,25 +55,25 @@ node {
 
     stage "Deploy"
 
-        sh "sed 's#dimuthuupe/api-server:v1.0#'$BUILDIMG_API_SERVER'#' airavata-kubernetes/scripts/k8s/api-server/api-server-dep.yaml | kubectl apply -f -"
+        sh "sed 's#dimuthuupe/api-server:v1.0#'$BUILDIMG_API_SERVER'#' airavata-kubernetes/scripts/k8s/api-server/api-server-dep.yml | kubectl apply -f -"
         sh "kubectl rollout status deployment/api-server"
 
-        sh "sed 's#dimuthuupe/event-sink:v1.0#'BUILDIMG_EVENT_SINK'#' airavata-kubernetes/scripts/k8s/event-sink/event-sink-dep.yaml | kubectl apply -f -"
+        sh "sed 's#dimuthuupe/event-sink:v1.0#'BUILDIMG_EVENT_SINK'#' airavata-kubernetes/scripts/k8s/event-sink/event-sink-dep.yml | kubectl apply -f -"
         sh "kubectl rollout status deployment/event-sink"
 
-        sh "sed 's#dimuthuupe/helix-controller:v1.0#'BUILDIMG_HELIX_CONTROLLER'#' airavata-kubernetes/scripts/k8s/helix-controller/helix-controller-dep.yaml | kubectl apply -f -"
+        sh "sed 's#dimuthuupe/helix-controller:v1.0#'BUILDIMG_HELIX_CONTROLLER'#' airavata-kubernetes/scripts/k8s/helix-controller/helix-controller-dep.yml | kubectl apply -f -"
         sh "kubectl rollout status deployment/helix-controller"
 
-        sh "sed 's#dimuthuupe/task-scheduler:v1.0#'BUILDIMG_TASK_SCHEDULER'#' airavata-kubernetes/scripts/k8s/task-scheduler/task-scheduler-dep.yaml | kubectl apply -f -"
+        sh "sed 's#dimuthuupe/task-scheduler:v1.0#'BUILDIMG_TASK_SCHEDULER'#' airavata-kubernetes/scripts/k8s/task-scheduler/task-scheduler-dep.yml | kubectl apply -f -"
         sh "kubectl rollout status deployment/task-scheduler"
 
-        sh "sed 's#dimuthuupe/command-task:v1.0#'BUILDIMG_COMMAND_TASK'#' airavata-kubernetes/scripts/k8s/command-task/command-task-dep.yaml | kubectl apply -f -"
+        sh "sed 's#dimuthuupe/command-task:v1.0#'BUILDIMG_COMMAND_TASK'#' airavata-kubernetes/scripts/k8s/command-task/command-task-dep.yml | kubectl apply -f -"
         sh "kubectl rollout status deployment/command-task"
 
-        sh "sed 's#dimuthuupe/data-in-task:v1.0#'BUILDIMG_DATA_IN_TASK'#' airavata-kubernetes/scripts/k8s/data-in-task/data-in-task-dep.yaml | kubectl apply -f -"
+        sh "sed 's#dimuthuupe/data-in-task:v1.0#'BUILDIMG_DATA_IN_TASK'#' airavata-kubernetes/scripts/k8s/data-in-task/data-in-task-dep.yml | kubectl apply -f -"
         sh "kubectl rollout status deployment/data-in-task"
 
-        sh "sed 's#dimuthuupe/data-out-task:v1.0#'BUILDIMG_DATA_OUT_TASK'#' airavata-kubernetes/scripts/k8s/data-out-task/data-out-task-dep.yaml | kubectl apply -f -"
+        sh "sed 's#dimuthuupe/data-out-task:v1.0#'BUILDIMG_DATA_OUT_TASK'#' airavata-kubernetes/scripts/k8s/data-out-task/data-out-task-dep.yml | kubectl apply -f -"
         sh "kubectl rollout status deployment/data-out-task"
 
 }
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
"commits@airavata.apache.org" <co...@airavata.apache.org>.

[airavata-sandbox] 19/19: Adding async monitor component

Posted by sm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

smarru pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-sandbox.git

commit c38e09e9664f5d00ef784e33c153f1128790260a
Author: dimuthu.upeksha2@gmail.com <Di...@1234>
AuthorDate: Sun Dec 3 13:38:38 2017 +0530

    Adding async monitor component
---
 .../airavata/agents/core/AsyncCommandStatus.java   |  19 ++++
 .../airavata/agents/core/StatusPublisher.java      |   4 +-
 .../agents/thrift/handler/OperationHandler.java    |   8 +-
 .../thrift/operation/ThriftAgentOperation.java     |  23 +++--
 .../event/listener/service/ListenerService.java    |   2 +-
 .../tasks/async-command-monitor/pom.xml            | 113 +++++++++++++++++++++
 .../async/command/monitor/AsyncCommandMonitor.java | 111 ++++++++++++++++++++
 .../task/async/command/monitor/Participant.java    |  54 ++++++++++
 .../src/main/resources/application.properties      |   7 ++
 .../src/main/resources/log4j.properties            |   9 ++
 .../helix/task/async/command/AsyncCommandTask.java |   1 +
 .../airavata/helix/task/command/CommandTask.java   |   2 +-
 airavata-kubernetes/pom.xml                        |   1 +
 .../components/workflow/create/create.component.ts |   5 +-
 14 files changed, 339 insertions(+), 20 deletions(-)

diff --git a/airavata-kubernetes/modules/agents/agent-core/src/main/java/org/apache/airavata/agents/core/AsyncCommandStatus.java b/airavata-kubernetes/modules/agents/agent-core/src/main/java/org/apache/airavata/agents/core/AsyncCommandStatus.java
new file mode 100644
index 0000000..1176f3d
--- /dev/null
+++ b/airavata-kubernetes/modules/agents/agent-core/src/main/java/org/apache/airavata/agents/core/AsyncCommandStatus.java
@@ -0,0 +1,19 @@
+package org.apache.airavata.agents.core;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public enum AsyncCommandStatus {
+
+    PENDING,
+    RUNNING,
+    SUCCESS,
+    PAUSE,
+    WARNING,
+    ERROR,
+    CANCEL,
+    MANUAL_RECOVERY;
+}
diff --git a/airavata-kubernetes/modules/agents/agent-core/src/main/java/org/apache/airavata/agents/core/StatusPublisher.java b/airavata-kubernetes/modules/agents/agent-core/src/main/java/org/apache/airavata/agents/core/StatusPublisher.java
index 71af84e..c689d02 100644
--- a/airavata-kubernetes/modules/agents/agent-core/src/main/java/org/apache/airavata/agents/core/StatusPublisher.java
+++ b/airavata-kubernetes/modules/agents/agent-core/src/main/java/org/apache/airavata/agents/core/StatusPublisher.java
@@ -24,9 +24,9 @@ public class StatusPublisher {
         this.initializeKafkaEventProducer();
     }
 
-    public void publishStatus(long callbackWorkflowId, String status, String message) {
+    public void publishStatus(long callbackWorkflowId, AsyncCommandStatus status, String message) {
         this.eventProducer.send(new ProducerRecord<String, String>(
-                this.topicName, String.join(",", callbackWorkflowId + "", status, message)));
+                this.topicName, String.join(",", callbackWorkflowId + "", status.name(), message)));
     }
 
     public void initializeKafkaEventProducer() {
diff --git a/airavata-kubernetes/modules/agents/thrift-agent/src/main/java/org/apache/airavata/agents/thrift/handler/OperationHandler.java b/airavata-kubernetes/modules/agents/thrift-agent/src/main/java/org/apache/airavata/agents/thrift/handler/OperationHandler.java
index c402a83..3dd4b09 100644
--- a/airavata-kubernetes/modules/agents/thrift-agent/src/main/java/org/apache/airavata/agents/thrift/handler/OperationHandler.java
+++ b/airavata-kubernetes/modules/agents/thrift-agent/src/main/java/org/apache/airavata/agents/thrift/handler/OperationHandler.java
@@ -5,6 +5,8 @@ import org.apache.airavata.agents.thrift.stubs.OperationException;
 import org.apache.airavata.agents.thrift.stubs.OperationService;
 import org.apache.thrift.TException;
 
+import static org.apache.airavata.agents.core.AsyncCommandStatus.*;
+
 /**
  * TODO: Class level comments please
  *
@@ -19,13 +21,13 @@ public class OperationHandler extends StatusPublisher implements OperationServic
 
     @Override
     public void executeCommand(String command, long callbackWorkflowId) throws OperationException, TException {
-        publishStatus(callbackWorkflowId, "PENDING", "Pending for execution");
-        publishStatus(callbackWorkflowId, "STARTED", "Starting command execution");
+        publishStatus(callbackWorkflowId, PENDING, "Pending for execution");
+        publishStatus(callbackWorkflowId, RUNNING, "Starting command execution");
         Runnable task = new Runnable() {
             @Override
             public void run() {
                 System.out.println("Executing command " + command);
-                publishStatus(callbackWorkflowId, "SUCCESS", "Command execution succeeded");
+                publishStatus(callbackWorkflowId, SUCCESS, "Command execution succeeded");
             }
         };
 
diff --git a/airavata-kubernetes/modules/agents/thrift-agent/src/main/java/org/apache/airavata/agents/thrift/operation/ThriftAgentOperation.java b/airavata-kubernetes/modules/agents/thrift-agent/src/main/java/org/apache/airavata/agents/thrift/operation/ThriftAgentOperation.java
index cb9010b..e46fea3 100644
--- a/airavata-kubernetes/modules/agents/thrift-agent/src/main/java/org/apache/airavata/agents/thrift/operation/ThriftAgentOperation.java
+++ b/airavata-kubernetes/modules/agents/thrift-agent/src/main/java/org/apache/airavata/agents/thrift/operation/ThriftAgentOperation.java
@@ -22,22 +22,23 @@ public class ThriftAgentOperation extends AsyncOperation {
 
     public ThriftAgentOperation(ComputeResource computeResource) {
         super(computeResource);
-
-        try {
-            TTransport transport = new TSocket(computeResource.getHost(), 9090);
-            transport.open();
-            TProtocol protocol = new TBinaryProtocol(transport);
-            this.client = new OperationService.Client(protocol);
-
-        } catch (TTransportException e) {
-            e.printStackTrace();
-        }
     }
 
     @Override
     public void executeCommandAsync(String command, long callbackWorkflowId) {
         try {
-            client.executeCommand(command, callbackWorkflowId);
+            System.out.println("Submitting command");
+            try {
+                TTransport transport = new TSocket(getComputeResource().getHost(), 9090);
+                transport.open();
+                TProtocol protocol = new TBinaryProtocol(transport);
+                this.client = new OperationService.Client(protocol);
+                client.executeCommand(command, callbackWorkflowId);
+                System.out.println("Finished submitting");
+                transport.close();
+            } catch (TTransportException e) {
+                e.printStackTrace();
+            }
         } catch (TException e) {
             e.printStackTrace();
         }
diff --git a/airavata-kubernetes/modules/microservices/async-event-listener/src/main/java/org/apache/airavata/async/event/listener/service/ListenerService.java b/airavata-kubernetes/modules/microservices/async-event-listener/src/main/java/org/apache/airavata/async/event/listener/service/ListenerService.java
index 72f5309..896b6f9 100644
--- a/airavata-kubernetes/modules/microservices/async-event-listener/src/main/java/org/apache/airavata/async/event/listener/service/ListenerService.java
+++ b/airavata-kubernetes/modules/microservices/async-event-listener/src/main/java/org/apache/airavata/async/event/listener/service/ListenerService.java
@@ -30,6 +30,6 @@ public class ListenerService {
         Map<String, String> boostrapData = new HashMap<>();
         boostrapData.put("event", event);
         boostrapData.put("message", message);
-        this.restTemplate.postForObject(apiServerUrl + "/workflow/" + workflowId + "/launch", boostrapData, Long.class);
+        this.restTemplate.postForObject("http://" + apiServerUrl + "/workflow/" + workflowId + "/launch", boostrapData, Long.class);
     }
 }
diff --git a/airavata-kubernetes/modules/microservices/tasks/async-command-monitor/pom.xml b/airavata-kubernetes/modules/microservices/tasks/async-command-monitor/pom.xml
new file mode 100644
index 0000000..7373733
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/tasks/async-command-monitor/pom.xml
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>airavata-kubernetes</artifactId>
+        <groupId>org.apache.airavata</groupId>
+        <version>1.0-SNAPSHOT</version>
+        <relativePath>../../../../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>async-command-monitor</artifactId>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.5.1</version>
+                <configuration>
+                    <source>${java.version}</source>
+                    <target>${java.version}</target>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <configuration>
+                    <archive>
+                        <manifest>
+                            <mainClass>org.apache.airavata.helix.task.async.command.Participant</mainClass>
+                        </manifest>
+                    </archive>
+                    <descriptorRefs>
+                        <descriptorRef>jar-with-dependencies</descriptorRef>
+                    </descriptorRefs>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>make-assembly</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+
+            <!-- Create a docker image that runs the executable jar-->
+            <plugin>
+                <groupId>com.spotify</groupId>
+                <artifactId>docker-maven-plugin</artifactId>
+                <version>1.0.0</version>
+                <configuration>
+                    <imageName>${docker.image.prefix}/async-command-monitor</imageName>
+                    <baseImage>java:openjdk-8-jdk-alpine</baseImage>
+                    <exposes>
+                        <expose>8080</expose>
+                    </exposes>
+                    <entryPoint>["java","-jar","/${project.build.finalName}-jar-with-dependencies.jar"]</entryPoint>
+                    <resources>
+                        <resource>
+                            <targetPath>/</targetPath>
+                            <directory>${project.build.directory}</directory>
+                            <include>${project.build.finalName}-jar-with-dependencies.jar</include>
+                        </resource>
+                    </resources>
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>build</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.airavata</groupId>
+            <artifactId>helix-task-api</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.helix</groupId>
+            <artifactId>helix-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-log4j12</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.airavata</groupId>
+            <artifactId>api-resource</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.airavata</groupId>
+            <artifactId>agent-core</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+    </dependencies>
+
+</project>
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/microservices/tasks/async-command-monitor/src/main/java/org/apache/airavata/task/async/command/monitor/AsyncCommandMonitor.java b/airavata-kubernetes/modules/microservices/tasks/async-command-monitor/src/main/java/org/apache/airavata/task/async/command/monitor/AsyncCommandMonitor.java
new file mode 100644
index 0000000..095b40f
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/tasks/async-command-monitor/src/main/java/org/apache/airavata/task/async/command/monitor/AsyncCommandMonitor.java
@@ -0,0 +1,111 @@
+package org.apache.airavata.task.async.command.monitor;
+
+import org.apache.airavata.agents.core.AsyncCommandStatus;
+import org.apache.airavata.helix.api.AbstractTask;
+import org.apache.airavata.helix.api.PropertyResolver;
+import org.apache.airavata.k8s.api.resources.task.TaskStatusResource;
+import org.apache.airavata.k8s.api.resources.task.type.TaskInputTypeResource;
+import org.apache.airavata.k8s.api.resources.task.type.TaskOutPortTypeResource;
+import org.apache.airavata.k8s.api.resources.task.type.TaskTypeResource;
+import org.apache.helix.task.TaskCallbackContext;
+import org.apache.helix.task.TaskResult;
+
+import java.util.Arrays;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public class AsyncCommandMonitor extends AbstractTask {
+
+    public static final String NAME = "ASYNC_COMMAND_MONITOR";
+
+    private AsyncCommandStatus status;
+    private String message;
+
+    public AsyncCommandMonitor(TaskCallbackContext callbackContext, PropertyResolver propertyResolver) {
+        super(callbackContext, propertyResolver);
+    }
+
+    @Override
+    public void init() {
+        super.init();
+        this.status = AsyncCommandStatus.valueOf(getCallbackContext().getTaskConfig().getConfigMap().get("event"));
+        this.message = getCallbackContext().getTaskConfig().getConfigMap().get("message");
+    }
+
+    @Override
+    public TaskResult onRun() {
+        System.out.println("Received status " + this.status.name() + " with message " + this.message);
+        if (this.status != null) {
+            sendToOutPort(this.status.name());
+            publishTaskStatus(TaskStatusResource.State.COMPLETED, "Sending to out port " + this.status.name());
+            return new TaskResult(TaskResult.Status.COMPLETED, "Sending to out port " + this.status.name());
+
+        } else {
+            publishTaskStatus(TaskStatusResource.State.FAILED, "Unsupported status");
+            return new TaskResult(TaskResult.Status.FATAL_FAILED, "Unsupported status");
+        }
+    }
+
+    @Override
+    public void onCancel() {
+
+    }
+
+    public static TaskTypeResource getTaskType() {
+        TaskTypeResource taskTypeResource = new TaskTypeResource();
+        taskTypeResource.setName(NAME);
+        taskTypeResource.setIcon("assets/icons/ssh.png");
+        taskTypeResource.getInputTypes().addAll(
+                Arrays.asList(
+                        new TaskInputTypeResource()
+                                .setName(PARAMS.STATUS_KEY)
+                                .setType("String")
+                                .setDefaultValue("status"),
+                        new TaskInputTypeResource()
+                                .setName(PARAMS.MESSAGE_KEY)
+                                .setType("String")
+                                .setDefaultValue("message")
+                ));
+
+        taskTypeResource.getOutPorts().addAll(
+                Arrays.asList(
+                        new TaskOutPortTypeResource()
+                                .setName(AsyncCommandStatus.PENDING.name())
+                                .setOrder(0),
+                        new TaskOutPortTypeResource()
+                                .setName(AsyncCommandStatus.RUNNING.name())
+                                .setOrder(1),
+                        new TaskOutPortTypeResource()
+                                .setName(AsyncCommandStatus.SUCCESS.name())
+                                .setOrder(2),
+                        new TaskOutPortTypeResource()
+                                .setName(AsyncCommandStatus.CANCEL.name())
+                                .setOrder(3),
+                        new TaskOutPortTypeResource()
+                                .setName(AsyncCommandStatus.ERROR.name())
+                                .setOrder(4),
+                        new TaskOutPortTypeResource()
+                                .setName(AsyncCommandStatus.PAUSE.name())
+                                .setOrder(5),
+                        new TaskOutPortTypeResource()
+                                .setName(AsyncCommandStatus.WARNING.name())
+                                .setOrder(6),
+                        new TaskOutPortTypeResource()
+                                .setName(AsyncCommandStatus.MANUAL_RECOVERY.name())
+                                .setOrder(7)
+                )
+
+        );
+
+        return taskTypeResource;
+    }
+
+    public static final class PARAMS {
+        public static final String STATUS_KEY = "status-key";
+        public static final String MESSAGE_KEY = "message-key";
+    }
+}
diff --git a/airavata-kubernetes/modules/microservices/tasks/async-command-monitor/src/main/java/org/apache/airavata/task/async/command/monitor/Participant.java b/airavata-kubernetes/modules/microservices/tasks/async-command-monitor/src/main/java/org/apache/airavata/task/async/command/monitor/Participant.java
new file mode 100644
index 0000000..ed4f0bd
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/tasks/async-command-monitor/src/main/java/org/apache/airavata/task/async/command/monitor/Participant.java
@@ -0,0 +1,54 @@
+package org.apache.airavata.task.async.command.monitor;
+
+import org.apache.airavata.helix.api.HelixParticipant;
+import org.apache.airavata.k8s.api.resources.task.type.TaskTypeResource;
+import org.apache.helix.task.Task;
+import org.apache.helix.task.TaskCallbackContext;
+import org.apache.helix.task.TaskFactory;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public class Participant extends HelixParticipant {
+
+    public Participant(String propertyFile) throws IOException {
+        super(propertyFile);
+    }
+
+    @Override
+    public Map<String, TaskFactory> getTaskFactory() {
+        Map<String, TaskFactory> taskRegistry = new HashMap<String, TaskFactory>();
+
+        TaskFactory commandTaskFac = new TaskFactory() {
+            @Override
+            public Task createNewTask(TaskCallbackContext context) {
+                return new AsyncCommandMonitor(context, getPropertyResolver());
+            }
+        };
+
+        taskRegistry.put(AsyncCommandMonitor.NAME, commandTaskFac);
+
+        return taskRegistry;
+    }
+
+    @Override
+    public TaskTypeResource getTaskType() {
+        return AsyncCommandMonitor.getTaskType();
+    }
+
+    public static void main(String args[]) {
+        try {
+            HelixParticipant participant = new Participant("application.properties");
+            new Thread(participant).start();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/airavata-kubernetes/modules/microservices/tasks/async-command-monitor/src/main/resources/application.properties b/airavata-kubernetes/modules/microservices/tasks/async-command-monitor/src/main/resources/application.properties
new file mode 100644
index 0000000..f7a613d
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/tasks/async-command-monitor/src/main/resources/application.properties
@@ -0,0 +1,7 @@
+api.server.url=api-server.default.svc.cluster.local:8080
+zookeeper.connection.url=localhost:2199
+helix.cluster.name=AiravataDemoCluster
+participant.name=async-command-monitor-p1
+task.type.name=ASYNC_COMMAND_MONITOR
+kafka.bootstrap.url=localhost:9092
+event.topic=airavata-task-event
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/microservices/tasks/async-command-monitor/src/main/resources/log4j.properties b/airavata-kubernetes/modules/microservices/tasks/async-command-monitor/src/main/resources/log4j.properties
new file mode 100644
index 0000000..5e31e3c
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/tasks/async-command-monitor/src/main/resources/log4j.properties
@@ -0,0 +1,9 @@
+# Set root logger level to DEBUG and its only appender to A1.
+log4j.rootLogger=INFO, A1
+
+# A1 is set to be a ConsoleAppender.
+log4j.appender.A1=org.apache.log4j.ConsoleAppender
+
+# A1 uses PatternLayout.
+log4j.appender.A1.layout=org.apache.log4j.PatternLayout
+log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/microservices/tasks/async-command-task/src/main/java/org/apache/airavata/helix/task/async/command/AsyncCommandTask.java b/airavata-kubernetes/modules/microservices/tasks/async-command-task/src/main/java/org/apache/airavata/helix/task/async/command/AsyncCommandTask.java
index 5ece6f2..5108347 100644
--- a/airavata-kubernetes/modules/microservices/tasks/async-command-task/src/main/java/org/apache/airavata/helix/task/async/command/AsyncCommandTask.java
+++ b/airavata-kubernetes/modules/microservices/tasks/async-command-task/src/main/java/org/apache/airavata/helix/task/async/command/AsyncCommandTask.java
@@ -56,6 +56,7 @@ public class AsyncCommandTask extends AbstractTask {
             AsyncOperation operation = (AsyncOperation) Class.forName("org.apache.airavata.agents.thrift.operation.ThriftAgentOperation")
                     .getConstructor(ComputeResource.class).newInstance(this.computeResource);
             operation.executeCommandAsync(this.command, this.callBackWorkflowId);
+            publishTaskStatus(TaskStatusResource.State.COMPLETED, "Task completed");
             return new TaskResult(TaskResult.Status.COMPLETED, "Task completed");
         } catch (InstantiationException | IllegalAccessException |
                 InvocationTargetException | ClassNotFoundException | NoSuchMethodException | ClassCastException e) {
diff --git a/airavata-kubernetes/modules/microservices/tasks/command-task/src/main/java/org/apache/airavata/helix/task/command/CommandTask.java b/airavata-kubernetes/modules/microservices/tasks/command-task/src/main/java/org/apache/airavata/helix/task/command/CommandTask.java
index d5b8bda..f6b1b6a 100644
--- a/airavata-kubernetes/modules/microservices/tasks/command-task/src/main/java/org/apache/airavata/helix/task/command/CommandTask.java
+++ b/airavata-kubernetes/modules/microservices/tasks/command-task/src/main/java/org/apache/airavata/helix/task/command/CommandTask.java
@@ -56,7 +56,7 @@ public class CommandTask extends AbstractTask {
             String finalCommand = command + (arguments != null ? arguments : "") + stdOutSuffix;
 
             System.out.println("Executing command " + finalCommand);
-            Thread.sleep(200000);
+            Thread.sleep(2000);
             ExecutionResult executionResult = fetchComputeResourceOperation(computeResource).executeCommand(finalCommand);
 
             if (executionResult.getExitStatus() == 0) {
diff --git a/airavata-kubernetes/pom.xml b/airavata-kubernetes/pom.xml
index 9ca584f..5d8b118 100644
--- a/airavata-kubernetes/pom.xml
+++ b/airavata-kubernetes/pom.xml
@@ -45,6 +45,7 @@
         <module>modules/agents/thrift-agent</module>
         <module>modules/microservices/tasks/async-command-task</module>
         <module>modules/microservices/async-event-listener</module>
+        <module>modules/microservices/tasks/async-command-monitor</module>
     </modules>
 
     <dependencyManagement>
diff --git a/airavata-kubernetes/web-console/src/app/components/workflow/create/create.component.ts b/airavata-kubernetes/web-console/src/app/components/workflow/create/create.component.ts
index e286c19..a76ff90 100644
--- a/airavata-kubernetes/web-console/src/app/components/workflow/create/create.component.ts
+++ b/airavata-kubernetes/web-console/src/app/components/workflow/create/create.component.ts
@@ -376,8 +376,6 @@ export class WorkflowCreateComponent implements AfterViewInit {
         model.beginUpdate();
 
         try {
-          v1 = graph.insertVertex(parent, null, prototype.cloneNode(true), x, y, 80, 60);
-          v1.setConnectable(false);
 
           let inputs = [];
           let inputKeys = [];
@@ -395,6 +393,9 @@ export class WorkflowCreateComponent implements AfterViewInit {
             }
           }
 
+          v1 = graph.insertVertex(parent, null, prototype.cloneNode(true), x, y, 80, outputs.length*20 + 20);
+          v1.setConnectable(false);
+
           let inputDivision = 1 / (inputs.length + 1);
           let outputDivision = 1 / (outputs.length + 1);
 

-- 
To stop receiving notification emails like this one, please contact
"commits@airavata.apache.org" <co...@airavata.apache.org>.

[airavata-sandbox] 03/19: Updated tasks to accept TaskContexts as inputs

Posted by sm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

smarru pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-sandbox.git

commit 7ee67aa756c3961bafc9573cd2c6a6b2f60e9bae
Author: dimuthu.upeksha2@gmail.com <Di...@1234>
AuthorDate: Wed Nov 8 17:06:59 2017 +0530

    Updated tasks to accept TaskContexts as inputs
---
 .../resources/process/ProcessStatusResource.java   |  59 ++++++++---
 .../k8s/api/resources/task/TaskDagResource.java    |  40 ++++++++
 .../api/resources/task/TaskOutPortResource.java    |  10 ++
 .../k8s/api/resources/task/TaskResource.java       |  22 +++-
 .../api/server/controller/ProcessController.java   |   2 +
 .../k8s/api/server/controller/TaskController.java  |   7 ++
 .../k8s/api/server/model/task/TaskModel.java       |  11 ++
 .../server/repository/task/TaskDAGRepository.java  |   4 +
 .../k8s/api/server/service/WorkflowService.java    |  12 ++-
 .../k8s/api/server/service/task/TaskService.java   |  17 +++-
 .../k8s/api/server/service/util/GraphParser.java   |   4 +-
 .../api/server/service/util/ToResourceUtil.java    |  42 ++++++--
 .../src/main/resources/application.properties      |   3 +-
 .../modules/microservices/task-scheduler/pom.xml   |   5 +
 .../k8s/gfac/core/ProcessLifeCycleManager.java     | 113 +++++++++++++++++----
 .../airavata/k8s/gfac/messaging/KafkaReceiver.java |  11 +-
 .../airavata/k8s/gfac/messaging/KafkaSender.java   |   8 +-
 .../k8s/gfac/messaging/ReceiverConfig.java         |  12 ++-
 .../airavata/k8s/gfac/messaging/SenderConfig.java  |  10 +-
 .../airavata/k8s/gfac/service/WorkerService.java   |  45 ++++----
 .../src/main/resources/application.properties      |   2 +-
 .../k8s/task/job/service/TaskExecutionService.java |  20 ++--
 .../src/main/resources/application.properties      |   4 +-
 .../task/egress/service/TaskExecutionService.java  |  13 ++-
 .../task/ingress/service/TaskExecutionService.java |  13 +--
 .../k8s/task/api/AbstractTaskExecutionService.java |  30 ++++--
 .../apache/airavata/k8s/task/api/TaskContext.java  |  74 +++++++++++++-
 .../k8s/task/api/TaskContextDeserializer.java      |  19 ++++
 .../k8s/task/api/TaskContextSerializer.java        |  22 +++-
 .../k8s/task/api/messaging/KafkaSender.java        |  11 +-
 .../k8s/task/api/messaging/ReceiverConfig.java     |  12 ++-
 .../k8s/task/api/messaging/SenderConfig.java       |  10 +-
 32 files changed, 518 insertions(+), 149 deletions(-)

diff --git a/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/process/ProcessStatusResource.java b/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/process/ProcessStatusResource.java
index cfaa212..40fc9b1 100644
--- a/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/process/ProcessStatusResource.java
+++ b/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/process/ProcessStatusResource.java
@@ -19,6 +19,9 @@
  */
 package org.apache.airavata.k8s.api.resources.process;
 
+import java.util.HashMap;
+import java.util.Map;
+
 /**
  * TODO: Class level comments please
  *
@@ -88,20 +91,46 @@ public class ProcessStatusResource {
         return this;
     }
 
-    public static final class State {
-        public static final int CREATED = 0;
-        public static final int VALIDATED = 1;
-        public static final int STARTED = 2;
-        public static final int PRE_PROCESSING = 3;
-        public static final int CONFIGURING_WORKSPACE = 4;
-        public static final int INPUT_DATA_STAGING = 5;
-        public static final int EXECUTING = 6;
-        public static final int MONITORING = 7;
-        public static final int OUTPUT_DATA_STAGING = 8;
-        public static final int POST_PROCESSING = 9;
-        public static final int COMPLETED = 10;
-        public static final int FAILED = 11;
-        public static final int CANCELLING = 12;
-        public static final int CANCELED = 13;
+    public static enum State {
+
+        CREATED(0),
+        VALIDATED(1),
+        STARTED(2),
+        PRE_PROCESSING(3),
+        CONFIGURING_WORKSPACE(4),
+        INPUT_DATA_STAGING(5),
+        EXECUTING(6),
+        MONITORING(7),
+        OUTPUT_DATA_STAGING(8),
+        POST_PROCESSING(9),
+        COMPLETED(10),
+        FAILED(11),
+        CANCELLING(12),
+        CANCELED(13);
+
+        private final int value;
+
+        private State(int value) {
+            this.value = value;
+        }
+
+        private static Map<Integer, State> map = new HashMap<>();
+
+        static {
+            for (State state : State.values()) {
+                map.put(state.value, state);
+            }
+        }
+
+        public static State valueOf(int taskState) {
+            return map.get(taskState);
+        }
+
+        /**
+         * Get the integer value of this enum value, as defined in the Thrift IDL.
+         */
+        public int getValue() {
+            return value;
+        }
     }
 }
diff --git a/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/task/TaskDagResource.java b/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/task/TaskDagResource.java
new file mode 100644
index 0000000..bfef611
--- /dev/null
+++ b/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/task/TaskDagResource.java
@@ -0,0 +1,40 @@
+package org.apache.airavata.k8s.api.resources.task;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public class TaskDagResource {
+    private long id;
+    private TaskOutPortResource sourceOutPort;
+    private TaskResource targetTask;
+
+    public long getId() {
+        return id;
+    }
+
+    public TaskDagResource setId(long id) {
+        this.id = id;
+        return this;
+    }
+
+    public TaskOutPortResource getSourceOutPort() {
+        return sourceOutPort;
+    }
+
+    public TaskDagResource setSourceOutPort(TaskOutPortResource sourceOutPort) {
+        this.sourceOutPort = sourceOutPort;
+        return this;
+    }
+
+    public TaskResource getTargetTask() {
+        return targetTask;
+    }
+
+    public TaskDagResource setTargetTask(TaskResource targetTask) {
+        this.targetTask = targetTask;
+        return this;
+    }
+}
diff --git a/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/task/TaskOutPortResource.java b/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/task/TaskOutPortResource.java
index de4ef1b..2ea8b3f 100644
--- a/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/task/TaskOutPortResource.java
+++ b/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/task/TaskOutPortResource.java
@@ -10,6 +10,7 @@ public class TaskOutPortResource {
     private long id;
     private String name;
     private int referenceId = 0;
+    private TaskResource taskResource;
 
     public long getId() {
         return id;
@@ -37,4 +38,13 @@ public class TaskOutPortResource {
         this.referenceId = referenceId;
         return this;
     }
+
+    public TaskResource getTaskResource() {
+        return taskResource;
+    }
+
+    public TaskOutPortResource setTaskResource(TaskResource taskResource) {
+        this.taskResource = taskResource;
+        return this;
+    }
 }
diff --git a/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/task/TaskResource.java b/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/task/TaskResource.java
index bdc34d7..bb54ceb 100644
--- a/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/task/TaskResource.java
+++ b/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/task/TaskResource.java
@@ -19,6 +19,8 @@
  */
 package org.apache.airavata.k8s.api.resources.task;
 
+import org.apache.airavata.k8s.api.resources.task.type.TaskTypeResource;
+
 import java.util.ArrayList;
 import java.util.List;
 
@@ -31,8 +33,9 @@ import java.util.List;
 public class TaskResource {
 
     private long id;
+    private int referenceId; // for workflows
     private String name;
-    private long taskTypeId;
+    private TaskTypeResource taskType;
     private String taskTypeStr;
     private long parentProcessId;
     private long creationTime;
@@ -57,12 +60,12 @@ public class TaskResource {
         return this;
     }
 
-    public long getTaskTypeId() {
-        return taskTypeId;
+    public TaskTypeResource getTaskType() {
+        return taskType;
     }
 
-    public TaskResource setTaskTypeId(long taskTypeId) {
-        this.taskTypeId = taskTypeId;
+    public TaskResource setTaskType(TaskTypeResource taskType) {
+        this.taskType = taskType;
         return this;
     }
 
@@ -199,6 +202,15 @@ public class TaskResource {
         return this;
     }
 
+    public int getReferenceId() {
+        return referenceId;
+    }
+
+    public TaskResource setReferenceId(int referenceId) {
+        this.referenceId = referenceId;
+        return this;
+    }
+
     public static final class TaskTypes {
         public static final int ENV_SETUP = 0;
         public static final int INGRESS_DATA_STAGING = 1;
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/controller/ProcessController.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/controller/ProcessController.java
index 7dffe68..93a530c 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/controller/ProcessController.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/controller/ProcessController.java
@@ -20,6 +20,7 @@
 package org.apache.airavata.k8s.api.server.controller;
 
 import org.apache.airavata.k8s.api.resources.process.ProcessStatusResource;
+import org.apache.airavata.k8s.api.resources.task.TaskDagResource;
 import org.apache.airavata.k8s.api.server.ServerRuntimeException;
 import org.apache.airavata.k8s.api.resources.process.ProcessResource;
 import org.apache.airavata.k8s.api.server.service.ProcessService;
@@ -27,6 +28,7 @@ import org.springframework.http.MediaType;
 import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
+import java.util.Set;
 
 /**
  * TODO: Class level comments please
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/controller/TaskController.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/controller/TaskController.java
index 2fb2334..c72ba47 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/controller/TaskController.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/controller/TaskController.java
@@ -19,6 +19,7 @@
  */
 package org.apache.airavata.k8s.api.server.controller;
 
+import org.apache.airavata.k8s.api.resources.task.TaskDagResource;
 import org.apache.airavata.k8s.api.resources.task.TaskResource;
 import org.apache.airavata.k8s.api.resources.task.TaskStatusResource;
 import org.apache.airavata.k8s.api.server.ServerRuntimeException;
@@ -27,6 +28,7 @@ import org.springframework.http.MediaType;
 import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
+import java.util.Set;
 
 /**
  * TODO: Class level comments please
@@ -62,4 +64,9 @@ public class TaskController {
         return this.taskService.findTaskStatusById(id)
                 .orElseThrow(() -> new ServerRuntimeException("Task status with id " + id + " not found"));
     }
+
+    @GetMapping(path = "dag/{process_id}", produces = MediaType.APPLICATION_JSON_VALUE)
+    public Set<TaskDagResource> getDagForProcess(@PathVariable("process_id") long processId) {
+        return this.taskService.getDagForProcess(processId);
+    }
 }
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/TaskModel.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/TaskModel.java
index 247bae1..4006e57 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/TaskModel.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/task/TaskModel.java
@@ -42,6 +42,8 @@ public class TaskModel {
     @GeneratedValue(strategy = GenerationType.AUTO)
     private long id;
 
+    private int referenceId; // to track workflows
+
     private String name;
 
     @ManyToOne
@@ -211,4 +213,13 @@ public class TaskModel {
     public void setTaskOutPorts(List<TaskOutPort> taskOutPorts) {
         this.taskOutPorts = taskOutPorts;
     }
+
+    public int getReferenceId() {
+        return referenceId;
+    }
+
+    public TaskModel setReferenceId(int referenceId) {
+        this.referenceId = referenceId;
+        return this;
+    }
 }
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/task/TaskDAGRepository.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/task/TaskDAGRepository.java
index b2cb7fa..dc64fc0 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/task/TaskDAGRepository.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/task/TaskDAGRepository.java
@@ -3,6 +3,9 @@ package org.apache.airavata.k8s.api.server.repository.task;
 import org.apache.airavata.k8s.api.server.model.task.TaskDAG;
 import org.springframework.data.repository.CrudRepository;
 
+import java.util.Iterator;
+import java.util.Optional;
+
 /**
  * TODO: Class level comments please
  *
@@ -10,4 +13,5 @@ import org.springframework.data.repository.CrudRepository;
  * @since 1.0.0-SNAPSHOT
  */
 public interface TaskDAGRepository extends CrudRepository<TaskDAG, Long> {
+    public Iterable<TaskDAG> findBysourceOutPort_taskModel_parentProcess_id(long processId);
 }
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/WorkflowService.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/WorkflowService.java
index 6090c90..68abab4 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/WorkflowService.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/WorkflowService.java
@@ -3,7 +3,6 @@ package org.apache.airavata.k8s.api.server.service;
 import org.apache.airavata.k8s.api.resources.process.ProcessResource;
 import org.apache.airavata.k8s.api.resources.workflow.WorkflowResource;
 import org.apache.airavata.k8s.api.server.ServerRuntimeException;
-import org.apache.airavata.k8s.api.server.model.process.ProcessModel;
 import org.apache.airavata.k8s.api.server.model.task.TaskDAG;
 import org.apache.airavata.k8s.api.server.model.task.TaskModel;
 import org.apache.airavata.k8s.api.server.model.task.TaskOutPort;
@@ -12,9 +11,11 @@ import org.apache.airavata.k8s.api.server.repository.task.TaskDAGRepository;
 import org.apache.airavata.k8s.api.server.repository.task.TaskOutPortRepository;
 import org.apache.airavata.k8s.api.server.repository.task.TaskRepository;
 import org.apache.airavata.k8s.api.server.repository.workflow.WorkflowRepository;
+import org.apache.airavata.k8s.api.server.service.messaging.MessagingService;
 import org.apache.airavata.k8s.api.server.service.task.TaskService;
 import org.apache.airavata.k8s.api.server.service.util.GraphParser;
 import org.apache.airavata.k8s.api.server.service.util.ToResourceUtil;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 
 import java.util.ArrayList;
@@ -33,14 +34,19 @@ public class WorkflowService {
 
     private ProcessService processService;
     private TaskService taskService;
+    private MessagingService messagingService;
 
     private WorkflowRepository workflowRepository;
     private TaskOutPortRepository taskOutPortRepository;
     private TaskRepository taskRepository;
     private TaskDAGRepository taskDAGRepository;
 
+    @Value("${scheduler.topic.name}")
+    private String schedulerTopic;
+
     public WorkflowService(ProcessService processService,
                            TaskService taskService,
+                           MessagingService messagingService,
                            WorkflowRepository workflowRepository,
                            TaskOutPortRepository taskOutPortRepository,
                            TaskRepository taskRepository,
@@ -48,6 +54,7 @@ public class WorkflowService {
 
         this.processService = processService;
         this.taskService = taskService;
+        this.messagingService = messagingService;
         this.workflowRepository = workflowRepository;
         this.taskOutPortRepository = taskOutPortRepository;
         this.taskRepository = taskRepository;
@@ -112,6 +119,8 @@ public class WorkflowService {
         } catch (Exception e) {
             throw new ServerRuntimeException("Failed to create workflow", e);
         }
+
+        this.messagingService.send(schedulerTopic, processId + "");
         return 0;
     }
 
@@ -123,6 +132,7 @@ public class WorkflowService {
         return workflowResources;
     }
 
+    @SuppressWarnings("WeakerAccess")
     public Optional<Workflow> findEntityById(long id) {
         return this.workflowRepository.findById(id);
     }
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/task/TaskService.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/task/TaskService.java
index d1b3828..9df55ab 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/task/TaskService.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/task/TaskService.java
@@ -19,6 +19,7 @@
  */
 package org.apache.airavata.k8s.api.server.service.task;
 
+import org.apache.airavata.k8s.api.resources.task.TaskDagResource;
 import org.apache.airavata.k8s.api.resources.task.TaskResource;
 import org.apache.airavata.k8s.api.resources.task.TaskStatusResource;
 import org.apache.airavata.k8s.api.server.ServerRuntimeException;
@@ -29,8 +30,7 @@ import org.apache.airavata.k8s.api.server.repository.task.type.TaskTypeRepositor
 import org.apache.airavata.k8s.api.server.service.util.ToResourceUtil;
 import org.springframework.stereotype.Service;
 
-import java.util.Optional;
-import java.util.UUID;
+import java.util.*;
 
 /**
  * TODO: Class level comments please
@@ -79,12 +79,13 @@ public class TaskService {
         taskModel.setStartingTask(resource.isStartingTask());
         taskModel.setStoppingTask(resource.isStoppingTask());
         taskModel.setTaskDetail(resource.getTaskDetail());
+        taskModel.setReferenceId(resource.getReferenceId());
         taskModel.setParentProcess(processRepository.findById(resource.getParentProcessId())
                 .orElseThrow(() -> new ServerRuntimeException("Can not find process with id " +
                         resource.getParentProcessId())));
-        taskModel.setTaskType(taskTypeRepository.findById(resource.getTaskTypeId())
+        taskModel.setTaskType(taskTypeRepository.findById(resource.getTaskType().getId())
                 .orElseThrow(() -> new ServerRuntimeException("Can not find task type with id " +
-                resource.getTaskTypeId())));
+                resource.getTaskType().getId())));
 
         TaskModel savedTask = taskRepository.save(taskModel);
 
@@ -141,4 +142,12 @@ public class TaskService {
         return ToResourceUtil.toResource(taskRepository.findById(id).get());
     }
 
+    public Set<TaskDagResource> getDagForProcess(long processId) {
+        Set<TaskDagResource> taskDagResources = new HashSet<>();
+        Iterable<TaskDAG> taskDags = this.taskDAGRepository.findBysourceOutPort_taskModel_parentProcess_id(processId);
+        Optional.ofNullable(taskDags).ifPresent(dags -> dags.forEach(taskDAG -> {
+           taskDagResources.add(ToResourceUtil.toResource(taskDAG).get());
+        }));
+        return taskDagResources;
+    }
 }
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/util/GraphParser.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/util/GraphParser.java
index 9cf1630..9f919f7 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/util/GraphParser.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/util/GraphParser.java
@@ -4,6 +4,7 @@ import org.apache.airavata.k8s.api.resources.task.TaskInputResource;
 import org.apache.airavata.k8s.api.resources.task.TaskOutPortResource;
 import org.apache.airavata.k8s.api.resources.task.TaskOutputResource;
 import org.apache.airavata.k8s.api.resources.task.TaskResource;
+import org.apache.airavata.k8s.api.resources.task.type.TaskTypeResource;
 import org.w3c.dom.*;
 import org.xml.sax.InputSource;
 
@@ -55,12 +56,13 @@ public class GraphParser {
                             taskResource.setName(attr.getNodeValue());
 
                         } else if ("Type".equals(attr.getNodeName())) {
-                            taskResource.setTaskTypeId(Long.parseLong(attr.getNodeValue()));
+                            taskResource.setTaskType(new TaskTypeResource().setId(Long.parseLong(attr.getNodeValue())));
 
                         } else if (attr.getNodeName().startsWith("in-") || attr.getNodeName().startsWith("out-")) {
 
                         } else if ("id".equals(attr.getNodeName())) {
                             id = Integer.parseInt(attr.getNodeValue());
+                            taskResource.setReferenceId(id);
 
                         } else if (attr.getNodeName().startsWith("output-")) {
                             TaskOutputResource outputResource = new TaskOutputResource();
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/util/ToResourceUtil.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/util/ToResourceUtil.java
index 7631b01..acd94c9 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/util/ToResourceUtil.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/util/ToResourceUtil.java
@@ -37,16 +37,13 @@ import org.apache.airavata.k8s.api.server.model.experiment.ExperimentOutputData;
 import org.apache.airavata.k8s.api.server.model.experiment.ExperimentStatus;
 import org.apache.airavata.k8s.api.server.model.process.ProcessModel;
 import org.apache.airavata.k8s.api.server.model.process.ProcessStatus;
-import org.apache.airavata.k8s.api.server.model.task.TaskInput;
-import org.apache.airavata.k8s.api.server.model.task.TaskModel;
+import org.apache.airavata.k8s.api.server.model.task.*;
 import org.apache.airavata.k8s.api.resources.application.*;
 import org.apache.airavata.k8s.api.resources.compute.ComputeResource;
 import org.apache.airavata.k8s.api.resources.experiment.ExperimentInputResource;
 import org.apache.airavata.k8s.api.resources.experiment.ExperimentOutputResource;
 import org.apache.airavata.k8s.api.resources.experiment.ExperimentResource;
 import org.apache.airavata.k8s.api.resources.process.ProcessResource;
-import org.apache.airavata.k8s.api.server.model.task.TaskOutput;
-import org.apache.airavata.k8s.api.server.model.task.TaskStatus;
 import org.apache.airavata.k8s.api.server.model.task.type.TaskInputType;
 import org.apache.airavata.k8s.api.server.model.task.type.TaskModelType;
 import org.apache.airavata.k8s.api.server.model.task.type.TaskOutPortType;
@@ -231,10 +228,11 @@ public class ToResourceUtil {
             resource.setLastUpdateTime(taskModel.getLastUpdateTime());
             resource.setCreationTime(taskModel.getCreationTime());
             resource.setParentProcessId(taskModel.getParentProcess().getId());
-            resource.setTaskTypeId(taskModel.getTaskType().getId());
+            resource.setTaskType(toResource(taskModel.getTaskType()).get());
             resource.setTaskDetail(taskModel.getTaskDetail());
             resource.setStartingTask(taskModel.isStartingTask());
             resource.setStoppingTask(taskModel.isStoppingTask());
+            resource.setReferenceId(taskModel.getReferenceId());
             Optional.ofNullable(taskModel.getTaskInputs())
                     .ifPresent(inputs ->
                             inputs.forEach(input -> resource.getInputs()
@@ -306,7 +304,14 @@ public class ToResourceUtil {
             ProcessResource processResource = new ProcessResource();
             processResource.setId(processModel.getId());
             processResource.setLastUpdateTime(processModel.getLastUpdateTime());
-            processResource.setExperimentId(processModel.getExperiment().getId());
+
+            Optional.ofNullable(processModel.getExperiment()).ifPresent(experiment -> {
+                processResource.setExperimentId(experiment.getId());
+            });
+            Optional.ofNullable(processModel.getWorkflow()).ifPresent(workflow -> {
+                processResource.setWorkflowId(workflow.getId());
+            });
+
             processResource.setTaskDag(processModel.getTaskDag());
             processResource.setCreationTime(processModel.getCreationTime());
             Optional.ofNullable(processModel.getProcessStatuses())
@@ -418,4 +423,29 @@ public class ToResourceUtil {
             return Optional.empty();
         }
     }
+
+    public static Optional<TaskDagResource> toResource(TaskDAG taskDAG) {
+        if (taskDAG != null) {
+            TaskDagResource resource = new TaskDagResource();
+            resource.setId(taskDAG.getId());
+            resource.setSourceOutPort(toResource(taskDAG.getSourceOutPort()).get());
+            resource.setTargetTask(toResource(taskDAG.getTargetTask()).get());
+            return Optional.of(resource);
+        } else {
+            return Optional.empty();
+        }
+    }
+
+    public static Optional<TaskOutPortResource> toResource(TaskOutPort outPort) {
+        if (outPort != null) {
+            TaskOutPortResource resource = new TaskOutPortResource();
+            resource.setId(outPort.getId());
+            resource.setReferenceId(outPort.getReferenceId());
+            resource.setName(outPort.getName());
+            resource.setTaskResource(toResource(outPort.getTaskModel()).get());
+            return Optional.of(resource);
+        } else {
+            return Optional.empty();
+        }
+    }
 }
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/resources/application.properties b/airavata-kubernetes/modules/microservices/api-server/src/main/resources/application.properties
index 6f76911..bdcb9bc 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/resources/application.properties
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/resources/application.properties
@@ -2,4 +2,5 @@ spring.jpa.hibernate.ddl-auto=update
 spring.datasource.url=jdbc:mysql://db.default.svc.cluster.local:3306/airavata
 spring.datasource.username=root
 spring.datasource.password=fun123
-launch.topic.name = airavata-launch
\ No newline at end of file
+launch.topic.name = airavata-launch
+scheduler.topic.name = airavata-scheduler
diff --git a/airavata-kubernetes/modules/microservices/task-scheduler/pom.xml b/airavata-kubernetes/modules/microservices/task-scheduler/pom.xml
index d2dca7c..6372cb1 100644
--- a/airavata-kubernetes/modules/microservices/task-scheduler/pom.xml
+++ b/airavata-kubernetes/modules/microservices/task-scheduler/pom.xml
@@ -39,6 +39,11 @@
             <artifactId>api-resource</artifactId>
             <version>1.0-SNAPSHOT</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.airavata</groupId>
+            <artifactId>task-api</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
 
         <dependency>
             <groupId>org.springframework.boot</groupId>
diff --git a/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/core/ProcessLifeCycleManager.java b/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/core/ProcessLifeCycleManager.java
index 751b2ec..8508f92 100644
--- a/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/core/ProcessLifeCycleManager.java
+++ b/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/core/ProcessLifeCycleManager.java
@@ -1,4 +1,4 @@
-/**
+/*
  *
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
@@ -20,9 +20,11 @@
 package org.apache.airavata.k8s.gfac.core;
 
 import org.apache.airavata.k8s.api.resources.process.ProcessStatusResource;
+import org.apache.airavata.k8s.api.resources.task.TaskOutPortResource;
 import org.apache.airavata.k8s.api.resources.task.TaskResource;
 import org.apache.airavata.k8s.api.resources.task.TaskStatusResource;
 import org.apache.airavata.k8s.gfac.messaging.KafkaSender;
+import org.apache.airavata.k8s.task.api.TaskContext;
 import org.springframework.web.client.RestTemplate;
 
 import java.util.*;
@@ -36,45 +38,108 @@ import java.util.*;
 public class ProcessLifeCycleManager {
 
     private long processId;
-    private List<TaskResource> taskDag;
-    private Map<Long, Integer> taskPoint;
+    private List<TaskResource> tasks;
+    private TaskResource currentTask;
+    private Map<Long, Long> edgeMap;
+
     private KafkaSender kafkaSender;
 
     // Todo abstract out these parameters to reusable class
     private final RestTemplate restTemplate;
     private String apiServerUrl;
 
-    public ProcessLifeCycleManager(long processId, List<TaskResource> tasks,
+    public ProcessLifeCycleManager(long processId, List<TaskResource> tasks, Map<Long, Long> edgeMap,
                                    KafkaSender kafkaSender,
                                    RestTemplate restTemplate, String apiServerUrl) {
         this.processId = processId;
-        this.taskDag = tasks;
+        this.tasks = tasks;
+        this.edgeMap = edgeMap;
         this.kafkaSender = kafkaSender;
         this.restTemplate = restTemplate;
         this.apiServerUrl = apiServerUrl;
     }
 
     public void init() {
-        taskDag.sort(Comparator.comparing(TaskResource::getOrder));
-        taskPoint = new HashMap<>();
-        for (int i = 0; i < taskDag.size(); i++) {
-            taskPoint.put(taskDag.get(i).getId(), i);
+
+        Optional<TaskResource> startingTask = tasks.stream().filter(TaskResource::isStartingTask).findFirst();
+        if (startingTask.isPresent()) {
+            this.currentTask = startingTask.get();
+        } else {
+            System.out.println("No starting task for this process " + processId);
+            updateProcessStatus(ProcessStatusResource.State.CANCELED, "No starting task for this process");
         }
+
+    }
+
+    public void start() {
         updateProcessStatus(ProcessStatusResource.State.EXECUTING);
+        System.out.println("Starting process " + processId + " with task " + currentTask.getName());
+
+        TaskContext startContext = new TaskContext();
+        startContext.assignTask(currentTask);
+
+        submitTaskToQueue(currentTask.getTaskType().getTopicName(), startContext);
     }
 
-    public synchronized void onTaskStateChanged(long taskId, int state) {
-        switch (state) {
+    public synchronized void onTaskStateChanged(TaskContext taskContext) {
+
+        updateProcessStatus(ProcessStatusResource.State.MONITORING, "Task moved to state "
+                + ProcessStatusResource.State.valueOf(taskContext.getStatus()).name());
+
+        if (taskContext.getTaskId() != currentTask.getId()) {
+            System.out.println("Incompatible task status received. " +
+                    "Currently running task id " + currentTask.getId() + " received task id " + taskContext.getTaskId());
+            updateProcessStatus(ProcessStatusResource.State.FAILED, "Incompatible task status received. " +
+                    "Currently running task id " + currentTask.getId() + " received task id " + taskContext.getTaskId());
+            return;
+        } else {
+            System.out.println("Compatible task status received");
+        }
+
+        switch (taskContext.getStatus()) {
             case TaskStatusResource.State.COMPLETED:
-                System.out.println("Task " + taskId + " was completed");
-                Optional.ofNullable(this.taskPoint.get(taskId)).ifPresent(point -> {
-                    if (point + 1 < taskDag.size()) {
-                        TaskResource resource = taskDag.get(point + 1);
-                        submitTaskToQueue(resource);
+
+                if (currentTask.isStoppingTask()) {
+                    System.out.println("Process completed with last task " + currentTask.getName());
+                    updateProcessStatus(ProcessStatusResource.State.COMPLETED, "Process completed with last task " + currentTask.getName());
+
+                } else {
+                    Optional<TaskOutPortResource> nextOutPort = currentTask.getOutPorts().stream()
+                            .filter(port -> port.getId() == taskContext.getOutPortId()).findFirst();
+                    if (nextOutPort.isPresent()) {
+
+                        if (edgeMap.containsKey(nextOutPort.get().getId())) {
+                            Long nextTaskId = edgeMap.get(nextOutPort.get().getId());
+                            Optional<TaskResource> nextTask = tasks.stream().filter(task -> task.getId() == nextTaskId).findFirst();
+
+                            if (nextTask.isPresent()) {
+
+                                this.currentTask = nextTask.get();
+                                taskContext.assignTask(this.currentTask);
+                                System.out.println("Submitting next task " + this.currentTask.getName() + " of process " + processId);
+                                submitTaskToQueue(this.currentTask.getTaskType().getTopicName(), taskContext);
+
+                            } else {
+                                System.out.println("Next task with id " + nextTaskId + " can not be found");
+                                updateProcessStatus(ProcessStatusResource.State.FAILED, "Next task with id "
+                                        + nextTaskId + " can not be found");
+                                return;
+                            }
+
+                        } else {
+                            System.out.println("Incomplete graph. Next outport " + nextOutPort.get().getName()
+                                    + " of task " + currentTask.getName() + " ends with a no endpoint");
+                            updateProcessStatus(ProcessStatusResource.State.FAILED, "Incomplete graph. Next outport "
+                                    + nextOutPort.get().getName() + " of task " + currentTask.getName()
+                                    + " ends with a no endpoint");
+                            return;
+                        }
                     } else {
-                        updateProcessStatus(ProcessStatusResource.State.COMPLETED);
+                        System.out.println("Invalid out port " + taskContext.getOutPortId() + " for task " + taskContext.getTaskId());
+                        updateProcessStatus(ProcessStatusResource.State.FAILED,
+                                "Invalid out port " + taskContext.getOutPortId() + " for task " + taskContext.getTaskId());
                     }
-                });
+                }
                 break;
             case TaskStatusResource.State.FAILED:
                 updateProcessStatus(ProcessStatusResource.State.FAILED);
@@ -82,14 +147,20 @@ public class ProcessLifeCycleManager {
         }
     }
 
-    public void submitTaskToQueue(TaskResource taskResource) {
+    private void submitTaskToQueue(String topicName, TaskContext taskContext) {
+        updateProcessStatus(ProcessStatusResource.State.MONITORING, "Submitting task " + taskContext.getTaskId() + " to queue");
+        kafkaSender.send(topicName, taskContext);
+    }
 
+    private void updateProcessStatus(ProcessStatusResource.State state) {
+        updateProcessStatus(state, "");
     }
 
-    private void updateProcessStatus(int state) {
+    private void updateProcessStatus(ProcessStatusResource.State state, String reason) {
         this.restTemplate.postForObject("http://" + apiServerUrl + "/process/" + this.processId + "/status",
                 new ProcessStatusResource()
-                        .setState(state)
+                        .setState(state.getValue())
+                        .setReason(reason)
                         .setTimeOfStateChange(System.currentTimeMillis()),
                 Long.class);
     }
diff --git a/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/KafkaReceiver.java b/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/KafkaReceiver.java
index 43e7526..42aa43d 100644
--- a/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/KafkaReceiver.java
+++ b/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/KafkaReceiver.java
@@ -20,6 +20,7 @@
 package org.apache.airavata.k8s.gfac.messaging;
 
 import org.apache.airavata.k8s.gfac.service.WorkerService;
+import org.apache.airavata.k8s.task.api.TaskContext;
 import org.springframework.kafka.annotation.KafkaListener;
 
 import javax.annotation.Resource;
@@ -42,12 +43,8 @@ public class KafkaReceiver {
     }
 
     @KafkaListener(topics = "${task.event.topic.name}", containerFactory = "kafkaEventListenerContainerFactory")
-    public void receiveTaskEvent(String payload) {
-        System.out.println("received event=" + payload);
-        String[] eventParts = payload.split(",");
-        long processId = Long.parseLong(eventParts[0]);
-        long taskId = Long.parseLong(eventParts[1]);
-        int state = Integer.parseInt(eventParts[2]);
-        workerService.onTaskStateEvent(processId, taskId, state);
+    public void receiveTaskEvent(TaskContext taskContext) {
+        System.out.println("received event for task id =" + taskContext.getTaskId());
+        workerService.onTaskStateEvent(taskContext);
     }
 }
diff --git a/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/KafkaSender.java b/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/KafkaSender.java
index f4afe30..c4df008 100644
--- a/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/KafkaSender.java
+++ b/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/KafkaSender.java
@@ -19,6 +19,8 @@
  */
 package org.apache.airavata.k8s.gfac.messaging;
 
+import org.apache.airavata.k8s.task.api.TaskContext;
+import org.apache.airavata.k8s.task.api.TaskContextSerializer;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.kafka.core.KafkaTemplate;
 
@@ -31,9 +33,9 @@ import org.springframework.kafka.core.KafkaTemplate;
 public class KafkaSender {
 
     @Autowired
-    private KafkaTemplate<String, String> kafkaTemplate;
+    private KafkaTemplate<String, TaskContext> kafkaTemplate;
 
-    public void send(String topic, String payload) {
-        kafkaTemplate.send(topic, payload);
+    public void send(String topic, TaskContext taskContext) {
+        kafkaTemplate.send(topic, taskContext);
     }
 }
diff --git a/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/ReceiverConfig.java b/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/ReceiverConfig.java
index cb94135..0b23bdd 100644
--- a/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/ReceiverConfig.java
+++ b/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/ReceiverConfig.java
@@ -19,6 +19,8 @@
  */
 package org.apache.airavata.k8s.gfac.messaging;
 
+import org.apache.airavata.k8s.task.api.TaskContext;
+import org.apache.airavata.k8s.task.api.TaskContextDeserializer;
 import org.apache.kafka.clients.consumer.ConsumerConfig;
 import org.apache.kafka.common.serialization.StringDeserializer;
 import org.springframework.beans.factory.annotation.Value;
@@ -69,7 +71,7 @@ public class ReceiverConfig {
         // list of host:port pairs used for establishing the initial connections to the Kakfa cluster
         props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
         props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
-        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
+        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, TaskContextDeserializer.class);
         // create a random group for each consumer in order to read all events form all consumers
         props.put(ConsumerConfig.GROUP_ID_CONFIG, "event-group-" + UUID.randomUUID().toString());
         return props;
@@ -81,8 +83,8 @@ public class ReceiverConfig {
     }
 
     @Bean
-    public ConsumerFactory<String, String> consumerFactoryForEvents() {
-        return new DefaultKafkaConsumerFactory<String, String>(consumerConfigsForEvents());
+    public ConsumerFactory<String, TaskContext> consumerFactoryForEvents() {
+        return new DefaultKafkaConsumerFactory<String, TaskContext>(consumerConfigsForEvents());
     }
 
     @Bean
@@ -95,8 +97,8 @@ public class ReceiverConfig {
     }
 
     @Bean
-    public KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<String, String>> kafkaEventListenerContainerFactory() {
-        ConcurrentKafkaListenerContainerFactory<String, String> factory =
+    public KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<String, TaskContext>> kafkaEventListenerContainerFactory() {
+        ConcurrentKafkaListenerContainerFactory<String, TaskContext> factory =
                 new ConcurrentKafkaListenerContainerFactory<>();
         factory.setConsumerFactory(consumerFactoryForEvents());
         return factory;
diff --git a/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/SenderConfig.java b/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/SenderConfig.java
index 3bd5303..4c6bf1e 100644
--- a/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/SenderConfig.java
+++ b/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/SenderConfig.java
@@ -19,6 +19,8 @@
  */
 package org.apache.airavata.k8s.gfac.messaging;
 
+import org.apache.airavata.k8s.task.api.TaskContext;
+import org.apache.airavata.k8s.task.api.TaskContextSerializer;
 import org.apache.kafka.clients.producer.ProducerConfig;
 import org.apache.kafka.common.serialization.StringSerializer;
 import org.springframework.beans.factory.annotation.Value;
@@ -48,17 +50,17 @@ public class SenderConfig {
         // list of host:port pairs used for establishing the initial connections to the Kakfa cluster
         props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
         props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
-        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
+        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, TaskContextSerializer.class);
         return props;
     }
 
     @Bean
-    public ProducerFactory<String, String> producerFactory() {
-        return new DefaultKafkaProducerFactory<String, String>(producerConfigs());
+    public ProducerFactory<String, TaskContext> producerFactory() {
+        return new DefaultKafkaProducerFactory<String, TaskContext>(producerConfigs());
     }
 
     @Bean
-    public KafkaTemplate<String, String> kafkaTemplate() {
+    public KafkaTemplate<String, TaskContext> kafkaTemplate() {
         return new KafkaTemplate<>(producerFactory());
     }
 
diff --git a/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/service/WorkerService.java b/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/service/WorkerService.java
index 0f20138..5449951 100644
--- a/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/service/WorkerService.java
+++ b/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/service/WorkerService.java
@@ -20,18 +20,17 @@
 package org.apache.airavata.k8s.gfac.service;
 
 import org.apache.airavata.k8s.api.resources.process.ProcessResource;
+import org.apache.airavata.k8s.api.resources.task.TaskDagResource;
 import org.apache.airavata.k8s.api.resources.task.TaskResource;
 import org.apache.airavata.k8s.api.resources.task.TaskStatusResource;
 import org.apache.airavata.k8s.gfac.core.ProcessLifeCycleManager;
 import org.apache.airavata.k8s.gfac.messaging.KafkaSender;
+import org.apache.airavata.k8s.task.api.TaskContext;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 import org.springframework.web.client.RestTemplate;
 
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
+import java.util.*;
 
 /**
  * TODO: Class level comments please
@@ -59,30 +58,26 @@ public class WorkerService {
         ProcessResource processResource = this.restTemplate.getForObject("http://" + apiServerUrl + "/process/" + processId,
                 ProcessResource.class);
         List<TaskResource> taskResources = processResource.getTasks();
-        boolean freshProcess = true;
-        for (TaskResource taskResource : taskResources) {
-            if (taskResource.getTaskStatus() != null && taskResource.getTaskStatus().size() > 0) {
-                // Already partially completed process. This happens when the task scheduler is killed while processing a process
-                TaskStatusResource lastStatusResource = taskResource.getTaskStatus().get(taskResource.getTaskStatus().size() - 1);
-                // TODO continue from last state
-                freshProcess = false;
-            } else {
-                // Fresh task
 
-            }
-        }
+        Set<TaskDagResource> takDagSet = this.restTemplate.getForObject("http://" + apiServerUrl + "/task/dag/"
+                + processId, Set.class);
 
-        if (freshProcess) {
-            System.out.println("Starting to execute process " + processId);
-            ProcessLifeCycleManager manager = new ProcessLifeCycleManager(processId, taskResources, kafkaSender, restTemplate, apiServerUrl);
-            manager.init();
-            manager.submitTaskToQueue(taskResources.get(0));
-            processLifecycleStore.put(processId, manager);
-        }
+        final Map<Long, Long> edgeMap = new HashMap<>();
+        Optional.ofNullable(takDagSet)
+                .ifPresent(dags -> dags.forEach(dag ->
+                        edgeMap.put(dag.getSourceOutPort().getId(), dag.getTargetTask().getId())));
+
+        System.out.println("Starting to execute process " + processId);
+        ProcessLifeCycleManager manager =
+                new ProcessLifeCycleManager(processId, taskResources, edgeMap, kafkaSender, restTemplate, apiServerUrl);
+
+        manager.init();
+        manager.start();
+        processLifecycleStore.put(processId, manager);
     }
 
-    public void onTaskStateEvent(long processId, long taskId, int state) {
-        Optional.ofNullable(processLifecycleStore.get(processId))
-                .ifPresent(manager -> manager.onTaskStateChanged(taskId, state));
+    public void onTaskStateEvent(TaskContext taskContext) {
+        Optional.ofNullable(processLifecycleStore.get(taskContext.getProcessId()))
+                .ifPresent(manager -> manager.onTaskStateChanged(taskContext));
     }
 }
diff --git a/airavata-kubernetes/modules/microservices/task-scheduler/src/main/resources/application.properties b/airavata-kubernetes/modules/microservices/task-scheduler/src/main/resources/application.properties
index c4aed73..c23a904 100644
--- a/airavata-kubernetes/modules/microservices/task-scheduler/src/main/resources/application.properties
+++ b/airavata-kubernetes/modules/microservices/task-scheduler/src/main/resources/application.properties
@@ -1,5 +1,5 @@
 server.port = 8195
 api.server.url = api-server.default.svc.cluster.local:8080
 scheduler.topic.name = airavata-scheduler
-scheduler.group.name = gfac
+scheduler.group.name = task-scheduler
 task.event.topic.name = airavata-task-event
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/microservices/tasks/command-executing-task/src/main/java/org/apache/airavata/k8s/task/job/service/TaskExecutionService.java b/airavata-kubernetes/modules/microservices/tasks/command-executing-task/src/main/java/org/apache/airavata/k8s/task/job/service/TaskExecutionService.java
index 23c147e..2fdb337 100644
--- a/airavata-kubernetes/modules/microservices/tasks/command-executing-task/src/main/java/org/apache/airavata/k8s/task/job/service/TaskExecutionService.java
+++ b/airavata-kubernetes/modules/microservices/tasks/command-executing-task/src/main/java/org/apache/airavata/k8s/task/job/service/TaskExecutionService.java
@@ -53,12 +53,12 @@ public class TaskExecutionService extends AbstractTaskExecutionService {
     @Override
     public void initializeParameters(TaskResource taskResource, TaskContext taskContext) throws Exception {
 
-        taskContext.getLocalContext().put(CommandTaskInfo.COMMAND, findInput(taskResource, CommandTaskInfo.COMMAND, false));
-        taskContext.getLocalContext().put(CommandTaskInfo.ARGUMENTS, findInput(taskResource, CommandTaskInfo.ARGUMENTS, true));
-        taskContext.getLocalContext().put(CommandTaskInfo.STD_OUT_PATH, findInput(taskResource, CommandTaskInfo.STD_OUT_PATH, false));
-        taskContext.getLocalContext().put(CommandTaskInfo.STD_ERR_PATH, findInput(taskResource, CommandTaskInfo.STD_ERR_PATH, false));
+        taskContext.getLocalContext().put(CommandTaskInfo.COMMAND, findInput(taskContext, taskResource, CommandTaskInfo.COMMAND, false));
+        taskContext.getLocalContext().put(CommandTaskInfo.ARGUMENTS, findInput(taskContext, taskResource, CommandTaskInfo.ARGUMENTS, true));
+        taskContext.getLocalContext().put(CommandTaskInfo.STD_OUT_PATH, findInput(taskContext, taskResource, CommandTaskInfo.STD_OUT_PATH, false));
+        taskContext.getLocalContext().put(CommandTaskInfo.STD_ERR_PATH, findInput(taskContext, taskResource, CommandTaskInfo.STD_ERR_PATH, false));
 
-        String computeId = findInput(taskResource, CommandTaskInfo.COMPUTE_RESOURCE, false);
+        String computeId = findInput(taskContext, taskResource, CommandTaskInfo.COMPUTE_RESOURCE, false);
         taskContext.getLocalContext().put(CommandTaskInfo.COMPUTE_RESOURCE, this.getRestTemplate().getForObject("http://" + this.getApiServerUrl()
                 + "/compute/" + Long.parseLong(computeId), ComputeResource.class));
 
@@ -75,7 +75,7 @@ public class TaskExecutionService extends AbstractTaskExecutionService {
             String stdErrPath = (String) taskContext.getLocalContext().get(CommandTaskInfo.STD_ERR_PATH);
             String stdOutSuffix = " > " + stdOutPath + " 2> " + stdErrPath;
 
-            publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.EXECUTING);
+            publishTaskStatus(taskContext, TaskStatusResource.State.EXECUTING);
 
             String finalCommand = command + (arguments != null ? arguments : "") + stdOutSuffix;
 
@@ -84,17 +84,17 @@ public class TaskExecutionService extends AbstractTaskExecutionService {
             ExecutionResult executionResult = fetchComputeResourceOperation(computeResource).executeCommand(finalCommand);
 
             if (executionResult.getExitStatus() == 0) {
-                publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.COMPLETED);
+                finishTaskExecution(taskContext, taskResource, "Out", TaskStatusResource.State.COMPLETED, "Task completed");
             } else if (executionResult.getExitStatus() == -1) {
-                publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.FAILED, "Process didn't exit successfully");
+                publishTaskStatus(taskContext, TaskStatusResource.State.FAILED, "Process didn't exit successfully");
             } else {
-                publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.FAILED, "Process exited with error status " + executionResult.getExitStatus());
+                publishTaskStatus(taskContext, TaskStatusResource.State.FAILED, "Process exited with error status " + executionResult.getExitStatus());
             }
 
         } catch (Exception e) {
 
             e.printStackTrace();
-            publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.FAILED, e.getMessage());
+            publishTaskStatus(taskContext, TaskStatusResource.State.FAILED, e.getMessage());
         }
     }
 }
diff --git a/airavata-kubernetes/modules/microservices/tasks/command-executing-task/src/main/resources/application.properties b/airavata-kubernetes/modules/microservices/tasks/command-executing-task/src/main/resources/application.properties
index d1a8582..188693e 100644
--- a/airavata-kubernetes/modules/microservices/tasks/command-executing-task/src/main/resources/application.properties
+++ b/airavata-kubernetes/modules/microservices/tasks/command-executing-task/src/main/resources/application.properties
@@ -1,5 +1,5 @@
 server.port = 8491
 api.server.url = api-server.default.svc.cluster.local:8080
-task.group.name = job-submission
+task.group.name = command-execution
 task.event.topic.name = airavata-task-event
-task.read.topic.name = airavata-task-job-submission
\ No newline at end of file
+task.read.topic.name = airavata-command
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/microservices/tasks/data-collecting-task/src/main/java/org/apacher/airavata/k8s/task/egress/service/TaskExecutionService.java b/airavata-kubernetes/modules/microservices/tasks/data-collecting-task/src/main/java/org/apacher/airavata/k8s/task/egress/service/TaskExecutionService.java
index 8b83708..c3ed302 100644
--- a/airavata-kubernetes/modules/microservices/tasks/data-collecting-task/src/main/java/org/apacher/airavata/k8s/task/egress/service/TaskExecutionService.java
+++ b/airavata-kubernetes/modules/microservices/tasks/data-collecting-task/src/main/java/org/apacher/airavata/k8s/task/egress/service/TaskExecutionService.java
@@ -56,10 +56,10 @@ public class TaskExecutionService extends AbstractTaskExecutionService {
     @Override
     public void initializeParameters(TaskResource taskResource, TaskContext taskContext) throws Exception {
 
-        taskContext.getLocalContext().put(DataCollectingTaskInfo.REMOTE_SOURCE_PATH, findInput(taskResource, DataCollectingTaskInfo.REMOTE_SOURCE_PATH, false));
-        taskContext.getLocalContext().put(DataCollectingTaskInfo.IDENTIFIER, findInput(taskResource, DataCollectingTaskInfo.IDENTIFIER, false));
+        taskContext.getLocalContext().put(DataCollectingTaskInfo.REMOTE_SOURCE_PATH, findInput(taskContext, taskResource, DataCollectingTaskInfo.REMOTE_SOURCE_PATH, false));
+        taskContext.getLocalContext().put(DataCollectingTaskInfo.IDENTIFIER, findInput(taskContext, taskResource, DataCollectingTaskInfo.IDENTIFIER, false));
 
-        String computeId = findInput(taskResource, DataCollectingTaskInfo.COMPUTE_RESOURCE, false);
+        String computeId = findInput(taskContext, taskResource, DataCollectingTaskInfo.COMPUTE_RESOURCE, false);
         taskContext.getLocalContext().put(DataCollectingTaskInfo.COMPUTE_RESOURCE, this.getRestTemplate().getForObject("http://" + this.getApiServerUrl()
                 + "/compute/" + Long.parseLong(computeId), ComputeResource.class));
 
@@ -73,7 +73,7 @@ public class TaskExecutionService extends AbstractTaskExecutionService {
             String identifier = (String) taskContext.getLocalContext().get(DataCollectingTaskInfo.IDENTIFIER);
             String remoteSourcePath = (String) taskContext.getLocalContext().get(DataCollectingTaskInfo.REMOTE_SOURCE_PATH);
 
-            publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.EXECUTING);
+            publishTaskStatus(taskContext, TaskStatusResource.State.EXECUTING);
 
             String temporaryFile = "/tmp/" + UUID.randomUUID().toString();
             System.out.println("Downloading " + remoteSourcePath + " to " + temporaryFile + " from compute resource "
@@ -94,12 +94,11 @@ public class TaskExecutionService extends AbstractTaskExecutionService {
             getRestTemplate().exchange("http://" + getApiServerUrl() + "/data/" + taskResource.getId()+ "/"
                             + identifier + "/upload", HttpMethod.POST, requestEntity, Long.class);
 
-            publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(),
-                    TaskStatusResource.State.COMPLETED);
+            finishTaskExecution(taskContext, taskResource, "Out", TaskStatusResource.State.COMPLETED, "Task completed");
 
         } catch (Exception e) {
             e.printStackTrace();
-            publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.FAILED, e.getMessage());
+            publishTaskStatus(taskContext, TaskStatusResource.State.FAILED, e.getMessage());
 
         }
     }
diff --git a/airavata-kubernetes/modules/microservices/tasks/data-pushing-task/src/main/java/org/apache/airavata/k8s/task/ingress/service/TaskExecutionService.java b/airavata-kubernetes/modules/microservices/tasks/data-pushing-task/src/main/java/org/apache/airavata/k8s/task/ingress/service/TaskExecutionService.java
index cb3fada..88de3a5 100644
--- a/airavata-kubernetes/modules/microservices/tasks/data-pushing-task/src/main/java/org/apache/airavata/k8s/task/ingress/service/TaskExecutionService.java
+++ b/airavata-kubernetes/modules/microservices/tasks/data-pushing-task/src/main/java/org/apache/airavata/k8s/task/ingress/service/TaskExecutionService.java
@@ -54,10 +54,10 @@ public class TaskExecutionService extends AbstractTaskExecutionService {
     @Override
     public void initializeParameters(TaskResource taskResource, TaskContext taskContext) throws Exception {
 
-        taskContext.getLocalContext().put(DATA_LOCATION_ID, findInput(taskResource, DATA_LOCATION_ID, false));
-        taskContext.getLocalContext().put(REMOTE_TARGET_PATH, findInput(taskResource, REMOTE_TARGET_PATH, false));
+        taskContext.getLocalContext().put(DATA_LOCATION_ID, findInput(taskContext, taskResource, DATA_LOCATION_ID, false));
+        taskContext.getLocalContext().put(REMOTE_TARGET_PATH, findInput(taskContext, taskResource, REMOTE_TARGET_PATH, false));
 
-        String computeId = findInput(taskResource, COMPUTE_RESOURCE, false);
+        String computeId = findInput(taskContext, taskResource, COMPUTE_RESOURCE, false);
         taskContext.getLocalContext().put(COMPUTE_RESOURCE, this.getRestTemplate().getForObject("http://" + this.getApiServerUrl()
                 + "/compute/" + Long.parseLong(computeId), ComputeResource.class));
     }
@@ -70,14 +70,15 @@ public class TaskExecutionService extends AbstractTaskExecutionService {
         ComputeResource computeResource = (ComputeResource) taskContext.getLocalContext().get(COMPUTE_RESOURCE);
 
         try {
-            publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.EXECUTING);
+            publishTaskStatus(taskContext, TaskStatusResource.State.EXECUTING);
             fetchComputeResourceOperation(computeResource).transferDataIn(dataLocationId, remoteTargetPath, "SCP");
-            publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.COMPLETED);
+            finishTaskExecution(taskContext, taskResource, "Out", TaskStatusResource.State.COMPLETED, "Task completed");
+
 
         } catch (Exception e) {
 
             e.printStackTrace();
-            publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.FAILED);
+            publishTaskStatus(taskContext, TaskStatusResource.State.FAILED);
         }
     }
 }
diff --git a/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/AbstractTaskExecutionService.java b/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/AbstractTaskExecutionService.java
index 1f958a7..69837a6 100644
--- a/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/AbstractTaskExecutionService.java
+++ b/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/AbstractTaskExecutionService.java
@@ -2,6 +2,7 @@ package org.apache.airavata.k8s.task.api;
 
 import org.apache.airavata.k8s.api.resources.compute.ComputeResource;
 import org.apache.airavata.k8s.api.resources.task.TaskInputResource;
+import org.apache.airavata.k8s.api.resources.task.TaskOutPortResource;
 import org.apache.airavata.k8s.api.resources.task.TaskResource;
 import org.apache.airavata.k8s.api.resources.task.TaskStatusResource;
 import org.apache.airavata.k8s.api.resources.task.type.TaskTypeResource;
@@ -54,7 +55,7 @@ public abstract class AbstractTaskExecutionService {
         System.out.println("Executing task " + taskContext.getTaskId());
         TaskResource taskResource = this.restTemplate.getForObject("http://" + apiServerUrl + "/task/" + taskContext.getTaskId(), TaskResource.class);
 
-        publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.SCHEDULED);
+        publishTaskStatus(taskContext, TaskStatusResource.State.SCHEDULED);
 
         this.executorService.execute(() -> {
             try {
@@ -80,7 +81,7 @@ public abstract class AbstractTaskExecutionService {
         return operations;
     }
 
-    public String findInput(TaskResource taskResource, String name, boolean optional) throws Exception {
+    public String findInput(TaskContext taskContext, TaskResource taskResource, String name, boolean optional) throws Exception {
 
         Optional<TaskInputResource> inputResource = taskResource.getInputs()
                 .stream()
@@ -92,7 +93,7 @@ public abstract class AbstractTaskExecutionService {
 
         } else {
             if (!optional) {
-                publishTaskStatus(taskResource.getParentProcessId(), taskResource.getId(), TaskStatusResource.State.FAILED,
+                publishTaskStatus(taskContext, TaskStatusResource.State.FAILED,
                         name + " is not available in inputs");
                 throw new Exception(name + " is not available in inputs");
             } else {
@@ -104,13 +105,26 @@ public abstract class AbstractTaskExecutionService {
     public abstract void initializeParameters(TaskResource taskResource, TaskContext taskContext) throws Exception;
     public abstract void executeTask(TaskResource taskResource, TaskContext taskContext);
 
-    public void publishTaskStatus(long processId, long taskId, int status) {
-        publishTaskStatus(processId, taskId, status, "");
+    public void publishTaskStatus(TaskContext taskContext, int status) {
+        publishTaskStatus(taskContext, status, "");
     }
 
-    public void publishTaskStatus(long processId, long taskId, int status, String reason) {
-        this.kafkaSender.send(this.taskEventPublishTopic, processId + "-" + taskId,
-                processId + "," + taskId + "," + status + "," + reason);
+    public void publishTaskStatus(TaskContext taskContext, int status, String reason) {
+        taskContext.setStatus(status);
+        taskContext.setReason(reason);
+
+        this.kafkaSender.send(this.taskEventPublishTopic, taskContext);
+    }
+
+    public void finishTaskExecution(TaskContext taskContext, TaskResource task, String outPortName, int status, String reason) throws Exception {
+        Optional<TaskOutPortResource> selectedOutPort = task.getOutPorts().stream().filter(outPort -> outPort.getName().equals(outPortName)).findFirst();
+        if (!selectedOutPort.isPresent()) {
+            throw new Exception("Selected out port " + outPortName + " does not exist in the task " + task.getName());
+        }
+
+        taskContext.setStatus(status);
+        taskContext.setReason(reason);
+        taskContext.setOutPortId(selectedOutPort.get().getId());
     }
 
     public RestTemplate getRestTemplate() {
diff --git a/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/TaskContext.java b/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/TaskContext.java
index 2fdf8af..e94beab 100644
--- a/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/TaskContext.java
+++ b/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/TaskContext.java
@@ -1,5 +1,9 @@
 package org.apache.airavata.k8s.task.api;
 
+import org.apache.airavata.k8s.api.resources.task.TaskResource;
+import org.apache.airavata.k8s.api.resources.task.TaskStatusResource;
+
+import java.io.Serializable;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -9,12 +13,50 @@ import java.util.Map;
  * @author dimuthu
  * @since 1.0.0-SNAPSHOT
  */
-public class TaskContext {
+public class TaskContext implements Serializable {
 
+    private long processId;
     private long taskId;
+    private int status;
+    private String reason;
+
+    public long getOutPortId() {
+        return outPortId;
+    }
+
+    public TaskContext setOutPortId(long outPortId) {
+        this.outPortId = outPortId;
+        return this;
+    }
+
+    private long outPortId;
     private Map<String, String> contextVariableParams = new HashMap<>();
     private Map<String, String> contextDataParams = new HashMap<>();
-    private Map<String, Object> localContext = new HashMap<>();
+    private transient Map<String, Object> localContext = new HashMap<>();
+
+    private void resetStatus() {
+        setStatus(-1);
+        setReason("");
+        setOutPortId(-1);
+        setProcessId(-1);
+        setTaskId(-1);
+    }
+
+    public void assignTask(TaskResource taskResource) {
+        resetStatus();
+        setTaskId(taskResource.getId());
+        setProcessId(taskResource.getParentProcessId());
+        setStatus(TaskStatusResource.State.SCHEDULED);
+    }
+
+    public void resetPublicContext() {
+        this.contextVariableParams = new HashMap<>();
+        this.contextDataParams = new HashMap<>();
+    }
+
+    public void resetLocalContext() {
+        this.localContext = new HashMap<>();
+    }
 
     public long getTaskId() {
         return taskId;
@@ -51,4 +93,32 @@ public class TaskContext {
         this.localContext = localContext;
         return this;
     }
+
+    public long getProcessId() {
+        return processId;
+    }
+
+    public TaskContext setProcessId(long processId) {
+        this.processId = processId;
+        return this;
+    }
+
+    public int getStatus() {
+        return status;
+    }
+
+    public TaskContext setStatus(int status) {
+        this.status = status;
+        return this;
+    }
+
+    public String getReason() {
+        return reason;
+    }
+
+    public TaskContext setReason(String reason) {
+        this.reason = reason;
+        return this;
+    }
+
 }
diff --git a/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/TaskContextDeserializer.java b/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/TaskContextDeserializer.java
index 524ce2e..b826d5b 100644
--- a/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/TaskContextDeserializer.java
+++ b/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/TaskContextDeserializer.java
@@ -2,6 +2,7 @@ package org.apache.airavata.k8s.task.api;
 
 import org.apache.kafka.common.serialization.Deserializer;
 
+import java.io.*;
 import java.util.Map;
 
 /**
@@ -19,6 +20,24 @@ public class TaskContextDeserializer implements Deserializer<TaskContext> {
 
     @Override
     public TaskContext deserialize(String topic, byte[] data) {
+        ByteArrayInputStream bis = new ByteArrayInputStream(data);
+        ObjectInput in = null;
+        try {
+            in = new ObjectInputStream(bis);
+            return(TaskContext)in.readObject();
+        } catch (IOException e) {
+            // ignore exception
+        } catch (ClassNotFoundException e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                if (in != null) {
+                    in.close();
+                }
+            } catch (IOException ex) {
+                // ignore close exception
+            }
+        }
         return null;
     }
 
diff --git a/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/TaskContextSerializer.java b/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/TaskContextSerializer.java
index eb4c762..0edac4b 100644
--- a/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/TaskContextSerializer.java
+++ b/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/TaskContextSerializer.java
@@ -2,6 +2,10 @@ package org.apache.airavata.k8s.task.api;
 
 import org.apache.kafka.common.serialization.Serializer;
 
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectOutput;
+import java.io.ObjectOutputStream;
 import java.util.Map;
 
 /**
@@ -18,7 +22,23 @@ public class TaskContextSerializer implements Serializer<TaskContext> {
 
     @Override
     public byte[] serialize(String topic, TaskContext data) {
-        return new byte[0];
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        ObjectOutput out = null;
+        try {
+            out = new ObjectOutputStream(bos);
+            out.writeObject(data);
+            out.flush();
+            return bos.toByteArray();
+        } catch (IOException e) {
+            // ignore catch
+        } finally {
+            try {
+                bos.close();
+            } catch (IOException ex) {
+                // ignore close exception
+            }
+        }
+        return null;
     }
 
     @Override
diff --git a/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/messaging/KafkaSender.java b/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/messaging/KafkaSender.java
index 307215f..b584833 100644
--- a/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/messaging/KafkaSender.java
+++ b/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/messaging/KafkaSender.java
@@ -19,6 +19,7 @@
  */
 package org.apache.airavata.k8s.task.api.messaging;
 
+import org.apache.airavata.k8s.task.api.TaskContext;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.kafka.core.KafkaTemplate;
 
@@ -31,13 +32,13 @@ import org.springframework.kafka.core.KafkaTemplate;
 public class KafkaSender {
 
     @Autowired
-    private KafkaTemplate<String, String> kafkaTemplate;
+    private KafkaTemplate<String, TaskContext> kafkaTemplate;
 
-    public void send(String topic, String payload) {
-        kafkaTemplate.send(topic, payload);
+    public void send(String topic, TaskContext taskContext) {
+        kafkaTemplate.send(topic, taskContext);
     }
 
-    public void send(String topic, String key, String payload) {
-        kafkaTemplate.send(topic, key, payload);
+    public void send(String topic, String key, TaskContext taskContext) {
+        kafkaTemplate.send(topic, key, taskContext);
     }
 }
diff --git a/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/messaging/ReceiverConfig.java b/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/messaging/ReceiverConfig.java
index b078a79..8a09a4e 100644
--- a/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/messaging/ReceiverConfig.java
+++ b/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/messaging/ReceiverConfig.java
@@ -19,6 +19,8 @@
  */
 package org.apache.airavata.k8s.task.api.messaging;
 
+import org.apache.airavata.k8s.task.api.TaskContext;
+import org.apache.airavata.k8s.task.api.TaskContextDeserializer;
 import org.apache.kafka.clients.consumer.ConsumerConfig;
 import org.apache.kafka.common.serialization.StringDeserializer;
 import org.springframework.beans.factory.annotation.Value;
@@ -59,7 +61,7 @@ public class ReceiverConfig {
         // list of host:port pairs used for establishing the initial connections to the Kakfa cluster
         props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
         props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
-        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
+        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, TaskContextDeserializer.class);
         // allows a pool of processes to divide the work of consuming and processing records
         props.put(ConsumerConfig.GROUP_ID_CONFIG, taskGroupName);
         props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
@@ -67,13 +69,13 @@ public class ReceiverConfig {
     }
 
     @Bean
-    public ConsumerFactory<String, String> consumerFactory() {
-        return new DefaultKafkaConsumerFactory<String, String>(consumerConfigs());
+    public ConsumerFactory<String, TaskContext> consumerFactory() {
+        return new DefaultKafkaConsumerFactory<String, TaskContext>(consumerConfigs());
     }
 
     @Bean
-    public KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<String, String>> kafkaListenerContainerFactory() {
-        ConcurrentKafkaListenerContainerFactory<String, String> factory =
+    public KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<String, TaskContext>> kafkaListenerContainerFactory() {
+        ConcurrentKafkaListenerContainerFactory<String, TaskContext> factory =
                 new ConcurrentKafkaListenerContainerFactory<>();
         factory.setConsumerFactory(consumerFactory());
         factory.getContainerProperties().setAckMode(AbstractMessageListenerContainer.AckMode.MANUAL_IMMEDIATE);
diff --git a/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/messaging/SenderConfig.java b/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/messaging/SenderConfig.java
index cd8b54b..e66e1fd 100644
--- a/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/messaging/SenderConfig.java
+++ b/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/messaging/SenderConfig.java
@@ -19,6 +19,8 @@
  */
 package org.apache.airavata.k8s.task.api.messaging;
 
+import org.apache.airavata.k8s.task.api.TaskContext;
+import org.apache.airavata.k8s.task.api.TaskContextSerializer;
 import org.apache.kafka.clients.producer.ProducerConfig;
 import org.apache.kafka.common.serialization.StringSerializer;
 import org.springframework.beans.factory.annotation.Value;
@@ -48,17 +50,17 @@ public class SenderConfig {
         // list of host:port pairs used for establishing the initial connections to the Kakfa cluster
         props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
         props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
-        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
+        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, TaskContextSerializer.class);
         return props;
     }
 
     @Bean
-    public ProducerFactory<String, String> producerFactory() {
-        return new DefaultKafkaProducerFactory<String, String>(producerConfigs());
+    public ProducerFactory<String, TaskContext> producerFactory() {
+        return new DefaultKafkaProducerFactory<String, TaskContext>(producerConfigs());
     }
 
     @Bean
-    public KafkaTemplate<String, String> kafkaTemplate() {
+    public KafkaTemplate<String, TaskContext> kafkaTemplate() {
         return new KafkaTemplate<>(producerFactory());
     }
 

-- 
To stop receiving notification emails like this one, please contact
"commits@airavata.apache.org" <co...@airavata.apache.org>.

[airavata-sandbox] 18/19: Initial agent implementation

Posted by sm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

smarru pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-sandbox.git

commit e42d3f65f1d326b06d106f529f4e4144aab051cb
Author: dimuthu.upeksha2@gmail.com <Di...@1234>
AuthorDate: Sat Dec 2 23:00:56 2017 +0530

    Initial agent implementation
---
 .../modules/agents/agent-core/pom.xml              |   40 +
 .../airavata/agents/core/AsyncOperation.java       |   24 +
 .../airavata/agents/core/StatusPublisher.java      |   49 +
 .../modules/agents/thrift-agent/pom.xml            |   47 +
 .../agents/thrift/handler/OperationHandler.java    |   34 +
 .../thrift/operation/ThriftAgentOperation.java     |   45 +
 .../agents/thrift/server/OperationServer.java      |   51 +
 .../agents/thrift/stubs/OperationException.java    |  476 +++++++++
 .../agents/thrift/stubs/OperationService.java      | 1072 ++++++++++++++++++++
 .../src/main/resources/application.properties      |    2 +
 .../thrift-agent/src/main/resources/schema.thrift  |   11 +
 .../process/ProcessBootstrapDataResource.java      |   40 +
 .../k8s/api/resources/process/ProcessResource.java |   10 +
 .../apache/airavata/helix/api/AbstractTask.java    |   24 +-
 .../airavata/helix/api/HelixParticipant.java       |    7 +-
 .../api/server/controller/WorkflowController.java  |   10 +-
 .../server/model/process/ProcessBootstrapData.java |   63 ++
 .../k8s/api/server/model/process/ProcessModel.java |   12 +
 .../process/ProcessBootstrapDataRepository.java    |   13 +
 .../k8s/api/server/service/ProcessService.java     |   15 +-
 .../k8s/api/server/service/WorkflowService.java    |   15 +-
 .../api/server/service/util/ToResourceUtil.java    |   17 +
 .../microservices/async-event-listener/pom.xml     |  129 +++
 .../airavata/async/event/listener/Application.java |   29 +
 .../event/listener/messaging/KafkaReceiver.java    |   52 +
 .../event/listener/messaging/ReceiverConfig.java   |   82 ++
 .../event/listener/service/ListenerService.java    |   35 +
 .../src/main/resources/application.properties      |    4 +
 .../src/main/resources/application.yml             |    4 +
 .../microservices/tasks/async-command-task/pom.xml |  118 +++
 .../task/async/command/AsyncCommandTask.java}      |   65 +-
 .../helix/task/async}/command/Participant.java     |    9 +-
 .../src/main/resources/application.properties      |    8 +
 .../src/main/resources/log4j.properties            |    9 +
 .../airavata/helix/task/command/CommandTask.java   |    5 +-
 .../airavata/helix/task/command/Participant.java   |    2 +-
 .../src/main/resources/application.properties      |    2 +
 .../airavata/helix/task/datain/DataInputTask.java  |    5 +-
 .../airavata/helix/task/datain/Participant.java    |    2 +-
 .../src/main/resources/application.properties      |    4 +-
 .../helix/task/dataout/DataOutputTask.java         |    5 +-
 .../airavata/helix/task/dataout/Participant.java   |    2 +-
 .../src/main/resources/application.properties      |    4 +-
 .../k8s/gfac/core/HelixWorkflowManager.java        |   24 +-
 .../airavata/k8s/gfac/service/WorkerService.java   |    3 +-
 airavata-kubernetes/pom.xml                        |    4 +
 46 files changed, 2607 insertions(+), 76 deletions(-)

diff --git a/airavata-kubernetes/modules/agents/agent-core/pom.xml b/airavata-kubernetes/modules/agents/agent-core/pom.xml
new file mode 100644
index 0000000..e8ce15d
--- /dev/null
+++ b/airavata-kubernetes/modules/agents/agent-core/pom.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>airavata-kubernetes</artifactId>
+        <groupId>org.apache.airavata</groupId>
+        <version>1.0-SNAPSHOT</version>
+        <relativePath>../../../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>agent-core</artifactId>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.5.1</version>
+                <configuration>
+                    <source>${java.version}</source>
+                    <target>${java.version}</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.airavata</groupId>
+            <artifactId>api-resource</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.kafka</groupId>
+            <artifactId>kafka-clients</artifactId>
+        </dependency>
+    </dependencies>
+</project>
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/agents/agent-core/src/main/java/org/apache/airavata/agents/core/AsyncOperation.java b/airavata-kubernetes/modules/agents/agent-core/src/main/java/org/apache/airavata/agents/core/AsyncOperation.java
new file mode 100644
index 0000000..cc488a0
--- /dev/null
+++ b/airavata-kubernetes/modules/agents/agent-core/src/main/java/org/apache/airavata/agents/core/AsyncOperation.java
@@ -0,0 +1,24 @@
+package org.apache.airavata.agents.core;
+
+import org.apache.airavata.k8s.api.resources.compute.ComputeResource;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public abstract class AsyncOperation {
+
+    private ComputeResource computeResource;
+
+    public AsyncOperation(ComputeResource computeResource) {
+        this.computeResource = computeResource;
+    }
+
+    public abstract void executeCommandAsync(String command, long callbackWorkflowId);
+
+    public ComputeResource getComputeResource() {
+        return computeResource;
+    }
+}
diff --git a/airavata-kubernetes/modules/agents/agent-core/src/main/java/org/apache/airavata/agents/core/StatusPublisher.java b/airavata-kubernetes/modules/agents/agent-core/src/main/java/org/apache/airavata/agents/core/StatusPublisher.java
new file mode 100644
index 0000000..71af84e
--- /dev/null
+++ b/airavata-kubernetes/modules/agents/agent-core/src/main/java/org/apache/airavata/agents/core/StatusPublisher.java
@@ -0,0 +1,49 @@
+package org.apache.airavata.agents.core;
+
+import org.apache.kafka.clients.producer.KafkaProducer;
+import org.apache.kafka.clients.producer.Producer;
+import org.apache.kafka.clients.producer.ProducerRecord;
+
+import java.util.Properties;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public class StatusPublisher {
+    private String brokerUrl;
+    private String topicName;
+
+    private Producer<String, String> eventProducer;
+
+    public StatusPublisher(String brokerUrl, String topicName) {
+        this.brokerUrl = brokerUrl;
+        this.topicName = topicName;
+        this.initializeKafkaEventProducer();
+    }
+
+    public void publishStatus(long callbackWorkflowId, String status, String message) {
+        this.eventProducer.send(new ProducerRecord<String, String>(
+                this.topicName, String.join(",", callbackWorkflowId + "", status, message)));
+    }
+
+    public void initializeKafkaEventProducer() {
+        Properties props = new Properties();
+
+        props.put("bootstrap.servers", this.brokerUrl);
+
+        props.put("acks", "all");
+        props.put("retries", 0);
+        props.put("batch.size", 16384);
+        props.put("linger.ms", 1);
+        props.put("buffer.memory", 33554432);
+        props.put("key.serializer",
+                "org.apache.kafka.common.serialization.StringSerializer");
+        props.put("value.serializer",
+                "org.apache.kafka.common.serialization.StringSerializer");
+
+        eventProducer = new KafkaProducer<String, String>(props);
+    }
+}
diff --git a/airavata-kubernetes/modules/agents/thrift-agent/pom.xml b/airavata-kubernetes/modules/agents/thrift-agent/pom.xml
new file mode 100644
index 0000000..1bfb0b8
--- /dev/null
+++ b/airavata-kubernetes/modules/agents/thrift-agent/pom.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>airavata-kubernetes</artifactId>
+        <groupId>org.apache.airavata</groupId>
+        <version>1.0-SNAPSHOT</version>
+        <relativePath>../../../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>thrift-agent</artifactId>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.5.1</version>
+                <configuration>
+                    <source>${java.version}</source>
+                    <target>${java.version}</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <!-- https://mvnrepository.com/artifact/org.apache.thrift/libthrift -->
+        <dependency>
+            <groupId>org.apache.thrift</groupId>
+            <artifactId>libthrift</artifactId>
+            <version>0.10.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.airavata</groupId>
+            <artifactId>agent-core</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.airavata</groupId>
+            <artifactId>helix-task-api</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+    </dependencies>
+</project>
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/agents/thrift-agent/src/main/java/org/apache/airavata/agents/thrift/handler/OperationHandler.java b/airavata-kubernetes/modules/agents/thrift-agent/src/main/java/org/apache/airavata/agents/thrift/handler/OperationHandler.java
new file mode 100644
index 0000000..c402a83
--- /dev/null
+++ b/airavata-kubernetes/modules/agents/thrift-agent/src/main/java/org/apache/airavata/agents/thrift/handler/OperationHandler.java
@@ -0,0 +1,34 @@
+package org.apache.airavata.agents.thrift.handler;
+
+import org.apache.airavata.agents.core.StatusPublisher;
+import org.apache.airavata.agents.thrift.stubs.OperationException;
+import org.apache.airavata.agents.thrift.stubs.OperationService;
+import org.apache.thrift.TException;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public class OperationHandler extends StatusPublisher implements OperationService.Iface {
+
+    public OperationHandler(String brokerUrl, String topicName) {
+        super(brokerUrl, topicName);
+    }
+
+    @Override
+    public void executeCommand(String command, long callbackWorkflowId) throws OperationException, TException {
+        publishStatus(callbackWorkflowId, "PENDING", "Pending for execution");
+        publishStatus(callbackWorkflowId, "STARTED", "Starting command execution");
+        Runnable task = new Runnable() {
+            @Override
+            public void run() {
+                System.out.println("Executing command " + command);
+                publishStatus(callbackWorkflowId, "SUCCESS", "Command execution succeeded");
+            }
+        };
+
+        new Thread(task).start();
+    }
+}
diff --git a/airavata-kubernetes/modules/agents/thrift-agent/src/main/java/org/apache/airavata/agents/thrift/operation/ThriftAgentOperation.java b/airavata-kubernetes/modules/agents/thrift-agent/src/main/java/org/apache/airavata/agents/thrift/operation/ThriftAgentOperation.java
new file mode 100644
index 0000000..cb9010b
--- /dev/null
+++ b/airavata-kubernetes/modules/agents/thrift-agent/src/main/java/org/apache/airavata/agents/thrift/operation/ThriftAgentOperation.java
@@ -0,0 +1,45 @@
+package org.apache.airavata.agents.thrift.operation;
+
+import org.apache.airavata.agents.core.AsyncOperation;
+import org.apache.airavata.agents.thrift.stubs.OperationService;
+import org.apache.airavata.k8s.api.resources.compute.ComputeResource;
+import org.apache.thrift.TException;
+import org.apache.thrift.protocol.TBinaryProtocol;
+import org.apache.thrift.protocol.TProtocol;
+import org.apache.thrift.transport.TSocket;
+import org.apache.thrift.transport.TTransport;
+import org.apache.thrift.transport.TTransportException;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public class ThriftAgentOperation extends AsyncOperation {
+
+    private OperationService.Client client;
+
+    public ThriftAgentOperation(ComputeResource computeResource) {
+        super(computeResource);
+
+        try {
+            TTransport transport = new TSocket(computeResource.getHost(), 9090);
+            transport.open();
+            TProtocol protocol = new TBinaryProtocol(transport);
+            this.client = new OperationService.Client(protocol);
+
+        } catch (TTransportException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public void executeCommandAsync(String command, long callbackWorkflowId) {
+        try {
+            client.executeCommand(command, callbackWorkflowId);
+        } catch (TException e) {
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/airavata-kubernetes/modules/agents/thrift-agent/src/main/java/org/apache/airavata/agents/thrift/server/OperationServer.java b/airavata-kubernetes/modules/agents/thrift-agent/src/main/java/org/apache/airavata/agents/thrift/server/OperationServer.java
new file mode 100644
index 0000000..4eecd8f
--- /dev/null
+++ b/airavata-kubernetes/modules/agents/thrift-agent/src/main/java/org/apache/airavata/agents/thrift/server/OperationServer.java
@@ -0,0 +1,51 @@
+package org.apache.airavata.agents.thrift.server;
+
+import org.apache.airavata.agents.thrift.handler.OperationHandler;
+import org.apache.airavata.agents.thrift.stubs.OperationService;
+import org.apache.airavata.helix.api.PropertyResolver;
+import org.apache.thrift.server.TServer;
+import org.apache.thrift.server.TSimpleServer;
+import org.apache.thrift.transport.TServerSocket;
+import org.apache.thrift.transport.TServerTransport;
+
+import java.io.IOException;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public class OperationServer {
+
+    private static OperationHandler operationHandler;
+    private static OperationService.Processor processor;
+
+    public static void main(String args[]) throws IOException {
+        PropertyResolver resolver = new PropertyResolver();
+        resolver.loadInputStream(OperationService.class.getClassLoader().getResourceAsStream("application.properties"));
+        operationHandler = new OperationHandler(resolver.get("kafka.bootstrap.url"), resolver.get("async.event.listener.topic"));
+        processor = new OperationService.Processor(operationHandler);
+
+        Runnable server = new Runnable() {
+            @Override
+            public void run() {
+                simple(processor);
+            }
+        };
+
+        new Thread(server).start();
+    }
+
+    public static void simple(OperationService.Processor processor) {
+        try {
+            TServerTransport serverTransport = new TServerSocket(9090);
+            TServer server = new TSimpleServer(new TServer.Args(serverTransport).processor(processor));
+
+            System.out.println("Starting the operation server...");
+            server.serve();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/airavata-kubernetes/modules/agents/thrift-agent/src/main/java/org/apache/airavata/agents/thrift/stubs/OperationException.java b/airavata-kubernetes/modules/agents/thrift-agent/src/main/java/org/apache/airavata/agents/thrift/stubs/OperationException.java
new file mode 100644
index 0000000..0ab63f5
--- /dev/null
+++ b/airavata-kubernetes/modules/agents/thrift-agent/src/main/java/org/apache/airavata/agents/thrift/stubs/OperationException.java
@@ -0,0 +1,476 @@
+/**
+ * Autogenerated by Thrift Compiler (0.10.0)
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ *  @generated
+ */
+package org.apache.airavata.agents.thrift.stubs;
+
+@SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"})
+@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.10.0)", date = "2017-12-02")
+public class OperationException extends org.apache.thrift.TException implements org.apache.thrift.TBase<OperationException, OperationException._Fields>, java.io.Serializable, Cloneable, Comparable<OperationException> {
+  private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("OperationException");
+
+  private static final org.apache.thrift.protocol.TField MESSAGE_FIELD_DESC = new org.apache.thrift.protocol.TField("message", org.apache.thrift.protocol.TType.STRING, (short)1);
+  private static final org.apache.thrift.protocol.TField STACKTRACE_FIELD_DESC = new org.apache.thrift.protocol.TField("stacktrace", org.apache.thrift.protocol.TType.STRING, (short)2);
+
+  private static final org.apache.thrift.scheme.SchemeFactory STANDARD_SCHEME_FACTORY = new OperationExceptionStandardSchemeFactory();
+  private static final org.apache.thrift.scheme.SchemeFactory TUPLE_SCHEME_FACTORY = new OperationExceptionTupleSchemeFactory();
+
+  public java.lang.String message; // required
+  public java.lang.String stacktrace; // required
+
+  /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
+  public enum _Fields implements org.apache.thrift.TFieldIdEnum {
+    MESSAGE((short)1, "message"),
+    STACKTRACE((short)2, "stacktrace");
+
+    private static final java.util.Map<java.lang.String, _Fields> byName = new java.util.HashMap<java.lang.String, _Fields>();
+
+    static {
+      for (_Fields field : java.util.EnumSet.allOf(_Fields.class)) {
+        byName.put(field.getFieldName(), field);
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, or null if its not found.
+     */
+    public static _Fields findByThriftId(int fieldId) {
+      switch(fieldId) {
+        case 1: // MESSAGE
+          return MESSAGE;
+        case 2: // STACKTRACE
+          return STACKTRACE;
+        default:
+          return null;
+      }
+    }
+
+    /**
+     * Find the _Fields constant that matches fieldId, throwing an exception
+     * if it is not found.
+     */
+    public static _Fields findByThriftIdOrThrow(int fieldId) {
+      _Fields fields = findByThriftId(fieldId);
+      if (fields == null) throw new java.lang.IllegalArgumentException("Field " + fieldId + " doesn't exist!");
+      return fields;
+    }
+
+    /**
+     * Find the _Fields constant that matches name, or null if its not found.
+     */
+    public static _Fields findByName(java.lang.String name) {
+      return byName.get(name);
+    }
+
+    private final short _thriftId;
+    private final java.lang.String _fieldName;
+
+    _Fields(short thriftId, java.lang.String fieldName) {
+      _thriftId = thriftId;
+      _fieldName = fieldName;
+    }
+
+    public short getThriftFieldId() {
+      return _thriftId;
+    }
+
+    public java.lang.String getFieldName() {
+      return _fieldName;
+    }
+  }
+
+  // isset id assignments
+  public static final java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
+  static {
+    java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new java.util.EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class);
+    tmpMap.put(_Fields.MESSAGE, new org.apache.thrift.meta_data.FieldMetaData("message", org.apache.thrift.TFieldRequirementType.DEFAULT, 
+        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING)));
+    tmpMap.put(_Fields.STACKTRACE, new org.apache.thrift.meta_data.FieldMetaData("stacktrace", org.apache.thrift.TFieldRequirementType.DEFAULT, 
+        new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING)));
+    metaDataMap = java.util.Collections.unmodifiableMap(tmpMap);
+    org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(OperationException.class, metaDataMap);
+  }
+
+  public OperationException() {
+  }
+
+  public OperationException(
+    java.lang.String message,
+    java.lang.String stacktrace)
+  {
+    this();
+    this.message = message;
+    this.stacktrace = stacktrace;
+  }
+
+  /**
+   * Performs a deep copy on <i>other</i>.
+   */
+  public OperationException(OperationException other) {
+    if (other.isSetMessage()) {
+      this.message = other.message;
+    }
+    if (other.isSetStacktrace()) {
+      this.stacktrace = other.stacktrace;
+    }
+  }
+
+  public OperationException deepCopy() {
+    return new OperationException(this);
+  }
+
+  @Override
+  public void clear() {
+    this.message = null;
+    this.stacktrace = null;
+  }
+
+  public java.lang.String getMessage() {
+    return this.message;
+  }
+
+  public OperationException setMessage(java.lang.String message) {
+    this.message = message;
+    return this;
+  }
+
+  public void unsetMessage() {
+    this.message = null;
+  }
+
+  /** Returns true if field message is set (has been assigned a value) and false otherwise */
+  public boolean isSetMessage() {
+    return this.message != null;
+  }
+
+  public void setMessageIsSet(boolean value) {
+    if (!value) {
+      this.message = null;
+    }
+  }
+
+  public java.lang.String getStacktrace() {
+    return this.stacktrace;
+  }
+
+  public OperationException setStacktrace(java.lang.String stacktrace) {
+    this.stacktrace = stacktrace;
+    return this;
+  }
+
+  public void unsetStacktrace() {
+    this.stacktrace = null;
+  }
+
+  /** Returns true if field stacktrace is set (has been assigned a value) and false otherwise */
+  public boolean isSetStacktrace() {
+    return this.stacktrace != null;
+  }
+
+  public void setStacktraceIsSet(boolean value) {
+    if (!value) {
+      this.stacktrace = null;
+    }
+  }
+
+  public void setFieldValue(_Fields field, java.lang.Object value) {
+    switch (field) {
+    case MESSAGE:
+      if (value == null) {
+        unsetMessage();
+      } else {
+        setMessage((java.lang.String)value);
+      }
+      break;
+
+    case STACKTRACE:
+      if (value == null) {
+        unsetStacktrace();
+      } else {
+        setStacktrace((java.lang.String)value);
+      }
+      break;
+
+    }
+  }
+
+  public java.lang.Object getFieldValue(_Fields field) {
+    switch (field) {
+    case MESSAGE:
+      return getMessage();
+
+    case STACKTRACE:
+      return getStacktrace();
+
+    }
+    throw new java.lang.IllegalStateException();
+  }
+
+  /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */
+  public boolean isSet(_Fields field) {
+    if (field == null) {
+      throw new java.lang.IllegalArgumentException();
+    }
+
+    switch (field) {
+    case MESSAGE:
+      return isSetMessage();
+    case STACKTRACE:
+      return isSetStacktrace();
+    }
+    throw new java.lang.IllegalStateException();
+  }
+
+  @Override
+  public boolean equals(java.lang.Object that) {
+    if (that == null)
+      return false;
+    if (that instanceof OperationException)
+      return this.equals((OperationException)that);
+    return false;
+  }
+
+  public boolean equals(OperationException that) {
+    if (that == null)
+      return false;
+    if (this == that)
+      return true;
+
+    boolean this_present_message = true && this.isSetMessage();
+    boolean that_present_message = true && that.isSetMessage();
+    if (this_present_message || that_present_message) {
+      if (!(this_present_message && that_present_message))
+        return false;
+      if (!this.message.equals(that.message))
+        return false;
+    }
+
+    boolean this_present_stacktrace = true && this.isSetStacktrace();
+    boolean that_present_stacktrace = true && that.isSetStacktrace();
+    if (this_present_stacktrace || that_present_stacktrace) {
+      if (!(this_present_stacktrace && that_present_stacktrace))
+        return false;
+      if (!this.stacktrace.equals(that.stacktrace))
+        return false;
+    }
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    int hashCode = 1;
+
+    hashCode = hashCode * 8191 + ((isSetMessage()) ? 131071 : 524287);
+    if (isSetMessage())
+      hashCode = hashCode * 8191 + message.hashCode();
+
+    hashCode = hashCode * 8191 + ((isSetStacktrace()) ? 131071 : 524287);
+    if (isSetStacktrace())
+      hashCode = hashCode * 8191 + stacktrace.hashCode();
+
+    return hashCode;
+  }
+
+  @Override
+  public int compareTo(OperationException other) {
+    if (!getClass().equals(other.getClass())) {
+      return getClass().getName().compareTo(other.getClass().getName());
+    }
+
+    int lastComparison = 0;
+
+    lastComparison = java.lang.Boolean.valueOf(isSetMessage()).compareTo(other.isSetMessage());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetMessage()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.message, other.message);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    lastComparison = java.lang.Boolean.valueOf(isSetStacktrace()).compareTo(other.isSetStacktrace());
+    if (lastComparison != 0) {
+      return lastComparison;
+    }
+    if (isSetStacktrace()) {
+      lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.stacktrace, other.stacktrace);
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+    }
+    return 0;
+  }
+
+  public _Fields fieldForId(int fieldId) {
+    return _Fields.findByThriftId(fieldId);
+  }
+
+  public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
+    scheme(iprot).read(iprot, this);
+  }
+
+  public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
+    scheme(oprot).write(oprot, this);
+  }
+
+  @Override
+  public java.lang.String toString() {
+    java.lang.StringBuilder sb = new java.lang.StringBuilder("OperationException(");
+    boolean first = true;
+
+    sb.append("message:");
+    if (this.message == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.message);
+    }
+    first = false;
+    if (!first) sb.append(", ");
+    sb.append("stacktrace:");
+    if (this.stacktrace == null) {
+      sb.append("null");
+    } else {
+      sb.append(this.stacktrace);
+    }
+    first = false;
+    sb.append(")");
+    return sb.toString();
+  }
+
+  public void validate() throws org.apache.thrift.TException {
+    // check for required fields
+    // check for sub-struct validity
+  }
+
+  private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
+    try {
+      write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+  private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, java.lang.ClassNotFoundException {
+    try {
+      read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in)));
+    } catch (org.apache.thrift.TException te) {
+      throw new java.io.IOException(te);
+    }
+  }
+
+  private static class OperationExceptionStandardSchemeFactory implements org.apache.thrift.scheme.SchemeFactory {
+    public OperationExceptionStandardScheme getScheme() {
+      return new OperationExceptionStandardScheme();
+    }
+  }
+
+  private static class OperationExceptionStandardScheme extends org.apache.thrift.scheme.StandardScheme<OperationException> {
+
+    public void read(org.apache.thrift.protocol.TProtocol iprot, OperationException struct) throws org.apache.thrift.TException {
+      org.apache.thrift.protocol.TField schemeField;
+      iprot.readStructBegin();
+      while (true)
+      {
+        schemeField = iprot.readFieldBegin();
+        if (schemeField.type == org.apache.thrift.protocol.TType.STOP) { 
+          break;
+        }
+        switch (schemeField.id) {
+          case 1: // MESSAGE
+            if (schemeField.type == org.apache.thrift.protocol.TType.STRING) {
+              struct.message = iprot.readString();
+              struct.setMessageIsSet(true);
+            } else { 
+              org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
+            }
+            break;
+          case 2: // STACKTRACE
+            if (schemeField.type == org.apache.thrift.protocol.TType.STRING) {
+              struct.stacktrace = iprot.readString();
+              struct.setStacktraceIsSet(true);
+            } else { 
+              org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
+            }
+            break;
+          default:
+            org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
+        }
+        iprot.readFieldEnd();
+      }
+      iprot.readStructEnd();
+
+      // check for required fields of primitive type, which can't be checked in the validate method
+      struct.validate();
+    }
+
+    public void write(org.apache.thrift.protocol.TProtocol oprot, OperationException struct) throws org.apache.thrift.TException {
+      struct.validate();
+
+      oprot.writeStructBegin(STRUCT_DESC);
+      if (struct.message != null) {
+        oprot.writeFieldBegin(MESSAGE_FIELD_DESC);
+        oprot.writeString(struct.message);
+        oprot.writeFieldEnd();
+      }
+      if (struct.stacktrace != null) {
+        oprot.writeFieldBegin(STACKTRACE_FIELD_DESC);
+        oprot.writeString(struct.stacktrace);
+        oprot.writeFieldEnd();
+      }
+      oprot.writeFieldStop();
+      oprot.writeStructEnd();
+    }
+
+  }
+
+  private static class OperationExceptionTupleSchemeFactory implements org.apache.thrift.scheme.SchemeFactory {
+    public OperationExceptionTupleScheme getScheme() {
+      return new OperationExceptionTupleScheme();
+    }
+  }
+
+  private static class OperationExceptionTupleScheme extends org.apache.thrift.scheme.TupleScheme<OperationException> {
+
+    @Override
+    public void write(org.apache.thrift.protocol.TProtocol prot, OperationException struct) throws org.apache.thrift.TException {
+      org.apache.thrift.protocol.TTupleProtocol oprot = (org.apache.thrift.protocol.TTupleProtocol) prot;
+      java.util.BitSet optionals = new java.util.BitSet();
+      if (struct.isSetMessage()) {
+        optionals.set(0);
+      }
+      if (struct.isSetStacktrace()) {
+        optionals.set(1);
+      }
+      oprot.writeBitSet(optionals, 2);
+      if (struct.isSetMessage()) {
+        oprot.writeString(struct.message);
+      }
+      if (struct.isSetStacktrace()) {
+        oprot.writeString(struct.stacktrace);
+      }
+    }
+
+    @Override
+    public void read(org.apache.thrift.protocol.TProtocol prot, OperationException struct) throws org.apache.thrift.TException {
+      org.apache.thrift.protocol.TTupleProtocol iprot = (org.apache.thrift.protocol.TTupleProtocol) prot;
+      java.util.BitSet incoming = iprot.readBitSet(2);
+      if (incoming.get(0)) {
+        struct.message = iprot.readString();
+        struct.setMessageIsSet(true);
+      }
+      if (incoming.get(1)) {
+        struct.stacktrace = iprot.readString();
+        struct.setStacktraceIsSet(true);
+      }
+    }
+  }
+
+  private static <S extends org.apache.thrift.scheme.IScheme> S scheme(org.apache.thrift.protocol.TProtocol proto) {
+    return (org.apache.thrift.scheme.StandardScheme.class.equals(proto.getScheme()) ? STANDARD_SCHEME_FACTORY : TUPLE_SCHEME_FACTORY).getScheme();
+  }
+}
+
diff --git a/airavata-kubernetes/modules/agents/thrift-agent/src/main/java/org/apache/airavata/agents/thrift/stubs/OperationService.java b/airavata-kubernetes/modules/agents/thrift-agent/src/main/java/org/apache/airavata/agents/thrift/stubs/OperationService.java
new file mode 100644
index 0000000..467daac
--- /dev/null
+++ b/airavata-kubernetes/modules/agents/thrift-agent/src/main/java/org/apache/airavata/agents/thrift/stubs/OperationService.java
@@ -0,0 +1,1072 @@
+/**
+ * Autogenerated by Thrift Compiler (0.10.0)
+ *
+ * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+ *  @generated
+ */
+package org.apache.airavata.agents.thrift.stubs;
+
+@SuppressWarnings({"cast", "rawtypes", "serial", "unchecked", "unused"})
+@javax.annotation.Generated(value = "Autogenerated by Thrift Compiler (0.10.0)", date = "2017-12-02")
+public class OperationService {
+
+  public interface Iface {
+
+    public void executeCommand(java.lang.String command, long callbackWorkflowId) throws OperationException, org.apache.thrift.TException;
+
+  }
+
+  public interface AsyncIface {
+
+    public void executeCommand(java.lang.String command, long callbackWorkflowId, org.apache.thrift.async.AsyncMethodCallback<Void> resultHandler) throws org.apache.thrift.TException;
+
+  }
+
+  public static class Client extends org.apache.thrift.TServiceClient implements Iface {
+    public static class Factory implements org.apache.thrift.TServiceClientFactory<Client> {
+      public Factory() {}
+      public Client getClient(org.apache.thrift.protocol.TProtocol prot) {
+        return new Client(prot);
+      }
+      public Client getClient(org.apache.thrift.protocol.TProtocol iprot, org.apache.thrift.protocol.TProtocol oprot) {
+        return new Client(iprot, oprot);
+      }
+    }
+
+    public Client(org.apache.thrift.protocol.TProtocol prot)
+    {
+      super(prot, prot);
+    }
+
+    public Client(org.apache.thrift.protocol.TProtocol iprot, org.apache.thrift.protocol.TProtocol oprot) {
+      super(iprot, oprot);
+    }
+
+    public void executeCommand(java.lang.String command, long callbackWorkflowId) throws OperationException, org.apache.thrift.TException
+    {
+      send_executeCommand(command, callbackWorkflowId);
+      recv_executeCommand();
+    }
+
+    public void send_executeCommand(java.lang.String command, long callbackWorkflowId) throws org.apache.thrift.TException
+    {
+      executeCommand_args args = new executeCommand_args();
+      args.setCommand(command);
+      args.setCallbackWorkflowId(callbackWorkflowId);
+      sendBase("executeCommand", args);
+    }
+
+    public void recv_executeCommand() throws OperationException, org.apache.thrift.TException
+    {
+      executeCommand_result result = new executeCommand_result();
+      receiveBase(result, "executeCommand");
+      if (result.ex != null) {
+        throw result.ex;
+      }
+      return;
+    }
+
+  }
+  public static class AsyncClient extends org.apache.thrift.async.TAsyncClient implements AsyncIface {
+    public static class Factory implements org.apache.thrift.async.TAsyncClientFactory<AsyncClient> {
+      private org.apache.thrift.async.TAsyncClientManager clientManager;
+      private org.apache.thrift.protocol.TProtocolFactory protocolFactory;
+      public Factory(org.apache.thrift.async.TAsyncClientManager clientManager, org.apache.thrift.protocol.TProtocolFactory protocolFactory) {
+        this.clientManager = clientManager;
+        this.protocolFactory = protocolFactory;
+      }
+      public AsyncClient getAsyncClient(org.apache.thrift.transport.TNonblockingTransport transport) {
+        return new AsyncClient(protocolFactory, clientManager, transport);
+      }
+    }
+
+    public AsyncClient(org.apache.thrift.protocol.TProtocolFactory protocolFactory, org.apache.thrift.async.TAsyncClientManager clientManager, org.apache.thrift.transport.TNonblockingTransport transport) {
+      super(protocolFactory, clientManager, transport);
+    }
+
+    public void executeCommand(java.lang.String command, long callbackWorkflowId, org.apache.thrift.async.AsyncMethodCallback<Void> resultHandler) throws org.apache.thrift.TException {
+      checkReady();
+      executeCommand_call method_call = new executeCommand_call(command, callbackWorkflowId, resultHandler, this, ___protocolFactory, ___transport);
+      this.___currentMethod = method_call;
+      ___manager.call(method_call);
+    }
+
+    public static class executeCommand_call extends org.apache.thrift.async.TAsyncMethodCall<Void> {
+      private java.lang.String command;
+      private long callbackWorkflowId;
+      public executeCommand_call(java.lang.String command, long callbackWorkflowId, org.apache.thrift.async.AsyncMethodCallback<Void> resultHandler, org.apache.thrift.async.TAsyncClient client, org.apache.thrift.protocol.TProtocolFactory protocolFactory, org.apache.thrift.transport.TNonblockingTransport transport) throws org.apache.thrift.TException {
+        super(client, protocolFactory, transport, resultHandler, false);
+        this.command = command;
+        this.callbackWorkflowId = callbackWorkflowId;
+      }
+
+      public void write_args(org.apache.thrift.protocol.TProtocol prot) throws org.apache.thrift.TException {
+        prot.writeMessageBegin(new org.apache.thrift.protocol.TMessage("executeCommand", org.apache.thrift.protocol.TMessageType.CALL, 0));
+        executeCommand_args args = new executeCommand_args();
+        args.setCommand(command);
+        args.setCallbackWorkflowId(callbackWorkflowId);
+        args.write(prot);
+        prot.writeMessageEnd();
+      }
+
+      public Void getResult() throws OperationException, org.apache.thrift.TException {
+        if (getState() != org.apache.thrift.async.TAsyncMethodCall.State.RESPONSE_READ) {
+          throw new java.lang.IllegalStateException("Method call not finished!");
+        }
+        org.apache.thrift.transport.TMemoryInputTransport memoryTransport = new org.apache.thrift.transport.TMemoryInputTransport(getFrameBuffer().array());
+        org.apache.thrift.protocol.TProtocol prot = client.getProtocolFactory().getProtocol(memoryTransport);
+        return null;
+      }
+    }
+
+  }
+
+  public static class Processor<I extends Iface> extends org.apache.thrift.TBaseProcessor<I> implements org.apache.thrift.TProcessor {
+    private static final org.slf4j.Logger _LOGGER = org.slf4j.LoggerFactory.getLogger(Processor.class.getName());
+    public Processor(I iface) {
+      super(iface, getProcessMap(new java.util.HashMap<java.lang.String, org.apache.thrift.ProcessFunction<I, ? extends org.apache.thrift.TBase>>()));
+    }
+
+    protected Processor(I iface, java.util.Map<java.lang.String, org.apache.thrift.ProcessFunction<I, ? extends org.apache.thrift.TBase>> processMap) {
+      super(iface, getProcessMap(processMap));
+    }
+
+    private static <I extends Iface> java.util.Map<java.lang.String,  org.apache.thrift.ProcessFunction<I, ? extends org.apache.thrift.TBase>> getProcessMap(java.util.Map<java.lang.String, org.apache.thrift.ProcessFunction<I, ? extends  org.apache.thrift.TBase>> processMap) {
+      processMap.put("executeCommand", new executeCommand());
+      return processMap;
+    }
+
+    public static class executeCommand<I extends Iface> extends org.apache.thrift.ProcessFunction<I, executeCommand_args> {
+      public executeCommand() {
+        super("executeCommand");
+      }
+
+      public executeCommand_args getEmptyArgsInstance() {
+        return new executeCommand_args();
+      }
+
+      protected boolean isOneway() {
+        return false;
+      }
+
+      public executeCommand_result getResult(I iface, executeCommand_args args) throws org.apache.thrift.TException {
+        executeCommand_result result = new executeCommand_result();
+        try {
+          iface.executeCommand(args.command, args.callbackWorkflowId);
+        } catch (OperationException ex) {
+          result.ex = ex;
+        }
+        return result;
+      }
+    }
+
+  }
+
+  public static class AsyncProcessor<I extends AsyncIface> extends org.apache.thrift.TBaseAsyncProcessor<I> {
+    private static final org.slf4j.Logger _LOGGER = org.slf4j.LoggerFactory.getLogger(AsyncProcessor.class.getName());
+    public AsyncProcessor(I iface) {
+      super(iface, getProcessMap(new java.util.HashMap<java.lang.String, org.apache.thrift.AsyncProcessFunction<I, ? extends org.apache.thrift.TBase, ?>>()));
+    }
+
+    protected AsyncProcessor(I iface, java.util.Map<java.lang.String,  org.apache.thrift.AsyncProcessFunction<I, ? extends  org.apache.thrift.TBase, ?>> processMap) {
+      super(iface, getProcessMap(processMap));
+    }
+
+    private static <I extends AsyncIface> java.util.Map<java.lang.String,  org.apache.thrift.AsyncProcessFunction<I, ? extends  org.apache.thrift.TBase,?>> getProcessMap(java.util.Map<java.lang.String,  org.apache.thrift.AsyncProcessFunction<I, ? extends  org.apache.thrift.TBase, ?>> processMap) {
+      processMap.put("executeCommand", new executeCommand());
+      return processMap;
+    }
+
+    public static class executeCommand<I extends AsyncIface> extends org.apache.thrift.AsyncProcessFunction<I, executeCommand_args, Void> {
+      public executeCommand() {
+        super("executeCommand");
+      }
+
+      public executeCommand_args getEmptyArgsInstance() {
+        return new executeCommand_args();
+      }
+
+      public org.apache.thrift.async.AsyncMethodCallback<Void> getResultHandler(final org.apache.thrift.server.AbstractNonblockingServer.AsyncFrameBuffer fb, final int seqid) {
+        final org.apache.thrift.AsyncProcessFunction fcall = this;
+        return new org.apache.thrift.async.AsyncMethodCallback<Void>() { 
+          public void onComplete(Void o) {
+            executeCommand_result result = new executeCommand_result();
+            try {
+              fcall.sendResponse(fb, result, org.apache.thrift.protocol.TMessageType.REPLY,seqid);
+            } catch (org.apache.thrift.transport.TTransportException e) {
+              _LOGGER.error("TTransportException writing to internal frame buffer", e);
+              fb.close();
+            } catch (java.lang.Exception e) {
+              _LOGGER.error("Exception writing to internal frame buffer", e);
+              onError(e);
+            }
+          }
+          public void onError(java.lang.Exception e) {
+            byte msgType = org.apache.thrift.protocol.TMessageType.REPLY;
+            org.apache.thrift.TSerializable msg;
+            executeCommand_result result = new executeCommand_result();
+            if (e instanceof OperationException) {
+              result.ex = (OperationException) e;
+              result.setExIsSet(true);
+              msg = result;
+            } else if (e instanceof org.apache.thrift.transport.TTransportException) {
+              _LOGGER.error("TTransportException inside handler", e);
+              fb.close();
+              return;
+            } else if (e instanceof org.apache.thrift.TApplicationException) {
+              _LOGGER.error("TApplicationException inside handler", e);
+              msgType = org.apache.thrift.protocol.TMessageType.EXCEPTION;
+              msg = (org.apache.thrift.TApplicationException)e;
+            } else {
+              _LOGGER.error("Exception inside handler", e);
+              msgType = org.apache.thrift.protocol.TMessageType.EXCEPTION;
+              msg = new org.apache.thrift.TApplicationException(org.apache.thrift.TApplicationException.INTERNAL_ERROR, e.getMessage());
+            }
+            try {
+              fcall.sendResponse(fb,msg,msgType,seqid);
+            } catch (java.lang.Exception ex) {
+              _LOGGER.error("Exception writing to internal frame buffer", ex);
+              fb.close();
+            }
+          }
+        };
+      }
+
+      protected boolean isOneway() {
+        return false;
+      }
+
+      public void start(I iface, executeCommand_args args, org.apache.thrift.async.AsyncMethodCallback<Void> resultHandler) throws org.apache.thrift.TException {
+        iface.executeCommand(args.command, args.callbackWorkflowId,resultHandler);
+      }
+    }
+
+  }
+
+  public static class executeCommand_args implements org.apache.thrift.TBase<executeCommand_args, executeCommand_args._Fields>, java.io.Serializable, Cloneable, Comparable<executeCommand_args>   {
+    private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("executeCommand_args");
+
+    private static final org.apache.thrift.protocol.TField COMMAND_FIELD_DESC = new org.apache.thrift.protocol.TField("command", org.apache.thrift.protocol.TType.STRING, (short)1);
+    private static final org.apache.thrift.protocol.TField CALLBACK_WORKFLOW_ID_FIELD_DESC = new org.apache.thrift.protocol.TField("callbackWorkflowId", org.apache.thrift.protocol.TType.I64, (short)2);
+
+    private static final org.apache.thrift.scheme.SchemeFactory STANDARD_SCHEME_FACTORY = new executeCommand_argsStandardSchemeFactory();
+    private static final org.apache.thrift.scheme.SchemeFactory TUPLE_SCHEME_FACTORY = new executeCommand_argsTupleSchemeFactory();
+
+    public java.lang.String command; // required
+    public long callbackWorkflowId; // required
+
+    /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
+    public enum _Fields implements org.apache.thrift.TFieldIdEnum {
+      COMMAND((short)1, "command"),
+      CALLBACK_WORKFLOW_ID((short)2, "callbackWorkflowId");
+
+      private static final java.util.Map<java.lang.String, _Fields> byName = new java.util.HashMap<java.lang.String, _Fields>();
+
+      static {
+        for (_Fields field : java.util.EnumSet.allOf(_Fields.class)) {
+          byName.put(field.getFieldName(), field);
+        }
+      }
+
+      /**
+       * Find the _Fields constant that matches fieldId, or null if its not found.
+       */
+      public static _Fields findByThriftId(int fieldId) {
+        switch(fieldId) {
+          case 1: // COMMAND
+            return COMMAND;
+          case 2: // CALLBACK_WORKFLOW_ID
+            return CALLBACK_WORKFLOW_ID;
+          default:
+            return null;
+        }
+      }
+
+      /**
+       * Find the _Fields constant that matches fieldId, throwing an exception
+       * if it is not found.
+       */
+      public static _Fields findByThriftIdOrThrow(int fieldId) {
+        _Fields fields = findByThriftId(fieldId);
+        if (fields == null) throw new java.lang.IllegalArgumentException("Field " + fieldId + " doesn't exist!");
+        return fields;
+      }
+
+      /**
+       * Find the _Fields constant that matches name, or null if its not found.
+       */
+      public static _Fields findByName(java.lang.String name) {
+        return byName.get(name);
+      }
+
+      private final short _thriftId;
+      private final java.lang.String _fieldName;
+
+      _Fields(short thriftId, java.lang.String fieldName) {
+        _thriftId = thriftId;
+        _fieldName = fieldName;
+      }
+
+      public short getThriftFieldId() {
+        return _thriftId;
+      }
+
+      public java.lang.String getFieldName() {
+        return _fieldName;
+      }
+    }
+
+    // isset id assignments
+    private static final int __CALLBACKWORKFLOWID_ISSET_ID = 0;
+    private byte __isset_bitfield = 0;
+    public static final java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
+    static {
+      java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new java.util.EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class);
+      tmpMap.put(_Fields.COMMAND, new org.apache.thrift.meta_data.FieldMetaData("command", org.apache.thrift.TFieldRequirementType.DEFAULT, 
+          new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.STRING)));
+      tmpMap.put(_Fields.CALLBACK_WORKFLOW_ID, new org.apache.thrift.meta_data.FieldMetaData("callbackWorkflowId", org.apache.thrift.TFieldRequirementType.DEFAULT, 
+          new org.apache.thrift.meta_data.FieldValueMetaData(org.apache.thrift.protocol.TType.I64)));
+      metaDataMap = java.util.Collections.unmodifiableMap(tmpMap);
+      org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(executeCommand_args.class, metaDataMap);
+    }
+
+    public executeCommand_args() {
+    }
+
+    public executeCommand_args(
+      java.lang.String command,
+      long callbackWorkflowId)
+    {
+      this();
+      this.command = command;
+      this.callbackWorkflowId = callbackWorkflowId;
+      setCallbackWorkflowIdIsSet(true);
+    }
+
+    /**
+     * Performs a deep copy on <i>other</i>.
+     */
+    public executeCommand_args(executeCommand_args other) {
+      __isset_bitfield = other.__isset_bitfield;
+      if (other.isSetCommand()) {
+        this.command = other.command;
+      }
+      this.callbackWorkflowId = other.callbackWorkflowId;
+    }
+
+    public executeCommand_args deepCopy() {
+      return new executeCommand_args(this);
+    }
+
+    @Override
+    public void clear() {
+      this.command = null;
+      setCallbackWorkflowIdIsSet(false);
+      this.callbackWorkflowId = 0;
+    }
+
+    public java.lang.String getCommand() {
+      return this.command;
+    }
+
+    public executeCommand_args setCommand(java.lang.String command) {
+      this.command = command;
+      return this;
+    }
+
+    public void unsetCommand() {
+      this.command = null;
+    }
+
+    /** Returns true if field command is set (has been assigned a value) and false otherwise */
+    public boolean isSetCommand() {
+      return this.command != null;
+    }
+
+    public void setCommandIsSet(boolean value) {
+      if (!value) {
+        this.command = null;
+      }
+    }
+
+    public long getCallbackWorkflowId() {
+      return this.callbackWorkflowId;
+    }
+
+    public executeCommand_args setCallbackWorkflowId(long callbackWorkflowId) {
+      this.callbackWorkflowId = callbackWorkflowId;
+      setCallbackWorkflowIdIsSet(true);
+      return this;
+    }
+
+    public void unsetCallbackWorkflowId() {
+      __isset_bitfield = org.apache.thrift.EncodingUtils.clearBit(__isset_bitfield, __CALLBACKWORKFLOWID_ISSET_ID);
+    }
+
+    /** Returns true if field callbackWorkflowId is set (has been assigned a value) and false otherwise */
+    public boolean isSetCallbackWorkflowId() {
+      return org.apache.thrift.EncodingUtils.testBit(__isset_bitfield, __CALLBACKWORKFLOWID_ISSET_ID);
+    }
+
+    public void setCallbackWorkflowIdIsSet(boolean value) {
+      __isset_bitfield = org.apache.thrift.EncodingUtils.setBit(__isset_bitfield, __CALLBACKWORKFLOWID_ISSET_ID, value);
+    }
+
+    public void setFieldValue(_Fields field, java.lang.Object value) {
+      switch (field) {
+      case COMMAND:
+        if (value == null) {
+          unsetCommand();
+        } else {
+          setCommand((java.lang.String)value);
+        }
+        break;
+
+      case CALLBACK_WORKFLOW_ID:
+        if (value == null) {
+          unsetCallbackWorkflowId();
+        } else {
+          setCallbackWorkflowId((java.lang.Long)value);
+        }
+        break;
+
+      }
+    }
+
+    public java.lang.Object getFieldValue(_Fields field) {
+      switch (field) {
+      case COMMAND:
+        return getCommand();
+
+      case CALLBACK_WORKFLOW_ID:
+        return getCallbackWorkflowId();
+
+      }
+      throw new java.lang.IllegalStateException();
+    }
+
+    /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */
+    public boolean isSet(_Fields field) {
+      if (field == null) {
+        throw new java.lang.IllegalArgumentException();
+      }
+
+      switch (field) {
+      case COMMAND:
+        return isSetCommand();
+      case CALLBACK_WORKFLOW_ID:
+        return isSetCallbackWorkflowId();
+      }
+      throw new java.lang.IllegalStateException();
+    }
+
+    @Override
+    public boolean equals(java.lang.Object that) {
+      if (that == null)
+        return false;
+      if (that instanceof executeCommand_args)
+        return this.equals((executeCommand_args)that);
+      return false;
+    }
+
+    public boolean equals(executeCommand_args that) {
+      if (that == null)
+        return false;
+      if (this == that)
+        return true;
+
+      boolean this_present_command = true && this.isSetCommand();
+      boolean that_present_command = true && that.isSetCommand();
+      if (this_present_command || that_present_command) {
+        if (!(this_present_command && that_present_command))
+          return false;
+        if (!this.command.equals(that.command))
+          return false;
+      }
+
+      boolean this_present_callbackWorkflowId = true;
+      boolean that_present_callbackWorkflowId = true;
+      if (this_present_callbackWorkflowId || that_present_callbackWorkflowId) {
+        if (!(this_present_callbackWorkflowId && that_present_callbackWorkflowId))
+          return false;
+        if (this.callbackWorkflowId != that.callbackWorkflowId)
+          return false;
+      }
+
+      return true;
+    }
+
+    @Override
+    public int hashCode() {
+      int hashCode = 1;
+
+      hashCode = hashCode * 8191 + ((isSetCommand()) ? 131071 : 524287);
+      if (isSetCommand())
+        hashCode = hashCode * 8191 + command.hashCode();
+
+      hashCode = hashCode * 8191 + org.apache.thrift.TBaseHelper.hashCode(callbackWorkflowId);
+
+      return hashCode;
+    }
+
+    @Override
+    public int compareTo(executeCommand_args other) {
+      if (!getClass().equals(other.getClass())) {
+        return getClass().getName().compareTo(other.getClass().getName());
+      }
+
+      int lastComparison = 0;
+
+      lastComparison = java.lang.Boolean.valueOf(isSetCommand()).compareTo(other.isSetCommand());
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+      if (isSetCommand()) {
+        lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.command, other.command);
+        if (lastComparison != 0) {
+          return lastComparison;
+        }
+      }
+      lastComparison = java.lang.Boolean.valueOf(isSetCallbackWorkflowId()).compareTo(other.isSetCallbackWorkflowId());
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+      if (isSetCallbackWorkflowId()) {
+        lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.callbackWorkflowId, other.callbackWorkflowId);
+        if (lastComparison != 0) {
+          return lastComparison;
+        }
+      }
+      return 0;
+    }
+
+    public _Fields fieldForId(int fieldId) {
+      return _Fields.findByThriftId(fieldId);
+    }
+
+    public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
+      scheme(iprot).read(iprot, this);
+    }
+
+    public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
+      scheme(oprot).write(oprot, this);
+    }
+
+    @Override
+    public java.lang.String toString() {
+      java.lang.StringBuilder sb = new java.lang.StringBuilder("executeCommand_args(");
+      boolean first = true;
+
+      sb.append("command:");
+      if (this.command == null) {
+        sb.append("null");
+      } else {
+        sb.append(this.command);
+      }
+      first = false;
+      if (!first) sb.append(", ");
+      sb.append("callbackWorkflowId:");
+      sb.append(this.callbackWorkflowId);
+      first = false;
+      sb.append(")");
+      return sb.toString();
+    }
+
+    public void validate() throws org.apache.thrift.TException {
+      // check for required fields
+      // check for sub-struct validity
+    }
+
+    private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
+      try {
+        write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out)));
+      } catch (org.apache.thrift.TException te) {
+        throw new java.io.IOException(te);
+      }
+    }
+
+    private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, java.lang.ClassNotFoundException {
+      try {
+        // it doesn't seem like you should have to do this, but java serialization is wacky, and doesn't call the default constructor.
+        __isset_bitfield = 0;
+        read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in)));
+      } catch (org.apache.thrift.TException te) {
+        throw new java.io.IOException(te);
+      }
+    }
+
+    private static class executeCommand_argsStandardSchemeFactory implements org.apache.thrift.scheme.SchemeFactory {
+      public executeCommand_argsStandardScheme getScheme() {
+        return new executeCommand_argsStandardScheme();
+      }
+    }
+
+    private static class executeCommand_argsStandardScheme extends org.apache.thrift.scheme.StandardScheme<executeCommand_args> {
+
+      public void read(org.apache.thrift.protocol.TProtocol iprot, executeCommand_args struct) throws org.apache.thrift.TException {
+        org.apache.thrift.protocol.TField schemeField;
+        iprot.readStructBegin();
+        while (true)
+        {
+          schemeField = iprot.readFieldBegin();
+          if (schemeField.type == org.apache.thrift.protocol.TType.STOP) { 
+            break;
+          }
+          switch (schemeField.id) {
+            case 1: // COMMAND
+              if (schemeField.type == org.apache.thrift.protocol.TType.STRING) {
+                struct.command = iprot.readString();
+                struct.setCommandIsSet(true);
+              } else { 
+                org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
+              }
+              break;
+            case 2: // CALLBACK_WORKFLOW_ID
+              if (schemeField.type == org.apache.thrift.protocol.TType.I64) {
+                struct.callbackWorkflowId = iprot.readI64();
+                struct.setCallbackWorkflowIdIsSet(true);
+              } else { 
+                org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
+              }
+              break;
+            default:
+              org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
+          }
+          iprot.readFieldEnd();
+        }
+        iprot.readStructEnd();
+
+        // check for required fields of primitive type, which can't be checked in the validate method
+        struct.validate();
+      }
+
+      public void write(org.apache.thrift.protocol.TProtocol oprot, executeCommand_args struct) throws org.apache.thrift.TException {
+        struct.validate();
+
+        oprot.writeStructBegin(STRUCT_DESC);
+        if (struct.command != null) {
+          oprot.writeFieldBegin(COMMAND_FIELD_DESC);
+          oprot.writeString(struct.command);
+          oprot.writeFieldEnd();
+        }
+        oprot.writeFieldBegin(CALLBACK_WORKFLOW_ID_FIELD_DESC);
+        oprot.writeI64(struct.callbackWorkflowId);
+        oprot.writeFieldEnd();
+        oprot.writeFieldStop();
+        oprot.writeStructEnd();
+      }
+
+    }
+
+    private static class executeCommand_argsTupleSchemeFactory implements org.apache.thrift.scheme.SchemeFactory {
+      public executeCommand_argsTupleScheme getScheme() {
+        return new executeCommand_argsTupleScheme();
+      }
+    }
+
+    private static class executeCommand_argsTupleScheme extends org.apache.thrift.scheme.TupleScheme<executeCommand_args> {
+
+      @Override
+      public void write(org.apache.thrift.protocol.TProtocol prot, executeCommand_args struct) throws org.apache.thrift.TException {
+        org.apache.thrift.protocol.TTupleProtocol oprot = (org.apache.thrift.protocol.TTupleProtocol) prot;
+        java.util.BitSet optionals = new java.util.BitSet();
+        if (struct.isSetCommand()) {
+          optionals.set(0);
+        }
+        if (struct.isSetCallbackWorkflowId()) {
+          optionals.set(1);
+        }
+        oprot.writeBitSet(optionals, 2);
+        if (struct.isSetCommand()) {
+          oprot.writeString(struct.command);
+        }
+        if (struct.isSetCallbackWorkflowId()) {
+          oprot.writeI64(struct.callbackWorkflowId);
+        }
+      }
+
+      @Override
+      public void read(org.apache.thrift.protocol.TProtocol prot, executeCommand_args struct) throws org.apache.thrift.TException {
+        org.apache.thrift.protocol.TTupleProtocol iprot = (org.apache.thrift.protocol.TTupleProtocol) prot;
+        java.util.BitSet incoming = iprot.readBitSet(2);
+        if (incoming.get(0)) {
+          struct.command = iprot.readString();
+          struct.setCommandIsSet(true);
+        }
+        if (incoming.get(1)) {
+          struct.callbackWorkflowId = iprot.readI64();
+          struct.setCallbackWorkflowIdIsSet(true);
+        }
+      }
+    }
+
+    private static <S extends org.apache.thrift.scheme.IScheme> S scheme(org.apache.thrift.protocol.TProtocol proto) {
+      return (org.apache.thrift.scheme.StandardScheme.class.equals(proto.getScheme()) ? STANDARD_SCHEME_FACTORY : TUPLE_SCHEME_FACTORY).getScheme();
+    }
+  }
+
+  public static class executeCommand_result implements org.apache.thrift.TBase<executeCommand_result, executeCommand_result._Fields>, java.io.Serializable, Cloneable, Comparable<executeCommand_result>   {
+    private static final org.apache.thrift.protocol.TStruct STRUCT_DESC = new org.apache.thrift.protocol.TStruct("executeCommand_result");
+
+    private static final org.apache.thrift.protocol.TField EX_FIELD_DESC = new org.apache.thrift.protocol.TField("ex", org.apache.thrift.protocol.TType.STRUCT, (short)1);
+
+    private static final org.apache.thrift.scheme.SchemeFactory STANDARD_SCHEME_FACTORY = new executeCommand_resultStandardSchemeFactory();
+    private static final org.apache.thrift.scheme.SchemeFactory TUPLE_SCHEME_FACTORY = new executeCommand_resultTupleSchemeFactory();
+
+    public OperationException ex; // required
+
+    /** The set of fields this struct contains, along with convenience methods for finding and manipulating them. */
+    public enum _Fields implements org.apache.thrift.TFieldIdEnum {
+      EX((short)1, "ex");
+
+      private static final java.util.Map<java.lang.String, _Fields> byName = new java.util.HashMap<java.lang.String, _Fields>();
+
+      static {
+        for (_Fields field : java.util.EnumSet.allOf(_Fields.class)) {
+          byName.put(field.getFieldName(), field);
+        }
+      }
+
+      /**
+       * Find the _Fields constant that matches fieldId, or null if its not found.
+       */
+      public static _Fields findByThriftId(int fieldId) {
+        switch(fieldId) {
+          case 1: // EX
+            return EX;
+          default:
+            return null;
+        }
+      }
+
+      /**
+       * Find the _Fields constant that matches fieldId, throwing an exception
+       * if it is not found.
+       */
+      public static _Fields findByThriftIdOrThrow(int fieldId) {
+        _Fields fields = findByThriftId(fieldId);
+        if (fields == null) throw new java.lang.IllegalArgumentException("Field " + fieldId + " doesn't exist!");
+        return fields;
+      }
+
+      /**
+       * Find the _Fields constant that matches name, or null if its not found.
+       */
+      public static _Fields findByName(java.lang.String name) {
+        return byName.get(name);
+      }
+
+      private final short _thriftId;
+      private final java.lang.String _fieldName;
+
+      _Fields(short thriftId, java.lang.String fieldName) {
+        _thriftId = thriftId;
+        _fieldName = fieldName;
+      }
+
+      public short getThriftFieldId() {
+        return _thriftId;
+      }
+
+      public java.lang.String getFieldName() {
+        return _fieldName;
+      }
+    }
+
+    // isset id assignments
+    public static final java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> metaDataMap;
+    static {
+      java.util.Map<_Fields, org.apache.thrift.meta_data.FieldMetaData> tmpMap = new java.util.EnumMap<_Fields, org.apache.thrift.meta_data.FieldMetaData>(_Fields.class);
+      tmpMap.put(_Fields.EX, new org.apache.thrift.meta_data.FieldMetaData("ex", org.apache.thrift.TFieldRequirementType.DEFAULT, 
+          new org.apache.thrift.meta_data.StructMetaData(org.apache.thrift.protocol.TType.STRUCT, OperationException.class)));
+      metaDataMap = java.util.Collections.unmodifiableMap(tmpMap);
+      org.apache.thrift.meta_data.FieldMetaData.addStructMetaDataMap(executeCommand_result.class, metaDataMap);
+    }
+
+    public executeCommand_result() {
+    }
+
+    public executeCommand_result(
+      OperationException ex)
+    {
+      this();
+      this.ex = ex;
+    }
+
+    /**
+     * Performs a deep copy on <i>other</i>.
+     */
+    public executeCommand_result(executeCommand_result other) {
+      if (other.isSetEx()) {
+        this.ex = new OperationException(other.ex);
+      }
+    }
+
+    public executeCommand_result deepCopy() {
+      return new executeCommand_result(this);
+    }
+
+    @Override
+    public void clear() {
+      this.ex = null;
+    }
+
+    public OperationException getEx() {
+      return this.ex;
+    }
+
+    public executeCommand_result setEx(OperationException ex) {
+      this.ex = ex;
+      return this;
+    }
+
+    public void unsetEx() {
+      this.ex = null;
+    }
+
+    /** Returns true if field ex is set (has been assigned a value) and false otherwise */
+    public boolean isSetEx() {
+      return this.ex != null;
+    }
+
+    public void setExIsSet(boolean value) {
+      if (!value) {
+        this.ex = null;
+      }
+    }
+
+    public void setFieldValue(_Fields field, java.lang.Object value) {
+      switch (field) {
+      case EX:
+        if (value == null) {
+          unsetEx();
+        } else {
+          setEx((OperationException)value);
+        }
+        break;
+
+      }
+    }
+
+    public java.lang.Object getFieldValue(_Fields field) {
+      switch (field) {
+      case EX:
+        return getEx();
+
+      }
+      throw new java.lang.IllegalStateException();
+    }
+
+    /** Returns true if field corresponding to fieldID is set (has been assigned a value) and false otherwise */
+    public boolean isSet(_Fields field) {
+      if (field == null) {
+        throw new java.lang.IllegalArgumentException();
+      }
+
+      switch (field) {
+      case EX:
+        return isSetEx();
+      }
+      throw new java.lang.IllegalStateException();
+    }
+
+    @Override
+    public boolean equals(java.lang.Object that) {
+      if (that == null)
+        return false;
+      if (that instanceof executeCommand_result)
+        return this.equals((executeCommand_result)that);
+      return false;
+    }
+
+    public boolean equals(executeCommand_result that) {
+      if (that == null)
+        return false;
+      if (this == that)
+        return true;
+
+      boolean this_present_ex = true && this.isSetEx();
+      boolean that_present_ex = true && that.isSetEx();
+      if (this_present_ex || that_present_ex) {
+        if (!(this_present_ex && that_present_ex))
+          return false;
+        if (!this.ex.equals(that.ex))
+          return false;
+      }
+
+      return true;
+    }
+
+    @Override
+    public int hashCode() {
+      int hashCode = 1;
+
+      hashCode = hashCode * 8191 + ((isSetEx()) ? 131071 : 524287);
+      if (isSetEx())
+        hashCode = hashCode * 8191 + ex.hashCode();
+
+      return hashCode;
+    }
+
+    @Override
+    public int compareTo(executeCommand_result other) {
+      if (!getClass().equals(other.getClass())) {
+        return getClass().getName().compareTo(other.getClass().getName());
+      }
+
+      int lastComparison = 0;
+
+      lastComparison = java.lang.Boolean.valueOf(isSetEx()).compareTo(other.isSetEx());
+      if (lastComparison != 0) {
+        return lastComparison;
+      }
+      if (isSetEx()) {
+        lastComparison = org.apache.thrift.TBaseHelper.compareTo(this.ex, other.ex);
+        if (lastComparison != 0) {
+          return lastComparison;
+        }
+      }
+      return 0;
+    }
+
+    public _Fields fieldForId(int fieldId) {
+      return _Fields.findByThriftId(fieldId);
+    }
+
+    public void read(org.apache.thrift.protocol.TProtocol iprot) throws org.apache.thrift.TException {
+      scheme(iprot).read(iprot, this);
+    }
+
+    public void write(org.apache.thrift.protocol.TProtocol oprot) throws org.apache.thrift.TException {
+      scheme(oprot).write(oprot, this);
+      }
+
+    @Override
+    public java.lang.String toString() {
+      java.lang.StringBuilder sb = new java.lang.StringBuilder("executeCommand_result(");
+      boolean first = true;
+
+      sb.append("ex:");
+      if (this.ex == null) {
+        sb.append("null");
+      } else {
+        sb.append(this.ex);
+      }
+      first = false;
+      sb.append(")");
+      return sb.toString();
+    }
+
+    public void validate() throws org.apache.thrift.TException {
+      // check for required fields
+      // check for sub-struct validity
+    }
+
+    private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
+      try {
+        write(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(out)));
+      } catch (org.apache.thrift.TException te) {
+        throw new java.io.IOException(te);
+      }
+    }
+
+    private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, java.lang.ClassNotFoundException {
+      try {
+        read(new org.apache.thrift.protocol.TCompactProtocol(new org.apache.thrift.transport.TIOStreamTransport(in)));
+      } catch (org.apache.thrift.TException te) {
+        throw new java.io.IOException(te);
+      }
+    }
+
+    private static class executeCommand_resultStandardSchemeFactory implements org.apache.thrift.scheme.SchemeFactory {
+      public executeCommand_resultStandardScheme getScheme() {
+        return new executeCommand_resultStandardScheme();
+      }
+    }
+
+    private static class executeCommand_resultStandardScheme extends org.apache.thrift.scheme.StandardScheme<executeCommand_result> {
+
+      public void read(org.apache.thrift.protocol.TProtocol iprot, executeCommand_result struct) throws org.apache.thrift.TException {
+        org.apache.thrift.protocol.TField schemeField;
+        iprot.readStructBegin();
+        while (true)
+        {
+          schemeField = iprot.readFieldBegin();
+          if (schemeField.type == org.apache.thrift.protocol.TType.STOP) { 
+            break;
+          }
+          switch (schemeField.id) {
+            case 1: // EX
+              if (schemeField.type == org.apache.thrift.protocol.TType.STRUCT) {
+                struct.ex = new OperationException();
+                struct.ex.read(iprot);
+                struct.setExIsSet(true);
+              } else { 
+                org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
+              }
+              break;
+            default:
+              org.apache.thrift.protocol.TProtocolUtil.skip(iprot, schemeField.type);
+          }
+          iprot.readFieldEnd();
+        }
+        iprot.readStructEnd();
+
+        // check for required fields of primitive type, which can't be checked in the validate method
+        struct.validate();
+      }
+
+      public void write(org.apache.thrift.protocol.TProtocol oprot, executeCommand_result struct) throws org.apache.thrift.TException {
+        struct.validate();
+
+        oprot.writeStructBegin(STRUCT_DESC);
+        if (struct.ex != null) {
+          oprot.writeFieldBegin(EX_FIELD_DESC);
+          struct.ex.write(oprot);
+          oprot.writeFieldEnd();
+        }
+        oprot.writeFieldStop();
+        oprot.writeStructEnd();
+      }
+
+    }
+
+    private static class executeCommand_resultTupleSchemeFactory implements org.apache.thrift.scheme.SchemeFactory {
+      public executeCommand_resultTupleScheme getScheme() {
+        return new executeCommand_resultTupleScheme();
+      }
+    }
+
+    private static class executeCommand_resultTupleScheme extends org.apache.thrift.scheme.TupleScheme<executeCommand_result> {
+
+      @Override
+      public void write(org.apache.thrift.protocol.TProtocol prot, executeCommand_result struct) throws org.apache.thrift.TException {
+        org.apache.thrift.protocol.TTupleProtocol oprot = (org.apache.thrift.protocol.TTupleProtocol) prot;
+        java.util.BitSet optionals = new java.util.BitSet();
+        if (struct.isSetEx()) {
+          optionals.set(0);
+        }
+        oprot.writeBitSet(optionals, 1);
+        if (struct.isSetEx()) {
+          struct.ex.write(oprot);
+        }
+      }
+
+      @Override
+      public void read(org.apache.thrift.protocol.TProtocol prot, executeCommand_result struct) throws org.apache.thrift.TException {
+        org.apache.thrift.protocol.TTupleProtocol iprot = (org.apache.thrift.protocol.TTupleProtocol) prot;
+        java.util.BitSet incoming = iprot.readBitSet(1);
+        if (incoming.get(0)) {
+          struct.ex = new OperationException();
+          struct.ex.read(iprot);
+          struct.setExIsSet(true);
+        }
+      }
+    }
+
+    private static <S extends org.apache.thrift.scheme.IScheme> S scheme(org.apache.thrift.protocol.TProtocol proto) {
+      return (org.apache.thrift.scheme.StandardScheme.class.equals(proto.getScheme()) ? STANDARD_SCHEME_FACTORY : TUPLE_SCHEME_FACTORY).getScheme();
+    }
+  }
+
+}
diff --git a/airavata-kubernetes/modules/agents/thrift-agent/src/main/resources/application.properties b/airavata-kubernetes/modules/agents/thrift-agent/src/main/resources/application.properties
new file mode 100644
index 0000000..bd5b373
--- /dev/null
+++ b/airavata-kubernetes/modules/agents/thrift-agent/src/main/resources/application.properties
@@ -0,0 +1,2 @@
+kafka.bootstrap.url=localhost:9092
+async.event.listener.topic=async-event-listener
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/agents/thrift-agent/src/main/resources/schema.thrift b/airavata-kubernetes/modules/agents/thrift-agent/src/main/resources/schema.thrift
new file mode 100644
index 0000000..9ff22d0
--- /dev/null
+++ b/airavata-kubernetes/modules/agents/thrift-agent/src/main/resources/schema.thrift
@@ -0,0 +1,11 @@
+namespace java org.apache.airavata.agents.thrift.stubs
+
+exception OperationException {
+  1: string message,
+  2: string stacktrace
+}
+
+service OperationService
+{
+        void executeCommand(1:string command, 2:i64 callbackWorkflowId) throws (1:OperationException ex)
+}
diff --git a/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/process/ProcessBootstrapDataResource.java b/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/process/ProcessBootstrapDataResource.java
new file mode 100644
index 0000000..25f0962
--- /dev/null
+++ b/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/process/ProcessBootstrapDataResource.java
@@ -0,0 +1,40 @@
+package org.apache.airavata.k8s.api.resources.process;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public class ProcessBootstrapDataResource {
+    private long id;
+    private String key;
+    private String value;
+
+    public long getId() {
+        return id;
+    }
+
+    public ProcessBootstrapDataResource setId(long id) {
+        this.id = id;
+        return this;
+    }
+
+    public String getKey() {
+        return key;
+    }
+
+    public ProcessBootstrapDataResource setKey(String key) {
+        this.key = key;
+        return this;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public ProcessBootstrapDataResource setValue(String value) {
+        this.value = value;
+        return this;
+    }
+}
diff --git a/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/process/ProcessResource.java b/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/process/ProcessResource.java
index b5081cf..fe81007 100644
--- a/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/process/ProcessResource.java
+++ b/airavata-kubernetes/modules/api-resource/src/main/java/org/apache/airavata/k8s/api/resources/process/ProcessResource.java
@@ -41,6 +41,7 @@ public class ProcessResource {
     private List<ProcessStatusResource> processStatuses = new ArrayList<>();
     private List<TaskResource> tasks = new ArrayList<>();
     private List<Long> processErrorIds = new ArrayList<>();
+    private List<ProcessBootstrapDataResource> processBootstrapData = new ArrayList<>();
     private String taskDag;
     private String experimentDataDir;
     private String processType;
@@ -152,4 +153,13 @@ public class ProcessResource {
         this.workflowId = workflowId;
         return this;
     }
+
+    public List<ProcessBootstrapDataResource> getProcessBootstrapData() {
+        return processBootstrapData;
+    }
+
+    public ProcessResource setProcessBootstrapData(List<ProcessBootstrapDataResource> processBootstrapData) {
+        this.processBootstrapData = processBootstrapData;
+        return this;
+    }
 }
diff --git a/airavata-kubernetes/modules/helix-task-api/src/main/java/org/apache/airavata/helix/api/AbstractTask.java b/airavata-kubernetes/modules/helix-task-api/src/main/java/org/apache/airavata/helix/api/AbstractTask.java
index 03589e7..a3ccffb 100644
--- a/airavata-kubernetes/modules/helix-task-api/src/main/java/org/apache/airavata/helix/api/AbstractTask.java
+++ b/airavata-kubernetes/modules/helix-task-api/src/main/java/org/apache/airavata/helix/api/AbstractTask.java
@@ -29,9 +29,9 @@ public abstract class AbstractTask extends UserContentStore implements Task {
     public static final String PROCESS_ID = "process_id";
 
     //Configurable values
-    private String apiServerUrl = "localhost:8080";
-    private String kafkaBootstrapUrl = "localhost:9092";
-    private String eventTopic = "airavata-task-event";
+    private String apiServerUrl;
+    private String kafkaBootstrapUrl;
+    private String eventTopic;
 
     private TaskCallbackContext callbackContext;
     private RestTemplate restTemplate;
@@ -39,11 +39,19 @@ public abstract class AbstractTask extends UserContentStore implements Task {
     private long processId;
     private long taskId;
 
-    public AbstractTask(TaskCallbackContext callbackContext) {
+    private PropertyResolver propertyResolver;
+
+    public AbstractTask(TaskCallbackContext callbackContext, PropertyResolver propertyResolver) {
         this.callbackContext = callbackContext;
         this.taskId = Long.parseLong(this.callbackContext.getTaskConfig().getConfigMap().get(TASK_ID));
         this.processId = Long.parseLong(this.callbackContext.getTaskConfig().getConfigMap().get(PROCESS_ID));
         this.restTemplate = new RestTemplate();
+        this.propertyResolver = propertyResolver;
+
+        this.apiServerUrl = getPropertyResolver().get("api.server.url");
+        this.kafkaBootstrapUrl = getPropertyResolver().get("kafka.bootstrap.url");
+        this.eventTopic = getPropertyResolver().get("event.topic");
+
         initializeKafkaEventProducer();
         init();
     }
@@ -135,4 +143,12 @@ public abstract class AbstractTask extends UserContentStore implements Task {
     public long getTaskId() {
         return taskId;
     }
+
+    public Producer<String, String> getEventProducer() {
+        return eventProducer;
+    }
+
+    public PropertyResolver getPropertyResolver() {
+        return propertyResolver;
+    }
 }
diff --git a/airavata-kubernetes/modules/helix-task-api/src/main/java/org/apache/airavata/helix/api/HelixParticipant.java b/airavata-kubernetes/modules/helix-task-api/src/main/java/org/apache/airavata/helix/api/HelixParticipant.java
index a2a56ca..ceb8126 100644
--- a/airavata-kubernetes/modules/helix-task-api/src/main/java/org/apache/airavata/helix/api/HelixParticipant.java
+++ b/airavata-kubernetes/modules/helix-task-api/src/main/java/org/apache/airavata/helix/api/HelixParticipant.java
@@ -38,12 +38,13 @@ public abstract class HelixParticipant implements Runnable {
     private String taskTypeName;
     private String apiServerUrl;
     private RestTemplate restTemplate;
+    private PropertyResolver propertyResolver;
 
     public HelixParticipant(String propertyFile) throws IOException {
 
         logger.debug("Initializing Participant Node");
 
-        PropertyResolver propertyResolver = new PropertyResolver();
+        this.propertyResolver = new PropertyResolver();
         propertyResolver.loadInputStream(this.getClass().getClassLoader().getResourceAsStream(propertyFile));
 
         this.zkAddress = propertyResolver.get("zookeeper.connection.url");
@@ -137,4 +138,8 @@ public abstract class HelixParticipant implements Runnable {
             zkHelixManager.disconnect();
         }
     }
+
+    public PropertyResolver getPropertyResolver() {
+        return propertyResolver;
+    }
 }
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/controller/WorkflowController.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/controller/WorkflowController.java
index 273a59f..67b4b2a 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/controller/WorkflowController.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/controller/WorkflowController.java
@@ -9,6 +9,7 @@ import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
 import java.util.List;
+import java.util.Map;
 
 /**
  * TODO: Class level comments please
@@ -40,7 +41,12 @@ public class WorkflowController {
     }
 
     @GetMapping(path = "{id}/launch", produces = MediaType.APPLICATION_JSON_VALUE)
-    public long launchExperiment(@PathVariable("id") long id) {
-        return this.workflowService.launchWorkflow(id);
+    public long launchWorkflow(@PathVariable("id") long id) {
+        return this.workflowService.launchWorkflow(id, null);
+    }
+
+    @PostMapping(path = "{id}/launch", produces = MediaType.APPLICATION_JSON_VALUE)
+    public long launchWorkflow(@PathVariable("id") long id, @RequestBody Map<String, String> boostrapData) {
+        return this.workflowService.launchWorkflow(id, boostrapData);
     }
 }
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/process/ProcessBootstrapData.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/process/ProcessBootstrapData.java
new file mode 100644
index 0000000..5ed9222
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/process/ProcessBootstrapData.java
@@ -0,0 +1,63 @@
+package org.apache.airavata.k8s.api.server.model.process;
+
+import javax.persistence.*;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+@Entity
+@Table(name = "PROCESS_BOOTSTRAP_DATA")
+public class ProcessBootstrapData {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.AUTO)
+    private long id;
+
+    @ManyToOne
+    private ProcessModel processModel;
+
+    @Column(name = "DATA_KEY")
+    private String key;
+
+    @Column(name = "DATA_VALUE")
+    private String value;
+
+    public long getId() {
+        return id;
+    }
+
+    public ProcessBootstrapData setId(long id) {
+        this.id = id;
+        return this;
+    }
+
+    public ProcessModel getProcessModel() {
+        return processModel;
+    }
+
+    public ProcessBootstrapData setProcessModel(ProcessModel processModel) {
+        this.processModel = processModel;
+        return this;
+    }
+
+    public String getKey() {
+        return key;
+    }
+
+    public ProcessBootstrapData setKey(String key) {
+        this.key = key;
+        return this;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public ProcessBootstrapData setValue(String value) {
+        this.value = value;
+        return this;
+    }
+}
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/process/ProcessModel.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/process/ProcessModel.java
index 5a4054f..b605b9f 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/process/ProcessModel.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/model/process/ProcessModel.java
@@ -60,6 +60,9 @@ public class ProcessModel {
     @OneToMany(mappedBy = "parentProcess", cascade = CascadeType.ALL)
     private List<TaskModel> tasks = new ArrayList<>();
 
+    @OneToMany(mappedBy = "processModel", cascade = CascadeType.ALL)
+    private List<ProcessBootstrapData> processBootstrapData = new ArrayList<>();
+
     private String taskDag;
 
     @OneToMany
@@ -177,6 +180,15 @@ public class ProcessModel {
         return this;
     }
 
+    public List<ProcessBootstrapData> getProcessBootstrapData() {
+        return processBootstrapData;
+    }
+
+    public ProcessModel setProcessBootstrapData(List<ProcessBootstrapData> processBootstrapData) {
+        this.processBootstrapData = processBootstrapData;
+        return this;
+    }
+
     public enum ProcessType {
         WORKFLOW, EXPERIMENT;
     }
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/process/ProcessBootstrapDataRepository.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/process/ProcessBootstrapDataRepository.java
new file mode 100644
index 0000000..e79d74e
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/repository/process/ProcessBootstrapDataRepository.java
@@ -0,0 +1,13 @@
+package org.apache.airavata.k8s.api.server.repository.process;
+
+import org.apache.airavata.k8s.api.server.model.process.ProcessBootstrapData;
+import org.springframework.data.repository.CrudRepository;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public interface ProcessBootstrapDataRepository extends CrudRepository<ProcessBootstrapData, Long> {
+}
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/ProcessService.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/ProcessService.java
index 500e83f..bee190f 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/ProcessService.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/ProcessService.java
@@ -21,9 +21,11 @@ package org.apache.airavata.k8s.api.server.service;
 
 import org.apache.airavata.k8s.api.resources.process.ProcessStatusResource;
 import org.apache.airavata.k8s.api.server.ServerRuntimeException;
+import org.apache.airavata.k8s.api.server.model.process.ProcessBootstrapData;
 import org.apache.airavata.k8s.api.server.model.process.ProcessModel;
 import org.apache.airavata.k8s.api.server.model.process.ProcessStatus;
 import org.apache.airavata.k8s.api.server.model.task.TaskModel;
+import org.apache.airavata.k8s.api.server.repository.process.ProcessBootstrapDataRepository;
 import org.apache.airavata.k8s.api.server.repository.process.ProcessRepository;
 import org.apache.airavata.k8s.api.resources.process.ProcessResource;
 import org.apache.airavata.k8s.api.server.repository.process.ProcessStatusRepository;
@@ -45,6 +47,7 @@ public class ProcessService {
 
     private ProcessRepository processRepository;
     private ProcessStatusRepository processStatusRepository;
+    private ProcessBootstrapDataRepository bootstrapDataRepository;
 
     private ExperimentService experimentService;
     private TaskService taskService;
@@ -55,13 +58,15 @@ public class ProcessService {
                           ProcessStatusRepository processStatusRepository,
                           ExperimentService experimentService,
                           TaskService taskService,
-                          WorkflowRepository workflowRepository) {
+                          WorkflowRepository workflowRepository,
+                          ProcessBootstrapDataRepository bootstrapDataRepository) {
 
         this.processRepository = processRepository;
         this.processStatusRepository = processStatusRepository;
         this.experimentService = experimentService;
         this.taskService = taskService;
         this.workflowRepository = workflowRepository;
+        this.bootstrapDataRepository = bootstrapDataRepository;
     }
 
     public long create(ProcessResource resource) {
@@ -95,6 +100,14 @@ public class ProcessService {
             taskModel.setId(taskService.create(taskRes));
         }));
 
+        Optional.ofNullable(resource.getProcessBootstrapData()).ifPresent(bootstrapDatas -> bootstrapDatas.forEach(data -> {
+            ProcessBootstrapData bootstrapData = new ProcessBootstrapData();
+            bootstrapData.setProcessModel(saved);
+            bootstrapData.setKey(data.getKey());
+            bootstrapData.setValue(data.getValue());
+            this.bootstrapDataRepository.save(bootstrapData);
+        }));
+
         return saved.getId();
     }
 
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/WorkflowService.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/WorkflowService.java
index 42be414..d76f5e5 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/WorkflowService.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/WorkflowService.java
@@ -1,5 +1,6 @@
 package org.apache.airavata.k8s.api.server.service;
 
+import org.apache.airavata.k8s.api.resources.process.ProcessBootstrapDataResource;
 import org.apache.airavata.k8s.api.resources.process.ProcessResource;
 import org.apache.airavata.k8s.api.resources.workflow.WorkflowResource;
 import org.apache.airavata.k8s.api.server.ServerRuntimeException;
@@ -18,10 +19,7 @@ import org.apache.airavata.k8s.api.server.service.util.ToResourceUtil;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Optional;
-import java.util.UUID;
+import java.util.*;
 
 /**
  * TODO: Class level comments please
@@ -70,15 +68,22 @@ public class WorkflowService {
         return saved.getId();
     }
 
-    public long launchWorkflow(long id) {
+    public long launchWorkflow(long id, Map<String, String> boostrapData) {
         Workflow workflow = this.workflowRepository.findById(id)
                 .orElseThrow(() -> new ServerRuntimeException("Workflow with id " + id + "can not be found"));
 
+        List<ProcessBootstrapDataResource> bootstrapDataResources = new ArrayList<>();
+
+        if (boostrapData != null) {
+            boostrapData.forEach((key, value) ->
+                    bootstrapDataResources.add(new ProcessBootstrapDataResource().setKey(key).setValue(value)));
+        }
 
         long processId = processService.create(new ProcessResource()
                 .setName("Workflow Process : " + workflow.getName() + "-" + UUID.randomUUID().toString())
                 .setCreationTime(System.currentTimeMillis())
                 .setProcessType("WORKFLOW")
+                .setProcessBootstrapData(bootstrapDataResources)
                 .setWorkflowId(id));
 
         try {
diff --git a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/util/ToResourceUtil.java b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/util/ToResourceUtil.java
index 35c7fde..6e61c47 100644
--- a/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/util/ToResourceUtil.java
+++ b/airavata-kubernetes/modules/microservices/api-server/src/main/java/org/apache/airavata/k8s/api/server/service/util/ToResourceUtil.java
@@ -20,6 +20,7 @@
 package org.apache.airavata.k8s.api.server.service.util;
 
 import org.apache.airavata.k8s.api.resources.experiment.ExperimentStatusResource;
+import org.apache.airavata.k8s.api.resources.process.ProcessBootstrapDataResource;
 import org.apache.airavata.k8s.api.resources.process.ProcessStatusResource;
 import org.apache.airavata.k8s.api.resources.task.*;
 import org.apache.airavata.k8s.api.resources.task.type.TaskInputTypeResource;
@@ -35,6 +36,7 @@ import org.apache.airavata.k8s.api.server.model.experiment.Experiment;
 import org.apache.airavata.k8s.api.server.model.experiment.ExperimentInputData;
 import org.apache.airavata.k8s.api.server.model.experiment.ExperimentOutputData;
 import org.apache.airavata.k8s.api.server.model.experiment.ExperimentStatus;
+import org.apache.airavata.k8s.api.server.model.process.ProcessBootstrapData;
 import org.apache.airavata.k8s.api.server.model.process.ProcessModel;
 import org.apache.airavata.k8s.api.server.model.process.ProcessStatus;
 import org.apache.airavata.k8s.api.server.model.task.*;
@@ -325,12 +327,27 @@ public class ToResourceUtil {
                     .ifPresent(tasks -> tasks.forEach(task -> processResource.getTasks().add(toResource(task).get())));
             Optional.ofNullable(processModel.getProcessErrors())
                     .ifPresent(errs -> errs.forEach(err -> processResource.getProcessErrorIds().add(err.getId())));
+            Optional.ofNullable(processModel.getProcessBootstrapData())
+                    .ifPresent(datas -> datas.forEach(data -> processResource.getProcessBootstrapData().add(toResource(data).get())));
+
             return Optional.of(processResource);
         } else {
             return Optional.empty();
         }
     }
 
+    public static Optional<ProcessBootstrapDataResource> toResource(ProcessBootstrapData bootstrapData) {
+        if (bootstrapData != null) {
+            ProcessBootstrapDataResource resource = new ProcessBootstrapDataResource();
+            resource.setId(bootstrapData.getId());
+            resource.setKey(bootstrapData.getKey());
+            resource.setValue(bootstrapData.getValue());
+            return Optional.of(resource);
+        } else {
+            return Optional.empty();
+        }
+    }
+
     public static Optional<ProcessStatusResource> toResource(ProcessStatus processStatus) {
         if (processStatus != null) {
             ProcessStatusResource resource = new ProcessStatusResource();
diff --git a/airavata-kubernetes/modules/microservices/async-event-listener/pom.xml b/airavata-kubernetes/modules/microservices/async-event-listener/pom.xml
new file mode 100644
index 0000000..7afdbf4
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/async-event-listener/pom.xml
@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>airavata-kubernetes</artifactId>
+        <groupId>org.apache.airavata</groupId>
+        <version>1.0-SNAPSHOT</version>
+        <relativePath>../../../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>async-event-listener</artifactId>
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.airavata</groupId>
+            <artifactId>api-resource</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-freemarker</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.kafka</groupId>
+            <artifactId>spring-kafka</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.5.1</version>
+                <configuration>
+                    <source>${java.version}</source>
+                    <target>${java.version}</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <profiles>
+        <profile>
+            <id>jar</id>
+            <activation>
+                <activeByDefault>true</activeByDefault>
+            </activation>
+            <properties>
+                <artifact-packaging>jar</artifact-packaging>
+            </properties>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.springframework.boot</groupId>
+                        <artifactId>spring-boot-maven-plugin</artifactId>
+                        <version>1.4.3.RELEASE</version>
+                        <executions>
+                            <execution>
+                                <goals>
+                                    <goal>repackage</goal>
+                                </goals>
+                            </execution>
+                        </executions>
+                    </plugin>
+                    <!-- Create a docker image that runs the executable jar-->
+                    <plugin>
+                        <groupId>com.spotify</groupId>
+                        <artifactId>docker-maven-plugin</artifactId>
+                        <version>1.0.0</version>
+                        <configuration>
+                            <imageName>${docker.image.prefix}/task-scheduler</imageName>
+                            <baseImage>java:openjdk-8-jdk-alpine</baseImage>
+                            <entryPoint>["java","-jar","/${project.build.finalName}.jar"]</entryPoint>
+                            <resources>
+                                <resource>
+                                    <targetPath>/</targetPath>
+                                    <directory>${project.build.directory}</directory>
+                                    <include>${project.build.finalName}.jar</include>
+                                </resource>
+                            </resources>
+                        </configuration>
+                        <executions>
+                            <execution>
+                                <goals>
+                                    <goal>build</goal>
+                                </goals>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+
+        <profile>
+            <id>war</id>
+            <properties>
+                <artifact-packaging>war</artifact-packaging>
+            </properties>
+            <dependencies>
+                <dependency>
+                    <groupId>org.springframework.boot</groupId>
+                    <artifactId>spring-boot-starter-tomcat</artifactId>
+                    <scope>provided</scope>
+                </dependency>
+            </dependencies>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.springframework.boot</groupId>
+                        <artifactId>spring-boot-maven-plugin</artifactId>
+                        <version>1.4.3.RELEASE</version>
+                        <configuration>
+                            <!-- this will get rid of version info from war file name -->
+                            <finalName>task-scheduler</finalName>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
+
+</project>
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/microservices/async-event-listener/src/main/java/org/apache/airavata/async/event/listener/Application.java b/airavata-kubernetes/modules/microservices/async-event-listener/src/main/java/org/apache/airavata/async/event/listener/Application.java
new file mode 100644
index 0000000..aa9b5d7
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/async-event-listener/src/main/java/org/apache/airavata/async/event/listener/Application.java
@@ -0,0 +1,29 @@
+package org.apache.airavata.async.event.listener;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.web.client.RestTemplateBuilder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.client.RestTemplate;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+@SpringBootApplication
+@Configuration
+@ComponentScan
+public class Application {
+    public static void main(String[] args) {
+        SpringApplication.run(Application.class, args);
+    }
+
+    @Bean
+    public RestTemplate restTemplate(RestTemplateBuilder builder) {
+        return builder.build();
+    }
+}
diff --git a/airavata-kubernetes/modules/microservices/async-event-listener/src/main/java/org/apache/airavata/async/event/listener/messaging/KafkaReceiver.java b/airavata-kubernetes/modules/microservices/async-event-listener/src/main/java/org/apache/airavata/async/event/listener/messaging/KafkaReceiver.java
new file mode 100644
index 0000000..c864ae2
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/async-event-listener/src/main/java/org/apache/airavata/async/event/listener/messaging/KafkaReceiver.java
@@ -0,0 +1,52 @@
+/**
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.airavata.async.event.listener.messaging;
+
+import org.apache.airavata.async.event.listener.service.ListenerService;
+import org.springframework.kafka.annotation.KafkaListener;
+
+import javax.annotation.Resource;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+public class KafkaReceiver {
+
+    @Resource
+    private ListenerService listenerService;
+
+    @KafkaListener(topics = "${listener.topic.name}", containerFactory = "kafkaListenerContainerFactory")
+    public void receiveProcesses(String payload) {
+        System.out.println("received process=" + payload);
+        long workflowId = Long.parseLong(payload.split(",")[0]);
+        String event = payload.split(",")[1];
+        String message = payload.split(",")[2];
+        listenerService.onEventReceived(workflowId, event, message);
+    }
+//
+//    @KafkaListener(topics = "${task.event.topic.name}", containerFactory = "kafkaEventListenerContainerFactory")
+//    public void receiveTaskEvent(TaskContext taskContext) {
+//        System.out.println("received event for task id =" + taskContext.getTaskId());
+//        workerService.onTaskStateEvent(taskContext);
+//    }
+}
diff --git a/airavata-kubernetes/modules/microservices/async-event-listener/src/main/java/org/apache/airavata/async/event/listener/messaging/ReceiverConfig.java b/airavata-kubernetes/modules/microservices/async-event-listener/src/main/java/org/apache/airavata/async/event/listener/messaging/ReceiverConfig.java
new file mode 100644
index 0000000..2a59c86
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/async-event-listener/src/main/java/org/apache/airavata/async/event/listener/messaging/ReceiverConfig.java
@@ -0,0 +1,82 @@
+/**
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.airavata.async.event.listener.messaging;
+
+import org.apache.kafka.clients.consumer.ConsumerConfig;
+import org.apache.kafka.common.serialization.StringDeserializer;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.kafka.annotation.EnableKafka;
+import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
+import org.springframework.kafka.config.KafkaListenerContainerFactory;
+import org.springframework.kafka.core.ConsumerFactory;
+import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
+import org.springframework.kafka.listener.ConcurrentMessageListenerContainer;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+@Configuration
+@EnableKafka
+public class ReceiverConfig {
+
+    @Value("${kafka.bootstrap-servers}")
+    private String bootstrapServers;
+
+    @Value("${listener.group.name}")
+    private String listenerGroupName;
+
+    @Bean
+    public Map<String, Object> consumerConfigs() {
+        Map<String, Object> props = new HashMap<>();
+        // list of host:port pairs used for establishing the initial connections to the Kakfa cluster
+        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
+        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
+        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
+        // allows a pool of processes to divide the work of consuming and processing records
+        props.put(ConsumerConfig.GROUP_ID_CONFIG, listenerGroupName);
+        return props;
+    }
+
+    @Bean
+    public ConsumerFactory<String, String> consumerFactory() {
+        return new DefaultKafkaConsumerFactory<String, String>(consumerConfigs());
+    }
+
+    @Bean
+    public KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<String, String>> kafkaListenerContainerFactory() {
+        ConcurrentKafkaListenerContainerFactory<String, String> factory =
+                new ConcurrentKafkaListenerContainerFactory<>();
+        factory.setConsumerFactory(consumerFactory());
+        return factory;
+    }
+
+    @Bean
+    public KafkaReceiver receiver() {
+        return new KafkaReceiver();
+    }
+}
diff --git a/airavata-kubernetes/modules/microservices/async-event-listener/src/main/java/org/apache/airavata/async/event/listener/service/ListenerService.java b/airavata-kubernetes/modules/microservices/async-event-listener/src/main/java/org/apache/airavata/async/event/listener/service/ListenerService.java
new file mode 100644
index 0000000..72f5309
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/async-event-listener/src/main/java/org/apache/airavata/async/event/listener/service/ListenerService.java
@@ -0,0 +1,35 @@
+package org.apache.airavata.async.event.listener.service;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * TODO: Class level comments please
+ *
+ * @author dimuthu
+ * @since 1.0.0-SNAPSHOT
+ */
+@Service
+public class ListenerService {
+
+    private final RestTemplate restTemplate;
+
+    @Value("${api.server.url}")
+    private String apiServerUrl;
+
+    public ListenerService(RestTemplate restTemplate) {
+        this.restTemplate = restTemplate;
+    }
+
+    public void onEventReceived(long workflowId, String event, String message) {
+        Map<String, String> boostrapData = new HashMap<>();
+        boostrapData.put("event", event);
+        boostrapData.put("message", message);
+        this.restTemplate.postForObject(apiServerUrl + "/workflow/" + workflowId + "/launch", boostrapData, Long.class);
+    }
+}
diff --git a/airavata-kubernetes/modules/microservices/async-event-listener/src/main/resources/application.properties b/airavata-kubernetes/modules/microservices/async-event-listener/src/main/resources/application.properties
new file mode 100644
index 0000000..f4f3812
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/async-event-listener/src/main/resources/application.properties
@@ -0,0 +1,4 @@
+server.port = 9195
+listener.topic.name=async-event-listener
+listener.group.name=async-event-listener
+api.server.url=api-server.default.svc.cluster.local:8080
diff --git a/airavata-kubernetes/modules/microservices/async-event-listener/src/main/resources/application.yml b/airavata-kubernetes/modules/microservices/async-event-listener/src/main/resources/application.yml
new file mode 100644
index 0000000..069dd61
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/async-event-listener/src/main/resources/application.yml
@@ -0,0 +1,4 @@
+kafka:
+  bootstrap-servers: kafka.default.svc.cluster.local:9092
+  topic:
+    helloworld: helloworld.t
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/microservices/tasks/async-command-task/pom.xml b/airavata-kubernetes/modules/microservices/tasks/async-command-task/pom.xml
new file mode 100644
index 0000000..a3a7669
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/tasks/async-command-task/pom.xml
@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>airavata-kubernetes</artifactId>
+        <groupId>org.apache.airavata</groupId>
+        <version>1.0-SNAPSHOT</version>
+        <relativePath>../../../../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>async-command-task</artifactId>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.5.1</version>
+                <configuration>
+                    <source>${java.version}</source>
+                    <target>${java.version}</target>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <configuration>
+                    <archive>
+                        <manifest>
+                            <mainClass>org.apache.airavata.helix.task.async.command.Participant</mainClass>
+                        </manifest>
+                    </archive>
+                    <descriptorRefs>
+                        <descriptorRef>jar-with-dependencies</descriptorRef>
+                    </descriptorRefs>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>make-assembly</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+
+            <!-- Create a docker image that runs the executable jar-->
+            <plugin>
+                <groupId>com.spotify</groupId>
+                <artifactId>docker-maven-plugin</artifactId>
+                <version>1.0.0</version>
+                <configuration>
+                    <imageName>${docker.image.prefix}/async-command-task</imageName>
+                    <baseImage>java:openjdk-8-jdk-alpine</baseImage>
+                    <exposes>
+                        <expose>8080</expose>
+                    </exposes>
+                    <entryPoint>["java","-jar","/${project.build.finalName}-jar-with-dependencies.jar"]</entryPoint>
+                    <resources>
+                        <resource>
+                            <targetPath>/</targetPath>
+                            <directory>${project.build.directory}</directory>
+                            <include>${project.build.finalName}-jar-with-dependencies.jar</include>
+                        </resource>
+                    </resources>
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>build</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.airavata</groupId>
+            <artifactId>helix-task-api</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.helix</groupId>
+            <artifactId>helix-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-log4j12</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.airavata</groupId>
+            <artifactId>agent-core</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.airavata</groupId>
+            <artifactId>thrift-agent</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.airavata</groupId>
+            <artifactId>api-resource</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+    </dependencies>
+
+</project>
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/microservices/tasks/command-task/src/main/java/org/apache/airavata/helix/task/command/CommandTask.java b/airavata-kubernetes/modules/microservices/tasks/async-command-task/src/main/java/org/apache/airavata/helix/task/async/command/AsyncCommandTask.java
similarity index 68%
copy from airavata-kubernetes/modules/microservices/tasks/command-task/src/main/java/org/apache/airavata/helix/task/command/CommandTask.java
copy to airavata-kubernetes/modules/microservices/tasks/async-command-task/src/main/java/org/apache/airavata/helix/task/async/command/AsyncCommandTask.java
index 866c2c7..5ece6f2 100644
--- a/airavata-kubernetes/modules/microservices/tasks/command-task/src/main/java/org/apache/airavata/helix/task/command/CommandTask.java
+++ b/airavata-kubernetes/modules/microservices/tasks/async-command-task/src/main/java/org/apache/airavata/helix/task/async/command/AsyncCommandTask.java
@@ -1,15 +1,18 @@
-package org.apache.airavata.helix.task.command;
+package org.apache.airavata.helix.task.async.command;
 
+import org.apache.airavata.agents.core.AsyncOperation;
 import org.apache.airavata.helix.api.AbstractTask;
+import org.apache.airavata.helix.api.PropertyResolver;
 import org.apache.airavata.k8s.api.resources.compute.ComputeResource;
 import org.apache.airavata.k8s.api.resources.task.TaskStatusResource;
 import org.apache.airavata.k8s.api.resources.task.type.TaskInputTypeResource;
 import org.apache.airavata.k8s.api.resources.task.type.TaskOutPortTypeResource;
 import org.apache.airavata.k8s.api.resources.task.type.TaskTypeResource;
-import org.apache.airavata.k8s.compute.api.ExecutionResult;
 import org.apache.helix.task.TaskCallbackContext;
 import org.apache.helix.task.TaskResult;
+import org.apache.kafka.clients.producer.ProducerRecord;
 
+import java.lang.reflect.InvocationTargetException;
 import java.util.Arrays;
 
 /**
@@ -18,9 +21,9 @@ import java.util.Arrays;
  * @author dimuthu
  * @since 1.0.0-SNAPSHOT
  */
-public class CommandTask extends AbstractTask {
+public class AsyncCommandTask extends AbstractTask {
 
-    public static final String NAME = "COMMAND";
+    public static final String NAME = "ASYNC_COMMAND_TASK";
 
     private String command;
     private String arguments;
@@ -28,9 +31,10 @@ public class CommandTask extends AbstractTask {
     private String stdErrPath;
     private String computeResourceId;
     private ComputeResource computeResource;
+    private Long callBackWorkflowId;
 
-    public CommandTask(TaskCallbackContext callbackContext) {
-        super(callbackContext);
+    public AsyncCommandTask(TaskCallbackContext callbackContext, PropertyResolver propertyResolver) {
+        super(callbackContext, propertyResolver);
     }
 
     @Override
@@ -40,48 +44,28 @@ public class CommandTask extends AbstractTask {
         this.stdOutPath = getCallbackContext().getTaskConfig().getConfigMap().get(PARAMS.STD_OUT_PATH);
         this.stdErrPath = getCallbackContext().getTaskConfig().getConfigMap().get(PARAMS.STD_ERR_PATH);
         this.computeResourceId = getCallbackContext().getTaskConfig().getConfigMap().get(PARAMS.COMPUTE_RESOURCE);
+        this.callBackWorkflowId = Long.parseLong(getCallbackContext().getTaskConfig().getConfigMap().get(PARAMS.CALLBACK_WORKFLOW));
         this.computeResource = this.getRestTemplate().getForObject("http://" + this.getApiServerUrl()
                 + "/compute/" + Long.parseLong(this.computeResourceId), ComputeResource.class);
+
     }
 
+    @Override
     public TaskResult onRun() {
-        System.out.println("Executing command " + command);
         try {
-
-            String stdOutSuffix = " > " + stdOutPath + " 2> " + stdErrPath;
-
-            publishTaskStatus(TaskStatusResource.State.EXECUTING, "");
-
-            String finalCommand = command + (arguments != null ? arguments : "") + stdOutSuffix;
-
-            System.out.println("Executing command " + finalCommand);
-            Thread.sleep(200000);
-            ExecutionResult executionResult = fetchComputeResourceOperation(computeResource).executeCommand(finalCommand);
-
-            if (executionResult.getExitStatus() == 0) {
-                publishTaskStatus(TaskStatusResource.State.COMPLETED, "Task completed");
-                sendToOutPort("Out");
-                return new TaskResult(TaskResult.Status.COMPLETED, "Task completed");
-
-            } else if (executionResult.getExitStatus() == -1) {
-                publishTaskStatus(TaskStatusResource.State.FAILED, "Process didn't exit successfully");
-                sendToOutPort("Error");
-                return new TaskResult(TaskResult.Status.FATAL_FAILED, "Task failed");
-
-            } else {
-                publishTaskStatus(TaskStatusResource.State.FAILED, "Process exited with error status " + executionResult.getExitStatus());
-                sendToOutPort("Error");
-                return new TaskResult(TaskResult.Status.FATAL_FAILED, "Task failed");
-            }
-
-        } catch (Exception e) {
-
+            AsyncOperation operation = (AsyncOperation) Class.forName("org.apache.airavata.agents.thrift.operation.ThriftAgentOperation")
+                    .getConstructor(ComputeResource.class).newInstance(this.computeResource);
+            operation.executeCommandAsync(this.command, this.callBackWorkflowId);
+            return new TaskResult(TaskResult.Status.COMPLETED, "Task completed");
+        } catch (InstantiationException | IllegalAccessException |
+                InvocationTargetException | ClassNotFoundException | NoSuchMethodException | ClassCastException e) {
             e.printStackTrace();
-            publishTaskStatus(TaskStatusResource.State.FAILED, e.getMessage());
+            publishTaskStatus(TaskStatusResource.State.FAILED, "Failed to load async operation");
             return new TaskResult(TaskResult.Status.FATAL_FAILED, "Task failed");
         }
     }
 
+    @Override
     public void onCancel() {
 
     }
@@ -89,7 +73,6 @@ public class CommandTask extends AbstractTask {
     public static TaskTypeResource getTaskType() {
         TaskTypeResource taskTypeResource = new TaskTypeResource();
         taskTypeResource.setName(NAME);
-        taskTypeResource.setTopicName("airavata-command");
         taskTypeResource.setIcon("assets/icons/ssh.png");
         taskTypeResource.getInputTypes().addAll(
                 Arrays.asList(
@@ -106,6 +89,10 @@ public class CommandTask extends AbstractTask {
                                 .setType("Long")
                                 .setDefaultValue(""),
                         new TaskInputTypeResource()
+                                .setName(PARAMS.CALLBACK_WORKFLOW)
+                                .setType("Long")
+                                .setDefaultValue(""),
+                        new TaskInputTypeResource()
                                 .setName(PARAMS.STD_OUT_PATH)
                                 .setType("String")
                                 .setDefaultValue(""),
@@ -114,6 +101,7 @@ public class CommandTask extends AbstractTask {
                                 .setType("String")
                                 .setDefaultValue("")));
 
+
         taskTypeResource.getOutPorts().addAll(
                 Arrays.asList(
                         new TaskOutPortTypeResource()
@@ -133,5 +121,6 @@ public class CommandTask extends AbstractTask {
         public static final String STD_OUT_PATH = "std_out_path";
         public static final String STD_ERR_PATH = "std_err_path";
         public static final String COMPUTE_RESOURCE = "compute_resource";
+        public static final String CALLBACK_WORKFLOW = "callback_workflow";
     }
 }
diff --git a/airavata-kubernetes/modules/microservices/tasks/command-task/src/main/java/org/apache/airavata/helix/task/command/Participant.java b/airavata-kubernetes/modules/microservices/tasks/async-command-task/src/main/java/org/apache/airavata/helix/task/async/command/Participant.java
similarity index 84%
copy from airavata-kubernetes/modules/microservices/tasks/command-task/src/main/java/org/apache/airavata/helix/task/command/Participant.java
copy to airavata-kubernetes/modules/microservices/tasks/async-command-task/src/main/java/org/apache/airavata/helix/task/async/command/Participant.java
index 3b7e55e..57bb26c 100644
--- a/airavata-kubernetes/modules/microservices/tasks/command-task/src/main/java/org/apache/airavata/helix/task/command/Participant.java
+++ b/airavata-kubernetes/modules/microservices/tasks/async-command-task/src/main/java/org/apache/airavata/helix/task/async/command/Participant.java
@@ -1,4 +1,4 @@
-package org.apache.airavata.helix.task.command;
+package org.apache.airavata.helix.task.async.command;
 
 import org.apache.airavata.helix.api.HelixParticipant;
 import org.apache.airavata.k8s.api.resources.task.type.TaskTypeResource;
@@ -18,7 +18,6 @@ import java.util.Map;
  */
 public class Participant extends HelixParticipant {
 
-
     public Participant(String propertyFile) throws IOException {
         super(propertyFile);
     }
@@ -30,18 +29,18 @@ public class Participant extends HelixParticipant {
         TaskFactory commandTaskFac = new TaskFactory() {
             @Override
             public Task createNewTask(TaskCallbackContext context) {
-                return new CommandTask(context);
+                return new AsyncCommandTask(context, getPropertyResolver());
             }
         };
 
-        taskRegistry.put(CommandTask.NAME, commandTaskFac);
+        taskRegistry.put(AsyncCommandTask.NAME, commandTaskFac);
 
         return taskRegistry;
     }
 
     @Override
     public TaskTypeResource getTaskType() {
-        return CommandTask.getTaskType();
+        return AsyncCommandTask.getTaskType();
     }
 
     public static void main(String args[]) {
diff --git a/airavata-kubernetes/modules/microservices/tasks/async-command-task/src/main/resources/application.properties b/airavata-kubernetes/modules/microservices/tasks/async-command-task/src/main/resources/application.properties
new file mode 100644
index 0000000..78ef57d
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/tasks/async-command-task/src/main/resources/application.properties
@@ -0,0 +1,8 @@
+api.server.url=api-server.default.svc.cluster.local:8080
+zookeeper.connection.url=localhost:2199
+helix.cluster.name=AiravataDemoCluster
+participant.name=async-command-p1
+task.type.name=ASYNC_COMMAND_TASK
+kafka.bootstrap.url=localhost:9092
+event.topic=airavata-task-event
+async.event.listener.topic=async-event-listener
diff --git a/airavata-kubernetes/modules/microservices/tasks/async-command-task/src/main/resources/log4j.properties b/airavata-kubernetes/modules/microservices/tasks/async-command-task/src/main/resources/log4j.properties
new file mode 100644
index 0000000..5e31e3c
--- /dev/null
+++ b/airavata-kubernetes/modules/microservices/tasks/async-command-task/src/main/resources/log4j.properties
@@ -0,0 +1,9 @@
+# Set root logger level to DEBUG and its only appender to A1.
+log4j.rootLogger=INFO, A1
+
+# A1 is set to be a ConsoleAppender.
+log4j.appender.A1=org.apache.log4j.ConsoleAppender
+
+# A1 uses PatternLayout.
+log4j.appender.A1.layout=org.apache.log4j.PatternLayout
+log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/microservices/tasks/command-task/src/main/java/org/apache/airavata/helix/task/command/CommandTask.java b/airavata-kubernetes/modules/microservices/tasks/command-task/src/main/java/org/apache/airavata/helix/task/command/CommandTask.java
index 866c2c7..d5b8bda 100644
--- a/airavata-kubernetes/modules/microservices/tasks/command-task/src/main/java/org/apache/airavata/helix/task/command/CommandTask.java
+++ b/airavata-kubernetes/modules/microservices/tasks/command-task/src/main/java/org/apache/airavata/helix/task/command/CommandTask.java
@@ -1,6 +1,7 @@
 package org.apache.airavata.helix.task.command;
 
 import org.apache.airavata.helix.api.AbstractTask;
+import org.apache.airavata.helix.api.PropertyResolver;
 import org.apache.airavata.k8s.api.resources.compute.ComputeResource;
 import org.apache.airavata.k8s.api.resources.task.TaskStatusResource;
 import org.apache.airavata.k8s.api.resources.task.type.TaskInputTypeResource;
@@ -29,8 +30,8 @@ public class CommandTask extends AbstractTask {
     private String computeResourceId;
     private ComputeResource computeResource;
 
-    public CommandTask(TaskCallbackContext callbackContext) {
-        super(callbackContext);
+    public CommandTask(TaskCallbackContext callbackContext, PropertyResolver propertyResolver) {
+        super(callbackContext, propertyResolver);
     }
 
     @Override
diff --git a/airavata-kubernetes/modules/microservices/tasks/command-task/src/main/java/org/apache/airavata/helix/task/command/Participant.java b/airavata-kubernetes/modules/microservices/tasks/command-task/src/main/java/org/apache/airavata/helix/task/command/Participant.java
index 3b7e55e..289135d 100644
--- a/airavata-kubernetes/modules/microservices/tasks/command-task/src/main/java/org/apache/airavata/helix/task/command/Participant.java
+++ b/airavata-kubernetes/modules/microservices/tasks/command-task/src/main/java/org/apache/airavata/helix/task/command/Participant.java
@@ -30,7 +30,7 @@ public class Participant extends HelixParticipant {
         TaskFactory commandTaskFac = new TaskFactory() {
             @Override
             public Task createNewTask(TaskCallbackContext context) {
-                return new CommandTask(context);
+                return new CommandTask(context, getPropertyResolver());
             }
         };
 
diff --git a/airavata-kubernetes/modules/microservices/tasks/command-task/src/main/resources/application.properties b/airavata-kubernetes/modules/microservices/tasks/command-task/src/main/resources/application.properties
index 92292ab..73a9b9c 100644
--- a/airavata-kubernetes/modules/microservices/tasks/command-task/src/main/resources/application.properties
+++ b/airavata-kubernetes/modules/microservices/tasks/command-task/src/main/resources/application.properties
@@ -3,3 +3,5 @@ zookeeper.connection.url=localhost:2199
 helix.cluster.name=AiravataDemoCluster
 participant.name=command-p2
 task.type.name=COMMAND
+kafka.bootstrap.url=localhost:9092
+event.topic=airavata-task-event
diff --git a/airavata-kubernetes/modules/microservices/tasks/data-in-task/src/main/java/org/apache/airavata/helix/task/datain/DataInputTask.java b/airavata-kubernetes/modules/microservices/tasks/data-in-task/src/main/java/org/apache/airavata/helix/task/datain/DataInputTask.java
index 00aeadc..cc6af6b 100644
--- a/airavata-kubernetes/modules/microservices/tasks/data-in-task/src/main/java/org/apache/airavata/helix/task/datain/DataInputTask.java
+++ b/airavata-kubernetes/modules/microservices/tasks/data-in-task/src/main/java/org/apache/airavata/helix/task/datain/DataInputTask.java
@@ -1,6 +1,7 @@
 package org.apache.airavata.helix.task.datain;
 
 import org.apache.airavata.helix.api.AbstractTask;
+import org.apache.airavata.helix.api.PropertyResolver;
 import org.apache.airavata.k8s.api.resources.compute.ComputeResource;
 import org.apache.airavata.k8s.api.resources.task.TaskStatusResource;
 import org.apache.airavata.k8s.api.resources.task.type.TaskInputTypeResource;
@@ -30,8 +31,8 @@ public class DataInputTask extends AbstractTask {
     private String computeResourceId;
     private ComputeResource computeResource;
 
-    public DataInputTask(TaskCallbackContext callbackContext) {
-        super(callbackContext);
+    public DataInputTask(TaskCallbackContext callbackContext, PropertyResolver propertyResolver) {
+        super(callbackContext, propertyResolver);
     }
 
     @Override
diff --git a/airavata-kubernetes/modules/microservices/tasks/data-in-task/src/main/java/org/apache/airavata/helix/task/datain/Participant.java b/airavata-kubernetes/modules/microservices/tasks/data-in-task/src/main/java/org/apache/airavata/helix/task/datain/Participant.java
index d804352..498dfee 100644
--- a/airavata-kubernetes/modules/microservices/tasks/data-in-task/src/main/java/org/apache/airavata/helix/task/datain/Participant.java
+++ b/airavata-kubernetes/modules/microservices/tasks/data-in-task/src/main/java/org/apache/airavata/helix/task/datain/Participant.java
@@ -30,7 +30,7 @@ public class Participant extends HelixParticipant {
         TaskFactory dataInTask = new TaskFactory() {
             @Override
             public Task createNewTask(TaskCallbackContext context) {
-                return new DataInputTask(context);
+                return new DataInputTask(context, getPropertyResolver());
             }
         };
 
diff --git a/airavata-kubernetes/modules/microservices/tasks/data-in-task/src/main/resources/application.properties b/airavata-kubernetes/modules/microservices/tasks/data-in-task/src/main/resources/application.properties
index 3f98946..acf5a9b 100644
--- a/airavata-kubernetes/modules/microservices/tasks/data-in-task/src/main/resources/application.properties
+++ b/airavata-kubernetes/modules/microservices/tasks/data-in-task/src/main/resources/application.properties
@@ -2,4 +2,6 @@ api.server.url=api-server.default.svc.cluster.local:8080
 zookeeper.connection.url=localhost:2199
 helix.cluster.name=AiravataDemoCluster
 participant.name=data-in-p1
-task.type.name=DATA_INPUT
\ No newline at end of file
+task.type.name=DATA_INPUT
+kafka.bootstrap.url=localhost:9092
+event.topic=airavata-task-event
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/microservices/tasks/data-out-task/src/main/java/org/apache/airavata/helix/task/dataout/DataOutputTask.java b/airavata-kubernetes/modules/microservices/tasks/data-out-task/src/main/java/org/apache/airavata/helix/task/dataout/DataOutputTask.java
index ad6123f..0fe8fbe 100644
--- a/airavata-kubernetes/modules/microservices/tasks/data-out-task/src/main/java/org/apache/airavata/helix/task/dataout/DataOutputTask.java
+++ b/airavata-kubernetes/modules/microservices/tasks/data-out-task/src/main/java/org/apache/airavata/helix/task/dataout/DataOutputTask.java
@@ -1,6 +1,7 @@
 package org.apache.airavata.helix.task.dataout;
 
 import org.apache.airavata.helix.api.AbstractTask;
+import org.apache.airavata.helix.api.PropertyResolver;
 import org.apache.airavata.k8s.api.resources.compute.ComputeResource;
 import org.apache.airavata.k8s.api.resources.task.TaskStatusResource;
 import org.apache.airavata.k8s.api.resources.task.type.TaskInputTypeResource;
@@ -33,8 +34,8 @@ public class DataOutputTask extends AbstractTask {
     private String computeResourceId;
     private ComputeResource computeResource;
 
-    public DataOutputTask(TaskCallbackContext callbackContext) {
-        super(callbackContext);
+    public DataOutputTask(TaskCallbackContext callbackContext, PropertyResolver propertyResolver) {
+        super(callbackContext, propertyResolver);
     }
 
     @Override
diff --git a/airavata-kubernetes/modules/microservices/tasks/data-out-task/src/main/java/org/apache/airavata/helix/task/dataout/Participant.java b/airavata-kubernetes/modules/microservices/tasks/data-out-task/src/main/java/org/apache/airavata/helix/task/dataout/Participant.java
index a26c654..1fe9fe6 100644
--- a/airavata-kubernetes/modules/microservices/tasks/data-out-task/src/main/java/org/apache/airavata/helix/task/dataout/Participant.java
+++ b/airavata-kubernetes/modules/microservices/tasks/data-out-task/src/main/java/org/apache/airavata/helix/task/dataout/Participant.java
@@ -29,7 +29,7 @@ public class Participant extends HelixParticipant {
         TaskFactory dataInTask = new TaskFactory() {
             @Override
             public Task createNewTask(TaskCallbackContext context) {
-                return new DataOutputTask(context);
+                return new DataOutputTask(context, getPropertyResolver());
             }
         };
 
diff --git a/airavata-kubernetes/modules/microservices/tasks/data-out-task/src/main/resources/application.properties b/airavata-kubernetes/modules/microservices/tasks/data-out-task/src/main/resources/application.properties
index 37109d6..2e10b26 100644
--- a/airavata-kubernetes/modules/microservices/tasks/data-out-task/src/main/resources/application.properties
+++ b/airavata-kubernetes/modules/microservices/tasks/data-out-task/src/main/resources/application.properties
@@ -2,4 +2,6 @@ api.server.url=api-server.default.svc.cluster.local:8080
 zookeeper.connection.url=localhost:2199
 helix.cluster.name=AiravataDemoCluster
 participant.name=data-out-p1
-task.type.name=DATA_OUTPUT
\ No newline at end of file
+task.type.name=DATA_OUTPUT
+kafka.bootstrap.url=localhost:9092
+event.topic=airavata-task-event
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/core/HelixWorkflowManager.java b/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/core/HelixWorkflowManager.java
index 4fe8579..37ca403 100644
--- a/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/core/HelixWorkflowManager.java
+++ b/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/core/HelixWorkflowManager.java
@@ -1,5 +1,6 @@
 package org.apache.airavata.k8s.gfac.core;
 
+import org.apache.airavata.k8s.api.resources.process.ProcessBootstrapDataResource;
 import org.apache.airavata.k8s.api.resources.process.ProcessStatusResource;
 import org.apache.airavata.k8s.api.resources.task.TaskResource;
 import org.apache.helix.HelixManagerFactory;
@@ -26,6 +27,7 @@ public class HelixWorkflowManager {
 
     private long processId;
     private List<TaskResource> tasks;
+    private List<ProcessBootstrapDataResource> boostrapData;
 
     // out port id, next task id
     private Map<Long, Long> edgeMap;
@@ -38,9 +40,9 @@ public class HelixWorkflowManager {
     private String helixClusterName;
     private String instanceName;
 
-    public HelixWorkflowManager(long processId, List<TaskResource> tasks, Map<Long, Long> edgeMap,
-                                RestTemplate restTemplate, String apiServerUrl, String zkConnectionString,
-                                String helixClusterName, String instanceName) {
+    public HelixWorkflowManager(long processId, List<TaskResource> tasks, List<ProcessBootstrapDataResource> boostrapData,
+                                Map<Long, Long> edgeMap, RestTemplate restTemplate, String apiServerUrl,
+                                String zkConnectionString, String helixClusterName, String instanceName) {
         this.processId = processId;
         this.tasks = tasks;
         this.edgeMap = edgeMap;
@@ -49,6 +51,7 @@ public class HelixWorkflowManager {
         this.zkConnectionString = zkConnectionString;
         this.helixClusterName = helixClusterName;
         this.instanceName = instanceName;
+        this.boostrapData = boostrapData;
     }
 
     public void launchWorkflow() {
@@ -57,7 +60,7 @@ public class HelixWorkflowManager {
 
         try {
             updateProcessStatus(ProcessStatusResource.State.CREATED);
-            Workflow.Builder workflowBuilder = createWorkflow();
+            Workflow.Builder workflowBuilder = createWorkflow(this.boostrapData);
             WorkflowConfig.Builder config = new WorkflowConfig.Builder().setFailureThreshold(0);
             workflowBuilder.setWorkflowConfig(config.build());
             if (workflowBuilder == null) {
@@ -94,11 +97,11 @@ public class HelixWorkflowManager {
         }
     }
 
-    private Workflow.Builder createWorkflow() {
+    private Workflow.Builder createWorkflow(List<ProcessBootstrapDataResource> bootstrapData) {
         Optional<TaskResource> startingTask = tasks.stream().filter(TaskResource::isStartingTask).findFirst();
         if (startingTask.isPresent()) {
             Workflow.Builder workflow = new Workflow.Builder("Airavata_Process_" + processId).setExpiry(0);
-            createWorkflowRecursively(startingTask.get(), workflow, null);
+            createWorkflowRecursively(startingTask.get(), workflow, null, bootstrapData);
             return workflow;
         } else {
             System.out.println("No starting task for this process " + processId);
@@ -107,7 +110,8 @@ public class HelixWorkflowManager {
         }
     }
 
-    private void createWorkflowRecursively(TaskResource taskResource, Workflow.Builder workflow, Long parentTaskId) {
+    private void createWorkflowRecursively(TaskResource taskResource, Workflow.Builder workflow, Long parentTaskId,
+                                           List<ProcessBootstrapDataResource> boostrapData) {
 
         TaskConfig.Builder taskBuilder = new TaskConfig.Builder().setTaskId("Task_" + taskResource.getId())
                 .setCommand(taskResource.getTaskType().getName());
@@ -119,6 +123,10 @@ public class HelixWorkflowManager {
         taskBuilder.addConfig("task_id", taskResource.getId() + "");
         taskBuilder.addConfig("process_id", taskResource.getParentProcessId() + "");
 
+        Optional.ofNullable(boostrapData).ifPresent(data -> {
+            data.forEach(d -> taskBuilder.addConfig(d.getKey(), d.getValue()));
+        });
+
         Optional.ofNullable(taskResource.getOutPorts()).ifPresent(outPorts -> outPorts.forEach(outPort -> {
             Optional.ofNullable(edgeMap.get(outPort.getId())).ifPresent(nextTask -> {
                 Optional<TaskResource> nextTaskResource = tasks.stream().filter(task -> task.getId() == nextTask).findFirst();
@@ -148,7 +156,7 @@ public class HelixWorkflowManager {
                 Optional<TaskResource> nextTaskResource = tasks.stream().filter(task -> task.getId() == nextTask).findFirst();
                 nextTaskResource.ifPresent(t -> {
 
-                    createWorkflowRecursively(t, workflow, taskResource.getId());
+                    createWorkflowRecursively(t, workflow, taskResource.getId(), null);
                 });
             });
         }));
diff --git a/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/service/WorkerService.java b/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/service/WorkerService.java
index 888f469..2ea84de 100644
--- a/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/service/WorkerService.java
+++ b/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/service/WorkerService.java
@@ -85,7 +85,8 @@ public class WorkerService {
 
         //processLifecycleStore.put(processId, manager);
 
-        final HelixWorkflowManager helixWorkflowManager = new HelixWorkflowManager(processId, taskResources, edgeMap,
+        final HelixWorkflowManager helixWorkflowManager = new HelixWorkflowManager(processId, taskResources,
+                processResource.getProcessBootstrapData(), edgeMap,
                 restTemplate, apiServerUrl,
                 zkConnectionString, helixClusterName, instanceName);
 
diff --git a/airavata-kubernetes/pom.xml b/airavata-kubernetes/pom.xml
index 30b9226..9ca584f 100644
--- a/airavata-kubernetes/pom.xml
+++ b/airavata-kubernetes/pom.xml
@@ -41,6 +41,10 @@
         <module>modules/microservices/tasks/command-task</module>
         <module>modules/microservices/tasks/data-in-task</module>
         <module>modules/microservices/tasks/data-out-task</module>
+        <module>modules/agents/agent-core</module>
+        <module>modules/agents/thrift-agent</module>
+        <module>modules/microservices/tasks/async-command-task</module>
+        <module>modules/microservices/async-event-listener</module>
     </modules>
 
     <dependencyManagement>

-- 
To stop receiving notification emails like this one, please contact
"commits@airavata.apache.org" <co...@airavata.apache.org>.

[airavata-sandbox] 07/19: Refactoring

Posted by sm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

smarru pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-sandbox.git

commit d6e055c6f86f98fea2d7a78a6f66801ec3929e6c
Author: dimuthu.upeksha2@gmail.com <Di...@1234>
AuthorDate: Mon Nov 20 19:54:48 2017 +0530

    Refactoring
---
 .../modules/{task-api => helix-task-api}/pom.xml   |  53 +++----
 .../apache/airavata/helix/api}/AbstractTask.java   |   2 +-
 .../airavata/helix/api}/HelixParticipant.java      |   8 +-
 .../org/apache/airavata/helix/HelixCluster.java    |  48 ------
 .../org/apache/airavata/helix/HelixManager.java    |  14 --
 .../org/apache/airavata/helix/WorkflowManager.java |  86 -----------
 .../airavata/helix/tasks/DataCollectingTask.java   |  32 ----
 .../airavata/helix/tasks/DataPushingTask.java      |  39 -----
 .../src/main/resources/log4j.properties            |   9 --
 .../helix-controller}/pom.xml                      |  18 +--
 .../org/apache/airavata/helix/HelixController.java |   2 -
 .../src/main/resources/application.yml             |   4 -
 .../tasks/command-executing-task/pom.xml           | 161 ---------------------
 .../apache/airavata/k8s/task/job/Application.java  |  55 -------
 .../airavata/k8s/task/job/CommandTaskInfo.java     |  59 --------
 .../airavata/k8s/task/job/config/RestConfig.java   |  10 --
 .../k8s/task/job/service/TaskExecutionService.java | 100 -------------
 .../src/main/resources/application.properties      |   5 -
 .../src/main/resources/application.yml             |   4 -
 .../tasks/command-task}/pom.xml                    |  42 ++----
 .../airavata/helix/task}/command/CommandTask.java  |   4 +-
 .../airavata/helix/task}/command/Participant.java  |   6 +-
 .../tasks/data-collecting-task/pom.xml             | 161 ---------------------
 .../airavata/k8s/task/egress/Application.java      |  55 -------
 .../k8s/task/egress/DataCollectingTaskInfo.java    |  50 -------
 .../task/egress/service/TaskExecutionService.java  | 106 --------------
 .../src/main/resources/application.properties      |   5 -
 .../src/main/resources/application.yml             |   4 -
 .../tasks/data-in-task}/pom.xml                    |  41 ++----
 .../airavata/helix/task}/datain/DataInputTask.java |   4 +-
 .../airavata/helix/task}/datain/Participant.java   |   5 +-
 .../tasks/data-out-task}/pom.xml                   |  42 ++----
 .../helix/task/datain}/dataout/DataOutputTask.java |   4 +-
 .../helix/task/datain}/dataout/Participant.java    |   4 +-
 .../microservices/tasks/data-pushing-task/pom.xml  | 161 ---------------------
 .../airavata/k8s/task/ingress/Application.java     |  49 -------
 .../task/ingress/service/TaskExecutionService.java |  84 -----------
 .../src/main/resources/application.properties      |   5 -
 .../{task-scheduler => workflow-scheduler}/pom.xml |   2 +-
 .../org/apache/airavata/k8s/gfac/Application.java  |   0
 .../k8s/gfac/core/HelixWorkflowManager.java        |   0
 .../k8s/gfac/core/ProcessLifeCycleManager.java     |   0
 .../airavata/k8s/gfac/messaging/KafkaReceiver.java |   0
 .../airavata/k8s/gfac/messaging/KafkaSender.java   |   0
 .../k8s/gfac/messaging/ReceiverConfig.java         |   0
 .../airavata/k8s/gfac/messaging/SenderConfig.java  |   0
 .../k8s/gfac/service/HelixWorkflowService.java     |   0
 .../airavata/k8s/gfac/service/WorkerService.java   |   0
 .../src/main/resources/application.properties      |   0
 .../src/main/resources/application.yml             |   0
 .../src/main/resources/log4j.properties            |   0
 .../k8s/task/api/AbstractTaskExecutionService.java | 137 ------------------
 .../apache/airavata/k8s/task/api/TaskContext.java  | 124 ----------------
 .../k8s/task/api/TaskContextDeserializer.java      |  48 ------
 .../k8s/task/api/TaskContextSerializer.java        |  48 ------
 .../k8s/task/api/messaging/KafkaReceiver.java      |  46 ------
 .../k8s/task/api/messaging/KafkaSender.java        |  44 ------
 .../k8s/task/api/messaging/ReceiverConfig.java     |  89 ------------
 .../k8s/task/api/messaging/SenderConfig.java       |  71 ---------
 airavata-kubernetes/pom.xml                        |  14 +-
 airavata-kubernetes/readme.txt                     |   6 +-
 61 files changed, 101 insertions(+), 2069 deletions(-)

diff --git a/airavata-kubernetes/modules/task-api/pom.xml b/airavata-kubernetes/modules/helix-task-api/pom.xml
similarity index 76%
copy from airavata-kubernetes/modules/task-api/pom.xml
copy to airavata-kubernetes/modules/helix-task-api/pom.xml
index d2621fc..e2363e6 100644
--- a/airavata-kubernetes/modules/task-api/pom.xml
+++ b/airavata-kubernetes/modules/helix-task-api/pom.xml
@@ -10,10 +10,34 @@
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
-    <artifactId>task-api</artifactId>
+    <artifactId>helix-task-api</artifactId>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.5.1</version>
+                <configuration>
+                    <source>${java.version}</source>
+                    <target>${java.version}</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
 
     <dependencies>
         <dependency>
+            <groupId>org.apache.helix</groupId>
+            <artifactId>helix-core</artifactId>
+            <version>0.6.7</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-web</artifactId>
+            <version>3.0.2.RELEASE</version>
+        </dependency>
+        <dependency>
             <groupId>org.apache.airavata</groupId>
             <artifactId>api-resource</artifactId>
             <version>1.0-SNAPSHOT</version>
@@ -24,31 +48,10 @@
             <version>1.0-SNAPSHOT</version>
         </dependency>
         <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-web</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-freemarker</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.kafka</groupId>
-            <artifactId>spring-kafka</artifactId>
+            <groupId>org.apache.kafka</groupId>
+            <artifactId>kafka-clients</artifactId>
+            <version>0.10.1.1</version>
         </dependency>
     </dependencies>
 
-    <build>
-        <plugins>
-            <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-compiler-plugin</artifactId>
-                <version>3.5.1</version>
-                <configuration>
-                    <source>${java.version}</source>
-                    <target>${java.version}</target>
-                </configuration>
-            </plugin>
-        </plugins>
-    </build>
-
 </project>
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/AbstractTask.java b/airavata-kubernetes/modules/helix-task-api/src/main/java/org/apache/airavata/helix/api/AbstractTask.java
similarity index 99%
rename from airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/AbstractTask.java
rename to airavata-kubernetes/modules/helix-task-api/src/main/java/org/apache/airavata/helix/api/AbstractTask.java
index 8e581ea..e7290c2 100644
--- a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/AbstractTask.java
+++ b/airavata-kubernetes/modules/helix-task-api/src/main/java/org/apache/airavata/helix/api/AbstractTask.java
@@ -1,4 +1,4 @@
-package org.apache.airavata.helix.tasks;
+package org.apache.airavata.helix.api;
 
 import org.apache.airavata.k8s.api.resources.compute.ComputeResource;
 import org.apache.airavata.k8s.api.resources.task.type.TaskTypeResource;
diff --git a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/HelixParticipant.java b/airavata-kubernetes/modules/helix-task-api/src/main/java/org/apache/airavata/helix/api/HelixParticipant.java
similarity index 94%
rename from airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/HelixParticipant.java
rename to airavata-kubernetes/modules/helix-task-api/src/main/java/org/apache/airavata/helix/api/HelixParticipant.java
index cbbc300..77073ff 100644
--- a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/HelixParticipant.java
+++ b/airavata-kubernetes/modules/helix-task-api/src/main/java/org/apache/airavata/helix/api/HelixParticipant.java
@@ -1,8 +1,5 @@
-package org.apache.airavata.helix;
+package org.apache.airavata.helix.api;
 
-import org.apache.airavata.helix.tasks.command.CommandTask;
-import org.apache.airavata.helix.tasks.DataCollectingTask;
-import org.apache.airavata.helix.tasks.DataPushingTask;
 import org.apache.airavata.k8s.api.resources.task.type.TaskTypeResource;
 import org.apache.helix.InstanceType;
 import org.apache.helix.examples.OnlineOfflineStateModelFactory;
@@ -13,15 +10,12 @@ import org.apache.helix.manager.zk.ZkClient;
 import org.apache.helix.model.BuiltInStateModelDefinitions;
 import org.apache.helix.model.InstanceConfig;
 import org.apache.helix.participant.StateMachineEngine;
-import org.apache.helix.task.Task;
-import org.apache.helix.task.TaskCallbackContext;
 import org.apache.helix.task.TaskFactory;
 import org.apache.helix.task.TaskStateModelFactory;
 import org.apache.log4j.LogManager;
 import org.apache.log4j.Logger;
 import org.springframework.web.client.RestTemplate;
 
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
diff --git a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/HelixCluster.java b/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/HelixCluster.java
deleted file mode 100644
index 2b96328..0000000
--- a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/HelixCluster.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package org.apache.airavata.helix;
-
-import org.apache.helix.manager.zk.ZKHelixAdmin;
-import org.apache.helix.manager.zk.ZNRecordSerializer;
-import org.apache.helix.manager.zk.ZkClient;
-import org.apache.helix.model.OnlineOfflineSMD;
-import org.apache.log4j.LogManager;
-import org.apache.log4j.Logger;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-public class HelixCluster {
-
-    private static final Logger logger = LogManager.getLogger(HelixCluster.class);
-
-    private String zkAddress;
-    private String clusterName;
-    private int numPartitions;
-
-    private ZkClient zkClient;
-    private ZKHelixAdmin zkHelixAdmin;
-
-    public HelixCluster(String zkAddress, String clusterName, int numPartitions) {
-        this.zkAddress = zkAddress;
-        this.clusterName = clusterName;
-        this.numPartitions = numPartitions;
-
-        zkClient = new ZkClient(this.zkAddress, ZkClient.DEFAULT_SESSION_TIMEOUT,
-                ZkClient.DEFAULT_CONNECTION_TIMEOUT, new ZNRecordSerializer());
-        zkHelixAdmin = new ZKHelixAdmin(zkClient);
-    }
-
-    public void setup() {
-        zkHelixAdmin.addCluster(clusterName, true);
-        zkHelixAdmin.addStateModelDef(clusterName, OnlineOfflineSMD.name, OnlineOfflineSMD.build());
-        logger.info("Cluster: " +  clusterName + ", has been added.");
-    }
-
-    public void disconnect() {
-        if (zkClient != null) {
-            zkClient.close();
-        }
-    }
-}
diff --git a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/HelixManager.java b/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/HelixManager.java
deleted file mode 100644
index e09f307..0000000
--- a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/HelixManager.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package org.apache.airavata.helix;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-public class HelixManager {
-    public static void main(String args[]) {
-        HelixCluster helixCluster = new HelixCluster("localhost:2199", "AiravataDemoCluster", 1);
-        helixCluster.setup();
-    }
-}
diff --git a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/WorkflowManager.java b/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/WorkflowManager.java
deleted file mode 100644
index 6866af3..0000000
--- a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/WorkflowManager.java
+++ /dev/null
@@ -1,86 +0,0 @@
-package org.apache.airavata.helix;
-
-import org.apache.airavata.helix.tasks.command.CommandTask;
-import org.apache.airavata.helix.tasks.DataCollectingTask;
-import org.apache.airavata.helix.tasks.DataPushingTask;
-import org.apache.helix.*;
-import org.apache.helix.task.*;
-import org.apache.log4j.LogManager;
-import org.apache.log4j.Logger;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-public class WorkflowManager {
-
-    private static final Logger logger = LogManager.getLogger(WorkflowManager.class);
-
-
-    public static void main(String args[]) {
-        Workflow workflow = createWorkflow().build();
-
-        org.apache.helix.HelixManager helixManager = HelixManagerFactory.getZKHelixManager("AiravataDemoCluster", "Admin",
-                InstanceType.SPECTATOR, "localhost:2199");
-
-        try {
-            helixManager.connect();
-            TaskDriver taskDriver = new TaskDriver(helixManager);
-
-            Runtime.getRuntime().addShutdownHook(
-                    new Thread() {
-                        @Override
-                        public void run() {
-                            helixManager.disconnect();
-                        }
-                    }
-            );
-
-            taskDriver.start(workflow);
-            logger.info("Started workflow");
-            TaskState taskState = taskDriver.pollForWorkflowState(workflow.getName(), TaskState.COMPLETED, TaskState.FAILED, TaskState.STOPPED, TaskState.ABORTED);
-            System.out.println("Task state " + taskState.name());
-
-        } catch (Exception ex) {
-            logger.error("Error in connect() for Admin, reason: " + ex, ex);
-        }
-    }
-
-    private static Workflow.Builder createWorkflow() {
-        List<TaskConfig> downloadDataTasks = new ArrayList<>();
-        downloadDataTasks.add(new TaskConfig.Builder().setTaskId("Download_Task").setCommand(DataCollectingTask.NAME).build());
-
-        List<TaskConfig> commandExecuteTasks = new ArrayList<>();
-        commandExecuteTasks.add(new TaskConfig.Builder().setTaskId("Command_Task").setCommand(CommandTask.NAME).build());
-
-        List<TaskConfig> pushDataTasks = new ArrayList<>();
-        pushDataTasks.add(new TaskConfig.Builder().setTaskId("Push_Task").setCommand(DataPushingTask.NAME).build());
-
-        JobConfig.Builder downloadDataJob = new JobConfig.Builder()
-                .addTaskConfigs(downloadDataTasks)
-                .setMaxAttemptsPerTask(3).setInstanceGroupTag("p1");
-
-        JobConfig.Builder commandExecuteJob = new JobConfig.Builder()
-                .addTaskConfigs(commandExecuteTasks)
-                .setMaxAttemptsPerTask(3).setInstanceGroupTag("p2");
-
-        JobConfig.Builder dataPushJob = new JobConfig.Builder()
-                .addTaskConfigs(pushDataTasks)
-                .setMaxAttemptsPerTask(3).setInstanceGroupTag("p3");
-
-        Workflow.Builder workflow = new Workflow.Builder("Airavata_Workflow3").setExpiry(0);
-        workflow.addJob("downloadDataJob", downloadDataJob);
-        workflow.addJob("commandExecuteJob", commandExecuteJob);
-        workflow.addJob("dataPushJob", dataPushJob);
-
-        workflow.addParentChildDependency("downloadDataJob", "commandExecuteJob");
-        workflow.addParentChildDependency("downloadDataJob", "dataPushJob");
-
-        return workflow;
-    }
-}
diff --git a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/DataCollectingTask.java b/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/DataCollectingTask.java
deleted file mode 100644
index 594ce35..0000000
--- a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/DataCollectingTask.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package org.apache.airavata.helix.tasks;
-
-import org.apache.helix.task.Task;
-import org.apache.helix.task.TaskCallbackContext;
-import org.apache.helix.task.TaskResult;
-import org.apache.helix.task.UserContentStore;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-public class DataCollectingTask extends UserContentStore implements Task {
-
-    public static final String NAME = "DATA_COLLECTING";
-
-    public DataCollectingTask(TaskCallbackContext callbackContext) {
-    }
-
-    public TaskResult run() {
-        System.out.println("Executing data collecting");
-        putUserContent("Key", "Hooo", Scope.WORKFLOW);
-
-        return new TaskResult(TaskResult.Status.COMPLETED, "HelixTaskB completed!");
-
-    }
-
-    public void cancel() {
-
-    }
-}
diff --git a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/DataPushingTask.java b/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/DataPushingTask.java
deleted file mode 100644
index fc7ee8b..0000000
--- a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/DataPushingTask.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package org.apache.airavata.helix.tasks;
-
-import org.apache.helix.task.Task;
-import org.apache.helix.task.TaskCallbackContext;
-import org.apache.helix.task.TaskResult;
-import org.apache.helix.task.UserContentStore;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-public class DataPushingTask extends UserContentStore implements Task {
-
-    public static final String NAME = "DATA_PUSHING";
-
-    public DataPushingTask(TaskCallbackContext callbackContext) {
-    }
-
-    public TaskResult run() {
-        System.out.println("Executing data pushing");
-        try {
-            Thread.currentThread().sleep(5000);
-        } catch (InterruptedException e) {
-            e.printStackTrace();
-        }
-        System.out.println("Continuing");
-        String key2 = getUserContent("Key2", Scope.WORKFLOW);
-
-        System.out.println(key2);
-        return new TaskResult(TaskResult.Status.COMPLETED, "HelixTaskB completed!");
-
-    }
-
-    public void cancel() {
-
-    }
-}
diff --git a/airavata-kubernetes/modules/helix-tasks/src/main/resources/log4j.properties b/airavata-kubernetes/modules/helix-tasks/src/main/resources/log4j.properties
deleted file mode 100644
index 5e31e3c..0000000
--- a/airavata-kubernetes/modules/helix-tasks/src/main/resources/log4j.properties
+++ /dev/null
@@ -1,9 +0,0 @@
-# Set root logger level to DEBUG and its only appender to A1.
-log4j.rootLogger=INFO, A1
-
-# A1 is set to be a ConsoleAppender.
-log4j.appender.A1=org.apache.log4j.ConsoleAppender
-
-# A1 uses PatternLayout.
-log4j.appender.A1.layout=org.apache.log4j.PatternLayout
-log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/helix-tasks/pom.xml b/airavata-kubernetes/modules/microservices/helix-controller/pom.xml
similarity index 82%
rename from airavata-kubernetes/modules/helix-tasks/pom.xml
rename to airavata-kubernetes/modules/microservices/helix-controller/pom.xml
index 568c922..9fe9f06 100644
--- a/airavata-kubernetes/modules/helix-tasks/pom.xml
+++ b/airavata-kubernetes/modules/microservices/helix-controller/pom.xml
@@ -6,11 +6,11 @@
         <artifactId>airavata-kubernetes</artifactId>
         <groupId>org.apache.airavata</groupId>
         <version>1.0-SNAPSHOT</version>
-        <relativePath>../../pom.xml</relativePath>
+        <relativePath>../../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
-    <artifactId>helix-tasks</artifactId>
+    <artifactId>helix-controller</artifactId>
 
     <dependencies>
         <dependency>
@@ -72,18 +72,4 @@
         </dependency>
     </dependencies>
 
-    <build>
-        <plugins>
-            <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-compiler-plugin</artifactId>
-                <version>3.5.1</version>
-                <configuration>
-                    <source>${java.version}</source>
-                    <target>${java.version}</target>
-                </configuration>
-            </plugin>
-        </plugins>
-    </build>
-
 </project>
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/HelixController.java b/airavata-kubernetes/modules/microservices/helix-controller/src/main/java/org/apache/airavata/helix/HelixController.java
similarity index 98%
rename from airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/HelixController.java
rename to airavata-kubernetes/modules/microservices/helix-controller/src/main/java/org/apache/airavata/helix/HelixController.java
index 8237d43..50fd82b 100644
--- a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/HelixController.java
+++ b/airavata-kubernetes/modules/microservices/helix-controller/src/main/java/org/apache/airavata/helix/HelixController.java
@@ -1,6 +1,5 @@
 package org.apache.airavata.helix;
 
-import org.apache.helix.*;
 import org.apache.helix.controller.HelixControllerMain;
 import org.apache.log4j.LogManager;
 import org.apache.log4j.Logger;
@@ -81,5 +80,4 @@ public class HelixController implements Runnable {
         HelixController helixController = new HelixController("localhost:2199", "AiravataDemoCluster", "AiravataController");
         helixController.start();
     }
-
 }
diff --git a/airavata-kubernetes/modules/microservices/task-scheduler/src/main/resources/application.yml b/airavata-kubernetes/modules/microservices/task-scheduler/src/main/resources/application.yml
deleted file mode 100644
index 069dd61..0000000
--- a/airavata-kubernetes/modules/microservices/task-scheduler/src/main/resources/application.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-kafka:
-  bootstrap-servers: kafka.default.svc.cluster.local:9092
-  topic:
-    helloworld: helloworld.t
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/microservices/tasks/command-executing-task/pom.xml b/airavata-kubernetes/modules/microservices/tasks/command-executing-task/pom.xml
deleted file mode 100644
index eff73f5..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/command-executing-task/pom.xml
+++ /dev/null
@@ -1,161 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-
-
-    Licensed to the Apache Software Foundation (ASF) under one
-    or more contributor license agreements.  See the NOTICE file
-    distributed with this work for additional information
-    regarding copyright ownership.  The ASF licenses this file
-    to you under the Apache License, Version 2.0 (the
-    "License"); you may not use this file except in compliance
-    with the License.  You may obtain a copy of the License at
-
-      http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing,
-    software distributed under the License is distributed on an
-    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-    KIND, either express or implied.  See the License for the
-    specific language governing permissions and limitations
-    under the License.
-
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-    <parent>
-        <artifactId>airavata-kubernetes</artifactId>
-        <groupId>org.apache.airavata</groupId>
-        <version>1.0-SNAPSHOT</version>
-        <relativePath>../../../../pom.xml</relativePath>
-    </parent>
-    <modelVersion>4.0.0</modelVersion>
-
-    <artifactId>command-executing-task</artifactId>
-
-    <dependencies>
-        <dependency>
-            <groupId>org.apache.airavata</groupId>
-            <artifactId>compute-resource-api</artifactId>
-            <version>1.0-SNAPSHOT</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.airavata</groupId>
-            <artifactId>api-resource</artifactId>
-            <version>1.0-SNAPSHOT</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.airavata</groupId>
-            <artifactId>task-api</artifactId>
-            <version>1.0-SNAPSHOT</version>
-        </dependency>
-
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-web</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-freemarker</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.kafka</groupId>
-            <artifactId>spring-kafka</artifactId>
-        </dependency>
-    </dependencies>
-
-    <build>
-        <plugins>
-            <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-compiler-plugin</artifactId>
-                <version>3.5.1</version>
-                <configuration>
-                    <source>${java.version}</source>
-                    <target>${java.version}</target>
-                </configuration>
-            </plugin>
-        </plugins>
-    </build>
-
-    <profiles>
-
-        <profile>
-            <id>jar</id>
-            <activation>
-                <activeByDefault>true</activeByDefault>
-            </activation>
-            <properties>
-                <artifact-packaging>jar</artifact-packaging>
-            </properties>
-            <build>
-                <plugins>
-                    <plugin>
-                        <groupId>org.springframework.boot</groupId>
-                        <artifactId>spring-boot-maven-plugin</artifactId>
-                        <version>1.4.3.RELEASE</version>
-                        <executions>
-                            <execution>
-                                <goals>
-                                    <goal>repackage</goal>
-                                </goals>
-                            </execution>
-                        </executions>
-                    </plugin>
-                    <!-- Create a docker image that runs the executable jar-->
-                    <plugin>
-                        <groupId>com.spotify</groupId>
-                        <artifactId>docker-maven-plugin</artifactId>
-                        <version>1.0.0</version>
-                        <configuration>
-                            <imageName>${docker.image.prefix}/job-submission-task</imageName>
-                            <baseImage>java:openjdk-8-jdk-alpine</baseImage>
-                            <entryPoint>["java","-jar","/${project.build.finalName}.jar"]</entryPoint>
-                            <resources>
-                                <resource>
-                                    <targetPath>/</targetPath>
-                                    <directory>${project.build.directory}</directory>
-                                    <include>${project.build.finalName}.jar</include>
-                                </resource>
-                            </resources>
-                        </configuration>
-                        <executions>
-                            <execution>
-                                <goals>
-                                    <goal>build</goal>
-                                </goals>
-                            </execution>
-                        </executions>
-                    </plugin>
-                </plugins>
-            </build>
-        </profile>
-
-        <profile>
-            <id>war</id>
-            <properties>
-                <artifact-packaging>war</artifact-packaging>
-            </properties>
-            <dependencies>
-                <dependency>
-                    <groupId>org.springframework.boot</groupId>
-                    <artifactId>spring-boot-starter-tomcat</artifactId>
-                    <scope>provided</scope>
-                </dependency>
-            </dependencies>
-            <build>
-                <plugins>
-                    <plugin>
-                        <groupId>org.springframework.boot</groupId>
-                        <artifactId>spring-boot-maven-plugin</artifactId>
-                        <version>1.4.3.RELEASE</version>
-                        <configuration>
-                            <!-- this will get rid of version info from war file name -->
-                            <finalName>job-submission</finalName>
-                        </configuration>
-                    </plugin>
-                </plugins>
-            </build>
-        </profile>
-    </profiles>
-</project>
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/microservices/tasks/command-executing-task/src/main/java/org/apache/airavata/k8s/task/job/Application.java b/airavata-kubernetes/modules/microservices/tasks/command-executing-task/src/main/java/org/apache/airavata/k8s/task/job/Application.java
deleted file mode 100644
index d11a7c5..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/command-executing-task/src/main/java/org/apache/airavata/k8s/task/job/Application.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/**
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.airavata.k8s.task.job;
-
-import org.apache.airavata.k8s.task.api.messaging.ReceiverConfig;
-import org.apache.airavata.k8s.task.api.messaging.SenderConfig;
-import org.springframework.boot.SpringApplication;
-import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.boot.web.client.RestTemplateBuilder;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.ComponentScan;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Import;
-import org.springframework.web.client.RestTemplate;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-@SpringBootApplication
-@Configuration
-@ComponentScan(basePackages = "org.apache.airavata.*")
-@EnableAutoConfiguration
-@Import({ReceiverConfig.class, SenderConfig.class})
-public class Application {
-
-    public static void main(String[] args) {
-        SpringApplication.run(Application.class, args);
-    }
-
-    @Bean
-    public RestTemplate restTemplate(RestTemplateBuilder builder) {
-        return builder.build();
-    }
-}
diff --git a/airavata-kubernetes/modules/microservices/tasks/command-executing-task/src/main/java/org/apache/airavata/k8s/task/job/CommandTaskInfo.java b/airavata-kubernetes/modules/microservices/tasks/command-executing-task/src/main/java/org/apache/airavata/k8s/task/job/CommandTaskInfo.java
deleted file mode 100644
index cbffce3..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/command-executing-task/src/main/java/org/apache/airavata/k8s/task/job/CommandTaskInfo.java
+++ /dev/null
@@ -1,59 +0,0 @@
-package org.apache.airavata.k8s.task.job;
-
-import org.apache.airavata.k8s.api.resources.task.type.TaskInputTypeResource;
-import org.apache.airavata.k8s.api.resources.task.type.TaskOutPortTypeResource;
-import org.apache.airavata.k8s.api.resources.task.type.TaskTypeResource;
-
-import java.util.Arrays;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-public class CommandTaskInfo {
-
-    public static final String COMMAND = "command";
-    public static final String ARGUMENTS = "arguments";
-    public static final String STD_OUT_PATH = "std_out_path";
-    public static final String STD_ERR_PATH = "std_err_path";
-    public static final String COMPUTE_RESOURCE = "compute_resource";
-
-    public static TaskTypeResource getTaskType() {
-        TaskTypeResource taskTypeResource = new TaskTypeResource();
-        taskTypeResource.setName("Command Execute");
-        taskTypeResource.setTopicName("airavata-command");
-        taskTypeResource.setIcon("assets/icons/ssh.png");
-        taskTypeResource.getInputTypes().addAll(
-                Arrays.asList(
-                        new TaskInputTypeResource()
-                                .setName(COMMAND)
-                                .setType("String")
-                                .setDefaultValue(""),
-                        new TaskInputTypeResource()
-                                .setName(ARGUMENTS)
-                                .setType("String"),
-                        new TaskInputTypeResource()
-                                .setName(COMPUTE_RESOURCE)
-                                .setType("Long"),
-                        new TaskInputTypeResource()
-                                .setName(STD_OUT_PATH)
-                                .setType("String"),
-                        new TaskInputTypeResource()
-                                .setName(STD_ERR_PATH)
-                                .setType("String")));
-
-        taskTypeResource.getOutPorts().addAll(
-                Arrays.asList(
-                        new TaskOutPortTypeResource()
-                                .setName("Out")
-                                .setOrder(0),
-                        new TaskOutPortTypeResource()
-                                .setName("Error")
-                                .setOrder(1))
-        );
-
-        return taskTypeResource;
-    }
-}
diff --git a/airavata-kubernetes/modules/microservices/tasks/command-executing-task/src/main/java/org/apache/airavata/k8s/task/job/config/RestConfig.java b/airavata-kubernetes/modules/microservices/tasks/command-executing-task/src/main/java/org/apache/airavata/k8s/task/job/config/RestConfig.java
deleted file mode 100644
index acbf74f..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/command-executing-task/src/main/java/org/apache/airavata/k8s/task/job/config/RestConfig.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package org.apache.airavata.k8s.task.job.config;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-public class RestConfig {
-}
diff --git a/airavata-kubernetes/modules/microservices/tasks/command-executing-task/src/main/java/org/apache/airavata/k8s/task/job/service/TaskExecutionService.java b/airavata-kubernetes/modules/microservices/tasks/command-executing-task/src/main/java/org/apache/airavata/k8s/task/job/service/TaskExecutionService.java
deleted file mode 100644
index 2fdb337..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/command-executing-task/src/main/java/org/apache/airavata/k8s/task/job/service/TaskExecutionService.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/**
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.airavata.k8s.task.job.service;
-
-import org.apache.airavata.k8s.api.resources.compute.ComputeResource;
-import org.apache.airavata.k8s.api.resources.task.TaskResource;
-import org.apache.airavata.k8s.api.resources.task.TaskStatusResource;
-import org.apache.airavata.k8s.api.resources.task.type.TaskTypeResource;
-import org.apache.airavata.k8s.compute.api.ExecutionResult;
-import org.apache.airavata.k8s.task.api.AbstractTaskExecutionService;
-import org.apache.airavata.k8s.task.api.TaskContext;
-import org.apache.airavata.k8s.task.api.messaging.KafkaSender;
-import org.apache.airavata.k8s.task.job.CommandTaskInfo;
-import org.springframework.stereotype.Service;
-import org.springframework.web.client.RestTemplate;
-
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-@Service
-public class TaskExecutionService extends AbstractTaskExecutionService {
-
-    public TaskExecutionService(RestTemplate restTemplate, KafkaSender kafkaSender) {
-        super(restTemplate, null, 10);
-    }
-
-    @Override
-    public TaskTypeResource getType() {
-        return CommandTaskInfo.getTaskType();
-    }
-
-    @Override
-    public void initializeParameters(TaskResource taskResource, TaskContext taskContext) throws Exception {
-
-        taskContext.getLocalContext().put(CommandTaskInfo.COMMAND, findInput(taskContext, taskResource, CommandTaskInfo.COMMAND, false));
-        taskContext.getLocalContext().put(CommandTaskInfo.ARGUMENTS, findInput(taskContext, taskResource, CommandTaskInfo.ARGUMENTS, true));
-        taskContext.getLocalContext().put(CommandTaskInfo.STD_OUT_PATH, findInput(taskContext, taskResource, CommandTaskInfo.STD_OUT_PATH, false));
-        taskContext.getLocalContext().put(CommandTaskInfo.STD_ERR_PATH, findInput(taskContext, taskResource, CommandTaskInfo.STD_ERR_PATH, false));
-
-        String computeId = findInput(taskContext, taskResource, CommandTaskInfo.COMPUTE_RESOURCE, false);
-        taskContext.getLocalContext().put(CommandTaskInfo.COMPUTE_RESOURCE, this.getRestTemplate().getForObject("http://" + this.getApiServerUrl()
-                + "/compute/" + Long.parseLong(computeId), ComputeResource.class));
-
-    }
-
-    @Override
-    public void executeTask(TaskResource taskResource, TaskContext taskContext) {
-
-        try {
-            String command = (String) taskContext.getLocalContext().get(CommandTaskInfo.COMMAND);
-            String arguments = (String) taskContext.getLocalContext().get(CommandTaskInfo.ARGUMENTS);
-            ComputeResource computeResource = (ComputeResource) taskContext.getLocalContext().get(CommandTaskInfo.COMPUTE_RESOURCE);
-            String stdOutPath = (String) taskContext.getLocalContext().get(CommandTaskInfo.STD_OUT_PATH);
-            String stdErrPath = (String) taskContext.getLocalContext().get(CommandTaskInfo.STD_ERR_PATH);
-            String stdOutSuffix = " > " + stdOutPath + " 2> " + stdErrPath;
-
-            publishTaskStatus(taskContext, TaskStatusResource.State.EXECUTING);
-
-            String finalCommand = command + (arguments != null ? arguments : "") + stdOutSuffix;
-
-            System.out.println("Executing command " + finalCommand);
-
-            ExecutionResult executionResult = fetchComputeResourceOperation(computeResource).executeCommand(finalCommand);
-
-            if (executionResult.getExitStatus() == 0) {
-                finishTaskExecution(taskContext, taskResource, "Out", TaskStatusResource.State.COMPLETED, "Task completed");
-            } else if (executionResult.getExitStatus() == -1) {
-                publishTaskStatus(taskContext, TaskStatusResource.State.FAILED, "Process didn't exit successfully");
-            } else {
-                publishTaskStatus(taskContext, TaskStatusResource.State.FAILED, "Process exited with error status " + executionResult.getExitStatus());
-            }
-
-        } catch (Exception e) {
-
-            e.printStackTrace();
-            publishTaskStatus(taskContext, TaskStatusResource.State.FAILED, e.getMessage());
-        }
-    }
-}
diff --git a/airavata-kubernetes/modules/microservices/tasks/command-executing-task/src/main/resources/application.properties b/airavata-kubernetes/modules/microservices/tasks/command-executing-task/src/main/resources/application.properties
deleted file mode 100644
index 188693e..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/command-executing-task/src/main/resources/application.properties
+++ /dev/null
@@ -1,5 +0,0 @@
-server.port = 8491
-api.server.url = api-server.default.svc.cluster.local:8080
-task.group.name = command-execution
-task.event.topic.name = airavata-task-event
-task.read.topic.name = airavata-command
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/microservices/tasks/command-executing-task/src/main/resources/application.yml b/airavata-kubernetes/modules/microservices/tasks/command-executing-task/src/main/resources/application.yml
deleted file mode 100644
index 069dd61..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/command-executing-task/src/main/resources/application.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-kafka:
-  bootstrap-servers: kafka.default.svc.cluster.local:9092
-  topic:
-    helloworld: helloworld.t
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/task-api/pom.xml b/airavata-kubernetes/modules/microservices/tasks/command-task/pom.xml
similarity index 59%
copy from airavata-kubernetes/modules/task-api/pom.xml
copy to airavata-kubernetes/modules/microservices/tasks/command-task/pom.xml
index d2621fc..a08c014 100644
--- a/airavata-kubernetes/modules/task-api/pom.xml
+++ b/airavata-kubernetes/modules/microservices/tasks/command-task/pom.xml
@@ -6,36 +6,11 @@
         <artifactId>airavata-kubernetes</artifactId>
         <groupId>org.apache.airavata</groupId>
         <version>1.0-SNAPSHOT</version>
-        <relativePath>../../pom.xml</relativePath>
+        <relativePath>../../../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
-    <artifactId>task-api</artifactId>
-
-    <dependencies>
-        <dependency>
-            <groupId>org.apache.airavata</groupId>
-            <artifactId>api-resource</artifactId>
-            <version>1.0-SNAPSHOT</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.airavata</groupId>
-            <artifactId>compute-resource-api</artifactId>
-            <version>1.0-SNAPSHOT</version>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-web</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-freemarker</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.kafka</groupId>
-            <artifactId>spring-kafka</artifactId>
-        </dependency>
-    </dependencies>
+    <artifactId>command-task</artifactId>
 
     <build>
         <plugins>
@@ -51,4 +26,17 @@
         </plugins>
     </build>
 
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.airavata</groupId>
+            <artifactId>helix-task-api</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.helix</groupId>
+            <artifactId>helix-core</artifactId>
+            <version>0.6.7</version>
+        </dependency>
+    </dependencies>
+
 </project>
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/command/CommandTask.java b/airavata-kubernetes/modules/microservices/tasks/command-task/src/main/java/org/apache/airavata/helix/task/command/CommandTask.java
similarity index 98%
rename from airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/command/CommandTask.java
rename to airavata-kubernetes/modules/microservices/tasks/command-task/src/main/java/org/apache/airavata/helix/task/command/CommandTask.java
index 5aae5b7..16c600e 100644
--- a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/command/CommandTask.java
+++ b/airavata-kubernetes/modules/microservices/tasks/command-task/src/main/java/org/apache/airavata/helix/task/command/CommandTask.java
@@ -1,6 +1,6 @@
-package org.apache.airavata.helix.tasks.command;
+package org.apache.airavata.helix.task.command;
 
-import org.apache.airavata.helix.tasks.AbstractTask;
+import org.apache.airavata.helix.api.AbstractTask;
 import org.apache.airavata.k8s.api.resources.compute.ComputeResource;
 import org.apache.airavata.k8s.api.resources.task.TaskStatusResource;
 import org.apache.airavata.k8s.api.resources.task.type.TaskInputTypeResource;
diff --git a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/command/Participant.java b/airavata-kubernetes/modules/microservices/tasks/command-task/src/main/java/org/apache/airavata/helix/task/command/Participant.java
similarity index 87%
rename from airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/command/Participant.java
rename to airavata-kubernetes/modules/microservices/tasks/command-task/src/main/java/org/apache/airavata/helix/task/command/Participant.java
index f852d8f..742ed88 100644
--- a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/command/Participant.java
+++ b/airavata-kubernetes/modules/microservices/tasks/command-task/src/main/java/org/apache/airavata/helix/task/command/Participant.java
@@ -1,8 +1,6 @@
-package org.apache.airavata.helix.tasks.command;
+package org.apache.airavata.helix.task.command;
 
-import org.apache.airavata.helix.HelixParticipant;
-import org.apache.airavata.helix.tasks.DataCollectingTask;
-import org.apache.airavata.helix.tasks.DataPushingTask;
+import org.apache.airavata.helix.api.HelixParticipant;
 import org.apache.airavata.k8s.api.resources.task.type.TaskTypeResource;
 import org.apache.helix.task.Task;
 import org.apache.helix.task.TaskCallbackContext;
diff --git a/airavata-kubernetes/modules/microservices/tasks/data-collecting-task/pom.xml b/airavata-kubernetes/modules/microservices/tasks/data-collecting-task/pom.xml
deleted file mode 100644
index fe50fe8..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/data-collecting-task/pom.xml
+++ /dev/null
@@ -1,161 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-
-
-    Licensed to the Apache Software Foundation (ASF) under one
-    or more contributor license agreements.  See the NOTICE file
-    distributed with this work for additional information
-    regarding copyright ownership.  The ASF licenses this file
-    to you under the Apache License, Version 2.0 (the
-    "License"); you may not use this file except in compliance
-    with the License.  You may obtain a copy of the License at
-
-      http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing,
-    software distributed under the License is distributed on an
-    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-    KIND, either express or implied.  See the License for the
-    specific language governing permissions and limitations
-    under the License.
-
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-    <parent>
-        <artifactId>airavata-kubernetes</artifactId>
-        <groupId>org.apache.airavata</groupId>
-        <version>1.0-SNAPSHOT</version>
-        <relativePath>../../../../pom.xml</relativePath>
-    </parent>
-    <modelVersion>4.0.0</modelVersion>
-
-    <artifactId>data-collecting-task</artifactId>
-
-    <dependencies>
-        <dependency>
-            <groupId>org.apache.airavata</groupId>
-            <artifactId>compute-resource-api</artifactId>
-            <version>1.0-SNAPSHOT</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.airavata</groupId>
-            <artifactId>api-resource</artifactId>
-            <version>1.0-SNAPSHOT</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.airavata</groupId>
-            <artifactId>task-api</artifactId>
-            <version>1.0-SNAPSHOT</version>
-        </dependency>
-
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-web</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-freemarker</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.kafka</groupId>
-            <artifactId>spring-kafka</artifactId>
-        </dependency>
-    </dependencies>
-
-    <build>
-        <plugins>
-            <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-compiler-plugin</artifactId>
-                <version>3.5.1</version>
-                <configuration>
-                    <source>${java.version}</source>
-                    <target>${java.version}</target>
-                </configuration>
-            </plugin>
-        </plugins>
-    </build>
-
-    <profiles>
-
-        <profile>
-            <id>jar</id>
-            <activation>
-                <activeByDefault>true</activeByDefault>
-            </activation>
-            <properties>
-                <artifact-packaging>jar</artifact-packaging>
-            </properties>
-            <build>
-                <plugins>
-                    <plugin>
-                        <groupId>org.springframework.boot</groupId>
-                        <artifactId>spring-boot-maven-plugin</artifactId>
-                        <version>1.4.3.RELEASE</version>
-                        <executions>
-                            <execution>
-                                <goals>
-                                    <goal>repackage</goal>
-                                </goals>
-                            </execution>
-                        </executions>
-                    </plugin>
-                    <!-- Create a docker image that runs the executable jar-->
-                    <plugin>
-                        <groupId>com.spotify</groupId>
-                        <artifactId>docker-maven-plugin</artifactId>
-                        <version>1.0.0</version>
-                        <configuration>
-                            <imageName>${docker.image.prefix}/egress-staging-task</imageName>
-                            <baseImage>java:openjdk-8-jdk-alpine</baseImage>
-                            <entryPoint>["java","-jar","/${project.build.finalName}.jar"]</entryPoint>
-                            <resources>
-                                <resource>
-                                    <targetPath>/</targetPath>
-                                    <directory>${project.build.directory}</directory>
-                                    <include>${project.build.finalName}.jar</include>
-                                </resource>
-                            </resources>
-                        </configuration>
-                        <executions>
-                            <execution>
-                                <goals>
-                                    <goal>build</goal>
-                                </goals>
-                            </execution>
-                        </executions>
-                    </plugin>
-                </plugins>
-            </build>
-        </profile>
-
-        <profile>
-            <id>war</id>
-            <properties>
-                <artifact-packaging>war</artifact-packaging>
-            </properties>
-            <dependencies>
-                <dependency>
-                    <groupId>org.springframework.boot</groupId>
-                    <artifactId>spring-boot-starter-tomcat</artifactId>
-                    <scope>provided</scope>
-                </dependency>
-            </dependencies>
-            <build>
-                <plugins>
-                    <plugin>
-                        <groupId>org.springframework.boot</groupId>
-                        <artifactId>spring-boot-maven-plugin</artifactId>
-                        <version>1.4.3.RELEASE</version>
-                        <configuration>
-                            <!-- this will get rid of version info from war file name -->
-                            <finalName>egress-staging</finalName>
-                        </configuration>
-                    </plugin>
-                </plugins>
-            </build>
-        </profile>
-    </profiles>
-</project>
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/microservices/tasks/data-collecting-task/src/main/java/org/apacher/airavata/k8s/task/egress/Application.java b/airavata-kubernetes/modules/microservices/tasks/data-collecting-task/src/main/java/org/apacher/airavata/k8s/task/egress/Application.java
deleted file mode 100644
index 1642d18..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/data-collecting-task/src/main/java/org/apacher/airavata/k8s/task/egress/Application.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/**
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apacher.airavata.k8s.task.egress;
-
-import org.apache.airavata.k8s.task.api.messaging.ReceiverConfig;
-import org.apache.airavata.k8s.task.api.messaging.SenderConfig;
-import org.springframework.boot.SpringApplication;
-import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.boot.web.client.RestTemplateBuilder;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.ComponentScan;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Import;
-import org.springframework.web.client.RestTemplate;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-@SpringBootApplication
-@Configuration
-@ComponentScan
-@EnableAutoConfiguration
-@Import({ReceiverConfig.class, SenderConfig.class})
-public class Application {
-
-    public static void main(String[] args) {
-        SpringApplication.run(Application.class, args);
-    }
-
-    @Bean
-    public RestTemplate restTemplate(RestTemplateBuilder builder) {
-        return builder.build();
-    }
-}
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/microservices/tasks/data-collecting-task/src/main/java/org/apacher/airavata/k8s/task/egress/DataCollectingTaskInfo.java b/airavata-kubernetes/modules/microservices/tasks/data-collecting-task/src/main/java/org/apacher/airavata/k8s/task/egress/DataCollectingTaskInfo.java
deleted file mode 100644
index 73a354f..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/data-collecting-task/src/main/java/org/apacher/airavata/k8s/task/egress/DataCollectingTaskInfo.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package org.apacher.airavata.k8s.task.egress;
-
-import org.apache.airavata.k8s.api.resources.task.type.TaskInputTypeResource;
-import org.apache.airavata.k8s.api.resources.task.type.TaskOutPortTypeResource;
-import org.apache.airavata.k8s.api.resources.task.type.TaskTypeResource;
-
-import java.util.Arrays;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-public class DataCollectingTaskInfo {
-    public static final String REMOTE_SOURCE_PATH = "remote_source_path";
-    public static final String COMPUTE_RESOURCE = "compute_resource";
-    public static final String IDENTIFIER = "identifier";
-
-    public static TaskTypeResource getTaskType() {
-        TaskTypeResource taskTypeResource = new TaskTypeResource();
-        taskTypeResource.setName("Data Collect");
-        taskTypeResource.setTopicName("airavata-data-collect");
-        taskTypeResource.setIcon("assets/icons/copy.png");
-        taskTypeResource.getInputTypes().addAll(
-                Arrays.asList(
-                        new TaskInputTypeResource()
-                                .setName(REMOTE_SOURCE_PATH)
-                                .setType("String")
-                                .setDefaultValue(""),
-                        new TaskInputTypeResource()
-                                .setName(IDENTIFIER)
-                                .setType("String"),
-                        new TaskInputTypeResource()
-                                .setName(COMPUTE_RESOURCE)
-                                .setType("Long")));
-
-        taskTypeResource.getOutPorts().addAll(
-                Arrays.asList(
-                        new TaskOutPortTypeResource()
-                                .setName("Out")
-                                .setOrder(0),
-                        new TaskOutPortTypeResource()
-                                .setName("Error")
-                                .setOrder(1))
-        );
-
-        return taskTypeResource;
-    }
-}
diff --git a/airavata-kubernetes/modules/microservices/tasks/data-collecting-task/src/main/java/org/apacher/airavata/k8s/task/egress/service/TaskExecutionService.java b/airavata-kubernetes/modules/microservices/tasks/data-collecting-task/src/main/java/org/apacher/airavata/k8s/task/egress/service/TaskExecutionService.java
deleted file mode 100644
index c3ed302..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/data-collecting-task/src/main/java/org/apacher/airavata/k8s/task/egress/service/TaskExecutionService.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/**
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apacher.airavata.k8s.task.egress.service;
-
-import org.apache.airavata.k8s.api.resources.compute.ComputeResource;
-import org.apache.airavata.k8s.api.resources.task.TaskResource;
-import org.apache.airavata.k8s.api.resources.task.TaskStatusResource;
-import org.apache.airavata.k8s.api.resources.task.type.TaskTypeResource;
-import org.apache.airavata.k8s.task.api.AbstractTaskExecutionService;
-import org.apache.airavata.k8s.task.api.TaskContext;
-import org.apache.airavata.k8s.task.api.messaging.KafkaSender;
-import org.apacher.airavata.k8s.task.egress.DataCollectingTaskInfo;
-import org.springframework.core.io.FileSystemResource;
-import org.springframework.http.*;
-import org.springframework.stereotype.Service;
-import org.springframework.util.LinkedMultiValueMap;
-import org.springframework.web.client.RestTemplate;
-
-import java.util.UUID;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-@Service
-public class TaskExecutionService extends AbstractTaskExecutionService {
-
-    public TaskExecutionService(RestTemplate restTemplate, KafkaSender kafkaSender) {
-        super(restTemplate, kafkaSender, 10);
-    }
-
-    @Override
-    public TaskTypeResource getType() {
-        return DataCollectingTaskInfo.getTaskType();
-    }
-
-    @Override
-    public void initializeParameters(TaskResource taskResource, TaskContext taskContext) throws Exception {
-
-        taskContext.getLocalContext().put(DataCollectingTaskInfo.REMOTE_SOURCE_PATH, findInput(taskContext, taskResource, DataCollectingTaskInfo.REMOTE_SOURCE_PATH, false));
-        taskContext.getLocalContext().put(DataCollectingTaskInfo.IDENTIFIER, findInput(taskContext, taskResource, DataCollectingTaskInfo.IDENTIFIER, false));
-
-        String computeId = findInput(taskContext, taskResource, DataCollectingTaskInfo.COMPUTE_RESOURCE, false);
-        taskContext.getLocalContext().put(DataCollectingTaskInfo.COMPUTE_RESOURCE, this.getRestTemplate().getForObject("http://" + this.getApiServerUrl()
-                + "/compute/" + Long.parseLong(computeId), ComputeResource.class));
-
-    }
-
-    public void executeTask(TaskResource taskResource, TaskContext taskContext) {
-
-        try {
-
-            ComputeResource computeResource = (ComputeResource) taskContext.getLocalContext().get(DataCollectingTaskInfo.COMPUTE_RESOURCE);
-            String identifier = (String) taskContext.getLocalContext().get(DataCollectingTaskInfo.IDENTIFIER);
-            String remoteSourcePath = (String) taskContext.getLocalContext().get(DataCollectingTaskInfo.REMOTE_SOURCE_PATH);
-
-            publishTaskStatus(taskContext, TaskStatusResource.State.EXECUTING);
-
-            String temporaryFile = "/tmp/" + UUID.randomUUID().toString();
-            System.out.println("Downloading " + remoteSourcePath + " to " + temporaryFile + " from compute resource "
-                    + computeResource.getName());
-
-            fetchComputeResourceOperation(computeResource).transferDataOut(remoteSourcePath, temporaryFile, "SCP");
-
-            LinkedMultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
-            map.add("file", new FileSystemResource(temporaryFile));
-            HttpHeaders headers = new HttpHeaders();
-            headers.setContentType(MediaType.MULTIPART_FORM_DATA);
-
-            HttpEntity<LinkedMultiValueMap<String, Object>> requestEntity = new HttpEntity<>(map, headers);
-
-            System.out.println("Uploading data file with task id " + taskResource.getId() + " and identifier "
-                    + identifier + " to data store");
-
-            getRestTemplate().exchange("http://" + getApiServerUrl() + "/data/" + taskResource.getId()+ "/"
-                            + identifier + "/upload", HttpMethod.POST, requestEntity, Long.class);
-
-            finishTaskExecution(taskContext, taskResource, "Out", TaskStatusResource.State.COMPLETED, "Task completed");
-
-        } catch (Exception e) {
-            e.printStackTrace();
-            publishTaskStatus(taskContext, TaskStatusResource.State.FAILED, e.getMessage());
-
-        }
-    }
-
-}
diff --git a/airavata-kubernetes/modules/microservices/tasks/data-collecting-task/src/main/resources/application.properties b/airavata-kubernetes/modules/microservices/tasks/data-collecting-task/src/main/resources/application.properties
deleted file mode 100644
index 3b443c2..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/data-collecting-task/src/main/resources/application.properties
+++ /dev/null
@@ -1,5 +0,0 @@
-server.port = 8691
-api.server.url = api-server.default.svc.cluster.local:8080
-task.group.name = egress-staging
-task.event.topic.name = airavata-task-event
-task.read.topic.name = airavata-task-egress-staging
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/microservices/tasks/data-collecting-task/src/main/resources/application.yml b/airavata-kubernetes/modules/microservices/tasks/data-collecting-task/src/main/resources/application.yml
deleted file mode 100644
index 069dd61..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/data-collecting-task/src/main/resources/application.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-kafka:
-  bootstrap-servers: kafka.default.svc.cluster.local:9092
-  topic:
-    helloworld: helloworld.t
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/task-api/pom.xml b/airavata-kubernetes/modules/microservices/tasks/data-in-task/pom.xml
similarity index 59%
copy from airavata-kubernetes/modules/task-api/pom.xml
copy to airavata-kubernetes/modules/microservices/tasks/data-in-task/pom.xml
index d2621fc..866ef14 100644
--- a/airavata-kubernetes/modules/task-api/pom.xml
+++ b/airavata-kubernetes/modules/microservices/tasks/data-in-task/pom.xml
@@ -6,36 +6,11 @@
         <artifactId>airavata-kubernetes</artifactId>
         <groupId>org.apache.airavata</groupId>
         <version>1.0-SNAPSHOT</version>
-        <relativePath>../../pom.xml</relativePath>
+        <relativePath>../../../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
-    <artifactId>task-api</artifactId>
-
-    <dependencies>
-        <dependency>
-            <groupId>org.apache.airavata</groupId>
-            <artifactId>api-resource</artifactId>
-            <version>1.0-SNAPSHOT</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.airavata</groupId>
-            <artifactId>compute-resource-api</artifactId>
-            <version>1.0-SNAPSHOT</version>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-web</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-freemarker</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.kafka</groupId>
-            <artifactId>spring-kafka</artifactId>
-        </dependency>
-    </dependencies>
+    <artifactId>data-in-task</artifactId>
 
     <build>
         <plugins>
@@ -51,4 +26,16 @@
         </plugins>
     </build>
 
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.airavata</groupId>
+            <artifactId>helix-task-api</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.helix</groupId>
+            <artifactId>helix-core</artifactId>
+            <version>0.6.7</version>
+        </dependency>
+    </dependencies>
 </project>
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/datain/DataInputTask.java b/airavata-kubernetes/modules/microservices/tasks/data-in-task/src/main/java/org/apache/airavata/helix/task/datain/DataInputTask.java
similarity index 97%
rename from airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/datain/DataInputTask.java
rename to airavata-kubernetes/modules/microservices/tasks/data-in-task/src/main/java/org/apache/airavata/helix/task/datain/DataInputTask.java
index 9d4a27e..00aeadc 100644
--- a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/datain/DataInputTask.java
+++ b/airavata-kubernetes/modules/microservices/tasks/data-in-task/src/main/java/org/apache/airavata/helix/task/datain/DataInputTask.java
@@ -1,6 +1,6 @@
-package org.apache.airavata.helix.tasks.datain;
+package org.apache.airavata.helix.task.datain;
 
-import org.apache.airavata.helix.tasks.AbstractTask;
+import org.apache.airavata.helix.api.AbstractTask;
 import org.apache.airavata.k8s.api.resources.compute.ComputeResource;
 import org.apache.airavata.k8s.api.resources.task.TaskStatusResource;
 import org.apache.airavata.k8s.api.resources.task.type.TaskInputTypeResource;
diff --git a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/datain/Participant.java b/airavata-kubernetes/modules/microservices/tasks/data-in-task/src/main/java/org/apache/airavata/helix/task/datain/Participant.java
similarity index 90%
rename from airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/datain/Participant.java
rename to airavata-kubernetes/modules/microservices/tasks/data-in-task/src/main/java/org/apache/airavata/helix/task/datain/Participant.java
index f06f56b..cfbe86a 100644
--- a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/datain/Participant.java
+++ b/airavata-kubernetes/modules/microservices/tasks/data-in-task/src/main/java/org/apache/airavata/helix/task/datain/Participant.java
@@ -1,7 +1,6 @@
-package org.apache.airavata.helix.tasks.datain;
+package org.apache.airavata.helix.task.datain;
 
-import org.apache.airavata.helix.HelixParticipant;
-import org.apache.airavata.helix.tasks.command.CommandTask;
+import org.apache.airavata.helix.api.HelixParticipant;
 import org.apache.airavata.k8s.api.resources.task.type.TaskTypeResource;
 import org.apache.helix.task.Task;
 import org.apache.helix.task.TaskCallbackContext;
diff --git a/airavata-kubernetes/modules/task-api/pom.xml b/airavata-kubernetes/modules/microservices/tasks/data-out-task/pom.xml
similarity index 59%
rename from airavata-kubernetes/modules/task-api/pom.xml
rename to airavata-kubernetes/modules/microservices/tasks/data-out-task/pom.xml
index d2621fc..e88a3a0 100644
--- a/airavata-kubernetes/modules/task-api/pom.xml
+++ b/airavata-kubernetes/modules/microservices/tasks/data-out-task/pom.xml
@@ -6,36 +6,11 @@
         <artifactId>airavata-kubernetes</artifactId>
         <groupId>org.apache.airavata</groupId>
         <version>1.0-SNAPSHOT</version>
-        <relativePath>../../pom.xml</relativePath>
+        <relativePath>../../../../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
-    <artifactId>task-api</artifactId>
-
-    <dependencies>
-        <dependency>
-            <groupId>org.apache.airavata</groupId>
-            <artifactId>api-resource</artifactId>
-            <version>1.0-SNAPSHOT</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.airavata</groupId>
-            <artifactId>compute-resource-api</artifactId>
-            <version>1.0-SNAPSHOT</version>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-web</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-freemarker</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.kafka</groupId>
-            <artifactId>spring-kafka</artifactId>
-        </dependency>
-    </dependencies>
+    <artifactId>data-out-task</artifactId>
 
     <build>
         <plugins>
@@ -51,4 +26,17 @@
         </plugins>
     </build>
 
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.airavata</groupId>
+            <artifactId>helix-task-api</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.helix</groupId>
+            <artifactId>helix-core</artifactId>
+            <version>0.6.7</version>
+        </dependency>
+    </dependencies>
+
 </project>
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/dataout/DataOutputTask.java b/airavata-kubernetes/modules/microservices/tasks/data-out-task/src/main/java/org/apache/airavata/helix/task/datain/dataout/DataOutputTask.java
similarity index 98%
rename from airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/dataout/DataOutputTask.java
rename to airavata-kubernetes/modules/microservices/tasks/data-out-task/src/main/java/org/apache/airavata/helix/task/datain/dataout/DataOutputTask.java
index fb696b2..8426c90 100644
--- a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/dataout/DataOutputTask.java
+++ b/airavata-kubernetes/modules/microservices/tasks/data-out-task/src/main/java/org/apache/airavata/helix/task/datain/dataout/DataOutputTask.java
@@ -1,6 +1,6 @@
-package org.apache.airavata.helix.tasks.dataout;
+package org.apache.airavata.helix.task.datain.dataout;
 
-import org.apache.airavata.helix.tasks.AbstractTask;
+import org.apache.airavata.helix.api.AbstractTask;
 import org.apache.airavata.k8s.api.resources.compute.ComputeResource;
 import org.apache.airavata.k8s.api.resources.task.TaskStatusResource;
 import org.apache.airavata.k8s.api.resources.task.type.TaskInputTypeResource;
diff --git a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/dataout/Participant.java b/airavata-kubernetes/modules/microservices/tasks/data-out-task/src/main/java/org/apache/airavata/helix/task/datain/dataout/Participant.java
similarity index 93%
rename from airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/dataout/Participant.java
rename to airavata-kubernetes/modules/microservices/tasks/data-out-task/src/main/java/org/apache/airavata/helix/task/datain/dataout/Participant.java
index b226511..8177eb4 100644
--- a/airavata-kubernetes/modules/helix-tasks/src/main/java/org/apache/airavata/helix/tasks/dataout/Participant.java
+++ b/airavata-kubernetes/modules/microservices/tasks/data-out-task/src/main/java/org/apache/airavata/helix/task/datain/dataout/Participant.java
@@ -1,6 +1,6 @@
-package org.apache.airavata.helix.tasks.dataout;
+package org.apache.airavata.helix.task.datain.dataout;
 
-import org.apache.airavata.helix.HelixParticipant;
+import org.apache.airavata.helix.api.HelixParticipant;
 import org.apache.airavata.k8s.api.resources.task.type.TaskTypeResource;
 import org.apache.helix.task.Task;
 import org.apache.helix.task.TaskCallbackContext;
diff --git a/airavata-kubernetes/modules/microservices/tasks/data-pushing-task/pom.xml b/airavata-kubernetes/modules/microservices/tasks/data-pushing-task/pom.xml
deleted file mode 100644
index 533914f..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/data-pushing-task/pom.xml
+++ /dev/null
@@ -1,161 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-
-
-    Licensed to the Apache Software Foundation (ASF) under one
-    or more contributor license agreements.  See the NOTICE file
-    distributed with this work for additional information
-    regarding copyright ownership.  The ASF licenses this file
-    to you under the Apache License, Version 2.0 (the
-    "License"); you may not use this file except in compliance
-    with the License.  You may obtain a copy of the License at
-
-      http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing,
-    software distributed under the License is distributed on an
-    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-    KIND, either express or implied.  See the License for the
-    specific language governing permissions and limitations
-    under the License.
-
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0"
-         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-    <parent>
-        <artifactId>airavata-kubernetes</artifactId>
-        <groupId>org.apache.airavata</groupId>
-        <version>1.0-SNAPSHOT</version>
-        <relativePath>../../../../pom.xml</relativePath>
-    </parent>
-    <modelVersion>4.0.0</modelVersion>
-
-    <artifactId>data-pushing-task</artifactId>
-
-    <dependencies>
-        <dependency>
-            <groupId>org.apache.airavata</groupId>
-            <artifactId>compute-resource-api</artifactId>
-            <version>1.0-SNAPSHOT</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.airavata</groupId>
-            <artifactId>api-resource</artifactId>
-            <version>1.0-SNAPSHOT</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.airavata</groupId>
-            <artifactId>task-api</artifactId>
-            <version>1.0-SNAPSHOT</version>
-        </dependency>
-
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-web</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-freemarker</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.kafka</groupId>
-            <artifactId>spring-kafka</artifactId>
-        </dependency>
-    </dependencies>
-
-    <build>
-        <plugins>
-            <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-compiler-plugin</artifactId>
-                <version>3.5.1</version>
-                <configuration>
-                    <source>${java.version}</source>
-                    <target>${java.version}</target>
-                </configuration>
-            </plugin>
-        </plugins>
-    </build>
-
-    <profiles>
-
-        <profile>
-            <id>jar</id>
-            <activation>
-                <activeByDefault>true</activeByDefault>
-            </activation>
-            <properties>
-                <artifact-packaging>jar</artifact-packaging>
-            </properties>
-            <build>
-                <plugins>
-                    <plugin>
-                        <groupId>org.springframework.boot</groupId>
-                        <artifactId>spring-boot-maven-plugin</artifactId>
-                        <version>1.4.3.RELEASE</version>
-                        <executions>
-                            <execution>
-                                <goals>
-                                    <goal>repackage</goal>
-                                </goals>
-                            </execution>
-                        </executions>
-                    </plugin>
-                    <!-- Create a docker image that runs the executable jar-->
-                    <plugin>
-                        <groupId>com.spotify</groupId>
-                        <artifactId>docker-maven-plugin</artifactId>
-                        <version>1.0.0</version>
-                        <configuration>
-                            <imageName>${docker.image.prefix}/ingress-staging-task</imageName>
-                            <baseImage>java:openjdk-8-jdk-alpine</baseImage>
-                            <entryPoint>["java","-jar","/${project.build.finalName}.jar"]</entryPoint>
-                            <resources>
-                                <resource>
-                                    <targetPath>/</targetPath>
-                                    <directory>${project.build.directory}</directory>
-                                    <include>${project.build.finalName}.jar</include>
-                                </resource>
-                            </resources>
-                        </configuration>
-                        <executions>
-                            <execution>
-                                <goals>
-                                    <goal>build</goal>
-                                </goals>
-                            </execution>
-                        </executions>
-                    </plugin>
-                </plugins>
-            </build>
-        </profile>
-
-        <profile>
-            <id>war</id>
-            <properties>
-                <artifact-packaging>war</artifact-packaging>
-            </properties>
-            <dependencies>
-                <dependency>
-                    <groupId>org.springframework.boot</groupId>
-                    <artifactId>spring-boot-starter-tomcat</artifactId>
-                    <scope>provided</scope>
-                </dependency>
-            </dependencies>
-            <build>
-                <plugins>
-                    <plugin>
-                        <groupId>org.springframework.boot</groupId>
-                        <artifactId>spring-boot-maven-plugin</artifactId>
-                        <version>1.4.3.RELEASE</version>
-                        <configuration>
-                            <!-- this will get rid of version info from war file name -->
-                            <finalName>ingress-staging</finalName>
-                        </configuration>
-                    </plugin>
-                </plugins>
-            </build>
-        </profile>
-    </profiles>
-</project>
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/microservices/tasks/data-pushing-task/src/main/java/org/apache/airavata/k8s/task/ingress/Application.java b/airavata-kubernetes/modules/microservices/tasks/data-pushing-task/src/main/java/org/apache/airavata/k8s/task/ingress/Application.java
deleted file mode 100644
index b51e719..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/data-pushing-task/src/main/java/org/apache/airavata/k8s/task/ingress/Application.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/**
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.airavata.k8s.task.ingress;
-
-import org.springframework.boot.SpringApplication;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.boot.web.client.RestTemplateBuilder;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.ComponentScan;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.web.client.RestTemplate;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-@SpringBootApplication
-@Configuration
-@ComponentScan
-public class Application {
-
-    public static void main(String[] args) {
-        SpringApplication.run(Application.class, args);
-    }
-
-    @Bean
-    public RestTemplate restTemplate(RestTemplateBuilder builder) {
-        return builder.build();
-    }
-}
diff --git a/airavata-kubernetes/modules/microservices/tasks/data-pushing-task/src/main/java/org/apache/airavata/k8s/task/ingress/service/TaskExecutionService.java b/airavata-kubernetes/modules/microservices/tasks/data-pushing-task/src/main/java/org/apache/airavata/k8s/task/ingress/service/TaskExecutionService.java
deleted file mode 100644
index 88de3a5..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/data-pushing-task/src/main/java/org/apache/airavata/k8s/task/ingress/service/TaskExecutionService.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/**
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.airavata.k8s.task.ingress.service;
-
-import org.apache.airavata.k8s.api.resources.compute.ComputeResource;
-import org.apache.airavata.k8s.api.resources.task.TaskResource;
-import org.apache.airavata.k8s.api.resources.task.TaskStatusResource;
-import org.apache.airavata.k8s.api.resources.task.type.TaskTypeResource;
-import org.apache.airavata.k8s.task.api.AbstractTaskExecutionService;
-import org.apache.airavata.k8s.task.api.TaskContext;
-import org.apache.airavata.k8s.task.api.messaging.KafkaSender;
-import org.springframework.stereotype.Service;
-import org.springframework.web.client.RestTemplate;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-@Service
-public class TaskExecutionService extends AbstractTaskExecutionService {
-
-    private final String COMPUTE_RESOURCE =  "compute_resource";
-    private final String REMOTE_TARGET_PATH = "remote_target_path";
-    private final String DATA_LOCATION_ID = "data_location_id";
-
-    public TaskExecutionService(RestTemplate restTemplate, KafkaSender kafkaSender) {
-        super(restTemplate, kafkaSender, 10);
-    }
-
-    @Override
-    public TaskTypeResource getType() {
-        return null;
-    }
-
-    @Override
-    public void initializeParameters(TaskResource taskResource, TaskContext taskContext) throws Exception {
-
-        taskContext.getLocalContext().put(DATA_LOCATION_ID, findInput(taskContext, taskResource, DATA_LOCATION_ID, false));
-        taskContext.getLocalContext().put(REMOTE_TARGET_PATH, findInput(taskContext, taskResource, REMOTE_TARGET_PATH, false));
-
-        String computeId = findInput(taskContext, taskResource, COMPUTE_RESOURCE, false);
-        taskContext.getLocalContext().put(COMPUTE_RESOURCE, this.getRestTemplate().getForObject("http://" + this.getApiServerUrl()
-                + "/compute/" + Long.parseLong(computeId), ComputeResource.class));
-    }
-
-    @Override
-    public void executeTask(TaskResource taskResource, TaskContext taskContext) {
-
-        String remoteTargetPath = (String) taskContext.getLocalContext().get(REMOTE_TARGET_PATH);
-        String dataLocationId = (String) taskContext.getLocalContext().get(DATA_LOCATION_ID);
-        ComputeResource computeResource = (ComputeResource) taskContext.getLocalContext().get(COMPUTE_RESOURCE);
-
-        try {
-            publishTaskStatus(taskContext, TaskStatusResource.State.EXECUTING);
-            fetchComputeResourceOperation(computeResource).transferDataIn(dataLocationId, remoteTargetPath, "SCP");
-            finishTaskExecution(taskContext, taskResource, "Out", TaskStatusResource.State.COMPLETED, "Task completed");
-
-
-        } catch (Exception e) {
-
-            e.printStackTrace();
-            publishTaskStatus(taskContext, TaskStatusResource.State.FAILED);
-        }
-    }
-}
diff --git a/airavata-kubernetes/modules/microservices/tasks/data-pushing-task/src/main/resources/application.properties b/airavata-kubernetes/modules/microservices/tasks/data-pushing-task/src/main/resources/application.properties
deleted file mode 100644
index 6ec9766..0000000
--- a/airavata-kubernetes/modules/microservices/tasks/data-pushing-task/src/main/resources/application.properties
+++ /dev/null
@@ -1,5 +0,0 @@
-server.port = 8291
-api.server.url = api-server.default.svc.cluster.local:8080
-task.group.name = ingress-staging
-task.event.topic.name = airavata-task-event
-task.read.topic.name = airavata-task-ingress-staging
\ No newline at end of file
diff --git a/airavata-kubernetes/modules/microservices/task-scheduler/pom.xml b/airavata-kubernetes/modules/microservices/workflow-scheduler/pom.xml
similarity index 99%
rename from airavata-kubernetes/modules/microservices/task-scheduler/pom.xml
rename to airavata-kubernetes/modules/microservices/workflow-scheduler/pom.xml
index 8f502c8..245716a 100644
--- a/airavata-kubernetes/modules/microservices/task-scheduler/pom.xml
+++ b/airavata-kubernetes/modules/microservices/workflow-scheduler/pom.xml
@@ -31,7 +31,7 @@
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
-    <artifactId>task-scheduler</artifactId>
+    <artifactId>workflow-scheduler</artifactId>
 
     <dependencies>
         <dependency>
diff --git a/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/Application.java b/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/Application.java
similarity index 100%
rename from airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/Application.java
rename to airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/Application.java
diff --git a/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/core/HelixWorkflowManager.java b/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/core/HelixWorkflowManager.java
similarity index 100%
rename from airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/core/HelixWorkflowManager.java
rename to airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/core/HelixWorkflowManager.java
diff --git a/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/core/ProcessLifeCycleManager.java b/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/core/ProcessLifeCycleManager.java
similarity index 100%
rename from airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/core/ProcessLifeCycleManager.java
rename to airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/core/ProcessLifeCycleManager.java
diff --git a/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/KafkaReceiver.java b/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/KafkaReceiver.java
similarity index 100%
rename from airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/KafkaReceiver.java
rename to airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/KafkaReceiver.java
diff --git a/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/KafkaSender.java b/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/KafkaSender.java
similarity index 100%
rename from airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/KafkaSender.java
rename to airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/KafkaSender.java
diff --git a/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/ReceiverConfig.java b/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/ReceiverConfig.java
similarity index 100%
rename from airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/ReceiverConfig.java
rename to airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/ReceiverConfig.java
diff --git a/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/SenderConfig.java b/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/SenderConfig.java
similarity index 100%
rename from airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/SenderConfig.java
rename to airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/messaging/SenderConfig.java
diff --git a/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/service/HelixWorkflowService.java b/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/service/HelixWorkflowService.java
similarity index 100%
rename from airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/service/HelixWorkflowService.java
rename to airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/service/HelixWorkflowService.java
diff --git a/airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/service/WorkerService.java b/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/service/WorkerService.java
similarity index 100%
rename from airavata-kubernetes/modules/microservices/task-scheduler/src/main/java/org/apache/airavata/k8s/gfac/service/WorkerService.java
rename to airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/java/org/apache/airavata/k8s/gfac/service/WorkerService.java
diff --git a/airavata-kubernetes/modules/microservices/task-scheduler/src/main/resources/application.properties b/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/resources/application.properties
similarity index 100%
rename from airavata-kubernetes/modules/microservices/task-scheduler/src/main/resources/application.properties
rename to airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/resources/application.properties
diff --git a/airavata-kubernetes/modules/microservices/tasks/data-pushing-task/src/main/resources/application.yml b/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/resources/application.yml
similarity index 100%
rename from airavata-kubernetes/modules/microservices/tasks/data-pushing-task/src/main/resources/application.yml
rename to airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/resources/application.yml
diff --git a/airavata-kubernetes/modules/microservices/task-scheduler/src/main/resources/log4j.properties b/airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/resources/log4j.properties
similarity index 100%
rename from airavata-kubernetes/modules/microservices/task-scheduler/src/main/resources/log4j.properties
rename to airavata-kubernetes/modules/microservices/workflow-scheduler/src/main/resources/log4j.properties
diff --git a/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/AbstractTaskExecutionService.java b/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/AbstractTaskExecutionService.java
deleted file mode 100644
index 69837a6..0000000
--- a/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/AbstractTaskExecutionService.java
+++ /dev/null
@@ -1,137 +0,0 @@
-package org.apache.airavata.k8s.task.api;
-
-import org.apache.airavata.k8s.api.resources.compute.ComputeResource;
-import org.apache.airavata.k8s.api.resources.task.TaskInputResource;
-import org.apache.airavata.k8s.api.resources.task.TaskOutPortResource;
-import org.apache.airavata.k8s.api.resources.task.TaskResource;
-import org.apache.airavata.k8s.api.resources.task.TaskStatusResource;
-import org.apache.airavata.k8s.api.resources.task.type.TaskTypeResource;
-import org.apache.airavata.k8s.compute.api.ComputeOperations;
-import org.apache.airavata.k8s.compute.impl.MockComputeOperation;
-import org.apache.airavata.k8s.compute.impl.SSHComputeOperations;
-import org.apache.airavata.k8s.task.api.messaging.KafkaSender;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.web.client.RestTemplate;
-
-import javax.annotation.PostConstruct;
-import java.util.Optional;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-public abstract class AbstractTaskExecutionService {
-
-    private final ExecutorService executorService;
-
-    private final RestTemplate restTemplate;
-    private final KafkaSender kafkaSender;
-
-    @Value("${api.server.url}")
-    private String apiServerUrl;
-
-    @Value("${task.event.topic.name}")
-    private String taskEventPublishTopic;
-
-    public AbstractTaskExecutionService(RestTemplate restTemplate, KafkaSender kafkaSender, int concurrentTasks) {
-        this.restTemplate = restTemplate;
-        this.kafkaSender = kafkaSender;
-        executorService = Executors.newFixedThreadPool(concurrentTasks);
-    }
-
-    @PostConstruct
-    public void init() {
-        getRestTemplate().postForObject("http://" + apiServerUrl + "/taskType", getType(), Long.class);
-    }
-
-    public abstract TaskTypeResource getType();
-
-    public void executeTaskAsync(TaskContext taskContext) {
-
-        System.out.println("Executing task " + taskContext.getTaskId());
-        TaskResource taskResource = this.restTemplate.getForObject("http://" + apiServerUrl + "/task/" + taskContext.getTaskId(), TaskResource.class);
-
-        publishTaskStatus(taskContext, TaskStatusResource.State.SCHEDULED);
-
-        this.executorService.execute(() -> {
-            try {
-                initializeParameters(taskResource, taskContext);
-                executeTask(taskResource, taskContext);
-            } catch (Exception e) {
-                e.printStackTrace();
-                // Ignore silently as this is already handled
-                // TODO add a new exception type
-            }
-        });
-    }
-
-    public ComputeOperations fetchComputeResourceOperation(ComputeResource computeResource) throws Exception {
-        ComputeOperations operations;
-        if ("SSH".equals(computeResource.getCommunicationType())) {
-            operations = new SSHComputeOperations(computeResource.getHost(), computeResource.getUserName(), computeResource.getPassword());
-        } else if ("Mock".equals(computeResource.getCommunicationType())) {
-            operations = new MockComputeOperation(computeResource.getHost());
-        } else {
-            throw new Exception("No compatible communication method {" + computeResource.getCommunicationType() + "} not found for compute resource " + computeResource.getName());
-        }
-        return operations;
-    }
-
-    public String findInput(TaskContext taskContext, TaskResource taskResource, String name, boolean optional) throws Exception {
-
-        Optional<TaskInputResource> inputResource = taskResource.getInputs()
-                .stream()
-                .filter(input -> name.equals(input.getValue()))
-                .findFirst();
-
-        if (inputResource.isPresent()) {
-            return inputResource.get().getValue();
-
-        } else {
-            if (!optional) {
-                publishTaskStatus(taskContext, TaskStatusResource.State.FAILED,
-                        name + " is not available in inputs");
-                throw new Exception(name + " is not available in inputs");
-            } else {
-                return null;
-            }
-        }
-    }
-
-    public abstract void initializeParameters(TaskResource taskResource, TaskContext taskContext) throws Exception;
-    public abstract void executeTask(TaskResource taskResource, TaskContext taskContext);
-
-    public void publishTaskStatus(TaskContext taskContext, int status) {
-        publishTaskStatus(taskContext, status, "");
-    }
-
-    public void publishTaskStatus(TaskContext taskContext, int status, String reason) {
-        taskContext.setStatus(status);
-        taskContext.setReason(reason);
-
-        this.kafkaSender.send(this.taskEventPublishTopic, taskContext);
-    }
-
-    public void finishTaskExecution(TaskContext taskContext, TaskResource task, String outPortName, int status, String reason) throws Exception {
-        Optional<TaskOutPortResource> selectedOutPort = task.getOutPorts().stream().filter(outPort -> outPort.getName().equals(outPortName)).findFirst();
-        if (!selectedOutPort.isPresent()) {
-            throw new Exception("Selected out port " + outPortName + " does not exist in the task " + task.getName());
-        }
-
-        taskContext.setStatus(status);
-        taskContext.setReason(reason);
-        taskContext.setOutPortId(selectedOutPort.get().getId());
-    }
-
-    public RestTemplate getRestTemplate() {
-        return restTemplate;
-    }
-
-    public String getApiServerUrl() {
-        return apiServerUrl;
-    }
-}
diff --git a/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/TaskContext.java b/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/TaskContext.java
deleted file mode 100644
index e94beab..0000000
--- a/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/TaskContext.java
+++ /dev/null
@@ -1,124 +0,0 @@
-package org.apache.airavata.k8s.task.api;
-
-import org.apache.airavata.k8s.api.resources.task.TaskResource;
-import org.apache.airavata.k8s.api.resources.task.TaskStatusResource;
-
-import java.io.Serializable;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-public class TaskContext implements Serializable {
-
-    private long processId;
-    private long taskId;
-    private int status;
-    private String reason;
-
-    public long getOutPortId() {
-        return outPortId;
-    }
-
-    public TaskContext setOutPortId(long outPortId) {
-        this.outPortId = outPortId;
-        return this;
-    }
-
-    private long outPortId;
-    private Map<String, String> contextVariableParams = new HashMap<>();
-    private Map<String, String> contextDataParams = new HashMap<>();
-    private transient Map<String, Object> localContext = new HashMap<>();
-
-    private void resetStatus() {
-        setStatus(-1);
-        setReason("");
-        setOutPortId(-1);
-        setProcessId(-1);
-        setTaskId(-1);
-    }
-
-    public void assignTask(TaskResource taskResource) {
-        resetStatus();
-        setTaskId(taskResource.getId());
-        setProcessId(taskResource.getParentProcessId());
-        setStatus(TaskStatusResource.State.SCHEDULED);
-    }
-
-    public void resetPublicContext() {
-        this.contextVariableParams = new HashMap<>();
-        this.contextDataParams = new HashMap<>();
-    }
-
-    public void resetLocalContext() {
-        this.localContext = new HashMap<>();
-    }
-
-    public long getTaskId() {
-        return taskId;
-    }
-
-    public TaskContext setTaskId(long taskId) {
-        this.taskId = taskId;
-        return this;
-    }
-
-    public Map<String, String> getContextVariableParams() {
-        return contextVariableParams;
-    }
-
-    public TaskContext setContextVariableParams(Map<String, String> contextVariableParams) {
-        this.contextVariableParams = contextVariableParams;
-        return this;
-    }
-
-    public Map<String, String> getContextDataParams() {
-        return contextDataParams;
-    }
-
-    public TaskContext setContextDataParams(Map<String, String> contextDataParams) {
-        this.contextDataParams = contextDataParams;
-        return this;
-    }
-
-    public Map<String, Object> getLocalContext() {
-        return localContext;
-    }
-
-    public TaskContext setLocalContext(Map<String, Object> localContext) {
-        this.localContext = localContext;
-        return this;
-    }
-
-    public long getProcessId() {
-        return processId;
-    }
-
-    public TaskContext setProcessId(long processId) {
-        this.processId = processId;
-        return this;
-    }
-
-    public int getStatus() {
-        return status;
-    }
-
-    public TaskContext setStatus(int status) {
-        this.status = status;
-        return this;
-    }
-
-    public String getReason() {
-        return reason;
-    }
-
-    public TaskContext setReason(String reason) {
-        this.reason = reason;
-        return this;
-    }
-
-}
diff --git a/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/TaskContextDeserializer.java b/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/TaskContextDeserializer.java
deleted file mode 100644
index b826d5b..0000000
--- a/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/TaskContextDeserializer.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package org.apache.airavata.k8s.task.api;
-
-import org.apache.kafka.common.serialization.Deserializer;
-
-import java.io.*;
-import java.util.Map;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-public class TaskContextDeserializer implements Deserializer<TaskContext> {
-
-    @Override
-    public void configure(Map<String, ?> configs, boolean isKey) {
-
-    }
-
-    @Override
-    public TaskContext deserialize(String topic, byte[] data) {
-        ByteArrayInputStream bis = new ByteArrayInputStream(data);
-        ObjectInput in = null;
-        try {
-            in = new ObjectInputStream(bis);
-            return(TaskContext)in.readObject();
-        } catch (IOException e) {
-            // ignore exception
-        } catch (ClassNotFoundException e) {
-            e.printStackTrace();
-        } finally {
-            try {
-                if (in != null) {
-                    in.close();
-                }
-            } catch (IOException ex) {
-                // ignore close exception
-            }
-        }
-        return null;
-    }
-
-    @Override
-    public void close() {
-
-    }
-}
diff --git a/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/TaskContextSerializer.java b/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/TaskContextSerializer.java
deleted file mode 100644
index 0edac4b..0000000
--- a/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/TaskContextSerializer.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package org.apache.airavata.k8s.task.api;
-
-import org.apache.kafka.common.serialization.Serializer;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.ObjectOutput;
-import java.io.ObjectOutputStream;
-import java.util.Map;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-public class TaskContextSerializer implements Serializer<TaskContext> {
-    @Override
-    public void configure(Map<String, ?> configs, boolean isKey) {
-
-    }
-
-    @Override
-    public byte[] serialize(String topic, TaskContext data) {
-        ByteArrayOutputStream bos = new ByteArrayOutputStream();
-        ObjectOutput out = null;
-        try {
-            out = new ObjectOutputStream(bos);
-            out.writeObject(data);
-            out.flush();
-            return bos.toByteArray();
-        } catch (IOException e) {
-            // ignore catch
-        } finally {
-            try {
-                bos.close();
-            } catch (IOException ex) {
-                // ignore close exception
-            }
-        }
-        return null;
-    }
-
-    @Override
-    public void close() {
-
-    }
-}
diff --git a/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/messaging/KafkaReceiver.java b/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/messaging/KafkaReceiver.java
deleted file mode 100644
index c3597dc..0000000
--- a/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/messaging/KafkaReceiver.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/**
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.airavata.k8s.task.api.messaging;
-
-import org.apache.airavata.k8s.task.api.AbstractTaskExecutionService;
-import org.apache.airavata.k8s.task.api.TaskContext;
-import org.springframework.kafka.annotation.KafkaListener;
-import org.springframework.kafka.support.Acknowledgment;
-
-import javax.annotation.Resource;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-public class KafkaReceiver {
-
-    @Resource
-    private AbstractTaskExecutionService taskExecutionService;
-
-    @KafkaListener(topics = "${task.read.topic.name}")
-    public void receiveTasks(TaskContext taskContext, Acknowledgment ack) {
-        System.out.println("received task=" + taskContext.toString());
-        taskExecutionService.executeTaskAsync(taskContext);
-        ack.acknowledge();
-    }
-}
diff --git a/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/messaging/KafkaSender.java b/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/messaging/KafkaSender.java
deleted file mode 100644
index b584833..0000000
--- a/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/messaging/KafkaSender.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/**
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.airavata.k8s.task.api.messaging;
-
-import org.apache.airavata.k8s.task.api.TaskContext;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.kafka.core.KafkaTemplate;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-public class KafkaSender {
-
-    @Autowired
-    private KafkaTemplate<String, TaskContext> kafkaTemplate;
-
-    public void send(String topic, TaskContext taskContext) {
-        kafkaTemplate.send(topic, taskContext);
-    }
-
-    public void send(String topic, String key, TaskContext taskContext) {
-        kafkaTemplate.send(topic, key, taskContext);
-    }
-}
diff --git a/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/messaging/ReceiverConfig.java b/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/messaging/ReceiverConfig.java
deleted file mode 100644
index 8a09a4e..0000000
--- a/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/messaging/ReceiverConfig.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/**
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.airavata.k8s.task.api.messaging;
-
-import org.apache.airavata.k8s.task.api.TaskContext;
-import org.apache.airavata.k8s.task.api.TaskContextDeserializer;
-import org.apache.kafka.clients.consumer.ConsumerConfig;
-import org.apache.kafka.common.serialization.StringDeserializer;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.ComponentScan;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.kafka.annotation.EnableKafka;
-import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
-import org.springframework.kafka.config.KafkaListenerContainerFactory;
-import org.springframework.kafka.core.ConsumerFactory;
-import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
-import org.springframework.kafka.listener.AbstractMessageListenerContainer;
-import org.springframework.kafka.listener.ConcurrentMessageListenerContainer;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-@Configuration
-@ComponentScan
-@EnableKafka
-public class ReceiverConfig {
-
-    @Value("${kafka.bootstrap-servers}")
-    private String bootstrapServers;
-
-    @Value("${task.group.name}")
-    private String taskGroupName;
-
-    @Bean
-    public Map<String, Object> consumerConfigs() {
-        Map<String, Object> props = new HashMap<>();
-        // list of host:port pairs used for establishing the initial connections to the Kakfa cluster
-        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
-        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
-        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, TaskContextDeserializer.class);
-        // allows a pool of processes to divide the work of consuming and processing records
-        props.put(ConsumerConfig.GROUP_ID_CONFIG, taskGroupName);
-        props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
-        return props;
-    }
-
-    @Bean
-    public ConsumerFactory<String, TaskContext> consumerFactory() {
-        return new DefaultKafkaConsumerFactory<String, TaskContext>(consumerConfigs());
-    }
-
-    @Bean
-    public KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<String, TaskContext>> kafkaListenerContainerFactory() {
-        ConcurrentKafkaListenerContainerFactory<String, TaskContext> factory =
-                new ConcurrentKafkaListenerContainerFactory<>();
-        factory.setConsumerFactory(consumerFactory());
-        factory.getContainerProperties().setAckMode(AbstractMessageListenerContainer.AckMode.MANUAL_IMMEDIATE);
-        return factory;
-    }
-
-    @Bean
-    public KafkaReceiver receiver() {
-        return new KafkaReceiver();
-    }
-}
diff --git a/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/messaging/SenderConfig.java b/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/messaging/SenderConfig.java
deleted file mode 100644
index e66e1fd..0000000
--- a/airavata-kubernetes/modules/task-api/src/main/java/org/apache/airavata/k8s/task/api/messaging/SenderConfig.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/**
- *
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.airavata.k8s.task.api.messaging;
-
-import org.apache.airavata.k8s.task.api.TaskContext;
-import org.apache.airavata.k8s.task.api.TaskContextSerializer;
-import org.apache.kafka.clients.producer.ProducerConfig;
-import org.apache.kafka.common.serialization.StringSerializer;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.kafka.core.DefaultKafkaProducerFactory;
-import org.springframework.kafka.core.KafkaTemplate;
-import org.springframework.kafka.core.ProducerFactory;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * TODO: Class level comments please
- *
- * @author dimuthu
- * @since 1.0.0-SNAPSHOT
- */
-@Configuration
-public class SenderConfig {
-    @Value("${kafka.bootstrap-servers}")
-    private String bootstrapServers;
-
-    @Bean
-    public Map<String, Object> producerConfigs() {
-        Map<String, Object> props = new HashMap<>();
-        // list of host:port pairs used for establishing the initial connections to the Kakfa cluster
-        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
-        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
-        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, TaskContextSerializer.class);
-        return props;
-    }
-
-    @Bean
-    public ProducerFactory<String, TaskContext> producerFactory() {
-        return new DefaultKafkaProducerFactory<String, TaskContext>(producerConfigs());
-    }
-
-    @Bean
-    public KafkaTemplate<String, TaskContext> kafkaTemplate() {
-        return new KafkaTemplate<>(producerFactory());
-    }
-
-    @Bean
-    public KafkaSender kafkaSender() {
-        return new KafkaSender();
-    }
-}
diff --git a/airavata-kubernetes/pom.xml b/airavata-kubernetes/pom.xml
index 222fffe..30439d3 100644
--- a/airavata-kubernetes/pom.xml
+++ b/airavata-kubernetes/pom.xml
@@ -32,15 +32,15 @@
     <modules>
         <module>modules/api-resource</module>
         <module>modules/compute-resource-api</module>
+        <module>modules/helix-task-api</module>
         <module>modules/microservices/api-server</module>
-        <module>modules/microservices/workflow-generator</module>
-        <module>modules/microservices/task-scheduler</module>
         <module>modules/microservices/event-sink</module>
-        <module>modules/microservices/tasks/command-executing-task</module>
-        <module>modules/microservices/tasks/data-pushing-task</module>
-        <module>modules/microservices/tasks/data-collecting-task</module>
-        <module>modules/task-api</module>
-        <module>modules/helix-tasks</module>
+        <module>modules/microservices/helix-controller</module>
+        <module>modules/microservices/workflow-generator</module>
+        <module>modules/microservices/workflow-scheduler</module>
+        <module>modules/microservices/tasks/command-task</module>
+        <module>modules/microservices/tasks/data-in-task</module>
+        <module>modules/microservices/tasks/data-out-task</module>
     </modules>
 
     <dependencyManagement>
diff --git a/airavata-kubernetes/readme.txt b/airavata-kubernetes/readme.txt
index 194d2f2..1b4f6b4 100644
--- a/airavata-kubernetes/readme.txt
+++ b/airavata-kubernetes/readme.txt
@@ -18,4 +18,8 @@ When running in a local machine, add following host entries to /etc/hosts file
 
 When running as docker containers, pass following environment variables to api-server container
 spring_datasource_username=<db user>
-spring_datasource_password=<db password>
\ No newline at end of file
+spring_datasource_password=<db password>
+
+Create Helix cluster
+
+./helix-admin.sh --zkSvr localhost:2199 --addCluster AiravataDemoCluster
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
"commits@airavata.apache.org" <co...@airavata.apache.org>.

[airavata-sandbox] 01/19: Initial workflow composer implementation

Posted by sm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

smarru pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-sandbox.git

commit 7f9341b1e0ea3434f5d27e046289b27d2653ed8a
Author: dimuthu.upeksha2@gmail.com <Di...@1234>
AuthorDate: Fri Nov 3 23:35:01 2017 +0530

    Initial workflow composer implementation
---
 airavata-kubernetes/workflow-composer/index.html   |   374 +
 airavata-kubernetes/workflow-composer/mxClient.js  | 88604 +++++++++++++++++++
 .../workflow-composer/mxClient.min.js              |  1797 +
 .../workflow-composer/src/css/common.css           |   160 +
 .../workflow-composer/src/css/explorer.css         |    18 +
 .../workflow-composer/src/icons/copy.png           |   Bin 0 -> 3575 bytes
 .../workflow-composer/src/icons/http.png           |   Bin 0 -> 3218 bytes
 .../workflow-composer/src/icons/parallel.png       |   Bin 0 -> 3150 bytes
 .../workflow-composer/src/icons/s3.png             |   Bin 0 -> 3988 bytes
 .../workflow-composer/src/icons/ssh.png            |   Bin 0 -> 3815 bytes
 .../workflow-composer/src/icons/start.png          |   Bin 0 -> 3303 bytes
 .../workflow-composer/src/icons/stop.png           |   Bin 0 -> 3525 bytes
 .../workflow-composer/src/images/button.gif        |   Bin 0 -> 137 bytes
 .../workflow-composer/src/images/close.gif         |   Bin 0 -> 70 bytes
 .../workflow-composer/src/images/collapsed.gif     |   Bin 0 -> 877 bytes
 .../workflow-composer/src/images/error.gif         |   Bin 0 -> 907 bytes
 .../workflow-composer/src/images/expanded.gif      |   Bin 0 -> 878 bytes
 .../workflow-composer/src/images/maximize.gif      |   Bin 0 -> 843 bytes
 .../workflow-composer/src/images/minimize.gif      |   Bin 0 -> 64 bytes
 .../workflow-composer/src/images/normalize.gif     |   Bin 0 -> 845 bytes
 .../workflow-composer/src/images/point.gif         |   Bin 0 -> 55 bytes
 .../workflow-composer/src/images/resize.gif        |   Bin 0 -> 74 bytes
 .../workflow-composer/src/images/separator.gif     |   Bin 0 -> 146 bytes
 .../workflow-composer/src/images/submenu.gif       |   Bin 0 -> 56 bytes
 .../workflow-composer/src/images/transparent.gif   |   Bin 0 -> 90 bytes
 .../workflow-composer/src/images/warning.gif       |   Bin 0 -> 276 bytes
 .../workflow-composer/src/images/warning.png       |   Bin 0 -> 425 bytes
 .../workflow-composer/src/images/window-title.gif  |   Bin 0 -> 275 bytes
 .../workflow-composer/src/images/window.gif        |   Bin 0 -> 75 bytes
 .../workflow-composer/src/js/components.js         |    53 +
 .../src/js/editor/mxDefaultKeyHandler.js           |   126 +
 .../src/js/editor/mxDefaultPopupMenu.js            |   306 +
 .../src/js/editor/mxDefaultToolbar.js              |   564 +
 .../workflow-composer/src/js/editor/mxEditor.js    |  3114 +
 .../src/js/handler/mxCellHighlight.js              |   314 +
 .../src/js/handler/mxCellMarker.js                 |   430 +
 .../src/js/handler/mxCellTracker.js                |   145 +
 .../src/js/handler/mxConnectionHandler.js          |  2204 +
 .../src/js/handler/mxConstraintHandler.js          |   520 +
 .../src/js/handler/mxEdgeHandler.js                |  2409 +
 .../src/js/handler/mxEdgeSegmentHandler.js         |   401 +
 .../src/js/handler/mxElbowEdgeHandler.js           |   229 +
 .../src/js/handler/mxGraphHandler.js               |  1074 +
 .../workflow-composer/src/js/handler/mxHandle.js   |   353 +
 .../src/js/handler/mxKeyHandler.js                 |   428 +
 .../src/js/handler/mxPanningHandler.js             |   462 +
 .../src/js/handler/mxPopupMenuHandler.js           |   218 +
 .../src/js/handler/mxRubberband.js                 |   401 +
 .../src/js/handler/mxSelectionCellsHandler.js      |   287 +
 .../src/js/handler/mxTooltipHandler.js             |   337 +
 .../src/js/handler/mxVertexHandler.js              |  1950 +
 .../workflow-composer/src/js/index.txt             |   316 +
 .../workflow-composer/src/js/io/mxCellCodec.js     |   189 +
 .../src/js/io/mxChildChangeCodec.js                |   149 +
 .../workflow-composer/src/js/io/mxCodec.js         |   596 +
 .../workflow-composer/src/js/io/mxCodecRegistry.js |   137 +
 .../src/js/io/mxDefaultKeyHandlerCodec.js          |    88 +
 .../src/js/io/mxDefaultPopupMenuCodec.js           |    54 +
 .../src/js/io/mxDefaultToolbarCodec.js             |   312 +
 .../workflow-composer/src/js/io/mxEditorCodec.js   |   245 +
 .../src/js/io/mxGenericChangeCodec.js              |    64 +
 .../workflow-composer/src/js/io/mxGraphCodec.js    |    28 +
 .../src/js/io/mxGraphViewCodec.js                  |   197 +
 .../workflow-composer/src/js/io/mxModelCodec.js    |    80 +
 .../workflow-composer/src/js/io/mxObjectCodec.js   |  1077 +
 .../src/js/io/mxRootChangeCodec.js                 |    83 +
 .../src/js/io/mxStylesheetCodec.js                 |   217 +
 .../src/js/io/mxTerminalChangeCodec.js             |    42 +
 .../model/mxGraphAbstractHierarchyCell.js          |   206 +
 .../hierarchical/model/mxGraphHierarchyEdge.js     |   187 +
 .../hierarchical/model/mxGraphHierarchyModel.js    |   681 +
 .../hierarchical/model/mxGraphHierarchyNode.js     |   220 +
 .../layout/hierarchical/model/mxSwimlaneModel.js   |   801 +
 .../js/layout/hierarchical/mxHierarchicalLayout.js |   846 +
 .../src/js/layout/hierarchical/mxSwimlaneLayout.js |   937 +
 .../hierarchical/stage/mxCoordinateAssignment.js   |  1830 +
 .../stage/mxHierarchicalLayoutStage.js             |    25 +
 .../stage/mxMedianHybridCrossingReduction.js       |   675 +
 .../hierarchical/stage/mxMinimumCycleRemover.js    |   108 +
 .../hierarchical/stage/mxSwimlaneOrdering.js       |    96 +
 .../src/js/layout/mxCircleLayout.js                |   203 +
 .../src/js/layout/mxCompactTreeLayout.js           |  1203 +
 .../src/js/layout/mxCompositeLayout.js             |   101 +
 .../src/js/layout/mxEdgeLabelLayout.js             |   165 +
 .../src/js/layout/mxFastOrganicLayout.js           |   591 +
 .../src/js/layout/mxGraphLayout.js                 |   461 +
 .../src/js/layout/mxParallelEdgeLayout.js          |   225 +
 .../src/js/layout/mxPartitionLayout.js             |   240 +
 .../src/js/layout/mxRadialTreeLayout.js            |   317 +
 .../src/js/layout/mxStackLayout.js                 |   515 +
 .../workflow-composer/src/js/model/mxCell.js       |   825 +
 .../workflow-composer/src/js/model/mxCellPath.js   |   163 +
 .../workflow-composer/src/js/model/mxGeometry.js   |   415 +
 .../workflow-composer/src/js/model/mxGraphModel.js |  2667 +
 .../workflow-composer/src/js/mxClient.js           |   769 +
 .../workflow-composer/src/js/shape/mxActor.js      |    86 +
 .../workflow-composer/src/js/shape/mxArrow.js      |   115 +
 .../src/js/shape/mxArrowConnector.js               |   485 +
 .../workflow-composer/src/js/shape/mxCloud.js      |    55 +
 .../workflow-composer/src/js/shape/mxConnector.js  |   149 +
 .../workflow-composer/src/js/shape/mxCylinder.js   |   105 +
 .../src/js/shape/mxDoubleEllipse.js                |   114 +
 .../workflow-composer/src/js/shape/mxEllipse.js    |    48 +
 .../workflow-composer/src/js/shape/mxHexagon.js    |    34 +
 .../workflow-composer/src/js/shape/mxImageShape.js |   233 +
 .../workflow-composer/src/js/shape/mxLabel.js      |   275 +
 .../workflow-composer/src/js/shape/mxLine.js       |    51 +
 .../workflow-composer/src/js/shape/mxMarker.js     |   208 +
 .../workflow-composer/src/js/shape/mxPolyline.js   |   127 +
 .../src/js/shape/mxRectangleShape.js               |   117 +
 .../workflow-composer/src/js/shape/mxRhombus.js    |    54 +
 .../workflow-composer/src/js/shape/mxShape.js      |  1604 +
 .../workflow-composer/src/js/shape/mxStencil.js    |   761 +
 .../src/js/shape/mxStencilRegistry.js              |    53 +
 .../workflow-composer/src/js/shape/mxSwimlane.js   |   410 +
 .../workflow-composer/src/js/shape/mxText.js       |  1263 +
 .../workflow-composer/src/js/shape/mxTriangle.js   |    33 +
 .../src/js/util/mxAbstractCanvas2D.js              |   642 +
 .../workflow-composer/src/js/util/mxAnimation.js   |    92 +
 .../src/js/util/mxAutoSaveManager.js               |   213 +
 .../workflow-composer/src/js/util/mxClipboard.js   |   221 +
 .../workflow-composer/src/js/util/mxConstants.js   |  2275 +
 .../workflow-composer/src/js/util/mxDictionary.js  |   130 +
 .../workflow-composer/src/js/util/mxDivResizer.js  |   151 +
 .../workflow-composer/src/js/util/mxDragSource.js  |   679 +
 .../workflow-composer/src/js/util/mxEffects.js     |   211 +
 .../workflow-composer/src/js/util/mxEvent.js       |  1406 +
 .../workflow-composer/src/js/util/mxEventObject.js |   111 +
 .../workflow-composer/src/js/util/mxEventSource.js |   189 +
 .../workflow-composer/src/js/util/mxForm.js        |   202 +
 .../workflow-composer/src/js/util/mxGuide.js       |   401 +
 .../workflow-composer/src/js/util/mxImage.js       |    40 +
 .../workflow-composer/src/js/util/mxImageBundle.js |   103 +
 .../workflow-composer/src/js/util/mxImageExport.js |   175 +
 .../workflow-composer/src/js/util/mxLog.js         |   413 +
 .../workflow-composer/src/js/util/mxMorphing.js    |   248 +
 .../workflow-composer/src/js/util/mxMouseEvent.js  |   244 +
 .../src/js/util/mxObjectIdentity.js                |    72 +
 .../src/js/util/mxPanningManager.js                |   262 +
 .../workflow-composer/src/js/util/mxPoint.js       |    54 +
 .../workflow-composer/src/js/util/mxPopupMenu.js   |   613 +
 .../workflow-composer/src/js/util/mxRectangle.js   |   179 +
 .../workflow-composer/src/js/util/mxResources.js   |   450 +
 .../workflow-composer/src/js/util/mxSvgCanvas2D.js |  2179 +
 .../workflow-composer/src/js/util/mxToolbar.js     |   527 +
 .../workflow-composer/src/js/util/mxUndoManager.js |   229 +
 .../src/js/util/mxUndoableEdit.js                  |   213 +
 .../src/js/util/mxUrlConverter.js                  |   151 +
 .../workflow-composer/src/js/util/mxUtils.js       |  4353 +
 .../workflow-composer/src/js/util/mxVmlCanvas2D.js |  1102 +
 .../workflow-composer/src/js/util/mxWindow.js      |  1130 +
 .../workflow-composer/src/js/util/mxXmlCanvas2D.js |  1217 +
 .../workflow-composer/src/js/util/mxXmlRequest.js  |   463 +
 .../workflow-composer/src/js/view/mxCellEditor.js  |  1069 +
 .../workflow-composer/src/js/view/mxCellOverlay.js |   233 +
 .../src/js/view/mxCellRenderer.js                  |  1553 +
 .../workflow-composer/src/js/view/mxCellState.js   |   431 +
 .../src/js/view/mxCellStatePreview.js              |   203 +
 .../src/js/view/mxConnectionConstraint.js          |    50 +
 .../workflow-composer/src/js/view/mxEdgeStyle.js   |  1569 +
 .../workflow-composer/src/js/view/mxGraph.js       | 12768 +++
 .../src/js/view/mxGraphSelectionModel.js           |   436 +
 .../workflow-composer/src/js/view/mxGraphView.js   |  3001 +
 .../src/js/view/mxLayoutManager.js                 |   409 +
 .../src/js/view/mxMultiplicity.js                  |   257 +
 .../workflow-composer/src/js/view/mxOutline.js     |   761 +
 .../workflow-composer/src/js/view/mxPerimeter.js   |   921 +
 .../src/js/view/mxPrintPreview.js                  |  1175 +
 .../src/js/view/mxStyleRegistry.js                 |    71 +
 .../workflow-composer/src/js/view/mxStylesheet.js  |   266 +
 .../src/js/view/mxSwimlaneManager.js               |   450 +
 .../src/js/view/mxTemporaryCellStates.js           |   108 +
 .../workflow-composer/src/resources/editor.txt     |     5 +
 .../workflow-composer/src/resources/editor_de.txt  |     5 +
 .../workflow-composer/src/resources/editor_zh.txt  |     5 +
 .../workflow-composer/src/resources/graph.txt      |    11 +
 .../workflow-composer/src/resources/graph_de.txt   |    11 +
 .../workflow-composer/src/resources/graph_zh.txt   |    11 +
 178 files changed, 180123 insertions(+)

diff --git a/airavata-kubernetes/workflow-composer/index.html b/airavata-kubernetes/workflow-composer/index.html
new file mode 100644
index 0000000..7db81ca
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/index.html
@@ -0,0 +1,374 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>Airavata Workflow Composer</title>
+    <script type="text/javascript">
+        mxBasePath = 'src';
+    </script>
+    <script type="text/javascript" src="src/js/mxClient.js"></script>
+    <script type="text/javascript" src="src/js/components.js"></script>
+    <script type="text/javascript">
+        function main(container, tbContainer)
+        {
+            if (!mxClient.isBrowserSupported())
+            {
+                // Displays an error message if the browser is not supported.
+                mxUtils.error('Browser is not supported!', 200, false);
+            }
+            else
+            {
+                var toolbar = new mxToolbar(tbContainer);
+                toolbar.enabled = false
+
+                // Workaround for Internet Explorer ignoring certain styles
+                if (mxClient.IS_QUIRKS)
+                {
+                    document.body.style.overflow = 'hidden';
+                    new mxDivResizer(tbContainer);
+                    new mxDivResizer(container);
+                }
+
+                var doc = mxUtils.createXmlDocument();
+
+                var relation = doc.createElement('Edge');
+
+                var graph = new mxGraph(container);
+
+                graph.dropEnabled = true;
+
+                mxDragSource.prototype.getDropTarget = function(graph, x, y)
+                {
+                    var cell = graph.getCellAt(x, y);
+
+                    if (!graph.isValidDropTarget(cell))
+                    {
+                        cell = null;
+                    }
+
+                    return cell;
+                };
+
+                // Enables new connections in the graph
+                graph.setConnectable(true);
+                graph.setMultigraph(false);
+
+                var addVertex = function(icon, w, h, componentName)
+                {
+                    addToolbarItem(graph, toolbar, fetchComponent(componentName, doc), icon);
+                };
+
+                addVertex('src/icons/start.png', 40, 40, 'START');
+                addVertex('src/icons/stop.png', 40, 40, 'STOP');
+                addVertex('src/icons/parallel.png', 40, 40, 'PARALLEL');
+                addVertex('src/icons/ssh.png', 40, 60, 'SSH');
+                addVertex('src/icons/copy.png', 40, 60, 'CP');
+                addVertex('src/icons/s3.png', 40, 60, 'S3');
+                toolbar.addLine();
+
+                graph.setCellsResizable(false);
+                graph.setResizeContainer(true);
+                graph.minimumContainerSize = new mxRectangle(0, 0, 500, 380);
+                graph.setBorder(60);
+
+                new mxKeyHandler(graph);
+
+                // Overrides method to disallow edge label editing
+                graph.isCellEditable = function(cell)
+                {
+                    return !this.getModel().isEdge(cell);
+                };
+
+                // Overrides method to provide a cell label in the display
+                graph.convertValueToString = function(cell)
+                {
+                    if (mxUtils.isNode(cell.value))
+                    {
+                        if (cell.value.nodeName.toLowerCase() == 'processingelement')
+                        {
+                            var name = cell.getAttribute('name', '');
+
+                            return name;
+                        }
+                        else if (cell.value.nodeName.toLowerCase() == 'edge')
+                        {
+                            return '';
+                        }
+
+                    }
+
+                    return '';
+                };
+
+                // Overrides method to store a cell label in the model
+                var cellLabelChanged = graph.cellLabelChanged;
+                graph.cellLabelChanged = function(cell, newValue, autoSize)
+                {
+                    if (mxUtils.isNode(cell.value) &&
+                        cell.value.nodeName.toLowerCase() == 'processingelement')
+                    {
+                        // Clones the value for correct undo/redo
+                        var elt = cell.value.cloneNode(true);
+
+                        elt.setAttribute('name', newValue);
+                        newValue = elt;
+                        autoSize = true;
+                    }
+
+                    cellLabelChanged.apply(this, arguments);
+                };
+
+                // Overrides method to create the editing value
+                var getEditingValue = graph.getEditingValue;
+                graph.getEditingValue = function(cell)
+                {
+                    if (mxUtils.isNode(cell.value) &&
+                        cell.value.nodeName.toLowerCase() == 'processingelement')
+                    {
+                        var name = cell.getAttribute('name', '');
+
+                        return name;
+                    }
+                };
+
+                new mxRubberband(graph);
+
+                document.body.appendChild(mxUtils.button('View XML', function()
+                {
+                    var encoder = new mxCodec();
+                    var node = encoder.encode(graph.getModel());
+                    mxUtils.popup(mxUtils.getPrettyXml(node), true);
+                }));
+
+                // Changes the style for match the markup
+                // Creates the default style for vertices
+                var style = graph.getStylesheet().getDefaultVertexStyle();
+                style[mxConstants.STYLE_STROKECOLOR] = 'gray';
+                style[mxConstants.STYLE_ROUNDED] = true;
+                style[mxConstants.STYLE_SHADOW] = true;
+                style[mxConstants.STYLE_FILLCOLOR] = '#DFDFDF';
+                style[mxConstants.STYLE_GRADIENTCOLOR] = 'white';
+                style[mxConstants.STYLE_FONTCOLOR] = 'black';
+                style[mxConstants.STYLE_FONTSIZE] = '12';
+                style[mxConstants.STYLE_SPACING] = 4;
+
+                // Creates the default style for edges
+                style = graph.getStylesheet().getDefaultEdgeStyle();
+                style[mxConstants.STYLE_STROKECOLOR] = '#0C0C0C';
+                style[mxConstants.STYLE_LABEL_BACKGROUNDCOLOR] = 'white';
+                style[mxConstants.STYLE_EDGE] = mxEdgeStyle.ElbowConnector;
+                style[mxConstants.STYLE_ROUNDED] = true;
+                style[mxConstants.STYLE_FONTCOLOR] = 'black';
+                style[mxConstants.STYLE_FONTSIZE] = '10';
+
+                var parent = graph.getDefaultParent();
+                graph.getModel().beginUpdate();
+                try
+                {
+                    //var v1 = graph.insertVertex(parent, null, pe1, 40, 40, 80, 30);
+                    //var v2 = graph.insertVertex(parent, null, pe2, 200, 150, 80, 30);
+                    //var e1 = graph.insertEdge(parent, null, relation, v1, v2);
+                }
+                finally
+                {
+                    // Updates the display
+                    graph.getModel().endUpdate();
+                }
+
+                // Implements a properties panel that uses
+                // mxCellAttributeChange to change properties
+                graph.getSelectionModel().addListener(mxEvent.CHANGE, function(sender, evt)
+                {
+                    selectionChanged(graph);
+                });
+
+                selectionChanged(graph);
+
+            }
+
+            /**
+             * Updates the properties panel
+             */
+            function selectionChanged(graph)
+            {
+                var div = document.getElementById('properties');
+
+                // Forces focusout in IE
+                graph.container.focus();
+
+                // Clears the DIV the non-DOM way
+                div.innerHTML = '';
+
+                // Gets the selection cell
+                var cell = graph.getSelectionCell();
+
+                if (cell == null)
+                {
+                    mxUtils.writeln(div, 'Nothing selected.');
+                }
+                else if (cell.value)
+                {
+                    // Writes the title
+                    var center = document.createElement('center');
+                    mxUtils.writeln(center, cell.value.nodeName + ' (' + cell.id + ')');
+                    div.appendChild(center);
+                    mxUtils.br(div);
+
+                    // Creates the form from the attributes of the user object
+                    var form = new mxForm();
+
+                    var attrs = cell.value.attributes;
+
+                    for (var i = 0; i < attrs.length; i++)
+                    {
+                        if (!attrs[i].nodeName.startsWith("in-") && !attrs[i].nodeName.startsWith("out-")) {
+                            createTextField(graph, form, cell, attrs[i]);
+                        }
+                    }
+
+                    div.appendChild(form.getTable());
+                    mxUtils.br(div);
+                }
+            }
+
+            function createTextField(graph, form, cell, attribute)
+            {
+                var input = form.addText(attribute.nodeName + ':', attribute.nodeValue);
+
+                var applyHandler = function()
+                {
+                    var newValue = input.value || '';
+                    var oldValue = cell.getAttribute(attribute.nodeName, '');
+
+                    if (newValue != oldValue)
+                    {
+                        graph.getModel().beginUpdate();
+
+                        try
+                        {
+                            var edit = new mxCellAttributeChange(
+                                cell, attribute.nodeName,
+                                newValue);
+                            graph.getModel().execute(edit);
+                            graph.updateCellSize(cell);
+                        }
+                        finally
+                        {
+                            graph.getModel().endUpdate();
+                        }
+                    }
+                };
+
+                mxEvent.addListener(input, 'keypress', function (evt)
+                {
+                    // Needs to take shift into account for textareas
+                    if (evt.keyCode == /*enter*/13 &&
+                        !mxEvent.isShiftDown(evt))
+                    {
+                        input.blur();
+                    }
+                });
+
+                if (mxClient.IS_IE)
+                {
+                    mxEvent.addListener(input, 'focusout', applyHandler);
+                }
+                else
+                {
+                    // Note: Known problem is the blurring of fields in
+                    // Firefox by changing the selection, in which case
+                    // no event is fired in FF and the change is lost.
+                    // As a workaround you should use a local variable
+                    // that stores the focused field and invoke blur
+                    // explicitely where we do the graph.focus above.
+                    mxEvent.addListener(input, 'blur', applyHandler);
+                }
+            }
+
+            function addToolbarItem(graph, toolbar, prototype, image)
+            {
+                // Function that is executed when the image is dropped on
+                // the graph. The cell argument points to the cell under
+                // the mousepointer if there is one.
+                var funct = function(graph, evt, cell, x, y)
+                {
+
+                    var parent = graph.getDefaultParent();
+                    var model = graph.getModel();
+
+                    var v1 = null;
+
+                    model.beginUpdate();
+
+                    try
+                    {
+                        v1 = graph.insertVertex(parent, null, prototype, x, y, 80, 60);
+                        v1.setConnectable(false);
+
+                        var inputs = [];
+                        var outputs = [];
+                        for (var i = 0; i < prototype.attributes.length; i++)
+                        {
+                            attr = prototype.attributes[i];
+                            if (attr.nodeName.startsWith("in-")) {
+                                inputs.push(attr.nodeValue);
+                            }
+                            if (attr.nodeName.startsWith("out-")) {
+                                outputs.push(attr.nodeValue);
+                            }
+                        }
+
+                        var inputDivision = 1/(inputs.length + 1);
+                        var outputDivision = 1/(outputs.length + 1);
+
+                        inputs.forEach(function(input, index) {
+                            var v11 = graph.insertVertex(v1, null, input, 0, (index*inputDivision + inputDivision), 10, 10);
+                            v11.geometry.offset = new mxPoint(-5, -5);
+                            v11.geometry.relative = true;
+                        });
+
+                        outputs.forEach(function(output, index) {
+                            var v11 = graph.insertVertex(v1, null, output, 1, (index*outputDivision + outputDivision), 10, 10);
+                            v11.geometry.offset = new mxPoint(-5, -5);
+                            v11.geometry.relative = true;
+                        });
+                    }
+                    finally
+                    {
+                        // Updates the display
+                        graph.getModel().endUpdate();
+                    }
+
+                    graph.updateCellSize(v1);
+                    graph.setSelectionCell(v1);
+                };
+
+                // Creates the image which is used as the drag icon (preview)
+                var img = toolbar.addMode(null, image, funct);
+                mxUtils.makeDraggable(img, graph, funct);
+            }
+        };
+    </script>
+</head>
+<body onload="main(document.getElementById('graphContainer'), document.getElementById('toolContainer'))">
+<table style="position:relative;">
+    <tr>
+        <td>
+            <div id="toolContainer" style="border: solid 1px black; width: 80px; cursor: default">
+
+            </div>
+        </td>
+        <td>
+            <div id="graphContainer"
+                 style="border: solid 1px black;overflow:hidden;width:321px; cursor:default;">
+            </div>
+        </td>
+        <td valign="top">
+            <div id="properties"
+                 style="border: solid 1px black; padding: 10px;">
+            </div>
+        </td>
+    </tr>
+</table>
+</body>
+</html>
\ No newline at end of file
diff --git a/airavata-kubernetes/workflow-composer/mxClient.js b/airavata-kubernetes/workflow-composer/mxClient.js
new file mode 100644
index 0000000..8ec0d23
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/mxClient.js
@@ -0,0 +1,88604 @@
+/**
+ * Copyright (c) 2006-2017, JGraph Ltd
+ * Copyright (c) 2006-2017, Gaudenz Alder
+ */
+var mxClient =
+{
+	/**
+	 * Class: mxClient
+	 *
+	 * Bootstrapping mechanism for the mxGraph thin client. The production version
+	 * of this file contains all code required to run the mxGraph thin client, as
+	 * well as global constants to identify the browser and operating system in
+	 * use. You may have to load chrome://global/content/contentAreaUtils.js in
+	 * your page to disable certain security restrictions in Mozilla.
+	 * 
+	 * Variable: VERSION
+	 *
+	 * Contains the current version of the mxGraph library. The strings that
+	 * communicate versions of mxGraph use the following format.
+	 * 
+	 * versionMajor.versionMinor.buildNumber.revisionNumber
+	 * 
+	 * Current version is 3.7.5.
+	 */
+	VERSION: '3.7.5',
+
+	/**
+	 * Variable: IS_IE
+	 *
+	 * True if the current browser is Internet Explorer 10 or below. Use <mxClient.IS_IE11>
+	 * to detect IE 11.
+	 */
+	IS_IE: navigator.userAgent.indexOf('MSIE') >= 0,
+
+	/**
+	 * Variable: IS_IE6
+	 *
+	 * True if the current browser is Internet Explorer 6.x.
+	 */
+	IS_IE6: navigator.userAgent.indexOf('MSIE 6') >= 0,
+
+	/**
+	 * Variable: IS_IE11
+	 *
+	 * True if the current browser is Internet Explorer 11.x.
+	 */
+	IS_IE11: !!navigator.userAgent.match(/Trident\/7\./),
+
+	/**
+	 * Variable: IS_EDGE
+	 *
+	 * True if the current browser is Microsoft Edge.
+	 */
+	IS_EDGE: !!navigator.userAgent.match(/Edge\//),
+
+	/**
+	 * Variable: IS_QUIRKS
+	 *
+	 * True if the current browser is Internet Explorer and it is in quirks mode.
+	 */
+	IS_QUIRKS: navigator.userAgent.indexOf('MSIE') >= 0 && (document.documentMode == null || document.documentMode == 5),
+
+	/**
+	 * Variable: IS_EM
+	 * 
+	 * True if the browser is IE11 in enterprise mode (IE8 standards mode).
+	 */
+	IS_EM: 'spellcheck' in document.createElement('textarea') && document.documentMode == 8,
+
+	/**
+	 * Variable: VML_PREFIX
+	 * 
+	 * Prefix for VML namespace in node names. Default is 'v'.
+	 */
+	VML_PREFIX: 'v',
+
+	/**
+	 * Variable: OFFICE_PREFIX
+	 * 
+	 * Prefix for VML office namespace in node names. Default is 'o'.
+	 */
+	OFFICE_PREFIX: 'o',
+
+	/**
+	 * Variable: IS_NS
+	 *
+	 * True if the current browser is Netscape (including Firefox).
+	 */
+  	IS_NS: navigator.userAgent.indexOf('Mozilla/') >= 0 &&
+  		navigator.userAgent.indexOf('MSIE') < 0 &&
+  		navigator.userAgent.indexOf('Edge/') < 0,
+
+	/**
+	 * Variable: IS_OP
+	 *
+	 * True if the current browser is Opera.
+	 */
+  	IS_OP: navigator.userAgent.indexOf('Opera/') >= 0 ||
+  		navigator.userAgent.indexOf('OPR/') >= 0,
+
+	/**
+	 * Variable: IS_OT
+	 *
+	 * True if -o-transform is available as a CSS style, ie for Opera browsers
+	 * based on a Presto engine with version 2.5 or later.
+	 */
+  	IS_OT: navigator.userAgent.indexOf('Presto/') >= 0 &&
+  		navigator.userAgent.indexOf('Presto/2.4.') < 0 &&
+  		navigator.userAgent.indexOf('Presto/2.3.') < 0 &&
+  		navigator.userAgent.indexOf('Presto/2.2.') < 0 &&
+  		navigator.userAgent.indexOf('Presto/2.1.') < 0 &&
+  		navigator.userAgent.indexOf('Presto/2.0.') < 0 &&
+  		navigator.userAgent.indexOf('Presto/1.') < 0,
+  	
+	/**
+	 * Variable: IS_SF
+	 *
+	 * True if the current browser is Safari.
+	 */
+  	IS_SF: navigator.userAgent.indexOf('AppleWebKit/') >= 0 &&
+  		navigator.userAgent.indexOf('Chrome/') < 0 &&
+  		navigator.userAgent.indexOf('Edge/') < 0,
+  	
+	/**
+	 * Variable: IS_IOS
+	 * 
+	 * Returns true if the user agent is an iPad, iPhone or iPod.
+	 */
+  	IS_IOS: (navigator.userAgent.match(/(iPad|iPhone|iPod)/g) ? true : false),
+  		
+	/**
+	 * Variable: IS_GC
+	 *
+	 * True if the current browser is Google Chrome.
+	 */
+  	IS_GC: navigator.userAgent.indexOf('Chrome/') >= 0 &&
+		navigator.userAgent.indexOf('Edge/') < 0,
+	
+	/**
+	 * Variable: IS_CHROMEAPP
+	 *
+	 * True if the this is running inside a Chrome App.
+	 */
+  	IS_CHROMEAPP: window.chrome != null && chrome.app != null && chrome.app.runtime != null,
+		
+	/**
+	 * Variable: IS_FF
+	 *
+	 * True if the current browser is Firefox.
+	 */
+  	IS_FF: navigator.userAgent.indexOf('Firefox/') >= 0,
+  	
+	/**
+	 * Variable: IS_MT
+	 *
+	 * True if -moz-transform is available as a CSS style. This is the case
+	 * for all Firefox-based browsers newer than or equal 3, such as Camino,
+	 * Iceweasel, Seamonkey and Iceape.
+	 */
+  	IS_MT: (navigator.userAgent.indexOf('Firefox/') >= 0 &&
+		navigator.userAgent.indexOf('Firefox/1.') < 0 &&
+  		navigator.userAgent.indexOf('Firefox/2.') < 0) ||
+  		(navigator.userAgent.indexOf('Iceweasel/') >= 0 &&
+  		navigator.userAgent.indexOf('Iceweasel/1.') < 0 &&
+  		navigator.userAgent.indexOf('Iceweasel/2.') < 0) ||
+  		(navigator.userAgent.indexOf('SeaMonkey/') >= 0 &&
+  		navigator.userAgent.indexOf('SeaMonkey/1.') < 0) ||
+  		(navigator.userAgent.indexOf('Iceape/') >= 0 &&
+  		navigator.userAgent.indexOf('Iceape/1.') < 0),
+
+	/**
+	 * Variable: IS_SVG
+	 *
+	 * True if the browser supports SVG.
+	 */
+  	IS_SVG: navigator.userAgent.indexOf('Firefox/') >= 0 || // FF and Camino
+	  	navigator.userAgent.indexOf('Iceweasel/') >= 0 || // Firefox on Debian
+	  	navigator.userAgent.indexOf('Seamonkey/') >= 0 || // Firefox-based
+	  	navigator.userAgent.indexOf('Iceape/') >= 0 || // Seamonkey on Debian
+	  	navigator.userAgent.indexOf('Galeon/') >= 0 || // Gnome Browser (old)
+	  	navigator.userAgent.indexOf('Epiphany/') >= 0 || // Gnome Browser (new)
+	  	navigator.userAgent.indexOf('AppleWebKit/') >= 0 || // Safari/Google Chrome
+	  	navigator.userAgent.indexOf('Gecko/') >= 0 || // Netscape/Gecko
+	  	navigator.userAgent.indexOf('Opera/') >= 0 || // Opera
+	  	(document.documentMode != null && document.documentMode >= 9), // IE9+
+
+	/**
+	 * Variable: NO_FO
+	 *
+	 * True if foreignObject support is not available. This is the case for
+	 * Opera, older SVG-based browsers and all versions of IE.
+	 */
+  	NO_FO: !document.createElementNS || document.createElementNS('http://www.w3.org/2000/svg',
+  		'foreignObject') != '[object SVGForeignObjectElement]' || navigator.userAgent.indexOf('Opera/') >= 0,
+
+	/**
+	 * Variable: IS_VML
+	 *
+	 * True if the browser supports VML.
+	 */
+  	IS_VML: navigator.appName.toUpperCase() == 'MICROSOFT INTERNET EXPLORER',
+
+	/**
+	 * Variable: IS_WIN
+	 *
+	 * True if the client is a Windows.
+	 */
+  	IS_WIN: navigator.appVersion.indexOf('Win') > 0,
+
+	/**
+	 * Variable: IS_MAC
+	 *
+	 * True if the client is a Mac.
+	 */
+  	IS_MAC: navigator.appVersion.indexOf('Mac') > 0,
+
+	/**
+	 * Variable: IS_TOUCH
+	 * 
+	 * True if this device supports touchstart/-move/-end events (Apple iOS,
+	 * Android, Chromebook and Chrome Browser on touch-enabled devices).
+	 */
+  	IS_TOUCH: 'ontouchstart' in document.documentElement,
+
+	/**
+	 * Variable: IS_POINTER
+	 * 
+	 * True if this device supports Microsoft pointer events (always false on Macs).
+	 */
+  	IS_POINTER: window.PointerEvent != null && !(navigator.appVersion.indexOf('Mac') > 0),
+
+	/**
+	 * Variable: IS_LOCAL
+	 *
+	 * True if the documents location does not start with http:// or https://.
+	 */
+  	IS_LOCAL: document.location.href.indexOf('http://') < 0 &&
+  			  document.location.href.indexOf('https://') < 0,
+
+	/**
+	 * Function: isBrowserSupported
+	 *
+	 * Returns true if the current browser is supported, that is, if
+	 * <mxClient.IS_VML> or <mxClient.IS_SVG> is true.
+	 * 
+	 * Example:
+	 * 
+	 * (code)
+	 * if (!mxClient.isBrowserSupported())
+	 * {
+	 *   mxUtils.error('Browser is not supported!', 200, false);
+	 * }
+	 * (end)
+	 */
+	isBrowserSupported: function()
+	{
+		return mxClient.IS_VML || mxClient.IS_SVG;
+	},
+
+	/**
+	 * Function: link
+	 *
+	 * Adds a link node to the head of the document. Use this
+	 * to add a stylesheet to the page as follows:
+	 *
+	 * (code)
+	 * mxClient.link('stylesheet', filename);
+	 * (end)
+	 *
+	 * where filename is the (relative) URL of the stylesheet. The charset
+	 * is hardcoded to ISO-8859-1 and the type is text/css.
+	 * 
+	 * Parameters:
+	 * 
+	 * rel - String that represents the rel attribute of the link node.
+	 * href - String that represents the href attribute of the link node.
+	 * doc - Optional parent document of the link node.
+	 */
+	link: function(rel, href, doc)
+	{
+		doc = doc || document;
+
+		// Workaround for Operation Aborted in IE6 if base tag is used in head
+		if (mxClient.IS_IE6)
+		{
+			doc.write('<link rel="' + rel + '" href="' + href + '" charset="UTF-8" type="text/css"/>');
+		}
+		else
+		{	
+			var link = doc.createElement('link');
+			
+			link.setAttribute('rel', rel);
+			link.setAttribute('href', href);
+			link.setAttribute('charset', 'UTF-8');
+			link.setAttribute('type', 'text/css');
+			
+			var head = doc.getElementsByTagName('head')[0];
+	   		head.appendChild(link);
+		}
+	},
+	
+	/**
+	 * Function: include
+	 *
+	 * Dynamically adds a script node to the document header.
+	 * 
+	 * In production environments, the includes are resolved in the mxClient.js
+	 * file to reduce the number of requests required for client startup. This
+	 * function should only be used in development environments, but not in
+	 * production systems.
+	 */
+	include: function(src)
+	{
+		document.write('<script src="'+src+'"></script>');
+	},
+	
+	/**
+	 * Function: dispose
+	 * 
+	 * Frees up memory in IE by resolving cyclic dependencies between the DOM
+	 * and the JavaScript objects.
+	 */
+	dispose: function()
+	{
+		// Cleans all objects where listeners have been added
+		for (var i = 0; i < mxEvent.objects.length; i++)
+		{
+			if (mxEvent.objects[i].mxListenerList != null)
+			{
+				mxEvent.removeAllListeners(mxEvent.objects[i]);
+			}
+		}
+	}
+
+};
+
+/**
+ * Variable: mxLoadResources
+ * 
+ * Optional global config variable to toggle loading of the two resource files
+ * in <mxGraph> and <mxEditor>. Default is true. NOTE: This is a global variable,
+ * not a variable of mxClient.
+ *
+ * (code)
+ * <script type="text/javascript">
+ * 		var mxLoadResources = false;
+ * </script>
+ * <script type="text/javascript" src="/path/to/core/directory/js/mxClient.js"></script>
+ * (end)
+ */
+if (typeof(mxLoadResources) == 'undefined')
+{
+	mxLoadResources = true;
+}
+
+/**
+ * Variable: mxForceIncludes
+ * 
+ * Optional global config variable to force loading the JavaScript files in
+ * development mode. Default is undefined. NOTE: This is a global variable,
+ * not a variable of mxClient.
+ *
+ * (code)
+ * <script type="text/javascript">
+ * 		var mxLoadResources = true;
+ * </script>
+ * <script type="text/javascript" src="/path/to/core/directory/js/mxClient.js"></script>
+ * (end)
+ */
+if (typeof(mxForceIncludes) == 'undefined')
+{
+	mxForceIncludes = false;
+}
+
+/**
+ * Variable: mxResourceExtension
+ * 
+ * Optional global config variable to specify the extension of resource files.
+ * Default is true. NOTE: This is a global variable, not a variable of mxClient.
+ *
+ * (code)
+ * <script type="text/javascript">
+ * 		var mxResourceExtension = '.txt';
+ * </script>
+ * <script type="text/javascript" src="/path/to/core/directory/js/mxClient.js"></script>
+ * (end)
+ */
+if (typeof(mxResourceExtension) == 'undefined')
+{
+	mxResourceExtension = '.txt';
+}
+
+/**
+ * Variable: mxLoadStylesheets
+ * 
+ * Optional global config variable to toggle loading of the CSS files when
+ * the library is initialized. Default is true. NOTE: This is a global variable,
+ * not a variable of mxClient.
+ *
+ * (code)
+ * <script type="text/javascript">
+ * 		var mxLoadStylesheets = false;
+ * </script>
+ * <script type="text/javascript" src="/path/to/core/directory/js/mxClient.js"></script>
+ * (end)
+ */
+if (typeof(mxLoadStylesheets) == 'undefined')
+{
+	mxLoadStylesheets = true;
+}
+
+/**
+ * Variable: basePath
+ *
+ * Basepath for all URLs in the core without trailing slash. Default is '.'.
+ * Set mxBasePath prior to loading the mxClient library as follows to override
+ * this setting:
+ *
+ * (code)
+ * <script type="text/javascript">
+ * 		mxBasePath = '/path/to/core/directory';
+ * </script>
+ * <script type="text/javascript" src="/path/to/core/directory/js/mxClient.js"></script>
+ * (end)
+ * 
+ * When using a relative path, the path is relative to the URL of the page that
+ * contains the assignment. Trailing slashes are automatically removed.
+ */
+if (typeof(mxBasePath) != 'undefined' && mxBasePath.length > 0)
+{
+	// Adds a trailing slash if required
+	if (mxBasePath.substring(mxBasePath.length - 1) == '/')
+	{
+		mxBasePath = mxBasePath.substring(0, mxBasePath.length - 1);
+	}
+
+	mxClient.basePath = mxBasePath;
+}
+else
+{
+	mxClient.basePath = '.';
+}
+
+/**
+ * Variable: imageBasePath
+ *
+ * Basepath for all images URLs in the core without trailing slash. Default is
+ * <mxClient.basePath> + '/images'. Set mxImageBasePath prior to loading the
+ * mxClient library as follows to override this setting:
+ *
+ * (code)
+ * <script type="text/javascript">
+ * 		mxImageBasePath = '/path/to/image/directory';
+ * </script>
+ * <script type="text/javascript" src="/path/to/core/directory/js/mxClient.js"></script>
+ * (end)
+ * 
+ * When using a relative path, the path is relative to the URL of the page that
+ * contains the assignment. Trailing slashes are automatically removed.
+ */
+if (typeof(mxImageBasePath) != 'undefined' && mxImageBasePath.length > 0)
+{
+	// Adds a trailing slash if required
+	if (mxImageBasePath.substring(mxImageBasePath.length - 1) == '/')
+	{
+		mxImageBasePath = mxImageBasePath.substring(0, mxImageBasePath.length - 1);
+	}
+
+	mxClient.imageBasePath = mxImageBasePath;
+}
+else
+{
+	mxClient.imageBasePath = mxClient.basePath + '/images';	
+}
+
+/**
+ * Variable: language
+ *
+ * Defines the language of the client, eg. en for english, de for german etc.
+ * The special value 'none' will disable all built-in internationalization and
+ * resource loading. See <mxResources.getSpecialBundle> for handling identifiers
+ * with and without a dash.
+ * 
+ * Set mxLanguage prior to loading the mxClient library as follows to override
+ * this setting:
+ *
+ * (code)
+ * <script type="text/javascript">
+ * 		mxLanguage = 'en';
+ * </script>
+ * <script type="text/javascript" src="js/mxClient.js"></script>
+ * (end)
+ * 
+ * If internationalization is disabled, then the following variables should be
+ * overridden to reflect the current language of the system. These variables are
+ * cleared when i18n is disabled.
+ * <mxEditor.askZoomResource>, <mxEditor.lastSavedResource>,
+ * <mxEditor.currentFileResource>, <mxEditor.propertiesResource>,
+ * <mxEditor.tasksResource>, <mxEditor.helpResource>, <mxEditor.outlineResource>,
+ * <mxElbowEdgeHandler.doubleClickOrientationResource>, <mxUtils.errorResource>,
+ * <mxUtils.closeResource>, <mxGraphSelectionModel.doneResource>,
+ * <mxGraphSelectionModel.updatingSelectionResource>, <mxGraphView.doneResource>,
+ * <mxGraphView.updatingDocumentResource>, <mxCellRenderer.collapseExpandResource>,
+ * <mxGraph.containsValidationErrorsResource> and
+ * <mxGraph.alreadyConnectedResource>.
+ */
+if (typeof(mxLanguage) != 'undefined' && mxLanguage != null)
+{
+	mxClient.language = mxLanguage;
+}
+else
+{
+	mxClient.language = (mxClient.IS_IE) ? navigator.userLanguage : navigator.language;
+}
+
+/**
+ * Variable: defaultLanguage
+ * 
+ * Defines the default language which is used in the common resource files. Any
+ * resources for this language will only load the common resource file, but not
+ * the language-specific resource file. Default is 'en'.
+ * 
+ * Set mxDefaultLanguage prior to loading the mxClient library as follows to override
+ * this setting:
+ *
+ * (code)
+ * <script type="text/javascript">
+ * 		mxDefaultLanguage = 'de';
+ * </script>
+ * <script type="text/javascript" src="js/mxClient.js"></script>
+ * (end)
+ */
+if (typeof(mxDefaultLanguage) != 'undefined' && mxDefaultLanguage != null)
+{
+	mxClient.defaultLanguage = mxDefaultLanguage;
+}
+else
+{
+	mxClient.defaultLanguage = 'en';
+}
+
+// Adds all required stylesheets and namespaces
+if (mxLoadStylesheets)
+{
+	mxClient.link('stylesheet', mxClient.basePath + '/css/common.css');
+}
+
+/**
+ * Variable: languages
+ *
+ * Defines the optional array of all supported language extensions. The default
+ * language does not have to be part of this list. See
+ * <mxResources.isLanguageSupported>.
+ *
+ * (code)
+ * <script type="text/javascript">
+ * 		mxLanguages = ['de', 'it', 'fr'];
+ * </script>
+ * <script type="text/javascript" src="js/mxClient.js"></script>
+ * (end)
+ * 
+ * This is used to avoid unnecessary requests to language files, ie. if a 404
+ * will be returned.
+ */
+if (typeof(mxLanguages) != 'undefined' && mxLanguages != null)
+{
+	mxClient.languages = mxLanguages;
+}
+
+// Adds required namespaces, stylesheets and memory handling for older IE browsers
+if (mxClient.IS_VML)
+{
+	if (mxClient.IS_SVG)
+	{
+		mxClient.IS_VML = false;
+	}
+	else
+	{
+		// Enables support for IE8 standards mode. Note that this requires all attributes for VML
+		// elements to be set using direct notation, ie. node.attr = value. The use of setAttribute
+		// is not possible.
+		if (document.documentMode == 8)
+		{
+			document.namespaces.add(mxClient.VML_PREFIX, 'urn:schemas-microsoft-com:vml', '#default#VML');
+			document.namespaces.add(mxClient.OFFICE_PREFIX, 'urn:schemas-microsoft-com:office:office', '#default#VML');
+		}
+		else
+		{
+			document.namespaces.add(mxClient.VML_PREFIX, 'urn:schemas-microsoft-com:vml');
+			document.namespaces.add(mxClient.OFFICE_PREFIX, 'urn:schemas-microsoft-com:office:office');
+		}
+
+		// Workaround for limited number of stylesheets in IE (does not work in standards mode)
+		if (mxClient.IS_QUIRKS && document.styleSheets.length >= 30)
+		{
+			(function()
+			{
+				var node = document.createElement('style');
+				node.type = 'text/css';
+				node.styleSheet.cssText = mxClient.VML_PREFIX + '\\:*{behavior:url(#default#VML)}' +
+		        	mxClient.OFFICE_PREFIX + '\\:*{behavior:url(#default#VML)}';
+		        document.getElementsByTagName('head')[0].appendChild(node);
+			})();
+		}
+		else
+		{
+			document.createStyleSheet().cssText = mxClient.VML_PREFIX + '\\:*{behavior:url(#default#VML)}' +
+		    	mxClient.OFFICE_PREFIX + '\\:*{behavior:url(#default#VML)}';
+		}
+	    
+	    if (mxLoadStylesheets)
+	    {
+	    	mxClient.link('stylesheet', mxClient.basePath + '/css/explorer.css');
+	    }
+	
+		// Cleans up resources when the application terminates
+		window.attachEvent('onunload', mxClient.dispose);
+	}
+}
+
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+var mxLog =
+{
+	/**
+	 * Class: mxLog
+	 * 
+	 * A singleton class that implements a simple console.
+	 * 
+	 * Variable: consoleName
+	 * 
+	 * Specifies the name of the console window. Default is 'Console'.
+	 */
+	consoleName: 'Console',
+	
+	/**
+	 * Variable: TRACE
+	 * 
+	 * Specified if the output for <enter> and <leave> should be visible in the
+	 * console. Default is false.
+	 */
+	TRACE: false,
+
+	/**
+	 * Variable: DEBUG
+	 * 
+	 * Specifies if the output for <debug> should be visible in the console.
+	 * Default is true.
+	 */
+	DEBUG: true,
+
+	/**
+	 * Variable: WARN
+	 * 
+	 * Specifies if the output for <warn> should be visible in the console.
+	 * Default is true.
+	 */
+	WARN: true,
+
+	/**
+	 * Variable: buffer
+	 * 
+	 * Buffer for pre-initialized content.
+	 */
+	buffer: '',
+	
+	/**
+	 * Function: init
+	 *
+	 * Initializes the DOM node for the console. This requires document.body to
+	 * point to a non-null value. This is called from within <setVisible> if the
+	 * log has not yet been initialized.
+	 */
+	init: function()
+	{
+		if (mxLog.window == null && document.body != null)
+		{
+			var title = mxLog.consoleName + ' - mxGraph ' + mxClient.VERSION;
+
+			// Creates a table that maintains the layout
+			var table = document.createElement('table');
+			table.setAttribute('width', '100%');
+			table.setAttribute('height', '100%');
+
+			var tbody = document.createElement('tbody');
+			var tr = document.createElement('tr');
+			var td = document.createElement('td');
+			td.style.verticalAlign = 'top';
+				
+			// Adds the actual console as a textarea
+			mxLog.textarea = document.createElement('textarea');
+			mxLog.textarea.setAttribute('wrap', 'off');
+			mxLog.textarea.setAttribute('readOnly', 'true');
+			mxLog.textarea.style.height = '100%';
+			mxLog.textarea.style.resize = 'none';
+			mxLog.textarea.value = mxLog.buffer;
+
+			// Workaround for wrong width in standards mode
+			if (mxClient.IS_NS && document.compatMode != 'BackCompat')
+			{
+				mxLog.textarea.style.width = '99%';
+			}
+			else
+			{
+				mxLog.textarea.style.width = '100%';
+			}
+			
+			td.appendChild(mxLog.textarea);
+			tr.appendChild(td);
+			tbody.appendChild(tr);
+
+			// Creates the container div
+			tr = document.createElement('tr');
+			mxLog.td = document.createElement('td');
+			mxLog.td.style.verticalAlign = 'top';
+			mxLog.td.setAttribute('height', '30px');
+			
+			tr.appendChild(mxLog.td);
+			tbody.appendChild(tr);
+			table.appendChild(tbody);
+
+			// Adds various debugging buttons
+			mxLog.addButton('Info', function (evt)
+			{
+				mxLog.info();
+			});
+		
+			mxLog.addButton('DOM', function (evt)
+			{
+				var content = mxUtils.getInnerHtml(document.body);
+				mxLog.debug(content);
+			});
+	
+			mxLog.addButton('Trace', function (evt)
+			{
+				mxLog.TRACE = !mxLog.TRACE;
+				
+				if (mxLog.TRACE)
+				{
+					mxLog.debug('Tracing enabled');
+				}
+				else
+				{
+					mxLog.debug('Tracing disabled');
+				}
+			});	
+
+			mxLog.addButton('Copy', function (evt)
+			{
+				try
+				{
+					mxUtils.copy(mxLog.textarea.value);
+				}
+				catch (err)
+				{
+					mxUtils.alert(err);
+				}
+			});			
+
+			mxLog.addButton('Show', function (evt)
+			{
+				try
+				{
+					mxUtils.popup(mxLog.textarea.value);
+				}
+				catch (err)
+				{
+					mxUtils.alert(err);
+				}
+			});	
+			
+			mxLog.addButton('Clear', function (evt)
+			{
+				mxLog.textarea.value = '';
+			});
+
+			// Cross-browser code to get window size
+			var h = 0;
+			var w = 0;
+			
+			if (typeof(window.innerWidth) === 'number')
+			{
+				h = window.innerHeight;
+				w = window.innerWidth;
+			}
+			else
+			{
+				h = (document.documentElement.clientHeight || document.body.clientHeight);
+				w = document.body.clientWidth;
+			}
+
+			mxLog.window = new mxWindow(title, table, Math.max(0, w - 320), Math.max(0, h - 210), 300, 160);
+			mxLog.window.setMaximizable(true);
+			mxLog.window.setScrollable(false);
+			mxLog.window.setResizable(true);
+			mxLog.window.setClosable(true);
+			mxLog.window.destroyOnClose = false;
+			
+			// Workaround for ignored textarea height in various setups
+			if (((mxClient.IS_NS || mxClient.IS_IE) && !mxClient.IS_GC &&
+				!mxClient.IS_SF && document.compatMode != 'BackCompat') ||
+				document.documentMode == 11)
+			{
+				var elt = mxLog.window.getElement();
+				
+				var resizeHandler = function(sender, evt)
+				{
+					mxLog.textarea.style.height = Math.max(0, elt.offsetHeight - 70) + 'px';
+				}; 
+				
+				mxLog.window.addListener(mxEvent.RESIZE_END, resizeHandler);
+				mxLog.window.addListener(mxEvent.MAXIMIZE, resizeHandler);
+				mxLog.window.addListener(mxEvent.NORMALIZE, resizeHandler);
+
+				mxLog.textarea.style.height = '92px';
+			}
+		}
+	},
+	
+	/**
+	 * Function: info
+	 * 
+	 * Writes the current navigator information to the console.
+	 */
+	info: function()
+	{
+		mxLog.writeln(mxUtils.toString(navigator));
+	},
+			
+	/**
+	 * Function: addButton
+	 * 
+	 * Adds a button to the console using the given label and function.
+	 */
+	addButton: function(lab, funct)
+	{
+		var button = document.createElement('button');
+		mxUtils.write(button, lab);
+		mxEvent.addListener(button, 'click', funct);
+		mxLog.td.appendChild(button);
+	},
+				
+	/**
+	 * Function: isVisible
+	 * 
+	 * Returns true if the console is visible.
+	 */
+	isVisible: function()
+	{
+		if (mxLog.window != null)
+		{
+			return mxLog.window.isVisible();
+		}
+		
+		return false;
+	},
+	
+
+	/**
+	 * Function: show
+	 * 
+	 * Shows the console.
+	 */
+	show: function()
+	{
+		mxLog.setVisible(true);
+	},
+
+	/**
+	 * Function: setVisible
+	 * 
+	 * Shows or hides the console.
+	 */
+	setVisible: function(visible)
+	{
+		if (mxLog.window == null)
+		{
+			mxLog.init();
+		}
+
+		if (mxLog.window != null)
+		{
+			mxLog.window.setVisible(visible);
+		}
+	},
+
+	/**
+	 * Function: enter
+	 * 
+	 * Writes the specified string to the console
+	 * if <TRACE> is true and returns the current 
+	 * time in milliseconds.
+	 *
+	 * Example:
+	 * 
+	 * (code)
+	 * mxLog.show();
+	 * var t0 = mxLog.enter('Hello');
+	 * // Do something
+	 * mxLog.leave('World!', t0);
+	 * (end)
+	 */
+	enter: function(string)
+	{
+		if (mxLog.TRACE)
+		{
+			mxLog.writeln('Entering '+string);
+			
+			return new Date().getTime();
+		}
+	},
+
+	/**
+	 * Function: leave
+	 * 
+	 * Writes the specified string to the console
+	 * if <TRACE> is true and computes the difference
+	 * between the current time and t0 in milliseconds.
+	 * See <enter> for an example.
+	 */
+	leave: function(string, t0)
+	{
+		if (mxLog.TRACE)
+		{
+			var dt = (t0 != 0) ? ' ('+(new Date().getTime() - t0)+' ms)' : '';
+			mxLog.writeln('Leaving '+string+dt);
+		}
+	},
+	
+	/**
+	 * Function: debug
+	 * 
+	 * Adds all arguments to the console if <DEBUG> is enabled.
+	 *
+	 * Example:
+	 * 
+	 * (code)
+	 * mxLog.show();
+	 * mxLog.debug('Hello, World!');
+	 * (end)
+	 */
+	debug: function()
+	{
+		if (mxLog.DEBUG)
+		{
+			mxLog.writeln.apply(this, arguments);
+		}
+	},
+	
+	/**
+	 * Function: warn
+	 * 
+	 * Adds all arguments to the console if <WARN> is enabled.
+	 *
+	 * Example:
+	 * 
+	 * (code)
+	 * mxLog.show();
+	 * mxLog.warn('Hello, World!');
+	 * (end)
+	 */
+	warn: function()
+	{
+		if (mxLog.WARN)
+		{
+			mxLog.writeln.apply(this, arguments);
+		}
+	},
+
+	/**
+	 * Function: write
+	 * 
+	 * Adds the specified strings to the console.
+	 */
+	write: function()
+	{
+		var string = '';
+		
+		for (var i = 0; i < arguments.length; i++)
+		{
+			string += arguments[i];
+			
+			if (i < arguments.length - 1)
+			{
+				string += ' ';
+			}
+		}
+		
+		if (mxLog.textarea != null)
+		{
+			mxLog.textarea.value = mxLog.textarea.value + string;
+
+			// Workaround for no update in Presto 2.5.22 (Opera 10.5)
+			if (navigator.userAgent.indexOf('Presto/2.5') >= 0)
+			{
+				mxLog.textarea.style.visibility = 'hidden';
+				mxLog.textarea.style.visibility = 'visible';
+			}
+			
+			mxLog.textarea.scrollTop = mxLog.textarea.scrollHeight;
+		}
+		else
+		{
+			mxLog.buffer += string;
+		}
+	},
+	
+	/**
+	 * Function: writeln
+	 * 
+	 * Adds the specified strings to the console, appending a linefeed at the
+	 * end of each string.
+	 */
+	writeln: function()
+	{
+		var string = '';
+		
+		for (var i = 0; i < arguments.length; i++)
+		{
+			string += arguments[i];
+			
+			if (i < arguments.length - 1)
+			{
+				string += ' ';
+			}
+		}
+
+		mxLog.write(string + '\n');
+	}
+	
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+var mxObjectIdentity =
+{
+	/**
+	 * Class: mxObjectIdentity
+	 * 
+	 * Identity for JavaScript objects and functions. This is implemented using
+	 * a simple incrementing counter which is stored in each object under
+	 * <FIELD_NAME>.
+	 * 
+	 * The identity for an object does not change during its lifecycle.
+	 * 
+	 * Variable: FIELD_NAME
+	 * 
+	 * Name of the field to be used to store the object ID. Default is
+	 * <code>mxObjectId</code>.
+	 */
+	FIELD_NAME: 'mxObjectId',
+
+	/**
+	 * Variable: counter
+	 * 
+	 * Current counter.
+	 */
+	counter: 0,
+
+	/**
+	 * Function: get
+	 * 
+	 * Returns the ID for the given object or function or null if no object
+	 * is specified.
+	 */
+	get: function(obj)
+	{
+		if (obj != null)
+		{
+			if (obj[mxObjectIdentity.FIELD_NAME] == null)
+			{
+				if (typeof obj === 'object')
+				{
+					var ctor = mxUtils.getFunctionName(obj.constructor);
+					obj[mxObjectIdentity.FIELD_NAME] = ctor + '#' + mxObjectIdentity.counter++;
+				}
+				else if (typeof obj === 'function')
+				{
+					obj[mxObjectIdentity.FIELD_NAME] = 'Function#' + mxObjectIdentity.counter++;
+				}
+			}
+			
+			return obj[mxObjectIdentity.FIELD_NAME];
+		}
+		
+		return null;
+	},
+
+	/**
+	 * Function: clear
+	 * 
+	 * Deletes the ID from the given object or function.
+	 */
+	clear: function(obj)
+	{
+		if (typeof(obj) === 'object' || typeof obj === 'function')
+		{
+			delete obj[mxObjectIdentity.FIELD_NAME];
+		}
+	}
+
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxDictionary
+ *
+ * A wrapper class for an associative array with object keys. Note: This
+ * implementation uses <mxObjectIdentitiy> to turn object keys into strings.
+ * 
+ * Constructor: mxEventSource
+ *
+ * Constructs a new dictionary which allows object to be used as keys.
+ */
+function mxDictionary()
+{
+	this.clear();
+};
+
+/**
+ * Function: map
+ *
+ * Stores the (key, value) pairs in this dictionary.
+ */
+mxDictionary.prototype.map = null;
+
+/**
+ * Function: clear
+ *
+ * Clears the dictionary.
+ */
+mxDictionary.prototype.clear = function()
+{
+	this.map = {};
+};
+
+/**
+ * Function: get
+ *
+ * Returns the value for the given key.
+ */
+mxDictionary.prototype.get = function(key)
+{
+	var id = mxObjectIdentity.get(key);
+	
+	return this.map[id];
+};
+
+/**
+ * Function: put
+ *
+ * Stores the value under the given key and returns the previous
+ * value for that key.
+ */
+mxDictionary.prototype.put = function(key, value)
+{
+	var id = mxObjectIdentity.get(key);
+	var previous = this.map[id];
+	this.map[id] = value;
+	
+	return previous;
+};
+
+/**
+ * Function: remove
+ *
+ * Removes the value for the given key and returns the value that
+ * has been removed.
+ */
+mxDictionary.prototype.remove = function(key)
+{
+	var id = mxObjectIdentity.get(key);
+	var previous = this.map[id];
+	delete this.map[id];
+	
+	return previous;
+};
+
+/**
+ * Function: getKeys
+ *
+ * Returns all keys as an array.
+ */
+mxDictionary.prototype.getKeys = function()
+{
+	var result = [];
+	
+	for (var key in this.map)
+	{
+		result.push(key);
+	}
+	
+	return result;
+};
+
+/**
+ * Function: getValues
+ *
+ * Returns all values as an array.
+ */
+mxDictionary.prototype.getValues = function()
+{
+	var result = [];
+	
+	for (var key in this.map)
+	{
+		result.push(this.map[key]);
+	}
+	
+	return result;
+};
+
+/**
+ * Function: visit
+ *
+ * Visits all entries in the dictionary using the given function with the
+ * following signature: function(key, value) where key is a string and
+ * value is an object.
+ * 
+ * Parameters:
+ * 
+ * visitor - A function that takes the key and value as arguments.
+ */
+mxDictionary.prototype.visit = function(visitor)
+{
+	for (var key in this.map)
+	{
+		visitor(key, this.map[key]);
+	}
+};
+/**
+ * Copyright (c) 2006-2016, JGraph Ltd
+ * Copyright (c) 2006-2016, Gaudenz Alder
+ */
+var mxResources =
+{
+	/**
+	 * Class: mxResources
+	 * 
+	 * Implements internationalization. You can provide any number of 
+	 * resource files on the server using the following format for the 
+	 * filename: name[-en].properties. The en stands for any lowercase 
+	 * 2-character language shortcut (eg. de for german, fr for french).
+	 *
+	 * If the optional language extension is omitted, then the file is used as a 
+	 * default resource which is loaded in all cases. If a properties file for a 
+	 * specific language exists, then it is used to override the settings in the 
+	 * default resource. All entries in the file are of the form key=value. The
+	 * values may then be accessed in code via <get>. Lines without 
+	 * equal signs in the properties files are ignored.
+	 *
+	 * Resource files may either be added programmatically using
+	 * <add> or via a resource tag in the UI section of the 
+	 * editor configuration file, eg:
+	 * 
+	 * (code)
+	 * <mxEditor>
+	 *   <ui>
+	 *     <resource basename="examples/resources/mxWorkflow"/>
+	 * (end)
+	 * 
+	 * The above element will load examples/resources/mxWorkflow.properties as well
+	 * as the language specific file for the current language, if it exists.
+	 * 
+	 * Values may contain placeholders of the form {1}...{n} where each placeholder
+	 * is replaced with the value of the corresponding array element in the params
+	 * argument passed to <mxResources.get>. The placeholder {1} maps to the first
+	 * element in the array (at index 0).
+	 * 
+	 * See <mxClient.language> for more information on specifying the default
+	 * language or disabling all loading of resources.
+	 * 
+	 * Lines that start with a # sign will be ignored.
+	 * 
+	 * Special characters
+	 * 
+	 * To use unicode characters, use the standard notation (eg. \u8fd1) or %u as a
+	 * prefix (eg. %u20AC will display a Euro sign). For normal hex encoded strings,
+	 * use % as a prefix, eg. %F6 will display a "o umlaut" (&ouml;).
+	 * 
+	 * See <resourcesEncoded> to disable this. If you disable this, make sure that
+	 * your files are UTF-8 encoded.
+	 * 
+	 * Asynchronous loading
+	 * 
+	 * By default, the core adds two resource files synchronously at load time.
+	 * To load these files asynchronously, set <mxLoadResources> to false
+	 * before loading mxClient.js and use <mxResources.loadResources> instead.
+	 * 
+	 * Variable: resources
+	 * 
+	 * Associative array that maps from keys to values.
+	 */
+	resources: [],
+
+	/**
+	 * Variable: extension
+	 * 
+	 * Specifies the extension used for language files. Default is <mxResourceExtension>.
+	 */
+	extension: mxResourceExtension,
+
+	/**
+	 * Variable: resourcesEncoded
+	 * 
+	 * Specifies whether or not values in resource files are encoded with \u or
+	 * percentage. Default is false.
+	 */
+	resourcesEncoded: false,
+
+	/**
+	 * Variable: loadDefaultBundle
+	 * 
+	 * Specifies if the default file for a given basename should be loaded.
+	 * Default is true.
+	 */
+	loadDefaultBundle: true,
+
+	/**
+	 * Variable: loadDefaultBundle
+	 * 
+	 * Specifies if the specific language file file for a given basename should
+	 * be loaded. Default is true.
+	 */
+	loadSpecialBundle: true,
+
+	/**
+	 * Function: isLanguageSupported
+	 * 
+	 * Hook for subclassers to disable support for a given language. This
+	 * implementation returns true if lan is in <mxClient.languages>.
+	 * 
+	 * Parameters:
+	 *
+	 * lan - The current language.
+	 */
+	isLanguageSupported: function(lan)
+	{
+		if (mxClient.languages != null)
+		{
+			return mxUtils.indexOf(mxClient.languages, lan) >= 0;
+		}
+		
+		return true;
+	},
+
+	/**
+	 * Function: getDefaultBundle
+	 * 
+	 * Hook for subclassers to return the URL for the special bundle. This
+	 * implementation returns basename + <extension> or null if
+	 * <loadDefaultBundle> is false.
+	 * 
+	 * Parameters:
+	 * 
+	 * basename - The basename for which the file should be loaded.
+	 * lan - The current language.
+	 */
+	getDefaultBundle: function(basename, lan)
+	{
+		if (mxResources.loadDefaultBundle || !mxResources.isLanguageSupported(lan))
+		{
+			return basename + mxResources.extension;
+		}
+		else
+		{
+			return null;
+		}
+	},
+
+	/**
+	 * Function: getSpecialBundle
+	 * 
+	 * Hook for subclassers to return the URL for the special bundle. This
+	 * implementation returns basename + '_' + lan + <extension> or null if
+	 * <loadSpecialBundle> is false or lan equals <mxClient.defaultLanguage>.
+	 * 
+	 * If <mxResources.languages> is not null and <mxClient.language> contains
+	 * a dash, then this method checks if <isLanguageSupported> returns true
+	 * for the full language (including the dash). If that returns false the
+	 * first part of the language (up to the dash) will be tried as an extension.
+	 * 
+	 * If <mxResources.language> is null then the first part of the language is
+	 * used to maintain backwards compatibility.
+	 * 
+	 * Parameters:
+	 * 
+	 * basename - The basename for which the file should be loaded.
+	 * lan - The language for which the file should be loaded.
+	 */
+	getSpecialBundle: function(basename, lan)
+	{
+		if (mxClient.languages == null || !this.isLanguageSupported(lan))
+		{
+			var dash = lan.indexOf('-');
+			
+			if (dash > 0)
+			{
+				lan = lan.substring(0, dash);
+			}
+		}
+
+		if (mxResources.loadSpecialBundle && mxResources.isLanguageSupported(lan) && lan != mxClient.defaultLanguage)
+		{
+			return basename + '_' + lan + mxResources.extension;
+		}
+		else
+		{
+			return null;
+		}
+	},
+
+	/**
+	 * Function: add
+	 * 
+	 * Adds the default and current language properties file for the specified
+	 * basename. Existing keys are overridden as new files are added. If no
+	 * callback is used then the request is synchronous.
+	 *
+	 * Example:
+	 * 
+	 * At application startup, additional resources may be 
+	 * added using the following code:
+	 * 
+	 * (code)
+	 * mxResources.add('resources/editor');
+	 * (end)
+	 * 
+	 * Parameters:
+	 * 
+	 * basename - The basename for which the file should be loaded.
+	 * lan - The language for which the file should be loaded.
+	 * callback - Optional callback for asynchronous loading.
+	 */
+	add: function(basename, lan, callback)
+	{
+		lan = (lan != null) ? lan : ((mxClient.language != null) ?
+			mxClient.language.toLowerCase() : mxConstants.NONE);
+		
+		if (lan != mxConstants.NONE)
+		{
+			var defaultBundle = mxResources.getDefaultBundle(basename, lan);
+			var specialBundle = mxResources.getSpecialBundle(basename, lan);
+			
+			var loadSpecialBundle = function()
+			{
+				if (specialBundle != null)
+				{
+					if (callback)
+					{
+						mxUtils.get(specialBundle, function(req)
+						{
+							mxResources.parse(req.getText());
+							callback();
+						}, function()
+						{
+							callback();
+						});
+					}
+					else
+					{
+						try
+						{
+					   		var req = mxUtils.load(specialBundle);
+					   		
+					   		if (req.isReady())
+					   		{
+					 	   		mxResources.parse(req.getText());
+					   		}
+				   		}
+				   		catch (e)
+				   		{
+				   			// ignore
+					   	}
+					}
+				}
+				else if (callback != null)
+				{
+					callback();
+				}
+			}
+			
+			if (defaultBundle != null)
+			{
+				if (callback)
+				{
+					mxUtils.get(defaultBundle, function(req)
+					{
+						mxResources.parse(req.getText());
+						loadSpecialBundle();
+					}, function()
+					{
+						loadSpecialBundle();
+					});
+				}
+				else
+				{
+					try
+					{
+				   		var req = mxUtils.load(defaultBundle);
+				   		
+				   		if (req.isReady())
+				   		{
+				 	   		mxResources.parse(req.getText());
+				   		}
+				   		
+				   		loadSpecialBundle();
+				  	}
+				  	catch (e)
+				  	{
+				  		// ignore
+				  	}
+				}
+			}
+			else
+			{
+				// Overlays the language specific file (_lan-extension)
+				loadSpecialBundle();
+			}
+		}
+	},
+
+	/**
+	 * Function: parse
+	 * 
+	 * Parses the key, value pairs in the specified
+	 * text and stores them as local resources.
+	 */
+	parse: function(text)
+	{
+		if (text != null)
+		{
+			var lines = text.split('\n');
+			
+			for (var i = 0; i < lines.length; i++)
+			{
+				if (lines[i].charAt(0) != '#')
+				{
+					var index = lines[i].indexOf('=');
+					
+					if (index > 0)
+					{
+						var key = lines[i].substring(0, index);
+						var idx = lines[i].length;
+						
+						if (lines[i].charCodeAt(idx - 1) == 13)
+						{
+							idx--;
+						}
+						
+						var value = lines[i].substring(index + 1, idx);
+						
+						if (this.resourcesEncoded)
+						{
+							value = value.replace(/\\(?=u[a-fA-F\d]{4})/g,"%");
+							mxResources.resources[key] = unescape(value);
+						}
+						else
+						{
+							mxResources.resources[key] = value;
+						}
+					}
+				}
+			}
+		}
+	},
+
+	/**
+	 * Function: get
+	 * 
+	 * Returns the value for the specified resource key.
+	 *
+	 * Example:
+	 * To read the value for 'welomeMessage', use the following:
+	 * (code)
+	 * var result = mxResources.get('welcomeMessage') || '';
+	 * (end)
+	 *
+	 * This would require an entry of the following form in
+	 * one of the English language resource files:
+	 * (code)
+	 * welcomeMessage=Welcome to mxGraph!
+	 * (end)
+	 * 
+	 * The part behind the || is the string value to be used if the given
+	 * resource is not available.
+	 * 
+	 * Parameters:
+	 * 
+	 * key - String that represents the key of the resource to be returned.
+	 * params - Array of the values for the placeholders of the form {1}...{n}
+	 * to be replaced with in the resulting string.
+	 * defaultValue - Optional string that specifies the default return value.
+	 */
+	get: function(key, params, defaultValue)
+	{
+		var value = mxResources.resources[key];
+		
+		// Applies the default value if no resource was found
+		if (value == null)
+		{
+			value = defaultValue;
+		}
+		
+		// Replaces the placeholders with the values in the array
+		if (value != null && params != null)
+		{
+			value = mxResources.replacePlaceholders(value, params);
+		}
+		
+		return value;
+	},
+
+	/**
+	 * Function: replacePlaceholders
+	 * 
+	 * Replaces the given placeholders with the given parameters.
+	 * 
+	 * Parameters:
+	 * 
+	 * value - String that contains the placeholders.
+	 * params - Array of the values for the placeholders of the form {1}...{n}
+	 * to be replaced with in the resulting string.
+	 */
+	replacePlaceholders: function(value, params)
+	{
+		var result = [];
+		var index = null;
+		
+		for (var i = 0; i < value.length; i++)
+		{
+			var c = value.charAt(i);
+
+			if (c == '{')
+			{
+				index = '';
+			}
+			else if (index != null && 	c == '}')
+			{
+				index = parseInt(index)-1;
+				
+				if (index >= 0 && index < params.length)
+				{
+					result.push(params[index]);
+				}
+				
+				index = null;
+			}
+			else if (index != null)
+			{
+				index += c;
+			}
+			else
+			{
+				result.push(c);
+			}
+		}
+		
+		return result.join('');
+	},
+
+	/**
+	 * Function: loadResources
+	 * 
+	 * Loads all required resources asynchronously. Use this to load the graph and
+	 * editor resources if <mxLoadResources> is false.
+	 * 
+	 * Parameters:
+	 * 
+	 * callback - Callback function for asynchronous loading.
+	 */
+	loadResources: function(callback)
+	{
+		mxResources.add(mxClient.basePath+'/resources/editor', null, function()
+		{
+			mxResources.add(mxClient.basePath+'/resources/graph', null, callback);
+		});
+	}
+
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxPoint
+ *
+ * Implements a 2-dimensional vector with double precision coordinates.
+ * 
+ * Constructor: mxPoint
+ *
+ * Constructs a new point for the optional x and y coordinates. If no
+ * coordinates are given, then the default values for <x> and <y> are used.
+ */
+function mxPoint(x, y)
+{
+	this.x = (x != null) ? x : 0;
+	this.y = (y != null) ? y : 0;
+};
+
+/**
+ * Variable: x
+ *
+ * Holds the x-coordinate of the point. Default is 0.
+ */
+mxPoint.prototype.x = null;
+
+/**
+ * Variable: y
+ *
+ * Holds the y-coordinate of the point. Default is 0.
+ */
+mxPoint.prototype.y = null;
+
+/**
+ * Function: equals
+ * 
+ * Returns true if the given object equals this point.
+ */
+mxPoint.prototype.equals = function(obj)
+{
+	return obj != null && obj.x == this.x && obj.y == this.y;
+};
+
+/**
+ * Function: clone
+ *
+ * Returns a clone of this <mxPoint>.
+ */
+mxPoint.prototype.clone = function()
+{
+	// Handles subclasses as well
+	return mxUtils.clone(this);
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxRectangle
+ *
+ * Extends <mxPoint> to implement a 2-dimensional rectangle with double
+ * precision coordinates.
+ * 
+ * Constructor: mxRectangle
+ *
+ * Constructs a new rectangle for the optional parameters. If no parameters
+ * are given then the respective default values are used.
+ */
+function mxRectangle(x, y, width, height)
+{
+	mxPoint.call(this, x, y);
+
+	this.width = (width != null) ? width : 0;
+	this.height = (height != null) ? height : 0;
+};
+
+/**
+ * Extends mxPoint.
+ */
+mxRectangle.prototype = new mxPoint();
+mxRectangle.prototype.constructor = mxRectangle;
+
+/**
+ * Variable: width
+ *
+ * Holds the width of the rectangle. Default is 0.
+ */
+mxRectangle.prototype.width = null;
+
+/**
+ * Variable: height
+ *
+ * Holds the height of the rectangle. Default is 0.
+ */
+mxRectangle.prototype.height = null;
+
+/**
+ * Function: setRect
+ * 
+ * Sets this rectangle to the specified values
+ */
+mxRectangle.prototype.setRect = function(x, y, w, h)
+{
+    this.x = x;
+    this.y = y;
+    this.width = w;
+    this.height = h;
+};
+
+/**
+ * Function: getCenterX
+ * 
+ * Returns the x-coordinate of the center point.
+ */
+mxRectangle.prototype.getCenterX = function ()
+{
+	return this.x + this.width/2;
+};
+
+/**
+ * Function: getCenterY
+ * 
+ * Returns the y-coordinate of the center point.
+ */
+mxRectangle.prototype.getCenterY = function ()
+{
+	return this.y + this.height/2;
+};
+
+/**
+ * Function: add
+ *
+ * Adds the given rectangle to this rectangle.
+ */
+mxRectangle.prototype.add = function(rect)
+{
+	if (rect != null)
+	{
+		var minX = Math.min(this.x, rect.x);
+		var minY = Math.min(this.y, rect.y);
+		var maxX = Math.max(this.x + this.width, rect.x + rect.width);
+		var maxY = Math.max(this.y + this.height, rect.y + rect.height);
+		
+		this.x = minX;
+		this.y = minY;
+		this.width = maxX - minX;
+		this.height = maxY - minY;
+	}
+};
+
+/**
+ * Function: intersect
+ * 
+ * Changes this rectangle to where it overlaps with the given rectangle.
+ */
+mxRectangle.prototype.intersect = function(rect)
+{
+	if (rect != null)
+	{
+		var r1 = this.x + this.width;
+		var r2 = rect.x + rect.width;
+		
+		var b1 = this.y + this.height;
+		var b2 = rect.y + rect.height;
+		
+		this.x = Math.max(this.x, rect.x);
+		this.y = Math.max(this.y, rect.y);
+		this.width = Math.min(r1, r2) - this.x;
+		this.height = Math.min(b1, b2) - this.y;
+	}
+};
+
+/**
+ * Function: grow
+ *
+ * Grows the rectangle by the given amount, that is, this method subtracts
+ * the given amount from the x- and y-coordinates and adds twice the amount
+ * to the width and height.
+ */
+mxRectangle.prototype.grow = function(amount)
+{
+	this.x -= amount;
+	this.y -= amount;
+	this.width += 2 * amount;
+	this.height += 2 * amount;
+};
+
+/**
+ * Function: getPoint
+ * 
+ * Returns the top, left corner as a new <mxPoint>.
+ */
+mxRectangle.prototype.getPoint = function()
+{
+	return new mxPoint(this.x, this.y);
+};
+
+/**
+ * Function: rotate90
+ * 
+ * Rotates this rectangle by 90 degree around its center point.
+ */
+mxRectangle.prototype.rotate90 = function()
+{
+	var t = (this.width - this.height) / 2;
+	this.x += t;
+	this.y -= t;
+	var tmp = this.width;
+	this.width = this.height;
+	this.height = tmp;
+};
+
+/**
+ * Function: equals
+ * 
+ * Returns true if the given object equals this rectangle.
+ */
+mxRectangle.prototype.equals = function(obj)
+{
+	return obj != null && obj.x == this.x && obj.y == this.y &&
+		obj.width == this.width && obj.height == this.height;
+};
+
+/**
+ * Function: fromRectangle
+ * 
+ * Returns a new <mxRectangle> which is a copy of the given rectangle.
+ */
+mxRectangle.fromRectangle = function(rect)
+{
+	return new mxRectangle(rect.x, rect.y, rect.width, rect.height);
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+var mxEffects =
+{
+
+	/**
+	 * Class: mxEffects
+	 * 
+	 * Provides animation effects.
+	 */
+
+	/**
+	 * Function: animateChanges
+	 * 
+	 * Asynchronous animated move operation. See also: <mxMorphing>.
+	 * 
+	 * Example:
+	 * 
+	 * (code)
+	 * graph.model.addListener(mxEvent.CHANGE, function(sender, evt)
+	 * {
+	 *   var changes = evt.getProperty('edit').changes;
+	 * 
+	 *   if (changes.length < 10)
+	 *   {
+	 *     mxEffects.animateChanges(graph, changes);
+	 *   }
+	 * });
+	 * (end)
+	 * 
+	 * Parameters:
+	 * 
+	 * graph - <mxGraph> that received the changes.
+	 * changes - Array of changes to be animated.
+	 * done - Optional function argument that is invoked after the
+	 * last step of the animation.
+	 */
+	animateChanges: function(graph, changes, done)
+	{
+		var maxStep = 10;
+		var step = 0;
+
+		var animate = function() 
+		{
+			var isRequired = false;
+			
+			for (var i = 0; i < changes.length; i++)
+			{
+				var change = changes[i];
+				
+				if (change instanceof mxGeometryChange ||
+					change instanceof mxTerminalChange ||
+					change instanceof mxValueChange ||
+					change instanceof mxChildChange ||
+					change instanceof mxStyleChange)
+				{
+					var state = graph.getView().getState(change.cell || change.child, false);
+					
+					if (state != null)
+					{
+						isRequired = true;
+					
+						if (change.constructor != mxGeometryChange || graph.model.isEdge(change.cell))
+						{
+							mxUtils.setOpacity(state.shape.node, 100 * step / maxStep);
+						}
+						else
+						{
+							var scale = graph.getView().scale;					
+
+							var dx = (change.geometry.x - change.previous.x) * scale;
+							var dy = (change.geometry.y - change.previous.y) * scale;
+							
+							var sx = (change.geometry.width - change.previous.width) * scale;
+							var sy = (change.geometry.height - change.previous.height) * scale;
+							
+							if (step == 0)
+							{
+								state.x -= dx;
+								state.y -= dy;
+								state.width -= sx;
+								state.height -= sy;
+							}
+							else
+							{
+								state.x += dx / maxStep;
+								state.y += dy / maxStep;
+								state.width += sx / maxStep;
+								state.height += sy / maxStep;
+							}
+							
+							graph.cellRenderer.redraw(state);
+							
+							// Fades all connected edges and children
+							mxEffects.cascadeOpacity(graph, change.cell, 100 * step / maxStep);
+						}
+					}
+				}
+			}
+
+			if (step < maxStep && isRequired)
+			{
+				step++;
+				window.setTimeout(animate, delay);
+			}
+			else if (done != null)
+			{
+				done();
+			}
+		};
+		
+		var delay = 30;
+		animate();
+	},
+    
+	/**
+	 * Function: cascadeOpacity
+	 * 
+	 * Sets the opacity on the given cell and its descendants.
+	 * 
+	 * Parameters:
+	 * 
+	 * graph - <mxGraph> that contains the cells.
+	 * cell - <mxCell> to set the opacity for.
+	 * opacity - New value for the opacity in %.
+	 */
+    cascadeOpacity: function(graph, cell, opacity)
+	{
+		// Fades all children
+		var childCount = graph.model.getChildCount(cell);
+		
+		for (var i=0; i<childCount; i++)
+		{
+			var child = graph.model.getChildAt(cell, i);
+			var childState = graph.getView().getState(child);
+			
+			if (childState != null)
+			{
+				mxUtils.setOpacity(childState.shape.node, opacity);
+				mxEffects.cascadeOpacity(graph, child, opacity);
+			}
+		}
+		
+		// Fades all connected edges
+		var edges = graph.model.getEdges(cell);
+		
+		if (edges != null)
+		{
+			for (var i=0; i<edges.length; i++)
+			{
+				var edgeState = graph.getView().getState(edges[i]);
+				
+				if (edgeState != null)
+				{
+					mxUtils.setOpacity(edgeState.shape.node, opacity);
+				}
+			}
+		}
+	},
+
+	/**
+	 * Function: fadeOut
+	 * 
+	 * Asynchronous fade-out operation.
+	 */
+	fadeOut: function(node, from, remove, step, delay, isEnabled)
+	{
+		step = step || 40;
+		delay = delay || 30;
+		
+		var opacity = from || 100;
+		
+		mxUtils.setOpacity(node, opacity);
+		
+		if (isEnabled || isEnabled == null)
+		{
+			var f = function()
+			{
+			    opacity = Math.max(opacity-step, 0);
+				mxUtils.setOpacity(node, opacity);
+				
+				if (opacity > 0)
+				{
+					window.setTimeout(f, delay);
+				}
+				else
+				{
+					node.style.visibility = 'hidden';
+					
+					if (remove && node.parentNode)
+					{
+						node.parentNode.removeChild(node);
+					}
+				}
+			};
+			window.setTimeout(f, delay);
+		}
+		else
+		{
+			node.style.visibility = 'hidden';
+			
+			if (remove && node.parentNode)
+			{
+				node.parentNode.removeChild(node);
+			}
+		}
+	}
+
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+var mxUtils =
+{
+	/**
+	 * Class: mxUtils
+	 * 
+	 * A singleton class that provides cross-browser helper methods.
+	 * This is a global functionality. To access the functions in this
+	 * class, use the global classname appended by the functionname.
+	 * You may have to load chrome://global/content/contentAreaUtils.js
+	 * to disable certain security restrictions in Mozilla for the <open>,
+	 * <save>, <saveAs> and <copy> function.
+	 * 
+	 * For example, the following code displays an error message:
+	 * 
+	 * (code)
+	 * mxUtils.error('Browser is not supported!', 200, false);
+	 * (end)
+	 * 
+	 * Variable: errorResource
+	 * 
+	 * Specifies the resource key for the title of the error window. If the
+	 * resource for this key does not exist then the value is used as
+	 * the title. Default is 'error'.
+	 */
+	errorResource: (mxClient.language != 'none') ? 'error' : '',
+	
+	/**
+	 * Variable: closeResource
+	 * 
+	 * Specifies the resource key for the label of the close button. If the
+	 * resource for this key does not exist then the value is used as
+	 * the label. Default is 'close'.
+	 */
+	closeResource: (mxClient.language != 'none') ? 'close' : '',
+
+	/**
+	 * Variable: errorImage
+	 * 
+	 * Defines the image used for error dialogs.
+	 */
+	errorImage: mxClient.imageBasePath + '/error.gif',
+	
+	/**
+	 * Function: removeCursors
+	 * 
+	 * Removes the cursors from the style of the given DOM node and its
+	 * descendants.
+	 * 
+	 * Parameters:
+	 * 
+	 * element - DOM node to remove the cursor style from.
+	 */
+	removeCursors: function(element)
+	{
+		if (element.style != null)
+		{
+			element.style.cursor = '';
+		}
+		
+		var children = element.childNodes;
+		
+		if (children != null)
+		{
+	        var childCount = children.length;
+	        
+	        for (var i = 0; i < childCount; i += 1)
+	        {
+	            mxUtils.removeCursors(children[i]);
+	        }
+	    }
+	},
+
+	/**
+	 * Function: getCurrentStyle
+	 * 
+	 * Returns the current style of the specified element.
+	 * 
+	 * Parameters:
+	 * 
+	 * element - DOM node whose current style should be returned.
+	 */
+	getCurrentStyle: function()
+	{
+		if (mxClient.IS_IE)
+		{
+			return function(element)
+			{
+				return (element != null) ? element.currentStyle : null;
+			};
+		}
+		else
+		{
+			return function(element)
+			{
+				return (element != null) ?
+					window.getComputedStyle(element, '') :
+					null;
+			};
+		}
+	}(),
+	
+	/**
+	 * Function: parseCssNumber
+	 * 
+	 * Parses the given CSS numeric value adding handling for the values thin,
+	 * medium and thick (2, 4 and 6).
+	 */
+	parseCssNumber: function(value)
+	{
+		if (value == 'thin')
+		{
+			value = '2';
+		}
+		else if (value == 'medium')
+		{
+			value = '4';
+		}
+		else if (value == 'thick')
+		{
+			value = '6';
+		}
+		
+		value = parseFloat(value);
+		
+		if (isNaN(value))
+		{
+			value = 0;
+		}
+		
+		return value;
+	},
+
+	/**
+	 * Function: setPrefixedStyle
+	 * 
+	 * Adds the given style with the standard name and an optional vendor prefix for the current
+	 * browser.
+	 * 
+	 * (code)
+	 * mxUtils.setPrefixedStyle(node.style, 'transformOrigin', '0% 0%');
+	 * (end)
+	 */
+	setPrefixedStyle: function()
+	{
+		var prefix = null;
+		
+		if (mxClient.IS_OT)
+		{
+			prefix = 'O';
+		}
+		else if (mxClient.IS_SF || mxClient.IS_GC)
+		{
+			prefix = 'Webkit';
+		}
+		else if (mxClient.IS_MT)
+		{
+			prefix = 'Moz';
+		}
+		else if (mxClient.IS_IE && document.documentMode >= 9 && document.documentMode < 10)
+		{
+			prefix = 'ms';
+		}
+
+		return function(style, name, value)
+		{
+			style[name] = value;
+			
+			if (prefix != null && name.length > 0)
+			{
+				name = prefix + name.substring(0, 1).toUpperCase() + name.substring(1);
+				style[name] = value;
+			}
+		};
+	}(),
+	
+	/**
+	 * Function: hasScrollbars
+	 * 
+	 * Returns true if the overflow CSS property of the given node is either
+	 * scroll or auto.
+	 * 
+	 * Parameters:
+	 * 
+	 * node - DOM node whose style should be checked for scrollbars.
+	 */
+	hasScrollbars: function(node)
+	{
+		var style = mxUtils.getCurrentStyle(node);
+
+		return style != null && (style.overflow == 'scroll' || style.overflow == 'auto');
+	},
+	
+	/**
+	 * Function: bind
+	 * 
+	 * Returns a wrapper function that locks the execution scope of the given
+	 * function to the specified scope. Inside funct, the "this" keyword
+	 * becomes a reference to that scope.
+	 */
+	bind: function(scope, funct)
+	{
+		return function()
+		{
+			return funct.apply(scope, arguments);
+		};
+	},
+	
+	/**
+	 * Function: eval
+	 * 
+	 * Evaluates the given expression using eval and returns the JavaScript
+	 * object that represents the expression result. Supports evaluation of
+	 * expressions that define functions and returns the function object for
+	 * these expressions.
+	 * 
+	 * Parameters:
+	 * 
+	 * expr - A string that represents a JavaScript expression.
+	 */
+	eval: function(expr)
+	{
+		var result = null;
+
+		if (expr.indexOf('function') >= 0)
+		{
+			try
+			{
+				eval('var _mxJavaScriptExpression='+expr);
+				result = _mxJavaScriptExpression;
+				// TODO: Use delete here?
+				_mxJavaScriptExpression = null;
+			}
+			catch (e)
+			{
+				mxLog.warn(e.message + ' while evaluating ' + expr);
+			}
+		}
+		else
+		{
+			try
+			{
+				result = eval(expr);
+			}
+			catch (e)
+			{
+				mxLog.warn(e.message + ' while evaluating ' + expr);
+			}
+		}
+		
+		return result;
+	},
+	
+	/**
+	 * Function: findNode
+	 * 
+	 * Returns the first node where attr equals value.
+	 * This implementation does not use XPath.
+	 */
+	findNode: function(node, attr, value)
+	{
+		if (node.nodeType == mxConstants.NODETYPE_ELEMENT)
+		{
+			var tmp = node.getAttribute(attr);
+	
+			if (tmp != null && tmp == value)
+			{
+				return node;
+			}
+		}
+		
+		node = node.firstChild;
+		
+		while (node != null)
+		{
+			var result = mxUtils.findNode(node, attr, value);
+			
+			if (result != null)
+			{
+				return result;
+			}
+			
+			node = node.nextSibling;
+		}
+		
+		return null;
+	},
+
+	/**
+	 * Function: getFunctionName
+	 * 
+	 * Returns the name for the given function.
+	 * 
+	 * Parameters:
+	 * 
+	 * f - JavaScript object that represents a function.
+	 */
+	getFunctionName: function(f)
+	{
+		var str = null;
+
+		if (f != null)
+		{
+			if (f.name != null)
+			{
+				str = f.name;
+			}
+			else
+			{
+				str = mxUtils.trim(f.toString());
+				
+				if (/^function\s/.test(str))
+				{
+					str = mxUtils.ltrim(str.substring(9));
+					var idx2 = str.indexOf('(');
+					
+					if (idx2 > 0)
+					{
+						str = str.substring(0, idx2);
+					}
+				}
+			}
+		}
+		
+		return str;
+	},
+
+	/**
+	 * Function: indexOf
+	 * 
+	 * Returns the index of obj in array or -1 if the array does not contain
+	 * the given object.
+	 * 
+	 * Parameters:
+	 * 
+	 * array - Array to check for the given obj.
+	 * obj - Object to find in the given array.
+	 */
+	indexOf: function(array, obj)
+	{
+		if (array != null && obj != null)
+		{
+			for (var i = 0; i < array.length; i++)
+			{
+				if (array[i] == obj)
+				{
+					return i;
+				}
+			}
+		}
+		
+		return -1;
+	},
+
+	/**
+	 * Function: forEach
+	 * 
+	 * Calls the given function for each element of the given array and returns
+	 * the array.
+	 * 
+	 * Parameters:
+	 * 
+	 * array - Array that contains the elements.
+	 * fn - Function to be called for each object.
+	 */
+	forEach: function(array, fn)
+	{
+		if (array != null && fn != null)
+		{
+			for (var i = 0; i < array.length; i++)
+			{
+				fn(array[i]);
+			}
+		}
+		
+		return array;
+	},
+
+	/**
+	 * Function: remove
+	 * 
+	 * Removes all occurrences of the given object in the given array or
+	 * object. If there are multiple occurrences of the object, be they
+	 * associative or as an array entry, all occurrences are removed from
+	 * the array or deleted from the object. By removing the object from
+	 * the array, all elements following the removed element are shifted
+	 * by one step towards the beginning of the array.
+	 * 
+	 * The length of arrays is not modified inside this function.
+	 * 
+	 * Parameters:
+	 * 
+	 * obj - Object to find in the given array.
+	 * array - Array to check for the given obj.
+	 */
+	remove: function(obj, array)
+	{
+		var result = null;
+		
+		if (typeof(array) == 'object')
+		{
+			var index = mxUtils.indexOf(array, obj);
+			
+			while (index >= 0)
+			{
+				array.splice(index, 1);
+				result = obj;
+				index = mxUtils.indexOf(array, obj);
+			}
+		}
+
+		for (var key in array)
+		{
+			if (array[key] == obj)
+			{
+				delete array[key];
+				result = obj;
+			}
+		}
+		
+		return result;
+	},
+	
+	/**
+	 * Function: isNode
+	 * 
+	 * Returns true if the given value is an XML node with the node name
+	 * and if the optional attribute has the specified value.
+	 * 
+	 * This implementation assumes that the given value is a DOM node if the
+	 * nodeType property is numeric, that is, if isNaN returns false for
+	 * value.nodeType.
+	 * 
+	 * Parameters:
+	 * 
+	 * value - Object that should be examined as a node.
+	 * nodeName - String that specifies the node name.
+	 * attributeName - Optional attribute name to check.
+	 * attributeValue - Optional attribute value to check.
+	 */
+	 isNode: function(value, nodeName, attributeName, attributeValue)
+	 {
+	 	if (value != null && !isNaN(value.nodeType) && (nodeName == null ||
+	 		value.nodeName.toLowerCase() == nodeName.toLowerCase()))
+ 		{
+ 			return attributeName == null ||
+ 				value.getAttribute(attributeName) == attributeValue;
+ 		}
+	 	
+	 	return false;
+	 },
+	
+	/**
+	 * Function: isAncestorNode
+	 * 
+	 * Returns true if the given ancestor is an ancestor of the
+	 * given DOM node in the DOM. This also returns true if the
+	 * child is the ancestor.
+	 * 
+	 * Parameters:
+	 * 
+	 * ancestor - DOM node that represents the ancestor.
+	 * child - DOM node that represents the child.
+	 */
+	 isAncestorNode: function(ancestor, child)
+	 {
+	 	var parent = child;
+	 	
+	 	while (parent != null)
+	 	{
+	 		if (parent == ancestor)
+	 		{
+	 			return true;
+	 		}
+
+	 		parent = parent.parentNode;
+	 	}
+	 	
+	 	return false;
+	 },
+
+	/**
+	 * Function: getChildNodes
+	 * 
+	 * Returns an array of child nodes that are of the given node type.
+	 * 
+	 * Parameters:
+	 * 
+	 * node - Parent DOM node to return the children from.
+	 * nodeType - Optional node type to return. Default is
+	 * <mxConstants.NODETYPE_ELEMENT>.
+	 */
+	getChildNodes: function(node, nodeType)
+	{
+		nodeType = nodeType || mxConstants.NODETYPE_ELEMENT;
+		
+		var children = [];
+		var tmp = node.firstChild;
+		
+		while (tmp != null)
+		{
+			if (tmp.nodeType == nodeType)
+			{
+				children.push(tmp);
+			}
+			
+			tmp = tmp.nextSibling;
+		}
+		
+		return children;
+	},
+
+	/**
+	 * Function: importNode
+	 * 
+	 * Cross browser implementation for document.importNode. Uses document.importNode
+	 * in all browsers but IE, where the node is cloned by creating a new node and
+	 * copying all attributes and children into it using importNode, recursively.
+	 * 
+	 * Parameters:
+	 * 
+	 * doc - Document to import the node into.
+	 * node - Node to be imported.
+	 * allChildren - If all children should be imported.
+	 */
+	importNode: function(doc, node, allChildren)
+	{
+		if (mxClient.IS_IE && (document.documentMode == null || document.documentMode < 10))
+		{
+			switch (node.nodeType)
+			{
+				case 1: /* element */
+				{
+					var newNode = doc.createElement(node.nodeName);
+					
+					if (node.attributes && node.attributes.length > 0)
+					{
+						for (var i = 0; i < node.attributes.length; i++)
+						{
+							newNode.setAttribute(node.attributes[i].nodeName,
+								node.getAttribute(node.attributes[i].nodeName));
+						}
+						
+						if (allChildren && node.childNodes && node.childNodes.length > 0)
+						{
+							for (var i = 0; i < node.childNodes.length; i++)
+							{
+								newNode.appendChild(mxUtils.importNode(doc, node.childNodes[i], allChildren));
+							}
+						}
+					}
+					
+					return newNode;
+					break;
+				}
+				case 3: /* text */
+			    case 4: /* cdata-section */
+			    case 8: /* comment */
+			    {
+			      return doc.createTextNode(node.value);
+			      break;
+			    }
+			};
+		}
+		else
+		{
+			return doc.importNode(node, allChildren);
+		}
+	},
+
+	/**
+	 * Function: createXmlDocument
+	 * 
+	 * Returns a new, empty XML document.
+	 */
+	createXmlDocument: function()
+	{
+		var doc = null;
+		
+		if (document.implementation && document.implementation.createDocument)
+		{
+			doc = document.implementation.createDocument('', '', null);
+		}
+		else if (window.ActiveXObject)
+		{
+			doc = new ActiveXObject('Microsoft.XMLDOM');
+	 	}
+	 	
+	 	return doc;
+	},
+
+	/**
+	 * Function: parseXml
+	 * 
+	 * Parses the specified XML string into a new XML document and returns the
+	 * new document.
+	 * 
+	 * Example:
+	 * 
+	 * (code)
+	 * var doc = mxUtils.parseXml(
+	 *   '<mxGraphModel><root><MyDiagram id="0"><mxCell/></MyDiagram>'+
+	 *   '<MyLayer id="1"><mxCell parent="0" /></MyLayer><MyObject id="2">'+
+	 *   '<mxCell style="strokeColor=blue;fillColor=red" parent="1" vertex="1">'+
+	 *   '<mxGeometry x="10" y="10" width="80" height="30" as="geometry"/>'+
+	 *   '</mxCell></MyObject></root></mxGraphModel>');
+	 * (end)
+	 * 
+	 * Parameters:
+	 * 
+	 * xml - String that contains the XML data.
+	 */
+	parseXml: function()
+	{
+		if (window.DOMParser)
+		{
+			return function(xml)
+			{
+				var parser = new DOMParser();
+				
+				return parser.parseFromString(xml, 'text/xml');
+			};
+		}
+		else // IE<=9
+		{
+			return function(xml)
+			{
+				var result = mxUtils.createXmlDocument();
+				result.async = false;
+				// Workaround for parsing errors with SVG DTD
+				result.validateOnParse = false;
+				result.resolveExternals = false;
+				result.loadXML(xml);
+				
+				return result;
+			};
+		}
+	}(),
+
+	/**
+	 * Function: clearSelection
+	 * 
+	 * Clears the current selection in the page.
+	 */
+	clearSelection: function()
+	{
+		if (document.selection)
+		{
+			return function()
+			{
+				document.selection.empty();
+			};
+		}
+		else if (window.getSelection)
+		{
+			return function()
+			{
+				window.getSelection().removeAllRanges();
+			};
+		}
+		else
+		{
+			return function() { };
+		}
+	}(),
+
+	/**
+	 * Function: getPrettyXML
+	 * 
+	 * Returns a pretty printed string that represents the XML tree for the
+	 * given node. This method should only be used to print XML for reading,
+	 * use <getXml> instead to obtain a string for processing.
+	 * 
+	 * Parameters:
+	 * 
+	 * node - DOM node to return the XML for.
+	 * tab - Optional string that specifies the indentation for one level.
+	 * Default is two spaces.
+	 * indent - Optional string that represents the current indentation.
+	 * Default is an empty string.
+	 */
+	getPrettyXml: function(node, tab, indent)
+	{
+		var result = [];
+		
+		if (node != null)
+		{
+			tab = tab || '  ';
+			indent = indent || '';
+			
+			if (node.nodeType == mxConstants.NODETYPE_TEXT)
+			{
+				result.push(node.value);
+			}
+			else
+			{
+				result.push(indent + '<' + node.nodeName);
+				
+				// Creates the string with the node attributes
+				// and converts all HTML entities in the values
+				var attrs = node.attributes;
+				
+				if (attrs != null)
+				{
+					for (var i = 0; i < attrs.length; i++)
+					{
+						var val = mxUtils.htmlEntities(attrs[i].value);
+						result.push(' ' + attrs[i].nodeName + '="' + val + '"');
+					}
+				}
+
+				// Recursively creates the XML string for each
+				// child nodes and appends it here with an
+				// indentation
+				var tmp = node.firstChild;
+				
+				if (tmp != null)
+				{
+					result.push('>\n');
+					
+					while (tmp != null)
+					{
+						result.push(mxUtils.getPrettyXml(tmp, tab, indent + tab));
+						tmp = tmp.nextSibling;
+					}
+					
+					result.push(indent + '</'+node.nodeName + '>\n');
+				}
+				else
+				{
+					result.push('/>\n');
+				}
+			}
+		}
+		
+		return result.join('');
+	},
+	
+	/**
+	 * Function: removeWhitespace
+	 * 
+	 * Removes the sibling text nodes for the given node that only consists
+	 * of tabs, newlines and spaces.
+	 * 
+	 * Parameters:
+	 * 
+	 * node - DOM node whose siblings should be removed.
+	 * before - Optional boolean that specifies the direction of the traversal.
+	 */
+	removeWhitespace: function(node, before)
+	{
+		var tmp = (before) ? node.previousSibling : node.nextSibling;
+		
+		while (tmp != null && tmp.nodeType == mxConstants.NODETYPE_TEXT)
+		{
+			var next = (before) ? tmp.previousSibling : tmp.nextSibling;
+			var text = mxUtils.getTextContent(tmp);
+			
+			if (mxUtils.trim(text).length == 0)
+			{
+				tmp.parentNode.removeChild(tmp);
+			}
+			
+			tmp = next;
+		}
+	},
+	
+	/**
+	 * Function: htmlEntities
+	 * 
+	 * Replaces characters (less than, greater than, newlines and quotes) with
+	 * their HTML entities in the given string and returns the result.
+	 * 
+	 * Parameters:
+	 * 
+	 * s - String that contains the characters to be converted.
+	 * newline - If newlines should be replaced. Default is true.
+	 */
+	htmlEntities: function(s, newline)
+	{
+		s = String(s || '');
+		
+		s = s.replace(/&/g,'&amp;'); // 38 26
+		s = s.replace(/"/g,'&quot;'); // 34 22
+		s = s.replace(/\'/g,'&#39;'); // 39 27
+		s = s.replace(/</g,'&lt;'); // 60 3C
+		s = s.replace(/>/g,'&gt;'); // 62 3E
+
+		if (newline == null || newline)
+		{
+			s = s.replace(/\n/g, '&#xa;');
+		}
+		
+		return s;
+	},
+	
+	/**
+	 * Function: isVml
+	 * 
+	 * Returns true if the given node is in the VML namespace.
+	 * 
+	 * Parameters:
+	 * 
+	 * node - DOM node whose tag urn should be checked.
+	 */
+	isVml: function(node)
+	{
+		return node != null && node.tagUrn == 'urn:schemas-microsoft-com:vml';
+	},
+
+	/**
+	 * Function: getXml
+	 * 
+	 * Returns the XML content of the specified node. For Internet Explorer,
+	 * all \r\n\t[\t]* are removed from the XML string and the remaining \r\n
+	 * are replaced by \n. All \n are then replaced with linefeed, or &#xa; if
+	 * no linefeed is defined.
+	 * 
+	 * Parameters:
+	 * 
+	 * node - DOM node to return the XML for.
+	 * linefeed - Optional string that linefeeds are converted into. Default is
+	 * &#xa;
+	 */
+	getXml: function(node, linefeed)
+	{
+		var xml = '';
+
+		if (window.XMLSerializer != null)
+		{
+			var xmlSerializer = new XMLSerializer();
+			xml = xmlSerializer.serializeToString(node);     
+		}
+		else if (node.xml != null)
+		{
+			xml = node.xml.replace(/\r\n\t[\t]*/g, '').
+				replace(/>\r\n/g, '>').
+				replace(/\r\n/g, '\n');
+		}
+
+		// Replaces linefeeds with HTML Entities.
+		linefeed = linefeed || '&#xa;';
+		xml = xml.replace(/\n/g, linefeed);
+		  
+		return xml;
+	},
+	
+	/**
+	 * Function: extractTextWithWhitespace
+	 * 
+	 * Returns the text content of the specified node.
+	 * 
+	 * Parameters:
+	 * 
+	 * elems - DOM nodes to return the text for.
+	 */
+	extractTextWithWhitespace: function(elems)
+	{
+	    // Known block elements for handling linefeeds (list is not complete)
+		var blocks = ['BLOCKQUOTE', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'OL', 'P', 'PRE', 'TABLE', 'UL'];
+		var ret = [];
+		
+		function doExtract(elts)
+		{
+			// Single break should be ignored
+			if (elts.length == 1 && (elts[0].nodeName == 'BR' ||
+				elts[0].innerHTML == '\n'))
+			{
+				return;
+			}
+			
+		    for (var i = 0; i < elts.length; i++)
+		    {
+		        var elem = elts[i];
+
+				// DIV with a br or linefeed forces a linefeed
+				if (elem.nodeName == 'BR' || elem.innerHTML == '\n' ||
+					((elts.length == 1 || i == 0) && (elem.nodeName == 'DIV' &&
+					elem.innerHTML.toLowerCase() == '<br>')))
+		    	{
+	    			ret.push('\n');
+		    	}
+				else
+				{
+			        if (elem.nodeType === 3 || elem.nodeType === 4)
+			        {
+			        	if (elem.nodeValue.length > 0)
+			        	{
+			        		ret.push(elem.nodeValue);
+			        	}
+			        }
+			        else if (elem.nodeType !== 8 && elem.childNodes.length > 0)
+					{
+						doExtract(elem.childNodes);
+					}
+			        
+	        		if (i < elts.length - 1 && mxUtils.indexOf(blocks, elts[i + 1].nodeName) >= 0)
+	        		{
+	        			ret.push('\n');		
+	        		}
+				}
+		    }
+		};
+		
+		doExtract(elems);
+	    
+	    return ret.join('');
+	},
+
+	/**
+	 * Function: replaceTrailingNewlines
+	 * 
+	 * Replaces each trailing newline with the given pattern.
+	 */
+	replaceTrailingNewlines: function(str, pattern)
+	{
+		// LATER: Check is this can be done with a regular expression
+		var postfix = '';
+		
+		while (str.length > 0 && str.charAt(str.length - 1) == '\n')
+		{
+			str = str.substring(0, str.length - 1);
+			postfix += pattern;
+		}
+		
+		return str + postfix;
+	},
+
+	/**
+	 * Function: getTextContent
+	 * 
+	 * Returns the text content of the specified node.
+	 * 
+	 * Parameters:
+	 * 
+	 * node - DOM node to return the text content for.
+	 */
+	getTextContent: function(node)
+	{
+		if (node.innerText !== undefined)
+		{
+			return node.innerText;
+		}
+		else
+		{
+			return (node != null) ? node[(node.textContent === undefined) ? 'text' : 'textContent'] : '';
+		}
+	},
+	
+	/**
+	 * Function: setTextContent
+	 * 
+	 * Sets the text content of the specified node.
+	 * 
+	 * Parameters:
+	 * 
+	 * node - DOM node to set the text content for.
+	 * text - String that represents the text content.
+	 */
+	setTextContent: function(node, text)
+	{
+		if (node.innerText !== undefined)
+		{
+			node.innerText = text;
+		}
+		else
+		{
+			node[(node.textContent === undefined) ? 'text' : 'textContent'] = text;
+		}
+	},
+	
+	/**
+	 * Function: getInnerHtml
+	 * 
+	 * Returns the inner HTML for the given node as a string or an empty string
+	 * if no node was specified. The inner HTML is the text representing all
+	 * children of the node, but not the node itself.
+	 * 
+	 * Parameters:
+	 * 
+	 * node - DOM node to return the inner HTML for.
+	 */
+	getInnerHtml: function()
+	{
+		if (mxClient.IS_IE)
+		{
+			return function(node)
+			{
+				if (node != null)
+				{
+					return node.innerHTML;
+				}
+				
+				return '';
+			};
+		}
+		else
+		{
+			return function(node)
+			{
+				if (node != null)
+				{
+					var serializer = new XMLSerializer();
+					return serializer.serializeToString(node);
+				}
+				
+				return '';
+			};
+		}
+	}(),
+
+	/**
+	 * Function: getOuterHtml
+	 * 
+	 * Returns the outer HTML for the given node as a string or an empty
+	 * string if no node was specified. The outer HTML is the text representing
+	 * all children of the node including the node itself.
+	 * 
+	 * Parameters:
+	 * 
+	 * node - DOM node to return the outer HTML for.
+	 */
+	getOuterHtml: function()
+	{
+		if (mxClient.IS_IE)
+		{
+			return function(node)
+			{
+				if (node != null)
+				{
+					if (node.outerHTML != null)
+					{
+						return node.outerHTML;
+					}
+					else
+					{
+						var tmp = [];
+						tmp.push('<'+node.nodeName);
+						
+						var attrs = node.attributes;
+						
+						if (attrs != null)
+						{
+							for (var i = 0; i < attrs.length; i++)
+							{
+								var value = attrs[i].value;
+								
+								if (value != null && value.length > 0)
+								{
+									tmp.push(' ');
+									tmp.push(attrs[i].nodeName);
+									tmp.push('="');
+									tmp.push(value);
+									tmp.push('"');
+								}
+							}
+						}
+						
+						if (node.innerHTML.length == 0)
+						{
+							tmp.push('/>');
+						}
+						else
+						{
+							tmp.push('>');
+							tmp.push(node.innerHTML);
+							tmp.push('</'+node.nodeName+'>');
+						}
+						
+						return tmp.join('');
+					}
+				}
+				
+				return '';
+			};
+		}
+		else
+		{
+			return function(node)
+			{
+				if (node != null)
+				{
+					var serializer = new XMLSerializer();
+					return serializer.serializeToString(node);
+				}
+				
+				return '';
+			};
+		}
+	}(),
+	
+	/**
+	 * Function: write
+	 * 
+	 * Creates a text node for the given string and appends it to the given
+	 * parent. Returns the text node.
+	 * 
+	 * Parameters:
+	 * 
+	 * parent - DOM node to append the text node to.
+	 * text - String representing the text to be added.
+	 */
+	write: function(parent, text)
+	{
+		var doc = parent.ownerDocument;
+		var node = doc.createTextNode(text);
+		
+		if (parent != null)
+		{
+			parent.appendChild(node);
+		}
+		
+		return node;
+	},
+	
+	/**
+	 * Function: writeln
+	 * 
+	 * Creates a text node for the given string and appends it to the given
+	 * parent with an additional linefeed. Returns the text node.
+	 * 
+	 * Parameters:
+	 * 
+	 * parent - DOM node to append the text node to.
+	 * text - String representing the text to be added.
+	 */
+	writeln: function(parent, text)
+	{
+		var doc = parent.ownerDocument;
+		var node = doc.createTextNode(text);
+		
+		if (parent != null)
+		{
+			parent.appendChild(node);
+			parent.appendChild(document.createElement('br'));
+		}
+		
+		return node;
+	},
+	
+	/**
+	 * Function: br
+	 * 
+	 * Appends a linebreak to the given parent and returns the linebreak.
+	 * 
+	 * Parameters:
+	 * 
+	 * parent - DOM node to append the linebreak to.
+	 */
+	br: function(parent, count)
+	{
+		count = count || 1;
+		var br = null;
+		
+		for (var i = 0; i < count; i++)
+		{
+			if (parent != null)
+			{
+				br = parent.ownerDocument.createElement('br');
+				parent.appendChild(br);
+			}
+		}
+		
+		return br;
+	},
+		
+	/**
+	 * Function: button
+	 * 
+	 * Returns a new button with the given level and function as an onclick
+	 * event handler.
+	 * 
+	 * (code)
+	 * document.body.appendChild(mxUtils.button('Test', function(evt)
+	 * {
+	 *   alert('Hello, World!');
+	 * }));
+	 * (end)
+	 * 
+	 * Parameters:
+	 * 
+	 * label - String that represents the label of the button.
+	 * funct - Function to be called if the button is pressed.
+	 * doc - Optional document to be used for creating the button. Default is the
+	 * current document.
+	 */
+	button: function(label, funct, doc)
+	{
+		doc = (doc != null) ? doc : document;
+		
+		var button = doc.createElement('button');
+		mxUtils.write(button, label);
+
+		mxEvent.addListener(button, 'click', function(evt)
+		{
+			funct(evt);
+		});
+		
+		return button;
+	},
+	
+	/**
+	 * Function: para
+	 * 
+	 * Appends a new paragraph with the given text to the specified parent and
+	 * returns the paragraph.
+	 * 
+	 * Parameters:
+	 * 
+	 * parent - DOM node to append the text node to.
+	 * text - String representing the text for the new paragraph.
+	 */
+	para: function(parent, text)
+	{
+		var p = document.createElement('p');
+		mxUtils.write(p, text);
+
+		if (parent != null)
+		{
+			parent.appendChild(p);
+		}
+		
+		return p;
+	},
+
+	/**
+	 * Function: addTransparentBackgroundFilter
+	 * 
+	 * Adds a transparent background to the filter of the given node. This
+	 * background can be used in IE8 standards mode (native IE8 only) to pass
+	 * events through the node.
+	 */
+	addTransparentBackgroundFilter: function(node)
+	{
+		node.style.filter += 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'' +
+			mxClient.imageBasePath + '/transparent.gif\', sizingMethod=\'scale\')';
+	},
+
+	/**
+	 * Function: linkAction
+	 * 
+	 * Adds a hyperlink to the specified parent that invokes action on the
+	 * specified editor.
+	 * 
+	 * Parameters:
+	 * 
+	 * parent - DOM node to contain the new link.
+	 * text - String that is used as the link label.
+	 * editor - <mxEditor> that will execute the action.
+	 * action - String that defines the name of the action to be executed.
+	 * pad - Optional left-padding for the link. Default is 0.
+	 */
+	linkAction: function(parent, text, editor, action, pad)
+	{
+		return mxUtils.link(parent, text, function()
+		{
+			editor.execute(action);
+		}, pad);
+	},
+
+	/**
+	 * Function: linkInvoke
+	 * 
+	 * Adds a hyperlink to the specified parent that invokes the specified
+	 * function on the editor passing along the specified argument. The
+	 * function name is the name of a function of the editor instance,
+	 * not an action name.
+	 * 
+	 * Parameters:
+	 * 
+	 * parent - DOM node to contain the new link.
+	 * text - String that is used as the link label.
+	 * editor - <mxEditor> instance to execute the function on.
+	 * functName - String that represents the name of the function.
+	 * arg - Object that represents the argument to the function.
+	 * pad - Optional left-padding for the link. Default is 0.
+	 */
+	linkInvoke: function(parent, text, editor, functName, arg, pad)
+	{
+		return mxUtils.link(parent, text, function()
+		{
+			editor[functName](arg);
+		}, pad);
+	},
+	
+	/**
+	 * Function: link
+	 * 
+	 * Adds a hyperlink to the specified parent and invokes the given function
+	 * when the link is clicked.
+	 * 
+	 * Parameters:
+	 * 
+	 * parent - DOM node to contain the new link.
+	 * text - String that is used as the link label.
+	 * funct - Function to execute when the link is clicked.
+	 * pad - Optional left-padding for the link. Default is 0.
+	 */
+	link: function(parent, text, funct, pad)
+	{
+		var a = document.createElement('span');
+		
+		a.style.color = 'blue';
+		a.style.textDecoration = 'underline';
+		a.style.cursor = 'pointer';
+		
+		if (pad != null)
+		{
+			a.style.paddingLeft = pad+'px';
+		}
+		
+		mxEvent.addListener(a, 'click', funct);
+		mxUtils.write(a, text);
+		
+		if (parent != null)
+		{
+			parent.appendChild(a);
+		}
+		
+		return a;
+	},
+
+	/**
+	 * Function: fit
+	 * 
+	 * Makes sure the given node is inside the visible area of the window. This
+	 * is done by setting the left and top in the style. 
+	 */
+	fit: function(node)
+	{
+		var left = parseInt(node.offsetLeft);
+		var width = parseInt(node.offsetWidth);
+			
+		var offset = mxUtils.getDocumentScrollOrigin(node.ownerDocument);
+		var sl = offset.x;
+		var st = offset.y;
+
+		var b = document.body;
+		var d = document.documentElement;
+		var right = (sl) + (b.clientWidth || d.clientWidth);
+		
+		if (left + width > right)
+		{
+			node.style.left = Math.max(sl, right - width) + 'px';
+		}
+		
+		var top = parseInt(node.offsetTop);
+		var height = parseInt(node.offsetHeight);
+		
+		var bottom = st + Math.max(b.clientHeight || 0, d.clientHeight);
+		
+		if (top + height > bottom)
+		{
+			node.style.top = Math.max(st, bottom - height) + 'px';
+		}
+	},
+
+	/**
+	 * Function: load
+	 * 
+	 * Loads the specified URL *synchronously* and returns the <mxXmlRequest>.
+	 * Throws an exception if the file cannot be loaded. See <mxUtils.get> for
+	 * an asynchronous implementation.
+	 *
+	 * Example:
+	 * 
+	 * (code)
+	 * try
+	 * {
+	 *   var req = mxUtils.load(filename);
+	 *   var root = req.getDocumentElement();
+	 *   // Process XML DOM...
+	 * }
+	 * catch (ex)
+	 * {
+	 *   mxUtils.alert('Cannot load '+filename+': '+ex);
+	 * }
+	 * (end)
+	 * 
+	 * Parameters:
+	 * 
+	 * url - URL to get the data from.
+	 */
+	load: function(url)
+	{
+		var req = new mxXmlRequest(url, null, 'GET', false);
+		req.send();
+		
+		return req;
+	},
+
+	/**
+	 * Function: get
+	 * 
+	 * Loads the specified URL *asynchronously* and invokes the given functions
+	 * depending on the request status. Returns the <mxXmlRequest> in use. Both
+	 * functions take the <mxXmlRequest> as the only parameter. See
+	 * <mxUtils.load> for a synchronous implementation.
+	 *
+	 * Example:
+	 * 
+	 * (code)
+	 * mxUtils.get(url, function(req)
+	 * {
+	 *    var node = req.getDocumentElement();
+	 *    // Process XML DOM...
+	 * });
+	 * (end)
+	 * 
+	 * So for example, to load a diagram into an existing graph model, the
+	 * following code is used.
+	 * 
+	 * (code)
+	 * mxUtils.get(url, function(req)
+	 * {
+	 *   var node = req.getDocumentElement();
+	 *   var dec = new mxCodec(node.ownerDocument);
+	 *   dec.decode(node, graph.getModel());
+	 * });
+	 * (end)
+	 * 
+	 * Parameters:
+	 * 
+	 * url - URL to get the data from.
+	 * onload - Optional function to execute for a successful response.
+	 * onerror - Optional function to execute on error.
+	 * binary - Optional boolean parameter that specifies if the request is
+	 * binary.
+	 * timeout - Optional timeout in ms before calling ontimeout.
+	 * ontimeout - Optional function to execute on timeout.
+	 */
+	get: function(url, onload, onerror, binary, timeout, ontimeout)
+	{
+		var req = new mxXmlRequest(url, null, 'GET');
+		
+		if (binary != null)
+		{
+			req.setBinary(binary);
+		}
+		
+		req.send(onload, onerror, timeout, ontimeout);
+		
+		return req;
+	},
+
+	/**
+	 * Function: getAll
+	 * 
+	 * Loads the URLs in the given array *asynchronously* and invokes the given function
+	 * if all requests returned with a valid 2xx status. The error handler is invoked
+	 * once on the first error or invalid response.
+	 *
+	 * Parameters:
+	 * 
+	 * urls - Array of URLs to be loaded.
+	 * onload - Callback with array of <mxXmlRequests>.
+	 * onerror - Optional function to execute on error.
+	 */
+	getAll: function(urls, onload, onerror)
+	{
+		var remain = urls.length;
+		var result = [];
+		var errors = 0;
+		var err = function()
+		{
+			if (errors == 0 && onerror != null)
+			{
+				onerror();
+			}
+
+			errors++;
+		};
+		
+		for (var i = 0; i < urls.length; i++)
+		{
+			(function(url, index)
+			{
+				mxUtils.get(url, function(req)
+				{
+					var status = req.getStatus();
+					
+					if (status < 200 || status > 299)
+					{
+						err();
+					}
+					else
+					{
+						result[index] = req;
+						remain--;
+						
+						if (remain == 0)
+						{
+							onload(result);
+						}
+					}
+				}, err);
+			})(urls[i], i);
+		}
+		
+		if (remain == 0)
+		{
+			onload(result);			
+		}
+	},
+	
+	/**
+	 * Function: post
+	 * 
+	 * Posts the specified params to the given URL *asynchronously* and invokes
+	 * the given functions depending on the request status. Returns the
+	 * <mxXmlRequest> in use. Both functions take the <mxXmlRequest> as the
+	 * only parameter. Make sure to use encodeURIComponent for the parameter
+	 * values.
+	 *
+	 * Example:
+	 * 
+	 * (code)
+	 * mxUtils.post(url, 'key=value', function(req)
+	 * {
+	 * 	mxUtils.alert('Ready: '+req.isReady()+' Status: '+req.getStatus());
+	 *  // Process req.getDocumentElement() using DOM API if OK...
+	 * });
+	 * (end)
+	 * 
+	 * Parameters:
+	 * 
+	 * url - URL to get the data from.
+	 * params - Parameters for the post request.
+	 * onload - Optional function to execute for a successful response.
+	 * onerror - Optional function to execute on error.
+	 */
+	post: function(url, params, onload, onerror)
+	{
+		return new mxXmlRequest(url, params).send(onload, onerror);
+	},
+	
+	/**
+	 * Function: submit
+	 * 
+	 * Submits the given parameters to the specified URL using
+	 * <mxXmlRequest.simulate> and returns the <mxXmlRequest>.
+	 * Make sure to use encodeURIComponent for the parameter
+	 * values.
+	 * 
+	 * Parameters:
+	 * 
+	 * url - URL to get the data from.
+	 * params - Parameters for the form.
+	 * doc - Document to create the form in.
+	 * target - Target to send the form result to.
+	 */
+	submit: function(url, params, doc, target)
+	{
+		return new mxXmlRequest(url, params).simulate(doc, target);
+	},
+	
+	/**
+	 * Function: loadInto
+	 * 
+	 * Loads the specified URL *asynchronously* into the specified document,
+	 * invoking onload after the document has been loaded. This implementation
+	 * does not use <mxXmlRequest>, but the document.load method.
+	 * 
+	 * Parameters:
+	 * 
+	 * url - URL to get the data from.
+	 * doc - The document to load the URL into.
+	 * onload - Function to execute when the URL has been loaded.
+	 */
+	loadInto: function(url, doc, onload)
+	{
+		if (mxClient.IS_IE)
+		{
+			doc.onreadystatechange = function ()
+			{
+				if (doc.readyState == 4)
+				{
+					onload();
+				}
+			};
+		}
+		else
+		{
+			doc.addEventListener('load', onload, false);
+		}
+		
+		doc.load(url);
+	},
+	
+	/**
+	 * Function: getValue
+	 * 
+	 * Returns the value for the given key in the given associative array or
+	 * the given default value if the value is null.
+	 * 
+	 * Parameters:
+	 * 
+	 * array - Associative array that contains the value for the key.
+	 * key - Key whose value should be returned.
+	 * defaultValue - Value to be returned if the value for the given
+	 * key is null.
+	 */
+	getValue: function(array, key, defaultValue)
+	{
+		var value = (array != null) ? array[key] : null;
+
+		if (value == null)
+		{
+			value = defaultValue;			
+		}
+		
+		return value;
+	},
+	
+	/**
+	 * Function: getNumber
+	 * 
+	 * Returns the numeric value for the given key in the given associative
+	 * array or the given default value (or 0) if the value is null. The value
+	 * is converted to a numeric value using the Number function.
+	 * 
+	 * Parameters:
+	 * 
+	 * array - Associative array that contains the value for the key.
+	 * key - Key whose value should be returned.
+	 * defaultValue - Value to be returned if the value for the given
+	 * key is null. Default is 0.
+	 */
+	getNumber: function(array, key, defaultValue)
+	{
+		var value = (array != null) ? array[key] : null;
+
+		if (value == null)
+		{
+			value = defaultValue || 0;			
+		}
+		
+		return Number(value);
+	},
+	
+	/**
+	 * Function: getColor
+	 * 
+	 * Returns the color value for the given key in the given associative
+	 * array or the given default value if the value is null. If the value
+	 * is <mxConstants.NONE> then null is returned.
+	 * 
+	 * Parameters:
+	 * 
+	 * array - Associative array that contains the value for the key.
+	 * key - Key whose value should be returned.
+	 * defaultValue - Value to be returned if the value for the given
+	 * key is null. Default is null.
+	 */
+	getColor: function(array, key, defaultValue)
+	{
+		var value = (array != null) ? array[key] : null;
+
+		if (value == null)
+		{
+			value = defaultValue;
+		}
+		else if (value == mxConstants.NONE)
+		{
+			value = null;
+		}
+		
+		return value;
+	},
+
+	/**
+	 * Function: clone
+	 * 
+	 * Recursively clones the specified object ignoring all fieldnames in the
+	 * given array of transient fields. <mxObjectIdentity.FIELD_NAME> is always
+	 * ignored by this function.
+	 * 
+	 * Parameters:
+	 * 
+	 * obj - Object to be cloned.
+	 * transients - Optional array of strings representing the fieldname to be
+	 * ignored.
+	 * shallow - Optional boolean argument to specify if a shallow clone should
+	 * be created, that is, one where all object references are not cloned or,
+	 * in other words, one where only atomic (strings, numbers) values are
+	 * cloned. Default is false.
+	 */
+	clone: function(obj, transients, shallow)
+	{
+		shallow = (shallow != null) ? shallow : false;
+		var clone = null;
+		
+		if (obj != null && typeof(obj.constructor) == 'function')
+		{
+			clone = new obj.constructor();
+			
+		    for (var i in obj)
+		    {
+		    	if (i != mxObjectIdentity.FIELD_NAME && (transients == null ||
+		    		mxUtils.indexOf(transients, i) < 0))
+		    	{
+			    	if (!shallow && typeof(obj[i]) == 'object')
+			    	{
+			            clone[i] = mxUtils.clone(obj[i]);
+			        }
+			        else
+			        {
+			            clone[i] = obj[i];
+			        }
+				}
+		    }
+		}
+		
+	    return clone;
+	},
+
+	/**
+	 * Function: equalPoints
+	 * 
+	 * Compares all mxPoints in the given lists.
+	 * 
+	 * Parameters:
+	 * 
+	 * a - Array of <mxPoints> to be compared.
+	 * b - Array of <mxPoints> to be compared.
+	 */
+	equalPoints: function(a, b)
+	{
+		if ((a == null && b != null) || (a != null && b == null) ||
+			(a != null && b != null && a.length != b.length))
+		{
+			return false;
+		}
+		else if (a != null && b != null)
+		{
+			for (var i = 0; i < a.length; i++)
+			{
+				if (a[i] == b[i] || (a[i] != null && !a[i].equals(b[i])))
+				{
+					return false;
+				}
+			}
+		}
+		
+		return true;
+	},
+
+	/**
+	 * Function: equalEntries
+	 * 
+	 * Returns true if all properties of the given objects are equal. Values
+	 * with NaN are equal to NaN and unequal to any other value.
+	 * 
+	 * Parameters:
+	 * 
+	 * a - First object to be compared.
+	 * b - Second object to be compared.
+	 */
+	equalEntries: function(a, b)
+	{
+		if ((a == null && b != null) || (a != null && b == null) ||
+			(a != null && b != null && a.length != b.length))
+		{
+			return false;
+		}
+		else if (a != null && b != null)
+		{
+			// Counts keys in b to check if all values have been compared
+			var count = 0;
+			
+			for (var key in b)
+			{
+				count++;
+			}
+			
+			for (var key in a)
+			{
+				count--
+				
+				if ((!mxUtils.isNaN(a[key]) || !mxUtils.isNaN(b[key])) && a[key] != b[key])
+				{
+					return false;
+				}
+			}
+		}
+		
+		return count == 0;
+	},
+	
+	/**
+	 * Function: removeDuplicates
+	 * 
+	 * Removes all duplicates from the given array.
+	 */
+	removeDuplicates: function(arr)
+	{
+		var dict = new mxDictionary();
+		var result = [];
+		
+		for (var i = 0; i < arr.length; i++)
+		{
+			if (!dict.get(arr[i]))
+			{
+				result.push(arr[i]);
+				dict.put(arr[i], true);
+			}
+		}
+
+		return result;
+	},
+	
+	/**
+	 * Function: isNaN
+	 *
+	 * Returns true if the given value is of type number and isNaN returns true.
+	 */
+	isNaN: function(value)
+	{
+		return typeof(value) == 'number' && isNaN(value);
+	},
+	
+	/**
+	 * Function: extend
+	 *
+	 * Assigns a copy of the superclass prototype to the subclass prototype.
+	 * Note that this does not call the constructor of the superclass at this
+	 * point, the superclass constructor should be called explicitely in the
+	 * subclass constructor. Below is an example.
+	 * 
+	 * (code)
+	 * MyGraph = function(container, model, renderHint, stylesheet)
+	 * {
+	 *   mxGraph.call(this, container, model, renderHint, stylesheet);
+	 * }
+	 * 
+	 * mxUtils.extend(MyGraph, mxGraph);
+	 * (end)
+	 * 
+	 * Parameters:
+	 * 
+	 * ctor - Constructor of the subclass.
+	 * superCtor - Constructor of the superclass.
+	 */
+	extend: function(ctor, superCtor)
+	{
+		var f = function() {};
+		f.prototype = superCtor.prototype;
+		
+		ctor.prototype = new f();
+		ctor.prototype.constructor = ctor;
+	},
+
+	/**
+	 * Function: toString
+	 * 
+	 * Returns a textual representation of the specified object.
+	 * 
+	 * Parameters:
+	 * 
+	 * obj - Object to return the string representation for.
+	 */
+	toString: function(obj)
+	{
+	    var output = '';
+	    
+	    for (var i in obj)
+	    {
+	    	try
+	    	{
+			    if (obj[i] == null)
+			    {
+		            output += i + ' = [null]\n';
+			    }
+			    else if (typeof(obj[i]) == 'function')
+			    {
+		            output += i + ' => [Function]\n';
+		        }
+		        else if (typeof(obj[i]) == 'object')
+		        {
+		        	var ctor = mxUtils.getFunctionName(obj[i].constructor); 
+		            output += i + ' => [' + ctor + ']\n';
+		        }
+		        else
+		        {
+		            output += i + ' = ' + obj[i] + '\n';
+		        }
+	    	}
+	    	catch (e)
+	    	{
+	    		output += i + '=' + e.message;
+	    	}
+	    }
+	    
+	    return output;
+	},
+
+	/**
+	 * Function: toRadians
+	 * 
+	 * Converts the given degree to radians.
+	 */
+	toRadians: function(deg)
+	{
+		return Math.PI * deg / 180;
+	},
+
+	/**
+	 * Function: toDegree
+	 * 
+	 * Converts the given radians to degree.
+	 */
+	toDegree: function(rad)
+	{
+		return rad * 180 / Math.PI;
+	},
+	
+	/**
+	 * Function: arcToCurves
+	 * 
+	 * Converts the given arc to a series of curves.
+	 */
+	arcToCurves: function(x0, y0, r1, r2, angle, largeArcFlag, sweepFlag, x, y)
+	{
+		x -= x0;
+		y -= y0;
+		
+        if (r1 === 0 || r2 === 0) 
+        {
+        	return result;
+        }
+        
+        var fS = sweepFlag;
+        var psai = angle;
+        r1 = Math.abs(r1);
+        r2 = Math.abs(r2);
+        var ctx = -x / 2;
+        var cty = -y / 2;
+        var cpsi = Math.cos(psai * Math.PI / 180);
+        var spsi = Math.sin(psai * Math.PI / 180);
+        var rxd = cpsi * ctx + spsi * cty;
+        var ryd = -1 * spsi * ctx + cpsi * cty;
+        var rxdd = rxd * rxd;
+        var rydd = ryd * ryd;
+        var r1x = r1 * r1;
+        var r2y = r2 * r2;
+        var lamda = rxdd / r1x + rydd / r2y;
+        var sds;
+        
+        if (lamda > 1) 
+        {
+        	r1 = Math.sqrt(lamda) * r1;
+        	r2 = Math.sqrt(lamda) * r2;
+        	sds = 0;
+        }  
+        else
+        {
+        	var seif = 1;
+            
+        	if (largeArcFlag === fS) 
+        	{
+        		seif = -1;
+        	}
+            
+        	sds = seif * Math.sqrt((r1x * r2y - r1x * rydd - r2y * rxdd) / (r1x * rydd + r2y * rxdd));
+        }
+        
+        var txd = sds * r1 * ryd / r2;
+        var tyd = -1 * sds * r2 * rxd / r1;
+        var tx = cpsi * txd - spsi * tyd + x / 2;
+        var ty = spsi * txd + cpsi * tyd + y / 2;
+        var rad = Math.atan2((ryd - tyd) / r2, (rxd - txd) / r1) - Math.atan2(0, 1);
+        var s1 = (rad >= 0) ? rad : 2 * Math.PI + rad;
+        rad = Math.atan2((-ryd - tyd) / r2, (-rxd - txd) / r1) - Math.atan2((ryd - tyd) / r2, (rxd - txd) / r1);
+        var dr = (rad >= 0) ? rad : 2 * Math.PI + rad;
+        
+        if (fS == 0 && dr > 0) 
+        {
+        	dr -= 2 * Math.PI;
+        }
+        else if (fS != 0 && dr < 0) 
+        {
+        	dr += 2 * Math.PI;
+        }
+        
+        var sse = dr * 2 / Math.PI;
+        var seg = Math.ceil(sse < 0 ? -1 * sse : sse);
+        var segr = dr / seg;
+        var t = 8/3 * Math.sin(segr / 4) * Math.sin(segr / 4) / Math.sin(segr / 2);
+        var cpsir1 = cpsi * r1;
+        var cpsir2 = cpsi * r2;
+        var spsir1 = spsi * r1;
+        var spsir2 = spsi * r2;
+        var mc = Math.cos(s1);
+        var ms = Math.sin(s1);
+        var x2 = -t * (cpsir1 * ms + spsir2 * mc);
+        var y2 = -t * (spsir1 * ms - cpsir2 * mc);
+        var x3 = 0;
+        var y3 = 0;
+
+		var result = [];
+        
+        for (var n = 0; n < seg; ++n) 
+        {
+            s1 += segr;
+            mc = Math.cos(s1);
+            ms = Math.sin(s1);
+            
+            x3 = cpsir1 * mc - spsir2 * ms + tx;
+            y3 = spsir1 * mc + cpsir2 * ms + ty;
+            var dx = -t * (cpsir1 * ms + spsir2 * mc);
+            var dy = -t * (spsir1 * ms - cpsir2 * mc);
+            
+            // CurveTo updates x0, y0 so need to restore it
+            var index = n * 6;
+            result[index] = Number(x2 + x0);
+            result[index + 1] = Number(y2 + y0);
+            result[index + 2] = Number(x3 - dx + x0);
+            result[index + 3] = Number(y3 - dy + y0);
+            result[index + 4] = Number(x3 + x0);
+            result[index + 5] = Number(y3 + y0);
+            
+			x2 = x3 + dx;
+            y2 = y3 + dy;
+        }
+        
+        return result;
+	},
+
+	/**
+	 * Function: getBoundingBox
+	 * 
+	 * Returns the bounding box for the rotated rectangle.
+	 * 
+	 * Parameters:
+	 * 
+	 * rect - <mxRectangle> to be rotated.
+	 * angle - Number that represents the angle (in degrees).
+	 * cx - Optional <mxPoint> that represents the rotation center. If no
+	 * rotation center is given then the center of rect is used.
+	 */
+	getBoundingBox: function(rect, rotation, cx)
+	{
+        var result = null;
+
+        if (rect != null && rotation != null && rotation != 0)
+        {
+            var rad = mxUtils.toRadians(rotation);
+            var cos = Math.cos(rad);
+            var sin = Math.sin(rad);
+
+            cx = (cx != null) ? cx : new mxPoint(rect.x + rect.width / 2, rect.y  + rect.height / 2);
+
+            var p1 = new mxPoint(rect.x, rect.y);
+            var p2 = new mxPoint(rect.x + rect.width, rect.y);
+            var p3 = new mxPoint(p2.x, rect.y + rect.height);
+            var p4 = new mxPoint(rect.x, p3.y);
+
+            p1 = mxUtils.getRotatedPoint(p1, cos, sin, cx);
+            p2 = mxUtils.getRotatedPoint(p2, cos, sin, cx);
+            p3 = mxUtils.getRotatedPoint(p3, cos, sin, cx);
+            p4 = mxUtils.getRotatedPoint(p4, cos, sin, cx);
+
+            result = new mxRectangle(p1.x, p1.y, 0, 0);
+            result.add(new mxRectangle(p2.x, p2.y, 0, 0));
+            result.add(new mxRectangle(p3.x, p3.y, 0, 0));
+            result.add(new mxRectangle(p4.x, p4.y, 0, 0));
+        }
+
+        return result;
+	},
+
+	/**
+	 * Function: getRotatedPoint
+	 * 
+	 * Rotates the given point by the given cos and sin.
+	 */
+	getRotatedPoint: function(pt, cos, sin, c)
+	{
+		c = (c != null) ? c : new mxPoint();
+		var x = pt.x - c.x;
+		var y = pt.y - c.y;
+
+		var x1 = x * cos - y * sin;
+		var y1 = y * cos + x * sin;
+
+		return new mxPoint(x1 + c.x, y1 + c.y);
+	},
+	
+	/**
+	 * Returns an integer mask of the port constraints of the given map
+	 * @param dict the style map to determine the port constraints for
+	 * @param defaultValue Default value to return if the key is undefined.
+	 * @return the mask of port constraint directions
+	 * 
+	 * Parameters:
+	 * 
+	 * terminal - <mxCelState> that represents the terminal.
+	 * edge - <mxCellState> that represents the edge.
+	 * source - Boolean that specifies if the terminal is the source terminal.
+	 * defaultValue - Default value to be returned.
+	 */
+	getPortConstraints: function(terminal, edge, source, defaultValue)
+	{
+		var value = mxUtils.getValue(terminal.style, mxConstants.STYLE_PORT_CONSTRAINT,
+			mxUtils.getValue(edge.style, (source) ? mxConstants.STYLE_SOURCE_PORT_CONSTRAINT :
+				mxConstants.STYLE_TARGET_PORT_CONSTRAINT, null));
+		
+		if (value == null)
+		{
+			return defaultValue;
+		}
+		else
+		{
+			var directions = value.toString();
+			var returnValue = mxConstants.DIRECTION_MASK_NONE;
+			var constraintRotationEnabled = mxUtils.getValue(terminal.style, mxConstants.STYLE_PORT_CONSTRAINT_ROTATION, 0);
+			var rotation = 0;
+			
+			if (constraintRotationEnabled == 1)
+			{
+				rotation = mxUtils.getValue(terminal.style, mxConstants.STYLE_ROTATION, 0);
+			}
+			
+			var quad = 0;
+
+			if (rotation > 45)
+			{
+				quad = 1;
+				
+				if (rotation >= 135)
+				{
+					quad = 2;
+				}
+			}
+			else if (rotation < -45)
+			{
+				quad = 3;
+				
+				if (rotation <= -135)
+				{
+					quad = 2;
+				}
+			}
+
+			if (directions.indexOf(mxConstants.DIRECTION_NORTH) >= 0)
+			{
+				switch (quad)
+				{
+					case 0:
+						returnValue |= mxConstants.DIRECTION_MASK_NORTH;
+						break;
+					case 1:
+						returnValue |= mxConstants.DIRECTION_MASK_EAST;
+						break;
+					case 2:
+						returnValue |= mxConstants.DIRECTION_MASK_SOUTH;
+						break;
+					case 3:
+						returnValue |= mxConstants.DIRECTION_MASK_WEST;
+						break;
+				}
+			}
+			if (directions.indexOf(mxConstants.DIRECTION_WEST) >= 0)
+			{
+				switch (quad)
+				{
+					case 0:
+						returnValue |= mxConstants.DIRECTION_MASK_WEST;
+						break;
+					case 1:
+						returnValue |= mxConstants.DIRECTION_MASK_NORTH;
+						break;
+					case 2:
+						returnValue |= mxConstants.DIRECTION_MASK_EAST;
+						break;
+					case 3:
+						returnValue |= mxConstants.DIRECTION_MASK_SOUTH;
+						break;
+				}
+			}
+			if (directions.indexOf(mxConstants.DIRECTION_SOUTH) >= 0)
+			{
+				switch (quad)
+				{
+					case 0:
+						returnValue |= mxConstants.DIRECTION_MASK_SOUTH;
+						break;
+					case 1:
+						returnValue |= mxConstants.DIRECTION_MASK_WEST;
+						break;
+					case 2:
+						returnValue |= mxConstants.DIRECTION_MASK_NORTH;
+						break;
+					case 3:
+						returnValue |= mxConstants.DIRECTION_MASK_EAST;
+						break;
+				}
+			}
+			if (directions.indexOf(mxConstants.DIRECTION_EAST) >= 0)
+			{
+				switch (quad)
+				{
+					case 0:
+						returnValue |= mxConstants.DIRECTION_MASK_EAST;
+						break;
+					case 1:
+						returnValue |= mxConstants.DIRECTION_MASK_SOUTH;
+						break;
+					case 2:
+						returnValue |= mxConstants.DIRECTION_MASK_WEST;
+						break;
+					case 3:
+						returnValue |= mxConstants.DIRECTION_MASK_NORTH;
+						break;
+				}
+			}
+
+			return returnValue;
+		}
+	},
+	
+	/**
+	 * Function: reversePortConstraints
+	 * 
+	 * Reverse the port constraint bitmask. For example, north | east
+	 * becomes south | west
+	 */
+	reversePortConstraints: function(constraint)
+	{
+		var result = 0;
+		
+		result = (constraint & mxConstants.DIRECTION_MASK_WEST) << 3;
+		result |= (constraint & mxConstants.DIRECTION_MASK_NORTH) << 1;
+		result |= (constraint & mxConstants.DIRECTION_MASK_SOUTH) >> 1;
+		result |= (constraint & mxConstants.DIRECTION_MASK_EAST) >> 3;
+		
+		return result;
+	},
+	
+	/**
+	 * Function: findNearestSegment
+	 * 
+	 * Finds the index of the nearest segment on the given cell state for
+	 * the specified coordinate pair.
+	 */
+	findNearestSegment: function(state, x, y)
+	{
+		var index = -1;
+		
+		if (state.absolutePoints.length > 0)
+		{
+			var last = state.absolutePoints[0];
+			var min = null;
+			
+			for (var i = 1; i < state.absolutePoints.length; i++)
+			{
+				var current = state.absolutePoints[i];
+				var dist = mxUtils.ptSegDistSq(last.x, last.y,
+					current.x, current.y, x, y);
+				
+				if (min == null || dist < min)
+				{
+					min = dist;
+					index = i - 1;
+				}
+
+				last = current;
+			}
+		}
+		
+		return index;
+	},
+
+	/**
+	 * Function: getDirectedBounds
+	 * 
+	 * Adds the given margins to the given rectangle and rotates and flips the
+	 * rectangle according to the respective styles in style.
+	 */
+	getDirectedBounds: function (rect, m, style, flipH, flipV)
+	{
+		var d = mxUtils.getValue(style, mxConstants.STYLE_DIRECTION, mxConstants.DIRECTION_EAST);
+		flipH = (flipH != null) ? flipH : mxUtils.getValue(style, mxConstants.STYLE_FLIPH, false);
+		flipV = (flipV != null) ? flipV : mxUtils.getValue(style, mxConstants.STYLE_FLIPV, false);
+
+		m.x = Math.round(Math.max(0, Math.min(rect.width, m.x)));
+		m.y = Math.round(Math.max(0, Math.min(rect.height, m.y)));
+		m.width = Math.round(Math.max(0, Math.min(rect.width, m.width)));
+		m.height = Math.round(Math.max(0, Math.min(rect.height, m.height)));
+		
+		if ((flipV && (d == mxConstants.DIRECTION_SOUTH || d == mxConstants.DIRECTION_NORTH)) ||
+			(flipH && (d == mxConstants.DIRECTION_EAST || d == mxConstants.DIRECTION_WEST)))
+		{
+			var tmp = m.x;
+			m.x = m.width;
+			m.width = tmp;
+		}
+			
+		if ((flipH && (d == mxConstants.DIRECTION_SOUTH || d == mxConstants.DIRECTION_NORTH)) ||
+			(flipV && (d == mxConstants.DIRECTION_EAST || d == mxConstants.DIRECTION_WEST)))
+		{
+			var tmp = m.y;
+			m.y = m.height;
+			m.height = tmp;
+		}
+		
+		var m2 = mxRectangle.fromRectangle(m);
+		
+		if (d == mxConstants.DIRECTION_SOUTH)
+		{
+			m2.y = m.x;
+			m2.x = m.height;
+			m2.width = m.y;
+			m2.height = m.width;
+		}
+		else if (d == mxConstants.DIRECTION_WEST)
+		{
+			m2.y = m.height;
+			m2.x = m.width;
+			m2.width = m.x;
+			m2.height = m.y;
+		}
+		else if (d == mxConstants.DIRECTION_NORTH)
+		{
+			m2.y = m.width;
+			m2.x = m.y;
+			m2.width = m.height;
+			m2.height = m.x;
+		}
+		
+		return new mxRectangle(rect.x + m2.x, rect.y + m2.y, rect.width - m2.width - m2.x, rect.height - m2.height - m2.y);
+	},
+
+	/**
+	 * Function: getPerimeterPoint
+	 * 
+	 * Returns the intersection between the polygon defined by the array of
+	 * points and the line between center and point.
+	 */
+	getPerimeterPoint: function (pts, center, point)
+	{
+		var min = null;
+		
+		for (var i = 0; i < pts.length - 1; i++)
+		{
+			var pt = mxUtils.intersection(pts[i].x, pts[i].y, pts[i + 1].x, pts[i + 1].y,
+				center.x, center.y, point.x, point.y);
+			
+			if (pt != null)
+			{
+				var dx = point.x - pt.x;
+				var dy = point.y - pt.y;
+				var ip = {p: pt, distSq: dy * dy + dx * dx};
+				
+				if (ip != null && (min == null || min.distSq > ip.distSq))
+				{
+					min = ip;
+				}
+			}
+		}
+		
+		return (min != null) ? min.p : null;
+	},
+
+	/**
+	 * Function: rectangleIntersectsSegment
+	 * 
+	 * Returns true if the given rectangle intersects the given segment.
+	 * 
+	 * Parameters:
+	 * 
+	 * bounds - <mxRectangle> that represents the rectangle.
+	 * p1 - <mxPoint> that represents the first point of the segment.
+	 * p2 - <mxPoint> that represents the second point of the segment.
+	 */
+	rectangleIntersectsSegment: function(bounds, p1, p2)
+	{
+		var top = bounds.y;
+		var left = bounds.x;
+		var bottom = top + bounds.height;
+		var right = left + bounds.width;
+			
+		// Find min and max X for the segment
+		var minX = p1.x;
+		var maxX = p2.x;
+		
+		if (p1.x > p2.x)
+		{
+		  minX = p2.x;
+		  maxX = p1.x;
+		}
+		
+		// Find the intersection of the segment's and rectangle's x-projections
+		if (maxX > right)
+		{
+		  maxX = right;
+		}
+		
+		if (minX < left)
+		{
+		  minX = left;
+		}
+		
+		if (minX > maxX) // If their projections do not intersect return false
+		{
+		  return false;
+		}
+		
+		// Find corresponding min and max Y for min and max X we found before
+		var minY = p1.y;
+		var maxY = p2.y;
+		var dx = p2.x - p1.x;
+		
+		if (Math.abs(dx) > 0.0000001)
+		{
+		  var a = (p2.y - p1.y) / dx;
+		  var b = p1.y - a * p1.x;
+		  minY = a * minX + b;
+		  maxY = a * maxX + b;
+		}
+		
+		if (minY > maxY)
+		{
+		  var tmp = maxY;
+		  maxY = minY;
+		  minY = tmp;
+		}
+		
+		// Find the intersection of the segment's and rectangle's y-projections
+		if (maxY > bottom)
+		{
+		  maxY = bottom;
+		}
+		
+		if (minY < top)
+		{
+		  minY = top;
+		}
+		
+		if (minY > maxY) // If Y-projections do not intersect return false
+		{
+		  return false;
+		}
+		
+		return true;
+	},
+	
+	/**
+	 * Function: contains
+	 * 
+	 * Returns true if the specified point (x, y) is contained in the given rectangle.
+	 * 
+	 * Parameters:
+	 * 
+	 * bounds - <mxRectangle> that represents the area.
+	 * x - X-coordinate of the point.
+	 * y - Y-coordinate of the point.
+	 */
+	contains: function(bounds, x, y)
+	{
+		return (bounds.x <= x && bounds.x + bounds.width >= x &&
+				bounds.y <= y && bounds.y + bounds.height >= y);
+	},
+
+	/**
+	 * Function: intersects
+	 * 
+	 * Returns true if the two rectangles intersect.
+	 * 
+	 * Parameters:
+	 * 
+	 * a - <mxRectangle> to be checked for intersection.
+	 * b - <mxRectangle> to be checked for intersection.
+	 */
+	intersects: function(a, b)
+	{
+		var tw = a.width;
+		var th = a.height;
+		var rw = b.width;
+		var rh = b.height;
+		
+		if (rw <= 0 || rh <= 0 || tw <= 0 || th <= 0)
+		{
+		    return false;
+		}
+		
+		var tx = a.x;
+		var ty = a.y;
+		var rx = b.x;
+		var ry = b.y;
+		
+		rw += rx;
+		rh += ry;
+		tw += tx;
+		th += ty;
+
+		return ((rw < rx || rw > tx) &&
+			(rh < ry || rh > ty) &&
+			(tw < tx || tw > rx) &&
+			(th < ty || th > ry));
+	},
+
+	/**
+	 * Function: intersects
+	 * 
+	 * Returns true if the two rectangles intersect.
+	 * 
+	 * Parameters:
+	 * 
+	 * a - <mxRectangle> to be checked for intersection.
+	 * b - <mxRectangle> to be checked for intersection.
+	 */
+	intersectsHotspot: function(state, x, y, hotspot, min, max)
+	{
+		hotspot = (hotspot != null) ? hotspot : 1;
+		min = (min != null) ? min : 0;
+		max = (max != null) ? max : 0;
+		
+		if (hotspot > 0)
+		{
+			var cx = state.getCenterX();
+			var cy = state.getCenterY();
+			var w = state.width;
+			var h = state.height;
+			
+			var start = mxUtils.getValue(state.style, mxConstants.STYLE_STARTSIZE) * state.view.scale;
+
+			if (start > 0)
+			{
+				if (mxUtils.getValue(state.style, mxConstants.STYLE_HORIZONTAL, true))
+				{
+					cy = state.y + start / 2;
+					h = start;
+				}
+				else
+				{
+					cx = state.x + start / 2;
+					w = start;
+				}
+			}
+
+			w = Math.max(min, w * hotspot);
+			h = Math.max(min, h * hotspot);
+			
+			if (max > 0)
+			{
+				w = Math.min(w, max);
+				h = Math.min(h, max);
+			}
+			
+			var rect = new mxRectangle(cx - w / 2, cy - h / 2, w, h);
+			var alpha = mxUtils.toRadians(mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION) || 0);
+			
+			if (alpha != 0)
+			{
+				var cos = Math.cos(-alpha);
+				var sin = Math.sin(-alpha);
+				var cx = new mxPoint(state.getCenterX(), state.getCenterY());
+				var pt = mxUtils.getRotatedPoint(new mxPoint(x, y), cos, sin, cx);
+				x = pt.x;
+				y = pt.y;
+			}
+			
+			return mxUtils.contains(rect, x, y);			
+		}
+		
+		return true;
+	},
+
+	/**
+	 * Function: getOffset
+	 * 
+	 * Returns the offset for the specified container as an <mxPoint>. The
+	 * offset is the distance from the top left corner of the container to the
+	 * top left corner of the document.
+	 * 
+	 * Parameters:
+	 * 
+	 * container - DOM node to return the offset for.
+	 * scollOffset - Optional boolean to add the scroll offset of the document.
+	 * Default is false.
+	 */
+	getOffset: function(container, scrollOffset)
+	{
+		var offsetLeft = 0;
+		var offsetTop = 0;
+		
+		// Ignores document scroll origin for fixed elements
+		var fixed = false;
+		var node = container;
+		var b = document.body;
+		var d = document.documentElement;
+
+		while (node != null && node != b && node != d && !fixed)
+		{
+			var style = mxUtils.getCurrentStyle(node);
+			
+			if (style != null)
+			{
+				fixed = fixed || style.position == 'fixed';
+			}
+			
+			node = node.parentNode;
+		}
+		
+		if (!scrollOffset && !fixed)
+		{
+			var offset = mxUtils.getDocumentScrollOrigin(container.ownerDocument);
+			offsetLeft += offset.x;
+			offsetTop += offset.y;
+		}
+		
+		var r = container.getBoundingClientRect();
+		
+		if (r != null)
+		{
+			offsetLeft += r.left;
+			offsetTop += r.top;
+		}
+		
+		return new mxPoint(offsetLeft, offsetTop);
+	},
+
+	/**
+	 * Function: getDocumentScrollOrigin
+	 * 
+	 * Returns the scroll origin of the given document or the current document
+	 * if no document is given.
+	 */
+	getDocumentScrollOrigin: function(doc)
+	{
+		if (mxClient.IS_QUIRKS)
+		{
+			return new mxPoint(doc.body.scrollLeft, doc.body.scrollTop);
+		}
+		else
+		{
+			var wnd = doc.defaultView || doc.parentWindow;
+			
+			var x = (wnd != null && window.pageXOffset !== undefined) ? window.pageXOffset : (document.documentElement || document.body.parentNode || document.body).scrollLeft;
+			var y = (wnd != null && window.pageYOffset !== undefined) ? window.pageYOffset : (document.documentElement || document.body.parentNode || document.body).scrollTop;
+			
+			return new mxPoint(x, y);
+		}
+	},
+	
+	/**
+	 * Function: getScrollOrigin
+	 * 
+	 * Returns the top, left corner of the viewrect as an <mxPoint>.
+	 * 
+	 * Parameters:
+	 * 
+	 * node - DOM node whose scroll origin should be returned.
+	 * includeAncestors - Whether the scroll origin of the ancestors should be
+	 * included. Default is false.
+	 * includeDocument - Whether the scroll origin of the document should be
+	 * included. Default is true.
+	 */
+	getScrollOrigin: function(node, includeAncestors, includeDocument)
+	{
+		includeAncestors = (includeAncestors != null) ? includeAncestors : false;
+		includeDocument = (includeDocument != null) ? includeDocument : true;
+		
+		var doc = (node != null) ? node.ownerDocument : document;
+		var b = doc.body;
+		var d = doc.documentElement;
+		var result = new mxPoint();
+		var fixed = false;
+
+		while (node != null && node != b && node != d)
+		{
+			if (!isNaN(node.scrollLeft) && !isNaN(node.scrollTop))
+			{
+				result.x += node.scrollLeft;
+				result.y += node.scrollTop;
+			}
+			
+			var style = mxUtils.getCurrentStyle(node);
+			
+			if (style != null)
+			{
+				fixed = fixed || style.position == 'fixed';
+			}
+
+			node = (includeAncestors) ? node.parentNode : null;
+		}
+
+		if (!fixed && includeDocument)
+		{
+			var origin = mxUtils.getDocumentScrollOrigin(doc);
+
+			result.x += origin.x;
+			result.y += origin.y;
+		}
+		
+		return result;
+	},
+
+	/**
+	 * Function: convertPoint
+	 * 
+	 * Converts the specified point (x, y) using the offset of the specified
+	 * container and returns a new <mxPoint> with the result.
+	 * 
+	 * (code)
+	 * var pt = mxUtils.convertPoint(graph.container,
+	 *   mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+	 * (end)
+	 * 
+	 * Parameters:
+	 * 
+	 * container - DOM node to use for the offset.
+	 * x - X-coordinate of the point to be converted.
+	 * y - Y-coordinate of the point to be converted.
+	 */
+	convertPoint: function(container, x, y)
+	{
+		var origin = mxUtils.getScrollOrigin(container, false);
+		var offset = mxUtils.getOffset(container);
+
+		offset.x -= origin.x;
+		offset.y -= origin.y;
+		
+		return new mxPoint(x - offset.x, y - offset.y);
+	},
+	
+	/**
+	 * Function: ltrim
+	 * 
+	 * Strips all whitespaces from the beginning of the string. Without the
+	 * second parameter, this will trim these characters:
+	 * 
+	 * - " " (ASCII 32 (0x20)), an ordinary space
+	 * - "\t" (ASCII 9 (0x09)), a tab
+	 * - "\n" (ASCII 10 (0x0A)), a new line (line feed)
+	 * - "\r" (ASCII 13 (0x0D)), a carriage return
+	 * - "\0" (ASCII 0 (0x00)), the NUL-byte
+	 * - "\x0B" (ASCII 11 (0x0B)), a vertical tab
+	 */
+	ltrim: function(str, chars)
+	{
+		chars = chars || "\\s";
+		
+		return (str != null) ? str.replace(new RegExp("^[" + chars + "]+", "g"), "") : null;
+	},
+	
+	/**
+	 * Function: rtrim
+	 * 
+	 * Strips all whitespaces from the end of the string. Without the second
+	 * parameter, this will trim these characters:
+	 * 
+	 * - " " (ASCII 32 (0x20)), an ordinary space
+	 * - "\t" (ASCII 9 (0x09)), a tab
+	 * - "\n" (ASCII 10 (0x0A)), a new line (line feed)
+	 * - "\r" (ASCII 13 (0x0D)), a carriage return
+	 * - "\0" (ASCII 0 (0x00)), the NUL-byte
+	 * - "\x0B" (ASCII 11 (0x0B)), a vertical tab
+	 */
+	rtrim: function(str, chars)
+	{
+		chars = chars || "\\s";
+		
+		return (str != null) ? str.replace(new RegExp("[" + chars + "]+$", "g"), "") : null;
+	},
+	
+	/**
+	 * Function: trim
+	 * 
+	 * Strips all whitespaces from both end of the string.
+	 * Without the second parameter, Javascript function will trim these
+	 * characters:
+	 * 
+	 * - " " (ASCII 32 (0x20)), an ordinary space
+	 * - "\t" (ASCII 9 (0x09)), a tab
+	 * - "\n" (ASCII 10 (0x0A)), a new line (line feed)
+	 * - "\r" (ASCII 13 (0x0D)), a carriage return
+	 * - "\0" (ASCII 0 (0x00)), the NUL-byte
+	 * - "\x0B" (ASCII 11 (0x0B)), a vertical tab
+	 */
+	trim: function(str, chars)
+	{
+		return mxUtils.ltrim(mxUtils.rtrim(str, chars), chars);
+	},
+	
+	/**
+	 * Function: isNumeric
+	 * 
+	 * Returns true if the specified value is numeric, that is, if it is not
+	 * null, not an empty string, not a HEX number and isNaN returns false.
+	 * 
+	 * Parameters:
+	 * 
+	 * n - String representing the possibly numeric value.
+	 */
+	isNumeric: function(n)
+	{
+		return !isNaN(parseFloat(n)) && isFinite(n) && (typeof(n) != 'string' || n.toLowerCase().indexOf('0x') < 0);
+	},
+
+	/**
+	 * Function: isInteger
+	 * 
+	 * Returns true if the given value is an valid integer number.
+	 * 
+	 * Parameters:
+	 * 
+	 * n - String representing the possibly numeric value.
+	 */
+	isInteger: function(n)
+	{
+		return String(parseInt(n)) === String(n);
+	},
+
+	/**
+	 * Function: mod
+	 * 
+	 * Returns the remainder of division of n by m. You should use this instead
+	 * of the built-in operation as the built-in operation does not properly
+	 * handle negative numbers.
+	 */
+	mod: function(n, m)
+	{
+		return ((n % m) + m) % m;
+	},
+
+	/**
+	 * Function: intersection
+	 * 
+	 * Returns the intersection of two lines as an <mxPoint>.
+	 * 
+	 * Parameters:
+	 * 
+	 * x0 - X-coordinate of the first line's startpoint.
+	 * y0 - X-coordinate of the first line's startpoint.
+	 * x1 - X-coordinate of the first line's endpoint.
+	 * y1 - Y-coordinate of the first line's endpoint.
+	 * x2 - X-coordinate of the second line's startpoint.
+	 * y2 - Y-coordinate of the second line's startpoint.
+	 * x3 - X-coordinate of the second line's endpoint.
+	 * y3 - Y-coordinate of the second line's endpoint.
+	 */
+	intersection: function (x0, y0, x1, y1, x2, y2, x3, y3)
+	{
+		var denom = ((y3 - y2) * (x1 - x0)) - ((x3 - x2) * (y1 - y0));
+		var nume_a = ((x3 - x2) * (y0 - y2)) - ((y3 - y2) * (x0 - x2));
+		var nume_b = ((x1 - x0) * (y0 - y2)) - ((y1 - y0) * (x0 - x2));
+
+		var ua = nume_a / denom;
+		var ub = nume_b / denom;
+		
+		if(ua >= 0.0 && ua <= 1.0 && ub >= 0.0 && ub <= 1.0)
+		{
+			// Get the intersection point
+			var x = x0 + ua * (x1 - x0);
+			var y = y0 + ua * (y1 - y0);
+			
+			return new mxPoint(x, y);
+		}
+		
+		// No intersection
+		return null;
+	},
+	
+	/**
+	 * Function: ptSegDistSq
+	 * 
+	 * Returns the square distance between a segment and a point. To get the
+	 * distance between a point and a line (with infinite length) use
+	 * <mxUtils.ptLineDist>.
+	 * 
+	 * Parameters:
+	 * 
+	 * x1 - X-coordinate of the startpoint of the segment.
+	 * y1 - Y-coordinate of the startpoint of the segment.
+	 * x2 - X-coordinate of the endpoint of the segment.
+	 * y2 - Y-coordinate of the endpoint of the segment.
+	 * px - X-coordinate of the point.
+	 * py - Y-coordinate of the point.
+	 */
+	ptSegDistSq: function(x1, y1, x2, y2, px, py)
+    {
+		x2 -= x1;
+		y2 -= y1;
+
+		px -= x1;
+		py -= y1;
+
+		var dotprod = px * x2 + py * y2;
+		var projlenSq;
+
+		if (dotprod <= 0.0)
+		{
+		    projlenSq = 0.0;
+		}
+		else
+		{
+		    px = x2 - px;
+		    py = y2 - py;
+		    dotprod = px * x2 + py * y2;
+
+		    if (dotprod <= 0.0)
+		    {
+				projlenSq = 0.0;
+		    }
+		    else
+		    {
+				projlenSq = dotprod * dotprod / (x2 * x2 + y2 * y2);
+		    }
+		}
+
+		var lenSq = px * px + py * py - projlenSq;
+		
+		if (lenSq < 0)
+		{
+		    lenSq = 0;
+		}
+		
+		return lenSq;
+    },
+	
+	/**
+	 * Function: ptLineDist
+	 * 
+	 * Returns the distance between a line defined by two points and a point.
+	 * To get the distance between a point and a segment (with a specific
+	 * length) use <mxUtils.ptSeqDistSq>.
+	 * 
+	 * Parameters:
+	 * 
+	 * x1 - X-coordinate of point 1 of the line.
+	 * y1 - Y-coordinate of point 1 of the line.
+	 * x2 - X-coordinate of point 1 of the line.
+	 * y2 - Y-coordinate of point 1 of the line.
+	 * px - X-coordinate of the point.
+	 * py - Y-coordinate of the point.
+	 */
+    ptLineDist: function(x1, y1, x2, y2, px, py)
+    {
+		return Math.abs((y2 - y1) * px - (x2 - x1) * py + x2 * y1 - y2 * x1) /
+			Math.sqrt((y2 - y1) * (y2 - y1) + (x2 - x1) * (x2 - x1));
+    },
+    	
+	/**
+	 * Function: relativeCcw
+	 * 
+	 * Returns 1 if the given point on the right side of the segment, 0 if its
+	 * on the segment, and -1 if the point is on the left side of the segment.
+	 * 
+	 * Parameters:
+	 * 
+	 * x1 - X-coordinate of the startpoint of the segment.
+	 * y1 - Y-coordinate of the startpoint of the segment.
+	 * x2 - X-coordinate of the endpoint of the segment.
+	 * y2 - Y-coordinate of the endpoint of the segment.
+	 * px - X-coordinate of the point.
+	 * py - Y-coordinate of the point.
+	 */
+	relativeCcw: function(x1, y1, x2, y2, px, py)
+    {
+		x2 -= x1;
+		y2 -= y1;
+		px -= x1;
+		py -= y1;
+		var ccw = px * y2 - py * x2;
+		
+		if (ccw == 0.0)
+		{
+		    ccw = px * x2 + py * y2;
+		    
+		    if (ccw > 0.0)
+		    {
+				px -= x2;
+				py -= y2;
+				ccw = px * x2 + py * y2;
+				
+				if (ccw < 0.0)
+				{
+				    ccw = 0.0;
+				}
+		    }
+		}
+		
+		return (ccw < 0.0) ? -1 : ((ccw > 0.0) ? 1 : 0);
+    },
+    
+	/**
+	 * Function: animateChanges
+	 * 
+	 * See <mxEffects.animateChanges>. This is for backwards compatibility and
+	 * will be removed later.
+	 */
+	animateChanges: function(graph, changes)
+	{
+		// LATER: Deprecated, remove this function
+    	mxEffects.animateChanges.apply(this, arguments);
+	},
+    
+	/**
+	 * Function: cascadeOpacity
+	 * 
+	 * See <mxEffects.cascadeOpacity>. This is for backwards compatibility and
+	 * will be removed later.
+	 */
+    cascadeOpacity: function(graph, cell, opacity)
+	{
+		mxEffects.cascadeOpacity.apply(this, arguments);
+	},
+
+	/**
+	 * Function: fadeOut
+	 * 
+	 * See <mxEffects.fadeOut>. This is for backwards compatibility and
+	 * will be removed later.
+	 */
+	fadeOut: function(node, from, remove, step, delay, isEnabled)
+	{
+		mxEffects.fadeOut.apply(this, arguments);
+	},
+	
+	/**
+	 * Function: setOpacity
+	 * 
+	 * Sets the opacity of the specified DOM node to the given value in %.
+	 * 
+	 * Parameters:
+	 * 
+	 * node - DOM node to set the opacity for.
+	 * value - Opacity in %. Possible values are between 0 and 100.
+	 */
+	setOpacity: function(node, value)
+	{
+		if (mxUtils.isVml(node))
+		{
+	    	if (value >= 100)
+	    	{
+	    		node.style.filter = '';
+	    	}
+	    	else
+	    	{
+	    		// TODO: Why is the division by 5 needed in VML?
+			    node.style.filter = 'alpha(opacity=' + (value/5) + ')';
+	    	}
+		}
+		else if (mxClient.IS_IE && (typeof(document.documentMode) === 'undefined' || document.documentMode < 9))
+	    {
+	    	if (value >= 100)
+	    	{
+	    		node.style.filter = '';
+	    	}
+	    	else
+	    	{
+			    node.style.filter = 'alpha(opacity=' + value + ')';
+	    	}
+		}
+		else
+		{
+		    node.style.opacity = (value / 100);
+		}
+	},
+
+	/**
+	 * Function: createImage
+	 * 
+	 * Creates and returns an image (IMG node) or VML image (v:image) in IE6 in
+	 * quirks mode.
+	 * 
+	 * Parameters:
+	 * 
+	 * src - URL that points to the image to be displayed.
+	 */
+	createImage: function(src)
+	{
+        var imageNode = null;
+        
+		if (mxClient.IS_IE6 && document.compatMode != 'CSS1Compat')
+		{
+        	imageNode = document.createElement(mxClient.VML_PREFIX + ':image');
+        	imageNode.setAttribute('src', src);
+        	imageNode.style.borderStyle = 'none';
+        }
+		else
+		{
+			imageNode = document.createElement('img');
+			imageNode.setAttribute('src', src);
+			imageNode.setAttribute('border', '0');
+		}
+		
+		return imageNode;
+	},
+
+	/**
+	 * Function: sortCells
+	 * 
+	 * Sorts the given cells according to the order in the cell hierarchy.
+	 * Ascending is optional and defaults to true.
+	 */
+	sortCells: function(cells, ascending)
+	{
+		ascending = (ascending != null) ? ascending : true;
+		var lookup = new mxDictionary();
+		cells.sort(function(o1, o2)
+		{
+			var p1 = lookup.get(o1);
+			
+			if (p1 == null)
+			{
+				p1 = mxCellPath.create(o1).split(mxCellPath.PATH_SEPARATOR);
+				lookup.put(o1, p1);
+			}
+			
+			var p2 = lookup.get(o2);
+			
+			if (p2 == null)
+			{
+				p2 = mxCellPath.create(o2).split(mxCellPath.PATH_SEPARATOR);
+				lookup.put(o2, p2);
+			}
+			
+			var comp = mxCellPath.compare(p1, p2);
+			
+			return (comp == 0) ? 0 : (((comp > 0) == ascending) ? 1 : -1);
+		});
+		
+		return cells;
+	},
+
+	/**
+	 * Function: getStylename
+	 * 
+	 * Returns the stylename in a style of the form [(stylename|key=value);] or
+	 * an empty string if the given style does not contain a stylename.
+	 * 
+	 * Parameters:
+	 * 
+	 * style - String of the form [(stylename|key=value);].
+	 */
+	getStylename: function(style)
+	{
+		if (style != null)
+		{
+			var pairs = style.split(';');
+			var stylename = pairs[0];
+			
+			if (stylename.indexOf('=') < 0)
+			{
+				return stylename;
+			}
+		}
+				
+		return '';
+	},
+
+	/**
+	 * Function: getStylenames
+	 * 
+	 * Returns the stylenames in a style of the form [(stylename|key=value);]
+	 * or an empty array if the given style does not contain any stylenames.
+	 * 
+	 * Parameters:
+	 * 
+	 * style - String of the form [(stylename|key=value);].
+	 */
+	getStylenames: function(style)
+	{
+		var result = [];
+		
+		if (style != null)
+		{
+			var pairs = style.split(';');
+			
+			for (var i = 0; i < pairs.length; i++)
+			{
+				if (pairs[i].indexOf('=') < 0)
+				{
+					result.push(pairs[i]);
+				}
+			}
+		}
+				
+		return result;
+	},
+
+	/**
+	 * Function: indexOfStylename
+	 * 
+	 * Returns the index of the given stylename in the given style. This
+	 * returns -1 if the given stylename does not occur (as a stylename) in the
+	 * given style, otherwise it returns the index of the first character.
+	 */
+	indexOfStylename: function(style, stylename)
+	{
+		if (style != null && stylename != null)
+		{
+			var tokens = style.split(';');
+			var pos = 0;
+			
+			for (var i = 0; i < tokens.length; i++)
+			{
+				if (tokens[i] == stylename)
+				{
+					return pos;
+				}
+				
+				pos += tokens[i].length + 1;
+			}
+		}
+
+		return -1;
+	},
+	
+	/**
+	 * Function: addStylename
+	 * 
+	 * Adds the specified stylename to the given style if it does not already
+	 * contain the stylename.
+	 */
+	addStylename: function(style, stylename)
+	{
+		if (mxUtils.indexOfStylename(style, stylename) < 0)
+		{
+			if (style == null)
+			{
+				style = '';
+			}
+			else if (style.length > 0 && style.charAt(style.length - 1) != ';')
+			{
+				style += ';';
+			}
+			
+			style += stylename;
+		}
+		
+		return style;
+	},
+	
+	/**
+	 * Function: removeStylename
+	 * 
+	 * Removes all occurrences of the specified stylename in the given style
+	 * and returns the updated style. Trailing semicolons are not preserved.
+	 */
+	removeStylename: function(style, stylename)
+	{
+		var result = [];
+		
+		if (style != null)
+		{
+			var tokens = style.split(';');
+			
+			for (var i = 0; i < tokens.length; i++)
+			{
+				if (tokens[i] != stylename)
+				{
+					result.push(tokens[i]);
+				}
+			}
+		}
+		
+		return result.join(';');
+	},
+	
+	/**
+	 * Function: removeAllStylenames
+	 * 
+	 * Removes all stylenames from the given style and returns the updated
+	 * style.
+	 */
+	removeAllStylenames: function(style)
+	{
+		var result = [];
+		
+		if (style != null)
+		{
+			var tokens = style.split(';');
+			
+			for (var i = 0; i < tokens.length; i++)
+			{
+				// Keeps the key, value assignments
+				if (tokens[i].indexOf('=') >= 0)
+				{
+					result.push(tokens[i]);
+				}
+			}
+		}
+		
+		return result.join(';');
+	},
+
+	/**
+	 * Function: setCellStyles
+	 * 
+	 * Assigns the value for the given key in the styles of the given cells, or
+	 * removes the key from the styles if the value is null.
+	 * 
+	 * Parameters:
+	 * 
+	 * model - <mxGraphModel> to execute the transaction in.
+	 * cells - Array of <mxCells> to be updated.
+	 * key - Key of the style to be changed.
+	 * value - New value for the given key.
+	 */
+	setCellStyles: function(model, cells, key, value)
+	{
+		if (cells != null && cells.length > 0)
+		{
+			model.beginUpdate();
+			try
+			{
+				for (var i = 0; i < cells.length; i++)
+				{
+					if (cells[i] != null)
+					{
+						var style = mxUtils.setStyle(model.getStyle(cells[i]), key, value);
+						model.setStyle(cells[i], style);
+					}
+				}
+			}
+			finally
+			{
+				model.endUpdate();
+			}
+		}
+	},
+	
+	/**
+	 * Function: setStyle
+	 * 
+	 * Adds or removes the given key, value pair to the style and returns the
+	 * new style. If value is null or zero length then the key is removed from
+	 * the style. This is for cell styles, not for CSS styles.
+	 * 
+	 * Parameters:
+	 * 
+	 * style - String of the form [(stylename|key=value);].
+	 * key - Key of the style to be changed.
+	 * value - New value for the given key.
+	 */
+	setStyle: function(style, key, value)
+	{
+		var isValue = value != null && (typeof(value.length) == 'undefined' || value.length > 0);
+		
+		if (style == null || style.length == 0)
+		{
+			if (isValue)
+			{
+				style = key + '=' + value + ';';
+			}
+		}
+		else
+		{
+			if (style.substring(0, key.length + 1) == key + '=')
+			{
+				var next = style.indexOf(';');
+				
+				if (isValue)
+				{
+					style = key + '=' + value + ((next < 0) ? ';' : style.substring(next));
+				}
+				else
+				{
+					style = (next < 0 || next == style.length - 1) ? '' : style.substring(next + 1);
+				}
+			}
+			else
+			{
+				var index = style.indexOf(';' + key + '=');
+				
+				if (index < 0)
+				{
+					if (isValue)
+					{
+						var sep = (style.charAt(style.length - 1) == ';') ? '' : ';';
+						style = style + sep + key + '=' + value + ';';
+					}
+				}
+				else
+				{
+					var next = style.indexOf(';', index + 1);
+					
+					if (isValue)
+					{
+						style = style.substring(0, index + 1) + key + '=' + value + ((next < 0) ? ';' : style.substring(next));
+					}
+					else
+					{
+						style = style.substring(0, index) + ((next < 0) ? ';' : style.substring(next));
+					}
+				}
+			}
+		}
+		
+		return style;
+	},
+
+	/**
+	 * Function: setCellStyleFlags
+	 * 
+	 * Sets or toggles the flag bit for the given key in the cell's styles.
+	 * If value is null then the flag is toggled.
+	 * 
+	 * Example:
+	 * 
+	 * (code)
+	 * var cells = graph.getSelectionCells();
+	 * mxUtils.setCellStyleFlags(graph.model,
+	 * 			cells,
+	 * 			mxConstants.STYLE_FONTSTYLE,
+	 * 			mxConstants.FONT_BOLD);
+	 * (end)
+	 * 
+	 * Toggles the bold font style.
+	 * 
+	 * Parameters:
+	 * 
+	 * model - <mxGraphModel> that contains the cells.
+	 * cells - Array of <mxCells> to change the style for.
+	 * key - Key of the style to be changed.
+	 * flag - Integer for the bit to be changed.
+	 * value - Optional boolean value for the flag.
+	 */
+	setCellStyleFlags: function(model, cells, key, flag, value)
+	{
+		if (cells != null && cells.length > 0)
+		{
+			model.beginUpdate();
+			try
+			{
+				for (var i = 0; i < cells.length; i++)
+				{
+					if (cells[i] != null)
+					{
+						var style = mxUtils.setStyleFlag(
+							model.getStyle(cells[i]),
+							key, flag, value);
+						model.setStyle(cells[i], style);
+					}
+				}
+			}
+			finally
+			{
+				model.endUpdate();
+			}
+		}
+	},
+	
+	/**
+	 * Function: setStyleFlag
+	 * 
+	 * Sets or removes the given key from the specified style and returns the
+	 * new style. If value is null then the flag is toggled.
+	 * 
+	 * Parameters:
+	 * 
+	 * style - String of the form [(stylename|key=value);].
+	 * key - Key of the style to be changed.
+	 * flag - Integer for the bit to be changed.
+	 * value - Optional boolean value for the given flag.
+	 */
+	setStyleFlag: function(style, key, flag, value)
+	{
+		if (style == null || style.length == 0)
+		{
+			if (value || value == null)
+			{
+				style = key+'='+flag;
+			}
+			else
+			{
+				style = key+'=0';
+			}
+		}
+		else
+		{
+			var index = style.indexOf(key+'=');
+			
+			if (index < 0)
+			{
+				var sep = (style.charAt(style.length-1) == ';') ? '' : ';';
+
+				if (value || value == null)
+				{
+					style = style + sep + key + '=' + flag;
+				}
+				else
+				{
+					style = style + sep + key + '=0';
+				}
+			}
+			else
+			{
+				var cont = style.indexOf(';', index);
+				var tmp = '';
+				
+				if (cont < 0)
+				{
+					tmp  = style.substring(index+key.length+1);
+				}
+				else
+				{
+					tmp = style.substring(index+key.length+1, cont);
+				}
+				
+				if (value == null)
+				{
+					tmp = parseInt(tmp) ^ flag;
+				}
+				else if (value)
+				{
+					tmp = parseInt(tmp) | flag;
+				}
+				else
+				{
+					tmp = parseInt(tmp) & ~flag;
+				}
+				
+				style = style.substring(0, index) + key + '=' + tmp +
+					((cont >= 0) ? style.substring(cont) : '');
+			}
+		}
+		
+		return style;
+	},
+	
+	/**
+	 * Function: getAlignmentAsPoint
+	 * 
+	 * Returns an <mxPoint> that represents the horizontal and vertical alignment
+	 * for numeric computations. X is -0.5 for center, -1 for right and 0 for
+	 * left alignment. Y is -0.5 for middle, -1 for bottom and 0 for top
+	 * alignment. Default values for missing arguments is top, left.
+	 */
+	getAlignmentAsPoint: function(align, valign)
+	{
+		var dx = 0;
+		var dy = 0;
+		
+		// Horizontal alignment
+		if (align == mxConstants.ALIGN_CENTER)
+		{
+			dx = -0.5;
+		}
+		else if (align == mxConstants.ALIGN_RIGHT)
+		{
+			dx = -1;
+		}
+
+		// Vertical alignment
+		if (valign == mxConstants.ALIGN_MIDDLE)
+		{
+			dy = -0.5;
+		}
+		else if (valign == mxConstants.ALIGN_BOTTOM)
+		{
+			dy = -1;
+		}
+		
+		return new mxPoint(dx, dy);
+	},
+	
+	/**
+	 * Function: getSizeForString
+	 * 
+	 * Returns an <mxRectangle> with the size (width and height in pixels) of
+	 * the given string. The string may contain HTML markup. Newlines should be
+	 * converted to <br> before calling this method. The caller is responsible
+	 * for sanitizing the HTML markup.
+	 * 
+	 * Example:
+	 * 
+	 * (code)
+	 * var label = graph.getLabel(cell).replace(/\n/g, "<br>");
+	 * var size = graph.getSizeForString(label);
+	 * (end)
+	 * 
+	 * Parameters:
+	 * 
+	 * text - String whose size should be returned.
+	 * fontSize - Integer that specifies the font size in pixels. Default is
+	 * <mxConstants.DEFAULT_FONTSIZE>.
+	 * fontFamily - String that specifies the name of the font family. Default
+	 * is <mxConstants.DEFAULT_FONTFAMILY>.
+	 * textWidth - Optional width for text wrapping.
+	 */
+	getSizeForString: function(text, fontSize, fontFamily, textWidth)
+	{
+		fontSize = (fontSize != null) ? fontSize : mxConstants.DEFAULT_FONTSIZE;
+		fontFamily = (fontFamily != null) ? fontFamily : mxConstants.DEFAULT_FONTFAMILY;
+		var div = document.createElement('div');
+		
+		// Sets the font size and family
+		div.style.fontFamily = fontFamily;
+		div.style.fontSize = Math.round(fontSize) + 'px';
+		div.style.lineHeight = Math.round(fontSize * mxConstants.LINE_HEIGHT) + 'px';
+		
+		// Disables block layout and outside wrapping and hides the div
+		div.style.position = 'absolute';
+		div.style.visibility = 'hidden';
+		div.style.display = (mxClient.IS_QUIRKS) ? 'inline' : 'inline-block';
+		div.style.zoom = '1';
+		
+		if (textWidth != null)
+		{
+			div.style.width = textWidth + 'px';
+			div.style.whiteSpace = 'normal';
+		}
+		else
+		{
+			div.style.whiteSpace = 'nowrap';
+		}
+		
+		// Adds the text and inserts into DOM for updating of size
+		div.innerHTML = text;
+		document.body.appendChild(div);
+		
+		// Gets the size and removes from DOM
+		var size = new mxRectangle(0, 0, div.offsetWidth, div.offsetHeight);
+		document.body.removeChild(div);
+		
+		return size;
+	},
+	
+	/**
+	 * Function: getViewXml
+	 */
+	getViewXml: function(graph, scale, cells, x0, y0)
+	{
+		x0 = (x0 != null) ? x0 : 0;
+		y0 = (y0 != null) ? y0 : 0;
+		scale = (scale != null) ? scale : 1;
+
+		if (cells == null)
+		{
+			var model = graph.getModel();
+			cells = [model.getRoot()];
+		}
+		
+		var view = graph.getView();
+		var result = null;
+
+		// Disables events on the view
+		var eventsEnabled = view.isEventsEnabled();
+		view.setEventsEnabled(false);
+
+		// Workaround for label bounds not taken into account for image export.
+		// Creates a temporary draw pane which is used for rendering the text.
+		// Text rendering is required for finding the bounds of the labels.
+		var drawPane = view.drawPane;
+		var overlayPane = view.overlayPane;
+
+		if (graph.dialect == mxConstants.DIALECT_SVG)
+		{
+			view.drawPane = document.createElementNS(mxConstants.NS_SVG, 'g');
+			view.canvas.appendChild(view.drawPane);
+
+			// Redirects cell overlays into temporary container
+			view.overlayPane = document.createElementNS(mxConstants.NS_SVG, 'g');
+			view.canvas.appendChild(view.overlayPane);
+		}
+		else
+		{
+			view.drawPane = view.drawPane.cloneNode(false);
+			view.canvas.appendChild(view.drawPane);
+			
+			// Redirects cell overlays into temporary container
+			view.overlayPane = view.overlayPane.cloneNode(false);
+			view.canvas.appendChild(view.overlayPane);
+		}
+
+		// Resets the translation
+		var translate = view.getTranslate();
+		view.translate = new mxPoint(x0, y0);
+
+		// Creates the temporary cell states in the view
+		var temp = new mxTemporaryCellStates(graph.getView(), scale, cells);
+
+		try
+		{
+			var enc = new mxCodec();
+			result = enc.encode(graph.getView());
+		}
+		finally
+		{
+			temp.destroy();
+			view.translate = translate;
+			view.canvas.removeChild(view.drawPane);
+			view.canvas.removeChild(view.overlayPane);
+			view.drawPane = drawPane;
+			view.overlayPane = overlayPane;
+			view.setEventsEnabled(eventsEnabled);
+		}
+
+		return result;
+	},
+	
+	/**
+	 * Function: getScaleForPageCount
+	 * 
+	 * Returns the scale to be used for printing the graph with the given
+	 * bounds across the specifies number of pages with the given format. The
+	 * scale is always computed such that it given the given amount or fewer
+	 * pages in the print output. See <mxPrintPreview> for an example.
+	 * 
+	 * Parameters:
+	 * 
+	 * pageCount - Specifies the number of pages in the print output.
+	 * graph - <mxGraph> that should be printed.
+	 * pageFormat - Optional <mxRectangle> that specifies the page format.
+	 * Default is <mxConstants.PAGE_FORMAT_A4_PORTRAIT>.
+	 * border - The border along each side of every page.
+	 */
+	getScaleForPageCount: function(pageCount, graph, pageFormat, border)
+	{
+		if (pageCount < 1)
+		{
+			// We can't work with less than 1 page, return no scale
+			// change
+			return 1;
+		}
+		
+		pageFormat = (pageFormat != null) ? pageFormat : mxConstants.PAGE_FORMAT_A4_PORTRAIT;
+		border = (border != null) ? border : 0;
+		
+		var availablePageWidth = pageFormat.width - (border * 2);
+		var availablePageHeight = pageFormat.height - (border * 2);
+
+		// Work out the number of pages required if the
+		// graph is not scaled.
+		var graphBounds = graph.getGraphBounds().clone();
+		var sc = graph.getView().getScale();
+		graphBounds.width /= sc;
+		graphBounds.height /= sc;
+		var graphWidth = graphBounds.width;
+		var graphHeight = graphBounds.height;
+
+		var scale = 1;
+		
+		// The ratio of the width/height for each printer page
+		var pageFormatAspectRatio = availablePageWidth / availablePageHeight;
+		// The ratio of the width/height for the graph to be printer
+		var graphAspectRatio = graphWidth / graphHeight;
+		
+		// The ratio of horizontal pages / vertical pages for this 
+		// graph to maintain its aspect ratio on this page format
+		var pagesAspectRatio = graphAspectRatio / pageFormatAspectRatio;
+		
+		// Factor the square root of the page count up and down 
+		// by the pages aspect ratio to obtain a horizontal and 
+		// vertical page count that adds up to the page count
+		// and has the correct aspect ratio
+		var pageRoot = Math.sqrt(pageCount);
+		var pagesAspectRatioSqrt = Math.sqrt(pagesAspectRatio);
+		var numRowPages = pageRoot * pagesAspectRatioSqrt;
+		var numColumnPages = pageRoot / pagesAspectRatioSqrt;
+
+		// These value are rarely more than 2 rounding downs away from
+		// a total that meets the page count. In cases of one being less 
+		// than 1 page, the other value can be too high and take more iterations 
+		// In this case, just change that value to be the page count, since 
+		// we know the other value is 1
+		if (numRowPages < 1 && numColumnPages > pageCount)
+		{
+			var scaleChange = numColumnPages / pageCount;
+			numColumnPages = pageCount;
+			numRowPages /= scaleChange;
+		}
+		
+		if (numColumnPages < 1 && numRowPages > pageCount)
+		{
+			var scaleChange = numRowPages / pageCount;
+			numRowPages = pageCount;
+			numColumnPages /= scaleChange;
+		}		
+
+		var currentTotalPages = Math.ceil(numRowPages) * Math.ceil(numColumnPages);
+
+		var numLoops = 0;
+		
+		// Iterate through while the rounded up number of pages comes to
+		// a total greater than the required number
+		while (currentTotalPages > pageCount)
+		{
+			// Round down the page count (rows or columns) that is
+			// closest to its next integer down in percentage terms.
+			// i.e. Reduce the page total by reducing the total
+			// page area by the least possible amount
+
+			var roundRowDownProportion = Math.floor(numRowPages) / numRowPages;
+			var roundColumnDownProportion = Math.floor(numColumnPages) / numColumnPages;
+			
+			// If the round down proportion is, work out the proportion to
+			// round down to 1 page less
+			if (roundRowDownProportion == 1)
+			{
+				roundRowDownProportion = Math.floor(numRowPages-1) / numRowPages;
+			}
+			if (roundColumnDownProportion == 1)
+			{
+				roundColumnDownProportion = Math.floor(numColumnPages-1) / numColumnPages;
+			}
+			
+			// Check which rounding down is smaller, but in the case of very small roundings
+			// try the other dimension instead
+			var scaleChange = 1;
+			
+			// Use the higher of the two values
+			if (roundRowDownProportion > roundColumnDownProportion)
+			{
+				scaleChange = roundRowDownProportion;
+			}
+			else
+			{
+				scaleChange = roundColumnDownProportion;
+			}
+
+			numRowPages = numRowPages * scaleChange;
+			numColumnPages = numColumnPages * scaleChange;
+			currentTotalPages = Math.ceil(numRowPages) * Math.ceil(numColumnPages);
+			
+			numLoops++;
+			
+			if (numLoops > 10)
+			{
+				break;
+			}
+		}
+
+		// Work out the scale from the number of row pages required
+		// The column pages will give the same value
+		var posterWidth = availablePageWidth * numRowPages;
+		scale = posterWidth / graphWidth;
+		
+		// Allow for rounding errors
+		return scale * 0.99999;
+	},
+	
+	/**
+	 * Function: show
+	 * 
+	 * Copies the styles and the markup from the graph's container into the
+	 * given document and removes all cursor styles. The document is returned.
+	 * 
+	 * This function should be called from within the document with the graph.
+	 * If you experience problems with missing stylesheets in IE then try adding
+	 * the domain to the trusted sites.
+	 * 
+	 * Parameters:
+	 * 
+	 * graph - <mxGraph> to be copied.
+	 * doc - Document where the new graph is created.
+	 * x0 - X-coordinate of the graph view origin. Default is 0.
+	 * y0 - Y-coordinate of the graph view origin. Default is 0.
+	 * w - Optional width of the graph view.
+	 * h - Optional height of the graph view.
+	 */
+	show: function(graph, doc, x0, y0, w, h)
+	{
+		x0 = (x0 != null) ? x0 : 0;
+		y0 = (y0 != null) ? y0 : 0;
+		
+		if (doc == null)
+		{
+			var wnd = window.open();
+			doc = wnd.document;
+		}
+		else
+		{
+			doc.open();
+		}
+
+		// Workaround for missing print output in IE9 standards
+		if (document.documentMode == 9)
+		{
+			doc.writeln('<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=9"><![endif]-->');
+		}
+		
+		var bounds = graph.getGraphBounds();
+		var dx = Math.ceil(x0 - bounds.x);
+		var dy = Math.ceil(y0 - bounds.y);
+		
+		if (w == null)
+		{
+			w = Math.ceil(bounds.width + x0) + Math.ceil(Math.ceil(bounds.x) - bounds.x);
+		}
+		
+		if (h == null)
+		{
+			h = Math.ceil(bounds.height + y0) + Math.ceil(Math.ceil(bounds.y) - bounds.y);
+		}
+		
+		// Needs a special way of creating the page so that no click is required
+		// to refresh the contents after the external CSS styles have been loaded.
+		// To avoid a click or programmatic refresh, the styleSheets[].cssText
+		// property is copied over from the original document.
+		if (mxClient.IS_IE || document.documentMode == 11)
+		{
+			var html = '<html><head>';
+
+			var base = document.getElementsByTagName('base');
+			
+			for (var i = 0; i < base.length; i++)
+			{
+				html += base[i].outerHTML;
+			}
+
+			html += '<style>';
+
+			// Copies the stylesheets without having to load them again
+			for (var i = 0; i < document.styleSheets.length; i++)
+			{
+				try
+				{
+					html += document.styleSheets[i].cssText;
+				}
+				catch (e)
+				{
+					// ignore security exception
+				}
+			}
+
+			html += '</style></head><body style="margin:0px;">';
+			
+			// Copies the contents of the graph container
+			html += '<div style="position:absolute;overflow:hidden;width:' + w + 'px;height:' + h + 'px;"><div style="position:relative;left:' + dx + 'px;top:' + dy + 'px;">';
+			html += graph.container.innerHTML;
+			html += '</div></div></body><html>';
+
+			doc.writeln(html);
+			doc.close();
+		}
+		else
+		{
+			doc.writeln('<html><head>');
+			
+			var base = document.getElementsByTagName('base');
+			
+			for (var i = 0; i < base.length; i++)
+			{
+				doc.writeln(mxUtils.getOuterHtml(base[i]));
+			}
+			
+			var links = document.getElementsByTagName('link');
+			
+			for (var i = 0; i < links.length; i++)
+			{
+				doc.writeln(mxUtils.getOuterHtml(links[i]));
+			}
+	
+			var styles = document.getElementsByTagName('style');
+			
+			for (var i = 0; i < styles.length; i++)
+			{
+				doc.writeln(mxUtils.getOuterHtml(styles[i]));
+			}
+
+			doc.writeln('</head><body style="margin:0px;"></body></html>');
+			doc.close();
+
+			var outer = doc.createElement('div');
+			outer.position = 'absolute';
+			outer.overflow = 'hidden';
+			outer.style.width = w + 'px';
+			outer.style.height = h + 'px';
+
+			// Required for HTML labels if foreignObjects are disabled
+			var div = doc.createElement('div');
+			div.style.position = 'absolute';
+			div.style.left = dx + 'px';
+			div.style.top = dy + 'px';
+
+			var node = graph.container.firstChild;
+			var svg = null;
+			
+			while (node != null)
+			{
+				var clone = node.cloneNode(true);
+				
+				if (node == graph.view.drawPane.ownerSVGElement)
+				{
+					outer.appendChild(clone);
+					svg = clone;
+				}
+				else
+				{
+					div.appendChild(clone);
+				}
+				
+				node = node.nextSibling;
+			}
+
+			doc.body.appendChild(outer);
+			
+			if (div.firstChild != null)
+			{
+				doc.body.appendChild(div);
+			}
+						
+			if (svg != null)
+			{
+				svg.style.minWidth = '';
+				svg.style.minHeight = '';
+				svg.firstChild.setAttribute('transform', 'translate(' + dx + ',' + dy + ')');
+			}
+		}
+		
+		mxUtils.removeCursors(doc.body);
+	
+		return doc;
+	},
+	
+	/**
+	 * Function: printScreen
+	 * 
+	 * Prints the specified graph using a new window and the built-in print
+	 * dialog.
+	 * 
+	 * This function should be called from within the document with the graph.
+	 * 
+	 * Parameters:
+	 * 
+	 * graph - <mxGraph> to be printed.
+	 */
+	printScreen: function(graph)
+	{
+		var wnd = window.open();
+		var bounds = graph.getGraphBounds();
+		mxUtils.show(graph, wnd.document);
+		
+		var print = function()
+		{
+			wnd.focus();
+			wnd.print();
+			wnd.close();
+		};
+		
+		// Workaround for Google Chrome which needs a bit of a
+		// delay in order to render the SVG contents
+		if (mxClient.IS_GC)
+		{
+			wnd.setTimeout(print, 500);
+		}
+		else
+		{
+			print();
+		}
+	},
+	
+	/**
+	 * Function: popup
+	 * 
+	 * Shows the specified text content in a new <mxWindow> or a new browser
+	 * window if isInternalWindow is false.
+	 * 
+	 * Parameters:
+	 * 
+	 * content - String that specifies the text to be displayed.
+	 * isInternalWindow - Optional boolean indicating if an mxWindow should be
+	 * used instead of a new browser window. Default is false.
+	 */
+	popup: function(content, isInternalWindow)
+	{
+	   	if (isInternalWindow)
+	   	{
+			var div = document.createElement('div');
+			
+			div.style.overflow = 'scroll';
+			div.style.width = '636px';
+			div.style.height = '460px';
+			
+			var pre = document.createElement('pre');
+		    pre.innerHTML = mxUtils.htmlEntities(content, false).
+		    	replace(/\n/g,'<br>').replace(/ /g, '&nbsp;');
+			
+			div.appendChild(pre);
+			
+			var w = document.body.clientWidth;
+			var h = Math.max(document.body.clientHeight || 0, document.documentElement.clientHeight)
+			var wnd = new mxWindow('Popup Window', div,
+				w/2-320, h/2-240, 640, 480, false, true);
+
+			wnd.setClosable(true);
+			wnd.setVisible(true);
+		}
+		else
+		{
+			// Wraps up the XML content in a textarea
+			if (mxClient.IS_NS)
+			{
+			    var wnd = window.open();
+				wnd.document.writeln('<pre>'+mxUtils.htmlEntities(content)+'</pre');
+			   	wnd.document.close();
+			}
+			else
+			{
+			    var wnd = window.open();
+			    var pre = wnd.document.createElement('pre');
+			    pre.innerHTML = mxUtils.htmlEntities(content, false).
+			    	replace(/\n/g,'<br>').replace(/ /g, '&nbsp;');
+			   	wnd.document.body.appendChild(pre);
+			}
+	   	}
+	},
+	
+	/**
+	 * Function: alert
+	 * 
+	 * Displayss the given alert in a new dialog. This implementation uses the
+	 * built-in alert function. This is used to display validation errors when
+	 * connections cannot be changed or created.
+	 * 
+	 * Parameters:
+	 * 
+	 * message - String specifying the message to be displayed.
+	 */
+	alert: function(message)
+	{
+		alert(message);
+	},
+	
+	/**
+	 * Function: prompt
+	 * 
+	 * Displays the given message in a prompt dialog. This implementation uses
+	 * the built-in prompt function.
+	 * 
+	 * Parameters:
+	 * 
+	 * message - String specifying the message to be displayed.
+	 * defaultValue - Optional string specifying the default value.
+	 */
+	prompt: function(message, defaultValue)
+	{
+		return prompt(message, (defaultValue != null) ? defaultValue : '');
+	},
+	
+	/**
+	 * Function: confirm
+	 * 
+	 * Displays the given message in a confirm dialog. This implementation uses
+	 * the built-in confirm function.
+	 * 
+	 * Parameters:
+	 * 
+	 * message - String specifying the message to be displayed.
+	 */
+	confirm: function(message)
+	{
+		return confirm(message);
+	},
+
+	/**
+	 * Function: error
+	 * 
+	 * Displays the given error message in a new <mxWindow> of the given width.
+	 * If close is true then an additional close button is added to the window.
+	 * The optional icon specifies the icon to be used for the window. Default
+	 * is <mxUtils.errorImage>.
+	 * 
+	 * Parameters:
+	 * 
+	 * message - String specifying the message to be displayed.
+	 * width - Integer specifying the width of the window.
+	 * close - Optional boolean indicating whether to add a close button.
+	 * icon - Optional icon for the window decoration.
+	 */
+	error: function(message, width, close, icon)
+	{
+		var div = document.createElement('div');
+		div.style.padding = '20px';
+
+		var img = document.createElement('img');
+		img.setAttribute('src', icon || mxUtils.errorImage);
+		img.setAttribute('valign', 'bottom');
+		img.style.verticalAlign = 'middle';
+		div.appendChild(img);
+
+		div.appendChild(document.createTextNode('\u00a0')); // &nbsp;
+		div.appendChild(document.createTextNode('\u00a0')); // &nbsp;
+		div.appendChild(document.createTextNode('\u00a0')); // &nbsp;
+		mxUtils.write(div, message);
+
+		var w = document.body.clientWidth;
+		var h = (document.body.clientHeight || document.documentElement.clientHeight);
+		var warn = new mxWindow(mxResources.get(mxUtils.errorResource) ||
+			mxUtils.errorResource, div, (w-width)/2, h/4, width, null,
+			false, true);
+
+		if (close)
+		{
+			mxUtils.br(div);
+			
+			var tmp = document.createElement('p');
+			var button = document.createElement('button');
+
+			if (mxClient.IS_IE)
+			{
+				button.style.cssText = 'float:right';
+			}
+			else
+			{
+				button.setAttribute('style', 'float:right');
+			}
+
+			mxEvent.addListener(button, 'click', function(evt)
+			{
+				warn.destroy();
+			});
+
+			mxUtils.write(button, mxResources.get(mxUtils.closeResource) ||
+				mxUtils.closeResource);
+			
+			tmp.appendChild(button);
+			div.appendChild(tmp);
+			
+			mxUtils.br(div);
+			
+			warn.setClosable(true);
+		}
+		
+		warn.setVisible(true);
+		
+		return warn;
+	},
+
+	/**
+	 * Function: makeDraggable
+	 * 
+	 * Configures the given DOM element to act as a drag source for the
+	 * specified graph. Returns a a new <mxDragSource>. If
+	 * <mxDragSource.guideEnabled> is enabled then the x and y arguments must
+	 * be used in funct to match the preview location.
+	 * 
+	 * Example:
+	 * 
+	 * (code)
+	 * var funct = function(graph, evt, cell, x, y)
+	 * {
+	 *   if (graph.canImportCell(cell))
+	 *   {
+	 *     var parent = graph.getDefaultParent();
+	 *     var vertex = null;
+	 *     
+	 *     graph.getModel().beginUpdate();
+	 *     try
+	 *     {
+	 * 	     vertex = graph.insertVertex(parent, null, 'Hello', x, y, 80, 30);
+	 *     }
+	 *     finally
+	 *     {
+	 *       graph.getModel().endUpdate();
+	 *     }
+	 *
+	 *     graph.setSelectionCell(vertex);
+	 *   }
+	 * }
+	 * 
+	 * var img = document.createElement('img');
+	 * img.setAttribute('src', 'editors/images/rectangle.gif');
+	 * img.style.position = 'absolute';
+	 * img.style.left = '0px';
+	 * img.style.top = '0px';
+	 * img.style.width = '16px';
+	 * img.style.height = '16px';
+	 * 
+	 * var dragImage = img.cloneNode(true);
+	 * dragImage.style.width = '32px';
+	 * dragImage.style.height = '32px';
+	 * mxUtils.makeDraggable(img, graph, funct, dragImage);
+	 * document.body.appendChild(img);
+	 * (end)
+	 * 
+	 * Parameters:
+	 * 
+	 * element - DOM element to make draggable.
+	 * graphF - <mxGraph> that acts as the drop target or a function that takes a
+	 * mouse event and returns the current <mxGraph>.
+	 * funct - Function to execute on a successful drop.
+	 * dragElement - Optional DOM node to be used for the drag preview.
+	 * dx - Optional horizontal offset between the cursor and the drag
+	 * preview.
+	 * dy - Optional vertical offset between the cursor and the drag
+	 * preview.
+	 * autoscroll - Optional boolean that specifies if autoscroll should be
+	 * used. Default is mxGraph.autoscroll.
+	 * scalePreview - Optional boolean that specifies if the preview element
+	 * should be scaled according to the graph scale. If this is true, then
+	 * the offsets will also be scaled. Default is false.
+	 * highlightDropTargets - Optional boolean that specifies if dropTargets
+	 * should be highlighted. Default is true.
+	 * getDropTarget - Optional function to return the drop target for a given
+	 * location (x, y). Default is mxGraph.getCellAt.
+	 */
+	makeDraggable: function(element, graphF, funct, dragElement, dx, dy, autoscroll,
+			scalePreview, highlightDropTargets, getDropTarget)
+	{
+		var dragSource = new mxDragSource(element, funct);
+		dragSource.dragOffset = new mxPoint((dx != null) ? dx : 0,
+			(dy != null) ? dy : mxConstants.TOOLTIP_VERTICAL_OFFSET);
+		dragSource.autoscroll = autoscroll;
+		
+		// Cannot enable this by default. This needs to be enabled in the caller
+		// if the funct argument uses the new x- and y-arguments.
+		dragSource.setGuidesEnabled(false);
+		
+		if (highlightDropTargets != null)
+		{
+			dragSource.highlightDropTargets = highlightDropTargets;
+		}
+		
+		// Overrides function to find drop target cell
+		if (getDropTarget != null)
+		{
+			dragSource.getDropTarget = getDropTarget;
+		}
+		
+		// Overrides function to get current graph
+		dragSource.getGraphForEvent = function(evt)
+		{
+			return (typeof(graphF) == 'function') ? graphF(evt) : graphF;
+		};
+		
+		// Translates switches into dragSource customizations
+		if (dragElement != null)
+		{
+			dragSource.createDragElement = function()
+			{
+				return dragElement.cloneNode(true);
+			};
+			
+			if (scalePreview)
+			{
+				dragSource.createPreviewElement = function(graph)
+				{
+					var elt = dragElement.cloneNode(true);
+
+					var w = parseInt(elt.style.width);
+					var h = parseInt(elt.style.height);
+					elt.style.width = Math.round(w * graph.view.scale) + 'px';
+					elt.style.height = Math.round(h * graph.view.scale) + 'px';
+					
+					return elt;
+				};
+			}
+		}
+		
+		return dragSource;
+	}
+
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+ var mxConstants =
+ {
+	/**
+	 * Class: mxConstants
+	 * 
+	 * Defines various global constants.
+	 * 
+	 * Variable: DEFAULT_HOTSPOT
+	 * 
+	 * Defines the portion of the cell which is to be used as a connectable
+	 * region. Default is 0.3. Possible values are 0 < x <= 1. 
+	 */
+	DEFAULT_HOTSPOT: 0.3,
+
+	/**
+	 * Variable: MIN_HOTSPOT_SIZE
+	 * 
+	 * Defines the minimum size in pixels of the portion of the cell which is
+	 * to be used as a connectable region. Default is 8.
+	 */
+	MIN_HOTSPOT_SIZE: 8,
+
+	/**
+	 * Variable: MAX_HOTSPOT_SIZE
+	 * 
+	 * Defines the maximum size in pixels of the portion of the cell which is
+	 * to be used as a connectable region. Use 0 for no maximum. Default is 0.
+	 */
+	MAX_HOTSPOT_SIZE: 0,
+
+	/**
+	 * Variable: RENDERING_HINT_EXACT
+	 * 
+	 * Defines the exact rendering hint.
+	 */
+	RENDERING_HINT_EXACT: 'exact',
+
+	/**
+	 * Variable: RENDERING_HINT_FASTER
+	 * 
+	 * Defines the faster rendering hint.
+	 */
+	RENDERING_HINT_FASTER: 'faster',
+
+	/**
+	 * Variable: RENDERING_HINT_FASTEST
+	 * 
+	 * Defines the fastest rendering hint.
+	 */
+	RENDERING_HINT_FASTEST: 'fastest',
+
+	/**
+	 * Variable: DIALECT_SVG
+	 * 
+	 * Defines the SVG display dialect name.
+	 */
+	DIALECT_SVG: 'svg',
+
+	/**
+	 * Variable: DIALECT_VML
+	 * 
+	 * Defines the VML display dialect name.
+	 */
+	DIALECT_VML: 'vml',
+
+	/**
+	 * Variable: DIALECT_MIXEDHTML
+	 * 
+	 * Defines the mixed HTML display dialect name.
+	 */
+	DIALECT_MIXEDHTML: 'mixedHtml',
+
+	/**
+	 * Variable: DIALECT_PREFERHTML
+	 * 
+	 * Defines the preferred HTML display dialect name.
+	 */
+	DIALECT_PREFERHTML: 'preferHtml',
+
+	/**
+	 * Variable: DIALECT_STRICTHTML
+	 * 
+	 * Defines the strict HTML display dialect.
+	 */
+	DIALECT_STRICTHTML: 'strictHtml',
+
+	/**
+	 * Variable: NS_SVG
+	 * 
+	 * Defines the SVG namespace.
+	 */
+	NS_SVG: 'http://www.w3.org/2000/svg',
+
+	/**
+	 * Variable: NS_XHTML
+	 * 
+	 * Defines the XHTML namespace.
+	 */
+	NS_XHTML: 'http://www.w3.org/1999/xhtml',
+
+	/**
+	 * Variable: NS_XLINK
+	 * 
+	 * Defines the XLink namespace.
+	 */
+	NS_XLINK: 'http://www.w3.org/1999/xlink',
+
+	/**
+	 * Variable: SHADOWCOLOR
+	 * 
+	 * Defines the color to be used to draw shadows in shapes and windows.
+	 * Default is gray.
+	 */
+	SHADOWCOLOR: 'gray',
+
+	/**
+	 * Variable: VML_SHADOWCOLOR
+	 * 
+	 * Used for shadow color in filters where transparency is not supported
+	 * (Microsoft Internet Explorer). Default is gray.
+	 */
+	VML_SHADOWCOLOR: 'gray',
+
+	/**
+	 * Variable: SHADOW_OFFSET_X
+	 * 
+	 * Specifies the x-offset of the shadow. Default is 2.
+	 */
+	SHADOW_OFFSET_X: 2,
+
+	/**
+	 * Variable: SHADOW_OFFSET_Y
+	 * 
+	 * Specifies the y-offset of the shadow. Default is 3.
+	 */
+	SHADOW_OFFSET_Y: 3,
+	
+	/**
+	 * Variable: SHADOW_OPACITY
+	 * 
+	 * Defines the opacity for shadows. Default is 1.
+	 */
+	SHADOW_OPACITY: 1,
+ 
+	/**
+	 * Variable: NODETYPE_ELEMENT
+	 * 
+	 * DOM node of type ELEMENT.
+	 */
+	NODETYPE_ELEMENT: 1,
+
+	/**
+	 * Variable: NODETYPE_ATTRIBUTE
+	 * 
+	 * DOM node of type ATTRIBUTE.
+	 */
+	NODETYPE_ATTRIBUTE: 2,
+
+	/**
+	 * Variable: NODETYPE_TEXT
+	 * 
+	 * DOM node of type TEXT.
+	 */
+	NODETYPE_TEXT: 3,
+
+	/**
+	 * Variable: NODETYPE_CDATA
+	 * 
+	 * DOM node of type CDATA.
+	 */
+	NODETYPE_CDATA: 4,
+	
+	/**
+	 * Variable: NODETYPE_ENTITY_REFERENCE
+	 * 
+	 * DOM node of type ENTITY_REFERENCE.
+	 */
+	NODETYPE_ENTITY_REFERENCE: 5,
+
+	/**
+	 * Variable: NODETYPE_ENTITY
+	 * 
+	 * DOM node of type ENTITY.
+	 */
+	NODETYPE_ENTITY: 6,
+
+	/**
+	 * Variable: NODETYPE_PROCESSING_INSTRUCTION
+	 * 
+	 * DOM node of type PROCESSING_INSTRUCTION.
+	 */
+	NODETYPE_PROCESSING_INSTRUCTION: 7,
+
+	/**
+	 * Variable: NODETYPE_COMMENT
+	 * 
+	 * DOM node of type COMMENT.
+	 */
+	NODETYPE_COMMENT: 8,
+		
+	/**
+	 * Variable: NODETYPE_DOCUMENT
+	 * 
+	 * DOM node of type DOCUMENT.
+	 */
+	NODETYPE_DOCUMENT: 9,
+
+	/**
+	 * Variable: NODETYPE_DOCUMENTTYPE
+	 * 
+	 * DOM node of type DOCUMENTTYPE.
+	 */
+	NODETYPE_DOCUMENTTYPE: 10,
+
+	/**
+	 * Variable: NODETYPE_DOCUMENT_FRAGMENT
+	 * 
+	 * DOM node of type DOCUMENT_FRAGMENT.
+	 */
+	NODETYPE_DOCUMENT_FRAGMENT: 11,
+
+	/**
+	 * Variable: NODETYPE_NOTATION
+	 * 
+	 * DOM node of type NOTATION.
+	 */
+	NODETYPE_NOTATION: 12,
+	
+	/**
+	 * Variable: TOOLTIP_VERTICAL_OFFSET
+	 * 
+	 * Defines the vertical offset for the tooltip.
+	 * Default is 16.
+	 */
+	TOOLTIP_VERTICAL_OFFSET: 16,
+
+	/**
+	 * Variable: DEFAULT_VALID_COLOR
+	 * 
+	 * Specifies the default valid color. Default is #0000FF.
+	 */
+	DEFAULT_VALID_COLOR: '#00FF00',
+
+	/**
+	 * Variable: DEFAULT_INVALID_COLOR
+	 * 
+	 * Specifies the default invalid color. Default is #FF0000.
+	 */
+	DEFAULT_INVALID_COLOR: '#FF0000',
+
+	/**
+	 * Variable: OUTLINE_HIGHLIGHT_COLOR
+	 * 
+	 * Specifies the default highlight color for shape outlines.
+	 * Default is #0000FF. This is used in <mxEdgeHandler>.
+	 */
+	OUTLINE_HIGHLIGHT_COLOR: '#00FF00',
+
+	/**
+	 * Variable: OUTLINE_HIGHLIGHT_COLOR
+	 * 
+	 * Defines the strokewidth to be used for shape outlines.
+	 * Default is 5. This is used in <mxEdgeHandler>.
+	 */
+	OUTLINE_HIGHLIGHT_STROKEWIDTH: 5,
+
+	/**
+	 * Variable: HIGHLIGHT_STROKEWIDTH
+	 * 
+	 * Defines the strokewidth to be used for the highlights.
+	 * Default is 3.
+	 */
+	HIGHLIGHT_STROKEWIDTH: 3,
+
+	/**
+	 * Variable: CONSTRAINT_HIGHLIGHT_SIZE
+	 * 
+	 * Size of the constraint highlight (in px). Default is 2.
+	 */
+	HIGHLIGHT_SIZE: 2,
+	
+	/**
+	 * Variable: HIGHLIGHT_OPACITY
+	 * 
+	 * Opacity (in %) used for the highlights (including outline).
+	 * Default is 100.
+	 */
+	HIGHLIGHT_OPACITY: 100,
+	
+	/**
+	 * Variable: CURSOR_MOVABLE_VERTEX
+	 * 
+	 * Defines the cursor for a movable vertex. Default is 'move'.
+	 */
+	CURSOR_MOVABLE_VERTEX: 'move',
+	
+	/**
+	 * Variable: CURSOR_MOVABLE_EDGE
+	 * 
+	 * Defines the cursor for a movable edge. Default is 'move'.
+	 */
+	CURSOR_MOVABLE_EDGE: 'move',
+	
+	/**
+	 * Variable: CURSOR_LABEL_HANDLE
+	 * 
+	 * Defines the cursor for a movable label. Default is 'default'.
+	 */
+	CURSOR_LABEL_HANDLE: 'default',
+	
+	/**
+	 * Variable: CURSOR_TERMINAL_HANDLE
+	 * 
+	 * Defines the cursor for a terminal handle. Default is 'pointer'.
+	 */
+	CURSOR_TERMINAL_HANDLE: 'pointer',
+	
+	/**
+	 * Variable: CURSOR_BEND_HANDLE
+	 * 
+	 * Defines the cursor for a movable bend. Default is 'crosshair'.
+	 */
+	CURSOR_BEND_HANDLE: 'crosshair',
+
+	/**
+	 * Variable: CURSOR_VIRTUAL_BEND_HANDLE
+	 * 
+	 * Defines the cursor for a movable bend. Default is 'crosshair'.
+	 */
+	CURSOR_VIRTUAL_BEND_HANDLE: 'crosshair',
+	
+	/**
+	 * Variable: CURSOR_CONNECT
+	 * 
+	 * Defines the cursor for a connectable state. Default is 'pointer'.
+	 */
+	CURSOR_CONNECT: 'pointer',
+
+	/**
+	 * Variable: HIGHLIGHT_COLOR
+	 * 
+	 * Defines the color to be used for the cell highlighting.
+	 * Use 'none' for no color. Default is #00FF00.
+	 */
+	HIGHLIGHT_COLOR: '#00FF00',
+
+	/**
+	 * Variable: TARGET_HIGHLIGHT_COLOR
+	 * 
+	 * Defines the color to be used for highlighting a target cell for a new
+	 * or changed connection. Note that this may be either a source or
+	 * target terminal in the graph. Use 'none' for no color.
+	 * Default is #0000FF.
+	 */
+	CONNECT_TARGET_COLOR: '#0000FF',
+
+	/**
+	 * Variable: INVALID_CONNECT_TARGET_COLOR
+	 * 
+	 * Defines the color to be used for highlighting a invalid target cells
+	 * for a new or changed connections. Note that this may be either a source
+	 * or target terminal in the graph. Use 'none' for no color. Default is
+	 * #FF0000.
+	 */
+	INVALID_CONNECT_TARGET_COLOR: '#FF0000',
+
+	/**
+	 * Variable: DROP_TARGET_COLOR
+	 * 
+	 * Defines the color to be used for the highlighting target parent cells
+	 * (for drag and drop). Use 'none' for no color. Default is #0000FF.
+	 */
+	DROP_TARGET_COLOR: '#0000FF',
+
+	/**
+	 * Variable: VALID_COLOR
+	 * 
+	 * Defines the color to be used for the coloring valid connection
+	 * previews. Use 'none' for no color. Default is #FF0000.
+	 */
+	VALID_COLOR: '#00FF00',
+
+	/**
+	 * Variable: INVALID_COLOR
+	 * 
+	 * Defines the color to be used for the coloring invalid connection
+	 * previews. Use 'none' for no color. Default is #FF0000.
+	 */
+	INVALID_COLOR: '#FF0000',
+
+	/**
+	 * Variable: EDGE_SELECTION_COLOR
+	 * 
+	 * Defines the color to be used for the selection border of edges. Use
+	 * 'none' for no color. Default is #00FF00.
+	 */
+	EDGE_SELECTION_COLOR: '#00FF00',
+
+	/**
+	 * Variable: VERTEX_SELECTION_COLOR
+	 * 
+	 * Defines the color to be used for the selection border of vertices. Use
+	 * 'none' for no color. Default is #00FF00.
+	 */
+	VERTEX_SELECTION_COLOR: '#00FF00',
+
+	/**
+	 * Variable: VERTEX_SELECTION_STROKEWIDTH
+	 * 
+	 * Defines the strokewidth to be used for vertex selections.
+	 * Default is 1.
+	 */
+	VERTEX_SELECTION_STROKEWIDTH: 1,
+
+	/**
+	 * Variable: EDGE_SELECTION_STROKEWIDTH
+	 * 
+	 * Defines the strokewidth to be used for edge selections.
+	 * Default is 1.
+	 */
+	EDGE_SELECTION_STROKEWIDTH: 1,
+
+	/**
+	 * Variable: SELECTION_DASHED
+	 * 
+	 * Defines the dashed state to be used for the vertex selection
+	 * border. Default is true.
+	 */
+	VERTEX_SELECTION_DASHED: true,
+
+	/**
+	 * Variable: SELECTION_DASHED
+	 * 
+	 * Defines the dashed state to be used for the edge selection
+	 * border. Default is true.
+	 */
+	EDGE_SELECTION_DASHED: true,
+
+	/**
+	 * Variable: GUIDE_COLOR
+	 * 
+	 * Defines the color to be used for the guidelines in mxGraphHandler.
+	 * Default is #FF0000.
+	 */
+	GUIDE_COLOR: '#FF0000',
+
+	/**
+	 * Variable: GUIDE_STROKEWIDTH
+	 * 
+	 * Defines the strokewidth to be used for the guidelines in mxGraphHandler.
+	 * Default is 1.
+	 */
+	GUIDE_STROKEWIDTH: 1,
+
+	/**
+	 * Variable: OUTLINE_COLOR
+	 * 
+	 * Defines the color to be used for the outline rectangle
+	 * border.  Use 'none' for no color. Default is #0099FF.
+	 */
+	OUTLINE_COLOR: '#0099FF',
+
+	/**
+	 * Variable: OUTLINE_STROKEWIDTH
+	 * 
+	 * Defines the strokewidth to be used for the outline rectangle
+	 * stroke width. Default is 3.
+	 */
+	OUTLINE_STROKEWIDTH: (mxClient.IS_IE) ? 2 : 3,
+
+	/**
+	 * Variable: HANDLE_SIZE
+	 * 
+	 * Defines the default size for handles. Default is 6.
+	 */
+	HANDLE_SIZE: 6,
+
+	/**
+	 * Variable: LABEL_HANDLE_SIZE
+	 * 
+	 * Defines the default size for label handles. Default is 4.
+	 */
+	LABEL_HANDLE_SIZE: 4,
+
+	/**
+	 * Variable: HANDLE_FILLCOLOR
+	 * 
+	 * Defines the color to be used for the handle fill color. Use 'none' for
+	 * no color. Default is #00FF00 (green).
+	 */
+	HANDLE_FILLCOLOR: '#00FF00',
+
+	/**
+	 * Variable: HANDLE_STROKECOLOR
+	 * 
+	 * Defines the color to be used for the handle stroke color. Use 'none' for
+	 * no color. Default is black.
+	 */
+	HANDLE_STROKECOLOR: 'black',
+
+	/**
+	 * Variable: LABEL_HANDLE_FILLCOLOR
+	 * 
+	 * Defines the color to be used for the label handle fill color. Use 'none'
+	 * for no color. Default is yellow.
+	 */
+	LABEL_HANDLE_FILLCOLOR: 'yellow',
+
+	/**
+	 * Variable: CONNECT_HANDLE_FILLCOLOR
+	 * 
+	 * Defines the color to be used for the connect handle fill color. Use
+	 * 'none' for no color. Default is #0000FF (blue).
+	 */
+	CONNECT_HANDLE_FILLCOLOR: '#0000FF',
+
+	/**
+	 * Variable: LOCKED_HANDLE_FILLCOLOR
+	 * 
+	 * Defines the color to be used for the locked handle fill color. Use
+	 * 'none' for no color. Default is #FF0000 (red).
+	 */
+	LOCKED_HANDLE_FILLCOLOR: '#FF0000',
+
+	/**
+	 * Variable: OUTLINE_HANDLE_FILLCOLOR
+	 * 
+	 * Defines the color to be used for the outline sizer fill color. Use
+	 * 'none' for no color. Default is #00FFFF.
+	 */
+	OUTLINE_HANDLE_FILLCOLOR: '#00FFFF',
+
+	/**
+	 * Variable: OUTLINE_HANDLE_STROKECOLOR
+	 * 
+	 * Defines the color to be used for the outline sizer stroke color. Use
+	 * 'none' for no color. Default is #0033FF.
+	 */
+	OUTLINE_HANDLE_STROKECOLOR: '#0033FF',
+
+	/**
+	 * Variable: DEFAULT_FONTFAMILY
+	 * 
+	 * Defines the default family for all fonts. Default is Arial,Helvetica.
+	 */
+	DEFAULT_FONTFAMILY: 'Arial,Helvetica',
+
+	/**
+	 * Variable: DEFAULT_FONTSIZE
+	 * 
+	 * Defines the default size (in px). Default is 11.
+	 */
+	DEFAULT_FONTSIZE: 11,
+
+	/**
+	 * Variable: DEFAULT_TEXT_DIRECTION
+	 * 
+	 * Defines the default value for the <STYLE_TEXT_DIRECTION> if no value is
+	 * defined for it in the style. Default value is an empty string which means
+	 * the default system setting is used and no direction is set.
+	 */
+	DEFAULT_TEXT_DIRECTION: '',
+
+	/**
+	 * Variable: LINE_HEIGHT
+	 * 
+	 * Defines the default line height for text labels. Default is 1.2.
+	 */
+	LINE_HEIGHT: 1.2,
+
+	/**
+	 * Variable: WORD_WRAP
+	 * 
+	 * Defines the CSS value for the word-wrap property. Default is "normal".
+	 * Change this to "break-word" to allow long words to be able to be broken
+	 * and wrap onto the next line.
+	 */
+	WORD_WRAP: 'normal',
+
+	/**
+	 * Variable: ABSOLUTE_LINE_HEIGHT
+	 * 
+	 * Specifies if absolute line heights should be used (px) in CSS. Default
+	 * is false. Set this to true for backwards compatibility.
+	 */
+	ABSOLUTE_LINE_HEIGHT: false,
+
+	/**
+	 * Variable: DEFAULT_FONTSTYLE
+	 * 
+	 * Defines the default style for all fonts. Default is 0. This can be set
+	 * to any combination of font styles as follows.
+	 * 
+	 * (code)
+	 * mxConstants.DEFAULT_FONTSTYLE = mxConstants.FONT_BOLD | mxConstants.FONT_ITALIC;
+	 * (end)
+	 */
+	DEFAULT_FONTSTYLE: 0,
+
+	/**
+	 * Variable: DEFAULT_STARTSIZE
+	 * 
+	 * Defines the default start size for swimlanes. Default is 40.
+	 */
+	DEFAULT_STARTSIZE: 40,
+
+	/**
+	 * Variable: DEFAULT_MARKERSIZE
+	 * 
+	 * Defines the default size for all markers. Default is 6.
+	 */
+	DEFAULT_MARKERSIZE: 6,
+
+	/**
+	 * Variable: DEFAULT_IMAGESIZE
+	 * 
+	 * Defines the default width and height for images used in the
+	 * label shape. Default is 24.
+	 */
+	DEFAULT_IMAGESIZE: 24,
+
+	/**
+	 * Variable: ENTITY_SEGMENT
+	 * 
+	 * Defines the length of the horizontal segment of an Entity Relation.
+	 * This can be overridden using <mxConstants.STYLE_SEGMENT> style.
+	 * Default is 30.
+	 */
+	ENTITY_SEGMENT: 30,
+
+	/**
+	 * Variable: RECTANGLE_ROUNDING_FACTOR
+	 * 
+	 * Defines the rounding factor for rounded rectangles in percent between
+	 * 0 and 1. Values should be smaller than 0.5. Default is 0.15.
+	 */
+	RECTANGLE_ROUNDING_FACTOR: 0.15,
+
+	/**
+	 * Variable: LINE_ARCSIZE
+	 * 
+	 * Defines the size of the arcs for rounded edges. Default is 20.
+	 */
+	LINE_ARCSIZE: 20,
+
+	/**
+	 * Variable: ARROW_SPACING
+	 * 
+	 * Defines the spacing between the arrow shape and its terminals. Default is 0.
+	 */
+	ARROW_SPACING: 0,
+
+	/**
+	 * Variable: ARROW_WIDTH
+	 * 
+	 * Defines the width of the arrow shape. Default is 30.
+	 */
+	ARROW_WIDTH: 30,
+
+	/**
+	 * Variable: ARROW_SIZE
+	 * 
+	 * Defines the size of the arrowhead in the arrow shape. Default is 30.
+	 */
+	ARROW_SIZE: 30,
+
+	/**
+	 * Variable: PAGE_FORMAT_A4_PORTRAIT
+	 * 
+	 * Defines the rectangle for the A4 portrait page format. The dimensions
+	 * of this page format are 826x1169 pixels.
+	 */
+	PAGE_FORMAT_A4_PORTRAIT: new mxRectangle(0, 0, 827, 1169),
+
+	/**
+	 * Variable: PAGE_FORMAT_A4_PORTRAIT
+	 * 
+	 * Defines the rectangle for the A4 portrait page format. The dimensions
+	 * of this page format are 826x1169 pixels.
+	 */
+	PAGE_FORMAT_A4_LANDSCAPE: new mxRectangle(0, 0, 1169, 827),
+
+	/**
+	 * Variable: PAGE_FORMAT_LETTER_PORTRAIT
+	 * 
+	 * Defines the rectangle for the Letter portrait page format. The
+	 * dimensions of this page format are 850x1100 pixels.
+	 */
+	PAGE_FORMAT_LETTER_PORTRAIT: new mxRectangle(0, 0, 850, 1100),
+
+	/**
+	 * Variable: PAGE_FORMAT_LETTER_PORTRAIT
+	 * 
+	 * Defines the rectangle for the Letter portrait page format. The dimensions
+	 * of this page format are 850x1100 pixels.
+	 */
+	PAGE_FORMAT_LETTER_LANDSCAPE: new mxRectangle(0, 0, 1100, 850),
+
+	/**
+	 * Variable: NONE
+	 * 
+	 * Defines the value for none. Default is "none".
+	 */
+	NONE: 'none',
+
+	/**
+	 * Variable: STYLE_PERIMETER
+	 * 
+	 * Defines the key for the perimeter style. This is a function that defines
+	 * the perimeter around a particular shape. Possible values are the
+	 * functions defined in <mxPerimeter>. Alternatively, the constants in this
+	 * class that start with "PERIMETER_" may be used to access
+	 * perimeter styles in <mxStyleRegistry>. Value is "perimeter".
+	 */
+	STYLE_PERIMETER: 'perimeter',
+	
+	/**
+	 * Variable: STYLE_SOURCE_PORT
+	 * 
+	 * Defines the ID of the cell that should be used for computing the
+	 * perimeter point of the source for an edge. This allows for graphically
+	 * connecting to a cell while keeping the actual terminal of the edge.
+	 * Value is "sourcePort".
+	 */
+	STYLE_SOURCE_PORT: 'sourcePort',
+	
+	/**
+	 * Variable: STYLE_TARGET_PORT
+	 * 
+	 * Defines the ID of the cell that should be used for computing the
+	 * perimeter point of the target for an edge. This allows for graphically
+	 * connecting to a cell while keeping the actual terminal of the edge.
+	 * Value is "targetPort".
+	 */
+	STYLE_TARGET_PORT: 'targetPort',
+
+	/**
+	 * Variable: STYLE_PORT_CONSTRAINT
+	 * 
+	 * Defines the direction(s) that edges are allowed to connect to cells in.
+	 * Possible values are "DIRECTION_NORTH, DIRECTION_SOUTH, 
+	 * DIRECTION_EAST" and "DIRECTION_WEST". Value is
+	 * "portConstraint".
+	 */
+	STYLE_PORT_CONSTRAINT: 'portConstraint',
+
+	/**
+	 * Variable: STYLE_PORT_CONSTRAINT_ROTATION
+	 * 
+	 * Define whether port constraint directions are rotated with vertex
+	 * rotation. 0 (default) causes port constraints to remain absolute, 
+	 * relative to the graph, 1 causes the constraints to rotate with
+	 * the vertex. Value is "portConstraintRotation".
+	 */
+	STYLE_PORT_CONSTRAINT_ROTATION: 'portConstraintRotation',
+
+	/**
+	 * Variable: STYLE_SOURCE_PORT_CONSTRAINT
+	 * 
+	 * Defines the direction(s) that edges are allowed to connect to sources in.
+	 * Possible values are "DIRECTION_NORTH, DIRECTION_SOUTH, DIRECTION_EAST"
+	 * and "DIRECTION_WEST". Value is "sourcePortConstraint".
+	 */
+	STYLE_SOURCE_PORT_CONSTRAINT: 'sourcePortConstraint',
+
+	/**
+	 * Variable: STYLE_TARGET_PORT_CONSTRAINT
+	 * 
+	 * Defines the direction(s) that edges are allowed to connect to targets in.
+	 * Possible values are "DIRECTION_NORTH, DIRECTION_SOUTH, DIRECTION_EAST"
+	 * and "DIRECTION_WEST". Value is "targetPortConstraint".
+	 */
+	STYLE_TARGET_PORT_CONSTRAINT: 'targetPortConstraint',
+
+	/**
+	 * Variable: STYLE_OPACITY
+	 * 
+	 * Defines the key for the opacity style. The type of the value is 
+	 * numeric and the possible range is 0-100. Value is "opacity".
+	 */
+	STYLE_OPACITY: 'opacity',
+
+	/**
+	 * Variable: STYLE_FILL_OPACITY
+	 * 
+	 * Defines the key for the fill opacity style. The type of the value is 
+	 * numeric and the possible range is 0-100. Value is "fillOpacity".
+	 */
+	STYLE_FILL_OPACITY: 'fillOpacity',
+
+	/**
+	 * Variable: STYLE_STROKE_OPACITY
+	 * 
+	 * Defines the key for the stroke opacity style. The type of the value is 
+	 * numeric and the possible range is 0-100. Value is "strokeOpacity".
+	 */
+	STYLE_STROKE_OPACITY: 'strokeOpacity',
+
+	/**
+	 * Variable: STYLE_TEXT_OPACITY
+	 * 
+	 * Defines the key for the text opacity style. The type of the value is 
+	 * numeric and the possible range is 0-100. Value is "textOpacity".
+	 */
+	STYLE_TEXT_OPACITY: 'textOpacity',
+
+	/**
+	 * Variable: STYLE_TEXT_DIRECTION
+	 * 
+	 * Defines the key for the text direction style. Possible values are
+	 * "TEXT_DIRECTION_DEFAULT, TEXT_DIRECTION_AUTO, TEXT_DIRECTION_LTR"
+	 * and "TEXT_DIRECTION_RTL". Value is "textDirection".
+	 * The default value for the style is defined in <DEFAULT_TEXT_DIRECTION>.
+	 * It is used is no value is defined for this key in a given style. This is
+	 * an experimental style that is currently ignored in the backends.
+	 */
+	STYLE_TEXT_DIRECTION: 'textDirection',
+
+	/**
+	 * Variable: STYLE_OVERFLOW
+	 * 
+	 * Defines the key for the overflow style. Possible values are 'visible',
+	 * 'hidden', 'fill' and 'width'. The default value is 'visible'. This value
+	 * specifies how overlapping vertex labels are handled. A value of
+	 * 'visible' will show the complete label. A value of 'hidden' will clip
+	 * the label so that it does not overlap the vertex bounds. A value of
+	 * 'fill' will use the vertex bounds and a value of 'width' will use the
+	 * the vertex width for the label. See <mxGraph.isLabelClipped>. Note that
+	 * the vertical alignment is ignored for overflow fill and for horizontal
+	 * alignment, left should be used to avoid pixel offsets in Internet Explorer
+	 * 11 and earlier or if foreignObjects are disabled. Value is "overflow".
+	 */
+	STYLE_OVERFLOW: 'overflow',
+
+	/**
+	 * Variable: STYLE_ORTHOGONAL
+	 * 
+	 * Defines if the connection points on either end of the edge should be
+	 * computed so that the edge is vertical or horizontal if possible and
+	 * if the point is not at a fixed location. Default is false. This is
+	 * used in <mxGraph.isOrthogonal>, which also returns true if the edgeStyle
+	 * of the edge is an elbow or entity. Value is "orthogonal".
+	 */
+	STYLE_ORTHOGONAL: 'orthogonal',
+
+	/**
+	 * Variable: STYLE_EXIT_X
+	 * 
+	 * Defines the key for the horizontal relative coordinate connection point
+	 * of an edge with its source terminal. Value is "exitX".
+	 */
+	STYLE_EXIT_X: 'exitX',
+
+	/**
+	 * Variable: STYLE_EXIT_Y
+	 * 
+	 * Defines the key for the vertical relative coordinate connection point
+	 * of an edge with its source terminal. Value is "exitY".
+	 */
+	STYLE_EXIT_Y: 'exitY',
+
+	/**
+	 * Variable: STYLE_EXIT_PERIMETER
+	 * 
+	 * Defines if the perimeter should be used to find the exact entry point
+	 * along the perimeter of the source. Possible values are 0 (false) and
+	 * 1 (true). Default is 1 (true). Value is "exitPerimeter".
+	 */
+	STYLE_EXIT_PERIMETER: 'exitPerimeter',
+
+	/**
+	 * Variable: STYLE_ENTRY_X
+	 * 
+	 * Defines the key for the horizontal relative coordinate connection point
+	 * of an edge with its target terminal. Value is "entryX".
+	 */
+	STYLE_ENTRY_X: 'entryX',
+
+	/**
+	 * Variable: STYLE_ENTRY_Y
+	 * 
+	 * Defines the key for the vertical relative coordinate connection point
+	 * of an edge with its target terminal. Value is "entryY".
+	 */
+	STYLE_ENTRY_Y: 'entryY',
+
+	/**
+	 * Variable: STYLE_ENTRY_PERIMETER
+	 * 
+	 * Defines if the perimeter should be used to find the exact entry point
+	 * along the perimeter of the target. Possible values are 0 (false) and
+	 * 1 (true). Default is 1 (true). Value is "entryPerimeter".
+	 */
+	STYLE_ENTRY_PERIMETER: 'entryPerimeter',
+
+	/**
+	 * Variable: STYLE_WHITE_SPACE
+	 * 
+	 * Defines the key for the white-space style. Possible values are 'nowrap'
+	 * and 'wrap'. The default value is 'nowrap'. This value specifies how
+	 * white-space inside a HTML vertex label should be handled. A value of
+	 * 'nowrap' means the text will never wrap to the next line until a
+	 * linefeed is encountered. A value of 'wrap' means text will wrap when
+	 * necessary. This style is only used for HTML labels.
+	 * See <mxGraph.isWrapping>. Value is "whiteSpace".
+	 */
+	STYLE_WHITE_SPACE: 'whiteSpace',
+
+	/**
+	 * Variable: STYLE_ROTATION
+	 * 
+	 * Defines the key for the rotation style. The type of the value is 
+	 * numeric and the possible range is 0-360. Value is "rotation".
+	 */
+	STYLE_ROTATION: 'rotation',
+
+	/**
+	 * Variable: STYLE_FILLCOLOR
+	 * 
+	 * Defines the key for the fill color. Possible values are all HTML color
+	 * names or HEX codes, as well as special keywords such as 'swimlane,
+	 * 'inherit' or 'indicated' to use the color code of a related cell or the
+	 * indicator shape. Value is "fillColor".
+	 */
+	STYLE_FILLCOLOR: 'fillColor',
+
+	/**
+	 * Variable: STYLE_POINTER_EVENTS
+	 * 
+	 * Specifies if pointer events should be fired on transparent backgrounds.
+	 * This style is currently only supported in <mxRectangleShape>. Default
+	 * is true. Value is "pointerEvents". This is typically set to
+	 * false in groups where the transparent part should allow any underlying
+	 * cells to be clickable.
+	 */
+	STYLE_POINTER_EVENTS: 'pointerEvents',
+
+	/**
+	 * Variable: STYLE_SWIMLANE_FILLCOLOR
+	 * 
+	 * Defines the key for the fill color of the swimlane background. Possible
+	 * values are all HTML color names or HEX codes. Default is no background.
+	 * Value is "swimlaneFillColor".
+	 */
+	STYLE_SWIMLANE_FILLCOLOR: 'swimlaneFillColor',
+
+	/**
+	 * Variable: STYLE_MARGIN
+	 * 
+	 * Defines the key for the margin between the ellipses in the double ellipse shape.
+	 * Possible values are all positive numbers. Value is "margin".
+	 */
+	STYLE_MARGIN: 'margin',
+
+	/**
+	 * Variable: STYLE_GRADIENTCOLOR
+	 * 
+	 * Defines the key for the gradient color. Possible values are all HTML color
+	 * names or HEX codes, as well as special keywords such as 'swimlane,
+	 * 'inherit' or 'indicated' to use the color code of a related cell or the
+	 * indicator shape. This is ignored if no fill color is defined. Value is
+	 * "gradientColor".
+	 */
+	STYLE_GRADIENTCOLOR: 'gradientColor',
+
+	/**
+	 * Variable: STYLE_GRADIENT_DIRECTION
+	 * 
+	 * Defines the key for the gradient direction. Possible values are
+	 * <DIRECTION_EAST>, <DIRECTION_WEST>, <DIRECTION_NORTH> and
+	 * <DIRECTION_SOUTH>. Default is <DIRECTION_SOUTH>. Generally, and by
+	 * default in mxGraph, gradient painting is done from the value of
+	 * <STYLE_FILLCOLOR> to the value of <STYLE_GRADIENTCOLOR>. Taking the
+	 * example of <DIRECTION_NORTH>, this means <STYLE_FILLCOLOR> color at the 
+	 * bottom of paint pattern and <STYLE_GRADIENTCOLOR> at top, with a
+	 * gradient in-between. Value is "gradientDirection".
+	 */
+	STYLE_GRADIENT_DIRECTION: 'gradientDirection',
+
+	/**
+	 * Variable: STYLE_STROKECOLOR
+	 * 
+	 * Defines the key for the strokeColor style. Possible values are all HTML
+	 * color names or HEX codes, as well as special keywords such as 'swimlane,
+	 * 'inherit', 'indicated' to use the color code of a related cell or the
+	 * indicator shape or 'none' for no color. Value is "strokeColor".
+	 */
+	STYLE_STROKECOLOR: 'strokeColor',
+
+	/**
+	 * Variable: STYLE_SEPARATORCOLOR
+	 * 
+	 * Defines the key for the separatorColor style. Possible values are all
+	 * HTML color names or HEX codes. This style is only used for
+	 * <SHAPE_SWIMLANE> shapes. Value is "separatorColor".
+	 */
+	STYLE_SEPARATORCOLOR: 'separatorColor',
+
+	/**
+	 * Variable: STYLE_STROKEWIDTH
+	 * 
+	 * Defines the key for the strokeWidth style. The type of the value is 
+	 * numeric and the possible range is any non-negative value larger or equal
+	 * to 1. The value defines the stroke width in pixels. Note: To hide a
+	 * stroke use strokeColor none. Value is "strokeWidth".
+	 */
+	STYLE_STROKEWIDTH: 'strokeWidth',
+
+	/**
+	 * Variable: STYLE_ALIGN
+	 * 
+	 * Defines the key for the align style. Possible values are <ALIGN_LEFT>,
+	 * <ALIGN_CENTER> and <ALIGN_RIGHT>. This value defines how the lines of
+	 * the label are horizontally aligned. <ALIGN_LEFT> mean label text lines
+	 * are aligned to left of the label bounds, <ALIGN_RIGHT> to the right of
+	 * the label bounds and <ALIGN_CENTER> means the center of the text lines
+	 * are aligned in the center of the label bounds. Note this value doesn't
+	 * affect the positioning of the overall label bounds relative to the
+	 * vertex, to move the label bounds horizontally, use
+	 * <STYLE_LABEL_POSITION>. Value is "align".
+	 */
+	STYLE_ALIGN: 'align',
+
+	/**
+	 * Variable: STYLE_VERTICAL_ALIGN
+	 * 
+	 * Defines the key for the verticalAlign style. Possible values are
+	 * <ALIGN_TOP>, <ALIGN_MIDDLE> and <ALIGN_BOTTOM>. This value defines how
+	 * the lines of the label are vertically aligned. <ALIGN_TOP> means the
+	 * topmost label text line is aligned against the top of the label bounds,
+	 * <ALIGN_BOTTOM> means the bottom-most label text line is aligned against
+	 * the bottom of the label bounds and <ALIGN_MIDDLE> means there is equal
+	 * spacing between the topmost text label line and the top of the label
+	 * bounds and the bottom-most text label line and the bottom of the label
+	 * bounds. Note this value doesn't affect the positioning of the overall
+	 * label bounds relative to the vertex, to move the label bounds
+	 * vertically, use <STYLE_VERTICAL_LABEL_POSITION>. Value is "verticalAlign".
+	 */
+	STYLE_VERTICAL_ALIGN: 'verticalAlign',
+
+	/**
+	 * Variable: STYLE_LABEL_WIDTH
+	 * 
+	 * Defines the key for the width of the label if the label position is not
+	 * center. Value is "labelWidth".
+	 */
+	STYLE_LABEL_WIDTH: 'labelWidth',
+
+	/**
+	 * Variable: STYLE_LABEL_POSITION
+	 * 
+	 * Defines the key for the horizontal label position of vertices. Possible
+	 * values are <ALIGN_LEFT>, <ALIGN_CENTER> and <ALIGN_RIGHT>. Default is
+	 * <ALIGN_CENTER>. The label align defines the position of the label
+	 * relative to the cell. <ALIGN_LEFT> means the entire label bounds is
+	 * placed completely just to the left of the vertex, <ALIGN_RIGHT> means
+	 * adjust to the right and <ALIGN_CENTER> means the label bounds are
+	 * vertically aligned with the bounds of the vertex. Note this value
+	 * doesn't affect the positioning of label within the label bounds, to move
+	 * the label horizontally within the label bounds, use <STYLE_ALIGN>.
+	 * Value is "labelPosition".
+	 */
+	STYLE_LABEL_POSITION: 'labelPosition',
+
+	/**
+	 * Variable: STYLE_VERTICAL_LABEL_POSITION
+	 * 
+	 * Defines the key for the vertical label position of vertices. Possible
+	 * values are <ALIGN_TOP>, <ALIGN_BOTTOM> and <ALIGN_MIDDLE>. Default is
+	 * <ALIGN_MIDDLE>. The label align defines the position of the label
+	 * relative to the cell. <ALIGN_TOP> means the entire label bounds is
+	 * placed completely just on the top of the vertex, <ALIGN_BOTTOM> means
+	 * adjust on the bottom and <ALIGN_MIDDLE> means the label bounds are
+	 * horizontally aligned with the bounds of the vertex. Note this value
+	 * doesn't affect the positioning of label within the label bounds, to move
+	 * the label vertically within the label bounds, use
+	 * <STYLE_VERTICAL_ALIGN>. Value is "verticalLabelPosition".
+	 */
+	STYLE_VERTICAL_LABEL_POSITION: 'verticalLabelPosition',
+	
+	/**
+	 * Variable: STYLE_IMAGE_ASPECT
+	 * 
+	 * Defines the key for the image aspect style. Possible values are 0 (do
+	 * not preserve aspect) or 1 (keep aspect). This is only used in
+	 * <mxImageShape>. Default is 1. Value is "imageAspect".
+	 */
+	STYLE_IMAGE_ASPECT: 'imageAspect',
+
+	/**
+	 * Variable: STYLE_IMAGE_ALIGN
+	 * 
+	 * Defines the key for the align style. Possible values are <ALIGN_LEFT>,
+	 * <ALIGN_CENTER> and <ALIGN_RIGHT>. The value defines how any image in the
+	 * vertex label is aligned horizontally within the label bounds of a
+	 * <SHAPE_LABEL> shape. Value is "imageAlign".
+	 */
+	STYLE_IMAGE_ALIGN: 'imageAlign',
+
+	/**
+	 * Variable: STYLE_IMAGE_VERTICAL_ALIGN
+	 * 
+	 * Defines the key for the verticalAlign style. Possible values are
+	 * <ALIGN_TOP>, <ALIGN_MIDDLE> and <ALIGN_BOTTOM>. The value defines how
+	 * any image in the vertex label is aligned vertically within the label
+	 * bounds of a <SHAPE_LABEL> shape. Value is "imageVerticalAlign".
+	 */
+	STYLE_IMAGE_VERTICAL_ALIGN: 'imageVerticalAlign',
+
+	/**
+	 * Variable: STYLE_GLASS
+	 * 
+	 * Defines the key for the glass style. Possible values are 0 (disabled) and
+	 * 1(enabled). The default value is 0. This is used in <mxLabel>. Value is
+	 * "glass".
+	 */
+	STYLE_GLASS: 'glass',
+
+	/**
+	 * Variable: STYLE_IMAGE
+	 * 
+	 * Defines the key for the image style. Possible values are any image URL,
+	 * the type of the value is String. This is the path to the image that is
+	 * to be displayed within the label of a vertex. Data URLs should use the
+	 * following format: data:image/png,xyz where xyz is the base64 encoded
+	 * data (without the "base64"-prefix). Note that Data URLs are only
+	 * supported in modern browsers. Value is "image".
+	 */
+	STYLE_IMAGE: 'image',
+
+	/**
+	 * Variable: STYLE_IMAGE_WIDTH
+	 * 
+	 * Defines the key for the imageWidth style. The type of this value is
+	 * int, the value is the image width in pixels and must be greater than 0.
+	 * Value is "imageWidth".
+	 */
+	STYLE_IMAGE_WIDTH: 'imageWidth',
+
+	/**
+	 * Variable: STYLE_IMAGE_HEIGHT
+	 * 
+	 * Defines the key for the imageHeight style. The type of this value is
+	 * int, the value is the image height in pixels and must be greater than 0.
+	 * Value is "imageHeight".
+	 */
+	STYLE_IMAGE_HEIGHT: 'imageHeight',
+
+	/**
+	 * Variable: STYLE_IMAGE_BACKGROUND
+	 * 
+	 * Defines the key for the image background color. This style is only used
+	 * in <mxImageShape>. Possible values are all HTML color names or HEX
+	 * codes. Value is "imageBackground".
+	 */
+	STYLE_IMAGE_BACKGROUND: 'imageBackground',
+
+	/**
+	 * Variable: STYLE_IMAGE_BORDER
+	 * 
+	 * Defines the key for the image border color. This style is only used in
+	 * <mxImageShape>. Possible values are all HTML color names or HEX codes.
+	 * Value is "imageBorder".
+	 */
+	STYLE_IMAGE_BORDER: 'imageBorder',
+
+	/**
+	 * Variable: STYLE_FLIPH
+	 * 
+	 * Defines the key for the horizontal image flip. This style is only used
+	 * in <mxImageShape>. Possible values are 0 and 1. Default is 0. Value is
+	 * "flipH".
+	 */
+	STYLE_FLIPH: 'flipH',
+
+	/**
+	 * Variable: STYLE_FLIPV
+	 * 
+	 * Defines the key for the vertical flip. Possible values are 0 and 1.
+	 * Default is 0. Value is "flipV".
+	 */
+	STYLE_FLIPV: 'flipV',
+
+	/**
+	 * Variable: STYLE_NOLABEL
+	 * 
+	 * Defines the key for the noLabel style. If this is true then no label is
+	 * visible for a given cell. Possible values are true or false (1 or 0).
+	 * Default is false. Value is "noLabel".
+	 */
+	STYLE_NOLABEL: 'noLabel',
+
+	/**
+	 * Variable: STYLE_NOEDGESTYLE
+	 * 
+	 * Defines the key for the noEdgeStyle style. If this is true then no edge
+	 * style is applied for a given edge. Possible values are true or false
+	 * (1 or 0). Default is false. Value is "noEdgeStyle".
+	 */
+	STYLE_NOEDGESTYLE: 'noEdgeStyle',
+
+	/**
+	 * Variable: STYLE_LABEL_BACKGROUNDCOLOR
+	 * 
+	 * Defines the key for the label background color. Possible values are all
+	 * HTML color names or HEX codes. Value is "labelBackgroundColor".
+	 */
+	STYLE_LABEL_BACKGROUNDCOLOR: 'labelBackgroundColor',
+
+	/**
+	 * Variable: STYLE_LABEL_BORDERCOLOR
+	 * 
+	 * Defines the key for the label border color. Possible values are all
+	 * HTML color names or HEX codes. Value is "labelBorderColor".
+	 */
+	STYLE_LABEL_BORDERCOLOR: 'labelBorderColor',
+
+	/**
+	 * Variable: STYLE_LABEL_PADDING
+	 * 
+	 * Defines the key for the label padding, ie. the space between the label
+	 * border and the label. Value is "labelPadding".
+	 */
+	STYLE_LABEL_PADDING: 'labelPadding',
+
+	/**
+	 * Variable: STYLE_INDICATOR_SHAPE
+	 * 
+	 * Defines the key for the indicator shape used within an <mxLabel>.
+	 * Possible values are all SHAPE_* constants or the names of any new
+	 * shapes. The indicatorShape has precedence over the indicatorImage.
+	 * Value is "indicatorShape".
+	 */
+	STYLE_INDICATOR_SHAPE: 'indicatorShape',
+
+	/**
+	 * Variable: STYLE_INDICATOR_IMAGE
+	 * 
+	 * Defines the key for the indicator image used within an <mxLabel>.
+	 * Possible values are all image URLs. The indicatorShape has
+	 * precedence over the indicatorImage. Value is "indicatorImage".
+	 */
+	STYLE_INDICATOR_IMAGE: 'indicatorImage',
+
+	/**
+	 * Variable: STYLE_INDICATOR_COLOR
+	 * 
+	 * Defines the key for the indicatorColor style. Possible values are all
+	 * HTML color names or HEX codes, as well as the special 'swimlane' keyword
+	 * to refer to the color of the parent swimlane if one exists. Value is
+	 * "indicatorColor".
+	 */
+	STYLE_INDICATOR_COLOR: 'indicatorColor',
+
+	/**
+	 * Variable: STYLE_INDICATOR_STROKECOLOR
+	 * 
+	 * Defines the key for the indicator stroke color in <mxLabel>.
+	 * Possible values are all color codes. Value is "indicatorStrokeColor".
+	 */
+	STYLE_INDICATOR_STROKECOLOR: 'indicatorStrokeColor',
+
+	/**
+	 * Variable: STYLE_INDICATOR_GRADIENTCOLOR
+	 * 
+	 * Defines the key for the indicatorGradientColor style. Possible values
+	 * are all HTML color names or HEX codes. This style is only supported in
+	 * <SHAPE_LABEL> shapes. Value is "indicatorGradientColor".
+	 */
+	STYLE_INDICATOR_GRADIENTCOLOR: 'indicatorGradientColor',
+
+	/**
+	 * Variable: STYLE_INDICATOR_SPACING
+	 * 
+	 * The defines the key for the spacing between the label and the
+	 * indicator in <mxLabel>. Possible values are in pixels. Value is
+	 * "indicatorSpacing".
+	 */
+	STYLE_INDICATOR_SPACING: 'indicatorSpacing',
+
+	/**
+	 * Variable: STYLE_INDICATOR_WIDTH
+	 * 
+	 * Defines the key for the indicator width. Possible values start at 0 (in
+	 * pixels). Value is "indicatorWidth".
+	 */
+	STYLE_INDICATOR_WIDTH: 'indicatorWidth',
+
+	/**
+	 * Variable: STYLE_INDICATOR_HEIGHT
+	 * 
+	 * Defines the key for the indicator height. Possible values start at 0 (in
+	 * pixels). Value is "indicatorHeight".
+	 */
+	STYLE_INDICATOR_HEIGHT: 'indicatorHeight',
+
+	/**
+	 * Variable: STYLE_INDICATOR_DIRECTION
+	 * 
+	 * Defines the key for the indicatorDirection style. The direction style is
+	 * used to specify the direction of certain shapes (eg. <mxTriangle>).
+	 * Possible values are <DIRECTION_EAST> (default), <DIRECTION_WEST>,
+	 * <DIRECTION_NORTH> and <DIRECTION_SOUTH>. Value is "indicatorDirection".
+	 */
+	STYLE_INDICATOR_DIRECTION: 'indicatorDirection',
+
+	/**
+	 * Variable: STYLE_SHADOW
+	 * 
+	 * Defines the key for the shadow style. The type of the value is Boolean.
+	 * Value is "shadow".
+	 */
+	STYLE_SHADOW: 'shadow',
+	
+	/**
+	 * Variable: STYLE_SEGMENT
+	 * 
+	 * Defines the key for the segment style. The type of this value is float
+	 * and the value represents the size of the horizontal segment of the
+	 * entity relation style. Default is ENTITY_SEGMENT. Value is "segment".
+	 */
+	STYLE_SEGMENT: 'segment',
+	
+	/**
+	 * Variable: STYLE_ENDARROW
+	 *
+	 * Defines the key for the end arrow marker. Possible values are all
+	 * constants with an ARROW-prefix. This is only used in <mxConnector>.
+	 * Value is "endArrow".
+	 *
+	 * Example:
+	 * (code)
+	 * style[mxConstants.STYLE_ENDARROW] = mxConstants.ARROW_CLASSIC;
+	 * (end)
+	 */
+	STYLE_ENDARROW: 'endArrow',
+
+	/**
+	 * Variable: STYLE_STARTARROW
+	 * 
+	 * Defines the key for the start arrow marker. Possible values are all
+	 * constants with an ARROW-prefix. This is only used in <mxConnector>.
+	 * See <STYLE_ENDARROW>. Value is "startArrow".
+	 */
+	STYLE_STARTARROW: 'startArrow',
+
+	/**
+	 * Variable: STYLE_ENDSIZE
+	 * 
+	 * Defines the key for the endSize style. The type of this value is numeric
+	 * and the value represents the size of the end marker in pixels. Value is
+	 * "endSize".
+	 */
+	STYLE_ENDSIZE: 'endSize',
+
+	/**
+	 * Variable: STYLE_STARTSIZE
+	 * 
+	 * Defines the key for the startSize style. The type of this value is
+	 * numeric and the value represents the size of the start marker or the
+	 * size of the swimlane title region depending on the shape it is used for.
+	 * Value is "startSize".
+	 */
+	STYLE_STARTSIZE: 'startSize',
+
+	/**
+	 * Variable: STYLE_SWIMLANE_LINE
+	 * 
+	 * Defines the key for the swimlaneLine style. This style specifies whether
+	 * the line between the title regio of a swimlane should be visible. Use 0
+	 * for hidden or 1 (default) for visible. Value is "swimlaneLine".
+	 */
+	STYLE_SWIMLANE_LINE: 'swimlaneLine',
+
+	/**
+	 * Variable: STYLE_ENDFILL
+	 * 
+	 * Defines the key for the endFill style. Use 0 for no fill or 1 (default)
+	 * for fill. (This style is only exported via <mxImageExport>.) Value is
+	 * "endFill".
+	 */
+	STYLE_ENDFILL: 'endFill',
+
+	/**
+	 * Variable: STYLE_STARTFILL
+	 * 
+	 * Defines the key for the startFill style. Use 0 for no fill or 1 (default)
+	 * for fill. (This style is only exported via <mxImageExport>.) Value is
+	 * "startFill".
+	 */
+	STYLE_STARTFILL: 'startFill',
+
+	/**
+	 * Variable: STYLE_DASHED
+	 * 
+	 * Defines the key for the dashed style. Use 0 (default) for non-dashed or 1
+	 * for dashed. Value is "dashed".
+	 */
+	STYLE_DASHED: 'dashed',
+
+	/**
+	 * Defines the key for the dashed pattern style in SVG and image exports.
+	 * The type of this value is a space separated list of numbers that specify
+	 * a custom-defined dash pattern. Dash styles are defined in terms of the
+	 * length of the dash (the drawn part of the stroke) and the length of the
+	 * space between the dashes. The lengths are relative to the line width: a
+	 * length of "1" is equal to the line width. VML ignores this style and
+	 * uses dashStyle instead as defined in the VML specification. This style
+	 * is only used in the <mxConnector> shape. Value is "dashPattern".
+	 */
+	STYLE_DASH_PATTERN: 'dashPattern',
+
+	/**
+	 * Variable: STYLE_FIX_DASH
+	 * 
+	 * Defines the key for the fixDash style. Use 0 (default) for dash patterns
+	 * that depend on the linewidth and 1 for dash patterns that ignore the
+	 * line width. Value is "fixDash".
+	 */
+	STYLE_FIX_DASH: 'fixDash',
+
+	/**
+	 * Variable: STYLE_ROUNDED
+	 * 
+	 * Defines the key for the rounded style. The type of this value is
+	 * Boolean. For edges this determines whether or not joins between edges
+	 * segments are smoothed to a rounded finish. For vertices that have the
+	 * rectangle shape, this determines whether or not the rectangle is
+	 * rounded. Use 0 (default) for non-rounded or 1 for rounded. Value is
+	 * "rounded".
+	 */
+	STYLE_ROUNDED: 'rounded',
+
+	/**
+	 * Variable: STYLE_CURVED
+	 * 
+	 * Defines the key for the curved style. The type of this value is
+	 * Boolean. It is only applicable for connector shapes. Use 0 (default)
+	 * for non-curved or 1 for curved. Value is "curved".
+	 */
+	STYLE_CURVED: 'curved',
+
+	/**
+	 * Variable: STYLE_ARCSIZE
+	 * 
+	 * Defines the rounding factor for a rounded rectangle in percent (without
+	 * the percent sign). Possible values are between 0 and 100. If this value
+	 * is not specified then RECTANGLE_ROUNDING_FACTOR * 100 is used. For
+	 * edges, this defines the absolute size of rounded corners in pixels. If
+	 * this values is not specified then LINE_ARCSIZE is used.
+	 * (This style is only exported via <mxImageExport>.) Value is "arcSize".
+	 */
+	STYLE_ARCSIZE: 'arcSize',
+
+	/**
+	 * Variable: STYLE_ABSOLUTE_ARCSIZE
+	 * 
+	 * Defines the key for the absolute arc size style. This specifies if
+	 * arcSize for rectangles is abolute or relative. Possible values are 1
+	 * and 0 (default). Value is "absoluteArcSize".
+	 */
+	STYLE_ABSOLUTE_ARCSIZE: 'absoluteArcSize',
+
+	/**
+	 * Variable: STYLE_SOURCE_PERIMETER_SPACING
+	 * 
+	 * Defines the key for the source perimeter spacing. The type of this value
+	 * is numeric. This is the distance between the source connection point of
+	 * an edge and the perimeter of the source vertex in pixels. This style
+	 * only applies to edges. Value is "sourcePerimeterSpacing".
+	 */
+	STYLE_SOURCE_PERIMETER_SPACING: 'sourcePerimeterSpacing',
+
+	/**
+	 * Variable: STYLE_TARGET_PERIMETER_SPACING
+	 * 
+	 * Defines the key for the target perimeter spacing. The type of this value
+	 * is numeric. This is the distance between the target connection point of
+	 * an edge and the perimeter of the target vertex in pixels. This style
+	 * only applies to edges. Value is "targetPerimeterSpacing".
+	 */
+	STYLE_TARGET_PERIMETER_SPACING: 'targetPerimeterSpacing',
+
+	/**
+	 * Variable: STYLE_PERIMETER_SPACING
+	 * 
+	 * Defines the key for the perimeter spacing. This is the distance between
+	 * the connection point and the perimeter in pixels. When used in a vertex
+	 * style, this applies to all incoming edges to floating ports (edges that
+	 * terminate on the perimeter of the vertex). When used in an edge style,
+	 * this spacing applies to the source and target separately, if they
+	 * terminate in floating ports (on the perimeter of the vertex). Value is
+	 * "perimeterSpacing".
+	 */
+	STYLE_PERIMETER_SPACING: 'perimeterSpacing',
+
+	/**
+	 * Variable: STYLE_SPACING
+	 * 
+	 * Defines the key for the spacing. The value represents the spacing, in
+	 * pixels, added to each side of a label in a vertex (style applies to
+	 * vertices only). Value is "spacing".
+	 */
+	STYLE_SPACING: 'spacing',
+
+	/**
+	 * Variable: STYLE_SPACING_TOP
+	 * 
+	 * Defines the key for the spacingTop style. The value represents the
+	 * spacing, in pixels, added to the top side of a label in a vertex (style
+	 * applies to vertices only). Value is "spacingTop".
+	 */
+	STYLE_SPACING_TOP: 'spacingTop',
+
+	/**
+	 * Variable: STYLE_SPACING_LEFT
+	 * 
+	 * Defines the key for the spacingLeft style. The value represents the
+	 * spacing, in pixels, added to the left side of a label in a vertex (style
+	 * applies to vertices only). Value is "spacingLeft".
+	 */
+	STYLE_SPACING_LEFT: 'spacingLeft',
+
+	/**
+	 * Variable: STYLE_SPACING_BOTTOM
+	 * 
+	 * Defines the key for the spacingBottom style The value represents the
+	 * spacing, in pixels, added to the bottom side of a label in a vertex
+	 * (style applies to vertices only). Value is "spacingBottom".
+	 */
+	STYLE_SPACING_BOTTOM: 'spacingBottom',
+
+	/**
+	 * Variable: STYLE_SPACING_RIGHT
+	 * 
+	 * Defines the key for the spacingRight style The value represents the
+	 * spacing, in pixels, added to the right side of a label in a vertex (style
+	 * applies to vertices only). Value is "spacingRight".
+	 */
+	STYLE_SPACING_RIGHT: 'spacingRight',
+
+	/**
+	 * Variable: STYLE_HORIZONTAL
+	 * 
+	 * Defines the key for the horizontal style. Possible values are
+	 * true or false. This value only applies to vertices. If the <STYLE_SHAPE>
+	 * is "SHAPE_SWIMLANE" a value of false indicates that the
+	 * swimlane should be drawn vertically, true indicates to draw it
+	 * horizontally. If the shape style does not indicate that this vertex is a
+	 * swimlane, this value affects only whether the label is drawn
+	 * horizontally or vertically. Value is "horizontal".
+	 */
+	STYLE_HORIZONTAL: 'horizontal',
+
+	/**
+	 * Variable: STYLE_DIRECTION
+	 * 
+	 * Defines the key for the direction style. The direction style is used
+	 * to specify the direction of certain shapes (eg. <mxTriangle>).
+	 * Possible values are <DIRECTION_EAST> (default), <DIRECTION_WEST>,
+	 * <DIRECTION_NORTH> and <DIRECTION_SOUTH>. Value is "direction".
+	 */
+	STYLE_DIRECTION: 'direction',
+
+	/**
+	 * Variable: STYLE_ELBOW
+	 * 
+	 * Defines the key for the elbow style. Possible values are
+	 * <ELBOW_HORIZONTAL> and <ELBOW_VERTICAL>. Default is <ELBOW_HORIZONTAL>.
+	 * This defines how the three segment orthogonal edge style leaves its
+	 * terminal vertices. The vertical style leaves the terminal vertices at
+	 * the top and bottom sides. Value is "elbow".
+	 */
+	STYLE_ELBOW: 'elbow',
+
+	/**
+	 * Variable: STYLE_FONTCOLOR
+	 * 
+	 * Defines the key for the fontColor style. Possible values are all HTML
+	 * color names or HEX codes. Value is "fontColor".
+	 */
+	STYLE_FONTCOLOR: 'fontColor',
+
+	/**
+	 * Variable: STYLE_FONTFAMILY
+	 * 
+	 * Defines the key for the fontFamily style. Possible values are names such
+	 * as Arial; Dialog; Verdana; Times New Roman. The value is of type String.
+	 * Value is fontFamily.
+	 */
+	STYLE_FONTFAMILY: 'fontFamily',
+
+	/**
+	 * Variable: STYLE_FONTSIZE
+	 * 
+	 * Defines the key for the fontSize style (in px). The type of the value
+	 * is int. Value is "fontSize".
+	 */
+	STYLE_FONTSIZE: 'fontSize',
+
+	/**
+	 * Variable: STYLE_FONTSTYLE
+	 * 
+	 * Defines the key for the fontStyle style. Values may be any logical AND
+	 * (sum) of <FONT_BOLD>, <FONT_ITALIC> and <FONT_UNDERLINE>.
+	 * The type of the value is int. Value is "fontStyle".
+	 */
+	STYLE_FONTSTYLE: 'fontStyle',
+	
+	/**
+	 * Variable: STYLE_ASPECT
+	 * 
+	 * Defines the key for the aspect style. Possible values are empty or fixed.
+	 * If fixed is used then the aspect ratio of the cell will be maintained
+	 * when resizing. Default is empty. Value is "aspect".
+	 */
+	STYLE_ASPECT: 'aspect',
+
+	/**
+	 * Variable: STYLE_AUTOSIZE
+	 * 
+	 * Defines the key for the autosize style. This specifies if a cell should be
+	 * resized automatically if the value has changed. Possible values are 0 or 1.
+	 * Default is 0. See <mxGraph.isAutoSizeCell>. This is normally combined with
+	 * <STYLE_RESIZABLE> to disable manual sizing. Value is "autosize".
+	 */
+	STYLE_AUTOSIZE: 'autosize',
+
+	/**
+	 * Variable: STYLE_FOLDABLE
+	 * 
+	 * Defines the key for the foldable style. This specifies if a cell is foldable
+	 * using a folding icon. Possible values are 0 or 1. Default is 1. See
+	 * <mxGraph.isCellFoldable>. Value is "foldable".
+	 */
+	STYLE_FOLDABLE: 'foldable',
+
+	/**
+	 * Variable: STYLE_EDITABLE
+	 * 
+	 * Defines the key for the editable style. This specifies if the value of
+	 * a cell can be edited using the in-place editor. Possible values are 0 or
+	 * 1. Default is 1. See <mxGraph.isCellEditable>. Value is "editable".
+	 */
+	STYLE_EDITABLE: 'editable',
+
+	/**
+	 * Variable: STYLE_BENDABLE
+	 * 
+	 * Defines the key for the bendable style. This specifies if the control
+	 * points of an edge can be moved. Possible values are 0 or 1. Default is
+	 * 1. See <mxGraph.isCellBendable>. Value is "bendable".
+	 */
+	STYLE_BENDABLE: 'bendable',
+
+	/**
+	 * Variable: STYLE_MOVABLE
+	 * 
+	 * Defines the key for the movable style. This specifies if a cell can
+	 * be moved. Possible values are 0 or 1. Default is 1. See
+	 * <mxGraph.isCellMovable>. Value is "movable".
+	 */
+	STYLE_MOVABLE: 'movable',
+
+	/**
+	 * Variable: STYLE_RESIZABLE
+	 * 
+	 * Defines the key for the resizable style. This specifies if a cell can
+	 * be resized. Possible values are 0 or 1. Default is 1. See
+	 * <mxGraph.isCellResizable>. Value is "resizable".
+	 */
+	STYLE_RESIZABLE: 'resizable',
+
+	/**
+	 * Variable: STYLE_RESIZE_WIDTH
+	 * 
+	 * Defines the key for the resizeWidth style. This specifies if a cell's
+	 * width is resized if the parent is resized. If this is 1 then the width
+	 * will be resized even if the cell's geometry is relative. If this is 0
+	 * then the cell's width will not be resized. Default is not defined. Value
+	 * is "resizeWidth".
+	 */
+	STYLE_RESIZE_WIDTH: 'resizeWidth',
+
+	/**
+	 * Variable: STYLE_RESIZE_WIDTH
+	 * 
+	 * Defines the key for the resizeHeight style. This specifies if a cell's
+	 * height if resize if the parent is resized. If this is 1 then the height
+	 * will be resized even if the cell's geometry is relative. If this is 0
+	 * then the cell's height will not be resized. Default is not defined. Value
+	 * is "resizeHeight".
+	 */
+	STYLE_RESIZE_HEIGHT: 'resizeHeight',
+
+	/**
+	 * Variable: STYLE_ROTATABLE
+	 * 
+	 * Defines the key for the rotatable style. This specifies if a cell can
+	 * be rotated. Possible values are 0 or 1. Default is 1. See
+	 * <mxGraph.isCellRotatable>. Value is "rotatable".
+	 */
+	STYLE_ROTATABLE: 'rotatable',
+
+	/**
+	 * Variable: STYLE_CLONEABLE
+	 * 
+	 * Defines the key for the cloneable style. This specifies if a cell can
+	 * be cloned. Possible values are 0 or 1. Default is 1. See
+	 * <mxGraph.isCellCloneable>. Value is "cloneable".
+	 */
+	STYLE_CLONEABLE: 'cloneable',
+
+	/**
+	 * Variable: STYLE_DELETABLE
+	 * 
+	 * Defines the key for the deletable style. This specifies if a cell can be
+	 * deleted. Possible values are 0 or 1. Default is 1. See
+	 * <mxGraph.isCellDeletable>. Value is "deletable".
+	 */
+	STYLE_DELETABLE: 'deletable',
+
+	/**
+	 * Variable: STYLE_SHAPE
+	 * 
+	 * Defines the key for the shape. Possible values are all constants with
+	 * a SHAPE-prefix or any newly defined shape names. Value is "shape".
+	 */
+	STYLE_SHAPE: 'shape',
+
+	/**
+	 * Variable: STYLE_EDGE
+	 * 
+	 * Defines the key for the edge style. Possible values are the functions
+	 * defined in <mxEdgeStyle>. Value is "edgeStyle".
+	 */
+	STYLE_EDGE: 'edgeStyle',
+
+	/**
+	 * Variable: STYLE_JETTY_SIZE
+	 * 
+	 * Defines the key for the jetty size in <mxEdgeStyle.OrthConnector>.
+	 * Default is 10. Possible values are all numeric values or "auto".
+	 * Value is "jettySize".
+	 */
+	STYLE_JETTY_SIZE: 'jettySize',
+
+	/**
+	 * Variable: STYLE_SOURCE_JETTY_SIZE
+	 * 
+	 * Defines the key for the jetty size in <mxEdgeStyle.OrthConnector>.
+	 * Default is 10. Possible values are numeric values or "auto". This has
+	 * precedence over <STYLE_JETTY_SIZE>. Value is "sourceJettySize".
+	 */
+	STYLE_SOURCE_JETTY_SIZE: 'sourceJettySize',
+
+	/**
+	 * Variable: targetJettySize
+	 * 
+	 * Defines the key for the jetty size in <mxEdgeStyle.OrthConnector>.
+	 * Default is 10. Possible values are numeric values or "auto". This has
+	 * precedence over <STYLE_JETTY_SIZE>. Value is "targetJettySize".
+	 */
+	STYLE_TARGET_JETTY_SIZE: 'targetJettySize',
+
+	/**
+	 * Variable: STYLE_LOOP
+	 * 
+	 * Defines the key for the loop style. Possible values are the functions
+	 * defined in <mxEdgeStyle>. Value is "loopStyle".
+	 */
+	STYLE_LOOP: 'loopStyle',
+
+	/**
+	 * Variable: STYLE_ORTHOGONAL_LOOP
+	 * 
+	 * Defines the key for the orthogonal loop style. Possible values are 0 and
+	 * 1. Default is 0. Value is "orthogonalLoop". Use this style to specify
+	 * if loops should be routed using an orthogonal router. Currently, this
+	 * uses <mxEdgeStyle.OrthConnector> but will be replaced with a dedicated
+	 * orthogonal loop router in later releases.
+	 */
+	STYLE_ORTHOGONAL_LOOP: 'orthogonalLoop',
+
+	/**
+	 * Variable: STYLE_ROUTING_CENTER_X
+	 * 
+	 * Defines the key for the horizontal routing center. Possible values are
+	 * between -0.5 and 0.5. This is the relative offset from the center used
+	 * for connecting edges. The type of this value is numeric. Value is
+	 * "routingCenterX".
+	 */
+	STYLE_ROUTING_CENTER_X: 'routingCenterX',
+
+	/**
+	 * Variable: STYLE_ROUTING_CENTER_Y
+	 * 
+	 * Defines the key for the vertical routing center. Possible values are
+	 * between -0.5 and 0.5. This is the relative offset from the center used
+	 * for connecting edges. The type of this value is numeric. Value is
+	 * "routingCenterY".
+	 */
+	STYLE_ROUTING_CENTER_Y: 'routingCenterY',
+
+	/**
+	 * Variable: FONT_BOLD
+	 * 
+	 * Constant for bold fonts. Default is 1.
+	 */
+	FONT_BOLD: 1,
+
+	/**
+	 * Variable: FONT_ITALIC
+	 * 
+	 * Constant for italic fonts. Default is 2.
+	 */
+	FONT_ITALIC: 2,
+
+	/**
+	 * Variable: FONT_UNDERLINE
+	 * 
+	 * Constant for underlined fonts. Default is 4.
+	 */
+	FONT_UNDERLINE: 4,
+
+	/**
+	 * Variable: SHAPE_RECTANGLE
+	 * 
+	 * Name under which <mxRectangleShape> is registered in <mxCellRenderer>.
+	 * Default is rectangle.
+	 */
+	SHAPE_RECTANGLE: 'rectangle',
+
+	/**
+	 * Variable: SHAPE_ELLIPSE
+	 * 
+	 * Name under which <mxEllipse> is registered in <mxCellRenderer>.
+	 * Default is ellipse.
+	 */
+	SHAPE_ELLIPSE: 'ellipse',
+
+	/**
+	 * Variable: SHAPE_DOUBLE_ELLIPSE
+	 * 
+	 * Name under which <mxDoubleEllipse> is registered in <mxCellRenderer>.
+	 * Default is doubleEllipse.
+	 */
+	SHAPE_DOUBLE_ELLIPSE: 'doubleEllipse',
+
+	/**
+	 * Variable: SHAPE_RHOMBUS
+	 * 
+	 * Name under which <mxRhombus> is registered in <mxCellRenderer>.
+	 * Default is rhombus.
+	 */
+	SHAPE_RHOMBUS: 'rhombus',
+
+	/**
+	 * Variable: SHAPE_LINE
+	 * 
+	 * Name under which <mxLine> is registered in <mxCellRenderer>.
+	 * Default is line.
+	 */
+	SHAPE_LINE: 'line',
+
+	/**
+	 * Variable: SHAPE_IMAGE
+	 * 
+	 * Name under which <mxImageShape> is registered in <mxCellRenderer>.
+	 * Default is image.
+	 */
+	SHAPE_IMAGE: 'image',
+	
+	/**
+	 * Variable: SHAPE_ARROW
+	 * 
+	 * Name under which <mxArrow> is registered in <mxCellRenderer>.
+	 * Default is arrow.
+	 */
+	SHAPE_ARROW: 'arrow',
+	
+	/**
+	 * Variable: SHAPE_ARROW_CONNECTOR
+	 * 
+	 * Name under which <mxArrowConnector> is registered in <mxCellRenderer>.
+	 * Default is arrowConnector.
+	 */
+	SHAPE_ARROW_CONNECTOR: 'arrowConnector',
+	
+	/**
+	 * Variable: SHAPE_LABEL
+	 * 
+	 * Name under which <mxLabel> is registered in <mxCellRenderer>.
+	 * Default is label.
+	 */
+	SHAPE_LABEL: 'label',
+	
+	/**
+	 * Variable: SHAPE_CYLINDER
+	 * 
+	 * Name under which <mxCylinder> is registered in <mxCellRenderer>.
+	 * Default is cylinder.
+	 */
+	SHAPE_CYLINDER: 'cylinder',
+	
+	/**
+	 * Variable: SHAPE_SWIMLANE
+	 * 
+	 * Name under which <mxSwimlane> is registered in <mxCellRenderer>.
+	 * Default is swimlane.
+	 */
+	SHAPE_SWIMLANE: 'swimlane',
+		
+	/**
+	 * Variable: SHAPE_CONNECTOR
+	 * 
+	 * Name under which <mxConnector> is registered in <mxCellRenderer>.
+	 * Default is connector.
+	 */
+	SHAPE_CONNECTOR: 'connector',
+
+	/**
+	 * Variable: SHAPE_ACTOR
+	 * 
+	 * Name under which <mxActor> is registered in <mxCellRenderer>.
+	 * Default is actor.
+	 */
+	SHAPE_ACTOR: 'actor',
+		
+	/**
+	 * Variable: SHAPE_CLOUD
+	 * 
+	 * Name under which <mxCloud> is registered in <mxCellRenderer>.
+	 * Default is cloud.
+	 */
+	SHAPE_CLOUD: 'cloud',
+		
+	/**
+	 * Variable: SHAPE_TRIANGLE
+	 * 
+	 * Name under which <mxTriangle> is registered in <mxCellRenderer>.
+	 * Default is triangle.
+	 */
+	SHAPE_TRIANGLE: 'triangle',
+		
+	/**
+	 * Variable: SHAPE_HEXAGON
+	 * 
+	 * Name under which <mxHexagon> is registered in <mxCellRenderer>.
+	 * Default is hexagon.
+	 */
+	SHAPE_HEXAGON: 'hexagon',
+
+	/**
+	 * Variable: ARROW_CLASSIC
+	 * 
+	 * Constant for classic arrow markers.
+	 */
+	ARROW_CLASSIC: 'classic',
+
+	/**
+	 * Variable: ARROW_CLASSIC_THIN
+	 * 
+	 * Constant for thin classic arrow markers.
+	 */
+	ARROW_CLASSIC_THIN: 'classicThin',
+
+	/**
+	 * Variable: ARROW_BLOCK
+	 * 
+	 * Constant for block arrow markers.
+	 */
+	ARROW_BLOCK: 'block',
+
+	/**
+	 * Variable: ARROW_BLOCK_THIN
+	 * 
+	 * Constant for thin block arrow markers.
+	 */
+	ARROW_BLOCK_THIN: 'blockThin',
+
+	/**
+	 * Variable: ARROW_OPEN
+	 * 
+	 * Constant for open arrow markers.
+	 */
+	ARROW_OPEN: 'open',
+
+	/**
+	 * Variable: ARROW_OPEN_THIN
+	 * 
+	 * Constant for thin open arrow markers.
+	 */
+	ARROW_OPEN_THIN: 'openThin',
+
+	/**
+	 * Variable: ARROW_OVAL
+	 * 
+	 * Constant for oval arrow markers.
+	 */
+	ARROW_OVAL: 'oval',
+
+	/**
+	 * Variable: ARROW_DIAMOND
+	 * 
+	 * Constant for diamond arrow markers.
+	 */
+	ARROW_DIAMOND: 'diamond',
+
+	/**
+	 * Variable: ARROW_DIAMOND_THIN
+	 * 
+	 * Constant for thin diamond arrow markers.
+	 */
+	ARROW_DIAMOND_THIN: 'diamondThin',
+
+	/**
+	 * Variable: ALIGN_LEFT
+	 * 
+	 * Constant for left horizontal alignment. Default is left.
+	 */
+	ALIGN_LEFT: 'left',
+
+	/**
+	 * Variable: ALIGN_CENTER
+	 * 
+	 * Constant for center horizontal alignment. Default is center.
+	 */
+	ALIGN_CENTER: 'center',
+
+	/**
+	 * Variable: ALIGN_RIGHT
+	 * 
+	 * Constant for right horizontal alignment. Default is right.
+	 */
+	ALIGN_RIGHT: 'right',
+
+	/**
+	 * Variable: ALIGN_TOP
+	 * 
+	 * Constant for top vertical alignment. Default is top.
+	 */
+	ALIGN_TOP: 'top',
+
+	/**
+	 * Variable: ALIGN_MIDDLE
+	 * 
+	 * Constant for middle vertical alignment. Default is middle.
+	 */
+	ALIGN_MIDDLE: 'middle',
+
+	/**
+	 * Variable: ALIGN_BOTTOM
+	 * 
+	 * Constant for bottom vertical alignment. Default is bottom.
+	 */
+	ALIGN_BOTTOM: 'bottom',
+
+	/**
+	 * Variable: DIRECTION_NORTH
+	 * 
+	 * Constant for direction north. Default is north.
+	 */
+	DIRECTION_NORTH: 'north',
+
+	/**
+	 * Variable: DIRECTION_SOUTH
+	 * 
+	 * Constant for direction south. Default is south.
+	 */
+	DIRECTION_SOUTH: 'south',
+
+	/**
+	 * Variable: DIRECTION_EAST
+	 * 
+	 * Constant for direction east. Default is east.
+	 */
+	DIRECTION_EAST: 'east',
+
+	/**
+	 * Variable: DIRECTION_WEST
+	 * 
+	 * Constant for direction west. Default is west.
+	 */
+	DIRECTION_WEST: 'west',
+
+	/**
+	 * Variable: TEXT_DIRECTION_DEFAULT
+	 * 
+	 * Constant for text direction default. Default is an empty string. Use
+	 * this value to use the default text direction of the operating system. 
+	 */
+	TEXT_DIRECTION_DEFAULT: '',
+
+	/**
+	 * Variable: TEXT_DIRECTION_AUTO
+	 * 
+	 * Constant for text direction automatic. Default is auto. Use this value
+	 * to find the direction for a given text with <mxText.getAutoDirection>. 
+	 */
+	TEXT_DIRECTION_AUTO: 'auto',
+
+	/**
+	 * Variable: TEXT_DIRECTION_LTR
+	 * 
+	 * Constant for text direction left to right. Default is ltr. Use this
+	 * value for left to right text direction.
+	 */
+	TEXT_DIRECTION_LTR: 'ltr',
+
+	/**
+	 * Variable: TEXT_DIRECTION_RTL
+	 * 
+	 * Constant for text direction right to left. Default is rtl. Use this
+	 * value for right to left text direction.
+	 */
+	TEXT_DIRECTION_RTL: 'rtl',
+
+	/**
+	 * Variable: DIRECTION_MASK_NONE
+	 * 
+	 * Constant for no direction.
+	 */
+	DIRECTION_MASK_NONE: 0,
+
+	/**
+	 * Variable: DIRECTION_MASK_WEST
+	 * 
+	 * Bitwise mask for west direction.
+	 */
+	DIRECTION_MASK_WEST: 1,
+	
+	/**
+	 * Variable: DIRECTION_MASK_NORTH
+	 * 
+	 * Bitwise mask for north direction.
+	 */
+	DIRECTION_MASK_NORTH: 2,
+
+	/**
+	 * Variable: DIRECTION_MASK_SOUTH
+	 * 
+	 * Bitwise mask for south direction.
+	 */
+	DIRECTION_MASK_SOUTH: 4,
+
+	/**
+	 * Variable: DIRECTION_MASK_EAST
+	 * 
+	 * Bitwise mask for east direction.
+	 */
+	DIRECTION_MASK_EAST: 8,
+	
+	/**
+	 * Variable: DIRECTION_MASK_ALL
+	 * 
+	 * Bitwise mask for all directions.
+	 */
+	DIRECTION_MASK_ALL: 15,
+
+	/**
+	 * Variable: ELBOW_VERTICAL
+	 * 
+	 * Constant for elbow vertical. Default is horizontal.
+	 */
+	ELBOW_VERTICAL: 'vertical',
+
+	/**
+	 * Variable: ELBOW_HORIZONTAL
+	 * 
+	 * Constant for elbow horizontal. Default is horizontal.
+	 */
+	ELBOW_HORIZONTAL: 'horizontal',
+
+	/**
+	 * Variable: EDGESTYLE_ELBOW
+	 * 
+	 * Name of the elbow edge style. Can be used as a string value
+	 * for the STYLE_EDGE style.
+	 */
+	EDGESTYLE_ELBOW: 'elbowEdgeStyle',
+
+	/**
+	 * Variable: EDGESTYLE_ENTITY_RELATION
+	 * 
+	 * Name of the entity relation edge style. Can be used as a string value
+	 * for the STYLE_EDGE style.
+	 */
+	EDGESTYLE_ENTITY_RELATION: 'entityRelationEdgeStyle',
+
+	/**
+	 * Variable: EDGESTYLE_LOOP
+	 * 
+	 * Name of the loop edge style. Can be used as a string value
+	 * for the STYLE_EDGE style.
+	 */
+	EDGESTYLE_LOOP: 'loopEdgeStyle',
+
+	/**
+	 * Variable: EDGESTYLE_SIDETOSIDE
+	 * 
+	 * Name of the side to side edge style. Can be used as a string value
+	 * for the STYLE_EDGE style.
+	 */
+	EDGESTYLE_SIDETOSIDE: 'sideToSideEdgeStyle',
+
+	/**
+	 * Variable: EDGESTYLE_TOPTOBOTTOM
+	 * 
+	 * Name of the top to bottom edge style. Can be used as a string value
+	 * for the STYLE_EDGE style.
+	 */
+	EDGESTYLE_TOPTOBOTTOM: 'topToBottomEdgeStyle',
+
+	/**
+	 * Variable: EDGESTYLE_ORTHOGONAL
+	 * 
+	 * Name of the generic orthogonal edge style. Can be used as a string value
+	 * for the STYLE_EDGE style.
+	 */
+	EDGESTYLE_ORTHOGONAL: 'orthogonalEdgeStyle',
+
+	/**
+	 * Variable: EDGESTYLE_SEGMENT
+	 * 
+	 * Name of the generic segment edge style. Can be used as a string value
+	 * for the STYLE_EDGE style.
+	 */
+	EDGESTYLE_SEGMENT: 'segmentEdgeStyle',
+ 
+	/**
+	 * Variable: PERIMETER_ELLIPSE
+	 * 
+	 * Name of the ellipse perimeter. Can be used as a string value
+	 * for the STYLE_PERIMETER style.
+	 */
+	PERIMETER_ELLIPSE: 'ellipsePerimeter',
+
+	/**
+	 * Variable: PERIMETER_RECTANGLE
+	 *
+	 * Name of the rectangle perimeter. Can be used as a string value
+	 * for the STYLE_PERIMETER style.
+	 */
+	PERIMETER_RECTANGLE: 'rectanglePerimeter',
+
+	/**
+	 * Variable: PERIMETER_RHOMBUS
+	 * 
+	 * Name of the rhombus perimeter. Can be used as a string value
+	 * for the STYLE_PERIMETER style.
+	 */
+	PERIMETER_RHOMBUS: 'rhombusPerimeter',
+
+	/**
+	 * Variable: PERIMETER_HEXAGON
+	 * 
+	 * Name of the hexagon perimeter. Can be used as a string value 
+	 * for the STYLE_PERIMETER style.
+	 */
+	PERIMETER_HEXAGON: 'hexagonPerimeter',
+
+	/**
+	 * Variable: PERIMETER_TRIANGLE
+	 * 
+	 * Name of the triangle perimeter. Can be used as a string value
+	 * for the STYLE_PERIMETER style.
+	 */
+	PERIMETER_TRIANGLE: 'trianglePerimeter'
+
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxEventObject
+ * 
+ * The mxEventObject is a wrapper for all properties of a single event.
+ * Additionally, it also offers functions to consume the event and check if it
+ * was consumed as follows:
+ * 
+ * (code)
+ * evt.consume();
+ * INV: evt.isConsumed() == true
+ * (end)
+ * 
+ * Constructor: mxEventObject
+ *
+ * Constructs a new event object with the specified name. An optional
+ * sequence of key, value pairs can be appended to define properties.
+ * 
+ * Example:
+ *
+ * (code)
+ * new mxEventObject("eventName", key1, val1, .., keyN, valN)
+ * (end)
+ */
+function mxEventObject(name)
+{
+	this.name = name;
+	this.properties = [];
+	
+	for (var i = 1; i < arguments.length; i += 2)
+	{
+		if (arguments[i + 1] != null)
+		{
+			this.properties[arguments[i]] = arguments[i + 1];
+		}
+	}
+};
+
+/**
+ * Variable: name
+ *
+ * Holds the name.
+ */
+mxEventObject.prototype.name = null;
+
+/**
+ * Variable: properties
+ *
+ * Holds the properties as an associative array.
+ */
+mxEventObject.prototype.properties = null;
+
+/**
+ * Variable: consumed
+ *
+ * Holds the consumed state. Default is false.
+ */
+mxEventObject.prototype.consumed = false;
+
+/**
+ * Function: getName
+ * 
+ * Returns <name>.
+ */
+mxEventObject.prototype.getName = function()
+{
+	return this.name;
+};
+
+/**
+ * Function: getProperties
+ * 
+ * Returns <properties>.
+ */
+mxEventObject.prototype.getProperties = function()
+{
+	return this.properties;
+};
+
+/**
+ * Function: getProperty
+ * 
+ * Returns the property for the given key.
+ */
+mxEventObject.prototype.getProperty = function(key)
+{
+	return this.properties[key];
+};
+
+/**
+ * Function: isConsumed
+ *
+ * Returns true if the event has been consumed.
+ */
+mxEventObject.prototype.isConsumed = function()
+{
+	return this.consumed;
+};
+
+/**
+ * Function: consume
+ *
+ * Consumes the event.
+ */
+mxEventObject.prototype.consume = function()
+{
+	this.consumed = true;
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxMouseEvent
+ * 
+ * Base class for all mouse events in mxGraph. A listener for this event should
+ * implement the following methods:
+ * 
+ * (code)
+ * graph.addMouseListener(
+ * {
+ *   mouseDown: function(sender, evt)
+ *   {
+ *     mxLog.debug('mouseDown');
+ *   },
+ *   mouseMove: function(sender, evt)
+ *   {
+ *     mxLog.debug('mouseMove');
+ *   },
+ *   mouseUp: function(sender, evt)
+ *   {
+ *     mxLog.debug('mouseUp');
+ *   }
+ * });
+ * (end)
+ * 
+ * Constructor: mxMouseEvent
+ *
+ * Constructs a new event object for the given arguments.
+ * 
+ * Parameters:
+ * 
+ * evt - Native mouse event.
+ * state - Optional <mxCellState> under the mouse.
+ * 
+ */
+function mxMouseEvent(evt, state)
+{
+	this.evt = evt;
+	this.state = state;
+	this.sourceState = state;
+};
+
+/**
+ * Variable: consumed
+ *
+ * Holds the consumed state of this event.
+ */
+mxMouseEvent.prototype.consumed = false;
+
+/**
+ * Variable: evt
+ *
+ * Holds the inner event object.
+ */
+mxMouseEvent.prototype.evt = null;
+
+/**
+ * Variable: graphX
+ *
+ * Holds the x-coordinate of the event in the graph. This value is set in
+ * <mxGraph.fireMouseEvent>.
+ */
+mxMouseEvent.prototype.graphX = null;
+
+/**
+ * Variable: graphY
+ *
+ * Holds the y-coordinate of the event in the graph. This value is set in
+ * <mxGraph.fireMouseEvent>.
+ */
+mxMouseEvent.prototype.graphY = null;
+
+/**
+ * Variable: state
+ *
+ * Holds the optional <mxCellState> associated with this event.
+ */
+mxMouseEvent.prototype.state = null;
+
+/**
+ * Variable: sourceState
+ * 
+ * Holds the <mxCellState> that was passed to the constructor. This can be
+ * different from <state> depending on the result of <mxGraph.getEventState>.
+ */
+mxMouseEvent.prototype.sourceState = null;
+
+/**
+ * Function: getEvent
+ * 
+ * Returns <evt>.
+ */
+mxMouseEvent.prototype.getEvent = function()
+{
+	return this.evt;
+};
+
+/**
+ * Function: getSource
+ * 
+ * Returns the target DOM element using <mxEvent.getSource> for <evt>.
+ */
+mxMouseEvent.prototype.getSource = function()
+{
+	return mxEvent.getSource(this.evt);
+};
+
+/**
+ * Function: isSource
+ * 
+ * Returns true if the given <mxShape> is the source of <evt>.
+ */
+mxMouseEvent.prototype.isSource = function(shape)
+{
+	if (shape != null)
+	{
+		return mxUtils.isAncestorNode(shape.node, this.getSource());
+	}
+	
+	return false;
+};
+
+/**
+ * Function: getX
+ * 
+ * Returns <evt.clientX>.
+ */
+mxMouseEvent.prototype.getX = function()
+{
+	return mxEvent.getClientX(this.getEvent());
+};
+
+/**
+ * Function: getY
+ * 
+ * Returns <evt.clientY>.
+ */
+mxMouseEvent.prototype.getY = function()
+{
+	return mxEvent.getClientY(this.getEvent());
+};
+
+/**
+ * Function: getGraphX
+ * 
+ * Returns <graphX>.
+ */
+mxMouseEvent.prototype.getGraphX = function()
+{
+	return this.graphX;
+};
+
+/**
+ * Function: getGraphY
+ * 
+ * Returns <graphY>.
+ */
+mxMouseEvent.prototype.getGraphY = function()
+{
+	return this.graphY;
+};
+
+/**
+ * Function: getState
+ * 
+ * Returns <state>.
+ */
+mxMouseEvent.prototype.getState = function()
+{
+	return this.state;
+};
+
+/**
+ * Function: getCell
+ * 
+ * Returns the <mxCell> in <state> is not null.
+ */
+mxMouseEvent.prototype.getCell = function()
+{
+	var state = this.getState();
+	
+	if (state != null)
+	{
+		return state.cell;
+	}
+	
+	return null;
+};
+
+/**
+ * Function: isPopupTrigger
+ *
+ * Returns true if the event is a popup trigger.
+ */
+mxMouseEvent.prototype.isPopupTrigger = function()
+{
+	return mxEvent.isPopupTrigger(this.getEvent());
+};
+
+/**
+ * Function: isConsumed
+ *
+ * Returns <consumed>.
+ */
+mxMouseEvent.prototype.isConsumed = function()
+{
+	return this.consumed;
+};
+
+/**
+ * Function: consume
+ *
+ * Sets <consumed> to true and invokes preventDefault on the native event
+ * if such a method is defined. This is used mainly to avoid the cursor from
+ * being changed to a text cursor in Webkit. You can use the preventDefault
+ * flag to disable this functionality.
+ * 
+ * Parameters:
+ * 
+ * preventDefault - Specifies if the native event should be canceled. Default
+ * is true.
+ */
+mxMouseEvent.prototype.consume = function(preventDefault)
+{
+	preventDefault = (preventDefault != null) ? preventDefault : true;
+	
+	if (preventDefault && this.evt.preventDefault)
+	{
+		this.evt.preventDefault();
+	}
+
+	// Workaround for images being dragged in IE
+	// Does not change returnValue in Opera
+	if (mxClient.IS_IE)
+	{
+		this.evt.returnValue = true;
+	}
+
+	// Sets local consumed state
+	this.consumed = true;
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxEventSource
+ *
+ * Base class for objects that dispatch named events. To create a subclass that
+ * inherits from mxEventSource, the following code is used.
+ *
+ * (code)
+ * function MyClass() { };
+ *
+ * MyClass.prototype = new mxEventSource();
+ * MyClass.prototype.constructor = MyClass;
+ * (end)
+ *
+ * Known Subclasses:
+ *
+ * <mxGraphModel>, <mxGraph>, <mxGraphView>, <mxEditor>, <mxCellOverlay>,
+ * <mxToolbar>, <mxWindow>
+ * 
+ * Constructor: mxEventSource
+ *
+ * Constructs a new event source.
+ */
+function mxEventSource(eventSource)
+{
+	this.setEventSource(eventSource);
+};
+
+/**
+ * Variable: eventListeners
+ *
+ * Holds the event names and associated listeners in an array. The array
+ * contains the event name followed by the respective listener for each
+ * registered listener.
+ */
+mxEventSource.prototype.eventListeners = null;
+
+/**
+ * Variable: eventsEnabled
+ *
+ * Specifies if events can be fired. Default is true.
+ */
+mxEventSource.prototype.eventsEnabled = true;
+
+/**
+ * Variable: eventSource
+ *
+ * Optional source for events. Default is null.
+ */
+mxEventSource.prototype.eventSource = null;
+
+/**
+ * Function: isEventsEnabled
+ * 
+ * Returns <eventsEnabled>.
+ */
+mxEventSource.prototype.isEventsEnabled = function()
+{
+	return this.eventsEnabled;
+};
+
+/**
+ * Function: setEventsEnabled
+ * 
+ * Sets <eventsEnabled>.
+ */
+mxEventSource.prototype.setEventsEnabled = function(value)
+{
+	this.eventsEnabled = value;
+};
+
+/**
+ * Function: getEventSource
+ * 
+ * Returns <eventSource>.
+ */
+mxEventSource.prototype.getEventSource = function()
+{
+	return this.eventSource;
+};
+
+/**
+ * Function: setEventSource
+ * 
+ * Sets <eventSource>.
+ */
+mxEventSource.prototype.setEventSource = function(value)
+{
+	this.eventSource = value;
+};
+
+/**
+ * Function: addListener
+ *
+ * Binds the specified function to the given event name. If no event name
+ * is given, then the listener is registered for all events.
+ * 
+ * The parameters of the listener are the sender and an <mxEventObject>.
+ */
+mxEventSource.prototype.addListener = function(name, funct)
+{
+	if (this.eventListeners == null)
+	{
+		this.eventListeners = [];
+	}
+	
+	this.eventListeners.push(name);
+	this.eventListeners.push(funct);
+};
+
+/**
+ * Function: removeListener
+ *
+ * Removes all occurrences of the given listener from <eventListeners>.
+ */
+mxEventSource.prototype.removeListener = function(funct)
+{
+	if (this.eventListeners != null)
+	{
+		var i = 0;
+		
+		while (i < this.eventListeners.length)
+		{
+			if (this.eventListeners[i+1] == funct)
+			{
+				this.eventListeners.splice(i, 2);
+			}
+			else
+			{
+				i += 2;
+			}
+		}
+	}
+};
+
+/**
+ * Function: fireEvent
+ *
+ * Dispatches the given event to the listeners which are registered for
+ * the event. The sender argument is optional. The current execution scope
+ * ("this") is used for the listener invocation (see <mxUtils.bind>).
+ *
+ * Example:
+ *
+ * (code)
+ * fireEvent(new mxEventObject("eventName", key1, val1, .., keyN, valN))
+ * (end)
+ * 
+ * Parameters:
+ *
+ * evt - <mxEventObject> that represents the event.
+ * sender - Optional sender to be passed to the listener. Default value is
+ * the return value of <getEventSource>.
+ */
+mxEventSource.prototype.fireEvent = function(evt, sender)
+{
+	if (this.eventListeners != null && this.isEventsEnabled())
+	{
+		if (evt == null)
+		{
+			evt = new mxEventObject();
+		}
+		
+		if (sender == null)
+		{
+			sender = this.getEventSource();
+		}
+
+		if (sender == null)
+		{
+			sender = this;
+		}
+
+		var args = [sender, evt];
+		
+		for (var i = 0; i < this.eventListeners.length; i += 2)
+		{
+			var listen = this.eventListeners[i];
+			
+			if (listen == null || listen == evt.getName())
+			{
+				this.eventListeners[i+1].apply(this, args);
+			}
+		}
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+var mxEvent =
+{
+
+	/**
+	 * Class: mxEvent
+	 * 
+	 * Cross-browser DOM event support. For internal event handling,
+	 * <mxEventSource> and the graph event dispatch loop in <mxGraph> are used.
+	 * 
+	 * Memory Leaks:
+	 * 
+	 * Use this class for adding and removing listeners to/from DOM nodes. The
+	 * <removeAllListeners> function is provided to remove all listeners that
+	 * have been added using <addListener>. The function should be invoked when
+	 * the last reference is removed in the JavaScript code, typically when the
+	 * referenced DOM node is removed from the DOM, and helps to reduce memory
+	 * leaks in IE6.
+	 * 
+	 * Variable: objects
+	 * 
+	 * Contains all objects where any listener was added using <addListener>.
+	 * This is used to reduce memory leaks in IE, see <mxClient.dispose>.
+	 */
+	objects: [],
+
+	 /**
+	  * Function: addListener
+	  * 
+	  * Binds the function to the specified event on the given element. Use
+	  * <mxUtils.bind> in order to bind the "this" keyword inside the function
+	  * to a given execution scope.
+	  */
+	addListener: function()
+	{
+		var updateListenerList = function(element, eventName, funct)
+		{
+			if (element.mxListenerList == null)
+			{
+				element.mxListenerList = [];
+				mxEvent.objects.push(element);
+			}
+			
+			var entry = {name: eventName, f: funct};
+			element.mxListenerList.push(entry);
+		};
+		
+		if (window.addEventListener)
+		{
+			return function(element, eventName, funct)
+			{
+				element.addEventListener(eventName, funct, false);
+				updateListenerList(element, eventName, funct);
+			};
+		}
+		else
+		{
+			return function(element, eventName, funct)
+			{
+				element.attachEvent('on' + eventName, funct);
+				updateListenerList(element, eventName, funct);				
+			};
+		}
+	}(),
+
+	/**
+	 * Function: removeListener
+	 *
+	 * Removes the specified listener from the given element.
+	 */
+	removeListener: function()
+	{
+		var updateListener = function(element, eventName, funct)
+		{
+			if (element.mxListenerList != null)
+			{
+				var listenerCount = element.mxListenerList.length;
+				
+				for (var i = 0; i < listenerCount; i++)
+				{
+					var entry = element.mxListenerList[i];
+					
+					if (entry.f == funct)
+					{
+						element.mxListenerList.splice(i, 1);
+						break;
+					}
+				}
+				
+				if (element.mxListenerList.length == 0)
+				{
+					element.mxListenerList = null;
+					
+					var idx = mxUtils.indexOf(mxEvent.objects, element);
+					
+					if (idx >= 0)
+					{
+						mxEvent.objects.splice(idx, 1);
+					}
+				}
+			}
+		};
+		
+		if (window.removeEventListener)
+		{
+			return function(element, eventName, funct)
+			{
+				element.removeEventListener(eventName, funct, false);
+				updateListener(element, eventName, funct);
+			};
+		}
+		else
+		{
+			return function(element, eventName, funct)
+			{
+				element.detachEvent('on' + eventName, funct);
+				updateListener(element, eventName, funct);
+			};
+		}
+	}(),
+
+	/**
+	 * Function: removeAllListeners
+	 * 
+	 * Removes all listeners from the given element.
+	 */
+	removeAllListeners: function(element)
+	{
+		var list = element.mxListenerList;
+
+		if (list != null)
+		{
+			while (list.length > 0)
+			{
+				var entry = list[0];
+				mxEvent.removeListener(element, entry.name, entry.f);
+			}
+		}
+	},
+	
+	/**
+	 * Function: addGestureListeners
+	 * 
+	 * Adds the given listeners for touch, mouse and/or pointer events. If
+	 * <mxClient.IS_POINTER> is true then pointer events will be registered,
+	 * else the respective mouse events will be registered. If <mxClient.IS_POINTER>
+	 * is false and <mxClient.IS_TOUCH> is true then the respective touch events
+	 * will be registered as well as the mouse events.
+	 */
+	addGestureListeners: function(node, startListener, moveListener, endListener)
+	{
+		if (startListener != null)
+		{
+			mxEvent.addListener(node, (mxClient.IS_POINTER) ? 'pointerdown' : 'mousedown', startListener);
+		}
+		
+		if (moveListener != null)
+		{
+			mxEvent.addListener(node, (mxClient.IS_POINTER) ? 'pointermove' : 'mousemove', moveListener);
+		}
+		
+		if (endListener != null)
+		{
+			mxEvent.addListener(node, (mxClient.IS_POINTER) ? 'pointerup' : 'mouseup', endListener);
+		}
+		
+		if (!mxClient.IS_POINTER && mxClient.IS_TOUCH)
+		{
+			if (startListener != null)
+			{
+				mxEvent.addListener(node, 'touchstart', startListener);
+			}
+			
+			if (moveListener != null)
+			{
+				mxEvent.addListener(node, 'touchmove', moveListener);
+			}
+			
+			if (endListener != null)
+			{
+				mxEvent.addListener(node, 'touchend', endListener);
+			}
+		}
+	},
+	
+	/**
+	 * Function: removeGestureListeners
+	 * 
+	 * Removes the given listeners from mousedown, mousemove, mouseup and the
+	 * respective touch events if <mxClient.IS_TOUCH> is true.
+	 */
+	removeGestureListeners: function(node, startListener, moveListener, endListener)
+	{
+		if (startListener != null)
+		{
+			mxEvent.removeListener(node, (mxClient.IS_POINTER) ? 'pointerdown' : 'mousedown', startListener);
+		}
+		
+		if (moveListener != null)
+		{
+			mxEvent.removeListener(node, (mxClient.IS_POINTER) ? 'pointermove' : 'mousemove', moveListener);
+		}
+		
+		if (endListener != null)
+		{
+			mxEvent.removeListener(node, (mxClient.IS_POINTER) ? 'pointerup' : 'mouseup', endListener);
+		}
+		
+		if (!mxClient.IS_POINTER && mxClient.IS_TOUCH)
+		{
+			if (startListener != null)
+			{
+				mxEvent.removeListener(node, 'touchstart', startListener);
+			}
+			
+			if (moveListener != null)
+			{
+				mxEvent.removeListener(node, 'touchmove', moveListener);
+			}
+			
+			if (endListener != null)
+			{
+				mxEvent.removeListener(node, 'touchend', endListener);
+			}
+		}
+	},
+	
+	/**
+	 * Function: redirectMouseEvents
+	 *
+	 * Redirects the mouse events from the given DOM node to the graph dispatch
+	 * loop using the event and given state as event arguments. State can
+	 * either be an instance of <mxCellState> or a function that returns an
+	 * <mxCellState>. The down, move, up and dblClick arguments are optional
+	 * functions that take the trigger event as arguments and replace the
+	 * default behaviour.
+	 */
+	redirectMouseEvents: function(node, graph, state, down, move, up, dblClick)
+	{
+		var getState = function(evt)
+		{
+			return (typeof(state) == 'function') ? state(evt) : state;
+		};
+		
+		mxEvent.addGestureListeners(node, function (evt)
+		{
+			if (down != null)
+			{
+				down(evt);
+			}
+			else if (!mxEvent.isConsumed(evt))
+			{
+				graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt, getState(evt)));
+			}
+		},
+		function (evt)
+		{
+			if (move != null)
+			{
+				move(evt);
+			}
+			else if (!mxEvent.isConsumed(evt))
+			{
+				graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, getState(evt)));
+			}
+		},
+		function (evt)
+		{
+			if (up != null)
+			{
+				up(evt);
+			}
+			else if (!mxEvent.isConsumed(evt))
+			{
+				graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt, getState(evt)));
+			}
+		});
+
+		mxEvent.addListener(node, 'dblclick', function (evt)
+		{
+			if (dblClick != null)
+			{
+				dblClick(evt);
+			}
+			else if (!mxEvent.isConsumed(evt))
+			{
+				var tmp = getState(evt);
+				graph.dblClick(evt, (tmp != null) ? tmp.cell : null);
+			}
+		});
+	},
+
+	/**
+	 * Function: release
+	 * 
+	 * Removes the known listeners from the given DOM node and its descendants.
+	 * 
+	 * Parameters:
+	 * 
+	 * element - DOM node to remove the listeners from.
+	 */
+	release: function(element)
+	{
+		if (element != null)
+		{
+			mxEvent.removeAllListeners(element);
+			
+			var children = element.childNodes;
+			
+			if (children != null)
+			{
+		        var childCount = children.length;
+		        
+		        for (var i = 0; i < childCount; i += 1)
+		        {
+		        	mxEvent.release(children[i]);
+		        }
+		    }
+		}
+	},
+
+	/**
+	 * Function: addMouseWheelListener
+	 * 
+	 * Installs the given function as a handler for mouse wheel events. The
+	 * function has two arguments: the mouse event and a boolean that specifies
+	 * if the wheel was moved up or down.
+	 * 
+	 * This has been tested with IE 6 and 7, Firefox (all versions), Opera and
+	 * Safari. It does currently not work on Safari for Mac.
+	 * 
+	 * Example:
+	 * 
+	 * (code)
+	 * mxEvent.addMouseWheelListener(function (evt, up)
+	 * {
+	 *   mxLog.show();
+	 *   mxLog.debug('mouseWheel: up='+up);
+	 * });
+	 *(end)
+	 * 
+	 * Parameters:
+	 * 
+	 * funct - Handler function that takes the event argument and a boolean up
+	 * argument for the mousewheel direction.
+	 */
+	addMouseWheelListener: function(funct)
+	{
+		if (funct != null)
+		{
+			var wheelHandler = function(evt)
+			{
+				// IE does not give an event object but the
+				// global event object is the mousewheel event
+				// at this point in time.
+				if (evt == null)
+				{
+					evt = window.event;
+				}
+			
+				var delta = 0;
+				
+				if (mxClient.IS_FF)
+				{
+					delta = -evt.detail / 2;
+				}
+				else
+				{
+					delta = evt.wheelDelta / 120;
+				}
+				
+				// Handles the event using the given function
+				if (delta != 0)
+				{
+					funct(evt, delta > 0);
+				}
+			};
+	
+			// Webkit has NS event API, but IE event name and details 
+			if (mxClient.IS_NS && document.documentMode == null)
+			{
+				var eventName = (mxClient.IS_SF || 	mxClient.IS_GC) ? 'mousewheel' : 'DOMMouseScroll';
+				mxEvent.addListener(window, eventName, wheelHandler);
+			}
+			else
+			{
+				mxEvent.addListener(document, 'mousewheel', wheelHandler);
+			}
+		}
+	},
+	
+	/**
+	 * Function: disableContextMenu
+	 *
+	 * Disables the context menu for the given element.
+	 */
+	disableContextMenu: function()
+	{
+		if (mxClient.IS_IE && (typeof(document.documentMode) === 'undefined' || document.documentMode < 9))
+		{
+			return function(element)
+			{
+				mxEvent.addListener(element, 'contextmenu', function()
+				{
+					return false;
+				});
+			};
+		}
+		else
+		{
+			return function(element)
+			{
+				element.setAttribute('oncontextmenu', 'return false;');
+			};		
+		}
+	}(),
+	
+	/**
+	 * Function: getSource
+	 * 
+	 * Returns the event's target or srcElement depending on the browser.
+	 */
+	getSource: function(evt)
+	{
+		return (evt.srcElement != null) ? evt.srcElement : evt.target;
+	},
+
+	/**
+	 * Function: isConsumed
+	 * 
+	 * Returns true if the event has been consumed using <consume>.
+	 */
+	isConsumed: function(evt)
+	{
+		return evt.isConsumed != null && evt.isConsumed;
+	},
+
+	/**
+	 * Function: isTouchEvent
+	 * 
+	 * Returns true if the event was generated using a touch device (not a pen or mouse).
+	 */
+	isTouchEvent: function(evt)
+	{
+		return (evt.pointerType != null) ? (evt.pointerType == 'touch' || evt.pointerType ===
+			evt.MSPOINTER_TYPE_TOUCH) : ((evt.mozInputSource != null) ?
+					evt.mozInputSource == 5 : evt.type.indexOf('touch') == 0);
+	},
+
+	/**
+	 * Function: isPenEvent
+	 * 
+	 * Returns true if the event was generated using a pen (not a touch device or mouse).
+	 */
+	isPenEvent: function(evt)
+	{
+		return (evt.pointerType != null) ? (evt.pointerType == 'pen' || evt.pointerType ===
+			evt.MSPOINTER_TYPE_PEN) : ((evt.mozInputSource != null) ?
+					evt.mozInputSource == 2 : evt.type.indexOf('pen') == 0);
+	},
+
+	/**
+	 * Function: isMultiTouchEvent
+	 * 
+	 * Returns true if the event was generated using a touch device (not a pen or mouse).
+	 */
+	isMultiTouchEvent: function(evt)
+	{
+		return (evt.type != null && evt.type.indexOf('touch') == 0 && evt.touches != null && evt.touches.length > 1);
+	},
+
+	/**
+	 * Function: isMouseEvent
+	 * 
+	 * Returns true if the event was generated using a mouse (not a pen or touch device).
+	 */
+	isMouseEvent: function(evt)
+	{
+		return (evt.pointerType != null) ? (evt.pointerType == 'mouse' || evt.pointerType ===
+			evt.MSPOINTER_TYPE_MOUSE) : ((evt.mozInputSource != null) ?
+				evt.mozInputSource == 1 : evt.type.indexOf('mouse') == 0);
+	},
+	
+	/**
+	 * Function: isLeftMouseButton
+	 * 
+	 * Returns true if the left mouse button is pressed for the given event.
+	 * To check if a button is pressed during a mouseMove you should use the
+	 * <mxGraph.isMouseDown> property. Note that this returns true in Firefox
+	 * for control+left-click on the Mac.
+	 */
+	isLeftMouseButton: function(evt)
+	{
+		// Special case for mousemove and mousedown we check the buttons
+		// if it exists because which is 0 even if no button is pressed
+		if ('buttons' in evt && (evt.type == 'mousedown' || evt.type == 'mousemove'))
+		{
+			return evt.buttons == 1;
+		}
+		else if ('which' in evt)
+		{
+	        return evt.which === 1;
+	    }
+		else
+		{
+	        return evt.button === 1;
+	    }
+	},
+	
+	/**
+	 * Function: isMiddleMouseButton
+	 * 
+	 * Returns true if the middle mouse button is pressed for the given event.
+	 * To check if a button is pressed during a mouseMove you should use the
+	 * <mxGraph.isMouseDown> property.
+	 */
+	isMiddleMouseButton: function(evt)
+	{
+		if ('which' in evt)
+		{
+	        return evt.which === 2;
+	    }
+		else
+		{
+	        return evt.button === 4;
+	    }
+	},
+	
+	/**
+	 * Function: isRightMouseButton
+	 * 
+	 * Returns true if the right mouse button was pressed. Note that this
+	 * button might not be available on some systems. For handling a popup
+	 * trigger <isPopupTrigger> should be used.
+	 */
+	isRightMouseButton: function(evt)
+	{
+		if ('which' in evt)
+		{
+	        return evt.which === 3;
+	    }
+		else
+		{
+	        return evt.button === 2;
+	    }
+	},
+
+	/**
+	 * Function: isPopupTrigger
+	 * 
+	 * Returns true if the event is a popup trigger. This implementation
+	 * returns true if the right button or the left button and control was
+	 * pressed on a Mac.
+	 */
+	isPopupTrigger: function(evt)
+	{
+		return mxEvent.isRightMouseButton(evt) || (mxClient.IS_MAC && mxEvent.isControlDown(evt) &&
+			!mxEvent.isShiftDown(evt) && !mxEvent.isMetaDown(evt) && !mxEvent.isAltDown(evt));
+	},
+
+	/**
+	 * Function: isShiftDown
+	 * 
+	 * Returns true if the shift key is pressed for the given event.
+	 */
+	isShiftDown: function(evt)
+	{
+		return (evt != null) ? evt.shiftKey : false;
+	},
+
+	/**
+	 * Function: isAltDown
+	 * 
+	 * Returns true if the alt key is pressed for the given event.
+	 */
+	isAltDown: function(evt)
+	{
+		return (evt != null) ? evt.altKey : false;
+	},
+
+	/**
+	 * Function: isControlDown
+	 * 
+	 * Returns true if the control key is pressed for the given event.
+	 */
+	isControlDown: function(evt)
+	{
+		return (evt != null) ? evt.ctrlKey : false;
+	},
+
+	/**
+	 * Function: isMetaDown
+	 * 
+	 * Returns true if the meta key is pressed for the given event.
+	 */
+	isMetaDown: function(evt)
+	{
+		return (evt != null) ? evt.metaKey : false;
+	},
+
+	/**
+	 * Function: getMainEvent
+	 * 
+	 * Returns the touch or mouse event that contains the mouse coordinates.
+	 */
+	getMainEvent: function(e)
+	{
+		if ((e.type == 'touchstart' || e.type == 'touchmove') && e.touches != null && e.touches[0] != null)
+		{
+			e = e.touches[0];
+		}
+		else if (e.type == 'touchend' && e.changedTouches != null && e.changedTouches[0] != null)
+		{
+			e = e.changedTouches[0];
+		}
+		
+		return e;
+	},
+	
+	/**
+	 * Function: getClientX
+	 * 
+	 * Returns true if the meta key is pressed for the given event.
+	 */
+	getClientX: function(e)
+	{
+		return mxEvent.getMainEvent(e).clientX;
+	},
+
+	/**
+	 * Function: getClientY
+	 * 
+	 * Returns true if the meta key is pressed for the given event.
+	 */
+	getClientY: function(e)
+	{
+		return mxEvent.getMainEvent(e).clientY;
+	},
+
+	/**
+	 * Function: consume
+	 * 
+	 * Consumes the given event.
+	 * 
+	 * Parameters:
+	 * 
+	 * evt - Native event to be consumed.
+	 * preventDefault - Optional boolean to prevent the default for the event.
+	 * Default is true.
+	 * stopPropagation - Option boolean to stop event propagation. Default is
+	 * true.
+	 */
+	consume: function(evt, preventDefault, stopPropagation)
+	{
+		preventDefault = (preventDefault != null) ? preventDefault : true;
+		stopPropagation = (stopPropagation != null) ? stopPropagation : true;
+		
+		if (preventDefault)
+		{
+			if (evt.preventDefault)
+			{
+				if (stopPropagation)
+				{
+					evt.stopPropagation();
+				}
+				
+				evt.preventDefault();
+			}
+			else if (stopPropagation)
+			{
+				evt.cancelBubble = true;
+			}
+		}
+
+		// Opera
+		evt.isConsumed = true;
+
+		// Other browsers
+		if (!evt.preventDefault)
+		{
+			evt.returnValue = false;
+		}
+	},
+	
+	//
+	// Special handles in mouse events
+	//
+	
+	/**
+	 * Variable: LABEL_HANDLE
+	 * 
+	 * Index for the label handle in an mxMouseEvent. This should be a negative
+	 * value that does not interfere with any possible handle indices. Default
+	 * is -1.
+	 */
+	LABEL_HANDLE: -1,
+	
+	/**
+	 * Variable: ROTATION_HANDLE
+	 * 
+	 * Index for the rotation handle in an mxMouseEvent. This should be a
+	 * negative value that does not interfere with any possible handle indices.
+	 * Default is -2.
+	 */
+	ROTATION_HANDLE: -2,
+	
+	/**
+	 * Variable: CUSTOM_HANDLE
+	 * 
+	 * Start index for the custom handles in an mxMouseEvent. This should be a
+	 * negative value and is the start index which is decremented for each
+	 * custom handle. Default is -100.
+	 */
+	CUSTOM_HANDLE: -100,
+	
+	/**
+	 * Variable: VIRTUAL_HANDLE
+	 * 
+	 * Start index for the virtual handles in an mxMouseEvent. This should be a
+	 * negative value and is the start index which is decremented for each
+	 * virtual handle. Default is -100000. This assumes that there are no more
+	 * than VIRTUAL_HANDLE - CUSTOM_HANDLE custom handles.
+	 * 
+	 */
+	VIRTUAL_HANDLE: -100000,
+	
+	//
+	// Event names
+	//
+	
+	/**
+	 * Variable: MOUSE_DOWN
+	 *
+	 * Specifies the event name for mouseDown.
+	 */
+	MOUSE_DOWN: 'mouseDown',
+	
+	/**
+	 * Variable: MOUSE_MOVE
+	 *
+	 * Specifies the event name for mouseMove. 
+	 */
+	MOUSE_MOVE: 'mouseMove',
+	
+	/**
+	 * Variable: MOUSE_UP
+	 *
+	 * Specifies the event name for mouseUp. 
+	 */
+	MOUSE_UP: 'mouseUp',
+
+	/**
+	 * Variable: ACTIVATE
+	 *
+	 * Specifies the event name for activate.
+	 */
+	ACTIVATE: 'activate',
+
+	/**
+	 * Variable: RESIZE_START
+	 *
+	 * Specifies the event name for resizeStart.
+	 */
+	RESIZE_START: 'resizeStart',
+
+	/**
+	 * Variable: RESIZE
+	 *
+	 * Specifies the event name for resize.
+	 */
+	RESIZE: 'resize',
+
+	/**
+	 * Variable: RESIZE_END
+	 *
+	 * Specifies the event name for resizeEnd.
+	 */
+	RESIZE_END: 'resizeEnd',
+
+	/**
+	 * Variable: MOVE_START
+	 *
+	 * Specifies the event name for moveStart.
+	 */
+	MOVE_START: 'moveStart',
+
+	/**
+	 * Variable: MOVE
+	 *
+	 * Specifies the event name for move.
+	 */
+	MOVE: 'move',
+
+	/**
+	 * Variable: MOVE_END
+	 *
+	 * Specifies the event name for moveEnd.
+	 */
+	MOVE_END: 'moveEnd',
+
+	/**
+	 * Variable: PAN_START
+	 *
+	 * Specifies the event name for panStart.
+	 */
+	PAN_START: 'panStart',
+
+	/**
+	 * Variable: PAN
+	 *
+	 * Specifies the event name for pan.
+	 */
+	PAN: 'pan',
+
+	/**
+	 * Variable: PAN_END
+	 *
+	 * Specifies the event name for panEnd.
+	 */
+	PAN_END: 'panEnd',
+
+	/**
+	 * Variable: MINIMIZE
+	 *
+	 * Specifies the event name for minimize.
+	 */
+	MINIMIZE: 'minimize',
+
+	/**
+	 * Variable: NORMALIZE
+	 *
+	 * Specifies the event name for normalize.
+	 */
+	NORMALIZE: 'normalize',
+
+	/**
+	 * Variable: MAXIMIZE
+	 *
+	 * Specifies the event name for maximize.
+	 */
+	MAXIMIZE: 'maximize',
+
+	/**
+	 * Variable: HIDE
+	 *
+	 * Specifies the event name for hide.
+	 */
+	HIDE: 'hide',
+
+	/**
+	 * Variable: SHOW
+	 *
+	 * Specifies the event name for show.
+	 */
+	SHOW: 'show',
+
+	/**
+	 * Variable: CLOSE
+	 *
+	 * Specifies the event name for close.
+	 */
+	CLOSE: 'close',
+
+	/**
+	 * Variable: DESTROY
+	 *
+	 * Specifies the event name for destroy.
+	 */
+	DESTROY: 'destroy',
+
+	/**
+	 * Variable: REFRESH
+	 *
+	 * Specifies the event name for refresh.
+	 */
+	REFRESH: 'refresh',
+
+	/**
+	 * Variable: SIZE
+	 *
+	 * Specifies the event name for size.
+	 */
+	SIZE: 'size',
+	
+	/**
+	 * Variable: SELECT
+	 *
+	 * Specifies the event name for select.
+	 */
+	SELECT: 'select',
+
+	/**
+	 * Variable: FIRED
+	 *
+	 * Specifies the event name for fired.
+	 */
+	FIRED: 'fired',
+
+	/**
+	 * Variable: FIRE_MOUSE_EVENT
+	 *
+	 * Specifies the event name for fireMouseEvent.
+	 */
+	FIRE_MOUSE_EVENT: 'fireMouseEvent',
+
+	/**
+	 * Variable: GESTURE
+	 *
+	 * Specifies the event name for gesture.
+	 */
+	GESTURE: 'gesture',
+
+	/**
+	 * Variable: TAP_AND_HOLD
+	 *
+	 * Specifies the event name for tapAndHold.
+	 */
+	TAP_AND_HOLD: 'tapAndHold',
+
+	/**
+	 * Variable: GET
+	 *
+	 * Specifies the event name for get.
+	 */
+	GET: 'get',
+
+	/**
+	 * Variable: RECEIVE
+	 *
+	 * Specifies the event name for receive.
+	 */
+	RECEIVE: 'receive',
+
+	/**
+	 * Variable: CONNECT
+	 *
+	 * Specifies the event name for connect.
+	 */
+	CONNECT: 'connect',
+
+	/**
+	 * Variable: DISCONNECT
+	 *
+	 * Specifies the event name for disconnect.
+	 */
+	DISCONNECT: 'disconnect',
+
+	/**
+	 * Variable: SUSPEND
+	 *
+	 * Specifies the event name for suspend.
+	 */
+	SUSPEND: 'suspend',
+
+	/**
+	 * Variable: RESUME
+	 *
+	 * Specifies the event name for suspend.
+	 */
+	RESUME: 'resume',
+
+	/**
+	 * Variable: MARK
+	 *
+	 * Specifies the event name for mark.
+	 */
+	MARK: 'mark',
+
+	/**
+	 * Variable: ROOT
+	 *
+	 * Specifies the event name for root.
+	 */
+	ROOT: 'root',
+
+	/**
+	 * Variable: POST
+	 *
+	 * Specifies the event name for post.
+	 */
+	POST: 'post',
+
+	/**
+	 * Variable: OPEN
+	 *
+	 * Specifies the event name for open.
+	 */
+	OPEN: 'open',
+
+	/**
+	 * Variable: SAVE
+	 *
+	 * Specifies the event name for open.
+	 */
+	SAVE: 'save',
+
+	/**
+	 * Variable: BEFORE_ADD_VERTEX
+	 *
+	 * Specifies the event name for beforeAddVertex.
+	 */
+	BEFORE_ADD_VERTEX: 'beforeAddVertex',
+
+	/**
+	 * Variable: ADD_VERTEX
+	 *
+	 * Specifies the event name for addVertex.
+	 */
+	ADD_VERTEX: 'addVertex',
+
+	/**
+	 * Variable: AFTER_ADD_VERTEX
+	 *
+	 * Specifies the event name for afterAddVertex.
+	 */
+	AFTER_ADD_VERTEX: 'afterAddVertex',
+
+	/**
+	 * Variable: DONE
+	 *
+	 * Specifies the event name for done.
+	 */
+	DONE: 'done',
+
+	/**
+	 * Variable: EXECUTE
+	 *
+	 * Specifies the event name for execute.
+	 */
+	EXECUTE: 'execute',
+
+	/**
+	 * Variable: EXECUTED
+	 *
+	 * Specifies the event name for executed.
+	 */
+	EXECUTED: 'executed',
+
+	/**
+	 * Variable: BEGIN_UPDATE
+	 *
+	 * Specifies the event name for beginUpdate.
+	 */
+	BEGIN_UPDATE: 'beginUpdate',
+
+	/**
+	 * Variable: START_EDIT
+	 *
+	 * Specifies the event name for startEdit.
+	 */
+	START_EDIT: 'startEdit',
+
+	/**
+	 * Variable: END_UPDATE
+	 *
+	 * Specifies the event name for endUpdate.
+	 */
+	END_UPDATE: 'endUpdate',
+
+	/**
+	 * Variable: END_EDIT
+	 *
+	 * Specifies the event name for endEdit.
+	 */
+	END_EDIT: 'endEdit',
+
+	/**
+	 * Variable: BEFORE_UNDO
+	 *
+	 * Specifies the event name for beforeUndo.
+	 */
+	BEFORE_UNDO: 'beforeUndo',
+
+	/**
+	 * Variable: UNDO
+	 *
+	 * Specifies the event name for undo.
+	 */
+	UNDO: 'undo',
+
+	/**
+	 * Variable: REDO
+	 *
+	 * Specifies the event name for redo.
+	 */
+	REDO: 'redo',
+
+	/**
+	 * Variable: CHANGE
+	 *
+	 * Specifies the event name for change.
+	 */
+	CHANGE: 'change',
+
+	/**
+	 * Variable: NOTIFY
+	 *
+	 * Specifies the event name for notify.
+	 */
+	NOTIFY: 'notify',
+
+	/**
+	 * Variable: LAYOUT_CELLS
+	 *
+	 * Specifies the event name for layoutCells.
+	 */
+	LAYOUT_CELLS: 'layoutCells',
+
+	/**
+	 * Variable: CLICK
+	 *
+	 * Specifies the event name for click.
+	 */
+	CLICK: 'click',
+
+	/**
+	 * Variable: SCALE
+	 *
+	 * Specifies the event name for scale.
+	 */
+	SCALE: 'scale',
+
+	/**
+	 * Variable: TRANSLATE
+	 *
+	 * Specifies the event name for translate.
+	 */
+	TRANSLATE: 'translate',
+
+	/**
+	 * Variable: SCALE_AND_TRANSLATE
+	 *
+	 * Specifies the event name for scaleAndTranslate.
+	 */
+	SCALE_AND_TRANSLATE: 'scaleAndTranslate',
+
+	/**
+	 * Variable: UP
+	 *
+	 * Specifies the event name for up.
+	 */
+	UP: 'up',
+
+	/**
+	 * Variable: DOWN
+	 *
+	 * Specifies the event name for down.
+	 */
+	DOWN: 'down',
+
+	/**
+	 * Variable: ADD
+	 *
+	 * Specifies the event name for add.
+	 */
+	ADD: 'add',
+
+	/**
+	 * Variable: REMOVE
+	 *
+	 * Specifies the event name for remove.
+	 */
+	REMOVE: 'remove',
+	
+	/**
+	 * Variable: CLEAR
+	 *
+	 * Specifies the event name for clear.
+	 */
+	CLEAR: 'clear',
+
+	/**
+	 * Variable: ADD_CELLS
+	 *
+	 * Specifies the event name for addCells.
+	 */
+	ADD_CELLS: 'addCells',
+
+	/**
+	 * Variable: CELLS_ADDED
+	 *
+	 * Specifies the event name for cellsAdded.
+	 */
+	CELLS_ADDED: 'cellsAdded',
+
+	/**
+	 * Variable: MOVE_CELLS
+	 *
+	 * Specifies the event name for moveCells.
+	 */
+	MOVE_CELLS: 'moveCells',
+
+	/**
+	 * Variable: CELLS_MOVED
+	 *
+	 * Specifies the event name for cellsMoved.
+	 */
+	CELLS_MOVED: 'cellsMoved',
+
+	/**
+	 * Variable: RESIZE_CELLS
+	 *
+	 * Specifies the event name for resizeCells.
+	 */
+	RESIZE_CELLS: 'resizeCells',
+
+	/**
+	 * Variable: CELLS_RESIZED
+	 *
+	 * Specifies the event name for cellsResized.
+	 */
+	CELLS_RESIZED: 'cellsResized',
+
+	/**
+	 * Variable: TOGGLE_CELLS
+	 *
+	 * Specifies the event name for toggleCells.
+	 */
+	TOGGLE_CELLS: 'toggleCells',
+
+	/**
+	 * Variable: CELLS_TOGGLED
+	 *
+	 * Specifies the event name for cellsToggled.
+	 */
+	CELLS_TOGGLED: 'cellsToggled',
+
+	/**
+	 * Variable: ORDER_CELLS
+	 *
+	 * Specifies the event name for orderCells.
+	 */
+	ORDER_CELLS: 'orderCells',
+
+	/**
+	 * Variable: CELLS_ORDERED
+	 *
+	 * Specifies the event name for cellsOrdered.
+	 */
+	CELLS_ORDERED: 'cellsOrdered',
+
+	/**
+	 * Variable: REMOVE_CELLS
+	 *
+	 * Specifies the event name for removeCells.
+	 */
+	REMOVE_CELLS: 'removeCells',
+
+	/**
+	 * Variable: CELLS_REMOVED
+	 *
+	 * Specifies the event name for cellsRemoved.
+	 */
+	CELLS_REMOVED: 'cellsRemoved',
+
+	/**
+	 * Variable: GROUP_CELLS
+	 *
+	 * Specifies the event name for groupCells.
+	 */
+	GROUP_CELLS: 'groupCells',
+
+	/**
+	 * Variable: UNGROUP_CELLS
+	 *
+	 * Specifies the event name for ungroupCells.
+	 */
+	UNGROUP_CELLS: 'ungroupCells',
+
+	/**
+	 * Variable: REMOVE_CELLS_FROM_PARENT
+	 *
+	 * Specifies the event name for removeCellsFromParent.
+	 */
+	REMOVE_CELLS_FROM_PARENT: 'removeCellsFromParent',
+
+	/**
+	 * Variable: FOLD_CELLS
+	 *
+	 * Specifies the event name for foldCells.
+	 */
+	FOLD_CELLS: 'foldCells',
+
+	/**
+	 * Variable: CELLS_FOLDED
+	 *
+	 * Specifies the event name for cellsFolded.
+	 */
+	CELLS_FOLDED: 'cellsFolded',
+
+	/**
+	 * Variable: ALIGN_CELLS
+	 *
+	 * Specifies the event name for alignCells.
+	 */
+	ALIGN_CELLS: 'alignCells',
+
+	/**
+	 * Variable: LABEL_CHANGED
+	 *
+	 * Specifies the event name for labelChanged.
+	 */
+	LABEL_CHANGED: 'labelChanged',
+
+	/**
+	 * Variable: CONNECT_CELL
+	 *
+	 * Specifies the event name for connectCell.
+	 */
+	CONNECT_CELL: 'connectCell',
+
+	/**
+	 * Variable: CELL_CONNECTED
+	 *
+	 * Specifies the event name for cellConnected.
+	 */
+	CELL_CONNECTED: 'cellConnected',
+
+	/**
+	 * Variable: SPLIT_EDGE
+	 *
+	 * Specifies the event name for splitEdge.
+	 */
+	SPLIT_EDGE: 'splitEdge',
+
+	/**
+	 * Variable: FLIP_EDGE
+	 *
+	 * Specifies the event name for flipEdge.
+	 */
+	FLIP_EDGE: 'flipEdge',
+
+	/**
+	 * Variable: START_EDITING
+	 *
+	 * Specifies the event name for startEditing.
+	 */
+	START_EDITING: 'startEditing',
+
+	/**
+	 * Variable: EDITING_STARTED
+	 *
+	 * Specifies the event name for editingStarted.
+	 */
+	EDITING_STARTED: 'editingStarted',
+
+	/**
+	 * Variable: EDITING_STOPPED
+	 *
+	 * Specifies the event name for editingStopped.
+	 */
+	EDITING_STOPPED: 'editingStopped',
+
+	/**
+	 * Variable: ADD_OVERLAY
+	 *
+	 * Specifies the event name for addOverlay.
+	 */
+	ADD_OVERLAY: 'addOverlay',
+
+	/**
+	 * Variable: REMOVE_OVERLAY
+	 *
+	 * Specifies the event name for removeOverlay.
+	 */
+	REMOVE_OVERLAY: 'removeOverlay',
+
+	/**
+	 * Variable: UPDATE_CELL_SIZE
+	 *
+	 * Specifies the event name for updateCellSize.
+	 */
+	UPDATE_CELL_SIZE: 'updateCellSize',
+
+	/**
+	 * Variable: ESCAPE
+	 *
+	 * Specifies the event name for escape.
+	 */
+	ESCAPE: 'escape',
+
+	/**
+	 * Variable: DOUBLE_CLICK
+	 *
+	 * Specifies the event name for doubleClick.
+	 */
+	DOUBLE_CLICK: 'doubleClick',
+
+	/**
+	 * Variable: START
+	 *
+	 * Specifies the event name for start.
+	 */
+	START: 'start',
+
+	/**
+	 * Variable: RESET
+	 *
+	 * Specifies the event name for reset.
+	 */
+	RESET: 'reset'
+
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxXmlRequest
+ * 
+ * XML HTTP request wrapper. See also: <mxUtils.get>, <mxUtils.post> and
+ * <mxUtils.load>. This class provides a cross-browser abstraction for Ajax
+ * requests.
+ * 
+ * Encoding:
+ * 
+ * For encoding parameter values, the built-in encodeURIComponent JavaScript
+ * method must be used. For automatic encoding of post data in <mxEditor> the
+ * <mxEditor.escapePostData> switch can be set to true (default). The encoding
+ * will be carried out using the conte type of the page. That is, the page
+ * containting the editor should contain a meta tag in the header, eg.
+ * <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ * 
+ * Example:
+ * 
+ * (code)
+ * var onload = function(req)
+ * {
+ *   mxUtils.alert(req.getDocumentElement());
+ * }
+ * 
+ * var onerror = function(req)
+ * {
+ *   mxUtils.alert('Error');
+ * }
+ * new mxXmlRequest(url, 'key=value').send(onload, onerror);
+ * (end)
+ * 
+ * Sends an asynchronous POST request to the specified URL.
+ * 
+ * Example:
+ * 
+ * (code)
+ * var req = new mxXmlRequest(url, 'key=value', 'POST', false);
+ * req.send();
+ * mxUtils.alert(req.getDocumentElement());
+ * (end)
+ * 
+ * Sends a synchronous POST request to the specified URL.
+ * 
+ * Example:
+ * 
+ * (code)
+ * var encoder = new mxCodec();
+ * var result = encoder.encode(graph.getModel());
+ * var xml = encodeURIComponent(mxUtils.getXml(result));
+ * new mxXmlRequest(url, 'xml='+xml).send();
+ * (end)
+ * 
+ * Sends an encoded graph model to the specified URL using xml as the
+ * parameter name. The parameter can then be retrieved in C# as follows:
+ * 
+ * (code)
+ * string xml = HttpUtility.UrlDecode(context.Request.Params["xml"]);
+ * (end)
+ * 
+ * Or in Java as follows:
+ * 
+ * (code)
+ * String xml = URLDecoder.decode(request.getParameter("xml"), "UTF-8").replace("\n", "&#xa;");
+ * (end)
+ *
+ * Note that the linefeeds should only be replaced if the XML is
+ * processed in Java, for example when creating an image.
+ * 
+ * Constructor: mxXmlRequest
+ * 
+ * Constructs an XML HTTP request.
+ * 
+ * Parameters:
+ * 
+ * url - Target URL of the request.
+ * params - Form encoded parameters to send with a POST request.
+ * method - String that specifies the request method. Possible values are
+ * POST and GET. Default is POST.
+ * async - Boolean specifying if an asynchronous request should be used.
+ * Default is true.
+ * username - String specifying the username to be used for the request.
+ * password - String specifying the password to be used for the request.
+ */
+function mxXmlRequest(url, params, method, async, username, password)
+{
+	this.url = url;
+	this.params = params;
+	this.method = method || 'POST';
+	this.async = (async != null) ? async : true;
+	this.username = username;
+	this.password = password;
+};
+
+/**
+ * Variable: url
+ * 
+ * Holds the target URL of the request.
+ */
+mxXmlRequest.prototype.url = null;
+
+/**
+ * Variable: params
+ * 
+ * Holds the form encoded data for the POST request.
+ */
+mxXmlRequest.prototype.params = null;
+
+/**
+ * Variable: method
+ * 
+ * Specifies the request method. Possible values are POST and GET. Default
+ * is POST.
+ */
+mxXmlRequest.prototype.method = null;
+
+/**
+ * Variable: async
+ * 
+ * Boolean indicating if the request is asynchronous.
+ */
+mxXmlRequest.prototype.async = null;
+
+/**
+ * Variable: binary
+ * 
+ * Boolean indicating if the request is binary. This option is ignored in IE.
+ * In all other browsers the requested mime type is set to
+ * text/plain; charset=x-user-defined. Default is false.
+ */
+mxXmlRequest.prototype.binary = false;
+
+/**
+ * Variable: withCredentials
+ * 
+ * Specifies if withCredentials should be used in HTML5-compliant browsers. Default is
+ * false.
+ */
+mxXmlRequest.prototype.withCredentials = false;
+
+/**
+ * Variable: username
+ * 
+ * Specifies the username to be used for authentication.
+ */
+mxXmlRequest.prototype.username = null;
+
+/**
+ * Variable: password
+ * 
+ * Specifies the password to be used for authentication.
+ */
+mxXmlRequest.prototype.password = null;
+
+/**
+ * Variable: request
+ * 
+ * Holds the inner, browser-specific request object.
+ */
+mxXmlRequest.prototype.request = null;
+
+/**
+ * Variable: decodeSimulateValues
+ * 
+ * Specifies if request values should be decoded as URIs before setting the
+ * textarea value in <simulate>. Defaults to false for backwards compatibility,
+ * to avoid another decode on the server this should be set to true.
+ */
+mxXmlRequest.prototype.decodeSimulateValues = false;
+
+/**
+ * Function: isBinary
+ * 
+ * Returns <binary>.
+ */
+mxXmlRequest.prototype.isBinary = function()
+{
+	return this.binary;
+};
+
+/**
+ * Function: setBinary
+ * 
+ * Sets <binary>.
+ */
+mxXmlRequest.prototype.setBinary = function(value)
+{
+	this.binary = value;
+};
+
+/**
+ * Function: getText
+ * 
+ * Returns the response as a string.
+ */
+mxXmlRequest.prototype.getText = function()
+{
+	return this.request.responseText;
+};
+
+/**
+ * Function: isReady
+ * 
+ * Returns true if the response is ready.
+ */
+mxXmlRequest.prototype.isReady = function()
+{
+	return this.request.readyState == 4;
+};
+
+/**
+ * Function: getDocumentElement
+ * 
+ * Returns the document element of the response XML document.
+ */
+mxXmlRequest.prototype.getDocumentElement = function()
+{
+	var doc = this.getXml();
+	
+	if (doc != null)
+	{
+		return doc.documentElement;
+	}
+	
+	return null;
+};
+
+/**
+ * Function: getXml
+ * 
+ * Returns the response as an XML document. Use <getDocumentElement> to get
+ * the document element of the XML document.
+ */
+mxXmlRequest.prototype.getXml = function()
+{
+	var xml = this.request.responseXML;
+	
+	// Handles missing response headers in IE, the first condition handles
+	// the case where responseXML is there, but using its nodes leads to
+	// type errors in the mxCellCodec when putting the nodes into a new
+	// document. This happens in IE9 standards mode and with XML user
+	// objects only, as they are used directly as values in cells.
+	if (document.documentMode >= 9 || xml == null || xml.documentElement == null)
+	{
+		xml = mxUtils.parseXml(this.request.responseText);
+	}
+	
+	return xml;
+};
+
+/**
+ * Function: getText
+ * 
+ * Returns the response as a string.
+ */
+mxXmlRequest.prototype.getText = function()
+{
+	return this.request.responseText;
+};
+
+/**
+ * Function: getStatus
+ * 
+ * Returns the status as a number, eg. 404 for "Not found" or 200 for "OK".
+ * Note: The NS_ERROR_NOT_AVAILABLE for invalid responses cannot be cought.
+ */
+mxXmlRequest.prototype.getStatus = function()
+{
+	return this.request.status;
+};
+
+/**
+ * Function: create
+ * 
+ * Creates and returns the inner <request> object.
+ */
+mxXmlRequest.prototype.create = function()
+{
+	if (window.XMLHttpRequest)
+	{
+		return function()
+		{
+			var req = new XMLHttpRequest();
+			
+			// TODO: Check for overrideMimeType required here?
+			if (this.isBinary() && req.overrideMimeType)
+			{
+				req.overrideMimeType('text/plain; charset=x-user-defined');
+			}
+
+			return req;
+		};
+	}
+	else if (typeof(ActiveXObject) != 'undefined')
+	{
+		return function()
+		{
+			// TODO: Implement binary option
+			return new ActiveXObject('Microsoft.XMLHTTP');
+		};
+	}
+}();
+
+/**
+ * Function: send
+ * 
+ * Send the <request> to the target URL using the specified functions to
+ * process the response asychronously.
+ * 
+ * Parameters:
+ * 
+ * onload - Function to be invoked if a successful response was received.
+ * onerror - Function to be called on any error.
+ * timeout - Optional timeout in ms before calling ontimeout.
+ * ontimeout - Optional function to execute on timeout.
+ */
+mxXmlRequest.prototype.send = function(onload, onerror, timeout, ontimeout)
+{
+	this.request = this.create();
+	
+	if (this.request != null)
+	{
+		if (onload != null)
+		{
+			this.request.onreadystatechange = mxUtils.bind(this, function()
+			{
+				if (this.isReady())
+				{
+					onload(this);
+					this.onreadystatechaange = null;
+				}
+			});
+		}
+
+		this.request.open(this.method, this.url, this.async,
+			this.username, this.password);
+		this.setRequestHeaders(this.request, this.params);
+		
+		if (window.XMLHttpRequest && this.withCredentials)
+		{
+			this.request.withCredentials = 'true';
+		}
+		
+		if (!mxClient.IS_QUIRKS && (document.documentMode == null || document.documentMode > 9) &&
+			window.XMLHttpRequest && timeout != null && ontimeout != null)
+		{
+			this.request.timeout = timeout;
+			this.request.ontimeout = ontimeout;
+		}
+				
+		this.request.send(this.params);
+	}
+};
+
+/**
+ * Function: setRequestHeaders
+ * 
+ * Sets the headers for the given request and parameters. This sets the
+ * content-type to application/x-www-form-urlencoded if any params exist.
+ * 
+ * Example:
+ * 
+ * (code)
+ * request.setRequestHeaders = function(request, params)
+ * {
+ *   if (params != null)
+ *   {
+ *     request.setRequestHeader('Content-Type',
+ *             'multipart/form-data');
+ *     request.setRequestHeader('Content-Length',
+ *             params.length);
+ *   }
+ * };
+ * (end)
+ * 
+ * Use the code above before calling <send> if you require a
+ * multipart/form-data request.   
+ */
+mxXmlRequest.prototype.setRequestHeaders = function(request, params)
+{
+	if (params != null)
+	{
+		request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+	}
+};
+
+/**
+ * Function: simulate
+ * 
+ * Creates and posts a request to the given target URL using a dynamically
+ * created form inside the given document.
+ * 
+ * Parameters:
+ * 
+ * docs - Document that contains the form element.
+ * target - Target to send the form result to.
+ */
+mxXmlRequest.prototype.simulate = function(doc, target)
+{
+	doc = doc || document;
+	var old = null;
+
+	if (doc == document)
+	{
+		old = window.onbeforeunload;		
+		window.onbeforeunload = null;
+	}
+			
+	var form = doc.createElement('form');
+	form.setAttribute('method', this.method);
+	form.setAttribute('action', this.url);
+
+	if (target != null)
+	{
+		form.setAttribute('target', target);
+	}
+
+	form.style.display = 'none';
+	form.style.visibility = 'hidden';
+	
+	var pars = (this.params.indexOf('&') > 0) ?
+		this.params.split('&') :
+		this.params.split();
+
+	// Adds the parameters as textareas to the form
+	for (var i=0; i<pars.length; i++)
+	{
+		var pos = pars[i].indexOf('=');
+		
+		if (pos > 0)
+		{
+			var name = pars[i].substring(0, pos);
+			var value = pars[i].substring(pos+1);
+			
+			if (this.decodeSimulateValues)
+			{
+				value = decodeURIComponent(value);
+			}
+			
+			var textarea = doc.createElement('textarea');
+			textarea.setAttribute('wrap', 'off');
+			textarea.setAttribute('name', name);
+			mxUtils.write(textarea, value);
+			form.appendChild(textarea);
+		}
+	}
+	
+	doc.body.appendChild(form);
+	form.submit();
+	
+	if (form.parentNode != null)
+	{
+		form.parentNode.removeChild(form);
+	}
+
+	if (old != null)
+	{		
+		window.onbeforeunload = old;
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+var mxClipboard =
+{
+	/**
+	 * Class: mxClipboard
+	 * 
+	 * Singleton that implements a clipboard for graph cells.
+	 *
+	 * Example:
+	 * 
+	 * (code)
+	 * mxClipboard.copy(graph);
+	 * mxClipboard.paste(graph2);
+	 * (end)
+	 *
+	 * This copies the selection cells from the graph to the clipboard and
+	 * pastes them into graph2.
+	 * 
+	 * For fine-grained control of the clipboard data the <mxGraph.canExportCell>
+	 * and <mxGraph.canImportCell> functions can be overridden.
+	 * 
+	 * To restore previous parents for pasted cells, the implementation for
+	 * <copy> and <paste> can be changed as follows.
+	 * 
+	 * (code)
+	 * mxClipboard.copy = function(graph, cells)
+	 * {
+	 *   cells = cells || graph.getSelectionCells();
+	 *   var result = graph.getExportableCells(cells);
+	 *   
+	 *   mxClipboard.parents = new Object();
+	 *   
+	 *   for (var i = 0; i < result.length; i++)
+	 *   {
+	 *     mxClipboard.parents[i] = graph.model.getParent(cells[i]);
+	 *   }
+	 *   
+	 *   mxClipboard.insertCount = 1;
+	 *   mxClipboard.setCells(graph.cloneCells(result));
+	 *   
+	 *   return result;
+	 * };
+	 * 
+	 * mxClipboard.paste = function(graph)
+	 * {
+	 *   if (!mxClipboard.isEmpty())
+	 *   {
+	 *     var cells = graph.getImportableCells(mxClipboard.getCells());
+	 *     var delta = mxClipboard.insertCount * mxClipboard.STEPSIZE;
+	 *     var parent = graph.getDefaultParent();
+	 *     
+	 *     graph.model.beginUpdate();
+	 *     try
+	 *     {
+	 *       for (var i = 0; i < cells.length; i++)
+	 *       {
+	 *         var tmp = (mxClipboard.parents != null && graph.model.contains(mxClipboard.parents[i])) ?
+	 *              mxClipboard.parents[i] : parent;
+	 *         cells[i] = graph.importCells([cells[i]], delta, delta, tmp)[0];
+	 *       }
+	 *     }
+	 *     finally
+	 *     {
+	 *       graph.model.endUpdate();
+	 *     }
+	 *     
+	 *     // Increments the counter and selects the inserted cells
+	 *     mxClipboard.insertCount++;
+	 *     graph.setSelectionCells(cells);
+	 *   }
+	 * };
+	 * (end)
+	 * 
+	 * Variable: STEPSIZE
+	 * 
+	 * Defines the step size to offset the cells after each paste operation.
+	 * Default is 10.
+	 */
+	STEPSIZE: 10,
+
+	/**
+	 * Variable: insertCount
+	 * 
+	 * Counts the number of times the clipboard data has been inserted.
+	 */
+	insertCount: 1,
+
+	/**
+	 * Variable: cells
+	 * 
+	 * Holds the array of <mxCells> currently in the clipboard.
+	 */
+	cells: null,
+
+	/**
+	 * Function: setCells
+	 * 
+	 * Sets the cells in the clipboard. Fires a <mxEvent.CHANGE> event.
+	 */
+	setCells: function(cells)
+	{
+		mxClipboard.cells = cells;
+	},
+
+	/**
+	 * Function: getCells
+	 * 
+	 * Returns  the cells in the clipboard.
+	 */
+	getCells: function()
+	{
+		return mxClipboard.cells;
+	},
+	
+	/**
+	 * Function: isEmpty
+	 * 
+	 * Returns true if the clipboard currently has not data stored.
+	 */
+	isEmpty: function()
+	{
+		return mxClipboard.getCells() == null;
+	},
+	
+	/**
+	 * Function: cut
+	 * 
+	 * Cuts the given array of <mxCells> from the specified graph.
+	 * If cells is null then the selection cells of the graph will
+	 * be used. Returns the cells that have been cut from the graph.
+	 *
+	 * Parameters:
+	 * 
+	 * graph - <mxGraph> that contains the cells to be cut.
+	 * cells - Optional array of <mxCells> to be cut.
+	 */
+	cut: function(graph, cells)
+	{
+		cells = mxClipboard.copy(graph, cells);
+		mxClipboard.insertCount = 0;
+		mxClipboard.removeCells(graph, cells);
+		
+		return cells;
+	},
+
+	/**
+	 * Function: removeCells
+	 * 
+	 * Hook to remove the given cells from the given graph after
+	 * a cut operation.
+	 *
+	 * Parameters:
+	 * 
+	 * graph - <mxGraph> that contains the cells to be cut.
+	 * cells - Array of <mxCells> to be cut.
+	 */
+	removeCells: function(graph, cells)
+	{
+		graph.removeCells(cells);
+	},
+
+	/**
+	 * Function: copy
+	 * 
+	 * Copies the given array of <mxCells> from the specified
+	 * graph to <cells>. Returns the original array of cells that has
+	 * been cloned. Descendants of cells in the array are ignored.
+	 * 
+	 * Parameters:
+	 * 
+	 * graph - <mxGraph> that contains the cells to be copied.
+	 * cells - Optional array of <mxCells> to be copied.
+	 */
+	copy: function(graph, cells)
+	{
+		cells = cells || graph.getSelectionCells();
+		var result = graph.getExportableCells(graph.model.getTopmostCells(cells));
+		mxClipboard.insertCount = 1;
+		mxClipboard.setCells(graph.cloneCells(result));
+
+		return result;
+	},
+
+	/**
+	 * Function: paste
+	 * 
+	 * Pastes the <cells> into the specified graph restoring
+	 * the relation to <parents>, if possible. If the parents
+	 * are no longer in the graph or invisible then the
+	 * cells are added to the graph's default or into the
+	 * swimlane under the cell's new location if one exists.
+	 * The cells are added to the graph using <mxGraph.importCells>
+	 * and returned.
+	 * 
+	 * Parameters:
+	 * 
+	 * graph - <mxGraph> to paste the <cells> into.
+	 */
+	paste: function(graph)
+	{
+		var cells = null;
+		
+		if (!mxClipboard.isEmpty())
+		{
+			cells = graph.getImportableCells(mxClipboard.getCells());
+			var delta = mxClipboard.insertCount * mxClipboard.STEPSIZE;
+			var parent = graph.getDefaultParent();
+			cells = graph.importCells(cells, delta, delta, parent);
+			
+			// Increments the counter and selects the inserted cells
+			mxClipboard.insertCount++;
+			graph.setSelectionCells(cells);
+		}
+		
+		return cells;
+	}
+
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxWindow
+ * 
+ * Basic window inside a document.
+ * 
+ * Examples:
+ * 
+ * Creating a simple window.
+ *
+ * (code)
+ * var tb = document.createElement('div');
+ * var wnd = new mxWindow('Title', tb, 100, 100, 200, 200, true, true);
+ * wnd.setVisible(true); 
+ * (end)
+ *
+ * Creating a window that contains an iframe. 
+ * 
+ * (code)
+ * var frame = document.createElement('iframe');
+ * frame.setAttribute('width', '192px');
+ * frame.setAttribute('height', '172px');
+ * frame.setAttribute('src', 'http://www.example.com/');
+ * frame.style.backgroundColor = 'white';
+ * 
+ * var w = document.body.clientWidth;
+ * var h = (document.body.clientHeight || document.documentElement.clientHeight);
+ * var wnd = new mxWindow('Title', frame, (w-200)/2, (h-200)/3, 200, 200);
+ * wnd.setVisible(true);
+ * (end)
+ * 
+ * To limit the movement of a window, eg. to keep it from being moved beyond
+ * the top, left corner the following method can be overridden (recommended):
+ * 
+ * (code)
+ * wnd.setLocation = function(x, y)
+ * {
+ *   x = Math.max(0, x);
+ *   y = Math.max(0, y);
+ *   mxWindow.prototype.setLocation.apply(this, arguments);
+ * };
+ * (end)
+ * 
+ * Or the following event handler can be used:
+ * 
+ * (code)
+ * wnd.addListener(mxEvent.MOVE, function(e)
+ * {
+ *   wnd.setLocation(Math.max(0, wnd.getX()), Math.max(0, wnd.getY()));
+ * });
+ * (end)
+ * 
+ * To keep a window inside the current window:
+ * 
+ * (code)
+ * mxEvent.addListener(window, 'resize', mxUtils.bind(this, function()
+ * {
+ *   var iw = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
+ *   var ih = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
+ *   
+ *   var x = this.window.getX();
+ *   var y = this.window.getY();
+ *   
+ *   if (x + this.window.table.clientWidth > iw)
+ *   {
+ *     x = Math.max(0, iw - this.window.table.clientWidth);
+ *   }
+ *   
+ *   if (y + this.window.table.clientHeight > ih)
+ *   {
+ *     y = Math.max(0, ih - this.window.table.clientHeight);
+ *   }
+ *   
+ *   if (this.window.getX() != x || this.window.getY() != y)
+ *   {
+ *     this.window.setLocation(x, y);
+ *   }
+ * }));
+ * (end)
+ *
+ * Event: mxEvent.MOVE_START
+ *
+ * Fires before the window is moved. The <code>event</code> property contains
+ * the corresponding mouse event.
+ *
+ * Event: mxEvent.MOVE
+ *
+ * Fires while the window is being moved. The <code>event</code> property
+ * contains the corresponding mouse event.
+ *
+ * Event: mxEvent.MOVE_END
+ *
+ * Fires after the window is moved. The <code>event</code> property contains
+ * the corresponding mouse event.
+ *
+ * Event: mxEvent.RESIZE_START
+ *
+ * Fires before the window is resized. The <code>event</code> property contains
+ * the corresponding mouse event.
+ *
+ * Event: mxEvent.RESIZE
+ *
+ * Fires while the window is being resized. The <code>event</code> property
+ * contains the corresponding mouse event.
+ *
+ * Event: mxEvent.RESIZE_END
+ *
+ * Fires after the window is resized. The <code>event</code> property contains
+ * the corresponding mouse event.
+ *
+ * Event: mxEvent.MAXIMIZE
+ * 
+ * Fires after the window is maximized. The <code>event</code> property
+ * contains the corresponding mouse event.
+ * 
+ * Event: mxEvent.MINIMIZE
+ * 
+ * Fires after the window is minimized. The <code>event</code> property
+ * contains the corresponding mouse event.
+ * 
+ * Event: mxEvent.NORMALIZE
+ * 
+ * Fires after the window is normalized, that is, it returned from
+ * maximized or minimized state. The <code>event</code> property contains the
+ * corresponding mouse event.
+ *  
+ * Event: mxEvent.ACTIVATE
+ * 
+ * Fires after a window is activated. The <code>previousWindow</code> property
+ * contains the previous window. The event sender is the active window.
+ * 
+ * Event: mxEvent.SHOW
+ * 
+ * Fires after the window is shown. This event has no properties.
+ * 
+ * Event: mxEvent.HIDE
+ * 
+ * Fires after the window is hidden. This event has no properties.
+ * 
+ * Event: mxEvent.CLOSE
+ * 
+ * Fires before the window is closed. The <code>event</code> property contains
+ * the corresponding mouse event.
+ * 
+ * Event: mxEvent.DESTROY
+ * 
+ * Fires before the window is destroyed. This event has no properties.
+ * 
+ * Constructor: mxWindow
+ * 
+ * Constructs a new window with the given dimension and title to display
+ * the specified content. The window elements use the given style as a
+ * prefix for the classnames of the respective window elements, namely,
+ * the window title and window pane. The respective postfixes are appended
+ * to the given stylename as follows:
+ * 
+ *   style - Base style for the window.
+ *   style+Title - Style for the window title.
+ *   style+Pane - Style for the window pane.
+ * 
+ * The default value for style is mxWindow, resulting in the following
+ * classnames for the window elements: mxWindow, mxWindowTitle and
+ * mxWindowPane.
+ * 
+ * If replaceNode is given then the window replaces the given DOM node in
+ * the document.
+ * 
+ * Parameters:
+ * 
+ * title - String that represents the title of the new window.
+ * content - DOM node that is used as the window content.
+ * x - X-coordinate of the window location.
+ * y - Y-coordinate of the window location.
+ * width - Width of the window.
+ * height - Optional height of the window. Default is to match the height
+ * of the content at the specified width.
+ * minimizable - Optional boolean indicating if the window is minimizable.
+ * Default is true.
+ * movable - Optional boolean indicating if the window is movable. Default
+ * is true.
+ * replaceNode - Optional DOM node that the window should replace.
+ * style - Optional base classname for the window elements. Default is
+ * mxWindow.
+ */
+function mxWindow(title, content, x, y, width, height, minimizable, movable, replaceNode, style)
+{
+	if (content != null)
+	{
+		minimizable = (minimizable != null) ? minimizable : true;
+		this.content = content;
+		this.init(x, y, width, height, style);
+		
+		this.installMaximizeHandler();
+		this.installMinimizeHandler();
+		this.installCloseHandler();
+		this.setMinimizable(minimizable);
+		this.setTitle(title);
+		
+		if (movable == null || movable)
+		{
+			this.installMoveHandler();
+		}
+
+		if (replaceNode != null && replaceNode.parentNode != null)
+		{
+			replaceNode.parentNode.replaceChild(this.div, replaceNode);
+		}
+		else
+		{
+			document.body.appendChild(this.div);
+		}
+	}
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxWindow.prototype = new mxEventSource();
+mxWindow.prototype.constructor = mxWindow;
+
+/**
+ * Variable: closeImage
+ * 
+ * URL of the image to be used for the close icon in the titlebar.
+ */
+mxWindow.prototype.closeImage = mxClient.imageBasePath + '/close.gif';
+
+/**
+ * Variable: minimizeImage
+ * 
+ * URL of the image to be used for the minimize icon in the titlebar.
+ */
+mxWindow.prototype.minimizeImage = mxClient.imageBasePath + '/minimize.gif';
+	
+/**
+ * Variable: normalizeImage
+ * 
+ * URL of the image to be used for the normalize icon in the titlebar.
+ */
+mxWindow.prototype.normalizeImage = mxClient.imageBasePath + '/normalize.gif';
+	
+/**
+ * Variable: maximizeImage
+ * 
+ * URL of the image to be used for the maximize icon in the titlebar.
+ */
+mxWindow.prototype.maximizeImage = mxClient.imageBasePath + '/maximize.gif';
+
+/**
+ * Variable: normalizeImage
+ * 
+ * URL of the image to be used for the resize icon.
+ */
+mxWindow.prototype.resizeImage = mxClient.imageBasePath + '/resize.gif';
+
+/**
+ * Variable: visible
+ * 
+ * Boolean flag that represents the visible state of the window.
+ */
+mxWindow.prototype.visible = false;
+
+/**
+ * Variable: minimumSize
+ * 
+ * <mxRectangle> that specifies the minimum width and height of the window.
+ * Default is (50, 40).
+ */
+mxWindow.prototype.minimumSize = new mxRectangle(0, 0, 50, 40);
+
+/**
+ * Variable: destroyOnClose
+ * 
+ * Specifies if the window should be destroyed when it is closed. If this
+ * is false then the window is hidden using <setVisible>. Default is true.
+ */
+mxWindow.prototype.destroyOnClose = true;
+
+/**
+ * Variable: contentHeightCorrection
+ * 
+ * Defines the correction factor for computing the height of the contentWrapper.
+ * Default is 6 for IE 7/8 standards mode and 2 for all other browsers and modes.
+ */
+mxWindow.prototype.contentHeightCorrection = (document.documentMode == 8 || document.documentMode == 7) ? 6 : 2;
+
+/**
+ * Variable: title
+ * 
+ * Reference to the DOM node (TD) that contains the title.
+ */
+mxWindow.prototype.title = null;
+
+/**
+ * Variable: content
+ * 
+ * Reference to the DOM node that represents the window content.
+ */
+mxWindow.prototype.content = null;
+
+/**
+ * Function: init
+ * 
+ * Initializes the DOM tree that represents the window.
+ */
+mxWindow.prototype.init = function(x, y, width, height, style)
+{
+	style = (style != null) ? style : 'mxWindow';
+	
+	this.div = document.createElement('div');
+	this.div.className = style;
+
+	this.div.style.left = x + 'px';
+	this.div.style.top = y + 'px';
+	this.table = document.createElement('table');
+	this.table.className = style;
+
+	// Disables built-in pan and zoom in IE10 and later
+	if (mxClient.IS_POINTER)
+	{
+		this.div.style.touchAction = 'none';
+	}
+	
+	// Workaround for table size problems in FF
+	if (width != null)
+	{
+		if (!mxClient.IS_QUIRKS)
+		{
+			this.div.style.width = width + 'px'; 
+		}
+		
+		this.table.style.width = width + 'px';
+	} 
+	
+	if (height != null)
+	{
+		if (!mxClient.IS_QUIRKS)
+		{
+			this.div.style.height = height + 'px';
+		}
+		
+		this.table.style.height = height + 'px';
+	}		
+	
+	// Creates title row
+	var tbody = document.createElement('tbody');
+	var tr = document.createElement('tr');
+	
+	this.title = document.createElement('td');
+	this.title.className = style + 'Title';
+	
+	this.buttons = document.createElement('div');
+	this.buttons.style.position = 'absolute';
+	this.buttons.style.display = 'inline-block';
+	this.buttons.style.right = '4px';
+	this.buttons.style.top = '5px';
+	this.title.appendChild(this.buttons);
+	
+	tr.appendChild(this.title);
+	tbody.appendChild(tr);
+	
+	// Creates content row and table cell
+	tr = document.createElement('tr');
+	this.td = document.createElement('td');
+	this.td.className = style + 'Pane';
+	
+	if (document.documentMode == 7)
+	{
+		this.td.style.height = '100%';
+	}
+
+	this.contentWrapper = document.createElement('div');
+	this.contentWrapper.className = style + 'Pane';
+	this.contentWrapper.style.width = '100%';
+	this.contentWrapper.appendChild(this.content);
+
+	// Workaround for div around div restricts height
+	// of inner div if outerdiv has hidden overflow
+	if (mxClient.IS_QUIRKS || this.content.nodeName.toUpperCase() != 'DIV')
+	{
+		this.contentWrapper.style.height = '100%';
+	}
+
+	// Puts all content into the DOM
+	this.td.appendChild(this.contentWrapper);
+	tr.appendChild(this.td);
+	tbody.appendChild(tr);
+	this.table.appendChild(tbody);
+	this.div.appendChild(this.table);
+	
+	// Puts the window on top of other windows when clicked
+	var activator = mxUtils.bind(this, function(evt)
+	{
+		this.activate();
+	});
+	
+	mxEvent.addGestureListeners(this.title, activator);
+	mxEvent.addGestureListeners(this.table, activator);
+
+	this.hide();
+};
+
+/**
+ * Function: setTitle
+ * 
+ * Sets the window title to the given string. HTML markup inside the title
+ * will be escaped.
+ */
+mxWindow.prototype.setTitle = function(title)
+{
+	// Removes all text content nodes (normally just one)
+	var child = this.title.firstChild;
+	
+	while (child != null)
+	{
+		var next = child.nextSibling;
+		
+		if (child.nodeType == mxConstants.NODETYPE_TEXT)
+		{
+			child.parentNode.removeChild(child);
+		}
+		
+		child = next;
+	}
+	
+	mxUtils.write(this.title, title || '');
+	this.title.appendChild(this.buttons);
+};
+
+/**
+ * Function: setScrollable
+ * 
+ * Sets if the window contents should be scrollable.
+ */
+mxWindow.prototype.setScrollable = function(scrollable)
+{
+	// Workaround for hang in Presto 2.5.22 (Opera 10.5)
+	if (navigator.userAgent.indexOf('Presto/2.5') < 0)
+	{
+		if (scrollable)
+		{
+			this.contentWrapper.style.overflow = 'auto';
+		}
+		else
+		{
+			this.contentWrapper.style.overflow = 'hidden';
+		}
+	}
+};
+
+/**
+ * Function: activate
+ * 
+ * Puts the window on top of all other windows.
+ */
+mxWindow.prototype.activate = function()
+{
+	if (mxWindow.activeWindow != this)
+	{
+		var style = mxUtils.getCurrentStyle(this.getElement());
+		var index = (style != null) ? style.zIndex : 3;
+
+		if (mxWindow.activeWindow)
+		{
+			var elt = mxWindow.activeWindow.getElement();
+			
+			if (elt != null && elt.style != null)
+			{
+				elt.style.zIndex = index;
+			}
+		}
+		
+		var previousWindow = mxWindow.activeWindow;
+		this.getElement().style.zIndex = parseInt(index) + 1;
+		mxWindow.activeWindow = this;
+		
+		this.fireEvent(new mxEventObject(mxEvent.ACTIVATE, 'previousWindow', previousWindow));
+	}
+};
+
+/**
+ * Function: getElement
+ * 
+ * Returuns the outermost DOM node that makes up the window.
+ */
+mxWindow.prototype.getElement = function()
+{
+	return this.div;
+};
+
+/**
+ * Function: fit
+ * 
+ * Makes sure the window is inside the client area of the window.
+ */
+mxWindow.prototype.fit = function()
+{
+	mxUtils.fit(this.div);
+};
+
+/**
+ * Function: isResizable
+ * 
+ * Returns true if the window is resizable.
+ */
+mxWindow.prototype.isResizable = function()
+{
+	if (this.resize != null)
+	{
+		return this.resize.style.display != 'none';
+	}
+	
+	return false;
+};
+
+/**
+ * Function: setResizable
+ * 
+ * Sets if the window should be resizable. To avoid interference with some
+ * built-in features of IE10 and later, the use of the following code is
+ * recommended if there are resizable <mxWindow>s in the page:
+ * 
+ * (code)
+ * if (mxClient.IS_POINTER)
+ * {
+ *   document.body.style.msTouchAction = 'none';
+ * }
+ * (end)
+ */
+mxWindow.prototype.setResizable = function(resizable)
+{
+	if (resizable)
+	{
+		if (this.resize == null)
+		{
+			this.resize = document.createElement('img');
+			this.resize.style.position = 'absolute';
+			this.resize.style.bottom = '2px';
+			this.resize.style.right = '2px';
+
+			this.resize.setAttribute('src', mxClient.imageBasePath + '/resize.gif');
+			this.resize.style.cursor = 'nw-resize';
+			
+			var startX = null;
+			var startY = null;
+			var width = null;
+			var height = null;
+			
+			var start = mxUtils.bind(this, function(evt)
+			{
+				// LATER: pointerdown starting on border of resize does start
+				// the drag operation but does not fire consecutive events via
+				// one of the listeners below (does pan instead).
+				// Workaround: document.body.style.msTouchAction = 'none'
+				this.activate();
+				startX = mxEvent.getClientX(evt);
+				startY = mxEvent.getClientY(evt);
+				width = this.div.offsetWidth;
+				height = this.div.offsetHeight;
+				
+				mxEvent.addGestureListeners(document, null, dragHandler, dropHandler);
+				this.fireEvent(new mxEventObject(mxEvent.RESIZE_START, 'event', evt));
+				mxEvent.consume(evt);
+			});
+
+			// Adds a temporary pair of listeners to intercept
+			// the gesture event in the document
+			var dragHandler = mxUtils.bind(this, function(evt)
+			{
+				if (startX != null && startY != null)
+				{
+					var dx = mxEvent.getClientX(evt) - startX;
+					var dy = mxEvent.getClientY(evt) - startY;
+	
+					this.setSize(width + dx, height + dy);
+	
+					this.fireEvent(new mxEventObject(mxEvent.RESIZE, 'event', evt));
+					mxEvent.consume(evt);
+				}
+			});
+			
+			var dropHandler = mxUtils.bind(this, function(evt)
+			{
+				if (startX != null && startY != null)
+				{
+					startX = null;
+					startY = null;
+					mxEvent.removeGestureListeners(document, null, dragHandler, dropHandler);
+					this.fireEvent(new mxEventObject(mxEvent.RESIZE_END, 'event', evt));
+					mxEvent.consume(evt);
+				}
+			});
+			
+			mxEvent.addGestureListeners(this.resize, start, dragHandler, dropHandler);
+			this.div.appendChild(this.resize);
+		}
+		else 
+		{
+			this.resize.style.display = 'inline';
+		}
+	}
+	else if (this.resize != null)
+	{
+		this.resize.style.display = 'none';
+	}
+};
+	
+/**
+ * Function: setSize
+ * 
+ * Sets the size of the window.
+ */
+mxWindow.prototype.setSize = function(width, height)
+{
+	width = Math.max(this.minimumSize.width, width);
+	height = Math.max(this.minimumSize.height, height);
+
+	// Workaround for table size problems in FF
+	if (!mxClient.IS_QUIRKS)
+	{
+		this.div.style.width =  width + 'px';
+		this.div.style.height = height + 'px';
+	}
+	
+	this.table.style.width =  width + 'px';
+	this.table.style.height = height + 'px';
+
+	if (!mxClient.IS_QUIRKS)
+	{
+		this.contentWrapper.style.height = (this.div.offsetHeight -
+			this.title.offsetHeight - this.contentHeightCorrection) + 'px';
+	}
+};
+	
+/**
+ * Function: setMinimizable
+ * 
+ * Sets if the window is minimizable.
+ */
+mxWindow.prototype.setMinimizable = function(minimizable)
+{
+	this.minimize.style.display = (minimizable) ? '' : 'none';
+};
+
+/**
+ * Function: getMinimumSize
+ * 
+ * Returns an <mxRectangle> that specifies the size for the minimized window.
+ * A width or height of 0 means keep the existing width or height. This
+ * implementation returns the height of the window title and keeps the width.
+ */
+mxWindow.prototype.getMinimumSize = function()
+{
+	return new mxRectangle(0, 0, 0, this.title.offsetHeight);
+};
+
+/**
+ * Function: installMinimizeHandler
+ * 
+ * Installs the event listeners required for minimizing the window.
+ */
+mxWindow.prototype.installMinimizeHandler = function()
+{
+	this.minimize = document.createElement('img');
+	
+	this.minimize.setAttribute('src', this.minimizeImage);
+	this.minimize.setAttribute('title', 'Minimize');
+	this.minimize.style.cursor = 'pointer';
+	this.minimize.style.marginLeft = '2px';
+	this.minimize.style.display = 'none';
+	
+	this.buttons.appendChild(this.minimize);
+	
+	var minimized = false;
+	var maxDisplay = null;
+	var height = null;
+
+	var funct = mxUtils.bind(this, function(evt)
+	{
+		this.activate();
+		
+		if (!minimized)
+		{
+			minimized = true;
+			
+			this.minimize.setAttribute('src', this.normalizeImage);
+			this.minimize.setAttribute('title', 'Normalize');
+			this.contentWrapper.style.display = 'none';
+			maxDisplay = this.maximize.style.display;
+			
+			this.maximize.style.display = 'none';
+			height = this.table.style.height;
+			
+			var minSize = this.getMinimumSize();
+			
+			if (minSize.height > 0)
+			{
+				if (!mxClient.IS_QUIRKS)
+				{
+					this.div.style.height = minSize.height + 'px';
+				}
+				
+				this.table.style.height = minSize.height + 'px';
+			}
+			
+			if (minSize.width > 0)
+			{
+				if (!mxClient.IS_QUIRKS)
+				{
+					this.div.style.width = minSize.width + 'px';
+				}
+				
+				this.table.style.width = minSize.width + 'px';
+			}
+			
+			if (this.resize != null)
+			{
+				this.resize.style.visibility = 'hidden';
+			}
+			
+			this.fireEvent(new mxEventObject(mxEvent.MINIMIZE, 'event', evt));
+		}
+		else
+		{
+			minimized = false;
+			
+			this.minimize.setAttribute('src', this.minimizeImage);
+			this.minimize.setAttribute('title', 'Minimize');
+			this.contentWrapper.style.display = ''; // default
+			this.maximize.style.display = maxDisplay;
+			
+			if (!mxClient.IS_QUIRKS)
+			{
+				this.div.style.height = height;
+			}
+			
+			this.table.style.height = height;
+
+			if (this.resize != null)
+			{
+				this.resize.style.visibility = '';
+			}
+			
+			this.fireEvent(new mxEventObject(mxEvent.NORMALIZE, 'event', evt));
+		}
+		
+		mxEvent.consume(evt);
+	});
+	
+	mxEvent.addGestureListeners(this.minimize, funct);
+};
+	
+/**
+ * Function: setMaximizable
+ * 
+ * Sets if the window is maximizable.
+ */
+mxWindow.prototype.setMaximizable = function(maximizable)
+{
+	this.maximize.style.display = (maximizable) ? '' : 'none';
+};
+
+/**
+ * Function: installMaximizeHandler
+ * 
+ * Installs the event listeners required for maximizing the window.
+ */
+mxWindow.prototype.installMaximizeHandler = function()
+{
+	this.maximize = document.createElement('img');
+	
+	this.maximize.setAttribute('src', this.maximizeImage);
+	this.maximize.setAttribute('title', 'Maximize');
+	this.maximize.style.cursor = 'default';
+	this.maximize.style.marginLeft = '2px';
+	this.maximize.style.cursor = 'pointer';
+	this.maximize.style.display = 'none';
+	
+	this.buttons.appendChild(this.maximize);
+	
+	var maximized = false;
+	var x = null;
+	var y = null;
+	var height = null;
+	var width = null;
+	var minDisplay = null;
+
+	var funct = mxUtils.bind(this, function(evt)
+	{
+		this.activate();
+		
+		if (this.maximize.style.display != 'none')
+		{
+			if (!maximized)
+			{
+				maximized = true;
+				
+				this.maximize.setAttribute('src', this.normalizeImage);
+				this.maximize.setAttribute('title', 'Normalize');
+				this.contentWrapper.style.display = '';
+				minDisplay = this.minimize.style.display;
+				this.minimize.style.display = 'none';
+				
+				// Saves window state
+				x = parseInt(this.div.style.left);
+				y = parseInt(this.div.style.top);
+				height = this.table.style.height;
+				width = this.table.style.width;
+
+				this.div.style.left = '0px';
+				this.div.style.top = '0px';
+				var docHeight = Math.max(document.body.clientHeight || 0, document.documentElement.clientHeight || 0);
+
+				if (!mxClient.IS_QUIRKS)
+				{
+					this.div.style.width = (document.body.clientWidth - 2) + 'px';
+					this.div.style.height = (docHeight - 2) + 'px';
+				}
+
+				this.table.style.width = (document.body.clientWidth - 2) + 'px';
+				this.table.style.height = (docHeight - 2) + 'px';
+				
+				if (this.resize != null)
+				{
+					this.resize.style.visibility = 'hidden';
+				}
+
+				if (!mxClient.IS_QUIRKS)
+				{
+					var style = mxUtils.getCurrentStyle(this.contentWrapper);
+		
+					if (style.overflow == 'auto' || this.resize != null)
+					{
+						this.contentWrapper.style.height = (this.div.offsetHeight -
+							this.title.offsetHeight - this.contentHeightCorrection) + 'px';
+					}
+				}
+
+				this.fireEvent(new mxEventObject(mxEvent.MAXIMIZE, 'event', evt));
+			}
+			else
+			{
+				maximized = false;
+				
+				this.maximize.setAttribute('src', this.maximizeImage);
+				this.maximize.setAttribute('title', 'Maximize');
+				this.contentWrapper.style.display = '';
+				this.minimize.style.display = minDisplay;
+
+				// Restores window state
+				this.div.style.left = x+'px';
+				this.div.style.top = y+'px';
+				
+				if (!mxClient.IS_QUIRKS)
+				{
+					this.div.style.height = height;
+					this.div.style.width = width;
+
+					var style = mxUtils.getCurrentStyle(this.contentWrapper);
+		
+					if (style.overflow == 'auto' || this.resize != null)
+					{
+						this.contentWrapper.style.height = (this.div.offsetHeight -
+							this.title.offsetHeight - this.contentHeightCorrection) + 'px';
+					}
+				}
+				
+				this.table.style.height = height;
+				this.table.style.width = width;
+
+				if (this.resize != null)
+				{
+					this.resize.style.visibility = '';
+				}
+				
+				this.fireEvent(new mxEventObject(mxEvent.NORMALIZE, 'event', evt));
+			}
+			
+			mxEvent.consume(evt);
+		}
+	});
+	
+	mxEvent.addGestureListeners(this.maximize, funct);
+	mxEvent.addListener(this.title, 'dblclick', funct);
+};
+	
+/**
+ * Function: installMoveHandler
+ * 
+ * Installs the event listeners required for moving the window.
+ */
+mxWindow.prototype.installMoveHandler = function()
+{
+	this.title.style.cursor = 'move';
+	
+	mxEvent.addGestureListeners(this.title,
+		mxUtils.bind(this, function(evt)
+		{
+			var startX = mxEvent.getClientX(evt);
+			var startY = mxEvent.getClientY(evt);
+			var x = this.getX();
+			var y = this.getY();
+						
+			// Adds a temporary pair of listeners to intercept
+			// the gesture event in the document
+			var dragHandler = mxUtils.bind(this, function(evt)
+			{
+				var dx = mxEvent.getClientX(evt) - startX;
+				var dy = mxEvent.getClientY(evt) - startY;
+				this.setLocation(x + dx, y + dy);
+				this.fireEvent(new mxEventObject(mxEvent.MOVE, 'event', evt));
+				mxEvent.consume(evt);
+			});
+			
+			var dropHandler = mxUtils.bind(this, function(evt)
+			{
+				mxEvent.removeGestureListeners(document, null, dragHandler, dropHandler);
+				this.fireEvent(new mxEventObject(mxEvent.MOVE_END, 'event', evt));
+				mxEvent.consume(evt);
+			});
+			
+			mxEvent.addGestureListeners(document, null, dragHandler, dropHandler);
+			this.fireEvent(new mxEventObject(mxEvent.MOVE_START, 'event', evt));
+			mxEvent.consume(evt);
+		}));
+	
+	// Disables built-in pan and zoom in IE10 and later
+	if (mxClient.IS_POINTER)
+	{
+		this.title.style.touchAction = 'none';
+	}
+};
+
+/**
+ * Function: setLocation
+ * 
+ * Sets the upper, left corner of the window.
+ */
+ mxWindow.prototype.setLocation = function(x, y)
+ {
+	this.div.style.left = x + 'px';
+	this.div.style.top = y + 'px';
+ };
+
+/**
+ * Function: getX
+ *
+ * Returns the current position on the x-axis.
+ */
+mxWindow.prototype.getX = function()
+{
+	return parseInt(this.div.style.left);
+};
+
+/**
+ * Function: getY
+ *
+ * Returns the current position on the y-axis.
+ */
+mxWindow.prototype.getY = function()
+{
+	return parseInt(this.div.style.top);
+};
+
+/**
+ * Function: installCloseHandler
+ *
+ * Adds the <closeImage> as a new image node in <closeImg> and installs the
+ * <close> event.
+ */
+mxWindow.prototype.installCloseHandler = function()
+{
+	this.closeImg = document.createElement('img');
+	
+	this.closeImg.setAttribute('src', this.closeImage);
+	this.closeImg.setAttribute('title', 'Close');
+	this.closeImg.style.marginLeft = '2px';
+	this.closeImg.style.cursor = 'pointer';
+	this.closeImg.style.display = 'none';
+	
+	this.buttons.appendChild(this.closeImg);
+
+	mxEvent.addGestureListeners(this.closeImg,
+		mxUtils.bind(this, function(evt)
+		{
+			this.fireEvent(new mxEventObject(mxEvent.CLOSE, 'event', evt));
+			
+			if (this.destroyOnClose)
+			{
+				this.destroy();
+			}
+			else
+			{
+				this.setVisible(false);
+			}
+			
+			mxEvent.consume(evt);
+		}));
+};
+
+/**
+ * Function: setImage
+ * 
+ * Sets the image associated with the window.
+ * 
+ * Parameters:
+ * 
+ * image - URL of the image to be used.
+ */
+mxWindow.prototype.setImage = function(image)
+{
+	this.image = document.createElement('img');
+	this.image.setAttribute('src', image);
+	this.image.setAttribute('align', 'left');
+	this.image.style.marginRight = '4px';
+	this.image.style.marginLeft = '0px';
+	this.image.style.marginTop = '-2px';
+	
+	this.title.insertBefore(this.image, this.title.firstChild);
+};
+
+/**
+ * Function: setClosable
+ * 
+ * Sets the image associated with the window.
+ * 
+ * Parameters:
+ * 
+ * closable - Boolean specifying if the window should be closable.
+ */
+mxWindow.prototype.setClosable = function(closable)
+{
+	this.closeImg.style.display = (closable) ? '' : 'none';
+};
+
+/**
+ * Function: isVisible
+ * 
+ * Returns true if the window is visible.
+ */
+mxWindow.prototype.isVisible = function()
+{
+	if (this.div != null)
+	{
+		return this.div.style.display != 'none';
+	}
+	
+	return false;
+};
+
+/**
+ * Function: setVisible
+ *
+ * Shows or hides the window depending on the given flag.
+ * 
+ * Parameters:
+ * 
+ * visible - Boolean indicating if the window should be made visible.
+ */
+mxWindow.prototype.setVisible = function(visible)
+{
+	if (this.div != null && this.isVisible() != visible)
+	{
+		if (visible)
+		{
+			this.show();
+		}
+		else
+		{
+			this.hide();
+		}
+	}
+};
+
+/**
+ * Function: show
+ *
+ * Shows the window.
+ */
+mxWindow.prototype.show = function()
+{
+	this.div.style.display = '';
+	this.activate();
+	
+	var style = mxUtils.getCurrentStyle(this.contentWrapper);
+	
+	if (!mxClient.IS_QUIRKS && (style.overflow == 'auto' || this.resize != null))
+	{
+		this.contentWrapper.style.height = (this.div.offsetHeight -
+				this.title.offsetHeight - this.contentHeightCorrection) + 'px';
+	}
+	
+	this.fireEvent(new mxEventObject(mxEvent.SHOW));
+};
+
+/**
+ * Function: hide
+ *
+ * Hides the window.
+ */
+mxWindow.prototype.hide = function()
+{
+	this.div.style.display = 'none';
+	this.fireEvent(new mxEventObject(mxEvent.HIDE));
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the window and removes all associated resources. Fires a
+ * <destroy> event prior to destroying the window.
+ */
+mxWindow.prototype.destroy = function()
+{
+	this.fireEvent(new mxEventObject(mxEvent.DESTROY));
+	
+	if (this.div != null)
+	{
+		mxEvent.release(this.div);
+		this.div.parentNode.removeChild(this.div);
+		this.div = null;
+	}
+	
+	this.title = null;
+	this.content = null;
+	this.contentWrapper = null;
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxForm
+ * 
+ * A simple class for creating HTML forms.
+ * 
+ * Constructor: mxForm
+ * 
+ * Creates a HTML table using the specified classname.
+ */
+function mxForm(className)
+{
+	this.table = document.createElement('table');
+	this.table.className = className;
+	this.body = document.createElement('tbody');
+	
+	this.table.appendChild(this.body);
+};
+
+/**
+ * Variable: table
+ * 
+ * Holds the DOM node that represents the table.
+ */
+mxForm.prototype.table = null;
+
+/**
+ * Variable: body
+ * 
+ * Holds the DOM node that represents the tbody (table body). New rows
+ * can be added to this object using DOM API.
+ */
+mxForm.prototype.body = false;
+
+/**
+ * Function: getTable
+ * 
+ * Returns the table that contains this form.
+ */
+mxForm.prototype.getTable = function()
+{
+	return this.table;
+};
+
+/**
+ * Function: addButtons
+ * 
+ * Helper method to add an OK and Cancel button using the respective
+ * functions.
+ */
+mxForm.prototype.addButtons = function(okFunct, cancelFunct)
+{
+	var tr = document.createElement('tr');
+	var td = document.createElement('td');
+	tr.appendChild(td);
+	td = document.createElement('td');
+
+	// Adds the ok button
+	var button = document.createElement('button');
+	mxUtils.write(button, mxResources.get('ok') || 'OK');
+	td.appendChild(button);
+
+	mxEvent.addListener(button, 'click', function()
+	{
+		okFunct();
+	});
+	
+	// Adds the cancel button
+	button = document.createElement('button');
+	mxUtils.write(button, mxResources.get('cancel') || 'Cancel');
+	td.appendChild(button);
+	
+	mxEvent.addListener(button, 'click', function()
+	{
+		cancelFunct();
+	});
+	
+	tr.appendChild(td);
+	this.body.appendChild(tr);
+};
+
+/**
+ * Function: addText
+ * 
+ * Adds an input for the given name, type and value and returns it.
+ */
+mxForm.prototype.addText = function(name, value, type)
+{
+	var input = document.createElement('input');
+	
+	input.setAttribute('type', type || 'text');
+	input.value = value;
+	
+	return this.addField(name, input);
+};
+
+/**
+ * Function: addCheckbox
+ * 
+ * Adds a checkbox for the given name and value and returns the textfield.
+ */
+mxForm.prototype.addCheckbox = function(name, value)
+{
+	var input = document.createElement('input');
+	
+	input.setAttribute('type', 'checkbox');
+	this.addField(name, input);
+
+	// IE can only change the checked value if the input is inside the DOM
+	if (value)
+	{
+		input.checked = true;
+	}
+
+	return input;
+};
+
+/**
+ * Function: addTextarea
+ * 
+ * Adds a textarea for the given name and value and returns the textarea.
+ */
+mxForm.prototype.addTextarea = function(name, value, rows)
+{
+	var input = document.createElement('textarea');
+	
+	if (mxClient.IS_NS)
+	{
+		rows--;
+	}
+	
+	input.setAttribute('rows', rows || 2);
+	input.value = value;
+	
+	return this.addField(name, input);
+};
+
+/**
+ * Function: addCombo
+ * 
+ * Adds a combo for the given name and returns the combo.
+ */
+mxForm.prototype.addCombo = function(name, isMultiSelect, size)
+{
+	var select = document.createElement('select');
+	
+	if (size != null)
+	{
+		select.setAttribute('size', size);
+	}
+	
+	if (isMultiSelect)
+	{
+		select.setAttribute('multiple', 'true');
+	}
+	
+	return this.addField(name, select);
+};
+
+/**
+ * Function: addOption
+ * 
+ * Adds an option for the given label to the specified combo.
+ */
+mxForm.prototype.addOption = function(combo, label, value, isSelected)
+{
+	var option = document.createElement('option');
+	
+	mxUtils.writeln(option, label);
+	option.setAttribute('value', value);
+	
+	if (isSelected)
+	{
+		option.setAttribute('selected', isSelected);
+	}
+	
+	combo.appendChild(option);
+};
+
+/**
+ * Function: addField
+ * 
+ * Adds a new row with the name and the input field in two columns and
+ * returns the given input.
+ */
+mxForm.prototype.addField = function(name, input)
+{
+	var tr = document.createElement('tr');
+	var td = document.createElement('td');
+	mxUtils.write(td, name);
+	tr.appendChild(td);
+	
+	td = document.createElement('td');
+	td.appendChild(input);
+	tr.appendChild(td);
+	this.body.appendChild(tr);
+	
+	return input;
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxImage
+ *
+ * Encapsulates the URL, width and height of an image.
+ * 
+ * Constructor: mxImage
+ * 
+ * Constructs a new image.
+ */
+function mxImage(src, width, height)
+{
+	this.src = src;
+	this.width = width;
+	this.height = height;
+};
+
+/**
+ * Variable: src
+ *
+ * String that specifies the URL of the image.
+ */
+mxImage.prototype.src = null;
+
+/**
+ * Variable: width
+ *
+ * Integer that specifies the width of the image.
+ */
+mxImage.prototype.width = null;
+
+/**
+ * Variable: height
+ *
+ * Integer that specifies the height of the image.
+ */
+mxImage.prototype.height = null;
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxDivResizer
+ * 
+ * Maintains the size of a div element in Internet Explorer. This is a
+ * workaround for the right and bottom style being ignored in IE.
+ * 
+ * If you need a div to cover the scrollwidth and -height of a document,
+ * then you can use this class as follows:
+ * 
+ * (code)
+ * var resizer = new mxDivResizer(background);
+ * resizer.getDocumentHeight = function()
+ * {
+ *   return document.body.scrollHeight;
+ * }
+ * resizer.getDocumentWidth = function()
+ * {
+ *   return document.body.scrollWidth;
+ * }
+ * resizer.resize();
+ * (end)
+ * 
+ * Constructor: mxDivResizer
+ * 
+ * Constructs an object that maintains the size of a div
+ * element when the window is being resized. This is only
+ * required for Internet Explorer as it ignores the respective
+ * stylesheet information for DIV elements.
+ * 
+ * Parameters:
+ * 
+ * div - Reference to the DOM node whose size should be maintained.
+ * container - Optional Container that contains the div. Default is the
+ * window.
+ */
+function mxDivResizer(div, container)
+{
+	if (div.nodeName.toLowerCase() == 'div')
+	{
+		if (container == null)
+		{
+			container = window;
+		}
+
+		this.div = div;
+		var style = mxUtils.getCurrentStyle(div);
+		
+		if (style != null)
+		{
+			this.resizeWidth = style.width == 'auto';
+			this.resizeHeight = style.height == 'auto';
+		}
+		
+		mxEvent.addListener(container, 'resize',
+			mxUtils.bind(this, function(evt)
+			{
+				if (!this.handlingResize)
+				{
+					this.handlingResize = true;
+					this.resize();
+					this.handlingResize = false;
+				}
+			})
+		);
+		
+		this.resize();
+	}
+};
+
+/**
+ * Function: resizeWidth
+ * 
+ * Boolean specifying if the width should be updated.
+ */
+mxDivResizer.prototype.resizeWidth = true;
+
+/**
+ * Function: resizeHeight
+ * 
+ * Boolean specifying if the height should be updated.
+ */
+mxDivResizer.prototype.resizeHeight = true;
+
+/**
+ * Function: handlingResize
+ * 
+ * Boolean specifying if the width should be updated.
+ */
+mxDivResizer.prototype.handlingResize = false;
+
+/**
+ * Function: resize
+ * 
+ * Updates the style of the DIV after the window has been resized.
+ */
+mxDivResizer.prototype.resize = function()
+{
+	var w = this.getDocumentWidth();
+	var h = this.getDocumentHeight();
+
+	var l = parseInt(this.div.style.left);
+	var r = parseInt(this.div.style.right);
+	var t = parseInt(this.div.style.top);
+	var b = parseInt(this.div.style.bottom);
+	
+	if (this.resizeWidth &&
+		!isNaN(l) &&
+		!isNaN(r) &&
+		l >= 0 &&
+		r >= 0 &&
+		w - r - l > 0)
+	{
+		this.div.style.width = (w - r - l)+'px';
+	}
+	
+	if (this.resizeHeight &&
+		!isNaN(t) &&
+		!isNaN(b) &&
+		t >= 0 &&
+		b >= 0 &&
+		h - t - b > 0)
+	{
+		this.div.style.height = (h - t - b)+'px';
+	}
+};
+
+/**
+ * Function: getDocumentWidth
+ * 
+ * Hook for subclassers to return the width of the document (without
+ * scrollbars).
+ */
+mxDivResizer.prototype.getDocumentWidth = function()
+{
+	return document.body.clientWidth;
+};
+
+/**
+ * Function: getDocumentHeight
+ * 
+ * Hook for subclassers to return the height of the document (without
+ * scrollbars).
+ */
+mxDivResizer.prototype.getDocumentHeight = function()
+{
+	return document.body.clientHeight;
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxDragSource
+ * 
+ * Wrapper to create a drag source from a DOM element so that the element can
+ * be dragged over a graph and dropped into the graph as a new cell.
+ * 
+ * Problem is that in the dropHandler the current preview location is not
+ * available, so the preview and the dropHandler must match.
+ * 
+ * Constructor: mxDragSource
+ * 
+ * Constructs a new drag source for the given element.
+ */
+function mxDragSource(element, dropHandler)
+{
+	this.element = element;
+	this.dropHandler = dropHandler;
+	
+	// Handles a drag gesture on the element
+	mxEvent.addGestureListeners(element, mxUtils.bind(this, function(evt)
+	{
+		this.mouseDown(evt);
+	}));
+	
+	// Prevents native drag and drop
+	mxEvent.addListener(element, 'dragstart', function(evt)
+	{
+		mxEvent.consume(evt);
+	});
+	
+	this.eventConsumer = function(sender, evt)
+	{
+		var evtName = evt.getProperty('eventName');
+		var me = evt.getProperty('event');
+		
+		if (evtName != mxEvent.MOUSE_DOWN)
+		{
+			me.consume();
+		}
+	};
+};
+
+/**
+ * Variable: element
+ *
+ * Reference to the DOM node which was made draggable.
+ */
+mxDragSource.prototype.element = null;
+
+/**
+ * Variable: dropHandler
+ *
+ * Holds the DOM node that is used to represent the drag preview. If this is
+ * null then the source element will be cloned and used for the drag preview.
+ */
+mxDragSource.prototype.dropHandler = null;
+
+/**
+ * Variable: dragOffset
+ *
+ * <mxPoint> that specifies the offset of the <dragElement>. Default is null.
+ */
+mxDragSource.prototype.dragOffset = null;
+
+/**
+ * Variable: dragElement
+ *
+ * Holds the DOM node that is used to represent the drag preview. If this is
+ * null then the source element will be cloned and used for the drag preview.
+ */
+mxDragSource.prototype.dragElement = null;
+
+/**
+ * Variable: previewElement
+ *
+ * Optional <mxRectangle> that specifies the unscaled size of the preview.
+ */
+mxDragSource.prototype.previewElement = null;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies if this drag source is enabled. Default is true.
+ */
+mxDragSource.prototype.enabled = true;
+
+/**
+ * Variable: currentGraph
+ *
+ * Reference to the <mxGraph> that is the current drop target.
+ */
+mxDragSource.prototype.currentGraph = null;
+
+/**
+ * Variable: currentDropTarget
+ *
+ * Holds the current drop target under the mouse.
+ */
+mxDragSource.prototype.currentDropTarget = null;
+
+/**
+ * Variable: currentPoint
+ *
+ * Holds the current drop location.
+ */
+mxDragSource.prototype.currentPoint = null;
+
+/**
+ * Variable: currentGuide
+ *
+ * Holds an <mxGuide> for the <currentGraph> if <dragPreview> is not null.
+ */
+mxDragSource.prototype.currentGuide = null;
+
+/**
+ * Variable: currentGuide
+ *
+ * Holds an <mxGuide> for the <currentGraph> if <dragPreview> is not null.
+ */
+mxDragSource.prototype.currentHighlight = null;
+
+/**
+ * Variable: autoscroll
+ *
+ * Specifies if the graph should scroll automatically. Default is true.
+ */
+mxDragSource.prototype.autoscroll = true;
+
+/**
+ * Variable: guidesEnabled
+ *
+ * Specifies if <mxGuide> should be enabled. Default is true.
+ */
+mxDragSource.prototype.guidesEnabled = true;
+
+/**
+ * Variable: gridEnabled
+ *
+ * Specifies if the grid should be allowed. Default is true.
+ */
+mxDragSource.prototype.gridEnabled = true;
+
+/**
+ * Variable: highlightDropTargets
+ *
+ * Specifies if drop targets should be highlighted. Default is true.
+ */
+mxDragSource.prototype.highlightDropTargets = true;
+
+/**
+ * Variable: dragElementZIndex
+ * 
+ * ZIndex for the drag element. Default is 100.
+ */
+mxDragSource.prototype.dragElementZIndex = 100;
+
+/**
+ * Variable: dragElementOpacity
+ * 
+ * Opacity of the drag element in %. Default is 70.
+ */
+mxDragSource.prototype.dragElementOpacity = 70;
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns <enabled>.
+ */
+mxDragSource.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ * 
+ * Sets <enabled>.
+ */
+mxDragSource.prototype.setEnabled = function(value)
+{
+	this.enabled = value;
+};
+
+/**
+ * Function: isGuidesEnabled
+ * 
+ * Returns <guidesEnabled>.
+ */
+mxDragSource.prototype.isGuidesEnabled = function()
+{
+	return this.guidesEnabled;
+};
+
+/**
+ * Function: setGuidesEnabled
+ * 
+ * Sets <guidesEnabled>.
+ */
+mxDragSource.prototype.setGuidesEnabled = function(value)
+{
+	this.guidesEnabled = value;
+};
+
+/**
+ * Function: isGridEnabled
+ * 
+ * Returns <gridEnabled>.
+ */
+mxDragSource.prototype.isGridEnabled = function()
+{
+	return this.gridEnabled;
+};
+
+/**
+ * Function: setGridEnabled
+ * 
+ * Sets <gridEnabled>.
+ */
+mxDragSource.prototype.setGridEnabled = function(value)
+{
+	this.gridEnabled = value;
+};
+
+/**
+ * Function: getGraphForEvent
+ * 
+ * Returns the graph for the given mouse event. This implementation returns
+ * null.
+ */
+mxDragSource.prototype.getGraphForEvent = function(evt)
+{
+	return null;
+};
+
+/**
+ * Function: getDropTarget
+ * 
+ * Returns the drop target for the given graph and coordinates. This
+ * implementation uses <mxGraph.getCellAt>.
+ */
+mxDragSource.prototype.getDropTarget = function(graph, x, y, evt)
+{
+	return graph.getCellAt(x, y);
+};
+
+/**
+ * Function: createDragElement
+ * 
+ * Creates and returns a clone of the <dragElementPrototype> or the <element>
+ * if the former is not defined.
+ */
+mxDragSource.prototype.createDragElement = function(evt)
+{
+	return this.element.cloneNode(true);
+};
+
+/**
+ * Function: createPreviewElement
+ * 
+ * Creates and returns an element which can be used as a preview in the given
+ * graph.
+ */
+mxDragSource.prototype.createPreviewElement = function(graph)
+{
+	return null;
+};
+
+/**
+ * Function: isActive
+ * 
+ * Returns true if this drag source is active.
+ */
+mxDragSource.prototype.isActive = function()
+{
+	return this.mouseMoveHandler != null;
+};
+
+/**
+ * Function: reset
+ * 
+ * Stops and removes everything and restores the state of the object.
+ */
+mxDragSource.prototype.reset = function()
+{
+	if (this.currentGraph != null)
+	{
+		this.dragExit(this.currentGraph);
+		this.currentGraph = null;
+	}
+	
+	this.removeDragElement();
+	this.removeListeners();
+	this.stopDrag();
+};
+
+/**
+ * Function: mouseDown
+ * 
+ * Returns the drop target for the given graph and coordinates. This
+ * implementation uses <mxGraph.getCellAt>.
+ * 
+ * To ignore popup menu events for a drag source, this function can be
+ * overridden as follows.
+ * 
+ * (code)
+ * var mouseDown = dragSource.mouseDown;
+ * 
+ * dragSource.mouseDown = function(evt)
+ * {
+ *   if (!mxEvent.isPopupTrigger(evt))
+ *   {
+ *     mouseDown.apply(this, arguments);
+ *   }
+ * };
+ * (end)
+ */
+mxDragSource.prototype.mouseDown = function(evt)
+{
+	if (this.enabled && !mxEvent.isConsumed(evt) && this.mouseMoveHandler == null)
+	{
+		this.startDrag(evt);
+		this.mouseMoveHandler = mxUtils.bind(this, this.mouseMove);
+		this.mouseUpHandler = mxUtils.bind(this, this.mouseUp);		
+		mxEvent.addGestureListeners(document, null, this.mouseMoveHandler, this.mouseUpHandler);
+		
+		if (mxClient.IS_TOUCH && !mxEvent.isMouseEvent(evt))
+		{
+			this.eventSource = mxEvent.getSource(evt);
+			mxEvent.addGestureListeners(this.eventSource, null, this.mouseMoveHandler, this.mouseUpHandler);
+		}
+	}
+};
+
+/**
+ * Function: startDrag
+ * 
+ * Creates the <dragElement> using <createDragElement>.
+ */
+mxDragSource.prototype.startDrag = function(evt)
+{
+	this.dragElement = this.createDragElement(evt);
+	this.dragElement.style.position = 'absolute';
+	this.dragElement.style.zIndex = this.dragElementZIndex;
+	mxUtils.setOpacity(this.dragElement, this.dragElementOpacity);
+};
+
+/**
+ * Function: stopDrag
+ * 
+ * Invokes <removeDragElement>.
+ */
+mxDragSource.prototype.stopDrag = function()
+{
+	// LATER: This used to have a mouse event. If that is still needed we need to add another
+	// final call to the DnD protocol to add a cleanup step in the case of escape press, which
+	// is not associated with a mouse event and which currently calles this method.
+	this.removeDragElement();
+};
+
+/**
+ * Function: removeDragElement
+ * 
+ * Removes and destroys the <dragElement>.
+ */
+mxDragSource.prototype.removeDragElement = function()
+{
+	if (this.dragElement != null)
+	{
+		if (this.dragElement.parentNode != null)
+		{
+			this.dragElement.parentNode.removeChild(this.dragElement);
+		}
+		
+		this.dragElement = null;
+	}
+};
+
+/**
+ * Function: graphContainsEvent
+ * 
+ * Returns true if the given graph contains the given event.
+ */
+mxDragSource.prototype.graphContainsEvent = function(graph, evt)
+{
+	var x = mxEvent.getClientX(evt);
+	var y = mxEvent.getClientY(evt);
+	var offset = mxUtils.getOffset(graph.container);
+	var origin = mxUtils.getScrollOrigin();
+
+	// Checks if event is inside the bounds of the graph container
+	return x >= offset.x - origin.x && y >= offset.y - origin.y &&
+		x <= offset.x - origin.x + graph.container.offsetWidth &&
+		y <= offset.y - origin.y + graph.container.offsetHeight;
+};
+
+/**
+ * Function: mouseMove
+ * 
+ * Gets the graph for the given event using <getGraphForEvent>, updates the
+ * <currentGraph>, calling <dragEnter> and <dragExit> on the new and old graph,
+ * respectively, and invokes <dragOver> if <currentGraph> is not null.
+ */
+mxDragSource.prototype.mouseMove = function(evt)
+{
+	var graph = this.getGraphForEvent(evt);
+	
+	// Checks if event is inside the bounds of the graph container
+	if (graph != null && !this.graphContainsEvent(graph, evt))
+	{
+		graph = null;
+	}
+
+	if (graph != this.currentGraph)
+	{
+		if (this.currentGraph != null)
+		{
+			this.dragExit(this.currentGraph, evt);
+		}
+		
+		this.currentGraph = graph;
+		
+		if (this.currentGraph != null)
+		{
+			this.dragEnter(this.currentGraph, evt);
+		}
+	}
+	
+	if (this.currentGraph != null)
+	{
+		this.dragOver(this.currentGraph, evt);
+	}
+
+	if (this.dragElement != null && (this.previewElement == null || this.previewElement.style.visibility != 'visible'))
+	{
+		var x = mxEvent.getClientX(evt);
+		var y = mxEvent.getClientY(evt);
+		
+		if (this.dragElement.parentNode == null)
+		{
+			document.body.appendChild(this.dragElement);
+		}
+
+		this.dragElement.style.visibility = 'visible';
+		
+		if (this.dragOffset != null)
+		{
+			x += this.dragOffset.x;
+			y += this.dragOffset.y;
+		}
+		
+		var offset = mxUtils.getDocumentScrollOrigin(document);
+		
+		this.dragElement.style.left = (x + offset.x) + 'px';
+		this.dragElement.style.top = (y + offset.y) + 'px';
+	}
+	else if (this.dragElement != null)
+	{
+		this.dragElement.style.visibility = 'hidden';
+	}
+	
+	mxEvent.consume(evt);
+};
+
+/**
+ * Function: mouseUp
+ * 
+ * Processes the mouse up event and invokes <drop>, <dragExit> and <stopDrag>
+ * as required.
+ */
+mxDragSource.prototype.mouseUp = function(evt)
+{
+	if (this.currentGraph != null)
+	{
+		if (this.currentPoint != null && (this.previewElement == null ||
+			this.previewElement.style.visibility != 'hidden'))
+		{
+			var scale = this.currentGraph.view.scale;
+			var tr = this.currentGraph.view.translate;
+			var x = this.currentPoint.x / scale - tr.x;
+			var y = this.currentPoint.y / scale - tr.y;
+			
+			this.drop(this.currentGraph, evt, this.currentDropTarget, x, y);
+		}
+		
+		this.dragExit(this.currentGraph);
+		this.currentGraph = null;
+	}
+
+	this.stopDrag();
+	this.removeListeners();
+	
+	mxEvent.consume(evt);
+};
+
+/**
+ * Function: removeListeners
+ * 
+ * Actives the given graph as a drop target.
+ */
+mxDragSource.prototype.removeListeners = function()
+{
+	if (this.eventSource != null)
+	{
+		mxEvent.removeGestureListeners(this.eventSource, null, this.mouseMoveHandler, this.mouseUpHandler);
+		this.eventSource = null;
+	}
+	
+	mxEvent.removeGestureListeners(document, null, this.mouseMoveHandler, this.mouseUpHandler);
+	this.mouseMoveHandler = null;
+	this.mouseUpHandler = null;
+};
+
+/**
+ * Function: dragEnter
+ * 
+ * Actives the given graph as a drop target.
+ */
+mxDragSource.prototype.dragEnter = function(graph, evt)
+{
+	graph.isMouseDown = true;
+	graph.isMouseTrigger = mxEvent.isMouseEvent(evt);
+	this.previewElement = this.createPreviewElement(graph);
+	
+	// Guide is only needed if preview element is used
+	if (this.isGuidesEnabled() && this.previewElement != null)
+	{
+		this.currentGuide = new mxGuide(graph, graph.graphHandler.getGuideStates());
+	}
+	
+	if (this.highlightDropTargets)
+	{
+		this.currentHighlight = new mxCellHighlight(graph, mxConstants.DROP_TARGET_COLOR);
+	}
+	
+	// Consumes all events in the current graph before they are fired
+	graph.addListener(mxEvent.FIRE_MOUSE_EVENT, this.eventConsumer);
+};
+
+/**
+ * Function: dragExit
+ * 
+ * Deactivates the given graph as a drop target.
+ */
+mxDragSource.prototype.dragExit = function(graph, evt)
+{
+	this.currentDropTarget = null;
+	this.currentPoint = null;
+	graph.isMouseDown = false;
+	
+	// Consumes all events in the current graph before they are fired
+	graph.removeListener(this.eventConsumer);
+	
+	if (this.previewElement != null)
+	{
+		if (this.previewElement.parentNode != null)
+		{
+			this.previewElement.parentNode.removeChild(this.previewElement);
+		}
+		
+		this.previewElement = null;
+	}
+	
+	if (this.currentGuide != null)
+	{
+		this.currentGuide.destroy();
+		this.currentGuide = null;
+	}
+	
+	if (this.currentHighlight != null)
+	{
+		this.currentHighlight.destroy();
+		this.currentHighlight = null;
+	}
+};
+
+/**
+ * Function: dragOver
+ * 
+ * Implements autoscroll, updates the <currentPoint>, highlights any drop
+ * targets and updates the preview.
+ */
+mxDragSource.prototype.dragOver = function(graph, evt)
+{
+	var offset = mxUtils.getOffset(graph.container);
+	var origin = mxUtils.getScrollOrigin(graph.container);
+	var x = mxEvent.getClientX(evt) - offset.x + origin.x - graph.panDx;
+	var y = mxEvent.getClientY(evt) - offset.y + origin.y - graph.panDy;
+
+	if (graph.autoScroll && (this.autoscroll == null || this.autoscroll))
+	{
+		graph.scrollPointToVisible(x, y, graph.autoExtend);
+	}
+
+	// Highlights the drop target under the mouse
+	if (this.currentHighlight != null && graph.isDropEnabled())
+	{
+		this.currentDropTarget = this.getDropTarget(graph, x, y, evt);
+		var state = graph.getView().getState(this.currentDropTarget);
+		this.currentHighlight.highlight(state);
+	}
+
+	// Updates the location of the preview
+	if (this.previewElement != null)
+	{
+		if (this.previewElement.parentNode == null)
+		{
+			graph.container.appendChild(this.previewElement);
+			
+			this.previewElement.style.zIndex = '3';
+			this.previewElement.style.position = 'absolute';
+		}
+		
+		var gridEnabled = this.isGridEnabled() && graph.isGridEnabledEvent(evt);
+		var hideGuide = true;
+
+		// Grid and guides
+		if (this.currentGuide != null && this.currentGuide.isEnabledForEvent(evt))
+		{
+			// LATER: HTML preview appears smaller than SVG preview
+			var w = parseInt(this.previewElement.style.width);
+			var h = parseInt(this.previewElement.style.height);
+			var bounds = new mxRectangle(0, 0, w, h);
+			var delta = new mxPoint(x, y);
+			delta = this.currentGuide.move(bounds, delta, gridEnabled);
+			hideGuide = false;
+			x = delta.x;
+			y = delta.y;
+		}
+		else if (gridEnabled)
+		{
+			var scale = graph.view.scale;
+			var tr = graph.view.translate;
+			var off = graph.gridSize / 2;
+			x = (graph.snap(x / scale - tr.x - off) + tr.x) * scale;
+			y = (graph.snap(y / scale - tr.y - off) + tr.y) * scale;
+		}
+		
+		if (this.currentGuide != null && hideGuide)
+		{
+			this.currentGuide.hide();
+		}
+		
+		if (this.previewOffset != null)
+		{
+			x += this.previewOffset.x;
+			y += this.previewOffset.y;
+		}
+
+		this.previewElement.style.left = Math.round(x) + 'px';
+		this.previewElement.style.top = Math.round(y) + 'px';
+		this.previewElement.style.visibility = 'visible';
+	}
+	
+	this.currentPoint = new mxPoint(x, y);
+};
+
+/**
+ * Function: drop
+ * 
+ * Returns the drop target for the given graph and coordinates. This
+ * implementation uses <mxGraph.getCellAt>.
+ */
+mxDragSource.prototype.drop = function(graph, evt, dropTarget, x, y)
+{
+	this.dropHandler(graph, evt, dropTarget, x, y);
+	
+	// Had to move this to after the insert because it will
+	// affect the scrollbars of the window in IE to try and
+	// make the complete container visible.
+	// LATER: Should be made optional.
+	if (graph.container.style.visibility != 'hidden')
+	{
+		graph.container.focus();
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxToolbar
+ * 
+ * Creates a toolbar inside a given DOM node. The toolbar may contain icons,
+ * buttons and combo boxes.
+ * 
+ * Event: mxEvent.SELECT
+ * 
+ * Fires when an item was selected in the toolbar. The <code>function</code>
+ * property contains the function that was selected in <selectMode>.
+ * 
+ * Constructor: mxToolbar
+ * 
+ * Constructs a toolbar in the specified container.
+ *
+ * Parameters:
+ *
+ * container - DOM node that contains the toolbar.
+ */
+function mxToolbar(container)
+{
+	this.container = container;
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxToolbar.prototype = new mxEventSource();
+mxToolbar.prototype.constructor = mxToolbar;
+
+/**
+ * Variable: container
+ * 
+ * Reference to the DOM nodes that contains the toolbar.
+ */
+mxToolbar.prototype.container = null;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies if events are handled. Default is true.
+ */
+mxToolbar.prototype.enabled = true;
+
+/**
+ * Variable: noReset
+ * 
+ * Specifies if <resetMode> requires a forced flag of true for resetting
+ * the current mode in the toolbar. Default is false. This is set to true
+ * if the toolbar item is double clicked to avoid a reset after a single
+ * use of the item.
+ */
+mxToolbar.prototype.noReset = false;
+
+/**
+ * Variable: updateDefaultMode
+ * 
+ * Boolean indicating if the default mode should be the last selected
+ * switch mode or the first inserted switch mode. Default is true, that
+ * is the last selected switch mode is the default mode. The default mode
+ * is the mode to be selected after a reset of the toolbar. If this is
+ * false, then the default mode is the first inserted mode item regardless
+ * of what was last selected. Otherwise, the selected item after a reset is
+ * the previously selected item.
+ */
+mxToolbar.prototype.updateDefaultMode = true;
+
+/**
+ * Function: addItem
+ * 
+ * Adds the given function as an image with the specified title and icon
+ * and returns the new image node.
+ * 
+ * Parameters:
+ * 
+ * title - Optional string that is used as the tooltip.
+ * icon - Optional URL of the image to be used. If no URL is given, then a
+ * button is created.
+ * funct - Function to execute on a mouse click.
+ * pressedIcon - Optional URL of the pressed image. Default is a gray
+ * background.
+ * style - Optional style classname. Default is mxToolbarItem.
+ * factoryMethod - Optional factory method for popup menu, eg.
+ * function(menu, evt, cell) { menu.addItem('Hello, World!'); }
+ */
+mxToolbar.prototype.addItem = function(title, icon, funct, pressedIcon, style, factoryMethod)
+{
+	var img = document.createElement((icon != null) ? 'img' : 'button');
+	var initialClassName = style || ((factoryMethod != null) ?
+			'mxToolbarMode' : 'mxToolbarItem');
+	img.className = initialClassName;
+	img.setAttribute('src', icon);
+	
+	if (title != null)
+	{
+		if (icon != null)
+		{
+			img.setAttribute('title', title);
+		}
+		else
+		{
+			mxUtils.write(img, title);
+		}
+	}
+	
+	this.container.appendChild(img);
+
+	// Invokes the function on a click on the toolbar item
+	if (funct != null)
+	{
+		mxEvent.addListener(img, 'click', funct);
+		
+		if (mxClient.IS_TOUCH)
+		{
+			mxEvent.addListener(img, 'touchend', funct);
+		}
+	}
+
+	var mouseHandler = mxUtils.bind(this, function(evt)
+	{
+		if (pressedIcon != null)
+		{
+			img.setAttribute('src', icon);
+		}
+		else
+		{
+			img.style.backgroundColor = '';
+		}
+	});
+
+	// Highlights the toolbar item with a gray background
+	// while it is being clicked with the mouse
+	mxEvent.addGestureListeners(img, mxUtils.bind(this, function(evt)
+	{
+		if (pressedIcon != null)
+		{
+			img.setAttribute('src', pressedIcon);
+		}
+		else
+		{
+			img.style.backgroundColor = 'gray';
+		}
+		
+		// Popup Menu
+		if (factoryMethod != null)
+		{
+			if (this.menu == null)
+			{
+				this.menu = new mxPopupMenu();
+				this.menu.init();
+			}
+			
+			var last = this.currentImg;
+			
+			if (this.menu.isMenuShowing())
+			{
+				this.menu.hideMenu();
+			}
+			
+			if (last != img)
+			{
+				// Redirects factory method to local factory method
+				this.currentImg = img;
+				this.menu.factoryMethod = factoryMethod;
+				
+				var point = new mxPoint(
+					img.offsetLeft,
+					img.offsetTop + img.offsetHeight);
+				this.menu.popup(point.x, point.y, null, evt);
+
+				// Sets and overrides to restore classname
+				if (this.menu.isMenuShowing())
+				{
+					img.className = initialClassName + 'Selected';
+					
+					this.menu.hideMenu = function()
+					{
+						mxPopupMenu.prototype.hideMenu.apply(this);
+						img.className = initialClassName;
+						this.currentImg = null;
+					};
+				}
+			}
+		}
+	}), null, mouseHandler);
+
+	mxEvent.addListener(img, 'mouseout', mouseHandler);
+	
+	return img;
+};
+
+/**
+ * Function: addCombo
+ * 
+ * Adds and returns a new SELECT element using the given style. The element
+ * is placed inside a DIV with the mxToolbarComboContainer style classname.
+ * 
+ * Parameters:
+ * 
+ * style - Optional style classname. Default is mxToolbarCombo.
+ */
+mxToolbar.prototype.addCombo = function(style)
+{
+	var div = document.createElement('div');
+	div.style.display = 'inline';
+	div.className = 'mxToolbarComboContainer';
+	
+	var select = document.createElement('select');
+	select.className = style || 'mxToolbarCombo';
+	div.appendChild(select);
+	
+	this.container.appendChild(div);
+	
+	return select;
+};
+
+/**
+ * Function: addCombo
+ * 
+ * Adds and returns a new SELECT element using the given title as the
+ * default element. The selection is reset to this element after each
+ * change.
+ * 
+ * Parameters:
+ * 
+ * title - String that specifies the title of the default element.
+ * style - Optional style classname. Default is mxToolbarCombo.
+ */
+mxToolbar.prototype.addActionCombo = function(title, style)
+{
+	var select = document.createElement('select');
+	select.className = style || 'mxToolbarCombo';
+	this.addOption(select, title, null);
+	
+	mxEvent.addListener(select, 'change', function(evt)
+	{
+		var value = select.options[select.selectedIndex];
+		select.selectedIndex = 0;
+		
+		if (value.funct != null)
+		{
+			value.funct(evt);
+		}
+	});
+	
+	this.container.appendChild(select);
+	
+	return select;
+};
+
+/**
+ * Function: addOption
+ * 
+ * Adds and returns a new OPTION element inside the given SELECT element.
+ * If the given value is a function then it is stored in the option's funct
+ * field.
+ * 
+ * Parameters:
+ * 
+ * combo - SELECT element that will contain the new entry.
+ * title - String that specifies the title of the option.
+ * value - Specifies the value associated with this option.
+ */
+mxToolbar.prototype.addOption = function(combo, title, value)
+{
+	var option = document.createElement('option');
+	mxUtils.writeln(option, title);
+	
+	if (typeof(value) == 'function')
+	{
+		option.funct = value;
+	}
+	else
+	{
+		option.setAttribute('value', value);
+	}
+	
+	combo.appendChild(option);
+	
+	return option;
+};
+
+/**
+ * Function: addSwitchMode
+ * 
+ * Adds a new selectable item to the toolbar. Only one switch mode item may
+ * be selected at a time. The currently selected item is the default item
+ * after a reset of the toolbar.
+ */
+mxToolbar.prototype.addSwitchMode = function(title, icon, funct, pressedIcon, style)
+{
+	var img = document.createElement('img');
+	img.initialClassName = style || 'mxToolbarMode';
+	img.className = img.initialClassName;
+	img.setAttribute('src', icon);
+	img.altIcon = pressedIcon;
+	
+	if (title != null)
+	{
+		img.setAttribute('title', title);
+	}
+	
+	mxEvent.addListener(img, 'click', mxUtils.bind(this, function(evt)
+	{
+		var tmp = this.selectedMode.altIcon;
+		
+		if (tmp != null)
+		{
+			this.selectedMode.altIcon = this.selectedMode.getAttribute('src');
+			this.selectedMode.setAttribute('src', tmp);
+		}
+		else
+		{
+			this.selectedMode.className = this.selectedMode.initialClassName;
+		}
+		
+		if (this.updateDefaultMode)
+		{
+			this.defaultMode = img;
+		}
+		
+		this.selectedMode = img;
+		
+		var tmp = img.altIcon;
+		
+		if (tmp != null)
+		{
+			img.altIcon = img.getAttribute('src');
+			img.setAttribute('src', tmp);
+		}
+		else
+		{
+			img.className = img.initialClassName+'Selected';
+		}
+		
+		this.fireEvent(new mxEventObject(mxEvent.SELECT));
+		funct();
+	}));
+	
+	this.container.appendChild(img);
+	
+	if (this.defaultMode == null)
+	{
+		this.defaultMode = img;
+		
+		// Function should fire only once so
+		// do not pass it with the select event
+		this.selectMode(img);
+		funct();
+	}
+	
+	return img;
+};
+
+/**
+ * Function: addMode
+ * 
+ * Adds a new item to the toolbar. The selection is typically reset after
+ * the item has been consumed, for example by adding a new vertex to the
+ * graph. The reset is not carried out if the item is double clicked.
+ * 
+ * The function argument uses the following signature: funct(evt, cell) where
+ * evt is the native mouse event and cell is the cell under the mouse.
+ */
+mxToolbar.prototype.addMode = function(title, icon, funct, pressedIcon, style, toggle)
+{
+	toggle = (toggle != null) ? toggle : true;
+	var img = document.createElement((icon != null) ? 'img' : 'button');
+	
+	img.initialClassName = style || 'mxToolbarMode';
+	img.className = img.initialClassName;
+	img.setAttribute('src', icon);
+	img.altIcon = pressedIcon;
+
+	if (title != null)
+	{
+		img.setAttribute('title', title);
+	}
+	
+	if (this.enabled && toggle)
+	{
+		mxEvent.addListener(img, 'click', mxUtils.bind(this, function(evt)
+		{
+			this.selectMode(img, funct);
+			this.noReset = false;
+		}));
+		
+		mxEvent.addListener(img, 'dblclick', mxUtils.bind(this, function(evt)
+		{
+			this.selectMode(img, funct);
+			this.noReset = true;
+		}));
+		
+		if (this.defaultMode == null)
+		{
+			this.defaultMode = img;
+			this.defaultFunction = funct;
+			this.selectMode(img, funct);
+		}
+	}
+
+	this.container.appendChild(img);					
+
+	return img;
+};
+
+/**
+ * Function: selectMode
+ * 
+ * Resets the state of the previously selected mode and displays the given
+ * DOM node as selected. This function fires a select event with the given
+ * function as a parameter.
+ */
+mxToolbar.prototype.selectMode = function(domNode, funct)
+{
+	if (this.selectedMode != domNode)
+	{
+		if (this.selectedMode != null)
+		{
+			var tmp = this.selectedMode.altIcon;
+			
+			if (tmp != null)
+			{
+				this.selectedMode.altIcon = this.selectedMode.getAttribute('src');
+				this.selectedMode.setAttribute('src', tmp);
+			}
+			else
+			{
+				this.selectedMode.className = this.selectedMode.initialClassName;
+			}
+		}
+		
+		this.selectedMode = domNode;
+		var tmp = this.selectedMode.altIcon;
+		
+		if (tmp != null)
+		{
+			this.selectedMode.altIcon = this.selectedMode.getAttribute('src');
+			this.selectedMode.setAttribute('src', tmp);
+		}
+		else
+		{
+			this.selectedMode.className = this.selectedMode.initialClassName+'Selected';
+		}
+		
+		this.fireEvent(new mxEventObject(mxEvent.SELECT, "function", funct));
+	}
+};
+
+/**
+ * Function: resetMode
+ * 
+ * Selects the default mode and resets the state of the previously selected
+ * mode.
+ */
+mxToolbar.prototype.resetMode = function(forced)
+{
+	if ((forced || !this.noReset) && this.selectedMode != this.defaultMode)
+	{
+		// The last selected switch mode will be activated
+		// so the function was already executed and is
+		// no longer required here
+		this.selectMode(this.defaultMode, this.defaultFunction);
+	}
+};
+
+/**
+ * Function: addSeparator
+ * 
+ * Adds the specifies image as a separator.
+ * 
+ * Parameters:
+ * 
+ * icon - URL of the separator icon.
+ */
+mxToolbar.prototype.addSeparator = function(icon)
+{
+	return this.addItem(null, icon, null);
+};
+
+/**
+ * Function: addBreak
+ * 
+ * Adds a break to the container.
+ */
+mxToolbar.prototype.addBreak = function()
+{
+	mxUtils.br(this.container);
+};
+
+/**
+ * Function: addLine
+ * 
+ * Adds a horizontal line to the container.
+ */
+mxToolbar.prototype.addLine = function()
+{
+	var hr = document.createElement('hr');
+	
+	hr.style.marginRight = '6px';
+	hr.setAttribute('size', '1');
+	
+	this.container.appendChild(hr);
+};
+
+/**
+ * Function: destroy
+ * 
+ * Removes the toolbar and all its associated resources.
+ */
+mxToolbar.prototype.destroy = function ()
+{
+	mxEvent.release(this.container);
+	this.container = null;
+	this.defaultMode = null;
+	this.defaultFunction = null;
+	this.selectedMode = null;
+	
+	if (this.menu != null)
+	{
+		this.menu.destroy();
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxUndoableEdit
+ * 
+ * Implements a composite undoable edit. Here is an example for a custom change
+ * which gets executed via the model:
+ * 
+ * (code)
+ * function CustomChange(model, name)
+ * {
+ *   this.model = model;
+ *   this.name = name;
+ *   this.previous = name;
+ * };
+ * 
+ * CustomChange.prototype.execute = function()
+ * {
+ *   var tmp = this.model.name;
+ *   this.model.name = this.previous;
+ *   this.previous = tmp;
+ * };
+ * 
+ * var name = prompt('Enter name');
+ * graph.model.execute(new CustomChange(graph.model, name));
+ * (end)
+ * 
+ * Event: mxEvent.EXECUTED
+ * 
+ * Fires between START_EDIT and END_EDIT after an atomic change was executed.
+ * The <code>change</code> property contains the change that was executed.
+ * 
+ * Event: mxEvent.START_EDIT
+ * 
+ * Fires before a set of changes will be executed in <undo> or <redo>.
+ * This event contains no properties.
+ * 
+ * Event: mxEvent.END_EDIT
+ *
+ * Fires after a set of changeswas executed in <undo> or <redo>.
+ * This event contains no properties.
+ * 
+ * Constructor: mxUndoableEdit
+ * 
+ * Constructs a new undoable edit for the given source.
+ */
+function mxUndoableEdit(source, significant)
+{
+	this.source = source;
+	this.changes = [];
+	this.significant = (significant != null) ? significant : true;
+};
+
+/**
+ * Variable: source
+ * 
+ * Specifies the source of the edit.
+ */
+mxUndoableEdit.prototype.source = null;
+
+/**
+ * Variable: changes
+ * 
+ * Array that contains the changes that make up this edit. The changes are
+ * expected to either have an undo and redo function, or an execute
+ * function. Default is an empty array.
+ */
+mxUndoableEdit.prototype.changes = null;
+
+/**
+ * Variable: significant
+ * 
+ * Specifies if the undoable change is significant.
+ * Default is true.
+ */
+mxUndoableEdit.prototype.significant = null;
+
+/**
+ * Variable: undone
+ * 
+ * Specifies if this edit has been undone. Default is false.
+ */
+mxUndoableEdit.prototype.undone = false;
+
+/**
+ * Variable: redone
+ * 
+ * Specifies if this edit has been redone. Default is false.
+ */
+mxUndoableEdit.prototype.redone = false;
+
+/**
+ * Function: isEmpty
+ * 
+ * Returns true if the this edit contains no changes.
+ */
+mxUndoableEdit.prototype.isEmpty = function()
+{
+	return this.changes.length == 0;
+};
+
+/**
+ * Function: isSignificant
+ * 
+ * Returns <significant>.
+ */
+mxUndoableEdit.prototype.isSignificant = function()
+{
+	return this.significant;
+};
+
+/**
+ * Function: add
+ * 
+ * Adds the specified change to this edit. The change is an object that is
+ * expected to either have an undo and redo, or an execute function.
+ */
+mxUndoableEdit.prototype.add = function(change)
+{
+	this.changes.push(change);
+};
+
+/**
+ * Function: notify
+ * 
+ * Hook to notify any listeners of the changes after an <undo> or <redo>
+ * has been carried out. This implementation is empty.
+ */
+mxUndoableEdit.prototype.notify = function() { };
+
+/**
+ * Function: die
+ * 
+ * Hook to free resources after the edit has been removed from the command
+ * history. This implementation is empty.
+ */
+mxUndoableEdit.prototype.die = function() { };
+
+/**
+ * Function: undo
+ * 
+ * Undoes all changes in this edit.
+ */
+mxUndoableEdit.prototype.undo = function()
+{
+	if (!this.undone)
+	{
+		this.source.fireEvent(new mxEventObject(mxEvent.START_EDIT));
+		var count = this.changes.length;
+		
+		for (var i = count - 1; i >= 0; i--)
+		{
+			var change = this.changes[i];
+			
+			if (change.execute != null)
+			{
+				change.execute();
+			}
+			else if (change.undo != null)
+			{
+				change.undo();
+			}
+			
+			// New global executed event
+			this.source.fireEvent(new mxEventObject(mxEvent.EXECUTED, 'change', change));
+		}
+		
+		this.undone = true;
+		this.redone = false;
+		this.source.fireEvent(new mxEventObject(mxEvent.END_EDIT));
+	}
+	
+	this.notify();
+};
+
+/**
+ * Function: redo
+ * 
+ * Redoes all changes in this edit.
+ */
+mxUndoableEdit.prototype.redo = function()
+{
+	if (!this.redone)
+	{
+		this.source.fireEvent(new mxEventObject(mxEvent.START_EDIT));
+		var count = this.changes.length;
+		
+		for (var i = 0; i < count; i++)
+		{
+			var change = this.changes[i];
+			
+			if (change.execute != null)
+			{
+				change.execute();
+			}
+			else if (change.redo != null)
+			{
+				change.redo();
+			}
+			
+			// New global executed event
+			this.source.fireEvent(new mxEventObject(mxEvent.EXECUTED, 'change', change));
+		}
+		
+		this.undone = false;
+		this.redone = true;
+		this.source.fireEvent(new mxEventObject(mxEvent.END_EDIT));
+	}
+	
+	this.notify();
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxUndoManager
+ *
+ * Implements a command history. When changing the graph model, an
+ * <mxUndoableChange> object is created at the start of the transaction (when
+ * model.beginUpdate is called). All atomic changes are then added to this
+ * object until the last model.endUpdate call, at which point the
+ * <mxUndoableEdit> is dispatched in an event, and added to the history inside
+ * <mxUndoManager>. This is done by an event listener in
+ * <mxEditor.installUndoHandler>.
+ * 
+ * Each atomic change of the model is represented by an object (eg.
+ * <mxRootChange>, <mxChildChange>, <mxTerminalChange> etc) which contains the
+ * complete undo information. The <mxUndoManager> also listens to the
+ * <mxGraphView> and stores it's changes to the current root as insignificant
+ * undoable changes, so that drilling (step into, step up) is undone.
+ * 
+ * This means when you execute an atomic change on the model, then change the
+ * current root on the view and click undo, the change of the root will be
+ * undone together with the change of the model so that the display represents
+ * the state at which the model was changed. However, these changes are not
+ * transmitted for sharing as they do not represent a state change.
+ *
+ * Example:
+ * 
+ * When adding an undo manager to a graph, make sure to add it
+ * to the model and the view as well to maintain a consistent
+ * display across multiple undo/redo steps.
+ *
+ * (code)
+ * var undoManager = new mxUndoManager();
+ * var listener = function(sender, evt)
+ * {
+ *   undoManager.undoableEditHappened(evt.getProperty('edit'));
+ * };
+ * graph.getModel().addListener(mxEvent.UNDO, listener);
+ * graph.getView().addListener(mxEvent.UNDO, listener);
+ * (end)
+ * 
+ * The code creates a function that informs the undoManager
+ * of an undoable edit and binds it to the undo event of
+ * <mxGraphModel> and <mxGraphView> using
+ * <mxEventSource.addListener>.
+ * 
+ * Event: mxEvent.CLEAR
+ * 
+ * Fires after <clear> was invoked. This event has no properties.
+ * 
+ * Event: mxEvent.UNDO
+ * 
+ * Fires afer a significant edit was undone in <undo>. The <code>edit</code>
+ * property contains the <mxUndoableEdit> that was undone.
+ * 
+ * Event: mxEvent.REDO
+ * 
+ * Fires afer a significant edit was redone in <redo>. The <code>edit</code>
+ * property contains the <mxUndoableEdit> that was redone.
+ * 
+ * Event: mxEvent.ADD
+ * 
+ * Fires after an undoable edit was added to the history. The <code>edit</code>
+ * property contains the <mxUndoableEdit> that was added.
+ * 
+ * Constructor: mxUndoManager
+ *
+ * Constructs a new undo manager with the given history size. If no history
+ * size is given, then a default size of 100 steps is used.
+ */
+function mxUndoManager(size)
+{
+	this.size = (size != null) ? size : 100;
+	this.clear();
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxUndoManager.prototype = new mxEventSource();
+mxUndoManager.prototype.constructor = mxUndoManager;
+
+/**
+ * Variable: size
+ * 
+ * Maximum command history size. 0 means unlimited history. Default is
+ * 100.
+ */
+mxUndoManager.prototype.size = null;
+
+/**
+ * Variable: history
+ * 
+ * Array that contains the steps of the command history.
+ */
+mxUndoManager.prototype.history = null;
+
+/**
+ * Variable: indexOfNextAdd
+ * 
+ * Index of the element to be added next.
+ */
+mxUndoManager.prototype.indexOfNextAdd = 0;
+
+/**
+ * Function: isEmpty
+ * 
+ * Returns true if the history is empty.
+ */
+mxUndoManager.prototype.isEmpty = function()
+{
+	return this.history.length == 0;
+};
+
+/**
+ * Function: clear
+ * 
+ * Clears the command history.
+ */
+mxUndoManager.prototype.clear = function()
+{
+	this.history = [];
+	this.indexOfNextAdd = 0;
+	this.fireEvent(new mxEventObject(mxEvent.CLEAR));
+};
+
+/**
+ * Function: canUndo
+ * 
+ * Returns true if an undo is possible.
+ */
+mxUndoManager.prototype.canUndo = function()
+{
+	return this.indexOfNextAdd > 0;
+};
+
+/**
+ * Function: undo
+ * 
+ * Undoes the last change.
+ */
+mxUndoManager.prototype.undo = function()
+{
+    while (this.indexOfNextAdd > 0)
+    {
+        var edit = this.history[--this.indexOfNextAdd];
+        edit.undo();
+
+		if (edit.isSignificant())
+        {
+        	this.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit));
+            break;
+        }
+    }
+};
+
+/**
+ * Function: canRedo
+ * 
+ * Returns true if a redo is possible.
+ */
+mxUndoManager.prototype.canRedo = function()
+{
+	return this.indexOfNextAdd < this.history.length;
+};
+
+/**
+ * Function: redo
+ * 
+ * Redoes the last change.
+ */
+mxUndoManager.prototype.redo = function()
+{
+    var n = this.history.length;
+    
+    while (this.indexOfNextAdd < n)
+    {
+        var edit =  this.history[this.indexOfNextAdd++];
+        edit.redo();
+        
+        if (edit.isSignificant())
+        {
+        	this.fireEvent(new mxEventObject(mxEvent.REDO, 'edit', edit));
+            break;
+        }
+    }
+};
+
+/**
+ * Function: undoableEditHappened
+ * 
+ * Method to be called to add new undoable edits to the <history>.
+ */
+mxUndoManager.prototype.undoableEditHappened = function(undoableEdit)
+{
+	this.trim();
+	
+	if (this.size > 0 &&
+		this.size == this.history.length)
+	{
+		this.history.shift();
+	}
+	
+	this.history.push(undoableEdit);
+	this.indexOfNextAdd = this.history.length;
+	this.fireEvent(new mxEventObject(mxEvent.ADD, 'edit', undoableEdit));
+};
+
+/**
+ * Function: trim
+ * 
+ * Removes all pending steps after <indexOfNextAdd> from the history,
+ * invoking die on each edit. This is called from <undoableEditHappened>.
+ */
+mxUndoManager.prototype.trim = function()
+{
+	if (this.history.length > this.indexOfNextAdd)
+	{
+		var edits = this.history.splice(this.indexOfNextAdd,
+			this.history.length - this.indexOfNextAdd);
+			
+		for (var i = 0; i < edits.length; i++)
+		{
+			edits[i].die();
+		}
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ *
+ * Class: mxUrlConverter
+ * 
+ * Converts relative and absolute URLs to absolute URLs with protocol and domain.
+ */
+var mxUrlConverter = function()
+{
+	// Empty constructor
+};
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies if the converter is enabled. Default is true.
+ */
+mxUrlConverter.prototype.enabled = true;
+
+/**
+ * Variable: baseUrl
+ * 
+ * Specifies the base URL to be used as a prefix for relative URLs.
+ */
+mxUrlConverter.prototype.baseUrl = null;
+
+/**
+ * Variable: baseDomain
+ * 
+ * Specifies the base domain to be used as a prefix for absolute URLs.
+ */
+mxUrlConverter.prototype.baseDomain = null;
+
+/**
+ * Function: updateBaseUrl
+ * 
+ * Private helper function to update the base URL.
+ */
+mxUrlConverter.prototype.updateBaseUrl = function()
+{
+	this.baseDomain = location.protocol + '//' + location.host;
+	this.baseUrl = this.baseDomain + location.pathname;
+	var tmp = this.baseUrl.lastIndexOf('/');
+	
+	// Strips filename etc
+	if (tmp > 0)
+	{
+		this.baseUrl = this.baseUrl.substring(0, tmp + 1);
+	}
+};
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns <enabled>.
+ */
+mxUrlConverter.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ * 
+ * Sets <enabled>.
+ */
+mxUrlConverter.prototype.setEnabled = function(value)
+{
+	this.enabled = value;
+};
+
+/**
+ * Function: getBaseUrl
+ * 
+ * Returns <baseUrl>.
+ */
+mxUrlConverter.prototype.getBaseUrl = function()
+{
+	return this.baseUrl;
+};
+
+/**
+ * Function: setBaseUrl
+ * 
+ * Sets <baseUrl>.
+ */
+mxUrlConverter.prototype.setBaseUrl = function(value)
+{
+	this.baseUrl = value;
+};
+
+/**
+ * Function: getBaseDomain
+ * 
+ * Returns <baseDomain>.
+ */
+mxUrlConverter.prototype.getBaseDomain = function()
+{
+	return this.baseDomain;
+},
+
+/**
+ * Function: setBaseDomain
+ * 
+ * Sets <baseDomain>.
+ */
+mxUrlConverter.prototype.setBaseDomain = function(value)
+{
+	this.baseDomain = value;
+},
+
+/**
+ * Function: isRelativeUrl
+ * 
+ * Returns true if the given URL is relative.
+ */
+mxUrlConverter.prototype.isRelativeUrl = function(url)
+{
+	return url.substring(0, 2) != '//' && url.substring(0, 7) != 'http://' && url.substring(0, 8) != 'https://' && url.substring(0, 10) != 'data:image';
+};
+
+/**
+ * Function: convert
+ * 
+ * Converts the given URL to an absolute URL with protol and domain.
+ * Relative URLs are first converted to absolute URLs.
+ */
+mxUrlConverter.prototype.convert = function(url)
+{
+	if (this.isEnabled() && this.isRelativeUrl(url))
+	{
+		if (this.getBaseUrl() == null)
+		{
+			this.updateBaseUrl();
+		}
+		
+		if (url.charAt(0) == '/')
+		{
+			url = this.getBaseDomain() + url;
+		}
+		else
+		{
+			url = this.getBaseUrl() + url;
+		}
+	}
+	
+	return url;
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxPanningManager
+ *
+ * Implements a handler for panning.
+ */
+function mxPanningManager(graph)
+{
+	this.thread = null;
+	this.active = false;
+	this.tdx = 0;
+	this.tdy = 0;
+	this.t0x = 0;
+	this.t0y = 0;
+	this.dx = 0;
+	this.dy = 0;
+	this.scrollbars = false;
+	this.scrollLeft = 0;
+	this.scrollTop = 0;
+	
+	this.mouseListener =
+	{
+	    mouseDown: function(sender, me) { },
+	    mouseMove: function(sender, me) { },
+	    mouseUp: mxUtils.bind(this, function(sender, me)
+	    {
+	    	if (this.active)
+	    	{
+	    		this.stop();
+	    	}
+	    })
+	};
+	
+	graph.addMouseListener(this.mouseListener);
+	
+	// Stops scrolling on every mouseup anywhere in the document
+	mxEvent.addListener(document, 'mouseup', mxUtils.bind(this, function()
+	{
+    	if (this.active)
+    	{
+    		this.stop();
+    	}
+	}));
+	
+	var createThread = mxUtils.bind(this, function()
+	{
+    	this.scrollbars = mxUtils.hasScrollbars(graph.container);
+    	this.scrollLeft = graph.container.scrollLeft;
+    	this.scrollTop = graph.container.scrollTop;
+
+    	return window.setInterval(mxUtils.bind(this, function()
+		{
+			this.tdx -= this.dx;
+			this.tdy -= this.dy;
+
+			if (this.scrollbars)
+			{
+				var left = -graph.container.scrollLeft - Math.ceil(this.dx);
+				var top = -graph.container.scrollTop - Math.ceil(this.dy);
+				graph.panGraph(left, top);
+				graph.panDx = this.scrollLeft - graph.container.scrollLeft;
+				graph.panDy = this.scrollTop - graph.container.scrollTop;
+				graph.fireEvent(new mxEventObject(mxEvent.PAN));
+				// TODO: Implement graph.autoExtend
+			}
+			else
+			{
+				graph.panGraph(this.getDx(), this.getDy());
+			}
+		}), this.delay);
+	});
+	
+	this.isActive = function()
+	{
+		return active;
+	};
+	
+	this.getDx = function()
+	{
+		return Math.round(this.tdx);
+	};
+	
+	this.getDy = function()
+	{
+		return Math.round(this.tdy);
+	};
+	
+	this.start = function()
+	{
+		this.t0x = graph.view.translate.x;
+		this.t0y = graph.view.translate.y;
+		this.active = true;
+	};
+	
+	this.panTo = function(x, y, w, h)
+	{
+		if (!this.active)
+		{
+			this.start();
+		}
+		
+    	this.scrollLeft = graph.container.scrollLeft;
+    	this.scrollTop = graph.container.scrollTop;
+		
+		w = (w != null) ? w : 0;
+		h = (h != null) ? h : 0;
+		
+		var c = graph.container;
+		this.dx = x + w - c.scrollLeft - c.clientWidth;
+		
+		if (this.dx < 0 && Math.abs(this.dx) < this.border)
+		{
+			this.dx = this.border + this.dx;
+		}
+		else if (this.handleMouseOut)
+		{
+			this.dx = Math.max(this.dx, 0);
+		}
+		else
+		{
+			this.dx = 0;
+		}
+		
+		if (this.dx == 0)
+		{
+			this.dx = x - c.scrollLeft;
+			
+			if (this.dx > 0 && this.dx < this.border)
+			{
+				this.dx = this.dx - this.border;
+			}
+			else if (this.handleMouseOut)
+			{
+				this.dx = Math.min(0, this.dx);
+			}
+			else
+			{
+				this.dx = 0;
+			}
+		}
+		
+		this.dy = y + h - c.scrollTop - c.clientHeight;
+
+		if (this.dy < 0 && Math.abs(this.dy) < this.border)
+		{
+			this.dy = this.border + this.dy;
+		}
+		else if (this.handleMouseOut)
+		{
+			this.dy = Math.max(this.dy, 0);
+		}
+		else
+		{
+			this.dy = 0;
+		}
+		
+		if (this.dy == 0)
+		{
+			this.dy = y - c.scrollTop;
+			
+			if (this.dy > 0 && this.dy < this.border)
+			{
+				this.dy = this.dy - this.border;
+			}
+			else if (this.handleMouseOut)
+			{
+				this.dy = Math.min(0, this.dy);
+			} 
+			else
+			{
+				this.dy = 0;
+			}
+		}
+		
+		if (this.dx != 0 || this.dy != 0)
+		{
+			this.dx *= this.damper;
+			this.dy *= this.damper;
+			
+			if (this.thread == null)
+			{
+				this.thread = createThread();
+			}
+		}
+		else if (this.thread != null)
+		{
+			window.clearInterval(this.thread);
+			this.thread = null;
+		}
+	};
+	
+	this.stop = function()
+	{
+		if (this.active)
+		{
+			this.active = false;
+		
+			if (this.thread != null)
+	    	{
+				window.clearInterval(this.thread);
+				this.thread = null;
+	    	}
+			
+			this.tdx = 0;
+			this.tdy = 0;
+			
+			if (!this.scrollbars)
+			{
+				var px = graph.panDx;
+				var py = graph.panDy;
+		    	
+		    	if (px != 0 || py != 0)
+		    	{
+		    		graph.panGraph(0, 0);
+			    	graph.view.setTranslate(this.t0x + px / graph.view.scale, this.t0y + py / graph.view.scale);
+		    	}
+			}
+			else
+			{
+				graph.panDx = 0;
+				graph.panDy = 0;
+				graph.fireEvent(new mxEventObject(mxEvent.PAN));
+			}
+		}
+	};
+	
+	this.destroy = function()
+	{
+		graph.removeMouseListener(this.mouseListener);
+	};
+};
+
+/**
+ * Variable: damper
+ * 
+ * Damper value for the panning. Default is 1/6.
+ */
+mxPanningManager.prototype.damper = 1/6;
+
+/**
+ * Variable: delay
+ * 
+ * Delay in milliseconds for the panning. Default is 10.
+ */
+mxPanningManager.prototype.delay = 10;
+
+/**
+ * Variable: handleMouseOut
+ * 
+ * Specifies if mouse events outside of the component should be handled. Default is true. 
+ */
+mxPanningManager.prototype.handleMouseOut = true;
+
+/**
+ * Variable: border
+ * 
+ * Border to handle automatic panning inside the component. Default is 0 (disabled).
+ */
+mxPanningManager.prototype.border = 0;
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxPopupMenu
+ * 
+ * Basic popup menu. To add a vertical scrollbar to a given submenu, the
+ * following code can be used.
+ * 
+ * (code)
+ * var mxPopupMenuShowMenu = mxPopupMenu.prototype.showMenu;
+ * mxPopupMenu.prototype.showMenu = function()
+ * {
+ *   mxPopupMenuShowMenu.apply(this, arguments);
+ *   
+ *   this.div.style.overflowY = 'auto';
+ *   this.div.style.overflowX = 'hidden';
+ *   this.div.style.maxHeight = '160px';
+ * };
+ * (end)
+ * 
+ * Constructor: mxPopupMenu
+ * 
+ * Constructs a popupmenu.
+ * 
+ * Event: mxEvent.SHOW
+ *
+ * Fires after the menu has been shown in <popup>.
+ */
+function mxPopupMenu(factoryMethod)
+{
+	this.factoryMethod = factoryMethod;
+	
+	if (factoryMethod != null)
+	{
+		this.init();
+	}
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxPopupMenu.prototype = new mxEventSource();
+mxPopupMenu.prototype.constructor = mxPopupMenu;
+
+/**
+ * Variable: submenuImage
+ * 
+ * URL of the image to be used for the submenu icon.
+ */
+mxPopupMenu.prototype.submenuImage = mxClient.imageBasePath + '/submenu.gif';
+
+/**
+ * Variable: zIndex
+ * 
+ * Specifies the zIndex for the popupmenu and its shadow. Default is 1006.
+ */
+mxPopupMenu.prototype.zIndex = 10006;
+
+/**
+ * Variable: factoryMethod
+ * 
+ * Function that is used to create the popup menu. The function takes the
+ * current panning handler, the <mxCell> under the mouse and the mouse
+ * event that triggered the call as arguments.
+ */
+mxPopupMenu.prototype.factoryMethod = null;
+
+/**
+ * Variable: useLeftButtonForPopup
+ * 
+ * Specifies if popupmenus should be activated by clicking the left mouse
+ * button. Default is false.
+ */
+mxPopupMenu.prototype.useLeftButtonForPopup = false;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies if events are handled. Default is true.
+ */
+mxPopupMenu.prototype.enabled = true;
+
+/**
+ * Variable: itemCount
+ * 
+ * Contains the number of times <addItem> has been called for a new menu.
+ */
+mxPopupMenu.prototype.itemCount = 0;
+
+/**
+ * Variable: autoExpand
+ * 
+ * Specifies if submenus should be expanded on mouseover. Default is false.
+ */
+mxPopupMenu.prototype.autoExpand = false;
+
+/**
+ * Variable: smartSeparators
+ * 
+ * Specifies if separators should only be added if a menu item follows them.
+ * Default is false.
+ */
+mxPopupMenu.prototype.smartSeparators = false;
+
+/**
+ * Variable: labels
+ * 
+ * Specifies if any labels should be visible. Default is true.
+ */
+mxPopupMenu.prototype.labels = true;
+
+/**
+ * Function: init
+ * 
+ * Initializes the shapes required for this vertex handler.
+ */
+mxPopupMenu.prototype.init = function()
+{
+	// Adds the inner table
+	this.table = document.createElement('table');
+	this.table.className = 'mxPopupMenu';
+	
+	this.tbody = document.createElement('tbody');
+	this.table.appendChild(this.tbody);
+
+	// Adds the outer div
+	this.div = document.createElement('div');
+	this.div.className = 'mxPopupMenu';
+	this.div.style.display = 'inline';
+	this.div.style.zIndex = this.zIndex;
+	this.div.appendChild(this.table);
+
+	// Disables the context menu on the outer div
+	mxEvent.disableContextMenu(this.div);
+};
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxPopupMenu.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+	
+/**
+ * Function: setEnabled
+ * 
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ */
+mxPopupMenu.prototype.setEnabled = function(enabled)
+{
+	this.enabled = enabled;
+};
+
+/**
+ * Function: isPopupTrigger
+ * 
+ * Returns true if the given event is a popupmenu trigger for the optional
+ * given cell.
+ * 
+ * Parameters:
+ * 
+ * me - <mxMouseEvent> that represents the mouse event.
+ */
+mxPopupMenu.prototype.isPopupTrigger = function(me)
+{
+	return me.isPopupTrigger() || (this.useLeftButtonForPopup && mxEvent.isLeftMouseButton(me.getEvent()));
+};
+
+/**
+ * Function: addItem
+ * 
+ * Adds the given item to the given parent item. If no parent item is specified
+ * then the item is added to the top-level menu. The return value may be used
+ * as the parent argument, ie. as a submenu item. The return value is the table
+ * row that represents the item.
+ * 
+ * Paramters:
+ * 
+ * title - String that represents the title of the menu item.
+ * image - Optional URL for the image icon.
+ * funct - Function associated that takes a mouseup or touchend event.
+ * parent - Optional item returned by <addItem>.
+ * iconCls - Optional string that represents the CSS class for the image icon.
+ * IconsCls is ignored if image is given.
+ * enabled - Optional boolean indicating if the item is enabled. Default is true.
+ * active - Optional boolean indicating if the menu should implement any event handling.
+ * Default is true.
+ */
+mxPopupMenu.prototype.addItem = function(title, image, funct, parent, iconCls, enabled, active)
+{
+	parent = parent || this;
+	this.itemCount++;
+	
+	// Smart separators only added if element contains items
+	if (parent.willAddSeparator)
+	{
+		if (parent.containsItems)
+		{
+			this.addSeparator(parent, true);
+		}
+
+		parent.willAddSeparator = false;
+	}
+
+	parent.containsItems = true;
+	var tr = document.createElement('tr');
+	tr.className = 'mxPopupMenuItem';
+	var col1 = document.createElement('td');
+	col1.className = 'mxPopupMenuIcon';
+
+	// Adds the given image into the first column
+	if (image != null)
+	{
+		var img = document.createElement('img');
+		img.src = image;
+		col1.appendChild(img);
+	}
+	else if (iconCls != null)
+	{
+		var div = document.createElement('div');
+		div.className = iconCls;
+		col1.appendChild(div);
+	}
+	
+	tr.appendChild(col1);
+	
+	if (this.labels)
+	{
+		var col2 = document.createElement('td');
+		col2.className = 'mxPopupMenuItem' +
+			((enabled != null && !enabled) ? ' mxDisabled' : '');
+		
+		mxUtils.write(col2, title);
+		col2.align = 'left';
+		tr.appendChild(col2);
+	
+		var col3 = document.createElement('td');
+		col3.className = 'mxPopupMenuItem' +
+			((enabled != null && !enabled) ? ' mxDisabled' : '');
+		col3.style.paddingRight = '6px';
+		col3.style.textAlign = 'right';
+		
+		tr.appendChild(col3);
+		
+		if (parent.div == null)
+		{
+			this.createSubmenu(parent);
+		}
+	}
+	
+	parent.tbody.appendChild(tr);
+
+	if (active != false && enabled != false)
+	{
+		var currentSelection = null;
+		
+		mxEvent.addGestureListeners(tr,
+			mxUtils.bind(this, function(evt)
+			{
+				this.eventReceiver = tr;
+				
+				if (parent.activeRow != tr && parent.activeRow != parent)
+				{
+					if (parent.activeRow != null && parent.activeRow.div.parentNode != null)
+					{
+						this.hideSubmenu(parent);
+					}
+					
+					if (tr.div != null)
+					{
+						this.showSubmenu(parent, tr);
+						parent.activeRow = tr;
+					}
+				}
+				
+				// Workaround for lost current selection in page because of focus in IE
+				if (mxClient.IS_QUIRKS || document.documentMode == 8)
+				{
+					currentSelection = document.selection.createRange();
+				}
+				
+				mxEvent.consume(evt);
+			}),
+			mxUtils.bind(this, function(evt)
+			{
+				if (parent.activeRow != tr && parent.activeRow != parent)
+				{
+					if (parent.activeRow != null && parent.activeRow.div.parentNode != null)
+					{
+						this.hideSubmenu(parent);
+					}
+					
+					if (this.autoExpand && tr.div != null)
+					{
+						this.showSubmenu(parent, tr);
+						parent.activeRow = tr;
+					}
+				}
+		
+				// Sets hover style because TR in IE doesn't have hover
+				tr.className = 'mxPopupMenuItemHover';
+			}),
+			mxUtils.bind(this, function(evt)
+			{
+				// EventReceiver avoids clicks on a submenu item
+				// which has just been shown in the mousedown
+				if (this.eventReceiver == tr)
+				{
+					if (parent.activeRow != tr)
+					{
+						this.hideMenu();
+					}
+					
+					// Workaround for lost current selection in page because of focus in IE
+					if (currentSelection != null)
+					{
+						// Workaround for "unspecified error" in IE8 standards
+						try
+						{
+							currentSelection.select();
+						}
+						catch (e)
+						{
+							// ignore
+						}
+
+						currentSelection = null;
+					}
+					
+					if (funct != null)
+					{
+						funct(evt);
+					}
+				}
+				
+				this.eventReceiver = null;
+				mxEvent.consume(evt);
+			})
+		);
+	
+		// Resets hover style because TR in IE doesn't have hover
+		mxEvent.addListener(tr, 'mouseout',
+			mxUtils.bind(this, function(evt)
+			{
+				tr.className = 'mxPopupMenuItem';
+			})
+		);
+	}
+	
+	return tr;
+};
+
+/**
+ * Adds a checkmark to the given menuitem.
+ */
+mxPopupMenu.prototype.addCheckmark = function(item, img)
+{
+	var td = item.firstChild.nextSibling;
+	td.style.backgroundImage = 'url(\'' + img + '\')';
+	td.style.backgroundRepeat = 'no-repeat';
+	td.style.backgroundPosition = '2px 50%';
+};
+
+/**
+ * Function: createSubmenu
+ * 
+ * Creates the nodes required to add submenu items inside the given parent
+ * item. This is called in <addItem> if a parent item is used for the first
+ * time. This adds various DOM nodes and a <submenuImage> to the parent.
+ * 
+ * Parameters:
+ * 
+ * parent - An item returned by <addItem>.
+ */
+mxPopupMenu.prototype.createSubmenu = function(parent)
+{
+	parent.table = document.createElement('table');
+	parent.table.className = 'mxPopupMenu';
+
+	parent.tbody = document.createElement('tbody');
+	parent.table.appendChild(parent.tbody);
+
+	parent.div = document.createElement('div');
+	parent.div.className = 'mxPopupMenu';
+
+	parent.div.style.position = 'absolute';
+	parent.div.style.display = 'inline';
+	parent.div.style.zIndex = this.zIndex;
+	
+	parent.div.appendChild(parent.table);
+	
+	var img = document.createElement('img');
+	img.setAttribute('src', this.submenuImage);
+	
+	// Last column of the submenu item in the parent menu
+	td = parent.firstChild.nextSibling.nextSibling;
+	td.appendChild(img);
+};
+
+/**
+ * Function: showSubmenu
+ * 
+ * Shows the submenu inside the given parent row.
+ */
+mxPopupMenu.prototype.showSubmenu = function(parent, row)
+{
+	if (row.div != null)
+	{
+		row.div.style.left = (parent.div.offsetLeft +
+			row.offsetLeft+row.offsetWidth - 1) + 'px';
+		row.div.style.top = (parent.div.offsetTop+row.offsetTop) + 'px';
+		document.body.appendChild(row.div);
+		
+		// Moves the submenu to the left side if there is no space
+		var left = parseInt(row.div.offsetLeft);
+		var width = parseInt(row.div.offsetWidth);
+		var offset = mxUtils.getDocumentScrollOrigin(document);
+		
+		var b = document.body;
+		var d = document.documentElement;
+		
+		var right = offset.x + (b.clientWidth || d.clientWidth);
+		
+		if (left + width > right)
+		{
+			row.div.style.left = Math.max(0, (parent.div.offsetLeft - width + ((mxClient.IS_IE) ? 6 : -6))) + 'px';
+		}
+		
+		mxUtils.fit(row.div);
+	}
+};
+
+/**
+ * Function: addSeparator
+ * 
+ * Adds a horizontal separator in the given parent item or the top-level menu
+ * if no parent is specified.
+ * 
+ * Parameters:
+ * 
+ * parent - Optional item returned by <addItem>.
+ * force - Optional boolean to ignore <smartSeparators>. Default is false.
+ */
+mxPopupMenu.prototype.addSeparator = function(parent, force)
+{
+	parent = parent || this;
+	
+	if (this.smartSeparators && !force)
+	{
+		parent.willAddSeparator = true;
+	}
+	else if (parent.tbody != null)
+	{
+		parent.willAddSeparator = false;
+		var tr = document.createElement('tr');
+		
+		var col1 = document.createElement('td');
+		col1.className = 'mxPopupMenuIcon';
+		col1.style.padding = '0 0 0 0px';
+		
+		tr.appendChild(col1);
+		
+		var col2 = document.createElement('td');
+		col2.style.padding = '0 0 0 0px';
+		col2.setAttribute('colSpan', '2');
+	
+		var hr = document.createElement('hr');
+		hr.setAttribute('size', '1');
+		col2.appendChild(hr);
+		
+		tr.appendChild(col2);
+		
+		parent.tbody.appendChild(tr);
+	}
+};
+
+/**
+ * Function: popup
+ * 
+ * Shows the popup menu for the given event and cell.
+ * 
+ * Example:
+ * 
+ * (code)
+ * graph.panningHandler.popup = function(x, y, cell, evt)
+ * {
+ *   mxUtils.alert('Hello, World!');
+ * }
+ * (end)
+ */
+mxPopupMenu.prototype.popup = function(x, y, cell, evt)
+{
+	if (this.div != null && this.tbody != null && this.factoryMethod != null)
+	{
+		this.div.style.left = x + 'px';
+		this.div.style.top = y + 'px';
+		
+		// Removes all child nodes from the existing menu
+		while (this.tbody.firstChild != null)
+		{
+			mxEvent.release(this.tbody.firstChild);
+			this.tbody.removeChild(this.tbody.firstChild);
+		}
+		
+		this.itemCount = 0;
+		this.factoryMethod(this, cell, evt);
+		
+		if (this.itemCount > 0)
+		{
+			this.showMenu();
+			this.fireEvent(new mxEventObject(mxEvent.SHOW));
+		}
+	}
+};
+
+/**
+ * Function: isMenuShowing
+ * 
+ * Returns true if the menu is showing.
+ */
+mxPopupMenu.prototype.isMenuShowing = function()
+{
+	return this.div != null && this.div.parentNode == document.body;
+};
+
+/**
+ * Function: showMenu
+ * 
+ * Shows the menu.
+ */
+mxPopupMenu.prototype.showMenu = function()
+{
+	// Disables filter-based shadow in IE9 standards mode
+	if (document.documentMode >= 9)
+	{
+		this.div.style.filter = 'none';
+	}
+	
+	// Fits the div inside the viewport
+	document.body.appendChild(this.div);
+	mxUtils.fit(this.div);
+};
+
+/**
+ * Function: hideMenu
+ * 
+ * Removes the menu and all submenus.
+ */
+mxPopupMenu.prototype.hideMenu = function()
+{
+	if (this.div != null)
+	{
+		if (this.div.parentNode != null)
+		{
+			this.div.parentNode.removeChild(this.div);
+		}
+		
+		this.hideSubmenu(this);
+		this.containsItems = false;
+		this.fireEvent(new mxEventObject(mxEvent.HIDE));
+	}
+};
+
+/**
+ * Function: hideSubmenu
+ * 
+ * Removes all submenus inside the given parent.
+ * 
+ * Parameters:
+ * 
+ * parent - An item returned by <addItem>.
+ */
+mxPopupMenu.prototype.hideSubmenu = function(parent)
+{
+	if (parent.activeRow != null)
+	{
+		this.hideSubmenu(parent.activeRow);
+		
+		if (parent.activeRow.div.parentNode != null)
+		{
+			parent.activeRow.div.parentNode.removeChild(parent.activeRow.div);
+		}
+		
+		parent.activeRow = null;
+	}
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the handler and all its resources and DOM nodes.
+ */
+mxPopupMenu.prototype.destroy = function()
+{
+	if (this.div != null)
+	{
+		mxEvent.release(this.div);
+		
+		if (this.div.parentNode != null)
+		{
+			this.div.parentNode.removeChild(this.div);
+		}
+		
+		this.div = null;
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxAutoSaveManager
+ * 
+ * Manager for automatically saving diagrams. The <save> hook must be
+ * implemented.
+ * 
+ * Example:
+ * 
+ * (code)
+ * var mgr = new mxAutoSaveManager(editor.graph);
+ * mgr.save = function()
+ * {
+ *   mxLog.show();
+ *   mxLog.debug('save');
+ * };
+ * (end)
+ * 
+ * Constructor: mxAutoSaveManager
+ *
+ * Constructs a new automatic layout for the given graph.
+ *
+ * Arguments:
+ * 
+ * graph - Reference to the enclosing graph. 
+ */
+function mxAutoSaveManager(graph)
+{
+	// Notifies the manager of a change
+	this.changeHandler = mxUtils.bind(this, function(sender, evt)
+	{
+		if (this.isEnabled())
+		{
+			this.graphModelChanged(evt.getProperty('edit').changes);
+		}
+	});
+
+	this.setGraph(graph);
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxAutoSaveManager.prototype = new mxEventSource();
+mxAutoSaveManager.prototype.constructor = mxAutoSaveManager;
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxAutoSaveManager.prototype.graph = null;
+
+/**
+ * Variable: autoSaveDelay
+ * 
+ * Minimum amount of seconds between two consecutive autosaves. Eg. a
+ * value of 1 (s) means the graph is not stored more than once per second.
+ * Default is 10.
+ */
+mxAutoSaveManager.prototype.autoSaveDelay = 10;
+
+/**
+ * Variable: autoSaveThrottle
+ * 
+ * Minimum amount of seconds between two consecutive autosaves triggered by
+ * more than <autoSaveThreshhold> changes within a timespan of less than
+ * <autoSaveDelay> seconds. Eg. a value of 1 (s) means the graph is not
+ * stored more than once per second even if there are more than
+ * <autoSaveThreshold> changes within that timespan. Default is 2.
+ */
+mxAutoSaveManager.prototype.autoSaveThrottle = 2;
+
+/**
+ * Variable: autoSaveThreshold
+ * 
+ * Minimum amount of ignored changes before an autosave. Eg. a value of 2
+ * means after 2 change of the graph model the autosave will trigger if the
+ * condition below is true. Default is 5.
+ */
+mxAutoSaveManager.prototype.autoSaveThreshold = 5;
+
+/**
+ * Variable: ignoredChanges
+ * 
+ * Counter for ignored changes in autosave.
+ */
+mxAutoSaveManager.prototype.ignoredChanges = 0;
+
+/**
+ * Variable: lastSnapshot
+ * 
+ * Used for autosaving. See <autosave>.
+ */
+mxAutoSaveManager.prototype.lastSnapshot = 0;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies if event handling is enabled. Default is true.
+ */
+mxAutoSaveManager.prototype.enabled = true;
+
+/**
+ * Variable: changeHandler
+ * 
+ * Holds the function that handles graph model changes.
+ */
+mxAutoSaveManager.prototype.changeHandler = null;
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxAutoSaveManager.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ * 
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ * 
+ * Parameters:
+ * 
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxAutoSaveManager.prototype.setEnabled = function(value)
+{
+	this.enabled = value;
+};
+
+/**
+ * Function: setGraph
+ * 
+ * Sets the graph that the layouts operate on.
+ */
+mxAutoSaveManager.prototype.setGraph = function(graph)
+{
+	if (this.graph != null)
+	{
+		this.graph.getModel().removeListener(this.changeHandler);
+	}
+	
+	this.graph = graph;
+	
+	if (this.graph != null)
+	{
+		this.graph.getModel().addListener(mxEvent.CHANGE, this.changeHandler);
+	}
+};
+
+/**
+ * Function: save
+ * 
+ * Empty hook that is called if the graph should be saved.
+ */
+mxAutoSaveManager.prototype.save = function()
+{
+	// empty
+};
+
+/**
+ * Function: graphModelChanged
+ * 
+ * Invoked when the graph model has changed.
+ */
+mxAutoSaveManager.prototype.graphModelChanged = function(changes)
+{
+	var now = new Date().getTime();
+	var dt = (now - this.lastSnapshot) / 1000;
+	
+	if (dt > this.autoSaveDelay ||
+		(this.ignoredChanges >= this.autoSaveThreshold &&
+		 dt > this.autoSaveThrottle))
+	{
+		this.save();
+		this.reset();
+	}
+	else
+	{
+		// Increments the number of ignored changes
+		this.ignoredChanges++;
+	}
+};
+
+/**
+ * Function: reset
+ * 
+ * Resets all counters.
+ */
+mxAutoSaveManager.prototype.reset = function()
+{
+	this.lastSnapshot = new Date().getTime();
+	this.ignoredChanges = 0;
+};
+
+/**
+ * Function: destroy
+ * 
+ * Removes all handlers from the <graph> and deletes the reference to it.
+ */
+mxAutoSaveManager.prototype.destroy = function()
+{
+	this.setGraph(null);
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ *
+ * Class: mxAnimation
+ * 
+ * Implements a basic animation in JavaScript.
+ * 
+ * Constructor: mxAnimation
+ * 
+ * Constructs an animation.
+ * 
+ * Parameters:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ */
+function mxAnimation(delay)
+{
+	this.delay = (delay != null) ? delay : 20;
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxAnimation.prototype = new mxEventSource();
+mxAnimation.prototype.constructor = mxAnimation;
+
+/**
+ * Variable: delay
+ * 
+ * Specifies the delay between the animation steps. Defaul is 30ms.
+ */
+mxAnimation.prototype.delay = null;
+
+/**
+ * Variable: thread
+ * 
+ * Reference to the thread while the animation is running.
+ */
+mxAnimation.prototype.thread = null;
+
+/**
+ * Function: isRunning
+ * 
+ * Returns true if the animation is running.
+ */
+mxAnimation.prototype.isRunning = function()
+{
+	return this.thread != null;
+};
+
+/**
+ * Function: startAnimation
+ *
+ * Starts the animation by repeatedly invoking updateAnimation.
+ */
+mxAnimation.prototype.startAnimation = function()
+{
+	if (this.thread == null)
+	{
+		this.thread = window.setInterval(mxUtils.bind(this, this.updateAnimation), this.delay);
+	}
+};
+
+/**
+ * Function: updateAnimation
+ *
+ * Hook for subclassers to implement the animation. Invoke stopAnimation
+ * when finished, startAnimation to resume. This is called whenever the
+ * timer fires and fires an mxEvent.EXECUTE event with no properties.
+ */
+mxAnimation.prototype.updateAnimation = function()
+{
+	this.fireEvent(new mxEventObject(mxEvent.EXECUTE));
+};
+
+/**
+ * Function: stopAnimation
+ *
+ * Stops the animation by deleting the timer and fires an <mxEvent.DONE>.
+ */
+mxAnimation.prototype.stopAnimation = function()
+{
+	if (this.thread != null)
+	{
+		window.clearInterval(this.thread);
+		this.thread = null;
+		this.fireEvent(new mxEventObject(mxEvent.DONE));
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ *
+ * Class: mxMorphing
+ * 
+ * Implements animation for morphing cells. Here is an example of
+ * using this class for animating the result of a layout algorithm:
+ * 
+ * (code)
+ * graph.getModel().beginUpdate();
+ * try
+ * {
+ *   var circleLayout = new mxCircleLayout(graph);
+ *   circleLayout.execute(graph.getDefaultParent());
+ * }
+ * finally
+ * {
+ *   var morph = new mxMorphing(graph);
+ *   morph.addListener(mxEvent.DONE, function()
+ *   {
+ *     graph.getModel().endUpdate();
+ *   });
+ *   
+ *   morph.startAnimation();
+ * }
+ * (end)
+ * 
+ * Constructor: mxMorphing
+ * 
+ * Constructs an animation.
+ * 
+ * Parameters:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ * steps - Optional number of steps in the morphing animation. Default is 6.
+ * ease - Optional easing constant for the animation. Default is 1.5.
+ * delay - Optional delay between the animation steps. Passed to <mxAnimation>.
+ */
+function mxMorphing(graph, steps, ease, delay)
+{
+	mxAnimation.call(this, delay);
+	this.graph = graph;
+	this.steps = (steps != null) ? steps : 6;
+	this.ease = (ease != null) ? ease : 1.5;
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxMorphing.prototype = new mxAnimation();
+mxMorphing.prototype.constructor = mxMorphing;
+
+/**
+ * Variable: graph
+ * 
+ * Specifies the delay between the animation steps. Defaul is 30ms.
+ */
+mxMorphing.prototype.graph = null;
+
+/**
+ * Variable: steps
+ * 
+ * Specifies the maximum number of steps for the morphing.
+ */
+mxMorphing.prototype.steps = null;
+
+/**
+ * Variable: step
+ * 
+ * Contains the current step.
+ */
+mxMorphing.prototype.step = 0;
+
+/**
+ * Variable: ease
+ * 
+ * Ease-off for movement towards the given vector. Larger values are
+ * slower and smoother. Default is 4.
+ */
+mxMorphing.prototype.ease = null;
+
+/**
+ * Variable: cells
+ * 
+ * Optional array of cells to be animated. If this is not specified
+ * then all cells are checked and animated if they have been moved
+ * in the current transaction.
+ */
+mxMorphing.prototype.cells = null;
+
+/**
+ * Function: updateAnimation
+ *
+ * Animation step.
+ */
+mxMorphing.prototype.updateAnimation = function()
+{
+	var move = new mxCellStatePreview(this.graph);
+
+	if (this.cells != null)
+	{
+		// Animates the given cells individually without recursion
+		for (var i = 0; i < this.cells.length; i++)
+		{
+			this.animateCell(this.cells[i], move, false);
+		}
+	}
+	else
+	{
+		// Animates all changed cells by using recursion to find
+		// the changed cells but not for the animation itself
+		this.animateCell(this.graph.getModel().getRoot(), move, true);
+	}
+	
+	this.show(move);
+	
+	if (move.isEmpty() || this.step++ >= this.steps)
+	{
+		this.stopAnimation();
+	}
+};
+
+/**
+ * Function: show
+ *
+ * Shows the changes in the given <mxCellStatePreview>.
+ */
+mxMorphing.prototype.show = function(move)
+{
+	move.show();
+};
+
+/**
+ * Function: animateCell
+ *
+ * Animates the given cell state using <mxCellStatePreview.moveState>.
+ */
+mxMorphing.prototype.animateCell = function(cell, move, recurse)
+{
+	var state = this.graph.getView().getState(cell);
+	var delta = null;
+
+	if (state != null)
+	{
+		// Moves the animated state from where it will be after the model
+		// change by subtracting the given delta vector from that location
+		delta = this.getDelta(state);
+
+		if (this.graph.getModel().isVertex(cell) && (delta.x != 0 || delta.y != 0))
+		{
+			var translate = this.graph.view.getTranslate();
+			var scale = this.graph.view.getScale();
+			
+			delta.x += translate.x * scale;
+			delta.y += translate.y * scale;
+			
+			move.moveState(state, -delta.x / this.ease, -delta.y / this.ease);
+		}
+	}
+	
+	if (recurse && !this.stopRecursion(state, delta))
+	{
+		var childCount = this.graph.getModel().getChildCount(cell);
+
+		for (var i = 0; i < childCount; i++)
+		{
+			this.animateCell(this.graph.getModel().getChildAt(cell, i), move, recurse);
+		}
+	}
+};
+
+/**
+ * Function: stopRecursion
+ *
+ * Returns true if the animation should not recursively find more
+ * deltas for children if the given parent state has been animated.
+ */
+mxMorphing.prototype.stopRecursion = function(state, delta)
+{
+	return delta != null && (delta.x != 0 || delta.y != 0);
+};
+
+/**
+ * Function: getDelta
+ *
+ * Returns the vector between the current rendered state and the future
+ * location of the state after the display will be updated.
+ */
+mxMorphing.prototype.getDelta = function(state)
+{
+	var origin = this.getOriginForCell(state.cell);
+	var translate = this.graph.getView().getTranslate();
+	var scale = this.graph.getView().getScale();
+	var x = state.x / scale - translate.x;
+	var y = state.y / scale - translate.y;
+
+	return new mxPoint((origin.x - x) * scale, (origin.y - y) * scale);
+};
+
+/**
+ * Function: getOriginForCell
+ *
+ * Returns the top, left corner of the given cell. TODO: Improve performance
+ * by using caching inside this method as the result per cell never changes
+ * during the lifecycle of this object.
+ */
+mxMorphing.prototype.getOriginForCell = function(cell)
+{
+	var result = null;
+	
+	if (cell != null)
+	{
+		var parent = this.graph.getModel().getParent(cell);
+		var geo = this.graph.getCellGeometry(cell);
+		result = this.getOriginForCell(parent);
+		
+		// TODO: Handle offsets
+		if (geo != null)
+		{
+			if (geo.relative)
+			{
+				var pgeo = this.graph.getCellGeometry(parent);
+				
+				if (pgeo != null)
+				{
+					result.x += geo.x * pgeo.width;
+					result.y += geo.y * pgeo.height;
+				}
+			}
+			else
+			{
+				result.x += geo.x;
+				result.y += geo.y;
+			}
+		}
+	}
+	
+	if (result == null)
+	{
+		var t = this.graph.view.getTranslate();
+		result = new mxPoint(-t.x, -t.y);
+	}
+	
+	return result;
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxImageBundle
+ *
+ * Maps from keys to base64 encoded images or file locations. All values must
+ * be URLs or use the format data:image/format followed by a comma and the base64
+ * encoded image data, eg. "data:image/gif,XYZ", where XYZ is the base64 encoded
+ * image data.
+ * 
+ * To add a new image bundle to an existing graph, the following code is used:
+ * 
+ * (code)
+ * var bundle = new mxImageBundle(alt);
+ * bundle.putImage('myImage', 'data:image/gif,R0lGODlhEAAQAMIGAAAAAICAAICAgP' +
+ *   '//AOzp2O3r2////////yH+FUNyZWF0ZWQgd2l0aCBUaGUgR0lNUAAh+QQBCgAHACwAAAAA' +
+ *   'EAAQAAADTXi63AowynnAMDfjPUDlnAAJhmeBFxAEloliKltWmiYCQvfVr6lBPB1ggxN1hi' +
+ *   'laSSASFQpIV5HJBDyHpqK2ejVRm2AAgZCdmCGO9CIBADs=', fallback);
+ * bundle.putImage('mySvgImage', 'data:image/svg+xml,' + encodeURIComponent(
+ *   '<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%">' +
+ *   '<linearGradient id="gradient"><stop offset="10%" stop-color="#F00"/>' +
+ *   '<stop offset="90%" stop-color="#fcc"/></linearGradient>' +
+ *   '<rect fill="url(#gradient)" width="100%" height="100%"/></svg>'), fallback);
+ * graph.addImageBundle(bundle);
+ * (end);
+ * 
+ * Alt is an optional boolean (default is false) that specifies if the value
+ * or the fallback should be returned in <getImage>.
+ * 
+ * The image can then be referenced in any cell style using image=myImage.
+ * If you are using mxOutline, you should use the same image bundles in the
+ * graph that renders the outline.
+ * 
+ * The keys for images are resolved in <mxGraph.postProcessCellStyle> and
+ * turned into a data URI if the returned value has a short data URI format
+ * as specified above.
+ * 
+ * A typical value for the fallback is a MTHML link as defined in RFC 2557.
+ * Note that this format requires a file to be dynamically created on the
+ * server-side, or the page that contains the graph to be modified to contain
+ * the resources, this can be done by adding a comment that contains the
+ * resource in the HEAD section of the page after the title tag.
+ * 
+ * This type of fallback mechanism should be used in IE6 and IE7. IE8 does
+ * support data URIs, but the maximum size is limited to 32 KB, which means
+ * all data URIs should be limited to 32 KB.
+ */
+function mxImageBundle(alt)
+{
+	this.images = [];
+	this.alt = (alt != null) ? alt : false;
+};
+
+/**
+ * Variable: images
+ * 
+ * Maps from keys to images.
+ */
+mxImageBundle.prototype.images = null;
+
+/**
+ * Variable: alt
+ * 
+ * Specifies if the fallback representation should be returned.
+ */
+mxImageBundle.prototype.images = null;
+
+/**
+ * Function: putImage
+ * 
+ * Adds the specified entry to the map. The entry is an object with a value and
+ * fallback property as specified in the arguments.
+ */
+mxImageBundle.prototype.putImage = function(key, value, fallback)
+{
+	this.images[key] = {value: value, fallback: fallback};
+};
+
+/**
+ * Function: getImage
+ * 
+ * Returns the value for the given key. This returns the value
+ * or fallback, depending on <alt>. The fallback is returned if
+ * <alt> is true, the value is returned otherwise.
+ */
+mxImageBundle.prototype.getImage = function(key)
+{
+	var result = null;
+	
+	if (key != null)
+	{
+		var img = this.images[key];
+		
+		if (img != null)
+		{
+			result = (this.alt) ? img.fallback : img.value;
+		}
+	}
+	
+	return result;
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxImageExport
+ * 
+ * Creates a new image export instance to be used with an export canvas. Here
+ * is an example that uses this class to create an image via a backend using
+ * <mxXmlExportCanvas>.
+ * 
+ * (code)
+ * var xmlDoc = mxUtils.createXmlDocument();
+ * var root = xmlDoc.createElement('output');
+ * xmlDoc.appendChild(root);
+ * 
+ * var xmlCanvas = new mxXmlCanvas2D(root);
+ * var imgExport = new mxImageExport();
+ * imgExport.drawState(graph.getView().getState(graph.model.root), xmlCanvas);
+ * 
+ * var bounds = graph.getGraphBounds();
+ * var w = Math.ceil(bounds.x + bounds.width);
+ * var h = Math.ceil(bounds.y + bounds.height);
+ * 
+ * var xml = mxUtils.getXml(root);
+ * new mxXmlRequest('export', 'format=png&w=' + w +
+ * 		'&h=' + h + '&bg=#F9F7ED&xml=' + encodeURIComponent(xml))
+ * 		.simulate(document, '_blank');
+ * (end)
+ * 
+ * Constructor: mxImageExport
+ * 
+ * Constructs a new image export.
+ */
+function mxImageExport() { };
+
+/**
+ * Variable: includeOverlays
+ * 
+ * Specifies if overlays should be included in the export. Default is false.
+ */
+mxImageExport.prototype.includeOverlays = false;
+
+/**
+ * Function: drawState
+ * 
+ * Draws the given state and all its descendants to the given canvas.
+ */
+mxImageExport.prototype.drawState = function(state, canvas)
+{
+	if (state != null)
+	{
+		this.visitStatesRecursive(state, canvas, mxUtils.bind(this, function()
+		{
+			this.drawCellState.apply(this, arguments);
+		}));
+				
+		// Paints the overlays
+		if (this.includeOverlays)
+		{
+			this.visitStatesRecursive(state, canvas, mxUtils.bind(this, function()
+			{
+				this.drawOverlays.apply(this, arguments);
+			}));
+		}
+	}
+};
+
+/**
+ * Function: drawState
+ * 
+ * Draws the given state and all its descendants to the given canvas.
+ */
+mxImageExport.prototype.visitStatesRecursive = function(state, canvas, visitor)
+{
+	if (state != null)
+	{
+		visitor(state, canvas);
+		
+		var graph = state.view.graph;
+		var childCount = graph.model.getChildCount(state.cell);
+		
+		for (var i = 0; i < childCount; i++)
+		{
+			var childState = graph.view.getState(graph.model.getChildAt(state.cell, i));
+			this.visitStatesRecursive(childState, canvas, visitor);
+		}
+	}
+};
+
+/**
+ * Function: getLinkForCellState
+ * 
+ * Returns the link for the given cell state and canvas. This returns null.
+ */
+mxImageExport.prototype.getLinkForCellState = function(state, canvas)
+{
+	return null;
+};
+
+/**
+ * Function: drawCellState
+ * 
+ * Draws the given state to the given canvas.
+ */
+mxImageExport.prototype.drawCellState = function(state, canvas)
+{
+	// Experimental feature
+	var link = this.getLinkForCellState(state, canvas);
+	
+	if (link != null)
+	{
+		canvas.setLink(link);
+	}
+	
+	// Paints the shape and text
+	this.drawShape(state, canvas);
+	this.drawText(state, canvas);
+
+	if (link != null)
+	{
+		canvas.setLink(null);
+	}
+};
+
+/**
+ * Function: drawShape
+ * 
+ * Draws the shape of the given state.
+ */
+mxImageExport.prototype.drawShape = function(state, canvas)
+{
+	if (state.shape instanceof mxShape && state.shape.checkBounds())
+	{
+		canvas.save();
+		state.shape.paint(canvas);
+		canvas.restore();
+	}
+};
+
+/**
+ * Function: drawText
+ * 
+ * Draws the text of the given state.
+ */
+mxImageExport.prototype.drawText = function(state, canvas)
+{
+	if (state.text != null && state.text.checkBounds())
+	{
+		canvas.save();
+		state.text.paint(canvas);
+		canvas.restore();
+	}
+};
+
+/**
+ * Function: drawOverlays
+ * 
+ * Draws the overlays for the given state. This is called if <includeOverlays>
+ * is true.
+ */
+mxImageExport.prototype.drawOverlays = function(state, canvas)
+{
+	if (state.overlays != null)
+	{
+		state.overlays.visit(function(id, shape)
+		{
+			if (shape instanceof mxShape)
+			{
+				shape.paint(canvas);
+			}
+		});
+	}
+};
+
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxAbstractCanvas2D
+ *
+ * Base class for all canvases. A description of the public API is available in <mxXmlCanvas2D>.
+ * All color values of <mxConstants.NONE> will be converted to null in the state.
+ * 
+ * Constructor: mxAbstractCanvas2D
+ *
+ * Constructs a new abstract canvas.
+ */
+function mxAbstractCanvas2D()
+{
+	/**
+	 * Variable: converter
+	 * 
+	 * Holds the <mxUrlConverter> to convert image URLs.
+	 */
+	this.converter = this.createUrlConverter();
+	
+	this.reset();
+};
+
+/**
+ * Variable: state
+ * 
+ * Holds the current state.
+ */
+mxAbstractCanvas2D.prototype.state = null;
+
+/**
+ * Variable: states
+ * 
+ * Stack of states.
+ */
+mxAbstractCanvas2D.prototype.states = null;
+
+/**
+ * Variable: path
+ * 
+ * Holds the current path as an array.
+ */
+mxAbstractCanvas2D.prototype.path = null;
+
+/**
+ * Variable: rotateHtml
+ * 
+ * Switch for rotation of HTML. Default is false.
+ */
+mxAbstractCanvas2D.prototype.rotateHtml = true;
+
+/**
+ * Variable: lastX
+ * 
+ * Holds the last x coordinate.
+ */
+mxAbstractCanvas2D.prototype.lastX = 0;
+
+/**
+ * Variable: lastY
+ * 
+ * Holds the last y coordinate.
+ */
+mxAbstractCanvas2D.prototype.lastY = 0;
+
+/**
+ * Variable: moveOp
+ * 
+ * Contains the string used for moving in paths. Default is 'M'.
+ */
+mxAbstractCanvas2D.prototype.moveOp = 'M';
+
+/**
+ * Variable: lineOp
+ * 
+ * Contains the string used for moving in paths. Default is 'L'.
+ */
+mxAbstractCanvas2D.prototype.lineOp = 'L';
+
+/**
+ * Variable: quadOp
+ * 
+ * Contains the string used for quadratic paths. Default is 'Q'.
+ */
+mxAbstractCanvas2D.prototype.quadOp = 'Q';
+
+/**
+ * Variable: curveOp
+ * 
+ * Contains the string used for bezier curves. Default is 'C'.
+ */
+mxAbstractCanvas2D.prototype.curveOp = 'C';
+
+/**
+ * Variable: closeOp
+ * 
+ * Holds the operator for closing curves. Default is 'Z'.
+ */
+mxAbstractCanvas2D.prototype.closeOp = 'Z';
+
+/**
+ * Variable: pointerEvents
+ * 
+ * Boolean value that specifies if events should be handled. Default is false.
+ */
+mxAbstractCanvas2D.prototype.pointerEvents = false;
+
+/**
+ * Function: createUrlConverter
+ * 
+ * Create a new <mxUrlConverter> and returns it.
+ */
+mxAbstractCanvas2D.prototype.createUrlConverter = function()
+{
+	return new mxUrlConverter();
+};
+
+/**
+ * Function: reset
+ * 
+ * Resets the state of this canvas.
+ */
+mxAbstractCanvas2D.prototype.reset = function()
+{
+	this.state = this.createState();
+	this.states = [];
+};
+
+/**
+ * Function: createState
+ * 
+ * Creates the state of the this canvas.
+ */
+mxAbstractCanvas2D.prototype.createState = function()
+{
+	return {
+		dx: 0,
+		dy: 0,
+		scale: 1,
+		alpha: 1,
+		fillAlpha: 1,
+		strokeAlpha: 1,
+		fillColor: null,
+		gradientFillAlpha: 1,
+		gradientColor: null,
+		gradientAlpha: 1,
+		gradientDirection: null,
+		strokeColor: null,
+		strokeWidth: 1,
+		dashed: false,
+		dashPattern: '3 3',
+		fixDash: false,
+		lineCap: 'flat',
+		lineJoin: 'miter',
+		miterLimit: 10,
+		fontColor: '#000000',
+		fontBackgroundColor: null,
+		fontBorderColor: null,
+		fontSize: mxConstants.DEFAULT_FONTSIZE,
+		fontFamily: mxConstants.DEFAULT_FONTFAMILY,
+		fontStyle: 0,
+		shadow: false,
+		shadowColor: mxConstants.SHADOWCOLOR,
+		shadowAlpha: mxConstants.SHADOW_OPACITY,
+		shadowDx: mxConstants.SHADOW_OFFSET_X,
+		shadowDy: mxConstants.SHADOW_OFFSET_Y,
+		rotation: 0,
+		rotationCx: 0,
+		rotationCy: 0
+	};
+};
+
+/**
+ * Function: format
+ * 
+ * Rounds all numbers to integers.
+ */
+mxAbstractCanvas2D.prototype.format = function(value)
+{
+	return Math.round(parseFloat(value));
+};
+
+/**
+ * Function: addOp
+ * 
+ * Adds the given operation to the path.
+ */
+mxAbstractCanvas2D.prototype.addOp = function()
+{
+	if (this.path != null)
+	{
+		this.path.push(arguments[0]);
+		
+		if (arguments.length > 2)
+		{
+			var s = this.state;
+
+			for (var i = 2; i < arguments.length; i += 2)
+			{
+				this.lastX = arguments[i - 1];
+				this.lastY = arguments[i];
+				
+				this.path.push(this.format((this.lastX + s.dx) * s.scale));
+				this.path.push(this.format((this.lastY + s.dy) * s.scale));
+			}
+		}
+	}
+};
+
+/**
+ * Function: rotatePoint
+ * 
+ * Rotates the given point and returns the result as an <mxPoint>.
+ */
+mxAbstractCanvas2D.prototype.rotatePoint = function(x, y, theta, cx, cy)
+{
+	var rad = theta * (Math.PI / 180);
+	
+	return mxUtils.getRotatedPoint(new mxPoint(x, y), Math.cos(rad),
+		Math.sin(rad), new mxPoint(cx, cy));
+};
+
+/**
+ * Function: save
+ * 
+ * Saves the current state.
+ */
+mxAbstractCanvas2D.prototype.save = function()
+{
+	this.states.push(this.state);
+	this.state = mxUtils.clone(this.state);
+};
+
+/**
+ * Function: restore
+ * 
+ * Restores the current state.
+ */
+mxAbstractCanvas2D.prototype.restore = function()
+{
+	if (this.states.length > 0)
+	{
+		this.state = this.states.pop();
+	}
+};
+
+/**
+ * Function: setLink
+ * 
+ * Sets the current link. Hook for subclassers.
+ */
+mxAbstractCanvas2D.prototype.setLink = function(link)
+{
+	// nop
+};
+
+/**
+ * Function: scale
+ * 
+ * Scales the current state.
+ */
+mxAbstractCanvas2D.prototype.scale = function(value)
+{
+	this.state.scale *= value;
+	this.state.strokeWidth *= value;
+};
+
+/**
+ * Function: translate
+ * 
+ * Translates the current state.
+ */
+mxAbstractCanvas2D.prototype.translate = function(dx, dy)
+{
+	this.state.dx += dx;
+	this.state.dy += dy;
+};
+
+/**
+ * Function: rotate
+ * 
+ * Rotates the current state.
+ */
+mxAbstractCanvas2D.prototype.rotate = function(theta, flipH, flipV, cx, cy)
+{
+	// nop
+};
+
+/**
+ * Function: setAlpha
+ * 
+ * Sets the current alpha.
+ */
+mxAbstractCanvas2D.prototype.setAlpha = function(value)
+{
+	this.state.alpha = value;
+};
+
+/**
+ * Function: setFillAlpha
+ * 
+ * Sets the current solid fill alpha.
+ */
+mxAbstractCanvas2D.prototype.setFillAlpha = function(value)
+{
+	this.state.fillAlpha = value;
+};
+
+/**
+ * Function: setStrokeAlpha
+ * 
+ * Sets the current stroke alpha.
+ */
+mxAbstractCanvas2D.prototype.setStrokeAlpha = function(value)
+{
+	this.state.strokeAlpha = value;
+};
+
+/**
+ * Function: setFillColor
+ * 
+ * Sets the current fill color.
+ */
+mxAbstractCanvas2D.prototype.setFillColor = function(value)
+{
+	if (value == mxConstants.NONE)
+	{
+		value = null;
+	}
+	
+	this.state.fillColor = value;
+	this.state.gradientColor = null;
+};
+
+/**
+ * Function: setGradient
+ * 
+ * Sets the current gradient.
+ */
+mxAbstractCanvas2D.prototype.setGradient = function(color1, color2, x, y, w, h, direction, alpha1, alpha2)
+{
+	var s = this.state;
+	s.fillColor = color1;
+	s.gradientFillAlpha = (alpha1 != null) ? alpha1 : 1;
+	s.gradientColor = color2;
+	s.gradientAlpha = (alpha2 != null) ? alpha2 : 1;
+	s.gradientDirection = direction;
+};
+
+/**
+ * Function: setStrokeColor
+ * 
+ * Sets the current stroke color.
+ */
+mxAbstractCanvas2D.prototype.setStrokeColor = function(value)
+{
+	if (value == mxConstants.NONE)
+	{
+		value = null;
+	}
+	
+	this.state.strokeColor = value;
+};
+
+/**
+ * Function: setStrokeWidth
+ * 
+ * Sets the current stroke width.
+ */
+mxAbstractCanvas2D.prototype.setStrokeWidth = function(value)
+{
+	this.state.strokeWidth = value;
+};
+
+/**
+ * Function: setDashed
+ * 
+ * Enables or disables dashed lines.
+ */
+mxAbstractCanvas2D.prototype.setDashed = function(value, fixDash)
+{
+	this.state.dashed = value;
+	this.state.fixDash = fixDash;
+};
+
+/**
+ * Function: setDashPattern
+ * 
+ * Sets the current dash pattern.
+ */
+mxAbstractCanvas2D.prototype.setDashPattern = function(value)
+{
+	this.state.dashPattern = value;
+};
+
+/**
+ * Function: setLineCap
+ * 
+ * Sets the current line cap.
+ */
+mxAbstractCanvas2D.prototype.setLineCap = function(value)
+{
+	this.state.lineCap = value;
+};
+
+/**
+ * Function: setLineJoin
+ * 
+ * Sets the current line join.
+ */
+mxAbstractCanvas2D.prototype.setLineJoin = function(value)
+{
+	this.state.lineJoin = value;
+};
+
+/**
+ * Function: setMiterLimit
+ * 
+ * Sets the current miter limit.
+ */
+mxAbstractCanvas2D.prototype.setMiterLimit = function(value)
+{
+	this.state.miterLimit = value;
+};
+
+/**
+ * Function: setFontColor
+ * 
+ * Sets the current font color.
+ */
+mxAbstractCanvas2D.prototype.setFontColor = function(value)
+{
+	if (value == mxConstants.NONE)
+	{
+		value = null;
+	}
+	
+	this.state.fontColor = value;
+};
+
+/**
+ * Function: setFontColor
+ * 
+ * Sets the current font color.
+ */
+mxAbstractCanvas2D.prototype.setFontBackgroundColor = function(value)
+{
+	if (value == mxConstants.NONE)
+	{
+		value = null;
+	}
+	
+	this.state.fontBackgroundColor = value;
+};
+
+/**
+ * Function: setFontColor
+ * 
+ * Sets the current font color.
+ */
+mxAbstractCanvas2D.prototype.setFontBorderColor = function(value)
+{
+	if (value == mxConstants.NONE)
+	{
+		value = null;
+	}
+	
+	this.state.fontBorderColor = value;
+};
+
+/**
+ * Function: setFontSize
+ * 
+ * Sets the current font size.
+ */
+mxAbstractCanvas2D.prototype.setFontSize = function(value)
+{
+	this.state.fontSize = parseFloat(value);
+};
+
+/**
+ * Function: setFontFamily
+ * 
+ * Sets the current font family.
+ */
+mxAbstractCanvas2D.prototype.setFontFamily = function(value)
+{
+	this.state.fontFamily = value;
+};
+
+/**
+ * Function: setFontStyle
+ * 
+ * Sets the current font style.
+ */
+mxAbstractCanvas2D.prototype.setFontStyle = function(value)
+{
+	if (value == null)
+	{
+		value = 0;
+	}
+	
+	this.state.fontStyle = value;
+};
+
+/**
+ * Function: setShadow
+ * 
+ * Enables or disables and configures the current shadow.
+ */
+mxAbstractCanvas2D.prototype.setShadow = function(enabled)
+{
+	this.state.shadow = enabled;
+};
+
+/**
+ * Function: setShadowColor
+ * 
+ * Enables or disables and configures the current shadow.
+ */
+mxAbstractCanvas2D.prototype.setShadowColor = function(value)
+{
+	if (value == mxConstants.NONE)
+	{
+		value = null;
+	}
+	
+	this.state.shadowColor = value;
+};
+
+/**
+ * Function: setShadowAlpha
+ * 
+ * Enables or disables and configures the current shadow.
+ */
+mxAbstractCanvas2D.prototype.setShadowAlpha = function(value)
+{
+	this.state.shadowAlpha = value;
+};
+
+/**
+ * Function: setShadowOffset
+ * 
+ * Enables or disables and configures the current shadow.
+ */
+mxAbstractCanvas2D.prototype.setShadowOffset = function(dx, dy)
+{
+	this.state.shadowDx = dx;
+	this.state.shadowDy = dy;
+};
+
+/**
+ * Function: begin
+ * 
+ * Starts a new path.
+ */
+mxAbstractCanvas2D.prototype.begin = function()
+{
+	this.lastX = 0;
+	this.lastY = 0;
+	this.path = [];
+};
+
+/**
+ * Function: moveTo
+ * 
+ *  Moves the current path the given coordinates.
+ */
+mxAbstractCanvas2D.prototype.moveTo = function(x, y)
+{
+	this.addOp(this.moveOp, x, y);
+};
+
+/**
+ * Function: lineTo
+ * 
+ * Draws a line to the given coordinates. Uses moveTo with the op argument.
+ */
+mxAbstractCanvas2D.prototype.lineTo = function(x, y)
+{
+	this.addOp(this.lineOp, x, y);
+};
+
+/**
+ * Function: quadTo
+ * 
+ * Adds a quadratic curve to the current path.
+ */
+mxAbstractCanvas2D.prototype.quadTo = function(x1, y1, x2, y2)
+{
+	this.addOp(this.quadOp, x1, y1, x2, y2);
+};
+
+/**
+ * Function: curveTo
+ * 
+ * Adds a bezier curve to the current path.
+ */
+mxAbstractCanvas2D.prototype.curveTo = function(x1, y1, x2, y2, x3, y3)
+{
+	this.addOp(this.curveOp, x1, y1, x2, y2, x3, y3);
+};
+
+/**
+ * Function: arcTo
+ * 
+ * Adds the given arc to the current path. This is a synthetic operation that
+ * is broken down into curves.
+ */
+mxAbstractCanvas2D.prototype.arcTo = function(rx, ry, angle, largeArcFlag, sweepFlag, x, y)
+{
+	var curves = mxUtils.arcToCurves(this.lastX, this.lastY, rx, ry, angle, largeArcFlag, sweepFlag, x, y);
+	
+	if (curves != null)
+	{
+		for (var i = 0; i < curves.length; i += 6) 
+		{
+			this.curveTo(curves[i], curves[i + 1], curves[i + 2],
+				curves[i + 3], curves[i + 4], curves[i + 5]);
+		}
+	}
+};
+
+/**
+ * Function: close
+ * 
+ * Closes the current path.
+ */
+mxAbstractCanvas2D.prototype.close = function(x1, y1, x2, y2, x3, y3)
+{
+	this.addOp(this.closeOp);
+};
+
+/**
+ * Function: end
+ * 
+ * Empty implementation for backwards compatibility. This will be removed.
+ */
+mxAbstractCanvas2D.prototype.end = function() { };
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxXmlCanvas2D
+ *
+ * Base class for all canvases. The following methods make up the public
+ * interface of the canvas 2D for all painting in mxGraph:
+ * 
+ * - <save>, <restore>
+ * - <scale>, <translate>, <rotate>
+ * - <setAlpha>, <setFillAlpha>, <setStrokeAlpha>, <setFillColor>, <setGradient>,
+ *   <setStrokeColor>, <setStrokeWidth>, <setDashed>, <setDashPattern>, <setLineCap>, 
+ *   <setLineJoin>, <setMiterLimit>
+ * - <setFontColor>, <setFontBackgroundColor>, <setFontBorderColor>, <setFontSize>,
+ *   <setFontFamily>, <setFontStyle>
+ * - <setShadow>, <setShadowColor>, <setShadowAlpha>, <setShadowOffset>
+ * - <rect>, <roundrect>, <ellipse>, <image>, <text>
+ * - <begin>, <moveTo>, <lineTo>, <quadTo>, <curveTo>
+ * - <stroke>, <fill>, <fillAndStroke>
+ * 
+ * <mxAbstractCanvas2D.arcTo> is an additional method for drawing paths. This is
+ * a synthetic method, meaning that it is turned into a sequence of curves by
+ * default. Subclassers may add native support for arcs.
+ * 
+ * Constructor: mxXmlCanvas2D
+ *
+ * Constructs a new abstract canvas.
+ */
+function mxXmlCanvas2D(root)
+{
+	mxAbstractCanvas2D.call(this);
+
+	/**
+	 * Variable: root
+	 * 
+	 * Reference to the container for the SVG content.
+	 */
+	this.root = root;
+
+	// Writes default settings;
+	this.writeDefaults();
+};
+
+/**
+ * Extends mxAbstractCanvas2D
+ */
+mxUtils.extend(mxXmlCanvas2D, mxAbstractCanvas2D);
+
+/**
+ * Variable: textEnabled
+ * 
+ * Specifies if text output should be enabled. Default is true.
+ */
+mxXmlCanvas2D.prototype.textEnabled = true;
+
+/**
+ * Variable: compressed
+ * 
+ * Specifies if the output should be compressed by removing redundant calls.
+ * Default is true.
+ */
+mxXmlCanvas2D.prototype.compressed = true;
+
+/**
+ * Function: writeDefaults
+ * 
+ * Writes the rendering defaults to <root>:
+ */
+mxXmlCanvas2D.prototype.writeDefaults = function()
+{
+	var elem;
+	
+	// Writes font defaults
+	elem = this.createElement('fontfamily');
+	elem.setAttribute('family', mxConstants.DEFAULT_FONTFAMILY);
+	this.root.appendChild(elem);
+	
+	elem = this.createElement('fontsize');
+	elem.setAttribute('size', mxConstants.DEFAULT_FONTSIZE);
+	this.root.appendChild(elem);
+	
+	// Writes shadow defaults
+	elem = this.createElement('shadowcolor');
+	elem.setAttribute('color', mxConstants.SHADOWCOLOR);
+	this.root.appendChild(elem);
+	
+	elem = this.createElement('shadowalpha');
+	elem.setAttribute('alpha', mxConstants.SHADOW_OPACITY);
+	this.root.appendChild(elem);
+	
+	elem = this.createElement('shadowoffset');
+	elem.setAttribute('dx', mxConstants.SHADOW_OFFSET_X);
+	elem.setAttribute('dy', mxConstants.SHADOW_OFFSET_Y);
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: format
+ * 
+ * Returns a formatted number with 2 decimal places.
+ */
+mxXmlCanvas2D.prototype.format = function(value)
+{
+	return parseFloat(parseFloat(value).toFixed(2));
+};
+
+/**
+ * Function: createElement
+ * 
+ * Creates the given element using the owner document of <root>.
+ */
+mxXmlCanvas2D.prototype.createElement = function(name)
+{
+	return this.root.ownerDocument.createElement(name);
+};
+
+/**
+ * Function: save
+ * 
+ * Saves the drawing state.
+ */
+mxXmlCanvas2D.prototype.save = function()
+{
+	if (this.compressed)
+	{
+		mxAbstractCanvas2D.prototype.save.apply(this, arguments);
+	}
+	
+	this.root.appendChild(this.createElement('save'));
+};
+
+/**
+ * Function: restore
+ * 
+ * Restores the drawing state.
+ */
+mxXmlCanvas2D.prototype.restore = function()
+{
+	if (this.compressed)
+	{
+		mxAbstractCanvas2D.prototype.restore.apply(this, arguments);
+	}
+	
+	this.root.appendChild(this.createElement('restore'));
+};
+
+/**
+ * Function: scale
+ * 
+ * Scales the output.
+ * 
+ * Parameters:
+ * 
+ * scale - Number that represents the scale where 1 is equal to 100%.
+ */
+mxXmlCanvas2D.prototype.scale = function(value)
+{
+        var elem = this.createElement('scale');
+        elem.setAttribute('scale', value);
+        this.root.appendChild(elem);
+};
+
+/**
+ * Function: translate
+ * 
+ * Translates the output.
+ * 
+ * Parameters:
+ * 
+ * dx - Number that specifies the horizontal translation.
+ * dy - Number that specifies the vertical translation.
+ */
+mxXmlCanvas2D.prototype.translate = function(dx, dy)
+{
+	var elem = this.createElement('translate');
+	elem.setAttribute('dx', this.format(dx));
+	elem.setAttribute('dy', this.format(dy));
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: rotate
+ * 
+ * Rotates and/or flips the output around a given center. (Note: Due to
+ * limitations in VML, the rotation cannot be concatenated.)
+ * 
+ * Parameters:
+ * 
+ * theta - Number that represents the angle of the rotation (in degrees).
+ * flipH - Boolean indicating if the output should be flipped horizontally.
+ * flipV - Boolean indicating if the output should be flipped vertically.
+ * cx - Number that represents the x-coordinate of the rotation center.
+ * cy - Number that represents the y-coordinate of the rotation center.
+ */
+mxXmlCanvas2D.prototype.rotate = function(theta, flipH, flipV, cx, cy)
+{
+	var elem = this.createElement('rotate');
+	
+	if (theta != 0 || flipH || flipV)
+	{
+		elem.setAttribute('theta', this.format(theta));
+		elem.setAttribute('flipH', (flipH) ? '1' : '0');
+		elem.setAttribute('flipV', (flipV) ? '1' : '0');
+		elem.setAttribute('cx', this.format(cx));
+		elem.setAttribute('cy', this.format(cy));
+		this.root.appendChild(elem);
+	}
+};
+
+/**
+ * Function: setAlpha
+ * 
+ * Sets the current alpha.
+ * 
+ * Parameters:
+ * 
+ * value - Number that represents the new alpha. Possible values are between
+ * 1 (opaque) and 0 (transparent).
+ */
+mxXmlCanvas2D.prototype.setAlpha = function(value)
+{
+	if (this.compressed)
+	{
+		if (this.state.alpha == value)
+		{
+			return;
+		}
+		
+		mxAbstractCanvas2D.prototype.setAlpha.apply(this, arguments);
+	}
+	
+	var elem = this.createElement('alpha');
+	elem.setAttribute('alpha', this.format(value));
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: setFillAlpha
+ * 
+ * Sets the current fill alpha.
+ * 
+ * Parameters:
+ * 
+ * value - Number that represents the new fill alpha. Possible values are between
+ * 1 (opaque) and 0 (transparent).
+ */
+mxXmlCanvas2D.prototype.setFillAlpha = function(value)
+{
+	if (this.compressed)
+	{
+		if (this.state.fillAlpha == value)
+		{
+			return;
+		}
+		
+		mxAbstractCanvas2D.prototype.setFillAlpha.apply(this, arguments);
+	}
+	
+	var elem = this.createElement('fillalpha');
+	elem.setAttribute('alpha', this.format(value));
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: setStrokeAlpha
+ * 
+ * Sets the current stroke alpha.
+ * 
+ * Parameters:
+ * 
+ * value - Number that represents the new stroke alpha. Possible values are between
+ * 1 (opaque) and 0 (transparent).
+ */
+mxXmlCanvas2D.prototype.setStrokeAlpha = function(value)
+{
+	if (this.compressed)
+	{
+		if (this.state.strokeAlpha == value)
+		{
+			return;
+		}
+		
+		mxAbstractCanvas2D.prototype.setStrokeAlpha.apply(this, arguments);
+	}
+	
+	var elem = this.createElement('strokealpha');
+	elem.setAttribute('alpha', this.format(value));
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: setFillColor
+ * 
+ * Sets the current fill color.
+ * 
+ * Parameters:
+ * 
+ * value - Hexadecimal representation of the color or 'none'.
+ */
+mxXmlCanvas2D.prototype.setFillColor = function(value)
+{
+	if (value == mxConstants.NONE)
+	{
+		value = null;
+	}
+	
+	if (this.compressed)
+	{
+		if (this.state.fillColor == value)
+		{
+			return;
+		}
+		
+		mxAbstractCanvas2D.prototype.setFillColor.apply(this, arguments);
+	}
+	
+	var elem = this.createElement('fillcolor');
+	elem.setAttribute('color', (value != null) ? value : mxConstants.NONE);
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: setGradient
+ * 
+ * Sets the gradient. Note that the coordinates may be ignored by some implementations.
+ * 
+ * Parameters:
+ * 
+ * color1 - Hexadecimal representation of the start color.
+ * color2 - Hexadecimal representation of the end color.
+ * x - X-coordinate of the gradient region.
+ * y - y-coordinate of the gradient region.
+ * w - Width of the gradient region.
+ * h - Height of the gradient region.
+ * direction - One of <mxConstants.DIRECTION_NORTH>, <mxConstants.DIRECTION_EAST>,
+ * <mxConstants.DIRECTION_SOUTH> or <mxConstants.DIRECTION_WEST>.
+ * alpha1 - Optional alpha of the start color. Default is 1. Possible values
+ * are between 1 (opaque) and 0 (transparent).
+ * alpha2 - Optional alpha of the end color. Default is 1. Possible values
+ * are between 1 (opaque) and 0 (transparent).
+ */
+mxXmlCanvas2D.prototype.setGradient = function(color1, color2, x, y, w, h, direction, alpha1, alpha2)
+{
+	if (color1 != null && color2 != null)
+	{
+		mxAbstractCanvas2D.prototype.setGradient.apply(this, arguments);
+		
+		var elem = this.createElement('gradient');
+		elem.setAttribute('c1', color1);
+		elem.setAttribute('c2', color2);
+		elem.setAttribute('x', this.format(x));
+		elem.setAttribute('y', this.format(y));
+		elem.setAttribute('w', this.format(w));
+		elem.setAttribute('h', this.format(h));
+		
+		// Default direction is south
+		if (direction != null)
+		{
+			elem.setAttribute('direction', direction);
+		}
+		
+		if (alpha1 != null)
+		{
+			elem.setAttribute('alpha1', alpha1);
+		}
+		
+		if (alpha2 != null)
+		{
+			elem.setAttribute('alpha2', alpha2);
+		}
+		
+		this.root.appendChild(elem);
+	}
+};
+
+/**
+ * Function: setStrokeColor
+ * 
+ * Sets the current stroke color.
+ * 
+ * Parameters:
+ * 
+ * value - Hexadecimal representation of the color or 'none'.
+ */
+mxXmlCanvas2D.prototype.setStrokeColor = function(value)
+{
+	if (value == mxConstants.NONE)
+	{
+		value = null;
+	}
+	
+	if (this.compressed)
+	{
+		if (this.state.strokeColor == value)
+		{
+			return;
+		}
+		
+		mxAbstractCanvas2D.prototype.setStrokeColor.apply(this, arguments);
+	}
+	
+	var elem = this.createElement('strokecolor');
+	elem.setAttribute('color', (value != null) ? value : mxConstants.NONE);
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: setStrokeWidth
+ * 
+ * Sets the current stroke width.
+ * 
+ * Parameters:
+ * 
+ * value - Numeric representation of the stroke width.
+ */
+mxXmlCanvas2D.prototype.setStrokeWidth = function(value)
+{
+	if (this.compressed)
+	{
+		if (this.state.strokeWidth == value)
+		{
+			return;
+		}
+		
+		mxAbstractCanvas2D.prototype.setStrokeWidth.apply(this, arguments);
+	}
+	
+	var elem = this.createElement('strokewidth');
+	elem.setAttribute('width', this.format(value));
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: setDashed
+ * 
+ * Enables or disables dashed lines.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean that specifies if dashed lines should be enabled.
+ * value - Boolean that specifies if the stroke width should be ignored
+ * for the dash pattern. Default is false.
+ */
+mxXmlCanvas2D.prototype.setDashed = function(value, fixDash)
+{
+	if (this.compressed)
+	{
+		if (this.state.dashed == value)
+		{
+			return;
+		}
+		
+		mxAbstractCanvas2D.prototype.setDashed.apply(this, arguments);
+	}
+	
+	var elem = this.createElement('dashed');
+	elem.setAttribute('dashed', (value) ? '1' : '0');
+	
+	if (fixDash != null)
+	{
+		elem.setAttribute('fixDash', (fixDash) ? '1' : '0');
+	}
+	
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: setDashPattern
+ * 
+ * Sets the current dash pattern. Default is '3 3'.
+ * 
+ * Parameters:
+ * 
+ * value - String that represents the dash pattern, which is a sequence of
+ * numbers defining the length of the dashes and the length of the spaces
+ * between the dashes. The lengths are relative to the line width - a length
+ * of 1 is equals to the line width.
+ */
+mxXmlCanvas2D.prototype.setDashPattern = function(value)
+{
+	if (this.compressed)
+	{
+		if (this.state.dashPattern == value)
+		{
+			return;
+		}
+		
+		mxAbstractCanvas2D.prototype.setDashPattern.apply(this, arguments);
+	}
+	
+	var elem = this.createElement('dashpattern');
+	elem.setAttribute('pattern', value);
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: setLineCap
+ * 
+ * Sets the line cap. Default is 'flat' which corresponds to 'butt' in SVG.
+ * 
+ * Parameters:
+ * 
+ * value - String that represents the line cap. Possible values are flat, round
+ * and square.
+ */
+mxXmlCanvas2D.prototype.setLineCap = function(value)
+{
+	if (this.compressed)
+	{
+		if (this.state.lineCap == value)
+		{
+			return;
+		}
+		
+		mxAbstractCanvas2D.prototype.setLineCap.apply(this, arguments);
+	}
+	
+	var elem = this.createElement('linecap');
+	elem.setAttribute('cap', value);
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: setLineJoin
+ * 
+ * Sets the line join. Default is 'miter'.
+ * 
+ * Parameters:
+ * 
+ * value - String that represents the line join. Possible values are miter,
+ * round and bevel.
+ */
+mxXmlCanvas2D.prototype.setLineJoin = function(value)
+{
+	if (this.compressed)
+	{
+		if (this.state.lineJoin == value)
+		{
+			return;
+		}
+		
+		mxAbstractCanvas2D.prototype.setLineJoin.apply(this, arguments);
+	}
+	
+	var elem = this.createElement('linejoin');
+	elem.setAttribute('join', value);
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: setMiterLimit
+ * 
+ * Sets the miter limit. Default is 10.
+ * 
+ * Parameters:
+ * 
+ * value - Number that represents the miter limit.
+ */
+mxXmlCanvas2D.prototype.setMiterLimit = function(value)
+{
+	if (this.compressed)
+	{
+		if (this.state.miterLimit == value)
+		{
+			return;
+		}
+		
+		mxAbstractCanvas2D.prototype.setMiterLimit.apply(this, arguments);
+	}
+	
+	var elem = this.createElement('miterlimit');
+	elem.setAttribute('limit', value);
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: setFontColor
+ * 
+ * Sets the current font color. Default is '#000000'.
+ * 
+ * Parameters:
+ * 
+ * value - Hexadecimal representation of the color or 'none'.
+ */
+mxXmlCanvas2D.prototype.setFontColor = function(value)
+{
+	if (this.textEnabled)
+	{
+		if (value == mxConstants.NONE)
+		{
+			value = null;
+		}
+		
+		if (this.compressed)
+		{
+			if (this.state.fontColor == value)
+			{
+				return;
+			}
+			
+			mxAbstractCanvas2D.prototype.setFontColor.apply(this, arguments);
+		}
+		
+		var elem = this.createElement('fontcolor');
+		elem.setAttribute('color', (value != null) ? value : mxConstants.NONE);
+		this.root.appendChild(elem);
+	}
+};
+
+/**
+ * Function: setFontBackgroundColor
+ * 
+ * Sets the current font background color.
+ * 
+ * Parameters:
+ * 
+ * value - Hexadecimal representation of the color or 'none'.
+ */
+mxXmlCanvas2D.prototype.setFontBackgroundColor = function(value)
+{
+	if (this.textEnabled)
+	{
+		if (value == mxConstants.NONE)
+		{
+			value = null;
+		}
+		
+		if (this.compressed)
+		{
+			if (this.state.fontBackgroundColor == value)
+			{
+				return;
+			}
+			
+			mxAbstractCanvas2D.prototype.setFontBackgroundColor.apply(this, arguments);
+		}
+
+		var elem = this.createElement('fontbackgroundcolor');
+		elem.setAttribute('color', (value != null) ? value : mxConstants.NONE);
+		this.root.appendChild(elem);
+	}
+};
+
+/**
+ * Function: setFontBorderColor
+ * 
+ * Sets the current font border color.
+ * 
+ * Parameters:
+ * 
+ * value - Hexadecimal representation of the color or 'none'.
+ */
+mxXmlCanvas2D.prototype.setFontBorderColor = function(value)
+{
+	if (this.textEnabled)
+	{
+		if (value == mxConstants.NONE)
+		{
+			value = null;
+		}
+		
+		if (this.compressed)
+		{
+			if (this.state.fontBorderColor == value)
+			{
+				return;
+			}
+			
+			mxAbstractCanvas2D.prototype.setFontBorderColor.apply(this, arguments);
+		}
+		
+		var elem = this.createElement('fontbordercolor');
+		elem.setAttribute('color', (value != null) ? value : mxConstants.NONE);
+		this.root.appendChild(elem);
+	}
+};
+
+/**
+ * Function: setFontSize
+ * 
+ * Sets the current font size. Default is <mxConstants.DEFAULT_FONTSIZE>.
+ * 
+ * Parameters:
+ * 
+ * value - Numeric representation of the font size.
+ */
+mxXmlCanvas2D.prototype.setFontSize = function(value)
+{
+	if (this.textEnabled)
+	{
+		if (this.compressed)
+		{
+			if (this.state.fontSize == value)
+			{
+				return;
+			}
+			
+			mxAbstractCanvas2D.prototype.setFontSize.apply(this, arguments);
+		}
+		
+		var elem = this.createElement('fontsize');
+		elem.setAttribute('size', value);
+		this.root.appendChild(elem);
+	}
+};
+
+/**
+ * Function: setFontFamily
+ * 
+ * Sets the current font family. Default is <mxConstants.DEFAULT_FONTFAMILY>.
+ * 
+ * Parameters:
+ * 
+ * value - String representation of the font family. This handles the same
+ * values as the CSS font-family property.
+ */
+mxXmlCanvas2D.prototype.setFontFamily = function(value)
+{
+	if (this.textEnabled)
+	{
+		if (this.compressed)
+		{
+			if (this.state.fontFamily == value)
+			{
+				return;
+			}
+			
+			mxAbstractCanvas2D.prototype.setFontFamily.apply(this, arguments);
+		}
+		
+		var elem = this.createElement('fontfamily');
+		elem.setAttribute('family', value);
+		this.root.appendChild(elem);
+	}
+};
+
+/**
+ * Function: setFontStyle
+ * 
+ * Sets the current font style.
+ * 
+ * Parameters:
+ * 
+ * value - Numeric representation of the font family. This is the sum of the
+ * font styles from <mxConstants>.
+ */
+mxXmlCanvas2D.prototype.setFontStyle = function(value)
+{
+	if (this.textEnabled)
+	{
+		if (value == null)
+		{
+			value = 0;
+		}
+		
+		if (this.compressed)
+		{
+			if (this.state.fontStyle == value)
+			{
+				return;
+			}
+			
+			mxAbstractCanvas2D.prototype.setFontStyle.apply(this, arguments);
+		}
+		
+		var elem = this.createElement('fontstyle');
+		elem.setAttribute('style', value);
+		this.root.appendChild(elem);
+	}
+};
+
+/**
+ * Function: setShadow
+ * 
+ * Enables or disables shadows.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean that specifies if shadows should be enabled.
+ */
+mxXmlCanvas2D.prototype.setShadow = function(value)
+{
+	if (this.compressed)
+	{
+		if (this.state.shadow == value)
+		{
+			return;
+		}
+		
+		mxAbstractCanvas2D.prototype.setShadow.apply(this, arguments);
+	}
+	
+	var elem = this.createElement('shadow');
+	elem.setAttribute('enabled', (value) ? '1' : '0');
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: setShadowColor
+ * 
+ * Sets the current shadow color. Default is <mxConstants.SHADOWCOLOR>.
+ * 
+ * Parameters:
+ * 
+ * value - Hexadecimal representation of the color or 'none'.
+ */
+mxXmlCanvas2D.prototype.setShadowColor = function(value)
+{
+	if (this.compressed)
+	{
+		if (value == mxConstants.NONE)
+		{
+			value = null;
+		}
+		
+		if (this.state.shadowColor == value)
+		{
+			return;
+		}
+		
+		mxAbstractCanvas2D.prototype.setShadowColor.apply(this, arguments);
+	}
+	
+	var elem = this.createElement('shadowcolor');
+	elem.setAttribute('color', (value != null) ? value : mxConstants.NONE);
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: setShadowAlpha
+ * 
+ * Sets the current shadows alpha. Default is <mxConstants.SHADOW_OPACITY>.
+ * 
+ * Parameters:
+ * 
+ * value - Number that represents the new alpha. Possible values are between
+ * 1 (opaque) and 0 (transparent).
+ */
+mxXmlCanvas2D.prototype.setShadowAlpha = function(value)
+{
+	if (this.compressed)
+	{
+		if (this.state.shadowAlpha == value)
+		{
+			return;
+		}
+		
+		mxAbstractCanvas2D.prototype.setShadowAlpha.apply(this, arguments);
+	}
+	
+	var elem = this.createElement('shadowalpha');
+	elem.setAttribute('alpha', value);
+	this.root.appendChild(elem);
+	
+};
+
+/**
+ * Function: setShadowOffset
+ * 
+ * Sets the current shadow offset.
+ * 
+ * Parameters:
+ * 
+ * dx - Number that represents the horizontal offset of the shadow.
+ * dy - Number that represents the vertical offset of the shadow.
+ */
+mxXmlCanvas2D.prototype.setShadowOffset = function(dx, dy)
+{
+	if (this.compressed)
+	{
+		if (this.state.shadowDx == dx && this.state.shadowDy == dy)
+		{
+			return;
+		}
+		
+		mxAbstractCanvas2D.prototype.setShadowOffset.apply(this, arguments);
+	}
+	
+	var elem = this.createElement('shadowoffset');
+	elem.setAttribute('dx', dx);
+	elem.setAttribute('dy', dy);
+	this.root.appendChild(elem);
+	
+};
+
+/**
+ * Function: rect
+ * 
+ * Puts a rectangle into the drawing buffer.
+ * 
+ * Parameters:
+ * 
+ * x - Number that represents the x-coordinate of the rectangle.
+ * y - Number that represents the y-coordinate of the rectangle.
+ * w - Number that represents the width of the rectangle.
+ * h - Number that represents the height of the rectangle.
+ */
+mxXmlCanvas2D.prototype.rect = function(x, y, w, h)
+{
+	var elem = this.createElement('rect');
+	elem.setAttribute('x', this.format(x));
+	elem.setAttribute('y', this.format(y));
+	elem.setAttribute('w', this.format(w));
+	elem.setAttribute('h', this.format(h));
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: roundrect
+ * 
+ * Puts a rounded rectangle into the drawing buffer.
+ * 
+ * Parameters:
+ * 
+ * x - Number that represents the x-coordinate of the rectangle.
+ * y - Number that represents the y-coordinate of the rectangle.
+ * w - Number that represents the width of the rectangle.
+ * h - Number that represents the height of the rectangle.
+ * dx - Number that represents the horizontal rounding.
+ * dy - Number that represents the vertical rounding.
+ */
+mxXmlCanvas2D.prototype.roundrect = function(x, y, w, h, dx, dy)
+{
+	var elem = this.createElement('roundrect');
+	elem.setAttribute('x', this.format(x));
+	elem.setAttribute('y', this.format(y));
+	elem.setAttribute('w', this.format(w));
+	elem.setAttribute('h', this.format(h));
+	elem.setAttribute('dx', this.format(dx));
+	elem.setAttribute('dy', this.format(dy));
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: ellipse
+ * 
+ * Puts an ellipse into the drawing buffer.
+ * 
+ * Parameters:
+ * 
+ * x - Number that represents the x-coordinate of the ellipse.
+ * y - Number that represents the y-coordinate of the ellipse.
+ * w - Number that represents the width of the ellipse.
+ * h - Number that represents the height of the ellipse.
+ */
+mxXmlCanvas2D.prototype.ellipse = function(x, y, w, h)
+{
+	var elem = this.createElement('ellipse');
+	elem.setAttribute('x', this.format(x));
+	elem.setAttribute('y', this.format(y));
+	elem.setAttribute('w', this.format(w));
+	elem.setAttribute('h', this.format(h));
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: image
+ * 
+ * Paints an image.
+ * 
+ * Parameters:
+ * 
+ * x - Number that represents the x-coordinate of the image.
+ * y - Number that represents the y-coordinate of the image.
+ * w - Number that represents the width of the image.
+ * h - Number that represents the height of the image.
+ * src - String that specifies the URL of the image.
+ * aspect - Boolean indicating if the aspect of the image should be preserved.
+ * flipH - Boolean indicating if the image should be flipped horizontally.
+ * flipV - Boolean indicating if the image should be flipped vertically.
+ */
+mxXmlCanvas2D.prototype.image = function(x, y, w, h, src, aspect, flipH, flipV)
+{
+	src = this.converter.convert(src);
+	
+	// LATER: Add option for embedding images as base64.
+	var elem = this.createElement('image');
+	elem.setAttribute('x', this.format(x));
+	elem.setAttribute('y', this.format(y));
+	elem.setAttribute('w', this.format(w));
+	elem.setAttribute('h', this.format(h));
+	elem.setAttribute('src', src);
+	elem.setAttribute('aspect', (aspect) ? '1' : '0');
+	elem.setAttribute('flipH', (flipH) ? '1' : '0');
+	elem.setAttribute('flipV', (flipV) ? '1' : '0');
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: begin
+ * 
+ * Starts a new path and puts it into the drawing buffer.
+ */
+mxXmlCanvas2D.prototype.begin = function()
+{
+	this.root.appendChild(this.createElement('begin'));
+	this.lastX = 0;
+	this.lastY = 0;
+};
+
+/**
+ * Function: moveTo
+ * 
+ * Moves the current path the given point.
+ * 
+ * Parameters:
+ * 
+ * x - Number that represents the x-coordinate of the point.
+ * y - Number that represents the y-coordinate of the point.
+ */
+mxXmlCanvas2D.prototype.moveTo = function(x, y)
+{
+	var elem = this.createElement('move');
+	elem.setAttribute('x', this.format(x));
+	elem.setAttribute('y', this.format(y));
+	this.root.appendChild(elem);
+	this.lastX = x;
+	this.lastY = y;
+};
+
+/**
+ * Function: lineTo
+ * 
+ * Draws a line to the given coordinates.
+ * 
+ * Parameters:
+ * 
+ * x - Number that represents the x-coordinate of the endpoint.
+ * y - Number that represents the y-coordinate of the endpoint.
+ */
+mxXmlCanvas2D.prototype.lineTo = function(x, y)
+{
+	var elem = this.createElement('line');
+	elem.setAttribute('x', this.format(x));
+	elem.setAttribute('y', this.format(y));
+	this.root.appendChild(elem);
+	this.lastX = x;
+	this.lastY = y;
+};
+
+/**
+ * Function: quadTo
+ * 
+ * Adds a quadratic curve to the current path.
+ * 
+ * Parameters:
+ * 
+ * x1 - Number that represents the x-coordinate of the control point.
+ * y1 - Number that represents the y-coordinate of the control point.
+ * x2 - Number that represents the x-coordinate of the endpoint.
+ * y2 - Number that represents the y-coordinate of the endpoint.
+ */
+mxXmlCanvas2D.prototype.quadTo = function(x1, y1, x2, y2)
+{
+	var elem = this.createElement('quad');
+	elem.setAttribute('x1', this.format(x1));
+	elem.setAttribute('y1', this.format(y1));
+	elem.setAttribute('x2', this.format(x2));
+	elem.setAttribute('y2', this.format(y2));
+	this.root.appendChild(elem);
+	this.lastX = x2;
+	this.lastY = y2;
+};
+
+/**
+ * Function: curveTo
+ * 
+ * Adds a bezier curve to the current path.
+ * 
+ * Parameters:
+ * 
+ * x1 - Number that represents the x-coordinate of the first control point.
+ * y1 - Number that represents the y-coordinate of the first control point.
+ * x2 - Number that represents the x-coordinate of the second control point.
+ * y2 - Number that represents the y-coordinate of the second control point.
+ * x3 - Number that represents the x-coordinate of the endpoint.
+ * y3 - Number that represents the y-coordinate of the endpoint.
+ */
+mxXmlCanvas2D.prototype.curveTo = function(x1, y1, x2, y2, x3, y3)
+{
+	var elem = this.createElement('curve');
+	elem.setAttribute('x1', this.format(x1));
+	elem.setAttribute('y1', this.format(y1));
+	elem.setAttribute('x2', this.format(x2));
+	elem.setAttribute('y2', this.format(y2));
+	elem.setAttribute('x3', this.format(x3));
+	elem.setAttribute('y3', this.format(y3));
+	this.root.appendChild(elem);
+	this.lastX = x3;
+	this.lastY = y3;
+};
+
+/**
+ * Function: close
+ * 
+ * Closes the current path.
+ */
+mxXmlCanvas2D.prototype.close = function()
+{
+	this.root.appendChild(this.createElement('close'));
+};
+
+/**
+ * Function: text
+ * 
+ * Paints the given text. Possible values for format are empty string for
+ * plain text and html for HTML markup. Background and border color as well
+ * as clipping is not available in plain text labels for VML. HTML labels
+ * are not available as part of shapes with no foreignObject support in SVG
+ * (eg. IE9, IE10).
+ * 
+ * Parameters:
+ * 
+ * x - Number that represents the x-coordinate of the text.
+ * y - Number that represents the y-coordinate of the text.
+ * w - Number that represents the available width for the text or 0 for automatic width.
+ * h - Number that represents the available height for the text or 0 for automatic height.
+ * str - String that specifies the text to be painted.
+ * align - String that represents the horizontal alignment.
+ * valign - String that represents the vertical alignment.
+ * wrap - Boolean that specifies if word-wrapping is enabled. Requires w > 0.
+ * format - Empty string for plain text or 'html' for HTML markup.
+ * overflow - Specifies the overflow behaviour of the label. Requires w > 0 and/or h > 0.
+ * clip - Boolean that specifies if the label should be clipped. Requires w > 0 and/or h > 0.
+ * rotation - Number that specifies the angle of the rotation around the anchor point of the text.
+ * dir - Optional string that specifies the text direction. Possible values are rtl and lrt.
+ */
+mxXmlCanvas2D.prototype.text = function(x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation, dir)
+{
+	if (this.textEnabled && str != null)
+	{
+		if (mxUtils.isNode(str))
+		{
+			str = mxUtils.getOuterHtml(str);
+		}
+		
+		var elem = this.createElement('text');
+		elem.setAttribute('x', this.format(x));
+		elem.setAttribute('y', this.format(y));
+		elem.setAttribute('w', this.format(w));
+		elem.setAttribute('h', this.format(h));
+		elem.setAttribute('str', str);
+		
+		if (align != null)
+		{
+			elem.setAttribute('align', align);
+		}
+		
+		if (valign != null)
+		{
+			elem.setAttribute('valign', valign);
+		}
+		
+		elem.setAttribute('wrap', (wrap) ? '1' : '0');
+		
+		if (format == null)
+		{
+			format = '';
+		}
+		
+		elem.setAttribute('format', format);
+		
+		if (overflow != null)
+		{
+			elem.setAttribute('overflow', overflow);
+		}
+		
+		if (clip != null)
+		{
+			elem.setAttribute('clip', (clip) ? '1' : '0');
+		}
+		
+		if (rotation != null)
+		{
+			elem.setAttribute('rotation', rotation);
+		}
+		
+		if (dir != null)
+		{
+			elem.setAttribute('dir', dir);
+		}
+		
+		this.root.appendChild(elem);
+	}
+};
+
+/**
+ * Function: stroke
+ * 
+ * Paints the outline of the current drawing buffer.
+ */
+mxXmlCanvas2D.prototype.stroke = function()
+{
+	this.root.appendChild(this.createElement('stroke'));
+};
+
+/**
+ * Function: fill
+ * 
+ * Fills the current drawing buffer.
+ */
+mxXmlCanvas2D.prototype.fill = function()
+{
+	this.root.appendChild(this.createElement('fill'));
+};
+
+/**
+ * Function: fillAndStroke
+ * 
+ * Fills the current drawing buffer and its outline.
+ */
+mxXmlCanvas2D.prototype.fillAndStroke = function()
+{
+	this.root.appendChild(this.createElement('fillstroke'));
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxSvgCanvas2D
+ *
+ * Extends <mxAbstractCanvas2D> to implement a canvas for SVG. This canvas writes all
+ * calls as SVG output to the given SVG root node.
+ * 
+ * (code)
+ * var svgDoc = mxUtils.createXmlDocument();
+ * var root = (svgDoc.createElementNS != null) ?
+ * 		svgDoc.createElementNS(mxConstants.NS_SVG, 'svg') : svgDoc.createElement('svg');
+ * 
+ * if (svgDoc.createElementNS == null)
+ * {
+ *   root.setAttribute('xmlns', mxConstants.NS_SVG);
+ *   root.setAttribute('xmlns:xlink', mxConstants.NS_XLINK);
+ * }
+ * else
+ * {
+ *   root.setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xlink', mxConstants.NS_XLINK);
+ * }
+ * 
+ * var bounds = graph.getGraphBounds();
+ * root.setAttribute('width', (bounds.x + bounds.width + 4) + 'px');
+ * root.setAttribute('height', (bounds.y + bounds.height + 4) + 'px');
+ * root.setAttribute('version', '1.1');
+ * 
+ * svgDoc.appendChild(root);
+ * 
+ * var svgCanvas = new mxSvgCanvas2D(root);
+ * (end)
+ * 
+ * A description of the public API is available in <mxXmlCanvas2D>.
+ * 
+ * To disable anti-aliasing in the output, use the following code.
+ * 
+ * (code)
+ * graph.view.canvas.ownerSVGElement.setAttribute('shape-rendering', 'crispEdges');
+ * (end)
+ * 
+ * Or set the respective attribute in the SVG element directly.
+ * 
+ * Constructor: mxSvgCanvas2D
+ *
+ * Constructs a new SVG canvas.
+ * 
+ * Parameters:
+ * 
+ * root - SVG container for the output.
+ * styleEnabled - Optional boolean that specifies if a style section should be
+ * added. The style section sets the default font-size, font-family and
+ * stroke-miterlimit globally. Default is false.
+ */
+function mxSvgCanvas2D(root, styleEnabled)
+{
+	mxAbstractCanvas2D.call(this);
+
+	/**
+	 * Variable: root
+	 * 
+	 * Reference to the container for the SVG content.
+	 */
+	this.root = root;
+
+	/**
+	 * Variable: gradients
+	 * 
+	 * Local cache of gradients for quick lookups.
+	 */
+	this.gradients = [];
+
+	/**
+	 * Variable: defs
+	 * 
+	 * Reference to the defs section of the SVG document. Only for export.
+	 */
+	this.defs = null;
+	
+	/**
+	 * Variable: styleEnabled
+	 * 
+	 * Stores the value of styleEnabled passed to the constructor.
+	 */
+	this.styleEnabled = (styleEnabled != null) ? styleEnabled : false;
+	
+	var svg = null;
+	
+	// Adds optional defs section for export
+	if (root.ownerDocument != document)
+	{
+		var node = root;
+
+		// Finds owner SVG element in XML DOM
+		while (node != null && node.nodeName != 'svg')
+		{
+			node = node.parentNode;
+		}
+		
+		svg = node;
+	}
+
+	if (svg != null)
+	{
+		// Tries to get existing defs section
+		var tmp = svg.getElementsByTagName('defs');
+		
+		if (tmp.length > 0)
+		{
+			this.defs = svg.getElementsByTagName('defs')[0];
+		}
+		
+		// Adds defs section if none exists
+		if (this.defs == null)
+		{
+			this.defs = this.createElement('defs');
+			
+			if (svg.firstChild != null)
+			{
+				svg.insertBefore(this.defs, svg.firstChild);
+			}
+			else
+			{
+				svg.appendChild(this.defs);
+			}
+		}
+
+		// Adds stylesheet
+		if (this.styleEnabled)
+		{
+			this.defs.appendChild(this.createStyle());
+		}
+	}
+};
+
+/**
+ * Extends mxAbstractCanvas2D
+ */
+mxUtils.extend(mxSvgCanvas2D, mxAbstractCanvas2D);
+
+/**
+ * Capability check for DOM parser.
+ */
+(function()
+{
+	mxSvgCanvas2D.prototype.useDomParser = !mxClient.IS_IE && typeof DOMParser === 'function' && typeof XMLSerializer === 'function';
+	
+	if (mxSvgCanvas2D.prototype.useDomParser)
+	{
+		// Checks using a generic test text if the parsing actually works. This is a workaround
+		// for older browsers where the capability check returns true but the parsing fails.
+		try
+		{
+			var doc = new DOMParser().parseFromString('test text', 'text/html');
+			mxSvgCanvas2D.prototype.useDomParser = doc != null;
+		}
+		catch (e)
+		{
+			mxSvgCanvas2D.prototype.useDomParser = false;
+		}
+	}
+})();
+
+/**
+ * Variable: path
+ * 
+ * Holds the current DOM node.
+ */
+mxSvgCanvas2D.prototype.node = null;
+
+/**
+ * Variable: matchHtmlAlignment
+ * 
+ * Specifies if plain text output should match the vertical HTML alignment.
+ * Defaul is true.
+ */
+mxSvgCanvas2D.prototype.matchHtmlAlignment = true;
+
+/**
+ * Variable: textEnabled
+ * 
+ * Specifies if text output should be enabled. Default is true.
+ */
+mxSvgCanvas2D.prototype.textEnabled = true;
+
+/**
+ * Variable: foEnabled
+ * 
+ * Specifies if use of foreignObject for HTML markup is allowed. Default is true.
+ */
+mxSvgCanvas2D.prototype.foEnabled = true;
+
+/**
+ * Variable: foAltText
+ * 
+ * Specifies the fallback text for unsupported foreignObjects in exported
+ * documents. Default is '[Object]'. If this is set to null then no fallback
+ * text is added to the exported document.
+ */
+mxSvgCanvas2D.prototype.foAltText = '[Object]';
+
+/**
+ * Variable: foOffset
+ * 
+ * Offset to be used for foreignObjects.
+ */
+mxSvgCanvas2D.prototype.foOffset = 0;
+
+/**
+ * Variable: textOffset
+ * 
+ * Offset to be used for text elements.
+ */
+mxSvgCanvas2D.prototype.textOffset = 0;
+
+/**
+ * Variable: imageOffset
+ * 
+ * Offset to be used for image elements.
+ */
+mxSvgCanvas2D.prototype.imageOffset = 0;
+
+/**
+ * Variable: strokeTolerance
+ * 
+ * Adds transparent paths for strokes.
+ */
+mxSvgCanvas2D.prototype.strokeTolerance = 0;
+
+/**
+ * Variable: refCount
+ * 
+ * Local counter for references in SVG export.
+ */
+mxSvgCanvas2D.prototype.refCount = 0;
+
+/**
+ * Variable: blockImagePointerEvents
+ * 
+ * Specifies if a transparent rectangle should be added on top of images to absorb
+ * all pointer events. Default is false. This is only needed in Firefox to disable
+ * control-clicks on images.
+ */
+mxSvgCanvas2D.prototype.blockImagePointerEvents = false;
+
+/**
+ * Variable: lineHeightCorrection
+ * 
+ * Correction factor for <mxConstants.LINE_HEIGHT> in HTML output. Default is 1.
+ */
+mxSvgCanvas2D.prototype.lineHeightCorrection = 1;
+
+/**
+ * Variable: pointerEventsValue
+ * 
+ * Default value for active pointer events. Default is all.
+ */
+mxSvgCanvas2D.prototype.pointerEventsValue = 'all';
+
+/**
+ * Variable: fontMetricsPadding
+ * 
+ * Padding to be added for text that is not wrapped to account for differences
+ * in font metrics on different platforms in pixels. Default is 10.
+ */
+mxSvgCanvas2D.prototype.fontMetricsPadding = 10;
+
+/**
+ * Variable: cacheOffsetSize
+ * 
+ * Specifies if offsetWidth and offsetHeight should be cached. Default is true.
+ * This is used to speed up repaint of text in <updateText>.
+ */
+mxSvgCanvas2D.prototype.cacheOffsetSize = true;
+
+/**
+ * Function: format
+ * 
+ * Rounds all numbers to 2 decimal points.
+ */
+mxSvgCanvas2D.prototype.format = function(value)
+{
+	return parseFloat(parseFloat(value).toFixed(2));
+};
+
+/**
+ * Function: getBaseUrl
+ * 
+ * Returns the URL of the page without the hash part. This needs to use href to
+ * include any search part with no params (ie question mark alone). This is a
+ * workaround for the fact that window.location.search is empty if there is
+ * no search string behind the question mark.
+ */
+mxSvgCanvas2D.prototype.getBaseUrl = function()
+{
+	var href = window.location.href;
+	var hash = href.lastIndexOf('#');
+	
+	if (hash > 0)
+	{
+		href = href.substring(0, hash);
+	}
+	
+	return href;
+};
+
+/**
+ * Function: reset
+ * 
+ * Returns any offsets for rendering pixels.
+ */
+mxSvgCanvas2D.prototype.reset = function()
+{
+	mxAbstractCanvas2D.prototype.reset.apply(this, arguments);
+	this.gradients = [];
+};
+
+/**
+ * Function: createStyle
+ * 
+ * Creates the optional style section.
+ */
+mxSvgCanvas2D.prototype.createStyle = function(x)
+{
+	var style = this.createElement('style');
+	style.setAttribute('type', 'text/css');
+	mxUtils.write(style, 'svg{font-family:' + mxConstants.DEFAULT_FONTFAMILY +
+			';font-size:' + mxConstants.DEFAULT_FONTSIZE +
+			';fill:none;stroke-miterlimit:10}');
+	
+	return style;
+};
+
+/**
+ * Function: createElement
+ * 
+ * Private helper function to create SVG elements
+ */
+mxSvgCanvas2D.prototype.createElement = function(tagName, namespace)
+{
+	if (this.root.ownerDocument.createElementNS != null)
+	{
+		return this.root.ownerDocument.createElementNS(namespace || mxConstants.NS_SVG, tagName);
+	}
+	else
+	{
+		var elt = this.root.ownerDocument.createElement(tagName);
+		
+		if (namespace != null)
+		{
+			elt.setAttribute('xmlns', namespace);
+		}
+		
+		return elt;
+	}
+};
+
+/**
+ * Function: getAlternateContent
+ * 
+ * Returns the alternate content for the given foreignObject.
+ */
+mxSvgCanvas2D.prototype.createAlternateContent = function(fo, x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation)
+{
+	if (this.foAltText != null)
+	{
+		var s = this.state;
+		var alt = this.createElement('text');
+		alt.setAttribute('x', Math.round(w / 2));
+		alt.setAttribute('y', Math.round((h + s.fontSize) / 2));
+		alt.setAttribute('fill', s.fontColor || 'black');
+		alt.setAttribute('text-anchor', 'middle');
+		alt.setAttribute('font-size', s.fontSize + 'px');
+		alt.setAttribute('font-family', s.fontFamily);
+		
+		if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
+		{
+			alt.setAttribute('font-weight', 'bold');
+		}
+		
+		if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
+		{
+			alt.setAttribute('font-style', 'italic');
+		}
+		
+		if ((s.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)
+		{
+			alt.setAttribute('text-decoration', 'underline');
+		}
+		
+		mxUtils.write(alt, this.foAltText);
+		
+		return alt;
+	}
+	else
+	{
+		return null;
+	}
+};
+
+/**
+ * Function: createGradientId
+ * 
+ * Private helper function to create SVG elements
+ */
+mxSvgCanvas2D.prototype.createGradientId = function(start, end, alpha1, alpha2, direction)
+{
+	// Removes illegal characters from gradient ID
+	if (start.charAt(0) == '#')
+	{
+		start = start.substring(1);
+	}
+	
+	if (end.charAt(0) == '#')
+	{
+		end = end.substring(1);
+	}
+	
+	// Workaround for gradient IDs not working in Safari 5 / Chrome 6
+	// if they contain uppercase characters
+	start = start.toLowerCase() + '-' + alpha1;
+	end = end.toLowerCase() + '-' + alpha2;
+
+	// Wrong gradient directions possible?
+	var dir = null;
+	
+	if (direction == null || direction == mxConstants.DIRECTION_SOUTH)
+	{
+		dir = 's';
+	}
+	else if (direction == mxConstants.DIRECTION_EAST)
+	{
+		dir = 'e';
+	}
+	else
+	{
+		var tmp = start;
+		start = end;
+		end = tmp;
+		
+		if (direction == mxConstants.DIRECTION_NORTH)
+		{
+			dir = 's';
+		}
+		else if (direction == mxConstants.DIRECTION_WEST)
+		{
+			dir = 'e';
+		}
+	}
+	
+	return 'mx-gradient-' + start + '-' + end + '-' + dir;
+};
+
+/**
+ * Function: getSvgGradient
+ * 
+ * Private helper function to create SVG elements
+ */
+mxSvgCanvas2D.prototype.getSvgGradient = function(start, end, alpha1, alpha2, direction)
+{
+	var id = this.createGradientId(start, end, alpha1, alpha2, direction);
+	var gradient = this.gradients[id];
+	
+	if (gradient == null)
+	{
+		var svg = this.root.ownerSVGElement;
+
+		var counter = 0;
+		var tmpId = id + '-' + counter;
+
+		if (svg != null)
+		{
+			gradient = svg.ownerDocument.getElementById(tmpId);
+			
+			while (gradient != null && gradient.ownerSVGElement != svg)
+			{
+				tmpId = id + '-' + counter++;
+				gradient = svg.ownerDocument.getElementById(tmpId);
+			}
+		}
+		else
+		{
+			// Uses shorter IDs for export
+			tmpId = 'id' + (++this.refCount);
+		}
+		
+		if (gradient == null)
+		{
+			gradient = this.createSvgGradient(start, end, alpha1, alpha2, direction);
+			gradient.setAttribute('id', tmpId);
+			
+			if (this.defs != null)
+			{
+				this.defs.appendChild(gradient);
+			}
+			else
+			{
+				svg.appendChild(gradient);
+			}
+		}
+
+		this.gradients[id] = gradient;
+	}
+
+	return gradient.getAttribute('id');
+};
+
+/**
+ * Function: createSvgGradient
+ * 
+ * Creates the given SVG gradient.
+ */
+mxSvgCanvas2D.prototype.createSvgGradient = function(start, end, alpha1, alpha2, direction)
+{
+	var gradient = this.createElement('linearGradient');
+	gradient.setAttribute('x1', '0%');
+	gradient.setAttribute('y1', '0%');
+	gradient.setAttribute('x2', '0%');
+	gradient.setAttribute('y2', '0%');
+	
+	if (direction == null || direction == mxConstants.DIRECTION_SOUTH)
+	{
+		gradient.setAttribute('y2', '100%');
+	}
+	else if (direction == mxConstants.DIRECTION_EAST)
+	{
+		gradient.setAttribute('x2', '100%');
+	}
+	else if (direction == mxConstants.DIRECTION_NORTH)
+	{
+		gradient.setAttribute('y1', '100%');
+	}
+	else if (direction == mxConstants.DIRECTION_WEST)
+	{
+		gradient.setAttribute('x1', '100%');
+	}
+	
+	var op = (alpha1 < 1) ? ';stop-opacity:' + alpha1 : '';
+	
+	var stop = this.createElement('stop');
+	stop.setAttribute('offset', '0%');
+	stop.setAttribute('style', 'stop-color:' + start + op);
+	gradient.appendChild(stop);
+	
+	op = (alpha2 < 1) ? ';stop-opacity:' + alpha2 : '';
+	
+	stop = this.createElement('stop');
+	stop.setAttribute('offset', '100%');
+	stop.setAttribute('style', 'stop-color:' + end + op);
+	gradient.appendChild(stop);
+	
+	return gradient;
+};
+
+/**
+ * Function: addNode
+ * 
+ * Private helper function to create SVG elements
+ */
+mxSvgCanvas2D.prototype.addNode = function(filled, stroked)
+{
+	var node = this.node;
+	var s = this.state;
+
+	if (node != null)
+	{
+		if (node.nodeName == 'path')
+		{
+			// Checks if the path is not empty
+			if (this.path != null && this.path.length > 0)
+			{
+				node.setAttribute('d', this.path.join(' '));
+			}
+			else
+			{
+				return;
+			}
+		}
+
+		if (filled && s.fillColor != null)
+		{
+			this.updateFill();
+		}
+		else if (!this.styleEnabled)
+		{
+			// Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=814952
+			if (node.nodeName == 'ellipse' && mxClient.IS_FF)
+			{
+				node.setAttribute('fill', 'transparent');
+			}
+			else
+			{
+				node.setAttribute('fill', 'none');
+			}
+			
+			// Sets the actual filled state for stroke tolerance
+			filled = false;
+		}
+		
+		if (stroked && s.strokeColor != null)
+		{
+			this.updateStroke();
+		}
+		else if (!this.styleEnabled)
+		{
+			node.setAttribute('stroke', 'none');
+		}
+		
+		if (s.transform != null && s.transform.length > 0)
+		{
+			node.setAttribute('transform', s.transform);
+		}
+		
+		if (s.shadow)
+		{
+			this.root.appendChild(this.createShadow(node));
+		}
+	
+		// Adds stroke tolerance
+		if (this.strokeTolerance > 0 && !filled)
+		{
+			this.root.appendChild(this.createTolerance(node));
+		}
+
+		// Adds pointer events
+		if (this.pointerEvents && (node.nodeName != 'path' ||
+			this.path[this.path.length - 1] == this.closeOp))
+		{
+			node.setAttribute('pointer-events', this.pointerEventsValue);
+		}
+		// Enables clicks for nodes inside a link element
+		else if (!this.pointerEvents && this.originalRoot == null)
+		{
+			node.setAttribute('pointer-events', 'none');
+		}
+		
+		// Removes invisible nodes from output if they don't handle events
+		if ((node.nodeName != 'rect' && node.nodeName != 'path' && node.nodeName != 'ellipse') ||
+			(node.getAttribute('fill') != 'none' && node.getAttribute('fill') != 'transparent') ||
+			node.getAttribute('stroke') != 'none' || node.getAttribute('pointer-events') != 'none')
+		{
+			// LATER: Update existing DOM for performance		
+			this.root.appendChild(node);
+		}
+		
+		this.node = null;
+	}
+};
+
+/**
+ * Function: updateFill
+ * 
+ * Transfers the stroke attributes from <state> to <node>.
+ */
+mxSvgCanvas2D.prototype.updateFill = function()
+{
+	var s = this.state;
+	
+	if (s.alpha < 1 || s.fillAlpha < 1)
+	{
+		this.node.setAttribute('fill-opacity', s.alpha * s.fillAlpha);
+	}
+	
+	if (s.fillColor != null)
+	{
+		if (s.gradientColor != null)
+		{
+			var id = this.getSvgGradient(s.fillColor, s.gradientColor, s.gradientFillAlpha, s.gradientAlpha, s.gradientDirection);
+			
+			if (!mxClient.IS_CHROME_APP && !mxClient.IS_IE && !mxClient.IS_IE11 &&
+				!mxClient.IS_EDGE && this.root.ownerDocument == document)
+			{
+				// Workaround for potential base tag and brackets must be escaped
+				var base = this.getBaseUrl().replace(/([\(\)])/g, '\\$1');
+				this.node.setAttribute('fill', 'url(' + base + '#' + id + ')');
+			}
+			else
+			{
+				this.node.setAttribute('fill', 'url(#' + id + ')');
+			}
+		}
+		else
+		{
+			this.node.setAttribute('fill', s.fillColor.toLowerCase());
+		}
+	}
+};
+
+/**
+ * Function: getCurrentStrokeWidth
+ * 
+ * Returns the current stroke width (>= 1), ie. max(1, this.format(this.state.strokeWidth * this.state.scale)).
+ */
+mxSvgCanvas2D.prototype.getCurrentStrokeWidth = function()
+{
+	return Math.max(1, this.format(this.state.strokeWidth * this.state.scale));
+};
+
+/**
+ * Function: updateStroke
+ * 
+ * Transfers the stroke attributes from <state> to <node>.
+ */
+mxSvgCanvas2D.prototype.updateStroke = function()
+{
+	var s = this.state;
+
+	this.node.setAttribute('stroke', s.strokeColor.toLowerCase());
+	
+	if (s.alpha < 1 || s.strokeAlpha < 1)
+	{
+		this.node.setAttribute('stroke-opacity', s.alpha * s.strokeAlpha);
+	}
+	
+	var sw = this.getCurrentStrokeWidth();
+	
+	if (sw != 1)
+	{
+		this.node.setAttribute('stroke-width', sw);
+	}
+	
+	if (this.node.nodeName == 'path')
+	{
+		this.updateStrokeAttributes();
+	}
+	
+	if (s.dashed)
+	{
+		this.node.setAttribute('stroke-dasharray', this.createDashPattern(
+			((s.fixDash) ? 1 : s.strokeWidth) * s.scale));
+	}
+};
+
+/**
+ * Function: updateStrokeAttributes
+ * 
+ * Transfers the stroke attributes from <state> to <node>.
+ */
+mxSvgCanvas2D.prototype.updateStrokeAttributes = function()
+{
+	var s = this.state;
+	
+	// Linejoin miter is default in SVG
+	if (s.lineJoin != null && s.lineJoin != 'miter')
+	{
+		this.node.setAttribute('stroke-linejoin', s.lineJoin);
+	}
+	
+	if (s.lineCap != null)
+	{
+		// flat is called butt in SVG
+		var value = s.lineCap;
+		
+		if (value == 'flat')
+		{
+			value = 'butt';
+		}
+		
+		// Linecap butt is default in SVG
+		if (value != 'butt')
+		{
+			this.node.setAttribute('stroke-linecap', value);
+		}
+	}
+	
+	// Miterlimit 10 is default in our document
+	if (s.miterLimit != null && (!this.styleEnabled || s.miterLimit != 10))
+	{
+		this.node.setAttribute('stroke-miterlimit', s.miterLimit);
+	}
+};
+
+/**
+ * Function: createDashPattern
+ * 
+ * Creates the SVG dash pattern for the given state.
+ */
+mxSvgCanvas2D.prototype.createDashPattern = function(scale)
+{
+	var pat = [];
+	
+	if (typeof(this.state.dashPattern) === 'string')
+	{
+		var dash = this.state.dashPattern.split(' ');
+		
+		if (dash.length > 0)
+		{
+			for (var i = 0; i < dash.length; i++)
+			{
+				pat[i] = Number(dash[i]) * scale;
+			}
+		}
+	}
+	
+	return pat.join(' ');
+};
+
+/**
+ * Function: createTolerance
+ * 
+ * Creates a hit detection tolerance shape for the given node.
+ */
+mxSvgCanvas2D.prototype.createTolerance = function(node)
+{
+	var tol = node.cloneNode(true);
+	var sw = parseFloat(tol.getAttribute('stroke-width') || 1) + this.strokeTolerance;
+	tol.setAttribute('pointer-events', 'stroke');
+	tol.setAttribute('visibility', 'hidden');
+	tol.removeAttribute('stroke-dasharray');
+	tol.setAttribute('stroke-width', sw);
+	tol.setAttribute('fill', 'none');
+	
+	// Workaround for Opera ignoring the visiblity attribute above while
+	// other browsers need a stroke color to perform the hit-detection but
+	// do not ignore the visibility attribute. Side-effect is that Opera's
+	// hit detection for horizontal/vertical edges seems to ignore the tol.
+	tol.setAttribute('stroke', (mxClient.IS_OT) ? 'none' : 'white');
+	
+	return tol;
+};
+
+/**
+ * Function: createShadow
+ * 
+ * Creates a shadow for the given node.
+ */
+mxSvgCanvas2D.prototype.createShadow = function(node)
+{
+	var shadow = node.cloneNode(true);
+	var s = this.state;
+
+	// Firefox uses transparent for no fill in ellipses
+	if (shadow.getAttribute('fill') != 'none' && (!mxClient.IS_FF || shadow.getAttribute('fill') != 'transparent'))
+	{
+		shadow.setAttribute('fill', s.shadowColor);
+	}
+	
+	if (shadow.getAttribute('stroke') != 'none')
+	{
+		shadow.setAttribute('stroke', s.shadowColor);
+	}
+
+	shadow.setAttribute('transform', 'translate(' + this.format(s.shadowDx * s.scale) +
+		',' + this.format(s.shadowDy * s.scale) + ')' + (s.transform || ''));
+	shadow.setAttribute('opacity', s.shadowAlpha);
+	
+	return shadow;
+};
+
+/**
+ * Function: setLink
+ * 
+ * Experimental implementation for hyperlinks.
+ */
+mxSvgCanvas2D.prototype.setLink = function(link)
+{
+	if (link == null)
+	{
+		this.root = this.originalRoot;
+	}
+	else
+	{
+		this.originalRoot = this.root;
+		
+		var node = this.createElement('a');
+		
+		// Workaround for implicit namespace handling in HTML5 export, IE adds NS1 namespace so use code below
+		// in all IE versions except quirks mode. KNOWN: Adds xlink namespace to each image tag in output.
+		if (node.setAttributeNS == null || (this.root.ownerDocument != document && document.documentMode == null))
+		{
+			node.setAttribute('xlink:href', link);
+		}
+		else
+		{
+			node.setAttributeNS(mxConstants.NS_XLINK, 'xlink:href', link);
+		}
+		
+		this.root.appendChild(node);
+		this.root = node;
+	}
+};
+
+/**
+ * Function: rotate
+ * 
+ * Sets the rotation of the canvas. Note that rotation cannot be concatenated.
+ */
+mxSvgCanvas2D.prototype.rotate = function(theta, flipH, flipV, cx, cy)
+{
+	if (theta != 0 || flipH || flipV)
+	{
+		var s = this.state;
+		cx += s.dx;
+		cy += s.dy;
+	
+		cx *= s.scale;
+		cy *= s.scale;
+
+		s.transform = s.transform || '';
+		
+		// This implementation uses custom scale/translate and built-in rotation
+		// Rotation state is part of the AffineTransform in state.transform
+		if (flipH && flipV)
+		{
+			theta += 180;
+		}
+		else if (flipH != flipV)
+		{
+			var tx = (flipH) ? cx : 0;
+			var sx = (flipH) ? -1 : 1;
+	
+			var ty = (flipV) ? cy : 0;
+			var sy = (flipV) ? -1 : 1;
+
+			s.transform += 'translate(' + this.format(tx) + ',' + this.format(ty) + ')' +
+				'scale(' + this.format(sx) + ',' + this.format(sy) + ')' +
+				'translate(' + this.format(-tx) + ',' + this.format(-ty) + ')';
+		}
+		
+		if (flipH ? !flipV : flipV)
+		{
+			theta *= -1;
+		}
+		
+		if (theta != 0)
+		{
+			s.transform += 'rotate(' + this.format(theta) + ',' + this.format(cx) + ',' + this.format(cy) + ')';
+		}
+		
+		s.rotation = s.rotation + theta;
+		s.rotationCx = cx;
+		s.rotationCy = cy;
+	}
+};
+
+/**
+ * Function: begin
+ * 
+ * Extends superclass to create path.
+ */
+mxSvgCanvas2D.prototype.begin = function()
+{
+	mxAbstractCanvas2D.prototype.begin.apply(this, arguments);
+	this.node = this.createElement('path');
+};
+
+/**
+ * Function: rect
+ * 
+ * Private helper function to create SVG elements
+ */
+mxSvgCanvas2D.prototype.rect = function(x, y, w, h)
+{
+	var s = this.state;
+	var n = this.createElement('rect');
+	n.setAttribute('x', this.format((x + s.dx) * s.scale));
+	n.setAttribute('y', this.format((y + s.dy) * s.scale));
+	n.setAttribute('width', this.format(w * s.scale));
+	n.setAttribute('height', this.format(h * s.scale));
+	
+	this.node = n;
+};
+
+/**
+ * Function: roundrect
+ * 
+ * Private helper function to create SVG elements
+ */
+mxSvgCanvas2D.prototype.roundrect = function(x, y, w, h, dx, dy)
+{
+	this.rect(x, y, w, h);
+	
+	if (dx > 0)
+	{
+		this.node.setAttribute('rx', this.format(dx * this.state.scale));
+	}
+	
+	if (dy > 0)
+	{
+		this.node.setAttribute('ry', this.format(dy * this.state.scale));
+	}
+};
+
+/**
+ * Function: ellipse
+ * 
+ * Private helper function to create SVG elements
+ */
+mxSvgCanvas2D.prototype.ellipse = function(x, y, w, h)
+{
+	var s = this.state;
+	var n = this.createElement('ellipse');
+	// No rounding for consistent output with 1.x
+	n.setAttribute('cx', Math.round((x + w / 2 + s.dx) * s.scale));
+	n.setAttribute('cy', Math.round((y + h / 2 + s.dy) * s.scale));
+	n.setAttribute('rx', w / 2 * s.scale);
+	n.setAttribute('ry', h / 2 * s.scale);
+	this.node = n;
+};
+
+/**
+ * Function: image
+ * 
+ * Private helper function to create SVG elements
+ */
+mxSvgCanvas2D.prototype.image = function(x, y, w, h, src, aspect, flipH, flipV)
+{
+	src = this.converter.convert(src);
+	
+	// LATER: Add option for embedding images as base64.
+	aspect = (aspect != null) ? aspect : true;
+	flipH = (flipH != null) ? flipH : false;
+	flipV = (flipV != null) ? flipV : false;
+	
+	var s = this.state;
+	x += s.dx;
+	y += s.dy;
+	
+	var node = this.createElement('image');
+	node.setAttribute('x', this.format(x * s.scale) + this.imageOffset);
+	node.setAttribute('y', this.format(y * s.scale) + this.imageOffset);
+	node.setAttribute('width', this.format(w * s.scale));
+	node.setAttribute('height', this.format(h * s.scale));
+	
+	// Workaround for missing namespace support
+	if (node.setAttributeNS == null)
+	{
+		node.setAttribute('xlink:href', src);
+	}
+	else
+	{
+		node.setAttributeNS(mxConstants.NS_XLINK, 'xlink:href', src);
+	}
+	
+	if (!aspect)
+	{
+		node.setAttribute('preserveAspectRatio', 'none');
+	}
+
+	if (s.alpha < 1 || s.fillAlpha < 1)
+	{
+		node.setAttribute('opacity', s.alpha * s.fillAlpha);
+	}
+	
+	var tr = this.state.transform || '';
+	
+	if (flipH || flipV)
+	{
+		var sx = 1;
+		var sy = 1;
+		var dx = 0;
+		var dy = 0;
+		
+		if (flipH)
+		{
+			sx = -1;
+			dx = -w - 2 * x;
+		}
+		
+		if (flipV)
+		{
+			sy = -1;
+			dy = -h - 2 * y;
+		}
+		
+		// Adds image tansformation to existing transform
+		tr += 'scale(' + sx + ',' + sy + ')translate(' + (dx * s.scale) + ',' + (dy * s.scale) + ')';
+	}
+
+	if (tr.length > 0)
+	{
+		node.setAttribute('transform', tr);
+	}
+	
+	if (!this.pointerEvents)
+	{
+		node.setAttribute('pointer-events', 'none');
+	}
+	
+	this.root.appendChild(node);
+	
+	// Disables control-clicks on images in Firefox to open in new tab
+	// by putting a rect in the foreground that absorbs all events and
+	// disabling all pointer-events on the original image tag.
+	if (this.blockImagePointerEvents)
+	{
+		node.setAttribute('style', 'pointer-events:none');
+		
+		node = this.createElement('rect');
+		node.setAttribute('visibility', 'hidden');
+		node.setAttribute('pointer-events', 'fill');
+		node.setAttribute('x', this.format(x * s.scale));
+		node.setAttribute('y', this.format(y * s.scale));
+		node.setAttribute('width', this.format(w * s.scale));
+		node.setAttribute('height', this.format(h * s.scale));
+		this.root.appendChild(node);
+	}
+};
+
+/**
+ * Function: convertHtml
+ * 
+ * Converts the given HTML string to XHTML.
+ */
+mxSvgCanvas2D.prototype.convertHtml = function(val)
+{
+	if (this.useDomParser)
+	{
+		var doc = new DOMParser().parseFromString(val, 'text/html');
+
+		if (doc != null)
+		{
+			val = new XMLSerializer().serializeToString(doc.body);
+			
+			// Extracts body content from DOM
+			if (val.substring(0, 5) == '<body')
+			{
+				val = val.substring(val.indexOf('>', 5) + 1);
+			}
+			
+			if (val.substring(val.length - 7, val.length) == '</body>')
+			{
+				val = val.substring(0, val.length - 7);
+			}
+		}
+	}
+	else if (document.implementation != null && document.implementation.createDocument != null)
+	{
+		var xd = document.implementation.createDocument('http://www.w3.org/1999/xhtml', 'html', null);
+		var xb = xd.createElement('body');
+		xd.documentElement.appendChild(xb);
+		
+		var div = document.createElement('div');
+		div.innerHTML = val;
+		var child = div.firstChild;
+		
+		while (child != null)
+		{
+			var next = child.nextSibling;
+			xb.appendChild(xd.adoptNode(child));
+			child = next;
+		}
+		
+		return xb.innerHTML;
+	}
+	else
+	{
+		var ta = document.createElement('textarea');
+		
+		// Handles special HTML entities < and > and double escaping
+		// and converts unclosed br, hr and img tags to XHTML
+		// LATER: Convert all unclosed tags
+		ta.innerHTML = val.replace(/&amp;/g, '&amp;amp;').
+			replace(/&#60;/g, '&amp;lt;').replace(/&#62;/g, '&amp;gt;').
+			replace(/&lt;/g, '&amp;lt;').replace(/&gt;/g, '&amp;gt;').
+			replace(/</g, '&lt;').replace(/>/g, '&gt;');
+		val = ta.value.replace(/&/g, '&amp;').replace(/&amp;lt;/g, '&lt;').
+			replace(/&amp;gt;/g, '&gt;').replace(/&amp;amp;/g, '&amp;').
+			replace(/<br>/g, '<br />').replace(/<hr>/g, '<hr />').
+			replace(/(<img[^>]+)>/gm, "$1 />");
+	}
+	
+	return val;
+};
+
+/**
+ * Function: createDiv
+ * 
+ * Private helper function to create SVG elements
+ */
+mxSvgCanvas2D.prototype.createDiv = function(str, align, valign, style, overflow)
+{
+	var s = this.state;
+
+	// Inline block for rendering HTML background over SVG in Safari
+	var lh = (mxConstants.ABSOLUTE_LINE_HEIGHT) ? (s.fontSize * mxConstants.LINE_HEIGHT) + 'px' :
+		(mxConstants.LINE_HEIGHT * this.lineHeightCorrection);
+	
+	style = 'display:inline-block;font-size:' + s.fontSize + 'px;font-family:' + s.fontFamily +
+		';color:' + s.fontColor + ';line-height:' + lh + ';' + style;
+
+	if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
+	{
+		style += 'font-weight:bold;';
+	}
+
+	if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
+	{
+		style += 'font-style:italic;';
+	}
+	
+	if ((s.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)
+	{
+		style += 'text-decoration:underline;';
+	}
+	
+	if (align == mxConstants.ALIGN_CENTER)
+	{
+		style += 'text-align:center;';
+	}
+	else if (align == mxConstants.ALIGN_RIGHT)
+	{
+		style += 'text-align:right;';
+	}
+
+	var css = '';
+	
+	if (s.fontBackgroundColor != null)
+	{
+		css += 'background-color:' + s.fontBackgroundColor + ';';
+	}
+	
+	if (s.fontBorderColor != null)
+	{
+		css += 'border:1px solid ' + s.fontBorderColor + ';';
+	}
+	
+	var val = str;
+	
+	if (!mxUtils.isNode(val))
+	{
+		val = this.convertHtml(val);
+		
+		if (overflow != 'fill' && overflow != 'width')
+		{
+			// Inner div always needed to measure wrapped text
+			val = '<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;' + css + '">' + val + '</div>';
+		}
+		else
+		{
+			style += css;
+		}
+	}
+
+	// Uses DOM API where available. This cannot be used in IE to avoid
+	// an opening and two (!) closing TBODY tags being added to tables.
+	if (!mxClient.IS_IE && document.createElementNS)
+	{
+		var div = document.createElementNS('http://www.w3.org/1999/xhtml', 'div');
+		div.setAttribute('style', style);
+		
+		if (mxUtils.isNode(val))
+		{
+			// Creates a copy for export
+			if (this.root.ownerDocument != document)
+			{
+				div.appendChild(val.cloneNode(true));
+			}
+			else
+			{
+				div.appendChild(val);
+			}
+		}
+		else
+		{
+			div.innerHTML = val;
+		}
+		
+		return div;
+	}
+	else
+	{
+		// Serializes for export
+		if (mxUtils.isNode(val) && this.root.ownerDocument != document)
+		{
+			val = val.outerHTML;
+		}
+
+		// NOTE: FF 3.6 crashes if content CSS contains "height:100%"
+		return mxUtils.parseXml('<div xmlns="http://www.w3.org/1999/xhtml" style="' + style + 
+			'">' + val + '</div>').documentElement;
+	}
+};
+
+/**
+ * Invalidates the cached offset size for the given node.
+ */
+mxSvgCanvas2D.prototype.invalidateCachedOffsetSize = function(node)
+{
+	delete node.firstChild.mxCachedOffsetWidth;
+	delete node.firstChild.mxCachedFinalOffsetWidth;
+	delete node.firstChild.mxCachedFinalOffsetHeight;
+};
+
+/**
+ * Updates existing DOM nodes for text rendering. LATER: Merge common parts with text function below.
+ */
+mxSvgCanvas2D.prototype.updateText = function(x, y, w, h, align, valign, wrap, overflow, clip, rotation, node)
+{
+	if (node != null && node.firstChild != null && node.firstChild.firstChild != null &&
+		node.firstChild.firstChild.firstChild != null)
+	{
+		// Uses outer group for opacity and transforms to
+		// fix rendering order in Chrome
+		var group = node.firstChild;
+		var fo = group.firstChild;
+		var div = fo.firstChild;
+
+		rotation = (rotation != null) ? rotation : 0;
+		
+		var s = this.state;
+		x += s.dx;
+		y += s.dy;
+		
+		if (clip)
+		{
+			div.style.maxHeight = Math.round(h) + 'px';
+			div.style.maxWidth = Math.round(w) + 'px';
+		}
+		else if (overflow == 'fill')
+		{
+			div.style.width = Math.round(w + 1) + 'px';
+			div.style.height = Math.round(h + 1) + 'px';
+		}
+		else if (overflow == 'width')
+		{
+			div.style.width = Math.round(w + 1) + 'px';
+			
+			if (h > 0)
+			{
+				div.style.maxHeight = Math.round(h) + 'px';
+			}
+		}
+
+		if (wrap && w > 0)
+		{
+			div.style.width = Math.round(w + 1) + 'px';
+		}
+		
+		// Code that depends on the size which is computed after
+		// the element was added to the DOM.
+		var ow = 0;
+		var oh = 0;
+		
+		// Padding avoids clipping on border and wrapping for differing font metrics on platforms
+		var padX = 2;
+		var padY = 2;
+
+		var sizeDiv = div;
+		
+		if (sizeDiv.firstChild != null && sizeDiv.firstChild.nodeName == 'DIV')
+		{
+			sizeDiv = sizeDiv.firstChild;
+		}
+		
+		var tmp = (group.mxCachedOffsetWidth != null) ? group.mxCachedOffsetWidth : sizeDiv.offsetWidth;
+		ow = tmp + padX;
+
+		// Recomputes the height of the element for wrapped width
+		if (wrap && overflow != 'fill')
+		{
+			if (clip)
+			{
+				ow = Math.min(ow, w);
+			}
+			
+			div.style.width = ow + 'px';
+		}
+		
+		ow = ((group.mxCachedFinalOffsetWidth != null) ? group.mxCachedFinalOffsetWidth :
+			sizeDiv.offsetWidth) + padX;
+		oh = ((group.mxCachedFinalOffsetHeight != null) ? group.mxCachedFinalOffsetHeight :
+			sizeDiv.offsetHeight) - 2;
+
+		if (clip)
+		{
+			oh = Math.min(oh, h);
+			ow = Math.min(ow, w);
+		}
+
+		if (overflow == 'width')
+		{
+			h = oh;
+		}
+		else if (overflow != 'fill')
+		{
+			w = ow;
+			h = oh;
+		}
+
+		var dx = 0;
+		var dy = 0;
+
+		if (align == mxConstants.ALIGN_CENTER)
+		{
+			dx -= w / 2;
+		}
+		else if (align == mxConstants.ALIGN_RIGHT)
+		{
+			dx -= w;
+		}
+		
+		x += dx;
+		
+		// FIXME: LINE_HEIGHT not ideal for all text sizes, fix for export
+		if (valign == mxConstants.ALIGN_MIDDLE)
+		{
+			dy -= h / 2;
+		}
+		else if (valign == mxConstants.ALIGN_BOTTOM)
+		{
+			dy -= h;
+		}
+		
+		// Workaround for rendering offsets
+		// TODO: Check if export needs these fixes, too
+		if (overflow != 'fill' && mxClient.IS_FF && mxClient.IS_WIN)
+		{
+			dy -= 2;
+		}
+		
+		y += dy;
+
+		var tr = (s.scale != 1) ? 'scale(' + s.scale + ')' : '';
+
+		if (s.rotation != 0 && this.rotateHtml)
+		{
+			tr += 'rotate(' + (s.rotation) + ',' + (w / 2) + ',' + (h / 2) + ')';
+			var pt = this.rotatePoint((x + w / 2) * s.scale, (y + h / 2) * s.scale,
+				s.rotation, s.rotationCx, s.rotationCy);
+			x = pt.x - w * s.scale / 2;
+			y = pt.y - h * s.scale / 2;
+		}
+		else
+		{
+			x *= s.scale;
+			y *= s.scale;
+		}
+
+		if (rotation != 0)
+		{
+			tr += 'rotate(' + (rotation) + ',' + (-dx) + ',' + (-dy) + ')';
+		}
+
+		group.setAttribute('transform', 'translate(' + Math.round(x) + ',' + Math.round(y) + ')' + tr);
+		fo.setAttribute('width', Math.round(Math.max(1, w)));
+		fo.setAttribute('height', Math.round(Math.max(1, h)));
+	}
+};
+
+/**
+ * Function: text
+ * 
+ * Paints the given text. Possible values for format are empty string for plain
+ * text and html for HTML markup. Note that HTML markup is only supported if
+ * foreignObject is supported and <foEnabled> is true. (This means IE9 and later
+ * does currently not support HTML text as part of shapes.)
+ */
+mxSvgCanvas2D.prototype.text = function(x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation, dir)
+{
+	if (this.textEnabled && str != null)
+	{
+		rotation = (rotation != null) ? rotation : 0;
+		
+		var s = this.state;
+		x += s.dx;
+		y += s.dy;
+		
+		if (this.foEnabled && format == 'html')
+		{
+			var style = 'vertical-align:top;';
+			
+			if (clip)
+			{
+				style += 'overflow:hidden;max-height:' + Math.round(h) + 'px;max-width:' + Math.round(w) + 'px;';
+			}
+			else if (overflow == 'fill')
+			{
+				style += 'width:' + Math.round(w + 1) + 'px;height:' + Math.round(h + 1) + 'px;overflow:hidden;';
+			}
+			else if (overflow == 'width')
+			{
+				style += 'width:' + Math.round(w + 1) + 'px;';
+				
+				if (h > 0)
+				{
+					style += 'max-height:' + Math.round(h) + 'px;overflow:hidden;';
+				}
+			}
+
+			if (wrap && w > 0)
+			{
+				style += 'width:' + Math.round(w + 1) + 'px;white-space:normal;word-wrap:' +
+					mxConstants.WORD_WRAP + ';';
+			}
+			else
+			{
+				style += 'white-space:nowrap;';
+			}
+			
+			// Uses outer group for opacity and transforms to
+			// fix rendering order in Chrome
+			var group = this.createElement('g');
+			
+			if (s.alpha < 1)
+			{
+				group.setAttribute('opacity', s.alpha);
+			}
+
+			var fo = this.createElement('foreignObject');
+			fo.setAttribute('style', 'overflow:visible;');
+			fo.setAttribute('pointer-events', 'all');
+			
+			var div = this.createDiv(str, align, valign, style, overflow);
+			
+			// Ignores invalid XHTML labels
+			if (div == null)
+			{
+				return;
+			}
+			else if (dir != null)
+			{
+				div.setAttribute('dir', dir);
+			}
+
+			group.appendChild(fo);
+			this.root.appendChild(group);
+			
+			// Code that depends on the size which is computed after
+			// the element was added to the DOM.
+			var ow = 0;
+			var oh = 0;
+			
+			// Padding avoids clipping on border and wrapping for differing font metrics on platforms
+			var padX = 2;
+			var padY = 2;
+
+			// NOTE: IE is always export as it does not support foreign objects
+			if (mxClient.IS_IE && (document.documentMode == 9 || !mxClient.IS_SVG))
+			{
+				// Handles non-standard namespace for getting size in IE
+				var clone = document.createElement('div');
+				
+				clone.style.cssText = div.getAttribute('style');
+				clone.style.display = (mxClient.IS_QUIRKS) ? 'inline' : 'inline-block';
+				clone.style.position = 'absolute';
+				clone.style.visibility = 'hidden';
+
+				// Inner DIV is needed for text measuring
+				var div2 = document.createElement('div');
+				div2.style.display = (mxClient.IS_QUIRKS) ? 'inline' : 'inline-block';
+				div2.style.wordWrap = mxConstants.WORD_WRAP;
+				div2.innerHTML = (mxUtils.isNode(str)) ? str.outerHTML : str;
+				clone.appendChild(div2);
+
+				document.body.appendChild(clone);
+
+				// Workaround for different box models
+				if (document.documentMode != 8 && document.documentMode != 9 && s.fontBorderColor != null)
+				{
+					padX += 2;
+					padY += 2;
+				}
+
+				if (wrap && w > 0)
+				{
+					var tmp = div2.offsetWidth;
+					
+					// Workaround for adding padding twice in IE8/IE9 standards mode if label is wrapped
+					var padDx = 0;
+					
+					// For export, if no wrapping occurs, we add a large padding to make
+					// sure there is no wrapping even if the text metrics are different.
+					// This adds support for text metrics on different operating systems.
+					// Disables wrapping if text is not wrapped for given width
+					if (!clip && wrap && w > 0 && this.root.ownerDocument != document && overflow != 'fill')
+					{
+						var ws = clone.style.whiteSpace;
+						div2.style.whiteSpace = 'nowrap';
+						
+						if (tmp < div2.offsetWidth)
+						{
+							clone.style.whiteSpace = ws;
+						}
+					}
+					
+					if (clip)
+					{
+						tmp = Math.min(tmp, w);
+					}
+					
+					clone.style.width = tmp + 'px';
+	
+					// Padding avoids clipping on border
+					ow = div2.offsetWidth + padX + padDx;
+					oh = div2.offsetHeight + padY;
+					
+					// Overrides the width of the DIV via XML DOM by using the
+					// clone DOM style, getting the CSS text for that and
+					// then setting that on the DIV via setAttribute
+					clone.style.display = 'inline-block';
+					clone.style.position = '';
+					clone.style.visibility = '';
+					clone.style.width = ow + 'px';
+					
+					div.setAttribute('style', clone.style.cssText);
+				}
+				else
+				{
+					// Padding avoids clipping on border
+					ow = div2.offsetWidth + padX;
+					oh = div2.offsetHeight + padY;
+				}
+
+				clone.parentNode.removeChild(clone);
+				fo.appendChild(div);
+			}
+			else
+			{
+				// Uses document for text measuring during export
+				if (this.root.ownerDocument != document)
+				{
+					div.style.visibility = 'hidden';
+					document.body.appendChild(div);
+				}
+				else
+				{
+					fo.appendChild(div);
+				}
+
+				var sizeDiv = div;
+				
+				if (sizeDiv.firstChild != null && sizeDiv.firstChild.nodeName == 'DIV')
+				{
+					sizeDiv = sizeDiv.firstChild;
+					
+					if (wrap && div.style.wordWrap == 'break-word')
+					{
+						sizeDiv.style.width = '100%';
+					}
+				}
+				
+				var tmp = sizeDiv.offsetWidth;
+				
+				// Workaround for text measuring in hidden containers
+				if (tmp == 0 && div.parentNode == fo)
+				{
+					div.style.visibility = 'hidden';
+					document.body.appendChild(div);
+					
+					tmp = sizeDiv.offsetWidth;
+				}
+				
+				if (this.cacheOffsetSize)
+				{
+					group.mxCachedOffsetWidth = tmp;
+				}
+				
+				// Disables wrapping if text is not wrapped for given width
+				if (!clip && wrap && w > 0 && this.root.ownerDocument != document &&
+					overflow != 'fill' && overflow != 'width')
+				{
+					var ws = div.style.whiteSpace;
+					div.style.whiteSpace = 'nowrap';
+					
+					if (tmp < sizeDiv.offsetWidth)
+					{
+						div.style.whiteSpace = ws;
+					}
+				}
+
+				ow = tmp + padX - 1;
+
+				// Recomputes the height of the element for wrapped width
+				if (wrap && overflow != 'fill' && overflow != 'width')
+				{
+					if (clip)
+					{
+						ow = Math.min(ow, w);
+					}
+					
+					div.style.width = ow + 'px';
+				}
+
+				ow = sizeDiv.offsetWidth;
+				oh = sizeDiv.offsetHeight;
+				
+				if (this.cacheOffsetSize)
+				{
+					group.mxCachedFinalOffsetWidth = ow;
+					group.mxCachedFinalOffsetHeight = oh;
+				}
+
+				oh -= padY;
+				
+				if (div.parentNode != fo)
+				{
+					fo.appendChild(div);
+					div.style.visibility = '';
+				}
+			}
+
+			if (clip)
+			{
+				oh = Math.min(oh, h);
+				ow = Math.min(ow, w);
+			}
+
+			if (overflow == 'width')
+			{
+				h = oh;
+			}
+			else if (overflow != 'fill')
+			{
+				w = ow;
+				h = oh;
+			}
+
+			if (s.alpha < 1)
+			{
+				group.setAttribute('opacity', s.alpha);
+			}
+			
+			var dx = 0;
+			var dy = 0;
+
+			if (align == mxConstants.ALIGN_CENTER)
+			{
+				dx -= w / 2;
+			}
+			else if (align == mxConstants.ALIGN_RIGHT)
+			{
+				dx -= w;
+			}
+			
+			x += dx;
+			
+			// FIXME: LINE_HEIGHT not ideal for all text sizes, fix for export
+			if (valign == mxConstants.ALIGN_MIDDLE)
+			{
+				dy -= h / 2;
+			}
+			else if (valign == mxConstants.ALIGN_BOTTOM)
+			{
+				dy -= h;
+			}
+			
+			// Workaround for rendering offsets
+			// TODO: Check if export needs these fixes, too
+			//if (this.root.ownerDocument == document)
+			if (overflow != 'fill' && mxClient.IS_FF && mxClient.IS_WIN)
+			{
+				dy -= 2;
+			}
+			
+			y += dy;
+
+			var tr = (s.scale != 1) ? 'scale(' + s.scale + ')' : '';
+
+			if (s.rotation != 0 && this.rotateHtml)
+			{
+				tr += 'rotate(' + (s.rotation) + ',' + (w / 2) + ',' + (h / 2) + ')';
+				var pt = this.rotatePoint((x + w / 2) * s.scale, (y + h / 2) * s.scale,
+					s.rotation, s.rotationCx, s.rotationCy);
+				x = pt.x - w * s.scale / 2;
+				y = pt.y - h * s.scale / 2;
+			}
+			else
+			{
+				x *= s.scale;
+				y *= s.scale;
+			}
+
+			if (rotation != 0)
+			{
+				tr += 'rotate(' + (rotation) + ',' + (-dx) + ',' + (-dy) + ')';
+			}
+
+			group.setAttribute('transform', 'translate(' + (Math.round(x) + this.foOffset) + ',' +
+				(Math.round(y) + this.foOffset) + ')' + tr);
+			fo.setAttribute('width', Math.round(Math.max(1, w)));
+			fo.setAttribute('height', Math.round(Math.max(1, h)));
+			
+			// Adds alternate content if foreignObject not supported in viewer
+			if (this.root.ownerDocument != document)
+			{
+				var alt = this.createAlternateContent(fo, x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation);
+				
+				if (alt != null)
+				{
+					fo.setAttribute('requiredFeatures', 'http://www.w3.org/TR/SVG11/feature#Extensibility');
+					var sw = this.createElement('switch');
+					sw.appendChild(fo);
+					sw.appendChild(alt);
+					group.appendChild(sw);
+				}
+			}
+		}
+		else
+		{
+			this.plainText(x, y, w, h, str, align, valign, wrap, overflow, clip, rotation, dir);
+		}
+	}
+};
+
+/**
+ * Function: createClip
+ * 
+ * Creates a clip for the given coordinates.
+ */
+mxSvgCanvas2D.prototype.createClip = function(x, y, w, h)
+{
+	x = Math.round(x);
+	y = Math.round(y);
+	w = Math.round(w);
+	h = Math.round(h);
+	
+	var id = 'mx-clip-' + x + '-' + y + '-' + w + '-' + h;
+
+	var counter = 0;
+	var tmp = id + '-' + counter;
+	
+	// Resolves ID conflicts
+	while (document.getElementById(tmp) != null)
+	{
+		tmp = id + '-' + (++counter);
+	}
+	
+	clip = this.createElement('clipPath');
+	clip.setAttribute('id', tmp);
+	
+	var rect = this.createElement('rect');
+	rect.setAttribute('x', x);
+	rect.setAttribute('y', y);
+	rect.setAttribute('width', w);
+	rect.setAttribute('height', h);
+		
+	clip.appendChild(rect);
+	
+	return clip;
+};
+
+/**
+ * Function: text
+ * 
+ * Paints the given text. Possible values for format are empty string for
+ * plain text and html for HTML markup.
+ */
+mxSvgCanvas2D.prototype.plainText = function(x, y, w, h, str, align, valign, wrap, overflow, clip, rotation, dir)
+{
+	rotation = (rotation != null) ? rotation : 0;
+	var s = this.state;
+	var size = s.fontSize;
+	var node = this.createElement('g');
+	var tr = s.transform || '';
+	this.updateFont(node);
+	
+	// Non-rotated text
+	if (rotation != 0)
+	{
+		tr += 'rotate(' + rotation  + ',' + this.format(x * s.scale) + ',' + this.format(y * s.scale) + ')';
+	}
+	
+	if (dir != null)
+	{
+		node.setAttribute('direction', dir);
+	}
+
+	if (clip && w > 0 && h > 0)
+	{
+		var cx = x;
+		var cy = y;
+		
+		if (align == mxConstants.ALIGN_CENTER)
+		{
+			cx -= w / 2;
+		}
+		else if (align == mxConstants.ALIGN_RIGHT)
+		{
+			cx -= w;
+		}
+		
+		if (overflow != 'fill')
+		{
+			if (valign == mxConstants.ALIGN_MIDDLE)
+			{
+				cy -= h / 2;
+			}
+			else if (valign == mxConstants.ALIGN_BOTTOM)
+			{
+				cy -= h;
+			}
+		}
+		
+		// LATER: Remove spacing from clip rectangle
+		var c = this.createClip(cx * s.scale - 2, cy * s.scale - 2, w * s.scale + 4, h * s.scale + 4);
+		
+		if (this.defs != null)
+		{
+			this.defs.appendChild(c);
+		}
+		else
+		{
+			// Makes sure clip is removed with referencing node
+			this.root.appendChild(c);
+		}
+		
+		if (!mxClient.IS_CHROME_APP && !mxClient.IS_IE && !mxClient.IS_IE11 &&
+			!mxClient.IS_EDGE && this.root.ownerDocument == document)
+		{
+			// Workaround for potential base tag
+			var base = this.getBaseUrl().replace(/([\(\)])/g, '\\$1');
+			node.setAttribute('clip-path', 'url(' + base + '#' + c.getAttribute('id') + ')');
+		}
+		else
+		{
+			node.setAttribute('clip-path', 'url(#' + c.getAttribute('id') + ')');
+		}
+	}
+
+	// Default is left
+	var anchor = (align == mxConstants.ALIGN_RIGHT) ? 'end' :
+					(align == mxConstants.ALIGN_CENTER) ? 'middle' :
+					'start';
+
+	// Text-anchor start is default in SVG
+	if (anchor != 'start')
+	{
+		node.setAttribute('text-anchor', anchor);
+	}
+	
+	if (!this.styleEnabled || size != mxConstants.DEFAULT_FONTSIZE)
+	{
+		node.setAttribute('font-size', (size * s.scale) + 'px');
+	}
+	
+	if (tr.length > 0)
+	{
+		node.setAttribute('transform', tr);
+	}
+	
+	if (s.alpha < 1)
+	{
+		node.setAttribute('opacity', s.alpha);
+	}
+	
+	var lines = str.split('\n');
+	var lh = Math.round(size * mxConstants.LINE_HEIGHT);
+	var textHeight = size + (lines.length - 1) * lh;
+
+	var cy = y + size - 1;
+
+	if (valign == mxConstants.ALIGN_MIDDLE)
+	{
+		if (overflow == 'fill')
+		{
+			cy -= h / 2;
+		}
+		else
+		{
+			var dy = ((this.matchHtmlAlignment && clip && h > 0) ? Math.min(textHeight, h) : textHeight) / 2;
+			cy -= dy + 1;
+		}
+	}
+	else if (valign == mxConstants.ALIGN_BOTTOM)
+	{
+		if (overflow == 'fill')
+		{
+			cy -= h;
+		}
+		else
+		{
+			var dy = (this.matchHtmlAlignment && clip && h > 0) ? Math.min(textHeight, h) : textHeight;
+			cy -= dy + 2;
+		}
+	}
+
+	for (var i = 0; i < lines.length; i++)
+	{
+		// Workaround for bounding box of empty lines and spaces
+		if (lines[i].length > 0 && mxUtils.trim(lines[i]).length > 0)
+		{
+			var text = this.createElement('text');
+			// LATER: Match horizontal HTML alignment
+			text.setAttribute('x', this.format(x * s.scale) + this.textOffset);
+			text.setAttribute('y', this.format(cy * s.scale) + this.textOffset);
+			
+			mxUtils.write(text, lines[i]);
+			node.appendChild(text);
+		}
+
+		cy += lh;
+	}
+
+	this.root.appendChild(node);
+	this.addTextBackground(node, str, x, y, w, (overflow == 'fill') ? h : textHeight, align, valign, overflow);
+};
+
+/**
+ * Function: updateFont
+ * 
+ * Updates the text properties for the given node. (NOTE: For this to work in
+ * IE, the given node must be a text or tspan element.)
+ */
+mxSvgCanvas2D.prototype.updateFont = function(node)
+{
+	var s = this.state;
+
+	node.setAttribute('fill', s.fontColor);
+	
+	if (!this.styleEnabled || s.fontFamily != mxConstants.DEFAULT_FONTFAMILY)
+	{
+		node.setAttribute('font-family', s.fontFamily);
+	}
+
+	if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
+	{
+		node.setAttribute('font-weight', 'bold');
+	}
+
+	if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
+	{
+		node.setAttribute('font-style', 'italic');
+	}
+	
+	if ((s.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)
+	{
+		node.setAttribute('text-decoration', 'underline');
+	}
+};
+
+/**
+ * Function: addTextBackground
+ * 
+ * Background color and border
+ */
+mxSvgCanvas2D.prototype.addTextBackground = function(node, str, x, y, w, h, align, valign, overflow)
+{
+	var s = this.state;
+
+	if (s.fontBackgroundColor != null || s.fontBorderColor != null)
+	{
+		var bbox = null;
+		
+		if (overflow == 'fill' || overflow == 'width')
+		{
+			if (align == mxConstants.ALIGN_CENTER)
+			{
+				x -= w / 2;
+			}
+			else if (align == mxConstants.ALIGN_RIGHT)
+			{
+				x -= w;
+			}
+			
+			if (valign == mxConstants.ALIGN_MIDDLE)
+			{
+				y -= h / 2;
+			}
+			else if (valign == mxConstants.ALIGN_BOTTOM)
+			{
+				y -= h;
+			}
+			
+			bbox = new mxRectangle((x + 1) * s.scale, y * s.scale, (w - 2) * s.scale, (h + 2) * s.scale);
+		}
+		else if (node.getBBox != null && this.root.ownerDocument == document)
+		{
+			// Uses getBBox only if inside document for correct size
+			try
+			{
+				bbox = node.getBBox();
+				var ie = mxClient.IS_IE && mxClient.IS_SVG;
+				bbox = new mxRectangle(bbox.x, bbox.y + ((ie) ? 0 : 1), bbox.width, bbox.height + ((ie) ? 1 : 0));
+			}
+			catch (e)
+			{
+				// Ignores NS_ERROR_FAILURE in FF if container display is none.
+			}
+		}
+		else
+		{
+			// Computes size if not in document or no getBBox available
+			var div = document.createElement('div');
+
+			// Wrapping and clipping can be ignored here
+			div.style.lineHeight = (mxConstants.ABSOLUTE_LINE_HEIGHT) ? (s.fontSize * mxConstants.LINE_HEIGHT) + 'px' : mxConstants.LINE_HEIGHT;
+			div.style.fontSize = s.fontSize + 'px';
+			div.style.fontFamily = s.fontFamily;
+			div.style.whiteSpace = 'nowrap';
+			div.style.position = 'absolute';
+			div.style.visibility = 'hidden';
+			div.style.display = (mxClient.IS_QUIRKS) ? 'inline' : 'inline-block';
+			div.style.zoom = '1';
+			
+			if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
+			{
+				div.style.fontWeight = 'bold';
+			}
+
+			if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
+			{
+				div.style.fontStyle = 'italic';
+			}
+			
+			str = mxUtils.htmlEntities(str, false);
+			div.innerHTML = str.replace(/\n/g, '<br/>');
+			
+			document.body.appendChild(div);
+			var w = div.offsetWidth;
+			var h = div.offsetHeight;
+			div.parentNode.removeChild(div);
+			
+			if (align == mxConstants.ALIGN_CENTER)
+			{
+				x -= w / 2;
+			}
+			else if (align == mxConstants.ALIGN_RIGHT)
+			{
+				x -= w;
+			}
+			
+			if (valign == mxConstants.ALIGN_MIDDLE)
+			{
+				y -= h / 2;
+			}
+			else if (valign == mxConstants.ALIGN_BOTTOM)
+			{
+				y -= h;
+			}
+			
+			bbox = new mxRectangle((x + 1) * s.scale, (y + 2) * s.scale, w * s.scale, (h + 1) * s.scale);
+		}
+		
+		if (bbox != null)
+		{
+			var n = this.createElement('rect');
+			n.setAttribute('fill', s.fontBackgroundColor || 'none');
+			n.setAttribute('stroke', s.fontBorderColor || 'none');
+			n.setAttribute('x', Math.floor(bbox.x - 1));
+			n.setAttribute('y', Math.floor(bbox.y - 1));
+			n.setAttribute('width', Math.ceil(bbox.width + 2));
+			n.setAttribute('height', Math.ceil(bbox.height));
+
+			var sw = (s.fontBorderColor != null) ? Math.max(1, this.format(s.scale)) : 0;
+			n.setAttribute('stroke-width', sw);
+			
+			// Workaround for crisp rendering - only required if not exporting
+			if (this.root.ownerDocument == document && mxUtils.mod(sw, 2) == 1)
+			{
+				n.setAttribute('transform', 'translate(0.5, 0.5)');
+			}
+			
+			node.insertBefore(n, node.firstChild);
+		}
+	}
+};
+
+/**
+ * Function: stroke
+ * 
+ * Paints the outline of the current path.
+ */
+mxSvgCanvas2D.prototype.stroke = function()
+{
+	this.addNode(false, true);
+};
+
+/**
+ * Function: fill
+ * 
+ * Fills the current path.
+ */
+mxSvgCanvas2D.prototype.fill = function()
+{
+	this.addNode(true, false);
+};
+
+/**
+ * Function: fillAndStroke
+ * 
+ * Fills and paints the outline of the current path.
+ */
+mxSvgCanvas2D.prototype.fillAndStroke = function()
+{
+	this.addNode(true, true);
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ *
+ * Class: mxVmlCanvas2D
+ * 
+ * Implements a canvas to be used for rendering VML. Here is an example of implementing a
+ * fallback for SVG images which are not supported in VML-based browsers.
+ * 
+ * (code)
+ * var mxVmlCanvas2DImage = mxVmlCanvas2D.prototype.image;
+ * mxVmlCanvas2D.prototype.image = function(x, y, w, h, src, aspect, flipH, flipV)
+ * {
+ *   if (src.substring(src.length - 4, src.length) == '.svg')
+ *   {
+ *     src = 'http://www.jgraph.com/images/mxgraph.gif';
+ *   }
+ *   
+ *   mxVmlCanvas2DImage.apply(this, arguments);
+ * };
+ * (end)
+ * 
+ * To disable anti-aliasing in the output, use the following code.
+ * 
+ * (code)
+ * document.createStyleSheet().cssText = mxClient.VML_PREFIX + '\\:*{antialias:false;)}';
+ * (end)
+ * 
+ * A description of the public API is available in <mxXmlCanvas2D>. Note that
+ * there is a known issue in VML where gradients are painted using the outer
+ * bounding box of rotated shapes, not the actual bounds of the shape. See
+ * also <text> for plain text label restrictions in shapes for VML.
+ */
+var mxVmlCanvas2D = function(root)
+{
+	mxAbstractCanvas2D.call(this);
+
+	/**
+	 * Variable: root
+	 * 
+	 * Reference to the container for the SVG content.
+	 */
+	this.root = root;
+};
+
+/**
+ * Extends mxAbstractCanvas2D
+ */
+mxUtils.extend(mxVmlCanvas2D, mxAbstractCanvas2D);
+
+/**
+ * Variable: path
+ * 
+ * Holds the current DOM node.
+ */
+mxVmlCanvas2D.prototype.node = null;
+
+/**
+ * Variable: textEnabled
+ * 
+ * Specifies if text output should be enabledetB. Default is true.
+ */
+mxVmlCanvas2D.prototype.textEnabled = true;
+
+/**
+ * Variable: moveOp
+ * 
+ * Contains the string used for moving in paths. Default is 'm'.
+ */
+mxVmlCanvas2D.prototype.moveOp = 'm';
+
+/**
+ * Variable: lineOp
+ * 
+ * Contains the string used for moving in paths. Default is 'l'.
+ */
+mxVmlCanvas2D.prototype.lineOp = 'l';
+
+/**
+ * Variable: curveOp
+ * 
+ * Contains the string used for bezier curves. Default is 'c'.
+ */
+mxVmlCanvas2D.prototype.curveOp = 'c';
+
+/**
+ * Variable: closeOp
+ * 
+ * Holds the operator for closing curves. Default is 'x e'.
+ */
+mxVmlCanvas2D.prototype.closeOp = 'x';
+
+/**
+ * Variable: rotatedHtmlBackground
+ * 
+ * Background color for rotated HTML. Default is ''. This can be set to eg.
+ * white to improve rendering of rotated text in VML for IE9.
+ */
+mxVmlCanvas2D.prototype.rotatedHtmlBackground = '';
+
+/**
+ * Variable: vmlScale
+ * 
+ * Specifies the scale used to draw VML shapes.
+ */
+mxVmlCanvas2D.prototype.vmlScale = 1;
+
+/**
+ * Function: createElement
+ * 
+ * Creates the given element using the document.
+ */
+mxVmlCanvas2D.prototype.createElement = function(name)
+{
+	return document.createElement(name);
+};
+
+/**
+ * Function: createVmlElement
+ * 
+ * Creates a new element using <createElement> and prefixes the given name with
+ * <mxClient.VML_PREFIX>.
+ */
+mxVmlCanvas2D.prototype.createVmlElement = function(name)
+{
+	return this.createElement(mxClient.VML_PREFIX + ':' + name);
+};
+
+/**
+ * Function: addNode
+ * 
+ * Adds the current node to the <root>.
+ */
+mxVmlCanvas2D.prototype.addNode = function(filled, stroked)
+{
+	var node = this.node;
+	var s = this.state;
+	
+	if (node != null)
+	{
+		if (node.nodeName == 'shape')
+		{
+			// Checks if the path is not empty
+			if (this.path != null && this.path.length > 0)
+			{
+				node.path = this.path.join(' ') + ' e';
+				node.style.width = this.root.style.width;
+				node.style.height = this.root.style.height;
+				node.coordsize = parseInt(node.style.width) + ' ' + parseInt(node.style.height);
+			}
+			else
+			{
+				return;
+			}
+		}
+
+		node.strokeweight = this.format(Math.max(1, s.strokeWidth * s.scale / this.vmlScale)) + 'px';
+		
+		if (s.shadow)
+		{
+			this.root.appendChild(this.createShadow(node,
+				filled && s.fillColor != null,
+				stroked && s.strokeColor != null));
+		}
+		
+		if (stroked && s.strokeColor != null)
+		{
+			node.stroked = 'true';
+			node.strokecolor = s.strokeColor;
+		}
+		else
+		{
+			node.stroked = 'false';
+		}
+
+		node.appendChild(this.createStroke());
+
+		if (filled && s.fillColor != null)
+		{
+			node.appendChild(this.createFill());
+		}
+		else if (this.pointerEvents && (node.nodeName != 'shape' ||
+			this.path[this.path.length - 1] == this.closeOp))
+		{
+			node.appendChild(this.createTransparentFill());
+		}
+		else
+		{
+			node.filled = 'false';
+		}
+
+		// LATER: Update existing DOM for performance
+		this.root.appendChild(node);
+	}
+};
+
+/**
+ * Function: createTransparentFill
+ * 
+ * Creates a transparent fill.
+ */
+mxVmlCanvas2D.prototype.createTransparentFill = function()
+{
+	var fill = this.createVmlElement('fill');
+	fill.src = mxClient.imageBasePath + '/transparent.gif';
+	fill.type = 'tile';
+	
+	return fill;
+};
+
+/**
+ * Function: createFill
+ * 
+ * Creates a fill for the current state.
+ */
+mxVmlCanvas2D.prototype.createFill = function()
+{
+	var s = this.state;
+	
+	// Gradients in foregrounds not supported because special gradients
+	// with bounds must be created for each element in graphics-canvases
+	var fill = this.createVmlElement('fill');
+	fill.color = s.fillColor;
+
+	if (s.gradientColor != null)
+	{
+		fill.type = 'gradient';
+		fill.method = 'none';
+		fill.color2 = s.gradientColor;
+		var angle = 180 - s.rotation;
+		
+		if (s.gradientDirection == mxConstants.DIRECTION_WEST)
+		{
+			angle -= 90 + ((this.root.style.flip == 'x') ? 180 : 0);
+		}
+		else if (s.gradientDirection == mxConstants.DIRECTION_EAST)
+		{
+			angle += 90 + ((this.root.style.flip == 'x') ? 180 : 0);
+		}
+		else if (s.gradientDirection == mxConstants.DIRECTION_NORTH)
+		{
+			angle -= 180 + ((this.root.style.flip == 'y') ? -180 : 0);
+		}
+		else
+		{
+			 angle += ((this.root.style.flip == 'y') ? -180 : 0);
+		}
+		
+		if (this.root.style.flip == 'x' || this.root.style.flip == 'y')
+		{
+			angle *= -1;
+		}
+
+		// LATER: Fix outer bounding box for rotated shapes used in VML.
+		fill.angle = mxUtils.mod(angle, 360);
+		fill.opacity = (s.alpha * s.gradientFillAlpha * 100) + '%';
+		fill.setAttribute(mxClient.OFFICE_PREFIX + ':opacity2', (s.alpha * s.gradientAlpha * 100) + '%');
+	}
+	else if (s.alpha < 1 || s.fillAlpha < 1)
+	{
+		fill.opacity = (s.alpha * s.fillAlpha * 100) + '%';			
+	}
+	
+	return fill;
+};
+/**
+ * Function: createStroke
+ * 
+ * Creates a fill for the current state.
+ */
+mxVmlCanvas2D.prototype.createStroke = function()
+{
+	var s = this.state;
+	var stroke = this.createVmlElement('stroke');
+	stroke.endcap = s.lineCap || 'flat';
+	stroke.joinstyle = s.lineJoin || 'miter';
+	stroke.miterlimit = s.miterLimit || '10';
+	
+	if (s.alpha < 1 || s.strokeAlpha < 1)
+	{
+		stroke.opacity = (s.alpha * s.strokeAlpha * 100) + '%';
+	}
+	
+	if (s.dashed)
+	{
+		stroke.dashstyle = this.getVmlDashStyle();
+	}
+	
+	return stroke;
+};
+
+/**
+ * Function: getVmlDashPattern
+ * 
+ * Returns a VML dash pattern for the current dashPattern.
+ * See http://msdn.microsoft.com/en-us/library/bb264085(v=vs.85).aspx
+ */
+mxVmlCanvas2D.prototype.getVmlDashStyle = function()
+{
+	var result = 'dash';
+	
+	if (typeof(this.state.dashPattern) === 'string')
+	{
+		var tok = this.state.dashPattern.split(' ');
+		
+		if (tok.length > 0 && tok[0] == 1)
+		{
+			result = '0 2';
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: createShadow
+ * 
+ * Creates a shadow for the given node.
+ */
+mxVmlCanvas2D.prototype.createShadow = function(node, filled, stroked)
+{
+	var s = this.state;
+	var rad = -s.rotation * (Math.PI / 180);
+	var cos = Math.cos(rad);
+	var sin = Math.sin(rad);
+
+	var dx = s.shadowDx * s.scale;
+	var dy = s.shadowDy * s.scale;
+
+	if (this.root.style.flip == 'x')
+	{
+		dx *= -1;
+	}
+	else if (this.root.style.flip == 'y')
+	{
+		dy *= -1;
+	}
+	
+	var shadow = node.cloneNode(true);
+	shadow.style.marginLeft = Math.round(dx * cos - dy * sin) + 'px';
+	shadow.style.marginTop = Math.round(dx * sin + dy * cos) + 'px';
+
+	// Workaround for wrong cloning in IE8 standards mode
+	if (document.documentMode == 8)
+	{
+		shadow.strokeweight = node.strokeweight;
+		
+		if (node.nodeName == 'shape')
+		{
+			shadow.path = this.path.join(' ') + ' e';
+			shadow.style.width = this.root.style.width;
+			shadow.style.height = this.root.style.height;
+			shadow.coordsize = parseInt(node.style.width) + ' ' + parseInt(node.style.height);
+		}
+	}
+	
+	if (stroked)
+	{
+		shadow.strokecolor = s.shadowColor;
+		shadow.appendChild(this.createShadowStroke());
+	}
+	else
+	{
+		shadow.stroked = 'false';
+	}
+	
+	if (filled)
+	{
+		shadow.appendChild(this.createShadowFill());
+	}
+	else
+	{
+		shadow.filled = 'false';
+	}
+	
+	return shadow;
+};
+
+/**
+ * Function: createShadowFill
+ * 
+ * Creates the fill for the shadow.
+ */
+mxVmlCanvas2D.prototype.createShadowFill = function()
+{
+	var fill = this.createVmlElement('fill');
+	fill.color = this.state.shadowColor;
+	fill.opacity = (this.state.alpha * this.state.shadowAlpha * 100) + '%';
+	
+	return fill;
+};
+
+/**
+ * Function: createShadowStroke
+ * 
+ * Creates the stroke for the shadow.
+ */
+mxVmlCanvas2D.prototype.createShadowStroke = function()
+{
+	var stroke = this.createStroke();
+	stroke.opacity = (this.state.alpha * this.state.shadowAlpha * 100) + '%';
+	
+	return stroke;
+};
+
+/**
+ * Function: rotate
+ * 
+ * Sets the rotation of the canvas. Note that rotation cannot be concatenated.
+ */
+mxVmlCanvas2D.prototype.rotate = function(theta, flipH, flipV, cx, cy)
+{
+	if (flipH && flipV)
+	{
+		theta += 180;
+	}
+	else if (flipH)
+	{
+		this.root.style.flip = 'x';
+	}
+	else if (flipV)
+	{
+		this.root.style.flip = 'y';
+	}
+
+	if (flipH ? !flipV : flipV)
+	{
+		theta *= -1;
+	}
+
+	this.root.style.rotation = theta;
+	this.state.rotation = this.state.rotation + theta;
+	this.state.rotationCx = cx;
+	this.state.rotationCy = cy;
+};
+
+/**
+ * Function: begin
+ * 
+ * Extends superclass to create path.
+ */
+mxVmlCanvas2D.prototype.begin = function()
+{
+	mxAbstractCanvas2D.prototype.begin.apply(this, arguments);
+	this.node = this.createVmlElement('shape');
+	this.node.style.position = 'absolute';
+};
+
+/**
+ * Function: quadTo
+ * 
+ * Replaces quadratic curve with bezier curve in VML.
+ */
+mxVmlCanvas2D.prototype.quadTo = function(x1, y1, x2, y2)
+{
+	var s = this.state;
+
+	var cpx0 = (this.lastX + s.dx) * s.scale;
+	var cpy0 = (this.lastY + s.dy) * s.scale;
+	var qpx1 = (x1 + s.dx) * s.scale;
+	var qpy1 = (y1 + s.dy) * s.scale;
+	var cpx3 = (x2 + s.dx) * s.scale;
+	var cpy3 = (y2 + s.dy) * s.scale;
+	
+	var cpx1 = cpx0 + 2/3 * (qpx1 - cpx0);
+	var cpy1 = cpy0 + 2/3 * (qpy1 - cpy0);
+	
+	var cpx2 = cpx3 + 2/3 * (qpx1 - cpx3);
+	var cpy2 = cpy3 + 2/3 * (qpy1 - cpy3);
+	
+	this.path.push('c ' + this.format(cpx1) + ' ' + this.format(cpy1) +
+			' ' + this.format(cpx2) + ' ' + this.format(cpy2) +
+			' ' + this.format(cpx3) + ' ' + this.format(cpy3));
+	this.lastX = (cpx3 / s.scale) - s.dx;
+	this.lastY = (cpy3 / s.scale) - s.dy;
+	
+};
+
+/**
+ * Function: createRect
+ * 
+ * Sets the glass gradient.
+ */
+mxVmlCanvas2D.prototype.createRect = function(nodeName, x, y, w, h)
+{
+	var s = this.state;
+	var n = this.createVmlElement(nodeName);
+	n.style.position = 'absolute';
+	n.style.left = this.format((x + s.dx) * s.scale) + 'px';
+	n.style.top = this.format((y + s.dy) * s.scale) + 'px';
+	n.style.width = this.format(w * s.scale) + 'px';
+	n.style.height = this.format(h * s.scale) + 'px';
+	
+	return n;
+};
+
+/**
+ * Function: rect
+ * 
+ * Sets the current path to a rectangle.
+ */
+mxVmlCanvas2D.prototype.rect = function(x, y, w, h)
+{
+	this.node = this.createRect('rect', x, y, w, h);
+};
+
+/**
+ * Function: roundrect
+ * 
+ * Sets the current path to a rounded rectangle.
+ */
+mxVmlCanvas2D.prototype.roundrect = function(x, y, w, h, dx, dy)
+{
+	this.node = this.createRect('roundrect', x, y, w, h);
+	// SetAttribute needed here for IE8
+	this.node.setAttribute('arcsize', Math.max(dx * 100 / w, dy * 100 / h) + '%');
+};
+
+/**
+ * Function: ellipse
+ * 
+ * Sets the current path to an ellipse.
+ */
+mxVmlCanvas2D.prototype.ellipse = function(x, y, w, h)
+{
+	this.node = this.createRect('oval', x, y, w, h);
+};
+
+/**
+ * Function: image
+ * 
+ * Paints an image.
+ */
+mxVmlCanvas2D.prototype.image = function(x, y, w, h, src, aspect, flipH, flipV)
+{
+	var node = null;
+	
+	if (!aspect)
+	{
+		node = this.createRect('image', x, y, w, h);
+		node.src = src;
+	}
+	else
+	{
+		// Uses fill with aspect to avoid asynchronous update of size
+		node = this.createRect('rect', x, y, w, h);
+		node.stroked = 'false';
+		
+		// Handles image aspect via fill
+		var fill = this.createVmlElement('fill');
+		fill.aspect = (aspect) ? 'atmost' : 'ignore';
+		fill.rotate = 'true';
+		fill.type = 'frame';
+		fill.src = src;
+
+		node.appendChild(fill);
+	}
+	
+	if (flipH && flipV)
+	{
+		node.style.rotation = '180';
+	}
+	else if (flipH)
+	{
+		node.style.flip = 'x';
+	}
+	else if (flipV)
+	{
+		node.style.flip = 'y';
+	}
+	
+	if (this.state.alpha < 1 || this.state.fillAlpha < 1)
+	{
+		// KNOWN: Borders around transparent images in IE<9. Using fill.opacity
+		// fixes this problem by adding a white background in all IE versions.
+		node.style.filter += 'alpha(opacity=' + (this.state.alpha * this.state.fillAlpha * 100) + ')';
+	}
+
+	this.root.appendChild(node);
+};
+
+/**
+ * Function: createText
+ * 
+ * Creates the innermost element that contains the HTML text.
+ */
+mxVmlCanvas2D.prototype.createDiv = function(str, align, valign, overflow)
+{
+	var div = this.createElement('div');
+	var state = this.state;
+
+	var css = '';
+	
+	if (state.fontBackgroundColor != null)
+	{
+		css += 'background-color:' + state.fontBackgroundColor + ';';
+	}
+	
+	if (state.fontBorderColor != null)
+	{
+		css += 'border:1px solid ' + state.fontBorderColor + ';';
+	}
+	
+	if (mxUtils.isNode(str))
+	{
+		div.appendChild(str);
+	}
+	else
+	{
+		if (overflow != 'fill' && overflow != 'width')
+		{
+			var div2 = this.createElement('div');
+			div2.style.cssText = css;
+			div2.style.display = (mxClient.IS_QUIRKS) ? 'inline' : 'inline-block';
+			div2.style.zoom = '1';
+			div2.style.textDecoration = 'inherit';
+			div2.innerHTML = str;
+			div.appendChild(div2);
+		}
+		else
+		{
+			div.style.cssText = css;
+			div.innerHTML = str;
+		}
+	}
+	
+	var style = div.style;
+
+	style.fontSize = (state.fontSize / this.vmlScale) + 'px';
+	style.fontFamily = state.fontFamily;
+	style.color = state.fontColor;
+	style.verticalAlign = 'top';
+	style.textAlign = align || 'left';
+	style.lineHeight = (mxConstants.ABSOLUTE_LINE_HEIGHT) ? (state.fontSize * mxConstants.LINE_HEIGHT / this.vmlScale) + 'px' : mxConstants.LINE_HEIGHT;
+
+	if ((state.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
+	{
+		style.fontWeight = 'bold';
+	}
+
+	if ((state.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
+	{
+		style.fontStyle = 'italic';
+	}
+	
+	if ((state.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)
+	{
+		style.textDecoration = 'underline';
+	}
+	
+	return div;
+};
+
+/**
+ * Function: text
+ * 
+ * Paints the given text. Possible values for format are empty string for plain
+ * text and html for HTML markup. Clipping, text background and border are not
+ * supported for plain text in VML.
+ */
+mxVmlCanvas2D.prototype.text = function(x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation, dir)
+{
+	if (this.textEnabled && str != null)
+	{
+		var s = this.state;
+		
+		if (format == 'html')
+		{
+			if (s.rotation != null)
+			{
+				var pt = this.rotatePoint(x, y, s.rotation, s.rotationCx, s.rotationCy);
+				
+				x = pt.x;
+				y = pt.y;
+			}
+
+			if (document.documentMode == 8 && !mxClient.IS_EM)
+			{
+				x += s.dx;
+				y += s.dy;
+				
+				// Workaround for rendering offsets
+				if (overflow != 'fill' && valign == mxConstants.ALIGN_TOP)
+				{
+					y -= 1;
+				}
+			}
+			else
+			{
+				x *= s.scale;
+				y *= s.scale;
+			}
+
+			// Adds event transparency in IE8 standards without the transparent background
+			// filter which cannot be used due to bugs in the zoomed bounding box (too slow)
+			// FIXME: No event transparency if inside v:rect (ie part of shape)
+			// KNOWN: Offset wrong for rotated text with word that are longer than the wrapping
+			// width in IE8 because real width of text cannot be determined here.
+			// This should be fixed in mxText.updateBoundingBox by calling before this and
+			// passing the real width to this method if not clipped and wrapped.
+			var abs = (document.documentMode == 8 && !mxClient.IS_EM) ? this.createVmlElement('group') : this.createElement('div');
+			abs.style.position = 'absolute';
+			abs.style.display = 'inline';
+			abs.style.left = this.format(x) + 'px';
+			abs.style.top = this.format(y) + 'px';
+			abs.style.zoom = s.scale;
+
+			var box = this.createElement('div');
+			box.style.position = 'relative';
+			box.style.display = 'inline';
+			
+			var margin = mxUtils.getAlignmentAsPoint(align, valign);
+			var dx = margin.x;
+			var dy = margin.y;
+
+			var div = this.createDiv(str, align, valign, overflow);
+			var inner = this.createElement('div');
+			
+			if (dir != null)
+			{
+				div.setAttribute('dir', dir);
+			}
+
+			if (wrap && w > 0)
+			{
+				if (!clip)
+				{
+					div.style.width = Math.round(w) + 'px';
+				}
+				
+				div.style.wordWrap = mxConstants.WORD_WRAP;
+				div.style.whiteSpace = 'normal';
+				
+				// LATER: Check if other cases need to be handled
+				if (div.style.wordWrap == 'break-word')
+				{
+					var tmp = div;
+					
+					if (tmp.firstChild != null && tmp.firstChild.nodeName == 'DIV')
+					{
+						tmp.firstChild.style.width = '100%';
+					}
+				}
+			}
+			else
+			{
+				div.style.whiteSpace = 'nowrap';
+			}
+			
+			var rot = s.rotation + (rotation || 0);
+			
+			if (this.rotateHtml && rot != 0)
+			{
+				inner.style.display = 'inline';
+				inner.style.zoom = '1';
+				inner.appendChild(div);
+
+				// Box not needed for rendering in IE8 standards
+				if (document.documentMode == 8 && !mxClient.IS_EM && this.root.nodeName != 'DIV')
+				{
+					box.appendChild(inner);
+					abs.appendChild(box);
+				}
+				else
+				{
+					abs.appendChild(inner);
+				}
+			}
+			else if (document.documentMode == 8 && !mxClient.IS_EM)
+			{
+				box.appendChild(div);
+				abs.appendChild(box);
+			}
+			else
+			{
+				div.style.display = 'inline';
+				abs.appendChild(div);
+			}
+			
+			// Inserts the node into the DOM
+			if (this.root.nodeName != 'DIV')
+			{
+				// Rectangle to fix position in group
+				var rect = this.createVmlElement('rect');
+				rect.stroked = 'false';
+				rect.filled = 'false';
+
+				rect.appendChild(abs);
+				this.root.appendChild(rect);
+			}
+			else
+			{
+				this.root.appendChild(abs);
+			}
+			
+			if (clip)
+			{
+				div.style.overflow = 'hidden';
+				div.style.width = Math.round(w) + 'px';
+				
+				if (!mxClient.IS_QUIRKS)
+				{
+					div.style.maxHeight = Math.round(h) + 'px';
+				}
+			}
+			else if (overflow == 'fill')
+			{
+				// KNOWN: Affects horizontal alignment in quirks
+				// but fill should only be used with align=left
+				div.style.overflow = 'hidden';
+				div.style.width = (Math.max(0, w) + 1) + 'px';
+				div.style.height = (Math.max(0, h) + 1) + 'px';
+			}
+			else if (overflow == 'width')
+			{
+				// KNOWN: Affects horizontal alignment in quirks
+				// but fill should only be used with align=left
+				div.style.overflow = 'hidden';
+				div.style.width = (Math.max(0, w) + 1) + 'px';
+				div.style.maxHeight = (Math.max(0, h) + 1) + 'px';
+			}
+			
+			if (this.rotateHtml && rot != 0)
+			{
+				var rad = rot * (Math.PI / 180);
+				
+				// Precalculate cos and sin for the rotation
+				var real_cos = parseFloat(parseFloat(Math.cos(rad)).toFixed(8));
+				var real_sin = parseFloat(parseFloat(Math.sin(-rad)).toFixed(8));
+
+				rad %= 2 * Math.PI;
+				if (rad < 0) rad += 2 * Math.PI;
+				rad %= Math.PI;
+				if (rad > Math.PI / 2) rad = Math.PI - rad;
+				
+				var cos = Math.cos(rad);
+				var sin = Math.sin(rad);
+
+				// Adds div to document to measure size
+				if (document.documentMode == 8 && !mxClient.IS_EM)
+				{
+					div.style.display = 'inline-block';
+					inner.style.display = 'inline-block';
+					box.style.display = 'inline-block';
+				}
+				
+				div.style.visibility = 'hidden';
+				div.style.position = 'absolute';
+				document.body.appendChild(div);
+				
+				var sizeDiv = div;
+				
+				if (sizeDiv.firstChild != null && sizeDiv.firstChild.nodeName == 'DIV')
+				{
+					sizeDiv = sizeDiv.firstChild;
+				}
+				
+				var tmp = sizeDiv.offsetWidth + 3;
+				var oh = sizeDiv.offsetHeight;
+				
+				if (clip)
+				{
+					w = Math.min(w, tmp);
+					oh = Math.min(oh, h);
+				}
+				else
+				{
+					w = tmp;
+				}
+
+				// Handles words that are longer than the given wrapping width
+				if (wrap)
+				{
+					div.style.width = w + 'px';
+				}
+				
+				// Simulates max-height in quirks
+				if (mxClient.IS_QUIRKS && (clip || overflow == 'width') && oh > h)
+				{
+					oh = h;
+					
+					// Quirks does not support maxHeight
+					div.style.height = oh + 'px';
+				}
+				
+				h = oh;
+
+				var top_fix = (h - h * cos + w * -sin) / 2 - real_sin * w * (dx + 0.5) + real_cos * h * (dy + 0.5);
+				var left_fix = (w - w * cos + h * -sin) / 2 + real_cos * w * (dx + 0.5) + real_sin * h * (dy + 0.5);
+
+				if (abs.nodeName == 'group' && this.root.nodeName == 'DIV')
+				{
+					// Workaround for bug where group gets moved away if left and top are non-zero in IE8 standards
+					var pos = this.createElement('div');
+					pos.style.display = 'inline-block';
+					pos.style.position = 'absolute';
+					pos.style.left = this.format(x + (left_fix - w / 2) * s.scale) + 'px';
+					pos.style.top = this.format(y + (top_fix - h / 2) * s.scale) + 'px';
+					
+					abs.parentNode.appendChild(pos);
+					pos.appendChild(abs);
+				}
+				else
+				{
+					var sc = (document.documentMode == 8 && !mxClient.IS_EM) ? 1 : s.scale;
+					
+					abs.style.left = this.format(x + (left_fix - w / 2) * sc) + 'px';
+					abs.style.top = this.format(y + (top_fix - h / 2) * sc) + 'px';
+				}
+				
+				// KNOWN: Rotated text rendering quality is bad for IE9 quirks
+				inner.style.filter = "progid:DXImageTransform.Microsoft.Matrix(M11="+real_cos+", M12="+
+					real_sin+", M21="+(-real_sin)+", M22="+real_cos+", sizingMethod='auto expand')";
+				inner.style.backgroundColor = this.rotatedHtmlBackground;
+				
+				if (this.state.alpha < 1)
+				{
+					inner.style.filter += 'alpha(opacity=' + (this.state.alpha * 100) + ')';
+				}
+
+				// Restore parent node for DIV
+				inner.appendChild(div);
+				div.style.position = '';
+				div.style.visibility = '';
+			}
+			else if (document.documentMode != 8 || mxClient.IS_EM)
+			{
+				div.style.verticalAlign = 'top';
+				
+				if (this.state.alpha < 1)
+				{
+					abs.style.filter = 'alpha(opacity=' + (this.state.alpha * 100) + ')';
+				}
+				
+				// Adds div to document to measure size
+				var divParent = div.parentNode;
+				div.style.visibility = 'hidden';
+				document.body.appendChild(div);
+				
+				w = div.offsetWidth;
+				var oh = div.offsetHeight;
+				
+				// Simulates max-height in quirks
+				if (mxClient.IS_QUIRKS && clip && oh > h)
+				{
+					oh = h;
+					
+					// Quirks does not support maxHeight
+					div.style.height = oh + 'px';
+				}
+				
+				h = oh;
+				
+				div.style.visibility = '';
+				divParent.appendChild(div);
+				
+				abs.style.left = this.format(x + w * dx * this.state.scale) + 'px';
+				abs.style.top = this.format(y + h * dy * this.state.scale) + 'px';
+			}
+			else
+			{
+				if (this.state.alpha < 1)
+				{
+					div.style.filter = 'alpha(opacity=' + (this.state.alpha * 100) + ')';
+				}
+				
+				// Faster rendering in IE8 without offsetWidth/Height
+				box.style.left = (dx * 100) + '%';
+				box.style.top = (dy * 100) + '%';
+			}
+		}
+		else
+		{
+			this.plainText(x, y, w, h, mxUtils.htmlEntities(str, false), align, valign, wrap, format, overflow, clip, rotation, dir);
+		}
+	}
+};
+
+/**
+ * Function: plainText
+ * 
+ * Paints the outline of the current path.
+ */
+mxVmlCanvas2D.prototype.plainText = function(x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation, dir)
+{
+	// TextDirection is ignored since this code is not used (format is always HTML in the text function)
+	var s = this.state;
+	x = (x + s.dx) * s.scale;
+	y = (y + s.dy) * s.scale;
+	
+	var node = this.createVmlElement('shape');
+	node.style.width = '1px';
+	node.style.height = '1px';
+	node.stroked = 'false';
+
+	var fill = this.createVmlElement('fill');
+	fill.color = s.fontColor;
+	fill.opacity = (s.alpha * 100) + '%';
+	node.appendChild(fill);
+	
+	var path = this.createVmlElement('path');
+	path.textpathok = 'true';
+	path.v = 'm ' + this.format(0) + ' ' + this.format(0) + ' l ' + this.format(1) + ' ' + this.format(0);
+	
+	node.appendChild(path);
+	
+	// KNOWN: Font family and text decoration ignored
+	var tp = this.createVmlElement('textpath');
+	tp.style.cssText = 'v-text-align:' + align;
+	tp.style.align = align;
+	tp.style.fontFamily = s.fontFamily;
+	tp.string = str;
+	tp.on = 'true';
+	
+	// Scale via fontsize instead of node.style.zoom for correct offsets in IE8
+	var size = s.fontSize * s.scale / this.vmlScale;
+	tp.style.fontSize = size + 'px';
+	
+	// Bold
+	if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
+	{
+		tp.style.fontWeight = 'bold';
+	}
+	
+	// Italic
+	if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
+	{
+		tp.style.fontStyle = 'italic';
+	}
+
+	// Underline
+	if ((s.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)
+	{
+		tp.style.textDecoration = 'underline';
+	}
+
+	var lines = str.split('\n');
+	var textHeight = size + (lines.length - 1) * size * mxConstants.LINE_HEIGHT;
+	var dx = 0;
+	var dy = 0;
+
+	if (valign == mxConstants.ALIGN_BOTTOM)
+	{
+		dy = - textHeight / 2;
+	}
+	else if (valign != mxConstants.ALIGN_MIDDLE) // top
+	{
+		dy = textHeight / 2;
+	}
+
+	if (rotation != null)
+	{
+		node.style.rotation = rotation;
+		var rad = rotation * (Math.PI / 180);
+		dx = Math.sin(rad) * dy;
+		dy = Math.cos(rad) * dy;
+	}
+
+	// FIXME: Clipping is relative to bounding box
+	/*if (clip)
+	{
+		node.style.clip = 'rect(0px ' + this.format(w) + 'px ' + this.format(h) + 'px 0px)';
+	}*/
+	
+	node.appendChild(tp);
+	node.style.left = this.format(x - dx) + 'px';
+	node.style.top = this.format(y + dy) + 'px';
+	
+	this.root.appendChild(node);
+};
+
+/**
+ * Function: stroke
+ * 
+ * Paints the outline of the current path.
+ */
+mxVmlCanvas2D.prototype.stroke = function()
+{
+	this.addNode(false, true);
+};
+
+/**
+ * Function: fill
+ * 
+ * Fills the current path.
+ */
+mxVmlCanvas2D.prototype.fill = function()
+{
+	this.addNode(true, false);
+};
+
+/**
+ * Function: fillAndStroke
+ * 
+ * Fills and paints the outline of the current path.
+ */
+mxVmlCanvas2D.prototype.fillAndStroke = function()
+{
+	this.addNode(true, true);
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxGuide
+ *
+ * Implements the alignment of selection cells to other cells in the graph.
+ * 
+ * Constructor: mxGuide
+ * 
+ * Constructs a new guide object.
+ */
+function mxGuide(graph, states)
+{
+	this.graph = graph;
+	this.setStates(states);
+};
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph> instance.
+ */
+mxGuide.prototype.graph = null;
+
+/**
+ * Variable: states
+ * 
+ * Contains the <mxCellStates> that are used for alignment.
+ */
+mxGuide.prototype.states = null;
+
+/**
+ * Variable: horizontal
+ *
+ * Specifies if horizontal guides are enabled. Default is true.
+ */
+mxGuide.prototype.horizontal = true;
+
+/**
+ * Variable: vertical
+ *
+ * Specifies if vertical guides are enabled. Default is true.
+ */
+mxGuide.prototype.vertical = true;
+
+/**
+ * Variable: vertical
+ *
+ * Holds the <mxShape> for the horizontal guide.
+ */
+mxGuide.prototype.guideX = null;
+
+/**
+ * Variable: vertical
+ *
+ * Holds the <mxShape> for the vertical guide.
+ */
+mxGuide.prototype.guideY = null;
+
+/**
+ * Function: setStates
+ * 
+ * Sets the <mxCellStates> that should be used for alignment.
+ */
+mxGuide.prototype.setStates = function(states)
+{
+	this.states = states;
+};
+
+/**
+ * Function: isEnabledForEvent
+ * 
+ * Returns true if the guide should be enabled for the given native event. This
+ * implementation always returns true.
+ */
+mxGuide.prototype.isEnabledForEvent = function(evt)
+{
+	return true;
+};
+
+/**
+ * Function: getGuideTolerance
+ * 
+ * Returns the tolerance for the guides. Default value is gridSize / 2.
+ */
+mxGuide.prototype.getGuideTolerance = function()
+{
+	return this.graph.gridSize / 2;
+};
+
+/**
+ * Function: createGuideShape
+ * 
+ * Returns the mxShape to be used for painting the respective guide. This
+ * implementation returns a new, dashed and crisp <mxPolyline> using
+ * <mxConstants.GUIDE_COLOR> and <mxConstants.GUIDE_STROKEWIDTH> as the format.
+ * 
+ * Parameters:
+ * 
+ * horizontal - Boolean that specifies which guide should be created.
+ */
+mxGuide.prototype.createGuideShape = function(horizontal)
+{
+	var guide = new mxPolyline([], mxConstants.GUIDE_COLOR, mxConstants.GUIDE_STROKEWIDTH);
+	guide.isDashed = true;
+	
+	return guide;
+};
+
+/**
+ * Function: move
+ * 
+ * Moves the <bounds> by the given <mxPoint> and returnt the snapped point.
+ */
+mxGuide.prototype.move = function(bounds, delta, gridEnabled)
+{
+	if (this.states != null && (this.horizontal || this.vertical) && bounds != null && delta != null)
+	{
+		var trx = this.graph.getView().translate;
+		var scale = this.graph.getView().scale;
+		var dx = delta.x;
+		var dy = delta.y;
+		
+		var overrideX = false;
+		var stateX = null;
+		var valueX = null;
+		var overrideY = false;
+		var stateY = null;
+		var valueY = null;
+		
+		var tt = this.getGuideTolerance();
+		var ttX = tt;
+		var ttY = tt;
+		
+		var b = bounds.clone();
+		b.x += delta.x;
+		b.y += delta.y;
+		
+		var left = b.x;
+		var right = b.x + b.width;
+		var center = b.getCenterX();
+		var top = b.y;
+		var bottom = b.y + b.height;
+		var middle = b.getCenterY();
+	
+		// Snaps the left, center and right to the given x-coordinate
+		function snapX(x, state)
+		{
+			x += this.graph.panDx;
+			var override = false;
+			
+			if (Math.abs(x - center) < ttX)
+			{
+				dx = x - bounds.getCenterX();
+				ttX = Math.abs(x - center);
+				override = true;
+			}
+			else if (Math.abs(x - left) < ttX)
+			{
+				dx = x - bounds.x;
+				ttX = Math.abs(x - left);
+				override = true;
+			}
+			else if (Math.abs(x - right) < ttX)
+			{
+				dx = x - bounds.x - bounds.width;
+				ttX = Math.abs(x - right);
+				override = true;
+			}
+			
+			if (override)
+			{
+				stateX = state;
+				valueX = Math.round(x - this.graph.panDx);
+				
+				if (this.guideX == null)
+				{
+					this.guideX = this.createGuideShape(true);
+					
+					// Makes sure to use either VML or SVG shapes in order to implement
+					// event-transparency on the background area of the rectangle since
+					// HTML shapes do not let mouseevents through even when transparent
+					this.guideX.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
+						mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+					this.guideX.pointerEvents = false;
+					this.guideX.init(this.graph.getView().getOverlayPane());
+				}
+			}
+			
+			overrideX = overrideX || override;
+		};
+		
+		// Snaps the top, middle or bottom to the given y-coordinate
+		function snapY(y)
+		{
+			y += this.graph.panDy;
+			var override = false;
+			
+			if (Math.abs(y - middle) < ttY)
+			{
+				dy = y - bounds.getCenterY();
+				ttY = Math.abs(y -  middle);
+				override = true;
+			}
+			else if (Math.abs(y - top) < ttY)
+			{
+				dy = y - bounds.y;
+				ttY = Math.abs(y - top);
+				override = true;
+			}
+			else if (Math.abs(y - bottom) < ttY)
+			{
+				dy = y - bounds.y - bounds.height;
+				ttY = Math.abs(y - bottom);
+				override = true;
+			}
+			
+			if (override)
+			{
+				stateY = state;
+				valueY = Math.round(y - this.graph.panDy);
+				
+				if (this.guideY == null)
+				{
+					this.guideY = this.createGuideShape(false);
+					
+					// Makes sure to use either VML or SVG shapes in order to implement
+					// event-transparency on the background area of the rectangle since
+					// HTML shapes do not let mouseevents through even when transparent
+					this.guideY.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
+						mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+					this.guideY.pointerEvents = false;
+					this.guideY.init(this.graph.getView().getOverlayPane());
+				}
+			}
+			
+			overrideY = overrideY || override;
+		};
+		
+		for (var i = 0; i < this.states.length; i++)
+		{
+			var state =  this.states[i];
+			
+			if (state != null)
+			{
+				// Align x
+				if (this.horizontal)
+				{
+					snapX.call(this, state.getCenterX(), state);
+					snapX.call(this, state.x, state);
+					snapX.call(this, state.x + state.width, state);
+				}
+	
+				// Align y
+				if (this.vertical)
+				{
+					snapY.call(this, state.getCenterY(), state);
+					snapY.call(this, state.y, state);
+					snapY.call(this, state.y + state.height, state);
+				}
+			}
+		}
+
+		// Moves cells that are off-grid back to the grid on move
+		if (gridEnabled)
+		{
+			if (!overrideX)
+			{
+				var tx = bounds.x - (this.graph.snap(bounds.x /
+					scale - trx.x) + trx.x) * scale;
+				dx = this.graph.snap(dx / scale) * scale - tx;
+			}
+			
+			if (!overrideY)
+			{
+				var ty = bounds.y - (this.graph.snap(bounds.y /
+					scale - trx.y) + trx.y) * scale;
+				dy = this.graph.snap(dy / scale) * scale - ty;
+			}
+		}
+		
+		// Redraws the guides
+		var c = this.graph.container;
+		
+		if (!overrideX && this.guideX != null)
+		{
+			this.guideX.node.style.visibility = 'hidden';
+		}
+		else if (this.guideX != null)
+		{
+			if (stateX != null && bounds != null)
+			{
+				minY = Math.min(bounds.y + dy - this.graph.panDy, stateX.y);
+				maxY = Math.max(bounds.y + bounds.height + dy - this.graph.panDy, stateX.y + stateX.height);
+			}
+			
+			if (minY != null && maxY != null)
+			{
+				this.guideX.points = [new mxPoint(valueX, minY), new mxPoint(valueX, maxY)];
+			}
+			else
+			{
+				this.guideX.points = [new mxPoint(valueX, -this.graph.panDy), new mxPoint(valueX, c.scrollHeight - 3 - this.graph.panDy)];
+			}
+			
+			this.guideX.stroke = this.getGuideColor(stateX, true);
+			this.guideX.node.style.visibility = 'visible';
+			this.guideX.redraw();
+		}
+		
+		if (!overrideY && this.guideY != null)
+		{
+			this.guideY.node.style.visibility = 'hidden';
+		}
+		else if (this.guideY != null)
+		{
+			if (stateY != null && bounds != null)
+			{
+				minX = Math.min(bounds.x + dx - this.graph.panDx, stateY.x);
+				maxX = Math.max(bounds.x + bounds.width + dx - this.graph.panDx, stateY.x + stateY.width);
+			}
+			
+			if (minX != null && maxX != null)
+			{
+				this.guideY.points = [new mxPoint(minX, valueY), new mxPoint(maxX, valueY)];
+			}
+			else
+			{
+				this.guideY.points = [new mxPoint(-this.graph.panDx, valueY), new mxPoint(c.scrollWidth - 3 - this.graph.panDx, valueY)];
+			}
+			
+			this.guideY.stroke = this.getGuideColor(stateY, false);
+			this.guideY.node.style.visibility = 'visible';
+			this.guideY.redraw();
+		}
+		
+		delta = new mxPoint(dx, dy);
+	}
+	
+	return delta;
+};
+
+/**
+ * Function: hide
+ * 
+ * Hides all current guides.
+ */
+mxGuide.prototype.getGuideColor = function(state, horizontal)
+{
+	return mxConstants.GUIDE_COLOR;
+};
+
+/**
+ * Function: hide
+ * 
+ * Hides all current guides.
+ */
+mxGuide.prototype.hide = function()
+{
+	this.setVisible(false);
+};
+
+/**
+ * Function: setVisible
+ * 
+ * Shows or hides the current guides.
+ */
+mxGuide.prototype.setVisible = function(visible)
+{
+	if (this.guideX != null)
+	{
+		this.guideX.node.style.visibility = (visible) ? 'visible' : 'hidden';
+	}
+	
+	if (this.guideY != null)
+	{
+		this.guideY.node.style.visibility = (visible) ? 'visible' : 'hidden';
+	}
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys all resources that this object uses.
+ */
+mxGuide.prototype.destroy = function()
+{
+	if (this.guideX != null)
+	{
+		this.guideX.destroy();
+		this.guideX = null;
+	}
+	
+	if (this.guideY != null)
+	{
+		this.guideY.destroy();
+		this.guideY = null;
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxStencil
+ *
+ * Implements a generic shape which is based on a XML node as a description.
+ * 
+ * shape:
+ * 
+ * The outer element is *shape*, that has attributes:
+ * 
+ * - "name", string, required. The stencil name that uniquely identifies the shape.
+ * - "w" and "h" are optional decimal view bounds. This defines your co-ordinate
+ * system for the graphics operations in the shape. The default is 100,100.
+ * - "aspect", optional string. Either "variable", the default, or "fixed". Fixed
+ * means always render the shape with the aspect ratio defined by the ratio w/h.
+ * Variable causes the ratio to match that of the geometry of the current vertex.
+ * - "strokewidth", optional string. Either an integer or the string "inherit".
+ * "inherit" indicates that the strokeWidth of the cell is only changed on scaling,
+ * not on resizing. Default is "1".
+ * If numeric values are used, the strokeWidth of the cell is changed on both
+ * scaling and resizing and the value defines the multiple that is applied to
+ * the width.
+ * 
+ * connections:
+ * 
+ * If you want to define specific fixed connection points on the shape use the
+ * *connections* element. Each *constraint* element within connections defines
+ * a fixed connection point on the shape. Constraints have attributes:
+ * 
+ * - "perimeter", required. 1 or 0. 0 sets the connection point where specified
+ * by x,y. 1 Causes the position of the connection point to be extrapolated from
+ * the center of the shape, through x,y to the point of intersection with the
+ * perimeter of the shape.
+ * - "x" and "y" are the position of the fixed point relative to the bounds of
+ * the shape. They can be automatically adjusted if perimeter=1. So, (0,0) is top
+ * left, (0.5,0.5) the center, (1,0.5) the center of the right hand edge of the
+ * bounds, etc. Values may be less than 0 or greater than 1 to be positioned
+ * outside of the shape.
+ * - "name", optional string. A unique identifier for the port on the shape.
+ * 
+ * background and foreground:
+ * 
+ * The path of the graphics drawing is split into two elements, *foreground* and
+ * *background*. The split is to define which part any shadow applied to the shape
+ * is derived from (the background). This, generally, means the background is the
+ * line tracing of the outside of the shape, but not always.
+ * 
+ * Any stroke, fill or fillstroke of a background must be the first element of the
+ * foreground element, they must not be used within *background*. If the background
+ * is empty, this is not required.
+ * 
+ * Because the background cannot have any fill or stroke, it can contain only one
+ * *path*, *rect*, *roundrect* or *ellipse* element (or none). It can also not
+ * include *image*, *text* or *include-shape*.
+ * 
+ * Note that the state, styling and drawing in mxGraph stencils is very close in
+ * design to that of HTML 5 canvas. Tutorials on this subject, if you're not
+ * familiar with the topic, will give a good high-level introduction to the
+ * concepts used.
+ * 
+ * State:
+ * 
+ * Rendering within the foreground and background elements has the concept of
+ * state. There are two types of operations other than state save/load, styling
+ * and drawing. The styling operations change the current state, so you can save
+ * the current state with <save/> and pull the last saved state from the state
+ * stack using <restore/>.
+ * 
+ * Styling:
+ * 
+ * The elements that change colors within the current state all take a hash
+ * prefixed hex color code ("#FFEA80").
+ * 
+ * - *strokecolor*, this sets the color that drawing paths will be rendered in
+ * when a stroke or fillstroke command is issued.
+ * - *fillcolor*, this sets the color that the inside of closed paths will be
+ * rendered in when a fill or fillstroke command is issued.
+ * - *fontcolor*, this sets the color that fonts are rendered in when text is drawn.
+ * 
+ * *alpha* defines the degree of transparency used between 1.0 for fully opaque
+ * and 0.0 for fully transparent.
+ * 
+ * *strokewidth* defines the integer thickness of drawing elements rendered by
+ * stroking. Use fixed="1" to apply the value as-is, without scaling.
+ * 
+ * *dashed* is "1" for dashing enabled and "0" for disabled.
+ * 
+ * When *dashed* is enabled the current dash pattern, defined by *dashpattern*,
+ * is used on strokes. dashpattern is a sequence of space separated "on, off"
+ * lengths that define what distance to paint the stroke for, then what distance
+ * to paint nothing for, repeat... The default is "3 3". You could define a more
+ * complex pattern with "5 3 2 6", for example. Generally, it makes sense to have
+ * an even number of elements in the dashpattern, but that's not required.
+ * 
+ * *linejoin*, *linecap* and *miterlimit* are best explained by the Mozilla page
+ * on Canvas styling (about halfway down). The values are all the same except we
+ * use "flat" for linecap, instead of Canvas' "butt".
+ * 
+ * For font styling there are.
+ * 
+ * - *fontsize*, an integer,
+ * - *fontstyle*, an ORed bit pattern of bold (1), italic (2) and underline (4),
+ * i.e bold underline is "5".
+ * - *fontfamily*, is a string defining the typeface to be used.
+ * 
+ * Drawing:
+ * 
+ * Most drawing is contained within a *path* element. Again, the graphic
+ * primitives are very similar to that of HTML 5 canvas.
+ * 
+ * - *move* to attributes required decimals (x,y).
+ * - *line* to attributes required decimals (x,y).
+ * - *quad* to required decimals (x2,y2) via control point required decimals
+ * (x1,y1).
+ * - *curve* to required decimals (x3,y3), via control points required decimals
+ * (x1,y1) and (x2,y2).
+ * - *arc*, this doesn't follow the HTML Canvas signatures, instead it's a copy
+ * of the SVG arc command. The SVG specification documentation gives the best
+ * description of its behaviors. The attributes are named identically, they are
+ * decimals and all required.
+ * - *close* ends the current subpath and causes an automatic straight line to
+ * be drawn from the current point to the initial point of the current subpath.
+ * 
+ * Complex drawing:
+ * 
+ * In addition to the graphics primitive operations there are non-primitive
+ * operations. These provide an easy method to draw some basic shapes.
+ * 
+ * - *rect*, attributes "x", "y", "w", "h", all required decimals
+ * - *roundrect*, attributes "x", "y", "w", "h", all required decimals. Also
+ * "arcsize" an optional decimal attribute defining how large, the corner curves
+ * are.
+ * - *ellipse*, attributes "x", "y", "w", "h", all required decimals.
+ * 
+ * Note that these 3 shapes and all paths must be followed by either a fill,
+ * stroke, or fillstroke.
+ * 
+ * Text:
+ * 
+ * *text* elements have the following attributes.
+ * 
+ * - "str", the text string to display, required.
+ * - "x" and "y", the decimal location (x,y) of the text element, required.
+ * - "align", the horizontal alignment of the text element, either "left",
+ * "center" or "right". Optional, default is "left".
+ * - "valign", the vertical alignment of the text element, either "top", "middle"
+ * or "bottom". Optional, default is "top".
+ * - "localized", 0 or 1, if 1 then the "str" actually contains a key to use to
+ * fetch the value out of mxResources. Optional, default is
+ * <mxStencil.defaultLocalized>.
+ * - "vertical", 0 or 1, if 1 the label is rendered vertically (rotated by 90
+ * degrees). Optional, default is 0.
+ * - "rotation", angle in degrees (0 to 360). The angle to rotate the text by.
+ * Optional, default is 0.
+ * - "align-shape", 0 or 1, if 0 ignore the rotation of the shape when setting
+ * the text rotation. Optional, default is 1.
+ * 
+ * If <allowEval> is true, then the text content of the this element can define
+ * a function which is invoked with the shape as the only argument and returns
+ * the value for the text element (ignored if the str attribute is not null).
+ * 
+ * Images:
+ * 
+ * *image* elements can either be external URLs, or data URIs, where supported
+ * (not in IE 7-). Attributes are:
+ * 
+ * - "src", required string. Either a data URI or URL.
+ * - "x", "y", required decimals. The (x,y) position of the image.
+ * - "w", "h", required decimals. The width and height of the image.
+ * - "flipH" and "flipV", optional 0 or 1. Whether to flip the image along the
+ * horizontal/vertical axis. Default is 0 for both.
+ * 
+ * If <allowEval> is true, then the text content of the this element can define
+ * a function which is invoked with the shape as the only argument and returns
+ * the value for the image source (ignored if the src attribute is not null).
+ * 
+ * Sub-shapes:
+ * 
+ * *include-shape* allow stencils to be rendered within the current stencil by
+ * referencing the sub-stencil by name. Attributes are:
+ * 
+ * - "name", required string. The unique shape name of the stencil.
+ * - "x", "y", "w", "h", required decimals. The (x,y) position of the sub-shape
+ * and its width and height.
+ * 
+ * Constructor: mxStencil
+ * 
+ * Constructs a new generic shape by setting <desc> to the given XML node and
+ * invoking <parseDescription> and <parseConstraints>.
+ * 
+ * Parameters:
+ * 
+ * desc - XML node that contains the stencil description.
+ */
+function mxStencil(desc)
+{
+	this.desc = desc;
+	this.parseDescription();
+	this.parseConstraints();
+};
+
+/**
+ * Variable: defaultLocalized
+ * 
+ * Static global variable that specifies the default value for the localized
+ * attribute of the text element. Default is false.
+ */
+mxStencil.defaultLocalized = false;
+
+/**
+ * Function: allowEval
+ * 
+ * Static global switch that specifies if the use of eval is allowed for
+ * evaluating text content and images. Default is false. Set this to true
+ * if stencils can not contain user input.
+ */
+mxStencil.allowEval = false;
+
+/**
+ * Variable: desc
+ *
+ * Holds the XML node with the stencil description.
+ */
+mxStencil.prototype.desc = null;
+
+/**
+ * Variable: constraints
+ * 
+ * Holds an array of <mxConnectionConstraints> as defined in the shape.
+ */
+mxStencil.prototype.constraints = null;
+
+/**
+ * Variable: aspect
+ *
+ * Holds the aspect of the shape. Default is 'auto'.
+ */
+mxStencil.prototype.aspect = null;
+
+/**
+ * Variable: w0
+ *
+ * Holds the width of the shape. Default is 100.
+ */
+mxStencil.prototype.w0 = null;
+
+/**
+ * Variable: h0
+ *
+ * Holds the height of the shape. Default is 100.
+ */
+mxStencil.prototype.h0 = null;
+
+/**
+ * Variable: bgNodes
+ *
+ * Holds the XML node with the stencil description.
+ */
+mxStencil.prototype.bgNode = null;
+
+/**
+ * Variable: fgNodes
+ *
+ * Holds the XML node with the stencil description.
+ */
+mxStencil.prototype.fgNode = null;
+
+/**
+ * Variable: strokewidth
+ *
+ * Holds the strokewidth direction from the description.
+ */
+mxStencil.prototype.strokewidth = null;
+
+/**
+ * Function: parseDescription
+ *
+ * Reads <w0>, <h0>, <aspect>, <bgNodes> and <fgNodes> from <desc>.
+ */
+mxStencil.prototype.parseDescription = function()
+{
+	// LATER: Preprocess nodes for faster painting
+	this.fgNode = this.desc.getElementsByTagName('foreground')[0];
+	this.bgNode = this.desc.getElementsByTagName('background')[0];
+	this.w0 = Number(this.desc.getAttribute('w') || 100);
+	this.h0 = Number(this.desc.getAttribute('h') || 100);
+	
+	// Possible values for aspect are: variable and fixed where
+	// variable means fill the available space and fixed means
+	// use w0 and h0 to compute the aspect.
+	var aspect = this.desc.getAttribute('aspect');
+	this.aspect = (aspect != null) ? aspect : 'variable';
+	
+	// Possible values for strokewidth are all numbers and "inherit"
+	// where the inherit means take the value from the style (ie. the
+	// user-defined stroke-width). Note that the strokewidth is scaled
+	// by the minimum scaling that is used to draw the shape (sx, sy).
+	var sw = this.desc.getAttribute('strokewidth');
+	this.strokewidth = (sw != null) ? sw : '1';
+};
+
+/**
+ * Function: parseConstraints
+ *
+ * Reads the constraints from <desc> into <constraints> using
+ * <parseConstraint>.
+ */
+mxStencil.prototype.parseConstraints = function()
+{
+	var conns = this.desc.getElementsByTagName('connections')[0];
+	
+	if (conns != null)
+	{
+		var tmp = mxUtils.getChildNodes(conns);
+		
+		if (tmp != null && tmp.length > 0)
+		{
+			this.constraints = [];
+			
+			for (var i = 0; i < tmp.length; i++)
+			{
+				this.constraints.push(this.parseConstraint(tmp[i]));
+			}
+		}
+	}
+};
+
+/**
+ * Function: parseConstraint
+ *
+ * Parses the given XML node and returns its <mxConnectionConstraint>.
+ */
+mxStencil.prototype.parseConstraint = function(node)
+{
+	var x = Number(node.getAttribute('x'));
+	var y = Number(node.getAttribute('y'));
+	var perimeter = node.getAttribute('perimeter') == '1';
+	var name = node.getAttribute('name');
+	
+	return new mxConnectionConstraint(new mxPoint(x, y), perimeter, name);
+};
+
+/**
+ * Function: evaluateTextAttribute
+ * 
+ * Gets the given attribute as a text. The return value from <evaluateAttribute>
+ * is used as a key to <mxResources.get> if the localized attribute in the text
+ * node is 1 or if <defaultLocalized> is true.
+ */
+mxStencil.prototype.evaluateTextAttribute = function(node, attribute, shape)
+{
+	var result = this.evaluateAttribute(node, attribute, shape);
+	var loc = node.getAttribute('localized');
+	
+	if ((mxStencil.defaultLocalized && loc == null) || loc == '1')
+	{
+		result = mxResources.get(result);
+	}
+
+	return result;
+};
+
+/**
+ * Function: evaluateAttribute
+ *
+ * Gets the attribute for the given name from the given node. If the attribute
+ * does not exist then the text content of the node is evaluated and if it is
+ * a function it is invoked with <shape> as the only argument and the return
+ * value is used as the attribute value to be returned.
+ */
+mxStencil.prototype.evaluateAttribute = function(node, attribute, shape)
+{
+	var result = node.getAttribute(attribute);
+	
+	if (result == null)
+	{
+		var text = mxUtils.getTextContent(node);
+		
+		if (text != null && mxStencil.allowEval)
+		{
+			var funct = mxUtils.eval(text);
+			
+			if (typeof(funct) == 'function')
+			{
+				result = funct(shape);
+			}
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: drawShape
+ *
+ * Draws this stencil inside the given bounds.
+ */
+mxStencil.prototype.drawShape = function(canvas, shape, x, y, w, h)
+{
+	// TODO: Internal structure (array of special structs?), relative and absolute
+	// coordinates (eg. note shape, process vs star, actor etc.), text rendering
+	// and non-proportional scaling, how to implement pluggable edge shapes
+	// (start, segment, end blocks), pluggable markers, how to implement
+	// swimlanes (title area) with this API, add icon, horizontal/vertical
+	// label, indicator for all shapes, rotation
+	var direction = mxUtils.getValue(shape.style, mxConstants.STYLE_DIRECTION, null);
+	var aspect = this.computeAspect(shape.style, x, y, w, h, direction);
+	var minScale = Math.min(aspect.width, aspect.height);
+	var sw = (this.strokewidth == 'inherit') ?
+			Number(mxUtils.getNumber(shape.style, mxConstants.STYLE_STROKEWIDTH, 1)) :
+			Number(this.strokewidth) * minScale;
+	canvas.setStrokeWidth(sw);
+
+	this.drawChildren(canvas, shape, x, y, w, h, this.bgNode, aspect, false);
+	this.drawChildren(canvas, shape, x, y, w, h, this.fgNode, aspect, true);
+};
+
+/**
+ * Function: drawChildren
+ *
+ * Draws this stencil inside the given bounds.
+ */
+mxStencil.prototype.drawChildren = function(canvas, shape, x, y, w, h, node, aspect, disableShadow)
+{
+	if (node != null && w > 0 && h > 0)
+	{
+		var tmp = node.firstChild;
+		
+		while (tmp != null)
+		{
+			if (tmp.nodeType == mxConstants.NODETYPE_ELEMENT)
+			{
+				this.drawNode(canvas, shape, tmp, aspect, disableShadow);
+			}
+			
+			tmp = tmp.nextSibling;
+		}
+	}
+};
+
+/**
+ * Function: computeAspect
+ *
+ * Returns a rectangle that contains the offset in x and y and the horizontal
+ * and vertical scale in width and height used to draw this shape inside the
+ * given <mxRectangle>.
+ * 
+ * Parameters:
+ * 
+ * shape - <mxShape> to be drawn.
+ * bounds - <mxRectangle> that should contain the stencil.
+ * direction - Optional direction of the shape to be darwn.
+ */
+mxStencil.prototype.computeAspect = function(shape, x, y, w, h, direction)
+{
+	var x0 = x;
+	var y0 = y;
+	var sx = w / this.w0;
+	var sy = h / this.h0;
+	
+	var inverse = (direction == mxConstants.DIRECTION_NORTH || direction == mxConstants.DIRECTION_SOUTH);
+
+	if (inverse)
+	{
+		sy = w / this.h0;
+		sx = h / this.w0;
+		
+		var delta = (w - h) / 2;
+
+		x0 += delta;
+		y0 -= delta;
+	}
+
+	if (this.aspect == 'fixed')
+	{
+		sy = Math.min(sx, sy);
+		sx = sy;
+		
+		// Centers the shape inside the available space
+		if (inverse)
+		{
+			x0 += (h - this.w0 * sx) / 2;
+			y0 += (w - this.h0 * sy) / 2;
+		}
+		else
+		{
+			x0 += (w - this.w0 * sx) / 2;
+			y0 += (h - this.h0 * sy) / 2;
+		}
+	}
+
+	return new mxRectangle(x0, y0, sx, sy);
+};
+
+/**
+ * Function: drawNode
+ *
+ * Draws this stencil inside the given bounds.
+ */
+mxStencil.prototype.drawNode = function(canvas, shape, node, aspect, disableShadow)
+{
+	var name = node.nodeName;
+	var x0 = aspect.x;
+	var y0 = aspect.y;
+	var sx = aspect.width;
+	var sy = aspect.height;
+	var minScale = Math.min(sx, sy);
+
+	if (name == 'save')
+	{
+		canvas.save();
+	}
+	else if (name == 'restore')
+	{
+		canvas.restore();
+	}
+	else if (name == 'path')
+	{
+		canvas.begin();
+
+		// Renders the elements inside the given path
+		var childNode = node.firstChild;
+		
+		while (childNode != null)
+		{
+			if (childNode.nodeType == mxConstants.NODETYPE_ELEMENT)
+			{
+				this.drawNode(canvas, shape, childNode, aspect, disableShadow);
+			}
+			
+			childNode = childNode.nextSibling;
+		}
+	}
+	else if (name == 'close')
+	{
+		canvas.close();
+	}
+	else if (name == 'move')
+	{
+		canvas.moveTo(x0 + Number(node.getAttribute('x')) * sx, y0 + Number(node.getAttribute('y')) * sy);
+	}
+	else if (name == 'line')
+	{
+		canvas.lineTo(x0 + Number(node.getAttribute('x')) * sx, y0 + Number(node.getAttribute('y')) * sy);
+	}
+	else if (name == 'quad')
+	{
+		canvas.quadTo(x0 + Number(node.getAttribute('x1')) * sx,
+				y0 + Number(node.getAttribute('y1')) * sy,
+				x0 + Number(node.getAttribute('x2')) * sx,
+				y0 + Number(node.getAttribute('y2')) * sy);
+	}
+	else if (name == 'curve')
+	{
+		canvas.curveTo(x0 + Number(node.getAttribute('x1')) * sx,
+				y0 + Number(node.getAttribute('y1')) * sy,
+				x0 + Number(node.getAttribute('x2')) * sx,
+				y0 + Number(node.getAttribute('y2')) * sy,
+				x0 + Number(node.getAttribute('x3')) * sx,
+				y0 + Number(node.getAttribute('y3')) * sy);
+	}
+	else if (name == 'arc')
+	{
+		canvas.arcTo(Number(node.getAttribute('rx')) * sx,
+				Number(node.getAttribute('ry')) * sy,
+				Number(node.getAttribute('x-axis-rotation')),
+				Number(node.getAttribute('large-arc-flag')),
+				Number(node.getAttribute('sweep-flag')),
+				x0 + Number(node.getAttribute('x')) * sx,
+				y0 + Number(node.getAttribute('y')) * sy);
+	}
+	else if (name == 'rect')
+	{
+		canvas.rect(x0 + Number(node.getAttribute('x')) * sx,
+				y0 + Number(node.getAttribute('y')) * sy,
+				Number(node.getAttribute('w')) * sx,
+				Number(node.getAttribute('h')) * sy);
+	}
+	else if (name == 'roundrect')
+	{
+		var arcsize = Number(node.getAttribute('arcsize'));
+
+		if (arcsize == 0)
+		{
+			arcsize = mxConstants.RECTANGLE_ROUNDING_FACTOR * 100;
+		}
+		
+		var w = Number(node.getAttribute('w')) * sx;
+		var h = Number(node.getAttribute('h')) * sy;
+		var factor = Number(arcsize) / 100;
+		var r = Math.min(w * factor, h * factor);
+		
+		canvas.roundrect(x0 + Number(node.getAttribute('x')) * sx,
+				y0 + Number(node.getAttribute('y')) * sy,
+				w, h, r, r);
+	}
+	else if (name == 'ellipse')
+	{
+		canvas.ellipse(x0 + Number(node.getAttribute('x')) * sx,
+			y0 + Number(node.getAttribute('y')) * sy,
+			Number(node.getAttribute('w')) * sx,
+			Number(node.getAttribute('h')) * sy);
+	}
+	else if (name == 'image')
+	{
+		if (!shape.outline)
+		{
+			var src = this.evaluateAttribute(node, 'src', shape);
+			
+			canvas.image(x0 + Number(node.getAttribute('x')) * sx,
+				y0 + Number(node.getAttribute('y')) * sy,
+				Number(node.getAttribute('w')) * sx,
+				Number(node.getAttribute('h')) * sy,
+				src, false, node.getAttribute('flipH') == '1',
+				node.getAttribute('flipV') == '1');
+		}
+	}
+	else if (name == 'text')
+	{
+		if (!shape.outline)
+		{
+			var str = this.evaluateTextAttribute(node, 'str', shape);
+			var rotation = node.getAttribute('vertical') == '1' ? -90 : 0;
+			
+			if (node.getAttribute('align-shape') == '0')
+			{
+				var dr = shape.rotation;
+	
+				// Depends on flipping
+				var flipH = mxUtils.getValue(shape.style, mxConstants.STYLE_FLIPH, 0) == 1;
+				var flipV = mxUtils.getValue(shape.style, mxConstants.STYLE_FLIPV, 0) == 1;
+				
+				if (flipH && flipV)
+				{
+					rotation -= dr;
+				}
+				else if (flipH || flipV)
+				{
+					rotation += dr;
+				}
+				else
+				{
+					rotation -= dr;
+				}
+			}
+	
+			rotation -= node.getAttribute('rotation');
+	
+			canvas.text(x0 + Number(node.getAttribute('x')) * sx,
+					y0 + Number(node.getAttribute('y')) * sy,
+					0, 0, str, node.getAttribute('align') || 'left',
+					node.getAttribute('valign') || 'top', false, '',
+					null, false, rotation);
+		}
+	}
+	else if (name == 'include-shape')
+	{
+		var stencil = mxStencilRegistry.getStencil(node.getAttribute('name'));
+		
+		if (stencil != null)
+		{
+			var x = x0 + Number(node.getAttribute('x')) * sx;
+			var y = y0 + Number(node.getAttribute('y')) * sy;
+			var w = Number(node.getAttribute('w')) * sx;
+			var h = Number(node.getAttribute('h')) * sy;
+			
+			stencil.drawShape(canvas, shape, x, y, w, h);
+		}
+	}
+	else if (name == 'fillstroke')
+	{
+		canvas.fillAndStroke();
+	}
+	else if (name == 'fill')
+	{
+		canvas.fill();
+	}
+	else if (name == 'stroke')
+	{
+		canvas.stroke();
+	}
+	else if (name == 'strokewidth')
+	{
+		var s = (node.getAttribute('fixed') == '1') ? 1 : minScale;
+		canvas.setStrokeWidth(Number(node.getAttribute('width')) * s);
+	}
+	else if (name == 'dashed')
+	{
+		canvas.setDashed(node.getAttribute('dashed') == '1');
+	}
+	else if (name == 'dashpattern')
+	{
+		var value = node.getAttribute('pattern');
+		
+		if (value != null)
+		{
+			var tmp = value.split(' ');
+			var pat = [];
+			
+			for (var i = 0; i < tmp.length; i++)
+			{
+				if (tmp[i].length > 0)
+				{
+					pat.push(Number(tmp[i]) * minScale);
+				}
+			}
+			
+			value = pat.join(' ');
+			canvas.setDashPattern(value);
+		}
+	}
+	else if (name == 'strokecolor')
+	{
+		canvas.setStrokeColor(node.getAttribute('color'));
+	}
+	else if (name == 'linecap')
+	{
+		canvas.setLineCap(node.getAttribute('cap'));
+	}
+	else if (name == 'linejoin')
+	{
+		canvas.setLineJoin(node.getAttribute('join'));
+	}
+	else if (name == 'miterlimit')
+	{
+		canvas.setMiterLimit(Number(node.getAttribute('limit')));
+	}
+	else if (name == 'fillcolor')
+	{
+		canvas.setFillColor(node.getAttribute('color'));
+	}
+	else if (name == 'alpha')
+	{
+		canvas.setAlpha(node.getAttribute('alpha'));
+	}
+	else if (name == 'fontcolor')
+	{
+		canvas.setFontColor(node.getAttribute('color'));
+	}
+	else if (name == 'fontstyle')
+	{
+		canvas.setFontStyle(node.getAttribute('style'));
+	}
+	else if (name == 'fontfamily')
+	{
+		canvas.setFontFamily(node.getAttribute('family'));
+	}
+	else if (name == 'fontsize')
+	{
+		canvas.setFontSize(Number(node.getAttribute('size')) * minScale);
+	}
+	
+	if (disableShadow && (name == 'fillstroke' || name == 'fill' || name == 'stroke'))
+	{
+		disableShadow = false;
+		canvas.setShadow(false);
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxShape
+ *
+ * Base class for all shapes. A shape in mxGraph is a
+ * separate implementation for SVG, VML and HTML. Which
+ * implementation to use is controlled by the <dialect>
+ * property which is assigned from within the <mxCellRenderer>
+ * when the shape is created. The dialect must be assigned
+ * for a shape, and it does normally depend on the browser and
+ * the confiuration of the graph (see <mxGraph> rendering hint).
+ *
+ * For each supported shape in SVG and VML, a corresponding
+ * shape exists in mxGraph, namely for text, image, rectangle,
+ * rhombus, ellipse and polyline. The other shapes are a
+ * combination of these shapes (eg. label and swimlane)
+ * or they consist of one or more (filled) path objects
+ * (eg. actor and cylinder). The HTML implementation is
+ * optional but may be required for a HTML-only view of
+ * the graph.
+ *
+ * Custom Shapes:
+ *
+ * To extend from this class, the basic code looks as follows.
+ * In the special case where the custom shape consists only of
+ * one filled region or one filled region and an additional stroke
+ * the <mxActor> and <mxCylinder> should be subclassed,
+ * respectively.
+ *
+ * (code)
+ * function CustomShape() { }
+ * 
+ * CustomShape.prototype = new mxShape();
+ * CustomShape.prototype.constructor = CustomShape; 
+ * (end)
+ *
+ * To register a custom shape in an existing graph instance,
+ * one must register the shape under a new name in the graph's
+ * cell renderer as follows:
+ *
+ * (code)
+ * mxCellRenderer.registerShape('customShape', CustomShape);
+ * (end)
+ *
+ * The second argument is the name of the constructor.
+ *
+ * In order to use the shape you can refer to the given name above
+ * in a stylesheet. For example, to change the shape for the default
+ * vertex style, the following code is used:
+ *
+ * (code)
+ * var style = graph.getStylesheet().getDefaultVertexStyle();
+ * style[mxConstants.STYLE_SHAPE] = 'customShape';
+ * (end)
+ * 
+ * Constructor: mxShape
+ *
+ * Constructs a new shape.
+ */
+function mxShape(stencil)
+{
+	this.stencil = stencil;
+	this.initStyles();
+};
+
+/**
+ * Variable: dialect
+ *
+ * Holds the dialect in which the shape is to be painted.
+ * This can be one of the DIALECT constants in <mxConstants>.
+ */
+mxShape.prototype.dialect = null;
+
+/**
+ * Variable: scale
+ *
+ * Holds the scale in which the shape is being painted.
+ */
+mxShape.prototype.scale = 1;
+
+/**
+ * Variable: antiAlias
+ * 
+ * Rendering hint for configuring the canvas.
+ */
+mxShape.prototype.antiAlias = true;
+
+/**
+ * Variable: bounds
+ *
+ * Holds the <mxRectangle> that specifies the bounds of this shape.
+ */
+mxShape.prototype.bounds = null;
+
+/**
+ * Variable: points
+ *
+ * Holds the array of <mxPoints> that specify the points of this shape.
+ */
+mxShape.prototype.points = null;
+
+/**
+ * Variable: node
+ *
+ * Holds the outermost DOM node that represents this shape.
+ */
+mxShape.prototype.node = null;
+ 
+/**
+ * Variable: state
+ * 
+ * Optional reference to the corresponding <mxCellState>.
+ */
+mxShape.prototype.state = null;
+
+/**
+ * Variable: style
+ *
+ * Optional reference to the style of the corresponding <mxCellState>.
+ */
+mxShape.prototype.style = null;
+
+/**
+ * Variable: boundingBox
+ *
+ * Contains the bounding box of the shape, that is, the smallest rectangle
+ * that includes all pixels of the shape.
+ */
+mxShape.prototype.boundingBox = null;
+
+/**
+ * Variable: stencil
+ *
+ * Holds the <mxStencil> that defines the shape.
+ */
+mxShape.prototype.stencil = null;
+
+/**
+ * Variable: svgStrokeTolerance
+ *
+ * Event-tolerance for SVG strokes (in px). Default is 8. This is only passed
+ * to the canvas in <createSvgCanvas> if <pointerEvents> is true.
+ */
+mxShape.prototype.svgStrokeTolerance = 8;
+
+/**
+ * Variable: pointerEvents
+ * 
+ * Specifies if pointer events should be handled. Default is true.
+ */
+mxShape.prototype.pointerEvents = true;
+
+/**
+ * Variable: svgPointerEvents
+ * 
+ * Specifies if pointer events should be handled. Default is true.
+ */
+mxShape.prototype.svgPointerEvents = 'all';
+
+/**
+ * Variable: shapePointerEvents
+ * 
+ * Specifies if pointer events outside of shape should be handled. Default
+ * is false.
+ */
+mxShape.prototype.shapePointerEvents = false;
+
+/**
+ * Variable: stencilPointerEvents
+ * 
+ * Specifies if pointer events outside of stencils should be handled. Default
+ * is false. Set this to true for backwards compatibility with the 1.x branch.
+ */
+mxShape.prototype.stencilPointerEvents = false;
+
+/**
+ * Variable: vmlScale
+ * 
+ * Scale for improving the precision of VML rendering. Default is 1.
+ */
+mxShape.prototype.vmlScale = 1;
+
+/**
+ * Variable: outline
+ * 
+ * Specifies if the shape should be drawn as an outline. This disables all
+ * fill colors and can be used to disable other drawing states that should
+ * not be painted for outlines. Default is false. This should be set before
+ * calling <apply>.
+ */
+mxShape.prototype.outline = false;
+
+/**
+ * Variable: visible
+ * 
+ * Specifies if the shape is visible. Default is true.
+ */
+mxShape.prototype.visible = true;
+
+/**
+ * Variable: useSvgBoundingBox
+ * 
+ * Allows to use the SVG bounding box in SVG. Default is false for performance
+ * reasons.
+ */
+mxShape.prototype.useSvgBoundingBox = false;
+
+/**
+ * Function: init
+ *
+ * Initializes the shape by creaing the DOM node using <create>
+ * and adding it into the given container.
+ *
+ * Parameters:
+ *
+ * container - DOM node that will contain the shape.
+ */
+mxShape.prototype.init = function(container)
+{
+	if (this.node == null)
+	{
+		this.node = this.create(container);
+		
+		if (container != null)
+		{
+			container.appendChild(this.node);
+		}
+	}
+};
+
+/**
+ * Function: initStyles
+ *
+ * Sets the styles to their default values.
+ */
+mxShape.prototype.initStyles = function(container)
+{
+	this.strokewidth = 1;
+	this.rotation = 0;
+	this.opacity = 100;
+	this.fillOpacity = 100;
+	this.strokeOpacity = 100;
+	this.flipH = false;
+	this.flipV = false;
+};
+
+/**
+ * Function: isParseVml
+ * 
+ * Specifies if any VML should be added via insertAdjacentHtml to the DOM. This
+ * is only needed in IE8 and only if the shape contains VML markup. This method
+ * returns true.
+ */
+mxShape.prototype.isParseVml = function()
+{
+	return true;
+};
+
+/**
+ * Function: isHtmlAllowed
+ * 
+ * Returns true if HTML is allowed for this shape. This implementation always
+ * returns false.
+ */
+mxShape.prototype.isHtmlAllowed = function()
+{
+	return false;
+};
+
+/**
+ * Function: getSvgScreenOffset
+ * 
+ * Returns 0, or 0.5 if <strokewidth> % 2 == 1.
+ */
+mxShape.prototype.getSvgScreenOffset = function()
+{
+	var sw = this.stencil && this.stencil.strokewidth != 'inherit' ? Number(this.stencil.strokewidth) : this.strokewidth;
+	
+	return (mxUtils.mod(Math.max(1, Math.round(sw * this.scale)), 2) == 1) ? 0.5 : 0;
+};
+
+/**
+ * Function: create
+ *
+ * Creates and returns the DOM node(s) for the shape in
+ * the given container. This implementation invokes
+ * <createSvg>, <createHtml> or <createVml> depending
+ * on the <dialect> and style settings.
+ *
+ * Parameters:
+ *
+ * container - DOM node that will contain the shape.
+ */
+mxShape.prototype.create = function(container)
+{
+	var node = null;
+	
+	if (container != null && container.ownerSVGElement != null)
+	{
+		node = this.createSvg(container);
+	}
+	else if (document.documentMode == 8 || !mxClient.IS_VML ||
+		(this.dialect != mxConstants.DIALECT_VML && this.isHtmlAllowed()))
+	{
+		node = this.createHtml(container);
+	}
+	else
+	{
+		node = this.createVml(container);
+	}
+	
+	return node;
+};
+
+/**
+ * Function: createSvg
+ *
+ * Creates and returns the SVG node(s) to represent this shape.
+ */
+mxShape.prototype.createSvg = function()
+{
+	return document.createElementNS(mxConstants.NS_SVG, 'g');
+};
+
+/**
+ * Function: createVml
+ *
+ * Creates and returns the VML node to represent this shape.
+ */
+mxShape.prototype.createVml = function()
+{
+	var node = document.createElement(mxClient.VML_PREFIX + ':group');
+	node.style.position = 'absolute';
+	
+	return node;
+};
+
+/**
+ * Function: createHtml
+ *
+ * Creates and returns the HTML DOM node(s) to represent
+ * this shape. This implementation falls back to <createVml>
+ * so that the HTML creation is optional.
+ */
+mxShape.prototype.createHtml = function()
+{
+	var node = document.createElement('div');
+	node.style.position = 'absolute';
+	
+	return node;
+};
+
+/**
+ * Function: reconfigure
+ *
+ * Reconfigures this shape. This will update the colors etc in
+ * addition to the bounds or points.
+ */
+mxShape.prototype.reconfigure = function()
+{
+	this.redraw();
+};
+
+/**
+ * Function: redraw
+ *
+ * Creates and returns the SVG node(s) to represent this shape.
+ */
+mxShape.prototype.redraw = function()
+{
+	this.updateBoundsFromPoints();
+	
+	if (this.visible && this.checkBounds())
+	{
+		this.node.style.visibility = 'visible';
+		this.clear();
+		
+		if (this.node.nodeName == 'DIV' && (this.isHtmlAllowed() || !mxClient.IS_VML))
+		{
+			this.redrawHtmlShape();
+		}
+		else
+		{	
+			this.redrawShape();
+		}
+
+		this.updateBoundingBox();
+	}
+	else
+	{
+		this.node.style.visibility = 'hidden';
+		this.boundingBox = null;
+	}
+};
+
+/**
+ * Function: clear
+ * 
+ * Removes all child nodes and resets all CSS.
+ */
+mxShape.prototype.clear = function()
+{
+	if (this.node.ownerSVGElement != null)
+	{
+		while (this.node.lastChild != null)
+		{
+			this.node.removeChild(this.node.lastChild);
+		}
+	}
+	else
+	{
+		this.node.style.cssText = 'position:absolute;' + ((this.cursor != null) ?
+			('cursor:' + this.cursor + ';') : '');
+		this.node.innerHTML = '';
+	}
+};
+
+/**
+ * Function: updateBoundsFromPoints
+ * 
+ * Updates the bounds based on the points.
+ */
+mxShape.prototype.updateBoundsFromPoints = function()
+{
+	var pts = this.points;
+	
+	if (pts != null && pts.length > 0 && pts[0] != null)
+	{
+		this.bounds = new mxRectangle(Number(pts[0].x), Number(pts[0].y), 1, 1);
+		
+		for (var i = 1; i < this.points.length; i++)
+		{
+			if (pts[i] != null)
+			{
+				this.bounds.add(new mxRectangle(Number(pts[i].x), Number(pts[i].y), 1, 1));
+			}
+		}
+	}
+};
+
+/**
+ * Function: getLabelBounds
+ * 
+ * Returns the <mxRectangle> for the label bounds of this shape, based on the
+ * given scaled and translated bounds of the shape. This method should not
+ * change the rectangle in-place. This implementation returns the given rect.
+ */
+mxShape.prototype.getLabelBounds = function(rect)
+{
+	var d = mxUtils.getValue(this.style, mxConstants.STYLE_DIRECTION, mxConstants.DIRECTION_EAST);
+	var bounds = rect;
+	
+	// Normalizes argument for getLabelMargins hook
+	if (d != mxConstants.DIRECTION_SOUTH && d != mxConstants.DIRECTION_NORTH &&
+		this.state != null && this.state.text != null &&
+		this.state.text.isPaintBoundsInverted())
+	{
+		bounds = bounds.clone();
+		var tmp = bounds.width;
+		bounds.width = bounds.height;
+		bounds.height = tmp;
+	}
+		
+	var m = this.getLabelMargins(bounds);
+	
+	if (m != null)
+	{
+		var flipH = mxUtils.getValue(this.style, mxConstants.STYLE_FLIPH, false) == '1';
+		var flipV = mxUtils.getValue(this.style, mxConstants.STYLE_FLIPV, false) == '1';
+		
+		// Handles special case for vertical labels
+		if (this.state != null && this.state.text != null &&
+			this.state.text.isPaintBoundsInverted())
+		{
+			var tmp = m.x;
+			m.x = m.height;
+			m.height = m.width;
+			m.width = m.y;
+			m.y = tmp;
+
+			tmp = flipH;
+			flipH = flipV;
+			flipV = tmp;
+		}
+		
+		return mxUtils.getDirectedBounds(rect, m, this.style, flipH, flipV);
+	}
+	
+	return rect;
+};
+
+/**
+ * Function: getLabelMargins
+ * 
+ * Returns the scaled top, left, bottom and right margin to be used for
+ * computing the label bounds as an <mxRectangle>, where the bottom and right
+ * margin are defined in the width and height of the rectangle, respectively.
+ */
+mxShape.prototype.getLabelMargins= function(rect)
+{
+	return null;
+};
+
+/**
+ * Function: checkBounds
+ * 
+ * Returns true if the bounds are not null and all of its variables are numeric.
+ */
+mxShape.prototype.checkBounds = function()
+{
+	return (!isNaN(this.scale) && isFinite(this.scale) && this.scale > 0 &&
+			this.bounds != null && !isNaN(this.bounds.x) && !isNaN(this.bounds.y) &&
+			!isNaN(this.bounds.width) && !isNaN(this.bounds.height) &&
+			this.bounds.width > 0 && this.bounds.height > 0);
+};
+
+/**
+ * Function: createVmlGroup
+ *
+ * Returns the temporary element used for rendering in IE8 standards mode.
+ */
+mxShape.prototype.createVmlGroup = function()
+{
+	var node = document.createElement(mxClient.VML_PREFIX + ':group');
+	node.style.position = 'absolute';
+	node.style.width = this.node.style.width;
+	node.style.height = this.node.style.height;
+	
+	return node;
+};
+
+/**
+ * Function: redrawShape
+ *
+ * Updates the SVG or VML shape.
+ */
+mxShape.prototype.redrawShape = function()
+{
+	var canvas = this.createCanvas();
+	
+	if (canvas != null)
+	{
+		// Specifies if events should be handled
+		canvas.pointerEvents = this.pointerEvents;
+	
+		this.paint(canvas);
+	
+		if (this.node != canvas.root)
+		{
+			// Forces parsing in IE8 standards mode - slow! avoid
+			this.node.insertAdjacentHTML('beforeend', canvas.root.outerHTML);
+		}
+	
+		if (this.node.nodeName == 'DIV' && document.documentMode == 8)
+		{
+			// Makes DIV transparent to events for IE8 in IE8 standards
+			// mode (Note: Does not work for IE9 in IE8 standards mode
+			// and not for IE11 in enterprise mode)
+			this.node.style.filter = '';
+			
+			// Adds event transparency in IE8 standards
+			mxUtils.addTransparentBackgroundFilter(this.node);
+		}
+		
+		this.destroyCanvas(canvas);
+	}
+};
+
+/**
+ * Function: createCanvas
+ * 
+ * Creates a new canvas for drawing this shape. May return null.
+ */
+mxShape.prototype.createCanvas = function()
+{
+	var canvas = null;
+	
+	// LATER: Check if reusing existing DOM nodes improves performance
+	if (this.node.ownerSVGElement != null)
+	{
+		canvas = this.createSvgCanvas();
+	}
+	else if (mxClient.IS_VML)
+	{
+		this.updateVmlContainer();
+		canvas = this.createVmlCanvas();
+	}
+	
+	if (canvas != null && this.outline)
+	{
+		canvas.setStrokeWidth(this.strokewidth);
+		canvas.setStrokeColor(this.stroke);
+		
+		if (this.isDashed != null)
+		{
+			canvas.setDashed(this.isDashed);
+		}
+		
+		canvas.setStrokeWidth = function() {};
+		canvas.setStrokeColor = function() {};
+		canvas.setFillColor = function() {};
+		canvas.setGradient = function() {};
+		canvas.setDashed = function() {};
+	}
+
+	return canvas;
+};
+
+/**
+ * Function: createSvgCanvas
+ * 
+ * Creates and returns an <mxSvgCanvas2D> for rendering this shape.
+ */
+mxShape.prototype.createSvgCanvas = function()
+{
+	var canvas = new mxSvgCanvas2D(this.node, false);
+	canvas.strokeTolerance = (this.pointerEvents) ? this.svgStrokeTolerance : 0;
+	canvas.pointerEventsValue = this.svgPointerEvents;
+	canvas.blockImagePointerEvents = mxClient.IS_FF;
+	var off = this.getSvgScreenOffset();
+
+	if (off != 0)
+	{
+		this.node.setAttribute('transform', 'translate(' + off + ',' + off + ')');
+	}
+	else
+	{
+		this.node.removeAttribute('transform');
+	}
+	
+	if (!this.antiAlias)
+	{
+		// Rounds all numbers in the SVG output to integers
+		canvas.format = function(value)
+		{
+			return Math.round(parseFloat(value));
+		};
+	}
+	
+	return canvas;
+};
+
+/**
+ * Function: createVmlCanvas
+ * 
+ * Creates and returns an <mxVmlCanvas2D> for rendering this shape.
+ */
+mxShape.prototype.createVmlCanvas = function()
+{
+	// Workaround for VML rendering bug in IE8 standards mode
+	var node = (document.documentMode == 8 && this.isParseVml()) ? this.createVmlGroup() : this.node;
+	var canvas = new mxVmlCanvas2D(node, false);
+	
+	if (node.tagUrn != '')
+	{
+		var w = Math.max(1, Math.round(this.bounds.width));
+		var h = Math.max(1, Math.round(this.bounds.height));
+		node.coordsize = (w * this.vmlScale) + ',' + (h * this.vmlScale);
+		canvas.scale(this.vmlScale);
+		canvas.vmlScale = this.vmlScale;
+	}
+
+	// Painting relative to top, left shape corner
+	var s = this.scale;
+	canvas.translate(-Math.round(this.bounds.x / s), -Math.round(this.bounds.y / s));
+	
+	return canvas;
+};
+
+/**
+ * Function: updateVmlContainer
+ * 
+ * Updates the bounds of the VML container.
+ */
+mxShape.prototype.updateVmlContainer = function()
+{
+	this.node.style.left = Math.round(this.bounds.x) + 'px';
+	this.node.style.top = Math.round(this.bounds.y) + 'px';
+	var w = Math.max(1, Math.round(this.bounds.width));
+	var h = Math.max(1, Math.round(this.bounds.height));
+	this.node.style.width = w + 'px';
+	this.node.style.height = h + 'px';
+	this.node.style.overflow = 'visible';
+};
+
+/**
+ * Function: redrawHtml
+ *
+ * Allow optimization by replacing VML with HTML.
+ */
+mxShape.prototype.redrawHtmlShape = function()
+{
+	// LATER: Refactor methods
+	this.updateHtmlBounds(this.node);
+	this.updateHtmlFilters(this.node);
+	this.updateHtmlColors(this.node);
+};
+
+/**
+ * Function: updateHtmlFilters
+ *
+ * Allow optimization by replacing VML with HTML.
+ */
+mxShape.prototype.updateHtmlFilters = function(node)
+{
+	var f = '';
+	
+	if (this.opacity < 100)
+	{
+		f += 'alpha(opacity=' + (this.opacity) + ')';
+	}
+	
+	if (this.isShadow)
+	{
+		// FIXME: Cannot implement shadow transparency with filter
+		f += 'progid:DXImageTransform.Microsoft.dropShadow (' +
+			'OffX=\'' + Math.round(mxConstants.SHADOW_OFFSET_X * this.scale) + '\', ' +
+			'OffY=\'' + Math.round(mxConstants.SHADOW_OFFSET_Y * this.scale) + '\', ' +
+			'Color=\'' + mxConstants.VML_SHADOWCOLOR + '\')';
+	}
+	
+	if (this.fill != null && this.fill != mxConstants.NONE && this.gradient && this.gradient != mxConstants.NONE)
+	{
+		var start = this.fill;
+		var end = this.gradient;
+		var type = '0';
+		
+		var lookup = {east:0,south:1,west:2,north:3};
+		var dir = (this.direction != null) ? lookup[this.direction] : 0;
+		
+		if (this.gradientDirection != null)
+		{
+			dir = mxUtils.mod(dir + lookup[this.gradientDirection] - 1, 4);
+		}
+
+		if (dir == 1)
+		{
+			type = '1';
+			var tmp = start;
+			start = end;
+			end = tmp;
+		}
+		else if (dir == 2)
+		{
+			var tmp = start;
+			start = end;
+			end = tmp;
+		}
+		else if (dir == 3)
+		{
+			type = '1';
+		}
+		
+		f += 'progid:DXImageTransform.Microsoft.gradient(' +
+			'startColorStr=\'' + start + '\', endColorStr=\'' + end +
+			'\', gradientType=\'' + type + '\')';
+	}
+
+	node.style.filter = f;
+};
+
+/**
+ * Function: mixedModeHtml
+ *
+ * Allow optimization by replacing VML with HTML.
+ */
+mxShape.prototype.updateHtmlColors = function(node)
+{
+	var color = this.stroke;
+	
+	if (color != null && color != mxConstants.NONE)
+	{
+		node.style.borderColor = color;
+
+		if (this.isDashed)
+		{
+			node.style.borderStyle = 'dashed';
+		}
+		else if (this.strokewidth > 0)
+		{
+			node.style.borderStyle = 'solid';
+		}
+
+		node.style.borderWidth = Math.max(1, Math.ceil(this.strokewidth * this.scale)) + 'px';
+	}
+	else
+	{
+		node.style.borderWidth = '0px';
+	}
+
+	color = (this.outline) ? null : this.fill;
+	
+	if (color != null && color != mxConstants.NONE)
+	{
+		node.style.backgroundColor = color;
+		node.style.backgroundImage = 'none';
+	}
+	else if (this.pointerEvents)
+	{
+		 node.style.backgroundColor = 'transparent';
+	}
+	else if (document.documentMode == 8)
+	{
+		mxUtils.addTransparentBackgroundFilter(node);
+	}
+	else
+	{
+		this.setTransparentBackgroundImage(node);
+	}
+};
+
+/**
+ * Function: mixedModeHtml
+ *
+ * Allow optimization by replacing VML with HTML.
+ */
+mxShape.prototype.updateHtmlBounds = function(node)
+{
+	var sw = (document.documentMode >= 9) ? 0 : Math.ceil(this.strokewidth * this.scale);
+	node.style.borderWidth = Math.max(1, sw) + 'px';
+	node.style.overflow = 'hidden';
+	
+	node.style.left = Math.round(this.bounds.x - sw / 2) + 'px';
+	node.style.top = Math.round(this.bounds.y - sw / 2) + 'px';
+
+	if (document.compatMode == 'CSS1Compat')
+	{
+		sw = -sw;
+	}
+	
+	node.style.width = Math.round(Math.max(0, this.bounds.width + sw)) + 'px';
+	node.style.height = Math.round(Math.max(0, this.bounds.height + sw)) + 'px';
+};
+
+/**
+ * Function: destroyCanvas
+ * 
+ * Destroys the given canvas which was used for drawing. This implementation
+ * increments the reference counts on all shared gradients used in the canvas.
+ */
+mxShape.prototype.destroyCanvas = function(canvas)
+{
+	// Manages reference counts
+	if (canvas instanceof mxSvgCanvas2D)
+	{
+		// Increments ref counts
+		for (var key in canvas.gradients)
+		{
+			var gradient = canvas.gradients[key];
+			
+			if (gradient != null)
+			{
+				gradient.mxRefCount = (gradient.mxRefCount || 0) + 1;
+			}
+		}
+		
+		this.releaseSvgGradients(this.oldGradients);
+		this.oldGradients = canvas.gradients;
+	}
+};
+
+/**
+ * Function: paint
+ * 
+ * Generic rendering code.
+ */
+mxShape.prototype.paint = function(c)
+{
+	// Scale is passed-through to canvas
+	var s = this.scale;
+	var x = this.bounds.x / s;
+	var y = this.bounds.y / s;
+	var w = this.bounds.width / s;
+	var h = this.bounds.height / s;
+
+	if (this.isPaintBoundsInverted())
+	{
+		var t = (w - h) / 2;
+		x += t;
+		y -= t;
+		var tmp = w;
+		w = h;
+		h = tmp;
+	}
+	
+	this.updateTransform(c, x, y, w, h);
+	this.configureCanvas(c, x, y, w, h);
+
+	// Adds background rectangle to capture events
+	var bg = null;
+	
+	if ((this.stencil == null && this.points == null && this.shapePointerEvents) ||
+		(this.stencil != null && this.stencilPointerEvents))
+	{
+		var bb = this.createBoundingBox();
+		
+		if (this.dialect == mxConstants.DIALECT_SVG)
+		{
+			bg = this.createTransparentSvgRectangle(bb.x, bb.y, bb.width, bb.height);
+			this.node.appendChild(bg);
+		}
+		else
+		{
+			var rect = c.createRect('rect', bb.x / s, bb.y / s, bb.width / s, bb.height / s);
+			rect.appendChild(c.createTransparentFill());
+			rect.stroked = 'false';
+			c.root.appendChild(rect);
+		}
+	}
+
+	if (this.stencil != null)
+	{
+		this.stencil.drawShape(c, this, x, y, w, h);
+	}
+	else
+	{
+		// Stencils have separate strokewidth
+		c.setStrokeWidth(this.strokewidth);
+		
+		if (this.points != null)
+		{
+			// Paints edge shape
+			var pts = [];
+			
+			for (var i = 0; i < this.points.length; i++)
+			{
+				if (this.points[i] != null)
+				{
+					pts.push(new mxPoint(this.points[i].x / s, this.points[i].y / s));
+				}
+			}
+
+			this.paintEdgeShape(c, pts);
+		}
+		else
+		{
+			// Paints vertex shape
+			this.paintVertexShape(c, x, y, w, h);
+		}
+	}
+	
+	if (bg != null && c.state != null && c.state.transform != null)
+	{
+		bg.setAttribute('transform', c.state.transform);
+	}
+};
+
+/**
+ * Function: configureCanvas
+ * 
+ * Sets the state of the canvas for drawing the shape.
+ */
+mxShape.prototype.configureCanvas = function(c, x, y, w, h)
+{
+	var dash = null;
+	
+	if (this.style != null)
+	{
+		dash = this.style['dashPattern'];		
+	}
+
+	c.setAlpha(this.opacity / 100);
+	c.setFillAlpha(this.fillOpacity / 100);
+	c.setStrokeAlpha(this.strokeOpacity / 100);
+
+	// Sets alpha, colors and gradients
+	if (this.isShadow != null)
+	{
+		c.setShadow(this.isShadow);
+	}
+	
+	// Dash pattern
+	if (this.isDashed != null)
+	{
+		c.setDashed(this.isDashed, (this.style != null) ?
+			mxUtils.getValue(this.style, mxConstants.STYLE_FIX_DASH, false) == 1 : false);
+	}
+
+	if (dash != null)
+	{
+		c.setDashPattern(dash);
+	}
+
+	if (this.fill != null && this.fill != mxConstants.NONE && this.gradient && this.gradient != mxConstants.NONE)
+	{
+		var b = this.getGradientBounds(c, x, y, w, h);
+		c.setGradient(this.fill, this.gradient, b.x, b.y, b.width, b.height, this.gradientDirection);
+	}
+	else
+	{
+		c.setFillColor(this.fill);
+	}
+
+	c.setStrokeColor(this.stroke);
+};
+
+/**
+ * Function: getGradientBounds
+ * 
+ * Returns the bounding box for the gradient box for this shape.
+ */
+mxShape.prototype.getGradientBounds = function(c, x, y, w, h)
+{
+	return new mxRectangle(x, y, w, h);
+};
+
+/**
+ * Function: updateTransform
+ * 
+ * Sets the scale and rotation on the given canvas.
+ */
+mxShape.prototype.updateTransform = function(c, x, y, w, h)
+{
+	// NOTE: Currently, scale is implemented in state and canvas. This will
+	// move to canvas in a later version, so that the states are unscaled
+	// and untranslated and do not need an update after zooming or panning.
+	c.scale(this.scale);
+	c.rotate(this.getShapeRotation(), this.flipH, this.flipV, x + w / 2, y + h / 2);
+};
+
+/**
+ * Function: paintVertexShape
+ * 
+ * Paints the vertex shape.
+ */
+mxShape.prototype.paintVertexShape = function(c, x, y, w, h)
+{
+	this.paintBackground(c, x, y, w, h);
+	c.setShadow(false);
+	this.paintForeground(c, x, y, w, h);
+};
+
+/**
+ * Function: paintBackground
+ * 
+ * Hook for subclassers. This implementation is empty.
+ */
+mxShape.prototype.paintBackground = function(c, x, y, w, h) { };
+
+/**
+ * Function: paintForeground
+ * 
+ * Hook for subclassers. This implementation is empty.
+ */
+mxShape.prototype.paintForeground = function(c, x, y, w, h) { };
+
+/**
+ * Function: paintEdgeShape
+ * 
+ * Hook for subclassers. This implementation is empty.
+ */
+mxShape.prototype.paintEdgeShape = function(c, pts) { };
+
+/**
+ * Function: getArcSize
+ * 
+ * Returns the arc size for the given dimension.
+ */
+mxShape.prototype.getArcSize = function(w, h)
+{
+	var r = 0;
+	
+	if (mxUtils.getValue(this.style, mxConstants.STYLE_ABSOLUTE_ARCSIZE, 0) == '1')
+	{
+		r = Math.min(w / 2, Math.min(h / 2, mxUtils.getValue(this.style,
+			mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2));
+	}
+	else
+	{
+		var f = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE,
+			mxConstants.RECTANGLE_ROUNDING_FACTOR * 100) / 100;
+		r = Math.min(w * f, h * f);
+	}
+	
+	return r;
+};
+
+/**
+ * Function: paintGlassEffect
+ * 
+ * Paints the glass gradient effect.
+ */
+mxShape.prototype.paintGlassEffect = function(c, x, y, w, h, arc)
+{
+	var sw = Math.ceil(this.strokewidth / 2);
+	var size = 0.4;
+	
+	c.setGradient('#ffffff', '#ffffff', x, y, w, h * 0.6, 'south', 0.9, 0.1);
+	c.begin();
+	arc += 2 * sw;
+		
+	if (this.isRounded)
+	{
+		c.moveTo(x - sw + arc, y - sw);
+		c.quadTo(x - sw, y - sw, x - sw, y - sw + arc);
+		c.lineTo(x - sw, y + h * size);
+		c.quadTo(x + w * 0.5, y + h * 0.7, x + w + sw, y + h * size);
+		c.lineTo(x + w + sw, y - sw + arc);
+		c.quadTo(x + w + sw, y - sw, x + w + sw - arc, y - sw);
+	}
+	else
+	{
+		c.moveTo(x - sw, y - sw);
+		c.lineTo(x - sw, y + h * size);
+		c.quadTo(x + w * 0.5, y + h * 0.7, x + w + sw, y + h * size);
+		c.lineTo(x + w + sw, y - sw);
+	}
+	
+	c.close();
+	c.fill();
+};
+
+/**
+ * Function: addPoints
+ * 
+ * Paints the given points with rounded corners.
+ */
+mxShape.prototype.addPoints = function(c, pts, rounded, arcSize, close, exclude, initialMove)
+{
+	if (pts != null && pts.length > 0)
+	{
+		initialMove = (initialMove != null) ? initialMove : true;
+		var pe = pts[pts.length - 1];
+		
+		// Adds virtual waypoint in the center between start and end point
+		if (close && rounded)
+		{
+			pts = pts.slice();
+			var p0 = pts[0];
+			var wp = new mxPoint(pe.x + (p0.x - pe.x) / 2, pe.y + (p0.y - pe.y) / 2);
+			pts.splice(0, 0, wp);
+		}
+	
+		var pt = pts[0];
+		var i = 1;
+	
+		// Draws the line segments
+		if (initialMove)
+		{
+			c.moveTo(pt.x, pt.y);
+		}
+		else
+		{
+			c.lineTo(pt.x, pt.y);
+		}
+		
+		while (i < ((close) ? pts.length : pts.length - 1))
+		{
+			var tmp = pts[mxUtils.mod(i, pts.length)];
+			var dx = pt.x - tmp.x;
+			var dy = pt.y - tmp.y;
+	
+			if (rounded && (dx != 0 || dy != 0) && (exclude == null || mxUtils.indexOf(exclude, i - 1) < 0))
+			{
+				// Draws a line from the last point to the current
+				// point with a spacing of size off the current point
+				// into direction of the last point
+				var dist = Math.sqrt(dx * dx + dy * dy);
+				var nx1 = dx * Math.min(arcSize, dist / 2) / dist;
+				var ny1 = dy * Math.min(arcSize, dist / 2) / dist;
+	
+				var x1 = tmp.x + nx1;
+				var y1 = tmp.y + ny1;
+				c.lineTo(x1, y1);
+	
+				// Draws a curve from the last point to the current
+				// point with a spacing of size off the current point
+				// into direction of the next point
+				var next = pts[mxUtils.mod(i + 1, pts.length)];
+				
+				// Uses next non-overlapping point
+				while (i < pts.length - 2 && Math.round(next.x - tmp.x) == 0 && Math.round(next.y - tmp.y) == 0)
+				{
+					next = pts[mxUtils.mod(i + 2, pts.length)];
+					i++;
+				}
+				
+				dx = next.x - tmp.x;
+				dy = next.y - tmp.y;
+	
+				dist = Math.max(1, Math.sqrt(dx * dx + dy * dy));
+				var nx2 = dx * Math.min(arcSize, dist / 2) / dist;
+				var ny2 = dy * Math.min(arcSize, dist / 2) / dist;
+	
+				var x2 = tmp.x + nx2;
+				var y2 = tmp.y + ny2;
+	
+				c.quadTo(tmp.x, tmp.y, x2, y2);
+				tmp = new mxPoint(x2, y2);
+			}
+			else
+			{
+				c.lineTo(tmp.x, tmp.y);
+			}
+	
+			pt = tmp;
+			i++;
+		}
+	
+		if (close)
+		{
+			c.close();
+		}
+		else
+		{
+			c.lineTo(pe.x, pe.y);
+		}
+	}
+};
+
+/**
+ * Function: resetStyles
+ * 
+ * Resets all styles.
+ */
+mxShape.prototype.resetStyles = function()
+{
+	this.initStyles();
+
+	this.spacing = 0;
+	
+	delete this.fill;
+	delete this.gradient;
+	delete this.gradientDirection;
+	delete this.stroke;
+	delete this.startSize;
+	delete this.endSize;
+	delete this.startArrow;
+	delete this.endArrow;
+	delete this.direction;
+	delete this.isShadow;
+	delete this.isDashed;
+	delete this.isRounded;
+	delete this.glass;
+};
+
+/**
+ * Function: apply
+ * 
+ * Applies the style of the given <mxCellState> to the shape. This
+ * implementation assigns the following styles to local fields:
+ * 
+ * - <mxConstants.STYLE_FILLCOLOR> => fill
+ * - <mxConstants.STYLE_GRADIENTCOLOR> => gradient
+ * - <mxConstants.STYLE_GRADIENT_DIRECTION> => gradientDirection
+ * - <mxConstants.STYLE_OPACITY> => opacity
+ * - <mxConstants.STYLE_FILL_OPACITY> => fillOpacity
+ * - <mxConstants.STYLE_STROKE_OPACITY> => strokeOpacity
+ * - <mxConstants.STYLE_STROKECOLOR> => stroke
+ * - <mxConstants.STYLE_STROKEWIDTH> => strokewidth
+ * - <mxConstants.STYLE_SHADOW> => isShadow
+ * - <mxConstants.STYLE_DASHED> => isDashed
+ * - <mxConstants.STYLE_SPACING> => spacing
+ * - <mxConstants.STYLE_STARTSIZE> => startSize
+ * - <mxConstants.STYLE_ENDSIZE> => endSize
+ * - <mxConstants.STYLE_ROUNDED> => isRounded
+ * - <mxConstants.STYLE_STARTARROW> => startArrow
+ * - <mxConstants.STYLE_ENDARROW> => endArrow
+ * - <mxConstants.STYLE_ROTATION> => rotation
+ * - <mxConstants.STYLE_DIRECTION> => direction
+ * - <mxConstants.STYLE_GLASS> => glass
+ *
+ * This keeps a reference to the <style>. If you need to keep a reference to
+ * the cell, you can override this method and store a local reference to
+ * state.cell or the <mxCellState> itself. If <outline> should be true, make
+ * sure to set it before calling this method.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> of the corresponding cell.
+ */
+mxShape.prototype.apply = function(state)
+{
+	this.state = state;
+	this.style = state.style;
+
+	if (this.style != null)
+	{
+		this.fill = mxUtils.getValue(this.style, mxConstants.STYLE_FILLCOLOR, this.fill);
+		this.gradient = mxUtils.getValue(this.style, mxConstants.STYLE_GRADIENTCOLOR, this.gradient);
+		this.gradientDirection = mxUtils.getValue(this.style, mxConstants.STYLE_GRADIENT_DIRECTION, this.gradientDirection);
+		this.opacity = mxUtils.getValue(this.style, mxConstants.STYLE_OPACITY, this.opacity);
+		this.fillOpacity = mxUtils.getValue(this.style, mxConstants.STYLE_FILL_OPACITY, this.fillOpacity);
+		this.strokeOpacity = mxUtils.getValue(this.style, mxConstants.STYLE_STROKE_OPACITY, this.strokeOpacity);
+		this.stroke = mxUtils.getValue(this.style, mxConstants.STYLE_STROKECOLOR, this.stroke);
+		this.strokewidth = mxUtils.getNumber(this.style, mxConstants.STYLE_STROKEWIDTH, this.strokewidth);
+		this.spacing = mxUtils.getValue(this.style, mxConstants.STYLE_SPACING, this.spacing);
+		this.startSize = mxUtils.getNumber(this.style, mxConstants.STYLE_STARTSIZE, this.startSize);
+		this.endSize = mxUtils.getNumber(this.style, mxConstants.STYLE_ENDSIZE, this.endSize);
+		this.startArrow = mxUtils.getValue(this.style, mxConstants.STYLE_STARTARROW, this.startArrow);
+		this.endArrow = mxUtils.getValue(this.style, mxConstants.STYLE_ENDARROW, this.endArrow);
+		this.rotation = mxUtils.getValue(this.style, mxConstants.STYLE_ROTATION, this.rotation);
+		this.direction = mxUtils.getValue(this.style, mxConstants.STYLE_DIRECTION, this.direction);
+		this.flipH = mxUtils.getValue(this.style, mxConstants.STYLE_FLIPH, 0) == 1;
+		this.flipV = mxUtils.getValue(this.style, mxConstants.STYLE_FLIPV, 0) == 1;	
+		
+		// Legacy support for stencilFlipH/V
+		if (this.stencil != null)
+		{
+			this.flipH = mxUtils.getValue(this.style, 'stencilFlipH', 0) == 1 || this.flipH;
+			this.flipV = mxUtils.getValue(this.style, 'stencilFlipV', 0) == 1 || this.flipV;
+		}
+		
+		if (this.direction == mxConstants.DIRECTION_NORTH || this.direction == mxConstants.DIRECTION_SOUTH)
+		{
+			var tmp = this.flipH;
+			this.flipH = this.flipV;
+			this.flipV = tmp;
+		}
+
+		this.isShadow = mxUtils.getValue(this.style, mxConstants.STYLE_SHADOW, this.isShadow) == 1;
+		this.isDashed = mxUtils.getValue(this.style, mxConstants.STYLE_DASHED, this.isDashed) == 1;
+		this.isRounded = mxUtils.getValue(this.style, mxConstants.STYLE_ROUNDED, this.isRounded) == 1;
+		this.glass = mxUtils.getValue(this.style, mxConstants.STYLE_GLASS, this.glass) == 1;
+		
+		if (this.fill == mxConstants.NONE)
+		{
+			this.fill = null;
+		}
+
+		if (this.gradient == mxConstants.NONE)
+		{
+			this.gradient = null;
+		}
+
+		if (this.stroke == mxConstants.NONE)
+		{
+			this.stroke = null;
+		}
+	}
+};
+
+/**
+ * Function: setCursor
+ * 
+ * Sets the cursor on the given shape.
+ *
+ * Parameters:
+ *
+ * cursor - The cursor to be used.
+ */
+mxShape.prototype.setCursor = function(cursor)
+{
+	if (cursor == null)
+	{
+		cursor = '';
+	}
+	
+	this.cursor = cursor;
+
+	if (this.node != null)
+	{
+		this.node.style.cursor = cursor;
+	}
+};
+
+/**
+ * Function: getCursor
+ * 
+ * Returns the current cursor.
+ */
+mxShape.prototype.getCursor = function()
+{
+	return this.cursor;
+};
+
+/**
+ * Function: updateBoundingBox
+ *
+ * Updates the <boundingBox> for this shape using <createBoundingBox> and
+ * <augmentBoundingBox> and stores the result in <boundingBox>.
+ */
+mxShape.prototype.updateBoundingBox = function()
+{
+	// Tries to get bounding box from SVG subsystem
+	// LATER: Use getBoundingClientRect for fallback in VML
+	if (this.useSvgBoundingBox && this.node != null && this.node.ownerSVGElement != null)
+	{
+		try
+		{
+			var b = this.node.getBBox();
+	
+			if (b.width > 0 && b.height > 0)
+			{
+				this.boundingBox = new mxRectangle(b.x, b.y, b.width, b.height);
+				
+				// Adds strokeWidth
+				this.boundingBox.grow(this.strokewidth * this.scale / 2);
+				
+				return;
+			}
+		}
+		catch(e)
+		{
+			// fallback to code below
+		}
+	}
+
+	if (this.bounds != null)
+	{
+		var bbox = this.createBoundingBox();
+		
+		if (bbox != null)
+		{
+			this.augmentBoundingBox(bbox);
+			var rot = this.getShapeRotation();
+			
+			if (rot != 0)
+			{
+				bbox = mxUtils.getBoundingBox(bbox, rot);
+			}
+		}
+
+		this.boundingBox = bbox;
+	}
+};
+
+/**
+ * Function: createBoundingBox
+ *
+ * Returns a new rectangle that represents the bounding box of the bare shape
+ * with no shadows or strokewidths.
+ */
+mxShape.prototype.createBoundingBox = function()
+{
+	var bb = this.bounds.clone();
+
+	if ((this.stencil != null && (this.direction == mxConstants.DIRECTION_NORTH ||
+		this.direction == mxConstants.DIRECTION_SOUTH)) || this.isPaintBoundsInverted())
+	{
+		bb.rotate90();
+	}
+	
+	return bb;
+};
+
+/**
+ * Function: augmentBoundingBox
+ *
+ * Augments the bounding box with the strokewidth and shadow offsets.
+ */
+mxShape.prototype.augmentBoundingBox = function(bbox)
+{
+	if (this.isShadow)
+	{
+		bbox.width += Math.ceil(mxConstants.SHADOW_OFFSET_X * this.scale);
+		bbox.height += Math.ceil(mxConstants.SHADOW_OFFSET_Y * this.scale);
+	}
+	
+	// Adds strokeWidth
+	bbox.grow(this.strokewidth * this.scale / 2);
+};
+
+/**
+ * Function: isPaintBoundsInverted
+ * 
+ * Returns true if the bounds should be inverted.
+ */
+mxShape.prototype.isPaintBoundsInverted = function()
+{
+	// Stencil implements inversion via aspect
+	return this.stencil == null && (this.direction == mxConstants.DIRECTION_NORTH ||
+			this.direction == mxConstants.DIRECTION_SOUTH);
+};
+
+/**
+ * Function: getRotation
+ * 
+ * Returns the rotation from the style.
+ */
+mxShape.prototype.getRotation = function()
+{
+	return (this.rotation != null) ? this.rotation : 0;
+};
+
+/**
+ * Function: getTextRotation
+ * 
+ * Returns the rotation for the text label.
+ */
+mxShape.prototype.getTextRotation = function()
+{
+	var rot = this.getRotation();
+	
+	if (mxUtils.getValue(this.style, mxConstants.STYLE_HORIZONTAL, 1) != 1)
+	{
+		rot += mxText.prototype.verticalTextRotation;
+	}
+	
+	return rot;
+};
+
+/**
+ * Function: getShapeRotation
+ * 
+ * Returns the actual rotation of the shape.
+ */
+mxShape.prototype.getShapeRotation = function()
+{
+	var rot = this.getRotation();
+	
+	if (this.direction != null)
+	{
+		if (this.direction == mxConstants.DIRECTION_NORTH)
+		{
+			rot += 270;
+		}
+		else if (this.direction == mxConstants.DIRECTION_WEST)
+		{
+			rot += 180;
+		}
+		else if (this.direction == mxConstants.DIRECTION_SOUTH)
+		{
+			rot += 90;
+		}
+	}
+	
+	return rot;
+};
+
+/**
+ * Function: createTransparentSvgRectangle
+ * 
+ * Adds a transparent rectangle that catches all events.
+ */
+mxShape.prototype.createTransparentSvgRectangle = function(x, y, w, h)
+{
+	var rect = document.createElementNS(mxConstants.NS_SVG, 'rect');
+	rect.setAttribute('x', x);
+	rect.setAttribute('y', y);
+	rect.setAttribute('width', w);
+	rect.setAttribute('height', h);
+	rect.setAttribute('fill', 'none');
+	rect.setAttribute('stroke', 'none');
+	rect.setAttribute('pointer-events', 'all');
+	
+	return rect;
+};
+
+/**
+ * Function: setTransparentBackgroundImage
+ * 
+ * Sets a transparent background CSS style to catch all events.
+ * 
+ * Paints the line shape.
+ */
+mxShape.prototype.setTransparentBackgroundImage = function(node)
+{
+	node.style.backgroundImage = 'url(\'' + mxClient.imageBasePath + '/transparent.gif\')';
+};
+
+/**
+ * Function: releaseSvgGradients
+ * 
+ * Paints the line shape.
+ */
+mxShape.prototype.releaseSvgGradients = function(grads)
+{
+	if (grads != null)
+	{
+		for (var key in grads)
+		{
+			var gradient = grads[key];
+			
+			if (gradient != null)
+			{
+				gradient.mxRefCount = (gradient.mxRefCount || 0) - 1;
+				
+				if (gradient.mxRefCount == 0 && gradient.parentNode != null)
+				{
+					gradient.parentNode.removeChild(gradient);
+				}
+			}
+		}
+	}
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the shape by removing it from the DOM and releasing the DOM
+ * node associated with the shape using <mxEvent.release>.
+ */
+mxShape.prototype.destroy = function()
+{
+	if (this.node != null)
+	{
+		mxEvent.release(this.node);
+		
+		if (this.node.parentNode != null)
+		{
+			this.node.parentNode.removeChild(this.node);
+		}
+		
+		this.node = null;
+	}
+	
+	// Decrements refCount and removes unused
+	this.releaseSvgGradients(this.oldGradients);
+	this.oldGradients = null;
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ * 
+ * Code to add stencils.
+ * 
+ * (code)
+ * var req = mxUtils.load('test/stencils.xml');
+ * var root = req.getDocumentElement();
+ * var shape = root.firstChild;
+ * 
+ * while (shape != null)
+ * {
+ * 	 if (shape.nodeType == mxConstants.NODETYPE_ELEMENT)
+ *   {
+ *     mxStencilRegistry.addStencil(shape.getAttribute('name'), new mxStencil(shape));
+ *   }
+ *   
+ *   shape = shape.nextSibling;
+ * }
+ * (end)
+ */
+var mxStencilRegistry =
+{
+	/**
+	 * Class: mxStencilRegistry
+	 * 
+	 * A singleton class that provides a registry for stencils and the methods
+	 * for painting those stencils onto a canvas or into a DOM.
+	 */
+	stencils: {},
+	
+	/**
+	 * Function: addStencil
+	 * 
+	 * Adds the given <mxStencil>.
+	 */
+	addStencil: function(name, stencil)
+	{
+		mxStencilRegistry.stencils[name] = stencil;
+	},
+	
+	/**
+	 * Function: getStencil
+	 * 
+	 * Returns the <mxStencil> for the given name.
+	 */
+	getStencil: function(name)
+	{
+		return mxStencilRegistry.stencils[name];
+	}
+
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+var mxMarker =
+{
+	/**
+	 * Class: mxMarker
+	 * 
+	 * A static class that implements all markers for VML and SVG using a
+	 * registry. NOTE: The signatures in this class will change.
+	 * 
+	 * Variable: markers
+	 * 
+	 * Maps from markers names to functions to paint the markers.
+	 */
+	markers: [],
+	
+	/**
+	 * Function: addMarker
+	 * 
+	 * Adds a factory method that updates a given endpoint and returns a
+	 * function to paint the marker onto the given canvas.
+	 */
+	addMarker: function(type, funct)
+	{
+		mxMarker.markers[type] = funct;
+	},
+	
+	/**
+	 * Function: createMarker
+	 * 
+	 * Returns a function to paint the given marker.
+	 */
+	createMarker: function(canvas, shape, type, pe, unitX, unitY, size, source, sw, filled)
+	{
+		var funct = mxMarker.markers[type];
+		
+		return (funct != null) ? funct(canvas, shape, type, pe, unitX, unitY, size, source, sw, filled) : null;
+	}
+
+};
+
+/**
+ * Adds the classic and block marker factory method.
+ */
+(function()
+{
+	function createArrow(widthFactor)
+	{
+		widthFactor = (widthFactor != null) ? widthFactor : 2;
+		
+		return function(canvas, shape, type, pe, unitX, unitY, size, source, sw, filled)
+		{
+			// The angle of the forward facing arrow sides against the x axis is
+			// 26.565 degrees, 1/sin(26.565) = 2.236 / 2 = 1.118 ( / 2 allows for
+			// only half the strokewidth is processed ).
+			var endOffsetX = unitX * sw * 1.118;
+			var endOffsetY = unitY * sw * 1.118;
+			
+			unitX = unitX * (size + sw);
+			unitY = unitY * (size + sw);
+	
+			var pt = pe.clone();
+			pt.x -= endOffsetX;
+			pt.y -= endOffsetY;
+			
+			var f = (type != mxConstants.ARROW_CLASSIC && type != mxConstants.ARROW_CLASSIC_THIN) ? 1 : 3 / 4;
+			pe.x += -unitX * f - endOffsetX;
+			pe.y += -unitY * f - endOffsetY;
+			
+			return function()
+			{
+				canvas.begin();
+				canvas.moveTo(pt.x, pt.y);
+				canvas.lineTo(pt.x - unitX - unitY / widthFactor, pt.y - unitY + unitX / widthFactor);
+			
+				if (type == mxConstants.ARROW_CLASSIC || type == mxConstants.ARROW_CLASSIC_THIN)
+				{
+					canvas.lineTo(pt.x - unitX * 3 / 4, pt.y - unitY * 3 / 4);
+				}
+			
+				canvas.lineTo(pt.x + unitY / widthFactor - unitX, pt.y - unitY - unitX / widthFactor);
+				canvas.close();
+	
+				if (filled)
+				{
+					canvas.fillAndStroke();
+				}
+				else
+				{
+					canvas.stroke();
+				}
+			};
+		}
+	};
+	
+	mxMarker.addMarker('classic', createArrow(2));
+	mxMarker.addMarker('classicThin', createArrow(3));
+	mxMarker.addMarker('block', createArrow(2));
+	mxMarker.addMarker('blockThin', createArrow(3));
+	
+	function createOpenArrow(widthFactor)
+	{
+		widthFactor = (widthFactor != null) ? widthFactor : 2;
+		
+		return function(canvas, shape, type, pe, unitX, unitY, size, source, sw, filled)
+		{
+			// The angle of the forward facing arrow sides against the x axis is
+			// 26.565 degrees, 1/sin(26.565) = 2.236 / 2 = 1.118 ( / 2 allows for
+			// only half the strokewidth is processed ).
+			var endOffsetX = unitX * sw * 1.118;
+			var endOffsetY = unitY * sw * 1.118;
+			
+			unitX = unitX * (size + sw);
+			unitY = unitY * (size + sw);
+			
+			var pt = pe.clone();
+			pt.x -= endOffsetX;
+			pt.y -= endOffsetY;
+			
+			pe.x += -endOffsetX * 2;
+			pe.y += -endOffsetY * 2;
+
+			return function()
+			{
+				canvas.begin();
+				canvas.moveTo(pt.x - unitX - unitY / widthFactor, pt.y - unitY + unitX / widthFactor);
+				canvas.lineTo(pt.x, pt.y);
+				canvas.lineTo(pt.x + unitY / widthFactor - unitX, pt.y - unitY - unitX / widthFactor);
+				canvas.stroke();
+			};
+		}
+	};
+	
+	mxMarker.addMarker('open', createOpenArrow(2));
+	mxMarker.addMarker('openThin', createOpenArrow(3));
+	
+	mxMarker.addMarker('oval', function(canvas, shape, type, pe, unitX, unitY, size, source, sw, filled)
+	{
+		var a = size / 2;
+		
+		var pt = pe.clone();
+		pe.x -= unitX * a;
+		pe.y -= unitY * a;
+
+		return function()
+		{
+			canvas.ellipse(pt.x - a, pt.y - a, size, size);
+						
+			if (filled)
+			{
+				canvas.fillAndStroke();
+			}
+			else
+			{
+				canvas.stroke();
+			}
+		};
+	});
+
+	function diamond(canvas, shape, type, pe, unitX, unitY, size, source, sw, filled)
+	{
+		// The angle of the forward facing arrow sides against the x axis is
+		// 45 degrees, 1/sin(45) = 1.4142 / 2 = 0.7071 ( / 2 allows for
+		// only half the strokewidth is processed ). Or 0.9862 for thin diamond.
+		// Note these values and the tk variable below are dependent, update
+		// both together (saves trig hard coding it).
+		var swFactor = (type == mxConstants.ARROW_DIAMOND) ?  0.7071 : 0.9862;
+		var endOffsetX = unitX * sw * swFactor;
+		var endOffsetY = unitY * sw * swFactor;
+		
+		unitX = unitX * (size + sw);
+		unitY = unitY * (size + sw);
+		
+		var pt = pe.clone();
+		pt.x -= endOffsetX;
+		pt.y -= endOffsetY;
+		
+		pe.x += -unitX - endOffsetX;
+		pe.y += -unitY - endOffsetY;
+		
+		// thickness factor for diamond
+		var tk = ((type == mxConstants.ARROW_DIAMOND) ?  2 : 3.4);
+		
+		return function()
+		{
+			canvas.begin();
+			canvas.moveTo(pt.x, pt.y);
+			canvas.lineTo(pt.x - unitX / 2 - unitY / tk, pt.y + unitX / tk - unitY / 2);
+			canvas.lineTo(pt.x - unitX, pt.y - unitY);
+			canvas.lineTo(pt.x - unitX / 2 + unitY / tk, pt.y - unitY / 2 - unitX / tk);
+			canvas.close();
+			
+			if (filled)
+			{
+				canvas.fillAndStroke();
+			}
+			else
+			{
+				canvas.stroke();
+			}
+		};
+	};
+
+	mxMarker.addMarker('diamond', diamond);
+	mxMarker.addMarker('diamondThin', diamond);
+})();
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxActor
+ *
+ * Extends <mxShape> to implement an actor shape. If a custom shape with one
+ * filled area is needed, then this shape's <redrawPath> should be overridden.
+ * 
+ * Example:
+ * 
+ * (code)
+ * function SampleShape() { }
+ * 
+ * SampleShape.prototype = new mxActor();
+ * SampleShape.prototype.constructor = vsAseShape;
+ * 
+ * mxCellRenderer.prototype.defaultShapes['sample'] = SampleShape;
+ * SampleShape.prototype.redrawPath = function(path, x, y, w, h)
+ * {
+ *   path.moveTo(0, 0);
+ *   path.lineTo(w, h);
+ *   // ...
+ *   path.close();
+ * }
+ * (end)
+ * 
+ * This shape is registered under <mxConstants.SHAPE_ACTOR> in
+ * <mxCellRenderer>.
+ * 
+ * Constructor: mxActor
+ *
+ * Constructs a new actor shape.
+ * 
+ * Parameters:
+ * 
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxActor(bounds, fill, stroke, strokewidth)
+{
+	mxShape.call(this);
+	this.bounds = bounds;
+	this.fill = fill;
+	this.stroke = stroke;
+	this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxUtils.extend(mxActor, mxShape);
+
+/**
+ * Function: paintVertexShape
+ * 
+ * Redirects to redrawPath for subclasses to work.
+ */
+mxActor.prototype.paintVertexShape = function(c, x, y, w, h)
+{
+	c.translate(x, y);
+	c.begin();
+	this.redrawPath(c, x, y, w, h);
+	c.fillAndStroke();
+};
+
+/**
+ * Function: redrawPath
+ *
+ * Draws the path for this shape.
+ */
+mxActor.prototype.redrawPath = function(c, x, y, w, h)
+{
+	var width = w/3;
+	c.moveTo(0, h);
+	c.curveTo(0, 3 * h / 5, 0, 2 * h / 5, w / 2, 2 * h / 5);
+	c.curveTo(w / 2 - width, 2 * h / 5, w / 2 - width, 0, w / 2, 0);
+	c.curveTo(w / 2 + width, 0, w / 2 + width, 2 * h / 5, w / 2, 2 * h / 5);
+	c.curveTo(w, 2 * h / 5, w, 3 * h / 5, w, h);
+	c.close();
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxCloud
+ *
+ * Extends <mxActor> to implement a cloud shape.
+ * 
+ * This shape is registered under <mxConstants.SHAPE_CLOUD> in
+ * <mxCellRenderer>.
+ * 
+ * Constructor: mxCloud
+ *
+ * Constructs a new cloud shape.
+ * 
+ * Parameters:
+ * 
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxCloud(bounds, fill, stroke, strokewidth)
+{
+	mxActor.call(this);
+	this.bounds = bounds;
+	this.fill = fill;
+	this.stroke = stroke;
+	this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxActor.
+ */
+mxUtils.extend(mxCloud, mxActor);
+
+/**
+ * Function: redrawPath
+ *
+ * Draws the path for this shape.
+ */
+mxCloud.prototype.redrawPath = function(c, x, y, w, h)
+{
+	c.moveTo(0.25 * w, 0.25 * h);
+	c.curveTo(0.05 * w, 0.25 * h, 0, 0.5 * h, 0.16 * w, 0.55 * h);
+	c.curveTo(0, 0.66 * h, 0.18 * w, 0.9 * h, 0.31 * w, 0.8 * h);
+	c.curveTo(0.4 * w, h, 0.7 * w, h, 0.8 * w, 0.8 * h);
+	c.curveTo(w, 0.8 * h, w, 0.6 * h, 0.875 * w, 0.5 * h);
+	c.curveTo(w, 0.3 * h, 0.8 * w, 0.1 * h, 0.625 * w, 0.2 * h);
+	c.curveTo(0.5 * w, 0.05 * h, 0.3 * w, 0.05 * h, 0.25 * w, 0.25 * h);
+	c.close();
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxRectangleShape
+ *
+ * Extends <mxShape> to implement a rectangle shape.
+ * This shape is registered under <mxConstants.SHAPE_RECTANGLE>
+ * in <mxCellRenderer>.
+ * 
+ * Constructor: mxRectangleShape
+ *
+ * Constructs a new rectangle shape.
+ * 
+ * Parameters:
+ * 
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxRectangleShape(bounds, fill, stroke, strokewidth)
+{
+	mxShape.call(this);
+	this.bounds = bounds;
+	this.fill = fill;
+	this.stroke = stroke;
+	this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxUtils.extend(mxRectangleShape, mxShape);
+
+/**
+ * Function: isHtmlAllowed
+ *
+ * Returns true for non-rounded, non-rotated shapes with no glass gradient.
+ */
+mxRectangleShape.prototype.isHtmlAllowed = function()
+{
+	var events = true;
+	
+	if (this.style != null)
+	{
+		events = mxUtils.getValue(this.style, mxConstants.STYLE_POINTER_EVENTS, '1') == '1';		
+	}
+	
+	return !this.isRounded && !this.glass && this.rotation == 0 && (events ||
+		(this.fill != null && this.fill != mxConstants.NONE));
+};
+
+/**
+ * Function: paintBackground
+ * 
+ * Generic background painting implementation.
+ */
+mxRectangleShape.prototype.paintBackground = function(c, x, y, w, h)
+{
+	var events = true;
+	
+	if (this.style != null)
+	{
+		events = mxUtils.getValue(this.style, mxConstants.STYLE_POINTER_EVENTS, '1') == '1';		
+	}
+	
+	if (events || (this.fill != null && this.fill != mxConstants.NONE) ||
+		(this.stroke != null && this.stroke != mxConstants.NONE))
+	{
+		if (!events && (this.fill == null || this.fill == mxConstants.NONE))
+		{
+			c.pointerEvents = false;
+		}
+		
+		if (this.isRounded)
+		{
+			var r = 0;
+			
+			if (mxUtils.getValue(this.style, mxConstants.STYLE_ABSOLUTE_ARCSIZE, 0) == '1')
+			{
+				r = Math.min(w / 2, Math.min(h / 2, mxUtils.getValue(this.style,
+					mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2));
+			}
+			else
+			{
+				var f = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE,
+					mxConstants.RECTANGLE_ROUNDING_FACTOR * 100) / 100;
+				r = Math.min(w * f, h * f);
+			}
+			
+			c.roundrect(x, y, w, h, r, r);
+		}
+		else
+		{
+			c.rect(x, y, w, h);
+		}
+			
+		c.fillAndStroke();
+	}
+};
+
+/**
+ * Function: paintForeground
+ * 
+ * Generic background painting implementation.
+ */
+mxRectangleShape.prototype.paintForeground = function(c, x, y, w, h)
+{
+	if (this.glass && !this.outline && this.fill != null && this.fill != mxConstants.NONE)
+	{
+		this.paintGlassEffect(c, x, y, w, h, this.getArcSize(w + this.strokewidth, h + this.strokewidth));
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxEllipse
+ *
+ * Extends <mxShape> to implement an ellipse shape.
+ * This shape is registered under <mxConstants.SHAPE_ELLIPSE>
+ * in <mxCellRenderer>.
+ * 
+ * Constructor: mxEllipse
+ *
+ * Constructs a new ellipse shape.
+ * 
+ * Parameters:
+ * 
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxEllipse(bounds, fill, stroke, strokewidth)
+{
+	mxShape.call(this);
+	this.bounds = bounds;
+	this.fill = fill;
+	this.stroke = stroke;
+	this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxUtils.extend(mxEllipse, mxShape);
+
+/**
+ * Function: paintVertexShape
+ * 
+ * Paints the ellipse shape.
+ */
+mxEllipse.prototype.paintVertexShape = function(c, x, y, w, h)
+{
+	c.ellipse(x, y, w, h);
+	c.fillAndStroke();
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxDoubleEllipse
+ *
+ * Extends <mxShape> to implement a double ellipse shape. This shape is
+ * registered under <mxConstants.SHAPE_DOUBLE_ELLIPSE> in <mxCellRenderer>.
+ * Use the following override to only fill the inner ellipse in this shape:
+ * 
+ * (code)
+ * mxDoubleEllipse.prototype.paintVertexShape = function(c, x, y, w, h)
+ * {
+ *   c.ellipse(x, y, w, h);
+ *   c.stroke();
+ *   
+ *   var inset = mxUtils.getValue(this.style, mxConstants.STYLE_MARGIN, Math.min(3 + this.strokewidth, Math.min(w / 5, h / 5)));
+ *   x += inset;
+ *   y += inset;
+ *   w -= 2 * inset;
+ *   h -= 2 * inset;
+ *   
+ *   if (w > 0 && h > 0)
+ *   {
+ *     c.ellipse(x, y, w, h);
+ *   }
+ *   
+ *   c.fillAndStroke();
+ * };
+ * (end)
+ * 
+ * Constructor: mxDoubleEllipse
+ *
+ * Constructs a new ellipse shape.
+ *
+ * Parameters:
+ *
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxDoubleEllipse(bounds, fill, stroke, strokewidth)
+{
+	mxShape.call(this);
+	this.bounds = bounds;
+	this.fill = fill;
+	this.stroke = stroke;
+	this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxUtils.extend(mxDoubleEllipse, mxShape);
+
+/**
+ * Variable: vmlScale
+ * 
+ * Scale for improving the precision of VML rendering. Default is 10.
+ */
+mxDoubleEllipse.prototype.vmlScale = 10;
+
+/**
+ * Function: paintBackground
+ * 
+ * Paints the background.
+ */
+mxDoubleEllipse.prototype.paintBackground = function(c, x, y, w, h)
+{
+	c.ellipse(x, y, w, h);
+	c.fillAndStroke();
+};
+
+/**
+ * Function: paintForeground
+ * 
+ * Paints the foreground.
+ */
+mxDoubleEllipse.prototype.paintForeground = function(c, x, y, w, h)
+{
+	if (!this.outline)
+	{
+		var margin = mxUtils.getValue(this.style, mxConstants.STYLE_MARGIN, Math.min(3 + this.strokewidth, Math.min(w / 5, h / 5)));
+		x += margin;
+		y += margin;
+		w -= 2 * margin;
+		h -= 2 * margin;
+		
+		// FIXME: Rounding issues in IE8 standards mode (not in 1.x)
+		if (w > 0 && h > 0)
+		{
+			c.ellipse(x, y, w, h);
+		}
+		
+		c.stroke();
+	}
+};
+
+/**
+ * Function: getLabelBounds
+ * 
+ * Returns the bounds for the label.
+ */
+mxDoubleEllipse.prototype.getLabelBounds = function(rect)
+{
+	var margin = (mxUtils.getValue(this.style, mxConstants.STYLE_MARGIN, Math.min(3 + this.strokewidth,
+			Math.min(rect.width / 5 / this.scale, rect.height / 5 / this.scale)))) * this.scale;
+
+	return new mxRectangle(rect.x + margin, rect.y + margin, rect.width - 2 * margin, rect.height - 2 * margin);
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxRhombus
+ *
+ * Extends <mxShape> to implement a rhombus (aka diamond) shape.
+ * This shape is registered under <mxConstants.SHAPE_RHOMBUS>
+ * in <mxCellRenderer>.
+ * 
+ * Constructor: mxRhombus
+ *
+ * Constructs a new rhombus shape.
+ * 
+ * Parameters:
+ * 
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxRhombus(bounds, fill, stroke, strokewidth)
+{
+	mxShape.call(this);
+	this.bounds = bounds;
+	this.fill = fill;
+	this.stroke = stroke;
+	this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxUtils.extend(mxRhombus, mxShape);
+
+/**
+ * Function: paintVertexShape
+ * 
+ * Generic painting implementation.
+ */
+mxRhombus.prototype.paintVertexShape = function(c, x, y, w, h)
+{
+	var hw = w / 2;
+	var hh = h / 2;
+	
+	var arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
+	c.begin();
+	this.addPoints(c, [new mxPoint(x + hw, y), new mxPoint(x + w, y + hh), new mxPoint(x + hw, y + h),
+	     new mxPoint(x, y + hh)], this.isRounded, arcSize, true);
+	c.fillAndStroke();
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxPolyline
+ *
+ * Extends <mxShape> to implement a polyline (a line with multiple points).
+ * This shape is registered under <mxConstants.SHAPE_POLYLINE> in
+ * <mxCellRenderer>.
+ * 
+ * Constructor: mxPolyline
+ *
+ * Constructs a new polyline shape.
+ * 
+ * Parameters:
+ * 
+ * points - Array of <mxPoints> that define the points. This is stored in
+ * <mxShape.points>.
+ * stroke - String that defines the stroke color. Default is 'black'. This is
+ * stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxPolyline(points, stroke, strokewidth)
+{
+	mxShape.call(this);
+	this.points = points;
+	this.stroke = stroke;
+	this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxUtils.extend(mxPolyline, mxShape);
+
+/**
+ * Function: getRotation
+ * 
+ * Returns 0.
+ */
+mxPolyline.prototype.getRotation = function()
+{
+	return 0;
+};
+
+/**
+ * Function: getShapeRotation
+ * 
+ * Returns 0.
+ */
+mxPolyline.prototype.getShapeRotation = function()
+{
+	return 0;
+};
+
+/**
+ * Function: isPaintBoundsInverted
+ * 
+ * Returns false.
+ */
+mxPolyline.prototype.isPaintBoundsInverted = function()
+{
+	return false;
+};
+
+/**
+ * Function: paintEdgeShape
+ * 
+ * Paints the line shape.
+ */
+mxPolyline.prototype.paintEdgeShape = function(c, pts)
+{
+	if (this.style == null || this.style[mxConstants.STYLE_CURVED] != 1)
+	{
+		this.paintLine(c, pts, this.isRounded);
+	}
+	else
+	{
+		this.paintCurvedLine(c, pts);
+	}
+};
+
+/**
+ * Function: paintLine
+ * 
+ * Paints the line shape.
+ */
+mxPolyline.prototype.paintLine = function(c, pts, rounded)
+{
+	var arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
+	c.begin();
+	this.addPoints(c, pts, rounded, arcSize, false);
+	c.stroke();
+};
+
+/**
+ * Function: paintLine
+ * 
+ * Paints the line shape.
+ */
+mxPolyline.prototype.paintCurvedLine = function(c, pts)
+{
+	c.begin();
+	
+	var pt = pts[0];
+	var n = pts.length;
+	
+	c.moveTo(pt.x, pt.y);
+	
+	for (var i = 1; i < n - 2; i++)
+	{
+		var p0 = pts[i];
+		var p1 = pts[i + 1];
+		var ix = (p0.x + p1.x) / 2;
+		var iy = (p0.y + p1.y) / 2;
+		
+		c.quadTo(p0.x, p0.y, ix, iy);
+	}
+	
+	var p0 = pts[n - 2];
+	var p1 = pts[n - 1];
+	
+	c.quadTo(p0.x, p0.y, p1.x, p1.y);
+	c.stroke();
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxArrow
+ *
+ * Extends <mxShape> to implement an arrow shape. (The shape
+ * is used to represent edges, not vertices.)
+ * This shape is registered under <mxConstants.SHAPE_ARROW>
+ * in <mxCellRenderer>.
+ * 
+ * Constructor: mxArrow
+ *
+ * Constructs a new arrow shape.
+ * 
+ * Parameters:
+ * 
+ * points - Array of <mxPoints> that define the points. This is stored in
+ * <mxShape.points>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ * arrowWidth - Optional integer that defines the arrow width. Default is
+ * <mxConstants.ARROW_WIDTH>. This is stored in <arrowWidth>.
+ * spacing - Optional integer that defines the spacing between the arrow shape
+ * and its endpoints. Default is <mxConstants.ARROW_SPACING>. This is stored in
+ * <spacing>.
+ * endSize - Optional integer that defines the size of the arrowhead. Default
+ * is <mxConstants.ARROW_SIZE>. This is stored in <endSize>.
+ */
+function mxArrow(points, fill, stroke, strokewidth, arrowWidth, spacing, endSize)
+{
+	mxShape.call(this);
+	this.points = points;
+	this.fill = fill;
+	this.stroke = stroke;
+	this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+	this.arrowWidth = (arrowWidth != null) ? arrowWidth : mxConstants.ARROW_WIDTH;
+	this.spacing = (spacing != null) ? spacing : mxConstants.ARROW_SPACING;
+	this.endSize = (endSize != null) ? endSize : mxConstants.ARROW_SIZE;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxUtils.extend(mxArrow, mxShape);
+
+/**
+ * Function: augmentBoundingBox
+ *
+ * Augments the bounding box with the edge width and markers.
+ */
+mxArrow.prototype.augmentBoundingBox = function(bbox)
+{
+	mxShape.prototype.augmentBoundingBox.apply(this, arguments);
+	
+	var w = Math.max(this.arrowWidth, this.endSize);
+	bbox.grow((w / 2 + this.strokewidth) * this.scale);
+};
+
+/**
+ * Function: paintEdgeShape
+ * 
+ * Paints the line shape.
+ */
+mxArrow.prototype.paintEdgeShape = function(c, pts)
+{
+	// Geometry of arrow
+	var spacing =  mxConstants.ARROW_SPACING;
+	var width = mxConstants.ARROW_WIDTH;
+	var arrow = mxConstants.ARROW_SIZE;
+
+	// Base vector (between end points)
+	var p0 = pts[0];
+	var pe = pts[pts.length - 1];
+	var dx = pe.x - p0.x;
+	var dy = pe.y - p0.y;
+	var dist = Math.sqrt(dx * dx + dy * dy);
+	var length = dist - 2 * spacing - arrow;
+	
+	// Computes the norm and the inverse norm
+	var nx = dx / dist;
+	var ny = dy / dist;
+	var basex = length * nx;
+	var basey = length * ny;
+	var floorx = width * ny/3;
+	var floory = -width * nx/3;
+	
+	// Computes points
+	var p0x = p0.x - floorx / 2 + spacing * nx;
+	var p0y = p0.y - floory / 2 + spacing * ny;
+	var p1x = p0x + floorx;
+	var p1y = p0y + floory;
+	var p2x = p1x + basex;
+	var p2y = p1y + basey;
+	var p3x = p2x + floorx;
+	var p3y = p2y + floory;
+	// p4 not necessary
+	var p5x = p3x - 3 * floorx;
+	var p5y = p3y - 3 * floory;
+	
+	c.begin();
+	c.moveTo(p0x, p0y);
+	c.lineTo(p1x, p1y);
+	c.lineTo(p2x, p2y);
+	c.lineTo(p3x, p3y);
+	c.lineTo(pe.x - spacing * nx, pe.y - spacing * ny);
+	c.lineTo(p5x, p5y);
+	c.lineTo(p5x + floorx, p5y + floory);
+	c.close();
+
+	c.fillAndStroke();
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxArrowConnector
+ *
+ * Extends <mxShape> to implement an new rounded arrow shape with support for
+ * waypoints and double arrows. (The shape is used to represent edges, not
+ * vertices.) This shape is registered under <mxConstants.SHAPE_ARROW_CONNECTOR>
+ * in <mxCellRenderer>.
+ * 
+ * Constructor: mxArrowConnector
+ *
+ * Constructs a new arrow shape.
+ * 
+ * Parameters:
+ * 
+ * points - Array of <mxPoints> that define the points. This is stored in
+ * <mxShape.points>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ * arrowWidth - Optional integer that defines the arrow width. Default is
+ * <mxConstants.ARROW_WIDTH>. This is stored in <arrowWidth>.
+ * spacing - Optional integer that defines the spacing between the arrow shape
+ * and its endpoints. Default is <mxConstants.ARROW_SPACING>. This is stored in
+ * <spacing>.
+ * endSize - Optional integer that defines the size of the arrowhead. Default
+ * is <mxConstants.ARROW_SIZE>. This is stored in <endSize>.
+ */
+function mxArrowConnector(points, fill, stroke, strokewidth, arrowWidth, spacing, endSize)
+{
+	mxShape.call(this);
+	this.points = points;
+	this.fill = fill;
+	this.stroke = stroke;
+	this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+	this.arrowWidth = (arrowWidth != null) ? arrowWidth : mxConstants.ARROW_WIDTH;
+	this.arrowSpacing = (spacing != null) ? spacing : mxConstants.ARROW_SPACING;
+	this.startSize = mxConstants.ARROW_SIZE / 5;
+	this.endSize = mxConstants.ARROW_SIZE / 5;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxUtils.extend(mxArrowConnector, mxShape);
+
+/**
+ * Variable: useSvgBoundingBox
+ * 
+ * Allows to use the SVG bounding box in SVG. Default is false for performance
+ * reasons.
+ */
+mxArrowConnector.prototype.useSvgBoundingBox = true;
+
+/**
+ * Variable: resetStyles
+ * 
+ * Overrides mxShape to reset spacing.
+ */
+mxArrowConnector.prototype.resetStyles = function()
+{
+	mxShape.prototype.resetStyles.apply(this, arguments);
+	
+	this.arrowSpacing = mxConstants.ARROW_SPACING;
+};
+
+/**
+ * Overrides apply to get smooth transition from default start- and endsize.
+ */
+mxArrowConnector.prototype.apply = function(state)
+{
+	mxShape.prototype.apply.apply(this, arguments);
+
+	if (this.style != null)
+	{
+		this.startSize = mxUtils.getNumber(this.style, mxConstants.STYLE_STARTSIZE, mxConstants.ARROW_SIZE / 5) * 3;
+		this.endSize = mxUtils.getNumber(this.style, mxConstants.STYLE_ENDSIZE, mxConstants.ARROW_SIZE / 5) * 3;
+	}
+};
+
+/**
+ * Function: augmentBoundingBox
+ *
+ * Augments the bounding box with the edge width and markers.
+ */
+mxArrowConnector.prototype.augmentBoundingBox = function(bbox)
+{
+	mxShape.prototype.augmentBoundingBox.apply(this, arguments);
+	
+	var w = this.getEdgeWidth();
+	
+	if (this.isMarkerStart())
+	{
+		w = Math.max(w, this.getStartArrowWidth());
+	}
+	
+	if (this.isMarkerEnd())
+	{
+		w = Math.max(w, this.getEndArrowWidth());
+	}
+	
+	bbox.grow((w / 2 + this.strokewidth) * this.scale);
+};
+
+/**
+ * Function: paintEdgeShape
+ * 
+ * Paints the line shape.
+ */
+mxArrowConnector.prototype.paintEdgeShape = function(c, pts)
+{
+	// Geometry of arrow
+	var strokeWidth = this.strokewidth;
+	
+	if (this.outline)
+	{
+		strokeWidth = Math.max(1, mxUtils.getNumber(this.style, mxConstants.STYLE_STROKEWIDTH, this.strokewidth));
+	}
+	
+	var startWidth = this.getStartArrowWidth() + strokeWidth;
+	var endWidth = this.getEndArrowWidth() + strokeWidth;
+	var edgeWidth = this.outline ? this.getEdgeWidth() + strokeWidth : this.getEdgeWidth();
+	var openEnded = this.isOpenEnded();
+	var markerStart = this.isMarkerStart();
+	var markerEnd = this.isMarkerEnd();
+	var spacing = (openEnded) ? 0 : this.arrowSpacing + strokeWidth / 2;
+	var startSize = this.startSize + strokeWidth;
+	var endSize = this.endSize + strokeWidth;
+	var isRounded = this.isArrowRounded();
+	
+	// Base vector (between first points)
+	var pe = pts[pts.length - 1];
+
+	// Finds first non-overlapping point
+	var i0 = 1;
+	
+	while (i0 < pts.length - 1 && pts[i0].x == pts[0].x && pts[i0].y == pts[0].y)
+	{
+		i0++;
+	}
+	
+	var dx = pts[i0].x - pts[0].x;
+	var dy = pts[i0].y - pts[0].y;
+	var dist = Math.sqrt(dx * dx + dy * dy);
+	
+	if (dist == 0)
+	{
+		return;
+	}
+	
+	// Computes the norm and the inverse norm
+	var nx = dx / dist;
+	var nx2, nx1 = nx;
+	var ny = dy / dist;
+	var ny2, ny1 = ny;
+	var orthx = edgeWidth * ny;
+	var orthy = -edgeWidth * nx;
+	
+	// Stores the inbound function calls in reverse order in fns
+	var fns = [];
+	
+	if (isRounded)
+	{
+		c.setLineJoin('round');
+	}
+	else if (pts.length > 2)
+	{
+		// Only mitre if there are waypoints
+		c.setMiterLimit(1.42);
+	}
+
+	c.begin();
+
+	var startNx = nx;
+	var startNy = ny;
+
+	if (markerStart && !openEnded)
+	{
+		this.paintMarker(c, pts[0].x, pts[0].y, nx, ny, startSize, startWidth, edgeWidth, spacing, true);
+	}
+	else
+	{
+		var outStartX = pts[0].x + orthx / 2 + spacing * nx;
+		var outStartY = pts[0].y + orthy / 2 + spacing * ny;
+		var inEndX = pts[0].x - orthx / 2 + spacing * nx;
+		var inEndY = pts[0].y - orthy / 2 + spacing * ny;
+		
+		if (openEnded)
+		{
+			c.moveTo(outStartX, outStartY);
+			
+			fns.push(function()
+			{
+				c.lineTo(inEndX, inEndY);
+			});
+		}
+		else
+		{
+			c.moveTo(inEndX, inEndY);
+			c.lineTo(outStartX, outStartY);
+		}
+	}
+	
+	var dx1 = 0;
+	var dy1 = 0;
+	var dist1 = 0;
+
+	for (var i = 0; i < pts.length - 2; i++)
+	{
+		// Work out in which direction the line is bending
+		var pos = mxUtils.relativeCcw(pts[i].x, pts[i].y, pts[i+1].x, pts[i+1].y, pts[i+2].x, pts[i+2].y);
+
+		dx1 = pts[i+2].x - pts[i+1].x;
+		dy1 = pts[i+2].y - pts[i+1].y;
+
+		dist1 = Math.sqrt(dx1 * dx1 + dy1 * dy1);
+		
+		if (dist1 != 0)
+		{
+			nx1 = dx1 / dist1;
+			ny1 = dy1 / dist1;
+			
+			var tmp1 = nx * nx1 + ny * ny1;
+			tmp = Math.max(Math.sqrt((tmp1 + 1) / 2), 0.04);
+			
+			// Work out the normal orthogonal to the line through the control point and the edge sides intersection
+			nx2 = (nx + nx1);
+			ny2 = (ny + ny1);
+	
+			var dist2 = Math.sqrt(nx2 * nx2 + ny2 * ny2);
+			
+			if (dist2 != 0)
+			{
+				nx2 = nx2 / dist2;
+				ny2 = ny2 / dist2;
+				
+				// Higher strokewidths require a larger minimum bend, 0.35 covers all but the most extreme cases
+				var strokeWidthFactor = Math.max(tmp, Math.min(this.strokewidth / 200 + 0.04, 0.35));
+				var angleFactor = (pos != 0 && isRounded) ? Math.max(0.1, strokeWidthFactor) : Math.max(tmp, 0.06);
+
+				var outX = pts[i+1].x + ny2 * edgeWidth / 2 / angleFactor;
+				var outY = pts[i+1].y - nx2 * edgeWidth / 2 / angleFactor;
+				var inX = pts[i+1].x - ny2 * edgeWidth / 2 / angleFactor;
+				var inY = pts[i+1].y + nx2 * edgeWidth / 2 / angleFactor;
+				
+				if (pos == 0 || !isRounded)
+				{
+					// If the two segments are aligned, or if we're not drawing curved sections between segments
+					// just draw straight to the intersection point
+					c.lineTo(outX, outY);
+					
+					(function(x, y)
+					{
+						fns.push(function()
+						{
+							c.lineTo(x, y);
+						});
+					})(inX, inY);
+				}
+				else if (pos == -1)
+				{
+					var c1x = inX + ny * edgeWidth;
+					var c1y = inY - nx * edgeWidth;
+					var c2x = inX + ny1 * edgeWidth;
+					var c2y = inY - nx1 * edgeWidth;
+					c.lineTo(c1x, c1y);
+					c.quadTo(outX, outY, c2x, c2y);
+					
+					(function(x, y)
+					{
+						fns.push(function()
+						{
+							c.lineTo(x, y);
+						});
+					})(inX, inY);
+				}
+				else
+				{
+					c.lineTo(outX, outY);
+					
+					(function(x, y)
+					{
+						var c1x = outX - ny * edgeWidth;
+						var c1y = outY + nx * edgeWidth;
+						var c2x = outX - ny1 * edgeWidth;
+						var c2y = outY + nx1 * edgeWidth;
+						
+						fns.push(function()
+						{
+							c.quadTo(x, y, c1x, c1y);
+						});
+						fns.push(function()
+						{
+							c.lineTo(c2x, c2y);
+						});
+					})(inX, inY);
+				}
+				
+				nx = nx1;
+				ny = ny1;
+			}
+		}
+	}
+	
+	orthx = edgeWidth * ny1;
+	orthy = - edgeWidth * nx1;
+
+	if (markerEnd && !openEnded)
+	{
+		this.paintMarker(c, pe.x, pe.y, -nx, -ny, endSize, endWidth, edgeWidth, spacing, false);
+	}
+	else
+	{
+		c.lineTo(pe.x - spacing * nx1 + orthx / 2, pe.y - spacing * ny1 + orthy / 2);
+		
+		var inStartX = pe.x - spacing * nx1 - orthx / 2;
+		var inStartY = pe.y - spacing * ny1 - orthy / 2;
+
+		if (!openEnded)
+		{
+			c.lineTo(inStartX, inStartY);
+		}
+		else
+		{
+			c.moveTo(inStartX, inStartY);
+			
+			fns.splice(0, 0, function()
+			{
+				c.moveTo(inStartX, inStartY);
+			});
+		}
+	}
+	
+	for (var i = fns.length - 1; i >= 0; i--)
+	{
+		fns[i]();
+	}
+
+	if (openEnded)
+	{
+		c.end();
+		c.stroke();
+	}
+	else
+	{
+		c.close();
+		c.fillAndStroke();
+	}
+	
+	// Workaround for shadow on top of base arrow
+	c.setShadow(false);
+	
+	// Need to redraw the markers without the low miter limit
+	c.setMiterLimit(4);
+	
+	if (isRounded)
+	{
+		c.setLineJoin('flat');
+	}
+
+	if (pts.length > 2)
+	{
+		// Only to repaint markers if no waypoints
+		// Need to redraw the markers without the low miter limit
+		c.setMiterLimit(4);
+		if (markerStart && !openEnded)
+		{
+			c.begin();
+			this.paintMarker(c, pts[0].x, pts[0].y, startNx, startNy, startSize, startWidth, edgeWidth, spacing, true);
+			c.stroke();
+			c.end();
+		}
+		
+		if (markerEnd && !openEnded)
+		{
+			c.begin();
+			this.paintMarker(c, pe.x, pe.y, -nx, -ny, endSize, endWidth, edgeWidth, spacing, true);
+			c.stroke();
+			c.end();
+		}
+	}
+};
+
+/**
+ * Function: paintEdgeShape
+ * 
+ * Paints the line shape.
+ */
+mxArrowConnector.prototype.paintMarker = function(c, ptX, ptY, nx, ny, size, arrowWidth, edgeWidth, spacing, initialMove)
+{
+	var widthArrowRatio = edgeWidth / arrowWidth;
+	var orthx = edgeWidth * ny / 2;
+	var orthy = -edgeWidth * nx / 2;
+
+	var spaceX = (spacing + size) * nx;
+	var spaceY = (spacing + size) * ny;
+
+	if (initialMove)
+	{
+		c.moveTo(ptX - orthx + spaceX, ptY - orthy + spaceY);
+	}
+	else
+	{
+		c.lineTo(ptX - orthx + spaceX, ptY - orthy + spaceY);
+	}
+
+	c.lineTo(ptX - orthx / widthArrowRatio + spaceX, ptY - orthy / widthArrowRatio + spaceY);
+	c.lineTo(ptX + spacing * nx, ptY + spacing * ny);
+	c.lineTo(ptX + orthx / widthArrowRatio + spaceX, ptY + orthy / widthArrowRatio + spaceY);
+	c.lineTo(ptX + orthx + spaceX, ptY + orthy + spaceY);
+}
+
+/**
+ * Function: isArrowRounded
+ * 
+ * Returns wether the arrow is rounded
+ */
+mxArrowConnector.prototype.isArrowRounded = function()
+{
+	return this.isRounded;
+};
+
+/**
+ * Function: getStartArrowWidth
+ * 
+ * Returns the width of the start arrow
+ */
+mxArrowConnector.prototype.getStartArrowWidth = function()
+{
+	return mxConstants.ARROW_WIDTH;
+};
+
+/**
+ * Function: getEndArrowWidth
+ * 
+ * Returns the width of the end arrow
+ */
+mxArrowConnector.prototype.getEndArrowWidth = function()
+{
+	return mxConstants.ARROW_WIDTH;
+};
+
+/**
+ * Function: getEdgeWidth
+ * 
+ * Returns the width of the body of the edge
+ */
+mxArrowConnector.prototype.getEdgeWidth = function()
+{
+	return mxConstants.ARROW_WIDTH / 3;
+};
+
+/**
+ * Function: isOpenEnded
+ * 
+ * Returns whether the ends of the shape are drawn
+ */
+mxArrowConnector.prototype.isOpenEnded = function()
+{
+	return false;
+};
+
+/**
+ * Function: isMarkerStart
+ * 
+ * Returns whether the start marker is drawn
+ */
+mxArrowConnector.prototype.isMarkerStart = function()
+{
+	return (mxUtils.getValue(this.style, mxConstants.STYLE_STARTARROW, mxConstants.NONE) != mxConstants.NONE);
+};
+
+/**
+ * Function: isMarkerEnd
+ * 
+ * Returns whether the end marker is drawn
+ */
+mxArrowConnector.prototype.isMarkerEnd = function()
+{
+	return (mxUtils.getValue(this.style, mxConstants.STYLE_ENDARROW, mxConstants.NONE) != mxConstants.NONE);
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxText
+ *
+ * Extends <mxShape> to implement a text shape. To change vertical text from
+ * bottom to top to top to bottom, the following code can be used:
+ * 
+ * (code)
+ * mxText.prototype.verticalTextRotation = 90;
+ * (end)
+ * 
+ * Constructor: mxText
+ *
+ * Constructs a new text shape.
+ * 
+ * Parameters:
+ * 
+ * value - String that represents the text to be displayed. This is stored in
+ * <value>.
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * align - Specifies the horizontal alignment. Default is ''. This is stored in
+ * <align>.
+ * valign - Specifies the vertical alignment. Default is ''. This is stored in
+ * <valign>.
+ * color - String that specifies the text color. Default is 'black'. This is
+ * stored in <color>.
+ * family - String that specifies the font family. Default is
+ * <mxConstants.DEFAULT_FONTFAMILY>. This is stored in <family>.
+ * size - Integer that specifies the font size. Default is
+ * <mxConstants.DEFAULT_FONTSIZE>. This is stored in <size>.
+ * fontStyle - Specifies the font style. Default is 0. This is stored in
+ * <fontStyle>.
+ * spacing - Integer that specifies the global spacing. Default is 2. This is
+ * stored in <spacing>.
+ * spacingTop - Integer that specifies the top spacing. Default is 0. The
+ * sum of the spacing and this is stored in <spacingTop>.
+ * spacingRight - Integer that specifies the right spacing. Default is 0. The
+ * sum of the spacing and this is stored in <spacingRight>.
+ * spacingBottom - Integer that specifies the bottom spacing. Default is 0.The
+ * sum of the spacing and this is stored in <spacingBottom>.
+ * spacingLeft - Integer that specifies the left spacing. Default is 0. The
+ * sum of the spacing and this is stored in <spacingLeft>.
+ * horizontal - Boolean that specifies if the label is horizontal. Default is
+ * true. This is stored in <horizontal>.
+ * background - String that specifies the background color. Default is null.
+ * This is stored in <background>.
+ * border - String that specifies the label border color. Default is null.
+ * This is stored in <border>.
+ * wrap - Specifies if word-wrapping should be enabled. Default is false.
+ * This is stored in <wrap>.
+ * clipped - Specifies if the label should be clipped. Default is false.
+ * This is stored in <clipped>.
+ * overflow - Value of the overflow style. Default is 'visible'.
+ */
+function mxText(value, bounds, align, valign, color,
+	family,	size, fontStyle, spacing, spacingTop, spacingRight,
+	spacingBottom, spacingLeft, horizontal, background, border,
+	wrap, clipped, overflow, labelPadding, textDirection)
+{
+	mxShape.call(this);
+	this.value = value;
+	this.bounds = bounds;
+	this.color = (color != null) ? color : 'black';
+	this.align = (align != null) ? align : mxConstants.ALIGN_CENTER;
+	this.valign = (valign != null) ? valign : mxConstants.ALIGN_MIDDLE;
+	this.family = (family != null) ? family : mxConstants.DEFAULT_FONTFAMILY;
+	this.size = (size != null) ? size : mxConstants.DEFAULT_FONTSIZE;
+	this.fontStyle = (fontStyle != null) ? fontStyle : mxConstants.DEFAULT_FONTSTYLE;
+	this.spacing = parseInt(spacing || 2);
+	this.spacingTop = this.spacing + parseInt(spacingTop || 0);
+	this.spacingRight = this.spacing + parseInt(spacingRight || 0);
+	this.spacingBottom = this.spacing + parseInt(spacingBottom || 0);
+	this.spacingLeft = this.spacing + parseInt(spacingLeft || 0);
+	this.horizontal = (horizontal != null) ? horizontal : true;
+	this.background = background;
+	this.border = border;
+	this.wrap = (wrap != null) ? wrap : false;
+	this.clipped = (clipped != null) ? clipped : false;
+	this.overflow = (overflow != null) ? overflow : 'visible';
+	this.labelPadding = (labelPadding != null) ? labelPadding : 0;
+	this.textDirection = textDirection;
+	this.rotation = 0;
+	this.updateMargin();
+};
+
+/**
+ * Extends mxShape.
+ */
+mxUtils.extend(mxText, mxShape);
+
+/**
+ * Variable: baseSpacingTop
+ * 
+ * Specifies the spacing to be added to the top spacing. Default is 0. Use the
+ * value 5 here to get the same label positions as in mxGraph 1.x.
+ */
+mxText.prototype.baseSpacingTop = 0;
+
+/**
+ * Variable: baseSpacingBottom
+ * 
+ * Specifies the spacing to be added to the bottom spacing. Default is 0. Use the
+ * value 1 here to get the same label positions as in mxGraph 1.x.
+ */
+mxText.prototype.baseSpacingBottom = 0;
+
+/**
+ * Variable: baseSpacingLeft
+ * 
+ * Specifies the spacing to be added to the left spacing. Default is 0.
+ */
+mxText.prototype.baseSpacingLeft = 0;
+
+/**
+ * Variable: baseSpacingRight
+ * 
+ * Specifies the spacing to be added to the right spacing. Default is 0.
+ */
+mxText.prototype.baseSpacingRight = 0;
+
+/**
+ * Variable: replaceLinefeeds
+ * 
+ * Specifies if linefeeds in HTML labels should be replaced with BR tags.
+ * Default is true.
+ */
+mxText.prototype.replaceLinefeeds = true;
+
+/**
+ * Variable: verticalTextRotation
+ * 
+ * Rotation for vertical text. Default is -90 (bottom to top).
+ */
+mxText.prototype.verticalTextRotation = -90;
+
+/**
+ * Variable: ignoreClippedStringSize
+ * 
+ * Specifies if the string size should be measured in <updateBoundingBox> if
+ * the label is clipped and the label position is center and middle. If this is
+ * true, then the bounding box will be set to <bounds>. Default is true.
+ * <ignoreStringSize> has precedence over this switch.
+ */
+mxText.prototype.ignoreClippedStringSize = true;
+
+/**
+ * Variable: ignoreStringSize
+ * 
+ * Specifies if the actual string size should be measured. If disabled the
+ * boundingBox will not ignore the actual size of the string, otherwise
+ * <bounds> will be used instead. Default is false.
+ */
+mxText.prototype.ignoreStringSize = false;
+
+/**
+ * Variable: textWidthPadding
+ * 
+ * Specifies the padding to be added to the text width for the bounding box.
+ * This is needed to make sure no clipping is applied to borders. Default is 4
+ * for IE 8 standards mode and 3 for all others.
+ */
+mxText.prototype.textWidthPadding = (document.documentMode == 8 && !mxClient.IS_EM) ? 4 : 3;
+
+/**
+ * Variable: lastValue
+ * 
+ * Contains the last rendered text value. Used for caching.
+ */
+mxText.prototype.lastValue = null;
+
+/**
+ * Variable: cacheEnabled
+ * 
+ * Specifies if caching for HTML labels should be enabled. Default is true.
+ */
+mxText.prototype.cacheEnabled = true;
+
+/**
+ * Function: isParseVml
+ * 
+ * Text shapes do not contain VML markup and do not need to be parsed. This
+ * method returns false to speed up rendering in IE8.
+ */
+mxText.prototype.isParseVml = function()
+{
+	return false;
+};
+
+/**
+ * Function: isHtmlAllowed
+ * 
+ * Returns true if HTML is allowed for this shape. This implementation returns
+ * true if the browser is not in IE8 standards mode.
+ */
+mxText.prototype.isHtmlAllowed = function()
+{
+	return document.documentMode != 8 || mxClient.IS_EM;
+};
+
+/**
+ * Function: getSvgScreenOffset
+ * 
+ * Disables offset in IE9 for crisper image output.
+ */
+mxText.prototype.getSvgScreenOffset = function()
+{
+	return 0;
+};
+
+/**
+ * Function: checkBounds
+ * 
+ * Returns true if the bounds are not null and all of its variables are numeric.
+ */
+mxText.prototype.checkBounds = function()
+{
+	return (!isNaN(this.scale) && isFinite(this.scale) && this.scale > 0 &&
+			this.bounds != null && !isNaN(this.bounds.x) && !isNaN(this.bounds.y) &&
+			!isNaN(this.bounds.width) && !isNaN(this.bounds.height));
+};
+
+/**
+ * Function: paint
+ * 
+ * Generic rendering code.
+ */
+mxText.prototype.paint = function(c, update)
+{
+	// Scale is passed-through to canvas
+	var s = this.scale;
+	var x = this.bounds.x / s;
+	var y = this.bounds.y / s;
+	var w = this.bounds.width / s;
+	var h = this.bounds.height / s;
+	
+	this.updateTransform(c, x, y, w, h);
+	this.configureCanvas(c, x, y, w, h);
+
+	var unscaledWidth = (this.state != null) ? this.state.unscaledWidth : null;
+
+	if (update)
+	{
+		if (this.node.firstChild != null && (unscaledWidth == null ||
+			this.lastUnscaledWidth != unscaledWidth))
+		{
+			c.invalidateCachedOffsetSize(this.node);
+		}
+
+		c.updateText(x, y, w, h, this.align, this.valign, this.wrap, this.overflow,
+				this.clipped, this.getTextRotation(), this.node);
+	}
+	else
+	{
+		// Checks if text contains HTML markup
+		var realHtml = mxUtils.isNode(this.value) || this.dialect == mxConstants.DIALECT_STRICTHTML;
+		
+		// Always renders labels as HTML in VML
+		var fmt = (realHtml || c instanceof mxVmlCanvas2D) ? 'html' : '';
+		var val = this.value;
+		
+		if (!realHtml && fmt == 'html')
+		{
+			val =  mxUtils.htmlEntities(val, false);
+		}
+		
+		if (fmt == 'html' && !mxUtils.isNode(this.value))
+		{
+			val = mxUtils.replaceTrailingNewlines(val, '<div><br></div>');			
+		}
+		
+		// Handles trailing newlines to make sure they are visible in rendering output
+		val = (!mxUtils.isNode(this.value) && this.replaceLinefeeds && fmt == 'html') ?
+			val.replace(/\n/g, '<br/>') : val;
+			
+		var dir = this.textDirection;
+	
+		if (dir == mxConstants.TEXT_DIRECTION_AUTO && !realHtml)
+		{
+			dir = this.getAutoDirection();
+		}
+		
+		if (dir != mxConstants.TEXT_DIRECTION_LTR && dir != mxConstants.TEXT_DIRECTION_RTL)
+		{
+			dir = null;
+		}
+	
+		c.text(x, y, w, h, val, this.align, this.valign, this.wrap, fmt, this.overflow,
+			this.clipped, this.getTextRotation(), dir);
+	}
+	
+	// Needs to invalidate the cached offset widths if the geometry changes
+	this.lastUnscaledWidth = unscaledWidth;
+};
+
+/**
+ * Function: redraw
+ * 
+ * Renders the text using the given DOM nodes.
+ */
+mxText.prototype.redraw = function()
+{
+	if (this.visible && this.checkBounds() && this.cacheEnabled && this.lastValue == this.value &&
+		(mxUtils.isNode(this.value) || this.dialect == mxConstants.DIALECT_STRICTHTML))
+	{
+		if (this.node.nodeName == 'DIV' && (this.isHtmlAllowed() || !mxClient.IS_VML))
+		{
+			this.updateSize(this.node, (this.state == null || this.state.view.textDiv == null));
+
+			if (mxClient.IS_IE && (document.documentMode == null || document.documentMode <= 8))
+			{
+				this.updateHtmlFilter();
+			}
+			else
+			{
+				this.updateHtmlTransform();
+			}
+			
+			this.updateBoundingBox();
+		}
+		else
+		{
+			var canvas = this.createCanvas();
+
+			if (canvas != null && canvas.updateText != null &&
+				canvas.invalidateCachedOffsetSize != null)
+			{
+				this.paint(canvas, true);
+				this.destroyCanvas(canvas);
+				this.updateBoundingBox();
+			}
+			else
+			{
+				// Fallback if canvas does not support updateText (VML)
+				mxShape.prototype.redraw.apply(this, arguments);
+			}
+		}
+	}
+	else
+	{
+		mxShape.prototype.redraw.apply(this, arguments);
+		
+		if (mxUtils.isNode(this.value) || this.dialect == mxConstants.DIALECT_STRICTHTML)
+		{
+			this.lastValue = this.value;
+		}
+		else
+		{
+			this.lastValue = null;
+		}
+	}
+};
+
+/**
+ * Function: resetStyles
+ * 
+ * Resets all styles.
+ */
+mxText.prototype.resetStyles = function()
+{
+	mxShape.prototype.resetStyles.apply(this, arguments);
+	
+	this.color = 'black';
+	this.align = mxConstants.ALIGN_CENTER;
+	this.valign = mxConstants.ALIGN_MIDDLE;
+	this.family = mxConstants.DEFAULT_FONTFAMILY;
+	this.size = mxConstants.DEFAULT_FONTSIZE;
+	this.fontStyle = mxConstants.DEFAULT_FONTSTYLE;
+	this.spacing = 2;
+	this.spacingTop = 2;
+	this.spacingRight = 2;
+	this.spacingBottom = 2;
+	this.spacingLeft = 2;
+	this.horizontal = true;
+	delete this.background;
+	delete this.border;
+	this.textDirection = mxConstants.DEFAULT_TEXT_DIRECTION;
+	delete this.margin;
+};
+
+/**
+ * Function: apply
+ * 
+ * Extends mxShape to update the text styles.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> of the corresponding cell.
+ */
+mxText.prototype.apply = function(state)
+{
+	var old = this.spacing;
+	mxShape.prototype.apply.apply(this, arguments);
+	
+	if (this.style != null)
+	{
+		this.fontStyle = mxUtils.getValue(this.style, mxConstants.STYLE_FONTSTYLE, this.fontStyle);
+		this.family = mxUtils.getValue(this.style, mxConstants.STYLE_FONTFAMILY, this.family);
+		this.size = mxUtils.getValue(this.style, mxConstants.STYLE_FONTSIZE, this.size);
+		this.color = mxUtils.getValue(this.style, mxConstants.STYLE_FONTCOLOR, this.color);
+		this.align = mxUtils.getValue(this.style, mxConstants.STYLE_ALIGN, this.align);
+		this.valign = mxUtils.getValue(this.style, mxConstants.STYLE_VERTICAL_ALIGN, this.valign);
+		this.spacing = parseInt(mxUtils.getValue(this.style, mxConstants.STYLE_SPACING, this.spacing));
+		this.spacingTop = parseInt(mxUtils.getValue(this.style, mxConstants.STYLE_SPACING_TOP, this.spacingTop - old)) + this.spacing;
+		this.spacingRight = parseInt(mxUtils.getValue(this.style, mxConstants.STYLE_SPACING_RIGHT, this.spacingRight - old)) + this.spacing;
+		this.spacingBottom = parseInt(mxUtils.getValue(this.style, mxConstants.STYLE_SPACING_BOTTOM, this.spacingBottom - old)) + this.spacing;
+		this.spacingLeft = parseInt(mxUtils.getValue(this.style, mxConstants.STYLE_SPACING_LEFT, this.spacingLeft - old)) + this.spacing;
+		this.horizontal = mxUtils.getValue(this.style, mxConstants.STYLE_HORIZONTAL, this.horizontal);
+		this.background = mxUtils.getValue(this.style, mxConstants.STYLE_LABEL_BACKGROUNDCOLOR, this.background);
+		this.border = mxUtils.getValue(this.style, mxConstants.STYLE_LABEL_BORDERCOLOR, this.border);
+		this.textDirection = mxUtils.getValue(this.style, mxConstants.STYLE_TEXT_DIRECTION, mxConstants.DEFAULT_TEXT_DIRECTION);
+		this.opacity = mxUtils.getValue(this.style, mxConstants.STYLE_TEXT_OPACITY, 100);
+		this.updateMargin();
+	}
+	
+	this.flipV = null;
+	this.flipH = null;
+};
+
+/**
+ * Function: getAutoDirection
+ * 
+ * Used to determine the automatic text direction. Returns
+ * <mxConstants.TEXT_DIRECTION_LTR> or <mxConstants.TEXT_DIRECTION_RTL>
+ * depending on the contents of <value>. This is not invoked for HTML, wrapped
+ * content or if <value> is a DOM node.
+ */
+mxText.prototype.getAutoDirection = function()
+{
+	// Looks for strong (directional) characters
+	var tmp = /[A-Za-z\u05d0-\u065f\u066a-\u06ef\u06fa-\u07ff\ufb1d-\ufdff\ufe70-\ufefc]/.exec(this.value);
+	
+	// Returns the direction defined by the character
+	return (tmp != null && tmp.length > 0 && tmp[0] > 'z') ?
+		mxConstants.TEXT_DIRECTION_RTL : mxConstants.TEXT_DIRECTION_LTR;
+};
+
+/**
+ * Function: updateBoundingBox
+ *
+ * Updates the <boundingBox> for this shape using the given node and position.
+ */
+mxText.prototype.updateBoundingBox = function()
+{
+	var node = this.node;
+	this.boundingBox = this.bounds.clone();
+	var rot = this.getTextRotation();
+	
+	var h = (this.style != null) ? mxUtils.getValue(this.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER) : null;
+	var v = (this.style != null) ? mxUtils.getValue(this.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE) : null;
+
+	if (!this.ignoreStringSize && node != null && this.overflow != 'fill' && (!this.clipped ||
+		!this.ignoreClippedStringSize || h != mxConstants.ALIGN_CENTER || v != mxConstants.ALIGN_MIDDLE))
+	{
+		var ow = null;
+		var oh = null;
+		
+		if (node.ownerSVGElement != null)
+		{
+			if (node.firstChild != null && node.firstChild.firstChild != null &&
+				node.firstChild.firstChild.nodeName == 'foreignObject')
+			{
+				node = node.firstChild.firstChild;
+				ow = parseInt(node.getAttribute('width')) * this.scale;
+				oh = parseInt(node.getAttribute('height')) * this.scale;
+			}
+			else
+			{
+				try
+				{
+					var b = node.getBBox();
+					
+					// Workaround for bounding box of empty string
+					if (typeof(this.value) == 'string' && mxUtils.trim(this.value) == 0)
+					{
+						this.boundingBox = null;
+					}
+					else if (b.width == 0 && b.height == 0)
+					{
+						this.boundingBox = null;
+					}
+					else
+					{
+						this.boundingBox = new mxRectangle(b.x, b.y, b.width, b.height);
+					}
+					
+					return;
+				}
+				catch (e)
+				{
+					// Ignores NS_ERROR_FAILURE in FF if container display is none.
+				}
+			}
+		}
+		else
+		{
+			var td = (this.state != null) ? this.state.view.textDiv : null;
+
+			// Use cached offset size
+			if (this.offsetWidth != null && this.offsetHeight != null)
+			{
+				ow = this.offsetWidth * this.scale;
+				oh = this.offsetHeight * this.scale;
+			}
+			else
+			{
+				// Cannot get node size while container hidden so a
+				// shared temporary DIV is used for text measuring
+				if (td != null)
+				{
+					this.updateFont(td);
+					this.updateSize(td, false);
+					this.updateInnerHtml(td);
+
+					node = td;
+				}
+				
+				var sizeDiv = node;
+
+				if (document.documentMode == 8 && !mxClient.IS_EM)
+				{
+					var w = Math.round(this.bounds.width / this.scale);
+	
+					if (this.wrap && w > 0)
+					{
+						node.style.wordWrap = mxConstants.WORD_WRAP;
+						node.style.whiteSpace = 'normal';
+
+						if (node.style.wordWrap != 'break-word')
+						{
+							// Innermost DIV is used for measuring text
+							var divs = sizeDiv.getElementsByTagName('div');
+							
+							if (divs.length > 0)
+							{
+								sizeDiv = divs[divs.length - 1];
+							}
+							
+							ow = sizeDiv.offsetWidth + 2;
+							divs = this.node.getElementsByTagName('div');
+							
+							if (this.clipped)
+							{
+								ow = Math.min(w, ow);
+							}
+							
+							// Second last DIV width must be updated in DOM tree
+							if (divs.length > 1)
+							{
+								divs[divs.length - 2].style.width = ow + 'px';
+							}
+						}
+					}
+					else
+					{
+						node.style.whiteSpace = 'nowrap';
+					}
+				}
+				else if (sizeDiv.firstChild != null && sizeDiv.firstChild.nodeName == 'DIV')
+				{
+					sizeDiv = sizeDiv.firstChild;
+				}
+
+				this.offsetWidth = sizeDiv.offsetWidth + this.textWidthPadding;
+				this.offsetHeight = sizeDiv.offsetHeight;
+				
+				ow = this.offsetWidth * this.scale;
+				oh = this.offsetHeight * this.scale;
+			}
+		}
+
+		if (ow != null && oh != null)
+		{	
+			this.boundingBox = new mxRectangle(this.bounds.x,
+				this.bounds.y, ow, oh);
+		}
+	}
+
+	if (this.boundingBox != null)
+	{
+		if (rot != 0)
+		{
+			// Accounts for pre-rotated x and y
+			var bbox = mxUtils.getBoundingBox(new mxRectangle(
+				this.margin.x * this.boundingBox.width,
+				this.margin.y * this.boundingBox.height,
+				this.boundingBox.width, this.boundingBox.height),
+				rot, new mxPoint(0, 0));
+			
+			this.unrotatedBoundingBox = mxRectangle.fromRectangle(this.boundingBox);
+			this.unrotatedBoundingBox.x += this.margin.x * this.unrotatedBoundingBox.width;
+			this.unrotatedBoundingBox.y += this.margin.y * this.unrotatedBoundingBox.height;
+			
+			this.boundingBox.x += bbox.x;
+			this.boundingBox.y += bbox.y;
+			this.boundingBox.width = bbox.width;
+			this.boundingBox.height = bbox.height;
+		}
+		else
+		{
+			this.boundingBox.x += this.margin.x * this.boundingBox.width;
+			this.boundingBox.y += this.margin.y * this.boundingBox.height;
+			this.unrotatedBoundingBox = null;
+		}
+	}
+};
+
+/**
+ * Function: getShapeRotation
+ * 
+ * Returns 0 to avoid using rotation in the canvas via updateTransform.
+ */
+mxText.prototype.getShapeRotation = function()
+{
+	return 0;
+};
+
+/**
+ * Function: getTextRotation
+ * 
+ * Returns the rotation for the text label of the corresponding shape.
+ */
+mxText.prototype.getTextRotation = function()
+{
+	return (this.state != null && this.state.shape != null) ? this.state.shape.getTextRotation() : 0;
+};
+
+/**
+ * Function: isPaintBoundsInverted
+ * 
+ * Inverts the bounds if <mxShape.isBoundsInverted> returns true or if the
+ * horizontal style is false.
+ */
+mxText.prototype.isPaintBoundsInverted = function()
+{
+	return !this.horizontal && this.state != null && this.state.view.graph.model.isVertex(this.state.cell);
+};
+
+/**
+ * Function: configureCanvas
+ * 
+ * Sets the state of the canvas for drawing the shape.
+ */
+mxText.prototype.configureCanvas = function(c, x, y, w, h)
+{
+	mxShape.prototype.configureCanvas.apply(this, arguments);
+	
+	c.setFontColor(this.color);
+	c.setFontBackgroundColor(this.background);
+	c.setFontBorderColor(this.border);
+	c.setFontFamily(this.family);
+	c.setFontSize(this.size);
+	c.setFontStyle(this.fontStyle);
+};
+
+/**
+ * Function: updateVmlContainer
+ * 
+ * Sets the width and height of the container to 1px.
+ */
+mxText.prototype.updateVmlContainer = function()
+{
+	this.node.style.left = Math.round(this.bounds.x) + 'px';
+	this.node.style.top = Math.round(this.bounds.y) + 'px';
+	this.node.style.width = '1px';
+	this.node.style.height = '1px';
+	this.node.style.overflow = 'visible';
+};
+
+/**
+ * Function: redrawHtmlShape
+ *
+ * Updates the HTML node(s) to reflect the latest bounds and scale.
+ */
+mxText.prototype.redrawHtmlShape = function()
+{
+	var style = this.node.style;
+
+	// Resets CSS styles
+	style.whiteSpace = 'normal';
+	style.overflow = '';
+	style.width = '';
+	style.height = '';
+	
+	this.updateValue();
+	this.updateFont(this.node);
+	this.updateSize(this.node, (this.state == null || this.state.view.textDiv == null));
+	
+	this.offsetWidth = null;
+	this.offsetHeight = null;
+
+	if (mxClient.IS_IE && (document.documentMode == null || document.documentMode <= 8))
+	{
+		this.updateHtmlFilter();
+	}
+	else
+	{
+		this.updateHtmlTransform();
+	}
+};
+
+/**
+ * Function: updateHtmlTransform
+ *
+ * Returns the spacing as an <mxPoint>.
+ */
+mxText.prototype.updateHtmlTransform = function()
+{
+	var theta = this.getTextRotation();
+	var style = this.node.style;
+	var dx = this.margin.x;
+	var dy = this.margin.y;
+	
+	if (theta != 0)
+	{
+		mxUtils.setPrefixedStyle(style, 'transformOrigin', (-dx * 100) + '%' + ' ' + (-dy * 100) + '%');
+		mxUtils.setPrefixedStyle(style, 'transform', 'translate(' + (dx * 100) + '%' + ',' + (dy * 100) + '%)' +
+			'scale(' + this.scale + ') rotate(' + theta + 'deg)');
+	}
+	else
+	{
+		mxUtils.setPrefixedStyle(style, 'transformOrigin', '0% 0%');
+		mxUtils.setPrefixedStyle(style, 'transform', 'scale(' + this.scale + ')' +
+			'translate(' + (dx * 100) + '%' + ',' + (dy * 100) + '%)');
+	}
+
+	style.left = Math.round(this.bounds.x - Math.ceil(dx * ((this.overflow != 'fill' &&
+		this.overflow != 'width') ? 3 : 1))) + 'px';
+	style.top = Math.round(this.bounds.y - dy * ((this.overflow != 'fill') ? 3 : 1)) + 'px';
+	
+	if (this.opacity < 100)
+	{
+		style.opacity = this.opacity / 100;
+	}
+	else
+	{
+		style.opacity = '';
+	}
+};
+
+/**
+ * Function: setInnerHtml
+ * 
+ * Sets the inner HTML of the given element to the <value>.
+ */
+mxText.prototype.updateInnerHtml = function(elt)
+{
+	if (mxUtils.isNode(this.value))
+	{
+		elt.innerHTML = this.value.outerHTML;
+	}
+	else
+	{
+		var val = this.value;
+		
+		if (this.dialect != mxConstants.DIALECT_STRICTHTML)
+		{
+			// LATER: Can be cached in updateValue
+			val = mxUtils.htmlEntities(val, false);
+		}
+		
+		// Handles trailing newlines to make sure they are visible in rendering output
+		val = mxUtils.replaceTrailingNewlines(val, '<div>&nbsp;</div>');
+		val = (this.replaceLinefeeds) ? val.replace(/\n/g, '<br/>') : val;
+		val = '<div style="display:inline-block;_display:inline;">' + val + '</div>';
+		
+		elt.innerHTML = val;
+	}
+};
+
+/**
+ * Function: updateHtmlFilter
+ *
+ * Rotated text rendering quality is bad for IE9 quirks/IE8 standards
+ */
+mxText.prototype.updateHtmlFilter = function()
+{
+	var style = this.node.style;
+	var dx = this.margin.x;
+	var dy = this.margin.y;
+	var s = this.scale;
+	
+	// Resets filter before getting offsetWidth
+	mxUtils.setOpacity(this.node, this.opacity);
+	
+	// Adds 1 to match table height in 1.x
+	var ow = 0;
+	var oh = 0;
+	var td = (this.state != null) ? this.state.view.textDiv : null;
+	var sizeDiv = this.node;
+	
+	// Fallback for hidden text rendering in IE quirks mode
+	if (td != null)
+	{
+		td.style.overflow = '';
+		td.style.height = '';
+		td.style.width = '';
+		
+		this.updateFont(td);
+		this.updateSize(td, false);
+		this.updateInnerHtml(td);
+		
+		var w = Math.round(this.bounds.width / this.scale);
+
+		if (this.wrap && w > 0)
+		{
+			td.style.whiteSpace = 'normal';
+			td.style.wordWrap = mxConstants.WORD_WRAP;
+			ow = w;
+			
+			if (this.clipped)
+			{
+				ow = Math.min(ow, this.bounds.width);
+			}
+
+			td.style.width = ow + 'px';
+		}
+		else
+		{
+			td.style.whiteSpace = 'nowrap';
+		}
+		
+		sizeDiv = td;
+		
+		if (sizeDiv.firstChild != null && sizeDiv.firstChild.nodeName == 'DIV')
+		{
+			sizeDiv = sizeDiv.firstChild;
+			
+			if (this.wrap && td.style.wordWrap == 'break-word')
+			{
+				sizeDiv.style.width = '100%';
+			}
+		}
+
+		// Required to update the height of the text box after wrapping width is known 
+		if (!this.clipped && this.wrap && w > 0)
+		{
+			ow = sizeDiv.offsetWidth + this.textWidthPadding;
+			td.style.width = ow + 'px';
+		}
+		
+		oh = sizeDiv.offsetHeight + 2;
+		
+		if (mxClient.IS_QUIRKS && this.border != null && this.border != mxConstants.NONE)
+		{
+			oh += 3;
+		}
+	}
+	else if (sizeDiv.firstChild != null && sizeDiv.firstChild.nodeName == 'DIV')
+	{
+		sizeDiv = sizeDiv.firstChild;
+		oh = sizeDiv.offsetHeight;
+	}
+
+	ow = sizeDiv.offsetWidth + this.textWidthPadding;
+	
+	if (this.clipped)
+	{
+		oh = Math.min(oh, this.bounds.height);
+	}
+
+	var w = this.bounds.width / s;
+	var h = this.bounds.height / s;
+
+	// Handles special case for live preview with no wrapper DIV and no textDiv
+	if (this.overflow == 'fill')
+	{
+		oh = h;
+		ow = w;
+	}
+	else if (this.overflow == 'width')
+	{
+		oh = sizeDiv.scrollHeight;
+		ow = w;
+	}
+	
+	// Stores for later use
+	this.offsetWidth = ow;
+	this.offsetHeight = oh;
+	
+	// Simulates max-height CSS in quirks mode
+	if (mxClient.IS_QUIRKS && (this.clipped || (this.overflow == 'width' && h > 0)))
+	{
+		h = Math.min(h, oh);
+		style.height = Math.round(h) + 'px';
+	}
+	else
+	{
+		h = oh;
+	}
+
+	if (this.overflow != 'fill' && this.overflow != 'width')
+	{
+		if (this.clipped)
+		{
+			ow = Math.min(w, ow);
+		}
+		
+		w = ow;
+
+		// Simulates max-width CSS in quirks mode
+		if ((mxClient.IS_QUIRKS && this.clipped) || this.wrap)
+		{
+			style.width = Math.round(w) + 'px';
+		}
+	}
+
+	h *= s;
+	w *= s;
+	
+	// Rotation case is handled via VML canvas
+	var rad = this.getTextRotation() * (Math.PI / 180);
+	
+	// Precalculate cos and sin for the rotation
+	var real_cos = parseFloat(parseFloat(Math.cos(rad)).toFixed(8));
+	var real_sin = parseFloat(parseFloat(Math.sin(-rad)).toFixed(8));
+
+	rad %= 2 * Math.PI;
+	
+	if (rad < 0)
+	{
+		rad += 2 * Math.PI;
+	}
+	
+	rad %= Math.PI;
+	
+	if (rad > Math.PI / 2)
+	{
+		rad = Math.PI - rad;
+	}
+	
+	var cos = Math.cos(rad);
+	var sin = Math.sin(-rad);
+
+	var tx = w * -(dx + 0.5);
+	var ty = h * -(dy + 0.5);
+
+	var top_fix = (h - h * cos + w * sin) / 2 + real_sin * tx - real_cos * ty;
+	var left_fix = (w - w * cos + h * sin) / 2 - real_cos * tx - real_sin * ty;
+	
+	if (rad != 0)
+	{
+		var f = 'progid:DXImageTransform.Microsoft.Matrix(M11=' + real_cos + ', M12='+
+			real_sin + ', M21=' + (-real_sin) + ', M22=' + real_cos + ', sizingMethod=\'auto expand\')';
+		
+		if (style.filter != null && style.filter.length > 0)
+		{
+			style.filter += ' ' + f;
+		}
+		else
+		{
+			style.filter = f;
+		}
+	}
+	
+	// Workaround for rendering offsets
+	var dy = 0;
+	
+	if (this.overflow != 'fill' && mxClient.IS_QUIRKS)
+	{
+		if (this.valign == mxConstants.ALIGN_TOP)
+		{
+			dy -= 1;
+		}
+		else if (this.valign == mxConstants.ALIGN_BOTTOM)
+		{
+			dy += 2;
+		}
+		else
+		{
+			dy += 1;
+		}
+	}
+
+	style.zoom = s;
+	style.left = Math.round(this.bounds.x + left_fix - w / 2) + 'px';
+	style.top = Math.round(this.bounds.y + top_fix - h / 2 + dy) + 'px';
+};
+
+/**
+ * Function: updateValue
+ *
+ * Updates the HTML node(s) to reflect the latest bounds and scale.
+ */
+mxText.prototype.updateValue = function()
+{
+	if (mxUtils.isNode(this.value))
+	{
+		this.node.innerHTML = '';
+		this.node.appendChild(this.value);
+	}
+	else
+	{
+		var val = this.value;
+		
+		if (this.dialect != mxConstants.DIALECT_STRICTHTML)
+		{
+			val = mxUtils.htmlEntities(val, false);
+		}
+		
+		// Handles trailing newlines to make sure they are visible in rendering output
+		val = mxUtils.replaceTrailingNewlines(val, '<div><br></div>');
+		val = (this.replaceLinefeeds) ? val.replace(/\n/g, '<br/>') : val;
+		var bg = (this.background != null && this.background != mxConstants.NONE) ? this.background : null;
+		var bd = (this.border != null && this.border != mxConstants.NONE) ? this.border : null;
+
+		if (this.overflow == 'fill' || this.overflow == 'width')
+		{
+			if (bg != null)
+			{
+				this.node.style.backgroundColor = bg;
+			}
+			
+			if (bd != null)
+			{
+				this.node.style.border = '1px solid ' + bd;
+			}
+		}
+		else
+		{
+			var css = '';
+			
+			if (bg != null)
+			{
+				css += 'background-color:' + bg + ';';
+			}
+			
+			if (bd != null)
+			{
+				css += 'border:1px solid ' + bd + ';';
+			}
+			
+			// Wrapper DIV for background, zoom needed for inline in quirks
+			// and to measure wrapped font sizes in all browsers
+			// FIXME: Background size in quirks mode for wrapped text
+			var lh = (mxConstants.ABSOLUTE_LINE_HEIGHT) ? (this.size * mxConstants.LINE_HEIGHT) + 'px' :
+				mxConstants.LINE_HEIGHT;
+			val = '<div style="zoom:1;' + css + 'display:inline-block;_display:inline;text-decoration:inherit;' +
+				'padding-bottom:1px;padding-right:1px;line-height:' + lh + '">' + val + '</div>';
+		}
+
+		this.node.innerHTML = val;
+		
+		// Sets text direction
+		var divs = this.node.getElementsByTagName('div');
+		
+		if (divs.length > 0)
+		{
+			var dir = this.textDirection;
+
+			if (dir == mxConstants.TEXT_DIRECTION_AUTO && this.dialect != mxConstants.DIALECT_STRICTHTML)
+			{
+				dir = this.getAutoDirection();
+			}
+			
+			if (dir == mxConstants.TEXT_DIRECTION_LTR || dir == mxConstants.TEXT_DIRECTION_RTL)
+			{
+				divs[divs.length - 1].setAttribute('dir', dir);
+			}
+			else
+			{
+				divs[divs.length - 1].removeAttribute('dir');
+			}
+		}
+	}
+};
+
+/**
+ * Function: updateFont
+ *
+ * Updates the HTML node(s) to reflect the latest bounds and scale.
+ */
+mxText.prototype.updateFont = function(node)
+{
+	var style = node.style;
+	
+	style.lineHeight = (mxConstants.ABSOLUTE_LINE_HEIGHT) ? (this.size * mxConstants.LINE_HEIGHT) + 'px' : mxConstants.LINE_HEIGHT;
+	style.fontSize = this.size + 'px';
+	style.fontFamily = this.family;
+	style.verticalAlign = 'top';
+	style.color = this.color;
+	
+	if ((this.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
+	{
+		style.fontWeight = 'bold';
+	}
+	else
+	{
+		style.fontWeight = '';
+	}
+
+	if ((this.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
+	{
+		style.fontStyle = 'italic';
+	}
+	else
+	{
+		style.fontStyle = '';
+	}
+	
+	if ((this.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)
+	{
+		style.textDecoration = 'underline';
+	}
+	else
+	{
+		style.textDecoration = '';
+	}
+	
+	if (this.align == mxConstants.ALIGN_CENTER)
+	{
+		style.textAlign = 'center';
+	}
+	else if (this.align == mxConstants.ALIGN_RIGHT)
+	{
+		style.textAlign = 'right';
+	}
+	else
+	{
+		style.textAlign = 'left';
+	}
+};
+
+/**
+ * Function: updateSize
+ *
+ * Updates the HTML node(s) to reflect the latest bounds and scale.
+ */
+mxText.prototype.updateSize = function(node, enableWrap)
+{
+	var w = Math.max(0, Math.round(this.bounds.width / this.scale));
+	var h = Math.max(0, Math.round(this.bounds.height / this.scale));
+	var style = node.style;
+	
+	// NOTE: Do not use maxWidth here because wrapping will
+	// go wrong if the cell is outside of the viewable area
+	if (this.clipped)
+	{
+		style.overflow = 'hidden';
+		
+		if (!mxClient.IS_QUIRKS)
+		{
+			style.maxHeight = h + 'px';
+			style.maxWidth = w + 'px';
+		}
+		else
+		{
+			style.width = w + 'px';
+		}
+	}
+	else if (this.overflow == 'fill')
+	{
+		style.width = (w + 1) + 'px';
+		style.height = (h + 1) + 'px';
+		style.overflow = 'hidden';
+	}
+	else if (this.overflow == 'width')
+	{
+		style.width = (w + 1) + 'px';
+		style.maxHeight = (h + 1) + 'px';
+		style.overflow = 'hidden';
+	}
+	
+	if (this.wrap && w > 0)
+	{
+		style.wordWrap = mxConstants.WORD_WRAP;
+		style.whiteSpace = 'normal';
+		style.width = w + 'px';
+
+		if (enableWrap && this.overflow != 'fill' && this.overflow != 'width')
+		{
+			var sizeDiv = node;
+			
+			if (sizeDiv.firstChild != null && sizeDiv.firstChild.nodeName == 'DIV')
+			{
+				sizeDiv = sizeDiv.firstChild;
+				
+				if (node.style.wordWrap == 'break-word')
+				{
+					sizeDiv.style.width = '100%';
+				}
+			}
+			
+			var tmp = sizeDiv.offsetWidth;
+			
+			// Workaround for text measuring in hidden containers
+			if (tmp == 0)
+			{
+				var prev = node.parentNode;
+				node.style.visibility = 'hidden';
+				document.body.appendChild(node);
+				tmp = sizeDiv.offsetWidth;
+				node.style.visibility = '';
+				prev.appendChild(node);
+			}
+
+			tmp += 3;
+			
+			if (this.clipped)
+			{
+				tmp = Math.min(tmp, w);
+			}
+			
+			style.width = tmp + 'px';
+		}
+	}
+	else
+	{
+		style.whiteSpace = 'nowrap';
+	}
+};
+
+/**
+ * Function: getMargin
+ *
+ * Returns the spacing as an <mxPoint>.
+ */
+mxText.prototype.updateMargin = function()
+{
+	this.margin = mxUtils.getAlignmentAsPoint(this.align, this.valign);
+};
+
+/**
+ * Function: getSpacing
+ *
+ * Returns the spacing as an <mxPoint>.
+ */
+mxText.prototype.getSpacing = function()
+{
+	var dx = 0;
+	var dy = 0;
+
+	if (this.align == mxConstants.ALIGN_CENTER)
+	{
+		dx = (this.spacingLeft - this.spacingRight) / 2;
+	}
+	else if (this.align == mxConstants.ALIGN_RIGHT)
+	{
+		dx = -this.spacingRight - this.baseSpacingRight;
+	}
+	else
+	{
+		dx = this.spacingLeft + this.baseSpacingLeft;
+	}
+
+	if (this.valign == mxConstants.ALIGN_MIDDLE)
+	{
+		dy = (this.spacingTop - this.spacingBottom) / 2;
+	}
+	else if (this.valign == mxConstants.ALIGN_BOTTOM)
+	{
+		dy = -this.spacingBottom - this.baseSpacingBottom;;
+	}
+	else
+	{
+		dy = this.spacingTop + this.baseSpacingTop;
+	}
+	
+	return new mxPoint(dx, dy);
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxTriangle
+ * 
+ * Implementation of the triangle shape.
+ * 
+ * Constructor: mxTriangle
+ *
+ * Constructs a new triangle shape.
+ */
+function mxTriangle()
+{
+	mxActor.call(this);
+};
+
+/**
+ * Extends mxActor.
+ */
+mxUtils.extend(mxTriangle, mxActor);
+
+/**
+ * Function: redrawPath
+ *
+ * Draws the path for this shape.
+ */
+mxTriangle.prototype.redrawPath = function(c, x, y, w, h)
+{
+	var arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
+	this.addPoints(c, [new mxPoint(0, 0), new mxPoint(w, 0.5 * h), new mxPoint(0, h)], this.isRounded, arcSize, true);
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxHexagon
+ * 
+ * Implementation of the hexagon shape.
+ * 
+ * Constructor: mxHexagon
+ *
+ * Constructs a new hexagon shape.
+ */
+function mxHexagon()
+{
+	mxActor.call(this);
+};
+
+/**
+ * Extends mxActor.
+ */
+mxUtils.extend(mxHexagon, mxActor);
+
+/**
+ * Function: redrawPath
+ *
+ * Draws the path for this shape.
+ */
+mxHexagon.prototype.redrawPath = function(c, x, y, w, h)
+{
+	var arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
+	this.addPoints(c, [new mxPoint(0.25 * w, 0), new mxPoint(0.75 * w, 0), new mxPoint(w, 0.5 * h), new mxPoint(0.75 * w, h),
+	                   new mxPoint(0.25 * w, h), new mxPoint(0, 0.5 * h)], this.isRounded, arcSize, true);
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxLine
+ *
+ * Extends <mxShape> to implement a horizontal line shape.
+ * This shape is registered under <mxConstants.SHAPE_LINE> in
+ * <mxCellRenderer>.
+ * 
+ * Constructor: mxLine
+ *
+ * Constructs a new line shape.
+ * 
+ * Parameters:
+ * 
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * stroke - String that defines the stroke color. Default is 'black'. This is
+ * stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxLine(bounds, stroke, strokewidth)
+{
+	mxShape.call(this);
+	this.bounds = bounds;
+	this.stroke = stroke;
+	this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxUtils.extend(mxLine, mxShape);
+
+/**
+ * Function: paintVertexShape
+ * 
+ * Redirects to redrawPath for subclasses to work.
+ */
+mxLine.prototype.paintVertexShape = function(c, x, y, w, h)
+{
+	var mid = y + h / 2;
+
+	c.begin();
+	c.moveTo(x, mid);
+	c.lineTo(x + w, mid);
+	c.stroke();
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxImageShape
+ *
+ * Extends <mxShape> to implement an image shape. This shape is registered
+ * under <mxConstants.SHAPE_IMAGE> in <mxCellRenderer>.
+ * 
+ * Constructor: mxImageShape
+ * 
+ * Constructs a new image shape.
+ * 
+ * Parameters:
+ * 
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * image - String that specifies the URL of the image. This is stored in
+ * <image>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 0. This is stored in <strokewidth>.
+ */
+function mxImageShape(bounds, image, fill, stroke, strokewidth)
+{
+	mxShape.call(this);
+	this.bounds = bounds;
+	this.image = image;
+	this.fill = fill;
+	this.stroke = stroke;
+	this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+	this.shadow = false;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxUtils.extend(mxImageShape, mxRectangleShape);
+
+/**
+ * Variable: preserveImageAspect
+ *
+ * Switch to preserve image aspect. Default is true.
+ */
+mxImageShape.prototype.preserveImageAspect = true;
+
+/**
+ * Function: getSvgScreenOffset
+ * 
+ * Disables offset in IE9 for crisper image output.
+ */
+mxImageShape.prototype.getSvgScreenOffset = function()
+{
+	return 0;
+};
+
+/**
+ * Function: apply
+ * 
+ * Overrides <mxShape.apply> to replace the fill and stroke colors with the
+ * respective values from <mxConstants.STYLE_IMAGE_BACKGROUND> and
+ * <mxConstants.STYLE_IMAGE_BORDER>.
+ * 
+ * Applies the style of the given <mxCellState> to the shape. This
+ * implementation assigns the following styles to local fields:
+ * 
+ * - <mxConstants.STYLE_IMAGE_BACKGROUND> => fill
+ * - <mxConstants.STYLE_IMAGE_BORDER> => stroke
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> of the corresponding cell.
+ */
+mxImageShape.prototype.apply = function(state)
+{
+	mxShape.prototype.apply.apply(this, arguments);
+	
+	this.fill = null;
+	this.stroke = null;
+	this.gradient = null;
+	
+	if (this.style != null)
+	{
+		this.preserveImageAspect = mxUtils.getNumber(this.style, mxConstants.STYLE_IMAGE_ASPECT, 1) == 1;
+		
+		// Legacy support for imageFlipH/V
+		this.flipH = this.flipH || mxUtils.getValue(this.style, 'imageFlipH', 0) == 1;
+		this.flipV = this.flipV || mxUtils.getValue(this.style, 'imageFlipV', 0) == 1;
+	}
+};
+
+/**
+ * Function: isHtmlAllowed
+ * 
+ * Returns true if HTML is allowed for this shape. This implementation always
+ * returns false.
+ */
+mxImageShape.prototype.isHtmlAllowed = function()
+{
+	return !this.preserveImageAspect;
+};
+
+/**
+ * Function: createHtml
+ *
+ * Creates and returns the HTML DOM node(s) to represent
+ * this shape. This implementation falls back to <createVml>
+ * so that the HTML creation is optional.
+ */
+mxImageShape.prototype.createHtml = function()
+{
+	var node = document.createElement('div');
+	node.style.position = 'absolute';
+
+	return node;
+};
+
+/**
+ * Function: paintVertexShape
+ * 
+ * Generic background painting implementation.
+ */
+mxImageShape.prototype.paintVertexShape = function(c, x, y, w, h)
+{
+	if (this.image != null)
+	{
+		var fill = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_BACKGROUND, null);
+		var stroke = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_BORDER, null);
+		
+		if (fill != null)
+		{
+			// Stroke rendering required for shadow
+			c.setFillColor(fill);
+			c.setStrokeColor(stroke);
+			c.rect(x, y, w, h);
+			c.fillAndStroke();
+		}
+
+		// FlipH/V are implicit via mxShape.updateTransform
+		c.image(x, y, w, h, this.image, this.preserveImageAspect, false, false);
+		
+		var stroke = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_BORDER, null);
+		
+		if (stroke != null)
+		{
+			c.setShadow(false);
+			c.setStrokeColor(stroke);
+			c.rect(x, y, w, h);
+			c.stroke();
+		}
+	}
+	else
+	{
+		mxRectangleShape.prototype.paintBackground.apply(this, arguments);
+	}
+};
+
+/**
+ * Function: redraw
+ * 
+ * Overrides <mxShape.redraw> to preserve the aspect ratio of images.
+ */
+mxImageShape.prototype.redrawHtmlShape = function()
+{
+	this.node.style.left = Math.round(this.bounds.x) + 'px';
+	this.node.style.top = Math.round(this.bounds.y) + 'px';
+	this.node.style.width = Math.max(0, Math.round(this.bounds.width)) + 'px';
+	this.node.style.height = Math.max(0, Math.round(this.bounds.height)) + 'px';
+	this.node.innerHTML = '';
+
+	if (this.image != null)
+	{
+		var fill = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_BACKGROUND, '');
+		var stroke = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_BORDER, '');
+		this.node.style.backgroundColor = fill;
+		this.node.style.borderColor = stroke;
+		
+		// VML image supports PNG in IE6
+		var useVml = mxClient.IS_IE6 || ((document.documentMode == null || document.documentMode <= 8) && this.rotation != 0);
+		var img = document.createElement((useVml) ? mxClient.VML_PREFIX + ':image' : 'img');
+		img.setAttribute('border', '0');
+		img.style.position = 'absolute';
+		img.src = this.image;
+
+		var filter = (this.opacity < 100) ? 'alpha(opacity=' + this.opacity + ')' : '';
+		this.node.style.filter = filter;
+		
+		if (this.flipH && this.flipV)
+		{
+			filter += 'progid:DXImageTransform.Microsoft.BasicImage(rotation=2)';
+		}
+		else if (this.flipH)
+		{
+			filter += 'progid:DXImageTransform.Microsoft.BasicImage(mirror=1)';
+		}
+		else if (this.flipV)
+		{
+			filter += 'progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)';
+		}
+
+		if (img.style.filter != filter)
+		{
+			img.style.filter = filter;
+		}
+
+		if (img.nodeName == 'image')
+		{
+			img.style.rotation = this.rotation;
+		}
+		else if (this.rotation != 0)
+		{
+			// LATER: Add flipV/H support
+			mxUtils.setPrefixedStyle(img.style, 'transform', 'rotate(' + this.rotation + 'deg)');
+		}
+		else
+		{
+			mxUtils.setPrefixedStyle(img.style, 'transform', '');
+		}
+
+		// Known problem: IE clips top line of image for certain angles
+		img.style.width = this.node.style.width;
+		img.style.height = this.node.style.height;
+		
+		this.node.style.backgroundImage = '';
+		this.node.appendChild(img);
+	}
+	else
+	{
+		this.setTransparentBackgroundImage(this.node);
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxLabel
+ *
+ * Extends <mxShape> to implement an image shape with a label.
+ * This shape is registered under <mxConstants.SHAPE_LABEL> in
+ * <mxCellRenderer>.
+ * 
+ * Constructor: mxLabel
+ *
+ * Constructs a new label shape.
+ * 
+ * Parameters:
+ * 
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxLabel(bounds, fill, stroke, strokewidth)
+{
+	mxRectangleShape.call(this, bounds, fill, stroke, strokewidth);
+};
+
+/**
+ * Extends mxShape.
+ */
+mxUtils.extend(mxLabel, mxRectangleShape);
+
+/**
+ * Variable: imageSize
+ *
+ * Default width and height for the image. Default is
+ * <mxConstants.DEFAULT_IMAGESIZE>.
+ */
+mxLabel.prototype.imageSize = mxConstants.DEFAULT_IMAGESIZE;
+
+/**
+ * Variable: spacing
+ *
+ * Default value for image spacing. Default is 2.
+ */
+mxLabel.prototype.spacing = 2;
+
+/**
+ * Variable: indicatorSize
+ *
+ * Default width and height for the indicicator. Default is 10.
+ */
+mxLabel.prototype.indicatorSize = 10;
+
+/**
+ * Variable: indicatorSpacing
+ *
+ * Default spacing between image and indicator. Default is 2.
+ */
+mxLabel.prototype.indicatorSpacing = 2;
+
+/**
+ * Function: init
+ *
+ * Initializes the shape and the <indicator>.
+ */
+mxLabel.prototype.init = function(container)
+{
+	mxShape.prototype.init.apply(this, arguments);
+
+	if (this.indicatorShape != null)
+	{
+		this.indicator = new this.indicatorShape();
+		this.indicator.dialect = this.dialect;
+		this.indicator.init(this.node);
+	}
+};
+
+/**
+ * Function: redraw
+ *
+ * Reconfigures this shape. This will update the colors of the indicator
+ * and reconfigure it if required.
+ */
+mxLabel.prototype.redraw = function()
+{
+	if (this.indicator != null)
+	{
+		this.indicator.fill = this.indicatorColor;
+		this.indicator.stroke = this.indicatorStrokeColor;
+		this.indicator.gradient = this.indicatorGradientColor;
+		this.indicator.direction = this.indicatorDirection;
+	}
+	
+	mxShape.prototype.redraw.apply(this, arguments);
+};
+
+/**
+ * Function: isHtmlAllowed
+ *
+ * Returns true for non-rounded, non-rotated shapes with no glass gradient and
+ * no indicator shape.
+ */
+mxLabel.prototype.isHtmlAllowed = function()
+{
+	return mxRectangleShape.prototype.isHtmlAllowed.apply(this, arguments) &&
+		this.indicatorColor == null && this.indicatorShape == null;
+};
+
+/**
+ * Function: paintForeground
+ * 
+ * Generic background painting implementation.
+ */
+mxLabel.prototype.paintForeground = function(c, x, y, w, h)
+{
+	this.paintImage(c, x, y, w, h);
+	this.paintIndicator(c, x, y, w, h);
+	
+	mxRectangleShape.prototype.paintForeground.apply(this, arguments);
+};
+
+/**
+ * Function: paintImage
+ * 
+ * Generic background painting implementation.
+ */
+mxLabel.prototype.paintImage = function(c, x, y, w, h)
+{
+	if (this.image != null)
+	{
+		var bounds = this.getImageBounds(x, y, w, h);
+		c.image(bounds.x, bounds.y, bounds.width, bounds.height, this.image, false, false, false);
+	}
+};
+
+/**
+ * Function: getImageBounds
+ * 
+ * Generic background painting implementation.
+ */
+mxLabel.prototype.getImageBounds = function(x, y, w, h)
+{
+	var align = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_ALIGN, mxConstants.ALIGN_LEFT);
+	var valign = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_VERTICAL_ALIGN, mxConstants.ALIGN_MIDDLE);
+	var width = mxUtils.getNumber(this.style, mxConstants.STYLE_IMAGE_WIDTH, mxConstants.DEFAULT_IMAGESIZE);
+	var height = mxUtils.getNumber(this.style, mxConstants.STYLE_IMAGE_HEIGHT, mxConstants.DEFAULT_IMAGESIZE);
+	var spacing = mxUtils.getNumber(this.style, mxConstants.STYLE_SPACING, this.spacing) + 5;
+
+	if (align == mxConstants.ALIGN_CENTER)
+	{
+		x += (w - width) / 2;
+	}
+	else if (align == mxConstants.ALIGN_RIGHT)
+	{
+		x += w - width - spacing;
+	}
+	else // default is left
+	{
+		x += spacing;
+	}
+
+	if (valign == mxConstants.ALIGN_TOP)
+	{
+		y += spacing;
+	}
+	else if (valign == mxConstants.ALIGN_BOTTOM)
+	{
+		y += h - height - spacing;
+	}
+	else // default is middle
+	{
+		y += (h - height) / 2;
+	}
+	
+	return new mxRectangle(x, y, width, height);
+};
+
+/**
+ * Function: paintIndicator
+ * 
+ * Generic background painting implementation.
+ */
+mxLabel.prototype.paintIndicator = function(c, x, y, w, h)
+{
+	if (this.indicator != null)
+	{
+		this.indicator.bounds = this.getIndicatorBounds(x, y, w, h);
+		this.indicator.paint(c);
+	}
+	else if (this.indicatorImage != null)
+	{
+		var bounds = this.getIndicatorBounds(x, y, w, h);
+		c.image(bounds.x, bounds.y, bounds.width, bounds.height, this.indicatorImage, false, false, false);
+	}
+};
+
+/**
+ * Function: getIndicatorBounds
+ * 
+ * Generic background painting implementation.
+ */
+mxLabel.prototype.getIndicatorBounds = function(x, y, w, h)
+{
+	var align = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_ALIGN, mxConstants.ALIGN_LEFT);
+	var valign = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_VERTICAL_ALIGN, mxConstants.ALIGN_MIDDLE);
+	var width = mxUtils.getNumber(this.style, mxConstants.STYLE_INDICATOR_WIDTH, this.indicatorSize);
+	var height = mxUtils.getNumber(this.style, mxConstants.STYLE_INDICATOR_HEIGHT, this.indicatorSize);
+	var spacing = this.spacing + 5;		
+	
+	if (align == mxConstants.ALIGN_RIGHT)
+	{
+		x += w - width - spacing;
+	}
+	else if (align == mxConstants.ALIGN_CENTER)
+	{
+		x += (w - width) / 2;
+	}
+	else // default is left
+	{
+		x += spacing;
+	}
+	
+	if (valign == mxConstants.ALIGN_BOTTOM)
+	{
+		y += h - height - spacing;
+	}
+	else if (valign == mxConstants.ALIGN_TOP)
+	{
+		y += spacing;
+	}
+	else // default is middle
+	{
+		y += (h - height) / 2;
+	}
+	
+	return new mxRectangle(x, y, width, height);
+};
+/**
+ * Function: redrawHtmlShape
+ * 
+ * Generic background painting implementation.
+ */
+mxLabel.prototype.redrawHtmlShape = function()
+{
+	mxRectangleShape.prototype.redrawHtmlShape.apply(this, arguments);
+	
+	// Removes all children
+	while(this.node.hasChildNodes())
+	{
+		this.node.removeChild(this.node.lastChild);
+	}
+	
+	if (this.image != null)
+	{
+		var node = document.createElement('img');
+		node.style.position = 'relative';
+		node.setAttribute('border', '0');
+		
+		var bounds = this.getImageBounds(this.bounds.x, this.bounds.y, this.bounds.width, this.bounds.height);
+		bounds.x -= this.bounds.x;
+		bounds.y -= this.bounds.y;
+
+		node.style.left = Math.round(bounds.x) + 'px';
+		node.style.top = Math.round(bounds.y) + 'px';
+		node.style.width = Math.round(bounds.width) + 'px';
+		node.style.height = Math.round(bounds.height) + 'px';
+		
+		node.src = this.image;
+		
+		this.node.appendChild(node);
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxCylinder
+ *
+ * Extends <mxShape> to implement an cylinder shape. If a
+ * custom shape with one filled area and an overlay path is
+ * needed, then this shape's <redrawPath> should be overridden.
+ * This shape is registered under <mxConstants.SHAPE_CYLINDER>
+ * in <mxCellRenderer>.
+ * 
+ * Constructor: mxCylinder
+ *
+ * Constructs a new cylinder shape.
+ * 
+ * Parameters:
+ * 
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxCylinder(bounds, fill, stroke, strokewidth)
+{
+	mxShape.call(this);
+	this.bounds = bounds;
+	this.fill = fill;
+	this.stroke = stroke;
+	this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxUtils.extend(mxCylinder, mxShape);
+
+/**
+ * Variable: maxHeight
+ *
+ * Defines the maximum height of the top and bottom part
+ * of the cylinder shape.
+ */
+mxCylinder.prototype.maxHeight = 40;
+
+/**
+ * Variable: svgStrokeTolerance
+ *
+ * Sets stroke tolerance to 0 for SVG.
+ */
+mxCylinder.prototype.svgStrokeTolerance = 0;
+
+/**
+ * Function: paintVertexShape
+ * 
+ * Redirects to redrawPath for subclasses to work.
+ */
+mxCylinder.prototype.paintVertexShape = function(c, x, y, w, h)
+{
+	c.translate(x, y);
+	c.begin();
+	this.redrawPath(c, x, y, w, h, false);
+	c.fillAndStroke();
+	
+	c.setShadow(false);
+	
+	c.begin();
+	this.redrawPath(c, x, y, w, h, true);
+	c.stroke();
+};
+
+/**
+ * Function: redrawPath
+ *
+ * Draws the path for this shape.
+ */
+mxCylinder.prototype.redrawPath = function(c, x, y, w, h, isForeground)
+{
+	var dy = Math.min(this.maxHeight, Math.round(h / 5));
+	
+	if ((isForeground && this.fill != null) || (!isForeground && this.fill == null))
+	{
+		c.moveTo(0, dy);
+		c.curveTo(0, 2 * dy, w, 2 * dy, w, dy);
+		
+		// Needs separate shapes for correct hit-detection
+		if (!isForeground)
+		{
+			c.stroke();
+			c.begin();
+		}
+	}
+	
+	if (!isForeground)
+	{
+		c.moveTo(0, dy);
+		c.curveTo(0, -dy / 3, w, -dy / 3, w, dy);
+		c.lineTo(w, h - dy);
+		c.curveTo(w, h + dy / 3, 0, h + dy / 3, 0, h - dy);
+		c.close();
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxConnector
+ * 
+ * Extends <mxShape> to implement a connector shape. The connector
+ * shape allows for arrow heads on either side.
+ * 
+ * This shape is registered under <mxConstants.SHAPE_CONNECTOR> in
+ * <mxCellRenderer>.
+ * 
+ * Constructor: mxConnector
+ * 
+ * Constructs a new connector shape.
+ * 
+ * Parameters:
+ * 
+ * points - Array of <mxPoints> that define the points. This is stored in
+ * <mxShape.points>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * Default is 'black'.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxConnector(points, stroke, strokewidth)
+{
+	mxPolyline.call(this, points, stroke, strokewidth);
+};
+
+/**
+ * Extends mxPolyline.
+ */
+mxUtils.extend(mxConnector, mxPolyline);
+
+/**
+ * Function: updateBoundingBox
+ *
+ * Updates the <boundingBox> for this shape using <createBoundingBox> and
+ * <augmentBoundingBox> and stores the result in <boundingBox>.
+ */
+mxConnector.prototype.updateBoundingBox = function()
+{
+	this.useSvgBoundingBox = this.style != null && this.style[mxConstants.STYLE_CURVED] == 1;
+	mxShape.prototype.updateBoundingBox.apply(this, arguments);
+};
+
+/**
+ * Function: paintEdgeShape
+ * 
+ * Paints the line shape.
+ */
+mxConnector.prototype.paintEdgeShape = function(c, pts)
+{
+	// The indirection via functions for markers is needed in
+	// order to apply the offsets before painting the line and
+	// paint the markers after painting the line.
+	var sourceMarker = this.createMarker(c, pts, true);
+	var targetMarker = this.createMarker(c, pts, false);
+
+	mxPolyline.prototype.paintEdgeShape.apply(this, arguments);
+	
+	// Disables shadows, dashed styles and fixes fill color for markers
+	c.setFillColor(this.stroke);
+	c.setShadow(false);
+	c.setDashed(false);
+	
+	if (sourceMarker != null)
+	{
+		sourceMarker();
+	}
+	
+	if (targetMarker != null)
+	{
+		targetMarker();
+	}
+};
+
+/**
+ * Function: createMarker
+ * 
+ * Prepares the marker by adding offsets in pts and returning a function to
+ * paint the marker.
+ */
+mxConnector.prototype.createMarker = function(c, pts, source)
+{
+	var result = null;
+	var n = pts.length;
+	var type = mxUtils.getValue(this.style, (source) ? mxConstants.STYLE_STARTARROW : mxConstants.STYLE_ENDARROW);
+	var p0 = (source) ? pts[1] : pts[n - 2];
+	var pe = (source) ? pts[0] : pts[n - 1];
+	
+	if (type != null && p0 != null && pe != null)
+	{
+		var count = 1;
+		
+		// Uses next non-overlapping point
+		while (count < n - 1 && Math.round(p0.x - pe.x) == 0 && Math.round(p0.y - pe.y) == 0)
+		{
+			p0 = (source) ? pts[1 + count] : pts[n - 2 - count];
+			count++;
+		}
+	
+		// Computes the norm and the inverse norm
+		var dx = pe.x - p0.x;
+		var dy = pe.y - p0.y;
+	
+		var dist = Math.max(1, Math.sqrt(dx * dx + dy * dy));
+		
+		var unitX = dx / dist;
+		var unitY = dy / dist;
+	
+		var size = mxUtils.getNumber(this.style, (source) ? mxConstants.STYLE_STARTSIZE : mxConstants.STYLE_ENDSIZE, mxConstants.DEFAULT_MARKERSIZE);
+		
+		// Allow for stroke width in the end point used and the 
+		// orthogonal vectors describing the direction of the marker
+		var filled = this.style[(source) ? mxConstants.STYLE_STARTFILL : mxConstants.STYLE_ENDFILL] != 0;
+		
+		result = mxMarker.createMarker(c, this, type, pe, unitX, unitY, size, source, this.strokewidth, filled);
+	}
+	
+	return result;
+};
+
+/**
+ * Function: augmentBoundingBox
+ *
+ * Augments the bounding box with the strokewidth and shadow offsets.
+ */
+mxConnector.prototype.augmentBoundingBox = function(bbox)
+{
+	mxShape.prototype.augmentBoundingBox.apply(this, arguments);
+	
+	// Adds marker sizes
+	var size = 0;
+	
+	if (mxUtils.getValue(this.style, mxConstants.STYLE_STARTARROW, mxConstants.NONE) != mxConstants.NONE)
+	{
+		size = mxUtils.getNumber(this.style, mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_MARKERSIZE) + 1;
+	}
+	
+	if (mxUtils.getValue(this.style, mxConstants.STYLE_ENDARROW, mxConstants.NONE) != mxConstants.NONE)
+	{
+		size = Math.max(size, mxUtils.getNumber(this.style, mxConstants.STYLE_ENDSIZE, mxConstants.DEFAULT_MARKERSIZE)) + 1;
+	}
+	
+	bbox.grow(size * this.scale);
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxSwimlane
+ *
+ * Extends <mxShape> to implement a swimlane shape. This shape is registered
+ * under <mxConstants.SHAPE_SWIMLANE> in <mxCellRenderer>. Use the
+ * <mxConstants.STYLE_STYLE_STARTSIZE> to define the size of the title
+ * region, <mxConstants.STYLE_SWIMLANE_FILLCOLOR> for the content area fill,
+ * <mxConstants.STYLE_SEPARATORCOLOR> to draw an additional vertical separator
+ * and <mxConstants.STYLE_SWIMLANE_LINE> to hide the line between the title
+ * region and the content area. The <mxConstants.STYLE_HORIZONTAL> affects
+ * the orientation of this shape, not only its label.
+ * 
+ * Constructor: mxSwimlane
+ *
+ * Constructs a new swimlane shape.
+ * 
+ * Parameters:
+ * 
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxSwimlane(bounds, fill, stroke, strokewidth)
+{
+	mxShape.call(this);
+	this.bounds = bounds;
+	this.fill = fill;
+	this.stroke = stroke;
+	this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxUtils.extend(mxSwimlane, mxShape);
+
+/**
+ * Variable: imageSize
+ *
+ * Default imagewidth and imageheight if an image but no imagewidth
+ * and imageheight are defined in the style. Value is 16.
+ */
+mxSwimlane.prototype.imageSize = 16;
+
+/**
+ * Function: getGradientBounds
+ * 
+ * Returns the bounding box for the gradient box for this shape.
+ */
+mxSwimlane.prototype.getTitleSize = function()
+{
+	return Math.max(0, mxUtils.getValue(this.style, mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE));
+};
+
+/**
+ * Function: getGradientBounds
+ * 
+ * Returns the bounding box for the gradient box for this shape.
+ */
+mxSwimlane.prototype.getLabelBounds = function(rect)
+{
+	var start = this.getTitleSize();
+	var bounds = new mxRectangle(rect.x, rect.y, rect.width, rect.height);
+	var horizontal = this.isHorizontal();
+	
+	var flipH = mxUtils.getValue(this.style, mxConstants.STYLE_FLIPH, 0) == 1;
+	var flipV = mxUtils.getValue(this.style, mxConstants.STYLE_FLIPV, 0) == 1;	
+	
+	// East is default
+	var shapeVertical = (this.direction == mxConstants.DIRECTION_NORTH ||
+			this.direction == mxConstants.DIRECTION_SOUTH);
+	var realHorizontal = horizontal == !shapeVertical;
+	
+	var realFlipH = !realHorizontal && flipH != (this.direction == mxConstants.DIRECTION_SOUTH ||
+			this.direction == mxConstants.DIRECTION_WEST);
+	var realFlipV = realHorizontal && flipV != (this.direction == mxConstants.DIRECTION_SOUTH ||
+			this.direction == mxConstants.DIRECTION_WEST);
+
+	// Shape is horizontal
+	if (!shapeVertical)
+	{
+		var tmp = Math.min(bounds.height, start * this.scale);
+
+		if (realFlipH || realFlipV)
+		{
+			bounds.y += bounds.height - tmp;
+		}
+
+		bounds.height = tmp;
+	}
+	else
+	{
+		var tmp = Math.min(bounds.width, start * this.scale);
+		
+		if (realFlipH || realFlipV)
+		{
+			bounds.x += bounds.width - tmp;	
+		}
+
+		bounds.width = tmp;
+	}
+	
+	return bounds;
+};
+
+/**
+ * Function: getGradientBounds
+ * 
+ * Returns the bounding box for the gradient box for this shape.
+ */
+mxSwimlane.prototype.getGradientBounds = function(c, x, y, w, h)
+{
+	var start = this.getTitleSize();
+	
+	if (this.isHorizontal())
+	{
+		start = Math.min(start, h);
+		return new mxRectangle(x, y, w, start);
+	}
+	else
+	{
+		start = Math.min(start, w);
+		return new mxRectangle(x, y, start, h);
+	}
+};
+
+/**
+ * Function: getArcSize
+ * 
+ * Returns the arcsize for the swimlane.
+ */
+mxSwimlane.prototype.getArcSize = function(w, h, start)
+{
+	var f = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.RECTANGLE_ROUNDING_FACTOR * 100) / 100;
+
+	return start * f * 3; 
+};
+
+/**
+ * Function: paintVertexShape
+ *
+ * Paints the swimlane vertex shape.
+ */
+mxSwimlane.prototype.isHorizontal = function()
+{
+	return mxUtils.getValue(this.style, mxConstants.STYLE_HORIZONTAL, 1) == 1;
+};
+
+/**
+ * Function: paintVertexShape
+ *
+ * Paints the swimlane vertex shape.
+ */
+mxSwimlane.prototype.paintVertexShape = function(c, x, y, w, h)
+{
+	var start = this.getTitleSize();
+	var fill = mxUtils.getValue(this.style, mxConstants.STYLE_SWIMLANE_FILLCOLOR, mxConstants.NONE);
+	var swimlaneLine = mxUtils.getValue(this.style, mxConstants.STYLE_SWIMLANE_LINE, 1) == 1;
+	var r = 0;
+	
+	if (this.isHorizontal())
+	{
+		start = Math.min(start, h);
+	}
+	else
+	{
+		start = Math.min(start, w);
+	}
+	
+	c.translate(x, y);
+	
+	if (!this.isRounded)
+	{
+		this.paintSwimlane(c, x, y, w, h, start, fill, swimlaneLine);
+	}
+	else
+	{
+		r = this.getArcSize(w, h, start);
+		this.paintRoundedSwimlane(c, x, y, w, h, start, r, fill, swimlaneLine);
+	}
+	
+	var sep = mxUtils.getValue(this.style, mxConstants.STYLE_SEPARATORCOLOR, mxConstants.NONE);
+	this.paintSeparator(c, x, y, w, h, start, sep);
+
+	if (this.image != null)
+	{
+		var bounds = this.getImageBounds(x, y, w, h);
+		c.image(bounds.x - x, bounds.y - y, bounds.width, bounds.height,
+				this.image, false, false, false);
+	}
+	
+	if (this.glass)
+	{
+		c.setShadow(false);
+		this.paintGlassEffect(c, 0, 0, w, start, r);
+	}
+};
+
+/**
+ * Function: paintSwimlane
+ *
+ * Paints the swimlane vertex shape.
+ */
+mxSwimlane.prototype.paintSwimlane = function(c, x, y, w, h, start, fill, swimlaneLine)
+{
+	if (fill != mxConstants.NONE)
+	{
+		c.save();
+		c.setFillColor(fill);
+		c.rect(0, 0, w, h);
+		c.fillAndStroke();
+		c.restore();
+		c.setShadow(false);
+	}
+
+	c.begin();
+	
+	if (this.isHorizontal())
+	{
+		c.moveTo(0, start);
+		c.lineTo(0, 0);
+		c.lineTo(w, 0);
+		c.lineTo(w, start);
+		
+		if (swimlaneLine || start >= h)
+		{
+			c.close();
+		}
+		
+		c.fillAndStroke();
+		
+		// Transparent content area
+		if (start < h && fill == mxConstants.NONE)
+		{
+			c.pointerEvents = false;
+			
+			c.begin();
+			c.moveTo(0, start);
+			c.lineTo(0, h);
+			c.lineTo(w, h);
+			c.lineTo(w, start);
+			c.stroke();
+		}
+	}
+	else
+	{
+		c.moveTo(start, 0);
+		c.lineTo(0, 0);
+		c.lineTo(0, h);
+		c.lineTo(start, h);
+		
+		if (swimlaneLine || start >= w)
+		{
+			c.close();
+		}
+		
+		c.fillAndStroke();
+		
+		// Transparent content area
+		if (start < w && fill == mxConstants.NONE)
+		{
+			c.pointerEvents = false;
+			
+			c.begin();
+			c.moveTo(start, 0);
+			c.lineTo(w, 0);
+			c.lineTo(w, h);
+			c.lineTo(start, h);
+			c.stroke();
+		}
+	}
+};
+
+/**
+ * Function: paintRoundedSwimlane
+ *
+ * Paints the swimlane vertex shape.
+ */
+mxSwimlane.prototype.paintRoundedSwimlane = function(c, x, y, w, h, start, r, fill, swimlaneLine)
+{
+	r = Math.min(h - start, Math.min(start, r));
+	
+	if (fill != mxConstants.NONE)
+	{
+		c.save();
+		c.setFillColor(fill);
+		c.roundrect(0, 0, w, h, r, r);
+		c.fillAndStroke();
+		c.restore();
+		c.setShadow(false);
+	}
+	
+	c.begin();
+	
+	if (this.isHorizontal())
+	{
+		c.moveTo(w, start);
+		c.lineTo(w, r);
+		c.quadTo(w, 0, w - Math.min(w / 2, r), 0);
+		c.lineTo(Math.min(w / 2, r), 0);
+		c.quadTo(0, 0, 0, r);
+		c.lineTo(0, start);
+		
+		if (swimlaneLine || start >= h)
+		{
+			c.close();
+		}
+	
+		c.fillAndStroke();
+		
+		// Transparent content area
+		if (start < h && fill == mxConstants.NONE)
+		{
+			c.pointerEvents = false;
+			
+			c.begin();
+			c.moveTo(0, start);
+			c.lineTo(0, h - r);
+			c.quadTo(0, h, Math.min(w / 2, r), h);
+			c.lineTo(w - Math.min(w / 2, r), h);
+			c.quadTo(w, h, w, h - r);
+			c.lineTo(w, start);
+			c.stroke();
+		}
+	}
+	else
+	{
+		c.moveTo(start, 0);
+		c.lineTo(r, 0);
+		c.quadTo(0, 0, 0, Math.min(h / 2, r));
+		c.lineTo(0, h - Math.min(h / 2, r));
+		c.quadTo(0, h, r, h);
+		c.lineTo(start, h);
+		
+		if (swimlaneLine || start >= w)
+		{
+			c.close();
+		}
+	
+		c.fillAndStroke();
+		
+		// Transparent content area
+		if (start < w && fill == mxConstants.NONE)
+		{
+			c.pointerEvents = false;
+			
+			c.begin();
+			c.moveTo(start, h);
+			c.lineTo(w - r, h);
+			c.quadTo(w, h, w, h - Math.min(h / 2, r));
+			c.lineTo(w, Math.min(h / 2, r));
+			c.quadTo(w, 0, w - r, 0);
+			c.lineTo(start, 0);
+			c.stroke();
+		}
+	}
+};
+
+/**
+ * Function: paintSwimlane
+ *
+ * Paints the swimlane vertex shape.
+ */
+mxSwimlane.prototype.paintSeparator = function(c, x, y, w, h, start, color)
+{
+	if (color != mxConstants.NONE)
+	{
+		c.setStrokeColor(color);
+		c.setDashed(true);
+		c.begin();
+		
+		if (this.isHorizontal())
+		{
+			c.moveTo(w, start);
+			c.lineTo(w, h);
+		}
+		else
+		{
+			c.moveTo(start, 0);
+			c.lineTo(w, 0);
+		}
+		
+		c.stroke();
+		c.setDashed(false);
+	}
+};
+
+/**
+ * Function: getImageBounds
+ *
+ * Paints the swimlane vertex shape.
+ */
+mxSwimlane.prototype.getImageBounds = function(x, y, w, h)
+{
+	if (this.isHorizontal())
+	{
+		return new mxRectangle(x + w - this.imageSize, y, this.imageSize, this.imageSize);
+	}
+	else
+	{
+		return new mxRectangle(x, y, this.imageSize, this.imageSize);
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxGraphLayout
+ * 
+ * Base class for all layout algorithms in mxGraph. Main public functions are
+ * <move> for handling a moved cell within a layouted parent, and <execute> for
+ * running the layout on a given parent cell.
+ *
+ * Known Subclasses:
+ *
+ * <mxCircleLayout>, <mxCompactTreeLayout>, <mxCompositeLayout>,
+ * <mxFastOrganicLayout>, <mxParallelEdgeLayout>, <mxPartitionLayout>,
+ * <mxStackLayout>
+ * 
+ * Constructor: mxGraphLayout
+ *
+ * Constructs a new layout using the given layouts.
+ *
+ * Arguments:
+ * 
+ * graph - Enclosing 
+ */
+function mxGraphLayout(graph)
+{
+	this.graph = graph;
+};
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxGraphLayout.prototype.graph = null;
+
+/**
+ * Variable: useBoundingBox
+ *
+ * Boolean indicating if the bounding box of the label should be used if
+ * its available. Default is true.
+ */
+mxGraphLayout.prototype.useBoundingBox = true;
+
+/**
+ * Variable: parent
+ *
+ * The parent cell of the layout, if any
+ */
+mxGraphLayout.prototype.parent = null;
+
+/**
+ * Function: moveCell
+ * 
+ * Notified when a cell is being moved in a parent that has automatic
+ * layout to update the cell state (eg. index) so that the outcome of the
+ * layout will position the vertex as close to the point (x, y) as
+ * possible.
+ * 
+ * Empty implementation.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> which has been moved.
+ * x - X-coordinate of the new cell location.
+ * y - Y-coordinate of the new cell location.
+ */
+mxGraphLayout.prototype.moveCell = function(cell, x, y) { };
+
+/**
+ * Function: execute
+ * 
+ * Executes the layout algorithm for the children of the given parent.
+ * 
+ * Parameters:
+ * 
+ * parent - <mxCell> whose children should be layed out.
+ */
+mxGraphLayout.prototype.execute = function(parent) { };
+
+/**
+ * Function: getGraph
+ * 
+ * Returns the graph that this layout operates on.
+ */
+mxGraphLayout.prototype.getGraph = function()
+{
+	return this.graph;
+};
+
+/**
+ * Function: getConstraint
+ * 
+ * Returns the constraint for the given key and cell. The optional edge and
+ * source arguments are used to return inbound and outgoing routing-
+ * constraints for the given edge and vertex. This implementation always
+ * returns the value for the given key in the style of the given cell.
+ * 
+ * Parameters:
+ * 
+ * key - Key of the constraint to be returned.
+ * cell - <mxCell> whose constraint should be returned.
+ * edge - Optional <mxCell> that represents the connection whose constraint
+ * should be returned. Default is null.
+ * source - Optional boolean that specifies if the connection is incoming
+ * or outgoing. Default is null.
+ */
+mxGraphLayout.prototype.getConstraint = function(key, cell, edge, source)
+{
+	var state = this.graph.view.getState(cell);
+	var style = (state != null) ? state.style : this.graph.getCellStyle(cell);
+	
+	return (style != null) ? style[key] : null;
+};
+
+/**
+ * Function: traverse
+ * 
+ * Traverses the (directed) graph invoking the given function for each
+ * visited vertex and edge. The function is invoked with the current vertex
+ * and the incoming edge as a parameter. This implementation makes sure
+ * each vertex is only visited once. The function may return false if the
+ * traversal should stop at the given vertex.
+ * 
+ * Example:
+ * 
+ * (code)
+ * mxLog.show();
+ * var cell = graph.getSelectionCell();
+ * graph.traverse(cell, false, function(vertex, edge)
+ * {
+ *   mxLog.debug(graph.getLabel(vertex));
+ * });
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * vertex - <mxCell> that represents the vertex where the traversal starts.
+ * directed - Optional boolean indicating if edges should only be traversed
+ * from source to target. Default is true.
+ * func - Visitor function that takes the current vertex and the incoming
+ * edge as arguments. The traversal stops if the function returns false.
+ * edge - Optional <mxCell> that represents the incoming edge. This is
+ * null for the first step of the traversal.
+ * visited - Optional <mxDictionary> of cell paths for the visited cells.
+ */
+mxGraphLayout.traverse = function(vertex, directed, func, edge, visited)
+{
+	if (func != null && vertex != null)
+	{
+		directed = (directed != null) ? directed : true;
+		visited = visited || new mxDictionary();
+		
+		if (!visited.get(vertex))
+		{
+			visited.put(vertex, true);
+			var result = func(vertex, edge);
+			
+			if (result == null || result)
+			{
+				var edgeCount = this.graph.model.getEdgeCount(vertex);
+				
+				if (edgeCount > 0)
+				{
+					for (var i = 0; i < edgeCount; i++)
+					{
+						var e = this.graph.model.getEdgeAt(vertex, i);
+						var isSource = this.graph.model.getTerminal(e, true) == vertex;
+												
+						if (!directed || isSource)
+						{
+							var next = this.graph.view.getVisibleTerminal(e, !isSource);
+							this.traverse(next, directed, func, e, visited);
+						}
+					}
+				}
+			}
+		}
+	}
+};
+
+/**
+ * Function: isVertexMovable
+ * 
+ * Returns a boolean indicating if the given <mxCell> is movable or
+ * bendable by the algorithm. This implementation returns true if the given
+ * cell is movable in the graph.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose movable state should be returned.
+ */
+mxGraphLayout.prototype.isVertexMovable = function(cell)
+{
+	return this.graph.isCellMovable(cell);
+};
+
+/**
+ * Function: isVertexIgnored
+ * 
+ * Returns a boolean indicating if the given <mxCell> should be ignored by
+ * the algorithm. This implementation returns false for all vertices.
+ * 
+ * Parameters:
+ * 
+ * vertex - <mxCell> whose ignored state should be returned.
+ */
+mxGraphLayout.prototype.isVertexIgnored = function(vertex)
+{
+	return !this.graph.getModel().isVertex(vertex) ||
+		!this.graph.isCellVisible(vertex);
+};
+
+/**
+ * Function: isEdgeIgnored
+ * 
+ * Returns a boolean indicating if the given <mxCell> should be ignored by
+ * the algorithm. This implementation returns false for all vertices.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose ignored state should be returned.
+ */
+mxGraphLayout.prototype.isEdgeIgnored = function(edge)
+{
+	var model = this.graph.getModel();
+	
+	return !model.isEdge(edge) ||
+		!this.graph.isCellVisible(edge) ||
+		model.getTerminal(edge, true) == null ||
+		model.getTerminal(edge, false) == null;
+};
+
+/**
+ * Function: setEdgeStyleEnabled
+ * 
+ * Disables or enables the edge style of the given edge.
+ */
+mxGraphLayout.prototype.setEdgeStyleEnabled = function(edge, value)
+{
+	this.graph.setCellStyles(mxConstants.STYLE_NOEDGESTYLE,
+			(value) ? '0' : '1', [edge]);
+};
+
+/**
+ * Function: setOrthogonalEdge
+ * 
+ * Disables or enables orthogonal end segments of the given edge.
+ */
+mxGraphLayout.prototype.setOrthogonalEdge = function(edge, value)
+{
+	this.graph.setCellStyles(mxConstants.STYLE_ORTHOGONAL,
+			(value) ? '1' : '0', [edge]);
+};
+
+/**
+ * Function: getParentOffset
+ * 
+ * Determines the offset of the given parent to the parent
+ * of the layout
+ */
+mxGraphLayout.prototype.getParentOffset = function(parent)
+{
+	var result = new mxPoint();
+
+	if (parent != null && parent != this.parent)
+	{
+		var model = this.graph.getModel();
+
+		if (model.isAncestor(this.parent, parent))
+		{
+			var parentGeo = model.getGeometry(parent);
+
+			while (parent != this.parent)
+			{
+				result.x = result.x + parentGeo.x;
+				result.y = result.y + parentGeo.y;
+
+				parent = model.getParent(parent);;
+				parentGeo = model.getGeometry(parent);
+			}
+		}
+	}
+
+	return result;
+};
+
+/**
+ * Function: setEdgePoints
+ * 
+ * Replaces the array of mxPoints in the geometry of the given edge
+ * with the given array of mxPoints.
+ */
+mxGraphLayout.prototype.setEdgePoints = function(edge, points)
+{
+	if (edge != null)
+	{
+		var model = this.graph.model;
+		var geometry = model.getGeometry(edge);
+
+		if (geometry == null)
+		{
+			geometry = new mxGeometry();
+			geometry.setRelative(true);
+		}
+		else
+		{
+			geometry = geometry.clone();
+		}
+
+		if (this.parent != null && points != null)
+		{
+			var parent = model.getParent(edge);
+
+			var parentOffset = this.getParentOffset(parent);
+
+			for (var i = 0; i < points.length; i++)
+			{
+				points[i].x = points[i].x - parentOffset.x;
+				points[i].y = points[i].y - parentOffset.y;
+			}
+		}
+
+		geometry.points = points;
+		model.setGeometry(edge, geometry);
+	}
+};
+
+/**
+ * Function: setVertexLocation
+ * 
+ * Sets the new position of the given cell taking into account the size of
+ * the bounding box if <useBoundingBox> is true. The change is only carried
+ * out if the new location is not equal to the existing location, otherwise
+ * the geometry is not replaced with an updated instance. The new or old
+ * bounds are returned (including overlapping labels).
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose geometry is to be set.
+ * x - Integer that defines the x-coordinate of the new location.
+ * y - Integer that defines the y-coordinate of the new location.
+ */
+mxGraphLayout.prototype.setVertexLocation = function(cell, x, y)
+{
+	var model = this.graph.getModel();
+	var geometry = model.getGeometry(cell);
+	var result = null;
+	
+	if (geometry != null)
+	{
+		result = new mxRectangle(x, y, geometry.width, geometry.height);
+		
+		// Checks for oversize labels and shifts the result
+		// TODO: Use mxUtils.getStringSize for label bounds
+		if (this.useBoundingBox)
+		{
+			var state = this.graph.getView().getState(cell);
+			
+			if (state != null && state.text != null && state.text.boundingBox != null)
+			{
+				var scale = this.graph.getView().scale;
+				var box = state.text.boundingBox;
+				
+				if (state.text.boundingBox.x < state.x)
+				{
+					x += (state.x - box.x) / scale;
+					result.width = box.width;
+				}
+				
+				if (state.text.boundingBox.y < state.y)
+				{
+					y += (state.y - box.y) / scale;
+					result.height = box.height;
+				}
+			}
+		}
+
+		if (this.parent != null)
+		{
+			var parent = model.getParent(cell);
+
+			if (parent != null && parent != this.parent)
+			{
+				var parentOffset = this.getParentOffset(parent);
+
+				x = x - parentOffset.x;
+				y = y - parentOffset.y;
+			}
+		}
+
+		if (geometry.x != x || geometry.y != y)
+		{
+			geometry = geometry.clone();
+			geometry.x = x;
+			geometry.y = y;
+			
+			model.setGeometry(cell, geometry);
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: getVertexBounds
+ * 
+ * Returns an <mxRectangle> that defines the bounds of the given cell or
+ * the bounding box if <useBoundingBox> is true.
+ */
+mxGraphLayout.prototype.getVertexBounds = function(cell)
+{
+	var geo = this.graph.getModel().getGeometry(cell);
+
+	// Checks for oversize label bounding box and corrects
+	// the return value accordingly
+	// TODO: Use mxUtils.getStringSize for label bounds
+	if (this.useBoundingBox)
+	{
+		var state = this.graph.getView().getState(cell);
+
+		if (state != null && state.text != null && state.text.boundingBox != null)
+		{
+			var scale = this.graph.getView().scale;
+			var tmp = state.text.boundingBox;
+
+			var dx0 = Math.max(state.x - tmp.x, 0) / scale;
+			var dy0 = Math.max(state.y - tmp.y, 0) / scale;
+			var dx1 = Math.max((tmp.x + tmp.width) - (state.x + state.width), 0) / scale;
+  			var dy1 = Math.max((tmp.y + tmp.height) - (state.y + state.height), 0) / scale;
+
+			geo = new mxRectangle(geo.x - dx0, geo.y - dy0, geo.width + dx0 + dx1, geo.height + dy0 + dy1);
+		}
+	}
+
+	if (this.parent != null)
+	{
+		var parent = this.graph.getModel().getParent(cell);
+		geo = geo.clone();
+
+		if (parent != null && parent != this.parent)
+		{
+			var parentOffset = this.getParentOffset(parent);
+			geo.x = geo.x + parentOffset.x;
+			geo.y = geo.y + parentOffset.y;
+		}
+	}
+
+	return new mxRectangle(geo.x, geo.y, geo.width, geo.height);
+};
+
+/**
+ * Function: arrangeGroups
+ * 
+ * Shortcut to <mxGraph.updateGroupBounds> with moveGroup set to true.
+ */
+mxGraphLayout.prototype.arrangeGroups = function(cells, border, topBorder, rightBorder, bottomBorder, leftBorder)
+{
+	return this.graph.updateGroupBounds(cells, border, true, topBorder, rightBorder, bottomBorder, leftBorder);
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxStackLayout
+ * 
+ * Extends <mxGraphLayout> to create a horizontal or vertical stack of the
+ * child vertices. The children do not need to be connected for this layout
+ * to work.
+ * 
+ * Example:
+ * 
+ * (code)
+ * var layout = new mxStackLayout(graph, true);
+ * layout.execute(graph.getDefaultParent());
+ * (end)
+ * 
+ * Constructor: mxStackLayout
+ * 
+ * Constructs a new stack layout layout for the specified graph,
+ * spacing, orientation and offset.
+ */
+function mxStackLayout(graph, horizontal, spacing, x0, y0, border)
+{
+	mxGraphLayout.call(this, graph);
+	this.horizontal = (horizontal != null) ? horizontal : true;
+	this.spacing = (spacing != null) ? spacing : 0;
+	this.x0 = (x0 != null) ? x0 : 0;
+	this.y0 = (y0 != null) ? y0 : 0;
+	this.border = (border != null) ? border : 0;
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxStackLayout.prototype = new mxGraphLayout();
+mxStackLayout.prototype.constructor = mxStackLayout;
+
+/**
+ * Variable: horizontal
+ *
+ * Specifies the orientation of the layout. Default is true.
+ */
+mxStackLayout.prototype.horizontal = null;
+
+/**
+ * Variable: spacing
+ *
+ * Specifies the spacing between the cells. Default is 0.
+ */
+mxStackLayout.prototype.spacing = null;
+
+/**
+ * Variable: x0
+ *
+ * Specifies the horizontal origin of the layout. Default is 0.
+ */
+mxStackLayout.prototype.x0 = null;
+
+/**
+ * Variable: y0
+ *
+ * Specifies the vertical origin of the layout. Default is 0.
+ */
+mxStackLayout.prototype.y0 = null;
+
+/**
+ * Variable: border
+ *
+ * Border to be added if fill is true. Default is 0.
+ */
+mxStackLayout.prototype.border = 0;
+
+/**
+ * Variable: marginTop
+ * 
+ * Top margin for the child area. Default is 0.
+ */
+mxStackLayout.prototype.marginTop = 0;
+
+/**
+ * Variable: marginLeft
+ * 
+ * Top margin for the child area. Default is 0.
+ */
+mxStackLayout.prototype.marginLeft = 0;
+
+/**
+ * Variable: marginRight
+ * 
+ * Top margin for the child area. Default is 0.
+ */
+mxStackLayout.prototype.marginRight = 0;
+
+/**
+ * Variable: marginBottom
+ * 
+ * Top margin for the child area. Default is 0.
+ */
+mxStackLayout.prototype.marginBottom = 0;
+
+/**
+ * Variable: keepFirstLocation
+ * 
+ * Boolean indicating if the location of the first cell should be
+ * kept, that is, it will not be moved to x0 or y0.
+ */
+mxStackLayout.prototype.keepFirstLocation = false;
+
+/**
+ * Variable: fill
+ * 
+ * Boolean indicating if dimension should be changed to fill out the parent
+ * cell. Default is false.
+ */
+mxStackLayout.prototype.fill = false;
+	
+/**
+ * Variable: resizeParent
+ * 
+ * If the parent should be resized to match the width/height of the
+ * stack. Default is false.
+ */
+mxStackLayout.prototype.resizeParent = false;
+
+/**
+ * Variable: resizeParentMax
+ * 
+ * Use maximum of existing value and new value for resize of parent.
+ * Default is false.
+ */
+mxStackLayout.prototype.resizeParentMax = false;
+
+/**
+ * Variable: resizeLast
+ * 
+ * If the last element should be resized to fill out the parent. Default is
+ * false. If <resizeParent> is true then this is ignored.
+ */
+mxStackLayout.prototype.resizeLast = false;
+
+/**
+ * Variable: wrap
+ * 
+ * Value at which a new column or row should be created. Default is null.
+ */
+mxStackLayout.prototype.wrap = null;
+
+/**
+ * Variable: borderCollapse
+ * 
+ * If the strokeWidth should be ignored. Default is true.
+ */
+mxStackLayout.prototype.borderCollapse = true;
+
+/**
+ * Function: isHorizontal
+ * 
+ * Returns <horizontal>.
+ */
+mxStackLayout.prototype.isHorizontal = function()
+{
+	return this.horizontal;
+};
+
+/**
+ * Function: moveCell
+ * 
+ * Implements <mxGraphLayout.moveCell>.
+ */
+mxStackLayout.prototype.moveCell = function(cell, x, y)
+{
+	var model = this.graph.getModel();
+	var parent = model.getParent(cell);
+	var horizontal = this.isHorizontal();
+	
+	if (cell != null && parent != null)
+	{
+		var i = 0;
+		var last = 0;
+		var childCount = model.getChildCount(parent);
+		var value = (horizontal) ? x : y;
+		var pstate = this.graph.getView().getState(parent);
+
+		if (pstate != null)
+		{
+			value -= (horizontal) ? pstate.x : pstate.y;
+		}
+		
+		value /= this.graph.view.scale;
+		
+		for (i = 0; i < childCount; i++)
+		{
+			var child = model.getChildAt(parent, i);
+			
+			if (child != cell)
+			{
+				var bounds = model.getGeometry(child);
+				
+				if (bounds != null)
+				{
+					var tmp = (horizontal) ?
+						bounds.x + bounds.width / 2 :
+						bounds.y + bounds.height / 2;
+					
+					if (last <= value && tmp > value)
+					{
+						break;
+					}
+					
+					last = tmp;
+				}
+			}
+		}
+
+		// Changes child order in parent
+		var idx = parent.getIndex(cell);
+		idx = Math.max(0, i - ((i > idx) ? 1 : 0));
+
+		model.add(parent, cell, idx);
+	}
+};
+
+/**
+ * Function: getParentSize
+ * 
+ * Returns the size for the parent container or the size of the graph
+ * container if the parent is a layer or the root of the model.
+ */
+mxStackLayout.prototype.getParentSize = function(parent)
+{
+	var model = this.graph.getModel();			
+	var pgeo = model.getGeometry(parent);
+	
+	// Handles special case where the parent is either a layer with no
+	// geometry or the current root of the view in which case the size
+	// of the graph's container will be used.
+	if (this.graph.container != null && ((pgeo == null &&
+		model.isLayer(parent)) || parent == this.graph.getView().currentRoot))
+	{
+		var width = this.graph.container.offsetWidth - 1;
+		var height = this.graph.container.offsetHeight - 1;
+		pgeo = new mxRectangle(0, 0, width, height);
+	}
+	
+	return pgeo;
+};
+
+/**
+ * Function: execute
+ * 
+ * Implements <mxGraphLayout.execute>.
+ * 
+ * Only children where <isVertexIgnored> returns false are taken into
+ * account.
+ */
+mxStackLayout.prototype.execute = function(parent)
+{
+	if (parent != null)
+	{
+		var pgeo = this.getParentSize(parent);
+		var horizontal = this.isHorizontal();
+		var model = this.graph.getModel();	
+		var fillValue = null;
+		
+		if (pgeo != null)
+		{
+			fillValue = (horizontal) ? pgeo.height - this.marginTop - this.marginBottom :
+				pgeo.width - this.marginLeft - this.marginRight;
+		}
+		
+		fillValue -= 2 * this.spacing + 2 * this.border;
+		var x0 = this.x0 + this.border + this.marginLeft;
+		var y0 = this.y0 + this.border + this.marginTop;
+		
+		// Handles swimlane start size
+		if (this.graph.isSwimlane(parent))
+		{
+			// Uses computed style to get latest 
+			var style = this.graph.getCellStyle(parent);
+			var start = mxUtils.getNumber(style, mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE);
+			var horz = mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, true) == 1;
+
+			if (pgeo != null)
+			{
+				if (horz)
+				{
+					start = Math.min(start, pgeo.height);
+				}
+				else
+				{
+					start = Math.min(start, pgeo.width);
+				}
+			}
+			
+			if (horizontal == horz)
+			{
+				fillValue -= start;
+			}
+
+			if (horz)
+			{
+				y0 += start;
+			}
+			else
+			{
+				x0 += start;
+			}
+		}
+
+		model.beginUpdate();
+		try
+		{
+			var tmp = 0;
+			var last = null;
+			var lastValue = 0;
+			var lastChild = null;
+			var 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);
+						var sw = 0;
+						
+						if (!this.borderCollapse)
+						{
+							var childStyle = this.graph.getCellStyle(child);
+							sw = mxUtils.getNumber(childStyle, mxConstants.STYLE_STROKEWIDTH, 1);
+						}
+						
+						if (last != null)
+						{
+							if (horizontal)
+							{
+								geo.x = lastValue + this.spacing + Math.floor(sw / 2);
+							}
+							else
+							{
+								geo.y = lastValue + this.spacing + Math.floor(sw / 2);
+							}
+						}
+						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 != null)
+						{
+							if (horizontal)
+							{
+								geo.height = fillValue;
+							}
+							else
+							{
+								geo.width = fillValue;									
+							}
+						}
+						
+						this.setChildGeometry(child, geo);
+						lastChild = child;
+						last = geo;
+						
+						if (horizontal)
+						{
+							lastValue = last.x + last.width + Math.floor(sw / 2);
+						}
+						else
+						{
+							lastValue = last.y + last.height + Math.floor(sw / 2);
+						}
+					}
+				}
+			}
+
+			if (this.resizeParent && pgeo != null && last != null && !this.graph.isCellCollapsed(parent))
+			{
+				this.updateParentGeometry(parent, pgeo, last);
+			}
+			else if (this.resizeLast && pgeo != null && last != null && lastChild != null)
+			{
+				if (horizontal)
+				{
+					last.width = pgeo.width - last.x - this.spacing - this.marginRight - this.marginLeft;
+				}
+				else
+				{
+					last.height = pgeo.height - last.y - this.spacing - this.marginBottom;
+				}
+				
+				this.setChildGeometry(lastChild, last);
+			}
+		}
+		finally
+		{
+			model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: execute
+ * 
+ * Implements <mxGraphLayout.execute>.
+ * 
+ * Only children where <isVertexIgnored> returns false are taken into
+ * account.
+ */
+mxStackLayout.prototype.setChildGeometry = function(child, geo)
+{
+	var geo2 = this.graph.getCellGeometry(child);
+	
+	if (geo2 == null || geo.x != geo2.x || geo.y != geo2.y ||
+		geo.width != geo2.width || geo.height != geo2.height)
+	{
+		this.graph.getModel().setGeometry(child, geo);
+	}
+};
+
+/**
+ * Function: execute
+ * 
+ * Implements <mxGraphLayout.execute>.
+ * 
+ * Only children where <isVertexIgnored> returns false are taken into
+ * account.
+ */
+mxStackLayout.prototype.updateParentGeometry = function(parent, pgeo, last)
+{
+	var horizontal = this.isHorizontal();
+	var model = this.graph.getModel();	
+
+	var pgeo2 = pgeo.clone();
+	
+	if (horizontal)
+	{
+		var tmp = last.x + last.width + this.spacing + this.marginRight;
+		
+		if (this.resizeParentMax)
+		{
+			pgeo2.width = Math.max(pgeo2.width, tmp);
+		}
+		else
+		{
+			pgeo2.width = tmp;
+		}
+	}
+	else
+	{
+		var tmp = last.y + last.height + this.spacing + this.marginBottom;
+		
+		if (this.resizeParentMax)
+		{
+			pgeo2.height = Math.max(pgeo2.height, tmp);
+		}
+		else
+		{
+			pgeo2.height = tmp;
+		}
+	}
+	
+	if (pgeo.x != pgeo2.x || pgeo.y != pgeo2.y ||
+		pgeo.width != pgeo2.width || pgeo.height != pgeo2.height)
+	{
+		model.setGeometry(parent, pgeo2);
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxPartitionLayout
+ * 
+ * Extends <mxGraphLayout> for partitioning the parent cell vertically or
+ * horizontally by filling the complete area with the child cells. A horizontal
+ * layout partitions the height of the given parent whereas a a non-horizontal
+ * layout partitions the width. If the parent is a layer (that is, a child of
+ * the root node), then the current graph size is partitioned. The children do
+ * not need to be connected for this layout to work.
+ * 
+ * Example:
+ * 
+ * (code)
+ * var layout = new mxPartitionLayout(graph, true, 10, 20);
+ * layout.execute(graph.getDefaultParent());
+ * (end)
+ * 
+ * Constructor: mxPartitionLayout
+ * 
+ * Constructs a new stack layout layout for the specified graph,
+ * spacing, orientation and offset.
+ */
+function mxPartitionLayout(graph, horizontal, spacing, border)
+{
+	mxGraphLayout.call(this, graph);
+	this.horizontal = (horizontal != null) ? horizontal : true;
+	this.spacing = spacing || 0;
+	this.border = border || 0;
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxPartitionLayout.prototype = new mxGraphLayout();
+mxPartitionLayout.prototype.constructor = mxPartitionLayout;
+
+/**
+ * Variable: horizontal
+ * 
+ * Boolean indicating the direction in which the space is partitioned.
+ * Default is true.
+ */
+mxPartitionLayout.prototype.horizontal = null;
+
+/**
+ * Variable: spacing
+ * 
+ * Integer that specifies the absolute spacing in pixels between the
+ * children. Default is 0.
+ */
+mxPartitionLayout.prototype.spacing = null;
+
+/**
+ * Variable: border
+ * 
+ * Integer that specifies the absolute inset in pixels for the parent that
+ * contains the children. Default is 0.
+ */
+mxPartitionLayout.prototype.border = null;
+
+/**
+ * Variable: resizeVertices
+ * 
+ * Boolean that specifies if vertices should be resized. Default is true.
+ */
+mxPartitionLayout.prototype.resizeVertices = true;
+
+/**
+ * Function: isHorizontal
+ * 
+ * Returns <horizontal>.
+ */
+mxPartitionLayout.prototype.isHorizontal = function()
+{
+	return this.horizontal;
+};
+
+/**
+ * Function: moveCell
+ * 
+ * Implements <mxGraphLayout.moveCell>.
+ */
+mxPartitionLayout.prototype.moveCell = function(cell, x, y)
+{
+	var model = this.graph.getModel();
+	var parent = model.getParent(cell);
+	
+	if (cell != null &&
+		parent != null)
+	{
+		var i = 0;
+		var last = 0;
+		var childCount = model.getChildCount(parent);
+		
+		// Finds index of the closest swimlane
+		// TODO: Take into account the orientation
+		for (i = 0; i < childCount; i++)
+		{
+			var child = model.getChildAt(parent, i);
+			var bounds = this.getVertexBounds(child);
+			
+			if (bounds != null)
+			{
+				var tmp = bounds.x + bounds.width / 2;
+				
+				if (last < x && tmp > x)
+				{
+					break;
+				}
+				
+				last = tmp;
+			}
+		}
+		
+		// Changes child order in parent
+		var idx = parent.getIndex(cell);
+		idx = Math.max(0, i - ((i > idx) ? 1 : 0));
+		
+		model.add(parent, cell, idx);
+	}
+};
+
+/**
+ * Function: execute
+ * 
+ * Implements <mxGraphLayout.execute>. All children where <isVertexIgnored>
+ * returns false and <isVertexMovable> returns true are modified.
+ */
+mxPartitionLayout.prototype.execute = function(parent)
+{
+	var horizontal = this.isHorizontal();
+	var model = this.graph.getModel();
+	var pgeo = model.getGeometry(parent);
+	
+	// Handles special case where the parent is either a layer with no
+	// geometry or the current root of the view in which case the size
+	// of the graph's container will be used.
+	if (this.graph.container != null &&
+		((pgeo == null &&
+		model.isLayer(parent)) ||
+		parent == this.graph.getView().currentRoot))
+	{
+		var width = this.graph.container.offsetWidth - 1;
+		var height = this.graph.container.offsetHeight - 1;
+		pgeo = new mxRectangle(0, 0, width, height);
+	}
+
+	if (pgeo != null)
+	{
+		var children = [];
+		var childCount = model.getChildCount(parent);
+		
+		for (var i = 0; i < childCount; i++)
+		{
+			var child = model.getChildAt(parent, i);
+			
+			if (!this.isVertexIgnored(child) &&
+				this.isVertexMovable(child))
+			{
+				children.push(child);
+			}
+		}
+		
+		var n = children.length;
+
+		if (n > 0)
+		{
+			var x0 = this.border;
+			var y0 = this.border;
+			var other = (horizontal) ? pgeo.height : pgeo.width;
+			other -= 2 * this.border;
+
+			var size = (this.graph.isSwimlane(parent)) ?
+				this.graph.getStartSize(parent) :
+				new mxRectangle();
+
+			other -= (horizontal) ? size.height : size.width;
+			x0 = x0 + size.width;
+			y0 = y0 + size.height;
+
+			var tmp = this.border + (n - 1) * this.spacing;
+			var value = (horizontal) ?
+				((pgeo.width - x0 - tmp) / n) :
+				((pgeo.height - y0 - tmp) / n);
+			
+			// Avoids negative values, that is values where the sum of the
+			// spacing plus the border is larger then the available space
+			if (value > 0)
+			{
+				model.beginUpdate();
+				try
+				{
+					for (var i = 0; i < n; i++)
+					{
+						var child = children[i];
+						var geo = model.getGeometry(child);
+					
+						if (geo != null)
+						{
+							geo = geo.clone();
+							geo.x = x0;
+							geo.y = y0;
+
+							if (horizontal)
+							{
+								if (this.resizeVertices)
+								{
+									geo.width = value;
+									geo.height = other;
+								}
+								
+								x0 += value + this.spacing;
+							}
+							else
+							{
+								if (this.resizeVertices)
+								{
+									geo.height = value;
+									geo.width = other;
+								}
+								
+								y0 += value + this.spacing;
+							}
+
+							model.setGeometry(child, geo);
+						}
+					}
+				}
+				finally
+				{
+					model.endUpdate();
+				}
+			}
+		}
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxCompactTreeLayout
+ * 
+ * Extends <mxGraphLayout> to implement a compact tree (Moen) algorithm. This
+ * layout is suitable for graphs that have no cycles (trees). Vertices that are
+ * not connected to the tree will be ignored by this layout.
+ * 
+ * Example:
+ * 
+ * (code)
+ * var layout = new mxCompactTreeLayout(graph);
+ * layout.execute(graph.getDefaultParent());
+ * (end)
+ * 
+ * Constructor: mxCompactTreeLayout
+ * 
+ * Constructs a new compact tree layout for the specified graph
+ * and orientation.
+ */
+function mxCompactTreeLayout(graph, horizontal, invert)
+{
+	mxGraphLayout.call(this, graph);
+	this.horizontal = (horizontal != null) ? horizontal : true;
+	this.invert = (invert != null) ? invert : false;
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxCompactTreeLayout.prototype = new mxGraphLayout();
+mxCompactTreeLayout.prototype.constructor = mxCompactTreeLayout;
+
+/**
+ * Variable: horizontal
+ *
+ * Specifies the orientation of the layout. Default is true.
+ */
+mxCompactTreeLayout.prototype.horizontal = null;	 
+
+/**
+ * Variable: invert
+ *
+ * Specifies if edge directions should be inverted. Default is false.
+ */
+mxCompactTreeLayout.prototype.invert = null;	 
+
+/**
+ * Variable: resizeParent
+ * 
+ * If the parents should be resized to match the width/height of the
+ * children. Default is true.
+ */
+mxCompactTreeLayout.prototype.resizeParent = true;
+
+/**
+ * Variable: maintainParentLocation
+ * 
+ * Specifies if the parent location should be maintained, so that the
+ * top, left corner stays the same before and after execution of
+ * the layout. Default is false for backwards compatibility.
+ */
+mxCompactTreeLayout.prototype.maintainParentLocation = false;
+
+/**
+ * Variable: groupPadding
+ * 
+ * Padding added to resized parents. Default is 10.
+ */
+mxCompactTreeLayout.prototype.groupPadding = 10;
+
+/**
+ * Variable: groupPaddingTop
+ * 
+ * Top padding added to resized parents. Default is 0.
+ */
+mxCompactTreeLayout.prototype.groupPaddingTop = 0;
+
+/**
+ * Variable: groupPaddingRight
+ * 
+ * Right padding added to resized parents. Default is 0.
+ */
+mxCompactTreeLayout.prototype.groupPaddingRight = 0;
+
+/**
+ * Variable: groupPaddingBottom
+ * 
+ * Bottom padding added to resized parents. Default is 0.
+ */
+mxCompactTreeLayout.prototype.groupPaddingBottom = 0;
+
+/**
+ * Variable: groupPaddingLeft
+ * 
+ * Left padding added to resized parents. Default is 0.
+ */
+mxCompactTreeLayout.prototype.groupPaddingLeft = 0;
+
+/**
+ * Variable: parentsChanged
+ *
+ * A set of the parents that need updating based on children
+ * process as part of the layout.
+ */
+mxCompactTreeLayout.prototype.parentsChanged = null;
+
+/**
+ * Variable: moveTree
+ * 
+ * Specifies if the tree should be moved to the top, left corner
+ * if it is inside a top-level layer. Default is false.
+ */
+mxCompactTreeLayout.prototype.moveTree = false;
+
+/**
+ * Variable: visited
+ * 
+ * Specifies if the tree should be moved to the top, left corner
+ * if it is inside a top-level layer. Default is false.
+ */
+mxCompactTreeLayout.prototype.visited = null;
+
+/**
+ * Variable: levelDistance
+ *
+ * Holds the levelDistance. Default is 10.
+ */
+mxCompactTreeLayout.prototype.levelDistance = 10;
+
+/**
+ * Variable: nodeDistance
+ *
+ * Holds the nodeDistance. Default is 20.
+ */
+mxCompactTreeLayout.prototype.nodeDistance = 20;
+
+/**
+ * Variable: resetEdges
+ * 
+ * Specifies if all edge points of traversed edges should be removed.
+ * Default is true.
+ */
+mxCompactTreeLayout.prototype.resetEdges = true;
+
+/**
+ * Variable: prefHozEdgeSep
+ * 
+ * The preferred horizontal distance between edges exiting a vertex.
+ */
+mxCompactTreeLayout.prototype.prefHozEdgeSep = 5;
+
+/**
+ * Variable: prefVertEdgeOff
+ * 
+ * The preferred vertical offset between edges exiting a vertex.
+ */
+mxCompactTreeLayout.prototype.prefVertEdgeOff = 4;
+
+/**
+ * Variable: minEdgeJetty
+ * 
+ * The minimum distance for an edge jetty from a vertex.
+ */
+mxCompactTreeLayout.prototype.minEdgeJetty = 8;
+
+/**
+ * Variable: channelBuffer
+ * 
+ * The size of the vertical buffer in the center of inter-rank channels
+ * where edge control points should not be placed.
+ */
+mxCompactTreeLayout.prototype.channelBuffer = 4;
+
+/**
+ * Variable: edgeRouting
+ * 
+ * Whether or not to apply the internal tree edge routing.
+ */
+mxCompactTreeLayout.prototype.edgeRouting = true;
+
+/**
+ * Variable: sortEdges
+ * 
+ * Specifies if edges should be sorted according to the order of their
+ * opposite terminal cell in the model.
+ */
+mxCompactTreeLayout.prototype.sortEdges = false;
+
+/**
+ * Variable: alignRanks
+ * 
+ * Whether or not the tops of cells in each rank should be aligned
+ * across the rank
+ */
+mxCompactTreeLayout.prototype.alignRanks = false;
+
+/**
+ * Variable: maxRankHeight
+ * 
+ * An array of the maximum height of cells (relative to the layout direction)
+ * per rank
+ */
+mxCompactTreeLayout.prototype.maxRankHeight = null;
+
+/**
+ * Variable: root
+ * 
+ * The cell to use as the root of the tree
+ */
+mxCompactTreeLayout.prototype.root = null;
+
+/**
+ * Variable: node
+ * 
+ * The internal node representation of the root cell. Do not set directly
+ * , this value is only exposed to assist with post-processing functionality
+ */
+mxCompactTreeLayout.prototype.node = null;
+
+/**
+ * Function: isVertexIgnored
+ * 
+ * Returns a boolean indicating if the given <mxCell> should be ignored as a
+ * vertex. This returns true if the cell has no connections.
+ * 
+ * Parameters:
+ * 
+ * vertex - <mxCell> whose ignored state should be returned.
+ */
+mxCompactTreeLayout.prototype.isVertexIgnored = function(vertex)
+{
+	return mxGraphLayout.prototype.isVertexIgnored.apply(this, arguments) ||
+		this.graph.getConnections(vertex).length == 0;
+};
+
+/**
+ * Function: isHorizontal
+ * 
+ * Returns <horizontal>.
+ */
+mxCompactTreeLayout.prototype.isHorizontal = function()
+{
+	return this.horizontal;
+};
+
+/**
+ * Function: execute
+ * 
+ * Implements <mxGraphLayout.execute>.
+ * 
+ * If the parent has any connected edges, then it is used as the root of
+ * the tree. Else, <mxGraph.findTreeRoots> will be used to find a suitable
+ * root node within the set of children of the given parent.
+ * 
+ * Parameters:
+ * 
+ * parent - <mxCell> whose children should be laid out.
+ * root - Optional <mxCell> that will be used as the root of the tree.
+ * Overrides <root> if specified.
+ */
+mxCompactTreeLayout.prototype.execute = function(parent, root)
+{
+	this.parent = parent;
+	var model = this.graph.getModel();
+
+	if (root == null)
+	{
+		// Takes the parent as the root if it has outgoing edges
+		if (this.graph.getEdges(parent, model.getParent(parent),
+			this.invert, !this.invert, false).length > 0)
+		{
+			this.root = parent;
+		}
+		
+		// Tries to find a suitable root in the parent's
+		// children
+		else
+		{
+			var roots = this.graph.findTreeRoots(parent, true, this.invert);
+			
+			if (roots.length > 0)
+			{
+				for (var i = 0; i < roots.length; i++)
+				{
+					if (!this.isVertexIgnored(roots[i]) &&
+						this.graph.getEdges(roots[i], null,
+							this.invert, !this.invert, false).length > 0)
+					{
+						this.root = roots[i];
+						break;
+					}
+				}
+			}
+		}
+	}
+	else
+	{
+		this.root = root;
+	}
+	
+	if (this.root != null)
+	{
+		if (this.resizeParent)
+		{
+			this.parentsChanged = new Object();
+		}
+		else
+		{
+			this.parentsChanged = null;
+		}
+
+		//  Maintaining parent location
+		this.parentX = null;
+		this.parentY = null;
+		
+		if (parent != this.root && model.isVertex(parent) != null && this.maintainParentLocation)
+		{
+			var geo = this.graph.getCellGeometry(parent);
+			
+			if (geo != null)
+			{
+				this.parentX = geo.x;
+				this.parentY = geo.y;
+			}
+		}
+		
+		model.beginUpdate();
+		
+		try
+		{
+			this.visited = new Object();
+			this.node = this.dfs(this.root, parent);
+			
+			if (this.alignRanks)
+			{
+				this.maxRankHeight = [];
+				this.findRankHeights(this.node, 0);
+				this.setCellHeights(this.node, 0);
+			}
+			
+			if (this.node != null)
+			{
+				this.layout(this.node);
+				var x0 = this.graph.gridSize;
+				var y0 = x0;
+				
+				if (!this.moveTree)
+				{
+					var g = this.getVertexBounds(this.root);
+					
+					if (g != null)
+					{
+						x0 = g.x;
+						y0 = g.y;
+					}
+				}
+				
+				var bounds = null;
+				
+				if (this.isHorizontal())
+				{
+					bounds = this.horizontalLayout(this.node, x0, y0);
+				}
+				else
+				{
+					bounds = this.verticalLayout(this.node, null, x0, y0);
+				}
+
+				if (bounds != null)
+				{
+					var dx = 0;
+					var dy = 0;
+
+					if (bounds.x < 0)
+					{
+						dx = Math.abs(x0 - bounds.x);
+					}
+
+					if (bounds.y < 0)
+					{
+						dy = Math.abs(y0 - bounds.y);	
+					}
+
+					if (dx != 0 || dy != 0)
+					{
+						this.moveNode(this.node, dx, dy);
+					}
+					
+					if (this.resizeParent)
+					{
+						this.adjustParents();
+					}
+
+					if (this.edgeRouting)
+					{
+						// Iterate through all edges setting their positions
+						this.localEdgeProcessing(this.node);
+					}
+				}
+				
+				// Maintaining parent location
+				if (this.parentX != null && this.parentY != null)
+				{
+					var geo = this.graph.getCellGeometry(parent);
+					
+					if (geo != null)
+					{
+						geo = geo.clone();
+						geo.x = this.parentX;
+						geo.y = this.parentY;
+						model.setGeometry(parent, geo);
+					}
+				}
+			}
+		}
+		finally
+		{
+			model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: moveNode
+ * 
+ * Moves the specified node and all of its children by the given amount.
+ */
+mxCompactTreeLayout.prototype.moveNode = function(node, dx, dy)
+{
+	node.x += dx;
+	node.y += dy;
+	this.apply(node);
+	
+	var child = node.child;
+	
+	while (child != null)
+	{
+		this.moveNode(child, dx, dy);
+		child = child.next;
+	}
+};
+
+
+/**
+ * Function: sortOutgoingEdges
+ * 
+ * Called if <sortEdges> is true to sort the array of outgoing edges in place.
+ */
+mxCompactTreeLayout.prototype.sortOutgoingEdges = function(source, edges)
+{
+	var lookup = new mxDictionary();
+	
+	edges.sort(function(e1, e2)
+	{
+		var end1 = e1.getTerminal(e1.getTerminal(false) == source);
+		var p1 = lookup.get(end1);
+		
+		if (p1 == null)
+		{
+			p1 = mxCellPath.create(end1).split(mxCellPath.PATH_SEPARATOR);
+			lookup.put(end1, p1);
+		}
+
+		var end2 = e2.getTerminal(e2.getTerminal(false) == source);
+		var p2 = lookup.get(end2);
+		
+		if (p2 == null)
+		{
+			p2 = mxCellPath.create(end2).split(mxCellPath.PATH_SEPARATOR);
+			lookup.put(end2, p2);
+		}
+
+		return mxCellPath.compare(p1, p2);
+	});
+};
+
+/**
+ * Function: findRankHeights
+ * 
+ * Stores the maximum height (relative to the layout
+ * direction) of cells in each rank
+ */
+mxCompactTreeLayout.prototype.findRankHeights = function(node, rank)
+{
+	if (this.maxRankHeight[rank] == null || this.maxRankHeight[rank] < node.height)
+	{
+		this.maxRankHeight[rank] = node.height;
+	}
+
+	var child = node.child;
+	
+	while (child != null)
+	{
+		this.findRankHeights(child, rank + 1);
+		child = child.next;
+	}
+};
+
+/**
+ * Function: setCellHeights
+ * 
+ * Set the cells heights (relative to the layout
+ * direction) when the tops of each rank are to be aligned
+ */
+mxCompactTreeLayout.prototype.setCellHeights = function(node, rank)
+{
+	if (this.maxRankHeight[rank] != null && this.maxRankHeight[rank] > node.height)
+	{
+		node.height = this.maxRankHeight[rank];
+	}
+
+	var child = node.child;
+	
+	while (child != null)
+	{
+		this.setCellHeights(child, rank + 1);
+		child = child.next;
+	}
+};
+
+/**
+ * Function: dfs
+ * 
+ * Does a depth first search starting at the specified cell.
+ * Makes sure the specified parent is never left by the
+ * algorithm.
+ */
+mxCompactTreeLayout.prototype.dfs = function(cell, parent)
+{
+	var id = mxCellPath.create(cell);
+	var node = null;
+	
+	if (cell != null && this.visited[id] == null && !this.isVertexIgnored(cell))
+	{
+		this.visited[id] = cell;
+		node = this.createNode(cell);
+
+		var model = this.graph.getModel();
+		var prev = null;
+		var out = this.graph.getEdges(cell, parent, this.invert, !this.invert, false, true);
+		var view = this.graph.getView();
+		
+		if (this.sortEdges)
+		{
+			this.sortOutgoingEdges(cell, out);
+		}
+
+		for (var i = 0; i < out.length; i++)
+		{
+			var edge = out[i];
+			
+			if (!this.isEdgeIgnored(edge))
+			{
+				// Resets the points on the traversed edge
+				if (this.resetEdges)
+				{
+					this.setEdgePoints(edge, null);
+				}
+				
+				if (this.edgeRouting)
+				{
+					this.setEdgeStyleEnabled(edge, false);
+					this.setEdgePoints(edge, null);
+				}
+				
+				// Checks if terminal in same swimlane
+				var state = view.getState(edge);
+				var target = (state != null) ? state.getVisibleTerminal(this.invert) : view.getVisibleTerminal(edge, this.invert);
+				var tmp = this.dfs(target, parent);
+				
+				if (tmp != null && model.getGeometry(target) != null)
+				{
+					if (prev == null)
+					{
+						node.child = tmp;
+					}
+					else
+					{
+						prev.next = tmp;
+					}
+					
+					prev = tmp;
+				}
+			}
+		}
+	}
+	
+	return node;
+};
+
+/**
+ * Function: layout
+ * 
+ * Starts the actual compact tree layout algorithm
+ * at the given node.
+ */
+mxCompactTreeLayout.prototype.layout = function(node)
+{
+	if (node != null)
+	{
+		var child = node.child;
+		
+		while (child != null)
+		{
+			this.layout(child);
+			child = child.next;
+		}
+		
+		if (node.child != null)
+		{
+			this.attachParent(node, this.join(node));
+		}
+		else
+		{
+			this.layoutLeaf(node);
+		}
+	}
+};
+
+/**
+ * Function: horizontalLayout
+ */
+mxCompactTreeLayout.prototype.horizontalLayout = function(node, x0, y0, bounds)
+{
+	node.x += x0 + node.offsetX;
+	node.y += y0 + node.offsetY;
+	bounds = this.apply(node, bounds);
+	var child = node.child;
+	
+	if (child != null)
+	{
+		bounds = this.horizontalLayout(child, node.x, node.y, bounds);
+		var siblingOffset = node.y + child.offsetY;
+		var s = child.next;
+		
+		while (s != null)
+		{
+			bounds = this.horizontalLayout(s, node.x + child.offsetX, siblingOffset, bounds);
+			siblingOffset += s.offsetY;
+			s = s.next;
+		}
+	}
+	
+	return bounds;
+};
+	
+/**
+ * Function: verticalLayout
+ */
+mxCompactTreeLayout.prototype.verticalLayout = function(node, parent, x0, y0, bounds)
+{
+	node.x += x0 + node.offsetY;
+	node.y += y0 + node.offsetX;
+	bounds = this.apply(node, bounds);
+	var child = node.child;
+	
+	if (child != null)
+	{
+		bounds = this.verticalLayout(child, node, node.x, node.y, bounds);
+		var siblingOffset = node.x + child.offsetY;
+		var s = child.next;
+		
+		while (s != null)
+		{
+			bounds = this.verticalLayout(s, node, siblingOffset, node.y + child.offsetX, bounds);
+			siblingOffset += s.offsetY;
+			s = s.next;
+		}
+	}
+	
+	return bounds;
+};
+
+/**
+ * Function: attachParent
+ */
+mxCompactTreeLayout.prototype.attachParent = function(node, height)
+{
+	var x = this.nodeDistance + this.levelDistance;
+	var y2 = (height - node.width) / 2 - this.nodeDistance;
+	var y1 = y2 + node.width + 2 * this.nodeDistance - height;
+	
+	node.child.offsetX = x + node.height;
+	node.child.offsetY = y1;
+	
+	node.contour.upperHead = this.createLine(node.height, 0,
+		this.createLine(x, y1, node.contour.upperHead));
+	node.contour.lowerHead = this.createLine(node.height, 0,
+		this.createLine(x, y2, node.contour.lowerHead));
+};
+
+/**
+ * Function: layoutLeaf
+ */
+mxCompactTreeLayout.prototype.layoutLeaf = function(node)
+{
+	var dist = 2 * this.nodeDistance;
+	
+	node.contour.upperTail = this.createLine(
+		node.height + dist, 0);
+	node.contour.upperHead = node.contour.upperTail;
+	node.contour.lowerTail = this.createLine(
+		0, -node.width - dist);
+	node.contour.lowerHead = this.createLine(
+		node.height + dist, 0, node.contour.lowerTail);
+};
+
+/**
+ * Function: join
+ */
+mxCompactTreeLayout.prototype.join = function(node)
+{
+	var dist = 2 * this.nodeDistance;
+	
+	var child = node.child;
+	node.contour = child.contour;
+	var h = child.width + dist;
+	var sum = h;
+	child = child.next;
+	
+	while (child != null)
+	{
+		var d = this.merge(node.contour, child.contour);
+		child.offsetY = d + h;
+		child.offsetX = 0;
+		h = child.width + dist;
+		sum += d + h;
+		child = child.next;
+	}
+	
+	return sum;
+};
+
+/**
+ * Function: merge
+ */
+mxCompactTreeLayout.prototype.merge = function(p1, p2)
+{
+	var x = 0;
+	var y = 0;
+	var total = 0;
+	
+	var upper = p1.lowerHead;
+	var lower = p2.upperHead;
+	
+	while (lower != null && upper != null)
+	{
+		var d = this.offset(x, y, lower.dx, lower.dy,
+			upper.dx, upper.dy);
+		y += d;
+		total += d;
+		
+		if (x + lower.dx <= upper.dx)
+		{
+			x += lower.dx;
+			y += lower.dy;
+			lower = lower.next;
+		}
+		else
+		{				
+			x -= upper.dx;
+			y -= upper.dy;
+			upper = upper.next;
+		}
+	}
+	
+	if (lower != null)
+	{
+		var b = this.bridge(p1.upperTail, 0, 0, lower, x, y);
+		p1.upperTail = (b.next != null) ? p2.upperTail : b;
+		p1.lowerTail = p2.lowerTail;
+	}
+	else
+	{
+		var b = this.bridge(p2.lowerTail, x, y, upper, 0, 0);
+		
+		if (b.next == null)
+		{
+			p1.lowerTail = b;
+		}
+	}
+	
+	p1.lowerHead = p2.lowerHead;
+	
+	return total;
+};
+
+/**
+ * Function: offset
+ */
+mxCompactTreeLayout.prototype.offset = function(p1, p2, a1, a2, b1, b2)
+{
+	var d = 0;
+	
+	if (b1 <= p1 || p1 + a1 <= 0)
+	{
+		return 0;
+	}
+
+	var t = b1 * a2 - a1 * b2;
+	
+	if (t > 0)
+	{
+		if (p1 < 0)
+		{
+			var s = p1 * a2;
+			d = s / a1 - p2;
+		}
+		else if (p1 > 0)
+		{
+			var s = p1 * b2;
+			d = s / b1 - p2;
+		}
+		else
+		{
+			d = -p2;
+		}
+	}
+	else if (b1 < p1 + a1)
+	{
+		var s = (b1 - p1) * a2;
+		d = b2 - (p2 + s / a1);
+	}
+	else if (b1 > p1 + a1)
+	{
+		var s = (a1 + p1) * b2;
+		d = s / b1 - (p2 + a2);
+	}
+	else
+	{
+		d = b2 - (p2 + a2);
+	}
+
+	if (d > 0)
+	{
+		return d;
+	}
+	else
+	{
+		return 0;
+	}
+};
+
+/**
+ * Function: bridge
+ */
+mxCompactTreeLayout.prototype.bridge = function(line1, x1, y1, line2, x2, y2)
+{
+	var dx = x2 + line2.dx - x1;
+	var dy = 0;
+	var s = 0;
+	
+	if (line2.dx == 0)
+	{
+		dy = line2.dy;
+	}
+	else
+	{
+		s = dx * line2.dy;
+		dy = s / line2.dx;
+	}
+	
+	var r = this.createLine(dx, dy, line2.next);
+	line1.next = this.createLine(0, y2 + line2.dy - dy - y1, r);
+	
+	return r;
+};
+
+/**
+ * Function: createNode
+ */
+mxCompactTreeLayout.prototype.createNode = function(cell)
+{
+	var node = new Object();
+	node.cell = cell;
+	node.x = 0;
+	node.y = 0;
+	node.width = 0;
+	node.height = 0;
+	
+	var geo = this.getVertexBounds(cell);
+	
+	if (geo != null)
+	{
+		if (this.isHorizontal())
+		{
+			node.width = geo.height;
+			node.height = geo.width;			
+		}
+		else
+		{
+			node.width = geo.width;
+			node.height = geo.height;
+		}
+	}
+	
+	node.offsetX = 0;
+	node.offsetY = 0;
+	node.contour = new Object();
+	
+	return node;
+};
+
+/**
+ * Function: apply
+ */
+mxCompactTreeLayout.prototype.apply = function(node, bounds)
+{
+	var model = this.graph.getModel();
+	var cell = node.cell;
+	var g = model.getGeometry(cell);
+
+	if (cell != null && g != null)
+	{
+		if (this.isVertexMovable(cell))
+		{
+			g = this.setVertexLocation(cell, node.x, node.y);
+			
+			if (this.resizeParent)
+			{
+				var parent = model.getParent(cell);
+				var id = mxCellPath.create(parent);
+				
+				// Implements set semantic
+				if (this.parentsChanged[id] == null)
+				{
+					this.parentsChanged[id] = parent;					
+				}
+			}
+		}
+		
+		if (bounds == null)
+		{
+			bounds = new mxRectangle(g.x, g.y, g.width, g.height);
+		}
+		else
+		{
+			bounds = new mxRectangle(Math.min(bounds.x, g.x),
+				Math.min(bounds.y, g.y),
+				Math.max(bounds.x + bounds.width, g.x + g.width),
+				Math.max(bounds.y + bounds.height, g.y + g.height));
+		}
+	}
+	
+	return bounds;
+};
+
+/**
+ * Function: createLine
+ */
+mxCompactTreeLayout.prototype.createLine = function(dx, dy, next)
+{
+	var line = new Object();
+	line.dx = dx;
+	line.dy = dy;
+	line.next = next;
+	
+	return line;
+};
+
+/**
+ * Function: adjustParents
+ * 
+ * Adjust parent cells whose child geometries have changed. The default 
+ * implementation adjusts the group to just fit around the children with 
+ * a padding.
+ */
+mxCompactTreeLayout.prototype.adjustParents = function()
+{
+	var tmp = [];
+	
+	for (var id in this.parentsChanged)
+	{
+		tmp.push(this.parentsChanged[id]);
+	}
+	
+	this.arrangeGroups(mxUtils.sortCells(tmp, true), this.groupPadding, this.groupPaddingTop,
+		this.groupPaddingRight, this.groupPaddingBottom, this.groupPaddingLeft);
+};
+
+/**
+ * Function: localEdgeProcessing
+ *
+ * Moves the specified node and all of its children by the given amount.
+ */
+mxCompactTreeLayout.prototype.localEdgeProcessing = function(node)
+{
+	this.processNodeOutgoing(node);
+	var child = node.child;
+
+	while (child != null)
+	{
+		this.localEdgeProcessing(child);
+		child = child.next;
+	}
+};
+
+/**
+ * Function: 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;
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxRadialTreeLayout
+ * 
+ * Extends <mxGraphLayout> to implement a radial tree algorithm. This
+ * layout is suitable for graphs that have no cycles (trees). Vertices that are
+ * not connected to the tree will be ignored by this layout.
+ * 
+ * Example:
+ * 
+ * (code)
+ * var layout = new mxRadialTreeLayout(graph);
+ * layout.execute(graph.getDefaultParent());
+ * (end)
+ * 
+ * Constructor: mxRadialTreeLayout
+ * 
+ * Constructs a new radial tree layout for the specified graph
+ */
+function mxRadialTreeLayout(graph)
+{
+	mxCompactTreeLayout.call(this, graph , false);
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxUtils.extend(mxRadialTreeLayout, mxCompactTreeLayout);
+
+/**
+ * Variable: angleOffset
+ *
+ * The initial offset to compute the angle position.
+ */
+mxRadialTreeLayout.prototype.angleOffset = 0.5;
+
+/**
+ * Variable: rootx
+ *
+ * The X co-ordinate of the root cell
+ */
+mxRadialTreeLayout.prototype.rootx = 0;
+
+/**
+ * Variable: rooty
+ *
+ * The Y co-ordinate of the root cell
+ */
+mxRadialTreeLayout.prototype.rooty = 0;
+
+/**
+ * Variable: levelDistance
+ *
+ * Holds the levelDistance. Default is 120.
+ */
+mxRadialTreeLayout.prototype.levelDistance = 120;
+
+/**
+ * Variable: nodeDistance
+ *
+ * Holds the nodeDistance. Default is 10.
+ */
+mxRadialTreeLayout.prototype.nodeDistance = 10;
+
+/**
+ * Variable: autoRadius
+ * 
+ * Specifies if the radios should be computed automatically
+ */
+mxRadialTreeLayout.prototype.autoRadius = false;
+
+/**
+ * Variable: sortEdges
+ * 
+ * Specifies if edges should be sorted according to the order of their
+ * opposite terminal cell in the model.
+ */
+mxRadialTreeLayout.prototype.sortEdges = false;
+
+/**
+ * Variable: rowMinX
+ * 
+ * Array of leftmost x coordinate of each row
+ */
+mxRadialTreeLayout.prototype.rowMinX = [];
+
+/**
+ * Variable: rowMaxX
+ * 
+ * Array of rightmost x coordinate of each row
+ */
+mxRadialTreeLayout.prototype.rowMaxX = [];
+
+/**
+ * Variable: rowMinCenX
+ * 
+ * Array of x coordinate of leftmost vertex of each row
+ */
+mxRadialTreeLayout.prototype.rowMinCenX = [];
+
+/**
+ * Variable: rowMaxCenX
+ * 
+ * Array of x coordinate of rightmost vertex of each row
+ */
+mxRadialTreeLayout.prototype.rowMaxCenX = [];
+
+/**
+ * Variable: rowRadi
+ * 
+ * Array of y deltas of each row behind root vertex, also the radius in the tree
+ */
+mxRadialTreeLayout.prototype.rowRadi = [];
+
+/**
+ * Variable: row
+ * 
+ * Array of vertices on each row
+ */
+mxRadialTreeLayout.prototype.row = [];
+
+/**
+ * Function: isVertexIgnored
+ * 
+ * Returns a boolean indicating if the given <mxCell> should be ignored as a
+ * vertex. This returns true if the cell has no connections.
+ * 
+ * Parameters:
+ * 
+ * vertex - <mxCell> whose ignored state should be returned.
+ */
+mxRadialTreeLayout.prototype.isVertexIgnored = function(vertex)
+{
+	return mxGraphLayout.prototype.isVertexIgnored.apply(this, arguments) ||
+		this.graph.getConnections(vertex).length == 0;
+};
+
+/**
+ * Function: execute
+ * 
+ * Implements <mxGraphLayout.execute>.
+ * 
+ * If the parent has any connected edges, then it is used as the root of
+ * the tree. Else, <mxGraph.findTreeRoots> will be used to find a suitable
+ * root node within the set of children of the given parent.
+ * 
+ * Parameters:
+ * 
+ * parent - <mxCell> whose children should be laid out.
+ * root - Optional <mxCell> that will be used as the root of the tree.
+ */
+mxRadialTreeLayout.prototype.execute = function(parent, root)
+{
+	this.parent = parent;
+	
+	this.useBoundingBox = false;
+	this.edgeRouting = false;
+	//this.horizontal = false;
+
+	mxCompactTreeLayout.prototype.execute.apply(this, arguments);
+	
+	var bounds = null;
+	var rootBounds = this.getVertexBounds(this.root);
+	this.centerX = rootBounds.x + rootBounds.width / 2;
+	this.centerY = rootBounds.y + rootBounds.height / 2;
+
+	// Calculate the bounds of the involved vertices directly from the values set in the compact tree
+	for (var vertex in this.visited)
+	{
+		var vertexBounds = this.getVertexBounds(this.visited[vertex]);
+		bounds = (bounds != null) ? bounds : vertexBounds.clone();
+		bounds.add(vertexBounds);
+	}
+	
+	this.calcRowDims([this.node], 0);
+	
+	var maxLeftGrad = 0;
+	var maxRightGrad = 0;
+
+	// Find the steepest left and right gradients
+	for (var i = 0; i < this.row.length; i++)
+	{
+		var leftGrad = (this.centerX - this.rowMinX[i] - this.nodeDistance) / this.rowRadi[i];
+		var rightGrad = (this.rowMaxX[i] - this.centerX - this.nodeDistance) / this.rowRadi[i];
+		
+		maxLeftGrad = Math.max (maxLeftGrad, leftGrad);
+		maxRightGrad = Math.max (maxRightGrad, rightGrad);
+	}
+	
+	// Extend out row so they meet the maximum gradient and convert to polar co-ords
+	for (var i = 0; i < this.row.length; i++)
+	{
+		var xLeftLimit = this.centerX - this.nodeDistance - maxLeftGrad * this.rowRadi[i];
+		var xRightLimit = this.centerX + this.nodeDistance + maxRightGrad * this.rowRadi[i];
+		var fullWidth = xRightLimit - xLeftLimit;
+		
+		for (var j = 0; j < this.row[i].length; j ++)
+		{
+			var row = this.row[i];
+			var node = row[j];
+			var vertexBounds = this.getVertexBounds(node.cell);
+			var xProportion = (vertexBounds.x + vertexBounds.width / 2 - xLeftLimit) / (fullWidth);
+			var theta =  2 * Math.PI * xProportion;
+			node.theta = theta;
+		}
+	}
+
+	// Post-process from outside inwards to try to align parents with children
+	for (var i = this.row.length - 2; i >= 0; i--)
+	{
+		var row = this.row[i];
+		
+		for (var j = 0; j < row.length; j++)
+		{
+			var node = row[j];
+			var child = node.child;
+			var counter = 0;
+			var totalTheta = 0;
+			
+			while (child != null)
+			{
+				totalTheta += child.theta;
+				counter++;
+				child = child.next;
+			}
+			
+			if (counter > 0)
+			{
+				var averTheta = totalTheta / counter;
+				
+				if (averTheta > node.theta && j < row.length - 1)
+				{
+					var nextTheta = row[j+1].theta;
+					node.theta = Math.min (averTheta, nextTheta - Math.PI/10);
+				}
+				else if (averTheta < node.theta && j > 0 )
+				{
+					var lastTheta = row[j-1].theta;
+					node.theta = Math.max (averTheta, lastTheta + Math.PI/10);
+				}
+			}
+		}
+	}
+	
+	// Set locations
+	for (var i = 0; i < this.row.length; i++)
+	{
+		for (var j = 0; j < this.row[i].length; j ++)
+		{
+			var row = this.row[i];
+			var node = row[j];
+			var vertexBounds = this.getVertexBounds(node.cell);
+			this.setVertexLocation(node.cell,
+									this.centerX - vertexBounds.width / 2 + this.rowRadi[i] * Math.cos(node.theta),
+									this.centerY - vertexBounds.height / 2 + this.rowRadi[i] * Math.sin(node.theta));
+		}
+	}
+};
+
+/**
+ * Function: calcRowDims
+ * 
+ * Recursive function to calculate the dimensions of each row
+ * 
+ * Parameters:
+ * 
+ * row - Array of internal nodes, the children of which are to be processed.
+ * rowNum - Integer indicating which row is being processed.
+ */
+mxRadialTreeLayout.prototype.calcRowDims = function(row, rowNum)
+{
+	if (row == null || row.length == 0)
+	{
+		return;
+	}
+
+	// Place root's children proportionally around the first level
+	this.rowMinX[rowNum] = this.centerX;
+	this.rowMaxX[rowNum] = this.centerX;
+	this.rowMinCenX[rowNum] = this.centerX;
+	this.rowMaxCenX[rowNum] = this.centerX;
+	this.row[rowNum] = [];
+
+	var rowHasChildren = false;
+
+	for (var i = 0; i < row.length; i++)
+	{
+		var child = row[i] != null ? row[i].child : null;
+
+		while (child != null)
+		{
+			var cell = child.cell;
+			vertexBounds = this.getVertexBounds(cell);
+			
+			this.rowMinX[rowNum] = Math.min(vertexBounds.x, this.rowMinX[rowNum]);
+			this.rowMaxX[rowNum] = Math.max(vertexBounds.x + vertexBounds.width, this.rowMaxX[rowNum]);
+			this.rowMinCenX[rowNum] = Math.min(vertexBounds.x + vertexBounds.width / 2, this.rowMinCenX[rowNum]);
+			this.rowMaxCenX[rowNum] = Math.max(vertexBounds.x + vertexBounds.width / 2, this.rowMaxCenX[rowNum]);
+			this.rowRadi[rowNum] = vertexBounds.y - this.getVertexBounds(this.root).y;
+	
+			if (child.child != null)
+			{
+				rowHasChildren = true;
+			}
+			this.row[rowNum].push(child);
+			child = child.next;
+		}
+	}
+	
+	if (rowHasChildren)
+	{
+		this.calcRowDims(this.row[rowNum], rowNum + 1);
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxFastOrganicLayout
+ * 
+ * Extends <mxGraphLayout> to implement a fast organic layout algorithm.
+ * The vertices need to be connected for this layout to work, vertices
+ * with no connections are ignored.
+ * 
+ * Example:
+ * 
+ * (code)
+ * var layout = new mxFastOrganicLayout(graph);
+ * layout.execute(graph.getDefaultParent());
+ * (end)
+ * 
+ * Constructor: mxCompactTreeLayout
+ * 
+ * Constructs a new fast organic layout for the specified graph.
+ */
+function mxFastOrganicLayout(graph)
+{
+	mxGraphLayout.call(this, graph);
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxFastOrganicLayout.prototype = new mxGraphLayout();
+mxFastOrganicLayout.prototype.constructor = mxFastOrganicLayout;
+
+/**
+ * Variable: useInputOrigin
+ * 
+ * Specifies if the top left corner of the input cells should be the origin
+ * of the layout result. Default is true.
+ */
+mxFastOrganicLayout.prototype.useInputOrigin = true;
+
+/**
+ * Variable: resetEdges
+ * 
+ * Specifies if all edge points of traversed edges should be removed.
+ * Default is true.
+ */
+mxFastOrganicLayout.prototype.resetEdges = true;
+
+/**
+ * Variable: disableEdgeStyle
+ * 
+ * Specifies if the STYLE_NOEDGESTYLE flag should be set on edges that are
+ * modified by the result. Default is true.
+ */
+mxFastOrganicLayout.prototype.disableEdgeStyle = true;
+
+/**
+ * Variable: forceConstant
+ * 
+ * The force constant by which the attractive forces are divided and the
+ * replusive forces are multiple by the square of. The value equates to the
+ * average radius there is of free space around each node. Default is 50.
+ */
+mxFastOrganicLayout.prototype.forceConstant = 50;
+
+/**
+ * Variable: forceConstantSquared
+ * 
+ * Cache of <forceConstant>^2 for performance.
+ */
+mxFastOrganicLayout.prototype.forceConstantSquared = 0;
+
+/**
+ * Variable: minDistanceLimit
+ * 
+ * Minimal distance limit. Default is 2. Prevents of
+ * dividing by zero.
+ */
+mxFastOrganicLayout.prototype.minDistanceLimit = 2;
+
+/**
+ * Variable: minDistanceLimit
+ * 
+ * Minimal distance limit. Default is 2. Prevents of
+ * dividing by zero.
+ */
+mxFastOrganicLayout.prototype.maxDistanceLimit = 500;
+
+/**
+ * Variable: minDistanceLimitSquared
+ * 
+ * Cached version of <minDistanceLimit> squared.
+ */
+mxFastOrganicLayout.prototype.minDistanceLimitSquared = 4;
+
+/**
+ * Variable: initialTemp
+ * 
+ * Start value of temperature. Default is 200.
+ */
+mxFastOrganicLayout.prototype.initialTemp = 200;
+
+/**
+ * Variable: temperature
+ * 
+ * Temperature to limit displacement at later stages of layout.
+ */
+mxFastOrganicLayout.prototype.temperature = 0;
+
+/**
+ * Variable: maxIterations
+ * 
+ * Total number of iterations to run the layout though.
+ */
+mxFastOrganicLayout.prototype.maxIterations = 0;
+
+/**
+ * Variable: iteration
+ * 
+ * Current iteration count.
+ */
+mxFastOrganicLayout.prototype.iteration = 0;
+
+/**
+ * Variable: vertexArray
+ * 
+ * An array of all vertices to be laid out.
+ */
+mxFastOrganicLayout.prototype.vertexArray;
+
+/**
+ * Variable: dispX
+ * 
+ * An array of locally stored X co-ordinate displacements for the vertices.
+ */
+mxFastOrganicLayout.prototype.dispX;
+
+/**
+ * Variable: dispY
+ * 
+ * An array of locally stored Y co-ordinate displacements for the vertices.
+ */
+mxFastOrganicLayout.prototype.dispY;
+
+/**
+ * Variable: cellLocation
+ * 
+ * An array of locally stored co-ordinate positions for the vertices.
+ */
+mxFastOrganicLayout.prototype.cellLocation;
+
+/**
+ * Variable: radius
+ * 
+ * The approximate radius of each cell, nodes only.
+ */
+mxFastOrganicLayout.prototype.radius;
+
+/**
+ * Variable: radiusSquared
+ * 
+ * The approximate radius squared of each cell, nodes only.
+ */
+mxFastOrganicLayout.prototype.radiusSquared;
+
+/**
+ * Variable: isMoveable
+ * 
+ * Array of booleans representing the movable states of the vertices.
+ */
+mxFastOrganicLayout.prototype.isMoveable;
+
+/**
+ * Variable: neighbours
+ * 
+ * Local copy of cell neighbours.
+ */
+mxFastOrganicLayout.prototype.neighbours;
+
+/**
+ * Variable: indices
+ * 
+ * Hashtable from cells to local indices.
+ */
+mxFastOrganicLayout.prototype.indices;
+
+/**
+ * Variable: allowedToRun
+ * 
+ * Boolean flag that specifies if the layout is allowed to run. If this is
+ * set to false, then the layout exits in the following iteration.
+ */
+mxFastOrganicLayout.prototype.allowedToRun = true;
+
+/**
+ * Function: isVertexIgnored
+ * 
+ * Returns a boolean indicating if the given <mxCell> should be ignored as a
+ * vertex. This returns true if the cell has no connections.
+ * 
+ * Parameters:
+ * 
+ * vertex - <mxCell> whose ignored state should be returned.
+ */
+mxFastOrganicLayout.prototype.isVertexIgnored = function(vertex)
+{
+	return mxGraphLayout.prototype.isVertexIgnored.apply(this, arguments) ||
+		this.graph.getConnections(vertex).length == 0;
+};
+
+/**
+ * Function: execute
+ * 
+ * Implements <mxGraphLayout.execute>. This operates on all children of the
+ * given parent where <isVertexIgnored> returns false.
+ */
+mxFastOrganicLayout.prototype.execute = function(parent)
+{
+	var model = this.graph.getModel();
+	this.vertexArray = [];
+	var cells = this.graph.getChildVertices(parent);
+	
+	for (var i = 0; i < cells.length; i++)
+	{
+		if (!this.isVertexIgnored(cells[i]))
+		{
+			this.vertexArray.push(cells[i]);
+		}
+	}
+	
+	var initialBounds = (this.useInputOrigin) ?
+			this.graph.getBoundingBoxFromGeometry(this.vertexArray) :
+				null;
+	var n = this.vertexArray.length;
+
+	this.indices = [];
+	this.dispX = [];
+	this.dispY = [];
+	this.cellLocation = [];
+	this.isMoveable = [];
+	this.neighbours = [];
+	this.radius = [];
+	this.radiusSquared = [];
+
+	if (this.forceConstant < 0.001)
+	{
+		this.forceConstant = 0.001;
+	}
+
+	this.forceConstantSquared = this.forceConstant * this.forceConstant;
+
+	// Create a map of vertices first. This is required for the array of
+	// arrays called neighbours which holds, for each vertex, a list of
+	// ints which represents the neighbours cells to that vertex as
+	// the indices into vertexArray
+	for (var i = 0; i < this.vertexArray.length; i++)
+	{
+		var vertex = this.vertexArray[i];
+		this.cellLocation[i] = [];
+		
+		// Set up the mapping from array indices to cells
+		var id = mxObjectIdentity.get(vertex);
+		this.indices[id] = i;
+		var bounds = this.getVertexBounds(vertex);
+
+		// Set the X,Y value of the internal version of the cell to
+		// the center point of the vertex for better positioning
+		var width = bounds.width;
+		var height = bounds.height;
+		
+		// Randomize (0, 0) locations
+		var x = bounds.x;
+		var y = bounds.y;
+		
+		this.cellLocation[i][0] = x + width / 2.0;
+		this.cellLocation[i][1] = y + height / 2.0;
+		this.radius[i] = Math.min(width, height);
+		this.radiusSquared[i] = this.radius[i] * this.radius[i];
+	}
+
+	// Moves cell location back to top-left from center locations used in
+	// algorithm, resetting the edge points is part of the transaction
+	model.beginUpdate();
+	try
+	{
+		for (var i = 0; i < n; i++)
+		{
+			this.dispX[i] = 0;
+			this.dispY[i] = 0;
+			this.isMoveable[i] = this.isVertexMovable(this.vertexArray[i]);
+
+			// Get lists of neighbours to all vertices, translate the cells
+			// obtained in indices into vertexArray and store as an array
+			// against the orginial cell index
+			var edges = this.graph.getConnections(this.vertexArray[i], parent);
+			var cells = this.graph.getOpposites(edges, this.vertexArray[i]);
+			this.neighbours[i] = [];
+
+			for (var j = 0; j < cells.length; j++)
+			{
+				// Resets the points on the traversed edge
+				if (this.resetEdges)
+				{
+					this.graph.resetEdge(edges[j]);
+				}
+
+			    if (this.disableEdgeStyle)
+			    {
+			    	this.setEdgeStyleEnabled(edges[j], false);
+			    }
+
+				// Looks the cell up in the indices dictionary
+				var id = mxObjectIdentity.get(cells[j]);
+				var index = this.indices[id];
+
+				// Check the connected cell in part of the vertex list to be
+				// acted on by this layout
+				if (index != null)
+				{
+					this.neighbours[i][j] = index;
+				}
+
+				// Else if index of the other cell doesn't correspond to
+				// any cell listed to be acted upon in this layout. Set
+				// the index to the value of this vertex (a dummy self-loop)
+				// so the attraction force of the edge is not calculated
+				else
+				{
+					this.neighbours[i][j] = i;
+				}
+			}
+		}
+		this.temperature = this.initialTemp;
+
+		// If max number of iterations has not been set, guess it
+		if (this.maxIterations == 0)
+		{
+			this.maxIterations = 20 * Math.sqrt(n);
+		}
+		
+		// Main iteration loop
+		for (this.iteration = 0; this.iteration < this.maxIterations; this.iteration++)
+		{
+			if (!this.allowedToRun)
+			{
+				return;
+			}
+			
+			// Calculate repulsive forces on all vertices
+			this.calcRepulsion();
+
+			// Calculate attractive forces through edges
+			this.calcAttraction();
+
+			this.calcPositions();
+			this.reduceTemperature();
+		}
+
+		var minx = null;
+		var miny = null;
+		
+		for (var i = 0; i < this.vertexArray.length; i++)
+		{
+			var vertex = this.vertexArray[i];
+			
+			if (this.isVertexMovable(vertex))
+			{
+				var bounds = this.getVertexBounds(vertex);
+				
+				if (bounds != null)
+				{
+					this.cellLocation[i][0] -= bounds.width / 2.0;
+					this.cellLocation[i][1] -= bounds.height / 2.0;
+					
+					var x = this.graph.snap(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);
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxCircleLayout
+ * 
+ * Extends <mxGraphLayout> to implement a circluar layout for a given radius.
+ * The vertices do not need to be connected for this layout to work and all
+ * connections between vertices are not taken into account.
+ * 
+ * Example:
+ * 
+ * (code)
+ * var layout = new mxCircleLayout(graph);
+ * layout.execute(graph.getDefaultParent());
+ * (end)
+ * 
+ * Constructor: mxCircleLayout
+ *
+ * Constructs a new circular layout for the specified radius.
+ *
+ * Arguments:
+ * 
+ * graph - <mxGraph> that contains the cells.
+ * radius - Optional radius as an int. Default is 100.
+ */
+function mxCircleLayout(graph, radius)
+{
+	mxGraphLayout.call(this, graph);
+	this.radius = (radius != null) ? radius : 100;
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxCircleLayout.prototype = new mxGraphLayout();
+mxCircleLayout.prototype.constructor = mxCircleLayout;
+
+/**
+ * Variable: radius
+ * 
+ * Integer specifying the size of the radius. Default is 100.
+ */
+mxCircleLayout.prototype.radius = null;
+
+/**
+ * Variable: moveCircle
+ * 
+ * Boolean specifying if the circle should be moved to the top,
+ * left corner specified by <x0> and <y0>. Default is false.
+ */
+mxCircleLayout.prototype.moveCircle = false;
+
+/**
+ * Variable: x0
+ * 
+ * Integer specifying the left coordinate of the circle.
+ * Default is 0.
+ */
+mxCircleLayout.prototype.x0 = 0;
+
+/**
+ * Variable: y0
+ * 
+ * Integer specifying the top coordinate of the circle.
+ * Default is 0.
+ */
+mxCircleLayout.prototype.y0 = 0;
+
+/**
+ * Variable: resetEdges
+ * 
+ * Specifies if all edge points of traversed edges should be removed.
+ * Default is true.
+ */
+mxCircleLayout.prototype.resetEdges = true;
+
+/**
+ * Variable: disableEdgeStyle
+ * 
+ * Specifies if the STYLE_NOEDGESTYLE flag should be set on edges that are
+ * modified by the result. Default is true.
+ */
+mxCircleLayout.prototype.disableEdgeStyle = true;
+
+/**
+ * Function: execute
+ * 
+ * Implements <mxGraphLayout.execute>.
+ */
+mxCircleLayout.prototype.execute = function(parent)
+{
+	var model = this.graph.getModel();
+
+	// Moves the vertices to build a circle. Makes sure the
+	// radius is large enough for the vertices to not
+	// overlap
+	model.beginUpdate();
+	try
+	{
+		// Gets all vertices inside the parent and finds
+		// the maximum dimension of the largest vertex
+		var max = 0;
+		var top = null;
+		var left = null;
+		var vertices = [];
+		var childCount = model.getChildCount(parent);
+		
+		for (var i = 0; i < childCount; i++)
+		{
+			var cell = model.getChildAt(parent, i);
+			
+			if (!this.isVertexIgnored(cell))
+			{
+				vertices.push(cell);
+				var bounds = this.getVertexBounds(cell);
+				
+				if (top == null)
+				{
+					top = bounds.y;
+				}
+				else
+				{
+					top = Math.min(top, bounds.y);
+				}
+				
+				if (left == null)
+				{
+					left = bounds.x;
+				}
+				else
+				{
+					left = Math.min(left, bounds.x);
+				}
+				
+				max = Math.max(max, Math.max(bounds.width, bounds.height));
+			}
+			else if (!this.isEdgeIgnored(cell))
+			{
+				// Resets the points on the traversed edge
+				if (this.resetEdges)
+				{
+					this.graph.resetEdge(cell);
+				}
+
+			    if (this.disableEdgeStyle)
+			    {
+			    	this.setEdgeStyleEnabled(cell, false);
+			    }
+			}
+		}
+		
+		var r = this.getRadius(vertices.length, max);
+
+		// Moves the circle to the specified origin
+		if (this.moveCircle)
+		{
+			left = this.x0;
+			top = this.y0;
+		}
+		
+		this.circle(vertices, r, left, top);
+	}
+	finally
+	{
+		model.endUpdate();
+	}
+};
+
+/**
+ * Function: getRadius
+ * 
+ * Returns the radius to be used for the given vertex count. Max is the maximum
+ * width or height of all vertices in the layout.
+ */
+mxCircleLayout.prototype.getRadius = function(count, max)
+{
+	return Math.max(count * max / Math.PI, this.radius);
+};
+
+/**
+ * Function: circle
+ * 
+ * Executes the circular layout for the specified array
+ * of vertices and the given radius. This is called from
+ * <execute>.
+ */
+mxCircleLayout.prototype.circle = function(vertices, r, left, top)
+{
+	var vertexCount = vertices.length;
+	var phi = 2 * Math.PI / vertexCount;
+	
+	for (var i = 0; i < vertexCount; i++)
+	{
+		if (this.isVertexMovable(vertices[i]))
+		{
+			this.setVertexLocation(vertices[i],
+				left + r + r * Math.sin(i*phi),
+				top + r + r * Math.cos(i*phi));
+		}
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxParallelEdgeLayout
+ * 
+ * Extends <mxGraphLayout> for arranging parallel edges. This layout works
+ * on edges for all pairs of vertices where there is more than one edge
+ * connecting the latter.
+ * 
+ * Example:
+ * 
+ * (code)
+ * var layout = new mxParallelEdgeLayout(graph);
+ * layout.execute(graph.getDefaultParent());
+ * (end)
+ * 
+ * To run the layout for the parallel edges of a changed edge only, the
+ * following code can be used.
+ * 
+ * (code)
+ * var layout = new mxParallelEdgeLayout(graph);
+ * 
+ * graph.addListener(mxEvent.CELL_CONNECTED, function(sender, evt)
+ * {
+ *   var model = graph.getModel();
+ *   var edge = evt.getProperty('edge');
+ *   var src = model.getTerminal(edge, true);
+ *   var trg = model.getTerminal(edge, false);
+ *   
+ *   layout.isEdgeIgnored = function(edge2)
+ *   {
+ *     var src2 = model.getTerminal(edge2, true);
+ *     var trg2 = model.getTerminal(edge2, false);
+ *     
+ *     return !(model.isEdge(edge2) && ((src == src2 && trg == trg2) || (src == trg2 && trg == src2)));
+ *   };
+ *   
+ *   layout.execute(graph.getDefaultParent());
+ * });
+ * (end)
+ * 
+ * Constructor: mxCompactTreeLayout
+ * 
+ * Constructs a new fast organic layout for the specified graph.
+ */
+function mxParallelEdgeLayout(graph)
+{
+	mxGraphLayout.call(this, graph);
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxParallelEdgeLayout.prototype = new mxGraphLayout();
+mxParallelEdgeLayout.prototype.constructor = mxParallelEdgeLayout;
+
+/**
+ * Variable: spacing
+ * 
+ * Defines the spacing between the parallels. Default is 20.
+ */
+mxParallelEdgeLayout.prototype.spacing = 20;
+
+/**
+ * Function: execute
+ * 
+ * Implements <mxGraphLayout.execute>.
+ */
+mxParallelEdgeLayout.prototype.execute = function(parent)
+{
+	var lookup = this.findParallels(parent);
+	
+	this.graph.model.beginUpdate();	
+	try
+	{
+		for (var i in lookup)
+		{
+			var parallels = lookup[i];
+
+			if (parallels.length > 1)
+			{
+				this.layout(parallels);
+			}
+		}
+	}
+	finally
+	{
+		this.graph.model.endUpdate();
+	}
+};
+
+/**
+ * Function: findParallels
+ * 
+ * Finds the parallel edges in the given parent.
+ */
+mxParallelEdgeLayout.prototype.findParallels = function(parent)
+{
+	var model = this.graph.getModel();
+	var lookup = [];
+	var childCount = model.getChildCount(parent);
+	
+	for (var i = 0; i < childCount; i++)
+	{
+		var child = model.getChildAt(parent, i);
+		
+		if (!this.isEdgeIgnored(child))
+		{
+			var id = this.getEdgeId(child);
+			
+			if (id != null)
+			{
+				if (lookup[id] == null)
+				{
+					lookup[id] = [];
+				}
+				
+				lookup[id].push(child);
+			}
+		}
+	}
+	
+	return lookup;
+};
+
+/**
+ * Function: getEdgeId
+ * 
+ * Returns a unique ID for the given edge. The id is independent of the
+ * edge direction and is built using the visible terminal of the given
+ * edge.
+ */
+mxParallelEdgeLayout.prototype.getEdgeId = function(edge)
+{
+	var view = this.graph.getView();
+	
+	// Cannot used cached visible terminal because this could be triggered in BEFORE_UNDO
+	var src = view.getVisibleTerminal(edge, true);
+	var trg = view.getVisibleTerminal(edge, false);
+
+	if (src != null && trg != null)
+	{
+		src = mxObjectIdentity.get(src);
+		trg = mxObjectIdentity.get(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 view = this.graph.getView();
+	var model = this.graph.getModel();
+	var src = model.getGeometry(view.getVisibleTerminal(edge, true));
+	var trg = model.getGeometry(view.getVisibleTerminal(edge, false));
+	
+	// Routes multiple loops
+	if (src == trg)
+	{
+		var x0 = src.x + src.width + this.spacing;
+		var y0 = src.y + src.height / 2;
+
+		for (var i = 0; i < parallels.length; i++)
+		{
+			this.route(parallels[i], x0, y0);
+			x0 += this.spacing;
+		}
+	}
+	else if (src != null && trg != null)
+	{
+		// Routes parallel edges
+		var scx = src.x + src.width / 2;
+		var scy = src.y + src.height / 2;
+		
+		var tcx = trg.x + trg.width / 2;
+		var tcy = trg.y + trg.height / 2;
+		
+		var dx = tcx - scx;
+		var dy = tcy - scy;
+
+		var len = Math.sqrt(dx * dx + dy * dy);
+		
+		if (len > 0)
+		{
+			var x0 = scx + dx / 2;
+			var y0 = scy + dy / 2;
+			
+			var nx = dy * this.spacing / len;
+			var ny = dx * this.spacing / len;
+			
+			x0 += nx * (parallels.length - 1) / 2;
+			y0 -= ny * (parallels.length - 1) / 2;
+	
+			for (var i = 0; i < parallels.length; i++)
+			{
+				this.route(parallels[i], x0, y0);
+				x0 -= nx;
+				y0 += ny;
+			}
+		}
+	}
+};
+
+/**
+ * Function: route
+ * 
+ * Routes the given edge via the given point.
+ */
+mxParallelEdgeLayout.prototype.route = function(edge, x, y)
+{
+	if (this.graph.isCellMovable(edge))
+	{
+		this.setEdgePoints(edge, [new mxPoint(x, y)]);
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxCompositeLayout
+ * 
+ * Allows to compose multiple layouts into a single layout. The master layout
+ * is the layout that handles move operations if another layout than the first
+ * element in <layouts> should be used. The <master> layout is not executed as
+ * the code assumes that it is part of <layouts>.
+ * 
+ * Example:
+ * (code)
+ * var first = new mxFastOrganicLayout(graph);
+ * var second = new mxParallelEdgeLayout(graph);
+ * var layout = new mxCompositeLayout(graph, [first, second], first);
+ * layout.execute(graph.getDefaultParent());
+ * (end)
+ * 
+ * Constructor: mxCompositeLayout
+ *
+ * Constructs a new layout using the given layouts. The graph instance is
+ * required for creating the transaction that contains all layouts.
+ *
+ * Arguments:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ * layouts - Array of <mxGraphLayouts>.
+ * master - Optional layout that handles moves. If no layout is given then
+ * the first layout of the above array is used to handle moves.
+ */
+function mxCompositeLayout(graph, layouts, master)
+{
+	mxGraphLayout.call(this, graph);
+	this.layouts = layouts;
+	this.master = master;
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxCompositeLayout.prototype = new mxGraphLayout();
+mxCompositeLayout.prototype.constructor = mxCompositeLayout;
+	
+/**
+ * Variable: layouts
+ * 
+ * Holds the array of <mxGraphLayouts> that this layout contains.
+ */
+mxCompositeLayout.prototype.layouts = null;
+
+/**
+ * Variable: layouts
+ * 
+ * Reference to the <mxGraphLayouts> that handles moves. If this is null
+ * then the first layout in <layouts> is used.
+ */
+mxCompositeLayout.prototype.master = null;
+
+/**
+ * Function: moveCell
+ * 
+ * Implements <mxGraphLayout.moveCell> by calling move on <master> or the first
+ * layout in <layouts>.
+ */
+mxCompositeLayout.prototype.moveCell = function(cell, x, y)
+{
+	if (this.master != null)
+	{
+		this.master.move.apply(this.master, arguments);
+	}
+	else
+	{
+		this.layouts[0].move.apply(this.layouts[0], arguments);
+	}
+};
+
+/**
+ * Function: execute
+ * 
+ * Implements <mxGraphLayout.execute> by executing all <layouts> in a
+ * single transaction.
+ */
+mxCompositeLayout.prototype.execute = function(parent)
+{
+	var model = this.graph.getModel();
+	
+	model.beginUpdate();
+	try
+	{
+		for (var i = 0; i < this.layouts.length; i++)
+		{
+			this.layouts[i].execute.apply(this.layouts[i], arguments);
+		}
+	}
+	finally
+	{
+		model.endUpdate();
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxEdgeLabelLayout
+ * 
+ * Extends <mxGraphLayout> to implement an edge label layout. This layout
+ * makes use of cell states, which means the graph must be validated in
+ * a graph view (so that the label bounds are available) before this layout
+ * can be executed.
+ * 
+ * Example:
+ * 
+ * (code)
+ * var layout = new mxEdgeLabelLayout(graph);
+ * layout.execute(graph.getDefaultParent());
+ * (end)
+ * 
+ * Constructor: mxEdgeLabelLayout
+ *
+ * Constructs a new edge label layout.
+ *
+ * Arguments:
+ * 
+ * graph - <mxGraph> that contains the cells.
+ */
+function mxEdgeLabelLayout(graph, radius)
+{
+	mxGraphLayout.call(this, graph);
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxEdgeLabelLayout.prototype = new mxGraphLayout();
+mxEdgeLabelLayout.prototype.constructor = mxEdgeLabelLayout;
+
+/**
+ * Function: execute
+ * 
+ * Implements <mxGraphLayout.execute>.
+ */
+mxEdgeLabelLayout.prototype.execute = function(parent)
+{
+	var view = this.graph.view;
+	var model = this.graph.getModel();
+	
+	// Gets all vertices and edges inside the parent
+	var edges = [];
+	var vertices = [];
+	var childCount = model.getChildCount(parent);
+	
+	for (var i = 0; i < childCount; i++)
+	{
+		var cell = model.getChildAt(parent, i);
+		var state = view.getState(cell);
+		
+		if (state != null)
+		{
+			if (!this.isVertexIgnored(cell))
+			{
+				vertices.push(state);
+			}
+			else if (!this.isEdgeIgnored(cell))
+			{
+				edges.push(state);
+			}
+		}
+	}
+	
+	this.placeLabels(vertices, edges);
+};
+
+/**
+ * Function: placeLabels
+ * 
+ * Places the labels of the given edges.
+ */
+mxEdgeLabelLayout.prototype.placeLabels = function(v, e)
+{
+	var model = this.graph.getModel();
+	
+	// Moves the vertices to build a circle. Makes sure the
+	// radius is large enough for the vertices to not
+	// overlap
+	model.beginUpdate();
+	try
+	{
+		for (var i = 0; i < e.length; i++)
+		{
+			var edge = e[i];
+			
+			if (edge != null && edge.text != null &&
+				edge.text.boundingBox != null)
+			{
+				for (var j = 0; j < v.length; j++)
+				{
+					var vertex = v[j];
+					
+					if (vertex != null)
+					{
+						this.avoid(edge, vertex);
+					}
+				}
+			}
+		}
+	}
+	finally
+	{
+		model.endUpdate();
+	}
+};
+
+/**
+ * Function: avoid
+ * 
+ * Places the labels of the given edges.
+ */
+mxEdgeLabelLayout.prototype.avoid = function(edge, vertex)
+{
+	var model = this.graph.getModel();
+	var labRect = edge.text.boundingBox;
+	
+	if (mxUtils.intersects(labRect, vertex))
+	{
+		var dy1 = -labRect.y - labRect.height + vertex.y;
+		var dy2 = -labRect.y + vertex.y + vertex.height;
+		
+		var dy = (Math.abs(dy1) < Math.abs(dy2)) ? dy1 : dy2;
+		
+		var dx1 = -labRect.x - labRect.width + vertex.x;
+		var dx2 = -labRect.x + vertex.x + vertex.width;
+	
+		var dx = (Math.abs(dx1) < Math.abs(dx2)) ? dx1 : dx2;
+		
+		if (Math.abs(dx) < Math.abs(dy))
+		{
+			dy = 0;
+		}
+		else
+		{
+			dx = 0;
+		}
+	
+		var g = model.getGeometry(edge.cell);
+		
+		if (g != null)
+		{
+			g = g.clone();
+			
+			if (g.offset != null)
+			{
+				g.offset.x += dx;
+				g.offset.y += dy;
+			}
+			else
+			{
+				g.offset = new mxPoint(dx, dy);
+			}
+			
+			model.setGeometry(edge.cell, g);
+		}
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxGraphAbstractHierarchyCell
+ * 
+ * An abstraction of an internal hierarchy node or edge
+ * 
+ * Constructor: mxGraphAbstractHierarchyCell
+ *
+ * Constructs a new hierarchical layout algorithm.
+ *
+ * Arguments:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ * deterministic - Optional boolean that specifies if this layout should be
+ * deterministic. Default is true.
+ */
+function mxGraphAbstractHierarchyCell()
+{
+	this.x = [];
+	this.y = [];
+	this.temp = [];
+};
+
+/**
+ * Variable: maxRank
+ * 
+ * The maximum rank this cell occupies. Default is -1.
+ */
+mxGraphAbstractHierarchyCell.prototype.maxRank = -1;
+
+/**
+ * Variable: minRank
+ * 
+ * The minimum rank this cell occupies. Default is -1.
+ */
+mxGraphAbstractHierarchyCell.prototype.minRank = -1;
+
+/**
+ * Variable: x
+ * 
+ * The x position of this cell for each layer it occupies
+ */
+mxGraphAbstractHierarchyCell.prototype.x = null;
+
+/**
+ * Variable: y
+ * 
+ * The y position of this cell for each layer it occupies
+ */
+mxGraphAbstractHierarchyCell.prototype.y = null;
+
+/**
+ * Variable: width
+ * 
+ * The width of this cell
+ */
+mxGraphAbstractHierarchyCell.prototype.width = 0;
+
+/**
+ * Variable: height
+ * 
+ * The height of this cell
+ */
+mxGraphAbstractHierarchyCell.prototype.height = 0;
+
+/**
+ * Variable: nextLayerConnectedCells
+ * 
+ * A cached version of the cells this cell connects to on the next layer up
+ */
+mxGraphAbstractHierarchyCell.prototype.nextLayerConnectedCells = null;
+
+/**
+ * Variable: previousLayerConnectedCells
+ * 
+ * A cached version of the cells this cell connects to on the next layer down
+ */
+mxGraphAbstractHierarchyCell.prototype.previousLayerConnectedCells = null;
+
+/**
+ * Variable: temp
+ * 
+ * Temporary variable for general use. Generally, try to avoid
+ * carrying information between stages. Currently, the longest
+ * path layering sets temp to the rank position in fixRanks()
+ * and the crossing reduction uses this. This meant temp couldn't
+ * be used for hashing the nodes in the model dfs and so hashCode
+ * was created
+ */
+mxGraphAbstractHierarchyCell.prototype.temp = null;
+
+/**
+ * Function: getNextLayerConnectedCells
+ * 
+ * Returns the cells this cell connects to on the next layer up
+ */
+mxGraphAbstractHierarchyCell.prototype.getNextLayerConnectedCells = function(layer)
+{
+	return null;
+};
+
+/**
+ * Function: getPreviousLayerConnectedCells
+ * 
+ * Returns the cells this cell connects to on the next layer down
+ */
+mxGraphAbstractHierarchyCell.prototype.getPreviousLayerConnectedCells = function(layer)
+{
+	return null;
+};
+
+/**
+ * Function: isEdge
+ * 
+ * Returns whether or not this cell is an edge
+ */
+mxGraphAbstractHierarchyCell.prototype.isEdge = function()
+{
+	return false;
+};
+
+/**
+ * Function: isVertex
+ * 
+ * Returns whether or not this cell is a node
+ */
+mxGraphAbstractHierarchyCell.prototype.isVertex = function()
+{
+	return false;
+};
+
+/**
+ * Function: getGeneralPurposeVariable
+ * 
+ * Gets the value of temp for the specified layer
+ */
+mxGraphAbstractHierarchyCell.prototype.getGeneralPurposeVariable = function(layer)
+{
+	return null;
+};
+
+/**
+ * Function: setGeneralPurposeVariable
+ * 
+ * Set the value of temp for the specified layer
+ */
+mxGraphAbstractHierarchyCell.prototype.setGeneralPurposeVariable = function(layer, value)
+{
+	return null;
+};
+
+/**
+ * Function: setX
+ * 
+ * Set the value of x for the specified layer
+ */
+mxGraphAbstractHierarchyCell.prototype.setX = function(layer, value)
+{
+	if (this.isVertex())
+	{
+		this.x[0] = value;
+	}
+	else if (this.isEdge())
+	{
+		this.x[layer - this.minRank - 1] = value;
+	}
+};
+
+/**
+ * Function: getX
+ * 
+ * Gets the value of x on the specified layer
+ */
+mxGraphAbstractHierarchyCell.prototype.getX = function(layer)
+{
+	if (this.isVertex())
+	{
+		return this.x[0];
+	}
+	else if (this.isEdge())
+	{
+		return this.x[layer - this.minRank - 1];
+	}
+
+	return 0.0;
+};
+
+/**
+ * Function: setY
+ * 
+ * Set the value of y for the specified layer
+ */
+mxGraphAbstractHierarchyCell.prototype.setY = function(layer, value)
+{
+	if (this.isVertex())
+	{
+		this.y[0] = value;
+	}
+	else if (this.isEdge())
+	{
+		this.y[layer -this. minRank - 1] = value;
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxGraphHierarchyNode
+ * 
+ * An abstraction of a hierarchical edge for the hierarchy layout
+ * 
+ * Constructor: mxGraphHierarchyNode
+ *
+ * Constructs an internal node to represent the specified real graph cell
+ *
+ * Arguments:
+ * 
+ * cell - the real graph cell this node represents
+ */
+function mxGraphHierarchyNode(cell)
+{
+	mxGraphAbstractHierarchyCell.apply(this, arguments);
+	this.cell = cell;
+	this.id = mxObjectIdentity.get(cell);
+	this.connectsAsTarget = [];
+	this.connectsAsSource = [];
+};
+
+/**
+ * Extends mxGraphAbstractHierarchyCell.
+ */
+mxGraphHierarchyNode.prototype = new mxGraphAbstractHierarchyCell();
+mxGraphHierarchyNode.prototype.constructor = mxGraphHierarchyNode;
+
+/**
+ * Variable: cell
+ * 
+ * The graph cell this object represents.
+ */
+mxGraphHierarchyNode.prototype.cell = null;
+
+/**
+ * Variable: id
+ * 
+ * The object identity of the wrapped cell
+ */
+mxGraphHierarchyNode.prototype.id = null;
+
+/**
+ * Variable: connectsAsTarget
+ * 
+ * Collection of hierarchy edges that have this node as a target
+ */
+mxGraphHierarchyNode.prototype.connectsAsTarget = null;
+
+/**
+ * Variable: connectsAsSource
+ * 
+ * Collection of hierarchy edges that have this node as a source
+ */
+mxGraphHierarchyNode.prototype.connectsAsSource = null;
+
+/**
+ * Variable: hashCode
+ * 
+ * Assigns a unique hashcode for each node. Used by the model dfs instead
+ * of copying HashSets
+ */
+mxGraphHierarchyNode.prototype.hashCode = false;
+
+/**
+ * Function: getRankValue
+ * 
+ * Returns the integer value of the layer that this node resides in
+ */
+mxGraphHierarchyNode.prototype.getRankValue = function(layer)
+{
+	return this.maxRank;
+};
+
+/**
+ * Function: getNextLayerConnectedCells
+ * 
+ * Returns the cells this cell connects to on the next layer up
+ */
+mxGraphHierarchyNode.prototype.getNextLayerConnectedCells = function(layer)
+{
+	if (this.nextLayerConnectedCells == null)
+	{
+		this.nextLayerConnectedCells = [];
+		this.nextLayerConnectedCells[0] = [];
+		
+		for (var i = 0; i < this.connectsAsTarget.length; i++)
+		{
+			var edge = this.connectsAsTarget[i];
+
+			if (edge.maxRank == -1 || edge.maxRank == layer + 1)
+			{
+				// Either edge is not in any rank or
+				// no dummy nodes in edge, add node of other side of edge
+				this.nextLayerConnectedCells[0].push(edge.source);
+			}
+			else
+			{
+				// Edge spans at least two layers, add edge
+				this.nextLayerConnectedCells[0].push(edge);
+			}
+		}
+	}
+
+	return this.nextLayerConnectedCells[0];
+};
+
+/**
+ * Function: getPreviousLayerConnectedCells
+ * 
+ * Returns the cells this cell connects to on the next layer down
+ */
+mxGraphHierarchyNode.prototype.getPreviousLayerConnectedCells = function(layer)
+{
+	if (this.previousLayerConnectedCells == null)
+	{
+		this.previousLayerConnectedCells = [];
+		this.previousLayerConnectedCells[0] = [];
+		
+		for (var i = 0; i < this.connectsAsSource.length; i++)
+		{
+			var edge = this.connectsAsSource[i];
+
+			if (edge.minRank == -1 || edge.minRank == layer - 1)
+			{
+				// No dummy nodes in edge, add node of other side of edge
+				this.previousLayerConnectedCells[0].push(edge.target);
+			}
+			else
+			{
+				// Edge spans at least two layers, add edge
+				this.previousLayerConnectedCells[0].push(edge);
+			}
+		}
+	}
+
+	return this.previousLayerConnectedCells[0];
+};
+
+/**
+ * Function: isVertex
+ * 
+ * Returns true.
+ */
+mxGraphHierarchyNode.prototype.isVertex = function()
+{
+	return true;
+};
+
+/**
+ * Function: getGeneralPurposeVariable
+ * 
+ * Gets the value of temp for the specified layer
+ */
+mxGraphHierarchyNode.prototype.getGeneralPurposeVariable = function(layer)
+{
+	return this.temp[0];
+};
+
+/**
+ * Function: setGeneralPurposeVariable
+ * 
+ * Set the value of temp for the specified layer
+ */
+mxGraphHierarchyNode.prototype.setGeneralPurposeVariable = function(layer, value)
+{
+	this.temp[0] = value;
+};
+
+/**
+ * Function: isAncestor
+ */
+mxGraphHierarchyNode.prototype.isAncestor = function(otherNode)
+{
+	// Firstly, the hash code of this node needs to be shorter than the
+	// other node
+	if (otherNode != null && this.hashCode != null && otherNode.hashCode != null
+			&& this.hashCode.length < otherNode.hashCode.length)
+	{
+		if (this.hashCode == otherNode.hashCode)
+		{
+			return true;
+		}
+		
+		if (this.hashCode == null || this.hashCode == null)
+		{
+			return false;
+		}
+		
+		// Secondly, this hash code must match the start of the other
+		// node's hash code. Arrays.equals cannot be used here since
+		// the arrays are different length, and we do not want to
+		// perform another array copy.
+		for (var i = 0; i < this.hashCode.length; i++)
+		{
+			if (this.hashCode[i] != otherNode.hashCode[i])
+			{
+				return false;
+			}
+		}
+
+		return true;
+	}
+
+	return false;
+};
+
+/**
+ * Function: getCoreCell
+ * 
+ * Gets the core vertex associated with this wrapper
+ */
+mxGraphHierarchyNode.prototype.getCoreCell = function()
+{
+	return this.cell;
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxGraphHierarchyEdge
+ * 
+ * An abstraction of a hierarchical edge for the hierarchy layout
+ * 
+ * Constructor: mxGraphHierarchyEdge
+ *
+ * Constructs a hierarchy edge
+ *
+ * Arguments:
+ * 
+ * edges - a list of real graph edges this abstraction represents
+ */
+function mxGraphHierarchyEdge(edges)
+{
+	mxGraphAbstractHierarchyCell.apply(this, arguments);
+	this.edges = edges;
+	this.ids = [];
+	
+	for (var i = 0; i < edges.length; i++)
+	{
+		this.ids.push(mxObjectIdentity.get(edges[i]));
+	}
+};
+
+/**
+ * Extends mxGraphAbstractHierarchyCell.
+ */
+mxGraphHierarchyEdge.prototype = new mxGraphAbstractHierarchyCell();
+mxGraphHierarchyEdge.prototype.constructor = mxGraphHierarchyEdge;
+
+/**
+ * Variable: edges
+ * 
+ * The graph edge(s) this object represents. Parallel edges are all grouped
+ * together within one hierarchy edge.
+ */
+mxGraphHierarchyEdge.prototype.edges = null;
+
+/**
+ * Variable: ids
+ * 
+ * The object identities of the wrapped cells
+ */
+mxGraphHierarchyEdge.prototype.ids = null;
+
+/**
+ * Variable: source
+ * 
+ * The node this edge is sourced at
+ */
+mxGraphHierarchyEdge.prototype.source = null;
+
+/**
+ * Variable: target
+ * 
+ * The node this edge targets
+ */
+mxGraphHierarchyEdge.prototype.target = null;
+
+/**
+ * Variable: isReversed
+ * 
+ * Whether or not the direction of this edge has been reversed
+ * internally to create a DAG for the hierarchical layout
+ */
+mxGraphHierarchyEdge.prototype.isReversed = false;
+
+/**
+ * Function: invert
+ * 
+ * Inverts the direction of this internal edge(s)
+ */
+mxGraphHierarchyEdge.prototype.invert = function(layer)
+{
+	var temp = this.source;
+	this.source = this.target;
+	this.target = temp;
+	this.isReversed = !this.isReversed;
+};
+
+/**
+ * Function: getNextLayerConnectedCells
+ * 
+ * Returns the cells this cell connects to on the next layer up
+ */
+mxGraphHierarchyEdge.prototype.getNextLayerConnectedCells = function(layer)
+{
+	if (this.nextLayerConnectedCells == null)
+	{
+		this.nextLayerConnectedCells = [];
+		
+		for (var i = 0; i < this.temp.length; i++)
+		{
+			this.nextLayerConnectedCells[i] = [];
+			
+			if (i == this.temp.length - 1)
+			{
+				this.nextLayerConnectedCells[i].push(this.source);
+			}
+			else
+			{
+				this.nextLayerConnectedCells[i].push(this);
+			}
+		}
+	}
+	
+	return this.nextLayerConnectedCells[layer - this.minRank - 1];
+};
+
+/**
+ * Function: getPreviousLayerConnectedCells
+ * 
+ * Returns the cells this cell connects to on the next layer down
+ */
+mxGraphHierarchyEdge.prototype.getPreviousLayerConnectedCells = function(layer)
+{
+	if (this.previousLayerConnectedCells == null)
+	{
+		this.previousLayerConnectedCells = [];
+
+		for (var i = 0; i < this.temp.length; i++)
+		{
+			this.previousLayerConnectedCells[i] = [];
+			
+			if (i == 0)
+			{
+				this.previousLayerConnectedCells[i].push(this.target);
+			}
+			else
+			{
+				this.previousLayerConnectedCells[i].push(this);
+			}
+		}
+	}
+
+	return this.previousLayerConnectedCells[layer - this.minRank - 1];
+};
+
+/**
+ * Function: isEdge
+ * 
+ * Returns true.
+ */
+mxGraphHierarchyEdge.prototype.isEdge = function()
+{
+	return true;
+};
+
+/**
+ * Function: getGeneralPurposeVariable
+ * 
+ * Gets the value of temp for the specified layer
+ */
+mxGraphHierarchyEdge.prototype.getGeneralPurposeVariable = function(layer)
+{
+	return this.temp[layer - this.minRank - 1];
+};
+
+/**
+ * Function: setGeneralPurposeVariable
+ * 
+ * Set the value of temp for the specified layer
+ */
+mxGraphHierarchyEdge.prototype.setGeneralPurposeVariable = function(layer, value)
+{
+	this.temp[layer - this.minRank - 1] = value;
+};
+
+/**
+ * Function: getCoreCell
+ * 
+ * Gets the first core edge associated with this wrapper
+ */
+mxGraphHierarchyEdge.prototype.getCoreCell = function()
+{
+	if (this.edges != null && this.edges.length > 0)
+	{
+		return this.edges[0];
+	}
+	
+	return null;
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxGraphHierarchyModel
+ *
+ * Internal model of a hierarchical graph. This model stores nodes and edges
+ * equivalent to the real graph nodes and edges, but also stores the rank of the
+ * cells, the order within the ranks and the new candidate locations of cells.
+ * The internal model also reverses edge direction were appropriate , ignores
+ * self-loop and groups parallels together under one edge object.
+ *
+ * Constructor: mxGraphHierarchyModel
+ *
+ * Creates an internal ordered graph model using the vertices passed in. If
+ * there are any, leftward edge need to be inverted in the internal model
+ *
+ * Arguments:
+ *
+ * graph - the facade describing the graph to be operated on
+ * vertices - the vertices for this hierarchy
+ * ordered - whether or not the vertices are already ordered
+ * deterministic - whether or not this layout should be deterministic on each
+ * tightenToSource - whether or not to tighten vertices towards the sources
+ * scanRanksFromSinks - Whether rank assignment is from the sinks or sources.
+ * usage
+ */
+function mxGraphHierarchyModel(layout, vertices, roots, parent, tightenToSource)
+{
+	var graph = layout.getGraph();
+	this.tightenToSource = tightenToSource;
+	this.roots = roots;
+	this.parent = parent;
+
+	// map of cells to internal cell needed for second run through
+	// to setup the sink of edges correctly
+	this.vertexMapper = new mxDictionary();
+	this.edgeMapper = new mxDictionary();
+	this.maxRank = 0;
+	var internalVertices = [];
+
+	if (vertices == null)
+	{
+		vertices = this.graph.getChildVertices(parent);
+	}
+
+	this.maxRank = this.SOURCESCANSTARTRANK;
+	// map of cells to internal cell needed for second run through
+	// to setup the sink of edges correctly. Guess size by number
+	// of edges is roughly same as number of vertices.
+	this.createInternalCells(layout, vertices, internalVertices);
+
+	// Go through edges set their sink values. Also check the
+	// ordering if and invert edges if necessary
+	for (var i = 0; i < vertices.length; i++)
+	{
+		var edges = internalVertices[i].connectsAsSource;
+
+		for (var j = 0; j < edges.length; j++)
+		{
+			var internalEdge = edges[j];
+			var realEdges = internalEdge.edges;
+
+			// Only need to process the first real edge, since
+			// all the edges connect to the same other vertex
+			if (realEdges != null && realEdges.length > 0)
+			{
+				var realEdge = realEdges[0];
+				var targetCell = layout.getVisibleTerminal(
+						realEdge, false);
+				var internalTargetCell = this.vertexMapper.get(targetCell);
+
+				if (internalVertices[i] == internalTargetCell)
+				{
+					// If there are parallel edges going between two vertices and not all are in the same direction
+					// you can have navigated across one direction when doing the cycle reversal that isn't the same
+					// direction as the first real edge in the array above. When that happens the if above catches
+					// that and we correct the target cell before continuing.
+					// This branch only detects this single case
+					targetCell = layout.getVisibleTerminal(
+							realEdge, true);
+					internalTargetCell = this.vertexMapper.get(targetCell);
+				}
+				
+				if (internalTargetCell != null
+						&& internalVertices[i] != internalTargetCell)
+				{
+					internalEdge.target = internalTargetCell;
+
+					if (internalTargetCell.connectsAsTarget.length == 0)
+					{
+						internalTargetCell.connectsAsTarget = [];
+					}
+
+					if (mxUtils.indexOf(internalTargetCell.connectsAsTarget, internalEdge) < 0)
+					{
+						internalTargetCell.connectsAsTarget.push(internalEdge);
+					}
+				}
+			}
+		}
+
+		// Use the temp variable in the internal nodes to mark this
+		// internal vertex as having been visited.
+		internalVertices[i].temp[0] = 1;
+	}
+};
+
+/**
+ * Variable: maxRank
+ *
+ * Stores the largest rank number allocated
+ */
+mxGraphHierarchyModel.prototype.maxRank = null;
+
+/**
+ * Variable: vertexMapper
+ *
+ * Map from graph vertices to internal model nodes.
+ */
+mxGraphHierarchyModel.prototype.vertexMapper = null;
+
+/**
+ * Variable: edgeMapper
+ *
+ * Map from graph edges to internal model edges
+ */
+mxGraphHierarchyModel.prototype.edgeMapper = null;
+
+/**
+ * Variable: ranks
+ *
+ * Mapping from rank number to actual rank
+ */
+mxGraphHierarchyModel.prototype.ranks = null;
+
+/**
+ * Variable: roots
+ *
+ * Store of roots of this hierarchy model, these are real graph cells, not
+ * internal cells
+ */
+mxGraphHierarchyModel.prototype.roots = null;
+
+/**
+ * Variable: parent
+ *
+ * The parent cell whose children are being laid out
+ */
+mxGraphHierarchyModel.prototype.parent = null;
+
+/**
+ * Variable: dfsCount
+ *
+ * Count of the number of times the ancestor dfs has been used.
+ */
+mxGraphHierarchyModel.prototype.dfsCount = 0;
+
+/**
+ * Variable: SOURCESCANSTARTRANK
+ *
+ * High value to start source layering scan rank value from.
+ */
+mxGraphHierarchyModel.prototype.SOURCESCANSTARTRANK = 100000000;
+
+/**
+ * Variable: tightenToSource
+ *
+ * Whether or not to tighten the assigned ranks of vertices up towards
+ * the source cells.
+ */
+mxGraphHierarchyModel.prototype.tightenToSource = false;
+
+/**
+ * Function: createInternalCells
+ *
+ * Creates all edges in the internal model
+ *
+ * Parameters:
+ *
+ * layout - Reference to the <mxHierarchicalLayout> algorithm.
+ * vertices - Array of <mxCells> that represent the vertices whom are to
+ * have an internal representation created.
+ * internalVertices - The array of <mxGraphHierarchyNodes> to have their
+ * information filled in using the real vertices.
+ */
+mxGraphHierarchyModel.prototype.createInternalCells = function(layout, vertices, internalVertices)
+{
+	var graph = layout.getGraph();
+
+	// Create internal edges
+	for (var i = 0; i < vertices.length; i++)
+	{
+		internalVertices[i] = new mxGraphHierarchyNode(vertices[i]);
+		this.vertexMapper.put(vertices[i], internalVertices[i]);
+
+		// If the layout is deterministic, order the cells
+		//List outgoingCells = graph.getNeighbours(vertices[i], deterministic);
+		var conns = layout.getEdges(vertices[i]);
+		internalVertices[i].connectsAsSource = [];
+
+		// Create internal edges, but don't do any rank assignment yet
+		// First use the information from the greedy cycle remover to
+		// invert the leftward edges internally
+		for (var j = 0; j < conns.length; j++)
+		{
+			var cell = layout.getVisibleTerminal(conns[j], false);
+
+			// Looking for outgoing edges only
+			if (cell != vertices[i] && layout.graph.model.isVertex(cell) &&
+					!layout.isVertexIgnored(cell))
+			{
+				// We process all edge between this source and its targets
+				// If there are edges going both ways, we need to collect
+				// them all into one internal edges to avoid looping problems
+				// later. We assume this direction (source -> target) is the 
+				// natural direction if at least half the edges are going in
+				// that direction.
+
+				// The check below for edges[0] being in the vertex mapper is
+				// in case we've processed this the other way around
+				// (target -> source) and the number of edges in each direction
+				// are the same. All the graph edges will have been assigned to
+				// an internal edge going the other way, so we don't want to 
+				// process them again
+				var undirectedEdges = layout.getEdgesBetween(vertices[i],
+						cell, false);
+				var directedEdges = layout.getEdgesBetween(vertices[i],
+						cell, true);
+				
+				if (undirectedEdges != null &&
+						undirectedEdges.length > 0 &&
+						this.edgeMapper.get(undirectedEdges[0]) == null &&
+						directedEdges.length * 2 >= undirectedEdges.length)
+				{
+					var internalEdge = new mxGraphHierarchyEdge(undirectedEdges);
+
+					for (var k = 0; k < undirectedEdges.length; k++)
+					{
+						var edge = undirectedEdges[k];
+						this.edgeMapper.put(edge, internalEdge);
+
+						// Resets all point on the edge and disables the edge style
+						// without deleting it from the cell style
+						graph.resetEdge(edge);
+
+					    if (layout.disableEdgeStyle)
+					    {
+					    	layout.setEdgeStyleEnabled(edge, false);
+					    	layout.setOrthogonalEdge(edge,true);
+					    }
+					}
+
+					internalEdge.source = internalVertices[i];
+
+					if (mxUtils.indexOf(internalVertices[i].connectsAsSource, internalEdge) < 0)
+					{
+						internalVertices[i].connectsAsSource.push(internalEdge);
+					}
+				}
+			}
+		}
+
+		// Ensure temp variable is cleared from any previous use
+		internalVertices[i].temp[0] = 0;
+	}
+};
+
+/**
+ * Function: initialRank
+ *
+ * Basic determination of minimum layer ranking by working from from sources
+ * or sinks and working through each node in the relevant edge direction.
+ * Starting at the sinks is basically a longest path layering algorithm.
+*/
+mxGraphHierarchyModel.prototype.initialRank = function()
+{
+	var startNodes = [];
+
+	if (this.roots != null)
+	{
+		for (var i = 0; i < this.roots.length; i++)
+		{
+			var internalNode = this.vertexMapper.get(this.roots[i]);
+
+			if (internalNode != null)
+			{
+				startNodes.push(internalNode);
+			}
+		}
+	}
+
+	var internalNodes = this.vertexMapper.getValues();
+	
+	for (var i=0; i < internalNodes.length; i++)
+	{
+		// Mark the node as not having had a layer assigned
+		internalNodes[i].temp[0] = -1;
+	}
+
+	var startNodesCopy = startNodes.slice();
+
+	while (startNodes.length > 0)
+	{
+		var internalNode = startNodes[0];
+		var layerDeterminingEdges;
+		var edgesToBeMarked;
+
+		layerDeterminingEdges = internalNode.connectsAsTarget;
+		edgesToBeMarked = internalNode.connectsAsSource;
+
+		// flag to keep track of whether or not all layer determining
+		// edges have been scanned
+		var allEdgesScanned = true;
+
+		// Work out the layer of this node from the layer determining
+		// edges. The minimum layer number of any node connected by one of
+		// the layer determining edges variable
+		var minimumLayer = this.SOURCESCANSTARTRANK;
+
+		for (var i = 0; i < layerDeterminingEdges.length; i++)
+		{
+			var internalEdge = layerDeterminingEdges[i];
+
+			if (internalEdge.temp[0] == 5270620)
+			{
+				// This edge has been scanned, get the layer of the
+				// node on the other end
+				var otherNode = internalEdge.source;
+				minimumLayer = Math.min(minimumLayer, otherNode.temp[0] - 1);
+			}
+			else
+			{
+				allEdgesScanned = false;
+
+				break;
+			}
+		}
+
+		// If all edge have been scanned, assign the layer, mark all
+		// edges in the other direction and remove from the nodes list
+		if (allEdgesScanned)
+		{
+			internalNode.temp[0] = minimumLayer;
+			this.maxRank = Math.min(this.maxRank, minimumLayer);
+
+			if (edgesToBeMarked != null)
+			{
+				for (var i = 0; i < edgesToBeMarked.length; i++)
+				{
+					var internalEdge = edgesToBeMarked[i];
+
+					// Assign unique stamp ( y/m/d/h )
+					internalEdge.temp[0] = 5270620;
+
+					// Add node on other end of edge to LinkedList of
+					// nodes to be analysed
+					var otherNode = internalEdge.target;
+
+					// Only add node if it hasn't been assigned a layer
+					if (otherNode.temp[0] == -1)
+					{
+						startNodes.push(otherNode);
+
+						// Mark this other node as neither being
+						// unassigned nor assigned so it isn't
+						// added to this list again, but it's
+						// layer isn't used in any calculation.
+						otherNode.temp[0] = -2;
+					}
+				}
+			}
+
+			startNodes.shift();
+		}
+		else
+		{
+			// Not all the edges have been scanned, get to the back of
+			// the class and put the dunces cap on
+			var removedCell = startNodes.shift();
+			startNodes.push(internalNode);
+
+			if (removedCell == internalNode && startNodes.length == 1)
+			{
+				// This is an error condition, we can't get out of
+				// this loop. It could happen for more than one node
+				// but that's a lot harder to detect. Log the error
+				// TODO make log comment
+				break;
+			}
+		}
+	}
+
+	// Normalize the ranks down from their large starting value to place
+	// at least 1 sink on layer 0
+	for (var i=0; i < internalNodes.length; i++)
+	{
+		// Mark the node as not having had a layer assigned
+		internalNodes[i].temp[0] -= this.maxRank;
+	}
+	
+	// Tighten the rank 0 nodes as far as possible
+	for ( var i = 0; i < startNodesCopy.length; i++)
+	{
+		var internalNode = startNodesCopy[i];
+		var currentMaxLayer = 0;
+		var layerDeterminingEdges = internalNode.connectsAsSource;
+
+		for ( var j = 0; j < layerDeterminingEdges.length; j++)
+		{
+			var internalEdge = layerDeterminingEdges[j];
+			var otherNode = internalEdge.target;
+			internalNode.temp[0] = Math.max(currentMaxLayer,
+					otherNode.temp[0] + 1);
+			currentMaxLayer = internalNode.temp[0];
+		}
+	}
+	
+	// Reset the maxRank to that which would be expected for a from-sink
+	// scan
+	this.maxRank = this.SOURCESCANSTARTRANK - this.maxRank;
+};
+
+/**
+ * Function: fixRanks
+ *
+ * Fixes the layer assignments to the values stored in the nodes. Also needs
+ * to create dummy nodes for edges that cross layers.
+ */
+mxGraphHierarchyModel.prototype.fixRanks = function()
+{
+	var rankList = [];
+	this.ranks = [];
+
+	for (var i = 0; i < this.maxRank + 1; i++)
+	{
+		rankList[i] = [];
+		this.ranks[i] = rankList[i];
+	}
+
+	// Perform a DFS to obtain an initial ordering for each rank.
+	// Without doing this you would end up having to process
+	// crossings for a standard tree.
+	var rootsArray = null;
+
+	if (this.roots != null)
+	{
+		var oldRootsArray = this.roots;
+		rootsArray = [];
+
+		for (var i = 0; i < oldRootsArray.length; i++)
+		{
+			var cell = oldRootsArray[i];
+			var internalNode = this.vertexMapper.get(cell);
+			rootsArray[i] = internalNode;
+		}
+	}
+
+	this.visit(function(parent, node, edge, layer, seen)
+	{
+		if (seen == 0 && node.maxRank < 0 && node.minRank < 0)
+		{
+			rankList[node.temp[0]].push(node);
+			node.maxRank = node.temp[0];
+			node.minRank = node.temp[0];
+
+			// Set temp[0] to the nodes position in the rank
+			node.temp[0] = rankList[node.maxRank].length - 1;
+		}
+
+		if (parent != null && edge != null)
+		{
+			var parentToCellRankDifference = parent.maxRank - node.maxRank;
+
+			if (parentToCellRankDifference > 1)
+			{
+				// There are ranks in between the parent and current cell
+				edge.maxRank = parent.maxRank;
+				edge.minRank = node.maxRank;
+				edge.temp = [];
+				edge.x = [];
+				edge.y = [];
+
+				for (var i = edge.minRank + 1; i < edge.maxRank; i++)
+				{
+					// The connecting edge must be added to the
+					// appropriate ranks
+					rankList[i].push(edge);
+					edge.setGeneralPurposeVariable(i, rankList[i]
+							.length - 1);
+				}
+			}
+		}
+	}, rootsArray, false, null);
+};
+
+/**
+ * Function: visit
+ *
+ * A depth first search through the internal heirarchy model.
+ *
+ * Parameters:
+ *
+ * visitor - The visitor function pattern to be called for each node.
+ * trackAncestors - Whether or not the search is to keep track all nodes
+ * directly above this one in the search path.
+ */
+mxGraphHierarchyModel.prototype.visit = function(visitor, dfsRoots, trackAncestors, seenNodes)
+{
+	// Run dfs through on all roots
+	if (dfsRoots != null)
+	{
+		for (var i = 0; i < dfsRoots.length; i++)
+		{
+			var internalNode = dfsRoots[i];
+
+			if (internalNode != null)
+			{
+				if (seenNodes == null)
+				{
+					seenNodes = new Object();
+				}
+
+				if (trackAncestors)
+				{
+					// Set up hash code for root
+					internalNode.hashCode = [];
+					internalNode.hashCode[0] = this.dfsCount;
+					internalNode.hashCode[1] = i;
+					this.extendedDfs(null, internalNode, null, visitor, seenNodes,
+							internalNode.hashCode, i, 0);
+				}
+				else
+				{
+					this.dfs(null, internalNode, null, visitor, seenNodes, 0);
+				}
+			}
+		}
+
+		this.dfsCount++;
+	}
+};
+
+/**
+ * Function: dfs
+ *
+ * Performs a depth first search on the internal hierarchy model
+ *
+ * Parameters:
+ *
+ * parent - the parent internal node of the current internal node
+ * root - the current internal node
+ * connectingEdge - the internal edge connecting the internal node and the parent
+ * internal node, if any
+ * visitor - the visitor pattern to be called for each node
+ * seen - a set of all nodes seen by this dfs a set of all of the
+ * ancestor node of the current node
+ * layer - the layer on the dfs tree ( not the same as the model ranks )
+ */
+mxGraphHierarchyModel.prototype.dfs = function(parent, root, connectingEdge, visitor, seen, layer)
+{
+	if (root != null)
+	{
+		var rootId = root.id;
+
+		if (seen[rootId] == null)
+		{
+			seen[rootId] = root;
+			visitor(parent, root, connectingEdge, layer, 0);
+
+			// Copy the connects as source list so that visitors
+			// can change the original for edge direction inversions
+			var outgoingEdges = root.connectsAsSource.slice();
+			
+			for (var i = 0; i< outgoingEdges.length; i++)
+			{
+				var internalEdge = outgoingEdges[i];
+				var targetNode = internalEdge.target;
+
+				// Root check is O(|roots|)
+				this.dfs(root, targetNode, internalEdge, visitor, seen,
+						layer + 1);
+			}
+		}
+		else
+		{
+			// Use the int field to indicate this node has been seen
+			visitor(parent, root, connectingEdge, layer, 1);
+		}
+	}
+};
+
+/**
+ * Function: extendedDfs
+ *
+ * Performs a depth first search on the internal hierarchy model. This dfs
+ * extends the default version by keeping track of cells ancestors, but it
+ * should be only used when necessary because of it can be computationally
+ * intensive for deep searches.
+ *
+ * Parameters:
+ *
+ * parent - the parent internal node of the current internal node
+ * root - the current internal node
+ * connectingEdge - the internal edge connecting the internal node and the parent
+ * internal node, if any
+ * visitor - the visitor pattern to be called for each node
+ * seen - a set of all nodes seen by this dfs
+ * ancestors - the parent hash code
+ * childHash - the new hash code for this node
+ * layer - the layer on the dfs tree ( not the same as the model ranks )
+ */
+mxGraphHierarchyModel.prototype.extendedDfs = function(parent, root, connectingEdge, visitor, seen, ancestors, childHash, layer)
+{
+	// Explanation of custom hash set. Previously, the ancestors variable
+	// was passed through the dfs as a HashSet. The ancestors were copied
+	// into a new HashSet and when the new child was processed it was also
+	// added to the set. If the current node was in its ancestor list it
+	// meant there is a cycle in the graph and this information is passed
+	// to the visitor.visit() in the seen parameter. The HashSet clone was
+	// very expensive on CPU so a custom hash was developed using primitive
+	// types. temp[] couldn't be used so hashCode[] was added to each node.
+	// Each new child adds another int to the array, copying the prefix
+	// from its parent. Child of the same parent add different ints (the
+	// limit is therefore 2^32 children per parent...). If a node has a
+	// child with the hashCode already set then the child code is compared
+	// to the same portion of the current nodes array. If they match there
+	// is a loop.
+	// Note that the basic mechanism would only allow for 1 use of this
+	// functionality, so the root nodes have two ints. The second int is
+	// incremented through each node root and the first is incremented
+	// through each run of the dfs algorithm (therefore the dfs is not
+	// thread safe). The hash code of each node is set if not already set,
+	// or if the first int does not match that of the current run.
+	if (root != null)
+	{
+		if (parent != null)
+		{
+			// Form this nodes hash code if necessary, that is, if the
+			// hashCode variable has not been initialized or if the
+			// start of the parent hash code does not equal the start of
+			// this nodes hash code, indicating the code was set on a
+			// previous run of this dfs.
+			if (root.hashCode == null ||
+				root.hashCode[0] != parent.hashCode[0])
+			{
+				var hashCodeLength = parent.hashCode.length + 1;
+				root.hashCode = parent.hashCode.slice();
+				root.hashCode[hashCodeLength - 1] = childHash;
+			}
+		}
+
+		var rootId = root.id;
+
+		if (seen[rootId] == null)
+		{
+			seen[rootId] = root;
+			visitor(parent, root, connectingEdge, layer, 0);
+
+			// Copy the connects as source list so that visitors
+			// can change the original for edge direction inversions
+			var outgoingEdges = root.connectsAsSource.slice();
+
+			for (var i = 0; i < outgoingEdges.length; i++)
+			{
+				var internalEdge = outgoingEdges[i];
+				var targetNode = internalEdge.target;
+
+				// Root check is O(|roots|)
+				this.extendedDfs(root, targetNode, internalEdge, visitor, seen,
+						root.hashCode, i, layer + 1);
+			}
+		}
+		else
+		{
+			// Use the int field to indicate this node has been seen
+			visitor(parent, root, connectingEdge, layer, 1);
+		}
+	}
+};
+/**
+ * Copyright (c) 2006-2016, JGraph Ltd
+ * Copyright (c) 2006-2016, Gaudenz Alder
+ */
+/**
+ * Class: mxSwimlaneModel
+ *
+ * Internal model of a hierarchical graph. This model stores nodes and edges
+ * equivalent to the real graph nodes and edges, but also stores the rank of the
+ * cells, the order within the ranks and the new candidate locations of cells.
+ * The internal model also reverses edge direction were appropriate , ignores
+ * self-loop and groups parallels together under one edge object.
+ *
+ * Constructor: mxSwimlaneModel
+ *
+ * Creates an internal ordered graph model using the vertices passed in. If
+ * there are any, leftward edge need to be inverted in the internal model
+ *
+ * Arguments:
+ *
+ * graph - the facade describing the graph to be operated on
+ * vertices - the vertices for this hierarchy
+ * ordered - whether or not the vertices are already ordered
+ * deterministic - whether or not this layout should be deterministic on each
+ * tightenToSource - whether or not to tighten vertices towards the sources
+ * scanRanksFromSinks - Whether rank assignment is from the sinks or sources.
+ * usage
+ */
+function mxSwimlaneModel(layout, vertices, roots, parent, tightenToSource)
+{
+	var graph = layout.getGraph();
+	this.tightenToSource = tightenToSource;
+	this.roots = roots;
+	this.parent = parent;
+
+	// map of cells to internal cell needed for second run through
+	// to setup the sink of edges correctly
+	this.vertexMapper = new mxDictionary();
+	this.edgeMapper = new mxDictionary();
+	this.maxRank = 0;
+	var internalVertices = [];
+
+	if (vertices == null)
+	{
+		vertices = this.graph.getChildVertices(parent);
+	}
+
+	this.maxRank = this.SOURCESCANSTARTRANK;
+	// map of cells to internal cell needed for second run through
+	// to setup the sink of edges correctly. Guess size by number
+	// of edges is roughly same as number of vertices.
+	this.createInternalCells(layout, vertices, internalVertices);
+
+	// Go through edges set their sink values. Also check the
+	// ordering if and invert edges if necessary
+	for (var i = 0; i < vertices.length; i++)
+	{
+		var edges = internalVertices[i].connectsAsSource;
+
+		for (var j = 0; j < edges.length; j++)
+		{
+			var internalEdge = edges[j];
+			var realEdges = internalEdge.edges;
+
+			// Only need to process the first real edge, since
+			// all the edges connect to the same other vertex
+			if (realEdges != null && realEdges.length > 0)
+			{
+				var realEdge = realEdges[0];
+				var targetCell = layout.getVisibleTerminal(
+						realEdge, false);
+				var internalTargetCell = this.vertexMapper.get(targetCell);
+
+				if (internalVertices[i] == internalTargetCell)
+				{
+					// If there are parallel edges going between two vertices and not all are in the same direction
+					// you can have navigated across one direction when doing the cycle reversal that isn't the same
+					// direction as the first real edge in the array above. When that happens the if above catches
+					// that and we correct the target cell before continuing.
+					// This branch only detects this single case
+					targetCell = layout.getVisibleTerminal(
+							realEdge, true);
+					internalTargetCell = this.vertexMapper.get(targetCell);
+				}
+
+				if (internalTargetCell != null
+						&& internalVertices[i] != internalTargetCell)
+				{
+					internalEdge.target = internalTargetCell;
+
+					if (internalTargetCell.connectsAsTarget.length == 0)
+					{
+						internalTargetCell.connectsAsTarget = [];
+					}
+
+					if (mxUtils.indexOf(internalTargetCell.connectsAsTarget, internalEdge) < 0)
+					{
+						internalTargetCell.connectsAsTarget.push(internalEdge);
+					}
+				}
+			}
+		}
+
+		// Use the temp variable in the internal nodes to mark this
+		// internal vertex as having been visited.
+		internalVertices[i].temp[0] = 1;
+	}
+};
+
+/**
+ * Variable: maxRank
+ *
+ * Stores the largest rank number allocated
+ */
+mxSwimlaneModel.prototype.maxRank = null;
+
+/**
+ * Variable: vertexMapper
+ *
+ * Map from graph vertices to internal model nodes.
+ */
+mxSwimlaneModel.prototype.vertexMapper = null;
+
+/**
+ * Variable: edgeMapper
+ *
+ * Map from graph edges to internal model edges
+ */
+mxSwimlaneModel.prototype.edgeMapper = null;
+
+/**
+ * Variable: ranks
+ *
+ * Mapping from rank number to actual rank
+ */
+mxSwimlaneModel.prototype.ranks = null;
+
+/**
+ * Variable: roots
+ *
+ * Store of roots of this hierarchy model, these are real graph cells, not
+ * internal cells
+ */
+mxSwimlaneModel.prototype.roots = null;
+
+/**
+ * Variable: parent
+ *
+ * The parent cell whose children are being laid out
+ */
+mxSwimlaneModel.prototype.parent = null;
+
+/**
+ * Variable: dfsCount
+ *
+ * Count of the number of times the ancestor dfs has been used.
+ */
+mxSwimlaneModel.prototype.dfsCount = 0;
+
+/**
+ * Variable: SOURCESCANSTARTRANK
+ *
+ * High value to start source layering scan rank value from.
+ */
+mxSwimlaneModel.prototype.SOURCESCANSTARTRANK = 100000000;
+
+/**
+ * Variable: tightenToSource
+ *
+ * Whether or not to tighten the assigned ranks of vertices up towards
+ * the source cells.
+ */
+mxGraphHierarchyModel.prototype.tightenToSource = false;
+
+/**
+ * Variable: ranksPerGroup
+ *
+ * An array of the number of ranks within each swimlane
+ */
+mxSwimlaneModel.prototype.ranksPerGroup = null;
+
+/**
+ * Function: createInternalCells
+ *
+ * Creates all edges in the internal model
+ *
+ * Parameters:
+ *
+ * layout - Reference to the <mxHierarchicalLayout> algorithm.
+ * vertices - Array of <mxCells> that represent the vertices whom are to
+ * have an internal representation created.
+ * internalVertices - The array of <mxGraphHierarchyNodes> to have their
+ * information filled in using the real vertices.
+ */
+mxSwimlaneModel.prototype.createInternalCells = function(layout, vertices, internalVertices)
+{
+	var graph = layout.getGraph();
+	var swimlanes = layout.swimlanes;
+
+	// Create internal edges
+	for (var i = 0; i < vertices.length; i++)
+	{
+		internalVertices[i] = new mxGraphHierarchyNode(vertices[i]);
+		this.vertexMapper.put(vertices[i], internalVertices[i]);
+		internalVertices[i].swimlaneIndex = -1;
+
+		for (var ii = 0; ii < swimlanes.length; ii++)
+		{
+			if (graph.model.getParent(vertices[i]) == swimlanes[ii])
+			{
+				internalVertices[i].swimlaneIndex = ii;
+				break;
+			}
+		}
+
+		// If the layout is deterministic, order the cells
+		//List outgoingCells = graph.getNeighbours(vertices[i], deterministic);
+		var conns = layout.getEdges(vertices[i]);
+		internalVertices[i].connectsAsSource = [];
+
+		// Create internal edges, but don't do any rank assignment yet
+		// First use the information from the greedy cycle remover to
+		// invert the leftward edges internally
+		for (var j = 0; j < conns.length; j++)
+		{
+			var cell = layout.getVisibleTerminal(conns[j], false);
+
+			// Looking for outgoing edges only
+			if (cell != vertices[i] && layout.graph.model.isVertex(cell) &&
+					!layout.isVertexIgnored(cell))
+			{
+				// We process all edge between this source and its targets
+				// If there are edges going both ways, we need to collect
+				// them all into one internal edges to avoid looping problems
+				// later. We assume this direction (source -> target) is the 
+				// natural direction if at least half the edges are going in
+				// that direction.
+
+				// The check below for edges[0] being in the vertex mapper is
+				// in case we've processed this the other way around
+				// (target -> source) and the number of edges in each direction
+				// are the same. All the graph edges will have been assigned to
+				// an internal edge going the other way, so we don't want to 
+				// process them again
+				var undirectedEdges = layout.getEdgesBetween(vertices[i],
+						cell, false);
+				var directedEdges = layout.getEdgesBetween(vertices[i],
+						cell, true);
+				
+				if (undirectedEdges != null &&
+						undirectedEdges.length > 0 &&
+						this.edgeMapper.get(undirectedEdges[0]) == null &&
+						directedEdges.length * 2 >= undirectedEdges.length)
+				{
+					var internalEdge = new mxGraphHierarchyEdge(undirectedEdges);
+
+					for (var k = 0; k < undirectedEdges.length; k++)
+					{
+						var edge = undirectedEdges[k];
+						this.edgeMapper.put(edge, internalEdge);
+
+						// Resets all point on the edge and disables the edge style
+						// without deleting it from the cell style
+						graph.resetEdge(edge);
+
+					    if (layout.disableEdgeStyle)
+					    {
+					    	layout.setEdgeStyleEnabled(edge, false);
+					    	layout.setOrthogonalEdge(edge,true);
+					    }
+					}
+
+					internalEdge.source = internalVertices[i];
+
+					if (mxUtils.indexOf(internalVertices[i].connectsAsSource, internalEdge) < 0)
+					{
+						internalVertices[i].connectsAsSource.push(internalEdge);
+					}
+				}
+			}
+		}
+
+		// Ensure temp variable is cleared from any previous use
+		internalVertices[i].temp[0] = 0;
+	}
+};
+
+/**
+ * Function: initialRank
+ *
+ * Basic determination of minimum layer ranking by working from from sources
+ * or sinks and working through each node in the relevant edge direction.
+ * Starting at the sinks is basically a longest path layering algorithm.
+*/
+mxSwimlaneModel.prototype.initialRank = function()
+{
+	this.ranksPerGroup = [];
+	
+	var startNodes = [];
+	var seen = new Object();
+
+	if (this.roots != null)
+	{
+		for (var i = 0; i < this.roots.length; i++)
+		{
+			var internalNode = this.vertexMapper.get(this.roots[i]);
+			this.maxChainDfs(null, internalNode, null, seen, 0);
+
+			if (internalNode != null)
+			{
+				startNodes.push(internalNode);
+			}
+		}
+	}
+
+	// Calculate the lower and upper rank bounds of each swimlane
+	var lowerRank = [];
+	var upperRank = [];
+	
+	for (var i = this.ranksPerGroup.length - 1; i >= 0; i--)
+	{
+		if (i == this.ranksPerGroup.length - 1)
+		{
+			lowerRank[i] = 0;
+		}
+		else
+		{
+			lowerRank[i] = upperRank[i+1] + 1;
+		}
+		
+		upperRank[i] = lowerRank[i] + this.ranksPerGroup[i];
+	}
+	
+	this.maxRank = upperRank[0];
+
+	var internalNodes = this.vertexMapper.getValues();
+	
+	for (var i=0; i < internalNodes.length; i++)
+	{
+		// Mark the node as not having had a layer assigned
+		internalNodes[i].temp[0] = -1;
+	}
+
+	var startNodesCopy = startNodes.slice();
+	
+	while (startNodes.length > 0)
+	{
+		var internalNode = startNodes[0];
+		var layerDeterminingEdges;
+		var edgesToBeMarked;
+
+		layerDeterminingEdges = internalNode.connectsAsTarget;
+		edgesToBeMarked = internalNode.connectsAsSource;
+
+		// flag to keep track of whether or not all layer determining
+		// edges have been scanned
+		var allEdgesScanned = true;
+
+		// Work out the layer of this node from the layer determining
+		// edges. The minimum layer number of any node connected by one of
+		// the layer determining edges variable
+		var minimumLayer = upperRank[0];
+
+		for (var i = 0; i < layerDeterminingEdges.length; i++)
+		{
+			var internalEdge = layerDeterminingEdges[i];
+
+			if (internalEdge.temp[0] == 5270620)
+			{
+				// This edge has been scanned, get the layer of the
+				// node on the other end
+				var otherNode = internalEdge.source;
+				minimumLayer = Math.min(minimumLayer, otherNode.temp[0] - 1);
+			}
+			else
+			{
+				allEdgesScanned = false;
+
+				break;
+			}
+		}
+
+		// If all edge have been scanned, assign the layer, mark all
+		// edges in the other direction and remove from the nodes list
+		if (allEdgesScanned)
+		{
+			if (minimumLayer > upperRank[internalNode.swimlaneIndex])
+			{
+				minimumLayer = upperRank[internalNode.swimlaneIndex];
+			}
+
+			internalNode.temp[0] = minimumLayer;
+
+			if (edgesToBeMarked != null)
+			{
+				for (var i = 0; i < edgesToBeMarked.length; i++)
+				{
+					var internalEdge = edgesToBeMarked[i];
+
+					// Assign unique stamp ( y/m/d/h )
+					internalEdge.temp[0] = 5270620;
+
+					// Add node on other end of edge to LinkedList of
+					// nodes to be analysed
+					var otherNode = internalEdge.target;
+
+					// Only add node if it hasn't been assigned a layer
+					if (otherNode.temp[0] == -1)
+					{
+						startNodes.push(otherNode);
+
+						// Mark this other node as neither being
+						// unassigned nor assigned so it isn't
+						// added to this list again, but it's
+						// layer isn't used in any calculation.
+						otherNode.temp[0] = -2;
+					}
+				}
+			}
+
+			startNodes.shift();
+		}
+		else
+		{
+			// Not all the edges have been scanned, get to the back of
+			// the class and put the dunces cap on
+			var removedCell = startNodes.shift();
+			startNodes.push(internalNode);
+
+			if (removedCell == internalNode && startNodes.length == 1)
+			{
+				// This is an error condition, we can't get out of
+				// this loop. It could happen for more than one node
+				// but that's a lot harder to detect. Log the error
+				// TODO make log comment
+				break;
+			}
+		}
+	}
+
+	// Normalize the ranks down from their large starting value to place
+	// at least 1 sink on layer 0
+//	for (var key in this.vertexMapper)
+//	{
+//		var internalNode = this.vertexMapper[key];
+//		// Mark the node as not having had a layer assigned
+//		internalNode.temp[0] -= this.maxRank;
+//	}
+	
+	// Tighten the rank 0 nodes as far as possible
+//	for ( var i = 0; i < startNodesCopy.length; i++)
+//	{
+//		var internalNode = startNodesCopy[i];
+//		var currentMaxLayer = 0;
+//		var layerDeterminingEdges = internalNode.connectsAsSource;
+//
+//		for ( var j = 0; j < layerDeterminingEdges.length; j++)
+//		{
+//			var internalEdge = layerDeterminingEdges[j];
+//			var otherNode = internalEdge.target;
+//			internalNode.temp[0] = Math.max(currentMaxLayer,
+//					otherNode.temp[0] + 1);
+//			currentMaxLayer = internalNode.temp[0];
+//		}
+//	}
+};
+
+/**
+ * Function: maxChainDfs
+ *
+ * Performs a depth first search on the internal hierarchy model. This dfs
+ * extends the default version by keeping track of chains within groups.
+ * Any cycles should be removed prior to running, but previously seen cells
+ * are ignored.
+ *
+ * Parameters:
+ *
+ * parent - the parent internal node of the current internal node
+ * root - the current internal node
+ * connectingEdge - the internal edge connecting the internal node and the parent
+ * internal node, if any
+ * seen - a set of all nodes seen by this dfs
+ * chainCount - the number of edges in the chain of vertices going through
+ * the current swimlane
+ */
+mxSwimlaneModel.prototype.maxChainDfs = function(parent, root, connectingEdge, seen, chainCount)
+{
+	if (root != null)
+	{
+		var rootId = mxCellPath.create(root.cell);
+
+		if (seen[rootId] == null)
+		{
+			seen[rootId] = root;
+			var slIndex = root.swimlaneIndex;
+			
+			if (this.ranksPerGroup[slIndex] == null || this.ranksPerGroup[slIndex] < chainCount)
+			{
+				this.ranksPerGroup[slIndex] = chainCount;
+			}
+
+			// Copy the connects as source list so that visitors
+			// can change the original for edge direction inversions
+			var outgoingEdges = root.connectsAsSource.slice();
+
+			for (var i = 0; i < outgoingEdges.length; i++)
+			{
+				var internalEdge = outgoingEdges[i];
+				var targetNode = internalEdge.target;
+
+				// Only navigate in source->target direction within the same
+				// swimlane, or from a lower index swimlane to a higher one
+				if (root.swimlaneIndex < targetNode.swimlaneIndex)
+				{
+					this.maxChainDfs(root, targetNode, internalEdge, mxUtils.clone(seen, null , true), 0);
+				}
+				else if (root.swimlaneIndex == targetNode.swimlaneIndex)
+				{
+					this.maxChainDfs(root, targetNode, internalEdge, mxUtils.clone(seen, null , true), chainCount + 1);
+				}
+			}
+		}
+	}
+};
+
+/**
+ * Function: fixRanks
+ *
+ * Fixes the layer assignments to the values stored in the nodes. Also needs
+ * to create dummy nodes for edges that cross layers.
+ */
+mxSwimlaneModel.prototype.fixRanks = function()
+{
+	var rankList = [];
+	this.ranks = [];
+
+	for (var i = 0; i < this.maxRank + 1; i++)
+	{
+		rankList[i] = [];
+		this.ranks[i] = rankList[i];
+	}
+
+	// Perform a DFS to obtain an initial ordering for each rank.
+	// Without doing this you would end up having to process
+	// crossings for a standard tree.
+	var rootsArray = null;
+
+	if (this.roots != null)
+	{
+		var oldRootsArray = this.roots;
+		rootsArray = [];
+
+		for (var i = 0; i < oldRootsArray.length; i++)
+		{
+			var cell = oldRootsArray[i];
+			var internalNode = this.vertexMapper.get(cell);
+			rootsArray[i] = internalNode;
+		}
+	}
+
+	this.visit(function(parent, node, edge, layer, seen)
+	{
+		if (seen == 0 && node.maxRank < 0 && node.minRank < 0)
+		{
+			rankList[node.temp[0]].push(node);
+			node.maxRank = node.temp[0];
+			node.minRank = node.temp[0];
+
+			// Set temp[0] to the nodes position in the rank
+			node.temp[0] = rankList[node.maxRank].length - 1;
+		}
+
+		if (parent != null && edge != null)
+		{
+			var parentToCellRankDifference = parent.maxRank - node.maxRank;
+
+			if (parentToCellRankDifference > 1)
+			{
+				// There are ranks in between the parent and current cell
+				edge.maxRank = parent.maxRank;
+				edge.minRank = node.maxRank;
+				edge.temp = [];
+				edge.x = [];
+				edge.y = [];
+
+				for (var i = edge.minRank + 1; i < edge.maxRank; i++)
+				{
+					// The connecting edge must be added to the
+					// appropriate ranks
+					rankList[i].push(edge);
+					edge.setGeneralPurposeVariable(i, rankList[i]
+							.length - 1);
+				}
+			}
+		}
+	}, rootsArray, false, null);
+};
+
+/**
+ * Function: visit
+ *
+ * A depth first search through the internal heirarchy model.
+ *
+ * Parameters:
+ *
+ * visitor - The visitor function pattern to be called for each node.
+ * trackAncestors - Whether or not the search is to keep track all nodes
+ * directly above this one in the search path.
+ */
+mxSwimlaneModel.prototype.visit = function(visitor, dfsRoots, trackAncestors, seenNodes)
+{
+	// Run dfs through on all roots
+	if (dfsRoots != null)
+	{
+		for (var i = 0; i < dfsRoots.length; i++)
+		{
+			var internalNode = dfsRoots[i];
+
+			if (internalNode != null)
+			{
+				if (seenNodes == null)
+				{
+					seenNodes = new Object();
+				}
+
+				if (trackAncestors)
+				{
+					// Set up hash code for root
+					internalNode.hashCode = [];
+					internalNode.hashCode[0] = this.dfsCount;
+					internalNode.hashCode[1] = i;
+					this.extendedDfs(null, internalNode, null, visitor, seenNodes,
+							internalNode.hashCode, i, 0);
+				}
+				else
+				{
+					this.dfs(null, internalNode, null, visitor, seenNodes, 0);
+				}
+			}
+		}
+
+		this.dfsCount++;
+	}
+};
+
+/**
+ * Function: dfs
+ *
+ * Performs a depth first search on the internal hierarchy model
+ *
+ * Parameters:
+ *
+ * parent - the parent internal node of the current internal node
+ * root - the current internal node
+ * connectingEdge - the internal edge connecting the internal node and the parent
+ * internal node, if any
+ * visitor - the visitor pattern to be called for each node
+ * seen - a set of all nodes seen by this dfs a set of all of the
+ * ancestor node of the current node
+ * layer - the layer on the dfs tree ( not the same as the model ranks )
+ */
+mxSwimlaneModel.prototype.dfs = function(parent, root, connectingEdge, visitor, seen, layer)
+{
+	if (root != null)
+	{
+		var rootId = root.id;
+
+		if (seen[rootId] == null)
+		{
+			seen[rootId] = root;
+			visitor(parent, root, connectingEdge, layer, 0);
+
+			// Copy the connects as source list so that visitors
+			// can change the original for edge direction inversions
+			var outgoingEdges = root.connectsAsSource.slice();
+			
+			for (var i = 0; i< outgoingEdges.length; i++)
+			{
+				var internalEdge = outgoingEdges[i];
+				var targetNode = internalEdge.target;
+
+				// Root check is O(|roots|)
+				this.dfs(root, targetNode, internalEdge, visitor, seen,
+						layer + 1);
+			}
+		}
+		else
+		{
+			// Use the int field to indicate this node has been seen
+			visitor(parent, root, connectingEdge, layer, 1);
+		}
+	}
+};
+
+/**
+ * Function: extendedDfs
+ *
+ * Performs a depth first search on the internal hierarchy model. This dfs
+ * extends the default version by keeping track of cells ancestors, but it
+ * should be only used when necessary because of it can be computationally
+ * intensive for deep searches.
+ *
+ * Parameters:
+ *
+ * parent - the parent internal node of the current internal node
+ * root - the current internal node
+ * connectingEdge - the internal edge connecting the internal node and the parent
+ * internal node, if any
+ * visitor - the visitor pattern to be called for each node
+ * seen - a set of all nodes seen by this dfs
+ * ancestors - the parent hash code
+ * childHash - the new hash code for this node
+ * layer - the layer on the dfs tree ( not the same as the model ranks )
+ */
+mxSwimlaneModel.prototype.extendedDfs = function(parent, root, connectingEdge, visitor, seen, ancestors, childHash, layer)
+{
+	// Explanation of custom hash set. Previously, the ancestors variable
+	// was passed through the dfs as a HashSet. The ancestors were copied
+	// into a new HashSet and when the new child was processed it was also
+	// added to the set. If the current node was in its ancestor list it
+	// meant there is a cycle in the graph and this information is passed
+	// to the visitor.visit() in the seen parameter. The HashSet clone was
+	// very expensive on CPU so a custom hash was developed using primitive
+	// types. temp[] couldn't be used so hashCode[] was added to each node.
+	// Each new child adds another int to the array, copying the prefix
+	// from its parent. Child of the same parent add different ints (the
+	// limit is therefore 2^32 children per parent...). If a node has a
+	// child with the hashCode already set then the child code is compared
+	// to the same portion of the current nodes array. If they match there
+	// is a loop.
+	// Note that the basic mechanism would only allow for 1 use of this
+	// functionality, so the root nodes have two ints. The second int is
+	// incremented through each node root and the first is incremented
+	// through each run of the dfs algorithm (therefore the dfs is not
+	// thread safe). The hash code of each node is set if not already set,
+	// or if the first int does not match that of the current run.
+	if (root != null)
+	{
+		if (parent != null)
+		{
+			// Form this nodes hash code if necessary, that is, if the
+			// hashCode variable has not been initialized or if the
+			// start of the parent hash code does not equal the start of
+			// this nodes hash code, indicating the code was set on a
+			// previous run of this dfs.
+			if (root.hashCode == null ||
+				root.hashCode[0] != parent.hashCode[0])
+			{
+				var hashCodeLength = parent.hashCode.length + 1;
+				root.hashCode = parent.hashCode.slice();
+				root.hashCode[hashCodeLength - 1] = childHash;
+			}
+		}
+
+		var rootId = root.id;
+
+		if (seen[rootId] == null)
+		{
+			seen[rootId] = root;
+			visitor(parent, root, connectingEdge, layer, 0);
+
+			// Copy the connects as source list so that visitors
+			// can change the original for edge direction inversions
+			var outgoingEdges = root.connectsAsSource.slice();
+			var incomingEdges = root.connectsAsTarget.slice();
+
+			for (var i = 0; i < outgoingEdges.length; i++)
+			{
+				var internalEdge = outgoingEdges[i];
+				var targetNode = internalEdge.target;
+				
+				// Only navigate in source->target direction within the same
+				// swimlane, or from a lower index swimlane to a higher one
+				if (root.swimlaneIndex <= targetNode.swimlaneIndex)
+				{
+					this.extendedDfs(root, targetNode, internalEdge, visitor, seen,
+							root.hashCode, i, layer + 1);
+				}
+			}
+			
+			for (var i = 0; i < incomingEdges.length; i++)
+			{
+				var internalEdge = incomingEdges[i];
+				var targetNode = internalEdge.source;
+
+				// Only navigate in target->source direction from a lower index 
+				// swimlane to a higher one
+				if (root.swimlaneIndex < targetNode.swimlaneIndex)
+				{
+					this.extendedDfs(root, targetNode, internalEdge, visitor, seen,
+							root.hashCode, i, layer + 1);
+				}
+			}
+		}
+		else
+		{
+			// Use the int field to indicate this node has been seen
+			visitor(parent, root, connectingEdge, layer, 1);
+		}
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxHierarchicalLayoutStage
+ * 
+ * The specific layout interface for hierarchical layouts. It adds a
+ * <code>run</code> method with a parameter for the hierarchical layout model
+ * that is shared between the layout stages.
+ * 
+ * Constructor: mxHierarchicalLayoutStage
+ *
+ * Constructs a new hierarchical layout stage.
+ */
+function mxHierarchicalLayoutStage() { };
+
+/**
+ * Function: execute
+ * 
+ * Takes the graph detail and configuration information within the facade
+ * and creates the resulting laid out graph within that facade for further
+ * use.
+ */
+mxHierarchicalLayoutStage.prototype.execute = function(parent) { };
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxMedianHybridCrossingReduction
+ * 
+ * Sets the horizontal locations of node and edge dummy nodes on each layer.
+ * Uses median down and up weighings as well heuristic to straighten edges as
+ * far as possible.
+ * 
+ * Constructor: mxMedianHybridCrossingReduction
+ *
+ * Creates a coordinate assignment.
+ * 
+ * Arguments:
+ * 
+ * intraCellSpacing - the minimum buffer between cells on the same rank
+ * interRankCellSpacing - the minimum distance between cells on adjacent ranks
+ * orientation - the position of the root node(s) relative to the graph
+ * initialX - the leftmost coordinate node placement starts at
+ */
+function mxMedianHybridCrossingReduction(layout)
+{
+	this.layout = layout;
+};
+
+/**
+ * Extends mxMedianHybridCrossingReduction.
+ */
+mxMedianHybridCrossingReduction.prototype = new mxHierarchicalLayoutStage();
+mxMedianHybridCrossingReduction.prototype.constructor = mxMedianHybridCrossingReduction;
+
+/**
+ * Variable: layout
+ * 
+ * Reference to the enclosing <mxHierarchicalLayout>.
+ */
+mxMedianHybridCrossingReduction.prototype.layout = null;
+
+/**
+ * Variable: maxIterations
+ * 
+ * The maximum number of iterations to perform whilst reducing edge
+ * crossings. Default is 24.
+ */
+mxMedianHybridCrossingReduction.prototype.maxIterations = 24;
+
+/**
+ * Variable: nestedBestRanks
+ * 
+ * Stores each rank as a collection of cells in the best order found for
+ * each layer so far
+ */
+mxMedianHybridCrossingReduction.prototype.nestedBestRanks = null;
+
+/**
+ * Variable: currentBestCrossings
+ * 
+ * The total number of crossings found in the best configuration so far
+ */
+mxMedianHybridCrossingReduction.prototype.currentBestCrossings = 0;
+
+/**
+ * Variable: iterationsWithoutImprovement
+ * 
+ * The total number of crossings found in the best configuration so far
+ */
+mxMedianHybridCrossingReduction.prototype.iterationsWithoutImprovement = 0;
+
+/**
+ * Variable: maxNoImprovementIterations
+ * 
+ * The total number of crossings found in the best configuration so far
+ */
+mxMedianHybridCrossingReduction.prototype.maxNoImprovementIterations = 2;
+
+/**
+ * Function: execute
+ * 
+ * Performs a vertex ordering within ranks as described by Gansner et al
+ * 1993
+ */
+mxMedianHybridCrossingReduction.prototype.execute = function(parent)
+{
+	var model = this.layout.getModel();
+
+	// Stores initial ordering as being the best one found so far
+	this.nestedBestRanks = [];
+	
+	for (var i = 0; i < model.ranks.length; i++)
+	{
+		this.nestedBestRanks[i] = model.ranks[i].slice();
+	}
+
+	var iterationsWithoutImprovement = 0;
+	var currentBestCrossings = this.calculateCrossings(model);
+
+	for (var i = 0; i < this.maxIterations &&
+		iterationsWithoutImprovement < this.maxNoImprovementIterations; i++)
+	{
+		this.weightedMedian(i, model);
+		this.transpose(i, model);
+		var candidateCrossings = this.calculateCrossings(model);
+
+		if (candidateCrossings < currentBestCrossings)
+		{
+			currentBestCrossings = candidateCrossings;
+			iterationsWithoutImprovement = 0;
+
+			// Store the current rankings as the best ones
+			for (var j = 0; j < this.nestedBestRanks.length; j++)
+			{
+				var rank = model.ranks[j];
+
+				for (var k = 0; k < rank.length; k++)
+				{
+					var cell = rank[k];
+					this.nestedBestRanks[j][cell.getGeneralPurposeVariable(j)] = cell;
+				}
+			}
+		}
+		else
+		{
+			// Increase count of iterations where we haven't improved the
+			// layout
+			iterationsWithoutImprovement++;
+
+			// Restore the best values to the cells
+			for (var j = 0; j < this.nestedBestRanks.length; j++)
+			{
+				var rank = model.ranks[j];
+				
+				for (var k = 0; k < rank.length; k++)
+				{
+					var cell = rank[k];
+					cell.setGeneralPurposeVariable(j, k);
+				}
+			}
+		}
+		
+		if (currentBestCrossings == 0)
+		{
+			// Do nothing further
+			break;
+		}
+	}
+
+	// Store the best rankings but in the model
+	var ranks = [];
+	var rankList = [];
+
+	for (var i = 0; i < model.maxRank + 1; i++)
+	{
+		rankList[i] = [];
+		ranks[i] = rankList[i];
+	}
+
+	for (var i = 0; i < this.nestedBestRanks.length; i++)
+	{
+		for (var j = 0; j < this.nestedBestRanks[i].length; j++)
+		{
+			rankList[i].push(this.nestedBestRanks[i][j]);
+		}
+	}
+
+	model.ranks = ranks;
+};
+
+
+/**
+ * Function: calculateCrossings
+ * 
+ * Calculates the total number of edge crossing in the current graph.
+ * Returns the current number of edge crossings in the hierarchy graph
+ * model in the current candidate layout
+ * 
+ * Parameters:
+ * 
+ * model - the internal model describing the hierarchy
+ */
+mxMedianHybridCrossingReduction.prototype.calculateCrossings = function(model)
+{
+	var numRanks = model.ranks.length;
+	var totalCrossings = 0;
+
+	for (var i = 1; i < numRanks; i++)
+	{
+		totalCrossings += this.calculateRankCrossing(i, model);
+	}
+	
+	return totalCrossings;
+};
+
+/**
+ * Function: calculateRankCrossing
+ * 
+ * Calculates the number of edges crossings between the specified rank and
+ * the rank below it. Returns the number of edges crossings with the rank
+ * beneath
+ * 
+ * Parameters:
+ * 
+ * i -  the topmost rank of the pair ( higher rank value )
+ * model - the internal model describing the hierarchy
+ */
+mxMedianHybridCrossingReduction.prototype.calculateRankCrossing = function(i, model)
+{
+	var totalCrossings = 0;
+	var rank = model.ranks[i];
+	var previousRank = model.ranks[i - 1];
+
+	var tmpIndices = [];
+
+	// Iterate over the top rank and fill in the connection information
+	for (var j = 0; j < rank.length; j++)
+	{
+		var node = rank[j];
+		var rankPosition = node.getGeneralPurposeVariable(i);
+		var connectedCells = node.getPreviousLayerConnectedCells(i);
+		var nodeIndices = [];
+
+		for (var k = 0; k < connectedCells.length; k++)
+		{
+			var connectedNode = connectedCells[k];
+			var otherCellRankPosition = connectedNode.getGeneralPurposeVariable(i - 1);
+			nodeIndices.push(otherCellRankPosition);
+		}
+		
+		nodeIndices.sort(function(x, y) { return x - y; });
+		tmpIndices[rankPosition] = nodeIndices;
+	}
+	
+	var indices = [];
+
+	for (var j = 0; j < tmpIndices.length; j++)
+	{
+		indices = indices.concat(tmpIndices[j]);
+	}
+
+	var firstIndex = 1;
+	
+	while (firstIndex < previousRank.length)
+	{
+		firstIndex <<= 1;
+	}
+
+	var treeSize = 2 * firstIndex - 1;
+	firstIndex -= 1;
+
+	var tree = [];
+	
+	for (var j = 0; j < treeSize; ++j)
+	{
+		tree[j] = 0;
+	}
+
+	for (var j = 0; j < indices.length; j++)
+	{
+		var index = indices[j];
+	    var treeIndex = index + firstIndex;
+	    ++tree[treeIndex];
+	    
+	    while (treeIndex > 0)
+	    {
+	    	if (treeIndex % 2)
+	    	{
+	    		totalCrossings += tree[treeIndex + 1];
+	    	}
+	      
+	    	treeIndex = (treeIndex - 1) >> 1;
+	    	++tree[treeIndex];
+	    }
+	}
+
+	return totalCrossings;
+};
+
+/**
+ * Function: transpose
+ * 
+ * Takes each possible adjacent cell pair on each rank and checks if
+ * swapping them around reduces the number of crossing
+ * 
+ * Parameters:
+ * 
+ * mainLoopIteration - the iteration number of the main loop
+ * model - the internal model describing the hierarchy
+ */
+mxMedianHybridCrossingReduction.prototype.transpose = function(mainLoopIteration, model)
+{
+	var improved = true;
+
+	// Track the number of iterations in case of looping
+	var count = 0;
+	var maxCount = 10;
+	while (improved && count++ < maxCount)
+	{
+		// On certain iterations allow allow swapping of cell pairs with
+		// equal edge crossings switched or not switched. This help to
+		// nudge a stuck layout into a lower crossing total.
+		var nudge = mainLoopIteration % 2 == 1 && count % 2 == 1;
+		improved = false;
+		
+		for (var i = 0; i < model.ranks.length; i++)
+		{
+			var rank = model.ranks[i];
+			var orderedCells = [];
+			
+			for (var j = 0; j < rank.length; j++)
+			{
+				var cell = rank[j];
+				var tempRank = cell.getGeneralPurposeVariable(i);
+				
+				// FIXME: Workaround to avoid negative tempRanks
+				if (tempRank < 0)
+				{
+					tempRank = j;
+				}
+				orderedCells[tempRank] = cell;
+			}
+			
+			var leftCellAboveConnections = null;
+			var leftCellBelowConnections = null;
+			var rightCellAboveConnections = null;
+			var rightCellBelowConnections = null;
+			
+			var leftAbovePositions = null;
+			var leftBelowPositions = null;
+			var rightAbovePositions = null;
+			var rightBelowPositions = null;
+			
+			var leftCell = null;
+			var rightCell = null;
+
+			for (var j = 0; j < (rank.length - 1); j++)
+			{
+				// For each intra-rank adjacent pair of cells
+				// see if swapping them around would reduce the
+				// number of edges crossing they cause in total
+				// On every cell pair except the first on each rank, we
+				// can save processing using the previous values for the
+				// right cell on the new left cell
+				if (j == 0)
+				{
+					leftCell = orderedCells[j];
+					leftCellAboveConnections = leftCell
+							.getNextLayerConnectedCells(i);
+					leftCellBelowConnections = leftCell
+							.getPreviousLayerConnectedCells(i);
+					leftAbovePositions = [];
+					leftBelowPositions = [];
+					
+					for (var k = 0; k < leftCellAboveConnections.length; k++)
+					{
+						leftAbovePositions[k] = leftCellAboveConnections[k].getGeneralPurposeVariable(i + 1);
+					}
+					
+					for (var k = 0; k < leftCellBelowConnections.length; k++)
+					{
+						leftBelowPositions[k] = leftCellBelowConnections[k].getGeneralPurposeVariable(i - 1);
+					}
+				}
+				else
+				{
+					leftCellAboveConnections = rightCellAboveConnections;
+					leftCellBelowConnections = rightCellBelowConnections;
+					leftAbovePositions = rightAbovePositions;
+					leftBelowPositions = rightBelowPositions;
+					leftCell = rightCell;
+				}
+				
+				rightCell = orderedCells[j + 1];
+				rightCellAboveConnections = rightCell
+						.getNextLayerConnectedCells(i);
+				rightCellBelowConnections = rightCell
+						.getPreviousLayerConnectedCells(i);
+
+				rightAbovePositions = [];
+				rightBelowPositions = [];
+
+				for (var k = 0; k < rightCellAboveConnections.length; k++)
+				{
+					rightAbovePositions[k] = rightCellAboveConnections[k].getGeneralPurposeVariable(i + 1);
+				}
+				
+				for (var k = 0; k < rightCellBelowConnections.length; k++)
+				{
+					rightBelowPositions[k] = rightCellBelowConnections[k].getGeneralPurposeVariable(i - 1);
+				}
+
+				var totalCurrentCrossings = 0;
+				var totalSwitchedCrossings = 0;
+				
+				for (var k = 0; k < leftAbovePositions.length; k++)
+				{
+					for (var ik = 0; ik < rightAbovePositions.length; ik++)
+					{
+						if (leftAbovePositions[k] > rightAbovePositions[ik])
+						{
+							totalCurrentCrossings++;
+						}
+
+						if (leftAbovePositions[k] < rightAbovePositions[ik])
+						{
+							totalSwitchedCrossings++;
+						}
+					}
+				}
+				
+				for (var k = 0; k < leftBelowPositions.length; k++)
+				{
+					for (var ik = 0; ik < rightBelowPositions.length; ik++)
+					{
+						if (leftBelowPositions[k] > rightBelowPositions[ik])
+						{
+							totalCurrentCrossings++;
+						}
+
+						if (leftBelowPositions[k] < rightBelowPositions[ik])
+						{
+							totalSwitchedCrossings++;
+						}
+					}
+				}
+				
+				if ((totalSwitchedCrossings < totalCurrentCrossings) ||
+					(totalSwitchedCrossings == totalCurrentCrossings &&
+					nudge))
+				{
+					var temp = leftCell.getGeneralPurposeVariable(i);
+					leftCell.setGeneralPurposeVariable(i, rightCell
+							.getGeneralPurposeVariable(i));
+					rightCell.setGeneralPurposeVariable(i, temp);
+
+					// With this pair exchanged we have to switch all of
+					// values for the left cell to the right cell so the
+					// next iteration for this rank uses it as the left
+					// cell again
+					rightCellAboveConnections = leftCellAboveConnections;
+					rightCellBelowConnections = leftCellBelowConnections;
+					rightAbovePositions = leftAbovePositions;
+					rightBelowPositions = leftBelowPositions;
+					rightCell = leftCell;
+					
+					if (!nudge)
+					{
+						// Don't count nudges as improvement or we'll end
+						// up stuck in two combinations and not finishing
+						// as early as we should
+						improved = true;
+					}
+				}
+			}
+		}
+	}
+};
+
+/**
+ * Function: weightedMedian
+ * 
+ * Sweeps up or down the layout attempting to minimise the median placement
+ * of connected cells on adjacent ranks
+ * 
+ * Parameters:
+ * 
+ * iteration - the iteration number of the main loop
+ * model - the internal model describing the hierarchy
+ */
+mxMedianHybridCrossingReduction.prototype.weightedMedian = function(iteration, model)
+{
+	// Reverse sweep direction each time through this method
+	var downwardSweep = (iteration % 2 == 0);
+	if (downwardSweep)
+	{
+		for (var j = model.maxRank - 1; j >= 0; j--)
+		{
+			this.medianRank(j, downwardSweep);
+		}
+	}
+	else
+	{
+		for (var j = 1; j < model.maxRank; j++)
+		{
+			this.medianRank(j, downwardSweep);
+		}
+	}
+};
+
+/**
+ * Function: medianRank
+ * 
+ * Attempts to minimise the median placement of connected cells on this rank
+ * and one of the adjacent ranks
+ * 
+ * Parameters:
+ * 
+ * rankValue - the layer number of this rank
+ * downwardSweep - whether or not this is a downward sweep through the graph
+ */
+mxMedianHybridCrossingReduction.prototype.medianRank = function(rankValue, downwardSweep)
+{
+	var numCellsForRank = this.nestedBestRanks[rankValue].length;
+	var medianValues = [];
+	var reservedPositions = [];
+
+	for (var i = 0; i < numCellsForRank; i++)
+	{
+		var cell = this.nestedBestRanks[rankValue][i];
+		var sorterEntry = new MedianCellSorter();
+		sorterEntry.cell = cell;
+
+		// Flip whether or not equal medians are flipped on up and down
+		// sweeps
+		// TODO re-implement some kind of nudge
+		// medianValues[i].nudge = !downwardSweep;
+		var nextLevelConnectedCells;
+		
+		if (downwardSweep)
+		{
+			nextLevelConnectedCells = cell
+					.getNextLayerConnectedCells(rankValue);
+		}
+		else
+		{
+			nextLevelConnectedCells = cell
+					.getPreviousLayerConnectedCells(rankValue);
+		}
+		
+		var nextRankValue;
+		
+		if (downwardSweep)
+		{
+			nextRankValue = rankValue + 1;
+		}
+		else
+		{
+			nextRankValue = rankValue - 1;
+		}
+
+		if (nextLevelConnectedCells != null
+				&& nextLevelConnectedCells.length != 0)
+		{
+			sorterEntry.medianValue = this.medianValue(
+					nextLevelConnectedCells, nextRankValue);
+			medianValues.push(sorterEntry);
+		}
+		else
+		{
+			// Nodes with no adjacent vertices are flagged in the reserved array
+			// to indicate they should be left in their current position.
+			reservedPositions[cell.getGeneralPurposeVariable(rankValue)] = true;
+		}
+	}
+	
+	medianValues.sort(MedianCellSorter.prototype.compare);
+	
+	// Set the new position of each node within the rank using
+	// its temp variable
+	for (var i = 0; i < numCellsForRank; i++)
+	{
+		if (reservedPositions[i] == null)
+		{
+			var cell = medianValues.shift().cell;
+			cell.setGeneralPurposeVariable(rankValue, i);
+		}
+	}
+};
+
+/**
+ * Function: medianValue
+ * 
+ * Calculates the median rank order positioning for the specified cell using
+ * the connected cells on the specified rank. Returns the median rank
+ * ordering value of the connected cells
+ * 
+ * Parameters:
+ * 
+ * connectedCells - the cells on the specified rank connected to the
+ * specified cell
+ * rankValue - the rank that the connected cell lie upon
+ */
+mxMedianHybridCrossingReduction.prototype.medianValue = function(connectedCells, rankValue)
+{
+	var medianValues = [];
+	var arrayCount = 0;
+	
+	for (var i = 0; i < connectedCells.length; i++)
+	{
+		var cell = connectedCells[i];
+		medianValues[arrayCount++] = cell.getGeneralPurposeVariable(rankValue);
+	}
+
+	// Sort() sorts lexicographically by default (i.e. 11 before 9) so force
+	// numerical order sort
+	medianValues.sort(function(a,b){return a - b;});
+	
+	if (arrayCount % 2 == 1)
+	{
+		// For odd numbers of adjacent vertices return the median
+		return medianValues[Math.floor(arrayCount / 2)];
+	}
+	else if (arrayCount == 2)
+	{
+		return ((medianValues[0] + medianValues[1]) / 2.0);
+	}
+	else
+	{
+		var medianPoint = arrayCount / 2;
+		var leftMedian = medianValues[medianPoint - 1] - medianValues[0];
+		var rightMedian = medianValues[arrayCount - 1]
+				- medianValues[medianPoint];
+
+		return (medianValues[medianPoint - 1] * rightMedian + medianValues[medianPoint]
+				* leftMedian)
+				/ (leftMedian + rightMedian);
+	}
+};
+
+/**
+ * Class: MedianCellSorter
+ * 
+ * A utility class used to track cells whilst sorting occurs on the median
+ * values. Does not violate (x.compareTo(y)==0) == (x.equals(y))
+ *
+ * Constructor: MedianCellSorter
+ * 
+ * Constructs a new median cell sorter.
+ */
+function MedianCellSorter()
+{
+	// empty
+};
+
+/**
+ * Variable: medianValue
+ * 
+ * The weighted value of the cell stored.
+ */
+MedianCellSorter.prototype.medianValue = 0;
+
+/**
+ * Variable: cell
+ * 
+ * The cell whose median value is being calculated
+ */
+MedianCellSorter.prototype.cell = false;
+
+/**
+ * Function: compare
+ * 
+ * Compares two MedianCellSorters.
+ */
+MedianCellSorter.prototype.compare = function(a, b)
+{
+	if (a != null && b != null)
+	{
+		if (b.medianValue > a.medianValue)
+		{
+			return -1;
+		}
+		else if (b.medianValue < a.medianValue)
+		{
+			return 1;
+		}
+		else
+		{
+			return 0;
+		}
+	}
+	else
+	{
+		return 0;
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxMinimumCycleRemover
+ * 
+ * An implementation of the first stage of the Sugiyama layout. Straightforward
+ * longest path calculation of layer assignment
+ * 
+ * Constructor: mxMinimumCycleRemover
+ *
+ * Creates a cycle remover for the given internal model.
+ */
+function mxMinimumCycleRemover(layout)
+{
+	this.layout = layout;
+};
+
+/**
+ * Extends mxHierarchicalLayoutStage.
+ */
+mxMinimumCycleRemover.prototype = new mxHierarchicalLayoutStage();
+mxMinimumCycleRemover.prototype.constructor = mxMinimumCycleRemover;
+
+/**
+ * Variable: layout
+ * 
+ * Reference to the enclosing <mxHierarchicalLayout>.
+ */
+mxMinimumCycleRemover.prototype.layout = null;
+
+/**
+ * Function: execute
+ * 
+ * Takes the graph detail and configuration information within the facade
+ * and creates the resulting laid out graph within that facade for further
+ * use.
+ */
+mxMinimumCycleRemover.prototype.execute = function(parent)
+{
+	var model = this.layout.getModel();
+	var seenNodes = new Object();
+	var unseenNodesArray = model.vertexMapper.getValues();
+	var unseenNodes = new Object();
+	
+	for (var i = 0; i < unseenNodesArray.length; i++)
+	{
+		unseenNodes[unseenNodesArray[i].id] = unseenNodesArray[i];
+	}
+	
+	// Perform a dfs through the internal model. If a cycle is found,
+	// reverse it.
+	var rootsArray = null;
+	
+	if (model.roots != null)
+	{
+		var modelRoots = model.roots;
+		rootsArray = [];
+		
+		for (var i = 0; i < modelRoots.length; i++)
+		{
+			rootsArray[i] = model.vertexMapper.get(modelRoots[i]);
+		}
+	}
+
+	model.visit(function(parent, node, connectingEdge, layer, seen)
+	{
+		// Check if the cell is in it's own ancestor list, if so
+		// invert the connecting edge and reverse the target/source
+		// relationship to that edge in the parent and the cell
+		if (node.isAncestor(parent))
+		{
+			connectingEdge.invert();
+			mxUtils.remove(connectingEdge, parent.connectsAsSource);
+			parent.connectsAsTarget.push(connectingEdge);
+			mxUtils.remove(connectingEdge, node.connectsAsTarget);
+			node.connectsAsSource.push(connectingEdge);
+		}
+		
+		seenNodes[node.id] = node;
+		delete unseenNodes[node.id];
+	}, rootsArray, true, null);
+
+	// If there are any nodes that should be nodes that the dfs can miss
+	// these need to be processed with the dfs and the roots assigned
+	// correctly to form a correct internal model
+	var seenNodesCopy = mxUtils.clone(seenNodes, null, true);
+
+	// Pick a random cell and dfs from it
+	model.visit(function(parent, node, connectingEdge, layer, seen)
+	{
+		// Check if the cell is in it's own ancestor list, if so
+		// invert the connecting edge and reverse the target/source
+		// relationship to that edge in the parent and the cell
+		if (node.isAncestor(parent))
+		{
+			connectingEdge.invert();
+			mxUtils.remove(connectingEdge, parent.connectsAsSource);
+			node.connectsAsSource.push(connectingEdge);
+			parent.connectsAsTarget.push(connectingEdge);
+			mxUtils.remove(connectingEdge, node.connectsAsTarget);
+		}
+		
+		seenNodes[node.id] = node;
+		delete unseenNodes[node.id];
+	}, unseenNodes, true, seenNodesCopy);
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxCoordinateAssignment
+ * 
+ * Sets the horizontal locations of node and edge dummy nodes on each layer.
+ * Uses median down and up weighings as well as heuristics to straighten edges as
+ * far as possible.
+ * 
+ * Constructor: mxCoordinateAssignment
+ *
+ * Creates a coordinate assignment.
+ * 
+ * Arguments:
+ * 
+ * intraCellSpacing - the minimum buffer between cells on the same rank
+ * interRankCellSpacing - the minimum distance between cells on adjacent ranks
+ * orientation - the position of the root node(s) relative to the graph
+ * initialX - the leftmost coordinate node placement starts at
+ */
+function mxCoordinateAssignment(layout, intraCellSpacing, interRankCellSpacing,
+	orientation, initialX, parallelEdgeSpacing)
+{
+	this.layout = layout;
+	this.intraCellSpacing = intraCellSpacing;
+	this.interRankCellSpacing = interRankCellSpacing;
+	this.orientation = orientation;
+	this.initialX = initialX;
+	this.parallelEdgeSpacing = parallelEdgeSpacing;
+};
+
+/**
+ * Extends mxHierarchicalLayoutStage.
+ */
+mxCoordinateAssignment.prototype = new mxHierarchicalLayoutStage();
+mxCoordinateAssignment.prototype.constructor = mxCoordinateAssignment;
+
+/**
+ * Variable: layout
+ * 
+ * Reference to the enclosing <mxHierarchicalLayout>.
+ */
+mxCoordinateAssignment.prototype.layout = null;
+
+/**
+ * Variable: intraCellSpacing
+ * 
+ * The minimum buffer between cells on the same rank. Default is 30.
+ */
+mxCoordinateAssignment.prototype.intraCellSpacing = 30;
+
+/**
+ * Variable: interRankCellSpacing
+ * 
+ * The minimum distance between cells on adjacent ranks. Default is 10.
+ */
+mxCoordinateAssignment.prototype.interRankCellSpacing = 100;
+
+/**
+ * Variable: parallelEdgeSpacing
+ * 
+ * The distance between each parallel edge on each ranks for long edges.
+ * Default is 10.
+ */
+mxCoordinateAssignment.prototype.parallelEdgeSpacing = 10;
+
+/**
+ * Variable: maxIterations
+ * 
+ * The number of heuristic iterations to run. Default is 8.
+ */
+mxCoordinateAssignment.prototype.maxIterations = 8;
+
+/**
+ * Variable: prefHozEdgeSep
+ * 
+ * The preferred horizontal distance between edges exiting a vertex
+ */
+mxCoordinateAssignment.prototype.prefHozEdgeSep = 5;
+
+/**
+ * Variable: prefVertEdgeOff
+ * 
+ * The preferred vertical offset between edges exiting a vertex
+ */
+mxCoordinateAssignment.prototype.prefVertEdgeOff = 2;
+
+/**
+ * Variable: minEdgeJetty
+ * 
+ * The minimum distance for an edge jetty from a vertex
+ */
+mxCoordinateAssignment.prototype.minEdgeJetty = 12;
+
+/**
+ * Variable: channelBuffer
+ * 
+ * The size of the vertical buffer in the center of inter-rank channels
+ * where edge control points should not be placed
+ */
+mxCoordinateAssignment.prototype.channelBuffer = 4;
+
+/**
+ * Variable: jettyPositions
+ * 
+ * Map of internal edges and (x,y) pair of positions of the start and end jetty
+ * for that edge where it connects to the source and target vertices.
+ * Note this should technically be a WeakHashMap, but since JS does not
+ * have an equivalent, housekeeping must be performed before using.
+ * i.e. check all edges are still in the model and clear the values.
+ * Note that the y co-ord is the offset of the jetty, not the
+ * absolute point
+ */
+mxCoordinateAssignment.prototype.jettyPositions = null;
+
+/**
+ * Variable: orientation
+ * 
+ * The position of the root ( start ) node(s) relative to the rest of the
+ * laid out graph. Default is <mxConstants.DIRECTION_NORTH>.
+ */
+mxCoordinateAssignment.prototype.orientation = mxConstants.DIRECTION_NORTH;
+
+/**
+ * Variable: initialX
+ * 
+ * The minimum x position node placement starts at
+ */
+mxCoordinateAssignment.prototype.initialX = null;
+
+/**
+ * Variable: limitX
+ * 
+ * The maximum x value this positioning lays up to
+ */
+mxCoordinateAssignment.prototype.limitX = null;
+
+/**
+ * Variable: currentXDelta
+ * 
+ * The sum of x-displacements for the current iteration
+ */
+mxCoordinateAssignment.prototype.currentXDelta = null;
+
+/**
+ * Variable: widestRank
+ * 
+ * The rank that has the widest x position
+ */
+mxCoordinateAssignment.prototype.widestRank = null;
+
+/**
+ * Variable: rankTopY
+ * 
+ * Internal cache of top-most values of Y for each rank
+ */
+mxCoordinateAssignment.prototype.rankTopY = null;
+
+/**
+ * Variable: rankBottomY
+ * 
+ * Internal cache of bottom-most value of Y for each rank
+ */
+mxCoordinateAssignment.prototype.rankBottomY = null;
+
+/**
+ * Variable: widestRankValue
+ * 
+ * The X-coordinate of the edge of the widest rank
+ */
+mxCoordinateAssignment.prototype.widestRankValue = null;
+
+/**
+ * Variable: rankWidths
+ * 
+ * The width of all the ranks
+ */
+mxCoordinateAssignment.prototype.rankWidths = null;
+
+/**
+ * Variable: rankY
+ * 
+ * The Y-coordinate of all the ranks
+ */
+mxCoordinateAssignment.prototype.rankY = null;
+
+/**
+ * Variable: fineTuning
+ * 
+ * Whether or not to perform local optimisations and iterate multiple times
+ * through the algorithm. Default is true.
+ */
+mxCoordinateAssignment.prototype.fineTuning = true;
+
+/**
+ * Variable: 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 = Object();
+	var model = this.layout.getModel();
+	this.currentXDelta = 0.0;
+
+	this.initialCoords(this.layout.getGraph(), model);
+	
+//	this.printStatus();
+	
+	if (this.fineTuning)
+	{
+		this.minNode(model);
+	}
+	
+	var bestXDelta = 100000000.0;
+	
+	if (this.fineTuning)
+	{
+		for (var i = 0; i < this.maxIterations; i++)
+		{
+//			this.printStatus();
+		
+			// Median Heuristic
+			if (i != 0)
+			{
+				this.medianPos(i, model);
+				this.minNode(model);
+			}
+			
+			// if the total offset is less for the current positioning,
+			// there are less heavily angled edges and so the current
+			// positioning is used
+			if (this.currentXDelta < bestXDelta)
+			{
+				for (var j = 0; j < model.ranks.length; j++)
+				{
+					var rank = model.ranks[j];
+					
+					for (var k = 0; k < rank.length; k++)
+					{
+						var cell = rank[k];
+						cell.setX(j, cell.getGeneralPurposeVariable(j));
+					}
+				}
+				
+				bestXDelta = this.currentXDelta;
+			}
+			else
+			{
+				// Restore the best positions
+				for (var j = 0; j < model.ranks.length; j++)
+				{
+					var rank = model.ranks[j];
+					
+					for (var k = 0; k < rank.length; k++)
+					{
+						var cell = rank[k];
+						cell.setGeneralPurposeVariable(j, cell.getX(j));
+					}
+				}
+			}
+			
+			this.minPath(this.layout.getGraph(), model);
+			
+			this.currentXDelta = 0;
+		}
+	}
+	
+	this.setCellLocations(this.layout.getGraph(), model);
+};
+
+/**
+ * Function: minNode
+ * 
+ * Performs one median positioning sweep in both directions
+ */
+mxCoordinateAssignment.prototype.minNode = function(model)
+{
+	// Queue all nodes
+	var nodeList = [];
+	
+	// Need to be able to map from cell to cellWrapper
+	var map = new mxDictionary();
+	var rank = [];
+	
+	for (var i = 0; i <= model.maxRank; i++)
+	{
+		rank[i] = model.ranks[i];
+		
+		for (var j = 0; j < rank[i].length; j++)
+		{
+			// Use the weight to store the rank and visited to store whether
+			// or not the cell is in the list
+			var node = rank[i][j];
+			var nodeWrapper = new WeightedCellSorter(node, i);
+			nodeWrapper.rankIndex = j;
+			nodeWrapper.visited = true;
+			nodeList.push(nodeWrapper);
+			
+			map.put(node, nodeWrapper);
+		}
+	}
+	
+	// Set a limit of the maximum number of times we will access the queue
+	// in case a loop appears
+	var maxTries = nodeList.length * 10;
+	var count = 0;
+	
+	// Don't move cell within this value of their median
+	var tolerance = 1;
+	
+	while (nodeList.length > 0 && count <= maxTries)
+	{
+		var cellWrapper = nodeList.shift();
+		var cell = cellWrapper.cell;
+		
+		var rankValue = cellWrapper.weightedValue;
+		var rankIndex = parseInt(cellWrapper.rankIndex);
+		
+		var nextLayerConnectedCells = cell.getNextLayerConnectedCells(rankValue);
+		var previousLayerConnectedCells = cell.getPreviousLayerConnectedCells(rankValue);
+		
+		var numNextLayerConnected = nextLayerConnectedCells.length;
+		var numPreviousLayerConnected = previousLayerConnectedCells.length;
+
+		var medianNextLevel = this.medianXValue(nextLayerConnectedCells,
+				rankValue + 1);
+		var medianPreviousLevel = this.medianXValue(previousLayerConnectedCells,
+				rankValue - 1);
+
+		var numConnectedNeighbours = numNextLayerConnected
+				+ numPreviousLayerConnected;
+		var currentPosition = cell.getGeneralPurposeVariable(rankValue);
+		var cellMedian = currentPosition;
+		
+		if (numConnectedNeighbours > 0)
+		{
+			cellMedian = (medianNextLevel * numNextLayerConnected + medianPreviousLevel
+					* numPreviousLayerConnected)
+					/ numConnectedNeighbours;
+		}
+
+		// Flag storing whether or not position has changed
+		var positionChanged = false;
+		
+		if (cellMedian < currentPosition - tolerance)
+		{
+			if (rankIndex == 0)
+			{
+				cell.setGeneralPurposeVariable(rankValue, cellMedian);
+				positionChanged = true;
+			}
+			else
+			{
+				var leftCell = rank[rankValue][rankIndex - 1];
+				var leftLimit = leftCell
+						.getGeneralPurposeVariable(rankValue);
+				leftLimit = leftLimit + leftCell.width / 2
+						+ this.intraCellSpacing + cell.width / 2;
+
+				if (leftLimit < cellMedian)
+				{
+					cell.setGeneralPurposeVariable(rankValue, cellMedian);
+					positionChanged = true;
+				}
+				else if (leftLimit < cell
+						.getGeneralPurposeVariable(rankValue)
+						- tolerance)
+				{
+					cell.setGeneralPurposeVariable(rankValue, leftLimit);
+					positionChanged = true;
+				}
+			}
+		}
+		else if (cellMedian > currentPosition + tolerance)
+		{
+			var rankSize = rank[rankValue].length;
+			
+			if (rankIndex == rankSize - 1)
+			{
+				cell.setGeneralPurposeVariable(rankValue, cellMedian);
+				positionChanged = true;
+			}
+			else
+			{
+				var rightCell = rank[rankValue][rankIndex + 1];
+				var rightLimit = rightCell
+						.getGeneralPurposeVariable(rankValue);
+				rightLimit = rightLimit - rightCell.width / 2
+						- this.intraCellSpacing - cell.width / 2;
+				
+				if (rightLimit > cellMedian)
+				{
+					cell.setGeneralPurposeVariable(rankValue, cellMedian);
+					positionChanged = true;
+				}
+				else if (rightLimit > cell
+						.getGeneralPurposeVariable(rankValue)
+						+ tolerance)
+				{
+					cell.setGeneralPurposeVariable(rankValue, rightLimit);
+					positionChanged = true;
+				}
+			}
+		}
+		
+		if (positionChanged)
+		{
+			// Add connected nodes to map and list
+			for (var i = 0; i < nextLayerConnectedCells.length; i++)
+			{
+				var connectedCell = nextLayerConnectedCells[i];
+				var connectedCellWrapper = map.get(connectedCell);
+				
+				if (connectedCellWrapper != null)
+				{
+					if (connectedCellWrapper.visited == false)
+					{
+						connectedCellWrapper.visited = true;
+						nodeList.push(connectedCellWrapper);
+					}
+				}
+			}
+
+			// Add connected nodes to map and list
+			for (var i = 0; i < previousLayerConnectedCells.length; i++)
+			{
+				var connectedCell = previousLayerConnectedCells[i];
+				var connectedCellWrapper = map.get(connectedCell);
+
+				if (connectedCellWrapper != null)
+				{
+					if (connectedCellWrapper.visited == false)
+					{
+						connectedCellWrapper.visited = true;
+						nodeList.push(connectedCellWrapper);
+					}
+				}
+			}
+		}
+		
+		cellWrapper.visited = false;
+		count++;
+	}
+};
+
+/**
+ * Function: medianPos
+ * 
+ * Performs one median positioning sweep in one direction
+ * 
+ * Parameters:
+ * 
+ * i - the iteration of the whole process
+ * model - an internal model of the hierarchical layout
+ */
+mxCoordinateAssignment.prototype.medianPos = function(i, model)
+{
+	// Reverse sweep direction each time through this method
+	var downwardSweep = (i % 2 == 0);
+	
+	if (downwardSweep)
+	{
+		for (var j = model.maxRank; j > 0; j--)
+		{
+			this.rankMedianPosition(j - 1, model, j);
+		}
+	}
+	else
+	{
+		for (var j = 0; j < model.maxRank - 1; j++)
+		{
+			this.rankMedianPosition(j + 1, model, j);
+		}
+	}
+};
+
+/**
+ * Function: rankMedianPosition
+ * 
+ * Performs median minimisation over one rank.
+ * 
+ * Parameters:
+ * 
+ * rankValue - the layer number of this rank
+ * model - an internal model of the hierarchical layout
+ * nextRankValue - the layer number whose connected cels are to be laid out
+ * relative to
+ */
+mxCoordinateAssignment.prototype.rankMedianPosition = function(rankValue, model, nextRankValue)
+{
+	var rank = model.ranks[rankValue];
+
+	// Form an array of the order in which the cell are to be processed
+	// , the order is given by the weighted sum of the in or out edges,
+	// depending on whether we're traveling up or down the hierarchy.
+	var weightedValues = [];
+	var cellMap = new Object();
+
+	for (var i = 0; i < rank.length; i++)
+	{
+		var currentCell = rank[i];
+		weightedValues[i] = new WeightedCellSorter();
+		weightedValues[i].cell = currentCell;
+		weightedValues[i].rankIndex = i;
+		cellMap[currentCell.id] = weightedValues[i];
+		var nextLayerConnectedCells = null;
+		
+		if (nextRankValue < rankValue)
+		{
+			nextLayerConnectedCells = currentCell
+					.getPreviousLayerConnectedCells(rankValue);
+		}
+		else
+		{
+			nextLayerConnectedCells = currentCell
+					.getNextLayerConnectedCells(rankValue);
+		}
+
+		// Calculate the weighing based on this node type and those this
+		// node is connected to on the next layer
+		weightedValues[i].weightedValue = this.calculatedWeightedValue(
+				currentCell, nextLayerConnectedCells);
+	}
+
+	weightedValues.sort(WeightedCellSorter.prototype.compare);
+
+	// Set the new position of each node within the rank using
+	// its temp variable
+	
+	for (var i = 0; i < weightedValues.length; i++)
+	{
+		var numConnectionsNextLevel = 0;
+		var cell = weightedValues[i].cell;
+		var nextLayerConnectedCells = null;
+		var medianNextLevel = 0;
+
+		if (nextRankValue < rankValue)
+		{
+			nextLayerConnectedCells = cell.getPreviousLayerConnectedCells(
+					rankValue).slice();
+		}
+		else
+		{
+			nextLayerConnectedCells = cell.getNextLayerConnectedCells(
+					rankValue).slice();
+		}
+
+		if (nextLayerConnectedCells != null)
+		{
+			numConnectionsNextLevel = nextLayerConnectedCells.length;
+			
+			if (numConnectionsNextLevel > 0)
+			{
+				medianNextLevel = this.medianXValue(nextLayerConnectedCells,
+						nextRankValue);
+			}
+			else
+			{
+				// For case of no connections on the next level set the
+				// median to be the current position and try to be
+				// positioned there
+				medianNextLevel = cell.getGeneralPurposeVariable(rankValue);
+			}
+		}
+
+		var leftBuffer = 0.0;
+		var leftLimit = -100000000.0;
+		
+		for (var j = weightedValues[i].rankIndex - 1; j >= 0;)
+		{
+			var weightedValue = cellMap[rank[j].id];
+			
+			if (weightedValue != null)
+			{
+				var leftCell = weightedValue.cell;
+				
+				if (weightedValue.visited)
+				{
+					// The left limit is the right hand limit of that
+					// cell plus any allowance for unallocated cells
+					// in-between
+					leftLimit = leftCell
+							.getGeneralPurposeVariable(rankValue)
+							+ leftCell.width
+							/ 2.0
+							+ this.intraCellSpacing
+							+ leftBuffer + cell.width / 2.0;
+					j = -1;
+				}
+				else
+				{
+					leftBuffer += leftCell.width + this.intraCellSpacing;
+					j--;
+				}
+			}
+		}
+
+		var rightBuffer = 0.0;
+		var rightLimit = 100000000.0;
+		
+		for (var j = weightedValues[i].rankIndex + 1; j < weightedValues.length;)
+		{
+			var weightedValue = cellMap[rank[j].id];
+			
+			if (weightedValue != null)
+			{
+				var rightCell = weightedValue.cell;
+				
+				if (weightedValue.visited)
+				{
+					// The left limit is the right hand limit of that
+					// cell plus any allowance for unallocated cells
+					// in-between
+					rightLimit = rightCell
+							.getGeneralPurposeVariable(rankValue)
+							- rightCell.width
+							/ 2.0
+							- this.intraCellSpacing
+							- rightBuffer - cell.width / 2.0;
+					j = weightedValues.length;
+				}
+				else
+				{
+					rightBuffer += rightCell.width + this.intraCellSpacing;
+					j++;
+				}
+			}
+		}
+		
+		if (medianNextLevel >= leftLimit && medianNextLevel <= rightLimit)
+		{
+			cell.setGeneralPurposeVariable(rankValue, medianNextLevel);
+		}
+		else if (medianNextLevel < leftLimit)
+		{
+			// Couldn't place at median value, place as close to that
+			// value as possible
+			cell.setGeneralPurposeVariable(rankValue, leftLimit);
+			this.currentXDelta += leftLimit - medianNextLevel;
+		}
+		else if (medianNextLevel > rightLimit)
+		{
+			// Couldn't place at median value, place as close to that
+			// value as possible
+			cell.setGeneralPurposeVariable(rankValue, rightLimit);
+			this.currentXDelta += medianNextLevel - rightLimit;
+		}
+
+		weightedValues[i].visited = true;
+	}
+};
+
+/**
+ * Function: calculatedWeightedValue
+ * 
+ * Calculates the priority the specified cell has based on the type of its
+ * cell and the cells it is connected to on the next layer
+ * 
+ * Parameters:
+ * 
+ * currentCell - the cell whose weight is to be calculated
+ * collection - the cells the specified cell is connected to
+ */
+mxCoordinateAssignment.prototype.calculatedWeightedValue = function(currentCell, collection)
+{
+	var totalWeight = 0;
+	
+	for (var i = 0; i < collection.length; i++)
+	{
+		var cell = collection[i];
+
+		if (currentCell.isVertex() && cell.isVertex())
+		{
+			totalWeight++;
+		}
+		else if (currentCell.isEdge() && cell.isEdge())
+		{
+			totalWeight += 8;
+		}
+		else
+		{
+			totalWeight += 2;
+		}
+	}
+
+	return totalWeight;
+};
+
+/**
+ * Function: medianXValue
+ * 
+ * Calculates the median position of the connected cell on the specified
+ * rank
+ * 
+ * Parameters:
+ * 
+ * connectedCells - the cells the candidate connects to on this level
+ * rankValue - the layer number of this rank
+ */
+mxCoordinateAssignment.prototype.medianXValue = function(connectedCells, rankValue)
+{
+	if (connectedCells.length == 0)
+	{
+		return 0;
+	}
+
+	var medianValues = [];
+
+	for (var i = 0; i < connectedCells.length; i++)
+	{
+		medianValues[i] = connectedCells[i].getGeneralPurposeVariable(rankValue);
+	}
+
+	medianValues.sort(function(a,b){return a - b;});
+	
+	if (connectedCells.length % 2 == 1)
+	{
+		// For odd numbers of adjacent vertices return the median
+		return medianValues[Math.floor(connectedCells.length / 2)];
+	}
+	else
+	{
+		var medianPoint = connectedCells.length / 2;
+		var leftMedian = medianValues[medianPoint - 1];
+		var rightMedian = medianValues[medianPoint];
+
+		return ((leftMedian + rightMedian) / 2);
+	}
+};
+
+/**
+ * Function: initialCoords
+ * 
+ * Sets up the layout in an initial positioning. The ranks are all centered
+ * as much as possible along the middle vertex in each rank. The other cells
+ * are then placed as close as possible on either side.
+ * 
+ * Parameters:
+ * 
+ * facade - the facade describing the input graph
+ * model - an internal model of the hierarchical layout
+ */
+mxCoordinateAssignment.prototype.initialCoords = function(facade, model)
+{
+	this.calculateWidestRank(facade, model);
+
+	// Sweep up and down from the widest rank
+	for (var i = this.widestRank; i >= 0; i--)
+	{
+		if (i < model.maxRank)
+		{
+			this.rankCoordinates(i, facade, model);
+		}
+	}
+
+	for (var i = this.widestRank+1; i <= model.maxRank; i++)
+	{
+		if (i > 0)
+		{
+			this.rankCoordinates(i, facade, model);
+		}
+	}
+};
+
+/**
+ * Function: rankCoordinates
+ * 
+ * Sets up the layout in an initial positioning. All the first cells in each
+ * rank are moved to the left and the rest of the rank inserted as close
+ * together as their size and buffering permits. This method works on just
+ * the specified rank.
+ * 
+ * Parameters:
+ * 
+ * rankValue - the current rank being processed
+ * graph - the facade describing the input graph
+ * model - an internal model of the hierarchical layout
+ */
+mxCoordinateAssignment.prototype.rankCoordinates = function(rankValue, graph, model)
+{
+	var rank = model.ranks[rankValue];
+	var maxY = 0.0;
+	var localX = this.initialX + (this.widestRankValue - this.rankWidths[rankValue])
+			/ 2;
+
+	// Store whether or not any of the cells' bounds were unavailable so
+	// to only issue the warning once for all cells
+	var boundsWarning = false;
+	
+	for (var i = 0; i < rank.length; i++)
+	{
+		var node = rank[i];
+		
+		if (node.isVertex())
+		{
+			var bounds = this.layout.getVertexBounds(node.cell);
+
+			if (bounds != null)
+			{
+				if (this.orientation == mxConstants.DIRECTION_NORTH ||
+					this.orientation == mxConstants.DIRECTION_SOUTH)
+				{
+					node.width = bounds.width;
+					node.height = bounds.height;
+				}
+				else
+				{
+					node.width = bounds.height;
+					node.height = bounds.width;
+				}
+			}
+			else
+			{
+				boundsWarning = true;
+			}
+
+			maxY = Math.max(maxY, node.height);
+		}
+		else if (node.isEdge())
+		{
+			// The width is the number of additional parallel edges
+			// time the parallel edge spacing
+			var numEdges = 1;
+
+			if (node.edges != null)
+			{
+				numEdges = node.edges.length;
+			}
+			else
+			{
+				mxLog.warn('edge.edges is null');
+			}
+
+			node.width = (numEdges - 1) * this.parallelEdgeSpacing;
+		}
+
+		// Set the initial x-value as being the best result so far
+		localX += node.width / 2.0;
+		node.setX(rankValue, localX);
+		node.setGeneralPurposeVariable(rankValue, localX);
+		localX += node.width / 2.0;
+		localX += this.intraCellSpacing;
+	}
+
+	if (boundsWarning == true)
+	{
+		mxLog.warn('At least one cell has no bounds');
+	}
+};
+
+/**
+ * Function: calculateWidestRank
+ * 
+ * Calculates the width rank in the hierarchy. Also set the y value of each
+ * rank whilst performing the calculation
+ * 
+ * Parameters:
+ * 
+ * graph - the facade describing the input graph
+ * model - an internal model of the hierarchical layout
+ */
+mxCoordinateAssignment.prototype.calculateWidestRank = function(graph, model)
+{
+	// Starting y co-ordinate
+	var y = -this.interRankCellSpacing;
+	
+	// Track the widest cell on the last rank since the y
+	// difference depends on it
+	var lastRankMaxCellHeight = 0.0;
+	this.rankWidths = [];
+	this.rankY = [];
+
+	for (var rankValue = model.maxRank; rankValue >= 0; rankValue--)
+	{
+		// Keep track of the widest cell on this rank
+		var maxCellHeight = 0.0;
+		var rank = model.ranks[rankValue];
+		var localX = this.initialX;
+
+		// Store whether or not any of the cells' bounds were unavailable so
+		// to only issue the warning once for all cells
+		var boundsWarning = false;
+		
+		for (var i = 0; i < rank.length; i++)
+		{
+			var node = rank[i];
+
+			if (node.isVertex())
+			{
+				var bounds = this.layout.getVertexBounds(node.cell);
+
+				if (bounds != null)
+				{
+					if (this.orientation == mxConstants.DIRECTION_NORTH ||
+						this.orientation == mxConstants.DIRECTION_SOUTH)
+					{
+						node.width = bounds.width;
+						node.height = bounds.height;
+					}
+					else
+					{
+						node.width = bounds.height;
+						node.height = bounds.width;
+					}
+				}
+				else
+				{
+					boundsWarning = true;
+				}
+
+				maxCellHeight = Math.max(maxCellHeight, node.height);
+			}
+			else if (node.isEdge())
+			{
+				// The width is the number of additional parallel edges
+				// time the parallel edge spacing
+				var numEdges = 1;
+
+				if (node.edges != null)
+				{
+					numEdges = node.edges.length;
+				}
+				else
+				{
+					mxLog.warn('edge.edges is null');
+				}
+
+				node.width = (numEdges - 1) * this.parallelEdgeSpacing;
+			}
+
+			// Set the initial x-value as being the best result so far
+			localX += node.width / 2.0;
+			node.setX(rankValue, localX);
+			node.setGeneralPurposeVariable(rankValue, localX);
+			localX += node.width / 2.0;
+			localX += this.intraCellSpacing;
+
+			if (localX > this.widestRankValue)
+			{
+				this.widestRankValue = localX;
+				this.widestRank = rankValue;
+			}
+
+			this.rankWidths[rankValue] = localX;
+		}
+
+		if (boundsWarning == true)
+		{
+			mxLog.warn('At least one cell has no bounds');
+		}
+
+		this.rankY[rankValue] = y;
+		var distanceToNextRank = maxCellHeight / 2.0
+				+ lastRankMaxCellHeight / 2.0 + this.interRankCellSpacing;
+		lastRankMaxCellHeight = maxCellHeight;
+
+		if (this.orientation == mxConstants.DIRECTION_NORTH ||
+			this.orientation == mxConstants.DIRECTION_WEST)
+		{
+			y += distanceToNextRank;
+		}
+		else
+		{
+			y -= distanceToNextRank;
+		}
+
+		for (var i = 0; i < rank.length; i++)
+		{
+			var cell = rank[i];
+			cell.setY(rankValue, y);
+		}
+	}
+};
+
+/**
+ * Function: minPath
+ * 
+ * Straightens out chains of virtual nodes where possibleacade to those stored after this layout
+ * processing step has completed.
+ * 
+ * Parameters:
+ *
+ * graph - the facade describing the input graph
+ * model - an internal model of the hierarchical layout
+ */
+mxCoordinateAssignment.prototype.minPath = function(graph, model)
+{
+	// Work down and up each edge with at least 2 control points
+	// trying to straighten each one out. If the same number of
+	// straight segments are formed in both directions, the 
+	// preferred direction used is the one where the final
+	// control points have the least offset from the connectable 
+	// region of the terminating vertices
+	var edges = model.edgeMapper.getValues();
+	
+	for (var j = 0; j < edges.length; j++)
+	{
+		var cell = edges[j];
+		
+		if (cell.maxRank - cell.minRank - 1 < 1)
+		{
+			continue;
+		}
+
+		// At least two virtual nodes in the edge
+		// Check first whether the edge is already straight
+		var referenceX = cell
+				.getGeneralPurposeVariable(cell.minRank + 1);
+		var edgeStraight = true;
+		var refSegCount = 0;
+		
+		for (var i = cell.minRank + 2; i < cell.maxRank; i++)
+		{
+			var x = cell.getGeneralPurposeVariable(i);
+
+			if (referenceX != x)
+			{
+				edgeStraight = false;
+				referenceX = x;
+			}
+			else
+			{
+				refSegCount++;
+			}
+		}
+
+		if (!edgeStraight)
+		{
+			var upSegCount = 0;
+			var downSegCount = 0;
+			var upXPositions = [];
+			var downXPositions = [];
+
+			var currentX = cell.getGeneralPurposeVariable(cell.minRank + 1);
+
+			for (var i = cell.minRank + 1; i < cell.maxRank - 1; i++)
+			{
+				// Attempt to straight out the control point on the
+				// next segment up with the current control point.
+				var nextX = cell.getX(i + 1);
+
+				if (currentX == nextX)
+				{
+					upXPositions[i - cell.minRank - 1] = currentX;
+					upSegCount++;
+				}
+				else if (this.repositionValid(model, cell, i + 1, currentX))
+				{
+					upXPositions[i - cell.minRank - 1] = currentX;
+					upSegCount++;
+					// Leave currentX at same value
+				}
+				else
+				{
+					upXPositions[i - cell.minRank - 1] = nextX;
+					currentX = nextX;
+				}				
+			}
+
+			currentX = cell.getX(i);
+
+			for (var i = cell.maxRank - 1; i > cell.minRank + 1; i--)
+			{
+				// Attempt to straight out the control point on the
+				// next segment down with the current control point.
+				var nextX = cell.getX(i - 1);
+
+				if (currentX == nextX)
+				{
+					downXPositions[i - cell.minRank - 2] = currentX;
+					downSegCount++;
+				}
+				else if (this.repositionValid(model, cell, i - 1, currentX))
+				{
+					downXPositions[i - cell.minRank - 2] = currentX;
+					downSegCount++;
+					// Leave currentX at same value
+				}
+				else
+				{
+					downXPositions[i - cell.minRank - 2] = cell.getX(i-1);
+					currentX = nextX;
+				}
+			}
+
+			if (downSegCount > refSegCount || upSegCount > refSegCount)
+			{
+				if (downSegCount >= upSegCount)
+				{
+					// Apply down calculation values
+					for (var i = cell.maxRank - 2; i > cell.minRank; i--)
+					{
+						cell.setX(i, downXPositions[i - cell.minRank - 1]);
+					}
+				}
+				else if (upSegCount > downSegCount)
+				{
+					// Apply up calculation values
+					for (var i = cell.minRank + 2; i < cell.maxRank; i++)
+					{
+						cell.setX(i, upXPositions[i - cell.minRank - 2]);
+					}
+				}
+				else
+				{
+					// Neither direction provided a favourable result
+					// But both calculations are better than the
+					// existing solution, so apply the one with minimal
+					// offset to attached vertices at either end.
+				}
+			}
+		}
+	}
+};
+
+/**
+ * Function: repositionValid
+ * 
+ * Determines whether or not a node may be moved to the specified x 
+ * position on the specified rank
+ * 
+ * Parameters:
+ *
+ * model - the layout model
+ * cell - the cell being analysed
+ * rank - the layer of the cell
+ * position - the x position being sought
+ */
+mxCoordinateAssignment.prototype.repositionValid = function(model, cell, rank, position)
+{
+	var rankArray = model.ranks[rank];
+	var rankIndex = -1;
+
+	for (var i = 0; i < rankArray.length; i++)
+	{
+		if (cell == rankArray[i])
+		{
+			rankIndex = i;
+			break;
+		}
+	}
+
+	if (rankIndex < 0)
+	{
+		return false;
+	}
+
+	var currentX = cell.getGeneralPurposeVariable(rank);
+
+	if (position < currentX)
+	{
+		// Trying to move node to the left.
+		if (rankIndex == 0)
+		{
+			// Left-most node, can move anywhere
+			return true;
+		}
+
+		var leftCell = rankArray[rankIndex - 1];
+		var leftLimit = leftCell.getGeneralPurposeVariable(rank);
+		leftLimit = leftLimit + leftCell.width / 2
+				+ this.intraCellSpacing + cell.width / 2;
+
+		if (leftLimit <= position)
+		{
+			return true;
+		}
+		else
+		{
+			return false;
+		}
+	}
+	else if (position > currentX)
+	{
+		// Trying to move node to the right.
+		if (rankIndex == rankArray.length - 1)
+		{
+			// Right-most node, can move anywhere
+			return true;
+		}
+
+		var rightCell = rankArray[rankIndex + 1];
+		var rightLimit = rightCell.getGeneralPurposeVariable(rank);
+		rightLimit = rightLimit - rightCell.width / 2
+				- this.intraCellSpacing - cell.width / 2;
+
+		if (rightLimit >= position)
+		{
+			return true;
+		}
+		else
+		{
+			return false;
+		}
+	}
+
+	return true;
+};
+
+/**
+ * Function: setCellLocations
+ * 
+ * Sets the cell locations in the facade to those stored after this layout
+ * processing step has completed.
+ * 
+ * Parameters:
+ *
+ * graph - the input graph
+ * model - the layout model
+ */
+mxCoordinateAssignment.prototype.setCellLocations = function(graph, model)
+{
+	this.rankTopY = [];
+	this.rankBottomY = [];
+
+	for (var i = 0; i < model.ranks.length; i++)
+	{
+		this.rankTopY[i] = Number.MAX_VALUE;
+		this.rankBottomY[i] = -Number.MAX_VALUE;
+	}
+	
+	var vertices = model.vertexMapper.getValues();
+
+	// Process vertices all first, since they define the lower and 
+	// limits of each rank. Between these limits lie the channels
+	// where the edges can be routed across the graph
+
+	for (var i = 0; i < vertices.length; i++)
+	{
+		this.setVertexLocation(vertices[i]);
+	}
+	
+	// Post process edge styles. Needs the vertex locations set for initial
+	// values of the top and bottoms of each rank
+	if (this.layout.edgeStyle == mxHierarchicalEdgeStyle.ORTHOGONAL
+			|| this.layout.edgeStyle == mxHierarchicalEdgeStyle.POLYLINE
+			|| this.layout.edgeStyle == mxHierarchicalEdgeStyle.CURVE)
+	{
+		this.localEdgeProcessing(model);
+	}
+
+	var edges = model.edgeMapper.getValues();
+
+	for (var i = 0; i < edges.length; i++)
+	{
+		this.setEdgePosition(edges[i]);
+	}
+};
+
+/**
+ * Function: localEdgeProcessing
+ * 
+ * Separates the x position of edges as they connect to vertices
+ * 
+ * Parameters:
+ *
+ * model - the layout model
+ */
+mxCoordinateAssignment.prototype.localEdgeProcessing = function(model)
+{
+	// Iterate through each vertex, look at the edges connected in
+	// both directions.
+	for (var rankIndex = 0; rankIndex < model.ranks.length; rankIndex++)
+	{
+		var rank = model.ranks[rankIndex];
+
+		for (var cellIndex = 0; cellIndex < rank.length; cellIndex++)
+		{
+			var cell = rank[cellIndex];
+
+			if (cell.isVertex())
+			{
+				var currentCells = cell.getPreviousLayerConnectedCells(rankIndex);
+
+				var currentRank = rankIndex - 1;
+
+				// Two loops, last connected cells, and next
+				for (var k = 0; k < 2; k++)
+				{
+					if (currentRank > -1
+							&& currentRank < model.ranks.length
+							&& currentCells != null
+							&& currentCells.length > 0)
+					{
+						var sortedCells = [];
+
+						for (var j = 0; j < currentCells.length; j++)
+						{
+							var sorter = new WeightedCellSorter(
+									currentCells[j], currentCells[j].getX(currentRank));
+							sortedCells.push(sorter);
+						}
+
+						sortedCells.sort(WeightedCellSorter.prototype.compare);
+
+						var leftLimit = cell.x[0] - cell.width / 2;
+						var rightLimit = leftLimit + cell.width;
+
+						// Connected edge count starts at 1 to allow for buffer
+						// with edge of vertex
+						var connectedEdgeCount = 0;
+						var connectedEdgeGroupCount = 0;
+						var connectedEdges = [];
+						// Calculate width requirements for all connected edges
+						for (var j = 0; j < sortedCells.length; j++)
+						{
+							var innerCell = sortedCells[j].cell;
+							var connections;
+
+							if (innerCell.isVertex())
+							{
+								// Get the connecting edge
+								if (k == 0)
+								{
+									connections = cell.connectsAsSource;
+
+								}
+								else
+								{
+									connections = cell.connectsAsTarget;
+								}
+
+								for (var connIndex = 0; connIndex < connections.length; connIndex++)
+								{
+									if (connections[connIndex].source == innerCell
+											|| connections[connIndex].target == innerCell)
+									{
+										connectedEdgeCount += connections[connIndex].edges
+												.length;
+										connectedEdgeGroupCount++;
+
+										connectedEdges.push(connections[connIndex]);
+									}
+								}
+							}
+							else
+							{
+								connectedEdgeCount += innerCell.edges.length;
+								connectedEdgeGroupCount++;
+								connectedEdges.push(innerCell);
+							}
+						}
+
+						var requiredWidth = (connectedEdgeCount + 1)
+								* this.prefHozEdgeSep;
+
+						// Add a buffer on the edges of the vertex if the edge count allows
+						if (cell.width > requiredWidth
+								+ (2 * this.prefHozEdgeSep))
+						{
+							leftLimit += this.prefHozEdgeSep;
+							rightLimit -= this.prefHozEdgeSep;
+						}
+
+						var availableWidth = rightLimit - leftLimit;
+						var edgeSpacing = availableWidth / connectedEdgeCount;
+
+						var currentX = leftLimit + edgeSpacing / 2.0;
+						var currentYOffset = this.minEdgeJetty - this.prefVertEdgeOff;
+						var maxYOffset = 0;
+
+						for (var j = 0; j < connectedEdges.length; j++)
+						{
+							var numActualEdges = connectedEdges[j].edges
+									.length;
+							var pos = this.jettyPositions[connectedEdges[j].ids[0]];
+							
+							if (pos == null)
+							{
+								pos = [];
+								this.jettyPositions[connectedEdges[j].ids[0]] = pos;
+							}
+
+							if (j < connectedEdgeCount / 2)
+							{
+								currentYOffset += this.prefVertEdgeOff;
+							}
+							else if (j > connectedEdgeCount / 2)
+							{
+								currentYOffset -= this.prefVertEdgeOff;
+							}
+							// Ignore the case if equals, this means the second of 2
+							// jettys with the same y (even number of edges)
+
+							for (var m = 0; m < numActualEdges; m++)
+							{
+								pos[m * 4 + k * 2] = currentX;
+								currentX += edgeSpacing;
+								pos[m * 4 + k * 2 + 1] = currentYOffset;
+							}
+							
+							maxYOffset = Math.max(maxYOffset,
+									currentYOffset);
+						}
+					}
+
+					currentCells = cell.getNextLayerConnectedCells(rankIndex);
+
+					currentRank = rankIndex + 1;
+				}
+			}
+		}
+	}
+};
+
+/**
+ * Function: setEdgePosition
+ * 
+ * Fixes the control points
+ */
+mxCoordinateAssignment.prototype.setEdgePosition = function(cell)
+{
+	// For parallel edges we need to seperate out the points a
+	// little
+	var offsetX = 0;
+	// Only set the edge control points once
+
+	if (cell.temp[0] != 101207)
+	{
+		var maxRank = cell.maxRank;
+		var minRank = cell.minRank;
+		
+		if (maxRank == minRank)
+		{
+			maxRank = cell.source.maxRank;
+			minRank = cell.target.minRank;
+		}
+		
+		var parallelEdgeCount = 0;
+		var jettys = this.jettyPositions[cell.ids[0]];
+
+		var source = cell.isReversed ? cell.target.cell : cell.source.cell;
+		var graph = this.layout.graph;
+		var layoutReversed = this.orientation == mxConstants.DIRECTION_EAST
+				|| this.orientation == mxConstants.DIRECTION_SOUTH;
+
+		for (var i = 0; i < cell.edges.length; i++)
+		{
+			var realEdge = cell.edges[i];
+			var realSource = this.layout.getVisibleTerminal(realEdge, true);
+
+			//List oldPoints = graph.getPoints(realEdge);
+			var newPoints = [];
+
+			// Single length reversed edges end up with the jettys in the wrong
+			// places. Since single length edges only have jettys, not segment
+			// control points, we just say the edge isn't reversed in this section
+			var reversed = cell.isReversed;
+			
+			if (realSource != source)
+			{
+				// The real edges include all core model edges and these can go
+				// in both directions. If the source of the hierarchical model edge
+				// isn't the source of the specific real edge in this iteration
+				// treat if as reversed
+				reversed = !reversed;
+			}
+
+			// First jetty of edge
+			if (jettys != null)
+			{
+				var arrayOffset = reversed ? 2 : 0;
+				var y = reversed ?
+						(layoutReversed ? this.rankBottomY[minRank] : this.rankTopY[minRank]) :
+							(layoutReversed ? this.rankTopY[maxRank] : this.rankBottomY[maxRank]);
+				var jetty = jettys[parallelEdgeCount * 4 + 1 + arrayOffset];
+				
+				if (reversed != layoutReversed)
+				{
+					jetty = -jetty;
+				}
+				
+				y += jetty;
+				var x = jettys[parallelEdgeCount * 4 + arrayOffset];
+				
+				var modelSource = graph.model.getTerminal(realEdge, true);
+
+				if (this.layout.isPort(modelSource) && graph.model.getParent(modelSource) == realSource)
+				{
+					var state = graph.view.getState(modelSource);
+					
+					if (state != null)
+					{
+						x = state.x;
+					}
+					else
+					{
+						x = realSource.geometry.x + cell.source.width * modelSource.geometry.x;
+					}
+				}
+
+				if (this.orientation == mxConstants.DIRECTION_NORTH
+						|| this.orientation == mxConstants.DIRECTION_SOUTH)
+				{
+					newPoints.push(new mxPoint(x, y));
+					
+					if (this.layout.edgeStyle == mxHierarchicalEdgeStyle.CURVE)
+					{
+						newPoints.push(new mxPoint(x, y + jetty));
+					}
+				}
+				else
+				{
+					newPoints.push(new mxPoint(y, x));
+					
+					if (this.layout.edgeStyle == mxHierarchicalEdgeStyle.CURVE)
+					{
+						newPoints.push(new mxPoint(y + jetty, x));
+					}
+				}
+			}
+
+			// Declare variables to define loop through edge points and 
+			// change direction if edge is reversed
+
+			var loopStart = cell.x.length - 1;
+			var loopLimit = -1;
+			var loopDelta = -1;
+			var currentRank = cell.maxRank - 1;
+
+			if (reversed)
+			{
+				loopStart = 0;
+				loopLimit = cell.x.length;
+				loopDelta = 1;
+				currentRank = cell.minRank + 1;
+			}
+			// Reversed edges need the points inserted in
+			// reverse order
+			for (var j = loopStart; (cell.maxRank != cell.minRank) && j != loopLimit; j += loopDelta)
+			{
+				// The horizontal position in a vertical layout
+				var positionX = cell.x[j] + offsetX;
+
+				// Work out the vertical positions in a vertical layout
+				// in the edge buffer channels above and below this rank
+				var topChannelY = (this.rankTopY[currentRank] + this.rankBottomY[currentRank + 1]) / 2.0;
+				var bottomChannelY = (this.rankTopY[currentRank - 1] + this.rankBottomY[currentRank]) / 2.0;
+
+				if (reversed)
+				{
+					var tmp = topChannelY;
+					topChannelY = bottomChannelY;
+					bottomChannelY = tmp;
+				}
+
+				if (this.orientation == mxConstants.DIRECTION_NORTH ||
+					this.orientation == mxConstants.DIRECTION_SOUTH)
+				{
+					newPoints.push(new mxPoint(positionX, topChannelY));
+					newPoints.push(new mxPoint(positionX, bottomChannelY));
+				}
+				else
+				{
+					newPoints.push(new mxPoint(topChannelY, positionX));
+					newPoints.push(new mxPoint(bottomChannelY, positionX));
+				}
+
+				this.limitX = Math.max(this.limitX, positionX);
+				currentRank += loopDelta;
+			}
+
+			// Second jetty of edge
+			if (jettys != null)
+			{
+				var arrayOffset = reversed ? 2 : 0;
+				var rankY = reversed ?
+						(layoutReversed ? this.rankTopY[maxRank] : this.rankBottomY[maxRank]) :
+							(layoutReversed ? this.rankBottomY[minRank] : this.rankTopY[minRank]);
+				var jetty = jettys[parallelEdgeCount * 4 + 3 - arrayOffset];
+				
+				if (reversed != layoutReversed)
+				{
+					jetty = -jetty;
+				}
+				var y = rankY - jetty;
+				var x = jettys[parallelEdgeCount * 4 + 2 - arrayOffset];
+				
+				var modelTarget = graph.model.getTerminal(realEdge, false);
+				var realTarget = this.layout.getVisibleTerminal(realEdge, false);
+
+				if (this.layout.isPort(modelTarget) && graph.model.getParent(modelTarget) == realTarget)
+				{
+					var state = graph.view.getState(modelTarget);
+					
+					if (state != null)
+					{
+						x = state.x;
+					}
+					else
+					{
+						x = realTarget.geometry.x + cell.target.width * modelTarget.geometry.x;
+					}
+				}
+
+				if (this.orientation == mxConstants.DIRECTION_NORTH ||
+						this.orientation == mxConstants.DIRECTION_SOUTH)
+				{
+					if (this.layout.edgeStyle == mxHierarchicalEdgeStyle.CURVE)
+					{
+						newPoints.push(new mxPoint(x, y - jetty));
+					}
+
+					newPoints.push(new mxPoint(x, y));
+				}
+				else
+				{
+					if (this.layout.edgeStyle == mxHierarchicalEdgeStyle.CURVE)
+					{
+						newPoints.push(new mxPoint(y - jetty, x));
+					}
+
+					newPoints.push(new mxPoint(y, x));
+				}
+			}
+
+			if (cell.isReversed)
+			{
+				this.processReversedEdge(cell, realEdge);
+			}
+
+			this.layout.setEdgePoints(realEdge, newPoints);
+
+			// Increase offset so next edge is drawn next to
+			// this one
+			if (offsetX == 0.0)
+			{
+				offsetX = this.parallelEdgeSpacing;
+			}
+			else if (offsetX > 0)
+			{
+				offsetX = -offsetX;
+			}
+			else
+			{
+				offsetX = -offsetX + this.parallelEdgeSpacing;
+			}
+			
+			parallelEdgeCount++;
+		}
+
+		cell.temp[0] = 101207;
+	}
+};
+
+
+/**
+ * Function: setVertexLocation
+ * 
+ * Fixes the position of the specified vertex.
+ * 
+ * Parameters:
+ * 
+ * cell - the vertex to position
+ */
+mxCoordinateAssignment.prototype.setVertexLocation = function(cell)
+{
+	var realCell = cell.cell;
+	var positionX = cell.x[0] - cell.width / 2;
+	var positionY = cell.y[0] - cell.height / 2;
+
+	this.rankTopY[cell.minRank] = Math.min(this.rankTopY[cell.minRank], positionY);
+	this.rankBottomY[cell.minRank] = Math.max(this.rankBottomY[cell.minRank],
+			positionY + cell.height);
+
+	if (this.orientation == mxConstants.DIRECTION_NORTH ||
+		this.orientation == mxConstants.DIRECTION_SOUTH)
+	{
+		this.layout.setVertexLocation(realCell, positionX, positionY);
+	}
+	else
+	{
+		this.layout.setVertexLocation(realCell, positionY, positionX);
+	}
+
+	this.limitX = Math.max(this.limitX, positionX + cell.width);
+};
+
+/**
+ * Function: processReversedEdge
+ * 
+ * Hook to add additional processing
+ * 
+ * Parameters:
+ * 
+ * edge - the hierarchical model edge
+ * realEdge - the real edge in the graph
+ */
+mxCoordinateAssignment.prototype.processReversedEdge = function(graph, model)
+{
+	// hook for subclassers
+};
+
+/**
+ * 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;
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxSwimlaneOrdering
+ * 
+ * An implementation of the first stage of the Sugiyama layout. Straightforward
+ * longest path calculation of layer assignment
+ * 
+ * Constructor: mxSwimlaneOrdering
+ *
+ * Creates a cycle remover for the given internal model.
+ */
+function mxSwimlaneOrdering(layout)
+{
+	this.layout = layout;
+};
+
+/**
+ * Extends mxHierarchicalLayoutStage.
+ */
+mxSwimlaneOrdering.prototype = new mxHierarchicalLayoutStage();
+mxSwimlaneOrdering.prototype.constructor = mxSwimlaneOrdering;
+
+/**
+ * Variable: layout
+ * 
+ * Reference to the enclosing <mxHierarchicalLayout>.
+ */
+mxSwimlaneOrdering.prototype.layout = null;
+
+/**
+ * Function: execute
+ * 
+ * Takes the graph detail and configuration information within the facade
+ * and creates the resulting laid out graph within that facade for further
+ * use.
+ */
+mxSwimlaneOrdering.prototype.execute = function(parent)
+{
+	var model = this.layout.getModel();
+	var seenNodes = new Object();
+	var unseenNodes = mxUtils.clone(model.vertexMapper, null, true);
+	
+	// Perform a dfs through the internal model. If a cycle is found,
+	// reverse it.
+	var rootsArray = null;
+	
+	if (model.roots != null)
+	{
+		var modelRoots = model.roots;
+		rootsArray = [];
+		
+		for (var i = 0; i < modelRoots.length; i++)
+		{
+			var nodeId = mxCellPath.create(modelRoots[i]);
+			rootsArray[i] = model.vertexMapper.get(modelRoots[i]);
+		}
+	}
+
+	model.visit(function(parent, node, connectingEdge, layer, seen)
+	{
+		// Check if the cell is in it's own ancestor list, if so
+		// invert the connecting edge and reverse the target/source
+		// relationship to that edge in the parent and the cell
+		// Ancestor hashes only line up within a swimlane
+		var isAncestor = parent != null && parent.swimlaneIndex == node.swimlaneIndex && node.isAncestor(parent);
+
+		// If the source->target swimlane indices go from higher to
+		// lower, the edge is reverse
+		var reversedOverSwimlane = parent != null && connectingEdge != null &&
+						parent.swimlaneIndex < node.swimlaneIndex && connectingEdge.source == node;
+
+		if (isAncestor)
+		{
+			connectingEdge.invert();
+			mxUtils.remove(connectingEdge, parent.connectsAsSource);
+			node.connectsAsSource.push(connectingEdge);
+			parent.connectsAsTarget.push(connectingEdge);
+			mxUtils.remove(connectingEdge, node.connectsAsTarget);
+		}
+		else if (reversedOverSwimlane)
+		{
+			connectingEdge.invert();
+			mxUtils.remove(connectingEdge, parent.connectsAsTarget);
+			node.connectsAsTarget.push(connectingEdge);
+			parent.connectsAsSource.push(connectingEdge);
+			mxUtils.remove(connectingEdge, node.connectsAsSource);
+		}
+		
+		var cellId = mxCellPath.create(node.cell);
+		seenNodes[cellId] = node;
+		delete unseenNodes[cellId];
+	}, rootsArray, true, null);
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxHierarchicalLayout
+ * 
+ * A hierarchical layout algorithm.
+ * 
+ * Constructor: mxHierarchicalLayout
+ *
+ * Constructs a new hierarchical layout algorithm.
+ *
+ * Arguments:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ * orientation - Optional constant that defines the orientation of this
+ * layout.
+ * deterministic - Optional boolean that specifies if this layout should be
+ * deterministic. Default is true.
+ */
+function mxHierarchicalLayout(graph, orientation, deterministic)
+{
+	mxGraphLayout.call(this, graph);
+	this.orientation = (orientation != null) ? orientation : mxConstants.DIRECTION_NORTH;
+	this.deterministic = (deterministic != null) ? deterministic : true;
+};
+
+var mxHierarchicalEdgeStyle =
+{
+	ORTHOGONAL: 1,
+	POLYLINE: 2,
+	STRAIGHT: 3,
+	CURVE: 4
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxHierarchicalLayout.prototype = new mxGraphLayout();
+mxHierarchicalLayout.prototype.constructor = mxHierarchicalLayout;
+
+/**
+ * Variable: roots
+ * 
+ * Holds the array of <mxCell> that this layout contains.
+ */
+mxHierarchicalLayout.prototype.roots = null;
+
+/**
+ * Variable: resizeParent
+ * 
+ * Specifies if the parent should be resized after the layout so that it
+ * contains all the child cells. Default is false. See also <parentBorder>.
+ */
+mxHierarchicalLayout.prototype.resizeParent = false;
+
+/**
+ * Variable: maintainParentLocation
+ * 
+ * Specifies if the parent location should be maintained, so that the
+ * top, left corner stays the same before and after execution of
+ * the layout. Default is false for backwards compatibility.
+ */
+mxHierarchicalLayout.prototype.maintainParentLocation = false;
+
+/**
+ * Variable: moveParent
+ * 
+ * Specifies if the parent should be moved if <resizeParent> is enabled.
+ * Default is false.
+ */
+mxHierarchicalLayout.prototype.moveParent = false;
+
+/**
+ * Variable: parentBorder
+ * 
+ * The border to be added around the children if the parent is to be
+ * resized using <resizeParent>. Default is 0.
+ */
+mxHierarchicalLayout.prototype.parentBorder = 0;
+
+/**
+ * Variable: intraCellSpacing
+ * 
+ * The spacing buffer added between cells on the same layer. Default is 30.
+ */
+mxHierarchicalLayout.prototype.intraCellSpacing = 30;
+
+/**
+ * Variable: interRankCellSpacing
+ * 
+ * The spacing buffer added between cell on adjacent layers. Default is 50.
+ */
+mxHierarchicalLayout.prototype.interRankCellSpacing = 100;
+
+/**
+ * Variable: interHierarchySpacing
+ * 
+ * The spacing buffer between unconnected hierarchies. Default is 60.
+ */
+mxHierarchicalLayout.prototype.interHierarchySpacing = 60;
+
+/**
+ * Variable: parallelEdgeSpacing
+ * 
+ * The distance between each parallel edge on each ranks for long edges
+ */
+mxHierarchicalLayout.prototype.parallelEdgeSpacing = 10;
+
+/**
+ * Variable: orientation
+ * 
+ * The position of the root node(s) relative to the laid out graph in.
+ * Default is <mxConstants.DIRECTION_NORTH>.
+ */
+mxHierarchicalLayout.prototype.orientation = mxConstants.DIRECTION_NORTH;
+
+/**
+ * Variable: fineTuning
+ * 
+ * Whether or not to perform local optimisations and iterate multiple times
+ * through the algorithm. Default is true.
+ */
+mxHierarchicalLayout.prototype.fineTuning = true;
+
+/**
+ * 
+ * Variable: tightenToSource
+ * 
+ * Whether or not to tighten the assigned ranks of vertices up towards
+ * the source cells.
+ */
+mxHierarchicalLayout.prototype.tightenToSource = true;
+
+/**
+ * Variable: disableEdgeStyle
+ * 
+ * Specifies if the STYLE_NOEDGESTYLE flag should be set on edges that are
+ * modified by the result. Default is true.
+ */
+mxHierarchicalLayout.prototype.disableEdgeStyle = true;
+
+/**
+ * Variable: traverseAncestors
+ * 
+ * Whether or not to drill into child cells and layout in reverse
+ * group order. This also cause the layout to navigate edges whose 
+ * terminal vertices  * have different parents but are in the same 
+ * ancestry chain
+ */
+mxHierarchicalLayout.prototype.traverseAncestors = true;
+
+/**
+ * Variable: model
+ * 
+ * The internal <mxGraphHierarchyModel> formed of the layout.
+ */
+mxHierarchicalLayout.prototype.model = null;
+
+/**
+ * Variable: edgesSet
+ * 
+ * A cache of edges whose source terminal is the key
+ */
+mxHierarchicalLayout.prototype.edgesCache = null;
+
+/**
+ * Variable: edgesSet
+ * 
+ * A cache of edges whose source terminal is the key
+ */
+mxHierarchicalLayout.prototype.edgeSourceTermCache = null;
+
+/**
+ * Variable: edgesSet
+ * 
+ * A cache of edges whose source terminal is the key
+ */
+mxHierarchicalLayout.prototype.edgesTargetTermCache = null;
+
+/**
+ * Variable: edgeStyle
+ * 
+ * The style to apply between cell layers to edge segments
+ */
+mxHierarchicalLayout.prototype.edgeStyle = mxHierarchicalEdgeStyle.POLYLINE;
+
+/**
+ * Function: getModel
+ * 
+ * Returns the internal <mxGraphHierarchyModel> for this layout algorithm.
+ */
+mxHierarchicalLayout.prototype.getModel = function()
+{
+	return this.model;
+};
+
+/**
+ * Function: execute
+ * 
+ * Executes the layout for the children of the specified parent.
+ * 
+ * Parameters:
+ * 
+ * parent - Parent <mxCell> that contains the children to be laid out.
+ * roots - Optional starting roots of the layout.
+ */
+mxHierarchicalLayout.prototype.execute = function(parent, roots)
+{
+	this.parent = parent;
+	var model = this.graph.model;
+	this.edgesCache = new mxDictionary();
+	this.edgeSourceTermCache = new mxDictionary();
+	this.edgesTargetTermCache = new mxDictionary();
+
+	if (roots != null && !(roots instanceof Array))
+	{
+		roots = [roots];
+	}
+	
+	// If the roots are set and the parent is set, only
+	// use the roots that are some dependent of the that
+	// parent.
+	// If just the root are set, use them as-is
+	// If just the parent is set use it's immediate
+	// children as the initial set
+
+	if (roots == null && parent == null)
+	{
+		// TODO indicate the problem
+		return;
+	}
+	
+	//  Maintaining parent location
+	this.parentX = null;
+	this.parentY = null;
+	
+	if (parent != this.root && model.isVertex(parent) != null && this.maintainParentLocation)
+	{
+		var geo = this.graph.getCellGeometry(parent);
+		
+		if (geo != null)
+		{
+			this.parentX = geo.x;
+			this.parentY = geo.y;
+		}
+	}
+	
+	if (roots != null)
+	{
+		var rootsCopy = [];
+
+		for (var i = 0; i < roots.length; i++)
+		{
+			var ancestor = parent != null ? model.isAncestor(parent, roots[i]) : true;
+			
+			if (ancestor && model.isVertex(roots[i]))
+			{
+				rootsCopy.push(roots[i]);
+			}
+		}
+
+		this.roots = rootsCopy;
+	}
+	
+	model.beginUpdate();
+	try
+	{
+		this.run(parent);
+		
+		if (this.resizeParent && !this.graph.isCellCollapsed(parent))
+		{
+			this.graph.updateGroupBounds([parent], this.parentBorder, this.moveParent);
+		}
+		
+		// Maintaining parent location
+		if (this.parentX != null && this.parentY != null)
+		{
+			var geo = this.graph.getCellGeometry(parent);
+			
+			if (geo != null)
+			{
+				geo = geo.clone();
+				geo.x = this.parentX;
+				geo.y = this.parentY;
+				model.setGeometry(parent, geo);
+			}
+		}
+	}
+	finally
+	{
+		model.endUpdate();
+	}
+};
+
+/**
+ * Function: findRoots
+ * 
+ * Returns all visible children in the given parent which do not have
+ * incoming edges. If the result is empty then the children with the
+ * maximum difference between incoming and outgoing edges are returned.
+ * This takes into account edges that are being promoted to the given
+ * root due to invisible children or collapsed cells.
+ * 
+ * Parameters:
+ * 
+ * parent - <mxCell> whose children should be checked.
+ * vertices - array of vertices to limit search to
+ */
+mxHierarchicalLayout.prototype.findRoots = function(parent, vertices)
+{
+	var roots = [];
+	
+	if (parent != null && vertices != null)
+	{
+		var model = this.graph.model;
+		var best = null;
+		var maxDiff = -100000;
+		
+		for (var i in vertices)
+		{
+			var cell = vertices[i];
+
+			if (model.isVertex(cell) && this.graph.isCellVisible(cell))
+			{
+				var conns = this.getEdges(cell);
+				var fanOut = 0;
+				var fanIn = 0;
+
+				for (var k = 0; k < conns.length; k++)
+				{
+					var src = this.getVisibleTerminal(conns[k], true);
+
+					if (src == cell)
+					{
+						fanOut++;
+					}
+					else
+					{
+						fanIn++;
+					}
+				}
+
+				if (fanIn == 0 && fanOut > 0)
+				{
+					roots.push(cell);
+				}
+
+				var diff = fanOut - fanIn;
+
+				if (diff > maxDiff)
+				{
+					maxDiff = diff;
+					best = cell;
+				}
+			}
+		}
+		
+		if (roots.length == 0 && best != null)
+		{
+			roots.push(best);
+		}
+	}
+	
+	return roots;
+};
+
+/**
+ * Function: getEdges
+ * 
+ * Returns the connected edges for the given cell.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose edges should be returned.
+ */
+mxHierarchicalLayout.prototype.getEdges = function(cell)
+{
+	var cachedEdges = this.edgesCache.get(cell);
+	
+	if (cachedEdges != null)
+	{
+		return cachedEdges;
+	}
+
+	var model = this.graph.model;
+	var edges = [];
+	var isCollapsed = this.graph.isCellCollapsed(cell);
+	var childCount = model.getChildCount(cell);
+
+	for (var i = 0; i < childCount; i++)
+	{
+		var child = model.getChildAt(cell, i);
+
+		if (this.isPort(child))
+		{
+			edges = edges.concat(model.getEdges(child, true, true));
+		}
+		else if (isCollapsed || !this.graph.isCellVisible(child))
+		{
+			edges = edges.concat(model.getEdges(child, true, true));
+		}
+	}
+
+	edges = edges.concat(model.getEdges(cell, true, true));
+	var result = [];
+	
+	for (var i = 0; i < edges.length; i++)
+	{
+		var source = this.getVisibleTerminal(edges[i], true);
+		var target = this.getVisibleTerminal(edges[i], false);
+		
+		if ((source == target) || ((source != target) && ((target == cell && (this.parent == null || this.graph.isValidAncestor(source, this.parent, this.traverseAncestors))) ||
+			(source == cell && (this.parent == null ||
+					this.graph.isValidAncestor(target, this.parent, this.traverseAncestors))))))
+		{
+			result.push(edges[i]);
+		}
+	}
+
+	this.edgesCache.put(cell, result);
+
+	return result;
+};
+
+/**
+ * Function: getVisibleTerminal
+ * 
+ * Helper function to return visible terminal for edge allowing for ports
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> whose edges should be returned.
+ * source - Boolean that specifies whether the source or target terminal is to be returned
+ */
+mxHierarchicalLayout.prototype.getVisibleTerminal = function(edge, source)
+{
+	var terminalCache = this.edgesTargetTermCache;
+	
+	if (source)
+	{
+		terminalCache = this.edgeSourceTermCache;
+	}
+
+	var term = terminalCache.get(edge);
+
+	if (term != null)
+	{
+		return term;
+	}
+
+	var state = this.graph.view.getState(edge);
+	
+	var terminal = (state != null) ? state.getVisibleTerminal(source) : this.graph.view.getVisibleTerminal(edge, source);
+	
+	if (terminal == null)
+	{
+		terminal = (state != null) ? state.getVisibleTerminal(source) : this.graph.view.getVisibleTerminal(edge, source);
+	}
+
+	if (terminal != null)
+	{
+		if (this.isPort(terminal))
+		{
+			terminal = this.graph.model.getParent(terminal);
+		}
+		
+		terminalCache.put(edge, terminal);
+	}
+
+	return terminal;
+};
+
+/**
+ * Function: run
+ * 
+ * The API method used to exercise the layout upon the graph description
+ * and produce a separate description of the vertex position and edge
+ * routing changes made. It runs each stage of the layout that has been
+ * created.
+ */
+mxHierarchicalLayout.prototype.run = function(parent)
+{
+	// Separate out unconnected hierarchies
+	var hierarchyVertices = [];
+	var allVertexSet = [];
+
+	if (this.roots == null && parent != null)
+	{
+		var filledVertexSet = Object();
+		this.filterDescendants(parent, filledVertexSet);
+
+		this.roots = [];
+		var filledVertexSetEmpty = true;
+
+		// Poor man's isSetEmpty
+		for (var key in filledVertexSet)
+		{
+			if (filledVertexSet[key] != null)
+			{
+				filledVertexSetEmpty = false;
+				break;
+			}
+		}
+
+		while (!filledVertexSetEmpty)
+		{
+			var candidateRoots = this.findRoots(parent, filledVertexSet);
+			
+			// If the candidate root is an unconnected group cell, remove it from
+			// the layout. We may need a custom set that holds such groups and forces
+			// them to be processed for resizing and/or moving.
+			
+
+			for (var i = 0; i < candidateRoots.length; i++)
+			{
+				var vertexSet = Object();
+				hierarchyVertices.push(vertexSet);
+
+				this.traverse(candidateRoots[i], true, null, allVertexSet, vertexSet,
+						hierarchyVertices, filledVertexSet);
+			}
+
+			for (var i = 0; i < candidateRoots.length; i++)
+			{
+				this.roots.push(candidateRoots[i]);
+			}
+			
+			filledVertexSetEmpty = true;
+			
+			// Poor man's isSetEmpty
+			for (var key in filledVertexSet)
+			{
+				if (filledVertexSet[key] != null)
+				{
+					filledVertexSetEmpty = false;
+					break;
+				}
+			}
+		}
+	}
+	else
+	{
+		// Find vertex set as directed traversal from roots
+
+		for (var i = 0; i < this.roots.length; i++)
+		{
+			var vertexSet = Object();
+			hierarchyVertices.push(vertexSet);
+
+			this.traverse(this.roots[i], true, null, allVertexSet, vertexSet,
+					hierarchyVertices, null);
+		}
+	}
+
+	// Iterate through the result removing parents who have children in this layout
+	
+	// Perform a layout for each seperate hierarchy
+	// Track initial coordinate x-positioning
+	var initialX = 0;
+
+	for (var i = 0; i < hierarchyVertices.length; i++)
+	{
+		var vertexSet = hierarchyVertices[i];
+		var tmp = [];
+		
+		for (var key in vertexSet)
+		{
+			tmp.push(vertexSet[key]);
+		}
+		
+		this.model = new mxGraphHierarchyModel(this, tmp, this.roots,
+			parent, this.tightenToSource);
+
+		this.cycleStage(parent);
+		this.layeringStage();
+		
+		this.crossingStage(parent);
+		initialX = this.placementStage(initialX, parent);
+	}
+};
+
+/**
+ * Function: filterDescendants
+ * 
+ * Creates an array of descendant cells
+ */
+mxHierarchicalLayout.prototype.filterDescendants = function(cell, result)
+{
+	var model = this.graph.model;
+
+	if (model.isVertex(cell) && cell != this.parent && this.graph.isCellVisible(cell))
+	{
+		result[mxObjectIdentity.get(cell)] = cell;
+	}
+
+	if (this.traverseAncestors || cell == this.parent
+			&& this.graph.isCellVisible(cell))
+	{
+		var childCount = model.getChildCount(cell);
+
+		for (var i = 0; i < childCount; i++)
+		{
+			var child = model.getChildAt(cell, i);
+			
+			// Ignore ports in the layout vertex list, they are dealt with
+			// in the traversal mechanisms
+			if (!this.isPort(child))
+			{
+				this.filterDescendants(child, result);
+			}
+		}
+	}
+};
+
+/**
+ * Function: isPort
+ * 
+ * Returns true if the given cell is a "port", that is, when connecting to
+ * it, its parent is the connecting vertex in terms of graph traversal
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the port.
+ */
+mxHierarchicalLayout.prototype.isPort = function(cell)
+{
+	if (cell.geometry.relative)
+	{
+		return true;
+	}
+	
+	return false;
+};
+
+/**
+ * Function: getEdgesBetween
+ * 
+ * Returns the edges between the given source and target. This takes into
+ * account collapsed and invisible cells and ports.
+ * 
+ * Parameters:
+ * 
+ * source -
+ * target -
+ * directed -
+ */
+mxHierarchicalLayout.prototype.getEdgesBetween = function(source, target, directed)
+{
+	directed = (directed != null) ? directed : false;
+	var edges = this.getEdges(source);
+	var result = [];
+
+	// Checks if the edge is connected to the correct
+	// cell and returns the first match
+	for (var i = 0; i < edges.length; i++)
+	{
+		var src = this.getVisibleTerminal(edges[i], true);
+		var trg = this.getVisibleTerminal(edges[i], false);
+
+		if ((src == source && trg == target) || (!directed && src == target && trg == source))
+		{
+			result.push(edges[i]);
+		}
+	}
+
+	return result;
+};
+
+/**
+ * Traverses the (directed) graph invoking the given function for each
+ * visited vertex and edge. The function is invoked with the current vertex
+ * and the incoming edge as a parameter. This implementation makes sure
+ * each vertex is only visited once. The function may return false if the
+ * traversal should stop at the given vertex.
+ * 
+ * Parameters:
+ * 
+ * vertex - <mxCell> that represents the vertex where the traversal starts.
+ * directed - boolean indicating if edges should only be traversed
+ * from source to target. Default is true.
+ * edge - Optional <mxCell> that represents the incoming edge. This is
+ * null for the first step of the traversal.
+ * allVertices - Array of cell paths for the visited cells.
+ */
+mxHierarchicalLayout.prototype.traverse = function(vertex, directed, edge, allVertices, currentComp,
+											hierarchyVertices, filledVertexSet)
+{
+	if (vertex != null && allVertices != null)
+	{
+		// Has this vertex been seen before in any traversal
+		// And if the filled vertex set is populated, only 
+		// process vertices in that it contains
+		var vertexID = mxObjectIdentity.get(vertex);
+		
+		if ((allVertices[vertexID] == null)
+				&& (filledVertexSet == null ? true : filledVertexSet[vertexID] != null))
+		{
+			if (currentComp[vertexID] == null)
+			{
+				currentComp[vertexID] = vertex;
+			}
+			if (allVertices[vertexID] == null)
+			{
+				allVertices[vertexID] = vertex;
+			}
+
+			if (filledVertexSet !== null)
+			{
+				delete filledVertexSet[vertexID];
+			}
+
+			var edges = this.getEdges(vertex);
+			var edgeIsSource = [];
+
+			for (var i = 0; i < edges.length; i++)
+			{
+				edgeIsSource[i] = (this.getVisibleTerminal(edges[i], true) == vertex);
+			}
+
+			for (var i = 0; i < edges.length; i++)
+			{
+				if (!directed || edgeIsSource[i])
+				{
+					var next = this.getVisibleTerminal(edges[i], !edgeIsSource[i]);
+					
+					// Check whether there are more edges incoming from the target vertex than outgoing
+					// The hierarchical model treats bi-directional parallel edges as being sourced
+					// from the more "sourced" terminal. If the directions are equal in number, the direction
+					// is that of the natural direction from the roots of the layout.
+					// The checks below are slightly more verbose than need be for performance reasons
+					var netCount = 1;
+
+					for (var j = 0; j < edges.length; j++)
+					{
+						if (j == i)
+						{
+							continue;
+						}
+						else
+						{
+							var isSource2 = edgeIsSource[j];
+							var otherTerm = this.getVisibleTerminal(edges[j], !isSource2);
+							
+							if (otherTerm == next)
+							{
+								if (isSource2)
+								{
+									netCount++;
+								}
+								else
+								{
+									netCount--;
+								}
+							}
+						}
+					}
+
+					if (netCount >= 0)
+					{
+						currentComp = this.traverse(next, directed, edges[i], allVertices,
+							currentComp, hierarchyVertices,
+							filledVertexSet);
+					}
+				}
+			}
+		}
+		else
+		{
+			if (currentComp[vertexID] == null)
+			{
+				// We've seen this vertex before, but not in the current component
+				// This component and the one it's in need to be merged
+
+				for (var i = 0; i < hierarchyVertices.length; i++)
+				{
+					var comp = hierarchyVertices[i];
+
+					if (comp[vertexID] != null)
+					{
+						for (var key in comp)
+						{
+							currentComp[key] = comp[key];
+						}
+						
+						// Remove the current component from the hierarchy set
+						hierarchyVertices.splice(i, 1);
+						return currentComp;
+					}
+				}
+			}
+		}
+	}
+	
+	return currentComp;
+};
+
+/**
+ * Function: cycleStage
+ * 
+ * Executes the cycle stage using mxMinimumCycleRemover.
+ */
+mxHierarchicalLayout.prototype.cycleStage = function(parent)
+{
+	var cycleStage = new mxMinimumCycleRemover(this);
+	cycleStage.execute(parent);
+};
+
+/**
+ * Function: layeringStage
+ * 
+ * Implements first stage of a Sugiyama layout.
+ */
+mxHierarchicalLayout.prototype.layeringStage = function()
+{
+	this.model.initialRank();
+	this.model.fixRanks();
+};
+
+/**
+ * Function: crossingStage
+ * 
+ * Executes the crossing stage using mxMedianHybridCrossingReduction.
+ */
+mxHierarchicalLayout.prototype.crossingStage = function(parent)
+{
+	var crossingStage = new mxMedianHybridCrossingReduction(this);
+	crossingStage.execute(parent);
+};
+
+/**
+ * Function: placementStage
+ * 
+ * Executes the placement stage using mxCoordinateAssignment.
+ */
+mxHierarchicalLayout.prototype.placementStage = function(initialX, parent)
+{
+	var placementStage = new mxCoordinateAssignment(this, this.intraCellSpacing,
+			this.interRankCellSpacing, this.orientation, initialX,
+			this.parallelEdgeSpacing);
+	placementStage.fineTuning = this.fineTuning;
+	placementStage.execute(parent);
+	
+	return placementStage.limitX + this.interHierarchySpacing;
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxSwimlaneLayout
+ * 
+ * A hierarchical layout algorithm.
+ * 
+ * Constructor: mxSwimlaneLayout
+ *
+ * Constructs a new hierarchical layout algorithm.
+ *
+ * Arguments:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ * orientation - Optional constant that defines the orientation of this
+ * layout.
+ * deterministic - Optional boolean that specifies if this layout should be
+ * deterministic. Default is true.
+ */
+function mxSwimlaneLayout(graph, orientation, deterministic)
+{
+	mxGraphLayout.call(this, graph);
+	this.orientation = (orientation != null) ? orientation : mxConstants.DIRECTION_NORTH;
+	this.deterministic = (deterministic != null) ? deterministic : true;
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxSwimlaneLayout.prototype = new mxGraphLayout();
+mxSwimlaneLayout.prototype.constructor = mxSwimlaneLayout;
+
+/**
+ * Variable: roots
+ * 
+ * Holds the array of <mxCell> that this layout contains.
+ */
+mxSwimlaneLayout.prototype.roots = null;
+
+/**
+ * Variable: swimlanes
+ * 
+ * Holds the array of <mxCell> of the ordered swimlanes to lay out
+ */
+mxSwimlaneLayout.prototype.swimlanes = null;
+
+/**
+ * Variable: dummyVertices
+ * 
+ * Holds an array of <mxCell> of dummy vertices inserted during the layout
+ * to pad out empty swimlanes
+ */
+mxSwimlaneLayout.prototype.dummyVertices = null;
+
+/**
+ * Variable: dummyVertexWidth
+ * 
+ * The cell width of any dummy vertices inserted
+ */
+mxSwimlaneLayout.prototype.dummyVertexWidth = 50;
+
+/**
+ * Variable: resizeParent
+ * 
+ * Specifies if the parent should be resized after the layout so that it
+ * contains all the child cells. Default is false. See also <parentBorder>.
+ */
+mxSwimlaneLayout.prototype.resizeParent = false;
+
+/**
+ * Variable: maintainParentLocation
+ * 
+ * Specifies if the parent location should be maintained, so that the
+ * top, left corner stays the same before and after execution of
+ * the layout. Default is false for backwards compatibility.
+ */
+mxSwimlaneLayout.prototype.maintainParentLocation = false;
+
+/**
+ * Variable: moveParent
+ * 
+ * Specifies if the parent should be moved if <resizeParent> is enabled.
+ * Default is false.
+ */
+mxSwimlaneLayout.prototype.moveParent = false;
+
+/**
+ * Variable: parentBorder
+ * 
+ * The border to be added around the children if the parent is to be
+ * resized using <resizeParent>. Default is 0.
+ */
+mxSwimlaneLayout.prototype.parentBorder = 30;
+
+/**
+ * Variable: intraCellSpacing
+ * 
+ * The spacing buffer added between cells on the same layer. Default is 30.
+ */
+mxSwimlaneLayout.prototype.intraCellSpacing = 30;
+
+/**
+ * Variable: interRankCellSpacing
+ * 
+ * The spacing buffer added between cell on adjacent layers. Default is 50.
+ */
+mxSwimlaneLayout.prototype.interRankCellSpacing = 100;
+
+/**
+ * Variable: interHierarchySpacing
+ * 
+ * The spacing buffer between unconnected hierarchies. Default is 60.
+ */
+mxSwimlaneLayout.prototype.interHierarchySpacing = 60;
+
+/**
+ * Variable: parallelEdgeSpacing
+ * 
+ * The distance between each parallel edge on each ranks for long edges
+ */
+mxSwimlaneLayout.prototype.parallelEdgeSpacing = 10;
+
+/**
+ * Variable: orientation
+ * 
+ * The position of the root node(s) relative to the laid out graph in.
+ * Default is <mxConstants.DIRECTION_NORTH>.
+ */
+mxSwimlaneLayout.prototype.orientation = mxConstants.DIRECTION_NORTH;
+
+/**
+ * Variable: fineTuning
+ * 
+ * Whether or not to perform local optimisations and iterate multiple times
+ * through the algorithm. Default is true.
+ */
+mxSwimlaneLayout.prototype.fineTuning = true;
+
+/**
+ * 
+ * Variable: tightenToSource
+ * 
+ * Whether or not to tighten the assigned ranks of vertices up towards
+ * the source cells.
+ */
+mxSwimlaneLayout.prototype.tightenToSource = true;
+
+/**
+ * Variable: disableEdgeStyle
+ * 
+ * Specifies if the STYLE_NOEDGESTYLE flag should be set on edges that are
+ * modified by the result. Default is true.
+ */
+mxSwimlaneLayout.prototype.disableEdgeStyle = true;
+
+/**
+ * Variable: traverseAncestors
+ * 
+ * Whether or not to drill into child cells and layout in reverse
+ * group order. This also cause the layout to navigate edges whose 
+ * terminal vertices  * have different parents but are in the same 
+ * ancestry chain
+ */
+mxSwimlaneLayout.prototype.traverseAncestors = true;
+
+/**
+ * Variable: model
+ * 
+ * The internal <mxSwimlaneModel> formed of the layout.
+ */
+mxSwimlaneLayout.prototype.model = null;
+
+/**
+ * Variable: edgesSet
+ * 
+ * A cache of edges whose source terminal is the key
+ */
+mxSwimlaneLayout.prototype.edgesCache = null;
+
+/**
+ * Variable: edgesSet
+ * 
+ * A cache of edges whose source terminal is the key
+ */
+mxHierarchicalLayout.prototype.edgeSourceTermCache = null;
+
+/**
+ * Variable: edgesSet
+ * 
+ * A cache of edges whose source terminal is the key
+ */
+mxHierarchicalLayout.prototype.edgesTargetTermCache = null;
+
+/**
+ * Variable: edgeStyle
+ * 
+ * The style to apply between cell layers to edge segments
+ */
+mxHierarchicalLayout.prototype.edgeStyle = mxHierarchicalEdgeStyle.POLYLINE;
+
+/**
+ * Function: getModel
+ * 
+ * Returns the internal <mxSwimlaneModel> for this layout algorithm.
+ */
+mxSwimlaneLayout.prototype.getModel = function()
+{
+	return this.model;
+};
+
+/**
+ * Function: execute
+ * 
+ * Executes the layout for the children of the specified parent.
+ * 
+ * Parameters:
+ * 
+ * parent - Parent <mxCell> that contains the children to be laid out.
+ * swimlanes - Ordered array of swimlanes to be laid out
+ */
+mxSwimlaneLayout.prototype.execute = function(parent, swimlanes)
+{
+	this.parent = parent;
+	var model = this.graph.model;
+	this.edgesCache = new mxDictionary();
+	this.edgeSourceTermCache = new mxDictionary();
+	this.edgesTargetTermCache = new mxDictionary();
+
+	// If the roots are set and the parent is set, only
+	// use the roots that are some dependent of the that
+	// parent.
+	// If just the root are set, use them as-is
+	// If just the parent is set use it's immediate
+	// children as the initial set
+
+	if (swimlanes == null || swimlanes.length < 1)
+	{
+		// TODO indicate the problem
+		return;
+	}
+
+	if (parent == null)
+	{
+		parent = model.getParent(swimlanes[0]);
+	}
+
+	//  Maintaining parent location
+	this.parentX = null;
+	this.parentY = null;
+	
+	if (parent != this.root && model.isVertex(parent) != null && this.maintainParentLocation)
+	{
+		var geo = this.graph.getCellGeometry(parent);
+		
+		if (geo != null)
+		{
+			this.parentX = geo.x;
+			this.parentY = geo.y;
+		}
+	}
+
+	this.swimlanes = swimlanes;
+	this.dummyVertices = [];
+	// Check the swimlanes all have vertices
+	// in them
+	for (var i = 0; i < swimlanes.length; i++)
+	{
+		var children = this.graph.getChildCells(swimlanes[i]);
+		
+		if (children == null || children.length == 0)
+		{
+			var vertex = this.graph.insertVertex(swimlanes[i], null, null, 0, 0, this.dummyVertexWidth, 0);
+			this.dummyVertices.push(vertex);
+		}
+	}
+	
+	model.beginUpdate();
+	try
+	{
+		this.run(parent);
+		
+		if (this.resizeParent && !this.graph.isCellCollapsed(parent))
+		{
+			this.graph.updateGroupBounds([parent], this.parentBorder, this.moveParent);
+		}
+		
+		// Maintaining parent location
+		if (this.parentX != null && this.parentY != null)
+		{
+			var geo = this.graph.getCellGeometry(parent);
+			
+			if (geo != null)
+			{
+				geo = geo.clone();
+				geo.x = this.parentX;
+				geo.y = this.parentY;
+				model.setGeometry(parent, geo);
+			}
+		}
+
+		this.graph.removeCells(this.dummyVertices);
+	}
+	finally
+	{
+		model.endUpdate();
+	}
+};
+
+/**
+ * Function: updateGroupBounds
+ * 
+ * Updates the bounds of the given array of groups so that it includes
+ * all child vertices.
+ * 
+ */
+mxSwimlaneLayout.prototype.updateGroupBounds = function()
+{
+	// Get all vertices and edge in the layout
+	var cells = [];
+	var model = this.model;
+	
+	for (var key in model.edgeMapper)
+	{
+		var edge = model.edgeMapper[key];
+		
+		for (var i = 0; i < edge.edges.length; i++)
+		{
+			cells.push(edge.edges[i]);
+		}
+	}
+	
+	var layoutBounds = this.graph.getBoundingBoxFromGeometry(cells, true);
+	var childBounds = [];
+
+	for (var i = 0; i < this.swimlanes.length; i++)
+	{
+		var lane = this.swimlanes[i];
+		var geo = this.graph.getCellGeometry(lane);
+		
+		if (geo != null)
+		{
+			var children = this.graph.getChildCells(lane);
+			
+			var size = (this.graph.isSwimlane(lane)) ?
+					this.graph.getStartSize(lane) : new mxRectangle();
+
+			var bounds = this.graph.getBoundingBoxFromGeometry(children);
+			childBounds[i] = bounds;
+			var childrenY = bounds.y + geo.y - size.height - this.parentBorder;
+			var maxChildrenY = bounds.y + geo.y + bounds.height;
+
+			if (layoutBounds == null)
+			{
+				layoutBounds = new mxRectangle(0, childrenY, 0, maxChildrenY - childrenY);
+			}
+			else
+			{
+				layoutBounds.y = Math.min(layoutBounds.y, childrenY);
+				var maxY = Math.max(layoutBounds.y + layoutBounds.height, maxChildrenY);
+				layoutBounds.height = maxY - layoutBounds.y;
+			}
+		}
+	}
+
+	
+	for (var i = 0; i < this.swimlanes.length; i++)
+	{
+		var lane = this.swimlanes[i];
+		var geo = this.graph.getCellGeometry(lane);
+		
+		if (geo != null)
+		{
+			var children = this.graph.getChildCells(lane);
+			
+			var size = (this.graph.isSwimlane(lane)) ?
+					this.graph.getStartSize(lane) : new mxRectangle();
+
+			var newGeo = geo.clone();
+			
+			var leftGroupBorder = (i == 0) ? this.parentBorder : this.interRankCellSpacing/2;
+			newGeo.x += childBounds[i].x - size.width - leftGroupBorder;
+			newGeo.y = newGeo.y + layoutBounds.y - geo.y - this.parentBorder;
+			
+			newGeo.width = childBounds[i].width + size.width + this.interRankCellSpacing/2 + leftGroupBorder;
+			newGeo.height = layoutBounds.height + size.height + 2 * this.parentBorder;
+			
+			this.graph.model.setGeometry(lane, newGeo);
+			this.graph.moveCells(children, -childBounds[i].x + size.width + leftGroupBorder, 
+					geo.y - layoutBounds.y + this.parentBorder);
+		}
+	}
+};
+
+/**
+ * Function: findRoots
+ * 
+ * Returns all visible children in the given parent which do not have
+ * incoming edges. If the result is empty then the children with the
+ * maximum difference between incoming and outgoing edges are returned.
+ * This takes into account edges that are being promoted to the given
+ * root due to invisible children or collapsed cells.
+ * 
+ * Parameters:
+ * 
+ * parent - <mxCell> whose children should be checked.
+ * vertices - array of vertices to limit search to
+ */
+mxSwimlaneLayout.prototype.findRoots = function(parent, vertices)
+{
+	var roots = [];
+	
+	if (parent != null && vertices != null)
+	{
+		var model = this.graph.model;
+		var best = null;
+		var maxDiff = -100000;
+		
+		for (var i in vertices)
+		{
+			var cell = vertices[i];
+
+			if (cell != null && model.isVertex(cell) && this.graph.isCellVisible(cell) && model.isAncestor(parent, cell))
+			{
+				var conns = this.getEdges(cell);
+				var fanOut = 0;
+				var fanIn = 0;
+
+				for (var k = 0; k < conns.length; k++)
+				{
+					var src = this.getVisibleTerminal(conns[k], true);
+
+					if (src == cell)
+					{
+						// Only count connection within this swimlane
+						var other = this.getVisibleTerminal(conns[k], false);
+						
+						if (model.isAncestor(parent, other))
+						{
+							fanOut++;
+						}
+					}
+					else if (model.isAncestor(parent, src))
+					{
+						fanIn++;
+					}
+				}
+
+				if (fanIn == 0 && fanOut > 0)
+				{
+					roots.push(cell);
+				}
+
+				var diff = fanOut - fanIn;
+
+				if (diff > maxDiff)
+				{
+					maxDiff = diff;
+					best = cell;
+				}
+			}
+		}
+		
+		if (roots.length == 0 && best != null)
+		{
+			roots.push(best);
+		}
+	}
+	
+	return roots;
+};
+
+/**
+ * Function: getEdges
+ * 
+ * Returns the connected edges for the given cell.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose edges should be returned.
+ */
+mxSwimlaneLayout.prototype.getEdges = function(cell)
+{
+	var cachedEdges = this.edgesCache.get(cell);
+	
+	if (cachedEdges != null)
+	{
+		return cachedEdges;
+	}
+
+	var model = this.graph.model;
+	var edges = [];
+	var isCollapsed = this.graph.isCellCollapsed(cell);
+	var childCount = model.getChildCount(cell);
+
+	for (var i = 0; i < childCount; i++)
+	{
+		var child = model.getChildAt(cell, i);
+
+		if (this.isPort(child))
+		{
+			edges = edges.concat(model.getEdges(child, true, true));
+		}
+		else if (isCollapsed || !this.graph.isCellVisible(child))
+		{
+			edges = edges.concat(model.getEdges(child, true, true));
+		}
+	}
+
+	edges = edges.concat(model.getEdges(cell, true, true));
+	var result = [];
+	
+	for (var i = 0; i < edges.length; i++)
+	{
+		var source = this.getVisibleTerminal(edges[i], true);
+		var target = this.getVisibleTerminal(edges[i], false);
+		
+		if ((source == target) || ((source != target) && ((target == cell && (this.parent == null || this.graph.isValidAncestor(source, this.parent, this.traverseAncestors))) ||
+			(source == cell && (this.parent == null ||
+					this.graph.isValidAncestor(target, this.parent, this.traverseAncestors))))))
+		{
+			result.push(edges[i]);
+		}
+	}
+
+	this.edgesCache.put(cell, result);
+
+	return result;
+};
+
+/**
+ * Function: getVisibleTerminal
+ * 
+ * Helper function to return visible terminal for edge allowing for ports
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> whose edges should be returned.
+ * source - Boolean that specifies whether the source or target terminal is to be returned
+ */
+mxSwimlaneLayout.prototype.getVisibleTerminal = function(edge, source)
+{
+	var terminalCache = this.edgesTargetTermCache;
+	
+	if (source)
+	{
+		terminalCache = this.edgeSourceTermCache;
+	}
+
+	var term = terminalCache.get(edge);
+
+	if (term != null)
+	{
+		return term;
+	}
+
+	var state = this.graph.view.getState(edge);
+	
+	var terminal = (state != null) ? state.getVisibleTerminal(source) : this.graph.view.getVisibleTerminal(edge, source);
+	
+	if (terminal == null)
+	{
+		terminal = (state != null) ? state.getVisibleTerminal(source) : this.graph.view.getVisibleTerminal(edge, source);
+	}
+
+	if (terminal != null)
+	{
+		if (this.isPort(terminal))
+		{
+			terminal = this.graph.model.getParent(terminal);
+		}
+		
+		terminalCache.put(edge, terminal);
+	}
+
+	return terminal;
+};
+
+/**
+ * Function: run
+ * 
+ * The API method used to exercise the layout upon the graph description
+ * and produce a separate description of the vertex position and edge
+ * routing changes made. It runs each stage of the layout that has been
+ * created.
+ */
+mxSwimlaneLayout.prototype.run = function(parent)
+{
+	// Separate out unconnected hierarchies
+	var hierarchyVertices = [];
+	var allVertexSet = [];
+
+	if (this.swimlanes != null && this.swimlanes.length > 0 && parent != null)
+	{
+		var filledVertexSet = Object();
+		
+		for (var i = 0; i < this.swimlanes.length; i++)
+		{
+			this.filterDescendants(this.swimlanes[i], filledVertexSet);
+		}
+
+		this.roots = [];
+		var filledVertexSetEmpty = true;
+
+		// Poor man's isSetEmpty
+		for (var key in filledVertexSet)
+		{
+			if (filledVertexSet[key] != null)
+			{
+				filledVertexSetEmpty = false;
+				break;
+			}
+		}
+
+		// Only test for candidates in each swimlane in order
+		var laneCounter = 0;
+
+		while (!filledVertexSetEmpty && laneCounter < this.swimlanes.length)
+		{
+			var candidateRoots = this.findRoots(this.swimlanes[laneCounter], filledVertexSet);
+			
+			if (candidateRoots.length == 0)
+			{
+				laneCounter++;
+				continue;
+			}
+			
+			// If the candidate root is an unconnected group cell, remove it from
+			// the layout. We may need a custom set that holds such groups and forces
+			// them to be processed for resizing and/or moving.
+			for (var i = 0; i < candidateRoots.length; i++)
+			{
+				var vertexSet = Object();
+				hierarchyVertices.push(vertexSet);
+
+				this.traverse(candidateRoots[i], true, null, allVertexSet, vertexSet,
+						hierarchyVertices, filledVertexSet, laneCounter);
+			}
+
+			for (var i = 0; i < candidateRoots.length; i++)
+			{
+				this.roots.push(candidateRoots[i]);
+			}
+			
+			filledVertexSetEmpty = true;
+			
+			// Poor man's isSetEmpty
+			for (var key in filledVertexSet)
+			{
+				if (filledVertexSet[key] != null)
+				{
+					filledVertexSetEmpty = false;
+					break;
+				}
+			}
+		}
+	}
+	else
+	{
+		// Find vertex set as directed traversal from roots
+
+		for (var i = 0; i < this.roots.length; i++)
+		{
+			var vertexSet = Object();
+			hierarchyVertices.push(vertexSet);
+
+			this.traverse(this.roots[i], true, null, allVertexSet, vertexSet,
+					hierarchyVertices, null);
+		}
+	}
+
+	var tmp = [];
+	
+	for (var key in allVertexSet)
+	{
+		tmp.push(allVertexSet[key]);
+	}
+	
+	this.model = new mxSwimlaneModel(this, tmp, this.roots,
+		parent, this.tightenToSource);
+
+	this.cycleStage(parent);
+	this.layeringStage();
+	
+	this.crossingStage(parent);
+	initialX = this.placementStage(0, parent);
+};
+
+/**
+ * Function: filterDescendants
+ * 
+ * Creates an array of descendant cells
+ */
+mxSwimlaneLayout.prototype.filterDescendants = function(cell, result)
+{
+	var model = this.graph.model;
+
+	if (model.isVertex(cell) && cell != this.parent && model.getParent(cell) != this.parent && this.graph.isCellVisible(cell))
+	{
+		result[mxObjectIdentity.get(cell)] = cell;
+	}
+
+	if (this.traverseAncestors || cell == this.parent
+			&& this.graph.isCellVisible(cell))
+	{
+		var childCount = model.getChildCount(cell);
+
+		for (var i = 0; i < childCount; i++)
+		{
+			var child = model.getChildAt(cell, i);
+			
+			// Ignore ports in the layout vertex list, they are dealt with
+			// in the traversal mechanisms
+			if (!this.isPort(child))
+			{
+				this.filterDescendants(child, result);
+			}
+		}
+	}
+};
+
+/**
+ * Function: isPort
+ * 
+ * Returns true if the given cell is a "port", that is, when connecting to
+ * it, its parent is the connecting vertex in terms of graph traversal
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the port.
+ */
+mxSwimlaneLayout.prototype.isPort = function(cell)
+{
+	if (cell.geometry.relative)
+	{
+		return true;
+	}
+	
+	return false;
+};
+
+/**
+ * Function: getEdgesBetween
+ * 
+ * Returns the edges between the given source and target. This takes into
+ * account collapsed and invisible cells and ports.
+ * 
+ * Parameters:
+ * 
+ * source -
+ * target -
+ * directed -
+ */
+mxSwimlaneLayout.prototype.getEdgesBetween = function(source, target, directed)
+{
+	directed = (directed != null) ? directed : false;
+	var edges = this.getEdges(source);
+	var result = [];
+
+	// Checks if the edge is connected to the correct
+	// cell and returns the first match
+	for (var i = 0; i < edges.length; i++)
+	{
+		var src = this.getVisibleTerminal(edges[i], true);
+		var trg = this.getVisibleTerminal(edges[i], false);
+
+		if ((src == source && trg == target) || (!directed && src == target && trg == source))
+		{
+			result.push(edges[i]);
+		}
+	}
+
+	return result;
+};
+
+/**
+ * Traverses the (directed) graph invoking the given function for each
+ * visited vertex and edge. The function is invoked with the current vertex
+ * and the incoming edge as a parameter. This implementation makes sure
+ * each vertex is only visited once. The function may return false if the
+ * traversal should stop at the given vertex.
+ * 
+ * Parameters:
+ * 
+ * vertex - <mxCell> that represents the vertex where the traversal starts.
+ * directed - boolean indicating if edges should only be traversed
+ * from source to target. Default is true.
+ * edge - Optional <mxCell> that represents the incoming edge. This is
+ * null for the first step of the traversal.
+ * allVertices - Array of cell paths for the visited cells.
+ * swimlaneIndex - the laid out order index of the swimlane vertex is contained in
+ */
+mxSwimlaneLayout.prototype.traverse = function(vertex, directed, edge, allVertices, currentComp,
+											hierarchyVertices, filledVertexSet, swimlaneIndex)
+{
+	if (vertex != null && allVertices != null)
+	{
+		// Has this vertex been seen before in any traversal
+		// And if the filled vertex set is populated, only 
+		// process vertices in that it contains
+		var vertexID = mxObjectIdentity.get(vertex);
+		
+		if ((allVertices[vertexID] == null)
+				&& (filledVertexSet == null ? true : filledVertexSet[vertexID] != null))
+		{
+			if (currentComp[vertexID] == null)
+			{
+				currentComp[vertexID] = vertex;
+			}
+			if (allVertices[vertexID] == null)
+			{
+				allVertices[vertexID] = vertex;
+			}
+
+			if (filledVertexSet !== null)
+			{
+				delete filledVertexSet[vertexID];
+			}
+
+			var edges = this.getEdges(vertex);
+			var model = this.graph.model;
+
+			for (var i = 0; i < edges.length; i++)
+			{
+				var otherVertex = this.getVisibleTerminal(edges[i], true);
+				var isSource = otherVertex == vertex;
+				
+				if (isSource)
+				{
+					otherVertex = this.getVisibleTerminal(edges[i], false);
+				}
+
+				var otherIndex = 0;
+				// Get the swimlane index of the other terminal
+				for (otherIndex = 0; otherIndex < this.swimlanes.length; otherIndex++)
+				{
+					if (model.isAncestor(this.swimlanes[otherIndex], otherVertex))
+					{
+						break;
+					}
+				}
+				
+				if (otherIndex >= this.swimlanes.length)
+				{
+					continue;
+				}
+
+				// Traverse if the other vertex is within the same swimlane as
+				// as the current vertex, or if the swimlane index of the other
+				// vertex is greater than that of this vertex
+				if ((otherIndex > swimlaneIndex) ||
+						((!directed || isSource) && otherIndex == swimlaneIndex))
+				{
+					currentComp = this.traverse(otherVertex, directed, edges[i], allVertices,
+							currentComp, hierarchyVertices,
+							filledVertexSet, otherIndex);
+				}
+			}
+		}
+		else
+		{
+			if (currentComp[vertexID] == null)
+			{
+				// We've seen this vertex before, but not in the current component
+				// This component and the one it's in need to be merged
+				for (var i = 0; i < hierarchyVertices.length; i++)
+				{
+					var comp = hierarchyVertices[i];
+
+					if (comp[vertexID] != null)
+					{
+						for (var key in comp)
+						{
+							currentComp[key] = comp[key];
+						}
+						
+						// Remove the current component from the hierarchy set
+						hierarchyVertices.splice(i, 1);
+						return currentComp;
+					}
+				}
+			}
+		}
+	}
+	
+	return currentComp;
+};
+
+/**
+ * Function: cycleStage
+ * 
+ * Executes the cycle stage using mxMinimumCycleRemover.
+ */
+mxSwimlaneLayout.prototype.cycleStage = function(parent)
+{
+	var cycleStage = new mxSwimlaneOrdering(this);
+	cycleStage.execute(parent);
+};
+
+/**
+ * Function: layeringStage
+ * 
+ * Implements first stage of a Sugiyama layout.
+ */
+mxSwimlaneLayout.prototype.layeringStage = function()
+{
+	this.model.initialRank();
+	this.model.fixRanks();
+};
+
+/**
+ * Function: crossingStage
+ * 
+ * Executes the crossing stage using mxMedianHybridCrossingReduction.
+ */
+mxSwimlaneLayout.prototype.crossingStage = function(parent)
+{
+	var crossingStage = new mxMedianHybridCrossingReduction(this);
+	crossingStage.execute(parent);
+};
+
+/**
+ * Function: placementStage
+ * 
+ * Executes the placement stage using mxCoordinateAssignment.
+ */
+mxSwimlaneLayout.prototype.placementStage = function(initialX, parent)
+{
+	var placementStage = new mxCoordinateAssignment(this, this.intraCellSpacing,
+			this.interRankCellSpacing, this.orientation, initialX,
+			this.parallelEdgeSpacing);
+	placementStage.fineTuning = this.fineTuning;
+	placementStage.execute(parent);
+	
+	return placementStage.limitX + this.interHierarchySpacing;
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxGraphModel
+ * 
+ * Extends <mxEventSource> to implement a graph model. The graph model acts as
+ * a wrapper around the cells which are in charge of storing the actual graph
+ * datastructure. The model acts as a transactional wrapper with event
+ * notification for all changes, whereas the cells contain the atomic
+ * operations for updating the actual datastructure.
+ * 
+ * Layers:
+ * 
+ * The cell hierarchy in the model must have a top-level root cell which
+ * contains the layers (typically one default layer), which in turn contain the
+ * top-level cells of the layers. This means each cell is contained in a layer.
+ * If no layers are required, then all new cells should be added to the default
+ * layer.
+ * 
+ * Layers are useful for hiding and showing groups of cells, or for placing
+ * groups of cells on top of other cells in the display. To identify a layer,
+ * the <isLayer> function is used. It returns true if the parent of the given
+ * cell is the root of the model.
+ * 
+ * Events:
+ * 
+ * See events section for more details. There is a new set of events for
+ * tracking transactional changes as they happen. The events are called
+ * startEdit for the initial beginUpdate, executed for each executed change
+ * and endEdit for the terminal endUpdate. The executed event contains a
+ * property called change which represents the change after execution.
+ * 
+ * Encoding the model:
+ * 
+ * To encode a graph model, use the following code:
+ * 
+ * (code)
+ * var enc = new mxCodec();
+ * var node = enc.encode(graph.getModel());
+ * (end)
+ * 
+ * This will create an XML node that contains all the model information.
+ * 
+ * Encoding and decoding changes:
+ * 
+ * For the encoding of changes, a graph model listener is required that encodes
+ * each change from the given array of changes.
+ * 
+ * (code)
+ * model.addListener(mxEvent.CHANGE, function(sender, evt)
+ * {
+ *   var changes = evt.getProperty('edit').changes;
+ *   var nodes = [];
+ *   var codec = new mxCodec();
+ * 
+ *   for (var i = 0; i < changes.length; i++)
+ *   {
+ *     nodes.push(codec.encode(changes[i]));
+ *   }
+ *   // do something with the nodes
+ * });
+ * (end)
+ * 
+ * For the decoding and execution of changes, the codec needs a lookup function
+ * that allows it to resolve cell IDs as follows:
+ * 
+ * (code)
+ * var codec = new mxCodec();
+ * codec.lookup = function(id)
+ * {
+ *   return model.getCell(id);
+ * }
+ * (end)
+ * 
+ * For each encoded change (represented by a node), the following code can be
+ * used to carry out the decoding and create a change object.
+ * 
+ * (code)
+ * var changes = [];
+ * var change = codec.decode(node);
+ * change.model = model;
+ * change.execute();
+ * changes.push(change);
+ * (end)
+ * 
+ * The changes can then be dispatched using the model as follows.
+ * 
+ * (code)
+ * var edit = new mxUndoableEdit(model, false);
+ * edit.changes = changes;
+ * 
+ * edit.notify = function()
+ * {
+ *   edit.source.fireEvent(new mxEventObject(mxEvent.CHANGE,
+ *   	'edit', edit, 'changes', edit.changes));
+ *   edit.source.fireEvent(new mxEventObject(mxEvent.NOTIFY,
+ *   	'edit', edit, 'changes', edit.changes));
+ * }
+ * 
+ * model.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit));
+ * model.fireEvent(new mxEventObject(mxEvent.CHANGE,
+ * 		'edit', edit, 'changes', changes));
+ * (end)
+ *
+ * Event: mxEvent.CHANGE
+ *
+ * Fires when an undoable edit is dispatched. The <code>edit</code> property
+ * contains the <mxUndoableEdit>. The <code>changes</code> property contains
+ * the array of atomic changes inside the undoable edit. The changes property
+ * is <strong>deprecated</strong>, please use edit.changes instead.
+ *
+ * Example:
+ * 
+ * For finding newly inserted cells, the following code can be used:
+ * 
+ * (code)
+ * graph.model.addListener(mxEvent.CHANGE, function(sender, evt)
+ * {
+ *   var changes = evt.getProperty('edit').changes;
+ * 
+ *   for (var i = 0; i < changes.length; i++)
+ *   {
+ *     var change = changes[i];
+ *     
+ *     if (change instanceof mxChildChange &&
+ *       change.change.previous == null)
+ *     {
+ *       graph.startEditingAtCell(change.child);
+ *       break;
+ *     }
+ *   }
+ * });
+ * (end)
+ * 
+ * 
+ * Event: mxEvent.NOTIFY
+ *
+ * Same as <mxEvent.CHANGE>, this event can be used for classes that need to
+ * implement a sync mechanism between this model and, say, a remote model. In
+ * such a setup, only local changes should trigger a notify event and all
+ * changes should trigger a change event.
+ * 
+ * Event: mxEvent.EXECUTE
+ * 
+ * Fires between begin- and endUpdate and after an atomic change was executed
+ * in the model. The <code>change</code> property contains the atomic change
+ * that was executed.
+ * 
+ * Event: mxEvent.EXECUTED
+ * 
+ * Fires between START_EDIT and END_EDIT after an atomic change was executed.
+ * The <code>change</code> property contains the change that was executed.
+ *
+ * Event: mxEvent.BEGIN_UPDATE
+ *
+ * Fires after the <updateLevel> was incremented in <beginUpdate>. This event
+ * contains no properties.
+ * 
+ * Event: mxEvent.START_EDIT
+ *
+ * Fires after the <updateLevel> was changed from 0 to 1. This event
+ * contains no properties.
+ * 
+ * Event: mxEvent.END_UPDATE
+ * 
+ * Fires after the <updateLevel> was decreased in <endUpdate> but before any
+ * notification or change dispatching. The <code>edit</code> property contains
+ * the <currentEdit>.
+ * 
+ * Event: mxEvent.END_EDIT
+ *
+ * Fires after the <updateLevel> was changed from 1 to 0. This event
+ * contains no properties.
+ * 
+ * Event: mxEvent.BEFORE_UNDO
+ * 
+ * Fires before the change is dispatched after the update level has reached 0
+ * in <endUpdate>. The <code>edit</code> property contains the <curreneEdit>.
+ * 
+ * Event: mxEvent.UNDO
+ * 
+ * Fires after the change was dispatched in <endUpdate>. The <code>edit</code>
+ * property contains the <currentEdit>.
+ * 
+ * Constructor: mxGraphModel
+ * 
+ * Constructs a new graph model. If no root is specified then a new root
+ * <mxCell> with a default layer is created.
+ * 
+ * Parameters:
+ * 
+ * root - <mxCell> that represents the root cell.
+ */
+function mxGraphModel(root)
+{
+	this.currentEdit = this.createUndoableEdit();
+	
+	if (root != null)
+	{
+		this.setRoot(root);
+	}
+	else
+	{
+		this.clear();
+	}
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxGraphModel.prototype = new mxEventSource();
+mxGraphModel.prototype.constructor = mxGraphModel;
+
+/**
+ * Variable: root
+ * 
+ * Holds the root cell, which in turn contains the cells that represent the
+ * layers of the diagram as child cells. That is, the actual elements of the
+ * diagram are supposed to live in the third generation of cells and below.
+ */
+mxGraphModel.prototype.root = null;
+
+/**
+ * Variable: cells
+ * 
+ * Maps from Ids to cells.
+ */
+mxGraphModel.prototype.cells = null;
+
+/**
+ * Variable: maintainEdgeParent
+ * 
+ * Specifies if edges should automatically be moved into the nearest common
+ * ancestor of their terminals. Default is true.
+ */
+mxGraphModel.prototype.maintainEdgeParent = true;
+
+/**
+ * Variable: ignoreRelativeEdgeParent
+ * 
+ * Specifies if relative edge parents should be ignored for finding the nearest
+ * common ancestors of an edge's terminals. Default is true.
+ */
+mxGraphModel.prototype.ignoreRelativeEdgeParent = true;
+
+/**
+ * Variable: createIds
+ * 
+ * Specifies if the model should automatically create Ids for new cells.
+ * Default is true.
+ */
+mxGraphModel.prototype.createIds = true;
+
+/**
+ * Variable: prefix
+ * 
+ * Defines the prefix of new Ids. Default is an empty string.
+ */
+mxGraphModel.prototype.prefix = '';
+
+/**
+ * Variable: postfix
+ * 
+ * Defines the postfix of new Ids. Default is an empty string.
+ */
+mxGraphModel.prototype.postfix = '';
+
+/**
+ * Variable: nextId
+ * 
+ * Specifies the next Id to be created. Initial value is 0.
+ */
+mxGraphModel.prototype.nextId = 0;
+
+/**
+ * Variable: currentEdit
+ * 
+ * Holds the changes for the current transaction. If the transaction is
+ * closed then a new object is created for this variable using
+ * <createUndoableEdit>.
+ */
+mxGraphModel.prototype.currentEdit = null;
+
+/**
+ * Variable: updateLevel
+ * 
+ * Counter for the depth of nested transactions. Each call to <beginUpdate>
+ * will increment this number and each call to <endUpdate> will decrement
+ * it. When the counter reaches 0, the transaction is closed and the
+ * respective events are fired. Initial value is 0.
+ */
+mxGraphModel.prototype.updateLevel = 0;
+
+/**
+ * Variable: endingUpdate
+ * 
+ * True if the program flow is currently inside endUpdate.
+ */
+mxGraphModel.prototype.endingUpdate = false;
+
+/**
+ * Function: clear
+ *
+ * Sets a new root using <createRoot>.
+ */
+mxGraphModel.prototype.clear = function()
+{
+	this.setRoot(this.createRoot());
+};
+
+/**
+ * Function: isCreateIds
+ *
+ * Returns <createIds>.
+ */
+mxGraphModel.prototype.isCreateIds = function()
+{
+	return this.createIds;
+};
+
+/**
+ * Function: setCreateIds
+ *
+ * Sets <createIds>.
+ */
+mxGraphModel.prototype.setCreateIds = function(value)
+{
+	this.createIds = value;
+};
+
+/**
+ * Function: createRoot
+ *
+ * Creates a new root cell with a default layer (child 0).
+ */
+mxGraphModel.prototype.createRoot = function()
+{
+	var cell = new mxCell();
+	cell.insert(new mxCell());
+	
+	return cell;
+};
+
+/**
+ * Function: getCell
+ *
+ * Returns the <mxCell> for the specified Id or null if no cell can be
+ * found for the given Id.
+ *
+ * Parameters:
+ * 
+ * id - A string representing the Id of the cell.
+ */
+mxGraphModel.prototype.getCell = function(id)
+{
+	return (this.cells != null) ? this.cells[id] : null;
+};
+
+/**
+ * Function: filterCells
+ * 
+ * Returns the cells from the given array where the given filter function
+ * returns true.
+ */
+mxGraphModel.prototype.filterCells = function(cells, filter)
+{
+	var result = null;
+	
+	if (cells != null)
+	{
+		result = [];
+		
+		for (var i = 0; i < cells.length; i++)
+		{
+			if (filter(cells[i]))
+			{
+				result.push(cells[i]);
+			}
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: getDescendants
+ * 
+ * Returns all descendants of the given cell and the cell itself in an array.
+ * 
+ * Parameters:
+ * 
+ * parent - <mxCell> whose descendants should be returned.
+ */
+mxGraphModel.prototype.getDescendants = function(parent)
+{
+	return this.filterDescendants(null, parent);
+};
+
+/**
+ * Function: filterDescendants
+ * 
+ * Visits all cells recursively and applies the specified filter function
+ * to each cell. If the function returns true then the cell is added
+ * to the resulting array. The parent and result paramters are optional.
+ * If parent is not specified then the recursion starts at <root>.
+ * 
+ * Example:
+ * The following example extracts all vertices from a given model:
+ * (code)
+ * var filter = function(cell)
+ * {
+ * 	return model.isVertex(cell);
+ * }
+ * var vertices = model.filterDescendants(filter);
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * filter - JavaScript function that takes an <mxCell> as an argument
+ * and returns a boolean.
+ * parent - Optional <mxCell> that is used as the root of the recursion.
+ */
+mxGraphModel.prototype.filterDescendants = function(filter, parent)
+{
+	// Creates a new array for storing the result
+	var result = [];
+
+	// Recursion starts at the root of the model
+	parent = parent || this.getRoot();
+	
+	// Checks if the filter returns true for the cell
+	// and adds it to the result array
+	if (filter == null || filter(parent))
+	{
+		result.push(parent);
+	}
+	
+	// Visits the children of the cell
+	var childCount = this.getChildCount(parent);
+	
+	for (var i = 0; i < childCount; i++)
+	{
+		var child = this.getChildAt(parent, i);
+		result = result.concat(this.filterDescendants(filter, child));
+	}
+
+	return result;
+};
+
+/**
+ * Function: getRoot
+ * 
+ * Returns the root of the model or the topmost parent of the given cell.
+ *
+ * Parameters:
+ * 
+ * cell - Optional <mxCell> that specifies the child.
+ */
+mxGraphModel.prototype.getRoot = function(cell)
+{
+	var root = cell || this.root;
+	
+	if (cell != null)
+	{
+		while (cell != null)
+		{
+			root = cell;
+			cell = this.getParent(cell);
+		}
+	}
+	
+	return root;
+};
+
+/**
+ * Function: setRoot
+ * 
+ * Sets the <root> of the model using <mxRootChange> and adds the change to
+ * the current transaction. This resets all datastructures in the model and
+ * is the preferred way of clearing an existing model. Returns the new
+ * root.
+ * 
+ * Example:
+ * 
+ * (code)
+ * var root = new mxCell();
+ * root.insert(new mxCell());
+ * model.setRoot(root);
+ * (end)
+ *
+ * Parameters:
+ * 
+ * root - <mxCell> that specifies the new root.
+ */
+mxGraphModel.prototype.setRoot = function(root)
+{
+	this.execute(new mxRootChange(this, root));
+	
+	return root;
+};
+
+/**
+ * Function: rootChanged
+ * 
+ * Inner callback to change the root of the model and update the internal
+ * datastructures, such as <cells> and <nextId>. Returns the previous root.
+ *
+ * Parameters:
+ * 
+ * root - <mxCell> that specifies the new root.
+ */
+mxGraphModel.prototype.rootChanged = function(root)
+{
+	var oldRoot = this.root;
+	this.root = root;
+	
+	// Resets counters and datastructures
+	this.nextId = 0;
+	this.cells = null;
+	this.cellAdded(root);
+	
+	return oldRoot;
+};
+
+/**
+ * Function: isRoot
+ * 
+ * Returns true if the given cell is the root of the model and a non-null
+ * value.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the possible root.
+ */
+mxGraphModel.prototype.isRoot = function(cell)
+{
+	return cell != null && this.root == cell;
+};
+
+/**
+ * Function: isLayer
+ * 
+ * Returns true if <isRoot> returns true for the parent of the given cell.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the possible layer.
+ */
+mxGraphModel.prototype.isLayer = function(cell)
+{
+	return this.isRoot(this.getParent(cell));
+};
+
+/**
+ * Function: isAncestor
+ * 
+ * Returns true if the given parent is an ancestor of the given child.
+ *
+ * Parameters:
+ * 
+ * parent - <mxCell> that specifies the parent.
+ * child - <mxCell> that specifies the child.
+ */
+mxGraphModel.prototype.isAncestor = function(parent, child)
+{
+	while (child != null && child != parent)
+	{
+		child = this.getParent(child);
+	}
+	
+	return child == parent;
+};
+
+/**
+ * Function: contains
+ * 
+ * Returns true if the model contains the given <mxCell>.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> that specifies the cell.
+ */
+mxGraphModel.prototype.contains = function(cell)
+{
+	return this.isAncestor(this.root, cell);
+};
+
+/**
+ * Function: getParent
+ * 
+ * Returns the parent of the given cell.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> whose parent should be returned.
+ */
+mxGraphModel.prototype.getParent = function(cell)
+{
+	return (cell != null) ? cell.getParent() : null;
+};
+
+/**
+ * Function: add
+ * 
+ * Adds the specified child to the parent at the given index using
+ * <mxChildChange> and adds the change to the current transaction. If no
+ * index is specified then the child is appended to the parent's array of
+ * children. Returns the inserted child.
+ * 
+ * Parameters:
+ * 
+ * parent - <mxCell> that specifies the parent to contain the child.
+ * child - <mxCell> that specifies the child to be inserted.
+ * index - Optional integer that specifies the index of the child.
+ */
+mxGraphModel.prototype.add = function(parent, child, index)
+{
+	if (child != parent && parent != null && child != null)
+	{	
+		// Appends the child if no index was specified
+		if (index == null)
+		{
+			index = this.getChildCount(parent);
+		}
+		
+		var parentChanged = parent != this.getParent(child);
+		this.execute(new mxChildChange(this, parent, child, index));
+
+		// Maintains the edges parents by moving the edges
+		// into the nearest common ancestor of its
+		// terminals
+		if (this.maintainEdgeParent && parentChanged)
+		{
+			this.updateEdgeParents(child);
+		}
+	}
+	
+	return child;
+};
+
+/**
+ * Function: cellAdded
+ * 
+ * Inner callback to update <cells> when a cell has been added. This
+ * implementation resolves collisions by creating new Ids. To change the
+ * ID of a cell after it was inserted into the model, use the following
+ * code:
+ * 
+ * (code
+ * delete model.cells[cell.getId()];
+ * cell.setId(newId);
+ * model.cells[cell.getId()] = cell;
+ * (end)
+ *
+ * If the change of the ID should be part of the command history, then the
+ * cell should be removed from the model and a clone with the new ID should
+ * be reinserted into the model instead.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> that specifies the cell that has been added.
+ */
+mxGraphModel.prototype.cellAdded = function(cell)
+{
+	if (cell != null)
+	{
+		// Creates an Id for the cell if not Id exists
+		if (cell.getId() == null && this.createIds)
+		{
+			cell.setId(this.createId(cell));
+		}
+		
+		if (cell.getId() != null)
+		{
+			var collision = this.getCell(cell.getId());
+			
+			if (collision != cell)
+			{	
+				// Creates new Id for the cell
+				// as long as there is a collision
+				while (collision != null)
+				{
+					cell.setId(this.createId(cell));
+					collision = this.getCell(cell.getId());
+				}
+				
+				// Lazily creates the cells dictionary
+				if (this.cells == null)
+				{
+					this.cells = new Object();
+				}
+				
+				this.cells[cell.getId()] = cell;
+			}
+		}
+		
+		// Makes sure IDs of deleted cells are not reused
+		if (mxUtils.isNumeric(cell.getId()))
+		{
+			this.nextId = Math.max(this.nextId, cell.getId());
+		}
+		
+		// Recursively processes child cells
+		var childCount = this.getChildCount(cell);
+		
+		for (var i=0; i<childCount; i++)
+		{
+			this.cellAdded(this.getChildAt(cell, i));
+		}
+	}
+};
+
+/**
+ * Function: createId
+ * 
+ * Hook method to create an Id for the specified cell. This implementation
+ * concatenates <prefix>, id and <postfix> to create the Id and increments
+ * <nextId>. The cell is ignored by this implementation, but can be used in
+ * overridden methods to prefix the Ids with eg. the cell type.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to create the Id for.
+ */
+mxGraphModel.prototype.createId = function(cell)
+{
+	var id = this.nextId;
+	this.nextId++;
+	
+	return this.prefix + id + this.postfix;
+};
+
+/**
+ * Function: updateEdgeParents
+ * 
+ * Updates the parent for all edges that are connected to cell or one of
+ * its descendants using <updateEdgeParent>.
+ */
+mxGraphModel.prototype.updateEdgeParents = function(cell, root)
+{
+	// Gets the topmost node of the hierarchy
+	root = root || this.getRoot(cell);
+	
+	// Updates edges on children first
+	var childCount = this.getChildCount(cell);
+	
+	for (var i = 0; i < childCount; i++)
+	{
+		var child = this.getChildAt(cell, i);
+		this.updateEdgeParents(child, root);
+	}
+	
+	// Updates the parents of all connected edges
+	var edgeCount = this.getEdgeCount(cell);
+	var edges = [];
+
+	for (var i = 0; i < edgeCount; i++)
+	{
+		edges.push(this.getEdgeAt(cell, i));
+	}
+	
+	for (var i = 0; i < edges.length; i++)
+	{
+		var edge = edges[i];
+		
+		// Updates edge parent if edge and child have
+		// a common root node (does not need to be the
+		// model root node)
+		if (this.isAncestor(root, edge))
+		{
+			this.updateEdgeParent(edge, root);
+		}
+	}
+};
+
+/**
+ * Function: updateEdgeParent
+ *
+ * Inner callback to update the parent of the specified <mxCell> to the
+ * nearest-common-ancestor of its two terminals.
+ *
+ * Parameters:
+ * 
+ * edge - <mxCell> that specifies the edge.
+ * root - <mxCell> that represents the current root of the model.
+ */
+mxGraphModel.prototype.updateEdgeParent = function(edge, root)
+{
+	var source = this.getTerminal(edge, true);
+	var target = this.getTerminal(edge, false);
+	var cell = null;
+	
+	// Uses the first non-relative descendants of the source terminal
+	while (source != null && !this.isEdge(source) &&
+		source.geometry != null && source.geometry.relative)
+	{
+		source = this.getParent(source);
+	}
+	
+	// Uses the first non-relative descendants of the target terminal
+	while (target != null && this.ignoreRelativeEdgeParent &&
+		!this.isEdge(target) && target.geometry != null && 
+		target.geometry.relative)
+	{
+		target = this.getParent(target);
+	}
+	
+	if (this.isAncestor(root, source) && this.isAncestor(root, target))
+	{
+		if (source == target)
+		{
+			cell = this.getParent(source);
+		}
+		else
+		{
+			cell = this.getNearestCommonAncestor(source, target);
+		}
+
+		if (cell != null && (this.getParent(cell) != this.root ||
+			this.isAncestor(cell, edge)) && this.getParent(edge) != cell)
+		{
+			var geo = this.getGeometry(edge);
+			
+			if (geo != null)
+			{
+				var origin1 = this.getOrigin(this.getParent(edge));
+				var origin2 = this.getOrigin(cell);
+				
+				var dx = origin2.x - origin1.x;
+				var dy = origin2.y - origin1.y;
+				
+				geo = geo.clone();
+				geo.translate(-dx, -dy);
+				this.setGeometry(edge, geo);
+			}
+
+			this.add(cell, edge, this.getChildCount(cell));
+		}
+	}
+};
+
+/**
+ * Function: getOrigin
+ * 
+ * Returns the absolute, accumulated origin for the children inside the
+ * given parent as an <mxPoint>.
+ */
+mxGraphModel.prototype.getOrigin = function(cell)
+{
+	var result = null;
+	
+	if (cell != null)
+	{
+		result = this.getOrigin(this.getParent(cell));
+		
+		if (!this.isEdge(cell))
+		{
+			var geo = this.getGeometry(cell);
+			
+			if (geo != null)
+			{
+				result.x += geo.x;
+				result.y += geo.y;
+			}
+		}
+	}
+	else
+	{
+		result = new mxPoint();
+	}
+	
+	return result;
+};
+
+/**
+ * Function: getNearestCommonAncestor
+ * 
+ * Returns the nearest common ancestor for the specified cells.
+ *
+ * Parameters:
+ * 
+ * cell1 - <mxCell> that specifies the first cell in the tree.
+ * cell2 - <mxCell> that specifies the second cell in the tree.
+ */
+mxGraphModel.prototype.getNearestCommonAncestor = function(cell1, cell2)
+{
+	if (cell1 != null && cell2 != null)
+	{		
+		// Creates the cell path for the second cell
+		var path = mxCellPath.create(cell2);
+
+		if (path != null && path.length > 0)
+		{
+			// Bubbles through the ancestors of the first
+			// cell to find the nearest common ancestor.
+			var cell = cell1;
+			var current = mxCellPath.create(cell);
+			
+			// Inverts arguments
+			if (path.length < current.length)
+			{
+				cell = cell2;
+				var tmp = current;
+				current = path;
+				path = tmp;
+			}
+			
+			while (cell != null)
+			{
+				var parent = this.getParent(cell);
+				
+				// Checks if the cell path is equal to the beginning of the given cell path
+				if (path.indexOf(current + mxCellPath.PATH_SEPARATOR) == 0 && parent != null)
+				{
+					return cell;
+				}
+				
+				current = mxCellPath.getParentPath(current);
+				cell = parent;
+			}
+		}
+	}
+	
+	return null;
+};
+
+/**
+ * Function: remove
+ * 
+ * Removes the specified cell from the model using <mxChildChange> and adds
+ * the change to the current transaction. This operation will remove the
+ * cell and all of its children from the model. Returns the removed cell.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> that should be removed.
+ */
+mxGraphModel.prototype.remove = function(cell)
+{
+	if (cell == this.root)
+	{
+		this.setRoot(null);
+	}
+	else if (this.getParent(cell) != null)
+	{
+		this.execute(new mxChildChange(this, null, cell));
+	}
+	
+	return cell;
+};
+
+/**
+ * Function: cellRemoved
+ * 
+ * Inner callback to update <cells> when a cell has been removed.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> that specifies the cell that has been removed.
+ */
+mxGraphModel.prototype.cellRemoved = function(cell)
+{
+	if (cell != null && this.cells != null)
+	{
+		// Recursively processes child cells
+		var childCount = this.getChildCount(cell);
+		
+		for (var i = childCount - 1; i >= 0; i--)
+		{
+			this.cellRemoved(this.getChildAt(cell, i));
+		}
+		
+		// Removes the dictionary entry for the cell
+		if (this.cells != null && cell.getId() != null)
+		{
+			delete this.cells[cell.getId()];
+		}
+	}
+};
+
+/**
+ * Function: parentForCellChanged
+ * 
+ * Inner callback to update the parent of a cell using <mxCell.insert>
+ * on the parent and return the previous parent.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> to update the parent for.
+ * parent - <mxCell> that specifies the new parent of the cell.
+ * index - Optional integer that defines the index of the child
+ * in the parent's child array.
+ */
+mxGraphModel.prototype.parentForCellChanged = function(cell, parent, index)
+{
+	var previous = this.getParent(cell);
+	
+	if (parent != null)
+	{
+		if (parent != previous || previous.getIndex(cell) != index)
+		{
+			parent.insert(cell, index);
+		}
+	}
+	else if (previous != null)
+	{
+		var oldIndex = previous.getIndex(cell);
+		previous.remove(oldIndex);
+	}
+	
+	// Checks if the previous parent was already in the
+	// model and avoids calling cellAdded if it was.
+	if (!this.contains(previous) && parent != null)
+	{
+		this.cellAdded(cell);
+	}
+	else if (parent == null)
+	{
+		this.cellRemoved(cell);
+	}
+	
+	return previous;
+};
+
+/**
+ * Function: getChildCount
+ *
+ * Returns the number of children in the given cell.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> whose number of children should be returned.
+ */
+mxGraphModel.prototype.getChildCount = function(cell)
+{
+	return (cell != null) ? cell.getChildCount() : 0;
+};
+
+/**
+ * Function: getChildAt
+ *
+ * Returns the child of the given <mxCell> at the given index.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the parent.
+ * index - Integer that specifies the index of the child to be returned.
+ */
+mxGraphModel.prototype.getChildAt = function(cell, index)
+{
+	return (cell != null) ? cell.getChildAt(index) : null;
+};
+
+/**
+ * Function: getChildren
+ * 
+ * Returns all children of the given <mxCell> as an array of <mxCells>. The
+ * return value should be only be read.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> the represents the parent.
+ */
+mxGraphModel.prototype.getChildren = function(cell)
+{
+	return (cell != null) ? cell.children : null;
+};
+	
+/**
+ * Function: getChildVertices
+ * 
+ * Returns the child vertices of the given parent.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> whose child vertices should be returned.
+ */
+mxGraphModel.prototype.getChildVertices = function(parent)
+{
+	return this.getChildCells(parent, true, false);
+};
+		
+/**
+ * Function: getChildEdges
+ * 
+ * Returns the child edges of the given parent.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> whose child edges should be returned.
+ */
+mxGraphModel.prototype.getChildEdges = function(parent)
+{
+	return this.getChildCells(parent, false, true);
+};
+
+/**
+ * Function: getChildCells
+ * 
+ * Returns the children of the given cell that are vertices and/or edges
+ * depending on the arguments.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> the represents the parent.
+ * vertices - Boolean indicating if child vertices should be returned.
+ * Default is false.
+ * edges - Boolean indicating if child edges should be returned.
+ * Default is false.
+ */
+mxGraphModel.prototype.getChildCells = function(parent, vertices, edges)
+{
+	vertices = (vertices != null) ? vertices : false;
+	edges = (edges != null) ? edges : false;
+	
+	var childCount = this.getChildCount(parent);
+	var result = [];
+
+	for (var i = 0; i < childCount; i++)
+	{
+		var child = this.getChildAt(parent, i);
+
+		if ((!edges && !vertices) || (edges && this.isEdge(child)) ||
+			(vertices && this.isVertex(child)))
+		{
+			result.push(child);
+		}
+	}
+
+	return result;
+};
+		
+/**
+ * Function: getTerminal
+ * 
+ * Returns the source or target <mxCell> of the given edge depending on the
+ * value of the boolean parameter.
+ *
+ * Parameters:
+ * 
+ * edge - <mxCell> that specifies the edge.
+ * isSource - Boolean indicating which end of the edge should be returned.
+ */
+mxGraphModel.prototype.getTerminal = function(edge, isSource)
+{
+	return (edge != null) ? edge.getTerminal(isSource) : null;
+};
+
+/**
+ * Function: setTerminal
+ * 
+ * Sets the source or target terminal of the given <mxCell> using
+ * <mxTerminalChange> and adds the change to the current transaction.
+ * This implementation updates the parent of the edge using <updateEdgeParent>
+ * if required.
+ *
+ * Parameters:
+ * 
+ * edge - <mxCell> that specifies the edge.
+ * terminal - <mxCell> that specifies the new terminal.
+ * isSource - Boolean indicating if the terminal is the new source or
+ * target terminal of the edge.
+ */
+mxGraphModel.prototype.setTerminal = function(edge, terminal, isSource)
+{
+	var terminalChanged = terminal != this.getTerminal(edge, isSource);
+	this.execute(new mxTerminalChange(this, edge, terminal, isSource));
+	
+	if (this.maintainEdgeParent && terminalChanged)
+	{
+		this.updateEdgeParent(edge, this.getRoot());
+	}
+	
+	return terminal;
+};
+	
+/**
+ * Function: setTerminals
+ * 
+ * Sets the source and target <mxCell> of the given <mxCell> in a single
+ * transaction using <setTerminal> for each end of the edge.
+ *
+ * Parameters:
+ * 
+ * edge - <mxCell> that specifies the edge.
+ * source - <mxCell> that specifies the new source terminal.
+ * target - <mxCell> that specifies the new target terminal.
+ */
+mxGraphModel.prototype.setTerminals = function(edge, source, target)
+{
+	this.beginUpdate();
+	try
+	{
+		this.setTerminal(edge, source, true);
+		this.setTerminal(edge, target, false);
+	}
+	finally
+	{
+		this.endUpdate();
+	}
+};
+
+/**
+ * Function: terminalForCellChanged
+ * 
+ * Inner helper function to update the terminal of the edge using
+ * <mxCell.insertEdge> and return the previous terminal.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> that specifies the edge to be updated.
+ * terminal - <mxCell> that specifies the new terminal.
+ * isSource - Boolean indicating if the terminal is the new source or
+ * target terminal of the edge.
+ */
+mxGraphModel.prototype.terminalForCellChanged = function(edge, terminal, isSource)
+{
+	var previous = this.getTerminal(edge, isSource);
+	
+	if (terminal != null)
+	{
+		terminal.insertEdge(edge, isSource);
+	}
+	else if (previous != null)
+	{
+		previous.removeEdge(edge, isSource);
+	}
+	
+	return previous;
+};
+
+/**
+ * Function: getEdgeCount
+ * 
+ * Returns the number of distinct edges connected to the given cell.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the vertex.
+ */
+mxGraphModel.prototype.getEdgeCount = function(cell)
+{
+	return (cell != null) ? cell.getEdgeCount() : 0;
+};
+
+/**
+ * Function: getEdgeAt
+ * 
+ * Returns the edge of cell at the given index.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> that specifies the vertex.
+ * index - Integer that specifies the index of the edge
+ * to return.
+ */
+mxGraphModel.prototype.getEdgeAt = function(cell, index)
+{
+	return (cell != null) ? cell.getEdgeAt(index) : null;
+};
+	
+/**
+ * Function: getDirectedEdgeCount
+ * 
+ * Returns the number of incoming or outgoing edges, ignoring the given
+ * edge.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose edge count should be returned.
+ * outgoing - Boolean that specifies if the number of outgoing or
+ * incoming edges should be returned.
+ * ignoredEdge - <mxCell> that represents an edge to be ignored.
+ */
+mxGraphModel.prototype.getDirectedEdgeCount = function(cell, outgoing, ignoredEdge)
+{
+	var count = 0;
+	var edgeCount = this.getEdgeCount(cell);
+
+	for (var i = 0; i < edgeCount; i++)
+	{
+		var edge = this.getEdgeAt(cell, i);
+
+		if (edge != ignoredEdge && this.getTerminal(edge, outgoing) == cell)
+		{
+			count++;
+		}
+	}
+
+	return count;
+};
+
+/**
+ * Function: getConnections
+ * 
+ * Returns all edges of the given cell without loops.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose edges should be returned.
+ * 
+ */
+mxGraphModel.prototype.getConnections = function(cell)
+{
+	return this.getEdges(cell, true, true, false);
+};
+
+/**
+ * Function: getIncomingEdges
+ * 
+ * Returns the incoming edges of the given cell without loops.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose incoming edges should be returned.
+ * 
+ */
+mxGraphModel.prototype.getIncomingEdges = function(cell)
+{
+	return this.getEdges(cell, true, false, false);
+};
+
+/**
+ * Function: getOutgoingEdges
+ * 
+ * Returns the outgoing edges of the given cell without loops.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose outgoing edges should be returned.
+ * 
+ */
+mxGraphModel.prototype.getOutgoingEdges = function(cell)
+{
+	return this.getEdges(cell, false, true, false);
+};
+
+/**
+ * Function: getEdges
+ * 
+ * Returns all distinct edges connected to this cell as a new array of
+ * <mxCells>. If at least one of incoming or outgoing is true, then loops
+ * are ignored, otherwise if both are false, then all edges connected to
+ * the given cell are returned including loops.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that specifies the cell.
+ * incoming - Optional boolean that specifies if incoming edges should be
+ * returned. Default is true.
+ * outgoing - Optional boolean that specifies if outgoing edges should be
+ * returned. Default is true.
+ * includeLoops - Optional boolean that specifies if loops should be returned.
+ * Default is true. 
+ */
+mxGraphModel.prototype.getEdges = function(cell, incoming, outgoing, includeLoops)
+{
+	incoming = (incoming != null) ? incoming : true;
+	outgoing = (outgoing != null) ? outgoing : true;
+	includeLoops = (includeLoops != null) ? includeLoops : true;
+	
+	var edgeCount = this.getEdgeCount(cell);
+	var result = [];
+
+	for (var i = 0; i < edgeCount; i++)
+	{
+		var edge = this.getEdgeAt(cell, i);
+		var source = this.getTerminal(edge, true);
+		var target = this.getTerminal(edge, false);
+
+		if ((includeLoops && source == target) || ((source != target) && ((incoming && target == cell) ||
+			(outgoing && source == cell))))
+		{
+			result.push(edge);
+		}
+	}
+
+	return result;
+};
+
+/**
+ * Function: getEdgesBetween
+ * 
+ * Returns all edges between the given source and target pair. If directed
+ * is true, then only edges from the source to the target are returned,
+ * otherwise, all edges between the two cells are returned.
+ * 
+ * Parameters:
+ * 
+ * source - <mxCell> that defines the source terminal of the edge to be
+ * returned.
+ * target - <mxCell> that defines the target terminal of the edge to be
+ * returned.
+ * directed - Optional boolean that specifies if the direction of the
+ * edge should be taken into account. Default is false.
+ */
+mxGraphModel.prototype.getEdgesBetween = function(source, target, directed)
+{
+	directed = (directed != null) ? directed : false;
+	
+	var tmp1 = this.getEdgeCount(source);
+	var tmp2 = this.getEdgeCount(target);
+	
+	// Assumes the source has less connected edges
+	var terminal = source;
+	var edgeCount = tmp1;
+	
+	// Uses the smaller array of connected edges
+	// for searching the edge
+	if (tmp2 < tmp1)
+	{
+		edgeCount = tmp2;
+		terminal = target;
+	}
+	
+	var result = [];
+	
+	// Checks if the edge is connected to the correct
+	// cell and returns the first match
+	for (var i = 0; i < edgeCount; i++)
+	{
+		var edge = this.getEdgeAt(terminal, i);
+		var src = this.getTerminal(edge, true);
+		var trg = this.getTerminal(edge, false);
+		var directedMatch = (src == source) && (trg == target);
+		var oppositeMatch = (trg == source) && (src == target);
+
+		if (directedMatch || (!directed && oppositeMatch))
+		{
+			result.push(edge);
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: getOpposites
+ * 
+ * Returns all opposite vertices wrt terminal for the given edges, only
+ * returning sources and/or targets as specified. The result is returned
+ * as an array of <mxCells>.
+ * 
+ * Parameters:
+ * 
+ * edges - Array of <mxCells> that contain the edges to be examined.
+ * terminal - <mxCell> that specifies the known end of the edges.
+ * sources - Boolean that specifies if source terminals should be contained
+ * in the result. Default is true.
+ * targets - Boolean that specifies if target terminals should be contained
+ * in the result. Default is true.
+ */
+mxGraphModel.prototype.getOpposites = function(edges, terminal, sources, targets)
+{
+	sources = (sources != null) ? sources : true;
+	targets = (targets != null) ? targets : true;
+	
+	var terminals = [];
+	
+	if (edges != null)
+	{
+		for (var i = 0; i < edges.length; i++)
+		{
+			var source = this.getTerminal(edges[i], true);
+			var target = this.getTerminal(edges[i], false);
+			
+			// Checks if the terminal is the source of
+			// the edge and if the target should be
+			// stored in the result
+			if (source == terminal && target != null && target != terminal && targets)
+			{
+				terminals.push(target);
+			}
+			
+			// Checks if the terminal is the taget of
+			// the edge and if the source should be
+			// stored in the result
+			else if (target == terminal && source != null && source != terminal && sources)
+			{
+				terminals.push(source);
+			}
+		}
+	}
+	
+	return terminals;
+};
+
+/**
+ * Function: getTopmostCells
+ * 
+ * Returns the topmost cells of the hierarchy in an array that contains no
+ * descendants for each <mxCell> that it contains. Duplicates should be
+ * removed in the cells array to improve performance.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> whose topmost ancestors should be returned.
+ */
+mxGraphModel.prototype.getTopmostCells = function(cells)
+{
+	var dict = new mxDictionary();
+	var tmp = [];
+	
+	for (var i = 0; i < cells.length; i++)
+	{
+		dict.put(cells[i], true);
+	}
+	
+	for (var i = 0; i < cells.length; i++)
+	{
+		var cell = cells[i];
+		var topmost = true;
+		var parent = this.getParent(cell);
+		
+		while (parent != null)
+		{
+			if (dict.get(parent))
+			{
+				topmost = false;
+				break;
+			}
+			
+			parent = this.getParent(parent);
+		}
+		
+		if (topmost)
+		{
+			tmp.push(cell);
+		}
+	}
+	
+	return tmp;
+};
+
+/**
+ * Function: isVertex
+ * 
+ * Returns true if the given cell is a vertex.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the possible vertex.
+ */
+mxGraphModel.prototype.isVertex = function(cell)
+{
+	return (cell != null) ? cell.isVertex() : false;
+};
+
+/**
+ * Function: isEdge
+ * 
+ * Returns true if the given cell is an edge.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the possible edge.
+ */
+mxGraphModel.prototype.isEdge = function(cell)
+{
+	return (cell != null) ? cell.isEdge() : false;
+};
+
+/**
+ * Function: isConnectable
+ * 
+ * Returns true if the given <mxCell> is connectable. If <edgesConnectable>
+ * is false, then this function returns false for all edges else it returns
+ * the return value of <mxCell.isConnectable>.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> whose connectable state should be returned.
+ */
+mxGraphModel.prototype.isConnectable = function(cell)
+{
+	return (cell != null) ? cell.isConnectable() : false;
+};
+
+/**
+ * Function: getValue
+ * 
+ * Returns the user object of the given <mxCell> using <mxCell.getValue>.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> whose user object should be returned.
+ */
+mxGraphModel.prototype.getValue = function(cell)
+{
+	return (cell != null) ? cell.getValue() : null;
+};
+
+/**
+ * Function: setValue
+ * 
+ * Sets the user object of then given <mxCell> using <mxValueChange>
+ * and adds the change to the current transaction.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> whose user object should be changed.
+ * value - Object that defines the new user object.
+ */
+mxGraphModel.prototype.setValue = function(cell, value)
+{
+	this.execute(new mxValueChange(this, cell, value));
+	
+	return value;
+};
+
+/**
+ * Function: valueForCellChanged
+ * 
+ * Inner callback to update the user object of the given <mxCell>
+ * using <mxCell.valueChanged> and return the previous value,
+ * that is, the return value of <mxCell.valueChanged>.
+ * 
+ * To change a specific attribute in an XML node, the following code can be
+ * used.
+ * 
+ * (code)
+ * graph.getModel().valueForCellChanged = function(cell, value)
+ * {
+ *   var previous = cell.value.getAttribute('label');
+ *   cell.value.setAttribute('label', value);
+ *   
+ *   return previous;
+ * };
+ * (end) 
+ */
+mxGraphModel.prototype.valueForCellChanged = function(cell, value)
+{
+	return cell.valueChanged(value);
+};
+
+/**
+ * Function: getGeometry
+ * 
+ * Returns the <mxGeometry> of the given <mxCell>.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> whose geometry should be returned.
+ */
+mxGraphModel.prototype.getGeometry = function(cell)
+{
+	return (cell != null) ? cell.getGeometry() : null;
+};
+
+/**
+ * Function: setGeometry
+ * 
+ * Sets the <mxGeometry> of the given <mxCell>. The actual update
+ * of the cell is carried out in <geometryForCellChanged>. The
+ * <mxGeometryChange> action is used to encapsulate the change.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose geometry should be changed.
+ * geometry - <mxGeometry> that defines the new geometry.
+ */
+mxGraphModel.prototype.setGeometry = function(cell, geometry)
+{
+	if (geometry != this.getGeometry(cell))
+	{
+		this.execute(new mxGeometryChange(this, cell, geometry));
+	}
+	
+	return geometry;
+};
+
+/**
+ * Function: geometryForCellChanged
+ * 
+ * Inner callback to update the <mxGeometry> of the given <mxCell> using
+ * <mxCell.setGeometry> and return the previous <mxGeometry>.
+ */
+mxGraphModel.prototype.geometryForCellChanged = function(cell, geometry)
+{
+	var previous = this.getGeometry(cell);
+	cell.setGeometry(geometry);
+	
+	return previous;
+};
+
+/**
+ * Function: getStyle
+ * 
+ * Returns the style of the given <mxCell>.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> whose style should be returned.
+ */
+mxGraphModel.prototype.getStyle = function(cell)
+{
+	return (cell != null) ? cell.getStyle() : null;
+};
+
+/**
+ * Function: setStyle
+ * 
+ * Sets the style of the given <mxCell> using <mxStyleChange> and
+ * adds the change to the current transaction.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> whose style should be changed.
+ * style - String of the form [stylename;|key=value;] to specify
+ * the new cell style.
+ */
+mxGraphModel.prototype.setStyle = function(cell, style)
+{
+	if (style != this.getStyle(cell))
+	{
+		this.execute(new mxStyleChange(this, cell, style));
+	}
+	
+	return style;
+};
+
+/**
+ * Function: styleForCellChanged
+ * 
+ * Inner callback to update the style of the given <mxCell>
+ * using <mxCell.setStyle> and return the previous style.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> that specifies the cell to be updated.
+ * style - String of the form [stylename;|key=value;] to specify
+ * the new cell style.
+ */
+mxGraphModel.prototype.styleForCellChanged = function(cell, style)
+{
+	var previous = this.getStyle(cell);
+	cell.setStyle(style);
+	
+	return previous;
+};
+
+/**
+ * Function: isCollapsed
+ * 
+ * Returns true if the given <mxCell> is collapsed.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> whose collapsed state should be returned.
+ */
+mxGraphModel.prototype.isCollapsed = function(cell)
+{
+	return (cell != null) ? cell.isCollapsed() : false;
+};
+
+/**
+ * Function: setCollapsed
+ * 
+ * Sets the collapsed state of the given <mxCell> using <mxCollapseChange>
+ * and adds the change to the current transaction.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> whose collapsed state should be changed.
+ * collapsed - Boolean that specifies the new collpased state.
+ */
+mxGraphModel.prototype.setCollapsed = function(cell, collapsed)
+{
+	if (collapsed != this.isCollapsed(cell))
+	{
+		this.execute(new mxCollapseChange(this, cell, collapsed));
+	}
+	
+	return collapsed;
+};
+	
+/**
+ * Function: collapsedStateForCellChanged
+ *
+ * Inner callback to update the collapsed state of the
+ * given <mxCell> using <mxCell.setCollapsed> and return
+ * the previous collapsed state.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> that specifies the cell to be updated.
+ * collapsed - Boolean that specifies the new collpased state.
+ */
+mxGraphModel.prototype.collapsedStateForCellChanged = function(cell, collapsed)
+{
+	var previous = this.isCollapsed(cell);
+	cell.setCollapsed(collapsed);
+	
+	return previous;
+};
+
+/**
+ * Function: isVisible
+ * 
+ * Returns true if the given <mxCell> is visible.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose visible state should be returned.
+ */
+mxGraphModel.prototype.isVisible = function(cell)
+{
+	return (cell != null) ? cell.isVisible() : false;
+};
+
+/**
+ * Function: setVisible
+ * 
+ * Sets the visible state of the given <mxCell> using <mxVisibleChange> and
+ * adds the change to the current transaction.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> whose visible state should be changed.
+ * visible - Boolean that specifies the new visible state.
+ */
+mxGraphModel.prototype.setVisible = function(cell, visible)
+{
+	if (visible != this.isVisible(cell))
+	{
+		this.execute(new mxVisibleChange(this, cell, visible));
+	}
+	
+	return visible;
+};
+	
+/**
+ * Function: visibleStateForCellChanged
+ *
+ * Inner callback to update the visible state of the
+ * given <mxCell> using <mxCell.setCollapsed> and return
+ * the previous visible state.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> that specifies the cell to be updated.
+ * visible - Boolean that specifies the new visible state.
+ */
+mxGraphModel.prototype.visibleStateForCellChanged = function(cell, visible)
+{
+	var previous = this.isVisible(cell);
+	cell.setVisible(visible);
+	
+	return previous;
+};
+
+/**
+ * Function: execute
+ * 
+ * Executes the given edit and fires events if required. The edit object
+ * requires an execute function which is invoked. The edit is added to the
+ * <currentEdit> between <beginUpdate> and <endUpdate> calls, so that
+ * events will be fired if this execute is an individual transaction, that
+ * is, if no previous <beginUpdate> calls have been made without calling
+ * <endUpdate>. This implementation fires an <execute> event before
+ * executing the given change.
+ * 
+ * Parameters:
+ * 
+ * change - Object that described the change.
+ */
+mxGraphModel.prototype.execute = function(change)
+{
+	change.execute();
+	this.beginUpdate();
+	this.currentEdit.add(change);
+	this.fireEvent(new mxEventObject(mxEvent.EXECUTE, 'change', change));
+	// New global executed event
+	this.fireEvent(new mxEventObject(mxEvent.EXECUTED, 'change', change));
+	this.endUpdate();
+};
+
+/**
+ * Function: beginUpdate
+ * 
+ * Increments the <updateLevel> by one. The event notification
+ * is queued until <updateLevel> reaches 0 by use of
+ * <endUpdate>.
+ *
+ * All changes on <mxGraphModel> are transactional,
+ * that is, they are executed in a single undoable change
+ * on the model (without transaction isolation).
+ * Therefore, if you want to combine any
+ * number of changes into a single undoable change,
+ * you should group any two or more API calls that
+ * modify the graph model between <beginUpdate>
+ * and <endUpdate> calls as shown here:
+ * 
+ * (code)
+ * var model = graph.getModel();
+ * var parent = graph.getDefaultParent();
+ * var index = model.getChildCount(parent);
+ * model.beginUpdate();
+ * try
+ * {
+ *   model.add(parent, v1, index);
+ *   model.add(parent, v2, index+1);
+ * }
+ * finally
+ * {
+ *   model.endUpdate();
+ * }
+ * (end)
+ * 
+ * Of course there is a shortcut for appending a
+ * sequence of cells into the default parent:
+ * 
+ * (code)
+ * graph.addCells([v1, v2]).
+ * (end)
+ */
+mxGraphModel.prototype.beginUpdate = function()
+{
+	this.updateLevel++;
+	this.fireEvent(new mxEventObject(mxEvent.BEGIN_UPDATE));
+	
+	if (this.updateLevel == 1)
+	{
+		this.fireEvent(new mxEventObject(mxEvent.START_EDIT));
+	}
+};
+
+/**
+ * Function: endUpdate
+ * 
+ * Decrements the <updateLevel> by one and fires an <undo>
+ * event if the <updateLevel> reaches 0. This function
+ * indirectly fires a <change> event by invoking the notify
+ * function on the <currentEdit> und then creates a new
+ * <currentEdit> using <createUndoableEdit>.
+ *
+ * The <undo> event is fired only once per edit, whereas
+ * the <change> event is fired whenever the notify
+ * function is invoked, that is, on undo and redo of
+ * the edit.
+ */
+mxGraphModel.prototype.endUpdate = function()
+{
+	this.updateLevel--;
+	
+	if (this.updateLevel == 0)
+	{
+		this.fireEvent(new mxEventObject(mxEvent.END_EDIT));
+	}
+	
+	if (!this.endingUpdate)
+	{
+		this.endingUpdate = this.updateLevel == 0;
+		this.fireEvent(new mxEventObject(mxEvent.END_UPDATE, 'edit', this.currentEdit));
+
+		try
+		{		
+			if (this.endingUpdate && !this.currentEdit.isEmpty())
+			{
+				this.fireEvent(new mxEventObject(mxEvent.BEFORE_UNDO, 'edit', this.currentEdit));
+				var tmp = this.currentEdit;
+				this.currentEdit = this.createUndoableEdit();
+				tmp.notify();
+				this.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', tmp));
+			}
+		}
+		finally
+		{
+			this.endingUpdate = false;
+		}
+	}
+};
+
+/**
+ * Function: createUndoableEdit
+ * 
+ * Creates a new <mxUndoableEdit> that implements the
+ * notify function to fire a <change> and <notify> event
+ * through the <mxUndoableEdit>'s source.
+ */
+mxGraphModel.prototype.createUndoableEdit = function()
+{
+	var edit = new mxUndoableEdit(this, true);
+	
+	edit.notify = function()
+	{
+		// LATER: Remove changes property (deprecated)
+		edit.source.fireEvent(new mxEventObject(mxEvent.CHANGE,
+			'edit', edit, 'changes', edit.changes));
+		edit.source.fireEvent(new mxEventObject(mxEvent.NOTIFY,
+			'edit', edit, 'changes', edit.changes));
+	};
+	
+	return edit;
+};
+
+/**
+ * Function: mergeChildren
+ * 
+ * Merges the children of the given cell into the given target cell inside
+ * this model. All cells are cloned unless there is a corresponding cell in
+ * the model with the same id, in which case the source cell is ignored and
+ * all edges are connected to the corresponding cell in this model. Edges
+ * are considered to have no identity and are always cloned unless the
+ * cloneAllEdges flag is set to false, in which case edges with the same
+ * id in the target model are reconnected to reflect the terminals of the
+ * source edges.
+ */
+mxGraphModel.prototype.mergeChildren = function(from, to, cloneAllEdges)
+{
+	cloneAllEdges = (cloneAllEdges != null) ? cloneAllEdges : true;
+	
+	this.beginUpdate();
+	try
+	{
+		var mapping = new Object();
+		this.mergeChildrenImpl(from, to, cloneAllEdges, mapping);
+		
+		// Post-processes all edges in the mapping and
+		// reconnects the terminals to the corresponding
+		// cells in the target model
+		for (var key in mapping)
+		{
+			var cell = mapping[key];
+			var terminal = this.getTerminal(cell, true);
+
+			if (terminal != null)
+			{
+				terminal = mapping[mxCellPath.create(terminal)];
+				this.setTerminal(cell, terminal, true);
+			}
+			
+			terminal = this.getTerminal(cell, false);
+			
+			if (terminal != null)
+			{
+				terminal = mapping[mxCellPath.create(terminal)];
+				this.setTerminal(cell, terminal, false);
+			}
+		}
+	}
+	finally
+	{
+		this.endUpdate();
+	}
+};
+
+/**
+ * Function: mergeChildren
+ * 
+ * Clones the children of the source cell into the given target cell in
+ * this model and adds an entry to the mapping that maps from the source
+ * cell to the target cell with the same id or the clone of the source cell
+ * that was inserted into this model.
+ */
+mxGraphModel.prototype.mergeChildrenImpl = function(from, to, cloneAllEdges, mapping)
+{
+	this.beginUpdate();
+	try
+	{
+		var childCount = from.getChildCount();
+		
+		for (var i = 0; i < childCount; i++)
+		{
+			var cell = from.getChildAt(i);
+			
+			if (typeof(cell.getId) == 'function')
+			{
+				var id = cell.getId();
+				var target = (id != null && (!this.isEdge(cell) || !cloneAllEdges)) ?
+						this.getCell(id) : null;
+				
+				// Clones and adds the child if no cell exists for the id
+				if (target == null)
+				{
+					var clone = cell.clone();
+					clone.setId(id);
+					
+					// Sets the terminals from the original cell to the clone
+					// because the lookup uses strings not cells in JS
+					clone.setTerminal(cell.getTerminal(true), true);
+					clone.setTerminal(cell.getTerminal(false), false);
+					
+					// Do *NOT* use model.add as this will move the edge away
+					// from the parent in updateEdgeParent if maintainEdgeParent
+					// is enabled in the target model
+					target = to.insert(clone);
+					this.cellAdded(target);
+				}
+				
+				// Stores the mapping for later reconnecting edges
+				mapping[mxCellPath.create(cell)] = target;
+				
+				// Recurses
+				this.mergeChildrenImpl(cell, target, cloneAllEdges, mapping);
+			}
+		}
+	}
+	finally
+	{
+		this.endUpdate();
+	}
+};
+
+/**
+ * Function: getParents
+ * 
+ * Returns an array that represents the set (no duplicates) of all parents
+ * for the given array of cells.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of cells whose parents should be returned.
+ */
+mxGraphModel.prototype.getParents = function(cells)
+{
+	var parents = [];
+	
+	if (cells != null)
+	{
+		var dict = new mxDictionary();
+		
+		for (var i = 0; i < cells.length; i++)
+		{
+			var parent = this.getParent(cells[i]);
+			
+			if (parent != null && !dict.get(parent))
+			{
+				dict.put(parent, true);
+				parents.push(parent);
+			}
+		}
+	}
+	
+	return parents;
+};
+
+//
+// Cell Cloning
+//
+
+/**
+ * Function: cloneCell
+ * 
+ * Returns a deep clone of the given <mxCell> (including
+ * the children) which is created using <cloneCells>.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> to be cloned.
+ */
+mxGraphModel.prototype.cloneCell = function(cell)
+{
+	if (cell != null)
+	{
+		return this.cloneCells([cell], true)[0];
+	}
+	
+	return null;
+};
+
+/**
+ * Function: cloneCells
+ * 
+ * Returns an array of clones for the given array of <mxCells>.
+ * Depending on the value of includeChildren, a deep clone is created for
+ * each cell. Connections are restored based if the corresponding
+ * cell is contained in the passed in array.
+ *
+ * Parameters:
+ * 
+ * cells - Array of <mxCell> to be cloned.
+ * includeChildren - Boolean indicating if the cells should be cloned
+ * with all descendants.
+ * mapping - Optional mapping for existing clones.
+ */
+mxGraphModel.prototype.cloneCells = function(cells, includeChildren, mapping)
+{
+	mapping = (mapping != null) ? mapping : new Object();
+	var clones = [];
+	
+	for (var i = 0; i < cells.length; i++)
+	{
+		if (cells[i] != null)
+		{
+			clones.push(this.cloneCellImpl(cells[i], mapping, includeChildren));
+		}
+		else
+		{
+			clones.push(null);
+		}
+	}
+	
+	for (var i = 0; i < clones.length; i++)
+	{
+		if (clones[i] != null)
+		{
+			this.restoreClone(clones[i], cells[i], mapping);
+		}
+	}
+	
+	return clones;
+};
+			
+/**
+ * Function: cloneCellImpl
+ * 
+ * Inner helper method for cloning cells recursively.
+ */
+mxGraphModel.prototype.cloneCellImpl = function(cell, mapping, includeChildren)
+{
+	var clone = this.cellCloned(cell);
+	
+	// Stores the clone in the lookup table
+	mapping[mxObjectIdentity.get(cell)] = clone;
+	
+	if (includeChildren)
+	{
+		var childCount = this.getChildCount(cell);
+		
+		for (var i = 0; i < childCount; i++)
+		{
+			var cloneChild = this.cloneCellImpl(
+				this.getChildAt(cell, i), mapping, true);
+			clone.insert(cloneChild);
+		}
+	}
+	
+	return clone;
+};
+
+/**
+ * Function: cellCloned
+ * 
+ * Hook for cloning the cell. This returns cell.clone() or
+ * any possible exceptions.
+ */
+mxGraphModel.prototype.cellCloned = function(cell)
+{
+	return cell.clone();
+};
+
+/**
+ * Function: restoreClone
+ * 
+ * Inner helper method for restoring the connections in
+ * a network of cloned cells.
+ */
+mxGraphModel.prototype.restoreClone = function(clone, cell, mapping)
+{
+	var source = this.getTerminal(cell, true);
+	
+	if (source != null)
+	{
+		var tmp = mapping[mxObjectIdentity.get(source)];
+		
+		if (tmp != null)
+		{
+			tmp.insertEdge(clone, true);
+		}
+	}
+	
+	var target = this.getTerminal(cell, false);
+	
+	if (target != null)
+	{
+		var tmp = mapping[mxObjectIdentity.get(target)];
+		
+		if (tmp != null)
+		{	
+			tmp.insertEdge(clone, false);
+		}
+	}
+	
+	var childCount = this.getChildCount(clone);
+	
+	for (var i = 0; i < childCount; i++)
+	{
+		this.restoreClone(this.getChildAt(clone, i),
+			this.getChildAt(cell, i), mapping);
+	}
+};
+
+//
+// Atomic changes
+//
+
+/**
+ * Class: mxRootChange
+ * 
+ * Action to change the root in a model.
+ *
+ * Constructor: mxRootChange
+ * 
+ * Constructs a change of the root in the
+ * specified model.
+ */
+function mxRootChange(model, root)
+{
+	this.model = model;
+	this.root = root;
+	this.previous = root;
+};
+
+/**
+ * Function: execute
+ * 
+ * Carries out a change of the root using
+ * <mxGraphModel.rootChanged>.
+ */
+mxRootChange.prototype.execute = function()
+{
+	this.root = this.previous;
+	this.previous = this.model.rootChanged(this.previous);
+};
+
+/**
+ * Class: mxChildChange
+ * 
+ * Action to add or remove a child in a model.
+ *
+ * Constructor: mxChildChange
+ * 
+ * Constructs a change of a child in the
+ * specified model.
+ */
+function mxChildChange(model, parent, child, index)
+{
+	this.model = model;
+	this.parent = parent;
+	this.previous = parent;
+	this.child = child;
+	this.index = index;
+	this.previousIndex = index;
+};
+
+/**
+ * Function: execute
+ * 
+ * Changes the parent of <child> using
+ * <mxGraphModel.parentForCellChanged> and
+ * removes or restores the cell's
+ * connections.
+ */
+mxChildChange.prototype.execute = function()
+{
+	var tmp = this.model.getParent(this.child);
+	var tmp2 = (tmp != null) ? tmp.getIndex(this.child) : 0;
+	
+	if (this.previous == null)
+	{
+		this.connect(this.child, false);
+	}
+	
+	tmp = this.model.parentForCellChanged(
+		this.child, this.previous, this.previousIndex);
+		
+	if (this.previous != null)
+	{
+		this.connect(this.child, true);
+	}
+	
+	this.parent = this.previous;
+	this.previous = tmp;
+	this.index = this.previousIndex;
+	this.previousIndex = tmp2;
+};
+
+/**
+ * Function: disconnect
+ * 
+ * Disconnects the given cell recursively from its
+ * terminals and stores the previous terminal in the
+ * cell's terminals.
+ */
+mxChildChange.prototype.connect = function(cell, isConnect)
+{
+	isConnect = (isConnect != null) ? isConnect : true;
+	
+	var source = cell.getTerminal(true);
+	var target = cell.getTerminal(false);
+	
+	if (source != null)
+	{
+		if (isConnect)
+		{
+			this.model.terminalForCellChanged(cell, source, true);
+		}
+		else
+		{
+			this.model.terminalForCellChanged(cell, null, true);
+		}
+	}
+	
+	if (target != null)
+	{
+		if (isConnect)
+		{
+			this.model.terminalForCellChanged(cell, target, false);
+		}
+		else
+		{
+			this.model.terminalForCellChanged(cell, null, false);
+		}
+	}
+	
+	cell.setTerminal(source, true);
+	cell.setTerminal(target, false);
+	
+	var childCount = this.model.getChildCount(cell);
+	
+	for (var i=0; i<childCount; i++)
+	{
+		this.connect(this.model.getChildAt(cell, i), isConnect);
+	}
+};
+
+/**
+ * Class: mxTerminalChange
+ * 
+ * Action to change a terminal in a model.
+ *
+ * Constructor: mxTerminalChange
+ * 
+ * Constructs a change of a terminal in the 
+ * specified model.
+ */
+function mxTerminalChange(model, cell, terminal, source)
+{
+	this.model = model;
+	this.cell = cell;
+	this.terminal = terminal;
+	this.previous = terminal;
+	this.source = source;
+};
+
+/**
+ * Function: execute
+ * 
+ * Changes the terminal of <cell> to <previous> using
+ * <mxGraphModel.terminalForCellChanged>.
+ */
+mxTerminalChange.prototype.execute = function()
+{
+	this.terminal = this.previous;
+	this.previous = this.model.terminalForCellChanged(
+		this.cell, this.previous, this.source);
+};
+
+/**
+ * Class: mxValueChange
+ * 
+ * Action to change a user object in a model.
+ *
+ * Constructor: mxValueChange
+ * 
+ * Constructs a change of a user object in the 
+ * specified model.
+ */
+function mxValueChange(model, cell, value)
+{
+	this.model = model;
+	this.cell = cell;
+	this.value = value;
+	this.previous = value;
+};
+
+/**
+ * Function: execute
+ * 
+ * Changes the value of <cell> to <previous> using
+ * <mxGraphModel.valueForCellChanged>.
+ */
+mxValueChange.prototype.execute = function()
+{
+	this.value = this.previous;
+	this.previous = this.model.valueForCellChanged(
+		this.cell, this.previous);
+};
+
+/**
+ * Class: mxStyleChange
+ * 
+ * Action to change a cell's style in a model.
+ *
+ * Constructor: mxStyleChange
+ * 
+ * Constructs a change of a style in the
+ * specified model.
+ */
+function mxStyleChange(model, cell, style)
+{
+	this.model = model;
+	this.cell = cell;
+	this.style = style;
+	this.previous = style;
+};
+
+/**
+ * Function: execute
+ * 
+ * Changes the style of <cell> to <previous> using
+ * <mxGraphModel.styleForCellChanged>.
+ */
+mxStyleChange.prototype.execute = function()
+{
+	this.style = this.previous;
+	this.previous = this.model.styleForCellChanged(
+		this.cell, this.previous);
+};
+
+/**
+ * Class: mxGeometryChange
+ * 
+ * Action to change a cell's geometry in a model.
+ *
+ * Constructor: mxGeometryChange
+ * 
+ * Constructs a change of a geometry in the
+ * specified model.
+ */
+function mxGeometryChange(model, cell, geometry)
+{
+	this.model = model;
+	this.cell = cell;
+	this.geometry = geometry;
+	this.previous = geometry;
+};
+
+/**
+ * Function: execute
+ * 
+ * Changes the geometry of <cell> ro <previous> using
+ * <mxGraphModel.geometryForCellChanged>.
+ */
+mxGeometryChange.prototype.execute = function()
+{
+	this.geometry = this.previous;
+	this.previous = this.model.geometryForCellChanged(
+		this.cell, this.previous);
+};
+
+/**
+ * Class: mxCollapseChange
+ * 
+ * Action to change a cell's collapsed state in a model.
+ *
+ * Constructor: mxCollapseChange
+ * 
+ * Constructs a change of a collapsed state in the
+ * specified model.
+ */
+function mxCollapseChange(model, cell, collapsed)
+{
+	this.model = model;
+	this.cell = cell;
+	this.collapsed = collapsed;
+	this.previous = collapsed;
+};
+
+/**
+ * Function: execute
+ * 
+ * Changes the collapsed state of <cell> to <previous> using
+ * <mxGraphModel.collapsedStateForCellChanged>.
+ */
+mxCollapseChange.prototype.execute = function()
+{
+	this.collapsed = this.previous;
+	this.previous = this.model.collapsedStateForCellChanged(
+		this.cell, this.previous);
+};
+
+/**
+ * Class: mxVisibleChange
+ * 
+ * Action to change a cell's visible state in a model.
+ *
+ * Constructor: mxVisibleChange
+ * 
+ * Constructs a change of a visible state in the
+ * specified model.
+ */
+function mxVisibleChange(model, cell, visible)
+{
+	this.model = model;
+	this.cell = cell;
+	this.visible = visible;
+	this.previous = visible;
+};
+
+/**
+ * Function: execute
+ * 
+ * Changes the visible state of <cell> to <previous> using
+ * <mxGraphModel.visibleStateForCellChanged>.
+ */
+mxVisibleChange.prototype.execute = function()
+{
+	this.visible = this.previous;
+	this.previous = this.model.visibleStateForCellChanged(
+		this.cell, this.previous);
+};
+
+/**
+ * Class: mxCellAttributeChange
+ * 
+ * Action to change the attribute of a cell's user object.
+ * There is no method on the graph model that uses this
+ * action. To use the action, you can use the code shown
+ * in the example below.
+ * 
+ * Example:
+ * 
+ * To change the attributeName in the cell's user object
+ * to attributeValue, use the following code:
+ * 
+ * (code)
+ * model.beginUpdate();
+ * try
+ * {
+ *   var edit = new mxCellAttributeChange(
+ *     cell, attributeName, attributeValue);
+ *   model.execute(edit);
+ * }
+ * finally
+ * {
+ *   model.endUpdate();
+ * } 
+ * (end)
+ *
+ * Constructor: mxCellAttributeChange
+ * 
+ * Constructs a change of a attribute of the DOM node
+ * stored as the value of the given <mxCell>.
+ */
+function mxCellAttributeChange(cell, attribute, value)
+{
+	this.cell = cell;
+	this.attribute = attribute;
+	this.value = value;
+	this.previous = value;
+};
+
+/**
+ * Function: execute
+ * 
+ * Changes the attribute of the cell's user object by
+ * using <mxCell.setAttribute>.
+ */
+mxCellAttributeChange.prototype.execute = function()
+{
+	var tmp = this.cell.getAttribute(this.attribute);
+	
+	if (this.previous == null)
+	{
+		this.cell.value.removeAttribute(this.attribute);
+	}
+	else
+	{
+		this.cell.setAttribute(this.attribute, this.previous);
+	}
+	
+	this.previous = tmp;
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxCell
+ *
+ * Cells are the elements of the graph model. They represent the state
+ * of the groups, vertices and edges in a graph.
+ * 
+ * Custom attributes:
+ * 
+ * For custom attributes we recommend using an XML node as the value of a cell.
+ * The following code can be used to create a cell with an XML node as the
+ * value:
+ * 
+ * (code)
+ * var doc = mxUtils.createXmlDocument();
+ * var node = doc.createElement('MyNode')
+ * node.setAttribute('label', 'MyLabel');
+ * node.setAttribute('attribute1', 'value1');
+ * graph.insertVertex(graph.getDefaultParent(), null, node, 40, 40, 80, 30);
+ * (end)
+ * 
+ * For the label to work, <mxGraph.convertValueToString> and
+ * <mxGraph.cellLabelChanged> should be overridden as follows:
+ * 
+ * (code)
+ * graph.convertValueToString = function(cell)
+ * {
+ *   if (mxUtils.isNode(cell.value))
+ *   {
+ *     return cell.getAttribute('label', '')
+ *   }
+ * };
+ * 
+ * var cellLabelChanged = graph.cellLabelChanged;
+ * graph.cellLabelChanged = function(cell, newValue, autoSize)
+ * {
+ *   if (mxUtils.isNode(cell.value))
+ *   {
+ *     // Clones the value for correct undo/redo
+ *     var elt = cell.value.cloneNode(true);
+ *     elt.setAttribute('label', newValue);
+ *     newValue = elt;
+ *   }
+ *   
+ *   cellLabelChanged.apply(this, arguments);
+ * };
+ * (end)
+ * 
+ * Callback: onInit
+ *
+ * Called from within the constructor.
+ * 
+ * Constructor: mxCell
+ *
+ * Constructs a new cell to be used in a graph model.
+ * This method invokes <onInit> upon completion.
+ * 
+ * Parameters:
+ * 
+ * value - Optional object that represents the cell value.
+ * geometry - Optional <mxGeometry> that specifies the geometry.
+ * style - Optional formatted string that defines the style.
+ */
+function mxCell(value, geometry, style)
+{
+	this.value = value;
+	this.setGeometry(geometry);
+	this.setStyle(style);
+	
+	if (this.onInit != null)
+	{
+		this.onInit();
+	}
+};
+
+/**
+ * Variable: id
+ *
+ * Holds the Id. Default is null.
+ */
+mxCell.prototype.id = null;
+
+/**
+ * Variable: value
+ *
+ * Holds the user object. Default is null.
+ */
+mxCell.prototype.value = null;
+
+/**
+ * Variable: geometry
+ *
+ * Holds the <mxGeometry>. Default is null.
+ */
+mxCell.prototype.geometry = null;
+
+/**
+ * Variable: style
+ *
+ * Holds the style as a string of the form [(stylename|key=value);]. Default is
+ * null.
+ */
+mxCell.prototype.style = null;
+
+/**
+ * Variable: vertex
+ *
+ * Specifies whether the cell is a vertex. Default is false.
+ */
+mxCell.prototype.vertex = false;
+
+/**
+ * Variable: edge
+ *
+ * Specifies whether the cell is an edge. Default is false.
+ */
+mxCell.prototype.edge = false;
+
+/**
+ * Variable: connectable
+ *
+ * Specifies whether the cell is connectable. Default is true.
+ */
+mxCell.prototype.connectable = true;
+
+/**
+ * Variable: visible
+ *
+ * Specifies whether the cell is visible. Default is true.
+ */
+mxCell.prototype.visible = true;
+
+/**
+ * Variable: collapsed
+ *
+ * Specifies whether the cell is collapsed. Default is false.
+ */
+mxCell.prototype.collapsed = false;
+
+/**
+ * Variable: parent
+ *
+ * Reference to the parent cell.
+ */
+mxCell.prototype.parent = null;
+
+/**
+ * Variable: source
+ *
+ * Reference to the source terminal.
+ */
+mxCell.prototype.source = null;
+
+/**
+ * Variable: target
+ *
+ * Reference to the target terminal.
+ */
+mxCell.prototype.target = null;
+
+/**
+ * Variable: children
+ *
+ * Holds the child cells.
+ */
+mxCell.prototype.children = null;
+
+/**
+ * Variable: edges
+ *
+ * Holds the edges.
+ */
+mxCell.prototype.edges = null;
+
+/**
+ * Variable: mxTransient
+ *
+ * List of members that should not be cloned inside <clone>. This field is
+ * passed to <mxUtils.clone> and is not made persistent in <mxCellCodec>.
+ * This is not a convention for all classes, it is only used in this class
+ * to mark transient fields since transient modifiers are not supported by
+ * the language.
+ */
+mxCell.prototype.mxTransient = ['id', 'value', 'parent', 'source',
+                                'target', 'children', 'edges'];
+
+/**
+ * Function: getId
+ *
+ * Returns the Id of the cell as a string.
+ */
+mxCell.prototype.getId = function()
+{
+	return this.id;
+};
+		
+/**
+ * Function: setId
+ *
+ * Sets the Id of the cell to the given string.
+ */
+mxCell.prototype.setId = function(id)
+{
+	this.id = id;
+};
+
+/**
+ * Function: getValue
+ *
+ * Returns the user object of the cell. The user
+ * object is stored in <value>.
+ */
+mxCell.prototype.getValue = function()
+{
+	return this.value;
+};
+		
+/**
+ * Function: setValue
+ *
+ * Sets the user object of the cell. The user object
+ * is stored in <value>.
+ */
+mxCell.prototype.setValue = function(value)
+{
+	this.value = value;
+};
+
+/**
+ * Function: valueChanged
+ *
+ * Changes the user object after an in-place edit
+ * and returns the previous value. This implementation
+ * replaces the user object with the given value and
+ * returns the old user object.
+ */
+mxCell.prototype.valueChanged = function(newValue)
+{
+	var previous = this.getValue();
+	this.setValue(newValue);
+	
+	return previous;
+};
+
+/**
+ * Function: getGeometry
+ *
+ * Returns the <mxGeometry> that describes the <geometry>.
+ */
+mxCell.prototype.getGeometry = function()
+{
+	return this.geometry;
+};
+
+/**
+ * Function: setGeometry
+ *
+ * Sets the <mxGeometry> to be used as the <geometry>.
+ */
+mxCell.prototype.setGeometry = function(geometry)
+{
+	this.geometry = geometry;
+};
+
+/**
+ * Function: getStyle
+ *
+ * Returns a string that describes the <style>.
+ */
+mxCell.prototype.getStyle = function()
+{
+	return this.style;
+};
+
+/**
+ * Function: setStyle
+ *
+ * Sets the string to be used as the <style>.
+ */
+mxCell.prototype.setStyle = function(style)
+{
+	this.style = style;
+};
+
+/**
+ * Function: isVertex
+ *
+ * Returns true if the cell is a vertex.
+ */
+mxCell.prototype.isVertex = function()
+{
+	return this.vertex != 0;
+};
+
+/**
+ * Function: setVertex
+ *
+ * Specifies if the cell is a vertex. This should only be assigned at
+ * construction of the cell and not be changed during its lifecycle.
+ * 
+ * Parameters:
+ * 
+ * vertex - Boolean that specifies if the cell is a vertex.
+ */
+mxCell.prototype.setVertex = function(vertex)
+{
+	this.vertex = vertex;
+};
+
+/**
+ * Function: isEdge
+ *
+ * Returns true if the cell is an edge.
+ */
+mxCell.prototype.isEdge = function()
+{
+	return this.edge != 0;
+};
+	
+/**
+ * Function: setEdge
+ * 
+ * Specifies if the cell is an edge. This should only be assigned at
+ * construction of the cell and not be changed during its lifecycle.
+ * 
+ * Parameters:
+ * 
+ * edge - Boolean that specifies if the cell is an edge.
+ */
+mxCell.prototype.setEdge = function(edge)
+{
+	this.edge = edge;
+};
+
+/**
+ * Function: isConnectable
+ *
+ * Returns true if the cell is connectable.
+ */
+mxCell.prototype.isConnectable = function()
+{
+	return this.connectable != 0;
+};
+
+/**
+ * Function: setConnectable
+ *
+ * Sets the connectable state.
+ * 
+ * Parameters:
+ * 
+ * connectable - Boolean that specifies the new connectable state.
+ */
+mxCell.prototype.setConnectable = function(connectable)
+{
+	this.connectable = connectable;
+};
+
+/**
+ * Function: isVisible
+ *
+ * Returns true if the cell is visibile.
+ */
+mxCell.prototype.isVisible = function()
+{
+	return this.visible != 0;
+};
+
+/**
+ * Function: setVisible
+ *
+ * Specifies if the cell is visible.
+ * 
+ * Parameters:
+ * 
+ * visible - Boolean that specifies the new visible state.
+ */
+mxCell.prototype.setVisible = function(visible)
+{
+	this.visible = visible;
+};
+
+/**
+ * Function: isCollapsed
+ *
+ * Returns true if the cell is collapsed.
+ */
+mxCell.prototype.isCollapsed = function()
+{
+	return this.collapsed != 0;
+};
+
+/**
+ * Function: setCollapsed
+ *
+ * Sets the collapsed state.
+ * 
+ * Parameters:
+ * 
+ * collapsed - Boolean that specifies the new collapsed state.
+ */
+mxCell.prototype.setCollapsed = function(collapsed)
+{
+	this.collapsed = collapsed;
+};
+
+/**
+ * Function: getParent
+ *
+ * Returns the cell's parent.
+ */
+mxCell.prototype.getParent = function()
+{
+	return this.parent;
+};
+
+/**
+ * Function: setParent
+ *
+ * Sets the parent cell.
+ * 
+ * Parameters:
+ * 
+ * parent - <mxCell> that represents the new parent.
+ */
+mxCell.prototype.setParent = function(parent)
+{
+	this.parent = parent;
+};
+
+/**
+ * Function: getTerminal
+ *
+ * Returns the source or target terminal.
+ * 
+ * Parameters:
+ * 
+ * source - Boolean that specifies if the source terminal should be
+ * returned.
+ */
+mxCell.prototype.getTerminal = function(source)
+{
+	return (source) ? this.source : this.target;
+};
+
+/**
+ * Function: setTerminal
+ *
+ * Sets the source or target terminal and returns the new terminal.
+ * 
+ * Parameters:
+ * 
+ * terminal - <mxCell> that represents the new source or target terminal.
+ * isSource - Boolean that specifies if the source or target terminal
+ * should be set.
+ */
+mxCell.prototype.setTerminal = function(terminal, isSource)
+{
+	if (isSource)
+	{
+		this.source = terminal;
+	}
+	else
+	{
+		this.target = terminal;
+	}
+	
+	return terminal;
+};
+
+/**
+ * Function: getChildCount
+ *
+ * Returns the number of child cells.
+ */
+mxCell.prototype.getChildCount = function()
+{
+	return (this.children == null) ? 0 : this.children.length;
+};
+
+/**
+ * Function: getIndex
+ *
+ * Returns the index of the specified child in the child array.
+ * 
+ * Parameters:
+ * 
+ * child - Child whose index should be returned.
+ */
+mxCell.prototype.getIndex = function(child)
+{
+	return mxUtils.indexOf(this.children, child);
+};
+
+/**
+ * Function: getChildAt
+ *
+ * Returns the child at the specified index.
+ * 
+ * Parameters:
+ * 
+ * index - Integer that specifies the child to be returned.
+ */
+mxCell.prototype.getChildAt = function(index)
+{
+	return (this.children == null) ? null : this.children[index];
+};
+
+/**
+ * Function: insert
+ *
+ * Inserts the specified child into the child array at the specified index
+ * and updates the parent reference of the child. If not childIndex is
+ * specified then the child is appended to the child array. Returns the
+ * inserted child.
+ * 
+ * Parameters:
+ * 
+ * child - <mxCell> to be inserted or appended to the child array.
+ * index - Optional integer that specifies the index at which the child
+ * should be inserted into the child array.
+ */
+mxCell.prototype.insert = function(child, index)
+{
+	if (child != null)
+	{
+		if (index == null)
+		{
+			index = this.getChildCount();
+			
+			if (child.getParent() == this)
+			{
+				index--;
+			}
+		}
+
+		child.removeFromParent();
+		child.setParent(this);
+		
+		if (this.children == null)
+		{
+			this.children = [];
+			this.children.push(child);
+		}
+		else
+		{
+			this.children.splice(index, 0, child);
+		}
+	}
+	
+	return child;
+};
+
+/**
+ * Function: remove
+ *
+ * Removes the child at the specified index from the child array and
+ * returns the child that was removed. Will remove the parent reference of
+ * the child.
+ * 
+ * Parameters:
+ * 
+ * index - Integer that specifies the index of the child to be
+ * removed.
+ */
+mxCell.prototype.remove = function(index)
+{
+	var child = null;
+	
+	if (this.children != null && index >= 0)
+	{
+		child = this.getChildAt(index);
+		
+		if (child != null)
+		{
+			this.children.splice(index, 1);
+			child.setParent(null);
+		}
+	}
+	
+	return child;
+};
+
+/**
+ * Function: removeFromParent
+ *
+ * Removes the cell from its parent.
+ */
+mxCell.prototype.removeFromParent = function()
+{
+	if (this.parent != null)
+	{
+		var index = this.parent.getIndex(this);
+		this.parent.remove(index);
+	}
+};
+
+/**
+ * Function: getEdgeCount
+ *
+ * Returns the number of edges in the edge array.
+ */
+mxCell.prototype.getEdgeCount = function()
+{
+	return (this.edges == null) ? 0 : this.edges.length;
+};
+
+/**
+ * Function: getEdgeIndex
+ *
+ * Returns the index of the specified edge in <edges>.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> whose index in <edges> should be returned.
+ */
+mxCell.prototype.getEdgeIndex = function(edge)
+{
+	return mxUtils.indexOf(this.edges, edge);
+};
+
+/**
+ * Function: getEdgeAt
+ *
+ * Returns the edge at the specified index in <edges>.
+ * 
+ * Parameters:
+ * 
+ * index - Integer that specifies the index of the edge to be returned.
+ */
+mxCell.prototype.getEdgeAt = function(index)
+{
+	return (this.edges == null) ? null : this.edges[index];
+};
+
+/**
+ * Function: insertEdge
+ *
+ * Inserts the specified edge into the edge array and returns the edge.
+ * Will update the respective terminal reference of the edge.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> to be inserted into the edge array.
+ * isOutgoing - Boolean that specifies if the edge is outgoing.
+ */
+mxCell.prototype.insertEdge = function(edge, isOutgoing)
+{
+	if (edge != null)
+	{
+		edge.removeFromTerminal(isOutgoing);
+		edge.setTerminal(this, isOutgoing);
+		
+		if (this.edges == null ||
+			edge.getTerminal(!isOutgoing) != this ||
+			mxUtils.indexOf(this.edges, edge) < 0)
+		{
+			if (this.edges == null)
+			{
+				this.edges = [];
+			}
+			
+			this.edges.push(edge);
+		}
+	}
+	
+	return edge;
+};
+
+/**
+ * Function: removeEdge
+ *
+ * Removes the specified edge from the edge array and returns the edge.
+ * Will remove the respective terminal reference from the edge.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> to be removed from the edge array.
+ * isOutgoing - Boolean that specifies if the edge is outgoing.
+ */
+mxCell.prototype.removeEdge = function(edge, isOutgoing)
+{
+	if (edge != null)
+	{
+		if (edge.getTerminal(!isOutgoing) != this &&
+			this.edges != null)
+		{
+			var index = this.getEdgeIndex(edge);
+			
+			if (index >= 0)
+			{
+				this.edges.splice(index, 1);
+			}
+		}
+		
+		edge.setTerminal(null, isOutgoing);
+	}
+	
+	return edge;
+};
+
+/**
+ * Function: removeFromTerminal
+ *
+ * Removes the edge from its source or target terminal.
+ * 
+ * Parameters:
+ * 
+ * isSource - Boolean that specifies if the edge should be removed from its
+ * source or target terminal.
+ */
+mxCell.prototype.removeFromTerminal = function(isSource)
+{
+	var terminal = this.getTerminal(isSource);
+	
+	if (terminal != null)
+	{
+		terminal.removeEdge(this, isSource);
+	}
+};
+
+/**
+ * Function: hasAttribute
+ * 
+ * Returns true if the user object is an XML node that contains the given
+ * attribute.
+ * 
+ * Parameters:
+ * 
+ * name - Name of the attribute.
+ */
+mxCell.prototype.hasAttribute = function(name)
+{
+	var userObject = this.getValue();
+	
+	return (userObject != null &&
+		userObject.nodeType == mxConstants.NODETYPE_ELEMENT && userObject.hasAttribute) ?
+		userObject.hasAttribute(name) : userObject.getAttribute(name) != null;
+};
+
+/**
+ * Function: getAttribute
+ *
+ * Returns the specified attribute from the user object if it is an XML
+ * node.
+ * 
+ * Parameters:
+ * 
+ * name - Name of the attribute whose value should be returned.
+ * defaultValue - Optional default value to use if the attribute has no
+ * value.
+ */
+mxCell.prototype.getAttribute = function(name, defaultValue)
+{
+	var userObject = this.getValue();
+	
+	var val = (userObject != null &&
+		userObject.nodeType == mxConstants.NODETYPE_ELEMENT) ?
+		userObject.getAttribute(name) : null;
+		
+	return val || defaultValue;
+};
+
+/**
+ * Function: setAttribute
+ *
+ * Sets the specified attribute on the user object if it is an XML node.
+ * 
+ * Parameters:
+ * 
+ * name - Name of the attribute whose value should be set.
+ * value - New value of the attribute.
+ */
+mxCell.prototype.setAttribute = function(name, value)
+{
+	var userObject = this.getValue();
+	
+	if (userObject != null &&
+		userObject.nodeType == mxConstants.NODETYPE_ELEMENT)
+	{
+		userObject.setAttribute(name, value);
+	}
+};
+
+/**
+ * Function: clone
+ *
+ * Returns a clone of the cell. Uses <cloneValue> to clone
+ * the user object. All fields in <mxTransient> are ignored
+ * during the cloning.
+ */
+mxCell.prototype.clone = function()
+{
+	var clone = mxUtils.clone(this, this.mxTransient);
+	clone.setValue(this.cloneValue());
+	
+	return clone;
+};
+
+/**
+ * Function: cloneValue
+ *
+ * Returns a clone of the cell's user object.
+ */
+mxCell.prototype.cloneValue = function()
+{
+	var value = this.getValue();
+	
+	if (value != null)
+	{
+		if (typeof(value.clone) == 'function')
+		{
+			value = value.clone();
+		}
+		else if (!isNaN(value.nodeType))
+		{
+			value = value.cloneNode(true);
+		}
+	}
+	
+	return value;
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxGeometry
+ * 
+ * Extends <mxRectangle> to represent the geometry of a cell.
+ * 
+ * For vertices, the geometry consists of the x- and y-location, and the width
+ * and height. For edges, the geometry consists of the optional terminal- and
+ * control points. The terminal points are only required if an edge is
+ * unconnected, and are stored in the sourcePoint> and <targetPoint>
+ * variables, respectively.
+ * 
+ * Example:
+ * 
+ * If an edge is unconnected, that is, it has no source or target terminal,
+ * then a geometry with terminal points for a new edge can be defined as
+ * follows.
+ * 
+ * (code)
+ * geometry.setTerminalPoint(new mxPoint(x1, y1), true);
+ * geometry.points = [new mxPoint(x2, y2)];
+ * geometry.setTerminalPoint(new mxPoint(x3, y3), false);
+ * (end)
+ * 
+ * Control points are used regardless of the connected state of an edge and may
+ * be ignored or interpreted differently depending on the edge's <mxEdgeStyle>.
+ * 
+ * To disable automatic reset of control points after a cell has been moved or
+ * resized, the the <mxGraph.resizeEdgesOnMove> and
+ * <mxGraph.resetEdgesOnResize> may be used.
+ *
+ * Edge Labels:
+ * 
+ * Using the x- and y-coordinates of a cell's geometry, it is possible to
+ * position the label on edges on a specific location on the actual edge shape
+ * as it appears on the screen. The x-coordinate of an edge's geometry is used
+ * to describe the distance from the center of the edge from -1 to 1 with 0
+ * being the center of the edge and the default value. The y-coordinate of an
+ * edge's geometry is used to describe the absolute, orthogonal distance in
+ * pixels from that point. In addition, the <mxGeometry.offset> is used as an
+ * absolute offset vector from the resulting point.
+ * 
+ * This coordinate system is applied if <relative> is true, otherwise the
+ * offset defines the absolute vector from the edge's center point to the
+ * label and the values for <x> and <y> are ignored.
+ * 
+ * The width and height parameter for edge geometries can be used to set the
+ * label width and height (eg. for word wrapping).
+ * 
+ * Ports:
+ * 
+ * The term "port" refers to a relatively positioned, connectable child cell,
+ * which is used to specify the connection between the parent and another cell
+ * in the graph. Ports are typically modeled as vertices with relative
+ * geometries.
+ * 
+ * Offsets:
+ * 
+ * The <offset> field is interpreted in 3 different ways, depending on the cell
+ * and the geometry. For edges, the offset defines the absolute offset for the
+ * edge label. For relative geometries, the offset defines the absolute offset
+ * for the origin (top, left corner) of the vertex, otherwise the offset
+ * defines the absolute offset for the label inside the vertex or group.
+ * 
+ * Constructor: mxGeometry
+ *
+ * Constructs a new object to describe the size and location of a vertex or
+ * the control points of an edge.
+ */
+function mxGeometry(x, y, width, height)
+{
+	mxRectangle.call(this, x, y, width, height);
+};
+
+/**
+ * Extends mxRectangle.
+ */
+mxGeometry.prototype = new mxRectangle();
+mxGeometry.prototype.constructor = mxGeometry;
+
+/**
+ * Variable: TRANSLATE_CONTROL_POINTS
+ * 
+ * Global switch to translate the points in translate. Default is true.
+ */
+mxGeometry.prototype.TRANSLATE_CONTROL_POINTS = true;
+
+/**
+ * Variable: alternateBounds
+ *
+ * Stores alternate values for x, y, width and height in a rectangle. See
+ * <swap> to exchange the values. Default is null.
+ */
+mxGeometry.prototype.alternateBounds = null;
+
+/**
+ * Variable: sourcePoint
+ *
+ * Defines the source <mxPoint> of the edge. This is used if the
+ * corresponding edge does not have a source vertex. Otherwise it is
+ * ignored. Default is  null.
+ */
+mxGeometry.prototype.sourcePoint = null;
+
+/**
+ * Variable: targetPoint
+ *
+ * Defines the target <mxPoint> of the edge. This is used if the
+ * corresponding edge does not have a target vertex. Otherwise it is
+ * ignored. Default is null.
+ */
+mxGeometry.prototype.targetPoint = null;
+
+/**
+ * Variable: points
+ *
+ * Array of <mxPoints> which specifies the control points along the edge.
+ * These points are the intermediate points on the edge, for the endpoints
+ * use <targetPoint> and <sourcePoint> or set the terminals of the edge to
+ * a non-null value. Default is null.
+ */
+mxGeometry.prototype.points = null;
+
+/**
+ * Variable: offset
+ *
+ * For edges, this holds the offset (in pixels) from the position defined
+ * by <x> and <y> on the edge. For relative geometries (for vertices), this
+ * defines the absolute offset from the point defined by the relative
+ * coordinates. For absolute geometries (for vertices), this defines the
+ * offset for the label. Default is null.
+ */
+mxGeometry.prototype.offset = null;
+
+/**
+ * Variable: relative
+ *
+ * Specifies if the coordinates in the geometry are to be interpreted as
+ * relative coordinates. For edges, this is used to define the location of
+ * the edge label relative to the edge as rendered on the display. For
+ * vertices, this specifies the relative location inside the bounds of the
+ * parent cell.
+ * 
+ * If this is false, then the coordinates are relative to the origin of the
+ * parent cell or, for edges, the edge label position is relative to the
+ * center of the edge as rendered on screen.
+ * 
+ * Default is false.
+ */
+mxGeometry.prototype.relative = false;
+
+/**
+ * Function: swap
+ * 
+ * Swaps the x, y, width and height with the values stored in
+ * <alternateBounds> and puts the previous values into <alternateBounds> as
+ * a rectangle. This operation is carried-out in-place, that is, using the
+ * existing geometry instance. If this operation is called during a graph
+ * model transactional change, then the geometry should be cloned before
+ * calling this method and setting the geometry of the cell using
+ * <mxGraphModel.setGeometry>.
+ */
+mxGeometry.prototype.swap = function()
+{
+	if (this.alternateBounds != null)
+	{
+		var old = new mxRectangle(
+			this.x, this.y, this.width, this.height);
+
+		this.x = this.alternateBounds.x;
+		this.y = this.alternateBounds.y;
+		this.width = this.alternateBounds.width;
+		this.height = this.alternateBounds.height;
+
+		this.alternateBounds = old;
+	}
+};
+
+/**
+ * Function: getTerminalPoint
+ * 
+ * Returns the <mxPoint> representing the source or target point of this
+ * edge. This is only used if the edge has no source or target vertex.
+ * 
+ * Parameters:
+ * 
+ * isSource - Boolean that specifies if the source or target point
+ * should be returned.
+ */
+mxGeometry.prototype.getTerminalPoint = function(isSource)
+{
+	return (isSource) ? this.sourcePoint : this.targetPoint;
+};
+
+/**
+ * Function: setTerminalPoint
+ * 
+ * Sets the <sourcePoint> or <targetPoint> to the given <mxPoint> and
+ * returns the new point.
+ * 
+ * Parameters:
+ * 
+ * point - Point to be used as the new source or target point.
+ * isSource - Boolean that specifies if the source or target point
+ * should be set.
+ */
+mxGeometry.prototype.setTerminalPoint = function(point, isSource)
+{
+	if (isSource)
+	{
+		this.sourcePoint = point;
+	}
+	else
+	{
+		this.targetPoint = point;
+	}
+	
+	return point;
+};
+
+/**
+ * Function: rotate
+ * 
+ * Rotates the geometry by the given angle around the given center. That is,
+ * <x> and <y> of the geometry, the <sourcePoint>, <targetPoint> and all
+ * <points> are translated by the given amount. <x> and <y> are only
+ * translated if <relative> is false.
+ * 
+ * Parameters:
+ * 
+ * angle - Number that specifies the rotation angle in degrees.
+ * cx - <mxPoint> that specifies the center of the rotation.
+ */
+mxGeometry.prototype.rotate = function(angle, cx)
+{
+	var rad = mxUtils.toRadians(angle);
+	var cos = Math.cos(rad);
+	var sin = Math.sin(rad);
+	
+	// Rotates the geometry
+	if (!this.relative)
+	{
+		var ct = new mxPoint(this.getCenterX(), this.getCenterY());
+		var pt = mxUtils.getRotatedPoint(ct, cos, sin, cx);
+		
+		this.x = Math.round(pt.x - this.width / 2);
+		this.y = Math.round(pt.y - this.height / 2);
+	}
+
+	// Rotates the source point
+	if (this.sourcePoint != null)
+	{
+		var pt = mxUtils.getRotatedPoint(this.sourcePoint, cos, sin, cx);
+		this.sourcePoint.x = Math.round(pt.x);
+		this.sourcePoint.y = Math.round(pt.y);
+	}
+	
+	// Translates the target point
+	if (this.targetPoint != null)
+	{
+		var pt = mxUtils.getRotatedPoint(this.targetPoint, cos, sin, cx);
+		this.targetPoint.x = Math.round(pt.x);
+		this.targetPoint.y = Math.round(pt.y);	
+	}
+	
+	// Translate the control points
+	if (this.points != null)
+	{
+		for (var i = 0; i < this.points.length; i++)
+		{
+			if (this.points[i] != null)
+			{
+				var pt = mxUtils.getRotatedPoint(this.points[i], cos, sin, cx);
+				this.points[i].x = Math.round(pt.x);
+				this.points[i].y = Math.round(pt.y);
+			}
+		}
+	}
+};
+
+/**
+ * Function: translate
+ * 
+ * Translates the geometry by the specified amount. That is, <x> and <y> of the
+ * geometry, the <sourcePoint>, <targetPoint> and all <points> are translated
+ * by the given amount. <x> and <y> are only translated if <relative> is false.
+ * If <TRANSLATE_CONTROL_POINTS> is false, then <points> are not modified by
+ * this function.
+ * 
+ * Parameters:
+ * 
+ * dx - Number that specifies the x-coordinate of the translation.
+ * dy - Number that specifies the y-coordinate of the translation.
+ */
+mxGeometry.prototype.translate = function(dx, dy)
+{
+	dx = parseFloat(dx);
+	dy = parseFloat(dy);
+	
+	// Translates the geometry
+	if (!this.relative)
+	{
+		this.x = parseFloat(this.x) + dx;
+		this.y = parseFloat(this.y) + dy;
+	}
+
+	// Translates the source point
+	if (this.sourcePoint != null)
+	{
+		this.sourcePoint.x = parseFloat(this.sourcePoint.x) + dx;
+		this.sourcePoint.y = parseFloat(this.sourcePoint.y) + dy;
+	}
+	
+	// Translates the target point
+	if (this.targetPoint != null)
+	{
+		this.targetPoint.x = parseFloat(this.targetPoint.x) + dx;
+		this.targetPoint.y = parseFloat(this.targetPoint.y) + dy;		
+	}
+
+	// Translate the control points
+	if (this.TRANSLATE_CONTROL_POINTS && this.points != null)
+	{
+		for (var i = 0; i < this.points.length; i++)
+		{
+			if (this.points[i] != null)
+			{
+				this.points[i].x = parseFloat(this.points[i].x) + dx;
+				this.points[i].y = parseFloat(this.points[i].y) + dy;
+			}
+		}
+	}
+};
+
+/**
+ * Function: scale
+ * 
+ * Scales the geometry by the given amount. That is, <x> and <y> of the
+ * geometry, the <sourcePoint>, <targetPoint> and all <points> are scaled
+ * by the given amount. <x>, <y>, <width> and <height> are only scaled if
+ * <relative> is false. If <fixedAspect> is true, then the smaller value
+ * is used to scale the width and the height.
+ * 
+ * Parameters:
+ * 
+ * sx - Number that specifies the horizontal scale factor.
+ * sy - Number that specifies the vertical scale factor.
+ * fixedAspect - Optional boolean to keep the aspect ratio fixed.
+ */
+mxGeometry.prototype.scale = function(sx, sy, fixedAspect)
+{
+	sx = parseFloat(sx);
+	sy = parseFloat(sy);
+
+	// Translates the source point
+	if (this.sourcePoint != null)
+	{
+		this.sourcePoint.x = parseFloat(this.sourcePoint.x) * sx;
+		this.sourcePoint.y = parseFloat(this.sourcePoint.y) * sy;
+	}
+	
+	// Translates the target point
+	if (this.targetPoint != null)
+	{
+		this.targetPoint.x = parseFloat(this.targetPoint.x) * sx;
+		this.targetPoint.y = parseFloat(this.targetPoint.y) * sy;		
+	}
+
+	// Translate the control points
+	if (this.points != null)
+	{
+		for (var i = 0; i < this.points.length; i++)
+		{
+			if (this.points[i] != null)
+			{
+				this.points[i].x = parseFloat(this.points[i].x) * sx;
+				this.points[i].y = parseFloat(this.points[i].y) * sy;
+			}
+		}
+	}
+	
+	// Translates the geometry
+	if (!this.relative)
+	{
+		this.x = parseFloat(this.x) * sx;
+		this.y = parseFloat(this.y) * sy;
+
+		if (fixedAspect)
+		{
+			sy = sx = Math.min(sx, sy);
+		}
+		
+		this.width = parseFloat(this.width) * sx;
+		this.height = parseFloat(this.height) * sy;
+	}
+};
+
+/**
+ * Function: equals
+ * 
+ * Returns true if the given object equals this geometry.
+ */
+mxGeometry.prototype.equals = function(obj)
+{
+	return mxRectangle.prototype.equals.apply(this, arguments) &&
+		this.relative == obj.relative &&
+		((this.sourcePoint == null && obj.sourcePoint == null) || (this.sourcePoint != null && this.sourcePoint.equals(obj.sourcePoint))) &&
+		((this.targetPoint == null && obj.targetPoint == null) || (this.targetPoint != null && this.targetPoint.equals(obj.targetPoint))) &&
+		((this.points == null && obj.points == null) || (this.points != null && mxUtils.equalPoints(this.points, obj.points))) &&
+		((this.alternateBounds == null && obj.alternateBounds == null) || (this.alternateBounds != null && this.alternateBounds.equals(obj.alternateBounds))) &&
+		((this.offset == null && obj.offset == null) || (this.offset != null && this.offset.equals(obj.offset)));
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+var mxCellPath =
+{
+
+	/**
+	 * Class: mxCellPath
+	 * 
+	 * Implements a mechanism for temporary cell Ids.
+	 * 
+	 * Variable: PATH_SEPARATOR
+	 * 
+	 * Defines the separator between the path components. Default is ".".
+	 */
+	PATH_SEPARATOR: '.',
+	
+	/**
+	 * Function: create
+	 * 
+	 * Creates the cell path for the given cell. The cell path is a
+	 * concatenation of the indices of all ancestors on the (finite) path to
+	 * the root, eg. "0.0.0.1".
+	 * 
+	 * Parameters:
+	 * 
+	 * cell - Cell whose path should be returned.
+	 */
+	create: function(cell)
+	{
+		var result = '';
+		
+		if (cell != null)
+		{
+			var parent = cell.getParent();
+			
+			while (parent != null)
+			{
+				var index = parent.getIndex(cell);
+				result = index + mxCellPath.PATH_SEPARATOR + result;
+				
+				cell = parent;
+				parent = cell.getParent();
+			}
+		}
+		
+		// Removes trailing separator
+		var n = result.length;
+		
+		if (n > 1)
+		{
+			result = result.substring(0, n - 1);
+		}
+		
+		return result;
+	},
+	
+	/**
+	 * Function: getParentPath
+	 * 
+	 * Returns the path for the parent of the cell represented by the given
+	 * path. Returns null if the given path has no parent.
+	 * 
+	 * Parameters:
+	 * 
+	 * path - Path whose parent path should be returned.
+	 */
+	getParentPath: function(path)
+	{
+		if (path != null)
+		{
+			var index = path.lastIndexOf(mxCellPath.PATH_SEPARATOR);
+
+			if (index >= 0)
+			{
+				return path.substring(0, index);
+			}
+			else if (path.length > 0)
+			{
+				return '';
+			}
+		}
+
+		return null;
+	},
+
+	/**
+	 * Function: resolve
+	 * 
+	 * Returns the cell for the specified cell path using the given root as the
+	 * root of the path.
+	 * 
+	 * Parameters:
+	 * 
+	 * root - Root cell of the path to be resolved.
+	 * path - String that defines the path.
+	 */
+	resolve: function(root, path)
+	{
+		var parent = root;
+		
+		if (path != null)
+		{
+			var tokens = path.split(mxCellPath.PATH_SEPARATOR);
+			
+			for (var i=0; i<tokens.length; i++)
+			{
+				parent = parent.getChildAt(parseInt(tokens[i]));
+			}
+		}
+		
+		return parent;
+	},
+	
+	/**
+	 * Function: compare
+	 * 
+	 * Compares the given cell paths and returns -1 if p1 is smaller, 0 if
+	 * p1 is equal and 1 if p1 is greater than p2.
+	 */
+	compare: function(p1, p2)
+	{
+		var min = Math.min(p1.length, p2.length);
+		var comp = 0;
+		
+		for (var i = 0; i < min; i++)
+		{
+			if (p1[i] != p2[i])
+			{
+				if (p1[i].length == 0 ||
+					p2[i].length == 0)
+				{
+					comp = (p1[i] == p2[i]) ? 0 : ((p1[i] > p2[i]) ? 1 : -1);
+				}
+				else
+				{
+					var t1 = parseInt(p1[i]);
+					var t2 = parseInt(p2[i]);
+					
+					comp = (t1 == t2) ? 0 : ((t1 > t2) ? 1 : -1);
+				}
+				
+				break;
+			}
+		}
+		
+		// Compares path length if both paths are equal to this point
+		if (comp == 0)
+		{
+			var t1 = p1.length;
+			var t2 = p2.length;
+			
+			if (t1 != t2)
+			{
+				comp = (t1 > t2) ? 1 : -1;
+			}
+		}
+		
+		return comp;
+	}
+
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+var mxPerimeter =
+{
+	/**
+	 * Class: mxPerimeter
+	 * 
+	 * Provides various perimeter functions to be used in a style
+	 * as the value of <mxConstants.STYLE_PERIMETER>. Perimeters for
+	 * rectangle, circle, rhombus and triangle are available.
+	 *
+	 * Example:
+	 * 
+	 * (code)
+	 * <add as="perimeter">mxPerimeter.RectanglePerimeter</add>
+	 * (end)
+	 * 
+	 * Or programmatically:
+	 * 
+	 * (code)
+	 * style[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter;
+	 * (end)
+	 * 
+	 * When adding new perimeter functions, it is recommended to use the 
+	 * mxPerimeter-namespace as follows:
+	 * 
+	 * (code)
+	 * mxPerimeter.CustomPerimeter = function (bounds, vertex, next, orthogonal)
+	 * {
+	 *   var x = 0; // Calculate x-coordinate
+	 *   var y = 0; // Calculate y-coordainte
+	 *   
+	 *   return new mxPoint(x, y);
+	 * }
+	 * (end)
+	 * 
+	 * The new perimeter should then be registered in the <mxStyleRegistry> as follows:
+	 * (code)
+	 * mxStyleRegistry.putValue('customPerimeter', mxPerimeter.CustomPerimeter);
+	 * (end)
+	 * 
+	 * The custom perimeter above can now be used in a specific vertex as follows:
+	 * 
+	 * (code)
+	 * model.setStyle(vertex, 'perimeter=customPerimeter');
+	 * (end)
+	 * 
+	 * Note that the key of the <mxStyleRegistry> entry for the function should
+	 * be used in string values, unless <mxGraphView.allowEval> is true, in
+	 * which case you can also use mxPerimeter.CustomPerimeter for the value in
+	 * the cell style above.
+	 * 
+	 * Or it can be used for all vertices in the graph as follows:
+	 * 
+	 * (code)
+	 * var style = graph.getStylesheet().getDefaultVertexStyle();
+	 * style[mxConstants.STYLE_PERIMETER] = mxPerimeter.CustomPerimeter;
+	 * (end)
+	 * 
+	 * Note that the object can be used directly when programmatically setting
+	 * the value, but the key in the <mxStyleRegistry> should be used when
+	 * setting the value via a key, value pair in a cell style.
+	 * 
+	 * The parameters are explained in <RectanglePerimeter>.
+	 * 
+	 * Function: RectanglePerimeter
+	 * 
+	 * Describes a rectangular perimeter for the given bounds.
+	 *
+	 * Parameters:
+	 * 
+	 * bounds - <mxRectangle> that represents the absolute bounds of the
+	 * vertex.
+	 * vertex - <mxCellState> that represents the vertex.
+	 * next - <mxPoint> that represents the nearest neighbour point on the
+	 * given edge.
+	 * orthogonal - Boolean that specifies if the orthogonal projection onto
+	 * the perimeter should be returned. If this is false then the intersection
+	 * of the perimeter and the line between the next and the center point is
+	 * returned.
+	 */
+	RectanglePerimeter: function (bounds, vertex, next, orthogonal)
+	{
+		var cx = bounds.getCenterX();
+		var cy = bounds.getCenterY();
+		var dx = next.x - cx;
+		var dy = next.y - cy;
+		var alpha = Math.atan2(dy, dx);
+		var p = new mxPoint(0, 0);
+		var pi = Math.PI;
+		var pi2 = Math.PI/2;
+		var beta = pi2 - alpha;
+		var t = Math.atan2(bounds.height, bounds.width);
+		
+		if (alpha < -pi + t || alpha > pi - t)
+		{
+			// Left edge
+			p.x = bounds.x;
+			p.y = cy - bounds.width * Math.tan(alpha) / 2;
+		}
+		else if (alpha < -t)
+		{
+			// Top Edge
+			p.y = bounds.y;
+			p.x = cx - bounds.height * Math.tan(beta) / 2;
+		}
+		else if (alpha < t)
+		{
+			// Right Edge
+			p.x = bounds.x + bounds.width;
+			p.y = cy + bounds.width * Math.tan(alpha) / 2;
+		}
+		else
+		{
+			// Bottom Edge
+			p.y = bounds.y + bounds.height;
+			p.x = cx + bounds.height * Math.tan(beta) / 2;
+		}
+		
+		if (orthogonal)
+		{
+			if (next.x >= bounds.x &&
+				next.x <= bounds.x + bounds.width)
+			{
+				p.x = next.x;
+			}
+			else if (next.y >= bounds.y &&
+					   next.y <= bounds.y + bounds.height)
+			{
+				p.y = next.y;
+			}
+			if (next.x < bounds.x)
+			{
+				p.x = bounds.x;
+			}
+			else if (next.x > bounds.x + bounds.width)
+			{
+				p.x = bounds.x + bounds.width;
+			}
+			if (next.y < bounds.y)
+			{
+				p.y = bounds.y;
+			}
+			else if (next.y > bounds.y + bounds.height)
+			{
+				p.y = bounds.y + bounds.height;
+			}
+		}
+		
+		return p;
+	},
+
+	/**
+	 * Function: EllipsePerimeter
+	 * 
+	 * Describes an elliptic perimeter. See <RectanglePerimeter>
+	 * for a description of the parameters.
+	 */
+	EllipsePerimeter: function (bounds, vertex, next, orthogonal)
+	{
+		var x = bounds.x;
+		var y = bounds.y;
+		var a = bounds.width / 2;
+		var b = bounds.height / 2;
+		var cx = x + a;
+		var cy = y + b;
+		var px = next.x;
+		var py = next.y;
+		
+		// Calculates straight line equation through
+		// point and ellipse center y = d * x + h
+		var dx = parseInt(px - cx);
+		var dy = parseInt(py - cy);
+		
+		if (dx == 0 && dy != 0)
+		{
+			return new mxPoint(cx, cy + b * dy / Math.abs(dy));
+		}
+		else if (dx == 0 && dy == 0)
+		{
+			return new mxPoint(px, py);
+		}
+
+		if (orthogonal)
+		{
+			if (py >= y && py <= y + bounds.height)
+			{
+				var ty = py - cy;
+				var tx = Math.sqrt(a*a*(1-(ty*ty)/(b*b))) || 0;
+				
+				if (px <= x)
+				{
+					tx = -tx;
+				}
+				
+				return new mxPoint(cx+tx, py);
+			}
+			
+			if (px >= x && px <= x + bounds.width)
+			{
+				var tx = px - cx;
+				var ty = Math.sqrt(b*b*(1-(tx*tx)/(a*a))) || 0;
+				
+				if (py <= y)
+				{
+					ty = -ty;	
+				}
+				
+				return new mxPoint(px, cy+ty);
+			}
+		}
+		
+		// Calculates intersection
+		var d = dy / dx;
+		var h = cy - d * cx;
+		var e = a * a * d * d + b * b;
+		var f = -2 * cx * e;
+		var g = a * a * d * d * cx * cx +
+				b * b * cx * cx -
+				a * a * b * b;
+		var det = Math.sqrt(f * f - 4 * e * g);
+		
+		// Two solutions (perimeter points)
+		var xout1 = (-f + det) / (2 * e);
+		var xout2 = (-f - det) / (2 * e);
+		var yout1 = d * xout1 + h;
+		var yout2 = d * xout2 + h;
+		var dist1 = Math.sqrt(Math.pow((xout1 - px), 2)
+					+ Math.pow((yout1 - py), 2));
+		var dist2 = Math.sqrt(Math.pow((xout2 - px), 2)
+					+ Math.pow((yout2 - py), 2));
+					
+		// Correct solution
+		var xout = 0;
+		var yout = 0;
+		
+		if (dist1 < dist2)
+		{
+			xout = xout1;
+			yout = yout1;
+		}
+		else
+		{
+			xout = xout2;
+			yout = yout2;
+		}
+		
+		return new mxPoint(xout, yout);
+	},
+
+	/**
+	 * Function: RhombusPerimeter
+	 * 
+	 * Describes a rhombus (aka diamond) perimeter. See <RectanglePerimeter>
+	 * for a description of the parameters.
+	 */
+	RhombusPerimeter: function (bounds, vertex, next, orthogonal)
+	{
+		var x = bounds.x;
+		var y = bounds.y;
+		var w = bounds.width;
+		var h = bounds.height;
+		
+		var cx = x + w / 2;
+		var cy = y + h / 2;
+
+		var px = next.x;
+		var py = next.y;
+
+		// Special case for intersecting the diamond's corners
+		if (cx == px)
+		{
+			if (cy > py)
+			{
+				return new mxPoint(cx, y); // top
+			}
+			else
+			{
+				return new mxPoint(cx, y + h); // bottom
+			}
+		}
+		else if (cy == py)
+		{
+			if (cx > px)
+			{
+				return new mxPoint(x, cy); // left
+			}
+			else
+			{
+				return new mxPoint(x + w, cy); // right
+			}
+		}
+		
+		var tx = cx;
+		var ty = cy;
+		
+		if (orthogonal)
+		{
+			if (px >= x && px <= x + w)
+			{
+				tx = px;
+			}
+			else if (py >= y && py <= y + h)
+			{
+				ty = py;
+			}
+		}
+		
+		// In which quadrant will the intersection be?
+		// set the slope and offset of the border line accordingly
+		if (px < cx)
+		{
+			if (py < cy)
+			{
+				return mxUtils.intersection(px, py, tx, ty, cx, y, x, cy);
+			}
+			else
+			{
+				return mxUtils.intersection(px, py, tx, ty, cx, y + h, x, cy);
+			}
+		}
+		else if (py < cy)
+		{
+			return mxUtils.intersection(px, py, tx, ty, cx, y, x + w, cy);
+		}
+		else
+		{
+			return mxUtils.intersection(px, py, tx, ty, cx, y + h, x + w, cy);
+		}
+	},
+	
+	/**
+	 * Function: TrianglePerimeter
+	 * 
+	 * Describes a triangle perimeter. See <RectanglePerimeter>
+	 * for a description of the parameters.
+	 */
+	TrianglePerimeter: function (bounds, vertex, next, orthogonal)
+	{
+		var direction = (vertex != null) ?
+			vertex.style[mxConstants.STYLE_DIRECTION] : null;
+		var vertical = direction == mxConstants.DIRECTION_NORTH ||
+			direction == mxConstants.DIRECTION_SOUTH;
+
+		var x = bounds.x;
+		var y = bounds.y;
+		var w = bounds.width;
+		var h = bounds.height;
+		
+		var cx = x + w / 2;
+		var cy = y + h / 2;
+		
+		var start = new mxPoint(x, y);
+		var corner = new mxPoint(x + w, cy);
+		var end = new mxPoint(x, y + h);
+		
+		if (direction == mxConstants.DIRECTION_NORTH)
+		{
+			start = end;
+			corner = new mxPoint(cx, y);
+			end = new mxPoint(x + w, y + h);
+		}
+		else if (direction == mxConstants.DIRECTION_SOUTH)
+		{
+			corner = new mxPoint(cx, y + h);
+			end = new mxPoint(x + w, y);
+		}
+		else if (direction == mxConstants.DIRECTION_WEST)
+		{
+			start = new mxPoint(x + w, y);
+			corner = new mxPoint(x, cy);
+			end = new mxPoint(x + w, y + h);
+		}
+
+		var dx = next.x - cx;
+		var dy = next.y - cy;
+
+		var alpha = (vertical) ? Math.atan2(dx, dy) : Math.atan2(dy, dx);
+		var t = (vertical) ? Math.atan2(w, h) : Math.atan2(h, w);
+		
+		var base = false;
+		
+		if (direction == mxConstants.DIRECTION_NORTH ||
+			direction == mxConstants.DIRECTION_WEST)
+		{
+			base = alpha > -t && alpha < t;
+		}
+		else
+		{
+			base = alpha < -Math.PI + t || alpha > Math.PI - t;	
+		}
+
+		var result = null;			
+
+		if (base)
+		{
+			if (orthogonal && ((vertical && next.x >= start.x && next.x <= end.x) ||
+				(!vertical && next.y >= start.y && next.y <= end.y)))
+			{
+				if (vertical)
+				{
+					result = new mxPoint(next.x, start.y);
+				}
+				else
+				{
+					result = new mxPoint(start.x, next.y);
+				}
+			}
+			else
+			{
+				if (direction == mxConstants.DIRECTION_NORTH)
+				{
+					result = new mxPoint(x + w / 2 + h * Math.tan(alpha) / 2,
+						y + h);
+				}
+				else if (direction == mxConstants.DIRECTION_SOUTH)
+				{
+					result = new mxPoint(x + w / 2 - h * Math.tan(alpha) / 2,
+						y);
+				}
+				else if (direction == mxConstants.DIRECTION_WEST)
+				{
+					result = new mxPoint(x + w, y + h / 2 +
+						w * Math.tan(alpha) / 2);
+				}
+				else
+				{
+					result = new mxPoint(x, y + h / 2 -
+						w * Math.tan(alpha) / 2);
+				}
+			}
+		}
+		else
+		{
+			if (orthogonal)
+			{
+				var pt = new mxPoint(cx, cy);
+		
+				if (next.y >= y && next.y <= y + h)
+				{
+					pt.x = (vertical) ? cx : (
+						(direction == mxConstants.DIRECTION_WEST) ?
+							x + w : x);
+					pt.y = next.y;
+				}
+				else if (next.x >= x && next.x <= x + w)
+				{
+					pt.x = next.x;
+					pt.y = (!vertical) ? cy : (
+						(direction == mxConstants.DIRECTION_NORTH) ?
+							y + h : y);
+				}
+				
+				// Compute angle
+				dx = next.x - pt.x;
+				dy = next.y - pt.y;
+				
+				cx = pt.x;
+				cy = pt.y;
+			}
+
+			if ((vertical && next.x <= x + w / 2) ||
+				(!vertical && next.y <= y + h / 2))
+			{
+				result = mxUtils.intersection(next.x, next.y, cx, cy,
+					start.x, start.y, corner.x, corner.y);
+			}
+			else
+			{
+				result = mxUtils.intersection(next.x, next.y, cx, cy,
+					corner.x, corner.y, end.x, end.y);
+			}
+		}
+		
+		if (result == null)
+		{
+			result = new mxPoint(cx, cy);
+		}
+		
+		return result;
+	},
+	
+	/**
+	 * Function: HexagonPerimeter
+	 * 
+	 * Describes a hexagon perimeter. See <RectanglePerimeter>
+	 * for a description of the parameters.
+	 */
+	HexagonPerimeter: function (bounds, vertex, next, orthogonal)
+	{
+		var x = bounds.x;
+		var y = bounds.y;
+		var w = bounds.width;
+		var h = bounds.height;
+
+		var cx = bounds.getCenterX();
+		var cy = bounds.getCenterY();
+		var px = next.x;
+		var py = next.y;
+		var dx = px - cx;
+		var dy = py - cy;
+		var alpha = -Math.atan2(dy, dx);
+		var pi = Math.PI;
+		var pi2 = Math.PI / 2;
+
+		var result = new mxPoint(cx, cy);
+
+		var direction = (vertex != null) ? mxUtils.getValue(
+				vertex.style, mxConstants.STYLE_DIRECTION,
+				mxConstants.DIRECTION_EAST) : mxConstants.DIRECTION_EAST;
+		var vertical = direction == mxConstants.DIRECTION_NORTH
+				|| direction == mxConstants.DIRECTION_SOUTH;
+		var a = new mxPoint();
+		var b = new mxPoint();
+
+		//Only consider corrects quadrants for the orthogonal case.
+		if ((px < x) && (py < y) || (px < x) && (py > y + h)
+				|| (px > x + w) && (py < y) || (px > x + w) && (py > y + h))
+		{
+			orthogonal = false;
+		}
+
+		if (orthogonal)
+		{
+			if (vertical)
+			{
+				//Special cases where intersects with hexagon corners
+				if (px == cx)
+				{
+					if (py <= y)
+					{
+						return new mxPoint(cx, y);
+					}
+					else if (py >= y + h)
+					{
+						return new mxPoint(cx, y + h);
+					}
+				}
+				else if (px < x)
+				{
+					if (py == y + h / 4)
+					{
+						return new mxPoint(x, y + h / 4);
+					}
+					else if (py == y + 3 * h / 4)
+					{
+						return new mxPoint(x, y + 3 * h / 4);
+					}
+				}
+				else if (px > x + w)
+				{
+					if (py == y + h / 4)
+					{
+						return new mxPoint(x + w, y + h / 4);
+					}
+					else if (py == y + 3 * h / 4)
+					{
+						return new mxPoint(x + w, y + 3 * h / 4);
+					}
+				}
+				else if (px == x)
+				{
+					if (py < cy)
+					{
+						return new mxPoint(x, y + h / 4);
+					}
+					else if (py > cy)
+					{
+						return new mxPoint(x, y + 3 * h / 4);
+					}
+				}
+				else if (px == x + w)
+				{
+					if (py < cy)
+					{
+						return new mxPoint(x + w, y + h / 4);
+					}
+					else if (py > cy)
+					{
+						return new mxPoint(x + w, y + 3 * h / 4);
+					}
+				}
+				if (py == y)
+				{
+					return new mxPoint(cx, y);
+				}
+				else if (py == y + h)
+				{
+					return new mxPoint(cx, y + h);
+				}
+
+				if (px < cx)
+				{
+					if ((py > y + h / 4) && (py < y + 3 * h / 4))
+					{
+						a = new mxPoint(x, y);
+						b = new mxPoint(x, y + h);
+					}
+					else if (py < y + h / 4)
+					{
+						a = new mxPoint(x - Math.floor(0.5 * w), y
+								+ Math.floor(0.5 * h));
+						b = new mxPoint(x + w, y - Math.floor(0.25 * h));
+					}
+					else if (py > y + 3 * h / 4)
+					{
+						a = new mxPoint(x - Math.floor(0.5 * w), y
+								+ Math.floor(0.5 * h));
+						b = new mxPoint(x + w, y + Math.floor(1.25 * h));
+					}
+				}
+				else if (px > cx)
+				{
+					if ((py > y + h / 4) && (py < y + 3 * h / 4))
+					{
+						a = new mxPoint(x + w, y);
+						b = new mxPoint(x + w, y + h);
+					}
+					else if (py < y + h / 4)
+					{
+						a = new mxPoint(x, y - Math.floor(0.25 * h));
+						b = new mxPoint(x + Math.floor(1.5 * w), y
+								+ Math.floor(0.5 * h));
+					}
+					else if (py > y + 3 * h / 4)
+					{
+						a = new mxPoint(x + Math.floor(1.5 * w), y
+								+ Math.floor(0.5 * h));
+						b = new mxPoint(x, y + Math.floor(1.25 * h));
+					}
+				}
+
+			}
+			else
+			{
+				//Special cases where intersects with hexagon corners
+				if (py == cy)
+				{
+					if (px <= x)
+					{
+						return new mxPoint(x, y + h / 2);
+					}
+					else if (px >= x + w)
+					{
+						return new mxPoint(x + w, y + h / 2);
+					}
+				}
+				else if (py < y)
+				{
+					if (px == x + w / 4)
+					{
+						return new mxPoint(x + w / 4, y);
+					}
+					else if (px == x + 3 * w / 4)
+					{
+						return new mxPoint(x + 3 * w / 4, y);
+					}
+				}
+				else if (py > y + h)
+				{
+					if (px == x + w / 4)
+					{
+						return new mxPoint(x + w / 4, y + h);
+					}
+					else if (px == x + 3 * w / 4)
+					{
+						return new mxPoint(x + 3 * w / 4, y + h);
+					}
+				}
+				else if (py == y)
+				{
+					if (px < cx)
+					{
+						return new mxPoint(x + w / 4, y);
+					}
+					else if (px > cx)
+					{
+						return new mxPoint(x + 3 * w / 4, y);
+					}
+				}
+				else if (py == y + h)
+				{
+					if (px < cx)
+					{
+						return new mxPoint(x + w / 4, y + h);
+					}
+					else if (py > cy)
+					{
+						return new mxPoint(x + 3 * w / 4, y + h);
+					}
+				}
+				if (px == x)
+				{
+					return new mxPoint(x, cy);
+				}
+				else if (px == x + w)
+				{
+					return new mxPoint(x + w, cy);
+				}
+
+				if (py < cy)
+				{
+					if ((px > x + w / 4) && (px < x + 3 * w / 4))
+					{
+						a = new mxPoint(x, y);
+						b = new mxPoint(x + w, y);
+					}
+					else if (px < x + w / 4)
+					{
+						a = new mxPoint(x - Math.floor(0.25 * w), y + h);
+						b = new mxPoint(x + Math.floor(0.5 * w), y
+								- Math.floor(0.5 * h));
+					}
+					else if (px > x + 3 * w / 4)
+					{
+						a = new mxPoint(x + Math.floor(0.5 * w), y
+								- Math.floor(0.5 * h));
+						b = new mxPoint(x + Math.floor(1.25 * w), y + h);
+					}
+				}
+				else if (py > cy)
+				{
+					if ((px > x + w / 4) && (px < x + 3 * w / 4))
+					{
+						a = new mxPoint(x, y + h);
+						b = new mxPoint(x + w, y + h);
+					}
+					else if (px < x + w / 4)
+					{
+						a = new mxPoint(x - Math.floor(0.25 * w), y);
+						b = new mxPoint(x + Math.floor(0.5 * w), y
+								+ Math.floor(1.5 * h));
+					}
+					else if (px > x + 3 * w / 4)
+					{
+						a = new mxPoint(x + Math.floor(0.5 * w), y
+								+ Math.floor(1.5 * h));
+						b = new mxPoint(x + Math.floor(1.25 * w), y);
+					}
+				}
+			}
+
+			var tx = cx;
+			var ty = cy;
+
+			if (px >= x && px <= x + w)
+			{
+				tx = px;
+				
+				if (py < cy)
+				{
+					ty = y + h;
+				}
+				else
+				{
+					ty = y;
+				}
+			}
+			else if (py >= y && py <= y + h)
+			{
+				ty = py;
+				
+				if (px < cx)
+				{
+					tx = x + w;
+				}
+				else
+				{
+					tx = x;
+				}
+			}
+
+			result = mxUtils.intersection(tx, ty, next.x, next.y, a.x, a.y, b.x, b.y);
+		}
+		else
+		{
+			if (vertical)
+			{
+				var beta = Math.atan2(h / 4, w / 2);
+
+				//Special cases where intersects with hexagon corners
+				if (alpha == beta)
+				{
+					return new mxPoint(x + w, y + Math.floor(0.25 * h));
+				}
+				else if (alpha == pi2)
+				{
+					return new mxPoint(x + Math.floor(0.5 * w), y);
+				}
+				else if (alpha == (pi - beta))
+				{
+					return new mxPoint(x, y + Math.floor(0.25 * h));
+				}
+				else if (alpha == -beta)
+				{
+					return new mxPoint(x + w, y + Math.floor(0.75 * h));
+				}
+				else if (alpha == (-pi2))
+				{
+					return new mxPoint(x + Math.floor(0.5 * w), y + h);
+				}
+				else if (alpha == (-pi + beta))
+				{
+					return new mxPoint(x, y + Math.floor(0.75 * h));
+				}
+
+				if ((alpha < beta) && (alpha > -beta))
+				{
+					a = new mxPoint(x + w, y);
+					b = new mxPoint(x + w, y + h);
+				}
+				else if ((alpha > beta) && (alpha < pi2))
+				{
+					a = new mxPoint(x, y - Math.floor(0.25 * h));
+					b = new mxPoint(x + Math.floor(1.5 * w), y
+							+ Math.floor(0.5 * h));
+				}
+				else if ((alpha > pi2) && (alpha < (pi - beta)))
+				{
+					a = new mxPoint(x - Math.floor(0.5 * w), y
+							+ Math.floor(0.5 * h));
+					b = new mxPoint(x + w, y - Math.floor(0.25 * h));
+				}
+				else if (((alpha > (pi - beta)) && (alpha <= pi))
+						|| ((alpha < (-pi + beta)) && (alpha >= -pi)))
+				{
+					a = new mxPoint(x, y);
+					b = new mxPoint(x, y + h);
+				}
+				else if ((alpha < -beta) && (alpha > -pi2))
+				{
+					a = new mxPoint(x + Math.floor(1.5 * w), y
+							+ Math.floor(0.5 * h));
+					b = new mxPoint(x, y + Math.floor(1.25 * h));
+				}
+				else if ((alpha < -pi2) && (alpha > (-pi + beta)))
+				{
+					a = new mxPoint(x - Math.floor(0.5 * w), y
+							+ Math.floor(0.5 * h));
+					b = new mxPoint(x + w, y + Math.floor(1.25 * h));
+				}
+			}
+			else
+			{
+				var beta = Math.atan2(h / 2, w / 4);
+
+				//Special cases where intersects with hexagon corners
+				if (alpha == beta)
+				{
+					return new mxPoint(x + Math.floor(0.75 * w), y);
+				}
+				else if (alpha == (pi - beta))
+				{
+					return new mxPoint(x + Math.floor(0.25 * w), y);
+				}
+				else if ((alpha == pi) || (alpha == -pi))
+				{
+					return new mxPoint(x, y + Math.floor(0.5 * h));
+				}
+				else if (alpha == 0)
+				{
+					return new mxPoint(x + w, y + Math.floor(0.5 * h));
+				}
+				else if (alpha == -beta)
+				{
+					return new mxPoint(x + Math.floor(0.75 * w), y + h);
+				}
+				else if (alpha == (-pi + beta))
+				{
+					return new mxPoint(x + Math.floor(0.25 * w), y + h);
+				}
+
+				if ((alpha > 0) && (alpha < beta))
+				{
+					a = new mxPoint(x + Math.floor(0.5 * w), y
+							- Math.floor(0.5 * h));
+					b = new mxPoint(x + Math.floor(1.25 * w), y + h);
+				}
+				else if ((alpha > beta) && (alpha < (pi - beta)))
+				{
+					a = new mxPoint(x, y);
+					b = new mxPoint(x + w, y);
+				}
+				else if ((alpha > (pi - beta)) && (alpha < pi))
+				{
+					a = new mxPoint(x - Math.floor(0.25 * w), y + h);
+					b = new mxPoint(x + Math.floor(0.5 * w), y
+							- Math.floor(0.5 * h));
+				}
+				else if ((alpha < 0) && (alpha > -beta))
+				{
+					a = new mxPoint(x + Math.floor(0.5 * w), y
+							+ Math.floor(1.5 * h));
+					b = new mxPoint(x + Math.floor(1.25 * w), y);
+				}
+				else if ((alpha < -beta) && (alpha > (-pi + beta)))
+				{
+					a = new mxPoint(x, y + h);
+					b = new mxPoint(x + w, y + h);
+				}
+				else if ((alpha < (-pi + beta)) && (alpha > -pi))
+				{
+					a = new mxPoint(x - Math.floor(0.25 * w), y);
+					b = new mxPoint(x + Math.floor(0.5 * w), y
+							+ Math.floor(1.5 * h));
+				}
+			}
+
+			result = mxUtils.intersection(cx, cy, next.x, next.y, a.x, a.y, b.x, b.y);
+		}
+		
+		if (result == null)
+		{
+			return new mxPoint(cx, cy);
+		}
+		
+		return result;
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxPrintPreview
+ * 
+ * Implements printing of a diagram across multiple pages. The following opens
+ * a print preview for an existing graph:
+ * 
+ * (code)
+ * var preview = new mxPrintPreview(graph);
+ * preview.open();
+ * (end)
+ * 
+ * Use <mxUtils.getScaleForPageCount> as follows in order to print the graph
+ * across a given number of pages:
+ * 
+ * (code)
+ * var pageCount = mxUtils.prompt('Enter page count', '1');
+ * 
+ * if (pageCount != null)
+ * {
+ *   var scale = mxUtils.getScaleForPageCount(pageCount, graph);
+ *   var preview = new mxPrintPreview(graph, scale);
+ *   preview.open();
+ * }
+ * (end)
+ * 
+ * Additional pages:
+ * 
+ * To add additional pages before and after the output, <getCoverPages> and
+ * <getAppendices> can be used, respectively.
+ * 
+ * (code)
+ * var preview = new mxPrintPreview(graph, 1);
+ * 
+ * preview.getCoverPages = function(w, h)
+ * {
+ *   return [this.renderPage(w, h, 0, 0, mxUtils.bind(this, function(div)
+ *   {
+ *     div.innerHTML = '<div style="position:relative;margin:4px;">Cover Page</p>'
+ *   }))];
+ * };
+ * 
+ * preview.getAppendices = function(w, h)
+ * {
+ *   return [this.renderPage(w, h, 0, 0, mxUtils.bind(this, function(div)
+ *   {
+ *     div.innerHTML = '<div style="position:relative;margin:4px;">Appendix</p>'
+ *   }))];
+ * };
+ * 
+ * preview.open();
+ * (end)
+ * 
+ * CSS:
+ * 
+ * The CSS from the original page is not carried over to the print preview.
+ * To add CSS to the page, use the css argument in the <open> function or
+ * override <writeHead> to add the respective link tags as follows:
+ * 
+ * (code)
+ * var writeHead = preview.writeHead;
+ * preview.writeHead = function(doc, css)
+ * {
+ *   writeHead.apply(this, arguments);
+ *   doc.writeln('<link rel="stylesheet" type="text/css" href="style.css">');
+ * };
+ * (end)
+ * 
+ * Padding:
+ * 
+ * To add a padding to the page in the preview (but not the print output), use
+ * the following code:
+ * 
+ * (code)
+ * preview.writeHead = function(doc)
+ * {
+ *   writeHead.apply(this, arguments);
+ *   
+ *   doc.writeln('<style type="text/css">');
+ *   doc.writeln('@media screen {');
+ *   doc.writeln('  body > div { padding-top:30px;padding-left:40px;box-sizing:content-box; }');
+ *   doc.writeln('}');
+ *   doc.writeln('</style>');
+ * };
+ * (end)
+ * 
+ * Headers:
+ * 
+ * Apart from setting the title argument in the mxPrintPreview constructor you
+ * can override <renderPage> as follows to add a header to any page:
+ * 
+ * (code)
+ * var oldRenderPage = mxPrintPreview.prototype.renderPage;
+ * mxPrintPreview.prototype.renderPage = function(w, h, x, y, content, pageNumber)
+ * {
+ *   var div = oldRenderPage.apply(this, arguments);
+ *   
+ *   var header = document.createElement('div');
+ *   header.style.position = 'absolute';
+ *   header.style.top = '0px';
+ *   header.style.width = '100%';
+ *   header.style.textAlign = 'right';
+ *   mxUtils.write(header, 'Your header here');
+ *   div.firstChild.appendChild(header);
+ *   
+ *   return div;
+ * };
+ * (end)
+ * 
+ * The pageNumber argument contains the number of the current page, starting at
+ * 1. To display a header on the first page only, check pageNumber and add a
+ * vertical offset in the constructor call for the height of the header.
+ * 
+ * Page Format:
+ * 
+ * For landscape printing, use <mxConstants.PAGE_FORMAT_A4_LANDSCAPE> as
+ * the pageFormat in <mxUtils.getScaleForPageCount> and <mxPrintPreview>.
+ * Keep in mind that one can not set the defaults for the print dialog
+ * of the operating system from JavaScript so the user must manually choose
+ * a page format that matches this setting.
+ * 
+ * You can try passing the following CSS directive to <open> to set the
+ * page format in the print dialog to landscape. However, this CSS
+ * directive seems to be ignored in most major browsers, including IE.
+ * 
+ * (code)
+ * @page {
+ *   size: landscape;
+ * }
+ * (end)
+ * 
+ * Note that the print preview behaves differently in IE when used from the
+ * filesystem or via HTTP so printing should always be tested via HTTP.
+ * 
+ * If you are using a DOCTYPE in the source page you can override <getDoctype>
+ * and provide the same DOCTYPE for the print preview if required. Here is
+ * an example for IE8 standards mode.
+ * 
+ * (code)
+ * var preview = new mxPrintPreview(graph);
+ * preview.getDoctype = function()
+ * {
+ *   return '<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=5,IE=8" ><![endif]-->';
+ * };
+ * preview.open();
+ * (end)
+ * 
+ * Constructor: mxPrintPreview
+ *
+ * Constructs a new print preview for the given parameters.
+ * 
+ * Parameters:
+ * 
+ * graph - <mxGraph> to be previewed.
+ * scale - Optional scale of the output. Default is 1 / <mxGraph.pageScale>.
+ * border - Border in pixels along each side of every page. Note that the
+ * actual print function in the browser will add another border for
+ * printing.
+ * pageFormat - <mxRectangle> that specifies the page format (in pixels).
+ * This should match the page format of the printer. Default uses the
+ * <mxGraph.pageFormat> of the given graph.
+ * x0 - Optional left offset of the output. Default is 0.
+ * y0 - Optional top offset of the output. Default is 0.
+ * borderColor - Optional color of the page border. Default is no border.
+ * Note that a border is sometimes useful to highlight the printed page
+ * border in the print preview of the browser.
+ * title - Optional string that is used for the window title. Default
+ * is 'Printer-friendly version'.
+ * pageSelector - Optional boolean that specifies if the page selector
+ * should appear in the window with the print preview. Default is true.
+ */
+function mxPrintPreview(graph, scale, pageFormat, border, x0, y0, borderColor, title, pageSelector)
+{
+	this.graph = graph;
+	this.scale = (scale != null) ? scale : 1 / graph.pageScale;
+	this.border = (border != null) ? border : 0;
+	this.pageFormat = mxRectangle.fromRectangle((pageFormat != null) ? pageFormat : graph.pageFormat);
+	this.title = (title != null) ? title : 'Printer-friendly version';
+	this.x0 = (x0 != null) ? x0 : 0;
+	this.y0 = (y0 != null) ? y0 : 0;
+	this.borderColor = borderColor;
+	this.pageSelector = (pageSelector != null) ? pageSelector : true;
+};
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the <mxGraph> that should be previewed.
+ */
+mxPrintPreview.prototype.graph = null;
+
+/**
+ * Variable: pageFormat
+ *
+ * Holds the <mxRectangle> that defines the page format.
+ */
+mxPrintPreview.prototype.pageFormat = null;
+
+/**
+ * Variable: scale
+ * 
+ * Holds the scale of the print preview.
+ */
+mxPrintPreview.prototype.scale = null;
+
+/**
+ * Variable: border
+ * 
+ * The border inset around each side of every page in the preview. This is set
+ * to 0 if autoOrigin is false.
+ */
+mxPrintPreview.prototype.border = 0;
+
+/**
+ * Variable: marginTop
+ * 
+ * The margin at the top of the page (number). Default is 0.
+ */
+mxPrintPreview.prototype.marginTop = 0;
+
+/**
+ * Variable: marginBottom
+ * 
+ * The margin at the bottom of the page (number). Default is 0.
+ */
+mxPrintPreview.prototype.marginBottom = 0;
+
+/**
+ * Variable: x0
+ * 
+ * Holds the horizontal offset of the output.
+ */
+mxPrintPreview.prototype.x0 = 0;
+
+/**
+ * Variable: y0
+ *
+ * Holds the vertical offset of the output.
+ */
+mxPrintPreview.prototype.y0 = 0;
+
+/**
+ * Variable: autoOrigin
+ * 
+ * Specifies if the origin should be automatically computed based on the top,
+ * left corner of the actual diagram contents. The required offset will be added
+ * to <x0> and <y0> in <open>. Default is true.
+ */
+mxPrintPreview.prototype.autoOrigin = true;
+
+/**
+ * Variable: printOverlays
+ * 
+ * Specifies if overlays should be printed. Default is false.
+ */
+mxPrintPreview.prototype.printOverlays = false;
+
+/**
+ * Variable: printControls
+ * 
+ * Specifies if controls (such as folding icons) should be printed. Default is
+ * false.
+ */
+mxPrintPreview.prototype.printControls = false;
+
+/**
+ * Variable: printBackgroundImage
+ * 
+ * Specifies if the background image should be printed. Default is false.
+ */
+mxPrintPreview.prototype.printBackgroundImage = false;
+
+/**
+ * Variable: backgroundColor
+ * 
+ * Holds the color value for the page background color. Default is #ffffff.
+ */
+mxPrintPreview.prototype.backgroundColor = '#ffffff';
+
+/**
+ * Variable: borderColor
+ * 
+ * Holds the color value for the page border.
+ */
+mxPrintPreview.prototype.borderColor = null;
+
+/**
+ * Variable: title
+ * 
+ * Holds the title of the preview window.
+ */
+mxPrintPreview.prototype.title = null;
+
+/**
+ * Variable: pageSelector
+ * 
+ * Boolean that specifies if the page selector should be
+ * displayed. Default is true.
+ */
+mxPrintPreview.prototype.pageSelector = null;
+
+/**
+ * Variable: wnd
+ * 
+ * Reference to the preview window.
+ */
+mxPrintPreview.prototype.wnd = null;
+
+/**
+ * Variable: targetWindow
+ * 
+ * Assign any window here to redirect the rendering in <open>.
+ */
+mxPrintPreview.prototype.targetWindow = null;
+
+/**
+ * Variable: pageCount
+ * 
+ * Holds the actual number of pages in the preview.
+ */
+mxPrintPreview.prototype.pageCount = 0;
+
+/**
+ * Variable: clipping
+ * 
+ * Specifies is clipping should be used to avoid creating too many cell states
+ * in large diagrams. The bounding box of the cells in the original diagram is
+ * used if this is enabled. Default is true.
+ */
+mxPrintPreview.prototype.clipping = true;
+
+/**
+ * Function: getWindow
+ * 
+ * Returns <wnd>.
+ */
+mxPrintPreview.prototype.getWindow = function()
+{
+	return this.wnd;
+};
+
+/**
+ * Function: getDocType
+ * 
+ * Returns the string that should go before the HTML tag in the print preview
+ * page. This implementation returns an X-UA meta tag for IE5 in quirks mode,
+ * IE8 in IE8 standards mode and edge in IE9 standards mode.
+ */
+mxPrintPreview.prototype.getDoctype = function()
+{
+	var dt = '';
+	
+	if (document.documentMode == 5)
+	{
+		dt = '<meta http-equiv="X-UA-Compatible" content="IE=5">';
+	}
+	else if (document.documentMode == 8)
+	{
+		dt = '<meta http-equiv="X-UA-Compatible" content="IE=8">';
+	}
+	else if (document.documentMode > 8)
+	{
+		// Comment needed to make standards doctype apply in IE
+		dt = '<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=edge"><![endif]-->';
+	}
+	
+	return dt;
+};
+
+/**
+ * Function: appendGraph
+ * 
+ * Adds the given graph to the existing print preview.
+ * 
+ * Parameters:
+ * 
+ * css - Optional CSS string to be used in the head section.
+ * targetWindow - Optional window that should be used for rendering. If
+ * this is specified then no HEAD tag, CSS and BODY tag will be written.
+ */
+mxPrintPreview.prototype.appendGraph = function(graph, scale, x0, y0, forcePageBreaks, keepOpen)
+{
+	this.graph = graph;
+	this.scale = (scale != null) ? scale : 1 / graph.pageScale;
+	this.x0 = x0;
+	this.y0 = y0;
+	this.open(null, null, forcePageBreaks, keepOpen);
+};
+
+/**
+ * Function: open
+ * 
+ * Shows the print preview window. The window is created here if it does
+ * not exist.
+ * 
+ * Parameters:
+ * 
+ * css - Optional CSS string to be used in the head section.
+ * targetWindow - Optional window that should be used for rendering. If
+ * this is specified then no HEAD tag, CSS and BODY tag will be written.
+ */
+mxPrintPreview.prototype.open = function(css, targetWindow, forcePageBreaks, keepOpen)
+{
+	// Closing the window while the page is being rendered may cause an
+	// exception in IE. This and any other exceptions are simply ignored.
+	var previousInitializeOverlay = this.graph.cellRenderer.initializeOverlay;
+	var div = null;
+	
+	try
+	{
+		// Temporarily overrides the method to redirect rendering of overlays
+		// to the draw pane so that they are visible in the printout
+		if (this.printOverlays)
+		{
+			this.graph.cellRenderer.initializeOverlay = function(state, overlay)
+			{
+				overlay.init(state.view.getDrawPane());
+			};
+		}
+		
+		if (this.printControls)
+		{
+			this.graph.cellRenderer.initControl = function(state, control, handleEvents, clickHandler)
+			{
+				control.dialect = state.view.graph.dialect;
+				control.init(state.view.getDrawPane());
+			};
+		}
+		
+		this.wnd = (targetWindow != null) ? targetWindow : this.wnd;
+		var isNewWindow = false;
+		
+		if (this.wnd == null)
+		{
+			isNewWindow = true;
+			this.wnd = window.open();
+		}
+		
+		var doc = this.wnd.document;
+		
+		if (isNewWindow)
+		{
+			var dt = this.getDoctype();
+			
+			if (dt != null && dt.length > 0)
+			{
+				doc.writeln(dt);
+			}
+			
+			if (mxClient.IS_VML)
+			{
+				doc.writeln('<html xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">');
+			}
+			else
+			{
+				if (document.compatMode === 'CSS1Compat')
+				{
+					doc.writeln('<!DOCTYPE html>');
+				}
+				
+				doc.writeln('<html>');
+			}
+			
+			doc.writeln('<head>');
+			this.writeHead(doc, css);
+			doc.writeln('</head>');
+			doc.writeln('<body class="mxPage">');
+		}
+
+		// Computes the horizontal and vertical page count
+		var bounds = this.graph.getGraphBounds().clone();
+		var currentScale = this.graph.getView().getScale();
+		var sc = currentScale / this.scale;
+		var tr = this.graph.getView().getTranslate();
+		
+		// Uses the absolute origin with no offset for all printing
+		if (!this.autoOrigin)
+		{
+			this.x0 -= tr.x * this.scale;
+			this.y0 -= tr.y * this.scale;
+			bounds.width += bounds.x;
+			bounds.height += bounds.y;
+			bounds.x = 0;
+			bounds.y = 0;
+			this.border = 0;
+		}
+		
+		// Store the available page area
+		var availableWidth = this.pageFormat.width - (this.border * 2);
+		var availableHeight = this.pageFormat.height - (this.border * 2);
+	
+		// Adds margins to page format
+		this.pageFormat.height += this.marginTop + this.marginBottom;
+
+		// Compute the unscaled, untranslated bounds to find
+		// the number of vertical and horizontal pages
+		bounds.width /= sc;
+		bounds.height /= sc;
+
+		var hpages = Math.max(1, Math.ceil((bounds.width + this.x0) / availableWidth));
+		var vpages = Math.max(1, Math.ceil((bounds.height + this.y0) / availableHeight));
+		this.pageCount = hpages * vpages;
+		
+		var writePageSelector = mxUtils.bind(this, function()
+		{
+			if (this.pageSelector && (vpages > 1 || hpages > 1))
+			{
+				var table = this.createPageSelector(vpages, hpages);
+				doc.body.appendChild(table);
+				
+				// Implements position: fixed in IE quirks mode
+				if (mxClient.IS_IE && doc.documentMode == null || doc.documentMode == 5 || doc.documentMode == 8 || doc.documentMode == 7)
+				{
+					table.style.position = 'absolute';
+					
+					var update = function()
+					{
+						table.style.top = ((doc.body.scrollTop || doc.documentElement.scrollTop) + 10) + 'px';
+					};
+					
+					mxEvent.addListener(this.wnd, 'scroll', function(evt)
+					{
+						update();
+					});
+					
+					mxEvent.addListener(this.wnd, 'resize', function(evt)
+					{
+						update();
+					});
+				}
+			}
+		});
+		
+		var addPage = mxUtils.bind(this, function(div, addBreak)
+		{
+			// Border of the DIV (aka page) inside the document
+			if (this.borderColor != null)
+			{
+				div.style.borderColor = this.borderColor;
+				div.style.borderStyle = 'solid';
+				div.style.borderWidth = '1px';
+			}
+			
+			// Needs to be assigned directly because IE doesn't support
+			// child selectors, eg. body > div { background: white; }
+			div.style.background = this.backgroundColor;
+			
+			if (forcePageBreaks || addBreak)
+			{
+				div.style.pageBreakAfter = 'always';
+			}
+
+			// NOTE: We are dealing with cross-window DOM here, which
+			// is a problem in IE, so we copy the HTML markup instead.
+			// The underlying problem is that the graph display markup
+			// creation (in mxShape, mxGraphView) is hardwired to using
+			// document.createElement and hence we must use this document
+			// to create the complete page and then copy it over to the
+			// new window.document. This can be fixed later by using the
+			// ownerDocument of the container in mxShape and mxGraphView.
+			if (isNewWindow && (mxClient.IS_IE || document.documentMode >= 11 || mxClient.IS_EDGE))
+			{
+				// For some obscure reason, removing the DIV from the
+				// parent before fetching its outerHTML has missing
+				// fillcolor properties and fill children, so the div
+				// must be removed afterwards to keep the fillcolors.
+				doc.writeln(div.outerHTML);
+				div.parentNode.removeChild(div);
+			}
+			else
+			{
+				div.parentNode.removeChild(div);
+				doc.body.appendChild(div);
+			}
+
+			if (forcePageBreaks || addBreak)
+			{
+				this.addPageBreak(doc);
+			}
+		});
+		
+		var cov = this.getCoverPages(this.pageFormat.width, this.pageFormat.height);
+		
+		if (cov != null)
+		{
+			for (var i = 0; i < cov.length; i++)
+			{
+				addPage(cov[i], true);
+			}
+		}
+		
+		var apx = this.getAppendices(this.pageFormat.width, this.pageFormat.height);
+		
+		// Appends each page to the page output for printing, making
+		// sure there will be a page break after each page (ie. div)
+		for (var i = 0; i < vpages; i++)
+		{
+			var dy = i * availableHeight / this.scale - this.y0 / this.scale +
+					(bounds.y - tr.y * currentScale) / currentScale;
+			
+			for (var j = 0; j < hpages; j++)
+			{
+				if (this.wnd == null)
+				{
+					return null;
+				}
+				
+				var dx = j * availableWidth / this.scale - this.x0 / this.scale +
+						(bounds.x - tr.x * currentScale) / currentScale;
+				var pageNum = i * hpages + j + 1;
+				var clip = new mxRectangle(dx, dy, availableWidth, availableHeight);
+				div = this.renderPage(this.pageFormat.width, this.pageFormat.height, 0, 0, mxUtils.bind(this, function(div)
+				{
+					this.addGraphFragment(-dx, -dy, this.scale, pageNum, div, clip);
+					
+					if (this.printBackgroundImage)
+					{
+						this.insertBackgroundImage(div, -dx, -dy);
+					}
+				}), pageNum);
+
+				// Gives the page a unique ID for later accessing the page
+				div.setAttribute('id', 'mxPage-'+pageNum);
+
+				addPage(div, apx != null || i < vpages - 1 || j < hpages - 1);
+			}
+		}
+
+		if (apx != null)
+		{
+			for (var i = 0; i < apx.length; i++)
+			{
+				addPage(apx[i], i < apx.length - 1);
+			}
+		}
+
+		if (isNewWindow && !keepOpen)
+		{
+			this.closeDocument();
+			writePageSelector();
+		}
+		
+		this.wnd.focus();
+	}
+	catch (e)
+	{
+		// Removes the DIV from the document in case of an error
+		if (div != null && div.parentNode != null)
+		{
+			div.parentNode.removeChild(div);
+		}
+	}
+	finally
+	{
+		this.graph.cellRenderer.initializeOverlay = previousInitializeOverlay;
+	}
+
+	return this.wnd;
+};
+
+/**
+ * Function: addPageBreak
+ * 
+ * Adds a page break to the given document.
+ */
+mxPrintPreview.prototype.addPageBreak = function(doc)
+{
+	var hr = doc.createElement('hr');
+	hr.className = 'mxPageBreak';
+	doc.body.appendChild(hr);
+};
+
+/**
+ * Function: closeDocument
+ * 
+ * Writes the closing tags for body and page after calling <writePostfix>.
+ */
+mxPrintPreview.prototype.closeDocument = function()
+{
+	if (this.wnd != null && this.wnd.document != null)
+	{
+		var doc = this.wnd.document;
+		
+		this.writePostfix(doc);
+		doc.writeln('</body>');
+		doc.writeln('</html>');
+		doc.close();
+		
+		// Removes all event handlers in the print output
+		mxEvent.release(doc.body);
+	}
+};
+
+/**
+ * Function: writeHead
+ * 
+ * Writes the HEAD section into the given document, without the opening
+ * and closing HEAD tags.
+ */
+mxPrintPreview.prototype.writeHead = function(doc, css)
+{
+	if (this.title != null)
+	{
+		doc.writeln('<title>' + this.title + '</title>');
+	}
+	
+	// Adds required namespaces
+	if (mxClient.IS_VML)
+	{
+		doc.writeln('<style type="text/css">v\\:*{behavior:url(#default#VML)}o\\:*{behavior:url(#default#VML)}</style>');
+	}
+
+	// Adds all required stylesheets
+	mxClient.link('stylesheet', mxClient.basePath + '/css/common.css', doc);
+
+	// Removes horizontal rules and page selector from print output
+	doc.writeln('<style type="text/css">');
+	doc.writeln('@media print {');
+	doc.writeln('  table.mxPageSelector { display: none; }');
+	doc.writeln('  hr.mxPageBreak { display: none; }');
+	doc.writeln('}');
+	doc.writeln('@media screen {');
+	
+	// NOTE: position: fixed is not supported in IE, so the page selector
+	// position (absolute) needs to be updated in IE (see below)
+	doc.writeln('  table.mxPageSelector { position: fixed; right: 10px; top: 10px;' +
+			'font-family: Arial; font-size:10pt; border: solid 1px darkgray;' +
+			'background: white; border-collapse:collapse; }');
+	doc.writeln('  table.mxPageSelector td { border: solid 1px gray; padding:4px; }');
+	doc.writeln('  body.mxPage { background: gray; }');
+	doc.writeln('}');
+	
+	if (css != null)
+	{
+		doc.writeln(css);
+	}
+	
+	doc.writeln('</style>');
+};
+
+/**
+ * Function: writePostfix
+ * 
+ * Called before closing the body of the page. This implementation is empty.
+ */
+mxPrintPreview.prototype.writePostfix = function(doc)
+{
+	// empty
+};
+
+/**
+ * Function: createPageSelector
+ * 
+ * Creates the page selector table.
+ */
+mxPrintPreview.prototype.createPageSelector = function(vpages, hpages)
+{
+	var doc = this.wnd.document;
+	var table = doc.createElement('table');
+	table.className = 'mxPageSelector';
+	table.setAttribute('border', '0');
+
+	var tbody = doc.createElement('tbody');
+	
+	for (var i = 0; i < vpages; i++)
+	{
+		var row = doc.createElement('tr');
+		
+		for (var j = 0; j < hpages; j++)
+		{
+			var pageNum = i * hpages + j + 1;
+			var cell = doc.createElement('td');
+			var a = doc.createElement('a');
+			a.setAttribute('href', '#mxPage-' + pageNum);
+
+			// Workaround for FF where the anchor is appended to the URL of the original document
+			if (mxClient.IS_NS && !mxClient.IS_SF && !mxClient.IS_GC)
+			{
+				var js = 'var page = document.getElementById(\'mxPage-' + pageNum + '\');page.scrollIntoView(true);event.preventDefault();';
+				a.setAttribute('onclick', js);
+			}
+			
+			mxUtils.write(a, pageNum, doc);
+			cell.appendChild(a);
+			row.appendChild(cell);
+		}
+		
+		tbody.appendChild(row);
+	}
+	
+	table.appendChild(tbody);
+	
+	return table;
+};
+
+/**
+ * Function: renderPage
+ * 
+ * Creates a DIV that prints a single page of the given
+ * graph using the given scale and returns the DIV that
+ * represents the page.
+ * 
+ * Parameters:
+ * 
+ * w - Width of the page in pixels.
+ * h - Height of the page in pixels.
+ * dx - Optional horizontal page offset in pixels (used internally).
+ * dy - Optional vertical page offset in pixels (used internally).
+ * content - Callback that adds the HTML content to the inner div of a page.
+ * Takes the inner div as the argument.
+ * pageNumber - Integer representing the page number.
+ */
+mxPrintPreview.prototype.renderPage = function(w, h, dx, dy, content, pageNumber)
+{
+	var doc = this.wnd.document;
+	var div = document.createElement('div');
+	var arg = null;
+
+	try
+	{
+		// Workaround for ignored clipping in IE 9 standards
+		// when printing with page breaks and HTML labels.
+		if (dx != 0 || dy != 0)
+		{
+			div.style.position = 'relative';
+			div.style.width = w + 'px';
+			div.style.height = h + 'px';
+			div.style.pageBreakInside = 'avoid';
+			
+			var innerDiv = document.createElement('div');
+			innerDiv.style.position = 'relative';
+			innerDiv.style.top = this.border + 'px';
+			innerDiv.style.left = this.border + 'px';
+			innerDiv.style.width = (w - 2 * this.border) + 'px';
+			innerDiv.style.height = (h - 2 * this.border) + 'px';
+			innerDiv.style.overflow = 'hidden';
+			
+			var viewport = document.createElement('div');
+			viewport.style.position = 'relative';
+			viewport.style.marginLeft = dx + 'px';
+			viewport.style.marginTop = dy + 'px';
+
+			// FIXME: IE8 standards output problems
+			if (doc.documentMode == 8)
+			{
+				innerDiv.style.position = 'absolute';
+				viewport.style.position = 'absolute';
+			}
+		
+			if (doc.documentMode == 10)
+			{
+				viewport.style.width = '100%';
+				viewport.style.height = '100%';
+			}
+			
+			innerDiv.appendChild(viewport);
+			div.appendChild(innerDiv);
+			document.body.appendChild(div);
+			arg = viewport;
+		}
+		// FIXME: IE10/11 too many pages
+		else
+		{
+			div.style.width = w + 'px';
+			div.style.height = h + 'px';
+			div.style.overflow = 'hidden';
+			div.style.pageBreakInside = 'avoid';
+			
+			// IE8 uses above branch currently
+			if (doc.documentMode == 8)
+			{
+				div.style.position = 'relative';
+			}
+			
+			var innerDiv = document.createElement('div');
+			innerDiv.style.width = (w - 2 * this.border) + 'px';
+			innerDiv.style.height = (h - 2 * this.border) + 'px';
+			innerDiv.style.overflow = 'hidden';
+
+			if (mxClient.IS_IE && (doc.documentMode == null || doc.documentMode == 5 || doc.documentMode == 8 || doc.documentMode == 7))
+			{
+				innerDiv.style.marginTop = this.border + 'px';
+				innerDiv.style.marginLeft = this.border + 'px';	
+			}
+			else
+			{
+				innerDiv.style.top = this.border + 'px';
+				innerDiv.style.left = this.border + 'px';
+			}
+	
+			if (this.graph.dialect == mxConstants.DIALECT_VML)
+			{
+				innerDiv.style.position = 'absolute';
+			}
+
+			div.appendChild(innerDiv);
+			document.body.appendChild(div);
+			arg = innerDiv;
+		}
+	}
+	catch (e)
+	{
+		div.parentNode.removeChild(div);
+		div = null;
+		
+		throw e;
+	}
+
+	content(arg);
+	 
+	return div;
+};
+
+/**
+ * Function: getRoot
+ * 
+ * Returns the root cell for painting the graph.
+ */
+mxPrintPreview.prototype.getRoot = function()
+{
+	var root = this.graph.view.currentRoot;
+	
+	if (root == null)
+	{
+		root = this.graph.getModel().getRoot();
+	}
+	
+	return root;
+};
+
+/**
+ * Function: addGraphFragment
+ * 
+ * Adds a graph fragment to the given div.
+ * 
+ * Parameters:
+ * 
+ * dx - Horizontal translation for the diagram.
+ * dy - Vertical translation for the diagram.
+ * scale - Scale for the diagram.
+ * pageNumber - Number of the page to be rendered.
+ * div - Div that contains the output.
+ * clip - Contains the clipping rectangle as an <mxRectangle>.
+ */
+mxPrintPreview.prototype.addGraphFragment = function(dx, dy, scale, pageNumber, div, clip)
+{
+	var view = this.graph.getView();
+	var previousContainer = this.graph.container;
+	this.graph.container = div;
+	
+	var canvas = view.getCanvas();
+	var backgroundPane = view.getBackgroundPane();
+	var drawPane = view.getDrawPane();
+	var overlayPane = view.getOverlayPane();
+
+	if (this.graph.dialect == mxConstants.DIALECT_SVG)
+	{
+		view.createSvg();
+	}
+	else if (this.graph.dialect == mxConstants.DIALECT_VML)
+	{
+		view.createVml();
+	}
+	else
+	{
+		view.createHtml();
+	}
+	
+	// Disables events on the view
+	var eventsEnabled = view.isEventsEnabled();
+	view.setEventsEnabled(false);
+	
+	// Disables the graph to avoid cursors
+	var graphEnabled = this.graph.isEnabled();
+	this.graph.setEnabled(false);
+
+	// Resets the translation
+	var translate = view.getTranslate();
+	view.translate = new mxPoint(dx, dy);
+	
+	// Redraws only states that intersect the clip
+	var redraw = this.graph.cellRenderer.redraw;
+	var states = view.states;
+	var s = view.scale;
+	
+	// Gets the transformed clip for intersection check below
+	if (this.clipping)
+	{
+		var tempClip = new mxRectangle((clip.x + translate.x) * s, (clip.y + translate.y) * s,
+				clip.width * s / scale, clip.height * s / scale);
+		
+		// Checks clipping rectangle for speedup
+		// Must create terminal states for edge clipping even if terminal outside of clip
+		this.graph.cellRenderer.redraw = function(state, force, rendering)
+		{
+			if (state != null)
+			{
+				// Gets original state from graph to find bounding box
+				var orig = states.get(state.cell);
+				
+				if (orig != null)
+				{
+					var bbox = view.getBoundingBox(orig, false);
+					
+					// Stops rendering if outside clip for speedup
+					if (bbox != null && !mxUtils.intersects(tempClip, bbox))
+					{
+						return;
+					}
+				}
+			}
+			
+			redraw.apply(this, arguments);
+		};
+	}
+	
+	var temp = null;
+	
+	try
+	{
+		// Creates the temporary cell states in the view and
+		// draws them onto the temporary DOM nodes in the view
+		var cells = [this.getRoot()];
+		temp = new mxTemporaryCellStates(view, scale, cells);
+	}
+	finally
+	{
+		// Removes overlay pane with selection handles
+		// controls and icons from the print output
+		if (mxClient.IS_IE)
+		{
+			view.overlayPane.innerHTML = '';
+			view.canvas.style.overflow = 'hidden';
+			view.canvas.style.position = 'relative';
+			view.canvas.style.top = this.marginTop + 'px';
+			view.canvas.style.width = clip.width + 'px';
+			view.canvas.style.height = clip.height + 'px';
+		}
+		else
+		{
+			// Removes everything but the SVG node
+			var tmp = div.firstChild;
+
+			while (tmp != null)
+			{
+				var next = tmp.nextSibling;
+				var name = tmp.nodeName.toLowerCase();
+
+				// Note: Width and height are required in FF 11
+				if (name == 'svg')
+				{
+					tmp.style.overflow = 'hidden';
+					tmp.style.position = 'relative';
+					tmp.style.top = this.marginTop + 'px';
+					tmp.setAttribute('width', clip.width);
+					tmp.setAttribute('height', clip.height);
+					tmp.style.width = '';
+					tmp.style.height = '';
+				}
+				// Tries to fetch all text labels and only text labels
+				else if (tmp.style.cursor != 'default' && name != 'div')
+				{
+					tmp.parentNode.removeChild(tmp);
+				}
+				
+				tmp = next;
+			}
+		}
+		
+		// Puts background image behind SVG output
+		if (this.printBackgroundImage)
+		{
+			var svgs = div.getElementsByTagName('svg');
+			
+			if (svgs.length > 0)
+			{
+				svgs[0].style.position = 'absolute';
+			}
+		}
+		
+		// Completely removes the overlay pane to remove more handles
+		view.overlayPane.parentNode.removeChild(view.overlayPane);
+
+		// Restores the state of the view
+		this.graph.setEnabled(graphEnabled);
+		this.graph.container = previousContainer;
+		this.graph.cellRenderer.redraw = redraw;
+		view.canvas = canvas;
+		view.backgroundPane = backgroundPane;
+		view.drawPane = drawPane;
+		view.overlayPane = overlayPane;
+		view.translate = translate;
+		temp.destroy();
+		view.setEventsEnabled(eventsEnabled);
+	}
+};
+
+/**
+ * Function: insertBackgroundImage
+ * 
+ * Inserts the background image into the given div.
+ */
+mxPrintPreview.prototype.insertBackgroundImage = function(div, dx, dy)
+{
+	var bg = this.graph.backgroundImage;
+	
+	if (bg != null)
+	{
+		var img = document.createElement('img');
+		img.style.position = 'absolute';
+		img.style.marginLeft = Math.round(dx * this.scale) + 'px';
+		img.style.marginTop = Math.round(dy * this.scale) + 'px';
+		img.setAttribute('width', Math.round(this.scale * bg.width));
+		img.setAttribute('height', Math.round(this.scale * bg.height));
+		img.src = bg.src;
+		
+		div.insertBefore(img, div.firstChild);
+	}
+};
+
+/**
+ * Function: getCoverPages
+ * 
+ * Returns the pages to be added before the print output. This returns null.
+ */
+mxPrintPreview.prototype.getCoverPages = function()
+{
+	return null;
+};
+
+/**
+ * Function: getAppendices
+ * 
+ * Returns the pages to be added after the print output. This returns null.
+ */
+mxPrintPreview.prototype.getAppendices = function()
+{
+	return null;
+};
+
+/**
+ * Function: print
+ * 
+ * Opens the print preview and shows the print dialog.
+ * 
+ * Parameters:
+ * 
+ * css - Optional CSS string to be used in the head section.
+ */
+mxPrintPreview.prototype.print = function(css)
+{
+	var wnd = this.open(css);
+	
+	if (wnd != null)
+	{
+		wnd.print();
+	}
+};
+
+/**
+ * Function: close
+ * 
+ * Closes the print preview window.
+ */
+mxPrintPreview.prototype.close = function()
+{
+	if (this.wnd != null)
+	{
+		this.wnd.close();
+		this.wnd = null;
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxStylesheet
+ * 
+ * Defines the appearance of the cells in a graph. See <putCellStyle> for an
+ * example of creating a new cell style. It is recommended to use objects, not
+ * arrays for holding cell styles. Existing styles can be cloned using
+ * <mxUtils.clone> and turned into a string for debugging using
+ * <mxUtils.toString>.
+ *
+ * Default Styles:
+ * 
+ * The stylesheet contains two built-in styles, which are used if no style is
+ * defined for a cell:
+ *
+ *   defaultVertex - Default style for vertices
+ *   defaultEdge - Default style for edges
+ * 
+ * Example:
+ * 
+ * (code)
+ * var vertexStyle = stylesheet.getDefaultVertexStyle();
+ * vertexStyle[mxConstants.ROUNDED] = true;
+ * var edgeStyle = stylesheet.getDefaultEdgeStyle();
+ * edgeStyle[mxConstants.STYLE_EDGE] = mxEdgeStyle.EntityRelation;
+ * (end)
+ * 
+ * Modifies the built-in default styles.
+ * 
+ * To avoid the default style for a cell, add a leading semicolon
+ * to the style definition, eg.
+ * 
+ * (code)
+ * ;shadow=1
+ * (end)
+ * 
+ * Removing keys:
+ * 
+ * For removing a key in a cell style of the form [stylename;|key=value;] the
+ * special value none can be used, eg. highlight;fillColor=none
+ * 
+ * See also the helper methods in mxUtils to modify strings of this format,
+ * namely <mxUtils.setStyle>, <mxUtils.indexOfStylename>,
+ * <mxUtils.addStylename>, <mxUtils.removeStylename>,
+ * <mxUtils.removeAllStylenames> and <mxUtils.setStyleFlag>.
+ * 
+ * Constructor: mxStylesheet
+ * 
+ * Constructs a new stylesheet and assigns default styles.
+ */
+function mxStylesheet()
+{
+	this.styles = new Object();
+	
+	this.putDefaultVertexStyle(this.createDefaultVertexStyle());
+	this.putDefaultEdgeStyle(this.createDefaultEdgeStyle());
+};
+
+/**
+ * Function: styles
+ * 
+ * Maps from names to cell styles. Each cell style is a map of key,
+ * value pairs.
+ */
+mxStylesheet.prototype.styles;
+
+/**
+ * Function: createDefaultVertexStyle
+ * 
+ * Creates and returns the default vertex style.
+ */
+mxStylesheet.prototype.createDefaultVertexStyle = function()
+{
+	var style = new Object();
+	
+	style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_RECTANGLE;
+	style[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter;
+	style[mxConstants.STYLE_VERTICAL_ALIGN] = mxConstants.ALIGN_MIDDLE;
+	style[mxConstants.STYLE_ALIGN] = mxConstants.ALIGN_CENTER;
+	style[mxConstants.STYLE_FILLCOLOR] = '#C3D9FF';
+	style[mxConstants.STYLE_STROKECOLOR] = '#6482B9';
+	style[mxConstants.STYLE_FONTCOLOR] = '#774400';
+	
+	return style;
+};
+
+/**
+ * Function: createDefaultEdgeStyle
+ * 
+ * Creates and returns the default edge style.
+ */
+mxStylesheet.prototype.createDefaultEdgeStyle = function()
+{
+	var style = new Object();
+	
+	style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_CONNECTOR;
+	style[mxConstants.STYLE_ENDARROW] = mxConstants.ARROW_CLASSIC;
+	style[mxConstants.STYLE_VERTICAL_ALIGN] = mxConstants.ALIGN_MIDDLE;
+	style[mxConstants.STYLE_ALIGN] = mxConstants.ALIGN_CENTER;
+	style[mxConstants.STYLE_STROKECOLOR] = '#6482B9';
+	style[mxConstants.STYLE_FONTCOLOR] = '#446299';
+	
+	return style;
+};
+
+/**
+ * Function: putDefaultVertexStyle
+ * 
+ * Sets the default style for vertices using defaultVertex as the
+ * stylename.
+ * 
+ * Parameters:
+ * style - Key, value pairs that define the style.
+ */
+mxStylesheet.prototype.putDefaultVertexStyle = function(style)
+{
+	this.putCellStyle('defaultVertex', style);
+};
+
+/**
+ * Function: putDefaultEdgeStyle
+ * 
+ * Sets the default style for edges using defaultEdge as the stylename.
+ */
+mxStylesheet.prototype.putDefaultEdgeStyle = function(style)
+{
+	this.putCellStyle('defaultEdge', style);
+};
+
+/**
+ * Function: getDefaultVertexStyle
+ * 
+ * Returns the default style for vertices.
+ */
+mxStylesheet.prototype.getDefaultVertexStyle = function()
+{
+	return this.styles['defaultVertex'];
+};
+
+/**
+ * Function: getDefaultEdgeStyle
+ * 
+ * Sets the default style for edges.
+ */
+mxStylesheet.prototype.getDefaultEdgeStyle = function()
+{
+	return this.styles['defaultEdge'];
+};
+
+/**
+ * Function: putCellStyle
+ * 
+ * Stores the given map of key, value pairs under the given name in
+ * <styles>.
+ *
+ * Example:
+ * 
+ * The following example adds a new style called 'rounded' into an
+ * existing stylesheet:
+ * 
+ * (code)
+ * var style = new Object();
+ * style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_RECTANGLE;
+ * style[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter;
+ * style[mxConstants.STYLE_ROUNDED] = true;
+ * graph.getStylesheet().putCellStyle('rounded', style);
+ * (end)
+ * 
+ * In the above example, the new style is an object. The possible keys of
+ * the object are all the constants in <mxConstants> that start with STYLE
+ * and the values are either JavaScript objects, such as
+ * <mxPerimeter.RightAngleRectanglePerimeter> (which is in fact a function)
+ * or expressions, such as true. Note that not all keys will be
+ * interpreted by all shapes (eg. the line shape ignores the fill color).
+ * The final call to this method associates the style with a name in the
+ * stylesheet. The style is used in a cell with the following code:
+ * 
+ * (code)
+ * model.setStyle(cell, 'rounded');
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * name - Name for the style to be stored.
+ * style - Key, value pairs that define the style.
+ */
+mxStylesheet.prototype.putCellStyle = function(name, style)
+{
+	this.styles[name] = style;
+};
+
+/**
+ * Function: getCellStyle
+ * 
+ * Returns the cell style for the specified stylename or the given
+ * defaultStyle if no style can be found for the given stylename.
+ * 
+ * Parameters:
+ * 
+ * name - String of the form [(stylename|key=value);] that represents the
+ * style.
+ * defaultStyle - Default style to be returned if no style can be found.
+ */
+mxStylesheet.prototype.getCellStyle = function(name, defaultStyle)
+{
+	var style = defaultStyle;
+	
+	if (name != null && name.length > 0)
+	{
+		var pairs = name.split(';');
+
+		if (style != null &&
+			name.charAt(0) != ';')
+		{
+			style = mxUtils.clone(style);
+		}
+		else
+		{
+			style = new Object();
+		}
+
+		// Parses each key, value pair into the existing style
+	 	for (var i = 0; i < pairs.length; i++)
+	 	{
+	 		var tmp = pairs[i];
+	 		var pos = tmp.indexOf('=');
+	 		
+	 		if (pos >= 0)
+	 		{
+		 		var key = tmp.substring(0, pos);
+		 		var value = tmp.substring(pos + 1);
+
+		 		if (value == mxConstants.NONE)
+		 		{
+		 			delete style[key];
+		 		}
+		 		else if (mxUtils.isNumeric(value))
+		 		{
+		 			style[key] = parseFloat(value);
+		 		}
+		 		else
+		 		{
+			 		style[key] = value;
+		 		}
+			}
+	 		else
+	 		{
+	 			// Merges the entries from a named style
+				var tmpStyle = this.styles[tmp];
+				
+				if (tmpStyle != null)
+				{
+					for (var key in tmpStyle)
+					{
+						style[key] = tmpStyle[key];
+					}
+				}
+	 		}
+		}
+	}
+	
+	return style;
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxCellState
+ * 
+ * Represents the current state of a cell in a given <mxGraphView>.
+ * 
+ * For edges, the edge label position is stored in <absoluteOffset>.
+ * 
+ * The size for oversize labels can be retrieved using the boundingBox property
+ * of the <text> field as shown below.
+ * 
+ * (code)
+ * var bbox = (state.text != null) ? state.text.boundingBox : null;
+ * (end)
+ * 
+ * Constructor: mxCellState
+ * 
+ * Constructs a new object that represents the current state of the given
+ * cell in the specified view.
+ * 
+ * Parameters:
+ * 
+ * view - <mxGraphView> that contains the state.
+ * cell - <mxCell> that this state represents.
+ * style - Array of key, value pairs that constitute the style.
+ */
+function mxCellState(view, cell, style)
+{
+	this.view = view;
+	this.cell = cell;
+	this.style = style;
+	
+	this.origin = new mxPoint();
+	this.absoluteOffset = new mxPoint();
+};
+
+/**
+ * Extends mxRectangle.
+ */
+mxCellState.prototype = new mxRectangle();
+mxCellState.prototype.constructor = mxCellState;
+
+/**
+ * Variable: view
+ * 
+ * Reference to the enclosing <mxGraphView>.
+ */
+mxCellState.prototype.view = null;
+
+/**
+ * Variable: cell
+ *
+ * Reference to the <mxCell> that is represented by this state.
+ */
+mxCellState.prototype.cell = null;
+
+/**
+ * Variable: style
+ * 
+ * Contains an array of key, value pairs that represent the style of the
+ * cell.
+ */
+mxCellState.prototype.style = null;
+
+/**
+ * Variable: invalid
+ * 
+ * Specifies if the state is invalid. Default is true.
+ */
+mxCellState.prototype.invalid = true;
+
+/**
+ * Variable: origin
+ *
+ * <mxPoint> that holds the origin for all child cells. Default is a new
+ * empty <mxPoint>.
+ */
+mxCellState.prototype.origin = null;
+
+/**
+ * Variable: absolutePoints
+ * 
+ * Holds an array of <mxPoints> that represent the absolute points of an
+ * edge.
+ */
+mxCellState.prototype.absolutePoints = null;
+
+/**
+ * Variable: absoluteOffset
+ *
+ * <mxPoint> that holds the absolute offset. For edges, this is the
+ * absolute coordinates of the label position. For vertices, this is the
+ * offset of the label relative to the top, left corner of the vertex. 
+ */
+mxCellState.prototype.absoluteOffset = null;
+
+/**
+ * Variable: visibleSourceState
+ * 
+ * Caches the visible source terminal state.
+ */
+mxCellState.prototype.visibleSourceState = null;
+
+/**
+ * Variable: visibleTargetState
+ * 
+ * Caches the visible target terminal state.
+ */
+mxCellState.prototype.visibleTargetState = null;
+
+/**
+ * Variable: terminalDistance
+ * 
+ * Caches the distance between the end points for an edge.
+ */
+mxCellState.prototype.terminalDistance = 0;
+
+/**
+ * Variable: length
+ *
+ * Caches the length of an edge.
+ */
+mxCellState.prototype.length = 0;
+
+/**
+ * Variable: segments
+ * 
+ * Array of numbers that represent the cached length of each segment of the
+ * edge.
+ */
+mxCellState.prototype.segments = null;
+
+/**
+ * Variable: shape
+ * 
+ * Holds the <mxShape> that represents the cell graphically.
+ */
+mxCellState.prototype.shape = null;
+
+/**
+ * Variable: text
+ * 
+ * Holds the <mxText> that represents the label of the cell. Thi smay be
+ * null if the cell has no label.
+ */
+mxCellState.prototype.text = null;
+
+/**
+ * Variable: unscaledWidth
+ * 
+ * Holds the unscaled width of the state.
+ */
+mxCellState.prototype.unscaledWidth = null;
+
+/**
+ * Function: getPerimeterBounds
+ * 
+ * Returns the <mxRectangle> that should be used as the perimeter of the
+ * cell.
+ * 
+ * Parameters:
+ * 
+ * border - Optional border to be added around the perimeter bounds.
+ * bounds - Optional <mxRectangle> to be used as the initial bounds.
+ */
+mxCellState.prototype.getPerimeterBounds = function(border, bounds)
+{
+	border = border || 0;
+	bounds = (bounds != null) ? bounds : new mxRectangle(this.x, this.y, this.width, this.height);
+	
+	if (this.shape != null && this.shape.stencil != null && this.shape.stencil.aspect == 'fixed')
+	{
+		var aspect = this.shape.stencil.computeAspect(this.style, bounds.x, bounds.y, bounds.width, bounds.height);
+		
+		bounds.x = aspect.x;
+		bounds.y = aspect.y;
+		bounds.width = this.shape.stencil.w0 * aspect.width;
+		bounds.height = this.shape.stencil.h0 * aspect.height;
+	}
+	
+	if (border != 0)
+	{
+		bounds.grow(border);
+	}
+	
+	return bounds;
+};
+
+/**
+ * Function: setAbsoluteTerminalPoint
+ * 
+ * Sets the first or last point in <absolutePoints> depending on isSource.
+ * 
+ * Parameters:
+ * 
+ * point - <mxPoint> that represents the terminal point.
+ * isSource - Boolean that specifies if the first or last point should
+ * be assigned.
+ */
+mxCellState.prototype.setAbsoluteTerminalPoint = function(point, isSource)
+{
+	if (isSource)
+	{
+		if (this.absolutePoints == null)
+		{
+			this.absolutePoints = [];
+		}
+		
+		if (this.absolutePoints.length == 0)
+		{
+			this.absolutePoints.push(point);
+		}
+		else
+		{
+			this.absolutePoints[0] = point;
+		}
+	}
+	else
+	{
+		if (this.absolutePoints == null)
+		{
+			this.absolutePoints = [];
+			this.absolutePoints.push(null);
+			this.absolutePoints.push(point);
+		}
+		else if (this.absolutePoints.length == 1)
+		{
+			this.absolutePoints.push(point);
+		}
+		else
+		{
+			this.absolutePoints[this.absolutePoints.length - 1] = point;
+		}
+	}
+};
+
+/**
+ * Function: setCursor
+ * 
+ * Sets the given cursor on the shape and text shape.
+ */
+mxCellState.prototype.setCursor = function(cursor)
+{
+	if (this.shape != null)
+	{
+		this.shape.setCursor(cursor);
+	}
+	
+	if (this.text != null)
+	{
+		this.text.setCursor(cursor);
+	}
+};
+
+/**
+ * Function: getVisibleTerminal
+ * 
+ * Returns the visible source or target terminal cell.
+ * 
+ * Parameters:
+ * 
+ * source - Boolean that specifies if the source or target cell should be
+ * returned.
+ */
+mxCellState.prototype.getVisibleTerminal = function(source)
+{
+	var tmp = this.getVisibleTerminalState(source);
+	
+	return (tmp != null) ? tmp.cell : null;
+};
+
+/**
+ * Function: getVisibleTerminalState
+ * 
+ * Returns the visible source or target terminal state.
+ * 
+ * Parameters:
+ * 
+ * source - Boolean that specifies if the source or target state should be
+ * returned.
+ */
+mxCellState.prototype.getVisibleTerminalState = function(source)
+{
+	return (source) ? this.visibleSourceState : this.visibleTargetState;
+};
+
+/**
+ * Function: setVisibleTerminalState
+ * 
+ * Sets the visible source or target terminal state.
+ * 
+ * Parameters:
+ * 
+ * terminalState - <mxCellState> that represents the terminal.
+ * source - Boolean that specifies if the source or target state should be set.
+ */
+mxCellState.prototype.setVisibleTerminalState = function(terminalState, source)
+{
+	if (source)
+	{
+		this.visibleSourceState = terminalState;
+	}
+	else
+	{
+		this.visibleTargetState = terminalState;
+	}
+};
+
+/**
+ * Function: getCellBounds
+ * 
+ * Returns the unscaled, untranslated bounds.
+ */
+mxCellState.prototype.getCellBounds = function()
+{
+	return this.cellBounds;
+};
+
+/**
+ * Function: getPaintBounds
+ * 
+ * Returns the unscaled, untranslated paint bounds. This is the same as
+ * <getCellBounds> but with a 90 degree rotation if the shape's
+ * isPaintBoundsInverted returns true.
+ */
+mxCellState.prototype.getPaintBounds = function()
+{
+	return this.paintBounds;
+};
+
+/**
+ * Function: updateCachedBounds
+ * 
+ * Updates the cellBounds and paintBounds.
+ */
+mxCellState.prototype.updateCachedBounds = function()
+{
+	var tr = this.view.translate;
+	var s = this.view.scale;
+	this.cellBounds = new mxRectangle(this.x / s - tr.x, this.y / s - tr.y, this.width / s, this.height / s);
+	this.paintBounds = mxRectangle.fromRectangle(this.cellBounds);
+	
+	if (this.shape != null && this.shape.isPaintBoundsInverted())
+	{
+		this.paintBounds.rotate90();
+	}
+};
+
+/**
+ * Destructor: setState
+ * 
+ * Copies all fields from the given state to this state.
+ */
+mxCellState.prototype.setState = function(state)
+{
+	this.view = state.view;
+	this.cell = state.cell;
+	this.style = state.style;
+	this.absolutePoints = state.absolutePoints;
+	this.origin = state.origin;
+	this.absoluteOffset = state.absoluteOffset;
+	this.boundingBox = state.boundingBox;
+	this.terminalDistance = state.terminalDistance;
+	this.segments = state.segments;
+	this.length = state.length;
+	this.x = state.x;
+	this.y = state.y;
+	this.width = state.width;
+	this.height = state.height;
+	this.unscaledWidth = state.unscaledWidth;
+};
+
+/**
+ * Function: clone
+ *
+ * Returns a clone of this <mxPoint>.
+ */
+mxCellState.prototype.clone = function()
+{
+ 	var clone = new mxCellState(this.view, this.cell, this.style);
+
+	// Clones the absolute points
+	if (this.absolutePoints != null)
+	{
+		clone.absolutePoints = [];
+		
+		for (var i = 0; i < this.absolutePoints.length; i++)
+		{
+			clone.absolutePoints[i] = this.absolutePoints[i].clone();
+		}
+	}
+
+	if (this.origin != null)
+	{
+		clone.origin = this.origin.clone();
+	}
+
+	if (this.absoluteOffset != null)
+	{
+		clone.absoluteOffset = this.absoluteOffset.clone();
+	}
+
+	if (this.boundingBox != null)
+	{
+		clone.boundingBox = this.boundingBox.clone();
+	}
+
+	clone.terminalDistance = this.terminalDistance;
+	clone.segments = this.segments;
+	clone.length = this.length;
+	clone.x = this.x;
+	clone.y = this.y;
+	clone.width = this.width;
+	clone.height = this.height;
+	clone.unscaledWidth = this.unscaledWidth;
+	
+	return clone;
+};
+
+/**
+ * Destructor: destroy
+ * 
+ * Destroys the state and all associated resources.
+ */
+mxCellState.prototype.destroy = function()
+{
+	this.view.graph.cellRenderer.destroy(this);
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxGraphSelectionModel
+ *
+ * Implements the selection model for a graph. Here is a listener that handles
+ * all removed selection cells.
+ * 
+ * (code)
+ * graph.getSelectionModel().addListener(mxEvent.CHANGE, function(sender, evt)
+ * {
+ *   var cells = evt.getProperty('added');
+ *   
+ *   for (var i = 0; i < cells.length; i++)
+ *   {
+ *     // Handle cells[i]...
+ *   }
+ * });
+ * (end)
+ * 
+ * Event: mxEvent.UNDO
+ * 
+ * Fires after the selection was changed in <changeSelection>. The
+ * <code>edit</code> property contains the <mxUndoableEdit> which contains the
+ * <mxSelectionChange>.
+ * 
+ * Event: mxEvent.CHANGE
+ * 
+ * Fires after the selection changes by executing an <mxSelectionChange>. The
+ * <code>added</code> and <code>removed</code> properties contain arrays of
+ * cells that have been added to or removed from the selection, respectively.
+ * The names are inverted due to historic reasons. This cannot be changed.
+ * 
+ * Constructor: mxGraphSelectionModel
+ *
+ * Constructs a new graph selection model for the given <mxGraph>.
+ * 
+ * Parameters:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ */
+function mxGraphSelectionModel(graph)
+{
+	this.graph = graph;
+	this.cells = [];
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxGraphSelectionModel.prototype = new mxEventSource();
+mxGraphSelectionModel.prototype.constructor = mxGraphSelectionModel;
+
+/**
+ * Variable: doneResource
+ * 
+ * Specifies the resource key for the status message after a long operation.
+ * If the resource for this key does not exist then the value is used as
+ * the status message. Default is 'done'.
+ */
+mxGraphSelectionModel.prototype.doneResource = (mxClient.language != 'none') ? 'done' : '';
+
+/**
+ * Variable: updatingSelectionResource
+ *
+ * Specifies the resource key for the status message while the selection is
+ * being updated. If the resource for this key does not exist then the
+ * value is used as the status message. Default is 'updatingSelection'.
+ */
+mxGraphSelectionModel.prototype.updatingSelectionResource = (mxClient.language != 'none') ? 'updatingSelection' : '';
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxGraphSelectionModel.prototype.graph = null;
+
+/**
+ * Variable: singleSelection
+ *
+ * Specifies if only one selected item at a time is allowed.
+ * Default is false.
+ */
+mxGraphSelectionModel.prototype.singleSelection = false;
+
+/**
+ * Function: isSingleSelection
+ *
+ * Returns <singleSelection> as a boolean.
+ */
+mxGraphSelectionModel.prototype.isSingleSelection = function()
+{
+	return this.singleSelection;
+};
+
+/**
+ * Function: setSingleSelection
+ *
+ * Sets the <singleSelection> flag.
+ * 
+ * Parameters:
+ * 
+ * singleSelection - Boolean that specifies the new value for
+ * <singleSelection>.
+ */
+mxGraphSelectionModel.prototype.setSingleSelection = function(singleSelection)
+{
+	this.singleSelection = singleSelection;
+};
+
+/**
+ * Function: isSelected
+ *
+ * Returns true if the given <mxCell> is selected.
+ */
+mxGraphSelectionModel.prototype.isSelected = function(cell)
+{
+	if (cell != null)
+	{
+		return mxUtils.indexOf(this.cells, cell) >= 0;
+	}
+	
+	return false;
+};
+
+/**
+ * Function: isEmpty
+ *
+ * Returns true if no cells are currently selected.
+ */
+mxGraphSelectionModel.prototype.isEmpty = function()
+{
+	return this.cells.length == 0;
+};
+
+/**
+ * Function: clear
+ *
+ * Clears the selection and fires a <change> event if the selection was not
+ * empty.
+ */
+mxGraphSelectionModel.prototype.clear = function()
+{
+	this.changeSelection(null, this.cells);
+};
+
+/**
+ * Function: setCell
+ *
+ * Selects the specified <mxCell> using <setCells>.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to be selected.
+ */
+mxGraphSelectionModel.prototype.setCell = function(cell)
+{
+	if (cell != null)
+	{
+		this.setCells([cell]);
+	}
+};
+
+/**
+ * Function: setCells
+ *
+ * Selects the given array of <mxCells> and fires a <change> event.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> to be selected.
+ */
+mxGraphSelectionModel.prototype.setCells = function(cells)
+{
+	if (cells != null)
+	{
+		if (this.singleSelection)
+		{
+			cells = [this.getFirstSelectableCell(cells)];
+		}
+	
+		var tmp = [];
+		
+		for (var i = 0; i < cells.length; i++)
+		{
+			if (this.graph.isCellSelectable(cells[i]))
+			{
+				tmp.push(cells[i]);
+			}	
+		}
+
+		this.changeSelection(tmp, this.cells);
+	}
+};
+
+/**
+ * Function: getFirstSelectableCell
+ *
+ * Returns the first selectable cell in the given array of cells.
+ */
+mxGraphSelectionModel.prototype.getFirstSelectableCell = function(cells)
+{
+	if (cells != null)
+	{
+		for (var i = 0; i < cells.length; i++)
+		{
+			if (this.graph.isCellSelectable(cells[i]))
+			{
+				return cells[i];
+			}
+		}
+	}
+	
+	return null;
+};
+
+/**
+ * Function: addCell
+ * 
+ * Adds the given <mxCell> to the selection and fires a <select> event.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to add to the selection.
+ */
+mxGraphSelectionModel.prototype.addCell = function(cell)
+{
+	if (cell != null)
+	{
+		this.addCells([cell]);
+	}
+};
+
+/**
+ * Function: addCells
+ * 
+ * Adds the given array of <mxCells> to the selection and fires a <select>
+ * event.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> to add to the selection.
+ */
+mxGraphSelectionModel.prototype.addCells = function(cells)
+{
+	if (cells != null)
+	{
+		var remove = null;
+		
+		if (this.singleSelection)
+		{
+			remove = this.cells;
+			cells = [this.getFirstSelectableCell(cells)];
+		}
+
+		var tmp = [];
+		
+		for (var i = 0; i < cells.length; i++)
+		{
+			if (!this.isSelected(cells[i]) &&
+				this.graph.isCellSelectable(cells[i]))
+			{
+				tmp.push(cells[i]);
+			}	
+		}
+
+		this.changeSelection(tmp, remove);
+	}
+};
+
+/**
+ * Function: removeCell
+ *
+ * Removes the specified <mxCell> from the selection and fires a <select>
+ * event for the remaining cells.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to remove from the selection.
+ */
+mxGraphSelectionModel.prototype.removeCell = function(cell)
+{
+	if (cell != null)
+	{
+		this.removeCells([cell]);
+	}
+};
+
+/**
+ * Function: removeCells
+ */
+mxGraphSelectionModel.prototype.removeCells = function(cells)
+{
+	if (cells != null)
+	{
+		var tmp = [];
+		
+		for (var i = 0; i < cells.length; i++)
+		{
+			if (this.isSelected(cells[i]))
+			{
+				tmp.push(cells[i]);
+			}
+		}
+		
+		this.changeSelection(null, tmp);	
+	}
+};
+
+/**
+ * Function: changeSelection
+ *
+ * Inner callback to add the specified <mxCell> to the selection. No event
+ * is fired in this implementation.
+ * 
+ * Paramters:
+ * 
+ * cell - <mxCell> to add to the selection.
+ */
+mxGraphSelectionModel.prototype.changeSelection = function(added, removed)
+{
+	if ((added != null &&
+		added.length > 0 &&
+		added[0] != null) ||
+		(removed != null &&
+		removed.length > 0 &&
+		removed[0] != null))
+	{
+		var change = new mxSelectionChange(this, added, removed);
+		change.execute();
+		var edit = new mxUndoableEdit(this, false);
+		edit.add(change);
+		this.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit));
+	}
+};
+
+/**
+ * Function: cellAdded
+ *
+ * Inner callback to add the specified <mxCell> to the selection. No event
+ * is fired in this implementation.
+ * 
+ * Paramters:
+ * 
+ * cell - <mxCell> to add to the selection.
+ */
+mxGraphSelectionModel.prototype.cellAdded = function(cell)
+{
+	if (cell != null &&
+		!this.isSelected(cell))
+	{
+		this.cells.push(cell);
+	}
+};
+
+/**
+ * Function: cellRemoved
+ *
+ * Inner callback to remove the specified <mxCell> from the selection. No
+ * event is fired in this implementation.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to remove from the selection.
+ */
+mxGraphSelectionModel.prototype.cellRemoved = function(cell)
+{
+	if (cell != null)
+	{
+		var index = mxUtils.indexOf(this.cells, cell);
+		
+		if (index >= 0)
+		{
+			this.cells.splice(index, 1);
+		}
+	}
+};
+
+/**
+ * Class: mxSelectionChange
+ *
+ * Action to change the current root in a view.
+ *
+ * Constructor: mxCurrentRootChange
+ *
+ * Constructs a change of the current root in the given view.
+ */
+function mxSelectionChange(selectionModel, added, removed)
+{
+	this.selectionModel = selectionModel;
+	this.added = (added != null) ? added.slice() : null;
+	this.removed = (removed != null) ? removed.slice() : null;
+};
+
+/**
+ * Function: execute
+ *
+ * Changes the current root of the view.
+ */
+mxSelectionChange.prototype.execute = function()
+{
+	var t0 = mxLog.enter('mxSelectionChange.execute');
+	window.status = mxResources.get(
+		this.selectionModel.updatingSelectionResource) ||
+		this.selectionModel.updatingSelectionResource;
+
+	if (this.removed != null)
+	{
+		for (var i = 0; i < this.removed.length; i++)
+		{
+			this.selectionModel.cellRemoved(this.removed[i]);
+		}
+	}
+
+	if (this.added != null)
+	{
+		for (var i = 0; i < this.added.length; i++)
+		{
+			this.selectionModel.cellAdded(this.added[i]);
+		}
+	}
+	
+	var tmp = this.added;
+	this.added = this.removed;
+	this.removed = tmp;
+
+	window.status = mxResources.get(this.selectionModel.doneResource) ||
+		this.selectionModel.doneResource;
+	mxLog.leave('mxSelectionChange.execute', t0);
+	
+	this.selectionModel.fireEvent(new mxEventObject(mxEvent.CHANGE,
+			'added', this.added, 'removed', this.removed));
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxCellEditor
+ *
+ * In-place editor for the graph. To control this editor, use
+ * <mxGraph.invokesStopCellEditing>, <mxGraph.enterStopsCellEditing> and
+ * <mxGraph.escapeEnabled>. If <mxGraph.enterStopsCellEditing> is true then
+ * ctrl-enter or shift-enter can be used to create a linefeed. The F2 and
+ * escape keys can always be used to stop editing.
+ * 
+ * To customize the location of the textbox in the graph, override
+ * <getEditorBounds> as follows:
+ * 
+ * (code)
+ * graph.cellEditor.getEditorBounds = function(state)
+ * {
+ *   var result = mxCellEditor.prototype.getEditorBounds.apply(this, arguments);
+ *   
+ *   if (this.graph.getModel().isEdge(state.cell))
+ *   {
+ *     result.x = state.getCenterX() - result.width / 2;
+ *     result.y = state.getCenterY() - result.height / 2;
+ *   }
+ *   
+ *   return result;
+ * };
+ * (end)
+ * 
+ * Note that this hook is only called if <autoSize> is false. If <autoSize> is true,
+ * then <mxShape.getLabelBounds> is used to compute the current bounds of the textbox.
+ * 
+ * The textarea uses the mxCellEditor CSS class. You can modify this class in
+ * your custom CSS. Note: You should modify the CSS after loading the client
+ * in the page.
+ *
+ * Example:
+ * 
+ * To only allow numeric input in the in-place editor, use the following code.
+ *
+ * (code)
+ * var text = graph.cellEditor.textarea;
+ * 
+ * mxEvent.addListener(text, 'keydown', function (evt)
+ * {
+ *   if (!(evt.keyCode >= 48 && evt.keyCode <= 57) &&
+ *       !(evt.keyCode >= 96 && evt.keyCode <= 105))
+ *   {
+ *     mxEvent.consume(evt);
+ *   }
+ * }); 
+ * (end)
+ * 
+ * Placeholder:
+ * 
+ * To implement a placeholder for cells without a label, use the
+ * <emptyLabelText> variable.
+ * 
+ * Resize in Chrome:
+ * 
+ * Resize of the textarea is disabled by default. If you want to enable
+ * this feature extend <init> and set this.textarea.style.resize = ''.
+ * 
+ * To start editing on a key press event, the container of the graph
+ * should have focus or a focusable parent should be used to add the
+ * key press handler as follows.
+ * 
+ * (code)
+ * mxEvent.addListener(graph.container, 'keypress', mxUtils.bind(this, function(evt)
+ * {
+ *   if (!graph.isEditing() && !graph.isSelectionEmpty() && evt.which !== 0 &&
+ *       !mxEvent.isAltDown(evt) && !mxEvent.isControlDown(evt) && !mxEvent.isMetaDown(evt))
+ *   {
+ *     graph.startEditing();
+ *     
+ *     if (mxClient.IS_FF)
+ *     {
+ *       graph.cellEditor.textarea.value = String.fromCharCode(evt.which);
+ *     }
+ *   }
+ * }));
+ * (end)
+ * 
+ * To allow focus for a DIV, and hence to receive key press events, some browsers
+ * require it to have a valid tabindex attribute. In this case the following
+ * code may be used to keep the container focused.
+ * 
+ * (code)
+ * var graphFireMouseEvent = graph.fireMouseEvent;
+ * graph.fireMouseEvent = function(evtName, me, sender)
+ * {
+ *   if (evtName == mxEvent.MOUSE_DOWN)
+ *   {
+ *     this.container.focus();
+ *   }
+ *   
+ *   graphFireMouseEvent.apply(this, arguments);
+ * };
+ * (end)
+ *
+ * Constructor: mxCellEditor
+ *
+ * Constructs a new in-place editor for the specified graph.
+ * 
+ * Parameters:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ */
+function mxCellEditor(graph)
+{
+	this.graph = graph;
+	
+	// Stops editing after zoom changes
+	this.zoomHandler = mxUtils.bind(this, function()
+	{
+		if (this.graph.isEditing())
+		{
+			this.resize();
+		}
+	});
+	
+	this.graph.view.addListener(mxEvent.SCALE, this.zoomHandler);
+	this.graph.view.addListener(mxEvent.SCALE_AND_TRANSLATE, this.zoomHandler);
+	
+	// Adds handling of deleted cells while editing
+	this.changeHandler = mxUtils.bind(this, function(sender)
+	{
+		if (this.editingCell != null && this.graph.getView().getState(this.editingCell) == null)
+		{
+			this.stopEditing(true);
+		}
+	});
+
+	this.graph.getModel().addListener(mxEvent.CHANGE, this.changeHandler);
+};
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxCellEditor.prototype.graph = null;
+
+/**
+ * Variable: textarea
+ *
+ * Holds the DIV that is used for text editing. Note that this may be null before the first
+ * edit. Instantiated in <init>.
+ */
+mxCellEditor.prototype.textarea = null;
+
+/**
+ * Variable: editingCell
+ * 
+ * Reference to the <mxCell> that is currently being edited.
+ */
+mxCellEditor.prototype.editingCell = null;
+
+/**
+ * Variable: trigger
+ * 
+ * Reference to the event that was used to start editing.
+ */
+mxCellEditor.prototype.trigger = null;
+
+/**
+ * Variable: modified
+ * 
+ * Specifies if the label has been modified.
+ */
+mxCellEditor.prototype.modified = false;
+
+/**
+ * Variable: autoSize
+ * 
+ * Specifies if the textarea should be resized while the text is being edited.
+ * Default is true.
+ */
+mxCellEditor.prototype.autoSize = true;
+
+/**
+ * Variable: selectText
+ * 
+ * Specifies if the text should be selected when editing starts. Default is
+ * true.
+ */
+mxCellEditor.prototype.selectText = true;
+
+/**
+ * Variable: emptyLabelText
+ * 
+ * Text to be displayed for empty labels. Default is '' or '<br>' in Firefox as
+ * a workaround for the missing cursor bug for empty content editable. This can
+ * be set to eg. "[Type Here]" to easier visualize editing of empty labels. The
+ * value is only displayed before the first keystroke and is never used as the
+ * actual editing value.
+ */
+mxCellEditor.prototype.emptyLabelText = (mxClient.IS_FF) ? '<br>' : '';
+
+/**
+ * Variable: escapeCancelsEditing
+ * 
+ * If true, pressing the escape key will stop editing and not accept the new
+ * value. Change this to false to accept the new value on escape, and cancel
+ * editing on Shift+Escape instead. Default is true.
+ */
+mxCellEditor.prototype.escapeCancelsEditing = true;
+
+/**
+ * Variable: textNode
+ * 
+ * Reference to the label DOM node that has been hidden.
+ */
+mxCellEditor.prototype.textNode = '';
+
+/**
+ * Variable: zIndex
+ * 
+ * Specifies the zIndex for the textarea. Default is 5.
+ */
+mxCellEditor.prototype.zIndex = 5;
+
+/**
+ * Variable: minResize
+ * 
+ * Defines the minimum width and height to be used in <resize>. Default is 0x20px.
+ */
+mxCellEditor.prototype.minResize = new mxRectangle(0, 20);
+
+/**
+ * Variable: wordWrapPadding
+ * 
+ * Correction factor for word wrapping width. Default is 2 in quirks, 0 in IE
+ * 11 and 1 in all other browsers and modes.
+ */
+mxCellEditor.prototype.wordWrapPadding = (mxClient.IS_QUIRKS) ? 2 : (!mxClient.IS_IE11) ? 1 : 0;
+
+/**
+ * Variable: blurEnabled
+ *
+ * If <focusLost> should be called if <textarea> loses the focus. Default is false.
+ */
+mxCellEditor.prototype.blurEnabled = false;
+
+/**
+ * Variable: initialValue
+ * 
+ * Holds the initial editing value to check if the current value was modified.
+ */
+mxCellEditor.prototype.initialValue = null;
+
+/**
+ * Function: init
+ *
+ * Creates the <textarea> and installs the event listeners. The key handler
+ * updates the <modified> state.
+ */
+mxCellEditor.prototype.init = function ()
+{
+	this.textarea = document.createElement('div');
+	this.textarea.className = 'mxCellEditor mxPlainTextEditor';
+	this.textarea.contentEditable = true;
+	
+	// Workaround for selection outside of DIV if height is 0
+	if (mxClient.IS_GC)
+	{
+		this.textarea.style.minHeight = '1em';
+	}
+	
+	this.installListeners(this.textarea);
+};
+
+/**
+ * Function: applyValue
+ * 
+ * Called in <stopEditing> if cancel is false to invoke <mxGraph.labelChanged>.
+ */
+mxCellEditor.prototype.applyValue = function(state, value)
+{
+	this.graph.labelChanged(state.cell, value, this.trigger);
+};
+
+/**
+ * Function: getInitialValue
+ * 
+ * Gets the initial editing value for the given cell.
+ */
+mxCellEditor.prototype.getInitialValue = function(state, trigger)
+{
+	var result = mxUtils.htmlEntities(this.graph.getEditingValue(state.cell, trigger), false);
+	
+    // Workaround for trailing line breaks being ignored in the editor
+	if (!mxClient.IS_QUIRKS && document.documentMode != 8 && document.documentMode != 9 &&
+		document.documentMode != 10)
+	{
+		result = mxUtils.replaceTrailingNewlines(result, '<div><br></div>');
+	}
+    
+    return result.replace(/\n/g, '<br>');
+};
+
+/**
+ * Function: getCurrentValue
+ * 
+ * Returns the current editing value.
+ */
+mxCellEditor.prototype.getCurrentValue = function(state)
+{
+	return mxUtils.extractTextWithWhitespace(this.textarea.childNodes);
+};
+
+/**
+ * Function: installListeners
+ * 
+ * Installs listeners for focus, change and standard key event handling.
+ */
+mxCellEditor.prototype.installListeners = function(elt)
+{
+	// Applies value if focus is lost
+	mxEvent.addListener(elt, 'blur', mxUtils.bind(this, function(evt)
+	{
+		if (this.blurEnabled)
+		{
+			this.focusLost(evt);
+		}
+	}));
+
+	// Updates modified state and handles placeholder text
+	mxEvent.addListener(elt, 'keydown', mxUtils.bind(this, function(evt)
+	{
+		if (!mxEvent.isConsumed(evt))
+		{
+			if (this.isStopEditingEvent(evt))
+			{
+				this.graph.stopEditing(false);
+				mxEvent.consume(evt);
+			}
+			else if (evt.keyCode == 27 /* Escape */)
+			{
+				this.graph.stopEditing(this.escapeCancelsEditing || mxEvent.isShiftDown(evt));
+				mxEvent.consume(evt);
+			}
+		}
+	}));
+
+	// Keypress only fires if printable key was pressed and handles removing the empty placeholder
+	var keypressHandler = mxUtils.bind(this, function(evt)
+	{
+		if (this.editingCell != null)
+		{
+			// Clears the initial empty label on the first keystroke
+			// and workaround for FF which fires keypress for delete and backspace
+			if (this.clearOnChange && elt.innerHTML == this.getEmptyLabelText() &&
+				(!mxClient.IS_FF || (evt.keyCode != 8 /* Backspace */ && evt.keyCode != 46 /* Delete */)))
+			{
+				this.clearOnChange = false;
+				elt.innerHTML = '';
+			}
+		}
+	});
+
+	mxEvent.addListener(elt, 'keypress', keypressHandler);
+	mxEvent.addListener(elt, 'paste', keypressHandler);
+	
+	// Handler for updating the empty label text value after a change
+	var keyupHandler = mxUtils.bind(this, function(evt)
+	{
+		if (this.editingCell != null)
+		{
+			// Uses an optional text value for sempty labels which is cleared
+			// when the first keystroke appears. This makes it easier to see
+			// that a label is being edited even if the label is empty.
+			// In Safari and FF, an empty text is represented by <BR> which isn't enough to force a valid size
+			if (this.textarea.innerHTML.length == 0 || this.textarea.innerHTML == '<br>')
+			{
+				this.textarea.innerHTML = this.getEmptyLabelText();
+				this.clearOnChange = this.textarea.innerHTML.length > 0;
+			}
+			else
+			{
+				this.clearOnChange = false;
+			}
+		}
+	});
+
+	mxEvent.addListener(elt, (!mxClient.IS_IE11 && !mxClient.IS_IE) ? 'input' : 'keyup', keyupHandler);
+	mxEvent.addListener(elt, 'cut', keyupHandler);
+	mxEvent.addListener(elt, 'paste', keyupHandler);
+
+	// Adds automatic resizing of the textbox while typing using input, keyup and/or DOM change events
+	var evtName = (!mxClient.IS_IE11 && !mxClient.IS_IE) ? 'input' : 'keydown';
+	
+	var resizeHandler = mxUtils.bind(this, function(evt)
+	{
+		if (this.editingCell != null && this.autoSize && !mxEvent.isConsumed(evt))
+		{
+			// Asynchronous is needed for keydown and shows better results for input events overall
+			// (ie non-blocking and cases where the offsetWidth/-Height was wrong at this time)
+			if (this.resizeThread != null)
+			{
+				window.clearTimeout(this.resizeThread);
+			}
+			
+			this.resizeThread = window.setTimeout(mxUtils.bind(this, function()
+			{
+				this.resizeThread = null;
+				this.resize();
+			}), 0);
+		}
+	});
+	
+	mxEvent.addListener(elt, evtName, resizeHandler);
+
+	if (document.documentMode >= 9)
+	{
+		mxEvent.addListener(elt, 'DOMNodeRemoved', resizeHandler);
+		mxEvent.addListener(elt, 'DOMNodeInserted', resizeHandler);
+	}
+	else
+	{
+		mxEvent.addListener(elt, 'cut', resizeHandler);
+		mxEvent.addListener(elt, 'paste', resizeHandler);
+	}
+};
+
+/**
+ * Function: isStopEditingEvent
+ * 
+ * Returns true if the given keydown event should stop cell editing. This
+ * returns true if F2 is pressed of if <mxGraph.enterStopsCellEditing> is true
+ * and enter is pressed without control or shift.
+ */
+mxCellEditor.prototype.isStopEditingEvent = function(evt)
+{
+	return evt.keyCode == 113 /* F2 */ || (this.graph.isEnterStopsCellEditing() &&
+		evt.keyCode == 13 /* Enter */ && !mxEvent.isControlDown(evt) &&
+		!mxEvent.isShiftDown(evt));
+};
+
+/**
+ * Function: isEventSource
+ * 
+ * Returns true if this editor is the source for the given native event.
+ */
+mxCellEditor.prototype.isEventSource = function(evt)
+{
+	return mxEvent.getSource(evt) == this.textarea;
+};
+
+/**
+ * Function: resize
+ * 
+ * Returns <modified>.
+ */
+mxCellEditor.prototype.resize = function()
+{
+	var state = this.graph.getView().getState(this.editingCell);
+	
+	if (state == null)
+	{
+		this.stopEditing(true);
+	}
+	else if (this.textarea != null)
+	{
+		var isEdge = this.graph.getModel().isEdge(state.cell);
+ 		var scale = this.graph.getView().scale;
+ 		var m = null;
+		
+		if (!this.autoSize || (state.style[mxConstants.STYLE_OVERFLOW] == 'fill'))
+		{
+			// Specifies the bounds of the editor box
+			this.bounds = this.getEditorBounds(state);
+			this.textarea.style.width = Math.round(this.bounds.width / scale) + 'px';
+			this.textarea.style.height = Math.round(this.bounds.height / scale) + 'px';
+			
+			// FIXME: Offset when scaled
+			if (document.documentMode == 8 || mxClient.IS_QUIRKS)
+			{
+				this.textarea.style.left = Math.round(this.bounds.x) + 'px';
+				this.textarea.style.top = Math.round(this.bounds.y) + 'px';
+			}
+			else
+			{
+				this.textarea.style.left = Math.max(0, Math.round(this.bounds.x + 1)) + 'px';
+				this.textarea.style.top = Math.max(0, Math.round(this.bounds.y + 1)) + 'px';
+			}
+			
+			// Installs native word wrapping and avoids word wrap for empty label placeholder
+			if (this.graph.isWrapping(state.cell) && (this.bounds.width >= 2 || this.bounds.height >= 2) &&
+				this.textarea.innerHTML != this.getEmptyLabelText())
+			{
+				this.textarea.style.wordWrap = mxConstants.WORD_WRAP;
+				this.textarea.style.whiteSpace = 'normal';
+				
+				if (state.style[mxConstants.STYLE_OVERFLOW] != 'fill')
+				{
+					this.textarea.style.width = Math.round(this.bounds.width / scale) + this.wordWrapPadding + 'px';
+				}
+			}
+			else
+			{
+				this.textarea.style.whiteSpace = 'nowrap';
+				
+				if (state.style[mxConstants.STYLE_OVERFLOW] != 'fill')
+				{
+					this.textarea.style.width = '';
+				}
+			}
+		}
+		else
+	 	{
+	 		var lw = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_WIDTH, null);
+			m = (state.text != null) ? state.text.margin : null;
+			
+			if (m == null)
+			{
+				m = mxUtils.getAlignmentAsPoint(mxUtils.getValue(state.style, mxConstants.STYLE_ALIGN, mxConstants.ALIGN_CENTER),
+						mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_ALIGN, mxConstants.ALIGN_MIDDLE));
+			}
+			
+	 		if (isEdge)
+			{
+				this.bounds = new mxRectangle(state.absoluteOffset.x, state.absoluteOffset.y, 0, 0);
+				
+				if (lw != null)
+			 	{
+					var tmp = (parseFloat(lw) + 2) * scale;
+					this.bounds.width = tmp;
+					this.bounds.x += m.x * tmp;
+			 	}
+			}
+			else
+			{
+				var bds = mxRectangle.fromRectangle(state);
+				var hpos = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);
+				var vpos = mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);
+
+				bds = (state.shape != null && hpos == mxConstants.ALIGN_CENTER && vpos == mxConstants.ALIGN_MIDDLE) ? state.shape.getLabelBounds(bds) : bds;
+			 	
+			 	if (lw != null)
+			 	{
+			 		bds.width = parseFloat(lw) * scale;
+			 	}
+			 	
+			 	if (!state.view.graph.cellRenderer.legacySpacing || state.style[mxConstants.STYLE_OVERFLOW] != 'width')
+			 	{
+					var spacing = parseInt(state.style[mxConstants.STYLE_SPACING] || 2) * scale;
+					var spacingTop = (parseInt(state.style[mxConstants.STYLE_SPACING_TOP] || 0) + mxText.prototype.baseSpacingTop) * scale + spacing;
+					var spacingRight = (parseInt(state.style[mxConstants.STYLE_SPACING_RIGHT] || 0) + mxText.prototype.baseSpacingRight) * scale + spacing;
+					var spacingBottom = (parseInt(state.style[mxConstants.STYLE_SPACING_BOTTOM] || 0) + mxText.prototype.baseSpacingBottom) * scale + spacing;
+					var spacingLeft = (parseInt(state.style[mxConstants.STYLE_SPACING_LEFT] || 0) + mxText.prototype.baseSpacingLeft) * scale + spacing;
+					
+					var hpos = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);
+					var vpos = mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);
+
+					bds = new mxRectangle(bds.x + spacingLeft, bds.y + spacingTop,
+						bds.width - ((hpos == mxConstants.ALIGN_CENTER && lw == null) ? (spacingLeft + spacingRight) : 0),
+						bds.height - ((vpos == mxConstants.ALIGN_MIDDLE) ? (spacingTop + spacingBottom) : 0));
+			 	}
+
+				this.bounds = new mxRectangle(bds.x + state.absoluteOffset.x, bds.y + state.absoluteOffset.y, bds.width, bds.height);
+			}
+
+			// Needed for word wrap inside text blocks with oversize lines to match the final result where
+	 		// the width of the longest line is used as the reference for text alignment in the cell
+	 		// TODO: Fix word wrapping preview for edge labels in helloworld.html
+			if (this.graph.isWrapping(state.cell) && (this.bounds.width >= 2 || this.bounds.height >= 2) &&
+				this.textarea.innerHTML != this.getEmptyLabelText())
+			{
+				this.textarea.style.wordWrap = mxConstants.WORD_WRAP;
+				this.textarea.style.whiteSpace = 'normal';
+				
+		 		// Forces automatic reflow if text is removed from an oversize label and normal word wrap
+				var tmp = Math.round(this.bounds.width / ((document.documentMode == 8) ? scale : scale)) + this.wordWrapPadding;
+				this.textarea.style.width = tmp + 'px';
+				
+				if (this.textarea.scrollWidth > tmp)
+				{
+					this.textarea.style.width = this.textarea.scrollWidth + 'px';
+				}
+			}
+			else
+			{
+				// KNOWN: Trailing cursor in IE9 quirks mode is not visible
+				this.textarea.style.whiteSpace = 'nowrap';
+				this.textarea.style.width = '';
+			}
+			
+			// LATER: Keep in visible area, add fine tuning for pixel precision
+			// Workaround for wrong measuring in IE8 standards
+			if (document.documentMode == 8)
+			{
+				this.textarea.style.zoom = '1';
+				this.textarea.style.height = 'auto';
+			}
+			
+			var ow = this.textarea.scrollWidth;
+			var oh = this.textarea.scrollHeight;
+			
+			// TODO: Update CSS width and height if smaller than minResize or remove minResize
+			//if (this.minResize != null)
+			//{
+			//	ow = Math.max(ow, this.minResize.width);
+			//	oh = Math.max(oh, this.minResize.height);
+			//}
+			
+			// LATER: Keep in visible area, add fine tuning for pixel precision
+			if (document.documentMode == 8)
+			{
+				// LATER: Scaled wrapping and position is wrong in IE8
+				this.textarea.style.left = Math.max(0, Math.ceil((this.bounds.x - m.x * (this.bounds.width - (ow + 1) * scale) + ow * (scale - 1) * 0 + (m.x + 0.5) * 2) / scale)) + 'px';
+				this.textarea.style.top = Math.max(0, Math.ceil((this.bounds.y - m.y * (this.bounds.height - (oh + 0.5) * scale) + oh * (scale - 1) * 0 + Math.abs(m.y + 0.5) * 1) / scale)) + 'px';
+				// Workaround for wrong event handling width and height
+				this.textarea.style.width = Math.round(ow * scale) + 'px';
+				this.textarea.style.height = Math.round(oh * scale) + 'px';
+			}
+			else if (mxClient.IS_QUIRKS)
+			{			
+				this.textarea.style.left = Math.max(0, Math.ceil(this.bounds.x - m.x * (this.bounds.width - (ow + 1) * scale) + ow * (scale - 1) * 0 + (m.x + 0.5) * 2)) + 'px';
+				this.textarea.style.top = Math.max(0, Math.ceil(this.bounds.y - m.y * (this.bounds.height - (oh + 0.5) * scale) + oh * (scale - 1) * 0 + Math.abs(m.y + 0.5) * 1)) + 'px';
+			}
+			else
+			{
+				this.textarea.style.left = Math.max(0, Math.round(this.bounds.x - m.x * (this.bounds.width - 2)) + 1) + 'px';
+				this.textarea.style.top = Math.max(0, Math.round(this.bounds.y - m.y * (this.bounds.height - 4) + ((m.y == -1) ? 3 : 0)) + 1) + 'px';
+			}
+	 	}
+
+		if (mxClient.IS_VML)
+		{
+			this.textarea.style.zoom = scale;
+		}
+		else
+		{
+			mxUtils.setPrefixedStyle(this.textarea.style, 'transformOrigin', '0px 0px');
+			mxUtils.setPrefixedStyle(this.textarea.style, 'transform',
+				'scale(' + scale + ',' + scale + ')' + ((m == null) ? '' :
+				' translate(' + (m.x * 100) + '%,' + (m.y * 100) + '%)'));
+		}
+	}
+};
+
+/**
+ * Function: focusLost
+ *
+ * Called if the textarea has lost focus.
+ */
+mxCellEditor.prototype.focusLost = function()
+{
+	this.stopEditing(!this.graph.isInvokesStopCellEditing());
+};
+
+/**
+ * Function: getBackgroundColor
+ * 
+ * Returns the background color for the in-place editor. This implementation
+ * always returns null.
+ */
+mxCellEditor.prototype.getBackgroundColor = function(state)
+{
+	return null;
+};
+
+/**
+ * Function: startEditing
+ *
+ * Starts the editor for the given cell.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to start editing.
+ * trigger - Optional mouse event that triggered the editor.
+ */
+mxCellEditor.prototype.startEditing = function(cell, trigger)
+{
+	this.stopEditing(true);
+	
+	// Creates new textarea instance
+	if (this.textarea == null)
+	{
+		this.init();
+	}
+	
+	if (this.graph.tooltipHandler != null)
+	{
+		this.graph.tooltipHandler.hideTooltip();
+	}
+	
+	var state = this.graph.getView().getState(cell);
+	
+	if (state != null)
+	{
+		// Configures the style of the in-place editor
+		var scale = this.graph.getView().scale;
+		var size = mxUtils.getValue(state.style, mxConstants.STYLE_FONTSIZE, mxConstants.DEFAULT_FONTSIZE);
+		var family = mxUtils.getValue(state.style, mxConstants.STYLE_FONTFAMILY, mxConstants.DEFAULT_FONTFAMILY);
+		var color = mxUtils.getValue(state.style, mxConstants.STYLE_FONTCOLOR, 'black');
+		var align = mxUtils.getValue(state.style, mxConstants.STYLE_ALIGN, mxConstants.ALIGN_LEFT);
+		var bold = (mxUtils.getValue(state.style, mxConstants.STYLE_FONTSTYLE, 0) &
+				mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD;
+		var italic = (mxUtils.getValue(state.style, mxConstants.STYLE_FONTSTYLE, 0) &
+				mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC;
+		var uline = (mxUtils.getValue(state.style, mxConstants.STYLE_FONTSTYLE, 0) &
+				mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE;
+		
+		this.textarea.style.lineHeight = (mxConstants.ABSOLUTE_LINE_HEIGHT) ? Math.round(size * mxConstants.LINE_HEIGHT) + 'px' : mxConstants.LINE_HEIGHT;
+		this.textarea.style.backgroundColor = this.getBackgroundColor(state);
+		this.textarea.style.textDecoration = (uline) ? 'underline' : '';
+		this.textarea.style.fontWeight = (bold) ? 'bold' : 'normal';
+		this.textarea.style.fontStyle = (italic) ? 'italic' : '';
+		this.textarea.style.fontSize = Math.round(size) + 'px';
+		this.textarea.style.zIndex = this.zIndex;
+		this.textarea.style.fontFamily = family;
+		this.textarea.style.textAlign = align;
+		this.textarea.style.outline = 'none';
+		this.textarea.style.color = color;
+		
+		var dir = this.textDirection = mxUtils.getValue(state.style, mxConstants.STYLE_TEXT_DIRECTION, mxConstants.DEFAULT_TEXT_DIRECTION);
+		
+		if (dir == mxConstants.TEXT_DIRECTION_AUTO)
+		{
+			if (state != null && state.text != null && state.text.dialect != mxConstants.DIALECT_STRICTHTML &&
+				!mxUtils.isNode(state.text.value))
+			{
+				dir = state.text.getAutoDirection();
+			}
+		}
+		
+		if (dir == mxConstants.TEXT_DIRECTION_LTR || dir == mxConstants.TEXT_DIRECTION_RTL)
+		{
+			this.textarea.setAttribute('dir', dir);
+		}
+		else
+		{
+			this.textarea.removeAttribute('dir');
+		}
+
+		// Sets the initial editing value
+		this.textarea.innerHTML = this.getInitialValue(state, trigger) || '';
+		this.initialValue = this.textarea.innerHTML;
+
+		// Uses an optional text value for empty labels which is cleared
+		// when the first keystroke appears. This makes it easier to see
+		// that a label is being edited even if the label is empty.
+		if (this.textarea.innerHTML.length == 0 || this.textarea.innerHTML == '<br>')
+		{
+			this.textarea.innerHTML = this.getEmptyLabelText();
+			this.clearOnChange = true;
+		}
+		else
+		{
+			this.clearOnChange = this.textarea.innerHTML == this.getEmptyLabelText();
+		}
+
+		this.graph.container.appendChild(this.textarea);
+		
+		// Update this after firing all potential events that could update the cleanOnChange flag
+		this.editingCell = cell;
+		this.trigger = trigger;
+		this.textNode = null;
+
+		if (state.text != null && this.isHideLabel(state))
+		{
+			this.textNode = state.text.node;
+			this.textNode.style.visibility = 'hidden';
+		}
+
+		// Workaround for initial offsetHeight not ready for heading in markup
+		if (this.autoSize && (this.graph.model.isEdge(state.cell) || state.style[mxConstants.STYLE_OVERFLOW] != 'fill'))
+		{
+			window.setTimeout(mxUtils.bind(this, function()
+			{
+				this.resize();
+			}), 0);
+		}
+		
+		this.resize();
+		
+		// Workaround for NS_ERROR_FAILURE in FF
+		try
+		{
+			// Prefers blinking cursor over no selected text if empty
+			this.textarea.focus();
+			
+			if (this.isSelectText() && this.textarea.innerHTML.length > 0 &&
+				(this.textarea.innerHTML != this.getEmptyLabelText() || !this.clearOnChange))
+			{
+				document.execCommand('selectAll', false, null);
+			}
+		}
+		catch (e)
+		{
+			// ignore
+		}
+	}
+};
+
+/**
+ * Function: isSelectText
+ * 
+ * Returns <selectText>.
+ */
+mxCellEditor.prototype.isSelectText = function()
+{
+	return this.selectText;
+};
+
+/**
+ * Function: stopEditing
+ *
+ * Stops the editor and applies the value if cancel is false.
+ */
+mxCellEditor.prototype.stopEditing = function(cancel)
+{
+	cancel = cancel || false;
+	
+	if (this.editingCell != null)
+	{
+		if (this.textNode != null)
+		{
+			this.textNode.style.visibility = 'visible';
+			this.textNode = null;
+		}
+
+		var state = (!cancel) ? this.graph.view.getState(this.editingCell) : null;
+
+		var initial = this.initialValue;
+		this.initialValue = null;
+		this.editingCell = null;
+		this.trigger = null;
+		this.bounds = null;
+		this.textarea.blur();
+		
+		if (this.textarea.parentNode != null)
+		{
+			this.textarea.parentNode.removeChild(this.textarea);
+		}
+		
+		if (this.clearOnChange && this.textarea.innerHTML == this.getEmptyLabelText())
+		{
+			this.textarea.innerHTML = '';
+			this.clearOnChange = false;
+		}
+		
+		if (state != null && this.textarea.innerHTML != initial)
+		{
+			this.prepareTextarea();
+			var value = this.getCurrentValue(state);
+			
+			if (value != null)
+			{
+				this.applyValue(state, value);
+			}
+		}
+		
+		// Forces new instance on next edit for undo history reset
+		mxEvent.release(this.textarea);
+		this.textarea = null;
+	}
+};
+
+/**
+ * Function: prepareTextarea
+ * 
+ * Prepares the textarea for getting its value in <stopEditing>.
+ * This implementation removes the extra trailing linefeed in Firefox.
+ */
+mxCellEditor.prototype.prepareTextarea = function()
+{
+	if (mxClient.IS_FF && this.textarea.lastChild != null &&
+		this.textarea.lastChild.nodeName == 'BR')
+	{
+		this.textarea.removeChild(this.textarea.lastChild);
+	}
+};
+
+/**
+ * Function: isHideLabel
+ * 
+ * Returns true if the label should be hidden while the cell is being
+ * edited.
+ */
+mxCellEditor.prototype.isHideLabel = function(state)
+{
+	return true;
+};
+
+/**
+ * Function: getMinimumSize
+ * 
+ * Returns the minimum width and height for editing the given state.
+ */
+mxCellEditor.prototype.getMinimumSize = function(state)
+{
+	var scale = this.graph.getView().scale;
+	
+	return new mxRectangle(0, 0, (state.text == null) ? 30 : state.text.size * scale + 20,
+			(this.textarea.style.textAlign == 'left') ? 120 : 40);
+};
+
+/**
+ * Function: getEditorBounds
+ * 
+ * Returns the <mxRectangle> that defines the bounds of the editor.
+ */
+mxCellEditor.prototype.getEditorBounds = function(state)
+{
+	var isEdge = this.graph.getModel().isEdge(state.cell);
+	var scale = this.graph.getView().scale;
+	var minSize = this.getMinimumSize(state);
+	var minWidth = minSize.width;
+ 	var minHeight = minSize.height;
+ 	var result = null;
+ 	
+ 	if (!isEdge && state.view.graph.cellRenderer.legacySpacing && state.style[mxConstants.STYLE_OVERFLOW] == 'fill')
+ 	{
+ 		result = state.shape.getLabelBounds(mxRectangle.fromRectangle(state));
+ 	}
+ 	else
+ 	{
+		var spacing = parseInt(state.style[mxConstants.STYLE_SPACING] || 0) * scale;
+		var spacingTop = (parseInt(state.style[mxConstants.STYLE_SPACING_TOP] || 0) + mxText.prototype.baseSpacingTop) * scale + spacing;
+		var spacingRight = (parseInt(state.style[mxConstants.STYLE_SPACING_RIGHT] || 0) + mxText.prototype.baseSpacingRight) * scale + spacing;
+		var spacingBottom = (parseInt(state.style[mxConstants.STYLE_SPACING_BOTTOM] || 0) + mxText.prototype.baseSpacingBottom) * scale + spacing;
+		var spacingLeft = (parseInt(state.style[mxConstants.STYLE_SPACING_LEFT] || 0) + mxText.prototype.baseSpacingLeft) * scale + spacing;
+	
+	 	result = new mxRectangle(state.x, state.y,
+	 		 Math.max(minWidth, state.width - spacingLeft - spacingRight),
+	 		 Math.max(minHeight, state.height - spacingTop - spacingBottom));
+		var hpos = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);
+		var vpos = mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);
+		
+		result = (state.shape != null && hpos == mxConstants.ALIGN_CENTER && vpos == mxConstants.ALIGN_MIDDLE) ? state.shape.getLabelBounds(result) : result;
+	
+		if (isEdge)
+		{
+			result.x = state.absoluteOffset.x;
+			result.y = state.absoluteOffset.y;
+	
+			if (state.text != null && state.text.boundingBox != null)
+			{
+				// Workaround for label containing just spaces in which case
+				// the bounding box location contains negative numbers 
+				if (state.text.boundingBox.x > 0)
+				{
+					result.x = state.text.boundingBox.x;
+				}
+				
+				if (state.text.boundingBox.y > 0)
+				{
+					result.y = state.text.boundingBox.y;
+				}
+			}
+		}
+		else if (state.text != null && state.text.boundingBox != null)
+		{
+			result.x = Math.min(result.x, state.text.boundingBox.x);
+			result.y = Math.min(result.y, state.text.boundingBox.y);
+		}
+	
+		result.x += spacingLeft;
+		result.y += spacingTop;
+	
+		if (state.text != null && state.text.boundingBox != null)
+		{
+			if (!isEdge)
+			{
+				result.width = Math.max(result.width, state.text.boundingBox.width);
+				result.height = Math.max(result.height, state.text.boundingBox.height);
+			}
+			else
+			{
+				result.width = Math.max(minWidth, state.text.boundingBox.width);
+				result.height = Math.max(minHeight, state.text.boundingBox.height);
+			}
+		}
+		
+		// Applies the horizontal and vertical label positions
+		if (this.graph.getModel().isVertex(state.cell))
+		{
+			var horizontal = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);
+	
+			if (horizontal == mxConstants.ALIGN_LEFT)
+			{
+				result.x -= state.width;
+			}
+			else if (horizontal == mxConstants.ALIGN_RIGHT)
+			{
+				result.x += state.width;
+			}
+	
+			var vertical = mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);
+	
+			if (vertical == mxConstants.ALIGN_TOP)
+			{
+				result.y -= state.height;
+			}
+			else if (vertical == mxConstants.ALIGN_BOTTOM)
+			{
+				result.y += state.height;
+			}
+		}
+ 	}
+ 	
+ 	return new mxRectangle(Math.round(result.x), Math.round(result.y), Math.round(result.width), Math.round(result.height));
+};
+
+/**
+ * Function: getEmptyLabelText
+ *
+ * Returns the initial label value to be used of the label of the given
+ * cell is empty. This label is displayed and cleared on the first keystroke.
+ * This implementation returns <emptyLabelText>.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> for which a text for an empty editing box should be
+ * returned.
+ */
+mxCellEditor.prototype.getEmptyLabelText = function (cell)
+{
+	return this.emptyLabelText;
+};
+
+/**
+ * Function: getEditingCell
+ *
+ * Returns the cell that is currently being edited or null if no cell is
+ * being edited.
+ */
+mxCellEditor.prototype.getEditingCell = function ()
+{
+	return this.editingCell;
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the editor and removes all associated resources.
+ */
+mxCellEditor.prototype.destroy = function ()
+{
+	if (this.textarea != null)
+	{
+		mxEvent.release(this.textarea);
+		
+		if (this.textarea.parentNode != null)
+		{
+			this.textarea.parentNode.removeChild(this.textarea);
+		}
+		
+		this.textarea = null;
+
+	}
+			
+	if (this.changeHandler != null)
+	{
+		this.graph.getModel().removeListener(this.changeHandler);
+		this.changeHandler = null;
+	}
+
+	if (this.zoomHandler)
+	{
+		this.graph.view.removeListener(this.zoomHandler);
+		this.zoomHandler = null;
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxCellRenderer
+ * 
+ * Renders cells into a document object model. The <defaultShapes> is a global
+ * map of shapename, constructor pairs that is used in all instances. You can
+ * get a list of all available shape names using the following code.
+ * 
+ * In general the cell renderer is in charge of creating, redrawing and
+ * destroying the shape and label associated with a cell state, as well as
+ * some other graphical objects, namely controls and overlays. The shape
+ * hieararchy in the display (ie. the hierarchy in which the DOM nodes
+ * appear in the document) does not reflect the cell hierarchy. The shapes
+ * are a (flat) sequence of shapes and labels inside the draw pane of the
+ * graph view, with some exceptions, namely the HTML labels being placed
+ * directly inside the graph container for certain browsers.
+ * 
+ * (code)
+ * mxLog.show();
+ * for (var i in mxCellRenderer.prototype.defaultShapes)
+ * {
+ *   mxLog.debug(i);
+ * }
+ * (end)
+ *
+ * Constructor: mxCellRenderer
+ * 
+ * Constructs a new cell renderer with the following built-in shapes:
+ * arrow, rectangle, ellipse, rhombus, image, line, label, cylinder,
+ * swimlane, connector, actor and cloud.
+ */
+function mxCellRenderer() { };
+
+/**
+ * Variable: defaultEdgeShape
+ * 
+ * Defines the default shape for edges. Default is <mxConnector>.
+ */
+mxCellRenderer.prototype.defaultEdgeShape = mxConnector;
+
+/**
+ * Variable: defaultVertexShape
+ * 
+ * Defines the default shape for vertices. Default is <mxRectangleShape>.
+ */
+mxCellRenderer.prototype.defaultVertexShape = mxRectangleShape;
+
+/**
+ * Variable: defaultTextShape
+ * 
+ * Defines the default shape for labels. Default is <mxText>.
+ */
+mxCellRenderer.prototype.defaultTextShape = mxText;
+
+/**
+ * Variable: legacyControlPosition
+ * 
+ * Specifies if the folding icon should ignore the horizontal
+ * orientation of a swimlane. Default is true.
+ */
+mxCellRenderer.prototype.legacyControlPosition = true;
+
+/**
+ * Variable: legacySpacing
+ * 
+ * Specifies if spacing and label position should be ignored if overflow is
+ * fill or width. Default is true for backwards compatiblity.
+ */
+mxCellRenderer.prototype.legacySpacing = true;
+
+/**
+ * Variable: defaultShapes
+ * 
+ * Static array that contains the globally registered shapes which are
+ * known to all instances of this class. For adding new shapes you should
+ * use the static <mxCellRenderer.registerShape> function.
+ */
+mxCellRenderer.prototype.defaultShapes = new Object();
+
+/**
+ * Variable: antiAlias
+ * 
+ * Anti-aliasing option for new shapes. Default is true.
+ */
+mxCellRenderer.prototype.antiAlias = true;
+
+/**
+ * Variable: forceControlClickHandler
+ * 
+ * Specifies if the enabled state of the graph should be ignored in the control
+ * click handler (to allow folding in disabled graphs). Default is false.
+ */
+mxCellRenderer.prototype.forceControlClickHandler = false;
+
+/**
+ * Function: registerShape
+ * 
+ * Registers the given constructor under the specified key in this instance
+ * of the renderer.
+ * 
+ * Example:
+ * 
+ * (code)
+ * mxCellRenderer.registerShape(mxConstants.SHAPE_RECTANGLE, mxRectangleShape);
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * key - String representing the shape name.
+ * shape - Constructor of the <mxShape> subclass.
+ */
+mxCellRenderer.registerShape = function(key, shape)
+{
+	mxCellRenderer.prototype.defaultShapes[key] = shape;
+};
+
+// Adds default shapes into the default shapes array
+mxCellRenderer.registerShape(mxConstants.SHAPE_RECTANGLE, mxRectangleShape);
+mxCellRenderer.registerShape(mxConstants.SHAPE_ELLIPSE, mxEllipse);
+mxCellRenderer.registerShape(mxConstants.SHAPE_RHOMBUS, mxRhombus);
+mxCellRenderer.registerShape(mxConstants.SHAPE_CYLINDER, mxCylinder);
+mxCellRenderer.registerShape(mxConstants.SHAPE_CONNECTOR, mxConnector);
+mxCellRenderer.registerShape(mxConstants.SHAPE_ACTOR, mxActor);
+mxCellRenderer.registerShape(mxConstants.SHAPE_TRIANGLE, mxTriangle);
+mxCellRenderer.registerShape(mxConstants.SHAPE_HEXAGON, mxHexagon);
+mxCellRenderer.registerShape(mxConstants.SHAPE_CLOUD, mxCloud);
+mxCellRenderer.registerShape(mxConstants.SHAPE_LINE, mxLine);
+mxCellRenderer.registerShape(mxConstants.SHAPE_ARROW, mxArrow);
+mxCellRenderer.registerShape(mxConstants.SHAPE_ARROW_CONNECTOR, mxArrowConnector);
+mxCellRenderer.registerShape(mxConstants.SHAPE_DOUBLE_ELLIPSE, mxDoubleEllipse);
+mxCellRenderer.registerShape(mxConstants.SHAPE_SWIMLANE, mxSwimlane);
+mxCellRenderer.registerShape(mxConstants.SHAPE_IMAGE, mxImageShape);
+mxCellRenderer.registerShape(mxConstants.SHAPE_LABEL, mxLabel);
+
+/**
+ * Function: initializeShape
+ * 
+ * Initializes the shape in the given state by calling its init method with
+ * the correct container after configuring it using <configureShape>.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> for which the shape should be initialized.
+ */
+mxCellRenderer.prototype.initializeShape = function(state)
+{
+	state.shape.dialect = state.view.graph.dialect;
+	this.configureShape(state);
+	state.shape.init(state.view.getDrawPane());
+};
+
+/**
+ * Function: createShape
+ * 
+ * Creates and returns the shape for the given cell state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> for which the shape should be created.
+ */
+mxCellRenderer.prototype.createShape = function(state)
+{
+	var shape = null;
+	
+	if (state.style != null)
+	{
+		// Checks if there is a stencil for the name and creates
+		// a shape instance for the stencil if one exists
+		var stencil = mxStencilRegistry.getStencil(state.style[mxConstants.STYLE_SHAPE]);
+		
+		if (stencil != null)
+		{
+			shape = new mxShape(stencil);
+		}
+		else
+		{
+			var ctor = this.getShapeConstructor(state);
+			shape = new ctor();
+		}
+	}
+	
+	return shape;
+};
+
+/**
+ * Function: createIndicatorShape
+ * 
+ * Creates the indicator shape for the given cell state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> for which the indicator shape should be created.
+ */
+mxCellRenderer.prototype.createIndicatorShape = function(state)
+{
+	state.shape.indicatorShape = this.getShape(state.view.graph.getIndicatorShape(state));
+};
+
+/**
+ * Function: getShape
+ * 
+ * Returns the shape for the given name from <defaultShapes>.
+ */
+mxCellRenderer.prototype.getShape = function(name)
+{
+	return (name != null) ? mxCellRenderer.prototype.defaultShapes[name] : null;
+};
+
+/**
+ * Function: getShapeConstructor
+ * 
+ * Returns the constructor to be used for creating the shape.
+ */
+mxCellRenderer.prototype.getShapeConstructor = function(state)
+{
+	var ctor = this.getShape(state.style[mxConstants.STYLE_SHAPE]);
+	
+	if (ctor == null)
+	{
+		ctor = (state.view.graph.getModel().isEdge(state.cell)) ?
+			this.defaultEdgeShape : this.defaultVertexShape;
+	}
+	
+	return ctor;
+};
+
+/**
+ * Function: configureShape
+ * 
+ * Configures the shape for the given cell state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> for which the shape should be configured.
+ */
+mxCellRenderer.prototype.configureShape = function(state)
+{
+	state.shape.apply(state);
+	state.shape.image = state.view.graph.getImage(state);
+	state.shape.indicatorColor = state.view.graph.getIndicatorColor(state);
+	state.shape.indicatorStrokeColor = state.style[mxConstants.STYLE_INDICATOR_STROKECOLOR];
+	state.shape.indicatorGradientColor = state.view.graph.getIndicatorGradientColor(state);
+	state.shape.indicatorDirection = state.style[mxConstants.STYLE_INDICATOR_DIRECTION];
+	state.shape.indicatorImage = state.view.graph.getIndicatorImage(state);
+
+	this.postConfigureShape(state);
+};
+
+/**
+ * Function: postConfigureShape
+ * 
+ * Replaces any reserved words used for attributes, eg. inherit,
+ * indicated or swimlane for colors in the shape for the given state.
+ * This implementation resolves these keywords on the fill, stroke
+ * and gradient color keys.
+ */
+mxCellRenderer.prototype.postConfigureShape = function(state)
+{
+	if (state.shape != null)
+	{
+		this.resolveColor(state, 'indicatorColor', mxConstants.STYLE_FILLCOLOR);
+		this.resolveColor(state, 'indicatorGradientColor', mxConstants.STYLE_GRADIENTCOLOR);
+		this.resolveColor(state, 'fill', mxConstants.STYLE_FILLCOLOR);
+		this.resolveColor(state, 'stroke', mxConstants.STYLE_STROKECOLOR);
+		this.resolveColor(state, 'gradient', mxConstants.STYLE_GRADIENTCOLOR);
+	}
+};
+
+/**
+ * Function: resolveColor
+ * 
+ * Resolves special keywords 'inherit', 'indicated' and 'swimlane' and sets
+ * the respective color on the shape.
+ */
+mxCellRenderer.prototype.resolveColor = function(state, field, key)
+{
+	var value = state.shape[field];
+	var graph = state.view.graph;
+	var referenced = null;
+	
+	if (value == 'inherit')
+	{
+		referenced = graph.model.getParent(state.cell);
+	}
+	else if (value == 'swimlane')
+	{
+		if (graph.model.getTerminal(state.cell, false) != null)
+		{
+			referenced = graph.model.getTerminal(state.cell, false);
+		}
+		else
+		{
+			referenced = state.cell;
+		}
+		
+		referenced = graph.getSwimlane(referenced);
+		key = graph.swimlaneIndicatorColorAttribute;
+	}
+	else if (value == 'indicated')
+	{
+		state.shape[field] = state.shape.indicatorColor;
+	}
+	
+	if (referenced != null)
+	{
+		var rstate = graph.getView().getState(referenced);
+		state.shape[field] = null;
+
+		if (rstate != null)
+		{
+			if (rstate.shape != null && field != 'indicatorColor')
+			{
+				state.shape[field] = rstate.shape[field];
+			}
+			else
+			{
+				state.shape[field] = rstate.style[key];
+			}
+		}
+	}
+};
+
+/**
+ * Function: getLabelValue
+ * 
+ * Returns the value to be used for the label.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> for which the label should be created.
+ */
+mxCellRenderer.prototype.getLabelValue = function(state)
+{
+	return state.view.graph.getLabel(state.cell);
+};
+
+/**
+ * Function: createLabel
+ * 
+ * Creates the label for the given cell state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> for which the label should be created.
+ */
+mxCellRenderer.prototype.createLabel = function(state, value)
+{
+	var graph = state.view.graph;
+	var isEdge = graph.getModel().isEdge(state.cell);
+	
+	if (state.style[mxConstants.STYLE_FONTSIZE] > 0 || state.style[mxConstants.STYLE_FONTSIZE] == null)
+	{
+		// Avoids using DOM node for empty labels
+		var isForceHtml = (graph.isHtmlLabel(state.cell) || (value != null && mxUtils.isNode(value)));
+
+		state.text = new this.defaultTextShape(value, new mxRectangle(),
+				(state.style[mxConstants.STYLE_ALIGN] || mxConstants.ALIGN_CENTER),
+				graph.getVerticalAlign(state),
+				state.style[mxConstants.STYLE_FONTCOLOR],
+				state.style[mxConstants.STYLE_FONTFAMILY],
+				state.style[mxConstants.STYLE_FONTSIZE],
+				state.style[mxConstants.STYLE_FONTSTYLE],
+				state.style[mxConstants.STYLE_SPACING],
+				state.style[mxConstants.STYLE_SPACING_TOP],
+				state.style[mxConstants.STYLE_SPACING_RIGHT],
+				state.style[mxConstants.STYLE_SPACING_BOTTOM],
+				state.style[mxConstants.STYLE_SPACING_LEFT],
+				state.style[mxConstants.STYLE_HORIZONTAL],
+				state.style[mxConstants.STYLE_LABEL_BACKGROUNDCOLOR],
+				state.style[mxConstants.STYLE_LABEL_BORDERCOLOR],
+				graph.isWrapping(state.cell) && graph.isHtmlLabel(state.cell),
+				graph.isLabelClipped(state.cell),
+				state.style[mxConstants.STYLE_OVERFLOW],
+				state.style[mxConstants.STYLE_LABEL_PADDING],
+				mxUtils.getValue(state.style, mxConstants.STYLE_TEXT_DIRECTION, mxConstants.DEFAULT_TEXT_DIRECTION));
+		state.text.opacity = mxUtils.getValue(state.style, mxConstants.STYLE_TEXT_OPACITY, 100);
+		state.text.dialect = (isForceHtml) ? mxConstants.DIALECT_STRICTHTML : state.view.graph.dialect;
+		state.text.style = state.style;
+		state.text.state = state;
+		this.initializeLabel(state, state.text);
+		
+		// Workaround for touch devices routing all events for a mouse gesture
+		// (down, move, up) via the initial DOM node. IE additionally redirects
+		// the event via the initial DOM node but the event source is the node
+		// under the mouse, so we need to check if this is the case and force
+		// getCellAt for the subsequent mouseMoves and the final mouseUp.
+		var forceGetCell = false;
+		
+		var getState = function(evt)
+		{
+			var result = state;
+
+			if (mxClient.IS_TOUCH || forceGetCell)
+			{
+				var x = mxEvent.getClientX(evt);
+				var y = mxEvent.getClientY(evt);
+				
+				// Dispatches the drop event to the graph which
+				// consumes and executes the source function
+				var pt = mxUtils.convertPoint(graph.container, x, y);
+				result = graph.view.getState(graph.getCellAt(pt.x, pt.y));
+			}
+			
+			return result;
+		};
+		
+		// TODO: Add handling for special touch device gestures
+		mxEvent.addGestureListeners(state.text.node,
+			mxUtils.bind(this, function(evt)
+			{
+				if (this.isLabelEvent(state, evt))
+				{
+					graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt, state));
+					forceGetCell = graph.dialect != mxConstants.DIALECT_SVG &&
+						mxEvent.getSource(evt).nodeName == 'IMG';
+				}
+			}),
+			mxUtils.bind(this, function(evt)
+			{
+				if (this.isLabelEvent(state, evt))
+				{
+					graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, getState(evt)));
+				}
+			}),
+			mxUtils.bind(this, function(evt)
+			{
+				if (this.isLabelEvent(state, evt))
+				{
+					graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt, getState(evt)));
+					forceGetCell = false;
+				}
+			})
+		);
+
+		// Uses double click timeout in mxGraph for quirks mode
+		if (graph.nativeDblClickEnabled)
+		{
+			mxEvent.addListener(state.text.node, 'dblclick',
+				mxUtils.bind(this, function(evt)
+				{
+					if (this.isLabelEvent(state, evt))
+					{
+						graph.dblClick(evt, state.cell);
+						mxEvent.consume(evt);
+					}
+				})
+			);
+		}
+	}
+};
+
+/**
+ * Function: initializeLabel
+ * 
+ * Initiailzes the label with a suitable container.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose label should be initialized.
+ */
+mxCellRenderer.prototype.initializeLabel = function(state, shape)
+{
+	if (mxClient.IS_SVG && mxClient.NO_FO && shape.dialect != mxConstants.DIALECT_SVG)
+	{
+		shape.init(state.view.graph.container);
+	}
+	else
+	{
+		shape.init(state.view.getDrawPane());
+	}
+};
+
+/**
+ * Function: createCellOverlays
+ * 
+ * Creates the actual shape for showing the overlay for the given cell state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> for which the overlay should be created.
+ */
+mxCellRenderer.prototype.createCellOverlays = function(state)
+{
+	var graph = state.view.graph;
+	var overlays = graph.getCellOverlays(state.cell);
+	var dict = null;
+	
+	if (overlays != null)
+	{
+		dict = new mxDictionary();
+		
+		for (var i = 0; i < overlays.length; i++)
+		{
+			var shape = (state.overlays != null) ? state.overlays.remove(overlays[i]) : null;
+			
+			if (shape == null)
+			{
+				var tmp = new mxImageShape(new mxRectangle(), overlays[i].image.src);
+				tmp.dialect = state.view.graph.dialect;
+				tmp.preserveImageAspect = false;
+				tmp.overlay = overlays[i];
+				this.initializeOverlay(state, tmp);
+				this.installCellOverlayListeners(state, overlays[i], tmp);
+	
+				if (overlays[i].cursor != null)
+				{
+					tmp.node.style.cursor = overlays[i].cursor;
+				}
+				
+				dict.put(overlays[i], tmp);
+			}
+			else
+			{
+				dict.put(overlays[i], shape);
+			}
+		}
+	}
+	
+	// Removes unused
+	if (state.overlays != null)
+	{
+		state.overlays.visit(function(id, shape)
+		{
+			shape.destroy();
+		});
+	}
+	
+	state.overlays = dict;
+};
+
+/**
+ * Function: initializeOverlay
+ * 
+ * Initializes the given overlay.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> for which the overlay should be created.
+ * overlay - <mxImageShape> that represents the overlay.
+ */
+mxCellRenderer.prototype.initializeOverlay = function(state, overlay)
+{
+	overlay.init(state.view.getOverlayPane());
+};
+
+/**
+ * Function: installOverlayListeners
+ * 
+ * Installs the listeners for the given <mxCellState>, <mxCellOverlay> and
+ * <mxShape> that represents the overlay.
+ */
+mxCellRenderer.prototype.installCellOverlayListeners = function(state, overlay, shape)
+{
+	var graph  = state.view.graph;
+	
+	mxEvent.addListener(shape.node, 'click', function (evt)
+	{
+		if (graph.isEditing())
+		{
+			graph.stopEditing(!graph.isInvokesStopCellEditing());
+		}
+		
+		overlay.fireEvent(new mxEventObject(mxEvent.CLICK,
+				'event', evt, 'cell', state.cell));
+	});
+	
+	mxEvent.addGestureListeners(shape.node,
+		function (evt)
+		{
+			mxEvent.consume(evt);
+		},
+		function (evt)
+		{
+			graph.fireMouseEvent(mxEvent.MOUSE_MOVE,
+				new mxMouseEvent(evt, state));
+		});
+	
+	if (mxClient.IS_TOUCH)
+	{
+		mxEvent.addListener(shape.node, 'touchend', function (evt)
+		{
+			overlay.fireEvent(new mxEventObject(mxEvent.CLICK,
+					'event', evt, 'cell', state.cell));
+		});
+	}
+};
+
+/**
+ * Function: createControl
+ * 
+ * Creates the control for the given cell state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> for which the control should be created.
+ */
+mxCellRenderer.prototype.createControl = function(state)
+{
+	var graph = state.view.graph;
+	var image = graph.getFoldingImage(state);
+	
+	if (graph.foldingEnabled && image != null)
+	{
+		if (state.control == null)
+		{
+			var b = new mxRectangle(0, 0, image.width, image.height);
+			state.control = new mxImageShape(b, image.src);
+			state.control.preserveImageAspect = false;
+			state.control.dialect = graph.dialect;
+
+			this.initControl(state, state.control, true, this.createControlClickHandler(state));
+		}
+	}
+	else if (state.control != null)
+	{
+		state.control.destroy();
+		state.control = null;
+	}
+};
+
+/**
+ * Function: createControlClickHandler
+ * 
+ * Hook for creating the click handler for the folding icon.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose control click handler should be returned.
+ */
+mxCellRenderer.prototype.createControlClickHandler = function(state)
+{
+	var graph = state.view.graph;
+	
+	return mxUtils.bind(this, function (evt)
+	{
+		if (this.forceControlClickHandler || graph.isEnabled())
+		{
+			var collapse = !graph.isCellCollapsed(state.cell);
+			graph.foldCells(collapse, false, [state.cell], null, evt);
+			mxEvent.consume(evt);
+		}
+	});
+};
+
+/**
+ * Function: initControl
+ * 
+ * Initializes the given control and returns the corresponding DOM node.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> for which the control should be initialized.
+ * control - <mxShape> to be initialized.
+ * handleEvents - Boolean indicating if mousedown and mousemove should fire events via the graph.
+ * clickHandler - Optional function to implement clicks on the control.
+ */
+mxCellRenderer.prototype.initControl = function(state, control, handleEvents, clickHandler)
+{
+	var graph = state.view.graph;
+	
+	// In the special case where the label is in HTML and the display is SVG the image
+	// should go into the graph container directly in order to be clickable. Otherwise
+	// it is obscured by the HTML label that overlaps the cell.
+	var isForceHtml = graph.isHtmlLabel(state.cell) && mxClient.NO_FO &&
+		graph.dialect == mxConstants.DIALECT_SVG;
+
+	if (isForceHtml)
+	{
+		control.dialect = mxConstants.DIALECT_PREFERHTML;
+		control.init(graph.container);
+		control.node.style.zIndex = 1;
+	}
+	else
+	{
+		control.init(state.view.getOverlayPane());
+	}
+
+	var node = control.innerNode || control.node;
+	
+	// Workaround for missing click event on iOS is to check tolerance below
+	if (clickHandler != null && !mxClient.IS_IOS)
+	{
+		if (graph.isEnabled())
+		{
+			node.style.cursor = 'pointer';
+		}
+		
+		mxEvent.addListener(node, 'click', clickHandler);
+	}
+	
+	if (handleEvents)
+	{
+		var first = null;
+
+		mxEvent.addGestureListeners(node,
+			function (evt)
+			{
+				first = new mxPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+				graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt, state));
+				mxEvent.consume(evt);
+			},
+			function (evt)
+			{
+				graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, state));
+			},
+			function (evt)
+			{
+				graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt, state));
+				mxEvent.consume(evt);
+			});
+		
+		// Uses capture phase for event interception to stop bubble phase
+		if (clickHandler != null && mxClient.IS_IOS)
+		{
+			node.addEventListener('touchend', function(evt)
+			{
+				if (first != null)
+				{
+					var tol = graph.tolerance;
+					
+					if (Math.abs(first.x - mxEvent.getClientX(evt)) < tol &&
+						Math.abs(first.y - mxEvent.getClientY(evt)) < tol)
+					{
+						clickHandler.call(clickHandler, evt);
+						mxEvent.consume(evt);
+					}
+				}
+			}, true);
+		}
+	}
+	
+	return node;
+};
+
+/**
+ * Function: isShapeEvent
+ * 
+ * Returns true if the event is for the shape of the given state. This
+ * implementation always returns true.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose shape fired the event.
+ * evt - Mouse event which was fired.
+ */
+mxCellRenderer.prototype.isShapeEvent = function(state, evt)
+{
+	return true;
+};
+
+/**
+ * Function: isLabelEvent
+ * 
+ * Returns true if the event is for the label of the given state. This
+ * implementation always returns true.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose label fired the event.
+ * evt - Mouse event which was fired.
+ */
+mxCellRenderer.prototype.isLabelEvent = function(state, evt)
+{
+	return true;
+};
+
+/**
+ * Function: installListeners
+ * 
+ * Installs the event listeners for the given cell state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> for which the event listeners should be isntalled.
+ */
+mxCellRenderer.prototype.installListeners = function(state)
+{
+	var graph = state.view.graph;
+
+	// Workaround for touch devices routing all events for a mouse
+	// gesture (down, move, up) via the initial DOM node. Same for
+	// HTML images in all IE versions (VML images are working).
+	var getState = function(evt)
+	{
+		var result = state;
+		
+		if ((graph.dialect != mxConstants.DIALECT_SVG && mxEvent.getSource(evt).nodeName == 'IMG') || mxClient.IS_TOUCH)
+		{
+			var x = mxEvent.getClientX(evt);
+			var y = mxEvent.getClientY(evt);
+			
+			// Dispatches the drop event to the graph which
+			// consumes and executes the source function
+			var pt = mxUtils.convertPoint(graph.container, x, y);
+			result = graph.view.getState(graph.getCellAt(pt.x, pt.y));
+		}
+		
+		return result;
+	};
+
+	mxEvent.addGestureListeners(state.shape.node,
+		mxUtils.bind(this, function(evt)
+		{
+			if (this.isShapeEvent(state, evt))
+			{
+				graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt, state));
+			}
+		}),
+		mxUtils.bind(this, function(evt)
+		{
+			if (this.isShapeEvent(state, evt))
+			{
+				graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, getState(evt)));
+			}
+		}),
+		mxUtils.bind(this, function(evt)
+		{
+			if (this.isShapeEvent(state, evt))
+			{
+				graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt, getState(evt)));
+			}
+		})
+	);
+	
+	// Uses double click timeout in mxGraph for quirks mode
+	if (graph.nativeDblClickEnabled)
+	{
+		mxEvent.addListener(state.shape.node, 'dblclick',
+			mxUtils.bind(this, function(evt)
+			{
+				if (this.isShapeEvent(state, evt))
+				{
+					graph.dblClick(evt, state.cell);
+					mxEvent.consume(evt);
+				}
+			})
+		);
+	}
+};
+
+/**
+ * Function: redrawLabel
+ * 
+ * Redraws the label for the given cell state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose label should be redrawn.
+ */
+mxCellRenderer.prototype.redrawLabel = function(state, forced)
+{
+	var value = this.getLabelValue(state);
+	
+	if (state.text == null && value != null && (mxUtils.isNode(value) || value.length > 0))
+	{
+		this.createLabel(state, value);
+	}
+	else if (state.text != null && (value == null || value.length == 0))
+	{
+		state.text.destroy();
+		state.text = null;
+	}
+
+	if (state.text != null)
+	{
+		var graph = state.view.graph;
+
+		// Forced is true if the style has changed, so to get the updated
+		// result in getLabelBounds we apply the new style to the shape
+		if (forced)
+		{
+
+			// Checks if a full repaint is needed
+			if (state.text.lastValue != null && this.isTextShapeInvalid(state, state.text))
+			{
+				// Forces a full repaint
+				state.text.lastValue = null;
+			}
+			
+			state.text.resetStyles();
+			state.text.apply(state);
+			
+			// Special case where value is obtained via hook in graph
+			state.text.valign = graph.getVerticalAlign(state);
+		}
+		
+		var bounds = this.getLabelBounds(state);
+		var wrapping = graph.isWrapping(state.cell);
+		var clipping = graph.isLabelClipped(state.cell);
+		var isForceHtml = (state.view.graph.isHtmlLabel(state.cell) || (value != null && mxUtils.isNode(value)));
+		var dialect = (isForceHtml) ? mxConstants.DIALECT_STRICTHTML : state.view.graph.dialect;
+
+		// Text is a special case where change of dialect is possible at runtime
+		var overflow = state.style[mxConstants.STYLE_OVERFLOW] || 'visible';
+		
+		if (forced || state.text.value != value || state.text.isWrapping != wrapping ||
+			state.text.overflow != overflow || state.text.isClipping != clipping ||
+			state.text.scale != this.getTextScale(state) || state.text.dialect != dialect ||
+			!state.text.bounds.equals(bounds))
+		{
+			state.text.dialect = dialect;
+			state.text.value = value;
+			state.text.bounds = bounds;
+			state.text.scale = this.getTextScale(state);
+			state.text.wrap = wrapping;
+			state.text.clipped = clipping;
+			state.text.overflow = overflow;
+			
+			// Preserves visible state
+			var vis = state.text.node.style.visibility;
+			this.redrawLabelShape(state.text);
+			state.text.node.style.visibility = vis;
+		}
+	}
+};
+
+/**
+ * Function: isTextShapeInvalid
+ * 
+ * Returns true if the style for the text shape has changed.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose label should be checked.
+ * shape - <mxText> shape to be checked.
+ */
+mxCellRenderer.prototype.isTextShapeInvalid = function(state, shape)
+{
+	function check(property, stylename, defaultValue)
+	{
+		// Workaround for spacing added to directional spacing
+		if (stylename == 'spacingTop' || stylename == 'spacingRight' ||
+			stylename == 'spacingBottom' || stylename == 'spacingLeft')
+		{
+			result = parseFloat(shape[property]) - parseFloat(shape.spacing) !=
+				(state.style[stylename] || defaultValue);
+		}
+		else
+		{
+			result = shape[property] != (state.style[stylename] || defaultValue);
+		}
+		
+		return result;
+	};
+
+	return check('fontStyle', mxConstants.STYLE_FONTSTYLE, mxConstants.DEFAULT_FONTSTYLE) ||
+		check('family', mxConstants.STYLE_FONTFAMILY, mxConstants.DEFAULT_FONTFAMILY) ||
+		check('size', mxConstants.STYLE_FONTSIZE, mxConstants.DEFAULT_FONTSIZE) ||
+		check('color', mxConstants.STYLE_FONTCOLOR, 'black') ||
+		check('align', mxConstants.STYLE_ALIGN, '') ||
+		check('valign', mxConstants.STYLE_VERTICAL_ALIGN, '') ||
+		check('spacing', mxConstants.STYLE_SPACING, 2) ||
+		check('spacingTop', mxConstants.STYLE_SPACING_TOP, 0) ||
+		check('spacingRight', mxConstants.STYLE_SPACING_RIGHT, 0) ||
+		check('spacingBottom', mxConstants.STYLE_SPACING_BOTTOM, 0) ||
+		check('spacingLeft', mxConstants.STYLE_SPACING_LEFT, 0) ||
+		check('horizontal', mxConstants.STYLE_HORIZONTAL, true) ||
+		check('background', mxConstants.STYLE_LABEL_BACKGROUNDCOLOR) ||
+		check('border', mxConstants.STYLE_LABEL_BORDERCOLOR) ||
+		check('opacity', mxConstants.STYLE_TEXT_OPACITY, 100) ||
+		check('textDirection', mxConstants.STYLE_TEXT_DIRECTION, mxConstants.DEFAULT_TEXT_DIRECTION);
+};
+
+/**
+ * Function: redrawLabelShape
+ * 
+ * Called to invoked redraw on the given text shape.
+ * 
+ * Parameters:
+ * 
+ * shape - <mxText> shape to be redrawn.
+ */
+mxCellRenderer.prototype.redrawLabelShape = function(shape)
+{
+	shape.redraw();
+};
+
+/**
+ * Function: getTextScale
+ * 
+ * Returns the scaling used for the label of the given state
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose label scale should be returned.
+ */
+mxCellRenderer.prototype.getTextScale = function(state)
+{
+	return state.view.scale;
+};
+
+/**
+ * Function: getLabelBounds
+ * 
+ * Returns the bounds to be used to draw the label of the given state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose label bounds should be returned.
+ */
+mxCellRenderer.prototype.getLabelBounds = function(state)
+{
+	var graph = state.view.graph;
+	var scale = state.view.scale;
+	var isEdge = graph.getModel().isEdge(state.cell);
+	var bounds = new mxRectangle(state.absoluteOffset.x, state.absoluteOffset.y);
+
+	if (isEdge)
+	{
+		var spacing = state.text.getSpacing();
+		bounds.x += spacing.x * scale;
+		bounds.y += spacing.y * scale;
+		
+		var geo = graph.getCellGeometry(state.cell);
+		
+		if (geo != null)
+		{
+			bounds.width = Math.max(0, geo.width * scale);
+			bounds.height = Math.max(0, geo.height * scale);
+		}
+	}
+	else
+	{
+		// Inverts label position
+		if (state.text.isPaintBoundsInverted())
+		{
+			var tmp = bounds.x;
+			bounds.x = bounds.y;
+			bounds.y = tmp;
+		}
+		
+		bounds.x += state.x;
+		bounds.y += state.y;
+		
+		// Minimum of 1 fixes alignment bug in HTML labels
+		bounds.width = Math.max(1, state.width);
+		bounds.height = Math.max(1, state.height);
+
+		var sc = mxUtils.getValue(state.style, mxConstants.STYLE_STROKECOLOR, mxConstants.NONE);
+		
+		if (sc != mxConstants.NONE && sc != '')
+		{
+			var s = parseFloat(mxUtils.getValue(state.style, mxConstants.STYLE_STROKEWIDTH, 1)) * scale;
+			var dx = 1 + Math.floor((s - 1) / 2);
+			var dh = Math.floor(s + 1);
+			
+			bounds.x += dx;
+			bounds.y += dx;
+			bounds.width -= dh;
+			bounds.height -= dh;
+		}
+	}
+
+	if (state.text.isPaintBoundsInverted())
+	{
+		// Rotates around center of state
+		var t = (state.width - state.height) / 2;
+		bounds.x += t;
+		bounds.y -= t;
+		var tmp = bounds.width;
+		bounds.width = bounds.height;
+		bounds.height = tmp;
+	}
+	
+	// Shape can modify its label bounds
+	if (state.shape != null)
+	{
+		var hpos = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);
+		var vpos = mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);
+		
+		if (hpos == mxConstants.ALIGN_CENTER && vpos == mxConstants.ALIGN_MIDDLE)
+		{
+			bounds = state.shape.getLabelBounds(bounds);
+		}
+	}
+	
+	// Label width style overrides actual label width
+	var lw = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_WIDTH, null);
+	
+	if (lw != null)
+	{
+		bounds.width = parseFloat(lw) * scale;
+	}
+	
+	if (!isEdge)
+	{
+		this.rotateLabelBounds(state, bounds);
+	}
+	
+	return bounds;
+};
+
+/**
+ * Function: rotateLabelBounds
+ * 
+ * Adds the shape rotation to the given label bounds and
+ * applies the alignment and offsets.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose label bounds should be rotated.
+ * bounds - <mxRectangle> the rectangle to be rotated.
+ */
+mxCellRenderer.prototype.rotateLabelBounds = function(state, bounds)
+{
+	bounds.y -= state.text.margin.y * bounds.height;
+	bounds.x -= state.text.margin.x * bounds.width;
+	
+	if (!this.legacySpacing || (state.style[mxConstants.STYLE_OVERFLOW] != 'fill' && state.style[mxConstants.STYLE_OVERFLOW] != 'width'))
+	{
+		var s = state.view.scale;
+		var spacing = state.text.getSpacing();
+		bounds.x += spacing.x * s;
+		bounds.y += spacing.y * s;
+		
+		var hpos = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);
+		var vpos = mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);
+		var lw = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_WIDTH, null);
+		
+		bounds.width = Math.max(0, bounds.width - ((hpos == mxConstants.ALIGN_CENTER && lw == null) ? (state.text.spacingLeft * s + state.text.spacingRight * s) : 0));
+		bounds.height = Math.max(0, bounds.height - ((vpos == mxConstants.ALIGN_MIDDLE) ? (state.text.spacingTop * s + state.text.spacingBottom * s) : 0));
+	}
+
+	var theta = state.text.getTextRotation();
+
+	// Only needed if rotated around another center
+	if (theta != 0 && state != null && state.view.graph.model.isVertex(state.cell))
+	{
+		var cx = state.getCenterX();
+		var cy = state.getCenterY();
+		
+		if (bounds.x != cx || bounds.y != cy)
+		{
+			var rad = theta * (Math.PI / 180);
+			pt = mxUtils.getRotatedPoint(new mxPoint(bounds.x, bounds.y),
+					Math.cos(rad), Math.sin(rad), new mxPoint(cx, cy));
+			
+			bounds.x = pt.x;
+			bounds.y = pt.y;
+		}
+	}
+};
+
+/**
+ * Function: redrawCellOverlays
+ * 
+ * Redraws the overlays for the given cell state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose overlays should be redrawn.
+ */
+mxCellRenderer.prototype.redrawCellOverlays = function(state, forced)
+{
+	this.createCellOverlays(state);
+
+	if (state.overlays != null)
+	{
+		var rot = mxUtils.mod(mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION, 0), 90);
+        var rad = mxUtils.toRadians(rot);
+        var cos = Math.cos(rad);
+        var sin = Math.sin(rad);
+		
+		state.overlays.visit(function(id, shape)
+		{
+			var bounds = shape.overlay.getBounds(state);
+		
+			if (!state.view.graph.getModel().isEdge(state.cell))
+			{
+				if (state.shape != null && rot != 0)
+				{
+					var cx = bounds.getCenterX();
+					var cy = bounds.getCenterY();
+
+					var point = mxUtils.getRotatedPoint(new mxPoint(cx, cy), cos, sin,
+			        		new mxPoint(state.getCenterX(), state.getCenterY()));
+
+			        cx = point.x;
+			        cy = point.y;
+			        bounds.x = Math.round(cx - bounds.width / 2);
+			        bounds.y = Math.round(cy - bounds.height / 2);
+				}
+			}
+			
+			if (forced || shape.bounds == null || shape.scale != state.view.scale ||
+				!shape.bounds.equals(bounds))
+			{
+				shape.bounds = bounds;
+				shape.scale = state.view.scale;
+				shape.redraw();
+			}
+		});
+	}
+};
+
+/**
+ * Function: redrawControl
+ * 
+ * Redraws the control for the given cell state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose control should be redrawn.
+ */
+mxCellRenderer.prototype.redrawControl = function(state, forced)
+{
+	var image = state.view.graph.getFoldingImage(state);
+	
+	if (state.control != null && image != null)
+	{
+		var bounds = this.getControlBounds(state, image.width, image.height);
+		var r = (this.legacyControlPosition) ?
+				mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION, 0) :
+				state.shape.getTextRotation();
+		var s = state.view.scale;
+		
+		if (forced || state.control.scale != s || !state.control.bounds.equals(bounds) ||
+			state.control.rotation != r)
+		{
+			state.control.rotation = r;
+			state.control.bounds = bounds;
+			state.control.scale = s;
+			
+			state.control.redraw();
+		}
+	}
+};
+
+/**
+ * Function: getControlBounds
+ * 
+ * Returns the bounds to be used to draw the control (folding icon) of the
+ * given state.
+ */
+mxCellRenderer.prototype.getControlBounds = function(state, w, h)
+{
+	if (state.control != null)
+	{
+		var s = state.view.scale;
+		var cx = state.getCenterX();
+		var cy = state.getCenterY();
+	
+		if (!state.view.graph.getModel().isEdge(state.cell))
+		{
+			cx = state.x + w * s;
+			cy = state.y + h * s;
+			
+			if (state.shape != null)
+			{
+				// TODO: Factor out common code
+				var rot = state.shape.getShapeRotation();
+				
+				if (this.legacyControlPosition)
+				{
+					rot = mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION, 0);
+				}
+				else
+				{
+					if (state.shape.isPaintBoundsInverted())
+					{
+						var t = (state.width - state.height) / 2;
+						cx += t;
+						cy -= t;
+					}
+				}
+				
+				if (rot != 0)
+				{
+			        var rad = mxUtils.toRadians(rot);
+			        var cos = Math.cos(rad);
+			        var sin = Math.sin(rad);
+			        
+			        var point = mxUtils.getRotatedPoint(new mxPoint(cx, cy), cos, sin,
+			        		new mxPoint(state.getCenterX(), state.getCenterY()));
+			        cx = point.x;
+			        cy = point.y;
+				}
+			}
+		}
+		
+		return (state.view.graph.getModel().isEdge(state.cell)) ? 
+			new mxRectangle(Math.round(cx - w / 2 * s), Math.round(cy - h / 2 * s), Math.round(w * s), Math.round(h * s))
+			: new mxRectangle(Math.round(cx - w / 2 * s), Math.round(cy - h / 2 * s), Math.round(w * s), Math.round(h * s));
+	}
+	
+	return null;
+};
+
+/**
+ * Function: insertStateAfter
+ * 
+ * Inserts the given array of <mxShapes> after the given nodes in the DOM.
+ * 
+ * Parameters:
+ * 
+ * shapes - Array of <mxShapes> to be inserted.
+ * node - Node in <drawPane> after which the shapes should be inserted.
+ * htmlNode - Node in the graph container after which the shapes should be inserted that
+ * will not go into the <drawPane> (eg. HTML labels without foreignObjects).
+ */
+mxCellRenderer.prototype.insertStateAfter = function(state, node, htmlNode)
+{
+	var shapes = this.getShapesForState(state);
+	
+	for (var i = 0; i < shapes.length; i++)
+	{
+		if (shapes[i] != null && shapes[i].node != null)
+		{
+			var html = shapes[i].node.parentNode != state.view.getDrawPane() &&
+				shapes[i].node.parentNode != state.view.getOverlayPane();
+			var temp = (html) ? htmlNode : node;
+			
+			if (temp != null && temp.nextSibling != shapes[i].node)
+			{
+				if (temp.nextSibling == null)
+				{
+					temp.parentNode.appendChild(shapes[i].node);
+				}
+				else
+				{
+					temp.parentNode.insertBefore(shapes[i].node, temp.nextSibling);
+				}
+			}
+			else if (temp == null)
+			{
+				// Special case: First HTML node should be first sibling after canvas
+				if (shapes[i].node.parentNode == state.view.graph.container)
+				{
+					var canvas = state.view.canvas;
+					
+					while (canvas != null && canvas.parentNode != state.view.graph.container)
+					{
+						canvas = canvas.parentNode;
+					}
+					
+					if (canvas != null && canvas.nextSibling != null)
+					{
+						if (canvas.nextSibling != shapes[i].node)
+						{
+							shapes[i].node.parentNode.insertBefore(shapes[i].node, canvas.nextSibling);
+						}
+					}
+					else
+					{
+						shapes[i].node.parentNode.appendChild(shapes[i].node);
+					}
+				}
+				else if (shapes[i].node.parentNode.firstChild != null && shapes[i].node.parentNode.firstChild != shapes[i].node)
+				{
+					// Inserts the node as the first child of the parent to implement the order
+					shapes[i].node.parentNode.insertBefore(shapes[i].node, shapes[i].node.parentNode.firstChild);
+				}
+			}
+			
+			if (html)
+			{
+				htmlNode = shapes[i].node;
+			}
+			else
+			{
+				node = shapes[i].node;
+			}
+		}
+	}
+
+	return [node, htmlNode];
+};
+
+/**
+ * Function: getShapesForState
+ * 
+ * Returns the <mxShapes> for the given cell state in the order in which they should
+ * appear in the DOM.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose shapes should be returned.
+ */
+mxCellRenderer.prototype.getShapesForState = function(state)
+{
+	return [state.shape, state.text, state.control];
+};
+
+/**
+ * Function: redraw
+ * 
+ * Updates the bounds or points and scale of the shapes for the given cell
+ * state. This is called in mxGraphView.validatePoints as the last step of
+ * updating all cells.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> for which the shapes should be updated.
+ * force - Optional boolean that specifies if the cell should be reconfiured
+ * and redrawn without any additional checks.
+ * rendering - Optional boolean that specifies if the cell should actually
+ * be drawn into the DOM. If this is false then redraw and/or reconfigure
+ * will not be called on the shape.
+ */
+mxCellRenderer.prototype.redraw = function(state, force, rendering)
+{
+	var shapeChanged = this.redrawShape(state, force, rendering);
+	
+	if (state.shape != null && (rendering == null || rendering))
+	{
+		this.redrawLabel(state, shapeChanged);
+		this.redrawCellOverlays(state, shapeChanged);
+		this.redrawControl(state, shapeChanged);
+	}
+};
+
+/**
+ * Function: redrawShape
+ * 
+ * Redraws the shape for the given cell state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose label should be redrawn.
+ */
+mxCellRenderer.prototype.redrawShape = function(state, force, rendering)
+{
+	var model = state.view.graph.model;
+	var shapeChanged = false;
+
+	// Forces creation of new shape if shape style has changed
+	if (state.shape != null && state.shape.style != null && state.style != null &&
+		state.shape.style[mxConstants.STYLE_SHAPE] != state.style[mxConstants.STYLE_SHAPE])
+	{
+		state.shape.destroy();
+		state.shape = null;
+	}
+	
+	if (state.shape == null && state.view.graph.container != null &&
+		state.cell != state.view.currentRoot &&
+		(model.isVertex(state.cell) || model.isEdge(state.cell)))
+	{
+		state.shape = this.createShape(state);
+		
+		if (state.shape != null)
+		{
+			state.shape.antiAlias = this.antiAlias;
+	
+			this.createIndicatorShape(state);
+			this.initializeShape(state);
+			this.createCellOverlays(state);
+			this.installListeners(state);
+			
+			// Forces a refresh of the handler of one exists
+			state.view.graph.selectionCellsHandler.updateHandler(state);
+		}
+	}
+	else if (state.shape != null && !mxUtils.equalEntries(state.shape.style, state.style))
+	{
+		state.shape.resetStyles();
+		this.configureShape(state);
+		// LATER: Ignore update for realtime to fix reset of current gesture
+		state.view.graph.selectionCellsHandler.updateHandler(state);
+		force = true;
+	}
+
+	if (state.shape != null)
+	{
+		// Handles changes of the collapse icon
+		this.createControl(state);
+		
+		// Redraws the cell if required, ignores changes to bounds if points are
+		// defined as the bounds are updated for the given points inside the shape
+		if (force || this.isShapeInvalid(state, state.shape))
+		{
+			if (state.absolutePoints != null)
+			{
+				state.shape.points = state.absolutePoints.slice();
+				state.shape.bounds = null;
+			}
+			else
+			{
+				state.shape.points = null;
+				state.shape.bounds = new mxRectangle(state.x, state.y, state.width, state.height);
+			}
+
+			state.shape.scale = state.view.scale;
+			
+			if (rendering == null || rendering)
+			{
+				state.shape.redraw();
+			}
+			else
+			{
+				state.shape.updateBoundingBox();
+			}
+			
+			shapeChanged = true;
+		}
+	}
+
+	return shapeChanged;
+};
+
+/**
+ * Function: isShapeInvalid
+ * 
+ * Returns true if the given shape must be repainted.
+ */
+mxCellRenderer.prototype.isShapeInvalid = function(state, shape)
+{
+	return shape.bounds == null || shape.scale != state.view.scale ||
+		(state.absolutePoints == null && !shape.bounds.equals(state)) ||
+		(state.absolutePoints != null && !mxUtils.equalPoints(shape.points, state.absolutePoints))
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the shapes associated with the given cell state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> for which the shapes should be destroyed.
+ */
+mxCellRenderer.prototype.destroy = function(state)
+{
+	if (state.shape != null)
+	{
+		if (state.text != null)
+		{		
+			state.text.destroy();
+			state.text = null;
+		}
+		
+		if (state.overlays != null)
+		{
+			state.overlays.visit(function(id, shape)
+			{
+				shape.destroy();
+			});
+			
+			state.overlays = null;
+		}
+
+		if (state.control != null)
+		{
+			state.control.destroy();
+			state.control = null;
+		}
+		
+		state.shape.destroy();
+		state.shape = null;
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+var mxEdgeStyle =
+{
+	/**
+	 * Class: mxEdgeStyle
+	 * 
+	 * Provides various edge styles to be used as the values for
+	 * <mxConstants.STYLE_EDGE> in a cell style.
+	 *
+	 * Example:
+	 * 
+	 * (code)
+	 * var style = stylesheet.getDefaultEdgeStyle();
+	 * style[mxConstants.STYLE_EDGE] = mxEdgeStyle.ElbowConnector;
+	 * (end)
+	 * 
+	 * Sets the default edge style to <ElbowConnector>.
+	 * 
+	 * Custom edge style:
+	 * 
+	 * To write a custom edge style, a function must be added to the mxEdgeStyle
+	 * object as follows:
+	 * 
+	 * (code)
+	 * mxEdgeStyle.MyStyle = function(state, source, target, points, result)
+	 * {
+	 *   if (source != null && target != null)
+	 *   {
+	 *     var pt = new mxPoint(target.getCenterX(), source.getCenterY());
+	 * 
+	 *     if (mxUtils.contains(source, pt.x, pt.y))
+	 *     {
+	 *       pt.y = source.y + source.height;
+	 *     }
+	 * 
+	 *     result.push(pt);
+	 *   }
+	 * };
+	 * (end)
+	 * 
+	 * In the above example, a right angle is created using a point on the
+	 * horizontal center of the target vertex and the vertical center of the source
+	 * vertex. The code checks if that point intersects the source vertex and makes
+	 * the edge straight if it does. The point is then added into the result array,
+	 * which acts as the return value of the function.
+	 *
+	 * The new edge style should then be registered in the <mxStyleRegistry> as follows:
+	 * (code)
+	 * mxStyleRegistry.putValue('myEdgeStyle', mxEdgeStyle.MyStyle);
+	 * (end)
+	 * 
+	 * The custom edge style above can now be used in a specific edge as follows:
+	 * 
+	 * (code)
+	 * model.setStyle(edge, 'edgeStyle=myEdgeStyle');
+	 * (end)
+	 * 
+	 * Note that the key of the <mxStyleRegistry> entry for the function should
+	 * be used in string values, unless <mxGraphView.allowEval> is true, in
+	 * which case you can also use mxEdgeStyle.MyStyle for the value in the
+	 * cell style above.
+	 * 
+	 * Or it can be used for all edges in the graph as follows:
+	 * 
+	 * (code)
+	 * var style = graph.getStylesheet().getDefaultEdgeStyle();
+	 * style[mxConstants.STYLE_EDGE] = mxEdgeStyle.MyStyle;
+	 * (end)
+	 * 
+	 * Note that the object can be used directly when programmatically setting
+	 * the value, but the key in the <mxStyleRegistry> should be used when
+	 * setting the value via a key, value pair in a cell style.
+	 * 
+	 * Function: EntityRelation
+	 * 
+	 * Implements an entity relation style for edges (as used in database
+	 * schema diagrams). At the time the function is called, the result
+	 * array contains a placeholder (null) for the first absolute point,
+	 * that is, the point where the edge and source terminal are connected.
+	 * The implementation of the style then adds all intermediate waypoints
+	 * except for the last point, that is, the connection point between the
+	 * edge and the target terminal. The first ant the last point in the
+	 * result array are then replaced with mxPoints that take into account
+	 * the terminal's perimeter and next point on the edge.
+	 *
+	 * Parameters:
+	 * 
+	 * state - <mxCellState> that represents the edge to be updated.
+	 * source - <mxCellState> that represents the source terminal.
+	 * target - <mxCellState> that represents the target terminal.
+	 * points - List of relative control points.
+	 * result - Array of <mxPoints> that represent the actual points of the
+	 * edge.
+	 */
+	 EntityRelation: function (state, source, target, points, result)
+	 {
+		var view = state.view;
+	 	var graph = view.graph;
+	 	var segment = mxUtils.getValue(state.style,
+	 			mxConstants.STYLE_SEGMENT,
+	 			mxConstants.ENTITY_SEGMENT) * view.scale;
+	 	
+		var pts = state.absolutePoints;
+		var p0 = pts[0];
+		var pe = pts[pts.length-1];
+
+	 	var isSourceLeft = false;
+
+		if (p0 != null)
+		{
+			source = new mxCellState();
+			source.x = p0.x;
+			source.y = p0.y;
+		}
+		else if (source != null)
+		{
+			var constraint = mxUtils.getPortConstraints(source, state, true, mxConstants.DIRECTION_MASK_NONE);
+			
+			if (constraint != mxConstants.DIRECTION_MASK_NONE && constraint != mxConstants.DIRECTION_MASK_WEST +
+				mxConstants.DIRECTION_MASK_EAST)
+			{
+				isSourceLeft = constraint == mxConstants.DIRECTION_MASK_WEST;
+			}
+			else
+			{
+			 	var sourceGeometry = graph.getCellGeometry(source.cell);
+		
+			 	if (sourceGeometry.relative)
+			 	{
+			 		isSourceLeft = sourceGeometry.x <= 0.5;
+			 	}
+			 	else if (target != null)
+			 	{
+			 		isSourceLeft = target.x + target.width < source.x;
+			 	}
+			}
+		}
+		else
+		{
+			return;
+		}
+	 	
+	 	var isTargetLeft = true;
+
+		if (pe != null)
+		{
+			target = new mxCellState();
+			target.x = pe.x;
+			target.y = pe.y;
+		}
+		else if (target != null)
+	 	{
+			var constraint = mxUtils.getPortConstraints(target, state, false, mxConstants.DIRECTION_MASK_NONE);
+
+			if (constraint != mxConstants.DIRECTION_MASK_NONE && constraint != mxConstants.DIRECTION_MASK_WEST +
+				mxConstants.DIRECTION_MASK_EAST)
+			{
+				isTargetLeft = constraint == mxConstants.DIRECTION_MASK_WEST;
+			}
+			else
+			{
+			 	var targetGeometry = graph.getCellGeometry(target.cell);
+	
+			 	if (targetGeometry.relative)
+			 	{
+			 		isTargetLeft = targetGeometry.x <= 0.5;
+			 	}
+			 	else if (source != null)
+			 	{
+			 		isTargetLeft = source.x + source.width < target.x;
+			 	}
+			}
+	 	}
+		
+		if (source != null && target != null)
+		{
+			var x0 = (isSourceLeft) ? source.x : source.x + source.width;
+			var y0 = view.getRoutingCenterY(source);
+			
+			var xe = (isTargetLeft) ? target.x : target.x + target.width;
+			var ye = view.getRoutingCenterY(target);
+	
+			var seg = segment;
+	
+			var dx = (isSourceLeft) ? -seg : seg;
+			var dep = new mxPoint(x0 + dx, y0);
+					
+			dx = (isTargetLeft) ? -seg : seg;
+			var arr = new mxPoint(xe + dx, ye);
+	
+			// Adds intermediate points if both go out on same side
+			if (isSourceLeft == isTargetLeft)
+			{
+				var x = (isSourceLeft) ?
+					Math.min(x0, xe)-segment :
+					Math.max(x0, xe)+segment;
+	
+				result.push(new mxPoint(x, y0));
+				result.push(new mxPoint(x, ye));
+			}
+			else if ((dep.x < arr.x) == isSourceLeft)
+			{
+				var midY = y0 + (ye - y0) / 2;
+	
+				result.push(dep);
+				result.push(new mxPoint(dep.x, midY));
+				result.push(new mxPoint(arr.x, midY));
+				result.push(arr);
+			}
+			else
+			{
+				result.push(dep);
+				result.push(arr);
+			}
+		}
+	 },
+
+	 /**
+	 * Function: Loop
+	 * 
+	 * Implements a self-reference, aka. loop.
+	 */
+	Loop: function (state, source, target, points, result)
+	{
+		var pts = state.absolutePoints;
+		
+		var p0 = pts[0];
+		var pe = pts[pts.length-1];
+
+		if (p0 != null && pe != null)
+		{
+			if (points != null && points.length > 0)
+			{
+				for (var i = 0; i < points.length; i++)
+				{
+					var pt = points[i];
+					pt = state.view.transformControlPoint(state, pt);
+					result.push(new mxPoint(pt.x, pt.y));
+				}
+			}
+
+			return;
+		}
+		
+		if (source != null)
+		{
+			var view = state.view;
+			var graph = view.graph;
+			var pt = (points != null && points.length > 0) ? points[0] : null;
+
+			if (pt != null)
+			{
+				pt = view.transformControlPoint(state, pt);
+					
+				if (mxUtils.contains(source, pt.x, pt.y))
+				{
+					pt = null;
+				}
+			}
+			
+			var x = 0;
+			var dx = 0;
+			var y = 0;
+			var dy = 0;
+			
+		 	var seg = mxUtils.getValue(state.style, mxConstants.STYLE_SEGMENT,
+		 		graph.gridSize) * view.scale;
+			var dir = mxUtils.getValue(state.style, mxConstants.STYLE_DIRECTION,
+				mxConstants.DIRECTION_WEST);
+			
+			if (dir == mxConstants.DIRECTION_NORTH ||
+				dir == mxConstants.DIRECTION_SOUTH)
+			{
+				x = view.getRoutingCenterX(source);
+				dx = seg;
+			}
+			else
+			{
+				y = view.getRoutingCenterY(source);
+				dy = seg;
+			}
+			
+			if (pt == null ||
+				pt.x < source.x ||
+				pt.x > source.x + source.width)
+			{
+				if (pt != null)
+				{
+					x = pt.x;
+					dy = Math.max(Math.abs(y - pt.y), dy);
+				}
+				else
+				{
+					if (dir == mxConstants.DIRECTION_NORTH)
+					{
+						y = source.y - 2 * dx;
+					}
+					else if (dir == mxConstants.DIRECTION_SOUTH)
+					{
+						y = source.y + source.height + 2 * dx;
+					}
+					else if (dir == mxConstants.DIRECTION_EAST)
+					{
+						x = source.x - 2 * dy;
+					}
+					else
+					{
+						x = source.x + source.width + 2 * dy;
+					}
+				}
+			}
+			else if (pt != null)
+			{
+				x = view.getRoutingCenterX(source);
+				dx = Math.max(Math.abs(x - pt.x), dy);
+				y = pt.y;
+				dy = 0;
+			}
+			
+			result.push(new mxPoint(x - dx, y - dy));
+			result.push(new mxPoint(x + dx, y + dy));
+		}
+	},
+	
+	/**
+	 * Function: ElbowConnector
+	 * 
+	 * Uses either <SideToSide> or <TopToBottom> depending on the horizontal
+	 * flag in the cell style. <SideToSide> is used if horizontal is true or
+	 * unspecified. See <EntityRelation> for a description of the
+	 * parameters.
+	 */
+	ElbowConnector: function (state, source, target, points, result)
+	{
+		var pt = (points != null && points.length > 0) ? points[0] : null;
+
+		var vertical = false;
+		var horizontal = false;
+		
+		if (source != null && target != null)
+		{
+			if (pt != null)
+			{
+				var left = Math.min(source.x, target.x);
+				var right = Math.max(source.x + source.width,
+					target.x + target.width);
+	
+				var top = Math.min(source.y, target.y);
+				var bottom = Math.max(source.y + source.height,
+					target.y + target.height);
+
+				pt = state.view.transformControlPoint(state, pt);
+					
+				vertical = pt.y < top || pt.y > bottom;
+				horizontal = pt.x < left || pt.x > right;
+			}
+			else
+			{
+				var left = Math.max(source.x, target.x);
+				var right = Math.min(source.x + source.width,
+					target.x + target.width);
+					
+				vertical = left == right;
+				
+				if (!vertical)
+				{
+					var top = Math.max(source.y, target.y);
+					var bottom = Math.min(source.y + source.height,
+						target.y + target.height);
+						
+					horizontal = top == bottom;
+				}
+			}
+		}
+
+		if (!horizontal && (vertical ||
+			state.style[mxConstants.STYLE_ELBOW] == mxConstants.ELBOW_VERTICAL))
+		{
+			mxEdgeStyle.TopToBottom(state, source, target, points, result);
+		}
+		else
+		{
+			mxEdgeStyle.SideToSide(state, source, target, points, result);
+		}
+	},
+
+	/**
+	 * Function: SideToSide
+	 * 
+	 * Implements a vertical elbow edge. See <EntityRelation> for a description
+	 * of the parameters.
+	 */
+	SideToSide: function (state, source, target, points, result)
+	{
+		var view = state.view;
+		var pt = (points != null && points.length > 0) ? points[0] : null;
+		var pts = state.absolutePoints;
+		var p0 = pts[0];
+		var pe = pts[pts.length-1];
+		
+		if (pt != null)
+		{
+			pt = view.transformControlPoint(state, pt);
+		}
+		
+		if (p0 != null)
+		{
+			source = new mxCellState();
+			source.x = p0.x;
+			source.y = p0.y;
+		}
+		
+		if (pe != null)
+		{
+			target = new mxCellState();
+			target.x = pe.x;
+			target.y = pe.y;
+		}
+		
+		if (source != null && target != null)
+		{
+			var l = Math.max(source.x, target.x);
+			var r = Math.min(source.x + source.width,
+							 target.x + target.width);
+	
+			var x = (pt != null) ? pt.x : Math.round(r + (l - r) / 2);
+	
+			var y1 = view.getRoutingCenterY(source);
+			var y2 = view.getRoutingCenterY(target);
+	
+			if (pt != null)
+			{
+				if (pt.y >= source.y && pt.y <= source.y + source.height)
+				{
+					y1 = pt.y;
+				}
+				
+				if (pt.y >= target.y && pt.y <= target.y + target.height)
+				{
+					y2 = pt.y;
+				}
+			}
+			
+			if (!mxUtils.contains(target, x, y1) &&
+				!mxUtils.contains(source, x, y1))
+			{
+				result.push(new mxPoint(x,  y1));
+			}
+	
+			if (!mxUtils.contains(target, x, y2) &&
+				!mxUtils.contains(source, x, y2))
+			{
+				result.push(new mxPoint(x, y2));
+			}
+	
+			if (result.length == 1)
+			{
+				if (pt != null)
+				{
+					if (!mxUtils.contains(target, x, pt.y) &&
+						!mxUtils.contains(source, x, pt.y))
+					{
+						result.push(new mxPoint(x, pt.y));
+					}
+				}
+				else
+				{	
+					var t = Math.max(source.y, target.y);
+					var b = Math.min(source.y + source.height,
+							 target.y + target.height);
+						 
+					result.push(new mxPoint(x, t + (b - t) / 2));
+				}
+			}
+		}
+	},
+
+	/**
+	 * Function: TopToBottom
+	 * 
+	 * Implements a horizontal elbow edge. See <EntityRelation> for a
+	 * description of the parameters.
+	 */
+	TopToBottom: function(state, source, target, points, result)
+	{
+		var view = state.view;
+		var pt = (points != null && points.length > 0) ? points[0] : null;
+		var pts = state.absolutePoints;
+		var p0 = pts[0];
+		var pe = pts[pts.length-1];
+		
+		if (pt != null)
+		{
+			pt = view.transformControlPoint(state, pt);
+		}
+		
+		if (p0 != null)
+		{
+			source = new mxCellState();
+			source.x = p0.x;
+			source.y = p0.y;
+		}
+		
+		if (pe != null)
+		{
+			target = new mxCellState();
+			target.x = pe.x;
+			target.y = pe.y;
+		}
+
+		if (source != null && target != null)
+		{
+			var t = Math.max(source.y, target.y);
+			var b = Math.min(source.y + source.height,
+							 target.y + target.height);
+	
+			var x = view.getRoutingCenterX(source);
+			
+			if (pt != null &&
+				pt.x >= source.x &&
+				pt.x <= source.x + source.width)
+			{
+				x = pt.x;
+			}
+			
+			var y = (pt != null) ? pt.y : Math.round(b + (t - b) / 2);
+			
+			if (!mxUtils.contains(target, x, y) &&
+				!mxUtils.contains(source, x, y))
+			{
+				result.push(new mxPoint(x, y));						
+			}
+			
+			if (pt != null &&
+				pt.x >= target.x &&
+				pt.x <= target.x + target.width)
+			{
+				x = pt.x;
+			}
+			else
+			{
+				x = view.getRoutingCenterX(target);
+			}
+			
+			if (!mxUtils.contains(target, x, y) &&
+				!mxUtils.contains(source, x, y))
+			{
+				result.push(new mxPoint(x, y));						
+			}
+			
+			if (result.length == 1)
+			{
+				if (pt != null && result.length == 1)
+				{
+					if (!mxUtils.contains(target, pt.x, y) &&
+						!mxUtils.contains(source, pt.x, y))
+					{
+						result.push(new mxPoint(pt.x, y));
+					}
+				}
+				else
+				{
+					var l = Math.max(source.x, target.x);
+					var r = Math.min(source.x + source.width,
+							 target.x + target.width);
+						 
+					result.push(new mxPoint(l + (r - l) / 2, y));
+				}
+			}
+		}
+	},
+
+	/**
+	 * Function: SegmentConnector
+	 * 
+	 * Implements an orthogonal edge style. Use <mxEdgeSegmentHandler>
+	 * as an interactive handler for this style.
+	 */
+	SegmentConnector: function(state, source, target, hints, result)
+	{
+		// Creates array of all way- and terminalpoints
+		var pts = state.absolutePoints;
+		var tol = Math.max(1, state.view.scale);
+		
+		// Whether the first segment outgoing from the source end is horizontal
+		var lastPushed = (result.length > 0) ? result[0] : null;
+		var horizontal = true;
+		var hint = null;
+		
+		// Adds waypoints only if outside of tolerance
+		function pushPoint(pt)
+		{
+			if (lastPushed == null || Math.abs(lastPushed.x - pt.x) >= tol || Math.abs(lastPushed.y - pt.y) >= tol)
+			{
+				result.push(pt);
+				lastPushed = pt;
+			}
+			
+			return lastPushed;
+		};
+
+		// Adds the first point
+		var pt = pts[0];
+		
+		if (pt == null && source != null)
+		{
+			pt = new mxPoint(state.view.getRoutingCenterX(source), state.view.getRoutingCenterY(source));
+		}
+		else if (pt != null)
+		{
+			pt = pt.clone();
+		}
+		
+		pt.x = Math.round(pt.x);
+		pt.y = Math.round(pt.y);
+		
+		var lastInx = pts.length - 1;
+
+		// Adds the waypoints
+		if (hints != null && hints.length > 0)
+		{
+			// Converts all hints and removes nulls
+			var newHints = [];
+			
+			for (var i = 0; i < hints.length; i++)
+			{
+				var tmp = state.view.transformControlPoint(state, hints[i]);
+				
+				if (tmp != null)
+				{
+					tmp.x = Math.round(tmp.x);
+					tmp.y = Math.round(tmp.y);
+					newHints.push(tmp);
+				}
+			}
+			
+			if (newHints.length == 0)
+			{
+				return;
+			}
+			
+			hints = newHints;
+			
+			// Aligns source and target hint to fixed points
+			if (pt != null && hints[0] != null)
+			{
+				if (Math.abs(hints[0].x - pt.x) < tol)
+				{
+					hints[0].x = pt.x;
+				}
+				
+				if (Math.abs(hints[0].y - pt.y) < tol)
+				{
+					hints[0].y = pt.y;
+				}
+			}
+			
+			var pe = pts[lastInx];
+			
+			if (pe != null && hints[hints.length - 1] != null)
+			{
+				if (Math.abs(hints[hints.length - 1].x - pe.x) < tol)
+				{
+					hints[hints.length - 1].x = pe.x;
+				}
+				
+				if (Math.abs(hints[hints.length - 1].y - pe.y) < tol)
+				{
+					hints[hints.length - 1].y = pe.y;
+				}
+			}
+			
+			hint = hints[0];
+
+			var currentTerm = source;
+			var currentPt = pts[0];
+			var hozChan = false;
+			var vertChan = false;
+			var currentHint = hint;
+			
+			if (currentPt != null)
+			{
+				currentPt.x = Math.round(currentPt.x);
+				currentPt.y = Math.round(currentPt.y);
+				currentTerm = null;
+			}
+			
+			// Check for alignment with fixed points and with channels
+			// at source and target segments only
+			for (var i = 0; i < 2; i++)
+			{
+				var fixedVertAlign = currentPt != null && currentPt.x == currentHint.x;
+				var fixedHozAlign = currentPt != null && currentPt.y == currentHint.y;
+				
+				var inHozChan = currentTerm != null && (currentHint.y >= currentTerm.y &&
+						currentHint.y <= currentTerm.y + currentTerm.height);
+				var inVertChan = currentTerm != null && (currentHint.x >= currentTerm.x &&
+						currentHint.x <= currentTerm.x + currentTerm.width);
+
+				hozChan = fixedHozAlign || (currentPt == null && inHozChan);
+				vertChan = fixedVertAlign || (currentPt == null && inVertChan);
+				
+				// If the current hint falls in both the hor and vert channels in the case
+				// of a floating port, or if the hint is exactly co-incident with a 
+				// fixed point, ignore the source and try to work out the orientation
+				// from the target end
+				if (i==0 && ((hozChan && vertChan) || (fixedVertAlign && fixedHozAlign)))
+				{
+				}
+				else
+				{
+					if (currentPt != null && (!fixedHozAlign && !fixedVertAlign) && (inHozChan || inVertChan)) 
+					{
+						horizontal = inHozChan ? false : true;
+						break;
+					}
+			
+					if (vertChan || hozChan)
+					{
+						horizontal = hozChan;
+						
+						if (i == 1)
+						{
+							// Work back from target end
+							horizontal = hints.length % 2 == 0 ? hozChan : vertChan;
+						}
+	
+						break;
+					}
+				}
+				
+				currentTerm = target;
+				currentPt = pts[lastInx];
+				
+				if (currentPt != null)
+				{
+					currentPt.x = Math.round(currentPt.x);
+					currentPt.y = Math.round(currentPt.y);
+					currentTerm = null;
+				}
+				
+				currentHint = hints[hints.length - 1];
+				
+				if (fixedVertAlign && fixedHozAlign)
+				{
+					hints = hints.slice(1);
+				}
+			}
+
+			if (horizontal && ((pts[0] != null && pts[0].y != hint.y) ||
+				(pts[0] == null && source != null &&
+				(hint.y < source.y || hint.y > source.y + source.height))))
+			{
+				pushPoint(new mxPoint(pt.x, hint.y));
+			}
+			else if (!horizontal && ((pts[0] != null && pts[0].x != hint.x) ||
+					(pts[0] == null && source != null &&
+					(hint.x < source.x || hint.x > source.x + source.width))))
+			{
+				pushPoint(new mxPoint(hint.x, pt.y));
+			}
+			
+			if (horizontal)
+			{
+				pt.y = hint.y;
+			}
+			else
+			{
+				pt.x = hint.x;
+			}
+		
+			for (var i = 0; i < hints.length; i++)
+			{
+				horizontal = !horizontal;
+				hint = hints[i];
+				
+//				mxLog.show();
+//				mxLog.debug('hint', i, hint.x, hint.y);
+				
+				if (horizontal)
+				{
+					pt.y = hint.y;
+				}
+				else
+				{
+					pt.x = hint.x;
+				}
+		
+				pushPoint(pt.clone());
+			}
+		}
+		else
+		{
+			hint = pt;
+			// FIXME: First click in connect preview toggles orientation
+			horizontal = true;
+		}
+
+		// Adds the last point
+		pt = pts[lastInx];
+
+		if (pt == null && target != null)
+		{
+			pt = new mxPoint(state.view.getRoutingCenterX(target), state.view.getRoutingCenterY(target));
+		}
+		
+		if (pt != null)
+		{
+			pt.x = Math.round(pt.x);
+			pt.y = Math.round(pt.y);
+			
+			if (hint != null)
+			{
+				if (horizontal && ((pts[lastInx] != null && pts[lastInx].y != hint.y) ||
+					(pts[lastInx] == null && target != null &&
+					(hint.y < target.y || hint.y > target.y + target.height))))
+				{
+					pushPoint(new mxPoint(pt.x, hint.y));
+				}
+				else if (!horizontal && ((pts[lastInx] != null && pts[lastInx].x != hint.x) ||
+						(pts[lastInx] == null && target != null &&
+						(hint.x < target.x || hint.x > target.x + target.width))))
+				{
+					pushPoint(new mxPoint(hint.x, pt.y));
+				}
+			}
+		}
+		
+		// Removes bends inside the source terminal for floating ports
+		if (pts[0] == null && source != null)
+		{
+			while (result.length > 1 && result[1] != null &&
+				mxUtils.contains(source, result[1].x, result[1].y))
+			{
+				result.splice(1, 1);
+			}
+		}
+		
+		// Removes bends inside the target terminal
+		if (pts[lastInx] == null && target != null)
+		{
+			while (result.length > 1 && result[result.length - 1] != null &&
+				mxUtils.contains(target, result[result.length - 1].x, result[result.length - 1].y))
+			{
+				result.splice(result.length - 1, 1);
+			}
+		}
+		
+		// Removes last point if inside tolerance with end point
+		if (pe != null && result[result.length - 1] != null &&
+			Math.abs(pe.x - result[result.length - 1].x) < tol &&
+			Math.abs(pe.y - result[result.length - 1].y) < tol)
+		{
+			result.splice(result.length - 1, 1);
+			
+			// Lines up second last point in result with end point
+			if (result[result.length - 1] != null)
+			{
+				if (Math.abs(result[result.length - 1].x - pe.x) < tol)
+				{
+					result[result.length - 1].x = pe.x;
+				}
+				
+				if (Math.abs(result[result.length - 1].y - pe.y) < tol)
+				{
+					result[result.length - 1].y = pe.y;
+				}
+			}
+		}
+	},
+	
+	orthBuffer: 10,
+	
+	orthPointsFallback: true,
+
+	dirVectors: [ [ -1, 0 ],
+			[ 0, -1 ], [ 1, 0 ], [ 0, 1 ], [ -1, 0 ], [ 0, -1 ], [ 1, 0 ] ],
+
+	wayPoints1: [ [ 0, 0], [ 0, 0],  [ 0, 0], [ 0, 0], [ 0, 0],  [ 0, 0],
+	              [ 0, 0],  [ 0, 0], [ 0, 0],  [ 0, 0], [ 0, 0],  [ 0, 0] ],
+
+	routePatterns: [
+		[ [ 513, 2308, 2081, 2562 ], [ 513, 1090, 514, 2184, 2114, 2561 ],
+			[ 513, 1090, 514, 2564, 2184, 2562 ],
+			[ 513, 2308, 2561, 1090, 514, 2568, 2308 ] ],
+	[ [ 514, 1057, 513, 2308, 2081, 2562 ], [ 514, 2184, 2114, 2561 ],
+			[ 514, 2184, 2562, 1057, 513, 2564, 2184 ],
+			[ 514, 1057, 513, 2568, 2308, 2561 ] ],
+	[ [ 1090, 514, 1057, 513, 2308, 2081, 2562 ], [ 2114, 2561 ],
+			[ 1090, 2562, 1057, 513, 2564, 2184 ],
+			[ 1090, 514, 1057, 513, 2308, 2561, 2568 ] ],
+	[ [ 2081, 2562 ], [ 1057, 513, 1090, 514, 2184, 2114, 2561 ],
+			[ 1057, 513, 1090, 514, 2184, 2562, 2564 ],
+			[ 1057, 2561, 1090, 514, 2568, 2308 ] ] ],
+	
+	inlineRoutePatterns: [
+			[ null, [ 2114, 2568 ], null, null ],
+			[ null, [ 514, 2081, 2114, 2568 ] , null, null ],
+			[ null, [ 2114, 2561 ], null, null ],
+			[ [ 2081, 2562 ], [ 1057, 2114, 2568 ],
+					[ 2184, 2562 ],
+					null ] ],
+	vertexSeperations: [],
+
+	limits: [
+	       [ 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
+	       [ 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ],
+
+	LEFT_MASK: 32,
+
+	TOP_MASK: 64,
+
+	RIGHT_MASK: 128,
+
+	BOTTOM_MASK: 256,
+
+	LEFT: 1,
+
+	TOP: 2,
+
+	RIGHT: 4,
+
+	BOTTOM: 8,
+
+	// TODO remove magic numbers
+	SIDE_MASK: 480,
+	//mxEdgeStyle.LEFT_MASK | mxEdgeStyle.TOP_MASK | mxEdgeStyle.RIGHT_MASK
+	//| mxEdgeStyle.BOTTOM_MASK,
+
+	CENTER_MASK: 512,
+
+	SOURCE_MASK: 1024,
+
+	TARGET_MASK: 2048,
+
+	VERTEX_MASK: 3072,
+	// mxEdgeStyle.SOURCE_MASK | mxEdgeStyle.TARGET_MASK,
+	
+	getJettySize: function(state, source, target, points, isSource)
+	{
+		var value = mxUtils.getValue(state.style, (isSource) ? mxConstants.STYLE_SOURCE_JETTY_SIZE :
+			mxConstants.STYLE_TARGET_JETTY_SIZE, mxUtils.getValue(state.style,
+					mxConstants.STYLE_JETTY_SIZE, mxEdgeStyle.orthBuffer));
+		
+		if (value == 'auto')
+		{
+			// Computes the automatic jetty size
+			var type = mxUtils.getValue(state.style, (isSource) ? mxConstants.STYLE_STARTARROW : mxConstants.STYLE_ENDARROW, mxConstants.NONE);
+			
+			if (type != mxConstants.NONE)
+			{
+				var size = mxUtils.getNumber(state.style, (isSource) ? mxConstants.STYLE_STARTSIZE : mxConstants.STYLE_ENDSIZE, mxConstants.DEFAULT_MARKERSIZE);
+				value = Math.max(2, Math.ceil((size + mxEdgeStyle.orthBuffer) / mxEdgeStyle.orthBuffer)) * mxEdgeStyle.orthBuffer;
+			}
+			else
+			{
+				value = 2 * mxEdgeStyle.orthBuffer;
+			}
+		}
+		
+		return value;
+	},
+
+	/**
+	 * Function: OrthConnector
+	 * 
+	 * Implements a local orthogonal router between the given
+	 * cells.
+	 * 
+	 * Parameters:
+	 * 
+	 * state - <mxCellState> that represents the edge to be updated.
+	 * source - <mxCellState> that represents the source terminal.
+	 * target - <mxCellState> that represents the target terminal.
+	 * points - List of relative control points.
+	 * result - Array of <mxPoints> that represent the actual points of the
+	 * edge.
+	 * 
+	 */
+	OrthConnector: function(state, source, target, points, result)
+	{
+		var graph = state.view.graph;
+		var sourceEdge = source == null ? false : graph.getModel().isEdge(source.cell);
+		var targetEdge = target == null ? false : graph.getModel().isEdge(target.cell);
+
+		var pts = state.absolutePoints;
+		var p0 = pts[0];
+		var pe = pts[pts.length-1];
+
+		var sourceX = source != null ? source.x : p0.x;
+		var sourceY = source != null ? source.y : p0.y;
+		var sourceWidth = source != null ? source.width : 0;
+		var sourceHeight = source != null ? source.height : 0;
+		
+		var targetX = target != null ? target.x : pe.x;
+		var targetY = target != null ? target.y : pe.y;
+		var targetWidth = target != null ? target.width : 0;
+		var targetHeight = target != null ? target.height : 0;
+
+		var scaledSourceBuffer = state.view.scale * mxEdgeStyle.getJettySize(state, source, target, points, true);
+		var scaledTargetBuffer = state.view.scale * mxEdgeStyle.getJettySize(state, source, target, points, false);
+		
+		// Workaround for loop routing within buffer zone
+		if (source != null && target == source)
+		{
+			scaledTargetBuffer = Math.max(scaledSourceBuffer, scaledTargetBuffer);
+			scaledSourceBuffer = scaledTargetBuffer;
+		}
+		
+		var totalBuffer = scaledTargetBuffer + scaledSourceBuffer;
+		var tooShort = false;
+		
+		// Checks minimum distance for fixed points and falls back to segment connector
+		if (p0 != null && pe != null)
+		{
+			var dx = pe.x - p0.x;
+			var dy = pe.y - p0.y;
+			
+			tooShort = dx * dx + dy * dy < totalBuffer * totalBuffer;
+		}
+
+		if (tooShort || (mxEdgeStyle.orthPointsFallback && (points != null &&
+			points.length > 0)) || sourceEdge || targetEdge)
+		{
+			mxEdgeStyle.SegmentConnector(state, source, target, points, result);
+			
+			return;
+		}
+
+		// Determine the side(s) of the source and target vertices
+		// that the edge may connect to
+		// portConstraint [source, target]
+		var portConstraint = [mxConstants.DIRECTION_MASK_ALL, mxConstants.DIRECTION_MASK_ALL];
+		var rotation = 0;
+		
+		if (source != null)
+		{
+			portConstraint[0] = mxUtils.getPortConstraints(source, state, true, 
+					mxConstants.DIRECTION_MASK_ALL);
+			rotation = mxUtils.getValue(source.style, mxConstants.STYLE_ROTATION, 0);
+			
+			if (rotation != 0)
+			{
+				var newRect = mxUtils.getBoundingBox(new mxRectangle(sourceX, sourceY, sourceWidth, sourceHeight), rotation);
+				sourceX = newRect.x; 
+				sourceY = newRect.y;
+				sourceWidth = newRect.width;
+				sourceHeight = newRect.height;
+			}
+		}
+
+		if (target != null)
+		{
+			portConstraint[1] = mxUtils.getPortConstraints(target, state, false,
+				mxConstants.DIRECTION_MASK_ALL);
+			rotation = mxUtils.getValue(target.style, mxConstants.STYLE_ROTATION, 0);
+
+			if (rotation != 0)
+			{
+				var newRect = mxUtils.getBoundingBox(new mxRectangle(targetX, targetY, targetWidth, targetHeight), rotation);
+				targetX = newRect.x;
+				targetY = newRect.y;
+				targetWidth = newRect.width;
+				targetHeight = newRect.height;
+			}
+		}
+
+		// Avoids floating point number errors
+		sourceX = Math.round(sourceX * 10) / 10;
+		sourceY = Math.round(sourceY * 10) / 10;
+		sourceWidth = Math.round(sourceWidth * 10) / 10;
+		sourceHeight = Math.round(sourceHeight * 10) / 10;
+		
+		targetX = Math.round(targetX * 10) / 10;
+		targetY = Math.round(targetY * 10) / 10;
+		targetWidth = Math.round(targetWidth * 10) / 10;
+		targetHeight = Math.round(targetHeight * 10) / 10;
+		
+		var dir = [0, 0];
+
+		// Work out which faces of the vertices present against each other
+		// in a way that would allow a 3-segment connection if port constraints
+		// permitted.
+		// geo -> [source, target] [x, y, width, height]
+		var geo = [ [sourceX, sourceY, sourceWidth, sourceHeight] ,
+		            [targetX, targetY, targetWidth, targetHeight] ];
+		var buffer = [scaledSourceBuffer, scaledTargetBuffer];
+
+		for (var i = 0; i < 2; i++)
+		{
+			mxEdgeStyle.limits[i][1] = geo[i][0] - buffer[i];
+			mxEdgeStyle.limits[i][2] = geo[i][1] - buffer[i];
+			mxEdgeStyle.limits[i][4] = geo[i][0] + geo[i][2] + buffer[i];
+			mxEdgeStyle.limits[i][8] = geo[i][1] + geo[i][3] + buffer[i];
+		}
+		
+		// Work out which quad the target is in
+		var sourceCenX = geo[0][0] + geo[0][2] / 2.0;
+		var sourceCenY = geo[0][1] + geo[0][3] / 2.0;
+		var targetCenX = geo[1][0] + geo[1][2] / 2.0;
+		var targetCenY = geo[1][1] + geo[1][3] / 2.0;
+		
+		var dx = sourceCenX - targetCenX;
+		var dy = sourceCenY - targetCenY;
+
+		var quad = 0;
+
+		if (dx < 0)
+		{
+			if (dy < 0)
+			{
+				quad = 2;
+			}
+			else
+			{
+				quad = 1;
+			}
+		}
+		else
+		{
+			if (dy <= 0)
+			{
+				quad = 3;
+				
+				// Special case on x = 0 and negative y
+				if (dx == 0)
+				{
+					quad = 2;
+				}
+			}
+		}
+
+		// Check for connection constraints
+		var currentTerm = null;
+		
+		if (source != null)
+		{
+			currentTerm = p0;
+		}
+
+		var constraint = [ [0.5, 0.5] , [0.5, 0.5] ];
+
+		for (var i = 0; i < 2; i++)
+		{
+			if (currentTerm != null)
+			{
+				constraint[i][0] = (currentTerm.x - geo[i][0]) / geo[i][2];
+				
+				if (Math.abs(currentTerm.x - geo[i][0]) <= 1)
+				{
+					dir[i] = mxConstants.DIRECTION_MASK_WEST;
+				}
+				else if (Math.abs(currentTerm.x - geo[i][0] - geo[i][2]) <= 1)
+				{
+					dir[i] = mxConstants.DIRECTION_MASK_EAST;
+				}
+
+				constraint[i][1] = (currentTerm.y - geo[i][1]) / geo[i][3];
+
+				if (Math.abs(currentTerm.y - geo[i][1]) <= 1)
+				{
+					dir[i] = mxConstants.DIRECTION_MASK_NORTH;
+				}
+				else if (Math.abs(currentTerm.y - geo[i][1] - geo[i][3]) <= 1)
+				{
+					dir[i] = mxConstants.DIRECTION_MASK_SOUTH;
+				}
+			}
+
+			currentTerm = null;
+			
+			if (target != null)
+			{
+				currentTerm = pe;
+			}
+		}
+
+		var sourceTopDist = geo[0][1] - (geo[1][1] + geo[1][3]);
+		var sourceLeftDist = geo[0][0] - (geo[1][0] + geo[1][2]);
+		var sourceBottomDist = geo[1][1] - (geo[0][1] + geo[0][3]);
+		var sourceRightDist = geo[1][0] - (geo[0][0] + geo[0][2]);
+
+		mxEdgeStyle.vertexSeperations[1] = Math.max(sourceLeftDist - totalBuffer, 0);
+		mxEdgeStyle.vertexSeperations[2] = Math.max(sourceTopDist - totalBuffer, 0);
+		mxEdgeStyle.vertexSeperations[4] = Math.max(sourceBottomDist - totalBuffer, 0);
+		mxEdgeStyle.vertexSeperations[3] = Math.max(sourceRightDist - totalBuffer, 0);
+				
+		//==============================================================
+		// Start of source and target direction determination
+
+		// Work through the preferred orientations by relative positioning
+		// of the vertices and list them in preferred and available order
+		
+		var dirPref = [];
+		var horPref = [];
+		var vertPref = [];
+
+		horPref[0] = (sourceLeftDist >= sourceRightDist) ? mxConstants.DIRECTION_MASK_WEST
+				: mxConstants.DIRECTION_MASK_EAST;
+		vertPref[0] = (sourceTopDist >= sourceBottomDist) ? mxConstants.DIRECTION_MASK_NORTH
+				: mxConstants.DIRECTION_MASK_SOUTH;
+
+		horPref[1] = mxUtils.reversePortConstraints(horPref[0]);
+		vertPref[1] = mxUtils.reversePortConstraints(vertPref[0]);
+		
+		var preferredHorizDist = sourceLeftDist >= sourceRightDist ? sourceLeftDist
+				: sourceRightDist;
+		var preferredVertDist = sourceTopDist >= sourceBottomDist ? sourceTopDist
+				: sourceBottomDist;
+
+		var prefOrdering = [ [0, 0] , [0, 0] ];
+		var preferredOrderSet = false;
+
+		// If the preferred port isn't available, switch it
+		for (var i = 0; i < 2; i++)
+		{
+			if (dir[i] != 0x0)
+			{
+				continue;
+			}
+
+			if ((horPref[i] & portConstraint[i]) == 0)
+			{
+				horPref[i] = mxUtils.reversePortConstraints(horPref[i]);
+			}
+
+			if ((vertPref[i] & portConstraint[i]) == 0)
+			{
+				vertPref[i] = mxUtils
+						.reversePortConstraints(vertPref[i]);
+			}
+
+			prefOrdering[i][0] = vertPref[i];
+			prefOrdering[i][1] = horPref[i];
+		}
+
+		if (preferredVertDist > 0
+				&& preferredHorizDist > 0)
+		{
+			// Possibility of two segment edge connection
+			if (((horPref[0] & portConstraint[0]) > 0)
+					&& ((vertPref[1] & portConstraint[1]) > 0))
+			{
+				prefOrdering[0][0] = horPref[0];
+				prefOrdering[0][1] = vertPref[0];
+				prefOrdering[1][0] = vertPref[1];
+				prefOrdering[1][1] = horPref[1];
+				preferredOrderSet = true;
+			}
+			else if (((vertPref[0] & portConstraint[0]) > 0)
+					&& ((horPref[1] & portConstraint[1]) > 0))
+			{
+				prefOrdering[0][0] = vertPref[0];
+				prefOrdering[0][1] = horPref[0];
+				prefOrdering[1][0] = horPref[1];
+				prefOrdering[1][1] = vertPref[1];
+				preferredOrderSet = true;
+			}
+		}
+		
+		if (preferredVertDist > 0 && !preferredOrderSet)
+		{
+			prefOrdering[0][0] = vertPref[0];
+			prefOrdering[0][1] = horPref[0];
+			prefOrdering[1][0] = vertPref[1];
+			prefOrdering[1][1] = horPref[1];
+			preferredOrderSet = true;
+
+		}
+		
+		if (preferredHorizDist > 0 && !preferredOrderSet)
+		{
+			prefOrdering[0][0] = horPref[0];
+			prefOrdering[0][1] = vertPref[0];
+			prefOrdering[1][0] = horPref[1];
+			prefOrdering[1][1] = vertPref[1];
+			preferredOrderSet = true;
+		}
+
+		// The source and target prefs are now an ordered list of
+		// the preferred port selections
+		// It the list can contain gaps, compact it
+
+		for (var i = 0; i < 2; i++)
+		{
+			if (dir[i] != 0x0)
+			{
+				continue;
+			}
+
+			if ((prefOrdering[i][0] & portConstraint[i]) == 0)
+			{
+				prefOrdering[i][0] = prefOrdering[i][1];
+			}
+
+			dirPref[i] = prefOrdering[i][0] & portConstraint[i];
+			dirPref[i] |= (prefOrdering[i][1] & portConstraint[i]) << 8;
+			dirPref[i] |= (prefOrdering[1 - i][i] & portConstraint[i]) << 16;
+			dirPref[i] |= (prefOrdering[1 - i][1 - i] & portConstraint[i]) << 24;
+
+			if ((dirPref[i] & 0xF) == 0)
+			{
+				dirPref[i] = dirPref[i] << 8;
+			}
+			
+			if ((dirPref[i] & 0xF00) == 0)
+			{
+				dirPref[i] = (dirPref[i] & 0xF) | dirPref[i] >> 8;
+			}
+			
+			if ((dirPref[i] & 0xF0000) == 0)
+			{
+				dirPref[i] = (dirPref[i] & 0xFFFF)
+						| ((dirPref[i] & 0xF000000) >> 8);
+			}
+
+			dir[i] = dirPref[i] & 0xF;
+
+			if (portConstraint[i] == mxConstants.DIRECTION_MASK_WEST
+					|| portConstraint[i] == mxConstants.DIRECTION_MASK_NORTH
+					|| portConstraint[i] == mxConstants.DIRECTION_MASK_EAST
+					|| portConstraint[i] == mxConstants.DIRECTION_MASK_SOUTH)
+			{
+				dir[i] = portConstraint[i];
+			}
+		}
+
+		//==============================================================
+		// End of source and target direction determination
+
+		var sourceIndex = dir[0] == mxConstants.DIRECTION_MASK_EAST ? 3
+				: dir[0];
+		var targetIndex = dir[1] == mxConstants.DIRECTION_MASK_EAST ? 3
+				: dir[1];
+
+		sourceIndex -= quad;
+		targetIndex -= quad;
+
+		if (sourceIndex < 1)
+		{
+			sourceIndex += 4;
+		}
+		
+		if (targetIndex < 1)
+		{
+			targetIndex += 4;
+		}
+
+		var routePattern = mxEdgeStyle.routePatterns[sourceIndex - 1][targetIndex - 1];
+
+		mxEdgeStyle.wayPoints1[0][0] = geo[0][0];
+		mxEdgeStyle.wayPoints1[0][1] = geo[0][1];
+
+		switch (dir[0])
+		{
+			case mxConstants.DIRECTION_MASK_WEST:
+				mxEdgeStyle.wayPoints1[0][0] -= scaledSourceBuffer;
+				mxEdgeStyle.wayPoints1[0][1] += constraint[0][1] * geo[0][3];
+				break;
+			case mxConstants.DIRECTION_MASK_SOUTH:
+				mxEdgeStyle.wayPoints1[0][0] += constraint[0][0] * geo[0][2];
+				mxEdgeStyle.wayPoints1[0][1] += geo[0][3] + scaledSourceBuffer;
+				break;
+			case mxConstants.DIRECTION_MASK_EAST:
+				mxEdgeStyle.wayPoints1[0][0] += geo[0][2] + scaledSourceBuffer;
+				mxEdgeStyle.wayPoints1[0][1] += constraint[0][1] * geo[0][3];
+				break;
+			case mxConstants.DIRECTION_MASK_NORTH:
+				mxEdgeStyle.wayPoints1[0][0] += constraint[0][0] * geo[0][2];
+				mxEdgeStyle.wayPoints1[0][1] -= scaledSourceBuffer;
+				break;
+		}
+
+		var currentIndex = 0;
+
+		// Orientation, 0 horizontal, 1 vertical
+		var lastOrientation = (dir[0] & (mxConstants.DIRECTION_MASK_EAST | mxConstants.DIRECTION_MASK_WEST)) > 0 ? 0
+				: 1;
+		var initialOrientation = lastOrientation;
+		var currentOrientation = 0;
+
+		for (var i = 0; i < routePattern.length; i++)
+		{
+			var nextDirection = routePattern[i] & 0xF;
+
+			// Rotate the index of this direction by the quad
+			// to get the real direction
+			var directionIndex = nextDirection == mxConstants.DIRECTION_MASK_EAST ? 3
+					: nextDirection;
+
+			directionIndex += quad;
+
+			if (directionIndex > 4)
+			{
+				directionIndex -= 4;
+			}
+
+			var direction = mxEdgeStyle.dirVectors[directionIndex - 1];
+
+			currentOrientation = (directionIndex % 2 > 0) ? 0 : 1;
+			// Only update the current index if the point moved
+			// in the direction of the current segment move,
+			// otherwise the same point is moved until there is 
+			// a segment direction change
+			if (currentOrientation != lastOrientation)
+			{
+				currentIndex++;
+				// Copy the previous way point into the new one
+				// We can't base the new position on index - 1
+				// because sometime elbows turn out not to exist,
+				// then we'd have to rewind.
+				mxEdgeStyle.wayPoints1[currentIndex][0] = mxEdgeStyle.wayPoints1[currentIndex - 1][0];
+				mxEdgeStyle.wayPoints1[currentIndex][1] = mxEdgeStyle.wayPoints1[currentIndex - 1][1];
+			}
+
+			var tar = (routePattern[i] & mxEdgeStyle.TARGET_MASK) > 0;
+			var sou = (routePattern[i] & mxEdgeStyle.SOURCE_MASK) > 0;
+			var side = (routePattern[i] & mxEdgeStyle.SIDE_MASK) >> 5;
+			side = side << quad;
+
+			if (side > 0xF)
+			{
+				side = side >> 4;
+			}
+
+			var center = (routePattern[i] & mxEdgeStyle.CENTER_MASK) > 0;
+
+			if ((sou || tar) && side < 9)
+			{
+				var limit = 0;
+				var souTar = sou ? 0 : 1;
+
+				if (center && currentOrientation == 0)
+				{
+					limit = geo[souTar][0] + constraint[souTar][0] * geo[souTar][2];
+				}
+				else if (center)
+				{
+					limit = geo[souTar][1] + constraint[souTar][1] * geo[souTar][3];
+				}
+				else
+				{
+					limit = mxEdgeStyle.limits[souTar][side];
+				}
+				
+				if (currentOrientation == 0)
+				{
+					var lastX = mxEdgeStyle.wayPoints1[currentIndex][0];
+					var deltaX = (limit - lastX) * direction[0];
+
+					if (deltaX > 0)
+					{
+						mxEdgeStyle.wayPoints1[currentIndex][0] += direction[0]
+								* deltaX;
+					}
+				}
+				else
+				{
+					var lastY = mxEdgeStyle.wayPoints1[currentIndex][1];
+					var deltaY = (limit - lastY) * direction[1];
+
+					if (deltaY > 0)
+					{
+						mxEdgeStyle.wayPoints1[currentIndex][1] += direction[1]
+								* deltaY;
+					}
+				}
+			}
+
+			else if (center)
+			{
+				// Which center we're travelling to depend on the current direction
+				mxEdgeStyle.wayPoints1[currentIndex][0] += direction[0]
+						* Math.abs(mxEdgeStyle.vertexSeperations[directionIndex] / 2);
+				mxEdgeStyle.wayPoints1[currentIndex][1] += direction[1]
+						* Math.abs(mxEdgeStyle.vertexSeperations[directionIndex] / 2);
+			}
+
+			if (currentIndex > 0
+					&& mxEdgeStyle.wayPoints1[currentIndex][currentOrientation] == mxEdgeStyle.wayPoints1[currentIndex - 1][currentOrientation])
+			{
+				currentIndex--;
+			}
+			else
+			{
+				lastOrientation = currentOrientation;
+			}
+		}
+
+		for (var i = 0; i <= currentIndex; i++)
+		{
+			if (i == currentIndex)
+			{
+				// Last point can cause last segment to be in
+				// same direction as jetty/approach. If so,
+				// check the number of points is consistent
+				// with the relative orientation of source and target
+				// jx. Same orientation requires an even
+				// number of turns (points), different requires
+				// odd.
+				var targetOrientation = (dir[1] & (mxConstants.DIRECTION_MASK_EAST | mxConstants.DIRECTION_MASK_WEST)) > 0 ? 0
+						: 1;
+				var sameOrient = targetOrientation == initialOrientation ? 0 : 1;
+
+				// (currentIndex + 1) % 2 is 0 for even number of points,
+				// 1 for odd
+				if (sameOrient != (currentIndex + 1) % 2)
+				{
+					// The last point isn't required
+					break;
+				}
+			}
+			
+			result.push(new mxPoint(Math.round(mxEdgeStyle.wayPoints1[i][0]), Math.round(mxEdgeStyle.wayPoints1[i][1])));
+		}
+		
+		// Removes duplicates
+		var index = 1;
+		
+		while (index < result.length)
+		{
+			if (result[index - 1] == null || result[index] == null ||
+				result[index - 1].x != result[index].x ||
+				result[index - 1].y != result[index].y)
+			{
+				index++;
+			}
+			else
+			{
+				result.splice(index, 1);
+			}
+		}
+	},
+	
+	getRoutePattern: function(dir, quad, dx, dy)
+	{
+		var sourceIndex = dir[0] == mxConstants.DIRECTION_MASK_EAST ? 3
+				: dir[0];
+		var targetIndex = dir[1] == mxConstants.DIRECTION_MASK_EAST ? 3
+				: dir[1];
+
+		sourceIndex -= quad;
+		targetIndex -= quad;
+
+		if (sourceIndex < 1)
+		{
+			sourceIndex += 4;
+		}
+		if (targetIndex < 1)
+		{
+			targetIndex += 4;
+		}
+
+		var result = routePatterns[sourceIndex - 1][targetIndex - 1];
+
+		if (dx == 0 || dy == 0)
+		{
+			if (inlineRoutePatterns[sourceIndex - 1][targetIndex - 1] != null)
+			{
+				result = inlineRoutePatterns[sourceIndex - 1][targetIndex - 1];
+			}
+		}
+
+		return result;
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+var mxStyleRegistry =
+{
+	/**
+	 * Class: mxStyleRegistry
+	 *
+	 * Singleton class that acts as a global converter from string to object values
+	 * in a style. This is currently only used to perimeters and edge styles.
+	 * 
+	 * Variable: values
+	 *
+	 * Maps from strings to objects.
+	 */
+	values: [],
+
+	/**
+	 * Function: putValue
+	 *
+	 * Puts the given object into the registry under the given name.
+	 */
+	putValue: function(name, obj)
+	{
+		mxStyleRegistry.values[name] = obj;
+	},
+
+	/**
+	 * Function: getValue
+	 *
+	 * Returns the value associated with the given name.
+	 */
+	getValue: function(name)
+	{
+		return mxStyleRegistry.values[name];
+	},
+	
+	/**
+	 * Function: getName
+	 * 
+	 * Returns the name for the given value.
+	 */
+	getName: function(value)
+	{
+		for (var key in mxStyleRegistry.values)
+		{
+			if (mxStyleRegistry.values[key] == value)
+			{
+				return key;
+			}
+		}
+		
+		return null;
+	}
+
+};
+
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_ELBOW, mxEdgeStyle.ElbowConnector);
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_ENTITY_RELATION, mxEdgeStyle.EntityRelation);
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_LOOP, mxEdgeStyle.Loop);
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_SIDETOSIDE, mxEdgeStyle.SideToSide);
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_TOPTOBOTTOM, mxEdgeStyle.TopToBottom);
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_ORTHOGONAL, mxEdgeStyle.OrthConnector);
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_SEGMENT, mxEdgeStyle.SegmentConnector);
+
+mxStyleRegistry.putValue(mxConstants.PERIMETER_ELLIPSE, mxPerimeter.EllipsePerimeter);
+mxStyleRegistry.putValue(mxConstants.PERIMETER_RECTANGLE, mxPerimeter.RectanglePerimeter);
+mxStyleRegistry.putValue(mxConstants.PERIMETER_RHOMBUS, mxPerimeter.RhombusPerimeter);
+mxStyleRegistry.putValue(mxConstants.PERIMETER_TRIANGLE, mxPerimeter.TrianglePerimeter);
+mxStyleRegistry.putValue(mxConstants.PERIMETER_HEXAGON, mxPerimeter.HexagonPerimeter);
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxGraphView
+ *
+ * Extends <mxEventSource> to implement a view for a graph. This class is in
+ * charge of computing the absolute coordinates for the relative child
+ * geometries, the points for perimeters and edge styles and keeping them
+ * cached in <mxCellStates> for faster retrieval. The states are updated
+ * whenever the model or the view state (translate, scale) changes. The scale
+ * and translate are honoured in the bounds.
+ * 
+ * Event: mxEvent.UNDO
+ * 
+ * Fires after the root was changed in <setCurrentRoot>. The <code>edit</code>
+ * property contains the <mxUndoableEdit> which contains the
+ * <mxCurrentRootChange>.
+ * 
+ * Event: mxEvent.SCALE_AND_TRANSLATE
+ * 
+ * Fires after the scale and translate have been changed in <scaleAndTranslate>.
+ * The <code>scale</code>, <code>previousScale</code>, <code>translate</code>
+ * and <code>previousTranslate</code> properties contain the new and previous
+ * scale and translate, respectively.
+ * 
+ * Event: mxEvent.SCALE
+ * 
+ * Fires after the scale was changed in <setScale>. The <code>scale</code> and
+ * <code>previousScale</code> properties contain the new and previous scale.
+ * 
+ * Event: mxEvent.TRANSLATE
+ * 
+ * Fires after the translate was changed in <setTranslate>. The
+ * <code>translate</code> and <code>previousTranslate</code> properties contain
+ * the new and previous value for translate.
+ * 
+ * Event: mxEvent.DOWN and mxEvent.UP
+ * 
+ * Fire if the current root is changed by executing an <mxCurrentRootChange>.
+ * The event name depends on the location of the root in the cell hierarchy
+ * with respect to the current root. The <code>root</code> and
+ * <code>previous</code> properties contain the new and previous root,
+ * respectively.
+ * 
+ * Constructor: mxGraphView
+ *
+ * Constructs a new view for the given <mxGraph>.
+ * 
+ * Parameters:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ */
+function mxGraphView(graph)
+{
+	this.graph = graph;
+	this.translate = new mxPoint();
+	this.graphBounds = new mxRectangle();
+	this.states = new mxDictionary();
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxGraphView.prototype = new mxEventSource();
+mxGraphView.prototype.constructor = mxGraphView;
+
+/**
+ *
+ */
+mxGraphView.prototype.EMPTY_POINT = new mxPoint();
+
+/**
+ * Variable: doneResource
+ * 
+ * Specifies the resource key for the status message after a long operation.
+ * If the resource for this key does not exist then the value is used as
+ * the status message. Default is 'done'.
+ */
+mxGraphView.prototype.doneResource = (mxClient.language != 'none') ? 'done' : '';
+
+/**
+ * Function: updatingDocumentResource
+ *
+ * Specifies the resource key for the status message while the document is
+ * being updated. If the resource for this key does not exist then the
+ * value is used as the status message. Default is 'updatingDocument'.
+ */
+mxGraphView.prototype.updatingDocumentResource = (mxClient.language != 'none') ? 'updatingDocument' : '';
+
+/**
+ * Variable: allowEval
+ * 
+ * Specifies if string values in cell styles should be evaluated using
+ * <mxUtils.eval>. This will only be used if the string values can't be mapped
+ * to objects using <mxStyleRegistry>. Default is false. NOTE: Enabling this
+ * switch carries a possible security risk.
+ */
+mxGraphView.prototype.allowEval = false;
+
+/**
+ * Variable: captureDocumentGesture
+ * 
+ * Specifies if a gesture should be captured when it goes outside of the
+ * graph container. Default is true.
+ */
+mxGraphView.prototype.captureDocumentGesture = true;
+
+/**
+ * Variable: optimizeVmlReflows
+ * 
+ * Specifies if the <canvas> should be hidden while rendering in IE8 standards
+ * mode and quirks mode. This will significantly improve rendering performance.
+ * Default is true.
+ */
+mxGraphView.prototype.optimizeVmlReflows = true;
+
+/**
+ * Variable: rendering
+ * 
+ * Specifies if shapes should be created, updated and destroyed using the
+ * methods of <mxCellRenderer> in <graph>. Default is true.
+ */
+mxGraphView.prototype.rendering = true;
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxGraphView.prototype.graph = null;
+
+/**
+ * Variable: currentRoot
+ *
+ * <mxCell> that acts as the root of the displayed cell hierarchy.
+ */
+mxGraphView.prototype.currentRoot = null;
+
+/**
+ * Variable: graphBounds
+ *
+ * <mxRectangle> that caches the scales, translated bounds of the current view.
+ */
+mxGraphView.prototype.graphBounds = null;
+
+/**
+ * Variable: scale
+ * 
+ * Specifies the scale. Default is 1 (100%).
+ */
+mxGraphView.prototype.scale = 1;
+	
+/**
+ * Variable: translate
+ *
+ * <mxPoint> that specifies the current translation. Default is a new
+ * empty <mxPoint>.
+ */
+mxGraphView.prototype.translate = null;
+
+/**
+ * Variable: states
+ * 
+ * <mxDictionary> that maps from cell IDs to <mxCellStates>.
+ */
+mxGraphView.prototype.states = null;
+
+/**
+ * Variable: updateStyle
+ * 
+ * Specifies if the style should be updated in each validation step. If this
+ * is false then the style is only updated if the state is created or if the
+ * style of the cell was changed. Default is false.
+ */
+mxGraphView.prototype.updateStyle = false;
+
+/**
+ * Variable: lastNode
+ * 
+ * During validation, this contains the last DOM node that was processed.
+ */
+mxGraphView.prototype.lastNode = null;
+
+/**
+ * Variable: lastHtmlNode
+ * 
+ * During validation, this contains the last HTML DOM node that was processed.
+ */
+mxGraphView.prototype.lastHtmlNode = null;
+
+/**
+ * Variable: lastForegroundNode
+ * 
+ * During validation, this contains the last edge's DOM node that was processed.
+ */
+mxGraphView.prototype.lastForegroundNode = null;
+
+/**
+ * Variable: lastForegroundHtmlNode
+ * 
+ * During validation, this contains the last edge HTML DOM node that was processed.
+ */
+mxGraphView.prototype.lastForegroundHtmlNode = null;
+
+/**
+ * Function: getGraphBounds
+ *
+ * Returns <graphBounds>.
+ */
+mxGraphView.prototype.getGraphBounds = function()
+{
+	return this.graphBounds;
+};
+
+/**
+ * Function: setGraphBounds
+ *
+ * Sets <graphBounds>.
+ */
+mxGraphView.prototype.setGraphBounds = function(value)
+{
+	this.graphBounds = value;
+};
+
+/**
+ * Function: getBounds
+ * 
+ * Returns the union of all <mxCellStates> for the given array of <mxCells>.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> whose bounds should be returned.
+ */
+mxGraphView.prototype.getBounds = function(cells)
+{
+	var result = null;
+	
+	if (cells != null && cells.length > 0)
+	{
+		var model = this.graph.getModel();
+		
+		for (var i = 0; i < cells.length; i++)
+		{
+			if (model.isVertex(cells[i]) || model.isEdge(cells[i]))
+			{
+				var state = this.getState(cells[i]);
+			
+				if (state != null)
+				{
+					if (result == null)
+					{
+						result = mxRectangle.fromRectangle(state);
+					}
+					else
+					{
+						result.add(state);
+					}
+				}
+			}
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: setCurrentRoot
+ *
+ * Sets and returns the current root and fires an <undo> event before
+ * calling <mxGraph.sizeDidChange>.
+ *
+ * Parameters:
+ *
+ * root - <mxCell> that specifies the root of the displayed cell hierarchy.
+ */
+mxGraphView.prototype.setCurrentRoot = function(root)
+{
+	if (this.currentRoot != root)
+	{
+		var change = new mxCurrentRootChange(this, root);
+		change.execute();
+		var edit = new mxUndoableEdit(this, false);
+		edit.add(change);
+		this.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit));
+		this.graph.sizeDidChange();
+	}
+	
+	return root;
+};
+
+/**
+ * Function: scaleAndTranslate
+ *
+ * Sets the scale and translation and fires a <scale> and <translate> event
+ * before calling <revalidate> followed by <mxGraph.sizeDidChange>.
+ *
+ * Parameters:
+ *
+ * scale - Decimal value that specifies the new scale (1 is 100%).
+ * dx - X-coordinate of the translation.
+ * dy - Y-coordinate of the translation.
+ */
+mxGraphView.prototype.scaleAndTranslate = function(scale, dx, dy)
+{
+	var previousScale = this.scale;
+	var previousTranslate = new mxPoint(this.translate.x, this.translate.y);
+	
+	if (this.scale != scale || this.translate.x != dx || this.translate.y != dy)
+	{
+		this.scale = scale;
+		
+		this.translate.x = dx;
+		this.translate.y = dy;
+
+		if (this.isEventsEnabled())
+		{
+			this.revalidate();
+			this.graph.sizeDidChange();
+		}
+	}
+	
+	this.fireEvent(new mxEventObject(mxEvent.SCALE_AND_TRANSLATE,
+		'scale', scale, 'previousScale', previousScale,
+		'translate', this.translate, 'previousTranslate', previousTranslate));
+};
+
+/**
+ * Function: getScale
+ * 
+ * Returns the <scale>.
+ */
+mxGraphView.prototype.getScale = function()
+{
+	return this.scale;
+};
+
+/**
+ * Function: setScale
+ *
+ * Sets the scale and fires a <scale> event before calling <revalidate> followed
+ * by <mxGraph.sizeDidChange>.
+ *
+ * Parameters:
+ *
+ * value - Decimal value that specifies the new scale (1 is 100%).
+ */
+mxGraphView.prototype.setScale = function(value)
+{
+	var previousScale = this.scale;
+	
+	if (this.scale != value)
+	{
+		this.scale = value;
+
+		if (this.isEventsEnabled())
+		{
+			this.revalidate();
+			this.graph.sizeDidChange();
+		}
+	}
+	
+	this.fireEvent(new mxEventObject(mxEvent.SCALE,
+		'scale', value, 'previousScale', previousScale));
+};
+
+/**
+ * Function: getTranslate
+ * 
+ * Returns the <translate>.
+ */
+mxGraphView.prototype.getTranslate = function()
+{
+	return this.translate;
+};
+
+/**
+ * Function: setTranslate
+ *
+ * Sets the translation and fires a <translate> event before calling
+ * <revalidate> followed by <mxGraph.sizeDidChange>. The translation is the
+ * negative of the origin.
+ *
+ * Parameters:
+ *
+ * dx - X-coordinate of the translation.
+ * dy - Y-coordinate of the translation.
+ */
+mxGraphView.prototype.setTranslate = function(dx, dy)
+{
+	var previousTranslate = new mxPoint(this.translate.x, this.translate.y);
+	
+	if (this.translate.x != dx || this.translate.y != dy)
+	{
+		this.translate.x = dx;
+		this.translate.y = dy;
+
+		if (this.isEventsEnabled())
+		{
+			this.revalidate();
+			this.graph.sizeDidChange();
+		}
+	}
+	
+	this.fireEvent(new mxEventObject(mxEvent.TRANSLATE,
+		'translate', this.translate, 'previousTranslate', previousTranslate));
+};
+
+/**
+ * Function: refresh
+ *
+ * Clears the view if <currentRoot> is not null and revalidates.
+ */
+mxGraphView.prototype.refresh = function()
+{
+	if (this.currentRoot != null)
+	{
+		this.clear();
+	}
+	
+	this.revalidate();
+};
+
+/**
+ * Function: revalidate
+ *
+ * Revalidates the complete view with all cell states.
+ */
+mxGraphView.prototype.revalidate = function()
+{
+	this.invalidate();
+	this.validate();
+};
+
+/**
+ * Function: clear
+ *
+ * Removes the state of the given cell and all descendants if the given
+ * cell is not the current root.
+ * 
+ * Parameters:
+ * 
+ * cell - Optional <mxCell> for which the state should be removed. Default
+ * is the root of the model.
+ * force - Boolean indicating if the current root should be ignored for
+ * recursion.
+ */
+mxGraphView.prototype.clear = function(cell, force, recurse)
+{
+	var model = this.graph.getModel();
+	cell = cell || model.getRoot();
+	force = (force != null) ? force : false;
+	recurse = (recurse != null) ? recurse : true;
+	
+	this.removeState(cell);
+	
+	if (recurse && (force || cell != this.currentRoot))
+	{
+		var childCount = model.getChildCount(cell);
+		
+		for (var i = 0; i < childCount; i++)
+		{
+			this.clear(model.getChildAt(cell, i), force);
+		}
+	}
+	else
+	{
+		this.invalidate(cell);
+	}
+};
+
+/**
+ * Function: invalidate
+ * 
+ * Invalidates the state of the given cell, all its descendants and
+ * connected edges.
+ * 
+ * Parameters:
+ * 
+ * cell - Optional <mxCell> to be invalidated. Default is the root of the
+ * model.
+ */
+mxGraphView.prototype.invalidate = function(cell, recurse, includeEdges)
+{
+	var model = this.graph.getModel();
+	cell = cell || model.getRoot();
+	recurse = (recurse != null) ? recurse : true;
+	includeEdges = (includeEdges != null) ? includeEdges : true;
+	
+	var state = this.getState(cell);
+	
+	if (state != null)
+	{
+		state.invalid = true;
+	}
+	
+	// Avoids infinite loops for invalid graphs
+	if (!cell.invalidating)
+	{
+		cell.invalidating = true;
+		
+		// Recursively invalidates all descendants
+		if (recurse)
+		{
+			var childCount = model.getChildCount(cell);
+			
+			for (var i = 0; i < childCount; i++)
+			{
+				var child = model.getChildAt(cell, i);
+				this.invalidate(child, recurse, includeEdges);
+			}
+		}
+		
+		// Propagates invalidation to all connected edges
+		if (includeEdges)
+		{
+			var edgeCount = model.getEdgeCount(cell);
+			
+			for (var i = 0; i < edgeCount; i++)
+			{
+				this.invalidate(model.getEdgeAt(cell, i), recurse, includeEdges);
+			}
+		}
+		
+		delete cell.invalidating;
+	}
+};
+
+/**
+ * Function: validate
+ * 
+ * Calls <validateCell> and <validateCellState> and updates the <graphBounds>
+ * using <getBoundingBox>. Finally the background is validated using
+ * <validateBackground>.
+ * 
+ * Parameters:
+ * 
+ * cell - Optional <mxCell> to be used as the root of the validation.
+ * Default is <currentRoot> or the root of the model.
+ */
+mxGraphView.prototype.validate = function(cell)
+{
+	var t0 = mxLog.enter('mxGraphView.validate');
+	window.status = mxResources.get(this.updatingDocumentResource) ||
+		this.updatingDocumentResource;
+	
+	this.resetValidationState();
+	
+	// Improves IE rendering speed by minimizing reflows
+	var prevDisplay = null;
+	
+	if (this.optimizeVmlReflows && this.canvas != null && this.textDiv == null &&
+		((document.documentMode == 8 && !mxClient.IS_EM) || mxClient.IS_QUIRKS))
+	{
+		// Placeholder keeps scrollbar positions when canvas is hidden
+		this.placeholder = document.createElement('div');
+		this.placeholder.style.position = 'absolute';
+		this.placeholder.style.width = this.canvas.clientWidth + 'px';
+		this.placeholder.style.height = this.canvas.clientHeight + 'px';
+		this.canvas.parentNode.appendChild(this.placeholder);
+
+		prevDisplay = this.drawPane.style.display;
+		this.canvas.style.display = 'none';
+		
+		// Creates temporary DIV used for text measuring in mxText.updateBoundingBox
+		this.textDiv = document.createElement('div');
+		this.textDiv.style.position = 'absolute';
+		this.textDiv.style.whiteSpace = 'nowrap';
+		this.textDiv.style.visibility = 'hidden';
+		this.textDiv.style.display = (mxClient.IS_QUIRKS) ? 'inline' : 'inline-block';
+		this.textDiv.style.zoom = '1';
+		
+		document.body.appendChild(this.textDiv);
+	}
+	
+	var graphBounds = this.getBoundingBox(this.validateCellState(
+		this.validateCell(cell || ((this.currentRoot != null) ?
+			this.currentRoot : this.graph.getModel().getRoot()))));
+	this.setGraphBounds((graphBounds != null) ? graphBounds : this.getEmptyBounds());
+	this.validateBackground();
+	
+	if (prevDisplay != null)
+	{
+		this.canvas.style.display = prevDisplay;
+		this.textDiv.parentNode.removeChild(this.textDiv);
+		
+		if (this.placeholder != null)
+		{
+			this.placeholder.parentNode.removeChild(this.placeholder);
+		}
+				
+		// Textdiv cannot be reused
+		this.textDiv = null;
+	}
+	
+	this.resetValidationState();
+	
+	window.status = mxResources.get(this.doneResource) ||
+		this.doneResource;
+	mxLog.leave('mxGraphView.validate', t0);
+};
+
+/**
+ * Function: getEmptyBounds
+ * 
+ * Returns the bounds for an empty graph. This returns a rectangle at
+ * <translate> with the size of 0 x 0.
+ */
+mxGraphView.prototype.getEmptyBounds = function()
+{
+	return new mxRectangle(this.translate.x * this.scale, this.translate.y * this.scale);
+};
+
+/**
+ * Function: getBoundingBox
+ * 
+ * Returns the bounding box of the shape and the label for the given
+ * <mxCellState> and its children if recurse is true.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose bounding box should be returned.
+ * recurse - Optional boolean indicating if the children should be included.
+ * Default is true.
+ */
+mxGraphView.prototype.getBoundingBox = function(state, recurse)
+{
+	recurse = (recurse != null) ? recurse : true;
+	var bbox = null;
+	
+	if (state != null)
+	{
+		if (state.shape != null && state.shape.boundingBox != null)
+		{
+			bbox = state.shape.boundingBox.clone();
+		}
+		
+		// Adds label bounding box to graph bounds
+		if (state.text != null && state.text.boundingBox != null)
+		{
+			if (bbox != null)
+			{
+				bbox.add(state.text.boundingBox);
+			}
+			else
+			{
+				bbox = state.text.boundingBox.clone();
+			}
+		}
+		
+		if (recurse)
+		{
+			var model = this.graph.getModel();
+			var childCount = model.getChildCount(state.cell);
+			
+			for (var i = 0; i < childCount; i++)
+			{
+				var bounds = this.getBoundingBox(this.getState(model.getChildAt(state.cell, i)));
+				
+				if (bounds != null)
+				{
+					if (bbox == null)
+					{
+						bbox = bounds;
+					}
+					else
+					{
+						bbox.add(bounds);
+					}
+				}
+			}
+		}
+	}
+	
+	return bbox;
+};
+
+/**
+ * Function: createBackgroundPageShape
+ *
+ * Creates and returns the shape used as the background page.
+ * 
+ * Parameters:
+ * 
+ * bounds - <mxRectangle> that represents the bounds of the shape.
+ */
+mxGraphView.prototype.createBackgroundPageShape = function(bounds)
+{
+	return new mxRectangleShape(bounds, 'white', 'black');
+};
+
+/**
+ * Function: validateBackground
+ *
+ * Calls <validateBackgroundImage> and <validateBackgroundPage>.
+ */
+mxGraphView.prototype.validateBackground = function()
+{
+	this.validateBackgroundImage();
+	this.validateBackgroundPage();
+};
+
+/**
+ * Function: validateBackgroundImage
+ * 
+ * Validates the background image.
+ */
+mxGraphView.prototype.validateBackgroundImage = function()
+{
+	var bg = this.graph.getBackgroundImage();
+	
+	if (bg != null)
+	{
+		if (this.backgroundImage == null || this.backgroundImage.image != bg.src)
+		{
+			if (this.backgroundImage != null)
+			{
+				this.backgroundImage.destroy();
+			}
+			
+			var bounds = new mxRectangle(0, 0, 1, 1);
+			
+			this.backgroundImage = new mxImageShape(bounds, bg.src);
+			this.backgroundImage.dialect = this.graph.dialect;
+			this.backgroundImage.init(this.backgroundPane);
+			this.backgroundImage.redraw();
+
+			// Workaround for ignored event on background in IE8 standards mode
+			if (document.documentMode == 8 && !mxClient.IS_EM)
+			{
+				mxEvent.addGestureListeners(this.backgroundImage.node,
+					mxUtils.bind(this, function(evt)
+					{
+						this.graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt));
+					}),
+					mxUtils.bind(this, function(evt)
+					{
+						this.graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt));
+					}),
+					mxUtils.bind(this, function(evt)
+					{
+						this.graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt));
+					})
+				);
+			}
+		}
+		
+		this.redrawBackgroundImage(this.backgroundImage, bg);
+	}
+	else if (this.backgroundImage != null)
+	{
+		this.backgroundImage.destroy();
+		this.backgroundImage = null;
+	}
+};
+
+/**
+ * Function: validateBackgroundPage
+ * 
+ * Validates the background page.
+ */
+mxGraphView.prototype.validateBackgroundPage = function()
+{
+	if (this.graph.pageVisible)
+	{
+		var bounds = this.getBackgroundPageBounds();
+		
+		if (this.backgroundPageShape == null)
+		{
+			this.backgroundPageShape = this.createBackgroundPageShape(bounds);
+			this.backgroundPageShape.scale = this.scale;
+			this.backgroundPageShape.isShadow = true;
+			this.backgroundPageShape.dialect = this.graph.dialect;
+			this.backgroundPageShape.init(this.backgroundPane);
+			this.backgroundPageShape.redraw();
+			
+			// Adds listener for double click handling on background
+			if (this.graph.nativeDblClickEnabled)
+			{
+				mxEvent.addListener(this.backgroundPageShape.node, 'dblclick', mxUtils.bind(this, function(evt)
+				{
+					this.graph.dblClick(evt);
+				}));
+			}
+
+			// Adds basic listeners for graph event dispatching outside of the
+			// container and finishing the handling of a single gesture
+			mxEvent.addGestureListeners(this.backgroundPageShape.node,
+				mxUtils.bind(this, function(evt)
+				{
+					this.graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt));
+				}),
+				mxUtils.bind(this, function(evt)
+				{
+					// Hides the tooltip if mouse is outside container
+					if (this.graph.tooltipHandler != null && this.graph.tooltipHandler.isHideOnHover())
+					{
+						this.graph.tooltipHandler.hide();
+					}
+					
+					if (this.graph.isMouseDown && !mxEvent.isConsumed(evt))
+					{
+						this.graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt));
+					}
+				}),
+				mxUtils.bind(this, function(evt)
+				{
+					this.graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt));
+				})
+			);
+		}
+		else
+		{
+			this.backgroundPageShape.scale = this.scale;
+			this.backgroundPageShape.bounds = bounds;
+			this.backgroundPageShape.redraw();
+		}
+	}
+	else if (this.backgroundPageShape != null)
+	{
+		this.backgroundPageShape.destroy();
+		this.backgroundPageShape = null;
+	}
+};
+
+/**
+ * Function: getBackgroundPageBounds
+ * 
+ * Returns the bounds for the background page.
+ */
+mxGraphView.prototype.getBackgroundPageBounds = function()
+{
+	var fmt = this.graph.pageFormat;
+	var ps = this.scale * this.graph.pageScale;
+	var bounds = new mxRectangle(this.scale * this.translate.x, this.scale * this.translate.y,
+			fmt.width * ps, fmt.height * ps);
+	
+	return bounds;
+};
+
+/**
+ * Function: redrawBackgroundImage
+ *
+ * Updates the bounds and redraws the background image.
+ * 
+ * Example:
+ * 
+ * If the background image should not be scaled, this can be replaced with
+ * the following.
+ * 
+ * (code)
+ * mxGraphView.prototype.redrawBackground = function(backgroundImage, bg)
+ * {
+ *   backgroundImage.bounds.x = this.translate.x;
+ *   backgroundImage.bounds.y = this.translate.y;
+ *   backgroundImage.bounds.width = bg.width;
+ *   backgroundImage.bounds.height = bg.height;
+ *
+ *   backgroundImage.redraw();
+ * };
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * backgroundImage - <mxImageShape> that represents the background image.
+ * bg - <mxImage> that specifies the image and its dimensions.
+ */
+mxGraphView.prototype.redrawBackgroundImage = function(backgroundImage, bg)
+{
+	backgroundImage.scale = this.scale;
+	backgroundImage.bounds.x = this.scale * this.translate.x;
+	backgroundImage.bounds.y = this.scale * this.translate.y;
+	backgroundImage.bounds.width = this.scale * bg.width;
+	backgroundImage.bounds.height = this.scale * bg.height;
+
+	backgroundImage.redraw();
+};
+
+/**
+ * Function: validateCell
+ * 
+ * Recursively creates the cell state for the given cell if visible is true and
+ * the given cell is visible. If the cell is not visible but the state exists
+ * then it is removed using <removeState>.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose <mxCellState> should be created.
+ * visible - Optional boolean indicating if the cell should be visible. Default
+ * is true.
+ */
+mxGraphView.prototype.validateCell = function(cell, visible)
+{
+	visible = (visible != null) ? visible : true;
+	
+	if (cell != null)
+	{
+		visible = visible && this.graph.isCellVisible(cell);
+		var state = this.getState(cell, visible);
+		
+		if (state != null && !visible)
+		{
+			this.removeState(cell);
+		}
+		else
+		{
+			var model = this.graph.getModel();
+			var childCount = model.getChildCount(cell);
+			
+			for (var i = 0; i < childCount; i++)
+			{
+				this.validateCell(model.getChildAt(cell, i), visible &&
+					(!this.isCellCollapsed(cell) || cell == this.currentRoot));
+			}
+		}
+	}
+	
+	return cell;
+};
+
+/**
+ * Function: validateCellState
+ * 
+ * Validates and repaints the <mxCellState> for the given <mxCell>.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose <mxCellState> should be validated.
+ * recurse - Optional boolean indicating if the children of the cell should be
+ * validated. Default is true.
+ */
+mxGraphView.prototype.validateCellState = function(cell, recurse)
+{
+	recurse = (recurse != null) ? recurse : true;
+	var state = null;
+	
+	if (cell != null)
+	{
+		state = this.getState(cell);
+		
+		if (state != null)
+		{
+			var model = this.graph.getModel();
+			
+			if (state.invalid)
+			{
+				state.invalid = false;
+				
+				if (state.style == null)
+				{
+					state.style = this.graph.getCellStyle(state.cell);
+				}
+				
+				if (cell != this.currentRoot)
+				{
+					this.validateCellState(model.getParent(cell), false);
+				}
+
+				state.setVisibleTerminalState(this.validateCellState(this.getVisibleTerminal(cell, true), false), true);
+				state.setVisibleTerminalState(this.validateCellState(this.getVisibleTerminal(cell, false), false), false);
+				
+				this.updateCellState(state);
+				
+				// Repaint happens immediately after the cell is validated
+				if (cell != this.currentRoot && !state.invalid)
+				{
+					this.graph.cellRenderer.redraw(state, false, this.isRendering());
+
+					// Handles changes to invertex paintbounds after update of rendering shape
+					state.updateCachedBounds();
+				}
+			}
+
+			if (recurse && !state.invalid)
+			{
+				// Updates order in DOM if recursively traversing
+				if (state.shape != null)
+				{
+					this.stateValidated(state);
+				}
+			
+				var childCount = model.getChildCount(cell);
+				
+				for (var i = 0; i < childCount; i++)
+				{
+					this.validateCellState(model.getChildAt(cell, i));
+				}
+			}
+		}
+	}
+	
+	return state;
+};
+
+/**
+ * Function: updateCellState
+ * 
+ * Updates the given <mxCellState>.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> to be updated.
+ */
+mxGraphView.prototype.updateCellState = function(state)
+{
+	state.absoluteOffset.x = 0;
+	state.absoluteOffset.y = 0;
+	state.origin.x = 0;
+	state.origin.y = 0;
+	state.length = 0;
+	
+	if (state.cell != this.currentRoot)
+	{
+		var model = this.graph.getModel();
+		var pState = this.getState(model.getParent(state.cell)); 
+		
+		if (pState != null && pState.cell != this.currentRoot)
+		{
+			state.origin.x += pState.origin.x;
+			state.origin.y += pState.origin.y;
+		}
+		
+		var offset = this.graph.getChildOffsetForCell(state.cell);
+		
+		if (offset != null)
+		{
+			state.origin.x += offset.x;
+			state.origin.y += offset.y;
+		}
+		
+		var geo = this.graph.getCellGeometry(state.cell);				
+	
+		if (geo != null)
+		{
+			if (!model.isEdge(state.cell))
+			{
+				offset = geo.offset || this.EMPTY_POINT;
+	
+				if (geo.relative && pState != null)
+				{
+					if (model.isEdge(pState.cell))
+					{
+						var origin = this.getPoint(pState, geo);
+
+						if (origin != null)
+						{
+							state.origin.x += (origin.x / this.scale) - pState.origin.x - this.translate.x;
+							state.origin.y += (origin.y / this.scale) - pState.origin.y - this.translate.y;
+						}
+					}
+					else
+					{
+						state.origin.x += geo.x * pState.width / this.scale + offset.x;
+						state.origin.y += geo.y * pState.height / this.scale + offset.y;
+					}
+				}
+				else
+				{
+					state.absoluteOffset.x = this.scale * offset.x;
+					state.absoluteOffset.y = this.scale * offset.y;
+					state.origin.x += geo.x;
+					state.origin.y += geo.y;
+				}
+			}
+	
+			state.x = this.scale * (this.translate.x + state.origin.x);
+			state.y = this.scale * (this.translate.y + state.origin.y);
+			state.width = this.scale * geo.width;
+			state.unscaledWidth = geo.width;
+			state.height = this.scale * geo.height;
+			
+			if (model.isVertex(state.cell))
+			{
+				this.updateVertexState(state, geo);
+			}
+			
+			if (model.isEdge(state.cell))
+			{
+				this.updateEdgeState(state, geo);
+			}
+		}
+	}
+
+	state.updateCachedBounds();
+};
+
+/**
+ * Function: isCellCollapsed
+ * 
+ * Returns true if the children of the given cell should not be visible in the
+ * view. This implementation uses <mxGraph.isCellVisible> but it can be
+ * overidden to use a separate condition.
+ */
+mxGraphView.prototype.isCellCollapsed = function(cell)
+{
+	return this.graph.isCellCollapsed(cell);
+};
+
+/**
+ * Function: updateVertexState
+ * 
+ * Validates the given cell state.
+ */
+mxGraphView.prototype.updateVertexState = function(state, geo)
+{
+	var model = this.graph.getModel();
+	var pState = this.getState(model.getParent(state.cell));
+	
+	if (geo.relative && pState != null && !model.isEdge(pState.cell))
+	{
+		var alpha = mxUtils.toRadians(pState.style[mxConstants.STYLE_ROTATION] || '0');
+		
+		if (alpha != 0)
+		{
+			var cos = Math.cos(alpha);
+			var sin = Math.sin(alpha);
+
+			var ct = new mxPoint(state.getCenterX(), state.getCenterY());
+			var cx = new mxPoint(pState.getCenterX(), pState.getCenterY());
+			var pt = mxUtils.getRotatedPoint(ct, cos, sin, cx);
+			state.x = pt.x - state.width / 2;
+			state.y = pt.y - state.height / 2;
+		}
+	}
+	
+	this.updateVertexLabelOffset(state);
+};
+
+/**
+ * Function: updateEdgeState
+ * 
+ * Validates the given cell state.
+ */
+mxGraphView.prototype.updateEdgeState = function(state, geo)
+{
+	var source = state.getVisibleTerminalState(true);
+	var target = state.getVisibleTerminalState(false);
+	
+	// This will remove edges with no terminals and no terminal points
+	// as such edges are invalid and produce NPEs in the edge styles.
+	// Also removes connected edges that have no visible terminals.
+	if ((this.graph.model.getTerminal(state.cell, true) != null && source == null) ||
+		(source == null && geo.getTerminalPoint(true) == null) ||
+		(this.graph.model.getTerminal(state.cell, false) != null && target == null) ||
+		(target == null && geo.getTerminalPoint(false) == null))
+	{
+		this.clear(state.cell, true);
+	}
+	else
+	{
+		this.updateFixedTerminalPoints(state, source, target);
+		this.updatePoints(state, geo.points, source, target);
+		this.updateFloatingTerminalPoints(state, source, target);
+		
+		var pts = state.absolutePoints;
+		
+		if (state.cell != this.currentRoot && (pts == null || pts.length < 2 ||
+			pts[0] == null || pts[pts.length - 1] == null))
+		{
+			// This will remove edges with invalid points from the list of states in the view.
+			// Happens if the one of the terminals and the corresponding terminal point is null.
+			this.clear(state.cell, true);
+		}
+		else
+		{
+			this.updateEdgeBounds(state);
+			this.updateEdgeLabelOffset(state);
+		}
+	}
+};
+
+/**
+ * Function: updateVertexLabelOffset
+ * 
+ * Updates the absoluteOffset of the given vertex cell state. This takes
+ * into account the label position styles.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose absolute offset should be updated.
+ */
+mxGraphView.prototype.updateVertexLabelOffset = function(state)
+{
+	var h = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);
+
+	if (h == mxConstants.ALIGN_LEFT)
+	{
+		var lw = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_WIDTH, null);
+		
+		if (lw != null)
+		{
+			lw *= this.scale;
+		}
+		else
+		{
+			lw = state.width;
+		}
+		
+		state.absoluteOffset.x -= lw;
+	}
+	else if (h == mxConstants.ALIGN_RIGHT)
+	{
+		state.absoluteOffset.x += state.width;
+	}
+	else if (h == mxConstants.ALIGN_CENTER)
+	{
+		var lw = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_WIDTH, null);
+		
+		if (lw != null)
+		{
+			// Aligns text block with given width inside the vertex width
+			var align = mxUtils.getValue(state.style, mxConstants.STYLE_ALIGN, mxConstants.ALIGN_CENTER);
+			var dx = 0;
+			
+			if (align == mxConstants.ALIGN_CENTER)
+			{
+				dx = 0.5;
+			}
+			else if (align == mxConstants.ALIGN_RIGHT)
+			{
+				dx = 1;
+			}
+			
+			if (dx != 0)
+			{
+				state.absoluteOffset.x -= (lw * this.scale - state.width) * dx;
+			}
+		}
+	}
+	
+	var v = mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);
+	
+	if (v == mxConstants.ALIGN_TOP)
+	{
+		state.absoluteOffset.y -= state.height;
+	}
+	else if (v == mxConstants.ALIGN_BOTTOM)
+	{
+		state.absoluteOffset.y += state.height;
+	}
+};
+
+/**
+ * Function: resetValidationState
+ *
+ * Resets the current validation state.
+ */
+mxGraphView.prototype.resetValidationState = function()
+{
+	this.lastNode = null;
+	this.lastHtmlNode = null;
+	this.lastForegroundNode = null;
+	this.lastForegroundHtmlNode = null;
+};
+
+/**
+ * Function: stateValidated
+ * 
+ * Invoked when a state has been processed in <validatePoints>. This is used
+ * to update the order of the DOM nodes of the shape.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> that represents the cell state.
+ */
+mxGraphView.prototype.stateValidated = function(state)
+{
+	var fg = (this.graph.getModel().isEdge(state.cell) && this.graph.keepEdgesInForeground) ||
+		(this.graph.getModel().isVertex(state.cell) && this.graph.keepEdgesInBackground);
+	var htmlNode = (fg) ? this.lastForegroundHtmlNode || this.lastHtmlNode : this.lastHtmlNode;
+	var node = (fg) ? this.lastForegroundNode || this.lastNode : this.lastNode;
+	var result = this.graph.cellRenderer.insertStateAfter(state, node, htmlNode);
+
+	if (fg)
+	{
+		this.lastForegroundHtmlNode = result[1];
+		this.lastForegroundNode = result[0];
+	}
+	else
+	{
+		this.lastHtmlNode = result[1];
+		this.lastNode = result[0];
+	}
+};
+
+/**
+ * Function: updateFixedTerminalPoints
+ *
+ * Sets the initial absolute terminal points in the given state before the edge
+ * style is computed.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCellState> whose initial terminal points should be updated.
+ * source - <mxCellState> which represents the source terminal.
+ * target - <mxCellState> which represents the target terminal.
+ */
+mxGraphView.prototype.updateFixedTerminalPoints = function(edge, source, target)
+{
+	this.updateFixedTerminalPoint(edge, source, true,
+		this.graph.getConnectionConstraint(edge, source, true));
+	this.updateFixedTerminalPoint(edge, target, false,
+		this.graph.getConnectionConstraint(edge, target, false));
+};
+
+/**
+ * Function: updateFixedTerminalPoint
+ *
+ * Sets the fixed source or target terminal point on the given edge.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCellState> whose terminal point should be updated.
+ * terminal - <mxCellState> which represents the actual terminal.
+ * source - Boolean that specifies if the terminal is the source.
+ * constraint - <mxConnectionConstraint> that specifies the connection.
+ */
+mxGraphView.prototype.updateFixedTerminalPoint = function(edge, terminal, source, constraint)
+{
+	edge.setAbsoluteTerminalPoint(this.getFixedTerminalPoint(edge, terminal, source, constraint), source);
+};
+
+/**
+ * Function: getFixedTerminalPoint
+ *
+ * Returns the fixed source or target terminal point for the given edge.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCellState> whose terminal point should be returned.
+ * terminal - <mxCellState> which represents the actual terminal.
+ * source - Boolean that specifies if the terminal is the source.
+ * constraint - <mxConnectionConstraint> that specifies the connection.
+ */
+mxGraphView.prototype.getFixedTerminalPoint = function(edge, terminal, source, constraint)
+{
+	var pt = null;
+	
+	if (constraint != null)
+	{
+		pt = this.graph.getConnectionPoint(terminal, constraint);
+	}
+	
+	if (pt == null && terminal == null)
+	{
+		var s = this.scale;
+		var tr = this.translate;
+		var orig = edge.origin;
+		var geo = this.graph.getCellGeometry(edge.cell);
+		pt = geo.getTerminalPoint(source);
+		
+		if (pt != null)
+		{
+			pt = new mxPoint(s * (tr.x + pt.x + orig.x),
+							 s * (tr.y + pt.y + orig.y));
+		}
+	}
+	
+	return pt;
+};
+
+/**
+ * Function: updateBoundsFromStencil
+ * 
+ * Updates the bounds of the given cell state to reflect the bounds of the stencil
+ * if it has a fixed aspect and returns the previous bounds as an <mxRectangle> if
+ * the bounds have been modified or null otherwise.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCellState> whose bounds should be updated.
+ */
+mxGraphView.prototype.updateBoundsFromStencil = function(state)
+{
+	var previous = null;
+	
+	if (state != null && state.shape != null && state.shape.stencil != null && state.shape.stencil.aspect == 'fixed')
+	{
+		previous = mxRectangle.fromRectangle(state);
+		var asp = state.shape.stencil.computeAspect(state.style, state.x, state.y, state.width, state.height);
+		state.setRect(asp.x, asp.y, state.shape.stencil.w0 * asp.width, state.shape.stencil.h0 * asp.height);
+	}
+	
+	return previous;
+};
+
+/**
+ * Function: updatePoints
+ *
+ * Updates the absolute points in the given state using the specified array
+ * of <mxPoints> as the relative points.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCellState> whose absolute points should be updated.
+ * points - Array of <mxPoints> that constitute the relative points.
+ * source - <mxCellState> that represents the source terminal.
+ * target - <mxCellState> that represents the target terminal.
+ */
+mxGraphView.prototype.updatePoints = function(edge, points, source, target)
+{
+	if (edge != null)
+	{
+		var pts = [];
+		pts.push(edge.absolutePoints[0]);
+		var edgeStyle = this.getEdgeStyle(edge, points, source, target);
+		
+		if (edgeStyle != null)
+		{
+			var src = this.getTerminalPort(edge, source, true);
+			var trg = this.getTerminalPort(edge, target, false);
+			
+			// Uses the stencil bounds for routing and restores after routing
+			var srcBounds = this.updateBoundsFromStencil(src);
+			var trgBounds = this.updateBoundsFromStencil(trg);
+
+			edgeStyle(edge, src, trg, points, pts);
+			
+			// Restores previous bounds
+			if (srcBounds != null)
+			{
+				src.setRect(srcBounds.x, srcBounds.y, srcBounds.width, srcBounds.height);
+			}
+			
+			if (trgBounds != null)
+			{
+				trg.setRect(trgBounds.x, trgBounds.y, trgBounds.width, trgBounds.height);
+			}
+		}
+		else if (points != null)
+		{
+			for (var i = 0; i < points.length; i++)
+			{
+				if (points[i] != null)
+				{
+					var pt = mxUtils.clone(points[i]);
+					pts.push(this.transformControlPoint(edge, pt));
+				}
+			}
+		}
+		
+		var tmp = edge.absolutePoints;
+		pts.push(tmp[tmp.length-1]);
+
+		edge.absolutePoints = pts;
+	}
+};
+
+/**
+ * Function: transformControlPoint
+ *
+ * Transforms the given control point to an absolute point.
+ */
+mxGraphView.prototype.transformControlPoint = function(state, pt)
+{
+	if (state != null && pt != null)
+	{
+		var orig = state.origin;
+		
+	    return new mxPoint(this.scale * (pt.x + this.translate.x + orig.x),
+	    	this.scale * (pt.y + this.translate.y + orig.y));
+	}
+	
+	return null;
+};
+
+/**
+ * Function: isLoopStyleEnabled
+ * 
+ * Returns true if the given edge should be routed with <mxGraph.defaultLoopStyle>
+ * or the <mxConstants.STYLE_LOOP> defined for the given edge. This implementation
+ * returns true if the given edge is a loop and does not 
+ */
+mxGraphView.prototype.isLoopStyleEnabled = function(edge, points, source, target)
+{
+	var sc = this.graph.getConnectionConstraint(edge, source, true);
+	var tc = this.graph.getConnectionConstraint(edge, target, false);
+	
+	if (!mxUtils.getValue(edge.style, mxConstants.STYLE_ORTHOGONAL_LOOP, false) ||
+		((sc == null || sc.point == null) && (tc == null || tc.point == null)))
+	{
+		return source != null && source == target;
+	}
+	
+	return false;
+};
+
+/**
+ * Function: getEdgeStyle
+ * 
+ * Returns the edge style function to be used to render the given edge state.
+ */
+mxGraphView.prototype.getEdgeStyle = function(edge, points, source, target)
+{
+	var edgeStyle = this.isLoopStyleEnabled(edge, points, source, target) ?
+		mxUtils.getValue(edge.style, mxConstants.STYLE_LOOP, this.graph.defaultLoopStyle) :
+		(!mxUtils.getValue(edge.style, mxConstants.STYLE_NOEDGESTYLE, false) ?
+		edge.style[mxConstants.STYLE_EDGE] : null);
+
+	// Converts string values to objects
+	if (typeof(edgeStyle) == "string")
+	{
+		var tmp = mxStyleRegistry.getValue(edgeStyle);
+		
+		if (tmp == null && this.isAllowEval())
+		{
+ 			tmp = mxUtils.eval(edgeStyle);
+		}
+		
+		edgeStyle = tmp;
+	}
+	
+	if (typeof(edgeStyle) == "function")
+	{
+		return edgeStyle;
+	}
+	
+	return null;
+};
+
+/**
+ * Function: updateFloatingTerminalPoints
+ *
+ * Updates the terminal points in the given state after the edge style was
+ * computed for the edge.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose terminal points should be updated.
+ * source - <mxCellState> that represents the source terminal.
+ * target - <mxCellState> that represents the target terminal.
+ */
+mxGraphView.prototype.updateFloatingTerminalPoints = function(state, source, target)
+{
+	var pts = state.absolutePoints;
+	var p0 = pts[0];
+	var pe = pts[pts.length - 1];
+
+	if (pe == null && target != null)
+	{
+		this.updateFloatingTerminalPoint(state, target, source, false);
+	}
+	
+	if (p0 == null && source != null)
+	{
+		this.updateFloatingTerminalPoint(state, source, target, true);
+	}
+};
+
+/**
+ * Function: updateFloatingTerminalPoint
+ *
+ * Updates the absolute terminal point in the given state for the given
+ * start and end state, where start is the source if source is true.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCellState> whose terminal point should be updated.
+ * start - <mxCellState> for the terminal on "this" side of the edge.
+ * end - <mxCellState> for the terminal on the other side of the edge.
+ * source - Boolean indicating if start is the source terminal state.
+ */
+mxGraphView.prototype.updateFloatingTerminalPoint = function(edge, start, end, source)
+{
+	edge.setAbsoluteTerminalPoint(this.getFloatingTerminalPoint(edge, start, end, source), source);
+};
+
+/**
+ * Function: getFloatingTerminalPoint
+ * 
+ * Returns the floating terminal point for the given edge, start and end
+ * state, where start is the source if source is true.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCellState> whose terminal point should be returned.
+ * start - <mxCellState> for the terminal on "this" side of the edge.
+ * end - <mxCellState> for the terminal on the other side of the edge.
+ * source - Boolean indicating if start is the source terminal state.
+ */
+mxGraphView.prototype.getFloatingTerminalPoint = function(edge, start, end, source)
+{
+	start = this.getTerminalPort(edge, start, source);
+	var next = this.getNextPoint(edge, end, source);
+	
+	var orth = this.graph.isOrthogonal(edge);
+	var alpha = mxUtils.toRadians(Number(start.style[mxConstants.STYLE_ROTATION] || '0'));
+	var center = new mxPoint(start.getCenterX(), start.getCenterY());
+	
+	if (alpha != 0)
+	{
+		var cos = Math.cos(-alpha);
+		var sin = Math.sin(-alpha);
+		next = mxUtils.getRotatedPoint(next, cos, sin, center);
+	}
+	
+	var border = parseFloat(edge.style[mxConstants.STYLE_PERIMETER_SPACING] || 0);
+	border += parseFloat(edge.style[(source) ?
+		mxConstants.STYLE_SOURCE_PERIMETER_SPACING :
+		mxConstants.STYLE_TARGET_PERIMETER_SPACING] || 0);
+	var pt = this.getPerimeterPoint(start, next, alpha == 0 && orth, border);
+
+	if (alpha != 0)
+	{
+		var cos = Math.cos(alpha);
+		var sin = Math.sin(alpha);
+		pt = mxUtils.getRotatedPoint(pt, cos, sin, center);
+	}
+	
+	return pt;
+};
+
+/**
+ * Function: getTerminalPort
+ * 
+ * Returns an <mxCellState> that represents the source or target terminal or
+ * port for the given edge.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> that represents the state of the edge.
+ * terminal - <mxCellState> that represents the terminal.
+ * source - Boolean indicating if the given terminal is the source terminal.
+ */
+mxGraphView.prototype.getTerminalPort = function(state, terminal, source)
+{
+	var key = (source) ? mxConstants.STYLE_SOURCE_PORT :
+		mxConstants.STYLE_TARGET_PORT;
+	var id = mxUtils.getValue(state.style, key);
+	
+	if (id != null)
+	{
+		var tmp = this.getState(this.graph.getModel().getCell(id));
+		
+		// Only uses ports where a cell state exists
+		if (tmp != null)
+		{
+			terminal = tmp;
+		}
+	}
+	
+	return terminal;
+};
+
+/**
+ * Function: getPerimeterPoint
+ *
+ * Returns an <mxPoint> that defines the location of the intersection point between
+ * the perimeter and the line between the center of the shape and the given point.
+ * 
+ * Parameters:
+ * 
+ * terminal - <mxCellState> for the source or target terminal.
+ * next - <mxPoint> that lies outside of the given terminal.
+ * orthogonal - Boolean that specifies if the orthogonal projection onto
+ * the perimeter should be returned. If this is false then the intersection
+ * of the perimeter and the line between the next and the center point is
+ * returned.
+ * border - Optional border between the perimeter and the shape.
+ */
+mxGraphView.prototype.getPerimeterPoint = function(terminal, next, orthogonal, border)
+{
+	var point = null;
+	
+	if (terminal != null)
+	{
+		var perimeter = this.getPerimeterFunction(terminal);
+		
+		if (perimeter != null && next != null)
+		{
+			var bounds = this.getPerimeterBounds(terminal, border);
+
+			if (bounds.width > 0 || bounds.height > 0)
+			{
+				point = new mxPoint(next.x, next.y);
+				var flipH = false;
+				var flipV = false;	
+				
+				if (this.graph.model.isVertex(terminal.cell))
+				{
+					flipH = mxUtils.getValue(terminal.style, mxConstants.STYLE_FLIPH, 0) == 1;
+					flipV = mxUtils.getValue(terminal.style, mxConstants.STYLE_FLIPV, 0) == 1;	
+	
+					// Legacy support for stencilFlipH/V
+					if (terminal.shape != null && terminal.shape.stencil != null)
+					{
+						flipH = (mxUtils.getValue(terminal.style, 'stencilFlipH', 0) == 1) || flipH;
+						flipV = (mxUtils.getValue(terminal.style, 'stencilFlipV', 0) == 1) || flipV;
+					}
+	
+					if (flipH)
+					{
+						point.x = 2 * bounds.getCenterX() - point.x;
+					}
+					
+					if (flipV)
+					{
+						point.y = 2 * bounds.getCenterY() - point.y;
+					}
+				}
+				
+				point = perimeter(bounds, terminal, point, orthogonal);
+
+				if (point != null)
+				{
+					if (flipH)
+					{
+						point.x = 2 * bounds.getCenterX() - point.x;
+					}
+					
+					if (flipV)
+					{
+						point.y = 2 * bounds.getCenterY() - point.y;
+					}
+				}
+			}
+		}
+		
+		if (point == null)
+		{
+			point = this.getPoint(terminal);
+		}
+	}
+	
+	return point;
+};
+
+/**
+ * Function: getRoutingCenterX
+ * 
+ * Returns the x-coordinate of the center point for automatic routing.
+ */
+mxGraphView.prototype.getRoutingCenterX = function (state)
+{
+	var f = (state.style != null) ? parseFloat(state.style
+		[mxConstants.STYLE_ROUTING_CENTER_X]) || 0 : 0;
+
+	return state.getCenterX() + f * state.width;
+};
+
+/**
+ * Function: getRoutingCenterY
+ * 
+ * Returns the y-coordinate of the center point for automatic routing.
+ */
+mxGraphView.prototype.getRoutingCenterY = function (state)
+{
+	var f = (state.style != null) ? parseFloat(state.style
+		[mxConstants.STYLE_ROUTING_CENTER_Y]) || 0 : 0;
+
+	return state.getCenterY() + f * state.height;
+};
+
+/**
+ * Function: getPerimeterBounds
+ *
+ * Returns the perimeter bounds for the given terminal, edge pair as an
+ * <mxRectangle>.
+ * 
+ * If you have a model where each terminal has a relative child that should
+ * act as the graphical endpoint for a connection from/to the terminal, then
+ * this method can be replaced as follows:
+ * 
+ * (code)
+ * var oldGetPerimeterBounds = mxGraphView.prototype.getPerimeterBounds;
+ * mxGraphView.prototype.getPerimeterBounds = function(terminal, edge, isSource)
+ * {
+ *   var model = this.graph.getModel();
+ *   var childCount = model.getChildCount(terminal.cell);
+ * 
+ *   if (childCount > 0)
+ *   {
+ *     var child = model.getChildAt(terminal.cell, 0);
+ *     var geo = model.getGeometry(child);
+ *
+ *     if (geo != null &&
+ *         geo.relative)
+ *     {
+ *       var state = this.getState(child);
+ *       
+ *       if (state != null)
+ *       {
+ *         terminal = state;
+ *       }
+ *     }
+ *   }
+ *   
+ *   return oldGetPerimeterBounds.apply(this, arguments);
+ * };
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * terminal - <mxCellState> that represents the terminal.
+ * border - Number that adds a border between the shape and the perimeter.
+ */
+mxGraphView.prototype.getPerimeterBounds = function(terminal, border)
+{
+	border = (border != null) ? border : 0;
+
+	if (terminal != null)
+	{
+		border += parseFloat(terminal.style[mxConstants.STYLE_PERIMETER_SPACING] || 0);
+	}
+
+	return terminal.getPerimeterBounds(border * this.scale);
+};
+
+/**
+ * Function: getPerimeterFunction
+ *
+ * Returns the perimeter function for the given state.
+ */
+mxGraphView.prototype.getPerimeterFunction = function(state)
+{
+	var perimeter = state.style[mxConstants.STYLE_PERIMETER];
+
+	// Converts string values to objects
+	if (typeof(perimeter) == "string")
+	{
+		var tmp = mxStyleRegistry.getValue(perimeter);
+		
+		if (tmp == null && this.isAllowEval())
+		{
+ 			tmp = mxUtils.eval(perimeter);
+		}
+
+		perimeter = tmp;
+	}
+	
+	if (typeof(perimeter) == "function")
+	{
+		return perimeter;
+	}
+	
+	return null;
+};
+
+/**
+ * Function: getNextPoint
+ *
+ * Returns the nearest point in the list of absolute points or the center
+ * of the opposite terminal.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCellState> that represents the edge.
+ * opposite - <mxCellState> that represents the opposite terminal.
+ * source - Boolean indicating if the next point for the source or target
+ * should be returned.
+ */
+mxGraphView.prototype.getNextPoint = function(edge, opposite, source)
+{
+	var pts = edge.absolutePoints;
+	var point = null;
+	
+	if (pts != null && pts.length >= 2)
+	{
+		var count = pts.length;
+		point = pts[(source) ? Math.min(1, count - 1) : Math.max(0, count - 2)];
+	}
+	
+	if (point == null && opposite != null)
+	{
+		point = new mxPoint(opposite.getCenterX(), opposite.getCenterY());
+	}
+	
+	return point;
+};
+
+/**
+ * Function: getVisibleTerminal
+ *
+ * Returns the nearest ancestor terminal that is visible. The edge appears
+ * to be connected to this terminal on the display. The result of this method
+ * is cached in <mxCellState.getVisibleTerminalState>.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> whose visible terminal should be returned.
+ * source - Boolean that specifies if the source or target terminal
+ * should be returned.
+ */
+mxGraphView.prototype.getVisibleTerminal = function(edge, source)
+{
+	var model = this.graph.getModel();
+	var result = model.getTerminal(edge, source);
+	var best = result;
+	
+	while (result != null && result != this.currentRoot)
+	{
+		if (!this.graph.isCellVisible(best) || this.isCellCollapsed(result))
+		{
+			best = result;
+		}
+		
+		result = model.getParent(result);
+	}
+
+	// Checks if the result is not a layer
+	if (model.getParent(best) == model.getRoot())
+	{
+		best = null;
+	}
+	
+	return best;
+};
+
+/**
+ * Function: updateEdgeBounds
+ *
+ * Updates the given state using the bounding box of t
+ * he absolute points.
+ * Also updates <mxCellState.terminalDistance>, <mxCellState.length> and
+ * <mxCellState.segments>.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose bounds should be updated.
+ */
+mxGraphView.prototype.updateEdgeBounds = function(state)
+{
+	var points = state.absolutePoints;
+	var p0 = points[0];
+	var pe = points[points.length - 1];
+	
+	if (p0.x != pe.x || p0.y != pe.y)
+	{
+		var dx = pe.x - p0.x;
+		var dy = pe.y - p0.y;
+		state.terminalDistance = Math.sqrt(dx * dx + dy * dy);
+	}
+	else
+	{
+		state.terminalDistance = 0;
+	}
+	
+	var length = 0;
+	var segments = [];
+	var pt = p0;
+	
+	if (pt != null)
+	{
+		var minX = pt.x;
+		var minY = pt.y;
+		var maxX = minX;
+		var maxY = minY;
+		
+		for (var i = 1; i < points.length; i++)
+		{
+			var tmp = points[i];
+			
+			if (tmp != null)
+			{
+				var dx = pt.x - tmp.x;
+				var dy = pt.y - tmp.y;
+				
+				var segment = Math.sqrt(dx * dx + dy * dy);
+				segments.push(segment);
+				length += segment;
+				
+				pt = tmp;
+				
+				minX = Math.min(pt.x, minX);
+				minY = Math.min(pt.y, minY);
+				maxX = Math.max(pt.x, maxX);
+				maxY = Math.max(pt.y, maxY);
+			}
+		}
+		
+		state.length = length;
+		state.segments = segments;
+		
+		var markerSize = 1; // TODO: include marker size
+		
+		state.x = minX;
+		state.y = minY;
+		state.width = Math.max(markerSize, maxX - minX);
+		state.height = Math.max(markerSize, maxY - minY);
+	}
+};
+
+/**
+ * Function: getPoint
+ *
+ * Returns the absolute point on the edge for the given relative
+ * <mxGeometry> as an <mxPoint>. The edge is represented by the given
+ * <mxCellState>.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> that represents the state of the parent edge.
+ * geometry - <mxGeometry> that represents the relative location.
+ */
+mxGraphView.prototype.getPoint = function(state, geometry)
+{
+	var x = state.getCenterX();
+	var y = state.getCenterY();
+	
+	if (state.segments != null && (geometry == null || geometry.relative))
+	{
+		var gx = (geometry != null) ? geometry.x / 2 : 0;
+		var pointCount = state.absolutePoints.length;
+		var dist = Math.round((gx + 0.5) * state.length);
+		var segment = state.segments[0];
+		var length = 0;				
+		var index = 1;
+
+		while (dist >= Math.round(length + segment) && index < pointCount - 1)
+		{
+			length += segment;
+			segment = state.segments[index++];
+		}
+
+		var factor = (segment == 0) ? 0 : (dist - length) / segment;
+		var p0 = state.absolutePoints[index-1];
+		var pe = state.absolutePoints[index];
+
+		if (p0 != null && pe != null)
+		{
+			var gy = 0;
+			var offsetX = 0;
+			var offsetY = 0;
+
+			if (geometry != null)
+			{
+				gy = geometry.y;
+				var offset = geometry.offset;
+				
+				if (offset != null)
+				{
+					offsetX = offset.x;
+					offsetY = offset.y;
+				}
+			}
+
+			var dx = pe.x - p0.x;
+			var dy = pe.y - p0.y;
+			var nx = (segment == 0) ? 0 : dy / segment;
+			var ny = (segment == 0) ? 0 : dx / segment;
+			
+			x = p0.x + dx * factor + (nx * gy + offsetX) * this.scale;
+			y = p0.y + dy * factor - (ny * gy - offsetY) * this.scale;
+		}
+	}
+	else if (geometry != null)
+	{
+		var offset = geometry.offset;
+		
+		if (offset != null)
+		{
+			x += offset.x;
+			y += offset.y;
+		}
+	}
+	
+	return new mxPoint(x, y);		
+};
+
+/**
+ * Function: getRelativePoint
+ *
+ * Gets the relative point that describes the given, absolute label
+ * position for the given edge state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> that represents the state of the parent edge.
+ * x - Specifies the x-coordinate of the absolute label location.
+ * y - Specifies the y-coordinate of the absolute label location.
+ */
+mxGraphView.prototype.getRelativePoint = function(edgeState, x, y)
+{
+	var model = this.graph.getModel();
+	var geometry = model.getGeometry(edgeState.cell);
+	
+	if (geometry != null)
+	{
+		var pointCount = edgeState.absolutePoints.length;
+		
+		if (geometry.relative && pointCount > 1)
+		{
+			var totalLength = edgeState.length;
+			var segments = edgeState.segments;
+
+			// Works which line segment the point of the label is closest to
+			var p0 = edgeState.absolutePoints[0];
+			var pe = edgeState.absolutePoints[1];
+			var minDist = mxUtils.ptSegDistSq(p0.x, p0.y, pe.x, pe.y, x, y);
+
+			var index = 0;
+			var tmp = 0;
+			var length = 0;
+			
+			for (var i = 2; i < pointCount; i++)
+			{
+				tmp += segments[i - 2];
+				pe = edgeState.absolutePoints[i];
+				var dist = mxUtils.ptSegDistSq(p0.x, p0.y, pe.x, pe.y, x, y);
+
+				if (dist <= minDist)
+				{
+					minDist = dist;
+					index = i - 1;
+					length = tmp;
+				}
+				
+				p0 = pe;
+			}
+			
+			var seg = segments[index];
+			p0 = edgeState.absolutePoints[index];
+			pe = edgeState.absolutePoints[index + 1];
+			
+			var x2 = p0.x;
+			var y2 = p0.y;
+			
+			var x1 = pe.x;
+			var y1 = pe.y;
+			
+			var px = x;
+			var py = y;
+			
+			var xSegment = x2 - x1;
+			var ySegment = y2 - y1;
+			
+			px -= x1;
+			py -= y1;
+			var projlenSq = 0;
+			
+			px = xSegment - px;
+			py = ySegment - py;
+			var dotprod = px * xSegment + py * ySegment;
+
+			if (dotprod <= 0.0)
+			{
+				projlenSq = 0;
+			}
+			else
+			{
+				projlenSq = dotprod * dotprod
+						/ (xSegment * xSegment + ySegment * ySegment);
+			}
+
+			var projlen = Math.sqrt(projlenSq);
+
+			if (projlen > seg)
+			{
+				projlen = seg;
+			}
+
+			var yDistance = Math.sqrt(mxUtils.ptSegDistSq(p0.x, p0.y, pe
+					.x, pe.y, x, y));
+			var direction = mxUtils.relativeCcw(p0.x, p0.y, pe.x, pe.y, x, y);
+
+			if (direction == -1)
+			{
+				yDistance = -yDistance;
+			}
+
+			// Constructs the relative point for the label
+			return new mxPoint(((totalLength / 2 - length - projlen) / totalLength) * -2,
+						yDistance / this.scale);
+		}
+	}
+	
+	return new mxPoint();
+};
+
+/**
+ * Function: updateEdgeLabelOffset
+ *
+ * Updates <mxCellState.absoluteOffset> for the given state. The absolute
+ * offset is normally used for the position of the edge label. Is is
+ * calculated from the geometry as an absolute offset from the center
+ * between the two endpoints if the geometry is absolute, or as the
+ * relative distance between the center along the line and the absolute
+ * orthogonal distance if the geometry is relative.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose absolute offset should be updated.
+ */
+mxGraphView.prototype.updateEdgeLabelOffset = function(state)
+{
+	var points = state.absolutePoints;
+	
+	state.absoluteOffset.x = state.getCenterX();
+	state.absoluteOffset.y = state.getCenterY();
+
+	if (points != null && points.length > 0 && state.segments != null)
+	{
+		var geometry = this.graph.getCellGeometry(state.cell);
+		
+		if (geometry.relative)
+		{
+			var offset = this.getPoint(state, geometry);
+			
+			if (offset != null)
+			{
+				state.absoluteOffset = offset;
+			}
+		}
+		else
+		{
+			var p0 = points[0];
+			var pe = points[points.length - 1];
+			
+			if (p0 != null && pe != null)
+			{
+				var dx = pe.x - p0.x;
+				var dy = pe.y - p0.y;
+				var x0 = 0;
+				var y0 = 0;
+
+				var off = geometry.offset;
+				
+				if (off != null)
+				{
+					x0 = off.x;
+					y0 = off.y;
+				}
+				
+				var x = p0.x + dx / 2 + x0 * this.scale;
+				var y = p0.y + dy / 2 + y0 * this.scale;
+				
+				state.absoluteOffset.x = x;
+				state.absoluteOffset.y = y;
+			}
+		}
+	}
+};
+
+/**
+ * Function: getState
+ *
+ * Returns the <mxCellState> for the given cell. If create is true, then
+ * the state is created if it does not yet exist.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> for which the <mxCellState> should be returned.
+ * create - Optional boolean indicating if a new state should be created
+ * if it does not yet exist. Default is false.
+ */
+mxGraphView.prototype.getState = function(cell, create)
+{
+	create = create || false;
+	var state = null;
+	
+	if (cell != null)
+	{
+		state = this.states.get(cell);
+		
+		if (create && (state == null || this.updateStyle) && this.graph.isCellVisible(cell))
+		{
+			if (state == null)
+			{
+				state = this.createState(cell);
+				this.states.put(cell, state);
+			}
+			else
+			{
+				state.style = this.graph.getCellStyle(cell);
+			}
+		}
+	}
+
+	return state;
+};
+
+/**
+ * Function: isRendering
+ *
+ * Returns <rendering>.
+ */
+mxGraphView.prototype.isRendering = function()
+{
+	return this.rendering;
+};
+
+/**
+ * Function: setRendering
+ *
+ * Sets <rendering>.
+ */
+mxGraphView.prototype.setRendering = function(value)
+{
+	this.rendering = value;
+};
+
+/**
+ * Function: isAllowEval
+ *
+ * Returns <allowEval>.
+ */
+mxGraphView.prototype.isAllowEval = function()
+{
+	return this.allowEval;
+};
+
+/**
+ * Function: setAllowEval
+ *
+ * Sets <allowEval>.
+ */
+mxGraphView.prototype.setAllowEval = function(value)
+{
+	this.allowEval = value;
+};
+
+/**
+ * Function: getStates
+ *
+ * Returns <states>.
+ */
+mxGraphView.prototype.getStates = function()
+{
+	return this.states;
+};
+
+/**
+ * Function: setStates
+ *
+ * Sets <states>.
+ */
+mxGraphView.prototype.setStates = function(value)
+{
+	this.states = value;
+};
+
+/**
+ * Function: getCellStates
+ *
+ * Returns the <mxCellStates> for the given array of <mxCells>. The array
+ * contains all states that are not null, that is, the returned array may
+ * have less elements than the given array. If no argument is given, then
+ * this returns <states>.
+ */
+mxGraphView.prototype.getCellStates = function(cells)
+{
+	if (cells == null)
+	{
+		return this.states;
+	}
+	else
+	{
+		var result = [];
+		
+		for (var i = 0; i < cells.length; i++)
+		{
+			var state = this.getState(cells[i]);
+			
+			if (state != null)
+			{
+				result.push(state);
+			}
+		}
+		
+		return result;
+	}
+};
+
+/**
+ * Function: removeState
+ *
+ * Removes and returns the <mxCellState> for the given cell.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> for which the <mxCellState> should be removed.
+ */
+mxGraphView.prototype.removeState = function(cell)
+{
+	var state = null;
+	
+	if (cell != null)
+	{
+		state = this.states.remove(cell);
+		
+		if (state != null)
+		{
+			this.graph.cellRenderer.destroy(state);
+			state.invalid = true;
+			state.destroy();
+		}
+	}
+	
+	return state;
+};
+
+/**
+ * Function: createState
+ *
+ * Creates and returns an <mxCellState> for the given cell and initializes
+ * it using <mxCellRenderer.initialize>.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> for which a new <mxCellState> should be created.
+ */
+mxGraphView.prototype.createState = function(cell)
+{
+	return new mxCellState(this, cell, this.graph.getCellStyle(cell));
+};
+
+/**
+ * Function: getCanvas
+ *
+ * Returns the DOM node that contains the background-, draw- and
+ * overlay- and decoratorpanes.
+ */
+mxGraphView.prototype.getCanvas = function()
+{
+	return this.canvas;
+};
+
+/**
+ * Function: getBackgroundPane
+ *
+ * Returns the DOM node that represents the background layer.
+ */
+mxGraphView.prototype.getBackgroundPane = function()
+{
+	return this.backgroundPane;
+};
+
+/**
+ * Function: getDrawPane
+ *
+ * Returns the DOM node that represents the main drawing layer.
+ */
+mxGraphView.prototype.getDrawPane = function()
+{
+	return this.drawPane;
+};
+
+/**
+ * Function: getOverlayPane
+ *
+ * Returns the DOM node that represents the layer above the drawing layer.
+ */
+mxGraphView.prototype.getOverlayPane = function()
+{
+	return this.overlayPane;
+};
+
+/**
+ * Function: getDecoratorPane
+ *
+ * Returns the DOM node that represents the topmost drawing layer.
+ */
+mxGraphView.prototype.getDecoratorPane = function()
+{
+	return this.decoratorPane;
+};
+
+/**
+ * Function: isContainerEvent
+ * 
+ * Returns true if the event origin is one of the drawing panes or
+ * containers of the view.
+ */
+mxGraphView.prototype.isContainerEvent = function(evt)
+{
+	var source = mxEvent.getSource(evt);
+
+	return (source == this.graph.container ||
+		source.parentNode == this.backgroundPane ||
+		(source.parentNode != null &&
+		source.parentNode.parentNode == this.backgroundPane) ||
+		source == this.canvas.parentNode ||
+		source == this.canvas ||
+		source == this.backgroundPane ||
+		source == this.drawPane ||
+		source == this.overlayPane ||
+		source == this.decoratorPane);
+};
+
+/**
+ * Function: isScrollEvent
+ * 
+ * Returns true if the event origin is one of the scrollbars of the
+ * container in IE. Such events are ignored.
+ */
+ mxGraphView.prototype.isScrollEvent = function(evt)
+{
+	var offset = mxUtils.getOffset(this.graph.container);
+	var pt = new mxPoint(evt.clientX - offset.x, evt.clientY - offset.y);
+
+	var outWidth = this.graph.container.offsetWidth;
+	var inWidth = this.graph.container.clientWidth;
+
+	if (outWidth > inWidth && pt.x > inWidth + 2 && pt.x <= outWidth)
+	{
+		return true;
+	}
+
+	var outHeight = this.graph.container.offsetHeight;
+	var inHeight = this.graph.container.clientHeight;
+	
+	if (outHeight > inHeight && pt.y > inHeight + 2 && pt.y <= outHeight)
+	{
+		return true;
+	}
+	
+	return false;
+};
+
+/**
+ * Function: init
+ *
+ * Initializes the graph event dispatch loop for the specified container
+ * and invokes <create> to create the required DOM nodes for the display.
+ */
+mxGraphView.prototype.init = function()
+{
+	this.installListeners();
+	
+	// Creates the DOM nodes for the respective display dialect
+	var graph = this.graph;
+	
+	if (graph.dialect == mxConstants.DIALECT_SVG)
+	{
+		this.createSvg();
+	}
+	else if (graph.dialect == mxConstants.DIALECT_VML)
+	{
+		this.createVml();
+	}
+	else
+	{
+		this.createHtml();
+	}
+};
+
+/**
+ * Function: installListeners
+ *
+ * Installs the required listeners in the container.
+ */
+mxGraphView.prototype.installListeners = function()
+{
+	var graph = this.graph;
+	var container = graph.container;
+	
+	if (container != null)
+	{
+		// Support for touch device gestures (eg. pinch to zoom)
+		// Double-tap handling is implemented in mxGraph.fireMouseEvent
+		if (mxClient.IS_TOUCH)
+		{
+			mxEvent.addListener(container, 'gesturestart', mxUtils.bind(this, function(evt)
+			{
+				graph.fireGestureEvent(evt);
+				mxEvent.consume(evt);
+			}));
+			
+			mxEvent.addListener(container, 'gesturechange', mxUtils.bind(this, function(evt)
+			{
+				graph.fireGestureEvent(evt);
+				mxEvent.consume(evt);
+			}));
+
+			mxEvent.addListener(container, 'gestureend', mxUtils.bind(this, function(evt)
+			{
+				graph.fireGestureEvent(evt);
+				mxEvent.consume(evt);
+			}));
+		}
+		
+		// Adds basic listeners for graph event dispatching
+		mxEvent.addGestureListeners(container, mxUtils.bind(this, function(evt)
+		{
+			// Condition to avoid scrollbar events starting a rubberband selection
+			if (this.isContainerEvent(evt) && ((!mxClient.IS_IE && !mxClient.IS_IE11 && !mxClient.IS_GC &&
+				!mxClient.IS_OP && !mxClient.IS_SF) || !this.isScrollEvent(evt)))
+			{
+				graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt));
+			}
+		}),
+		mxUtils.bind(this, function(evt)
+		{
+			if (this.isContainerEvent(evt))
+			{
+				graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt));
+			}
+		}),
+		mxUtils.bind(this, function(evt)
+		{
+			if (this.isContainerEvent(evt))
+			{
+				graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt));
+			}
+		}));
+		
+		// Adds listener for double click handling on background, this does always
+		// use native event handler, we assume that the DOM of the background
+		// does not change during the double click
+		mxEvent.addListener(container, 'dblclick', mxUtils.bind(this, function(evt)
+		{
+			if (this.isContainerEvent(evt))
+			{
+				graph.dblClick(evt);
+			}
+		}));
+
+		// Workaround for touch events which started on some DOM node
+		// on top of the container, in which case the cells under the
+		// mouse for the move and up events are not detected.
+		var getState = function(evt)
+		{
+			var state = null;
+			
+			// Workaround for touch events which started on some DOM node
+			// on top of the container, in which case the cells under the
+			// mouse for the move and up events are not detected.
+			if (mxClient.IS_TOUCH)
+			{
+				var x = mxEvent.getClientX(evt);
+				var y = mxEvent.getClientY(evt);
+				
+				// Dispatches the drop event to the graph which
+				// consumes and executes the source function
+				var pt = mxUtils.convertPoint(container, x, y);
+				state = graph.view.getState(graph.getCellAt(pt.x, pt.y));
+			}
+			
+			return state;
+		};
+		
+		// Adds basic listeners for graph event dispatching outside of the
+		// container and finishing the handling of a single gesture
+		// Implemented via graph event dispatch loop to avoid duplicate events
+		// in Firefox and Chrome
+		graph.addMouseListener(
+		{
+			mouseDown: function(sender, me)
+			{
+				graph.popupMenuHandler.hideMenu();
+			},
+			mouseMove: function() { },
+			mouseUp: function() { }
+		});
+		
+		this.moveHandler = mxUtils.bind(this, function(evt)
+		{
+			// Hides the tooltip if mouse is outside container
+			if (graph.tooltipHandler != null && graph.tooltipHandler.isHideOnHover())
+			{
+				graph.tooltipHandler.hide();
+			}
+
+			if (this.captureDocumentGesture && graph.isMouseDown && graph.container != null &&
+				!this.isContainerEvent(evt) && graph.container.style.display != 'none' &&
+				graph.container.style.visibility != 'hidden' && !mxEvent.isConsumed(evt))
+			{
+				graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, getState(evt)));
+			}
+		});
+		
+		this.endHandler = mxUtils.bind(this, function(evt)
+		{
+			if (this.captureDocumentGesture && graph.isMouseDown && graph.container != null &&
+				!this.isContainerEvent(evt) && graph.container.style.display != 'none' &&
+				graph.container.style.visibility != 'hidden')
+			{
+				graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt));
+			}
+		});
+		
+		mxEvent.addGestureListeners(document, null, this.moveHandler, this.endHandler);
+	}
+};
+
+/**
+ * Function: create
+ *
+ * Creates the DOM nodes for the HTML display.
+ */
+mxGraphView.prototype.createHtml = function()
+{
+	var container = this.graph.container;
+	
+	if (container != null)
+	{
+		this.canvas = this.createHtmlPane('100%', '100%');
+		this.canvas.style.overflow = 'hidden';
+	
+		// Uses minimal size for inner DIVs on Canvas. This is required
+		// for correct event processing in IE. If we have an overlapping
+		// DIV then the events on the cells are only fired for labels.
+		this.backgroundPane = this.createHtmlPane('1px', '1px');
+		this.drawPane = this.createHtmlPane('1px', '1px');
+		this.overlayPane = this.createHtmlPane('1px', '1px');
+		this.decoratorPane = this.createHtmlPane('1px', '1px');
+		
+		this.canvas.appendChild(this.backgroundPane);
+		this.canvas.appendChild(this.drawPane);
+		this.canvas.appendChild(this.overlayPane);
+		this.canvas.appendChild(this.decoratorPane);
+
+		container.appendChild(this.canvas);
+		this.updateContainerStyle(container);
+		
+		// Implements minWidth/minHeight in quirks mode
+		if (mxClient.IS_QUIRKS)
+		{
+			var onResize = mxUtils.bind(this, function(evt)
+			{
+				var bounds = this.getGraphBounds();
+				var width = bounds.x + bounds.width + this.graph.border;
+				var height = bounds.y + bounds.height + this.graph.border;
+				
+				this.updateHtmlCanvasSize(width, height);
+			});
+			
+			mxEvent.addListener(window, 'resize', onResize);
+		}
+	}
+};
+
+/**
+ * Function: updateHtmlCanvasSize
+ * 
+ * Updates the size of the HTML canvas.
+ */
+mxGraphView.prototype.updateHtmlCanvasSize = function(width, height)
+{
+	if (this.graph.container != null)
+	{
+		var ow = this.graph.container.offsetWidth;
+		var oh = this.graph.container.offsetHeight;
+
+		if (ow < width)
+		{
+			this.canvas.style.width = width + 'px';
+		}
+		else
+		{
+			this.canvas.style.width = '100%';
+		}
+
+		if (oh < height)
+		{
+			this.canvas.style.height = height + 'px';
+		}
+		else
+		{
+			this.canvas.style.height = '100%';
+		}
+	}
+};
+
+/**
+ * Function: createHtmlPane
+ * 
+ * Creates and returns a drawing pane in HTML (DIV).
+ */
+mxGraphView.prototype.createHtmlPane = function(width, height)
+{
+	var pane = document.createElement('DIV');
+	
+	if (width != null && height != null)
+	{
+		pane.style.position = 'absolute';
+		pane.style.left = '0px';
+		pane.style.top = '0px';
+
+		pane.style.width = width;
+		pane.style.height = height;
+	}
+	else
+	{
+		pane.style.position = 'relative';
+	}
+	
+	return pane;
+};
+
+/**
+ * Function: create
+ *
+ * Creates the DOM nodes for the VML display.
+ */
+mxGraphView.prototype.createVml = function()
+{
+	var container = this.graph.container;
+
+	if (container != null)
+	{
+		var width = container.offsetWidth;
+		var height = container.offsetHeight;
+		this.canvas = this.createVmlPane(width, height);
+		this.canvas.style.overflow = 'hidden';
+		
+		this.backgroundPane = this.createVmlPane(width, height);
+		this.drawPane = this.createVmlPane(width, height);
+		this.overlayPane = this.createVmlPane(width, height);
+		this.decoratorPane = this.createVmlPane(width, height);
+		
+		this.canvas.appendChild(this.backgroundPane);
+		this.canvas.appendChild(this.drawPane);
+		this.canvas.appendChild(this.overlayPane);
+		this.canvas.appendChild(this.decoratorPane);
+		
+		container.appendChild(this.canvas);
+	}
+};
+
+/**
+ * Function: createVmlPane
+ * 
+ * Creates a drawing pane in VML (group).
+ */
+mxGraphView.prototype.createVmlPane = function(width, height)
+{
+	var pane = document.createElement(mxClient.VML_PREFIX + ':group');
+	
+	// At this point the width and height are potentially
+	// uninitialized. That's OK.
+	pane.style.position = 'absolute';
+	pane.style.left = '0px';
+	pane.style.top = '0px';
+
+	pane.style.width = width + 'px';
+	pane.style.height = height + 'px';
+
+	pane.setAttribute('coordsize', width + ',' + height);
+	pane.setAttribute('coordorigin', '0,0');
+	
+	return pane;
+};
+
+/**
+ * Function: create
+ *
+ * Creates and returns the DOM nodes for the SVG display.
+ */
+mxGraphView.prototype.createSvg = function()
+{
+	var container = this.graph.container;
+	this.canvas = document.createElementNS(mxConstants.NS_SVG, 'g');
+	
+	// For background image
+	this.backgroundPane = document.createElementNS(mxConstants.NS_SVG, 'g');
+	this.canvas.appendChild(this.backgroundPane);
+
+	// Adds two layers (background is early feature)
+	this.drawPane = document.createElementNS(mxConstants.NS_SVG, 'g');
+	this.canvas.appendChild(this.drawPane);
+
+	this.overlayPane = document.createElementNS(mxConstants.NS_SVG, 'g');
+	this.canvas.appendChild(this.overlayPane);
+	
+	this.decoratorPane = document.createElementNS(mxConstants.NS_SVG, 'g');
+	this.canvas.appendChild(this.decoratorPane);
+	
+	var root = document.createElementNS(mxConstants.NS_SVG, 'svg');
+	root.style.width = '100%';
+	root.style.height = '100%';
+	
+	// NOTE: In standards mode, the SVG must have block layout
+	// in order for the container DIV to not show scrollbars.
+	root.style.display = 'block';
+	root.appendChild(this.canvas);
+	
+	// Workaround for scrollbars in IE11 and below
+	if (mxClient.IS_IE || mxClient.IS_IE11)
+	{
+		root.style.overflow = 'hidden';
+	}
+
+	if (container != null)
+	{
+		container.appendChild(root);
+		this.updateContainerStyle(container);
+	}
+};
+
+/**
+ * Function: updateContainerStyle
+ * 
+ * Updates the style of the container after installing the SVG DOM elements.
+ */
+mxGraphView.prototype.updateContainerStyle = function(container)
+{
+	// Workaround for offset of container
+	var style = mxUtils.getCurrentStyle(container);
+	
+	if (style != null && style.position == 'static')
+	{
+		container.style.position = 'relative';
+	}
+	
+	// Disables built-in pan and zoom in IE10 and later
+	if (mxClient.IS_POINTER)
+	{
+		container.style.touchAction = 'none';
+	}
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the view and all its resources.
+ */
+mxGraphView.prototype.destroy = function()
+{
+	var root = (this.canvas != null) ? this.canvas.ownerSVGElement : null;
+	
+	if (root == null)
+	{
+		root = this.canvas;
+	}
+	
+	if (root != null && root.parentNode != null)
+	{
+		this.clear(this.currentRoot, true);
+		mxEvent.removeGestureListeners(document, null, this.moveHandler, this.endHandler);
+		mxEvent.release(this.graph.container);
+		root.parentNode.removeChild(root);
+		
+		this.moveHandler = null;
+		this.endHandler = null;
+		this.canvas = null;
+		this.backgroundPane = null;
+		this.drawPane = null;
+		this.overlayPane = null;
+		this.decoratorPane = null;
+	}
+};
+
+/**
+ * Class: mxCurrentRootChange
+ *
+ * Action to change the current root in a view.
+ *
+ * Constructor: mxCurrentRootChange
+ *
+ * Constructs a change of the current root in the given view.
+ */
+function mxCurrentRootChange(view, root)
+{
+	this.view = view;
+	this.root = root;
+	this.previous = root;
+	this.isUp = root == null;
+	
+	if (!this.isUp)
+	{
+		var tmp = this.view.currentRoot;
+		var model = this.view.graph.getModel();
+		
+		while (tmp != null)
+		{
+			if (tmp == root)
+			{
+				this.isUp = true;
+				break;
+			}
+			
+			tmp = model.getParent(tmp);
+		}
+	}
+};
+
+/**
+ * Function: execute
+ *
+ * Changes the current root of the view.
+ */
+mxCurrentRootChange.prototype.execute = function()
+{
+	var tmp = this.view.currentRoot;
+	this.view.currentRoot = this.previous;
+	this.previous = tmp;
+
+	var translate = this.view.graph.getTranslateForRoot(this.view.currentRoot);
+	
+	if (translate != null)
+	{
+		this.view.translate = new mxPoint(-translate.x, -translate.y);
+	}
+
+	if (this.isUp)
+	{
+		this.view.clear(this.view.currentRoot, true);
+		this.view.validate();
+	}
+	else
+	{
+		this.view.refresh();
+	}
+	
+	var name = (this.isUp) ? mxEvent.UP : mxEvent.DOWN;
+	this.view.fireEvent(new mxEventObject(name,
+		'root', this.view.currentRoot, 'previous', this.previous));
+	this.isUp = !this.isUp;
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxGraph
+ *
+ * Extends <mxEventSource> to implement a graph component for
+ * the browser. This is the main class of the package. To activate
+ * panning and connections use <setPanning> and <setConnectable>.
+ * For rubberband selection you must create a new instance of
+ * <mxRubberband>. The following listeners are added to
+ * <mouseListeners> by default:
+ * 
+ * - <tooltipHandler>: <mxTooltipHandler> that displays tooltips
+ * - <panningHandler>: <mxPanningHandler> for panning and popup menus
+ * - <connectionHandler>: <mxConnectionHandler> for creating connections
+ * - <graphHandler>: <mxGraphHandler> for moving and cloning cells
+ * 
+ * These listeners will be called in the above order if they are enabled.
+ *
+ * Background Images:
+ * 
+ * To display a background image, set the image, image width and
+ * image height using <setBackgroundImage>. If one of the
+ * above values has changed then the <view>'s <mxGraphView.validate>
+ * should be invoked.
+ * 
+ * Cell Images:
+ * 
+ * To use images in cells, a shape must be specified in the default
+ * vertex style (or any named style). Possible shapes are
+ * <mxConstants.SHAPE_IMAGE> and <mxConstants.SHAPE_LABEL>.
+ * The code to change the shape used in the default vertex style,
+ * the following code is used:
+ * 
+ * (code)
+ * var style = graph.getStylesheet().getDefaultVertexStyle();
+ * style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_IMAGE;
+ * (end)
+ * 
+ * For the default vertex style, the image to be displayed can be
+ * specified in a cell's style using the <mxConstants.STYLE_IMAGE>
+ * key and the image URL as a value, for example:
+ * 
+ * (code)
+ * image=http://www.example.com/image.gif
+ * (end)
+ * 
+ * For a named style, the the stylename must be the first element
+ * of the cell style:
+ * 
+ * (code)
+ * stylename;image=http://www.example.com/image.gif
+ * (end)
+ * 
+ * A cell style can have any number of key=value pairs added, divided
+ * by a semicolon as follows:
+ * 
+ * (code)
+ * [stylename;|key=value;]
+ * (end)
+ *
+ * Labels:
+ * 
+ * The cell labels are defined by <getLabel> which uses <convertValueToString>
+ * if <labelsVisible> is true. If a label must be rendered as HTML markup, then
+ * <isHtmlLabel> should return true for the respective cell. If all labels
+ * contain HTML markup, <htmlLabels> can be set to true. NOTE: Enabling HTML
+ * labels carries a possible security risk (see the section on security in
+ * the manual).
+ * 
+ * If wrapping is needed for a label, then <isHtmlLabel> and <isWrapping> must
+ * return true for the cell whose label should be wrapped. See <isWrapping> for
+ * an example.
+ * 
+ * If clipping is needed to keep the rendering of a HTML label inside the
+ * bounds of its vertex, then <isClipping> should return true for the
+ * respective cell.
+ * 
+ * By default, edge labels are movable and vertex labels are fixed. This can be
+ * changed by setting <edgeLabelsMovable> and <vertexLabelsMovable>, or by
+ * overriding <isLabelMovable>.
+ *
+ * In-place Editing:
+ * 
+ * In-place editing is started with a doubleclick or by typing F2.
+ * Programmatically, <edit> is used to check if the cell is editable
+ * (<isCellEditable>) and call <startEditingAtCell>, which invokes
+ * <mxCellEditor.startEditing>. The editor uses the value returned
+ * by <getEditingValue> as the editing value.
+ * 
+ * After in-place editing, <labelChanged> is called, which invokes
+ * <mxGraphModel.setValue>, which in turn calls
+ * <mxGraphModel.valueForCellChanged> via <mxValueChange>.
+ * 
+ * The event that triggers in-place editing is passed through to the
+ * <cellEditor>, which may take special actions depending on the type of the
+ * event or mouse location, and is also passed to <getEditingValue>. The event
+ * is then passed back to the event processing functions which can perform
+ * specific actions based on the trigger event.
+ * 
+ * Tooltips:
+ * 
+ * Tooltips are implemented by <getTooltip>, which calls <getTooltipForCell>
+ * if a cell is under the mousepointer. The default implementation checks if
+ * the cell has a getTooltip function and calls it if it exists. Hence, in order
+ * to provide custom tooltips, the cell must provide a getTooltip function, or 
+ * one of the two above functions must be overridden.
+ * 
+ * Typically, for custom cell tooltips, the latter function is overridden as
+ * follows:
+ * 
+ * (code)
+ * graph.getTooltipForCell = function(cell)
+ * {
+ *   var label = this.convertValueToString(cell);
+ *   return 'Tooltip for '+label;
+ * }
+ * (end)
+ * 
+ * When using a config file, the function is overridden in the mxGraph section
+ * using the following entry:
+ * 
+ * (code)
+ * <add as="getTooltipForCell"><![CDATA[
+ *   function(cell)
+ *   {
+ *     var label = this.convertValueToString(cell);
+ *     return 'Tooltip for '+label;
+ *   }
+ * ]]></add>
+ * (end)
+ * 
+ * "this" refers to the graph in the implementation, so for example to check if 
+ * a cell is an edge, you use this.getModel().isEdge(cell)
+ *
+ * For replacing the default implementation of <getTooltipForCell> (rather than 
+ * replacing the function on a specific instance), the following code should be 
+ * used after loading the JavaScript files, but before creating a new mxGraph 
+ * instance using <mxGraph>:
+ * 
+ * (code)
+ * mxGraph.prototype.getTooltipForCell = function(cell)
+ * {
+ *   var label = this.convertValueToString(cell);
+ *   return 'Tooltip for '+label;
+ * }
+ * (end)
+ * 
+ * Shapes & Styles:
+ * 
+ * The implementation of new shapes is demonstrated in the examples. We'll assume
+ * that we have implemented a custom shape with the name BoxShape which we want
+ * to use for drawing vertices. To use this shape, it must first be registered in
+ * the cell renderer as follows:
+ * 
+ * (code)
+ * mxCellRenderer.registerShape('box', BoxShape);
+ * (end)
+ * 
+ * The code registers the BoxShape constructor under the name box in the cell
+ * renderer of the graph. The shape can now be referenced using the shape-key in
+ * a style definition. (The cell renderer contains a set of additional shapes,
+ * namely one for each constant with a SHAPE-prefix in <mxConstants>.)
+ *
+ * Styles are a collection of key, value pairs and a stylesheet is a collection
+ * of named styles. The names are referenced by the cellstyle, which is stored
+ * in <mxCell.style> with the following format: [stylename;|key=value;]. The
+ * string is resolved to a collection of key, value pairs, where the keys are
+ * overridden with the values in the string.
+ *
+ * When introducing a new shape, the name under which the shape is registered
+ * must be used in the stylesheet. There are three ways of doing this:
+ * 
+ *   - By changing the default style, so that all vertices will use the new
+ * 		shape
+ *   - By defining a new style, so that only vertices with the respective
+ * 		cellstyle will use the new shape
+ *   - By using shape=box in the cellstyle's optional list of key, value pairs
+ * 		to be overridden
+ *
+ * In the first case, the code to fetch and modify the default style for
+ * vertices is as follows:
+ * 
+ * (code)
+ * var style = graph.getStylesheet().getDefaultVertexStyle();
+ * style[mxConstants.STYLE_SHAPE] = 'box';
+ * (end)
+ * 
+ * The code takes the default vertex style, which is used for all vertices that
+ * do not have a specific cellstyle, and modifies the value for the shape-key
+ * in-place to use the new BoxShape for drawing vertices. This is done by
+ * assigning the box value in the second line, which refers to the name of the
+ * BoxShape in the cell renderer.
+ * 
+ * In the second case, a collection of key, value pairs is created and then
+ * added to the stylesheet under a new name. In order to distinguish the
+ * shapename and the stylename we'll use boxstyle for the stylename:
+ * 
+ * (code)
+ * var style = new Object();
+ * style[mxConstants.STYLE_SHAPE] = 'box';
+ * style[mxConstants.STYLE_STROKECOLOR] = '#000000';
+ * style[mxConstants.STYLE_FONTCOLOR] = '#000000';
+ * graph.getStylesheet().putCellStyle('boxstyle', style);
+ * (end)
+ * 
+ * The code adds a new style with the name boxstyle to the stylesheet. To use
+ * this style with a cell, it must be referenced from the cellstyle as follows:
+ * 
+ * (code)
+ * var vertex = graph.insertVertex(parent, null, 'Hello, World!', 20, 20, 80, 20,
+ * 				'boxstyle');
+ * (end)
+ * 
+ * To summarize, each new shape must be registered in the <mxCellRenderer> with
+ * a unique name. That name is then used as the value of the shape-key in a
+ * default or custom style. If there are multiple custom shapes, then there
+ * should be a separate style for each shape.
+ * 
+ * Inheriting Styles:
+ * 
+ * For fill-, stroke-, gradient- and indicatorColors special keywords can be
+ * used. The inherit keyword for one of these colors will inherit the color
+ * for the same key from the parent cell. The swimlane keyword does the same,
+ * but inherits from the nearest swimlane in the ancestor hierarchy. Finally,
+ * the indicated keyword will use the color of the indicator as the color for
+ * the given key.
+ * 
+ * Scrollbars:
+ * 
+ * The <containers> overflow CSS property defines if scrollbars are used to
+ * display the graph. For values of 'auto' or 'scroll', the scrollbars will
+ * be shown. Note that the <resizeContainer> flag is normally not used
+ * together with scrollbars, as it will resize the container to match the
+ * size of the graph after each change.
+ * 
+ * Multiplicities and Validation:
+ * 
+ * To control the possible connections in mxGraph, <getEdgeValidationError> is
+ * used. The default implementation of the function uses <multiplicities>,
+ * which is an array of <mxMultiplicity>. Using this class allows to establish
+ * simple multiplicities, which are enforced by the graph.
+ * 
+ * The <mxMultiplicity> uses <mxCell.is> to determine for which terminals it
+ * applies. The default implementation of <mxCell.is> works with DOM nodes (XML
+ * nodes) and checks if the given type parameter matches the nodeName of the
+ * node (case insensitive). Optionally, an attributename and value can be
+ * specified which are also checked.
+ * 
+ * <getEdgeValidationError> is called whenever the connectivity of an edge
+ * changes. It returns an empty string or an error message if the edge is
+ * invalid or null if the edge is valid. If the returned string is not empty
+ * then it is displayed as an error message.
+ * 
+ * <mxMultiplicity> allows to specify the multiplicity between a terminal and
+ * its possible neighbors. For example, if any rectangle may only be connected
+ * to, say, a maximum of two circles you can add the following rule to
+ * <multiplicities>:
+ * 
+ * (code)
+ * graph.multiplicities.push(new mxMultiplicity(
+ *   true, 'rectangle', null, null, 0, 2, ['circle'],
+ *   'Only 2 targets allowed',
+ *   'Only shape targets allowed'));
+ * (end)
+ * 
+ * This will display the first error message whenever a rectangle is connected
+ * to more than two circles and the second error message if a rectangle is
+ * connected to anything but a circle.
+ * 
+ * For certain multiplicities, such as a minimum of 1 connection, which cannot
+ * be enforced at cell creation time (unless the cell is created together with
+ * the connection), mxGraph offers <validate> which checks all multiplicities
+ * for all cells and displays the respective error messages in an overlay icon
+ * on the cells.
+ * 
+ * If a cell is collapsed and contains validation errors, a respective warning
+ * icon is attached to the collapsed cell.
+ * 
+ * Auto-Layout:
+ * 
+ * For automatic layout, the <getLayout> hook is provided in <mxLayoutManager>.
+ * It can be overridden to return a layout algorithm for the children of a
+ * given cell.
+ * 
+ * Unconnected edges:
+ * 
+ * The default values for all switches are designed to meet the requirements of
+ * general diagram drawing applications. A very typical set of settings to
+ * avoid edges that are not connected is the following:
+ * 
+ * (code)
+ * graph.setAllowDanglingEdges(false);
+ * graph.setDisconnectOnMove(false);
+ * (end)
+ * 
+ * Setting the <cloneInvalidEdges> switch to true is optional. This switch
+ * controls if edges are inserted after a copy, paste or clone-drag if they are
+ * invalid. For example, edges are invalid if copied or control-dragged without 
+ * having selected the corresponding terminals and allowDanglingEdges is
+ * false, in which case the edges will not be cloned if the switch is false.
+ * 
+ * Output:
+ * 
+ * To produce an XML representation for a diagram, the following code can be
+ * used.
+ * 
+ * (code)
+ * var enc = new mxCodec(mxUtils.createXmlDocument());
+ * var node = enc.encode(graph.getModel());
+ * (end)
+ * 
+ * This will produce an XML node than can be handled using the DOM API or
+ * turned into a string representation using the following code:
+ * 
+ * (code)
+ * var xml = mxUtils.getXml(node);
+ * (end)
+ * 
+ * To obtain a formatted string, mxUtils.getPrettyXml can be used instead.
+ * 
+ * This string can now be stored in a local persistent storage (for example
+ * using Google Gears) or it can be passed to a backend using mxUtils.post as
+ * follows. The url variable is the URL of the Java servlet, PHP page or HTTP
+ * handler, depending on the server.
+ * 
+ * (code)
+ * var xmlString = encodeURIComponent(mxUtils.getXml(node));
+ * mxUtils.post(url, 'xml='+xmlString, function(req)
+ * {
+ *   // Process server response using req of type mxXmlRequest
+ * });
+ * (end)
+ * 
+ * Input:
+ * 
+ * To load an XML representation of a diagram into an existing graph object
+ * mxUtils.load can be used as follows. The url variable is the URL of the Java
+ * servlet, PHP page or HTTP handler that produces the XML string.
+ * 
+ * (code)
+ * var xmlDoc = mxUtils.load(url).getXml();
+ * var node = xmlDoc.documentElement;
+ * var dec = new mxCodec(node.ownerDocument);
+ * dec.decode(node, graph.getModel());
+ * (end)
+ * 
+ * For creating a page that loads the client and a diagram using a single
+ * request please refer to the deployment examples in the backends.
+ * 
+ * Functional dependencies:
+ * 
+ * (see images/callgraph.png)
+ * 
+ * Resources:
+ *
+ * resources/graph - Language resources for mxGraph
+ *
+ * Group: Events
+ * 
+ * Event: mxEvent.ROOT
+ * 
+ * Fires if the root in the model has changed. This event has no properties.
+ * 
+ * Event: mxEvent.ALIGN_CELLS
+ * 
+ * Fires between begin- and endUpdate in <alignCells>. The <code>cells</code>
+ * and <code>align</code> properties contain the respective arguments that were
+ * passed to <alignCells>.
+ *
+ * Event: mxEvent.FLIP_EDGE
+ *
+ * Fires between begin- and endUpdate in <flipEdge>. The <code>edge</code>
+ * property contains the edge passed to <flipEdge>.
+ * 
+ * Event: mxEvent.ORDER_CELLS
+ * 
+ * Fires between begin- and endUpdate in <orderCells>. The <code>cells</code>
+ * and <code>back</code> properties contain the respective arguments that were
+ * passed to <orderCells>.
+ *
+ * Event: mxEvent.CELLS_ORDERED
+ *
+ * Fires between begin- and endUpdate in <cellsOrdered>. The <code>cells</code>
+ * and <code>back</code> arguments contain the respective arguments that were
+ * passed to <cellsOrdered>.
+ * 
+ * Event: mxEvent.GROUP_CELLS
+ * 
+ * Fires between begin- and endUpdate in <groupCells>. The <code>group</code>,
+ * <code>cells</code> and <code>border</code> arguments contain the respective
+ * arguments that were passed to <groupCells>.
+ * 
+ * Event: mxEvent.UNGROUP_CELLS
+ * 
+ * Fires between begin- and endUpdate in <ungroupCells>. The <code>cells</code>
+ * property contains the array of cells that was passed to <ungroupCells>.
+ * 
+ * Event: mxEvent.REMOVE_CELLS_FROM_PARENT
+ * 
+ * Fires between begin- and endUpdate in <removeCellsFromParent>. The
+ * <code>cells</code> property contains the array of cells that was passed to
+ * <removeCellsFromParent>.
+ * 
+ * Event: mxEvent.ADD_CELLS
+ * 
+ * Fires between begin- and endUpdate in <addCells>. The <code>cells</code>,
+ * <code>parent</code>, <code>index</code>, <code>source</code> and
+ * <code>target</code> properties contain the respective arguments that were
+ * passed to <addCells>.
+ * 
+ * Event: mxEvent.CELLS_ADDED
+ * 
+ * Fires between begin- and endUpdate in <cellsAdded>. The <code>cells</code>,
+ * <code>parent</code>, <code>index</code>, <code>source</code>,
+ * <code>target</code> and <code>absolute</code> properties contain the
+ * respective arguments that were passed to <cellsAdded>.
+ * 
+ * Event: mxEvent.REMOVE_CELLS
+ * 
+ * Fires between begin- and endUpdate in <removeCells>. The <code>cells</code>
+ * and <code>includeEdges</code> arguments contain the respective arguments
+ * that were passed to <removeCells>.
+ * 
+ * Event: mxEvent.CELLS_REMOVED
+ * 
+ * Fires between begin- and endUpdate in <cellsRemoved>. The <code>cells</code>
+ * argument contains the array of cells that was removed.
+ * 
+ * Event: mxEvent.SPLIT_EDGE
+ * 
+ * Fires between begin- and endUpdate in <splitEdge>. The <code>edge</code>
+ * property contains the edge to be splitted, the <code>cells</code>,
+ * <code>newEdge</code>, <code>dx</code> and <code>dy</code> properties contain
+ * the respective arguments that were passed to <splitEdge>.
+ * 
+ * Event: mxEvent.TOGGLE_CELLS
+ * 
+ * Fires between begin- and endUpdate in <toggleCells>. The <code>show</code>,
+ * <code>cells</code> and <code>includeEdges</code> properties contain the
+ * respective arguments that were passed to <toggleCells>.
+ * 
+ * Event: mxEvent.FOLD_CELLS
+ * 
+ * Fires between begin- and endUpdate in <foldCells>. The
+ * <code>collapse</code>, <code>cells</code> and <code>recurse</code>
+ * properties contain the respective arguments that were passed to <foldCells>.
+ * 
+ * Event: mxEvent.CELLS_FOLDED
+ * 
+ * Fires between begin- and endUpdate in cellsFolded. The
+ * <code>collapse</code>, <code>cells</code> and <code>recurse</code>
+ * properties contain the respective arguments that were passed to
+ * <cellsFolded>.
+ * 
+ * Event: mxEvent.UPDATE_CELL_SIZE
+ * 
+ * Fires between begin- and endUpdate in <updateCellSize>. The
+ * <code>cell</code> and <code>ignoreChildren</code> properties contain the
+ * respective arguments that were passed to <updateCellSize>.
+ * 
+ * Event: mxEvent.RESIZE_CELLS
+ * 
+ * Fires between begin- and endUpdate in <resizeCells>. The <code>cells</code>
+ * and <code>bounds</code> properties contain the respective arguments that
+ * were passed to <resizeCells>.
+ * 
+ * Event: mxEvent.CELLS_RESIZED
+ * 
+ * Fires between begin- and endUpdate in <cellsResized>. The <code>cells</code>
+ * and <code>bounds</code> properties contain the respective arguments that
+ * were passed to <cellsResized>.
+ * 
+ * Event: mxEvent.MOVE_CELLS
+ * 
+ * Fires between begin- and endUpdate in <moveCells>. The <code>cells</code>,
+ * <code>dx</code>, <code>dy</code>, <code>clone</code>, <code>target</code>
+ * and <code>event</code> properties contain the respective arguments that
+ * were passed to <moveCells>.
+ * 
+ * Event: mxEvent.CELLS_MOVED
+ * 
+ * Fires between begin- and endUpdate in <cellsMoved>. The <code>cells</code>,
+ * <code>dx</code>, <code>dy</code> and <code>disconnect</code> properties
+ * contain the respective arguments that were passed to <cellsMoved>.
+ * 
+ * Event: mxEvent.CONNECT_CELL
+ * 
+ * Fires between begin- and endUpdate in <connectCell>. The <code>edge</code>,
+ * <code>terminal</code> and <code>source</code> properties contain the
+ * respective arguments that were passed to <connectCell>.
+ * 
+ * Event: mxEvent.CELL_CONNECTED
+ * 
+ * Fires between begin- and endUpdate in <cellConnected>. The
+ * <code>edge</code>, <code>terminal</code> and <code>source</code> properties
+ * contain the respective arguments that were passed to <cellConnected>.
+ * 
+ * Event: mxEvent.REFRESH
+ * 
+ * Fires after <refresh> was executed. This event has no properties.
+ *
+ * Event: mxEvent.CLICK
+ * 
+ * Fires in <click> after a click event. The <code>event</code> property
+ * contains the original mouse event and <code>cell</code> property contains
+ * the cell under the mouse or null if the background was clicked.
+ * 
+ * Event: mxEvent.DOUBLE_CLICK
+ *
+ * Fires in <dblClick> after a double click. The <code>event</code> property
+ * contains the original mouse event and the <code>cell</code> property
+ * contains the cell under the mouse or null if the background was clicked.
+ * 
+ * Event: mxEvent.GESTURE
+ *
+ * Fires in <fireGestureEvent> after a touch gesture. The <code>event</code>
+ * property contains the original gesture end event and the <code>cell</code>
+ * property contains the optional cell associated with the gesture.
+ *
+ * Event: mxEvent.TAP_AND_HOLD
+ *
+ * Fires in <tapAndHold> if a tap and hold event was detected. The <code>event</code>
+ * property contains the initial touch event and the <code>cell</code> property
+ * contains the cell under the mouse or null if the background was clicked.
+ *
+ * Event: mxEvent.FIRE_MOUSE_EVENT
+ *
+ * Fires in <fireMouseEvent> before the mouse listeners are invoked. The
+ * <code>eventName</code> property contains the event name and the
+ * <code>event</code> property contains the <mxMouseEvent>.
+ *
+ * Event: mxEvent.SIZE
+ *
+ * Fires after <sizeDidChange> was executed. The <code>bounds</code> property
+ * contains the new graph bounds.
+ *
+ * Event: mxEvent.START_EDITING
+ *
+ * Fires before the in-place editor starts in <startEditingAtCell>. The
+ * <code>cell</code> property contains the cell that is being edited and the
+ * <code>event</code> property contains the optional event argument that was
+ * passed to <startEditingAtCell>.
+ * 
+ * Event: mxEvent.EDITING_STARTED
+ *
+ * Fires after the in-place editor starts in <startEditingAtCell>. The
+ * <code>cell</code> property contains the cell that is being edited and the
+ * <code>event</code> property contains the optional event argument that was
+ * passed to <startEditingAtCell>.
+ * 
+ * Event: mxEvent.EDITING_STOPPED
+ *
+ * Fires after the in-place editor stops in <stopEditing>.
+ *
+ * Event: mxEvent.LABEL_CHANGED
+ *
+ * Fires between begin- and endUpdate in <cellLabelChanged>. The
+ * <code>cell</code> property contains the cell, the <code>value</code>
+ * property contains the new value for the cell, the <code>old</code> property
+ * contains the old value and the optional <code>event</code> property contains
+ * the mouse event that started the edit.
+ * 
+ * Event: mxEvent.ADD_OVERLAY
+ *
+ * Fires after an overlay is added in <addCellOverlay>. The <code>cell</code>
+ * property contains the cell and the <code>overlay</code> property contains
+ * the <mxCellOverlay> that was added.
+ *
+ * Event: mxEvent.REMOVE_OVERLAY
+ *
+ * Fires after an overlay is removed in <removeCellOverlay> and
+ * <removeCellOverlays>. The <code>cell</code> property contains the cell and
+ * the <code>overlay</code> property contains the <mxCellOverlay> that was
+ * removed.
+ * 
+ * Constructor: mxGraph
+ * 
+ * Constructs a new mxGraph in the specified container. Model is an optional
+ * mxGraphModel. If no model is provided, a new mxGraphModel instance is 
+ * used as the model. The container must have a valid owner document prior 
+ * to calling this function in Internet Explorer. RenderHint is a string to
+ * affect the display performance and rendering in IE, but not in SVG-based 
+ * browsers. The parameter is mapped to <dialect>, which may 
+ * be one of <mxConstants.DIALECT_SVG> for SVG-based browsers, 
+ * <mxConstants.DIALECT_STRICTHTML> for fastest display mode,
+ * <mxConstants.DIALECT_PREFERHTML> for faster display mode,
+ * <mxConstants.DIALECT_MIXEDHTML> for fast and <mxConstants.DIALECT_VML> 
+ * for exact display mode (slowest). The dialects are defined in mxConstants.
+ * The default values are DIALECT_SVG for SVG-based browsers and
+ * DIALECT_MIXED for IE.
+ *
+ * The possible values for the renderingHint parameter are explained below:
+ * 
+ * fast - The parameter is based on the fact that the display performance is 
+ * highly improved in IE if the VML is not contained within a VML group 
+ * element. The lack of a group element only slightly affects the display while 
+ * panning, but improves the performance by almost a factor of 2, while keeping 
+ * the display sufficiently accurate. This also allows to render certain shapes as HTML 
+ * if the display accuracy is not affected, which is implemented by 
+ * <mxShape.isMixedModeHtml>. This is the default setting and is mapped to
+ * DIALECT_MIXEDHTML.
+ * faster - Same as fast, but more expensive shapes are avoided. This is 
+ * controlled by <mxShape.preferModeHtml>. The default implementation will 
+ * avoid gradients and rounded rectangles, but more significant shapes, such 
+ * as rhombus, ellipse, actor and cylinder will be rendered accurately. This 
+ * setting is mapped to DIALECT_PREFERHTML.
+ * fastest - Almost anything will be rendered in Html. This allows for 
+ * rectangles, labels and images. This setting is mapped to
+ * DIALECT_STRICTHTML.
+ * exact - If accurate panning is required and if the diagram is small (up
+ * to 100 cells), then this value should be used. In this mode, a group is 
+ * created that contains the VML. This allows for accurate panning and is 
+ * mapped to DIALECT_VML.
+ *
+ * Example:
+ * 
+ * To create a graph inside a DOM node with an id of graph:
+ * (code)
+ * var container = document.getElementById('graph');
+ * var graph = new mxGraph(container);
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * container - Optional DOM node that acts as a container for the graph.
+ * If this is null then the container can be initialized later using
+ * <init>.
+ * model - Optional <mxGraphModel> that constitutes the graph data.
+ * renderHint - Optional string that specifies the display accuracy and
+ * performance. Default is mxConstants.DIALECT_MIXEDHTML (for IE).
+ * stylesheet - Optional <mxStylesheet> to be used in the graph.
+ */
+function mxGraph(container, model, renderHint, stylesheet)
+{
+	// Initializes the variable in case the prototype has been
+	// modified to hold some listeners (which is possible because
+	// the createHandlers call is executed regardless of the
+	// arguments passed into the ctor).
+	this.mouseListeners = null;
+	
+	// Converts the renderHint into a dialect
+	this.renderHint = renderHint;
+
+	if (mxClient.IS_SVG)
+	{
+		this.dialect = mxConstants.DIALECT_SVG;
+	}
+	else if (renderHint == mxConstants.RENDERING_HINT_EXACT && mxClient.IS_VML)
+	{
+		this.dialect = mxConstants.DIALECT_VML;
+	}
+	else if (renderHint == mxConstants.RENDERING_HINT_FASTEST)
+	{
+		this.dialect = mxConstants.DIALECT_STRICTHTML;
+	}
+	else if (renderHint == mxConstants.RENDERING_HINT_FASTER)
+	{
+		this.dialect = mxConstants.DIALECT_PREFERHTML;
+	}
+	else // default for VML
+	{
+		this.dialect = mxConstants.DIALECT_MIXEDHTML;
+	}
+	
+	// Initializes the main members that do not require a container
+	this.model = (model != null) ? model : new mxGraphModel();
+	this.multiplicities = [];
+	this.imageBundles = [];
+	this.cellRenderer = this.createCellRenderer();
+	this.setSelectionModel(this.createSelectionModel());
+	this.setStylesheet((stylesheet != null) ? stylesheet : this.createStylesheet());
+	this.view = this.createGraphView();
+	
+	// Adds a graph model listener to update the view
+	this.graphModelChangeListener = mxUtils.bind(this, function(sender, evt)
+	{
+		this.graphModelChanged(evt.getProperty('edit').changes);
+	});
+	
+	this.model.addListener(mxEvent.CHANGE, this.graphModelChangeListener);
+
+	// Installs basic event handlers with disabled default settings.
+	this.createHandlers();
+	
+	// Initializes the display if a container was specified
+	if (container != null)
+	{
+		this.init(container);
+	}
+	
+	this.view.revalidate();
+};
+
+/**
+ * Installs the required language resources at class
+ * loading time.
+ */
+if (mxLoadResources)
+{
+	mxResources.add(mxClient.basePath+'/resources/graph');
+}
+
+/**
+ * Extends mxEventSource.
+ */
+mxGraph.prototype = new mxEventSource();
+mxGraph.prototype.constructor = mxGraph;
+
+/**
+ * Variable: EMPTY_ARRAY
+ *
+ * Immutable empty array instance.
+ */
+mxGraph.prototype.EMPTY_ARRAY = [];
+
+/**
+ * Group: Variables
+ */
+
+/**
+ * Variable: mouseListeners
+ * 
+ * Holds the mouse event listeners. See <fireMouseEvent>.
+ */
+mxGraph.prototype.mouseListeners = null;
+
+/**
+ * Variable: isMouseDown
+ * 
+ * Holds the state of the mouse button.
+ */
+mxGraph.prototype.isMouseDown = false;
+
+/**
+ * Variable: model
+ * 
+ * Holds the <mxGraphModel> that contains the cells to be displayed.
+ */
+mxGraph.prototype.model = null;
+
+/**
+ * Variable: view
+ * 
+ * Holds the <mxGraphView> that caches the <mxCellStates> for the cells.
+ */
+mxGraph.prototype.view = null;
+
+/**
+ * Variable: stylesheet
+ * 
+ * Holds the <mxStylesheet> that defines the appearance of the cells.
+ * 
+ * 
+ * Example:
+ * 
+ * Use the following code to read a stylesheet into an existing graph.
+ * 
+ * (code)
+ * var req = mxUtils.load('stylesheet.xml');
+ * var root = req.getDocumentElement();
+ * var dec = new mxCodec(root.ownerDocument);
+ * dec.decode(root, graph.stylesheet);
+ * (end)
+ */
+mxGraph.prototype.stylesheet = null;
+	
+/**
+ * Variable: selectionModel
+ * 
+ * Holds the <mxGraphSelectionModel> that models the current selection.
+ */
+mxGraph.prototype.selectionModel = null;
+
+/**
+ * Variable: cellEditor
+ * 
+ * Holds the <mxCellEditor> that is used as the in-place editing.
+ */
+mxGraph.prototype.cellEditor = null;
+
+/**
+ * Variable: cellRenderer
+ * 
+ * Holds the <mxCellRenderer> for rendering the cells in the graph.
+ */
+mxGraph.prototype.cellRenderer = null;
+
+/**
+ * Variable: multiplicities
+ * 
+ * An array of <mxMultiplicities> describing the allowed
+ * connections in a graph.
+ */
+mxGraph.prototype.multiplicities = null;
+
+/**
+ * Variable: renderHint
+ * 
+ * RenderHint as it was passed to the constructor.
+ */
+mxGraph.prototype.renderHint = null;
+
+/**
+ * Variable: dialect
+ * 
+ * Dialect to be used for drawing the graph. Possible values are all
+ * constants in <mxConstants> with a DIALECT-prefix.
+ */
+mxGraph.prototype.dialect = null;
+
+/**
+ * Variable: gridSize
+ * 
+ * Specifies the grid size. Default is 10.
+ */
+mxGraph.prototype.gridSize = 10;
+	
+/**
+ * Variable: gridEnabled
+ * 
+ * Specifies if the grid is enabled. This is used in <snap>. Default is
+ * true.
+ */
+mxGraph.prototype.gridEnabled = true;
+
+/**
+ * Variable: portsEnabled
+ * 
+ * Specifies if ports are enabled. This is used in <cellConnected> to update
+ * the respective style. Default is true.
+ */
+mxGraph.prototype.portsEnabled = true;
+
+/**
+ * Variable: nativeDoubleClickEnabled
+ * 
+ * Specifies if native double click events should be detected. Default is true.
+ */
+mxGraph.prototype.nativeDblClickEnabled = true;
+
+/**
+ * Variable: doubleTapEnabled
+ * 
+ * Specifies if double taps on touch-based devices should be handled as a
+ * double click. Default is true.
+ */
+mxGraph.prototype.doubleTapEnabled = true;
+
+/**
+ * Variable: doubleTapTimeout
+ * 
+ * Specifies the timeout for double taps and non-native double clicks. Default
+ * is 500 ms.
+ */
+mxGraph.prototype.doubleTapTimeout = 500;
+
+/**
+ * Variable: doubleTapTolerance
+ * 
+ * Specifies the tolerance for double taps and double clicks in quirks mode.
+ * Default is 25 pixels.
+ */
+mxGraph.prototype.doubleTapTolerance = 25;
+
+/**
+ * Variable: lastTouchX
+ * 
+ * Holds the x-coordinate of the last touch event for double tap detection.
+ */
+mxGraph.prototype.lastTouchY = 0;
+
+/**
+ * Variable: lastTouchX
+ * 
+ * Holds the y-coordinate of the last touch event for double tap detection.
+ */
+mxGraph.prototype.lastTouchY = 0;
+
+/**
+ * Variable: lastTouchTime
+ * 
+ * Holds the time of the last touch event for double click detection.
+ */
+mxGraph.prototype.lastTouchTime = 0;
+
+/**
+ * Variable: tapAndHoldEnabled
+ * 
+ * Specifies if tap and hold should be used for starting connections on touch-based
+ * devices. Default is true.
+ */
+mxGraph.prototype.tapAndHoldEnabled = true;
+
+/**
+ * Variable: tapAndHoldDelay
+ * 
+ * Specifies the time for a tap and hold. Default is 500 ms.
+ */
+mxGraph.prototype.tapAndHoldDelay = 500;
+
+/**
+ * Variable: tapAndHoldInProgress
+ * 
+ * True if the timer for tap and hold events is running.
+ */
+mxGraph.prototype.tapAndHoldInProgress = false;
+
+/**
+ * Variable: tapAndHoldValid
+ * 
+ * True as long as the timer is running and the touch events
+ * stay within the given <tapAndHoldTolerance>.
+ */
+mxGraph.prototype.tapAndHoldValid = false;
+
+/**
+ * Variable: initialTouchX
+ * 
+ * Holds the x-coordinate of the intial touch event for tap and hold.
+ */
+mxGraph.prototype.initialTouchX = 0;
+
+/**
+ * Variable: initialTouchY
+ * 
+ * Holds the y-coordinate of the intial touch event for tap and hold.
+ */
+mxGraph.prototype.initialTouchY = 0;
+
+/**
+ * Variable: tolerance
+ * 
+ * Tolerance for a move to be handled as a single click.
+ * Default is 4 pixels.
+ */
+mxGraph.prototype.tolerance = 4;
+
+/**
+ * Variable: defaultOverlap
+ * 
+ * Value returned by <getOverlap> if <isAllowOverlapParent> returns
+ * true for the given cell. <getOverlap> is used in <constrainChild> if
+ * <isConstrainChild> returns true. The value specifies the
+ * portion of the child which is allowed to overlap the parent.
+ */
+mxGraph.prototype.defaultOverlap = 0.5;
+
+/**
+ * Variable: defaultParent
+ * 
+ * Specifies the default parent to be used to insert new cells.
+ * This is used in <getDefaultParent>. Default is null.
+ */
+mxGraph.prototype.defaultParent = null;
+
+/**
+ * Variable: alternateEdgeStyle
+ * 
+ * Specifies the alternate edge style to be used if the main control point
+ * on an edge is being doubleclicked. Default is null.
+ */
+mxGraph.prototype.alternateEdgeStyle = null;
+
+/**
+ * Variable: backgroundImage
+ *
+ * Specifies the <mxImage> to be returned by <getBackgroundImage>. Default
+ * is null.
+ * 
+ * Example:
+ *
+ * (code)
+ * var img = new mxImage('http://www.example.com/maps/examplemap.jpg', 1024, 768);
+ * graph.setBackgroundImage(img);
+ * graph.view.validate();
+ * (end)
+ */
+mxGraph.prototype.backgroundImage = null;
+
+/**
+ * Variable: pageVisible
+ *
+ * Specifies if the background page should be visible. Default is false.
+ * Not yet implemented.
+ */
+mxGraph.prototype.pageVisible = false;
+
+/**
+ * Variable: pageBreaksVisible
+ * 
+ * Specifies if a dashed line should be drawn between multiple pages. Default
+ * is false. If you change this value while a graph is being displayed then you
+ * should call <sizeDidChange> to force an update of the display.
+ */
+mxGraph.prototype.pageBreaksVisible = false;
+
+/**
+ * Variable: pageBreakColor
+ * 
+ * Specifies the color for page breaks. Default is 'gray'.
+ */
+mxGraph.prototype.pageBreakColor = 'gray';
+
+/**
+ * Variable: pageBreakDashed
+ * 
+ * Specifies the page breaks should be dashed. Default is true.
+ */
+mxGraph.prototype.pageBreakDashed = true;
+
+/**
+ * Variable: minPageBreakDist
+ * 
+ * Specifies the minimum distance for page breaks to be visible. Default is
+ * 20 (in pixels).
+ */
+mxGraph.prototype.minPageBreakDist = 20;
+
+/**
+ * Variable: preferPageSize
+ * 
+ * Specifies if the graph size should be rounded to the next page number in
+ * <sizeDidChange>. This is only used if the graph container has scrollbars.
+ * Default is false.
+ */
+mxGraph.prototype.preferPageSize = false;
+
+/**
+ * Variable: pageFormat
+ *
+ * Specifies the page format for the background page. Default is
+ * <mxConstants.PAGE_FORMAT_A4_PORTRAIT>. This is used as the default in
+ * <mxPrintPreview> and for painting the background page if <pageVisible> is
+ * true and the pagebreaks if <pageBreaksVisible> is true.
+ */
+mxGraph.prototype.pageFormat = mxConstants.PAGE_FORMAT_A4_PORTRAIT;
+
+/**
+ * Variable: pageScale
+ *
+ * Specifies the scale of the background page. Default is 1.5.
+ * Not yet implemented.
+ */
+mxGraph.prototype.pageScale = 1.5;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies the return value for <isEnabled>. Default is true.
+ */
+mxGraph.prototype.enabled = true;
+
+/**
+ * Variable: escapeEnabled
+ * 
+ * Specifies if <mxKeyHandler> should invoke <escape> when the escape key
+ * is pressed. Default is true.
+ */
+mxGraph.prototype.escapeEnabled = true;
+
+/**
+ * Variable: invokesStopCellEditing
+ * 
+ * If true, when editing is to be stopped by way of selection changing,
+ * data in diagram changing or other means stopCellEditing is invoked, and
+ * changes are saved. This is implemented in a focus handler in
+ * <mxCellEditor>. Default is true.
+ */
+mxGraph.prototype.invokesStopCellEditing = true;
+
+/**
+ * Variable: enterStopsCellEditing
+ * 
+ * If true, pressing the enter key without pressing control or shift will stop
+ * editing and accept the new value. This is used in <mxCellEditor> to stop
+ * cell editing. Note: You can always use F2 and escape to stop editing.
+ * Default is false.
+ */
+mxGraph.prototype.enterStopsCellEditing = false;
+
+/**
+ * Variable: useScrollbarsForPanning
+ * 
+ * Specifies if scrollbars should be used for panning in <panGraph> if
+ * any scrollbars are available. If scrollbars are enabled in CSS, but no
+ * scrollbars appear because the graph is smaller than the container size,
+ * then no panning occurs if this is true. Default is true.
+ */
+mxGraph.prototype.useScrollbarsForPanning = true;
+
+/**
+ * Variable: exportEnabled
+ * 
+ * Specifies the return value for <canExportCell>. Default is true.
+ */
+mxGraph.prototype.exportEnabled = true;
+
+/**
+ * Variable: importEnabled
+ * 
+ * Specifies the return value for <canImportCell>. Default is true.
+ */
+mxGraph.prototype.importEnabled = true;
+
+/**
+ * Variable: cellsLocked
+ * 
+ * Specifies the return value for <isCellLocked>. Default is false.
+ */
+mxGraph.prototype.cellsLocked = false;
+
+/**
+ * Variable: cellsCloneable
+ * 
+ * Specifies the return value for <isCellCloneable>. Default is true.
+ */
+mxGraph.prototype.cellsCloneable = true;
+
+/**
+ * Variable: foldingEnabled
+ * 
+ * Specifies if folding (collapse and expand via an image icon in the graph
+ * should be enabled). Default is true.
+ */
+mxGraph.prototype.foldingEnabled = true;
+
+/**
+ * Variable: cellsEditable
+ * 
+ * Specifies the return value for <isCellEditable>. Default is true.
+ */
+mxGraph.prototype.cellsEditable = true;
+		
+/**
+ * Variable: cellsDeletable
+ * 
+ * Specifies the return value for <isCellDeletable>. Default is true.
+ */
+mxGraph.prototype.cellsDeletable = true;
+
+/**
+ * Variable: cellsMovable
+ * 
+ * Specifies the return value for <isCellMovable>. Default is true.
+ */
+mxGraph.prototype.cellsMovable = true;
+	
+/**
+ * Variable: edgeLabelsMovable
+ * 
+ * Specifies the return value for edges in <isLabelMovable>. Default is true.
+ */
+mxGraph.prototype.edgeLabelsMovable = true;
+	
+/**
+ * Variable: vertexLabelsMovable
+ * 
+ * Specifies the return value for vertices in <isLabelMovable>. Default is false.
+ */
+mxGraph.prototype.vertexLabelsMovable = false;
+
+/**
+ * Variable: dropEnabled
+ * 
+ * Specifies the return value for <isDropEnabled>. Default is false.
+ */
+mxGraph.prototype.dropEnabled = false;
+
+/**
+ * Variable: splitEnabled
+ * 
+ * Specifies if dropping onto edges should be enabled. This is ignored if
+ * <dropEnabled> is false. If enabled, it will call <splitEdge> to carry
+ * out the drop operation. Default is true.
+ */
+mxGraph.prototype.splitEnabled = true;
+
+/**
+ * Variable: cellsResizable
+ * 
+ * Specifies the return value for <isCellResizable>. Default is true.
+ */
+mxGraph.prototype.cellsResizable = true;
+
+/**
+ * Variable: cellsBendable
+ * 
+ * Specifies the return value for <isCellsBendable>. Default is true.
+ */
+mxGraph.prototype.cellsBendable = true;
+
+/**
+ * Variable: cellsSelectable
+ * 
+ * Specifies the return value for <isCellSelectable>. Default is true.
+ */
+mxGraph.prototype.cellsSelectable = true;
+
+/**
+ * Variable: cellsDisconnectable
+ * 
+ * Specifies the return value for <isCellDisconntable>. Default is true.
+ */
+mxGraph.prototype.cellsDisconnectable = true;
+
+/**
+ * Variable: autoSizeCells
+ * 
+ * Specifies if the graph should automatically update the cell size after an
+ * edit. This is used in <isAutoSizeCell>. Default is false.
+ */
+mxGraph.prototype.autoSizeCells = false;
+
+/**
+ * Variable: autoSizeCellsOnAdd
+ * 
+ * Specifies if autoSize style should be applied when cells are added. Default is false.
+ */
+mxGraph.prototype.autoSizeCellsOnAdd = false;
+
+/**
+ * Variable: autoScroll
+ * 
+ * Specifies if the graph should automatically scroll if the mouse goes near
+ * the container edge while dragging. This is only taken into account if the
+ * container has scrollbars. Default is true.
+ * 
+ * If you need this to work without scrollbars then set <ignoreScrollbars> to
+ * true. Please consult the <ignoreScrollbars> for details. In general, with
+ * no scrollbars, the use of <allowAutoPanning> is recommended.
+ */
+mxGraph.prototype.autoScroll = true;
+
+/**
+ * Variable: ignoreScrollbars
+ * 
+ * Specifies if the graph should automatically scroll regardless of the
+ * scrollbars. This will scroll the container using positive values for
+ * scroll positions (ie usually only rightwards and downwards). To avoid
+ * possible conflicts with panning, set <translateToScrollPosition> to true.
+ */
+mxGraph.prototype.ignoreScrollbars = false;
+
+/**
+ * Variable: translateToScrollPosition
+ * 
+ * Specifies if the graph should automatically convert the current scroll
+ * position to a translate in the graph view when a mouseUp event is received.
+ * This can be used to avoid conflicts when using <autoScroll> and
+ * <ignoreScrollbars> with no scrollbars in the container.
+ */
+mxGraph.prototype.translateToScrollPosition = false;
+
+/**
+ * Variable: timerAutoScroll
+ * 
+ * Specifies if autoscrolling should be carried out via mxPanningManager even
+ * if the container has scrollbars. This disables <scrollPointToVisible> and
+ * uses <mxPanningManager> instead. If this is true then <autoExtend> is
+ * disabled. It should only be used with a scroll buffer or when scollbars
+ * are visible and scrollable in all directions. Default is false.
+ */
+mxGraph.prototype.timerAutoScroll = false;
+
+/**
+ * Variable: allowAutoPanning
+ * 
+ * Specifies if panning via <panGraph> should be allowed to implement autoscroll
+ * if no scrollbars are available in <scrollPointToVisible>. To enable panning
+ * inside the container, near the edge, set <mxPanningManager.border> to a
+ * positive value. Default is false.
+ */
+mxGraph.prototype.allowAutoPanning = false;
+
+/**
+ * Variable: autoExtend
+ * 
+ * Specifies if the size of the graph should be automatically extended if the
+ * mouse goes near the container edge while dragging. This is only taken into
+ * account if the container has scrollbars. Default is true. See <autoScroll>.
+ */
+mxGraph.prototype.autoExtend = true;
+
+/**
+ * Variable: maximumGraphBounds
+ * 
+ * <mxRectangle> that specifies the area in which all cells in the diagram
+ * should be placed. Uses in <getMaximumGraphBounds>. Use a width or height of
+ * 0 if you only want to give a upper, left corner.
+ */
+mxGraph.prototype.maximumGraphBounds = null;
+
+/**
+ * Variable: minimumGraphSize
+ * 
+ * <mxRectangle> that specifies the minimum size of the graph. This is ignored
+ * if the graph container has no scrollbars. Default is null.
+ */
+mxGraph.prototype.minimumGraphSize = null;
+
+/**
+ * Variable: minimumContainerSize
+ * 
+ * <mxRectangle> that specifies the minimum size of the <container> if
+ * <resizeContainer> is true.
+ */
+mxGraph.prototype.minimumContainerSize = null;
+		
+/**
+ * Variable: maximumContainerSize
+ * 
+ * <mxRectangle> that specifies the maximum size of the container if
+ * <resizeContainer> is true.
+ */
+mxGraph.prototype.maximumContainerSize = null;
+
+/**
+ * Variable: resizeContainer
+ * 
+ * Specifies if the container should be resized to the graph size when
+ * the graph size has changed. Default is false.
+ */
+mxGraph.prototype.resizeContainer = false;
+
+/**
+ * Variable: border
+ * 
+ * Border to be added to the bottom and right side when the container is
+ * being resized after the graph has been changed. Default is 0.
+ */
+mxGraph.prototype.border = 0;
+		
+/**
+ * Variable: keepEdgesInForeground
+ * 
+ * Specifies if edges should appear in the foreground regardless of their order
+ * in the model. If <keepEdgesInForeground> and <keepEdgesInBackground> are
+ * both true then the normal order is applied. Default is false.
+ */
+mxGraph.prototype.keepEdgesInForeground = false;
+
+/**
+ * Variable: keepEdgesInBackground
+ * 
+ * Specifies if edges should appear in the background regardless of their order
+ * in the model. If <keepEdgesInForeground> and <keepEdgesInBackground> are
+ * both true then the normal order is applied. Default is false.
+ */
+mxGraph.prototype.keepEdgesInBackground = false;
+
+/**
+ * Variable: allowNegativeCoordinates
+ * 
+ * Specifies if negative coordinates for vertices are allowed. Default is true.
+ */
+mxGraph.prototype.allowNegativeCoordinates = true;
+
+/**
+ * Variable: constrainChildren
+ * 
+ * Specifies if a child should be constrained inside the parent bounds after a
+ * move or resize of the child. Default is true.
+ */
+mxGraph.prototype.constrainChildren = true;
+
+/**
+ * Variable: constrainRelativeChildren
+ * 
+ * Specifies if child cells with relative geometries should be constrained
+ * inside the parent bounds, if <constrainChildren> is true, and/or the
+ * <maximumGraphBounds>. Default is false.
+ */
+mxGraph.prototype.constrainRelativeChildren = false;
+
+/**
+ * Variable: extendParents
+ * 
+ * Specifies if a parent should contain the child bounds after a resize of
+ * the child. Default is true. This has precedence over <constrainChildren>.
+ */
+mxGraph.prototype.extendParents = true;
+
+/**
+ * Variable: extendParentsOnAdd
+ * 
+ * Specifies if parents should be extended according to the <extendParents>
+ * switch if cells are added. Default is true.
+ */
+mxGraph.prototype.extendParentsOnAdd = true;
+
+/**
+ * Variable: extendParentsOnAdd
+ * 
+ * Specifies if parents should be extended according to the <extendParents>
+ * switch if cells are added. Default is false for backwards compatiblity.
+ */
+mxGraph.prototype.extendParentsOnMove = false;
+
+/**
+ * Variable: recursiveResize
+ * 
+ * Specifies the return value for <isRecursiveResize>. Default is
+ * false for backwards compatiblity.
+ */
+mxGraph.prototype.recursiveResize = false;
+
+/**
+ * Variable: collapseToPreferredSize
+ * 
+ * Specifies if the cell size should be changed to the preferred size when
+ * a cell is first collapsed. Default is true.
+ */
+mxGraph.prototype.collapseToPreferredSize = true;
+
+/**
+ * Variable: zoomFactor
+ * 
+ * Specifies the factor used for <zoomIn> and <zoomOut>. Default is 1.2
+ * (120%).
+ */
+mxGraph.prototype.zoomFactor = 1.2;
+
+/**
+ * Variable: keepSelectionVisibleOnZoom
+ * 
+ * Specifies if the viewport should automatically contain the selection cells
+ * after a zoom operation. Default is false.
+ */
+mxGraph.prototype.keepSelectionVisibleOnZoom = false;
+
+/**
+ * Variable: centerZoom
+ * 
+ * Specifies if the zoom operations should go into the center of the actual
+ * diagram rather than going from top, left. Default is true.
+ */
+mxGraph.prototype.centerZoom = true;
+
+/**
+ * Variable: resetViewOnRootChange
+ * 
+ * Specifies if the scale and translate should be reset if the root changes in
+ * the model. Default is true.
+ */
+mxGraph.prototype.resetViewOnRootChange = true;
+
+/**
+ * Variable: resetEdgesOnResize
+ * 
+ * Specifies if edge control points should be reset after the resize of a
+ * connected cell. Default is false.
+ */
+mxGraph.prototype.resetEdgesOnResize = false;
+
+/**
+ * Variable: resetEdgesOnMove
+ * 
+ * Specifies if edge control points should be reset after the move of a
+ * connected cell. Default is false.
+ */
+mxGraph.prototype.resetEdgesOnMove = false;
+
+/**
+ * Variable: resetEdgesOnConnect
+ * 
+ * Specifies if edge control points should be reset after the the edge has been
+ * reconnected. Default is true.
+ */
+mxGraph.prototype.resetEdgesOnConnect = true;
+
+/**
+ * Variable: allowLoops
+ * 
+ * Specifies if loops (aka self-references) are allowed. Default is false.
+ */
+mxGraph.prototype.allowLoops = false;
+	
+/**
+ * Variable: defaultLoopStyle
+ * 
+ * <mxEdgeStyle> to be used for loops. This is a fallback for loops if the
+ * <mxConstants.STYLE_LOOP> is undefined. Default is <mxEdgeStyle.Loop>.
+ */
+mxGraph.prototype.defaultLoopStyle = mxEdgeStyle.Loop;
+
+/**
+ * Variable: multigraph
+ * 
+ * Specifies if multiple edges in the same direction between the same pair of
+ * vertices are allowed. Default is true.
+ */
+mxGraph.prototype.multigraph = true;
+
+/**
+ * Variable: connectableEdges
+ * 
+ * Specifies if edges are connectable. Default is false. This overrides the
+ * connectable field in edges.
+ */
+mxGraph.prototype.connectableEdges = false;
+
+/**
+ * Variable: allowDanglingEdges
+ * 
+ * Specifies if edges with disconnected terminals are allowed in the graph.
+ * Default is true.
+ */
+mxGraph.prototype.allowDanglingEdges = true;
+
+/**
+ * Variable: cloneInvalidEdges
+ * 
+ * Specifies if edges that are cloned should be validated and only inserted
+ * if they are valid. Default is true.
+ */
+mxGraph.prototype.cloneInvalidEdges = false;
+
+/**
+ * Variable: disconnectOnMove
+ * 
+ * Specifies if edges should be disconnected from their terminals when they
+ * are moved. Default is true.
+ */
+mxGraph.prototype.disconnectOnMove = true;
+
+/**
+ * Variable: labelsVisible
+ * 
+ * Specifies if labels should be visible. This is used in <getLabel>. Default
+ * is true.
+ */
+mxGraph.prototype.labelsVisible = true;
+	
+/**
+ * Variable: htmlLabels
+ * 
+ * Specifies the return value for <isHtmlLabel>. Default is false.
+ */
+mxGraph.prototype.htmlLabels = false;
+
+/**
+ * Variable: swimlaneSelectionEnabled
+ * 
+ * Specifies if swimlanes should be selectable via the content if the
+ * mouse is released. Default is true.
+ */
+mxGraph.prototype.swimlaneSelectionEnabled = true;
+
+/**
+ * Variable: swimlaneNesting
+ * 
+ * Specifies if nesting of swimlanes is allowed. Default is true.
+ */
+mxGraph.prototype.swimlaneNesting = true;
+	
+/**
+ * Variable: swimlaneIndicatorColorAttribute
+ * 
+ * The attribute used to find the color for the indicator if the indicator
+ * color is set to 'swimlane'. Default is <mxConstants.STYLE_FILLCOLOR>.
+ */
+mxGraph.prototype.swimlaneIndicatorColorAttribute = mxConstants.STYLE_FILLCOLOR;
+
+/**
+ * Variable: imageBundles
+ * 
+ * Holds the list of image bundles.
+ */
+mxGraph.prototype.imageBundles = null;
+
+/**
+ * Variable: minFitScale
+ * 
+ * Specifies the minimum scale to be applied in <fit>. Default is 0.1. Set this
+ * to null to allow any value.
+ */
+mxGraph.prototype.minFitScale = 0.1;
+
+/**
+ * Variable: maxFitScale
+ * 
+ * Specifies the maximum scale to be applied in <fit>. Default is 8. Set this
+ * to null to allow any value.
+ */
+mxGraph.prototype.maxFitScale = 8;
+
+/**
+ * Variable: panDx
+ * 
+ * Current horizontal panning value. Default is 0.
+ */
+mxGraph.prototype.panDx = 0;
+
+/**
+ * Variable: panDy
+ * 
+ * Current vertical panning value. Default is 0.
+ */
+mxGraph.prototype.panDy = 0;
+
+/**
+ * Variable: collapsedImage
+ * 
+ * Specifies the <mxImage> to indicate a collapsed state.
+ * Default value is mxClient.imageBasePath + '/collapsed.gif'
+ */
+mxGraph.prototype.collapsedImage = new mxImage(mxClient.imageBasePath + '/collapsed.gif', 9, 9);
+
+/**
+ * Variable: expandedImage
+ * 
+ * Specifies the <mxImage> to indicate a expanded state.
+ * Default value is mxClient.imageBasePath + '/expanded.gif'
+ */
+mxGraph.prototype.expandedImage = new mxImage(mxClient.imageBasePath + '/expanded.gif', 9, 9);
+
+/**
+ * Variable: warningImage
+ * 
+ * Specifies the <mxImage> for the image to be used to display a warning
+ * overlay. See <setCellWarning>. Default value is mxClient.imageBasePath +
+ * '/warning'.  The extension for the image depends on the platform. It is
+ * '.png' on the Mac and '.gif' on all other platforms.
+ */
+mxGraph.prototype.warningImage = new mxImage(mxClient.imageBasePath + '/warning'+
+	((mxClient.IS_MAC) ? '.png' : '.gif'), 16, 16);
+
+/**
+ * Variable: alreadyConnectedResource
+ * 
+ * Specifies the resource key for the error message to be displayed in
+ * non-multigraphs when two vertices are already connected. If the resource
+ * for this key does not exist then the value is used as the error message.
+ * Default is 'alreadyConnected'.
+ */
+mxGraph.prototype.alreadyConnectedResource = (mxClient.language != 'none') ? 'alreadyConnected' : '';
+
+/**
+ * Variable: containsValidationErrorsResource
+ * 
+ * Specifies the resource key for the warning message to be displayed when
+ * a collapsed cell contains validation errors. If the resource for this
+ * key does not exist then the value is used as the warning message.
+ * Default is 'containsValidationErrors'.
+ */
+mxGraph.prototype.containsValidationErrorsResource = (mxClient.language != 'none') ? 'containsValidationErrors' : '';
+
+/**
+ * Variable: collapseExpandResource
+ * 
+ * Specifies the resource key for the tooltip on the collapse/expand icon.
+ * If the resource for this key does not exist then the value is used as
+ * the tooltip. Default is 'collapse-expand'.
+ */
+mxGraph.prototype.collapseExpandResource = (mxClient.language != 'none') ? 'collapse-expand' : '';
+
+/**
+ * Function: init
+ * 
+ * Initializes the <container> and creates the respective datastructures.
+ * 
+ * Parameters:
+ * 
+ * container - DOM node that will contain the graph display.
+ */
+mxGraph.prototype.init = function(container)
+{
+	this.container = container;
+	
+	// Initializes the in-place editor
+	this.cellEditor = this.createCellEditor();	
+
+	// Initializes the container using the view
+	this.view.init();
+	
+	// Updates the size of the container for the current graph
+	this.sizeDidChange();
+	
+	// Hides tooltips and resets tooltip timer if mouse leaves container
+	mxEvent.addListener(container, 'mouseleave', mxUtils.bind(this, function()
+	{
+		if (this.tooltipHandler != null)
+		{
+			this.tooltipHandler.hide();
+		}
+	}));
+
+	// Automatic deallocation of memory
+	if (mxClient.IS_IE)
+	{
+		mxEvent.addListener(window, 'unload', mxUtils.bind(this, function()
+		{
+			this.destroy();
+		}));
+		
+		// Disable shift-click for text
+		mxEvent.addListener(container, 'selectstart',
+			mxUtils.bind(this, function(evt)
+			{
+				return this.isEditing() || (!this.isMouseDown && !mxEvent.isShiftDown(evt));
+			})
+		);
+	}
+	
+	// Workaround for missing last shape and connect preview in IE8 standards
+	// mode if no initial graph displayed or no label for shape defined
+	if (document.documentMode == 8)
+	{
+		container.insertAdjacentHTML('beforeend', '<' + mxClient.VML_PREFIX + ':group' +
+			' style="DISPLAY: none;"></' + mxClient.VML_PREFIX + ':group>');
+	}
+};
+
+/**
+ * Function: createHandlers
+ * 
+ * Creates the tooltip-, panning-, connection- and graph-handler (in this
+ * order). This is called in the constructor before <init> is called.
+ */
+mxGraph.prototype.createHandlers = function()
+{
+	this.tooltipHandler = this.createTooltipHandler();
+	this.tooltipHandler.setEnabled(false);
+	this.selectionCellsHandler = this.createSelectionCellsHandler();
+	this.connectionHandler = this.createConnectionHandler();
+	this.connectionHandler.setEnabled(false);
+	this.graphHandler = this.createGraphHandler();
+	this.panningHandler = this.createPanningHandler();
+	this.panningHandler.panningEnabled = false;
+	this.popupMenuHandler = this.createPopupMenuHandler();
+};
+
+/**
+ * Function: createTooltipHandler
+ * 
+ * Creates and returns a new <mxTooltipHandler> to be used in this graph.
+ */
+mxGraph.prototype.createTooltipHandler = function()
+{
+	return new mxTooltipHandler(this);
+};
+
+/**
+ * Function: createSelectionCellsHandler
+ * 
+ * Creates and returns a new <mxTooltipHandler> to be used in this graph.
+ */
+mxGraph.prototype.createSelectionCellsHandler = function()
+{
+	return new mxSelectionCellsHandler(this);
+};
+
+/**
+ * Function: createConnectionHandler
+ * 
+ * Creates and returns a new <mxConnectionHandler> to be used in this graph.
+ */
+mxGraph.prototype.createConnectionHandler = function()
+{
+	return new mxConnectionHandler(this);
+};
+
+/**
+ * Function: createGraphHandler
+ * 
+ * Creates and returns a new <mxGraphHandler> to be used in this graph.
+ */
+mxGraph.prototype.createGraphHandler = function()
+{
+	return new mxGraphHandler(this);
+};
+
+/**
+ * Function: createPanningHandler
+ * 
+ * Creates and returns a new <mxPanningHandler> to be used in this graph.
+ */
+mxGraph.prototype.createPanningHandler = function()
+{
+	return new mxPanningHandler(this);
+};
+
+/**
+ * Function: createPopupMenuHandler
+ * 
+ * Creates and returns a new <mxPopupMenuHandler> to be used in this graph.
+ */
+mxGraph.prototype.createPopupMenuHandler = function()
+{
+	return new mxPopupMenuHandler(this);
+};
+
+/**
+ * Function: createSelectionModel
+ * 
+ * Creates a new <mxGraphSelectionModel> to be used in this graph.
+ */
+mxGraph.prototype.createSelectionModel = function()
+{
+	return new mxGraphSelectionModel(this);
+};
+
+/**
+ * Function: createStylesheet
+ * 
+ * Creates a new <mxGraphSelectionModel> to be used in this graph.
+ */
+mxGraph.prototype.createStylesheet = function()
+{
+	return new mxStylesheet();
+};
+
+/**
+ * Function: createGraphView
+ * 
+ * Creates a new <mxGraphView> to be used in this graph.
+ */
+mxGraph.prototype.createGraphView = function()
+{
+	return new mxGraphView(this);
+};
+ 
+/**
+ * Function: createCellRenderer
+ * 
+ * Creates a new <mxCellRenderer> to be used in this graph.
+ */
+mxGraph.prototype.createCellRenderer = function()
+{
+	return new mxCellRenderer();
+};
+
+/**
+ * Function: createCellEditor
+ * 
+ * Creates a new <mxCellEditor> to be used in this graph.
+ */
+mxGraph.prototype.createCellEditor = function()
+{
+	return new mxCellEditor(this);
+};
+
+/**
+ * Function: getModel
+ * 
+ * Returns the <mxGraphModel> that contains the cells.
+ */
+mxGraph.prototype.getModel = function()
+{
+	return this.model;
+};
+
+/**
+ * Function: getView
+ * 
+ * Returns the <mxGraphView> that contains the <mxCellStates>.
+ */
+mxGraph.prototype.getView = function()
+{
+	return this.view;
+};
+
+/**
+ * Function: getStylesheet
+ * 
+ * Returns the <mxStylesheet> that defines the style.
+ */
+mxGraph.prototype.getStylesheet = function()
+{
+	return this.stylesheet;
+};
+
+/**
+ * Function: setStylesheet
+ * 
+ * Sets the <mxStylesheet> that defines the style.
+ */
+mxGraph.prototype.setStylesheet = function(stylesheet)
+{
+	this.stylesheet = stylesheet;
+};
+
+/**
+ * Function: getSelectionModel
+ * 
+ * Returns the <mxGraphSelectionModel> that contains the selection.
+ */
+mxGraph.prototype.getSelectionModel = function()
+{
+	return this.selectionModel;
+};
+
+/**
+ * Function: setSelectionModel
+ * 
+ * Sets the <mxSelectionModel> that contains the selection.
+ */
+mxGraph.prototype.setSelectionModel = function(selectionModel)
+{
+	this.selectionModel = selectionModel;
+};
+
+/**
+ * Function: getSelectionCellsForChanges
+ * 
+ * Returns the cells to be selected for the given array of changes.
+ */
+mxGraph.prototype.getSelectionCellsForChanges = function(changes)
+{
+	var cells = [];
+	
+	for (var i = 0; i < changes.length; i++)
+	{
+		var change = changes[i];
+		
+		if (change.constructor != mxRootChange)
+		{
+			var cell = null;
+
+			if (change instanceof mxChildChange && change.previous == null)
+			{
+				cell = change.child;
+			}
+			else if (change.cell != null && change.cell instanceof mxCell)
+			{
+				cell = change.cell;
+			}
+			
+			if (cell != null && mxUtils.indexOf(cells, cell) < 0)
+			{
+				cells.push(cell);
+			}
+		}
+	}
+	
+	return this.getModel().getTopmostCells(cells);
+};
+
+/**
+ * Function: graphModelChanged
+ * 
+ * Called when the graph model changes. Invokes <processChange> on each
+ * item of the given array to update the view accordingly.
+ * 
+ * Parameters:
+ * 
+ * changes - Array that contains the individual changes.
+ */
+mxGraph.prototype.graphModelChanged = function(changes)
+{
+	for (var i = 0; i < changes.length; i++)
+	{
+		this.processChange(changes[i]);
+	}
+	
+	this.removeSelectionCells(this.getRemovedCellsForChanges(changes));
+	
+	this.view.validate();
+	this.sizeDidChange();
+};
+
+/**
+ * Function: getRemovedCellsForChanges
+ * 
+ * Returns the cells that have been removed from the model.
+ */
+mxGraph.prototype.getRemovedCellsForChanges = function(changes)
+{
+	var result = [];
+	
+	for (var i = 0; i < changes.length; i++)
+	{
+		var change = changes[i];
+		
+		// Resets the view settings, removes all cells and clears
+		// the selection if the root changes.
+		if (change instanceof mxRootChange)
+		{
+			break;
+		}
+		else if (change instanceof mxChildChange)
+		{
+			if (change.previous != null && change.parent == null)
+			{
+				result = result.concat(this.model.getDescendants(change.child));
+			}
+		}
+		else if (change instanceof mxVisibleChange)
+		{
+			result = result.concat(this.model.getDescendants(change.cell));
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: processChange
+ * 
+ * Processes the given change and invalidates the respective cached data
+ * in <view>. This fires a <root> event if the root has changed in the
+ * model.
+ * 
+ * Parameters:
+ * 
+ * change - Object that represents the change on the model.
+ */
+mxGraph.prototype.processChange = function(change)
+{
+	// Resets the view settings, removes all cells and clears
+	// the selection if the root changes.
+	if (change instanceof mxRootChange)
+	{
+		this.clearSelection();
+		this.setDefaultParent(null);
+		this.removeStateForCell(change.previous);
+		
+		if (this.resetViewOnRootChange)
+		{
+			this.view.scale = 1;
+			this.view.translate.x = 0;
+			this.view.translate.y = 0;
+		}
+
+		this.fireEvent(new mxEventObject(mxEvent.ROOT));
+	}
+	
+	// Adds or removes a child to the view by online invaliding
+	// the minimal required portions of the cache, namely, the
+	// old and new parent and the child.
+	else if (change instanceof mxChildChange)
+	{
+		var newParent = this.model.getParent(change.child);
+		this.view.invalidate(change.child, true, true);
+
+		if (newParent == null || this.isCellCollapsed(newParent))
+		{
+			this.view.invalidate(change.child, true, true);
+			this.removeStateForCell(change.child);
+			
+			// Handles special case of current root of view being removed
+			if (this.view.currentRoot == change.child)
+			{
+				this.home();
+			}
+		}
+ 
+		if (newParent != change.previous)
+		{
+			// Refreshes the collapse/expand icons on the parents
+			if (newParent != null)
+			{
+				this.view.invalidate(newParent, false, false);
+			}
+			
+			if (change.previous != null)
+			{
+				this.view.invalidate(change.previous, false, false);
+			}
+		}
+	}
+
+	// Handles two special cases where the shape does not need to be
+	// recreated from scratch, it only needs to be invalidated.
+	else if (change instanceof mxTerminalChange || change instanceof mxGeometryChange)
+	{
+		// Checks if the geometry has changed to avoid unnessecary revalidation
+		if (change instanceof mxTerminalChange || ((change.previous == null && change.geometry != null) ||
+			(change.previous != null && !change.previous.equals(change.geometry))))
+		{
+			this.view.invalidate(change.cell);
+		}
+	}
+
+	// Handles two special cases where only the shape, but no
+	// descendants need to be recreated
+	else if (change instanceof mxValueChange)
+	{
+		this.view.invalidate(change.cell, false, false);
+	}
+	
+	// Requires a new mxShape in JavaScript
+	else if (change instanceof mxStyleChange)
+	{
+		this.view.invalidate(change.cell, true, true);
+		var state = this.view.getState(change.cell);
+		
+		if (state != null)
+		{
+			state.style = null;
+		}
+	}
+	
+	// Removes the state from the cache by default
+	else if (change.cell != null && change.cell instanceof mxCell)
+	{
+		this.removeStateForCell(change.cell);
+	}
+};
+
+/**
+ * Function: removeStateForCell
+ * 
+ * Removes all cached information for the given cell and its descendants.
+ * This is called when a cell was removed from the model.
+ * 
+ * Paramters:
+ * 
+ * cell - <mxCell> that was removed from the model.
+ */
+mxGraph.prototype.removeStateForCell = function(cell)
+{
+	var childCount = this.model.getChildCount(cell);
+	
+	for (var i = 0; i < childCount; i++)
+	{
+		this.removeStateForCell(this.model.getChildAt(cell, i));
+	}
+
+	this.view.invalidate(cell, false, true);
+	this.view.removeState(cell);
+};
+
+/**
+ * Group: Overlays
+ */
+
+/**
+ * Function: addCellOverlay
+ * 
+ * Adds an <mxCellOverlay> for the specified cell. This method fires an
+ * <addoverlay> event and returns the new <mxCellOverlay>.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to add the overlay for.
+ * overlay - <mxCellOverlay> to be added for the cell.
+ */
+mxGraph.prototype.addCellOverlay = function(cell, overlay)
+{
+	if (cell.overlays == null)
+	{
+		cell.overlays = [];
+	}
+	
+	cell.overlays.push(overlay);
+
+	var state = this.view.getState(cell);
+
+	// Immediately updates the cell display if the state exists
+	if (state != null)
+	{
+		this.cellRenderer.redraw(state);
+	}
+	
+	this.fireEvent(new mxEventObject(mxEvent.ADD_OVERLAY,
+			'cell', cell, 'overlay', overlay));
+	
+	return overlay;
+};
+
+/**
+ * Function: getCellOverlays
+ * 
+ * Returns the array of <mxCellOverlays> for the given cell or null, if
+ * no overlays are defined.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose overlays should be returned.
+ */
+mxGraph.prototype.getCellOverlays = function(cell)
+{
+	return cell.overlays;
+};
+
+/**
+ * Function: removeCellOverlay
+ * 
+ * Removes and returns the given <mxCellOverlay> from the given cell. This
+ * method fires a <removeoverlay> event. If no overlay is given, then all
+ * overlays are removed using <removeOverlays>.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose overlay should be removed.
+ * overlay - Optional <mxCellOverlay> to be removed.
+ */
+mxGraph.prototype.removeCellOverlay = function(cell, overlay)
+{
+	if (overlay == null)
+	{
+		this.removeCellOverlays(cell);
+	}
+	else
+	{
+		var index = mxUtils.indexOf(cell.overlays, overlay);
+		
+		if (index >= 0)
+		{
+			cell.overlays.splice(index, 1);
+			
+			if (cell.overlays.length == 0)
+			{
+				cell.overlays = null;
+			}
+			
+			// Immediately updates the cell display if the state exists
+			var state = this.view.getState(cell);
+			
+			if (state != null)
+			{
+				this.cellRenderer.redraw(state);
+			}
+			
+			this.fireEvent(new mxEventObject(mxEvent.REMOVE_OVERLAY,
+					'cell', cell, 'overlay', overlay));	
+		}
+		else
+		{
+			overlay = null;
+		}
+	}
+	
+	return overlay;
+};
+
+/**
+ * Function: removeCellOverlays
+ * 
+ * Removes all <mxCellOverlays> from the given cell. This method
+ * fires a <removeoverlay> event for each <mxCellOverlay> and returns
+ * the array of <mxCellOverlays> that was removed from the cell.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose overlays should be removed
+ */
+mxGraph.prototype.removeCellOverlays = function(cell)
+{
+	var overlays = cell.overlays;
+	
+	if (overlays != null)
+	{
+		cell.overlays = null;
+		
+		// Immediately updates the cell display if the state exists
+		var state = this.view.getState(cell);
+		
+		if (state != null)
+		{
+			this.cellRenderer.redraw(state);
+		}
+		
+		for (var i = 0; i < overlays.length; i++)
+		{
+			this.fireEvent(new mxEventObject(mxEvent.REMOVE_OVERLAY,
+					'cell', cell, 'overlay', overlays[i]));
+		}
+	}
+	
+	return overlays;
+};
+
+/**
+ * Function: clearCellOverlays
+ * 
+ * Removes all <mxCellOverlays> in the graph for the given cell and all its
+ * descendants. If no cell is specified then all overlays are removed from
+ * the graph. This implementation uses <removeCellOverlays> to remove the
+ * overlays from the individual cells.
+ * 
+ * Parameters:
+ * 
+ * cell - Optional <mxCell> that represents the root of the subtree to
+ * remove the overlays from. Default is the root in the model.
+ */
+mxGraph.prototype.clearCellOverlays = function(cell)
+{
+	cell = (cell != null) ? cell : this.model.getRoot();
+	this.removeCellOverlays(cell);
+	
+	// Recursively removes all overlays from the children
+	var childCount = this.model.getChildCount(cell);
+	
+	for (var i = 0; i < childCount; i++)
+	{
+		var child = this.model.getChildAt(cell, i);
+		this.clearCellOverlays(child); // recurse
+	}
+};
+
+/**
+ * Function: setCellWarning
+ * 
+ * Creates an overlay for the given cell using the warning and image or
+ * <warningImage> and returns the new <mxCellOverlay>. The warning is
+ * displayed as a tooltip in a red font and may contain HTML markup. If
+ * the warning is null or a zero length string, then all overlays are
+ * removed from the cell.
+ * 
+ * Example:
+ * 
+ * (code)
+ * graph.setCellWarning(cell, '<b>Warning:</b>: Hello, World!');
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose warning should be set.
+ * warning - String that represents the warning to be displayed.
+ * img - Optional <mxImage> to be used for the overlay. Default is
+ * <warningImage>.
+ * isSelect - Optional boolean indicating if a click on the overlay
+ * should select the corresponding cell. Default is false.
+ */
+mxGraph.prototype.setCellWarning = function(cell, warning, img, isSelect)
+{
+	if (warning != null && warning.length > 0)
+	{
+		img = (img != null) ? img : this.warningImage;
+		
+		// Creates the overlay with the image and warning
+		var overlay = new mxCellOverlay(img,
+			'<font color=red>'+warning+'</font>');
+		
+		// Adds a handler for single mouseclicks to select the cell
+		if (isSelect)
+		{
+			overlay.addListener(mxEvent.CLICK,
+				mxUtils.bind(this, function(sender, evt)
+				{
+					if (this.isEnabled())
+					{
+						this.setSelectionCell(cell);
+					}
+				})
+			);
+		}
+		
+		// Sets and returns the overlay in the graph
+		return this.addCellOverlay(cell, overlay);
+	}
+	else
+	{
+		this.removeCellOverlays(cell);
+	}
+	
+	return null;
+};
+
+/**
+ * Group: In-place editing
+ */
+
+/**
+ * Function: startEditing
+ * 
+ * Calls <startEditingAtCell> using the given cell or the first selection
+ * cell.
+ * 
+ * Parameters:
+ * 
+ * evt - Optional mouse event that triggered the editing.
+ */
+mxGraph.prototype.startEditing = function(evt)
+{
+	this.startEditingAtCell(null, evt);
+};
+
+/**
+ * Function: startEditingAtCell
+ * 
+ * Fires a <startEditing> event and invokes <mxCellEditor.startEditing>
+ * on <editor>. After editing was started, a <editingStarted> event is
+ * fired.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to start the in-place editor for.
+ * evt - Optional mouse event that triggered the editing.
+ */
+mxGraph.prototype.startEditingAtCell = function(cell, evt)
+{
+	if (evt == null || !mxEvent.isMultiTouchEvent(evt))
+	{
+		if (cell == null)
+		{
+			cell = this.getSelectionCell();
+			
+			if (cell != null && !this.isCellEditable(cell))
+			{
+				cell = null;
+			}
+		}
+	
+		if (cell != null)
+		{
+			this.fireEvent(new mxEventObject(mxEvent.START_EDITING,
+					'cell', cell, 'event', evt));
+			this.cellEditor.startEditing(cell, evt);
+			this.fireEvent(new mxEventObject(mxEvent.EDITING_STARTED,
+					'cell', cell, 'event', evt));
+		}
+	}
+};
+
+/**
+ * Function: getEditingValue
+ * 
+ * Returns the initial value for in-place editing. This implementation
+ * returns <convertValueToString> for the given cell. If this function is
+ * overridden, then <mxGraphModel.valueForCellChanged> should take care
+ * of correctly storing the actual new value inside the user object.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> for which the initial editing value should be returned.
+ * evt - Optional mouse event that triggered the editor.
+ */
+mxGraph.prototype.getEditingValue = function(cell, evt)
+{
+	return this.convertValueToString(cell);
+};
+
+/**
+ * Function: stopEditing
+ * 
+ * Stops the current editing  and fires a <editingStopped> event.
+ * 
+ * Parameters:
+ * 
+ * cancel - Boolean that specifies if the current editing value
+ * should be stored.
+ */
+mxGraph.prototype.stopEditing = function(cancel)
+{
+	this.cellEditor.stopEditing(cancel);
+	this.fireEvent(new mxEventObject(mxEvent.EDITING_STOPPED, 'cancel', cancel));
+};
+
+/**
+ * Function: labelChanged
+ * 
+ * Sets the label of the specified cell to the given value using
+ * <cellLabelChanged> and fires <mxEvent.LABEL_CHANGED> while the
+ * transaction is in progress. Returns the cell whose label was changed.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose label should be changed.
+ * value - New label to be assigned.
+ * evt - Optional event that triggered the change.
+ */
+mxGraph.prototype.labelChanged = function(cell, value, evt)
+{
+	this.model.beginUpdate();
+	try
+	{
+		var old = cell.value;
+		this.cellLabelChanged(cell, value, this.isAutoSizeCell(cell));
+		this.fireEvent(new mxEventObject(mxEvent.LABEL_CHANGED,
+			'cell', cell, 'value', value, 'old', old, 'event', evt));
+	}
+	finally
+	{
+		this.model.endUpdate();
+	}
+	
+	return cell;
+};
+
+/**
+ * Function: cellLabelChanged
+ * 
+ * Sets the new label for a cell. If autoSize is true then
+ * <cellSizeUpdated> will be called.
+ * 
+ * In the following example, the function is extended to map changes to
+ * attributes in an XML node, as shown in <convertValueToString>.
+ * Alternatively, the handling of this can be implemented as shown in
+ * <mxGraphModel.valueForCellChanged> without the need to clone the
+ * user object.
+ * 
+ * (code)
+ * var graphCellLabelChanged = graph.cellLabelChanged;
+ * graph.cellLabelChanged = function(cell, newValue, autoSize)
+ * {
+ * 	// Cloned for correct undo/redo
+ * 	var elt = cell.value.cloneNode(true);
+ *  elt.setAttribute('label', newValue);
+ *  
+ *  newValue = elt;
+ *  graphCellLabelChanged.apply(this, arguments);
+ * };
+ * (end) 
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose label should be changed.
+ * value - New label to be assigned.
+ * autoSize - Boolean that specifies if <cellSizeUpdated> should be called.
+ */
+mxGraph.prototype.cellLabelChanged = function(cell, value, autoSize)
+{
+	this.model.beginUpdate();
+	try
+	{
+		this.model.setValue(cell, value);
+		
+		if (autoSize)
+		{
+			this.cellSizeUpdated(cell, false);
+		}
+	}
+	finally
+	{
+		this.model.endUpdate();
+	}
+};
+
+/**
+ * Group: Event processing
+ */
+
+/**
+ * Function: escape
+ * 
+ * Processes an escape keystroke.
+ * 
+ * Parameters:
+ * 
+ * evt - Mouseevent that represents the keystroke.
+ */
+mxGraph.prototype.escape = function(evt)
+{
+	this.fireEvent(new mxEventObject(mxEvent.ESCAPE, 'event', evt));
+};
+
+/**
+ * Function: click
+ * 
+ * Processes a singleclick on an optional cell and fires a <click> event.
+ * The click event is fired initially. If the graph is enabled and the
+ * event has not been consumed, then the cell is selected using
+ * <selectCellForEvent> or the selection is cleared using
+ * <clearSelection>. The events consumed state is set to true if the
+ * corresponding <mxMouseEvent> has been consumed.
+ *
+ * To handle a click event, use the following code.
+ * 
+ * (code)
+ * graph.addListener(mxEvent.CLICK, function(sender, evt)
+ * {
+ *   var e = evt.getProperty('event'); // mouse event
+ *   var cell = evt.getProperty('cell'); // cell may be null
+ *   
+ *   if (cell != null)
+ *   {
+ *     // Do something useful with cell and consume the event
+ *     evt.consume();
+ *   }
+ * });
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * me - <mxMouseEvent> that represents the single click.
+ */
+mxGraph.prototype.click = function(me)
+{
+	var evt = me.getEvent();
+	var cell = me.getCell();
+	var mxe = new mxEventObject(mxEvent.CLICK, 'event', evt, 'cell', cell);
+	
+	if (me.isConsumed())
+	{
+		mxe.consume();
+	}
+	
+	this.fireEvent(mxe);
+	
+	// Handles the event if it has not been consumed
+	if (this.isEnabled() && !mxEvent.isConsumed(evt) && !mxe.isConsumed())
+	{
+		if (cell != null)
+		{
+			if (this.isTransparentClickEvent(evt))
+			{
+				var active = false;
+				
+				var tmp = this.getCellAt(me.graphX, me.graphY, null, null, null, mxUtils.bind(this, function(state)
+				{
+					var selected = this.isCellSelected(state.cell);
+					active = active || selected;
+					
+					return !active || selected;
+				}));
+				
+				if (tmp != null)
+				{
+					cell = tmp;
+				}
+			}
+			
+			this.selectCellForEvent(cell, evt);
+		}
+		else
+		{
+			var swimlane = null;
+			
+			if (this.isSwimlaneSelectionEnabled())
+			{
+				// Gets the swimlane at the location (includes
+				// content area of swimlanes)
+				swimlane = this.getSwimlaneAt(me.getGraphX(), me.getGraphY());
+			}
+
+			// Selects the swimlane and consumes the event
+			if (swimlane != null)
+			{
+				this.selectCellForEvent(swimlane, evt);
+			}
+			
+			// Ignores the event if the control key is pressed
+			else if (!this.isToggleEvent(evt))
+			{
+				this.clearSelection();
+			}
+		}
+	}
+};
+
+/**
+ * Function: dblClick
+ * 
+ * Processes a doubleclick on an optional cell and fires a <dblclick>
+ * event. The event is fired initially. If the graph is enabled and the
+ * event has not been consumed, then <edit> is called with the given
+ * cell. The event is ignored if no cell was specified.
+ *
+ * Example for overriding this method.
+ *
+ * (code)
+ * graph.dblClick = function(evt, cell)
+ * {
+ *   var mxe = new mxEventObject(mxEvent.DOUBLE_CLICK, 'event', evt, 'cell', cell);
+ *   this.fireEvent(mxe);
+ *   
+ *   if (this.isEnabled() && !mxEvent.isConsumed(evt) && !mxe.isConsumed())
+ *   {
+ * 	   mxUtils.alert('Hello, World!');
+ *     mxe.consume();
+ *   }
+ * }
+ * (end)
+ * 
+ * Example listener for this event.
+ * 
+ * (code)
+ * graph.addListener(mxEvent.DOUBLE_CLICK, function(sender, evt)
+ * {
+ *   var cell = evt.getProperty('cell');
+ *   // do something with the cell and consume the
+ *   // event to prevent in-place editing from start
+ * });
+ * (end) 
+ * 
+ * Parameters:
+ * 
+ * evt - Mouseevent that represents the doubleclick.
+ * cell - Optional <mxCell> under the mousepointer.
+ */
+mxGraph.prototype.dblClick = function(evt, cell)
+{
+	var mxe = new mxEventObject(mxEvent.DOUBLE_CLICK, 'event', evt, 'cell', cell);
+	this.fireEvent(mxe);
+	
+	// Handles the event if it has not been consumed
+	if (this.isEnabled() && !mxEvent.isConsumed(evt) && !mxe.isConsumed() &&
+		cell != null && this.isCellEditable(cell) && !this.isEditing(cell))
+	{
+		this.startEditingAtCell(cell, evt);
+		mxEvent.consume(evt);
+	}
+};
+
+/**
+ * Function: tapAndHold
+ * 
+ * Handles the <mxMouseEvent> by highlighting the <mxCellState>.
+ * 
+ * Parameters:
+ * 
+ * me - <mxMouseEvent> that represents the touch event.
+ * state - Optional <mxCellState> that is associated with the event.
+ */
+mxGraph.prototype.tapAndHold = function(me)
+{
+	var evt = me.getEvent();
+	var mxe = new mxEventObject(mxEvent.TAP_AND_HOLD, 'event', evt, 'cell', me.getCell());
+
+	// LATER: Check if event should be consumed if me is consumed
+	this.fireEvent(mxe);
+
+	if (mxe.isConsumed())
+	{
+		// Resets the state of the panning handler
+		this.panningHandler.panningTrigger = false;
+	}
+	
+	// Handles the event if it has not been consumed
+	if (this.isEnabled() && !mxEvent.isConsumed(evt) && !mxe.isConsumed() && this.connectionHandler.isEnabled())
+	{
+		var state = this.view.getState(this.connectionHandler.marker.getCell(me));
+
+		if (state != null)
+		{
+			this.connectionHandler.marker.currentColor = this.connectionHandler.marker.validColor;
+			this.connectionHandler.marker.markedState = state;
+			this.connectionHandler.marker.mark();
+			
+			this.connectionHandler.first = new mxPoint(me.getGraphX(), me.getGraphY());
+			this.connectionHandler.edgeState = this.connectionHandler.createEdgeState(me);
+			this.connectionHandler.previous = state;
+			this.connectionHandler.fireEvent(new mxEventObject(mxEvent.START, 'state', this.connectionHandler.previous));
+		}
+	}
+};
+
+/**
+ * Function: scrollPointToVisible
+ * 
+ * Scrolls the graph to the given point, extending the graph container if
+ * specified.
+ */
+mxGraph.prototype.scrollPointToVisible = function(x, y, extend, border)
+{
+	if (!this.timerAutoScroll && (this.ignoreScrollbars || mxUtils.hasScrollbars(this.container)))
+	{
+		var c = this.container;
+		border = (border != null) ? border : 20;
+		
+		if (x >= c.scrollLeft && y >= c.scrollTop && x <= c.scrollLeft + c.clientWidth &&
+			y <= c.scrollTop + c.clientHeight)
+		{
+			var dx = c.scrollLeft + c.clientWidth - x;
+			
+			if (dx < border)
+			{
+				var old = c.scrollLeft;
+				c.scrollLeft += border - dx;
+
+				// Automatically extends the canvas size to the bottom, right
+				// if the event is outside of the canvas and the edge of the
+				// canvas has been reached. Notes: Needs fix for IE.
+				if (extend && old == c.scrollLeft)
+				{
+					if (this.dialect == mxConstants.DIALECT_SVG)
+					{
+						var root = this.view.getDrawPane().ownerSVGElement;
+						var width = this.container.scrollWidth + border - dx;
+						
+						// Updates the clipping region. This is an expensive
+						// operation that should not be executed too often.
+						root.style.width = width + 'px';
+					}
+					else
+					{
+						var width = Math.max(c.clientWidth, c.scrollWidth) + border - dx;
+						var canvas = this.view.getCanvas();
+						canvas.style.width = width + 'px';
+					}
+					
+					c.scrollLeft += border - dx;
+				}
+			}
+			else
+			{
+				dx = x - c.scrollLeft;
+				
+				if (dx < border)
+				{
+					c.scrollLeft -= border - dx;
+				}
+			}
+			
+			var dy = c.scrollTop + c.clientHeight - y;
+			
+			if (dy < border)
+			{
+				var old = c.scrollTop;
+				c.scrollTop += border - dy;
+
+				if (old == c.scrollTop && extend)
+				{
+					if (this.dialect == mxConstants.DIALECT_SVG)
+					{
+						var root = this.view.getDrawPane().ownerSVGElement;
+						var height = this.container.scrollHeight + border - dy;
+						
+						// Updates the clipping region. This is an expensive
+						// operation that should not be executed too often.
+						root.style.height = height + 'px';
+					}
+					else
+					{
+						var height = Math.max(c.clientHeight, c.scrollHeight) + border - dy;
+						var canvas = this.view.getCanvas();
+						canvas.style.height = height + 'px';
+					}
+					
+					c.scrollTop += border - dy;
+				}
+			}
+			else
+			{
+				dy = y - c.scrollTop;
+				
+				if (dy < border)
+				{
+					c.scrollTop -= border - dy;
+				}
+			}
+		}
+	}
+	else if (this.allowAutoPanning && !this.panningHandler.isActive())
+	{
+		if (this.panningManager == null)
+		{
+			this.panningManager = this.createPanningManager();
+		}
+
+		this.panningManager.panTo(x + this.panDx, y + this.panDy);
+	}
+};
+
+
+/**
+ * Function: createPanningManager
+ * 
+ * Creates and returns an <mxPanningManager>.
+ */
+mxGraph.prototype.createPanningManager = function()
+{
+	return new mxPanningManager(this);
+};
+
+/**
+ * Function: getBorderSizes
+ * 
+ * Returns the size of the border and padding on all four sides of the
+ * container. The left, top, right and bottom borders are stored in the x, y,
+ * width and height of the returned <mxRectangle>, respectively.
+ */
+mxGraph.prototype.getBorderSizes = function()
+{
+	var css = mxUtils.getCurrentStyle(this.container);
+	
+	return new mxRectangle(mxUtils.parseCssNumber(css.paddingLeft) +
+			((css.borderLeftStyle != 'none') ? mxUtils.parseCssNumber(css.borderLeftWidth) : 0),
+		mxUtils.parseCssNumber(css.paddingTop) +
+			((css.borderTopStyle != 'none') ? mxUtils.parseCssNumber(css.borderTopWidth) : 0),
+		mxUtils.parseCssNumber(css.paddingRight) +
+			((css.borderRightStyle != 'none') ? mxUtils.parseCssNumber(css.borderRightWidth) : 0),
+		mxUtils.parseCssNumber(css.paddingBottom) +
+			((css.borderBottomStyle != 'none') ? mxUtils.parseCssNumber(css.borderBottomWidth) : 0));
+};
+
+/**
+ * Function: getPreferredPageSize
+ * 
+ * Returns the preferred size of the background page if <preferPageSize> is true.
+ */
+mxGraph.prototype.getPreferredPageSize = function(bounds, width, height)
+{
+	var scale = this.view.scale;
+	var tr = this.view.translate;
+	var fmt = this.pageFormat;
+	var ps = this.pageScale;
+	var page = new mxRectangle(0, 0, Math.ceil(fmt.width * ps), Math.ceil(fmt.height * ps));
+	
+	var hCount = (this.pageBreaksVisible) ? Math.ceil(width / page.width) : 1;
+	var vCount = (this.pageBreaksVisible) ? Math.ceil(height / page.height) : 1;
+	
+	return new mxRectangle(0, 0, hCount * page.width + 2 + tr.x, vCount * page.height + 2 + tr.y);
+};
+
+/**
+ * Function: fit
+ *
+ * Scales the graph such that the complete diagram fits into <container> and
+ * returns the current scale in the view. To fit an initial graph prior to
+ * rendering, set <mxGraphView.rendering> to false prior to changing the model
+ * and execute the following after changing the model.
+ * 
+ * (code)
+ * graph.fit();
+ * graph.view.rendering = true;
+ * graph.refresh();
+ * (end)
+ * 
+ * To fit and center the graph, the following code can be used.
+ * 
+ * (code)
+ * var margin = 2;
+ * var max = 3;
+ * 
+ * var bounds = graph.getGraphBounds();
+ * var cw = graph.container.clientWidth - margin;
+ * var ch = graph.container.clientHeight - margin;
+ * var w = bounds.width / graph.view.scale;
+ * var h = bounds.height / graph.view.scale;
+ * var s = Math.min(max, Math.min(cw / w, ch / h));
+ * 
+ * graph.view.scaleAndTranslate(s,
+ *   (margin + cw - w * s) / (2 * s) - bounds.x / graph.view.scale,
+ *   (margin + ch - h * s) / (2 * s) - bounds.y / graph.view.scale);
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * border - Optional number that specifies the border. Default is <border>.
+ * keepOrigin - Optional boolean that specifies if the translate should be
+ * changed. Default is false.
+ * margin - Optional margin in pixels. Default is 0.
+ * enabled - Optional boolean that specifies if the scale should be set or
+ * just returned. Default is true.
+ * ignoreWidth - Optional boolean that specifies if the width should be
+ * ignored. Default is false.
+ * ignoreHeight - Optional boolean that specifies if the height should be
+ * ignored. Default is false.
+ */
+mxGraph.prototype.fit = function(border, keepOrigin, margin, enabled, ignoreWidth, ignoreHeight)
+{
+	if (this.container != null)
+	{
+		border = (border != null) ? border : this.getBorder();
+		keepOrigin = (keepOrigin != null) ? keepOrigin : false;
+		margin = (margin != null) ? margin : 0;
+		enabled = (enabled != null) ? enabled : true;
+		ignoreWidth = (ignoreWidth != null) ? ignoreWidth : false;
+		ignoreHeight = (ignoreHeight != null) ? ignoreHeight : false;
+		
+		// Adds spacing and border from css
+		var cssBorder = this.getBorderSizes();
+		var w1 = this.container.offsetWidth - cssBorder.x - cssBorder.width - 1;
+		var h1 = this.container.offsetHeight - cssBorder.y - cssBorder.height - 1;
+		var bounds = this.view.getGraphBounds();
+		
+		if (bounds.width > 0 && bounds.height > 0)
+		{
+			if (keepOrigin && bounds.x != null && bounds.y != null)
+			{
+				bounds = bounds.clone();
+				bounds.width += bounds.x;
+				bounds.height += bounds.y;
+				bounds.x = 0;
+				bounds.y = 0;
+			}
+			
+			// LATER: Use unscaled bounding boxes to fix rounding errors
+			var s = this.view.scale;
+			var w2 = bounds.width / s;
+			var h2 = bounds.height / s;
+			
+			// Fits to the size of the background image if required
+			if (this.backgroundImage != null)
+			{
+				w2 = Math.max(w2, this.backgroundImage.width - bounds.x / s);
+				h2 = Math.max(h2, this.backgroundImage.height - bounds.y / s);
+			}
+			
+			var b = ((keepOrigin) ? border : 2 * border) + margin;
+
+			w1 -= b;
+			h1 -= b;
+			
+			var s2 = (((ignoreWidth) ? h1 / h2 : (ignoreHeight) ? w1 / w2 :
+				Math.min(w1 / w2, h1 / h2)));
+			
+			if (this.minFitScale != null)
+			{
+				s2 = Math.max(s2, this.minFitScale);
+			}
+			
+			if (this.maxFitScale != null)
+			{
+				s2 = Math.min(s2, this.maxFitScale);
+			}
+	
+			if (enabled)
+			{
+				if (!keepOrigin)
+				{
+					if (!mxUtils.hasScrollbars(this.container))
+					{
+						var x0 = (bounds.x != null) ? Math.floor(this.view.translate.x - bounds.x / s + border / s2 + margin / 2) : border;
+						var y0 = (bounds.y != null) ? Math.floor(this.view.translate.y - bounds.y / s + border / s2 + margin / 2) : border;
+
+						this.view.scaleAndTranslate(s2, x0, y0);
+					}
+					else
+					{
+						this.view.setScale(s2);
+						var b2 = this.getGraphBounds();
+						
+						if (b2.x != null)
+						{
+							this.container.scrollLeft = b2.x;
+						}
+						
+						if (b2.y != null)
+						{
+							this.container.scrollTop = b2.y;
+						}
+					}
+				}
+				else if (this.view.scale != s2)
+				{
+					this.view.setScale(s2);
+				}
+			}
+			else
+			{
+				return s2;
+			}
+		}
+	}
+
+	return this.view.scale;
+};
+
+/**
+ * Function: sizeDidChange
+ * 
+ * Called when the size of the graph has changed. This implementation fires
+ * a <size> event after updating the clipping region of the SVG element in
+ * SVG-bases browsers.
+ */
+mxGraph.prototype.sizeDidChange = function()
+{
+	var bounds = this.getGraphBounds();
+	
+	if (this.container != null)
+	{
+		var border = this.getBorder();
+		
+		var width = Math.max(0, bounds.x + bounds.width + border);
+		var height = Math.max(0, bounds.y + bounds.height + border);
+		
+		if (this.minimumContainerSize != null)
+		{
+			width = Math.max(width, this.minimumContainerSize.width);
+			height = Math.max(height, this.minimumContainerSize.height);
+		}
+
+		if (this.resizeContainer)
+		{
+			this.doResizeContainer(width, height);
+		}
+
+		if (this.preferPageSize || (!mxClient.IS_IE && this.pageVisible))
+		{
+			var size = this.getPreferredPageSize(bounds, Math.max(1, width), Math.max(1, height));
+			
+			if (size != null)
+			{
+				width = size.width * this.view.scale;
+				height = size.height * this.view.scale;
+			}
+		}
+		
+		if (this.minimumGraphSize != null)
+		{
+			width = Math.max(width, this.minimumGraphSize.width * this.view.scale);
+			height = Math.max(height, this.minimumGraphSize.height * this.view.scale);
+		}
+
+		width = Math.ceil(width);
+		height = Math.ceil(height);
+
+		if (this.dialect == mxConstants.DIALECT_SVG)
+		{
+			var root = this.view.getDrawPane().ownerSVGElement;
+			
+			root.style.minWidth = Math.max(1, width) + 'px';
+			root.style.minHeight = Math.max(1, height) + 'px';
+			root.style.width = '100%';
+			root.style.height = '100%';
+		}
+		else
+		{
+			if (mxClient.IS_QUIRKS)
+			{
+				// Quirks mode does not support minWidth/-Height
+				this.view.updateHtmlCanvasSize(Math.max(1, width), Math.max(1, height));
+			}
+			else
+			{
+				this.view.canvas.style.minWidth = Math.max(1, width) + 'px';
+				this.view.canvas.style.minHeight = Math.max(1, height) + 'px';
+			}
+		}
+		
+		this.updatePageBreaks(this.pageBreaksVisible, width, height);
+	}
+
+	this.fireEvent(new mxEventObject(mxEvent.SIZE, 'bounds', bounds));
+};
+
+/**
+ * Function: doResizeContainer
+ * 
+ * Resizes the container for the given graph width and height.
+ */
+mxGraph.prototype.doResizeContainer = function(width, height)
+{
+	if (this.maximumContainerSize != null)
+	{
+		width = Math.min(this.maximumContainerSize.width, width);
+		height = Math.min(this.maximumContainerSize.height, height);
+	}
+
+	this.container.style.width = Math.ceil(width) + 'px';
+	this.container.style.height = Math.ceil(height) + 'px';
+};
+
+/**
+ * Function: updatePageBreaks
+ * 
+ * Invokes from <sizeDidChange> to redraw the page breaks.
+ * 
+ * Parameters:
+ * 
+ * visible - Boolean that specifies if page breaks should be shown.
+ * width - Specifies the width of the container in pixels.
+ * height - Specifies the height of the container in pixels.
+ */
+mxGraph.prototype.updatePageBreaks = function(visible, width, height)
+{
+	var scale = this.view.scale;
+	var tr = this.view.translate;
+	var fmt = this.pageFormat;
+	var ps = scale * this.pageScale;
+	var bounds = new mxRectangle(0, 0, fmt.width * ps, fmt.height * ps);
+
+	var gb = mxRectangle.fromRectangle(this.getGraphBounds());
+	gb.width = Math.max(1, gb.width);
+	gb.height = Math.max(1, gb.height);
+	
+	bounds.x = Math.floor((gb.x - tr.x * scale) / bounds.width) * bounds.width + tr.x * scale;
+	bounds.y = Math.floor((gb.y - tr.y * scale) / bounds.height) * bounds.height + tr.y * scale;
+	
+	gb.width = Math.ceil((gb.width + (gb.x - bounds.x)) / bounds.width) * bounds.width;
+	gb.height = Math.ceil((gb.height + (gb.y - bounds.y)) / bounds.height) * bounds.height;
+	
+	// Does not show page breaks if the scale is too small
+	visible = visible && Math.min(bounds.width, bounds.height) > this.minPageBreakDist;
+
+	var horizontalCount = (visible) ? Math.ceil(gb.height / bounds.height) + 1 : 0;
+	var verticalCount = (visible) ? Math.ceil(gb.width / bounds.width) + 1 : 0;
+	var right = (verticalCount - 1) * bounds.width;
+	var bottom = (horizontalCount - 1) * bounds.height;
+	
+	if (this.horizontalPageBreaks == null && horizontalCount > 0)
+	{
+		this.horizontalPageBreaks = [];
+	}
+
+	if (this.verticalPageBreaks == null && verticalCount > 0)
+	{
+		this.verticalPageBreaks = [];
+	}
+	
+	var drawPageBreaks = mxUtils.bind(this, function(breaks)
+	{
+		if (breaks != null)
+		{
+			var count = (breaks == this.horizontalPageBreaks) ? horizontalCount : verticalCount; 
+			
+			for (var i = 0; i <= count; i++)
+			{
+				var pts = (breaks == this.horizontalPageBreaks) ?
+					[new mxPoint(Math.round(bounds.x), Math.round(bounds.y + i * bounds.height)),
+			         new mxPoint(Math.round(bounds.x + right), Math.round(bounds.y + i * bounds.height))] :
+			        [new mxPoint(Math.round(bounds.x + i * bounds.width), Math.round(bounds.y)),
+			         new mxPoint(Math.round(bounds.x + i * bounds.width), Math.round(bounds.y + bottom))];
+
+				if (breaks[i] != null)
+				{
+					breaks[i].points = pts;
+					breaks[i].redraw();
+				}
+				else
+				{
+					var pageBreak = new mxPolyline(pts, this.pageBreakColor);
+					pageBreak.dialect = this.dialect;
+					pageBreak.pointerEvents = false;
+					pageBreak.isDashed = this.pageBreakDashed;
+					pageBreak.init(this.view.backgroundPane);
+					pageBreak.redraw();
+					
+					breaks[i] = pageBreak;
+				}
+			}
+			
+			for (var i = count; i < breaks.length; i++)
+			{
+				breaks[i].destroy();
+			}
+			
+			breaks.splice(count, breaks.length - count);
+		}
+	});
+	
+	drawPageBreaks(this.horizontalPageBreaks);
+	drawPageBreaks(this.verticalPageBreaks);
+};
+
+/**
+ * Group: Cell styles
+ */
+
+/**
+ * Function: getCellStyle
+ * 
+ * Returns an array of key, value pairs representing the cell style for the
+ * given cell. If no string is defined in the model that specifies the
+ * style, then the default style for the cell is returned or <EMPTY_ARRAY>,
+ * if not style can be found. Note: You should try and get the cell state
+ * for the given cell and use the cached style in the state before using
+ * this method.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose style should be returned as an array.
+ */
+mxGraph.prototype.getCellStyle = function(cell)
+{
+	var stylename = this.model.getStyle(cell);
+	var style = null;
+	
+	// Gets the default style for the cell
+	if (this.model.isEdge(cell))
+	{
+		style = this.stylesheet.getDefaultEdgeStyle();
+	}
+	else
+	{
+		style = this.stylesheet.getDefaultVertexStyle();
+	}
+	
+	// Resolves the stylename using the above as the default
+	if (stylename != null)
+	{
+		style = this.postProcessCellStyle(this.stylesheet.getCellStyle(stylename, style));
+	}
+	
+	// Returns a non-null value if no style can be found
+	if (style == null)
+	{
+		style = mxGraph.prototype.EMPTY_ARRAY;
+	}
+	
+	return style;
+};
+
+/**
+ * Function: postProcessCellStyle
+ * 
+ * Tries to resolve the value for the image style in the image bundles and
+ * turns short data URIs as defined in mxImageBundle to data URIs as
+ * defined in RFC 2397 of the IETF.
+ */
+mxGraph.prototype.postProcessCellStyle = function(style)
+{
+	if (style != null)
+	{
+		var key = style[mxConstants.STYLE_IMAGE];
+		var image = this.getImageFromBundles(key);
+
+		if (image != null)
+		{
+			style[mxConstants.STYLE_IMAGE] = image;
+		}
+		else
+		{
+			image = key;
+		}
+		
+		// Converts short data uris to normal data uris
+		if (image != null && image.substring(0, 11) == 'data:image/')
+		{
+			if (image.substring(0, 20) == 'data:image/svg+xml,<')
+			{
+				// Required for FF and IE11
+				image = image.substring(0, 19) + encodeURIComponent(image.substring(19));
+			}
+			else if (image.substring(0, 22) != 'data:image/svg+xml,%3C')
+			{
+				var comma = image.indexOf(',');
+				
+				// Adds base64 encoding prefix if needed
+				if (comma > 0 && image.substring(comma - 7, comma + 1) != ';base64,')
+				{
+					image = image.substring(0, comma) + ';base64,'
+						+ image.substring(comma + 1);
+				}
+			}
+			
+			style[mxConstants.STYLE_IMAGE] = image;
+		}
+	}
+
+	return style;
+};
+
+/**
+ * Function: setCellStyle
+ * 
+ * Sets the style of the specified cells. If no cells are given, then the
+ * selection cells are changed.
+ * 
+ * Parameters:
+ * 
+ * style - String representing the new style of the cells.
+ * cells - Optional array of <mxCells> to set the style for. Default is the
+ * selection cells.
+ */
+mxGraph.prototype.setCellStyle = function(style, cells)
+{
+	cells = cells || this.getSelectionCells();
+	
+	if (cells != null)
+	{
+		this.model.beginUpdate();
+		try
+		{
+			for (var i = 0; i < cells.length; i++)
+			{
+				this.model.setStyle(cells[i], style);
+			}
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: toggleCellStyle
+ * 
+ * Toggles the boolean value for the given key in the style of the given cell
+ * and returns the new value as 0 or 1. If no cell is specified then the
+ * selection cell is used.
+ * 
+ * Parameter:
+ * 
+ * key - String representing the key for the boolean value to be toggled.
+ * defaultValue - Optional boolean default value if no value is defined.
+ * Default is false.
+ * cell - Optional <mxCell> whose style should be modified. Default is
+ * the selection cell.
+ */
+mxGraph.prototype.toggleCellStyle = function(key, defaultValue, cell)
+{
+	cell = cell || this.getSelectionCell();
+	
+	return this.toggleCellStyles(key, defaultValue, [cell]);
+};
+
+/**
+ * Function: toggleCellStyles
+ * 
+ * Toggles the boolean value for the given key in the style of the given cells
+ * and returns the new value as 0 or 1. If no cells are specified, then the
+ * selection cells are used. For example, this can be used to toggle
+ * <mxConstants.STYLE_ROUNDED> or any other style with a boolean value.
+ * 
+ * Parameter:
+ * 
+ * key - String representing the key for the boolean value to be toggled.
+ * defaultValue - Optional boolean default value if no value is defined.
+ * Default is false.
+ * cells - Optional array of <mxCells> whose styles should be modified.
+ * Default is the selection cells.
+ */
+mxGraph.prototype.toggleCellStyles = function(key, defaultValue, cells)
+{
+	defaultValue = (defaultValue != null) ? defaultValue : false;
+	cells = cells || this.getSelectionCells();
+	var value = null;
+	
+	if (cells != null && cells.length > 0)
+	{
+		var state = this.view.getState(cells[0]);
+		var style = (state != null) ? state.style : this.getCellStyle(cells[0]);
+		
+		if (style != null)
+		{
+			value = (mxUtils.getValue(style, key, defaultValue)) ? 0 : 1;
+			this.setCellStyles(key, value, cells);
+		}
+	}
+	
+	return value;
+};
+
+/**
+ * Function: setCellStyles
+ * 
+ * Sets the key to value in the styles of the given cells. This will modify
+ * the existing cell styles in-place and override any existing assignment
+ * for the given key. If no cells are specified, then the selection cells
+ * are changed. If no value is specified, then the respective key is
+ * removed from the styles.
+ * 
+ * Parameters:
+ * 
+ * key - String representing the key to be assigned.
+ * value - String representing the new value for the key.
+ * cells - Optional array of <mxCells> to change the style for. Default is
+ * the selection cells.
+ */
+mxGraph.prototype.setCellStyles = function(key, value, cells)
+{
+	cells = cells || this.getSelectionCells();
+	mxUtils.setCellStyles(this.model, cells, key, value);
+};
+
+/**
+ * Function: toggleCellStyleFlags
+ * 
+ * Toggles the given bit for the given key in the styles of the specified
+ * cells.
+ * 
+ * Parameters:
+ * 
+ * key - String representing the key to toggle the flag in.
+ * flag - Integer that represents the bit to be toggled.
+ * cells - Optional array of <mxCells> to change the style for. Default is
+ * the selection cells.
+ */
+mxGraph.prototype.toggleCellStyleFlags = function(key, flag, cells)
+{
+	this.setCellStyleFlags(key, flag, null, cells);
+};
+
+/**
+ * Function: setCellStyleFlags
+ * 
+ * Sets or toggles the given bit for the given key in the styles of the
+ * specified cells.
+ * 
+ * Parameters:
+ * 
+ * key - String representing the key to toggle the flag in.
+ * flag - Integer that represents the bit to be toggled.
+ * value - Boolean value to be used or null if the value should be toggled.
+ * cells - Optional array of <mxCells> to change the style for. Default is
+ * the selection cells.
+ */
+mxGraph.prototype.setCellStyleFlags = function(key, flag, value, cells)
+{
+	cells = cells || this.getSelectionCells();
+	
+	if (cells != null && cells.length > 0)
+	{
+		if (value == null)
+		{
+			var state = this.view.getState(cells[0]);
+			var style = (state != null) ? state.style : this.getCellStyle(cells[0]);
+			
+			if (style != null)
+			{
+				var current = parseInt(style[key] || 0);
+				value = !((current & flag) == flag);
+			}
+		}
+
+		mxUtils.setCellStyleFlags(this.model, cells, key, flag, value);
+	}
+};
+
+/**
+ * Group: Cell alignment and orientation
+ */
+
+/**
+ * Function: alignCells
+ * 
+ * Aligns the given cells vertically or horizontally according to the given
+ * alignment using the optional parameter as the coordinate.
+ * 
+ * Parameters:
+ * 
+ * align - Specifies the alignment. Possible values are all constants in
+ * mxConstants with an ALIGN prefix.
+ * cells - Array of <mxCells> to be aligned.
+ * param - Optional coordinate for the alignment.
+ */
+mxGraph.prototype.alignCells = function(align, cells, param)
+{
+	if (cells == null)
+	{
+		cells = this.getSelectionCells();
+	}
+	
+	if (cells != null && cells.length > 1)
+	{
+		// Finds the required coordinate for the alignment
+		if (param == null)
+		{
+			for (var i = 0; i < cells.length; i++)
+			{
+				var state = this.view.getState(cells[i]);
+				
+				if (state != null && !this.model.isEdge(cells[i]))
+				{
+					if (param == null)
+					{
+						if (align == mxConstants.ALIGN_CENTER)
+						{
+							param = state.x + state.width / 2;
+							break;
+						}
+						else if (align == mxConstants.ALIGN_RIGHT)
+						{
+							param = state.x + state.width;
+						}
+						else if (align == mxConstants.ALIGN_TOP)
+						{
+							param = state.y;
+						}
+						else if (align == mxConstants.ALIGN_MIDDLE)
+						{
+							param = state.y + state.height / 2;
+							break;
+						}
+						else if (align == mxConstants.ALIGN_BOTTOM)
+						{
+							param = state.y + state.height;
+						}
+						else
+						{
+							param = state.x;
+						}
+					}
+					else
+					{
+						if (align == mxConstants.ALIGN_RIGHT)
+						{
+							param = Math.max(param, state.x + state.width);
+						}
+						else if (align == mxConstants.ALIGN_TOP)
+						{
+							param = Math.min(param, state.y);
+						}
+						else if (align == mxConstants.ALIGN_BOTTOM)
+						{
+							param = Math.max(param, state.y + state.height);
+						}
+						else
+						{
+							param = Math.min(param, state.x);
+						}
+					}
+				}
+			}
+		}
+
+		// Aligns the cells to the coordinate
+		if (param != null)
+		{
+			var s = this.view.scale;
+
+			this.model.beginUpdate();
+			try
+			{
+				for (var i = 0; i < cells.length; i++)
+				{
+					var state = this.view.getState(cells[i]);
+					
+					if (state != null)
+					{
+						var geo = this.getCellGeometry(cells[i]);
+						
+						if (geo != null && !this.model.isEdge(cells[i]))
+						{
+							geo = geo.clone();
+							
+							if (align == mxConstants.ALIGN_CENTER)
+							{
+								geo.x += (param - state.x - state.width / 2) / s;
+							}
+							else if (align == mxConstants.ALIGN_RIGHT)
+							{
+								geo.x += (param - state.x - state.width) / s;
+							}
+							else if (align == mxConstants.ALIGN_TOP)
+							{
+								geo.y += (param - state.y) / s;
+							}
+							else if (align == mxConstants.ALIGN_MIDDLE)
+							{
+								geo.y += (param - state.y - state.height / 2) / s;
+							}
+							else if (align == mxConstants.ALIGN_BOTTOM)
+							{
+								geo.y += (param - state.y - state.height) / s;
+							}
+							else
+							{
+								geo.x += (param - state.x) / s;
+							}
+							
+							this.resizeCell(cells[i], geo);
+						}
+					}
+				}
+				
+				this.fireEvent(new mxEventObject(mxEvent.ALIGN_CELLS,
+						'align', align, 'cells', cells));
+			}
+			finally
+			{
+				this.model.endUpdate();
+			}
+		}
+	}
+	
+	return cells;
+};
+
+/**
+ * Function: flipEdge
+ * 
+ * Toggles the style of the given edge between null (or empty) and
+ * <alternateEdgeStyle>. This method fires <mxEvent.FLIP_EDGE> while the
+ * transaction is in progress. Returns the edge that was flipped.
+ * 
+ * Here is an example that overrides this implementation to invert the
+ * value of <mxConstants.STYLE_ELBOW> without removing any existing styles.
+ * 
+ * (code)
+ * graph.flipEdge = function(edge)
+ * {
+ *   if (edge != null)
+ *   {
+ *     var state = this.view.getState(edge);
+ *     var style = (state != null) ? state.style : this.getCellStyle(edge);
+ *     
+ *     if (style != null)
+ *     {
+ *       var elbow = mxUtils.getValue(style, mxConstants.STYLE_ELBOW,
+ *           mxConstants.ELBOW_HORIZONTAL);
+ *       var value = (elbow == mxConstants.ELBOW_HORIZONTAL) ?
+ *           mxConstants.ELBOW_VERTICAL : mxConstants.ELBOW_HORIZONTAL;
+ *       this.setCellStyles(mxConstants.STYLE_ELBOW, value, [edge]);
+ *     }
+ *   }
+ * };
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> whose style should be changed.
+ */
+mxGraph.prototype.flipEdge = function(edge)
+{
+	if (edge != null &&
+		this.alternateEdgeStyle != null)
+	{
+		this.model.beginUpdate();
+		try
+		{
+			var style = this.model.getStyle(edge);
+
+			if (style == null || style.length == 0)
+			{
+				this.model.setStyle(edge, this.alternateEdgeStyle);
+			}
+			else
+			{
+				this.model.setStyle(edge, null);
+			}
+
+			// Removes all existing control points
+			this.resetEdge(edge);
+			this.fireEvent(new mxEventObject(mxEvent.FLIP_EDGE, 'edge', edge));
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+
+	return edge;
+};
+
+/**
+ * Function: addImageBundle
+ *
+ * Adds the specified <mxImageBundle>.
+ */
+mxGraph.prototype.addImageBundle = function(bundle)
+{
+	this.imageBundles.push(bundle);
+};
+
+/**
+ * Function: removeImageBundle
+ * 
+ * Removes the specified <mxImageBundle>.
+ */
+mxGraph.prototype.removeImageBundle = function(bundle)
+{
+	var tmp = [];
+	
+	for (var i = 0; i < this.imageBundles.length; i++)
+	{
+		if (this.imageBundles[i] != bundle)
+		{
+			tmp.push(this.imageBundles[i]);
+		}
+	}
+	
+	this.imageBundles = tmp;
+};
+
+/**
+ * Function: getImageFromBundles
+ *
+ * Searches all <imageBundles> for the specified key and returns the value
+ * for the first match or null if the key is not found.
+ */
+mxGraph.prototype.getImageFromBundles = function(key)
+{
+	if (key != null)
+	{
+		for (var i = 0; i < this.imageBundles.length; i++)
+		{
+			var image = this.imageBundles[i].getImage(key);
+			
+			if (image != null)
+			{
+				return image;
+			}
+		}
+	}
+	
+	return null;
+};
+
+/**
+ * Group: Order
+ */
+
+/**
+ * Function: orderCells
+ * 
+ * Moves the given cells to the front or back. The change is carried out
+ * using <cellsOrdered>. This method fires <mxEvent.ORDER_CELLS> while the
+ * transaction is in progress.
+ * 
+ * Parameters:
+ * 
+ * back - Boolean that specifies if the cells should be moved to back.
+ * cells - Array of <mxCells> to move to the background. If null is
+ * specified then the selection cells are used.
+ */
+mxGraph.prototype.orderCells = function(back, cells)
+{
+	if (cells == null)
+	{
+		cells = mxUtils.sortCells(this.getSelectionCells(), true);
+	}
+
+	this.model.beginUpdate();
+	try
+	{
+		this.cellsOrdered(cells, back);
+		this.fireEvent(new mxEventObject(mxEvent.ORDER_CELLS,
+				'back', back, 'cells', cells));
+	}
+	finally
+	{
+		this.model.endUpdate();
+	}
+
+	return cells;
+};
+
+/**
+ * Function: cellsOrdered
+ * 
+ * Moves the given cells to the front or back. This method fires
+ * <mxEvent.CELLS_ORDERED> while the transaction is in progress.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> whose order should be changed.
+ * back - Boolean that specifies if the cells should be moved to back.
+ */
+mxGraph.prototype.cellsOrdered = function(cells, back)
+{
+	if (cells != null)
+	{
+		this.model.beginUpdate();
+		try
+		{
+			for (var i = 0; i < cells.length; i++)
+			{
+				var parent = this.model.getParent(cells[i]);
+
+				if (back)
+				{
+					this.model.add(parent, cells[i], i);
+				}
+				else
+				{
+					this.model.add(parent, cells[i],
+							this.model.getChildCount(parent) - 1);
+				}
+			}
+			
+			this.fireEvent(new mxEventObject(mxEvent.CELLS_ORDERED,
+					'back', back, 'cells', cells));
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Group: Grouping
+ */
+
+/**
+ * Function: groupCells
+ * 
+ * Adds the cells into the given group. The change is carried out using
+ * <cellsAdded>, <cellsMoved> and <cellsResized>. This method fires
+ * <mxEvent.GROUP_CELLS> while the transaction is in progress. Returns the
+ * new group. A group is only created if there is at least one entry in the
+ * given array of cells.
+ * 
+ * Parameters:
+ * 
+ * group - <mxCell> that represents the target group. If null is specified
+ * then a new group is created using <createGroupCell>.
+ * border - Optional integer that specifies the border between the child
+ * area and the group bounds. Default is 0.
+ * cells - Optional array of <mxCells> to be grouped. If null is specified
+ * then the selection cells are used.
+ */
+mxGraph.prototype.groupCells = function(group, border, cells)
+{
+	if (cells == null)
+	{
+		cells = mxUtils.sortCells(this.getSelectionCells(), true);
+	}
+
+	cells = this.getCellsForGroup(cells);
+
+	if (group == null)
+	{
+		group = this.createGroupCell(cells);
+	}
+
+	var bounds = this.getBoundsForGroup(group, cells, border);
+
+	if (cells.length > 0 && bounds != null)
+	{
+		// Uses parent of group or previous parent of first child
+		var parent = this.model.getParent(group);
+		
+		if (parent == null)
+		{
+			parent = this.model.getParent(cells[0]);
+		}
+
+		this.model.beginUpdate();
+		try
+		{
+			// Checks if the group has a geometry and
+			// creates one if one does not exist
+			if (this.getCellGeometry(group) == null)
+			{
+				this.model.setGeometry(group, new mxGeometry());
+			}
+
+			// Adds the group into the parent
+			var index = this.model.getChildCount(parent);
+			this.cellsAdded([group], parent, index, null, null, false, false, false);
+
+			// Adds the children into the group and moves
+			index = this.model.getChildCount(group);
+			this.cellsAdded(cells, group, index, null, null, false, false, false);
+			this.cellsMoved(cells, -bounds.x, -bounds.y, false, false, false);
+
+			// Resizes the group
+			this.cellsResized([group], [bounds], false);
+
+			this.fireEvent(new mxEventObject(mxEvent.GROUP_CELLS,
+					'group', group, 'border', border, 'cells', cells));
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+
+	return group;
+};
+
+/**
+ * Function: getCellsForGroup
+ * 
+ * Returns the cells with the same parent as the first cell
+ * in the given array.
+ */
+mxGraph.prototype.getCellsForGroup = function(cells)
+{
+	var result = [];
+
+	if (cells != null && cells.length > 0)
+	{
+		var parent = this.model.getParent(cells[0]);
+		result.push(cells[0]);
+
+		// Filters selection cells with the same parent
+		for (var i = 1; i < cells.length; i++)
+		{
+			if (this.model.getParent(cells[i]) == parent)
+			{
+				result.push(cells[i]);
+			}
+		}
+	}
+
+	return result;
+};
+
+/**
+ * Function: getBoundsForGroup
+ * 
+ * Returns the bounds to be used for the given group and children.
+ */
+mxGraph.prototype.getBoundsForGroup = function(group, children, border)
+{
+	var result = this.getBoundingBoxFromGeometry(children, true);
+	
+	if (result != null)
+	{
+		if (this.isSwimlane(group))
+		{
+			var size = this.getStartSize(group);
+			
+			result.x -= size.width;
+			result.y -= size.height;
+			result.width += size.width;
+			result.height += size.height;
+		}
+		
+		// Adds the border
+		if (border != null)
+		{
+			result.x -= border;
+			result.y -= border;
+			result.width += 2 * border;
+			result.height += 2 * border;
+		}
+	}			
+	
+	return result;
+};
+
+/**
+ * Function: createGroupCell
+ * 
+ * Hook for creating the group cell to hold the given array of <mxCells> if
+ * no group cell was given to the <group> function.
+ * 
+ * The following code can be used to set the style of new group cells.
+ * 
+ * (code)
+ * var graphCreateGroupCell = graph.createGroupCell;
+ * graph.createGroupCell = function(cells)
+ * {
+ *   var group = graphCreateGroupCell.apply(this, arguments);
+ *   group.setStyle('group');
+ *   
+ *   return group;
+ * };
+ */
+mxGraph.prototype.createGroupCell = function(cells)
+{
+	var group = new mxCell('');
+	group.setVertex(true);
+	group.setConnectable(false);
+	
+	return group;
+};
+
+/**
+ * Function: ungroupCells
+ * 
+ * Ungroups the given cells by moving the children the children to their
+ * parents parent and removing the empty groups. Returns the children that
+ * have been removed from the groups.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of cells to be ungrouped. If null is specified then the
+ * selection cells are used.
+ */
+mxGraph.prototype.ungroupCells = function(cells)
+{
+	var result = [];
+	
+	if (cells == null)
+	{
+		cells = this.getSelectionCells();
+
+		// Finds the cells with children
+		var tmp = [];
+		
+		for (var i = 0; i < cells.length; i++)
+		{
+			if (this.model.getChildCount(cells[i]) > 0)
+			{
+				tmp.push(cells[i]);
+			}
+		}
+
+		cells = tmp;
+	}
+	
+	if (cells != null && cells.length > 0)
+	{
+		this.model.beginUpdate();
+		try
+		{
+			for (var i = 0; i < cells.length; i++)
+			{
+				var children = this.model.getChildren(cells[i]);
+				
+				if (children != null && children.length > 0)
+				{
+					children = children.slice();
+					var parent = this.model.getParent(cells[i]);
+					var index = this.model.getChildCount(parent);
+
+					this.cellsAdded(children, parent, index, null, null, true);
+					result = result.concat(children);
+				}
+			}
+
+			this.removeCellsAfterUngroup(cells);
+			this.fireEvent(new mxEventObject(mxEvent.UNGROUP_CELLS, 'cells', cells));
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: removeCellsAfterUngroup
+ * 
+ * Hook to remove the groups after <ungroupCells>.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> that were ungrouped.
+ */
+mxGraph.prototype.removeCellsAfterUngroup = function(cells)
+{
+	this.cellsRemoved(this.addAllEdges(cells));
+};
+
+/**
+ * Function: removeCellsFromParent
+ * 
+ * Removes the specified cells from their parents and adds them to the
+ * default parent. Returns the cells that were removed from their parents.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> to be removed from their parents.
+ */
+mxGraph.prototype.removeCellsFromParent = function(cells)
+{
+	if (cells == null)
+	{
+		cells = this.getSelectionCells();
+	}
+	
+	this.model.beginUpdate();
+	try
+	{
+		var parent = this.getDefaultParent();
+		var index = this.model.getChildCount(parent);
+
+		this.cellsAdded(cells, parent, index, null, null, true);
+		this.fireEvent(new mxEventObject(mxEvent.REMOVE_CELLS_FROM_PARENT, 'cells', cells));
+	}
+	finally
+	{
+		this.model.endUpdate();
+	}
+
+	return cells;
+};
+
+/**
+ * Function: updateGroupBounds
+ * 
+ * Updates the bounds of the given groups to include all children and returns
+ * the passed-in cells. Call this with the groups in parent to child order,
+ * top-most group first, the cells are processed in reverse order and cells
+ * with no children are ignored.
+ * 
+ * Parameters:
+ * 
+ * cells - The groups whose bounds should be updated. If this is null, then
+ * the selection cells are used.
+ * border - Optional border to be added in the group. Default is 0.
+ * moveGroup - Optional boolean that allows the group to be moved. Default
+ * is false.
+ * topBorder - Optional top border to be added in the group. Default is 0.
+ * rightBorder - Optional top border to be added in the group. Default is 0.
+ * bottomBorder - Optional top border to be added in the group. Default is 0.
+ * leftBorder - Optional top border to be added in the group. Default is 0.
+ */
+mxGraph.prototype.updateGroupBounds = function(cells, border, moveGroup, topBorder, rightBorder, bottomBorder, leftBorder)
+{
+	if (cells == null)
+	{
+		cells = this.getSelectionCells();
+	}
+	
+	border = (border != null) ? border : 0;
+	moveGroup = (moveGroup != null) ? moveGroup : false;
+	topBorder = (topBorder != null) ? topBorder : 0;
+	rightBorder = (rightBorder != null) ? rightBorder : 0;
+	bottomBorder = (bottomBorder != null) ? bottomBorder : 0;
+	leftBorder = (leftBorder != null) ? leftBorder : 0;
+
+	this.model.beginUpdate();
+	try
+	{
+		for (var i = cells.length - 1; i >= 0; i--)
+		{
+			var geo = this.getCellGeometry(cells[i]);
+			
+			if (geo != null)
+			{
+				var children = this.getChildCells(cells[i]);
+				
+				if (children != null && children.length > 0)
+				{
+					var bounds = this.getBoundingBoxFromGeometry(children, true);
+					
+					if (bounds != null && bounds.width > 0 && bounds.height > 0)
+					{
+						var left = 0;
+						var top = 0;
+						
+						// Adds the size of the title area for swimlanes
+						if (this.isSwimlane(cells[i]))
+						{
+							var size = this.getStartSize(cells[i]);
+							left = size.width;
+							top = size.height;
+						}
+						
+						geo = geo.clone();
+						
+						if (moveGroup)
+						{
+							geo.x = Math.round(geo.x + bounds.x - border - left - leftBorder);
+							geo.y = Math.round(geo.y + bounds.y - border - top - topBorder);
+						}
+						
+						geo.width = Math.round(bounds.width + 2 * border + left + leftBorder + rightBorder);
+						geo.height = Math.round(bounds.height + 2 * border + top + topBorder + bottomBorder);
+						
+						this.model.setGeometry(cells[i], geo);
+						this.moveCells(children, border + left - bounds.x + leftBorder,
+								border + top - bounds.y + topBorder);
+					}
+				}
+			}
+		}
+	}
+	finally
+	{
+		this.model.endUpdate();
+	}
+
+	return cells;
+};
+
+/**
+ * Function: getBoundingBox
+ * 
+ * Returns the bounding box for the given array of <mxCells>. The bounding box for
+ * each cell and its descendants is computed using <mxGraphView.getBoundingBox>.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> whose bounding box should be returned.
+ */
+mxGraph.prototype.getBoundingBox = function(cells)
+{
+	var result = null;
+	
+	if (cells != null && cells.length > 0)
+	{
+		for (var i = 0; i < cells.length; i++)
+		{
+			if (this.model.isVertex(cells[i]) || this.model.isEdge(cells[i]))
+			{
+				var bbox = this.view.getBoundingBox(this.view.getState(cells[i]), true);
+			
+				if (bbox != null)
+				{
+					if (result == null)
+					{
+						result = mxRectangle.fromRectangle(bbox);
+					}
+					else
+					{
+						result.add(bbox);
+					}
+				}
+			}
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Group: Cell cloning, insertion and removal
+ */
+
+/**
+ * Function: cloneCells
+ * 
+ * Returns the clones for the given cells. The clones are created recursively
+ * using <mxGraphModel.cloneCells>. If the terminal of an edge is not in the
+ * given array, then the respective end is assigned a terminal point and the
+ * terminal is removed.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> to be cloned.
+ * allowInvalidEdges - Optional boolean that specifies if invalid edges
+ * should be cloned. Default is true.
+ * mapping - Optional mapping for existing clones.
+ */
+mxGraph.prototype.cloneCells = function(cells, allowInvalidEdges, mapping)
+{
+	allowInvalidEdges = (allowInvalidEdges != null) ? allowInvalidEdges : true;
+	var clones = null;
+	
+	if (cells != null)
+	{
+		// Creates a dictionary for fast lookups
+		var dict = new mxDictionary();
+		var tmp = [];
+		
+		for (var i = 0; i < cells.length; i++)
+		{
+			dict.put(cells[i], true);
+			tmp.push(cells[i]);
+		}
+		
+		if (tmp.length > 0)
+		{
+			var scale = this.view.scale;
+			var trans = this.view.translate;
+			clones = this.model.cloneCells(cells, true, mapping);
+		
+			for (var i = 0; i < cells.length; i++)
+			{
+				if (!allowInvalidEdges && this.model.isEdge(clones[i]) &&
+					this.getEdgeValidationError(clones[i],
+						this.model.getTerminal(clones[i], true),
+						this.model.getTerminal(clones[i], false)) != null)
+				{
+					clones[i] = null;
+				}
+				else
+				{
+					var g = this.model.getGeometry(clones[i]);
+					
+					if (g != null)
+					{
+						var state = this.view.getState(cells[i]);
+						var pstate = this.view.getState(this.model.getParent(cells[i]));
+						
+						if (state != null && pstate != null)
+						{
+							var dx = pstate.origin.x;
+							var dy = pstate.origin.y;
+							
+							if (this.model.isEdge(clones[i]))
+							{
+								var pts = state.absolutePoints;
+								
+								// Checks if the source is cloned or sets the terminal point
+								var src = this.model.getTerminal(cells[i], true);
+								
+								while (src != null && !dict.get(src))
+								{
+									src = this.model.getParent(src);
+								}
+								
+								if (src == null)
+								{
+									g.setTerminalPoint(
+										new mxPoint(pts[0].x / scale - trans.x,
+											pts[0].y / scale - trans.y), true);
+								}
+								
+								// Checks if the target is cloned or sets the terminal point
+								var trg = this.model.getTerminal(cells[i], false);
+								
+								while (trg != null && !dict.get(trg))
+								{
+									trg = this.model.getParent(trg);
+								}
+								
+								if (trg == null)
+								{
+									var n = pts.length - 1;
+									g.setTerminalPoint(
+										new mxPoint(pts[n].x / scale - trans.x,
+											pts[n].y / scale - trans.y), false);
+								}
+								
+								// Translates the control points
+								var points = g.points;
+								
+								if (points != null)
+								{
+									for (var j = 0; j < points.length; j++)
+									{
+										points[j].x += dx;
+										points[j].y += dy;
+									}
+								}
+							}
+							else
+							{
+								g.translate(dx, dy);
+							}
+						}
+					}
+				}
+			}
+		}
+		else
+		{
+			clones = [];
+		}
+	}
+	
+	return clones;
+};
+
+/**
+ * Function: insertVertex
+ * 
+ * Adds a new vertex into the given parent <mxCell> using value as the user
+ * object and the given coordinates as the <mxGeometry> of the new vertex.
+ * The id and style are used for the respective properties of the new
+ * <mxCell>, which is returned.
+ *
+ * When adding new vertices from a mouse event, one should take into
+ * account the offset of the graph container and the scale and translation
+ * of the view in order to find the correct unscaled, untranslated
+ * coordinates using <mxGraph.getPointForEvent> as follows:
+ * 
+ * (code)
+ * var pt = graph.getPointForEvent(evt);
+ * var parent = graph.getDefaultParent();
+ * graph.insertVertex(parent, null,
+ * 			'Hello, World!', x, y, 220, 30);
+ * (end)
+ * 
+ * For adding image cells, the style parameter can be assigned as
+ * 
+ * (code)
+ * stylename;image=imageUrl
+ * (end)
+ * 
+ * See <mxGraph> for more information on using images.
+ *
+ * Parameters:
+ * 
+ * parent - <mxCell> that specifies the parent of the new vertex.
+ * id - Optional string that defines the Id of the new vertex.
+ * value - Object to be used as the user object.
+ * x - Integer that defines the x coordinate of the vertex.
+ * y - Integer that defines the y coordinate of the vertex.
+ * width - Integer that defines the width of the vertex.
+ * height - Integer that defines the height of the vertex.
+ * style - Optional string that defines the cell style.
+ * relative - Optional boolean that specifies if the geometry is relative.
+ * Default is false.
+ */
+mxGraph.prototype.insertVertex = function(parent, id, value,
+	x, y, width, height, style, relative)
+{
+	var vertex = this.createVertex(parent, id, value, x, y, width, height, style, relative);
+
+	return this.addCell(vertex, parent);
+};
+
+/**
+ * Function: createVertex
+ * 
+ * Hook method that creates the new vertex for <insertVertex>.
+ */
+mxGraph.prototype.createVertex = function(parent, id, value,
+		x, y, width, height, style, relative)
+{
+	// Creates the geometry for the vertex
+	var geometry = new mxGeometry(x, y, width, height);
+	geometry.relative = (relative != null) ? relative : false;
+	
+	// Creates the vertex
+	var vertex = new mxCell(value, geometry, style);
+	vertex.setId(id);
+	vertex.setVertex(true);
+	vertex.setConnectable(true);
+	
+	return vertex;
+};
+	
+/**
+ * Function: insertEdge
+ * 
+ * Adds a new edge into the given parent <mxCell> using value as the user
+ * object and the given source and target as the terminals of the new edge.
+ * The id and style are used for the respective properties of the new
+ * <mxCell>, which is returned.
+ *
+ * Parameters:
+ * 
+ * parent - <mxCell> that specifies the parent of the new edge.
+ * id - Optional string that defines the Id of the new edge.
+ * value - JavaScript object to be used as the user object.
+ * source - <mxCell> that defines the source of the edge.
+ * target - <mxCell> that defines the target of the edge.
+ * style - Optional string that defines the cell style.
+ */
+mxGraph.prototype.insertEdge = function(parent, id, value, source, target, style)
+{
+	var edge = this.createEdge(parent, id, value, source, target, style);
+	
+	return this.addEdge(edge, parent, source, target);
+};
+
+/**
+ * Function: createEdge
+ * 
+ * Hook method that creates the new edge for <insertEdge>. This
+ * implementation does not set the source and target of the edge, these
+ * are set when the edge is added to the model.
+ * 
+ */
+mxGraph.prototype.createEdge = function(parent, id, value, source, target, style)
+{
+	// Creates the edge
+	var edge = new mxCell(value, new mxGeometry(), style);
+	edge.setId(id);
+	edge.setEdge(true);
+	edge.geometry.relative = true;
+	
+	return edge;
+};
+
+/**
+ * Function: addEdge
+ * 
+ * Adds the edge to the parent and connects it to the given source and
+ * target terminals. This is a shortcut method. Returns the edge that was
+ * added.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> to be inserted into the given parent.
+ * parent - <mxCell> that represents the new parent. If no parent is
+ * given then the default parent is used.
+ * source - Optional <mxCell> that represents the source terminal.
+ * target - Optional <mxCell> that represents the target terminal.
+ * index - Optional index to insert the cells at. Default is to append.
+ */
+mxGraph.prototype.addEdge = function(edge, parent, source, target, index)
+{
+	return this.addCell(edge, parent, index, source, target);
+};
+
+/**
+ * Function: addCell
+ * 
+ * Adds the cell to the parent and connects it to the given source and
+ * target terminals. This is a shortcut method. Returns the cell that was
+ * added.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to be inserted into the given parent.
+ * parent - <mxCell> that represents the new parent. If no parent is
+ * given then the default parent is used.
+ * index - Optional index to insert the cells at. Default is to append.
+ * source - Optional <mxCell> that represents the source terminal.
+ * target - Optional <mxCell> that represents the target terminal.
+ */
+mxGraph.prototype.addCell = function(cell, parent, index, source, target)
+{
+	return this.addCells([cell], parent, index, source, target)[0];
+};
+
+/**
+ * Function: addCells
+ * 
+ * Adds the cells to the parent at the given index, connecting each cell to
+ * the optional source and target terminal. The change is carried out using
+ * <cellsAdded>. This method fires <mxEvent.ADD_CELLS> while the
+ * transaction is in progress. Returns the cells that were added.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> to be inserted.
+ * parent - <mxCell> that represents the new parent. If no parent is
+ * given then the default parent is used.
+ * index - Optional index to insert the cells at. Default is to append.
+ * source - Optional source <mxCell> for all inserted cells.
+ * target - Optional target <mxCell> for all inserted cells.
+ */
+mxGraph.prototype.addCells = function(cells, parent, index, source, target)
+{
+	if (parent == null)
+	{
+		parent = this.getDefaultParent();
+	}
+	
+	if (index == null)
+	{
+		index = this.model.getChildCount(parent);
+	}
+	
+	this.model.beginUpdate();
+	try
+	{
+		this.cellsAdded(cells, parent, index, source, target, false, true);
+		this.fireEvent(new mxEventObject(mxEvent.ADD_CELLS, 'cells', cells,
+				'parent', parent, 'index', index, 'source', source, 'target', target));
+	}
+	finally
+	{
+		this.model.endUpdate();
+	}
+
+	return cells;
+};
+
+/**
+ * Function: cellsAdded
+ * 
+ * Adds the specified cells to the given parent. This method fires
+ * <mxEvent.CELLS_ADDED> while the transaction is in progress.
+ */
+mxGraph.prototype.cellsAdded = function(cells, parent, index, source, target, absolute, constrain, extend)
+{
+	if (cells != null && parent != null && index != null)
+	{
+		this.model.beginUpdate();
+		try
+		{
+			var parentState = (absolute) ? this.view.getState(parent) : null;
+			var o1 = (parentState != null) ? parentState.origin : null;
+			var zero = new mxPoint(0, 0);
+
+			for (var i = 0; i < cells.length; i++)
+			{
+				if (cells[i] == null)
+				{
+					index--;
+				}
+				else
+				{
+					var previous = this.model.getParent(cells[i]);
+	
+					// Keeps the cell at its absolute location
+					if (o1 != null && cells[i] != parent && parent != previous)
+					{
+						var oldState = this.view.getState(previous);
+						var o2 = (oldState != null) ? oldState.origin : zero;
+						var geo = this.model.getGeometry(cells[i]);
+	
+						if (geo != null)
+						{
+							var dx = o2.x - o1.x;
+							var dy = o2.y - o1.y;
+	
+							// FIXME: Cells should always be inserted first before any other edit
+							// to avoid forward references in sessions.
+							geo = geo.clone();
+							geo.translate(dx, dy);
+							
+							if (!geo.relative && this.model.isVertex(cells[i]) &&
+								!this.isAllowNegativeCoordinates())
+							{
+								geo.x = Math.max(0, geo.x);
+								geo.y = Math.max(0, geo.y);
+							}
+							
+							this.model.setGeometry(cells[i], geo);
+						}
+					}
+	
+					// Decrements all following indices
+					// if cell is already in parent
+					if (parent == previous && index + i > this.model.getChildCount(parent))
+					{
+						index--;
+					}
+
+					this.model.add(parent, cells[i], index + i);
+					
+					if (this.autoSizeCellsOnAdd)
+					{
+						this.autoSizeCell(cells[i], true);
+					}
+
+					// Extends the parent or constrains the child
+					if ((extend == null || extend) &&
+						this.isExtendParentsOnAdd(cells[i]) && this.isExtendParent(cells[i]))
+					{
+						this.extendParent(cells[i]);
+					}
+					
+					// Additionally constrains the child after extending the parent
+					if (constrain == null || constrain)
+					{
+						this.constrainChild(cells[i]);
+					}
+					
+					// Sets the source terminal
+					if (source != null)
+					{
+						this.cellConnected(cells[i], source, true);
+					}
+					
+					// Sets the target terminal
+					if (target != null)
+					{
+						this.cellConnected(cells[i], target, false);
+					}
+				}
+			}
+			
+			this.fireEvent(new mxEventObject(mxEvent.CELLS_ADDED, 'cells', cells,
+				'parent', parent, 'index', index, 'source', source, 'target', target,
+				'absolute', absolute));
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: autoSizeCell
+ * 
+ * Resizes the specified cell to just fit around the its label and/or children
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCells> to be resized.
+ * recurse - Optional boolean which specifies if all descendants should be
+ * autosized. Default is true.
+ */
+mxGraph.prototype.autoSizeCell = function(cell, recurse)
+{
+	recurse = (recurse != null) ? recurse : true;
+	
+	if (recurse)
+	{
+		var childCount = this.model.getChildCount(cell);
+		
+		for (var i = 0; i < childCount; i++)
+		{
+			this.autoSizeCell(this.model.getChildAt(cell, i));
+		}
+	}
+
+	if (this.getModel().isVertex(cell) && this.isAutoSizeCell(cell))
+	{
+		this.updateCellSize(cell);
+	}
+};
+
+/**
+ * Function: removeCells
+ * 
+ * Removes the given cells from the graph including all connected edges if
+ * includeEdges is true. The change is carried out using <cellsRemoved>.
+ * This method fires <mxEvent.REMOVE_CELLS> while the transaction is in
+ * progress. The removed cells are returned as an array.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> to remove. If null is specified then the
+ * selection cells which are deletable are used.
+ * includeEdges - Optional boolean which specifies if all connected edges
+ * should be removed as well. Default is true.
+ */
+mxGraph.prototype.removeCells = function(cells, includeEdges)
+{
+	includeEdges = (includeEdges != null) ? includeEdges : true;
+	
+	if (cells == null)
+	{
+		cells = this.getDeletableCells(this.getSelectionCells());
+	}
+
+	// Adds all edges to the cells
+	if (includeEdges)
+	{
+		// FIXME: Remove duplicate cells in result or do not add if
+		// in cells or descendant of cells
+		cells = this.getDeletableCells(this.addAllEdges(cells));
+	}
+
+	this.model.beginUpdate();
+	try
+	{
+		this.cellsRemoved(cells);
+		this.fireEvent(new mxEventObject(mxEvent.REMOVE_CELLS, 
+				'cells', cells, 'includeEdges', includeEdges));
+	}
+	finally
+	{
+		this.model.endUpdate();
+	}
+	
+	return cells;
+};
+
+/**
+ * Function: cellsRemoved
+ * 
+ * Removes the given cells from the model. This method fires
+ * <mxEvent.CELLS_REMOVED> while the transaction is in progress.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> to remove.
+ */
+mxGraph.prototype.cellsRemoved = function(cells)
+{
+	if (cells != null && cells.length > 0)
+	{
+		var scale = this.view.scale;
+		var tr = this.view.translate;
+		
+		this.model.beginUpdate();
+		try
+		{
+			// Creates hashtable for faster lookup
+			var dict = new mxDictionary();
+			
+			for (var i = 0; i < cells.length; i++)
+			{
+				dict.put(cells[i], true);
+			}
+			
+			for (var i = 0; i < cells.length; i++)
+			{
+				// Disconnects edges which are not in cells
+				var edges = this.getAllEdges([cells[i]]);
+				
+				var disconnectTerminal = mxUtils.bind(this, function(edge, source)
+				{
+					var geo = this.model.getGeometry(edge);
+
+					if (geo != null)
+					{
+						var state = this.view.getState(edge);
+								
+						if (state != null)
+						{
+							// Checks which side of the edge is being disconnected
+							var tmp = state.getVisibleTerminal(source);
+							var connected = false;
+							
+							while (tmp != null)
+							{
+								if (cells[i] == tmp)
+								{
+									connected = true;
+									break;
+								}
+								
+								tmp = this.model.getParent(tmp);
+							}
+							
+							if (connected)
+							{
+								var dx = tr.x;
+								var dy = tr.y;
+								var parentState = this.view.getState(this.model.getParent(edge));
+								
+								if (parentState != null && this.model.isVertex(parentState.cell))
+								{
+									dx = parentState.x / scale;
+									dy = parentState.y / scale;
+								}
+								
+								geo = geo.clone();
+								var pts = state.absolutePoints;
+								var n = (source) ? 0 : pts.length - 1;
+								geo.setTerminalPoint(new mxPoint(pts[n].x / scale - dx, pts[n].y / scale - dy), source);
+								this.model.setTerminal(edges[j], null, source);
+								this.model.setGeometry(edges[j], geo);
+							}
+						}
+					}
+				});
+				
+				for (var j = 0; j < edges.length; j++)
+				{
+					if (!dict.get(edges[j]))
+					{
+						disconnectTerminal(edges[j], true);
+						disconnectTerminal(edges[j], false);
+					}
+				}
+
+				this.model.remove(cells[i]);
+			}
+			
+			this.fireEvent(new mxEventObject(mxEvent.CELLS_REMOVED, 'cells', cells));
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: splitEdge
+ * 
+ * Splits the given edge by adding the newEdge between the previous source
+ * and the given cell and reconnecting the source of the given edge to the
+ * given cell. This method fires <mxEvent.SPLIT_EDGE> while the transaction
+ * is in progress. Returns the new edge that was inserted.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> that represents the edge to be splitted.
+ * cells - <mxCells> that represents the cells to insert into the edge.
+ * newEdge - <mxCell> that represents the edge to be inserted.
+ * dx - Optional integer that specifies the vector to move the cells.
+ * dy - Optional integer that specifies the vector to move the cells.
+ */
+mxGraph.prototype.splitEdge = function(edge, cells, newEdge, dx, dy)
+{
+	dx = dx || 0;
+	dy = dy || 0;
+
+	var parent = this.model.getParent(edge);
+	var source = this.model.getTerminal(edge, true);
+
+	this.model.beginUpdate();
+	try
+	{
+		if (newEdge == null)
+		{
+			newEdge = this.cloneCells([edge])[0];
+			
+			// Removes waypoints before/after new cell
+			var state = this.view.getState(edge);
+			var geo = this.getCellGeometry(newEdge);
+			
+			if (geo != null && geo.points != null && state != null)
+			{
+				var t = this.view.translate;
+				var s = this.view.scale;
+				var idx = mxUtils.findNearestSegment(state, (dx + t.x) * s, (dy + t.y) * s);
+				geo.points = geo.points.slice(0, idx);
+								
+				geo = this.getCellGeometry(edge);
+				
+				if (geo != null && geo.points != null)
+				{
+					geo = geo.clone();
+					geo.points = geo.points.slice(idx);
+					this.model.setGeometry(edge, geo);
+				}
+			}
+		}
+		
+		this.cellsMoved(cells, dx, dy, false, false);
+		this.cellsAdded(cells, parent, this.model.getChildCount(parent), null, null,
+				true);
+		this.cellsAdded([newEdge], parent, this.model.getChildCount(parent),
+				source, cells[0], false);
+		this.cellConnected(edge, cells[0], true);
+		this.fireEvent(new mxEventObject(mxEvent.SPLIT_EDGE, 'edge', edge,
+				'cells', cells, 'newEdge', newEdge, 'dx', dx, 'dy', dy));
+	}
+	finally
+	{
+		this.model.endUpdate();
+	}
+
+	return newEdge;
+};
+
+/**
+ * Group: Cell visibility
+ */
+
+/**
+ * Function: toggleCells
+ * 
+ * Sets the visible state of the specified cells and all connected edges
+ * if includeEdges is true. The change is carried out using <cellsToggled>.
+ * This method fires <mxEvent.TOGGLE_CELLS> while the transaction is in
+ * progress. Returns the cells whose visible state was changed.
+ * 
+ * Parameters:
+ * 
+ * show - Boolean that specifies the visible state to be assigned.
+ * cells - Array of <mxCells> whose visible state should be changed. If
+ * null is specified then the selection cells are used.
+ * includeEdges - Optional boolean indicating if the visible state of all
+ * connected edges should be changed as well. Default is true.
+ */
+mxGraph.prototype.toggleCells = function(show, cells, includeEdges)
+{
+	if (cells == null)
+	{
+		cells = this.getSelectionCells();
+	}
+
+	// Adds all connected edges recursively
+	if (includeEdges)
+	{
+		cells = this.addAllEdges(cells);
+	}
+
+	this.model.beginUpdate();
+	try
+	{
+		this.cellsToggled(cells, show);
+		this.fireEvent(new mxEventObject(mxEvent.TOGGLE_CELLS,
+			'show', show, 'cells', cells, 'includeEdges', includeEdges));
+	}
+	finally
+	{
+		this.model.endUpdate();
+	}
+
+	return cells;
+};
+
+/**
+ * Function: cellsToggled
+ * 
+ * Sets the visible state of the specified cells.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> whose visible state should be changed.
+ * show - Boolean that specifies the visible state to be assigned.
+ */
+mxGraph.prototype.cellsToggled = function(cells, show)
+{
+	if (cells != null && cells.length > 0)
+	{
+		this.model.beginUpdate();
+		try
+		{
+			for (var i = 0; i < cells.length; i++)
+			{
+				this.model.setVisible(cells[i], show);
+			}
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Group: Folding
+ */
+
+/**
+ * Function: foldCells
+ * 
+ * Sets the collapsed state of the specified cells and all descendants
+ * if recurse is true. The change is carried out using <cellsFolded>.
+ * This method fires <mxEvent.FOLD_CELLS> while the transaction is in
+ * progress. Returns the cells whose collapsed state was changed.
+ * 
+ * Parameters:
+ * 
+ * collapsed - Boolean indicating the collapsed state to be assigned.
+ * recurse - Optional boolean indicating if the collapsed state of all
+ * descendants should be set. Default is false.
+ * cells - Array of <mxCells> whose collapsed state should be set. If
+ * null is specified then the foldable selection cells are used.
+ * checkFoldable - Optional boolean indicating of isCellFoldable should be
+ * checked. Default is false.
+ * evt - Optional native event that triggered the invocation.
+ */
+mxGraph.prototype.foldCells = function(collapse, recurse, cells, checkFoldable, evt)
+{
+	recurse = (recurse != null) ? recurse : false;
+	
+	if (cells == null)
+	{
+		cells = this.getFoldableCells(this.getSelectionCells(), collapse);
+	}
+
+	this.stopEditing(false);
+
+	this.model.beginUpdate();
+	try
+	{
+		this.cellsFolded(cells, collapse, recurse, checkFoldable);
+		this.fireEvent(new mxEventObject(mxEvent.FOLD_CELLS,
+			'collapse', collapse, 'recurse', recurse, 'cells', cells));
+	}
+	finally
+	{
+		this.model.endUpdate();
+	}
+
+	return cells;
+};
+
+/**
+ * Function: cellsFolded
+ * 
+ * Sets the collapsed state of the specified cells. This method fires
+ * <mxEvent.CELLS_FOLDED> while the transaction is in progress. Returns the
+ * cells whose collapsed state was changed.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> whose collapsed state should be set.
+ * collapsed - Boolean indicating the collapsed state to be assigned.
+ * recurse - Boolean indicating if the collapsed state of all descendants
+ * should be set.
+ * checkFoldable - Optional boolean indicating of isCellFoldable should be
+ * checked. Default is false.
+ */
+mxGraph.prototype.cellsFolded = function(cells, collapse, recurse, checkFoldable)
+{
+	if (cells != null && cells.length > 0)
+	{
+		this.model.beginUpdate();
+		try
+		{
+			for (var i = 0; i < cells.length; i++)
+			{
+				if ((!checkFoldable || this.isCellFoldable(cells[i], collapse)) &&
+					collapse != this.isCellCollapsed(cells[i]))
+				{
+					this.model.setCollapsed(cells[i], collapse);
+					this.swapBounds(cells[i], collapse);
+
+					if (this.isExtendParent(cells[i]))
+					{
+						this.extendParent(cells[i]);
+					}
+
+					if (recurse)
+					{
+						var children = this.model.getChildren(cells[i]);
+						this.foldCells(children, collapse, recurse);
+					}
+					
+					this.constrainChild(cells[i]);
+				}
+			}
+			
+			this.fireEvent(new mxEventObject(mxEvent.CELLS_FOLDED,
+				'cells', cells, 'collapse', collapse, 'recurse', recurse));
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: swapBounds
+ * 
+ * Swaps the alternate and the actual bounds in the geometry of the given
+ * cell invoking <updateAlternateBounds> before carrying out the swap.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> for which the bounds should be swapped.
+ * willCollapse - Boolean indicating if the cell is going to be collapsed.
+ */
+mxGraph.prototype.swapBounds = function(cell, willCollapse)
+{
+	if (cell != null)
+	{
+		var geo = this.model.getGeometry(cell);
+		
+		if (geo != null)
+		{
+			geo = geo.clone();
+			
+			this.updateAlternateBounds(cell, geo, willCollapse);
+			geo.swap();
+			
+			this.model.setGeometry(cell, geo);
+		}
+	}
+};
+
+/**
+ * Function: updateAlternateBounds
+ * 
+ * Updates or sets the alternate bounds in the given geometry for the given
+ * cell depending on whether the cell is going to be collapsed. If no
+ * alternate bounds are defined in the geometry and
+ * <collapseToPreferredSize> is true, then the preferred size is used for
+ * the alternate bounds. The top, left corner is always kept at the same
+ * location.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> for which the geometry is being udpated.
+ * g - <mxGeometry> for which the alternate bounds should be updated.
+ * willCollapse - Boolean indicating if the cell is going to be collapsed.
+ */
+mxGraph.prototype.updateAlternateBounds = function(cell, geo, willCollapse)
+{
+	if (cell != null && geo != null)
+	{
+		var state = this.view.getState(cell);
+		var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+		if (geo.alternateBounds == null)
+		{
+			var bounds = geo;
+			
+			if (this.collapseToPreferredSize)
+			{
+				var tmp = this.getPreferredSizeForCell(cell);
+				
+				if (tmp != null)
+				{
+					bounds = tmp;
+
+					var startSize = mxUtils.getValue(style, mxConstants.STYLE_STARTSIZE);
+
+					if (startSize > 0)
+					{
+						bounds.height = Math.max(bounds.height, startSize);
+					}
+				}
+			}
+			
+			geo.alternateBounds = new mxRectangle(0, 0, bounds.width, bounds.height);
+		}
+		
+		if (geo.alternateBounds != null)
+		{
+			geo.alternateBounds.x = geo.x;
+			geo.alternateBounds.y = geo.y;
+			
+			var alpha = mxUtils.toRadians(style[mxConstants.STYLE_ROTATION] || 0);
+			
+			if (alpha != 0)
+			{
+				var dx = geo.alternateBounds.getCenterX() - geo.getCenterX();
+				var dy = geo.alternateBounds.getCenterY() - geo.getCenterY();
+	
+				var cos = Math.cos(alpha);
+				var sin = Math.sin(alpha);
+	
+				var dx2 = cos * dx - sin * dy;
+				var dy2 = sin * dx + cos * dy;
+				
+				geo.alternateBounds.x += dx2 - dx;
+				geo.alternateBounds.y += dy2 - dy;
+			}
+		}
+	}
+};
+
+/**
+ * Function: addAllEdges
+ * 
+ * Returns an array with the given cells and all edges that are connected
+ * to a cell or one of its descendants.
+ */
+mxGraph.prototype.addAllEdges = function(cells)
+{
+	var allCells = cells.slice();
+	
+	return mxUtils.removeDuplicates(allCells.concat(this.getAllEdges(cells)));
+};
+
+/**
+ * Function: getAllEdges
+ * 
+ * Returns all edges connected to the given cells or its descendants.
+ */
+mxGraph.prototype.getAllEdges = function(cells)
+{
+	var edges = [];
+	
+	if (cells != null)
+	{
+		for (var i = 0; i < cells.length; i++)
+		{
+			var edgeCount = this.model.getEdgeCount(cells[i]);
+			
+			for (var j = 0; j < edgeCount; j++)
+			{
+				edges.push(this.model.getEdgeAt(cells[i], j));
+			}
+
+			// Recurses
+			var children = this.model.getChildren(cells[i]);
+			edges = edges.concat(this.getAllEdges(children));
+		}
+	}
+	
+	return edges;
+};
+
+/**
+ * Group: Cell sizing
+ */
+
+/**
+ * Function: updateCellSize
+ * 
+ * Updates the size of the given cell in the model using <cellSizeUpdated>.
+ * This method fires <mxEvent.UPDATE_CELL_SIZE> while the transaction is in
+ * progress. Returns the cell whose size was updated.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose size should be updated.
+ */
+mxGraph.prototype.updateCellSize = function(cell, ignoreChildren)
+{
+	ignoreChildren = (ignoreChildren != null) ? ignoreChildren : false;
+	
+	this.model.beginUpdate();				
+	try
+	{
+		this.cellSizeUpdated(cell, ignoreChildren);
+		this.fireEvent(new mxEventObject(mxEvent.UPDATE_CELL_SIZE,
+				'cell', cell, 'ignoreChildren', ignoreChildren));
+	}
+	finally
+	{
+		this.model.endUpdate();
+	}
+	
+	return cell;
+};
+
+/**
+ * Function: cellSizeUpdated
+ * 
+ * Updates the size of the given cell in the model using
+ * <getPreferredSizeForCell> to get the new size.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> for which the size should be changed.
+ */
+mxGraph.prototype.cellSizeUpdated = function(cell, ignoreChildren)
+{
+	if (cell != null)
+	{
+		this.model.beginUpdate();				
+		try
+		{
+			var size = this.getPreferredSizeForCell(cell);
+			var geo = this.model.getGeometry(cell);
+			
+			if (size != null && geo != null)
+			{
+				var collapsed = this.isCellCollapsed(cell);
+				geo = geo.clone();
+
+				if (this.isSwimlane(cell))
+				{
+					var state = this.view.getState(cell);
+					var style = (state != null) ? state.style : this.getCellStyle(cell);
+					var cellStyle = this.model.getStyle(cell);
+
+					if (cellStyle == null)
+					{
+						cellStyle = '';
+					}
+
+					if (mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, true))
+					{
+						cellStyle = mxUtils.setStyle(cellStyle,
+								mxConstants.STYLE_STARTSIZE, size.height + 8);
+
+						if (collapsed)
+						{
+							geo.height = size.height + 8;
+						}
+
+						geo.width = size.width;
+					}
+					else
+					{
+						cellStyle = mxUtils.setStyle(cellStyle,
+								mxConstants.STYLE_STARTSIZE, size.width + 8);
+
+						if (collapsed)
+						{
+							geo.width = size.width + 8;
+						}
+
+						geo.height = size.height;
+					}
+
+					this.model.setStyle(cell, cellStyle);
+				}
+				else
+				{
+					geo.width = size.width;
+					geo.height = size.height;
+				}
+
+				if (!ignoreChildren && !collapsed)
+				{
+					var bounds = this.view.getBounds(this.model.getChildren(cell));
+
+					if (bounds != null)
+					{
+						var tr = this.view.translate;
+						var scale = this.view.scale;
+
+						var width = (bounds.x + bounds.width) / scale - geo.x - tr.x;
+						var height = (bounds.y + bounds.height) / scale - geo.y - tr.y;
+
+						geo.width = Math.max(geo.width, width);
+						geo.height = Math.max(geo.height, height);
+					}
+				}
+
+				this.cellsResized([cell], [geo], false);
+			}
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: getPreferredSizeForCell
+ * 
+ * Returns the preferred width and height of the given <mxCell> as an
+ * <mxRectangle>. To implement a minimum width, add a new style eg.
+ * minWidth in the vertex and override this method as follows.
+ * 
+ * (code)
+ * var graphGetPreferredSizeForCell = graph.getPreferredSizeForCell;
+ * graph.getPreferredSizeForCell = function(cell)
+ * {
+ *   var result = graphGetPreferredSizeForCell.apply(this, arguments);
+ *   var style = this.getCellStyle(cell);
+ *   
+ *   if (style['minWidth'] > 0)
+ *   {
+ *     result.width = Math.max(style['minWidth'], result.width);
+ *   }
+ * 
+ *   return result;
+ * };
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> for which the preferred size should be returned.
+ */
+mxGraph.prototype.getPreferredSizeForCell = function(cell)
+{
+	var result = null;
+	
+	if (cell != null)
+	{
+		var state = this.view.getState(cell) || this.view.createState(cell);
+		var style = state.style;
+
+		if (!this.model.isEdge(cell))
+		{
+			var fontSize = style[mxConstants.STYLE_FONTSIZE] || mxConstants.DEFAULT_FONTSIZE;
+			var dx = 0;
+			var dy = 0;
+			
+			// Adds dimension of image if shape is a label
+			if (this.getImage(state) != null || style[mxConstants.STYLE_IMAGE] != null)
+			{
+				if (style[mxConstants.STYLE_SHAPE] == mxConstants.SHAPE_LABEL)
+				{
+					if (style[mxConstants.STYLE_VERTICAL_ALIGN] == mxConstants.ALIGN_MIDDLE)
+					{
+						dx += parseFloat(style[mxConstants.STYLE_IMAGE_WIDTH]) || mxLabel.prototype.imageSize;
+					}
+					
+					if (style[mxConstants.STYLE_ALIGN] != mxConstants.ALIGN_CENTER)
+					{
+						dy += parseFloat(style[mxConstants.STYLE_IMAGE_HEIGHT]) || mxLabel.prototype.imageSize;
+					}
+				}
+			}
+
+			// Adds spacings
+			dx += 2 * (style[mxConstants.STYLE_SPACING] || 0);
+			dx += style[mxConstants.STYLE_SPACING_LEFT] || 0;
+			dx += style[mxConstants.STYLE_SPACING_RIGHT] || 0;
+
+			dy += 2 * (style[mxConstants.STYLE_SPACING] || 0);
+			dy += style[mxConstants.STYLE_SPACING_TOP] || 0;
+			dy += style[mxConstants.STYLE_SPACING_BOTTOM] || 0;
+			
+			// Add spacing for collapse/expand icon
+			// LATER: Check alignment and use constants
+			// for image spacing
+			var image = this.getFoldingImage(state);
+			
+			if (image != null)
+			{
+				dx += image.width + 8;
+			}
+
+			// Adds space for label
+			var value = this.cellRenderer.getLabelValue(state);
+
+			if (value != null && value.length > 0)
+			{
+				if (!this.isHtmlLabel(state.cell))
+				{
+					value = mxUtils.htmlEntities(value);
+				}
+				
+				value = value.replace(/\n/g, '<br>');
+				
+				var size = mxUtils.getSizeForString(value, fontSize, style[mxConstants.STYLE_FONTFAMILY]);
+				var width = size.width + dx;
+				var height = size.height + dy;
+				
+				if (!mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, true))
+				{
+					var tmp = height;
+					
+					height = width;
+					width = tmp;
+				}
+			
+				if (this.gridEnabled)
+				{
+					width = this.snap(width + this.gridSize / 2);
+					height = this.snap(height + this.gridSize / 2);
+				}
+
+				result = new mxRectangle(0, 0, width, height);
+			}
+			else
+			{
+				var gs2 = 4 * this.gridSize;
+				result = new mxRectangle(0, 0, gs2, gs2);
+			}
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: resizeCell
+ * 
+ * Sets the bounds of the given cell using <resizeCells>. Returns the
+ * cell which was passed to the function.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose bounds should be changed.
+ * bounds - <mxRectangle> that represents the new bounds.
+ */
+mxGraph.prototype.resizeCell = function(cell, bounds, recurse)
+{
+	return this.resizeCells([cell], [bounds], recurse)[0];
+};
+
+/**
+ * Function: resizeCells
+ * 
+ * Sets the bounds of the given cells and fires a <mxEvent.RESIZE_CELLS>
+ * event while the transaction is in progress. Returns the cells which
+ * have been passed to the function.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> whose bounds should be changed.
+ * bounds - Array of <mxRectangles> that represent the new bounds.
+ */
+mxGraph.prototype.resizeCells = function(cells, bounds, recurse)
+{
+	recurse = (recurse != null) ? recurse : this.isRecursiveResize();
+	
+	this.model.beginUpdate();
+	try
+	{
+		this.cellsResized(cells, bounds, recurse);
+		this.fireEvent(new mxEventObject(mxEvent.RESIZE_CELLS,
+				'cells', cells, 'bounds', bounds));
+	}
+	finally
+	{
+		this.model.endUpdate();
+	}
+
+	return cells;
+};
+
+/**
+ * Function: cellsResized
+ * 
+ * Sets the bounds of the given cells and fires a <mxEvent.CELLS_RESIZED>
+ * event. If <extendParents> is true, then the parent is extended if a
+ * child size is changed so that it overlaps with the parent.
+ * 
+ * The following example shows how to control group resizes to make sure
+ * that all child cells stay within the group.
+ * 
+ * (code)
+ * graph.addListener(mxEvent.CELLS_RESIZED, function(sender, evt)
+ * {
+ *   var cells = evt.getProperty('cells');
+ *   
+ *   if (cells != null)
+ *   {
+ *     for (var i = 0; i < cells.length; i++)
+ *     {
+ *       if (graph.getModel().getChildCount(cells[i]) > 0)
+ *       {
+ *         var geo = graph.getCellGeometry(cells[i]);
+ *         
+ *         if (geo != null)
+ *         {
+ *           var children = graph.getChildCells(cells[i], true, true);
+ *           var bounds = graph.getBoundingBoxFromGeometry(children, true);
+ *           
+ *           geo = geo.clone();
+ *           geo.width = Math.max(geo.width, bounds.width);
+ *           geo.height = Math.max(geo.height, bounds.height);
+ *           
+ *           graph.getModel().setGeometry(cells[i], geo);
+ *         }
+ *       }
+ *     }
+ *   }
+ * });
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> whose bounds should be changed.
+ * bounds - Array of <mxRectangles> that represent the new bounds.
+ * recurse - Optional boolean that specifies if the children should be resized.
+ */
+mxGraph.prototype.cellsResized = function(cells, bounds, recurse)
+{
+	recurse = (recurse != null) ? recurse : false;
+	
+	if (cells != null && bounds != null && cells.length == bounds.length)
+	{
+		this.model.beginUpdate();
+		try
+		{
+			for (var i = 0; i < cells.length; i++)
+			{
+				this.cellResized(cells[i], bounds[i], false, recurse);
+
+				if (this.isExtendParent(cells[i]))
+				{
+					this.extendParent(cells[i]);
+				}
+				
+				this.constrainChild(cells[i]);
+			}
+
+			if (this.resetEdgesOnResize)
+			{
+				this.resetEdges(cells);
+			}
+			
+			this.fireEvent(new mxEventObject(mxEvent.CELLS_RESIZED,
+					'cells', cells, 'bounds', bounds));
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: cellResized
+ * 
+ * Resizes the parents recursively so that they contain the complete area
+ * of the resized child cell.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose bounds should be changed.
+ * bounds - <mxRectangles> that represent the new bounds.
+ * ignoreRelative - Boolean that indicates if relative cells should be ignored.
+ * recurse - Optional boolean that specifies if the children should be resized.
+ */
+mxGraph.prototype.cellResized = function(cell, bounds, ignoreRelative, recurse)
+{
+	var geo = this.model.getGeometry(cell);
+
+	if (geo != null && (geo.x != bounds.x || geo.y != bounds.y ||
+		geo.width != bounds.width || geo.height != bounds.height))
+	{
+		geo = geo.clone();
+
+		if (!ignoreRelative && geo.relative)
+		{
+			var offset = geo.offset;
+
+			if (offset != null)
+			{
+				offset.x += bounds.x - geo.x;
+				offset.y += bounds.y - geo.y;
+			}
+		}
+		else
+		{
+			geo.x = bounds.x;
+			geo.y = bounds.y;
+		}
+
+		geo.width = bounds.width;
+		geo.height = bounds.height;
+
+		if (!geo.relative && this.model.isVertex(cell) && !this.isAllowNegativeCoordinates())
+		{
+			geo.x = Math.max(0, geo.x);
+			geo.y = Math.max(0, geo.y);
+		}
+
+		this.model.beginUpdate();
+		try
+		{
+			if (recurse)
+			{
+				this.resizeChildCells(cell, geo);
+			}
+						
+			this.model.setGeometry(cell, geo);
+			this.constrainChildCells(cell);
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: resizeChildCells
+ * 
+ * Resizes the child cells of the given cell for the given new geometry with
+ * respect to the current geometry of the cell.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that has been resized.
+ * newGeo - <mxGeometry> that represents the new bounds.
+ */
+mxGraph.prototype.resizeChildCells = function(cell, newGeo)
+{
+	var geo = this.model.getGeometry(cell);
+	var dx = newGeo.width / geo.width;
+	var dy = newGeo.height / geo.height;
+	var childCount = this.model.getChildCount(cell);
+	
+	for (var i = 0; i < childCount; i++)
+	{
+		this.scaleCell(this.model.getChildAt(cell, i), dx, dy, true);
+	}
+};
+
+/**
+ * Function: constrainChildCells
+ * 
+ * Constrains the children of the given cell using <constrainChild>.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that has been resized.
+ */
+mxGraph.prototype.constrainChildCells = function(cell)
+{
+	var childCount = this.model.getChildCount(cell);
+	
+	for (var i = 0; i < childCount; i++)
+	{
+		this.constrainChild(this.model.getChildAt(cell, i));
+	}
+};
+
+/**
+ * Function: scaleCell
+ * 
+ * Scales the points, position and size of the given cell according to the
+ * given vertical and horizontal scaling factors.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose geometry should be scaled.
+ * dx - Horizontal scaling factor.
+ * dy - Vertical scaling factor.
+ * recurse - Boolean indicating if the child cells should be scaled.
+ */
+mxGraph.prototype.scaleCell = function(cell, dx, dy, recurse)
+{
+	var geo = this.model.getGeometry(cell);
+	
+	if (geo != null)
+	{
+		var state = this.view.getState(cell);
+		var style = (state != null) ? state.style : this.getCellStyle(cell);
+		
+		geo = geo.clone();
+		
+		// Stores values for restoring based on style
+		var x = geo.x;
+		var y = geo.y
+		var w = geo.width;
+		var h = geo.height;
+		
+		geo.scale(dx, dy, style[mxConstants.STYLE_ASPECT] == 'fixed');
+		
+		if (style[mxConstants.STYLE_RESIZE_WIDTH] == '1')
+		{
+			geo.width = w * dx;
+		}
+		else if (style[mxConstants.STYLE_RESIZE_WIDTH] == '0')
+		{
+			geo.width = w;
+		}
+		
+		if (style[mxConstants.STYLE_RESIZE_HEIGHT] == '1')
+		{
+			geo.height = h * dy;
+		}
+		else if (style[mxConstants.STYLE_RESIZE_HEIGHT] == '0')
+		{
+			geo.height = h;
+		}
+		
+		if (!this.isCellMovable(cell))
+		{
+			geo.x = x;
+			geo.y = y;
+		}
+		
+		if (!this.isCellResizable(cell))
+		{
+			geo.width = w;
+			geo.height = h;
+		}
+
+		if (this.model.isVertex(cell))
+		{
+			this.cellResized(cell, geo, true, recurse);
+		}
+		else
+		{
+			this.model.setGeometry(cell, geo);
+		}
+	}
+};
+
+/**
+ * Function: extendParent
+ * 
+ * Resizes the parents recursively so that they contain the complete area
+ * of the resized child cell.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that has been resized.
+ */
+mxGraph.prototype.extendParent = function(cell)
+{
+	if (cell != null)
+	{
+		var parent = this.model.getParent(cell);
+		var p = this.getCellGeometry(parent);
+		
+		if (parent != null && p != null && !this.isCellCollapsed(parent))
+		{
+			var geo = this.getCellGeometry(cell);
+			
+			if (geo != null && !geo.relative &&
+				(p.width < geo.x + geo.width ||
+				p.height < geo.y + geo.height))
+			{
+				p = p.clone();
+				
+				p.width = Math.max(p.width, geo.x + geo.width);
+				p.height = Math.max(p.height, geo.y + geo.height);
+				
+				this.cellsResized([parent], [p], false);
+			}
+		}
+	}
+};
+
+/**
+ * Group: Cell moving
+ */
+
+/**
+ * Function: importCells
+ * 
+ * Clones and inserts the given cells into the graph using the move
+ * method and returns the inserted cells. This shortcut is used if
+ * cells are inserted via datatransfer.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> to be imported.
+ * dx - Integer that specifies the x-coordinate of the vector. Default is 0.
+ * dy - Integer that specifies the y-coordinate of the vector. Default is 0.
+ * target - <mxCell> that represents the new parent of the cells.
+ * evt - Mouseevent that triggered the invocation.
+ * mapping - Optional mapping for existing clones.
+ */
+mxGraph.prototype.importCells = function(cells, dx, dy, target, evt, mapping)
+{	
+	return this.moveCells(cells, dx, dy, true, target, evt, mapping);
+};
+
+/**
+ * Function: moveCells
+ * 
+ * Moves or clones the specified cells and moves the cells or clones by the
+ * given amount, adding them to the optional target cell. The evt is the
+ * mouse event as the mouse was released. The change is carried out using
+ * <cellsMoved>. This method fires <mxEvent.MOVE_CELLS> while the
+ * transaction is in progress. Returns the cells that were moved.
+ * 
+ * Use the following code to move all cells in the graph.
+ * 
+ * (code)
+ * graph.moveCells(graph.getChildCells(null, true, true), 10, 10);
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> to be moved, cloned or added to the target.
+ * dx - Integer that specifies the x-coordinate of the vector. Default is 0.
+ * dy - Integer that specifies the y-coordinate of the vector. Default is 0.
+ * clone - Boolean indicating if the cells should be cloned. Default is false.
+ * target - <mxCell> that represents the new parent of the cells.
+ * evt - Mouseevent that triggered the invocation.
+ * mapping - Optional mapping for existing clones.
+ */
+mxGraph.prototype.moveCells = function(cells, dx, dy, clone, target, evt, mapping)
+{
+	dx = (dx != null) ? dx : 0;
+	dy = (dy != null) ? dy : 0;
+	clone = (clone != null) ? clone : false;
+	
+	if (cells != null && (dx != 0 || dy != 0 || clone || target != null))
+	{
+		// Removes descandants with ancestors in cells to avoid multiple moving
+		cells = this.model.getTopmostCells(cells);
+
+		this.model.beginUpdate();
+		try
+		{
+			// Faster cell lookups to remove relative edge labels with selected
+			// terminals to avoid explicit and implicit move at same time
+			var dict = new mxDictionary();
+			
+			for (var i = 0; i < cells.length; i++)
+			{
+				dict.put(cells[i], true);
+			}
+			
+			var isSelected = mxUtils.bind(this, function(cell)
+			{
+				while (cell != null)
+				{
+					if (dict.get(cell))
+					{
+						return true;
+					}
+					
+					cell = this.model.getParent(cell);
+				}
+				
+				return false;
+			});
+			
+			// Removes relative edge labels with selected terminals
+			var checked = [];
+			
+			for (var i = 0; i < cells.length; i++)
+			{
+				var geo = this.getCellGeometry(cells[i]);
+				var parent = this.model.getParent(cells[i]);
+		
+				if ((geo == null || !geo.relative) || !this.model.isEdge(parent) ||
+					(!isSelected(this.model.getTerminal(parent, true)) &&
+					!isSelected(this.model.getTerminal(parent, false))))
+				{
+					checked.push(cells[i]);
+				}
+			}
+
+			cells = checked;
+			
+			if (clone)
+			{
+				cells = this.cloneCells(cells, this.isCloneInvalidEdges(), mapping);
+
+				if (target == null)
+				{
+					target = this.getDefaultParent();
+				}
+			}
+
+			// FIXME: Cells should always be inserted first before any other edit
+			// to avoid forward references in sessions.
+			// Need to disable allowNegativeCoordinates if target not null to
+			// allow for temporary negative numbers until cellsAdded is called.
+			var previous = this.isAllowNegativeCoordinates();
+			
+			if (target != null)
+			{
+				this.setAllowNegativeCoordinates(true);
+			}
+			
+			this.cellsMoved(cells, dx, dy, !clone && this.isDisconnectOnMove()
+					&& this.isAllowDanglingEdges(), target == null,
+					this.isExtendParentsOnMove() && target == null);
+			
+			this.setAllowNegativeCoordinates(previous);
+
+			if (target != null)
+			{
+				var index = this.model.getChildCount(target);
+				this.cellsAdded(cells, target, index, null, null, true);
+			}
+
+			// Dispatches a move event
+			this.fireEvent(new mxEventObject(mxEvent.MOVE_CELLS, 'cells', cells,
+				'dx', dx, 'dy', dy, 'clone', clone, 'target', target, 'event', evt));
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+
+	return cells;
+};
+
+/**
+ * Function: cellsMoved
+ * 
+ * Moves the specified cells by the given vector, disconnecting the cells
+ * using disconnectGraph is disconnect is true. This method fires
+ * <mxEvent.CELLS_MOVED> while the transaction is in progress.
+ */
+mxGraph.prototype.cellsMoved = function(cells, dx, dy, disconnect, constrain, extend)
+{
+	if (cells != null && (dx != 0 || dy != 0))
+	{
+		extend = (extend != null) ? extend : false;
+
+		this.model.beginUpdate();
+		try
+		{
+			if (disconnect)
+			{
+				this.disconnectGraph(cells);
+			}
+
+			for (var i = 0; i < cells.length; i++)
+			{
+				this.translateCell(cells[i], dx, dy);
+				
+				if (extend && this.isExtendParent(cells[i]))
+				{
+					this.extendParent(cells[i]);
+				}
+				else if (constrain)
+				{
+					this.constrainChild(cells[i]);
+				}
+			}
+
+			if (this.resetEdgesOnMove)
+			{
+				this.resetEdges(cells);
+			}
+			
+			this.fireEvent(new mxEventObject(mxEvent.CELLS_MOVED,
+				'cells', cells, 'dx', dx, 'dy', dy, 'disconnect', disconnect));
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: translateCell
+ * 
+ * Translates the geometry of the given cell and stores the new,
+ * translated geometry in the model as an atomic change.
+ */
+mxGraph.prototype.translateCell = function(cell, dx, dy)
+{
+	var geo = this.model.getGeometry(cell);
+
+	if (geo != null)
+	{
+		dx = parseFloat(dx);
+		dy = parseFloat(dy);
+		geo = geo.clone();
+		geo.translate(dx, dy);
+
+		if (!geo.relative && this.model.isVertex(cell) && !this.isAllowNegativeCoordinates())
+		{
+			geo.x = Math.max(0, parseFloat(geo.x));
+			geo.y = Math.max(0, parseFloat(geo.y));
+		}
+		
+		if (geo.relative && !this.model.isEdge(cell))
+		{
+			var parent = this.model.getParent(cell);
+			var angle = 0;
+			
+			if (this.model.isVertex(parent))
+			{
+				var state = this.view.getState(parent);
+				var style = (state != null) ? state.style : this.getCellStyle(parent);
+				
+				angle = mxUtils.getValue(style, mxConstants.STYLE_ROTATION, 0);
+			}
+			
+			if (angle != 0)
+			{
+				var rad = mxUtils.toRadians(-angle);
+				var cos = Math.cos(rad);
+				var sin = Math.sin(rad);
+				var pt = mxUtils.getRotatedPoint(new mxPoint(dx, dy), cos, sin, new mxPoint(0, 0));
+				dx = pt.x;
+				dy = pt.y;
+			}
+			
+			if (geo.offset == null)
+			{
+				geo.offset = new mxPoint(dx, dy);
+			}
+			else
+			{
+				geo.offset.x = parseFloat(geo.offset.x) + dx;
+				geo.offset.y = parseFloat(geo.offset.y) + dy;
+			}
+		}
+
+		this.model.setGeometry(cell, geo);
+	}
+};
+
+/**
+ * Function: getCellContainmentArea
+ * 
+ * Returns the <mxRectangle> inside which a cell is to be kept.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> for which the area should be returned.
+ */
+mxGraph.prototype.getCellContainmentArea = function(cell)
+{
+	if (cell != null && !this.model.isEdge(cell))
+	{
+		var parent = this.model.getParent(cell);
+		
+		if (parent != null && parent != this.getDefaultParent())
+		{
+			var g = this.model.getGeometry(parent);
+			
+			if (g != null)
+			{
+				var x = 0;
+				var y = 0;
+				var w = g.width;
+				var h = g.height;
+				
+				if (this.isSwimlane(parent))
+				{
+					var size = this.getStartSize(parent);
+					
+					var state = this.view.getState(parent);
+					var style = (state != null) ? state.style : this.getCellStyle(parent);
+					var dir = mxUtils.getValue(style, mxConstants.STYLE_DIRECTION, mxConstants.DIRECTION_EAST);
+					var flipH = mxUtils.getValue(style, mxConstants.STYLE_FLIPH, 0) == 1;
+					var flipV = mxUtils.getValue(style, mxConstants.STYLE_FLIPV, 0) == 1;
+					
+					if (dir == mxConstants.DIRECTION_SOUTH || dir == mxConstants.DIRECTION_NORTH)
+					{
+						var tmp = size.width;
+						size.width = size.height;
+						size.height = tmp;
+					}
+					
+					if ((dir == mxConstants.DIRECTION_EAST && !flipV) || (dir == mxConstants.DIRECTION_NORTH && !flipH) ||
+						(dir == mxConstants.DIRECTION_WEST && flipV) || (dir == mxConstants.DIRECTION_SOUTH && flipH))
+					{
+						x = size.width;
+						y = size.height;
+					}
+
+					w -= size.width;
+					h -= size.height;
+				}
+				
+				return new mxRectangle(x, y, w, h);
+			}
+		}
+	}
+	
+	return null;
+};
+
+/**
+ * Function: getMaximumGraphBounds
+ * 
+ * Returns the bounds inside which the diagram should be kept as an
+ * <mxRectangle>.
+ */
+mxGraph.prototype.getMaximumGraphBounds = function()
+{
+	return this.maximumGraphBounds;
+};
+
+/**
+ * Function: constrainChild
+ * 
+ * Keeps the given cell inside the bounds returned by
+ * <getCellContainmentArea> for its parent, according to the rules defined by
+ * <getOverlap> and <isConstrainChild>. This modifies the cell's geometry
+ * in-place and does not clone it.
+ * 
+ * Parameters:
+ * 
+ * cells - <mxCell> which should be constrained.
+ * sizeFirst - Specifies if the size should be changed first. Default is true.
+ */
+mxGraph.prototype.constrainChild = function(cell, sizeFirst)
+{
+	sizeFirst = (sizeFirst != null) ? sizeFirst : true;
+	
+	if (cell != null)
+	{
+		var geo = this.getCellGeometry(cell);
+		
+		if (geo != null && (this.isConstrainRelativeChildren() || !geo.relative))
+		{
+			var parent = this.model.getParent(cell);
+			var pgeo = this.getCellGeometry(parent);
+			var max = this.getMaximumGraphBounds();
+			
+			// Finds parent offset
+			if (max != null)
+			{
+				var off = this.getBoundingBoxFromGeometry([parent], false);
+				
+				if (off != null)
+				{
+					max = mxRectangle.fromRectangle(max);
+					
+					max.x -= off.x;
+					max.y -= off.y;
+				}
+			}
+			
+			if (this.isConstrainChild(cell))
+			{
+				var tmp = this.getCellContainmentArea(cell);
+				
+				if (tmp != null)
+				{
+					var overlap = this.getOverlap(cell);
+	
+					if (overlap > 0)
+					{
+						tmp = mxRectangle.fromRectangle(tmp);
+						
+						tmp.x -= tmp.width * overlap;
+						tmp.y -= tmp.height * overlap;
+						tmp.width += 2 * tmp.width * overlap;
+						tmp.height += 2 * tmp.height * overlap;
+					}
+					
+					// Find the intersection between max and tmp
+					if (max == null)
+					{
+						max = tmp;
+					}
+					else
+					{
+						max = mxRectangle.fromRectangle(max);
+						max.intersect(tmp);
+					}
+				}
+			}
+			
+			if (max != null)
+			{
+				var cells = [cell];
+				
+				if (!this.isCellCollapsed(cell))
+				{
+					var desc = this.model.getDescendants(cell);
+					
+					for (var i = 0; i < desc.length; i++)
+					{
+						if (this.isCellVisible(desc[i]))
+						{
+							cells.push(desc[i]);
+						}
+					}
+				}
+				
+				var bbox = this.getBoundingBoxFromGeometry(cells, false);
+				
+				if (bbox != null)
+				{
+					geo = geo.clone();
+					
+					// Cumulative horizontal movement
+					var dx = 0;
+					
+					if (geo.width > max.width)
+					{
+						dx = geo.width - max.width;
+						geo.width -= dx;
+					}
+					
+					if (bbox.x + bbox.width > max.x + max.width)
+					{
+						dx -= bbox.x + bbox.width - max.x - max.width - dx;
+					}
+					
+					// Cumulative vertical movement
+					var dy = 0;
+					
+					if (geo.height > max.height)
+					{
+						dy = geo.height - max.height;
+						geo.height -= dy;
+					}
+					
+					if (bbox.y + bbox.height > max.y + max.height)
+					{
+						dy -= bbox.y + bbox.height - max.y - max.height - dy;
+					}
+					
+					if (bbox.x < max.x)
+					{
+						dx -= bbox.x - max.x;
+					}
+					
+					if (bbox.y < max.y)
+					{
+						dy -= bbox.y - max.y;
+					}
+					
+					if (dx != 0 || dy != 0)
+					{
+						if (geo.relative)
+						{
+							// Relative geometries are moved via absolute offset
+							if (geo.offset == null)
+							{
+								geo.offset = new mxPoint();
+							}
+						
+							geo.offset.x += dx;
+							geo.offset.y += dy;
+						}
+						else
+						{
+							geo.x += dx;
+							geo.y += dy;
+						}
+					}
+					
+					this.model.setGeometry(cell, geo);
+				}
+			}
+		}
+	}
+};
+
+/**
+ * Function: resetEdges
+ * 
+ * Resets the control points of the edges that are connected to the given
+ * cells if not both ends of the edge are in the given cells array.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> for which the connected edges should be
+ * reset.
+ */
+mxGraph.prototype.resetEdges = function(cells)
+{
+	if (cells != null)
+	{
+		// Prepares faster cells lookup
+		var dict = new mxDictionary();
+		
+		for (var i = 0; i < cells.length; i++)
+		{
+			dict.put(cells[i], true);
+		}
+		
+		this.model.beginUpdate();
+		try
+		{
+			for (var i = 0; i < cells.length; i++)
+			{
+				var edges = this.model.getEdges(cells[i]);
+				
+				if (edges != null)
+				{
+					for (var j = 0; j < edges.length; j++)
+					{
+						var state = this.view.getState(edges[j]);
+						
+						var source = (state != null) ? state.getVisibleTerminal(true) : this.view.getVisibleTerminal(edges[j], true);
+						var target = (state != null) ? state.getVisibleTerminal(false) : this.view.getVisibleTerminal(edges[j], false);
+						
+						// Checks if one of the terminals is not in the given array
+						if (!dict.get(source) || !dict.get(target))
+						{
+							this.resetEdge(edges[j]);
+						}
+					}
+				}
+				
+				this.resetEdges(this.model.getChildren(cells[i]));
+			}
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: resetEdge
+ * 
+ * Resets the control points of the given edge.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> whose points should be reset.
+ */
+mxGraph.prototype.resetEdge = function(edge)
+{
+	var geo = this.model.getGeometry(edge);
+	
+	// Resets the control points
+	if (geo != null && geo.points != null && geo.points.length > 0)
+	{
+		geo = geo.clone();
+		geo.points = [];
+		this.model.setGeometry(edge, geo);
+	}
+	
+	return edge;
+};
+
+/**
+ * Group: Cell connecting and connection constraints
+ */
+
+/**
+ * Function: getOutlineConstraint
+ * 
+ * Returns the constraint used to connect to the outline of the given state.
+ */
+mxGraph.prototype.getOutlineConstraint = function(point, terminalState, me)
+{
+	if (terminalState.shape != null)
+	{
+		var bounds = this.view.getPerimeterBounds(terminalState);
+		var direction = terminalState.style[mxConstants.STYLE_DIRECTION];
+		
+		if (direction == mxConstants.DIRECTION_NORTH || direction == mxConstants.DIRECTION_SOUTH)
+		{
+			bounds.x += bounds.width / 2 - bounds.height / 2;
+			bounds.y += bounds.height / 2 - bounds.width / 2;
+			var tmp = bounds.width;
+			bounds.width = bounds.height;
+			bounds.height = tmp;
+		}
+	
+		var alpha = mxUtils.toRadians(terminalState.shape.getShapeRotation());
+		
+		if (alpha != 0)
+		{
+			var cos = Math.cos(-alpha);
+			var sin = Math.sin(-alpha);
+	
+			var ct = new mxPoint(bounds.getCenterX(), bounds.getCenterY());
+			point = mxUtils.getRotatedPoint(point, cos, sin, ct);
+		}
+
+		var sx = 1;
+		var sy = 1;
+		var dx = 0;
+		var dy = 0;
+		
+		// LATER: Add flipping support for image shapes
+		if (this.getModel().isVertex(terminalState.cell))
+		{
+			var flipH = terminalState.style[mxConstants.STYLE_FLIPH];
+			var flipV = terminalState.style[mxConstants.STYLE_FLIPV];
+			
+			// Legacy support for stencilFlipH/V
+			if (terminalState.shape != null && terminalState.shape.stencil != null)
+			{
+				flipH = mxUtils.getValue(terminalState.style, 'stencilFlipH', 0) == 1 || flipH;
+				flipV = mxUtils.getValue(terminalState.style, 'stencilFlipV', 0) == 1 || flipV;
+			}
+			
+			if (direction == mxConstants.DIRECTION_NORTH || direction == mxConstants.DIRECTION_SOUTH)
+			{
+				var tmp = flipH;
+				flipH = flipV;
+				flipV = tmp;
+			}
+			
+			if (flipH)
+			{
+				sx = -1;
+				dx = -bounds.width;
+			}
+			
+			if (flipV)
+			{
+				sy = -1;
+				dy = -bounds.height ;
+			}
+		}
+		
+		point = new mxPoint((point.x - bounds.x) * sx - dx + bounds.x, (point.y - bounds.y) * sy - dy + bounds.y);
+		
+		var x = Math.round((point.x - bounds.x) * 1000 / bounds.width) / 1000;
+		var y = Math.round((point.y - bounds.y) * 1000 / bounds.height) / 1000;
+		
+		return new mxConnectionConstraint(new mxPoint(x, y), false);
+	}
+	
+	return null;
+};
+
+/**
+ * Function: getAllConnectionConstraints
+ * 
+ * Returns an array of all <mxConnectionConstraints> for the given terminal. If
+ * the shape of the given terminal is a <mxStencilShape> then the constraints
+ * of the corresponding <mxStencil> are returned.
+ * 
+ * Parameters:
+ * 
+ * terminal - <mxCellState> that represents the terminal.
+ * source - Boolean that specifies if the terminal is the source or target.
+ */
+mxGraph.prototype.getAllConnectionConstraints = function(terminal, source)
+{
+	if (terminal != null && terminal.shape != null && terminal.shape.stencil != null)
+	{
+		return terminal.shape.stencil.constraints;
+	}
+
+	return null;
+};
+
+/**
+ * Function: getConnectionConstraint
+ * 
+ * Returns an <mxConnectionConstraint> that describes the given connection
+ * point. This result can then be passed to <getConnectionPoint>.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCellState> that represents the edge.
+ * terminal - <mxCellState> that represents the terminal.
+ * source - Boolean indicating if the terminal is the source or target.
+ */
+mxGraph.prototype.getConnectionConstraint = function(edge, terminal, source)
+{
+	var point = null;
+	var x = edge.style[(source) ? mxConstants.STYLE_EXIT_X : mxConstants.STYLE_ENTRY_X];
+
+	if (x != null)
+	{
+		var y = edge.style[(source) ? mxConstants.STYLE_EXIT_Y : mxConstants.STYLE_ENTRY_Y];
+		
+		if (y != null)
+		{
+			point = new mxPoint(parseFloat(x), parseFloat(y));
+		}
+	}
+	
+	var perimeter = false;
+	
+	if (point != null)
+	{
+		perimeter = mxUtils.getValue(edge.style, (source) ? mxConstants.STYLE_EXIT_PERIMETER :
+			mxConstants.STYLE_ENTRY_PERIMETER, true);
+	}
+	
+	return new mxConnectionConstraint(point, perimeter);
+};
+
+/**
+ * Function: setConnectionConstraint
+ * 
+ * Sets the <mxConnectionConstraint> that describes the given connection point.
+ * If no constraint is given then nothing is changed. To remove an existing
+ * constraint from the given edge, use an empty constraint instead.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> that represents the edge.
+ * terminal - <mxCell> that represents the terminal.
+ * source - Boolean indicating if the terminal is the source or target.
+ * constraint - Optional <mxConnectionConstraint> to be used for this
+ * connection.
+ */
+mxGraph.prototype.setConnectionConstraint = function(edge, terminal, source, constraint)
+{
+	if (constraint != null)
+	{
+		this.model.beginUpdate();
+		
+		try
+		{
+			if (constraint == null || constraint.point == null)
+			{
+				this.setCellStyles((source) ? mxConstants.STYLE_EXIT_X :
+					mxConstants.STYLE_ENTRY_X, null, [edge]);
+				this.setCellStyles((source) ? mxConstants.STYLE_EXIT_Y :
+					mxConstants.STYLE_ENTRY_Y, null, [edge]);
+				this.setCellStyles((source) ? mxConstants.STYLE_EXIT_PERIMETER :
+					mxConstants.STYLE_ENTRY_PERIMETER, null, [edge]);
+			}
+			else if (constraint.point != null)
+			{
+				this.setCellStyles((source) ? mxConstants.STYLE_EXIT_X :
+					mxConstants.STYLE_ENTRY_X, constraint.point.x, [edge]);
+				this.setCellStyles((source) ? mxConstants.STYLE_EXIT_Y :
+					mxConstants.STYLE_ENTRY_Y, constraint.point.y, [edge]);
+				
+				// Only writes 0 since 1 is default
+				if (!constraint.perimeter)
+				{
+					this.setCellStyles((source) ? mxConstants.STYLE_EXIT_PERIMETER :
+						mxConstants.STYLE_ENTRY_PERIMETER, '0', [edge]);
+				}
+				else
+				{
+					this.setCellStyles((source) ? mxConstants.STYLE_EXIT_PERIMETER :
+						mxConstants.STYLE_ENTRY_PERIMETER, null, [edge]);
+				}
+			}
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: getConnectionPoint
+ *
+ * Returns the nearest point in the list of absolute points or the center
+ * of the opposite terminal.
+ * 
+ * Parameters:
+ * 
+ * vertex - <mxCellState> that represents the vertex.
+ * constraint - <mxConnectionConstraint> that represents the connection point
+ * constraint as returned by <getConnectionConstraint>.
+ */
+mxGraph.prototype.getConnectionPoint = function(vertex, constraint)
+{
+	var point = null;
+	
+	if (vertex != null && constraint.point != null)
+	{
+		var bounds = this.view.getPerimeterBounds(vertex);
+        var cx = new mxPoint(bounds.getCenterX(), bounds.getCenterY());
+		var direction = vertex.style[mxConstants.STYLE_DIRECTION];
+		var r1 = 0;
+		
+		// Bounds need to be rotated by 90 degrees for further computation
+		if (direction != null)
+		{
+			if (direction == mxConstants.DIRECTION_NORTH)
+			{
+				r1 += 270;
+			}
+			else if (direction == mxConstants.DIRECTION_WEST)
+			{
+				r1 += 180;
+			}
+			else if (direction == mxConstants.DIRECTION_SOUTH)
+			{
+				r1 += 90;
+			}
+
+			// Bounds need to be rotated by 90 degrees for further computation
+			if (direction == mxConstants.DIRECTION_NORTH || direction == mxConstants.DIRECTION_SOUTH)
+			{
+				bounds.rotate90();
+			}
+		}
+
+		point = new mxPoint(bounds.x + constraint.point.x * bounds.width,
+				bounds.y + constraint.point.y * bounds.height);
+		
+		// Rotation for direction before projection on perimeter
+		var r2 = vertex.style[mxConstants.STYLE_ROTATION] || 0;
+		
+		if (constraint.perimeter)
+		{
+			if (r1 != 0)
+			{
+				// Only 90 degrees steps possible here so no trig needed
+				var cos = 0;
+				var sin = 0;
+				
+				if (r1 == 90)
+				{
+					sin = 1;
+				}
+				else if (r1 == 180)
+				{
+					cos = -1;
+				}
+				else if (r1 == 270)
+				{
+					sin = -1;
+				}
+				
+		        point = mxUtils.getRotatedPoint(point, cos, sin, cx);
+			}
+	
+			point = this.view.getPerimeterPoint(vertex, point, false);
+		}
+		else
+		{
+			r2 += r1;
+			
+			if (this.getModel().isVertex(vertex.cell))
+			{
+				var flipH = vertex.style[mxConstants.STYLE_FLIPH] == 1;
+				var flipV = vertex.style[mxConstants.STYLE_FLIPV] == 1;
+				
+				// Legacy support for stencilFlipH/V
+				if (vertex.shape != null && vertex.shape.stencil != null)
+				{
+					flipH = (mxUtils.getValue(vertex.style, 'stencilFlipH', 0) == 1) || flipH;
+					flipV = (mxUtils.getValue(vertex.style, 'stencilFlipV', 0) == 1) || flipV;
+				}
+				
+				if (flipH)
+				{
+					point.x = 2 * bounds.getCenterX() - point.x;
+				}
+				
+				if (flipV)
+				{
+					point.y = 2 * bounds.getCenterY() - point.y;
+				}
+			}
+		}
+
+		// Generic rotation after projection on perimeter
+		if (r2 != 0 && point != null)
+		{
+	        var rad = mxUtils.toRadians(r2);
+	        var cos = Math.cos(rad);
+	        var sin = Math.sin(rad);
+	        
+	        point = mxUtils.getRotatedPoint(point, cos, sin, cx);
+		}
+	}
+	
+	if (point != null)
+	{
+		point.x = Math.round(point.x);
+		point.y = Math.round(point.y);
+	}
+
+	return point;
+};
+
+/**
+ * Function: connectCell
+ * 
+ * Connects the specified end of the given edge to the given terminal
+ * using <cellConnected> and fires <mxEvent.CONNECT_CELL> while the
+ * transaction is in progress. Returns the updated edge.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> whose terminal should be updated.
+ * terminal - <mxCell> that represents the new terminal to be used.
+ * source - Boolean indicating if the new terminal is the source or target.
+ * constraint - Optional <mxConnectionConstraint> to be used for this
+ * connection.
+ */
+mxGraph.prototype.connectCell = function(edge, terminal, source, constraint)
+{
+	this.model.beginUpdate();
+	try
+	{
+		var previous = this.model.getTerminal(edge, source);
+		this.cellConnected(edge, terminal, source, constraint);
+		this.fireEvent(new mxEventObject(mxEvent.CONNECT_CELL,
+			'edge', edge, 'terminal', terminal, 'source', source,
+			'previous', previous));
+	}
+	finally
+	{
+		this.model.endUpdate();
+	}
+
+	return edge;
+};
+
+/**
+ * Function: cellConnected
+ * 
+ * Sets the new terminal for the given edge and resets the edge points if
+ * <resetEdgesOnConnect> is true. This method fires
+ * <mxEvent.CELL_CONNECTED> while the transaction is in progress.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> whose terminal should be updated.
+ * terminal - <mxCell> that represents the new terminal to be used.
+ * source - Boolean indicating if the new terminal is the source or target.
+ * constraint - <mxConnectionConstraint> to be used for this connection.
+ */
+mxGraph.prototype.cellConnected = function(edge, terminal, source, constraint)
+{
+	if (edge != null)
+	{
+		this.model.beginUpdate();
+		try
+		{
+			var previous = this.model.getTerminal(edge, source);
+
+			// Updates the constraint
+			this.setConnectionConstraint(edge, terminal, source, constraint);
+			
+			// Checks if the new terminal is a port, uses the ID of the port in the
+			// style and the parent of the port as the actual terminal of the edge.
+			if (this.isPortsEnabled())
+			{
+				var id = null;
+	
+				if (this.isPort(terminal))
+				{
+					id = terminal.getId();
+					terminal = this.getTerminalForPort(terminal, source);
+				}
+				
+				// Sets or resets all previous information for connecting to a child port
+				var key = (source) ? mxConstants.STYLE_SOURCE_PORT :
+					mxConstants.STYLE_TARGET_PORT;
+				this.setCellStyles(key, id, [edge]);
+			}
+			
+			this.model.setTerminal(edge, terminal, source);
+			
+			if (this.resetEdgesOnConnect)
+			{
+				this.resetEdge(edge);
+			}
+
+			this.fireEvent(new mxEventObject(mxEvent.CELL_CONNECTED,
+				'edge', edge, 'terminal', terminal, 'source', source,
+				'previous', previous));
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: disconnectGraph
+ * 
+ * Disconnects the given edges from the terminals which are not in the
+ * given array.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> to be disconnected.
+ */
+mxGraph.prototype.disconnectGraph = function(cells)
+{
+	if (cells != null)
+	{
+		this.model.beginUpdate();
+		try
+		{							
+			var scale = this.view.scale;
+			var tr = this.view.translate;
+			
+			// Fast lookup for finding cells in array
+			var dict = new mxDictionary();
+			
+			for (var i = 0; i < cells.length; i++)
+			{
+				dict.put(cells[i], true);
+			}
+			
+			for (var i = 0; i < cells.length; i++)
+			{
+				if (this.model.isEdge(cells[i]))
+				{
+					var geo = this.model.getGeometry(cells[i]);
+					
+					if (geo != null)
+					{
+						var state = this.view.getState(cells[i]);
+						var pstate = this.view.getState(
+							this.model.getParent(cells[i]));
+						
+						if (state != null &&
+							pstate != null)
+						{
+							geo = geo.clone();
+							
+							var dx = -pstate.origin.x;
+							var dy = -pstate.origin.y;
+							var pts = state.absolutePoints;
+
+							var src = this.model.getTerminal(cells[i], true);
+							
+							if (src != null && this.isCellDisconnectable(cells[i], src, true))
+							{
+								while (src != null && !dict.get(src))
+								{
+									src = this.model.getParent(src);
+								}
+								
+								if (src == null)
+								{
+									geo.setTerminalPoint(
+										new mxPoint(pts[0].x / scale - tr.x + dx,
+											pts[0].y / scale - tr.y + dy), true);
+									this.model.setTerminal(cells[i], null, true);
+								}
+							}
+							
+							var trg = this.model.getTerminal(cells[i], false);
+							
+							if (trg != null && this.isCellDisconnectable(cells[i], trg, false))
+							{
+								while (trg != null && !dict.get(trg))
+								{
+									trg = this.model.getParent(trg);
+								}
+								
+								if (trg == null)
+								{
+									var n = pts.length - 1;
+									geo.setTerminalPoint(
+										new mxPoint(pts[n].x / scale - tr.x + dx,
+											pts[n].y / scale - tr.y + dy), false);
+									this.model.setTerminal(cells[i], null, false);
+								}
+							}
+
+							this.model.setGeometry(cells[i], geo);
+						}
+					}
+				}
+			}
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Group: Drilldown
+ */
+
+/**
+ * Function: getCurrentRoot
+ * 
+ * Returns the current root of the displayed cell hierarchy. This is a
+ * shortcut to <mxGraphView.currentRoot> in <view>.
+ */
+mxGraph.prototype.getCurrentRoot = function()
+{
+	return this.view.currentRoot;
+};
+ 
+/**
+ * Function: getTranslateForRoot
+ * 
+ * Returns the translation to be used if the given cell is the root cell as
+ * an <mxPoint>. This implementation returns null.
+ * 
+ * Example:
+ * 
+ * To keep the children at their absolute position while stepping into groups,
+ * this function can be overridden as follows.
+ * 
+ * (code)
+ * var offset = new mxPoint(0, 0);
+ * 
+ * while (cell != null)
+ * {
+ *   var geo = this.model.getGeometry(cell);
+ * 
+ *   if (geo != null)
+ *   {
+ *     offset.x -= geo.x;
+ *     offset.y -= geo.y;
+ *   }
+ * 
+ *   cell = this.model.getParent(cell);
+ * }
+ * 
+ * return offset;
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the root.
+ */
+mxGraph.prototype.getTranslateForRoot = function(cell)
+{
+	return null;
+};
+
+/**
+ * Function: isPort
+ * 
+ * Returns true if the given cell is a "port", that is, when connecting to
+ * it, the cell returned by getTerminalForPort should be used as the
+ * terminal and the port should be referenced by the ID in either the
+ * mxConstants.STYLE_SOURCE_PORT or the or the
+ * mxConstants.STYLE_TARGET_PORT. Note that a port should not be movable.
+ * This implementation always returns false.
+ * 
+ * A typical implementation is the following:
+ * 
+ * (code)
+ * graph.isPort = function(cell)
+ * {
+ *   var geo = this.getCellGeometry(cell);
+ *   
+ *   return (geo != null) ? geo.relative : false;
+ * };
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the port.
+ */
+mxGraph.prototype.isPort = function(cell)
+{
+	return false;
+};
+
+/**
+ * Function: getTerminalForPort
+ * 
+ * Returns the terminal to be used for a given port. This implementation
+ * always returns the parent cell.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the port.
+ * source - If the cell is the source or target port.
+ */
+mxGraph.prototype.getTerminalForPort = function(cell, source)
+{
+	return this.model.getParent(cell);
+};
+
+/**
+ * Function: getChildOffsetForCell
+ * 
+ * Returns the offset to be used for the cells inside the given cell. The
+ * root and layer cells may be identified using <mxGraphModel.isRoot> and
+ * <mxGraphModel.isLayer>. For all other current roots, the
+ * <mxGraphView.currentRoot> field points to the respective cell, so that
+ * the following holds: cell == this.view.currentRoot. This implementation
+ * returns null.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose offset should be returned.
+ */
+mxGraph.prototype.getChildOffsetForCell = function(cell)
+{
+	return null;
+};
+
+/**
+ * Function: enterGroup
+ * 
+ * Uses the given cell as the root of the displayed cell hierarchy. If no
+ * cell is specified then the selection cell is used. The cell is only used
+ * if <isValidRoot> returns true.
+ * 
+ * Parameters:
+ * 
+ * cell - Optional <mxCell> to be used as the new root. Default is the
+ * selection cell.
+ */
+mxGraph.prototype.enterGroup = function(cell)
+{
+	cell = cell || this.getSelectionCell();
+	
+	if (cell != null && this.isValidRoot(cell))
+	{
+		this.view.setCurrentRoot(cell);
+		this.clearSelection();
+	}
+};
+
+/**
+ * Function: exitGroup
+ * 
+ * Changes the current root to the next valid root in the displayed cell
+ * hierarchy.
+ */
+mxGraph.prototype.exitGroup = function()
+{
+	var root = this.model.getRoot();
+	var current = this.getCurrentRoot();
+	
+	if (current != null)
+	{
+		var next = this.model.getParent(current);
+		
+		// Finds the next valid root in the hierarchy
+		while (next != root && !this.isValidRoot(next) &&
+				this.model.getParent(next) != root)
+		{
+			next = this.model.getParent(next);
+		}
+		
+		// Clears the current root if the new root is
+		// the model's root or one of the layers.
+		if (next == root || this.model.getParent(next) == root)
+		{
+			this.view.setCurrentRoot(null);
+		}
+		else
+		{
+			this.view.setCurrentRoot(next);
+		}
+		
+		var state = this.view.getState(current);
+		
+		// Selects the previous root in the graph
+		if (state != null)
+		{
+			this.setSelectionCell(current);
+		}
+	}
+};
+
+/**
+ * Function: home
+ * 
+ * Uses the root of the model as the root of the displayed cell hierarchy
+ * and selects the previous root.
+ */
+mxGraph.prototype.home = function()
+{
+	var current = this.getCurrentRoot();
+	
+	if (current != null)
+	{
+		this.view.setCurrentRoot(null);
+		var state = this.view.getState(current);
+		
+		if (state != null)
+		{
+			this.setSelectionCell(current);
+		}
+	}
+};
+
+/**
+ * Function: isValidRoot
+ * 
+ * Returns true if the given cell is a valid root for the cell display
+ * hierarchy. This implementation returns true for all non-null values.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> which should be checked as a possible root.
+ */
+mxGraph.prototype.isValidRoot = function(cell)
+{
+	return (cell != null);
+};
+
+/**
+ * Group: Graph display
+ */
+ 
+/**
+ * Function: getGraphBounds
+ * 
+ * Returns the bounds of the visible graph. Shortcut to
+ * <mxGraphView.getGraphBounds>. See also: <getBoundingBoxFromGeometry>.
+ */
+ mxGraph.prototype.getGraphBounds = function()
+ {
+ 	return this.view.getGraphBounds();
+ };
+
+/**
+ * Function: getCellBounds
+ * 
+ * Returns the scaled, translated bounds for the given cell. See
+ * <mxGraphView.getBounds> for arrays.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose bounds should be returned.
+ * includeEdge - Optional boolean that specifies if the bounds of
+ * the connected edges should be included. Default is false.
+ * includeDescendants - Optional boolean that specifies if the bounds
+ * of all descendants should be included. Default is false.
+ */
+mxGraph.prototype.getCellBounds = function(cell, includeEdges, includeDescendants)
+{
+	var cells = [cell];
+	
+	// Includes all connected edges
+	if (includeEdges)
+	{
+		cells = cells.concat(this.model.getEdges(cell));
+	}
+	
+	var result = this.view.getBounds(cells);
+	
+	// Recursively includes the bounds of the children
+	if (includeDescendants)
+	{
+		var childCount = this.model.getChildCount(cell);
+		
+		for (var i = 0; i < childCount; i++)
+		{
+			var tmp = this.getCellBounds(this.model.getChildAt(cell, i),
+				includeEdges, true);
+
+			if (result != null)
+			{
+				result.add(tmp);
+			}
+			else
+			{
+				result = tmp;
+			}
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: getBoundingBoxFromGeometry
+ * 
+ * Returns the bounding box for the geometries of the vertices in the
+ * given array of cells. This can be used to find the graph bounds during
+ * a layout operation (ie. before the last endUpdate) as follows:
+ * 
+ * (code)
+ * var cells = graph.getChildCells(graph.getDefaultParent(), true, true);
+ * var bounds = graph.getBoundingBoxFromGeometry(cells, true);
+ * (end)
+ * 
+ * This can then be used to move cells to the origin:
+ * 
+ * (code)
+ * if (bounds.x < 0 || bounds.y < 0)
+ * {
+ *   graph.moveCells(cells, -Math.min(bounds.x, 0), -Math.min(bounds.y, 0))
+ * }
+ * (end)
+ * 
+ * Or to translate the graph view:
+ * 
+ * (code)
+ * if (bounds.x < 0 || bounds.y < 0)
+ * {
+ *   graph.view.setTranslate(-Math.min(bounds.x, 0), -Math.min(bounds.y, 0));
+ * }
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> whose bounds should be returned.
+ * includeEdges - Specifies if edge bounds should be included by computing
+ * the bounding box for all points in geometry. Default is false.
+ */
+mxGraph.prototype.getBoundingBoxFromGeometry = function(cells, includeEdges)
+{
+	includeEdges = (includeEdges != null) ? includeEdges : false;
+	var result = null;
+	
+	if (cells != null)
+	{
+		for (var i = 0; i < cells.length; i++)
+		{
+			if (includeEdges || this.model.isVertex(cells[i]))
+			{
+				// Computes the bounding box for the points in the geometry
+				var geo = this.getCellGeometry(cells[i]);
+				
+				if (geo != null)
+				{
+					var bbox = null;
+					
+					if (this.model.isEdge(cells[i]))
+					{
+						var addPoint = function(pt)
+						{
+							if (pt != null)
+							{
+								if (tmp == null)
+								{
+									tmp = new mxRectangle(pt.x, pt.y, 0, 0);
+								}
+								else
+								{
+									tmp.add(new mxRectangle(pt.x, pt.y, 0, 0));
+								}
+							}
+						};
+						
+						if (this.model.getTerminal(cells[i], true) == null)
+						{
+							addPoint(geo.getTerminalPoint(true));
+						}
+						
+						if (this.model.getTerminal(cells[i], false) == null)
+						{
+							addPoint(geo.getTerminalPoint(false));
+						}
+												
+						var pts = geo.points;
+						
+						if (pts != null && pts.length > 0)
+						{
+							var tmp = new mxRectangle(pts[0].x, pts[0].y, 0, 0);
+
+							for (var j = 1; j < pts.length; j++)
+							{
+								addPoint(pts[j]);
+							}
+						}
+						
+						bbox = tmp;
+					}
+					else
+					{
+						var parent = this.model.getParent(cells[i]);
+						
+						if (geo.relative)
+						{
+							if (this.model.isVertex(parent) && parent != this.view.currentRoot)
+							{
+								var tmp = this.getBoundingBoxFromGeometry([parent], false);
+								
+								if (tmp != null)
+								{
+									bbox = new mxRectangle(geo.x * tmp.width, geo.y * tmp.height, geo.width, geo.height);
+									
+									if (mxUtils.indexOf(cells, parent) >= 0)
+									{
+										bbox.x += tmp.x;
+										bbox.y += tmp.y;
+									}
+								}
+							}
+						}
+						else
+						{
+							bbox = mxRectangle.fromRectangle(geo);
+							
+							if (this.model.isVertex(parent) && mxUtils.indexOf(cells, parent) >= 0)
+							{
+								var tmp = this.getBoundingBoxFromGeometry([parent], false);
+
+								if (tmp != null)
+								{
+									bbox.x += tmp.x;
+									bbox.y += tmp.y;
+								}
+							}
+						}
+						
+						if (bbox != null && geo.offset != null)
+						{
+							bbox.x += geo.offset.x;
+							bbox.y += geo.offset.y;
+						}
+					}
+					
+					if (bbox != null)
+					{
+						if (result == null)
+						{
+							result = mxRectangle.fromRectangle(bbox);
+						}
+						else
+						{
+							result.add(bbox);
+						}
+					}
+				}
+			}
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: refresh
+ * 
+ * Clears all cell states or the states for the hierarchy starting at the
+ * given cell and validates the graph. This fires a refresh event as the
+ * last step.
+ * 
+ * Parameters:
+ * 
+ * cell - Optional <mxCell> for which the cell states should be cleared.
+ */
+mxGraph.prototype.refresh = function(cell)
+{
+	this.view.clear(cell, cell == null);
+	this.view.validate();
+	this.sizeDidChange();
+	this.fireEvent(new mxEventObject(mxEvent.REFRESH));
+};
+
+/**
+ * Function: snap
+ * 
+ * Snaps the given numeric value to the grid if <gridEnabled> is true.
+ * 
+ * Parameters:
+ * 
+ * value - Numeric value to be snapped to the grid.
+ */
+mxGraph.prototype.snap = function(value)
+{
+	if (this.gridEnabled)
+	{
+		value = Math.round(value / this.gridSize ) * this.gridSize;
+	}
+	
+	return value;
+};
+
+/**
+ * Function: panGraph
+ * 
+ * Shifts the graph display by the given amount. This is used to preview
+ * panning operations, use <mxGraphView.setTranslate> to set a persistent
+ * translation of the view. Fires <mxEvent.PAN>.
+ * 
+ * Parameters:
+ * 
+ * dx - Amount to shift the graph along the x-axis.
+ * dy - Amount to shift the graph along the y-axis.
+ */
+mxGraph.prototype.panGraph = function(dx, dy)
+{
+	if (this.useScrollbarsForPanning && mxUtils.hasScrollbars(this.container))
+	{
+		this.container.scrollLeft = -dx;
+		this.container.scrollTop = -dy;
+	}
+	else
+	{
+		var canvas = this.view.getCanvas();
+		
+		if (this.dialect == mxConstants.DIALECT_SVG)
+		{
+			// Puts everything inside the container in a DIV so that it
+			// can be moved without changing the state of the container
+			if (dx == 0 && dy == 0)
+			{
+				// Workaround for ignored removeAttribute on SVG element in IE9 standards
+				if (mxClient.IS_IE)
+				{
+					canvas.setAttribute('transform', 'translate(' + dx + ',' + dy + ')');
+				}
+				else
+				{
+					canvas.removeAttribute('transform');
+				}
+				
+				if (this.shiftPreview1 != null)
+				{
+					var child = this.shiftPreview1.firstChild;
+					
+					while (child != null)
+					{
+						var next = child.nextSibling;
+						this.container.appendChild(child);
+						child = next;
+					}
+
+					if (this.shiftPreview1.parentNode != null)
+					{
+						this.shiftPreview1.parentNode.removeChild(this.shiftPreview1);
+					}
+					
+					this.shiftPreview1 = null;
+					
+					this.container.appendChild(canvas.parentNode);
+					
+					child = this.shiftPreview2.firstChild;
+					
+					while (child != null)
+					{
+						var next = child.nextSibling;
+						this.container.appendChild(child);
+						child = next;
+					}
+
+					if (this.shiftPreview2.parentNode != null)
+					{
+						this.shiftPreview2.parentNode.removeChild(this.shiftPreview2);
+					}
+					
+					this.shiftPreview2 = null;
+				}
+			}
+			else
+			{
+				canvas.setAttribute('transform', 'translate(' + dx + ',' + dy + ')');
+				
+				if (this.shiftPreview1 == null)
+				{
+					// Needs two divs for stuff before and after the SVG element
+					this.shiftPreview1 = document.createElement('div');
+					this.shiftPreview1.style.position = 'absolute';
+					this.shiftPreview1.style.overflow = 'visible';
+					
+					this.shiftPreview2 = document.createElement('div');
+					this.shiftPreview2.style.position = 'absolute';
+					this.shiftPreview2.style.overflow = 'visible';
+
+					var current = this.shiftPreview1;
+					var child = this.container.firstChild;
+					
+					while (child != null)
+					{
+						var next = child.nextSibling;
+						
+						// SVG element is moved via transform attribute
+						if (child != canvas.parentNode)
+						{
+							current.appendChild(child);
+						}
+						else
+						{
+							current = this.shiftPreview2;
+						}
+						
+						child = next;
+					}
+					
+					// Inserts elements only if not empty
+					if (this.shiftPreview1.firstChild != null)
+					{
+						this.container.insertBefore(this.shiftPreview1, canvas.parentNode);
+					}
+					
+					if (this.shiftPreview2.firstChild != null)
+					{
+						this.container.appendChild(this.shiftPreview2);
+					}
+				}
+				
+				this.shiftPreview1.style.left = dx + 'px';
+				this.shiftPreview1.style.top = dy + 'px';
+				this.shiftPreview2.style.left = dx + 'px';
+				this.shiftPreview2.style.top = dy + 'px';
+			}
+		}
+		else
+		{
+			canvas.style.left = dx + 'px';
+			canvas.style.top = dy + 'px';
+		}
+		
+		this.panDx = dx;
+		this.panDy = dy;
+
+		this.fireEvent(new mxEventObject(mxEvent.PAN));
+	}
+};
+
+/**
+ * Function: zoomIn
+ * 
+ * Zooms into the graph by <zoomFactor>.
+ */
+mxGraph.prototype.zoomIn = function()
+{
+	this.zoom(this.zoomFactor);
+};
+
+/**
+ * Function: zoomOut
+ * 
+ * Zooms out of the graph by <zoomFactor>.
+ */
+mxGraph.prototype.zoomOut = function()
+{
+	this.zoom(1 / this.zoomFactor);
+};
+
+/**
+ * Function: zoomActual
+ * 
+ * Resets the zoom and panning in the view.
+ */
+mxGraph.prototype.zoomActual = function()
+{
+	if (this.view.scale == 1)
+	{
+		this.view.setTranslate(0, 0);
+	}
+	else
+	{
+		this.view.translate.x = 0;
+		this.view.translate.y = 0;
+
+		this.view.setScale(1);
+	}
+};
+
+/**
+ * Function: zoomTo
+ * 
+ * Zooms the graph to the given scale with an optional boolean center
+ * argument, which is passd to <zoom>.
+ */
+mxGraph.prototype.zoomTo = function(scale, center)
+{
+	this.zoom(scale / this.view.scale, center);
+};
+
+/**
+ * Function: center
+ * 
+ * Centers the graph in the container.
+ * 
+ * Parameters:
+ * 
+ * horizontal - Optional boolean that specifies if the graph should be centered
+ * horizontally. Default is true.
+ * vertical - Optional boolean that specifies if the graph should be centered
+ * vertically. Default is true.
+ * cx - Optional float that specifies the horizontal center. Default is 0.5.
+ * cy - Optional float that specifies the vertical center. Default is 0.5.
+ */
+mxGraph.prototype.center = function(horizontal, vertical, cx, cy)
+{
+	horizontal = (horizontal != null) ? horizontal : true;
+	vertical = (vertical != null) ? vertical : true;
+	cx = (cx != null) ? cx : 0.5;
+	cy = (cy != null) ? cy : 0.5;
+	
+	var hasScrollbars = mxUtils.hasScrollbars(this.container);
+	var cw = this.container.clientWidth;
+	var ch = this.container.clientHeight;
+	var bounds = this.getGraphBounds();
+
+	var t = this.view.translate;
+	var s = this.view.scale;
+
+	var dx = (horizontal) ? cw - bounds.width : 0;
+	var dy = (vertical) ? ch - bounds.height : 0;
+	
+	if (!hasScrollbars)
+	{
+		this.view.setTranslate((horizontal) ? Math.floor(t.x - bounds.x * s + dx * cx / s) : t.x,
+			(vertical) ? Math.floor(t.y - bounds.y * s + dy * cy / s) : t.y);
+	}
+	else
+	{
+		bounds.x -= t.x;
+		bounds.y -= t.y;
+	
+		var sw = this.container.scrollWidth;
+		var sh = this.container.scrollHeight;
+		
+		if (sw > cw)
+		{
+			dx = 0;
+		}
+		
+		if (sh > ch)
+		{
+			dy = 0;
+		}
+
+		this.view.setTranslate(Math.floor(dx / 2 - bounds.x), Math.floor(dy / 2 - bounds.y));
+		this.container.scrollLeft = (sw - cw) / 2;
+		this.container.scrollTop = (sh - ch) / 2;
+	}
+};
+
+/**
+ * Function: zoom
+ * 
+ * Zooms the graph using the given factor. Center is an optional boolean
+ * argument that keeps the graph scrolled to the center. If the center argument
+ * is omitted, then <centerZoom> will be used as its value.
+ */
+mxGraph.prototype.zoom = function(factor, center)
+{
+	center = (center != null) ? center : this.centerZoom;
+	var scale = Math.round(this.view.scale * factor * 100) / 100;
+	var state = this.view.getState(this.getSelectionCell());
+	factor = scale / this.view.scale;
+	
+	if (this.keepSelectionVisibleOnZoom && state != null)
+	{
+		var rect = new mxRectangle(state.x * factor, state.y * factor,
+			state.width * factor, state.height * factor);
+		
+		// Refreshes the display only once if a scroll is carried out
+		this.view.scale = scale;
+		
+		if (!this.scrollRectToVisible(rect))
+		{
+			this.view.revalidate();
+			
+			// Forces an event to be fired but does not revalidate again
+			this.view.setScale(scale);
+		}
+	}
+	else
+	{
+		var hasScrollbars = mxUtils.hasScrollbars(this.container);
+		
+		if (center && !hasScrollbars)
+		{
+			var dx = this.container.offsetWidth;
+			var dy = this.container.offsetHeight;
+			
+			if (factor > 1)
+			{
+				var f = (factor - 1) / (scale * 2);
+				dx *= -f;
+				dy *= -f;
+			}
+			else
+			{
+				var f = (1 / factor - 1) / (this.view.scale * 2);
+				dx *= f;
+				dy *= f;
+			}
+
+			this.view.scaleAndTranslate(scale,
+				this.view.translate.x + dx,
+				this.view.translate.y + dy);
+		}
+		else
+		{
+			// Allows for changes of translate and scrollbars during setscale
+			var tx = this.view.translate.x;
+			var ty = this.view.translate.y;
+			var sl = this.container.scrollLeft;
+			var st = this.container.scrollTop;
+			
+			this.view.setScale(scale);
+			
+			if (hasScrollbars)
+			{
+				var dx = 0;
+				var dy = 0;
+				
+				if (center)
+				{
+					dx = this.container.offsetWidth * (factor - 1) / 2;
+					dy = this.container.offsetHeight * (factor - 1) / 2;
+				}
+				
+				this.container.scrollLeft = (this.view.translate.x - tx) * this.view.scale + Math.round(sl * factor + dx);
+				this.container.scrollTop = (this.view.translate.y - ty) * this.view.scale + Math.round(st * factor + dy);
+			}
+		}
+	}
+};
+
+/**
+ * Function: zoomToRect
+ * 
+ * Zooms the graph to the specified rectangle. If the rectangle does not have same aspect
+ * ratio as the display container, it is increased in the smaller relative dimension only
+ * until the aspect match. The original rectangle is centralised within this expanded one.
+ * 
+ * Note that the input rectangular must be un-scaled and un-translated.
+ * 
+ * Parameters:
+ * 
+ * rect - The un-scaled and un-translated rectangluar region that should be just visible 
+ * after the operation
+ */
+mxGraph.prototype.zoomToRect = function(rect)
+{
+	var scaleX = this.container.clientWidth / rect.width;
+	var scaleY = this.container.clientHeight / rect.height;
+	var aspectFactor = scaleX / scaleY;
+
+	// Remove any overlap of the rect outside the client area
+	rect.x = Math.max(0, rect.x);
+	rect.y = Math.max(0, rect.y);
+	var rectRight = Math.min(this.container.scrollWidth, rect.x + rect.width);
+	var rectBottom = Math.min(this.container.scrollHeight, rect.y + rect.height);
+	rect.width = rectRight - rect.x;
+	rect.height = rectBottom - rect.y;
+
+	// The selection area has to be increased to the same aspect
+	// ratio as the container, centred around the centre point of the 
+	// original rect passed in.
+	if (aspectFactor < 1.0)
+	{
+		// Height needs increasing
+		var newHeight = rect.height / aspectFactor;
+		var deltaHeightBuffer = (newHeight - rect.height) / 2.0;
+		rect.height = newHeight;
+		
+		// Assign up to half the buffer to the upper part of the rect, not crossing 0
+		// put the rest on the bottom
+		var upperBuffer = Math.min(rect.y , deltaHeightBuffer);
+		rect.y = rect.y - upperBuffer;
+		
+		// Check if the bottom has extended too far
+		rectBottom = Math.min(this.container.scrollHeight, rect.y + rect.height);
+		rect.height = rectBottom - rect.y;
+	}
+	else
+	{
+		// Width needs increasing
+		var newWidth = rect.width * aspectFactor;
+		var deltaWidthBuffer = (newWidth - rect.width) / 2.0;
+		rect.width = newWidth;
+		
+		// Assign up to half the buffer to the upper part of the rect, not crossing 0
+		// put the rest on the bottom
+		var leftBuffer = Math.min(rect.x , deltaWidthBuffer);
+		rect.x = rect.x - leftBuffer;
+		
+		// Check if the right hand side has extended too far
+		rectRight = Math.min(this.container.scrollWidth, rect.x + rect.width);
+		rect.width = rectRight - rect.x;
+	}
+
+	var scale = this.container.clientWidth / rect.width;
+	var newScale = this.view.scale * scale;
+
+	if (!mxUtils.hasScrollbars(this.container))
+	{
+		this.view.scaleAndTranslate(newScale, (this.view.translate.x - rect.x / this.view.scale), (this.view.translate.y - rect.y / this.view.scale));
+	}
+	else
+	{
+		this.view.setScale(newScale);
+		this.container.scrollLeft = Math.round(rect.x * scale);
+		this.container.scrollTop = Math.round(rect.y * scale);
+	}
+};
+
+/**
+ * Function: scrollCellToVisible
+ * 
+ * Pans the graph so that it shows the given cell. Optionally the cell may
+ * be centered in the container.
+ * 
+ * To center a given graph if the <container> has no scrollbars, use the following code.
+ * 
+ * [code]
+ * var bounds = graph.getGraphBounds();
+ * graph.view.setTranslate(-bounds.x - (bounds.width - container.clientWidth) / 2,
+ * 						   -bounds.y - (bounds.height - container.clientHeight) / 2);
+ * [/code]
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to be made visible.
+ * center - Optional boolean flag. Default is false.
+ */
+mxGraph.prototype.scrollCellToVisible = function(cell, center)
+{
+	var x = -this.view.translate.x;
+	var y = -this.view.translate.y;
+
+	var state = this.view.getState(cell);
+
+	if (state != null)
+	{
+		var bounds = new mxRectangle(x + state.x, y + state.y, state.width,
+			state.height);
+
+		if (center && this.container != null)
+		{
+			var w = this.container.clientWidth;
+			var h = this.container.clientHeight;
+
+			bounds.x = bounds.getCenterX() - w / 2;
+			bounds.width = w;
+			bounds.y = bounds.getCenterY() - h / 2;
+			bounds.height = h;
+		}
+		
+		var tr = new mxPoint(this.view.translate.x, this.view.translate.y);
+
+		if (this.scrollRectToVisible(bounds))
+		{
+			// Triggers an update via the view's event source
+			var tr2 = new mxPoint(this.view.translate.x, this.view.translate.y);
+			this.view.translate.x = tr.x;
+			this.view.translate.y = tr.y;
+			this.view.setTranslate(tr2.x, tr2.y);
+		}
+	}
+};
+
+/**
+ * Function: scrollRectToVisible
+ * 
+ * Pans the graph so that it shows the given rectangle.
+ * 
+ * Parameters:
+ * 
+ * rect - <mxRectangle> to be made visible.
+ */
+mxGraph.prototype.scrollRectToVisible = function(rect)
+{
+	var isChanged = false;
+	
+	if (rect != null)
+	{
+		var w = this.container.offsetWidth;
+		var h = this.container.offsetHeight;
+
+        var widthLimit = Math.min(w, rect.width);
+        var heightLimit = Math.min(h, rect.height);
+
+		if (mxUtils.hasScrollbars(this.container))
+		{
+			var c = this.container;
+			rect.x += this.view.translate.x;
+			rect.y += this.view.translate.y;
+			var dx = c.scrollLeft - rect.x;
+			var ddx = Math.max(dx - c.scrollLeft, 0);
+
+			if (dx > 0)
+			{
+				c.scrollLeft -= dx + 2;
+			}
+			else
+			{
+				dx = rect.x + widthLimit - c.scrollLeft - c.clientWidth;
+
+				if (dx > 0)
+				{
+					c.scrollLeft += dx + 2;
+				}
+			}
+
+			var dy = c.scrollTop - rect.y;
+			var ddy = Math.max(0, dy - c.scrollTop);
+
+			if (dy > 0)
+			{
+				c.scrollTop -= dy + 2;
+			}
+			else
+			{
+				dy = rect.y + heightLimit - c.scrollTop - c.clientHeight;
+
+				if (dy > 0)
+				{
+					c.scrollTop += dy + 2;
+				}
+			}
+
+			if (!this.useScrollbarsForPanning && (ddx != 0 || ddy != 0))
+			{
+				this.view.setTranslate(ddx, ddy);
+			}
+		}
+		else
+		{
+			var x = -this.view.translate.x;
+			var y = -this.view.translate.y;
+
+			var s = this.view.scale;
+
+			if (rect.x + widthLimit > x + w)
+			{
+				this.view.translate.x -= (rect.x + widthLimit - w - x) / s;
+				isChanged = true;
+			}
+
+			if (rect.y + heightLimit > y + h)
+			{
+				this.view.translate.y -= (rect.y + heightLimit - h - y) / s;
+				isChanged = true;
+			}
+
+			if (rect.x < x)
+			{
+				this.view.translate.x += (x - rect.x) / s;
+				isChanged = true;
+			}
+
+			if (rect.y  < y)
+			{
+				this.view.translate.y += (y - rect.y) / s;
+				isChanged = true;
+			}
+
+			if (isChanged)
+			{
+				this.view.refresh();
+				
+				// Repaints selection marker (ticket 18)
+				if (this.selectionCellsHandler != null)
+				{
+					this.selectionCellsHandler.refresh();
+				}
+			}
+		}
+	}
+
+	return isChanged;
+};
+
+/**
+ * Function: getCellGeometry
+ * 
+ * Returns the <mxGeometry> for the given cell. This implementation uses
+ * <mxGraphModel.getGeometry>. Subclasses can override this to implement
+ * specific geometries for cells in only one graph, that is, it can return
+ * geometries that depend on the current state of the view.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose geometry should be returned.
+ */
+mxGraph.prototype.getCellGeometry = function(cell)
+{
+	return this.model.getGeometry(cell);
+};
+
+/**
+ * Function: isCellVisible
+ * 
+ * Returns true if the given cell is visible in this graph. This
+ * implementation uses <mxGraphModel.isVisible>. Subclassers can override
+ * this to implement specific visibility for cells in only one graph, that
+ * is, without affecting the visible state of the cell.
+ * 
+ * When using dynamic filter expressions for cell visibility, then the
+ * graph should be revalidated after the filter expression has changed.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose visible state should be returned.
+ */
+mxGraph.prototype.isCellVisible = function(cell)
+{
+	return this.model.isVisible(cell);
+};
+
+/**
+ * Function: isCellCollapsed
+ * 
+ * Returns true if the given cell is collapsed in this graph. This
+ * implementation uses <mxGraphModel.isCollapsed>. Subclassers can override
+ * this to implement specific collapsed states for cells in only one graph,
+ * that is, without affecting the collapsed state of the cell.
+ * 
+ * When using dynamic filter expressions for the collapsed state, then the
+ * graph should be revalidated after the filter expression has changed.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose collapsed state should be returned.
+ */
+mxGraph.prototype.isCellCollapsed = function(cell)
+{
+	return this.model.isCollapsed(cell);
+};
+
+/**
+ * Function: isCellConnectable
+ * 
+ * Returns true if the given cell is connectable in this graph. This
+ * implementation uses <mxGraphModel.isConnectable>. Subclassers can override
+ * this to implement specific connectable states for cells in only one graph,
+ * that is, without affecting the connectable state of the cell in the model.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose connectable state should be returned.
+ */
+mxGraph.prototype.isCellConnectable = function(cell)
+{
+	return this.model.isConnectable(cell);
+};
+
+/**
+ * Function: isOrthogonal
+ * 
+ * Returns true if perimeter points should be computed such that the
+ * resulting edge has only horizontal or vertical segments.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCellState> that represents the edge.
+ */
+mxGraph.prototype.isOrthogonal = function(edge)
+{
+	var orthogonal = edge.style[mxConstants.STYLE_ORTHOGONAL];
+	
+	if (orthogonal != null)
+	{
+		return orthogonal;
+	}
+	
+	var tmp = this.view.getEdgeStyle(edge);
+	
+	return tmp == mxEdgeStyle.SegmentConnector ||
+		tmp == mxEdgeStyle.ElbowConnector ||
+		tmp == mxEdgeStyle.SideToSide ||
+		tmp == mxEdgeStyle.TopToBottom ||
+		tmp == mxEdgeStyle.EntityRelation ||
+		tmp == mxEdgeStyle.OrthConnector;
+};
+
+/**
+ * Function: isLoop
+ * 
+ * Returns true if the given cell state is a loop.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> that represents a potential loop.
+ */
+mxGraph.prototype.isLoop = function(state)
+{
+	var src = state.getVisibleTerminalState(true);
+	var trg = state.getVisibleTerminalState(false);
+	
+	return (src != null && src == trg);
+};
+
+/**
+ * Function: isCloneEvent
+ * 
+ * Returns true if the given event is a clone event. This implementation
+ * returns true if control is pressed.
+ */
+mxGraph.prototype.isCloneEvent = function(evt)
+{
+	return mxEvent.isControlDown(evt);
+};
+
+/**
+ * Function: isTransparentClickEvent
+ * 
+ * Hook for implementing click-through behaviour on selected cells. If this
+ * returns true the cell behind the selected cell will be selected. This
+ * implementation returns false;
+ */
+mxGraph.prototype.isTransparentClickEvent = function(evt)
+{
+	return false;
+};
+
+/**
+ * Function: isToggleEvent
+ * 
+ * Returns true if the given event is a toggle event. This implementation
+ * returns true if the meta key (Cmd) is pressed on Macs or if control is
+ * pressed on any other platform.
+ */
+mxGraph.prototype.isToggleEvent = function(evt)
+{
+	return (mxClient.IS_MAC) ? mxEvent.isMetaDown(evt) : mxEvent.isControlDown(evt);
+};
+
+/**
+ * Function: isGridEnabledEvent
+ * 
+ * Returns true if the given mouse event should be aligned to the grid.
+ */
+mxGraph.prototype.isGridEnabledEvent = function(evt)
+{
+	return evt != null && !mxEvent.isAltDown(evt);
+};
+
+/**
+ * Function: isConstrainedEvent
+ * 
+ * Returns true if the given mouse event should be aligned to the grid.
+ */
+mxGraph.prototype.isConstrainedEvent = function(evt)
+{
+	return mxEvent.isShiftDown(evt);
+};
+
+/**
+ * Function: isIgnoreTerminalEvent
+ * 
+ * Returns true if the given mouse event should not allow any connections to be
+ * made. This implementation returns false.
+ */
+mxGraph.prototype.isIgnoreTerminalEvent = function(evt)
+{
+	return false;
+};
+
+/**
+ * Group: Validation
+ */
+
+/**
+ * Function: validationAlert
+ * 
+ * Displays the given validation error in a dialog. This implementation uses
+ * mxUtils.alert.
+ */
+mxGraph.prototype.validationAlert = function(message)
+{
+	mxUtils.alert(message);
+};
+
+/**
+ * Function: isEdgeValid
+ * 
+ * Checks if the return value of <getEdgeValidationError> for the given
+ * arguments is null.
+ *  
+ * Parameters:
+ * 
+ * edge - <mxCell> that represents the edge to validate.
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ */
+mxGraph.prototype.isEdgeValid = function(edge, source, target)
+{
+	return this.getEdgeValidationError(edge, source, target) == null;
+};
+
+/**
+ * Function: getEdgeValidationError
+ * 
+ * Returns the validation error message to be displayed when inserting or
+ * changing an edges' connectivity. A return value of null means the edge
+ * is valid, a return value of '' means it's not valid, but do not display
+ * an error message. Any other (non-empty) string returned from this method
+ * is displayed as an error message when trying to connect an edge to a
+ * source and target. This implementation uses the <multiplicities>, and
+ * checks <multigraph>, <allowDanglingEdges> and <allowLoops> to generate
+ * validation errors.
+ * 
+ * For extending this method with specific checks for source/target cells,
+ * the method can be extended as follows. Returning an empty string means
+ * the edge is invalid with no error message, a non-null string specifies
+ * the error message, and null means the edge is valid.
+ * 
+ * (code)
+ * graph.getEdgeValidationError = function(edge, source, target)
+ * {
+ *   if (source != null && target != null &&
+ *     this.model.getValue(source) != null &&
+ *     this.model.getValue(target) != null)
+ *   {
+ *     if (target is not valid for source)
+ *     {
+ *       return 'Invalid Target';
+ *     }
+ *   }
+ *   
+ *   // "Supercall"
+ *   return mxGraph.prototype.getEdgeValidationError.apply(this, arguments);
+ * }
+ * (end)
+ *  
+ * Parameters:
+ * 
+ * edge - <mxCell> that represents the edge to validate.
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ */
+mxGraph.prototype.getEdgeValidationError = function(edge, source, target)
+{
+	if (edge != null && !this.isAllowDanglingEdges() && (source == null || target == null))
+	{
+		return '';
+	}
+	
+	if (edge != null && this.model.getTerminal(edge, true) == null &&
+		this.model.getTerminal(edge, false) == null)	
+	{
+		return null;
+	}
+	
+	// Checks if we're dealing with a loop
+	if (!this.allowLoops && source == target && source != null)
+	{
+		return '';
+	}
+	
+	// Checks if the connection is generally allowed
+	if (!this.isValidConnection(source, target))
+	{
+		return '';
+	}
+
+	if (source != null && target != null)
+	{
+		var error = '';
+
+		// Checks if the cells are already connected
+		// and adds an error message if required			
+		if (!this.multigraph)
+		{
+			var tmp = this.model.getEdgesBetween(source, target, true);
+			
+			// Checks if the source and target are not connected by another edge
+			if (tmp.length > 1 || (tmp.length == 1 && tmp[0] != edge))
+			{
+				error += (mxResources.get(this.alreadyConnectedResource) ||
+					this.alreadyConnectedResource)+'\n';
+			}
+		}
+
+		// Gets the number of outgoing edges from the source
+		// and the number of incoming edges from the target
+		// without counting the edge being currently changed.
+		var sourceOut = this.model.getDirectedEdgeCount(source, true, edge);
+		var targetIn = this.model.getDirectedEdgeCount(target, false, edge);
+
+		// Checks the change against each multiplicity rule
+		if (this.multiplicities != null)
+		{
+			for (var i = 0; i < this.multiplicities.length; i++)
+			{
+				var err = this.multiplicities[i].check(this, edge, source,
+					target, sourceOut, targetIn);
+				
+				if (err != null)
+				{
+					error += err;
+				}
+			}
+		}
+
+		// Validates the source and target terminals independently
+		var err = this.validateEdge(edge, source, target);
+		
+		if (err != null)
+		{
+			error += err;
+		}
+		
+		return (error.length > 0) ? error : null;
+	}
+	
+	return (this.allowDanglingEdges) ? null : '';
+};
+
+/**
+ * Function: validateEdge
+ * 
+ * Hook method for subclassers to return an error message for the given
+ * edge and terminals. This implementation returns null.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> that represents the edge to validate.
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ */
+mxGraph.prototype.validateEdge = function(edge, source, target)
+{
+	return null;
+};
+
+/**
+ * Function: validateGraph
+ * 
+ * Validates the graph by validating each descendant of the given cell or
+ * the root of the model. Context is an object that contains the validation
+ * state for the complete validation run. The validation errors are
+ * attached to their cells using <setCellWarning>. Returns null in the case of
+ * successful validation or an array of strings (warnings) in the case of
+ * failed validations.
+ * 
+ * Paramters:
+ * 
+ * cell - Optional <mxCell> to start the validation recursion. Default is
+ * the graph root.
+ * context - Object that represents the global validation state.
+ */
+mxGraph.prototype.validateGraph = function(cell, context)
+{
+	cell = (cell != null) ? cell : this.model.getRoot();
+	context = (context != null) ? context : new Object();
+	
+	var isValid = true;
+	var childCount = this.model.getChildCount(cell);
+	
+	for (var i = 0; i < childCount; i++)
+	{
+		var tmp = this.model.getChildAt(cell, i);
+		var ctx = context;
+		
+		if (this.isValidRoot(tmp))
+		{
+			ctx = new Object();
+		}
+		
+		var warn = this.validateGraph(tmp, ctx);
+		
+		if (warn != null)
+		{
+			this.setCellWarning(tmp, warn.replace(/\n/g, '<br>'));
+		}
+		else
+		{
+			this.setCellWarning(tmp, null);
+		}
+		
+		isValid = isValid && warn == null;
+	}
+	
+	var warning = '';
+	
+	// Adds error for invalid children if collapsed (children invisible)
+	if (this.isCellCollapsed(cell) && !isValid)
+	{
+		warning += (mxResources.get(this.containsValidationErrorsResource) ||
+			this.containsValidationErrorsResource) + '\n';
+	}
+	
+	// Checks edges and cells using the defined multiplicities
+	if (this.model.isEdge(cell))
+	{
+		warning += this.getEdgeValidationError(cell,
+		this.model.getTerminal(cell, true),
+		this.model.getTerminal(cell, false)) || '';
+	}
+	else
+	{
+		warning += this.getCellValidationError(cell) || '';
+	}
+	
+	// Checks custom validation rules
+	var err = this.validateCell(cell, context);
+	
+	if (err != null)
+	{
+		warning += err;
+	}
+	
+	// Updates the display with the warning icons
+	// before any potential alerts are displayed.
+	// LATER: Move this into addCellOverlay. Redraw
+	// should check if overlay was added or removed.
+	if (this.model.getParent(cell) == null)
+	{
+		this.view.validate();
+	}
+
+	return (warning.length > 0 || !isValid) ? warning : null;
+};
+
+/**
+ * Function: getCellValidationError
+ * 
+ * Checks all <multiplicities> that cannot be enforced while the graph is
+ * being modified, namely, all multiplicities that require a minimum of
+ * 1 edge.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> for which the multiplicities should be checked.
+ */
+mxGraph.prototype.getCellValidationError = function(cell)
+{
+	var outCount = this.model.getDirectedEdgeCount(cell, true);
+	var inCount = this.model.getDirectedEdgeCount(cell, false);
+	var value = this.model.getValue(cell);
+	var error = '';
+
+	if (this.multiplicities != null)
+	{
+		for (var i = 0; i < this.multiplicities.length; i++)
+		{
+			var rule = this.multiplicities[i];
+			
+			if (rule.source && mxUtils.isNode(value, rule.type,
+				rule.attr, rule.value) && (outCount > rule.max ||
+				outCount < rule.min))
+			{
+				error += rule.countError + '\n';
+			}
+			else if (!rule.source && mxUtils.isNode(value, rule.type,
+					rule.attr, rule.value) && (inCount > rule.max ||
+					inCount < rule.min))
+			{
+				error += rule.countError + '\n';
+			}
+		}
+	}
+
+	return (error.length > 0) ? error : null;
+};
+
+/**
+ * Function: validateCell
+ * 
+ * Hook method for subclassers to return an error message for the given
+ * cell and validation context. This implementation returns null. Any HTML
+ * breaks will be converted to linefeeds in the calling method.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the cell to validate.
+ * context - Object that represents the global validation state.
+ */
+mxGraph.prototype.validateCell = function(cell, context)
+{
+	return null;
+};
+
+/**
+ * Group: Graph appearance
+ */
+
+/**
+ * Function: getBackgroundImage
+ * 
+ * Returns the <backgroundImage> as an <mxImage>.
+ */
+mxGraph.prototype.getBackgroundImage = function()
+{
+	return this.backgroundImage;
+};
+
+/**
+ * Function: setBackgroundImage
+ * 
+ * Sets the new <backgroundImage>.
+ * 
+ * Parameters:
+ * 
+ * image - New <mxImage> to be used for the background.
+ */
+mxGraph.prototype.setBackgroundImage = function(image)
+{
+	this.backgroundImage = image;
+};
+
+/**
+ * Function: getFoldingImage
+ * 
+ * Returns the <mxImage> used to display the collapsed state of
+ * the specified cell state. This returns null for all edges.
+ */
+mxGraph.prototype.getFoldingImage = function(state)
+{
+	if (state != null && this.foldingEnabled && !this.getModel().isEdge(state.cell))
+	{
+		var tmp = this.isCellCollapsed(state.cell);
+		
+		if (this.isCellFoldable(state.cell, !tmp))
+		{
+			return (tmp) ? this.collapsedImage : this.expandedImage;
+		}
+	}
+	
+	return null;
+};
+
+/**
+ * Function: convertValueToString
+ * 
+ * Returns the textual representation for the given cell. This
+ * implementation returns the nodename or string-representation of the user
+ * object.
+ *
+ * Example:
+ * 
+ * The following returns the label attribute from the cells user
+ * object if it is an XML node.
+ * 
+ * (code)
+ * graph.convertValueToString = function(cell)
+ * {
+ * 	return cell.getAttribute('label');
+ * }
+ * (end)
+ * 
+ * See also: <cellLabelChanged>.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose textual representation should be returned.
+ */
+mxGraph.prototype.convertValueToString = function(cell)
+{
+	var value = this.model.getValue(cell);
+	
+	if (value != null)
+	{
+		if (mxUtils.isNode(value))
+		{
+			return value.nodeName;
+		}
+		else if (typeof(value.toString) == 'function')
+		{
+			return value.toString();
+		}
+	}
+	
+	return '';
+};
+
+/**
+ * Function: getLabel
+ * 
+ * Returns a string or DOM node that represents the label for the given
+ * cell. This implementation uses <convertValueToString> if <labelsVisible>
+ * is true. Otherwise it returns an empty string.
+ * 
+ * To truncate a label to match the size of the cell, the following code
+ * can be used.
+ * 
+ * (code)
+ * graph.getLabel = function(cell)
+ * {
+ *   var label = mxGraph.prototype.getLabel.apply(this, arguments);
+ * 
+ *   if (label != null && this.model.isVertex(cell))
+ *   {
+ *     var geo = this.getCellGeometry(cell);
+ * 
+ *     if (geo != null)
+ *     {
+ *       var max = parseInt(geo.width / 8);
+ * 
+ *       if (label.length > max)
+ *       {
+ *         label = label.substring(0, max)+'...';
+ *       }
+ *     }
+ *   } 
+ *   return mxUtils.htmlEntities(label);
+ * }
+ * (end)
+ * 
+ * A resize listener is needed in the graph to force a repaint of the label
+ * after a resize.
+ * 
+ * (code)
+ * graph.addListener(mxEvent.RESIZE_CELLS, function(sender, evt)
+ * {
+ *   var cells = evt.getProperty('cells');
+ * 
+ *   for (var i = 0; i < cells.length; i++)
+ *   {
+ *     this.view.removeState(cells[i]);
+ *   }
+ * });
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose label should be returned.
+ */
+mxGraph.prototype.getLabel = function(cell)
+{
+	var result = '';
+	
+	if (this.labelsVisible && cell != null)
+	{
+		var state = this.view.getState(cell);
+		var style = (state != null) ? state.style : this.getCellStyle(cell);
+		
+		if (!mxUtils.getValue(style, mxConstants.STYLE_NOLABEL, false))
+		{
+			result = this.convertValueToString(cell);
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: isHtmlLabel
+ * 
+ * Returns true if the label must be rendered as HTML markup. The default
+ * implementation returns <htmlLabels>.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose label should be displayed as HTML markup.
+ */
+mxGraph.prototype.isHtmlLabel = function(cell)
+{
+	return this.isHtmlLabels();
+};
+ 
+/**
+ * Function: isHtmlLabels
+ * 
+ * Returns <htmlLabels>.
+ */
+mxGraph.prototype.isHtmlLabels = function()
+{
+	return this.htmlLabels;
+};
+ 
+/**
+ * Function: setHtmlLabels
+ * 
+ * Sets <htmlLabels>.
+ */
+mxGraph.prototype.setHtmlLabels = function(value)
+{
+	this.htmlLabels = value;
+};
+
+/**
+ * Function: isWrapping
+ * 
+ * This enables wrapping for HTML labels.
+ * 
+ * Returns true if no white-space CSS style directive should be used for
+ * displaying the given cells label. This implementation returns true if
+ * <mxConstants.STYLE_WHITE_SPACE> in the style of the given cell is 'wrap'.
+ * 
+ * This is used as a workaround for IE ignoring the white-space directive
+ * of child elements if the directive appears in a parent element. It
+ * should be overridden to return true if a white-space directive is used
+ * in the HTML markup that represents the given cells label. In order for
+ * HTML markup to work in labels, <isHtmlLabel> must also return true
+ * for the given cell.
+ * 
+ * Example:
+ * 
+ * (code)
+ * graph.getLabel = function(cell)
+ * {
+ *   var tmp = mxGraph.prototype.getLabel.apply(this, arguments); // "supercall"
+ *   
+ *   if (this.model.isEdge(cell))
+ *   {
+ *     tmp = '<div style="width: 150px; white-space:normal;">'+tmp+'</div>';
+ *   }
+ *   
+ *   return tmp;
+ * }
+ * 
+ * graph.isWrapping = function(state)
+ * {
+ * 	 return this.model.isEdge(state.cell);
+ * }
+ * (end)
+ * 
+ * Makes sure no edge label is wider than 150 pixels, otherwise the content
+ * is wrapped. Note: No width must be specified for wrapped vertex labels as
+ * the vertex defines the width in its geometry.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCell> whose label should be wrapped.
+ */
+mxGraph.prototype.isWrapping = function(cell)
+{
+	var state = this.view.getState(cell);
+	var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+	return (style != null) ? style[mxConstants.STYLE_WHITE_SPACE] == 'wrap' : false;
+};
+
+/**
+ * Function: isLabelClipped
+ * 
+ * Returns true if the overflow portion of labels should be hidden. If this
+ * returns true then vertex labels will be clipped to the size of the vertices.
+ * This implementation returns true if <mxConstants.STYLE_OVERFLOW> in the
+ * style of the given cell is 'hidden'.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCell> whose label should be clipped.
+ */
+mxGraph.prototype.isLabelClipped = function(cell)
+{
+	var state = this.view.getState(cell);
+	var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+	return (style != null) ? style[mxConstants.STYLE_OVERFLOW] == 'hidden' : false;
+};
+
+/**
+ * Function: getTooltip
+ * 
+ * Returns the string or DOM node that represents the tooltip for the given
+ * state, node and coordinate pair. This implementation checks if the given
+ * node is a folding icon or overlay and returns the respective tooltip. If
+ * this does not result in a tooltip, the handler for the cell is retrieved
+ * from <selectionCellsHandler> and the optional getTooltipForNode method is
+ * called. If no special tooltip exists here then <getTooltipForCell> is used
+ * with the cell in the given state as the argument to return a tooltip for the
+ * given state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose tooltip should be returned.
+ * node - DOM node that is currently under the mouse.
+ * x - X-coordinate of the mouse.
+ * y - Y-coordinate of the mouse.
+ */
+mxGraph.prototype.getTooltip = function(state, node, x, y)
+{
+	var tip = null;
+	
+	if (state != null)
+	{
+		// Checks if the mouse is over the folding icon
+		if (state.control != null && (node == state.control.node ||
+			node.parentNode == state.control.node))
+		{
+			tip = this.collapseExpandResource;
+			tip = mxUtils.htmlEntities(mxResources.get(tip) || tip).replace(/\\n/g, '<br>');
+		}
+
+		if (tip == null && state.overlays != null)
+		{
+			state.overlays.visit(function(id, shape)
+			{
+				// LATER: Exit loop if tip is not null
+				if (tip == null && (node == shape.node || node.parentNode == shape.node))
+				{
+					tip = shape.overlay.toString();
+				}
+			});
+		}
+		
+		if (tip == null)
+		{
+			var handler = this.selectionCellsHandler.getHandler(state.cell);
+			
+			if (handler != null && typeof(handler.getTooltipForNode) == 'function')
+			{
+				tip = handler.getTooltipForNode(node);
+			}
+		}
+		
+		if (tip == null)
+		{
+			tip = this.getTooltipForCell(state.cell);
+		}
+	}
+	
+	return tip;
+};
+
+/**
+ * Function: getTooltipForCell
+ * 
+ * Returns the string or DOM node to be used as the tooltip for the given
+ * cell. This implementation uses the cells getTooltip function if it
+ * exists, or else it returns <convertValueToString> for the cell.
+ * 
+ * Example:
+ * 
+ * (code)
+ * graph.getTooltipForCell = function(cell)
+ * {
+ *   return 'Hello, World!';
+ * }
+ * (end)
+ * 
+ * Replaces all tooltips with the string Hello, World!
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose tooltip should be returned.
+ */
+mxGraph.prototype.getTooltipForCell = function(cell)
+{
+	var tip = null;
+	
+	if (cell != null && cell.getTooltip != null)
+	{
+		tip = cell.getTooltip();
+	}
+	else
+	{
+		tip = this.convertValueToString(cell);
+	}
+	
+	return tip;
+};
+
+/**
+ * Function: getCursorForMouseEvent
+ * 
+ * Returns the cursor value to be used for the CSS of the shape for the
+ * given event. This implementation calls <getCursorForCell>.
+ * 
+ * Parameters:
+ * 
+ * me - <mxMouseEvent> whose cursor should be returned.
+ */
+mxGraph.prototype.getCursorForMouseEvent = function(me)
+{
+	return this.getCursorForCell(me.getCell());
+};
+
+/**
+ * Function: getCursorForCell
+ * 
+ * Returns the cursor value to be used for the CSS of the shape for the
+ * given cell. This implementation returns null.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose cursor should be returned.
+ */
+mxGraph.prototype.getCursorForCell = function(cell)
+{
+	return null;
+};
+
+/**
+ * Function: getStartSize
+ * 
+ * Returns the start size of the given swimlane, that is, the width or
+ * height of the part that contains the title, depending on the
+ * horizontal style. The return value is an <mxRectangle> with either
+ * width or height set as appropriate.
+ * 
+ * Parameters:
+ * 
+ * swimlane - <mxCell> whose start size should be returned.
+ */
+mxGraph.prototype.getStartSize = function(swimlane)
+{
+	var result = new mxRectangle();
+	var state = this.view.getState(swimlane);
+	var style = (state != null) ? state.style : this.getCellStyle(swimlane);
+	
+	if (style != null)
+	{
+		var size = parseInt(mxUtils.getValue(style,
+			mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE));
+		
+		if (mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, true))
+		{
+			result.height = size;
+		}
+		else
+		{
+			result.width = size;
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: getImage
+ * 
+ * Returns the image URL for the given cell state. This implementation
+ * returns the value stored under <mxConstants.STYLE_IMAGE> in the cell
+ * style.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose image URL should be returned.
+ */
+mxGraph.prototype.getImage = function(state)
+{
+	return (state != null && state.style != null) ? state.style[mxConstants.STYLE_IMAGE] : null;
+};
+
+/**
+ * Function: getVerticalAlign
+ * 
+ * Returns the vertical alignment for the given cell state. This
+ * implementation returns the value stored under
+ * <mxConstants.STYLE_VERTICAL_ALIGN> in the cell style.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose vertical alignment should be
+ * returned.
+ */
+mxGraph.prototype.getVerticalAlign = function(state)
+{
+	return (state != null && state.style != null) ?
+		(state.style[mxConstants.STYLE_VERTICAL_ALIGN] ||
+		mxConstants.ALIGN_MIDDLE) : null;
+};
+
+/**
+ * Function: getIndicatorColor
+ * 
+ * Returns the indicator color for the given cell state. This
+ * implementation returns the value stored under
+ * <mxConstants.STYLE_INDICATOR_COLOR> in the cell style.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose indicator color should be
+ * returned.
+ */
+mxGraph.prototype.getIndicatorColor = function(state)
+{
+	return (state != null && state.style != null) ? state.style[mxConstants.STYLE_INDICATOR_COLOR] : null;
+};
+
+/**
+ * Function: getIndicatorGradientColor
+ * 
+ * Returns the indicator gradient color for the given cell state. This
+ * implementation returns the value stored under
+ * <mxConstants.STYLE_INDICATOR_GRADIENTCOLOR> in the cell style.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose indicator gradient color should be
+ * returned.
+ */
+mxGraph.prototype.getIndicatorGradientColor = function(state)
+{
+	return (state != null && state.style != null) ? state.style[mxConstants.STYLE_INDICATOR_GRADIENTCOLOR] : null;
+};
+
+/**
+ * Function: getIndicatorShape
+ * 
+ * Returns the indicator shape for the given cell state. This
+ * implementation returns the value stored under
+ * <mxConstants.STYLE_INDICATOR_SHAPE> in the cell style.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose indicator shape should be returned.
+ */
+mxGraph.prototype.getIndicatorShape = function(state)
+{
+	return (state != null && state.style != null) ? state.style[mxConstants.STYLE_INDICATOR_SHAPE] : null;
+};
+
+/**
+ * Function: getIndicatorImage
+ * 
+ * Returns the indicator image for the given cell state. This
+ * implementation returns the value stored under
+ * <mxConstants.STYLE_INDICATOR_IMAGE> in the cell style.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose indicator image should be returned.
+ */
+mxGraph.prototype.getIndicatorImage = function(state)
+{
+	return (state != null && state.style != null) ? state.style[mxConstants.STYLE_INDICATOR_IMAGE] : null;
+};
+
+/**
+ * Function: getBorder
+ * 
+ * Returns the value of <border>.
+ */
+mxGraph.prototype.getBorder = function()
+{
+	return this.border;
+};
+
+/**
+ * Function: setBorder
+ * 
+ * Sets the value of <border>.
+ * 
+ * Parameters:
+ * 
+ * value - Positive integer that represents the border to be used.
+ */
+mxGraph.prototype.setBorder = function(value)
+{
+	this.border = value;
+};
+
+/**
+ * Function: isSwimlane
+ * 
+ * Returns true if the given cell is a swimlane in the graph. A swimlane is
+ * a container cell with some specific behaviour. This implementation
+ * checks if the shape associated with the given cell is a <mxSwimlane>.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to be checked.
+ */
+mxGraph.prototype.isSwimlane = function (cell)
+{
+	if (cell != null)
+	{
+		if (this.model.getParent(cell) != this.model.getRoot())
+		{
+			var state = this.view.getState(cell);
+			var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+			if (style != null && !this.model.isEdge(cell))
+			{
+				return style[mxConstants.STYLE_SHAPE] == mxConstants.SHAPE_SWIMLANE;
+			}
+		}
+	}
+	
+	return false;
+};
+
+/**
+ * Group: Graph behaviour
+ */
+
+/**
+ * Function: isResizeContainer
+ * 
+ * Returns <resizeContainer>.
+ */
+mxGraph.prototype.isResizeContainer = function()
+{
+	return this.resizeContainer;
+};
+
+/**
+ * Function: setResizeContainer
+ * 
+ * Sets <resizeContainer>.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if the container should be resized.
+ */
+mxGraph.prototype.setResizeContainer = function(value)
+{
+	this.resizeContainer = value;
+};
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns true if the graph is <enabled>.
+ */
+mxGraph.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ * 
+ * Specifies if the graph should allow any interactions. This
+ * implementation updates <enabled>.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if the graph should be enabled.
+ */
+mxGraph.prototype.setEnabled = function(value)
+{
+	this.enabled = value;
+};
+
+/**
+ * Function: isEscapeEnabled
+ * 
+ * Returns <escapeEnabled>.
+ */
+mxGraph.prototype.isEscapeEnabled = function()
+{
+	return this.escapeEnabled;
+};
+
+/**
+ * Function: setEscapeEnabled
+ * 
+ * Sets <escapeEnabled>.
+ * 
+ * Parameters:
+ * 
+ * enabled - Boolean indicating if escape should be enabled.
+ */
+mxGraph.prototype.setEscapeEnabled = function(value)
+{
+	this.escapeEnabled = value;
+};
+
+/**
+ * Function: isInvokesStopCellEditing
+ * 
+ * Returns <invokesStopCellEditing>.
+ */
+mxGraph.prototype.isInvokesStopCellEditing = function()
+{
+	return this.invokesStopCellEditing;
+};
+
+/**
+ * Function: setInvokesStopCellEditing
+ * 
+ * Sets <invokesStopCellEditing>.
+ */
+mxGraph.prototype.setInvokesStopCellEditing = function(value)
+{
+	this.invokesStopCellEditing = value;
+};
+
+/**
+ * Function: isEnterStopsCellEditing
+ * 
+ * Returns <enterStopsCellEditing>.
+ */
+mxGraph.prototype.isEnterStopsCellEditing = function()
+{
+	return this.enterStopsCellEditing;
+};
+
+/**
+ * Function: setEnterStopsCellEditing
+ * 
+ * Sets <enterStopsCellEditing>.
+ */
+mxGraph.prototype.setEnterStopsCellEditing = function(value)
+{
+	this.enterStopsCellEditing = value;
+};
+
+/**
+ * Function: isCellLocked
+ * 
+ * Returns true if the given cell may not be moved, sized, bended,
+ * disconnected, edited or selected. This implementation returns true for
+ * all vertices with a relative geometry if <locked> is false.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose locked state should be returned.
+ */
+mxGraph.prototype.isCellLocked = function(cell)
+{
+	var geometry = this.model.getGeometry(cell);
+	
+	return this.isCellsLocked() || (geometry != null && this.model.isVertex(cell) && geometry.relative);
+};
+
+/**
+ * Function: isCellsLocked
+ * 
+ * Returns true if the given cell may not be moved, sized, bended,
+ * disconnected, edited or selected. This implementation returns true for
+ * all vertices with a relative geometry if <locked> is false.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose locked state should be returned.
+ */
+mxGraph.prototype.isCellsLocked = function()
+{
+	return this.cellsLocked;
+};
+
+/**
+ * Function: setLocked
+ * 
+ * Sets if any cell may be moved, sized, bended, disconnected, edited or
+ * selected.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean that defines the new value for <cellsLocked>.
+ */
+mxGraph.prototype.setCellsLocked = function(value)
+{
+	this.cellsLocked = value;
+};
+
+/**
+ * Function: getCloneableCells
+ * 
+ * Returns the cells which may be exported in the given array of cells.
+ */
+mxGraph.prototype.getCloneableCells = function(cells)
+{
+	return this.model.filterCells(cells, mxUtils.bind(this, function(cell)
+	{
+		return this.isCellCloneable(cell);
+	}));
+};
+
+/**
+ * Function: isCellCloneable
+ * 
+ * Returns true if the given cell is cloneable. This implementation returns
+ * <isCellsCloneable> for all cells unless a cell style specifies
+ * <mxConstants.STYLE_CLONEABLE> to be 0. 
+ * 
+ * Parameters:
+ * 
+ * cell - Optional <mxCell> whose cloneable state should be returned.
+ */
+mxGraph.prototype.isCellCloneable = function(cell)
+{
+	var state = this.view.getState(cell);
+	var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+	return this.isCellsCloneable() && style[mxConstants.STYLE_CLONEABLE] != 0;
+};
+
+/**
+ * Function: isCellsCloneable
+ * 
+ * Returns <cellsCloneable>, that is, if the graph allows cloning of cells
+ * by using control-drag.
+ */
+mxGraph.prototype.isCellsCloneable = function()
+{
+	return this.cellsCloneable;
+};
+
+/**
+ * Function: setCellsCloneable
+ * 
+ * Specifies if the graph should allow cloning of cells by holding down the
+ * control key while cells are being moved. This implementation updates
+ * <cellsCloneable>.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if the graph should be cloneable.
+ */
+mxGraph.prototype.setCellsCloneable = function(value)
+{
+	this.cellsCloneable = value;
+};
+
+/**
+ * Function: getExportableCells
+ * 
+ * Returns the cells which may be exported in the given array of cells.
+ */
+mxGraph.prototype.getExportableCells = function(cells)
+{
+	return this.model.filterCells(cells, mxUtils.bind(this, function(cell)
+	{
+		return this.canExportCell(cell);
+	}));
+};
+
+/**
+ * Function: canExportCell
+ * 
+ * Returns true if the given cell may be exported to the clipboard. This
+ * implementation returns <exportEnabled> for all cells.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the cell to be exported.
+ */
+mxGraph.prototype.canExportCell = function(cell)
+{
+	return this.exportEnabled;
+};
+
+/**
+ * Function: getImportableCells
+ * 
+ * Returns the cells which may be imported in the given array of cells.
+ */
+mxGraph.prototype.getImportableCells = function(cells)
+{
+	return this.model.filterCells(cells, mxUtils.bind(this, function(cell)
+	{
+		return this.canImportCell(cell);
+	}));
+};
+
+/**
+ * Function: canImportCell
+ * 
+ * Returns true if the given cell may be imported from the clipboard.
+ * This implementation returns <importEnabled> for all cells.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the cell to be imported.
+ */
+mxGraph.prototype.canImportCell = function(cell)
+{
+	return this.importEnabled;
+};
+
+/**
+ * Function: isCellSelectable
+ *
+ * Returns true if the given cell is selectable. This implementation
+ * returns <cellsSelectable>.
+ * 
+ * To add a new style for making cells (un)selectable, use the following code.
+ * 
+ * (code)
+ * mxGraph.prototype.isCellSelectable = function(cell)
+ * {
+ *   var state = this.view.getState(cell);
+ *   var style = (state != null) ? state.style : this.getCellStyle(cell);
+ *   
+ *   return this.isCellsSelectable() && !this.isCellLocked(cell) && style['selectable'] != 0;
+ * };
+ * (end)
+ * 
+ * You can then use the new style as shown in this example.
+ * 
+ * (code)
+ * graph.insertVertex(parent, null, 'Hello,', 20, 20, 80, 30, 'selectable=0');
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose selectable state should be returned.
+ */
+mxGraph.prototype.isCellSelectable = function(cell)
+{
+	return this.isCellsSelectable();
+};
+
+/**
+ * Function: isCellsSelectable
+ *
+ * Returns <cellsSelectable>.
+ */
+mxGraph.prototype.isCellsSelectable = function()
+{
+	return this.cellsSelectable;
+};
+
+/**
+ * Function: setCellsSelectable
+ *
+ * Sets <cellsSelectable>.
+ */
+mxGraph.prototype.setCellsSelectable = function(value)
+{
+	this.cellsSelectable = value;
+};
+
+/**
+ * Function: getDeletableCells
+ * 
+ * Returns the cells which may be exported in the given array of cells.
+ */
+mxGraph.prototype.getDeletableCells = function(cells)
+{
+	return this.model.filterCells(cells, mxUtils.bind(this, function(cell)
+	{
+		return this.isCellDeletable(cell);
+	}));
+};
+
+/**
+ * Function: isCellDeletable
+ *
+ * Returns true if the given cell is moveable. This returns
+ * <cellsDeletable> for all given cells if a cells style does not specify
+ * <mxConstants.STYLE_DELETABLE> to be 0.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose deletable state should be returned.
+ */
+mxGraph.prototype.isCellDeletable = function(cell)
+{
+	var state = this.view.getState(cell);
+	var style = (state != null) ? state.style : this.getCellStyle(cell);
+	
+	return this.isCellsDeletable() && style[mxConstants.STYLE_DELETABLE] != 0;
+};
+
+/**
+ * Function: isCellsDeletable
+ *
+ * Returns <cellsDeletable>.
+ */
+mxGraph.prototype.isCellsDeletable = function()
+{
+	return this.cellsDeletable;
+};
+
+/**
+ * Function: setCellsDeletable
+ * 
+ * Sets <cellsDeletable>.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if the graph should allow deletion of cells.
+ */
+mxGraph.prototype.setCellsDeletable = function(value)
+{
+	this.cellsDeletable = value;
+};
+
+/**
+ * Function: isLabelMovable
+ *
+ * Returns true if the given edges's label is moveable. This returns
+ * <movable> for all given cells if <isLocked> does not return true
+ * for the given cell.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose label should be moved.
+ */
+mxGraph.prototype.isLabelMovable = function(cell)
+{
+	return !this.isCellLocked(cell) &&
+		((this.model.isEdge(cell) && this.edgeLabelsMovable) ||
+		(this.model.isVertex(cell) && this.vertexLabelsMovable));
+};
+
+/**
+ * Function: isCellRotatable
+ *
+ * Returns true if the given cell is rotatable. This returns true for the given
+ * cell if its style does not specify <mxConstants.STYLE_ROTATABLE> to be 0.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose rotatable state should be returned.
+ */
+mxGraph.prototype.isCellRotatable = function(cell)
+{
+	var state = this.view.getState(cell);
+	var style = (state != null) ? state.style : this.getCellStyle(cell);
+	
+	return style[mxConstants.STYLE_ROTATABLE] != 0;
+};
+
+/**
+ * Function: getMovableCells
+ * 
+ * Returns the cells which are movable in the given array of cells.
+ */
+mxGraph.prototype.getMovableCells = function(cells)
+{
+	return this.model.filterCells(cells, mxUtils.bind(this, function(cell)
+	{
+		return this.isCellMovable(cell);
+	}));
+};
+
+/**
+ * Function: isCellMovable
+ *
+ * Returns true if the given cell is moveable. This returns <cellsMovable>
+ * for all given cells if <isCellLocked> does not return true for the given
+ * cell and its style does not specify <mxConstants.STYLE_MOVABLE> to be 0.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose movable state should be returned.
+ */
+mxGraph.prototype.isCellMovable = function(cell)
+{
+	var state = this.view.getState(cell);
+	var style = (state != null) ? state.style : this.getCellStyle(cell);
+	
+	return this.isCellsMovable() && !this.isCellLocked(cell) && style[mxConstants.STYLE_MOVABLE] != 0;
+};
+
+/**
+ * Function: isCellsMovable
+ *
+ * Returns <cellsMovable>.
+ */
+mxGraph.prototype.isCellsMovable = function()
+{
+	return this.cellsMovable;
+};
+
+/**
+ * Function: setCellsMovable
+ * 
+ * Specifies if the graph should allow moving of cells. This implementation
+ * updates <cellsMsovable>.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if the graph should allow moving of cells.
+ */
+mxGraph.prototype.setCellsMovable = function(value)
+{
+	this.cellsMovable = value;
+};
+
+/**
+ * Function: isGridEnabled
+ *
+ * Returns <gridEnabled> as a boolean.
+ */
+mxGraph.prototype.isGridEnabled = function()
+{
+	return this.gridEnabled;
+};
+
+/**
+ * Function: setGridEnabled
+ * 
+ * Specifies if the grid should be enabled.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if the grid should be enabled.
+ */
+mxGraph.prototype.setGridEnabled = function(value)
+{
+	this.gridEnabled = value;
+};
+
+/**
+ * Function: isPortsEnabled
+ *
+ * Returns <portsEnabled> as a boolean.
+ */
+mxGraph.prototype.isPortsEnabled = function()
+{
+	return this.portsEnabled;
+};
+
+/**
+ * Function: setPortsEnabled
+ * 
+ * Specifies if the ports should be enabled.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if the ports should be enabled.
+ */
+mxGraph.prototype.setPortsEnabled = function(value)
+{
+	this.portsEnabled = value;
+};
+
+/**
+ * Function: getGridSize
+ *
+ * Returns <gridSize>.
+ */
+mxGraph.prototype.getGridSize = function()
+{
+	return this.gridSize;
+};
+
+/**
+ * Function: setGridSize
+ * 
+ * Sets <gridSize>.
+ */
+mxGraph.prototype.setGridSize = function(value)
+{
+	this.gridSize = value;
+};
+
+/**
+ * Function: getTolerance
+ *
+ * Returns <tolerance>.
+ */
+mxGraph.prototype.getTolerance = function()
+{
+	return this.tolerance;
+};
+
+/**
+ * Function: setTolerance
+ * 
+ * Sets <tolerance>.
+ */
+mxGraph.prototype.setTolerance = function(value)
+{
+	this.tolerance = value;
+};
+
+/**
+ * Function: isVertexLabelsMovable
+ *
+ * Returns <vertexLabelsMovable>.
+ */
+mxGraph.prototype.isVertexLabelsMovable = function()
+{
+	return this.vertexLabelsMovable;
+};
+
+/**
+ * Function: setVertexLabelsMovable
+ * 
+ * Sets <vertexLabelsMovable>.
+ */
+mxGraph.prototype.setVertexLabelsMovable = function(value)
+{
+	this.vertexLabelsMovable = value;
+};
+
+/**
+ * Function: isEdgeLabelsMovable
+ *
+ * Returns <edgeLabelsMovable>.
+ */
+mxGraph.prototype.isEdgeLabelsMovable = function()
+{
+	return this.edgeLabelsMovable;
+};
+
+/**
+ * Function: isEdgeLabelsMovable
+ * 
+ * Sets <edgeLabelsMovable>.
+ */
+mxGraph.prototype.setEdgeLabelsMovable = function(value)
+{
+	this.edgeLabelsMovable = value;
+};
+
+/**
+ * Function: isSwimlaneNesting
+ *
+ * Returns <swimlaneNesting> as a boolean.
+ */
+mxGraph.prototype.isSwimlaneNesting = function()
+{
+	return this.swimlaneNesting;
+};
+
+/**
+ * Function: setSwimlaneNesting
+ * 
+ * Specifies if swimlanes can be nested by drag and drop. This is only
+ * taken into account if dropEnabled is true.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if swimlanes can be nested.
+ */
+mxGraph.prototype.setSwimlaneNesting = function(value)
+{
+	this.swimlaneNesting = value;
+};
+
+/**
+ * Function: isSwimlaneSelectionEnabled
+ *
+ * Returns <swimlaneSelectionEnabled> as a boolean.
+ */
+mxGraph.prototype.isSwimlaneSelectionEnabled = function()
+{
+	return this.swimlaneSelectionEnabled;
+};
+
+/**
+ * Function: setSwimlaneSelectionEnabled
+ * 
+ * Specifies if swimlanes should be selected if the mouse is released
+ * over their content area.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if swimlanes content areas
+ * should be selected when the mouse is released over them.
+ */
+mxGraph.prototype.setSwimlaneSelectionEnabled = function(value)
+{
+	this.swimlaneSelectionEnabled = value;
+};
+
+/**
+ * Function: isMultigraph
+ *
+ * Returns <multigraph> as a boolean.
+ */
+mxGraph.prototype.isMultigraph = function()
+{
+	return this.multigraph;
+};
+
+/**
+ * Function: setMultigraph
+ * 
+ * Specifies if the graph should allow multiple connections between the
+ * same pair of vertices.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if the graph allows multiple connections
+ * between the same pair of vertices.
+ */
+mxGraph.prototype.setMultigraph = function(value)
+{
+	this.multigraph = value;
+};
+
+/**
+ * Function: isAllowLoops
+ *
+ * Returns <allowLoops> as a boolean.
+ */
+mxGraph.prototype.isAllowLoops = function()
+{
+	return this.allowLoops;
+};
+
+/**
+ * Function: setAllowDanglingEdges
+ * 
+ * Specifies if dangling edges are allowed, that is, if edges are allowed
+ * that do not have a source and/or target terminal defined.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if dangling edges are allowed.
+ */
+mxGraph.prototype.setAllowDanglingEdges = function(value)
+{
+	this.allowDanglingEdges = value;
+};
+
+/**
+ * Function: isAllowDanglingEdges
+ *
+ * Returns <allowDanglingEdges> as a boolean.
+ */
+mxGraph.prototype.isAllowDanglingEdges = function()
+{
+	return this.allowDanglingEdges;
+};
+
+/**
+ * Function: setConnectableEdges
+ * 
+ * Specifies if edges should be connectable.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if edges should be connectable.
+ */
+mxGraph.prototype.setConnectableEdges = function(value)
+{
+	this.connectableEdges = value;
+};
+
+/**
+ * Function: isConnectableEdges
+ *
+ * Returns <connectableEdges> as a boolean.
+ */
+mxGraph.prototype.isConnectableEdges = function()
+{
+	return this.connectableEdges;
+};
+
+/**
+ * Function: setCloneInvalidEdges
+ * 
+ * Specifies if edges should be inserted when cloned but not valid wrt.
+ * <getEdgeValidationError>. If false such edges will be silently ignored.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if cloned invalid edges should be
+ * inserted into the graph or ignored.
+ */
+mxGraph.prototype.setCloneInvalidEdges = function(value)
+{
+	this.cloneInvalidEdges = value;
+};
+
+/**
+ * Function: isCloneInvalidEdges
+ *
+ * Returns <cloneInvalidEdges> as a boolean.
+ */
+mxGraph.prototype.isCloneInvalidEdges = function()
+{
+	return this.cloneInvalidEdges;
+};
+
+/**
+ * Function: setAllowLoops
+ * 
+ * Specifies if loops are allowed.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if loops are allowed.
+ */
+mxGraph.prototype.setAllowLoops = function(value)
+{
+	this.allowLoops = value;
+};
+
+/**
+ * Function: isDisconnectOnMove
+ *
+ * Returns <disconnectOnMove> as a boolean.
+ */
+mxGraph.prototype.isDisconnectOnMove = function()
+{
+	return this.disconnectOnMove;
+};
+
+/**
+ * Function: setDisconnectOnMove
+ * 
+ * Specifies if edges should be disconnected when moved. (Note: Cloned
+ * edges are always disconnected.)
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if edges should be disconnected
+ * when moved.
+ */
+mxGraph.prototype.setDisconnectOnMove = function(value)
+{
+	this.disconnectOnMove = value;
+};
+
+/**
+ * Function: isDropEnabled
+ *
+ * Returns <dropEnabled> as a boolean.
+ */
+mxGraph.prototype.isDropEnabled = function()
+{
+	return this.dropEnabled;
+};
+
+/**
+ * Function: setDropEnabled
+ * 
+ * Specifies if the graph should allow dropping of cells onto or into other
+ * cells.
+ * 
+ * Parameters:
+ * 
+ * dropEnabled - Boolean indicating if the graph should allow dropping
+ * of cells into other cells.
+ */
+mxGraph.prototype.setDropEnabled = function(value)
+{
+	this.dropEnabled = value;
+};
+
+/**
+ * Function: isSplitEnabled
+ *
+ * Returns <splitEnabled> as a boolean.
+ */
+mxGraph.prototype.isSplitEnabled = function()
+{
+	return this.splitEnabled;
+};
+
+/**
+ * Function: setSplitEnabled
+ * 
+ * Specifies if the graph should allow dropping of cells onto or into other
+ * cells.
+ * 
+ * Parameters:
+ * 
+ * dropEnabled - Boolean indicating if the graph should allow dropping
+ * of cells into other cells.
+ */
+mxGraph.prototype.setSplitEnabled = function(value)
+{
+	this.splitEnabled = value;
+};
+
+/**
+ * Function: isCellResizable
+ *
+ * Returns true if the given cell is resizable. This returns
+ * <cellsResizable> for all given cells if <isCellLocked> does not return
+ * true for the given cell and its style does not specify
+ * <mxConstants.STYLE_RESIZABLE> to be 0.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose resizable state should be returned.
+ */
+mxGraph.prototype.isCellResizable = function(cell)
+{
+	var state = this.view.getState(cell);
+	var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+	return this.isCellsResizable() && !this.isCellLocked(cell) &&
+		mxUtils.getValue(style, mxConstants.STYLE_RESIZABLE, '1') != '0';
+};
+
+/**
+ * Function: isCellsResizable
+ *
+ * Returns <cellsResizable>.
+ */
+mxGraph.prototype.isCellsResizable = function()
+{
+	return this.cellsResizable;
+};
+
+/**
+ * Function: setCellsResizable
+ * 
+ * Specifies if the graph should allow resizing of cells. This
+ * implementation updates <cellsResizable>.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if the graph should allow resizing of
+ * cells.
+ */
+mxGraph.prototype.setCellsResizable = function(value)
+{
+	this.cellsResizable = value;
+};
+
+/**
+ * Function: isTerminalPointMovable
+ *
+ * Returns true if the given terminal point is movable. This is independent
+ * from <isCellConnectable> and <isCellDisconnectable> and controls if terminal
+ * points can be moved in the graph if the edge is not connected. Note that it
+ * is required for this to return true to connect unconnected edges. This
+ * implementation returns true.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose terminal point should be moved.
+ * source - Boolean indicating if the source or target terminal should be moved.
+ */
+mxGraph.prototype.isTerminalPointMovable = function(cell, source)
+{
+	return true;
+};
+
+/**
+ * Function: isCellBendable
+ *
+ * Returns true if the given cell is bendable. This returns <cellsBendable>
+ * for all given cells if <isLocked> does not return true for the given
+ * cell and its style does not specify <mxConstants.STYLE_BENDABLE> to be 0.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose bendable state should be returned.
+ */
+mxGraph.prototype.isCellBendable = function(cell)
+{
+	var state = this.view.getState(cell);
+	var style = (state != null) ? state.style : this.getCellStyle(cell);
+	
+	return this.isCellsBendable() && !this.isCellLocked(cell) && style[mxConstants.STYLE_BENDABLE] != 0;
+};
+
+/**
+ * Function: isCellsBendable
+ *
+ * Returns <cellsBenadable>.
+ */
+mxGraph.prototype.isCellsBendable = function()
+{
+	return this.cellsBendable;
+};
+
+/**
+ * Function: setCellsBendable
+ * 
+ * Specifies if the graph should allow bending of edges. This
+ * implementation updates <bendable>.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if the graph should allow bending of
+ * edges.
+ */
+mxGraph.prototype.setCellsBendable = function(value)
+{
+	this.cellsBendable = value;
+};
+
+/**
+ * Function: isCellEditable
+ *
+ * Returns true if the given cell is editable. This returns <cellsEditable> for
+ * all given cells if <isCellLocked> does not return true for the given cell
+ * and its style does not specify <mxConstants.STYLE_EDITABLE> to be 0.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose editable state should be returned.
+ */
+mxGraph.prototype.isCellEditable = function(cell)
+{
+	var state = this.view.getState(cell);
+	var style = (state != null) ? state.style : this.getCellStyle(cell);
+	
+	return this.isCellsEditable() && !this.isCellLocked(cell) && style[mxConstants.STYLE_EDITABLE] != 0;
+};
+
+/**
+ * Function: isCellsEditable
+ *
+ * Returns <cellsEditable>.
+ */
+mxGraph.prototype.isCellsEditable = function()
+{
+	return this.cellsEditable;
+};
+
+/**
+ * Function: setCellsEditable
+ * 
+ * Specifies if the graph should allow in-place editing for cell labels.
+ * This implementation updates <cellsEditable>.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if the graph should allow in-place
+ * editing.
+ */
+mxGraph.prototype.setCellsEditable = function(value)
+{
+	this.cellsEditable = value;
+};
+
+/**
+ * Function: isCellDisconnectable
+ *
+ * Returns true if the given cell is disconnectable from the source or
+ * target terminal. This returns <isCellsDisconnectable> for all given
+ * cells if <isCellLocked> does not return true for the given cell.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose disconnectable state should be returned.
+ * terminal - <mxCell> that represents the source or target terminal.
+ * source - Boolean indicating if the source or target terminal is to be
+ * disconnected.
+ */
+mxGraph.prototype.isCellDisconnectable = function(cell, terminal, source)
+{
+	return this.isCellsDisconnectable() && !this.isCellLocked(cell);
+};
+
+/**
+ * Function: isCellsDisconnectable
+ *
+ * Returns <cellsDisconnectable>.
+ */
+mxGraph.prototype.isCellsDisconnectable = function()
+{
+	return this.cellsDisconnectable;
+};
+
+/**
+ * Function: setCellsDisconnectable
+ *
+ * Sets <cellsDisconnectable>.
+ */
+mxGraph.prototype.setCellsDisconnectable = function(value)
+{
+	this.cellsDisconnectable = value;
+};
+
+/**
+ * Function: isValidSource
+ * 
+ * Returns true if the given cell is a valid source for new connections.
+ * This implementation returns true for all non-null values and is
+ * called by is called by <isValidConnection>.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents a possible source or null.
+ */
+mxGraph.prototype.isValidSource = function(cell)
+{
+	return (cell == null && this.allowDanglingEdges) ||
+		(cell != null && (!this.model.isEdge(cell) ||
+		this.connectableEdges) && this.isCellConnectable(cell));
+};
+	
+/**
+ * Function: isValidTarget
+ * 
+ * Returns <isValidSource> for the given cell. This is called by
+ * <isValidConnection>.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents a possible target or null.
+ */
+mxGraph.prototype.isValidTarget = function(cell)
+{
+	return this.isValidSource(cell);
+};
+
+/**
+ * Function: isValidConnection
+ * 
+ * Returns true if the given target cell is a valid target for source.
+ * This is a boolean implementation for not allowing connections between
+ * certain pairs of vertices and is called by <getEdgeValidationError>.
+ * This implementation returns true if <isValidSource> returns true for
+ * the source and <isValidTarget> returns true for the target.
+ * 
+ * Parameters:
+ * 
+ * source - <mxCell> that represents the source cell.
+ * target - <mxCell> that represents the target cell.
+ */
+mxGraph.prototype.isValidConnection = function(source, target)
+{
+	return this.isValidSource(source) && this.isValidTarget(target);
+};
+
+/**
+ * Function: setConnectable
+ * 
+ * Specifies if the graph should allow new connections. This implementation
+ * updates <mxConnectionHandler.enabled> in <connectionHandler>.
+ * 
+ * Parameters:
+ * 
+ * connectable - Boolean indicating if new connections should be allowed.
+ */
+mxGraph.prototype.setConnectable = function(connectable)
+{
+	this.connectionHandler.setEnabled(connectable);
+};
+	
+/**
+ * Function: isConnectable
+ * 
+ * Returns true if the <connectionHandler> is enabled.
+ */
+mxGraph.prototype.isConnectable = function(connectable)
+{
+	return this.connectionHandler.isEnabled();
+};
+
+/**
+ * Function: setTooltips
+ * 
+ * Specifies if tooltips should be enabled. This implementation updates
+ * <mxTooltipHandler.enabled> in <tooltipHandler>.
+ * 
+ * Parameters:
+ * 
+ * enabled - Boolean indicating if tooltips should be enabled.
+ */
+mxGraph.prototype.setTooltips = function (enabled)
+{
+	this.tooltipHandler.setEnabled(enabled);
+};
+
+/**
+ * Function: setPanning
+ * 
+ * Specifies if panning should be enabled. This implementation updates
+ * <mxPanningHandler.panningEnabled> in <panningHandler>.
+ * 
+ * Parameters:
+ * 
+ * enabled - Boolean indicating if panning should be enabled.
+ */
+mxGraph.prototype.setPanning = function(enabled)
+{
+	this.panningHandler.panningEnabled = enabled;
+};
+
+/**
+ * Function: isEditing
+ * 
+ * Returns true if the given cell is currently being edited.
+ * If no cell is specified then this returns true if any
+ * cell is currently being edited.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> that should be checked.
+ */
+mxGraph.prototype.isEditing = function(cell)
+{
+	if (this.cellEditor != null)
+	{
+		var editingCell = this.cellEditor.getEditingCell();
+		
+		return (cell == null) ? editingCell != null : cell == editingCell;
+	}
+	
+	return false;
+};
+
+/**
+ * Function: isAutoSizeCell
+ * 
+ * Returns true if the size of the given cell should automatically be
+ * updated after a change of the label. This implementation returns
+ * <autoSizeCells> or checks if the cell style does specify
+ * <mxConstants.STYLE_AUTOSIZE> to be 1.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that should be resized.
+ */
+mxGraph.prototype.isAutoSizeCell = function(cell)
+{
+	var state = this.view.getState(cell);
+	var style = (state != null) ? state.style : this.getCellStyle(cell);
+	
+	return this.isAutoSizeCells() || style[mxConstants.STYLE_AUTOSIZE] == 1;
+};
+
+/**
+ * Function: isAutoSizeCells
+ * 
+ * Returns <autoSizeCells>.
+ */
+mxGraph.prototype.isAutoSizeCells = function()
+{
+	return this.autoSizeCells;
+};
+
+/**
+ * Function: setAutoSizeCells
+ * 
+ * Specifies if cell sizes should be automatically updated after a label
+ * change. This implementation sets <autoSizeCells> to the given parameter.
+ * To update the size of cells when the cells are added, set
+ * <autoSizeCellsOnAdd> to true.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if cells should be resized
+ * automatically.
+ */
+mxGraph.prototype.setAutoSizeCells = function(value)
+{
+	this.autoSizeCells = value;
+};
+
+/**
+ * Function: isExtendParent
+ * 
+ * Returns true if the parent of the given cell should be extended if the
+ * child has been resized so that it overlaps the parent. This
+ * implementation returns <isExtendParents> if the cell is not an edge.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that has been resized.
+ */
+mxGraph.prototype.isExtendParent = function(cell)
+{
+	return !this.getModel().isEdge(cell) && this.isExtendParents();
+};
+
+/**
+ * Function: isExtendParents
+ * 
+ * Returns <extendParents>.
+ */
+mxGraph.prototype.isExtendParents = function()
+{
+	return this.extendParents;
+};
+
+/**
+ * Function: setExtendParents
+ * 
+ * Sets <extendParents>.
+ * 
+ * Parameters:
+ * 
+ * value - New boolean value for <extendParents>.
+ */
+mxGraph.prototype.setExtendParents = function(value)
+{
+	this.extendParents = value;
+};
+
+/**
+ * Function: isExtendParentsOnAdd
+ * 
+ * Returns <extendParentsOnAdd>.
+ */
+mxGraph.prototype.isExtendParentsOnAdd = function(cell)
+{
+	return this.extendParentsOnAdd;
+};
+
+/**
+ * Function: setExtendParentsOnAdd
+ * 
+ * Sets <extendParentsOnAdd>.
+ * 
+ * Parameters:
+ * 
+ * value - New boolean value for <extendParentsOnAdd>.
+ */
+mxGraph.prototype.setExtendParentsOnAdd = function(value)
+{
+	this.extendParentsOnAdd = value;
+};
+
+/**
+ * Function: isExtendParentsOnMove
+ * 
+ * Returns <extendParentsOnMove>.
+ */
+mxGraph.prototype.isExtendParentsOnMove = function()
+{
+	return this.extendParentsOnMove;
+};
+
+/**
+ * Function: setExtendParentsOnMove
+ * 
+ * Sets <extendParentsOnMove>.
+ * 
+ * Parameters:
+ * 
+ * value - New boolean value for <extendParentsOnAdd>.
+ */
+mxGraph.prototype.setExtendParentsOnMove = function(value)
+{
+	this.extendParentsOnMove = value;
+};
+
+/**
+ * Function: isRecursiveResize
+ * 
+ * Returns <recursiveResize>.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> that is being resized.
+ */
+mxGraph.prototype.isRecursiveResize = function(state)
+{
+	return this.recursiveResize;
+};
+
+/**
+ * Function: setRecursiveResize
+ * 
+ * Sets <recursiveResize>.
+ * 
+ * Parameters:
+ * 
+ * value - New boolean value for <recursiveResize>.
+ */
+mxGraph.prototype.setRecursiveResize = function(value)
+{
+	this.recursiveResize = value;
+};
+
+/**
+ * Function: isConstrainChild
+ * 
+ * Returns true if the given cell should be kept inside the bounds of its
+ * parent according to the rules defined by <getOverlap> and
+ * <isAllowOverlapParent>. This implementation returns false for all children
+ * of edges and <isConstrainChildren> otherwise.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that should be constrained.
+ */
+mxGraph.prototype.isConstrainChild = function(cell)
+{
+	return this.isConstrainChildren() && !this.getModel().isEdge(this.getModel().getParent(cell));
+};
+
+/**
+ * Function: isConstrainChildren
+ * 
+ * Returns <constrainChildren>.
+ */
+mxGraph.prototype.isConstrainChildren = function()
+{
+	return this.constrainChildren;
+};
+
+/**
+ * Function: setConstrainChildren
+ * 
+ * Sets <constrainChildren>.
+ */
+mxGraph.prototype.setConstrainChildren = function(value)
+{
+	this.constrainChildren = value;
+};
+
+/**
+ * Function: isConstrainRelativeChildren
+ * 
+ * Returns <constrainRelativeChildren>.
+ */
+mxGraph.prototype.isConstrainRelativeChildren = function()
+{
+	return this.constrainRelativeChildren;
+};
+
+/**
+ * Function: setConstrainRelativeChildren
+ * 
+ * Sets <constrainRelativeChildren>.
+ */
+mxGraph.prototype.setConstrainRelativeChildren = function(value)
+{
+	this.constrainRelativeChildren = value;
+};
+
+/**
+ * Function: isConstrainChildren
+ * 
+ * Returns <allowNegativeCoordinates>.
+ */
+mxGraph.prototype.isAllowNegativeCoordinates = function()
+{
+	return this.allowNegativeCoordinates;
+};
+
+/**
+ * Function: setConstrainChildren
+ * 
+ * Sets <allowNegativeCoordinates>.
+ */
+mxGraph.prototype.setAllowNegativeCoordinates = function(value)
+{
+	this.allowNegativeCoordinates = value;
+};
+
+/**
+ * Function: getOverlap
+ * 
+ * Returns a decimal number representing the amount of the width and height
+ * of the given cell that is allowed to overlap its parent. A value of 0
+ * means all children must stay inside the parent, 1 means the child is
+ * allowed to be placed outside of the parent such that it touches one of
+ * the parents sides. If <isAllowOverlapParent> returns false for the given
+ * cell, then this method returns 0.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> for which the overlap ratio should be returned.
+ */
+mxGraph.prototype.getOverlap = function(cell)
+{
+	return (this.isAllowOverlapParent(cell)) ? this.defaultOverlap : 0;
+};
+	
+/**
+ * Function: isAllowOverlapParent
+ * 
+ * Returns true if the given cell is allowed to be placed outside of the
+ * parents area.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the child to be checked.
+ */
+mxGraph.prototype.isAllowOverlapParent = function(cell)
+{
+	return false;
+};
+
+/**
+ * Function: getFoldableCells
+ * 
+ * Returns the cells which are movable in the given array of cells.
+ */
+mxGraph.prototype.getFoldableCells = function(cells, collapse)
+{
+	return this.model.filterCells(cells, mxUtils.bind(this, function(cell)
+	{
+		return this.isCellFoldable(cell, collapse);
+	}));
+};
+
+/**
+ * Function: isCellFoldable
+ * 
+ * Returns true if the given cell is foldable. This implementation
+ * returns true if the cell has at least one child and its style
+ * does not specify <mxConstants.STYLE_FOLDABLE> to be 0.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose foldable state should be returned.
+ */
+mxGraph.prototype.isCellFoldable = function(cell, collapse)
+{
+	var state = this.view.getState(cell);
+	var style = (state != null) ? state.style : this.getCellStyle(cell);
+	
+	return this.model.getChildCount(cell) > 0 && style[mxConstants.STYLE_FOLDABLE] != 0;
+};
+
+/**
+ * Function: isValidDropTarget
+ *
+ * Returns true if the given cell is a valid drop target for the specified
+ * cells. If <splitEnabled> is true then this returns <isSplitTarget> for
+ * the given arguments else it returns true if the cell is not collapsed
+ * and its child count is greater than 0.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the possible drop target.
+ * cells - <mxCells> that should be dropped into the target.
+ * evt - Mouseevent that triggered the invocation.
+ */
+mxGraph.prototype.isValidDropTarget = function(cell, cells, evt)
+{
+	return cell != null && ((this.isSplitEnabled() &&
+		this.isSplitTarget(cell, cells, evt)) || (!this.model.isEdge(cell) &&
+		(this.isSwimlane(cell) || (this.model.getChildCount(cell) > 0 &&
+		!this.isCellCollapsed(cell)))));
+};
+
+/**
+ * Function: isSplitTarget
+ *
+ * Returns true if the given edge may be splitted into two edges with the
+ * given cell as a new terminal between the two.
+ * 
+ * Parameters:
+ * 
+ * target - <mxCell> that represents the edge to be splitted.
+ * cells - <mxCells> that should split the edge.
+ * evt - Mouseevent that triggered the invocation.
+ */
+mxGraph.prototype.isSplitTarget = function(target, cells, evt)
+{
+	if (this.model.isEdge(target) && cells != null && cells.length == 1 &&
+		this.isCellConnectable(cells[0]) && this.getEdgeValidationError(target,
+			this.model.getTerminal(target, true), cells[0]) == null)
+	{
+		var src = this.model.getTerminal(target, true);
+		var trg = this.model.getTerminal(target, false);
+
+		return (!this.model.isAncestor(cells[0], src) &&
+				!this.model.isAncestor(cells[0], trg));
+	}
+
+	return false;
+};
+
+/**
+ * Function: getDropTarget
+ * 
+ * Returns the given cell if it is a drop target for the given cells or the
+ * nearest ancestor that may be used as a drop target for the given cells.
+ * If the given array contains a swimlane and <swimlaneNesting> is false
+ * then this always returns null. If no cell is given, then the bottommost
+ * swimlane at the location of the given event is returned.
+ * 
+ * This function should only be used if <isDropEnabled> returns true.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> which are to be dropped onto the target.
+ * evt - Mouseevent for the drag and drop.
+ * cell - <mxCell> that is under the mousepointer.
+ * clone - Optional boolean to indicate of cells will be cloned.
+ */
+mxGraph.prototype.getDropTarget = function(cells, evt, cell, clone)
+{
+	if (!this.isSwimlaneNesting())
+	{
+		for (var i = 0; i < cells.length; i++)
+		{
+			if (this.isSwimlane(cells[i]))
+			{
+				return null;
+			}
+		}
+	}
+
+	var pt = mxUtils.convertPoint(this.container,
+		mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+	pt.x -= this.panDx;
+	pt.y -= this.panDy;
+	var swimlane = this.getSwimlaneAt(pt.x, pt.y);
+	
+	if (cell == null)
+	{
+		cell = swimlane;
+	}
+	else if (swimlane != null)
+	{
+		// Checks if the cell is an ancestor of the swimlane
+		// under the mouse and uses the swimlane in that case
+		var tmp = this.model.getParent(swimlane);
+		
+		while (tmp != null && this.isSwimlane(tmp) && tmp != cell)
+		{
+			tmp = this.model.getParent(tmp);
+		}
+		
+		if (tmp == cell)
+		{
+			cell = swimlane;
+		}
+	}
+	
+	while (cell != null && !this.isValidDropTarget(cell, cells, evt) &&
+		!this.model.isLayer(cell))
+	{
+		cell = this.model.getParent(cell);
+	}
+	
+	// Checks if parent is dropped into child if not cloning
+	if (clone == null || !clone)
+	{
+		var parent = cell;
+		
+		while (parent != null && mxUtils.indexOf(cells, parent) < 0)
+		{
+			parent = this.model.getParent(parent);
+		}
+	}
+
+	return (!this.model.isLayer(cell) && parent == null) ? cell : null;
+};
+
+/**
+ * Group: Cell retrieval
+ */
+
+/**
+ * Function: getDefaultParent
+ * 
+ * Returns <defaultParent> or <mxGraphView.currentRoot> or the first child
+ * child of <mxGraphModel.root> if both are null. The value returned by
+ * this function should be used as the parent for new cells (aka default
+ * layer).
+ */
+mxGraph.prototype.getDefaultParent = function()
+{
+	var parent = this.getCurrentRoot();
+	
+	if (parent == null)
+	{
+		parent = this.defaultParent;
+		
+		if (parent == null)
+		{
+			var root = this.model.getRoot();
+			parent = this.model.getChildAt(root, 0);
+		}
+	}
+	
+	return parent;
+};
+
+/**
+ * Function: setDefaultParent
+ * 
+ * Sets the <defaultParent> to the given cell. Set this to null to return
+ * the first child of the root in getDefaultParent.
+ */
+mxGraph.prototype.setDefaultParent = function(cell)
+{
+	this.defaultParent = cell;
+};
+
+/**
+ * Function: getSwimlane
+ * 
+ * Returns the nearest ancestor of the given cell which is a swimlane, or
+ * the given cell, if it is itself a swimlane.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> for which the ancestor swimlane should be returned.
+ */
+mxGraph.prototype.getSwimlane = function(cell)
+{
+	while (cell != null && !this.isSwimlane(cell))
+	{
+		cell = this.model.getParent(cell);
+	}
+	
+	return cell;
+};
+
+/**
+ * Function: getSwimlaneAt
+ * 
+ * Returns the bottom-most swimlane that intersects the given point (x, y)
+ * in the cell hierarchy that starts at the given parent.
+ * 
+ * Parameters:
+ * 
+ * x - X-coordinate of the location to be checked.
+ * y - Y-coordinate of the location to be checked.
+ * parent - <mxCell> that should be used as the root of the recursion.
+ * Default is <defaultParent>.
+ */
+mxGraph.prototype.getSwimlaneAt = function (x, y, parent)
+{
+	parent = parent || this.getDefaultParent();
+	
+	if (parent != null)
+	{
+		var childCount = this.model.getChildCount(parent);
+		
+		for (var i = 0; i < childCount; i++)
+		{
+			var child = this.model.getChildAt(parent, i);
+			var result = this.getSwimlaneAt(x, y, child);
+			
+			if (result != null)
+			{
+				return result;
+			}
+			else if (this.isSwimlane(child))
+			{
+				var state = this.view.getState(child);
+				
+				if (this.intersects(state, x, y))
+				{
+					return child;
+				}
+			}
+		}
+	}
+	
+	return null;
+};
+
+/**
+ * Function: getCellAt
+ * 
+ * Returns the bottom-most cell that intersects the given point (x, y) in
+ * the cell hierarchy starting at the given parent. This will also return
+ * swimlanes if the given location intersects the content area of the
+ * swimlane. If this is not desired, then the <hitsSwimlaneContent> may be
+ * used if the returned cell is a swimlane to determine if the location
+ * is inside the content area or on the actual title of the swimlane.
+ * 
+ * Parameters:
+ * 
+ * x - X-coordinate of the location to be checked.
+ * y - Y-coordinate of the location to be checked.
+ * parent - <mxCell> that should be used as the root of the recursion.
+ * Default is current root of the view or the root of the model.
+ * vertices - Optional boolean indicating if vertices should be returned.
+ * Default is true.
+ * edges - Optional boolean indicating if edges should be returned. Default
+ * is true.
+ * ignoreFn - Optional function that returns true if cell should be ignored.
+ * The function is passed the cell state and the x and y parameter.
+ */
+mxGraph.prototype.getCellAt = function(x, y, parent, vertices, edges, ignoreFn)
+{
+	vertices = (vertices != null) ? vertices : true;
+	edges = (edges != null) ? edges : true;
+
+	if (parent == null)
+	{
+		parent = this.getCurrentRoot();
+		
+		if (parent == null)
+		{
+			parent = this.getModel().getRoot();
+		}
+	}
+
+	if (parent != null)
+	{
+		var childCount = this.model.getChildCount(parent);
+		
+		for (var i = childCount - 1; i >= 0; i--)
+		{
+			var cell = this.model.getChildAt(parent, i);
+			var result = this.getCellAt(x, y, cell, vertices, edges, ignoreFn);
+			
+			if (result != null)
+			{
+				return result;
+			}
+			else if (this.isCellVisible(cell) && (edges && this.model.isEdge(cell) ||
+				vertices && this.model.isVertex(cell)))
+			{
+				var state = this.view.getState(cell);
+
+				if (state != null && (ignoreFn == null || !ignoreFn(state, x, y)) &&
+					this.intersects(state, x, y))
+				{
+					return cell;
+				}
+			}
+		}
+	}
+	
+	return null;
+};
+
+/**
+ * Function: intersects
+ * 
+ * Returns the bottom-most cell that intersects the given point (x, y) in
+ * the cell hierarchy that starts at the given parent.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> that represents the cell state.
+ * x - X-coordinate of the location to be checked.
+ * y - Y-coordinate of the location to be checked.
+ */
+mxGraph.prototype.intersects = function(state, x, y)
+{
+	if (state != null)
+	{
+		var pts = state.absolutePoints;
+
+		if (pts != null)
+		{
+			var t2 = this.tolerance * this.tolerance;
+			var pt = pts[0];
+			
+			for (var i = 1; i < pts.length; i++)
+			{
+				var next = pts[i];
+				var dist = mxUtils.ptSegDistSq(pt.x, pt.y, next.x, next.y, x, y);
+				
+				if (dist <= t2)
+				{
+					return true;
+				}
+				
+				pt = next;
+			}
+		}
+		else
+		{
+			var alpha = mxUtils.toRadians(mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION) || 0);
+			
+			if (alpha != 0)
+			{
+				var cos = Math.cos(-alpha);
+				var sin = Math.sin(-alpha);
+				var cx = new mxPoint(state.getCenterX(), state.getCenterY());
+				var pt = mxUtils.getRotatedPoint(new mxPoint(x, y), cos, sin, cx);
+				x = pt.x;
+				y = pt.y;
+			}
+			
+			if (mxUtils.contains(state, x, y))
+			{
+				return true;
+			}
+		}
+	}
+	
+	return false;
+};
+
+/**
+ * Function: hitsSwimlaneContent
+ * 
+ * Returns true if the given coordinate pair is inside the content
+ * are of the given swimlane.
+ * 
+ * Parameters:
+ * 
+ * swimlane - <mxCell> that specifies the swimlane.
+ * x - X-coordinate of the mouse event.
+ * y - Y-coordinate of the mouse event.
+ */
+mxGraph.prototype.hitsSwimlaneContent = function(swimlane, x, y)
+{
+	var state = this.getView().getState(swimlane);
+	var size = this.getStartSize(swimlane);
+	
+	if (state != null)
+	{
+		var scale = this.getView().getScale();
+		x -= state.x;
+		y -= state.y;
+		
+		if (size.width > 0 && x > 0 && x > size.width * scale)
+		{
+			return true;
+		}
+		else if (size.height > 0 && y > 0 && y > size.height * scale)
+		{
+			return true;
+		}
+	}
+	
+	return false;
+};
+
+/**
+ * Function: getChildVertices
+ * 
+ * Returns the visible child vertices of the given parent.
+ * 
+ * Parameters:
+ * 
+ * parent - <mxCell> whose children should be returned.
+ */
+mxGraph.prototype.getChildVertices = function(parent)
+{
+	return this.getChildCells(parent, true, false);
+};
+	
+/**
+ * Function: getChildEdges
+ * 
+ * Returns the visible child edges of the given parent.
+ * 
+ * Parameters:
+ * 
+ * parent - <mxCell> whose child vertices should be returned.
+ */
+mxGraph.prototype.getChildEdges = function(parent)
+{
+	return this.getChildCells(parent, false, true);
+};
+
+/**
+ * Function: getChildCells
+ * 
+ * Returns the visible child vertices or edges in the given parent. If
+ * vertices and edges is false, then all children are returned.
+ * 
+ * Parameters:
+ * 
+ * parent - <mxCell> whose children should be returned.
+ * vertices - Optional boolean that specifies if child vertices should
+ * be returned. Default is false.
+ * edges - Optional boolean that specifies if child edges should
+ * be returned. Default is false.
+ */
+mxGraph.prototype.getChildCells = function(parent, vertices, edges)
+{
+	parent = (parent != null) ? parent : this.getDefaultParent();
+	vertices = (vertices != null) ? vertices : false;
+	edges = (edges != null) ? edges : false;
+
+	var cells = this.model.getChildCells(parent, vertices, edges);
+	var result = [];
+
+	// Filters out the non-visible child cells
+	for (var i = 0; i < cells.length; i++)
+	{
+		if (this.isCellVisible(cells[i]))
+		{
+			result.push(cells[i]);
+		}
+	}
+
+	return result;
+};
+	
+/**
+ * Function: getConnections
+ * 
+ * Returns all visible edges connected to the given cell without loops.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose connections should be returned.
+ * parent - Optional parent of the opposite end for a connection to be
+ * returned.
+ */
+mxGraph.prototype.getConnections = function(cell, parent)
+{
+	return this.getEdges(cell, parent, true, true, false);
+};
+	
+/**
+ * Function: getIncomingEdges
+ * 
+ * Returns the visible incoming edges for the given cell. If the optional
+ * parent argument is specified, then only child edges of the given parent
+ * are returned.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose incoming edges should be returned.
+ * parent - Optional parent of the opposite end for an edge to be
+ * returned.
+ */
+mxGraph.prototype.getIncomingEdges = function(cell, parent)
+{
+	return this.getEdges(cell, parent, true, false, false);
+};
+	
+/**
+ * Function: getOutgoingEdges
+ * 
+ * Returns the visible outgoing edges for the given cell. If the optional
+ * parent argument is specified, then only child edges of the given parent
+ * are returned.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose outgoing edges should be returned.
+ * parent - Optional parent of the opposite end for an edge to be
+ * returned.
+ */
+mxGraph.prototype.getOutgoingEdges = function(cell, parent)
+{
+	return this.getEdges(cell, parent, false, true, false);
+};
+	
+/**
+ * Function: getEdges
+ * 
+ * Returns the incoming and/or outgoing edges for the given cell.
+ * If the optional parent argument is specified, then only edges are returned
+ * where the opposite is in the given parent cell. If at least one of incoming
+ * or outgoing is true, then loops are ignored, if both are false, then all
+ * edges connected to the given cell are returned including loops.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose edges should be returned.
+ * parent - Optional parent of the opposite end for an edge to be
+ * returned.
+ * incoming - Optional boolean that specifies if incoming edges should
+ * be included in the result. Default is true.
+ * outgoing - Optional boolean that specifies if outgoing edges should
+ * be included in the result. Default is true.
+ * includeLoops - Optional boolean that specifies if loops should be
+ * included in the result. Default is true.
+ * recurse - Optional boolean the specifies if the parent specified only 
+ * need be an ancestral parent, true, or the direct parent, false.
+ * Default is false
+ */
+mxGraph.prototype.getEdges = function(cell, parent, incoming, outgoing, includeLoops, recurse)
+{
+	incoming = (incoming != null) ? incoming : true;
+	outgoing = (outgoing != null) ? outgoing : true;
+	includeLoops = (includeLoops != null) ? includeLoops : true;
+	recurse = (recurse != null) ? recurse : false;
+	
+	var edges = [];
+	var isCollapsed = this.isCellCollapsed(cell);
+	var childCount = this.model.getChildCount(cell);
+
+	for (var i = 0; i < childCount; i++)
+	{
+		var child = this.model.getChildAt(cell, i);
+
+		if (isCollapsed || !this.isCellVisible(child))
+		{
+			edges = edges.concat(this.model.getEdges(child, incoming, outgoing));
+		}
+	}
+
+	edges = edges.concat(this.model.getEdges(cell, incoming, outgoing));
+	var result = [];
+	
+	for (var i = 0; i < edges.length; i++)
+	{
+		var state = this.view.getState(edges[i]);
+		
+		var source = (state != null) ? state.getVisibleTerminal(true) : this.view.getVisibleTerminal(edges[i], true);
+		var target = (state != null) ? state.getVisibleTerminal(false) : this.view.getVisibleTerminal(edges[i], false);
+
+		if ((includeLoops && source == target) || ((source != target) && ((incoming &&
+			target == cell && (parent == null || this.isValidAncestor(source, parent, recurse))) ||
+			(outgoing && source == cell && (parent == null ||
+					this.isValidAncestor(target, parent, recurse))))))
+		{
+			result.push(edges[i]);
+		}
+	}
+
+	return result;
+};
+
+/**
+ * Function: isValidAncestor
+ * 
+ * Returns whether or not the specified parent is a valid
+ * ancestor of the specified cell, either direct or indirectly
+ * based on whether ancestor recursion is enabled.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> the possible child cell
+ * parent - <mxCell> the possible parent cell
+ * recurse - boolean whether or not to recurse the child ancestors
+ */
+mxGraph.prototype.isValidAncestor = function(cell, parent, recurse)
+{
+	return (recurse ? this.model.isAncestor(parent, cell) : this.model
+			.getParent(cell) == parent);
+};
+
+/**
+ * Function: getOpposites
+ * 
+ * Returns all distinct visible opposite cells for the specified terminal
+ * on the given edges.
+ * 
+ * Parameters:
+ * 
+ * edges - Array of <mxCells> that contains the edges whose opposite
+ * terminals should be returned.
+ * terminal - Terminal that specifies the end whose opposite should be
+ * returned.
+ * source - Optional boolean that specifies if source terminals should be
+ * included in the result. Default is true.
+ * targets - Optional boolean that specifies if targer terminals should be
+ * included in the result. Default is true.
+ */
+mxGraph.prototype.getOpposites = function(edges, terminal, sources, targets)
+{
+	sources = (sources != null) ? sources : true;
+	targets = (targets != null) ? targets : true;
+	
+	var terminals = [];
+	
+	// Fast lookup to avoid duplicates in terminals array
+	var dict = new mxDictionary();
+	
+	if (edges != null)
+	{
+		for (var i = 0; i < edges.length; i++)
+		{
+			var state = this.view.getState(edges[i]);
+			
+			var source = (state != null) ? state.getVisibleTerminal(true) : this.view.getVisibleTerminal(edges[i], true);
+			var target = (state != null) ? state.getVisibleTerminal(false) : this.view.getVisibleTerminal(edges[i], false);
+			
+			// Checks if the terminal is the source of the edge and if the
+			// target should be stored in the result
+			if (source == terminal && target != null && target != terminal && targets)
+			{
+				if (!dict.get(target))
+				{
+					dict.put(target, true);
+					terminals.push(target);
+				}
+			}
+			
+			// Checks if the terminal is the taget of the edge and if the
+			// source should be stored in the result
+			else if (target == terminal && source != null && source != terminal && sources)
+			{
+				if (!dict.get(source))
+				{
+					dict.put(source, true);
+					terminals.push(source);
+				}
+			}
+		}
+	}
+	
+	return terminals;
+};
+
+/**
+ * Function: getEdgesBetween
+ * 
+ * Returns the edges between the given source and target. This takes into
+ * account collapsed and invisible cells and returns the connected edges
+ * as displayed on the screen.
+ * 
+ * Parameters:
+ * 
+ * source -
+ * target -
+ * directed -
+ */
+mxGraph.prototype.getEdgesBetween = function(source, target, directed)
+{
+	directed = (directed != null) ? directed : false;
+	var edges = this.getEdges(source);
+	var result = [];
+
+	// Checks if the edge is connected to the correct
+	// cell and returns the first match
+	for (var i = 0; i < edges.length; i++)
+	{
+		var state = this.view.getState(edges[i]);
+		
+		var src = (state != null) ? state.getVisibleTerminal(true) : this.view.getVisibleTerminal(edges[i], true);
+		var trg = (state != null) ? state.getVisibleTerminal(false) : this.view.getVisibleTerminal(edges[i], false);
+
+		if ((src == source && trg == target) || (!directed && src == target && trg == source))
+		{
+			result.push(edges[i]);
+		}
+	}
+
+	return result;
+};
+
+/**
+ * Function: getPointForEvent
+ * 
+ * Returns an <mxPoint> representing the given event in the unscaled,
+ * non-translated coordinate space of <container> and applies the grid.
+ * 
+ * Parameters:
+ * 
+ * evt - Mousevent that contains the mouse pointer location.
+ * addOffset - Optional boolean that specifies if the position should be
+ * offset by half of the <gridSize>. Default is true.
+ */
+ mxGraph.prototype.getPointForEvent = function(evt, addOffset)
+ {
+	var p = mxUtils.convertPoint(this.container,
+		mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+	
+	var s = this.view.scale;
+	var tr = this.view.translate;
+	var off = (addOffset != false) ? this.gridSize / 2 : 0;
+	
+	p.x = this.snap(p.x / s - tr.x - off);
+	p.y = this.snap(p.y / s - tr.y - off);
+	
+	return p;
+ };
+
+/**
+ * Function: getCells
+ * 
+ * Returns the child vertices and edges of the given parent that are contained
+ * in the given rectangle. The result is added to the optional result array,
+ * which is returned. If no result array is specified then a new array is
+ * created and returned.
+ * 
+ * Parameters:
+ * 
+ * x - X-coordinate of the rectangle.
+ * y - Y-coordinate of the rectangle.
+ * width - Width of the rectangle.
+ * height - Height of the rectangle.
+ * parent - <mxCell> that should be used as the root of the recursion.
+ * Default is current root of the view or the root of the model.
+ * result - Optional array to store the result in.
+ */
+mxGraph.prototype.getCells = function(x, y, width, height, parent, result)
+{
+	result = (result != null) ? result : [];
+	
+	if (width > 0 || height > 0)
+	{
+		var model = this.getModel();
+		var right = x + width;
+		var bottom = y + height;
+
+		if (parent == null)
+		{
+			parent = this.getCurrentRoot();
+			
+			if (parent == null)
+			{
+				parent = model.getRoot();
+			}
+		}
+		
+		if (parent != null)
+		{
+			var childCount = model.getChildCount(parent);
+			
+			for (var i = 0; i < childCount; i++)
+			{
+				var cell = model.getChildAt(parent, i);
+				var state = this.view.getState(cell);
+				
+				if (state != null && this.isCellVisible(cell))
+				{
+					var deg = mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION) || 0;
+					var box = state;
+					
+					if (deg != 0)
+					{
+						box = mxUtils.getBoundingBox(box, deg);
+					}
+					
+					if ((model.isEdge(cell) || model.isVertex(cell)) &&
+						box.x >= x && box.y + box.height <= bottom &&
+						box.y >= y && box.x + box.width <= right)
+					{
+						result.push(cell);
+					}
+					else
+					{
+						this.getCells(x, y, width, height, cell, result);
+					}
+				}
+			}
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: getCellsBeyond
+ * 
+ * Returns the children of the given parent that are contained in the
+ * halfpane from the given point (x0, y0) rightwards or downwards
+ * depending on rightHalfpane and bottomHalfpane.
+ * 
+ * Parameters:
+ * 
+ * x0 - X-coordinate of the origin.
+ * y0 - Y-coordinate of the origin.
+ * parent - Optional <mxCell> whose children should be checked. Default is
+ * <defaultParent>.
+ * rightHalfpane - Boolean indicating if the cells in the right halfpane
+ * from the origin should be returned.
+ * bottomHalfpane - Boolean indicating if the cells in the bottom halfpane
+ * from the origin should be returned.
+ */
+mxGraph.prototype.getCellsBeyond = function(x0, y0, parent, rightHalfpane, bottomHalfpane)
+{
+	var result = [];
+	
+	if (rightHalfpane || bottomHalfpane)
+	{
+		if (parent == null)
+		{
+			parent = this.getDefaultParent();
+		}
+		
+		if (parent != null)
+		{
+			var childCount = this.model.getChildCount(parent);
+			
+			for (var i = 0; i < childCount; i++)
+			{
+				var child = this.model.getChildAt(parent, i);
+				var state = this.view.getState(child);
+				
+				if (this.isCellVisible(child) && state != null)
+				{
+					if ((!rightHalfpane || state.x >= x0) &&
+						(!bottomHalfpane || state.y >= y0))
+					{
+						result.push(child);
+					}
+				}
+			}
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: findTreeRoots
+ * 
+ * Returns all children in the given parent which do not have incoming
+ * edges. If the result is empty then the with the greatest difference
+ * between incoming and outgoing edges is returned.
+ * 
+ * Parameters:
+ * 
+ * parent - <mxCell> whose children should be checked.
+ * isolate - Optional boolean that specifies if edges should be ignored if
+ * the opposite end is not a child of the given parent cell. Default is
+ * false.
+ * invert - Optional boolean that specifies if outgoing or incoming edges
+ * should be counted for a tree root. If false then outgoing edges will be
+ * counted. Default is false.
+ */
+mxGraph.prototype.findTreeRoots = function(parent, isolate, invert)
+{
+	isolate = (isolate != null) ? isolate : false;
+	invert = (invert != null) ? invert : false;
+	var roots = [];
+	
+	if (parent != null)
+	{
+		var model = this.getModel();
+		var childCount = model.getChildCount(parent);
+		var best = null;
+		var maxDiff = 0;
+		
+		for (var i=0; i<childCount; i++)
+		{
+			var cell = model.getChildAt(parent, i);
+			
+			if (this.model.isVertex(cell) && this.isCellVisible(cell))
+			{
+				var conns = this.getConnections(cell, (isolate) ? parent : null);
+				var fanOut = 0;
+				var fanIn = 0;
+				
+				for (var j = 0; j < conns.length; j++)
+				{
+					var src = this.view.getVisibleTerminal(conns[j], true);
+
+                    if (src == cell)
+                    {
+                        fanOut++;
+                    }
+                    else
+                    {
+                        fanIn++;
+                    }
+				}
+				
+				if ((invert && fanOut == 0 && fanIn > 0) ||
+					(!invert && fanIn == 0 && fanOut > 0))
+				{
+					roots.push(cell);
+				}
+				
+				var diff = (invert) ? fanIn - fanOut : fanOut - fanIn;
+				
+				if (diff > maxDiff)
+				{
+					maxDiff = diff;
+					best = cell;
+				}
+			}
+		}
+		
+		if (roots.length == 0 && best != null)
+		{
+			roots.push(best);
+		}
+	}
+	
+	return roots;
+};
+
+/**
+ * Function: traverse
+ * 
+ * Traverses the (directed) graph invoking the given function for each
+ * visited vertex and edge. The function is invoked with the current vertex
+ * and the incoming edge as a parameter. This implementation makes sure
+ * each vertex is only visited once. The function may return false if the
+ * traversal should stop at the given vertex.
+ * 
+ * Example:
+ * 
+ * (code)
+ * mxLog.show();
+ * var cell = graph.getSelectionCell();
+ * graph.traverse(cell, false, function(vertex, edge)
+ * {
+ *   mxLog.debug(graph.getLabel(vertex));
+ * });
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * vertex - <mxCell> that represents the vertex where the traversal starts.
+ * directed - Optional boolean indicating if edges should only be traversed
+ * from source to target. Default is true.
+ * func - Visitor function that takes the current vertex and the incoming
+ * edge as arguments. The traversal stops if the function returns false.
+ * edge - Optional <mxCell> that represents the incoming edge. This is
+ * null for the first step of the traversal.
+ * visited - Optional <mxDictionary> from cells to true for the visited cells.
+ * inverse - Optional boolean to traverse in inverse direction. Default is false.
+ * This is ignored if directed is false.
+ */
+mxGraph.prototype.traverse = function(vertex, directed, func, edge, visited, inverse)
+{
+	if (func != null && vertex != null)
+	{
+		directed = (directed != null) ? directed : true;
+		inverse = (inverse != null) ? inverse : false;
+		visited = visited || new mxDictionary();
+		
+		if (!visited.get(vertex))
+		{
+			visited.put(vertex, true);
+			var result = func(vertex, edge);
+			
+			if (result == null || result)
+			{
+				var edgeCount = this.model.getEdgeCount(vertex);
+				
+				if (edgeCount > 0)
+				{
+					for (var i = 0; i < edgeCount; i++)
+					{
+						var e = this.model.getEdgeAt(vertex, i);
+						var isSource = this.model.getTerminal(e, true) == vertex;
+						
+						if (!directed || (!inverse == isSource))
+						{
+							var next = this.model.getTerminal(e, !isSource);
+							this.traverse(next, directed, func, e, visited, inverse);
+						}
+					}
+				}
+			}
+		}
+	}
+};
+
+/**
+ * Group: Selection
+ */
+
+/**
+ * Function: isCellSelected
+ * 
+ * Returns true if the given cell is selected.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> for which the selection state should be returned.
+ */
+mxGraph.prototype.isCellSelected = function(cell)
+{
+	return this.getSelectionModel().isSelected(cell);
+};
+
+/**
+ * Function: isSelectionEmpty
+ * 
+ * Returns true if the selection is empty.
+ */
+mxGraph.prototype.isSelectionEmpty = function()
+{
+	return this.getSelectionModel().isEmpty();
+};
+
+/**
+ * Function: clearSelection
+ * 
+ * Clears the selection using <mxGraphSelectionModel.clear>.
+ */
+mxGraph.prototype.clearSelection = function()
+{
+	return this.getSelectionModel().clear();
+};
+
+/**
+ * Function: getSelectionCount
+ * 
+ * Returns the number of selected cells.
+ */
+mxGraph.prototype.getSelectionCount = function()
+{
+	return this.getSelectionModel().cells.length;
+};
+	
+/**
+ * Function: getSelectionCell
+ * 
+ * Returns the first cell from the array of selected <mxCells>.
+ */
+mxGraph.prototype.getSelectionCell = function()
+{
+	return this.getSelectionModel().cells[0];
+};
+
+/**
+ * Function: getSelectionCells
+ * 
+ * Returns the array of selected <mxCells>.
+ */
+mxGraph.prototype.getSelectionCells = function()
+{
+	return this.getSelectionModel().cells.slice();
+};
+
+/**
+ * Function: setSelectionCell
+ * 
+ * Sets the selection cell.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to be selected.
+ */
+mxGraph.prototype.setSelectionCell = function(cell)
+{
+	this.getSelectionModel().setCell(cell);
+};
+
+/**
+ * Function: setSelectionCells
+ * 
+ * Sets the selection cell.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> to be selected.
+ */
+mxGraph.prototype.setSelectionCells = function(cells)
+{
+	this.getSelectionModel().setCells(cells);
+};
+
+/**
+ * Function: addSelectionCell
+ * 
+ * Adds the given cell to the selection.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to be add to the selection.
+ */
+mxGraph.prototype.addSelectionCell = function(cell)
+{
+	this.getSelectionModel().addCell(cell);
+};
+
+/**
+ * Function: addSelectionCells
+ * 
+ * Adds the given cells to the selection.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> to be added to the selection.
+ */
+mxGraph.prototype.addSelectionCells = function(cells)
+{
+	this.getSelectionModel().addCells(cells);
+};
+
+/**
+ * Function: removeSelectionCell
+ * 
+ * Removes the given cell from the selection.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to be removed from the selection.
+ */
+mxGraph.prototype.removeSelectionCell = function(cell)
+{
+	this.getSelectionModel().removeCell(cell);
+};
+
+/**
+ * Function: removeSelectionCells
+ * 
+ * Removes the given cells from the selection.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> to be removed from the selection.
+ */
+mxGraph.prototype.removeSelectionCells = function(cells)
+{
+	this.getSelectionModel().removeCells(cells);
+};
+
+/**
+ * Function: selectRegion
+ * 
+ * Selects and returns the cells inside the given rectangle for the
+ * specified event.
+ * 
+ * Parameters:
+ * 
+ * rect - <mxRectangle> that represents the region to be selected.
+ * evt - Mouseevent that triggered the selection.
+ */
+mxGraph.prototype.selectRegion = function(rect, evt)
+{
+	var cells = this.getCells(rect.x, rect.y, rect.width, rect.height);
+	this.selectCellsForEvent(cells, evt);
+	
+	return cells;
+};
+
+/**
+ * Function: selectNextCell
+ * 
+ * Selects the next cell.
+ */
+mxGraph.prototype.selectNextCell = function()
+{
+	this.selectCell(true);
+};
+
+/**
+ * Function: selectPreviousCell
+ * 
+ * Selects the previous cell.
+ */
+mxGraph.prototype.selectPreviousCell = function()
+{
+	this.selectCell();
+};
+
+/**
+ * Function: selectParentCell
+ * 
+ * Selects the parent cell.
+ */
+mxGraph.prototype.selectParentCell = function()
+{
+	this.selectCell(false, true);
+};
+
+/**
+ * Function: selectChildCell
+ * 
+ * Selects the first child cell.
+ */
+mxGraph.prototype.selectChildCell = function()
+{
+	this.selectCell(false, false, true);
+};
+
+/**
+ * Function: selectCell
+ * 
+ * Selects the next, parent, first child or previous cell, if all arguments
+ * are false.
+ * 
+ * Parameters:
+ * 
+ * isNext - Boolean indicating if the next cell should be selected.
+ * isParent - Boolean indicating if the parent cell should be selected.
+ * isChild - Boolean indicating if the first child cell should be selected.
+ */
+mxGraph.prototype.selectCell = function(isNext, isParent, isChild)
+{
+	var sel = this.selectionModel;
+	var cell = (sel.cells.length > 0) ? sel.cells[0] : null;
+	
+	if (sel.cells.length > 1)
+	{
+		sel.clear();
+	}
+	
+	var parent = (cell != null) ?
+		this.model.getParent(cell) :
+		this.getDefaultParent();
+	
+	var childCount = this.model.getChildCount(parent);
+	
+	if (cell == null && childCount > 0)
+	{
+		var child = this.model.getChildAt(parent, 0);
+		this.setSelectionCell(child);
+	}
+	else if ((cell == null || isParent) &&
+		this.view.getState(parent) != null &&
+		this.model.getGeometry(parent) != null)
+	{
+		if (this.getCurrentRoot() != parent)
+		{
+			this.setSelectionCell(parent);
+		}
+	}
+	else if (cell != null && isChild)
+	{
+		var tmp = this.model.getChildCount(cell);
+		
+		if (tmp > 0)
+		{
+			var child = this.model.getChildAt(cell, 0);
+			this.setSelectionCell(child);
+		}
+	}
+	else if (childCount > 0)
+	{
+		var i = parent.getIndex(cell);
+		
+		if (isNext)
+		{
+			i++;
+			var child = this.model.getChildAt(parent, i % childCount);
+			this.setSelectionCell(child);
+		}
+		else
+		{
+			i--;
+			var index =  (i < 0) ? childCount - 1 : i;
+			var child = this.model.getChildAt(parent, index);
+			this.setSelectionCell(child);
+		}
+	}
+};
+
+/**
+ * Function: selectAll
+ * 
+ * Selects all children of the given parent cell or the children of the
+ * default parent if no parent is specified. To select leaf vertices and/or
+ * edges use <selectCells>.
+ * 
+ * Parameters:
+ * 
+ * parent - Optional <mxCell> whose children should be selected.
+ * Default is <defaultParent>.
+ * descendants - Optional boolean specifying whether all descendants should be
+ * selected. Default is false.
+ */
+mxGraph.prototype.selectAll = function(parent, descendants)
+{
+	parent = parent || this.getDefaultParent();
+	
+	var cells = (descendants) ? this.model.filterDescendants(function(cell)
+	{
+		return cell != parent;
+	}, parent) : this.model.getChildren(parent);
+	
+	if (cells != null)
+	{
+		this.setSelectionCells(cells);
+	}
+};
+
+/**
+ * Function: selectVertices
+ * 
+ * Select all vertices inside the given parent or the default parent.
+ */
+mxGraph.prototype.selectVertices = function(parent)
+{
+	this.selectCells(true, false, parent);
+};
+
+/**
+ * Function: selectVertices
+ * 
+ * Select all vertices inside the given parent or the default parent.
+ */
+mxGraph.prototype.selectEdges = function(parent)
+{
+	this.selectCells(false, true, parent);
+};
+
+/**
+ * Function: selectCells
+ * 
+ * Selects all vertices and/or edges depending on the given boolean
+ * arguments recursively, starting at the given parent or the default
+ * parent if no parent is specified. Use <selectAll> to select all cells.
+ * For vertices, only cells with no children are selected.
+ * 
+ * Parameters:
+ * 
+ * vertices - Boolean indicating if vertices should be selected.
+ * edges - Boolean indicating if edges should be selected.
+ * parent - Optional <mxCell> that acts as the root of the recursion.
+ * Default is <defaultParent>.
+ */
+mxGraph.prototype.selectCells = function(vertices, edges, parent)
+{
+	parent = parent || this.getDefaultParent();
+	
+	var filter = mxUtils.bind(this, function(cell)
+	{
+		return this.view.getState(cell) != null &&
+			((this.model.getChildCount(cell) == 0 && this.model.isVertex(cell) && vertices
+			&& !this.model.isEdge(this.model.getParent(cell))) ||
+			(this.model.isEdge(cell) && edges));
+	});
+	
+	var cells = this.model.filterDescendants(filter, parent);
+	this.setSelectionCells(cells);
+};
+
+/**
+ * Function: selectCellForEvent
+ * 
+ * Selects the given cell by either adding it to the selection or
+ * replacing the selection depending on whether the given mouse event is a
+ * toggle event.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to be selected.
+ * evt - Optional mouseevent that triggered the selection.
+ */
+mxGraph.prototype.selectCellForEvent = function(cell, evt)
+{
+	var isSelected = this.isCellSelected(cell);
+	
+	if (this.isToggleEvent(evt))
+	{
+		if (isSelected)
+		{
+			this.removeSelectionCell(cell);
+		}
+		else
+		{
+			this.addSelectionCell(cell);
+		}
+	}
+	else if (!isSelected || this.getSelectionCount() != 1)
+	{
+		this.setSelectionCell(cell);
+	}
+};
+
+/**
+ * Function: selectCellsForEvent
+ * 
+ * Selects the given cells by either adding them to the selection or
+ * replacing the selection depending on whether the given mouse event is a
+ * toggle event.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> to be selected.
+ * evt - Optional mouseevent that triggered the selection.
+ */
+mxGraph.prototype.selectCellsForEvent = function(cells, evt)
+{
+	if (this.isToggleEvent(evt))
+	{
+		this.addSelectionCells(cells);
+	}
+	else
+	{
+		this.setSelectionCells(cells);
+	}
+};
+
+/**
+ * Group: Selection state
+ */
+
+/**
+ * Function: createHandler
+ * 
+ * Creates a new handler for the given cell state. This implementation
+ * returns a new <mxEdgeHandler> of the corresponding cell is an edge,
+ * otherwise it returns an <mxVertexHandler>.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose handler should be created.
+ */
+mxGraph.prototype.createHandler = function(state)
+{
+	var result = null;
+	
+	if (state != null)
+	{
+		if (this.model.isEdge(state.cell))
+		{
+			var source = state.getVisibleTerminalState(true);
+			var target = state.getVisibleTerminalState(false);
+			var geo = this.getCellGeometry(state.cell);
+			
+			var edgeStyle = this.view.getEdgeStyle(state, (geo != null) ? geo.points : null, source, target);
+			result = this.createEdgeHandler(state, edgeStyle);
+		}
+		else
+		{
+			result = this.createVertexHandler(state);
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: createVertexHandler
+ * 
+ * Hooks to create a new <mxVertexHandler> for the given <mxCellState>.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> to create the handler for.
+ */
+mxGraph.prototype.createVertexHandler = function(state)
+{
+	return new mxVertexHandler(state);
+};
+
+/**
+ * Function: createEdgeHandler
+ * 
+ * Hooks to create a new <mxEdgeHandler> for the given <mxCellState>.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> to create the handler for.
+ */
+mxGraph.prototype.createEdgeHandler = function(state, edgeStyle)
+{
+	var result = null;
+	
+	if (edgeStyle == mxEdgeStyle.Loop ||
+		edgeStyle == mxEdgeStyle.ElbowConnector ||
+		edgeStyle == mxEdgeStyle.SideToSide ||
+		edgeStyle == mxEdgeStyle.TopToBottom)
+	{
+		result = this.createElbowEdgeHandler(state);
+	}
+	else if (edgeStyle == mxEdgeStyle.SegmentConnector || 
+			edgeStyle == mxEdgeStyle.OrthConnector)
+	{
+		result = this.createEdgeSegmentHandler(state);
+	}
+	else
+	{
+		result = new mxEdgeHandler(state);
+	}
+	
+	return result;
+};
+
+/**
+ * Function: createEdgeSegmentHandler
+ * 
+ * Hooks to create a new <mxEdgeSegmentHandler> for the given <mxCellState>.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> to create the handler for.
+ */
+mxGraph.prototype.createEdgeSegmentHandler = function(state)
+{
+	return new mxEdgeSegmentHandler(state);
+};
+
+/**
+ * Function: createElbowEdgeHandler
+ * 
+ * Hooks to create a new <mxElbowEdgeHandler> for the given <mxCellState>.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> to create the handler for.
+ */
+mxGraph.prototype.createElbowEdgeHandler = function(state)
+{
+	return new mxElbowEdgeHandler(state);
+};
+
+/**
+ * Group: Graph events
+ */
+
+/**
+ * Function: addMouseListener
+ * 
+ * Adds a listener to the graph event dispatch loop. The listener
+ * must implement the mouseDown, mouseMove and mouseUp methods
+ * as shown in the <mxMouseEvent> class.
+ * 
+ * Parameters:
+ * 
+ * listener - Listener to be added to the graph event listeners.
+ */
+mxGraph.prototype.addMouseListener = function(listener)
+{
+	if (this.mouseListeners == null)
+	{
+		this.mouseListeners = [];
+	}
+	
+	this.mouseListeners.push(listener);
+};
+
+/**
+ * Function: removeMouseListener
+ * 
+ * Removes the specified graph listener.
+ * 
+ * Parameters:
+ * 
+ * listener - Listener to be removed from the graph event listeners.
+ */
+mxGraph.prototype.removeMouseListener = function(listener)
+{
+	if (this.mouseListeners != null)
+	{
+		for (var i = 0; i < this.mouseListeners.length; i++)
+		{
+			if (this.mouseListeners[i] == listener)
+			{
+				this.mouseListeners.splice(i, 1);
+				break;
+			}
+		}
+	}
+};
+
+/**
+ * Function: updateMouseEvent
+ * 
+ * Sets the graphX and graphY properties if the given <mxMouseEvent> if
+ * required and returned the event.
+ * 
+ * Parameters:
+ * 
+ * me - <mxMouseEvent> to be updated.
+ * evtName - Name of the mouse event.
+ */
+mxGraph.prototype.updateMouseEvent = function(me, evtName)
+{
+	if (me.graphX == null || me.graphY == null)
+	{
+		var pt = mxUtils.convertPoint(this.container, me.getX(), me.getY());
+		
+		me.graphX = pt.x - this.panDx;
+		me.graphY = pt.y - this.panDy;
+		
+		// Searches for rectangles using method if native hit detection is disabled on shape
+		if (me.getCell() == null && this.isMouseDown && evtName == mxEvent.MOUSE_MOVE)
+		{
+			me.state = this.view.getState(this.getCellAt(pt.x, pt.y, null, null, null, function(state)
+			{
+				return state.shape == null || state.shape.paintBackground != mxRectangleShape.prototype.paintBackground ||
+					mxUtils.getValue(state.style, mxConstants.STYLE_POINTER_EVENTS, '1') == '1' ||
+					(state.shape.fill != null && state.shape.fill != mxConstants.NONE);
+			}));
+		}
+	}
+	
+	return me;
+};
+
+/**
+ * Function: getStateForEvent
+ * 
+ * Returns the state for the given touch event.
+ */
+mxGraph.prototype.getStateForTouchEvent = function(evt)
+{
+	var x = mxEvent.getClientX(evt);
+	var y = mxEvent.getClientY(evt);
+	
+	// Dispatches the drop event to the graph which
+	// consumes and executes the source function
+	var pt = mxUtils.convertPoint(this.container, x, y);
+
+	return this.view.getState(this.getCellAt(pt.x, pt.y));
+};
+
+/**
+ * Function: isEventIgnored
+ * 
+ * Returns true if the event should be ignored in <fireMouseEvent>.
+ */
+mxGraph.prototype.isEventIgnored = function(evtName, me, sender)
+{
+	var mouseEvent = mxEvent.isMouseEvent(me.getEvent());
+	var result = false;
+
+	// Drops events that are fired more than once
+	if (me.getEvent() == this.lastEvent)
+	{
+		result = true;
+	}
+	else
+	{
+		this.lastEvent = me.getEvent();
+	}
+
+	// Installs event listeners to capture the complete gesture from the event source
+	// for non-MS touch events as a workaround for all events for the same geture being
+	// fired from the event source even if that was removed from the DOM.
+	if (this.eventSource != null && evtName != mxEvent.MOUSE_MOVE)
+	{
+		mxEvent.removeGestureListeners(this.eventSource, null, this.mouseMoveRedirect, this.mouseUpRedirect);
+		this.mouseMoveRedirect = null;
+		this.mouseUpRedirect = null;
+		this.eventSource = null;
+	}
+	else if (this.eventSource != null && me.getSource() != this.eventSource)
+	{
+		result = true;
+	}
+	else if (mxClient.IS_TOUCH && evtName == mxEvent.MOUSE_DOWN && !mouseEvent && !mxEvent.isPenEvent(me.getEvent()))
+	{
+		this.eventSource = me.getSource();
+
+		this.mouseMoveRedirect = mxUtils.bind(this, function(evt)
+		{
+			this.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, this.getStateForTouchEvent(evt)));
+		});
+		this.mouseUpRedirect = mxUtils.bind(this, function(evt)
+		{
+			this.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt, this.getStateForTouchEvent(evt)));
+		});
+		
+		mxEvent.addGestureListeners(this.eventSource, null, this.mouseMoveRedirect, this.mouseUpRedirect);
+	}
+
+	// Factored out the workarounds for FF to make it easier to override/remove
+	// Note this method has side-effects!
+	if (this.isSyntheticEventIgnored(evtName, me, sender))
+	{
+		result = true;
+	}
+
+	// Never fires mouseUp/-Down for double clicks
+	if (!mxEvent.isPopupTrigger(this.lastEvent) && evtName != mxEvent.MOUSE_MOVE && this.lastEvent.detail == 2)
+	{
+		return true;
+	}
+	
+	// Filters out of sequence events or mixed event types during a gesture
+	if (evtName == mxEvent.MOUSE_UP && this.isMouseDown)
+	{
+		this.isMouseDown = false;
+	}
+	else if (evtName == mxEvent.MOUSE_DOWN && !this.isMouseDown)
+	{
+		this.isMouseDown = true;
+		this.isMouseTrigger = mouseEvent;
+	}
+	// Drops mouse events that are fired during touch gestures as a workaround for Webkit
+	// and mouse events that are not in sync with the current internal button state
+	else if (!result && (((!mxClient.IS_FF || evtName != mxEvent.MOUSE_MOVE) &&
+		this.isMouseDown && this.isMouseTrigger != mouseEvent) ||
+		(evtName == mxEvent.MOUSE_DOWN && this.isMouseDown) ||
+		(evtName == mxEvent.MOUSE_UP && !this.isMouseDown)))
+	{
+		result = true;
+	}
+	
+	if (!result && evtName == mxEvent.MOUSE_DOWN)
+	{
+		this.lastMouseX = me.getX();
+		this.lastMouseY = me.getY();
+	}
+
+	return result;
+};
+
+/**
+ * Function: isSyntheticEventIgnored
+ * 
+ * Hook for ignoring synthetic mouse events after touchend in Firefox.
+ */
+mxGraph.prototype.isSyntheticEventIgnored = function(evtName, me, sender)
+{
+	var result = false;
+	var mouseEvent = mxEvent.isMouseEvent(me.getEvent());
+	
+	// LATER: This does not cover all possible cases that can go wrong in FF
+	if (this.ignoreMouseEvents && mouseEvent && evtName != mxEvent.MOUSE_MOVE)
+	{
+		this.ignoreMouseEvents = evtName != mxEvent.MOUSE_UP;
+		result = true;
+	}
+	else if (mxClient.IS_FF && !mouseEvent && evtName == mxEvent.MOUSE_UP)
+	{
+		this.ignoreMouseEvents = true;
+	}
+	
+	return result;
+};
+
+/**
+ * Function: isEventSourceIgnored
+ * 
+ * Returns true if the event should be ignored in <fireMouseEvent>. This
+ * implementation returns true for select, option and input (if not of type
+ * checkbox, radio, button, submit or file) event sources if the event is not
+ * a mouse event or a left mouse button press event.
+ * 
+ * Parameters:
+ * 
+ * evtName - The name of the event.
+ * me - <mxMouseEvent> that should be ignored.
+ */
+mxGraph.prototype.isEventSourceIgnored = function(evtName, me)
+{
+	var source = me.getSource();
+	var name = (source.nodeName != null) ? source.nodeName.toLowerCase() : '';
+	var candidate = !mxEvent.isMouseEvent(me.getEvent()) || mxEvent.isLeftMouseButton(me.getEvent());
+	
+	return evtName == mxEvent.MOUSE_DOWN && candidate && (name == 'select' || name == 'option' ||
+		(name == 'input' && source.type != 'checkbox' && source.type != 'radio' &&
+		source.type != 'button' && source.type != 'submit' && source.type != 'file'));
+};
+
+/**
+ * Function: getEventState
+ * 
+ * Returns the <mxCellState> to be used when firing the mouse event for the
+ * given state. This implementation returns the given state.
+ * 
+ * Parameters:
+ * 
+ * <mxCellState> - State whose event source should be returned.
+ */
+mxGraph.prototype.getEventState = function(state)
+{
+	return state;
+};
+
+/**
+ * Function: fireMouseEvent
+ * 
+ * Dispatches the given event in the graph event dispatch loop. Possible
+ * event names are <mxEvent.MOUSE_DOWN>, <mxEvent.MOUSE_MOVE> and
+ * <mxEvent.MOUSE_UP>. All listeners are invoked for all events regardless
+ * of the consumed state of the event.
+ * 
+ * Parameters:
+ * 
+ * evtName - String that specifies the type of event to be dispatched.
+ * me - <mxMouseEvent> to be fired.
+ * sender - Optional sender argument. Default is this.
+ */
+mxGraph.prototype.fireMouseEvent = function(evtName, me, sender)
+{
+	if (this.isEventSourceIgnored(evtName, me))
+	{
+		if (this.tooltipHandler != null)
+		{
+			this.tooltipHandler.hide();
+		}
+		
+		return;
+	}
+	
+	if (sender == null)
+	{
+		sender = this;
+	}
+
+	// Updates the graph coordinates in the event
+	me = this.updateMouseEvent(me, evtName);
+
+	// Detects and processes double taps for touch-based devices which do not have native double click events
+	// or where detection of double click is not always possible (quirks, IE10+). Note that this can only handle
+	// double clicks on cells because the sequence of events in IE prevents detection on the background, it fires
+	// two mouse ups, one of which without a cell but no mousedown for the second click which means we cannot
+	// detect which mouseup(s) are part of the first click, ie we do not know when the first click ends.
+	if ((!this.nativeDblClickEnabled && !mxEvent.isPopupTrigger(me.getEvent())) || (this.doubleTapEnabled &&
+		mxClient.IS_TOUCH && (mxEvent.isTouchEvent(me.getEvent()) || mxEvent.isPenEvent(me.getEvent()))))
+	{
+		var currentTime = new Date().getTime();
+		
+		// NOTE: Second mouseDown for double click missing in quirks mode
+		if ((!mxClient.IS_QUIRKS && evtName == mxEvent.MOUSE_DOWN) || (mxClient.IS_QUIRKS && evtName == mxEvent.MOUSE_UP && !this.fireDoubleClick))
+		{
+			if (this.lastTouchEvent != null && this.lastTouchEvent != me.getEvent() &&
+				currentTime - this.lastTouchTime < this.doubleTapTimeout &&
+				Math.abs(this.lastTouchX - me.getX()) < this.doubleTapTolerance &&
+				Math.abs(this.lastTouchY - me.getY()) < this.doubleTapTolerance &&
+				this.doubleClickCounter < 2)
+			{
+				this.doubleClickCounter++;
+				var doubleClickFired = false;
+				
+				if (evtName == mxEvent.MOUSE_UP)
+				{
+					if (me.getCell() == this.lastTouchCell && this.lastTouchCell != null)
+					{
+						this.lastTouchTime = 0;
+						var cell = this.lastTouchCell;
+						this.lastTouchCell = null;
+
+						// Fires native dblclick event via event source
+						// NOTE: This fires two double click events on edges in quirks mode. While
+						// trying to fix this, we realized that nativeDoubleClick can be disabled for
+						// quirks and IE10+ (or we didn't find the case mentioned above where it
+						// would not work), ie. all double clicks seem to be working without this.
+						if (mxClient.IS_QUIRKS)
+						{
+							me.getSource().fireEvent('ondblclick');
+						}
+						
+						this.dblClick(me.getEvent(), cell);
+						doubleClickFired = true;
+					}
+				}
+				else
+				{
+					this.fireDoubleClick = true;
+					this.lastTouchTime = 0;
+				}
+
+				// Do not ignore mouse up in quirks in this case
+				if (!mxClient.IS_QUIRKS || doubleClickFired)
+				{
+					mxEvent.consume(me.getEvent());
+					return;
+				}
+			}
+			else if (this.lastTouchEvent == null || this.lastTouchEvent != me.getEvent())
+			{
+				this.lastTouchCell = me.getCell();
+				this.lastTouchX = me.getX();
+				this.lastTouchY = me.getY();
+				this.lastTouchTime = currentTime;
+				this.lastTouchEvent = me.getEvent();
+				this.doubleClickCounter = 0;
+			}
+		}
+		else if ((this.isMouseDown || evtName == mxEvent.MOUSE_UP) && this.fireDoubleClick)
+		{
+			this.fireDoubleClick = false;
+			var cell = this.lastTouchCell;
+			this.lastTouchCell = null;
+			this.isMouseDown = false;
+			
+			// Workaround for Chrome/Safari not firing native double click events for double touch on background
+			var valid = (cell != null) || ((mxEvent.isTouchEvent(me.getEvent()) || mxEvent.isPenEvent(me.getEvent())) &&
+				(mxClient.IS_GC || mxClient.IS_SF));
+			
+			if (valid && Math.abs(this.lastTouchX - me.getX()) < this.doubleTapTolerance &&
+				Math.abs(this.lastTouchY - me.getY()) < this.doubleTapTolerance)
+			{
+				this.dblClick(me.getEvent(), cell);
+			}
+			else
+			{
+				mxEvent.consume(me.getEvent());
+			}
+			
+			return;
+		}
+	}
+
+	if (!this.isEventIgnored(evtName, me, sender))
+	{
+		// Updates the event state via getEventState
+		me.state = this.getEventState(me.getState());
+		this.fireEvent(new mxEventObject(mxEvent.FIRE_MOUSE_EVENT, 'eventName', evtName, 'event', me));
+		
+		if ((mxClient.IS_OP || mxClient.IS_SF || mxClient.IS_GC || mxClient.IS_IE11 ||
+			(mxClient.IS_IE && mxClient.IS_SVG) || me.getEvent().target != this.container))
+		{
+			if (evtName == mxEvent.MOUSE_MOVE && this.isMouseDown && this.autoScroll && !mxEvent.isMultiTouchEvent(me.getEvent))
+			{
+				this.scrollPointToVisible(me.getGraphX(), me.getGraphY(), this.autoExtend);
+			}
+			else if (evtName == mxEvent.MOUSE_UP && this.ignoreScrollbars && this.translateToScrollPosition &&
+					(this.container.scrollLeft != 0 || this.container.scrollTop != 0))
+			{
+				var s = this.view.scale;
+				var tr = this.view.translate;
+				this.view.setTranslate(tr.x - this.container.scrollLeft / s, tr.y - this.container.scrollTop / s);
+				this.container.scrollLeft = 0;
+				this.container.scrollTop = 0;
+			}
+			
+			if (this.mouseListeners != null)
+			{
+				var args = [sender, me];
+	
+				// Does not change returnValue in Opera
+				if (!me.getEvent().preventDefault)
+				{
+					me.getEvent().returnValue = true;
+				}
+				
+				for (var i = 0; i < this.mouseListeners.length; i++)
+				{
+					var l = this.mouseListeners[i];
+					
+					if (evtName == mxEvent.MOUSE_DOWN)
+					{
+						l.mouseDown.apply(l, args);
+					}
+					else if (evtName == mxEvent.MOUSE_MOVE)
+					{
+						l.mouseMove.apply(l, args);
+					}
+					else if (evtName == mxEvent.MOUSE_UP)
+					{
+						l.mouseUp.apply(l, args);
+					}
+				}
+			}
+			
+			// Invokes the click handler
+			if (evtName == mxEvent.MOUSE_UP)
+			{
+				this.click(me);
+			}
+		}
+		
+		// Detects tapAndHold events using a timer
+		if ((mxEvent.isTouchEvent(me.getEvent()) || mxEvent.isPenEvent(me.getEvent())) &&
+			evtName == mxEvent.MOUSE_DOWN && this.tapAndHoldEnabled && !this.tapAndHoldInProgress)
+		{
+			this.tapAndHoldInProgress = true;
+			this.initialTouchX = me.getGraphX();
+			this.initialTouchY = me.getGraphY();
+			
+			var handler = function()
+			{
+				if (this.tapAndHoldValid)
+				{
+					this.tapAndHold(me);
+				}
+				
+				this.tapAndHoldInProgress = false;
+				this.tapAndHoldValid = false;
+			};
+			
+			if (this.tapAndHoldThread)
+			{
+				window.clearTimeout(this.tapAndHoldThread);
+			}
+	
+			this.tapAndHoldThread = window.setTimeout(mxUtils.bind(this, handler), this.tapAndHoldDelay);
+			this.tapAndHoldValid = true;
+		}
+		else if (evtName == mxEvent.MOUSE_UP)
+		{
+			this.tapAndHoldInProgress = false;
+			this.tapAndHoldValid = false;
+		}
+		else if (this.tapAndHoldValid)
+		{
+			this.tapAndHoldValid =
+				Math.abs(this.initialTouchX - me.getGraphX()) < this.tolerance &&
+				Math.abs(this.initialTouchY - me.getGraphY()) < this.tolerance;
+		}
+
+		// Stops editing for all events other than from cellEditor
+		if (evtName == mxEvent.MOUSE_DOWN && this.isEditing() && !this.cellEditor.isEventSource(me.getEvent()))
+		{
+			this.stopEditing(!this.isInvokesStopCellEditing());
+		}
+
+		this.consumeMouseEvent(evtName, me, sender);
+	}
+};
+
+/**
+ * Function: consumeMouseEvent
+ * 
+ * Consumes the given <mxMouseEvent> if it's a touchStart event.
+ */
+mxGraph.prototype.consumeMouseEvent = function(evtName, me, sender)
+{
+	// Workaround for duplicate click in Windows 8 with Chrome/FF/Opera with touch
+	if (evtName == mxEvent.MOUSE_DOWN && mxEvent.isTouchEvent(me.getEvent()))
+	{
+		me.consume(false);
+	}
+};
+
+/**
+ * Function: fireGestureEvent
+ * 
+ * Dispatches a <mxEvent.GESTURE> event. The following example will resize the
+ * cell under the mouse based on the scale property of the native touch event.
+ * 
+ * (code)
+ * graph.addListener(mxEvent.GESTURE, function(sender, eo)
+ * {
+ *   var evt = eo.getProperty('event');
+ *   var state = graph.view.getState(eo.getProperty('cell'));
+ *   
+ *   if (graph.isEnabled() && graph.isCellResizable(state.cell) && Math.abs(1 - evt.scale) > 0.2)
+ *   {
+ *     var scale = graph.view.scale;
+ *     var tr = graph.view.translate;
+ *     
+ *     var w = state.width * evt.scale;
+ *     var h = state.height * evt.scale;
+ *     var x = state.x - (w - state.width) / 2;
+ *     var y = state.y - (h - state.height) / 2;
+ *     
+ *     var bounds = new mxRectangle(graph.snap(x / scale) - tr.x,
+ *     		graph.snap(y / scale) - tr.y, graph.snap(w / scale), graph.snap(h / scale));
+ *     graph.resizeCell(state.cell, bounds);
+ *     eo.consume();
+ *   }
+ * });
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * evt - Gestureend event that represents the gesture.
+ * cell - Optional <mxCell> associated with the gesture.
+ */
+mxGraph.prototype.fireGestureEvent = function(evt, cell)
+{
+	// Resets double tap event handling when gestures take place
+	this.lastTouchTime = 0;
+	this.fireEvent(new mxEventObject(mxEvent.GESTURE, 'event', evt, 'cell', cell));
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the graph and all its resources.
+ */
+mxGraph.prototype.destroy = function()
+{
+	if (!this.destroyed)
+	{
+		this.destroyed = true;
+		
+		if (this.tooltipHandler != null)
+		{
+			this.tooltipHandler.destroy();
+		}
+		
+		if (this.selectionCellsHandler != null)
+		{
+			this.selectionCellsHandler.destroy();
+		}
+
+		if (this.panningHandler != null)
+		{
+			this.panningHandler.destroy();
+		}
+
+		if (this.popupMenuHandler != null)
+		{
+			this.popupMenuHandler.destroy();
+		}
+		
+		if (this.connectionHandler != null)
+		{
+			this.connectionHandler.destroy();
+		}
+		
+		if (this.graphHandler != null)
+		{
+			this.graphHandler.destroy();
+		}
+		
+		if (this.cellEditor != null)
+		{
+			this.cellEditor.destroy();
+		}
+		
+		if (this.view != null)
+		{
+			this.view.destroy();
+		}
+
+		if (this.model != null && this.graphModelChangeListener != null)
+		{
+			this.model.removeListener(this.graphModelChangeListener);
+			this.graphModelChangeListener = null;
+		}
+
+		this.container = null;
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxCellOverlay
+ *
+ * Extends <mxEventSource> to implement a graph overlay, represented by an icon
+ * and a tooltip. Overlays can handle and fire <click> events and are added to
+ * the graph using <mxGraph.addCellOverlay>, and removed using
+ * <mxGraph.removeCellOverlay>, or <mxGraph.removeCellOverlays> to remove all overlays.
+ * The <mxGraph.getCellOverlays> function returns the array of overlays for a given
+ * cell in a graph. If multiple overlays exist for the same cell, then
+ * <getBounds> should be overridden in at least one of the overlays.
+ * 
+ * Overlays appear on top of all cells in a special layer. If this is not
+ * desirable, then the image must be rendered as part of the shape or label of
+ * the cell instead.
+ *
+ * Example:
+ * 
+ * The following adds a new overlays for a given vertex and selects the cell
+ * if the overlay is clicked.
+ *
+ * (code)
+ * var overlay = new mxCellOverlay(img, html);
+ * graph.addCellOverlay(vertex, overlay);
+ * overlay.addListener(mxEvent.CLICK, function(sender, evt)
+ * {
+ *   var cell = evt.getProperty('cell');
+ *   graph.setSelectionCell(cell);
+ * });
+ * (end)
+ * 
+ * For cell overlays to be printed use <mxPrintPreview.printOverlays>.
+ *
+ * Event: mxEvent.CLICK
+ *
+ * Fires when the user clicks on the overlay. The <code>event</code> property
+ * contains the corresponding mouse event and the <code>cell</code> property
+ * contains the cell. For touch devices this is fired if the element receives
+ * a touchend event.
+ * 
+ * Constructor: mxCellOverlay
+ *
+ * Constructs a new overlay using the given image and tooltip.
+ * 
+ * Parameters:
+ * 
+ * image - <mxImage> that represents the icon to be displayed.
+ * tooltip - Optional string that specifies the tooltip.
+ * align - Optional horizontal alignment for the overlay. Possible
+ * values are <ALIGN_LEFT>, <ALIGN_CENTER> and <ALIGN_RIGHT>
+ * (default).
+ * verticalAlign - Vertical alignment for the overlay. Possible
+ * values are <ALIGN_TOP>, <ALIGN_MIDDLE> and <ALIGN_BOTTOM>
+ * (default).
+ */
+function mxCellOverlay(image, tooltip, align, verticalAlign, offset, cursor)
+{
+	this.image = image;
+	this.tooltip = tooltip;
+	this.align = (align != null) ? align : this.align;
+	this.verticalAlign = (verticalAlign != null) ? verticalAlign : this.verticalAlign;
+	this.offset = (offset != null) ? offset : new mxPoint();
+	this.cursor = (cursor != null) ? cursor : 'help';
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxCellOverlay.prototype = new mxEventSource();
+mxCellOverlay.prototype.constructor = mxCellOverlay;
+
+/**
+ * Variable: image
+ *
+ * Holds the <mxImage> to be used as the icon.
+ */
+mxCellOverlay.prototype.image = null;
+
+/**
+ * Variable: tooltip
+ * 
+ * Holds the optional string to be used as the tooltip.
+ */
+mxCellOverlay.prototype.tooltip = null;
+
+/**
+ * Variable: align
+ * 
+ * Holds the horizontal alignment for the overlay. Default is
+ * <mxConstants.ALIGN_RIGHT>. For edges, the overlay always appears in the
+ * center of the edge.
+ */
+mxCellOverlay.prototype.align = mxConstants.ALIGN_RIGHT;
+
+/**
+ * Variable: verticalAlign
+ * 
+ * Holds the vertical alignment for the overlay. Default is
+ * <mxConstants.ALIGN_BOTTOM>. For edges, the overlay always appears in the
+ * center of the edge.
+ */
+mxCellOverlay.prototype.verticalAlign = mxConstants.ALIGN_BOTTOM;
+
+/**
+ * Variable: offset
+ * 
+ * Holds the offset as an <mxPoint>. The offset will be scaled according to the
+ * current scale.
+ */
+mxCellOverlay.prototype.offset = null;
+
+/**
+ * Variable: cursor
+ * 
+ * Holds the cursor for the overlay. Default is 'help'.
+ */
+mxCellOverlay.prototype.cursor = null;
+
+/**
+ * Variable: defaultOverlap
+ * 
+ * Defines the overlapping for the overlay, that is, the proportional distance
+ * from the origin to the point defined by the alignment. Default is 0.5.
+ */
+mxCellOverlay.prototype.defaultOverlap = 0.5;
+
+/**
+ * Function: getBounds
+ * 
+ * Returns the bounds of the overlay for the given <mxCellState> as an
+ * <mxRectangle>. This should be overridden when using multiple overlays
+ * per cell so that the overlays do not overlap.
+ * 
+ * The following example will place the overlay along an edge (where
+ * x=[-1..1] from the start to the end of the edge and y is the
+ * orthogonal offset in px).
+ * 
+ * (code)
+ * overlay.getBounds = function(state)
+ * {
+ *   var bounds = mxCellOverlay.prototype.getBounds.apply(this, arguments);
+ *   
+ *   if (state.view.graph.getModel().isEdge(state.cell))
+ *   {
+ *     var pt = state.view.getPoint(state, {x: 0, y: 0, relative: true});
+ *     
+ *     bounds.x = pt.x - bounds.width / 2;
+ *     bounds.y = pt.y - bounds.height / 2;
+ *   }
+ *   
+ *   return bounds;
+ * };
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> that represents the current state of the
+ * associated cell.
+ */
+mxCellOverlay.prototype.getBounds = function(state)
+{
+	var isEdge = state.view.graph.getModel().isEdge(state.cell);
+	var s = state.view.scale;
+	var pt = null;
+
+	var w = this.image.width;
+	var h = this.image.height;
+	
+	if (isEdge)
+	{
+		var pts = state.absolutePoints;
+		
+		if (pts.length % 2 == 1)
+		{
+			pt = pts[Math.floor(pts.length / 2)];
+		}
+		else
+		{
+			var idx = pts.length / 2;
+			var p0 = pts[idx-1];
+			var p1 = pts[idx];
+			pt = new mxPoint(p0.x + (p1.x - p0.x) / 2,
+				p0.y + (p1.y - p0.y) / 2);
+		}
+	}
+	else
+	{
+		pt = new mxPoint();
+		
+		if (this.align == mxConstants.ALIGN_LEFT)
+		{
+			pt.x = state.x;
+		}
+		else if (this.align == mxConstants.ALIGN_CENTER)
+		{
+			pt.x = state.x + state.width / 2;
+		}
+		else
+		{
+			pt.x = state.x + state.width;
+		}
+		
+		if (this.verticalAlign == mxConstants.ALIGN_TOP)
+		{
+			pt.y = state.y;
+		}
+		else if (this.verticalAlign == mxConstants.ALIGN_MIDDLE)
+		{
+			pt.y = state.y + state.height / 2;
+		}
+		else
+		{
+			pt.y = state.y + state.height;
+		}
+	}
+
+	return new mxRectangle(Math.round(pt.x - (w * this.defaultOverlap - this.offset.x) * s),
+		Math.round(pt.y - (h * this.defaultOverlap - this.offset.y) * s), w * s, h * s);
+};
+
+/**
+ * Function: toString
+ * 
+ * Returns the textual representation of the overlay to be used as the
+ * tooltip. This implementation returns <tooltip>.
+ */
+mxCellOverlay.prototype.toString = function()
+{
+	return this.tooltip;
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxOutline
+ *
+ * Implements an outline (aka overview) for a graph. Set <updateOnPan> to true
+ * to enable updates while the source graph is panning.
+ * 
+ * Example:
+ * 
+ * (code)
+ * var outline = new mxOutline(graph, div);
+ * (end)
+ * 
+ * If an outline is used in an <mxWindow> in IE8 standards mode, the following
+ * code makes sure that the shadow filter is not inherited and that any
+ * transparent elements in the graph do not show the page background, but the
+ * background of the graph container.
+ * 
+ * (code)
+ * if (document.documentMode == 8)
+ * {
+ *   container.style.filter = 'progid:DXImageTransform.Microsoft.alpha(opacity=100)';
+ * }
+ * (end)
+ * 
+ * To move the graph to the top, left corner the following code can be used.
+ * 
+ * (code)
+ * var scale = graph.view.scale;
+ * var bounds = graph.getGraphBounds();
+ * graph.view.setTranslate(-bounds.x / scale, -bounds.y / scale);
+ * (end)
+ * 
+ * To toggle the suspended mode, the following can be used.
+ * 
+ * (code)
+ * outline.suspended = !outln.suspended;
+ * if (!outline.suspended)
+ * {
+ *   outline.update(true);
+ * }
+ * (end)
+ * 
+ * Constructor: mxOutline
+ *
+ * Constructs a new outline for the specified graph inside the given
+ * container.
+ * 
+ * Parameters:
+ * 
+ * source - <mxGraph> to create the outline for.
+ * container - DOM node that will contain the outline.
+ */
+function mxOutline(source, container)
+{
+	this.source = source;
+
+	if (container != null)
+	{
+		this.init(container);
+	}
+};
+
+/**
+ * Function: source
+ * 
+ * Reference to the source <mxGraph>.
+ */
+mxOutline.prototype.source = null;
+
+/**
+ * Function: outline
+ * 
+ * Reference to the <mxGraph> that renders the outline.
+ */
+mxOutline.prototype.outline = null;
+
+/**
+ * Function: graphRenderHint
+ * 
+ * Renderhint to be used for the outline graph. Default is faster.
+ */
+mxOutline.prototype.graphRenderHint = mxConstants.RENDERING_HINT_FASTER;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies if events are handled. Default is true.
+ */
+mxOutline.prototype.enabled = true;
+
+/**
+ * Variable: showViewport
+ * 
+ * Specifies a viewport rectangle should be shown. Default is true.
+ */
+mxOutline.prototype.showViewport = true;
+
+/**
+ * Variable: border
+ * 
+ * Border to be added at the bottom and right. Default is 10.
+ */
+mxOutline.prototype.border = 10;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies the size of the sizer handler. Default is 8.
+ */
+mxOutline.prototype.sizerSize = 8;
+
+/**
+ * Variable: labelsVisible
+ * 
+ * Specifies if labels should be visible in the outline. Default is false.
+ */
+mxOutline.prototype.labelsVisible = false;
+
+/**
+ * Variable: updateOnPan
+ * 
+ * Specifies if <update> should be called for <mxEvent.PAN> in the source
+ * graph. Default is false.
+ */
+mxOutline.prototype.updateOnPan = false;
+
+/**
+ * Variable: sizerImage
+ * 
+ * Optional <mxImage> to be used for the sizer. Default is null.
+ */
+mxOutline.prototype.sizerImage = null;
+
+/**
+ * Variable: minScale
+ * 
+ * Minimum scale to be used. Default is 0.001.
+ */
+mxOutline.prototype.minScale = 0.0001;
+
+/**
+ * Variable: suspended
+ * 
+ * Optional boolean flag to suspend updates. Default is false. This flag will
+ * also suspend repaints of the outline. To toggle this switch, use the
+ * following code.
+ * 
+ * (code)
+ * nav.suspended = !nav.suspended;
+ * 
+ * if (!nav.suspended)
+ * {
+ *   nav.update(true);
+ * }
+ * (end)
+ */
+mxOutline.prototype.suspended = false;
+
+/**
+ * Variable: forceVmlHandles
+ * 
+ * Specifies if VML should be used to render the handles in this control. This
+ * is true for IE8 standards mode and false for all other browsers and modes.
+ * This is a workaround for rendering issues of HTML elements over elements
+ * with filters in IE 8 standards mode.
+ */
+mxOutline.prototype.forceVmlHandles = document.documentMode == 8;
+
+/**
+ * Function: createGraph
+ * 
+ * Creates the <mxGraph> used in the outline.
+ */
+mxOutline.prototype.createGraph = function(container)
+{
+	var graph = new mxGraph(container, this.source.getModel(), this.graphRenderHint, this.source.getStylesheet());
+	graph.foldingEnabled = false;
+	graph.autoScroll = false;
+	
+	return graph;
+};
+
+/**
+ * Function: init
+ * 
+ * Initializes the outline inside the given container.
+ */
+mxOutline.prototype.init = function(container)
+{
+	this.outline = this.createGraph(container);
+	
+	// Do not repaint when suspended
+	var outlineGraphModelChanged = this.outline.graphModelChanged;
+	this.outline.graphModelChanged = mxUtils.bind(this, function(changes)
+	{
+		if (!this.suspended && this.outline != null)
+		{
+			outlineGraphModelChanged.apply(this.outline, arguments);
+		}
+	});
+
+	// Enables faster painting in SVG
+	if (mxClient.IS_SVG)
+	{
+		var node = this.outline.getView().getCanvas().parentNode;
+		node.setAttribute('shape-rendering', 'optimizeSpeed');
+		node.setAttribute('image-rendering', 'optimizeSpeed');
+	}
+	
+	// Hides cursors and labels
+	this.outline.labelsVisible = this.labelsVisible;
+	this.outline.setEnabled(false);
+	
+	this.updateHandler = mxUtils.bind(this, function(sender, evt)
+	{
+		if (!this.suspended && !this.active)
+		{
+			this.update();
+		}
+	});
+	
+	// Updates the scale of the outline after a change of the main graph
+	this.source.getModel().addListener(mxEvent.CHANGE, this.updateHandler);
+	this.outline.addMouseListener(this);
+	
+	// Adds listeners to keep the outline in sync with the source graph
+	var view = this.source.getView();
+	view.addListener(mxEvent.SCALE, this.updateHandler);
+	view.addListener(mxEvent.TRANSLATE, this.updateHandler);
+	view.addListener(mxEvent.SCALE_AND_TRANSLATE, this.updateHandler);
+	view.addListener(mxEvent.DOWN, this.updateHandler);
+	view.addListener(mxEvent.UP, this.updateHandler);
+
+	// Updates blue rectangle on scroll
+	mxEvent.addListener(this.source.container, 'scroll', this.updateHandler);
+	
+	this.panHandler = mxUtils.bind(this, function(sender)
+	{
+		if (this.updateOnPan)
+		{
+			this.updateHandler.apply(this, arguments);
+		}
+	});
+	this.source.addListener(mxEvent.PAN, this.panHandler);
+	
+	// Refreshes the graph in the outline after a refresh of the main graph
+	this.refreshHandler = mxUtils.bind(this, function(sender)
+	{
+		this.outline.setStylesheet(this.source.getStylesheet());
+		this.outline.refresh();
+	});
+	this.source.addListener(mxEvent.REFRESH, this.refreshHandler);
+
+	// Creates the blue rectangle for the viewport
+	this.bounds = new mxRectangle(0, 0, 0, 0);
+	this.selectionBorder = new mxRectangleShape(this.bounds, null,
+		mxConstants.OUTLINE_COLOR, mxConstants.OUTLINE_STROKEWIDTH);
+	this.selectionBorder.dialect = this.outline.dialect;
+
+	if (this.forceVmlHandles)
+	{
+		this.selectionBorder.isHtmlAllowed = function()
+		{
+			return false;
+		};
+	}
+	
+	this.selectionBorder.init(this.outline.getView().getOverlayPane());
+
+	// Handles event by catching the initial pointer start and then listening to the
+	// complete gesture on the event target. This is needed because all the events
+	// are routed via the initial element even if that element is removed from the
+	// DOM, which happens when we repaint the selection border and zoom handles.
+	var handler = mxUtils.bind(this, function(evt)
+	{
+		var t = mxEvent.getSource(evt);
+		
+		var redirect = mxUtils.bind(this, function(evt)
+		{
+			this.outline.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt));
+		});
+		
+		var redirect2 = mxUtils.bind(this, function(evt)
+		{
+			mxEvent.removeGestureListeners(t, null, redirect, redirect2);
+			this.outline.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt));
+		});
+		
+		mxEvent.addGestureListeners(t, null, redirect, redirect2);
+		this.outline.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt));
+	});
+	
+	mxEvent.addGestureListeners(this.selectionBorder.node, handler);
+
+	// Creates a small blue rectangle for sizing (sizer handle)
+	this.sizer = this.createSizer();
+	
+	if (this.forceVmlHandles)
+	{
+		this.sizer.isHtmlAllowed = function()
+		{
+			return false;
+		};
+	}
+	
+	this.sizer.init(this.outline.getView().getOverlayPane());
+	
+	if (this.enabled)
+	{
+		this.sizer.node.style.cursor = 'nwse-resize';
+	}
+	
+	mxEvent.addGestureListeners(this.sizer.node, handler);
+
+	this.selectionBorder.node.style.display = (this.showViewport) ? '' : 'none';
+	this.sizer.node.style.display = this.selectionBorder.node.style.display;
+	this.selectionBorder.node.style.cursor = 'move';
+
+	this.update(false);
+};
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxOutline.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ * 
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean that specifies the new enabled state.
+ */
+mxOutline.prototype.setEnabled = function(value)
+{
+	this.enabled = value;
+};
+
+/**
+ * Function: setZoomEnabled
+ * 
+ * Enables or disables the zoom handling by showing or hiding the respective
+ * handle.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean that specifies the new enabled state.
+ */
+mxOutline.prototype.setZoomEnabled = function(value)
+{
+	this.sizer.node.style.visibility = (value) ? 'visible' : 'hidden';
+};
+
+/**
+ * Function: refresh
+ * 
+ * Invokes <update> and revalidate the outline. This method is deprecated.
+ */
+mxOutline.prototype.refresh = function()
+{
+	this.update(true);
+};
+
+/**
+ * Function: createSizer
+ * 
+ * Creates the shape used as the sizer.
+ */
+mxOutline.prototype.createSizer = function()
+{
+	if (this.sizerImage != null)
+	{
+		var sizer = new mxImageShape(new mxRectangle(0, 0, this.sizerImage.width, this.sizerImage.height), this.sizerImage.src);
+		sizer.dialect = this.outline.dialect;
+		
+		return sizer;
+	}
+	else
+	{
+		var sizer = new mxRectangleShape(new mxRectangle(0, 0, this.sizerSize, this.sizerSize),
+			mxConstants.OUTLINE_HANDLE_FILLCOLOR, mxConstants.OUTLINE_HANDLE_STROKECOLOR);
+		sizer.dialect = this.outline.dialect;
+	
+		return sizer;
+	}
+};
+
+/**
+ * Function: getSourceContainerSize
+ * 
+ * Returns the size of the source container.
+ */
+mxOutline.prototype.getSourceContainerSize = function()
+{
+	return new mxRectangle(0, 0, this.source.container.scrollWidth, this.source.container.scrollHeight);
+};
+
+/**
+ * Function: getOutlineOffset
+ * 
+ * Returns the offset for drawing the outline graph.
+ */
+mxOutline.prototype.getOutlineOffset = function(scale)
+{
+	return null;
+};
+
+/**
+ * Function: getOutlineOffset
+ * 
+ * Returns the offset for drawing the outline graph.
+ */
+mxOutline.prototype.getSourceGraphBounds = function()
+{
+	return this.source.getGraphBounds();
+};
+
+/**
+ * Function: update
+ * 
+ * Updates the outline.
+ */
+mxOutline.prototype.update = function(revalidate)
+{
+	if (this.source != null && this.outline != null)
+	{
+		var sourceScale = this.source.view.scale;
+		var scaledGraphBounds = this.getSourceGraphBounds();
+		var unscaledGraphBounds = new mxRectangle(scaledGraphBounds.x / sourceScale + this.source.panDx,
+				scaledGraphBounds.y / sourceScale + this.source.panDy, scaledGraphBounds.width / sourceScale,
+				scaledGraphBounds.height / sourceScale);
+
+		var unscaledFinderBounds = new mxRectangle(0, 0,
+			this.source.container.clientWidth / sourceScale,
+			this.source.container.clientHeight / sourceScale);
+		
+		var union = unscaledGraphBounds.clone();
+		union.add(unscaledFinderBounds);
+	
+		// Zooms to the scrollable area if that is bigger than the graph
+		var size = this.getSourceContainerSize();
+		var completeWidth = Math.max(size.width / sourceScale, union.width);
+		var completeHeight = Math.max(size.height / sourceScale, union.height);
+	
+		var availableWidth = Math.max(0, this.outline.container.clientWidth - this.border);
+		var availableHeight = Math.max(0, this.outline.container.clientHeight - this.border);
+		
+		var outlineScale = Math.min(availableWidth / completeWidth, availableHeight / completeHeight);
+		var scale = (isNaN(outlineScale)) ? this.minScale : Math.max(this.minScale, outlineScale);
+
+		if (scale > 0)
+		{
+			if (this.outline.getView().scale != scale)
+			{
+				this.outline.getView().scale = scale;
+				revalidate = true;
+			}
+		
+			var navView = this.outline.getView();
+			
+			if (navView.currentRoot != this.source.getView().currentRoot)
+			{
+				navView.setCurrentRoot(this.source.getView().currentRoot);
+			}
+
+			var t = this.source.view.translate;
+			var tx = t.x + this.source.panDx;
+			var ty = t.y + this.source.panDy;
+			
+			var off = this.getOutlineOffset(scale);
+			
+			if (off != null)
+			{
+				tx += off.x;
+				ty += off.y;
+			}
+			
+			if (unscaledGraphBounds.x < 0)
+			{
+				tx = tx - unscaledGraphBounds.x;
+			}
+			if (unscaledGraphBounds.y < 0)
+			{
+				ty = ty - unscaledGraphBounds.y;
+			}
+			
+			if (navView.translate.x != tx || navView.translate.y != ty)
+			{
+				navView.translate.x = tx;
+				navView.translate.y = ty;
+				revalidate = true;
+			}
+		
+			// Prepares local variables for computations
+			var t2 = navView.translate;
+			scale = this.source.getView().scale;
+			var scale2 = scale / navView.scale;
+			var scale3 = 1.0 / navView.scale;
+			var container = this.source.container;
+			
+			// Updates the bounds of the viewrect in the navigation
+			this.bounds = new mxRectangle(
+				(t2.x - t.x - this.source.panDx) / scale3,
+				(t2.y - t.y - this.source.panDy) / scale3,
+				(container.clientWidth / scale2),
+				(container.clientHeight / scale2));
+			
+			// Adds the scrollbar offset to the finder
+			this.bounds.x += this.source.container.scrollLeft * navView.scale / scale;
+			this.bounds.y += this.source.container.scrollTop * navView.scale / scale;
+			
+			var b = this.selectionBorder.bounds;
+			
+			if (b.x != this.bounds.x || b.y != this.bounds.y || b.width != this.bounds.width || b.height != this.bounds.height)
+			{
+				this.selectionBorder.bounds = this.bounds;
+				this.selectionBorder.redraw();
+			}
+		
+			// Updates the bounds of the zoom handle at the bottom right
+			var b = this.sizer.bounds;
+			var b2 = new mxRectangle(this.bounds.x + this.bounds.width - b.width / 2,
+					this.bounds.y + this.bounds.height - b.height / 2, b.width, b.height);
+
+			if (b.x != b2.x || b.y != b2.y || b.width != b2.width || b.height != b2.height)
+			{
+				this.sizer.bounds = b2;
+				
+				// Avoids update of visibility in redraw for VML
+				if (this.sizer.node.style.visibility != 'hidden')
+				{
+					this.sizer.redraw();
+				}
+			}
+
+			if (revalidate)
+			{
+				this.outline.view.revalidate();
+			}
+		}
+	}
+};
+
+/**
+ * Function: mouseDown
+ * 
+ * Handles the event by starting a translation or zoom.
+ */
+mxOutline.prototype.mouseDown = function(sender, me)
+{
+	if (this.enabled && this.showViewport)
+	{
+		var tol = (!mxEvent.isMouseEvent(me.getEvent())) ? this.source.tolerance : 0;
+		var hit = (this.source.allowHandleBoundsCheck && (mxClient.IS_IE || tol > 0)) ?
+				new mxRectangle(me.getGraphX() - tol, me.getGraphY() - tol, 2 * tol, 2 * tol) : null;
+		this.zoom = me.isSource(this.sizer) || (hit != null && mxUtils.intersects(shape.bounds, hit));
+		this.startX = me.getX();
+		this.startY = me.getY();
+		this.active = true;
+
+		if (this.source.useScrollbarsForPanning && mxUtils.hasScrollbars(this.source.container))
+		{
+			this.dx0 = this.source.container.scrollLeft;
+			this.dy0 = this.source.container.scrollTop;
+		}
+		else
+		{
+			this.dx0 = 0;
+			this.dy0 = 0;
+		}
+	}
+
+	me.consume();
+};
+
+/**
+ * Function: mouseMove
+ * 
+ * Handles the event by previewing the viewrect in <graph> and updating the
+ * rectangle that represents the viewrect in the outline.
+ */
+mxOutline.prototype.mouseMove = function(sender, me)
+{
+	if (this.active)
+	{
+		this.selectionBorder.node.style.display = (this.showViewport) ? '' : 'none';
+		this.sizer.node.style.display = this.selectionBorder.node.style.display; 
+
+		var delta = this.getTranslateForEvent(me);
+		var dx = delta.x;
+		var dy = delta.y;
+		var bounds = null;
+		
+		if (!this.zoom)
+		{
+			// Previews the panning on the source graph
+			var scale = this.outline.getView().scale;
+			bounds = new mxRectangle(this.bounds.x + dx,
+				this.bounds.y + dy, this.bounds.width, this.bounds.height);
+			this.selectionBorder.bounds = bounds;
+			this.selectionBorder.redraw();
+			dx /= scale;
+			dx *= this.source.getView().scale;
+			dy /= scale;
+			dy *= this.source.getView().scale;
+			this.source.panGraph(-dx - this.dx0, -dy - this.dy0);
+		}
+		else
+		{
+			// Does *not* preview zooming on the source graph
+			var container = this.source.container;
+			var viewRatio = container.clientWidth / container.clientHeight;
+			dy = dx / viewRatio;
+			bounds = new mxRectangle(this.bounds.x,
+				this.bounds.y,
+				Math.max(1, this.bounds.width + dx),
+				Math.max(1, this.bounds.height + dy));
+			this.selectionBorder.bounds = bounds;
+			this.selectionBorder.redraw();
+		}
+		
+		// Updates the zoom handle
+		var b = this.sizer.bounds;
+		this.sizer.bounds = new mxRectangle(
+			bounds.x + bounds.width - b.width / 2,
+			bounds.y + bounds.height - b.height / 2,
+			b.width, b.height);
+		
+		// Avoids update of visibility in redraw for VML
+		if (this.sizer.node.style.visibility != 'hidden')
+		{
+			this.sizer.redraw();
+		}
+		
+		me.consume();
+	}
+};
+
+/**
+ * Function: getTranslateForEvent
+ * 
+ * Gets the translate for the given mouse event. Here is an example to limit
+ * the outline to stay within positive coordinates:
+ * 
+ * (code)
+ * outline.getTranslateForEvent = function(me)
+ * {
+ *   var pt = new mxPoint(me.getX() - this.startX, me.getY() - this.startY);
+ *   
+ *   if (!this.zoom)
+ *   {
+ *     var tr = this.source.view.translate;
+ *     pt.x = Math.max(tr.x * this.outline.view.scale, pt.x);
+ *     pt.y = Math.max(tr.y * this.outline.view.scale, pt.y);
+ *   }
+ *   
+ *   return pt;
+ * };
+ * (end)
+ */
+mxOutline.prototype.getTranslateForEvent = function(me)
+{
+	return new mxPoint(me.getX() - this.startX, me.getY() - this.startY);
+};
+
+/**
+ * Function: mouseUp
+ * 
+ * Handles the event by applying the translation or zoom to <graph>.
+ */
+mxOutline.prototype.mouseUp = function(sender, me)
+{
+	if (this.active)
+	{
+		var delta = this.getTranslateForEvent(me);
+		var dx = delta.x;
+		var dy = delta.y;
+		
+		if (Math.abs(dx) > 0 || Math.abs(dy) > 0)
+		{
+			if (!this.zoom)
+			{
+				// Applies the new translation if the source
+				// has no scrollbars
+				if (!this.source.useScrollbarsForPanning ||
+					!mxUtils.hasScrollbars(this.source.container))
+				{
+					this.source.panGraph(0, 0);
+					dx /= this.outline.getView().scale;
+					dy /= this.outline.getView().scale;
+					var t = this.source.getView().translate;
+					this.source.getView().setTranslate(t.x - dx, t.y - dy);
+				}
+			}
+			else
+			{
+				// Applies the new zoom
+				var w = this.selectionBorder.bounds.width;
+				var scale = this.source.getView().scale;
+				this.source.zoomTo(Math.max(this.minScale, scale - (dx * scale) / w), false);
+			}
+
+			this.update();
+			me.consume();
+		}
+			
+		// Resets the state of the handler
+		this.index = null;
+		this.active = false;
+	}
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroy this outline and removes all listeners from <source>.
+ */
+mxOutline.prototype.destroy = function()
+{
+	if (this.source != null)
+	{
+		this.source.removeListener(this.panHandler);
+		this.source.removeListener(this.refreshHandler);
+		this.source.getModel().removeListener(this.updateHandler);
+		this.source.getView().removeListener(this.updateHandler);
+		mxEvent.addListener(this.source.container, 'scroll', this.updateHandler);
+		this.source = null;
+	}
+	
+	if (this.outline != null)
+	{
+		this.outline.removeMouseListener(this);
+		this.outline.destroy();
+		this.outline = null;
+	}
+
+	if (this.selectionBorder != null)
+	{
+		this.selectionBorder.destroy();
+		this.selectionBorder = null;
+	}
+	
+	if (this.sizer != null)
+	{
+		this.sizer.destroy();
+		this.sizer = null;
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxMultiplicity
+ * 
+ * Defines invalid connections along with the error messages that they produce.
+ * To add or remove rules on a graph, you must add/remove instances of this
+ * class to <mxGraph.multiplicities>.
+ * 
+ * Example:
+ * 
+ * (code)
+ * graph.multiplicities.push(new mxMultiplicity(
+ *   true, 'rectangle', null, null, 0, 2, ['circle'],
+ *   'Only 2 targets allowed',
+ *   'Only circle targets allowed'));
+ * (end)
+ * 
+ * Defines a rule where each rectangle must be connected to no more than 2
+ * circles and no other types of targets are allowed.
+ * 
+ * Constructor: mxMultiplicity
+ * 
+ * Instantiate class mxMultiplicity in order to describe allowed
+ * connections in a graph. Not all constraints can be enforced while
+ * editing, some must be checked at validation time. The <countError> and
+ * <typeError> are treated as resource keys in <mxResources>.
+ * 
+ * Parameters:
+ * 
+ * source - Boolean indicating if this rule applies to the source or target
+ * terminal.
+ * type - Type of the source or target terminal that this rule applies to.
+ * See <type> for more information.
+ * attr - Optional attribute name to match the source or target terminal.
+ * value - Optional attribute value to match the source or target terminal.
+ * min - Minimum number of edges for this rule. Default is 1.
+ * max - Maximum number of edges for this rule. n means infinite. Default
+ * is n.
+ * validNeighbors - Array of types of the opposite terminal for which this
+ * rule applies.
+ * countError - Error to be displayed for invalid number of edges.
+ * typeError - Error to be displayed for invalid opposite terminals.
+ * validNeighborsAllowed - Optional boolean indicating if the array of
+ * opposite types should be valid or invalid.
+ */
+function mxMultiplicity(source, type, attr, value, min, max,
+	validNeighbors, countError, typeError, validNeighborsAllowed)
+{
+	this.source = source;
+	this.type = type;
+	this.attr = attr;
+	this.value = value;
+	this.min = (min != null) ? min : 0;
+	this.max = (max != null) ? max : 'n';
+	this.validNeighbors = validNeighbors;
+	this.countError = mxResources.get(countError) || countError;
+	this.typeError = mxResources.get(typeError) || typeError;
+	this.validNeighborsAllowed = (validNeighborsAllowed != null) ?
+		validNeighborsAllowed : true;
+};
+
+/**
+ * Variable: type
+ * 
+ * Defines the type of the source or target terminal. The type is a string
+ * passed to <mxUtils.isNode> together with the source or target vertex
+ * value as the first argument.
+ */
+mxMultiplicity.prototype.type = null;
+
+/**
+ * Variable: attr
+ * 
+ * Optional string that specifies the attributename to be passed to
+ * <mxUtils.isNode> to check if the rule applies to a cell.
+ */
+mxMultiplicity.prototype.attr = null;
+
+/**
+ * Variable: value
+ * 
+ * Optional string that specifies the value of the attribute to be passed
+ * to <mxUtils.isNode> to check if the rule applies to a cell.
+ */
+mxMultiplicity.prototype.value = null;
+
+/**
+ * Variable: source
+ * 
+ * Boolean that specifies if the rule is applied to the source or target
+ * terminal of an edge.
+ */
+mxMultiplicity.prototype.source = null;
+
+/**
+ * Variable: min
+ * 
+ * Defines the minimum number of connections for which this rule applies.
+ * Default is 0.
+ */
+mxMultiplicity.prototype.min = null;
+
+/**
+ * Variable: max
+ * 
+ * Defines the maximum number of connections for which this rule applies.
+ * A value of 'n' means unlimited times. Default is 'n'. 
+ */
+mxMultiplicity.prototype.max = null;
+
+/**
+ * Variable: validNeighbors
+ * 
+ * Holds an array of strings that specify the type of neighbor for which
+ * this rule applies. The strings are used in <mxCell.is> on the opposite
+ * terminal to check if the rule applies to the connection.
+ */
+mxMultiplicity.prototype.validNeighbors = null;
+
+/**
+ * Variable: validNeighborsAllowed
+ * 
+ * Boolean indicating if the list of validNeighbors are those that are allowed
+ * for this rule or those that are not allowed for this rule.
+ */
+mxMultiplicity.prototype.validNeighborsAllowed = true;
+
+/**
+ * Variable: countError
+ * 
+ * Holds the localized error message to be displayed if the number of
+ * connections for which the rule applies is smaller than <min> or greater
+ * than <max>.
+ */
+mxMultiplicity.prototype.countError = null;
+
+/**
+ * Variable: typeError
+ * 
+ * Holds the localized error message to be displayed if the type of the
+ * neighbor for a connection does not match the rule.
+ */
+mxMultiplicity.prototype.typeError = null;
+
+/**
+ * Function: check
+ * 
+ * Checks the multiplicity for the given arguments and returns the error
+ * for the given connection or null if the multiplicity does not apply.
+ *  
+ * Parameters:
+ * 
+ * graph - Reference to the enclosing <mxGraph> instance.
+ * edge - <mxCell> that represents the edge to validate.
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ * sourceOut - Number of outgoing edges from the source terminal.
+ * targetIn - Number of incoming edges for the target terminal.
+ */
+mxMultiplicity.prototype.check = function(graph, edge, source, target, sourceOut, targetIn)
+{
+	var error = '';
+
+	if ((this.source && this.checkTerminal(graph, source, edge)) ||
+		(!this.source && this.checkTerminal(graph, target, edge)))
+	{
+		if (this.countError != null && 
+			((this.source && (this.max == 0 || (sourceOut >= this.max))) ||
+			(!this.source && (this.max == 0 || (targetIn >= this.max)))))
+		{
+			error += this.countError + '\n';
+		}
+
+		if (this.validNeighbors != null && this.typeError != null && this.validNeighbors.length > 0)
+		{
+			var isValid = this.checkNeighbors(graph, edge, source, target);
+
+			if (!isValid)
+			{
+				error += this.typeError + '\n';
+			}
+		}
+	}
+	
+	return (error.length > 0) ? error : null;
+};
+
+/**
+ * Function: checkNeighbors
+ * 
+ * Checks if there are any valid neighbours in <validNeighbors>. This is only
+ * called if <validNeighbors> is a non-empty array.
+ */
+mxMultiplicity.prototype.checkNeighbors = function(graph, edge, source, target)
+{
+	var sourceValue = graph.model.getValue(source);
+	var targetValue = graph.model.getValue(target);
+	var isValid = !this.validNeighborsAllowed;
+	var valid = this.validNeighbors;
+	
+	for (var j = 0; j < valid.length; j++)
+	{
+		if (this.source &&
+			this.checkType(graph, targetValue, valid[j]))
+		{
+			isValid = this.validNeighborsAllowed;
+			break;
+		}
+		else if (!this.source && 
+			this.checkType(graph, sourceValue, valid[j]))
+		{
+			isValid = this.validNeighborsAllowed;
+			break;
+		}
+	}
+	
+	return isValid;
+};
+
+/**
+ * Function: checkTerminal
+ * 
+ * Checks the given terminal cell and returns true if this rule applies. The
+ * given cell is the source or target of the given edge, depending on
+ * <source>. This implementation uses <checkType> on the terminal's value.
+ */
+mxMultiplicity.prototype.checkTerminal = function(graph, terminal, edge)
+{
+	var value = graph.model.getValue(terminal);
+	
+	return this.checkType(graph, value, this.type, this.attr, this.value);
+};
+
+/**
+ * Function: checkType
+ * 
+ * Checks the type of the given value.
+ */
+mxMultiplicity.prototype.checkType = function(graph, value, type, attr, attrValue)
+{
+	if (value != null)
+	{
+		if (!isNaN(value.nodeType)) // Checks if value is a DOM node
+		{
+			return mxUtils.isNode(value, type, attr, attrValue);
+		}
+		else
+		{
+			return value == type;
+		}
+	}
+	
+	return false;
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxLayoutManager
+ * 
+ * Implements a layout manager that runs a given layout after any changes to the graph:
+ * 
+ * Example:
+ * 
+ * (code)
+ * var layoutMgr = new mxLayoutManager(graph);
+ * layoutMgr.getLayout = function(cell)
+ * {
+ *   return layout;
+ * };
+ * (end)
+ * 
+ * Event: mxEvent.LAYOUT_CELLS
+ * 
+ * Fires between begin- and endUpdate after all cells have been layouted in
+ * <layoutCells>. The <code>cells</code> property contains all cells that have
+ * been passed to <layoutCells>.
+ * 
+ * Constructor: mxLayoutManager
+ *
+ * Constructs a new automatic layout for the given graph.
+ *
+ * Arguments:
+ * 
+ * graph - Reference to the enclosing graph. 
+ */
+function mxLayoutManager(graph)
+{
+	// Executes the layout before the changes are dispatched
+	this.undoHandler = mxUtils.bind(this, function(sender, evt)
+	{
+		if (this.isEnabled())
+		{
+			this.beforeUndo(evt.getProperty('edit'));
+		}
+	});
+	
+	// Notifies the layout of a move operation inside a parent
+	this.moveHandler = mxUtils.bind(this, function(sender, evt)
+	{
+		if (this.isEnabled())
+		{
+			this.cellsMoved(evt.getProperty('cells'), evt.getProperty('event'));
+		}
+	});
+	
+	this.setGraph(graph);
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxLayoutManager.prototype = new mxEventSource();
+mxLayoutManager.prototype.constructor = mxLayoutManager;
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxLayoutManager.prototype.graph = null;
+
+/**
+ * Variable: bubbling
+ * 
+ * Specifies if the layout should bubble along
+ * the cell hierarchy. Default is true.
+ */
+mxLayoutManager.prototype.bubbling = true;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies if event handling is enabled. Default is true.
+ */
+mxLayoutManager.prototype.enabled = true;
+
+/**
+ * Variable: updateHandler
+ * 
+ * Holds the function that handles the endUpdate event.
+ */
+mxLayoutManager.prototype.updateHandler = null;
+
+/**
+ * Variable: moveHandler
+ * 
+ * Holds the function that handles the move event.
+ */
+mxLayoutManager.prototype.moveHandler = null;
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxLayoutManager.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ * 
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ * 
+ * Parameters:
+ * 
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxLayoutManager.prototype.setEnabled = function(enabled)
+{
+	this.enabled = enabled;
+};
+
+/**
+ * Function: isBubbling
+ * 
+ * Returns true if a layout should bubble, that is, if the parent layout
+ * should be executed whenever a cell layout (layout of the children of
+ * a cell) has been executed. This implementation returns <bubbling>.
+ */
+mxLayoutManager.prototype.isBubbling = function()
+{
+	return this.bubbling;
+};
+
+/**
+ * Function: setBubbling
+ * 
+ * Sets <bubbling>.
+ */
+mxLayoutManager.prototype.setBubbling = function(value)
+{
+	this.bubbling = value;
+};
+
+/**
+ * Function: getGraph
+ * 
+ * Returns the graph that this layout operates on.
+ */
+mxLayoutManager.prototype.getGraph = function()
+{
+	return this.graph;
+};
+
+/**
+ * Function: setGraph
+ * 
+ * Sets the graph that the layouts operate on.
+ */
+mxLayoutManager.prototype.setGraph = function(graph)
+{
+	if (this.graph != null)
+	{
+		var model = this.graph.getModel();		
+		model.removeListener(this.undoHandler);
+		this.graph.removeListener(this.moveHandler);
+	}
+	
+	this.graph = graph;
+	
+	if (this.graph != null)
+	{
+		var model = this.graph.getModel();	
+		model.addListener(mxEvent.BEFORE_UNDO, this.undoHandler);
+		this.graph.addListener(mxEvent.MOVE_CELLS, this.moveHandler);
+	}
+};
+
+/**
+ * Function: getLayout
+ * 
+ * Returns the layout to be executed for the given graph and parent.
+ */
+mxLayoutManager.prototype.getLayout = function(parent)
+{
+	return null;
+};
+
+/**
+ * Function: beforeUndo
+ * 
+ * Called from the undoHandler.
+ *
+ * Parameters:
+ * 
+ * cell - Array of <mxCells> that have been moved.
+ * evt - Mouse event that represents the mousedown.
+ */
+mxLayoutManager.prototype.beforeUndo = function(undoableEdit)
+{
+	var cells = this.getCellsForChanges(undoableEdit.changes);
+	var model = this.getGraph().getModel();
+
+	// Adds all descendants
+	var tmp = [];
+	
+	for (var i = 0; i < cells.length; i++)
+	{
+		tmp = tmp.concat(model.getDescendants(cells[i]));
+	}
+	
+	cells = tmp;
+	
+	// Adds all parent ancestors
+	if (this.isBubbling())
+	{
+		tmp = model.getParents(cells);
+		
+		while (tmp.length > 0)
+		{
+			cells = cells.concat(tmp);
+			tmp = model.getParents(tmp);
+		}
+	}
+	
+	this.executeLayoutForCells(cells);
+};
+
+/**
+ * Function: executeLayout
+ * 
+ * Executes the given layout on the given parent.
+ */
+mxLayoutManager.prototype.executeLayoutForCells = function(cells)
+{
+	// Adds reverse to this array to avoid duplicate execution of leafes
+	// Works like capture/bubble for events, first executes all layout
+	// from top to bottom and in reverse order and removes duplicates.
+	var sorted = mxUtils.sortCells(cells, true);
+	sorted = sorted.concat(sorted.slice().reverse());
+	this.layoutCells(sorted);
+};
+
+/**
+ * Function: cellsMoved
+ * 
+ * Called from the moveHandler.
+ *
+ * Parameters:
+ * 
+ * cell - Array of <mxCells> that have been moved.
+ * evt - Mouse event that represents the mousedown.
+ */
+mxLayoutManager.prototype.cellsMoved = function(cells, evt)
+{
+	if (cells != null && evt != null)
+	{
+		var point = mxUtils.convertPoint(this.getGraph().container,
+			mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+		var model = this.getGraph().getModel();
+		
+		// Checks if a layout exists to take care of the moving if the
+		// parent itself is not being moved
+		for (var i = 0; i < cells.length; i++)
+		{
+			var parent = model.getParent(cells[i]);
+			
+			if (mxUtils.indexOf(cells, parent) < 0)
+			{
+				var layout = this.getLayout(parent);
+	
+				if (layout != null)
+				{
+					layout.moveCell(cells[i], point.x, point.y);
+				}
+			}
+		}
+	}
+};
+
+/**
+ * Function: getCellsForEdit
+ * 
+ * Returns the cells to be layouted for the given sequence of changes.
+ */
+mxLayoutManager.prototype.getCellsForChanges = function(changes)
+{
+	var dict = new mxDictionary();
+	var result = [];
+	
+	for (var i = 0; i < changes.length; i++)
+	{
+		var change = changes[i];
+		
+		if (change instanceof mxRootChange)
+		{
+			return [];
+		}
+		else
+		{
+			var cells = this.getCellsForChange(change);
+			
+			for (var j = 0; j < cells.length; j++)
+			{
+				if (cells[j] != null && !dict.get(cells[j]))
+				{
+					dict.put(cells[j], true);
+					result.push(cells[j]);
+				}
+			}
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: getCellsForChange
+ * 
+ * Executes all layouts which have been scheduled during the
+ * changes.
+ */
+mxLayoutManager.prototype.getCellsForChange = function(change)
+{
+	var model = this.getGraph().getModel();
+	
+	if (change instanceof mxChildChange)
+	{
+		return [change.child, change.previous, model.getParent(change.child)];
+	}
+	else if (change instanceof mxTerminalChange || change instanceof mxGeometryChange)
+	{
+		return [change.cell, model.getParent(change.cell)];
+	}
+	else if (change instanceof mxVisibleChange || change instanceof mxStyleChange)
+	{
+		return [change.cell];
+	}
+	
+	return [];
+};
+
+/**
+ * Function: layoutCells
+ * 
+ * Executes all layouts which have been scheduled during the
+ * changes.
+ */
+mxLayoutManager.prototype.layoutCells = function(cells)
+{
+	if (cells.length > 0)
+	{
+		// Invokes the layouts while removing duplicates
+		var model = this.getGraph().getModel();
+		
+		model.beginUpdate();
+		try 
+		{
+			var last = null;
+			
+			for (var i = 0; i < cells.length; i++)
+			{
+				if (cells[i] != model.getRoot() && cells[i] != last)
+				{
+					if (this.executeLayout(this.getLayout(cells[i]), cells[i]))
+					{
+						last = cells[i];
+					}
+				}
+			}
+			
+			this.fireEvent(new mxEventObject(mxEvent.LAYOUT_CELLS, 'cells', cells));
+		}
+		finally
+		{
+			model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: executeLayout
+ * 
+ * Executes the given layout on the given parent.
+ */
+mxLayoutManager.prototype.executeLayout = function(layout, parent)
+{
+	var result = false;
+	
+	if (layout != null && parent != null)
+	{
+		layout.execute(parent);
+		result = true;
+	}
+	
+	return result;
+};
+
+/**
+ * Function: destroy
+ * 
+ * Removes all handlers from the <graph> and deletes the reference to it.
+ */
+mxLayoutManager.prototype.destroy = function()
+{
+	this.setGraph(null);
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxSwimlaneManager
+ * 
+ * Manager for swimlanes and nested swimlanes that sets the size of newly added
+ * swimlanes to that of their siblings, and propagates changes to the size of a
+ * swimlane to its siblings, if <siblings> is true, and its ancestors, if
+ * <bubbling> is true.
+ * 
+ * Constructor: mxSwimlaneManager
+ *
+ * Constructs a new swimlane manager for the given graph.
+ *
+ * Arguments:
+ * 
+ * graph - Reference to the enclosing graph. 
+ */
+function mxSwimlaneManager(graph, horizontal, addEnabled, resizeEnabled)
+{
+	this.horizontal = (horizontal != null) ? horizontal : true;
+	this.addEnabled = (addEnabled != null) ? addEnabled : true;
+	this.resizeEnabled = (resizeEnabled != null) ? resizeEnabled : true;
+
+	this.addHandler = mxUtils.bind(this, function(sender, evt)
+	{
+		if (this.isEnabled() && this.isAddEnabled())
+		{
+			this.cellsAdded(evt.getProperty('cells'));
+		}
+	});
+	
+	this.resizeHandler = mxUtils.bind(this, function(sender, evt)
+	{
+		if (this.isEnabled() && this.isResizeEnabled())
+		{
+			this.cellsResized(evt.getProperty('cells'));
+		}
+	});
+	
+	this.setGraph(graph);
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxSwimlaneManager.prototype = new mxEventSource();
+mxSwimlaneManager.prototype.constructor = mxSwimlaneManager;
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxSwimlaneManager.prototype.graph = null;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies if event handling is enabled. Default is true.
+ */
+mxSwimlaneManager.prototype.enabled = true;
+
+/**
+ * Variable: horizontal
+ * 
+ * Specifies the orientation of the swimlanes. Default is true.
+ */
+mxSwimlaneManager.prototype.horizontal = true;
+
+/**
+ * Variable: addEnabled
+ * 
+ * Specifies if newly added cells should be resized to match the size of their
+ * existing siblings. Default is true.
+ */
+mxSwimlaneManager.prototype.addEnabled = true;
+
+/**
+ * Variable: resizeEnabled
+ * 
+ * Specifies if resizing of swimlanes should be handled. Default is true.
+ */
+mxSwimlaneManager.prototype.resizeEnabled = true;
+
+/**
+ * Variable: moveHandler
+ * 
+ * Holds the function that handles the move event.
+ */
+mxSwimlaneManager.prototype.addHandler = null;
+
+/**
+ * Variable: moveHandler
+ * 
+ * Holds the function that handles the move event.
+ */
+mxSwimlaneManager.prototype.resizeHandler = null;
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxSwimlaneManager.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ * 
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ * 
+ * Parameters:
+ * 
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxSwimlaneManager.prototype.setEnabled = function(value)
+{
+	this.enabled = value;
+};
+
+/**
+ * Function: isHorizontal
+ * 
+ * Returns <horizontal>.
+ */
+mxSwimlaneManager.prototype.isHorizontal = function()
+{
+	return this.horizontal;
+};
+
+/**
+ * Function: setHorizontal
+ * 
+ * Sets <horizontal>.
+ */
+mxSwimlaneManager.prototype.setHorizontal = function(value)
+{
+	this.horizontal = value;
+};
+
+/**
+ * Function: isAddEnabled
+ * 
+ * Returns <addEnabled>.
+ */
+mxSwimlaneManager.prototype.isAddEnabled = function()
+{
+	return this.addEnabled;
+};
+
+/**
+ * Function: setAddEnabled
+ * 
+ * Sets <addEnabled>.
+ */
+mxSwimlaneManager.prototype.setAddEnabled = function(value)
+{
+	this.addEnabled = value;
+};
+
+/**
+ * Function: isResizeEnabled
+ * 
+ * Returns <resizeEnabled>.
+ */
+mxSwimlaneManager.prototype.isResizeEnabled = function()
+{
+	return this.resizeEnabled;
+};
+
+/**
+ * Function: setResizeEnabled
+ * 
+ * Sets <resizeEnabled>.
+ */
+mxSwimlaneManager.prototype.setResizeEnabled = function(value)
+{
+	this.resizeEnabled = value;
+};
+
+/**
+ * Function: getGraph
+ * 
+ * Returns the graph that this manager operates on.
+ */
+mxSwimlaneManager.prototype.getGraph = function()
+{
+	return this.graph;
+};
+
+/**
+ * Function: setGraph
+ * 
+ * Sets the graph that the manager operates on.
+ */
+mxSwimlaneManager.prototype.setGraph = function(graph)
+{
+	if (this.graph != null)
+	{
+		this.graph.removeListener(this.addHandler);
+		this.graph.removeListener(this.resizeHandler);
+	}
+	
+	this.graph = graph;
+	
+	if (this.graph != null)
+	{
+		this.graph.addListener(mxEvent.ADD_CELLS, this.addHandler);
+		this.graph.addListener(mxEvent.CELLS_RESIZED, this.resizeHandler);
+	}
+};
+
+/**
+ * Function: isSwimlaneIgnored
+ * 
+ * Returns true if the given swimlane should be ignored.
+ */
+mxSwimlaneManager.prototype.isSwimlaneIgnored = function(swimlane)
+{
+	return !this.getGraph().isSwimlane(swimlane);
+};
+
+/**
+ * Function: isCellHorizontal
+ * 
+ * Returns true if the given cell is horizontal. If the given cell is not a
+ * swimlane, then the global orientation is returned.
+ */
+mxSwimlaneManager.prototype.isCellHorizontal = function(cell)
+{
+	if (this.graph.isSwimlane(cell))
+	{
+		var style = this.graph.getCellStyle(cell);
+		
+		return mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, 1) == 1;
+	}
+	
+	return !this.isHorizontal();
+};
+
+/**
+ * Function: cellsAdded
+ * 
+ * Called if any cells have been added.
+ * 
+ * Parameters:
+ * 
+ * cell - Array of <mxCells> that have been added.
+ */
+mxSwimlaneManager.prototype.cellsAdded = function(cells)
+{
+	if (cells != null)
+	{
+		var model = this.getGraph().getModel();
+
+		model.beginUpdate();
+		try
+		{
+			for (var i = 0; i < cells.length; i++)
+			{
+				if (!this.isSwimlaneIgnored(cells[i]))
+				{
+					this.swimlaneAdded(cells[i]);
+				}
+			}
+		}
+		finally
+		{
+			model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: swimlaneAdded
+ * 
+ * Updates the size of the given swimlane to match that of any existing
+ * siblings swimlanes.
+ * 
+ * Parameters:
+ * 
+ * swimlane - <mxCell> that represents the new swimlane.
+ */
+mxSwimlaneManager.prototype.swimlaneAdded = function(swimlane)
+{
+	var model = this.getGraph().getModel();
+	var parent = model.getParent(swimlane);
+	var childCount = model.getChildCount(parent);
+	var geo = null;
+	
+	// Finds the first valid sibling swimlane as reference
+	for (var i = 0; i < childCount; i++)
+	{
+		var child = model.getChildAt(parent, i);
+		
+		if (child != swimlane && !this.isSwimlaneIgnored(child))
+		{
+			geo = model.getGeometry(child);
+			
+			if (geo != null)
+			{	
+				break;
+			}
+		}
+	}
+	
+	// Applies the size of the refernece to the newly added swimlane
+	if (geo != null)
+	{
+		var parentHorizontal = (parent != null) ? this.isCellHorizontal(parent) : this.horizontal;
+		this.resizeSwimlane(swimlane, geo.width, geo.height, parentHorizontal);
+	}
+};
+
+/**
+ * Function: cellsResized
+ * 
+ * Called if any cells have been resizes. Calls <swimlaneResized> for all
+ * swimlanes where <isSwimlaneIgnored> returns false.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> whose size was changed.
+ */
+mxSwimlaneManager.prototype.cellsResized = function(cells)
+{
+	if (cells != null)
+	{
+		var model = this.getGraph().getModel();
+		
+		model.beginUpdate();
+		try
+		{
+			// Finds the top-level swimlanes and adds offsets
+			for (var i = 0; i < cells.length; i++)
+			{
+				if (!this.isSwimlaneIgnored(cells[i]))
+				{
+					var geo = model.getGeometry(cells[i]);
+
+					if (geo != null)
+					{
+						var size = new mxRectangle(0, 0, geo.width, geo.height);
+						var top = cells[i];
+						var current = top;
+						
+						while (current != null)
+						{
+							top = current;
+							current = model.getParent(current);
+							var tmp = (this.graph.isSwimlane(current)) ?
+									this.graph.getStartSize(current) :
+									new mxRectangle();
+							size.width += tmp.width;
+							size.height += tmp.height;
+						}
+						
+						var parentHorizontal = (current != null) ? this.isCellHorizontal(current) : this.horizontal;
+						this.resizeSwimlane(top, size.width, size.height, parentHorizontal);
+					}
+				}
+			}
+		}
+		finally
+		{
+			model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: resizeSwimlane
+ * 
+ * Called from <cellsResized> for all swimlanes that are not ignored to update
+ * the size of the siblings and the size of the parent swimlanes, recursively,
+ * if <bubbling> is true.
+ * 
+ * Parameters:
+ * 
+ * swimlane - <mxCell> whose size has changed.
+ */
+mxSwimlaneManager.prototype.resizeSwimlane = function(swimlane, w, h, parentHorizontal)
+{
+	var model = this.getGraph().getModel();
+	
+	model.beginUpdate();
+	try
+	{
+		var horizontal = this.isCellHorizontal(swimlane);
+		
+		if (!this.isSwimlaneIgnored(swimlane))
+		{
+			var geo = model.getGeometry(swimlane);
+			
+			if (geo != null)
+			{
+				if ((parentHorizontal && geo.height != h) || (!parentHorizontal && geo.width != w))
+				{
+					geo = geo.clone();
+					
+					if (parentHorizontal)
+					{
+						geo.height = h;
+					}
+					else
+					{
+						geo.width = w;
+					}
+
+					model.setGeometry(swimlane, geo);
+				}
+			}
+		}
+
+		var tmp = (this.graph.isSwimlane(swimlane)) ?
+				this.graph.getStartSize(swimlane) :
+				new mxRectangle();
+		w -= tmp.width;
+		h -= tmp.height;
+		
+		var childCount = model.getChildCount(swimlane);
+		
+		for (var i = 0; i < childCount; i++)
+		{
+			var child = model.getChildAt(swimlane, i);
+			this.resizeSwimlane(child, w, h, horizontal);
+		}
+	}
+	finally
+	{
+		model.endUpdate();
+	}
+};
+
+/**
+ * Function: destroy
+ * 
+ * Removes all handlers from the <graph> and deletes the reference to it.
+ */
+mxSwimlaneManager.prototype.destroy = function()
+{
+	this.setGraph(null);
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxTemporaryCellStates
+ *
+ * Extends <mxPoint> to implement a 2-dimensional rectangle with double
+ * precision coordinates.
+ * 
+ * Constructor: mxRectangle
+ *
+ * Constructs a new rectangle for the optional parameters. If no parameters
+ * are given then the respective default values are used.
+ */
+function mxTemporaryCellStates(view, scale, cells, isCellVisibleFn)
+{
+	scale = (scale != null) ? scale : 1;
+	this.view = view;
+	
+	// Stores the previous state
+	this.oldValidateCellState = view.validateCellState;
+	this.oldBounds = view.getGraphBounds();
+	this.oldStates = view.getStates();
+	this.oldScale = view.getScale();
+	
+	// Overrides validateCellState to ignore invisible cells
+	var self = this;
+	
+	view.validateCellState = function(cell, resurse)
+	{
+		if (cell == null || isCellVisibleFn == null || isCellVisibleFn(cell))
+		{
+			return self.oldValidateCellState.apply(view, arguments);
+		}
+		
+		return null;
+	};
+	
+	// Creates space for new states
+	view.setStates(new mxDictionary());
+	view.setScale(scale);
+	
+	if (cells != null)
+	{
+		view.resetValidationState();
+		var bbox = null;
+
+		// Validates the vertices and edges without adding them to
+		// the model so that the original cells are not modified
+		for (var i = 0; i < cells.length; i++)
+		{
+			var bounds = view.getBoundingBox(view.validateCellState(view.validateCell(cells[i])));
+			
+			if (bbox == null)
+			{
+				bbox = bounds;
+			}
+			else
+			{
+				bbox.add(bounds);
+			}
+		}
+
+		view.setGraphBounds(bbox || new mxRectangle());
+	}
+};
+
+/**
+ * Variable: view
+ *
+ * Holds the width of the rectangle. Default is 0.
+ */
+mxTemporaryCellStates.prototype.view = null;
+
+/**
+ * Variable: oldStates
+ *
+ * Holds the height of the rectangle. Default is 0.
+ */
+mxTemporaryCellStates.prototype.oldStates = null;
+
+/**
+ * Variable: oldBounds
+ *
+ * Holds the height of the rectangle. Default is 0.
+ */
+mxTemporaryCellStates.prototype.oldBounds = null;
+
+/**
+ * Variable: oldScale
+ *
+ * Holds the height of the rectangle. Default is 0.
+ */
+mxTemporaryCellStates.prototype.oldScale = null;
+
+/**
+ * Function: destroy
+ * 
+ * Returns the top, left corner as a new <mxPoint>.
+ */
+mxTemporaryCellStates.prototype.destroy = function()
+{
+	this.view.setScale(this.oldScale);
+	this.view.setStates(this.oldStates);
+	this.view.setGraphBounds(this.oldBounds);
+	this.view.validateCellState = this.oldValidateCellState;
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ *
+ * Class: mxCellStatePreview
+ * 
+ * Implements a live preview for moving cells.
+ * 
+ * Constructor: mxCellStatePreview
+ * 
+ * Constructs a move preview for the given graph.
+ * 
+ * Parameters:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ */
+function mxCellStatePreview(graph)
+{
+	this.deltas = new mxDictionary();
+	this.graph = graph;
+};
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxCellStatePreview.prototype.graph = null;
+
+/**
+ * Variable: deltas
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxCellStatePreview.prototype.deltas = null;
+
+/**
+ * Variable: count
+ * 
+ * Contains the number of entries in the map.
+ */
+mxCellStatePreview.prototype.count = 0;
+
+/**
+ * Function: isEmpty
+ * 
+ * Returns true if this contains no entries.
+ */
+mxCellStatePreview.prototype.isEmpty = function()
+{
+	return this.count == 0;
+};
+
+/**
+ * Function: moveState
+ */
+mxCellStatePreview.prototype.moveState = function(state, dx, dy, add, includeEdges)
+{
+	add = (add != null) ? add : true;
+	includeEdges = (includeEdges != null) ? includeEdges : true;
+	
+	var delta = this.deltas.get(state.cell);
+
+	if (delta == null)
+	{
+		// Note: Deltas stores the point and the state since the key is a string.
+		delta = {point: new mxPoint(dx, dy), state: state};
+		this.deltas.put(state.cell, delta);
+		this.count++;
+	}
+	else if (add)
+	{
+		delta.point.x += dx;
+		delta.point.y += dy;
+	}
+	else
+	{
+		delta.point.x = dx;
+		delta.point.y = dy;
+	}
+	
+	if (includeEdges)
+	{
+		this.addEdges(state);
+	}
+	
+	return delta.point;
+};
+
+/**
+ * Function: show
+ */
+mxCellStatePreview.prototype.show = function(visitor)
+{
+	this.deltas.visit(mxUtils.bind(this, function(key, delta)
+	{
+		this.translateState(delta.state, delta.point.x, delta.point.y);
+	}));
+	
+	this.deltas.visit(mxUtils.bind(this, function(key, delta)
+	{
+		this.revalidateState(delta.state, delta.point.x, delta.point.y, visitor);
+	}));
+};
+
+/**
+ * Function: translateState
+ */
+mxCellStatePreview.prototype.translateState = function(state, dx, dy)
+{
+	if (state != null)
+	{
+		var model = this.graph.getModel();
+		
+		if (model.isVertex(state.cell))
+		{
+			state.view.updateCellState(state);
+			var geo = model.getGeometry(state.cell);
+			
+			// Moves selection cells and non-relative vertices in
+			// the first phase so that edge terminal points will
+			// be updated in the second phase
+			if ((dx != 0 || dy != 0) && geo != null && (!geo.relative || this.deltas.get(state.cell) != null))
+			{
+				state.x += dx;
+				state.y += dy;
+			}
+		}
+	    
+	    var childCount = model.getChildCount(state.cell);
+	    
+	    for (var i = 0; i < childCount; i++)
+	    {
+	    	this.translateState(state.view.getState(model.getChildAt(state.cell, i)), dx, dy);
+	    }
+	}
+};
+
+/**
+ * Function: revalidateState
+ */
+mxCellStatePreview.prototype.revalidateState = function(state, dx, dy, visitor)
+{
+	if (state != null)
+	{
+		var model = this.graph.getModel();
+		
+		// Updates the edge terminal points and restores the
+		// (relative) positions of any (relative) children
+		if (model.isEdge(state.cell))
+		{
+			state.view.updateCellState(state);
+		}
+
+		var geo = this.graph.getCellGeometry(state.cell);
+		var pState = state.view.getState(model.getParent(state.cell));
+		
+		// Moves selection vertices which are relative
+		if ((dx != 0 || dy != 0) && geo != null && geo.relative &&
+			model.isVertex(state.cell) && (pState == null ||
+			model.isVertex(pState.cell) || this.deltas.get(state.cell) != null))
+		{
+			state.x += dx;
+			state.y += dy;
+		}
+		
+		this.graph.cellRenderer.redraw(state);
+	
+		// Invokes the visitor on the given state
+		if (visitor != null)
+		{
+			visitor(state);
+		}
+						
+	    var childCount = model.getChildCount(state.cell);
+	    
+	    for (var i = 0; i < childCount; i++)
+	    {
+	    	this.revalidateState(this.graph.view.getState(model.getChildAt(state.cell, i)), dx, dy, visitor);
+	    }
+	}
+};
+
+/**
+ * Function: addEdges
+ */
+mxCellStatePreview.prototype.addEdges = function(state)
+{
+	var model = this.graph.getModel();
+	var edgeCount = model.getEdgeCount(state.cell);
+
+	for (var i = 0; i < edgeCount; i++)
+	{
+		var s = state.view.getState(model.getEdgeAt(state.cell, i));
+
+		if (s != null)
+		{
+			this.moveState(s, 0, 0);
+		}
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxConnectionConstraint
+ * 
+ * Defines an object that contains the constraints about how to connect one
+ * side of an edge to its terminal.
+ * 
+ * Constructor: mxConnectionConstraint
+ * 
+ * Constructs a new connection constraint for the given point and boolean
+ * arguments.
+ * 
+ * Parameters:
+ * 
+ * point - Optional <mxPoint> that specifies the fixed location of the point
+ * in relative coordinates. Default is null.
+ * perimeter - Optional boolean that specifies if the fixed point should be
+ * projected onto the perimeter of the terminal. Default is true.
+ */
+function mxConnectionConstraint(point, perimeter, name)
+{
+	this.point = point;
+	this.perimeter = (perimeter != null) ? perimeter : true;
+	this.name = name;
+};
+
+/**
+ * Variable: point
+ * 
+ * <mxPoint> that specifies the fixed location of the connection point.
+ */
+mxConnectionConstraint.prototype.point = null;
+
+/**
+ * Variable: perimeter
+ * 
+ * Boolean that specifies if the point should be projected onto the perimeter
+ * of the terminal.
+ */
+mxConnectionConstraint.prototype.perimeter = null;
+
+/**
+ * Variable: name
+ * 
+ * Optional string that specifies the name of the constraint.
+ */
+mxConnectionConstraint.prototype.name = null;
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxGraphHandler
+ * 
+ * Graph event handler that handles selection. Individual cells are handled
+ * separately using <mxVertexHandler> or one of the edge handlers. These
+ * handlers are created using <mxGraph.createHandler> in
+ * <mxGraphSelectionModel.cellAdded>.
+ * 
+ * To avoid the container to scroll a moved cell into view, set
+ * <scrollAfterMove> to false.
+ * 
+ * Constructor: mxGraphHandler
+ * 
+ * Constructs an event handler that creates handles for the
+ * selection cells.
+ * 
+ * Parameters:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ */
+function mxGraphHandler(graph)
+{
+	this.graph = graph;
+	this.graph.addMouseListener(this);
+	
+	// Repaints the handler after autoscroll
+	this.panHandler = mxUtils.bind(this, function()
+	{
+		this.updatePreviewShape();
+		this.updateHint();
+	});
+	
+	this.graph.addListener(mxEvent.PAN, this.panHandler);
+	
+	// Handles escape keystrokes
+	this.escapeHandler = mxUtils.bind(this, function(sender, evt)
+	{
+		this.reset();
+	});
+	
+	this.graph.addListener(mxEvent.ESCAPE, this.escapeHandler);
+};
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxGraphHandler.prototype.graph = null;
+
+/**
+ * Variable: maxCells
+ * 
+ * Defines the maximum number of cells to paint subhandles
+ * for. Default is 50 for Firefox and 20 for IE. Set this
+ * to 0 if you want an unlimited number of handles to be
+ * displayed. This is only recommended if the number of
+ * cells in the graph is limited to a small number, eg.
+ * 500.
+ */
+mxGraphHandler.prototype.maxCells = (mxClient.IS_IE) ? 20 : 50;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies if events are handled. Default is true.
+ */
+mxGraphHandler.prototype.enabled = true;
+
+/**
+ * Variable: highlightEnabled
+ * 
+ * Specifies if drop targets under the mouse should be enabled. Default is
+ * true.
+ */
+mxGraphHandler.prototype.highlightEnabled = true;
+
+/**
+ * Variable: cloneEnabled
+ * 
+ * Specifies if cloning by control-drag is enabled. Default is true.
+ */
+mxGraphHandler.prototype.cloneEnabled = true;
+
+/**
+ * Variable: moveEnabled
+ * 
+ * Specifies if moving is enabled. Default is true.
+ */
+mxGraphHandler.prototype.moveEnabled = true;
+
+/**
+ * Variable: guidesEnabled
+ * 
+ * Specifies if other cells should be used for snapping the right, center or
+ * left side of the current selection. Default is false.
+ */
+mxGraphHandler.prototype.guidesEnabled = false;
+
+/**
+ * Variable: guide
+ * 
+ * Holds the <mxGuide> instance that is used for alignment.
+ */
+mxGraphHandler.prototype.guide = null;
+
+/**
+ * Variable: currentDx
+ * 
+ * Stores the x-coordinate of the current mouse move.
+ */
+mxGraphHandler.prototype.currentDx = null;
+
+/**
+ * Variable: currentDy
+ * 
+ * Stores the y-coordinate of the current mouse move.
+ */
+mxGraphHandler.prototype.currentDy = null;
+
+/**
+ * Variable: updateCursor
+ * 
+ * Specifies if a move cursor should be shown if the mouse is over a movable
+ * cell. Default is true.
+ */
+mxGraphHandler.prototype.updateCursor = true;
+
+/**
+ * Variable: selectEnabled
+ * 
+ * Specifies if selecting is enabled. Default is true.
+ */
+mxGraphHandler.prototype.selectEnabled = true;
+
+/**
+ * Variable: removeCellsFromParent
+ * 
+ * Specifies if cells may be moved out of their parents. Default is true.
+ */
+mxGraphHandler.prototype.removeCellsFromParent = true;
+
+/**
+ * Variable: connectOnDrop
+ * 
+ * Specifies if drop events are interpreted as new connections if no other
+ * drop action is defined. Default is false.
+ */
+mxGraphHandler.prototype.connectOnDrop = false;
+
+/**
+ * Variable: scrollOnMove
+ * 
+ * Specifies if the view should be scrolled so that a moved cell is
+ * visible. Default is true.
+ */
+mxGraphHandler.prototype.scrollOnMove = true;
+
+/**
+ * Variable: minimumSize
+ * 
+ * Specifies the minimum number of pixels for the width and height of a
+ * selection border. Default is 6.
+ */
+mxGraphHandler.prototype.minimumSize = 6;
+
+/**
+ * Variable: previewColor
+ * 
+ * Specifies the color of the preview shape. Default is black.
+ */
+mxGraphHandler.prototype.previewColor = 'black';
+
+/**
+ * Variable: htmlPreview
+ * 
+ * Specifies if the graph container should be used for preview. If this is used
+ * then drop target detection relies entirely on <mxGraph.getCellAt> because
+ * the HTML preview does not "let events through". Default is false.
+ */
+mxGraphHandler.prototype.htmlPreview = false;
+
+/**
+ * Variable: shape
+ * 
+ * Reference to the <mxShape> that represents the preview.
+ */
+mxGraphHandler.prototype.shape = null;
+
+/**
+ * Variable: scaleGrid
+ * 
+ * Specifies if the grid should be scaled. Default is false.
+ */
+mxGraphHandler.prototype.scaleGrid = false;
+
+/**
+ * Variable: rotationEnabled
+ * 
+ * Specifies if the bounding box should allow for rotation. Default is true.
+ */
+mxGraphHandler.prototype.rotationEnabled = true;
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns <enabled>.
+ */
+mxGraphHandler.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ * 
+ * Sets <enabled>.
+ */
+mxGraphHandler.prototype.setEnabled = function(value)
+{
+	this.enabled = value;
+};
+
+/**
+ * Function: isCloneEnabled
+ * 
+ * Returns <cloneEnabled>.
+ */
+mxGraphHandler.prototype.isCloneEnabled = function()
+{
+	return this.cloneEnabled;
+};
+
+/**
+ * Function: setCloneEnabled
+ * 
+ * Sets <cloneEnabled>.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean that specifies the new clone enabled state.
+ */
+mxGraphHandler.prototype.setCloneEnabled = function(value)
+{
+	this.cloneEnabled = value;
+};
+
+/**
+ * Function: isMoveEnabled
+ * 
+ * Returns <moveEnabled>.
+ */
+mxGraphHandler.prototype.isMoveEnabled = function()
+{
+	return this.moveEnabled;
+};
+
+/**
+ * Function: setMoveEnabled
+ * 
+ * Sets <moveEnabled>.
+ */
+mxGraphHandler.prototype.setMoveEnabled = function(value)
+{
+	this.moveEnabled = value;
+};
+
+/**
+ * Function: isSelectEnabled
+ * 
+ * Returns <selectEnabled>.
+ */
+mxGraphHandler.prototype.isSelectEnabled = function()
+{
+	return this.selectEnabled;
+};
+
+/**
+ * Function: setSelectEnabled
+ * 
+ * Sets <selectEnabled>.
+ */
+mxGraphHandler.prototype.setSelectEnabled = function(value)
+{
+	this.selectEnabled = value;
+};
+
+/**
+ * Function: isRemoveCellsFromParent
+ * 
+ * Returns <removeCellsFromParent>.
+ */
+mxGraphHandler.prototype.isRemoveCellsFromParent = function()
+{
+	return this.removeCellsFromParent;
+};
+
+/**
+ * Function: setRemoveCellsFromParent
+ * 
+ * Sets <removeCellsFromParent>.
+ */
+mxGraphHandler.prototype.setRemoveCellsFromParent = function(value)
+{
+	this.removeCellsFromParent = value;
+};
+
+/**
+ * Function: getInitialCellForEvent
+ * 
+ * Hook to return initial cell for the given event.
+ */
+mxGraphHandler.prototype.getInitialCellForEvent = function(me)
+{
+	return me.getCell();
+};
+
+/**
+ * Function: isDelayedSelection
+ * 
+ * Hook to return true for delayed selections.
+ */
+mxGraphHandler.prototype.isDelayedSelection = function(cell, me)
+{
+	return this.graph.isCellSelected(cell);
+};
+
+/**
+ * Function: consumeMouseEvent
+ * 
+ * Consumes the given mouse event. NOTE: This may be used to enable click
+ * events for links in labels on iOS as follows as consuming the initial
+ * touchStart disables firing the subsequent click evnent on the link.
+ * 
+ * <code>
+ * mxGraphHandler.prototype.consumeMouseEvent = function(evtName, me)
+ * {
+ *   var source = mxEvent.getSource(me.getEvent());
+ *   
+ *   if (!mxEvent.isTouchEvent(me.getEvent()) || source.nodeName != 'A')
+ *   {
+ *     me.consume();
+ *   }
+ * }
+ * </code>
+ */
+mxGraphHandler.prototype.consumeMouseEvent = function(evtName, me)
+{
+	me.consume();
+};
+
+/**
+ * Function: mouseDown
+ * 
+ * Handles the event by selecing the given cell and creating a handle for
+ * it. By consuming the event all subsequent events of the gesture are
+ * redirected to this handler.
+ */
+mxGraphHandler.prototype.mouseDown = function(sender, me)
+{
+	if (!me.isConsumed() && this.isEnabled() && this.graph.isEnabled() &&
+		me.getState() != null && !mxEvent.isMultiTouchEvent(me.getEvent()))
+	{
+		var cell = this.getInitialCellForEvent(me);
+		this.delayedSelection = this.isDelayedSelection(cell, me);
+		this.cell = null;
+		
+		if (this.isSelectEnabled() && !this.delayedSelection)
+		{
+			this.graph.selectCellForEvent(cell, me.getEvent());
+		}
+
+		if (this.isMoveEnabled())
+		{
+			var model = this.graph.model;
+			var geo = model.getGeometry(cell);
+
+			if (this.graph.isCellMovable(cell) && ((!model.isEdge(cell) || this.graph.getSelectionCount() > 1 ||
+				(geo.points != null && geo.points.length > 0) || model.getTerminal(cell, true) == null ||
+				model.getTerminal(cell, false) == null) || this.graph.allowDanglingEdges || 
+				(this.graph.isCloneEvent(me.getEvent()) && this.graph.isCellsCloneable())))
+			{
+				this.start(cell, me.getX(), me.getY());
+			}
+			else if (this.delayedSelection)
+			{
+				this.cell = cell;
+			}
+
+			this.cellWasClicked = true;
+			this.consumeMouseEvent(mxEvent.MOUSE_DOWN, me);
+		}
+	}
+};
+
+/**
+ * Function: getGuideStates
+ * 
+ * Creates an array of cell states which should be used as guides.
+ */
+mxGraphHandler.prototype.getGuideStates = function()
+{
+	var parent = this.graph.getDefaultParent();
+	var model = this.graph.getModel();
+	
+	var filter = mxUtils.bind(this, function(cell)
+	{
+		return this.graph.view.getState(cell) != null &&
+			model.isVertex(cell) &&
+			model.getGeometry(cell) != null &&
+			!model.getGeometry(cell).relative;
+	});
+	
+	return this.graph.view.getCellStates(model.filterDescendants(filter, parent));
+};
+
+/**
+ * Function: getCells
+ * 
+ * Returns the cells to be modified by this handler. This implementation
+ * returns all selection cells that are movable, or the given initial cell if
+ * the given cell is not selected and movable. This handles the case of moving
+ * unselectable or unselected cells.
+ * 
+ * Parameters:
+ * 
+ * initialCell - <mxCell> that triggered this handler.
+ */
+mxGraphHandler.prototype.getCells = function(initialCell)
+{
+	if (!this.delayedSelection && this.graph.isCellMovable(initialCell))
+	{
+		return [initialCell];
+	}
+	else
+	{
+		return this.graph.getMovableCells(this.graph.getSelectionCells());
+	}
+};
+
+/**
+ * Function: getPreviewBounds
+ * 
+ * Returns the <mxRectangle> used as the preview bounds for
+ * moving the given cells.
+ */
+mxGraphHandler.prototype.getPreviewBounds = function(cells)
+{
+	var bounds = this.getBoundingBox(cells);
+	
+	if (bounds != null)
+	{
+		// Corrects width and height
+		bounds.width = Math.max(0, bounds.width - 1);
+		bounds.height = Math.max(0, bounds.height - 1);
+		
+		if (bounds.width < this.minimumSize)
+		{
+			var dx = this.minimumSize - bounds.width;
+			bounds.x -= dx / 2;
+			bounds.width = this.minimumSize;
+		}
+		else
+		{
+			bounds.x = Math.round(bounds.x);
+			bounds.width = Math.ceil(bounds.width);
+		}
+		
+		var tr = this.graph.view.translate;
+		var s = this.graph.view.scale;
+		
+		if (bounds.height < this.minimumSize)
+		{
+			var dy = this.minimumSize - bounds.height;
+			bounds.y -= dy / 2;
+			bounds.height = this.minimumSize;
+		}
+		else
+		{
+			bounds.y = Math.round(bounds.y);
+			bounds.height = Math.ceil(bounds.height);
+		}
+	}
+	
+	return bounds;
+};
+
+/**
+ * Function: getBoundingBox
+ * 
+ * Returns the union of the <mxCellStates> for the given array of <mxCells>.
+ * For vertices, this method uses the bounding box of the corresponding shape
+ * if one exists. The bounding box of the corresponding text label and all
+ * controls and overlays are ignored. See also: <mxGraphView.getBounds> and
+ * <mxGraph.getBoundingBox>.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> whose bounding box should be returned.
+ */
+mxGraphHandler.prototype.getBoundingBox = function(cells)
+{
+	var result = null;
+	
+	if (cells != null && cells.length > 0)
+	{
+		var model = this.graph.getModel();
+		
+		for (var i = 0; i < cells.length; i++)
+		{
+			if (model.isVertex(cells[i]) || model.isEdge(cells[i]))
+			{
+				var state = this.graph.view.getState(cells[i]);
+			
+				if (state != null)
+				{
+					var bbox = state;
+					
+					if (model.isVertex(cells[i]) && state.shape != null && state.shape.boundingBox != null)
+					{
+						bbox = state.shape.boundingBox;
+					}
+					
+					if (result == null)
+					{
+						result = mxRectangle.fromRectangle(bbox);
+					}
+					else
+					{
+						result.add(bbox);
+					}
+				}
+			}
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: createPreviewShape
+ * 
+ * Creates the shape used to draw the preview for the given bounds.
+ */
+mxGraphHandler.prototype.createPreviewShape = function(bounds)
+{
+	var shape = new mxRectangleShape(bounds, null, this.previewColor);
+	shape.isDashed = true;
+	
+	if (this.htmlPreview)
+	{
+		shape.dialect = mxConstants.DIALECT_STRICTHTML;
+		shape.init(this.graph.container);
+	}
+	else
+	{
+		// Makes sure to use either VML or SVG shapes in order to implement
+		// event-transparency on the background area of the rectangle since
+		// HTML shapes do not let mouseevents through even when transparent
+		shape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
+			mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+		shape.init(this.graph.getView().getOverlayPane());
+		shape.pointerEvents = false;
+		
+		// Workaround for artifacts on iOS
+		if (mxClient.IS_IOS)
+		{
+			shape.getSvgScreenOffset = function()
+			{
+				return 0;
+			};
+		}
+	}
+	
+	return shape;
+};
+
+/**
+ * Function: start
+ * 
+ * Starts the handling of the mouse gesture.
+ */
+mxGraphHandler.prototype.start = function(cell, x, y)
+{
+	this.cell = cell;
+	this.first = mxUtils.convertPoint(this.graph.container, x, y);
+	this.cells = this.getCells(this.cell);
+	this.bounds = this.graph.getView().getBounds(this.cells);
+	this.pBounds = this.getPreviewBounds(this.cells);
+
+	if (this.guidesEnabled)
+	{
+		this.guide = new mxGuide(this.graph, this.getGuideStates());
+	}
+};
+
+/**
+ * Function: useGuidesForEvent
+ * 
+ * Returns true if the guides should be used for the given <mxMouseEvent>.
+ * This implementation returns <mxGuide.isEnabledForEvent>.
+ */
+mxGraphHandler.prototype.useGuidesForEvent = function(me)
+{
+	return (this.guide != null) ? this.guide.isEnabledForEvent(me.getEvent()) : true;
+};
+
+
+/**
+ * Function: snap
+ * 
+ * Snaps the given vector to the grid and returns the given mxPoint instance.
+ */
+mxGraphHandler.prototype.snap = function(vector)
+{
+	var scale = (this.scaleGrid) ? this.graph.view.scale : 1;
+	
+	vector.x = this.graph.snap(vector.x / scale) * scale;
+	vector.y = this.graph.snap(vector.y / scale) * scale;
+	
+	return vector;
+};
+
+/**
+ * Function: getDelta
+ * 
+ * Returns an <mxPoint> that represents the vector for moving the cells
+ * for the given <mxMouseEvent>.
+ */
+mxGraphHandler.prototype.getDelta = function(me)
+{
+	var point = mxUtils.convertPoint(this.graph.container, me.getX(), me.getY());
+	var s = this.graph.view.scale;
+	
+	return new mxPoint(this.roundLength((point.x - this.first.x) / s) * s,
+		this.roundLength((point.y - this.first.y) / s) * s);
+};
+
+/**
+ * Function: updateHint
+ * 
+ * Hook for subclassers do show details while the handler is active.
+ */
+mxGraphHandler.prototype.updateHint = function(me) { };
+
+/**
+ * Function: removeHint
+ * 
+ * Hooks for subclassers to hide details when the handler gets inactive.
+ */
+mxGraphHandler.prototype.removeHint = function() { };
+
+/**
+ * Function: roundLength
+ * 
+ * Hook for rounding the unscaled vector. This uses Math.round.
+ */
+mxGraphHandler.prototype.roundLength = function(length)
+{
+	return Math.round(length);
+};
+
+/**
+ * Function: mouseMove
+ * 
+ * Handles the event by highlighting possible drop targets and updating the
+ * preview.
+ */
+mxGraphHandler.prototype.mouseMove = function(sender, me)
+{
+	var graph = this.graph;
+
+	if (!me.isConsumed() && graph.isMouseDown && this.cell != null &&
+		this.first != null && this.bounds != null)
+	{
+		// Stops moving if a multi touch event is received
+		if (mxEvent.isMultiTouchEvent(me.getEvent()))
+		{
+			this.reset();
+			return;
+		}
+		
+		var delta = this.getDelta(me);
+		var dx = delta.x;
+		var dy = delta.y;
+		var tol = graph.tolerance;
+
+		if (this.shape != null || Math.abs(dx) > tol || Math.abs(dy) > tol)
+		{
+			// Highlight is used for highlighting drop targets
+			if (this.highlight == null)
+			{
+				this.highlight = new mxCellHighlight(this.graph,
+					mxConstants.DROP_TARGET_COLOR, 3);
+			}
+			
+			if (this.shape == null)
+			{
+				this.shape = this.createPreviewShape(this.bounds);
+			}
+			
+			var gridEnabled = graph.isGridEnabledEvent(me.getEvent());
+			var hideGuide = true;
+			
+			if (this.guide != null && this.useGuidesForEvent(me))
+			{
+				delta = this.guide.move(this.bounds, new mxPoint(dx, dy), gridEnabled);
+				hideGuide = false;
+				dx = delta.x;
+				dy = delta.y;
+			}
+			else if (gridEnabled)
+			{
+				var trx = graph.getView().translate;
+				var scale = graph.getView().scale;				
+				
+				var tx = this.bounds.x - (graph.snap(this.bounds.x / scale - trx.x) + trx.x) * scale;
+				var ty = this.bounds.y - (graph.snap(this.bounds.y / scale - trx.y) + trx.y) * scale;
+				var v = this.snap(new mxPoint(dx, dy));
+			
+				dx = v.x - tx;
+				dy = v.y - ty;
+			}
+			
+			if (this.guide != null && hideGuide)
+			{
+				this.guide.hide();
+			}
+
+			// Constrained movement if shift key is pressed
+			if (graph.isConstrainedEvent(me.getEvent()))
+			{
+				if (Math.abs(dx) > Math.abs(dy))
+				{
+					dy = 0;
+				}
+				else
+				{
+					dx = 0;
+				}
+			}
+
+			this.currentDx = dx;
+			this.currentDy = dy;
+			this.updatePreviewShape();
+
+			var target = null;
+			var cell = me.getCell();
+
+			var clone = graph.isCloneEvent(me.getEvent()) && graph.isCellsCloneable() && this.isCloneEnabled();
+			
+			if (graph.isDropEnabled() && this.highlightEnabled)
+			{
+				// Contains a call to getCellAt to find the cell under the mouse
+				target = graph.getDropTarget(this.cells, me.getEvent(), cell, clone);
+			}
+
+			var state = graph.getView().getState(target);
+			var highlight = false;
+			
+			if (state != null && (graph.model.getParent(this.cell) != target || clone))
+			{
+			    if (this.target != target)
+			    {
+				    this.target = target;
+				    this.setHighlightColor(mxConstants.DROP_TARGET_COLOR);
+				}
+			    
+			    highlight = true;
+			}
+			else
+			{
+				this.target = null;
+
+				if (this.connectOnDrop && cell != null && this.cells.length == 1 &&
+					graph.getModel().isVertex(cell) && graph.isCellConnectable(cell))
+				{
+					state = graph.getView().getState(cell);
+					
+					if (state != null)
+					{
+						var error = graph.getEdgeValidationError(null, this.cell, cell);
+						var color = (error == null) ?
+							mxConstants.VALID_COLOR :
+							mxConstants.INVALID_CONNECT_TARGET_COLOR;
+						this.setHighlightColor(color);
+						highlight = true;
+					}
+				}
+			}
+			
+			if (state != null && highlight)
+			{
+				this.highlight.highlight(state);
+			}
+			else
+			{
+				this.highlight.hide();
+			}
+		}
+
+		this.updateHint(me);
+		this.consumeMouseEvent(mxEvent.MOUSE_MOVE, me);
+		
+		// Cancels the bubbling of events to the container so
+		// that the droptarget is not reset due to an mouseMove
+		// fired on the container with no associated state.
+		mxEvent.consume(me.getEvent());
+	}
+	else if ((this.isMoveEnabled() || this.isCloneEnabled()) && this.updateCursor &&
+		!me.isConsumed() && me.getState() != null && !graph.isMouseDown)
+	{
+		var cursor = graph.getCursorForMouseEvent(me);
+		
+		if (cursor == null && graph.isEnabled() && graph.isCellMovable(me.getCell()))
+		{
+			if (graph.getModel().isEdge(me.getCell()))
+			{
+				cursor = mxConstants.CURSOR_MOVABLE_EDGE;
+			}
+			else
+			{
+				cursor = mxConstants.CURSOR_MOVABLE_VERTEX;
+			}
+		}
+
+		// Sets the cursor on the original source state under the mouse
+		// instead of the event source state which can be the parent
+		if (me.sourceState != null)
+		{
+			me.sourceState.setCursor(cursor);
+		}
+	}
+};
+
+/**
+ * Function: updatePreviewShape
+ * 
+ * Updates the bounds of the preview shape.
+ */
+mxGraphHandler.prototype.updatePreviewShape = function()
+{
+	if (this.shape != null)
+	{
+		this.shape.bounds = new mxRectangle(Math.round(this.pBounds.x + this.currentDx - this.graph.panDx),
+				Math.round(this.pBounds.y + this.currentDy - this.graph.panDy), this.pBounds.width, this.pBounds.height);
+		this.shape.redraw();
+	}
+};
+
+/**
+ * Function: setHighlightColor
+ * 
+ * Sets the color of the rectangle used to highlight drop targets.
+ * 
+ * Parameters:
+ * 
+ * color - String that represents the new highlight color.
+ */
+mxGraphHandler.prototype.setHighlightColor = function(color)
+{
+	if (this.highlight != null)
+	{
+		this.highlight.setHighlightColor(color);
+	}
+};
+
+/**
+ * Function: mouseUp
+ * 
+ * Handles the event by applying the changes to the selection cells.
+ */
+mxGraphHandler.prototype.mouseUp = function(sender, me)
+{
+	if (!me.isConsumed())
+	{
+		var graph = this.graph;
+		
+		if (this.cell != null && this.first != null && this.shape != null &&
+			this.currentDx != null && this.currentDy != null)
+		{
+			var cell = me.getCell();
+			
+			if (this.connectOnDrop && this.target == null && cell != null && graph.getModel().isVertex(cell) &&
+				graph.isCellConnectable(cell) && graph.isEdgeValid(null, this.cell, cell))
+			{
+				graph.connectionHandler.connect(this.cell, cell, me.getEvent());
+			}
+			else
+			{
+				var clone = graph.isCloneEvent(me.getEvent()) && graph.isCellsCloneable() && this.isCloneEnabled();
+				var scale = graph.getView().scale;
+				var dx = this.roundLength(this.currentDx / scale);
+				var dy = this.roundLength(this.currentDy / scale);
+				var target = this.target;
+				
+				if (graph.isSplitEnabled() && graph.isSplitTarget(target, this.cells, me.getEvent()))
+				{
+					graph.splitEdge(target, this.cells, null, dx, dy);
+				}
+				else
+				{
+					this.moveCells(this.cells, dx, dy, clone, this.target, me.getEvent());
+				}
+			}
+		}
+		else if (this.isSelectEnabled() && this.delayedSelection && this.cell != null)
+		{
+			this.selectDelayed(me);
+		}
+	}
+
+	// Consumes the event if a cell was initially clicked
+	if (this.cellWasClicked)
+	{
+		this.consumeMouseEvent(mxEvent.MOUSE_UP, me);
+	}
+
+	this.reset();
+};
+
+/**
+ * Function: selectDelayed
+ * 
+ * Implements the delayed selection for the given mouse event.
+ */
+mxGraphHandler.prototype.selectDelayed = function(me)
+{
+	if (!this.graph.isCellSelected(this.cell) || !this.graph.popupMenuHandler.isPopupTrigger(me))
+	{
+		this.graph.selectCellForEvent(this.cell, me.getEvent());
+	}
+};
+
+/**
+ * Function: reset
+ * 
+ * Resets the state of this handler.
+ */
+mxGraphHandler.prototype.reset = function()
+{
+	this.destroyShapes();
+	this.removeHint();
+	
+	this.cellWasClicked = false;
+	this.delayedSelection = false;
+	this.currentDx = null;
+	this.currentDy = null;
+	this.guides = null;
+	this.first = null;
+	this.cell = null;
+	this.target = null;
+};
+
+/**
+ * Function: shouldRemoveCellsFromParent
+ * 
+ * Returns true if the given cells should be removed from the parent for the specified
+ * mousereleased event.
+ */
+mxGraphHandler.prototype.shouldRemoveCellsFromParent = function(parent, cells, evt)
+{
+	if (this.graph.getModel().isVertex(parent))
+	{
+		var pState = this.graph.getView().getState(parent);
+		
+		if (pState != null)
+		{
+			var pt = mxUtils.convertPoint(this.graph.container,
+				mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+			var alpha = mxUtils.toRadians(mxUtils.getValue(pState.style, mxConstants.STYLE_ROTATION) || 0);
+			
+			if (alpha != 0)
+			{
+				var cos = Math.cos(-alpha);
+				var sin = Math.sin(-alpha);
+				var cx = new mxPoint(pState.getCenterX(), pState.getCenterY());
+				pt = mxUtils.getRotatedPoint(pt, cos, sin, cx);
+			}
+		
+			return !mxUtils.contains(pState, pt.x, pt.y);
+		}
+	}
+	
+	return false;
+};
+
+/**
+ * Function: moveCells
+ * 
+ * Moves the given cells by the specified amount.
+ */
+mxGraphHandler.prototype.moveCells = function(cells, dx, dy, clone, target, evt)
+{
+	if (clone)
+	{
+		cells = this.graph.getCloneableCells(cells);
+	}
+	
+	// Removes cells from parent
+	if (target == null && this.isRemoveCellsFromParent() &&
+		this.shouldRemoveCellsFromParent(this.graph.getModel().getParent(this.cell), cells, evt))
+	{
+		target = this.graph.getDefaultParent();
+	}
+	
+	// Passes all selected cells in order to correctly clone or move into
+	// the target cell. The method checks for each cell if its movable.
+	cells = this.graph.moveCells(cells, dx - this.graph.panDx / this.graph.view.scale,
+			dy - this.graph.panDy / this.graph.view.scale, clone, target, evt);
+	
+	if (this.isSelectEnabled() && this.scrollOnMove)
+	{
+		this.graph.scrollCellToVisible(cells[0]);
+	}
+			
+	// Selects the new cells if cells have been cloned
+	if (clone)
+	{
+		this.graph.setSelectionCells(cells);
+	}
+};
+
+/**
+ * Function: destroyShapes
+ * 
+ * Destroy the preview and highlight shapes.
+ */
+mxGraphHandler.prototype.destroyShapes = function()
+{
+	// Destroys the preview dashed rectangle
+	if (this.shape != null)
+	{
+		this.shape.destroy();
+		this.shape = null;
+	}
+	
+	if (this.guide != null)
+	{
+		this.guide.destroy();
+		this.guide = null;
+	}
+	
+	// Destroys the drop target highlight
+	if (this.highlight != null)
+	{
+		this.highlight.destroy();
+		this.highlight = null;
+	}
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the handler and all its resources and DOM nodes.
+ */
+mxGraphHandler.prototype.destroy = function()
+{
+	this.graph.removeMouseListener(this);
+	this.graph.removeListener(this.panHandler);
+	
+	if (this.escapeHandler != null)
+	{
+		this.graph.removeListener(this.escapeHandler);
+		this.escapeHandler = null;
+	}
+	
+	this.destroyShapes();
+	this.removeHint();
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxPanningHandler
+ * 
+ * Event handler that pans and creates popupmenus. To use the left
+ * mousebutton for panning without interfering with cell moving and
+ * resizing, use <isUseLeftButton> and <isIgnoreCell>. For grid size
+ * steps while panning, use <useGrid>. This handler is built-into
+ * <mxGraph.panningHandler> and enabled using <mxGraph.setPanning>.
+ * 
+ * Constructor: mxPanningHandler
+ * 
+ * Constructs an event handler that creates a <mxPopupMenu>
+ * and pans the graph.
+ *
+ * Event: mxEvent.PAN_START
+ *
+ * Fires when the panning handler changes its <active> state to true. The
+ * <code>event</code> property contains the corresponding <mxMouseEvent>.
+ *
+ * Event: mxEvent.PAN
+ *
+ * Fires while handle is processing events. The <code>event</code> property contains
+ * the corresponding <mxMouseEvent>.
+ *
+ * Event: mxEvent.PAN_END
+ *
+ * Fires when the panning handler changes its <active> state to false. The
+ * <code>event</code> property contains the corresponding <mxMouseEvent>.
+ */
+function mxPanningHandler(graph)
+{
+	if (graph != null)
+	{
+		this.graph = graph;
+		this.graph.addMouseListener(this);
+
+		// Handles force panning event
+		this.forcePanningHandler = mxUtils.bind(this, function(sender, evt)
+		{
+			var evtName = evt.getProperty('eventName');
+			var me = evt.getProperty('event');
+			
+			if (evtName == mxEvent.MOUSE_DOWN && this.isForcePanningEvent(me))
+			{
+				this.start(me);
+				this.active = true;
+				this.fireEvent(new mxEventObject(mxEvent.PAN_START, 'event', me));
+				me.consume();
+			}
+		});
+
+		this.graph.addListener(mxEvent.FIRE_MOUSE_EVENT, this.forcePanningHandler);
+		
+		// Handles pinch gestures
+		this.gestureHandler = mxUtils.bind(this, function(sender, eo)
+		{
+			if (this.isPinchEnabled())
+			{
+				var evt = eo.getProperty('event');
+				
+				if (!mxEvent.isConsumed(evt) && evt.type == 'gesturestart')
+				{
+					this.initialScale = this.graph.view.scale;
+				
+					// Forces start of panning when pinch gesture starts
+					if (!this.active && this.mouseDownEvent != null)
+					{
+						this.start(this.mouseDownEvent);
+						this.mouseDownEvent = null;
+					}
+				}
+				else if (evt.type == 'gestureend' && this.initialScale != null)
+				{
+					this.initialScale = null;
+				}
+				
+				if (this.initialScale != null)
+				{
+					var value = Math.round(this.initialScale * evt.scale * 100) / 100;
+					
+					if (this.minScale != null)
+					{
+						value = Math.max(this.minScale, value);
+					}
+					
+					if (this.maxScale != null)
+					{
+						value = Math.min(this.maxScale, value);
+					}
+	
+					if (this.graph.view.scale != value)
+					{
+						this.graph.zoomTo(value);
+						mxEvent.consume(evt);
+					}
+				}
+			}
+		});
+		
+		this.graph.addListener(mxEvent.GESTURE, this.gestureHandler);
+	}
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxPanningHandler.prototype = new mxEventSource();
+mxPanningHandler.prototype.constructor = mxPanningHandler;
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxPanningHandler.prototype.graph = null;
+
+/**
+ * Variable: useLeftButtonForPanning
+ * 
+ * Specifies if panning should be active for the left mouse button.
+ * Setting this to true may conflict with <mxRubberband>. Default is false.
+ */
+mxPanningHandler.prototype.useLeftButtonForPanning = false;
+
+/**
+ * Variable: usePopupTrigger
+ * 
+ * Specifies if <mxEvent.isPopupTrigger> should also be used for panning.
+ */
+mxPanningHandler.prototype.usePopupTrigger = true;
+
+/**
+ * Variable: ignoreCell
+ * 
+ * Specifies if panning should be active even if there is a cell under the
+ * mousepointer. Default is false.
+ */
+mxPanningHandler.prototype.ignoreCell = false;
+
+/**
+ * Variable: previewEnabled
+ * 
+ * Specifies if the panning should be previewed. Default is true.
+ */
+mxPanningHandler.prototype.previewEnabled = true;
+
+/**
+ * Variable: useGrid
+ * 
+ * Specifies if the panning steps should be aligned to the grid size.
+ * Default is false.
+ */
+mxPanningHandler.prototype.useGrid = false;
+
+/**
+ * Variable: panningEnabled
+ * 
+ * Specifies if panning should be enabled. Default is true.
+ */
+mxPanningHandler.prototype.panningEnabled = true;
+
+/**
+ * Variable: pinchEnabled
+ * 
+ * Specifies if pinch gestures should be handled as zoom. Default is true.
+ */
+mxPanningHandler.prototype.pinchEnabled = true;
+
+/**
+ * Variable: maxScale
+ * 
+ * Specifies the maximum scale. Default is 8.
+ */
+mxPanningHandler.prototype.maxScale = 8;
+
+/**
+ * Variable: minScale
+ * 
+ * Specifies the minimum scale. Default is 0.01.
+ */
+mxPanningHandler.prototype.minScale = 0.01;
+
+/**
+ * Variable: dx
+ * 
+ * Holds the current horizontal offset.
+ */
+mxPanningHandler.prototype.dx = null;
+
+/**
+ * Variable: dy
+ * 
+ * Holds the current vertical offset.
+ */
+mxPanningHandler.prototype.dy = null;
+
+/**
+ * Variable: startX
+ * 
+ * Holds the x-coordinate of the start point.
+ */
+mxPanningHandler.prototype.startX = 0;
+
+/**
+ * Variable: startY
+ * 
+ * Holds the y-coordinate of the start point.
+ */
+mxPanningHandler.prototype.startY = 0;
+
+/**
+ * Function: isActive
+ * 
+ * Returns true if the handler is currently active.
+ */
+mxPanningHandler.prototype.isActive = function()
+{
+	return this.active || this.initialScale != null;
+};
+
+/**
+ * Function: isPanningEnabled
+ * 
+ * Returns <panningEnabled>.
+ */
+mxPanningHandler.prototype.isPanningEnabled = function()
+{
+	return this.panningEnabled;
+};
+
+/**
+ * Function: setPanningEnabled
+ * 
+ * Sets <panningEnabled>.
+ */
+mxPanningHandler.prototype.setPanningEnabled = function(value)
+{
+	this.panningEnabled = value;
+};
+
+/**
+ * Function: isPinchEnabled
+ * 
+ * Returns <pinchEnabled>.
+ */
+mxPanningHandler.prototype.isPinchEnabled = function()
+{
+	return this.pinchEnabled;
+};
+
+/**
+ * Function: setPinchEnabled
+ * 
+ * Sets <pinchEnabled>.
+ */
+mxPanningHandler.prototype.setPinchEnabled = function(value)
+{
+	this.pinchEnabled = value;
+};
+
+/**
+ * Function: isPanningTrigger
+ * 
+ * Returns true if the given event is a panning trigger for the optional
+ * given cell. This returns true if control-shift is pressed or if
+ * <usePopupTrigger> is true and the event is a popup trigger.
+ */
+mxPanningHandler.prototype.isPanningTrigger = function(me)
+{
+	var evt = me.getEvent();
+	
+	return (this.useLeftButtonForPanning && me.getState() == null &&
+			mxEvent.isLeftMouseButton(evt)) || (mxEvent.isControlDown(evt) &&
+			mxEvent.isShiftDown(evt)) || (this.usePopupTrigger && mxEvent.isPopupTrigger(evt));
+};
+
+/**
+ * Function: isForcePanningEvent
+ * 
+ * Returns true if the given <mxMouseEvent> should start panning. This
+ * implementation always returns true if <ignoreCell> is true or for
+ * multi touch events.
+ */
+mxPanningHandler.prototype.isForcePanningEvent = function(me)
+{
+	return this.ignoreCell || mxEvent.isMultiTouchEvent(me.getEvent());
+};
+
+/**
+ * Function: mouseDown
+ * 
+ * Handles the event by initiating the panning. By consuming the event all
+ * subsequent events of the gesture are redirected to this handler.
+ */
+mxPanningHandler.prototype.mouseDown = function(sender, me)
+{
+	this.mouseDownEvent = me;
+	
+	if (!me.isConsumed() && this.isPanningEnabled() && !this.active && this.isPanningTrigger(me))
+	{
+		this.start(me);
+		this.consumePanningTrigger(me);
+	}
+};
+
+/**
+ * Function: start
+ * 
+ * Starts panning at the given event.
+ */
+mxPanningHandler.prototype.start = function(me)
+{
+	this.dx0 = -this.graph.container.scrollLeft;
+	this.dy0 = -this.graph.container.scrollTop;
+
+	// Stores the location of the trigger event
+	this.startX = me.getX();
+	this.startY = me.getY();
+	this.dx = null;
+	this.dy = null;
+	
+	this.panningTrigger = true;
+};
+
+/**
+ * Function: consumePanningTrigger
+ * 
+ * Consumes the given <mxMouseEvent> if it was a panning trigger in
+ * <mouseDown>. The default is to invoke <mxMouseEvent.consume>. Note that this
+ * will block any further event processing. If you haven't disabled built-in
+ * context menus and require immediate selection of the cell on mouseDown in
+ * Safari and/or on the Mac, then use the following code:
+ * 
+ * (code)
+ * mxPanningHandler.prototype.consumePanningTrigger = function(me)
+ * {
+ *   if (me.evt.preventDefault)
+ *   {
+ *     me.evt.preventDefault();
+ *   }
+ *   
+ *   // Stops event processing in IE
+ *   me.evt.returnValue = false;
+ *   
+ *   // Sets local consumed state
+ *   if (!mxClient.IS_SF && !mxClient.IS_MAC)
+ *   {
+ *     me.consumed = true;
+ *   }
+ * };
+ * (end)
+ */
+mxPanningHandler.prototype.consumePanningTrigger = function(me)
+{
+	me.consume();
+};
+
+/**
+ * Function: mouseMove
+ * 
+ * Handles the event by updating the panning on the graph.
+ */
+mxPanningHandler.prototype.mouseMove = function(sender, me)
+{
+	this.dx = me.getX() - this.startX;
+	this.dy = me.getY() - this.startY;
+	
+	if (this.active)
+	{
+		if (this.previewEnabled)
+		{
+			// Applies the grid to the panning steps
+			if (this.useGrid)
+			{
+				this.dx = this.graph.snap(this.dx);
+				this.dy = this.graph.snap(this.dy);
+			}
+			
+			this.graph.panGraph(this.dx + this.dx0, this.dy + this.dy0);
+		}
+
+		this.fireEvent(new mxEventObject(mxEvent.PAN, 'event', me));
+	}
+	else if (this.panningTrigger)
+	{
+		var tmp = this.active;
+
+		// Panning is activated only if the mouse is moved
+		// beyond the graph tolerance
+		this.active = Math.abs(this.dx) > this.graph.tolerance || Math.abs(this.dy) > this.graph.tolerance;
+
+		if (!tmp && this.active)
+		{
+			this.fireEvent(new mxEventObject(mxEvent.PAN_START, 'event', me));
+		}
+	}
+	
+	if (this.active || this.panningTrigger)
+	{
+		me.consume();
+	}
+};
+
+/**
+ * Function: mouseUp
+ * 
+ * Handles the event by setting the translation on the view or showing the
+ * popupmenu.
+ */
+mxPanningHandler.prototype.mouseUp = function(sender, me)
+{
+	if (this.active)
+	{
+		if (this.dx != null && this.dy != null)
+		{
+			// Ignores if scrollbars have been used for panning
+			if (!this.graph.useScrollbarsForPanning || !mxUtils.hasScrollbars(this.graph.container))
+			{
+				var scale = this.graph.getView().scale;
+				var t = this.graph.getView().translate;
+				this.graph.panGraph(0, 0);
+				this.panGraph(t.x + this.dx / scale, t.y + this.dy / scale);
+			}
+			
+			me.consume();
+		}
+		
+		this.fireEvent(new mxEventObject(mxEvent.PAN_END, 'event', me));
+	}
+	
+	this.panningTrigger = false;
+	this.mouseDownEvent = null;
+	this.active = false;
+	this.dx = null;
+	this.dy = null;
+};
+
+/**
+ * Function: panGraph
+ * 
+ * Pans <graph> by the given amount.
+ */
+mxPanningHandler.prototype.panGraph = function(dx, dy)
+{
+	this.graph.getView().setTranslate(dx, dy);
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the handler and all its resources and DOM nodes.
+ */
+mxPanningHandler.prototype.destroy = function()
+{
+	this.graph.removeMouseListener(this);
+	this.graph.removeListener(this.forcePanningHandler);
+	this.graph.removeListener(this.gestureHandler);
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxPopupMenuHandler
+ * 
+ * Event handler that creates popupmenus.
+ * 
+ * Constructor: mxPopupMenuHandler
+ * 
+ * Constructs an event handler that creates a <mxPopupMenu>.
+ */
+function mxPopupMenuHandler(graph, factoryMethod)
+{
+	if (graph != null)
+	{
+		this.graph = graph;
+		this.factoryMethod = factoryMethod;
+		this.graph.addMouseListener(this);
+		
+		// Does not show menu if any touch gestures take place after the trigger
+		this.gestureHandler = mxUtils.bind(this, function(sender, eo)
+		{
+			this.inTolerance = false;
+		});
+		
+		this.graph.addListener(mxEvent.GESTURE, this.gestureHandler);
+		
+		this.init();
+	}
+};
+
+/**
+ * Extends mxPopupMenu.
+ */
+mxPopupMenuHandler.prototype = new mxPopupMenu();
+mxPopupMenuHandler.prototype.constructor = mxPopupMenuHandler;
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxPopupMenuHandler.prototype.graph = null;
+
+/**
+ * Variable: selectOnPopup
+ * 
+ * Specifies if cells should be selected if a popupmenu is displayed for
+ * them. Default is true.
+ */
+mxPopupMenuHandler.prototype.selectOnPopup = true;
+
+/**
+ * Variable: clearSelectionOnBackground
+ * 
+ * Specifies if cells should be deselected if a popupmenu is displayed for
+ * the diagram background. Default is true.
+ */
+mxPopupMenuHandler.prototype.clearSelectionOnBackground = true;
+
+/**
+ * Variable: triggerX
+ * 
+ * X-coordinate of the mouse down event.
+ */
+mxPopupMenuHandler.prototype.triggerX = null;
+
+/**
+ * Variable: triggerY
+ * 
+ * Y-coordinate of the mouse down event.
+ */
+mxPopupMenuHandler.prototype.triggerY = null;
+
+/**
+ * Variable: screenX
+ * 
+ * Screen X-coordinate of the mouse down event.
+ */
+mxPopupMenuHandler.prototype.screenX = null;
+
+/**
+ * Variable: screenY
+ * 
+ * Screen Y-coordinate of the mouse down event.
+ */
+mxPopupMenuHandler.prototype.screenY = null;
+
+/**
+ * Function: init
+ * 
+ * Initializes the shapes required for this vertex handler.
+ */
+mxPopupMenuHandler.prototype.init = function()
+{
+	// Supercall
+	mxPopupMenu.prototype.init.apply(this);
+
+	// Hides the tooltip if the mouse is over
+	// the context menu
+	mxEvent.addGestureListeners(this.div, mxUtils.bind(this, function(evt)
+	{
+		this.graph.tooltipHandler.hide();
+	}));
+};
+
+/**
+ * Function: isSelectOnPopup
+ * 
+ * Hook for returning if a cell should be selected for a given <mxMouseEvent>.
+ * This implementation returns <selectOnPopup>.
+ */
+mxPopupMenuHandler.prototype.isSelectOnPopup = function(me)
+{
+	return this.selectOnPopup;
+};
+
+/**
+ * Function: mouseDown
+ * 
+ * Handles the event by initiating the panning. By consuming the event all
+ * subsequent events of the gesture are redirected to this handler.
+ */
+mxPopupMenuHandler.prototype.mouseDown = function(sender, me)
+{
+	if (this.isEnabled() && !mxEvent.isMultiTouchEvent(me.getEvent()))
+	{
+		// Hides the popupmenu if is is being displayed
+		this.hideMenu();
+		this.triggerX = me.getGraphX();
+		this.triggerY = me.getGraphY();
+		this.screenX = mxEvent.getMainEvent(me.getEvent()).screenX;
+		this.screenY = mxEvent.getMainEvent(me.getEvent()).screenY;
+		this.popupTrigger = this.isPopupTrigger(me);
+		this.inTolerance = true;
+	}
+};
+
+/**
+ * Function: mouseMove
+ * 
+ * Handles the event by updating the panning on the graph.
+ */
+mxPopupMenuHandler.prototype.mouseMove = function(sender, me)
+{
+	// Popup trigger may change on mouseUp so ignore it
+	if (this.inTolerance && this.screenX != null && this.screenY != null)
+	{
+		if (Math.abs(mxEvent.getMainEvent(me.getEvent()).screenX - this.screenX) > this.graph.tolerance ||
+			Math.abs(mxEvent.getMainEvent(me.getEvent()).screenY - this.screenY) > this.graph.tolerance)
+		{
+			this.inTolerance = false;
+		}
+	}
+};
+
+/**
+ * Function: mouseUp
+ * 
+ * Handles the event by setting the translation on the view or showing the
+ * popupmenu.
+ */
+mxPopupMenuHandler.prototype.mouseUp = function(sender, me)
+{
+	if (this.popupTrigger && this.inTolerance && this.triggerX != null && this.triggerY != null)
+	{
+		var cell = this.getCellForPopupEvent(me);
+
+		// Selects the cell for which the context menu is being displayed
+		if (this.graph.isEnabled() && this.isSelectOnPopup(me) &&
+			cell != null && !this.graph.isCellSelected(cell))
+		{
+			this.graph.setSelectionCell(cell);
+		}
+		else if (this.clearSelectionOnBackground && cell == null)
+		{
+			this.graph.clearSelection();
+		}
+		
+		// Hides the tooltip if there is one
+		this.graph.tooltipHandler.hide();
+
+		// Menu is shifted by 1 pixel so that the mouse up event
+		// is routed via the underlying shape instead of the DIV
+		var origin = mxUtils.getScrollOrigin();
+		this.popup(me.getX() + origin.x + 1, me.getY() + origin.y + 1, cell, me.getEvent());
+		me.consume();
+	}
+	
+	this.popupTrigger = false;
+	this.inTolerance = false;
+};
+
+/**
+ * Function: getCellForPopupEvent
+ * 
+ * Hook to return the cell for the mouse up popup trigger handling.
+ */
+mxPopupMenuHandler.prototype.getCellForPopupEvent = function(me)
+{
+	return me.getCell();
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the handler and all its resources and DOM nodes.
+ */
+mxPopupMenuHandler.prototype.destroy = function()
+{
+	this.graph.removeMouseListener(this);
+	this.graph.removeListener(this.gestureHandler);
+	
+	// Supercall
+	mxPopupMenu.prototype.destroy.apply(this);
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxCellMarker
+ * 
+ * A helper class to process mouse locations and highlight cells.
+ * 
+ * Helper class to highlight cells. To add a cell marker to an existing graph
+ * for highlighting all cells, the following code is used:
+ * 
+ * (code)
+ * var marker = new mxCellMarker(graph);
+ * graph.addMouseListener({
+ *   mouseDown: function() {},
+ *   mouseMove: function(sender, me)
+ *   {
+ *     marker.process(me);
+ *   },
+ *   mouseUp: function() {}
+ * });
+ * (end)
+ *
+ * Event: mxEvent.MARK
+ * 
+ * Fires after a cell has been marked or unmarked. The <code>state</code>
+ * property contains the marked <mxCellState> or null if no state is marked.
+ * 
+ * Constructor: mxCellMarker
+ * 
+ * Constructs a new cell marker.
+ * 
+ * Parameters:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ * validColor - Optional marker color for valid states. Default is
+ * <mxConstants.DEFAULT_VALID_COLOR>.
+ * invalidColor - Optional marker color for invalid states. Default is
+ * <mxConstants.DEFAULT_INVALID_COLOR>.
+ * hotspot - Portion of the width and hight where a state intersects a
+ * given coordinate pair. A value of 0 means always highlight. Default is
+ * <mxConstants.DEFAULT_HOTSPOT>.
+ */
+function mxCellMarker(graph, validColor, invalidColor, hotspot)
+{
+	mxEventSource.call(this);
+	
+	if (graph != null)
+	{
+		this.graph = graph;
+		this.validColor = (validColor != null) ? validColor : mxConstants.DEFAULT_VALID_COLOR;
+		this.invalidColor = (validColor != null) ? invalidColor : mxConstants.DEFAULT_INVALID_COLOR;
+		this.hotspot = (hotspot != null) ? hotspot : mxConstants.DEFAULT_HOTSPOT;
+		
+		this.highlight = new mxCellHighlight(graph);
+	}
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxUtils.extend(mxCellMarker, mxEventSource);
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxCellMarker.prototype.graph = null;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies if the marker is enabled. Default is true.
+ */
+mxCellMarker.prototype.enabled = true;
+
+/**
+ * Variable: hotspot
+ * 
+ * Specifies the portion of the width and height that should trigger
+ * a highlight. The area around the center of the cell to be marked is used
+ * as the hotspot. Possible values are between 0 and 1. Default is
+ * mxConstants.DEFAULT_HOTSPOT.
+ */
+mxCellMarker.prototype.hotspot = mxConstants.DEFAULT_HOTSPOT; 
+
+/**
+ * Variable: hotspotEnabled
+ * 
+ * Specifies if the hotspot is enabled. Default is false.
+ */
+mxCellMarker.prototype.hotspotEnabled = false;
+
+/**
+ * Variable: validColor
+ * 
+ * Holds the valid marker color.
+ */
+mxCellMarker.prototype.validColor = null;
+
+/**
+ * Variable: invalidColor
+ * 
+ * Holds the invalid marker color.
+ */
+mxCellMarker.prototype.invalidColor = null;
+
+/**
+ * Variable: currentColor
+ * 
+ * Holds the current marker color.
+ */
+mxCellMarker.prototype.currentColor = null;
+
+/**
+ * Variable: validState
+ * 
+ * Holds the marked <mxCellState> if it is valid.
+ */
+mxCellMarker.prototype.validState = null; 
+
+/**
+ * Variable: markedState
+ * 
+ * Holds the marked <mxCellState>.
+ */
+mxCellMarker.prototype.markedState = null;
+
+/**
+ * Function: setEnabled
+ * 
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ * 
+ * Parameters:
+ * 
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxCellMarker.prototype.setEnabled = function(enabled)
+{
+	this.enabled = enabled;
+};
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxCellMarker.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+
+/**
+ * Function: setHotspot
+ * 
+ * Sets the <hotspot>.
+ */
+mxCellMarker.prototype.setHotspot = function(hotspot)
+{
+	this.hotspot = hotspot;
+};
+
+/**
+ * Function: getHotspot
+ * 
+ * Returns the <hotspot>.
+ */
+mxCellMarker.prototype.getHotspot = function()
+{
+	return this.hotspot;
+};
+
+/**
+ * Function: setHotspotEnabled
+ * 
+ * Specifies whether the hotspot should be used in <intersects>.
+ */
+mxCellMarker.prototype.setHotspotEnabled = function(enabled)
+{
+	this.hotspotEnabled = enabled;
+};
+
+/**
+ * Function: isHotspotEnabled
+ * 
+ * Returns true if hotspot is used in <intersects>.
+ */
+mxCellMarker.prototype.isHotspotEnabled = function()
+{
+	return this.hotspotEnabled;
+};
+
+/**
+ * Function: hasValidState
+ * 
+ * Returns true if <validState> is not null.
+ */
+mxCellMarker.prototype.hasValidState = function()
+{
+	return this.validState != null;
+};
+
+/**
+ * Function: getValidState
+ * 
+ * Returns the <validState>.
+ */
+mxCellMarker.prototype.getValidState = function()
+{
+	return this.validState;
+};
+
+/**
+ * Function: getMarkedState
+ * 
+ * Returns the <markedState>.
+ */
+mxCellMarker.prototype.getMarkedState = function()
+{
+	return this.markedState;
+};
+
+/**
+ * Function: reset
+ * 
+ * Resets the state of the cell marker.
+ */
+mxCellMarker.prototype.reset = function()
+{
+	this.validState = null;
+	
+	if (this.markedState != null)
+	{
+		this.markedState = null;
+		this.unmark();
+	}
+};
+
+/**
+ * Function: process
+ * 
+ * Processes the given event and cell and marks the state returned by
+ * <getState> with the color returned by <getMarkerColor>. If the
+ * markerColor is not null, then the state is stored in <markedState>. If
+ * <isValidState> returns true, then the state is stored in <validState>
+ * regardless of the marker color. The state is returned regardless of the
+ * marker color and valid state. 
+ */
+mxCellMarker.prototype.process = function(me)
+{
+	var state = null;
+	
+	if (this.isEnabled())
+	{
+		state = this.getState(me);
+		this.setCurrentState(state, me);
+	}
+	
+	return state;
+};
+
+/**
+ * Function: setCurrentState
+ * 
+ * Sets and marks the current valid state.
+ */
+mxCellMarker.prototype.setCurrentState = function(state, me, color)
+{
+	var isValid = (state != null) ? this.isValidState(state) : false;
+	color = (color != null) ? color : this.getMarkerColor(me.getEvent(), state, isValid);
+	
+	if (isValid)
+	{
+		this.validState = state;
+	}
+	else
+	{
+		this.validState = null;
+	}
+	
+	if (state != this.markedState || color != this.currentColor)
+	{
+		this.currentColor = color;
+		
+		if (state != null && this.currentColor != null)
+		{
+			this.markedState = state;
+			this.mark();		
+		}
+		else if (this.markedState != null)
+		{
+			this.markedState = null;
+			this.unmark();
+		}
+	}
+};
+
+/**
+ * Function: markCell
+ * 
+ * Marks the given cell using the given color, or <validColor> if no color is specified.
+ */
+mxCellMarker.prototype.markCell = function(cell, color)
+{
+	var state = this.graph.getView().getState(cell);
+	
+	if (state != null)
+	{
+		this.currentColor = (color != null) ? color : this.validColor;
+		this.markedState = state;
+		this.mark();
+	}
+};
+
+/**
+ * Function: mark
+ * 
+ * Marks the <markedState> and fires a <mark> event.
+ */
+mxCellMarker.prototype.mark = function()
+{
+	this.highlight.setHighlightColor(this.currentColor);
+	this.highlight.highlight(this.markedState);
+	this.fireEvent(new mxEventObject(mxEvent.MARK, 'state', this.markedState));
+};
+
+/**
+ * Function: unmark
+ * 
+ * Hides the marker and fires a <mark> event.
+ */
+mxCellMarker.prototype.unmark = function()
+{
+	this.mark();
+};
+
+/**
+ * Function: isValidState
+ * 
+ * Returns true if the given <mxCellState> is a valid state. If this
+ * returns true, then the state is stored in <validState>. The return value
+ * of this method is used as the argument for <getMarkerColor>.
+ */
+mxCellMarker.prototype.isValidState = function(state)
+{
+	return true;
+};
+
+/**
+ * Function: getMarkerColor
+ * 
+ * Returns the valid- or invalidColor depending on the value of isValid.
+ * The given <mxCellState> is ignored by this implementation.
+ */
+mxCellMarker.prototype.getMarkerColor = function(evt, state, isValid)
+{
+	return (isValid) ? this.validColor : this.invalidColor;
+};
+
+/**
+ * Function: getState
+ * 
+ * Uses <getCell>, <getStateToMark> and <intersects> to return the
+ * <mxCellState> for the given <mxMouseEvent>.
+ */
+mxCellMarker.prototype.getState = function(me)
+{
+	var view = this.graph.getView();
+	var cell = this.getCell(me);
+	var state = this.getStateToMark(view.getState(cell));
+
+	return (state != null && this.intersects(state, me)) ? state : null;
+};
+
+/**
+ * Function: getCell
+ * 
+ * Returns the <mxCell> for the given event and cell. This returns the
+ * given cell.
+ */
+mxCellMarker.prototype.getCell = function(me)
+{
+	return me.getCell();
+};
+
+/**
+ * Function: getStateToMark
+ * 
+ * Returns the <mxCellState> to be marked for the given <mxCellState> under
+ * the mouse. This returns the given state.
+ */
+mxCellMarker.prototype.getStateToMark = function(state)
+{
+	return state;
+};
+
+/**
+ * Function: intersects
+ * 
+ * Returns true if the given coordinate pair intersects the given state.
+ * This returns true if the <hotspot> is 0 or the coordinates are inside
+ * the hotspot for the given cell state.
+ */
+mxCellMarker.prototype.intersects = function(state, me)
+{
+	if (this.hotspotEnabled)
+	{
+		return mxUtils.intersectsHotspot(state, me.getGraphX(), me.getGraphY(),
+			this.hotspot, mxConstants.MIN_HOTSPOT_SIZE,
+			mxConstants.MAX_HOTSPOT_SIZE);
+	}
+	
+	return true;
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the handler and all its resources and DOM nodes.
+ */
+mxCellMarker.prototype.destroy = function()
+{
+	this.graph.getView().removeListener(this.resetHandler);
+	this.graph.getModel().removeListener(this.resetHandler);
+	this.highlight.destroy();
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxSelectionCellsHandler
+ * 
+ * An event handler that manages cell handlers and invokes their mouse event
+ * processing functions.
+ * 
+ * Group: Events
+ * 
+ * Event: mxEvent.ADD
+ * 
+ * Fires if a cell has been added to the selection. The <code>state</code>
+ * property contains the <mxCellState> that has been added.
+ * 
+ * Event: mxEvent.REMOVE
+ * 
+ * Fires if a cell has been remove from the selection. The <code>state</code>
+ * property contains the <mxCellState> that has been removed.
+ * 
+ * Parameters:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ */
+function mxSelectionCellsHandler(graph)
+{
+	mxEventSource.call(this);
+	
+	this.graph = graph;
+	this.handlers = new mxDictionary();
+	this.graph.addMouseListener(this);
+	
+	this.refreshHandler = mxUtils.bind(this, function(sender, evt)
+	{
+		if (this.isEnabled())
+		{
+			this.refresh();
+		}
+	});
+	
+	this.graph.getSelectionModel().addListener(mxEvent.CHANGE, this.refreshHandler);
+	this.graph.getModel().addListener(mxEvent.CHANGE, this.refreshHandler);
+	this.graph.getView().addListener(mxEvent.SCALE, this.refreshHandler);
+	this.graph.getView().addListener(mxEvent.TRANSLATE, this.refreshHandler);
+	this.graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE, this.refreshHandler);
+	this.graph.getView().addListener(mxEvent.DOWN, this.refreshHandler);
+	this.graph.getView().addListener(mxEvent.UP, this.refreshHandler);
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxUtils.extend(mxSelectionCellsHandler, mxEventSource);
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxSelectionCellsHandler.prototype.graph = null;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies if events are handled. Default is true.
+ */
+mxSelectionCellsHandler.prototype.enabled = true;
+
+/**
+ * Variable: refreshHandler
+ * 
+ * Keeps a reference to an event listener for later removal.
+ */
+mxSelectionCellsHandler.prototype.refreshHandler = null;
+
+/**
+ * Variable: maxHandlers
+ * 
+ * Defines the maximum number of handlers to paint individually. Default is 100.
+ */
+mxSelectionCellsHandler.prototype.maxHandlers = 100;
+
+/**
+ * Variable: handlers
+ * 
+ * <mxDictionary> that maps from cells to handlers.
+ */
+mxSelectionCellsHandler.prototype.handlers = null;
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns <enabled>.
+ */
+mxSelectionCellsHandler.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ * 
+ * Sets <enabled>.
+ */
+mxSelectionCellsHandler.prototype.setEnabled = function(value)
+{
+	this.enabled = value;
+};
+
+/**
+ * Function: getHandler
+ * 
+ * Returns the handler for the given cell.
+ */
+mxSelectionCellsHandler.prototype.getHandler = function(cell)
+{
+	return this.handlers.get(cell);
+};
+
+/**
+ * Function: reset
+ * 
+ * Resets all handlers.
+ */
+mxSelectionCellsHandler.prototype.reset = function()
+{
+	this.handlers.visit(function(key, handler)
+	{
+		handler.reset.apply(handler);
+	});
+};
+
+/**
+ * Function: refresh
+ * 
+ * Reloads or updates all handlers.
+ */
+mxSelectionCellsHandler.prototype.refresh = function()
+{
+	// Removes all existing handlers
+	var oldHandlers = this.handlers;
+	this.handlers = new mxDictionary();
+	
+	// Creates handles for all selection cells
+	var tmp = this.graph.getSelectionCells();
+
+	for (var i = 0; i < tmp.length; i++)
+	{
+		var state = this.graph.view.getState(tmp[i]);
+
+		if (state != null)
+		{
+			var handler = oldHandlers.remove(tmp[i]);
+
+			if (handler != null)
+			{
+				if (handler.state != state)
+				{
+					handler.destroy();
+					handler = null;
+				}
+				else
+				{
+					if (handler.refresh != null)
+					{
+						handler.refresh();
+					}
+					
+					handler.redraw();
+				}
+			}
+			
+			if (handler == null)
+			{
+				handler = this.graph.createHandler(state);
+				this.fireEvent(new mxEventObject(mxEvent.ADD, 'state', state));
+			}
+			
+			if (handler != null)
+			{
+				this.handlers.put(tmp[i], handler);
+			}
+		}
+	}
+	
+	// Destroys all unused handlers
+	oldHandlers.visit(mxUtils.bind(this, function(key, handler)
+	{
+		this.fireEvent(new mxEventObject(mxEvent.REMOVE, 'state', handler.state));
+		handler.destroy();
+	}));
+};
+
+/**
+ * Function: updateHandler
+ * 
+ * Updates the handler for the given shape if one exists.
+ */
+mxSelectionCellsHandler.prototype.updateHandler = function(state)
+{
+	var handler = this.handlers.remove(state.cell);
+	
+	if (handler != null)
+	{
+		handler.destroy();
+		handler = this.graph.createHandler(state);
+		
+		if (handler != null)
+		{
+			this.handlers.put(state.cell, handler);
+		}
+	}
+};
+
+/**
+ * Function: mouseDown
+ * 
+ * Redirects the given event to the handlers.
+ */
+mxSelectionCellsHandler.prototype.mouseDown = function(sender, me)
+{
+	if (this.graph.isEnabled() && this.isEnabled())
+	{
+		var args = [sender, me];
+
+		this.handlers.visit(function(key, handler)
+		{
+			handler.mouseDown.apply(handler, args);
+		});
+	}
+};
+
+/**
+ * Function: mouseMove
+ * 
+ * Redirects the given event to the handlers.
+ */
+mxSelectionCellsHandler.prototype.mouseMove = function(sender, me)
+{
+	if (this.graph.isEnabled() && this.isEnabled())
+	{
+		var args = [sender, me];
+
+		this.handlers.visit(function(key, handler)
+		{
+			handler.mouseMove.apply(handler, args);
+		});
+	}
+};
+
+/**
+ * Function: mouseUp
+ * 
+ * Redirects the given event to the handlers.
+ */
+mxSelectionCellsHandler.prototype.mouseUp = function(sender, me)
+{
+	if (this.graph.isEnabled() && this.isEnabled())
+	{
+		var args = [sender, me];
+
+		this.handlers.visit(function(key, handler)
+		{
+			handler.mouseUp.apply(handler, args);
+		});
+	}
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the handler and all its resources and DOM nodes.
+ */
+mxSelectionCellsHandler.prototype.destroy = function()
+{
+	this.graph.removeMouseListener(this);
+	
+	if (this.refreshHandler != null)
+	{
+		this.graph.getSelectionModel().removeListener(this.refreshHandler);
+		this.graph.getModel().removeListener(this.refreshHandler);
+		this.graph.getView().removeListener(this.refreshHandler);
+		this.refreshHandler = null;
+	}
+};
+/**
+ * Copyright (c) 2006-2016, JGraph Ltd
+ * Copyright (c) 2006-2016, Gaudenz Alder
+ */
+/**
+ * Class: mxConnectionHandler
+ *
+ * Graph event handler that creates new connections. Uses <mxTerminalMarker>
+ * for finding and highlighting the source and target vertices and
+ * <factoryMethod> to create the edge instance. This handler is built-into
+ * <mxGraph.connectionHandler> and enabled using <mxGraph.setConnectable>.
+ *
+ * Example:
+ * 
+ * (code)
+ * new mxConnectionHandler(graph, function(source, target, style)
+ * {
+ *   edge = new mxCell('', new mxGeometry());
+ *   edge.setEdge(true);
+ *   edge.setStyle(style);
+ *   edge.geometry.relative = true;
+ *   return edge;
+ * });
+ * (end)
+ * 
+ * Here is an alternative solution that just sets a specific user object for
+ * new edges by overriding <insertEdge>.
+ *
+ * (code)
+ * mxConnectionHandlerInsertEdge = mxConnectionHandler.prototype.insertEdge;
+ * mxConnectionHandler.prototype.insertEdge = function(parent, id, value, source, target, style)
+ * {
+ *   value = 'Test';
+ * 
+ *   return mxConnectionHandlerInsertEdge.apply(this, arguments);
+ * };
+ * (end)
+ * 
+ * Using images to trigger connections:
+ * 
+ * This handler uses mxTerminalMarker to find the source and target cell for
+ * the new connection and creates a new edge using <connect>. The new edge is
+ * created using <createEdge> which in turn uses <factoryMethod> or creates a
+ * new default edge.
+ * 
+ * The handler uses a "highlight-paradigm" for indicating if a cell is being
+ * used as a source or target terminal, as seen in other diagramming products.
+ * In order to allow both, moving and connecting cells at the same time,
+ * <mxConstants.DEFAULT_HOTSPOT> is used in the handler to determine the hotspot
+ * of a cell, that is, the region of the cell which is used to trigger a new
+ * connection. The constant is a value between 0 and 1 that specifies the
+ * amount of the width and height around the center to be used for the hotspot
+ * of a cell and its default value is 0.5. In addition,
+ * <mxConstants.MIN_HOTSPOT_SIZE> defines the minimum number of pixels for the
+ * width and height of the hotspot.
+ * 
+ * This solution, while standards compliant, may be somewhat confusing because
+ * there is no visual indicator for the hotspot and the highlight is seen to
+ * switch on and off while the mouse is being moved in and out. Furthermore,
+ * this paradigm does not allow to create different connections depending on
+ * the highlighted hotspot as there is only one hotspot per cell and it
+ * normally does not allow cells to be moved and connected at the same time as
+ * there is no clear indication of the connectable area of the cell.
+ * 
+ * To come across these issues, the handle has an additional <createIcons> hook
+ * with a default implementation that allows to create one icon to be used to
+ * trigger new connections. If this icon is specified, then new connections can
+ * only be created if the image is clicked while the cell is being highlighted.
+ * The <createIcons> hook may be overridden to create more than one
+ * <mxImageShape> for creating new connections, but the default implementation
+ * supports one image and is used as follows:
+ * 
+ * In order to display the "connect image" whenever the mouse is over the cell,
+ * an DEFAULT_HOTSPOT of 1 should be used:
+ * 
+ * (code)
+ * mxConstants.DEFAULT_HOTSPOT = 1;
+ * (end)
+ * 
+ * In order to avoid confusion with the highlighting, the highlight color
+ * should not be used with a connect image:
+ * 
+ * (code)
+ * mxConstants.HIGHLIGHT_COLOR = null;
+ * (end)
+ * 
+ * To install the image, the connectImage field of the mxConnectionHandler must
+ * be assigned a new <mxImage> instance:
+ * 
+ * (code)
+ * mxConnectionHandler.prototype.connectImage = new mxImage('images/green-dot.gif', 14, 14);
+ * (end)
+ * 
+ * This will use the green-dot.gif with a width and height of 14 pixels as the
+ * image to trigger new connections. In createIcons the icon field of the
+ * handler will be set in order to remember the icon that has been clicked for
+ * creating the new connection. This field will be available under selectedIcon
+ * in the connect method, which may be overridden to take the icon that
+ * triggered the new connection into account. This is useful if more than one
+ * icon may be used to create a connection.
+ *
+ * Group: Events
+ * 
+ * Event: mxEvent.START
+ * 
+ * Fires when a new connection is being created by the user. The <code>state</code>
+ * property contains the state of the source cell.
+ * 
+ * Event: mxEvent.CONNECT
+ * 
+ * Fires between begin- and endUpdate in <connect>. The <code>cell</code>
+ * property contains the inserted edge, the <code>event</code> and <code>target</code> 
+ * properties contain the respective arguments that were passed to <connect> (where
+ * target corresponds to the dropTarget argument). Finally, the <code>terminal</code>
+ * property corresponds to the target argument in <connect> or the clone of the source
+ * terminal if <createTarget> is enabled.
+ * 
+ * Note that the target is the cell under the mouse where the mouse button was released.
+ * Depending on the logic in the handler, this doesn't necessarily have to be the target
+ * of the inserted edge. To print the source, target or any optional ports IDs that the
+ * edge is connected to, the following code can be used. To get more details about the
+ * actual connection point, <mxGraph.getConnectionConstraint> can be used. To resolve
+ * the port IDs, use <mxGraphModel.getCell>.
+ * 
+ * (code)
+ * graph.connectionHandler.addListener(mxEvent.CONNECT, function(sender, evt)
+ * {
+ *   var edge = evt.getProperty('cell');
+ *   var source = graph.getModel().getTerminal(edge, true);
+ *   var target = graph.getModel().getTerminal(edge, false);
+ *   
+ *   var style = graph.getCellStyle(edge);
+ *   var sourcePortId = style[mxConstants.STYLE_SOURCE_PORT];
+ *   var targetPortId = style[mxConstants.STYLE_TARGET_PORT];
+ *   
+ *   mxLog.show();
+ *   mxLog.debug('connect', edge, source.id, target.id, sourcePortId, targetPortId);
+ * });
+ * (end)
+ *
+ * Event: mxEvent.RESET
+ * 
+ * Fires when the <reset> method is invoked.
+ *
+ * Constructor: mxConnectionHandler
+ *
+ * Constructs an event handler that connects vertices using the specified
+ * factory method to create the new edges. Modify
+ * <mxConstants.ACTIVE_REGION> to setup the region on a cell which triggers
+ * the creation of a new connection or use connect icons as explained
+ * above.
+ * 
+ * Parameters:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ * factoryMethod - Optional function to create the edge. The function takes
+ * the source and target <mxCell> as the first and second argument and an
+ * optional cell style from the preview as the third argument. It returns
+ * the <mxCell> that represents the new edge.
+ */
+function mxConnectionHandler(graph, factoryMethod)
+{
+	mxEventSource.call(this);
+	
+	if (graph != null)
+	{
+		this.graph = graph;
+		this.factoryMethod = factoryMethod;
+		this.init();
+		
+		// Handles escape keystrokes
+		this.escapeHandler = mxUtils.bind(this, function(sender, evt)
+		{
+			this.reset();
+		});
+		
+		this.graph.addListener(mxEvent.ESCAPE, this.escapeHandler);
+	}
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxUtils.extend(mxConnectionHandler, mxEventSource);
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxConnectionHandler.prototype.graph = null;
+
+/**
+ * Variable: factoryMethod
+ * 
+ * Function that is used for creating new edges. The function takes the
+ * source and target <mxCell> as the first and second argument and returns
+ * a new <mxCell> that represents the edge. This is used in <createEdge>.
+ */
+mxConnectionHandler.prototype.factoryMethod = true;
+
+/**
+ * Variable: moveIconFront
+ * 
+ * Specifies if icons should be displayed inside the graph container instead
+ * of the overlay pane. This is used for HTML labels on vertices which hide
+ * the connect icon. This has precendence over <moveIconBack> when set
+ * to true. Default is false.
+ */
+mxConnectionHandler.prototype.moveIconFront = false;
+
+/**
+ * Variable: moveIconBack
+ * 
+ * Specifies if icons should be moved to the back of the overlay pane. This can
+ * be set to true if the icons of the connection handler conflict with other
+ * handles, such as the vertex label move handle. Default is false.
+ */
+mxConnectionHandler.prototype.moveIconBack = false;
+
+/**
+ * Variable: connectImage
+ * 
+ * <mxImage> that is used to trigger the creation of a new connection. This
+ * is used in <createIcons>. Default is null.
+ */
+mxConnectionHandler.prototype.connectImage = null;
+
+/**
+ * Variable: targetConnectImage
+ * 
+ * Specifies if the connect icon should be centered on the target state
+ * while connections are being previewed. Default is false.
+ */
+mxConnectionHandler.prototype.targetConnectImage = false;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies if events are handled. Default is true.
+ */
+mxConnectionHandler.prototype.enabled = true;
+
+/**
+ * Variable: select
+ * 
+ * Specifies if new edges should be selected. Default is true.
+ */
+mxConnectionHandler.prototype.select = true;
+
+/**
+ * Variable: createTarget
+ * 
+ * Specifies if <createTargetVertex> should be called if no target was under the
+ * mouse for the new connection. Setting this to true means the connection
+ * will be drawn as valid if no target is under the mouse, and
+ * <createTargetVertex> will be called before the connection is created between
+ * the source cell and the newly created vertex in <createTargetVertex>, which
+ * can be overridden to create a new target. Default is false.
+ */
+mxConnectionHandler.prototype.createTarget = false;
+
+/**
+ * Variable: marker
+ * 
+ * Holds the <mxTerminalMarker> used for finding source and target cells.
+ */
+mxConnectionHandler.prototype.marker = null;
+
+/**
+ * Variable: constraintHandler
+ * 
+ * Holds the <mxConstraintHandler> used for drawing and highlighting
+ * constraints.
+ */
+mxConnectionHandler.prototype.constraintHandler = null;
+
+/**
+ * Variable: error
+ * 
+ * Holds the current validation error while connections are being created.
+ */
+mxConnectionHandler.prototype.error = null;
+
+/**
+ * Variable: waypointsEnabled
+ * 
+ * Specifies if single clicks should add waypoints on the new edge. Default is
+ * false.
+ */
+mxConnectionHandler.prototype.waypointsEnabled = false;
+
+/**
+ * Variable: ignoreMouseDown
+ * 
+ * Specifies if the connection handler should ignore the state of the mouse
+ * button when highlighting the source. Default is false, that is, the
+ * handler only highlights the source if no button is being pressed.
+ */
+mxConnectionHandler.prototype.ignoreMouseDown = false;
+
+/**
+ * Variable: first
+ * 
+ * Holds the <mxPoint> where the mouseDown took place while the handler is
+ * active.
+ */
+mxConnectionHandler.prototype.first = null;
+
+/**
+ * Variable: connectIconOffset
+ * 
+ * Holds the offset for connect icons during connection preview.
+ * Default is mxPoint(0, <mxConstants.TOOLTIP_VERTICAL_OFFSET>).
+ * Note that placing the icon under the mouse pointer with an
+ * offset of (0,0) will affect hit detection.
+ */
+mxConnectionHandler.prototype.connectIconOffset = new mxPoint(0, mxConstants.TOOLTIP_VERTICAL_OFFSET);
+
+/**
+ * Variable: edgeState
+ * 
+ * Optional <mxCellState> that represents the preview edge while the
+ * handler is active. This is created in <createEdgeState>.
+ */
+mxConnectionHandler.prototype.edgeState = null;
+
+/**
+ * Variable: changeHandler
+ * 
+ * Holds the change event listener for later removal.
+ */
+mxConnectionHandler.prototype.changeHandler = null;
+
+/**
+ * Variable: drillHandler
+ * 
+ * Holds the drill event listener for later removal.
+ */
+mxConnectionHandler.prototype.drillHandler = null;
+
+/**
+ * Variable: mouseDownCounter
+ * 
+ * Counts the number of mouseDown events since the start. The initial mouse
+ * down event counts as 1.
+ */
+mxConnectionHandler.prototype.mouseDownCounter = 0;
+
+/**
+ * Variable: movePreviewAway
+ * 
+ * Switch to enable moving the preview away from the mousepointer. This is required in browsers
+ * where the preview cannot be made transparent to events and if the built-in hit detection on
+ * the HTML elements in the page should be used. Default is the value of <mxClient.IS_VML>.
+ */
+mxConnectionHandler.prototype.movePreviewAway = mxClient.IS_VML;
+
+/**
+ * Variable: outlineConnect
+ * 
+ * Specifies if connections to the outline of a highlighted target should be
+ * enabled. This will allow to place the connection point along the outline of
+ * the highlighted target. Default is false.
+ */
+mxConnectionHandler.prototype.outlineConnect = false;
+
+/**
+ * Variable: livePreview
+ * 
+ * Specifies if the actual shape of the edge state should be used for the preview.
+ * Default is false. (Ignored if no edge state is created in <createEdgeState>.)
+ */
+mxConnectionHandler.prototype.livePreview = false;
+
+/**
+ * Variable: cursor
+ * 
+ * Specifies the cursor to be used while the handler is active. Default is null.
+ */
+mxConnectionHandler.prototype.cursor = null;
+
+/**
+ * Variable: insertBeforeSource
+ * 
+ * Specifies if new edges should be inserted before the source vertex in the
+ * cell hierarchy. Default is false for backwards compatibility.
+ */
+mxConnectionHandler.prototype.insertBeforeSource = false;
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxConnectionHandler.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+	
+/**
+ * Function: setEnabled
+ * 
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ * 
+ * Parameters:
+ * 
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxConnectionHandler.prototype.setEnabled = function(enabled)
+{
+	this.enabled = enabled;
+};
+
+/**
+ * Function: isInsertBefore
+ * 
+ * Returns <insertBeforeSource> for non-loops and false for loops.
+ *
+ * Parameters:
+ * 
+ * edge - <mxCell> that represents the edge to be inserted.
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ * evt - Mousedown event of the connect gesture.
+ * dropTarget - <mxCell> that represents the cell under the mouse when it was
+ * released.
+ */
+mxConnectionHandler.prototype.isInsertBefore = function(edge, source, target, evt, dropTarget)
+{
+	return this.insertBeforeSource && source != target;
+};
+
+/**
+ * Function: isCreateTarget
+ * 
+ * Returns <createTarget>.
+ *
+ * Parameters:
+ *
+ * evt - Current active native pointer event.
+ */
+mxConnectionHandler.prototype.isCreateTarget = function(evt)
+{
+	return this.createTarget;
+};
+
+/**
+ * Function: setCreateTarget
+ * 
+ * Sets <createTarget>.
+ */
+mxConnectionHandler.prototype.setCreateTarget = function(value)
+{
+	this.createTarget = value;
+};
+
+/**
+ * Function: createShape
+ * 
+ * Creates the preview shape for new connections.
+ */
+mxConnectionHandler.prototype.createShape = function()
+{
+	// Creates the edge preview
+	var shape = (this.livePreview && this.edgeState != null) ?
+		this.graph.cellRenderer.createShape(this.edgeState) :
+		new mxPolyline([], mxConstants.INVALID_COLOR);
+	shape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
+		mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+	shape.scale = this.graph.view.scale;
+	shape.pointerEvents = false;
+	shape.isDashed = true;
+	shape.init(this.graph.getView().getOverlayPane());
+	mxEvent.redirectMouseEvents(shape.node, this.graph, null);
+
+	return shape;
+};
+
+/**
+ * Function: init
+ * 
+ * Initializes the shapes required for this connection handler. This should
+ * be invoked if <mxGraph.container> is assigned after the connection
+ * handler has been created.
+ */
+mxConnectionHandler.prototype.init = function()
+{
+	this.graph.addMouseListener(this);
+	this.marker = this.createMarker();
+	this.constraintHandler = new mxConstraintHandler(this.graph);
+
+	// Redraws the icons if the graph changes
+	this.changeHandler = mxUtils.bind(this, function(sender)
+	{
+		if (this.iconState != null)
+		{
+			this.iconState = this.graph.getView().getState(this.iconState.cell);
+		}
+		
+		if (this.iconState != null)
+		{
+			this.redrawIcons(this.icons, this.iconState);
+			this.constraintHandler.reset();
+		}
+		else if (this.previous != null && this.graph.view.getState(this.previous.cell) == null)
+		{
+			this.reset();
+		}
+	});
+	
+	this.graph.getModel().addListener(mxEvent.CHANGE, this.changeHandler);
+	this.graph.getView().addListener(mxEvent.SCALE, this.changeHandler);
+	this.graph.getView().addListener(mxEvent.TRANSLATE, this.changeHandler);
+	this.graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE, this.changeHandler);
+	
+	// Removes the icon if we step into/up or start editing
+	this.drillHandler = mxUtils.bind(this, function(sender)
+	{
+		this.reset();
+	});
+	
+	this.graph.addListener(mxEvent.START_EDITING, this.drillHandler);
+	this.graph.getView().addListener(mxEvent.DOWN, this.drillHandler);
+	this.graph.getView().addListener(mxEvent.UP, this.drillHandler);
+};
+
+/**
+ * Function: isConnectableCell
+ * 
+ * Returns true if the given cell is connectable. This is a hook to
+ * disable floating connections. This implementation returns true.
+ */
+mxConnectionHandler.prototype.isConnectableCell = function(cell)
+{
+	return true;
+};
+
+/**
+ * Function: createMarker
+ * 
+ * Creates and returns the <mxCellMarker> used in <marker>.
+ */
+mxConnectionHandler.prototype.createMarker = function()
+{
+	var marker = new mxCellMarker(this.graph);
+	marker.hotspotEnabled = true;
+
+	// Overrides to return cell at location only if valid (so that
+	// there is no highlight for invalid cells)
+	marker.getCell = mxUtils.bind(this, function(me)
+	{
+		var cell = mxCellMarker.prototype.getCell.apply(marker, arguments);
+		this.error = null;
+		
+		// Checks for cell at preview point (with grid)
+		if (cell == null && this.currentPoint != null)
+		{
+			cell = this.graph.getCellAt(this.currentPoint.x, this.currentPoint.y);
+		}
+		
+		// Uses connectable parent vertex if one exists
+		if (cell != null && !this.graph.isCellConnectable(cell))
+		{
+			var parent = this.graph.getModel().getParent(cell);
+			
+			if (this.graph.getModel().isVertex(parent) && this.graph.isCellConnectable(parent))
+			{
+				cell = parent;
+			}
+		}
+		
+		if ((this.graph.isSwimlane(cell) && this.currentPoint != null &&
+			this.graph.hitsSwimlaneContent(cell, this.currentPoint.x, this.currentPoint.y)) ||
+			!this.isConnectableCell(cell))
+		{
+			cell = null;
+		}
+		
+		if (cell != null)
+		{
+			if (this.isConnecting())
+			{
+				if (this.previous != null)
+				{
+					this.error = this.validateConnection(this.previous.cell, cell);
+					
+					if (this.error != null && this.error.length == 0)
+					{
+						cell = null;
+						
+						// Enables create target inside groups
+						if (this.isCreateTarget(me.getEvent()))
+						{
+							this.error = null;
+						}
+					}
+				}
+			}
+			else if (!this.isValidSource(cell, me))
+			{
+				cell = null;
+			}
+		}
+		else if (this.isConnecting() && !this.isCreateTarget(me.getEvent()) &&
+				!this.graph.allowDanglingEdges)
+		{
+			this.error = '';
+		}
+
+		return cell;
+	});
+
+	// Sets the highlight color according to validateConnection
+	marker.isValidState = mxUtils.bind(this, function(state)
+	{
+		if (this.isConnecting())
+		{
+			return this.error == null;
+		}
+		else
+		{
+			return mxCellMarker.prototype.isValidState.apply(marker, arguments);
+		}
+	});
+
+	// Overrides to use marker color only in highlight mode or for
+	// target selection
+	marker.getMarkerColor = mxUtils.bind(this, function(evt, state, isValid)
+	{
+		return (this.connectImage == null || this.isConnecting()) ?
+			mxCellMarker.prototype.getMarkerColor.apply(marker, arguments) :
+			null;
+	});
+
+	// Overrides to use hotspot only for source selection otherwise
+	// intersects always returns true when over a cell
+	marker.intersects = mxUtils.bind(this, function(state, evt)
+	{
+		if (this.connectImage != null || this.isConnecting())
+		{
+			return true;
+		}
+		
+		return mxCellMarker.prototype.intersects.apply(marker, arguments);
+	});
+
+	return marker;
+};
+
+/**
+ * Function: start
+ * 
+ * Starts a new connection for the given state and coordinates.
+ */
+mxConnectionHandler.prototype.start = function(state, x, y, edgeState)
+{
+	this.previous = state;
+	this.first = new mxPoint(x, y);
+	this.edgeState = (edgeState != null) ? edgeState : this.createEdgeState(null);
+	
+	// Marks the source state
+	this.marker.currentColor = this.marker.validColor;
+	this.marker.markedState = state;
+	this.marker.mark();
+
+	this.fireEvent(new mxEventObject(mxEvent.START, 'state', this.previous));
+};
+
+/**
+ * Function: isConnecting
+ * 
+ * Returns true if the source terminal has been clicked and a new
+ * connection is currently being previewed.
+ */
+mxConnectionHandler.prototype.isConnecting = function()
+{
+	return this.first != null && this.shape != null;
+};
+
+/**
+ * Function: isValidSource
+ * 
+ * Returns <mxGraph.isValidSource> for the given source terminal.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the source terminal.
+ * me - <mxMouseEvent> that is associated with this call.
+ */
+mxConnectionHandler.prototype.isValidSource = function(cell, me)
+{
+	return this.graph.isValidSource(cell);
+};
+
+/**
+ * Function: isValidTarget
+ * 
+ * Returns true. The call to <mxGraph.isValidTarget> is implicit by calling
+ * <mxGraph.getEdgeValidationError> in <validateConnection>. This is an
+ * additional hook for disabling certain targets in this specific handler.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the target terminal.
+ */
+mxConnectionHandler.prototype.isValidTarget = function(cell)
+{
+	return true;
+};
+
+/**
+ * Function: validateConnection
+ * 
+ * Returns the error message or an empty string if the connection for the
+ * given source target pair is not valid. Otherwise it returns null. This
+ * implementation uses <mxGraph.getEdgeValidationError>.
+ * 
+ * Parameters:
+ * 
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ */
+mxConnectionHandler.prototype.validateConnection = function(source, target)
+{
+	if (!this.isValidTarget(target))
+	{
+		return '';
+	}
+	
+	return this.graph.getEdgeValidationError(null, source, target);
+};
+
+/**
+ * Function: getConnectImage
+ * 
+ * Hook to return the <mxImage> used for the connection icon of the given
+ * <mxCellState>. This implementation returns <connectImage>.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose connect image should be returned.
+ */
+mxConnectionHandler.prototype.getConnectImage = function(state)
+{
+	return this.connectImage;
+};
+
+/**
+ * Function: isMoveIconToFrontForState
+ * 
+ * Returns true if the state has a HTML label in the graph's container, otherwise
+ * it returns <moveIconFront>.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose connect icons should be returned.
+ */
+mxConnectionHandler.prototype.isMoveIconToFrontForState = function(state)
+{
+	if (state.text != null && state.text.node.parentNode == this.graph.container)
+	{
+		return true;
+	}
+	
+	return this.moveIconFront;
+};
+
+/**
+ * Function: createIcons
+ * 
+ * Creates the array <mxImageShapes> that represent the connect icons for
+ * the given <mxCellState>.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose connect icons should be returned.
+ */
+mxConnectionHandler.prototype.createIcons = function(state)
+{
+	var image = this.getConnectImage(state);
+	
+	if (image != null && state != null)
+	{
+		this.iconState = state;
+		var icons = [];
+
+		// Cannot use HTML for the connect icons because the icon receives all
+		// mouse move events in IE, must use VML and SVG instead even if the
+		// connect-icon appears behind the selection border and the selection
+		// border consumes the events before the icon gets a chance
+		var bounds = new mxRectangle(0, 0, image.width, image.height);
+		var icon = new mxImageShape(bounds, image.src, null, null, 0);
+		icon.preserveImageAspect = false;
+		
+		if (this.isMoveIconToFrontForState(state))
+		{
+			icon.dialect = mxConstants.DIALECT_STRICTHTML;
+			icon.init(this.graph.container);
+		}
+		else
+		{
+			icon.dialect = (this.graph.dialect == mxConstants.DIALECT_SVG) ?
+				mxConstants.DIALECT_SVG : mxConstants.DIALECT_VML;
+			icon.init(this.graph.getView().getOverlayPane());
+
+			// Move the icon back in the overlay pane
+			if (this.moveIconBack && icon.node.previousSibling != null)
+			{
+				icon.node.parentNode.insertBefore(icon.node, icon.node.parentNode.firstChild);
+			}
+		}
+
+		icon.node.style.cursor = mxConstants.CURSOR_CONNECT;
+
+		// Events transparency
+		var getState = mxUtils.bind(this, function()
+		{
+			return (this.currentState != null) ? this.currentState : state;
+		});
+		
+		// Updates the local icon before firing the mouse down event.
+		var mouseDown = mxUtils.bind(this, function(evt)
+		{
+			if (!mxEvent.isConsumed(evt))
+			{
+				this.icon = icon;
+				this.graph.fireMouseEvent(mxEvent.MOUSE_DOWN,
+					new mxMouseEvent(evt, getState()));
+			}
+		});
+
+		mxEvent.redirectMouseEvents(icon.node, this.graph, getState, mouseDown);
+		
+		icons.push(icon);
+		this.redrawIcons(icons, this.iconState);
+		
+		return icons;
+	}
+	
+	return null;
+};
+
+/**
+ * Function: redrawIcons
+ * 
+ * Redraws the given array of <mxImageShapes>.
+ * 
+ * Parameters:
+ * 
+ * icons - Optional array of <mxImageShapes> to be redrawn.
+ */
+mxConnectionHandler.prototype.redrawIcons = function(icons, state)
+{
+	if (icons != null && icons[0] != null && state != null)
+	{
+		var pos = this.getIconPosition(icons[0], state);
+		icons[0].bounds.x = pos.x;
+		icons[0].bounds.y = pos.y;
+		icons[0].redraw();
+	}
+};
+
+/**
+ * Function: redrawIcons
+ * 
+ * Redraws the given array of <mxImageShapes>.
+ * 
+ * Parameters:
+ * 
+ * icons - Optional array of <mxImageShapes> to be redrawn.
+ */
+mxConnectionHandler.prototype.getIconPosition = function(icon, state)
+{
+	var scale = this.graph.getView().scale;
+	var cx = state.getCenterX();
+	var cy = state.getCenterY();
+	
+	if (this.graph.isSwimlane(state.cell))
+	{
+		var size = this.graph.getStartSize(state.cell);
+		
+		cx = (size.width != 0) ? state.x + size.width * scale / 2 : cx;
+		cy = (size.height != 0) ? state.y + size.height * scale / 2 : cy;
+		
+		var alpha = mxUtils.toRadians(mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION) || 0);
+		
+		if (alpha != 0)
+		{
+			var cos = Math.cos(alpha);
+			var sin = Math.sin(alpha);
+			var ct = new mxPoint(state.getCenterX(), state.getCenterY());
+			var pt = mxUtils.getRotatedPoint(new mxPoint(cx, cy), cos, sin, ct);
+			cx = pt.x;
+			cy = pt.y;
+		}
+	}
+
+	return new mxPoint(cx - icon.bounds.width / 2,
+			cy - icon.bounds.height / 2);
+};
+
+/**
+ * Function: destroyIcons
+ * 
+ * Destroys the connect icons and resets the respective state.
+ */
+mxConnectionHandler.prototype.destroyIcons = function()
+{
+	if (this.icons != null)
+	{
+		for (var i = 0; i < this.icons.length; i++)
+		{
+			this.icons[i].destroy();
+		}
+		
+		this.icons = null;
+		this.icon = null;
+		this.selectedIcon = null;
+		this.iconState = null;
+	}
+};
+
+/**
+ * Function: isStartEvent
+ * 
+ * Returns true if the given mouse down event should start this handler. The
+ * This implementation returns true if the event does not force marquee
+ * selection, and the currentConstraint and currentFocus of the
+ * <constraintHandler> are not null, or <previous> and <error> are not null and
+ * <icons> is null or <icons> and <icon> are not null.
+ */
+mxConnectionHandler.prototype.isStartEvent = function(me)
+{
+	return ((this.constraintHandler.currentFocus != null && this.constraintHandler.currentConstraint != null) ||
+		(this.previous != null && this.error == null && (this.icons == null || (this.icons != null &&
+		this.icon != null))));
+};
+
+/**
+ * Function: mouseDown
+ * 
+ * Handles the event by initiating a new connection.
+ */
+mxConnectionHandler.prototype.mouseDown = function(sender, me)
+{
+	this.mouseDownCounter++;
+	
+	if (this.isEnabled() && this.graph.isEnabled() && !me.isConsumed() &&
+		!this.isConnecting() && this.isStartEvent(me))
+	{
+		if (this.constraintHandler.currentConstraint != null &&
+			this.constraintHandler.currentFocus != null &&
+			this.constraintHandler.currentPoint != null)
+		{
+			this.sourceConstraint = this.constraintHandler.currentConstraint;
+			this.previous = this.constraintHandler.currentFocus;
+			this.first = this.constraintHandler.currentPoint.clone();
+		}
+		else
+		{
+			// Stores the location of the initial mousedown
+			this.first = new mxPoint(me.getGraphX(), me.getGraphY());
+		}
+	
+		this.edgeState = this.createEdgeState(me);
+		this.mouseDownCounter = 1;
+		
+		if (this.waypointsEnabled && this.shape == null)
+		{
+			this.waypoints = null;
+			this.shape = this.createShape();
+			
+			if (this.edgeState != null)
+			{
+				this.shape.apply(this.edgeState);
+			}
+		}
+
+		// Stores the starting point in the geometry of the preview
+		if (this.previous == null && this.edgeState != null)
+		{
+			var pt = this.graph.getPointForEvent(me.getEvent());
+			this.edgeState.cell.geometry.setTerminalPoint(pt, true);
+		}
+		
+		this.fireEvent(new mxEventObject(mxEvent.START, 'state', this.previous));
+
+		me.consume();
+	}
+
+	this.selectedIcon = this.icon;
+	this.icon = null;
+};
+
+/**
+ * Function: isImmediateConnectSource
+ * 
+ * Returns true if a tap on the given source state should immediately start
+ * connecting. This implementation returns true if the state is not movable
+ * in the graph. 
+ */
+mxConnectionHandler.prototype.isImmediateConnectSource = function(state)
+{
+	return !this.graph.isCellMovable(state.cell);
+};
+
+/**
+ * Function: createEdgeState
+ * 
+ * Hook to return an <mxCellState> which may be used during the preview.
+ * This implementation returns null.
+ * 
+ * Use the following code to create a preview for an existing edge style:
+ * 
+ * (code)
+ * graph.connectionHandler.createEdgeState = function(me)
+ * {
+ *   var edge = graph.createEdge(null, null, null, null, null, 'edgeStyle=elbowEdgeStyle');
+ *   
+ *   return new mxCellState(this.graph.view, edge, this.graph.getCellStyle(edge));
+ * };
+ * (end)
+ */
+mxConnectionHandler.prototype.createEdgeState = function(me)
+{
+	return null;
+};
+
+/**
+ * Function: isOutlineConnectEvent
+ * 
+ * Returns true if <outlineConnect> is true and the source of the event is the outline shape
+ * or shift is pressed.
+ */
+mxConnectionHandler.prototype.isOutlineConnectEvent = function(me)
+{
+	var offset = mxUtils.getOffset(this.graph.container);
+	var evt = me.getEvent();
+	
+	var clientX = mxEvent.getClientX(evt);
+	var clientY = mxEvent.getClientY(evt);
+	
+	var doc = document.documentElement;
+	var left = (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0);
+	var top = (window.pageYOffset || doc.scrollTop)  - (doc.clientTop || 0);
+	
+	var gridX = this.currentPoint.x - this.graph.container.scrollLeft + offset.x - left;
+	var gridY = this.currentPoint.y - this.graph.container.scrollTop + offset.y - top;
+
+	return this.outlineConnect && !mxEvent.isShiftDown(me.getEvent()) &&
+		(me.isSource(this.marker.highlight.shape) ||
+		(mxEvent.isAltDown(me.getEvent()) && me.getState() != null) ||
+		this.marker.highlight.isHighlightAt(clientX, clientY) ||
+		((gridX != clientX || gridY != clientY) && me.getState() == null &&
+		this.marker.highlight.isHighlightAt(gridX, gridY)));
+};
+
+/**
+ * Function: updateCurrentState
+ * 
+ * Updates the current state for a given mouse move event by using
+ * the <marker>.
+ */
+mxConnectionHandler.prototype.updateCurrentState = function(me, point)
+{
+	this.constraintHandler.update(me, this.first == null, false, (this.first == null ||
+		me.isSource(this.marker.highlight.shape)) ? null : point);
+	
+	if (this.constraintHandler.currentFocus != null && this.constraintHandler.currentConstraint != null)
+	{
+		// Handles special case where grid is large and connection point is at actual point in which
+		// case the outline is not followed as long as we're < gridSize / 2 away from that point
+		if (this.marker.highlight != null && this.marker.highlight.state != null &&
+			this.marker.highlight.state.cell == this.constraintHandler.currentFocus.cell)
+		{
+			// Direct repaint needed if cell already highlighted
+			if (this.marker.highlight.shape.stroke != 'transparent')
+			{
+				this.marker.highlight.shape.stroke = 'transparent';
+				this.marker.highlight.repaint();
+			}
+		}
+		else
+		{
+			this.marker.markCell(this.constraintHandler.currentFocus.cell, 'transparent');
+		}
+
+		// Updates validation state
+		if (this.previous != null)
+		{
+			this.error = this.validateConnection(this.previous.cell, this.constraintHandler.currentFocus.cell);
+			
+			if (this.error == null)
+			{
+				this.currentState = this.constraintHandler.currentFocus;
+			}
+			else
+			{
+				this.constraintHandler.reset();
+			}
+		}
+	}
+	else
+	{
+		if (this.graph.isIgnoreTerminalEvent(me.getEvent()))
+		{
+			this.marker.reset();
+			this.currentState = null;
+		}
+		else
+		{
+			this.marker.process(me);
+			this.currentState = this.marker.getValidState();
+		}
+
+		var outline = this.isOutlineConnectEvent(me);
+		
+		if (this.currentState != null && outline)
+		{
+			// Handles special case where mouse is on outline away from actual end point
+			// in which case the grid is ignored and mouse point is used instead
+			if (me.isSource(this.marker.highlight.shape))
+			{
+				point = new mxPoint(me.getGraphX(), me.getGraphY());
+			}
+			
+			var constraint = this.graph.getOutlineConstraint(point, this.currentState, me);
+			this.constraintHandler.setFocus(me, this.currentState, false);
+			this.constraintHandler.currentConstraint = constraint;
+			this.constraintHandler.currentPoint = point;
+		}
+
+		if (this.outlineConnect)
+		{
+			if (this.marker.highlight != null && this.marker.highlight.shape != null)
+			{
+				var s = this.graph.view.scale;
+				
+				if (this.constraintHandler.currentConstraint != null &&
+					this.constraintHandler.currentFocus != null)
+				{
+					this.marker.highlight.shape.stroke = mxConstants.OUTLINE_HIGHLIGHT_COLOR;
+					this.marker.highlight.shape.strokewidth = mxConstants.OUTLINE_HIGHLIGHT_STROKEWIDTH / s / s;
+					this.marker.highlight.repaint();
+				} 
+				else if (this.marker.hasValidState())
+				{
+					// Handles special case where actual end point of edge and current mouse point
+					// are not equal (due to grid snapping) and there is no hit on shape or highlight
+					if (this.marker.getValidState() != me.getState())
+					{
+						this.marker.highlight.shape.stroke = 'transparent';
+						this.currentState = null;
+					}
+					else
+					{
+						this.marker.highlight.shape.stroke = mxConstants.DEFAULT_VALID_COLOR;
+					}
+	
+					this.marker.highlight.shape.strokewidth = mxConstants.HIGHLIGHT_STROKEWIDTH / s / s;
+					this.marker.highlight.repaint();
+				}
+			}
+		}
+	}
+};
+
+/**
+ * Function: convertWaypoint
+ * 
+ * Converts the given point from screen coordinates to model coordinates.
+ */
+mxConnectionHandler.prototype.convertWaypoint = function(point)
+{
+	var scale = this.graph.getView().getScale();
+	var tr = this.graph.getView().getTranslate();
+	
+	point.x = point.x / scale - tr.x;
+	point.y = point.y / scale - tr.y;
+};
+
+/**
+ * Function: snapToPreview
+ * 
+ * Called to snap the given point to the current preview. This snaps to the
+ * first point of the preview if alt is not pressed.
+ */
+mxConnectionHandler.prototype.snapToPreview = function(me, point)
+{
+	if (!mxEvent.isAltDown(me.getEvent()) && this.previous != null)
+	{
+		var tol = this.graph.gridSize * this.graph.view.scale / 2;	
+		var tmp = (this.sourceConstraint != null) ? this.first :
+			new mxPoint(this.previous.getCenterX(), this.previous.getCenterY());
+
+		if (Math.abs(tmp.x - me.getGraphX()) < tol)
+		{
+			point.x = tmp.x;
+		}
+		
+		if (Math.abs(tmp.y - me.getGraphY()) < tol)
+		{
+			point.y = tmp.y;
+		}
+	}	
+};
+
+/**
+ * Function: mouseMove
+ * 
+ * Handles the event by updating the preview edge or by highlighting
+ * a possible source or target terminal.
+ */
+mxConnectionHandler.prototype.mouseMove = function(sender, me)
+{
+	if (!me.isConsumed() && (this.ignoreMouseDown || this.first != null || !this.graph.isMouseDown))
+	{
+		// Handles special case when handler is disabled during highlight
+		if (!this.isEnabled() && this.currentState != null)
+		{
+			this.destroyIcons();
+			this.currentState = null;
+		}
+
+		var view = this.graph.getView();
+		var scale = view.scale;
+		var tr = view.translate;
+		var point = new mxPoint(me.getGraphX(), me.getGraphY());
+		this.error = null;
+
+		if (this.graph.isGridEnabledEvent(me.getEvent()))
+		{
+			point = new mxPoint((this.graph.snap(point.x / scale - tr.x) + tr.x) * scale,
+				(this.graph.snap(point.y / scale - tr.y) + tr.y) * scale);
+		}
+		
+		this.snapToPreview(me, point);
+		this.currentPoint = point;
+		
+		if (this.first != null || (this.isEnabled() && this.graph.isEnabled()))
+		{
+			this.updateCurrentState(me, point);
+		}
+
+		if (this.first != null)
+		{
+			var constraint = null;
+			var current = point;
+			
+			// Uses the current point from the constraint handler if available
+			if (this.constraintHandler.currentConstraint != null &&
+				this.constraintHandler.currentFocus != null &&
+				this.constraintHandler.currentPoint != null)
+			{
+				constraint = this.constraintHandler.currentConstraint;
+				current = this.constraintHandler.currentPoint.clone();
+			}
+			else if (this.previous != null && !this.graph.isIgnoreTerminalEvent(me.getEvent()) && mxEvent.isShiftDown(me.getEvent()))
+			{
+				if (Math.abs(this.previous.getCenterX() - point.x) < Math.abs(this.previous.getCenterY() - point.y))
+				{
+					point.x = this.previous.getCenterX();
+				}
+				else
+				{
+					point.y = this.previous.getCenterY();
+				}
+			}
+			
+			var pt2 = this.first;
+			
+			// Moves the connect icon with the mouse
+			if (this.selectedIcon != null)
+			{
+				var w = this.selectedIcon.bounds.width;
+				var h = this.selectedIcon.bounds.height;
+				
+				if (this.currentState != null && this.targetConnectImage)
+				{
+					var pos = this.getIconPosition(this.selectedIcon, this.currentState);
+					this.selectedIcon.bounds.x = pos.x;
+					this.selectedIcon.bounds.y = pos.y;
+				}
+				else
+				{
+					var bounds = new mxRectangle(me.getGraphX() + this.connectIconOffset.x,
+						me.getGraphY() + this.connectIconOffset.y, w, h);
+					this.selectedIcon.bounds = bounds;
+				}
+				
+				this.selectedIcon.redraw();
+			}
+
+			// Uses edge state to compute the terminal points
+			if (this.edgeState != null)
+			{
+				this.updateEdgeState(current, constraint);
+				current = this.edgeState.absolutePoints[this.edgeState.absolutePoints.length - 1];
+				pt2 = this.edgeState.absolutePoints[0];
+			}
+			else
+			{
+				if (this.currentState != null)
+				{
+					if (this.constraintHandler.currentConstraint == null)
+					{
+						var tmp = this.getTargetPerimeterPoint(this.currentState, me);
+						
+						if (tmp != null)
+						{
+							current = tmp;
+						}
+					}
+				}
+				
+				// Computes the source perimeter point
+				if (this.sourceConstraint == null && this.previous != null)
+				{
+					var next = (this.waypoints != null && this.waypoints.length > 0) ?
+							this.waypoints[0] : current;
+					var tmp = this.getSourcePerimeterPoint(this.previous, next, me);
+					
+					if (tmp != null)
+					{
+						pt2 = tmp;
+					}
+				}
+			}
+
+			// Makes sure the cell under the mousepointer can be detected
+			// by moving the preview shape away from the mouse. This
+			// makes sure the preview shape does not prevent the detection
+			// of the cell under the mousepointer even for slow gestures.
+			if (this.currentState == null && this.movePreviewAway)
+			{
+				var tmp = pt2; 
+				
+				if (this.edgeState != null && this.edgeState.absolutePoints.length >= 2)
+				{
+					var tmp2 = this.edgeState.absolutePoints[this.edgeState.absolutePoints.length - 2];
+					
+					if (tmp2 != null)
+					{
+						tmp = tmp2;
+					}
+				}
+				
+				var dx = current.x - tmp.x;
+				var dy = current.y - tmp.y;
+				
+				var len = Math.sqrt(dx * dx + dy * dy);
+				
+				if (len == 0)
+				{
+					return;
+				}
+
+				// Stores old point to reuse when creating edge
+				this.originalPoint = current.clone();
+				current.x -= dx * 4 / len;
+				current.y -= dy * 4 / len;
+			}
+			else
+			{
+				this.originalPoint = null;
+			}
+			
+			// Creates the preview shape (lazy)
+			if (this.shape == null)
+			{
+				var dx = Math.abs(point.x - this.first.x);
+				var dy = Math.abs(point.y - this.first.y);
+
+				if (dx > this.graph.tolerance || dy > this.graph.tolerance)
+				{
+					this.shape = this.createShape();
+
+					if (this.edgeState != null)
+					{
+						this.shape.apply(this.edgeState);
+					}
+					
+					// Revalidates current connection
+					this.updateCurrentState(me, point);
+				}
+			}
+
+			// Updates the points in the preview edge
+			if (this.shape != null)
+			{
+				if (this.edgeState != null)
+				{
+					this.shape.points = this.edgeState.absolutePoints;
+				}
+				else
+				{
+					var pts = [pt2];
+					
+					if (this.waypoints != null)
+					{
+						pts = pts.concat(this.waypoints);
+					}
+
+					pts.push(current);
+					this.shape.points = pts;
+				}
+				
+				this.drawPreview();
+			}
+			
+			// Makes sure endpoint of edge is visible during connect
+			if (this.cursor != null)
+			{
+				this.graph.container.style.cursor = this.cursor;
+			}
+			
+			mxEvent.consume(me.getEvent());
+			me.consume();
+		}
+		else if (!this.isEnabled() || !this.graph.isEnabled())
+		{
+			this.constraintHandler.reset();
+		}
+		else if (this.previous != this.currentState && this.edgeState == null)
+		{
+			this.destroyIcons();
+			
+			// Sets the cursor on the current shape				
+			if (this.currentState != null && this.error == null && this.constraintHandler.currentConstraint == null)
+			{
+				this.icons = this.createIcons(this.currentState);
+
+				if (this.icons == null)
+				{
+					this.currentState.setCursor(mxConstants.CURSOR_CONNECT);
+					me.consume();
+				}
+			}
+
+			this.previous = this.currentState;
+		}
+		else if (this.previous == this.currentState && this.currentState != null && this.icons == null &&
+			!this.graph.isMouseDown)
+		{
+			// Makes sure that no cursors are changed
+			me.consume();
+		}
+
+		if (!this.graph.isMouseDown && this.currentState != null && this.icons != null)
+		{
+			var hitsIcon = false;
+			var target = me.getSource();
+			
+			for (var i = 0; i < this.icons.length && !hitsIcon; i++)
+			{
+				hitsIcon = target == this.icons[i].node || target.parentNode == this.icons[i].node;
+			}
+
+			if (!hitsIcon)
+			{
+				this.updateIcons(this.currentState, this.icons, me);
+			}
+		}
+	}
+	else
+	{
+		this.constraintHandler.reset();
+	}
+};
+
+/**
+ * Function: updateEdgeState
+ * 
+ * Updates <edgeState>.
+ */
+mxConnectionHandler.prototype.updateEdgeState = function(current, constraint)
+{
+	// TODO: Use generic method for writing constraint to style
+	if (this.sourceConstraint != null && this.sourceConstraint.point != null)
+	{
+		this.edgeState.style[mxConstants.STYLE_EXIT_X] = this.sourceConstraint.point.x;
+		this.edgeState.style[mxConstants.STYLE_EXIT_Y] = this.sourceConstraint.point.y;
+	}
+
+	if (constraint != null && constraint.point != null)
+	{
+		this.edgeState.style[mxConstants.STYLE_ENTRY_X] = constraint.point.x;
+		this.edgeState.style[mxConstants.STYLE_ENTRY_Y] = constraint.point.y;
+	}
+	else
+	{
+		delete this.edgeState.style[mxConstants.STYLE_ENTRY_X];
+		delete this.edgeState.style[mxConstants.STYLE_ENTRY_Y];
+	}
+	
+	this.edgeState.absolutePoints = [null, (this.currentState != null) ? null : current];
+	this.graph.view.updateFixedTerminalPoint(this.edgeState, this.previous, true, this.sourceConstraint);
+	
+	if (this.currentState != null)
+	{
+		if (constraint == null)
+		{
+			constraint = this.graph.getConnectionConstraint(this.edgeState, this.previous, false);
+		}
+		
+		this.edgeState.setAbsoluteTerminalPoint(null, false);
+		this.graph.view.updateFixedTerminalPoint(this.edgeState, this.currentState, false, constraint);
+	}
+	
+	// Scales and translates the waypoints to the model
+	var realPoints = null;
+	
+	if (this.waypoints != null)
+	{
+		realPoints = [];
+		
+		for (var i = 0; i < this.waypoints.length; i++)
+		{
+			var pt = this.waypoints[i].clone();
+			this.convertWaypoint(pt);
+			realPoints[i] = pt;
+		}
+	}
+	
+	this.graph.view.updatePoints(this.edgeState, realPoints, this.previous, this.currentState);
+	this.graph.view.updateFloatingTerminalPoints(this.edgeState, this.previous, this.currentState);
+};
+
+/**
+ * Function: getTargetPerimeterPoint
+ * 
+ * Returns the perimeter point for the given target state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> that represents the target cell state.
+ * me - <mxMouseEvent> that represents the mouse move.
+ */
+mxConnectionHandler.prototype.getTargetPerimeterPoint = function(state, me)
+{
+	var result = null;
+	var view = state.view;
+	var targetPerimeter = view.getPerimeterFunction(state);
+	
+	if (targetPerimeter != null)
+	{
+		var next = (this.waypoints != null && this.waypoints.length > 0) ?
+				this.waypoints[this.waypoints.length - 1] :
+				new mxPoint(this.previous.getCenterX(), this.previous.getCenterY());
+		var tmp = targetPerimeter(view.getPerimeterBounds(state),
+			this.edgeState, next, false);
+			
+		if (tmp != null)
+		{
+			result = tmp;
+		}
+	}
+	else
+	{
+		result = new mxPoint(state.getCenterX(), state.getCenterY());
+	}
+	
+	return result;
+};
+
+/**
+ * Function: getSourcePerimeterPoint
+ * 
+ * Hook to update the icon position(s) based on a mouseOver event. This is
+ * an empty implementation.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> that represents the target cell state.
+ * next - <mxPoint> that represents the next point along the previewed edge.
+ * me - <mxMouseEvent> that represents the mouse move.
+ */
+mxConnectionHandler.prototype.getSourcePerimeterPoint = function(state, next, me)
+{
+	var result = null;
+	var view = state.view;
+	var sourcePerimeter = view.getPerimeterFunction(state);
+	var c = new mxPoint(state.getCenterX(), state.getCenterY());
+	
+	if (sourcePerimeter != null)
+	{
+		var theta = mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION, 0);
+		var rad = -theta * (Math.PI / 180);
+		
+		if (theta != 0)
+		{
+			next = mxUtils.getRotatedPoint(new mxPoint(next.x, next.y), Math.cos(rad), Math.sin(rad), c);
+		}
+		
+		var tmp = sourcePerimeter(view.getPerimeterBounds(state), state, next, false);
+			
+		if (tmp != null)
+		{
+			if (theta != 0)
+			{
+				tmp = mxUtils.getRotatedPoint(new mxPoint(tmp.x, tmp.y), Math.cos(-rad), Math.sin(-rad), c);
+			}
+			
+			result = tmp;
+		}
+	}
+	else
+	{
+		result = c;
+	}
+	
+	return result;
+};
+
+
+/**
+ * Function: updateIcons
+ * 
+ * Hook to update the icon position(s) based on a mouseOver event. This is
+ * an empty implementation.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> under the mouse.
+ * icons - Array of currently displayed icons.
+ * me - <mxMouseEvent> that contains the mouse event.
+ */
+mxConnectionHandler.prototype.updateIcons = function(state, icons, me)
+{
+	// empty
+};
+
+/**
+ * Function: isStopEvent
+ * 
+ * Returns true if the given mouse up event should stop this handler. The
+ * connection will be created if <error> is null. Note that this is only
+ * called if <waypointsEnabled> is true. This implemtation returns true
+ * if there is a cell state in the given event.
+ */
+mxConnectionHandler.prototype.isStopEvent = function(me)
+{
+	return me.getState() != null;
+};
+
+/**
+ * Function: addWaypoint
+ * 
+ * Adds the waypoint for the given event to <waypoints>.
+ */
+mxConnectionHandler.prototype.addWaypointForEvent = function(me)
+{
+	var point = mxUtils.convertPoint(this.graph.container, me.getX(), me.getY());
+	var dx = Math.abs(point.x - this.first.x);
+	var dy = Math.abs(point.y - this.first.y);
+	var addPoint = this.waypoints != null || (this.mouseDownCounter > 1 &&
+			(dx > this.graph.tolerance || dy > this.graph.tolerance));
+
+	if (addPoint)
+	{
+		if (this.waypoints == null)
+		{
+			this.waypoints = [];
+		}
+		
+		var scale = this.graph.view.scale;
+		var point = new mxPoint(this.graph.snap(me.getGraphX() / scale) * scale,
+				this.graph.snap(me.getGraphY() / scale) * scale);
+		this.waypoints.push(point);
+	}
+};
+
+/**
+ * Function: mouseUp
+ * 
+ * Handles the event by inserting the new connection.
+ */
+mxConnectionHandler.prototype.mouseUp = function(sender, me)
+{
+	if (!me.isConsumed() && this.isConnecting())
+	{
+		if (this.waypointsEnabled && !this.isStopEvent(me))
+		{
+			this.addWaypointForEvent(me);
+			me.consume();
+			
+			return;
+		}
+		
+		// Inserts the edge if no validation error exists
+		if (this.error == null)
+		{
+			var source = (this.previous != null) ? this.previous.cell : null;
+			var target = null;
+			
+			if (this.constraintHandler.currentConstraint != null &&
+				this.constraintHandler.currentFocus != null)
+			{
+				target = this.constraintHandler.currentFocus.cell;
+			}
+			
+			if (target == null && this.currentState != null)
+			{
+				target = this.currentState.cell;
+			}
+			
+			this.connect(source, target, me.getEvent(), me.getCell());
+		}
+		else
+		{
+			// Selects the source terminal for self-references
+			if (this.previous != null && this.marker.validState != null &&
+				this.previous.cell == this.marker.validState.cell)
+			{
+				this.graph.selectCellForEvent(this.marker.source, evt);
+			}
+			
+			// Displays the error message if it is not an empty string,
+			// for empty error messages, the event is silently dropped
+			if (this.error.length > 0)
+			{
+				this.graph.validationAlert(this.error);
+			}
+		}
+		
+		// Redraws the connect icons and resets the handler state
+		this.destroyIcons();
+		me.consume();
+	}
+
+	if (this.first != null)
+	{
+		this.reset();
+	}
+};
+
+/**
+ * Function: reset
+ * 
+ * Resets the state of this handler.
+ */
+mxConnectionHandler.prototype.reset = function()
+{
+	if (this.shape != null)
+	{
+		this.shape.destroy();
+		this.shape = null;
+	}
+	
+	// Resets the cursor on the container
+	if (this.cursor != null && this.graph.container != null)
+	{
+		this.graph.container.style.cursor = '';
+	}
+	
+	this.destroyIcons();
+	this.marker.reset();
+	this.constraintHandler.reset();
+	this.originalPoint = null;
+	this.currentPoint = null;
+	this.edgeState = null;
+	this.previous = null;
+	this.error = null;
+	this.sourceConstraint = null;
+	this.mouseDownCounter = 0;
+	this.first = null;
+
+	this.fireEvent(new mxEventObject(mxEvent.RESET));
+};
+
+/**
+ * Function: drawPreview
+ * 
+ * Redraws the preview edge using the color and width returned by
+ * <getEdgeColor> and <getEdgeWidth>.
+ */
+mxConnectionHandler.prototype.drawPreview = function()
+{
+	this.updatePreview(this.error == null);
+	this.shape.redraw();
+};
+
+/**
+ * Function: getEdgeColor
+ * 
+ * Returns the color used to draw the preview edge. This returns green if
+ * there is no edge validation error and red otherwise.
+ * 
+ * Parameters:
+ * 
+ * valid - Boolean indicating if the color for a valid edge should be
+ * returned.
+ */
+mxConnectionHandler.prototype.updatePreview = function(valid)
+{
+	this.shape.strokewidth = this.getEdgeWidth(valid);
+	this.shape.stroke = this.getEdgeColor(valid);
+};
+
+/**
+ * Function: getEdgeColor
+ * 
+ * Returns the color used to draw the preview edge. This returns green if
+ * there is no edge validation error and red otherwise.
+ * 
+ * Parameters:
+ * 
+ * valid - Boolean indicating if the color for a valid edge should be
+ * returned.
+ */
+mxConnectionHandler.prototype.getEdgeColor = function(valid)
+{
+	return (valid) ? mxConstants.VALID_COLOR : mxConstants.INVALID_COLOR;
+};
+	
+/**
+ * Function: getEdgeWidth
+ * 
+ * Returns the width used to draw the preview edge. This returns 3 if
+ * there is no edge validation error and 1 otherwise.
+ * 
+ * Parameters:
+ * 
+ * valid - Boolean indicating if the width for a valid edge should be
+ * returned.
+ */
+mxConnectionHandler.prototype.getEdgeWidth = function(valid)
+{
+	return (valid) ? 3 : 1;
+};
+
+/**
+ * Function: connect
+ * 
+ * Connects the given source and target using a new edge. This
+ * implementation uses <createEdge> to create the edge.
+ * 
+ * Parameters:
+ * 
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ * evt - Mousedown event of the connect gesture.
+ * dropTarget - <mxCell> that represents the cell under the mouse when it was
+ * released.
+ */
+mxConnectionHandler.prototype.connect = function(source, target, evt, dropTarget)
+{
+	if (target != null || this.isCreateTarget(evt) || this.graph.allowDanglingEdges)
+	{
+		// Uses the common parent of source and target or
+		// the default parent to insert the edge
+		var model = this.graph.getModel();
+		var terminalInserted = false;
+		var edge = null;
+
+		model.beginUpdate();
+		try
+		{
+			if (source != null && target == null && !this.graph.isIgnoreTerminalEvent(evt) && this.isCreateTarget(evt))
+			{
+				target = this.createTargetVertex(evt, source);
+				
+				if (target != null)
+				{
+					dropTarget = this.graph.getDropTarget([target], evt, dropTarget);
+					terminalInserted = true;
+					
+					// Disables edges as drop targets if the target cell was created
+					// FIXME: Should not shift if vertex was aligned (same in Java)
+					if (dropTarget == null || !this.graph.getModel().isEdge(dropTarget))
+					{
+						var pstate = this.graph.getView().getState(dropTarget);
+						
+						if (pstate != null)
+						{
+							var tmp = model.getGeometry(target);
+							tmp.x -= pstate.origin.x;
+							tmp.y -= pstate.origin.y;
+						}
+					}
+					else
+					{
+						dropTarget = this.graph.getDefaultParent();
+					}
+						
+					this.graph.addCell(target, dropTarget);
+				}
+			}
+
+			var parent = this.graph.getDefaultParent();
+
+			if (source != null && target != null &&
+				model.getParent(source) == model.getParent(target) &&
+				model.getParent(model.getParent(source)) != model.getRoot())
+			{
+				parent = model.getParent(source);
+
+				if ((source.geometry != null && source.geometry.relative) &&
+					(target.geometry != null && target.geometry.relative))
+				{
+					parent = model.getParent(parent);
+				}
+			}
+			
+			// Uses the value of the preview edge state for inserting
+			// the new edge into the graph
+			var value = null;
+			var style = null;
+			
+			if (this.edgeState != null)
+			{
+				value = this.edgeState.cell.value;
+				style = this.edgeState.cell.style;
+			}
+
+			edge = this.insertEdge(parent, null, value, source, target, style);
+			
+			if (edge != null)
+			{
+				// Updates the connection constraints
+				this.graph.setConnectionConstraint(edge, source, true, this.sourceConstraint);
+				this.graph.setConnectionConstraint(edge, target, false, this.constraintHandler.currentConstraint);
+				
+				// Uses geometry of the preview edge state
+				if (this.edgeState != null)
+				{
+					model.setGeometry(edge, this.edgeState.cell.geometry);
+				}
+				
+				var parent = model.getParent(source);
+				
+				// Inserts edge before source
+				if (this.isInsertBefore(edge, source, target, evt, dropTarget))
+				{
+					var index = null;
+					var tmp = source;
+
+					while (tmp.parent != null && tmp.geometry != null &&
+						tmp.geometry.relative && tmp.parent != edge.parent)
+					{
+						tmp = this.graph.model.getParent(tmp);
+					}
+
+					if (tmp != null && tmp.parent != null && tmp.parent == edge.parent)
+					{
+						var index = tmp.parent.getIndex(tmp);
+						tmp.parent.insert(edge, index);
+					}
+				}
+				
+				// Makes sure the edge has a non-null, relative geometry
+				var geo = model.getGeometry(edge);
+
+				if (geo == null)
+				{
+					geo = new mxGeometry();
+					geo.relative = true;
+					
+					model.setGeometry(edge, geo);
+				}
+				
+				// Uses scaled waypoints in geometry
+				if (this.waypoints != null && this.waypoints.length > 0)
+				{
+					var s = this.graph.view.scale;
+					var tr = this.graph.view.translate;
+					geo.points = [];
+					
+					for (var i = 0; i < this.waypoints.length; i++)
+					{
+						var pt = this.waypoints[i];
+						geo.points.push(new mxPoint(pt.x / s - tr.x, pt.y / s - tr.y));
+					}
+				}
+
+				if (target == null)
+				{
+					var t = this.graph.view.translate;
+					var s = this.graph.view.scale;
+					var pt = (this.originalPoint != null) ?
+							new mxPoint(this.originalPoint.x / s - t.x, this.originalPoint.y / s - t.y) :
+						new mxPoint(this.currentPoint.x / s - t.x, this.currentPoint.y / s - t.y);
+					pt.x -= this.graph.panDx / this.graph.view.scale;
+					pt.y -= this.graph.panDy / this.graph.view.scale;
+					geo.setTerminalPoint(pt, false);
+				}
+				
+				this.fireEvent(new mxEventObject(mxEvent.CONNECT, 'cell', edge, 'terminal', target,
+					'event', evt, 'target', dropTarget, 'terminalInserted', terminalInserted));
+			}
+		}
+		catch (e)
+		{
+			mxLog.show();
+			mxLog.debug(e.message);
+		}
+		finally
+		{
+			model.endUpdate();
+		}
+		
+		if (this.select)
+		{
+			this.selectCells(edge, (terminalInserted) ? target : null);
+		}
+	}
+};
+
+/**
+ * Function: selectCells
+ * 
+ * Selects the given edge after adding a new connection. The target argument
+ * contains the target vertex if one has been inserted.
+ */
+mxConnectionHandler.prototype.selectCells = function(edge, target)
+{
+	this.graph.setSelectionCell(edge);
+};
+
+/**
+ * Function: insertEdge
+ * 
+ * Creates, inserts and returns the new edge for the given parameters. This
+ * implementation does only use <createEdge> if <factoryMethod> is defined,
+ * otherwise <mxGraph.insertEdge> will be used.
+ */
+mxConnectionHandler.prototype.insertEdge = function(parent, id, value, source, target, style)
+{
+	if (this.factoryMethod == null)
+	{
+		return this.graph.insertEdge(parent, id, value, source, target, style);
+	}
+	else
+	{
+		var edge = this.createEdge(value, source, target, style);
+		edge = this.graph.addEdge(edge, parent, source, target);
+		
+		return edge;
+	}
+};
+
+/**
+ * Function: createTargetVertex
+ * 
+ * Hook method for creating new vertices on the fly if no target was
+ * under the mouse. This is only called if <createTarget> is true and
+ * returns null.
+ * 
+ * Parameters:
+ * 
+ * evt - Mousedown event of the connect gesture.
+ * source - <mxCell> that represents the source terminal.
+ */
+mxConnectionHandler.prototype.createTargetVertex = function(evt, source)
+{
+	// Uses the first non-relative source
+	var geo = this.graph.getCellGeometry(source);
+	
+	while (geo != null && geo.relative)
+	{
+		source = this.graph.getModel().getParent(source);
+		geo = this.graph.getCellGeometry(source);
+	}
+	
+	var clone = this.graph.cloneCells([source])[0];
+	var geo = this.graph.getModel().getGeometry(clone);
+	
+	if (geo != null)
+	{
+		var t = this.graph.view.translate;
+		var s = this.graph.view.scale;
+		var point = new mxPoint(this.currentPoint.x / s - t.x, this.currentPoint.y / s - t.y);
+		geo.x = Math.round(point.x - geo.width / 2 - this.graph.panDx / s);
+		geo.y = Math.round(point.y - geo.height / 2 - this.graph.panDy / s);
+
+		// Aligns with source if within certain tolerance
+		var tol = this.getAlignmentTolerance();
+		
+		if (tol > 0)
+		{
+			var sourceState = this.graph.view.getState(source);
+			
+			if (sourceState != null)
+			{
+				var x = sourceState.x / s - t.x;
+				var y = sourceState.y / s - t.y;
+				
+				if (Math.abs(x - geo.x) <= tol)
+				{
+					geo.x = Math.round(x);
+				}
+				
+				if (Math.abs(y - geo.y) <= tol)
+				{
+					geo.y = Math.round(y);
+				}
+			}
+		}
+	}
+
+	return clone;		
+};
+
+/**
+ * Function: getAlignmentTolerance
+ * 
+ * Returns the tolerance for aligning new targets to sources. This returns the grid size / 2.
+ */
+mxConnectionHandler.prototype.getAlignmentTolerance = function(evt)
+{
+	return (this.graph.isGridEnabled()) ? this.graph.gridSize / 2 : this.graph.tolerance;
+};
+
+/**
+ * Function: createEdge
+ * 
+ * Creates and returns a new edge using <factoryMethod> if one exists. If
+ * no factory method is defined, then a new default edge is returned. The
+ * source and target arguments are informal, the actual connection is
+ * setup later by the caller of this function.
+ * 
+ * Parameters:
+ * 
+ * value - Value to be used for creating the edge.
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ * style - Optional style from the preview edge.
+ */
+mxConnectionHandler.prototype.createEdge = function(value, source, target, style)
+{
+	var edge = null;
+	
+	// Creates a new edge using the factoryMethod
+	if (this.factoryMethod != null)
+	{
+		edge = this.factoryMethod(source, target, style);
+	}
+	
+	if (edge == null)
+	{
+		edge = new mxCell(value || '');
+		edge.setEdge(true);
+		edge.setStyle(style);
+		
+		var geo = new mxGeometry();
+		geo.relative = true;
+		edge.setGeometry(geo);
+	}
+
+	return edge;
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the handler and all its resources and DOM nodes. This should be
+ * called on all instances. It is called automatically for the built-in
+ * instance created for each <mxGraph>.
+ */
+mxConnectionHandler.prototype.destroy = function()
+{
+	this.graph.removeMouseListener(this);
+	
+	if (this.shape != null)
+	{
+		this.shape.destroy();
+		this.shape = null;
+	}
+	
+	if (this.marker != null)
+	{
+		this.marker.destroy();
+		this.marker = null;
+	}
+
+	if (this.constraintHandler != null)
+	{
+		this.constraintHandler.destroy();
+		this.constraintHandler = null;
+	}
+
+	if (this.changeHandler != null)
+	{
+		this.graph.getModel().removeListener(this.changeHandler);
+		this.graph.getView().removeListener(this.changeHandler);
+		this.changeHandler = null;
+	}
+	
+	if (this.drillHandler != null)
+	{
+		this.graph.removeListener(this.drillHandler);
+		this.graph.getView().removeListener(this.drillHandler);
+		this.drillHandler = null;
+	}
+	
+	if (this.escapeHandler != null)
+	{
+		this.graph.removeListener(this.escapeHandler);
+		this.escapeHandler = null;
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxConstraintHandler
+ *
+ * Handles constraints on connection targets. This class is in charge of
+ * showing fixed points when the mouse is over a vertex and handles constraints
+ * to establish new connections.
+ *
+ * Constructor: mxConstraintHandler
+ *
+ * Constructs an new constraint handler.
+ * 
+ * Parameters:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ * factoryMethod - Optional function to create the edge. The function takes
+ * the source and target <mxCell> as the first and second argument and
+ * returns the <mxCell> that represents the new edge.
+ */
+function mxConstraintHandler(graph)
+{
+	this.graph = graph;
+	
+	// Adds a graph model listener to update the current focus on changes
+	this.resetHandler = mxUtils.bind(this, function(sender, evt)
+	{
+		if (this.currentFocus != null && this.graph.view.getState(this.currentFocus.cell) == null)
+		{
+			this.reset();
+		}
+		else
+		{
+			this.redraw();
+		}
+	});
+	
+	this.graph.model.addListener(mxEvent.CHANGE, this.resetHandler);
+	this.graph.view.addListener(mxEvent.SCALE, this.resetHandler);
+	this.graph.addListener(mxEvent.ROOT, this.resetHandler);
+};
+
+/**
+ * Variable: pointImage
+ * 
+ * <mxImage> to be used as the image for fixed connection points.
+ */
+mxConstraintHandler.prototype.pointImage = new mxImage(mxClient.imageBasePath + '/point.gif', 5, 5);
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxConstraintHandler.prototype.graph = null;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies if events are handled. Default is true.
+ */
+mxConstraintHandler.prototype.enabled = true;
+
+/**
+ * Variable: highlightColor
+ * 
+ * Specifies the color for the highlight. Default is <mxConstants.DEFAULT_VALID_COLOR>.
+ */
+mxConstraintHandler.prototype.highlightColor = mxConstants.DEFAULT_VALID_COLOR;
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxConstraintHandler.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+	
+/**
+ * Function: setEnabled
+ * 
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ * 
+ * Parameters:
+ * 
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxConstraintHandler.prototype.setEnabled = function(enabled)
+{
+	this.enabled = enabled;
+};
+
+/**
+ * Function: reset
+ * 
+ * Resets the state of this handler.
+ */
+mxConstraintHandler.prototype.reset = function()
+{
+	if (this.focusIcons != null)
+	{
+		for (var i = 0; i < this.focusIcons.length; i++)
+		{
+			this.focusIcons[i].destroy();
+		}
+		
+		this.focusIcons = null;
+	}
+	
+	if (this.focusHighlight != null)
+	{
+		this.focusHighlight.destroy();
+		this.focusHighlight = null;
+	}
+	
+	this.currentConstraint = null;
+	this.currentFocusArea = null;
+	this.currentPoint = null;
+	this.currentFocus = null;
+	this.focusPoints = null;
+};
+
+/**
+ * Function: getTolerance
+ * 
+ * Returns the tolerance to be used for intersecting connection points. This
+ * implementation returns <mxGraph.tolerance>.
+ * 
+ * Parameters:
+ * 
+ * me - <mxMouseEvent> whose tolerance should be returned.
+ */
+mxConstraintHandler.prototype.getTolerance = function(me)
+{
+	return this.graph.getTolerance();
+};
+
+/**
+ * Function: getImageForConstraint
+ * 
+ * Returns the tolerance to be used for intersecting connection points.
+ */
+mxConstraintHandler.prototype.getImageForConstraint = function(state, constraint, point)
+{
+	return this.pointImage;
+};
+
+/**
+ * Function: isEventIgnored
+ * 
+ * Returns true if the given <mxMouseEvent> should be ignored in <update>. This
+ * implementation always returns false.
+ */
+mxConstraintHandler.prototype.isEventIgnored = function(me, source)
+{
+	return false;
+};
+
+/**
+ * Function: isStateIgnored
+ * 
+ * Returns true if the given state should be ignored. This always returns false.
+ */
+mxConstraintHandler.prototype.isStateIgnored = function(state, source)
+{
+	return false;
+};
+
+/**
+ * Function: destroyIcons
+ * 
+ * Destroys the <focusIcons> if they exist.
+ */
+mxConstraintHandler.prototype.destroyIcons = function()
+{
+	if (this.focusIcons != null)
+	{
+		for (var i = 0; i < this.focusIcons.length; i++)
+		{
+			this.focusIcons[i].destroy();
+		}
+		
+		this.focusIcons = null;
+		this.focusPoints = null;
+	}
+};
+
+/**
+ * Function: destroyFocusHighlight
+ * 
+ * Destroys the <focusHighlight> if one exists.
+ */
+mxConstraintHandler.prototype.destroyFocusHighlight = function()
+{
+	if (this.focusHighlight != null)
+	{
+		this.focusHighlight.destroy();
+		this.focusHighlight = null;
+	}
+};
+
+/**
+ * Function: isKeepFocusEvent
+ * 
+ * Returns true if the current focused state should not be changed for the given event.
+ * This returns true if shift and alt are pressed.
+ */
+mxConstraintHandler.prototype.isKeepFocusEvent = function(me)
+{
+	return mxEvent.isShiftDown(me.getEvent());
+};
+
+/**
+ * Function: getCellForEvent
+ * 
+ * Returns the cell for the given event.
+ */
+mxConstraintHandler.prototype.getCellForEvent = function(me, point)
+{
+	var cell = me.getCell();
+	
+	// Gets cell under actual point if different from event location
+	if (cell == null && point != null && (me.getGraphX() != point.x || me.getGraphY() != point.y))
+	{
+		cell = this.graph.getCellAt(point.x, point.y);
+	}
+	
+	// Uses connectable parent vertex if one exists
+	if (cell != null && !this.graph.isCellConnectable(cell))
+	{
+		var parent = this.graph.getModel().getParent(cell);
+		
+		if (this.graph.getModel().isVertex(parent) && this.graph.isCellConnectable(parent))
+		{
+			cell = parent;
+		}
+	}
+	
+	return cell;
+};
+
+/**
+ * Function: update
+ * 
+ * Updates the state of this handler based on the given <mxMouseEvent>.
+ * Source is a boolean indicating if the cell is a source or target.
+ */
+mxConstraintHandler.prototype.update = function(me, source, existingEdge, point)
+{
+	if (this.isEnabled() && !this.isEventIgnored(me))
+	{
+		// Lazy installation of mouseleave handler
+		if (this.mouseleaveHandler == null && this.graph.container != null)
+		{
+			this.mouseleaveHandler = mxUtils.bind(this, function()
+			{
+				this.reset();
+			});
+
+			mxEvent.addListener(this.graph.container, 'mouseleave', this.resetHandler);	
+		}
+		
+		var tol = this.getTolerance(me);
+		var x = (point != null) ? point.x : me.getGraphX();
+		var y = (point != null) ? point.y : me.getGraphY();
+		var grid = new mxRectangle(x - tol, y - tol, 2 * tol, 2 * tol);
+		var mouse = new mxRectangle(me.getGraphX() - tol, me.getGraphY() - tol, 2 * tol, 2 * tol);
+		var state = this.graph.view.getState(this.getCellForEvent(me, point));
+
+		// Keeps focus icons visible while over vertex bounds and no other cell under mouse or shift is pressed
+		if (!this.isKeepFocusEvent(me) && (this.currentFocusArea == null || this.currentFocus == null ||
+			(state != null) || !this.graph.getModel().isVertex(this.currentFocus.cell) ||
+			!mxUtils.intersects(this.currentFocusArea, mouse)) && (state != this.currentFocus))
+		{
+			this.currentFocusArea = null;
+			this.currentFocus = null;
+			this.setFocus(me, state, source);
+		}
+
+		this.currentConstraint = null;
+		this.currentPoint = null;
+		var minDistSq = null;
+		
+		if (this.focusIcons != null && this.constraints != null &&
+			(state == null || this.currentFocus == state))
+		{
+			var cx = mouse.getCenterX();
+			var cy = mouse.getCenterY();
+			
+			for (var i = 0; i < this.focusIcons.length; i++)
+			{
+				var dx = cx - this.focusIcons[i].bounds.getCenterX();
+				var dy = cy - this.focusIcons[i].bounds.getCenterY();
+				var tmp = dx * dx + dy * dy;
+				
+				if ((this.intersects(this.focusIcons[i], mouse, source, existingEdge) || (point != null &&
+					this.intersects(this.focusIcons[i], grid, source, existingEdge))) &&
+					(minDistSq == null || tmp < minDistSq))
+				{
+					this.currentConstraint = this.constraints[i];
+					this.currentPoint = this.focusPoints[i];
+					minDistSq = tmp;
+					
+					var tmp = this.focusIcons[i].bounds.clone();
+					tmp.grow(mxConstants.HIGHLIGHT_SIZE);
+					
+					if (mxClient.IS_IE)
+					{
+						tmp.grow(1);
+						tmp.width -= 1;
+						tmp.height -= 1;
+					}
+					
+					if (this.focusHighlight == null)
+					{
+						var hl = this.createHighlightShape();
+						hl.dialect = (this.graph.dialect == mxConstants.DIALECT_SVG) ?
+								mxConstants.DIALECT_SVG : mxConstants.DIALECT_VML;
+						hl.pointerEvents = false;
+
+						hl.init(this.graph.getView().getOverlayPane());
+						this.focusHighlight = hl;
+						
+						var getState = mxUtils.bind(this, function()
+						{
+							return (this.currentFocus != null) ? this.currentFocus : state;
+						});
+	
+						mxEvent.redirectMouseEvents(hl.node, this.graph, getState);
+					}
+
+					this.focusHighlight.bounds = tmp;
+					this.focusHighlight.redraw();
+				}
+			}
+		}
+		
+		if (this.currentConstraint == null)
+		{
+			this.destroyFocusHighlight();
+		}
+	}
+	else
+	{
+		this.currentConstraint = null;
+		this.currentFocus = null;
+		this.currentPoint = null;
+	}
+};
+
+/**
+ * Function: redraw
+ * 
+ * Transfers the focus to the given state as a source or target terminal. If
+ * the handler is not enabled then the outline is painted, but the constraints
+ * are ignored.
+ */
+mxConstraintHandler.prototype.redraw = function()
+{
+	if (this.currentFocus != null && this.constraints != null && this.focusIcons != null)
+	{
+		var state = this.graph.view.getState(this.currentFocus.cell);
+		this.currentFocus = state;
+		this.currentFocusArea = new mxRectangle(state.x, state.y, state.width, state.height);
+		
+		for (var i = 0; i < this.constraints.length; i++)
+		{
+			var cp = this.graph.getConnectionPoint(state, this.constraints[i]);
+			var img = this.getImageForConstraint(state, this.constraints[i], cp);
+
+			var bounds = new mxRectangle(Math.round(cp.x - img.width / 2),
+				Math.round(cp.y - img.height / 2), img.width, img.height);
+			this.focusIcons[i].bounds = bounds;
+			this.focusIcons[i].redraw();
+			this.currentFocusArea.add(this.focusIcons[i].bounds);
+			this.focusPoints[i] = cp;
+		}
+	}	
+};
+
+/**
+ * Function: setFocus
+ * 
+ * Transfers the focus to the given state as a source or target terminal. If
+ * the handler is not enabled then the outline is painted, but the constraints
+ * are ignored.
+ */
+mxConstraintHandler.prototype.setFocus = function(me, state, source)
+{
+	this.constraints = (state != null && !this.isStateIgnored(state, source) &&
+		this.graph.isCellConnectable(state.cell)) ? ((this.isEnabled()) ?
+		(this.graph.getAllConnectionConstraints(state, source) || []) : []) : null;
+
+	// Only uses cells which have constraints
+	if (this.constraints != null)
+	{
+		this.currentFocus = state;
+		this.currentFocusArea = new mxRectangle(state.x, state.y, state.width, state.height);
+		
+		if (this.focusIcons != null)
+		{
+			for (var i = 0; i < this.focusIcons.length; i++)
+			{
+				this.focusIcons[i].destroy();
+			}
+			
+			this.focusIcons = null;
+			this.focusPoints = null;
+		}
+		
+		this.focusPoints = [];
+		this.focusIcons = [];
+		
+		for (var i = 0; i < this.constraints.length; i++)
+		{
+			var cp = this.graph.getConnectionPoint(state, this.constraints[i]);
+			var img = this.getImageForConstraint(state, this.constraints[i], cp);
+
+			var src = img.src;
+			var bounds = new mxRectangle(Math.round(cp.x - img.width / 2),
+				Math.round(cp.y - img.height / 2), img.width, img.height);
+			var icon = new mxImageShape(bounds, src);
+			icon.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
+					mxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG;
+			icon.preserveImageAspect = false;
+			icon.init(this.graph.getView().getDecoratorPane());
+			
+			// Fixes lost event tracking for images in quirks / IE8 standards
+			if (mxClient.IS_QUIRKS || document.documentMode == 8)
+			{
+				mxEvent.addListener(icon.node, 'dragstart', function(evt)
+				{
+					mxEvent.consume(evt);
+					
+					return false;
+				});
+			}
+			
+			// Move the icon behind all other overlays
+			if (icon.node.previousSibling != null)
+			{
+				icon.node.parentNode.insertBefore(icon.node, icon.node.parentNode.firstChild);
+			}
+
+			var getState = mxUtils.bind(this, function()
+			{
+				return (this.currentFocus != null) ? this.currentFocus : state;
+			});
+			
+			icon.redraw();
+
+			mxEvent.redirectMouseEvents(icon.node, this.graph, getState);
+			this.currentFocusArea.add(icon.bounds);
+			this.focusIcons.push(icon);
+			this.focusPoints.push(cp);
+		}
+		
+		this.currentFocusArea.grow(this.getTolerance(me));
+	}
+	else
+	{
+		this.destroyIcons();
+		this.destroyFocusHighlight();
+	}
+};
+
+/**
+ * Function: createHighlightShape
+ * 
+ * Create the shape used to paint the highlight.
+ * 
+ * Returns true if the given icon intersects the given point.
+ */
+mxConstraintHandler.prototype.createHighlightShape = function()
+{
+	var hl = new mxRectangleShape(null, this.highlightColor, this.highlightColor, mxConstants.HIGHLIGHT_STROKEWIDTH);
+	hl.opacity = mxConstants.HIGHLIGHT_OPACITY;
+	
+	return hl;
+};
+
+/**
+ * Function: intersects
+ * 
+ * Returns true if the given icon intersects the given rectangle.
+ */
+mxConstraintHandler.prototype.intersects = function(icon, mouse, source, existingEdge)
+{
+	return mxUtils.intersects(icon.bounds, mouse);
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroy this handler.
+ */
+mxConstraintHandler.prototype.destroy = function()
+{
+	this.reset();
+	
+	if (this.resetHandler != null)
+	{
+		this.graph.model.removeListener(this.resetHandler);
+		this.graph.view.removeListener(this.resetHandler);
+		this.graph.removeListener(this.resetHandler);
+		this.resetHandler = null;
+	}
+	
+	if (this.mouseleaveHandler != null && this.graph.container != null)
+	{
+		mxEvent.removeListener(this.graph.container, 'mouseleave', this.mouseleaveHandler);
+		this.mouseleaveHandler = null;
+	}
+};
+/**
+ * Copyright (c) 2006-2016, JGraph Ltd
+ * Copyright (c) 2006-2016, Gaudenz Alder
+ */
+/**
+ * Class: mxRubberband
+ * 
+ * Event handler that selects rectangular regions. This is not built-into
+ * <mxGraph>. To enable rubberband selection in a graph, use the following code.
+ * 
+ * Example:
+ * 
+ * (code)
+ * var rubberband = new mxRubberband(graph);
+ * (end)
+ * 
+ * Constructor: mxRubberband
+ * 
+ * Constructs an event handler that selects rectangular regions in the graph
+ * using rubberband selection.
+ */
+function mxRubberband(graph)
+{
+	if (graph != null)
+	{
+		this.graph = graph;
+		this.graph.addMouseListener(this);
+
+		// Handles force rubberband event
+		this.forceRubberbandHandler = mxUtils.bind(this, function(sender, evt)
+		{
+			var evtName = evt.getProperty('eventName');
+			var me = evt.getProperty('event');
+			
+			if (evtName == mxEvent.MOUSE_DOWN && this.isForceRubberbandEvent(me))
+			{
+				var offset = mxUtils.getOffset(this.graph.container);
+				var origin = mxUtils.getScrollOrigin(this.graph.container);
+				origin.x -= offset.x;
+				origin.y -= offset.y;
+				this.start(me.getX() + origin.x, me.getY() + origin.y);
+				me.consume(false);
+			}
+		});
+		
+		this.graph.addListener(mxEvent.FIRE_MOUSE_EVENT, this.forceRubberbandHandler);
+		
+		// Repaints the marquee after autoscroll
+		this.panHandler = mxUtils.bind(this, function()
+		{
+			this.repaint();
+		});
+		
+		this.graph.addListener(mxEvent.PAN, this.panHandler);
+		
+		// Does not show menu if any touch gestures take place after the trigger
+		this.gestureHandler = mxUtils.bind(this, function(sender, eo)
+		{
+			if (this.first != null)
+			{
+				this.reset();
+			}
+		});
+		
+		this.graph.addListener(mxEvent.GESTURE, this.gestureHandler);
+		
+		// Automatic deallocation of memory
+		if (mxClient.IS_IE)
+		{
+			mxEvent.addListener(window, 'unload',
+				mxUtils.bind(this, function()
+				{
+					this.destroy();
+				})
+			);
+		}
+	}
+};
+
+/**
+ * Variable: defaultOpacity
+ * 
+ * Specifies the default opacity to be used for the rubberband div. Default
+ * is 20.
+ */
+mxRubberband.prototype.defaultOpacity = 20;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies if events are handled. Default is true.
+ */
+mxRubberband.prototype.enabled = true;
+
+/**
+ * Variable: div
+ * 
+ * Holds the DIV element which is currently visible.
+ */
+mxRubberband.prototype.div = null;
+
+/**
+ * Variable: sharedDiv
+ * 
+ * Holds the DIV element which is used to display the rubberband.
+ */
+mxRubberband.prototype.sharedDiv = null;
+
+/**
+ * Variable: currentX
+ * 
+ * Holds the value of the x argument in the last call to <update>.
+ */
+mxRubberband.prototype.currentX = 0;
+
+/**
+ * Variable: currentY
+ * 
+ * Holds the value of the y argument in the last call to <update>.
+ */
+mxRubberband.prototype.currentY = 0;
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns true if events are handled. This implementation returns
+ * <enabled>.
+ */
+mxRubberband.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+		
+/**
+ * Function: setEnabled
+ * 
+ * Enables or disables event handling. This implementation updates
+ * <enabled>.
+ */
+mxRubberband.prototype.setEnabled = function(enabled)
+{
+	this.enabled = enabled;
+};
+
+/**
+ * Function: isForceRubberbandEvent
+ * 
+ * Returns true if the given <mxMouseEvent> should start rubberband selection.
+ * This implementation returns true if the alt key is pressed.
+ */
+mxRubberband.prototype.isForceRubberbandEvent = function(me)
+{
+	return mxEvent.isAltDown(me.getEvent());
+};
+
+/**
+ * Function: mouseDown
+ * 
+ * Handles the event by initiating a rubberband selection. By consuming the
+ * event all subsequent events of the gesture are redirected to this
+ * handler.
+ */
+mxRubberband.prototype.mouseDown = function(sender, me)
+{
+	if (!me.isConsumed() && this.isEnabled() && this.graph.isEnabled() &&
+		me.getState() == null && !mxEvent.isMultiTouchEvent(me.getEvent()))
+	{
+		var offset = mxUtils.getOffset(this.graph.container);
+		var origin = mxUtils.getScrollOrigin(this.graph.container);
+		origin.x -= offset.x;
+		origin.y -= offset.y;
+		this.start(me.getX() + origin.x, me.getY() + origin.y);
+
+		// Does not prevent the default for this event so that the
+		// event processing chain is still executed even if we start
+		// rubberbanding. This is required eg. in ExtJs to hide the
+		// current context menu. In mouseMove we'll make sure we're
+		// not selecting anything while we're rubberbanding.
+		me.consume(false);
+	}
+};
+
+/**
+ * Function: start
+ * 
+ * Sets the start point for the rubberband selection.
+ */
+mxRubberband.prototype.start = function(x, y)
+{
+	this.first = new mxPoint(x, y);
+
+	var container = this.graph.container;
+	
+	function createMouseEvent(evt)
+	{
+		var me = new mxMouseEvent(evt);
+		var pt = mxUtils.convertPoint(container, me.getX(), me.getY());
+		
+		me.graphX = pt.x;
+		me.graphY = pt.y;
+		
+		return me;
+	};
+
+	this.dragHandler = mxUtils.bind(this, function(evt)
+	{
+		this.mouseMove(this.graph, createMouseEvent(evt));
+	});
+
+	this.dropHandler = mxUtils.bind(this, function(evt)
+	{
+		this.mouseUp(this.graph, createMouseEvent(evt));
+	});
+
+	// Workaround for rubberband stopping if the mouse leaves the container in Firefox
+	if (mxClient.IS_FF)
+	{
+		mxEvent.addGestureListeners(document, null, this.dragHandler, this.dropHandler);
+	}
+};
+
+/**
+ * Function: mouseMove
+ * 
+ * Handles the event by updating therubberband selection.
+ */
+mxRubberband.prototype.mouseMove = function(sender, me)
+{
+	if (!me.isConsumed() && this.first != null)
+	{
+		var origin = mxUtils.getScrollOrigin(this.graph.container);
+		var offset = mxUtils.getOffset(this.graph.container);
+		origin.x -= offset.x;
+		origin.y -= offset.y;
+		var x = me.getX() + origin.x;
+		var y = me.getY() + origin.y;
+		var dx = this.first.x - x;
+		var dy = this.first.y - y;
+		var tol = this.graph.tolerance;
+		
+		if (this.div != null || Math.abs(dx) > tol ||  Math.abs(dy) > tol)
+		{
+			if (this.div == null)
+			{
+				this.div = this.createShape();
+			}
+			
+			// Clears selection while rubberbanding. This is required because
+			// the event is not consumed in mouseDown.
+			mxUtils.clearSelection();
+			
+			this.update(x, y);
+			me.consume();
+		}
+	}
+};
+
+/**
+ * Function: createShape
+ * 
+ * Creates the rubberband selection shape.
+ */
+mxRubberband.prototype.createShape = function()
+{
+	if (this.sharedDiv == null)
+	{
+		this.sharedDiv = document.createElement('div');
+		this.sharedDiv.className = 'mxRubberband';
+		mxUtils.setOpacity(this.sharedDiv, this.defaultOpacity);
+	}
+
+	this.graph.container.appendChild(this.sharedDiv);
+		
+	return this.sharedDiv;
+};
+
+/**
+ * Function: isActive
+ * 
+ * Returns true if this handler is active.
+ */
+mxRubberband.prototype.isActive = function(sender, me)
+{
+	return this.div != null && this.div.style.display != 'none';
+};
+
+/**
+ * Function: mouseUp
+ * 
+ * Handles the event by selecting the region of the rubberband using
+ * <mxGraph.selectRegion>.
+ */
+mxRubberband.prototype.mouseUp = function(sender, me)
+{
+	var active = this.isActive();
+	this.reset();
+	
+	if (active)
+	{
+		this.execute(me.getEvent());
+		me.consume();
+	}
+};
+
+/**
+ * Function: execute
+ * 
+ * Resets the state of this handler and selects the current region
+ * for the given event.
+ */
+mxRubberband.prototype.execute = function(evt)
+{
+	var rect = new mxRectangle(this.x, this.y, this.width, this.height);
+	this.graph.selectRegion(rect, evt);
+};
+
+/**
+ * Function: reset
+ * 
+ * Resets the state of the rubberband selection.
+ */
+mxRubberband.prototype.reset = function()
+{
+	if (this.div != null)
+	{
+		this.div.parentNode.removeChild(this.div);
+	}
+
+	mxEvent.removeGestureListeners(document, null, this.dragHandler, this.dropHandler);
+	this.dragHandler = null;
+	this.dropHandler = null;
+	
+	this.currentX = 0;
+	this.currentY = 0;
+	this.first = null;
+	this.div = null;
+};
+
+/**
+ * Function: update
+ * 
+ * Sets <currentX> and <currentY> and calls <repaint>.
+ */
+mxRubberband.prototype.update = function(x, y)
+{
+	this.currentX = x;
+	this.currentY = y;
+	
+	this.repaint();
+};
+
+/**
+ * Function: repaint
+ * 
+ * Computes the bounding box and updates the style of the <div>.
+ */
+mxRubberband.prototype.repaint = function()
+{
+	if (this.div != null)
+	{
+		var x = this.currentX - this.graph.panDx;
+		var y = this.currentY - this.graph.panDy;
+		
+		this.x = Math.min(this.first.x, x);
+		this.y = Math.min(this.first.y, y);
+		this.width = Math.max(this.first.x, x) - this.x;
+		this.height =  Math.max(this.first.y, y) - this.y;
+
+		var dx = (mxClient.IS_VML) ? this.graph.panDx : 0;
+		var dy = (mxClient.IS_VML) ? this.graph.panDy : 0;
+		
+		this.div.style.left = (this.x + dx) + 'px';
+		this.div.style.top = (this.y + dy) + 'px';
+		this.div.style.width = Math.max(1, this.width) + 'px';
+		this.div.style.height = Math.max(1, this.height) + 'px';
+	}
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the handler and all its resources and DOM nodes. This does
+ * normally not need to be called, it is called automatically when the
+ * window unloads.
+ */
+mxRubberband.prototype.destroy = function()
+{
+	if (!this.destroyed)
+	{
+		this.destroyed = true;
+		this.graph.removeMouseListener(this);
+		this.graph.removeListener(this.forceRubberbandHandler);
+		this.graph.removeListener(this.panHandler);
+		this.reset();
+		
+		if (this.sharedDiv != null)
+		{
+			this.sharedDiv = null;
+		}
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxHandle
+ * 
+ * Implements a single custom handle for vertices.
+ * 
+ * Constructor: mxHandle
+ * 
+ * Constructs a new handle for the given state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> of the cell to be handled.
+ */
+function mxHandle(state, cursor, image)
+{
+	this.graph = state.view.graph;
+	this.state = state;
+	this.cursor = (cursor != null) ? cursor : this.cursor;
+	this.image = (image != null) ? image : this.image;
+	this.init();
+};
+
+/**
+ * Variable: cursor
+ * 
+ * Specifies the cursor to be used for this handle. Default is 'default'.
+ */
+mxHandle.prototype.cursor = 'default';
+
+/**
+ * Variable: image
+ * 
+ * Specifies the <mxImage> to be used to render the handle. Default is null.
+ */
+mxHandle.prototype.image = null;
+
+/**
+ * Variable: image
+ * 
+ * Specifies the <mxImage> to be used to render the handle. Default is null.
+ */
+mxHandle.prototype.ignoreGrid = false;
+
+/**
+ * Function: getPosition
+ * 
+ * Hook for subclassers to return the current position of the handle.
+ */
+mxHandle.prototype.getPosition = function(bounds) { };
+
+/**
+ * Function: setPosition
+ * 
+ * Hooks for subclassers to update the style in the <state>.
+ */
+mxHandle.prototype.setPosition = function(bounds, pt, me) { };
+
+/**
+ * Function: execute
+ * 
+ * Hook for subclassers to execute the handle.
+ */
+mxHandle.prototype.execute = function() { };
+
+/**
+ * Function: copyStyle
+ * 
+ * Sets the cell style with the given name to the corresponding value in <state>.
+ */
+mxHandle.prototype.copyStyle = function(key)
+{
+	this.graph.setCellStyles(key, this.state.style[key], [this.state.cell]);
+};
+
+/**
+ * Function: processEvent
+ * 
+ * Processes the given <mxMouseEvent> and invokes <setPosition>.
+ */
+mxHandle.prototype.processEvent = function(me)
+{
+	var scale = this.graph.view.scale;
+	var tr = this.graph.view.translate;
+	var pt = new mxPoint(me.getGraphX() / scale - tr.x, me.getGraphY() / scale - tr.y);
+	
+	// Center shape on mouse cursor
+	if (this.shape != null && this.shape.bounds != null)
+	{
+		pt.x -= this.shape.bounds.width / scale / 4;
+		pt.y -= this.shape.bounds.height / scale / 4;
+	}
+
+	// Snaps to grid for the rotated position then applies the rotation for the direction after that
+	var alpha1 = -mxUtils.toRadians(this.getRotation());
+	var alpha2 = -mxUtils.toRadians(this.getTotalRotation()) - alpha1;
+	pt = this.flipPoint(this.rotatePoint(this.snapPoint(this.rotatePoint(pt, alpha1),
+			this.ignoreGrid || !this.graph.isGridEnabledEvent(me.getEvent())), alpha2));
+	this.setPosition(this.state.getPaintBounds(), pt, me);
+	this.positionChanged();
+	this.redraw();
+};
+
+/**
+ * Function: positionChanged
+ * 
+ * Called after <setPosition> has been called in <processEvent>. This repaints
+ * the state using <mxCellRenderer>.
+ */
+mxHandle.prototype.positionChanged = function()
+{
+	if (this.state.text != null)
+	{
+		this.state.text.apply(this.state);
+	}
+	
+	if (this.state.shape != null)
+	{
+		this.state.shape.apply(this.state);
+	}
+	
+	// Needed to force update of text bounds
+	this.state.unscaledWidth = null;
+	this.graph.cellRenderer.redraw(this.state, true);
+};
+
+/**
+ * Function: getRotation
+ * 
+ * Returns the rotation defined in the style of the cell.
+ */
+mxHandle.prototype.getRotation = function()
+{
+	if (this.state.shape != null)
+	{
+		return this.state.shape.getRotation();
+	}
+	
+	return 0;
+};
+
+/**
+ * Function: getTotalRotation
+ * 
+ * Returns the rotation from the style and the rotation from the direction of
+ * the cell.
+ */
+mxHandle.prototype.getTotalRotation = function()
+{
+	if (this.state.shape != null)
+	{
+		return this.state.shape.getShapeRotation();
+	}
+	
+	return 0;
+};
+
+/**
+ * Function: init
+ * 
+ * Creates and initializes the shapes required for this handle.
+ */
+mxHandle.prototype.init = function()
+{
+	var html = this.isHtmlRequired();
+	
+	if (this.image != null)
+	{
+		this.shape = new mxImageShape(new mxRectangle(0, 0, this.image.width, this.image.height), this.image.src);
+		this.shape.preserveImageAspect = false;
+	}
+	else
+	{
+		this.shape = this.createShape(html);
+	}
+	
+	this.initShape(html);
+};
+
+/**
+ * Function: createShape
+ * 
+ * Creates and returns the shape for this handle.
+ */
+mxHandle.prototype.createShape = function(html)
+{
+	var bounds = new mxRectangle(0, 0, mxConstants.HANDLE_SIZE, mxConstants.HANDLE_SIZE);
+	
+	return new mxRectangleShape(bounds, mxConstants.HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR);
+};
+
+/**
+ * Function: initShape
+ * 
+ * Initializes <shape> and sets its cursor.
+ */
+mxHandle.prototype.initShape = function(html)
+{
+	if (html && this.shape.isHtmlAllowed())
+	{
+		this.shape.dialect = mxConstants.DIALECT_STRICTHTML;
+		this.shape.init(this.graph.container);
+	}
+	else
+	{
+		this.shape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? mxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG;
+		
+		if (this.cursor != null)
+		{
+			this.shape.init(this.graph.getView().getOverlayPane());
+		}
+	}
+
+	mxEvent.redirectMouseEvents(this.shape.node, this.graph, this.state);
+	this.shape.node.style.cursor = this.cursor;
+};
+
+/**
+ * Function: redraw
+ * 
+ * Renders the shape for this handle.
+ */
+mxHandle.prototype.redraw = function()
+{
+	if (this.shape != null && this.state.shape != null)
+	{
+		var pt = this.getPosition(this.state.getPaintBounds());
+		
+		if (pt != null)
+		{
+			var alpha = mxUtils.toRadians(this.getTotalRotation());
+			pt = this.rotatePoint(this.flipPoint(pt), alpha);
+	
+			var scale = this.graph.view.scale;
+			var tr = this.graph.view.translate;
+			this.shape.bounds.x = Math.floor((pt.x + tr.x) * scale - this.shape.bounds.width / 2);
+			this.shape.bounds.y = Math.floor((pt.y + tr.y) * scale - this.shape.bounds.height / 2);
+			
+			// Needed to force update of text bounds
+			this.shape.redraw();
+		}
+	}
+};
+
+/**
+ * Function: isHtmlRequired
+ * 
+ * Returns true if this handle should be rendered in HTML. This returns true if
+ * the text node is in the graph container.
+ */
+mxHandle.prototype.isHtmlRequired = function()
+{
+	return this.state.text != null && this.state.text.node.parentNode == this.graph.container;
+};
+
+/**
+ * Function: rotatePoint
+ * 
+ * Rotates the point by the given angle.
+ */
+mxHandle.prototype.rotatePoint = function(pt, alpha)
+{
+	var bounds = this.state.getCellBounds();
+	var cx = new mxPoint(bounds.getCenterX(), bounds.getCenterY());
+	var cos = Math.cos(alpha);
+	var sin = Math.sin(alpha); 
+
+	return mxUtils.getRotatedPoint(pt, cos, sin, cx);
+};
+
+/**
+ * Function: flipPoint
+ * 
+ * Flips the given point vertically and/or horizontally.
+ */
+mxHandle.prototype.flipPoint = function(pt)
+{
+	if (this.state.shape != null)
+	{
+		var bounds = this.state.getCellBounds();
+		
+		if (this.state.shape.flipH)
+		{
+			pt.x = 2 * bounds.x + bounds.width - pt.x;
+		}
+		
+		if (this.state.shape.flipV)
+		{
+			pt.y = 2 * bounds.y + bounds.height - pt.y;
+		}
+	}
+	
+	return pt;
+};
+
+/**
+ * Function: snapPoint
+ * 
+ * Snaps the given point to the grid if ignore is false. This modifies
+ * the given point in-place and also returns it.
+ */
+mxHandle.prototype.snapPoint = function(pt, ignore)
+{
+	if (!ignore)
+	{
+		pt.x = this.graph.snap(pt.x);
+		pt.y = this.graph.snap(pt.y);
+	}
+	
+	return pt;
+};
+
+/**
+ * Function: setVisible
+ * 
+ * Shows or hides this handle.
+ */
+mxHandle.prototype.setVisible = function(visible)
+{
+	if (this.shape != null && this.shape.node != null)
+	{
+		this.shape.node.style.display = (visible) ? '' : 'none';
+	}
+};
+
+/**
+ * Function: reset
+ * 
+ * Resets the state of this handle by setting its visibility to true.
+ */
+mxHandle.prototype.reset = function()
+{
+	this.setVisible(true);
+	this.state.style = this.graph.getCellStyle(this.state.cell);
+	this.positionChanged();
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys this handle.
+ */
+mxHandle.prototype.destroy = function()
+{
+	if (this.shape != null)
+	{
+		this.shape.destroy();
+		this.shape = null;
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxVertexHandler
+ * 
+ * Event handler for resizing cells. This handler is automatically created in
+ * <mxGraph.createHandler>.
+ * 
+ * Constructor: mxVertexHandler
+ * 
+ * Constructs an event handler that allows to resize vertices
+ * and groups.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> of the cell to be resized.
+ */
+function mxVertexHandler(state)
+{
+	if (state != null)
+	{
+		this.state = state;
+		this.init();
+		
+		// Handles escape keystrokes
+		this.escapeHandler = mxUtils.bind(this, function(sender, evt)
+		{
+			if (this.livePreview && this.index != null)
+			{
+				// Redraws the live preview
+				this.state.view.graph.cellRenderer.redraw(this.state, true);
+				
+				// Redraws connected edges
+				this.state.view.invalidate(this.state.cell);
+				this.state.invalid = false;
+				this.state.view.validate();
+			}
+			
+			this.reset();
+		});
+		
+		this.state.view.graph.addListener(mxEvent.ESCAPE, this.escapeHandler);
+	}
+};
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxVertexHandler.prototype.graph = null;
+
+/**
+ * Variable: state
+ * 
+ * Reference to the <mxCellState> being modified.
+ */
+mxVertexHandler.prototype.state = null;
+
+/**
+ * Variable: singleSizer
+ * 
+ * Specifies if only one sizer handle at the bottom, right corner should be
+ * used. Default is false.
+ */
+mxVertexHandler.prototype.singleSizer = false;
+
+/**
+ * Variable: index
+ * 
+ * Holds the index of the current handle.
+ */
+mxVertexHandler.prototype.index = null;
+
+/**
+ * Variable: allowHandleBoundsCheck
+ * 
+ * Specifies if the bounds of handles should be used for hit-detection in IE or
+ * if <tolerance> > 0. Default is true.
+ */
+mxVertexHandler.prototype.allowHandleBoundsCheck = true;
+
+/**
+ * Variable: handleImage
+ * 
+ * Optional <mxImage> to be used as handles. Default is null.
+ */
+mxVertexHandler.prototype.handleImage = null;
+
+/**
+ * Variable: tolerance
+ * 
+ * Optional tolerance for hit-detection in <getHandleForEvent>. Default is 0.
+ */
+mxVertexHandler.prototype.tolerance = 0;
+
+/**
+ * Variable: rotationEnabled
+ * 
+ * Specifies if a rotation handle should be visible. Default is false.
+ */
+mxVertexHandler.prototype.rotationEnabled = false;
+
+/**
+ * Variable: parentHighlightEnabled
+ * 
+ * Specifies if the parent should be highlighted if a child cell is selected.
+ * Default is false.
+ */
+mxVertexHandler.prototype.parentHighlightEnabled = false;
+
+/**
+ * Variable: rotationRaster
+ * 
+ * Specifies if rotation steps should be "rasterized" depening on the distance
+ * to the handle. Default is true.
+ */
+mxVertexHandler.prototype.rotationRaster = true;
+
+/**
+ * Variable: rotationCursor
+ * 
+ * Specifies the cursor for the rotation handle. Default is 'crosshair'.
+ */
+mxVertexHandler.prototype.rotationCursor = 'crosshair';
+
+/**
+ * Variable: livePreview
+ * 
+ * Specifies if resize should change the cell in-place. This is an experimental
+ * feature for non-touch devices. Default is false.
+ */
+mxVertexHandler.prototype.livePreview = false;
+
+/**
+ * Variable: manageSizers
+ * 
+ * Specifies if sizers should be hidden and spaced if the vertex is small.
+ * Default is false.
+ */
+mxVertexHandler.prototype.manageSizers = false;
+
+/**
+ * Variable: constrainGroupByChildren
+ * 
+ * Specifies if the size of groups should be constrained by the children.
+ * Default is false.
+ */
+mxVertexHandler.prototype.constrainGroupByChildren = false;
+
+/**
+ * Variable: rotationHandleVSpacing
+ * 
+ * Vertical spacing for rotation icon. Default is -16.
+ */
+mxVertexHandler.prototype.rotationHandleVSpacing = -16;
+
+/**
+ * Variable: horizontalOffset
+ * 
+ * The horizontal offset for the handles. This is updated in <redrawHandles>
+ * if <manageSizers> is true and the sizers are offset horizontally.
+ */
+mxVertexHandler.prototype.horizontalOffset = 0;
+
+/**
+ * Variable: verticalOffset
+ * 
+ * The horizontal offset for the handles. This is updated in <redrawHandles>
+ * if <manageSizers> is true and the sizers are offset vertically.
+ */
+mxVertexHandler.prototype.verticalOffset = 0;
+
+/**
+ * Function: init
+ * 
+ * Initializes the shapes required for this vertex handler.
+ */
+mxVertexHandler.prototype.init = function()
+{
+	this.graph = this.state.view.graph;
+	this.selectionBounds = this.getSelectionBounds(this.state);
+	this.bounds = new mxRectangle(this.selectionBounds.x, this.selectionBounds.y, this.selectionBounds.width, this.selectionBounds.height);
+	this.selectionBorder = this.createSelectionShape(this.bounds);
+	// VML dialect required here for event transparency in IE
+	this.selectionBorder.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+	this.selectionBorder.pointerEvents = false;
+	this.selectionBorder.rotation = Number(this.state.style[mxConstants.STYLE_ROTATION] || '0');
+	this.selectionBorder.init(this.graph.getView().getOverlayPane());
+	mxEvent.redirectMouseEvents(this.selectionBorder.node, this.graph, this.state);
+	
+	if (this.graph.isCellMovable(this.state.cell))
+	{
+		this.selectionBorder.setCursor(mxConstants.CURSOR_MOVABLE_VERTEX);
+	}
+
+	// Adds the sizer handles
+	if (mxGraphHandler.prototype.maxCells <= 0 || this.graph.getSelectionCount() < mxGraphHandler.prototype.maxCells)
+	{
+		var resizable = this.graph.isCellResizable(this.state.cell);
+		this.sizers = [];
+
+		if (resizable || (this.graph.isLabelMovable(this.state.cell) &&
+			this.state.width >= 2 && this.state.height >= 2))
+		{
+			var i = 0;
+
+			if (resizable)
+			{
+				if (!this.singleSizer)
+				{
+					this.sizers.push(this.createSizer('nw-resize', i++));
+					this.sizers.push(this.createSizer('n-resize', i++));
+					this.sizers.push(this.createSizer('ne-resize', i++));
+					this.sizers.push(this.createSizer('w-resize', i++));
+					this.sizers.push(this.createSizer('e-resize', i++));
+					this.sizers.push(this.createSizer('sw-resize', i++));
+					this.sizers.push(this.createSizer('s-resize', i++));
+				}
+				
+				this.sizers.push(this.createSizer('se-resize', i++));
+			}
+			
+			var geo = this.graph.model.getGeometry(this.state.cell);
+			
+			if (geo != null && !geo.relative && !this.graph.isSwimlane(this.state.cell) &&
+				this.graph.isLabelMovable(this.state.cell))
+			{
+				// Marks this as the label handle for getHandleForEvent
+				this.labelShape = this.createSizer(mxConstants.CURSOR_LABEL_HANDLE, mxEvent.LABEL_HANDLE, mxConstants.LABEL_HANDLE_SIZE, mxConstants.LABEL_HANDLE_FILLCOLOR);
+				this.sizers.push(this.labelShape);
+			}
+		}
+		else if (this.graph.isCellMovable(this.state.cell) && !this.graph.isCellResizable(this.state.cell) &&
+			this.state.width < 2 && this.state.height < 2)
+		{
+			this.labelShape = this.createSizer(mxConstants.CURSOR_MOVABLE_VERTEX,
+				mxEvent.LABEL_HANDLE, null, mxConstants.LABEL_HANDLE_FILLCOLOR);
+			this.sizers.push(this.labelShape);
+		}
+	}
+	
+	// Adds the rotation handler
+	if (this.isRotationHandleVisible())
+	{
+		this.rotationShape = this.createSizer(this.rotationCursor, mxEvent.ROTATION_HANDLE,
+			mxConstants.HANDLE_SIZE + 3, mxConstants.HANDLE_FILLCOLOR);
+		this.sizers.push(this.rotationShape);
+	}
+
+	this.customHandles = this.createCustomHandles();
+	this.redraw();
+	
+	if (this.constrainGroupByChildren)
+	{
+		this.updateMinBounds();
+	}
+};
+
+/**
+ * Function: isRotationHandleVisible
+ * 
+ * Returns true if the rotation handle should be showing.
+ */
+mxVertexHandler.prototype.isRotationHandleVisible = function()
+{
+	return this.graph.isEnabled() && this.rotationEnabled && this.graph.isCellRotatable(this.state.cell) &&
+		(mxGraphHandler.prototype.maxCells <= 0 || this.graph.getSelectionCount() < mxGraphHandler.prototype.maxCells) &&
+		this.state.width >= 2 && this.state.height >= 2;
+};
+
+/**
+ * Function: isConstrainedEvent
+ * 
+ * Returns true if the aspect ratio if the cell should be maintained.
+ */
+mxVertexHandler.prototype.isConstrainedEvent = function(me)
+{
+	return mxEvent.isShiftDown(me.getEvent()) || this.state.style[mxConstants.STYLE_ASPECT] == 'fixed';
+};
+
+/**
+ * Function: isCenteredEvent
+ * 
+ * Returns true if the center of the vertex should be maintained during the resize.
+ */
+mxVertexHandler.prototype.isCenteredEvent = function(state, me)
+{
+	return false;
+};
+
+/**
+ * Function: createCustomHandles
+ * 
+ * Returns an array of custom handles. This implementation returns null.
+ */
+mxVertexHandler.prototype.createCustomHandles = function()
+{
+	return null;
+};
+
+/**
+ * Function: updateMinBounds
+ * 
+ * Initializes the shapes required for this vertex handler.
+ */
+mxVertexHandler.prototype.updateMinBounds = function()
+{
+	var children = this.graph.getChildCells(this.state.cell);
+	
+	if (children.length > 0)
+	{
+		this.minBounds = this.graph.view.getBounds(children);
+		
+		if (this.minBounds != null)
+		{
+			var s = this.state.view.scale;
+			var t = this.state.view.translate;
+
+			this.minBounds.x -= this.state.x;
+			this.minBounds.y -= this.state.y;
+			this.minBounds.x /= s;
+			this.minBounds.y /= s;
+			this.minBounds.width /= s;
+			this.minBounds.height /= s;
+			this.x0 = this.state.x / s - t.x;
+			this.y0 = this.state.y / s - t.y;
+		}
+	}
+};
+
+/**
+ * Function: getSelectionBounds
+ * 
+ * Returns the mxRectangle that defines the bounds of the selection
+ * border.
+ */
+mxVertexHandler.prototype.getSelectionBounds = function(state)
+{
+	return new mxRectangle(Math.round(state.x), Math.round(state.y), Math.round(state.width), Math.round(state.height));
+};
+
+/**
+ * Function: createParentHighlightShape
+ * 
+ * Creates the shape used to draw the selection border.
+ */
+mxVertexHandler.prototype.createParentHighlightShape = function(bounds)
+{
+	return this.createSelectionShape(bounds);
+};
+
+/**
+ * Function: createSelectionShape
+ * 
+ * Creates the shape used to draw the selection border.
+ */
+mxVertexHandler.prototype.createSelectionShape = function(bounds)
+{
+	var shape = new mxRectangleShape(bounds, null, this.getSelectionColor());
+	shape.strokewidth = this.getSelectionStrokeWidth();
+	shape.isDashed = this.isSelectionDashed();
+	
+	return shape;
+};
+
+/**
+ * Function: getSelectionColor
+ * 
+ * Returns <mxConstants.VERTEX_SELECTION_COLOR>.
+ */
+mxVertexHandler.prototype.getSelectionColor = function()
+{
+	return mxConstants.VERTEX_SELECTION_COLOR;
+};
+
+/**
+ * Function: getSelectionStrokeWidth
+ * 
+ * Returns <mxConstants.VERTEX_SELECTION_STROKEWIDTH>.
+ */
+mxVertexHandler.prototype.getSelectionStrokeWidth = function()
+{
+	return mxConstants.VERTEX_SELECTION_STROKEWIDTH;
+};
+
+/**
+ * Function: isSelectionDashed
+ * 
+ * Returns <mxConstants.VERTEX_SELECTION_DASHED>.
+ */
+mxVertexHandler.prototype.isSelectionDashed = function()
+{
+	return mxConstants.VERTEX_SELECTION_DASHED;
+};
+
+/**
+ * Function: createSizer
+ * 
+ * Creates a sizer handle for the specified cursor and index and returns
+ * the new <mxRectangleShape> that represents the handle.
+ */
+mxVertexHandler.prototype.createSizer = function(cursor, index, size, fillColor)
+{
+	size = size || mxConstants.HANDLE_SIZE;
+	
+	var bounds = new mxRectangle(0, 0, size, size);
+	var sizer = this.createSizerShape(bounds, index, fillColor);
+
+	if (sizer.isHtmlAllowed() && this.state.text != null && this.state.text.node.parentNode == this.graph.container)
+	{
+		sizer.bounds.height -= 1;
+		sizer.bounds.width -= 1;
+		sizer.dialect = mxConstants.DIALECT_STRICTHTML;
+		sizer.init(this.graph.container);
+	}
+	else
+	{
+		sizer.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
+				mxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG;
+		sizer.init(this.graph.getView().getOverlayPane());
+	}
+
+	mxEvent.redirectMouseEvents(sizer.node, this.graph, this.state);
+	
+	if (this.graph.isEnabled())
+	{
+		sizer.setCursor(cursor);
+	}
+	
+	if (!this.isSizerVisible(index))
+	{
+		sizer.visible = false;
+	}
+	
+	return sizer;
+};
+
+/**
+ * Function: isSizerVisible
+ * 
+ * Returns true if the sizer for the given index is visible.
+ * This returns true for all given indices.
+ */
+mxVertexHandler.prototype.isSizerVisible = function(index)
+{
+	return true;
+};
+
+/**
+ * Function: createSizerShape
+ * 
+ * Creates the shape used for the sizer handle for the specified bounds an
+ * index. Only images and rectangles should be returned if support for HTML
+ * labels with not foreign objects is required.
+ */
+mxVertexHandler.prototype.createSizerShape = function(bounds, index, fillColor)
+{
+	if (this.handleImage != null)
+	{
+		bounds = new mxRectangle(bounds.x, bounds.y, this.handleImage.width, this.handleImage.height);
+		var shape = new mxImageShape(bounds, this.handleImage.src);
+		
+		// Allows HTML rendering of the images
+		shape.preserveImageAspect = false;
+
+		return shape;
+	}
+	else if (index == mxEvent.ROTATION_HANDLE)
+	{
+		return new mxEllipse(bounds, fillColor || mxConstants.HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR);
+	}
+	else
+	{
+		return new mxRectangleShape(bounds, fillColor || mxConstants.HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR);
+	}
+};
+
+/**
+ * Function: createBounds
+ * 
+ * Helper method to create an <mxRectangle> around the given centerpoint
+ * with a width and height of 2*s or 6, if no s is given.
+ */
+mxVertexHandler.prototype.moveSizerTo = function(shape, x, y)
+{
+	if (shape != null)
+	{
+		shape.bounds.x = Math.floor(x - shape.bounds.width / 2);
+		shape.bounds.y = Math.floor(y - shape.bounds.height / 2);
+		
+		// Fixes visible inactive handles in VML
+		if (shape.node != null && shape.node.style.display != 'none')
+		{
+			shape.redraw();
+		}
+	}
+};
+
+/**
+ * Function: getHandleForEvent
+ * 
+ * Returns the index of the handle for the given event. This returns the index
+ * of the sizer from where the event originated or <mxEvent.LABEL_INDEX>.
+ */
+mxVertexHandler.prototype.getHandleForEvent = function(me)
+{
+	// Connection highlight may consume events before they reach sizer handle
+	var tol = (!mxEvent.isMouseEvent(me.getEvent())) ? this.tolerance : 1;
+	var hit = (this.allowHandleBoundsCheck && (mxClient.IS_IE || tol > 0)) ?
+		new mxRectangle(me.getGraphX() - tol, me.getGraphY() - tol, 2 * tol, 2 * tol) : null;
+	
+	function checkShape(shape)
+	{
+		return shape != null && (me.isSource(shape) || (hit != null && mxUtils.intersects(shape.bounds, hit) &&
+			shape.node.style.display != 'none' && shape.node.style.visibility != 'hidden'));
+	}
+
+	if (this.customHandles != null && this.isCustomHandleEvent(me))
+	{
+		// Inverse loop order to match display order
+		for (var i = this.customHandles.length - 1; i >= 0; i--)
+		{
+			if (checkShape(this.customHandles[i].shape))
+			{
+				// LATER: Return reference to active shape
+				return mxEvent.CUSTOM_HANDLE - i;
+			}
+		}
+	}
+
+	if (checkShape(this.rotationShape))
+	{
+		return mxEvent.ROTATION_HANDLE;
+	}
+	else if (checkShape(this.labelShape))
+	{
+		return mxEvent.LABEL_HANDLE;
+	}
+	
+	if (this.sizers != null)
+	{
+		for (var i = 0; i < this.sizers.length; i++)
+		{
+			if (checkShape(this.sizers[i]))
+			{
+				return i;
+			}
+		}
+	}
+
+	return null;
+};
+
+/**
+ * Function: isCustomHandleEvent
+ * 
+ * Returns true if the given event allows custom handles to be changed. This
+ * implementation returns true.
+ */
+mxVertexHandler.prototype.isCustomHandleEvent = function(me)
+{
+	return true;
+};
+
+/**
+ * Function: mouseDown
+ * 
+ * Handles the event if a handle has been clicked. By consuming the
+ * event all subsequent events of the gesture are redirected to this
+ * handler.
+ */
+mxVertexHandler.prototype.mouseDown = function(sender, me)
+{
+	var tol = (!mxEvent.isMouseEvent(me.getEvent())) ? this.tolerance : 0;
+	
+	if (!me.isConsumed() && this.graph.isEnabled() && (tol > 0 || me.getState() == this.state))
+	{
+		var handle = this.getHandleForEvent(me);
+
+		if (handle != null)
+		{
+			this.start(me.getGraphX(), me.getGraphY(), handle);
+			me.consume();
+		}
+	}
+};
+
+/**
+ * Function: isLivePreviewBorder
+ * 
+ * Called if <livePreview> is enabled to check if a border should be painted.
+ * This implementation returns true if the shape is transparent.
+ */
+mxVertexHandler.prototype.isLivePreviewBorder = function()
+{
+	return this.state.shape != null && this.state.shape.fill == null && this.state.shape.stroke == null;
+};
+
+/**
+ * Function: start
+ * 
+ * Starts the handling of the mouse gesture.
+ */
+mxVertexHandler.prototype.start = function(x, y, index)
+{
+	this.inTolerance = true;
+	this.childOffsetX = 0;
+	this.childOffsetY = 0;
+	this.index = index;
+	this.startX = x;
+	this.startY = y;
+	
+	// Saves reference to parent state
+	var model = this.state.view.graph.model;
+	var parent = model.getParent(this.state.cell);
+	
+	if (this.state.view.currentRoot != parent && (model.isVertex(parent) || model.isEdge(parent)))
+	{
+		this.parentState = this.state.view.graph.view.getState(parent);
+	}
+	
+	// Creates a preview that can be on top of any HTML label
+	this.selectionBorder.node.style.display = (index == mxEvent.ROTATION_HANDLE) ? 'inline' : 'none';
+	
+	// Creates the border that represents the new bounds
+	if (!this.livePreview || this.isLivePreviewBorder())
+	{
+		this.preview = this.createSelectionShape(this.bounds);
+		
+		if (!(mxClient.IS_SVG && Number(this.state.style[mxConstants.STYLE_ROTATION] || '0') != 0) &&
+			this.state.text != null && this.state.text.node.parentNode == this.graph.container)
+		{
+			this.preview.dialect = mxConstants.DIALECT_STRICTHTML;
+			this.preview.init(this.graph.container);
+		}
+		else
+		{
+			this.preview.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
+					mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+			this.preview.init(this.graph.view.getOverlayPane());
+		}
+	}
+	
+	// Prepares the handles for live preview
+	if (this.livePreview)
+	{
+		this.hideSizers();
+		
+		if (index == mxEvent.ROTATION_HANDLE)
+		{
+			this.rotationShape.node.style.display = '';
+		}
+		else if (index == mxEvent.LABEL_HANDLE)
+		{
+			this.labelShape.node.style.display = '';
+		}
+		else if (this.sizers != null && this.sizers[index] != null)
+		{
+			this.sizers[index].node.style.display = '';
+		}
+		else if (index <= mxEvent.CUSTOM_HANDLE && this.customHandles != null)
+		{
+			this.customHandles[mxEvent.CUSTOM_HANDLE - index].setVisible(true);
+		}
+		
+		// Gets the array of connected edge handlers for redrawing
+		var edges = this.graph.getEdges(this.state.cell);
+		this.edgeHandlers = [];
+		
+		for (var i = 0; i < edges.length; i++)
+		{
+			var handler = this.graph.selectionCellsHandler.getHandler(edges[i]);
+			
+			if (handler != null)
+			{
+				this.edgeHandlers.push(handler);
+			}
+		}
+	}
+};
+
+/**
+ * Function: hideHandles
+ * 
+ * Shortcut to <hideSizers>.
+ */
+mxVertexHandler.prototype.setHandlesVisible = function(visible)
+{
+	if (this.sizers != null)
+	{
+		for (var i = 0; i < this.sizers.length; i++)
+		{
+			this.sizers[i].node.style.display = (visible) ? '' : 'none';
+		}
+	}
+
+	if (this.customHandles != null)
+	{
+		for (var i = 0; i < this.customHandles.length; i++)
+		{
+			this.customHandles[i].setVisible(visible);
+		}
+	}
+};
+
+/**
+ * Function: hideSizers
+ * 
+ * Hides all sizers except.
+ * 
+ * Starts the handling of the mouse gesture.
+ */
+mxVertexHandler.prototype.hideSizers = function()
+{
+	this.setHandlesVisible(false);
+};
+
+/**
+ * Function: checkTolerance
+ * 
+ * Checks if the coordinates for the given event are within the
+ * <mxGraph.tolerance>. If the event is a mouse event then the tolerance is
+ * ignored.
+ */
+mxVertexHandler.prototype.checkTolerance = function(me)
+{
+	if (this.inTolerance && this.startX != null && this.startY != null)
+	{
+		if (mxEvent.isMouseEvent(me.getEvent()) ||
+			Math.abs(me.getGraphX() - this.startX) > this.graph.tolerance ||
+			Math.abs(me.getGraphY() - this.startY) > this.graph.tolerance)
+		{
+			this.inTolerance = false;
+		}
+	}
+};
+
+/**
+ * Function: updateHint
+ * 
+ * Hook for subclassers do show details while the handler is active.
+ */
+mxVertexHandler.prototype.updateHint = function(me) { };
+
+/**
+ * Function: removeHint
+ * 
+ * Hooks for subclassers to hide details when the handler gets inactive.
+ */
+mxVertexHandler.prototype.removeHint = function() { };
+
+/**
+ * Function: roundAngle
+ * 
+ * Hook for rounding the angle. This uses Math.round.
+ */
+mxVertexHandler.prototype.roundAngle = function(angle)
+{
+	return Math.round(angle * 10) / 10;
+};
+
+/**
+ * Function: roundLength
+ * 
+ * Hook for rounding the unscaled width or height. This uses Math.round.
+ */
+mxVertexHandler.prototype.roundLength = function(length)
+{
+	return Math.round(length);
+};
+
+/**
+ * Function: mouseMove
+ * 
+ * Handles the event by updating the preview.
+ */
+mxVertexHandler.prototype.mouseMove = function(sender, me)
+{
+	if (!me.isConsumed() && this.index != null)
+	{
+		// Checks tolerance for ignoring single clicks
+		this.checkTolerance(me);
+
+		if (!this.inTolerance)
+		{
+			if (this.index <= mxEvent.CUSTOM_HANDLE)
+			{
+				if (this.customHandles != null)
+				{
+					this.customHandles[mxEvent.CUSTOM_HANDLE - this.index].processEvent(me);
+					this.customHandles[mxEvent.CUSTOM_HANDLE - this.index].active = true;
+				}
+			}
+			else if (this.index == mxEvent.LABEL_HANDLE)
+			{
+				this.moveLabel(me);
+			}
+			else if (this.index == mxEvent.ROTATION_HANDLE)
+			{
+				this.rotateVertex(me);
+			}
+			else
+			{
+				this.resizeVertex(me);
+			}
+
+			this.updateHint(me);
+		}
+		
+		me.consume();
+	}
+	// Workaround for disabling the connect highlight when over handle
+	else if (!this.graph.isMouseDown && this.getHandleForEvent(me) != null)
+	{
+		me.consume(false);
+	}
+};
+
+/**
+ * Function: rotateVertex
+ * 
+ * Rotates the vertex.
+ */
+mxVertexHandler.prototype.moveLabel = function(me)
+{
+	var point = new mxPoint(me.getGraphX(), me.getGraphY());
+	var tr = this.graph.view.translate;
+	var scale = this.graph.view.scale;
+	
+	if (this.graph.isGridEnabledEvent(me.getEvent()))
+	{
+		point.x = (this.graph.snap(point.x / scale - tr.x) + tr.x) * scale;
+		point.y = (this.graph.snap(point.y / scale - tr.y) + tr.y) * scale;
+	}
+
+	var index = (this.rotationShape != null) ? this.sizers.length - 2 : this.sizers.length - 1;
+	this.moveSizerTo(this.sizers[index], point.x, point.y);
+};
+
+/**
+ * Function: rotateVertex
+ * 
+ * Rotates the vertex.
+ */
+mxVertexHandler.prototype.rotateVertex = function(me)
+{
+	var point = new mxPoint(me.getGraphX(), me.getGraphY());
+	var dx = this.state.x + this.state.width / 2 - point.x;
+	var dy = this.state.y + this.state.height / 2 - point.y;
+	this.currentAlpha = (dx != 0) ? Math.atan(dy / dx) * 180 / Math.PI + 90 : ((dy < 0) ? 180 : 0);
+	
+	if (dx > 0)
+	{
+		this.currentAlpha -= 180;
+	}
+
+	// Rotation raster
+	if (this.rotationRaster && this.graph.isGridEnabledEvent(me.getEvent()))
+	{
+		var dx = point.x - this.state.getCenterX();
+		var dy = point.y - this.state.getCenterY();
+		var dist = Math.abs(Math.sqrt(dx * dx + dy * dy) - 20) * 3;
+		var raster = Math.max(1, 5 * Math.min(3, Math.max(0, Math.round(80 / Math.abs(dist)))));
+		
+		this.currentAlpha = Math.round(this.currentAlpha / raster) * raster;
+	}
+	else
+	{
+		this.currentAlpha = this.roundAngle(this.currentAlpha);
+	}
+
+	this.selectionBorder.rotation = this.currentAlpha;
+	this.selectionBorder.redraw();
+					
+	if (this.livePreview)
+	{
+		this.redrawHandles();
+	}
+};
+
+/**
+ * Function: rotateVertex
+ * 
+ * Rotates the vertex.
+ */
+mxVertexHandler.prototype.resizeVertex = function(me)
+{
+	var ct = new mxPoint(this.state.getCenterX(), this.state.getCenterY());
+	var alpha = mxUtils.toRadians(this.state.style[mxConstants.STYLE_ROTATION] || '0');
+	var point = new mxPoint(me.getGraphX(), me.getGraphY());
+	var tr = this.graph.view.translate;
+	var scale = this.graph.view.scale;
+	var cos = Math.cos(-alpha);
+	var sin = Math.sin(-alpha);
+	
+	var dx = point.x - this.startX;
+	var dy = point.y - this.startY;
+
+	// Rotates vector for mouse gesture
+	var tx = cos * dx - sin * dy;
+	var ty = sin * dx + cos * dy;
+	
+	dx = tx;
+	dy = ty;
+
+	var geo = this.graph.getCellGeometry(this.state.cell);
+	this.unscaledBounds = this.union(geo, dx / scale, dy / scale, this.index,
+		this.graph.isGridEnabledEvent(me.getEvent()), 1,
+		new mxPoint(0, 0), this.isConstrainedEvent(me),
+		this.isCenteredEvent(this.state, me));
+	
+	// Keeps vertex within maximum graph or parent bounds
+	if (!geo.relative)
+	{
+		var max = this.graph.getMaximumGraphBounds();
+		
+		// Handles child cells
+		if (max != null && this.parentState != null)
+		{
+			max = mxRectangle.fromRectangle(max);
+			
+			max.x -= (this.parentState.x - tr.x * scale) / scale;
+			max.y -= (this.parentState.y - tr.y * scale) / scale;
+		}
+		
+		if (this.graph.isConstrainChild(this.state.cell))
+		{
+			var tmp = this.graph.getCellContainmentArea(this.state.cell);
+			
+			if (tmp != null)
+			{
+				var overlap = this.graph.getOverlap(this.state.cell);
+				
+				if (overlap > 0)
+				{
+					tmp = mxRectangle.fromRectangle(tmp);
+					
+					tmp.x -= tmp.width * overlap;
+					tmp.y -= tmp.height * overlap;
+					tmp.width += 2 * tmp.width * overlap;
+					tmp.height += 2 * tmp.height * overlap;
+				}
+				
+				if (max == null)
+				{
+					max = tmp;
+				}
+				else
+				{
+					max = mxRectangle.fromRectangle(max);
+					max.intersect(tmp);
+				}
+			}
+		}
+	
+		if (max != null)
+		{
+			if (this.unscaledBounds.x < max.x)
+			{
+				this.unscaledBounds.width -= max.x - this.unscaledBounds.x;
+				this.unscaledBounds.x = max.x;
+			}
+			
+			if (this.unscaledBounds.y < max.y)
+			{
+				this.unscaledBounds.height -= max.y - this.unscaledBounds.y;
+				this.unscaledBounds.y = max.y;
+			}
+			
+			if (this.unscaledBounds.x + this.unscaledBounds.width > max.x + max.width)
+			{
+				this.unscaledBounds.width -= this.unscaledBounds.x +
+					this.unscaledBounds.width - max.x - max.width;
+			}
+			
+			if (this.unscaledBounds.y + this.unscaledBounds.height > max.y + max.height)
+			{
+				this.unscaledBounds.height -= this.unscaledBounds.y +
+					this.unscaledBounds.height - max.y - max.height;
+			}
+		}
+	}
+	
+	this.bounds = new mxRectangle(((this.parentState != null) ? this.parentState.x : tr.x * scale) +
+		(this.unscaledBounds.x) * scale, ((this.parentState != null) ? this.parentState.y : tr.y * scale) +
+		(this.unscaledBounds.y) * scale, this.unscaledBounds.width * scale, this.unscaledBounds.height * scale);
+
+	if (geo.relative && this.parentState != null)
+	{
+		this.bounds.x += this.state.x - this.parentState.x;
+		this.bounds.y += this.state.y - this.parentState.y;
+	}
+
+	cos = Math.cos(alpha);
+	sin = Math.sin(alpha);
+	
+	var c2 = new mxPoint(this.bounds.getCenterX(), this.bounds.getCenterY());
+
+	var dx = c2.x - ct.x;
+	var dy = c2.y - ct.y;
+	
+	var dx2 = cos * dx - sin * dy;
+	var dy2 = sin * dx + cos * dy;
+	
+	var dx3 = dx2 - dx;
+	var dy3 = dy2 - dy;
+	
+	var dx4 = this.bounds.x - this.state.x;
+	var dy4 = this.bounds.y - this.state.y;
+	
+	var dx5 = cos * dx4 - sin * dy4;
+	var dy5 = sin * dx4 + cos * dy4;
+	
+	this.bounds.x += dx3;
+	this.bounds.y += dy3;
+	
+	// Rounds unscaled bounds to int
+	this.unscaledBounds.x = this.roundLength(this.unscaledBounds.x + dx3 / scale);
+	this.unscaledBounds.y = this.roundLength(this.unscaledBounds.y + dy3 / scale);
+	this.unscaledBounds.width = this.roundLength(this.unscaledBounds.width);
+	this.unscaledBounds.height = this.roundLength(this.unscaledBounds.height);
+	
+	// Shifts the children according to parent offset
+	if (!this.graph.isCellCollapsed(this.state.cell) && (dx3 != 0 || dy3 != 0))
+	{
+		this.childOffsetX = this.state.x - this.bounds.x + dx5;
+		this.childOffsetY = this.state.y - this.bounds.y + dy5;
+	}
+	else
+	{
+		this.childOffsetX = 0;
+		this.childOffsetY = 0;
+	}
+	
+	if (this.livePreview)
+	{
+		this.updateLivePreview(me);
+	}
+	
+	if (this.preview != null)
+	{
+		this.drawPreview();
+	}
+};
+
+/**
+ * Function: updateLivePreview
+ * 
+ * Repaints the live preview.
+ */
+mxVertexHandler.prototype.updateLivePreview = function(me)
+{
+	// TODO: Apply child offset to children in live preview
+	var scale = this.graph.view.scale;
+	var tr = this.graph.view.translate;
+	
+	// Saves current state
+	var tempState = this.state.clone();
+
+	// Temporarily changes size and origin
+	this.state.x = this.bounds.x;
+	this.state.y = this.bounds.y;
+	this.state.origin = new mxPoint(this.state.x / scale - tr.x, this.state.y / scale - tr.y);
+	this.state.width = this.bounds.width;
+	this.state.height = this.bounds.height;
+	
+	// Needed to force update of text bounds
+	this.state.unscaledWidth = null;
+	
+	// Redraws cell and handles
+	var off = this.state.absoluteOffset;
+	off = new mxPoint(off.x, off.y);
+
+	// Required to store and reset absolute offset for updating label position
+	this.state.absoluteOffset.x = 0;
+	this.state.absoluteOffset.y = 0;
+	var geo = this.graph.getCellGeometry(this.state.cell);				
+
+	if (geo != null)
+	{
+		var offset = geo.offset || this.EMPTY_POINT;
+
+		if (offset != null && !geo.relative)
+		{
+			this.state.absoluteOffset.x = this.state.view.scale * offset.x;
+			this.state.absoluteOffset.y = this.state.view.scale * offset.y;
+		}
+		
+		this.state.view.updateVertexLabelOffset(this.state);
+	}
+	
+	// Draws the live preview
+	this.state.view.graph.cellRenderer.redraw(this.state, true);
+	
+	// Redraws connected edges TODO: Include child edges
+	this.state.view.invalidate(this.state.cell);
+	this.state.invalid = false;
+	this.state.view.validate();
+	this.redrawHandles();
+	
+	// Restores current state
+	this.state.setState(tempState);
+};
+
+/**
+ * Function: mouseUp
+ * 
+ * Handles the event by applying the changes to the geometry.
+ */
+mxVertexHandler.prototype.mouseUp = function(sender, me)
+{
+	if (this.index != null && this.state != null)
+	{
+		var point = new mxPoint(me.getGraphX(), me.getGraphY());
+
+		this.graph.getModel().beginUpdate();
+		try
+		{
+			if (this.index <= mxEvent.CUSTOM_HANDLE)
+			{
+				if (this.customHandles != null)
+				{
+					this.customHandles[mxEvent.CUSTOM_HANDLE - this.index].active = false;
+					this.customHandles[mxEvent.CUSTOM_HANDLE - this.index].execute();
+				}
+			}
+			else if (this.index == mxEvent.ROTATION_HANDLE)
+			{
+				if (this.currentAlpha != null)
+				{
+					var delta = this.currentAlpha - (this.state.style[mxConstants.STYLE_ROTATION] || 0);
+					
+					if (delta != 0)
+					{
+						this.rotateCell(this.state.cell, delta);
+					}
+				}
+				else
+				{
+					this.rotateClick();
+				}
+			}
+			else
+			{
+				var gridEnabled = this.graph.isGridEnabledEvent(me.getEvent());
+				var alpha = mxUtils.toRadians(this.state.style[mxConstants.STYLE_ROTATION] || '0');
+				var cos = Math.cos(-alpha);
+				var sin = Math.sin(-alpha);
+				
+				var dx = point.x - this.startX;
+				var dy = point.y - this.startY;
+				
+				// Rotates vector for mouse gesture
+				var tx = cos * dx - sin * dy;
+				var ty = sin * dx + cos * dy;
+				
+				dx = tx;
+				dy = ty;
+				
+				var s = this.graph.view.scale;
+				var recurse = this.isRecursiveResize(this.state, me);
+				this.resizeCell(this.state.cell, this.roundLength(dx / s), this.roundLength(dy / s),
+					this.index, gridEnabled, this.isConstrainedEvent(me), recurse);
+			}
+		}
+		finally
+		{
+			this.graph.getModel().endUpdate();
+		}
+
+		me.consume();
+		this.reset();
+	}
+};
+
+/**
+ * Function: rotateCell
+ * 
+ * Rotates the given cell to the given rotation.
+ */
+mxVertexHandler.prototype.isRecursiveResize = function(state, me)
+{
+	return this.graph.isRecursiveResize(this.state);
+};
+
+/**
+ * Function: rotateClick
+ * 
+ * Hook for subclassers to implement a single click on the rotation handle.
+ * This code is executed as part of the model transaction. This implementation
+ * is empty.
+ */
+mxVertexHandler.prototype.rotateClick = function() { };
+
+/**
+ * Function: rotateCell
+ * 
+ * Rotates the given cell and its children by the given angle in degrees.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to be rotated.
+ * angle - Angle in degrees.
+ */
+mxVertexHandler.prototype.rotateCell = function(cell, angle, parent)
+{
+	if (angle != 0)
+	{
+		var model = this.graph.getModel();
+
+		if (model.isVertex(cell) || model.isEdge(cell))
+		{
+			if (!model.isEdge(cell))
+			{
+				var state = this.graph.view.getState(cell);
+				var style = (state != null) ? state.style : this.graph.getCellStyle(cell);
+		
+				if (style != null)
+				{
+					var total = (style[mxConstants.STYLE_ROTATION] || 0) + angle;
+					this.graph.setCellStyles(mxConstants.STYLE_ROTATION, total, [cell]);
+				}
+			}
+			
+			var geo = this.graph.getCellGeometry(cell);
+			
+			if (geo != null)
+			{
+				var pgeo = this.graph.getCellGeometry(parent);
+				
+				if (pgeo != null && !model.isEdge(parent))
+				{
+					geo = geo.clone();
+					geo.rotate(angle, new mxPoint(pgeo.width / 2, pgeo.height / 2));
+					model.setGeometry(cell, geo);
+				}
+				
+				if ((model.isVertex(cell) && !geo.relative) || model.isEdge(cell))
+				{
+					// Recursive rotation
+					var childCount = model.getChildCount(cell);
+					
+					for (var i = 0; i < childCount; i++)
+					{
+						this.rotateCell(model.getChildAt(cell, i), angle, cell);
+					}
+				}
+			}
+		}
+	}
+};
+
+/**
+ * Function: reset
+ * 
+ * Resets the state of this handler.
+ */
+mxVertexHandler.prototype.reset = function()
+{
+	if (this.sizers != null && this.index != null && this.sizers[this.index] != null &&
+		this.sizers[this.index].node.style.display == 'none')
+	{
+		this.sizers[this.index].node.style.display = '';
+	}
+
+	this.currentAlpha = null;
+	this.inTolerance = null;
+	this.index = null;
+
+	// TODO: Reset and redraw cell states for live preview
+	if (this.preview != null)
+	{
+		this.preview.destroy();
+		this.preview = null;
+	}
+
+	if (this.livePreview && this.sizers != null)
+	{
+		for (var i = 0; i < this.sizers.length; i++)
+		{
+			if (this.sizers[i] != null)
+			{
+				this.sizers[i].node.style.display = '';
+			}
+		}
+	}
+
+	if (this.customHandles != null)
+	{
+		for (var i = 0; i < this.customHandles.length; i++)
+		{
+			if (this.customHandles[i].active)
+			{
+				this.customHandles[i].active = false;
+				this.customHandles[i].reset();
+			}
+			else
+			{
+				this.customHandles[i].setVisible(true);
+			}
+		}
+	}
+	
+	// Checks if handler has been destroyed
+	if (this.selectionBorder != null)
+	{
+		this.selectionBorder.node.style.display = 'inline';
+		this.selectionBounds = this.getSelectionBounds(this.state);
+		this.bounds = new mxRectangle(this.selectionBounds.x, this.selectionBounds.y,
+			this.selectionBounds.width, this.selectionBounds.height);
+		this.drawPreview();
+	}
+
+	this.removeHint();
+	this.redrawHandles();
+	this.edgeHandlers = null;
+	this.unscaledBounds = null;
+};
+
+/**
+ * Function: resizeCell
+ * 
+ * Uses the given vector to change the bounds of the given cell
+ * in the graph using <mxGraph.resizeCell>.
+ */
+mxVertexHandler.prototype.resizeCell = function(cell, dx, dy, index, gridEnabled, constrained, recurse)
+{
+	var geo = this.graph.model.getGeometry(cell);
+	
+	if (geo != null)
+	{
+		if (index == mxEvent.LABEL_HANDLE)
+		{
+			var scale = this.graph.view.scale;
+			dx = Math.round((this.labelShape.bounds.getCenterX() - this.startX) / scale);
+			dy = Math.round((this.labelShape.bounds.getCenterY() - this.startY) / scale);
+			
+			geo = geo.clone();
+			
+			if (geo.offset == null)
+			{
+				geo.offset = new mxPoint(dx, dy);
+			}
+			else
+			{
+				geo.offset.x += dx;
+				geo.offset.y += dy;
+			}
+			
+			this.graph.model.setGeometry(cell, geo);
+		}
+		else if (this.unscaledBounds != null)
+		{
+			var scale = this.graph.view.scale;
+
+			if (this.childOffsetX != 0 || this.childOffsetY != 0)
+			{
+				this.moveChildren(cell, Math.round(this.childOffsetX / scale), Math.round(this.childOffsetY / scale));
+			}
+
+			this.graph.resizeCell(cell, this.unscaledBounds, recurse);
+		}
+	}
+};
+
+/**
+ * Function: moveChildren
+ * 
+ * Moves the children of the given cell by the given vector.
+ */
+mxVertexHandler.prototype.moveChildren = function(cell, dx, dy)
+{
+	var model = this.graph.getModel();
+	var childCount = model.getChildCount(cell);
+	
+	for (var i = 0; i < childCount; i++)
+	{
+		var child = model.getChildAt(cell, i);
+		var geo = this.graph.getCellGeometry(child);
+		
+		if (geo != null)
+		{
+			geo = geo.clone();
+			geo.translate(dx, dy);
+			model.setGeometry(child, geo);
+		}
+	}
+};
+/**
+ * Function: union
+ * 
+ * Returns the union of the given bounds and location for the specified
+ * handle index.
+ * 
+ * To override this to limit the size of vertex via a minWidth/-Height style,
+ * the following code can be used.
+ * 
+ * (code)
+ * var vertexHandlerUnion = mxVertexHandler.prototype.union;
+ * mxVertexHandler.prototype.union = function(bounds, dx, dy, index, gridEnabled, scale, tr, constrained)
+ * {
+ *   var result = vertexHandlerUnion.apply(this, arguments);
+ *   
+ *   result.width = Math.max(result.width, mxUtils.getNumber(this.state.style, 'minWidth', 0));
+ *   result.height = Math.max(result.height, mxUtils.getNumber(this.state.style, 'minHeight', 0));
+ *   
+ *   return result;
+ * };
+ * (end)
+ * 
+ * The minWidth/-Height style can then be used as follows:
+ * 
+ * (code)
+ * graph.insertVertex(parent, null, 'Hello,', 20, 20, 80, 30, 'minWidth=100;minHeight=100;');
+ * (end)
+ * 
+ * To override this to update the height for a wrapped text if the width of a vertex is
+ * changed, the following can be used.
+ * 
+ * (code)
+ * var mxVertexHandlerUnion = mxVertexHandler.prototype.union;
+ * mxVertexHandler.prototype.union = function(bounds, dx, dy, index, gridEnabled, scale, tr, constrained)
+ * {
+ *   var result = mxVertexHandlerUnion.apply(this, arguments);
+ *   var s = this.state;
+ *   
+ *   if (this.graph.isHtmlLabel(s.cell) && (index == 3 || index == 4) &&
+ *       s.text != null && s.style[mxConstants.STYLE_WHITE_SPACE] == 'wrap')
+ *   {
+ *     var label = this.graph.getLabel(s.cell);
+ *     var fontSize = mxUtils.getNumber(s.style, mxConstants.STYLE_FONTSIZE, mxConstants.DEFAULT_FONTSIZE);
+ *     var ww = result.width / s.view.scale - s.text.spacingRight - s.text.spacingLeft
+ *     
+ *     result.height = mxUtils.getSizeForString(label, fontSize, s.style[mxConstants.STYLE_FONTFAMILY], ww).height;
+ *   }
+ *   
+ *   return result;
+ * };
+ * (end)
+ */
+mxVertexHandler.prototype.union = function(bounds, dx, dy, index, gridEnabled, scale, tr, constrained, centered)
+{
+	if (this.singleSizer)
+	{
+		var x = bounds.x + bounds.width + dx;
+		var y = bounds.y + bounds.height + dy;
+		
+		if (gridEnabled)
+		{
+			x = this.graph.snap(x / scale) * scale;
+			y = this.graph.snap(y / scale) * scale;
+		}
+		
+		var rect = new mxRectangle(bounds.x, bounds.y, 0, 0);
+		rect.add(new mxRectangle(x, y, 0, 0));
+		
+		return rect;
+	}
+	else
+	{
+		var w0 = bounds.width;
+		var h0 = bounds.height;
+		var left = bounds.x - tr.x * scale;
+		var right = left + w0;
+		var top = bounds.y - tr.y * scale;
+		var bottom = top + h0;
+		
+		var cx = left + w0 / 2;
+		var cy = top + h0 / 2;
+		
+		if (index > 4 /* Bottom Row */)
+		{
+			bottom = bottom + dy;
+			
+			if (gridEnabled)
+			{
+				bottom = this.graph.snap(bottom / scale) * scale;
+			}
+		}
+		else if (index < 3 /* Top Row */)
+		{
+			top = top + dy;
+			
+			if (gridEnabled)
+			{
+				top = this.graph.snap(top / scale) * scale;
+			}
+		}
+		
+		if (index == 0 || index == 3 || index == 5 /* Left */)
+		{
+			left += dx;
+			
+			if (gridEnabled)
+			{
+				left = this.graph.snap(left / scale) * scale;
+			}
+		}
+		else if (index == 2 || index == 4 || index == 7 /* Right */)
+		{
+			right += dx;
+			
+			if (gridEnabled)
+			{
+				right = this.graph.snap(right / scale) * scale;
+			}
+		}
+		
+		var width = right - left;
+		var height = bottom - top;
+
+		if (constrained)
+		{
+			var geo = this.graph.getCellGeometry(this.state.cell);
+
+			if (geo != null)
+			{
+				var aspect = geo.width / geo.height;
+				
+				if (index== 1 || index== 2 || index == 7 || index == 6)
+				{
+					width = height * aspect;
+				}
+				else
+				{
+					height = width / aspect;
+				}
+				
+				if (index == 0)
+				{
+					left = right - width;
+					top = bottom - height;
+				}
+			}
+		}
+
+		if (centered)
+		{
+			width += (width - w0);
+			height += (height - h0);
+			
+			var cdx = cx - (left + width / 2);
+			var cdy = cy - (top + height / 2);
+
+			left += cdx;
+			top += cdy;
+			right += cdx;
+			bottom += cdy;
+		}
+
+		// Flips over left side
+		if (width < 0)
+		{
+			left += width;
+			width = Math.abs(width);
+		}
+		
+		// Flips over top side
+		if (height < 0)
+		{
+			top += height;
+			height = Math.abs(height);
+		}
+
+		var result = new mxRectangle(left + tr.x * scale, top + tr.y * scale, width, height);
+		
+		if (this.minBounds != null)
+		{
+			result.width = Math.max(result.width, this.minBounds.x * scale + this.minBounds.width * scale +
+				Math.max(0, this.x0 * scale - result.x));
+			result.height = Math.max(result.height, this.minBounds.y * scale + this.minBounds.height * scale +
+				Math.max(0, this.y0 * scale - result.y));
+		}
+		
+		return result;
+	}
+};
+
+/**
+ * Function: redraw
+ * 
+ * Redraws the handles and the preview.
+ */
+mxVertexHandler.prototype.redraw = function()
+{
+	this.selectionBounds = this.getSelectionBounds(this.state);
+	this.bounds = new mxRectangle(this.selectionBounds.x, this.selectionBounds.y, this.selectionBounds.width, this.selectionBounds.height);
+	
+	this.redrawHandles();
+	this.drawPreview();
+};
+
+/**
+ * Returns the padding to be used for drawing handles for the current <bounds>.
+ */
+mxVertexHandler.prototype.getHandlePadding = function()
+{
+	// KNOWN: Tolerance depends on event type (eg. 0 for mouse events)
+	var result = new mxPoint(0, 0);
+	var tol = this.tolerance;
+
+	if (this.sizers != null && this.sizers.length > 0 && this.sizers[0] != null &&
+		(this.bounds.width < 2 * this.sizers[0].bounds.width + 2 * tol ||
+		this.bounds.height < 2 * this.sizers[0].bounds.height + 2 * tol))
+	{
+		tol /= 2;
+		
+		result.x = this.sizers[0].bounds.width + tol;
+		result.y = this.sizers[0].bounds.height + tol;
+	}
+	
+	return result;
+};
+
+/**
+ * Function: redrawHandles
+ * 
+ * Redraws the handles. To hide certain handles the following code can be used.
+ * 
+ * (code)
+ * mxVertexHandler.prototype.redrawHandles = function()
+ * {
+ *   mxVertexHandlerRedrawHandles.apply(this, arguments);
+ *   
+ *   if (this.sizers != null && this.sizers.length > 7)
+ *   {
+ *     this.sizers[1].node.style.display = 'none';
+ *     this.sizers[6].node.style.display = 'none';
+ *   }
+ * };
+ * (end)
+ */
+mxVertexHandler.prototype.redrawHandles = function()
+{
+	var tol = this.tolerance;
+	this.horizontalOffset = 0;
+	this.verticalOffset = 0;
+	var s = this.bounds;
+
+	if (this.sizers != null && this.sizers.length > 0 && this.sizers[0] != null)
+	{
+		if (this.index == null && this.manageSizers && this.sizers.length >= 8)
+		{
+			// KNOWN: Tolerance depends on event type (eg. 0 for mouse events)
+			var padding = this.getHandlePadding();
+			this.horizontalOffset = padding.x;
+			this.verticalOffset = padding.y;
+			
+			if (this.horizontalOffset != 0 || this.verticalOffset != 0)
+			{
+				s = new mxRectangle(s.x, s.y, s.width, s.height);
+
+				s.x -= this.horizontalOffset / 2;
+				s.width += this.horizontalOffset;
+				s.y -= this.verticalOffset / 2;
+				s.height += this.verticalOffset;
+			}
+			
+			if (this.sizers.length >= 8)
+			{
+				if ((s.width < 2 * this.sizers[0].bounds.width + 2 * tol) ||
+					(s.height < 2 * this.sizers[0].bounds.height + 2 * tol))
+				{
+					this.sizers[0].node.style.display = 'none';
+					this.sizers[2].node.style.display = 'none';
+					this.sizers[5].node.style.display = 'none';
+					this.sizers[7].node.style.display = 'none';
+				}
+				else
+				{
+					this.sizers[0].node.style.display = '';
+					this.sizers[2].node.style.display = '';
+					this.sizers[5].node.style.display = '';
+					this.sizers[7].node.style.display = '';
+				}
+			}
+		}
+
+		var r = s.x + s.width;
+		var b = s.y + s.height;
+		
+		if (this.singleSizer)
+		{
+			this.moveSizerTo(this.sizers[0], r, b);
+		}
+		else
+		{
+			var cx = s.x + s.width / 2;
+			var cy = s.y + s.height / 2;
+			
+			if (this.sizers.length >= 8)
+			{
+				var crs = ['nw-resize', 'n-resize', 'ne-resize', 'e-resize', 'se-resize', 's-resize', 'sw-resize', 'w-resize'];
+				
+				var alpha = mxUtils.toRadians(this.state.style[mxConstants.STYLE_ROTATION] || '0');
+				var cos = Math.cos(alpha);
+				var sin = Math.sin(alpha);
+				
+				var da = Math.round(alpha * 4 / Math.PI);
+				
+				var ct = new mxPoint(s.getCenterX(), s.getCenterY());
+				var pt = mxUtils.getRotatedPoint(new mxPoint(s.x, s.y), cos, sin, ct);
+				
+				this.moveSizerTo(this.sizers[0], pt.x, pt.y);
+				this.sizers[0].setCursor(crs[mxUtils.mod(0 + da, crs.length)]);
+				
+				pt.x = cx;
+				pt.y = s.y;
+				pt = mxUtils.getRotatedPoint(pt, cos, sin, ct);
+				
+				this.moveSizerTo(this.sizers[1], pt.x, pt.y);
+				this.sizers[1].setCursor(crs[mxUtils.mod(1 + da, crs.length)]);
+				
+				pt.x = r;
+				pt.y = s.y;
+				pt = mxUtils.getRotatedPoint(pt, cos, sin, ct);
+				
+				this.moveSizerTo(this.sizers[2], pt.x, pt.y);
+				this.sizers[2].setCursor(crs[mxUtils.mod(2 + da, crs.length)]);
+				
+				pt.x = s.x;
+				pt.y = cy;
+				pt = mxUtils.getRotatedPoint(pt, cos, sin, ct);
+				
+				this.moveSizerTo(this.sizers[3], pt.x, pt.y);
+				this.sizers[3].setCursor(crs[mxUtils.mod(7 + da, crs.length)]);
+
+				pt.x = r;
+				pt.y = cy;
+				pt = mxUtils.getRotatedPoint(pt, cos, sin, ct);
+				
+				this.moveSizerTo(this.sizers[4], pt.x, pt.y);
+				this.sizers[4].setCursor(crs[mxUtils.mod(3 + da, crs.length)]);
+
+				pt.x = s.x;
+				pt.y = b;
+				pt = mxUtils.getRotatedPoint(pt, cos, sin, ct);
+				
+				this.moveSizerTo(this.sizers[5], pt.x, pt.y);
+				this.sizers[5].setCursor(crs[mxUtils.mod(6 + da, crs.length)]);
+
+				pt.x = cx;
+				pt.y = b;
+				pt = mxUtils.getRotatedPoint(pt, cos, sin, ct);
+				
+				this.moveSizerTo(this.sizers[6], pt.x, pt.y);
+				this.sizers[6].setCursor(crs[mxUtils.mod(5 + da, crs.length)]);
+
+				pt.x = r;
+				pt.y = b;
+				pt = mxUtils.getRotatedPoint(pt, cos, sin, ct);
+				
+				this.moveSizerTo(this.sizers[7], pt.x, pt.y);
+				this.sizers[7].setCursor(crs[mxUtils.mod(4 + da, crs.length)]);
+				
+				this.moveSizerTo(this.sizers[8], cx + this.state.absoluteOffset.x, cy + this.state.absoluteOffset.y);
+			}
+			else if (this.state.width >= 2 && this.state.height >= 2)
+			{
+				this.moveSizerTo(this.sizers[0], cx + this.state.absoluteOffset.x, cy + this.state.absoluteOffset.y);
+			}
+			else
+			{
+				this.moveSizerTo(this.sizers[0], this.state.x, this.state.y);
+			}
+		}
+	}
+
+	if (this.rotationShape != null)
+	{
+		var alpha = mxUtils.toRadians((this.currentAlpha != null) ? this.currentAlpha : this.state.style[mxConstants.STYLE_ROTATION] || '0');
+		var cos = Math.cos(alpha);
+		var sin = Math.sin(alpha);
+		
+		var ct = new mxPoint(this.state.getCenterX(), this.state.getCenterY());
+		var pt = mxUtils.getRotatedPoint(new mxPoint(s.x + s.width / 2, s.y + this.rotationHandleVSpacing), cos, sin, ct);
+
+		if (this.rotationShape.node != null)
+		{
+			this.moveSizerTo(this.rotationShape, pt.x, pt.y);
+		}
+	}
+	
+	if (this.selectionBorder != null)
+	{
+		this.selectionBorder.rotation = Number(this.state.style[mxConstants.STYLE_ROTATION] || '0');
+	}
+	
+	if (this.edgeHandlers != null)
+	{		
+		for (var i = 0; i < this.edgeHandlers.length; i++)
+		{
+			this.edgeHandlers[i].redraw();
+		}
+	}
+
+	if (this.customHandles != null)
+	{
+		for (var i = 0; i < this.customHandles.length; i++)
+		{
+			var temp = this.customHandles[i].shape.node.style.display;
+			this.customHandles[i].redraw();
+			this.customHandles[i].shape.node.style.display = temp;
+		}
+	}
+
+	this.updateParentHighlight();
+};
+
+/**
+ * Function: updateParentHighlight
+ * 
+ * Updates the highlight of the parent if <parentHighlightEnabled> is true.
+ */
+mxVertexHandler.prototype.updateParentHighlight = function()
+{
+	// If not destroyed
+	if (this.selectionBorder != null)
+	{
+		if (this.parentHighlight != null)
+		{
+			var parent = this.graph.model.getParent(this.state.cell);
+	
+			if (this.graph.model.isVertex(parent))
+			{
+				var pstate = this.graph.view.getState(parent);
+				var b = this.parentHighlight.bounds;
+				
+				if (pstate != null && (b.x != pstate.x || b.y != pstate.y ||
+					b.width != pstate.width || b.height != pstate.height))
+				{
+					this.parentHighlight.bounds = pstate;
+					this.parentHighlight.redraw();
+				}
+			}
+			else
+			{
+				this.parentHighlight.destroy();
+				this.parentHighlight = null;
+			}
+		}
+		else if (this.parentHighlightEnabled)
+		{
+			var parent = this.graph.model.getParent(this.state.cell);
+			
+			if (this.graph.model.isVertex(parent))
+			{
+				var pstate = this.graph.view.getState(parent);
+				
+				if (pstate != null)
+				{
+					this.parentHighlight = this.createParentHighlightShape(pstate);
+					// VML dialect required here for event transparency in IE
+					this.parentHighlight.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+					this.parentHighlight.pointerEvents = false;
+					this.parentHighlight.rotation = Number(pstate.style[mxConstants.STYLE_ROTATION] || '0');
+					this.parentHighlight.init(this.graph.getView().getOverlayPane());
+				}
+			}
+		}
+	}
+};
+
+/**
+ * Function: drawPreview
+ * 
+ * Redraws the preview.
+ */
+mxVertexHandler.prototype.drawPreview = function()
+{
+	if (this.preview != null)
+	{
+		this.preview.bounds = this.bounds;
+		
+		if (this.preview.node.parentNode == this.graph.container)
+		{
+			this.preview.bounds.width = Math.max(0, this.preview.bounds.width - 1);
+			this.preview.bounds.height = Math.max(0, this.preview.bounds.height - 1);
+		}
+	
+		this.preview.rotation = Number(this.state.style[mxConstants.STYLE_ROTATION] || '0');
+		this.preview.redraw();
+	}
+	
+	this.selectionBorder.bounds = this.bounds;
+	this.selectionBorder.redraw();
+	
+	if (this.parentHighlight != null)
+	{
+		this.parentHighlight.redraw();
+	}
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the handler and all its resources and DOM nodes.
+ */
+mxVertexHandler.prototype.destroy = function()
+{
+	if (this.escapeHandler != null)
+	{
+		this.state.view.graph.removeListener(this.escapeHandler);
+		this.escapeHandler = null;
+	}
+	
+	if (this.preview != null)
+	{
+		this.preview.destroy();
+		this.preview = null;
+	}
+	
+	if (this.parentHighlight != null)
+	{
+		this.parentHighlight.destroy();
+		this.parentHighlight = null;
+	}
+	
+	if (this.selectionBorder != null)
+	{
+		this.selectionBorder.destroy();
+		this.selectionBorder = null;
+	}
+	
+	this.labelShape = null;
+	this.removeHint();
+
+	if (this.sizers != null)
+	{
+		for (var i = 0; i < this.sizers.length; i++)
+		{
+			this.sizers[i].destroy();
+		}
+		
+		this.sizers = null;
+	}
+
+	if (this.customHandles != null)
+	{
+		for (var i = 0; i < this.customHandles.length; i++)
+		{
+			this.customHandles[i].destroy();
+		}
+		
+		this.customHandles = null;
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxEdgeHandler
+ *
+ * Graph event handler that reconnects edges and modifies control points and
+ * the edge label location. Uses <mxTerminalMarker> for finding and
+ * highlighting new source and target vertices. This handler is automatically
+ * created in <mxGraph.createHandler> for each selected edge.
+ * 
+ * To enable adding/removing control points, the following code can be used:
+ * 
+ * (code)
+ * mxEdgeHandler.prototype.addEnabled = true;
+ * mxEdgeHandler.prototype.removeEnabled = true;
+ * (end)
+ * 
+ * Note: This experimental feature is not recommended for production use.
+ * 
+ * Constructor: mxEdgeHandler
+ *
+ * Constructs an edge handler for the specified <mxCellState>.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> of the cell to be handled.
+ */
+function mxEdgeHandler(state)
+{
+	if (state != null)
+	{
+		this.state = state;
+		this.init();
+		
+		// Handles escape keystrokes
+		this.escapeHandler = mxUtils.bind(this, function(sender, evt)
+		{
+			this.reset();
+		});
+		
+		this.state.view.graph.addListener(mxEvent.ESCAPE, this.escapeHandler);
+	}
+};
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxEdgeHandler.prototype.graph = null;
+
+/**
+ * Variable: state
+ * 
+ * Reference to the <mxCellState> being modified.
+ */
+mxEdgeHandler.prototype.state = null;
+
+/**
+ * Variable: marker
+ * 
+ * Holds the <mxTerminalMarker> which is used for highlighting terminals.
+ */
+mxEdgeHandler.prototype.marker = null;
+
+/**
+ * Variable: constraintHandler
+ * 
+ * Holds the <mxConstraintHandler> used for drawing and highlighting
+ * constraints.
+ */
+mxEdgeHandler.prototype.constraintHandler = null;
+
+/**
+ * Variable: error
+ * 
+ * Holds the current validation error while a connection is being changed.
+ */
+mxEdgeHandler.prototype.error = null;
+
+/**
+ * Variable: shape
+ * 
+ * Holds the <mxShape> that represents the preview edge.
+ */
+mxEdgeHandler.prototype.shape = null;
+
+/**
+ * Variable: bends
+ * 
+ * Holds the <mxShapes> that represent the points.
+ */
+mxEdgeHandler.prototype.bends = null;
+
+/**
+ * Variable: labelShape
+ * 
+ * Holds the <mxShape> that represents the label position.
+ */
+mxEdgeHandler.prototype.labelShape = null;
+
+/**
+ * Variable: cloneEnabled
+ * 
+ * Specifies if cloning by control-drag is enabled. Default is true.
+ */
+mxEdgeHandler.prototype.cloneEnabled = true;
+
+/**
+ * Variable: addEnabled
+ * 
+ * Specifies if adding bends by shift-click is enabled. Default is false.
+ * Note: This experimental feature is not recommended for production use.
+ */
+mxEdgeHandler.prototype.addEnabled = false;
+
+/**
+ * Variable: removeEnabled
+ * 
+ * Specifies if removing bends by shift-click is enabled. Default is false.
+ * Note: This experimental feature is not recommended for production use.
+ */
+mxEdgeHandler.prototype.removeEnabled = false;
+
+/**
+ * Variable: dblClickRemoveEnabled
+ * 
+ * Specifies if removing bends by double click is enabled. Default is false.
+ */
+mxEdgeHandler.prototype.dblClickRemoveEnabled = false;
+
+/**
+ * Variable: mergeRemoveEnabled
+ * 
+ * Specifies if removing bends by dropping them on other bends is enabled.
+ * Default is false.
+ */
+mxEdgeHandler.prototype.mergeRemoveEnabled = false;
+
+/**
+ * Variable: straightRemoveEnabled
+ * 
+ * Specifies if removing bends by creating straight segments should be enabled.
+ * If enabled, this can be overridden by holding down the alt key while moving.
+ * Default is false.
+ */
+mxEdgeHandler.prototype.straightRemoveEnabled = false;
+
+/**
+ * Variable: virtualBendsEnabled
+ * 
+ * Specifies if virtual bends should be added in the center of each
+ * segments. These bends can then be used to add new waypoints.
+ * Default is false.
+ */
+mxEdgeHandler.prototype.virtualBendsEnabled = false;
+
+/**
+ * Variable: virtualBendOpacity
+ * 
+ * Opacity to be used for virtual bends (see <virtualBendsEnabled>).
+ * Default is 20.
+ */
+mxEdgeHandler.prototype.virtualBendOpacity = 20;
+
+/**
+ * Variable: parentHighlightEnabled
+ * 
+ * Specifies if the parent should be highlighted if a child cell is selected.
+ * Default is false.
+ */
+mxEdgeHandler.prototype.parentHighlightEnabled = false;
+
+/**
+ * Variable: preferHtml
+ * 
+ * Specifies if bends should be added to the graph container. This is updated
+ * in <init> based on whether the edge or one of its terminals has an HTML
+ * label in the container.
+ */
+mxEdgeHandler.prototype.preferHtml = false;
+
+/**
+ * Variable: allowHandleBoundsCheck
+ * 
+ * Specifies if the bounds of handles should be used for hit-detection in IE
+ * Default is true.
+ */
+mxEdgeHandler.prototype.allowHandleBoundsCheck = true;
+
+/**
+ * Variable: snapToTerminals
+ * 
+ * Specifies if waypoints should snap to the routing centers of terminals.
+ * Default is false.
+ */
+mxEdgeHandler.prototype.snapToTerminals = false;
+
+/**
+ * Variable: handleImage
+ * 
+ * Optional <mxImage> to be used as handles. Default is null.
+ */
+mxEdgeHandler.prototype.handleImage = null;
+
+/**
+ * Variable: tolerance
+ * 
+ * Optional tolerance for hit-detection in <getHandleForEvent>. Default is 0.
+ */
+mxEdgeHandler.prototype.tolerance = 0;
+
+/**
+ * Variable: outlineConnect
+ * 
+ * Specifies if connections to the outline of a highlighted target should be
+ * enabled. This will allow to place the connection point along the outline of
+ * the highlighted target. Default is false.
+ */
+mxEdgeHandler.prototype.outlineConnect = false;
+
+/**
+ * Variable: manageLabelHandle
+ * 
+ * Specifies if the label handle should be moved if it intersects with another
+ * handle. Uses <checkLabelHandle> for checking and moving. Default is false.
+ */
+mxEdgeHandler.prototype.manageLabelHandle = false;
+
+/**
+ * Function: init
+ * 
+ * Initializes the shapes required for this edge handler.
+ */
+mxEdgeHandler.prototype.init = function()
+{
+	this.graph = this.state.view.graph;
+	this.marker = this.createMarker();
+	this.constraintHandler = new mxConstraintHandler(this.graph);
+	
+	// Clones the original points from the cell
+	// and makes sure at least one point exists
+	this.points = [];
+	
+	// Uses the absolute points of the state
+	// for the initial configuration and preview
+	this.abspoints = this.getSelectionPoints(this.state);
+	this.shape = this.createSelectionShape(this.abspoints);
+	this.shape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
+		mxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG;
+	this.shape.init(this.graph.getView().getOverlayPane());
+	this.shape.pointerEvents = false;
+	this.shape.setCursor(mxConstants.CURSOR_MOVABLE_EDGE);
+	mxEvent.redirectMouseEvents(this.shape.node, this.graph, this.state);
+
+	// Updates preferHtml
+	this.preferHtml = this.state.text != null &&
+		this.state.text.node.parentNode == this.graph.container;
+	
+	if (!this.preferHtml)
+	{
+		// Checks source terminal
+		var sourceState = this.state.getVisibleTerminalState(true);
+		
+		if (sourceState != null)
+		{
+			this.preferHtml = sourceState.text != null &&
+				sourceState.text.node.parentNode == this.graph.container;
+		}
+		
+		if (!this.preferHtml)
+		{
+			// Checks target terminal
+			var targetState = this.state.getVisibleTerminalState(false);
+			
+			if (targetState != null)
+			{
+				this.preferHtml = targetState.text != null &&
+				targetState.text.node.parentNode == this.graph.container;
+			}
+		}
+	}
+	
+	// Adds highlight for parent group
+	if (this.parentHighlightEnabled)
+	{
+		var parent = this.graph.model.getParent(this.state.cell);
+		
+		if (this.graph.model.isVertex(parent))
+		{
+			var pstate = this.graph.view.getState(parent);
+			
+			if (pstate != null)
+			{
+				this.parentHighlight = this.createParentHighlightShape(pstate);
+				// VML dialect required here for event transparency in IE
+				this.parentHighlight.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+				this.parentHighlight.pointerEvents = false;
+				this.parentHighlight.rotation = Number(pstate.style[mxConstants.STYLE_ROTATION] || '0');
+				this.parentHighlight.init(this.graph.getView().getOverlayPane());
+			}
+		}
+	}
+	
+	// Creates bends for the non-routed absolute points
+	// or bends that don't correspond to points
+	if (this.graph.getSelectionCount() < mxGraphHandler.prototype.maxCells ||
+		mxGraphHandler.prototype.maxCells <= 0)
+	{
+		this.bends = this.createBends();
+
+		if (this.isVirtualBendsEnabled())
+		{
+			this.virtualBends = this.createVirtualBends();
+		}
+	}
+
+	// Adds a rectangular handle for the label position
+	this.label = new mxPoint(this.state.absoluteOffset.x, this.state.absoluteOffset.y);
+	this.labelShape = this.createLabelHandleShape();
+	this.initBend(this.labelShape);
+	this.labelShape.setCursor(mxConstants.CURSOR_LABEL_HANDLE);
+	
+	this.customHandles = this.createCustomHandles();
+	
+	this.redraw();
+};
+
+/**
+ * Function: createCustomHandles
+ * 
+ * Returns an array of custom handles. This implementation returns null.
+ */
+mxEdgeHandler.prototype.createCustomHandles = function()
+{
+	return null;
+};
+
+/**
+ * Function: isVirtualBendsEnabled
+ * 
+ * Returns true if virtual bends should be added. This returns true if
+ * <virtualBendsEnabled> is true and the current style allows and
+ * renders custom waypoints.
+ */
+mxEdgeHandler.prototype.isVirtualBendsEnabled = function(evt)
+{
+	return this.virtualBendsEnabled && (this.state.style[mxConstants.STYLE_EDGE] == null ||
+			this.state.style[mxConstants.STYLE_EDGE] == mxConstants.NONE ||
+			this.state.style[mxConstants.STYLE_NOEDGESTYLE] == 1)  &&
+			mxUtils.getValue(this.state.style, mxConstants.STYLE_SHAPE, null) != 'arrow';
+};
+
+/**
+ * Function: isAddPointEvent
+ * 
+ * Returns true if the given event is a trigger to add a new point. This
+ * implementation returns true if shift is pressed.
+ */
+mxEdgeHandler.prototype.isAddPointEvent = function(evt)
+{
+	return mxEvent.isShiftDown(evt);
+};
+
+/**
+ * Function: isRemovePointEvent
+ * 
+ * Returns true if the given event is a trigger to remove a point. This
+ * implementation returns true if shift is pressed.
+ */
+mxEdgeHandler.prototype.isRemovePointEvent = function(evt)
+{
+	return mxEvent.isShiftDown(evt);
+};
+
+/**
+ * Function: getSelectionPoints
+ * 
+ * Returns the list of points that defines the selection stroke.
+ */
+mxEdgeHandler.prototype.getSelectionPoints = function(state)
+{
+	return state.absolutePoints;
+};
+
+/**
+ * Function: createSelectionShape
+ * 
+ * Creates the shape used to draw the selection border.
+ */
+mxEdgeHandler.prototype.createParentHighlightShape = function(bounds)
+{
+	var shape = new mxRectangleShape(bounds, null, this.getSelectionColor());
+	shape.strokewidth = this.getSelectionStrokeWidth();
+	shape.isDashed = this.isSelectionDashed();
+	
+	return shape;
+};
+
+/**
+ * Function: createSelectionShape
+ * 
+ * Creates the shape used to draw the selection border.
+ */
+mxEdgeHandler.prototype.createSelectionShape = function(points)
+{
+	var shape = new this.state.shape.constructor();
+	shape.outline = true;
+	shape.apply(this.state);
+	
+	shape.isDashed = this.isSelectionDashed();
+	shape.stroke = this.getSelectionColor();
+	shape.isShadow = false;
+	
+	return shape;
+};
+
+/**
+ * Function: getSelectionColor
+ * 
+ * Returns <mxConstants.EDGE_SELECTION_COLOR>.
+ */
+mxEdgeHandler.prototype.getSelectionColor = function()
+{
+	return mxConstants.EDGE_SELECTION_COLOR;
+};
+
+/**
+ * Function: getSelectionStrokeWidth
+ * 
+ * Returns <mxConstants.EDGE_SELECTION_STROKEWIDTH>.
+ */
+mxEdgeHandler.prototype.getSelectionStrokeWidth = function()
+{
+	return mxConstants.EDGE_SELECTION_STROKEWIDTH;
+};
+
+/**
+ * Function: isSelectionDashed
+ * 
+ * Returns <mxConstants.EDGE_SELECTION_DASHED>.
+ */
+mxEdgeHandler.prototype.isSelectionDashed = function()
+{
+	return mxConstants.EDGE_SELECTION_DASHED;
+};
+
+/**
+ * Function: isConnectableCell
+ * 
+ * Returns true if the given cell is connectable. This is a hook to
+ * disable floating connections. This implementation returns true.
+ */
+mxEdgeHandler.prototype.isConnectableCell = function(cell)
+{
+	return true;
+};
+
+/**
+ * Function: getCellAt
+ * 
+ * Creates and returns the <mxCellMarker> used in <marker>.
+ */
+mxEdgeHandler.prototype.getCellAt = function(x, y)
+{
+	return (!this.outlineConnect) ? this.graph.getCellAt(x, y) : null;
+};
+
+/**
+ * Function: createMarker
+ * 
+ * Creates and returns the <mxCellMarker> used in <marker>.
+ */
+mxEdgeHandler.prototype.createMarker = function()
+{
+	var marker = new mxCellMarker(this.graph);
+	var self = this; // closure
+
+	// Only returns edges if they are connectable and never returns
+	// the edge that is currently being modified
+	marker.getCell = function(me)
+	{
+		var cell = mxCellMarker.prototype.getCell.apply(this, arguments);
+
+		// Checks for cell at preview point (with grid)
+		if ((cell == self.state.cell || cell == null) && self.currentPoint != null)
+		{
+			cell = self.graph.getCellAt(self.currentPoint.x, self.currentPoint.y);
+		}
+		
+		// Uses connectable parent vertex if one exists
+		if (cell != null && !this.graph.isCellConnectable(cell))
+		{
+			var parent = this.graph.getModel().getParent(cell);
+			
+			if (this.graph.getModel().isVertex(parent) && this.graph.isCellConnectable(parent))
+			{
+				cell = parent;
+			}
+		}
+		
+		var model = self.graph.getModel();
+		
+		if ((this.graph.isSwimlane(cell) && self.currentPoint != null &&
+			this.graph.hitsSwimlaneContent(cell, self.currentPoint.x, self.currentPoint.y)) ||
+			(!self.isConnectableCell(cell)) || (cell == self.state.cell ||
+			(cell != null && !self.graph.connectableEdges && model.isEdge(cell))) ||
+			model.isAncestor(self.state.cell, cell))
+		{
+			cell = null;
+		}
+		
+		if (!this.graph.isCellConnectable(cell))
+		{
+			cell = null;
+		}
+		
+		return cell;
+	};
+
+	// Sets the highlight color according to validateConnection
+	marker.isValidState = function(state)
+	{
+		var model = self.graph.getModel();
+		var other = self.graph.view.getTerminalPort(state,
+			self.graph.view.getState(model.getTerminal(self.state.cell,
+			!self.isSource)), !self.isSource);
+		var otherCell = (other != null) ? other.cell : null;
+		var source = (self.isSource) ? state.cell : otherCell;
+		var target = (self.isSource) ? otherCell : state.cell;
+		
+		// Updates the error message of the handler
+		self.error = self.validateConnection(source, target);
+
+		return self.error == null;
+	};
+	
+	return marker;
+};
+
+/**
+ * Function: validateConnection
+ * 
+ * Returns the error message or an empty string if the connection for the
+ * given source, target pair is not valid. Otherwise it returns null. This
+ * implementation uses <mxGraph.getEdgeValidationError>.
+ * 
+ * Parameters:
+ * 
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ */
+mxEdgeHandler.prototype.validateConnection = function(source, target)
+{
+	return this.graph.getEdgeValidationError(this.state.cell, source, target);
+};
+
+/**
+ * Function: createBends
+ * 
+ * Creates and returns the bends used for modifying the edge. This is
+ * typically an array of <mxRectangleShapes>.
+ */
+ mxEdgeHandler.prototype.createBends = function()
+ {
+	var cell = this.state.cell;
+	var bends = [];
+
+	for (var i = 0; i < this.abspoints.length; i++)
+	{
+		if (this.isHandleVisible(i))
+		{
+			var source = i == 0;
+			var target = i == this.abspoints.length - 1;
+			var terminal = source || target;
+
+			if (terminal || this.graph.isCellBendable(cell))
+			{
+				(mxUtils.bind(this, function(index)
+				{
+					var bend = this.createHandleShape(index);
+					this.initBend(bend, mxUtils.bind(this, mxUtils.bind(this, function()
+					{
+						if (this.dblClickRemoveEnabled)
+						{
+							this.removePoint(this.state, index);
+						}
+					})));
+	
+					if (this.isHandleEnabled(i))
+					{
+						bend.setCursor((terminal) ? mxConstants.CURSOR_TERMINAL_HANDLE : mxConstants.CURSOR_BEND_HANDLE);
+					}
+					
+					bends.push(bend);
+				
+					if (!terminal)
+					{
+						this.points.push(new mxPoint(0,0));
+						bend.node.style.visibility = 'hidden';
+					}
+				}))(i);
+			}
+		}
+	}
+
+	return bends;
+};
+
+/**
+ * Function: createVirtualBends
+ * 
+ * Creates and returns the bends used for modifying the edge. This is
+ * typically an array of <mxRectangleShapes>.
+ */
+ mxEdgeHandler.prototype.createVirtualBends = function()
+ {
+	var cell = this.state.cell;
+	var last = this.abspoints[0];
+	var bends = [];
+
+	if (this.graph.isCellBendable(cell))
+	{
+		for (var i = 1; i < this.abspoints.length; i++)
+		{
+			(mxUtils.bind(this, function(bend)
+			{
+				this.initBend(bend);
+				bend.setCursor(mxConstants.CURSOR_VIRTUAL_BEND_HANDLE);
+				bends.push(bend);
+			}))(this.createHandleShape());
+		}
+	}
+
+	return bends;
+};
+
+/**
+ * Function: isHandleEnabled
+ * 
+ * Creates the shape used to display the given bend.
+ */
+mxEdgeHandler.prototype.isHandleEnabled = function(index)
+{
+	return true;
+};
+
+/**
+ * Function: isHandleVisible
+ * 
+ * Returns true if the handle at the given index is visible.
+ */
+mxEdgeHandler.prototype.isHandleVisible = function(index)
+{
+	var source = this.state.getVisibleTerminalState(true);
+	var target = this.state.getVisibleTerminalState(false);
+	var geo = this.graph.getCellGeometry(this.state.cell);
+	var edgeStyle = (geo != null) ? this.graph.view.getEdgeStyle(this.state, geo.points, source, target) : null;
+
+	return edgeStyle != mxEdgeStyle.EntityRelation || index == 0 || index == this.abspoints.length - 1;
+};
+
+/**
+ * Function: createHandleShape
+ * 
+ * Creates the shape used to display the given bend. Note that the index may be
+ * null for special cases, such as when called from
+ * <mxElbowEdgeHandler.createVirtualBend>. Only images and rectangles should be
+ * returned if support for HTML labels with not foreign objects is required.
+ * Index if null for virtual handles.
+ */
+mxEdgeHandler.prototype.createHandleShape = function(index)
+{
+	if (this.handleImage != null)
+	{
+		var shape = new mxImageShape(new mxRectangle(0, 0, this.handleImage.width, this.handleImage.height), this.handleImage.src);
+		
+		// Allows HTML rendering of the images
+		shape.preserveImageAspect = false;
+
+		return shape;
+	}
+	else
+	{
+		var s = mxConstants.HANDLE_SIZE;
+		
+		if (this.preferHtml)
+		{
+			s -= 1;
+		}
+		
+		return new mxRectangleShape(new mxRectangle(0, 0, s, s), mxConstants.HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR);
+	}
+};
+
+/**
+ * Function: createLabelHandleShape
+ * 
+ * Creates the shape used to display the the label handle.
+ */
+mxEdgeHandler.prototype.createLabelHandleShape = function()
+{
+	if (this.labelHandleImage != null)
+	{
+		var shape = new mxImageShape(new mxRectangle(0, 0, this.labelHandleImage.width, this.labelHandleImage.height), this.labelHandleImage.src);
+		
+		// Allows HTML rendering of the images
+		shape.preserveImageAspect = false;
+
+		return shape;
+	}
+	else
+	{
+		var s = mxConstants.LABEL_HANDLE_SIZE;
+		return new mxRectangleShape(new mxRectangle(0, 0, s, s), mxConstants.LABEL_HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR);
+	}
+};
+
+/**
+ * Function: initBend
+ * 
+ * Helper method to initialize the given bend.
+ * 
+ * Parameters:
+ * 
+ * bend - <mxShape> that represents the bend to be initialized.
+ */
+mxEdgeHandler.prototype.initBend = function(bend, dblClick)
+{
+	if (this.preferHtml)
+	{
+		bend.dialect = mxConstants.DIALECT_STRICTHTML;
+		bend.init(this.graph.container);
+	}
+	else
+	{
+		bend.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
+			mxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG;
+		bend.init(this.graph.getView().getOverlayPane());
+	}
+
+	mxEvent.redirectMouseEvents(bend.node, this.graph, this.state,
+			null, null, null, dblClick);
+	
+	// Fixes lost event tracking for images in quirks / IE8 standards
+	if (mxClient.IS_QUIRKS || document.documentMode == 8)
+	{
+		mxEvent.addListener(bend.node, 'dragstart', function(evt)
+		{
+			mxEvent.consume(evt);
+			
+			return false;
+		});
+	}
+	
+	if (mxClient.IS_TOUCH)
+	{
+		bend.node.setAttribute('pointer-events', 'none');
+	}
+};
+
+/**
+ * Function: getHandleForEvent
+ * 
+ * Returns the index of the handle for the given event.
+ */
+mxEdgeHandler.prototype.getHandleForEvent = function(me)
+{
+	// Connection highlight may consume events before they reach sizer handle
+	var tol = (!mxEvent.isMouseEvent(me.getEvent())) ? this.tolerance : 1;
+	var hit = (this.allowHandleBoundsCheck && (mxClient.IS_IE || tol > 0)) ?
+		new mxRectangle(me.getGraphX() - tol, me.getGraphY() - tol, 2 * tol, 2 * tol) : null;
+	var minDistSq = null;
+	var result = null;
+
+	function checkShape(shape)
+	{
+		if (shape != null && shape.node.style.display != 'none' && shape.node.style.visibility != 'hidden' &&
+			(me.isSource(shape) || (hit != null && mxUtils.intersects(shape.bounds, hit))))
+		{
+			var dx = me.getGraphX() - shape.bounds.getCenterX();
+			var dy = me.getGraphY() - shape.bounds.getCenterY();
+			var tmp = dx * dx + dy * dy;
+			
+			if (minDistSq == null || tmp <= minDistSq)
+			{
+				minDistSq = tmp;
+			
+				return true;
+			}
+		}
+		
+		return false;
+	}
+	
+	if (this.customHandles != null && this.isCustomHandleEvent(me))
+	{
+		// Inverse loop order to match display order
+		for (var i = this.customHandles.length - 1; i >= 0; i--)
+		{
+			if (checkShape(this.customHandles[i].shape))
+			{
+				// LATER: Return reference to active shape
+				return mxEvent.CUSTOM_HANDLE - i;
+			}
+		}
+	}
+
+	if (me.isSource(this.state.text) || checkShape(this.labelShape))
+	{
+		result = mxEvent.LABEL_HANDLE;
+	}
+	
+	if (this.bends != null)
+	{
+		for (var i = 0; i < this.bends.length; i++)
+		{
+			if (checkShape(this.bends[i]))
+			{
+				result = i;
+			}
+		}
+	}
+	
+	if (this.virtualBends != null && this.isAddVirtualBendEvent(me))
+	{
+		for (var i = 0; i < this.virtualBends.length; i++)
+		{
+			if (checkShape(this.virtualBends[i]))
+			{
+				result = mxEvent.VIRTUAL_HANDLE - i;
+			}
+		}
+	}
+
+	return result;
+};
+
+/**
+ * Function: isAddVirtualBendEvent
+ * 
+ * Returns true if the given event allows virtual bends to be added. This
+ * implementation returns true.
+ */
+mxEdgeHandler.prototype.isAddVirtualBendEvent = function(me)
+{
+	return true;
+};
+
+/**
+ * Function: isCustomHandleEvent
+ * 
+ * Returns true if the given event allows custom handles to be changed. This
+ * implementation returns true.
+ */
+mxEdgeHandler.prototype.isCustomHandleEvent = function(me)
+{
+	return true;
+};
+
+/**
+ * Function: mouseDown
+ * 
+ * Handles the event by checking if a special element of the handler
+ * was clicked, in which case the index parameter is non-null. The
+ * indices may be one of <LABEL_HANDLE> or the number of the respective
+ * control point. The source and target points are used for reconnecting
+ * the edge.
+ */
+mxEdgeHandler.prototype.mouseDown = function(sender, me)
+{
+	var handle = this.getHandleForEvent(me);
+	
+	if (this.bends != null && this.bends[handle] != null)
+	{
+		var b = this.bends[handle].bounds;
+		this.snapPoint = new mxPoint(b.getCenterX(), b.getCenterY());
+	}
+	
+	if (this.addEnabled && handle == null && this.isAddPointEvent(me.getEvent()))
+	{
+		this.addPoint(this.state, me.getEvent());
+		me.consume();
+	}
+	else if (handle != null && !me.isConsumed() && this.graph.isEnabled())
+	{
+		if (this.removeEnabled && this.isRemovePointEvent(me.getEvent()))
+		{
+			this.removePoint(this.state, handle);
+		}
+		else if (handle != mxEvent.LABEL_HANDLE || this.graph.isLabelMovable(me.getCell()))
+		{
+			if (handle <= mxEvent.VIRTUAL_HANDLE)
+			{
+				mxUtils.setOpacity(this.virtualBends[mxEvent.VIRTUAL_HANDLE - handle].node, 100);
+			}
+			
+			this.start(me.getX(), me.getY(), handle);
+		}
+		
+		me.consume();
+	}
+};
+
+/**
+ * Function: start
+ * 
+ * Starts the handling of the mouse gesture.
+ */
+mxEdgeHandler.prototype.start = function(x, y, index)
+{
+	this.startX = x;
+	this.startY = y;
+
+	this.isSource = (this.bends == null) ? false : index == 0;
+	this.isTarget = (this.bends == null) ? false : index == this.bends.length - 1;
+	this.isLabel = index == mxEvent.LABEL_HANDLE;
+
+	if (this.isSource || this.isTarget)
+	{
+		var cell = this.state.cell;
+		var terminal = this.graph.model.getTerminal(cell, this.isSource);
+
+		if ((terminal == null && this.graph.isTerminalPointMovable(cell, this.isSource)) ||
+			(terminal != null && this.graph.isCellDisconnectable(cell, terminal, this.isSource)))
+		{
+			this.index = index;
+		}
+	}
+	else
+	{
+		this.index = index;
+	}
+	
+	// Hides other custom handles
+	if (this.index <= mxEvent.CUSTOM_HANDLE && this.index > mxEvent.VIRTUAL_HANDLE)
+	{
+		if (this.customHandles != null)
+		{
+			for (var i = 0; i < this.customHandles.length; i++)
+			{
+				if (i != mxEvent.CUSTOM_HANDLE - this.index)
+				{
+					this.customHandles[i].setVisible(false);
+				}
+			}
+		}
+	}
+};
+
+/**
+ * Function: clonePreviewState
+ * 
+ * Returns a clone of the current preview state for the given point and terminal.
+ */
+mxEdgeHandler.prototype.clonePreviewState = function(point, terminal)
+{
+	return this.state.clone();
+};
+
+/**
+ * Function: getSnapToTerminalTolerance
+ * 
+ * Returns the tolerance for the guides. Default value is
+ * gridSize * scale / 2.
+ */
+mxEdgeHandler.prototype.getSnapToTerminalTolerance = function()
+{
+	return this.graph.gridSize * this.graph.view.scale / 2;
+};
+
+/**
+ * Function: updateHint
+ * 
+ * Hook for subclassers do show details while the handler is active.
+ */
+mxEdgeHandler.prototype.updateHint = function(me, point) { };
+
+/**
+ * Function: removeHint
+ * 
+ * Hooks for subclassers to hide details when the handler gets inactive.
+ */
+mxEdgeHandler.prototype.removeHint = function() { };
+
+/**
+ * Function: roundLength
+ * 
+ * Hook for rounding the unscaled width or height. This uses Math.round.
+ */
+mxEdgeHandler.prototype.roundLength = function(length)
+{
+	return Math.round(length);
+};
+
+/**
+ * Function: isSnapToTerminalsEvent
+ * 
+ * Returns true if <snapToTerminals> is true and if alt is not pressed.
+ */
+mxEdgeHandler.prototype.isSnapToTerminalsEvent = function(me)
+{
+	return this.snapToTerminals && !mxEvent.isAltDown(me.getEvent());
+};
+
+/**
+ * Function: getPointForEvent
+ * 
+ * Returns the point for the given event.
+ */
+mxEdgeHandler.prototype.getPointForEvent = function(me)
+{
+	var view = this.graph.getView();
+	var scale = view.scale;
+	var point = new mxPoint(this.roundLength(me.getGraphX() / scale) * scale,
+		this.roundLength(me.getGraphY() / scale) * scale);
+	
+	var tt = this.getSnapToTerminalTolerance();
+	var overrideX = false;
+	var overrideY = false;		
+	
+	if (tt > 0 && this.isSnapToTerminalsEvent(me))
+	{
+		function snapToPoint(pt)
+		{
+			if (pt != null)
+			{
+				var x = pt.x;
+
+				if (Math.abs(point.x - x) < tt)
+				{
+					point.x = x;
+					overrideX = true;
+				}
+				
+				var y = pt.y;
+
+				if (Math.abs(point.y - y) < tt)
+				{
+					point.y = y;
+					overrideY = true;
+				}
+			}
+		}
+		
+		// Temporary function
+		function snapToTerminal(terminal)
+		{
+			if (terminal != null)
+			{
+				snapToPoint.call(this, new mxPoint(view.getRoutingCenterX(terminal),
+						view.getRoutingCenterY(terminal)));
+			}
+		};
+
+		snapToTerminal.call(this, this.state.getVisibleTerminalState(true));
+		snapToTerminal.call(this, this.state.getVisibleTerminalState(false));
+
+		if (this.state.absolutePoints != null)
+		{
+			for (var i = 0; i < this.state.absolutePoints.length; i++)
+			{
+				snapToPoint.call(this, this.state.absolutePoints[i]);
+			}
+		}
+	}
+
+	if (this.graph.isGridEnabledEvent(me.getEvent()))
+	{
+		var tr = view.translate;
+		
+		if (!overrideX)
+		{
+			point.x = (this.graph.snap(point.x / scale - tr.x) + tr.x) * scale;
+		}
+		
+		if (!overrideY)
+		{
+			point.y = (this.graph.snap(point.y / scale - tr.y) + tr.y) * scale;
+		}
+	}
+	
+	return point;
+};
+
+/**
+ * Function: getPreviewTerminalState
+ * 
+ * Updates the given preview state taking into account the state of the constraint handler.
+ */
+mxEdgeHandler.prototype.getPreviewTerminalState = function(me)
+{
+	this.constraintHandler.update(me, this.isSource, true, me.isSource(this.marker.highlight.shape) ? null : this.currentPoint);
+	
+	if (this.constraintHandler.currentFocus != null && this.constraintHandler.currentConstraint != null)
+	{
+		// Handles special case where grid is large and connection point is at actual point in which
+		// case the outline is not followed as long as we're < gridSize / 2 away from that point
+		if (this.marker.highlight != null && this.marker.highlight.state != null &&
+			this.marker.highlight.state.cell == this.constraintHandler.currentFocus.cell)
+		{
+			// Direct repaint needed if cell already highlighted
+			if (this.marker.highlight.shape.stroke != 'transparent')
+			{
+				this.marker.highlight.shape.stroke = 'transparent';
+				this.marker.highlight.repaint();
+			}
+		}
+		else
+		{
+			this.marker.markCell(this.constraintHandler.currentFocus.cell, 'transparent');
+		}
+		
+		var model = this.graph.getModel();
+		var other = this.graph.view.getTerminalPort(this.state,
+				this.graph.view.getState(model.getTerminal(this.state.cell,
+			!this.isSource)), !this.isSource);
+		var otherCell = (other != null) ? other.cell : null;
+		var source = (this.isSource) ? this.constraintHandler.currentFocus.cell : otherCell;
+		var target = (this.isSource) ? otherCell : this.constraintHandler.currentFocus.cell;
+		
+		// Updates the error message of the handler
+		this.error = this.validateConnection(source, target);
+		var result = null;
+		
+		if (this.error == null)
+		{
+			result = this.constraintHandler.currentFocus;
+		}
+		else
+		{
+			this.constraintHandler.reset();
+		}
+		
+		return result;
+	}
+	else if (!this.graph.isIgnoreTerminalEvent(me.getEvent()))
+	{
+		this.marker.process(me);
+
+		return this.marker.getValidState();
+	}
+	else
+	{
+		this.marker.reset();
+		
+		return null;
+	}
+};
+
+/**
+ * Function: getPreviewPoints
+ * 
+ * Updates the given preview state taking into account the state of the constraint handler.
+ * 
+ * Parameters:
+ * 
+ * pt - <mxPoint> that contains the current pointer position.
+ * me - Optional <mxMouseEvent> that contains the current event.
+ */
+mxEdgeHandler.prototype.getPreviewPoints = function(pt, me)
+{
+	var geometry = this.graph.getCellGeometry(this.state.cell);
+	var points = (geometry.points != null) ? geometry.points.slice() : null;
+	var point = new mxPoint(pt.x, pt.y);
+	var result = null;
+	
+	if (!this.isSource && !this.isTarget)
+	{
+		this.convertPoint(point, false);
+		
+		if (points == null)
+		{
+			points = [point];
+		}
+		else
+		{
+			// Adds point from virtual bend
+			if (this.index <= mxEvent.VIRTUAL_HANDLE)
+			{
+				points.splice(mxEvent.VIRTUAL_HANDLE - this.index, 0, point);
+			}
+
+			// Removes point if dragged on terminal point
+			if (!this.isSource && !this.isTarget)
+			{
+				for (var i = 0; i < this.bends.length; i++)
+				{
+					if (i != this.index)
+					{
+						var bend = this.bends[i];
+						
+						if (bend != null && mxUtils.contains(bend.bounds, pt.x, pt.y))
+						{
+							if (this.index <= mxEvent.VIRTUAL_HANDLE)
+							{
+								points.splice(mxEvent.VIRTUAL_HANDLE - this.index, 1);
+							}
+							else
+							{
+								points.splice(this.index - 1, 1);
+							}
+							
+							result = points;
+						}
+					}
+				}
+				
+				// Removes point if user tries to straighten a segment
+				if (result == null && this.straightRemoveEnabled && (me == null || !mxEvent.isAltDown(me.getEvent())))
+				{
+					var tol = this.graph.tolerance * this.graph.tolerance;
+					var abs = this.state.absolutePoints.slice();
+					abs[this.index] = pt;
+					
+					// Handes special case where removing waypoint affects tolerance (flickering)
+					var src = this.state.getVisibleTerminalState(true);
+					
+					if (src != null)
+					{
+						var c = this.graph.getConnectionConstraint(this.state, src, true);
+						
+						// Checks if point is not fixed
+						if (c == null || this.graph.getConnectionPoint(src, c) == null)
+						{
+							abs[0] = new mxPoint(src.view.getRoutingCenterX(src), src.view.getRoutingCenterY(src));
+						}
+					}
+					
+					var trg = this.state.getVisibleTerminalState(false);
+					
+					if (trg != null)
+					{
+						var c = this.graph.getConnectionConstraint(this.state, trg, false);
+						
+						// Checks if point is not fixed
+						if (c == null || this.graph.getConnectionPoint(trg, c) == null)
+						{
+							abs[abs.length - 1] = new mxPoint(trg.view.getRoutingCenterX(trg), trg.view.getRoutingCenterY(trg));
+						}
+					}
+
+					function checkRemove(idx, tmp)
+					{
+						if (idx > 0 && idx < abs.length - 1 &&
+							mxUtils.ptSegDistSq(abs[idx - 1].x, abs[idx - 1].y,
+								abs[idx + 1].x, abs[idx + 1].y, tmp.x, tmp.y) < tol)
+						{
+							points.splice(idx - 1, 1);
+							result = points;
+						}
+					};
+					
+					// LATER: Check if other points can be removed if a segment is made straight
+					checkRemove(this.index, pt);
+				}
+			}
+			
+			// Updates existing point
+			if (result == null && this.index > mxEvent.VIRTUAL_HANDLE)
+			{
+				points[this.index - 1] = point;
+			}
+		}
+	}
+	else if (this.graph.resetEdgesOnConnect)
+	{
+		points = null;
+	}
+	
+	return (result != null) ? result : points;
+};
+
+/**
+ * Function: isOutlineConnectEvent
+ * 
+ * Returns true if <outlineConnect> is true and the source of the event is the outline shape
+ * or shift is pressed.
+ */
+mxEdgeHandler.prototype.isOutlineConnectEvent = function(me)
+{
+	var offset = mxUtils.getOffset(this.graph.container);
+	var evt = me.getEvent();
+	
+	var clientX = mxEvent.getClientX(evt);
+	var clientY = mxEvent.getClientY(evt);
+	
+	var doc = document.documentElement;
+	var left = (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0);
+	var top = (window.pageYOffset || doc.scrollTop)  - (doc.clientTop || 0);
+	
+	var gridX = this.currentPoint.x - this.graph.container.scrollLeft + offset.x - left;
+	var gridY = this.currentPoint.y - this.graph.container.scrollTop + offset.y - top;
+
+	return this.outlineConnect && !mxEvent.isShiftDown(me.getEvent()) &&
+		(me.isSource(this.marker.highlight.shape) ||
+		(mxEvent.isAltDown(me.getEvent()) && me.getState() != null) ||
+		this.marker.highlight.isHighlightAt(clientX, clientY) ||
+		((gridX != clientX || gridY != clientY) && me.getState() == null &&
+		this.marker.highlight.isHighlightAt(gridX, gridY)));
+};
+
+/**
+ * Function: updatePreviewState
+ * 
+ * Updates the given preview state taking into account the state of the constraint handler.
+ */
+mxEdgeHandler.prototype.updatePreviewState = function(edge, point, terminalState, me, outline)
+{
+	// Computes the points for the edge style and terminals
+	var sourceState = (this.isSource) ? terminalState : this.state.getVisibleTerminalState(true);
+	var targetState = (this.isTarget) ? terminalState : this.state.getVisibleTerminalState(false);
+	
+	var sourceConstraint = this.graph.getConnectionConstraint(edge, sourceState, true);
+	var targetConstraint = this.graph.getConnectionConstraint(edge, targetState, false);
+
+	var constraint = this.constraintHandler.currentConstraint;
+
+	if (constraint == null && outline)
+	{
+		if (terminalState != null)
+		{
+			// Handles special case where mouse is on outline away from actual end point
+			// in which case the grid is ignored and mouse point is used instead
+			if (me.isSource(this.marker.highlight.shape))
+			{
+				point = new mxPoint(me.getGraphX(), me.getGraphY());
+			}
+			
+			constraint = this.graph.getOutlineConstraint(point, terminalState, me);
+			this.constraintHandler.setFocus(me, terminalState, this.isSource);
+			this.constraintHandler.currentConstraint = constraint;
+			this.constraintHandler.currentPoint = point;
+		}
+		else
+		{
+			constraint = new mxConnectionConstraint();
+		}
+	}
+	
+	if (this.outlineConnect && this.marker.highlight != null && this.marker.highlight.shape != null)
+	{
+		var s = this.graph.view.scale;
+		
+		if (this.constraintHandler.currentConstraint != null &&
+			this.constraintHandler.currentFocus != null)
+		{
+			this.marker.highlight.shape.stroke = (outline) ? mxConstants.OUTLINE_HIGHLIGHT_COLOR : 'transparent';
+			this.marker.highlight.shape.strokewidth = mxConstants.OUTLINE_HIGHLIGHT_STROKEWIDTH / s / s;
+			this.marker.highlight.repaint();
+		}
+		else if (this.marker.hasValidState())
+		{
+			this.marker.highlight.shape.stroke = (this.marker.getValidState() == me.getState()) ?
+				mxConstants.DEFAULT_VALID_COLOR : 'transparent';
+			this.marker.highlight.shape.strokewidth = mxConstants.HIGHLIGHT_STROKEWIDTH / s / s;
+			this.marker.highlight.repaint();
+		}
+	}
+	
+	if (this.isSource)
+	{
+		sourceConstraint = constraint;
+	}
+	else if (this.isTarget)
+	{
+		targetConstraint = constraint;
+	}
+	
+	if (this.isSource || this.isTarget)
+	{
+		if (constraint != null && constraint.point != null)
+		{
+			edge.style[(this.isSource) ? mxConstants.STYLE_EXIT_X : mxConstants.STYLE_ENTRY_X] = constraint.point.x;
+			edge.style[(this.isSource) ? mxConstants.STYLE_EXIT_Y : mxConstants.STYLE_ENTRY_Y] = constraint.point.y;
+		}
+		else
+		{
+			delete edge.style[(this.isSource) ? mxConstants.STYLE_EXIT_X : mxConstants.STYLE_ENTRY_X];
+			delete edge.style[(this.isSource) ? mxConstants.STYLE_EXIT_Y : mxConstants.STYLE_ENTRY_Y];
+		}
+	}
+	
+	edge.setVisibleTerminalState(sourceState, true);
+	edge.setVisibleTerminalState(targetState, false);
+	
+	if (!this.isSource || sourceState != null)
+	{
+		edge.view.updateFixedTerminalPoint(edge, sourceState, true, sourceConstraint);
+	}
+	
+	if (!this.isTarget || targetState != null)
+	{
+		edge.view.updateFixedTerminalPoint(edge, targetState, false, targetConstraint);
+	}
+	
+	if ((this.isSource || this.isTarget) && terminalState == null)
+	{
+		edge.setAbsoluteTerminalPoint(point, this.isSource);
+
+		if (this.marker.getMarkedState() == null)
+		{
+			this.error = (this.graph.allowDanglingEdges) ? null : '';
+		}
+	}
+	
+	edge.view.updatePoints(edge, this.points, sourceState, targetState);
+	edge.view.updateFloatingTerminalPoints(edge, sourceState, targetState);
+};
+
+/**
+ * Function: mouseMove
+ * 
+ * Handles the event by updating the preview.
+ */
+mxEdgeHandler.prototype.mouseMove = function(sender, me)
+{
+	if (this.index != null && this.marker != null)
+	{
+		this.currentPoint = this.getPointForEvent(me);
+		this.error = null;
+		
+		// Uses the current point from the constraint handler if available
+		if (!this.graph.isIgnoreTerminalEvent(me.getEvent()) && mxEvent.isShiftDown(me.getEvent()) && this.snapPoint != null)
+		{
+			if (Math.abs(this.snapPoint.x - this.currentPoint.x) < Math.abs(this.snapPoint.y - this.currentPoint.y))
+			{
+				this.currentPoint.x = this.snapPoint.x;
+			}
+			else
+			{
+				this.currentPoint.y = this.snapPoint.y;
+			}
+		}
+		
+		if (this.index <= mxEvent.CUSTOM_HANDLE && this.index > mxEvent.VIRTUAL_HANDLE)
+		{
+			if (this.customHandles != null)
+			{
+				this.customHandles[mxEvent.CUSTOM_HANDLE - this.index].processEvent(me);
+			}
+		}
+		else if (this.isLabel)
+		{
+			this.label.x = this.currentPoint.x;
+			this.label.y = this.currentPoint.y;
+		}
+		else
+		{
+			this.points = this.getPreviewPoints(this.currentPoint, me);
+			var terminalState = (this.isSource || this.isTarget) ? this.getPreviewTerminalState(me) : null;
+
+			if (this.constraintHandler.currentConstraint != null &&
+				this.constraintHandler.currentFocus != null &&
+				this.constraintHandler.currentPoint != null)
+			{
+				this.currentPoint = this.constraintHandler.currentPoint.clone();
+			}
+			else if (this.outlineConnect)
+			{
+				// Need to check outline before cloning terminal state
+				var outline = (this.isSource || this.isTarget) ? this.isOutlineConnectEvent(me) : false
+						
+				if (outline)
+				{
+					terminalState = this.marker.highlight.state;
+				}
+				else if (terminalState != null && terminalState != me.getState() && this.marker.highlight.shape != null)
+				{
+					this.marker.highlight.shape.stroke = 'transparent';
+					this.marker.highlight.repaint();
+					terminalState = null;
+				}
+			}
+			
+			var clone = this.clonePreviewState(this.currentPoint, (terminalState != null) ? terminalState.cell : null);
+			this.updatePreviewState(clone, this.currentPoint, terminalState, me, outline);
+
+			// Sets the color of the preview to valid or invalid, updates the
+			// points of the preview and redraws
+			var color = (this.error == null) ? this.marker.validColor : this.marker.invalidColor;
+			this.setPreviewColor(color);
+			this.abspoints = clone.absolutePoints;
+			this.active = true;
+		}
+
+		// This should go before calling isOutlineConnectEvent above. As a workaround
+		// we add an offset of gridSize to the hint to avoid problem with hit detection
+		// in highlight.isHighlightAt (which uses comonentFromPoint)
+		this.updateHint(me, this.currentPoint);
+		this.drawPreview();
+		mxEvent.consume(me.getEvent());
+		me.consume();
+	}
+	// Workaround for disabling the connect highlight when over handle
+	else if (mxClient.IS_IE && this.getHandleForEvent(me) != null)
+	{
+		me.consume(false);
+	}
+};
+
+/**
+ * Function: mouseUp
+ * 
+ * Handles the event to applying the previewed changes on the edge by
+ * using <moveLabel>, <connect> or <changePoints>.
+ */
+mxEdgeHandler.prototype.mouseUp = function(sender, me)
+{
+	// Workaround for wrong event source in Webkit
+	if (this.index != null && this.marker != null)
+	{
+		var edge = this.state.cell;
+		
+		// Ignores event if mouse has not been moved
+		if (me.getX() != this.startX || me.getY() != this.startY)
+		{
+			var clone = !this.graph.isIgnoreTerminalEvent(me.getEvent()) && this.graph.isCloneEvent(me.getEvent()) &&
+				this.cloneEnabled && this.graph.isCellsCloneable();
+			
+			// Displays the reason for not carriying out the change
+			// if there is an error message with non-zero length
+			if (this.error != null)
+			{
+				if (this.error.length > 0)
+				{
+					this.graph.validationAlert(this.error);
+				}
+			}
+			else if (this.index <= mxEvent.CUSTOM_HANDLE && this.index > mxEvent.VIRTUAL_HANDLE)
+			{
+				if (this.customHandles != null)
+				{
+					var model = this.graph.getModel();
+					
+					model.beginUpdate();
+					try
+					{
+						this.customHandles[mxEvent.CUSTOM_HANDLE - this.index].execute();
+					}
+					finally
+					{
+						model.endUpdate();
+					}
+				}
+			}
+			else if (this.isLabel)
+			{
+				this.moveLabel(this.state, this.label.x, this.label.y);
+			}
+			else if (this.isSource || this.isTarget)
+			{
+				var terminal = null;
+				
+				if (this.constraintHandler.currentConstraint != null &&
+					this.constraintHandler.currentFocus != null)
+				{
+					terminal = this.constraintHandler.currentFocus.cell;
+				}
+				
+				if (terminal == null && this.marker.hasValidState() && this.marker.highlight != null &&
+					this.marker.highlight.shape != null &&
+					this.marker.highlight.shape.stroke != 'transparent' &&
+					this.marker.highlight.shape.stroke != 'white')
+				{
+					terminal = this.marker.validState.cell;
+				}
+				
+				if (terminal != null)
+				{
+					edge = this.connect(edge, terminal, this.isSource, clone, me);
+				}
+				else if (this.graph.isAllowDanglingEdges())
+				{
+					var pt = this.abspoints[(this.isSource) ? 0 : this.abspoints.length - 1];
+					pt.x = this.roundLength(pt.x / this.graph.view.scale - this.graph.view.translate.x);
+					pt.y = this.roundLength(pt.y / this.graph.view.scale - this.graph.view.translate.y);
+
+					var pstate = this.graph.getView().getState(
+							this.graph.getModel().getParent(edge));
+							
+					if (pstate != null)
+					{
+						pt.x -= pstate.origin.x;
+						pt.y -= pstate.origin.y;
+					}
+					
+					pt.x -= this.graph.panDx / this.graph.view.scale;
+					pt.y -= this.graph.panDy / this.graph.view.scale;
+										
+					// Destroys and recreates this handler
+					edge = this.changeTerminalPoint(edge, pt, this.isSource, clone);
+				}
+			}
+			else if (this.active)
+			{
+				edge = this.changePoints(edge, this.points, clone);
+			}
+			else
+			{
+				this.graph.getView().invalidate(this.state.cell);
+				this.graph.getView().validate(this.state.cell);						
+			}
+		}
+		
+		// Resets the preview color the state of the handler if this
+		// handler has not been recreated
+		if (this.marker != null)
+		{
+			this.reset();
+
+			// Updates the selection if the edge has been cloned
+			if (edge != this.state.cell)
+			{
+				this.graph.setSelectionCell(edge);
+			}
+		}
+
+		me.consume();
+	}
+};
+
+/**
+ * Function: reset
+ * 
+ * Resets the state of this handler.
+ */
+mxEdgeHandler.prototype.reset = function()
+{
+	this.error = null;
+	this.index = null;
+	this.label = null;
+	this.points = null;
+	this.snapPoint = null;
+	this.active = false;
+	this.isLabel = false;
+	this.isSource = false;
+	this.isTarget = false;
+	
+	if (this.livePreview && this.sizers != null)
+	{
+		for (var i = 0; i < this.sizers.length; i++)
+		{
+			if (this.sizers[i] != null)
+			{
+				this.sizers[i].node.style.display = '';
+			}
+		}
+	}
+
+	if (this.marker != null)
+	{
+		this.marker.reset();
+	}
+	
+	if (this.constraintHandler != null)
+	{
+		this.constraintHandler.reset();
+	}
+	
+	if (this.customHandles != null)
+	{
+		for (var i = 0; i < this.customHandles.length; i++)
+		{
+			this.customHandles[i].reset();
+		}
+	}
+
+	this.setPreviewColor(mxConstants.EDGE_SELECTION_COLOR);
+	this.removeHint();
+	this.redraw();
+};
+
+/**
+ * Function: setPreviewColor
+ * 
+ * Sets the color of the preview to the given value.
+ */
+mxEdgeHandler.prototype.setPreviewColor = function(color)
+{
+	if (this.shape != null)
+	{
+		this.shape.stroke = color;
+	}
+};
+
+
+/**
+ * Function: convertPoint
+ * 
+ * Converts the given point in-place from screen to unscaled, untranslated
+ * graph coordinates and applies the grid. Returns the given, modified
+ * point instance.
+ * 
+ * Parameters:
+ * 
+ * point - <mxPoint> to be converted.
+ * gridEnabled - Boolean that specifies if the grid should be applied.
+ */
+mxEdgeHandler.prototype.convertPoint = function(point, gridEnabled)
+{
+	var scale = this.graph.getView().getScale();
+	var tr = this.graph.getView().getTranslate();
+		
+	if (gridEnabled)
+	{
+		point.x = this.graph.snap(point.x);
+		point.y = this.graph.snap(point.y);
+	}
+	
+	point.x = Math.round(point.x / scale - tr.x);
+	point.y = Math.round(point.y / scale - tr.y);
+
+	var pstate = this.graph.getView().getState(
+		this.graph.getModel().getParent(this.state.cell));
+
+	if (pstate != null)
+	{
+		point.x -= pstate.origin.x;
+		point.y -= pstate.origin.y;
+	}
+
+	return point;
+};
+
+/**
+ * Function: moveLabel
+ * 
+ * Changes the coordinates for the label of the given edge.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> that represents the edge.
+ * x - Integer that specifies the x-coordinate of the new location.
+ * y - Integer that specifies the y-coordinate of the new location.
+ */
+mxEdgeHandler.prototype.moveLabel = function(edgeState, x, y)
+{
+	var model = this.graph.getModel();
+	var geometry = model.getGeometry(edgeState.cell);
+	
+	if (geometry != null)
+	{
+		var scale = this.graph.getView().scale;
+		geometry = geometry.clone();
+		
+		if (geometry.relative)
+		{
+			// Resets the relative location stored inside the geometry
+			var pt = this.graph.getView().getRelativePoint(edgeState, x, y);
+			geometry.x = Math.round(pt.x * 10000) / 10000;
+			geometry.y = Math.round(pt.y);
+			
+			// Resets the offset inside the geometry to find the offset
+			// from the resulting point
+			geometry.offset = new mxPoint(0, 0);
+			var pt = this.graph.view.getPoint(edgeState, geometry);
+			geometry.offset = new mxPoint(Math.round((x - pt.x) / scale), Math.round((y - pt.y) / scale));
+		}
+		else
+		{
+			var points = edgeState.absolutePoints;
+			var p0 = points[0];
+			var pe = points[points.length - 1];
+			
+			if (p0 != null && pe != null)
+			{
+				var cx = p0.x + (pe.x - p0.x) / 2;
+				var cy = p0.y + (pe.y - p0.y) / 2;
+				
+				geometry.offset = new mxPoint(Math.round((x - cx) / scale), Math.round((y - cy) / scale));
+				geometry.x = 0;
+				geometry.y = 0;
+			}
+		}
+
+		model.setGeometry(edgeState.cell, geometry);
+	}
+};
+
+/**
+ * Function: connect
+ * 
+ * Changes the terminal or terminal point of the given edge in the graph
+ * model.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> that represents the edge to be reconnected.
+ * terminal - <mxCell> that represents the new terminal.
+ * isSource - Boolean indicating if the new terminal is the source or
+ * target terminal.
+ * isClone - Boolean indicating if the new connection should be a clone of
+ * the old edge.
+ * me - <mxMouseEvent> that contains the mouse up event.
+ */
+mxEdgeHandler.prototype.connect = function(edge, terminal, isSource, isClone, me)
+{
+	var model = this.graph.getModel();
+	var parent = model.getParent(edge);
+	
+	model.beginUpdate();
+	try
+	{
+		// Clones and adds the cell
+		if (isClone)
+		{
+			var clone = this.graph.cloneCells([edge])[0];
+			model.add(parent, clone, model.getChildCount(parent));
+			
+			var other = model.getTerminal(edge, !isSource);
+			this.graph.connectCell(clone, other, !isSource);
+			
+			edge = clone;
+		}
+
+		var constraint = this.constraintHandler.currentConstraint;
+		
+		if (constraint == null)
+		{
+			constraint = new mxConnectionConstraint();
+		}
+
+		this.graph.connectCell(edge, terminal, isSource, constraint);
+	}
+	finally
+	{
+		model.endUpdate();
+	}
+	
+	return edge;
+};
+
+/**
+ * Function: changeTerminalPoint
+ * 
+ * Changes the terminal point of the given edge.
+ */
+mxEdgeHandler.prototype.changeTerminalPoint = function(edge, point, isSource, clone)
+{
+	var model = this.graph.getModel();
+
+	model.beginUpdate();
+	try
+	{
+		if (clone)
+		{
+			var parent = model.getParent(edge);
+			var terminal = model.getTerminal(edge, !isSource);
+			edge = this.graph.cloneCells([edge])[0];
+			model.add(parent, edge, model.getChildCount(parent));
+			model.setTerminal(edge, terminal, !isSource);
+		}
+
+		var geo = model.getGeometry(edge);
+		
+		if (geo != null)
+		{
+			geo = geo.clone();
+			geo.setTerminalPoint(point, isSource);
+			model.setGeometry(edge, geo);
+			this.graph.connectCell(edge, null, isSource, new mxConnectionConstraint());
+		}
+	}
+	finally
+	{
+		model.endUpdate();
+	}
+	
+	return edge;
+};
+
+/**
+ * Function: changePoints
+ * 
+ * Changes the control points of the given edge in the graph model.
+ */
+mxEdgeHandler.prototype.changePoints = function(edge, points, clone)
+{
+	var model = this.graph.getModel();
+	model.beginUpdate();
+	try
+	{
+		if (clone)
+		{
+			var parent = model.getParent(edge);
+			var source = model.getTerminal(edge, true);
+			var target = model.getTerminal(edge, false);
+			edge = this.graph.cloneCells([edge])[0];
+			model.add(parent, edge, model.getChildCount(parent));
+			model.setTerminal(edge, source, true);
+			model.setTerminal(edge, target, false);
+		}
+		
+		var geo = model.getGeometry(edge);
+		
+		if (geo != null)
+		{
+			geo = geo.clone();
+			geo.points = points;
+			
+			model.setGeometry(edge, geo);
+		}
+	}
+	finally
+	{
+		model.endUpdate();
+	}
+	
+	return edge;
+};
+
+/**
+ * Function: addPoint
+ * 
+ * Adds a control point for the given state and event.
+ */
+mxEdgeHandler.prototype.addPoint = function(state, evt)
+{
+	var pt = mxUtils.convertPoint(this.graph.container, mxEvent.getClientX(evt),
+			mxEvent.getClientY(evt));
+	var gridEnabled = this.graph.isGridEnabledEvent(evt);
+	this.convertPoint(pt, gridEnabled);
+	this.addPointAt(state, pt.x, pt.y);
+	mxEvent.consume(evt);
+};
+
+/**
+ * Function: addPointAt
+ * 
+ * Adds a control point at the given point.
+ */
+mxEdgeHandler.prototype.addPointAt = function(state, x, y)
+{
+	var geo = this.graph.getCellGeometry(state.cell);
+	var pt = new mxPoint(x, y);
+	
+	if (geo != null)
+	{
+		geo = geo.clone();
+		var t = this.graph.view.translate;
+		var s = this.graph.view.scale;
+		var offset = new mxPoint(t.x * s, t.y * s);
+		
+		var parent = this.graph.model.getParent(this.state.cell);
+		
+		if (this.graph.model.isVertex(parent))
+		{
+			var pState = this.graph.view.getState(parent);
+			offset = new mxPoint(pState.x, pState.y);
+		}
+		
+		var index = mxUtils.findNearestSegment(state, pt.x * s + offset.x, pt.y * s + offset.y);
+
+		if (geo.points == null)
+		{
+			geo.points = [pt];
+		}
+		else
+		{
+			geo.points.splice(index, 0, pt);
+		}
+		
+		this.graph.getModel().setGeometry(state.cell, geo);
+		this.refresh();	
+		this.redraw();
+	}
+};
+
+/**
+ * Function: removePoint
+ * 
+ * Removes the control point at the given index from the given state.
+ */
+mxEdgeHandler.prototype.removePoint = function(state, index)
+{
+	if (index > 0 && index < this.abspoints.length - 1)
+	{
+		var geo = this.graph.getCellGeometry(this.state.cell);
+		
+		if (geo != null && geo.points != null)
+		{
+			geo = geo.clone();
+			geo.points.splice(index - 1, 1);
+			this.graph.getModel().setGeometry(state.cell, geo);
+			this.refresh();
+			this.redraw();
+		}
+	}
+};
+
+/**
+ * Function: getHandleFillColor
+ * 
+ * Returns the fillcolor for the handle at the given index.
+ */
+mxEdgeHandler.prototype.getHandleFillColor = function(index)
+{
+	var isSource = index == 0;
+	var cell = this.state.cell;
+	var terminal = this.graph.getModel().getTerminal(cell, isSource);
+	var color = mxConstants.HANDLE_FILLCOLOR;
+	
+	if ((terminal != null && !this.graph.isCellDisconnectable(cell, terminal, isSource)) ||
+		(terminal == null && !this.graph.isTerminalPointMovable(cell, isSource)))
+	{
+		color = mxConstants.LOCKED_HANDLE_FILLCOLOR;
+	}
+	else if (terminal != null && this.graph.isCellDisconnectable(cell, terminal, isSource))
+	{
+		color = mxConstants.CONNECT_HANDLE_FILLCOLOR;
+	}
+	
+	return color;
+};
+
+/**
+ * Function: redraw
+ * 
+ * Redraws the preview, and the bends- and label control points.
+ */
+mxEdgeHandler.prototype.redraw = function()
+{
+	this.abspoints = this.state.absolutePoints.slice();
+	this.redrawHandles();
+	
+	var g = this.graph.getModel().getGeometry(this.state.cell);
+	var pts = g.points;
+
+	if (this.bends != null && this.bends.length > 0)
+	{
+		if (pts != null)
+		{
+			if (this.points == null)
+			{
+				this.points = [];
+			}
+			
+			for (var i = 1; i < this.bends.length - 1; i++)
+			{
+				if (this.bends[i] != null && this.abspoints[i] != null)
+				{
+					this.points[i - 1] = pts[i - 1];
+				}
+			}
+		}
+	}
+
+	this.drawPreview();
+};
+
+/**
+ * Function: redrawHandles
+ * 
+ * Redraws the handles.
+ */
+mxEdgeHandler.prototype.redrawHandles = function()
+{
+	var cell = this.state.cell;
+
+	// Updates the handle for the label position
+	var b = this.labelShape.bounds;
+	this.label = new mxPoint(this.state.absoluteOffset.x, this.state.absoluteOffset.y);
+	this.labelShape.bounds = new mxRectangle(Math.round(this.label.x - b.width / 2),
+		Math.round(this.label.y - b.height / 2), b.width, b.height);
+
+	// Shows or hides the label handle depending on the label
+	var lab = this.graph.getLabel(cell);
+	this.labelShape.visible = (lab != null && lab.length > 0 && this.graph.isLabelMovable(cell));
+	
+	if (this.bends != null && this.bends.length > 0)
+	{
+		var n = this.abspoints.length - 1;
+		
+		var p0 = this.abspoints[0];
+		var x0 = p0.x;
+		var y0 = p0.y;
+		
+		b = this.bends[0].bounds;
+		this.bends[0].bounds = new mxRectangle(Math.floor(x0 - b.width / 2),
+				Math.floor(y0 - b.height / 2), b.width, b.height);
+		this.bends[0].fill = this.getHandleFillColor(0);
+		this.bends[0].redraw();
+		
+		if (this.manageLabelHandle)
+		{
+			this.checkLabelHandle(this.bends[0].bounds);
+		}
+				
+		var pe = this.abspoints[n];
+		var xn = pe.x;
+		var yn = pe.y;
+		
+		var bn = this.bends.length - 1;
+		b = this.bends[bn].bounds;
+		this.bends[bn].bounds = new mxRectangle(Math.floor(xn - b.width / 2),
+				Math.floor(yn - b.height / 2), b.width, b.height);
+		this.bends[bn].fill = this.getHandleFillColor(bn);
+		this.bends[bn].redraw();
+				
+		if (this.manageLabelHandle)
+		{
+			this.checkLabelHandle(this.bends[bn].bounds);
+		}
+		
+		this.redrawInnerBends(p0, pe);
+	}
+
+	if (this.abspoints != null && this.virtualBends != null && this.virtualBends.length > 0)
+	{
+		var last = this.abspoints[0];
+		
+		for (var i = 0; i < this.virtualBends.length; i++)
+		{
+			if (this.virtualBends[i] != null && this.abspoints[i + 1] != null)
+			{
+				var pt = this.abspoints[i + 1];
+				var b = this.virtualBends[i];
+				var x = last.x + (pt.x - last.x) / 2;
+				var y = last.y + (pt.y - last.y) / 2;
+				b.bounds = new mxRectangle(Math.floor(x - b.bounds.width / 2),
+						Math.floor(y - b.bounds.height / 2), b.bounds.width, b.bounds.height);
+				b.redraw();
+				mxUtils.setOpacity(b.node, this.virtualBendOpacity);
+				last = pt;
+				
+				if (this.manageLabelHandle)
+				{
+					this.checkLabelHandle(b.bounds);
+				}
+			}
+		}
+	}
+	
+	if (this.labelShape != null)
+	{
+		this.labelShape.redraw();
+	}
+	
+	if (this.customHandles != null)
+	{
+		for (var i = 0; i < this.customHandles.length; i++)
+		{
+			this.customHandles[i].redraw();
+		}
+	}
+};
+
+/**
+ * Function: hideHandles
+ * 
+ * Shortcut to <hideSizers>.
+ */
+mxEdgeHandler.prototype.setHandlesVisible = function(visible)
+{
+	if (this.bends != null)
+	{
+		for (var i = 0; i < this.bends.length; i++)
+		{
+			this.bends[i].node.style.display = (visible) ? '' : 'none';
+		}
+	}
+	
+	if (this.virtualBends != null)
+	{
+		for (var i = 0; i < this.virtualBends.length; i++)
+		{
+			this.virtualBends[i].node.style.display = (visible) ? '' : 'none';
+		}
+	}
+
+	if (this.labelShape != null)
+	{
+		this.labelShape.node.style.display = (visible) ? '' : 'none';
+	}
+	
+	if (this.customHandles != null)
+	{
+		for (var i = 0; i < this.customHandles.length; i++)
+		{
+			this.customHandles[i].setVisible(visible);
+		}
+	}
+};
+
+/**
+ * Function: redrawInnerBends
+ * 
+ * Updates and redraws the inner bends.
+ * 
+ * Parameters:
+ * 
+ * p0 - <mxPoint> that represents the location of the first point.
+ * pe - <mxPoint> that represents the location of the last point.
+ */
+mxEdgeHandler.prototype.redrawInnerBends = function(p0, pe)
+{
+	for (var i = 1; i < this.bends.length - 1; i++)
+	{
+		if (this.bends[i] != null)
+		{
+			if (this.abspoints[i] != null)
+			{
+				var x = this.abspoints[i].x;
+				var y = this.abspoints[i].y;
+				
+				var b = this.bends[i].bounds;
+				this.bends[i].node.style.visibility = 'visible';
+				this.bends[i].bounds = new mxRectangle(Math.round(x - b.width / 2),
+						Math.round(y - b.height / 2), b.width, b.height);
+				
+				if (this.manageLabelHandle)
+				{
+					this.checkLabelHandle(this.bends[i].bounds);
+				}
+				else if (this.handleImage == null && this.labelShape.visible && mxUtils.intersects(this.bends[i].bounds, this.labelShape.bounds))
+				{
+					w = mxConstants.HANDLE_SIZE + 3;
+					h = mxConstants.HANDLE_SIZE + 3;
+					this.bends[i].bounds = new mxRectangle(Math.round(x - w / 2), Math.round(y - h / 2), w, h);
+				}
+				
+				this.bends[i].redraw();
+			}
+			else
+			{
+				this.bends[i].destroy();
+				this.bends[i] = null;
+			}
+		}
+	}
+};
+
+/**
+ * Function: checkLabelHandle
+ * 
+ * Checks if the label handle intersects the given bounds and moves it if it
+ * intersects.
+ */
+mxEdgeHandler.prototype.checkLabelHandle = function(b)
+{
+	if (this.labelShape != null)
+	{
+		var b2 = this.labelShape.bounds;
+		
+		if (mxUtils.intersects(b, b2))
+		{
+			if (b.getCenterY() < b2.getCenterY())
+			{
+				b2.y = b.y + b.height;
+			}
+			else
+			{
+				b2.y = b.y - b2.height;
+			}
+		}
+	}
+};
+
+/**
+ * Function: drawPreview
+ * 
+ * Redraws the preview.
+ */
+mxEdgeHandler.prototype.drawPreview = function()
+{
+	if (this.isLabel)
+	{
+		var b = this.labelShape.bounds;
+		var bounds = new mxRectangle(Math.round(this.label.x - b.width / 2),
+				Math.round(this.label.y - b.height / 2), b.width, b.height);
+		this.labelShape.bounds = bounds;
+		this.labelShape.redraw();
+	}
+	else if (this.shape != null)
+	{
+		this.shape.apply(this.state);
+		this.shape.points = this.abspoints;
+		this.shape.scale = this.state.view.scale;
+		this.shape.isDashed = this.isSelectionDashed();
+		this.shape.stroke = this.getSelectionColor();
+		this.shape.strokewidth = this.getSelectionStrokeWidth() / this.shape.scale / this.shape.scale;
+		this.shape.isShadow = false;
+		this.shape.redraw();
+	}
+	
+	if (this.parentHighlight != null)
+	{
+		this.parentHighlight.redraw();
+	}
+};
+
+/**
+ * Function: refresh
+ * 
+ * Refreshes the bends of this handler.
+ */
+mxEdgeHandler.prototype.refresh = function()
+{
+	this.abspoints = this.getSelectionPoints(this.state);
+	this.points = [];
+
+	if (this.shape != null)
+	{
+		this.shape.points = this.abspoints;
+	}
+	
+	if (this.bends != null)
+	{
+		this.destroyBends(this.bends);
+		this.bends = this.createBends();
+	}
+	
+	if (this.virtualBends != null)
+	{
+		this.destroyBends(this.virtualBends);
+		this.virtualBends = this.createVirtualBends();
+	}
+	
+	if (this.customHandles != null)
+	{
+		this.destroyBends(this.customHandles);
+		this.customHandles = this.createCustomHandles();
+	}
+	
+	// Puts label node on top of bends
+	if (this.labelShape != null && this.labelShape.node != null && this.labelShape.node.parentNode != null)
+	{
+		this.labelShape.node.parentNode.appendChild(this.labelShape.node);
+	}
+};
+
+/**
+ * Function: destroyBends
+ * 
+ * Destroys all elements in <bends>.
+ */
+mxEdgeHandler.prototype.destroyBends = function(bends)
+{
+	if (bends != null)
+	{
+		for (var i = 0; i < bends.length; i++)
+		{
+			if (bends[i] != null)
+			{
+				bends[i].destroy();
+			}
+		}
+	}
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the handler and all its resources and DOM nodes. This does
+ * normally not need to be called as handlers are destroyed automatically
+ * when the corresponding cell is deselected.
+ */
+mxEdgeHandler.prototype.destroy = function()
+{
+	if (this.escapeHandler != null)
+	{
+		this.state.view.graph.removeListener(this.escapeHandler);
+		this.escapeHandler = null;
+	}
+	
+	if (this.marker != null)
+	{
+		this.marker.destroy();
+		this.marker = null;
+	}
+	
+	if (this.shape != null)
+	{
+		this.shape.destroy();
+		this.shape = null;
+	}
+	
+	if (this.parentHighlight != null)
+	{
+		this.parentHighlight.destroy();
+		this.parentHighlight = null;
+	}
+	
+	if (this.labelShape != null)
+	{
+		this.labelShape.destroy();
+		this.labelShape = null;
+	}
+
+	if (this.constraintHandler != null)
+	{
+		this.constraintHandler.destroy();
+		this.constraintHandler = null;
+	}
+	
+	this.destroyBends(this.virtualBends);
+	this.virtualBends = null;
+	
+	this.destroyBends(this.customHandles);
+	this.customHandles = null;
+
+	this.destroyBends(this.bends);
+	this.bends = null;
+	
+	this.removeHint();
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxElbowEdgeHandler
+ *
+ * Graph event handler that reconnects edges and modifies control points and
+ * the edge label location. Uses <mxTerminalMarker> for finding and
+ * highlighting new source and target vertices. This handler is automatically
+ * created in <mxGraph.createHandler>. It extends <mxEdgeHandler>.
+ * 
+ * Constructor: mxEdgeHandler
+ *
+ * Constructs an edge handler for the specified <mxCellState>.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> of the cell to be modified.
+ */
+function mxElbowEdgeHandler(state)
+{
+	mxEdgeHandler.call(this, state);
+};
+
+/**
+ * Extends mxEdgeHandler.
+ */
+mxUtils.extend(mxElbowEdgeHandler, mxEdgeHandler);
+
+/**
+ * Specifies if a double click on the middle handle should call
+ * <mxGraph.flipEdge>. Default is true.
+ */
+mxElbowEdgeHandler.prototype.flipEnabled = true;
+
+/**
+ * Variable: doubleClickOrientationResource
+ * 
+ * Specifies the resource key for the tooltip to be displayed on the single
+ * control point for routed edges. If the resource for this key does not
+ * exist then the value is used as the error message. Default is
+ * 'doubleClickOrientation'.
+ */
+mxElbowEdgeHandler.prototype.doubleClickOrientationResource =
+	(mxClient.language != 'none') ? 'doubleClickOrientation' : '';
+
+/**
+ * Function: createBends
+ * 
+ * Overrides <mxEdgeHandler.createBends> to create custom bends.
+ */
+ mxElbowEdgeHandler.prototype.createBends = function()
+ {
+	var bends = [];
+	
+	// Source
+	var bend = this.createHandleShape(0);
+	this.initBend(bend);
+	bend.setCursor(mxConstants.CURSOR_TERMINAL_HANDLE);
+	bends.push(bend);
+
+	// Virtual
+	bends.push(this.createVirtualBend(mxUtils.bind(this, function(evt)
+	{
+		if (!mxEvent.isConsumed(evt) && this.flipEnabled)
+		{
+			this.graph.flipEdge(this.state.cell, evt);
+			mxEvent.consume(evt);
+		}
+	})));
+	this.points.push(new mxPoint(0,0));
+
+	// Target
+	bend = this.createHandleShape(2);
+	this.initBend(bend);
+	bend.setCursor(mxConstants.CURSOR_TERMINAL_HANDLE);
+	bends.push(bend);
+	
+	return bends;
+ };
+
+/**
+ * Function: createVirtualBend
+ * 
+ * Creates a virtual bend that supports double clicking and calls
+ * <mxGraph.flipEdge>.
+ */
+mxElbowEdgeHandler.prototype.createVirtualBend = function(dblClickHandler)
+{
+	var bend = this.createHandleShape();
+	this.initBend(bend, dblClickHandler);
+
+	bend.setCursor(this.getCursorForBend());
+
+	if (!this.graph.isCellBendable(this.state.cell))
+	{
+		bend.node.style.display = 'none';
+	}
+
+	return bend;
+};
+
+/**
+ * Function: getCursorForBend
+ * 
+ * Returns the cursor to be used for the bend.
+ */
+mxElbowEdgeHandler.prototype.getCursorForBend = function()
+{
+	return (this.state.style[mxConstants.STYLE_EDGE] == mxEdgeStyle.TopToBottom ||
+		this.state.style[mxConstants.STYLE_EDGE] == mxConstants.EDGESTYLE_TOPTOBOTTOM ||
+		((this.state.style[mxConstants.STYLE_EDGE] == mxEdgeStyle.ElbowConnector ||
+		this.state.style[mxConstants.STYLE_EDGE] == mxConstants.EDGESTYLE_ELBOW)&&
+		this.state.style[mxConstants.STYLE_ELBOW] == mxConstants.ELBOW_VERTICAL)) ? 
+		'row-resize' : 'col-resize';
+};
+
+/**
+ * Function: getTooltipForNode
+ * 
+ * Returns the tooltip for the given node.
+ */
+mxElbowEdgeHandler.prototype.getTooltipForNode = function(node)
+{
+	var tip = null;
+	
+	if (this.bends != null && this.bends[1] != null && (node == this.bends[1].node ||
+		node.parentNode == this.bends[1].node))
+	{
+		tip = this.doubleClickOrientationResource;
+		tip = mxResources.get(tip) || tip; // translate
+	}
+
+	return tip;
+};
+
+/**
+ * Function: convertPoint
+ * 
+ * Converts the given point in-place from screen to unscaled, untranslated
+ * graph coordinates and applies the grid.
+ * 
+ * Parameters:
+ * 
+ * point - <mxPoint> to be converted.
+ * gridEnabled - Boolean that specifies if the grid should be applied.
+ */
+mxElbowEdgeHandler.prototype.convertPoint = function(point, gridEnabled)
+{
+	var scale = this.graph.getView().getScale();
+	var tr = this.graph.getView().getTranslate();
+	var origin = this.state.origin;
+	
+	if (gridEnabled)
+	{
+		point.x = this.graph.snap(point.x);
+		point.y = this.graph.snap(point.y);
+	}
+	
+	point.x = Math.round(point.x / scale - tr.x - origin.x);
+	point.y = Math.round(point.y / scale - tr.y - origin.y);
+	
+	return point;
+};
+
+/**
+ * Function: redrawInnerBends
+ * 
+ * Updates and redraws the inner bends.
+ * 
+ * Parameters:
+ * 
+ * p0 - <mxPoint> that represents the location of the first point.
+ * pe - <mxPoint> that represents the location of the last point.
+ */
+mxElbowEdgeHandler.prototype.redrawInnerBends = function(p0, pe)
+{
+	var g = this.graph.getModel().getGeometry(this.state.cell);
+	var pts = this.state.absolutePoints;
+	var pt = null;
+
+	// Keeps the virtual bend on the edge shape
+	if (pts.length > 1)
+	{
+		p0 = pts[1];
+		pe = pts[pts.length - 2];
+	}
+	else if (g.points != null && g.points.length > 0)
+	{
+		pt = pts[0];
+	}
+	
+	if (pt == null)
+	{
+		pt = new mxPoint(p0.x + (pe.x - p0.x) / 2, p0.y + (pe.y - p0.y) / 2);
+	}
+	else
+	{
+		pt = new mxPoint(this.graph.getView().scale * (pt.x + this.graph.getView().translate.x + this.state.origin.x),
+				this.graph.getView().scale * (pt.y + this.graph.getView().translate.y + this.state.origin.y));
+	}
+
+	// Makes handle slightly bigger if the yellow  label handle
+	// exists and intersects this green handle
+	var b = this.bends[1].bounds;
+	var w = b.width;
+	var h = b.height;
+	var bounds = new mxRectangle(Math.round(pt.x - w / 2), Math.round(pt.y - h / 2), w, h);
+
+	if (this.manageLabelHandle)
+	{
+		this.checkLabelHandle(bounds);
+	}
+	else if (this.handleImage == null && this.labelShape.visible && mxUtils.intersects(bounds, this.labelShape.bounds))
+	{
+		w = mxConstants.HANDLE_SIZE + 3;
+		h = mxConstants.HANDLE_SIZE + 3;
+		bounds = new mxRectangle(Math.floor(pt.x - w / 2), Math.floor(pt.y - h / 2), w, h);
+	}
+
+	this.bends[1].bounds = bounds;
+	this.bends[1].redraw();
+	
+	if (this.manageLabelHandle)
+	{
+		this.checkLabelHandle(this.bends[1].bounds);
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+function mxEdgeSegmentHandler(state)
+{
+	mxEdgeHandler.call(this, state);
+};
+
+/**
+ * Extends mxEdgeHandler.
+ */
+mxUtils.extend(mxEdgeSegmentHandler, mxElbowEdgeHandler);
+
+/**
+ * Function: getCurrentPoints
+ * 
+ * Returns the current absolute points.
+ */
+mxEdgeSegmentHandler.prototype.getCurrentPoints = function()
+{
+	var pts = this.state.absolutePoints;
+	
+	if (pts != null)
+	{
+		// Special case for straight edges where we add a virtual middle handle for moving the edge
+		if (pts.length == 2 || (pts.length == 3 && (pts[0].x == pts[1].x && pts[1].x == pts[2].x ||
+				pts[0].y == pts[1].y && pts[1].y == pts[2].y)))
+		{
+			var cx = pts[0].x + (pts[pts.length - 1].x - pts[0].x) / 2;
+			var cy = pts[0].y + (pts[pts.length - 1].y - pts[0].y) / 2;
+			
+			pts = [pts[0], new mxPoint(cx, cy), new mxPoint(cx, cy), pts[pts.length - 1]];	
+		}
+	}
+
+	return pts;
+};
+
+/**
+ * Function: getPreviewPoints
+ * 
+ * Updates the given preview state taking into account the state of the constraint handler.
+ */
+mxEdgeSegmentHandler.prototype.getPreviewPoints = function(point)
+{
+	if (this.isSource || this.isTarget)
+	{
+		return mxElbowEdgeHandler.prototype.getPreviewPoints.apply(this, arguments);
+	}
+	else
+	{
+		var pts = this.getCurrentPoints();
+		var last = this.convertPoint(pts[0].clone(), false);
+		point = this.convertPoint(point.clone(), false);
+		var result = [];
+
+		for (var i = 1; i < pts.length; i++)
+		{
+			var pt = this.convertPoint(pts[i].clone(), false);
+			
+			if (i == this.index)
+			{
+				if (Math.round(last.x - pt.x) == 0)
+		 		{
+					last.x = point.x;
+					pt.x = point.x;
+		 		}
+		 		
+				if (Math.round(last.y - pt.y) == 0)
+		 		{
+		 			last.y = point.y;
+		 			pt.y = point.y;
+		 		}
+			}
+
+			if (i < pts.length - 1)
+			{
+				result.push(pt);
+			}
+
+			last = pt;
+		}
+		
+		// Replaces single point that intersects with source or target
+		if (result.length == 1)
+		{
+			var source = this.state.getVisibleTerminalState(true);
+			var target = this.state.getVisibleTerminalState(false);
+			var scale = this.state.view.getScale();
+			var tr = this.state.view.getTranslate();
+			
+			var x = result[0].x * scale + tr.x;
+			var y = result[0].y * scale + tr.y;
+			
+			if ((source != null && mxUtils.contains(source, x, y)) ||
+				(target != null && mxUtils.contains(target, x, y)))
+			{
+				result = [point, point];
+			}
+		}
+
+		return result;
+	}
+};
+
+/**
+ * Function: updatePreviewState
+ * 
+ * Overridden to perform optimization of the edge style result.
+ */
+mxEdgeSegmentHandler.prototype.updatePreviewState = function(edge, point, terminalState, me)
+{
+	mxEdgeHandler.prototype.updatePreviewState.apply(this, arguments);
+
+	// Checks and corrects preview by running edge style again
+	if (!this.isSource && !this.isTarget)
+	{
+		point = this.convertPoint(point.clone(), false);
+		var pts = edge.absolutePoints;
+		var pt0 = pts[0];
+		var pt1 = pts[1];
+
+		var result = [];
+		
+		for (var i = 2; i < pts.length; i++)
+		{
+			var pt2 = pts[i];
+		
+			// Merges adjacent segments only if more than 2 to allow for straight edges
+			if ((Math.round(pt0.x - pt1.x) != 0 || Math.round(pt1.x - pt2.x) != 0) &&
+				(Math.round(pt0.y - pt1.y) != 0 || Math.round(pt1.y - pt2.y) != 0))
+			{
+				result.push(this.convertPoint(pt1.clone(), false));
+			}
+
+			pt0 = pt1;
+			pt1 = pt2;
+		}
+		
+		var source = this.state.getVisibleTerminalState(true);
+		var target = this.state.getVisibleTerminalState(false);
+		var rpts = this.state.absolutePoints;
+		
+		// A straight line is represented by 3 handles
+		if (result.length == 0 && (Math.round(pts[0].x - pts[pts.length - 1].x) == 0 ||
+			Math.round(pts[0].y - pts[pts.length - 1].y) == 0))
+		{
+			result = [point, point];
+		}
+		// Handles special case of transitions from straight vertical to routed
+		else if (pts.length == 5 && result.length == 2 && source != null && target != null &&
+				rpts != null && Math.round(rpts[0].x - rpts[rpts.length - 1].x) == 0)
+		{
+			var view = this.graph.getView();
+			var scale = view.getScale();
+			var tr = view.getTranslate();
+			
+			var y0 = view.getRoutingCenterY(source) / scale - tr.y;
+			
+			// Use fixed connection point y-coordinate if one exists
+			var sc = this.graph.getConnectionConstraint(edge, source, true);
+			
+			if (sc != null)
+			{
+				var pt = this.graph.getConnectionPoint(source, sc);
+				
+				if (pt != null)
+				{
+					this.convertPoint(pt, false);
+					y0 = pt.y;
+				}
+			}
+			
+			var ye = view.getRoutingCenterY(target) / scale - tr.y;
+			
+			// Use fixed connection point y-coordinate if one exists
+			var tc = this.graph.getConnectionConstraint(edge, target, false);
+			
+			if (tc)
+			{
+				var pt = this.graph.getConnectionPoint(target, tc);
+				
+				if (pt != null)
+				{
+					this.convertPoint(pt, false);
+					ye = pt.y;
+				}
+			}
+			
+			result = [new mxPoint(point.x, y0), new mxPoint(point.x, ye)];
+		}
+
+		this.points = result;
+
+		// LATER: Check if points and result are different
+		edge.view.updateFixedTerminalPoints(edge, source, target);
+		edge.view.updatePoints(edge, this.points, source, target);
+		edge.view.updateFloatingTerminalPoints(edge, source, target);
+	}
+};
+
+/**
+ * Overriden to merge edge segments.
+ */
+mxEdgeSegmentHandler.prototype.connect = function(edge, terminal, isSource, isClone, me)
+{
+	// Merges adjacent edge segments
+	var pts = this.abspoints;
+	var pt0 = pts[0];
+	var pt1 = pts[1];
+	var result = [];
+	
+	for (var i = 2; i < pts.length; i++)
+	{
+		var pt2 = pts[i];
+	
+		// Merges adjacent segments only if more than 2 to allow for straight edges
+		if ((Math.round(pt0.x - pt1.x) != 0 || Math.round(pt1.x - pt2.x) != 0) &&
+			(Math.round(pt0.y - pt1.y) != 0 || Math.round(pt1.y - pt2.y) != 0))
+		{
+			result.push(this.convertPoint(pt1.clone(), false));
+		}
+
+		pt0 = pt1;
+		pt1 = pt2;
+	}
+	
+	var model = this.graph.getModel();
+	
+	model.beginUpdate();
+	try
+	{
+		var geo = model.getGeometry(edge);
+		
+		if (geo != null)
+		{
+			geo = geo.clone();
+			geo.points = result;
+			
+			model.setGeometry(edge, geo);
+		}
+		
+		edge = mxEdgeHandler.prototype.connect.apply(this, arguments);
+	}
+	finally
+	{
+		model.endUpdate();
+	}
+	
+	return edge;
+};
+
+/**
+ * Function: getTooltipForNode
+ * 
+ * Returns no tooltips.
+ */
+mxEdgeSegmentHandler.prototype.getTooltipForNode = function(node)
+{
+	return null;
+};
+
+/**
+ * Function: createBends
+ * 
+ * Adds custom bends for the center of each segment.
+ */
+mxEdgeSegmentHandler.prototype.start = function(x, y, index)
+{
+	mxEdgeHandler.prototype.start.apply(this, arguments);
+	
+	if (this.bends[index] != null && !this.isSource && !this.isTarget)
+	{
+		mxUtils.setOpacity(this.bends[index].node, 100);
+	}
+};
+
+/**
+ * Function: createBends
+ * 
+ * Adds custom bends for the center of each segment.
+ */
+mxEdgeSegmentHandler.prototype.createBends = function()
+{
+	var bends = [];
+	
+	// Source
+	var bend = this.createHandleShape(0);
+	this.initBend(bend);
+	bend.setCursor(mxConstants.CURSOR_TERMINAL_HANDLE);
+	bends.push(bend);
+
+	var pts = this.getCurrentPoints();
+
+	// Waypoints (segment handles)
+	if (this.graph.isCellBendable(this.state.cell))
+	{
+		if (this.points == null)
+		{
+			this.points = [];
+		}
+
+		for (var i = 0; i < pts.length - 1; i++)
+		{
+			bend = this.createVirtualBend();
+			bends.push(bend);
+			var horizontal = Math.round(pts[i].x - pts[i + 1].x) == 0;
+			
+			// Special case where dy is 0 as well
+			if (Math.round(pts[i].y - pts[i + 1].y) == 0 && i < pts.length - 2)
+			{
+				horizontal = Math.round(pts[i].x - pts[i + 2].x) == 0;
+			}
+			
+			bend.setCursor((horizontal) ? 'col-resize' : 'row-resize');
+			this.points.push(new mxPoint(0,0));
+		}
+	}
+
+	// Target
+	var bend = this.createHandleShape(pts.length);
+	this.initBend(bend);
+	bend.setCursor(mxConstants.CURSOR_TERMINAL_HANDLE);
+	bends.push(bend);
+
+	return bends;
+};
+
+/**
+ * Function: redraw
+ * 
+ * Overridden to invoke <refresh> before the redraw.
+ */
+mxEdgeSegmentHandler.prototype.redraw = function()
+{
+	this.refresh();
+	mxEdgeHandler.prototype.redraw.apply(this, arguments);
+};
+
+/**
+ * Function: redrawInnerBends
+ * 
+ * Updates the position of the custom bends.
+ */
+mxEdgeSegmentHandler.prototype.redrawInnerBends = function(p0, pe)
+{
+	if (this.graph.isCellBendable(this.state.cell))
+	{
+		var pts = this.getCurrentPoints();
+		
+		if (pts != null && pts.length > 1)
+		{
+			var straight = false;
+			
+			// Puts handle in the center of straight edges
+			if (pts.length == 4 && Math.round(pts[1].x - pts[2].x) == 0 && Math.round(pts[1].y - pts[2].y) == 0)
+			{
+				straight = true;
+				
+				if (Math.round(pts[0].y - pts[pts.length - 1].y) == 0)
+				{
+					var cx = pts[0].x + (pts[pts.length - 1].x - pts[0].x) / 2;
+					pts[1] = new mxPoint(cx, pts[1].y);
+					pts[2] = new mxPoint(cx, pts[2].y);
+				}
+				else
+				{
+					var cy = pts[0].y + (pts[pts.length - 1].y - pts[0].y) / 2;
+					pts[1] = new mxPoint(pts[1].x, cy);
+					pts[2] = new mxPoint(pts[2].x, cy);
+				}
+			}
+			
+			for (var i = 0; i < pts.length - 1; i++)
+			{
+				if (this.bends[i + 1] != null)
+				{
+		 			var p0 = pts[i];
+	 				var pe = pts[i + 1];
+			 		var pt = new mxPoint(p0.x + (pe.x - p0.x) / 2, p0.y + (pe.y - p0.y) / 2);
+			 		var b = this.bends[i + 1].bounds;
+			 		this.bends[i + 1].bounds = new mxRectangle(Math.floor(pt.x - b.width / 2),
+			 				Math.floor(pt.y - b.height / 2), b.width, b.height);
+				 	this.bends[i + 1].redraw();
+				 	
+				 	if (this.manageLabelHandle)
+					{
+						this.checkLabelHandle(this.bends[i + 1].bounds);
+					}
+				}
+			}
+			
+			if (straight)
+			{
+				mxUtils.setOpacity(this.bends[1].node, this.virtualBendOpacity);
+				mxUtils.setOpacity(this.bends[3].node, this.virtualBendOpacity);
+			}
+		}
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxKeyHandler
+ *
+ * Event handler that listens to keystroke events. This is not a singleton,
+ * however, it is normally only required once if the target is the document
+ * element (default).
+ * 
+ * This handler installs a key event listener in the topmost DOM node and
+ * processes all events that originate from descandants of <mxGraph.container>
+ * or from the topmost DOM node. The latter means that all unhandled keystrokes
+ * are handled by this object regardless of the focused state of the <graph>.
+ * 
+ * Example:
+ * 
+ * The following example creates a key handler that listens to the delete key
+ * (46) and deletes the selection cells if the graph is enabled.
+ * 
+ * (code)
+ * var keyHandler = new mxKeyHandler(graph);
+ * keyHandler.bindKey(46, function(evt)
+ * {
+ *   if (graph.isEnabled())
+ *   {
+ *     graph.removeCells();
+ *   }
+ * });
+ * (end)
+ * 
+ * Keycodes:
+ * 
+ * See http://tinyurl.com/yp8jgl or http://tinyurl.com/229yqw for a list of
+ * keycodes or install a key event listener into the document element and print
+ * the key codes of the respective events to the console.
+ * 
+ * To support the Command key and the Control key on the Mac, the following
+ * code can be used.
+ *
+ * (code)
+ * keyHandler.getFunction = function(evt)
+ * {
+ *   if (evt != null)
+ *   {
+ *     return (mxEvent.isControlDown(evt) || (mxClient.IS_MAC && evt.metaKey)) ? this.controlKeys[evt.keyCode] : this.normalKeys[evt.keyCode];
+ *   }
+ *   
+ *   return null;
+ * };
+ * (end)
+ * 
+ * Constructor: mxKeyHandler
+ *
+ * Constructs an event handler that executes functions bound to specific
+ * keystrokes.
+ * 
+ * Parameters:
+ * 
+ * graph - Reference to the associated <mxGraph>.
+ * target - Optional reference to the event target. If null, the document
+ * element is used as the event target, that is, the object where the key
+ * event listener is installed.
+ */
+function mxKeyHandler(graph, target)
+{
+	if (graph != null)
+	{
+		this.graph = graph;
+		this.target = target || document.documentElement;
+		
+		// Creates the arrays to map from keycodes to functions
+		this.normalKeys = [];
+		this.shiftKeys = [];
+		this.controlKeys = [];
+		this.controlShiftKeys = [];
+		
+		this.keydownHandler = mxUtils.bind(this, function(evt)
+		{
+			this.keyDown(evt);
+		});
+
+		// Installs the keystroke listener in the target
+		mxEvent.addListener(this.target, 'keydown', this.keydownHandler);
+		
+		// Automatically deallocates memory in IE
+		if (mxClient.IS_IE)
+		{
+			mxEvent.addListener(window, 'unload',
+				mxUtils.bind(this, function()
+				{
+					this.destroy();
+				})
+			);
+		}
+	}
+};
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the <mxGraph> associated with this handler.
+ */
+mxKeyHandler.prototype.graph = null;
+
+/**
+ * Variable: target
+ * 
+ * Reference to the target DOM, that is, the DOM node where the key event
+ * listeners are installed.
+ */
+mxKeyHandler.prototype.target = null;
+
+/**
+ * Variable: normalKeys
+ * 
+ * Maps from keycodes to functions for non-pressed control keys.
+ */
+mxKeyHandler.prototype.normalKeys = null;
+
+/**
+ * Variable: shiftKeys
+ * 
+ * Maps from keycodes to functions for pressed shift keys.
+ */
+mxKeyHandler.prototype.shiftKeys = null;
+
+/**
+ * Variable: controlKeys
+ * 
+ * Maps from keycodes to functions for pressed control keys.
+ */
+mxKeyHandler.prototype.controlKeys = null;
+
+/**
+ * Variable: controlShiftKeys
+ * 
+ * Maps from keycodes to functions for pressed control and shift keys.
+ */
+mxKeyHandler.prototype.controlShiftKeys = null;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies if events are handled. Default is true.
+ */
+mxKeyHandler.prototype.enabled = true;
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns true if events are handled. This implementation returns
+ * <enabled>.
+ */
+mxKeyHandler.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ * 
+ * Enables or disables event handling by updating <enabled>.
+ * 
+ * Parameters:
+ * 
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxKeyHandler.prototype.setEnabled = function(enabled)
+{
+	this.enabled = enabled;
+};
+
+/**
+ * Function: bindKey
+ * 
+ * Binds the specified keycode to the given function. This binding is used
+ * if the control key is not pressed.
+ * 
+ * Parameters:
+ *
+ * code - Integer that specifies the keycode.
+ * funct - JavaScript function that takes the key event as an argument.
+ */
+mxKeyHandler.prototype.bindKey = function(code, funct)
+{
+	this.normalKeys[code] = funct;
+};
+
+/**
+ * Function: bindShiftKey
+ * 
+ * Binds the specified keycode to the given function. This binding is used
+ * if the shift key is pressed.
+ * 
+ * Parameters:
+ *
+ * code - Integer that specifies the keycode.
+ * funct - JavaScript function that takes the key event as an argument.
+ */
+mxKeyHandler.prototype.bindShiftKey = function(code, funct)
+{
+	this.shiftKeys[code] = funct;
+};
+
+/**
+ * Function: bindControlKey
+ * 
+ * Binds the specified keycode to the given function. This binding is used
+ * if the control key is pressed.
+ * 
+ * Parameters:
+ *
+ * code - Integer that specifies the keycode.
+ * funct - JavaScript function that takes the key event as an argument.
+ */
+mxKeyHandler.prototype.bindControlKey = function(code, funct)
+{
+	this.controlKeys[code] = funct;
+};
+
+/**
+ * Function: bindControlShiftKey
+ * 
+ * Binds the specified keycode to the given function. This binding is used
+ * if the control and shift key are pressed.
+ * 
+ * Parameters:
+ *
+ * code - Integer that specifies the keycode.
+ * funct - JavaScript function that takes the key event as an argument.
+ */
+mxKeyHandler.prototype.bindControlShiftKey = function(code, funct)
+{
+	this.controlShiftKeys[code] = funct;
+};
+
+/**
+ * Function: isControlDown
+ * 
+ * Returns true if the control key is pressed. This uses <mxEvent.isControlDown>.
+ * 
+ * Parameters:
+ * 
+ * evt - Key event whose control key pressed state should be returned.
+ */
+mxKeyHandler.prototype.isControlDown = function(evt)
+{
+	return mxEvent.isControlDown(evt);
+};
+
+/**
+ * Function: getFunction
+ * 
+ * Returns the function associated with the given key event or null if no
+ * function is associated with the given event.
+ * 
+ * Parameters:
+ * 
+ * evt - Key event whose associated function should be returned.
+ */
+mxKeyHandler.prototype.getFunction = function(evt)
+{
+	if (evt != null && !mxEvent.isAltDown(evt))
+	{
+		if (this.isControlDown(evt))
+		{
+			if (mxEvent.isShiftDown(evt))
+			{
+				return this.controlShiftKeys[evt.keyCode];
+			}
+			else
+			{
+				return this.controlKeys[evt.keyCode];
+			}
+		}
+		else
+		{
+			if (mxEvent.isShiftDown(evt))
+			{
+				return this.shiftKeys[evt.keyCode];
+			}
+			else
+			{
+				return this.normalKeys[evt.keyCode];
+			}
+		}
+	}
+	
+	return null;
+};
+	
+/**
+ * Function: isGraphEvent
+ * 
+ * Returns true if the event should be processed by this handler, that is,
+ * if the event source is either the target, one of its direct children, a
+ * descendant of the <mxGraph.container>, or the <mxGraph.cellEditor> of the
+ * <graph>.
+ * 
+ * Parameters:
+ * 
+ * evt - Key event that represents the keystroke.
+ */
+mxKeyHandler.prototype.isGraphEvent = function(evt)
+{
+	var source = mxEvent.getSource(evt);
+	
+	// Accepts events from the target object or
+	// in-place editing inside graph
+	if ((source == this.target || source.parentNode == this.target) ||
+		(this.graph.cellEditor != null && this.graph.cellEditor.isEventSource(evt)))
+	{
+		return true;
+	}
+	
+	// Accepts events from inside the container
+	return mxUtils.isAncestorNode(this.graph.container, source);
+};
+
+/**
+ * Function: keyDown
+ * 
+ * Handles the event by invoking the function bound to the respective keystroke
+ * if <isEnabledForEvent> returns true for the given event and if
+ * <isEventIgnored> returns false, except for escape for which
+ * <isEventIgnored> is not invoked.
+ * 
+ * Parameters:
+ * 
+ * evt - Key event that represents the keystroke.
+ */
+mxKeyHandler.prototype.keyDown = function(evt)
+{
+	if (this.isEnabledForEvent(evt))
+	{
+		// Cancels the editing if escape is pressed
+		if (evt.keyCode == 27 /* Escape */)
+		{
+			this.escape(evt);
+		}
+		
+		// Invokes the function for the keystroke
+		else if (!this.isEventIgnored(evt))
+		{
+			var boundFunction = this.getFunction(evt);
+			
+			if (boundFunction != null)
+			{
+				boundFunction(evt);
+				mxEvent.consume(evt);
+			}
+		}
+	}
+};
+
+/**
+ * Function: isEnabledForEvent
+ * 
+ * Returns true if the given event should be handled. <isEventIgnored> is
+ * called later if the event is not an escape key stroke, in which case
+ * <escape> is called. This implementation returns true if <isEnabled>
+ * returns true for both, this handler and <graph>, if the event is not
+ * consumed and if <isGraphEvent> returns true.
+ * 
+ * Parameters:
+ * 
+ * evt - Key event that represents the keystroke.
+ */
+mxKeyHandler.prototype.isEnabledForEvent = function(evt)
+{
+	return (this.graph.isEnabled() && !mxEvent.isConsumed(evt) &&
+		this.isGraphEvent(evt) && this.isEnabled());
+};
+
+/**
+ * Function: isEventIgnored
+ * 
+ * Returns true if the given keystroke should be ignored. This returns
+ * graph.isEditing().
+ * 
+ * Parameters:
+ * 
+ * evt - Key event that represents the keystroke.
+ */
+mxKeyHandler.prototype.isEventIgnored = function(evt)
+{
+	return this.graph.isEditing();
+};
+
+/**
+ * Function: escape
+ * 
+ * Hook to process ESCAPE keystrokes. This implementation invokes
+ * <mxGraph.stopEditing> to cancel the current editing, connecting
+ * and/or other ongoing modifications.
+ * 
+ * Parameters:
+ * 
+ * evt - Key event that represents the keystroke. Possible keycode in this
+ * case is 27 (ESCAPE).
+ */
+mxKeyHandler.prototype.escape = function(evt)
+{
+	if (this.graph.isEscapeEnabled())
+	{
+		this.graph.escape(evt);
+	}
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the handler and all its references into the DOM. This does
+ * normally not need to be called, it is called automatically when the
+ * window unloads (in IE).
+ */
+mxKeyHandler.prototype.destroy = function()
+{
+	if (this.target != null && this.keydownHandler != null)
+	{
+		mxEvent.removeListener(this.target, 'keydown', this.keydownHandler);
+		this.keydownHandler = null;
+	}
+	
+	this.target = null;
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxTooltipHandler
+ * 
+ * Graph event handler that displays tooltips. <mxGraph.getTooltip> is used to
+ * get the tooltip for a cell or handle. This handler is built-into
+ * <mxGraph.tooltipHandler> and enabled using <mxGraph.setTooltips>.
+ *
+ * Example:
+ * 
+ * (code>
+ * new mxTooltipHandler(graph);
+ * (end)
+ * 
+ * Constructor: mxTooltipHandler
+ * 
+ * Constructs an event handler that displays tooltips with the specified
+ * delay (in milliseconds). If no delay is specified then a default delay
+ * of 500 ms (0.5 sec) is used.
+ * 
+ * Parameters:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ * delay - Optional delay in milliseconds.
+ */
+function mxTooltipHandler(graph, delay)
+{
+	if (graph != null)
+	{
+		this.graph = graph;
+		this.delay = delay || 500;
+		this.graph.addMouseListener(this);
+	}
+};
+
+/**
+ * Variable: zIndex
+ * 
+ * Specifies the zIndex for the tooltip and its shadow. Default is 10005.
+ */
+mxTooltipHandler.prototype.zIndex = 10005;
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxTooltipHandler.prototype.graph = null;
+
+/**
+ * Variable: delay
+ * 
+ * Delay to show the tooltip in milliseconds. Default is 500.
+ */
+mxTooltipHandler.prototype.delay = null;
+
+/**
+ * Variable: ignoreTouchEvents
+ * 
+ * Specifies if touch and pen events should be ignored. Default is true.
+ */
+mxTooltipHandler.prototype.ignoreTouchEvents = true;
+
+/**
+ * Variable: hideOnHover
+ * 
+ * Specifies if the tooltip should be hidden if the mouse is moved over the
+ * current cell. Default is false.
+ */
+mxTooltipHandler.prototype.hideOnHover = false;
+
+/**
+ * Variable: destroyed
+ * 
+ * True if this handler was destroyed using <destroy>.
+ */
+mxTooltipHandler.prototype.destroyed = false;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies if events are handled. Default is true.
+ */
+mxTooltipHandler.prototype.enabled = true;
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxTooltipHandler.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ * 
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ */
+mxTooltipHandler.prototype.setEnabled = function(enabled)
+{
+	this.enabled = enabled;
+};
+
+/**
+ * Function: isHideOnHover
+ * 
+ * Returns <hideOnHover>.
+ */
+mxTooltipHandler.prototype.isHideOnHover = function()
+{
+	return this.hideOnHover;
+};
+
+/**
+ * Function: setHideOnHover
+ * 
+ * Sets <hideOnHover>.
+ */
+mxTooltipHandler.prototype.setHideOnHover = function(value)
+{
+	this.hideOnHover = value;
+};
+
+/**
+ * Function: init
+ * 
+ * Initializes the DOM nodes required for this tooltip handler.
+ */
+mxTooltipHandler.prototype.init = function()
+{
+	if (document.body != null)
+	{
+		this.div = document.createElement('div');
+		this.div.className = 'mxTooltip';
+		this.div.style.visibility = 'hidden';
+
+		document.body.appendChild(this.div);
+
+		mxEvent.addGestureListeners(this.div, mxUtils.bind(this, function(evt)
+		{
+			this.hideTooltip();
+		}));
+	}
+};
+
+/**
+ * Function: mouseDown
+ * 
+ * Handles the event by initiating a rubberband selection. By consuming the
+ * event all subsequent events of the gesture are redirected to this
+ * handler.
+ */
+mxTooltipHandler.prototype.mouseDown = function(sender, me)
+{
+	this.reset(me, false);
+	this.hideTooltip();
+};
+
+/**
+ * Function: mouseMove
+ * 
+ * Handles the event by updating the rubberband selection.
+ */
+mxTooltipHandler.prototype.mouseMove = function(sender, me)
+{
+	if (me.getX() != this.lastX || me.getY() != this.lastY)
+	{
+		this.reset(me, true);
+		
+		if (this.isHideOnHover() || me.getState() != this.state || (me.getSource() != this.node &&
+			(!this.stateSource || (me.getState() != null && this.stateSource ==
+			(me.isSource(me.getState().shape) || !me.isSource(me.getState().text))))))
+		{
+			this.hideTooltip();
+		}
+	}
+	
+	this.lastX = me.getX();
+	this.lastY = me.getY();
+};
+
+/**
+ * Function: mouseUp
+ * 
+ * Handles the event by resetting the tooltip timer or hiding the existing
+ * tooltip.
+ */
+mxTooltipHandler.prototype.mouseUp = function(sender, me)
+{
+	this.reset(me, true);
+	this.hideTooltip();
+};
+
+
+/**
+ * Function: resetTimer
+ * 
+ * Resets the timer.
+ */
+mxTooltipHandler.prototype.resetTimer = function()
+{
+	if (this.thread != null)
+	{
+		window.clearTimeout(this.thread);
+		this.thread = null;
+	}
+};
+
+/**
+ * Function: reset
+ * 
+ * Resets and/or restarts the timer to trigger the display of the tooltip.
+ */
+mxTooltipHandler.prototype.reset = function(me, restart)
+{
+	if (!this.ignoreTouchEvents || mxEvent.isMouseEvent(me.getEvent()))
+	{
+		this.resetTimer();
+		
+		if (restart && this.isEnabled() && me.getState() != null && (this.div == null ||
+			this.div.style.visibility == 'hidden'))
+		{
+			var state = me.getState();
+			var node = me.getSource();
+			var x = me.getX();
+			var y = me.getY();
+			var stateSource = me.isSource(state.shape) || me.isSource(state.text);
+	
+			this.thread = window.setTimeout(mxUtils.bind(this, function()
+			{
+				if (!this.graph.isEditing() && !this.graph.popupMenuHandler.isMenuShowing() && !this.graph.isMouseDown)
+				{
+					// Uses information from inside event cause using the event at
+					// this (delayed) point in time is not possible in IE as it no
+					// longer contains the required information (member not found)
+					var tip = this.graph.getTooltip(state, node, x, y);
+					this.show(tip, x, y);
+					this.state = state;
+					this.node = node;
+					this.stateSource = stateSource;
+				}
+			}), this.delay);
+		}
+	}
+};
+
+/**
+ * Function: hide
+ * 
+ * Hides the tooltip and resets the timer.
+ */
+mxTooltipHandler.prototype.hide = function()
+{
+	this.resetTimer();
+	this.hideTooltip();
+};
+
+/**
+ * Function: hideTooltip
+ * 
+ * Hides the tooltip.
+ */
+mxTooltipHandler.prototype.hideTooltip = function()
+{
+	if (this.div != null)
+	{
+		this.div.style.visibility = 'hidden';
+		this.div.innerHTML = '';
+	}
+};
+
+/**
+ * Function: show
+ * 
+ * Shows the tooltip for the specified cell and optional index at the
+ * specified location (with a vertical offset of 10 pixels).
+ */
+mxTooltipHandler.prototype.show = function(tip, x, y)
+{
+	if (!this.destroyed && tip != null && tip.length > 0)
+	{
+		// Initializes the DOM nodes if required
+		if (this.div == null)
+		{
+			this.init();
+		}
+		
+		var origin = mxUtils.getScrollOrigin();
+
+		this.div.style.zIndex = this.zIndex;
+		this.div.style.left = (x + origin.x) + 'px';
+		this.div.style.top = (y + mxConstants.TOOLTIP_VERTICAL_OFFSET +
+			origin.y) + 'px';
+
+		if (!mxUtils.isNode(tip))
+		{	
+			this.div.innerHTML = tip.replace(/\n/g, '<br>');
+		}
+		else
+		{
+			this.div.innerHTML = '';
+			this.div.appendChild(tip);
+		}
+		
+		this.div.style.visibility = '';
+		mxUtils.fit(this.div);
+	}
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the handler and all its resources and DOM nodes.
+ */
+mxTooltipHandler.prototype.destroy = function()
+{
+	if (!this.destroyed)
+	{
+		this.graph.removeMouseListener(this);
+		mxEvent.release(this.div);
+		
+		if (this.div != null && this.div.parentNode != null)
+		{
+			this.div.parentNode.removeChild(this.div);
+		}
+		
+		this.destroyed = true;
+		this.div = null;
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxCellTracker
+ * 
+ * Event handler that highlights cells. Inherits from <mxCellMarker>.
+ * 
+ * Example:
+ * 
+ * (code)
+ * new mxCellTracker(graph, '#00FF00');
+ * (end)
+ * 
+ * For detecting dragEnter, dragOver and dragLeave on cells, the following
+ * code can be used:
+ * 
+ * (code)
+ * graph.addMouseListener(
+ * {
+ *   cell: null,
+ *   mouseDown: function(sender, me) { },
+ *   mouseMove: function(sender, me)
+ *   {
+ *     var tmp = me.getCell();
+ *     
+ *     if (tmp != this.cell)
+ *     {
+ *       if (this.cell != null)
+ *       {
+ *         this.dragLeave(me.getEvent(), this.cell);
+ *       }
+ *       
+ *       this.cell = tmp;
+ *       
+ *       if (this.cell != null)
+ *       {
+ *         this.dragEnter(me.getEvent(), this.cell);
+ *       }
+ *     }
+ *     
+ *     if (this.cell != null)
+ *     {
+ *       this.dragOver(me.getEvent(), this.cell);
+ *     }
+ *   },
+ *   mouseUp: function(sender, me) { },
+ *   dragEnter: function(evt, cell)
+ *   {
+ *     mxLog.debug('dragEnter', cell.value);
+ *   },
+ *   dragOver: function(evt, cell)
+ *   {
+ *     mxLog.debug('dragOver', cell.value);
+ *   },
+ *   dragLeave: function(evt, cell)
+ *   {
+ *     mxLog.debug('dragLeave', cell.value);
+ *   }
+ * });
+ * (end)
+ * 
+ * Constructor: mxCellTracker
+ * 
+ * Constructs an event handler that highlights cells.
+ * 
+ * Parameters:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ * color - Color of the highlight. Default is blue.
+ * funct - Optional JavaScript function that is used to override
+ * <mxCellMarker.getCell>.
+ */
+function mxCellTracker(graph, color, funct)
+{
+	mxCellMarker.call(this, graph, color);
+
+	this.graph.addMouseListener(this);
+	
+	if (funct != null)
+	{
+		this.getCell = funct;
+	}
+	
+	// Automatic deallocation of memory
+	if (mxClient.IS_IE)
+	{
+		mxEvent.addListener(window, 'unload', mxUtils.bind(this, function()
+		{
+			this.destroy();
+		}));
+	}
+};
+
+/**
+ * Extends mxCellMarker.
+ */
+mxUtils.extend(mxCellTracker, mxCellMarker);
+
+/**
+ * Function: mouseDown
+ * 
+ * Ignores the event. The event is not consumed.
+ */
+mxCellTracker.prototype.mouseDown = function(sender, me) { };
+
+/**
+ * Function: mouseMove
+ * 
+ * Handles the event by highlighting the cell under the mousepointer if it
+ * is over the hotspot region of the cell.
+ */
+mxCellTracker.prototype.mouseMove = function(sender, me)
+{
+	if (this.isEnabled())
+	{
+		this.process(me);
+	}
+};
+
+/**
+ * Function: mouseUp
+ * 
+ * Handles the event by reseting the highlight.
+ */
+mxCellTracker.prototype.mouseUp = function(sender, me) { };
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the object and all its resources and DOM nodes. This doesn't
+ * normally need to be called. It is called automatically when the window
+ * unloads.
+ */
+mxCellTracker.prototype.destroy = function()
+{
+	if (!this.destroyed)
+	{
+		this.destroyed = true;
+
+		this.graph.removeMouseListener(this);
+		mxCellMarker.prototype.destroy.apply(this);
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxCellHighlight
+ * 
+ * A helper class to highlight cells. Here is an example for a given cell.
+ * 
+ * (code)
+ * var highlight = new mxCellHighlight(graph, '#ff0000', 2);
+ * highlight.highlight(graph.view.getState(cell)));
+ * (end)
+ * 
+ * Constructor: mxCellHighlight
+ * 
+ * Constructs a cell highlight.
+ */
+function mxCellHighlight(graph, highlightColor, strokeWidth, dashed)
+{
+	if (graph != null)
+	{
+		this.graph = graph;
+		this.highlightColor = (highlightColor != null) ? highlightColor : mxConstants.DEFAULT_VALID_COLOR;
+		this.strokeWidth = (strokeWidth != null) ? strokeWidth : mxConstants.HIGHLIGHT_STROKEWIDTH;
+		this.dashed = (dashed != null) ? dashed : false;
+		this.opacity = mxConstants.HIGHLIGHT_OPACITY;
+
+		// Updates the marker if the graph changes
+		this.repaintHandler = mxUtils.bind(this, function()
+		{
+			// Updates reference to state
+			if (this.state != null)
+			{
+				var tmp = this.graph.view.getState(this.state.cell);
+				
+				if (tmp == null)
+				{
+					this.hide();
+				}
+				else
+				{
+					this.state = tmp;
+					this.repaint();
+				}
+			}
+		});
+
+		this.graph.getView().addListener(mxEvent.SCALE, this.repaintHandler);
+		this.graph.getView().addListener(mxEvent.TRANSLATE, this.repaintHandler);
+		this.graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE, this.repaintHandler);
+		this.graph.getModel().addListener(mxEvent.CHANGE, this.repaintHandler);
+		
+		// Hides the marker if the current root changes
+		this.resetHandler = mxUtils.bind(this, function()
+		{
+			this.hide();
+		});
+
+		this.graph.getView().addListener(mxEvent.DOWN, this.resetHandler);
+		this.graph.getView().addListener(mxEvent.UP, this.resetHandler);
+	}
+};
+
+/**
+ * Variable: keepOnTop
+ * 
+ * Specifies if the highlights should appear on top of everything
+ * else in the overlay pane. Default is false.
+ */
+mxCellHighlight.prototype.keepOnTop = false;
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxCellHighlight.prototype.graph = true;
+
+/**
+ * Variable: state
+ * 
+ * Reference to the <mxCellState>.
+ */
+mxCellHighlight.prototype.state = null;
+
+/**
+ * Variable: spacing
+ * 
+ * Specifies the spacing between the highlight for vertices and the vertex.
+ * Default is 2.
+ */
+mxCellHighlight.prototype.spacing = 2;
+
+/**
+ * Variable: resetHandler
+ * 
+ * Holds the handler that automatically invokes reset if the highlight
+ * should be hidden.
+ */
+mxCellHighlight.prototype.resetHandler = null;
+
+/**
+ * Function: setHighlightColor
+ * 
+ * Sets the color of the rectangle used to highlight drop targets.
+ * 
+ * Parameters:
+ * 
+ * color - String that represents the new highlight color.
+ */
+mxCellHighlight.prototype.setHighlightColor = function(color)
+{
+	this.highlightColor = color;
+	
+	if (this.shape != null)
+	{
+		this.shape.stroke = color;
+	}
+};
+
+/**
+ * Function: drawHighlight
+ * 
+ * Creates and returns the highlight shape for the given state.
+ */
+mxCellHighlight.prototype.drawHighlight = function()
+{
+	this.shape = this.createShape();
+	this.repaint();
+
+	if (!this.keepOnTop && this.shape.node.parentNode.firstChild != this.shape.node)
+	{
+		this.shape.node.parentNode.insertBefore(this.shape.node, this.shape.node.parentNode.firstChild);
+	}
+};
+
+/**
+ * Function: createShape
+ * 
+ * Creates and returns the highlight shape for the given state.
+ */
+mxCellHighlight.prototype.createShape = function()
+{
+	var shape = this.graph.cellRenderer.createShape(this.state);
+	
+	shape.svgStrokeTolerance = this.graph.tolerance;
+	shape.points = this.state.absolutePoints;
+	shape.apply(this.state);
+	shape.stroke = this.highlightColor;
+	shape.opacity = this.opacity;
+	shape.isDashed = this.dashed;
+	shape.isShadow = false;
+	
+	shape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+	shape.init(this.graph.getView().getOverlayPane());
+	mxEvent.redirectMouseEvents(shape.node, this.graph, this.state);
+	
+	if (this.graph.dialect != mxConstants.DIALECT_SVG)
+	{
+		shape.pointerEvents = false;
+	}
+	else
+	{
+		shape.svgPointerEvents = 'stroke';
+	}
+	
+	return shape;
+};
+
+/**
+ * Function: repaint
+ * 
+ * Updates the highlight after a change of the model or view.
+ */
+mxCellHighlight.prototype.getStrokeWidth = function(state)
+{
+	return this.strokeWidth;
+};
+
+/**
+ * Function: repaint
+ * 
+ * Updates the highlight after a change of the model or view.
+ */
+mxCellHighlight.prototype.repaint = function()
+{
+	if (this.state != null && this.shape != null)
+	{
+		this.shape.scale = this.state.view.scale;
+		
+		if (this.graph.model.isEdge(this.state.cell))
+		{
+			this.shape.strokewidth = this.getStrokeWidth();
+			this.shape.points = this.state.absolutePoints;
+			this.shape.outline = false;
+		}
+		else
+		{
+			this.shape.bounds = new mxRectangle(this.state.x - this.spacing, this.state.y - this.spacing,
+					this.state.width + 2 * this.spacing, this.state.height + 2 * this.spacing);
+			this.shape.rotation = Number(this.state.style[mxConstants.STYLE_ROTATION] || '0');
+			this.shape.strokewidth = this.getStrokeWidth() / this.state.view.scale;
+			this.shape.outline = true;
+		}
+
+		// Uses cursor from shape in highlight
+		if (this.state.shape != null)
+		{
+			this.shape.setCursor(this.state.shape.getCursor());
+		}
+		
+		// Workaround for event transparency in VML with transparent color
+		// is to use a non-transparent color with near zero opacity
+		if (mxClient.IS_QUIRKS || document.documentMode == 8)
+		{
+			if (this.shape.stroke == 'transparent')
+			{
+				// KNOWN: Quirks mode does not seem to catch events if
+				// we do not force an update of the DOM via a change such
+				// as mxLog.debug. Since IE6 is EOL we do not add a fix.
+				this.shape.stroke = 'white';
+				this.shape.opacity = 1;
+			}
+			else
+			{
+				this.shape.opacity = this.opacity;
+			}
+		}
+		
+		this.shape.redraw();
+	}
+};
+
+/**
+ * Function: hide
+ * 
+ * Resets the state of the cell marker.
+ */
+mxCellHighlight.prototype.hide = function()
+{
+	this.highlight(null);
+};
+
+/**
+ * Function: mark
+ * 
+ * Marks the <markedState> and fires a <mark> event.
+ */
+mxCellHighlight.prototype.highlight = function(state)
+{
+	if (this.state != state)
+	{
+		if (this.shape != null)
+		{
+			this.shape.destroy();
+			this.shape = null;
+		}
+
+		this.state = state;
+		
+		if (this.state != null)
+		{
+			this.drawHighlight();
+		}
+	}
+};
+
+/**
+ * Function: isHighlightAt
+ * 
+ * Returns true if this highlight is at the given position.
+ */
+mxCellHighlight.prototype.isHighlightAt = function(x, y)
+{
+	var hit = false;
+	
+	// Quirks mode is currently not supported as it used a different coordinate system
+	if (this.shape != null && document.elementFromPoint != null && !mxClient.IS_QUIRKS)
+	{
+		var elt = document.elementFromPoint(x, y);
+
+		while (elt != null)
+		{
+			if (elt == this.shape.node)
+			{
+				hit = true;
+				break;
+			}
+			
+			elt = elt.parentNode;
+		}
+	}
+	
+	return hit;
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the handler and all its resources and DOM nodes.
+ */
+mxCellHighlight.prototype.destroy = function()
+{
+	this.graph.getView().removeListener(this.resetHandler);
+	this.graph.getView().removeListener(this.repaintHandler);
+	this.graph.getModel().removeListener(this.repaintHandler);
+	
+	if (this.shape != null)
+	{
+		this.shape.destroy();
+		this.shape = null;
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxDefaultKeyHandler
+ *
+ * Binds keycodes to actionnames in an editor. This aggregates an internal
+ * <handler> and extends the implementation of <mxKeyHandler.escape> to not
+ * only cancel the editing, but also hide the properties dialog and fire an
+ * <mxEditor.escape> event via <editor>. An instance of this class is created
+ * by <mxEditor> and stored in <mxEditor.keyHandler>.
+ * 
+ * Example:
+ * 
+ * Bind the delete key to the delete action in an existing editor.
+ * 
+ * (code)
+ * var keyHandler = new mxDefaultKeyHandler(editor);
+ * keyHandler.bindAction(46, 'delete');
+ * (end)
+ *
+ * Codec:
+ * 
+ * This class uses the <mxDefaultKeyHandlerCodec> to read configuration
+ * data into an existing instance. See <mxDefaultKeyHandlerCodec> for a
+ * description of the configuration format.
+ * 
+ * Keycodes:
+ * 
+ * See <mxKeyHandler>.
+ * 
+ * An <mxEvent.ESCAPE> event is fired via the editor if the escape key is
+ * pressed.
+ * 
+ * Constructor: mxDefaultKeyHandler
+ *
+ * Constructs a new default key handler for the <mxEditor.graph> in the
+ * given <mxEditor>. (The editor may be null if a prototypical instance for
+ * a <mxDefaultKeyHandlerCodec> is created.)
+ * 
+ * Parameters:
+ * 
+ * editor - Reference to the enclosing <mxEditor>.
+ */
+function mxDefaultKeyHandler(editor)
+{
+	if (editor != null)
+	{
+		this.editor = editor;
+		this.handler = new mxKeyHandler(editor.graph);
+		
+		// Extends the escape function of the internal key
+		// handle to hide the properties dialog and fire
+		// the escape event via the editor instance
+		var old = this.handler.escape;
+		
+		this.handler.escape = function(evt)
+		{
+			old.apply(this, arguments);
+			editor.hideProperties();
+			editor.fireEvent(new mxEventObject(mxEvent.ESCAPE, 'event', evt));
+		};
+	}
+};
+	
+/**
+ * Variable: editor
+ *
+ * Reference to the enclosing <mxEditor>.
+ */
+mxDefaultKeyHandler.prototype.editor = null;
+
+/**
+ * Variable: handler
+ *
+ * Holds the <mxKeyHandler> for key event handling.
+ */
+mxDefaultKeyHandler.prototype.handler = null;
+
+/**
+ * Function: bindAction
+ *
+ * Binds the specified keycode to the given action in <editor>. The
+ * optional control flag specifies if the control key must be pressed
+ * to trigger the action.
+ *
+ * Parameters:
+ *
+ * code - Integer that specifies the keycode.
+ * action - Name of the action to execute in <editor>.
+ * control - Optional boolean that specifies if control must be pressed.
+ * Default is false.
+ */
+mxDefaultKeyHandler.prototype.bindAction = function (code, action, control)
+{
+	var keyHandler = mxUtils.bind(this, function()
+	{
+		this.editor.execute(action);
+	});
+
+	// Binds the function to control-down keycode
+	if (control)
+	{
+		this.handler.bindControlKey(code, keyHandler);
+	}
+
+	// Binds the function to the normal keycode
+	else
+	{
+		this.handler.bindKey(code, keyHandler);				
+	}
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the <handler> associated with this object. This does normally
+ * not need to be called, the <handler> is destroyed automatically when the
+ * window unloads (in IE) by <mxEditor>.
+ */
+mxDefaultKeyHandler.prototype.destroy = function ()
+{
+	this.handler.destroy();
+	this.handler = null;
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxDefaultPopupMenu
+ *
+ * Creates popupmenus for mouse events. This object holds an XML node
+ * which is a description of the popup menu to be created. In
+ * <createMenu>, the configuration is applied to the context and
+ * the resulting menu items are added to the menu dynamically. See
+ * <createMenu> for a description of the configuration format.
+ * 
+ * This class does not create the DOM nodes required for the popup menu, it
+ * only parses an XML description to invoke the respective methods on an
+ * <mxPopupMenu> each time the menu is displayed.
+ *
+ * Codec:
+ * 
+ * This class uses the <mxDefaultPopupMenuCodec> to read configuration
+ * data into an existing instance, however, the actual parsing is done
+ * by this class during program execution, so the format is described
+ * below.
+ * 
+ * Constructor: mxDefaultPopupMenu
+ *
+ * Constructs a new popupmenu-factory based on given configuration.
+ *
+ * Paramaters:
+ *
+ * config - XML node that contains the configuration data.
+ */
+function mxDefaultPopupMenu(config)
+{
+	this.config = config;
+};
+
+/**
+ * Variable: imageBasePath
+ *
+ * Base path for all icon attributes in the config. Default is null.
+ */
+mxDefaultPopupMenu.prototype.imageBasePath = null;
+
+/**
+ * Variable: config
+ *
+ * XML node used as the description of new menu items. This node is
+ * used in <createMenu> to dynamically create the menu items if their
+ * respective conditions evaluate to true for the given arguments.
+ */
+mxDefaultPopupMenu.prototype.config = null;
+
+/**
+ * Function: createMenu
+ *
+ * This function is called from <mxEditor> to add items to the
+ * given menu based on <config>. The config is a sequence of
+ * the following nodes and attributes.
+ *
+ * Child Nodes: 
+ *
+ * add - Adds a new menu item. See below for attributes.
+ * separator - Adds a separator. No attributes.
+ * condition - Adds a custom condition. Name attribute.
+ * 
+ * The add-node may have a child node that defines a function to be invoked
+ * before the action is executed (or instead of an action to be executed).
+ *
+ * Attributes:
+ *
+ * as - Resource key for the label (needs entry in property file).
+ * action - Name of the action to execute in enclosing editor.
+ * icon - Optional icon (relative/absolute URL).
+ * iconCls - Optional CSS class for the icon.
+ * if - Optional name of condition that must be true (see below).
+ * enabled-if - Optional name of condition that specifies if the menu item
+ * should be enabled.
+ * name - Name of custom condition. Only for condition nodes.
+ *
+ * Conditions:
+ *
+ * nocell - No cell under the mouse.
+ * ncells - More than one cell selected.
+ * notRoot - Drilling position is other than home.
+ * cell - Cell under the mouse.
+ * notEmpty - Exactly one cell with children under mouse.
+ * expandable - Exactly one expandable cell under mouse.
+ * collapsable - Exactly one collapsable cell under mouse.
+ * validRoot - Exactly one cell which is a possible root under mouse.
+ * swimlane - Exactly one cell which is a swimlane under mouse.
+ *
+ * Example:
+ *
+ * To add a new item for a given action to the popupmenu:
+ * 
+ * (code)
+ * <mxDefaultPopupMenu as="popupHandler">
+ *   <add as="delete" action="delete" icon="images/delete.gif" if="cell"/>
+ * </mxDefaultPopupMenu>
+ * (end)
+ * 
+ * To add a new item for a custom function:
+ * 
+ * (code)
+ * <mxDefaultPopupMenu as="popupHandler">
+ *   <add as="action1"><![CDATA[
+ *		function (editor, cell, evt)
+ *		{
+ *			editor.execute('action1', cell, 'myArg');
+ *		}
+ *   ]]></add>
+ * </mxDefaultPopupMenu>
+ * (end)
+ * 
+ * The above example invokes action1 with an additional third argument via
+ * the editor instance. The third argument is passed to the function that
+ * defines action1. If the add-node has no action-attribute, then only the
+ * function defined in the text content is executed, otherwise first the
+ * function and then the action defined in the action-attribute is
+ * executed. The function in the text content has 3 arguments, namely the
+ * <mxEditor> instance, the <mxCell> instance under the mouse, and the
+ * native mouse event.
+ *
+ * Custom Conditions:
+ *
+ * To add a new condition for popupmenu items:
+ *  
+ * (code)
+ * <condition name="condition1"><![CDATA[
+ *   function (editor, cell, evt)
+ *   {
+ *     return cell != null;
+ *   }
+ * ]]></condition>
+ * (end)
+ * 
+ * The new condition can then be used in any item as follows:
+ * 
+ * (code)
+ * <add as="action1" action="action1" icon="action1.gif" if="condition1"/>
+ * (end)
+ * 
+ * The order in which the items and conditions appear is not significant as
+ * all connditions are evaluated before any items are created.
+ * 
+ * Parameters:
+ *
+ * editor - Enclosing <mxEditor> instance.
+ * menu - <mxPopupMenu> that is used for adding items and separators.
+ * cell - Optional <mxCell> which is under the mousepointer.
+ * evt - Optional mouse event which triggered the menu. 
+ */
+mxDefaultPopupMenu.prototype.createMenu = function(editor, menu, cell, evt)
+{
+	if (this.config != null)
+	{
+		var conditions = this.createConditions(editor, cell, evt);
+		var item = this.config.firstChild;
+
+		this.addItems(editor, menu, cell, evt, conditions, item, null);
+	}
+};
+
+/**
+ * Function: addItems
+ * 
+ * Recursively adds the given items and all of its children into the given menu.
+ * 
+ * Parameters:
+ *
+ * editor - Enclosing <mxEditor> instance.
+ * menu - <mxPopupMenu> that is used for adding items and separators.
+ * cell - Optional <mxCell> which is under the mousepointer.
+ * evt - Optional mouse event which triggered the menu.
+ * conditions - Array of names boolean conditions.
+ * item - XML node that represents the current menu item.
+ * parent - DOM node that represents the parent menu item.
+ */
+mxDefaultPopupMenu.prototype.addItems = function(editor, menu, cell, evt, conditions, item, parent)
+{
+	var addSeparator = false;
+	
+	while (item != null)
+	{
+		if (item.nodeName == 'add')
+		{
+			var condition = item.getAttribute('if');
+			
+			if (condition == null || conditions[condition])
+			{
+				var as = item.getAttribute('as');
+				as = mxResources.get(as) || as;
+				var funct = mxUtils.eval(mxUtils.getTextContent(item));
+				var action = item.getAttribute('action');
+				var icon = item.getAttribute('icon');
+				var iconCls = item.getAttribute('iconCls');
+				var enabledCond = item.getAttribute('enabled-if');
+				var enabled = enabledCond == null || conditions[enabledCond];
+				
+				if (addSeparator)
+				{
+					menu.addSeparator(parent);
+					addSeparator = false;
+				}
+				
+				if (icon != null && this.imageBasePath)
+				{
+					icon = this.imageBasePath + icon;
+				}
+				
+				var row = this.addAction(menu, editor, as, icon, funct, action, cell, parent, iconCls, enabled);
+				this.addItems(editor, menu, cell, evt, conditions, item.firstChild, row);
+			}
+		}
+		else if (item.nodeName == 'separator')
+		{
+			addSeparator = true;
+		}
+		
+		item = item.nextSibling;
+	}
+};
+
+/**
+ * Function: addAction
+ *
+ * Helper method to bind an action to a new menu item.
+ * 
+ * Parameters:
+ *
+ * menu - <mxPopupMenu> that is used for adding items and separators.
+ * editor - Enclosing <mxEditor> instance.
+ * lab - String that represents the label of the menu item.
+ * icon - Optional URL that represents the icon of the menu item.
+ * action - Optional name of the action to execute in the given editor.
+ * funct - Optional function to execute before the optional action. The
+ * function takes an <mxEditor>, the <mxCell> under the mouse and the
+ * mouse event that triggered the call.
+ * cell - Optional <mxCell> to use as an argument for the action.
+ * parent - DOM node that represents the parent menu item.
+ * iconCls - Optional CSS class for the menu icon.
+ * enabled - Optional boolean that specifies if the menu item is enabled.
+ * Default is true.
+ */
+mxDefaultPopupMenu.prototype.addAction = function(menu, editor, lab, icon, funct, action, cell, parent, iconCls, enabled)
+{
+	var clickHandler = function(evt)
+	{
+		if (typeof(funct) == 'function')
+		{
+			funct.call(editor, editor, cell, evt);
+		}
+		
+		if (action != null)
+		{
+			editor.execute(action, cell, evt);
+		}
+	};
+	
+	return menu.addItem(lab, icon, clickHandler, parent, iconCls, enabled);
+};
+
+/**
+ * Function: createConditions
+ * 
+ * Evaluates the default conditions for the given context.
+ */
+mxDefaultPopupMenu.prototype.createConditions = function(editor, cell, evt)
+{
+	// Creates array with conditions
+	var model = editor.graph.getModel();
+	var childCount = model.getChildCount(cell);
+	
+	// Adds some frequently used conditions
+	var conditions = [];
+	conditions['nocell'] = cell == null;
+	conditions['ncells'] = editor.graph.getSelectionCount() > 1;
+	conditions['notRoot'] = model.getRoot() !=
+		model.getParent(editor.graph.getDefaultParent());
+	conditions['cell'] = cell != null;
+	
+	var isCell = cell != null && editor.graph.getSelectionCount() == 1;
+	conditions['nonEmpty'] = isCell && childCount > 0;
+	conditions['expandable'] = isCell && editor.graph.isCellFoldable(cell, false);
+	conditions['collapsable'] = isCell && editor.graph.isCellFoldable(cell, true);
+	conditions['validRoot'] = isCell && editor.graph.isValidRoot(cell);
+	conditions['emptyValidRoot'] = conditions['validRoot'] && childCount == 0;
+	conditions['swimlane'] = isCell && editor.graph.isSwimlane(cell);
+
+	// Evaluates dynamic conditions from config file
+	var condNodes = this.config.getElementsByTagName('condition');
+	
+	for (var i=0; i<condNodes.length; i++)
+	{
+		var funct = mxUtils.eval(mxUtils.getTextContent(condNodes[i]));
+		var name = condNodes[i].getAttribute('name');
+		
+		if (name != null && typeof(funct) == 'function')
+		{
+			conditions[name] = funct(editor, cell, evt);
+		}
+	}
+	
+	return conditions;
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxDefaultToolbar
+ *
+ * Toolbar for the editor. This modifies the state of the graph
+ * or inserts new cells upon mouse clicks.
+ * 
+ * Example:
+ * 
+ * Create a toolbar with a button to copy the selection into the clipboard,
+ * and a combo box with one action to paste the selection from the clipboard
+ * into the graph.
+ * 
+ * (code)
+ * var toolbar = new mxDefaultToolbar(container, editor);
+ * toolbar.addItem('Copy', null, 'copy');
+ * 
+ * var combo = toolbar.addActionCombo('More actions...');
+ * toolbar.addActionOption(combo, 'Paste', 'paste');
+ * (end) 
+ *
+ * Codec:
+ * 
+ * This class uses the <mxDefaultToolbarCodec> to read configuration
+ * data into an existing instance. See <mxDefaultToolbarCodec> for a
+ * description of the configuration format.
+ * 
+ * Constructor: mxDefaultToolbar
+ *
+ * Constructs a new toolbar for the given container and editor. The
+ * container and editor may be null if a prototypical instance for a
+ * <mxDefaultKeyHandlerCodec> is created.
+ *
+ * Parameters:
+ *
+ * container - DOM node that contains the toolbar.
+ * editor - Reference to the enclosing <mxEditor>. 
+ */
+function mxDefaultToolbar(container, editor)
+{
+	this.editor = editor;
+
+	if (container != null && editor != null)
+	{
+		this.init(container);
+	}
+};
+	
+/**
+ * Variable: editor
+ *
+ * Reference to the enclosing <mxEditor>.
+ */
+mxDefaultToolbar.prototype.editor = null;
+
+/**
+ * Variable: toolbar
+ *
+ * Holds the internal <mxToolbar>.
+ */
+mxDefaultToolbar.prototype.toolbar = null;
+
+/**
+ * Variable: resetHandler
+ *
+ * Reference to the function used to reset the <toolbar>.
+ */
+mxDefaultToolbar.prototype.resetHandler = null;
+
+/**
+ * Variable: spacing
+ *
+ * Defines the spacing between existing and new vertices in
+ * gridSize units when a new vertex is dropped on an existing
+ * cell. Default is 4 (40 pixels).
+ */
+mxDefaultToolbar.prototype.spacing = 4;
+
+/**
+ * Variable: connectOnDrop
+ * 
+ * Specifies if elements should be connected if new cells are dropped onto
+ * connectable elements. Default is false.
+ */
+mxDefaultToolbar.prototype.connectOnDrop = false;
+
+/**
+ * Variable: init
+ * 
+ * Constructs the <toolbar> for the given container and installs a listener
+ * that updates the <mxEditor.insertFunction> on <editor> if an item is
+ * selected in the toolbar. This assumes that <editor> is not null.
+ *
+ * Parameters:
+ *
+ * container - DOM node that contains the toolbar.
+ */
+mxDefaultToolbar.prototype.init = function(container)
+{
+	if (container != null)
+	{
+		this.toolbar = new mxToolbar(container);
+		
+		// Installs the insert function in the editor if an item is
+		// selected in the toolbar
+		this.toolbar.addListener(mxEvent.SELECT, mxUtils.bind(this, function(sender, evt)
+		{
+			var funct = evt.getProperty('function');
+			
+			if (funct != null)
+			{
+				this.editor.insertFunction = mxUtils.bind(this, function()
+				{
+					funct.apply(this, arguments);
+					this.toolbar.resetMode();
+				});
+			}
+			else
+			{
+				this.editor.insertFunction = null;
+			}
+		}));
+		
+		// Resets the selected tool after a doubleclick or escape keystroke
+		this.resetHandler = mxUtils.bind(this, function()
+		{
+			if (this.toolbar != null)
+			{
+				this.toolbar.resetMode(true);
+			}
+		});
+
+		this.editor.graph.addListener(mxEvent.DOUBLE_CLICK, this.resetHandler);
+		this.editor.addListener(mxEvent.ESCAPE, this.resetHandler);
+	}
+};
+
+/**
+ * Function: addItem
+ *
+ * Adds a new item that executes the given action in <editor>. The title,
+ * icon and pressedIcon are used to display the toolbar item.
+ * 
+ * Parameters:
+ *
+ * title - String that represents the title (tooltip) for the item.
+ * icon - URL of the icon to be used for displaying the item.
+ * action - Name of the action to execute when the item is clicked.
+ * pressed - Optional URL of the icon for the pressed state.
+ */
+mxDefaultToolbar.prototype.addItem = function(title, icon, action, pressed)
+{
+	var clickHandler = mxUtils.bind(this, function()
+	{
+		if (action != null && action.length > 0)
+		{
+			this.editor.execute(action);
+		}
+	});
+	
+	return this.toolbar.addItem(title, icon, clickHandler, pressed);
+};
+
+/**
+ * Function: addSeparator
+ *
+ * Adds a vertical separator using the optional icon.
+ * 
+ * Parameters:
+ * 
+ * icon - Optional URL of the icon that represents the vertical separator.
+ * Default is <mxClient.imageBasePath> + '/separator.gif'.
+ */
+mxDefaultToolbar.prototype.addSeparator = function(icon)
+{
+	icon = icon || mxClient.imageBasePath + '/separator.gif';
+	this.toolbar.addSeparator(icon);
+};
+	
+/**
+ * Function: addCombo
+ *
+ * Helper method to invoke <mxToolbar.addCombo> on <toolbar> and return the
+ * resulting DOM node.
+ */
+mxDefaultToolbar.prototype.addCombo = function()
+{
+	return this.toolbar.addCombo();
+};
+		
+/**
+ * Function: addActionCombo
+ *
+ * Helper method to invoke <mxToolbar.addActionCombo> on <toolbar> using
+ * the given title and return the resulting DOM node.
+ * 
+ * Parameters:
+ * 
+ * title - String that represents the title of the combo.
+ */
+mxDefaultToolbar.prototype.addActionCombo = function(title)
+{
+	return this.toolbar.addActionCombo(title);
+};
+
+/**
+ * Function: addActionOption
+ *
+ * Binds the given action to a option with the specified label in the
+ * given combo. Combo is an object returned from an earlier call to
+ * <addCombo> or <addActionCombo>.
+ * 
+ * Parameters:
+ * 
+ * combo - DOM node that represents the combo box.
+ * title - String that represents the title of the combo.
+ * action - Name of the action to execute in <editor>.
+ */
+mxDefaultToolbar.prototype.addActionOption = function(combo, title, action)
+{
+	var clickHandler = mxUtils.bind(this, function()
+	{
+		this.editor.execute(action);
+	});
+	
+	this.addOption(combo, title, clickHandler);
+};
+
+/**
+ * Function: addOption
+ *
+ * Helper method to invoke <mxToolbar.addOption> on <toolbar> and return
+ * the resulting DOM node that represents the option.
+ * 
+ * Parameters:
+ * 
+ * combo - DOM node that represents the combo box.
+ * title - String that represents the title of the combo.
+ * value - Object that represents the value of the option.
+ */
+mxDefaultToolbar.prototype.addOption = function(combo, title, value)
+{
+	return this.toolbar.addOption(combo, title, value);
+};
+	
+/**
+ * Function: addMode
+ *
+ * Creates an item for selecting the given mode in the <editor>'s graph.
+ * Supported modenames are select, connect and pan.
+ * 
+ * Parameters:
+ * 
+ * title - String that represents the title of the item.
+ * icon - URL of the icon that represents the item.
+ * mode - String that represents the mode name to be used in
+ * <mxEditor.setMode>.
+ * pressed - Optional URL of the icon that represents the pressed state.
+ * funct - Optional JavaScript function that takes the <mxEditor> as the
+ * first and only argument that is executed after the mode has been
+ * selected.
+ */
+mxDefaultToolbar.prototype.addMode = function(title, icon, mode, pressed, funct)
+{
+	var clickHandler = mxUtils.bind(this, function()
+	{
+		this.editor.setMode(mode);
+		
+		if (funct != null)
+		{
+			funct(this.editor);
+		}
+	});
+	
+	return this.toolbar.addSwitchMode(title, icon, clickHandler, pressed);
+};
+
+/**
+ * Function: addPrototype
+ *
+ * Creates an item for inserting a clone of the specified prototype cell into
+ * the <editor>'s graph. The ptype may either be a cell or a function that
+ * returns a cell.
+ * 
+ * Parameters:
+ * 
+ * title - String that represents the title of the item.
+ * icon - URL of the icon that represents the item.
+ * ptype - Function or object that represents the prototype cell. If ptype
+ * is a function then it is invoked with no arguments to create new
+ * instances.
+ * pressed - Optional URL of the icon that represents the pressed state.
+ * insert - Optional JavaScript function that handles an insert of the new
+ * cell. This function takes the <mxEditor>, new cell to be inserted, mouse
+ * event and optional <mxCell> under the mouse pointer as arguments.
+ * toggle - Optional boolean that specifies if the item can be toggled.
+ * Default is true.
+ */
+mxDefaultToolbar.prototype.addPrototype = function(title, icon, ptype, pressed, insert, toggle)
+{
+	// Creates a wrapper function that is in charge of constructing
+	// the new cell instance to be inserted into the graph
+	var factory = mxUtils.bind(this, function()
+	{
+		if (typeof(ptype) == 'function')
+		{
+			return ptype();
+		}
+		else if (ptype != null)
+		{
+			return this.editor.graph.cloneCells([ptype])[0];
+		}
+		
+		return null;
+	});
+	
+	// Defines the function for a click event on the graph
+	// after this item has been selected in the toolbar
+	var clickHandler = mxUtils.bind(this, function(evt, cell)
+	{
+		if (typeof(insert) == 'function')
+		{
+			insert(this.editor, factory(), evt, cell);
+		}
+		else
+		{
+			this.drop(factory(), evt, cell);
+		}
+		
+		this.toolbar.resetMode();
+		mxEvent.consume(evt);
+	});
+	
+	var img = this.toolbar.addMode(title, icon, clickHandler, pressed, null, toggle);
+				
+	// Creates a wrapper function that calls the click handler without
+	// the graph argument
+	var dropHandler = function(graph, evt, cell)
+	{
+		clickHandler(evt, cell);
+	};
+	
+	this.installDropHandler(img, dropHandler);
+	
+	return img;
+};
+
+/**
+ * Function: drop
+ * 
+ * Handles a drop from a toolbar item to the graph. The given vertex
+ * represents the new cell to be inserted. This invokes <insert> or
+ * <connect> depending on the given target cell.
+ * 
+ * Parameters:
+ * 
+ * vertex - <mxCell> to be inserted.
+ * evt - Mouse event that represents the drop.
+ * target - Optional <mxCell> that represents the drop target.
+ */
+mxDefaultToolbar.prototype.drop = function(vertex, evt, target)
+{
+	var graph = this.editor.graph;
+	var model = graph.getModel();
+	
+	if (target == null ||
+		model.isEdge(target) ||
+		!this.connectOnDrop ||
+		!graph.isCellConnectable(target))
+	{
+		while (target != null &&
+			!graph.isValidDropTarget(target, [vertex], evt))
+		{
+			target = model.getParent(target);
+		}
+		
+		this.insert(vertex, evt, target);
+	}
+	else
+	{
+		this.connect(vertex, evt, target);
+	}
+};
+
+/**
+ * Function: insert
+ *
+ * Handles a drop by inserting the given vertex into the given parent cell
+ * or the default parent if no parent is specified.
+ * 
+ * Parameters:
+ * 
+ * vertex - <mxCell> to be inserted.
+ * evt - Mouse event that represents the drop.
+ * parent - Optional <mxCell> that represents the parent.
+ */
+mxDefaultToolbar.prototype.insert = function(vertex, evt, target)
+{
+	var graph = this.editor.graph;
+	
+	if (graph.canImportCell(vertex))
+	{
+		var x = mxEvent.getClientX(evt);
+		var y = mxEvent.getClientY(evt);
+		var pt = mxUtils.convertPoint(graph.container, x, y);
+		
+		// Splits the target edge or inserts into target group
+		if (graph.isSplitEnabled() &&
+			graph.isSplitTarget(target, [vertex], evt))
+		{
+			return graph.splitEdge(target, [vertex], null, pt.x, pt.y);
+		}
+		else
+		{
+			return this.editor.addVertex(target, vertex, pt.x, pt.y);
+		}
+	}
+	
+	return null;
+};
+
+/**
+ * Function: connect
+ * 
+ * Handles a drop by connecting the given vertex to the given source cell.
+ * 
+ * vertex - <mxCell> to be inserted.
+ * evt - Mouse event that represents the drop.
+ * source - Optional <mxCell> that represents the source terminal.
+ */
+mxDefaultToolbar.prototype.connect = function(vertex, evt, source)
+{
+	var graph = this.editor.graph;
+	var model = graph.getModel();
+	
+	if (source != null &&
+		graph.isCellConnectable(vertex) &&
+		graph.isEdgeValid(null, source, vertex))
+	{
+		var edge = null;
+
+		model.beginUpdate();
+		try
+		{
+			var geo = model.getGeometry(source);
+			var g = model.getGeometry(vertex).clone();
+			
+			// Moves the vertex away from the drop target that will
+			// be used as the source for the new connection
+			g.x = geo.x + (geo.width - g.width) / 2;
+			g.y = geo.y + (geo.height - g.height) / 2;
+			
+			var step = this.spacing * graph.gridSize;
+			var dist = model.getDirectedEdgeCount(source, true) * 20;
+			
+			if (this.editor.horizontalFlow)
+			{
+				g.x += (g.width + geo.width) / 2 + step + dist;
+			}
+			else
+			{
+				g.y += (g.height + geo.height) / 2 + step + dist;
+			}
+			
+			vertex.setGeometry(g);
+			
+			// Fires two add-events with the code below - should be fixed
+			// to only fire one add event for both inserts
+			var parent = model.getParent(source);
+			graph.addCell(vertex, parent);
+			graph.constrainChild(vertex);
+
+			// Creates the edge using the editor instance and calls
+			// the second function that fires an add event
+			edge = this.editor.createEdge(source, vertex);
+			
+			if (model.getGeometry(edge) == null)
+			{
+				var edgeGeometry = new mxGeometry();
+				edgeGeometry.relative = true;
+				
+				model.setGeometry(edge, edgeGeometry);
+			}
+			
+			graph.addEdge(edge, parent, source, vertex);
+		}
+		finally
+		{
+			model.endUpdate();
+		}
+		
+		graph.setSelectionCells([vertex, edge]);
+		graph.scrollCellToVisible(vertex);
+	}
+};
+
+/**
+ * Function: installDropHandler
+ * 
+ * Makes the given img draggable using the given function for handling a
+ * drop event.
+ * 
+ * Parameters:
+ * 
+ * img - DOM node that represents the image.
+ * dropHandler - Function that handles a drop of the image.
+ */
+mxDefaultToolbar.prototype.installDropHandler = function (img, dropHandler)
+{
+	var sprite = document.createElement('img');
+	sprite.setAttribute('src', img.getAttribute('src'));
+
+	// Handles delayed loading of the images
+	var loader = mxUtils.bind(this, function(evt)
+	{
+		// Preview uses the image node with double size. Later this can be
+		// changed to use a separate preview and guides, but for this the
+		// dropHandler must use the additional x- and y-arguments and the
+		// dragsource which makeDraggable returns much be configured to
+		// use guides via mxDragSource.isGuidesEnabled.
+		sprite.style.width = (2 * img.offsetWidth) + 'px';
+		sprite.style.height = (2 * img.offsetHeight) + 'px';
+
+		mxUtils.makeDraggable(img, this.editor.graph, dropHandler,
+			sprite);
+		mxEvent.removeListener(sprite, 'load', loader);
+	});
+
+	if (mxClient.IS_IE)
+	{
+		loader();
+	}
+	else
+	{
+		mxEvent.addListener(sprite, 'load', loader);
+	}	
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the <toolbar> associated with this object and removes all
+ * installed listeners. This does normally not need to be called, the
+ * <toolbar> is destroyed automatically when the window unloads (in IE) by
+ * <mxEditor>.
+ */
+mxDefaultToolbar.prototype.destroy = function ()
+{
+	if (this.resetHandler != null)
+	{
+		this.editor.graph.removeListener('dblclick', this.resetHandler);
+		this.editor.removeListener('escape', this.resetHandler);
+		this.resetHandler = null;
+	}
+	
+	if (this.toolbar != null)
+	{
+		this.toolbar.destroy();
+		this.toolbar = null;
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxEditor
+ *
+ * Extends <mxEventSource> to implement a application wrapper for a graph that
+ * adds <actions>, I/O using <mxCodec>, auto-layout using <mxLayoutManager>,
+ * command history using <undoManager>, and standard dialogs and widgets, eg.
+ * properties, help, outline, toolbar, and popupmenu. It also adds <templates>
+ * to be used as cells in toolbars, auto-validation using the <validation>
+ * flag, attribute cycling using <cycleAttributeValues>, higher-level events
+ * such as <root>, and backend integration using <urlPost> and <urlImage>. 
+ * 
+ * Actions:
+ * 
+ * Actions are functions stored in the <actions> array under their names. The
+ * functions take the <mxEditor> as the first, and an optional <mxCell> as the
+ * second argument and are invoked using <execute>. Any additional arguments
+ * passed to execute are passed on to the action as-is.
+ * 
+ * A list of built-in actions is available in the <addActions> description.
+ * 
+ * Read/write Diagrams:
+ * 
+ * To read a diagram from an XML string, for example from a textfield within the 
+ * page, the following code is used:
+ * 
+ * (code)
+ * var doc = mxUtils.parseXML(xmlString);
+ * var node = doc.documentElement;
+ * editor.readGraphModel(node);
+ * (end)
+ * 
+ * For reading a diagram from a remote location, use the <open> method.
+ * 
+ * To save diagrams in XML on a server, you can set the <urlPost> variable. 
+ * This variable will be used in <getUrlPost> to construct a URL for the post 
+ * request that is issued in the <save> method. The post request contains the 
+ * XML representation of the diagram as returned by <writeGraphModel> in the 
+ * xml parameter.
+ * 
+ * On the server side, the post request is processed using standard
+ * technologies such as Java Servlets, CGI, .NET or ASP.
+ * 
+ * Here are some examples of processing a post request in various languages.
+ * 
+ * - Java: URLDecoder.decode(request.getParameter("xml"), "UTF-8").replace("\n", "&#xa;")
+ * 
+ * Note that the linefeeds should only be replaced if the XML is
+ * processed in Java, for example when creating an image, but not
+ * if the XML is passed back to the client-side.
+ * 
+ * - .NET: HttpUtility.UrlDecode(context.Request.Params["xml"])
+ * - PHP: urldecode($_POST["xml"])
+ * 
+ * Creating images:
+ * 
+ * A backend (Java, PHP or C#) is required for creating images. The
+ * distribution contains an example for each backend (ImageHandler.java,
+ * ImageHandler.cs and graph.php). More information about using a backend
+ * to create images can be found in the readme.html files. Note that the
+ * preview is implemented using VML/SVG in the browser and does not require
+ * a backend. The backend is only required to creates images (bitmaps).
+ * 
+ * Special characters:
+ * 
+ * Note There are five characters that should always appear in XML content as
+ * escapes, so that they do not interact with the syntax of the markup. These
+ * are part of the language for all documents based on XML and for HTML.
+ * 
+ * - &lt; (<)
+ * - &gt; (>)
+ * - &amp; (&)
+ * - &quot; (")
+ * - &apos; (')
+ * 
+ * Although it is part of the XML language, &apos; is not defined in HTML.
+ * For this reason the XHTML specification recommends instead the use of
+ * &#39; if text may be passed to a HTML user agent.
+ * 
+ * If you are having problems with special characters on the server-side then
+ * you may want to try the <escapePostData> flag.
+ * 
+ * For converting decimal escape sequences inside strings, a user has provided
+ * us with the following function:
+ * 
+ * (code)
+ * function html2js(text)
+ * {
+ *   var entitySearch = /&#[0-9]+;/;
+ *   var entity;
+ *   
+ *   while (entity = entitySearch.exec(text))
+ *   {
+ *     var charCode = entity[0].substring(2, entity[0].length -1);
+ *     text = text.substring(0, entity.index)
+ *            + String.fromCharCode(charCode)
+ *            + text.substring(entity.index + entity[0].length);
+ *   }
+ *   
+ *   return text;
+ * }
+ * (end)
+ * 
+ * Otherwise try using hex escape sequences and the built-in unescape function
+ * for converting such strings.
+ * 
+ * Local Files:
+ * 
+ * For saving and opening local files, no standardized method exists that
+ * works across all browsers. The recommended way of dealing with local files
+ * is to create a backend that streams the XML data back to the browser (echo)
+ * as an attachment so that a Save-dialog is displayed on the client-side and
+ * the file can be saved to the local disk.
+ * 
+ * For example, in PHP the code that does this looks as follows.
+ * 
+ * (code)
+ * $xml = stripslashes($_POST["xml"]);
+ * header("Content-Disposition: attachment; filename=\"diagram.xml\"");
+ * echo($xml);
+ * (end)
+ * 
+ * To open a local file, the file should be uploaded via a form in the browser
+ * and then opened from the server in the editor.
+ * 
+ * Cell Properties:
+ * 
+ * The properties displayed in the properties dialog are the attributes and 
+ * values of the cell's user object, which is an XML node. The XML node is 
+ * defined in the templates section of the config file.
+ * 
+ * The templates are stored in <mxEditor.templates> and contain cells which
+ * are cloned at insertion time to create new vertices by use of drag and
+ * drop from the toolbar. Each entry in the toolbar for adding a new vertex
+ * must refer to an existing template.
+ * 
+ * In the following example, the task node is a business object and only the 
+ * mxCell node and its mxGeometry child contain graph information:
+ * 
+ * (code)
+ * <Task label="Task" description="">
+ *   <mxCell vertex="true">
+ *     <mxGeometry as="geometry" width="72" height="32"/>
+ *   </mxCell>
+ * </Task> 
+ * (end)
+ * 
+ * The idea is that the XML representation is inverse from the in-memory 
+ * representation: The outer XML node is the user object and the inner node is 
+ * the cell. This means the user object of the cell is the Task node with no 
+ * children for the above example:
+ * 
+ * (code)
+ * <Task label="Task" description=""/>
+ * (end)
+ * 
+ * The Task node can have any tag name, attributes and child nodes. The 
+ * <mxCodec> will use the XML hierarchy as the user object, while removing the 
+ * "known annotations", such as the mxCell node. At save-time the cell data 
+ * will be "merged" back into the user object. The user object is only modified 
+ * via the properties dialog during the lifecycle of the cell.
+ * 
+ * In the default implementation of <createProperties>, the user object's
+ * attributes are put into a form for editing. Attributes are changed using
+ * the <mxCellAttributeChange> action in the model. The dialog can be replaced 
+ * by overriding the <createProperties> hook or by replacing the showProperties
+ * action in <actions>. Alternatively, the entry in the config file's popupmenu
+ * section can be modified to invoke a different action.
+ * 
+ * If you want to displey the properties dialog on a doubleclick, you can set
+ * <mxEditor.dblClickAction> to showProperties as follows:
+ * 
+ * (code)
+ * editor.dblClickAction = 'showProperties';
+ * (end)
+ * 
+ * Popupmenu and Toolbar:
+ * 
+ * The toolbar and popupmenu are typically configured using the respective
+ * sections in the config file, that is, the popupmenu is defined as follows:
+ * 
+ * (code)
+ * <mxEditor>
+ *   <mxDefaultPopupMenu as="popupHandler">
+ * 		<add as="cut" action="cut" icon="images/cut.gif"/>
+ *      ...
+ * (end)
+ * 
+ * New entries can be added to the toolbar by inserting an add-node into the
+ * above configuration. Existing entries may be removed and changed by
+ * modifying or removing the respective entries in the configuration.
+ * The configuration is read by the <mxDefaultPopupMenuCodec>, the format of the
+ * configuration is explained in <mxDefaultPopupMenu.decode>.
+ * 
+ * The toolbar is defined in the mxDefaultToolbar section. Items can be added
+ * and removed in this section.
+ * 
+ * (code)
+ * <mxEditor>
+ *   <mxDefaultToolbar>
+ *     <add as="save" action="save" icon="images/save.gif"/>
+ *     <add as="Swimlane" template="swimlane" icon="images/swimlane.gif"/>
+ *     ...
+ * (end)
+ * 
+ * The format of the configuration is described in
+ * <mxDefaultToolbarCodec.decode>.
+ * 
+ * Ids:
+ * 
+ * For the IDs, there is an implicit behaviour in <mxCodec>: It moves the Id
+ * from the cell to the user object at encoding time and vice versa at decoding
+ * time. For example, if the Task node from above has an id attribute, then
+ * the <mxCell.id> of the corresponding cell will have this value. If there
+ * is no Id collision in the model, then the cell may be retrieved using this
+ * Id with the <mxGraphModel.getCell> function. If there is a collision, a new
+ * Id will be created for the cell using <mxGraphModel.createId>. At encoding
+ * time, this new Id will replace the value previously stored under the id
+ * attribute in the Task node.
+ * 
+ * See <mxEditorCodec>, <mxDefaultToolbarCodec> and <mxDefaultPopupMenuCodec>
+ * for information about configuring the editor and user interface.
+ * 
+ * Programmatically inserting cells:
+ * 
+ * For inserting a new cell, say, by clicking a button in the document,
+ * the following code can be used. This requires an reference to the editor.
+ * 
+ * (code)
+ * var userObject = new Object();
+ * var parent = editor.graph.getDefaultParent();
+ * var model = editor.graph.model;
+ * model.beginUpdate();
+ * try
+ * {
+ *   editor.graph.insertVertex(parent, null, userObject, 20, 20, 80, 30);
+ * }
+ * finally
+ * {
+ *   model.endUpdate();
+ * }
+ * (end)
+ * 
+ * If a template cell from the config file should be inserted, then a clone
+ * of the template can be created as follows. The clone is then inserted using
+ * the add function instead of addVertex.
+ * 
+ * (code)
+ * var template = editor.templates['task'];
+ * var clone = editor.graph.model.cloneCell(template);
+ * (end)
+ * 
+ * Resources:
+ *
+ * resources/editor - Language resources for mxEditor
+ *
+ * Callback: onInit
+ *
+ * Called from within the constructor. In the callback,
+ * "this" refers to the editor instance.
+ *
+ * Cookie: mxgraph=seen
+ *
+ * Set when the editor is started. Never expires. Use
+ * <resetFirstTime> to reset this cookie. This cookie
+ * only exists if <onInit> is implemented.
+ *
+ * Event: mxEvent.OPEN
+ *
+ * Fires after a file was opened in <open>. The <code>filename</code> property
+ * contains the filename that was used. The same value is also available in
+ * <filename>.
+ *
+ * Event: mxEvent.SAVE
+ *
+ * Fires after the current file was saved in <save>. The <code>url</code>
+ * property contains the URL that was used for saving.
+ *
+ * Event: mxEvent.POST
+ * 
+ * Fires if a successful response was received in <postDiagram>. The
+ * <code>request</code> property contains the <mxXmlRequest>, the
+ * <code>url</code> and <code>data</code> properties contain the URL and the
+ * data that were used in the post request. 
+ *
+ * Event: mxEvent.ROOT
+ *
+ * Fires when the current root has changed, or when the title of the current
+ * root has changed. This event has no properties.
+ *
+ * Event: mxEvent.BEFORE_ADD_VERTEX
+ * 
+ * Fires before a vertex is added in <addVertex>. The <code>vertex</code>
+ * property contains the new vertex and the <code>parent</code> property
+ * contains its parent.
+ * 
+ * Event: mxEvent.ADD_VERTEX
+ * 
+ * Fires between begin- and endUpdate in <addVertex>. The <code>vertex</code>
+ * property contains the vertex that is being inserted.
+ * 
+ * Event: mxEvent.AFTER_ADD_VERTEX
+ * 
+ * Fires after a vertex was inserted and selected in <addVertex>. The
+ * <code>vertex</code> property contains the new vertex.
+ * 
+ * Example:
+ * 
+ * For starting an in-place edit after a new vertex has been added to the
+ * graph, the following code can be used.
+ * 
+ * (code)
+ * editor.addListener(mxEvent.AFTER_ADD_VERTEX, function(sender, evt)
+ * {
+ *   var vertex = evt.getProperty('vertex');
+ * 
+ *   if (editor.graph.isCellEditable(vertex))
+ *   {
+ *   	editor.graph.startEditingAtCell(vertex);
+ *   }
+ * });
+ * (end)
+ * 
+ * Event: mxEvent.ESCAPE
+ * 
+ * Fires when the escape key is pressed. The <code>event</code> property
+ * contains the key event.
+ * 
+ * Constructor: mxEditor
+ *
+ * Constructs a new editor. This function invokes the <onInit> callback
+ * upon completion.
+ *
+ * Example:
+ *
+ * (code)
+ * var config = mxUtils.load('config/diagrameditor.xml').getDocumentElement();
+ * var editor = new mxEditor(config);
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * config - Optional XML node that contains the configuration.
+ */
+function mxEditor(config)
+{
+	this.actions = [];
+	this.addActions();
+
+	// Executes the following only if a document has been instanciated.
+	// That is, don't execute when the editorcodec is setup.
+	if (document.body != null)
+	{
+		// Defines instance fields
+		this.cycleAttributeValues = [];
+		this.popupHandler = new mxDefaultPopupMenu();
+		this.undoManager = new mxUndoManager();
+
+		// Creates the graph and toolbar without the containers
+		this.graph = this.createGraph();
+		this.toolbar = this.createToolbar();
+
+		// Creates the global keyhandler (requires graph instance)
+		this.keyHandler = new mxDefaultKeyHandler(this);
+
+		// Configures the editor using the URI
+		// which was passed to the ctor
+		this.configure(config);
+		
+		// Assigns the swimlaneIndicatorColorAttribute on the graph
+		this.graph.swimlaneIndicatorColorAttribute = this.cycleAttributeName;
+
+		// Checks if the <onInit> hook has been set
+		if (this.onInit != null)
+		{
+			// Invokes the <onInit> hook
+			this.onInit();
+		}
+		
+		// Automatic deallocation of memory
+		if (mxClient.IS_IE)
+		{
+			mxEvent.addListener(window, 'unload', mxUtils.bind(this, function()
+			{
+				this.destroy();
+			}));
+		}
+	}
+};
+
+/**
+ * Installs the required language resources at class
+ * loading time.
+ */
+if (mxLoadResources)
+{
+	mxResources.add(mxClient.basePath+'/resources/editor');
+}
+
+/**
+ * Extends mxEventSource.
+ */
+mxEditor.prototype = new mxEventSource();
+mxEditor.prototype.constructor = mxEditor;
+
+/**
+ * Group: Controls and Handlers
+ */
+	
+/**
+ * Variable: askZoomResource
+ * 
+ * Specifies the resource key for the zoom dialog. If the resource for this
+ * key does not exist then the value is used as the error message. Default
+ * is 'askZoom'.
+ */
+mxEditor.prototype.askZoomResource = (mxClient.language != 'none') ? 'askZoom' : '';
+	
+/**
+ * Variable: lastSavedResource
+ * 
+ * Specifies the resource key for the last saved info. If the resource for
+ * this key does not exist then the value is used as the error message.
+ * Default is 'lastSaved'.
+ */
+mxEditor.prototype.lastSavedResource = (mxClient.language != 'none') ? 'lastSaved' : '';
+	
+/**
+ * Variable: currentFileResource
+ * 
+ * Specifies the resource key for the current file info. If the resource for
+ * this key does not exist then the value is used as the error message.
+ * Default is 'lastSaved'.
+ */
+mxEditor.prototype.currentFileResource = (mxClient.language != 'none') ? 'currentFile' : '';
+	
+/**
+ * Variable: propertiesResource
+ * 
+ * Specifies the resource key for the properties window title. If the
+ * resource for this key does not exist then the value is used as the
+ * error message. Default is 'properties'.
+ */
+mxEditor.prototype.propertiesResource = (mxClient.language != 'none') ? 'properties' : '';
+	
+/**
+ * Variable: tasksResource
+ * 
+ * Specifies the resource key for the tasks window title. If the
+ * resource for this key does not exist then the value is used as the
+ * error message. Default is 'tasks'.
+ */
+mxEditor.prototype.tasksResource = (mxClient.language != 'none') ? 'tasks' : '';
+	
+/**
+ * Variable: helpResource
+ * 
+ * Specifies the resource key for the help window title. If the
+ * resource for this key does not exist then the value is used as the
+ * error message. Default is 'help'.
+ */
+mxEditor.prototype.helpResource = (mxClient.language != 'none') ? 'help' : '';
+	
+/**
+ * Variable: outlineResource
+ * 
+ * Specifies the resource key for the outline window title. If the
+ * resource for this key does not exist then the value is used as the
+ * error message. Default is 'outline'.
+ */
+mxEditor.prototype.outlineResource = (mxClient.language != 'none') ? 'outline' : '';
+	
+/**
+ * Variable: outline
+ * 
+ * Reference to the <mxWindow> that contains the outline. The <mxOutline>
+ * is stored in outline.outline.
+ */
+mxEditor.prototype.outline = null;
+
+/**
+ * Variable: graph
+ *
+ * Holds a <mxGraph> for displaying the diagram. The graph
+ * is created in <setGraphContainer>.
+ */
+mxEditor.prototype.graph = null;
+
+/**
+ * Variable: graphRenderHint
+ *
+ * Holds the render hint used for creating the
+ * graph in <setGraphContainer>. See <mxGraph>.
+ * Default is null.
+ */
+mxEditor.prototype.graphRenderHint = null;
+
+/**
+ * Variable: toolbar
+ *
+ * Holds a <mxDefaultToolbar> for displaying the toolbar. The
+ * toolbar is created in <setToolbarContainer>.
+ */
+mxEditor.prototype.toolbar = null;
+
+/**
+ * Variable: status
+ *
+ * DOM container that holds the statusbar. Default is null.
+ * Use <setStatusContainer> to set this value.
+ */
+mxEditor.prototype.status = null;
+
+/**
+ * Variable: popupHandler
+ *
+ * Holds a <mxDefaultPopupMenu> for displaying
+ * popupmenus.
+ */
+mxEditor.prototype.popupHandler = null;
+
+/**
+ * Variable: undoManager
+ *
+ * Holds an <mxUndoManager> for the command history.
+ */
+mxEditor.prototype.undoManager = null;
+
+/**
+ * Variable: keyHandler
+ *
+ * Holds a <mxDefaultKeyHandler> for handling keyboard events.
+ * The handler is created in <setGraphContainer>.
+ */
+mxEditor.prototype.keyHandler = null;
+
+/**
+ * Group: Actions and Options
+ */
+
+/**
+ * Variable: actions
+ *
+ * Maps from actionnames to actions, which are functions taking
+ * the editor and the cell as arguments. Use <addAction>
+ * to add or replace an action and <execute> to execute an action
+ * by name, passing the cell to be operated upon as the second
+ * argument.
+ */
+mxEditor.prototype.actions = null;
+
+/**
+ * Variable: dblClickAction
+ *
+ * Specifies the name of the action to be executed
+ * when a cell is double clicked. Default is edit.
+ * 
+ * To handle a singleclick, use the following code.
+ * 
+ * (code)
+ * editor.graph.addListener(mxEvent.CLICK, function(sender, evt)
+ * {
+ *   var e = evt.getProperty('event');
+ *   var cell = evt.getProperty('cell');
+ * 
+ *   if (cell != null && !e.isConsumed())
+ *   {
+ *     // Do something useful with cell...
+ *     e.consume();
+ *   }
+ * });
+ * (end)
+ */
+mxEditor.prototype.dblClickAction = 'edit';
+
+/**
+ * Variable: swimlaneRequired
+ * 
+ * Specifies if new cells must be inserted
+ * into an existing swimlane. Otherwise, cells
+ * that are not swimlanes can be inserted as
+ * top-level cells. Default is false.
+ */
+mxEditor.prototype.swimlaneRequired = false;
+
+/**
+ * Variable: disableContextMenu
+ *
+ * Specifies if the context menu should be disabled in the graph container.
+ * Default is true.
+ */
+mxEditor.prototype.disableContextMenu = true;
+
+/**
+ * Group: Templates
+ */
+
+/**
+ * Variable: insertFunction
+ *
+ * Specifies the function to be used for inserting new
+ * cells into the graph. This is assigned from the
+ * <mxDefaultToolbar> if a vertex-tool is clicked.
+ */
+mxEditor.prototype.insertFunction = null;
+
+/**
+ * Variable: forcedInserting
+ *
+ * Specifies if a new cell should be inserted on a single
+ * click even using <insertFunction> if there is a cell 
+ * under the mousepointer, otherwise the cell under the 
+ * mousepointer is selected. Default is false.
+ */
+mxEditor.prototype.forcedInserting = false;
+
+/**
+ * Variable: templates
+ * 
+ * Maps from names to protoype cells to be used
+ * in the toolbar for inserting new cells into
+ * the diagram.
+ */
+mxEditor.prototype.templates = null;
+
+/**
+ * Variable: defaultEdge
+ * 
+ * Prototype edge cell that is used for creating
+ * new edges.
+ */
+mxEditor.prototype.defaultEdge = null;
+
+/**
+ * Variable: defaultEdgeStyle
+ * 
+ * Specifies the edge style to be returned in <getEdgeStyle>.
+ * Default is null.
+ */
+mxEditor.prototype.defaultEdgeStyle = null;
+
+/**
+ * Variable: defaultGroup
+ * 
+ * Prototype group cell that is used for creating
+ * new groups.
+ */
+mxEditor.prototype.defaultGroup = null;
+
+/**
+ * Variable: graphRenderHint
+ *
+ * Default size for the border of new groups. If null,
+ * then then <mxGraph.gridSize> is used. Default is
+ * null.
+ */
+mxEditor.prototype.groupBorderSize = null;
+
+/**
+ * Group: Backend Integration
+ */
+
+/**
+ * Variable: filename
+ *
+ * Contains the URL of the last opened file as a string.
+ * Default is null.
+ */
+mxEditor.prototype.filename = null;
+
+/**
+ * Variable: lineFeed
+ *
+ * Character to be used for encoding linefeeds in <save>. Default is '&#xa;'.
+ */
+mxEditor.prototype.linefeed = '&#xa;';
+
+/**
+ * Variable: postParameterName
+ *
+ * Specifies if the name of the post parameter that contains the diagram
+ * data in a post request to the server. Default is xml.
+ */
+mxEditor.prototype.postParameterName = 'xml';
+
+/**
+ * Variable: escapePostData
+ *
+ * Specifies if the data in the post request for saving a diagram
+ * should be converted using encodeURIComponent. Default is true.
+ */
+mxEditor.prototype.escapePostData = true;
+
+/**
+ * Variable: urlPost
+ *
+ * Specifies the URL to be used for posting the diagram
+ * to a backend in <save>.
+ */
+mxEditor.prototype.urlPost = null;
+
+/**
+ * Variable: urlImage
+ *
+ * Specifies the URL to be used for creating a bitmap of
+ * the graph in the image action.
+ */
+mxEditor.prototype.urlImage = null;
+
+/**
+ * Group: Autolayout
+ */
+
+/**
+ * Variable: horizontalFlow
+ *
+ * Specifies the direction of the flow
+ * in the diagram. This is used in the
+ * layout algorithms. Default is false,
+ * ie. vertical flow.
+ */
+mxEditor.prototype.horizontalFlow = false;
+
+/**
+ * Variable: layoutDiagram
+ *
+ * Specifies if the top-level elements in the
+ * diagram should be layed out using a vertical
+ * or horizontal stack depending on the setting
+ * of <horizontalFlow>. The spacing between the
+ * swimlanes is specified by <swimlaneSpacing>.
+ * Default is false.
+ * 
+ * If the top-level elements are swimlanes, then
+ * the intra-swimlane layout is activated by
+ * the <layoutSwimlanes> switch.
+ */
+mxEditor.prototype.layoutDiagram = false;
+
+/**
+ * Variable: swimlaneSpacing
+ *
+ * Specifies the spacing between swimlanes if
+ * automatic layout is turned on in
+ * <layoutDiagram>. Default is 0.
+ */
+mxEditor.prototype.swimlaneSpacing = 0;
+
+/**
+ * Variable: maintainSwimlanes
+ * 
+ * Specifies if the swimlanes should be kept at the same
+ * width or height depending on the setting of
+ * <horizontalFlow>.  Default is false.
+ * 
+ * For horizontal flows, all swimlanes
+ * have the same height and for vertical flows, all swimlanes
+ * have the same width. Furthermore, the swimlanes are
+ * automatically "stacked" if <layoutDiagram> is true.
+ */
+mxEditor.prototype.maintainSwimlanes = false;
+
+/**
+ * Variable: layoutSwimlanes
+ *
+ * Specifies if the children of swimlanes should
+ * be layed out, either vertically or horizontally
+ * depending on <horizontalFlow>.
+ * Default is false.
+ */
+mxEditor.prototype.layoutSwimlanes = false;
+
+/**
+ * Group: Attribute Cycling
+ */
+ 
+/**
+ * Variable: cycleAttributeValues
+ * 
+ * Specifies the attribute values to be cycled when
+ * inserting new swimlanes. Default is an empty
+ * array.
+ */
+mxEditor.prototype.cycleAttributeValues = null;
+
+/**
+ * Variable: cycleAttributeIndex
+ * 
+ * Index of the last consumed attribute index. If a new
+ * swimlane is inserted, then the <cycleAttributeValues>
+ * at this index will be used as the value for
+ * <cycleAttributeName>. Default is 0.
+ */
+mxEditor.prototype.cycleAttributeIndex = 0;
+
+/**
+ * Variable: cycleAttributeName
+ * 
+ * Name of the attribute to be assigned a <cycleAttributeValues>
+ * when inserting new swimlanes. Default is fillColor.
+ */
+mxEditor.prototype.cycleAttributeName = 'fillColor';
+
+/**
+ * Group: Windows
+ */
+
+/**
+ * Variable: tasks
+ * 
+ * Holds the <mxWindow> created in <showTasks>.
+ */
+mxEditor.prototype.tasks = null;
+
+/**
+ * Variable: tasksWindowImage
+ *
+ * Icon for the tasks window.
+ */
+mxEditor.prototype.tasksWindowImage = null;
+
+/**
+ * Variable: tasksTop
+ * 
+ * Specifies the top coordinate of the tasks window in pixels.
+ * Default is 20.
+ */
+mxEditor.prototype.tasksTop = 20;
+
+/**
+ * Variable: help
+ * 
+ * Holds the <mxWindow> created in <showHelp>.
+ */
+mxEditor.prototype.help = null;
+
+/**
+ * Variable: helpWindowImage
+ *
+ * Icon for the help window.
+ */
+mxEditor.prototype.helpWindowImage = null;
+
+/**
+ * Variable: urlHelp
+ *
+ * Specifies the URL to be used for the contents of the
+ * Online Help window. This is usually specified in the
+ * resources file under urlHelp for language-specific
+ * online help support.
+ */
+mxEditor.prototype.urlHelp = null;
+
+/**
+ * Variable: helpWidth
+ * 
+ * Specifies the width of the help window in pixels.
+ * Default is 300.
+ */
+mxEditor.prototype.helpWidth = 300;
+	
+/**
+ * Variable: helpWidth
+ * 
+ * Specifies the width of the help window in pixels.
+ * Default is 260.
+ */
+mxEditor.prototype.helpHeight = 260;
+
+/**
+ * Variable: propertiesWidth
+ * 
+ * Specifies the width of the properties window in pixels.
+ * Default is 240.
+ */
+mxEditor.prototype.propertiesWidth = 240;
+		
+/**
+ * Variable: propertiesHeight
+ * 
+ * Specifies the height of the properties window in pixels.
+ * If no height is specified then the window will be automatically
+ * sized to fit its contents. Default is null.
+ */
+mxEditor.prototype.propertiesHeight = null;
+		
+/**
+ * Variable: movePropertiesDialog
+ *
+ * Specifies if the properties dialog should be automatically
+ * moved near the cell it is displayed for, otherwise the
+ * dialog is not moved. This value is only taken into 
+ * account if the dialog is already visible. Default is false.
+ */
+mxEditor.prototype.movePropertiesDialog = false;
+
+/**
+ * Variable: validating
+ *
+ * Specifies if <mxGraph.validateGraph> should automatically be invoked after
+ * each change. Default is false.
+ */
+mxEditor.prototype.validating = false;
+
+/**
+ * Variable: modified
+ *
+ * True if the graph has been modified since it was last saved.
+ */
+mxEditor.prototype.modified = false;
+
+/**
+ * Function: isModified
+ * 
+ * Returns <modified>.
+ */
+mxEditor.prototype.isModified = function ()
+{
+	return this.modified;
+};
+
+/**
+ * Function: setModified
+ * 
+ * Sets <modified> to the specified boolean value.
+ */
+mxEditor.prototype.setModified = function (value)
+{
+	this.modified = value;
+};
+
+/**
+ * Function: addActions
+ *
+ * Adds the built-in actions to the editor instance.
+ *
+ * save - Saves the graph using <urlPost>.
+ * print - Shows the graph in a new print preview window.
+ * show - Shows the graph in a new window.
+ * exportImage - Shows the graph as a bitmap image using <getUrlImage>.
+ * refresh - Refreshes the graph's display.
+ * cut - Copies the current selection into the clipboard
+ * and removes it from the graph.
+ * copy - Copies the current selection into the clipboard.
+ * paste - Pastes the clipboard into the graph.
+ * delete - Removes the current selection from the graph.
+ * group - Puts the current selection into a new group.
+ * ungroup - Removes the selected groups and selects the children.
+ * undo - Undoes the last change on the graph model.
+ * redo - Redoes the last change on the graph model.
+ * zoom - Sets the zoom via a dialog.
+ * zoomIn - Zooms into the graph.
+ * zoomOut - Zooms out of the graph
+ * actualSize - Resets the scale and translation on the graph.
+ * fit - Changes the scale so that the graph fits into the window.
+ * showProperties - Shows the properties dialog.
+ * selectAll - Selects all cells.
+ * selectNone - Clears the selection.
+ * selectVertices - Selects all vertices.
+ * selectEdges = Selects all edges.
+ * edit - Starts editing the current selection cell.
+ * enterGroup - Drills down into the current selection cell.
+ * exitGroup - Moves up in the drilling hierachy
+ * home - Moves to the topmost parent in the drilling hierarchy
+ * selectPrevious - Selects the previous cell.
+ * selectNext - Selects the next cell.
+ * selectParent - Selects the parent of the selection cell.
+ * selectChild - Selects the first child of the selection cell.
+ * collapse - Collapses the currently selected cells.
+ * expand - Expands the currently selected cells.
+ * bold - Toggle bold text style.
+ * italic - Toggle italic text style.
+ * underline - Toggle underline text style.
+ * alignCellsLeft - Aligns the selection cells at the left.
+ * alignCellsCenter - Aligns the selection cells in the center.
+ * alignCellsRight - Aligns the selection cells at the right.
+ * alignCellsTop - Aligns the selection cells at the top.
+ * alignCellsMiddle - Aligns the selection cells in the middle.
+ * alignCellsBottom - Aligns the selection cells at the bottom.
+ * alignFontLeft - Sets the horizontal text alignment to left.
+ * alignFontCenter - Sets the horizontal text alignment to center.
+ * alignFontRight - Sets the horizontal text alignment to right.
+ * alignFontTop - Sets the vertical text alignment to top.
+ * alignFontMiddle - Sets the vertical text alignment to middle.
+ * alignFontBottom - Sets the vertical text alignment to bottom.
+ * toggleTasks - Shows or hides the tasks window.
+ * toggleHelp - Shows or hides the help window.
+ * toggleOutline - Shows or hides the outline window.
+ * toggleConsole - Shows or hides the console window.
+ */
+mxEditor.prototype.addActions = function ()
+{
+	this.addAction('save', function(editor)
+	{
+		editor.save();
+	});
+	
+	this.addAction('print', function(editor)
+	{
+		var preview = new mxPrintPreview(editor.graph, 1);
+		preview.open();
+	});
+	
+	this.addAction('show', function(editor)
+	{
+		mxUtils.show(editor.graph, null, 10, 10);
+	});
+
+	this.addAction('exportImage', function(editor)
+	{
+		var url = editor.getUrlImage();
+		
+		if (url == null || mxClient.IS_LOCAL)
+		{
+			editor.execute('show');
+		}
+		else
+		{
+			var node = mxUtils.getViewXml(editor.graph, 1);
+			var xml = mxUtils.getXml(node, '\n');
+
+			mxUtils.submit(url, editor.postParameterName + '=' +
+				encodeURIComponent(xml), document, '_blank');
+		}
+	});
+	
+	this.addAction('refresh', function(editor)
+	{
+		editor.graph.refresh();
+	});
+	
+	this.addAction('cut', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			mxClipboard.cut(editor.graph);
+		}
+	});
+	
+	this.addAction('copy', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			mxClipboard.copy(editor.graph);
+		}
+	});
+	
+	this.addAction('paste', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			mxClipboard.paste(editor.graph);
+		}
+	});
+	
+	this.addAction('delete', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.removeCells();
+		}
+	});
+	
+	this.addAction('group', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.setSelectionCell(editor.groupCells());
+		}
+	});
+	
+	this.addAction('ungroup', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.setSelectionCells(editor.graph.ungroupCells());
+		}
+	});
+	
+	this.addAction('removeFromParent', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.removeCellsFromParent();
+		}
+	});
+	
+	this.addAction('undo', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.undo();
+		}
+	});
+	
+	this.addAction('redo', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.redo();
+		}
+	});
+	
+	this.addAction('zoomIn', function(editor)
+	{
+		editor.graph.zoomIn();
+	});
+	
+	this.addAction('zoomOut', function(editor)
+	{
+		editor.graph.zoomOut();
+	});
+	
+	this.addAction('actualSize', function(editor)
+	{
+		editor.graph.zoomActual();
+	});
+	
+	this.addAction('fit', function(editor)
+	{
+		editor.graph.fit();
+	});
+	
+	this.addAction('showProperties', function(editor, cell)
+	{
+		editor.showProperties(cell);
+	});
+	
+	this.addAction('selectAll', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.selectAll();
+		}
+	});
+	
+	this.addAction('selectNone', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.clearSelection();
+		}
+	});
+	
+	this.addAction('selectVertices', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.selectVertices();
+		}
+	});
+	
+	this.addAction('selectEdges', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.selectEdges();
+		}
+	});
+	
+	this.addAction('edit', function(editor, cell)
+	{
+		if (editor.graph.isEnabled() &&
+			editor.graph.isCellEditable(cell))
+		{
+			editor.graph.startEditingAtCell(cell);
+		}
+	});
+	
+	this.addAction('toBack', function(editor, cell)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.orderCells(true);
+		}
+	});
+	
+	this.addAction('toFront', function(editor, cell)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.orderCells(false);
+		}
+	});
+	
+	this.addAction('enterGroup', function(editor, cell)
+	{
+		editor.graph.enterGroup(cell);
+	});
+	
+	this.addAction('exitGroup', function(editor)
+	{
+		editor.graph.exitGroup();
+	});
+	
+	this.addAction('home', function(editor)
+	{
+		editor.graph.home();
+	});
+	
+	this.addAction('selectPrevious', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.selectPreviousCell();
+		}
+	});
+	
+	this.addAction('selectNext', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.selectNextCell();
+		}
+	});
+	
+	this.addAction('selectParent', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.selectParentCell();
+		}
+	});
+	
+	this.addAction('selectChild', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.selectChildCell();
+		}
+	});
+	
+	this.addAction('collapse', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.foldCells(true);
+		}
+	});
+	
+	this.addAction('collapseAll', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			var cells = editor.graph.getChildVertices();
+			editor.graph.foldCells(true, false, cells);
+		}
+	});
+	
+	this.addAction('expand', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.foldCells(false);
+		}
+	});
+	
+	this.addAction('expandAll', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			var cells = editor.graph.getChildVertices();
+			editor.graph.foldCells(false, false, cells);
+		}
+	});
+	
+	this.addAction('bold', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.toggleCellStyleFlags(
+				mxConstants.STYLE_FONTSTYLE,
+				mxConstants.FONT_BOLD);
+		}
+	});
+	
+	this.addAction('italic', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.toggleCellStyleFlags(
+				mxConstants.STYLE_FONTSTYLE,
+				mxConstants.FONT_ITALIC);
+		}
+	});
+	
+	this.addAction('underline', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.toggleCellStyleFlags(
+				mxConstants.STYLE_FONTSTYLE,
+				mxConstants.FONT_UNDERLINE);
+		}
+	});
+
+	this.addAction('alignCellsLeft', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.alignCells(mxConstants.ALIGN_LEFT);
+		}
+	});
+	
+	this.addAction('alignCellsCenter', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.alignCells(mxConstants.ALIGN_CENTER);
+		}
+	});
+	
+	this.addAction('alignCellsRight', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.alignCells(mxConstants.ALIGN_RIGHT);
+		}
+	});
+	
+	this.addAction('alignCellsTop', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.alignCells(mxConstants.ALIGN_TOP);
+		}
+	});
+	
+	this.addAction('alignCellsMiddle', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.alignCells(mxConstants.ALIGN_MIDDLE);
+		}
+	});
+	
+	this.addAction('alignCellsBottom', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.alignCells(mxConstants.ALIGN_BOTTOM);
+		}
+	});
+	
+	this.addAction('alignFontLeft', function(editor)
+	{
+		
+		editor.graph.setCellStyles(
+			mxConstants.STYLE_ALIGN,
+			mxConstants.ALIGN_LEFT);
+	});
+	
+	this.addAction('alignFontCenter', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.setCellStyles(
+				mxConstants.STYLE_ALIGN,
+				mxConstants.ALIGN_CENTER);
+		}
+	});
+	
+	this.addAction('alignFontRight', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.setCellStyles(
+				mxConstants.STYLE_ALIGN,
+				mxConstants.ALIGN_RIGHT);
+		}
+	});
+	
+	this.addAction('alignFontTop', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.setCellStyles(
+				mxConstants.STYLE_VERTICAL_ALIGN,
+				mxConstants.ALIGN_TOP);
+		}
+	});
+	
+	this.addAction('alignFontMiddle', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.setCellStyles(
+				mxConstants.STYLE_VERTICAL_ALIGN,
+				mxConstants.ALIGN_MIDDLE);
+		}
+	});
+	
+	this.addAction('alignFontBottom', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.setCellStyles(
+				mxConstants.STYLE_VERTICAL_ALIGN,
+				mxConstants.ALIGN_BOTTOM);
+		}
+	});
+	
+	this.addAction('zoom', function(editor)
+	{
+		var current = editor.graph.getView().scale*100;
+		var scale = parseFloat(mxUtils.prompt(
+			mxResources.get(editor.askZoomResource) ||
+			editor.askZoomResource,
+			current))/100;
+
+		if (!isNaN(scale))
+		{
+			editor.graph.getView().setScale(scale);
+		}
+	});
+	
+	this.addAction('toggleTasks', function(editor)
+	{
+		if (editor.tasks != null)
+		{
+			editor.tasks.setVisible(!editor.tasks.isVisible());
+		}
+		else
+		{
+			editor.showTasks();
+		}
+	});
+	
+	this.addAction('toggleHelp', function(editor)
+	{
+		if (editor.help != null)
+		{
+			editor.help.setVisible(!editor.help.isVisible());
+		}
+		else
+		{
+			editor.showHelp();
+		}
+	});
+	
+	this.addAction('toggleOutline', function(editor)
+	{
+		if (editor.outline == null)
+		{
+			editor.showOutline();
+		}
+		else
+		{
+			editor.outline.setVisible(!editor.outline.isVisible());
+		}
+	});
+	
+	this.addAction('toggleConsole', function(editor)
+	{
+		mxLog.setVisible(!mxLog.isVisible());
+	});
+};
+
+/**
+ * Function: configure
+ *
+ * Configures the editor using the specified node. To load the
+ * configuration from a given URL the following code can be used to obtain
+ * the XML node.
+ * 
+ * (code)
+ * var node = mxUtils.load(url).getDocumentElement();
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * node - XML node that contains the configuration.
+ */
+mxEditor.prototype.configure = function (node)
+{
+	if (node != null)
+	{
+		// Creates a decoder for the XML data
+		// and uses it to configure the editor
+		var dec = new mxCodec(node.ownerDocument);
+		dec.decode(node, this);
+		
+		// Resets the counters, modified state and
+		// command history
+		this.resetHistory();
+	}
+};
+
+/**
+ * Function: resetFirstTime
+ * 
+ * Resets the cookie that is used to remember if the editor has already
+ * been used.
+ */
+mxEditor.prototype.resetFirstTime = function ()
+{
+	document.cookie =
+		'mxgraph=seen; expires=Fri, 27 Jul 2001 02:47:11 UTC; path=/';
+};
+
+/**
+ * Function: resetHistory
+ * 
+ * Resets the command history, modified state and counters.
+ */
+mxEditor.prototype.resetHistory = function ()
+{
+	this.lastSnapshot = new Date().getTime();
+	this.undoManager.clear();
+	this.ignoredChanges = 0;
+	this.setModified(false);
+};
+
+/**
+ * Function: addAction
+ * 
+ * Binds the specified actionname to the specified function.
+ * 
+ * Parameters:
+ * 
+ * actionname - String that specifies the name of the action
+ * to be added.
+ * funct - Function that implements the new action. The first
+ * argument of the function is the editor it is used
+ * with, the second argument is the cell it operates
+ * upon.
+ * 
+ * Example:
+ * (code)
+ * editor.addAction('test', function(editor, cell)
+ * {
+ * 		mxUtils.alert("test "+cell);
+ * });
+ * (end)
+ */
+mxEditor.prototype.addAction = function (actionname, funct)
+{
+	this.actions[actionname] = funct;
+};
+
+/**
+ * Function: execute
+ * 
+ * Executes the function with the given name in <actions> passing the
+ * editor instance and given cell as the first and second argument. All
+ * additional arguments are passed to the action as well. This method
+ * contains a try-catch block and displays an error message if an action
+ * causes an exception. The exception is re-thrown after the error
+ * message was displayed.
+ * 
+ * Example:
+ * 
+ * (code)
+ * editor.execute("showProperties", cell);
+ * (end)
+ */
+mxEditor.prototype.execute = function (actionname, cell, evt)
+{
+	var action = this.actions[actionname];
+	
+	if (action != null)
+	{
+		try
+		{
+			// Creates the array of arguments by replacing the actionname
+			// with the editor instance in the args of this function
+			var args = arguments;
+			args[0] = this;
+			
+			// Invokes the function on the editor using the args
+			action.apply(this, args);
+		}
+		catch (e)
+		{
+			mxUtils.error('Cannot execute ' + actionname +
+				': ' + e.message, 280, true);
+			
+			throw e;
+		}
+	}
+	else
+	{
+		mxUtils.error('Cannot find action '+actionname, 280, true);
+	}
+};
+
+/**
+ * Function: addTemplate
+ * 
+ * Adds the specified template under the given name in <templates>.
+ */
+mxEditor.prototype.addTemplate = function (name, template)
+{
+	this.templates[name] = template;
+};
+
+/**
+ * Function: getTemplate
+ * 
+ * Returns the template for the given name.
+ */
+mxEditor.prototype.getTemplate = function (name)
+{
+	return this.templates[name];
+};
+
+/**
+ * Function: createGraph
+ * 
+ * Creates the <graph> for the editor. The graph is created with no
+ * container and is initialized from <setGraphContainer>.
+ */
+mxEditor.prototype.createGraph = function ()
+{
+	var graph = new mxGraph(null, null, this.graphRenderHint);
+	
+	// Enables rubberband, tooltips, panning
+	graph.setTooltips(true);
+	graph.setPanning(true);
+
+	// Overrides the dblclick method on the graph to
+	// invoke the dblClickAction for a cell and reset
+	// the selection tool in the toolbar
+	this.installDblClickHandler(graph);
+	
+	// Installs the command history
+	this.installUndoHandler(graph);
+
+	// Installs the handlers for the root event
+	this.installDrillHandler(graph);
+	
+	// Installs the handler for validation
+	this.installChangeHandler(graph);
+
+	// Installs the handler for calling the
+	// insert function and consume the
+	// event if an insert function is defined
+	this.installInsertHandler(graph);
+
+	// Redirects the function for creating the
+	// popupmenu items
+	graph.popupMenuHandler.factoryMethod =
+		mxUtils.bind(this, function(menu, cell, evt)
+		{
+			return this.createPopupMenu(menu, cell, evt);
+		});
+
+	// Redirects the function for creating
+	// new connections in the diagram
+	graph.connectionHandler.factoryMethod =
+		mxUtils.bind(this, function(source, target)
+		{
+			return this.createEdge(source, target);
+		});
+	
+	// Maintains swimlanes and installs autolayout
+	this.createSwimlaneManager(graph);
+	this.createLayoutManager(graph);
+	
+	return graph;
+};
+
+/**
+ * Function: createSwimlaneManager
+ * 
+ * Sets the graph's container using <mxGraph.init>.
+ */
+mxEditor.prototype.createSwimlaneManager = function (graph)
+{
+	var swimlaneMgr = new mxSwimlaneManager(graph, false);
+
+	swimlaneMgr.isHorizontal = mxUtils.bind(this, function()
+	{
+		return this.horizontalFlow;
+	});
+	
+	swimlaneMgr.isEnabled = mxUtils.bind(this, function()
+	{
+		return this.maintainSwimlanes;
+	});
+	
+	return swimlaneMgr;
+};
+
+/**
+ * Function: createLayoutManager
+ * 
+ * Creates a layout manager for the swimlane and diagram layouts, that
+ * is, the locally defined inter- and intraswimlane layouts.
+ */
+mxEditor.prototype.createLayoutManager = function (graph)
+{
+	var layoutMgr = new mxLayoutManager(graph);
+	
+	var self = this; // closure
+	layoutMgr.getLayout = function(cell)
+	{
+		var layout = null;
+		var model = self.graph.getModel();
+		
+		if (model.getParent(cell) != null)
+		{
+			// Executes the swimlane layout if a child of
+			// a swimlane has been changed. The layout is
+			// lazy created in createSwimlaneLayout.
+			if (self.layoutSwimlanes &&
+				graph.isSwimlane(cell))
+			{
+				if (self.swimlaneLayout == null)
+				{
+					self.swimlaneLayout = self.createSwimlaneLayout();
+				}
+				
+				layout = self.swimlaneLayout;
+			}
+			
+			// Executes the diagram layout if the modified
+			// cell is a top-level cell. The layout is
+			// lazy created in createDiagramLayout.
+			else if (self.layoutDiagram &&
+				(graph.isValidRoot(cell) ||
+				model.getParent(model.getParent(cell)) == null))
+			{
+				if (self.diagramLayout == null)
+				{
+					self.diagramLayout = self.createDiagramLayout();
+				}
+				
+				layout = self.diagramLayout;
+			}
+		}
+			
+		return layout;
+	};
+	
+	return layoutMgr;
+};
+
+/**
+ * Function: setGraphContainer
+ * 
+ * Sets the graph's container using <mxGraph.init>.
+ */
+mxEditor.prototype.setGraphContainer = function (container)
+{
+	if (this.graph.container == null)
+	{
+		// Creates the graph instance inside the given container and render hint
+		//this.graph = new mxGraph(container, null, this.graphRenderHint);
+		this.graph.init(container);
+
+		// Install rubberband selection as the last
+		// action handler in the chain
+		this.rubberband = new mxRubberband(this.graph);
+
+		// Disables the context menu
+		if (this.disableContextMenu)
+		{
+			mxEvent.disableContextMenu(container);
+		}
+
+		// Workaround for stylesheet directives in IE
+		if (mxClient.IS_QUIRKS)
+		{
+			new mxDivResizer(container);
+		}
+	}
+};
+
+/**
+ * Function: installDblClickHandler
+ * 
+ * Overrides <mxGraph.dblClick> to invoke <dblClickAction>
+ * on a cell and reset the selection tool in the toolbar.
+ */
+mxEditor.prototype.installDblClickHandler = function (graph)
+{
+	// Installs a listener for double click events
+	graph.addListener(mxEvent.DOUBLE_CLICK,
+		mxUtils.bind(this, function(sender, evt)
+		{
+			var cell = evt.getProperty('cell');
+			
+			if (cell != null &&
+				graph.isEnabled() &&
+				this.dblClickAction != null)
+			{
+				this.execute(this.dblClickAction, cell);
+				evt.consume();
+			}
+		})
+	);
+};
+		
+/**
+ * Function: installUndoHandler
+ * 
+ * Adds the <undoManager> to the graph model and the view.
+ */
+mxEditor.prototype.installUndoHandler = function (graph)
+{				
+	var listener = mxUtils.bind(this, function(sender, evt)
+	{
+		var edit = evt.getProperty('edit');
+		this.undoManager.undoableEditHappened(edit);
+	});
+	
+	graph.getModel().addListener(mxEvent.UNDO, listener);
+	graph.getView().addListener(mxEvent.UNDO, listener);
+
+	// Keeps the selection state in sync
+	var undoHandler = function(sender, evt)
+	{
+		var changes = evt.getProperty('edit').changes;
+		graph.setSelectionCells(graph.getSelectionCellsForChanges(changes));
+	};
+	
+	this.undoManager.addListener(mxEvent.UNDO, undoHandler);
+	this.undoManager.addListener(mxEvent.REDO, undoHandler);
+};
+		
+/**
+ * Function: installDrillHandler
+ * 
+ * Installs listeners for dispatching the <root> event.
+ */
+mxEditor.prototype.installDrillHandler = function (graph)
+{				
+	var listener = mxUtils.bind(this, function(sender)
+	{
+		this.fireEvent(new mxEventObject(mxEvent.ROOT));
+	});
+	
+	graph.getView().addListener(mxEvent.DOWN, listener);
+	graph.getView().addListener(mxEvent.UP, listener);
+};
+
+/**
+ * Function: installChangeHandler
+ * 
+ * Installs the listeners required to automatically validate
+ * the graph. On each change of the root, this implementation
+ * fires a <root> event.
+ */
+mxEditor.prototype.installChangeHandler = function (graph)
+{
+	var listener = mxUtils.bind(this, function(sender, evt)
+	{
+		// Updates the modified state
+		this.setModified(true);
+
+		// Automatically validates the graph
+		// after each change
+		if (this.validating == true)
+		{
+			graph.validateGraph();
+		}
+
+		// Checks if the root has been changed
+		var changes = evt.getProperty('edit').changes;
+		
+		for (var i = 0; i < changes.length; i++)
+		{
+			var change = changes[i];
+			
+			if (change instanceof mxRootChange ||
+				(change instanceof mxValueChange &&
+				change.cell == this.graph.model.root) ||
+				(change instanceof mxCellAttributeChange &&
+				change.cell == this.graph.model.root))
+			{
+				this.fireEvent(new mxEventObject(mxEvent.ROOT));
+				break;
+			}
+		}
+	});
+	
+	graph.getModel().addListener(mxEvent.CHANGE, listener);
+};
+
+/**
+ * Function: installInsertHandler
+ * 
+ * Installs the handler for invoking <insertFunction> if
+ * one is defined.
+ */
+mxEditor.prototype.installInsertHandler = function (graph)
+{
+	var self = this; // closure
+	var insertHandler =
+	{
+		mouseDown: function(sender, me)
+		{
+			if (self.insertFunction != null &&
+				!me.isPopupTrigger() &&
+				(self.forcedInserting ||
+				me.getState() == null))
+			{
+				self.graph.clearSelection();
+				self.insertFunction(me.getEvent(), me.getCell());
+
+				// Consumes the rest of the events
+				// for this gesture (down, move, up)
+				this.isActive = true;
+				me.consume();
+			}
+		},
+		
+		mouseMove: function(sender, me)
+		{
+			if (this.isActive)
+			{
+				me.consume();
+			}
+		},
+		
+		mouseUp: function(sender, me)
+		{
+			if (this.isActive)
+			{
+				this.isActive = false;
+				me.consume();
+			}
+		}
+	};
+	
+	graph.addMouseListener(insertHandler);
+};
+
+/**
+ * Function: createDiagramLayout
+ * 
+ * Creates the layout instance used to layout the
+ * swimlanes in the diagram.
+ */
+mxEditor.prototype.createDiagramLayout = function ()
+{
+	var gs = this.graph.gridSize;
+	var layout = new mxStackLayout(this.graph, !this.horizontalFlow,
+		 this.swimlaneSpacing, 2*gs, 2*gs);
+	
+	// Overrides isIgnored to only take into account swimlanes
+	layout.isVertexIgnored = function(cell)
+	{
+		return !layout.graph.isSwimlane(cell);
+	};
+	
+	return layout;
+};
+
+/**
+ * Function: createSwimlaneLayout
+ * 
+ * Creates the layout instance used to layout the
+ * children of each swimlane.
+ */
+mxEditor.prototype.createSwimlaneLayout = function ()
+{
+	return new mxCompactTreeLayout(this.graph, this.horizontalFlow);
+};
+
+/**
+ * Function: createToolbar
+ * 
+ * Creates the <toolbar> with no container.
+ */
+mxEditor.prototype.createToolbar = function ()
+{
+	return new mxDefaultToolbar(null, this);
+};
+
+/**
+ * Function: setToolbarContainer
+ * 
+ * Initializes the toolbar for the given container.
+ */
+mxEditor.prototype.setToolbarContainer = function (container)
+{
+	this.toolbar.init(container);
+	
+	// Workaround for stylesheet directives in IE
+	if (mxClient.IS_QUIRKS)
+	{
+		new mxDivResizer(container);
+	}
+};
+
+/**
+ * Function: setStatusContainer
+ * 
+ * Creates the <status> using the specified container.
+ * 
+ * This implementation adds listeners in the editor to 
+ * display the last saved time and the current filename 
+ * in the status bar.
+ * 
+ * Parameters:
+ * 
+ * container - DOM node that will contain the statusbar.
+ */
+mxEditor.prototype.setStatusContainer = function (container)
+{
+	if (this.status == null)
+	{
+		this.status = container;
+		
+		// Prints the last saved time in the status bar
+		// when files are saved
+		this.addListener(mxEvent.SAVE, mxUtils.bind(this, function()
+		{
+			var tstamp = new Date().toLocaleString();
+			this.setStatus((mxResources.get(this.lastSavedResource) ||
+				this.lastSavedResource)+': '+tstamp);
+		}));
+		
+		// Updates the statusbar to display the filename
+		// when new files are opened
+		this.addListener(mxEvent.OPEN, mxUtils.bind(this, function()
+		{
+			this.setStatus((mxResources.get(this.currentFileResource) ||
+				this.currentFileResource)+': '+this.filename);
+		}));
+		
+		// Workaround for stylesheet directives in IE
+		if (mxClient.IS_QUIRKS)
+		{
+			new mxDivResizer(container);
+		}
+	}
+};
+
+/**
+ * Function: setStatus
+ * 
+ * Display the specified message in the status bar.
+ * 
+ * Parameters:
+ * 
+ * message - String the specified the message to
+ * be displayed.
+ */
+mxEditor.prototype.setStatus = function (message)
+{
+	if (this.status != null && message != null)
+	{
+		this.status.innerHTML = message;
+	}
+};
+
+/**
+ * Function: setTitleContainer
+ * 
+ * Creates a listener to update the inner HTML of the
+ * specified DOM node with the value of <getTitle>.
+ * 
+ * Parameters:
+ * 
+ * container - DOM node that will contain the title.
+ */
+mxEditor.prototype.setTitleContainer = function (container)
+{
+	this.addListener(mxEvent.ROOT, mxUtils.bind(this, function(sender)
+	{
+		container.innerHTML = this.getTitle();
+	}));
+
+	// Workaround for stylesheet directives in IE
+	if (mxClient.IS_QUIRKS)
+	{
+		new mxDivResizer(container);
+	}
+};
+
+/**
+ * Function: treeLayout
+ * 
+ * Executes a vertical or horizontal compact tree layout
+ * using the specified cell as an argument. The cell may
+ * either be a group or the root of a tree.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to use in the compact tree layout.
+ * horizontal - Optional boolean to specify the tree's
+ * orientation. Default is true.
+ */
+mxEditor.prototype.treeLayout = function (cell, horizontal)
+{
+	if (cell != null)
+	{
+		var layout = new mxCompactTreeLayout(this.graph, horizontal);
+		layout.execute(cell);
+	}
+};
+
+/**
+ * Function: getTitle
+ * 
+ * Returns the string value for the current root of the
+ * diagram.
+ */
+mxEditor.prototype.getTitle = function ()
+{
+	var title = '';
+	var graph = this.graph;
+	var cell = graph.getCurrentRoot();
+	
+	while (cell != null &&
+		   graph.getModel().getParent(
+				graph.getModel().getParent(cell)) != null)
+	{
+		// Append each label of a valid root
+		if (graph.isValidRoot(cell))
+		{
+			title = ' > ' +
+			graph.convertValueToString(cell) + title;
+		}
+		
+		cell = graph.getModel().getParent(cell);
+	}
+	
+	var prefix = this.getRootTitle();
+	
+	return prefix + title;
+};
+
+/**
+ * Function: getRootTitle
+ * 
+ * Returns the string value of the root cell in
+ * <mxGraph.model>.
+ */
+mxEditor.prototype.getRootTitle = function ()
+{
+	var root = this.graph.getModel().getRoot();
+	return this.graph.convertValueToString(root);
+};
+
+/**
+ * Function: undo
+ * 
+ * Undo the last change in <graph>.
+ */
+mxEditor.prototype.undo = function ()
+{
+	this.undoManager.undo();
+};
+
+/**
+ * Function: redo
+ * 
+ * Redo the last change in <graph>.
+ */
+mxEditor.prototype.redo = function ()
+{
+	this.undoManager.redo();
+};
+
+/**
+ * Function: groupCells
+ * 
+ * Invokes <createGroup> to create a new group cell and the invokes
+ * <mxGraph.groupCells>, using the grid size of the graph as the spacing
+ * in the group's content area.
+ */
+mxEditor.prototype.groupCells = function ()
+{
+	var border = (this.groupBorderSize != null) ?
+		this.groupBorderSize :
+		this.graph.gridSize;
+	return this.graph.groupCells(this.createGroup(), border);
+};
+
+/**
+ * Function: createGroup
+ * 
+ * Creates and returns a clone of <defaultGroup> to be used
+ * as a new group cell in <group>.
+ */
+mxEditor.prototype.createGroup = function ()
+{
+	var model = this.graph.getModel();
+	
+	return model.cloneCell(this.defaultGroup);
+};
+
+/**
+ * Function: open
+ * 
+ * Opens the specified file synchronously and parses it using
+ * <readGraphModel>. It updates <filename> and fires an <open>-event after
+ * the file has been opened. Exceptions should be handled as follows:
+ * 
+ * (code)
+ * try
+ * {
+ *   editor.open(filename);
+ * }
+ * catch (e)
+ * {
+ *   mxUtils.error('Cannot open ' + filename +
+ *     ': ' + e.message, 280, true);
+ * }
+ * (end)
+ *
+ * Parameters:
+ * 
+ * filename - URL of the file to be opened.
+ */
+mxEditor.prototype.open = function (filename)
+{
+	if (filename != null)
+	{
+		var xml = mxUtils.load(filename).getXml();
+		this.readGraphModel(xml.documentElement);
+		this.filename = filename;
+		
+		this.fireEvent(new mxEventObject(mxEvent.OPEN, 'filename', filename));
+	}
+};
+
+/**
+ * Function: readGraphModel
+ * 
+ * Reads the specified XML node into the existing graph model and resets
+ * the command history and modified state.
+ */
+mxEditor.prototype.readGraphModel = function (node)
+{
+	var dec = new mxCodec(node.ownerDocument);
+	dec.decode(node, this.graph.getModel());
+	this.resetHistory();
+};
+
+/**
+ * Function: save
+ * 
+ * Posts the string returned by <writeGraphModel> to the given URL or the
+ * URL returned by <getUrlPost>. The actual posting is carried out by
+ * <postDiagram>. If the URL is null then the resulting XML will be
+ * displayed using <mxUtils.popup>. Exceptions should be handled as
+ * follows:
+ * 
+ * (code)
+ * try
+ * {
+ *   editor.save();
+ * }
+ * catch (e)
+ * {
+ *   mxUtils.error('Cannot save : ' + e.message, 280, true);
+ * }
+ * (end)
+ */
+mxEditor.prototype.save = function (url, linefeed)
+{
+	// Gets the URL to post the data to
+	url = url || this.getUrlPost();
+
+	// Posts the data if the URL is not empty
+	if (url != null && url.length > 0)
+	{
+		var data = this.writeGraphModel(linefeed);
+		this.postDiagram(url, data);
+		
+		// Resets the modified flag
+		this.setModified(false);
+	}
+	
+	// Dispatches a save event
+	this.fireEvent(new mxEventObject(mxEvent.SAVE, 'url', url));
+};
+
+/**
+ * Function: postDiagram
+ * 
+ * Hook for subclassers to override the posting of a diagram
+ * represented by the given node to the given URL. This fires
+ * an asynchronous <post> event if the diagram has been posted.
+ * 
+ * Example:
+ * 
+ * To replace the diagram with the diagram in the response, use the
+ * following code.
+ * 
+ * (code)
+ * editor.addListener(mxEvent.POST, function(sender, evt)
+ * {
+ *   // Process response (replace diagram)
+ *   var req = evt.getProperty('request');
+ *   var root = req.getDocumentElement();
+ *   editor.graph.readGraphModel(root)
+ * });
+ * (end)
+ */
+mxEditor.prototype.postDiagram = function (url, data)
+{
+	if (this.escapePostData)
+	{
+		data = encodeURIComponent(data);
+	}
+
+	mxUtils.post(url, this.postParameterName+'='+data,
+		mxUtils.bind(this, function(req)
+		{
+			this.fireEvent(new mxEventObject(mxEvent.POST,
+				'request', req, 'url', url, 'data', data));
+		})
+	);
+};
+
+/**
+ * Function: writeGraphModel
+ * 
+ * Hook to create the string representation of the diagram. The default
+ * implementation uses an <mxCodec> to encode the graph model as
+ * follows:
+ * 
+ * (code)
+ * var enc = new mxCodec();
+ * var node = enc.encode(this.graph.getModel());
+ * return mxUtils.getXml(node, this.linefeed);
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * linefeed - Optional character to be used as the linefeed. Default is
+ * <linefeed>.
+ */
+mxEditor.prototype.writeGraphModel = function (linefeed)
+{
+	linefeed = (linefeed != null) ? linefeed : this.linefeed;
+	var enc = new mxCodec();
+	var node = enc.encode(this.graph.getModel());
+
+	return mxUtils.getXml(node, linefeed);
+};
+
+/**
+ * Function: getUrlPost
+ * 
+ * Returns the URL to post the diagram to. This is used
+ * in <save>. The default implementation returns <urlPost>,
+ * adding <code>?draft=true</code>.
+ */
+mxEditor.prototype.getUrlPost = function ()
+{
+	return this.urlPost;
+};
+
+/**
+ * Function: getUrlImage
+ * 
+ * Returns the URL to create the image with. This is typically
+ * the URL of a backend which accepts an XML representation
+ * of a graph view to create an image. The function is used
+ * in the image action to create an image. This implementation
+ * returns <urlImage>.
+ */
+mxEditor.prototype.getUrlImage = function ()
+{
+	return this.urlImage;
+};
+
+/**
+ * Function: swapStyles
+ * 
+ * Swaps the styles for the given names in the graph's
+ * stylesheet and refreshes the graph.
+ */
+mxEditor.prototype.swapStyles = function (first, second)
+{
+	var style = this.graph.getStylesheet().styles[second];
+	this.graph.getView().getStylesheet().putCellStyle(
+		second, this.graph.getStylesheet().styles[first]);
+	this.graph.getStylesheet().putCellStyle(first, style);
+	this.graph.refresh();
+};
+
+/**
+ * Function: showProperties
+ * 
+ * Creates and shows the properties dialog for the given
+ * cell. The content area of the dialog is created using
+ * <createProperties>.
+ */
+mxEditor.prototype.showProperties = function (cell)
+{
+	cell = cell || this.graph.getSelectionCell();
+	
+	// Uses the root node for the properties dialog
+	// if not cell was passed in and no cell is
+	// selected
+	if (cell == null)
+	{
+		cell = this.graph.getCurrentRoot();
+		
+		if (cell == null)
+		{
+			cell = this.graph.getModel().getRoot();
+		}
+	}
+	
+	if (cell != null)
+	{
+		// Makes sure there is no in-place editor in the
+		// graph and computes the location of the dialog
+		this.graph.stopEditing(true);
+
+		var offset = mxUtils.getOffset(this.graph.container);
+		var x = offset.x+10;
+		var y = offset.y;
+		
+		// Avoids moving the dialog if it is alredy open
+		if (this.properties != null && !this.movePropertiesDialog)
+		{
+			x = this.properties.getX();
+			y = this.properties.getY();
+		}
+		
+		// Places the dialog near the cell for which it
+		// displays the properties
+		else
+		{
+			var bounds = this.graph.getCellBounds(cell);
+			
+			if (bounds != null)
+			{
+				x += bounds.x+Math.min(200, bounds.width);
+				y += bounds.y;				
+			}			
+		}
+		
+		// Hides the existing properties dialog and creates a new one with the
+		// contents created in the hook method
+		this.hideProperties();
+		var node = this.createProperties(cell);
+		
+		if (node != null)
+		{
+			// Displays the contents in a window and stores a reference to the
+			// window for later hiding of the window
+			this.properties = new mxWindow(mxResources.get(this.propertiesResource) ||
+				this.propertiesResource, node, x, y, this.propertiesWidth, this.propertiesHeight, false);
+			this.properties.setVisible(true);
+		}
+	}
+};
+
+/**
+ * Function: isPropertiesVisible
+ * 
+ * Returns true if the properties dialog is currently visible.
+ */
+mxEditor.prototype.isPropertiesVisible = function ()
+{
+	return this.properties != null;
+};
+
+/**
+ * Function: createProperties
+ * 
+ * Creates and returns the DOM node that represents the contents
+ * of the properties dialog for the given cell. This implementation
+ * works for user objects that are XML nodes and display all the
+ * node attributes in a form.
+ */
+mxEditor.prototype.createProperties = function (cell)
+{
+	var model = this.graph.getModel();
+	var value = model.getValue(cell);
+	
+	if (mxUtils.isNode(value))
+	{
+		// Creates a form for the user object inside
+		// the cell
+		var form = new mxForm('properties');
+		
+		// Adds a readonly field for the cell id
+		var id = form.addText('ID', cell.getId());
+		id.setAttribute('readonly', 'true');
+
+		var geo = null;
+		var yField = null;
+		var xField = null;
+		var widthField = null;
+		var heightField = null;
+
+		// Adds fields for the location and size
+		if (model.isVertex(cell))
+		{
+			geo = model.getGeometry(cell);
+			
+			if (geo != null)
+			{
+				yField = form.addText('top', geo.y);
+				xField = form.addText('left', geo.x);
+				widthField = form.addText('width', geo.width);
+				heightField = form.addText('height', geo.height);
+			}
+		}
+		
+		// Adds a field for the cell style			
+		var tmp = model.getStyle(cell);
+		var style = form.addText('Style', tmp || '');
+		
+		// Creates textareas for each attribute of the
+		// user object within the cell
+		var attrs = value.attributes;
+		var texts = [];
+		
+		for (var i = 0; i < attrs.length; i++)
+		{
+			// Creates a textarea with more lines for
+			// the cell label
+			var val = attrs[i].value;
+			texts[i] = form.addTextarea(attrs[i].nodeName, val,
+				(attrs[i].nodeName == 'label') ? 4 : 2);
+		}
+		
+		// Adds an OK and Cancel button to the dialog
+		// contents and implements the respective
+		// actions below
+		
+		// Defines the function to be executed when the
+		// OK button is pressed in the dialog
+		var okFunction = mxUtils.bind(this, function()
+		{
+			// Hides the dialog
+			this.hideProperties();
+			
+			// Supports undo for the changes on the underlying
+			// XML structure / XML node attribute changes.
+			model.beginUpdate();
+			try
+			{
+				if (geo != null)
+				{
+					geo = geo.clone();
+					
+					geo.x = parseFloat(xField.value);
+					geo.y = parseFloat(yField.value);
+					geo.width = parseFloat(widthField.value);
+					geo.height = parseFloat(heightField.value);
+					
+					model.setGeometry(cell, geo);
+				}
+				
+				// Applies the style
+				if (style.value.length > 0)
+				{
+					model.setStyle(cell, style.value);
+				}
+				else
+				{
+					model.setStyle(cell, null);
+				}
+				
+				// Creates an undoable change for each
+				// attribute and executes it using the
+				// model, which will also make the change
+				// part of the current transaction
+				for (var i=0; i<attrs.length; i++)
+				{
+					var edit = new mxCellAttributeChange(
+						cell, attrs[i].nodeName,
+						texts[i].value);
+					model.execute(edit);
+				}
+				
+				// Checks if the graph wants cells to 
+				// be automatically sized and updates
+				// the size as an undoable step if
+				// the feature is enabled
+				if (this.graph.isAutoSizeCell(cell))
+				{
+					this.graph.updateCellSize(cell);
+				}
+			}
+			finally
+			{
+				model.endUpdate();
+			}
+		});
+		
+		// Defines the function to be executed when the
+		// Cancel button is pressed in the dialog
+		var cancelFunction = mxUtils.bind(this, function()
+		{
+			// Hides the dialog
+			this.hideProperties();
+		});
+		
+		form.addButtons(okFunction, cancelFunction);
+		
+		return form.table;
+	}
+
+	return null;
+};
+
+/**
+ * Function: hideProperties
+ * 
+ * Hides the properties dialog.
+ */
+mxEditor.prototype.hideProperties = function ()
+{
+	if (this.properties != null)
+	{
+		this.properties.destroy();
+		this.properties = null;
+	}
+};
+
+/**
+ * Function: showTasks
+ * 
+ * Shows the tasks window. The tasks window is created using <createTasks>. The
+ * default width of the window is 200 pixels, the y-coordinate of the location
+ * can be specifies in <tasksTop> and the x-coordinate is right aligned with a
+ * 20 pixel offset from the right border. To change the location of the tasks
+ * window, the following code can be used:
+ * 
+ * (code)
+ * var oldShowTasks = mxEditor.prototype.showTasks;
+ * mxEditor.prototype.showTasks = function()
+ * {
+ *   oldShowTasks.apply(this, arguments); // "supercall"
+ *   
+ *   if (this.tasks != null)
+ *   {
+ *     this.tasks.setLocation(10, 10);
+ *   }
+ * };
+ * (end)
+ */
+mxEditor.prototype.showTasks = function ()
+{
+	if (this.tasks == null)
+	{
+		var div = document.createElement('div');
+		div.style.padding = '4px';
+		div.style.paddingLeft = '20px';
+		var w = document.body.clientWidth;
+		var wnd = new mxWindow(
+			mxResources.get(this.tasksResource) ||
+			this.tasksResource,
+			div, w - 220, this.tasksTop, 200);
+		wnd.setClosable(true);
+		wnd.destroyOnClose = false;
+		
+		// Installs a function to update the contents
+		// of the tasks window on every change of the
+		// model, selection or root.
+		var funct = mxUtils.bind(this, function(sender)
+		{
+			mxEvent.release(div);
+			div.innerHTML = '';
+			this.createTasks(div);
+		});
+		
+		this.graph.getModel().addListener(mxEvent.CHANGE, funct);
+		this.graph.getSelectionModel().addListener(mxEvent.CHANGE, funct);
+		this.graph.addListener(mxEvent.ROOT, funct);
+		
+		// Assigns the icon to the tasks window
+		if (this.tasksWindowImage != null)
+		{
+			wnd.setImage(this.tasksWindowImage);
+		}
+		
+		this.tasks = wnd;
+		this.createTasks(div);
+	}
+	
+	this.tasks.setVisible(true);
+};
+		
+/**
+ * Function: refreshTasks
+ * 
+ * Updates the contents of the tasks window using <createTasks>.
+ */
+mxEditor.prototype.refreshTasks = function (div)
+{
+	if (this.tasks != null)
+	{
+		var div = this.tasks.content;
+		mxEvent.release(div);
+		div.innerHTML = '';
+		this.createTasks(div);
+	}
+};
+		
+/**
+ * Function: createTasks
+ * 
+ * Updates the contents of the given DOM node to
+ * display the tasks associated with the current
+ * editor state. This is invoked whenever there
+ * is a possible change of state in the editor.
+ * Default implementation is empty.
+ */
+mxEditor.prototype.createTasks = function (div)
+{
+	// override
+};
+	
+/**
+ * Function: showHelp
+ * 
+ * Shows the help window. If the help window does not exist
+ * then it is created using an iframe pointing to the resource
+ * for the <code>urlHelp</code> key or <urlHelp> if the resource
+ * is undefined.
+ */
+mxEditor.prototype.showHelp = function (tasks)
+{
+	if (this.help == null)
+	{
+		var frame = document.createElement('iframe');
+		frame.setAttribute('src', mxResources.get('urlHelp') || this.urlHelp);
+		frame.setAttribute('height', '100%');
+		frame.setAttribute('width', '100%');
+		frame.setAttribute('frameBorder', '0');
+		frame.style.backgroundColor = 'white';
+	
+		var w = document.body.clientWidth;
+		var h = (document.body.clientHeight || document.documentElement.clientHeight);
+		
+		var wnd = new mxWindow(mxResources.get(this.helpResource) || this.helpResource,
+			frame, (w-this.helpWidth)/2, (h-this.helpHeight)/3, this.helpWidth, this.helpHeight);
+		wnd.setMaximizable(true);
+		wnd.setClosable(true);
+		wnd.destroyOnClose = false;
+		wnd.setResizable(true);
+
+		// Assigns the icon to the help window
+		if (this.helpWindowImage != null)
+		{
+			wnd.setImage(this.helpWindowImage);
+		}
+		
+		// Workaround for ignored iframe height 100% in FF
+		if (mxClient.IS_NS)
+		{
+			var handler = function(sender)
+			{
+				var h = wnd.div.offsetHeight;
+				frame.setAttribute('height', (h-26)+'px');
+			};
+			
+			wnd.addListener(mxEvent.RESIZE_END, handler);
+			wnd.addListener(mxEvent.MAXIMIZE, handler);
+			wnd.addListener(mxEvent.NORMALIZE, handler);
+			wnd.addListener(mxEvent.SHOW, handler);
+		}
+		
+		this.help = wnd;
+	}
+	
+	this.help.setVisible(true);
+};
+
+/**
+ * Function: showOutline
+ * 
+ * Shows the outline window. If the window does not exist, then it is
+ * created using an <mxOutline>.
+ */
+mxEditor.prototype.showOutline = function ()
+{
+	var create = this.outline == null;
+	
+	if (create)
+	{
+		var div = document.createElement('div');
+		
+		div.style.overflow = 'hidden';
+		div.style.position = 'relative';
+		div.style.width = '100%';
+		div.style.height = '100%';
+		div.style.background = 'white';
+		div.style.cursor = 'move';
+		
+		if (document.documentMode == 8)
+		{
+			div.style.filter = 'progid:DXImageTransform.Microsoft.alpha(opacity=100)';
+		}
+		
+		var wnd = new mxWindow(
+			mxResources.get(this.outlineResource) ||
+			this.outlineResource,
+			div, 600, 480, 200, 200, false);
+				
+		// Creates the outline in the specified div
+		// and links it to the existing graph
+		var outline = new mxOutline(this.graph, div);			
+		wnd.setClosable(true);
+		wnd.setResizable(true);
+		wnd.destroyOnClose = false;
+		
+		wnd.addListener(mxEvent.RESIZE_END, function()
+		{
+			outline.update();
+		});
+		
+		this.outline = wnd;
+		this.outline.outline = outline;
+	}
+	
+	// Finally shows the outline
+	this.outline.setVisible(true);
+	this.outline.outline.update(true);
+};
+		
+/**
+ * Function: setMode
+ *
+ * Puts the graph into the specified mode. The following modenames are
+ * supported:
+ * 
+ * select - Selects using the left mouse button, new connections
+ * are disabled.
+ * connect - Selects using the left mouse button or creates new
+ * connections if mouse over cell hotspot. See <mxConnectionHandler>.
+ * pan - Pans using the left mouse button, new connections are disabled.
+ */
+mxEditor.prototype.setMode = function(modename)
+{
+	if (modename == 'select')
+	{
+		this.graph.panningHandler.useLeftButtonForPanning = false;
+		this.graph.setConnectable(false);
+	}
+	else if (modename == 'connect')
+	{
+		this.graph.panningHandler.useLeftButtonForPanning = false;
+		this.graph.setConnectable(true);
+	}
+	else if (modename == 'pan')
+	{
+		this.graph.panningHandler.useLeftButtonForPanning = true;
+		this.graph.setConnectable(false);
+	}
+};
+
+/**
+ * Function: createPopupMenu
+ * 
+ * Uses <popupHandler> to create the menu in the graph's
+ * panning handler. The redirection is setup in
+ * <setToolbarContainer>.
+ */
+mxEditor.prototype.createPopupMenu = function (menu, cell, evt)
+{
+	this.popupHandler.createMenu(this, menu, cell, evt);
+};
+
+/**
+ * Function: createEdge
+ * 
+ * Uses <defaultEdge> as the prototype for creating new edges
+ * in the connection handler of the graph. The style of the
+ * edge will be overridden with the value returned by
+ * <getEdgeStyle>.
+ */
+mxEditor.prototype.createEdge = function (source, target)
+{
+	// Clones the defaultedge prototype
+	var e = null;
+	
+	if (this.defaultEdge != null)
+	{
+		var model = this.graph.getModel();
+		e = model.cloneCell(this.defaultEdge);
+	}
+	else
+	{
+		e = new mxCell('');
+		e.setEdge(true);
+		
+		var geo = new mxGeometry();
+		geo.relative = true;
+		e.setGeometry(geo);
+	}
+	
+	// Overrides the edge style
+	var style = this.getEdgeStyle();
+	
+	if (style != null)
+	{
+		e.setStyle(style);
+	}
+	
+	return e;
+};
+
+/**
+ * Function: getEdgeStyle
+ * 
+ * Returns a string identifying the style of new edges.
+ * The function is used in <createEdge> when new edges
+ * are created in the graph.
+ */
+mxEditor.prototype.getEdgeStyle = function ()
+{
+	return this.defaultEdgeStyle;
+};
+
+/**
+ * Function: consumeCycleAttribute
+ * 
+ * Returns the next attribute in <cycleAttributeValues>
+ * or null, if not attribute should be used in the
+ * specified cell.
+ */
+mxEditor.prototype.consumeCycleAttribute = function (cell)
+{
+	return (this.cycleAttributeValues != null &&
+		this.cycleAttributeValues.length > 0 &&
+		this.graph.isSwimlane(cell)) ?
+		this.cycleAttributeValues[this.cycleAttributeIndex++ %
+			this.cycleAttributeValues.length] : null;
+};
+
+/**
+ * Function: cycleAttribute
+ * 
+ * Uses the returned value from <consumeCycleAttribute>
+ * as the value for the <cycleAttributeName> key in
+ * the given cell's style.
+ */
+mxEditor.prototype.cycleAttribute = function (cell)
+{
+	if (this.cycleAttributeName != null)
+	{
+		var value = this.consumeCycleAttribute(cell);
+		
+		if (value != null)
+		{
+			cell.setStyle(cell.getStyle()+';'+
+				this.cycleAttributeName+'='+value);
+		}
+	}
+};
+
+/**
+ * Function: addVertex
+ * 
+ * Adds the given vertex as a child of parent at the specified
+ * x and y coordinate and fires an <addVertex> event.
+ */
+mxEditor.prototype.addVertex = function (parent, vertex, x, y)
+{
+	var model = this.graph.getModel();
+	
+	while (parent != null && !this.graph.isValidDropTarget(parent))
+	{
+		parent = model.getParent(parent);
+	}
+	
+	parent = (parent != null) ? parent : this.graph.getSwimlaneAt(x, y);
+	var scale = this.graph.getView().scale;
+	
+	var geo = model.getGeometry(vertex);
+	var pgeo = model.getGeometry(parent);
+	
+	if (this.graph.isSwimlane(vertex) &&
+		!this.graph.swimlaneNesting)
+	{
+		parent = null;
+	}
+	else if (parent == null && this.swimlaneRequired)
+	{
+		return null;
+	}
+	else if (parent != null && pgeo != null)
+	{
+		// Keeps vertex inside parent
+		var state = this.graph.getView().getState(parent);
+		
+		if (state != null)
+		{			
+			x -= state.origin.x * scale;
+			y -= state.origin.y * scale;
+			
+			if (this.graph.isConstrainedMoving)
+			{
+				var width = geo.width;
+				var height = geo.height;				
+				var tmp = state.x+state.width;
+				
+				if (x+width > tmp)
+				{
+					x -= x+width - tmp;
+				}
+				
+				tmp = state.y+state.height;
+				
+				if (y+height > tmp)
+				{
+					y -= y+height - tmp;
+				}
+			}
+		}
+		else if (pgeo != null)
+		{
+			x -= pgeo.x*scale;
+			y -= pgeo.y*scale;
+		}
+	}
+	
+	geo = geo.clone();
+	geo.x = this.graph.snap(x / scale -
+		this.graph.getView().translate.x -
+		this.graph.gridSize/2);
+	geo.y = this.graph.snap(y / scale -
+		this.graph.getView().translate.y -
+		this.graph.gridSize/2);
+	vertex.setGeometry(geo);
+	
+	if (parent == null)
+	{
+		parent = this.graph.getDefaultParent();
+	}
+
+	this.cycleAttribute(vertex);
+	this.fireEvent(new mxEventObject(mxEvent.BEFORE_ADD_VERTEX,
+			'vertex', vertex, 'parent', parent));
+
+	model.beginUpdate();
+	try
+	{
+		vertex = this.graph.addCell(vertex, parent);
+		
+		if (vertex != null)
+		{
+			this.graph.constrainChild(vertex);
+			
+			this.fireEvent(new mxEventObject(mxEvent.ADD_VERTEX, 'vertex', vertex));
+		}
+	}
+	finally
+	{
+		model.endUpdate();
+	}
+	
+	if (vertex != null)
+	{
+		this.graph.setSelectionCell(vertex);
+		this.graph.scrollCellToVisible(vertex);
+		this.fireEvent(new mxEventObject(mxEvent.AFTER_ADD_VERTEX, 'vertex', vertex));
+	}
+	
+	return vertex;
+};
+
+/**
+ * Function: destroy
+ * 
+ * Removes the editor and all its associated resources. This does not
+ * normally need to be called, it is called automatically when the window
+ * unloads.
+ */
+mxEditor.prototype.destroy = function ()
+{
+	if (!this.destroyed)
+	{
+		this.destroyed = true;
+
+		if (this.tasks != null)
+		{
+			this.tasks.destroy();
+		}
+		
+		if (this.outline != null)
+		{
+			this.outline.destroy();
+		}
+		
+		if (this.properties != null)
+		{
+			this.properties.destroy();
+		}
+		
+		if (this.keyHandler != null)
+		{
+			this.keyHandler.destroy();
+		}
+		
+		if (this.rubberband != null)
+		{
+			this.rubberband.destroy();
+		}
+		
+		if (this.toolbar != null)
+		{
+			this.toolbar.destroy();
+		}
+		
+		if (this.graph != null)
+		{
+			this.graph.destroy();
+		}
+	
+		this.status = null;
+		this.templates = null;
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+var mxCodecRegistry =
+{
+	/**
+	 * Class: mxCodecRegistry
+	 *
+	 * Singleton class that acts as a global registry for codecs.
+	 *
+	 * Adding an <mxCodec>:
+	 *
+	 * 1. Define a default codec with a new instance of the 
+	 * object to be handled.
+	 *
+	 * (code)
+	 * var codec = new mxObjectCodec(new mxGraphModel());
+	 * (end)
+	 *
+	 * 2. Define the functions required for encoding and decoding
+	 * objects.
+	 *
+	 * (code)
+	 * codec.encode = function(enc, obj) { ... }
+	 * codec.decode = function(dec, node, into) { ... }
+	 * (end)
+	 *
+	 * 3. Register the codec in the <mxCodecRegistry>.
+	 *
+	 * (code)
+	 * mxCodecRegistry.register(codec);
+	 * (end)
+	 *
+	 * <mxObjectCodec.decode> may be used to either create a new 
+	 * instance of an object or to configure an existing instance, 
+	 * in which case the into argument points to the existing
+	 * object. In this case, we say the codec "configures" the
+	 * object.
+	 * 
+	 * Variable: codecs
+	 *
+	 * Maps from constructor names to codecs.
+	 */
+	codecs: [],
+	
+	/**
+	 * Variable: aliases
+	 *
+	 * Maps from classnames to codecnames.
+	 */
+	aliases: [],
+
+	/**
+	 * Function: register
+	 *
+	 * Registers a new codec and associates the name of the template
+	 * constructor in the codec with the codec object.
+	 *
+	 * Parameters:
+	 *
+	 * codec - <mxObjectCodec> to be registered.
+	 */
+	register: function(codec)
+	{
+		if (codec != null)
+		{
+			var name = codec.getName();
+			mxCodecRegistry.codecs[name] = codec;
+			
+			var classname = mxUtils.getFunctionName(codec.template.constructor);
+
+			if (classname != name)
+			{
+				mxCodecRegistry.addAlias(classname, name);
+			}
+		}
+
+		return codec;
+	},
+
+	/**
+	 * Function: addAlias
+	 *
+	 * Adds an alias for mapping a classname to a codecname.
+	 */
+	addAlias: function(classname, codecname)
+	{
+		mxCodecRegistry.aliases[classname] = codecname;
+	},
+
+	/**
+	 * Function: getCodec
+	 *
+	 * Returns a codec that handles objects that are constructed
+	 * using the given constructor.
+	 *
+	 * Parameters:
+	 *
+	 * ctor - JavaScript constructor function. 
+	 */
+	getCodec: function(ctor)
+	{
+		var codec = null;
+		
+		if (ctor != null)
+		{
+			var name = mxUtils.getFunctionName(ctor);
+			var tmp = mxCodecRegistry.aliases[name];
+			
+			if (tmp != null)
+			{
+				name = tmp;
+			}
+			
+			codec = mxCodecRegistry.codecs[name];
+			
+			// Registers a new default codec for the given constructor
+			// if no codec has been previously defined.
+			if (codec == null)
+			{
+				try
+				{
+					codec = new mxObjectCodec(new ctor());
+					mxCodecRegistry.register(codec);
+				}
+				catch (e)
+				{
+					// ignore
+				}
+			}
+		}
+		
+		return codec;
+	}
+
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxCodec
+ *
+ * XML codec for JavaScript object graphs. See <mxObjectCodec> for a
+ * description of the general encoding/decoding scheme. This class uses the
+ * codecs registered in <mxCodecRegistry> for encoding/decoding each object.
+ * 
+ * References:
+ * 
+ * In order to resolve references, especially forward references, the mxCodec
+ * constructor must be given the document that contains the referenced
+ * elements.
+ *
+ * Examples:
+ *
+ * The following code is used to encode a graph model.
+ *
+ * (code)
+ * var encoder = new mxCodec();
+ * var result = encoder.encode(graph.getModel());
+ * var xml = mxUtils.getXml(result);
+ * (end)
+ * 
+ * Example:
+ * 
+ * Using the code below, an XML document is decoded into an existing model. The
+ * document may be obtained using one of the functions in mxUtils for loading
+ * an XML file, eg. <mxUtils.get>, or using <mxUtils.parseXml> for parsing an
+ * XML string.
+ * 
+ * (code)
+ * var doc = mxUtils.parseXml(xmlString);
+ * var codec = new mxCodec(doc);
+ * codec.decode(doc.documentElement, graph.getModel());
+ * (end)
+ * 
+ * Example:
+ * 
+ * This example demonstrates parsing a list of isolated cells into an existing
+ * graph model. Note that the cells do not have a parent reference so they can
+ * be added anywhere in the cell hierarchy after parsing.
+ * 
+ * (code)
+ * var xml = '<root><mxCell id="2" value="Hello," vertex="1"><mxGeometry x="20" y="20" width="80" height="30" as="geometry"/></mxCell><mxCell id="3" value="World!" vertex="1"><mxGeometry x="200" y="150" width="80" height="30" as="geometry"/></mxCell><mxCell id="4" value="" edge="1" source="2" target="3"><mxGeometry relative="1" as="geometry"/></mxCell></root>';
+ * var doc = mxUtils.parseXml(xml);
+ * var codec = new mxCodec(doc);
+ * var elt = doc.documentElement.firstChild;
+ * var cells = [];
+ * 
+ * while (elt != null)
+ * {
+ *   cells.push(codec.decode(elt));
+ *   elt = elt.nextSibling;
+ * }
+ * 
+ * graph.addCells(cells);
+ * (end)
+ * 
+ * Example:
+ * 
+ * Using the following code, the selection cells of a graph are encoded and the
+ * output is displayed in a dialog box.
+ * 
+ * (code)
+ * var enc = new mxCodec();
+ * var cells = graph.getSelectionCells();
+ * mxUtils.alert(mxUtils.getPrettyXml(enc.encode(cells)));
+ * (end)
+ * 
+ * Newlines in the XML can be converted to <br>, in which case a '<br>' argument
+ * must be passed to <mxUtils.getXml> as the second argument.
+ * 
+ * Debugging:
+ * 
+ * For debugging I/O you can use the following code to get the sequence of
+ * encoded objects:
+ * 
+ * (code)
+ * var oldEncode = mxCodec.prototype.encode;
+ * mxCodec.prototype.encode = function(obj)
+ * {
+ *   mxLog.show();
+ *   mxLog.debug('mxCodec.encode: obj='+mxUtils.getFunctionName(obj.constructor));
+ *   
+ *   return oldEncode.apply(this, arguments);
+ * };
+ * (end)
+ * 
+ * Note that the I/O system adds object codecs for new object automatically. For
+ * decoding those objects, the constructor should be written as follows:
+ * 
+ * (code)
+ * var MyObj = function(name)
+ * {
+ *   // ...
+ * };
+ * (end)
+ * 
+ * Constructor: mxCodec
+ *
+ * Constructs an XML encoder/decoder for the specified
+ * owner document.
+ *
+ * Parameters:
+ *
+ * document - Optional XML document that contains the data.
+ * If no document is specified then a new document is created
+ * using <mxUtils.createXmlDocument>.
+ */
+function mxCodec(document)
+{
+	this.document = document || mxUtils.createXmlDocument();
+	this.objects = [];
+};
+
+/**
+ * Variable: document
+ *
+ * The owner document of the codec.
+ */
+mxCodec.prototype.document = null;
+
+/**
+ * Variable: objects
+ *
+ * Maps from IDs to objects.
+ */
+mxCodec.prototype.objects = null;
+
+/**
+ * Variable: elements
+ * 
+ * Lookup table for resolving IDs to elements.
+ */
+mxCodec.prototype.elements = null;
+
+/**
+ * Variable: encodeDefaults
+ *
+ * Specifies if default values should be encoded. Default is false.
+ */
+mxCodec.prototype.encodeDefaults = false;
+
+
+/**
+ * Function: putObject
+ * 
+ * Assoiates the given object with the given ID and returns the given object.
+ * 
+ * Parameters
+ * 
+ * id - ID for the object to be associated with.
+ * obj - Object to be associated with the ID.
+ */
+mxCodec.prototype.putObject = function(id, obj)
+{
+	this.objects[id] = obj;
+	
+	return obj;
+};
+
+/**
+ * Function: getObject
+ *
+ * Returns the decoded object for the element with the specified ID in
+ * <document>. If the object is not known then <lookup> is used to find an
+ * object. If no object is found, then the element with the respective ID
+ * from the document is parsed using <decode>.
+ */
+mxCodec.prototype.getObject = function(id)
+{
+	var obj = null;
+
+	if (id != null)
+	{
+		obj = this.objects[id];
+		
+		if (obj == null)
+		{
+			obj = this.lookup(id);
+			
+			if (obj == null)
+			{
+				var node = this.getElementById(id);
+				
+				if (node != null)
+				{
+					obj = this.decode(node);
+				}
+			}
+		}
+	}
+	
+	return obj;
+};
+
+/**
+ * Function: lookup
+ *
+ * Hook for subclassers to implement a custom lookup mechanism for cell IDs.
+ * This implementation always returns null.
+ *
+ * Example:
+ *
+ * (code)
+ * var codec = new mxCodec();
+ * codec.lookup = function(id)
+ * {
+ *   return model.getCell(id);
+ * };
+ * (end)
+ *
+ * Parameters:
+ *
+ * id - ID of the object to be returned.
+ */
+mxCodec.prototype.lookup = function(id)
+{
+	return null;
+};
+
+/**
+ * Function: getElementById
+ *
+ * Returns the element with the given ID from <document>.
+ *
+ * Parameters:
+ *
+ * id - String that contains the ID.
+ */
+mxCodec.prototype.getElementById = function(id)
+{
+	if (this.elements == null)
+	{
+		// Throws custom error for cases where a reference should be resolved
+		// in an empty document. This happens if an XML node is decoded without
+		// passing the owner document to the codec constructor.
+		if (this.document.documentElement == null)
+		{
+			throw new Error('mxCodec constructor needs document parameter');
+		}
+		
+		this.elements = new Object();
+		this.addElement(this.document.documentElement);
+	}
+	
+	return this.elements[id];
+};
+
+/**
+ * Function: addElement
+ *
+ * Adds the given element to <elements> if it has an ID.
+ */
+mxCodec.prototype.addElement = function(node)
+{
+	if (node.nodeType == mxConstants.NODETYPE_ELEMENT)
+	{
+		var id = node.getAttribute('id');
+		
+		if (id != null && this.elements[id] == null)
+		{
+			this.elements[id] = node;
+		}
+	}
+	
+	node = node.firstChild;
+	
+	while (node != null)
+	{
+		this.addElement(node);
+		node = node.nextSibling;
+	}
+};
+
+/**
+ * Function: getId
+ *
+ * Returns the ID of the specified object. This implementation
+ * calls <reference> first and if that returns null handles
+ * the object as an <mxCell> by returning their IDs using
+ * <mxCell.getId>. If no ID exists for the given cell, then
+ * an on-the-fly ID is generated using <mxCellPath.create>.
+ *
+ * Parameters:
+ *
+ * obj - Object to return the ID for.
+ */
+mxCodec.prototype.getId = function(obj)
+{
+	var id = null;
+	
+	if (obj != null)
+	{
+		id = this.reference(obj);
+		
+		if (id == null && obj instanceof mxCell)
+		{
+			id = obj.getId();
+			
+			if (id == null)
+			{
+				// Uses an on-the-fly Id
+				id = mxCellPath.create(obj);
+				
+				if (id.length == 0)
+				{
+					id = 'root';
+				}
+			}
+		}
+	}
+	
+	return id;
+};
+
+/**
+ * Function: reference
+ *
+ * Hook for subclassers to implement a custom method
+ * for retrieving IDs from objects. This implementation
+ * always returns null.
+ *
+ * Example:
+ *
+ * (code)
+ * var codec = new mxCodec();
+ * codec.reference = function(obj)
+ * {
+ *   return obj.getCustomId();
+ * };
+ * (end)
+ *
+ * Parameters:
+ *
+ * obj - Object whose ID should be returned.
+ */
+mxCodec.prototype.reference = function(obj)
+{
+	return null;
+};
+
+/**
+ * Function: encode
+ *
+ * Encodes the specified object and returns the resulting
+ * XML node.
+ *
+ * Parameters:
+ *
+ * obj - Object to be encoded. 
+ */
+mxCodec.prototype.encode = function(obj)
+{
+	var node = null;
+	
+	if (obj != null && obj.constructor != null)
+	{
+		var enc = mxCodecRegistry.getCodec(obj.constructor);
+		
+		if (enc != null)
+		{
+			node = enc.encode(this, obj);
+		}
+		else
+		{
+			if (mxUtils.isNode(obj))
+			{
+				node = mxUtils.importNode(this.document, obj, true);
+			}
+			else
+			{
+	    		mxLog.warn('mxCodec.encode: No codec for ' + mxUtils.getFunctionName(obj.constructor));
+			}
+		}
+	}
+	
+	return node;
+};
+
+/**
+ * Function: decode
+ *
+ * Decodes the given XML node. The optional "into"
+ * argument specifies an existing object to be
+ * used. If no object is given, then a new instance
+ * is created using the constructor from the codec.
+ *
+ * The function returns the passed in object or
+ * the new instance if no object was given.
+ *
+ * Parameters:
+ *
+ * node - XML node to be decoded.
+ * into - Optional object to be decodec into.
+ */
+mxCodec.prototype.decode = function(node, into)
+{
+	var obj = null;
+	
+	if (node != null && node.nodeType == mxConstants.NODETYPE_ELEMENT)
+	{
+		var ctor = null;
+		
+		try
+		{
+			ctor = window[node.nodeName];
+		}
+		catch (err)
+		{
+			// ignore
+		}
+		
+		var dec = mxCodecRegistry.getCodec(ctor);
+		
+		if (dec != null)
+		{
+			obj = dec.decode(this, node, into);
+		}
+		else
+		{
+			obj = node.cloneNode(true);
+			obj.removeAttribute('as');
+		}
+	}
+	
+	return obj;
+};
+
+/**
+ * Function: encodeCell
+ *
+ * Encoding of cell hierarchies is built-into the core, but
+ * is a higher-level function that needs to be explicitely
+ * used by the respective object encoders (eg. <mxModelCodec>,
+ * <mxChildChangeCodec> and <mxRootChangeCodec>). This
+ * implementation writes the given cell and its children as a
+ * (flat) sequence into the given node. The children are not
+ * encoded if the optional includeChildren is false. The
+ * function is in charge of adding the result into the
+ * given node and has no return value.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to be encoded.
+ * node - Parent XML node to add the encoded cell into.
+ * includeChildren - Optional boolean indicating if the
+ * function should include all descendents. Default is true. 
+ */
+mxCodec.prototype.encodeCell = function(cell, node, includeChildren)
+{
+	node.appendChild(this.encode(cell));
+	
+	if (includeChildren == null || includeChildren)
+	{
+		var childCount = cell.getChildCount();
+		
+		for (var i = 0; i < childCount; i++)
+		{
+			this.encodeCell(cell.getChildAt(i), node);
+		}
+	}
+};
+
+/**
+ * Function: isCellCodec
+ * 
+ * Returns true if the given codec is a cell codec. This uses
+ * <mxCellCodec.isCellCodec> to check if the codec is of the
+ * given type.
+ */
+mxCodec.prototype.isCellCodec = function(codec)
+{
+	if (codec != null && typeof(codec.isCellCodec) == 'function')
+	{
+		return codec.isCellCodec();
+	}
+	
+	return false;
+};
+
+/**
+ * Function: decodeCell
+ *
+ * Decodes cells that have been encoded using inversion, ie.
+ * where the user object is the enclosing node in the XML,
+ * and restores the group and graph structure in the cells.
+ * Returns a new <mxCell> instance that represents the
+ * given node.
+ *
+ * Parameters:
+ *
+ * node - XML node that contains the cell data.
+ * restoreStructures - Optional boolean indicating whether
+ * the graph structure should be restored by calling insert
+ * and insertEdge on the parent and terminals, respectively.
+ * Default is true.
+ */
+mxCodec.prototype.decodeCell = function(node, restoreStructures)
+{
+	restoreStructures = (restoreStructures != null) ? restoreStructures : true;
+	var cell = null;
+	
+	if (node != null && node.nodeType == mxConstants.NODETYPE_ELEMENT)
+	{
+		// Tries to find a codec for the given node name. If that does
+		// not return a codec then the node is the user object (an XML node
+		// that contains the mxCell, aka inversion).
+		var decoder = mxCodecRegistry.getCodec(node.nodeName);
+		
+		// Tries to find the codec for the cell inside the user object.
+		// This assumes all node names inside the user object are either
+		// not registered or they correspond to a class for cells.
+		if (!this.isCellCodec(decoder))
+		{
+			var child = node.firstChild;
+			
+			while (child != null && !this.isCellCodec(decoder))
+			{
+				decoder = mxCodecRegistry.getCodec(child.nodeName);
+				child = child.nextSibling;
+			}
+		}
+		
+		if (!this.isCellCodec(decoder))
+		{
+			decoder = mxCodecRegistry.getCodec(mxCell);
+		}
+
+		cell = decoder.decode(this, node);
+		
+		if (restoreStructures)
+		{
+			this.insertIntoGraph(cell);
+		}
+	}
+	
+	return cell;
+};
+
+/**
+ * Function: insertIntoGraph
+ *
+ * Inserts the given cell into its parent and terminal cells.
+ */
+mxCodec.prototype.insertIntoGraph = function(cell)
+{
+	var parent = cell.parent;
+	var source = cell.getTerminal(true);
+	var target = cell.getTerminal(false);
+
+	// Fixes possible inconsistencies during insert into graph
+	cell.setTerminal(null, false);
+	cell.setTerminal(null, true);
+	cell.parent = null;
+	
+	if (parent != null)
+	{
+		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);
+	}
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxObjectCodec
+ *
+ * Generic codec for JavaScript objects that implements a mapping between
+ * JavaScript objects and XML nodes that maps each field or element to an
+ * attribute or child node, and vice versa.
+ * 
+ * Atomic Values:
+ * 
+ * Consider the following example.
+ * 
+ * (code)
+ * var obj = new Object();
+ * obj.foo = "Foo";
+ * obj.bar = "Bar";
+ * (end)
+ * 
+ * This object is encoded into an XML node using the following.
+ * 
+ * (code)
+ * var enc = new mxCodec();
+ * var node = enc.encode(obj);
+ * (end)
+ * 
+ * The output of the encoding may be viewed using <mxLog> as follows.
+ * 
+ * (code)
+ * mxLog.show();
+ * mxLog.debug(mxUtils.getPrettyXml(node));
+ * (end)
+ * 
+ * Finally, the result of the encoding looks as follows.
+ * 
+ * (code)
+ * <Object foo="Foo" bar="Bar"/>
+ * (end)
+ * 
+ * In the above output, the foo and bar fields have been mapped to attributes
+ * with the same names, and the name of the constructor was used for the
+ * nodename.
+ * 
+ * Booleans:
+ *
+ * Since booleans are numbers in JavaScript, all boolean values are encoded
+ * into 1 for true and 0 for false. The decoder also accepts the string true
+ * and false for boolean values.
+ * 
+ * Objects:
+ * 
+ * The above scheme is applied to all atomic fields, that is, to all non-object
+ * fields of an object. For object fields, a child node is created with a
+ * special attribute that contains the fieldname. This special attribute is
+ * called "as" and hence, as is a reserved word that should not be used for a
+ * fieldname.
+ * 
+ * Consider the following example where foo is an object and bar is an atomic
+ * property of foo.
+ * 
+ * (code)
+ * var obj = {foo: {bar: "Bar"}};
+ * (end)
+ * 
+ * This will be mapped to the following XML structure by mxObjectCodec.
+ * 
+ * (code)
+ * <Object>
+ *   <Object bar="Bar" as="foo"/>
+ * </Object>
+ * (end)
+ * 
+ * In the above output, the inner Object node contains the as-attribute that
+ * specifies the fieldname in the enclosing object. That is, the field foo was
+ * mapped to a child node with an as-attribute that has the value foo.
+ * 
+ * Arrays:
+ * 
+ * Arrays are special objects that are either associative, in which case each
+ * key, value pair is treated like a field where the key is the fieldname, or
+ * they are a sequence of atomic values and objects, which is mapped to a
+ * sequence of child nodes. For object elements, the above scheme is applied
+ * without the use of the special as-attribute for creating each child. For
+ * atomic elements, a special add-node is created with the value stored in the
+ * value-attribute.
+ * 
+ * For example, the following array contains one atomic value and one object
+ * with a field called bar. Furthermore it contains two associative entries
+ * called bar with an atomic value, and foo with an object value.
+ * 
+ * (code)
+ * var obj = ["Bar", {bar: "Bar"}];
+ * obj["bar"] = "Bar";
+ * obj["foo"] = {bar: "Bar"};
+ * (end)
+ * 
+ * This array is represented by the following XML nodes.
+ * 
+ * (code)
+ * <Array bar="Bar">
+ *   <add value="Bar"/>
+ *   <Object bar="Bar"/>
+ *   <Object bar="Bar" as="foo"/>
+ * </Array>
+ * (end)
+ * 
+ * The Array node name is the name of the constructor. The additional
+ * as-attribute in the last child contains the key of the associative entry,
+ * whereas the second last child is part of the array sequence and does not
+ * have an as-attribute.
+ * 
+ * References:
+ * 
+ * Objects may be represented as child nodes or attributes with ID values,
+ * which are used to lookup the object in a table within <mxCodec>. The
+ * <isReference> function is in charge of deciding if a specific field should
+ * be encoded as a reference or not. Its default implementation returns true if
+ * the fieldname is in <idrefs>, an array of strings that is used to configure
+ * the <mxObjectCodec>.
+ * 
+ * Using this approach, the mapping does not guarantee that the referenced
+ * object itself exists in the document. The fields that are encoded as
+ * references must be carefully chosen to make sure all referenced objects
+ * exist in the document, or may be resolved by some other means if necessary.
+ * 
+ * For example, in the case of the graph model all cells are stored in a tree
+ * whose root is referenced by the model's root field. A tree is a structure
+ * that is well suited for an XML representation, however, the additional edges
+ * in the graph model have a reference to a source and target cell, which are
+ * also contained in the tree. To handle this case, the source and target cell
+ * of an edge are treated as references, whereas the children are treated as
+ * objects. Since all cells are contained in the tree and no edge references a
+ * source or target outside the tree, this setup makes sure all referenced
+ * objects are contained in the document.
+ * 
+ * In the case of a tree structure we must further avoid infinite recursion by
+ * ignoring the parent reference of each child. This is done by returning true
+ * in <isExcluded>, whose default implementation uses the array of excluded
+ * fieldnames passed to the mxObjectCodec constructor.
+ * 
+ * References are only used for cells in mxGraph. For defining other
+ * referencable object types, the codec must be able to work out the ID of an
+ * object. This is done by implementing <mxCodec.reference>. For decoding a
+ * reference, the XML node with the respective id-attribute is fetched from the
+ * document, decoded, and stored in a lookup table for later reference. For
+ * looking up external objects, <mxCodec.lookup> may be implemented.
+ * 
+ * Expressions:
+ * 
+ * For decoding JavaScript expressions, the add-node may be used with a text
+ * content that contains the JavaScript expression. For example, the following
+ * creates a field called foo in the enclosing object and assigns it the value
+ * of <mxConstants.ALIGN_LEFT>.
+ * 
+ * (code)
+ * <Object>
+ *   <add as="foo">mxConstants.ALIGN_LEFT</add>
+ * </Object>
+ * (end)
+ * 
+ * The resulting object has a field called foo with the value "left". Its XML
+ * representation looks as follows.
+ * 
+ * (code)
+ * <Object foo="left"/>
+ * (end)
+ * 
+ * This means the expression is evaluated at decoding time and the result of
+ * the evaluation is stored in the respective field. Valid expressions are all
+ * JavaScript expressions, including function definitions, which are mapped to
+ * functions on the resulting object.
+ * 
+ * Expressions are only evaluated if <allowEval> is true.
+ * 
+ * Constructor: mxObjectCodec
+ *
+ * Constructs a new codec for the specified template object.
+ * The variables in the optional exclude array are ignored by
+ * the codec. Variables in the optional idrefs array are
+ * turned into references in the XML. The optional mapping
+ * may be used to map from variable names to XML attributes.
+ * The argument is created as follows:
+ *
+ * (code)
+ * var mapping = new Object();
+ * mapping['variableName'] = 'attribute-name';
+ * (end)
+ *
+ * Parameters:
+ *
+ * template - Prototypical instance of the object to be
+ * encoded/decoded.
+ * exclude - Optional array of fieldnames to be ignored.
+ * idrefs - Optional array of fieldnames to be converted to/from
+ * references.
+ * mapping - Optional mapping from field- to attributenames.
+ */
+function mxObjectCodec(template, exclude, idrefs, mapping)
+{
+	this.template = template;
+	
+	this.exclude = (exclude != null) ? exclude : [];
+	this.idrefs = (idrefs != null) ? idrefs : [];
+	this.mapping = (mapping != null) ? mapping : [];
+	
+	this.reverse = new Object();
+	
+	for (var i in this.mapping)
+	{
+		this.reverse[this.mapping[i]] = i;
+	}
+};
+
+/**
+ * Variable: allowEval
+ *
+ * Static global switch that specifies if expressions in arrays are allowed.
+ * Default is false. NOTE: Enabling this carries a possible security risk.
+ */
+mxObjectCodec.allowEval = false;
+
+/**
+ * Variable: template
+ *
+ * Holds the template object associated with this codec.
+ */
+mxObjectCodec.prototype.template = null;
+
+/**
+ * Variable: exclude
+ *
+ * Array containing the variable names that should be
+ * ignored by the codec.
+ */
+mxObjectCodec.prototype.exclude = null;
+
+/**
+ * Variable: idrefs
+ *
+ * Array containing the variable names that should be
+ * turned into or converted from references. See
+ * <mxCodec.getId> and <mxCodec.getObject>.
+ */
+mxObjectCodec.prototype.idrefs = null;
+
+/**
+ * Variable: mapping
+ *
+ * Maps from from fieldnames to XML attribute names.
+ */
+mxObjectCodec.prototype.mapping = null;
+
+/**
+ * Variable: reverse
+ *
+ * Maps from from XML attribute names to fieldnames.
+ */
+mxObjectCodec.prototype.reverse = null;
+
+/**
+ * Function: getName
+ * 
+ * Returns the name used for the nodenames and lookup of the codec when
+ * classes are encoded and nodes are decoded. For classes to work with
+ * this the codec registry automatically adds an alias for the classname
+ * if that is different than what this returns. The default implementation
+ * returns the classname of the template class.
+ */
+mxObjectCodec.prototype.getName = function()
+{
+	return mxUtils.getFunctionName(this.template.constructor);
+};
+
+/**
+ * Function: cloneTemplate
+ * 
+ * Returns a new instance of the template for this codec.
+ */
+mxObjectCodec.prototype.cloneTemplate = function()
+{
+	return new this.template.constructor();
+};
+
+/**
+ * Function: getFieldName
+ * 
+ * Returns the fieldname for the given attributename.
+ * Looks up the value in the <reverse> mapping or returns
+ * the input if there is no reverse mapping for the
+ * given name.
+ */
+mxObjectCodec.prototype.getFieldName = function(attributename)
+{
+	if (attributename != null)
+	{
+		var mapped = this.reverse[attributename];
+		
+		if (mapped != null)
+		{
+			attributename = mapped;
+		}
+	}
+	
+	return attributename;
+};
+
+/**
+ * Function: getAttributeName
+ * 
+ * Returns the attributename for the given fieldname.
+ * Looks up the value in the <mapping> or returns
+ * the input if there is no mapping for the
+ * given name.
+ */
+mxObjectCodec.prototype.getAttributeName = function(fieldname)
+{
+	if (fieldname != null)
+	{
+		var mapped = this.mapping[fieldname];
+		
+		if (mapped != null)
+		{
+			fieldname = mapped;
+		}
+	}
+	
+	return fieldname;
+};
+
+/**
+ * Function: isExcluded
+ *
+ * Returns true if the given attribute is to be ignored by the codec. This
+ * implementation returns true if the given fieldname is in <exclude> or
+ * if the fieldname equals <mxObjectIdentity.FIELD_NAME>.
+ *
+ * Parameters:
+ *
+ * obj - Object instance that contains the field.
+ * attr - Fieldname of the field.
+ * value - Value of the field.
+ * write - Boolean indicating if the field is being encoded or decoded.
+ * Write is true if the field is being encoded, else it is being decoded.
+ */
+mxObjectCodec.prototype.isExcluded = function(obj, attr, value, write)
+{
+	return attr == mxObjectIdentity.FIELD_NAME ||
+		mxUtils.indexOf(this.exclude, attr) >= 0;
+};
+
+/**
+ * Function: isReference
+ *
+ * Returns true if the given fieldname is to be treated
+ * as a textual reference (ID). This implementation returns
+ * true if the given fieldname is in <idrefs>.
+ *
+ * Parameters:
+ *
+ * obj - Object instance that contains the field.
+ * attr - Fieldname of the field.
+ * value - Value of the field. 
+ * write - Boolean indicating if the field is being encoded or decoded.
+ * Write is true if the field is being encoded, else it is being decoded.
+ */
+mxObjectCodec.prototype.isReference = function(obj, attr, value, write)
+{
+	return mxUtils.indexOf(this.idrefs, attr) >= 0;
+};
+
+/**
+ * Function: encode
+ *
+ * Encodes the specified object and returns a node
+ * representing then given object. Calls <beforeEncode>
+ * after creating the node and <afterEncode> with the 
+ * resulting node after processing.
+ *
+ * Enc is a reference to the calling encoder. It is used
+ * to encode complex objects and create references.
+ *
+ * This implementation encodes all variables of an
+ * object according to the following rules:
+ *
+ * - If the variable name is in <exclude> then it is ignored.
+ * - If the variable name is in <idrefs> then <mxCodec.getId>
+ * is used to replace the object with its ID.
+ * - The variable name is mapped using <mapping>.
+ * - If obj is an array and the variable name is numeric
+ * (ie. an index) then it is not encoded.
+ * - If the value is an object, then the codec is used to
+ * create a child node with the variable name encoded into
+ * the "as" attribute.
+ * - Else, if <encodeDefaults> is true or the value differs
+ * from the template value, then ...
+ * - ... if obj is not an array, then the value is mapped to
+ * an attribute.
+ * - ... else if obj is an array, the value is mapped to an
+ * add child with a value attribute or a text child node,
+ * if the value is a function.
+ *
+ * If no ID exists for a variable in <idrefs> or if an object
+ * cannot be encoded, a warning is issued using <mxLog.warn>.
+ *
+ * Returns the resulting XML node that represents the given
+ * object.
+ *
+ * Parameters:
+ *
+ * enc - <mxCodec> that controls the encoding process.
+ * obj - Object to be encoded.
+ */
+mxObjectCodec.prototype.encode = function(enc, obj)
+{
+	var node = enc.document.createElement(this.getName());
+	
+	obj = this.beforeEncode(enc, obj, node);
+	this.encodeObject(enc, obj, node);
+	
+	return this.afterEncode(enc, obj, node);
+};
+	
+/**
+ * Function: encodeObject
+ *
+ * Encodes the value of each member in then given obj into the given node using
+ * <encodeValue>.
+ * 
+ * Parameters:
+ *
+ * enc - <mxCodec> that controls the encoding process.
+ * obj - Object to be encoded.
+ * node - XML node that contains the encoded object.
+ */
+mxObjectCodec.prototype.encodeObject = function(enc, obj, node)
+{
+	enc.setAttribute(node, 'id', enc.getId(obj));
+	
+    for (var i in obj)
+    {
+		var name = i;
+		var value = obj[name];
+		
+    	if (value != null && !this.isExcluded(obj, name, value, true))
+    	{
+    		if (mxUtils.isInteger(name))
+    		{
+    			name = null;
+    		}
+    		
+    		this.encodeValue(enc, obj, name, value, node);
+    	}
+    }
+};
+
+/**
+ * Function: encodeValue
+ * 
+ * Converts the given value according to the mappings
+ * and id-refs in this codec and uses <writeAttribute>
+ * to write the attribute into the given node.
+ * 
+ * Parameters:
+ *
+ * enc - <mxCodec> that controls the encoding process.
+ * obj - Object whose property is going to be encoded.
+ * name - XML node that contains the encoded object.
+ * value - Value of the property to be encoded.
+ * node - XML node that contains the encoded object.
+ */
+mxObjectCodec.prototype.encodeValue = function(enc, obj, name, value, node)
+{
+	if (value != null)
+	{
+		if (this.isReference(obj, name, value, true))
+		{
+			var tmp = enc.getId(value);
+			
+			if (tmp == null)
+			{
+		    	mxLog.warn('mxObjectCodec.encode: No ID for ' +
+		    		this.getName() + '.' + name + '=' + value);
+		    	return; // exit
+		    }
+		    
+		    value = tmp;
+		}
+
+		var defaultValue = this.template[name];
+		
+		// Checks if the value is a default value and
+		// the name is correct
+		if (name == null || enc.encodeDefaults || defaultValue != value)
+		{
+			name = this.getAttributeName(name);
+			this.writeAttribute(enc, obj, name, value, node);	
+		}
+	}
+};
+
+/**
+ * Function: writeAttribute
+ * 
+ * Writes the given value into node using <writePrimitiveAttribute>
+ * or <writeComplexAttribute> depending on the type of the value.
+ */
+mxObjectCodec.prototype.writeAttribute = function(enc, obj, name, value, node)
+{
+	if (typeof(value) != 'object' /* primitive type */)
+	{
+		this.writePrimitiveAttribute(enc, obj, name, value, node);
+	}
+	else /* complex type */
+	{
+		this.writeComplexAttribute(enc, obj, name, value, node);
+	}
+};
+
+/**
+ * Function: writePrimitiveAttribute
+ * 
+ * Writes the given value as an attribute of the given node.
+ */
+mxObjectCodec.prototype.writePrimitiveAttribute = function(enc, obj, name, value, node)
+{
+	value = this.convertAttributeToXml(enc, obj, name, value, node);
+	
+	if (name == null)
+	{
+		var child = enc.document.createElement('add');
+		
+		if (typeof(value) == 'function')
+		{
+    		child.appendChild(enc.document.createTextNode(value));
+    	}
+    	else
+    	{
+    		enc.setAttribute(child, 'value', value);
+    	}
+    	
+		node.appendChild(child);
+	}
+	else if (typeof(value) != 'function')
+	{
+    	enc.setAttribute(node, name, value);
+	}		
+};
+	
+/**
+ * Function: writeComplexAttribute
+ * 
+ * Writes the given value as a child node of the given node.
+ */
+mxObjectCodec.prototype.writeComplexAttribute = function(enc, obj, name, value, node)
+{
+	var child = enc.encode(value);
+	
+	if (child != null)
+	{
+		if (name != null)
+		{
+    		child.setAttribute('as', name);
+    	}
+    	
+    	node.appendChild(child);
+	}
+	else
+	{
+		mxLog.warn('mxObjectCodec.encode: No node for ' + this.getName() + '.' + name + ': ' + value);
+	}
+};
+
+/**
+ * Function: convertAttributeToXml
+ * 
+ * Converts true to "1" and false to "0" is <isBooleanAttribute> returns true.
+ * All other values are not converted.
+ * 
+ * Parameters:
+ *
+ * enc - <mxCodec> that controls the encoding process.
+ * obj - Objec to convert the attribute for.
+ * name - Name of the attribute to be converted.
+ * value - Value to be converted.
+ */
+mxObjectCodec.prototype.convertAttributeToXml = function(enc, obj, name, value)
+{
+	// Makes sure to encode boolean values as numeric values
+	if (this.isBooleanAttribute(enc, obj, name, value))
+	{	
+		// Checks if the value is true (do not use the value as is, because
+		// this would check if the value is not null, so 0 would be true)
+		value = (value == true) ? '1' : '0';
+	}
+	
+	return value;
+};
+
+/**
+ * Function: isBooleanAttribute
+ * 
+ * Returns true if the given object attribute is a boolean value.
+ * 
+ * Parameters:
+ *
+ * enc - <mxCodec> that controls the encoding process.
+ * obj - Objec to convert the attribute for.
+ * name - Name of the attribute to be converted.
+ * value - Value of the attribute to be converted.
+ */
+mxObjectCodec.prototype.isBooleanAttribute = function(enc, obj, name, value)
+{
+	return (typeof(value.length) == 'undefined' && (value == true || value == false));
+};
+
+/**
+ * Function: convertAttributeFromXml
+ * 
+ * Converts booleans and numeric values to the respective types. Values are
+ * numeric if <isNumericAttribute> returns true.
+ * 
+ * Parameters:
+ *
+ * dec - <mxCodec> that controls the decoding process.
+ * attr - XML attribute to be converted.
+ * obj - Objec to convert the attribute for.
+ */
+mxObjectCodec.prototype.convertAttributeFromXml = function(dec, attr, obj)
+{
+	var value = attr.value;
+	
+	if (this.isNumericAttribute(dec, attr, obj))
+	{
+		value = parseFloat(value);
+	}
+	
+	return value;
+};
+
+/**
+ * Function: isNumericAttribute
+ * 
+ * Returns true if the given XML attribute is a numeric value.
+ * 
+ * Parameters:
+ *
+ * dec - <mxCodec> that controls the decoding process.
+ * attr - XML attribute to be converted.
+ * obj - Objec to convert the attribute for.
+ */
+mxObjectCodec.prototype.isNumericAttribute = function(dec, attr, obj)
+{
+	return mxUtils.isNumeric(attr.value);
+};
+
+/**
+ * Function: beforeEncode
+ *
+ * Hook for subclassers to pre-process the object before
+ * encoding. This returns the input object. The return
+ * value of this function is used in <encode> to perform
+ * the default encoding into the given node.
+ *
+ * Parameters:
+ *
+ * enc - <mxCodec> that controls the encoding process.
+ * obj - Object to be encoded.
+ * node - XML node to encode the object into.
+ */
+mxObjectCodec.prototype.beforeEncode = function(enc, obj, node)
+{
+	return obj;
+};
+
+/**
+ * Function: afterEncode
+ *
+ * Hook for subclassers to post-process the node
+ * for the given object after encoding and return the
+ * post-processed node. This implementation returns 
+ * the input node. The return value of this method
+ * is returned to the encoder from <encode>.
+ *
+ * Parameters:
+ *
+ * enc - <mxCodec> that controls the encoding process.
+ * obj - Object to be encoded.
+ * node - XML node that represents the default encoding.
+ */
+mxObjectCodec.prototype.afterEncode = function(enc, obj, node)
+{
+	return node;
+};
+
+/**
+ * Function: decode
+ *
+ * Parses the given node into the object or returns a new object
+ * representing the given node.
+ *
+ * Dec is a reference to the calling decoder. It is used to decode
+ * complex objects and resolve references.
+ *
+ * If a node has an id attribute then the object cache is checked for the
+ * object. If the object is not yet in the cache then it is constructed
+ * using the constructor of <template> and cached in <mxCodec.objects>.
+ *
+ * This implementation decodes all attributes and childs of a node
+ * according to the following rules:
+ *
+ * - If the variable name is in <exclude> or if the attribute name is "id"
+ * or "as" then it is ignored.
+ * - If the variable name is in <idrefs> then <mxCodec.getObject> is used
+ * to replace the reference with an object.
+ * - The variable name is mapped using a reverse <mapping>.
+ * - If the value has a child node, then the codec is used to create a
+ * child object with the variable name taken from the "as" attribute.
+ * - If the object is an array and the variable name is empty then the
+ * value or child object is appended to the array.
+ * - If an add child has no value or the object is not an array then
+ * the child text content is evaluated using <mxUtils.eval>.
+ *
+ * For add nodes where the object is not an array and the variable name
+ * is defined, the default mechanism is used, allowing to override/add
+ * methods as follows:
+ *
+ * (code)
+ * <Object>
+ *   <add as="hello"><![CDATA[
+ *     function(arg1) {
+ *       mxUtils.alert('Hello '+arg1);
+ *     }
+ *   ]]></add>
+ * </Object>
+ * (end) 
+ *
+ * If no object exists for an ID in <idrefs> a warning is issued
+ * using <mxLog.warn>.
+ *
+ * Returns the resulting object that represents the given XML node
+ * or the object given to the method as the into parameter.
+ *
+ * Parameters:
+ *
+ * dec - <mxCodec> that controls the decoding process.
+ * node - XML node to be decoded.
+ * into - Optional objec to encode the node into.
+ */
+mxObjectCodec.prototype.decode = function(dec, node, into)
+{
+	var id = node.getAttribute('id');
+	var obj = dec.objects[id];
+	
+	if (obj == null)
+	{
+		obj = into || this.cloneTemplate();
+		
+		if (id != null)
+		{
+			dec.putObject(id, obj);
+		}
+	}
+	
+	node = this.beforeDecode(dec, node, obj);
+	this.decodeNode(dec, node, obj);
+	
+    return this.afterDecode(dec, node, obj);
+};	
+
+/**
+ * Function: decodeNode
+ * 
+ * Calls <decodeAttributes> and <decodeChildren> for the given node.
+ * 
+ * Parameters:
+ *
+ * dec - <mxCodec> that controls the decoding process.
+ * node - XML node to be decoded.
+ * obj - Objec to encode the node into.
+ */	
+mxObjectCodec.prototype.decodeNode = function(dec, node, obj)
+{
+	if (node != null)
+	{
+		this.decodeAttributes(dec, node, obj);
+		this.decodeChildren(dec, node, obj);
+	}
+};
+
+/**
+ * Function: decodeAttributes
+ * 
+ * Decodes all attributes of the given node using <decodeAttribute>.
+ * 
+ * Parameters:
+ *
+ * dec - <mxCodec> that controls the decoding process.
+ * node - XML node to be decoded.
+ * obj - Objec to encode the node into.
+ */	
+mxObjectCodec.prototype.decodeAttributes = function(dec, node, obj)
+{
+	var attrs = node.attributes;
+	
+	if (attrs != null)
+	{
+		for (var i = 0; i < attrs.length; i++)
+		{
+			this.decodeAttribute(dec, attrs[i], obj);
+		}
+	}
+};
+
+/**
+ * Function: isIgnoredAttribute
+ * 
+ * Returns true if the given attribute should be ignored. This implementation
+ * returns true if the attribute name is "as" or "id".
+ * 
+ * Parameters:
+ *
+ * dec - <mxCodec> that controls the decoding process.
+ * attr - XML attribute to be decoded.
+ * obj - Objec to encode the attribute into.
+ */	
+mxObjectCodec.prototype.isIgnoredAttribute = function(dec, attr, obj)
+{
+	return attr.nodeName == 'as' || attr.nodeName == 'id';
+};
+
+/**
+ * Function: decodeAttribute
+ * 
+ * Reads the given attribute into the specified object.
+ * 
+ * Parameters:
+ *
+ * dec - <mxCodec> that controls the decoding process.
+ * attr - XML attribute to be decoded.
+ * obj - Objec to encode the attribute into.
+ */	
+mxObjectCodec.prototype.decodeAttribute = function(dec, attr, obj)
+{
+	if (!this.isIgnoredAttribute(dec, attr, obj))
+	{
+		var name = attr.nodeName;
+		
+		// Converts the string true and false to their boolean values.
+		// This may require an additional check on the obj to see if
+		// the existing field is a boolean value or uninitialized, in
+		// which case we may want to convert true and false to a string.
+		var value = this.convertAttributeFromXml(dec, attr, obj);
+		var fieldname = this.getFieldName(name);
+		
+		if (this.isReference(obj, fieldname, value, false))
+		{
+			var tmp = dec.getObject(value);
+			
+			if (tmp == null)
+			{
+		    	mxLog.warn('mxObjectCodec.decode: No object for ' +
+		    		this.getName() + '.' + name + '=' + value);
+		    	return; // exit
+		    }
+		    
+		    value = tmp;
+		}
+
+		if (!this.isExcluded(obj, name, value, false))
+		{
+			//mxLog.debug(mxUtils.getFunctionName(obj.constructor)+'.'+name+'='+value);
+			obj[name] = value;
+		}
+	}
+};
+
+/**
+ * Function: decodeChildren
+ * 
+ * Decodes all children of the given node using <decodeChild>.
+ * 
+ * Parameters:
+ *
+ * dec - <mxCodec> that controls the decoding process.
+ * node - XML node to be decoded.
+ * obj - Objec to encode the node into.
+ */	
+mxObjectCodec.prototype.decodeChildren = function(dec, node, obj)
+{
+	var child = node.firstChild;
+	
+	while (child != null)
+	{
+		var tmp = child.nextSibling;
+		
+		if (child.nodeType == mxConstants.NODETYPE_ELEMENT &&
+			!this.processInclude(dec, child, obj))
+		{
+			this.decodeChild(dec, child, obj);
+		}
+		
+		child = tmp;
+	}
+};
+
+/**
+ * Function: decodeChild
+ * 
+ * Reads the specified child into the given object.
+ * 
+ * Parameters:
+ *
+ * dec - <mxCodec> that controls the decoding process.
+ * child - XML child element to be decoded.
+ * obj - Objec to encode the node into.
+ */	
+mxObjectCodec.prototype.decodeChild = function(dec, child, obj)
+{
+	var fieldname = this.getFieldName(child.getAttribute('as'));
+	
+	if (fieldname == null || !this.isExcluded(obj, fieldname, child, false))
+	{
+		var template = this.getFieldTemplate(obj, fieldname, child);
+		var value = null;
+		
+		if (child.nodeName == 'add')
+		{
+			value = child.getAttribute('value');
+			
+			if (value == null && mxObjectCodec.allowEval)
+			{
+				value = mxUtils.eval(mxUtils.getTextContent(child));
+			}
+		}
+		else
+		{
+			value = dec.decode(child, template);
+		}
+
+		this.addObjectValue(obj, fieldname, value, template);
+	}
+};
+
+/**
+ * Function: getFieldTemplate
+ * 
+ * Returns the template instance for the given field. This returns the
+ * value of the field, null if the value is an array or an empty collection
+ * if the value is a collection. The value is then used to populate the
+ * field for a new instance. For strongly typed languages it may be
+ * required to override this to return the correct collection instance
+ * based on the encoded child.
+ */	
+mxObjectCodec.prototype.getFieldTemplate = function(obj, fieldname, child)
+{
+	var template = obj[fieldname];
+	
+	// Non-empty arrays are replaced completely
+    if (template instanceof Array && template.length > 0)
+    {
+        template = null;
+    }
+    
+    return template;
+};
+
+/**
+ * Function: addObjectValue
+ * 
+ * Sets the decoded child node as a value of the given object. If the
+ * object is a map, then the value is added with the given fieldname as a
+ * key. If the fieldname is not empty, then setFieldValue is called or
+ * else, if the object is a collection, the value is added to the
+ * collection. For strongly typed languages it may be required to
+ * override this with the correct code to add an entry to an object.
+ */	
+mxObjectCodec.prototype.addObjectValue = function(obj, fieldname, value, template)
+{
+	if (value != null && value != template)
+	{
+		if (fieldname != null && fieldname.length > 0)
+		{
+			obj[fieldname] = value;
+		}
+		else
+		{
+			obj.push(value);
+		}
+		//mxLog.debug('Decoded '+mxUtils.getFunctionName(obj.constructor)+'.'+fieldname+': '+value);
+	}
+};
+
+/**
+ * Function: processInclude
+ *
+ * Returns true if the given node is an include directive and
+ * executes the include by decoding the XML document. Returns
+ * false if the given node is not an include directive.
+ *
+ * Parameters:
+ *
+ * dec - <mxCodec> that controls the encoding/decoding process.
+ * node - XML node to be checked.
+ * into - Optional object to pass-thru to the codec.
+ */
+mxObjectCodec.prototype.processInclude = function(dec, node, into)
+{
+	if (node.nodeName == 'include')
+	{
+		var name = node.getAttribute('name');
+		
+		if (name != null)
+		{
+			try
+			{
+				var xml = mxUtils.load(name).getDocumentElement();
+				
+				if (xml != null)
+				{
+					dec.decode(xml, into);
+				}
+			}
+			catch (e)
+			{
+				// ignore
+			}
+		}
+		
+		return true;
+	}
+	
+	return false;
+};
+
+/**
+ * Function: beforeDecode
+ *
+ * Hook for subclassers to pre-process the node for
+ * the specified object and return the node to be
+ * used for further processing by <decode>.
+ * The object is created based on the template in the 
+ * calling method and is never null. This implementation
+ * returns the input node. The return value of this
+ * function is used in <decode> to perform
+ * the default decoding into the given object.
+ *
+ * Parameters:
+ *
+ * dec - <mxCodec> that controls the decoding process.
+ * node - XML node to be decoded.
+ * obj - Object to encode the node into.
+ */
+mxObjectCodec.prototype.beforeDecode = function(dec, node, obj)
+{
+	return node;
+};
+
+/**
+ * Function: afterDecode
+ *
+ * Hook for subclassers to post-process the object after
+ * decoding. This implementation returns the given object
+ * without any changes. The return value of this method
+ * is returned to the decoder from <decode>.
+ *
+ * Parameters:
+ *
+ * enc - <mxCodec> that controls the encoding process.
+ * node - XML node to be decoded.
+ * obj - Object that represents the default decoding.
+ */
+mxObjectCodec.prototype.afterDecode = function(dec, node, obj)
+{
+	return obj;
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+mxCodecRegistry.register(function()
+{
+	/**
+	 * Class: mxCellCodec
+	 *
+	 * Codec for <mxCell>s. This class is created and registered
+	 * dynamically at load time and used implicitely via <mxCodec>
+	 * and the <mxCodecRegistry>.
+	 *
+	 * Transient Fields:
+	 *
+	 * - children
+	 * - edges
+	 * - overlays
+	 * - mxTransient
+	 *
+	 * Reference Fields:
+	 *
+	 * - parent
+	 * - source
+	 * - target
+	 * 
+	 * Transient fields can be added using the following code:
+	 * 
+	 * mxCodecRegistry.getCodec(mxCell).exclude.push('name_of_field');
+	 * 
+	 * To subclass <mxCell>, replace the template and add an alias as
+	 * follows.
+	 * 
+	 * (code)
+	 * function CustomCell(value, geometry, style)
+	 * {
+	 *   mxCell.apply(this, arguments);
+	 * }
+	 * 
+	 * mxUtils.extend(CustomCell, mxCell);
+	 * 
+	 * mxCodecRegistry.getCodec(mxCell).template = new CustomCell();
+	 * mxCodecRegistry.addAlias('CustomCell', 'mxCell');
+	 * (end)
+	 */
+	var codec = new mxObjectCodec(new mxCell(),
+		['children', 'edges', 'overlays', 'mxTransient'],
+		['parent', 'source', 'target']);
+
+	/**
+	 * Function: isCellCodec
+	 *
+	 * Returns true since this is a cell codec.
+	 */
+	codec.isCellCodec = function()
+	{
+		return true;
+	};
+
+	/**
+	 * Overidden to disable conversion of value to number.
+	 */
+	codec.isNumericAttribute = function(dec, attr, obj)
+	{
+		return attr.nodeName !== 'value' && mxObjectCodec.prototype.isNumericAttribute.apply(this, arguments);
+	};
+	
+	/**
+	 * Function: isExcluded
+	 *
+	 * Excludes user objects that are XML nodes.
+	 */ 
+	codec.isExcluded = function(obj, attr, value, isWrite)
+	{
+		return mxObjectCodec.prototype.isExcluded.apply(this, arguments) ||
+			(isWrite && attr == 'value' &&
+			value.nodeType == mxConstants.NODETYPE_ELEMENT);
+	};
+	
+	/**
+	 * Function: afterEncode
+	 *
+	 * Encodes an <mxCell> and wraps the XML up inside the
+	 * XML of the user object (inversion).
+	 */
+	codec.afterEncode = function(enc, obj, node)
+	{
+		if (obj.value != null && obj.value.nodeType == mxConstants.NODETYPE_ELEMENT)
+		{
+			// Wraps the graphical annotation up in the user object (inversion)
+			// by putting the result of the default encoding into a clone of the
+			// user object (node type 1) and returning this cloned user object.
+			var tmp = node;
+			node = mxUtils.importNode(enc.document, obj.value, true);
+			node.appendChild(tmp);
+			
+			// Moves the id attribute to the outermost XML node, namely the
+			// node which denotes the object boundaries in the file.
+			var id = tmp.getAttribute('id');
+			node.setAttribute('id', id);
+			tmp.removeAttribute('id');
+		}
+
+		return node;
+	};
+
+	/**
+	 * Function: beforeDecode
+	 *
+	 * Decodes an <mxCell> and uses the enclosing XML node as
+	 * the user object for the cell (inversion).
+	 */
+	codec.beforeDecode = function(dec, node, obj)
+	{
+		var inner = node.cloneNode(true);
+		var classname = this.getName();
+		
+		if (node.nodeName != classname)
+		{
+			// Passes the inner graphical annotation node to the
+			// object codec for further processing of the cell.
+			var tmp = node.getElementsByTagName(classname)[0];
+			
+			if (tmp != null && tmp.parentNode == node)
+			{
+				mxUtils.removeWhitespace(tmp, true);
+				mxUtils.removeWhitespace(tmp, false);
+				tmp.parentNode.removeChild(tmp);
+				inner = tmp;
+			}
+			else
+			{
+				inner = null;
+			}
+			
+			// Creates the user object out of the XML node
+			obj.value = node.cloneNode(true);
+			var id = obj.value.getAttribute('id');
+			
+			if (id != null)
+			{
+				obj.setId(id);
+				obj.value.removeAttribute('id');
+			}
+		}
+		else
+		{
+			// Uses ID from XML file as ID for cell in model
+			obj.setId(node.getAttribute('id'));
+		}
+			
+		// Preprocesses and removes all Id-references in order to use the
+		// correct encoder (this) for the known references to cells (all).
+		if (inner != null)
+		{
+			for (var i = 0; i < this.idrefs.length; i++)
+			{
+				var attr = this.idrefs[i];
+				var ref = inner.getAttribute(attr);
+				
+				if (ref != null)
+				{
+					inner.removeAttribute(attr);
+					var object = dec.objects[ref] || dec.lookup(ref);
+					
+					if (object == null)
+					{
+						// Needs to decode forward reference
+						var element = dec.getElementById(ref);
+						
+						if (element != null)
+						{
+							var decoder = mxCodecRegistry.codecs[element.nodeName] || this;
+							object = decoder.decode(dec, element);
+						}
+					}
+					
+					obj[attr] = object;
+				}
+			}
+		}
+		
+		return inner;
+	};
+
+	// Returns the codec into the registry
+	return codec;
+
+}());
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+mxCodecRegistry.register(function()
+{
+	/**
+	 * Class: mxModelCodec
+	 *
+	 * Codec for <mxGraphModel>s. This class is created and registered
+	 * dynamically at load time and used implicitely via <mxCodec>
+	 * and the <mxCodecRegistry>.
+	 */
+	var codec = new mxObjectCodec(new mxGraphModel());
+
+	/**
+	 * Function: encodeObject
+	 *
+	 * Encodes the given <mxGraphModel> by writing a (flat) XML sequence of
+	 * cell nodes as produced by the <mxCellCodec>. The sequence is
+	 * wrapped-up in a node with the name root.
+	 */
+	codec.encodeObject = function(enc, obj, node)
+	{
+		var rootNode = enc.document.createElement('root');
+		enc.encodeCell(obj.getRoot(), rootNode);
+		node.appendChild(rootNode);
+	};
+
+	/**
+	 * Function: decodeChild
+	 * 
+	 * Overrides decode child to handle special child nodes.
+	 */	
+	codec.decodeChild = function(dec, child, obj)
+	{
+		if (child.nodeName == 'root')
+		{
+			this.decodeRoot(dec, child, obj);
+		}
+		else
+		{
+			mxObjectCodec.prototype.decodeChild.apply(this, arguments);
+		}
+	};
+
+	/**
+	 * Function: decodeRoot
+	 *
+	 * Reads the cells into the graph model. All cells
+	 * are children of the root element in the node.
+	 */
+	codec.decodeRoot = function(dec, root, model)
+	{
+		var rootCell = null;
+		var tmp = root.firstChild;
+		
+		while (tmp != null)
+		{
+			var cell = dec.decodeCell(tmp);
+			
+			if (cell != null && cell.getParent() == null)
+			{
+				rootCell = cell;
+			}
+			
+			tmp = tmp.nextSibling;
+		}
+
+		// Sets the root on the model if one has been decoded
+		if (rootCell != null)
+		{
+			model.setRoot(rootCell);
+		}
+	};
+
+	// Returns the codec into the registry
+	return codec;
+
+}());
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+mxCodecRegistry.register(function()
+{
+	/**
+	 * Class: mxRootChangeCodec
+	 *
+	 * Codec for <mxRootChange>s. This class is created and registered
+	 * dynamically at load time and used implicitely via <mxCodec> and
+	 * the <mxCodecRegistry>.
+	 *
+	 * Transient Fields:
+	 *
+	 * - model
+	 * - previous
+	 * - root
+	 */
+	var codec = new mxObjectCodec(new mxRootChange(),
+		['model', 'previous', 'root']);
+
+	/**
+	 * Function: onEncode
+	 *
+	 * Encodes the child recursively.
+	 */
+	codec.afterEncode = function(enc, obj, node)
+	{
+		enc.encodeCell(obj.root, node);
+		
+		return node;
+	};
+
+	/**
+	 * Function: beforeDecode
+	 *
+	 * Decodes the optional children as cells
+	 * using the respective decoder.
+	 */
+	codec.beforeDecode = function(dec, node, obj)
+	{
+		if (node.firstChild != null &&
+			node.firstChild.nodeType == mxConstants.NODETYPE_ELEMENT)
+		{
+			// Makes sure the original node isn't modified
+			node = node.cloneNode(true);
+			
+			var tmp = node.firstChild;
+			obj.root = dec.decodeCell(tmp, false);
+
+			var tmp2 = tmp.nextSibling;
+			tmp.parentNode.removeChild(tmp);
+			tmp = tmp2;
+		
+			while (tmp != null)
+			{
+				tmp2 = tmp.nextSibling;
+				dec.decodeCell(tmp);
+				tmp.parentNode.removeChild(tmp);
+				tmp = tmp2;
+			}
+		}
+		
+		return node;
+	};
+	
+	/**
+	 * Function: afterDecode
+	 *
+	 * Restores the state by assigning the previous value.
+	 */
+	codec.afterDecode = function(dec, node, obj)
+	{
+		obj.previous = obj.root;
+		
+		return obj;
+	};
+
+	// Returns the codec into the registry
+	return codec;
+
+}());
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+mxCodecRegistry.register(function()
+{
+	/**
+	 * Class: mxChildChangeCodec
+	 *
+	 * Codec for <mxChildChange>s. This class is created and registered
+	 * dynamically at load time and used implicitely via <mxCodec> and
+	 * the <mxCodecRegistry>.
+	 *
+	 * Transient Fields:
+	 *
+	 * - model
+	 * - previous
+	 * - previousIndex
+	 * - child
+	 *
+	 * Reference Fields:
+	 *
+	 * - parent
+	 */
+	var codec = new mxObjectCodec(new mxChildChange(),
+		['model', 'child', 'previousIndex'],
+		['parent', 'previous']);
+
+	/**
+	 * Function: isReference
+	 *
+	 * Returns true for the child attribute if the child
+	 * cell had a previous parent or if we're reading the
+	 * child as an attribute rather than a child node, in
+	 * which case it's always a reference.
+	 */
+	codec.isReference = function(obj, attr, value, isWrite)
+	{
+		if (attr == 'child' &&
+			(obj.previous != null ||
+			!isWrite))
+		{
+			return true;
+		}
+		
+		return mxUtils.indexOf(this.idrefs, attr) >= 0;
+	};
+
+	/**
+	 * Function: afterEncode
+	 *
+	 * Encodes the child recusively and adds the result
+	 * to the given node.
+	 */
+	codec.afterEncode = function(enc, obj, node)
+	{
+		if (this.isReference(obj, 'child',  obj.child, true))
+		{
+			// Encodes as reference (id)
+			node.setAttribute('child', enc.getId(obj.child));
+		}
+		else
+		{
+			// At this point, the encoder is no longer able to know which cells
+			// are new, so we have to encode the complete cell hierarchy and
+			// ignore the ones that are already there at decoding time. Note:
+			// This can only be resolved by moving the notify event into the
+			// execute of the edit.
+			enc.encodeCell(obj.child, node);
+		}
+		
+		return node;
+	};
+
+	/**
+	 * Function: beforeDecode
+	 *
+	 * Decodes the any child nodes as using the respective
+	 * codec from the registry.
+	 */
+	codec.beforeDecode = function(dec, node, obj)
+	{
+		if (node.firstChild != null &&
+			node.firstChild.nodeType == mxConstants.NODETYPE_ELEMENT)
+		{
+			// Makes sure the original node isn't modified
+			node = node.cloneNode(true);
+			
+			var tmp = node.firstChild;
+			obj.child = dec.decodeCell(tmp, false);
+
+			var tmp2 = tmp.nextSibling;
+			tmp.parentNode.removeChild(tmp);
+			tmp = tmp2;
+			
+			while (tmp != null)
+			{
+				tmp2 = tmp.nextSibling;
+				
+				if (tmp.nodeType == mxConstants.NODETYPE_ELEMENT)
+				{
+					// Ignores all existing cells because those do not need to
+					// be re-inserted into the model. Since the encoded version
+					// of these cells contains the new parent, this would leave
+					// to an inconsistent state on the model (ie. a parent
+					// change without a call to parentForCellChanged).
+					var id = tmp.getAttribute('id');
+					
+					if (dec.lookup(id) == null)
+					{
+						dec.decodeCell(tmp);
+					}
+				}
+				
+				tmp.parentNode.removeChild(tmp);
+				tmp = tmp2;
+			}
+		}
+		else
+		{
+			var childRef = node.getAttribute('child');
+			obj.child = dec.getObject(childRef);
+		}
+		
+		return node;
+	};
+	
+	/**
+	 * Function: afterDecode
+	 *
+	 * Restores object state in the child change.
+	 */
+	codec.afterDecode = function(dec, node, obj)
+	{
+		// Cells are encoded here after a complete transaction so the previous
+		// parent must be restored on the cell for the case where the cell was
+		// added. This is needed for the local model to identify the cell as a
+		// new cell and register the ID.
+		obj.child.parent = obj.previous;
+		obj.previous = obj.parent;
+		obj.previousIndex = obj.index;
+
+		return obj;
+	};
+
+	// Returns the codec into the registry
+	return codec;
+
+}());
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+mxCodecRegistry.register(function()
+{
+	/**
+	 * Class: mxTerminalChangeCodec
+	 *
+	 * Codec for <mxTerminalChange>s. This class is created and registered
+	 * dynamically at load time and used implicitely via <mxCodec> and
+	 * the <mxCodecRegistry>.
+	 *
+	 * Transient Fields:
+	 *
+	 * - model
+	 * - previous
+	 *
+	 * Reference Fields:
+	 *
+	 * - cell
+	 * - terminal
+	 */
+	var codec = new mxObjectCodec(new mxTerminalChange(),
+		['model', 'previous'], ['cell', 'terminal']);
+
+	/**
+	 * Function: afterDecode
+	 *
+	 * Restores the state by assigning the previous value.
+	 */
+	codec.afterDecode = function(dec, node, obj)
+	{
+		obj.previous = obj.terminal;
+		
+		return obj;
+	};
+
+	// Returns the codec into the registry
+	return codec;
+
+}());
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxGenericChangeCodec
+ *
+ * Codec for <mxValueChange>s, <mxStyleChange>s, <mxGeometryChange>s,
+ * <mxCollapseChange>s and <mxVisibleChange>s. This class is created
+ * and registered dynamically at load time and used implicitely
+ * via <mxCodec> and the <mxCodecRegistry>.
+ *
+ * Transient Fields:
+ *
+ * - model
+ * - previous
+ *
+ * Reference Fields:
+ *
+ * - cell
+ * 
+ * Constructor: mxGenericChangeCodec
+ *
+ * Factory function that creates a <mxObjectCodec> for
+ * the specified change and fieldname.
+ *
+ * Parameters:
+ *
+ * obj - An instance of the change object.
+ * variable - The fieldname for the change data.
+ */
+var mxGenericChangeCodec = function(obj, variable)
+{
+	var codec = new mxObjectCodec(obj,  ['model', 'previous'], ['cell']);
+
+	/**
+	 * Function: afterDecode
+	 *
+	 * Restores the state by assigning the previous value.
+	 */
+	codec.afterDecode = function(dec, node, obj)
+	{
+		// Allows forward references in sessions. This is a workaround
+		// for the sequence of edits in mxGraph.moveCells and cellsAdded.
+		if (mxUtils.isNode(obj.cell))
+		{
+			obj.cell = dec.decodeCell(obj.cell, false);
+		}
+
+		obj.previous = obj[variable];
+
+		return obj;
+	};
+	
+	return codec;
+};
+
+// Registers the codecs
+mxCodecRegistry.register(mxGenericChangeCodec(new mxValueChange(), 'value'));
+mxCodecRegistry.register(mxGenericChangeCodec(new mxStyleChange(), 'style'));
+mxCodecRegistry.register(mxGenericChangeCodec(new mxGeometryChange(), 'geometry'));
+mxCodecRegistry.register(mxGenericChangeCodec(new mxCollapseChange(), 'collapsed'));
+mxCodecRegistry.register(mxGenericChangeCodec(new mxVisibleChange(), 'visible'));
+mxCodecRegistry.register(mxGenericChangeCodec(new mxCellAttributeChange(), 'value'));
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+mxCodecRegistry.register(function()
+{
+	/**
+	 * Class: mxGraphCodec
+	 *
+	 * Codec for <mxGraph>s. This class is created and registered
+	 * dynamically at load time and used implicitely via <mxCodec>
+	 * and the <mxCodecRegistry>.
+	 *
+	 * Transient Fields:
+	 *
+	 * - graphListeners
+	 * - eventListeners
+	 * - view
+	 * - container
+	 * - cellRenderer
+	 * - editor
+	 * - selection
+	 */
+	return new mxObjectCodec(new mxGraph(),
+		['graphListeners', 'eventListeners', 'view', 'container',
+		'cellRenderer', 'editor', 'selection']);
+
+}());
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+mxCodecRegistry.register(function()
+{
+	/**
+	 * Class: mxGraphViewCodec
+	 *
+	 * Custom encoder for <mxGraphView>s. This class is created
+	 * and registered dynamically at load time and used implicitely via
+	 * <mxCodec> and the <mxCodecRegistry>. This codec only writes views
+	 * into a XML format that can be used to create an image for
+	 * the graph, that is, it contains absolute coordinates with
+	 * computed perimeters, edge styles and cell styles.
+	 */
+	var codec = new mxObjectCodec(new mxGraphView());
+
+	/**
+	 * Function: encode
+	 *
+	 * Encodes the given <mxGraphView> using <encodeCell>
+	 * starting at the model's root. This returns the
+	 * top-level graph node of the recursive encoding.
+	 */
+	codec.encode = function(enc, view)
+	{
+		return this.encodeCell(enc, view,
+			view.graph.getModel().getRoot());
+	};
+
+	/**
+	 * Function: encodeCell
+	 *
+	 * Recursively encodes the specifed cell. Uses layer
+	 * as the default nodename. If the cell's parent is
+	 * null, then graph is used for the nodename. If
+	 * <mxGraphModel.isEdge> returns true for the cell,
+	 * then edge is used for the nodename, else if
+	 * <mxGraphModel.isVertex> returns true for the cell,
+	 * then vertex is used for the nodename.
+	 *
+	 * <mxGraph.getLabel> is used to create the label
+	 * attribute for the cell. For graph nodes and vertices
+	 * the bounds are encoded into x, y, width and height.
+	 * For edges the points are encoded into a points
+	 * attribute as a space-separated list of comma-separated
+	 * coordinate pairs (eg. x0,y0 x1,y1 ... xn,yn). All
+	 * values from the cell style are added as attribute
+	 * values to the node. 
+	 */
+	codec.encodeCell = function(enc, view, cell)
+	{
+		var model = view.graph.getModel();
+		var state = view.getState(cell);
+		var parent = model.getParent(cell);
+		
+		if (parent == null || state != null)
+		{
+			var childCount = model.getChildCount(cell);
+			var geo = view.graph.getCellGeometry(cell);
+			var name = null;
+			
+			if (parent == model.getRoot())
+			{
+				name = 'layer';
+			}
+			else if (parent == null)
+			{
+				name = 'graph';
+			}
+			else if (model.isEdge(cell))
+			{
+				name = 'edge';
+			}
+			else if (childCount > 0 && geo != null)
+			{
+				name = 'group';
+			}
+			else if (model.isVertex(cell))
+			{
+				name = 'vertex';
+			}
+			
+			if (name != null)
+			{
+				var node = enc.document.createElement(name);
+				var lab = view.graph.getLabel(cell);
+				
+				if (lab != null)
+				{
+					node.setAttribute('label', view.graph.getLabel(cell));
+					
+					if (view.graph.isHtmlLabel(cell))
+					{
+						node.setAttribute('html', true);
+					}
+				}
+		
+				if (parent == null)
+				{
+					var bounds = view.getGraphBounds();
+					
+					if (bounds != null)
+					{
+						node.setAttribute('x', Math.round(bounds.x));
+						node.setAttribute('y', Math.round(bounds.y));
+						node.setAttribute('width', Math.round(bounds.width));
+						node.setAttribute('height', Math.round(bounds.height));
+					}
+					
+					node.setAttribute('scale', view.scale);
+				}
+				else if (state != null && geo != null)
+				{
+					// Writes each key, value in the style pair to an attribute
+				    for (var i in state.style)
+				    {
+				    	var value = state.style[i];
+		
+				    	// Tries to turn objects and functions into strings
+					    if (typeof(value) == 'function' &&
+							typeof(value) == 'object')
+						{
+					    	value = mxStyleRegistry.getName(value);
+				        }
+				    	
+				    	if (value != null &&
+				    		typeof(value) != 'function' &&
+							typeof(value) != 'object')
+						{
+							node.setAttribute(i, value);
+				        }
+				    }
+				    
+					var abs = state.absolutePoints;
+					
+					// Writes the list of points into one attribute
+					if (abs != null && abs.length > 0)
+					{
+						var pts = Math.round(abs[0].x) + ',' + Math.round(abs[0].y);
+		
+						for (var i=1; i<abs.length; i++)
+						{
+							pts += ' ' + Math.round(abs[i].x) + ',' +
+								Math.round(abs[i].y);
+						}
+		
+						node.setAttribute('points', pts);
+					}
+					
+					// Writes the bounds into 4 attributes
+					else
+					{
+						node.setAttribute('x', Math.round(state.x));
+						node.setAttribute('y', Math.round(state.y));
+						node.setAttribute('width', Math.round(state.width));
+						node.setAttribute('height', Math.round(state.height));				
+					}
+		
+					var offset = state.absoluteOffset;
+					
+					// Writes the offset into 2 attributes
+					if (offset != null)
+					{
+						if (offset.x != 0)
+						{
+							node.setAttribute('dx', Math.round(offset.x));
+						}
+						
+						if (offset.y != 0)
+						{
+							node.setAttribute('dy', Math.round(offset.y));
+						}
+					}
+				}
+		
+				for (var i=0; i<childCount; i++)
+				{
+					var childNode = this.encodeCell(enc,
+							view, model.getChildAt(cell, i));
+					
+					if (childNode != null)
+					{
+						node.appendChild(childNode);
+					}
+				}
+			}
+		}
+		
+		return node;
+	};
+
+	// Returns the codec into the registry
+	return codec;
+
+}());
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxStylesheetCodec
+ *
+ * Codec for <mxStylesheet>s. This class is created and registered
+ * dynamically at load time and used implicitely via <mxCodec>
+ * and the <mxCodecRegistry>.
+ */
+var mxStylesheetCodec = mxCodecRegistry.register(function()
+{
+	var codec = new mxObjectCodec(new mxStylesheet());
+
+	/**
+	 * Function: encode
+	 *
+	 * Encodes a stylesheet. See <decode> for a description of the
+	 * format.
+	 */
+	codec.encode = function(enc, obj)
+	{
+		var node = enc.document.createElement(this.getName());
+		
+		for (var i in obj.styles)
+		{
+			var style = obj.styles[i];
+			var styleNode = enc.document.createElement('add');
+			
+			if (i != null)
+			{
+				styleNode.setAttribute('as', i);
+				
+				for (var j in style)
+				{
+					var value = this.getStringValue(j, style[j]);
+					
+					if (value != null)
+					{
+						var entry = enc.document.createElement('add');
+						entry.setAttribute('value', value);
+						entry.setAttribute('as', j);
+						styleNode.appendChild(entry);
+					}
+				}
+				
+				if (styleNode.childNodes.length > 0)
+				{
+					node.appendChild(styleNode);
+				}
+			}
+		}
+		
+	    return node;
+	};
+
+	/**
+	 * Function: getStringValue
+	 *
+	 * Returns the string for encoding the given value.
+	 */
+	codec.getStringValue = function(key, value)
+	{
+		var type = typeof(value);
+		
+		if (type == 'function')
+		{
+			value = mxStyleRegistry.getName(style[j]);
+		}
+		else if (type == 'object')
+		{
+			value = null;
+		}
+		
+		return value;
+	};
+	
+	/**
+	 * Function: decode
+	 *
+	 * Reads a sequence of the following child nodes
+	 * and attributes:
+	 *
+	 * Child Nodes:
+	 *
+	 * add - Adds a new style.
+	 *
+	 * Attributes:
+	 *
+	 * as - Name of the style.
+	 * extend - Name of the style to inherit from.
+	 *
+	 * Each node contains another sequence of add and remove nodes with the following
+	 * attributes:
+	 *
+	 * as - Name of the style (see <mxConstants>).
+	 * value - Value for the style.
+	 *
+	 * Instead of the value-attribute, one can put Javascript expressions into
+	 * the node as follows if <mxStylesheetCodec.allowEval> is true:
+	 * <add as="perimeter">mxPerimeter.RectanglePerimeter</add>
+	 *
+	 * A remove node will remove the entry with the name given in the as-attribute
+	 * from the style.
+	 * 
+	 * Example:
+	 *
+	 * (code)
+	 * <mxStylesheet as="stylesheet">
+	 *   <add as="text">
+	 *     <add as="fontSize" value="12"/>
+	 *   </add>
+	 *   <add as="defaultVertex" extend="text">
+	 *     <add as="shape" value="rectangle"/>
+	 *   </add>
+	 * </mxStylesheet>
+	 * (end)
+	 */
+	codec.decode = function(dec, node, into)
+	{
+		var obj = into || new this.template.constructor();
+		var id = node.getAttribute('id');
+		
+		if (id != null)
+		{
+			dec.objects[id] = obj;
+		}
+		
+		node = node.firstChild;
+		
+		while (node != null)
+		{
+			if (!this.processInclude(dec, node, obj) && node.nodeName == 'add')
+			{
+				var as = node.getAttribute('as');
+				
+				if (as != null)
+				{
+					var extend = node.getAttribute('extend');
+					var style = (extend != null) ? mxUtils.clone(obj.styles[extend]) : null;
+					
+					if (style == null)
+					{
+						if (extend != null)
+						{
+							mxLog.warn('mxStylesheetCodec.decode: stylesheet ' +
+								extend + ' not found to extend');
+						}
+						
+						style = new Object();
+					}
+					
+					var entry = node.firstChild;
+					
+					while (entry != null)
+					{
+						if (entry.nodeType == mxConstants.NODETYPE_ELEMENT)
+						{
+						 	var key = entry.getAttribute('as');
+						 	
+						 	if (entry.nodeName == 'add')
+						 	{
+							 	var text = mxUtils.getTextContent(entry);
+							 	var value = null;
+							 	
+							 	if (text != null && text.length > 0 && mxStylesheetCodec.allowEval)
+							 	{
+							 		value = mxUtils.eval(text);
+							 	}
+							 	else
+							 	{
+							 		value = entry.getAttribute('value');
+							 		
+							 		if (mxUtils.isNumeric(value))
+							 		{
+										value = parseFloat(value);
+									}
+							 	}
+
+							 	if (value != null)
+							 	{
+							 		style[key] = value;
+							 	}
+						 	}
+						 	else if (entry.nodeName == 'remove')
+						 	{
+						 		delete style[key];
+						 	}
+						}
+						
+						entry = entry.nextSibling;
+					}
+					
+					obj.putCellStyle(as, style);
+				}
+			}
+			
+			node = node.nextSibling;
+		}
+		
+		return obj;
+	};
+
+	// Returns the codec into the registry
+	return codec;
+
+}());
+
+/**
+ * Variable: allowEval
+ * 
+ * Static global switch that specifies if the use of eval is allowed for
+ * evaluating text content. Default is true. Set this to false if stylesheets
+ * may contain user input.
+ */
+mxStylesheetCodec.allowEval = true;
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+mxCodecRegistry.register(function()
+{
+	/**
+	 * Class: mxDefaultKeyHandlerCodec
+	 *
+	 * Custom codec for configuring <mxDefaultKeyHandler>s. This class is created
+	 * and registered dynamically at load time and used implicitely via
+	 * <mxCodec> and the <mxCodecRegistry>. This codec only reads configuration
+	 * data for existing key handlers, it does not encode or create key handlers.
+	 */
+	var codec = new mxObjectCodec(new mxDefaultKeyHandler());
+
+	/**
+	 * Function: encode
+	 *
+	 * Returns null.
+	 */
+	codec.encode = function(enc, obj)
+	{
+		return null;
+	};
+	
+	/**
+	 * Function: decode
+	 *
+	 * Reads a sequence of the following child nodes
+	 * and attributes:
+	 *
+	 * Child Nodes:
+	 *
+	 * add - Binds a keystroke to an actionname.
+	 *
+	 * Attributes:
+	 *
+	 * as - Keycode.
+	 * action - Actionname to execute in editor.
+	 * control - Optional boolean indicating if
+	 * 		the control key must be pressed.
+	 *
+	 * Example:
+	 *
+	 * (code)
+	 * <mxDefaultKeyHandler as="keyHandler">
+	 *   <add as="88" control="true" action="cut"/>
+	 *   <add as="67" control="true" action="copy"/>
+	 *   <add as="86" control="true" action="paste"/>
+	 * </mxDefaultKeyHandler>
+	 * (end)
+	 *
+	 * The keycodes are for the x, c and v keys.
+	 *
+	 * See also: <mxDefaultKeyHandler.bindAction>,
+	 * http://www.js-examples.com/page/tutorials__key_codes.html
+	 */
+	codec.decode = function(dec, node, into)
+	{
+		if (into != null)
+		{
+			var editor = into.editor;
+			node = node.firstChild;
+			
+			while (node != null)
+			{
+				if (!this.processInclude(dec, node, into) &&
+					node.nodeName == 'add')
+				{
+					var as = node.getAttribute('as');
+					var action = node.getAttribute('action');
+					var control = node.getAttribute('control');
+					
+					into.bindAction(as, action, control);
+				}
+				
+				node = node.nextSibling;
+			}
+		}
+		
+		return into;
+	};
+
+	// Returns the codec into the registry
+	return codec;
+
+}());
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxDefaultToolbarCodec
+ *
+ * Custom codec for configuring <mxDefaultToolbar>s. This class is created
+ * and registered dynamically at load time and used implicitely via
+ * <mxCodec> and the <mxCodecRegistry>. This codec only reads configuration
+ * data for existing toolbars handlers, it does not encode or create toolbars.
+ */
+var mxDefaultToolbarCodec = mxCodecRegistry.register(function()
+{
+	var codec = new mxObjectCodec(new mxDefaultToolbar());
+
+	/**
+	 * Function: encode
+	 *
+	 * Returns null.
+	 */
+	codec.encode = function(enc, obj)
+	{
+		return null;
+	};
+	
+	/**
+	 * Function: decode
+	 *
+	 * Reads a sequence of the following child nodes
+	 * and attributes:
+	 *
+	 * Child Nodes:
+	 *
+	 * add - Adds a new item to the toolbar. See below for attributes.
+	 * separator - Adds a vertical separator. No attributes.
+	 * hr - Adds a horizontal separator. No attributes.
+	 * br - Adds a linefeed. No attributes. 
+	 *
+	 * Attributes:
+	 *
+	 * as - Resource key for the label.
+	 * action - Name of the action to execute in enclosing editor.
+	 * mode - Modename (see below).
+	 * template - Template name for cell insertion.
+	 * style - Optional style to override the template style.
+	 * icon - Icon (relative/absolute URL).
+	 * pressedIcon - Optional icon for pressed state (relative/absolute URL).
+	 * id - Optional ID to be used for the created DOM element.
+	 * toggle - Optional 0 or 1 to disable toggling of the element. Default is
+	 * 1 (true).
+	 *
+	 * The action, mode and template attributes are mutually exclusive. The
+	 * style can only be used with the template attribute. The add node may
+	 * contain another sequence of add nodes with as and action attributes
+	 * to create a combo box in the toolbar. If the icon is specified then
+	 * a list of the child node is expected to have its template attribute
+	 * set and the action is ignored instead.
+	 * 
+	 * Nodes with a specified template may define a function to be used for
+	 * inserting the cloned template into the graph. Here is an example of such
+	 * a node:
+	 * 
+	 * (code)
+	 * <add as="Swimlane" template="swimlane" icon="images/swimlane.gif"><![CDATA[
+	 *   function (editor, cell, evt, targetCell)
+	 *   {
+	 *     var pt = mxUtils.convertPoint(
+	 *       editor.graph.container, mxEvent.getClientX(evt),
+	 *         mxEvent.getClientY(evt));
+	 *     return editor.addVertex(targetCell, cell, pt.x, pt.y);
+	 *   }
+	 * ]]></add>
+	 * (end)
+	 * 
+	 * In the above function, editor is the enclosing <mxEditor> instance, cell
+	 * is the clone of the template, evt is the mouse event that represents the
+	 * drop and targetCell is the cell under the mousepointer where the drop
+	 * occurred. The targetCell is retrieved using <mxGraph.getCellAt>.
+	 *
+	 * Futhermore, nodes with the mode attribute may define a function to
+	 * be executed upon selection of the respective toolbar icon. In the
+	 * example below, the default edge style is set when this specific
+	 * connect-mode is activated:
+	 *
+	 * (code)
+	 * <add as="connect" mode="connect"><![CDATA[
+	 *   function (editor)
+	 *   {
+	 *     if (editor.defaultEdge != null)
+	 *     {
+	 *       editor.defaultEdge.style = 'straightEdge';
+	 *     }
+	 *   }
+	 * ]]></add>
+	 * (end)
+	 * 
+	 * Both functions require <mxDefaultToolbarCodec.allowEval> to be set to true.
+	 *
+	 * Modes:
+	 *
+	 * select - Left mouse button used for rubberband- & cell-selection.
+	 * connect - Allows connecting vertices by inserting new edges.
+	 * pan - Disables selection and switches to panning on the left button.
+	 *
+	 * Example:
+	 *
+	 * To add items to the toolbar:
+	 * 
+	 * (code)
+	 * <mxDefaultToolbar as="toolbar">
+	 *   <add as="save" action="save" icon="images/save.gif"/>
+	 *   <br/><hr/>
+	 *   <add as="select" mode="select" icon="images/select.gif"/>
+	 *   <add as="connect" mode="connect" icon="images/connect.gif"/>
+	 * </mxDefaultToolbar>
+	 * (end)
+	 */
+	codec.decode = function(dec, node, into)
+	{
+		if (into != null)
+		{
+			var editor = into.editor;
+			node = node.firstChild;
+			
+			while (node != null)
+			{
+				if (node.nodeType == mxConstants.NODETYPE_ELEMENT)
+				{
+					if (!this.processInclude(dec, node, into))
+					{
+						if (node.nodeName == 'separator')
+						{
+							into.addSeparator();
+						}
+						else if (node.nodeName == 'br')
+						{
+							into.toolbar.addBreak();
+						}
+						else if (node.nodeName == 'hr')
+						{
+							into.toolbar.addLine();
+						}
+						else if (node.nodeName == 'add')
+						{
+							var as = node.getAttribute('as');
+							as = mxResources.get(as) || as;
+							var icon = node.getAttribute('icon');
+							var pressedIcon = node.getAttribute('pressedIcon');
+							var action = node.getAttribute('action');
+							var mode = node.getAttribute('mode');
+							var template = node.getAttribute('template');
+							var toggle = node.getAttribute('toggle') != '0';
+							var text = mxUtils.getTextContent(node);
+							var elt = null;
+
+							if (action != null)
+							{
+								elt = into.addItem(as, icon, action, pressedIcon);
+							}
+							else if (mode != null)
+							{
+								var funct = (mxDefaultToolbarCodec.allowEval) ? mxUtils.eval(text) : null;
+								elt = into.addMode(as, icon, mode, pressedIcon, funct);
+							}
+							else if (template != null || (text != null && text.length > 0))
+							{
+								var cell = editor.templates[template];
+								var style = node.getAttribute('style');
+								
+								if (cell != null && style != null)
+								{
+									cell = editor.graph.cloneCells([cell])[0];
+									cell.setStyle(style);
+								}
+								
+								var insertFunction = null;
+								
+								if (text != null && text.length > 0 && mxDefaultToolbarCodec.allowEval)
+								{
+									insertFunction = mxUtils.eval(text);
+								}
+								
+								elt = into.addPrototype(as, icon, cell, pressedIcon, insertFunction, toggle);
+							}
+							else
+							{
+								var children = mxUtils.getChildNodes(node);
+								
+								if (children.length > 0)
+								{
+									if (icon == null)
+									{
+										var combo = into.addActionCombo(as);
+										
+										for (var i=0; i<children.length; i++)
+										{
+											var child = children[i];
+											
+											if (child.nodeName == 'separator')
+											{
+												into.addOption(combo, '---');
+											}
+											else if (child.nodeName == 'add')
+											{
+												var lab = child.getAttribute('as');
+												var act = child.getAttribute('action');
+												into.addActionOption(combo, lab, act);
+											}
+										}
+									}
+									else
+									{
+										var select = null;
+										var create = function()
+										{
+											var template = editor.templates[select.value];
+											
+											if (template != null)
+											{
+												var clone = template.clone();
+												var style = select.options[select.selectedIndex].cellStyle;
+												
+												if (style != null)
+												{
+													clone.setStyle(style);
+												}
+												
+												return clone;
+											}
+											else
+											{
+												mxLog.warn('Template '+template+' not found');
+											}
+											
+											return null;
+										};
+										
+										var img = into.addPrototype(as, icon, create, null, null, toggle);
+										select = into.addCombo();
+										
+										// Selects the toolbar icon if a selection change
+										// is made in the corresponding combobox.
+										mxEvent.addListener(select, 'change', function()
+										{
+											into.toolbar.selectMode(img, function(evt)
+											{
+												var pt = mxUtils.convertPoint(editor.graph.container,
+													mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+												
+												return editor.addVertex(null, funct(), pt.x, pt.y);
+											});
+											
+											into.toolbar.noReset = false;
+										});
+										
+										// Adds the entries to the combobox
+										for (var i=0; i<children.length; i++)
+										{
+											var child = children[i];
+											
+											if (child.nodeName == 'separator')
+											{
+												into.addOption(select, '---');
+											}
+											else if (child.nodeName == 'add')
+											{
+												var lab = child.getAttribute('as');
+												var tmp = child.getAttribute('template');
+												var option = into.addOption(select, lab, tmp || template);
+												option.cellStyle = child.getAttribute('style');
+											}
+										}
+										
+									}
+								}
+							}
+							
+							// Assigns an ID to the created element to access it later.
+							if (elt != null)
+							{
+								var id = node.getAttribute('id');
+								
+								if (id != null && id.length > 0)
+								{
+									elt.setAttribute('id', id);
+								}
+							}
+						}
+					}
+				}
+				
+				node = node.nextSibling;
+			}
+		}
+		
+		return into;
+	};
+	
+	// Returns the codec into the registry
+	return codec;
+
+}());
+
+/**
+ * Variable: allowEval
+ * 
+ * Static global switch that specifies if the use of eval is allowed for
+ * evaluating text content. Default is true. Set this to false if stylesheets
+ * may contain user input
+ */
+mxDefaultToolbarCodec.allowEval = true;
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+mxCodecRegistry.register(function()
+{
+	/**
+	 * Class: mxDefaultPopupMenuCodec
+	 *
+	 * Custom codec for configuring <mxDefaultPopupMenu>s. This class is created
+	 * and registered dynamically at load time and used implicitely via
+	 * <mxCodec> and the <mxCodecRegistry>. This codec only reads configuration
+	 * data for existing popup menus, it does not encode or create menus. Note
+	 * that this codec only passes the configuration node to the popup menu,
+	 * which uses the config to dynamically create menus. See
+	 * <mxDefaultPopupMenu.createMenu>.
+	 */
+	var codec = new mxObjectCodec(new mxDefaultPopupMenu());
+
+	/**
+	 * Function: encode
+	 *
+	 * Returns null.
+	 */
+	codec.encode = function(enc, obj)
+	{
+		return null;
+	};
+	
+	/**
+	 * Function: decode
+	 *
+	 * Uses the given node as the config for <mxDefaultPopupMenu>.
+	 */
+	codec.decode = function(dec, node, into)
+	{
+		var inc = node.getElementsByTagName('include')[0];
+		
+		if (inc != null)
+		{
+			this.processInclude(dec, inc, into);
+		}
+		else if (into != null)
+		{
+			into.config = node;
+		}
+		
+		return into;
+	};
+	
+	// Returns the codec into the registry
+	return codec;
+
+}());
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+mxCodecRegistry.register(function()
+{
+	/**
+	 * Class: mxEditorCodec
+	 *
+	 * Codec for <mxEditor>s. This class is created and registered
+	 * dynamically at load time and used implicitely via <mxCodec>
+	 * and the <mxCodecRegistry>.
+	 *
+	 * Transient Fields:
+	 *
+	 * - modified
+	 * - lastSnapshot
+	 * - ignoredChanges
+	 * - undoManager
+	 * - graphContainer
+	 * - toolbarContainer
+	 */
+	var codec = new mxObjectCodec(new mxEditor(),
+		['modified', 'lastSnapshot', 'ignoredChanges',
+		'undoManager', 'graphContainer', 'toolbarContainer']);
+
+	/**
+	 * Function: beforeDecode
+	 *
+	 * Decodes the ui-part of the configuration node by reading
+	 * a sequence of the following child nodes and attributes
+	 * and passes the control to the default decoding mechanism:
+	 *
+	 * Child Nodes:
+	 *
+	 * stylesheet - Adds a CSS stylesheet to the document.
+	 * resource - Adds the basename of a resource bundle.
+	 * add - Creates or configures a known UI element.
+	 *
+	 * These elements may appear in any order given that the
+	 * graph UI element is added before the toolbar element
+	 * (see Known Keys).
+	 *
+	 * Attributes:
+	 *
+	 * as - Key for the UI element (see below).
+	 * element - ID for the element in the document.
+	 * style - CSS style to be used for the element or window.
+	 * x - X coordinate for the new window.
+	 * y - Y coordinate for the new window.
+	 * width - Width for the new window.
+	 * height - Optional height for the new window.
+	 * name - Name of the stylesheet (absolute/relative URL).
+	 * basename - Basename of the resource bundle (see <mxResources>).
+	 *
+	 * The x, y, width and height attributes are used to create a new
+	 * <mxWindow> if the element attribute is not specified in an add
+	 * node. The name and basename are only used in the stylesheet and
+	 * resource nodes, respectively.
+	 *
+	 * Known Keys:
+	 *
+	 * graph - Main graph element (see <mxEditor.setGraphContainer>).
+	 * title - Title element (see <mxEditor.setTitleContainer>).
+	 * toolbar - Toolbar element (see <mxEditor.setToolbarContainer>).
+	 * status - Status bar element (see <mxEditor.setStatusContainer>).
+	 *
+	 * Example:
+	 *
+	 * (code)
+	 * <ui>
+	 *   <stylesheet name="css/process.css"/>
+	 *   <resource basename="resources/app"/>
+	 *   <add as="graph" element="graph"
+	 *     style="left:70px;right:20px;top:20px;bottom:40px"/>
+	 *   <add as="status" element="status"/>
+	 *   <add as="toolbar" x="10" y="20" width="54"/>
+	 * </ui>
+	 * (end)
+	 */
+	codec.afterDecode = function(dec, node, obj)
+	{
+		// Assigns the specified templates for edges
+		var defaultEdge = node.getAttribute('defaultEdge');
+		
+		if (defaultEdge != null)
+		{
+			node.removeAttribute('defaultEdge');
+			obj.defaultEdge = obj.templates[defaultEdge];
+		}
+
+		// Assigns the specified templates for groups
+		var defaultGroup = node.getAttribute('defaultGroup');
+		
+		if (defaultGroup != null)
+		{
+			node.removeAttribute('defaultGroup');
+			obj.defaultGroup = obj.templates[defaultGroup];
+		}
+
+		return obj;
+	};
+	
+	/**
+	 * Function: decodeChild
+	 * 
+	 * Overrides decode child to handle special child nodes.
+	 */	
+	codec.decodeChild = function(dec, child, obj)
+	{
+		if (child.nodeName == 'Array')
+		{
+			var role = child.getAttribute('as');
+			
+			if (role == 'templates')
+			{
+				this.decodeTemplates(dec, child, obj);
+				return;
+			}
+		}
+		else if (child.nodeName == 'ui')
+		{
+			this.decodeUi(dec, child, obj);
+			return;
+		}
+		
+		mxObjectCodec.prototype.decodeChild.apply(this, arguments);
+	};
+		
+	/**
+	 * Function: decodeTemplates
+	 *
+	 * Decodes the cells from the given node as templates.
+	 */
+	codec.decodeUi = function(dec, node, editor)
+	{
+		var tmp = node.firstChild;
+		while (tmp != null)
+		{
+			if (tmp.nodeName == 'add')
+			{
+				var as = tmp.getAttribute('as');
+				var elt = tmp.getAttribute('element');
+				var style = tmp.getAttribute('style');
+				var element = null;
+
+				if (elt != null)
+				{
+					element = document.getElementById(elt);
+					
+					if (element != null && style != null)
+					{
+						element.style.cssText += ';' + style;
+					}
+				}
+				else
+				{
+					var x = parseInt(tmp.getAttribute('x'));
+					var y = parseInt(tmp.getAttribute('y'));
+					var width = tmp.getAttribute('width');
+					var height = tmp.getAttribute('height');
+
+					// Creates a new window around the element
+					element = document.createElement('div');
+					element.style.cssText = style;
+					
+					var wnd = new mxWindow(mxResources.get(as) || as,
+						element, x, y, width, height, false, true);
+					wnd.setVisible(true);
+				}
+				
+				// TODO: Make more generic
+				if (as == 'graph')
+				{
+					editor.setGraphContainer(element);
+				}
+				else if (as == 'toolbar')
+				{
+					editor.setToolbarContainer(element);
+				}
+				else if (as == 'title')
+				{
+					editor.setTitleContainer(element);
+				}
+				else if (as == 'status')
+				{
+					editor.setStatusContainer(element);
+				}
+				else if (as == 'map')
+				{
+					editor.setMapContainer(element);
+				}
+			}
+			else if (tmp.nodeName == 'resource')
+			{
+				mxResources.add(tmp.getAttribute('basename'));
+			}
+			else if (tmp.nodeName == 'stylesheet')
+			{
+				mxClient.link('stylesheet', tmp.getAttribute('name'));
+			}
+			
+			tmp = tmp.nextSibling;
+		}	
+	};
+	
+	/**
+	 * Function: decodeTemplates
+	 *
+	 * Decodes the cells from the given node as templates.
+	 */
+	codec.decodeTemplates = function(dec, node, editor)
+	{
+		if (editor.templates == null)
+		{
+			editor.templates = [];
+		}
+		
+		var children = mxUtils.getChildNodes(node);
+		for (var j=0; j<children.length; j++)
+		{
+			var name = children[j].getAttribute('as');
+			var child = children[j].firstChild;
+			
+			while (child != null && child.nodeType != 1)
+			{
+				child = child.nextSibling;
+			}
+			
+			if (child != null)
+			{
+				// LATER: Only single cells means you need
+				// to group multiple cells within another
+				// cell. This should be changed to support
+				// arrays of cells, or the wrapper must
+				// be automatically handled in this class.
+				editor.templates[name] = dec.decodeCell(child);
+			}
+		}
+	};
+	
+	// Returns the codec into the registry
+	return codec;
+
+}());
diff --git a/airavata-kubernetes/workflow-composer/mxClient.min.js b/airavata-kubernetes/workflow-composer/mxClient.min.js
new file mode 100644
index 0000000..84da810
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/mxClient.min.js
@@ -0,0 +1,1797 @@
+var mxClient={VERSION:"3.7.5",IS_IE:0<=navigator.userAgent.indexOf("MSIE"),IS_IE6:0<=navigator.userAgent.indexOf("MSIE 6"),IS_IE11:!!navigator.userAgent.match(/Trident\/7\./),IS_EDGE:!!navigator.userAgent.match(/Edge\//),IS_QUIRKS:0<=navigator.userAgent.indexOf("MSIE")&&(null==document.documentMode||5==document.documentMode),IS_EM:"spellcheck"in document.createElement("textarea")&&8==document.documentMode,VML_PREFIX:"v",OFFICE_PREFIX:"o",IS_NS:0<=navigator.userAgent.indexOf("Mozilla/")&& [...]
+0>navigator.userAgent.indexOf("Edge/"),IS_OP:0<=navigator.userAgent.indexOf("Opera/")||0<=navigator.userAgent.indexOf("OPR/"),IS_OT:0<=navigator.userAgent.indexOf("Presto/")&&0>navigator.userAgent.indexOf("Presto/2.4.")&&0>navigator.userAgent.indexOf("Presto/2.3.")&&0>navigator.userAgent.indexOf("Presto/2.2.")&&0>navigator.userAgent.indexOf("Presto/2.1.")&&0>navigator.userAgent.indexOf("Presto/2.0.")&&0>navigator.userAgent.indexOf("Presto/1."),IS_SF:0<=navigator.userAgent.indexOf("AppleW [...]
+0>navigator.userAgent.indexOf("Chrome/")&&0>navigator.userAgent.indexOf("Edge/"),IS_IOS:navigator.userAgent.match(/(iPad|iPhone|iPod)/g)?!0:!1,IS_GC:0<=navigator.userAgent.indexOf("Chrome/")&&0>navigator.userAgent.indexOf("Edge/"),IS_CHROMEAPP:null!=window.chrome&&null!=chrome.app&&null!=chrome.app.runtime,IS_FF:0<=navigator.userAgent.indexOf("Firefox/"),IS_MT:0<=navigator.userAgent.indexOf("Firefox/")&&0>navigator.userAgent.indexOf("Firefox/1.")&&0>navigator.userAgent.indexOf("Firefox/2 [...]
+0>navigator.userAgent.indexOf("Iceweasel/1.")&&0>navigator.userAgent.indexOf("Iceweasel/2.")||0<=navigator.userAgent.indexOf("SeaMonkey/")&&0>navigator.userAgent.indexOf("SeaMonkey/1.")||0<=navigator.userAgent.indexOf("Iceape/")&&0>navigator.userAgent.indexOf("Iceape/1."),IS_SVG:0<=navigator.userAgent.indexOf("Firefox/")||0<=navigator.userAgent.indexOf("Iceweasel/")||0<=navigator.userAgent.indexOf("Seamonkey/")||0<=navigator.userAgent.indexOf("Iceape/")||0<=navigator.userAgent.indexOf("G [...]
+0<=navigator.userAgent.indexOf("Epiphany/")||0<=navigator.userAgent.indexOf("AppleWebKit/")||0<=navigator.userAgent.indexOf("Gecko/")||0<=navigator.userAgent.indexOf("Opera/")||null!=document.documentMode&&9<=document.documentMode,NO_FO:!document.createElementNS||"[object SVGForeignObjectElement]"!=document.createElementNS("http://www.w3.org/2000/svg","foreignObject")||0<=navigator.userAgent.indexOf("Opera/"),IS_VML:"MICROSOFT INTERNET EXPLORER"==navigator.appName.toUpperCase(),IS_WIN:0< [...]
+IS_MAC:0<navigator.appVersion.indexOf("Mac"),IS_TOUCH:"ontouchstart"in document.documentElement,IS_POINTER:null!=window.PointerEvent&&!(0<navigator.appVersion.indexOf("Mac")),IS_LOCAL:0>document.location.href.indexOf("http://")&&0>document.location.href.indexOf("https://"),isBrowserSupported:function(){return mxClient.IS_VML||mxClient.IS_SVG},link:function(a,b,c){c=c||document;if(mxClient.IS_IE6)c.write('<link rel="'+a+'" href="'+b+'" charset="UTF-8" type="text/css"/>');else{var d=c.crea [...]
+d.setAttribute("rel",a);d.setAttribute("href",b);d.setAttribute("charset","UTF-8");d.setAttribute("type","text/css");c.getElementsByTagName("head")[0].appendChild(d)}},include:function(a){document.write('<script src="'+a+'">\x3c/script>')},dispose:function(){for(var a=0;a<mxEvent.objects.length;a++)null!=mxEvent.objects[a].mxListenerList&&mxEvent.removeAllListeners(mxEvent.objects[a])}};"undefined"==typeof mxLoadResources&&(mxLoadResources=!0);
+"undefined"==typeof mxForceIncludes&&(mxForceIncludes=!1);"undefined"==typeof mxResourceExtension&&(mxResourceExtension=".txt");"undefined"==typeof mxLoadStylesheets&&(mxLoadStylesheets=!0);"undefined"!=typeof mxBasePath&&0<mxBasePath.length?("/"==mxBasePath.substring(mxBasePath.length-1)&&(mxBasePath=mxBasePath.substring(0,mxBasePath.length-1)),mxClient.basePath=mxBasePath):mxClient.basePath=".";
+"undefined"!=typeof mxImageBasePath&&0<mxImageBasePath.length?("/"==mxImageBasePath.substring(mxImageBasePath.length-1)&&(mxImageBasePath=mxImageBasePath.substring(0,mxImageBasePath.length-1)),mxClient.imageBasePath=mxImageBasePath):mxClient.imageBasePath=mxClient.basePath+"/images";mxClient.language="undefined"!=typeof mxLanguage&&null!=mxLanguage?mxLanguage:mxClient.IS_IE?navigator.userLanguage:navigator.language;
+mxClient.defaultLanguage="undefined"!=typeof mxDefaultLanguage&&null!=mxDefaultLanguage?mxDefaultLanguage:"en";mxLoadStylesheets&&mxClient.link("stylesheet",mxClient.basePath+"/css/common.css");"undefined"!=typeof mxLanguages&&null!=mxLanguages&&(mxClient.languages=mxLanguages);
+mxClient.IS_VML&&(mxClient.IS_SVG?mxClient.IS_VML=!1:(8==document.documentMode?(document.namespaces.add(mxClient.VML_PREFIX,"urn:schemas-microsoft-com:vml","#default#VML"),document.namespaces.add(mxClient.OFFICE_PREFIX,"urn:schemas-microsoft-com:office:office","#default#VML")):(document.namespaces.add(mxClient.VML_PREFIX,"urn:schemas-microsoft-com:vml"),document.namespaces.add(mxClient.OFFICE_PREFIX,"urn:schemas-microsoft-com:office:office")),mxClient.IS_QUIRKS&&30<=document.styleSheets. [...]
+document.createElement("style");a.type="text/css";a.styleSheet.cssText=mxClient.VML_PREFIX+"\\:*{behavior:url(#default#VML)}"+mxClient.OFFICE_PREFIX+"\\:*{behavior:url(#default#VML)}";document.getElementsByTagName("head")[0].appendChild(a)}():document.createStyleSheet().cssText=mxClient.VML_PREFIX+"\\:*{behavior:url(#default#VML)}"+mxClient.OFFICE_PREFIX+"\\:*{behavior:url(#default#VML)}",mxLoadStylesheets&&mxClient.link("stylesheet",mxClient.basePath+"/css/explorer.css"),window.attachEv [...]
+mxClient.dispose)));
+var mxLog={consoleName:"Console",TRACE:!1,DEBUG:!0,WARN:!0,buffer:"",init:function(){if(null==mxLog.window&&null!=document.body){var a=mxLog.consoleName+" - mxGraph "+mxClient.VERSION,b=document.createElement("table");b.setAttribute("width","100%");b.setAttribute("height","100%");var c=document.createElement("tbody"),d=document.createElement("tr"),e=document.createElement("td");e.style.verticalAlign="top";mxLog.textarea=document.createElement("textarea");mxLog.textarea.setAttribute("wrap [...]
+mxLog.textarea.setAttribute("readOnly","true");mxLog.textarea.style.height="100%";mxLog.textarea.style.resize="none";mxLog.textarea.value=mxLog.buffer;mxLog.textarea.style.width=mxClient.IS_NS&&"BackCompat"!=document.compatMode?"99%":"100%";e.appendChild(mxLog.textarea);d.appendChild(e);c.appendChild(d);d=document.createElement("tr");mxLog.td=document.createElement("td");mxLog.td.style.verticalAlign="top";mxLog.td.setAttribute("height","30px");d.appendChild(mxLog.td);c.appendChild(d);b.a [...]
+mxLog.addButton("Info",function(a){mxLog.info()});mxLog.addButton("DOM",function(a){a=mxUtils.getInnerHtml(document.body);mxLog.debug(a)});mxLog.addButton("Trace",function(a){mxLog.TRACE=!mxLog.TRACE;mxLog.TRACE?mxLog.debug("Tracing enabled"):mxLog.debug("Tracing disabled")});mxLog.addButton("Copy",function(a){try{mxUtils.copy(mxLog.textarea.value)}catch(k){mxUtils.alert(k)}});mxLog.addButton("Show",function(a){try{mxUtils.popup(mxLog.textarea.value)}catch(k){mxUtils.alert(k)}});mxLog.ad [...]
+function(a){mxLog.textarea.value=""});d=c=0;"number"===typeof window.innerWidth?(c=window.innerHeight,d=window.innerWidth):(c=document.documentElement.clientHeight||document.body.clientHeight,d=document.body.clientWidth);mxLog.window=new mxWindow(a,b,Math.max(0,d-320),Math.max(0,c-210),300,160);mxLog.window.setMaximizable(!0);mxLog.window.setScrollable(!1);mxLog.window.setResizable(!0);mxLog.window.setClosable(!0);mxLog.window.destroyOnClose=!1;if((mxClient.IS_NS||mxClient.IS_IE)&&!mxCli [...]
+!mxClient.IS_SF&&"BackCompat"!=document.compatMode||11==document.documentMode){var f=mxLog.window.getElement(),a=function(a,b){mxLog.textarea.style.height=Math.max(0,f.offsetHeight-70)+"px"};mxLog.window.addListener(mxEvent.RESIZE_END,a);mxLog.window.addListener(mxEvent.MAXIMIZE,a);mxLog.window.addListener(mxEvent.NORMALIZE,a);mxLog.textarea.style.height="92px"}}},info:function(){mxLog.writeln(mxUtils.toString(navigator))},addButton:function(a,b){var c=document.createElement("button");mx [...]
+a);mxEvent.addListener(c,"click",b);mxLog.td.appendChild(c)},isVisible:function(){return null!=mxLog.window?mxLog.window.isVisible():!1},show:function(){mxLog.setVisible(!0)},setVisible:function(a){null==mxLog.window&&mxLog.init();null!=mxLog.window&&mxLog.window.setVisible(a)},enter:function(a){if(mxLog.TRACE)return mxLog.writeln("Entering "+a),(new Date).getTime()},leave:function(a,b){if(mxLog.TRACE){var c=0!=b?" ("+((new Date).getTime()-b)+" ms)":"";mxLog.writeln("Leaving "+a+c)}},deb [...]
+mxLog.writeln.apply(this,arguments)},warn:function(){mxLog.WARN&&mxLog.writeln.apply(this,arguments)},write:function(){for(var a="",b=0;b<arguments.length;b++)a+=arguments[b],b<arguments.length-1&&(a+=" ");null!=mxLog.textarea?(mxLog.textarea.value+=a,0<=navigator.userAgent.indexOf("Presto/2.5")&&(mxLog.textarea.style.visibility="hidden",mxLog.textarea.style.visibility="visible"),mxLog.textarea.scrollTop=mxLog.textarea.scrollHeight):mxLog.buffer+=a},writeln:function(){for(var a="",b=0;b< [...]
+arguments[b],b<arguments.length-1&&(a+=" ");mxLog.write(a+"\n")}},mxObjectIdentity={FIELD_NAME:"mxObjectId",counter:0,get:function(a){if(null!=a){if(null==a[mxObjectIdentity.FIELD_NAME])if("object"===typeof a){var b=mxUtils.getFunctionName(a.constructor);a[mxObjectIdentity.FIELD_NAME]=b+"#"+mxObjectIdentity.counter++}else"function"===typeof a&&(a[mxObjectIdentity.FIELD_NAME]="Function#"+mxObjectIdentity.counter++);return a[mxObjectIdentity.FIELD_NAME]}return null},clear:function(a){"obje [...]
+"function"!==typeof a||delete a[mxObjectIdentity.FIELD_NAME]}};function mxDictionary(){this.clear()}mxDictionary.prototype.map=null;mxDictionary.prototype.clear=function(){this.map={}};mxDictionary.prototype.get=function(a){a=mxObjectIdentity.get(a);return this.map[a]};mxDictionary.prototype.put=function(a,b){var c=mxObjectIdentity.get(a),d=this.map[c];this.map[c]=b;return d};mxDictionary.prototype.remove=function(a){a=mxObjectIdentity.get(a);var b=this.map[a];delete this.map[a];return b};
+mxDictionary.prototype.getKeys=function(){var a=[],b;for(b in this.map)a.push(b);return a};mxDictionary.prototype.getValues=function(){var a=[],b;for(b in this.map)a.push(this.map[b]);return a};mxDictionary.prototype.visit=function(a){for(var b in this.map)a(b,this.map[b])};
+var mxResources={resources:[],extension:mxResourceExtension,resourcesEncoded:!1,loadDefaultBundle:!0,loadSpecialBundle:!0,isLanguageSupported:function(a){return null!=mxClient.languages?0<=mxUtils.indexOf(mxClient.languages,a):!0},getDefaultBundle:function(a,b){return mxResources.loadDefaultBundle||!mxResources.isLanguageSupported(b)?a+mxResources.extension:null},getSpecialBundle:function(a,b){if(null==mxClient.languages||!this.isLanguageSupported(b)){var c=b.indexOf("-");0<c&&(b=b.subst [...]
+mxResources.isLanguageSupported(b)&&b!=mxClient.defaultLanguage?a+"_"+b+mxResources.extension:null},add:function(a,b,c){b=null!=b?b:null!=mxClient.language?mxClient.language.toLowerCase():mxConstants.NONE;if(b!=mxConstants.NONE){var d=mxResources.getDefaultBundle(a,b),e=mxResources.getSpecialBundle(a,b),f=function(){if(null!=e)if(c)mxUtils.get(e,function(a){mxResources.parse(a.getText());c()},function(){c()});else try{var a=mxUtils.load(e);a.isReady()&&mxResources.parse(a.getText())}catc [...]
+c&&c()};if(null!=d)if(c)mxUtils.get(d,function(a){mxResources.parse(a.getText());f()},function(){f()});else try{var g=mxUtils.load(d);g.isReady()&&mxResources.parse(g.getText());f()}catch(k){}else f()}},parse:function(a){if(null!=a){a=a.split("\n");for(var b=0;b<a.length;b++)if("#"!=a[b].charAt(0)){var c=a[b].indexOf("=");if(0<c){var d=a[b].substring(0,c),e=a[b].length;13==a[b].charCodeAt(e-1)&&e--;c=a[b].substring(c+1,e);this.resourcesEncoded?(c=c.replace(/\\(?=u[a-fA-F\d]{4})/g,"%"),mx [...]
+unescape(c)):mxResources.resources[d]=c}}}},get:function(a,b,c){a=mxResources.resources[a];null==a&&(a=c);null!=a&&null!=b&&(a=mxResources.replacePlaceholders(a,b));return a},replacePlaceholders:function(a,b){for(var c=[],d=null,e=0;e<a.length;e++){var f=a.charAt(e);"{"==f?d="":null!=d&&"}"==f?(d=parseInt(d)-1,0<=d&&d<b.length&&c.push(b[d]),d=null):null!=d?d+=f:c.push(f)}return c.join("")},loadResources:function(a){mxResources.add(mxClient.basePath+"/resources/editor",null,function(){mxR [...]
+"/resources/graph",null,a)})}};function mxPoint(a,b){this.x=null!=a?a:0;this.y=null!=b?b:0}mxPoint.prototype.x=null;mxPoint.prototype.y=null;mxPoint.prototype.equals=function(a){return null!=a&&a.x==this.x&&a.y==this.y};mxPoint.prototype.clone=function(){return mxUtils.clone(this)};function mxRectangle(a,b,c,d){mxPoint.call(this,a,b);this.width=null!=c?c:0;this.height=null!=d?d:0}mxRectangle.prototype=new mxPoint;mxRectangle.prototype.constructor=mxRectangle;mxRectangle.prototype.width=null;
+mxRectangle.prototype.height=null;mxRectangle.prototype.setRect=function(a,b,c,d){this.x=a;this.y=b;this.width=c;this.height=d};mxRectangle.prototype.getCenterX=function(){return this.x+this.width/2};mxRectangle.prototype.getCenterY=function(){return this.y+this.height/2};
+mxRectangle.prototype.add=function(a){if(null!=a){var b=Math.min(this.x,a.x),c=Math.min(this.y,a.y),d=Math.max(this.x+this.width,a.x+a.width);a=Math.max(this.y+this.height,a.y+a.height);this.x=b;this.y=c;this.width=d-b;this.height=a-c}};mxRectangle.prototype.intersect=function(a){if(null!=a){var b=this.x+this.width,c=a.x+a.width,d=this.y+this.height,e=a.y+a.height;this.x=Math.max(this.x,a.x);this.y=Math.max(this.y,a.y);this.width=Math.min(b,c)-this.x;this.height=Math.min(d,e)-this.y}};
+mxRectangle.prototype.grow=function(a){this.x-=a;this.y-=a;this.width+=2*a;this.height+=2*a};mxRectangle.prototype.getPoint=function(){return new mxPoint(this.x,this.y)};mxRectangle.prototype.rotate90=function(){var a=(this.width-this.height)/2;this.x+=a;this.y-=a;a=this.width;this.width=this.height;this.height=a};mxRectangle.prototype.equals=function(a){return null!=a&&a.x==this.x&&a.y==this.y&&a.width==this.width&&a.height==this.height};
+mxRectangle.fromRectangle=function(a){return new mxRectangle(a.x,a.y,a.width,a.height)};
+var mxEffects={animateChanges:function(a,b,c){var d=0,e=function(){for(var g=!1,k=0;k<b.length;k++){var l=b[k];if(l instanceof mxGeometryChange||l instanceof mxTerminalChange||l instanceof mxValueChange||l instanceof mxChildChange||l instanceof mxStyleChange){var m=a.getView().getState(l.cell||l.child,!1);if(null!=m)if(g=!0,l.constructor!=mxGeometryChange||a.model.isEdge(l.cell))mxUtils.setOpacity(m.shape.node,100*d/10);else{var n=a.getView().scale,p=(l.geometry.x-l.previous.x)*n,q=(l.ge [...]
+l.previous.y)*n,r=(l.geometry.width-l.previous.width)*n,n=(l.geometry.height-l.previous.height)*n;0==d?(m.x-=p,m.y-=q,m.width-=r,m.height-=n):(m.x+=p/10,m.y+=q/10,m.width+=r/10,m.height+=n/10);a.cellRenderer.redraw(m);mxEffects.cascadeOpacity(a,l.cell,100*d/10)}}}10>d&&g?(d++,window.setTimeout(e,f)):null!=c&&c()},f=30;e()},cascadeOpacity:function(a,b,c){for(var d=a.model.getChildCount(b),e=0;e<d;e++){var f=a.model.getChildAt(b,e),g=a.getView().getState(f);null!=g&&(mxUtils.setOpacity(g.s [...]
+c),mxEffects.cascadeOpacity(a,f,c))}b=a.model.getEdges(b);if(null!=b)for(e=0;e<b.length;e++)d=a.getView().getState(b[e]),null!=d&&mxUtils.setOpacity(d.shape.node,c)},fadeOut:function(a,b,c,d,e,f){d=d||40;e=e||30;var g=b||100;mxUtils.setOpacity(a,g);if(f||null==f){var k=function(){g=Math.max(g-d,0);mxUtils.setOpacity(a,g);0<g?window.setTimeout(k,e):(a.style.visibility="hidden",c&&a.parentNode&&a.parentNode.removeChild(a))};window.setTimeout(k,e)}else a.style.visibility="hidden",c&&a.paren [...]
+mxUtils={errorResource:"none"!=mxClient.language?"error":"",closeResource:"none"!=mxClient.language?"close":"",errorImage:mxClient.imageBasePath+"/error.gif",removeCursors:function(a){null!=a.style&&(a.style.cursor="");a=a.childNodes;if(null!=a)for(var b=a.length,c=0;c<b;c+=1)mxUtils.removeCursors(a[c])},getCurrentStyle:function(){return mxClient.IS_IE?function(a){return null!=a?a.currentStyle:null}:function(a){return null!=a?window.getComputedStyle(a,""):null}}(),parseCssNumber:function [...]
+a?a="2":"medium"==a?a="4":"thick"==a&&(a="6");a=parseFloat(a);isNaN(a)&&(a=0);return a},setPrefixedStyle:function(){var a=null;mxClient.IS_OT?a="O":mxClient.IS_SF||mxClient.IS_GC?a="Webkit":mxClient.IS_MT?a="Moz":mxClient.IS_IE&&9<=document.documentMode&&10>document.documentMode&&(a="ms");return function(b,c,d){b[c]=d;null!=a&&0<c.length&&(c=a+c.substring(0,1).toUpperCase()+c.substring(1),b[c]=d)}}(),hasScrollbars:function(a){a=mxUtils.getCurrentStyle(a);return null!=a&&("scroll"==a.over [...]
+a.overflow)},bind:function(a,b){return function(){return b.apply(a,arguments)}},eval:function(a){var b=null;if(0<=a.indexOf("function"))try{eval("var _mxJavaScriptExpression="+a),b=_mxJavaScriptExpression,_mxJavaScriptExpression=null}catch(c){mxLog.warn(c.message+" while evaluating "+a)}else try{b=eval(a)}catch(c){mxLog.warn(c.message+" while evaluating "+a)}return b},findNode:function(a,b,c){if(a.nodeType==mxConstants.NODETYPE_ELEMENT){var d=a.getAttribute(b);if(null!=d&&d==c)return a}f [...]
+a;){d=mxUtils.findNode(a,b,c);if(null!=d)return d;a=a.nextSibling}return null},getFunctionName:function(a){var b=null;null!=a&&(null!=a.name?b=a.name:(b=mxUtils.trim(a.toString()),/^function\s/.test(b)&&(b=mxUtils.ltrim(b.substring(9)),a=b.indexOf("("),0<a&&(b=b.substring(0,a)))));return b},indexOf:function(a,b){if(null!=a&&null!=b)for(var c=0;c<a.length;c++)if(a[c]==b)return c;return-1},forEach:function(a,b){if(null!=a&&null!=b)for(var c=0;c<a.length;c++)b(a[c]);return a},remove:functio [...]
+null;if("object"==typeof b)for(var d=mxUtils.indexOf(b,a);0<=d;)b.splice(d,1),c=a,d=mxUtils.indexOf(b,a);for(var e in b)b[e]==a&&(delete b[e],c=a);return c},isNode:function(a,b,c,d){return null==a||isNaN(a.nodeType)||null!=b&&a.nodeName.toLowerCase()!=b.toLowerCase()?!1:null==c||a.getAttribute(c)==d},isAncestorNode:function(a,b){for(var c=b;null!=c;){if(c==a)return!0;c=c.parentNode}return!1},getChildNodes:function(a,b){b=b||mxConstants.NODETYPE_ELEMENT;for(var c=[],d=a.firstChild;null!=d [...]
+b&&c.push(d),d=d.nextSibling;return c},importNode:function(a,b,c){if(mxClient.IS_IE&&(null==document.documentMode||10>document.documentMode))switch(b.nodeType){case 1:var d=a.createElement(b.nodeName);if(b.attributes&&0<b.attributes.length){for(var e=0;e<b.attributes.length;e++)d.setAttribute(b.attributes[e].nodeName,b.getAttribute(b.attributes[e].nodeName));if(c&&b.childNodes&&0<b.childNodes.length)for(e=0;e<b.childNodes.length;e++)d.appendChild(mxUtils.importNode(a,b.childNodes[e],c))} [...]
+case 3:case 4:case 8:return a.createTextNode(b.value)}else return a.importNode(b,c)},createXmlDocument:function(){var a=null;document.implementation&&document.implementation.createDocument?a=document.implementation.createDocument("","",null):window.ActiveXObject&&(a=new ActiveXObject("Microsoft.XMLDOM"));return a},parseXml:function(){return window.DOMParser?function(a){return(new DOMParser).parseFromString(a,"text/xml")}:function(a){var b=mxUtils.createXmlDocument();b.async=!1;b.validate [...]
+b.resolveExternals=!1;b.loadXML(a);return b}}(),clearSelection:function(){return document.selection?function(){document.selection.empty()}:window.getSelection?function(){window.getSelection().removeAllRanges()}:function(){}}(),getPrettyXml:function(a,b,c){var d=[];if(null!=a)if(b=b||"  ",c=c||"",a.nodeType==mxConstants.NODETYPE_TEXT)d.push(a.value);else{d.push(c+"<"+a.nodeName);var e=a.attributes;if(null!=e)for(var f=0;f<e.length;f++){var g=mxUtils.htmlEntities(e[f].value);d.push(" "+e[f [...]
+'="'+g+'"')}e=a.firstChild;if(null!=e){for(d.push(">\n");null!=e;)d.push(mxUtils.getPrettyXml(e,b,c+b)),e=e.nextSibling;d.push(c+"</"+a.nodeName+">\n")}else d.push("/>\n")}return d.join("")},removeWhitespace:function(a,b){for(var c=b?a.previousSibling:a.nextSibling;null!=c&&c.nodeType==mxConstants.NODETYPE_TEXT;){var d=b?c.previousSibling:c.nextSibling,e=mxUtils.getTextContent(c);0==mxUtils.trim(e).length&&c.parentNode.removeChild(c);c=d}},htmlEntities:function(a,b){a=String(a||"");a=a.r [...]
+"&amp;");a=a.replace(/"/g,"&quot;");a=a.replace(/\'/g,"&#39;");a=a.replace(/</g,"&lt;");a=a.replace(/>/g,"&gt;");if(null==b||b)a=a.replace(/\n/g,"&#xa;");return a},isVml:function(a){return null!=a&&"urn:schemas-microsoft-com:vml"==a.tagUrn},getXml:function(a,b){var c="";null!=window.XMLSerializer?c=(new XMLSerializer).serializeToString(a):null!=a.xml&&(c=a.xml.replace(/\r\n\t[\t]*/g,"").replace(/>\r\n/g,">").replace(/\r\n/g,"\n"));return c=c.replace(/\n/g,b||"&#xa;")},extractTextWithWhit [...]
+a.length||"BR"!=a[0].nodeName&&"\n"!=a[0].innerHTML)for(var e=0;e<a.length;e++){var g=a[e];"BR"==g.nodeName||"\n"==g.innerHTML||(1==a.length||0==e)&&"DIV"==g.nodeName&&"<br>"==g.innerHTML.toLowerCase()?d.push("\n"):(3===g.nodeType||4===g.nodeType?0<g.nodeValue.length&&d.push(g.nodeValue):8!==g.nodeType&&0<g.childNodes.length&&b(g.childNodes),e<a.length-1&&0<=mxUtils.indexOf(c,a[e+1].nodeName)&&d.push("\n"))}}var c="BLOCKQUOTE DIV H1 H2 H3 H4 H5 H6 OL P PRE TABLE UL".split(" "),d=[];b(a); [...]
+replaceTrailingNewlines:function(a,b){for(var c="";0<a.length&&"\n"==a.charAt(a.length-1);)a=a.substring(0,a.length-1),c+=b;return a+c},getTextContent:function(a){return void 0!==a.innerText?a.innerText:null!=a?a[void 0===a.textContent?"text":"textContent"]:""},setTextContent:function(a,b){void 0!==a.innerText?a.innerText=b:a[void 0===a.textContent?"text":"textContent"]=b},getInnerHtml:function(){return mxClient.IS_IE?function(a){return null!=a?a.innerHTML:""}:function(a){return null!=a? [...]
+""}}(),getOuterHtml:function(){return mxClient.IS_IE?function(a){if(null!=a){if(null!=a.outerHTML)return a.outerHTML;var b=[];b.push("<"+a.nodeName);var c=a.attributes;if(null!=c)for(var d=0;d<c.length;d++){var e=c[d].value;null!=e&&0<e.length&&(b.push(" "),b.push(c[d].nodeName),b.push('="'),b.push(e),b.push('"'))}0==a.innerHTML.length?b.push("/>"):(b.push(">"),b.push(a.innerHTML),b.push("</"+a.nodeName+">"));return b.join("")}return""}:function(a){return null!=a?(new XMLSerializer).seri [...]
+""}}(),write:function(a,b){var c=a.ownerDocument.createTextNode(b);null!=a&&a.appendChild(c);return c},writeln:function(a,b){var c=a.ownerDocument.createTextNode(b);null!=a&&(a.appendChild(c),a.appendChild(document.createElement("br")));return c},br:function(a,b){b=b||1;for(var c=null,d=0;d<b;d++)null!=a&&(c=a.ownerDocument.createElement("br"),a.appendChild(c));return c},button:function(a,b,c){c=null!=c?c:document;c=c.createElement("button");mxUtils.write(c,a);mxEvent.addListener(c,"clic [...]
+return c},para:function(a,b){var c=document.createElement("p");mxUtils.write(c,b);null!=a&&a.appendChild(c);return c},addTransparentBackgroundFilter:function(a){a.style.filter+="progid:DXImageTransform.Microsoft.AlphaImageLoader(src='"+mxClient.imageBasePath+"/transparent.gif', sizingMethod='scale')"},linkAction:function(a,b,c,d,e){return mxUtils.link(a,b,function(){c.execute(d)},e)},linkInvoke:function(a,b,c,d,e,f){return mxUtils.link(a,b,function(){c[d](e)},f)},link:function(a,b,c,d){v [...]
+e.style.color="blue";e.style.textDecoration="underline";e.style.cursor="pointer";null!=d&&(e.style.paddingLeft=d+"px");mxEvent.addListener(e,"click",c);mxUtils.write(e,b);null!=a&&a.appendChild(e);return e},fit:function(a){var b=parseInt(a.offsetLeft),c=parseInt(a.offsetWidth),d=mxUtils.getDocumentScrollOrigin(a.ownerDocument),e=d.x,d=d.y,f=document.body,g=document.documentElement,k=e+(f.clientWidth||g.clientWidth);b+c>k&&(a.style.left=Math.max(e,k-c)+"px");b=parseInt(a.offsetTop);c=pars [...]
+f=d+Math.max(f.clientHeight||0,g.clientHeight);b+c>f&&(a.style.top=Math.max(d,f-c)+"px")},load:function(a){a=new mxXmlRequest(a,null,"GET",!1);a.send();return a},get:function(a,b,c,d,e,f){a=new mxXmlRequest(a,null,"GET");null!=d&&a.setBinary(d);a.send(b,c,e,f);return a},getAll:function(a,b,c){for(var d=a.length,e=[],f=0,g=function(){0==f&&null!=c&&c();f++},k=0;k<a.length;k++)(function(a,c){mxUtils.get(a,function(a){var f=a.getStatus();200>f||299<f?g():(e[c]=a,d--,0==d&&b(e))},g)})(a[k],k [...]
+post:function(a,b,c,d){return(new mxXmlRequest(a,b)).send(c,d)},submit:function(a,b,c,d){return(new mxXmlRequest(a,b)).simulate(c,d)},loadInto:function(a,b,c){mxClient.IS_IE?b.onreadystatechange=function(){4==b.readyState&&c()}:b.addEventListener("load",c,!1);b.load(a)},getValue:function(a,b,c){a=null!=a?a[b]:null;null==a&&(a=c);return a},getNumber:function(a,b,c){a=null!=a?a[b]:null;null==a&&(a=c||0);return Number(a)},getColor:function(a,b,c){a=null!=a?a[b]:null;null==a?a=c:a==mxConstan [...]
+(a=null);return a},clone:function(a,b,c){c=null!=c?c:!1;var d=null;if(null!=a&&"function"==typeof a.constructor){var d=new a.constructor,e;for(e in a)e!=mxObjectIdentity.FIELD_NAME&&(null==b||0>mxUtils.indexOf(b,e))&&(d[e]=c||"object"!=typeof a[e]?a[e]:mxUtils.clone(a[e]))}return d},equalPoints:function(a,b){if(null==a&&null!=b||null!=a&&null==b||null!=a&&null!=b&&a.length!=b.length)return!1;if(null!=a&&null!=b)for(var c=0;c<a.length;c++)if(a[c]==b[c]||null!=a[c]&&!a[c].equals(b[c]))retu [...]
+equalEntries:function(a,b){if(null==a&&null!=b||null!=a&&null==b||null!=a&&null!=b&&a.length!=b.length)return!1;if(null!=a&&null!=b){var c=0,d;for(d in b)c++;for(d in a)if(c--,!(mxUtils.isNaN(a[d])&&mxUtils.isNaN(b[d])||a[d]==b[d]))return!1}return 0==c},removeDuplicates:function(a){for(var b=new mxDictionary,c=[],d=0;d<a.length;d++)b.get(a[d])||(c.push(a[d]),b.put(a[d],!0));return c},isNaN:function(a){return"number"==typeof a&&isNaN(a)},extend:function(a,b){var c=function(){};c.prototype [...]
+a.prototype=new c;a.prototype.constructor=a},toString:function(a){var b="",c;for(c in a)try{if(null==a[c])b+=c+" = [null]\n";else if("function"==typeof a[c])b+=c+" => [Function]\n";else if("object"==typeof a[c])var d=mxUtils.getFunctionName(a[c].constructor),b=b+(c+" => ["+d+"]\n");else b+=c+" = "+a[c]+"\n"}catch(e){b+=c+"="+e.message}return b},toRadians:function(a){return Math.PI*a/180},toDegree:function(a){return 180*a/Math.PI},arcToCurves:function(a,b,c,d,e,f,g,k,l){k-=a;l-=b;if(0===c [...]
+c=Math.abs(c);d=Math.abs(d);var m=-k/2,n=-l/2,p=Math.cos(e*Math.PI/180),q=Math.sin(e*Math.PI/180);e=p*m+q*n;var m=-1*q*m+p*n,n=e*e,r=m*m,t=c*c,u=d*d,x=n/t+r/u;1<x?(c*=Math.sqrt(x),d*=Math.sqrt(x),f=0):(x=1,f===g&&(x=-1),f=x*Math.sqrt((t*u-t*r-u*n)/(t*r+u*n)));n=f*c*m/d;r=-1*f*d*e/c;k=p*n-q*r+k/2;l=q*n+p*r+l/2;t=Math.atan2((m-r)/d,(e-n)/c)-Math.atan2(0,1);f=0<=t?t:2*Math.PI+t;t=Math.atan2((-m-r)/d,(-e-n)/c)-Math.atan2((m-r)/d,(e-n)/c);e=0<=t?t:2*Math.PI+t;0==g&&0<e?e-=2*Math.PI:0!=g&&0>e& [...]
+g=2*e/Math.PI;g=Math.ceil(0>g?-1*g:g);e/=g;m=8/3*Math.sin(e/4)*Math.sin(e/4)/Math.sin(e/2);n=p*c;p*=d;c*=q;d*=q;for(var y=Math.cos(f),A=Math.sin(f),r=-m*(n*A+d*y),t=-m*(c*A-p*y),q=[],z=0;z<g;++z){f+=e;var y=Math.cos(f),A=Math.sin(f),u=n*y-d*A+k,x=c*y+p*A+l,v=-m*(n*A+d*y),y=-m*(c*A-p*y),A=6*z;q[A]=Number(r+a);q[A+1]=Number(t+b);q[A+2]=Number(u-v+a);q[A+3]=Number(x-y+b);q[A+4]=Number(u+a);q[A+5]=Number(x+b);r=u+v;t=x+y}return q},getBoundingBox:function(a,b,c){var d=null;if(null!=a&&null!=b [...]
+mxUtils.toRadians(b);var d=Math.cos(b),e=Math.sin(b);c=null!=c?c:new mxPoint(a.x+a.width/2,a.y+a.height/2);var f=new mxPoint(a.x,a.y);b=new mxPoint(a.x+a.width,a.y);var g=new mxPoint(b.x,a.y+a.height);a=new mxPoint(a.x,g.y);f=mxUtils.getRotatedPoint(f,d,e,c);b=mxUtils.getRotatedPoint(b,d,e,c);g=mxUtils.getRotatedPoint(g,d,e,c);a=mxUtils.getRotatedPoint(a,d,e,c);d=new mxRectangle(f.x,f.y,0,0);d.add(new mxRectangle(b.x,b.y,0,0));d.add(new mxRectangle(g.x,g.y,0,0));d.add(new mxRectangle(a.x [...]
+getRotatedPoint:function(a,b,c,d){d=null!=d?d:new mxPoint;var e=a.x-d.x;a=a.y-d.y;return new mxPoint(e*b-a*c+d.x,a*b+e*c+d.y)},getPortConstraints:function(a,b,c,d){b=mxUtils.getValue(a.style,mxConstants.STYLE_PORT_CONSTRAINT,mxUtils.getValue(b.style,c?mxConstants.STYLE_SOURCE_PORT_CONSTRAINT:mxConstants.STYLE_TARGET_PORT_CONSTRAINT,null));if(null==b)return d;d=b.toString();b=mxConstants.DIRECTION_MASK_NONE;c=0;1==mxUtils.getValue(a.style,mxConstants.STYLE_PORT_CONSTRAINT_ROTATION,0)&&(c= [...]
+mxConstants.STYLE_ROTATION,0));a=0;45<c?(a=1,135<=c&&(a=2)):-45>c&&(a=3,-135>=c&&(a=2));if(0<=d.indexOf(mxConstants.DIRECTION_NORTH))switch(a){case 0:b|=mxConstants.DIRECTION_MASK_NORTH;break;case 1:b|=mxConstants.DIRECTION_MASK_EAST;break;case 2:b|=mxConstants.DIRECTION_MASK_SOUTH;break;case 3:b|=mxConstants.DIRECTION_MASK_WEST}if(0<=d.indexOf(mxConstants.DIRECTION_WEST))switch(a){case 0:b|=mxConstants.DIRECTION_MASK_WEST;break;case 1:b|=mxConstants.DIRECTION_MASK_NORTH;break;case 2:b|= [...]
+break;case 3:b|=mxConstants.DIRECTION_MASK_SOUTH}if(0<=d.indexOf(mxConstants.DIRECTION_SOUTH))switch(a){case 0:b|=mxConstants.DIRECTION_MASK_SOUTH;break;case 1:b|=mxConstants.DIRECTION_MASK_WEST;break;case 2:b|=mxConstants.DIRECTION_MASK_NORTH;break;case 3:b|=mxConstants.DIRECTION_MASK_EAST}if(0<=d.indexOf(mxConstants.DIRECTION_EAST))switch(a){case 0:b|=mxConstants.DIRECTION_MASK_EAST;break;case 1:b|=mxConstants.DIRECTION_MASK_SOUTH;break;case 2:b|=mxConstants.DIRECTION_MASK_WEST;break;c [...]
+mxConstants.DIRECTION_MASK_NORTH}return b},reversePortConstraints:function(a){var b;b=(a&mxConstants.DIRECTION_MASK_WEST)<<3;b|=(a&mxConstants.DIRECTION_MASK_NORTH)<<1;b|=(a&mxConstants.DIRECTION_MASK_SOUTH)>>1;return b|=(a&mxConstants.DIRECTION_MASK_EAST)>>3},findNearestSegment:function(a,b,c){var d=-1;if(0<a.absolutePoints.length)for(var e=a.absolutePoints[0],f=null,g=1;g<a.absolutePoints.length;g++){var k=a.absolutePoints[g],e=mxUtils.ptSegDistSq(e.x,e.y,k.x,k.y,b,c);if(null==f||e<f)f [...]
+e=k}return d},getDirectedBounds:function(a,b,c,d,e){var f=mxUtils.getValue(c,mxConstants.STYLE_DIRECTION,mxConstants.DIRECTION_EAST);d=null!=d?d:mxUtils.getValue(c,mxConstants.STYLE_FLIPH,!1);e=null!=e?e:mxUtils.getValue(c,mxConstants.STYLE_FLIPV,!1);b.x=Math.round(Math.max(0,Math.min(a.width,b.x)));b.y=Math.round(Math.max(0,Math.min(a.height,b.y)));b.width=Math.round(Math.max(0,Math.min(a.width,b.width)));b.height=Math.round(Math.max(0,Math.min(a.height,b.height)));if(e&&(f==mxConstants [...]
+f==mxConstants.DIRECTION_NORTH)||d&&(f==mxConstants.DIRECTION_EAST||f==mxConstants.DIRECTION_WEST))c=b.x,b.x=b.width,b.width=c;if(d&&(f==mxConstants.DIRECTION_SOUTH||f==mxConstants.DIRECTION_NORTH)||e&&(f==mxConstants.DIRECTION_EAST||f==mxConstants.DIRECTION_WEST))c=b.y,b.y=b.height,b.height=c;d=mxRectangle.fromRectangle(b);f==mxConstants.DIRECTION_SOUTH?(d.y=b.x,d.x=b.height,d.width=b.y,d.height=b.width):f==mxConstants.DIRECTION_WEST?(d.y=b.height,d.x=b.width,d.width=b.x,d.height=b.y):f [...]
+(d.y=b.width,d.x=b.y,d.width=b.height,d.height=b.x);return new mxRectangle(a.x+d.x,a.y+d.y,a.width-d.width-d.x,a.height-d.height-d.y)},getPerimeterPoint:function(a,b,c){for(var d=null,e=0;e<a.length-1;e++){var f=mxUtils.intersection(a[e].x,a[e].y,a[e+1].x,a[e+1].y,b.x,b.y,c.x,c.y);if(null!=f){var g=c.x-f.x,k=c.y-f.y,f={p:f,distSq:k*k+g*g};null!=f&&(null==d||d.distSq>f.distSq)&&(d=f)}}return null!=d?d.p:null},rectangleIntersectsSegment:function(a,b,c){var d=a.y,e=a.x,f=d+a.height,g=e+a.wi [...]
+var k=c.x;b.x>c.x&&(a=c.x,k=b.x);k>g&&(k=g);a<e&&(a=e);if(a>k)return!1;var e=b.y,g=c.y,l=c.x-b.x;1E-7<Math.abs(l)&&(c=(c.y-b.y)/l,b=b.y-c*b.x,e=c*a+b,g=c*k+b);e>g&&(b=g,g=e,e=b);g>f&&(g=f);e<d&&(e=d);return e>g?!1:!0},contains:function(a,b,c){return a.x<=b&&a.x+a.width>=b&&a.y<=c&&a.y+a.height>=c},intersects:function(a,b){var c=a.width,d=a.height,e=b.width,f=b.height;if(0>=e||0>=f||0>=c||0>=d)return!1;var g=a.x,k=a.y,l=b.x,m=b.y,e=e+l,f=f+m,c=c+g,d=d+k;return(e<l||e>g)&&(f<m||f>k)&&(c<g| [...]
+k||d>m)},intersectsHotspot:function(a,b,c,d,e,f){d=null!=d?d:1;e=null!=e?e:0;f=null!=f?f:0;if(0<d){var g=a.getCenterX(),k=a.getCenterY(),l=a.width,m=a.height,n=mxUtils.getValue(a.style,mxConstants.STYLE_STARTSIZE)*a.view.scale;0<n&&(mxUtils.getValue(a.style,mxConstants.STYLE_HORIZONTAL,!0)?(k=a.y+n/2,m=n):(g=a.x+n/2,l=n));l=Math.max(e,l*d);m=Math.max(e,m*d);0<f&&(l=Math.min(l,f),m=Math.min(m,f));d=new mxRectangle(g-l/2,k-m/2,l,m);g=mxUtils.toRadians(mxUtils.getValue(a.style,mxConstants.S [...]
+0);0!=g&&(e=Math.cos(-g),f=Math.sin(-g),g=new mxPoint(a.getCenterX(),a.getCenterY()),a=mxUtils.getRotatedPoint(new mxPoint(b,c),e,f,g),b=a.x,c=a.y);return mxUtils.contains(d,b,c)}return!0},getOffset:function(a,b){for(var c=0,d=0,e=!1,f=a,g=document.body,k=document.documentElement;null!=f&&f!=g&&f!=k&&!e;){var l=mxUtils.getCurrentStyle(f);null!=l&&(e=e||"fixed"==l.position);f=f.parentNode}b||e||(e=mxUtils.getDocumentScrollOrigin(a.ownerDocument),c+=e.x,d+=e.y);e=a.getBoundingClientRect(); [...]
+(c+=e.left,d+=e.top);return new mxPoint(c,d)},getDocumentScrollOrigin:function(a){if(mxClient.IS_QUIRKS)return new mxPoint(a.body.scrollLeft,a.body.scrollTop);a=a.defaultView||a.parentWindow;return new mxPoint(null!=a&&void 0!==window.pageXOffset?window.pageXOffset:(document.documentElement||document.body.parentNode||document.body).scrollLeft,null!=a&&void 0!==window.pageYOffset?window.pageYOffset:(document.documentElement||document.body.parentNode||document.body).scrollTop)},getScrollOr [...]
+b,c){b=null!=b?b:!1;c=null!=c?c:!0;for(var d=null!=a?a.ownerDocument:document,e=d.body,f=d.documentElement,g=new mxPoint,k=!1;null!=a&&a!=e&&a!=f;){isNaN(a.scrollLeft)||isNaN(a.scrollTop)||(g.x+=a.scrollLeft,g.y+=a.scrollTop);var l=mxUtils.getCurrentStyle(a);null!=l&&(k=k||"fixed"==l.position);a=b?a.parentNode:null}!k&&c&&(a=mxUtils.getDocumentScrollOrigin(d),g.x+=a.x,g.y+=a.y);return g},convertPoint:function(a,b,c){var d=mxUtils.getScrollOrigin(a,!1);a=mxUtils.getOffset(a);a.x-=d.x;a.y- [...]
+a.x,c-a.y)},ltrim:function(a,b){return null!=a?a.replace(new RegExp("^["+(b||"\\s")+"]+","g"),""):null},rtrim:function(a,b){return null!=a?a.replace(new RegExp("["+(b||"\\s")+"]+$","g"),""):null},trim:function(a,b){return mxUtils.ltrim(mxUtils.rtrim(a,b),b)},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)&&("string"!=typeof a||0>a.toLowerCase().indexOf("0x"))},isInteger:function(a){return String(parseInt(a))===String(a)},mod:function(a,b){return(a%b+b)%b},intersection:func [...]
+c,d,e,f,g,k){var l=(k-f)*(c-a)-(g-e)*(d-b);g=((g-e)*(b-f)-(k-f)*(a-e))/l;e=((c-a)*(b-f)-(d-b)*(a-e))/l;return 0<=g&&1>=g&&0<=e&&1>=e?new mxPoint(a+g*(c-a),b+g*(d-b)):null},ptSegDistSq:function(a,b,c,d,e,f){c-=a;d-=b;e-=a;f-=b;0>=e*c+f*d?c=0:(e=c-e,f=d-f,a=e*c+f*d,c=0>=a?0:a*a/(c*c+d*d));e=e*e+f*f-c;0>e&&(e=0);return e},ptLineDist:function(a,b,c,d,e,f){return Math.abs((d-b)*e-(c-a)*f+c*b-d*a)/Math.sqrt((d-b)*(d-b)+(c-a)*(c-a))},relativeCcw:function(a,b,c,d,e,f){c-=a;d-=b;e-=a;f-=b;a=e*d-f [...]
+(a=e*c+f*d,0<a&&(a=(e-c)*c+(f-d)*d,0>a&&(a=0)));return 0>a?-1:0<a?1:0},animateChanges:function(a,b){mxEffects.animateChanges.apply(this,arguments)},cascadeOpacity:function(a,b,c){mxEffects.cascadeOpacity.apply(this,arguments)},fadeOut:function(a,b,c,d,e,f){mxEffects.fadeOut.apply(this,arguments)},setOpacity:function(a,b){mxUtils.isVml(a)?a.style.filter=100<=b?"":"alpha(opacity="+b/5+")":mxClient.IS_IE&&("undefined"===typeof document.documentMode||9>document.documentMode)?a.style.filter=1 [...]
+"alpha(opacity="+b+")":a.style.opacity=b/100},createImage:function(a){var b;mxClient.IS_IE6&&"CSS1Compat"!=document.compatMode?(b=document.createElement(mxClient.VML_PREFIX+":image"),b.setAttribute("src",a),b.style.borderStyle="none"):(b=document.createElement("img"),b.setAttribute("src",a),b.setAttribute("border","0"));return b},sortCells:function(a,b){b=null!=b?b:!0;var c=new mxDictionary;a.sort(function(a,e){var d=c.get(a);null==d&&(d=mxCellPath.create(a).split(mxCellPath.PATH_SEPARAT [...]
+d));var g=c.get(e);null==g&&(g=mxCellPath.create(e).split(mxCellPath.PATH_SEPARATOR),c.put(e,g));d=mxCellPath.compare(d,g);return 0==d?0:0<d==b?1:-1});return a},getStylename:function(a){return null!=a&&(a=a.split(";")[0],0>a.indexOf("="))?a:""},getStylenames:function(a){var b=[];if(null!=a){a=a.split(";");for(var c=0;c<a.length;c++)0>a[c].indexOf("=")&&b.push(a[c])}return b},indexOfStylename:function(a,b){if(null!=a&&null!=b)for(var c=a.split(";"),d=0,e=0;e<c.length;e++){if(c[e]==b)retur [...]
+1}return-1},addStylename:function(a,b){0>mxUtils.indexOfStylename(a,b)&&(null==a?a="":0<a.length&&";"!=a.charAt(a.length-1)&&(a+=";"),a+=b);return a},removeStylename:function(a,b){var c=[];if(null!=a)for(var d=a.split(";"),e=0;e<d.length;e++)d[e]!=b&&c.push(d[e]);return c.join(";")},removeAllStylenames:function(a){var b=[];if(null!=a){a=a.split(";");for(var c=0;c<a.length;c++)0<=a[c].indexOf("=")&&b.push(a[c])}return b.join(";")},setCellStyles:function(a,b,c,d){if(null!=b&&0<b.length){a. [...]
+try{for(var e=0;e<b.length;e++)if(null!=b[e]){var f=mxUtils.setStyle(a.getStyle(b[e]),c,d);a.setStyle(b[e],f)}}finally{a.endUpdate()}}},setStyle:function(a,b,c){var d=null!=c&&("undefined"==typeof c.length||0<c.length);if(null==a||0==a.length)d&&(a=b+"="+c+";");else if(a.substring(0,b.length+1)==b+"="){var e=a.indexOf(";");a=d?b+"="+c+(0>e?";":a.substring(e)):0>e||e==a.length-1?"":a.substring(e+1)}else{var f=a.indexOf(";"+b+"=");0>f?d&&(d=";"==a.charAt(a.length-1)?"":";",a=a+d+b+"="+c+"; [...]
+f+1),a=d?a.substring(0,f+1)+b+"="+c+(0>e?";":a.substring(e)):a.substring(0,f)+(0>e?";":a.substring(e)))}return a},setCellStyleFlags:function(a,b,c,d,e){if(null!=b&&0<b.length){a.beginUpdate();try{for(var f=0;f<b.length;f++)if(null!=b[f]){var g=mxUtils.setStyleFlag(a.getStyle(b[f]),c,d,e);a.setStyle(b[f],g)}}finally{a.endUpdate()}}},setStyleFlag:function(a,b,c,d){if(null==a||0==a.length)a=d||null==d?b+"="+c:b+"=0";else{var e=a.indexOf(b+"=");if(0>e)e=";"==a.charAt(a.length-1)?"":";",a=d|| [...]
+e+b+"="+c:a+e+b+"=0";else{var f=a.indexOf(";",e),g;g=0>f?a.substring(e+b.length+1):a.substring(e+b.length+1,f);g=null==d?parseInt(g)^c:d?parseInt(g)|c:parseInt(g)&~c;a=a.substring(0,e)+b+"="+g+(0<=f?a.substring(f):"")}}return a},getAlignmentAsPoint:function(a,b){var c=0,d=0;a==mxConstants.ALIGN_CENTER?c=-.5:a==mxConstants.ALIGN_RIGHT&&(c=-1);b==mxConstants.ALIGN_MIDDLE?d=-.5:b==mxConstants.ALIGN_BOTTOM&&(d=-1);return new mxPoint(c,d)},getSizeForString:function(a,b,c,d){b=null!=b?b:mxCons [...]
+c=null!=c?c:mxConstants.DEFAULT_FONTFAMILY;var e=document.createElement("div");e.style.fontFamily=c;e.style.fontSize=Math.round(b)+"px";e.style.lineHeight=Math.round(b*mxConstants.LINE_HEIGHT)+"px";e.style.position="absolute";e.style.visibility="hidden";e.style.display=mxClient.IS_QUIRKS?"inline":"inline-block";e.style.zoom="1";null!=d?(e.style.width=d+"px",e.style.whiteSpace="normal"):e.style.whiteSpace="nowrap";e.innerHTML=a;document.body.appendChild(e);a=new mxRectangle(0,0,e.offsetWi [...]
+document.body.removeChild(e);return a},getViewXml:function(a,b,c,d,e){d=null!=d?d:0;e=null!=e?e:0;b=null!=b?b:1;null==c&&(c=[a.getModel().getRoot()]);var f=a.getView(),g=null,k=f.isEventsEnabled();f.setEventsEnabled(!1);var l=f.drawPane,m=f.overlayPane;a.dialect==mxConstants.DIALECT_SVG?(f.drawPane=document.createElementNS(mxConstants.NS_SVG,"g"),f.canvas.appendChild(f.drawPane),f.overlayPane=document.createElementNS(mxConstants.NS_SVG,"g")):(f.drawPane=f.drawPane.cloneNode(!1),f.canvas. [...]
+f.overlayPane=f.overlayPane.cloneNode(!1));f.canvas.appendChild(f.overlayPane);var n=f.getTranslate();f.translate=new mxPoint(d,e);b=new mxTemporaryCellStates(a.getView(),b,c);try{g=(new mxCodec).encode(a.getView())}finally{b.destroy(),f.translate=n,f.canvas.removeChild(f.drawPane),f.canvas.removeChild(f.overlayPane),f.drawPane=l,f.overlayPane=m,f.setEventsEnabled(k)}return g},getScaleForPageCount:function(a,b,c,d){if(1>a)return 1;c=null!=c?c:mxConstants.PAGE_FORMAT_A4_PORTRAIT;d=null!=d [...]
+c.width-2*d;c=c.height-2*d;d=b.getGraphBounds().clone();b=b.getView().getScale();d.width/=b;d.height/=b;b=d.width;var f=Math.sqrt(a);d=Math.sqrt(b/d.height/(e/c));c=f*d;d=f/d;if(1>c&&d>a){var g=d/a;d=a;c/=g}1>d&&c>a&&(g=c/a,c=a,d/=g);g=Math.ceil(c)*Math.ceil(d);for(f=0;g>a;){var g=Math.floor(c)/c,k=Math.floor(d)/d;1==g&&(g=Math.floor(c-1)/c);1==k&&(k=Math.floor(d-1)/d);g=g>k?g:k;c*=g;d*=g;g=Math.ceil(c)*Math.ceil(d);f++;if(10<f)break}return e*c/b*.99999},show:function(a,b,c,d,e,f){c=null [...]
+null!=d?d:0;null==b?b=window.open().document:b.open();9==document.documentMode&&b.writeln('\x3c!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=9"><![endif]--\x3e');var g=a.getGraphBounds(),k=Math.ceil(c-g.x),l=Math.ceil(d-g.y);null==e&&(e=Math.ceil(g.width+c)+Math.ceil(Math.ceil(g.x)-g.x));null==f&&(f=Math.ceil(g.height+d)+Math.ceil(Math.ceil(g.y)-g.y));if(mxClient.IS_IE||11==document.documentMode){d="<html><head>";g=document.getElementsByTagName("base");for(c=0;c<g.length;c++) [...]
+d+="<style>";for(c=0;c<document.styleSheets.length;c++)try{d+=document.styleSheets[c].cssText}catch(m){}d=d+'</style></head><body style="margin:0px;">'+('<div style="position:absolute;overflow:hidden;width:'+e+"px;height:"+f+'px;"><div style="position:relative;left:'+k+"px;top:"+l+'px;">')+a.container.innerHTML;b.writeln(d+"</div></div></body><html>");b.close()}else{b.writeln("<html><head>");g=document.getElementsByTagName("base");for(c=0;c<g.length;c++)b.writeln(mxUtils.getOuterHtml(g[c [...]
+for(c=0;c<d.length;c++)b.writeln(mxUtils.getOuterHtml(d[c]));d=document.getElementsByTagName("style");for(c=0;c<d.length;c++)b.writeln(mxUtils.getOuterHtml(d[c]));b.writeln('</head><body style="margin:0px;"></body></html>');b.close();c=b.createElement("div");c.position="absolute";c.overflow="hidden";c.style.width=e+"px";c.style.height=f+"px";e=b.createElement("div");e.style.position="absolute";e.style.left=k+"px";e.style.top=l+"px";f=a.container.firstChild;for(d=null;null!=f;)g=f.cloneNo [...]
+a.view.drawPane.ownerSVGElement?(c.appendChild(g),d=g):e.appendChild(g),f=f.nextSibling;b.body.appendChild(c);null!=e.firstChild&&b.body.appendChild(e);null!=d&&(d.style.minWidth="",d.style.minHeight="",d.firstChild.setAttribute("transform","translate("+k+","+l+")"))}mxUtils.removeCursors(b.body);return b},printScreen:function(a){var b=window.open();a.getGraphBounds();mxUtils.show(a,b.document);a=function(){b.focus();b.print();b.close()};mxClient.IS_GC?b.setTimeout(a,500):a()},popup:func [...]
+document.createElement("div");c.style.overflow="scroll";c.style.width="636px";c.style.height="460px";var d=document.createElement("pre");d.innerHTML=mxUtils.htmlEntities(a,!1).replace(/\n/g,"<br>").replace(/ /g,"&nbsp;");c.appendChild(d);c=new mxWindow("Popup Window",c,document.body.clientWidth/2-320,Math.max(document.body.clientHeight||0,document.documentElement.clientHeight)/2-240,640,480,!1,!0);c.setClosable(!0);c.setVisible(!0)}else mxClient.IS_NS?(c=window.open(),c.document.writeln( [...]
+"</pre"),c.document.close()):(c=window.open(),d=c.document.createElement("pre"),d.innerHTML=mxUtils.htmlEntities(a,!1).replace(/\n/g,"<br>").replace(/ /g,"&nbsp;"),c.document.body.appendChild(d))},alert:function(a){alert(a)},prompt:function(a,b){return prompt(a,null!=b?b:"")},confirm:function(a){return confirm(a)},error:function(a,b,c,d){var e=document.createElement("div");e.style.padding="20px";var f=document.createElement("img");f.setAttribute("src",d||mxUtils.errorImage);f.setAttribut [...]
+"bottom");f.style.verticalAlign="middle";e.appendChild(f);e.appendChild(document.createTextNode(" "));e.appendChild(document.createTextNode(" "));e.appendChild(document.createTextNode(" "));mxUtils.write(e,a);a=document.body.clientWidth;d=document.body.clientHeight||document.documentElement.clientHeight;var g=new mxWindow(mxResources.get(mxUtils.errorResource)||mxUtils.errorResource,e,(a-b)/2,d/4,b,null,!1,!0);c&&(mxUtils.br(e),b=document.createElement("p"),c=document.createElement("butt [...]
+c.style.cssText="float:right":c.setAttribute("style","float:right"),mxEvent.addListener(c,"click",function(a){g.destroy()}),mxUtils.write(c,mxResources.get(mxUtils.closeResource)||mxUtils.closeResource),b.appendChild(c),e.appendChild(b),mxUtils.br(e),g.setClosable(!0));g.setVisible(!0);return g},makeDraggable:function(a,b,c,d,e,f,g,k,l,m){a=new mxDragSource(a,c);a.dragOffset=new mxPoint(null!=e?e:0,null!=f?f:mxConstants.TOOLTIP_VERTICAL_OFFSET);a.autoscroll=g;a.setGuidesEnabled(!1);null! [...]
+l);null!=m&&(a.getDropTarget=m);a.getGraphForEvent=function(a){return"function"==typeof b?b(a):b};null!=d&&(a.createDragElement=function(){return d.cloneNode(!0)},k&&(a.createPreviewElement=function(a){var b=d.cloneNode(!0),c=parseInt(b.style.width),e=parseInt(b.style.height);b.style.width=Math.round(c*a.view.scale)+"px";b.style.height=Math.round(e*a.view.scale)+"px";return b}));return a}},mxConstants={DEFAULT_HOTSPOT:.3,MIN_HOTSPOT_SIZE:8,MAX_HOTSPOT_SIZE:0,RENDERING_HINT_EXACT:"exact", [...]
+RENDERING_HINT_FASTEST:"fastest",DIALECT_SVG:"svg",DIALECT_VML:"vml",DIALECT_MIXEDHTML:"mixedHtml",DIALECT_PREFERHTML:"preferHtml",DIALECT_STRICTHTML:"strictHtml",NS_SVG:"http://www.w3.org/2000/svg",NS_XHTML:"http://www.w3.org/1999/xhtml",NS_XLINK:"http://www.w3.org/1999/xlink",SHADOWCOLOR:"gray",VML_SHADOWCOLOR:"gray",SHADOW_OFFSET_X:2,SHADOW_OFFSET_Y:3,SHADOW_OPACITY:1,NODETYPE_ELEMENT:1,NODETYPE_ATTRIBUTE:2,NODETYPE_TEXT:3,NODETYPE_CDATA:4,NODETYPE_ENTITY_REFERENCE:5,NODETYPE_ENTITY:6 [...]
+NODETYPE_COMMENT:8,NODETYPE_DOCUMENT:9,NODETYPE_DOCUMENTTYPE:10,NODETYPE_DOCUMENT_FRAGMENT:11,NODETYPE_NOTATION:12,TOOLTIP_VERTICAL_OFFSET:16,DEFAULT_VALID_COLOR:"#00FF00",DEFAULT_INVALID_COLOR:"#FF0000",OUTLINE_HIGHLIGHT_COLOR:"#00FF00",OUTLINE_HIGHLIGHT_STROKEWIDTH:5,HIGHLIGHT_STROKEWIDTH:3,HIGHLIGHT_SIZE:2,HIGHLIGHT_OPACITY:100,CURSOR_MOVABLE_VERTEX:"move",CURSOR_MOVABLE_EDGE:"move",CURSOR_LABEL_HANDLE:"default",CURSOR_TERMINAL_HANDLE:"pointer",CURSOR_BEND_HANDLE:"crosshair",CURSOR_VI [...]
+CURSOR_CONNECT:"pointer",HIGHLIGHT_COLOR:"#00FF00",CONNECT_TARGET_COLOR:"#0000FF",INVALID_CONNECT_TARGET_COLOR:"#FF0000",DROP_TARGET_COLOR:"#0000FF",VALID_COLOR:"#00FF00",INVALID_COLOR:"#FF0000",EDGE_SELECTION_COLOR:"#00FF00",VERTEX_SELECTION_COLOR:"#00FF00",VERTEX_SELECTION_STROKEWIDTH:1,EDGE_SELECTION_STROKEWIDTH:1,VERTEX_SELECTION_DASHED:!0,EDGE_SELECTION_DASHED:!0,GUIDE_COLOR:"#FF0000",GUIDE_STROKEWIDTH:1,OUTLINE_COLOR:"#0099FF",OUTLINE_STROKEWIDTH:mxClient.IS_IE?2:3,HANDLE_SIZE:6,LA [...]
+HANDLE_FILLCOLOR:"#00FF00",HANDLE_STROKECOLOR:"black",LABEL_HANDLE_FILLCOLOR:"yellow",CONNECT_HANDLE_FILLCOLOR:"#0000FF",LOCKED_HANDLE_FILLCOLOR:"#FF0000",OUTLINE_HANDLE_FILLCOLOR:"#00FFFF",OUTLINE_HANDLE_STROKECOLOR:"#0033FF",DEFAULT_FONTFAMILY:"Arial,Helvetica",DEFAULT_FONTSIZE:11,DEFAULT_TEXT_DIRECTION:"",LINE_HEIGHT:1.2,WORD_WRAP:"normal",ABSOLUTE_LINE_HEIGHT:!1,DEFAULT_FONTSTYLE:0,DEFAULT_STARTSIZE:40,DEFAULT_MARKERSIZE:6,DEFAULT_IMAGESIZE:24,ENTITY_SEGMENT:30,RECTANGLE_ROUNDING_FAC [...]
+ARROW_SPACING:0,ARROW_WIDTH:30,ARROW_SIZE:30,PAGE_FORMAT_A4_PORTRAIT:new mxRectangle(0,0,827,1169),PAGE_FORMAT_A4_LANDSCAPE:new mxRectangle(0,0,1169,827),PAGE_FORMAT_LETTER_PORTRAIT:new mxRectangle(0,0,850,1100),PAGE_FORMAT_LETTER_LANDSCAPE:new mxRectangle(0,0,1100,850),NONE:"none",STYLE_PERIMETER:"perimeter",STYLE_SOURCE_PORT:"sourcePort",STYLE_TARGET_PORT:"targetPort",STYLE_PORT_CONSTRAINT:"portConstraint",STYLE_PORT_CONSTRAINT_ROTATION:"portConstraintRotation",STYLE_SOURCE_PORT_CONSTR [...]
+STYLE_TARGET_PORT_CONSTRAINT:"targetPortConstraint",STYLE_OPACITY:"opacity",STYLE_FILL_OPACITY:"fillOpacity",STYLE_STROKE_OPACITY:"strokeOpacity",STYLE_TEXT_OPACITY:"textOpacity",STYLE_TEXT_DIRECTION:"textDirection",STYLE_OVERFLOW:"overflow",STYLE_ORTHOGONAL:"orthogonal",STYLE_EXIT_X:"exitX",STYLE_EXIT_Y:"exitY",STYLE_EXIT_PERIMETER:"exitPerimeter",STYLE_ENTRY_X:"entryX",STYLE_ENTRY_Y:"entryY",STYLE_ENTRY_PERIMETER:"entryPerimeter",STYLE_WHITE_SPACE:"whiteSpace",STYLE_ROTATION:"rotation" [...]
+STYLE_POINTER_EVENTS:"pointerEvents",STYLE_SWIMLANE_FILLCOLOR:"swimlaneFillColor",STYLE_MARGIN:"margin",STYLE_GRADIENTCOLOR:"gradientColor",STYLE_GRADIENT_DIRECTION:"gradientDirection",STYLE_STROKECOLOR:"strokeColor",STYLE_SEPARATORCOLOR:"separatorColor",STYLE_STROKEWIDTH:"strokeWidth",STYLE_ALIGN:"align",STYLE_VERTICAL_ALIGN:"verticalAlign",STYLE_LABEL_WIDTH:"labelWidth",STYLE_LABEL_POSITION:"labelPosition",STYLE_VERTICAL_LABEL_POSITION:"verticalLabelPosition",STYLE_IMAGE_ASPECT:"imageA [...]
+STYLE_IMAGE_VERTICAL_ALIGN:"imageVerticalAlign",STYLE_GLASS:"glass",STYLE_IMAGE:"image",STYLE_IMAGE_WIDTH:"imageWidth",STYLE_IMAGE_HEIGHT:"imageHeight",STYLE_IMAGE_BACKGROUND:"imageBackground",STYLE_IMAGE_BORDER:"imageBorder",STYLE_FLIPH:"flipH",STYLE_FLIPV:"flipV",STYLE_NOLABEL:"noLabel",STYLE_NOEDGESTYLE:"noEdgeStyle",STYLE_LABEL_BACKGROUNDCOLOR:"labelBackgroundColor",STYLE_LABEL_BORDERCOLOR:"labelBorderColor",STYLE_LABEL_PADDING:"labelPadding",STYLE_INDICATOR_SHAPE:"indicatorShape",ST [...]
+STYLE_INDICATOR_COLOR:"indicatorColor",STYLE_INDICATOR_STROKECOLOR:"indicatorStrokeColor",STYLE_INDICATOR_GRADIENTCOLOR:"indicatorGradientColor",STYLE_INDICATOR_SPACING:"indicatorSpacing",STYLE_INDICATOR_WIDTH:"indicatorWidth",STYLE_INDICATOR_HEIGHT:"indicatorHeight",STYLE_INDICATOR_DIRECTION:"indicatorDirection",STYLE_SHADOW:"shadow",STYLE_SEGMENT:"segment",STYLE_ENDARROW:"endArrow",STYLE_STARTARROW:"startArrow",STYLE_ENDSIZE:"endSize",STYLE_STARTSIZE:"startSize",STYLE_SWIMLANE_LINE:"sw [...]
+STYLE_ENDFILL:"endFill",STYLE_STARTFILL:"startFill",STYLE_DASHED:"dashed",STYLE_DASH_PATTERN:"dashPattern",STYLE_FIX_DASH:"fixDash",STYLE_ROUNDED:"rounded",STYLE_CURVED:"curved",STYLE_ARCSIZE:"arcSize",STYLE_ABSOLUTE_ARCSIZE:"absoluteArcSize",STYLE_SOURCE_PERIMETER_SPACING:"sourcePerimeterSpacing",STYLE_TARGET_PERIMETER_SPACING:"targetPerimeterSpacing",STYLE_PERIMETER_SPACING:"perimeterSpacing",STYLE_SPACING:"spacing",STYLE_SPACING_TOP:"spacingTop",STYLE_SPACING_LEFT:"spacingLeft",STYLE_ [...]
+STYLE_SPACING_RIGHT:"spacingRight",STYLE_HORIZONTAL:"horizontal",STYLE_DIRECTION:"direction",STYLE_ELBOW:"elbow",STYLE_FONTCOLOR:"fontColor",STYLE_FONTFAMILY:"fontFamily",STYLE_FONTSIZE:"fontSize",STYLE_FONTSTYLE:"fontStyle",STYLE_ASPECT:"aspect",STYLE_AUTOSIZE:"autosize",STYLE_FOLDABLE:"foldable",STYLE_EDITABLE:"editable",STYLE_BENDABLE:"bendable",STYLE_MOVABLE:"movable",STYLE_RESIZABLE:"resizable",STYLE_RESIZE_WIDTH:"resizeWidth",STYLE_RESIZE_HEIGHT:"resizeHeight",STYLE_ROTATABLE:"rota [...]
+STYLE_DELETABLE:"deletable",STYLE_SHAPE:"shape",STYLE_EDGE:"edgeStyle",STYLE_JETTY_SIZE:"jettySize",STYLE_SOURCE_JETTY_SIZE:"sourceJettySize",STYLE_TARGET_JETTY_SIZE:"targetJettySize",STYLE_LOOP:"loopStyle",STYLE_ORTHOGONAL_LOOP:"orthogonalLoop",STYLE_ROUTING_CENTER_X:"routingCenterX",STYLE_ROUTING_CENTER_Y:"routingCenterY",FONT_BOLD:1,FONT_ITALIC:2,FONT_UNDERLINE:4,SHAPE_RECTANGLE:"rectangle",SHAPE_ELLIPSE:"ellipse",SHAPE_DOUBLE_ELLIPSE:"doubleEllipse",SHAPE_RHOMBUS:"rhombus",SHAPE_LINE [...]
+SHAPE_ARROW:"arrow",SHAPE_ARROW_CONNECTOR:"arrowConnector",SHAPE_LABEL:"label",SHAPE_CYLINDER:"cylinder",SHAPE_SWIMLANE:"swimlane",SHAPE_CONNECTOR:"connector",SHAPE_ACTOR:"actor",SHAPE_CLOUD:"cloud",SHAPE_TRIANGLE:"triangle",SHAPE_HEXAGON:"hexagon",ARROW_CLASSIC:"classic",ARROW_CLASSIC_THIN:"classicThin",ARROW_BLOCK:"block",ARROW_BLOCK_THIN:"blockThin",ARROW_OPEN:"open",ARROW_OPEN_THIN:"openThin",ARROW_OVAL:"oval",ARROW_DIAMOND:"diamond",ARROW_DIAMOND_THIN:"diamondThin",ALIGN_LEFT:"left" [...]
+ALIGN_RIGHT:"right",ALIGN_TOP:"top",ALIGN_MIDDLE:"middle",ALIGN_BOTTOM:"bottom",DIRECTION_NORTH:"north",DIRECTION_SOUTH:"south",DIRECTION_EAST:"east",DIRECTION_WEST:"west",TEXT_DIRECTION_DEFAULT:"",TEXT_DIRECTION_AUTO:"auto",TEXT_DIRECTION_LTR:"ltr",TEXT_DIRECTION_RTL:"rtl",DIRECTION_MASK_NONE:0,DIRECTION_MASK_WEST:1,DIRECTION_MASK_NORTH:2,DIRECTION_MASK_SOUTH:4,DIRECTION_MASK_EAST:8,DIRECTION_MASK_ALL:15,ELBOW_VERTICAL:"vertical",ELBOW_HORIZONTAL:"horizontal",EDGESTYLE_ELBOW:"elbowEdgeS [...]
+EDGESTYLE_LOOP:"loopEdgeStyle",EDGESTYLE_SIDETOSIDE:"sideToSideEdgeStyle",EDGESTYLE_TOPTOBOTTOM:"topToBottomEdgeStyle",EDGESTYLE_ORTHOGONAL:"orthogonalEdgeStyle",EDGESTYLE_SEGMENT:"segmentEdgeStyle",PERIMETER_ELLIPSE:"ellipsePerimeter",PERIMETER_RECTANGLE:"rectanglePerimeter",PERIMETER_RHOMBUS:"rhombusPerimeter",PERIMETER_HEXAGON:"hexagonPerimeter",PERIMETER_TRIANGLE:"trianglePerimeter"};
+function mxEventObject(a){this.name=a;this.properties=[];for(var b=1;b<arguments.length;b+=2)null!=arguments[b+1]&&(this.properties[arguments[b]]=arguments[b+1])}mxEventObject.prototype.name=null;mxEventObject.prototype.properties=null;mxEventObject.prototype.consumed=!1;mxEventObject.prototype.getName=function(){return this.name};mxEventObject.prototype.getProperties=function(){return this.properties};mxEventObject.prototype.getProperty=function(a){return this.properties[a]};
+mxEventObject.prototype.isConsumed=function(){return this.consumed};mxEventObject.prototype.consume=function(){this.consumed=!0};function mxMouseEvent(a,b){this.evt=a;this.sourceState=this.state=b}mxMouseEvent.prototype.consumed=!1;mxMouseEvent.prototype.evt=null;mxMouseEvent.prototype.graphX=null;mxMouseEvent.prototype.graphY=null;mxMouseEvent.prototype.state=null;mxMouseEvent.prototype.sourceState=null;mxMouseEvent.prototype.getEvent=function(){return this.evt};
+mxMouseEvent.prototype.getSource=function(){return mxEvent.getSource(this.evt)};mxMouseEvent.prototype.isSource=function(a){return null!=a?mxUtils.isAncestorNode(a.node,this.getSource()):!1};mxMouseEvent.prototype.getX=function(){return mxEvent.getClientX(this.getEvent())};mxMouseEvent.prototype.getY=function(){return mxEvent.getClientY(this.getEvent())};mxMouseEvent.prototype.getGraphX=function(){return this.graphX};mxMouseEvent.prototype.getGraphY=function(){return this.graphY};
+mxMouseEvent.prototype.getState=function(){return this.state};mxMouseEvent.prototype.getCell=function(){var a=this.getState();return null!=a?a.cell:null};mxMouseEvent.prototype.isPopupTrigger=function(){return mxEvent.isPopupTrigger(this.getEvent())};mxMouseEvent.prototype.isConsumed=function(){return this.consumed};mxMouseEvent.prototype.consume=function(a){(null!=a?a:1)&&this.evt.preventDefault&&this.evt.preventDefault();mxClient.IS_IE&&(this.evt.returnValue=!0);this.consumed=!0};
+function mxEventSource(a){this.setEventSource(a)}mxEventSource.prototype.eventListeners=null;mxEventSource.prototype.eventsEnabled=!0;mxEventSource.prototype.eventSource=null;mxEventSource.prototype.isEventsEnabled=function(){return this.eventsEnabled};mxEventSource.prototype.setEventsEnabled=function(a){this.eventsEnabled=a};mxEventSource.prototype.getEventSource=function(){return this.eventSource};mxEventSource.prototype.setEventSource=function(a){this.eventSource=a};
+mxEventSource.prototype.addListener=function(a,b){null==this.eventListeners&&(this.eventListeners=[]);this.eventListeners.push(a);this.eventListeners.push(b)};mxEventSource.prototype.removeListener=function(a){if(null!=this.eventListeners)for(var b=0;b<this.eventListeners.length;)this.eventListeners[b+1]==a?this.eventListeners.splice(b,2):b+=2};
+mxEventSource.prototype.fireEvent=function(a,b){if(null!=this.eventListeners&&this.isEventsEnabled()){null==a&&(a=new mxEventObject);null==b&&(b=this.getEventSource());null==b&&(b=this);for(var c=[b,a],d=0;d<this.eventListeners.length;d+=2){var e=this.eventListeners[d];null!=e&&e!=a.getName()||this.eventListeners[d+1].apply(this,c)}}};
+var mxEvent={objects:[],addListener:function(){var a=function(a,c,d){null==a.mxListenerList&&(a.mxListenerList=[],mxEvent.objects.push(a));a.mxListenerList.push({name:c,f:d})};return window.addEventListener?function(b,c,d){b.addEventListener(c,d,!1);a(b,c,d)}:function(b,c,d){b.attachEvent("on"+c,d);a(b,c,d)}}(),removeListener:function(){var a=function(a,c,d){if(null!=a.mxListenerList){c=a.mxListenerList.length;for(var b=0;b<c;b++)if(a.mxListenerList[b].f==d){a.mxListenerList.splice(b,1); [...]
+a.mxListenerList.length&&(a.mxListenerList=null,a=mxUtils.indexOf(mxEvent.objects,a),0<=a&&mxEvent.objects.splice(a,1))}};return window.removeEventListener?function(b,c,d){b.removeEventListener(c,d,!1);a(b,c,d)}:function(b,c,d){b.detachEvent("on"+c,d);a(b,c,d)}}(),removeAllListeners:function(a){var b=a.mxListenerList;if(null!=b)for(;0<b.length;){var c=b[0];mxEvent.removeListener(a,c.name,c.f)}},addGestureListeners:function(a,b,c,d){null!=b&&mxEvent.addListener(a,mxClient.IS_POINTER?"poin [...]
+"mousedown",b);null!=c&&mxEvent.addListener(a,mxClient.IS_POINTER?"pointermove":"mousemove",c);null!=d&&mxEvent.addListener(a,mxClient.IS_POINTER?"pointerup":"mouseup",d);!mxClient.IS_POINTER&&mxClient.IS_TOUCH&&(null!=b&&mxEvent.addListener(a,"touchstart",b),null!=c&&mxEvent.addListener(a,"touchmove",c),null!=d&&mxEvent.addListener(a,"touchend",d))},removeGestureListeners:function(a,b,c,d){null!=b&&mxEvent.removeListener(a,mxClient.IS_POINTER?"pointerdown":"mousedown",b);null!=c&&mxEven [...]
+mxClient.IS_POINTER?"pointermove":"mousemove",c);null!=d&&mxEvent.removeListener(a,mxClient.IS_POINTER?"pointerup":"mouseup",d);!mxClient.IS_POINTER&&mxClient.IS_TOUCH&&(null!=b&&mxEvent.removeListener(a,"touchstart",b),null!=c&&mxEvent.removeListener(a,"touchmove",c),null!=d&&mxEvent.removeListener(a,"touchend",d))},redirectMouseEvents:function(a,b,c,d,e,f,g){var k=function(a){return"function"==typeof c?c(a):c};mxEvent.addGestureListeners(a,function(a){null!=d?d(a):mxEvent.isConsumed(a) [...]
+new mxMouseEvent(a,k(a)))},function(a){null!=e?e(a):mxEvent.isConsumed(a)||b.fireMouseEvent(mxEvent.MOUSE_MOVE,new mxMouseEvent(a,k(a)))},function(a){null!=f?f(a):mxEvent.isConsumed(a)||b.fireMouseEvent(mxEvent.MOUSE_UP,new mxMouseEvent(a,k(a)))});mxEvent.addListener(a,"dblclick",function(a){if(null!=g)g(a);else if(!mxEvent.isConsumed(a)){var c=k(a);b.dblClick(a,null!=c?c.cell:null)}})},release:function(a){if(null!=a&&(mxEvent.removeAllListeners(a),a=a.childNodes,null!=a))for(var b=a.len [...]
+b;c+=1)mxEvent.release(a[c])},addMouseWheelListener:function(a){if(null!=a){var b=function(b){null==b&&(b=window.event);var c;c=mxClient.IS_FF?-b.detail/2:b.wheelDelta/120;0!=c&&a(b,0<c)};mxClient.IS_NS&&null==document.documentMode?mxEvent.addListener(window,mxClient.IS_SF||mxClient.IS_GC?"mousewheel":"DOMMouseScroll",b):mxEvent.addListener(document,"mousewheel",b)}},disableContextMenu:function(){return mxClient.IS_IE&&("undefined"===typeof document.documentMode||9>document.documentMode) [...]
+"contextmenu",function(){return!1})}:function(a){a.setAttribute("oncontextmenu","return false;")}}(),getSource:function(a){return null!=a.srcElement?a.srcElement:a.target},isConsumed:function(a){return null!=a.isConsumed&&a.isConsumed},isTouchEvent:function(a){return null!=a.pointerType?"touch"==a.pointerType||a.pointerType===a.MSPOINTER_TYPE_TOUCH:null!=a.mozInputSource?5==a.mozInputSource:0==a.type.indexOf("touch")},isPenEvent:function(a){return null!=a.pointerType?"pen"==a.pointerType [...]
+a.MSPOINTER_TYPE_PEN:null!=a.mozInputSource?2==a.mozInputSource:0==a.type.indexOf("pen")},isMultiTouchEvent:function(a){return null!=a.type&&0==a.type.indexOf("touch")&&null!=a.touches&&1<a.touches.length},isMouseEvent:function(a){return null!=a.pointerType?"mouse"==a.pointerType||a.pointerType===a.MSPOINTER_TYPE_MOUSE:null!=a.mozInputSource?1==a.mozInputSource:0==a.type.indexOf("mouse")},isLeftMouseButton:function(a){return"buttons"in a&&("mousedown"==a.type||"mousemove"==a.type)?1==a.b [...]
+a?1===a.which:1===a.button},isMiddleMouseButton:function(a){return"which"in a?2===a.which:4===a.button},isRightMouseButton:function(a){return"which"in a?3===a.which:2===a.button},isPopupTrigger:function(a){return mxEvent.isRightMouseButton(a)||mxClient.IS_MAC&&mxEvent.isControlDown(a)&&!mxEvent.isShiftDown(a)&&!mxEvent.isMetaDown(a)&&!mxEvent.isAltDown(a)},isShiftDown:function(a){return null!=a?a.shiftKey:!1},isAltDown:function(a){return null!=a?a.altKey:!1},isControlDown:function(a){ret [...]
+a?a.ctrlKey:!1},isMetaDown:function(a){return null!=a?a.metaKey:!1},getMainEvent:function(a){"touchstart"!=a.type&&"touchmove"!=a.type||null==a.touches||null==a.touches[0]?"touchend"==a.type&&null!=a.changedTouches&&null!=a.changedTouches[0]&&(a=a.changedTouches[0]):a=a.touches[0];return a},getClientX:function(a){return mxEvent.getMainEvent(a).clientX},getClientY:function(a){return mxEvent.getMainEvent(a).clientY},consume:function(a,b,c){c=null!=c?c:!0;if(null!=b?b:1)a.preventDefault?(c& [...]
+a.preventDefault()):c&&(a.cancelBubble=!0);a.isConsumed=!0;a.preventDefault||(a.returnValue=!1)},LABEL_HANDLE:-1,ROTATION_HANDLE:-2,CUSTOM_HANDLE:-100,VIRTUAL_HANDLE:-1E5,MOUSE_DOWN:"mouseDown",MOUSE_MOVE:"mouseMove",MOUSE_UP:"mouseUp",ACTIVATE:"activate",RESIZE_START:"resizeStart",RESIZE:"resize",RESIZE_END:"resizeEnd",MOVE_START:"moveStart",MOVE:"move",MOVE_END:"moveEnd",PAN_START:"panStart",PAN:"pan",PAN_END:"panEnd",MINIMIZE:"minimize",NORMALIZE:"normalize",MAXIMIZE:"maximize",HIDE:" [...]
+CLOSE:"close",DESTROY:"destroy",REFRESH:"refresh",SIZE:"size",SELECT:"select",FIRED:"fired",FIRE_MOUSE_EVENT:"fireMouseEvent",GESTURE:"gesture",TAP_AND_HOLD:"tapAndHold",GET:"get",RECEIVE:"receive",CONNECT:"connect",DISCONNECT:"disconnect",SUSPEND:"suspend",RESUME:"resume",MARK:"mark",ROOT:"root",POST:"post",OPEN:"open",SAVE:"save",BEFORE_ADD_VERTEX:"beforeAddVertex",ADD_VERTEX:"addVertex",AFTER_ADD_VERTEX:"afterAddVertex",DONE:"done",EXECUTE:"execute",EXECUTED:"executed",BEGIN_UPDATE:"b [...]
+START_EDIT:"startEdit",END_UPDATE:"endUpdate",END_EDIT:"endEdit",BEFORE_UNDO:"beforeUndo",UNDO:"undo",REDO:"redo",CHANGE:"change",NOTIFY:"notify",LAYOUT_CELLS:"layoutCells",CLICK:"click",SCALE:"scale",TRANSLATE:"translate",SCALE_AND_TRANSLATE:"scaleAndTranslate",UP:"up",DOWN:"down",ADD:"add",REMOVE:"remove",CLEAR:"clear",ADD_CELLS:"addCells",CELLS_ADDED:"cellsAdded",MOVE_CELLS:"moveCells",CELLS_MOVED:"cellsMoved",RESIZE_CELLS:"resizeCells",CELLS_RESIZED:"cellsResized",TOGGLE_CELLS:"toggl [...]
+ORDER_CELLS:"orderCells",CELLS_ORDERED:"cellsOrdered",REMOVE_CELLS:"removeCells",CELLS_REMOVED:"cellsRemoved",GROUP_CELLS:"groupCells",UNGROUP_CELLS:"ungroupCells",REMOVE_CELLS_FROM_PARENT:"removeCellsFromParent",FOLD_CELLS:"foldCells",CELLS_FOLDED:"cellsFolded",ALIGN_CELLS:"alignCells",LABEL_CHANGED:"labelChanged",CONNECT_CELL:"connectCell",CELL_CONNECTED:"cellConnected",SPLIT_EDGE:"splitEdge",FLIP_EDGE:"flipEdge",START_EDITING:"startEditing",EDITING_STARTED:"editingStarted",EDITING_STO [...]
+ADD_OVERLAY:"addOverlay",REMOVE_OVERLAY:"removeOverlay",UPDATE_CELL_SIZE:"updateCellSize",ESCAPE:"escape",DOUBLE_CLICK:"doubleClick",START:"start",RESET:"reset"};function mxXmlRequest(a,b,c,d,e,f){this.url=a;this.params=b;this.method=c||"POST";this.async=null!=d?d:!0;this.username=e;this.password=f}mxXmlRequest.prototype.url=null;mxXmlRequest.prototype.params=null;mxXmlRequest.prototype.method=null;mxXmlRequest.prototype.async=null;mxXmlRequest.prototype.binary=!1;
+mxXmlRequest.prototype.withCredentials=!1;mxXmlRequest.prototype.username=null;mxXmlRequest.prototype.password=null;mxXmlRequest.prototype.request=null;mxXmlRequest.prototype.decodeSimulateValues=!1;mxXmlRequest.prototype.isBinary=function(){return this.binary};mxXmlRequest.prototype.setBinary=function(a){this.binary=a};mxXmlRequest.prototype.getText=function(){return this.request.responseText};mxXmlRequest.prototype.isReady=function(){return 4==this.request.readyState};
+mxXmlRequest.prototype.getDocumentElement=function(){var a=this.getXml();return null!=a?a.documentElement:null};mxXmlRequest.prototype.getXml=function(){var a=this.request.responseXML;if(9<=document.documentMode||null==a||null==a.documentElement)a=mxUtils.parseXml(this.request.responseText);return a};mxXmlRequest.prototype.getText=function(){return this.request.responseText};mxXmlRequest.prototype.getStatus=function(){return this.request.status};
+mxXmlRequest.prototype.create=function(){if(window.XMLHttpRequest)return function(){var a=new XMLHttpRequest;this.isBinary()&&a.overrideMimeType&&a.overrideMimeType("text/plain; charset=x-user-defined");return a};if("undefined"!=typeof ActiveXObject)return function(){return new ActiveXObject("Microsoft.XMLHTTP")}}();
+mxXmlRequest.prototype.send=function(a,b,c,d){this.request=this.create();null!=this.request&&(null!=a&&(this.request.onreadystatechange=mxUtils.bind(this,function(){this.isReady()&&(a(this),this.onreadystatechaange=null)})),this.request.open(this.method,this.url,this.async,this.username,this.password),this.setRequestHeaders(this.request,this.params),window.XMLHttpRequest&&this.withCredentials&&(this.request.withCredentials="true"),!mxClient.IS_QUIRKS&&(null==document.documentMode||9<docu [...]
+window.XMLHttpRequest&&null!=c&&null!=d&&(this.request.timeout=c,this.request.ontimeout=d),this.request.send(this.params))};mxXmlRequest.prototype.setRequestHeaders=function(a,b){null!=b&&a.setRequestHeader("Content-Type","application/x-www-form-urlencoded")};
+mxXmlRequest.prototype.simulate=function(a,b){a=a||document;var c=null;a==document&&(c=window.onbeforeunload,window.onbeforeunload=null);var d=a.createElement("form");d.setAttribute("method",this.method);d.setAttribute("action",this.url);null!=b&&d.setAttribute("target",b);d.style.display="none";d.style.visibility="hidden";for(var e=0<this.params.indexOf("&")?this.params.split("&"):this.params.split(),f=0;f<e.length;f++){var g=e[f].indexOf("=");if(0<g){var k=e[f].substring(0,g),g=e[f].su [...]
+1);this.decodeSimulateValues&&(g=decodeURIComponent(g));var l=a.createElement("textarea");l.setAttribute("wrap","off");l.setAttribute("name",k);mxUtils.write(l,g);d.appendChild(l)}}a.body.appendChild(d);d.submit();null!=d.parentNode&&d.parentNode.removeChild(d);null!=c&&(window.onbeforeunload=c)};
+var mxClipboard={STEPSIZE:10,insertCount:1,cells:null,setCells:function(a){mxClipboard.cells=a},getCells:function(){return mxClipboard.cells},isEmpty:function(){return null==mxClipboard.getCells()},cut:function(a,b){b=mxClipboard.copy(a,b);mxClipboard.insertCount=0;mxClipboard.removeCells(a,b);return b},removeCells:function(a,b){a.removeCells(b)},copy:function(a,b){b=b||a.getSelectionCells();var c=a.getExportableCells(a.model.getTopmostCells(b));mxClipboard.insertCount=1;mxClipboard.setC [...]
+return c},paste:function(a){var b=null;if(!mxClipboard.isEmpty()){var b=a.getImportableCells(mxClipboard.getCells()),c=mxClipboard.insertCount*mxClipboard.STEPSIZE,d=a.getDefaultParent(),b=a.importCells(b,c,c,d);mxClipboard.insertCount++;a.setSelectionCells(b)}return b}};
+function mxWindow(a,b,c,d,e,f,g,k,l,m){null!=b&&(g=null!=g?g:!0,this.content=b,this.init(c,d,e,f,m),this.installMaximizeHandler(),this.installMinimizeHandler(),this.installCloseHandler(),this.setMinimizable(g),this.setTitle(a),(null==k||k)&&this.installMoveHandler(),null!=l&&null!=l.parentNode?l.parentNode.replaceChild(this.div,l):document.body.appendChild(this.div))}mxWindow.prototype=new mxEventSource;mxWindow.prototype.constructor=mxWindow;mxWindow.prototype.closeImage=mxClient.imageB [...]
+mxWindow.prototype.minimizeImage=mxClient.imageBasePath+"/minimize.gif";mxWindow.prototype.normalizeImage=mxClient.imageBasePath+"/normalize.gif";mxWindow.prototype.maximizeImage=mxClient.imageBasePath+"/maximize.gif";mxWindow.prototype.resizeImage=mxClient.imageBasePath+"/resize.gif";mxWindow.prototype.visible=!1;mxWindow.prototype.minimumSize=new mxRectangle(0,0,50,40);mxWindow.prototype.destroyOnClose=!0;
+mxWindow.prototype.contentHeightCorrection=8==document.documentMode||7==document.documentMode?6:2;mxWindow.prototype.title=null;mxWindow.prototype.content=null;
+mxWindow.prototype.init=function(a,b,c,d,e){e=null!=e?e:"mxWindow";this.div=document.createElement("div");this.div.className=e;this.div.style.left=a+"px";this.div.style.top=b+"px";this.table=document.createElement("table");this.table.className=e;mxClient.IS_POINTER&&(this.div.style.touchAction="none");null!=c&&(mxClient.IS_QUIRKS||(this.div.style.width=c+"px"),this.table.style.width=c+"px");null!=d&&(mxClient.IS_QUIRKS||(this.div.style.height=d+"px"),this.table.style.height=d+"px");a=doc [...]
+b=document.createElement("tr");this.title=document.createElement("td");this.title.className=e+"Title";this.buttons=document.createElement("div");this.buttons.style.position="absolute";this.buttons.style.display="inline-block";this.buttons.style.right="4px";this.buttons.style.top="5px";this.title.appendChild(this.buttons);b.appendChild(this.title);a.appendChild(b);b=document.createElement("tr");this.td=document.createElement("td");this.td.className=e+"Pane";7==document.documentMode&&(this [...]
+"100%");this.contentWrapper=document.createElement("div");this.contentWrapper.className=e+"Pane";this.contentWrapper.style.width="100%";this.contentWrapper.appendChild(this.content);if(mxClient.IS_QUIRKS||"DIV"!=this.content.nodeName.toUpperCase())this.contentWrapper.style.height="100%";this.td.appendChild(this.contentWrapper);b.appendChild(this.td);a.appendChild(b);this.table.appendChild(a);this.div.appendChild(this.table);e=mxUtils.bind(this,function(a){this.activate()});mxEvent.addGes [...]
+e);mxEvent.addGestureListeners(this.table,e);this.hide()};mxWindow.prototype.setTitle=function(a){for(var b=this.title.firstChild;null!=b;){var c=b.nextSibling;b.nodeType==mxConstants.NODETYPE_TEXT&&b.parentNode.removeChild(b);b=c}mxUtils.write(this.title,a||"");this.title.appendChild(this.buttons)};mxWindow.prototype.setScrollable=function(a){0>navigator.userAgent.indexOf("Presto/2.5")&&(this.contentWrapper.style.overflow=a?"auto":"hidden")};
+mxWindow.prototype.activate=function(){if(mxWindow.activeWindow!=this){var a=mxUtils.getCurrentStyle(this.getElement()),a=null!=a?a.zIndex:3;if(mxWindow.activeWindow){var b=mxWindow.activeWindow.getElement();null!=b&&null!=b.style&&(b.style.zIndex=a)}b=mxWindow.activeWindow;this.getElement().style.zIndex=parseInt(a)+1;mxWindow.activeWindow=this;this.fireEvent(new mxEventObject(mxEvent.ACTIVATE,"previousWindow",b))}};mxWindow.prototype.getElement=function(){return this.div};
+mxWindow.prototype.fit=function(){mxUtils.fit(this.div)};mxWindow.prototype.isResizable=function(){return null!=this.resize?"none"!=this.resize.style.display:!1};
+mxWindow.prototype.setResizable=function(a){if(a)if(null==this.resize){this.resize=document.createElement("img");this.resize.style.position="absolute";this.resize.style.bottom="2px";this.resize.style.right="2px";this.resize.setAttribute("src",mxClient.imageBasePath+"/resize.gif");this.resize.style.cursor="nw-resize";var b=null,c=null,d=null,e=null;a=mxUtils.bind(this,function(a){this.activate();b=mxEvent.getClientX(a);c=mxEvent.getClientY(a);d=this.div.offsetWidth;e=this.div.offsetHeight [...]
+null,f,g);this.fireEvent(new mxEventObject(mxEvent.RESIZE_START,"event",a));mxEvent.consume(a)});var f=mxUtils.bind(this,function(a){if(null!=b&&null!=c){var f=mxEvent.getClientX(a)-b,g=mxEvent.getClientY(a)-c;this.setSize(d+f,e+g);this.fireEvent(new mxEventObject(mxEvent.RESIZE,"event",a));mxEvent.consume(a)}}),g=mxUtils.bind(this,function(a){null!=b&&null!=c&&(c=b=null,mxEvent.removeGestureListeners(document,null,f,g),this.fireEvent(new mxEventObject(mxEvent.RESIZE_END,"event",a)),mxEv [...]
+mxEvent.addGestureListeners(this.resize,a,f,g);this.div.appendChild(this.resize)}else this.resize.style.display="inline";else null!=this.resize&&(this.resize.style.display="none")};
+mxWindow.prototype.setSize=function(a,b){a=Math.max(this.minimumSize.width,a);b=Math.max(this.minimumSize.height,b);mxClient.IS_QUIRKS||(this.div.style.width=a+"px",this.div.style.height=b+"px");this.table.style.width=a+"px";this.table.style.height=b+"px";mxClient.IS_QUIRKS||(this.contentWrapper.style.height=this.div.offsetHeight-this.title.offsetHeight-this.contentHeightCorrection+"px")};mxWindow.prototype.setMinimizable=function(a){this.minimize.style.display=a?"":"none"};
+mxWindow.prototype.getMinimumSize=function(){return new mxRectangle(0,0,0,this.title.offsetHeight)};
+mxWindow.prototype.installMinimizeHandler=function(){this.minimize=document.createElement("img");this.minimize.setAttribute("src",this.minimizeImage);this.minimize.setAttribute("title","Minimize");this.minimize.style.cursor="pointer";this.minimize.style.marginLeft="2px";this.minimize.style.display="none";this.buttons.appendChild(this.minimize);var a=!1,b=null,c=null,d=mxUtils.bind(this,function(d){this.activate();if(a)a=!1,this.minimize.setAttribute("src",this.minimizeImage),this.minimiz [...]
+"Minimize"),this.contentWrapper.style.display="",this.maximize.style.display=b,mxClient.IS_QUIRKS||(this.div.style.height=c),this.table.style.height=c,null!=this.resize&&(this.resize.style.visibility=""),this.fireEvent(new mxEventObject(mxEvent.NORMALIZE,"event",d));else{a=!0;this.minimize.setAttribute("src",this.normalizeImage);this.minimize.setAttribute("title","Normalize");this.contentWrapper.style.display="none";b=this.maximize.style.display;this.maximize.style.display="none";c=this. [...]
+var e=this.getMinimumSize();0<e.height&&(mxClient.IS_QUIRKS||(this.div.style.height=e.height+"px"),this.table.style.height=e.height+"px");0<e.width&&(mxClient.IS_QUIRKS||(this.div.style.width=e.width+"px"),this.table.style.width=e.width+"px");null!=this.resize&&(this.resize.style.visibility="hidden");this.fireEvent(new mxEventObject(mxEvent.MINIMIZE,"event",d))}mxEvent.consume(d)});mxEvent.addGestureListeners(this.minimize,d)};
+mxWindow.prototype.setMaximizable=function(a){this.maximize.style.display=a?"":"none"};
+mxWindow.prototype.installMaximizeHandler=function(){this.maximize=document.createElement("img");this.maximize.setAttribute("src",this.maximizeImage);this.maximize.setAttribute("title","Maximize");this.maximize.style.cursor="default";this.maximize.style.marginLeft="2px";this.maximize.style.cursor="pointer";this.maximize.style.display="none";this.buttons.appendChild(this.maximize);var a=!1,b=null,c=null,d=null,e=null,f=null,g=mxUtils.bind(this,function(g){this.activate();if("none"!=this.m [...]
+!1,this.maximize.setAttribute("src",this.maximizeImage),this.maximize.setAttribute("title","Maximize"),this.contentWrapper.style.display="",this.minimize.style.display=f,this.div.style.left=b+"px",this.div.style.top=c+"px",mxClient.IS_QUIRKS||(this.div.style.height=d,this.div.style.width=e,k=mxUtils.getCurrentStyle(this.contentWrapper),"auto"!=k.overflow&&null==this.resize)||(this.contentWrapper.style.height=this.div.offsetHeight-this.title.offsetHeight-this.contentHeightCorrection+"px") [...]
+d,this.table.style.width=e,null!=this.resize&&(this.resize.style.visibility=""),this.fireEvent(new mxEventObject(mxEvent.NORMALIZE,"event",g));else{a=!0;this.maximize.setAttribute("src",this.normalizeImage);this.maximize.setAttribute("title","Normalize");this.contentWrapper.style.display="";f=this.minimize.style.display;this.minimize.style.display="none";b=parseInt(this.div.style.left);c=parseInt(this.div.style.top);d=this.table.style.height;e=this.table.style.width;this.div.style.left=" [...]
+"0px";k=Math.max(document.body.clientHeight||0,document.documentElement.clientHeight||0);mxClient.IS_QUIRKS||(this.div.style.width=document.body.clientWidth-2+"px",this.div.style.height=k-2+"px");this.table.style.width=document.body.clientWidth-2+"px";this.table.style.height=k-2+"px";null!=this.resize&&(this.resize.style.visibility="hidden");if(!mxClient.IS_QUIRKS){var k=mxUtils.getCurrentStyle(this.contentWrapper);if("auto"==k.overflow||null!=this.resize)this.contentWrapper.style.height [...]
+this.title.offsetHeight-this.contentHeightCorrection+"px"}this.fireEvent(new mxEventObject(mxEvent.MAXIMIZE,"event",g))}mxEvent.consume(g)}});mxEvent.addGestureListeners(this.maximize,g);mxEvent.addListener(this.title,"dblclick",g)};
+mxWindow.prototype.installMoveHandler=function(){this.title.style.cursor="move";mxEvent.addGestureListeners(this.title,mxUtils.bind(this,function(a){var b=mxEvent.getClientX(a),c=mxEvent.getClientY(a),d=this.getX(),e=this.getY(),f=mxUtils.bind(this,function(a){var f=mxEvent.getClientX(a)-b,g=mxEvent.getClientY(a)-c;this.setLocation(d+f,e+g);this.fireEvent(new mxEventObject(mxEvent.MOVE,"event",a));mxEvent.consume(a)}),g=mxUtils.bind(this,function(a){mxEvent.removeGestureListeners(documen [...]
+g);this.fireEvent(new mxEventObject(mxEvent.MOVE_END,"event",a));mxEvent.consume(a)});mxEvent.addGestureListeners(document,null,f,g);this.fireEvent(new mxEventObject(mxEvent.MOVE_START,"event",a));mxEvent.consume(a)}));mxClient.IS_POINTER&&(this.title.style.touchAction="none")};mxWindow.prototype.setLocation=function(a,b){this.div.style.left=a+"px";this.div.style.top=b+"px"};mxWindow.prototype.getX=function(){return parseInt(this.div.style.left)};mxWindow.prototype.getY=function(){return [...]
+mxWindow.prototype.installCloseHandler=function(){this.closeImg=document.createElement("img");this.closeImg.setAttribute("src",this.closeImage);this.closeImg.setAttribute("title","Close");this.closeImg.style.marginLeft="2px";this.closeImg.style.cursor="pointer";this.closeImg.style.display="none";this.buttons.appendChild(this.closeImg);mxEvent.addGestureListeners(this.closeImg,mxUtils.bind(this,function(a){this.fireEvent(new mxEventObject(mxEvent.CLOSE,"event",a));this.destroyOnClose?this [...]
+this.setVisible(!1);mxEvent.consume(a)}))};mxWindow.prototype.setImage=function(a){this.image=document.createElement("img");this.image.setAttribute("src",a);this.image.setAttribute("align","left");this.image.style.marginRight="4px";this.image.style.marginLeft="0px";this.image.style.marginTop="-2px";this.title.insertBefore(this.image,this.title.firstChild)};mxWindow.prototype.setClosable=function(a){this.closeImg.style.display=a?"":"none"};
+mxWindow.prototype.isVisible=function(){return null!=this.div?"none"!=this.div.style.display:!1};mxWindow.prototype.setVisible=function(a){null!=this.div&&this.isVisible()!=a&&(a?this.show():this.hide())};
+mxWindow.prototype.show=function(){this.div.style.display="";this.activate();var a=mxUtils.getCurrentStyle(this.contentWrapper);mxClient.IS_QUIRKS||"auto"!=a.overflow&&null==this.resize||(this.contentWrapper.style.height=this.div.offsetHeight-this.title.offsetHeight-this.contentHeightCorrection+"px");this.fireEvent(new mxEventObject(mxEvent.SHOW))};mxWindow.prototype.hide=function(){this.div.style.display="none";this.fireEvent(new mxEventObject(mxEvent.HIDE))};
+mxWindow.prototype.destroy=function(){this.fireEvent(new mxEventObject(mxEvent.DESTROY));null!=this.div&&(mxEvent.release(this.div),this.div.parentNode.removeChild(this.div),this.div=null);this.contentWrapper=this.content=this.title=null};function mxForm(a){this.table=document.createElement("table");this.table.className=a;this.body=document.createElement("tbody");this.table.appendChild(this.body)}mxForm.prototype.table=null;mxForm.prototype.body=!1;mxForm.prototype.getTable=function(){re [...]
+mxForm.prototype.addButtons=function(a,b){var c=document.createElement("tr"),d=document.createElement("td");c.appendChild(d);var d=document.createElement("td"),e=document.createElement("button");mxUtils.write(e,mxResources.get("ok")||"OK");d.appendChild(e);mxEvent.addListener(e,"click",function(){a()});e=document.createElement("button");mxUtils.write(e,mxResources.get("cancel")||"Cancel");d.appendChild(e);mxEvent.addListener(e,"click",function(){b()});c.appendChild(d);this.body.appendChild(c)};
+mxForm.prototype.addText=function(a,b,c){var d=document.createElement("input");d.setAttribute("type",c||"text");d.value=b;return this.addField(a,d)};mxForm.prototype.addCheckbox=function(a,b){var c=document.createElement("input");c.setAttribute("type","checkbox");this.addField(a,c);b&&(c.checked=!0);return c};mxForm.prototype.addTextarea=function(a,b,c){var d=document.createElement("textarea");mxClient.IS_NS&&c--;d.setAttribute("rows",c||2);d.value=b;return this.addField(a,d)};
+mxForm.prototype.addCombo=function(a,b,c){var d=document.createElement("select");null!=c&&d.setAttribute("size",c);b&&d.setAttribute("multiple","true");return this.addField(a,d)};mxForm.prototype.addOption=function(a,b,c,d){var e=document.createElement("option");mxUtils.writeln(e,b);e.setAttribute("value",c);d&&e.setAttribute("selected",d);a.appendChild(e)};
+mxForm.prototype.addField=function(a,b){var c=document.createElement("tr"),d=document.createElement("td");mxUtils.write(d,a);c.appendChild(d);d=document.createElement("td");d.appendChild(b);c.appendChild(d);this.body.appendChild(c);return b};function mxImage(a,b,c){this.src=a;this.width=b;this.height=c}mxImage.prototype.src=null;mxImage.prototype.width=null;mxImage.prototype.height=null;
+function mxDivResizer(a,b){if("div"==a.nodeName.toLowerCase()){null==b&&(b=window);this.div=a;var c=mxUtils.getCurrentStyle(a);null!=c&&(this.resizeWidth="auto"==c.width,this.resizeHeight="auto"==c.height);mxEvent.addListener(b,"resize",mxUtils.bind(this,function(a){this.handlingResize||(this.handlingResize=!0,this.resize(),this.handlingResize=!1)}));this.resize()}}mxDivResizer.prototype.resizeWidth=!0;mxDivResizer.prototype.resizeHeight=!0;mxDivResizer.prototype.handlingResize=!1;
+mxDivResizer.prototype.resize=function(){var a=this.getDocumentWidth(),b=this.getDocumentHeight(),c=parseInt(this.div.style.left),d=parseInt(this.div.style.right),e=parseInt(this.div.style.top),f=parseInt(this.div.style.bottom);this.resizeWidth&&!isNaN(c)&&!isNaN(d)&&0<=c&&0<=d&&0<a-d-c&&(this.div.style.width=a-d-c+"px");this.resizeHeight&&!isNaN(e)&&!isNaN(f)&&0<=e&&0<=f&&0<b-e-f&&(this.div.style.height=b-e-f+"px")};mxDivResizer.prototype.getDocumentWidth=function(){return document.body [...]
+mxDivResizer.prototype.getDocumentHeight=function(){return document.body.clientHeight};function mxDragSource(a,b){this.element=a;this.dropHandler=b;mxEvent.addGestureListeners(a,mxUtils.bind(this,function(a){this.mouseDown(a)}));mxEvent.addListener(a,"dragstart",function(a){mxEvent.consume(a)});this.eventConsumer=function(a,b){var c=b.getProperty("eventName"),d=b.getProperty("event");c!=mxEvent.MOUSE_DOWN&&d.consume()}}mxDragSource.prototype.element=null;mxDragSource.prototype.dropHandler=null;
+mxDragSource.prototype.dragOffset=null;mxDragSource.prototype.dragElement=null;mxDragSource.prototype.previewElement=null;mxDragSource.prototype.enabled=!0;mxDragSource.prototype.currentGraph=null;mxDragSource.prototype.currentDropTarget=null;mxDragSource.prototype.currentPoint=null;mxDragSource.prototype.currentGuide=null;mxDragSource.prototype.currentHighlight=null;mxDragSource.prototype.autoscroll=!0;mxDragSource.prototype.guidesEnabled=!0;mxDragSource.prototype.gridEnabled=!0;
+mxDragSource.prototype.highlightDropTargets=!0;mxDragSource.prototype.dragElementZIndex=100;mxDragSource.prototype.dragElementOpacity=70;mxDragSource.prototype.isEnabled=function(){return this.enabled};mxDragSource.prototype.setEnabled=function(a){this.enabled=a};mxDragSource.prototype.isGuidesEnabled=function(){return this.guidesEnabled};mxDragSource.prototype.setGuidesEnabled=function(a){this.guidesEnabled=a};mxDragSource.prototype.isGridEnabled=function(){return this.gridEnabled};
+mxDragSource.prototype.setGridEnabled=function(a){this.gridEnabled=a};mxDragSource.prototype.getGraphForEvent=function(a){return null};mxDragSource.prototype.getDropTarget=function(a,b,c,d){return a.getCellAt(b,c)};mxDragSource.prototype.createDragElement=function(a){return this.element.cloneNode(!0)};mxDragSource.prototype.createPreviewElement=function(a){return null};mxDragSource.prototype.isActive=function(){return null!=this.mouseMoveHandler};
+mxDragSource.prototype.reset=function(){null!=this.currentGraph&&(this.dragExit(this.currentGraph),this.currentGraph=null);this.removeDragElement();this.removeListeners();this.stopDrag()};
+mxDragSource.prototype.mouseDown=function(a){this.enabled&&!mxEvent.isConsumed(a)&&null==this.mouseMoveHandler&&(this.startDrag(a),this.mouseMoveHandler=mxUtils.bind(this,this.mouseMove),this.mouseUpHandler=mxUtils.bind(this,this.mouseUp),mxEvent.addGestureListeners(document,null,this.mouseMoveHandler,this.mouseUpHandler),mxClient.IS_TOUCH&&!mxEvent.isMouseEvent(a)&&(this.eventSource=mxEvent.getSource(a),mxEvent.addGestureListeners(this.eventSource,null,this.mouseMoveHandler,this.mouseUp [...]
+mxDragSource.prototype.startDrag=function(a){this.dragElement=this.createDragElement(a);this.dragElement.style.position="absolute";this.dragElement.style.zIndex=this.dragElementZIndex;mxUtils.setOpacity(this.dragElement,this.dragElementOpacity)};mxDragSource.prototype.stopDrag=function(){this.removeDragElement()};
+mxDragSource.prototype.removeDragElement=function(){null!=this.dragElement&&(null!=this.dragElement.parentNode&&this.dragElement.parentNode.removeChild(this.dragElement),this.dragElement=null)};mxDragSource.prototype.graphContainsEvent=function(a,b){var c=mxEvent.getClientX(b),d=mxEvent.getClientY(b),e=mxUtils.getOffset(a.container),f=mxUtils.getScrollOrigin();return c>=e.x-f.x&&d>=e.y-f.y&&c<=e.x-f.x+a.container.offsetWidth&&d<=e.y-f.y+a.container.offsetHeight};
+mxDragSource.prototype.mouseMove=function(a){var b=this.getGraphForEvent(a);null==b||this.graphContainsEvent(b,a)||(b=null);b!=this.currentGraph&&(null!=this.currentGraph&&this.dragExit(this.currentGraph,a),this.currentGraph=b,null!=this.currentGraph&&this.dragEnter(this.currentGraph,a));null!=this.currentGraph&&this.dragOver(this.currentGraph,a);if(null==this.dragElement||null!=this.previewElement&&"visible"==this.previewElement.style.visibility)null!=this.dragElement&&(this.dragElement [...]
+"hidden");else{var b=mxEvent.getClientX(a),c=mxEvent.getClientY(a);null==this.dragElement.parentNode&&document.body.appendChild(this.dragElement);this.dragElement.style.visibility="visible";null!=this.dragOffset&&(b+=this.dragOffset.x,c+=this.dragOffset.y);var d=mxUtils.getDocumentScrollOrigin(document);this.dragElement.style.left=b+d.x+"px";this.dragElement.style.top=c+d.y+"px"}mxEvent.consume(a)};
+mxDragSource.prototype.mouseUp=function(a){if(null!=this.currentGraph){if(null!=this.currentPoint&&(null==this.previewElement||"hidden"!=this.previewElement.style.visibility)){var b=this.currentGraph.view.scale,c=this.currentGraph.view.translate;this.drop(this.currentGraph,a,this.currentDropTarget,this.currentPoint.x/b-c.x,this.currentPoint.y/b-c.y)}this.dragExit(this.currentGraph);this.currentGraph=null}this.stopDrag();this.removeListeners();mxEvent.consume(a)};
+mxDragSource.prototype.removeListeners=function(){null!=this.eventSource&&(mxEvent.removeGestureListeners(this.eventSource,null,this.mouseMoveHandler,this.mouseUpHandler),this.eventSource=null);mxEvent.removeGestureListeners(document,null,this.mouseMoveHandler,this.mouseUpHandler);this.mouseUpHandler=this.mouseMoveHandler=null};
+mxDragSource.prototype.dragEnter=function(a,b){a.isMouseDown=!0;a.isMouseTrigger=mxEvent.isMouseEvent(b);this.previewElement=this.createPreviewElement(a);this.isGuidesEnabled()&&null!=this.previewElement&&(this.currentGuide=new mxGuide(a,a.graphHandler.getGuideStates()));this.highlightDropTargets&&(this.currentHighlight=new mxCellHighlight(a,mxConstants.DROP_TARGET_COLOR));a.addListener(mxEvent.FIRE_MOUSE_EVENT,this.eventConsumer)};
+mxDragSource.prototype.dragExit=function(a,b){this.currentPoint=this.currentDropTarget=null;a.isMouseDown=!1;a.removeListener(this.eventConsumer);null!=this.previewElement&&(null!=this.previewElement.parentNode&&this.previewElement.parentNode.removeChild(this.previewElement),this.previewElement=null);null!=this.currentGuide&&(this.currentGuide.destroy(),this.currentGuide=null);null!=this.currentHighlight&&(this.currentHighlight.destroy(),this.currentHighlight=null)};
+mxDragSource.prototype.dragOver=function(a,b){var c=mxUtils.getOffset(a.container),d=mxUtils.getScrollOrigin(a.container),e=mxEvent.getClientX(b)-c.x+d.x-a.panDx,c=mxEvent.getClientY(b)-c.y+d.y-a.panDy;a.autoScroll&&(null==this.autoscroll||this.autoscroll)&&a.scrollPointToVisible(e,c,a.autoExtend);null!=this.currentHighlight&&a.isDropEnabled()&&(this.currentDropTarget=this.getDropTarget(a,e,c,b),d=a.getView().getState(this.currentDropTarget),this.currentHighlight.highlight(d));if(null!=t [...]
+this.previewElement.parentNode&&(a.container.appendChild(this.previewElement),this.previewElement.style.zIndex="3",this.previewElement.style.position="absolute");var d=this.isGridEnabled()&&a.isGridEnabledEvent(b),f=!0;if(null!=this.currentGuide&&this.currentGuide.isEnabledForEvent(b))var f=parseInt(this.previewElement.style.width),g=parseInt(this.previewElement.style.height),f=new mxRectangle(0,0,f,g),c=new mxPoint(e,c),c=this.currentGuide.move(f,c,d),f=!1,e=c.x,c=c.y;else if(d)var d=a. [...]
+g=a.view.translate,k=a.gridSize/2,e=(a.snap(e/d-g.x-k)+g.x)*d,c=(a.snap(c/d-g.y-k)+g.y)*d;null!=this.currentGuide&&f&&this.currentGuide.hide();null!=this.previewOffset&&(e+=this.previewOffset.x,c+=this.previewOffset.y);this.previewElement.style.left=Math.round(e)+"px";this.previewElement.style.top=Math.round(c)+"px";this.previewElement.style.visibility="visible"}this.currentPoint=new mxPoint(e,c)};
+mxDragSource.prototype.drop=function(a,b,c,d,e){this.dropHandler(a,b,c,d,e);"hidden"!=a.container.style.visibility&&a.container.focus()};function mxToolbar(a){this.container=a}mxToolbar.prototype=new mxEventSource;mxToolbar.prototype.constructor=mxToolbar;mxToolbar.prototype.container=null;mxToolbar.prototype.enabled=!0;mxToolbar.prototype.noReset=!1;mxToolbar.prototype.updateDefaultMode=!0;
+mxToolbar.prototype.addItem=function(a,b,c,d,e,f){var g=document.createElement(null!=b?"img":"button"),k=e||(null!=f?"mxToolbarMode":"mxToolbarItem");g.className=k;g.setAttribute("src",b);null!=a&&(null!=b?g.setAttribute("title",a):mxUtils.write(g,a));this.container.appendChild(g);null!=c&&(mxEvent.addListener(g,"click",c),mxClient.IS_TOUCH&&mxEvent.addListener(g,"touchend",c));a=mxUtils.bind(this,function(a){null!=d?g.setAttribute("src",b):g.style.backgroundColor=""});mxEvent.addGesture [...]
+mxUtils.bind(this,function(a){null!=d?g.setAttribute("src",d):g.style.backgroundColor="gray";if(null!=f){null==this.menu&&(this.menu=new mxPopupMenu,this.menu.init());var b=this.currentImg;this.menu.isMenuShowing()&&this.menu.hideMenu();b!=g&&(this.currentImg=g,this.menu.factoryMethod=f,b=new mxPoint(g.offsetLeft,g.offsetTop+g.offsetHeight),this.menu.popup(b.x,b.y,null,a),this.menu.isMenuShowing()&&(g.className=k+"Selected",this.menu.hideMenu=function(){mxPopupMenu.prototype.hideMenu.app [...]
+g.className=k;this.currentImg=null}))}}),null,a);mxEvent.addListener(g,"mouseout",a);return g};mxToolbar.prototype.addCombo=function(a){var b=document.createElement("div");b.style.display="inline";b.className="mxToolbarComboContainer";var c=document.createElement("select");c.className=a||"mxToolbarCombo";b.appendChild(c);this.container.appendChild(b);return c};
+mxToolbar.prototype.addActionCombo=function(a,b){var c=document.createElement("select");c.className=b||"mxToolbarCombo";this.addOption(c,a,null);mxEvent.addListener(c,"change",function(a){var b=c.options[c.selectedIndex];c.selectedIndex=0;null!=b.funct&&b.funct(a)});this.container.appendChild(c);return c};mxToolbar.prototype.addOption=function(a,b,c){var d=document.createElement("option");mxUtils.writeln(d,b);"function"==typeof c?d.funct=c:d.setAttribute("value",c);a.appendChild(d);return d};
+mxToolbar.prototype.addSwitchMode=function(a,b,c,d,e){var f=document.createElement("img");f.initialClassName=e||"mxToolbarMode";f.className=f.initialClassName;f.setAttribute("src",b);f.altIcon=d;null!=a&&f.setAttribute("title",a);mxEvent.addListener(f,"click",mxUtils.bind(this,function(a){a=this.selectedMode.altIcon;null!=a?(this.selectedMode.altIcon=this.selectedMode.getAttribute("src"),this.selectedMode.setAttribute("src",a)):this.selectedMode.className=this.selectedMode.initialClassNa [...]
+(this.defaultMode=f);this.selectedMode=f;a=f.altIcon;null!=a?(f.altIcon=f.getAttribute("src"),f.setAttribute("src",a)):f.className=f.initialClassName+"Selected";this.fireEvent(new mxEventObject(mxEvent.SELECT));c()}));this.container.appendChild(f);null==this.defaultMode&&(this.defaultMode=f,this.selectMode(f),c());return f};
+mxToolbar.prototype.addMode=function(a,b,c,d,e,f){f=null!=f?f:!0;var g=document.createElement(null!=b?"img":"button");g.initialClassName=e||"mxToolbarMode";g.className=g.initialClassName;g.setAttribute("src",b);g.altIcon=d;null!=a&&g.setAttribute("title",a);this.enabled&&f&&(mxEvent.addListener(g,"click",mxUtils.bind(this,function(a){this.selectMode(g,c);this.noReset=!1})),mxEvent.addListener(g,"dblclick",mxUtils.bind(this,function(a){this.selectMode(g,c);this.noReset=!0})),null==this.de [...]
+(this.defaultMode=g,this.defaultFunction=c,this.selectMode(g,c)));this.container.appendChild(g);return g};
+mxToolbar.prototype.selectMode=function(a,b){if(this.selectedMode!=a){if(null!=this.selectedMode){var c=this.selectedMode.altIcon;null!=c?(this.selectedMode.altIcon=this.selectedMode.getAttribute("src"),this.selectedMode.setAttribute("src",c)):this.selectedMode.className=this.selectedMode.initialClassName}this.selectedMode=a;c=this.selectedMode.altIcon;null!=c?(this.selectedMode.altIcon=this.selectedMode.getAttribute("src"),this.selectedMode.setAttribute("src",c)):this.selectedMode.class [...]
+"Selected";this.fireEvent(new mxEventObject(mxEvent.SELECT,"function",b))}};mxToolbar.prototype.resetMode=function(a){!a&&this.noReset||this.selectedMode==this.defaultMode||this.selectMode(this.defaultMode,this.defaultFunction)};mxToolbar.prototype.addSeparator=function(a){return this.addItem(null,a,null)};mxToolbar.prototype.addBreak=function(){mxUtils.br(this.container)};
+mxToolbar.prototype.addLine=function(){var a=document.createElement("hr");a.style.marginRight="6px";a.setAttribute("size","1");this.container.appendChild(a)};mxToolbar.prototype.destroy=function(){mxEvent.release(this.container);this.selectedMode=this.defaultFunction=this.defaultMode=this.container=null;null!=this.menu&&this.menu.destroy()};function mxUndoableEdit(a,b){this.source=a;this.changes=[];this.significant=null!=b?b:!0}mxUndoableEdit.prototype.source=null;
+mxUndoableEdit.prototype.changes=null;mxUndoableEdit.prototype.significant=null;mxUndoableEdit.prototype.undone=!1;mxUndoableEdit.prototype.redone=!1;mxUndoableEdit.prototype.isEmpty=function(){return 0==this.changes.length};mxUndoableEdit.prototype.isSignificant=function(){return this.significant};mxUndoableEdit.prototype.add=function(a){this.changes.push(a)};mxUndoableEdit.prototype.notify=function(){};mxUndoableEdit.prototype.die=function(){};
+mxUndoableEdit.prototype.undo=function(){if(!this.undone){this.source.fireEvent(new mxEventObject(mxEvent.START_EDIT));for(var a=this.changes.length-1;0<=a;a--){var b=this.changes[a];null!=b.execute?b.execute():null!=b.undo&&b.undo();this.source.fireEvent(new mxEventObject(mxEvent.EXECUTED,"change",b))}this.undone=!0;this.redone=!1;this.source.fireEvent(new mxEventObject(mxEvent.END_EDIT))}this.notify()};
+mxUndoableEdit.prototype.redo=function(){if(!this.redone){this.source.fireEvent(new mxEventObject(mxEvent.START_EDIT));for(var a=this.changes.length,b=0;b<a;b++){var c=this.changes[b];null!=c.execute?c.execute():null!=c.redo&&c.redo();this.source.fireEvent(new mxEventObject(mxEvent.EXECUTED,"change",c))}this.undone=!1;this.redone=!0;this.source.fireEvent(new mxEventObject(mxEvent.END_EDIT))}this.notify()};function mxUndoManager(a){this.size=null!=a?a:100;this.clear()}mxUndoManager.protot [...]
+mxUndoManager.prototype.constructor=mxUndoManager;mxUndoManager.prototype.size=null;mxUndoManager.prototype.history=null;mxUndoManager.prototype.indexOfNextAdd=0;mxUndoManager.prototype.isEmpty=function(){return 0==this.history.length};mxUndoManager.prototype.clear=function(){this.history=[];this.indexOfNextAdd=0;this.fireEvent(new mxEventObject(mxEvent.CLEAR))};mxUndoManager.prototype.canUndo=function(){return 0<this.indexOfNextAdd};
+mxUndoManager.prototype.undo=function(){for(;0<this.indexOfNextAdd;){var a=this.history[--this.indexOfNextAdd];a.undo();if(a.isSignificant()){this.fireEvent(new mxEventObject(mxEvent.UNDO,"edit",a));break}}};mxUndoManager.prototype.canRedo=function(){return this.indexOfNextAdd<this.history.length};
+mxUndoManager.prototype.redo=function(){for(var a=this.history.length;this.indexOfNextAdd<a;){var b=this.history[this.indexOfNextAdd++];b.redo();if(b.isSignificant()){this.fireEvent(new mxEventObject(mxEvent.REDO,"edit",b));break}}};mxUndoManager.prototype.undoableEditHappened=function(a){this.trim();0<this.size&&this.size==this.history.length&&this.history.shift();this.history.push(a);this.indexOfNextAdd=this.history.length;this.fireEvent(new mxEventObject(mxEvent.ADD,"edit",a))};
+mxUndoManager.prototype.trim=function(){if(this.history.length>this.indexOfNextAdd)for(var a=this.history.splice(this.indexOfNextAdd,this.history.length-this.indexOfNextAdd),b=0;b<a.length;b++)a[b].die()};var mxUrlConverter=function(){};mxUrlConverter.prototype.enabled=!0;mxUrlConverter.prototype.baseUrl=null;mxUrlConverter.prototype.baseDomain=null;
+mxUrlConverter.prototype.updateBaseUrl=function(){this.baseDomain=location.protocol+"//"+location.host;this.baseUrl=this.baseDomain+location.pathname;var a=this.baseUrl.lastIndexOf("/");0<a&&(this.baseUrl=this.baseUrl.substring(0,a+1))};mxUrlConverter.prototype.isEnabled=function(){return this.enabled};mxUrlConverter.prototype.setEnabled=function(a){this.enabled=a};mxUrlConverter.prototype.getBaseUrl=function(){return this.baseUrl};mxUrlConverter.prototype.setBaseUrl=function(a){this.bas [...]
+mxUrlConverter.prototype.getBaseDomain=function(){return this.baseDomain};mxUrlConverter.prototype.setBaseDomain=function(a){this.baseDomain=a};mxUrlConverter.prototype.isRelativeUrl=function(a){return"//"!=a.substring(0,2)&&"http://"!=a.substring(0,7)&&"https://"!=a.substring(0,8)&&"data:image"!=a.substring(0,10)};
+mxUrlConverter.prototype.convert=function(a){this.isEnabled()&&this.isRelativeUrl(a)&&(null==this.getBaseUrl()&&this.updateBaseUrl(),a="/"==a.charAt(0)?this.getBaseDomain()+a:this.getBaseUrl()+a);return a};
+function mxPanningManager(a){this.thread=null;this.active=!1;this.dy=this.dx=this.t0y=this.t0x=this.tdy=this.tdx=0;this.scrollbars=!1;this.scrollTop=this.scrollLeft=0;this.mouseListener={mouseDown:function(a,b){},mouseMove:function(a,b){},mouseUp:mxUtils.bind(this,function(a,b){this.active&&this.stop()})};a.addMouseListener(this.mouseListener);mxEvent.addListener(document,"mouseup",mxUtils.bind(this,function(){this.active&&this.stop()}));var b=mxUtils.bind(this,function(){this.scrollbars [...]
+this.scrollLeft=a.container.scrollLeft;this.scrollTop=a.container.scrollTop;return window.setInterval(mxUtils.bind(this,function(){this.tdx-=this.dx;this.tdy-=this.dy;this.scrollbars?(a.panGraph(-a.container.scrollLeft-Math.ceil(this.dx),-a.container.scrollTop-Math.ceil(this.dy)),a.panDx=this.scrollLeft-a.container.scrollLeft,a.panDy=this.scrollTop-a.container.scrollTop,a.fireEvent(new mxEventObject(mxEvent.PAN))):a.panGraph(this.getDx(),this.getDy())}),this.delay)});this.isActive=functi [...]
+this.getDx=function(){return Math.round(this.tdx)};this.getDy=function(){return Math.round(this.tdy)};this.start=function(){this.t0x=a.view.translate.x;this.t0y=a.view.translate.y;this.active=!0};this.panTo=function(c,d,e,f){this.active||this.start();this.scrollLeft=a.container.scrollLeft;this.scrollTop=a.container.scrollTop;var g=a.container;this.dx=c+(null!=e?e:0)-g.scrollLeft-g.clientWidth;this.dx=0>this.dx&&Math.abs(this.dx)<this.border?this.border+this.dx:this.handleMouseOut?Math.ma [...]
+0):0;0==this.dx&&(this.dx=c-g.scrollLeft,this.dx=0<this.dx&&this.dx<this.border?this.dx-this.border:this.handleMouseOut?Math.min(0,this.dx):0);this.dy=d+(null!=f?f:0)-g.scrollTop-g.clientHeight;this.dy=0>this.dy&&Math.abs(this.dy)<this.border?this.border+this.dy:this.handleMouseOut?Math.max(this.dy,0):0;0==this.dy&&(this.dy=d-g.scrollTop,this.dy=0<this.dy&&this.dy<this.border?this.dy-this.border:this.handleMouseOut?Math.min(0,this.dy):0);0!=this.dx||0!=this.dy?(this.dx*=this.damper,this. [...]
+null==this.thread&&(this.thread=b())):null!=this.thread&&(window.clearInterval(this.thread),this.thread=null)};this.stop=function(){if(this.active)if(this.active=!1,null!=this.thread&&(window.clearInterval(this.thread),this.thread=null),this.tdy=this.tdx=0,this.scrollbars)a.panDx=0,a.panDy=0,a.fireEvent(new mxEventObject(mxEvent.PAN));else{var b=a.panDx,d=a.panDy;if(0!=b||0!=d)a.panGraph(0,0),a.view.setTranslate(this.t0x+b/a.view.scale,this.t0y+d/a.view.scale)}};this.destroy=function(){a [...]
+mxPanningManager.prototype.damper=1/6;mxPanningManager.prototype.delay=10;mxPanningManager.prototype.handleMouseOut=!0;mxPanningManager.prototype.border=0;function mxPopupMenu(a){this.factoryMethod=a;null!=a&&this.init()}mxPopupMenu.prototype=new mxEventSource;mxPopupMenu.prototype.constructor=mxPopupMenu;mxPopupMenu.prototype.submenuImage=mxClient.imageBasePath+"/submenu.gif";mxPopupMenu.prototype.zIndex=10006;mxPopupMenu.prototype.factoryMethod=null;mxPopupMenu.prototype.useLeftButtonF [...]
+mxPopupMenu.prototype.enabled=!0;mxPopupMenu.prototype.itemCount=0;mxPopupMenu.prototype.autoExpand=!1;mxPopupMenu.prototype.smartSeparators=!1;mxPopupMenu.prototype.labels=!0;
+mxPopupMenu.prototype.init=function(){this.table=document.createElement("table");this.table.className="mxPopupMenu";this.tbody=document.createElement("tbody");this.table.appendChild(this.tbody);this.div=document.createElement("div");this.div.className="mxPopupMenu";this.div.style.display="inline";this.div.style.zIndex=this.zIndex;this.div.appendChild(this.table);mxEvent.disableContextMenu(this.div)};mxPopupMenu.prototype.isEnabled=function(){return this.enabled};
+mxPopupMenu.prototype.setEnabled=function(a){this.enabled=a};mxPopupMenu.prototype.isPopupTrigger=function(a){return a.isPopupTrigger()||this.useLeftButtonForPopup&&mxEvent.isLeftMouseButton(a.getEvent())};
+mxPopupMenu.prototype.addItem=function(a,b,c,d,e,f,g){d=d||this;this.itemCount++;d.willAddSeparator&&(d.containsItems&&this.addSeparator(d,!0),d.willAddSeparator=!1);d.containsItems=!0;var k=document.createElement("tr");k.className="mxPopupMenuItem";var l=document.createElement("td");l.className="mxPopupMenuIcon";null!=b?(e=document.createElement("img"),e.src=b,l.appendChild(e)):null!=e&&(b=document.createElement("div"),b.className=e,l.appendChild(b));k.appendChild(l);this.labels&&(l=doc [...]
+l.className="mxPopupMenuItem"+(null==f||f?"":" mxDisabled"),mxUtils.write(l,a),l.align="left",k.appendChild(l),a=document.createElement("td"),a.className="mxPopupMenuItem"+(null==f||f?"":" mxDisabled"),a.style.paddingRight="6px",a.style.textAlign="right",k.appendChild(a),null==d.div&&this.createSubmenu(d));d.tbody.appendChild(k);if(0!=g&&0!=f){var m=null;mxEvent.addGestureListeners(k,mxUtils.bind(this,function(a){this.eventReceiver=k;d.activeRow!=k&&d.activeRow!=d&&(null!=d.activeRow&&nu [...]
+this.hideSubmenu(d),null!=k.div&&(this.showSubmenu(d,k),d.activeRow=k));if(mxClient.IS_QUIRKS||8==document.documentMode)m=document.selection.createRange();mxEvent.consume(a)}),mxUtils.bind(this,function(a){d.activeRow!=k&&d.activeRow!=d&&(null!=d.activeRow&&null!=d.activeRow.div.parentNode&&this.hideSubmenu(d),this.autoExpand&&null!=k.div&&(this.showSubmenu(d,k),d.activeRow=k));k.className="mxPopupMenuItemHover"}),mxUtils.bind(this,function(a){if(this.eventReceiver==k){d.activeRow!=k&&th [...]
+if(null!=m){try{m.select()}catch(p){}m=null}null!=c&&c(a)}this.eventReceiver=null;mxEvent.consume(a)}));mxEvent.addListener(k,"mouseout",mxUtils.bind(this,function(a){k.className="mxPopupMenuItem"}))}return k};mxPopupMenu.prototype.addCheckmark=function(a,b){var c=a.firstChild.nextSibling;c.style.backgroundImage="url('"+b+"')";c.style.backgroundRepeat="no-repeat";c.style.backgroundPosition="2px 50%"};
+mxPopupMenu.prototype.createSubmenu=function(a){a.table=document.createElement("table");a.table.className="mxPopupMenu";a.tbody=document.createElement("tbody");a.table.appendChild(a.tbody);a.div=document.createElement("div");a.div.className="mxPopupMenu";a.div.style.position="absolute";a.div.style.display="inline";a.div.style.zIndex=this.zIndex;a.div.appendChild(a.table);var b=document.createElement("img");b.setAttribute("src",this.submenuImage);td=a.firstChild.nextSibling.nextSibling;td [...]
+mxPopupMenu.prototype.showSubmenu=function(a,b){if(null!=b.div){b.div.style.left=a.div.offsetLeft+b.offsetLeft+b.offsetWidth-1+"px";b.div.style.top=a.div.offsetTop+b.offsetTop+"px";document.body.appendChild(b.div);var c=parseInt(b.div.offsetLeft),d=parseInt(b.div.offsetWidth),e=mxUtils.getDocumentScrollOrigin(document),f=document.documentElement;c+d>e.x+(document.body.clientWidth||f.clientWidth)&&(b.div.style.left=Math.max(0,a.div.offsetLeft-d+(mxClient.IS_IE?6:-6))+"px");mxUtils.fit(b.div)}};
+mxPopupMenu.prototype.addSeparator=function(a,b){a=a||this;if(this.smartSeparators&&!b)a.willAddSeparator=!0;else if(null!=a.tbody){a.willAddSeparator=!1;var c=document.createElement("tr"),d=document.createElement("td");d.className="mxPopupMenuIcon";d.style.padding="0 0 0 0px";c.appendChild(d);d=document.createElement("td");d.style.padding="0 0 0 0px";d.setAttribute("colSpan","2");var e=document.createElement("hr");e.setAttribute("size","1");d.appendChild(e);c.appendChild(d);a.tbody.appe [...]
+mxPopupMenu.prototype.popup=function(a,b,c,d){if(null!=this.div&&null!=this.tbody&&null!=this.factoryMethod){this.div.style.left=a+"px";for(this.div.style.top=b+"px";null!=this.tbody.firstChild;)mxEvent.release(this.tbody.firstChild),this.tbody.removeChild(this.tbody.firstChild);this.itemCount=0;this.factoryMethod(this,c,d);0<this.itemCount&&(this.showMenu(),this.fireEvent(new mxEventObject(mxEvent.SHOW)))}};
+mxPopupMenu.prototype.isMenuShowing=function(){return null!=this.div&&this.div.parentNode==document.body};mxPopupMenu.prototype.showMenu=function(){9<=document.documentMode&&(this.div.style.filter="none");document.body.appendChild(this.div);mxUtils.fit(this.div)};mxPopupMenu.prototype.hideMenu=function(){null!=this.div&&(null!=this.div.parentNode&&this.div.parentNode.removeChild(this.div),this.hideSubmenu(this),this.containsItems=!1,this.fireEvent(new mxEventObject(mxEvent.HIDE)))};
+mxPopupMenu.prototype.hideSubmenu=function(a){null!=a.activeRow&&(this.hideSubmenu(a.activeRow),null!=a.activeRow.div.parentNode&&a.activeRow.div.parentNode.removeChild(a.activeRow.div),a.activeRow=null)};mxPopupMenu.prototype.destroy=function(){null!=this.div&&(mxEvent.release(this.div),null!=this.div.parentNode&&this.div.parentNode.removeChild(this.div),this.div=null)};
+function mxAutoSaveManager(a){this.changeHandler=mxUtils.bind(this,function(a,c){this.isEnabled()&&this.graphModelChanged(c.getProperty("edit").changes)});this.setGraph(a)}mxAutoSaveManager.prototype=new mxEventSource;mxAutoSaveManager.prototype.constructor=mxAutoSaveManager;mxAutoSaveManager.prototype.graph=null;mxAutoSaveManager.prototype.autoSaveDelay=10;mxAutoSaveManager.prototype.autoSaveThrottle=2;mxAutoSaveManager.prototype.autoSaveThreshold=5;mxAutoSaveManager.prototype.ignoredCh [...]
+mxAutoSaveManager.prototype.lastSnapshot=0;mxAutoSaveManager.prototype.enabled=!0;mxAutoSaveManager.prototype.changeHandler=null;mxAutoSaveManager.prototype.isEnabled=function(){return this.enabled};mxAutoSaveManager.prototype.setEnabled=function(a){this.enabled=a};mxAutoSaveManager.prototype.setGraph=function(a){null!=this.graph&&this.graph.getModel().removeListener(this.changeHandler);this.graph=a;null!=this.graph&&this.graph.getModel().addListener(mxEvent.CHANGE,this.changeHandler)};
+mxAutoSaveManager.prototype.save=function(){};mxAutoSaveManager.prototype.graphModelChanged=function(a){a=((new Date).getTime()-this.lastSnapshot)/1E3;a>this.autoSaveDelay||this.ignoredChanges>=this.autoSaveThreshold&&a>this.autoSaveThrottle?(this.save(),this.reset()):this.ignoredChanges++};mxAutoSaveManager.prototype.reset=function(){this.lastSnapshot=(new Date).getTime();this.ignoredChanges=0};mxAutoSaveManager.prototype.destroy=function(){this.setGraph(null)};
+function mxAnimation(a){this.delay=null!=a?a:20}mxAnimation.prototype=new mxEventSource;mxAnimation.prototype.constructor=mxAnimation;mxAnimation.prototype.delay=null;mxAnimation.prototype.thread=null;mxAnimation.prototype.isRunning=function(){return null!=this.thread};mxAnimation.prototype.startAnimation=function(){null==this.thread&&(this.thread=window.setInterval(mxUtils.bind(this,this.updateAnimation),this.delay))};mxAnimation.prototype.updateAnimation=function(){this.fireEvent(new m [...]
+mxAnimation.prototype.stopAnimation=function(){null!=this.thread&&(window.clearInterval(this.thread),this.thread=null,this.fireEvent(new mxEventObject(mxEvent.DONE)))};function mxMorphing(a,b,c,d){mxAnimation.call(this,d);this.graph=a;this.steps=null!=b?b:6;this.ease=null!=c?c:1.5}mxMorphing.prototype=new mxAnimation;mxMorphing.prototype.constructor=mxMorphing;mxMorphing.prototype.graph=null;mxMorphing.prototype.steps=null;mxMorphing.prototype.step=0;mxMorphing.prototype.ease=null;
+mxMorphing.prototype.cells=null;mxMorphing.prototype.updateAnimation=function(){var a=new mxCellStatePreview(this.graph);if(null!=this.cells)for(var b=0;b<this.cells.length;b++)this.animateCell(this.cells[b],a,!1);else this.animateCell(this.graph.getModel().getRoot(),a,!0);this.show(a);(a.isEmpty()||this.step++>=this.steps)&&this.stopAnimation()};mxMorphing.prototype.show=function(a){a.show()};
+mxMorphing.prototype.animateCell=function(a,b,c){var d=this.graph.getView().getState(a),e=null;if(null!=d&&(e=this.getDelta(d),this.graph.getModel().isVertex(a)&&(0!=e.x||0!=e.y))){var f=this.graph.view.getTranslate(),g=this.graph.view.getScale();e.x+=f.x*g;e.y+=f.y*g;b.moveState(d,-e.x/this.ease,-e.y/this.ease)}if(c&&!this.stopRecursion(d,e))for(d=this.graph.getModel().getChildCount(a),e=0;e<d;e++)this.animateCell(this.graph.getModel().getChildAt(a,e),b,c)};
+mxMorphing.prototype.stopRecursion=function(a,b){return null!=b&&(0!=b.x||0!=b.y)};mxMorphing.prototype.getDelta=function(a){var b=this.getOriginForCell(a.cell),c=this.graph.getView().getTranslate(),d=this.graph.getView().getScale();return new mxPoint((b.x-(a.x/d-c.x))*d,(b.y-(a.y/d-c.y))*d)};
+mxMorphing.prototype.getOriginForCell=function(a){var b=null;if(null!=a){var c=this.graph.getModel().getParent(a);a=this.graph.getCellGeometry(a);b=this.getOriginForCell(c);null!=a&&(a.relative?(c=this.graph.getCellGeometry(c),null!=c&&(b.x+=a.x*c.width,b.y+=a.y*c.height)):(b.x+=a.x,b.y+=a.y))}null==b&&(b=this.graph.view.getTranslate(),b=new mxPoint(-b.x,-b.y));return b};function mxImageBundle(a){this.images=[];this.alt=null!=a?a:!1}mxImageBundle.prototype.images=null;
+mxImageBundle.prototype.images=null;mxImageBundle.prototype.putImage=function(a,b,c){this.images[a]={value:b,fallback:c}};mxImageBundle.prototype.getImage=function(a){var b=null;null!=a&&(a=this.images[a],null!=a&&(b=this.alt?a.fallback:a.value));return b};function mxImageExport(){}mxImageExport.prototype.includeOverlays=!1;
+mxImageExport.prototype.drawState=function(a,b){null!=a&&(this.visitStatesRecursive(a,b,mxUtils.bind(this,function(){this.drawCellState.apply(this,arguments)})),this.includeOverlays&&this.visitStatesRecursive(a,b,mxUtils.bind(this,function(){this.drawOverlays.apply(this,arguments)})))};
+mxImageExport.prototype.visitStatesRecursive=function(a,b,c){if(null!=a){c(a,b);for(var d=a.view.graph,e=d.model.getChildCount(a.cell),f=0;f<e;f++){var g=d.view.getState(d.model.getChildAt(a.cell,f));this.visitStatesRecursive(g,b,c)}}};mxImageExport.prototype.getLinkForCellState=function(a,b){return null};mxImageExport.prototype.drawCellState=function(a,b){var c=this.getLinkForCellState(a,b);null!=c&&b.setLink(c);this.drawShape(a,b);this.drawText(a,b);null!=c&&b.setLink(null)};
+mxImageExport.prototype.drawShape=function(a,b){a.shape instanceof mxShape&&a.shape.checkBounds()&&(b.save(),a.shape.paint(b),b.restore())};mxImageExport.prototype.drawText=function(a,b){null!=a.text&&a.text.checkBounds()&&(b.save(),a.text.paint(b),b.restore())};mxImageExport.prototype.drawOverlays=function(a,b){null!=a.overlays&&a.overlays.visit(function(a,d){d instanceof mxShape&&d.paint(b)})};function mxAbstractCanvas2D(){this.converter=this.createUrlConverter();this.reset()}
+mxAbstractCanvas2D.prototype.state=null;mxAbstractCanvas2D.prototype.states=null;mxAbstractCanvas2D.prototype.path=null;mxAbstractCanvas2D.prototype.rotateHtml=!0;mxAbstractCanvas2D.prototype.lastX=0;mxAbstractCanvas2D.prototype.lastY=0;mxAbstractCanvas2D.prototype.moveOp="M";mxAbstractCanvas2D.prototype.lineOp="L";mxAbstractCanvas2D.prototype.quadOp="Q";mxAbstractCanvas2D.prototype.curveOp="C";mxAbstractCanvas2D.prototype.closeOp="Z";mxAbstractCanvas2D.prototype.pointerEvents=!1;
+mxAbstractCanvas2D.prototype.createUrlConverter=function(){return new mxUrlConverter};mxAbstractCanvas2D.prototype.reset=function(){this.state=this.createState();this.states=[]};
+mxAbstractCanvas2D.prototype.createState=function(){return{dx:0,dy:0,scale:1,alpha:1,fillAlpha:1,strokeAlpha:1,fillColor:null,gradientFillAlpha:1,gradientColor:null,gradientAlpha:1,gradientDirection:null,strokeColor:null,strokeWidth:1,dashed:!1,dashPattern:"3 3",fixDash:!1,lineCap:"flat",lineJoin:"miter",miterLimit:10,fontColor:"#000000",fontBackgroundColor:null,fontBorderColor:null,fontSize:mxConstants.DEFAULT_FONTSIZE,fontFamily:mxConstants.DEFAULT_FONTFAMILY,fontStyle:0,shadow:!1,shad [...]
+shadowAlpha:mxConstants.SHADOW_OPACITY,shadowDx:mxConstants.SHADOW_OFFSET_X,shadowDy:mxConstants.SHADOW_OFFSET_Y,rotation:0,rotationCx:0,rotationCy:0}};mxAbstractCanvas2D.prototype.format=function(a){return Math.round(parseFloat(a))};
+mxAbstractCanvas2D.prototype.addOp=function(){if(null!=this.path&&(this.path.push(arguments[0]),2<arguments.length))for(var a=this.state,b=2;b<arguments.length;b+=2)this.lastX=arguments[b-1],this.lastY=arguments[b],this.path.push(this.format((this.lastX+a.dx)*a.scale)),this.path.push(this.format((this.lastY+a.dy)*a.scale))};mxAbstractCanvas2D.prototype.rotatePoint=function(a,b,c,d,e){c*=Math.PI/180;return mxUtils.getRotatedPoint(new mxPoint(a,b),Math.cos(c),Math.sin(c),new mxPoint(d,e))};
+mxAbstractCanvas2D.prototype.save=function(){this.states.push(this.state);this.state=mxUtils.clone(this.state)};mxAbstractCanvas2D.prototype.restore=function(){0<this.states.length&&(this.state=this.states.pop())};mxAbstractCanvas2D.prototype.setLink=function(a){};mxAbstractCanvas2D.prototype.scale=function(a){this.state.scale*=a;this.state.strokeWidth*=a};mxAbstractCanvas2D.prototype.translate=function(a,b){this.state.dx+=a;this.state.dy+=b};mxAbstractCanvas2D.prototype.rotate=function( [...]
+mxAbstractCanvas2D.prototype.setAlpha=function(a){this.state.alpha=a};mxAbstractCanvas2D.prototype.setFillAlpha=function(a){this.state.fillAlpha=a};mxAbstractCanvas2D.prototype.setStrokeAlpha=function(a){this.state.strokeAlpha=a};mxAbstractCanvas2D.prototype.setFillColor=function(a){a==mxConstants.NONE&&(a=null);this.state.fillColor=a;this.state.gradientColor=null};
+mxAbstractCanvas2D.prototype.setGradient=function(a,b,c,d,e,f,g,k,l){c=this.state;c.fillColor=a;c.gradientFillAlpha=null!=k?k:1;c.gradientColor=b;c.gradientAlpha=null!=l?l:1;c.gradientDirection=g};mxAbstractCanvas2D.prototype.setStrokeColor=function(a){a==mxConstants.NONE&&(a=null);this.state.strokeColor=a};mxAbstractCanvas2D.prototype.setStrokeWidth=function(a){this.state.strokeWidth=a};mxAbstractCanvas2D.prototype.setDashed=function(a,b){this.state.dashed=a;this.state.fixDash=b};
+mxAbstractCanvas2D.prototype.setDashPattern=function(a){this.state.dashPattern=a};mxAbstractCanvas2D.prototype.setLineCap=function(a){this.state.lineCap=a};mxAbstractCanvas2D.prototype.setLineJoin=function(a){this.state.lineJoin=a};mxAbstractCanvas2D.prototype.setMiterLimit=function(a){this.state.miterLimit=a};mxAbstractCanvas2D.prototype.setFontColor=function(a){a==mxConstants.NONE&&(a=null);this.state.fontColor=a};
+mxAbstractCanvas2D.prototype.setFontBackgroundColor=function(a){a==mxConstants.NONE&&(a=null);this.state.fontBackgroundColor=a};mxAbstractCanvas2D.prototype.setFontBorderColor=function(a){a==mxConstants.NONE&&(a=null);this.state.fontBorderColor=a};mxAbstractCanvas2D.prototype.setFontSize=function(a){this.state.fontSize=parseFloat(a)};mxAbstractCanvas2D.prototype.setFontFamily=function(a){this.state.fontFamily=a};
+mxAbstractCanvas2D.prototype.setFontStyle=function(a){null==a&&(a=0);this.state.fontStyle=a};mxAbstractCanvas2D.prototype.setShadow=function(a){this.state.shadow=a};mxAbstractCanvas2D.prototype.setShadowColor=function(a){a==mxConstants.NONE&&(a=null);this.state.shadowColor=a};mxAbstractCanvas2D.prototype.setShadowAlpha=function(a){this.state.shadowAlpha=a};mxAbstractCanvas2D.prototype.setShadowOffset=function(a,b){this.state.shadowDx=a;this.state.shadowDy=b};
+mxAbstractCanvas2D.prototype.begin=function(){this.lastY=this.lastX=0;this.path=[]};mxAbstractCanvas2D.prototype.moveTo=function(a,b){this.addOp(this.moveOp,a,b)};mxAbstractCanvas2D.prototype.lineTo=function(a,b){this.addOp(this.lineOp,a,b)};mxAbstractCanvas2D.prototype.quadTo=function(a,b,c,d){this.addOp(this.quadOp,a,b,c,d)};mxAbstractCanvas2D.prototype.curveTo=function(a,b,c,d,e,f){this.addOp(this.curveOp,a,b,c,d,e,f)};
+mxAbstractCanvas2D.prototype.arcTo=function(a,b,c,d,e,f,g){a=mxUtils.arcToCurves(this.lastX,this.lastY,a,b,c,d,e,f,g);if(null!=a)for(b=0;b<a.length;b+=6)this.curveTo(a[b],a[b+1],a[b+2],a[b+3],a[b+4],a[b+5])};mxAbstractCanvas2D.prototype.close=function(a,b,c,d,e,f){this.addOp(this.closeOp)};mxAbstractCanvas2D.prototype.end=function(){};function mxXmlCanvas2D(a){mxAbstractCanvas2D.call(this);this.root=a;this.writeDefaults()}mxUtils.extend(mxXmlCanvas2D,mxAbstractCanvas2D);
+mxXmlCanvas2D.prototype.textEnabled=!0;mxXmlCanvas2D.prototype.compressed=!0;
+mxXmlCanvas2D.prototype.writeDefaults=function(){var a;a=this.createElement("fontfamily");a.setAttribute("family",mxConstants.DEFAULT_FONTFAMILY);this.root.appendChild(a);a=this.createElement("fontsize");a.setAttribute("size",mxConstants.DEFAULT_FONTSIZE);this.root.appendChild(a);a=this.createElement("shadowcolor");a.setAttribute("color",mxConstants.SHADOWCOLOR);this.root.appendChild(a);a=this.createElement("shadowalpha");a.setAttribute("alpha",mxConstants.SHADOW_OPACITY);this.root.appen [...]
+a=this.createElement("shadowoffset");a.setAttribute("dx",mxConstants.SHADOW_OFFSET_X);a.setAttribute("dy",mxConstants.SHADOW_OFFSET_Y);this.root.appendChild(a)};mxXmlCanvas2D.prototype.format=function(a){return parseFloat(parseFloat(a).toFixed(2))};mxXmlCanvas2D.prototype.createElement=function(a){return this.root.ownerDocument.createElement(a)};mxXmlCanvas2D.prototype.save=function(){this.compressed&&mxAbstractCanvas2D.prototype.save.apply(this,arguments);this.root.appendChild(this.crea [...]
+mxXmlCanvas2D.prototype.restore=function(){this.compressed&&mxAbstractCanvas2D.prototype.restore.apply(this,arguments);this.root.appendChild(this.createElement("restore"))};mxXmlCanvas2D.prototype.scale=function(a){var b=this.createElement("scale");b.setAttribute("scale",a);this.root.appendChild(b)};mxXmlCanvas2D.prototype.translate=function(a,b){var c=this.createElement("translate");c.setAttribute("dx",this.format(a));c.setAttribute("dy",this.format(b));this.root.appendChild(c)};
+mxXmlCanvas2D.prototype.rotate=function(a,b,c,d,e){var f=this.createElement("rotate");if(0!=a||b||c)f.setAttribute("theta",this.format(a)),f.setAttribute("flipH",b?"1":"0"),f.setAttribute("flipV",c?"1":"0"),f.setAttribute("cx",this.format(d)),f.setAttribute("cy",this.format(e)),this.root.appendChild(f)};
+mxXmlCanvas2D.prototype.setAlpha=function(a){if(this.compressed){if(this.state.alpha==a)return;mxAbstractCanvas2D.prototype.setAlpha.apply(this,arguments)}var b=this.createElement("alpha");b.setAttribute("alpha",this.format(a));this.root.appendChild(b)};mxXmlCanvas2D.prototype.setFillAlpha=function(a){if(this.compressed){if(this.state.fillAlpha==a)return;mxAbstractCanvas2D.prototype.setFillAlpha.apply(this,arguments)}var b=this.createElement("fillalpha");b.setAttribute("alpha",this.forma [...]
+mxXmlCanvas2D.prototype.setStrokeAlpha=function(a){if(this.compressed){if(this.state.strokeAlpha==a)return;mxAbstractCanvas2D.prototype.setStrokeAlpha.apply(this,arguments)}var b=this.createElement("strokealpha");b.setAttribute("alpha",this.format(a));this.root.appendChild(b)};
+mxXmlCanvas2D.prototype.setFillColor=function(a){a==mxConstants.NONE&&(a=null);if(this.compressed){if(this.state.fillColor==a)return;mxAbstractCanvas2D.prototype.setFillColor.apply(this,arguments)}var b=this.createElement("fillcolor");b.setAttribute("color",null!=a?a:mxConstants.NONE);this.root.appendChild(b)};
+mxXmlCanvas2D.prototype.setGradient=function(a,b,c,d,e,f,g,k,l){if(null!=a&&null!=b){mxAbstractCanvas2D.prototype.setGradient.apply(this,arguments);var m=this.createElement("gradient");m.setAttribute("c1",a);m.setAttribute("c2",b);m.setAttribute("x",this.format(c));m.setAttribute("y",this.format(d));m.setAttribute("w",this.format(e));m.setAttribute("h",this.format(f));null!=g&&m.setAttribute("direction",g);null!=k&&m.setAttribute("alpha1",k);null!=l&&m.setAttribute("alpha2",l);this.root. [...]
+mxXmlCanvas2D.prototype.setStrokeColor=function(a){a==mxConstants.NONE&&(a=null);if(this.compressed){if(this.state.strokeColor==a)return;mxAbstractCanvas2D.prototype.setStrokeColor.apply(this,arguments)}var b=this.createElement("strokecolor");b.setAttribute("color",null!=a?a:mxConstants.NONE);this.root.appendChild(b)};
+mxXmlCanvas2D.prototype.setStrokeWidth=function(a){if(this.compressed){if(this.state.strokeWidth==a)return;mxAbstractCanvas2D.prototype.setStrokeWidth.apply(this,arguments)}var b=this.createElement("strokewidth");b.setAttribute("width",this.format(a));this.root.appendChild(b)};
+mxXmlCanvas2D.prototype.setDashed=function(a,b){if(this.compressed){if(this.state.dashed==a)return;mxAbstractCanvas2D.prototype.setDashed.apply(this,arguments)}var c=this.createElement("dashed");c.setAttribute("dashed",a?"1":"0");null!=b&&c.setAttribute("fixDash",b?"1":"0");this.root.appendChild(c)};
+mxXmlCanvas2D.prototype.setDashPattern=function(a){if(this.compressed){if(this.state.dashPattern==a)return;mxAbstractCanvas2D.prototype.setDashPattern.apply(this,arguments)}var b=this.createElement("dashpattern");b.setAttribute("pattern",a);this.root.appendChild(b)};mxXmlCanvas2D.prototype.setLineCap=function(a){if(this.compressed){if(this.state.lineCap==a)return;mxAbstractCanvas2D.prototype.setLineCap.apply(this,arguments)}var b=this.createElement("linecap");b.setAttribute("cap",a);this [...]
+mxXmlCanvas2D.prototype.setLineJoin=function(a){if(this.compressed){if(this.state.lineJoin==a)return;mxAbstractCanvas2D.prototype.setLineJoin.apply(this,arguments)}var b=this.createElement("linejoin");b.setAttribute("join",a);this.root.appendChild(b)};mxXmlCanvas2D.prototype.setMiterLimit=function(a){if(this.compressed){if(this.state.miterLimit==a)return;mxAbstractCanvas2D.prototype.setMiterLimit.apply(this,arguments)}var b=this.createElement("miterlimit");b.setAttribute("limit",a);this. [...]
+mxXmlCanvas2D.prototype.setFontColor=function(a){if(this.textEnabled){a==mxConstants.NONE&&(a=null);if(this.compressed){if(this.state.fontColor==a)return;mxAbstractCanvas2D.prototype.setFontColor.apply(this,arguments)}var b=this.createElement("fontcolor");b.setAttribute("color",null!=a?a:mxConstants.NONE);this.root.appendChild(b)}};
+mxXmlCanvas2D.prototype.setFontBackgroundColor=function(a){if(this.textEnabled){a==mxConstants.NONE&&(a=null);if(this.compressed){if(this.state.fontBackgroundColor==a)return;mxAbstractCanvas2D.prototype.setFontBackgroundColor.apply(this,arguments)}var b=this.createElement("fontbackgroundcolor");b.setAttribute("color",null!=a?a:mxConstants.NONE);this.root.appendChild(b)}};
+mxXmlCanvas2D.prototype.setFontBorderColor=function(a){if(this.textEnabled){a==mxConstants.NONE&&(a=null);if(this.compressed){if(this.state.fontBorderColor==a)return;mxAbstractCanvas2D.prototype.setFontBorderColor.apply(this,arguments)}var b=this.createElement("fontbordercolor");b.setAttribute("color",null!=a?a:mxConstants.NONE);this.root.appendChild(b)}};
+mxXmlCanvas2D.prototype.setFontSize=function(a){if(this.textEnabled){if(this.compressed){if(this.state.fontSize==a)return;mxAbstractCanvas2D.prototype.setFontSize.apply(this,arguments)}var b=this.createElement("fontsize");b.setAttribute("size",a);this.root.appendChild(b)}};
+mxXmlCanvas2D.prototype.setFontFamily=function(a){if(this.textEnabled){if(this.compressed){if(this.state.fontFamily==a)return;mxAbstractCanvas2D.prototype.setFontFamily.apply(this,arguments)}var b=this.createElement("fontfamily");b.setAttribute("family",a);this.root.appendChild(b)}};
+mxXmlCanvas2D.prototype.setFontStyle=function(a){if(this.textEnabled){null==a&&(a=0);if(this.compressed){if(this.state.fontStyle==a)return;mxAbstractCanvas2D.prototype.setFontStyle.apply(this,arguments)}var b=this.createElement("fontstyle");b.setAttribute("style",a);this.root.appendChild(b)}};
+mxXmlCanvas2D.prototype.setShadow=function(a){if(this.compressed){if(this.state.shadow==a)return;mxAbstractCanvas2D.prototype.setShadow.apply(this,arguments)}var b=this.createElement("shadow");b.setAttribute("enabled",a?"1":"0");this.root.appendChild(b)};
+mxXmlCanvas2D.prototype.setShadowColor=function(a){if(this.compressed){a==mxConstants.NONE&&(a=null);if(this.state.shadowColor==a)return;mxAbstractCanvas2D.prototype.setShadowColor.apply(this,arguments)}var b=this.createElement("shadowcolor");b.setAttribute("color",null!=a?a:mxConstants.NONE);this.root.appendChild(b)};
+mxXmlCanvas2D.prototype.setShadowAlpha=function(a){if(this.compressed){if(this.state.shadowAlpha==a)return;mxAbstractCanvas2D.prototype.setShadowAlpha.apply(this,arguments)}var b=this.createElement("shadowalpha");b.setAttribute("alpha",a);this.root.appendChild(b)};
+mxXmlCanvas2D.prototype.setShadowOffset=function(a,b){if(this.compressed){if(this.state.shadowDx==a&&this.state.shadowDy==b)return;mxAbstractCanvas2D.prototype.setShadowOffset.apply(this,arguments)}var c=this.createElement("shadowoffset");c.setAttribute("dx",a);c.setAttribute("dy",b);this.root.appendChild(c)};
+mxXmlCanvas2D.prototype.rect=function(a,b,c,d){var e=this.createElement("rect");e.setAttribute("x",this.format(a));e.setAttribute("y",this.format(b));e.setAttribute("w",this.format(c));e.setAttribute("h",this.format(d));this.root.appendChild(e)};
+mxXmlCanvas2D.prototype.roundrect=function(a,b,c,d,e,f){var g=this.createElement("roundrect");g.setAttribute("x",this.format(a));g.setAttribute("y",this.format(b));g.setAttribute("w",this.format(c));g.setAttribute("h",this.format(d));g.setAttribute("dx",this.format(e));g.setAttribute("dy",this.format(f));this.root.appendChild(g)};
+mxXmlCanvas2D.prototype.ellipse=function(a,b,c,d){var e=this.createElement("ellipse");e.setAttribute("x",this.format(a));e.setAttribute("y",this.format(b));e.setAttribute("w",this.format(c));e.setAttribute("h",this.format(d));this.root.appendChild(e)};
+mxXmlCanvas2D.prototype.image=function(a,b,c,d,e,f,g,k){e=this.converter.convert(e);var l=this.createElement("image");l.setAttribute("x",this.format(a));l.setAttribute("y",this.format(b));l.setAttribute("w",this.format(c));l.setAttribute("h",this.format(d));l.setAttribute("src",e);l.setAttribute("aspect",f?"1":"0");l.setAttribute("flipH",g?"1":"0");l.setAttribute("flipV",k?"1":"0");this.root.appendChild(l)};
+mxXmlCanvas2D.prototype.begin=function(){this.root.appendChild(this.createElement("begin"));this.lastY=this.lastX=0};mxXmlCanvas2D.prototype.moveTo=function(a,b){var c=this.createElement("move");c.setAttribute("x",this.format(a));c.setAttribute("y",this.format(b));this.root.appendChild(c);this.lastX=a;this.lastY=b};
+mxXmlCanvas2D.prototype.lineTo=function(a,b){var c=this.createElement("line");c.setAttribute("x",this.format(a));c.setAttribute("y",this.format(b));this.root.appendChild(c);this.lastX=a;this.lastY=b};mxXmlCanvas2D.prototype.quadTo=function(a,b,c,d){var e=this.createElement("quad");e.setAttribute("x1",this.format(a));e.setAttribute("y1",this.format(b));e.setAttribute("x2",this.format(c));e.setAttribute("y2",this.format(d));this.root.appendChild(e);this.lastX=c;this.lastY=d};
+mxXmlCanvas2D.prototype.curveTo=function(a,b,c,d,e,f){var g=this.createElement("curve");g.setAttribute("x1",this.format(a));g.setAttribute("y1",this.format(b));g.setAttribute("x2",this.format(c));g.setAttribute("y2",this.format(d));g.setAttribute("x3",this.format(e));g.setAttribute("y3",this.format(f));this.root.appendChild(g);this.lastX=e;this.lastY=f};mxXmlCanvas2D.prototype.close=function(){this.root.appendChild(this.createElement("close"))};
+mxXmlCanvas2D.prototype.text=function(a,b,c,d,e,f,g,k,l,m,n,p,q){if(this.textEnabled&&null!=e){mxUtils.isNode(e)&&(e=mxUtils.getOuterHtml(e));var r=this.createElement("text");r.setAttribute("x",this.format(a));r.setAttribute("y",this.format(b));r.setAttribute("w",this.format(c));r.setAttribute("h",this.format(d));r.setAttribute("str",e);null!=f&&r.setAttribute("align",f);null!=g&&r.setAttribute("valign",g);r.setAttribute("wrap",k?"1":"0");null==l&&(l="");r.setAttribute("format",l);null!= [...]
+m);null!=n&&r.setAttribute("clip",n?"1":"0");null!=p&&r.setAttribute("rotation",p);null!=q&&r.setAttribute("dir",q);this.root.appendChild(r)}};mxXmlCanvas2D.prototype.stroke=function(){this.root.appendChild(this.createElement("stroke"))};mxXmlCanvas2D.prototype.fill=function(){this.root.appendChild(this.createElement("fill"))};mxXmlCanvas2D.prototype.fillAndStroke=function(){this.root.appendChild(this.createElement("fillstroke"))};
+function mxSvgCanvas2D(a,b){mxAbstractCanvas2D.call(this);this.root=a;this.gradients=[];this.defs=null;this.styleEnabled=null!=b?b:!1;var c=null;if(a.ownerDocument!=document)for(c=a;null!=c&&"svg"!=c.nodeName;)c=c.parentNode;null!=c&&(0<c.getElementsByTagName("defs").length&&(this.defs=c.getElementsByTagName("defs")[0]),null==this.defs&&(this.defs=this.createElement("defs"),null!=c.firstChild?c.insertBefore(this.defs,c.firstChild):c.appendChild(this.defs)),this.styleEnabled&&this.defs.ap [...]
+mxUtils.extend(mxSvgCanvas2D,mxAbstractCanvas2D);(function(){mxSvgCanvas2D.prototype.useDomParser=!mxClient.IS_IE&&"function"===typeof DOMParser&&"function"===typeof XMLSerializer;if(mxSvgCanvas2D.prototype.useDomParser)try{var a=(new DOMParser).parseFromString("test text","text/html");mxSvgCanvas2D.prototype.useDomParser=null!=a}catch(b){mxSvgCanvas2D.prototype.useDomParser=!1}})();mxSvgCanvas2D.prototype.node=null;mxSvgCanvas2D.prototype.matchHtmlAlignment=!0;
+mxSvgCanvas2D.prototype.textEnabled=!0;mxSvgCanvas2D.prototype.foEnabled=!0;mxSvgCanvas2D.prototype.foAltText="[Object]";mxSvgCanvas2D.prototype.foOffset=0;mxSvgCanvas2D.prototype.textOffset=0;mxSvgCanvas2D.prototype.imageOffset=0;mxSvgCanvas2D.prototype.strokeTolerance=0;mxSvgCanvas2D.prototype.refCount=0;mxSvgCanvas2D.prototype.blockImagePointerEvents=!1;mxSvgCanvas2D.prototype.lineHeightCorrection=1;mxSvgCanvas2D.prototype.pointerEventsValue="all";mxSvgCanvas2D.prototype.fontMetricsPa [...]
+mxSvgCanvas2D.prototype.cacheOffsetSize=!0;mxSvgCanvas2D.prototype.format=function(a){return parseFloat(parseFloat(a).toFixed(2))};mxSvgCanvas2D.prototype.getBaseUrl=function(){var a=window.location.href,b=a.lastIndexOf("#");0<b&&(a=a.substring(0,b));return a};mxSvgCanvas2D.prototype.reset=function(){mxAbstractCanvas2D.prototype.reset.apply(this,arguments);this.gradients=[]};
+mxSvgCanvas2D.prototype.createStyle=function(a){a=this.createElement("style");a.setAttribute("type","text/css");mxUtils.write(a,"svg{font-family:"+mxConstants.DEFAULT_FONTFAMILY+";font-size:"+mxConstants.DEFAULT_FONTSIZE+";fill:none;stroke-miterlimit:10}");return a};
+mxSvgCanvas2D.prototype.createElement=function(a,b){if(null!=this.root.ownerDocument.createElementNS)return this.root.ownerDocument.createElementNS(b||mxConstants.NS_SVG,a);var c=this.root.ownerDocument.createElement(a);null!=b&&c.setAttribute("xmlns",b);return c};
+mxSvgCanvas2D.prototype.createAlternateContent=function(a,b,c,d,e,f,g,k,l,m,n,p,q){return null!=this.foAltText?(a=this.state,b=this.createElement("text"),b.setAttribute("x",Math.round(d/2)),b.setAttribute("y",Math.round((e+a.fontSize)/2)),b.setAttribute("fill",a.fontColor||"black"),b.setAttribute("text-anchor","middle"),b.setAttribute("font-size",a.fontSize+"px"),b.setAttribute("font-family",a.fontFamily),(a.fontStyle&mxConstants.FONT_BOLD)==mxConstants.FONT_BOLD&&b.setAttribute("font-we [...]
+(a.fontStyle&mxConstants.FONT_ITALIC)==mxConstants.FONT_ITALIC&&b.setAttribute("font-style","italic"),(a.fontStyle&mxConstants.FONT_UNDERLINE)==mxConstants.FONT_UNDERLINE&&b.setAttribute("text-decoration","underline"),mxUtils.write(b,this.foAltText),b):null};
+mxSvgCanvas2D.prototype.createGradientId=function(a,b,c,d,e){"#"==a.charAt(0)&&(a=a.substring(1));"#"==b.charAt(0)&&(b=b.substring(1));a=a.toLowerCase()+"-"+c;b=b.toLowerCase()+"-"+d;c=null;null==e||e==mxConstants.DIRECTION_SOUTH?c="s":e==mxConstants.DIRECTION_EAST?c="e":(d=a,a=b,b=d,e==mxConstants.DIRECTION_NORTH?c="s":e==mxConstants.DIRECTION_WEST&&(c="e"));return"mx-gradient-"+a+"-"+b+"-"+c};
+mxSvgCanvas2D.prototype.getSvgGradient=function(a,b,c,d,e){var f=this.createGradientId(a,b,c,d,e),g=this.gradients[f];if(null==g){var k=this.root.ownerSVGElement,l=0,m=f+"-"+l;if(null!=k)for(g=k.ownerDocument.getElementById(m);null!=g&&g.ownerSVGElement!=k;)m=f+"-"+l++,g=k.ownerDocument.getElementById(m);else m="id"+ ++this.refCount;null==g&&(g=this.createSvgGradient(a,b,c,d,e),g.setAttribute("id",m),null!=this.defs?this.defs.appendChild(g):k.appendChild(g));this.gradients[f]=g}return g. [...]
+mxSvgCanvas2D.prototype.createSvgGradient=function(a,b,c,d,e){var f=this.createElement("linearGradient");f.setAttribute("x1","0%");f.setAttribute("y1","0%");f.setAttribute("x2","0%");f.setAttribute("y2","0%");null==e||e==mxConstants.DIRECTION_SOUTH?f.setAttribute("y2","100%"):e==mxConstants.DIRECTION_EAST?f.setAttribute("x2","100%"):e==mxConstants.DIRECTION_NORTH?f.setAttribute("y1","100%"):e==mxConstants.DIRECTION_WEST&&f.setAttribute("x1","100%");c=1>c?";stop-opacity:"+c:"";e=this.crea [...]
+e.setAttribute("offset","0%");e.setAttribute("style","stop-color:"+a+c);f.appendChild(e);c=1>d?";stop-opacity:"+d:"";e=this.createElement("stop");e.setAttribute("offset","100%");e.setAttribute("style","stop-color:"+b+c);f.appendChild(e);return f};
+mxSvgCanvas2D.prototype.addNode=function(a,b){var c=this.node,d=this.state;if(null!=c){if("path"==c.nodeName)if(null!=this.path&&0<this.path.length)c.setAttribute("d",this.path.join(" "));else return;a&&null!=d.fillColor?this.updateFill():this.styleEnabled||("ellipse"==c.nodeName&&mxClient.IS_FF?c.setAttribute("fill","transparent"):c.setAttribute("fill","none"),a=!1);b&&null!=d.strokeColor?this.updateStroke():this.styleEnabled||c.setAttribute("stroke","none");null!=d.transform&&0<d.trans [...]
+c.setAttribute("transform",d.transform);d.shadow&&this.root.appendChild(this.createShadow(c));0<this.strokeTolerance&&!a&&this.root.appendChild(this.createTolerance(c));!this.pointerEvents||"path"==c.nodeName&&this.path[this.path.length-1]!=this.closeOp?this.pointerEvents||null!=this.originalRoot||c.setAttribute("pointer-events","none"):c.setAttribute("pointer-events",this.pointerEventsValue);("rect"!=c.nodeName&&"path"!=c.nodeName&&"ellipse"!=c.nodeName||"none"!=c.getAttribute("fill")&& [...]
+c.getAttribute("fill")||"none"!=c.getAttribute("stroke")||"none"!=c.getAttribute("pointer-events"))&&this.root.appendChild(c);this.node=null}};
+mxSvgCanvas2D.prototype.updateFill=function(){var a=this.state;(1>a.alpha||1>a.fillAlpha)&&this.node.setAttribute("fill-opacity",a.alpha*a.fillAlpha);if(null!=a.fillColor)if(null!=a.gradientColor)if(a=this.getSvgGradient(a.fillColor,a.gradientColor,a.gradientFillAlpha,a.gradientAlpha,a.gradientDirection),mxClient.IS_CHROME_APP||mxClient.IS_IE||mxClient.IS_IE11||mxClient.IS_EDGE||this.root.ownerDocument!=document)this.node.setAttribute("fill","url(#"+a+")");else{var b=this.getBaseUrl().re [...]
+"\\$1");this.node.setAttribute("fill","url("+b+"#"+a+")")}else this.node.setAttribute("fill",a.fillColor.toLowerCase())};mxSvgCanvas2D.prototype.getCurrentStrokeWidth=function(){return Math.max(1,this.format(this.state.strokeWidth*this.state.scale))};
+mxSvgCanvas2D.prototype.updateStroke=function(){var a=this.state;this.node.setAttribute("stroke",a.strokeColor.toLowerCase());(1>a.alpha||1>a.strokeAlpha)&&this.node.setAttribute("stroke-opacity",a.alpha*a.strokeAlpha);var b=this.getCurrentStrokeWidth();1!=b&&this.node.setAttribute("stroke-width",b);"path"==this.node.nodeName&&this.updateStrokeAttributes();a.dashed&&this.node.setAttribute("stroke-dasharray",this.createDashPattern((a.fixDash?1:a.strokeWidth)*a.scale))};
+mxSvgCanvas2D.prototype.updateStrokeAttributes=function(){var a=this.state;null!=a.lineJoin&&"miter"!=a.lineJoin&&this.node.setAttribute("stroke-linejoin",a.lineJoin);if(null!=a.lineCap){var b=a.lineCap;"flat"==b&&(b="butt");"butt"!=b&&this.node.setAttribute("stroke-linecap",b)}null==a.miterLimit||this.styleEnabled&&10==a.miterLimit||this.node.setAttribute("stroke-miterlimit",a.miterLimit)};
+mxSvgCanvas2D.prototype.createDashPattern=function(a){var b=[];if("string"===typeof this.state.dashPattern){var c=this.state.dashPattern.split(" ");if(0<c.length)for(var d=0;d<c.length;d++)b[d]=Number(c[d])*a}return b.join(" ")};
+mxSvgCanvas2D.prototype.createTolerance=function(a){a=a.cloneNode(!0);var b=parseFloat(a.getAttribute("stroke-width")||1)+this.strokeTolerance;a.setAttribute("pointer-events","stroke");a.setAttribute("visibility","hidden");a.removeAttribute("stroke-dasharray");a.setAttribute("stroke-width",b);a.setAttribute("fill","none");a.setAttribute("stroke",mxClient.IS_OT?"none":"white");return a};
+mxSvgCanvas2D.prototype.createShadow=function(a){a=a.cloneNode(!0);var b=this.state;"none"==a.getAttribute("fill")||mxClient.IS_FF&&"transparent"==a.getAttribute("fill")||a.setAttribute("fill",b.shadowColor);"none"!=a.getAttribute("stroke")&&a.setAttribute("stroke",b.shadowColor);a.setAttribute("transform","translate("+this.format(b.shadowDx*b.scale)+","+this.format(b.shadowDy*b.scale)+")"+(b.transform||""));a.setAttribute("opacity",b.shadowAlpha);return a};
+mxSvgCanvas2D.prototype.setLink=function(a){if(null==a)this.root=this.originalRoot;else{this.originalRoot=this.root;var b=this.createElement("a");null==b.setAttributeNS||this.root.ownerDocument!=document&&null==document.documentMode?b.setAttribute("xlink:href",a):b.setAttributeNS(mxConstants.NS_XLINK,"xlink:href",a);this.root.appendChild(b);this.root=b}};
+mxSvgCanvas2D.prototype.rotate=function(a,b,c,d,e){if(0!=a||b||c){var f=this.state;d+=f.dx;e+=f.dy;d*=f.scale;e*=f.scale;f.transform=f.transform||"";if(b&&c)a+=180;else if(b!=c){var g=b?d:0,k=b?-1:1,l=c?e:0,m=c?-1:1;f.transform+="translate("+this.format(g)+","+this.format(l)+")scale("+this.format(k)+","+this.format(m)+")translate("+this.format(-g)+","+this.format(-l)+")"}if(b?!c:c)a*=-1;0!=a&&(f.transform+="rotate("+this.format(a)+","+this.format(d)+","+this.format(e)+")");f.rotation+=a; [...]
+d;f.rotationCy=e}};mxSvgCanvas2D.prototype.begin=function(){mxAbstractCanvas2D.prototype.begin.apply(this,arguments);this.node=this.createElement("path")};mxSvgCanvas2D.prototype.rect=function(a,b,c,d){var e=this.state,f=this.createElement("rect");f.setAttribute("x",this.format((a+e.dx)*e.scale));f.setAttribute("y",this.format((b+e.dy)*e.scale));f.setAttribute("width",this.format(c*e.scale));f.setAttribute("height",this.format(d*e.scale));this.node=f};
+mxSvgCanvas2D.prototype.roundrect=function(a,b,c,d,e,f){this.rect(a,b,c,d);0<e&&this.node.setAttribute("rx",this.format(e*this.state.scale));0<f&&this.node.setAttribute("ry",this.format(f*this.state.scale))};mxSvgCanvas2D.prototype.ellipse=function(a,b,c,d){var e=this.state,f=this.createElement("ellipse");f.setAttribute("cx",Math.round((a+c/2+e.dx)*e.scale));f.setAttribute("cy",Math.round((b+d/2+e.dy)*e.scale));f.setAttribute("rx",c/2*e.scale);f.setAttribute("ry",d/2*e.scale);this.node=f};
+mxSvgCanvas2D.prototype.image=function(a,b,c,d,e,f,g,k){e=this.converter.convert(e);f=null!=f?f:!0;g=null!=g?g:!1;k=null!=k?k:!1;var l=this.state;a+=l.dx;b+=l.dy;var m=this.createElement("image");m.setAttribute("x",this.format(a*l.scale)+this.imageOffset);m.setAttribute("y",this.format(b*l.scale)+this.imageOffset);m.setAttribute("width",this.format(c*l.scale));m.setAttribute("height",this.format(d*l.scale));null==m.setAttributeNS?m.setAttribute("xlink:href",e):m.setAttributeNS(mxConstant [...]
+"xlink:href",e);f||m.setAttribute("preserveAspectRatio","none");(1>l.alpha||1>l.fillAlpha)&&m.setAttribute("opacity",l.alpha*l.fillAlpha);e=this.state.transform||"";if(g||k){var n=f=1,p=0,q=0;g&&(f=-1,p=-c-2*a);k&&(n=-1,q=-d-2*b);e+="scale("+f+","+n+")translate("+p*l.scale+","+q*l.scale+")"}0<e.length&&m.setAttribute("transform",e);this.pointerEvents||m.setAttribute("pointer-events","none");this.root.appendChild(m);this.blockImagePointerEvents&&(m.setAttribute("style","pointer-events:non [...]
+m.setAttribute("visibility","hidden"),m.setAttribute("pointer-events","fill"),m.setAttribute("x",this.format(a*l.scale)),m.setAttribute("y",this.format(b*l.scale)),m.setAttribute("width",this.format(c*l.scale)),m.setAttribute("height",this.format(d*l.scale)),this.root.appendChild(m))};
+mxSvgCanvas2D.prototype.convertHtml=function(a){if(this.useDomParser){var b=(new DOMParser).parseFromString(a,"text/html");null!=b&&(a=(new XMLSerializer).serializeToString(b.body),"<body"==a.substring(0,5)&&(a=a.substring(a.indexOf(">",5)+1)),"</body>"==a.substring(a.length-7,a.length)&&(a=a.substring(0,a.length-7)))}else{if(null!=document.implementation&&null!=document.implementation.createDocument){var b=document.implementation.createDocument("http://www.w3.org/1999/xhtml","html",null [...]
+b.documentElement.appendChild(c);var d=document.createElement("div");d.innerHTML=a;for(a=d.firstChild;null!=a;)d=a.nextSibling,c.appendChild(b.adoptNode(a)),a=d;return c.innerHTML}b=document.createElement("textarea");b.innerHTML=a.replace(/&amp;/g,"&amp;amp;").replace(/&#60;/g,"&amp;lt;").replace(/&#62;/g,"&amp;gt;").replace(/&lt;/g,"&amp;lt;").replace(/&gt;/g,"&amp;gt;").replace(/</g,"&lt;").replace(/>/g,"&gt;");a=b.value.replace(/&/g,"&amp;").replace(/&amp;lt;/g,"&lt;").replace(/&amp;g [...]
+"&amp;").replace(/<br>/g,"<br />").replace(/<hr>/g,"<hr />").replace(/(<img[^>]+)>/gm,"$1 />")}return a};
+mxSvgCanvas2D.prototype.createDiv=function(a,b,c,d,e){c=this.state;d="display:inline-block;font-size:"+c.fontSize+"px;font-family:"+c.fontFamily+";color:"+c.fontColor+";line-height:"+(mxConstants.ABSOLUTE_LINE_HEIGHT?c.fontSize*mxConstants.LINE_HEIGHT+"px":mxConstants.LINE_HEIGHT*this.lineHeightCorrection)+";"+d;(c.fontStyle&mxConstants.FONT_BOLD)==mxConstants.FONT_BOLD&&(d+="font-weight:bold;");(c.fontStyle&mxConstants.FONT_ITALIC)==mxConstants.FONT_ITALIC&&(d+="font-style:italic;");(c. [...]
+mxConstants.FONT_UNDERLINE)==mxConstants.FONT_UNDERLINE&&(d+="text-decoration:underline;");b==mxConstants.ALIGN_CENTER?d+="text-align:center;":b==mxConstants.ALIGN_RIGHT&&(d+="text-align:right;");b="";null!=c.fontBackgroundColor&&(b+="background-color:"+c.fontBackgroundColor+";");null!=c.fontBorderColor&&(b+="border:1px solid "+c.fontBorderColor+";");mxUtils.isNode(a)||(a=this.convertHtml(a),"fill"!=e&&"width"!=e?a='<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;te [...]
+b+'">'+a+"</div>":d+=b);if(!mxClient.IS_IE&&document.createElementNS)return e=document.createElementNS("http://www.w3.org/1999/xhtml","div"),e.setAttribute("style",d),mxUtils.isNode(a)?this.root.ownerDocument!=document?e.appendChild(a.cloneNode(!0)):e.appendChild(a):e.innerHTML=a,e;mxUtils.isNode(a)&&this.root.ownerDocument!=document&&(a=a.outerHTML);return mxUtils.parseXml('<div xmlns="http://www.w3.org/1999/xhtml" style="'+d+'">'+a+"</div>").documentElement};
+mxSvgCanvas2D.prototype.invalidateCachedOffsetSize=function(a){delete a.firstChild.mxCachedOffsetWidth;delete a.firstChild.mxCachedFinalOffsetWidth;delete a.firstChild.mxCachedFinalOffsetHeight};
+mxSvgCanvas2D.prototype.updateText=function(a,b,c,d,e,f,g,k,l,m,n){if(null!=n&&null!=n.firstChild&&null!=n.firstChild.firstChild&&null!=n.firstChild.firstChild.firstChild){n=n.firstChild;var p=n.firstChild,q=p.firstChild;m=null!=m?m:0;var r=this.state;a+=r.dx;b+=r.dy;l?(q.style.maxHeight=Math.round(d)+"px",q.style.maxWidth=Math.round(c)+"px"):"fill"==k?(q.style.width=Math.round(c+1)+"px",q.style.height=Math.round(d+1)+"px"):"width"==k&&(q.style.width=Math.round(c+1)+"px",0<d&&(q.style.ma [...]
+"px"));g&&0<c&&(q.style.width=Math.round(c+1)+"px");var t,u=q;null!=u.firstChild&&"DIV"==u.firstChild.nodeName&&(u=u.firstChild);t=(null!=n.mxCachedOffsetWidth?n.mxCachedOffsetWidth:u.offsetWidth)+2;g&&"fill"!=k&&(l&&(t=Math.min(t,c)),q.style.width=t+"px");t=(null!=n.mxCachedFinalOffsetWidth?n.mxCachedFinalOffsetWidth:u.offsetWidth)+2;g=(null!=n.mxCachedFinalOffsetHeight?n.mxCachedFinalOffsetHeight:u.offsetHeight)-2;l&&(g=Math.min(g,d),t=Math.min(t,c));"width"==k?d=g:"fill"!=k&&(c=t,d=g) [...]
+mxConstants.ALIGN_CENTER?l-=c/2:e==mxConstants.ALIGN_RIGHT&&(l-=c);a+=l;f==mxConstants.ALIGN_MIDDLE?g-=d/2:f==mxConstants.ALIGN_BOTTOM&&(g-=d);"fill"!=k&&mxClient.IS_FF&&mxClient.IS_WIN&&(g-=2);b+=g;e=1!=r.scale?"scale("+r.scale+")":"";0!=r.rotation&&this.rotateHtml?(e+="rotate("+r.rotation+","+c/2+","+d/2+")",b=this.rotatePoint((a+c/2)*r.scale,(b+d/2)*r.scale,r.rotation,r.rotationCx,r.rotationCy),a=b.x-c*r.scale/2,b=b.y-d*r.scale/2):(a*=r.scale,b*=r.scale);0!=m&&(e+="rotate("+m+","+-l+" [...]
+n.setAttribute("transform","translate("+Math.round(a)+","+Math.round(b)+")"+e);p.setAttribute("width",Math.round(Math.max(1,c)));p.setAttribute("height",Math.round(Math.max(1,d)))}};
+mxSvgCanvas2D.prototype.text=function(a,b,c,d,e,f,g,k,l,m,n,p,q){if(this.textEnabled&&null!=e){p=null!=p?p:0;var r=this.state;a+=r.dx;b+=r.dy;if(this.foEnabled&&"html"==l){var t="vertical-align:top;";n?t+="overflow:hidden;max-height:"+Math.round(d)+"px;max-width:"+Math.round(c)+"px;":"fill"==m?t+="width:"+Math.round(c+1)+"px;height:"+Math.round(d+1)+"px;overflow:hidden;":"width"==m&&(t+="width:"+Math.round(c+1)+"px;",0<d&&(t+="max-height:"+Math.round(d)+"px;overflow:hidden;"));var t=k&&0 [...]
+Math.round(c+1)+"px;white-space:normal;word-wrap:"+mxConstants.WORD_WRAP+";"):t+"white-space:nowrap;",u=this.createElement("g");1>r.alpha&&u.setAttribute("opacity",r.alpha);var x=this.createElement("foreignObject");x.setAttribute("style","overflow:visible;");x.setAttribute("pointer-events","all");t=this.createDiv(e,f,g,t,m);if(null!=t){null!=q&&t.setAttribute("dir",q);u.appendChild(x);this.root.appendChild(u);var y,A;q=y=2;if(!mxClient.IS_IE||9!=document.documentMode&&mxClient.IS_SVG){th [...]
+document?(t.style.visibility="hidden",document.body.appendChild(t)):x.appendChild(t);var z=t;null!=z.firstChild&&"DIV"==z.firstChild.nodeName&&(z=z.firstChild,k&&"break-word"==t.style.wordWrap&&(z.style.width="100%"));v=z.offsetWidth;0==v&&t.parentNode==x&&(t.style.visibility="hidden",document.body.appendChild(t),v=z.offsetWidth);this.cacheOffsetSize&&(u.mxCachedOffsetWidth=v);!n&&k&&0<c&&this.root.ownerDocument!=document&&"fill"!=m&&"width"!=m&&(B=t.style.whiteSpace,t.style.whiteSpace=" [...]
+z.offsetWidth&&(t.style.whiteSpace=B));y=v+y-1;k&&"fill"!=m&&"width"!=m&&(n&&(y=Math.min(y,c)),t.style.width=y+"px");y=z.offsetWidth;A=z.offsetHeight;this.cacheOffsetSize&&(u.mxCachedFinalOffsetWidth=y,u.mxCachedFinalOffsetHeight=A);A-=q;t.parentNode!=x&&(x.appendChild(t),t.style.visibility="")}else{z=document.createElement("div");z.style.cssText=t.getAttribute("style");z.style.display=mxClient.IS_QUIRKS?"inline":"inline-block";z.style.position="absolute";z.style.visibility="hidden";A=do [...]
+A.style.display=mxClient.IS_QUIRKS?"inline":"inline-block";A.style.wordWrap=mxConstants.WORD_WRAP;A.innerHTML=mxUtils.isNode(e)?e.outerHTML:e;z.appendChild(A);document.body.appendChild(z);8!=document.documentMode&&9!=document.documentMode&&null!=r.fontBorderColor&&(y+=2,q+=2);if(k&&0<c){var v=A.offsetWidth;if(!n&&k&&0<c&&this.root.ownerDocument!=document&&"fill"!=m){var B=z.style.whiteSpace;A.style.whiteSpace="nowrap";v<A.offsetWidth&&(z.style.whiteSpace=B)}n&&(v=Math.min(v,c));z.style.w [...]
+y=A.offsetWidth+y+0;A=A.offsetHeight+q;z.style.display="inline-block";z.style.position="";z.style.visibility="";z.style.width=y+"px";t.setAttribute("style",z.style.cssText)}else y=A.offsetWidth+y,A=A.offsetHeight+q;z.parentNode.removeChild(z);x.appendChild(t)}n&&(A=Math.min(A,d),y=Math.min(y,c));"width"==m?d=A:"fill"!=m&&(c=y,d=A);1>r.alpha&&u.setAttribute("opacity",r.alpha);q=t=0;f==mxConstants.ALIGN_CENTER?t-=c/2:f==mxConstants.ALIGN_RIGHT&&(t-=c);a+=t;g==mxConstants.ALIGN_MIDDLE?q-=d/ [...]
+(q-=d);"fill"!=m&&mxClient.IS_FF&&mxClient.IS_WIN&&(q-=2);b+=q;z=1!=r.scale?"scale("+r.scale+")":"";0!=r.rotation&&this.rotateHtml?(z+="rotate("+r.rotation+","+c/2+","+d/2+")",b=this.rotatePoint((a+c/2)*r.scale,(b+d/2)*r.scale,r.rotation,r.rotationCx,r.rotationCy),a=b.x-c*r.scale/2,b=b.y-d*r.scale/2):(a*=r.scale,b*=r.scale);0!=p&&(z+="rotate("+p+","+-t+","+-q+")");u.setAttribute("transform","translate("+(Math.round(a)+this.foOffset)+","+(Math.round(b)+this.foOffset)+")"+z);x.setAttribute [...]
+Math.round(Math.max(1,c)));x.setAttribute("height",Math.round(Math.max(1,d)));this.root.ownerDocument!=document&&(a=this.createAlternateContent(x,a,b,c,d,e,f,g,k,l,m,n,p),null!=a&&(x.setAttribute("requiredFeatures","http://www.w3.org/TR/SVG11/feature#Extensibility"),c=this.createElement("switch"),c.appendChild(x),c.appendChild(a),u.appendChild(c)))}}else this.plainText(a,b,c,d,e,f,g,k,m,n,p,q)}};
+mxSvgCanvas2D.prototype.createClip=function(a,b,c,d){a=Math.round(a);b=Math.round(b);c=Math.round(c);d=Math.round(d);for(var e="mx-clip-"+a+"-"+b+"-"+c+"-"+d,f=0,g=e+"-"+f;null!=document.getElementById(g);)g=e+"-"+ ++f;clip=this.createElement("clipPath");clip.setAttribute("id",g);e=this.createElement("rect");e.setAttribute("x",a);e.setAttribute("y",b);e.setAttribute("width",c);e.setAttribute("height",d);clip.appendChild(e);return clip};
+mxSvgCanvas2D.prototype.plainText=function(a,b,c,d,e,f,g,k,l,m,n,p){n=null!=n?n:0;k=this.state;var q=k.fontSize,r=this.createElement("g"),t=k.transform||"";this.updateFont(r);0!=n&&(t+="rotate("+n+","+this.format(a*k.scale)+","+this.format(b*k.scale)+")");null!=p&&r.setAttribute("direction",p);m&&0<c&&0<d&&(p=a,n=b,f==mxConstants.ALIGN_CENTER?p-=c/2:f==mxConstants.ALIGN_RIGHT&&(p-=c),"fill"!=l&&(g==mxConstants.ALIGN_MIDDLE?n-=d/2:g==mxConstants.ALIGN_BOTTOM&&(n-=d)),n=this.createClip(p*k [...]
+n*k.scale-2,c*k.scale+4,d*k.scale+4),null!=this.defs?this.defs.appendChild(n):this.root.appendChild(n),mxClient.IS_CHROME_APP||mxClient.IS_IE||mxClient.IS_IE11||mxClient.IS_EDGE||this.root.ownerDocument!=document?r.setAttribute("clip-path","url(#"+n.getAttribute("id")+")"):(p=this.getBaseUrl().replace(/([\(\)])/g,"\\$1"),r.setAttribute("clip-path","url("+p+"#"+n.getAttribute("id")+")")));n=f==mxConstants.ALIGN_RIGHT?"end":f==mxConstants.ALIGN_CENTER?"middle":"start";"start"!=n&&r.setAttr [...]
+n);this.styleEnabled&&q==mxConstants.DEFAULT_FONTSIZE||r.setAttribute("font-size",q*k.scale+"px");0<t.length&&r.setAttribute("transform",t);1>k.alpha&&r.setAttribute("opacity",k.alpha);t=e.split("\n");p=Math.round(q*mxConstants.LINE_HEIGHT);var u=q+(t.length-1)*p;n=b+q-1;g==mxConstants.ALIGN_MIDDLE?"fill"==l?n-=d/2:(m=(this.matchHtmlAlignment&&m&&0<d?Math.min(u,d):u)/2,n-=m+1):g==mxConstants.ALIGN_BOTTOM&&("fill"==l?n-=d:(m=this.matchHtmlAlignment&&m&&0<d?Math.min(u,d):u,n-=m+2));for(m=0 [...]
+t[m].length&&0<mxUtils.trim(t[m]).length&&(q=this.createElement("text"),q.setAttribute("x",this.format(a*k.scale)+this.textOffset),q.setAttribute("y",this.format(n*k.scale)+this.textOffset),mxUtils.write(q,t[m]),r.appendChild(q)),n+=p;this.root.appendChild(r);this.addTextBackground(r,e,a,b,c,"fill"==l?d:u,f,g,l)};
+mxSvgCanvas2D.prototype.updateFont=function(a){var b=this.state;a.setAttribute("fill",b.fontColor);this.styleEnabled&&b.fontFamily==mxConstants.DEFAULT_FONTFAMILY||a.setAttribute("font-family",b.fontFamily);(b.fontStyle&mxConstants.FONT_BOLD)==mxConstants.FONT_BOLD&&a.setAttribute("font-weight","bold");(b.fontStyle&mxConstants.FONT_ITALIC)==mxConstants.FONT_ITALIC&&a.setAttribute("font-style","italic");(b.fontStyle&mxConstants.FONT_UNDERLINE)==mxConstants.FONT_UNDERLINE&&a.setAttribute(" [...]
+"underline")};
+mxSvgCanvas2D.prototype.addTextBackground=function(a,b,c,d,e,f,g,k,l){var m=this.state;if(null!=m.fontBackgroundColor||null!=m.fontBorderColor){var n=null;if("fill"==l||"width"==l)g==mxConstants.ALIGN_CENTER?c-=e/2:g==mxConstants.ALIGN_RIGHT&&(c-=e),k==mxConstants.ALIGN_MIDDLE?d-=f/2:k==mxConstants.ALIGN_BOTTOM&&(d-=f),n=new mxRectangle((c+1)*m.scale,d*m.scale,(e-2)*m.scale,(f+2)*m.scale);else if(null!=a.getBBox&&this.root.ownerDocument==document)try{var n=a.getBBox(),p=mxClient.IS_IE&&m [...]
+n=new mxRectangle(n.x,n.y+(p?0:1),n.width,n.height+(p?1:0))}catch(q){}else n=document.createElement("div"),n.style.lineHeight=mxConstants.ABSOLUTE_LINE_HEIGHT?m.fontSize*mxConstants.LINE_HEIGHT+"px":mxConstants.LINE_HEIGHT,n.style.fontSize=m.fontSize+"px",n.style.fontFamily=m.fontFamily,n.style.whiteSpace="nowrap",n.style.position="absolute",n.style.visibility="hidden",n.style.display=mxClient.IS_QUIRKS?"inline":"inline-block",n.style.zoom="1",(m.fontStyle&mxConstants.FONT_BOLD)==mxConst [...]
+(n.style.fontWeight="bold"),(m.fontStyle&mxConstants.FONT_ITALIC)==mxConstants.FONT_ITALIC&&(n.style.fontStyle="italic"),b=mxUtils.htmlEntities(b,!1),n.innerHTML=b.replace(/\n/g,"<br/>"),document.body.appendChild(n),e=n.offsetWidth,f=n.offsetHeight,n.parentNode.removeChild(n),g==mxConstants.ALIGN_CENTER?c-=e/2:g==mxConstants.ALIGN_RIGHT&&(c-=e),k==mxConstants.ALIGN_MIDDLE?d-=f/2:k==mxConstants.ALIGN_BOTTOM&&(d-=f),n=new mxRectangle((c+1)*m.scale,(d+2)*m.scale,e*m.scale,(f+1)*m.scale);nul [...]
+this.createElement("rect"),b.setAttribute("fill",m.fontBackgroundColor||"none"),b.setAttribute("stroke",m.fontBorderColor||"none"),b.setAttribute("x",Math.floor(n.x-1)),b.setAttribute("y",Math.floor(n.y-1)),b.setAttribute("width",Math.ceil(n.width+2)),b.setAttribute("height",Math.ceil(n.height)),m=null!=m.fontBorderColor?Math.max(1,this.format(m.scale)):0,b.setAttribute("stroke-width",m),this.root.ownerDocument==document&&1==mxUtils.mod(m,2)&&b.setAttribute("transform","translate(0.5, 0. [...]
+a.firstChild))}};mxSvgCanvas2D.prototype.stroke=function(){this.addNode(!1,!0)};mxSvgCanvas2D.prototype.fill=function(){this.addNode(!0,!1)};mxSvgCanvas2D.prototype.fillAndStroke=function(){this.addNode(!0,!0)};var mxVmlCanvas2D=function(a){mxAbstractCanvas2D.call(this);this.root=a};mxUtils.extend(mxVmlCanvas2D,mxAbstractCanvas2D);mxVmlCanvas2D.prototype.node=null;mxVmlCanvas2D.prototype.textEnabled=!0;mxVmlCanvas2D.prototype.moveOp="m";mxVmlCanvas2D.prototype.lineOp="l";
+mxVmlCanvas2D.prototype.curveOp="c";mxVmlCanvas2D.prototype.closeOp="x";mxVmlCanvas2D.prototype.rotatedHtmlBackground="";mxVmlCanvas2D.prototype.vmlScale=1;mxVmlCanvas2D.prototype.createElement=function(a){return document.createElement(a)};mxVmlCanvas2D.prototype.createVmlElement=function(a){return this.createElement(mxClient.VML_PREFIX+":"+a)};
+mxVmlCanvas2D.prototype.addNode=function(a,b){var c=this.node,d=this.state;if(null!=c){if("shape"==c.nodeName)if(null!=this.path&&0<this.path.length)c.path=this.path.join(" ")+" e",c.style.width=this.root.style.width,c.style.height=this.root.style.height,c.coordsize=parseInt(c.style.width)+" "+parseInt(c.style.height);else return;c.strokeweight=this.format(Math.max(1,d.strokeWidth*d.scale/this.vmlScale))+"px";d.shadow&&this.root.appendChild(this.createShadow(c,a&&null!=d.fillColor,b&&nul [...]
+b&&null!=d.strokeColor?(c.stroked="true",c.strokecolor=d.strokeColor):c.stroked="false";c.appendChild(this.createStroke());a&&null!=d.fillColor?c.appendChild(this.createFill()):!this.pointerEvents||"shape"==c.nodeName&&this.path[this.path.length-1]!=this.closeOp?c.filled="false":c.appendChild(this.createTransparentFill());this.root.appendChild(c)}};
+mxVmlCanvas2D.prototype.createTransparentFill=function(){var a=this.createVmlElement("fill");a.src=mxClient.imageBasePath+"/transparent.gif";a.type="tile";return a};
+mxVmlCanvas2D.prototype.createFill=function(){var a=this.state,b=this.createVmlElement("fill");b.color=a.fillColor;if(null!=a.gradientColor){b.type="gradient";b.method="none";b.color2=a.gradientColor;var c=180-a.rotation,c=a.gradientDirection==mxConstants.DIRECTION_WEST?c-(90+("x"==this.root.style.flip?180:0)):a.gradientDirection==mxConstants.DIRECTION_EAST?c+(90+("x"==this.root.style.flip?180:0)):a.gradientDirection==mxConstants.DIRECTION_NORTH?c-(180+("y"==this.root.style.flip?-180:0)) [...]
+this.root.style.flip?-180:0);if("x"==this.root.style.flip||"y"==this.root.style.flip)c*=-1;b.angle=mxUtils.mod(c,360);b.opacity=a.alpha*a.gradientFillAlpha*100+"%";b.setAttribute(mxClient.OFFICE_PREFIX+":opacity2",a.alpha*a.gradientAlpha*100+"%")}else if(1>a.alpha||1>a.fillAlpha)b.opacity=a.alpha*a.fillAlpha*100+"%";return b};
+mxVmlCanvas2D.prototype.createStroke=function(){var a=this.state,b=this.createVmlElement("stroke");b.endcap=a.lineCap||"flat";b.joinstyle=a.lineJoin||"miter";b.miterlimit=a.miterLimit||"10";if(1>a.alpha||1>a.strokeAlpha)b.opacity=a.alpha*a.strokeAlpha*100+"%";a.dashed&&(b.dashstyle=this.getVmlDashStyle());return b};mxVmlCanvas2D.prototype.getVmlDashStyle=function(){var a="dash";if("string"===typeof this.state.dashPattern){var b=this.state.dashPattern.split(" ");0<b.length&&1==b[0]&&(a="0 [...]
+mxVmlCanvas2D.prototype.createShadow=function(a,b,c){var d=this.state,e=Math.PI/180*-d.rotation,f=Math.cos(e),e=Math.sin(e),g=d.shadowDx*d.scale,k=d.shadowDy*d.scale;"x"==this.root.style.flip?g*=-1:"y"==this.root.style.flip&&(k*=-1);var l=a.cloneNode(!0);l.style.marginLeft=Math.round(g*f-k*e)+"px";l.style.marginTop=Math.round(g*e+k*f)+"px";8==document.documentMode&&(l.strokeweight=a.strokeweight,"shape"==a.nodeName&&(l.path=this.path.join(" ")+" e",l.style.width=this.root.style.width,l.s [...]
+this.root.style.height,l.coordsize=parseInt(a.style.width)+" "+parseInt(a.style.height)));c?(l.strokecolor=d.shadowColor,l.appendChild(this.createShadowStroke())):l.stroked="false";b?l.appendChild(this.createShadowFill()):l.filled="false";return l};mxVmlCanvas2D.prototype.createShadowFill=function(){var a=this.createVmlElement("fill");a.color=this.state.shadowColor;a.opacity=this.state.alpha*this.state.shadowAlpha*100+"%";return a};
+mxVmlCanvas2D.prototype.createShadowStroke=function(){var a=this.createStroke();a.opacity=this.state.alpha*this.state.shadowAlpha*100+"%";return a};mxVmlCanvas2D.prototype.rotate=function(a,b,c,d,e){b&&c?a+=180:b?this.root.style.flip="x":c&&(this.root.style.flip="y");if(b?!c:c)a*=-1;this.root.style.rotation=a;this.state.rotation+=a;this.state.rotationCx=d;this.state.rotationCy=e};
+mxVmlCanvas2D.prototype.begin=function(){mxAbstractCanvas2D.prototype.begin.apply(this,arguments);this.node=this.createVmlElement("shape");this.node.style.position="absolute"};
+mxVmlCanvas2D.prototype.quadTo=function(a,b,c,d){var e=this.state,f=(this.lastX+e.dx)*e.scale,g=(this.lastY+e.dy)*e.scale;a=(a+e.dx)*e.scale;b=(b+e.dy)*e.scale;c=(c+e.dx)*e.scale;d=(d+e.dy)*e.scale;var g=g+2/3*(b-g),k=c+2/3*(a-c);b=d+2/3*(b-d);this.path.push("c "+this.format(f+2/3*(a-f))+" "+this.format(g)+" "+this.format(k)+" "+this.format(b)+" "+this.format(c)+" "+this.format(d));this.lastX=c/e.scale-e.dx;this.lastY=d/e.scale-e.dy};
+mxVmlCanvas2D.prototype.createRect=function(a,b,c,d,e){var f=this.state;a=this.createVmlElement(a);a.style.position="absolute";a.style.left=this.format((b+f.dx)*f.scale)+"px";a.style.top=this.format((c+f.dy)*f.scale)+"px";a.style.width=this.format(d*f.scale)+"px";a.style.height=this.format(e*f.scale)+"px";return a};mxVmlCanvas2D.prototype.rect=function(a,b,c,d){this.node=this.createRect("rect",a,b,c,d)};
+mxVmlCanvas2D.prototype.roundrect=function(a,b,c,d,e,f){this.node=this.createRect("roundrect",a,b,c,d);this.node.setAttribute("arcsize",Math.max(100*e/c,100*f/d)+"%")};mxVmlCanvas2D.prototype.ellipse=function(a,b,c,d){this.node=this.createRect("oval",a,b,c,d)};
+mxVmlCanvas2D.prototype.image=function(a,b,c,d,e,f,g,k){f?(a=this.createRect("rect",a,b,c,d),a.stroked="false",b=this.createVmlElement("fill"),b.aspect=f?"atmost":"ignore",b.rotate="true",b.type="frame",b.src=e,a.appendChild(b)):(a=this.createRect("image",a,b,c,d),a.src=e);g&&k?a.style.rotation="180":g?a.style.flip="x":k&&(a.style.flip="y");if(1>this.state.alpha||1>this.state.fillAlpha)a.style.filter+="alpha(opacity="+this.state.alpha*this.state.fillAlpha*100+")";this.root.appendChild(a)};
+mxVmlCanvas2D.prototype.createDiv=function(a,b,c,d){c=this.createElement("div");var e=this.state,f="";null!=e.fontBackgroundColor&&(f+="background-color:"+e.fontBackgroundColor+";");null!=e.fontBorderColor&&(f+="border:1px solid "+e.fontBorderColor+";");mxUtils.isNode(a)?c.appendChild(a):"fill"!=d&&"width"!=d?(d=this.createElement("div"),d.style.cssText=f,d.style.display=mxClient.IS_QUIRKS?"inline":"inline-block",d.style.zoom="1",d.style.textDecoration="inherit",d.innerHTML=a,c.appendChi [...]
+f,c.innerHTML=a);a=c.style;a.fontSize=e.fontSize/this.vmlScale+"px";a.fontFamily=e.fontFamily;a.color=e.fontColor;a.verticalAlign="top";a.textAlign=b||"left";a.lineHeight=mxConstants.ABSOLUTE_LINE_HEIGHT?e.fontSize*mxConstants.LINE_HEIGHT/this.vmlScale+"px":mxConstants.LINE_HEIGHT;(e.fontStyle&mxConstants.FONT_BOLD)==mxConstants.FONT_BOLD&&(a.fontWeight="bold");(e.fontStyle&mxConstants.FONT_ITALIC)==mxConstants.FONT_ITALIC&&(a.fontStyle="italic");(e.fontStyle&mxConstants.FONT_UNDERLINE)= [...]
+(a.textDecoration="underline");return c};
+mxVmlCanvas2D.prototype.text=function(a,b,c,d,e,f,g,k,l,m,n,p,q){if(this.textEnabled&&null!=e){var r=this.state;if("html"==l){null!=r.rotation&&(b=this.rotatePoint(a,b,r.rotation,r.rotationCx,r.rotationCy),a=b.x,b=b.y);8!=document.documentMode||mxClient.IS_EM?(a*=r.scale,b*=r.scale):(a+=r.dx,b+=r.dy,"fill"!=m&&g==mxConstants.ALIGN_TOP&&--b);l=8!=document.documentMode||mxClient.IS_EM?this.createElement("div"):this.createVmlElement("group");l.style.position="absolute";l.style.display="inli [...]
+this.format(a)+"px";l.style.top=this.format(b)+"px";l.style.zoom=r.scale;var t=this.createElement("div");t.style.position="relative";t.style.display="inline";var u=mxUtils.getAlignmentAsPoint(f,g),x=u.x,u=u.y;e=this.createDiv(e,f,g,m);f=this.createElement("div");null!=q&&e.setAttribute("dir",q);if(k&&0<c){if(n||(e.style.width=Math.round(c)+"px"),e.style.wordWrap=mxConstants.WORD_WRAP,e.style.whiteSpace="normal","break-word"==e.style.wordWrap){var y=e;null!=y.firstChild&&"DIV"==y.firstChi [...]
+(y.firstChild.style.width="100%")}}else e.style.whiteSpace="nowrap";p=r.rotation+(p||0);this.rotateHtml&&0!=p?(f.style.display="inline",f.style.zoom="1",f.appendChild(e),8!=document.documentMode||mxClient.IS_EM||"DIV"==this.root.nodeName?l.appendChild(f):(t.appendChild(f),l.appendChild(t))):8!=document.documentMode||mxClient.IS_EM?(e.style.display="inline",l.appendChild(e)):(t.appendChild(e),l.appendChild(t));"DIV"!=this.root.nodeName?(q=this.createVmlElement("rect"),q.stroked="false",q. [...]
+q.appendChild(l),this.root.appendChild(q)):this.root.appendChild(l);n?(e.style.overflow="hidden",e.style.width=Math.round(c)+"px",mxClient.IS_QUIRKS||(e.style.maxHeight=Math.round(d)+"px")):"fill"==m?(e.style.overflow="hidden",e.style.width=Math.max(0,c)+1+"px",e.style.height=Math.max(0,d)+1+"px"):"width"==m&&(e.style.overflow="hidden",e.style.width=Math.max(0,c)+1+"px",e.style.maxHeight=Math.max(0,d)+1+"px");if(this.rotateHtml&&0!=p){y=Math.PI/180*p;p=parseFloat(parseFloat(Math.cos(y)). [...]
+q=parseFloat(parseFloat(Math.sin(-y)).toFixed(8));y%=2*Math.PI;0>y&&(y+=2*Math.PI);y%=Math.PI;y>Math.PI/2&&(y=Math.PI-y);g=Math.cos(y);var A=Math.sin(y);8!=document.documentMode||mxClient.IS_EM||(e.style.display="inline-block",f.style.display="inline-block",t.style.display="inline-block");e.style.visibility="hidden";e.style.position="absolute";document.body.appendChild(e);t=e;null!=t.firstChild&&"DIV"==t.firstChild.nodeName&&(t=t.firstChild);y=t.offsetWidth+3;t=t.offsetHeight;n?(c=Math.m [...]
+Math.min(t,d)):c=y;k&&(e.style.width=c+"px");mxClient.IS_QUIRKS&&(n||"width"==m)&&t>d&&(t=d,e.style.height=t+"px");d=t;n=(d-d*g+c*-A)/2-q*c*(x+.5)+p*d*(u+.5);k=(c-c*g+d*-A)/2+p*c*(x+.5)+q*d*(u+.5);"group"==l.nodeName&&"DIV"==this.root.nodeName?(m=this.createElement("div"),m.style.display="inline-block",m.style.position="absolute",m.style.left=this.format(a+(k-c/2)*r.scale)+"px",m.style.top=this.format(b+(n-d/2)*r.scale)+"px",l.parentNode.appendChild(m),m.appendChild(l)):(r=8!=document.do [...]
+mxClient.IS_EM?r.scale:1,l.style.left=this.format(a+(k-c/2)*r)+"px",l.style.top=this.format(b+(n-d/2)*r)+"px");f.style.filter="progid:DXImageTransform.Microsoft.Matrix(M11="+p+", M12="+q+", M21="+-q+", M22="+p+", sizingMethod='auto expand')";f.style.backgroundColor=this.rotatedHtmlBackground;1>this.state.alpha&&(f.style.filter+="alpha(opacity="+100*this.state.alpha+")");f.appendChild(e);e.style.position="";e.style.visibility=""}else 8!=document.documentMode||mxClient.IS_EM?(e.style.verti [...]
+"top",1>this.state.alpha&&(l.style.filter="alpha(opacity="+100*this.state.alpha+")"),r=e.parentNode,e.style.visibility="hidden",document.body.appendChild(e),c=e.offsetWidth,t=e.offsetHeight,mxClient.IS_QUIRKS&&n&&t>d&&(t=d,e.style.height=t+"px"),d=t,e.style.visibility="",r.appendChild(e),l.style.left=this.format(a+c*x*this.state.scale)+"px",l.style.top=this.format(b+d*u*this.state.scale)+"px"):(1>this.state.alpha&&(e.style.filter="alpha(opacity="+100*this.state.alpha+")"),t.style.left=10 [...]
+100*u+"%")}else this.plainText(a,b,c,d,mxUtils.htmlEntities(e,!1),f,g,k,l,m,n,p,q)}};
+mxVmlCanvas2D.prototype.plainText=function(a,b,c,d,e,f,g,k,l,m,n,p,q){k=this.state;a=(a+k.dx)*k.scale;b=(b+k.dy)*k.scale;c=this.createVmlElement("shape");c.style.width="1px";c.style.height="1px";c.stroked="false";d=this.createVmlElement("fill");d.color=k.fontColor;d.opacity=100*k.alpha+"%";c.appendChild(d);d=this.createVmlElement("path");d.textpathok="true";d.v="m "+this.format(0)+" "+this.format(0)+" l "+this.format(1)+" "+this.format(0);c.appendChild(d);d=this.createVmlElement("textpat [...]
+"v-text-align:"+f;d.style.align=f;d.style.fontFamily=k.fontFamily;d.string=e;d.on="true";f=k.fontSize*k.scale/this.vmlScale;d.style.fontSize=f+"px";(k.fontStyle&mxConstants.FONT_BOLD)==mxConstants.FONT_BOLD&&(d.style.fontWeight="bold");(k.fontStyle&mxConstants.FONT_ITALIC)==mxConstants.FONT_ITALIC&&(d.style.fontStyle="italic");(k.fontStyle&mxConstants.FONT_UNDERLINE)==mxConstants.FONT_UNDERLINE&&(d.style.textDecoration="underline");e=e.split("\n");k=f+(e.length-1)*f*mxConstants.LINE_HEIG [...]
+g==mxConstants.ALIGN_BOTTOM?f=-k/2:g!=mxConstants.ALIGN_MIDDLE&&(f=k/2);null!=p&&(c.style.rotation=p,g=Math.PI/180*p,e=Math.sin(g)*f,f*=Math.cos(g));c.appendChild(d);c.style.left=this.format(a-e)+"px";c.style.top=this.format(b+f)+"px";this.root.appendChild(c)};mxVmlCanvas2D.prototype.stroke=function(){this.addNode(!1,!0)};mxVmlCanvas2D.prototype.fill=function(){this.addNode(!0,!1)};mxVmlCanvas2D.prototype.fillAndStroke=function(){this.addNode(!0,!0)};
+function mxGuide(a,b){this.graph=a;this.setStates(b)}mxGuide.prototype.graph=null;mxGuide.prototype.states=null;mxGuide.prototype.horizontal=!0;mxGuide.prototype.vertical=!0;mxGuide.prototype.guideX=null;mxGuide.prototype.guideY=null;mxGuide.prototype.setStates=function(a){this.states=a};mxGuide.prototype.isEnabledForEvent=function(a){return!0};mxGuide.prototype.getGuideTolerance=function(){return this.graph.gridSize/2};
+mxGuide.prototype.createGuideShape=function(a){a=new mxPolyline([],mxConstants.GUIDE_COLOR,mxConstants.GUIDE_STROKEWIDTH);a.isDashed=!0;return a};
+mxGuide.prototype.move=function(a,b,c){if(null!=this.states&&(this.horizontal||this.vertical)&&null!=a&&null!=b){var d=function(b){b+=this.graph.panDy;var c=!1;Math.abs(b-F)<y?(l=b-a.getCenterY(),y=Math.abs(b-F),c=!0):Math.abs(b-B)<y?(l=b-a.y,y=Math.abs(b-B),c=!0):Math.abs(b-C)<y&&(l=b-a.y-a.height,y=Math.abs(b-C),c=!0);c&&(r=D,t=Math.round(b-this.graph.panDy),null==this.guideY&&(this.guideY=this.createGuideShape(!1),this.guideY.dialect=this.graph.dialect!=mxConstants.DIALECT_SVG?mxConst [...]
+mxConstants.DIALECT_SVG,this.guideY.pointerEvents=!1,this.guideY.init(this.graph.getView().getOverlayPane())));q=q||c},e=function(b,c){b+=this.graph.panDx;var d=!1;Math.abs(b-v)<x?(k=b-a.getCenterX(),x=Math.abs(b-v),d=!0):Math.abs(b-A)<x?(k=b-a.x,x=Math.abs(b-A),d=!0):Math.abs(b-z)<x&&(k=b-a.x-a.width,x=Math.abs(b-z),d=!0);d&&(n=c,p=Math.round(b-this.graph.panDx),null==this.guideX&&(this.guideX=this.createGuideShape(!0),this.guideX.dialect=this.graph.dialect!=mxConstants.DIALECT_SVG?mxCo [...]
+mxConstants.DIALECT_SVG,this.guideX.pointerEvents=!1,this.guideX.init(this.graph.getView().getOverlayPane())));m=m||d},f=this.graph.getView().translate,g=this.graph.getView().scale,k=b.x,l=b.y,m=!1,n=null,p=null,q=!1,r=null,t=null,u=this.getGuideTolerance(),x=u,y=u,u=a.clone();u.x+=b.x;u.y+=b.y;var A=u.x,z=u.x+u.width,v=u.getCenterX(),B=u.y,C=u.y+u.height,F=u.getCenterY();for(b=0;b<this.states.length;b++){var D=this.states[b];null!=D&&(this.horizontal&&(e.call(this,D.getCenterX(),D),e.ca [...]
+D),e.call(this,D.x+D.width,D)),this.vertical&&(d.call(this,D.getCenterY(),D),d.call(this,D.y,D),d.call(this,D.y+D.height,D)))}c&&(m||(c=a.x-(this.graph.snap(a.x/g-f.x)+f.x)*g,k=this.graph.snap(k/g)*g-c),q||(f=a.y-(this.graph.snap(a.y/g-f.y)+f.y)*g,l=this.graph.snap(l/g)*g-f));g=this.graph.container;m||null==this.guideX?null!=this.guideX&&(null!=n&&null!=a&&(minY=Math.min(a.y+l-this.graph.panDy,n.y),maxY=Math.max(a.y+a.height+l-this.graph.panDy,n.y+n.height)),this.guideX.points=null!=minY [...]
+[new mxPoint(p,minY),new mxPoint(p,maxY)]:[new mxPoint(p,-this.graph.panDy),new mxPoint(p,g.scrollHeight-3-this.graph.panDy)],this.guideX.stroke=this.getGuideColor(n,!0),this.guideX.node.style.visibility="visible",this.guideX.redraw()):this.guideX.node.style.visibility="hidden";q||null==this.guideY?null!=this.guideY&&(null!=r&&null!=a&&(minX=Math.min(a.x+k-this.graph.panDx,r.x),maxX=Math.max(a.x+a.width+k-this.graph.panDx,r.x+r.width)),this.guideY.points=null!=minX&&null!=maxX?[new mxPoi [...]
+new mxPoint(maxX,t)]:[new mxPoint(-this.graph.panDx,t),new mxPoint(g.scrollWidth-3-this.graph.panDx,t)],this.guideY.stroke=this.getGuideColor(r,!1),this.guideY.node.style.visibility="visible",this.guideY.redraw()):this.guideY.node.style.visibility="hidden";b=new mxPoint(k,l)}return b};mxGuide.prototype.getGuideColor=function(a,b){return mxConstants.GUIDE_COLOR};mxGuide.prototype.hide=function(){this.setVisible(!1)};
+mxGuide.prototype.setVisible=function(a){null!=this.guideX&&(this.guideX.node.style.visibility=a?"visible":"hidden");null!=this.guideY&&(this.guideY.node.style.visibility=a?"visible":"hidden")};mxGuide.prototype.destroy=function(){null!=this.guideX&&(this.guideX.destroy(),this.guideX=null);null!=this.guideY&&(this.guideY.destroy(),this.guideY=null)};function mxStencil(a){this.desc=a;this.parseDescription();this.parseConstraints()}mxStencil.defaultLocalized=!1;mxStencil.allowEval=!1;
+mxStencil.prototype.desc=null;mxStencil.prototype.constraints=null;mxStencil.prototype.aspect=null;mxStencil.prototype.w0=null;mxStencil.prototype.h0=null;mxStencil.prototype.bgNode=null;mxStencil.prototype.fgNode=null;mxStencil.prototype.strokewidth=null;
+mxStencil.prototype.parseDescription=function(){this.fgNode=this.desc.getElementsByTagName("foreground")[0];this.bgNode=this.desc.getElementsByTagName("background")[0];this.w0=Number(this.desc.getAttribute("w")||100);this.h0=Number(this.desc.getAttribute("h")||100);var a=this.desc.getAttribute("aspect");this.aspect=null!=a?a:"variable";a=this.desc.getAttribute("strokewidth");this.strokewidth=null!=a?a:"1"};
+mxStencil.prototype.parseConstraints=function(){var a=this.desc.getElementsByTagName("connections")[0];if(null!=a&&(a=mxUtils.getChildNodes(a),null!=a&&0<a.length)){this.constraints=[];for(var b=0;b<a.length;b++)this.constraints.push(this.parseConstraint(a[b]))}};mxStencil.prototype.parseConstraint=function(a){var b=Number(a.getAttribute("x")),c=Number(a.getAttribute("y")),d="1"==a.getAttribute("perimeter");a=a.getAttribute("name");return new mxConnectionConstraint(new mxPoint(b,c),d,a)};
+mxStencil.prototype.evaluateTextAttribute=function(a,b,c){b=this.evaluateAttribute(a,b,c);a=a.getAttribute("localized");if(mxStencil.defaultLocalized&&null==a||"1"==a)b=mxResources.get(b);return b};mxStencil.prototype.evaluateAttribute=function(a,b,c){b=a.getAttribute(b);null==b&&(a=mxUtils.getTextContent(a),null!=a&&mxStencil.allowEval&&(a=mxUtils.eval(a),"function"==typeof a&&(b=a(c))));return b};
+mxStencil.prototype.drawShape=function(a,b,c,d,e,f){var g=mxUtils.getValue(b.style,mxConstants.STYLE_DIRECTION,null),g=this.computeAspect(b.style,c,d,e,f,g),k=Math.min(g.width,g.height),k="inherit"==this.strokewidth?Number(mxUtils.getNumber(b.style,mxConstants.STYLE_STROKEWIDTH,1)):Number(this.strokewidth)*k;a.setStrokeWidth(k);this.drawChildren(a,b,c,d,e,f,this.bgNode,g,!1);this.drawChildren(a,b,c,d,e,f,this.fgNode,g,!0)};
+mxStencil.prototype.drawChildren=function(a,b,c,d,e,f,g,k,l){if(null!=g&&0<e&&0<f)for(c=g.firstChild;null!=c;)c.nodeType==mxConstants.NODETYPE_ELEMENT&&this.drawNode(a,b,c,k,l),c=c.nextSibling};
+mxStencil.prototype.computeAspect=function(a,b,c,d,e,f){a=b;b=d/this.w0;var g=e/this.h0;if(f=f==mxConstants.DIRECTION_NORTH||f==mxConstants.DIRECTION_SOUTH){g=d/this.h0;b=e/this.w0;var k=(d-e)/2;a+=k;c-=k}"fixed"==this.aspect&&(b=g=Math.min(b,g),f?(a+=(e-this.w0*b)/2,c+=(d-this.h0*g)/2):(a+=(d-this.w0*b)/2,c+=(e-this.h0*g)/2));return new mxRectangle(a,c,b,g)};
+mxStencil.prototype.drawNode=function(a,b,c,d,e){var f=c.nodeName,g=d.x,k=d.y,l=d.width,m=d.height,n=Math.min(l,m);if("save"==f)a.save();else if("restore"==f)a.restore();else if("path"==f)for(a.begin(),c=c.firstChild;null!=c;)c.nodeType==mxConstants.NODETYPE_ELEMENT&&this.drawNode(a,b,c,d,e),c=c.nextSibling;else if("close"==f)a.close();else if("move"==f)a.moveTo(g+Number(c.getAttribute("x"))*l,k+Number(c.getAttribute("y"))*m);else if("line"==f)a.lineTo(g+Number(c.getAttribute("x"))*l,k+N [...]
+m);else if("quad"==f)a.quadTo(g+Number(c.getAttribute("x1"))*l,k+Number(c.getAttribute("y1"))*m,g+Number(c.getAttribute("x2"))*l,k+Number(c.getAttribute("y2"))*m);else if("curve"==f)a.curveTo(g+Number(c.getAttribute("x1"))*l,k+Number(c.getAttribute("y1"))*m,g+Number(c.getAttribute("x2"))*l,k+Number(c.getAttribute("y2"))*m,g+Number(c.getAttribute("x3"))*l,k+Number(c.getAttribute("y3"))*m);else if("arc"==f)a.arcTo(Number(c.getAttribute("rx"))*l,Number(c.getAttribute("ry"))*m,Number(c.getAt [...]
+Number(c.getAttribute("large-arc-flag")),Number(c.getAttribute("sweep-flag")),g+Number(c.getAttribute("x"))*l,k+Number(c.getAttribute("y"))*m);else if("rect"==f)a.rect(g+Number(c.getAttribute("x"))*l,k+Number(c.getAttribute("y"))*m,Number(c.getAttribute("w"))*l,Number(c.getAttribute("h"))*m);else if("roundrect"==f)b=Number(c.getAttribute("arcsize")),0==b&&(b=100*mxConstants.RECTANGLE_ROUNDING_FACTOR),n=Number(c.getAttribute("w"))*l,d=Number(c.getAttribute("h"))*m,b=Number(b)/100,b=Math.m [...]
+b),a.roundrect(g+Number(c.getAttribute("x"))*l,k+Number(c.getAttribute("y"))*m,n,d,b,b);else if("ellipse"==f)a.ellipse(g+Number(c.getAttribute("x"))*l,k+Number(c.getAttribute("y"))*m,Number(c.getAttribute("w"))*l,Number(c.getAttribute("h"))*m);else if("image"==f)b.outline||(b=this.evaluateAttribute(c,"src",b),a.image(g+Number(c.getAttribute("x"))*l,k+Number(c.getAttribute("y"))*m,Number(c.getAttribute("w"))*l,Number(c.getAttribute("h"))*m,b,!1,"1"==c.getAttribute("flipH"),"1"==c.getAttri [...]
+else if("text"==f){if(!b.outline){n=this.evaluateTextAttribute(c,"str",b);d="1"==c.getAttribute("vertical")?-90:0;if("0"==c.getAttribute("align-shape")){var p=b.rotation,q=1==mxUtils.getValue(b.style,mxConstants.STYLE_FLIPH,0);b=1==mxUtils.getValue(b.style,mxConstants.STYLE_FLIPV,0);d=q&&b?d-p:q||b?d+p:d-p}d-=c.getAttribute("rotation");a.text(g+Number(c.getAttribute("x"))*l,k+Number(c.getAttribute("y"))*m,0,0,n,c.getAttribute("align")||"left",c.getAttribute("valign")||"top",!1,"",null,!1 [...]
+f)p=mxStencilRegistry.getStencil(c.getAttribute("name")),null!=p&&(g+=Number(c.getAttribute("x"))*l,k+=Number(c.getAttribute("y"))*m,n=Number(c.getAttribute("w"))*l,d=Number(c.getAttribute("h"))*m,p.drawShape(a,b,g,k,n,d));else if("fillstroke"==f)a.fillAndStroke();else if("fill"==f)a.fill();else if("stroke"==f)a.stroke();else if("strokewidth"==f)l="1"==c.getAttribute("fixed")?1:n,a.setStrokeWidth(Number(c.getAttribute("width"))*l);else if("dashed"==f)a.setDashed("1"==c.getAttribute("dash [...]
+f){if(c=c.getAttribute("pattern"),null!=c){c=c.split(" ");l=[];for(m=0;m<c.length;m++)0<c[m].length&&l.push(Number(c[m])*n);c=l.join(" ");a.setDashPattern(c)}}else"strokecolor"==f?a.setStrokeColor(c.getAttribute("color")):"linecap"==f?a.setLineCap(c.getAttribute("cap")):"linejoin"==f?a.setLineJoin(c.getAttribute("join")):"miterlimit"==f?a.setMiterLimit(Number(c.getAttribute("limit"))):"fillcolor"==f?a.setFillColor(c.getAttribute("color")):"alpha"==f?a.setAlpha(c.getAttribute("alpha")):"f [...]
+f?a.setFontColor(c.getAttribute("color")):"fontstyle"==f?a.setFontStyle(c.getAttribute("style")):"fontfamily"==f?a.setFontFamily(c.getAttribute("family")):"fontsize"==f&&a.setFontSize(Number(c.getAttribute("size"))*n);!e||"fillstroke"!=f&&"fill"!=f&&"stroke"!=f||a.setShadow(!1)};function mxShape(a){this.stencil=a;this.initStyles()}mxShape.prototype.dialect=null;mxShape.prototype.scale=1;mxShape.prototype.antiAlias=!0;mxShape.prototype.bounds=null;mxShape.prototype.points=null;
+mxShape.prototype.node=null;mxShape.prototype.state=null;mxShape.prototype.style=null;mxShape.prototype.boundingBox=null;mxShape.prototype.stencil=null;mxShape.prototype.svgStrokeTolerance=8;mxShape.prototype.pointerEvents=!0;mxShape.prototype.svgPointerEvents="all";mxShape.prototype.shapePointerEvents=!1;mxShape.prototype.stencilPointerEvents=!1;mxShape.prototype.vmlScale=1;mxShape.prototype.outline=!1;mxShape.prototype.visible=!0;mxShape.prototype.useSvgBoundingBox=!1;
+mxShape.prototype.init=function(a){null==this.node&&(this.node=this.create(a),null!=a&&a.appendChild(this.node))};mxShape.prototype.initStyles=function(a){this.strokewidth=1;this.rotation=0;this.strokeOpacity=this.fillOpacity=this.opacity=100;this.flipV=this.flipH=!1};mxShape.prototype.isParseVml=function(){return!0};mxShape.prototype.isHtmlAllowed=function(){return!1};
+mxShape.prototype.getSvgScreenOffset=function(){return 1==mxUtils.mod(Math.max(1,Math.round((this.stencil&&"inherit"!=this.stencil.strokewidth?Number(this.stencil.strokewidth):this.strokewidth)*this.scale)),2)?.5:0};mxShape.prototype.create=function(a){return null!=a&&null!=a.ownerSVGElement?this.createSvg(a):8==document.documentMode||!mxClient.IS_VML||this.dialect!=mxConstants.DIALECT_VML&&this.isHtmlAllowed()?this.createHtml(a):this.createVml(a)};
+mxShape.prototype.createSvg=function(){return document.createElementNS(mxConstants.NS_SVG,"g")};mxShape.prototype.createVml=function(){var a=document.createElement(mxClient.VML_PREFIX+":group");a.style.position="absolute";return a};mxShape.prototype.createHtml=function(){var a=document.createElement("div");a.style.position="absolute";return a};mxShape.prototype.reconfigure=function(){this.redraw()};
+mxShape.prototype.redraw=function(){this.updateBoundsFromPoints();this.visible&&this.checkBounds()?(this.node.style.visibility="visible",this.clear(),"DIV"!=this.node.nodeName||!this.isHtmlAllowed()&&mxClient.IS_VML?this.redrawShape():this.redrawHtmlShape(),this.updateBoundingBox()):(this.node.style.visibility="hidden",this.boundingBox=null)};
+mxShape.prototype.clear=function(){if(null!=this.node.ownerSVGElement)for(;null!=this.node.lastChild;)this.node.removeChild(this.node.lastChild);else this.node.style.cssText="position:absolute;"+(null!=this.cursor?"cursor:"+this.cursor+";":""),this.node.innerHTML=""};
+mxShape.prototype.updateBoundsFromPoints=function(){var a=this.points;if(null!=a&&0<a.length&&null!=a[0]){this.bounds=new mxRectangle(Number(a[0].x),Number(a[0].y),1,1);for(var b=1;b<this.points.length;b++)null!=a[b]&&this.bounds.add(new mxRectangle(Number(a[b].x),Number(a[b].y),1,1))}};
+mxShape.prototype.getLabelBounds=function(a){var b=mxUtils.getValue(this.style,mxConstants.STYLE_DIRECTION,mxConstants.DIRECTION_EAST),c=a;b!=mxConstants.DIRECTION_SOUTH&&b!=mxConstants.DIRECTION_NORTH&&null!=this.state&&null!=this.state.text&&this.state.text.isPaintBoundsInverted()&&(c=c.clone(),b=c.width,c.width=c.height,c.height=b);c=this.getLabelMargins(c);if(null!=c){var d="1"==mxUtils.getValue(this.style,mxConstants.STYLE_FLIPH,!1),e="1"==mxUtils.getValue(this.style,mxConstants.STY [...]
+!1);null!=this.state&&null!=this.state.text&&this.state.text.isPaintBoundsInverted()&&(b=c.x,c.x=c.height,c.height=c.width,c.width=c.y,c.y=b,b=d,d=e,e=b);return mxUtils.getDirectedBounds(a,c,this.style,d,e)}return a};mxShape.prototype.getLabelMargins=function(a){return null};
+mxShape.prototype.checkBounds=function(){return!isNaN(this.scale)&&isFinite(this.scale)&&0<this.scale&&null!=this.bounds&&!isNaN(this.bounds.x)&&!isNaN(this.bounds.y)&&!isNaN(this.bounds.width)&&!isNaN(this.bounds.height)&&0<this.bounds.width&&0<this.bounds.height};mxShape.prototype.createVmlGroup=function(){var a=document.createElement(mxClient.VML_PREFIX+":group");a.style.position="absolute";a.style.width=this.node.style.width;a.style.height=this.node.style.height;return a};
+mxShape.prototype.redrawShape=function(){var a=this.createCanvas();null!=a&&(a.pointerEvents=this.pointerEvents,this.paint(a),this.node!=a.root&&this.node.insertAdjacentHTML("beforeend",a.root.outerHTML),"DIV"==this.node.nodeName&&8==document.documentMode&&(this.node.style.filter="",mxUtils.addTransparentBackgroundFilter(this.node)),this.destroyCanvas(a))};
+mxShape.prototype.createCanvas=function(){var a=null;null!=this.node.ownerSVGElement?a=this.createSvgCanvas():mxClient.IS_VML&&(this.updateVmlContainer(),a=this.createVmlCanvas());null!=a&&this.outline&&(a.setStrokeWidth(this.strokewidth),a.setStrokeColor(this.stroke),null!=this.isDashed&&a.setDashed(this.isDashed),a.setStrokeWidth=function(){},a.setStrokeColor=function(){},a.setFillColor=function(){},a.setGradient=function(){},a.setDashed=function(){});return a};
+mxShape.prototype.createSvgCanvas=function(){var a=new mxSvgCanvas2D(this.node,!1);a.strokeTolerance=this.pointerEvents?this.svgStrokeTolerance:0;a.pointerEventsValue=this.svgPointerEvents;a.blockImagePointerEvents=mxClient.IS_FF;var b=this.getSvgScreenOffset();0!=b?this.node.setAttribute("transform","translate("+b+","+b+")"):this.node.removeAttribute("transform");this.antiAlias||(a.format=function(a){return Math.round(parseFloat(a))});return a};
+mxShape.prototype.createVmlCanvas=function(){var a=8==document.documentMode&&this.isParseVml()?this.createVmlGroup():this.node,b=new mxVmlCanvas2D(a,!1);""!=a.tagUrn&&(a.coordsize=Math.max(1,Math.round(this.bounds.width))*this.vmlScale+","+Math.max(1,Math.round(this.bounds.height))*this.vmlScale,b.scale(this.vmlScale),b.vmlScale=this.vmlScale);a=this.scale;b.translate(-Math.round(this.bounds.x/a),-Math.round(this.bounds.y/a));return b};
+mxShape.prototype.updateVmlContainer=function(){this.node.style.left=Math.round(this.bounds.x)+"px";this.node.style.top=Math.round(this.bounds.y)+"px";var a=Math.max(1,Math.round(this.bounds.height));this.node.style.width=Math.max(1,Math.round(this.bounds.width))+"px";this.node.style.height=a+"px";this.node.style.overflow="visible"};mxShape.prototype.redrawHtmlShape=function(){this.updateHtmlBounds(this.node);this.updateHtmlFilters(this.node);this.updateHtmlColors(this.node)};
+mxShape.prototype.updateHtmlFilters=function(a){var b="";100>this.opacity&&(b+="alpha(opacity="+this.opacity+")");this.isShadow&&(b+="progid:DXImageTransform.Microsoft.dropShadow (OffX='"+Math.round(mxConstants.SHADOW_OFFSET_X*this.scale)+"', OffY='"+Math.round(mxConstants.SHADOW_OFFSET_Y*this.scale)+"', Color='"+mxConstants.VML_SHADOWCOLOR+"')");if(null!=this.fill&&this.fill!=mxConstants.NONE&&this.gradient&&this.gradient!=mxConstants.NONE){var c=this.fill,d=this.gradient,e="0",f={east: [...]
+west:2,north:3},g=null!=this.direction?f[this.direction]:0;null!=this.gradientDirection&&(g=mxUtils.mod(g+f[this.gradientDirection]-1,4));1==g?(e="1",f=c,c=d,d=f):2==g?(f=c,c=d,d=f):3==g&&(e="1");b+="progid:DXImageTransform.Microsoft.gradient(startColorStr='"+c+"', endColorStr='"+d+"', gradientType='"+e+"')"}a.style.filter=b};
+mxShape.prototype.updateHtmlColors=function(a){var b=this.stroke;null!=b&&b!=mxConstants.NONE?(a.style.borderColor=b,this.isDashed?a.style.borderStyle="dashed":0<this.strokewidth&&(a.style.borderStyle="solid"),a.style.borderWidth=Math.max(1,Math.ceil(this.strokewidth*this.scale))+"px"):a.style.borderWidth="0px";b=this.outline?null:this.fill;null!=b&&b!=mxConstants.NONE?(a.style.backgroundColor=b,a.style.backgroundImage="none"):this.pointerEvents?a.style.backgroundColor="transparent":8==d [...]
+mxUtils.addTransparentBackgroundFilter(a):this.setTransparentBackgroundImage(a)};
+mxShape.prototype.updateHtmlBounds=function(a){var b=9<=document.documentMode?0:Math.ceil(this.strokewidth*this.scale);a.style.borderWidth=Math.max(1,b)+"px";a.style.overflow="hidden";a.style.left=Math.round(this.bounds.x-b/2)+"px";a.style.top=Math.round(this.bounds.y-b/2)+"px";"CSS1Compat"==document.compatMode&&(b=-b);a.style.width=Math.round(Math.max(0,this.bounds.width+b))+"px";a.style.height=Math.round(Math.max(0,this.bounds.height+b))+"px"};
+mxShape.prototype.destroyCanvas=function(a){if(a instanceof mxSvgCanvas2D){for(var b in a.gradients){var c=a.gradients[b];null!=c&&(c.mxRefCount=(c.mxRefCount||0)+1)}this.releaseSvgGradients(this.oldGradients);this.oldGradients=a.gradients}};
+mxShape.prototype.paint=function(a){var b=this.scale,c=this.bounds.x/b,d=this.bounds.y/b,e=this.bounds.width/b,f=this.bounds.height/b;if(this.isPaintBoundsInverted())var g=(e-f)/2,c=c+g,d=d-g,g=e,e=f,f=g;this.updateTransform(a,c,d,e,f);this.configureCanvas(a,c,d,e,f);g=null;if(null==this.stencil&&null==this.points&&this.shapePointerEvents||null!=this.stencil&&this.stencilPointerEvents){var k=this.createBoundingBox();this.dialect==mxConstants.DIALECT_SVG?(g=this.createTransparentSvgRectan [...]
+k.width,k.height),this.node.appendChild(g)):(k=a.createRect("rect",k.x/b,k.y/b,k.width/b,k.height/b),k.appendChild(a.createTransparentFill()),k.stroked="false",a.root.appendChild(k))}if(null!=this.stencil)this.stencil.drawShape(a,this,c,d,e,f);else if(a.setStrokeWidth(this.strokewidth),null!=this.points){c=[];for(d=0;d<this.points.length;d++)null!=this.points[d]&&c.push(new mxPoint(this.points[d].x/b,this.points[d].y/b));this.paintEdgeShape(a,c)}else this.paintVertexShape(a,c,d,e,f);null [...]
+a.state&&null!=a.state.transform&&g.setAttribute("transform",a.state.transform)};
+mxShape.prototype.configureCanvas=function(a,b,c,d,e){var f=null;null!=this.style&&(f=this.style.dashPattern);a.setAlpha(this.opacity/100);a.setFillAlpha(this.fillOpacity/100);a.setStrokeAlpha(this.strokeOpacity/100);null!=this.isShadow&&a.setShadow(this.isShadow);null!=this.isDashed&&a.setDashed(this.isDashed,null!=this.style?1==mxUtils.getValue(this.style,mxConstants.STYLE_FIX_DASH,!1):!1);null!=f&&a.setDashPattern(f);null!=this.fill&&this.fill!=mxConstants.NONE&&this.gradient&&this.gr [...]
+(b=this.getGradientBounds(a,b,c,d,e),a.setGradient(this.fill,this.gradient,b.x,b.y,b.width,b.height,this.gradientDirection)):a.setFillColor(this.fill);a.setStrokeColor(this.stroke)};mxShape.prototype.getGradientBounds=function(a,b,c,d,e){return new mxRectangle(b,c,d,e)};mxShape.prototype.updateTransform=function(a,b,c,d,e){a.scale(this.scale);a.rotate(this.getShapeRotation(),this.flipH,this.flipV,b+d/2,c+e/2)};
+mxShape.prototype.paintVertexShape=function(a,b,c,d,e){this.paintBackground(a,b,c,d,e);a.setShadow(!1);this.paintForeground(a,b,c,d,e)};mxShape.prototype.paintBackground=function(a,b,c,d,e){};mxShape.prototype.paintForeground=function(a,b,c,d,e){};mxShape.prototype.paintEdgeShape=function(a,b){};
+mxShape.prototype.getArcSize=function(a,b){var c;"1"==mxUtils.getValue(this.style,mxConstants.STYLE_ABSOLUTE_ARCSIZE,0)?c=Math.min(a/2,Math.min(b/2,mxUtils.getValue(this.style,mxConstants.STYLE_ARCSIZE,mxConstants.LINE_ARCSIZE)/2)):(c=mxUtils.getValue(this.style,mxConstants.STYLE_ARCSIZE,100*mxConstants.RECTANGLE_ROUNDING_FACTOR)/100,c=Math.min(a*c,b*c));return c};
+mxShape.prototype.paintGlassEffect=function(a,b,c,d,e,f){var g=Math.ceil(this.strokewidth/2);a.setGradient("#ffffff","#ffffff",b,c,d,.6*e,"south",.9,.1);a.begin();f+=2*g;this.isRounded?(a.moveTo(b-g+f,c-g),a.quadTo(b-g,c-g,b-g,c-g+f),a.lineTo(b-g,c+.4*e),a.quadTo(b+.5*d,c+.7*e,b+d+g,c+.4*e),a.lineTo(b+d+g,c-g+f),a.quadTo(b+d+g,c-g,b+d+g-f,c-g)):(a.moveTo(b-g,c-g),a.lineTo(b-g,c+.4*e),a.quadTo(b+.5*d,c+.7*e,b+d+g,c+.4*e),a.lineTo(b+d+g,c-g));a.close();a.fill()};
+mxShape.prototype.addPoints=function(a,b,c,d,e,f,g){if(null!=b&&0<b.length){g=null!=g?g:!0;var k=b[b.length-1];if(e&&c){b=b.slice();var l=b[0],l=new mxPoint(k.x+(l.x-k.x)/2,k.y+(l.y-k.y)/2);b.splice(0,0,l)}var m=b[0],l=1;for(g?a.moveTo(m.x,m.y):a.lineTo(m.x,m.y);l<(e?b.length:b.length-1);){g=b[mxUtils.mod(l,b.length)];var n=m.x-g.x,m=m.y-g.y;if(c&&(0!=n||0!=m)&&(null==f||0>mxUtils.indexOf(f,l-1))){var p=Math.sqrt(n*n+m*m);a.lineTo(g.x+n*Math.min(d,p/2)/p,g.y+m*Math.min(d,p/2)/p);for(m=b[ [...]
+1,b.length)];l<b.length-2&&0==Math.round(m.x-g.x)&&0==Math.round(m.y-g.y);)m=b[mxUtils.mod(l+2,b.length)],l++;n=m.x-g.x;m=m.y-g.y;p=Math.max(1,Math.sqrt(n*n+m*m));n=g.x+n*Math.min(d,p/2)/p;m=g.y+m*Math.min(d,p/2)/p;a.quadTo(g.x,g.y,n,m);g=new mxPoint(n,m)}else a.lineTo(g.x,g.y);m=g;l++}e?a.close():a.lineTo(k.x,k.y)}};
+mxShape.prototype.resetStyles=function(){this.initStyles();this.spacing=0;delete this.fill;delete this.gradient;delete this.gradientDirection;delete this.stroke;delete this.startSize;delete this.endSize;delete this.startArrow;delete this.endArrow;delete this.direction;delete this.isShadow;delete this.isDashed;delete this.isRounded;delete this.glass};
+mxShape.prototype.apply=function(a){this.state=a;this.style=a.style;if(null!=this.style){this.fill=mxUtils.getValue(this.style,mxConstants.STYLE_FILLCOLOR,this.fill);this.gradient=mxUtils.getValue(this.style,mxConstants.STYLE_GRADIENTCOLOR,this.gradient);this.gradientDirection=mxUtils.getValue(this.style,mxConstants.STYLE_GRADIENT_DIRECTION,this.gradientDirection);this.opacity=mxUtils.getValue(this.style,mxConstants.STYLE_OPACITY,this.opacity);this.fillOpacity=mxUtils.getValue(this.style [...]
+this.fillOpacity);this.strokeOpacity=mxUtils.getValue(this.style,mxConstants.STYLE_STROKE_OPACITY,this.strokeOpacity);this.stroke=mxUtils.getValue(this.style,mxConstants.STYLE_STROKECOLOR,this.stroke);this.strokewidth=mxUtils.getNumber(this.style,mxConstants.STYLE_STROKEWIDTH,this.strokewidth);this.spacing=mxUtils.getValue(this.style,mxConstants.STYLE_SPACING,this.spacing);this.startSize=mxUtils.getNumber(this.style,mxConstants.STYLE_STARTSIZE,this.startSize);this.endSize=mxUtils.getNumb [...]
+mxConstants.STYLE_ENDSIZE,this.endSize);this.startArrow=mxUtils.getValue(this.style,mxConstants.STYLE_STARTARROW,this.startArrow);this.endArrow=mxUtils.getValue(this.style,mxConstants.STYLE_ENDARROW,this.endArrow);this.rotation=mxUtils.getValue(this.style,mxConstants.STYLE_ROTATION,this.rotation);this.direction=mxUtils.getValue(this.style,mxConstants.STYLE_DIRECTION,this.direction);this.flipH=1==mxUtils.getValue(this.style,mxConstants.STYLE_FLIPH,0);this.flipV=1==mxUtils.getValue(this.st [...]
+0);null!=this.stencil&&(this.flipH=1==mxUtils.getValue(this.style,"stencilFlipH",0)||this.flipH,this.flipV=1==mxUtils.getValue(this.style,"stencilFlipV",0)||this.flipV);if(this.direction==mxConstants.DIRECTION_NORTH||this.direction==mxConstants.DIRECTION_SOUTH)a=this.flipH,this.flipH=this.flipV,this.flipV=a;this.isShadow=1==mxUtils.getValue(this.style,mxConstants.STYLE_SHADOW,this.isShadow);this.isDashed=1==mxUtils.getValue(this.style,mxConstants.STYLE_DASHED,this.isDashed);this.isRounde [...]
+mxConstants.STYLE_ROUNDED,this.isRounded);this.glass=1==mxUtils.getValue(this.style,mxConstants.STYLE_GLASS,this.glass);this.fill==mxConstants.NONE&&(this.fill=null);this.gradient==mxConstants.NONE&&(this.gradient=null);this.stroke==mxConstants.NONE&&(this.stroke=null)}};mxShape.prototype.setCursor=function(a){null==a&&(a="");this.cursor=a;null!=this.node&&(this.node.style.cursor=a)};mxShape.prototype.getCursor=function(){return this.cursor};
+mxShape.prototype.updateBoundingBox=function(){if(this.useSvgBoundingBox&&null!=this.node&&null!=this.node.ownerSVGElement)try{var a=this.node.getBBox();if(0<a.width&&0<a.height){this.boundingBox=new mxRectangle(a.x,a.y,a.width,a.height);this.boundingBox.grow(this.strokewidth*this.scale/2);return}}catch(c){}if(null!=this.bounds){a=this.createBoundingBox();if(null!=a){this.augmentBoundingBox(a);var b=this.getShapeRotation();0!=b&&(a=mxUtils.getBoundingBox(a,b))}this.boundingBox=a}};
+mxShape.prototype.createBoundingBox=function(){var a=this.bounds.clone();(null!=this.stencil&&(this.direction==mxConstants.DIRECTION_NORTH||this.direction==mxConstants.DIRECTION_SOUTH)||this.isPaintBoundsInverted())&&a.rotate90();return a};mxShape.prototype.augmentBoundingBox=function(a){this.isShadow&&(a.width+=Math.ceil(mxConstants.SHADOW_OFFSET_X*this.scale),a.height+=Math.ceil(mxConstants.SHADOW_OFFSET_Y*this.scale));a.grow(this.strokewidth*this.scale/2)};
+mxShape.prototype.isPaintBoundsInverted=function(){return null==this.stencil&&(this.direction==mxConstants.DIRECTION_NORTH||this.direction==mxConstants.DIRECTION_SOUTH)};mxShape.prototype.getRotation=function(){return null!=this.rotation?this.rotation:0};mxShape.prototype.getTextRotation=function(){var a=this.getRotation();1!=mxUtils.getValue(this.style,mxConstants.STYLE_HORIZONTAL,1)&&(a+=mxText.prototype.verticalTextRotation);return a};
+mxShape.prototype.getShapeRotation=function(){var a=this.getRotation();null!=this.direction&&(this.direction==mxConstants.DIRECTION_NORTH?a+=270:this.direction==mxConstants.DIRECTION_WEST?a+=180:this.direction==mxConstants.DIRECTION_SOUTH&&(a+=90));return a};
+mxShape.prototype.createTransparentSvgRectangle=function(a,b,c,d){var e=document.createElementNS(mxConstants.NS_SVG,"rect");e.setAttribute("x",a);e.setAttribute("y",b);e.setAttribute("width",c);e.setAttribute("height",d);e.setAttribute("fill","none");e.setAttribute("stroke","none");e.setAttribute("pointer-events","all");return e};mxShape.prototype.setTransparentBackgroundImage=function(a){a.style.backgroundImage="url('"+mxClient.imageBasePath+"/transparent.gif')"};
+mxShape.prototype.releaseSvgGradients=function(a){if(null!=a)for(var b in a){var c=a[b];null!=c&&(c.mxRefCount=(c.mxRefCount||0)-1,0==c.mxRefCount&&null!=c.parentNode&&c.parentNode.removeChild(c))}};mxShape.prototype.destroy=function(){null!=this.node&&(mxEvent.release(this.node),null!=this.node.parentNode&&this.node.parentNode.removeChild(this.node),this.node=null);this.releaseSvgGradients(this.oldGradients);this.oldGradients=null};
+var mxStencilRegistry={stencils:{},addStencil:function(a,b){mxStencilRegistry.stencils[a]=b},getStencil:function(a){return mxStencilRegistry.stencils[a]}},mxMarker={markers:[],addMarker:function(a,b){mxMarker.markers[a]=b},createMarker:function(a,b,c,d,e,f,g,k,l,m){var n=mxMarker.markers[c];return null!=n?n(a,b,c,d,e,f,g,k,l,m):null}};
+(function(){function a(a){a=null!=a?a:2;return function(b,c,d,k,l,m,n,p,q,r){c=l*q*1.118;p=m*q*1.118;l*=n+q;m*=n+q;var e=k.clone();e.x-=c;e.y-=p;n=d!=mxConstants.ARROW_CLASSIC&&d!=mxConstants.ARROW_CLASSIC_THIN?1:.75;k.x+=-l*n-c;k.y+=-m*n-p;return function(){b.begin();b.moveTo(e.x,e.y);b.lineTo(e.x-l-m/a,e.y-m+l/a);d!=mxConstants.ARROW_CLASSIC&&d!=mxConstants.ARROW_CLASSIC_THIN||b.lineTo(e.x-3*l/4,e.y-3*m/4);b.lineTo(e.x+m/a-l,e.y-m-l/a);b.close();r?b.fillAndStroke():b.stroke()}}}functio [...]
+null!=a?a:2;return function(b,c,d,k,l,m,n,p,q,r){c=l*q*1.118;d=m*q*1.118;l*=n+q;m*=n+q;var e=k.clone();e.x-=c;e.y-=d;k.x+=2*-c;k.y+=2*-d;return function(){b.begin();b.moveTo(e.x-l-m/a,e.y-m+l/a);b.lineTo(e.x,e.y);b.lineTo(e.x+m/a-l,e.y-m-l/a);b.stroke()}}}function c(a,b,c,g,k,l,m,n,p,q){n=c==mxConstants.ARROW_DIAMOND?.7071:.9862;b=k*p*n;n*=l*p;k*=m+p;l*=m+p;var d=g.clone();d.x-=b;d.y-=n;g.x+=-k-b;g.y+=-l-n;var e=c==mxConstants.ARROW_DIAMOND?2:3.4;return function(){a.begin();a.moveTo(d.x, [...]
+k/2-l/e,d.y+k/e-l/2);a.lineTo(d.x-k,d.y-l);a.lineTo(d.x-k/2+l/e,d.y-l/2-k/e);a.close();q?a.fillAndStroke():a.stroke()}}mxMarker.addMarker("classic",a(2));mxMarker.addMarker("classicThin",a(3));mxMarker.addMarker("block",a(2));mxMarker.addMarker("blockThin",a(3));mxMarker.addMarker("open",b(2));mxMarker.addMarker("openThin",b(3));mxMarker.addMarker("oval",function(a,b,c,g,k,l,m,n,p,q){var d=m/2,e=g.clone();g.x-=k*d;g.y-=l*d;return function(){a.ellipse(e.x-d,e.y-d,m,m);q?a.fillAndStroke(): [...]
+mxMarker.addMarker("diamond",c);mxMarker.addMarker("diamondThin",c)})();function mxActor(a,b,c,d){mxShape.call(this);this.bounds=a;this.fill=b;this.stroke=c;this.strokewidth=null!=d?d:1}mxUtils.extend(mxActor,mxShape);mxActor.prototype.paintVertexShape=function(a,b,c,d,e){a.translate(b,c);a.begin();this.redrawPath(a,b,c,d,e);a.fillAndStroke()};
+mxActor.prototype.redrawPath=function(a,b,c,d,e){b=d/3;a.moveTo(0,e);a.curveTo(0,3*e/5,0,2*e/5,d/2,2*e/5);a.curveTo(d/2-b,2*e/5,d/2-b,0,d/2,0);a.curveTo(d/2+b,0,d/2+b,2*e/5,d/2,2*e/5);a.curveTo(d,2*e/5,d,3*e/5,d,e);a.close()};function mxCloud(a,b,c,d){mxActor.call(this);this.bounds=a;this.fill=b;this.stroke=c;this.strokewidth=null!=d?d:1}mxUtils.extend(mxCloud,mxActor);
+mxCloud.prototype.redrawPath=function(a,b,c,d,e){a.moveTo(.25*d,.25*e);a.curveTo(.05*d,.25*e,0,.5*e,.16*d,.55*e);a.curveTo(0,.66*e,.18*d,.9*e,.31*d,.8*e);a.curveTo(.4*d,e,.7*d,e,.8*d,.8*e);a.curveTo(d,.8*e,d,.6*e,.875*d,.5*e);a.curveTo(d,.3*e,.8*d,.1*e,.625*d,.2*e);a.curveTo(.5*d,.05*e,.3*d,.05*e,.25*d,.25*e);a.close()};function mxRectangleShape(a,b,c,d){mxShape.call(this);this.bounds=a;this.fill=b;this.stroke=c;this.strokewidth=null!=d?d:1}mxUtils.extend(mxRectangleShape,mxShape);
+mxRectangleShape.prototype.isHtmlAllowed=function(){var a=!0;null!=this.style&&(a="1"==mxUtils.getValue(this.style,mxConstants.STYLE_POINTER_EVENTS,"1"));return!this.isRounded&&!this.glass&&0==this.rotation&&(a||null!=this.fill&&this.fill!=mxConstants.NONE)};
+mxRectangleShape.prototype.paintBackground=function(a,b,c,d,e){var f=!0;null!=this.style&&(f="1"==mxUtils.getValue(this.style,mxConstants.STYLE_POINTER_EVENTS,"1"));if(f||null!=this.fill&&this.fill!=mxConstants.NONE||null!=this.stroke&&this.stroke!=mxConstants.NONE)f||null!=this.fill&&this.fill!=mxConstants.NONE||(a.pointerEvents=!1),this.isRounded?("1"==mxUtils.getValue(this.style,mxConstants.STYLE_ABSOLUTE_ARCSIZE,0)?f=Math.min(d/2,Math.min(e/2,mxUtils.getValue(this.style,mxConstants.S [...]
+mxConstants.LINE_ARCSIZE)/2)):(f=mxUtils.getValue(this.style,mxConstants.STYLE_ARCSIZE,100*mxConstants.RECTANGLE_ROUNDING_FACTOR)/100,f=Math.min(d*f,e*f)),a.roundrect(b,c,d,e,f,f)):a.rect(b,c,d,e),a.fillAndStroke()};mxRectangleShape.prototype.paintForeground=function(a,b,c,d,e){this.glass&&!this.outline&&null!=this.fill&&this.fill!=mxConstants.NONE&&this.paintGlassEffect(a,b,c,d,e,this.getArcSize(d+this.strokewidth,e+this.strokewidth))};
+function mxEllipse(a,b,c,d){mxShape.call(this);this.bounds=a;this.fill=b;this.stroke=c;this.strokewidth=null!=d?d:1}mxUtils.extend(mxEllipse,mxShape);mxEllipse.prototype.paintVertexShape=function(a,b,c,d,e){a.ellipse(b,c,d,e);a.fillAndStroke()};function mxDoubleEllipse(a,b,c,d){mxShape.call(this);this.bounds=a;this.fill=b;this.stroke=c;this.strokewidth=null!=d?d:1}mxUtils.extend(mxDoubleEllipse,mxShape);mxDoubleEllipse.prototype.vmlScale=10;
+mxDoubleEllipse.prototype.paintBackground=function(a,b,c,d,e){a.ellipse(b,c,d,e);a.fillAndStroke()};mxDoubleEllipse.prototype.paintForeground=function(a,b,c,d,e){if(!this.outline){var f=mxUtils.getValue(this.style,mxConstants.STYLE_MARGIN,Math.min(3+this.strokewidth,Math.min(d/5,e/5)));d-=2*f;e-=2*f;0<d&&0<e&&a.ellipse(b+f,c+f,d,e);a.stroke()}};
+mxDoubleEllipse.prototype.getLabelBounds=function(a){var b=mxUtils.getValue(this.style,mxConstants.STYLE_MARGIN,Math.min(3+this.strokewidth,Math.min(a.width/5/this.scale,a.height/5/this.scale)))*this.scale;return new mxRectangle(a.x+b,a.y+b,a.width-2*b,a.height-2*b)};function mxRhombus(a,b,c,d){mxShape.call(this);this.bounds=a;this.fill=b;this.stroke=c;this.strokewidth=null!=d?d:1}mxUtils.extend(mxRhombus,mxShape);
+mxRhombus.prototype.paintVertexShape=function(a,b,c,d,e){var f=d/2,g=e/2,k=mxUtils.getValue(this.style,mxConstants.STYLE_ARCSIZE,mxConstants.LINE_ARCSIZE)/2;a.begin();this.addPoints(a,[new mxPoint(b+f,c),new mxPoint(b+d,c+g),new mxPoint(b+f,c+e),new mxPoint(b,c+g)],this.isRounded,k,!0);a.fillAndStroke()};function mxPolyline(a,b,c){mxShape.call(this);this.points=a;this.stroke=b;this.strokewidth=null!=c?c:1}mxUtils.extend(mxPolyline,mxShape);mxPolyline.prototype.getRotation=function(){return 0};
+mxPolyline.prototype.getShapeRotation=function(){return 0};mxPolyline.prototype.isPaintBoundsInverted=function(){return!1};mxPolyline.prototype.paintEdgeShape=function(a,b){null==this.style||1!=this.style[mxConstants.STYLE_CURVED]?this.paintLine(a,b,this.isRounded):this.paintCurvedLine(a,b)};mxPolyline.prototype.paintLine=function(a,b,c){var d=mxUtils.getValue(this.style,mxConstants.STYLE_ARCSIZE,mxConstants.LINE_ARCSIZE)/2;a.begin();this.addPoints(a,b,c,d,!1);a.stroke()};
+mxPolyline.prototype.paintCurvedLine=function(a,b){a.begin();var c=b[0],d=b.length;a.moveTo(c.x,c.y);for(c=1;c<d-2;c++){var e=b[c],f=b[c+1];a.quadTo(e.x,e.y,(e.x+f.x)/2,(e.y+f.y)/2)}e=b[d-2];f=b[d-1];a.quadTo(e.x,e.y,f.x,f.y);a.stroke()};
+function mxArrow(a,b,c,d,e,f,g){mxShape.call(this);this.points=a;this.fill=b;this.stroke=c;this.strokewidth=null!=d?d:1;this.arrowWidth=null!=e?e:mxConstants.ARROW_WIDTH;this.spacing=null!=f?f:mxConstants.ARROW_SPACING;this.endSize=null!=g?g:mxConstants.ARROW_SIZE}mxUtils.extend(mxArrow,mxShape);mxArrow.prototype.augmentBoundingBox=function(a){mxShape.prototype.augmentBoundingBox.apply(this,arguments);a.grow((Math.max(this.arrowWidth,this.endSize)/2+this.strokewidth)*this.scale)};
+mxArrow.prototype.paintEdgeShape=function(a,b){var c=mxConstants.ARROW_SPACING,d=mxConstants.ARROW_WIDTH,e=b[0],f=b[b.length-1],g=f.x-e.x,k=f.y-e.y,l=Math.sqrt(g*g+k*k),m=l-2*c-mxConstants.ARROW_SIZE,g=g/l,k=k/l,l=d*k/3,d=-d*g/3,n=e.x-l/2+c*g,e=e.y-d/2+c*k,p=n+l,q=e+d,r=p+m*g,m=q+m*k,t=r+l,u=m+d,x=t-3*l,y=u-3*d;a.begin();a.moveTo(n,e);a.lineTo(p,q);a.lineTo(r,m);a.lineTo(t,u);a.lineTo(f.x-c*g,f.y-c*k);a.lineTo(x,y);a.lineTo(x+l,y+d);a.close();a.fillAndStroke()};
+function mxArrowConnector(a,b,c,d,e,f,g){mxShape.call(this);this.points=a;this.fill=b;this.stroke=c;this.strokewidth=null!=d?d:1;this.arrowWidth=null!=e?e:mxConstants.ARROW_WIDTH;this.arrowSpacing=null!=f?f:mxConstants.ARROW_SPACING;this.startSize=mxConstants.ARROW_SIZE/5;this.endSize=mxConstants.ARROW_SIZE/5}mxUtils.extend(mxArrowConnector,mxShape);mxArrowConnector.prototype.useSvgBoundingBox=!0;
+mxArrowConnector.prototype.resetStyles=function(){mxShape.prototype.resetStyles.apply(this,arguments);this.arrowSpacing=mxConstants.ARROW_SPACING};mxArrowConnector.prototype.apply=function(a){mxShape.prototype.apply.apply(this,arguments);null!=this.style&&(this.startSize=3*mxUtils.getNumber(this.style,mxConstants.STYLE_STARTSIZE,mxConstants.ARROW_SIZE/5),this.endSize=3*mxUtils.getNumber(this.style,mxConstants.STYLE_ENDSIZE,mxConstants.ARROW_SIZE/5))};
+mxArrowConnector.prototype.augmentBoundingBox=function(a){mxShape.prototype.augmentBoundingBox.apply(this,arguments);var b=this.getEdgeWidth();this.isMarkerStart()&&(b=Math.max(b,this.getStartArrowWidth()));this.isMarkerEnd()&&(b=Math.max(b,this.getEndArrowWidth()));a.grow((b/2+this.strokewidth)*this.scale)};
+mxArrowConnector.prototype.paintEdgeShape=function(a,b){var c=this.strokewidth;this.outline&&(c=Math.max(1,mxUtils.getNumber(this.style,mxConstants.STYLE_STROKEWIDTH,this.strokewidth)));for(var d=this.getStartArrowWidth()+c,e=this.getEndArrowWidth()+c,f=this.outline?this.getEdgeWidth()+c:this.getEdgeWidth(),g=this.isOpenEnded(),k=this.isMarkerStart(),l=this.isMarkerEnd(),m=g?0:this.arrowSpacing+c/2,n=this.startSize+c,c=this.endSize+c,p=this.isArrowRounded(),q=b[b.length-1],r=1;r<b.length [...]
+b[0].x&&b[r].y==b[0].y;)r++;var t=b[r].x-b[0].x,r=b[r].y-b[0].y,u=Math.sqrt(t*t+r*r);if(0!=u){var x=t/u,y,A=x,z=r/u,v,B=z,u=f*z,C=-f*x,F=[];p?a.setLineJoin("round"):2<b.length&&a.setMiterLimit(1.42);a.begin();t=x;r=z;if(k&&!g)this.paintMarker(a,b[0].x,b[0].y,x,z,n,d,f,m,!0);else{y=b[0].x+u/2+m*x;v=b[0].y+C/2+m*z;var D=b[0].x-u/2+m*x,I=b[0].y-C/2+m*z;g?(a.moveTo(y,v),F.push(function(){a.lineTo(D,I)})):(a.moveTo(D,I),a.lineTo(y,v))}for(var E=v=y=0,u=0;u<b.length-2;u++)if(C=mxUtils.relative [...]
+b[u].y,b[u+1].x,b[u+1].y,b[u+2].x,b[u+2].y),y=b[u+2].x-b[u+1].x,v=b[u+2].y-b[u+1].y,E=Math.sqrt(y*y+v*v),0!=E&&(A=y/E,B=v/E,tmp=Math.max(Math.sqrt((x*A+z*B+1)/2),.04),y=x+A,v=z+B,E=Math.sqrt(y*y+v*v),0!=E)){y/=E;v/=E;var E=Math.max(tmp,Math.min(this.strokewidth/200+.04,.35)),E=0!=C&&p?Math.max(.1,E):Math.max(tmp,.06),G=b[u+1].x+v*f/2/E,H=b[u+1].y-y*f/2/E;v=b[u+1].x-v*f/2/E;y=b[u+1].y+y*f/2/E;0!=C&&p?-1==C?(C=v+B*f,E=y-A*f,a.lineTo(v+z*f,y-x*f),a.quadTo(G,H,C,E),function(b,c){F.push(funct [...]
+c)})}(v,y)):(a.lineTo(G,H),function(b,c){var d=G-z*f,e=H+x*f,g=G-B*f,k=H+A*f;F.push(function(){a.quadTo(b,c,d,e)});F.push(function(){a.lineTo(g,k)})}(v,y)):(a.lineTo(G,H),function(b,c){F.push(function(){a.lineTo(b,c)})}(v,y));x=A;z=B}u=f*B;C=-f*A;if(l&&!g)this.paintMarker(a,q.x,q.y,-x,-z,c,e,f,m,!1);else{a.lineTo(q.x-m*A+u/2,q.y-m*B+C/2);var J=q.x-m*A-u/2,K=q.y-m*B-C/2;g?(a.moveTo(J,K),F.splice(0,0,function(){a.moveTo(J,K)})):a.lineTo(J,K)}for(u=F.length-1;0<=u;u--)F[u]();g?(a.end(),a.st [...]
+a.fillAndStroke());a.setShadow(!1);a.setMiterLimit(4);p&&a.setLineJoin("flat");2<b.length&&(a.setMiterLimit(4),k&&!g&&(a.begin(),this.paintMarker(a,b[0].x,b[0].y,t,r,n,d,f,m,!0),a.stroke(),a.end()),l&&!g&&(a.begin(),this.paintMarker(a,q.x,q.y,-x,-z,c,e,f,m,!0),a.stroke(),a.end()))}};
+mxArrowConnector.prototype.paintMarker=function(a,b,c,d,e,f,g,k,l,m){g=k/g;var n=k*e/2;k=-k*d/2;var p=(l+f)*d;f=(l+f)*e;m?a.moveTo(b-n+p,c-k+f):a.lineTo(b-n+p,c-k+f);a.lineTo(b-n/g+p,c-k/g+f);a.lineTo(b+l*d,c+l*e);a.lineTo(b+n/g+p,c+k/g+f);a.lineTo(b+n+p,c+k+f)};mxArrowConnector.prototype.isArrowRounded=function(){return this.isRounded};mxArrowConnector.prototype.getStartArrowWidth=function(){return mxConstants.ARROW_WIDTH};mxArrowConnector.prototype.getEndArrowWidth=function(){return mx [...]
+mxArrowConnector.prototype.getEdgeWidth=function(){return mxConstants.ARROW_WIDTH/3};mxArrowConnector.prototype.isOpenEnded=function(){return!1};mxArrowConnector.prototype.isMarkerStart=function(){return mxUtils.getValue(this.style,mxConstants.STYLE_STARTARROW,mxConstants.NONE)!=mxConstants.NONE};mxArrowConnector.prototype.isMarkerEnd=function(){return mxUtils.getValue(this.style,mxConstants.STYLE_ENDARROW,mxConstants.NONE)!=mxConstants.NONE};
+function mxText(a,b,c,d,e,f,g,k,l,m,n,p,q,r,t,u,x,y,A,z,v){mxShape.call(this);this.value=a;this.bounds=b;this.color=null!=e?e:"black";this.align=null!=c?c:mxConstants.ALIGN_CENTER;this.valign=null!=d?d:mxConstants.ALIGN_MIDDLE;this.family=null!=f?f:mxConstants.DEFAULT_FONTFAMILY;this.size=null!=g?g:mxConstants.DEFAULT_FONTSIZE;this.fontStyle=null!=k?k:mxConstants.DEFAULT_FONTSTYLE;this.spacing=parseInt(l||2);this.spacingTop=this.spacing+parseInt(m||0);this.spacingRight=this.spacing+parse [...]
+this.spacingBottom=this.spacing+parseInt(p||0);this.spacingLeft=this.spacing+parseInt(q||0);this.horizontal=null!=r?r:!0;this.background=t;this.border=u;this.wrap=null!=x?x:!1;this.clipped=null!=y?y:!1;this.overflow=null!=A?A:"visible";this.labelPadding=null!=z?z:0;this.textDirection=v;this.rotation=0;this.updateMargin()}mxUtils.extend(mxText,mxShape);mxText.prototype.baseSpacingTop=0;mxText.prototype.baseSpacingBottom=0;mxText.prototype.baseSpacingLeft=0;mxText.prototype.baseSpacingRight=0;
+mxText.prototype.replaceLinefeeds=!0;mxText.prototype.verticalTextRotation=-90;mxText.prototype.ignoreClippedStringSize=!0;mxText.prototype.ignoreStringSize=!1;mxText.prototype.textWidthPadding=8!=document.documentMode||mxClient.IS_EM?3:4;mxText.prototype.lastValue=null;mxText.prototype.cacheEnabled=!0;mxText.prototype.isParseVml=function(){return!1};mxText.prototype.isHtmlAllowed=function(){return 8!=document.documentMode||mxClient.IS_EM};mxText.prototype.getSvgScreenOffset=function(){r [...]
+mxText.prototype.checkBounds=function(){return!isNaN(this.scale)&&isFinite(this.scale)&&0<this.scale&&null!=this.bounds&&!isNaN(this.bounds.x)&&!isNaN(this.bounds.y)&&!isNaN(this.bounds.width)&&!isNaN(this.bounds.height)};
+mxText.prototype.paint=function(a,b){var c=this.scale,d=this.bounds.x/c,e=this.bounds.y/c,f=this.bounds.width/c,c=this.bounds.height/c;this.updateTransform(a,d,e,f,c);this.configureCanvas(a,d,e,f,c);var g=null!=this.state?this.state.unscaledWidth:null;if(b)null==this.node.firstChild||null!=g&&this.lastUnscaledWidth==g||a.invalidateCachedOffsetSize(this.node),a.updateText(d,e,f,c,this.align,this.valign,this.wrap,this.overflow,this.clipped,this.getTextRotation(),this.node);else{var k=mxUti [...]
+this.dialect==mxConstants.DIALECT_STRICTHTML,l=k||a instanceof mxVmlCanvas2D?"html":"",m=this.value;k||"html"!=l||(m=mxUtils.htmlEntities(m,!1));"html"!=l||mxUtils.isNode(this.value)||(m=mxUtils.replaceTrailingNewlines(m,"<div><br></div>"));var m=!mxUtils.isNode(this.value)&&this.replaceLinefeeds&&"html"==l?m.replace(/\n/g,"<br/>"):m,n=this.textDirection;n!=mxConstants.TEXT_DIRECTION_AUTO||k||(n=this.getAutoDirection());n!=mxConstants.TEXT_DIRECTION_LTR&&n!=mxConstants.TEXT_DIRECTION_RTL [...]
+a.text(d,e,f,c,m,this.align,this.valign,this.wrap,l,this.overflow,this.clipped,this.getTextRotation(),n)}this.lastUnscaledWidth=g};
+mxText.prototype.redraw=function(){if(this.visible&&this.checkBounds()&&this.cacheEnabled&&this.lastValue==this.value&&(mxUtils.isNode(this.value)||this.dialect==mxConstants.DIALECT_STRICTHTML))if("DIV"!=this.node.nodeName||!this.isHtmlAllowed()&&mxClient.IS_VML){var a=this.createCanvas();null!=a&&null!=a.updateText&&null!=a.invalidateCachedOffsetSize?(this.paint(a,!0),this.destroyCanvas(a),this.updateBoundingBox()):mxShape.prototype.redraw.apply(this,arguments)}else this.updateSize(this [...]
+this.state||null==this.state.view.textDiv),mxClient.IS_IE&&(null==document.documentMode||8>=document.documentMode)?this.updateHtmlFilter():this.updateHtmlTransform(),this.updateBoundingBox();else mxShape.prototype.redraw.apply(this,arguments),mxUtils.isNode(this.value)||this.dialect==mxConstants.DIALECT_STRICTHTML?this.lastValue=this.value:this.lastValue=null};
+mxText.prototype.resetStyles=function(){mxShape.prototype.resetStyles.apply(this,arguments);this.color="black";this.align=mxConstants.ALIGN_CENTER;this.valign=mxConstants.ALIGN_MIDDLE;this.family=mxConstants.DEFAULT_FONTFAMILY;this.size=mxConstants.DEFAULT_FONTSIZE;this.fontStyle=mxConstants.DEFAULT_FONTSTYLE;this.spacingLeft=this.spacingBottom=this.spacingRight=this.spacingTop=this.spacing=2;this.horizontal=!0;delete this.background;delete this.border;this.textDirection=mxConstants.DEFA [...]
+delete this.margin};
+mxText.prototype.apply=function(a){var b=this.spacing;mxShape.prototype.apply.apply(this,arguments);null!=this.style&&(this.fontStyle=mxUtils.getValue(this.style,mxConstants.STYLE_FONTSTYLE,this.fontStyle),this.family=mxUtils.getValue(this.style,mxConstants.STYLE_FONTFAMILY,this.family),this.size=mxUtils.getValue(this.style,mxConstants.STYLE_FONTSIZE,this.size),this.color=mxUtils.getValue(this.style,mxConstants.STYLE_FONTCOLOR,this.color),this.align=mxUtils.getValue(this.style,mxConstant [...]
+this.align),this.valign=mxUtils.getValue(this.style,mxConstants.STYLE_VERTICAL_ALIGN,this.valign),this.spacing=parseInt(mxUtils.getValue(this.style,mxConstants.STYLE_SPACING,this.spacing)),this.spacingTop=parseInt(mxUtils.getValue(this.style,mxConstants.STYLE_SPACING_TOP,this.spacingTop-b))+this.spacing,this.spacingRight=parseInt(mxUtils.getValue(this.style,mxConstants.STYLE_SPACING_RIGHT,this.spacingRight-b))+this.spacing,this.spacingBottom=parseInt(mxUtils.getValue(this.style,mxConstan [...]
+this.spacingBottom-b))+this.spacing,this.spacingLeft=parseInt(mxUtils.getValue(this.style,mxConstants.STYLE_SPACING_LEFT,this.spacingLeft-b))+this.spacing,this.horizontal=mxUtils.getValue(this.style,mxConstants.STYLE_HORIZONTAL,this.horizontal),this.background=mxUtils.getValue(this.style,mxConstants.STYLE_LABEL_BACKGROUNDCOLOR,this.background),this.border=mxUtils.getValue(this.style,mxConstants.STYLE_LABEL_BORDERCOLOR,this.border),this.textDirection=mxUtils.getValue(this.style,mxConstant [...]
+mxConstants.DEFAULT_TEXT_DIRECTION),this.opacity=mxUtils.getValue(this.style,mxConstants.STYLE_TEXT_OPACITY,100),this.updateMargin());this.flipH=this.flipV=null};mxText.prototype.getAutoDirection=function(){var a=/[A-Za-z\u05d0-\u065f\u066a-\u06ef\u06fa-\u07ff\ufb1d-\ufdff\ufe70-\ufefc]/.exec(this.value);return null!=a&&0<a.length&&"z"<a[0]?mxConstants.TEXT_DIRECTION_RTL:mxConstants.TEXT_DIRECTION_LTR};
+mxText.prototype.updateBoundingBox=function(){var a=this.node;this.boundingBox=this.bounds.clone();var b=this.getTextRotation(),c=null!=this.style?mxUtils.getValue(this.style,mxConstants.STYLE_LABEL_POSITION,mxConstants.ALIGN_CENTER):null,d=null!=this.style?mxUtils.getValue(this.style,mxConstants.STYLE_VERTICAL_LABEL_POSITION,mxConstants.ALIGN_MIDDLE):null;if(!(this.ignoreStringSize||null==a||"fill"==this.overflow||this.clipped&&this.ignoreClippedStringSize&&c==mxConstants.ALIGN_CENTER&& [...]
+c=null;if(null!=a.ownerSVGElement)if(null!=a.firstChild&&null!=a.firstChild.firstChild&&"foreignObject"==a.firstChild.firstChild.nodeName)a=a.firstChild.firstChild,c=parseInt(a.getAttribute("width"))*this.scale,d=parseInt(a.getAttribute("height"))*this.scale;else try{var e=a.getBBox();"string"==typeof this.value&&0==mxUtils.trim(this.value)?this.boundingBox=null:this.boundingBox=0==e.width&&0==e.height?null:new mxRectangle(e.x,e.y,e.width,e.height);return}catch(f){}else{c=null!=this.stat [...]
+null;if(null==this.offsetWidth||null==this.offsetHeight)null!=c&&(this.updateFont(c),this.updateSize(c,!1),this.updateInnerHtml(c),a=c),e=a,8!=document.documentMode||mxClient.IS_EM?null!=e.firstChild&&"DIV"==e.firstChild.nodeName&&(e=e.firstChild):(d=Math.round(this.bounds.width/this.scale),this.wrap&&0<d?(a.style.wordWrap=mxConstants.WORD_WRAP,a.style.whiteSpace="normal","break-word"!=a.style.wordWrap&&(a=e.getElementsByTagName("div"),0<a.length&&(e=a[a.length-1]),c=e.offsetWidth+2,a=th [...]
+this.clipped&&(c=Math.min(d,c)),1<a.length&&(a[a.length-2].style.width=c+"px"))):a.style.whiteSpace="nowrap"),this.offsetWidth=e.offsetWidth+this.textWidthPadding,this.offsetHeight=e.offsetHeight;c=this.offsetWidth*this.scale;d=this.offsetHeight*this.scale}null!=c&&null!=d&&(this.boundingBox=new mxRectangle(this.bounds.x,this.bounds.y,c,d))}null!=this.boundingBox&&(0!=b?(b=mxUtils.getBoundingBox(new mxRectangle(this.margin.x*this.boundingBox.width,this.margin.y*this.boundingBox.height,th [...]
+this.boundingBox.height),b,new mxPoint(0,0)),this.unrotatedBoundingBox=mxRectangle.fromRectangle(this.boundingBox),this.unrotatedBoundingBox.x+=this.margin.x*this.unrotatedBoundingBox.width,this.unrotatedBoundingBox.y+=this.margin.y*this.unrotatedBoundingBox.height,this.boundingBox.x+=b.x,this.boundingBox.y+=b.y,this.boundingBox.width=b.width,this.boundingBox.height=b.height):(this.boundingBox.x+=this.margin.x*this.boundingBox.width,this.boundingBox.y+=this.margin.y*this.boundingBox.heig [...]
+null))};mxText.prototype.getShapeRotation=function(){return 0};mxText.prototype.getTextRotation=function(){return null!=this.state&&null!=this.state.shape?this.state.shape.getTextRotation():0};mxText.prototype.isPaintBoundsInverted=function(){return!this.horizontal&&null!=this.state&&this.state.view.graph.model.isVertex(this.state.cell)};
+mxText.prototype.configureCanvas=function(a,b,c,d,e){mxShape.prototype.configureCanvas.apply(this,arguments);a.setFontColor(this.color);a.setFontBackgroundColor(this.background);a.setFontBorderColor(this.border);a.setFontFamily(this.family);a.setFontSize(this.size);a.setFontStyle(this.fontStyle)};
+mxText.prototype.updateVmlContainer=function(){this.node.style.left=Math.round(this.bounds.x)+"px";this.node.style.top=Math.round(this.bounds.y)+"px";this.node.style.width="1px";this.node.style.height="1px";this.node.style.overflow="visible"};
+mxText.prototype.redrawHtmlShape=function(){var a=this.node.style;a.whiteSpace="normal";a.overflow="";a.width="";a.height="";this.updateValue();this.updateFont(this.node);this.updateSize(this.node,null==this.state||null==this.state.view.textDiv);this.offsetHeight=this.offsetWidth=null;mxClient.IS_IE&&(null==document.documentMode||8>=document.documentMode)?this.updateHtmlFilter():this.updateHtmlTransform()};
+mxText.prototype.updateHtmlTransform=function(){var a=this.getTextRotation(),b=this.node.style,c=this.margin.x,d=this.margin.y;0!=a?(mxUtils.setPrefixedStyle(b,"transformOrigin",100*-c+"% "+100*-d+"%"),mxUtils.setPrefixedStyle(b,"transform","translate("+100*c+"%,"+100*d+"%)scale("+this.scale+") rotate("+a+"deg)")):(mxUtils.setPrefixedStyle(b,"transformOrigin","0% 0%"),mxUtils.setPrefixedStyle(b,"transform","scale("+this.scale+")translate("+100*c+"%,"+100*d+"%)"));b.left=Math.round(this.b [...]
+("fill"!=this.overflow&&"width"!=this.overflow?3:1)))+"px";b.top=Math.round(this.bounds.y-d*("fill"!=this.overflow?3:1))+"px";b.opacity=100>this.opacity?this.opacity/100:""};
+mxText.prototype.updateInnerHtml=function(a){if(mxUtils.isNode(this.value))a.innerHTML=this.value.outerHTML;else{var b=this.value;this.dialect!=mxConstants.DIALECT_STRICTHTML&&(b=mxUtils.htmlEntities(b,!1));b=mxUtils.replaceTrailingNewlines(b,"<div>&nbsp;</div>");b=this.replaceLinefeeds?b.replace(/\n/g,"<br/>"):b;a.innerHTML='<div style="display:inline-block;_display:inline;">'+b+"</div>"}};
+mxText.prototype.updateHtmlFilter=function(){var a=this.node.style,b=this.margin.x,c=this.margin.y,d=this.scale;mxUtils.setOpacity(this.node,this.opacity);var e,f=0,g=null!=this.state?this.state.view.textDiv:null,k=this.node;if(null!=g){g.style.overflow="";g.style.height="";g.style.width="";this.updateFont(g);this.updateSize(g,!1);this.updateInnerHtml(g);var l=Math.round(this.bounds.width/this.scale);this.wrap&&0<l?(g.style.whiteSpace="normal",g.style.wordWrap=mxConstants.WORD_WRAP,e=l,t [...]
+(e=Math.min(e,this.bounds.width)),g.style.width=e+"px"):g.style.whiteSpace="nowrap";k=g;null!=k.firstChild&&"DIV"==k.firstChild.nodeName&&(k=k.firstChild,this.wrap&&"break-word"==g.style.wordWrap&&(k.style.width="100%"));!this.clipped&&this.wrap&&0<l&&(e=k.offsetWidth+this.textWidthPadding,g.style.width=e+"px");f=k.offsetHeight+2;mxClient.IS_QUIRKS&&null!=this.border&&this.border!=mxConstants.NONE&&(f+=3)}else null!=k.firstChild&&"DIV"==k.firstChild.nodeName&&(k=k.firstChild,f=k.offsetHe [...]
+this.textWidthPadding;this.clipped&&(f=Math.min(f,this.bounds.height));l=this.bounds.width/d;g=this.bounds.height/d;"fill"==this.overflow?(f=g,e=l):"width"==this.overflow&&(f=k.scrollHeight,e=l);this.offsetWidth=e;this.offsetHeight=f;mxClient.IS_QUIRKS&&(this.clipped||"width"==this.overflow&&0<g)?(g=Math.min(g,f),a.height=Math.round(g)+"px"):g=f;"fill"!=this.overflow&&"width"!=this.overflow&&(this.clipped&&(e=Math.min(l,e)),l=e,mxClient.IS_QUIRKS&&this.clipped||this.wrap)&&(a.width=Math. [...]
+"px");var g=g*d,l=l*d,m=this.getTextRotation()*(Math.PI/180);e=parseFloat(parseFloat(Math.cos(m)).toFixed(8));f=parseFloat(parseFloat(Math.sin(-m)).toFixed(8));m%=2*Math.PI;0>m&&(m+=2*Math.PI);m%=Math.PI;m>Math.PI/2&&(m=Math.PI-m);var k=Math.cos(m),n=Math.sin(-m),b=l*-(b+.5),p=g*-(c+.5);0!=m&&(c="progid:DXImageTransform.Microsoft.Matrix(M11="+e+", M12="+f+", M21="+-f+", M22="+e+", sizingMethod='auto expand')",a.filter=null!=a.filter&&0<a.filter.length?a.filter+(" "+c):c);c=0;"fill"!=this [...]
+mxClient.IS_QUIRKS&&(c=this.valign==mxConstants.ALIGN_TOP?c-1:this.valign==mxConstants.ALIGN_BOTTOM?c+2:c+1);a.zoom=d;a.left=Math.round(this.bounds.x+((l-l*k+g*n)/2-e*b-f*p)-l/2)+"px";a.top=Math.round(this.bounds.y+((g-g*k+l*n)/2+f*b-e*p)-g/2+c)+"px"};
+mxText.prototype.updateValue=function(){if(mxUtils.isNode(this.value))this.node.innerHTML="",this.node.appendChild(this.value);else{var a=this.value;this.dialect!=mxConstants.DIALECT_STRICTHTML&&(a=mxUtils.htmlEntities(a,!1));var a=mxUtils.replaceTrailingNewlines(a,"<div><br></div>"),a=this.replaceLinefeeds?a.replace(/\n/g,"<br/>"):a,b=null!=this.background&&this.background!=mxConstants.NONE?this.background:null,c=null!=this.border&&this.border!=mxConstants.NONE?this.border:null;if("fill [...]
+"width"==this.overflow)null!=b&&(this.node.style.backgroundColor=b),null!=c&&(this.node.style.border="1px solid "+c);else{var d="";null!=b&&(d+="background-color:"+b+";");null!=c&&(d+="border:1px solid "+c+";");a='<div style="zoom:1;'+d+"display:inline-block;_display:inline;text-decoration:inherit;padding-bottom:1px;padding-right:1px;line-height:"+(mxConstants.ABSOLUTE_LINE_HEIGHT?this.size*mxConstants.LINE_HEIGHT+"px":mxConstants.LINE_HEIGHT)+'">'+a+"</div>"}this.node.innerHTML=a;a=this [...]
+0<a.length&&(b=this.textDirection,b==mxConstants.TEXT_DIRECTION_AUTO&&this.dialect!=mxConstants.DIALECT_STRICTHTML&&(b=this.getAutoDirection()),b==mxConstants.TEXT_DIRECTION_LTR||b==mxConstants.TEXT_DIRECTION_RTL?a[a.length-1].setAttribute("dir",b):a[a.length-1].removeAttribute("dir"))}};
+mxText.prototype.updateFont=function(a){a=a.style;a.lineHeight=mxConstants.ABSOLUTE_LINE_HEIGHT?this.size*mxConstants.LINE_HEIGHT+"px":mxConstants.LINE_HEIGHT;a.fontSize=this.size+"px";a.fontFamily=this.family;a.verticalAlign="top";a.color=this.color;a.fontWeight=(this.fontStyle&mxConstants.FONT_BOLD)==mxConstants.FONT_BOLD?"bold":"";a.fontStyle=(this.fontStyle&mxConstants.FONT_ITALIC)==mxConstants.FONT_ITALIC?"italic":"";a.textDecoration=(this.fontStyle&mxConstants.FONT_UNDERLINE)==mxCo [...]
+"underline":"";a.textAlign=this.align==mxConstants.ALIGN_CENTER?"center":this.align==mxConstants.ALIGN_RIGHT?"right":"left"};
+mxText.prototype.updateSize=function(a,b){var c=Math.max(0,Math.round(this.bounds.width/this.scale)),d=Math.max(0,Math.round(this.bounds.height/this.scale)),e=a.style;this.clipped?(e.overflow="hidden",mxClient.IS_QUIRKS?e.width=c+"px":(e.maxHeight=d+"px",e.maxWidth=c+"px")):"fill"==this.overflow?(e.width=c+1+"px",e.height=d+1+"px",e.overflow="hidden"):"width"==this.overflow&&(e.width=c+1+"px",e.maxHeight=d+1+"px",e.overflow="hidden");if(this.wrap&&0<c){if(e.wordWrap=mxConstants.WORD_WRAP [...]
+"normal",e.width=c+"px",b&&"fill"!=this.overflow&&"width"!=this.overflow){d=a;null!=d.firstChild&&"DIV"==d.firstChild.nodeName&&(d=d.firstChild,"break-word"==a.style.wordWrap&&(d.style.width="100%"));var f=d.offsetWidth;if(0==f){var g=a.parentNode;a.style.visibility="hidden";document.body.appendChild(a);f=d.offsetWidth;a.style.visibility="";g.appendChild(a)}f+=3;this.clipped&&(f=Math.min(f,c));e.width=f+"px"}}else e.whiteSpace="nowrap"};
+mxText.prototype.updateMargin=function(){this.margin=mxUtils.getAlignmentAsPoint(this.align,this.valign)};
+mxText.prototype.getSpacing=function(){return new mxPoint(this.align==mxConstants.ALIGN_CENTER?(this.spacingLeft-this.spacingRight)/2:this.align==mxConstants.ALIGN_RIGHT?-this.spacingRight-this.baseSpacingRight:this.spacingLeft+this.baseSpacingLeft,this.valign==mxConstants.ALIGN_MIDDLE?(this.spacingTop-this.spacingBottom)/2:this.valign==mxConstants.ALIGN_BOTTOM?-this.spacingBottom-this.baseSpacingBottom:this.spacingTop+this.baseSpacingTop)};function mxTriangle(){mxActor.call(this)}
+mxUtils.extend(mxTriangle,mxActor);mxTriangle.prototype.redrawPath=function(a,b,c,d,e){b=mxUtils.getValue(this.style,mxConstants.STYLE_ARCSIZE,mxConstants.LINE_ARCSIZE)/2;this.addPoints(a,[new mxPoint(0,0),new mxPoint(d,.5*e),new mxPoint(0,e)],this.isRounded,b,!0)};function mxHexagon(){mxActor.call(this)}mxUtils.extend(mxHexagon,mxActor);
+mxHexagon.prototype.redrawPath=function(a,b,c,d,e){b=mxUtils.getValue(this.style,mxConstants.STYLE_ARCSIZE,mxConstants.LINE_ARCSIZE)/2;this.addPoints(a,[new mxPoint(.25*d,0),new mxPoint(.75*d,0),new mxPoint(d,.5*e),new mxPoint(.75*d,e),new mxPoint(.25*d,e),new mxPoint(0,.5*e)],this.isRounded,b,!0)};function mxLine(a,b,c){mxShape.call(this);this.bounds=a;this.stroke=b;this.strokewidth=null!=c?c:1}mxUtils.extend(mxLine,mxShape);
+mxLine.prototype.paintVertexShape=function(a,b,c,d,e){c+=e/2;a.begin();a.moveTo(b,c);a.lineTo(b+d,c);a.stroke()};function mxImageShape(a,b,c,d,e){mxShape.call(this);this.bounds=a;this.image=b;this.fill=c;this.stroke=d;this.strokewidth=null!=e?e:1;this.shadow=!1}mxUtils.extend(mxImageShape,mxRectangleShape);mxImageShape.prototype.preserveImageAspect=!0;mxImageShape.prototype.getSvgScreenOffset=function(){return 0};
+mxImageShape.prototype.apply=function(a){mxShape.prototype.apply.apply(this,arguments);this.gradient=this.stroke=this.fill=null;null!=this.style&&(this.preserveImageAspect=1==mxUtils.getNumber(this.style,mxConstants.STYLE_IMAGE_ASPECT,1),this.flipH=this.flipH||1==mxUtils.getValue(this.style,"imageFlipH",0),this.flipV=this.flipV||1==mxUtils.getValue(this.style,"imageFlipV",0))};mxImageShape.prototype.isHtmlAllowed=function(){return!this.preserveImageAspect};
+mxImageShape.prototype.createHtml=function(){var a=document.createElement("div");a.style.position="absolute";return a};
+mxImageShape.prototype.paintVertexShape=function(a,b,c,d,e){if(null!=this.image){var f=mxUtils.getValue(this.style,mxConstants.STYLE_IMAGE_BACKGROUND,null),g=mxUtils.getValue(this.style,mxConstants.STYLE_IMAGE_BORDER,null);null!=f&&(a.setFillColor(f),a.setStrokeColor(g),a.rect(b,c,d,e),a.fillAndStroke());a.image(b,c,d,e,this.image,this.preserveImageAspect,!1,!1);g=mxUtils.getValue(this.style,mxConstants.STYLE_IMAGE_BORDER,null);null!=g&&(a.setShadow(!1),a.setStrokeColor(g),a.rect(b,c,d,e [...]
+arguments)};
+mxImageShape.prototype.redrawHtmlShape=function(){this.node.style.left=Math.round(this.bounds.x)+"px";this.node.style.top=Math.round(this.bounds.y)+"px";this.node.style.width=Math.max(0,Math.round(this.bounds.width))+"px";this.node.style.height=Math.max(0,Math.round(this.bounds.height))+"px";this.node.innerHTML="";if(null!=this.image){var a=mxUtils.getValue(this.style,mxConstants.STYLE_IMAGE_BACKGROUND,""),b=mxUtils.getValue(this.style,mxConstants.STYLE_IMAGE_BORDER,"");this.node.style.b [...]
+this.node.style.borderColor=b;a=document.createElement(mxClient.IS_IE6||(null==document.documentMode||8>=document.documentMode)&&0!=this.rotation?mxClient.VML_PREFIX+":image":"img");a.setAttribute("border","0");a.style.position="absolute";a.src=this.image;b=100>this.opacity?"alpha(opacity="+this.opacity+")":"";this.node.style.filter=b;this.flipH&&this.flipV?b+="progid:DXImageTransform.Microsoft.BasicImage(rotation=2)":this.flipH?b+="progid:DXImageTransform.Microsoft.BasicImage(mirror=1)" [...]
+(b+="progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)");a.style.filter!=b&&(a.style.filter=b);"image"==a.nodeName?a.style.rotation=this.rotation:0!=this.rotation?mxUtils.setPrefixedStyle(a.style,"transform","rotate("+this.rotation+"deg)"):mxUtils.setPrefixedStyle(a.style,"transform","");a.style.width=this.node.style.width;a.style.height=this.node.style.height;this.node.style.backgroundImage="";this.node.appendChild(a)}else this.setTransparentBackgroundImage(this.node)};
+function mxLabel(a,b,c,d){mxRectangleShape.call(this,a,b,c,d)}mxUtils.extend(mxLabel,mxRectangleShape);mxLabel.prototype.imageSize=mxConstants.DEFAULT_IMAGESIZE;mxLabel.prototype.spacing=2;mxLabel.prototype.indicatorSize=10;mxLabel.prototype.indicatorSpacing=2;mxLabel.prototype.init=function(a){mxShape.prototype.init.apply(this,arguments);null!=this.indicatorShape&&(this.indicator=new this.indicatorShape,this.indicator.dialect=this.dialect,this.indicator.init(this.node))};
+mxLabel.prototype.redraw=function(){null!=this.indicator&&(this.indicator.fill=this.indicatorColor,this.indicator.stroke=this.indicatorStrokeColor,this.indicator.gradient=this.indicatorGradientColor,this.indicator.direction=this.indicatorDirection);mxShape.prototype.redraw.apply(this,arguments)};mxLabel.prototype.isHtmlAllowed=function(){return mxRectangleShape.prototype.isHtmlAllowed.apply(this,arguments)&&null==this.indicatorColor&&null==this.indicatorShape};
+mxLabel.prototype.paintForeground=function(a,b,c,d,e){this.paintImage(a,b,c,d,e);this.paintIndicator(a,b,c,d,e);mxRectangleShape.prototype.paintForeground.apply(this,arguments)};mxLabel.prototype.paintImage=function(a,b,c,d,e){null!=this.image&&(b=this.getImageBounds(b,c,d,e),a.image(b.x,b.y,b.width,b.height,this.image,!1,!1,!1))};
+mxLabel.prototype.getImageBounds=function(a,b,c,d){var e=mxUtils.getValue(this.style,mxConstants.STYLE_IMAGE_ALIGN,mxConstants.ALIGN_LEFT),f=mxUtils.getValue(this.style,mxConstants.STYLE_IMAGE_VERTICAL_ALIGN,mxConstants.ALIGN_MIDDLE),g=mxUtils.getNumber(this.style,mxConstants.STYLE_IMAGE_WIDTH,mxConstants.DEFAULT_IMAGESIZE),k=mxUtils.getNumber(this.style,mxConstants.STYLE_IMAGE_HEIGHT,mxConstants.DEFAULT_IMAGESIZE),l=mxUtils.getNumber(this.style,mxConstants.STYLE_SPACING,this.spacing)+5; [...]
+a+(c-g)/2:e==mxConstants.ALIGN_RIGHT?a+(c-g-l):a+l;b=f==mxConstants.ALIGN_TOP?b+l:f==mxConstants.ALIGN_BOTTOM?b+(d-k-l):b+(d-k)/2;return new mxRectangle(a,b,g,k)};mxLabel.prototype.paintIndicator=function(a,b,c,d,e){null!=this.indicator?(this.indicator.bounds=this.getIndicatorBounds(b,c,d,e),this.indicator.paint(a)):null!=this.indicatorImage&&(b=this.getIndicatorBounds(b,c,d,e),a.image(b.x,b.y,b.width,b.height,this.indicatorImage,!1,!1,!1))};
+mxLabel.prototype.getIndicatorBounds=function(a,b,c,d){var e=mxUtils.getValue(this.style,mxConstants.STYLE_IMAGE_ALIGN,mxConstants.ALIGN_LEFT),f=mxUtils.getValue(this.style,mxConstants.STYLE_IMAGE_VERTICAL_ALIGN,mxConstants.ALIGN_MIDDLE),g=mxUtils.getNumber(this.style,mxConstants.STYLE_INDICATOR_WIDTH,this.indicatorSize),k=mxUtils.getNumber(this.style,mxConstants.STYLE_INDICATOR_HEIGHT,this.indicatorSize),l=this.spacing+5;a=e==mxConstants.ALIGN_RIGHT?a+(c-g-l):e==mxConstants.ALIGN_CENTER [...]
+2:a+l;b=f==mxConstants.ALIGN_BOTTOM?b+(d-k-l):f==mxConstants.ALIGN_TOP?b+l:b+(d-k)/2;return new mxRectangle(a,b,g,k)};
+mxLabel.prototype.redrawHtmlShape=function(){for(mxRectangleShape.prototype.redrawHtmlShape.apply(this,arguments);this.node.hasChildNodes();)this.node.removeChild(this.node.lastChild);if(null!=this.image){var a=document.createElement("img");a.style.position="relative";a.setAttribute("border","0");var b=this.getImageBounds(this.bounds.x,this.bounds.y,this.bounds.width,this.bounds.height);b.x-=this.bounds.x;b.y-=this.bounds.y;a.style.left=Math.round(b.x)+"px";a.style.top=Math.round(b.y)+"p [...]
+Math.round(b.width)+"px";a.style.height=Math.round(b.height)+"px";a.src=this.image;this.node.appendChild(a)}};function mxCylinder(a,b,c,d){mxShape.call(this);this.bounds=a;this.fill=b;this.stroke=c;this.strokewidth=null!=d?d:1}mxUtils.extend(mxCylinder,mxShape);mxCylinder.prototype.maxHeight=40;mxCylinder.prototype.svgStrokeTolerance=0;
+mxCylinder.prototype.paintVertexShape=function(a,b,c,d,e){a.translate(b,c);a.begin();this.redrawPath(a,b,c,d,e,!1);a.fillAndStroke();a.setShadow(!1);a.begin();this.redrawPath(a,b,c,d,e,!0);a.stroke()};
+mxCylinder.prototype.redrawPath=function(a,b,c,d,e,f){b=Math.min(this.maxHeight,Math.round(e/5));if(f&&null!=this.fill||!f&&null==this.fill)a.moveTo(0,b),a.curveTo(0,2*b,d,2*b,d,b),f||(a.stroke(),a.begin());f||(a.moveTo(0,b),a.curveTo(0,-b/3,d,-b/3,d,b),a.lineTo(d,e-b),a.curveTo(d,e+b/3,0,e+b/3,0,e-b),a.close())};function mxConnector(a,b,c){mxPolyline.call(this,a,b,c)}mxUtils.extend(mxConnector,mxPolyline);
+mxConnector.prototype.updateBoundingBox=function(){this.useSvgBoundingBox=null!=this.style&&1==this.style[mxConstants.STYLE_CURVED];mxShape.prototype.updateBoundingBox.apply(this,arguments)};mxConnector.prototype.paintEdgeShape=function(a,b){var c=this.createMarker(a,b,!0),d=this.createMarker(a,b,!1);mxPolyline.prototype.paintEdgeShape.apply(this,arguments);a.setFillColor(this.stroke);a.setShadow(!1);a.setDashed(!1);null!=c&&c();null!=d&&d()};
+mxConnector.prototype.createMarker=function(a,b,c){var d=null,e=b.length,f=mxUtils.getValue(this.style,c?mxConstants.STYLE_STARTARROW:mxConstants.STYLE_ENDARROW),g=c?b[1]:b[e-2],k=c?b[0]:b[e-1];if(null!=f&&null!=g&&null!=k){for(d=1;d<e-1&&0==Math.round(g.x-k.x)&&0==Math.round(g.y-k.y);)g=c?b[1+d]:b[e-2-d],d++;b=k.x-g.x;e=k.y-g.y;d=Math.max(1,Math.sqrt(b*b+e*e));g=b/d;b=e/d;e=mxUtils.getNumber(this.style,c?mxConstants.STYLE_STARTSIZE:mxConstants.STYLE_ENDSIZE,mxConstants.DEFAULT_MARKERSIZ [...]
+this,f,k,g,b,e,c,this.strokewidth,0!=this.style[c?mxConstants.STYLE_STARTFILL:mxConstants.STYLE_ENDFILL])}return d};
+mxConnector.prototype.augmentBoundingBox=function(a){mxShape.prototype.augmentBoundingBox.apply(this,arguments);var b=0;mxUtils.getValue(this.style,mxConstants.STYLE_STARTARROW,mxConstants.NONE)!=mxConstants.NONE&&(b=mxUtils.getNumber(this.style,mxConstants.STYLE_STARTSIZE,mxConstants.DEFAULT_MARKERSIZE)+1);mxUtils.getValue(this.style,mxConstants.STYLE_ENDARROW,mxConstants.NONE)!=mxConstants.NONE&&(b=Math.max(b,mxUtils.getNumber(this.style,mxConstants.STYLE_ENDSIZE,mxConstants.DEFAULT_MA [...]
+1);a.grow(b*this.scale)};function mxSwimlane(a,b,c,d){mxShape.call(this);this.bounds=a;this.fill=b;this.stroke=c;this.strokewidth=null!=d?d:1}mxUtils.extend(mxSwimlane,mxShape);mxSwimlane.prototype.imageSize=16;mxSwimlane.prototype.getTitleSize=function(){return Math.max(0,mxUtils.getValue(this.style,mxConstants.STYLE_STARTSIZE,mxConstants.DEFAULT_STARTSIZE))};
+mxSwimlane.prototype.getLabelBounds=function(a){var b=this.getTitleSize();a=new mxRectangle(a.x,a.y,a.width,a.height);var c=this.isHorizontal(),d=1==mxUtils.getValue(this.style,mxConstants.STYLE_FLIPH,0),e=1==mxUtils.getValue(this.style,mxConstants.STYLE_FLIPV,0),f=this.direction==mxConstants.DIRECTION_NORTH||this.direction==mxConstants.DIRECTION_SOUTH,c=c==!f,d=!c&&d!=(this.direction==mxConstants.DIRECTION_SOUTH||this.direction==mxConstants.DIRECTION_WEST),e=c&&e!=(this.direction==mxCon [...]
+this.direction==mxConstants.DIRECTION_WEST);if(f){b=Math.min(a.width,b*this.scale);if(d||e)a.x+=a.width-b;a.width=b}else{b=Math.min(a.height,b*this.scale);if(d||e)a.y+=a.height-b;a.height=b}return a};mxSwimlane.prototype.getGradientBounds=function(a,b,c,d,e){a=this.getTitleSize();if(this.isHorizontal())return a=Math.min(a,e),new mxRectangle(b,c,d,a);a=Math.min(a,d);return new mxRectangle(b,c,a,e)};
+mxSwimlane.prototype.getArcSize=function(a,b,c){a=mxUtils.getValue(this.style,mxConstants.STYLE_ARCSIZE,100*mxConstants.RECTANGLE_ROUNDING_FACTOR)/100;return c*a*3};mxSwimlane.prototype.isHorizontal=function(){return 1==mxUtils.getValue(this.style,mxConstants.STYLE_HORIZONTAL,1)};
+mxSwimlane.prototype.paintVertexShape=function(a,b,c,d,e){var f=this.getTitleSize(),g=mxUtils.getValue(this.style,mxConstants.STYLE_SWIMLANE_FILLCOLOR,mxConstants.NONE),k=1==mxUtils.getValue(this.style,mxConstants.STYLE_SWIMLANE_LINE,1),l=0,f=this.isHorizontal()?Math.min(f,e):Math.min(f,d);a.translate(b,c);this.isRounded?(l=this.getArcSize(d,e,f),this.paintRoundedSwimlane(a,b,c,d,e,f,l,g,k)):this.paintSwimlane(a,b,c,d,e,f,g,k);g=mxUtils.getValue(this.style,mxConstants.STYLE_SEPARATORCOLO [...]
+this.paintSeparator(a,b,c,d,e,f,g);null!=this.image&&(e=this.getImageBounds(b,c,d,e),a.image(e.x-b,e.y-c,e.width,e.height,this.image,!1,!1,!1));this.glass&&(a.setShadow(!1),this.paintGlassEffect(a,0,0,d,f,l))};
+mxSwimlane.prototype.paintSwimlane=function(a,b,c,d,e,f,g,k){g!=mxConstants.NONE&&(a.save(),a.setFillColor(g),a.rect(0,0,d,e),a.fillAndStroke(),a.restore(),a.setShadow(!1));a.begin();this.isHorizontal()?(a.moveTo(0,f),a.lineTo(0,0),a.lineTo(d,0),a.lineTo(d,f),(k||f>=e)&&a.close(),a.fillAndStroke(),f<e&&g==mxConstants.NONE&&(a.pointerEvents=!1,a.begin(),a.moveTo(0,f),a.lineTo(0,e),a.lineTo(d,e),a.lineTo(d,f),a.stroke())):(a.moveTo(f,0),a.lineTo(0,0),a.lineTo(0,e),a.lineTo(f,e),(k||f>=d)&& [...]
+a.fillAndStroke(),f<d&&g==mxConstants.NONE&&(a.pointerEvents=!1,a.begin(),a.moveTo(f,0),a.lineTo(d,0),a.lineTo(d,e),a.lineTo(f,e),a.stroke()))};
+mxSwimlane.prototype.paintRoundedSwimlane=function(a,b,c,d,e,f,g,k,l){g=Math.min(e-f,Math.min(f,g));k!=mxConstants.NONE&&(a.save(),a.setFillColor(k),a.roundrect(0,0,d,e,g,g),a.fillAndStroke(),a.restore(),a.setShadow(!1));a.begin();this.isHorizontal()?(a.moveTo(d,f),a.lineTo(d,g),a.quadTo(d,0,d-Math.min(d/2,g),0),a.lineTo(Math.min(d/2,g),0),a.quadTo(0,0,0,g),a.lineTo(0,f),(l||f>=e)&&a.close(),a.fillAndStroke(),f<e&&k==mxConstants.NONE&&(a.pointerEvents=!1,a.begin(),a.moveTo(0,f),a.lineTo( [...]
+e,Math.min(d/2,g),e),a.lineTo(d-Math.min(d/2,g),e),a.quadTo(d,e,d,e-g),a.lineTo(d,f),a.stroke())):(a.moveTo(f,0),a.lineTo(g,0),a.quadTo(0,0,0,Math.min(e/2,g)),a.lineTo(0,e-Math.min(e/2,g)),a.quadTo(0,e,g,e),a.lineTo(f,e),(l||f>=d)&&a.close(),a.fillAndStroke(),f<d&&k==mxConstants.NONE&&(a.pointerEvents=!1,a.begin(),a.moveTo(f,e),a.lineTo(d-g,e),a.quadTo(d,e,d,e-Math.min(e/2,g)),a.lineTo(d,Math.min(e/2,g)),a.quadTo(d,0,d-g,0),a.lineTo(f,0),a.stroke()))};
+mxSwimlane.prototype.paintSeparator=function(a,b,c,d,e,f,g){g!=mxConstants.NONE&&(a.setStrokeColor(g),a.setDashed(!0),a.begin(),this.isHorizontal()?(a.moveTo(d,f),a.lineTo(d,e)):(a.moveTo(f,0),a.lineTo(d,0)),a.stroke(),a.setDashed(!1))};mxSwimlane.prototype.getImageBounds=function(a,b,c,d){return this.isHorizontal()?new mxRectangle(a+c-this.imageSize,b,this.imageSize,this.imageSize):new mxRectangle(a,b,this.imageSize,this.imageSize)};function mxGraphLayout(a){this.graph=a}
+mxGraphLayout.prototype.graph=null;mxGraphLayout.prototype.useBoundingBox=!0;mxGraphLayout.prototype.parent=null;mxGraphLayout.prototype.moveCell=function(a,b,c){};mxGraphLayout.prototype.execute=function(a){};mxGraphLayout.prototype.getGraph=function(){return this.graph};mxGraphLayout.prototype.getConstraint=function(a,b,c,d){c=this.graph.view.getState(b);b=null!=c?c.style:this.graph.getCellStyle(b);return null!=b?b[a]:null};
+mxGraphLayout.traverse=function(a,b,c,d,e){if(null!=c&&null!=a&&(b=null!=b?b:!0,e=e||new mxDictionary,!e.get(a)&&(e.put(a,!0),d=c(a,d),null==d||d))&&(d=this.graph.model.getEdgeCount(a),0<d))for(var f=0;f<d;f++){var g=this.graph.model.getEdgeAt(a,f),k=this.graph.model.getTerminal(g,!0)==a;if(!b||k)k=this.graph.view.getVisibleTerminal(g,!k),this.traverse(k,b,c,g,e)}};mxGraphLayout.prototype.isVertexMovable=function(a){return this.graph.isCellMovable(a)};
+mxGraphLayout.prototype.isVertexIgnored=function(a){return!this.graph.getModel().isVertex(a)||!this.graph.isCellVisible(a)};mxGraphLayout.prototype.isEdgeIgnored=function(a){var b=this.graph.getModel();return!b.isEdge(a)||!this.graph.isCellVisible(a)||null==b.getTerminal(a,!0)||null==b.getTerminal(a,!1)};mxGraphLayout.prototype.setEdgeStyleEnabled=function(a,b){this.graph.setCellStyles(mxConstants.STYLE_NOEDGESTYLE,b?"0":"1",[a])};
+mxGraphLayout.prototype.setOrthogonalEdge=function(a,b){this.graph.setCellStyles(mxConstants.STYLE_ORTHOGONAL,b?"1":"0",[a])};mxGraphLayout.prototype.getParentOffset=function(a){var b=new mxPoint;if(null!=a&&a!=this.parent){var c=this.graph.getModel();if(c.isAncestor(this.parent,a))for(var d=c.getGeometry(a);a!=this.parent;)b.x+=d.x,b.y+=d.y,a=c.getParent(a),d=c.getGeometry(a)}return b};
+mxGraphLayout.prototype.setEdgePoints=function(a,b){if(null!=a){var c=this.graph.model,d=c.getGeometry(a);null==d?(d=new mxGeometry,d.setRelative(!0)):d=d.clone();if(null!=this.parent&&null!=b)for(var e=c.getParent(a),e=this.getParentOffset(e),f=0;f<b.length;f++)b[f].x-=e.x,b[f].y-=e.y;d.points=b;c.setGeometry(a,d)}};
+mxGraphLayout.prototype.setVertexLocation=function(a,b,c){var d=this.graph.getModel(),e=d.getGeometry(a),f=null;if(null!=e){f=new mxRectangle(b,c,e.width,e.height);if(this.useBoundingBox){var g=this.graph.getView().getState(a);if(null!=g&&null!=g.text&&null!=g.text.boundingBox){var k=this.graph.getView().scale,l=g.text.boundingBox;g.text.boundingBox.x<g.x&&(b+=(g.x-l.x)/k,f.width=l.width);g.text.boundingBox.y<g.y&&(c+=(g.y-l.y)/k,f.height=l.height)}}null!=this.parent&&(g=d.getParent(a),n [...]
+this.parent&&(g=this.getParentOffset(g),b-=g.x,c-=g.y));if(e.x!=b||e.y!=c)e=e.clone(),e.x=b,e.y=c,d.setGeometry(a,e)}return f};
+mxGraphLayout.prototype.getVertexBounds=function(a){var b=this.graph.getModel().getGeometry(a);if(this.useBoundingBox){var c=this.graph.getView().getState(a);if(null!=c&&null!=c.text&&null!=c.text.boundingBox)var d=this.graph.getView().scale,e=c.text.boundingBox,f=Math.max(c.x-e.x,0)/d,g=Math.max(c.y-e.y,0)/d,b=new mxRectangle(b.x-f,b.y-g,b.width+f+Math.max(e.x+e.width-(c.x+c.width),0)/d,b.height+g+Math.max(e.y+e.height-(c.y+c.height),0)/d)}null!=this.parent&&(a=this.graph.getModel().get [...]
+b=b.clone(),null!=a&&a!=this.parent&&(a=this.getParentOffset(a),b.x+=a.x,b.y+=a.y));return new mxRectangle(b.x,b.y,b.width,b.height)};mxGraphLayout.prototype.arrangeGroups=function(a,b,c,d,e,f){return this.graph.updateGroupBounds(a,b,!0,c,d,e,f)};function mxStackLayout(a,b,c,d,e,f){mxGraphLayout.call(this,a);this.horizontal=null!=b?b:!0;this.spacing=null!=c?c:0;this.x0=null!=d?d:0;this.y0=null!=e?e:0;this.border=null!=f?f:0}mxStackLayout.prototype=new mxGraphLayout;
+mxStackLayout.prototype.constructor=mxStackLayout;mxStackLayout.prototype.horizontal=null;mxStackLayout.prototype.spacing=null;mxStackLayout.prototype.x0=null;mxStackLayout.prototype.y0=null;mxStackLayout.prototype.border=0;mxStackLayout.prototype.marginTop=0;mxStackLayout.prototype.marginLeft=0;mxStackLayout.prototype.marginRight=0;mxStackLayout.prototype.marginBottom=0;mxStackLayout.prototype.keepFirstLocation=!1;mxStackLayout.prototype.fill=!1;mxStackLayout.prototype.resizeParent=!1;
+mxStackLayout.prototype.resizeParentMax=!1;mxStackLayout.prototype.resizeLast=!1;mxStackLayout.prototype.wrap=null;mxStackLayout.prototype.borderCollapse=!0;mxStackLayout.prototype.isHorizontal=function(){return this.horizontal};
+mxStackLayout.prototype.moveCell=function(a,b,c){var d=this.graph.getModel(),e=d.getParent(a),f=this.isHorizontal();if(null!=a&&null!=e){var g=0,k=d.getChildCount(e);c=f?b:c;b=this.graph.getView().getState(e);null!=b&&(c-=f?b.x:b.y);c/=this.graph.view.scale;for(b=0;b<k;b++){var l=d.getChildAt(e,b);if(l!=a&&(l=d.getGeometry(l),null!=l)){l=f?l.x+l.width/2:l.y+l.height/2;if(g<=c&&l>c)break;g=l}}f=e.getIndex(a);f=Math.max(0,b-(b>f?1:0));d.add(e,a,f)}};
+mxStackLayout.prototype.getParentSize=function(a){var b=this.graph.getModel(),c=b.getGeometry(a);null!=this.graph.container&&(null==c&&b.isLayer(a)||a==this.graph.getView().currentRoot)&&(c=new mxRectangle(0,0,this.graph.container.offsetWidth-1,this.graph.container.offsetHeight-1));return c};
+mxStackLayout.prototype.execute=function(a){if(null!=a){var b=this.getParentSize(a),c=this.isHorizontal(),d=this.graph.getModel(),e=null;null!=b&&(e=c?b.height-this.marginTop-this.marginBottom:b.width-this.marginLeft-this.marginRight);var e=e-(2*this.spacing+2*this.border),f=this.x0+this.border+this.marginLeft,g=this.y0+this.border+this.marginTop;if(this.graph.isSwimlane(a)){var k=this.graph.getCellStyle(a),l=mxUtils.getNumber(k,mxConstants.STYLE_STARTSIZE,mxConstants.DEFAULT_STARTSIZE), [...]
+mxConstants.STYLE_HORIZONTAL,!0);null!=b&&(l=k?Math.min(l,b.height):Math.min(l,b.width));c==k&&(e-=l);k?g+=l:f+=l}d.beginUpdate();try{for(var l=0,k=null,m=0,n=null,p=d.getChildCount(a),q=0;q<p;q++){var r=d.getChildAt(a,q);if(!this.isVertexIgnored(r)&&this.isVertexMovable(r)){var t=d.getGeometry(r);if(null!=t){t=t.clone();null!=this.wrap&&null!=k&&(c&&k.x+k.width+t.width+2*this.spacing>this.wrap||!c&&k.y+k.height+t.height+2*this.spacing>this.wrap)&&(k=null,c?g+=l+this.spacing:f+=l+this.sp [...]
+var l=Math.max(l,c?t.height:t.width),u=0;if(!this.borderCollapse)var x=this.graph.getCellStyle(r),u=mxUtils.getNumber(x,mxConstants.STYLE_STROKEWIDTH,1);null!=k?c?t.x=m+this.spacing+Math.floor(u/2):t.y=m+this.spacing+Math.floor(u/2):this.keepFirstLocation||(c?t.x=f:t.y=g);c?t.y=g:t.x=f;this.fill&&null!=e&&(c?t.height=e:t.width=e);this.setChildGeometry(r,t);n=r;k=t;m=c?k.x+k.width+Math.floor(u/2):k.y+k.height+Math.floor(u/2)}}}this.resizeParent&&null!=b&&null!=k&&!this.graph.isCellCollaps [...]
+b,k):this.resizeLast&&null!=b&&null!=k&&null!=n&&(c?k.width=b.width-k.x-this.spacing-this.marginRight-this.marginLeft:k.height=b.height-k.y-this.spacing-this.marginBottom,this.setChildGeometry(n,k))}finally{d.endUpdate()}}};mxStackLayout.prototype.setChildGeometry=function(a,b){var c=this.graph.getCellGeometry(a);null!=c&&b.x==c.x&&b.y==c.y&&b.width==c.width&&b.height==c.height||this.graph.getModel().setGeometry(a,b)};
+mxStackLayout.prototype.updateParentGeometry=function(a,b,c){var d=this.isHorizontal(),e=this.graph.getModel(),f=b.clone();d?(c=c.x+c.width+this.spacing+this.marginRight,f.width=this.resizeParentMax?Math.max(f.width,c):c):(c=c.y+c.height+this.spacing+this.marginBottom,f.height=this.resizeParentMax?Math.max(f.height,c):c);b.x==f.x&&b.y==f.y&&b.width==f.width&&b.height==f.height||e.setGeometry(a,f)};
+function mxPartitionLayout(a,b,c,d){mxGraphLayout.call(this,a);this.horizontal=null!=b?b:!0;this.spacing=c||0;this.border=d||0}mxPartitionLayout.prototype=new mxGraphLayout;mxPartitionLayout.prototype.constructor=mxPartitionLayout;mxPartitionLayout.prototype.horizontal=null;mxPartitionLayout.prototype.spacing=null;mxPartitionLayout.prototype.border=null;mxPartitionLayout.prototype.resizeVertices=!0;mxPartitionLayout.prototype.isHorizontal=function(){return this.horizontal};
+mxPartitionLayout.prototype.moveCell=function(a,b,c){c=this.graph.getModel();var d=c.getParent(a);if(null!=a&&null!=d){var e,f=0,g=c.getChildCount(d);for(e=0;e<g;e++){var k=c.getChildAt(d,e),k=this.getVertexBounds(k);if(null!=k){k=k.x+k.width/2;if(f<b&&k>b)break;f=k}}b=d.getIndex(a);b=Math.max(0,e-(e>b?1:0));c.add(d,a,b)}};
+mxPartitionLayout.prototype.execute=function(a){var b=this.isHorizontal(),c=this.graph.getModel(),d=c.getGeometry(a);null!=this.graph.container&&(null==d&&c.isLayer(a)||a==this.graph.getView().currentRoot)&&(d=new mxRectangle(0,0,this.graph.container.offsetWidth-1,this.graph.container.offsetHeight-1));if(null!=d){for(var e=[],f=c.getChildCount(a),g=0;g<f;g++){var k=c.getChildAt(a,g);!this.isVertexIgnored(k)&&this.isVertexMovable(k)&&e.push(k)}f=e.length;if(0<f){var l=this.border,m=this.b [...]
+d.height:d.width,n=n-2*this.border;a=this.graph.isSwimlane(a)?this.graph.getStartSize(a):new mxRectangle;n-=b?a.height:a.width;l+=a.width;m+=a.height;a=this.border+(f-1)*this.spacing;d=b?(d.width-l-a)/f:(d.height-m-a)/f;if(0<d){c.beginUpdate();try{for(g=0;g<f;g++){var k=e[g],p=c.getGeometry(k);null!=p&&(p=p.clone(),p.x=l,p.y=m,b?(this.resizeVertices&&(p.width=d,p.height=n),l+=d+this.spacing):(this.resizeVertices&&(p.height=d,p.width=n),m+=d+this.spacing),c.setGeometry(k,p))}}finally{c.en [...]
+function mxCompactTreeLayout(a,b,c){mxGraphLayout.call(this,a);this.horizontal=null!=b?b:!0;this.invert=null!=c?c:!1}mxCompactTreeLayout.prototype=new mxGraphLayout;mxCompactTreeLayout.prototype.constructor=mxCompactTreeLayout;mxCompactTreeLayout.prototype.horizontal=null;mxCompactTreeLayout.prototype.invert=null;mxCompactTreeLayout.prototype.resizeParent=!0;mxCompactTreeLayout.prototype.maintainParentLocation=!1;mxCompactTreeLayout.prototype.groupPadding=10;
+mxCompactTreeLayout.prototype.groupPaddingTop=0;mxCompactTreeLayout.prototype.groupPaddingRight=0;mxCompactTreeLayout.prototype.groupPaddingBottom=0;mxCompactTreeLayout.prototype.groupPaddingLeft=0;mxCompactTreeLayout.prototype.parentsChanged=null;mxCompactTreeLayout.prototype.moveTree=!1;mxCompactTreeLayout.prototype.visited=null;mxCompactTreeLayout.prototype.levelDistance=10;mxCompactTreeLayout.prototype.nodeDistance=20;mxCompactTreeLayout.prototype.resetEdges=!0;
+mxCompactTreeLayout.prototype.prefHozEdgeSep=5;mxCompactTreeLayout.prototype.prefVertEdgeOff=4;mxCompactTreeLayout.prototype.minEdgeJetty=8;mxCompactTreeLayout.prototype.channelBuffer=4;mxCompactTreeLayout.prototype.edgeRouting=!0;mxCompactTreeLayout.prototype.sortEdges=!1;mxCompactTreeLayout.prototype.alignRanks=!1;mxCompactTreeLayout.prototype.maxRankHeight=null;mxCompactTreeLayout.prototype.root=null;mxCompactTreeLayout.prototype.node=null;
+mxCompactTreeLayout.prototype.isVertexIgnored=function(a){return mxGraphLayout.prototype.isVertexIgnored.apply(this,arguments)||0==this.graph.getConnections(a).length};mxCompactTreeLayout.prototype.isHorizontal=function(){return this.horizontal};
+mxCompactTreeLayout.prototype.execute=function(a,b){this.parent=a;var c=this.graph.getModel();if(null==b)if(0<this.graph.getEdges(a,c.getParent(a),this.invert,!this.invert,!1).length)this.root=a;else{var d=this.graph.findTreeRoots(a,!0,this.invert);if(0<d.length)for(var e=0;e<d.length;e++)if(!this.isVertexIgnored(d[e])&&0<this.graph.getEdges(d[e],null,this.invert,!this.invert,!1).length){this.root=d[e];break}}else this.root=b;if(null!=this.root){this.parentsChanged=this.resizeParent?{}:n [...]
+this.parentX=null;if(a!=this.root&&null!=c.isVertex(a)&&this.maintainParentLocation){var f=this.graph.getCellGeometry(a);null!=f&&(this.parentX=f.x,this.parentY=f.y)}c.beginUpdate();try{if(this.visited={},this.node=this.dfs(this.root,a),this.alignRanks&&(this.maxRankHeight=[],this.findRankHeights(this.node,0),this.setCellHeights(this.node,0)),null!=this.node){this.layout(this.node);var g=this.graph.gridSize,d=g;if(!this.moveTree){var k=this.getVertexBounds(this.root);null!=k&&(g=k.x,d=k. [...]
+k=this.isHorizontal()?this.horizontalLayout(this.node,g,d):this.verticalLayout(this.node,null,g,d);if(null!=k){var l=e=0;0>k.x&&(e=Math.abs(g-k.x));0>k.y&&(l=Math.abs(d-k.y));0==e&&0==l||this.moveNode(this.node,e,l);this.resizeParent&&this.adjustParents();this.edgeRouting&&this.localEdgeProcessing(this.node)}null!=this.parentX&&null!=this.parentY&&(f=this.graph.getCellGeometry(a),null!=f&&(f=f.clone(),f.x=this.parentX,f.y=this.parentY,c.setGeometry(a,f)))}}finally{c.endUpdate()}}};
+mxCompactTreeLayout.prototype.moveNode=function(a,b,c){a.x+=b;a.y+=c;this.apply(a);for(a=a.child;null!=a;)this.moveNode(a,b,c),a=a.next};
+mxCompactTreeLayout.prototype.sortOutgoingEdges=function(a,b){var c=new mxDictionary;b.sort(function(b,e){var d=b.getTerminal(b.getTerminal(!1)==a),g=c.get(d);null==g&&(g=mxCellPath.create(d).split(mxCellPath.PATH_SEPARATOR),c.put(d,g));var d=e.getTerminal(e.getTerminal(!1)==a),k=c.get(d);null==k&&(k=mxCellPath.create(d).split(mxCellPath.PATH_SEPARATOR),c.put(d,k));return mxCellPath.compare(g,k)})};
+mxCompactTreeLayout.prototype.findRankHeights=function(a,b){if(null==this.maxRankHeight[b]||this.maxRankHeight[b]<a.height)this.maxRankHeight[b]=a.height;for(var c=a.child;null!=c;)this.findRankHeights(c,b+1),c=c.next};mxCompactTreeLayout.prototype.setCellHeights=function(a,b){null!=this.maxRankHeight[b]&&this.maxRankHeight[b]>a.height&&(a.height=this.maxRankHeight[b]);for(var c=a.child;null!=c;)this.setCellHeights(c,b+1),c=c.next};
+mxCompactTreeLayout.prototype.dfs=function(a,b){var c=mxCellPath.create(a),d=null;if(null!=a&&null==this.visited[c]&&!this.isVertexIgnored(a)){this.visited[c]=a;var d=this.createNode(a),c=this.graph.getModel(),e=null,f=this.graph.getEdges(a,b,this.invert,!this.invert,!1,!0),g=this.graph.getView();this.sortEdges&&this.sortOutgoingEdges(a,f);for(var k=0;k<f.length;k++){var l=f[k];if(!this.isEdgeIgnored(l)){this.resetEdges&&this.setEdgePoints(l,null);this.edgeRouting&&(this.setEdgeStyleEnab [...]
+this.setEdgePoints(l,null));var m=g.getState(l),l=null!=m?m.getVisibleTerminal(this.invert):g.getVisibleTerminal(l,this.invert),m=this.dfs(l,b);null!=m&&null!=c.getGeometry(l)&&(null==e?d.child=m:e.next=m,e=m)}}}return d};mxCompactTreeLayout.prototype.layout=function(a){if(null!=a){for(var b=a.child;null!=b;)this.layout(b),b=b.next;null!=a.child?this.attachParent(a,this.join(a)):this.layoutLeaf(a)}};
+mxCompactTreeLayout.prototype.horizontalLayout=function(a,b,c,d){a.x+=b+a.offsetX;a.y+=c+a.offsetY;d=this.apply(a,d);b=a.child;if(null!=b){d=this.horizontalLayout(b,a.x,a.y,d);c=a.y+b.offsetY;for(var e=b.next;null!=e;)d=this.horizontalLayout(e,a.x+b.offsetX,c,d),c+=e.offsetY,e=e.next}return d};
+mxCompactTreeLayout.prototype.verticalLayout=function(a,b,c,d,e){a.x+=c+a.offsetY;a.y+=d+a.offsetX;e=this.apply(a,e);b=a.child;if(null!=b)for(e=this.verticalLayout(b,a,a.x,a.y,e),c=a.x+b.offsetY,d=b.next;null!=d;)e=this.verticalLayout(d,a,c,a.y+b.offsetX,e),c+=d.offsetY,d=d.next;return e};
+mxCompactTreeLayout.prototype.attachParent=function(a,b){var c=this.nodeDistance+this.levelDistance,d=(b-a.width)/2-this.nodeDistance,e=d+a.width+2*this.nodeDistance-b;a.child.offsetX=c+a.height;a.child.offsetY=e;a.contour.upperHead=this.createLine(a.height,0,this.createLine(c,e,a.contour.upperHead));a.contour.lowerHead=this.createLine(a.height,0,this.createLine(c,d,a.contour.lowerHead))};
+mxCompactTreeLayout.prototype.layoutLeaf=function(a){var b=2*this.nodeDistance;a.contour.upperTail=this.createLine(a.height+b,0);a.contour.upperHead=a.contour.upperTail;a.contour.lowerTail=this.createLine(0,-a.width-b);a.contour.lowerHead=this.createLine(a.height+b,0,a.contour.lowerTail)};
+mxCompactTreeLayout.prototype.join=function(a){var b=2*this.nodeDistance,c=a.child;a.contour=c.contour;for(var d=c.width+b,e=d,c=c.next;null!=c;){var f=this.merge(a.contour,c.contour);c.offsetY=f+d;c.offsetX=0;d=c.width+b;e+=f+d;c=c.next}return e};
+mxCompactTreeLayout.prototype.merge=function(a,b){for(var c=0,d=0,e=0,f=a.lowerHead,g=b.upperHead;null!=g&&null!=f;){var k=this.offset(c,d,g.dx,g.dy,f.dx,f.dy),d=d+k,e=e+k;c+g.dx<=f.dx?(c+=g.dx,d+=g.dy,g=g.next):(c-=f.dx,d-=f.dy,f=f.next)}null!=g?(c=this.bridge(a.upperTail,0,0,g,c,d),a.upperTail=null!=c.next?b.upperTail:c,a.lowerTail=b.lowerTail):(c=this.bridge(b.lowerTail,c,d,f,0,0),null==c.next&&(a.lowerTail=c));a.lowerHead=b.lowerHead;return e};
+mxCompactTreeLayout.prototype.offset=function(a,b,c,d,e,f){if(e<=a||0>=a+c)return 0;a=0<e*d-c*f?0>a?a*d/c-b:0<a?a*f/e-b:-b:e<a+c?f-(b+(e-a)*d/c):e>a+c?(c+a)*f/e-(b+d):f-(b+d);return 0<a?a:0};mxCompactTreeLayout.prototype.bridge=function(a,b,c,d,e,f){b=e+d.dx-b;0==d.dx?e=d.dy:(e=b*d.dy,e/=d.dx);b=this.createLine(b,e,d.next);a.next=this.createLine(0,f+d.dy-e-c,b);return b};
+mxCompactTreeLayout.prototype.createNode=function(a){var b={};b.cell=a;b.x=0;b.y=0;b.width=0;b.height=0;a=this.getVertexBounds(a);null!=a&&(this.isHorizontal()?(b.width=a.height,b.height=a.width):(b.width=a.width,b.height=a.height));b.offsetX=0;b.offsetY=0;b.contour={};return b};
+mxCompactTreeLayout.prototype.apply=function(a,b){var c=this.graph.getModel(),d=a.cell,e=c.getGeometry(d);null!=d&&null!=e&&(this.isVertexMovable(d)&&(e=this.setVertexLocation(d,a.x,a.y),this.resizeParent&&(c=c.getParent(d),d=mxCellPath.create(c),null==this.parentsChanged[d]&&(this.parentsChanged[d]=c))),b=null==b?new mxRectangle(e.x,e.y,e.width,e.height):new mxRectangle(Math.min(b.x,e.x),Math.min(b.y,e.y),Math.max(b.x+b.width,e.x+e.width),Math.max(b.y+b.height,e.y+e.height)));return b};
+mxCompactTreeLayout.prototype.createLine=function(a,b,c){var d={};d.dx=a;d.dy=b;d.next=c;return d};mxCompactTreeLayout.prototype.adjustParents=function(){var a=[],b;for(b in this.parentsChanged)a.push(this.parentsChanged[b]);this.arrangeGroups(mxUtils.sortCells(a,!0),this.groupPadding,this.groupPaddingTop,this.groupPaddingRight,this.groupPaddingBottom,this.groupPaddingLeft)};
+mxCompactTreeLayout.prototype.localEdgeProcessing=function(a){this.processNodeOutgoing(a);for(a=a.child;null!=a;)this.localEdgeProcessing(a),a=a.next};
+mxCompactTreeLayout.prototype.processNodeOutgoing=function(a){for(var b=a.child,c=a.cell,d=0,e=[];null!=b;){d++;var f=b.x;this.horizontal&&(f=b.y);e.push(new WeightedCellSorter(b,f));b=b.next}e.sort(WeightedCellSorter.prototype.compare);var f=a.width,g=(d+1)*this.prefHozEdgeSep;f>g+2*this.prefHozEdgeSep&&(f-=2*this.prefHozEdgeSep);a=f/d;b=a/2;f>g+2*this.prefHozEdgeSep&&(b+=this.prefHozEdgeSep);for(var f=this.minEdgeJetty-this.prefVertEdgeOff,g=this.getVertexBounds(c),k=0;k<e.length;k++){ [...]
+e[k].cell.cell,m=this.getVertexBounds(l),l=this.graph.getEdgesBetween(c,l,!1),n=[],p,q,r=0;r<l.length;r++)this.horizontal?(p=g.x+g.width,q=g.y+b,n.push(new mxPoint(p,q)),p=g.x+g.width+f,n.push(new mxPoint(p,q)),q=m.y+m.height/2):(p=g.x+b,q=g.y+g.height,n.push(new mxPoint(p,q)),q=g.y+g.height+f,n.push(new mxPoint(p,q)),p=m.x+m.width/2),n.push(new mxPoint(p,q)),this.setEdgePoints(l[r],n);k<d/2?f+=this.prefVertEdgeOff:k>d/2&&(f-=this.prefVertEdgeOff);b+=a}};
+function WeightedCellSorter(a,b){this.cell=a;this.weightedValue=b}WeightedCellSorter.prototype.weightedValue=0;WeightedCellSorter.prototype.nudge=!1;WeightedCellSorter.prototype.visited=!1;WeightedCellSorter.prototype.rankIndex=null;WeightedCellSorter.prototype.cell=null;WeightedCellSorter.prototype.compare=function(a,b){return null!=a&&null!=b?b.weightedValue>a.weightedValue?1:b.weightedValue<a.weightedValue?-1:b.nudge?1:-1:0};function mxRadialTreeLayout(a){mxCompactTreeLayout.call(this,a,!1)}
+mxUtils.extend(mxRadialTreeLayout,mxCompactTreeLayout);mxRadialTreeLayout.prototype.angleOffset=.5;mxRadialTreeLayout.prototype.rootx=0;mxRadialTreeLayout.prototype.rooty=0;mxRadialTreeLayout.prototype.levelDistance=120;mxRadialTreeLayout.prototype.nodeDistance=10;mxRadialTreeLayout.prototype.autoRadius=!1;mxRadialTreeLayout.prototype.sortEdges=!1;mxRadialTreeLayout.prototype.rowMinX=[];mxRadialTreeLayout.prototype.rowMaxX=[];mxRadialTreeLayout.prototype.rowMinCenX=[];
+mxRadialTreeLayout.prototype.rowMaxCenX=[];mxRadialTreeLayout.prototype.rowRadi=[];mxRadialTreeLayout.prototype.row=[];mxRadialTreeLayout.prototype.isVertexIgnored=function(a){return mxGraphLayout.prototype.isVertexIgnored.apply(this,arguments)||0==this.graph.getConnections(a).length};
+mxRadialTreeLayout.prototype.execute=function(a,b){this.parent=a;this.edgeRouting=this.useBoundingBox=!1;mxCompactTreeLayout.prototype.execute.apply(this,arguments);var c=null,d=this.getVertexBounds(this.root);this.centerX=d.x+d.width/2;this.centerY=d.y+d.height/2;for(var e in this.visited){var f=this.getVertexBounds(this.visited[e]),c=null!=c?c:f.clone();c.add(f)}this.calcRowDims([this.node],0);for(var g=0,k=0,c=0;c<this.row.length;c++)e=(this.rowMaxX[c]-this.centerX-this.nodeDistance)/ [...]
+g=Math.max(g,(this.centerX-this.rowMinX[c]-this.nodeDistance)/this.rowRadi[c]),k=Math.max(k,e);for(c=0;c<this.row.length;c++){var l=this.centerX-this.nodeDistance-g*this.rowRadi[c],m=this.centerX+this.nodeDistance+k*this.rowRadi[c]-l;for(e=0;e<this.row[c].length;e++)f=this.row[c],d=f[e],f=this.getVertexBounds(d.cell),d.theta=(f.x+f.width/2-l)/m*Math.PI*2}for(c=this.row.length-2;0<=c;c--)for(f=this.row[c],e=0;e<f.length;e++){d=f[e];g=d.child;for(l=k=0;null!=g;)l+=g.theta,k++,g=g.next;0<k& [...]
+d.theta&&e<f.length-1?d.theta=Math.min(g,f[e+1].theta-Math.PI/10):g<d.theta&&0<e&&(d.theta=Math.max(g,f[e-1].theta+Math.PI/10)))}for(c=0;c<this.row.length;c++)for(e=0;e<this.row[c].length;e++)f=this.row[c],d=f[e],f=this.getVertexBounds(d.cell),this.setVertexLocation(d.cell,this.centerX-f.width/2+this.rowRadi[c]*Math.cos(d.theta),this.centerY-f.height/2+this.rowRadi[c]*Math.sin(d.theta))};
+mxRadialTreeLayout.prototype.calcRowDims=function(a,b){if(null!=a&&0!=a.length){this.rowMinX[b]=this.centerX;this.rowMaxX[b]=this.centerX;this.rowMinCenX[b]=this.centerX;this.rowMaxCenX[b]=this.centerX;this.row[b]=[];for(var c=!1,d=0;d<a.length;d++)for(var e=null!=a[d]?a[d].child:null;null!=e;)vertexBounds=this.getVertexBounds(e.cell),this.rowMinX[b]=Math.min(vertexBounds.x,this.rowMinX[b]),this.rowMaxX[b]=Math.max(vertexBounds.x+vertexBounds.width,this.rowMaxX[b]),this.rowMinCenX[b]=Mat [...]
+vertexBounds.width/2,this.rowMinCenX[b]),this.rowMaxCenX[b]=Math.max(vertexBounds.x+vertexBounds.width/2,this.rowMaxCenX[b]),this.rowRadi[b]=vertexBounds.y-this.getVertexBounds(this.root).y,null!=e.child&&(c=!0),this.row[b].push(e),e=e.next;c&&this.calcRowDims(this.row[b],b+1)}};function mxFastOrganicLayout(a){mxGraphLayout.call(this,a)}mxFastOrganicLayout.prototype=new mxGraphLayout;mxFastOrganicLayout.prototype.constructor=mxFastOrganicLayout;mxFastOrganicLayout.prototype.useInputOrigin=!0;
+mxFastOrganicLayout.prototype.resetEdges=!0;mxFastOrganicLayout.prototype.disableEdgeStyle=!0;mxFastOrganicLayout.prototype.forceConstant=50;mxFastOrganicLayout.prototype.forceConstantSquared=0;mxFastOrganicLayout.prototype.minDistanceLimit=2;mxFastOrganicLayout.prototype.maxDistanceLimit=500;mxFastOrganicLayout.prototype.minDistanceLimitSquared=4;mxFastOrganicLayout.prototype.initialTemp=200;mxFastOrganicLayout.prototype.temperature=0;mxFastOrganicLayout.prototype.maxIterations=0;
+mxFastOrganicLayout.prototype.iteration=0;mxFastOrganicLayout.prototype.allowedToRun=!0;mxFastOrganicLayout.prototype.isVertexIgnored=function(a){return mxGraphLayout.prototype.isVertexIgnored.apply(this,arguments)||0==this.graph.getConnections(a).length};
+mxFastOrganicLayout.prototype.execute=function(a){var b=this.graph.getModel();this.vertexArray=[];for(var c=this.graph.getChildVertices(a),d=0;d<c.length;d++)this.isVertexIgnored(c[d])||this.vertexArray.push(c[d]);var e=this.useInputOrigin?this.graph.getBoundingBoxFromGeometry(this.vertexArray):null,f=this.vertexArray.length;this.indices=[];this.dispX=[];this.dispY=[];this.cellLocation=[];this.isMoveable=[];this.neighbours=[];this.radius=[];this.radiusSquared=[];.001>this.forceConstant&& [...]
+.001);this.forceConstantSquared=this.forceConstant*this.forceConstant;for(d=0;d<this.vertexArray.length;d++){var g=this.vertexArray[d];this.cellLocation[d]=[];var k=mxObjectIdentity.get(g);this.indices[k]=d;var l=this.getVertexBounds(g),m=l.width,n=l.height,p=l.x,q=l.y;this.cellLocation[d][0]=p+m/2;this.cellLocation[d][1]=q+n/2;this.radius[d]=Math.min(m,n);this.radiusSquared[d]=this.radius[d]*this.radius[d]}b.beginUpdate();try{for(d=0;d<f;d++){this.dispX[d]=0;this.dispY[d]=0;this.isMovea [...]
+var r=this.graph.getConnections(this.vertexArray[d],a),c=this.graph.getOpposites(r,this.vertexArray[d]);this.neighbours[d]=[];for(m=0;m<c.length;m++){this.resetEdges&&this.graph.resetEdge(r[m]);this.disableEdgeStyle&&this.setEdgeStyleEnabled(r[m],!1);var k=mxObjectIdentity.get(c[m]),t=this.indices[k];this.neighbours[d][m]=null!=t?t:d}}this.temperature=this.initialTemp;0==this.maxIterations&&(this.maxIterations=20*Math.sqrt(f));for(this.iteration=0;this.iteration<this.maxIterations;this.i [...]
+this.calcRepulsion();this.calcAttraction();this.calcPositions();this.reduceTemperature()}a=c=null;for(d=0;d<this.vertexArray.length;d++)g=this.vertexArray[d],this.isVertexMovable(g)&&(l=this.getVertexBounds(g),null!=l&&(this.cellLocation[d][0]-=l.width/2,this.cellLocation[d][1]-=l.height/2,p=this.graph.snap(this.cellLocation[d][0]),q=this.graph.snap(this.cellLocation[d][1]),this.setVertexLocation(g,p,q),c=null==c?p:Math.min(c,p),a=null==a?q:Math.min(a,q)));d=-(c||0)+1;g=-(a||0)+1;null!=e [...]
+g+=e.y);this.graph.moveCells(this.vertexArray,d,g)}finally{b.endUpdate()}};mxFastOrganicLayout.prototype.calcPositions=function(){for(var a=0;a<this.vertexArray.length;a++)if(this.isMoveable[a]){var b=Math.sqrt(this.dispX[a]*this.dispX[a]+this.dispY[a]*this.dispY[a]);.001>b&&(b=.001);var c=this.dispX[a]/b*Math.min(b,this.temperature),b=this.dispY[a]/b*Math.min(b,this.temperature);this.dispX[a]=0;this.dispY[a]=0;this.cellLocation[a][0]+=c;this.cellLocation[a][1]+=b}};
+mxFastOrganicLayout.prototype.calcAttraction=function(){for(var a=0;a<this.vertexArray.length;a++)for(var b=0;b<this.neighbours[a].length;b++){var c=this.neighbours[a][b];if(a!=c&&this.isMoveable[a]&&this.isMoveable[c]){var d=this.cellLocation[a][0]-this.cellLocation[c][0],e=this.cellLocation[a][1]-this.cellLocation[c][1],f=d*d+e*e-this.radiusSquared[a]-this.radiusSquared[c];f<this.minDistanceLimitSquared&&(f=this.minDistanceLimitSquared);var g=Math.sqrt(f),f=f/this.forceConstant,d=d/g*f [...]
+this.dispX[a]-=d;this.dispY[a]-=e;this.dispX[c]+=d;this.dispY[c]+=e}}};
+mxFastOrganicLayout.prototype.calcRepulsion=function(){for(var a=this.vertexArray.length,b=0;b<a;b++)for(var c=b;c<a;c++){if(!this.allowedToRun)return;if(c!=b&&this.isMoveable[b]&&this.isMoveable[c]){var d=this.cellLocation[b][0]-this.cellLocation[c][0],e=this.cellLocation[b][1]-this.cellLocation[c][1];0==d&&(d=.01+Math.random());0==e&&(e=.01+Math.random());var f=Math.sqrt(d*d+e*e),g=f-this.radius[b]-this.radius[c];g>this.maxDistanceLimit||(g<this.minDistanceLimit&&(g=this.minDistanceLim [...]
+g,d=d/f*g,e=e/f*g,this.dispX[b]+=d,this.dispY[b]+=e,this.dispX[c]-=d,this.dispY[c]-=e)}}};mxFastOrganicLayout.prototype.reduceTemperature=function(){this.temperature=this.initialTemp*(1-this.iteration/this.maxIterations)};function mxCircleLayout(a,b){mxGraphLayout.call(this,a);this.radius=null!=b?b:100}mxCircleLayout.prototype=new mxGraphLayout;mxCircleLayout.prototype.constructor=mxCircleLayout;mxCircleLayout.prototype.radius=null;mxCircleLayout.prototype.moveCircle=!1;
+mxCircleLayout.prototype.x0=0;mxCircleLayout.prototype.y0=0;mxCircleLayout.prototype.resetEdges=!0;mxCircleLayout.prototype.disableEdgeStyle=!0;
+mxCircleLayout.prototype.execute=function(a){var b=this.graph.getModel();b.beginUpdate();try{for(var c=0,d=null,e=null,f=[],g=b.getChildCount(a),k=0;k<g;k++){var l=b.getChildAt(a,k);if(this.isVertexIgnored(l))this.isEdgeIgnored(l)||(this.resetEdges&&this.graph.resetEdge(l),this.disableEdgeStyle&&this.setEdgeStyleEnabled(l,!1));else{f.push(l);var m=this.getVertexBounds(l),d=null==d?m.y:Math.min(d,m.y),e=null==e?m.x:Math.min(e,m.x),c=Math.max(c,Math.max(m.width,m.height))}}var n=this.getRa [...]
+c);this.moveCircle&&(e=this.x0,d=this.y0);this.circle(f,n,e,d)}finally{b.endUpdate()}};mxCircleLayout.prototype.getRadius=function(a,b){return Math.max(a*b/Math.PI,this.radius)};mxCircleLayout.prototype.circle=function(a,b,c,d){for(var e=a.length,f=2*Math.PI/e,g=0;g<e;g++)this.isVertexMovable(a[g])&&this.setVertexLocation(a[g],c+b+b*Math.sin(g*f),d+b+b*Math.cos(g*f))};function mxParallelEdgeLayout(a){mxGraphLayout.call(this,a)}mxParallelEdgeLayout.prototype=new mxGraphLayout;
+mxParallelEdgeLayout.prototype.constructor=mxParallelEdgeLayout;mxParallelEdgeLayout.prototype.spacing=20;mxParallelEdgeLayout.prototype.execute=function(a){a=this.findParallels(a);this.graph.model.beginUpdate();try{for(var b in a){var c=a[b];1<c.length&&this.layout(c)}}finally{this.graph.model.endUpdate()}};
+mxParallelEdgeLayout.prototype.findParallels=function(a){for(var b=this.graph.getModel(),c=[],d=b.getChildCount(a),e=0;e<d;e++){var f=b.getChildAt(a,e);if(!this.isEdgeIgnored(f)){var g=this.getEdgeId(f);null!=g&&(null==c[g]&&(c[g]=[]),c[g].push(f))}}return c};mxParallelEdgeLayout.prototype.getEdgeId=function(a){var b=this.graph.getView(),c=b.getVisibleTerminal(a,!0);a=b.getVisibleTerminal(a,!1);return null!=c&&null!=a?(c=mxObjectIdentity.get(c),a=mxObjectIdentity.get(a),c>a?a+"-"+c:c+"-" [...]
+mxParallelEdgeLayout.prototype.layout=function(a){var b=a[0],c=this.graph.getView(),d=this.graph.getModel(),e=d.getGeometry(c.getVisibleTerminal(b,!0)),d=d.getGeometry(c.getVisibleTerminal(b,!1));if(e==d)for(var b=e.x+e.width+this.spacing,c=e.y+e.height/2,f=0;f<a.length;f++)this.route(a[f],b,c),b+=this.spacing;else if(null!=e&&null!=d){var b=e.x+e.width/2,c=e.y+e.height/2,f=d.x+d.width/2-b,g=d.y+d.height/2-c,d=Math.sqrt(f*f+g*g);if(0<d)for(e=g*this.spacing/d,d=f*this.spacing/d,b=b+f/2+e* [...]
+1)/2,c=c+g/2-d*(a.length-1)/2,f=0;f<a.length;f++)this.route(a[f],b,c),b-=e,c+=d}};mxParallelEdgeLayout.prototype.route=function(a,b,c){this.graph.isCellMovable(a)&&this.setEdgePoints(a,[new mxPoint(b,c)])};function mxCompositeLayout(a,b,c){mxGraphLayout.call(this,a);this.layouts=b;this.master=c}mxCompositeLayout.prototype=new mxGraphLayout;mxCompositeLayout.prototype.constructor=mxCompositeLayout;mxCompositeLayout.prototype.layouts=null;mxCompositeLayout.prototype.master=null;
+mxCompositeLayout.prototype.moveCell=function(a,b,c){null!=this.master?this.master.move.apply(this.master,arguments):this.layouts[0].move.apply(this.layouts[0],arguments)};mxCompositeLayout.prototype.execute=function(a){var b=this.graph.getModel();b.beginUpdate();try{for(var c=0;c<this.layouts.length;c++)this.layouts[c].execute.apply(this.layouts[c],arguments)}finally{b.endUpdate()}};function mxEdgeLabelLayout(a,b){mxGraphLayout.call(this,a)}mxEdgeLabelLayout.prototype=new mxGraphLayout;
+mxEdgeLabelLayout.prototype.constructor=mxEdgeLabelLayout;mxEdgeLabelLayout.prototype.execute=function(a){for(var b=this.graph.view,c=this.graph.getModel(),d=[],e=[],f=c.getChildCount(a),g=0;g<f;g++){var k=c.getChildAt(a,g),l=b.getState(k);null!=l&&(this.isVertexIgnored(k)?this.isEdgeIgnored(k)||d.push(l):e.push(l))}this.placeLabels(e,d)};
+mxEdgeLabelLayout.prototype.placeLabels=function(a,b){var c=this.graph.getModel();c.beginUpdate();try{for(var d=0;d<b.length;d++){var e=b[d];if(null!=e&&null!=e.text&&null!=e.text.boundingBox)for(var f=0;f<a.length;f++){var g=a[f];null!=g&&this.avoid(e,g)}}}finally{c.endUpdate()}};
+mxEdgeLabelLayout.prototype.avoid=function(a,b){var c=this.graph.getModel(),d=a.text.boundingBox;if(mxUtils.intersects(d,b)){var e=-d.y-d.height+b.y,f=-d.y+b.y+b.height,e=Math.abs(e)<Math.abs(f)?e:f,f=-d.x-d.width+b.x,d=-d.x+b.x+b.width,d=Math.abs(f)<Math.abs(d)?f:d;Math.abs(d)<Math.abs(e)?e=0:d=0;f=c.getGeometry(a.cell);null!=f&&(f=f.clone(),null!=f.offset?(f.offset.x+=d,f.offset.y+=e):f.offset=new mxPoint(d,e),c.setGeometry(a.cell,f))}};
+function mxGraphAbstractHierarchyCell(){this.x=[];this.y=[];this.temp=[]}mxGraphAbstractHierarchyCell.prototype.maxRank=-1;mxGraphAbstractHierarchyCell.prototype.minRank=-1;mxGraphAbstractHierarchyCell.prototype.x=null;mxGraphAbstractHierarchyCell.prototype.y=null;mxGraphAbstractHierarchyCell.prototype.width=0;mxGraphAbstractHierarchyCell.prototype.height=0;mxGraphAbstractHierarchyCell.prototype.nextLayerConnectedCells=null;mxGraphAbstractHierarchyCell.prototype.previousLayerConnectedCel [...]
+mxGraphAbstractHierarchyCell.prototype.temp=null;mxGraphAbstractHierarchyCell.prototype.getNextLayerConnectedCells=function(a){return null};mxGraphAbstractHierarchyCell.prototype.getPreviousLayerConnectedCells=function(a){return null};mxGraphAbstractHierarchyCell.prototype.isEdge=function(){return!1};mxGraphAbstractHierarchyCell.prototype.isVertex=function(){return!1};mxGraphAbstractHierarchyCell.prototype.getGeneralPurposeVariable=function(a){return null};
+mxGraphAbstractHierarchyCell.prototype.setGeneralPurposeVariable=function(a,b){return null};mxGraphAbstractHierarchyCell.prototype.setX=function(a,b){this.isVertex()?this.x[0]=b:this.isEdge()&&(this.x[a-this.minRank-1]=b)};mxGraphAbstractHierarchyCell.prototype.getX=function(a){return this.isVertex()?this.x[0]:this.isEdge()?this.x[a-this.minRank-1]:0};mxGraphAbstractHierarchyCell.prototype.setY=function(a,b){this.isVertex()?this.y[0]=b:this.isEdge()&&(this.y[a-this.minRank-1]=b)};
+function mxGraphHierarchyNode(a){mxGraphAbstractHierarchyCell.apply(this,arguments);this.cell=a;this.id=mxObjectIdentity.get(a);this.connectsAsTarget=[];this.connectsAsSource=[]}mxGraphHierarchyNode.prototype=new mxGraphAbstractHierarchyCell;mxGraphHierarchyNode.prototype.constructor=mxGraphHierarchyNode;mxGraphHierarchyNode.prototype.cell=null;mxGraphHierarchyNode.prototype.id=null;mxGraphHierarchyNode.prototype.connectsAsTarget=null;mxGraphHierarchyNode.prototype.connectsAsSource=null;
+mxGraphHierarchyNode.prototype.hashCode=!1;mxGraphHierarchyNode.prototype.getRankValue=function(a){return this.maxRank};mxGraphHierarchyNode.prototype.getNextLayerConnectedCells=function(a){if(null==this.nextLayerConnectedCells){this.nextLayerConnectedCells=[];this.nextLayerConnectedCells[0]=[];for(var b=0;b<this.connectsAsTarget.length;b++){var c=this.connectsAsTarget[b];-1==c.maxRank||c.maxRank==a+1?this.nextLayerConnectedCells[0].push(c.source):this.nextLayerConnectedCells[0].push(c)} [...]
+mxGraphHierarchyNode.prototype.getPreviousLayerConnectedCells=function(a){if(null==this.previousLayerConnectedCells){this.previousLayerConnectedCells=[];this.previousLayerConnectedCells[0]=[];for(var b=0;b<this.connectsAsSource.length;b++){var c=this.connectsAsSource[b];-1==c.minRank||c.minRank==a-1?this.previousLayerConnectedCells[0].push(c.target):this.previousLayerConnectedCells[0].push(c)}}return this.previousLayerConnectedCells[0]};mxGraphHierarchyNode.prototype.isVertex=function(){ [...]
+mxGraphHierarchyNode.prototype.getGeneralPurposeVariable=function(a){return this.temp[0]};mxGraphHierarchyNode.prototype.setGeneralPurposeVariable=function(a,b){this.temp[0]=b};mxGraphHierarchyNode.prototype.isAncestor=function(a){if(null!=a&&null!=this.hashCode&&null!=a.hashCode&&this.hashCode.length<a.hashCode.length){if(this.hashCode==a.hashCode)return!0;if(null==this.hashCode||null==this.hashCode)return!1;for(var b=0;b<this.hashCode.length;b++)if(this.hashCode[b]!=a.hashCode[b])retur [...]
+mxGraphHierarchyNode.prototype.getCoreCell=function(){return this.cell};function mxGraphHierarchyEdge(a){mxGraphAbstractHierarchyCell.apply(this,arguments);this.edges=a;this.ids=[];for(var b=0;b<a.length;b++)this.ids.push(mxObjectIdentity.get(a[b]))}mxGraphHierarchyEdge.prototype=new mxGraphAbstractHierarchyCell;mxGraphHierarchyEdge.prototype.constructor=mxGraphHierarchyEdge;mxGraphHierarchyEdge.prototype.edges=null;mxGraphHierarchyEdge.prototype.ids=null;mxGraphHierarchyEdge.prototype.s [...]
+mxGraphHierarchyEdge.prototype.target=null;mxGraphHierarchyEdge.prototype.isReversed=!1;mxGraphHierarchyEdge.prototype.invert=function(a){a=this.source;this.source=this.target;this.target=a;this.isReversed=!this.isReversed};
+mxGraphHierarchyEdge.prototype.getNextLayerConnectedCells=function(a){if(null==this.nextLayerConnectedCells){this.nextLayerConnectedCells=[];for(var b=0;b<this.temp.length;b++)this.nextLayerConnectedCells[b]=[],b==this.temp.length-1?this.nextLayerConnectedCells[b].push(this.source):this.nextLayerConnectedCells[b].push(this)}return this.nextLayerConnectedCells[a-this.minRank-1]};
+mxGraphHierarchyEdge.prototype.getPreviousLayerConnectedCells=function(a){if(null==this.previousLayerConnectedCells){this.previousLayerConnectedCells=[];for(var b=0;b<this.temp.length;b++)this.previousLayerConnectedCells[b]=[],0==b?this.previousLayerConnectedCells[b].push(this.target):this.previousLayerConnectedCells[b].push(this)}return this.previousLayerConnectedCells[a-this.minRank-1]};mxGraphHierarchyEdge.prototype.isEdge=function(){return!0};
+mxGraphHierarchyEdge.prototype.getGeneralPurposeVariable=function(a){return this.temp[a-this.minRank-1]};mxGraphHierarchyEdge.prototype.setGeneralPurposeVariable=function(a,b){this.temp[a-this.minRank-1]=b};mxGraphHierarchyEdge.prototype.getCoreCell=function(){return null!=this.edges&&0<this.edges.length?this.edges[0]:null};
+function mxGraphHierarchyModel(a,b,c,d,e){a.getGraph();this.tightenToSource=e;this.roots=c;this.parent=d;this.vertexMapper=new mxDictionary;this.edgeMapper=new mxDictionary;this.maxRank=0;c=[];null==b&&(b=this.graph.getChildVertices(d));this.maxRank=this.SOURCESCANSTARTRANK;this.createInternalCells(a,b,c);for(d=0;d<b.length;d++){e=c[d].connectsAsSource;for(var f=0;f<e.length;f++){var g=e[f],k=g.edges;if(null!=k&&0<k.length){var k=k[0],l=a.getVisibleTerminal(k,!1),l=this.vertexMapper.get( [...]
+l&&(l=a.getVisibleTerminal(k,!0),l=this.vertexMapper.get(l));null!=l&&c[d]!=l&&(g.target=l,0==l.connectsAsTarget.length&&(l.connectsAsTarget=[]),0>mxUtils.indexOf(l.connectsAsTarget,g)&&l.connectsAsTarget.push(g))}}c[d].temp[0]=1}}mxGraphHierarchyModel.prototype.maxRank=null;mxGraphHierarchyModel.prototype.vertexMapper=null;mxGraphHierarchyModel.prototype.edgeMapper=null;mxGraphHierarchyModel.prototype.ranks=null;mxGraphHierarchyModel.prototype.roots=null;mxGraphHierarchyModel.prototype. [...]
+mxGraphHierarchyModel.prototype.dfsCount=0;mxGraphHierarchyModel.prototype.SOURCESCANSTARTRANK=1E8;mxGraphHierarchyModel.prototype.tightenToSource=!1;
+mxGraphHierarchyModel.prototype.createInternalCells=function(a,b,c){for(var d=a.getGraph(),e=0;e<b.length;e++){c[e]=new mxGraphHierarchyNode(b[e]);this.vertexMapper.put(b[e],c[e]);var f=a.getEdges(b[e]);c[e].connectsAsSource=[];for(var g=0;g<f.length;g++){var k=a.getVisibleTerminal(f[g],!1);if(k!=b[e]&&a.graph.model.isVertex(k)&&!a.isVertexIgnored(k)){var l=a.getEdgesBetween(b[e],k,!1),k=a.getEdgesBetween(b[e],k,!0);if(null!=l&&0<l.length&&null==this.edgeMapper.get(l[0])&&2*k.length>=l.l [...]
+new mxGraphHierarchyEdge(l),m=0;m<l.length;m++){var n=l[m];this.edgeMapper.put(n,k);d.resetEdge(n);a.disableEdgeStyle&&(a.setEdgeStyleEnabled(n,!1),a.setOrthogonalEdge(n,!0))}k.source=c[e];0>mxUtils.indexOf(c[e].connectsAsSource,k)&&c[e].connectsAsSource.push(k)}}}c[e].temp[0]=0}};
+mxGraphHierarchyModel.prototype.initialRank=function(){var a=[];if(null!=this.roots)for(var b=0;b<this.roots.length;b++){var c=this.vertexMapper.get(this.roots[b]);null!=c&&a.push(c)}for(var d=this.vertexMapper.getValues(),b=0;b<d.length;b++)d[b].temp[0]=-1;for(var e=a.slice();0<a.length;){var c=a[0],f,g;f=c.connectsAsTarget;g=c.connectsAsSource;for(var k=!0,l=this.SOURCESCANSTARTRANK,b=0;b<f.length;b++){var m=f[b];if(5270620==m.temp[0])m=m.source,l=Math.min(l,m.temp[0]-1);else{k=!1;brea [...]
+l;this.maxRank=Math.min(this.maxRank,l);if(null!=g)for(b=0;b<g.length;b++)m=g[b],m.temp[0]=5270620,m=m.target,-1==m.temp[0]&&(a.push(m),m.temp[0]=-2);a.shift()}else if(b=a.shift(),a.push(c),b==c&&1==a.length)break}for(b=0;b<d.length;b++)d[b].temp[0]-=this.maxRank;for(b=0;b<e.length;b++)for(c=e[b],a=0,f=c.connectsAsSource,d=0;d<f.length;d++)m=f[d],m=m.target,c.temp[0]=Math.max(a,m.temp[0]+1),a=c.temp[0];this.maxRank=this.SOURCESCANSTARTRANK-this.maxRank};
+mxGraphHierarchyModel.prototype.fixRanks=function(){var a=[];this.ranks=[];for(var b=0;b<this.maxRank+1;b++)a[b]=[],this.ranks[b]=a[b];var c=null;if(null!=this.roots)for(var d=this.roots,c=[],b=0;b<d.length;b++){var e=this.vertexMapper.get(d[b]);c[b]=e}this.visit(function(b,c,d,e,m){0==m&&0>c.maxRank&&0>c.minRank&&(a[c.temp[0]].push(c),c.maxRank=c.temp[0],c.minRank=c.temp[0],c.temp[0]=a[c.maxRank].length-1);if(null!=b&&null!=d&&1<b.maxRank-c.maxRank)for(d.maxRank=b.maxRank,d.minRank=c.ma [...]
+[],d.x=[],d.y=[],b=d.minRank+1;b<d.maxRank;b++)a[b].push(d),d.setGeneralPurposeVariable(b,a[b].length-1)},c,!1,null)};mxGraphHierarchyModel.prototype.visit=function(a,b,c,d){if(null!=b){for(var e=0;e<b.length;e++){var f=b[e];null!=f&&(null==d&&(d={}),c?(f.hashCode=[],f.hashCode[0]=this.dfsCount,f.hashCode[1]=e,this.extendedDfs(null,f,null,a,d,f.hashCode,e,0)):this.dfs(null,f,null,a,d,0))}this.dfsCount++}};
+mxGraphHierarchyModel.prototype.dfs=function(a,b,c,d,e,f){if(null!=b){var g=b.id;if(null==e[g])for(e[g]=b,d(a,b,c,f,0),a=b.connectsAsSource.slice(),c=0;c<a.length;c++)g=a[c],this.dfs(b,g.target,g,d,e,f+1);else d(a,b,c,f,1)}};
+mxGraphHierarchyModel.prototype.extendedDfs=function(a,b,c,d,e,f,g,k){if(null!=b)if(null==a||null!=b.hashCode&&b.hashCode[0]==a.hashCode[0]||(f=a.hashCode.length+1,b.hashCode=a.hashCode.slice(),b.hashCode[f-1]=g),g=b.id,null==e[g])for(e[g]=b,d(a,b,c,k,0),a=b.connectsAsSource.slice(),c=0;c<a.length;c++)g=a[c],this.extendedDfs(b,g.target,g,d,e,b.hashCode,c,k+1);else d(a,b,c,k,1)};
+function mxSwimlaneModel(a,b,c,d,e){a.getGraph();this.tightenToSource=e;this.roots=c;this.parent=d;this.vertexMapper=new mxDictionary;this.edgeMapper=new mxDictionary;this.maxRank=0;c=[];null==b&&(b=this.graph.getChildVertices(d));this.maxRank=this.SOURCESCANSTARTRANK;this.createInternalCells(a,b,c);for(d=0;d<b.length;d++){e=c[d].connectsAsSource;for(var f=0;f<e.length;f++){var g=e[f],k=g.edges;if(null!=k&&0<k.length){var k=k[0],l=a.getVisibleTerminal(k,!1),l=this.vertexMapper.get(l);c[d [...]
+a.getVisibleTerminal(k,!0),l=this.vertexMapper.get(l));null!=l&&c[d]!=l&&(g.target=l,0==l.connectsAsTarget.length&&(l.connectsAsTarget=[]),0>mxUtils.indexOf(l.connectsAsTarget,g)&&l.connectsAsTarget.push(g))}}c[d].temp[0]=1}}mxSwimlaneModel.prototype.maxRank=null;mxSwimlaneModel.prototype.vertexMapper=null;mxSwimlaneModel.prototype.edgeMapper=null;mxSwimlaneModel.prototype.ranks=null;mxSwimlaneModel.prototype.roots=null;mxSwimlaneModel.prototype.parent=null;mxSwimlaneModel.prototype.dfsCount=0;
+mxSwimlaneModel.prototype.SOURCESCANSTARTRANK=1E8;mxGraphHierarchyModel.prototype.tightenToSource=!1;mxSwimlaneModel.prototype.ranksPerGroup=null;
+mxSwimlaneModel.prototype.createInternalCells=function(a,b,c){for(var d=a.getGraph(),e=a.swimlanes,f=0;f<b.length;f++){c[f]=new mxGraphHierarchyNode(b[f]);this.vertexMapper.put(b[f],c[f]);c[f].swimlaneIndex=-1;for(var g=0;g<e.length;g++)if(d.model.getParent(b[f])==e[g]){c[f].swimlaneIndex=g;break}g=a.getEdges(b[f]);c[f].connectsAsSource=[];for(var k=0;k<g.length;k++){var l=a.getVisibleTerminal(g[k],!1);if(l!=b[f]&&a.graph.model.isVertex(l)&&!a.isVertexIgnored(l)){var m=a.getEdgesBetween( [...]
+l=a.getEdgesBetween(b[f],l,!0);if(null!=m&&0<m.length&&null==this.edgeMapper.get(m[0])&&2*l.length>=m.length){for(var l=new mxGraphHierarchyEdge(m),n=0;n<m.length;n++){var p=m[n];this.edgeMapper.put(p,l);d.resetEdge(p);a.disableEdgeStyle&&(a.setEdgeStyleEnabled(p,!1),a.setOrthogonalEdge(p,!0))}l.source=c[f];0>mxUtils.indexOf(c[f].connectsAsSource,l)&&c[f].connectsAsSource.push(l)}}}c[f].temp[0]=0}};
+mxSwimlaneModel.prototype.initialRank=function(){this.ranksPerGroup=[];var a=[],b={};if(null!=this.roots)for(var c=0;c<this.roots.length;c++){var d=this.vertexMapper.get(this.roots[c]);this.maxChainDfs(null,d,null,b,0);null!=d&&a.push(d)}d=[];b=[];for(c=this.ranksPerGroup.length-1;0<=c;c--)d[c]=c==this.ranksPerGroup.length-1?0:b[c+1]+1,b[c]=d[c]+this.ranksPerGroup[c];this.maxRank=b[0];d=this.vertexMapper.getValues();for(c=0;c<d.length;c++)d[c].temp[0]=-1;for(a.slice();0<a.length;){var d= [...]
+e=d.connectsAsTarget;f=d.connectsAsSource;for(var g=!0,k=b[0],c=0;c<e.length;c++){var l=e[c];if(5270620==l.temp[0])l=l.source,k=Math.min(k,l.temp[0]-1);else{g=!1;break}}if(g){k>b[d.swimlaneIndex]&&(k=b[d.swimlaneIndex]);d.temp[0]=k;if(null!=f)for(c=0;c<f.length;c++)l=f[c],l.temp[0]=5270620,l=l.target,-1==l.temp[0]&&(a.push(l),l.temp[0]=-2);a.shift()}else if(c=a.shift(),a.push(d),c==d&&1==a.length)break}};
+mxSwimlaneModel.prototype.maxChainDfs=function(a,b,c,d,e){if(null!=b&&(a=mxCellPath.create(b.cell),null==d[a])){d[a]=b;a=b.swimlaneIndex;if(null==this.ranksPerGroup[a]||this.ranksPerGroup[a]<e)this.ranksPerGroup[a]=e;a=b.connectsAsSource.slice();for(c=0;c<a.length;c++){var f=a[c],g=f.target;b.swimlaneIndex<g.swimlaneIndex?this.maxChainDfs(b,g,f,mxUtils.clone(d,null,!0),0):b.swimlaneIndex==g.swimlaneIndex&&this.maxChainDfs(b,g,f,mxUtils.clone(d,null,!0),e+1)}}};
+mxSwimlaneModel.prototype.fixRanks=function(){var a=[];this.ranks=[];for(var b=0;b<this.maxRank+1;b++)a[b]=[],this.ranks[b]=a[b];var c=null;if(null!=this.roots)for(var d=this.roots,c=[],b=0;b<d.length;b++){var e=this.vertexMapper.get(d[b]);c[b]=e}this.visit(function(b,c,d,e,m){0==m&&0>c.maxRank&&0>c.minRank&&(a[c.temp[0]].push(c),c.maxRank=c.temp[0],c.minRank=c.temp[0],c.temp[0]=a[c.maxRank].length-1);if(null!=b&&null!=d&&1<b.maxRank-c.maxRank)for(d.maxRank=b.maxRank,d.minRank=c.maxRank, [...]
+d.x=[],d.y=[],b=d.minRank+1;b<d.maxRank;b++)a[b].push(d),d.setGeneralPurposeVariable(b,a[b].length-1)},c,!1,null)};mxSwimlaneModel.prototype.visit=function(a,b,c,d){if(null!=b){for(var e=0;e<b.length;e++){var f=b[e];null!=f&&(null==d&&(d={}),c?(f.hashCode=[],f.hashCode[0]=this.dfsCount,f.hashCode[1]=e,this.extendedDfs(null,f,null,a,d,f.hashCode,e,0)):this.dfs(null,f,null,a,d,0))}this.dfsCount++}};
+mxSwimlaneModel.prototype.dfs=function(a,b,c,d,e,f){if(null!=b){var g=b.id;if(null==e[g])for(e[g]=b,d(a,b,c,f,0),a=b.connectsAsSource.slice(),c=0;c<a.length;c++)g=a[c],this.dfs(b,g.target,g,d,e,f+1);else d(a,b,c,f,1)}};
+mxSwimlaneModel.prototype.extendedDfs=function(a,b,c,d,e,f,g,k){if(null!=b)if(null==a||null!=b.hashCode&&b.hashCode[0]==a.hashCode[0]||(f=a.hashCode.length+1,b.hashCode=a.hashCode.slice(),b.hashCode[f-1]=g),g=b.id,null==e[g]){e[g]=b;d(a,b,c,k,0);a=b.connectsAsSource.slice();c=b.connectsAsTarget.slice();for(g=0;g<a.length;g++){f=a[g];var l=f.target;b.swimlaneIndex<=l.swimlaneIndex&&this.extendedDfs(b,l,f,d,e,b.hashCode,g,k+1)}for(g=0;g<c.length;g++)f=c[g],l=f.source,b.swimlaneIndex<l.swim [...]
+this.extendedDfs(b,l,f,d,e,b.hashCode,g,k+1)}else d(a,b,c,k,1)};function mxHierarchicalLayoutStage(){}mxHierarchicalLayoutStage.prototype.execute=function(a){};function mxMedianHybridCrossingReduction(a){this.layout=a}mxMedianHybridCrossingReduction.prototype=new mxHierarchicalLayoutStage;mxMedianHybridCrossingReduction.prototype.constructor=mxMedianHybridCrossingReduction;mxMedianHybridCrossingReduction.prototype.layout=null;mxMedianHybridCrossingReduction.prototype.maxIterations=24;
+mxMedianHybridCrossingReduction.prototype.nestedBestRanks=null;mxMedianHybridCrossingReduction.prototype.currentBestCrossings=0;mxMedianHybridCrossingReduction.prototype.iterationsWithoutImprovement=0;mxMedianHybridCrossingReduction.prototype.maxNoImprovementIterations=2;
+mxMedianHybridCrossingReduction.prototype.execute=function(a){a=this.layout.getModel();this.nestedBestRanks=[];for(var b=0;b<a.ranks.length;b++)this.nestedBestRanks[b]=a.ranks[b].slice();for(var c=0,d=this.calculateCrossings(a),b=0;b<this.maxIterations&&c<this.maxNoImprovementIterations;b++){this.weightedMedian(b,a);this.transpose(b,a);var e=this.calculateCrossings(a);if(e<d)for(d=e,e=c=0;e<this.nestedBestRanks.length;e++)for(var f=a.ranks[e],g=0;g<f.length;g++){var k=f[g];this.nestedBes [...]
+k}else for(c++,e=0;e<this.nestedBestRanks.length;e++)for(f=a.ranks[e],g=0;g<f.length;g++)k=f[g],k.setGeneralPurposeVariable(e,g);if(0==d)break}c=[];d=[];for(b=0;b<a.maxRank+1;b++)d[b]=[],c[b]=d[b];for(b=0;b<this.nestedBestRanks.length;b++)for(e=0;e<this.nestedBestRanks[b].length;e++)d[b].push(this.nestedBestRanks[b][e]);a.ranks=c};mxMedianHybridCrossingReduction.prototype.calculateCrossings=function(a){for(var b=a.ranks.length,c=0,d=1;d<b;d++)c+=this.calculateRankCrossing(d,a);return c};
+mxMedianHybridCrossingReduction.prototype.calculateRankCrossing=function(a,b){for(var c=0,d=b.ranks[a],e=b.ranks[a-1],f=[],g=0;g<d.length;g++){for(var k=d[g],l=k.getGeneralPurposeVariable(a),k=k.getPreviousLayerConnectedCells(a),m=[],n=0;n<k.length;n++){var p=k[n].getGeneralPurposeVariable(a-1);m.push(p)}m.sort(function(a,b){return a-b});f[l]=m}d=[];for(g=0;g<f.length;g++)d=d.concat(f[g]);for(f=1;f<e.length;)f<<=1;l=2*f-1;--f;e=[];for(g=0;g<l;++g)e[g]=0;for(g=0;g<d.length;g++)for(l=d[g]+ [...]
+l;)l%2&&(c+=e[l+1]),l=l-1>>1,++e[l];return c};
+mxMedianHybridCrossingReduction.prototype.transpose=function(a,b){for(var c=!0,d=0;c&&10>d++;)for(var e=1==a%2&&1==d%2,c=!1,f=0;f<b.ranks.length;f++){for(var g=b.ranks[f],k=[],l=0;l<g.length;l++){var m=g[l],n=m.getGeneralPurposeVariable(f);0>n&&(n=l);k[n]=m}for(var p=null,q=null,r,t,u=null,x=null,y,A=null,l=0;l<g.length-1;l++){if(0==l){y=k[l];m=y.getNextLayerConnectedCells(f);n=y.getPreviousLayerConnectedCells(f);r=[];t=[];for(var z=0;z<m.length;z++)r[z]=m[z].getGeneralPurposeVariable(f+ [...]
+0;z<n.length;z++)t[z]=n[z].getGeneralPurposeVariable(f-1)}else m=p,n=q,r=u,t=x,y=A;A=k[l+1];p=A.getNextLayerConnectedCells(f);q=A.getPreviousLayerConnectedCells(f);u=[];x=[];for(z=0;z<p.length;z++)u[z]=p[z].getGeneralPurposeVariable(f+1);for(z=0;z<q.length;z++)x[z]=q[z].getGeneralPurposeVariable(f-1);for(var v=0,B=0,z=0;z<r.length;z++)for(var C=0;C<u.length;C++)r[z]>u[C]&&v++,r[z]<u[C]&&B++;for(z=0;z<t.length;z++)for(C=0;C<x.length;C++)t[z]>x[C]&&v++,t[z]<x[C]&&B++;if(B<v||B==v&&e)p=y.ge [...]
+y.setGeneralPurposeVariable(f,A.getGeneralPurposeVariable(f)),A.setGeneralPurposeVariable(f,p),p=m,q=n,u=r,x=t,A=y,e||(c=!0)}}};mxMedianHybridCrossingReduction.prototype.weightedMedian=function(a,b){var c=0==a%2;if(c)for(var d=b.maxRank-1;0<=d;d--)this.medianRank(d,c);else for(d=1;d<b.maxRank;d++)this.medianRank(d,c)};
+mxMedianHybridCrossingReduction.prototype.medianRank=function(a,b){for(var c=this.nestedBestRanks[a].length,d=[],e=[],f=0;f<c;f++){var g=this.nestedBestRanks[a][f],k=new MedianCellSorter;k.cell=g;var l;l=b?g.getNextLayerConnectedCells(a):g.getPreviousLayerConnectedCells(a);var m;m=b?a+1:a-1;null!=l&&0!=l.length?(k.medianValue=this.medianValue(l,m),d.push(k)):e[g.getGeneralPurposeVariable(a)]=!0}d.sort(MedianCellSorter.prototype.compare);for(f=0;f<c;f++)null==e[f]&&(g=d.shift().cell,g.set [...]
+f))};mxMedianHybridCrossingReduction.prototype.medianValue=function(a,b){for(var c=[],d=0,e=0;e<a.length;e++){var f=a[e];c[d++]=f.getGeneralPurposeVariable(b)}c.sort(function(a,b){return a-b});if(1==d%2)return c[Math.floor(d/2)];if(2==d)return(c[0]+c[1])/2;e=d/2;f=c[e-1]-c[0];d=c[d-1]-c[e];return(c[e-1]*d+c[e]*f)/(f+d)};function MedianCellSorter(){}MedianCellSorter.prototype.medianValue=0;MedianCellSorter.prototype.cell=!1;
+MedianCellSorter.prototype.compare=function(a,b){return null!=a&&null!=b?b.medianValue>a.medianValue?-1:b.medianValue<a.medianValue?1:0:0};function mxMinimumCycleRemover(a){this.layout=a}mxMinimumCycleRemover.prototype=new mxHierarchicalLayoutStage;mxMinimumCycleRemover.prototype.constructor=mxMinimumCycleRemover;mxMinimumCycleRemover.prototype.layout=null;
+mxMinimumCycleRemover.prototype.execute=function(a){a=this.layout.getModel();for(var b={},c=a.vertexMapper.getValues(),d={},e=0;e<c.length;e++)d[c[e].id]=c[e];c=null;if(null!=a.roots)for(var f=a.roots,c=[],e=0;e<f.length;e++)c[e]=a.vertexMapper.get(f[e]);a.visit(function(a,c,e,f,n){c.isAncestor(a)&&(e.invert(),mxUtils.remove(e,a.connectsAsSource),a.connectsAsTarget.push(e),mxUtils.remove(e,c.connectsAsTarget),c.connectsAsSource.push(e));b[c.id]=c;delete d[c.id]},c,!0,null);e=mxUtils.clon [...]
+!0);a.visit(function(a,c,e,f,n){c.isAncestor(a)&&(e.invert(),mxUtils.remove(e,a.connectsAsSource),c.connectsAsSource.push(e),a.connectsAsTarget.push(e),mxUtils.remove(e,c.connectsAsTarget));b[c.id]=c;delete d[c.id]},d,!0,e)};function mxCoordinateAssignment(a,b,c,d,e,f){this.layout=a;this.intraCellSpacing=b;this.interRankCellSpacing=c;this.orientation=d;this.initialX=e;this.parallelEdgeSpacing=f}mxCoordinateAssignment.prototype=new mxHierarchicalLayoutStage;
+mxCoordinateAssignment.prototype.constructor=mxCoordinateAssignment;mxCoordinateAssignment.prototype.layout=null;mxCoordinateAssignment.prototype.intraCellSpacing=30;mxCoordinateAssignment.prototype.interRankCellSpacing=100;mxCoordinateAssignment.prototype.parallelEdgeSpacing=10;mxCoordinateAssignment.prototype.maxIterations=8;mxCoordinateAssignment.prototype.prefHozEdgeSep=5;mxCoordinateAssignment.prototype.prefVertEdgeOff=2;mxCoordinateAssignment.prototype.minEdgeJetty=12;
+mxCoordinateAssignment.prototype.channelBuffer=4;mxCoordinateAssignment.prototype.jettyPositions=null;mxCoordinateAssignment.prototype.orientation=mxConstants.DIRECTION_NORTH;mxCoordinateAssignment.prototype.initialX=null;mxCoordinateAssignment.prototype.limitX=null;mxCoordinateAssignment.prototype.currentXDelta=null;mxCoordinateAssignment.prototype.widestRank=null;mxCoordinateAssignment.prototype.rankTopY=null;mxCoordinateAssignment.prototype.rankBottomY=null;
+mxCoordinateAssignment.prototype.widestRankValue=null;mxCoordinateAssignment.prototype.rankWidths=null;mxCoordinateAssignment.prototype.rankY=null;mxCoordinateAssignment.prototype.fineTuning=!0;mxCoordinateAssignment.prototype.nextLayerConnectedCache=null;mxCoordinateAssignment.prototype.previousLayerConnectedCache=null;mxCoordinateAssignment.prototype.groupPadding=10;
+mxCoordinateAssignment.prototype.printStatus=function(){var a=this.layout.getModel();mxLog.show();mxLog.writeln("======Coord assignment debug=======");for(var b=0;b<a.ranks.length;b++){mxLog.write("Rank ",b," : ");for(var c=a.ranks[b],d=0;d<c.length;d++)mxLog.write(c[d].getGeneralPurposeVariable(b),"  ");mxLog.writeln()}mxLog.writeln("====================================")};
+mxCoordinateAssignment.prototype.execute=function(a){this.jettyPositions={};a=this.layout.getModel();this.currentXDelta=0;this.initialCoords(this.layout.getGraph(),a);this.fineTuning&&this.minNode(a);var b=1E8;if(this.fineTuning)for(var c=0;c<this.maxIterations;c++){0!=c&&(this.medianPos(c,a),this.minNode(a));if(this.currentXDelta<b){for(var d=0;d<a.ranks.length;d++)for(var e=a.ranks[d],f=0;f<e.length;f++){var g=e[f];g.setX(d,g.getGeneralPurposeVariable(d))}b=this.currentXDelta}else for( [...]
+a.ranks[d],f=0;f<e.length;f++)g=e[f],g.setGeneralPurposeVariable(d,g.getX(d));this.minPath(this.layout.getGraph(),a);this.currentXDelta=0}this.setCellLocations(this.layout.getGraph(),a)};
+mxCoordinateAssignment.prototype.minNode=function(a){for(var b=[],c=new mxDictionary,d=[],e=0;e<=a.maxRank;e++){d[e]=a.ranks[e];for(var f=0;f<d[e].length;f++){var g=d[e][f],k=new WeightedCellSorter(g,e);k.rankIndex=f;k.visited=!0;b.push(k);c.put(g,k)}}a=10*b.length;for(f=0;0<b.length&&f<=a;){var g=b.shift(),e=g.cell,l=g.weightedValue,m=parseInt(g.rankIndex),k=e.getNextLayerConnectedCells(l),n=e.getPreviousLayerConnectedCells(l),p=k.length,q=n.length,r=this.medianXValue(k,l+1),t=this.medi [...]
+l-1),u=p+q,x=e.getGeneralPurposeVariable(l),y=x;0<u&&(y=(r*p+t*q)/u);p=!1;y<x-1?0==m?(e.setGeneralPurposeVariable(l,y),p=!0):(m=d[l][m-1],x=m.getGeneralPurposeVariable(l),x=x+m.width/2+this.intraCellSpacing+e.width/2,x<y?(e.setGeneralPurposeVariable(l,y),p=!0):x<e.getGeneralPurposeVariable(l)-1&&(e.setGeneralPurposeVariable(l,x),p=!0)):y>x+1&&(m==d[l].length-1?(e.setGeneralPurposeVariable(l,y),p=!0):(m=d[l][m+1],x=m.getGeneralPurposeVariable(l),x=x-m.width/2-this.intraCellSpacing-e.width [...]
+y),p=!0):x>e.getGeneralPurposeVariable(l)+1&&(e.setGeneralPurposeVariable(l,x),p=!0)));if(p){for(e=0;e<k.length;e++)l=k[e],l=c.get(l),null!=l&&0==l.visited&&(l.visited=!0,b.push(l));for(e=0;e<n.length;e++)l=n[e],l=c.get(l),null!=l&&0==l.visited&&(l.visited=!0,b.push(l))}g.visited=!1;f++}};mxCoordinateAssignment.prototype.medianPos=function(a,b){if(0==a%2)for(var c=b.maxRank;0<c;c--)this.rankMedianPosition(c-1,b,c);else for(c=0;c<b.maxRank-1;c++)this.rankMedianPosition(c+1,b,c)};
+mxCoordinateAssignment.prototype.rankMedianPosition=function(a,b,c){b=b.ranks[a];for(var d=[],e={},f=0;f<b.length;f++){var g=b[f];d[f]=new WeightedCellSorter;d[f].cell=g;d[f].rankIndex=f;e[g.id]=d[f];var k;k=c<a?g.getPreviousLayerConnectedCells(a):g.getNextLayerConnectedCells(a);d[f].weightedValue=this.calculatedWeightedValue(g,k)}d.sort(WeightedCellSorter.prototype.compare);for(f=0;f<d.length;f++){var l,g=d[f].cell;l=0;k=c<a?g.getPreviousLayerConnectedCells(a).slice():g.getNextLayerConn [...]
+null!=k&&(l=k.length,l=0<l?this.medianXValue(k,c):g.getGeneralPurposeVariable(a));var m=0;k=-1E8;for(var n=d[f].rankIndex-1;0<=n;){var p=e[b[n].id];if(null!=p){var q=p.cell;p.visited?(k=q.getGeneralPurposeVariable(a)+q.width/2+this.intraCellSpacing+m+g.width/2,n=-1):(m+=q.width+this.intraCellSpacing,n--)}}m=0;q=1E8;for(n=d[f].rankIndex+1;n<d.length;)if(p=e[b[n].id],null!=p){var r=p.cell;p.visited?(q=r.getGeneralPurposeVariable(a)-r.width/2-this.intraCellSpacing-m-g.width/2,n=d.length):(m [...]
+this.intraCellSpacing,n++)}l>=k&&l<=q?g.setGeneralPurposeVariable(a,l):l<k?(g.setGeneralPurposeVariable(a,k),this.currentXDelta+=k-l):l>q&&(g.setGeneralPurposeVariable(a,q),this.currentXDelta+=l-q);d[f].visited=!0}};mxCoordinateAssignment.prototype.calculatedWeightedValue=function(a,b){for(var c=0,d=0;d<b.length;d++){var e=b[d];a.isVertex()&&e.isVertex()?c++:c=a.isEdge()&&e.isEdge()?c+8:c+2}return c};
+mxCoordinateAssignment.prototype.medianXValue=function(a,b){if(0==a.length)return 0;for(var c=[],d=0;d<a.length;d++)c[d]=a[d].getGeneralPurposeVariable(b);c.sort(function(a,b){return a-b});if(1==a.length%2)return c[Math.floor(a.length/2)];d=a.length/2;return(c[d-1]+c[d])/2};
+mxCoordinateAssignment.prototype.initialCoords=function(a,b){this.calculateWidestRank(a,b);for(var c=this.widestRank;0<=c;c--)c<b.maxRank&&this.rankCoordinates(c,a,b);for(c=this.widestRank+1;c<=b.maxRank;c++)0<c&&this.rankCoordinates(c,a,b)};
+mxCoordinateAssignment.prototype.rankCoordinates=function(a,b,c){b=c.ranks[a];c=this.initialX+(this.widestRankValue-this.rankWidths[a])/2;for(var d=!1,e=0;e<b.length;e++){var f=b[e];if(f.isVertex()){var g=this.layout.getVertexBounds(f.cell);null!=g?this.orientation==mxConstants.DIRECTION_NORTH||this.orientation==mxConstants.DIRECTION_SOUTH?(f.width=g.width,f.height=g.height):(f.width=g.height,f.height=g.width):d=!0}else f.isEdge()&&(g=1,null!=f.edges?g=f.edges.length:mxLog.warn("edge.edg [...]
+f.width=(g-1)*this.parallelEdgeSpacing);c+=f.width/2;f.setX(a,c);f.setGeneralPurposeVariable(a,c);c+=f.width/2;c+=this.intraCellSpacing}1==d&&mxLog.warn("At least one cell has no bounds")};
+mxCoordinateAssignment.prototype.calculateWidestRank=function(a,b){var c=-this.interRankCellSpacing,d=0;this.rankWidths=[];this.rankY=[];for(var e=b.maxRank;0<=e;e--){for(var f=0,g=b.ranks[e],k=this.initialX,l=!1,m=0;m<g.length;m++){var n=g[m];if(n.isVertex()){var p=this.layout.getVertexBounds(n.cell);null!=p?this.orientation==mxConstants.DIRECTION_NORTH||this.orientation==mxConstants.DIRECTION_SOUTH?(n.width=p.width,n.height=p.height):(n.width=p.height,n.height=p.width):l=!0;f=Math.max( [...]
+(p=1,null!=n.edges?p=n.edges.length:mxLog.warn("edge.edges is null"),n.width=(p-1)*this.parallelEdgeSpacing);k+=n.width/2;n.setX(e,k);n.setGeneralPurposeVariable(e,k);k+=n.width/2;k+=this.intraCellSpacing;k>this.widestRankValue&&(this.widestRankValue=k,this.widestRank=e);this.rankWidths[e]=k}1==l&&mxLog.warn("At least one cell has no bounds");this.rankY[e]=c;k=f/2+d/2+this.interRankCellSpacing;d=f;c=this.orientation==mxConstants.DIRECTION_NORTH||this.orientation==mxConstants.DIRECTION_WE [...]
+k;for(m=0;m<g.length;m++)g[m].setY(e,c)}};
+mxCoordinateAssignment.prototype.minPath=function(a,b){for(var c=b.edgeMapper.getValues(),d=0;d<c.length;d++){var e=c[d];if(!(1>e.maxRank-e.minRank-1)){for(var f=e.getGeneralPurposeVariable(e.minRank+1),g=!0,k=0,l=e.minRank+2;l<e.maxRank;l++){var m=e.getGeneralPurposeVariable(l);f!=m?(g=!1,f=m):k++}if(!g){for(var g=f=0,m=[],n=[],p=e.getGeneralPurposeVariable(e.minRank+1),l=e.minRank+1;l<e.maxRank-1;l++){var q=e.getX(l+1);p==q?(m[l-e.minRank-1]=p,f++):this.repositionValid(b,e,l+1,p)?(m[l- [...]
+1]=p,f++):p=m[l-e.minRank-1]=q}p=e.getX(l);for(l=e.maxRank-1;l>e.minRank+1;l--)q=e.getX(l-1),p==q?(n[l-e.minRank-2]=p,g++):this.repositionValid(b,e,l-1,p)?(n[l-e.minRank-2]=p,g++):(n[l-e.minRank-2]=e.getX(l-1),p=q);if(g>k||f>k)if(g>=f)for(l=e.maxRank-2;l>e.minRank;l--)e.setX(l,n[l-e.minRank-1]);else if(f>g)for(l=e.minRank+2;l<e.maxRank;l++)e.setX(l,m[l-e.minRank-2])}}}};
+mxCoordinateAssignment.prototype.repositionValid=function(a,b,c,d){a=a.ranks[c];for(var e=-1,f=0;f<a.length;f++)if(b==a[f]){e=f;break}if(0>e)return!1;f=b.getGeneralPurposeVariable(c);if(d<f){if(0==e)return!0;a=a[e-1];c=a.getGeneralPurposeVariable(c);c=c+a.width/2+this.intraCellSpacing+b.width/2;if(!(c<=d))return!1}else if(d>f){if(e==a.length-1)return!0;a=a[e+1];c=a.getGeneralPurposeVariable(c);c=c-a.width/2-this.intraCellSpacing-b.width/2;if(!(c>=d))return!1}return!0};
+mxCoordinateAssignment.prototype.setCellLocations=function(a,b){this.rankTopY=[];this.rankBottomY=[];for(var c=0;c<b.ranks.length;c++)this.rankTopY[c]=Number.MAX_VALUE,this.rankBottomY[c]=-Number.MAX_VALUE;for(var d=b.vertexMapper.getValues(),c=0;c<d.length;c++)this.setVertexLocation(d[c]);this.layout.edgeStyle!=mxHierarchicalEdgeStyle.ORTHOGONAL&&this.layout.edgeStyle!=mxHierarchicalEdgeStyle.POLYLINE&&this.layout.edgeStyle!=mxHierarchicalEdgeStyle.CURVE||this.localEdgeProcessing(b);d=b [...]
+for(c=0;c<d.length;c++)this.setEdgePosition(d[c])};
+mxCoordinateAssignment.prototype.localEdgeProcessing=function(a){for(var b=0;b<a.ranks.length;b++)for(var c=a.ranks[b],d=0;d<c.length;d++){var e=c[d];if(e.isVertex())for(var f=e.getPreviousLayerConnectedCells(b),g=b-1,k=0;2>k;k++){if(-1<g&&g<a.ranks.length&&null!=f&&0<f.length){for(var l=[],m=0;m<f.length;m++){var n=new WeightedCellSorter(f[m],f[m].getX(g));l.push(n)}l.sort(WeightedCellSorter.prototype.compare);for(var n=e.x[0]-e.width/2,p=n+e.width,q=f=0,g=[],m=0;m<l.length;m++){var r=l [...]
+t;if(r.isVertex()){t=0==k?e.connectsAsSource:e.connectsAsTarget;for(var u=0;u<t.length;u++)if(t[u].source==r||t[u].target==r)f+=t[u].edges.length,q++,g.push(t[u])}else f+=r.edges.length,q++,g.push(r)}e.width>(f+1)*this.prefHozEdgeSep+2*this.prefHozEdgeSep&&(n+=this.prefHozEdgeSep,p-=this.prefHozEdgeSep);l=(p-n)/f;n+=l/2;p=this.minEdgeJetty-this.prefVertEdgeOff;for(m=0;m<g.length;m++)for(q=g[m].edges.length,r=this.jettyPositions[g[m].ids[0]],null==r&&(r=[],this.jettyPositions[g[m].ids[0]] [...]
+p+=this.prefVertEdgeOff:m>f/2&&(p-=this.prefVertEdgeOff),t=0;t<q;t++)r[4*t+2*k]=n,n+=l,r[4*t+2*k+1]=p}f=e.getNextLayerConnectedCells(b);g=b+1}}};
+mxCoordinateAssignment.prototype.setEdgePosition=function(a){var b=0;if(101207!=a.temp[0]){var c=a.maxRank,d=a.minRank;c==d&&(c=a.source.maxRank,d=a.target.minRank);for(var e=0,f=this.jettyPositions[a.ids[0]],g=a.isReversed?a.target.cell:a.source.cell,k=this.layout.graph,l=this.orientation==mxConstants.DIRECTION_EAST||this.orientation==mxConstants.DIRECTION_SOUTH,m=0;m<a.edges.length;m++){var n=a.edges[m],p=this.layout.getVisibleTerminal(n,!0),q=[],r=a.isReversed;p!=g&&(r=!r);if(null!=f) [...]
+2:0,u=r?l?this.rankBottomY[d]:this.rankTopY[d]:l?this.rankTopY[c]:this.rankBottomY[c],x=f[4*e+1+t];r!=l&&(x=-x);var u=u+x,t=f[4*e+t],y=k.model.getTerminal(n,!0);this.layout.isPort(y)&&k.model.getParent(y)==p&&(t=k.view.getState(y),t=null!=t?t.x:p.geometry.x+a.source.width*y.geometry.x);this.orientation==mxConstants.DIRECTION_NORTH||this.orientation==mxConstants.DIRECTION_SOUTH?(q.push(new mxPoint(t,u)),this.layout.edgeStyle==mxHierarchicalEdgeStyle.CURVE&&q.push(new mxPoint(t,u+x))):(q.p [...]
+t)),this.layout.edgeStyle==mxHierarchicalEdgeStyle.CURVE&&q.push(new mxPoint(u+x,t)))}t=a.x.length-1;u=x=-1;p=a.maxRank-1;r&&(t=0,x=a.x.length,u=1,p=a.minRank+1);for(;a.maxRank!=a.minRank&&t!=x;t+=u){var y=a.x[t]+b,A=(this.rankTopY[p]+this.rankBottomY[p+1])/2,z=(this.rankTopY[p-1]+this.rankBottomY[p])/2;if(r)var v=A,A=z,z=v;this.orientation==mxConstants.DIRECTION_NORTH||this.orientation==mxConstants.DIRECTION_SOUTH?(q.push(new mxPoint(y,A)),q.push(new mxPoint(y,z))):(q.push(new mxPoint(A [...]
+y)));this.limitX=Math.max(this.limitX,y);p+=u}null!=f&&(t=r?2:0,u=r?l?this.rankTopY[c]:this.rankBottomY[c]:l?this.rankBottomY[d]:this.rankTopY[d],x=f[4*e+3-t],r!=l&&(x=-x),u-=x,t=f[4*e+2-t],r=k.model.getTerminal(n,!1),p=this.layout.getVisibleTerminal(n,!1),this.layout.isPort(r)&&k.model.getParent(r)==p&&(t=k.view.getState(r),t=null!=t?t.x:p.geometry.x+a.target.width*r.geometry.x),this.orientation==mxConstants.DIRECTION_NORTH||this.orientation==mxConstants.DIRECTION_SOUTH?(this.layout.edg [...]
+q.push(new mxPoint(t,u-x)),q.push(new mxPoint(t,u))):(this.layout.edgeStyle==mxHierarchicalEdgeStyle.CURVE&&q.push(new mxPoint(u-x,t)),q.push(new mxPoint(u,t))));a.isReversed&&this.processReversedEdge(a,n);this.layout.setEdgePoints(n,q);b=0==b?this.parallelEdgeSpacing:0<b?-b:-b+this.parallelEdgeSpacing;e++}a.temp[0]=101207}};
+mxCoordinateAssignment.prototype.setVertexLocation=function(a){var b=a.cell,c=a.x[0]-a.width/2,d=a.y[0]-a.height/2;this.rankTopY[a.minRank]=Math.min(this.rankTopY[a.minRank],d);this.rankBottomY[a.minRank]=Math.max(this.rankBottomY[a.minRank],d+a.height);this.orientation==mxConstants.DIRECTION_NORTH||this.orientation==mxConstants.DIRECTION_SOUTH?this.layout.setVertexLocation(b,c,d):this.layout.setVertexLocation(b,d,c);this.limitX=Math.max(this.limitX,c+a.width)};
+mxCoordinateAssignment.prototype.processReversedEdge=function(a,b){};function WeightedCellSorter(a,b){this.cell=a;this.weightedValue=b}WeightedCellSorter.prototype.weightedValue=0;WeightedCellSorter.prototype.nudge=!1;WeightedCellSorter.prototype.visited=!1;WeightedCellSorter.prototype.rankIndex=null;WeightedCellSorter.prototype.cell=null;WeightedCellSorter.prototype.compare=function(a,b){return null!=a&&null!=b?b.weightedValue>a.weightedValue?-1:b.weightedValue<a.weightedValue?1:b.nudge [...]
+function mxSwimlaneOrdering(a){this.layout=a}mxSwimlaneOrdering.prototype=new mxHierarchicalLayoutStage;mxSwimlaneOrdering.prototype.constructor=mxSwimlaneOrdering;mxSwimlaneOrdering.prototype.layout=null;
+mxSwimlaneOrdering.prototype.execute=function(a){a=this.layout.getModel();var b=mxUtils.clone(a.vertexMapper,null,!0),c=null;if(null!=a.roots)for(var d=a.roots,c=[],e=0;e<d.length;e++)mxCellPath.create(d[e]),c[e]=a.vertexMapper.get(d[e]);a.visit(function(a,c,d,e,m){e=null!=a&&a.swimlaneIndex==c.swimlaneIndex&&c.isAncestor(a);m=null!=a&&null!=d&&a.swimlaneIndex<c.swimlaneIndex&&d.source==c;e?(d.invert(),mxUtils.remove(d,a.connectsAsSource),c.connectsAsSource.push(d),a.connectsAsTarget.pus [...]
+c.connectsAsTarget)):m&&(d.invert(),mxUtils.remove(d,a.connectsAsTarget),c.connectsAsTarget.push(d),a.connectsAsSource.push(d),mxUtils.remove(d,c.connectsAsSource));a=mxCellPath.create(c.cell);delete b[a]},c,!0,null)};function mxHierarchicalLayout(a,b,c){mxGraphLayout.call(this,a);this.orientation=null!=b?b:mxConstants.DIRECTION_NORTH;this.deterministic=null!=c?c:!0}var mxHierarchicalEdgeStyle={ORTHOGONAL:1,POLYLINE:2,STRAIGHT:3,CURVE:4};mxHierarchicalLayout.prototype=new mxGraphLayout;
+mxHierarchicalLayout.prototype.constructor=mxHierarchicalLayout;mxHierarchicalLayout.prototype.roots=null;mxHierarchicalLayout.prototype.resizeParent=!1;mxHierarchicalLayout.prototype.maintainParentLocation=!1;mxHierarchicalLayout.prototype.moveParent=!1;mxHierarchicalLayout.prototype.parentBorder=0;mxHierarchicalLayout.prototype.intraCellSpacing=30;mxHierarchicalLayout.prototype.interRankCellSpacing=100;mxHierarchicalLayout.prototype.interHierarchySpacing=60;
+mxHierarchicalLayout.prototype.parallelEdgeSpacing=10;mxHierarchicalLayout.prototype.orientation=mxConstants.DIRECTION_NORTH;mxHierarchicalLayout.prototype.fineTuning=!0;mxHierarchicalLayout.prototype.tightenToSource=!0;mxHierarchicalLayout.prototype.disableEdgeStyle=!0;mxHierarchicalLayout.prototype.traverseAncestors=!0;mxHierarchicalLayout.prototype.model=null;mxHierarchicalLayout.prototype.edgesCache=null;mxHierarchicalLayout.prototype.edgeSourceTermCache=null;
+mxHierarchicalLayout.prototype.edgesTargetTermCache=null;mxHierarchicalLayout.prototype.edgeStyle=mxHierarchicalEdgeStyle.POLYLINE;mxHierarchicalLayout.prototype.getModel=function(){return this.model};
+mxHierarchicalLayout.prototype.execute=function(a,b){this.parent=a;var c=this.graph.model;this.edgesCache=new mxDictionary;this.edgeSourceTermCache=new mxDictionary;this.edgesTargetTermCache=new mxDictionary;null==b||b instanceof Array||(b=[b]);if(null!=b||null!=a){this.parentY=this.parentX=null;if(a!=this.root&&null!=c.isVertex(a)&&this.maintainParentLocation){var d=this.graph.getCellGeometry(a);null!=d&&(this.parentX=d.x,this.parentY=d.y)}if(null!=b){for(var e=[],f=0;f<b.length;f++)(nu [...]
+b[f]):1)&&c.isVertex(b[f])&&e.push(b[f]);this.roots=e}c.beginUpdate();try{this.run(a),this.resizeParent&&!this.graph.isCellCollapsed(a)&&this.graph.updateGroupBounds([a],this.parentBorder,this.moveParent),null!=this.parentX&&null!=this.parentY&&(d=this.graph.getCellGeometry(a),null!=d&&(d=d.clone(),d.x=this.parentX,d.y=this.parentY,c.setGeometry(a,d)))}finally{c.endUpdate()}}};
+mxHierarchicalLayout.prototype.findRoots=function(a,b){var c=[];if(null!=a&&null!=b){var d=this.graph.model,e=null,f=-1E5,g;for(g in b){var k=b[g];if(d.isVertex(k)&&this.graph.isCellVisible(k)){for(var l=this.getEdges(k),m=0,n=0,p=0;p<l.length;p++)this.getVisibleTerminal(l[p],!0)==k?m++:n++;0==n&&0<m&&c.push(k);l=m-n;l>f&&(f=l,e=k)}}0==c.length&&null!=e&&c.push(e)}return c};
+mxHierarchicalLayout.prototype.getEdges=function(a){var b=this.edgesCache.get(a);if(null!=b)return b;for(var c=this.graph.model,b=[],d=this.graph.isCellCollapsed(a),e=c.getChildCount(a),f=0;f<e;f++){var g=c.getChildAt(a,f);if(this.isPort(g))b=b.concat(c.getEdges(g,!0,!0));else if(d||!this.graph.isCellVisible(g))b=b.concat(c.getEdges(g,!0,!0))}b=b.concat(c.getEdges(a,!0,!0));c=[];for(f=0;f<b.length;f++)d=this.getVisibleTerminal(b[f],!0),e=this.getVisibleTerminal(b[f],!1),(d==e||d!=e&&(e== [...]
+this.parent||this.graph.isValidAncestor(d,this.parent,this.traverseAncestors))||d==a&&(null==this.parent||this.graph.isValidAncestor(e,this.parent,this.traverseAncestors))))&&c.push(b[f]);this.edgesCache.put(a,c);return c};
+mxHierarchicalLayout.prototype.getVisibleTerminal=function(a,b){var c=this.edgesTargetTermCache;b&&(c=this.edgeSourceTermCache);var d=c.get(a);if(null!=d)return d;var d=this.graph.view.getState(a),e=null!=d?d.getVisibleTerminal(b):this.graph.view.getVisibleTerminal(a,b);null==e&&(e=null!=d?d.getVisibleTerminal(b):this.graph.view.getVisibleTerminal(a,b));null!=e&&(this.isPort(e)&&(e=this.graph.model.getParent(e)),c.put(a,e));return e};
+mxHierarchicalLayout.prototype.run=function(a){var b=[],c=[];if(null==this.roots&&null!=a){var d={};this.filterDescendants(a,d);this.roots=[];var e=!0,f;for(f in d)if(null!=d[f]){e=!1;break}for(;!e;){for(var g=this.findRoots(a,d),e=0;e<g.length;e++){var k={};b.push(k);this.traverse(g[e],!0,null,c,k,b,d)}for(e=0;e<g.length;e++)this.roots.push(g[e]);e=!0;for(f in d)if(null!=d[f]){e=!1;break}}}else for(e=0;e<this.roots.length;e++)k={},b.push(k),this.traverse(this.roots[e],!0,null,c,k,b,null [...]
+0;e<b.length;e++){k=b[e];d=[];for(f in k)d.push(k[f]);this.model=new mxGraphHierarchyModel(this,d,this.roots,a,this.tightenToSource);this.cycleStage(a);this.layeringStage();this.crossingStage(a);c=this.placementStage(c,a)}};
+mxHierarchicalLayout.prototype.filterDescendants=function(a,b){var c=this.graph.model;c.isVertex(a)&&a!=this.parent&&this.graph.isCellVisible(a)&&(b[mxObjectIdentity.get(a)]=a);if(this.traverseAncestors||a==this.parent&&this.graph.isCellVisible(a))for(var d=c.getChildCount(a),e=0;e<d;e++){var f=c.getChildAt(a,e);this.isPort(f)||this.filterDescendants(f,b)}};mxHierarchicalLayout.prototype.isPort=function(a){return a.geometry.relative?!0:!1};
+mxHierarchicalLayout.prototype.getEdgesBetween=function(a,b,c){c=null!=c?c:!1;for(var d=this.getEdges(a),e=[],f=0;f<d.length;f++){var g=this.getVisibleTerminal(d[f],!0),k=this.getVisibleTerminal(d[f],!1);(g==a&&k==b||!c&&g==b&&k==a)&&e.push(d[f])}return e};
+mxHierarchicalLayout.prototype.traverse=function(a,b,c,d,e,f,g){if(null!=a&&null!=d){var k=mxObjectIdentity.get(a);if(null==d[k]&&(null==g||null!=g[k])){null==e[k]&&(e[k]=a);null==d[k]&&(d[k]=a);null!==g&&delete g[k];var l=this.getEdges(a),k=[];for(c=0;c<l.length;c++)k[c]=this.getVisibleTerminal(l[c],!0)==a;for(c=0;c<l.length;c++)if(!b||k[c]){a=this.getVisibleTerminal(l[c],!k[c]);for(var m=1,n=0;n<l.length;n++)if(n!=c){var p=k[n];this.getVisibleTerminal(l[n],!p)==a&&(p?m++:m--)}0<=m&&(e= [...]
+b,l[c],d,e,f,g))}}else if(null==e[k])for(c=0;c<f.length;c++)if(b=f[c],null!=b[k]){for(l in b)e[l]=b[l];f.splice(c,1);break}}return e};mxHierarchicalLayout.prototype.cycleStage=function(a){(new mxMinimumCycleRemover(this)).execute(a)};mxHierarchicalLayout.prototype.layeringStage=function(){this.model.initialRank();this.model.fixRanks()};mxHierarchicalLayout.prototype.crossingStage=function(a){(new mxMedianHybridCrossingReduction(this)).execute(a)};
+mxHierarchicalLayout.prototype.placementStage=function(a,b){var c=new mxCoordinateAssignment(this,this.intraCellSpacing,this.interRankCellSpacing,this.orientation,a,this.parallelEdgeSpacing);c.fineTuning=this.fineTuning;c.execute(b);return c.limitX+this.interHierarchySpacing};function mxSwimlaneLayout(a,b,c){mxGraphLayout.call(this,a);this.orientation=null!=b?b:mxConstants.DIRECTION_NORTH;this.deterministic=null!=c?c:!0}mxSwimlaneLayout.prototype=new mxGraphLayout;
+mxSwimlaneLayout.prototype.constructor=mxSwimlaneLayout;mxSwimlaneLayout.prototype.roots=null;mxSwimlaneLayout.prototype.swimlanes=null;mxSwimlaneLayout.prototype.dummyVertices=null;mxSwimlaneLayout.prototype.dummyVertexWidth=50;mxSwimlaneLayout.prototype.resizeParent=!1;mxSwimlaneLayout.prototype.maintainParentLocation=!1;mxSwimlaneLayout.prototype.moveParent=!1;mxSwimlaneLayout.prototype.parentBorder=30;mxSwimlaneLayout.prototype.intraCellSpacing=30;
+mxSwimlaneLayout.prototype.interRankCellSpacing=100;mxSwimlaneLayout.prototype.interHierarchySpacing=60;mxSwimlaneLayout.prototype.parallelEdgeSpacing=10;mxSwimlaneLayout.prototype.orientation=mxConstants.DIRECTION_NORTH;mxSwimlaneLayout.prototype.fineTuning=!0;mxSwimlaneLayout.prototype.tightenToSource=!0;mxSwimlaneLayout.prototype.disableEdgeStyle=!0;mxSwimlaneLayout.prototype.traverseAncestors=!0;mxSwimlaneLayout.prototype.model=null;mxSwimlaneLayout.prototype.edgesCache=null;
+mxHierarchicalLayout.prototype.edgeSourceTermCache=null;mxHierarchicalLayout.prototype.edgesTargetTermCache=null;mxHierarchicalLayout.prototype.edgeStyle=mxHierarchicalEdgeStyle.POLYLINE;mxSwimlaneLayout.prototype.getModel=function(){return this.model};
+mxSwimlaneLayout.prototype.execute=function(a,b){this.parent=a;var c=this.graph.model;this.edgesCache=new mxDictionary;this.edgeSourceTermCache=new mxDictionary;this.edgesTargetTermCache=new mxDictionary;if(!(null==b||1>b.length)){null==a&&(a=c.getParent(b[0]));this.parentY=this.parentX=null;if(a!=this.root&&null!=c.isVertex(a)&&this.maintainParentLocation){var d=this.graph.getCellGeometry(a);null!=d&&(this.parentX=d.x,this.parentY=d.y)}this.swimlanes=b;this.dummyVertices=[];for(var e=0; [...]
+this.graph.getChildCells(b[e]);if(null==f||0==f.length)f=this.graph.insertVertex(b[e],null,null,0,0,this.dummyVertexWidth,0),this.dummyVertices.push(f)}c.beginUpdate();try{this.run(a),this.resizeParent&&!this.graph.isCellCollapsed(a)&&this.graph.updateGroupBounds([a],this.parentBorder,this.moveParent),null!=this.parentX&&null!=this.parentY&&(d=this.graph.getCellGeometry(a),null!=d&&(d=d.clone(),d.x=this.parentX,d.y=this.parentY,c.setGeometry(a,d))),this.graph.removeCells(this.dummyVertic [...]
+mxSwimlaneLayout.prototype.updateGroupBounds=function(){var a=[],b=this.model,c;for(c in b.edgeMapper)for(var d=b.edgeMapper[c],e=0;e<d.edges.length;e++)a.push(d.edges[e]);a=this.graph.getBoundingBoxFromGeometry(a,!0);b=[];for(e=0;e<this.swimlanes.length;e++){var f=this.swimlanes[e];c=this.graph.getCellGeometry(f);if(null!=c){var g=this.graph.getChildCells(f),d=this.graph.isSwimlane(f)?this.graph.getStartSize(f):new mxRectangle,f=this.graph.getBoundingBoxFromGeometry(g);b[e]=f;d=f.y+c.y- [...]
+this.parentBorder;c=f.y+c.y+f.height;null==a?a=new mxRectangle(0,d,0,c-d):(a.y=Math.min(a.y,d),a.height=Math.max(a.y+a.height,c)-a.y)}}for(e=0;e<this.swimlanes.length;e++)if(f=this.swimlanes[e],c=this.graph.getCellGeometry(f),null!=c){var g=this.graph.getChildCells(f),d=this.graph.isSwimlane(f)?this.graph.getStartSize(f):new mxRectangle,k=c.clone(),l=0==e?this.parentBorder:this.interRankCellSpacing/2;k.x+=b[e].x-d.width-l;k.y=k.y+a.y-c.y-this.parentBorder;k.width=b[e].width+d.width+this. [...]
+2+l;k.height=a.height+d.height+2*this.parentBorder;this.graph.model.setGeometry(f,k);this.graph.moveCells(g,-b[e].x+d.width+l,c.y-a.y+this.parentBorder)}};
+mxSwimlaneLayout.prototype.findRoots=function(a,b){var c=[];if(null!=a&&null!=b){var d=this.graph.model,e=null,f=-1E5,g;for(g in b){var k=b[g];if(null!=k&&d.isVertex(k)&&this.graph.isCellVisible(k)&&d.isAncestor(a,k)){for(var l=this.getEdges(k),m=0,n=0,p=0;p<l.length;p++){var q=this.getVisibleTerminal(l[p],!0);q==k?(q=this.getVisibleTerminal(l[p],!1),d.isAncestor(a,q)&&m++):d.isAncestor(a,q)&&n++}0==n&&0<m&&c.push(k);l=m-n;l>f&&(f=l,e=k)}}0==c.length&&null!=e&&c.push(e)}return c};
+mxSwimlaneLayout.prototype.getEdges=function(a){var b=this.edgesCache.get(a);if(null!=b)return b;for(var c=this.graph.model,b=[],d=this.graph.isCellCollapsed(a),e=c.getChildCount(a),f=0;f<e;f++){var g=c.getChildAt(a,f);if(this.isPort(g))b=b.concat(c.getEdges(g,!0,!0));else if(d||!this.graph.isCellVisible(g))b=b.concat(c.getEdges(g,!0,!0))}b=b.concat(c.getEdges(a,!0,!0));c=[];for(f=0;f<b.length;f++)d=this.getVisibleTerminal(b[f],!0),e=this.getVisibleTerminal(b[f],!1),(d==e||d!=e&&(e==a&&( [...]
+this.graph.isValidAncestor(d,this.parent,this.traverseAncestors))||d==a&&(null==this.parent||this.graph.isValidAncestor(e,this.parent,this.traverseAncestors))))&&c.push(b[f]);this.edgesCache.put(a,c);return c};
+mxSwimlaneLayout.prototype.getVisibleTerminal=function(a,b){var c=this.edgesTargetTermCache;b&&(c=this.edgeSourceTermCache);var d=c.get(a);if(null!=d)return d;var d=this.graph.view.getState(a),e=null!=d?d.getVisibleTerminal(b):this.graph.view.getVisibleTerminal(a,b);null==e&&(e=null!=d?d.getVisibleTerminal(b):this.graph.view.getVisibleTerminal(a,b));null!=e&&(this.isPort(e)&&(e=this.graph.model.getParent(e)),c.put(a,e));return e};
+mxSwimlaneLayout.prototype.run=function(a){var b=[],c=[];if(null!=this.swimlanes&&0<this.swimlanes.length&&null!=a){for(var d={},e=0;e<this.swimlanes.length;e++)this.filterDescendants(this.swimlanes[e],d);this.roots=[];var e=!0,f;for(f in d)if(null!=d[f]){e=!1;break}for(var g=0;!e&&g<this.swimlanes.length;){var k=this.findRoots(this.swimlanes[g],d);if(0==k.length)g++;else{for(e=0;e<k.length;e++){var l={};b.push(l);this.traverse(k[e],!0,null,c,l,b,d,g)}for(e=0;e<k.length;e++)this.roots.pu [...]
+e=!0;for(f in d)if(null!=d[f]){e=!1;break}}}}else for(e=0;e<this.roots.length;e++)l={},b.push(l),this.traverse(this.roots[e],!0,null,c,l,b,null);b=[];for(f in c)b.push(c[f]);this.model=new mxSwimlaneModel(this,b,this.roots,a,this.tightenToSource);this.cycleStage(a);this.layeringStage();this.crossingStage(a);initialX=this.placementStage(0,a)};
+mxSwimlaneLayout.prototype.filterDescendants=function(a,b){var c=this.graph.model;c.isVertex(a)&&a!=this.parent&&c.getParent(a)!=this.parent&&this.graph.isCellVisible(a)&&(b[mxObjectIdentity.get(a)]=a);if(this.traverseAncestors||a==this.parent&&this.graph.isCellVisible(a))for(var d=c.getChildCount(a),e=0;e<d;e++){var f=c.getChildAt(a,e);this.isPort(f)||this.filterDescendants(f,b)}};mxSwimlaneLayout.prototype.isPort=function(a){return a.geometry.relative?!0:!1};
+mxSwimlaneLayout.prototype.getEdgesBetween=function(a,b,c){c=null!=c?c:!1;for(var d=this.getEdges(a),e=[],f=0;f<d.length;f++){var g=this.getVisibleTerminal(d[f],!0),k=this.getVisibleTerminal(d[f],!1);(g==a&&k==b||!c&&g==b&&k==a)&&e.push(d[f])}return e};
+mxSwimlaneLayout.prototype.traverse=function(a,b,c,d,e,f,g,k){if(null!=a&&null!=d){var l=mxObjectIdentity.get(a);if(null==d[l]&&(null==g||null!=g[l])){null==e[l]&&(e[l]=a);null==d[l]&&(d[l]=a);null!==g&&delete g[l];var m=this.getEdges(a),l=this.graph.model;for(c=0;c<m.length;c++){var n=this.getVisibleTerminal(m[c],!0),p=n==a;p&&(n=this.getVisibleTerminal(m[c],!1));var q;for(q=0;q<this.swimlanes.length&&!l.isAncestor(this.swimlanes[q],n);q++);q>=this.swimlanes.length||!(q>k||(!b||p)&&q==k [...]
+b,m[c],d,e,f,g,q))}}else if(null==e[l])for(c=0;c<f.length;c++)if(a=f[c],null!=a[l]){for(m in a)e[m]=a[m];f.splice(c,1);break}}return e};mxSwimlaneLayout.prototype.cycleStage=function(a){(new mxSwimlaneOrdering(this)).execute(a)};mxSwimlaneLayout.prototype.layeringStage=function(){this.model.initialRank();this.model.fixRanks()};mxSwimlaneLayout.prototype.crossingStage=function(a){(new mxMedianHybridCrossingReduction(this)).execute(a)};
+mxSwimlaneLayout.prototype.placementStage=function(a,b){var c=new mxCoordinateAssignment(this,this.intraCellSpacing,this.interRankCellSpacing,this.orientation,a,this.parallelEdgeSpacing);c.fineTuning=this.fineTuning;c.execute(b);return c.limitX+this.interHierarchySpacing};function mxGraphModel(a){this.currentEdit=this.createUndoableEdit();null!=a?this.setRoot(a):this.clear()}mxGraphModel.prototype=new mxEventSource;mxGraphModel.prototype.constructor=mxGraphModel;mxGraphModel.prototype.ro [...]
+mxGraphModel.prototype.cells=null;mxGraphModel.prototype.maintainEdgeParent=!0;mxGraphModel.prototype.ignoreRelativeEdgeParent=!0;mxGraphModel.prototype.createIds=!0;mxGraphModel.prototype.prefix="";mxGraphModel.prototype.postfix="";mxGraphModel.prototype.nextId=0;mxGraphModel.prototype.currentEdit=null;mxGraphModel.prototype.updateLevel=0;mxGraphModel.prototype.endingUpdate=!1;mxGraphModel.prototype.clear=function(){this.setRoot(this.createRoot())};mxGraphModel.prototype.isCreateIds=fun [...]
+mxGraphModel.prototype.setCreateIds=function(a){this.createIds=a};mxGraphModel.prototype.createRoot=function(){var a=new mxCell;a.insert(new mxCell);return a};mxGraphModel.prototype.getCell=function(a){return null!=this.cells?this.cells[a]:null};mxGraphModel.prototype.filterCells=function(a,b){var c=null;if(null!=a)for(var c=[],d=0;d<a.length;d++)b(a[d])&&c.push(a[d]);return c};mxGraphModel.prototype.getDescendants=function(a){return this.filterDescendants(null,a)};
+mxGraphModel.prototype.filterDescendants=function(a,b){var c=[];b=b||this.getRoot();(null==a||a(b))&&c.push(b);for(var d=this.getChildCount(b),e=0;e<d;e++)var f=this.getChildAt(b,e),c=c.concat(this.filterDescendants(a,f));return c};mxGraphModel.prototype.getRoot=function(a){var b=a||this.root;if(null!=a)for(;null!=a;)b=a,a=this.getParent(a);return b};mxGraphModel.prototype.setRoot=function(a){this.execute(new mxRootChange(this,a));return a};
+mxGraphModel.prototype.rootChanged=function(a){var b=this.root;this.root=a;this.nextId=0;this.cells=null;this.cellAdded(a);return b};mxGraphModel.prototype.isRoot=function(a){return null!=a&&this.root==a};mxGraphModel.prototype.isLayer=function(a){return this.isRoot(this.getParent(a))};mxGraphModel.prototype.isAncestor=function(a,b){for(;null!=b&&b!=a;)b=this.getParent(b);return b==a};mxGraphModel.prototype.contains=function(a){return this.isAncestor(this.root,a)};
+mxGraphModel.prototype.getParent=function(a){return null!=a?a.getParent():null};mxGraphModel.prototype.add=function(a,b,c){if(b!=a&&null!=a&&null!=b){null==c&&(c=this.getChildCount(a));var d=a!=this.getParent(b);this.execute(new mxChildChange(this,a,b,c));this.maintainEdgeParent&&d&&this.updateEdgeParents(b)}return b};
+mxGraphModel.prototype.cellAdded=function(a){if(null!=a){null==a.getId()&&this.createIds&&a.setId(this.createId(a));if(null!=a.getId()){var b=this.getCell(a.getId());if(b!=a){for(;null!=b;)a.setId(this.createId(a)),b=this.getCell(a.getId());null==this.cells&&(this.cells={});this.cells[a.getId()]=a}}mxUtils.isNumeric(a.getId())&&(this.nextId=Math.max(this.nextId,a.getId()));for(var b=this.getChildCount(a),c=0;c<b;c++)this.cellAdded(this.getChildAt(a,c))}};
+mxGraphModel.prototype.createId=function(a){a=this.nextId;this.nextId++;return this.prefix+a+this.postfix};mxGraphModel.prototype.updateEdgeParents=function(a,b){b=b||this.getRoot(a);for(var c=this.getChildCount(a),d=0;d<c;d++){var e=this.getChildAt(a,d);this.updateEdgeParents(e,b)}e=this.getEdgeCount(a);c=[];for(d=0;d<e;d++)c.push(this.getEdgeAt(a,d));for(d=0;d<c.length;d++)e=c[d],this.isAncestor(b,e)&&this.updateEdgeParent(e,b)};
+mxGraphModel.prototype.updateEdgeParent=function(a,b){for(var c=this.getTerminal(a,!0),d=this.getTerminal(a,!1);null!=c&&!this.isEdge(c)&&null!=c.geometry&&c.geometry.relative;)c=this.getParent(c);for(;null!=d&&this.ignoreRelativeEdgeParent&&!this.isEdge(d)&&null!=d.geometry&&d.geometry.relative;)d=this.getParent(d);if(this.isAncestor(b,c)&&this.isAncestor(b,d)&&(c=c==d?this.getParent(c):this.getNearestCommonAncestor(c,d),null!=c&&(this.getParent(c)!=this.root||this.isAncestor(c,a))&&thi [...]
+c)){d=this.getGeometry(a);if(null!=d){var e=this.getOrigin(this.getParent(a)),f=this.getOrigin(c),g=f.x-e.x,e=f.y-e.y,d=d.clone();d.translate(-g,-e);this.setGeometry(a,d)}this.add(c,a,this.getChildCount(c))}};mxGraphModel.prototype.getOrigin=function(a){var b;null!=a?(b=this.getOrigin(this.getParent(a)),this.isEdge(a)||(a=this.getGeometry(a),null!=a&&(b.x+=a.x,b.y+=a.y))):b=new mxPoint;return b};
+mxGraphModel.prototype.getNearestCommonAncestor=function(a,b){if(null!=a&&null!=b){var c=mxCellPath.create(b);if(null!=c&&0<c.length){var d=a,e=mxCellPath.create(d);if(c.length<e.length)var d=b,f=e,e=c,c=f;for(;null!=d;){f=this.getParent(d);if(0==c.indexOf(e+mxCellPath.PATH_SEPARATOR)&&null!=f)return d;e=mxCellPath.getParentPath(e);d=f}}}return null};mxGraphModel.prototype.remove=function(a){a==this.root?this.setRoot(null):null!=this.getParent(a)&&this.execute(new mxChildChange(this,null [...]
+mxGraphModel.prototype.cellRemoved=function(a){if(null!=a&&null!=this.cells){for(var b=this.getChildCount(a)-1;0<=b;b--)this.cellRemoved(this.getChildAt(a,b));null!=this.cells&&null!=a.getId()&&delete this.cells[a.getId()]}};mxGraphModel.prototype.parentForCellChanged=function(a,b,c){var d=this.getParent(a);null!=b?b==d&&d.getIndex(a)==c||b.insert(a,c):null!=d&&(c=d.getIndex(a),d.remove(c));this.contains(d)||null==b?null==b&&this.cellRemoved(a):this.cellAdded(a);return d};
+mxGraphModel.prototype.getChildCount=function(a){return null!=a?a.getChildCount():0};mxGraphModel.prototype.getChildAt=function(a,b){return null!=a?a.getChildAt(b):null};mxGraphModel.prototype.getChildren=function(a){return null!=a?a.children:null};mxGraphModel.prototype.getChildVertices=function(a){return this.getChildCells(a,!0,!1)};mxGraphModel.prototype.getChildEdges=function(a){return this.getChildCells(a,!1,!0)};
+mxGraphModel.prototype.getChildCells=function(a,b,c){b=null!=b?b:!1;c=null!=c?c:!1;for(var d=this.getChildCount(a),e=[],f=0;f<d;f++){var g=this.getChildAt(a,f);(!c&&!b||c&&this.isEdge(g)||b&&this.isVertex(g))&&e.push(g)}return e};mxGraphModel.prototype.getTerminal=function(a,b){return null!=a?a.getTerminal(b):null};
+mxGraphModel.prototype.setTerminal=function(a,b,c){var d=b!=this.getTerminal(a,c);this.execute(new mxTerminalChange(this,a,b,c));this.maintainEdgeParent&&d&&this.updateEdgeParent(a,this.getRoot());return b};mxGraphModel.prototype.setTerminals=function(a,b,c){this.beginUpdate();try{this.setTerminal(a,b,!0),this.setTerminal(a,c,!1)}finally{this.endUpdate()}};
+mxGraphModel.prototype.terminalForCellChanged=function(a,b,c){var d=this.getTerminal(a,c);null!=b?b.insertEdge(a,c):null!=d&&d.removeEdge(a,c);return d};mxGraphModel.prototype.getEdgeCount=function(a){return null!=a?a.getEdgeCount():0};mxGraphModel.prototype.getEdgeAt=function(a,b){return null!=a?a.getEdgeAt(b):null};mxGraphModel.prototype.getDirectedEdgeCount=function(a,b,c){for(var d=0,e=this.getEdgeCount(a),f=0;f<e;f++){var g=this.getEdgeAt(a,f);g!=c&&this.getTerminal(g,b)==a&&d++}return d};
+mxGraphModel.prototype.getConnections=function(a){return this.getEdges(a,!0,!0,!1)};mxGraphModel.prototype.getIncomingEdges=function(a){return this.getEdges(a,!0,!1,!1)};mxGraphModel.prototype.getOutgoingEdges=function(a){return this.getEdges(a,!1,!0,!1)};
+mxGraphModel.prototype.getEdges=function(a,b,c,d){b=null!=b?b:!0;c=null!=c?c:!0;d=null!=d?d:!0;for(var e=this.getEdgeCount(a),f=[],g=0;g<e;g++){var k=this.getEdgeAt(a,g),l=this.getTerminal(k,!0),m=this.getTerminal(k,!1);(d&&l==m||l!=m&&(b&&m==a||c&&l==a))&&f.push(k)}return f};
+mxGraphModel.prototype.getEdgesBetween=function(a,b,c){c=null!=c?c:!1;var d=this.getEdgeCount(a),e=this.getEdgeCount(b),f=a,g=d;e<d&&(g=e,f=b);d=[];for(e=0;e<g;e++){var k=this.getEdgeAt(f,e),l=this.getTerminal(k,!0),m=this.getTerminal(k,!1),n=m==a&&l==b;(l==a&&m==b||!c&&n)&&d.push(k)}return d};
+mxGraphModel.prototype.getOpposites=function(a,b,c,d){c=null!=c?c:!0;d=null!=d?d:!0;var e=[];if(null!=a)for(var f=0;f<a.length;f++){var g=this.getTerminal(a[f],!0),k=this.getTerminal(a[f],!1);g==b&&null!=k&&k!=b&&d?e.push(k):k==b&&null!=g&&g!=b&&c&&e.push(g)}return e};
+mxGraphModel.prototype.getTopmostCells=function(a){for(var b=new mxDictionary,c=[],d=0;d<a.length;d++)b.put(a[d],!0);for(d=0;d<a.length;d++){for(var e=a[d],f=!0,g=this.getParent(e);null!=g;){if(b.get(g)){f=!1;break}g=this.getParent(g)}f&&c.push(e)}return c};mxGraphModel.prototype.isVertex=function(a){return null!=a?a.isVertex():!1};mxGraphModel.prototype.isEdge=function(a){return null!=a?a.isEdge():!1};mxGraphModel.prototype.isConnectable=function(a){return null!=a?a.isConnectable():!1};
+mxGraphModel.prototype.getValue=function(a){return null!=a?a.getValue():null};mxGraphModel.prototype.setValue=function(a,b){this.execute(new mxValueChange(this,a,b));return b};mxGraphModel.prototype.valueForCellChanged=function(a,b){return a.valueChanged(b)};mxGraphModel.prototype.getGeometry=function(a){return null!=a?a.getGeometry():null};mxGraphModel.prototype.setGeometry=function(a,b){b!=this.getGeometry(a)&&this.execute(new mxGeometryChange(this,a,b));return b};
+mxGraphModel.prototype.geometryForCellChanged=function(a,b){var c=this.getGeometry(a);a.setGeometry(b);return c};mxGraphModel.prototype.getStyle=function(a){return null!=a?a.getStyle():null};mxGraphModel.prototype.setStyle=function(a,b){b!=this.getStyle(a)&&this.execute(new mxStyleChange(this,a,b));return b};mxGraphModel.prototype.styleForCellChanged=function(a,b){var c=this.getStyle(a);a.setStyle(b);return c};mxGraphModel.prototype.isCollapsed=function(a){return null!=a?a.isCollapsed():!1};
+mxGraphModel.prototype.setCollapsed=function(a,b){b!=this.isCollapsed(a)&&this.execute(new mxCollapseChange(this,a,b));return b};mxGraphModel.prototype.collapsedStateForCellChanged=function(a,b){var c=this.isCollapsed(a);a.setCollapsed(b);return c};mxGraphModel.prototype.isVisible=function(a){return null!=a?a.isVisible():!1};mxGraphModel.prototype.setVisible=function(a,b){b!=this.isVisible(a)&&this.execute(new mxVisibleChange(this,a,b));return b};
+mxGraphModel.prototype.visibleStateForCellChanged=function(a,b){var c=this.isVisible(a);a.setVisible(b);return c};mxGraphModel.prototype.execute=function(a){a.execute();this.beginUpdate();this.currentEdit.add(a);this.fireEvent(new mxEventObject(mxEvent.EXECUTE,"change",a));this.fireEvent(new mxEventObject(mxEvent.EXECUTED,"change",a));this.endUpdate()};mxGraphModel.prototype.beginUpdate=function(){this.updateLevel++;this.fireEvent(new mxEventObject(mxEvent.BEGIN_UPDATE));1==this.updateLe [...]
+mxGraphModel.prototype.endUpdate=function(){this.updateLevel--;0==this.updateLevel&&this.fireEvent(new mxEventObject(mxEvent.END_EDIT));if(!this.endingUpdate){this.endingUpdate=0==this.updateLevel;this.fireEvent(new mxEventObject(mxEvent.END_UPDATE,"edit",this.currentEdit));try{if(this.endingUpdate&&!this.currentEdit.isEmpty()){this.fireEvent(new mxEventObject(mxEvent.BEFORE_UNDO,"edit",this.currentEdit));var a=this.currentEdit;this.currentEdit=this.createUndoableEdit();a.notify();this.f [...]
+"edit",a))}}finally{this.endingUpdate=!1}}};mxGraphModel.prototype.createUndoableEdit=function(){var a=new mxUndoableEdit(this,!0);a.notify=function(){a.source.fireEvent(new mxEventObject(mxEvent.CHANGE,"edit",a,"changes",a.changes));a.source.fireEvent(new mxEventObject(mxEvent.NOTIFY,"edit",a,"changes",a.changes))};return a};
+mxGraphModel.prototype.mergeChildren=function(a,b,c){c=null!=c?c:!0;this.beginUpdate();try{var d={};this.mergeChildrenImpl(a,b,c,d);for(var e in d){var f=d[e],g=this.getTerminal(f,!0);null!=g&&(g=d[mxCellPath.create(g)],this.setTerminal(f,g,!0));g=this.getTerminal(f,!1);null!=g&&(g=d[mxCellPath.create(g)],this.setTerminal(f,g,!1))}}finally{this.endUpdate()}};
+mxGraphModel.prototype.mergeChildrenImpl=function(a,b,c,d){this.beginUpdate();try{for(var e=a.getChildCount(),f=0;f<e;f++){var g=a.getChildAt(f);if("function"==typeof g.getId){var k=g.getId(),l=null==k||this.isEdge(g)&&c?null:this.getCell(k);if(null==l){var m=g.clone();m.setId(k);m.setTerminal(g.getTerminal(!0),!0);m.setTerminal(g.getTerminal(!1),!1);l=b.insert(m);this.cellAdded(l)}d[mxCellPath.create(g)]=l;this.mergeChildrenImpl(g,l,c,d)}}}finally{this.endUpdate()}};
+mxGraphModel.prototype.getParents=function(a){var b=[];if(null!=a)for(var c=new mxDictionary,d=0;d<a.length;d++){var e=this.getParent(a[d]);null==e||c.get(e)||(c.put(e,!0),b.push(e))}return b};mxGraphModel.prototype.cloneCell=function(a){return null!=a?this.cloneCells([a],!0)[0]:null};
+mxGraphModel.prototype.cloneCells=function(a,b,c){c=null!=c?c:{};for(var d=[],e=0;e<a.length;e++)null!=a[e]?d.push(this.cloneCellImpl(a[e],c,b)):d.push(null);for(e=0;e<d.length;e++)null!=d[e]&&this.restoreClone(d[e],a[e],c);return d};mxGraphModel.prototype.cloneCellImpl=function(a,b,c){var d=this.cellCloned(a);b[mxObjectIdentity.get(a)]=d;if(c){c=this.getChildCount(a);for(var e=0;e<c;e++){var f=this.cloneCellImpl(this.getChildAt(a,e),b,!0);d.insert(f)}}return d};
+mxGraphModel.prototype.cellCloned=function(a){return a.clone()};mxGraphModel.prototype.restoreClone=function(a,b,c){var d=this.getTerminal(b,!0);null!=d&&(d=c[mxObjectIdentity.get(d)],null!=d&&d.insertEdge(a,!0));d=this.getTerminal(b,!1);null!=d&&(d=c[mxObjectIdentity.get(d)],null!=d&&d.insertEdge(a,!1));for(var d=this.getChildCount(a),e=0;e<d;e++)this.restoreClone(this.getChildAt(a,e),this.getChildAt(b,e),c)};function mxRootChange(a,b){this.model=a;this.previous=this.root=b}
+mxRootChange.prototype.execute=function(){this.root=this.previous;this.previous=this.model.rootChanged(this.previous)};function mxChildChange(a,b,c,d){this.model=a;this.previous=this.parent=b;this.child=c;this.previousIndex=this.index=d}
+mxChildChange.prototype.execute=function(){var a=this.model.getParent(this.child),b=null!=a?a.getIndex(this.child):0;null==this.previous&&this.connect(this.child,!1);a=this.model.parentForCellChanged(this.child,this.previous,this.previousIndex);null!=this.previous&&this.connect(this.child,!0);this.parent=this.previous;this.previous=a;this.index=this.previousIndex;this.previousIndex=b};
+mxChildChange.prototype.connect=function(a,b){b=null!=b?b:!0;var c=a.getTerminal(!0),d=a.getTerminal(!1);null!=c&&(b?this.model.terminalForCellChanged(a,c,!0):this.model.terminalForCellChanged(a,null,!0));null!=d&&(b?this.model.terminalForCellChanged(a,d,!1):this.model.terminalForCellChanged(a,null,!1));a.setTerminal(c,!0);a.setTerminal(d,!1);c=this.model.getChildCount(a);for(d=0;d<c;d++)this.connect(this.model.getChildAt(a,d),b)};
+function mxTerminalChange(a,b,c,d){this.model=a;this.cell=b;this.previous=this.terminal=c;this.source=d}mxTerminalChange.prototype.execute=function(){this.terminal=this.previous;this.previous=this.model.terminalForCellChanged(this.cell,this.previous,this.source)};function mxValueChange(a,b,c){this.model=a;this.cell=b;this.previous=this.value=c}mxValueChange.prototype.execute=function(){this.value=this.previous;this.previous=this.model.valueForCellChanged(this.cell,this.previous)};
+function mxStyleChange(a,b,c){this.model=a;this.cell=b;this.previous=this.style=c}mxStyleChange.prototype.execute=function(){this.style=this.previous;this.previous=this.model.styleForCellChanged(this.cell,this.previous)};function mxGeometryChange(a,b,c){this.model=a;this.cell=b;this.previous=this.geometry=c}mxGeometryChange.prototype.execute=function(){this.geometry=this.previous;this.previous=this.model.geometryForCellChanged(this.cell,this.previous)};
+function mxCollapseChange(a,b,c){this.model=a;this.cell=b;this.previous=this.collapsed=c}mxCollapseChange.prototype.execute=function(){this.collapsed=this.previous;this.previous=this.model.collapsedStateForCellChanged(this.cell,this.previous)};function mxVisibleChange(a,b,c){this.model=a;this.cell=b;this.previous=this.visible=c}mxVisibleChange.prototype.execute=function(){this.visible=this.previous;this.previous=this.model.visibleStateForCellChanged(this.cell,this.previous)};
+function mxCellAttributeChange(a,b,c){this.cell=a;this.attribute=b;this.previous=this.value=c}mxCellAttributeChange.prototype.execute=function(){var a=this.cell.getAttribute(this.attribute);null==this.previous?this.cell.value.removeAttribute(this.attribute):this.cell.setAttribute(this.attribute,this.previous);this.previous=a};function mxCell(a,b,c){this.value=a;this.setGeometry(b);this.setStyle(c);if(null!=this.onInit)this.onInit()}mxCell.prototype.id=null;mxCell.prototype.value=null;
+mxCell.prototype.geometry=null;mxCell.prototype.style=null;mxCell.prototype.vertex=!1;mxCell.prototype.edge=!1;mxCell.prototype.connectable=!0;mxCell.prototype.visible=!0;mxCell.prototype.collapsed=!1;mxCell.prototype.parent=null;mxCell.prototype.source=null;mxCell.prototype.target=null;mxCell.prototype.children=null;mxCell.prototype.edges=null;mxCell.prototype.mxTransient="id value parent source target children edges".split(" ");mxCell.prototype.getId=function(){return this.id};
+mxCell.prototype.setId=function(a){this.id=a};mxCell.prototype.getValue=function(){return this.value};mxCell.prototype.setValue=function(a){this.value=a};mxCell.prototype.valueChanged=function(a){var b=this.getValue();this.setValue(a);return b};mxCell.prototype.getGeometry=function(){return this.geometry};mxCell.prototype.setGeometry=function(a){this.geometry=a};mxCell.prototype.getStyle=function(){return this.style};mxCell.prototype.setStyle=function(a){this.style=a};
+mxCell.prototype.isVertex=function(){return 0!=this.vertex};mxCell.prototype.setVertex=function(a){this.vertex=a};mxCell.prototype.isEdge=function(){return 0!=this.edge};mxCell.prototype.setEdge=function(a){this.edge=a};mxCell.prototype.isConnectable=function(){return 0!=this.connectable};mxCell.prototype.setConnectable=function(a){this.connectable=a};mxCell.prototype.isVisible=function(){return 0!=this.visible};mxCell.prototype.setVisible=function(a){this.visible=a};
+mxCell.prototype.isCollapsed=function(){return 0!=this.collapsed};mxCell.prototype.setCollapsed=function(a){this.collapsed=a};mxCell.prototype.getParent=function(){return this.parent};mxCell.prototype.setParent=function(a){this.parent=a};mxCell.prototype.getTerminal=function(a){return a?this.source:this.target};mxCell.prototype.setTerminal=function(a,b){b?this.source=a:this.target=a;return a};mxCell.prototype.getChildCount=function(){return null==this.children?0:this.children.length};
+mxCell.prototype.getIndex=function(a){return mxUtils.indexOf(this.children,a)};mxCell.prototype.getChildAt=function(a){return null==this.children?null:this.children[a]};mxCell.prototype.insert=function(a,b){null!=a&&(null==b&&(b=this.getChildCount(),a.getParent()==this&&b--),a.removeFromParent(),a.setParent(this),null==this.children?(this.children=[],this.children.push(a)):this.children.splice(b,0,a));return a};
+mxCell.prototype.remove=function(a){var b=null;null!=this.children&&0<=a&&(b=this.getChildAt(a),null!=b&&(this.children.splice(a,1),b.setParent(null)));return b};mxCell.prototype.removeFromParent=function(){if(null!=this.parent){var a=this.parent.getIndex(this);this.parent.remove(a)}};mxCell.prototype.getEdgeCount=function(){return null==this.edges?0:this.edges.length};mxCell.prototype.getEdgeIndex=function(a){return mxUtils.indexOf(this.edges,a)};
+mxCell.prototype.getEdgeAt=function(a){return null==this.edges?null:this.edges[a]};mxCell.prototype.insertEdge=function(a,b){null!=a&&(a.removeFromTerminal(b),a.setTerminal(this,b),null==this.edges||a.getTerminal(!b)!=this||0>mxUtils.indexOf(this.edges,a))&&(null==this.edges&&(this.edges=[]),this.edges.push(a));return a};mxCell.prototype.removeEdge=function(a,b){if(null!=a){if(a.getTerminal(!b)!=this&&null!=this.edges){var c=this.getEdgeIndex(a);0<=c&&this.edges.splice(c,1)}a.setTerminal [...]
+mxCell.prototype.removeFromTerminal=function(a){var b=this.getTerminal(a);null!=b&&b.removeEdge(this,a)};mxCell.prototype.hasAttribute=function(a){var b=this.getValue();return null!=b&&b.nodeType==mxConstants.NODETYPE_ELEMENT&&b.hasAttribute?b.hasAttribute(a):null!=b.getAttribute(a)};mxCell.prototype.getAttribute=function(a,b){var c=this.getValue();return(null!=c&&c.nodeType==mxConstants.NODETYPE_ELEMENT?c.getAttribute(a):null)||b};
+mxCell.prototype.setAttribute=function(a,b){var c=this.getValue();null!=c&&c.nodeType==mxConstants.NODETYPE_ELEMENT&&c.setAttribute(a,b)};mxCell.prototype.clone=function(){var a=mxUtils.clone(this,this.mxTransient);a.setValue(this.cloneValue());return a};mxCell.prototype.cloneValue=function(){var a=this.getValue();null!=a&&("function"==typeof a.clone?a=a.clone():isNaN(a.nodeType)||(a=a.cloneNode(!0)));return a};function mxGeometry(a,b,c,d){mxRectangle.call(this,a,b,c,d)}mxGeometry.protot [...]
+mxGeometry.prototype.constructor=mxGeometry;mxGeometry.prototype.TRANSLATE_CONTROL_POINTS=!0;mxGeometry.prototype.alternateBounds=null;mxGeometry.prototype.sourcePoint=null;mxGeometry.prototype.targetPoint=null;mxGeometry.prototype.points=null;mxGeometry.prototype.offset=null;mxGeometry.prototype.relative=!1;
+mxGeometry.prototype.swap=function(){if(null!=this.alternateBounds){var a=new mxRectangle(this.x,this.y,this.width,this.height);this.x=this.alternateBounds.x;this.y=this.alternateBounds.y;this.width=this.alternateBounds.width;this.height=this.alternateBounds.height;this.alternateBounds=a}};mxGeometry.prototype.getTerminalPoint=function(a){return a?this.sourcePoint:this.targetPoint};mxGeometry.prototype.setTerminalPoint=function(a,b){b?this.sourcePoint=a:this.targetPoint=a;return a};
+mxGeometry.prototype.rotate=function(a,b){var c=mxUtils.toRadians(a),d=Math.cos(c),c=Math.sin(c);if(!this.relative){var e=new mxPoint(this.getCenterX(),this.getCenterY()),e=mxUtils.getRotatedPoint(e,d,c,b);this.x=Math.round(e.x-this.width/2);this.y=Math.round(e.y-this.height/2)}null!=this.sourcePoint&&(e=mxUtils.getRotatedPoint(this.sourcePoint,d,c,b),this.sourcePoint.x=Math.round(e.x),this.sourcePoint.y=Math.round(e.y));null!=this.targetPoint&&(e=mxUtils.getRotatedPoint(this.targetPoint [...]
+Math.round(e.x),this.targetPoint.y=Math.round(e.y));if(null!=this.points)for(var f=0;f<this.points.length;f++)null!=this.points[f]&&(e=mxUtils.getRotatedPoint(this.points[f],d,c,b),this.points[f].x=Math.round(e.x),this.points[f].y=Math.round(e.y))};
+mxGeometry.prototype.translate=function(a,b){a=parseFloat(a);b=parseFloat(b);this.relative||(this.x=parseFloat(this.x)+a,this.y=parseFloat(this.y)+b);null!=this.sourcePoint&&(this.sourcePoint.x=parseFloat(this.sourcePoint.x)+a,this.sourcePoint.y=parseFloat(this.sourcePoint.y)+b);null!=this.targetPoint&&(this.targetPoint.x=parseFloat(this.targetPoint.x)+a,this.targetPoint.y=parseFloat(this.targetPoint.y)+b);if(this.TRANSLATE_CONTROL_POINTS&&null!=this.points)for(var c=0;c<this.points.leng [...]
+this.points[c]&&(this.points[c].x=parseFloat(this.points[c].x)+a,this.points[c].y=parseFloat(this.points[c].y)+b)};
+mxGeometry.prototype.scale=function(a,b,c){a=parseFloat(a);b=parseFloat(b);null!=this.sourcePoint&&(this.sourcePoint.x=parseFloat(this.sourcePoint.x)*a,this.sourcePoint.y=parseFloat(this.sourcePoint.y)*b);null!=this.targetPoint&&(this.targetPoint.x=parseFloat(this.targetPoint.x)*a,this.targetPoint.y=parseFloat(this.targetPoint.y)*b);if(null!=this.points)for(var d=0;d<this.points.length;d++)null!=this.points[d]&&(this.points[d].x=parseFloat(this.points[d].x)*a,this.points[d].y=parseFloat( [...]
+b);this.relative||(this.x=parseFloat(this.x)*a,this.y=parseFloat(this.y)*b,c&&(b=a=Math.min(a,b)),this.width=parseFloat(this.width)*a,this.height=parseFloat(this.height)*b)};
+mxGeometry.prototype.equals=function(a){return mxRectangle.prototype.equals.apply(this,arguments)&&this.relative==a.relative&&(null==this.sourcePoint&&null==a.sourcePoint||null!=this.sourcePoint&&this.sourcePoint.equals(a.sourcePoint))&&(null==this.targetPoint&&null==a.targetPoint||null!=this.targetPoint&&this.targetPoint.equals(a.targetPoint))&&(null==this.points&&null==a.points||null!=this.points&&mxUtils.equalPoints(this.points,a.points))&&(null==this.alternateBounds&&null==a.alternat [...]
+null!=this.alternateBounds&&this.alternateBounds.equals(a.alternateBounds))&&(null==this.offset&&null==a.offset||null!=this.offset&&this.offset.equals(a.offset))};
+var mxCellPath={PATH_SEPARATOR:".",create:function(a){var b="";if(null!=a)for(var c=a.getParent();null!=c;)b=c.getIndex(a)+mxCellPath.PATH_SEPARATOR+b,a=c,c=a.getParent();a=b.length;1<a&&(b=b.substring(0,a-1));return b},getParentPath:function(a){if(null!=a){var b=a.lastIndexOf(mxCellPath.PATH_SEPARATOR);if(0<=b)return a.substring(0,b);if(0<a.length)return""}return null},resolve:function(a,b){var c=a;if(null!=b)for(var d=b.split(mxCellPath.PATH_SEPARATOR),e=0;e<d.length;e++)c=c.getChildAt [...]
+return c},compare:function(a,b){for(var c=Math.min(a.length,b.length),d=0,e=0;e<c;e++)if(a[e]!=b[e]){0==a[e].length||0==b[e].length?d=a[e]==b[e]?0:a[e]>b[e]?1:-1:(c=parseInt(a[e]),e=parseInt(b[e]),d=c==e?0:c>e?1:-1);break}0==d&&(c=a.length,e=b.length,c!=e&&(d=c>e?1:-1));return d}},mxPerimeter={RectanglePerimeter:function(a,b,c,d){b=a.getCenterX();var e=a.getCenterY(),f=Math.atan2(c.y-e,c.x-b),g=new mxPoint(0,0),k=Math.PI,l=Math.PI/2-f,m=Math.atan2(a.height,a.width);f<-k+m||f>k-m?(g.x=a.x [...]
+Math.tan(f)/2):f<-m?(g.y=a.y,g.x=b-a.height*Math.tan(l)/2):f<m?(g.x=a.x+a.width,g.y=e+a.width*Math.tan(f)/2):(g.y=a.y+a.height,g.x=b+a.height*Math.tan(l)/2);d&&(c.x>=a.x&&c.x<=a.x+a.width?g.x=c.x:c.y>=a.y&&c.y<=a.y+a.height&&(g.y=c.y),c.x<a.x?g.x=a.x:c.x>a.x+a.width&&(g.x=a.x+a.width),c.y<a.y?g.y=a.y:c.y>a.y+a.height&&(g.y=a.y+a.height));return g},EllipsePerimeter:function(a,b,c,d){var e=a.x,f=a.y,g=a.width/2,k=a.height/2,l=e+g,m=f+k;b=c.x;c=c.y;var n=parseInt(b-l),p=parseInt(c-m);if(0== [...]
+m+k*p/Math.abs(p));if(0==n&&0==p)return new mxPoint(b,c);if(d){if(c>=f&&c<=f+a.height)return a=c-m,a=Math.sqrt(g*g*(1-a*a/(k*k)))||0,b<=e&&(a=-a),new mxPoint(l+a,c);if(b>=e&&b<=e+a.width)return a=b-l,a=Math.sqrt(k*k*(1-a*a/(g*g)))||0,c<=f&&(a=-a),new mxPoint(b,m+a)}e=p/n;m-=e*l;f=g*g*e*e+k*k;a=-2*l*f;k=Math.sqrt(a*a-4*f*(g*g*e*e*l*l+k*k*l*l-g*g*k*k));g=(-a+k)/(2*f);l=(-a-k)/(2*f);k=e*g+m;m=e*l+m;Math.sqrt(Math.pow(g-b,2)+Math.pow(k-c,2))<Math.sqrt(Math.pow(l-b,2)+Math.pow(m-c,2))?(b=g,c= [...]
+m);return new mxPoint(b,c)},RhombusPerimeter:function(a,b,c,d){b=a.x;var e=a.y,f=a.width;a=a.height;var g=b+f/2,k=e+a/2,l=c.x;c=c.y;if(g==l)return k>c?new mxPoint(g,e):new mxPoint(g,e+a);if(k==c)return g>l?new mxPoint(b,k):new mxPoint(b+f,k);var m=g,n=k;d&&(l>=b&&l<=b+f?m=l:c>=e&&c<=e+a&&(n=c));return l<g?c<k?mxUtils.intersection(l,c,m,n,g,e,b,k):mxUtils.intersection(l,c,m,n,g,e+a,b,k):c<k?mxUtils.intersection(l,c,m,n,g,e,b+f,k):mxUtils.intersection(l,c,m,n,g,e+a,b+f,k)},TrianglePerimete [...]
+b,c,d){b=null!=b?b.style[mxConstants.STYLE_DIRECTION]:null;var e=b==mxConstants.DIRECTION_NORTH||b==mxConstants.DIRECTION_SOUTH,f=a.x,g=a.y,k=a.width,l=a.height;a=f+k/2;var m=g+l/2,n=new mxPoint(f,g),p=new mxPoint(f+k,m),q=new mxPoint(f,g+l);b==mxConstants.DIRECTION_NORTH?(n=q,p=new mxPoint(a,g),q=new mxPoint(f+k,g+l)):b==mxConstants.DIRECTION_SOUTH?(p=new mxPoint(a,g+l),q=new mxPoint(f+k,g)):b==mxConstants.DIRECTION_WEST&&(n=new mxPoint(f+k,g),p=new mxPoint(f,m),q=new mxPoint(f+k,g+l)); [...]
+a,t=c.y-m,r=e?Math.atan2(r,t):Math.atan2(t,r),t=e?Math.atan2(k,l):Math.atan2(l,k);(b==mxConstants.DIRECTION_NORTH||b==mxConstants.DIRECTION_WEST?r>-t&&r<t:r<-Math.PI+t||r>Math.PI-t)?c=d&&(e&&c.x>=n.x&&c.x<=q.x||!e&&c.y>=n.y&&c.y<=q.y)?e?new mxPoint(c.x,n.y):new mxPoint(n.x,c.y):b==mxConstants.DIRECTION_NORTH?new mxPoint(f+k/2+l*Math.tan(r)/2,g+l):b==mxConstants.DIRECTION_SOUTH?new mxPoint(f+k/2-l*Math.tan(r)/2,g):b==mxConstants.DIRECTION_WEST?new mxPoint(f+k,g+l/2+k*Math.tan(r)/2):new mx [...]
+l/2-k*Math.tan(r)/2):(d&&(d=new mxPoint(a,m),c.y>=g&&c.y<=g+l?(d.x=e?a:b==mxConstants.DIRECTION_WEST?f+k:f,d.y=c.y):c.x>=f&&c.x<=f+k&&(d.x=c.x,d.y=e?b==mxConstants.DIRECTION_NORTH?g+l:g:m),a=d.x,m=d.y),c=e&&c.x<=f+k/2||!e&&c.y<=g+l/2?mxUtils.intersection(c.x,c.y,a,m,n.x,n.y,p.x,p.y):mxUtils.intersection(c.x,c.y,a,m,p.x,p.y,q.x,q.y));null==c&&(c=new mxPoint(a,m));return c},HexagonPerimeter:function(a,b,c,d){var e=a.x,f=a.y,g=a.width,k=a.height,l=a.getCenterX();a=a.getCenterY();var m=c.x,n [...]
+a,m-l),q=Math.PI,r=Math.PI/2;new mxPoint(l,a);b=null!=b?mxUtils.getValue(b.style,mxConstants.STYLE_DIRECTION,mxConstants.DIRECTION_EAST):mxConstants.DIRECTION_EAST;var t=b==mxConstants.DIRECTION_NORTH||b==mxConstants.DIRECTION_SOUTH;b=new mxPoint;var u=new mxPoint;if(m<e&&n<f||m<e&&n>f+k||m>e+g&&n<f||m>e+g&&n>f+k)d=!1;if(d){if(t){if(m==l){if(n<=f)return new mxPoint(l,f);if(n>=f+k)return new mxPoint(l,f+k)}else if(m<e){if(n==f+k/4)return new mxPoint(e,f+k/4);if(n==f+3*k/4)return new mxPoi [...]
+k/4)}else if(m>e+g){if(n==f+k/4)return new mxPoint(e+g,f+k/4);if(n==f+3*k/4)return new mxPoint(e+g,f+3*k/4)}else if(m==e){if(n<a)return new mxPoint(e,f+k/4);if(n>a)return new mxPoint(e,f+3*k/4)}else if(m==e+g){if(n<a)return new mxPoint(e+g,f+k/4);if(n>a)return new mxPoint(e+g,f+3*k/4)}if(n==f)return new mxPoint(l,f);if(n==f+k)return new mxPoint(l,f+k);m<l?n>f+k/4&&n<f+3*k/4?(b=new mxPoint(e,f),u=new mxPoint(e,f+k)):n<f+k/4?(b=new mxPoint(e-Math.floor(.5*g),f+Math.floor(.5*k)),u=new mxPoi [...]
+Math.floor(.25*k))):n>f+3*k/4&&(b=new mxPoint(e-Math.floor(.5*g),f+Math.floor(.5*k)),u=new mxPoint(e+g,f+Math.floor(1.25*k))):m>l&&(n>f+k/4&&n<f+3*k/4?(b=new mxPoint(e+g,f),u=new mxPoint(e+g,f+k)):n<f+k/4?(b=new mxPoint(e,f-Math.floor(.25*k)),u=new mxPoint(e+Math.floor(1.5*g),f+Math.floor(.5*k))):n>f+3*k/4&&(b=new mxPoint(e+Math.floor(1.5*g),f+Math.floor(.5*k)),u=new mxPoint(e,f+Math.floor(1.25*k))))}else{if(n==a){if(m<=e)return new mxPoint(e,f+k/2);if(m>=e+g)return new mxPoint(e+g,f+k/2 [...]
+f){if(m==e+g/4)return new mxPoint(e+g/4,f);if(m==e+3*g/4)return new mxPoint(e+3*g/4,f)}else if(n>f+k){if(m==e+g/4)return new mxPoint(e+g/4,f+k);if(m==e+3*g/4)return new mxPoint(e+3*g/4,f+k)}else if(n==f){if(m<l)return new mxPoint(e+g/4,f);if(m>l)return new mxPoint(e+3*g/4,f)}else if(n==f+k){if(m<l)return new mxPoint(e+g/4,f+k);if(n>a)return new mxPoint(e+3*g/4,f+k)}if(m==e)return new mxPoint(e,a);if(m==e+g)return new mxPoint(e+g,a);n<a?m>e+g/4&&m<e+3*g/4?(b=new mxPoint(e,f),u=new mxPoint [...]
+m<e+g/4?(b=new mxPoint(e-Math.floor(.25*g),f+k),u=new mxPoint(e+Math.floor(.5*g),f-Math.floor(.5*k))):m>e+3*g/4&&(b=new mxPoint(e+Math.floor(.5*g),f-Math.floor(.5*k)),u=new mxPoint(e+Math.floor(1.25*g),f+k)):n>a&&(m>e+g/4&&m<e+3*g/4?(b=new mxPoint(e,f+k),u=new mxPoint(e+g,f+k)):m<e+g/4?(b=new mxPoint(e-Math.floor(.25*g),f),u=new mxPoint(e+Math.floor(.5*g),f+Math.floor(1.5*k))):m>e+3*g/4&&(b=new mxPoint(e+Math.floor(.5*g),f+Math.floor(1.5*k)),u=new mxPoint(e+Math.floor(1.25*g),f)))}d=l;p= [...]
+e+g?(d=m,p=n<a?f+k:f):n>=f&&n<=f+k&&(p=n,d=m<l?e+g:e);c=mxUtils.intersection(d,p,c.x,c.y,b.x,b.y,u.x,u.y)}else{if(t){m=Math.atan2(k/4,g/2);if(p==m)return new mxPoint(e+g,f+Math.floor(.25*k));if(p==r)return new mxPoint(e+Math.floor(.5*g),f);if(p==q-m)return new mxPoint(e,f+Math.floor(.25*k));if(p==-m)return new mxPoint(e+g,f+Math.floor(.75*k));if(p==-r)return new mxPoint(e+Math.floor(.5*g),f+k);if(p==-q+m)return new mxPoint(e,f+Math.floor(.75*k));p<m&&p>-m?(b=new mxPoint(e+g,f),u=new mxPo [...]
+k)):p>m&&p<r?(b=new mxPoint(e,f-Math.floor(.25*k)),u=new mxPoint(e+Math.floor(1.5*g),f+Math.floor(.5*k))):p>r&&p<q-m?(b=new mxPoint(e-Math.floor(.5*g),f+Math.floor(.5*k)),u=new mxPoint(e+g,f-Math.floor(.25*k))):p>q-m&&p<=q||p<-q+m&&p>=-q?(b=new mxPoint(e,f),u=new mxPoint(e,f+k)):p<-m&&p>-r?(b=new mxPoint(e+Math.floor(1.5*g),f+Math.floor(.5*k)),u=new mxPoint(e,f+Math.floor(1.25*k))):p<-r&&p>-q+m&&(b=new mxPoint(e-Math.floor(.5*g),f+Math.floor(.5*k)),u=new mxPoint(e+g,f+Math.floor(1.25*k)) [...]
+Math.atan2(k/2,g/4);if(p==m)return new mxPoint(e+Math.floor(.75*g),f);if(p==q-m)return new mxPoint(e+Math.floor(.25*g),f);if(p==q||p==-q)return new mxPoint(e,f+Math.floor(.5*k));if(0==p)return new mxPoint(e+g,f+Math.floor(.5*k));if(p==-m)return new mxPoint(e+Math.floor(.75*g),f+k);if(p==-q+m)return new mxPoint(e+Math.floor(.25*g),f+k);0<p&&p<m?(b=new mxPoint(e+Math.floor(.5*g),f-Math.floor(.5*k)),u=new mxPoint(e+Math.floor(1.25*g),f+k)):p>m&&p<q-m?(b=new mxPoint(e,f),u=new mxPoint(e+g,f) [...]
+p<q?(b=new mxPoint(e-Math.floor(.25*g),f+k),u=new mxPoint(e+Math.floor(.5*g),f-Math.floor(.5*k))):0>p&&p>-m?(b=new mxPoint(e+Math.floor(.5*g),f+Math.floor(1.5*k)),u=new mxPoint(e+Math.floor(1.25*g),f)):p<-m&&p>-q+m?(b=new mxPoint(e,f+k),u=new mxPoint(e+g,f+k)):p<-q+m&&p>-q&&(b=new mxPoint(e-Math.floor(.25*g),f),u=new mxPoint(e+Math.floor(.5*g),f+Math.floor(1.5*k)))}c=mxUtils.intersection(l,a,c.x,c.y,b.x,b.y,u.x,u.y)}return null==c?new mxPoint(l,a):c}};
+function mxPrintPreview(a,b,c,d,e,f,g,k,l){this.graph=a;this.scale=null!=b?b:1/a.pageScale;this.border=null!=d?d:0;this.pageFormat=mxRectangle.fromRectangle(null!=c?c:a.pageFormat);this.title=null!=k?k:"Printer-friendly version";this.x0=null!=e?e:0;this.y0=null!=f?f:0;this.borderColor=g;this.pageSelector=null!=l?l:!0}mxPrintPreview.prototype.graph=null;mxPrintPreview.prototype.pageFormat=null;mxPrintPreview.prototype.scale=null;mxPrintPreview.prototype.border=0;
+mxPrintPreview.prototype.marginTop=0;mxPrintPreview.prototype.marginBottom=0;mxPrintPreview.prototype.x0=0;mxPrintPreview.prototype.y0=0;mxPrintPreview.prototype.autoOrigin=!0;mxPrintPreview.prototype.printOverlays=!1;mxPrintPreview.prototype.printControls=!1;mxPrintPreview.prototype.printBackgroundImage=!1;mxPrintPreview.prototype.backgroundColor="#ffffff";mxPrintPreview.prototype.borderColor=null;mxPrintPreview.prototype.title=null;mxPrintPreview.prototype.pageSelector=null;
+mxPrintPreview.prototype.wnd=null;mxPrintPreview.prototype.targetWindow=null;mxPrintPreview.prototype.pageCount=0;mxPrintPreview.prototype.clipping=!0;mxPrintPreview.prototype.getWindow=function(){return this.wnd};
+mxPrintPreview.prototype.getDoctype=function(){var a="";5==document.documentMode?a='<meta http-equiv="X-UA-Compatible" content="IE=5">':8==document.documentMode?a='<meta http-equiv="X-UA-Compatible" content="IE=8">':8<document.documentMode&&(a='\x3c!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=edge"><![endif]--\x3e');return a};mxPrintPreview.prototype.appendGraph=function(a,b,c,d,e,f){this.graph=a;this.scale=null!=b?b:1/a.pageScale;this.x0=c;this.y0=d;this.open(null,null,e,f)};
+mxPrintPreview.prototype.open=function(a,b,c,d){var e=this.graph.cellRenderer.initializeOverlay,f=null;try{this.printOverlays&&(this.graph.cellRenderer.initializeOverlay=function(a,b){b.init(a.view.getDrawPane())});this.printControls&&(this.graph.cellRenderer.initControl=function(a,b,c,d){b.dialect=a.view.graph.dialect;b.init(a.view.getDrawPane())});this.wnd=null!=b?b:this.wnd;var g=!1;null==this.wnd&&(g=!0,this.wnd=window.open());var k=this.wnd.document;if(g){var l=this.getDoctype();nul [...]
+k.writeln(l);mxClient.IS_VML?k.writeln('<html xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">'):("CSS1Compat"===document.compatMode&&k.writeln("<!DOCTYPE html>"),k.writeln("<html>"));k.writeln("<head>");this.writeHead(k,a);k.writeln("</head>");k.writeln('<body class="mxPage">')}var m=this.graph.getGraphBounds().clone(),n=this.graph.getView().getScale(),p=n/this.scale,q=this.graph.getView().getTranslate();this.autoOrigin||(this.x0-=q.x*this.scale [...]
+q.y*this.scale,m.width+=m.x,m.height+=m.y,m.x=0,this.border=m.y=0);var r=this.pageFormat.width-2*this.border,t=this.pageFormat.height-2*this.border;this.pageFormat.height+=this.marginTop+this.marginBottom;m.width/=p;m.height/=p;var u=Math.max(1,Math.ceil((m.width+this.x0)/r)),x=Math.max(1,Math.ceil((m.height+this.y0)/t));this.pageCount=u*x;var y=mxUtils.bind(this,function(){if(this.pageSelector&&(1<x||1<u)){var a=this.createPageSelector(x,u);k.body.appendChild(a);if(mxClient.IS_IE&&null= [...]
+5==k.documentMode||8==k.documentMode||7==k.documentMode){a.style.position="absolute";var b=function(){a.style.top=(k.body.scrollTop||k.documentElement.scrollTop)+10+"px"};mxEvent.addListener(this.wnd,"scroll",function(a){b()});mxEvent.addListener(this.wnd,"resize",function(a){b()})}}}),A=mxUtils.bind(this,function(a,b){null!=this.borderColor&&(a.style.borderColor=this.borderColor,a.style.borderStyle="solid",a.style.borderWidth="1px");a.style.background=this.backgroundColor;if(c||b)a.styl [...]
+"always";g&&(mxClient.IS_IE||11<=document.documentMode||mxClient.IS_EDGE)?(k.writeln(a.outerHTML),a.parentNode.removeChild(a)):(a.parentNode.removeChild(a),k.body.appendChild(a));(c||b)&&this.addPageBreak(k)}),z=this.getCoverPages(this.pageFormat.width,this.pageFormat.height);if(null!=z)for(var v=0;v<z.length;v++)A(z[v],!0);for(var B=this.getAppendices(this.pageFormat.width,this.pageFormat.height),v=0;v<x;v++){var C=v*t/this.scale-this.y0/this.scale+(m.y-q.y*n)/n;for(a=0;a<u;a++){if(null [...]
+var F=a*r/this.scale-this.x0/this.scale+(m.x-q.x*n)/n,D=v*u+a+1,I=new mxRectangle(F,C,r,t),f=this.renderPage(this.pageFormat.width,this.pageFormat.height,0,0,mxUtils.bind(this,function(a){this.addGraphFragment(-F,-C,this.scale,D,a,I);this.printBackgroundImage&&this.insertBackgroundImage(a,-F,-C)}),D);f.setAttribute("id","mxPage-"+D);A(f,null!=B||v<x-1||a<u-1)}}if(null!=B)for(v=0;v<B.length;v++)A(B[v],v<B.length-1);g&&!d&&(this.closeDocument(),y());this.wnd.focus()}catch(E){null!=f&&null! [...]
+f.parentNode.removeChild(f)}finally{this.graph.cellRenderer.initializeOverlay=e}return this.wnd};mxPrintPreview.prototype.addPageBreak=function(a){var b=a.createElement("hr");b.className="mxPageBreak";a.body.appendChild(b)};mxPrintPreview.prototype.closeDocument=function(){if(null!=this.wnd&&null!=this.wnd.document){var a=this.wnd.document;this.writePostfix(a);a.writeln("</body>");a.writeln("</html>");a.close();mxEvent.release(a.body)}};
+mxPrintPreview.prototype.writeHead=function(a,b){null!=this.title&&a.writeln("<title>"+this.title+"</title>");mxClient.IS_VML&&a.writeln('<style type="text/css">v\\:*{behavior:url(#default#VML)}o\\:*{behavior:url(#default#VML)}</style>');mxClient.link("stylesheet",mxClient.basePath+"/css/common.css",a);a.writeln('<style type="text/css">');a.writeln("@media print {");a.writeln("  table.mxPageSelector { display: none; }");a.writeln("  hr.mxPageBreak { display: none; }");a.writeln("}");a.wr [...]
+a.writeln("  table.mxPageSelector { position: fixed; right: 10px; top: 10px;font-family: Arial; font-size:10pt; border: solid 1px darkgray;background: white; border-collapse:collapse; }");a.writeln("  table.mxPageSelector td { border: solid 1px gray; padding:4px; }");a.writeln("  body.mxPage { background: gray; }");a.writeln("}");null!=b&&a.writeln(b);a.writeln("</style>")};mxPrintPreview.prototype.writePostfix=function(a){};
+mxPrintPreview.prototype.createPageSelector=function(a,b){var c=this.wnd.document,d=c.createElement("table");d.className="mxPageSelector";d.setAttribute("border","0");for(var e=c.createElement("tbody"),f=0;f<a;f++){for(var g=c.createElement("tr"),k=0;k<b;k++){var l=f*b+k+1,m=c.createElement("td"),n=c.createElement("a");n.setAttribute("href","#mxPage-"+l);!mxClient.IS_NS||mxClient.IS_SF||mxClient.IS_GC||n.setAttribute("onclick","var page = document.getElementById('mxPage-"+l+"');page.scro [...]
+mxUtils.write(n,l,c);m.appendChild(n);g.appendChild(m)}e.appendChild(g)}d.appendChild(e);return d};
+mxPrintPreview.prototype.renderPage=function(a,b,c,d,e,f){f=this.wnd.document;var g=document.createElement("div"),k=null;try{if(0!=c||0!=d){g.style.position="relative";g.style.width=a+"px";g.style.height=b+"px";g.style.pageBreakInside="avoid";var l=document.createElement("div");l.style.position="relative";l.style.top=this.border+"px";l.style.left=this.border+"px";l.style.width=a-2*this.border+"px";l.style.height=b-2*this.border+"px";l.style.overflow="hidden";var m=document.createElement( [...]
+"relative";m.style.marginLeft=c+"px";m.style.marginTop=d+"px";8==f.documentMode&&(l.style.position="absolute",m.style.position="absolute");10==f.documentMode&&(m.style.width="100%",m.style.height="100%");l.appendChild(m);g.appendChild(l);document.body.appendChild(g);k=m}else g.style.width=a+"px",g.style.height=b+"px",g.style.overflow="hidden",g.style.pageBreakInside="avoid",8==f.documentMode&&(g.style.position="relative"),l=document.createElement("div"),l.style.width=a-2*this.border+"px" [...]
+b-2*this.border+"px",l.style.overflow="hidden",!mxClient.IS_IE||null!=f.documentMode&&5!=f.documentMode&&8!=f.documentMode&&7!=f.documentMode?(l.style.top=this.border+"px",l.style.left=this.border+"px"):(l.style.marginTop=this.border+"px",l.style.marginLeft=this.border+"px"),this.graph.dialect==mxConstants.DIALECT_VML&&(l.style.position="absolute"),g.appendChild(l),document.body.appendChild(g),k=l}catch(n){throw g.parentNode.removeChild(g),n;}e(k);return g};
+mxPrintPreview.prototype.getRoot=function(){var a=this.graph.view.currentRoot;null==a&&(a=this.graph.getModel().getRoot());return a};
+mxPrintPreview.prototype.addGraphFragment=function(a,b,c,d,e,f){var g=this.graph.getView();d=this.graph.container;this.graph.container=e;var k=g.getCanvas(),l=g.getBackgroundPane(),m=g.getDrawPane(),n=g.getOverlayPane();this.graph.dialect==mxConstants.DIALECT_SVG?g.createSvg():this.graph.dialect==mxConstants.DIALECT_VML?g.createVml():g.createHtml();var p=g.isEventsEnabled();g.setEventsEnabled(!1);var q=this.graph.isEnabled();this.graph.setEnabled(!1);var r=g.getTranslate();g.translate=ne [...]
+b);var t=this.graph.cellRenderer.redraw,u=g.states;a=g.scale;if(this.clipping){var x=new mxRectangle((f.x+r.x)*a,(f.y+r.y)*a,f.width*a/c,f.height*a/c);this.graph.cellRenderer.redraw=function(a,b,c){if(null!=a){var d=u.get(a.cell);if(null!=d&&(d=g.getBoundingBox(d,!1),null!=d&&!mxUtils.intersects(x,d)))return}t.apply(this,arguments)}}a=null;try{var y=[this.getRoot()];a=new mxTemporaryCellStates(g,c,y)}finally{if(mxClient.IS_IE)g.overlayPane.innerHTML="",g.canvas.style.overflow="hidden",g. [...]
+"relative",g.canvas.style.top=this.marginTop+"px",g.canvas.style.width=f.width+"px",g.canvas.style.height=f.height+"px";else for(c=e.firstChild;null!=c;)y=c.nextSibling,b=c.nodeName.toLowerCase(),"svg"==b?(c.style.overflow="hidden",c.style.position="relative",c.style.top=this.marginTop+"px",c.setAttribute("width",f.width),c.setAttribute("height",f.height),c.style.width="",c.style.height=""):"default"!=c.style.cursor&&"div"!=b&&c.parentNode.removeChild(c),c=y;this.printBackgroundImage&&(e [...]
+0<e.length&&(e[0].style.position="absolute"));g.overlayPane.parentNode.removeChild(g.overlayPane);this.graph.setEnabled(q);this.graph.container=d;this.graph.cellRenderer.redraw=t;g.canvas=k;g.backgroundPane=l;g.drawPane=m;g.overlayPane=n;g.translate=r;a.destroy();g.setEventsEnabled(p)}};
+mxPrintPreview.prototype.insertBackgroundImage=function(a,b,c){var d=this.graph.backgroundImage;if(null!=d){var e=document.createElement("img");e.style.position="absolute";e.style.marginLeft=Math.round(b*this.scale)+"px";e.style.marginTop=Math.round(c*this.scale)+"px";e.setAttribute("width",Math.round(this.scale*d.width));e.setAttribute("height",Math.round(this.scale*d.height));e.src=d.src;a.insertBefore(e,a.firstChild)}};mxPrintPreview.prototype.getCoverPages=function(){return null};
+mxPrintPreview.prototype.getAppendices=function(){return null};mxPrintPreview.prototype.print=function(a){a=this.open(a);null!=a&&a.print()};mxPrintPreview.prototype.close=function(){null!=this.wnd&&(this.wnd.close(),this.wnd=null)};function mxStylesheet(){this.styles={};this.putDefaultVertexStyle(this.createDefaultVertexStyle());this.putDefaultEdgeStyle(this.createDefaultEdgeStyle())}
+mxStylesheet.prototype.createDefaultVertexStyle=function(){var a={};a[mxConstants.STYLE_SHAPE]=mxConstants.SHAPE_RECTANGLE;a[mxConstants.STYLE_PERIMETER]=mxPerimeter.RectanglePerimeter;a[mxConstants.STYLE_VERTICAL_ALIGN]=mxConstants.ALIGN_MIDDLE;a[mxConstants.STYLE_ALIGN]=mxConstants.ALIGN_CENTER;a[mxConstants.STYLE_FILLCOLOR]="#C3D9FF";a[mxConstants.STYLE_STROKECOLOR]="#6482B9";a[mxConstants.STYLE_FONTCOLOR]="#774400";return a};
+mxStylesheet.prototype.createDefaultEdgeStyle=function(){var a={};a[mxConstants.STYLE_SHAPE]=mxConstants.SHAPE_CONNECTOR;a[mxConstants.STYLE_ENDARROW]=mxConstants.ARROW_CLASSIC;a[mxConstants.STYLE_VERTICAL_ALIGN]=mxConstants.ALIGN_MIDDLE;a[mxConstants.STYLE_ALIGN]=mxConstants.ALIGN_CENTER;a[mxConstants.STYLE_STROKECOLOR]="#6482B9";a[mxConstants.STYLE_FONTCOLOR]="#446299";return a};mxStylesheet.prototype.putDefaultVertexStyle=function(a){this.putCellStyle("defaultVertex",a)};
+mxStylesheet.prototype.putDefaultEdgeStyle=function(a){this.putCellStyle("defaultEdge",a)};mxStylesheet.prototype.getDefaultVertexStyle=function(){return this.styles.defaultVertex};mxStylesheet.prototype.getDefaultEdgeStyle=function(){return this.styles.defaultEdge};mxStylesheet.prototype.putCellStyle=function(a,b){this.styles[a]=b};
+mxStylesheet.prototype.getCellStyle=function(a,b){var c=b;if(null!=a&&0<a.length)for(var d=a.split(";"),c=null!=c&&";"!=a.charAt(0)?mxUtils.clone(c):{},e=0;e<d.length;e++){var f=d[e],g=f.indexOf("=");if(0<=g){var k=f.substring(0,g),f=f.substring(g+1);f==mxConstants.NONE?delete c[k]:mxUtils.isNumeric(f)?c[k]=parseFloat(f):c[k]=f}else if(f=this.styles[f],null!=f)for(k in f)c[k]=f[k]}return c};
+function mxCellState(a,b,c){this.view=a;this.cell=b;this.style=c;this.origin=new mxPoint;this.absoluteOffset=new mxPoint}mxCellState.prototype=new mxRectangle;mxCellState.prototype.constructor=mxCellState;mxCellState.prototype.view=null;mxCellState.prototype.cell=null;mxCellState.prototype.style=null;mxCellState.prototype.invalid=!0;mxCellState.prototype.origin=null;mxCellState.prototype.absolutePoints=null;mxCellState.prototype.absoluteOffset=null;mxCellState.prototype.visibleSourceState=null;
+mxCellState.prototype.visibleTargetState=null;mxCellState.prototype.terminalDistance=0;mxCellState.prototype.length=0;mxCellState.prototype.segments=null;mxCellState.prototype.shape=null;mxCellState.prototype.text=null;mxCellState.prototype.unscaledWidth=null;
+mxCellState.prototype.getPerimeterBounds=function(a,b){a=a||0;b=null!=b?b:new mxRectangle(this.x,this.y,this.width,this.height);if(null!=this.shape&&null!=this.shape.stencil&&"fixed"==this.shape.stencil.aspect){var c=this.shape.stencil.computeAspect(this.style,b.x,b.y,b.width,b.height);b.x=c.x;b.y=c.y;b.width=this.shape.stencil.w0*c.width;b.height=this.shape.stencil.h0*c.height}0!=a&&b.grow(a);return b};
+mxCellState.prototype.setAbsoluteTerminalPoint=function(a,b){b?(null==this.absolutePoints&&(this.absolutePoints=[]),0==this.absolutePoints.length?this.absolutePoints.push(a):this.absolutePoints[0]=a):null==this.absolutePoints?(this.absolutePoints=[],this.absolutePoints.push(null),this.absolutePoints.push(a)):1==this.absolutePoints.length?this.absolutePoints.push(a):this.absolutePoints[this.absolutePoints.length-1]=a};
+mxCellState.prototype.setCursor=function(a){null!=this.shape&&this.shape.setCursor(a);null!=this.text&&this.text.setCursor(a)};mxCellState.prototype.getVisibleTerminal=function(a){a=this.getVisibleTerminalState(a);return null!=a?a.cell:null};mxCellState.prototype.getVisibleTerminalState=function(a){return a?this.visibleSourceState:this.visibleTargetState};mxCellState.prototype.setVisibleTerminalState=function(a,b){b?this.visibleSourceState=a:this.visibleTargetState=a};
+mxCellState.prototype.getCellBounds=function(){return this.cellBounds};mxCellState.prototype.getPaintBounds=function(){return this.paintBounds};mxCellState.prototype.updateCachedBounds=function(){var a=this.view.translate,b=this.view.scale;this.cellBounds=new mxRectangle(this.x/b-a.x,this.y/b-a.y,this.width/b,this.height/b);this.paintBounds=mxRectangle.fromRectangle(this.cellBounds);null!=this.shape&&this.shape.isPaintBoundsInverted()&&this.paintBounds.rotate90()};
+mxCellState.prototype.setState=function(a){this.view=a.view;this.cell=a.cell;this.style=a.style;this.absolutePoints=a.absolutePoints;this.origin=a.origin;this.absoluteOffset=a.absoluteOffset;this.boundingBox=a.boundingBox;this.terminalDistance=a.terminalDistance;this.segments=a.segments;this.length=a.length;this.x=a.x;this.y=a.y;this.width=a.width;this.height=a.height;this.unscaledWidth=a.unscaledWidth};
+mxCellState.prototype.clone=function(){var a=new mxCellState(this.view,this.cell,this.style);if(null!=this.absolutePoints){a.absolutePoints=[];for(var b=0;b<this.absolutePoints.length;b++)a.absolutePoints[b]=this.absolutePoints[b].clone()}null!=this.origin&&(a.origin=this.origin.clone());null!=this.absoluteOffset&&(a.absoluteOffset=this.absoluteOffset.clone());null!=this.boundingBox&&(a.boundingBox=this.boundingBox.clone());a.terminalDistance=this.terminalDistance;a.segments=this.segment [...]
+this.length;a.x=this.x;a.y=this.y;a.width=this.width;a.height=this.height;a.unscaledWidth=this.unscaledWidth;return a};mxCellState.prototype.destroy=function(){this.view.graph.cellRenderer.destroy(this)};function mxGraphSelectionModel(a){this.graph=a;this.cells=[]}mxGraphSelectionModel.prototype=new mxEventSource;mxGraphSelectionModel.prototype.constructor=mxGraphSelectionModel;mxGraphSelectionModel.prototype.doneResource="none"!=mxClient.language?"done":"";
+mxGraphSelectionModel.prototype.updatingSelectionResource="none"!=mxClient.language?"updatingSelection":"";mxGraphSelectionModel.prototype.graph=null;mxGraphSelectionModel.prototype.singleSelection=!1;mxGraphSelectionModel.prototype.isSingleSelection=function(){return this.singleSelection};mxGraphSelectionModel.prototype.setSingleSelection=function(a){this.singleSelection=a};mxGraphSelectionModel.prototype.isSelected=function(a){return null!=a?0<=mxUtils.indexOf(this.cells,a):!1};
+mxGraphSelectionModel.prototype.isEmpty=function(){return 0==this.cells.length};mxGraphSelectionModel.prototype.clear=function(){this.changeSelection(null,this.cells)};mxGraphSelectionModel.prototype.setCell=function(a){null!=a&&this.setCells([a])};mxGraphSelectionModel.prototype.setCells=function(a){if(null!=a){this.singleSelection&&(a=[this.getFirstSelectableCell(a)]);for(var b=[],c=0;c<a.length;c++)this.graph.isCellSelectable(a[c])&&b.push(a[c]);this.changeSelection(b,this.cells)}};
+mxGraphSelectionModel.prototype.getFirstSelectableCell=function(a){if(null!=a)for(var b=0;b<a.length;b++)if(this.graph.isCellSelectable(a[b]))return a[b];return null};mxGraphSelectionModel.prototype.addCell=function(a){null!=a&&this.addCells([a])};
+mxGraphSelectionModel.prototype.addCells=function(a){if(null!=a){var b=null;this.singleSelection&&(b=this.cells,a=[this.getFirstSelectableCell(a)]);for(var c=[],d=0;d<a.length;d++)!this.isSelected(a[d])&&this.graph.isCellSelectable(a[d])&&c.push(a[d]);this.changeSelection(c,b)}};mxGraphSelectionModel.prototype.removeCell=function(a){null!=a&&this.removeCells([a])};
+mxGraphSelectionModel.prototype.removeCells=function(a){if(null!=a){for(var b=[],c=0;c<a.length;c++)this.isSelected(a[c])&&b.push(a[c]);this.changeSelection(null,b)}};mxGraphSelectionModel.prototype.changeSelection=function(a,b){if(null!=a&&0<a.length&&null!=a[0]||null!=b&&0<b.length&&null!=b[0]){var c=new mxSelectionChange(this,a,b);c.execute();var d=new mxUndoableEdit(this,!1);d.add(c);this.fireEvent(new mxEventObject(mxEvent.UNDO,"edit",d))}};
+mxGraphSelectionModel.prototype.cellAdded=function(a){null==a||this.isSelected(a)||this.cells.push(a)};mxGraphSelectionModel.prototype.cellRemoved=function(a){null!=a&&(a=mxUtils.indexOf(this.cells,a),0<=a&&this.cells.splice(a,1))};function mxSelectionChange(a,b,c){this.selectionModel=a;this.added=null!=b?b.slice():null;this.removed=null!=c?c.slice():null}
+mxSelectionChange.prototype.execute=function(){var a=mxLog.enter("mxSelectionChange.execute");window.status=mxResources.get(this.selectionModel.updatingSelectionResource)||this.selectionModel.updatingSelectionResource;if(null!=this.removed)for(var b=0;b<this.removed.length;b++)this.selectionModel.cellRemoved(this.removed[b]);if(null!=this.added)for(b=0;b<this.added.length;b++)this.selectionModel.cellAdded(this.added[b]);b=this.added;this.added=this.removed;this.removed=b;window.status=mx [...]
+this.selectionModel.doneResource;mxLog.leave("mxSelectionChange.execute",a);this.selectionModel.fireEvent(new mxEventObject(mxEvent.CHANGE,"added",this.added,"removed",this.removed))};
+function mxCellEditor(a){this.graph=a;this.zoomHandler=mxUtils.bind(this,function(){this.graph.isEditing()&&this.resize()});this.graph.view.addListener(mxEvent.SCALE,this.zoomHandler);this.graph.view.addListener(mxEvent.SCALE_AND_TRANSLATE,this.zoomHandler);this.changeHandler=mxUtils.bind(this,function(a){null!=this.editingCell&&null==this.graph.getView().getState(this.editingCell)&&this.stopEditing(!0)});this.graph.getModel().addListener(mxEvent.CHANGE,this.changeHandler)}
+mxCellEditor.prototype.graph=null;mxCellEditor.prototype.textarea=null;mxCellEditor.prototype.editingCell=null;mxCellEditor.prototype.trigger=null;mxCellEditor.prototype.modified=!1;mxCellEditor.prototype.autoSize=!0;mxCellEditor.prototype.selectText=!0;mxCellEditor.prototype.emptyLabelText=mxClient.IS_FF?"<br>":"";mxCellEditor.prototype.escapeCancelsEditing=!0;mxCellEditor.prototype.textNode="";mxCellEditor.prototype.zIndex=5;mxCellEditor.prototype.minResize=new mxRectangle(0,20);
+mxCellEditor.prototype.wordWrapPadding=mxClient.IS_QUIRKS?2:mxClient.IS_IE11?0:1;mxCellEditor.prototype.blurEnabled=!1;mxCellEditor.prototype.initialValue=null;mxCellEditor.prototype.init=function(){this.textarea=document.createElement("div");this.textarea.className="mxCellEditor mxPlainTextEditor";this.textarea.contentEditable=!0;mxClient.IS_GC&&(this.textarea.style.minHeight="1em");this.installListeners(this.textarea)};
+mxCellEditor.prototype.applyValue=function(a,b){this.graph.labelChanged(a.cell,b,this.trigger)};mxCellEditor.prototype.getInitialValue=function(a,b){var c=mxUtils.htmlEntities(this.graph.getEditingValue(a.cell,b),!1);mxClient.IS_QUIRKS||8==document.documentMode||9==document.documentMode||10==document.documentMode||(c=mxUtils.replaceTrailingNewlines(c,"<div><br></div>"));return c.replace(/\n/g,"<br>")};mxCellEditor.prototype.getCurrentValue=function(a){return mxUtils.extractTextWithWhites [...]
+mxCellEditor.prototype.installListeners=function(a){mxEvent.addListener(a,"blur",mxUtils.bind(this,function(a){this.blurEnabled&&this.focusLost(a)}));mxEvent.addListener(a,"keydown",mxUtils.bind(this,function(a){mxEvent.isConsumed(a)||(this.isStopEditingEvent(a)?(this.graph.stopEditing(!1),mxEvent.consume(a)):27==a.keyCode&&(this.graph.stopEditing(this.escapeCancelsEditing||mxEvent.isShiftDown(a)),mxEvent.consume(a)))}));var b=mxUtils.bind(this,function(b){null!=this.editingCell&&this.cl [...]
+a.innerHTML==this.getEmptyLabelText()&&(!mxClient.IS_FF||8!=b.keyCode&&46!=b.keyCode)&&(this.clearOnChange=!1,a.innerHTML="")});mxEvent.addListener(a,"keypress",b);mxEvent.addListener(a,"paste",b);b=mxUtils.bind(this,function(a){null!=this.editingCell&&(0==this.textarea.innerHTML.length||"<br>"==this.textarea.innerHTML?(this.textarea.innerHTML=this.getEmptyLabelText(),this.clearOnChange=0<this.textarea.innerHTML.length):this.clearOnChange=!1)});mxEvent.addListener(a,mxClient.IS_IE11||mxC [...]
+"keyup":"input",b);mxEvent.addListener(a,"cut",b);mxEvent.addListener(a,"paste",b);var b=mxClient.IS_IE11||mxClient.IS_IE?"keydown":"input",c=mxUtils.bind(this,function(a){null!=this.editingCell&&this.autoSize&&!mxEvent.isConsumed(a)&&(null!=this.resizeThread&&window.clearTimeout(this.resizeThread),this.resizeThread=window.setTimeout(mxUtils.bind(this,function(){this.resizeThread=null;this.resize()}),0))});mxEvent.addListener(a,b,c);9<=document.documentMode?(mxEvent.addListener(a,"DOMNod [...]
+c),mxEvent.addListener(a,"DOMNodeInserted",c)):(mxEvent.addListener(a,"cut",c),mxEvent.addListener(a,"paste",c))};mxCellEditor.prototype.isStopEditingEvent=function(a){return 113==a.keyCode||this.graph.isEnterStopsCellEditing()&&13==a.keyCode&&!mxEvent.isControlDown(a)&&!mxEvent.isShiftDown(a)};mxCellEditor.prototype.isEventSource=function(a){return mxEvent.getSource(a)==this.textarea};
+mxCellEditor.prototype.resize=function(){var a=this.graph.getView().getState(this.editingCell);if(null==a)this.stopEditing(!0);else if(null!=this.textarea){var b=this.graph.getModel().isEdge(a.cell),c=this.graph.getView().scale,d=null;if(this.autoSize&&"fill"!=a.style[mxConstants.STYLE_OVERFLOW]){var e=mxUtils.getValue(a.style,mxConstants.STYLE_LABEL_WIDTH,null),d=null!=a.text?a.text.margin:null;null==d&&(d=mxUtils.getAlignmentAsPoint(mxUtils.getValue(a.style,mxConstants.STYLE_ALIGN,mxCo [...]
+mxUtils.getValue(a.style,mxConstants.STYLE_VERTICAL_ALIGN,mxConstants.ALIGN_MIDDLE)));if(b)this.bounds=new mxRectangle(a.absoluteOffset.x,a.absoluteOffset.y,0,0),null!=e&&(e=(parseFloat(e)+2)*c,this.bounds.width=e,this.bounds.x+=d.x*e);else{var b=mxRectangle.fromRectangle(a),f=mxUtils.getValue(a.style,mxConstants.STYLE_LABEL_POSITION,mxConstants.ALIGN_CENTER),g=mxUtils.getValue(a.style,mxConstants.STYLE_VERTICAL_LABEL_POSITION,mxConstants.ALIGN_MIDDLE),b=null!=a.shape&&f==mxConstants.ALI [...]
+g==mxConstants.ALIGN_MIDDLE?a.shape.getLabelBounds(b):b;null!=e&&(b.width=parseFloat(e)*c);if(!a.view.graph.cellRenderer.legacySpacing||"width"!=a.style[mxConstants.STYLE_OVERFLOW])var f=parseInt(a.style[mxConstants.STYLE_SPACING]||2)*c,k=(parseInt(a.style[mxConstants.STYLE_SPACING_TOP]||0)+mxText.prototype.baseSpacingTop)*c+f,l=(parseInt(a.style[mxConstants.STYLE_SPACING_RIGHT]||0)+mxText.prototype.baseSpacingRight)*c+f,m=(parseInt(a.style[mxConstants.STYLE_SPACING_BOTTOM]||0)+mxText.pr [...]
+c+f,n=(parseInt(a.style[mxConstants.STYLE_SPACING_LEFT]||0)+mxText.prototype.baseSpacingLeft)*c+f,f=mxUtils.getValue(a.style,mxConstants.STYLE_LABEL_POSITION,mxConstants.ALIGN_CENTER),g=mxUtils.getValue(a.style,mxConstants.STYLE_VERTICAL_LABEL_POSITION,mxConstants.ALIGN_MIDDLE),b=new mxRectangle(b.x+n,b.y+k,b.width-(f==mxConstants.ALIGN_CENTER&&null==e?n+l:0),b.height-(g==mxConstants.ALIGN_MIDDLE?k+m:0));this.bounds=new mxRectangle(b.x+a.absoluteOffset.x,b.y+a.absoluteOffset.y,b.width,b. [...]
+(2<=this.bounds.width||2<=this.bounds.height)&&this.textarea.innerHTML!=this.getEmptyLabelText()?(this.textarea.style.wordWrap=mxConstants.WORD_WRAP,this.textarea.style.whiteSpace="normal",e=Math.round(this.bounds.width/c)+this.wordWrapPadding,this.textarea.style.width=e+"px",this.textarea.scrollWidth>e&&(this.textarea.style.width=this.textarea.scrollWidth+"px")):(this.textarea.style.whiteSpace="nowrap",this.textarea.style.width="");8==document.documentMode&&(this.textarea.style.zoom="1" [...]
+"auto");a=this.textarea.scrollWidth;e=this.textarea.scrollHeight;8==document.documentMode?(this.textarea.style.left=Math.max(0,Math.ceil((this.bounds.x-d.x*(this.bounds.width-(a+1)*c)+a*(c-1)*0+2*(d.x+.5))/c))+"px",this.textarea.style.top=Math.max(0,Math.ceil((this.bounds.y-d.y*(this.bounds.height-(e+.5)*c)+e*(c-1)*0+1*Math.abs(d.y+.5))/c))+"px",this.textarea.style.width=Math.round(a*c)+"px",this.textarea.style.height=Math.round(e*c)+"px"):mxClient.IS_QUIRKS?(this.textarea.style.left=Mat [...]
+d.x*(this.bounds.width-(a+1)*c)+a*(c-1)*0+2*(d.x+.5)))+"px",this.textarea.style.top=Math.max(0,Math.ceil(this.bounds.y-d.y*(this.bounds.height-(e+.5)*c)+e*(c-1)*0+1*Math.abs(d.y+.5)))+"px"):(this.textarea.style.left=Math.max(0,Math.round(this.bounds.x-d.x*(this.bounds.width-2))+1)+"px",this.textarea.style.top=Math.max(0,Math.round(this.bounds.y-d.y*(this.bounds.height-4)+(-1==d.y?3:0))+1)+"px")}else this.bounds=this.getEditorBounds(a),this.textarea.style.width=Math.round(this.bounds.widt [...]
+this.textarea.style.height=Math.round(this.bounds.height/c)+"px",8==document.documentMode||mxClient.IS_QUIRKS?(this.textarea.style.left=Math.round(this.bounds.x)+"px",this.textarea.style.top=Math.round(this.bounds.y)+"px"):(this.textarea.style.left=Math.max(0,Math.round(this.bounds.x+1))+"px",this.textarea.style.top=Math.max(0,Math.round(this.bounds.y+1))+"px"),this.graph.isWrapping(a.cell)&&(2<=this.bounds.width||2<=this.bounds.height)&&this.textarea.innerHTML!=this.getEmptyLabelText()? [...]
+mxConstants.WORD_WRAP,this.textarea.style.whiteSpace="normal","fill"!=a.style[mxConstants.STYLE_OVERFLOW]&&(this.textarea.style.width=Math.round(this.bounds.width/c)+this.wordWrapPadding+"px")):(this.textarea.style.whiteSpace="nowrap","fill"!=a.style[mxConstants.STYLE_OVERFLOW]&&(this.textarea.style.width=""));mxClient.IS_VML?this.textarea.style.zoom=c:(mxUtils.setPrefixedStyle(this.textarea.style,"transformOrigin","0px 0px"),mxUtils.setPrefixedStyle(this.textarea.style,"transform","scal [...]
+c+")"+(null==d?"":" translate("+100*d.x+"%,"+100*d.y+"%)")))}};mxCellEditor.prototype.focusLost=function(){this.stopEditing(!this.graph.isInvokesStopCellEditing())};mxCellEditor.prototype.getBackgroundColor=function(a){return null};
+mxCellEditor.prototype.startEditing=function(a,b){this.stopEditing(!0);null==this.textarea&&this.init();null!=this.graph.tooltipHandler&&this.graph.tooltipHandler.hideTooltip();var c=this.graph.getView().getState(a);if(null!=c){this.graph.getView();var d=mxUtils.getValue(c.style,mxConstants.STYLE_FONTSIZE,mxConstants.DEFAULT_FONTSIZE),e=mxUtils.getValue(c.style,mxConstants.STYLE_FONTFAMILY,mxConstants.DEFAULT_FONTFAMILY),f=mxUtils.getValue(c.style,mxConstants.STYLE_FONTCOLOR,"black"),g=m [...]
+mxConstants.STYLE_ALIGN,mxConstants.ALIGN_LEFT),k=(mxUtils.getValue(c.style,mxConstants.STYLE_FONTSTYLE,0)&mxConstants.FONT_BOLD)==mxConstants.FONT_BOLD,l=(mxUtils.getValue(c.style,mxConstants.STYLE_FONTSTYLE,0)&mxConstants.FONT_ITALIC)==mxConstants.FONT_ITALIC,m=(mxUtils.getValue(c.style,mxConstants.STYLE_FONTSTYLE,0)&mxConstants.FONT_UNDERLINE)==mxConstants.FONT_UNDERLINE;this.textarea.style.lineHeight=mxConstants.ABSOLUTE_LINE_HEIGHT?Math.round(d*mxConstants.LINE_HEIGHT)+"px":mxConsta [...]
+this.textarea.style.backgroundColor=this.getBackgroundColor(c);this.textarea.style.textDecoration=m?"underline":"";this.textarea.style.fontWeight=k?"bold":"normal";this.textarea.style.fontStyle=l?"italic":"";this.textarea.style.fontSize=Math.round(d)+"px";this.textarea.style.zIndex=this.zIndex;this.textarea.style.fontFamily=e;this.textarea.style.textAlign=g;this.textarea.style.outline="none";this.textarea.style.color=f;d=this.textDirection=mxUtils.getValue(c.style,mxConstants.STYLE_TEXT_ [...]
+mxConstants.DEFAULT_TEXT_DIRECTION);d==mxConstants.TEXT_DIRECTION_AUTO&&(null==c||null==c.text||c.text.dialect==mxConstants.DIALECT_STRICTHTML||mxUtils.isNode(c.text.value)||(d=c.text.getAutoDirection()));d==mxConstants.TEXT_DIRECTION_LTR||d==mxConstants.TEXT_DIRECTION_RTL?this.textarea.setAttribute("dir",d):this.textarea.removeAttribute("dir");this.textarea.innerHTML=this.getInitialValue(c,b)||"";this.initialValue=this.textarea.innerHTML;0==this.textarea.innerHTML.length||"<br>"==this.t [...]
+(this.textarea.innerHTML=this.getEmptyLabelText(),this.clearOnChange=!0):this.clearOnChange=this.textarea.innerHTML==this.getEmptyLabelText();this.graph.container.appendChild(this.textarea);this.editingCell=a;this.trigger=b;this.textNode=null;null!=c.text&&this.isHideLabel(c)&&(this.textNode=c.text.node,this.textNode.style.visibility="hidden");this.autoSize&&(this.graph.model.isEdge(c.cell)||"fill"!=c.style[mxConstants.STYLE_OVERFLOW])&&window.setTimeout(mxUtils.bind(this,function(){this [...]
+0);this.resize();try{this.textarea.focus(),this.isSelectText()&&0<this.textarea.innerHTML.length&&(this.textarea.innerHTML!=this.getEmptyLabelText()||!this.clearOnChange)&&document.execCommand("selectAll",!1,null)}catch(n){}}};mxCellEditor.prototype.isSelectText=function(){return this.selectText};
+mxCellEditor.prototype.stopEditing=function(a){if(null!=this.editingCell){null!=this.textNode&&(this.textNode.style.visibility="visible",this.textNode=null);a=a?null:this.graph.view.getState(this.editingCell);var b=this.initialValue;this.bounds=this.trigger=this.editingCell=this.initialValue=null;this.textarea.blur();null!=this.textarea.parentNode&&this.textarea.parentNode.removeChild(this.textarea);this.clearOnChange&&this.textarea.innerHTML==this.getEmptyLabelText()&&(this.textarea.inn [...]
+this.clearOnChange=!1);null!=a&&this.textarea.innerHTML!=b&&(this.prepareTextarea(),b=this.getCurrentValue(a),null!=b&&this.applyValue(a,b));mxEvent.release(this.textarea);this.textarea=null}};mxCellEditor.prototype.prepareTextarea=function(){mxClient.IS_FF&&null!=this.textarea.lastChild&&"BR"==this.textarea.lastChild.nodeName&&this.textarea.removeChild(this.textarea.lastChild)};mxCellEditor.prototype.isHideLabel=function(a){return!0};
+mxCellEditor.prototype.getMinimumSize=function(a){var b=this.graph.getView().scale;return new mxRectangle(0,0,null==a.text?30:a.text.size*b+20,"left"==this.textarea.style.textAlign?120:40)};
+mxCellEditor.prototype.getEditorBounds=function(a){var b=this.graph.getModel().isEdge(a.cell),c=this.graph.getView().scale,d=this.getMinimumSize(a),e=d.width,d=d.height;if(!b&&a.view.graph.cellRenderer.legacySpacing&&"fill"==a.style[mxConstants.STYLE_OVERFLOW])c=a.shape.getLabelBounds(mxRectangle.fromRectangle(a));else{var f=parseInt(a.style[mxConstants.STYLE_SPACING]||0)*c,g=(parseInt(a.style[mxConstants.STYLE_SPACING_TOP]||0)+mxText.prototype.baseSpacingTop)*c+f,k=(parseInt(a.style[mxC [...]
+0)+mxText.prototype.baseSpacingRight)*c+f,l=(parseInt(a.style[mxConstants.STYLE_SPACING_BOTTOM]||0)+mxText.prototype.baseSpacingBottom)*c+f,f=(parseInt(a.style[mxConstants.STYLE_SPACING_LEFT]||0)+mxText.prototype.baseSpacingLeft)*c+f,c=new mxRectangle(a.x,a.y,Math.max(e,a.width-f-k),Math.max(d,a.height-g-l)),k=mxUtils.getValue(a.style,mxConstants.STYLE_LABEL_POSITION,mxConstants.ALIGN_CENTER),l=mxUtils.getValue(a.style,mxConstants.STYLE_VERTICAL_LABEL_POSITION,mxConstants.ALIGN_MIDDLE),c [...]
+k==mxConstants.ALIGN_CENTER&&l==mxConstants.ALIGN_MIDDLE?a.shape.getLabelBounds(c):c;b?(c.x=a.absoluteOffset.x,c.y=a.absoluteOffset.y,null!=a.text&&null!=a.text.boundingBox&&(0<a.text.boundingBox.x&&(c.x=a.text.boundingBox.x),0<a.text.boundingBox.y&&(c.y=a.text.boundingBox.y))):null!=a.text&&null!=a.text.boundingBox&&(c.x=Math.min(c.x,a.text.boundingBox.x),c.y=Math.min(c.y,a.text.boundingBox.y));c.x+=f;c.y+=g;null!=a.text&&null!=a.text.boundingBox&&(b?(c.width=Math.max(e,a.text.boundingB [...]
+c.height=Math.max(d,a.text.boundingBox.height)):(c.width=Math.max(c.width,a.text.boundingBox.width),c.height=Math.max(c.height,a.text.boundingBox.height)));this.graph.getModel().isVertex(a.cell)&&(b=mxUtils.getValue(a.style,mxConstants.STYLE_LABEL_POSITION,mxConstants.ALIGN_CENTER),b==mxConstants.ALIGN_LEFT?c.x-=a.width:b==mxConstants.ALIGN_RIGHT&&(c.x+=a.width),b=mxUtils.getValue(a.style,mxConstants.STYLE_VERTICAL_LABEL_POSITION,mxConstants.ALIGN_MIDDLE),b==mxConstants.ALIGN_TOP?c.y-=a. [...]
+mxConstants.ALIGN_BOTTOM&&(c.y+=a.height))}return new mxRectangle(Math.round(c.x),Math.round(c.y),Math.round(c.width),Math.round(c.height))};mxCellEditor.prototype.getEmptyLabelText=function(a){return this.emptyLabelText};mxCellEditor.prototype.getEditingCell=function(){return this.editingCell};
+mxCellEditor.prototype.destroy=function(){null!=this.textarea&&(mxEvent.release(this.textarea),null!=this.textarea.parentNode&&this.textarea.parentNode.removeChild(this.textarea),this.textarea=null);null!=this.changeHandler&&(this.graph.getModel().removeListener(this.changeHandler),this.changeHandler=null);this.zoomHandler&&(this.graph.view.removeListener(this.zoomHandler),this.zoomHandler=null)};function mxCellRenderer(){}mxCellRenderer.prototype.defaultEdgeShape=mxConnector;
+mxCellRenderer.prototype.defaultVertexShape=mxRectangleShape;mxCellRenderer.prototype.defaultTextShape=mxText;mxCellRenderer.prototype.legacyControlPosition=!0;mxCellRenderer.prototype.legacySpacing=!0;mxCellRenderer.prototype.defaultShapes={};mxCellRenderer.prototype.antiAlias=!0;mxCellRenderer.prototype.forceControlClickHandler=!1;mxCellRenderer.registerShape=function(a,b){mxCellRenderer.prototype.defaultShapes[a]=b};mxCellRenderer.registerShape(mxConstants.SHAPE_RECTANGLE,mxRectangleShape);
+mxCellRenderer.registerShape(mxConstants.SHAPE_ELLIPSE,mxEllipse);mxCellRenderer.registerShape(mxConstants.SHAPE_RHOMBUS,mxRhombus);mxCellRenderer.registerShape(mxConstants.SHAPE_CYLINDER,mxCylinder);mxCellRenderer.registerShape(mxConstants.SHAPE_CONNECTOR,mxConnector);mxCellRenderer.registerShape(mxConstants.SHAPE_ACTOR,mxActor);mxCellRenderer.registerShape(mxConstants.SHAPE_TRIANGLE,mxTriangle);mxCellRenderer.registerShape(mxConstants.SHAPE_HEXAGON,mxHexagon);
+mxCellRenderer.registerShape(mxConstants.SHAPE_CLOUD,mxCloud);mxCellRenderer.registerShape(mxConstants.SHAPE_LINE,mxLine);mxCellRenderer.registerShape(mxConstants.SHAPE_ARROW,mxArrow);mxCellRenderer.registerShape(mxConstants.SHAPE_ARROW_CONNECTOR,mxArrowConnector);mxCellRenderer.registerShape(mxConstants.SHAPE_DOUBLE_ELLIPSE,mxDoubleEllipse);mxCellRenderer.registerShape(mxConstants.SHAPE_SWIMLANE,mxSwimlane);mxCellRenderer.registerShape(mxConstants.SHAPE_IMAGE,mxImageShape);
+mxCellRenderer.registerShape(mxConstants.SHAPE_LABEL,mxLabel);mxCellRenderer.prototype.initializeShape=function(a){a.shape.dialect=a.view.graph.dialect;this.configureShape(a);a.shape.init(a.view.getDrawPane())};mxCellRenderer.prototype.createShape=function(a){var b=null;null!=a.style&&(b=mxStencilRegistry.getStencil(a.style[mxConstants.STYLE_SHAPE]),b=null!=b?new mxShape(b):new (this.getShapeConstructor(a)));return b};
+mxCellRenderer.prototype.createIndicatorShape=function(a){a.shape.indicatorShape=this.getShape(a.view.graph.getIndicatorShape(a))};mxCellRenderer.prototype.getShape=function(a){return null!=a?mxCellRenderer.prototype.defaultShapes[a]:null};mxCellRenderer.prototype.getShapeConstructor=function(a){var b=this.getShape(a.style[mxConstants.STYLE_SHAPE]);null==b&&(b=a.view.graph.getModel().isEdge(a.cell)?this.defaultEdgeShape:this.defaultVertexShape);return b};
+mxCellRenderer.prototype.configureShape=function(a){a.shape.apply(a);a.shape.image=a.view.graph.getImage(a);a.shape.indicatorColor=a.view.graph.getIndicatorColor(a);a.shape.indicatorStrokeColor=a.style[mxConstants.STYLE_INDICATOR_STROKECOLOR];a.shape.indicatorGradientColor=a.view.graph.getIndicatorGradientColor(a);a.shape.indicatorDirection=a.style[mxConstants.STYLE_INDICATOR_DIRECTION];a.shape.indicatorImage=a.view.graph.getIndicatorImage(a);this.postConfigureShape(a)};
+mxCellRenderer.prototype.postConfigureShape=function(a){null!=a.shape&&(this.resolveColor(a,"indicatorColor",mxConstants.STYLE_FILLCOLOR),this.resolveColor(a,"indicatorGradientColor",mxConstants.STYLE_GRADIENTCOLOR),this.resolveColor(a,"fill",mxConstants.STYLE_FILLCOLOR),this.resolveColor(a,"stroke",mxConstants.STYLE_STROKECOLOR),this.resolveColor(a,"gradient",mxConstants.STYLE_GRADIENTCOLOR))};
+mxCellRenderer.prototype.resolveColor=function(a,b,c){var d=a.shape[b],e=a.view.graph,f=null;"inherit"==d?f=e.model.getParent(a.cell):"swimlane"==d?(f=null!=e.model.getTerminal(a.cell,!1)?e.model.getTerminal(a.cell,!1):a.cell,f=e.getSwimlane(f),c=e.swimlaneIndicatorColorAttribute):"indicated"==d&&(a.shape[b]=a.shape.indicatorColor);null!=f&&(d=e.getView().getState(f),a.shape[b]=null,null!=d&&(a.shape[b]=null!=d.shape&&"indicatorColor"!=b?d.shape[b]:d.style[c]))};
+mxCellRenderer.prototype.getLabelValue=function(a){return a.view.graph.getLabel(a.cell)};
+mxCellRenderer.prototype.createLabel=function(a,b){var c=a.view.graph;c.getModel().isEdge(a.cell);if(0<a.style[mxConstants.STYLE_FONTSIZE]||null==a.style[mxConstants.STYLE_FONTSIZE]){var d=c.isHtmlLabel(a.cell)||null!=b&&mxUtils.isNode(b);a.text=new this.defaultTextShape(b,new mxRectangle,a.style[mxConstants.STYLE_ALIGN]||mxConstants.ALIGN_CENTER,c.getVerticalAlign(a),a.style[mxConstants.STYLE_FONTCOLOR],a.style[mxConstants.STYLE_FONTFAMILY],a.style[mxConstants.STYLE_FONTSIZE],a.style[mx [...]
+a.style[mxConstants.STYLE_SPACING],a.style[mxConstants.STYLE_SPACING_TOP],a.style[mxConstants.STYLE_SPACING_RIGHT],a.style[mxConstants.STYLE_SPACING_BOTTOM],a.style[mxConstants.STYLE_SPACING_LEFT],a.style[mxConstants.STYLE_HORIZONTAL],a.style[mxConstants.STYLE_LABEL_BACKGROUNDCOLOR],a.style[mxConstants.STYLE_LABEL_BORDERCOLOR],c.isWrapping(a.cell)&&c.isHtmlLabel(a.cell),c.isLabelClipped(a.cell),a.style[mxConstants.STYLE_OVERFLOW],a.style[mxConstants.STYLE_LABEL_PADDING],mxUtils.getValue( [...]
+mxConstants.DEFAULT_TEXT_DIRECTION));a.text.opacity=mxUtils.getValue(a.style,mxConstants.STYLE_TEXT_OPACITY,100);a.text.dialect=d?mxConstants.DIALECT_STRICTHTML:a.view.graph.dialect;a.text.style=a.style;a.text.state=a;this.initializeLabel(a,a.text);var e=!1,f=function(b){var d=a;if(mxClient.IS_TOUCH||e)d=mxEvent.getClientX(b),b=mxEvent.getClientY(b),b=mxUtils.convertPoint(c.container,d,b),d=c.view.getState(c.getCellAt(b.x,b.y));return d};mxEvent.addGestureListeners(a.text.node,mxUtils.bi [...]
+b)&&(c.fireMouseEvent(mxEvent.MOUSE_DOWN,new mxMouseEvent(b,a)),e=c.dialect!=mxConstants.DIALECT_SVG&&"IMG"==mxEvent.getSource(b).nodeName)}),mxUtils.bind(this,function(b){this.isLabelEvent(a,b)&&c.fireMouseEvent(mxEvent.MOUSE_MOVE,new mxMouseEvent(b,f(b)))}),mxUtils.bind(this,function(b){this.isLabelEvent(a,b)&&(c.fireMouseEvent(mxEvent.MOUSE_UP,new mxMouseEvent(b,f(b))),e=!1)}));c.nativeDblClickEnabled&&mxEvent.addListener(a.text.node,"dblclick",mxUtils.bind(this,function(b){this.isLab [...]
+b)&&(c.dblClick(b,a.cell),mxEvent.consume(b))}))}};mxCellRenderer.prototype.initializeLabel=function(a,b){mxClient.IS_SVG&&mxClient.NO_FO&&b.dialect!=mxConstants.DIALECT_SVG?b.init(a.view.graph.container):b.init(a.view.getDrawPane())};
+mxCellRenderer.prototype.createCellOverlays=function(a){var b=a.view.graph.getCellOverlays(a.cell),c=null;if(null!=b)for(var c=new mxDictionary,d=0;d<b.length;d++){var e=null!=a.overlays?a.overlays.remove(b[d]):null;null==e&&(e=new mxImageShape(new mxRectangle,b[d].image.src),e.dialect=a.view.graph.dialect,e.preserveImageAspect=!1,e.overlay=b[d],this.initializeOverlay(a,e),this.installCellOverlayListeners(a,b[d],e),null!=b[d].cursor&&(e.node.style.cursor=b[d].cursor));c.put(b[d],e)}null! [...]
+a.overlays.visit(function(a,b){b.destroy()});a.overlays=c};mxCellRenderer.prototype.initializeOverlay=function(a,b){b.init(a.view.getOverlayPane())};
+mxCellRenderer.prototype.installCellOverlayListeners=function(a,b,c){var d=a.view.graph;mxEvent.addListener(c.node,"click",function(c){d.isEditing()&&d.stopEditing(!d.isInvokesStopCellEditing());b.fireEvent(new mxEventObject(mxEvent.CLICK,"event",c,"cell",a.cell))});mxEvent.addGestureListeners(c.node,function(a){mxEvent.consume(a)},function(b){d.fireMouseEvent(mxEvent.MOUSE_MOVE,new mxMouseEvent(b,a))});mxClient.IS_TOUCH&&mxEvent.addListener(c.node,"touchend",function(c){b.fireEvent(new  [...]
+"event",c,"cell",a.cell))})};mxCellRenderer.prototype.createControl=function(a){var b=a.view.graph,c=b.getFoldingImage(a);if(b.foldingEnabled&&null!=c){if(null==a.control){var d=new mxRectangle(0,0,c.width,c.height);a.control=new mxImageShape(d,c.src);a.control.preserveImageAspect=!1;a.control.dialect=b.dialect;this.initControl(a,a.control,!0,this.createControlClickHandler(a))}}else null!=a.control&&(a.control.destroy(),a.control=null)};
+mxCellRenderer.prototype.createControlClickHandler=function(a){var b=a.view.graph;return mxUtils.bind(this,function(c){if(this.forceControlClickHandler||b.isEnabled()){var d=!b.isCellCollapsed(a.cell);b.foldCells(d,!1,[a.cell],null,c);mxEvent.consume(c)}})};
+mxCellRenderer.prototype.initControl=function(a,b,c,d){var e=a.view.graph;e.isHtmlLabel(a.cell)&&mxClient.NO_FO&&e.dialect==mxConstants.DIALECT_SVG?(b.dialect=mxConstants.DIALECT_PREFERHTML,b.init(e.container),b.node.style.zIndex=1):b.init(a.view.getOverlayPane());b=b.innerNode||b.node;null==d||mxClient.IS_IOS||(e.isEnabled()&&(b.style.cursor="pointer"),mxEvent.addListener(b,"click",d));if(c){var f=null;mxEvent.addGestureListeners(b,function(b){f=new mxPoint(mxEvent.getClientX(b),mxEvent [...]
+e.fireMouseEvent(mxEvent.MOUSE_DOWN,new mxMouseEvent(b,a));mxEvent.consume(b)},function(b){e.fireMouseEvent(mxEvent.MOUSE_MOVE,new mxMouseEvent(b,a))},function(b){e.fireMouseEvent(mxEvent.MOUSE_UP,new mxMouseEvent(b,a));mxEvent.consume(b)});null!=d&&mxClient.IS_IOS&&b.addEventListener("touchend",function(a){if(null!=f){var b=e.tolerance;Math.abs(f.x-mxEvent.getClientX(a))<b&&Math.abs(f.y-mxEvent.getClientY(a))<b&&(d.call(d,a),mxEvent.consume(a))}},!0)}return b};
+mxCellRenderer.prototype.isShapeEvent=function(a,b){return!0};mxCellRenderer.prototype.isLabelEvent=function(a,b){return!0};
+mxCellRenderer.prototype.installListeners=function(a){var b=a.view.graph,c=function(c){var d=a;if(b.dialect!=mxConstants.DIALECT_SVG&&"IMG"==mxEvent.getSource(c).nodeName||mxClient.IS_TOUCH)d=mxEvent.getClientX(c),c=mxEvent.getClientY(c),c=mxUtils.convertPoint(b.container,d,c),d=b.view.getState(b.getCellAt(c.x,c.y));return d};mxEvent.addGestureListeners(a.shape.node,mxUtils.bind(this,function(c){this.isShapeEvent(a,c)&&b.fireMouseEvent(mxEvent.MOUSE_DOWN,new mxMouseEvent(c,a))}),mxUtils. [...]
+function(d){this.isShapeEvent(a,d)&&b.fireMouseEvent(mxEvent.MOUSE_MOVE,new mxMouseEvent(d,c(d)))}),mxUtils.bind(this,function(d){this.isShapeEvent(a,d)&&b.fireMouseEvent(mxEvent.MOUSE_UP,new mxMouseEvent(d,c(d)))}));b.nativeDblClickEnabled&&mxEvent.addListener(a.shape.node,"dblclick",mxUtils.bind(this,function(c){this.isShapeEvent(a,c)&&(b.dblClick(c,a.cell),mxEvent.consume(c))}))};
+mxCellRenderer.prototype.redrawLabel=function(a,b){var c=this.getLabelValue(a);null==a.text&&null!=c&&(mxUtils.isNode(c)||0<c.length)?this.createLabel(a,c):null==a.text||null!=c&&0!=c.length||(a.text.destroy(),a.text=null);if(null!=a.text){var d=a.view.graph;b&&(null!=a.text.lastValue&&this.isTextShapeInvalid(a,a.text)&&(a.text.lastValue=null),a.text.resetStyles(),a.text.apply(a),a.text.valign=d.getVerticalAlign(a));var e=this.getLabelBounds(a),f=d.isWrapping(a.cell),d=d.isLabelClipped(a [...]
+a.view.graph.isHtmlLabel(a.cell)||null!=c&&mxUtils.isNode(c)?mxConstants.DIALECT_STRICTHTML:a.view.graph.dialect,k=a.style[mxConstants.STYLE_OVERFLOW]||"visible";if(b||a.text.value!=c||a.text.isWrapping!=f||a.text.overflow!=k||a.text.isClipping!=d||a.text.scale!=this.getTextScale(a)||a.text.dialect!=g||!a.text.bounds.equals(e))a.text.dialect=g,a.text.value=c,a.text.bounds=e,a.text.scale=this.getTextScale(a),a.text.wrap=f,a.text.clipped=d,a.text.overflow=k,c=a.text.node.style.visibility,t [...]
+a.text.node.style.visibility=c}};
+mxCellRenderer.prototype.isTextShapeInvalid=function(a,b){function c(c,e,f){return result="spacingTop"==e||"spacingRight"==e||"spacingBottom"==e||"spacingLeft"==e?parseFloat(b[c])-parseFloat(b.spacing)!=(a.style[e]||f):b[c]!=(a.style[e]||f)}return c("fontStyle",mxConstants.STYLE_FONTSTYLE,mxConstants.DEFAULT_FONTSTYLE)||c("family",mxConstants.STYLE_FONTFAMILY,mxConstants.DEFAULT_FONTFAMILY)||c("size",mxConstants.STYLE_FONTSIZE,mxConstants.DEFAULT_FONTSIZE)||c("color",mxConstants.STYLE_FO [...]
+c("align",mxConstants.STYLE_ALIGN,"")||c("valign",mxConstants.STYLE_VERTICAL_ALIGN,"")||c("spacing",mxConstants.STYLE_SPACING,2)||c("spacingTop",mxConstants.STYLE_SPACING_TOP,0)||c("spacingRight",mxConstants.STYLE_SPACING_RIGHT,0)||c("spacingBottom",mxConstants.STYLE_SPACING_BOTTOM,0)||c("spacingLeft",mxConstants.STYLE_SPACING_LEFT,0)||c("horizontal",mxConstants.STYLE_HORIZONTAL,!0)||c("background",mxConstants.STYLE_LABEL_BACKGROUNDCOLOR)||c("border",mxConstants.STYLE_LABEL_BORDERCOLOR)| [...]
+mxConstants.STYLE_TEXT_OPACITY,100)||c("textDirection",mxConstants.STYLE_TEXT_DIRECTION,mxConstants.DEFAULT_TEXT_DIRECTION)};mxCellRenderer.prototype.redrawLabelShape=function(a){a.redraw()};mxCellRenderer.prototype.getTextScale=function(a){return a.view.scale};
+mxCellRenderer.prototype.getLabelBounds=function(a){var b=a.view.graph,c=a.view.scale,d=b.getModel().isEdge(a.cell),e=new mxRectangle(a.absoluteOffset.x,a.absoluteOffset.y);if(d){var f=a.text.getSpacing();e.x+=f.x*c;e.y+=f.y*c;b=b.getCellGeometry(a.cell);null!=b&&(e.width=Math.max(0,b.width*c),e.height=Math.max(0,b.height*c))}else a.text.isPaintBoundsInverted()&&(b=e.x,e.x=e.y,e.y=b),e.x+=a.x,e.y+=a.y,e.width=Math.max(1,a.width),e.height=Math.max(1,a.height),b=mxUtils.getValue(a.style,mx [...]
+mxConstants.NONE),b!=mxConstants.NONE&&""!=b&&(f=parseFloat(mxUtils.getValue(a.style,mxConstants.STYLE_STROKEWIDTH,1))*c,b=1+Math.floor((f-1)/2),f=Math.floor(f+1),e.x+=b,e.y+=b,e.width-=f,e.height-=f);a.text.isPaintBoundsInverted()&&(b=(a.width-a.height)/2,e.x+=b,e.y-=b,b=e.width,e.width=e.height,e.height=b);null!=a.shape&&(b=mxUtils.getValue(a.style,mxConstants.STYLE_LABEL_POSITION,mxConstants.ALIGN_CENTER),f=mxUtils.getValue(a.style,mxConstants.STYLE_VERTICAL_LABEL_POSITION,mxConstants [...]
+b==mxConstants.ALIGN_CENTER&&f==mxConstants.ALIGN_MIDDLE&&(e=a.shape.getLabelBounds(e)));b=mxUtils.getValue(a.style,mxConstants.STYLE_LABEL_WIDTH,null);null!=b&&(e.width=parseFloat(b)*c);d||this.rotateLabelBounds(a,e);return e};
+mxCellRenderer.prototype.rotateLabelBounds=function(a,b){b.y-=a.text.margin.y*b.height;b.x-=a.text.margin.x*b.width;if(!this.legacySpacing||"fill"!=a.style[mxConstants.STYLE_OVERFLOW]&&"width"!=a.style[mxConstants.STYLE_OVERFLOW]){var c=a.view.scale,d=a.text.getSpacing();b.x+=d.x*c;b.y+=d.y*c;var d=mxUtils.getValue(a.style,mxConstants.STYLE_LABEL_POSITION,mxConstants.ALIGN_CENTER),e=mxUtils.getValue(a.style,mxConstants.STYLE_VERTICAL_LABEL_POSITION,mxConstants.ALIGN_MIDDLE),f=mxUtils.get [...]
+mxConstants.STYLE_LABEL_WIDTH,null);b.width=Math.max(0,b.width-(d==mxConstants.ALIGN_CENTER&&null==f?a.text.spacingLeft*c+a.text.spacingRight*c:0));b.height=Math.max(0,b.height-(e==mxConstants.ALIGN_MIDDLE?a.text.spacingTop*c+a.text.spacingBottom*c:0))}e=a.text.getTextRotation();0!=e&&null!=a&&a.view.graph.model.isVertex(a.cell)&&(c=a.getCenterX(),d=a.getCenterY(),b.x!=c||b.y!=d)&&(e*=Math.PI/180,pt=mxUtils.getRotatedPoint(new mxPoint(b.x,b.y),Math.cos(e),Math.sin(e),new mxPoint(c,d)),b. [...]
+pt.y)};
+mxCellRenderer.prototype.redrawCellOverlays=function(a,b){this.createCellOverlays(a);if(null!=a.overlays){var c=mxUtils.mod(mxUtils.getValue(a.style,mxConstants.STYLE_ROTATION,0),90),d=mxUtils.toRadians(c),e=Math.cos(d),f=Math.sin(d);a.overlays.visit(function(d,k){var g=k.overlay.getBounds(a);if(!a.view.graph.getModel().isEdge(a.cell)&&null!=a.shape&&0!=c){var m=g.getCenterX(),n=g.getCenterY(),n=mxUtils.getRotatedPoint(new mxPoint(m,n),e,f,new mxPoint(a.getCenterX(),a.getCenterY())),m=n. [...]
+Math.round(m-g.width/2);g.y=Math.round(n-g.height/2)}if(b||null==k.bounds||k.scale!=a.view.scale||!k.bounds.equals(g))k.bounds=g,k.scale=a.view.scale,k.redraw()})}};
+mxCellRenderer.prototype.redrawControl=function(a,b){var c=a.view.graph.getFoldingImage(a);if(null!=a.control&&null!=c){var c=this.getControlBounds(a,c.width,c.height),d=this.legacyControlPosition?mxUtils.getValue(a.style,mxConstants.STYLE_ROTATION,0):a.shape.getTextRotation(),e=a.view.scale;if(b||a.control.scale!=e||!a.control.bounds.equals(c)||a.control.rotation!=d)a.control.rotation=d,a.control.bounds=c,a.control.scale=e,a.control.redraw()}};
+mxCellRenderer.prototype.getControlBounds=function(a,b,c){if(null!=a.control){var d=a.view.scale,e=a.getCenterX(),f=a.getCenterY();if(!a.view.graph.getModel().isEdge(a.cell)&&(e=a.x+b*d,f=a.y+c*d,null!=a.shape)){var g=a.shape.getShapeRotation();if(this.legacyControlPosition)g=mxUtils.getValue(a.style,mxConstants.STYLE_ROTATION,0);else if(a.shape.isPaintBoundsInverted())var k=(a.width-a.height)/2,e=e+k,f=f-k;0!=g&&(k=mxUtils.toRadians(g),g=Math.cos(k),k=Math.sin(k),f=mxUtils.getRotatedPoi [...]
+f),g,k,new mxPoint(a.getCenterX(),a.getCenterY())),e=f.x,f=f.y)}return a.view.graph.getModel().isEdge(a.cell),new mxRectangle(Math.round(e-b/2*d),Math.round(f-c/2*d),Math.round(b*d),Math.round(c*d))}return null};
+mxCellRenderer.prototype.insertStateAfter=function(a,b,c){for(var d=this.getShapesForState(a),e=0;e<d.length;e++)if(null!=d[e]&&null!=d[e].node){var f=d[e].node.parentNode!=a.view.getDrawPane()&&d[e].node.parentNode!=a.view.getOverlayPane(),g=f?c:b;if(null!=g&&g.nextSibling!=d[e].node)null==g.nextSibling?g.parentNode.appendChild(d[e].node):g.parentNode.insertBefore(d[e].node,g.nextSibling);else if(null==g)if(d[e].node.parentNode==a.view.graph.container){for(g=a.view.canvas;null!=g&&g.par [...]
+a.view.graph.container;)g=g.parentNode;null!=g&&null!=g.nextSibling?g.nextSibling!=d[e].node&&d[e].node.parentNode.insertBefore(d[e].node,g.nextSibling):d[e].node.parentNode.appendChild(d[e].node)}else null!=d[e].node.parentNode.firstChild&&d[e].node.parentNode.firstChild!=d[e].node&&d[e].node.parentNode.insertBefore(d[e].node,d[e].node.parentNode.firstChild);f?c=d[e].node:b=d[e].node}return[b,c]};mxCellRenderer.prototype.getShapesForState=function(a){return[a.shape,a.text,a.control]};
+mxCellRenderer.prototype.redraw=function(a,b,c){b=this.redrawShape(a,b,c);null==a.shape||null!=c&&!c||(this.redrawLabel(a,b),this.redrawCellOverlays(a,b),this.redrawControl(a,b))};
+mxCellRenderer.prototype.redrawShape=function(a,b,c){var d=a.view.graph.model,e=!1;null!=a.shape&&null!=a.shape.style&&null!=a.style&&a.shape.style[mxConstants.STYLE_SHAPE]!=a.style[mxConstants.STYLE_SHAPE]&&(a.shape.destroy(),a.shape=null);null==a.shape&&null!=a.view.graph.container&&a.cell!=a.view.currentRoot&&(d.isVertex(a.cell)||d.isEdge(a.cell))?(a.shape=this.createShape(a),null!=a.shape&&(a.shape.antiAlias=this.antiAlias,this.createIndicatorShape(a),this.initializeShape(a),this.cre [...]
+this.installListeners(a),a.view.graph.selectionCellsHandler.updateHandler(a))):null==a.shape||mxUtils.equalEntries(a.shape.style,a.style)||(a.shape.resetStyles(),this.configureShape(a),a.view.graph.selectionCellsHandler.updateHandler(a),b=!0);null!=a.shape&&(this.createControl(a),b||this.isShapeInvalid(a,a.shape))&&(null!=a.absolutePoints?(a.shape.points=a.absolutePoints.slice(),a.shape.bounds=null):(a.shape.points=null,a.shape.bounds=new mxRectangle(a.x,a.y,a.width,a.height)),a.shape.sc [...]
+null==c||c?a.shape.redraw():a.shape.updateBoundingBox(),e=!0);return e};mxCellRenderer.prototype.isShapeInvalid=function(a,b){return null==b.bounds||b.scale!=a.view.scale||null==a.absolutePoints&&!b.bounds.equals(a)||null!=a.absolutePoints&&!mxUtils.equalPoints(b.points,a.absolutePoints)};
+mxCellRenderer.prototype.destroy=function(a){null!=a.shape&&(null!=a.text&&(a.text.destroy(),a.text=null),null!=a.overlays&&(a.overlays.visit(function(a,c){c.destroy()}),a.overlays=null),null!=a.control&&(a.control.destroy(),a.control=null),a.shape.destroy(),a.shape=null)};
+var mxEdgeStyle={EntityRelation:function(a,b,c,d,e){var f=a.view,g=f.graph;d=mxUtils.getValue(a.style,mxConstants.STYLE_SEGMENT,mxConstants.ENTITY_SEGMENT)*f.scale;var k=a.absolutePoints,l=k[0],m=k[k.length-1],k=!1;if(null!=l)b=new mxCellState,b.x=l.x,b.y=l.y;else if(null!=b){var n=mxUtils.getPortConstraints(b,a,!0,mxConstants.DIRECTION_MASK_NONE);n!=mxConstants.DIRECTION_MASK_NONE&&n!=mxConstants.DIRECTION_MASK_WEST+mxConstants.DIRECTION_MASK_EAST?k=n==mxConstants.DIRECTION_MASK_WEST:(l [...]
+l.relative?k=.5>=l.x:null!=c&&(k=c.x+c.width<b.x))}else return;l=!0;null!=m?(c=new mxCellState,c.x=m.x,c.y=m.y):null!=c&&(n=mxUtils.getPortConstraints(c,a,!1,mxConstants.DIRECTION_MASK_NONE),n!=mxConstants.DIRECTION_MASK_NONE&&n!=mxConstants.DIRECTION_MASK_WEST+mxConstants.DIRECTION_MASK_EAST?l=n==mxConstants.DIRECTION_MASK_WEST:(a=g.getCellGeometry(c.cell),a.relative?l=.5>=a.x:null!=b&&(l=b.x+b.width<c.x)));null!=b&&null!=c&&(a=k?b.x:b.x+b.width,b=f.getRoutingCenterY(b),g=l?c.x:c.x+c.wi [...]
+f=new mxPoint(a+(k?-d:d),b),m=new mxPoint(g+(l?-d:d),c),k==l?(d=k?Math.min(a,g)-d:Math.max(a,g)+d,e.push(new mxPoint(d,b)),e.push(new mxPoint(d,c))):(f.x<m.x==k?(d=b+(c-b)/2,e.push(f),e.push(new mxPoint(f.x,d)),e.push(new mxPoint(m.x,d))):e.push(f),e.push(m)))},Loop:function(a,b,c,d,e){c=a.absolutePoints;var f=c[c.length-1];if(null!=c[0]&&null!=f){if(null!=d&&0<d.length)for(b=0;b<d.length;b++)c=d[b],c=a.view.transformControlPoint(a,c),e.push(new mxPoint(c.x,c.y))}else if(null!=b){var f=a [...]
+c=null!=d&&0<d.length?d[0]:null;null!=c&&(c=f.transformControlPoint(a,c),mxUtils.contains(b,c.x,c.y)&&(c=null));var k=d=0,l=0,m=0,g=mxUtils.getValue(a.style,mxConstants.STYLE_SEGMENT,g.gridSize)*f.scale;a=mxUtils.getValue(a.style,mxConstants.STYLE_DIRECTION,mxConstants.DIRECTION_WEST);a==mxConstants.DIRECTION_NORTH||a==mxConstants.DIRECTION_SOUTH?(d=f.getRoutingCenterX(b),k=g):(l=f.getRoutingCenterY(b),m=g);null==c||c.x<b.x||c.x>b.x+b.width?null!=c?(d=c.x,m=Math.max(Math.abs(l-c.y),m)):a [...]
+l=b.y-2*k:a==mxConstants.DIRECTION_SOUTH?l=b.y+b.height+2*k:d=a==mxConstants.DIRECTION_EAST?b.x-2*m:b.x+b.width+2*m:null!=c&&(d=f.getRoutingCenterX(b),k=Math.max(Math.abs(d-c.x),m),l=c.y,m=0);e.push(new mxPoint(d-k,l-m));e.push(new mxPoint(d+k,l+m))}},ElbowConnector:function(a,b,c,d,e){var f=null!=d&&0<d.length?d[0]:null,g=!1,k=!1;if(null!=b&&null!=c)if(null!=f)var l=Math.min(b.x,c.x),m=Math.max(b.x+b.width,c.x+c.width),k=Math.min(b.y,c.y),n=Math.max(b.y+b.height,c.y+c.height),f=a.view.t [...]
+f),g=f.y<k||f.y>n,k=f.x<l||f.x>m;else l=Math.max(b.x,c.x),m=Math.min(b.x+b.width,c.x+c.width),g=l==m,g||(k=Math.max(b.y,c.y),n=Math.min(b.y+b.height,c.y+c.height),k=k==n);k||!g&&a.style[mxConstants.STYLE_ELBOW]!=mxConstants.ELBOW_VERTICAL?mxEdgeStyle.SideToSide(a,b,c,d,e):mxEdgeStyle.TopToBottom(a,b,c,d,e)},SideToSide:function(a,b,c,d,e){var f=a.view;d=null!=d&&0<d.length?d[0]:null;var g=a.absolutePoints,k=g[0],g=g[g.length-1];null!=d&&(d=f.transformControlPoint(a,d));null!=k&&(b=new mxC [...]
+b.x=k.x,b.y=k.y);null!=g&&(c=new mxCellState,c.x=g.x,c.y=g.y);null!=b&&null!=c&&(a=Math.max(b.x,c.x),k=Math.min(b.x+b.width,c.x+c.width),a=null!=d?d.x:Math.round(k+(a-k)/2),k=f.getRoutingCenterY(b),f=f.getRoutingCenterY(c),null!=d&&(d.y>=b.y&&d.y<=b.y+b.height&&(k=d.y),d.y>=c.y&&d.y<=c.y+c.height&&(f=d.y)),mxUtils.contains(c,a,k)||mxUtils.contains(b,a,k)||e.push(new mxPoint(a,k)),mxUtils.contains(c,a,f)||mxUtils.contains(b,a,f)||e.push(new mxPoint(a,f)),1==e.length&&(null!=d?mxUtils.cont [...]
+d.y)||mxUtils.contains(b,a,d.y)||e.push(new mxPoint(a,d.y)):(f=Math.max(b.y,c.y),e.push(new mxPoint(a,f+(Math.min(b.y+b.height,c.y+c.height)-f)/2)))))},TopToBottom:function(a,b,c,d,e){var f=a.view;d=null!=d&&0<d.length?d[0]:null;var g=a.absolutePoints,k=g[0],g=g[g.length-1];null!=d&&(d=f.transformControlPoint(a,d));null!=k&&(b=new mxCellState,b.x=k.x,b.y=k.y);null!=g&&(c=new mxCellState,c.x=g.x,c.y=g.y);null!=b&&null!=c&&(k=Math.max(b.y,c.y),g=Math.min(b.y+b.height,c.y+c.height),a=f.getR [...]
+null!=d&&d.x>=b.x&&d.x<=b.x+b.width&&(a=d.x),k=null!=d?d.y:Math.round(g+(k-g)/2),mxUtils.contains(c,a,k)||mxUtils.contains(b,a,k)||e.push(new mxPoint(a,k)),a=null!=d&&d.x>=c.x&&d.x<=c.x+c.width?d.x:f.getRoutingCenterX(c),mxUtils.contains(c,a,k)||mxUtils.contains(b,a,k)||e.push(new mxPoint(a,k)),1==e.length&&(null!=d&&1==e.length?mxUtils.contains(c,d.x,k)||mxUtils.contains(b,d.x,k)||e.push(new mxPoint(d.x,k)):(f=Math.max(b.x,c.x),e.push(new mxPoint(f+(Math.min(b.x+b.width,c.x+c.width)-f)/ [...]
+SegmentConnector:function(a,b,c,d,e){function f(a){if(null==l||Math.abs(l.x-a.x)>=k||Math.abs(l.y-a.y)>=k)e.push(a),l=a;return l}var g=a.absolutePoints,k=Math.max(1,a.view.scale),l=0<e.length?e[0]:null,m=!0,n=null,p=g[0];null==p&&null!=b?p=new mxPoint(a.view.getRoutingCenterX(b),a.view.getRoutingCenterY(b)):null!=p&&(p=p.clone());p.x=Math.round(p.x);p.y=Math.round(p.y);var q=g.length-1;if(null!=d&&0<d.length){for(var n=[],r=0;r<d.length;r++){var t=a.view.transformControlPoint(a,d[r]);nul [...]
+Math.round(t.x),t.y=Math.round(t.y),n.push(t))}if(0==n.length)return;d=n;null!=p&&null!=d[0]&&(Math.abs(d[0].x-p.x)<k&&(d[0].x=p.x),Math.abs(d[0].y-p.y)<k&&(d[0].y=p.y));t=g[q];null!=t&&null!=d[d.length-1]&&(Math.abs(d[d.length-1].x-t.x)<k&&(d[d.length-1].x=t.x),Math.abs(d[d.length-1].y-t.y)<k&&(d[d.length-1].y=t.y));var n=d[0],u=b,x=g[0],y=!1,A=!1,y=n;null!=x&&(x.x=Math.round(x.x),x.y=Math.round(x.y),u=null);for(r=0;2>r;r++){var z=null!=x&&x.x==y.x,v=null!=x&&x.y==y.y,B=null!=u&&y.y>=u. [...]
+u.height,u=null!=u&&y.x>=u.x&&y.x<=u.x+u.width,y=v||null==x&&B,A=z||null==x&&u;if(0!=r||!(y&&A||z&&v)){if(null!=x&&!v&&!z&&(B||u)){m=B?!1:!0;break}if(A||y){m=y;1==r&&(m=0==d.length%2?y:A);break}}u=c;x=g[q];null!=x&&(x.x=Math.round(x.x),x.y=Math.round(x.y),u=null);y=d[d.length-1];z&&v&&(d=d.slice(1))}m&&(null!=g[0]&&g[0].y!=n.y||null==g[0]&&null!=b&&(n.y<b.y||n.y>b.y+b.height))?f(new mxPoint(p.x,n.y)):!m&&(null!=g[0]&&g[0].x!=n.x||null==g[0]&&null!=b&&(n.x<b.x||n.x>b.x+b.width))&&f(new mx [...]
+p.y));m?p.y=n.y:p.x=n.x;for(r=0;r<d.length;r++)m=!m,n=d[r],m?p.y=n.y:p.x=n.x,f(p.clone())}else n=p,m=!0;p=g[q];null==p&&null!=c&&(p=new mxPoint(a.view.getRoutingCenterX(c),a.view.getRoutingCenterY(c)));null!=p&&(p.x=Math.round(p.x),p.y=Math.round(p.y),null!=n&&(m&&(null!=g[q]&&g[q].y!=n.y||null==g[q]&&null!=c&&(n.y<c.y||n.y>c.y+c.height))?f(new mxPoint(p.x,n.y)):!m&&(null!=g[q]&&g[q].x!=n.x||null==g[q]&&null!=c&&(n.x<c.x||n.x>c.x+c.width))&&f(new mxPoint(n.x,p.y))));if(null==g[0]&&null!= [...]
+e.length&&null!=e[1]&&mxUtils.contains(b,e[1].x,e[1].y);)e.splice(1,1);if(null==g[q]&&null!=c)for(;1<e.length&&null!=e[e.length-1]&&mxUtils.contains(c,e[e.length-1].x,e[e.length-1].y);)e.splice(e.length-1,1);null!=t&&null!=e[e.length-1]&&Math.abs(t.x-e[e.length-1].x)<k&&Math.abs(t.y-e[e.length-1].y)<k&&(e.splice(e.length-1,1),null!=e[e.length-1]&&(Math.abs(e[e.length-1].x-t.x)<k&&(e[e.length-1].x=t.x),Math.abs(e[e.length-1].y-t.y)<k&&(e[e.length-1].y=t.y)))},orthBuffer:10,orthPointsFallb [...]
+0],[0,-1],[1,0],[0,1],[-1,0],[0,-1],[1,0]],wayPoints1:[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],routePatterns:[[[513,2308,2081,2562],[513,1090,514,2184,2114,2561],[513,1090,514,2564,2184,2562],[513,2308,2561,1090,514,2568,2308]],[[514,1057,513,2308,2081,2562],[514,2184,2114,2561],[514,2184,2562,1057,513,2564,2184],[514,1057,513,2568,2308,2561]],[[1090,514,1057,513,2308,2081,2562],[2114,2561],[1090,2562,1057,513,2564,2184],[1090,514,1057,513,2308,2561,2568] [...]
+2562],[1057,513,1090,514,2184,2114,2561],[1057,513,1090,514,2184,2562,2564],[1057,2561,1090,514,2568,2308]]],inlineRoutePatterns:[[null,[2114,2568],null,null],[null,[514,2081,2114,2568],null,null],[null,[2114,2561],null,null],[[2081,2562],[1057,2114,2568],[2184,2562],null]],vertexSeperations:[],limits:[[0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0]],LEFT_MASK:32,TOP_MASK:64,RIGHT_MASK:128,BOTTOM_MASK:256,LEFT:1,TOP:2,RIGHT:4,BOTTOM:8,SIDE_MASK:480,CENTER_MASK:512,SOURCE_MASK:1024,TARGET_MASK:20 [...]
+getJettySize:function(a,b,c,d,e){b=mxUtils.getValue(a.style,e?mxConstants.STYLE_SOURCE_JETTY_SIZE:mxConstants.STYLE_TARGET_JETTY_SIZE,mxUtils.getValue(a.style,mxConstants.STYLE_JETTY_SIZE,mxEdgeStyle.orthBuffer));"auto"==b&&(mxUtils.getValue(a.style,e?mxConstants.STYLE_STARTARROW:mxConstants.STYLE_ENDARROW,mxConstants.NONE)!=mxConstants.NONE?(a=mxUtils.getNumber(a.style,e?mxConstants.STYLE_STARTSIZE:mxConstants.STYLE_ENDSIZE,mxConstants.DEFAULT_MARKERSIZE),b=Math.max(2,Math.ceil((a+mxEdg [...]
+mxEdgeStyle.orthBuffer))*mxEdgeStyle.orthBuffer):b=2*mxEdgeStyle.orthBuffer);return b},OrthConnector:function(a,b,c,d,e){var f=a.view.graph,g=null==b?!1:f.getModel().isEdge(b.cell),k=null==c?!1:f.getModel().isEdge(c.cell),f=a.absolutePoints,l=f[0],m=f[f.length-1],n=null!=b?b.x:l.x,p=null!=b?b.y:l.y,q=null!=b?b.width:0,r=null!=b?b.height:0,t=null!=c?c.x:m.x,u=null!=c?c.y:m.y,x=null!=c?c.width:0,y=null!=c?c.height:0,f=a.view.scale*mxEdgeStyle.getJettySize(a,b,c,d,!0),A=a.view.scale*mxEdgeS [...]
+b,c,d,!1);null!=b&&c==b&&(f=A=Math.max(f,A));var z=A+f,v=!1;if(null!=l&&null!=m)var v=m.x-l.x,B=m.y-l.y,v=v*v+B*B<z*z;if(v||mxEdgeStyle.orthPointsFallback&&null!=d&&0<d.length||g||k)mxEdgeStyle.SegmentConnector(a,b,c,d,e);else{d=[mxConstants.DIRECTION_MASK_ALL,mxConstants.DIRECTION_MASK_ALL];null!=b&&(d[0]=mxUtils.getPortConstraints(b,a,!0,mxConstants.DIRECTION_MASK_ALL),v=mxUtils.getValue(b.style,mxConstants.STYLE_ROTATION,0),0!=v&&(v=mxUtils.getBoundingBox(new mxRectangle(n,p,q,r),v),n [...]
+q=v.width,r=v.height));null!=c&&(d[1]=mxUtils.getPortConstraints(c,a,!1,mxConstants.DIRECTION_MASK_ALL),v=mxUtils.getValue(c.style,mxConstants.STYLE_ROTATION,0),0!=v&&(v=mxUtils.getBoundingBox(new mxRectangle(t,u,x,y),v),t=v.x,u=v.y,x=v.width,y=v.height));n=Math.round(10*n)/10;p=Math.round(10*p)/10;q=Math.round(10*q)/10;r=Math.round(10*r)/10;t=Math.round(10*t)/10;u=Math.round(10*u)/10;x=Math.round(10*x)/10;y=Math.round(10*y)/10;a=[0,0];n=[[n,p,q,r],[t,u,x,y]];A=[f,A];for(v=0;2>v;v++)mxEd [...]
+n[v][0]-A[v],mxEdgeStyle.limits[v][2]=n[v][1]-A[v],mxEdgeStyle.limits[v][4]=n[v][0]+n[v][2]+A[v],mxEdgeStyle.limits[v][8]=n[v][1]+n[v][3]+A[v];A=n[0][1]+n[0][3]/2;p=n[1][1]+n[1][3]/2;v=n[0][0]+n[0][2]/2-(n[1][0]+n[1][2]/2);B=A-p;A=0;0>v?A=0>B?2:1:0>=B&&(A=3,0==v&&(A=2));p=null;null!=b&&(p=l);b=[[.5,.5],[.5,.5]];for(v=0;2>v;v++)null!=p&&(b[v][0]=(p.x-n[v][0])/n[v][2],1>=Math.abs(p.x-n[v][0])?a[v]=mxConstants.DIRECTION_MASK_WEST:1>=Math.abs(p.x-n[v][0]-n[v][2])&&(a[v]=mxConstants.DIRECTION [...]
+b[v][1]=(p.y-n[v][1])/n[v][3],1>=Math.abs(p.y-n[v][1])?a[v]=mxConstants.DIRECTION_MASK_NORTH:1>=Math.abs(p.y-n[v][1]-n[v][3])&&(a[v]=mxConstants.DIRECTION_MASK_SOUTH)),p=null,null!=c&&(p=m);v=n[0][1]-(n[1][1]+n[1][3]);m=n[0][0]-(n[1][0]+n[1][2]);p=n[1][1]-(n[0][1]+n[0][3]);q=n[1][0]-(n[0][0]+n[0][2]);mxEdgeStyle.vertexSeperations[1]=Math.max(m-z,0);mxEdgeStyle.vertexSeperations[2]=Math.max(v-z,0);mxEdgeStyle.vertexSeperations[4]=Math.max(p-z,0);mxEdgeStyle.vertexSeperations[3]=Math.max(q [...]
+c=[];l=[];c[0]=m>=q?mxConstants.DIRECTION_MASK_WEST:mxConstants.DIRECTION_MASK_EAST;l[0]=v>=p?mxConstants.DIRECTION_MASK_NORTH:mxConstants.DIRECTION_MASK_SOUTH;c[1]=mxUtils.reversePortConstraints(c[0]);l[1]=mxUtils.reversePortConstraints(l[0]);m=m>=q?m:q;p=v>=p?v:p;q=[[0,0],[0,0]];r=!1;for(v=0;2>v;v++)0==a[v]&&(0==(c[v]&d[v])&&(c[v]=mxUtils.reversePortConstraints(c[v])),0==(l[v]&d[v])&&(l[v]=mxUtils.reversePortConstraints(l[v])),q[v][0]=l[v],q[v][1]=c[v]);0<p&&0<m&&(0<(c[0]&d[0])&&0<(l[1 [...]
+c[0],q[0][1]=l[0],q[1][0]=l[1],q[1][1]=c[1],r=!0):0<(l[0]&d[0])&&0<(c[1]&d[1])&&(q[0][0]=l[0],q[0][1]=c[0],q[1][0]=c[1],q[1][1]=l[1],r=!0));0<p&&!r&&(q[0][0]=l[0],q[0][1]=c[0],q[1][0]=l[1],q[1][1]=c[1],r=!0);0<m&&!r&&(q[0][0]=c[0],q[0][1]=l[0],q[1][0]=c[1],q[1][1]=l[1]);for(v=0;2>v;v++)0==a[v]&&(0==(q[v][0]&d[v])&&(q[v][0]=q[v][1]),z[v]=q[v][0]&d[v],z[v]|=(q[v][1]&d[v])<<8,z[v]|=(q[1-v][v]&d[v])<<16,z[v]|=(q[1-v][1-v]&d[v])<<24,0==(z[v]&15)&&(z[v]<<=8),0==(z[v]&3840)&&(z[v]=z[v]&15|z[v]> [...]
+983040)&&(z[v]=z[v]&65535|(z[v]&251658240)>>8),a[v]=z[v]&15,d[v]==mxConstants.DIRECTION_MASK_WEST||d[v]==mxConstants.DIRECTION_MASK_NORTH||d[v]==mxConstants.DIRECTION_MASK_EAST||d[v]==mxConstants.DIRECTION_MASK_SOUTH)&&(a[v]=d[v]);d=a[0]==mxConstants.DIRECTION_MASK_EAST?3:a[0];z=a[1]==mxConstants.DIRECTION_MASK_EAST?3:a[1];d-=A;z-=A;1>d&&(d+=4);1>z&&(z+=4);d=mxEdgeStyle.routePatterns[d-1][z-1];mxEdgeStyle.wayPoints1[0][0]=n[0][0];mxEdgeStyle.wayPoints1[0][1]=n[0][1];switch(a[0]){case mxC [...]
+f;mxEdgeStyle.wayPoints1[0][1]+=b[0][1]*n[0][3];break;case mxConstants.DIRECTION_MASK_SOUTH:mxEdgeStyle.wayPoints1[0][0]+=b[0][0]*n[0][2];mxEdgeStyle.wayPoints1[0][1]+=n[0][3]+f;break;case mxConstants.DIRECTION_MASK_EAST:mxEdgeStyle.wayPoints1[0][0]+=n[0][2]+f;mxEdgeStyle.wayPoints1[0][1]+=b[0][1]*n[0][3];break;case mxConstants.DIRECTION_MASK_NORTH:mxEdgeStyle.wayPoints1[0][0]+=b[0][0]*n[0][2],mxEdgeStyle.wayPoints1[0][1]-=f}f=0;c=z=0<(a[0]&(mxConstants.DIRECTION_MASK_EAST|mxConstants.DI [...]
+0:1;for(v=0;v<d.length;v++)l=d[v]&15,r=l==mxConstants.DIRECTION_MASK_EAST?3:l,r+=A,4<r&&(r-=4),m=mxEdgeStyle.dirVectors[r-1],l=0<r%2?0:1,l!=z&&(f++,mxEdgeStyle.wayPoints1[f][0]=mxEdgeStyle.wayPoints1[f-1][0],mxEdgeStyle.wayPoints1[f][1]=mxEdgeStyle.wayPoints1[f-1][1]),t=0<(d[v]&mxEdgeStyle.TARGET_MASK),u=0<(d[v]&mxEdgeStyle.SOURCE_MASK),p=(d[v]&mxEdgeStyle.SIDE_MASK)>>5,p<<=A,15<p&&(p>>=4),q=0<(d[v]&mxEdgeStyle.CENTER_MASK),(u||t)&&9>p?(r=u?0:1,p=q&&0==l?n[r][0]+b[r][0]*n[r][2]:q?n[r][1] [...]
+n[r][3]:mxEdgeStyle.limits[r][p],0==l?(p=(p-mxEdgeStyle.wayPoints1[f][0])*m[0],0<p&&(mxEdgeStyle.wayPoints1[f][0]+=m[0]*p)):(p=(p-mxEdgeStyle.wayPoints1[f][1])*m[1],0<p&&(mxEdgeStyle.wayPoints1[f][1]+=m[1]*p))):q&&(mxEdgeStyle.wayPoints1[f][0]+=m[0]*Math.abs(mxEdgeStyle.vertexSeperations[r]/2),mxEdgeStyle.wayPoints1[f][1]+=m[1]*Math.abs(mxEdgeStyle.vertexSeperations[r]/2)),0<f&&mxEdgeStyle.wayPoints1[f][l]==mxEdgeStyle.wayPoints1[f-1][l]?f--:z=l;for(v=0;v<=f&&(v!=f||((0<(a[1]&(mxConstant [...]
+mxConstants.DIRECTION_MASK_WEST))?0:1)==c?0:1)==(f+1)%2);v++)e.push(new mxPoint(Math.round(mxEdgeStyle.wayPoints1[v][0]),Math.round(mxEdgeStyle.wayPoints1[v][1])));for(a=1;a<e.length;)null==e[a-1]||null==e[a]||e[a-1].x!=e[a].x||e[a-1].y!=e[a].y?a++:e.splice(a,1)}},getRoutePattern:function(a,b,c,d){var e=a[0]==mxConstants.DIRECTION_MASK_EAST?3:a[0];a=a[1]==mxConstants.DIRECTION_MASK_EAST?3:a[1];e-=b;a-=b;1>e&&(e+=4);1>a&&(a+=4);b=routePatterns[e-1][a-1];0!=c&&0!=d||null==inlineRoutePatter [...]
+1]||(b=inlineRoutePatterns[e-1][a-1]);return b}},mxStyleRegistry={values:[],putValue:function(a,b){mxStyleRegistry.values[a]=b},getValue:function(a){return mxStyleRegistry.values[a]},getName:function(a){for(var b in mxStyleRegistry.values)if(mxStyleRegistry.values[b]==a)return b;return null}};mxStyleRegistry.putValue(mxConstants.EDGESTYLE_ELBOW,mxEdgeStyle.ElbowConnector);mxStyleRegistry.putValue(mxConstants.EDGESTYLE_ENTITY_RELATION,mxEdgeStyle.EntityRelation);
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_LOOP,mxEdgeStyle.Loop);mxStyleRegistry.putValue(mxConstants.EDGESTYLE_SIDETOSIDE,mxEdgeStyle.SideToSide);mxStyleRegistry.putValue(mxConstants.EDGESTYLE_TOPTOBOTTOM,mxEdgeStyle.TopToBottom);mxStyleRegistry.putValue(mxConstants.EDGESTYLE_ORTHOGONAL,mxEdgeStyle.OrthConnector);mxStyleRegistry.putValue(mxConstants.EDGESTYLE_SEGMENT,mxEdgeStyle.SegmentConnector);mxStyleRegistry.putValue(mxConstants.PERIMETER_ELLIPSE,mxPerimeter.EllipsePerimeter);
+mxStyleRegistry.putValue(mxConstants.PERIMETER_RECTANGLE,mxPerimeter.RectanglePerimeter);mxStyleRegistry.putValue(mxConstants.PERIMETER_RHOMBUS,mxPerimeter.RhombusPerimeter);mxStyleRegistry.putValue(mxConstants.PERIMETER_TRIANGLE,mxPerimeter.TrianglePerimeter);mxStyleRegistry.putValue(mxConstants.PERIMETER_HEXAGON,mxPerimeter.HexagonPerimeter);function mxGraphView(a){this.graph=a;this.translate=new mxPoint;this.graphBounds=new mxRectangle;this.states=new mxDictionary}mxGraphView.prototyp [...]
+mxGraphView.prototype.constructor=mxGraphView;mxGraphView.prototype.EMPTY_POINT=new mxPoint;mxGraphView.prototype.doneResource="none"!=mxClient.language?"done":"";mxGraphView.prototype.updatingDocumentResource="none"!=mxClient.language?"updatingDocument":"";mxGraphView.prototype.allowEval=!1;mxGraphView.prototype.captureDocumentGesture=!0;mxGraphView.prototype.optimizeVmlReflows=!0;mxGraphView.prototype.rendering=!0;mxGraphView.prototype.graph=null;mxGraphView.prototype.currentRoot=null;
+mxGraphView.prototype.graphBounds=null;mxGraphView.prototype.scale=1;mxGraphView.prototype.translate=null;mxGraphView.prototype.states=null;mxGraphView.prototype.updateStyle=!1;mxGraphView.prototype.lastNode=null;mxGraphView.prototype.lastHtmlNode=null;mxGraphView.prototype.lastForegroundNode=null;mxGraphView.prototype.lastForegroundHtmlNode=null;mxGraphView.prototype.getGraphBounds=function(){return this.graphBounds};mxGraphView.prototype.setGraphBounds=function(a){this.graphBounds=a};
+mxGraphView.prototype.getBounds=function(a){var b=null;if(null!=a&&0<a.length)for(var c=this.graph.getModel(),d=0;d<a.length;d++)if(c.isVertex(a[d])||c.isEdge(a[d])){var e=this.getState(a[d]);null!=e&&(null==b?b=mxRectangle.fromRectangle(e):b.add(e))}return b};mxGraphView.prototype.setCurrentRoot=function(a){if(this.currentRoot!=a){var b=new mxCurrentRootChange(this,a);b.execute();var c=new mxUndoableEdit(this,!1);c.add(b);this.fireEvent(new mxEventObject(mxEvent.UNDO,"edit",c));this.gra [...]
+mxGraphView.prototype.scaleAndTranslate=function(a,b,c){var d=this.scale,e=new mxPoint(this.translate.x,this.translate.y);if(this.scale!=a||this.translate.x!=b||this.translate.y!=c)this.scale=a,this.translate.x=b,this.translate.y=c,this.isEventsEnabled()&&(this.revalidate(),this.graph.sizeDidChange());this.fireEvent(new mxEventObject(mxEvent.SCALE_AND_TRANSLATE,"scale",a,"previousScale",d,"translate",this.translate,"previousTranslate",e))};mxGraphView.prototype.getScale=function(){return [...]
+mxGraphView.prototype.setScale=function(a){var b=this.scale;this.scale!=a&&(this.scale=a,this.isEventsEnabled()&&(this.revalidate(),this.graph.sizeDidChange()));this.fireEvent(new mxEventObject(mxEvent.SCALE,"scale",a,"previousScale",b))};mxGraphView.prototype.getTranslate=function(){return this.translate};
+mxGraphView.prototype.setTranslate=function(a,b){var c=new mxPoint(this.translate.x,this.translate.y);if(this.translate.x!=a||this.translate.y!=b)this.translate.x=a,this.translate.y=b,this.isEventsEnabled()&&(this.revalidate(),this.graph.sizeDidChange());this.fireEvent(new mxEventObject(mxEvent.TRANSLATE,"translate",this.translate,"previousTranslate",c))};mxGraphView.prototype.refresh=function(){null!=this.currentRoot&&this.clear();this.revalidate()};
+mxGraphView.prototype.revalidate=function(){this.invalidate();this.validate()};mxGraphView.prototype.clear=function(a,b,c){var d=this.graph.getModel();a=a||d.getRoot();b=null!=b?b:!1;c=null!=c?c:!0;this.removeState(a);if(c&&(b||a!=this.currentRoot)){c=d.getChildCount(a);for(var e=0;e<c;e++)this.clear(d.getChildAt(a,e),b)}else this.invalidate(a)};
+mxGraphView.prototype.invalidate=function(a,b,c){var d=this.graph.getModel();a=a||d.getRoot();b=null!=b?b:!0;c=null!=c?c:!0;var e=this.getState(a);null!=e&&(e.invalid=!0);if(!a.invalidating){a.invalidating=!0;if(b)for(var f=d.getChildCount(a),e=0;e<f;e++){var g=d.getChildAt(a,e);this.invalidate(g,b,c)}if(c)for(f=d.getEdgeCount(a),e=0;e<f;e++)this.invalidate(d.getEdgeAt(a,e),b,c);delete a.invalidating}};
+mxGraphView.prototype.validate=function(a){var b=mxLog.enter("mxGraphView.validate");window.status=mxResources.get(this.updatingDocumentResource)||this.updatingDocumentResource;this.resetValidationState();var c=null;this.optimizeVmlReflows&&null!=this.canvas&&null==this.textDiv&&(8==document.documentMode&&!mxClient.IS_EM||mxClient.IS_QUIRKS)&&(this.placeholder=document.createElement("div"),this.placeholder.style.position="absolute",this.placeholder.style.width=this.canvas.clientWidth+"px [...]
+this.canvas.clientHeight+"px",this.canvas.parentNode.appendChild(this.placeholder),c=this.drawPane.style.display,this.canvas.style.display="none",this.textDiv=document.createElement("div"),this.textDiv.style.position="absolute",this.textDiv.style.whiteSpace="nowrap",this.textDiv.style.visibility="hidden",this.textDiv.style.display=mxClient.IS_QUIRKS?"inline":"inline-block",this.textDiv.style.zoom="1",document.body.appendChild(this.textDiv));a=this.getBoundingBox(this.validateCellState(th [...]
+(null!=this.currentRoot?this.currentRoot:this.graph.getModel().getRoot()))));this.setGraphBounds(null!=a?a:this.getEmptyBounds());this.validateBackground();null!=c&&(this.canvas.style.display=c,this.textDiv.parentNode.removeChild(this.textDiv),null!=this.placeholder&&this.placeholder.parentNode.removeChild(this.placeholder),this.textDiv=null);this.resetValidationState();window.status=mxResources.get(this.doneResource)||this.doneResource;mxLog.leave("mxGraphView.validate",b)};
+mxGraphView.prototype.getEmptyBounds=function(){return new mxRectangle(this.translate.x*this.scale,this.translate.y*this.scale)};
+mxGraphView.prototype.getBoundingBox=function(a,b){b=null!=b?b:!0;var c=null;if(null!=a&&(null!=a.shape&&null!=a.shape.boundingBox&&(c=a.shape.boundingBox.clone()),null!=a.text&&null!=a.text.boundingBox&&(null!=c?c.add(a.text.boundingBox):c=a.text.boundingBox.clone()),b))for(var d=this.graph.getModel(),e=d.getChildCount(a.cell),f=0;f<e;f++){var g=this.getBoundingBox(this.getState(d.getChildAt(a.cell,f)));null!=g&&(null==c?c=g:c.add(g))}return c};
+mxGraphView.prototype.createBackgroundPageShape=function(a){return new mxRectangleShape(a,"white","black")};mxGraphView.prototype.validateBackground=function(){this.validateBackgroundImage();this.validateBackgroundPage()};
+mxGraphView.prototype.validateBackgroundImage=function(){var a=this.graph.getBackgroundImage();if(null!=a){if(null==this.backgroundImage||this.backgroundImage.image!=a.src){null!=this.backgroundImage&&this.backgroundImage.destroy();var b=new mxRectangle(0,0,1,1);this.backgroundImage=new mxImageShape(b,a.src);this.backgroundImage.dialect=this.graph.dialect;this.backgroundImage.init(this.backgroundPane);this.backgroundImage.redraw();8!=document.documentMode||mxClient.IS_EM||mxEvent.addGest [...]
+mxUtils.bind(this,function(a){this.graph.fireMouseEvent(mxEvent.MOUSE_DOWN,new mxMouseEvent(a))}),mxUtils.bind(this,function(a){this.graph.fireMouseEvent(mxEvent.MOUSE_MOVE,new mxMouseEvent(a))}),mxUtils.bind(this,function(a){this.graph.fireMouseEvent(mxEvent.MOUSE_UP,new mxMouseEvent(a))}))}this.redrawBackgroundImage(this.backgroundImage,a)}else null!=this.backgroundImage&&(this.backgroundImage.destroy(),this.backgroundImage=null)};
+mxGraphView.prototype.validateBackgroundPage=function(){if(this.graph.pageVisible){var a=this.getBackgroundPageBounds();null==this.backgroundPageShape?(this.backgroundPageShape=this.createBackgroundPageShape(a),this.backgroundPageShape.scale=this.scale,this.backgroundPageShape.isShadow=!0,this.backgroundPageShape.dialect=this.graph.dialect,this.backgroundPageShape.init(this.backgroundPane),this.backgroundPageShape.redraw(),this.graph.nativeDblClickEnabled&&mxEvent.addListener(this.backgr [...]
+"dblclick",mxUtils.bind(this,function(a){this.graph.dblClick(a)})),mxEvent.addGestureListeners(this.backgroundPageShape.node,mxUtils.bind(this,function(a){this.graph.fireMouseEvent(mxEvent.MOUSE_DOWN,new mxMouseEvent(a))}),mxUtils.bind(this,function(a){null!=this.graph.tooltipHandler&&this.graph.tooltipHandler.isHideOnHover()&&this.graph.tooltipHandler.hide();this.graph.isMouseDown&&!mxEvent.isConsumed(a)&&this.graph.fireMouseEvent(mxEvent.MOUSE_MOVE,new mxMouseEvent(a))}),mxUtils.bind(t [...]
+new mxMouseEvent(a))}))):(this.backgroundPageShape.scale=this.scale,this.backgroundPageShape.bounds=a,this.backgroundPageShape.redraw())}else null!=this.backgroundPageShape&&(this.backgroundPageShape.destroy(),this.backgroundPageShape=null)};mxGraphView.prototype.getBackgroundPageBounds=function(){var a=this.graph.pageFormat,b=this.scale*this.graph.pageScale;return new mxRectangle(this.scale*this.translate.x,this.scale*this.translate.y,a.width*b,a.height*b)};
+mxGraphView.prototype.redrawBackgroundImage=function(a,b){a.scale=this.scale;a.bounds.x=this.scale*this.translate.x;a.bounds.y=this.scale*this.translate.y;a.bounds.width=this.scale*b.width;a.bounds.height=this.scale*b.height;a.redraw()};
+mxGraphView.prototype.validateCell=function(a,b){if(null!=a)if(b=(null!=b?b:!0)&&this.graph.isCellVisible(a),null==this.getState(a,b)||b)for(var c=this.graph.getModel(),d=c.getChildCount(a),e=0;e<d;e++)this.validateCell(c.getChildAt(a,e),b&&(!this.isCellCollapsed(a)||a==this.currentRoot));else this.removeState(a);return a};
+mxGraphView.prototype.validateCellState=function(a,b){b=null!=b?b:!0;var c=null;if(null!=a&&(c=this.getState(a),null!=c)){var d=this.graph.getModel();c.invalid&&(c.invalid=!1,null==c.style&&(c.style=this.graph.getCellStyle(c.cell)),a!=this.currentRoot&&this.validateCellState(d.getParent(a),!1),c.setVisibleTerminalState(this.validateCellState(this.getVisibleTerminal(a,!0),!1),!0),c.setVisibleTerminalState(this.validateCellState(this.getVisibleTerminal(a,!1),!1),!1),this.updateCellState(c) [...]
+c.invalid||(this.graph.cellRenderer.redraw(c,!1,this.isRendering()),c.updateCachedBounds()));if(b&&!c.invalid){null!=c.shape&&this.stateValidated(c);for(var e=d.getChildCount(a),f=0;f<e;f++)this.validateCellState(d.getChildAt(a,f))}}return c};
+mxGraphView.prototype.updateCellState=function(a){a.absoluteOffset.x=0;a.absoluteOffset.y=0;a.origin.x=0;a.origin.y=0;a.length=0;if(a.cell!=this.currentRoot){var b=this.graph.getModel(),c=this.getState(b.getParent(a.cell));null!=c&&c.cell!=this.currentRoot&&(a.origin.x+=c.origin.x,a.origin.y+=c.origin.y);var d=this.graph.getChildOffsetForCell(a.cell);null!=d&&(a.origin.x+=d.x,a.origin.y+=d.y);var e=this.graph.getCellGeometry(a.cell);null!=e&&(b.isEdge(a.cell)||(d=e.offset||this.EMPTY_POI [...]
+null!=c?b.isEdge(c.cell)?(d=this.getPoint(c,e),null!=d&&(a.origin.x+=d.x/this.scale-c.origin.x-this.translate.x,a.origin.y+=d.y/this.scale-c.origin.y-this.translate.y)):(a.origin.x+=e.x*c.width/this.scale+d.x,a.origin.y+=e.y*c.height/this.scale+d.y):(a.absoluteOffset.x=this.scale*d.x,a.absoluteOffset.y=this.scale*d.y,a.origin.x+=e.x,a.origin.y+=e.y)),a.x=this.scale*(this.translate.x+a.origin.x),a.y=this.scale*(this.translate.y+a.origin.y),a.width=this.scale*e.width,a.unscaledWidth=e.widt [...]
+this.scale*e.height,b.isVertex(a.cell)&&this.updateVertexState(a,e),b.isEdge(a.cell)&&this.updateEdgeState(a,e))}a.updateCachedBounds()};mxGraphView.prototype.isCellCollapsed=function(a){return this.graph.isCellCollapsed(a)};
+mxGraphView.prototype.updateVertexState=function(a,b){var c=this.graph.getModel(),d=this.getState(c.getParent(a.cell));if(b.relative&&null!=d&&!c.isEdge(d.cell)){var e=mxUtils.toRadians(d.style[mxConstants.STYLE_ROTATION]||"0");if(0!=e){var c=Math.cos(e),e=Math.sin(e),f=new mxPoint(a.getCenterX(),a.getCenterY()),d=new mxPoint(d.getCenterX(),d.getCenterY()),d=mxUtils.getRotatedPoint(f,c,e,d);a.x=d.x-a.width/2;a.y=d.y-a.height/2}}this.updateVertexLabelOffset(a)};
+mxGraphView.prototype.updateEdgeState=function(a,b){var c=a.getVisibleTerminalState(!0),d=a.getVisibleTerminalState(!1);null!=this.graph.model.getTerminal(a.cell,!0)&&null==c||null==c&&null==b.getTerminalPoint(!0)||null!=this.graph.model.getTerminal(a.cell,!1)&&null==d||null==d&&null==b.getTerminalPoint(!1)?this.clear(a.cell,!0):(this.updateFixedTerminalPoints(a,c,d),this.updatePoints(a,b.points,c,d),this.updateFloatingTerminalPoints(a,c,d),c=a.absolutePoints,a.cell!=this.currentRoot&&(n [...]
+c.length||null==c[0]||null==c[c.length-1])?this.clear(a.cell,!0):(this.updateEdgeBounds(a),this.updateEdgeLabelOffset(a)))};
+mxGraphView.prototype.updateVertexLabelOffset=function(a){var b=mxUtils.getValue(a.style,mxConstants.STYLE_LABEL_POSITION,mxConstants.ALIGN_CENTER);if(b==mxConstants.ALIGN_LEFT)b=mxUtils.getValue(a.style,mxConstants.STYLE_LABEL_WIDTH,null),b=null!=b?b*this.scale:a.width,a.absoluteOffset.x-=b;else if(b==mxConstants.ALIGN_RIGHT)a.absoluteOffset.x+=a.width;else if(b==mxConstants.ALIGN_CENTER&&(b=mxUtils.getValue(a.style,mxConstants.STYLE_LABEL_WIDTH,null),null!=b)){var c=mxUtils.getValue(a. [...]
+mxConstants.ALIGN_CENTER),d=0;c==mxConstants.ALIGN_CENTER?d=.5:c==mxConstants.ALIGN_RIGHT&&(d=1);0!=d&&(a.absoluteOffset.x-=(b*this.scale-a.width)*d)}b=mxUtils.getValue(a.style,mxConstants.STYLE_VERTICAL_LABEL_POSITION,mxConstants.ALIGN_MIDDLE);b==mxConstants.ALIGN_TOP?a.absoluteOffset.y-=a.height:b==mxConstants.ALIGN_BOTTOM&&(a.absoluteOffset.y+=a.height)};mxGraphView.prototype.resetValidationState=function(){this.lastForegroundHtmlNode=this.lastForegroundNode=this.lastHtmlNode=this.las [...]
+mxGraphView.prototype.stateValidated=function(a){var b=this.graph.getModel().isEdge(a.cell)&&this.graph.keepEdgesInForeground||this.graph.getModel().isVertex(a.cell)&&this.graph.keepEdgesInBackground;a=this.graph.cellRenderer.insertStateAfter(a,b?this.lastForegroundNode||this.lastNode:this.lastNode,b?this.lastForegroundHtmlNode||this.lastHtmlNode:this.lastHtmlNode);b?(this.lastForegroundHtmlNode=a[1],this.lastForegroundNode=a[0]):(this.lastHtmlNode=a[1],this.lastNode=a[0])};
+mxGraphView.prototype.updateFixedTerminalPoints=function(a,b,c){this.updateFixedTerminalPoint(a,b,!0,this.graph.getConnectionConstraint(a,b,!0));this.updateFixedTerminalPoint(a,c,!1,this.graph.getConnectionConstraint(a,c,!1))};mxGraphView.prototype.updateFixedTerminalPoint=function(a,b,c,d){a.setAbsoluteTerminalPoint(this.getFixedTerminalPoint(a,b,c,d),c)};
+mxGraphView.prototype.getFixedTerminalPoint=function(a,b,c,d){var e=null;null!=d&&(e=this.graph.getConnectionPoint(b,d));if(null==e&&null==b){b=this.scale;d=this.translate;var f=a.origin,e=this.graph.getCellGeometry(a.cell).getTerminalPoint(c);null!=e&&(e=new mxPoint(b*(d.x+e.x+f.x),b*(d.y+e.y+f.y)))}return e};
+mxGraphView.prototype.updateBoundsFromStencil=function(a){var b=null;if(null!=a&&null!=a.shape&&null!=a.shape.stencil&&"fixed"==a.shape.stencil.aspect){var b=mxRectangle.fromRectangle(a),c=a.shape.stencil.computeAspect(a.style,a.x,a.y,a.width,a.height);a.setRect(c.x,c.y,a.shape.stencil.w0*c.width,a.shape.stencil.h0*c.height)}return b};
+mxGraphView.prototype.updatePoints=function(a,b,c,d){if(null!=a){var e=[];e.push(a.absolutePoints[0]);var f=this.getEdgeStyle(a,b,c,d);if(null!=f){c=this.getTerminalPort(a,c,!0);d=this.getTerminalPort(a,d,!1);var g=this.updateBoundsFromStencil(c),k=this.updateBoundsFromStencil(d);f(a,c,d,b,e);null!=g&&c.setRect(g.x,g.y,g.width,g.height);null!=k&&d.setRect(k.x,k.y,k.width,k.height)}else if(null!=b)for(f=0;f<b.length;f++)null!=b[f]&&(c=mxUtils.clone(b[f]),e.push(this.transformControlPoint( [...]
+a.absolutePoints;e.push(b[b.length-1]);a.absolutePoints=e}};mxGraphView.prototype.transformControlPoint=function(a,b){if(null!=a&&null!=b){var c=a.origin;return new mxPoint(this.scale*(b.x+this.translate.x+c.x),this.scale*(b.y+this.translate.y+c.y))}return null};
+mxGraphView.prototype.isLoopStyleEnabled=function(a,b,c,d){b=this.graph.getConnectionConstraint(a,c,!0);var e=this.graph.getConnectionConstraint(a,d,!1);return mxUtils.getValue(a.style,mxConstants.STYLE_ORTHOGONAL_LOOP,!1)&&(null!=b&&null!=b.point||null!=e&&null!=e.point)?!1:null!=c&&c==d};
+mxGraphView.prototype.getEdgeStyle=function(a,b,c,d){a=this.isLoopStyleEnabled(a,b,c,d)?mxUtils.getValue(a.style,mxConstants.STYLE_LOOP,this.graph.defaultLoopStyle):mxUtils.getValue(a.style,mxConstants.STYLE_NOEDGESTYLE,!1)?null:a.style[mxConstants.STYLE_EDGE];"string"==typeof a&&(b=mxStyleRegistry.getValue(a),null==b&&this.isAllowEval()&&(b=mxUtils.eval(a)),a=b);return"function"==typeof a?a:null};
+mxGraphView.prototype.updateFloatingTerminalPoints=function(a,b,c){var d=a.absolutePoints,e=d[0];null==d[d.length-1]&&null!=c&&this.updateFloatingTerminalPoint(a,c,b,!1);null==e&&null!=b&&this.updateFloatingTerminalPoint(a,b,c,!0)};mxGraphView.prototype.updateFloatingTerminalPoint=function(a,b,c,d){a.setAbsoluteTerminalPoint(this.getFloatingTerminalPoint(a,b,c,d),d)};
+mxGraphView.prototype.getFloatingTerminalPoint=function(a,b,c,d){b=this.getTerminalPort(a,b,d);var e=this.getNextPoint(a,c,d),f=this.graph.isOrthogonal(a);c=mxUtils.toRadians(Number(b.style[mxConstants.STYLE_ROTATION]||"0"));var g=new mxPoint(b.getCenterX(),b.getCenterY());if(0!=c)var k=Math.cos(-c),l=Math.sin(-c),e=mxUtils.getRotatedPoint(e,k,l,g);k=parseFloat(a.style[mxConstants.STYLE_PERIMETER_SPACING]||0);k+=parseFloat(a.style[d?mxConstants.STYLE_SOURCE_PERIMETER_SPACING:mxConstants. [...]
+0);a=this.getPerimeterPoint(b,e,0==c&&f,k);0!=c&&(k=Math.cos(c),l=Math.sin(c),a=mxUtils.getRotatedPoint(a,k,l,g));return a};mxGraphView.prototype.getTerminalPort=function(a,b,c){a=mxUtils.getValue(a.style,c?mxConstants.STYLE_SOURCE_PORT:mxConstants.STYLE_TARGET_PORT);null!=a&&(a=this.getState(this.graph.getModel().getCell(a)),null!=a&&(b=a));return b};
+mxGraphView.prototype.getPerimeterPoint=function(a,b,c,d){var e=null;if(null!=a){var f=this.getPerimeterFunction(a);if(null!=f&&null!=b&&(d=this.getPerimeterBounds(a,d),0<d.width||0<d.height)){var e=new mxPoint(b.x,b.y),g=b=!1;this.graph.model.isVertex(a.cell)&&(b=1==mxUtils.getValue(a.style,mxConstants.STYLE_FLIPH,0),g=1==mxUtils.getValue(a.style,mxConstants.STYLE_FLIPV,0),null!=a.shape&&null!=a.shape.stencil&&(b=1==mxUtils.getValue(a.style,"stencilFlipH",0)||b,g=1==mxUtils.getValue(a.s [...]
+0)||g),b&&(e.x=2*d.getCenterX()-e.x),g&&(e.y=2*d.getCenterY()-e.y));e=f(d,a,e,c);null!=e&&(b&&(e.x=2*d.getCenterX()-e.x),g&&(e.y=2*d.getCenterY()-e.y))}null==e&&(e=this.getPoint(a))}return e};mxGraphView.prototype.getRoutingCenterX=function(a){var b=null!=a.style?parseFloat(a.style[mxConstants.STYLE_ROUTING_CENTER_X])||0:0;return a.getCenterX()+b*a.width};
+mxGraphView.prototype.getRoutingCenterY=function(a){var b=null!=a.style?parseFloat(a.style[mxConstants.STYLE_ROUTING_CENTER_Y])||0:0;return a.getCenterY()+b*a.height};mxGraphView.prototype.getPerimeterBounds=function(a,b){b=null!=b?b:0;null!=a&&(b+=parseFloat(a.style[mxConstants.STYLE_PERIMETER_SPACING]||0));return a.getPerimeterBounds(b*this.scale)};
+mxGraphView.prototype.getPerimeterFunction=function(a){a=a.style[mxConstants.STYLE_PERIMETER];if("string"==typeof a){var b=mxStyleRegistry.getValue(a);null==b&&this.isAllowEval()&&(b=mxUtils.eval(a));a=b}return"function"==typeof a?a:null};mxGraphView.prototype.getNextPoint=function(a,b,c){a=a.absolutePoints;var d=null;null!=a&&2<=a.length&&(d=a.length,d=a[c?Math.min(1,d-1):Math.max(0,d-2)]);null==d&&null!=b&&(d=new mxPoint(b.getCenterX(),b.getCenterY()));return d};
+mxGraphView.prototype.getVisibleTerminal=function(a,b){for(var c=this.graph.getModel(),d=c.getTerminal(a,b),e=d;null!=d&&d!=this.currentRoot;){if(!this.graph.isCellVisible(e)||this.isCellCollapsed(d))e=d;d=c.getParent(d)}c.getParent(e)==c.getRoot()&&(e=null);return e};
+mxGraphView.prototype.updateEdgeBounds=function(a){var b=a.absolutePoints,c=b[0],d=b[b.length-1];if(c.x!=d.x||c.y!=d.y){var e=d.x-c.x,f=d.y-c.y;a.terminalDistance=Math.sqrt(e*e+f*f)}else a.terminalDistance=0;var d=0,g=[],f=c;if(null!=f){for(var c=f.x,k=f.y,l=c,m=k,n=1;n<b.length;n++){var p=b[n];null!=p&&(e=f.x-p.x,f=f.y-p.y,e=Math.sqrt(e*e+f*f),g.push(e),d+=e,f=p,c=Math.min(f.x,c),k=Math.min(f.y,k),l=Math.max(f.x,l),m=Math.max(f.y,m))}a.length=d;a.segments=g;a.x=c;a.y=k;a.width=Math.max( [...]
+Math.max(1,m-k)}};
+mxGraphView.prototype.getPoint=function(a,b){var c=a.getCenterX(),d=a.getCenterY();if(null==a.segments||null!=b&&!b.relative)null!=b&&(m=b.offset,null!=m&&(c+=m.x,d+=m.y));else{for(var e=a.absolutePoints.length,f=Math.round(((null!=b?b.x/2:0)+.5)*a.length),g=a.segments[0],k=0,l=1;f>=Math.round(k+g)&&l<e-1;)k+=g,g=a.segments[l++];e=0==g?0:(f-k)/g;f=a.absolutePoints[l-1];l=a.absolutePoints[l];if(null!=f&&null!=l){k=c=d=0;if(null!=b){var d=b.y,m=b.offset;null!=m&&(c=m.x,k=m.y)}m=l.x-f.x;l=l [...]
+f.x+m*e+((0==g?0:l/g)*d+c)*this.scale;d=f.y+l*e-((0==g?0:m/g)*d-k)*this.scale}}return new mxPoint(c,d)};
+mxGraphView.prototype.getRelativePoint=function(a,b,c){var d=this.graph.getModel().getGeometry(a.cell);if(null!=d){var e=a.absolutePoints.length;if(d.relative&&1<e){for(var d=a.length,f=a.segments,g=a.absolutePoints[0],k=a.absolutePoints[1],l=mxUtils.ptSegDistSq(g.x,g.y,k.x,k.y,b,c),m=0,n=0,p=0,q=2;q<e;q++)n+=f[q-2],k=a.absolutePoints[q],g=mxUtils.ptSegDistSq(g.x,g.y,k.x,k.y,b,c),g<=l&&(l=g,m=q-1,p=n),g=k;e=f[m];g=a.absolutePoints[m];k=a.absolutePoints[m+1];l=k.x;f=k.y;a=g.x-l;m=g.y-f;l= [...]
+m-(c-f);f=l*a+f*m;a=Math.sqrt(0>=f?0:f*f/(a*a+m*m));a>e&&(a=e);e=Math.sqrt(mxUtils.ptSegDistSq(g.x,g.y,k.x,k.y,b,c));-1==mxUtils.relativeCcw(g.x,g.y,k.x,k.y,b,c)&&(e=-e);return new mxPoint((d/2-p-a)/d*-2,e/this.scale)}}return new mxPoint};
+mxGraphView.prototype.updateEdgeLabelOffset=function(a){var b=a.absolutePoints;a.absoluteOffset.x=a.getCenterX();a.absoluteOffset.y=a.getCenterY();if(null!=b&&0<b.length&&null!=a.segments){var c=this.graph.getCellGeometry(a.cell);if(c.relative){var d=this.getPoint(a,c);null!=d&&(a.absoluteOffset=d)}else{var d=b[0],e=b[b.length-1];if(null!=d&&null!=e){var b=e.x-d.x,f=e.y-d.y,g=e=0,c=c.offset;null!=c&&(e=c.x,g=c.y);c=d.y+f/2+g*this.scale;a.absoluteOffset.x=d.x+b/2+e*this.scale;a.absoluteOf [...]
+mxGraphView.prototype.getState=function(a,b){b=b||!1;var c=null;null!=a&&(c=this.states.get(a),b&&(null==c||this.updateStyle)&&this.graph.isCellVisible(a)&&(null==c?(c=this.createState(a),this.states.put(a,c)):c.style=this.graph.getCellStyle(a)));return c};mxGraphView.prototype.isRendering=function(){return this.rendering};mxGraphView.prototype.setRendering=function(a){this.rendering=a};mxGraphView.prototype.isAllowEval=function(){return this.allowEval};
+mxGraphView.prototype.setAllowEval=function(a){this.allowEval=a};mxGraphView.prototype.getStates=function(){return this.states};mxGraphView.prototype.setStates=function(a){this.states=a};mxGraphView.prototype.getCellStates=function(a){if(null==a)return this.states;for(var b=[],c=0;c<a.length;c++){var d=this.getState(a[c]);null!=d&&b.push(d)}return b};
+mxGraphView.prototype.removeState=function(a){var b=null;null!=a&&(b=this.states.remove(a),null!=b&&(this.graph.cellRenderer.destroy(b),b.invalid=!0,b.destroy()));return b};mxGraphView.prototype.createState=function(a){return new mxCellState(this,a,this.graph.getCellStyle(a))};mxGraphView.prototype.getCanvas=function(){return this.canvas};mxGraphView.prototype.getBackgroundPane=function(){return this.backgroundPane};mxGraphView.prototype.getDrawPane=function(){return this.drawPane};
+mxGraphView.prototype.getOverlayPane=function(){return this.overlayPane};mxGraphView.prototype.getDecoratorPane=function(){return this.decoratorPane};mxGraphView.prototype.isContainerEvent=function(a){a=mxEvent.getSource(a);return a==this.graph.container||a.parentNode==this.backgroundPane||null!=a.parentNode&&a.parentNode.parentNode==this.backgroundPane||a==this.canvas.parentNode||a==this.canvas||a==this.backgroundPane||a==this.drawPane||a==this.overlayPane||a==this.decoratorPane};
+mxGraphView.prototype.isScrollEvent=function(a){var b=mxUtils.getOffset(this.graph.container);a=new mxPoint(a.clientX-b.x,a.clientY-b.y);var b=this.graph.container.offsetWidth,c=this.graph.container.clientWidth;if(b>c&&a.x>c+2&&a.x<=b)return!0;b=this.graph.container.offsetHeight;c=this.graph.container.clientHeight;return b>c&&a.y>c+2&&a.y<=b?!0:!1};
+mxGraphView.prototype.init=function(){this.installListeners();var a=this.graph;a.dialect==mxConstants.DIALECT_SVG?this.createSvg():a.dialect==mxConstants.DIALECT_VML?this.createVml():this.createHtml()};
+mxGraphView.prototype.installListeners=function(){var a=this.graph,b=a.container;if(null!=b){mxClient.IS_TOUCH&&(mxEvent.addListener(b,"gesturestart",mxUtils.bind(this,function(b){a.fireGestureEvent(b);mxEvent.consume(b)})),mxEvent.addListener(b,"gesturechange",mxUtils.bind(this,function(b){a.fireGestureEvent(b);mxEvent.consume(b)})),mxEvent.addListener(b,"gestureend",mxUtils.bind(this,function(b){a.fireGestureEvent(b);mxEvent.consume(b)})));mxEvent.addGestureListeners(b,mxUtils.bind(thi [...]
+(mxClient.IS_IE||mxClient.IS_IE11||mxClient.IS_GC||mxClient.IS_OP||mxClient.IS_SF)&&this.isScrollEvent(b)||a.fireMouseEvent(mxEvent.MOUSE_DOWN,new mxMouseEvent(b))}),mxUtils.bind(this,function(b){this.isContainerEvent(b)&&a.fireMouseEvent(mxEvent.MOUSE_MOVE,new mxMouseEvent(b))}),mxUtils.bind(this,function(b){this.isContainerEvent(b)&&a.fireMouseEvent(mxEvent.MOUSE_UP,new mxMouseEvent(b))}));mxEvent.addListener(b,"dblclick",mxUtils.bind(this,function(b){this.isContainerEvent(b)&&a.dblCli [...]
+var c=function(c){var d=null;mxClient.IS_TOUCH&&(d=mxEvent.getClientX(c),c=mxEvent.getClientY(c),c=mxUtils.convertPoint(b,d,c),d=a.view.getState(a.getCellAt(c.x,c.y)));return d};a.addMouseListener({mouseDown:function(b,c){a.popupMenuHandler.hideMenu()},mouseMove:function(){},mouseUp:function(){}});this.moveHandler=mxUtils.bind(this,function(b){null!=a.tooltipHandler&&a.tooltipHandler.isHideOnHover()&&a.tooltipHandler.hide();this.captureDocumentGesture&&a.isMouseDown&&null!=a.container&&! [...]
+"none"!=a.container.style.display&&"hidden"!=a.container.style.visibility&&!mxEvent.isConsumed(b)&&a.fireMouseEvent(mxEvent.MOUSE_MOVE,new mxMouseEvent(b,c(b)))});this.endHandler=mxUtils.bind(this,function(b){this.captureDocumentGesture&&a.isMouseDown&&null!=a.container&&!this.isContainerEvent(b)&&"none"!=a.container.style.display&&"hidden"!=a.container.style.visibility&&a.fireMouseEvent(mxEvent.MOUSE_UP,new mxMouseEvent(b))});mxEvent.addGestureListeners(document,null,this.moveHandler,th [...]
+mxGraphView.prototype.createHtml=function(){var a=this.graph.container;null!=a&&(this.canvas=this.createHtmlPane("100%","100%"),this.canvas.style.overflow="hidden",this.backgroundPane=this.createHtmlPane("1px","1px"),this.drawPane=this.createHtmlPane("1px","1px"),this.overlayPane=this.createHtmlPane("1px","1px"),this.decoratorPane=this.createHtmlPane("1px","1px"),this.canvas.appendChild(this.backgroundPane),this.canvas.appendChild(this.drawPane),this.canvas.appendChild(this.overlayPane), [...]
+a.appendChild(this.canvas),this.updateContainerStyle(a),mxClient.IS_QUIRKS&&(a=mxUtils.bind(this,function(a){a=this.getGraphBounds();this.updateHtmlCanvasSize(a.x+a.width+this.graph.border,a.y+a.height+this.graph.border)}),mxEvent.addListener(window,"resize",a)))};
+mxGraphView.prototype.updateHtmlCanvasSize=function(a,b){if(null!=this.graph.container){var c=this.graph.container.offsetHeight;this.canvas.style.width=this.graph.container.offsetWidth<a?a+"px":"100%";this.canvas.style.height=c<b?b+"px":"100%"}};mxGraphView.prototype.createHtmlPane=function(a,b){var c=document.createElement("DIV");null!=a&&null!=b?(c.style.position="absolute",c.style.left="0px",c.style.top="0px",c.style.width=a,c.style.height=b):c.style.position="relative";return c};
+mxGraphView.prototype.createVml=function(){var a=this.graph.container;if(null!=a){var b=a.offsetWidth,c=a.offsetHeight;this.canvas=this.createVmlPane(b,c);this.canvas.style.overflow="hidden";this.backgroundPane=this.createVmlPane(b,c);this.drawPane=this.createVmlPane(b,c);this.overlayPane=this.createVmlPane(b,c);this.decoratorPane=this.createVmlPane(b,c);this.canvas.appendChild(this.backgroundPane);this.canvas.appendChild(this.drawPane);this.canvas.appendChild(this.overlayPane);this.canv [...]
+a.appendChild(this.canvas)}};mxGraphView.prototype.createVmlPane=function(a,b){var c=document.createElement(mxClient.VML_PREFIX+":group");c.style.position="absolute";c.style.left="0px";c.style.top="0px";c.style.width=a+"px";c.style.height=b+"px";c.setAttribute("coordsize",a+","+b);c.setAttribute("coordorigin","0,0");return c};
+mxGraphView.prototype.createSvg=function(){var a=this.graph.container;this.canvas=document.createElementNS(mxConstants.NS_SVG,"g");this.backgroundPane=document.createElementNS(mxConstants.NS_SVG,"g");this.canvas.appendChild(this.backgroundPane);this.drawPane=document.createElementNS(mxConstants.NS_SVG,"g");this.canvas.appendChild(this.drawPane);this.overlayPane=document.createElementNS(mxConstants.NS_SVG,"g");this.canvas.appendChild(this.overlayPane);this.decoratorPane=document.createEle [...]
+"g");this.canvas.appendChild(this.decoratorPane);var b=document.createElementNS(mxConstants.NS_SVG,"svg");b.style.width="100%";b.style.height="100%";b.style.display="block";b.appendChild(this.canvas);if(mxClient.IS_IE||mxClient.IS_IE11)b.style.overflow="hidden";null!=a&&(a.appendChild(b),this.updateContainerStyle(a))};
+mxGraphView.prototype.updateContainerStyle=function(a){var b=mxUtils.getCurrentStyle(a);null!=b&&"static"==b.position&&(a.style.position="relative");mxClient.IS_POINTER&&(a.style.touchAction="none")};
+mxGraphView.prototype.destroy=function(){var a=null!=this.canvas?this.canvas.ownerSVGElement:null;null==a&&(a=this.canvas);null!=a&&null!=a.parentNode&&(this.clear(this.currentRoot,!0),mxEvent.removeGestureListeners(document,null,this.moveHandler,this.endHandler),mxEvent.release(this.graph.container),a.parentNode.removeChild(a),this.decoratorPane=this.overlayPane=this.drawPane=this.backgroundPane=this.canvas=this.endHandler=this.moveHandler=null)};
+function mxCurrentRootChange(a,b){this.view=a;this.previous=this.root=b;this.isUp=null==b;if(!this.isUp)for(var c=this.view.currentRoot,d=this.view.graph.getModel();null!=c;){if(c==b){this.isUp=!0;break}c=d.getParent(c)}}
+mxCurrentRootChange.prototype.execute=function(){var a=this.view.currentRoot;this.view.currentRoot=this.previous;this.previous=a;a=this.view.graph.getTranslateForRoot(this.view.currentRoot);null!=a&&(this.view.translate=new mxPoint(-a.x,-a.y));this.isUp?(this.view.clear(this.view.currentRoot,!0),this.view.validate()):this.view.refresh();this.view.fireEvent(new mxEventObject(this.isUp?mxEvent.UP:mxEvent.DOWN,"root",this.view.currentRoot,"previous",this.previous));this.isUp=!this.isUp};
+function mxGraph(a,b,c,d){this.mouseListeners=null;this.renderHint=c;this.dialect=mxClient.IS_SVG?mxConstants.DIALECT_SVG:c==mxConstants.RENDERING_HINT_EXACT&&mxClient.IS_VML?mxConstants.DIALECT_VML:c==mxConstants.RENDERING_HINT_FASTEST?mxConstants.DIALECT_STRICTHTML:c==mxConstants.RENDERING_HINT_FASTER?mxConstants.DIALECT_PREFERHTML:mxConstants.DIALECT_MIXEDHTML;this.model=null!=b?b:new mxGraphModel;this.multiplicities=[];this.imageBundles=[];this.cellRenderer=this.createCellRenderer(); [...]
+this.setStylesheet(null!=d?d:this.createStylesheet());this.view=this.createGraphView();this.graphModelChangeListener=mxUtils.bind(this,function(a,b){this.graphModelChanged(b.getProperty("edit").changes)});this.model.addListener(mxEvent.CHANGE,this.graphModelChangeListener);this.createHandlers();null!=a&&this.init(a);this.view.revalidate()}mxLoadResources&&mxResources.add(mxClient.basePath+"/resources/graph");mxGraph.prototype=new mxEventSource;mxGraph.prototype.constructor=mxGraph;
+mxGraph.prototype.EMPTY_ARRAY=[];mxGraph.prototype.mouseListeners=null;mxGraph.prototype.isMouseDown=!1;mxGraph.prototype.model=null;mxGraph.prototype.view=null;mxGraph.prototype.stylesheet=null;mxGraph.prototype.selectionModel=null;mxGraph.prototype.cellEditor=null;mxGraph.prototype.cellRenderer=null;mxGraph.prototype.multiplicities=null;mxGraph.prototype.renderHint=null;mxGraph.prototype.dialect=null;mxGraph.prototype.gridSize=10;mxGraph.prototype.gridEnabled=!0;mxGraph.prototype.ports [...]
+mxGraph.prototype.nativeDblClickEnabled=!0;mxGraph.prototype.doubleTapEnabled=!0;mxGraph.prototype.doubleTapTimeout=500;mxGraph.prototype.doubleTapTolerance=25;mxGraph.prototype.lastTouchY=0;mxGraph.prototype.lastTouchY=0;mxGraph.prototype.lastTouchTime=0;mxGraph.prototype.tapAndHoldEnabled=!0;mxGraph.prototype.tapAndHoldDelay=500;mxGraph.prototype.tapAndHoldInProgress=!1;mxGraph.prototype.tapAndHoldValid=!1;mxGraph.prototype.initialTouchX=0;mxGraph.prototype.initialTouchY=0;
+mxGraph.prototype.tolerance=4;mxGraph.prototype.defaultOverlap=.5;mxGraph.prototype.defaultParent=null;mxGraph.prototype.alternateEdgeStyle=null;mxGraph.prototype.backgroundImage=null;mxGraph.prototype.pageVisible=!1;mxGraph.prototype.pageBreaksVisible=!1;mxGraph.prototype.pageBreakColor="gray";mxGraph.prototype.pageBreakDashed=!0;mxGraph.prototype.minPageBreakDist=20;mxGraph.prototype.preferPageSize=!1;mxGraph.prototype.pageFormat=mxConstants.PAGE_FORMAT_A4_PORTRAIT;mxGraph.prototype.pa [...]
+mxGraph.prototype.enabled=!0;mxGraph.prototype.escapeEnabled=!0;mxGraph.prototype.invokesStopCellEditing=!0;mxGraph.prototype.enterStopsCellEditing=!1;mxGraph.prototype.useScrollbarsForPanning=!0;mxGraph.prototype.exportEnabled=!0;mxGraph.prototype.importEnabled=!0;mxGraph.prototype.cellsLocked=!1;mxGraph.prototype.cellsCloneable=!0;mxGraph.prototype.foldingEnabled=!0;mxGraph.prototype.cellsEditable=!0;mxGraph.prototype.cellsDeletable=!0;mxGraph.prototype.cellsMovable=!0;
+mxGraph.prototype.edgeLabelsMovable=!0;mxGraph.prototype.vertexLabelsMovable=!1;mxGraph.prototype.dropEnabled=!1;mxGraph.prototype.splitEnabled=!0;mxGraph.prototype.cellsResizable=!0;mxGraph.prototype.cellsBendable=!0;mxGraph.prototype.cellsSelectable=!0;mxGraph.prototype.cellsDisconnectable=!0;mxGraph.prototype.autoSizeCells=!1;mxGraph.prototype.autoSizeCellsOnAdd=!1;mxGraph.prototype.autoScroll=!0;mxGraph.prototype.ignoreScrollbars=!1;mxGraph.prototype.translateToScrollPosition=!1;
+mxGraph.prototype.timerAutoScroll=!1;mxGraph.prototype.allowAutoPanning=!1;mxGraph.prototype.autoExtend=!0;mxGraph.prototype.maximumGraphBounds=null;mxGraph.prototype.minimumGraphSize=null;mxGraph.prototype.minimumContainerSize=null;mxGraph.prototype.maximumContainerSize=null;mxGraph.prototype.resizeContainer=!1;mxGraph.prototype.border=0;mxGraph.prototype.keepEdgesInForeground=!1;mxGraph.prototype.keepEdgesInBackground=!1;mxGraph.prototype.allowNegativeCoordinates=!0;
+mxGraph.prototype.constrainChildren=!0;mxGraph.prototype.constrainRelativeChildren=!1;mxGraph.prototype.extendParents=!0;mxGraph.prototype.extendParentsOnAdd=!0;mxGraph.prototype.extendParentsOnMove=!1;mxGraph.prototype.recursiveResize=!1;mxGraph.prototype.collapseToPreferredSize=!0;mxGraph.prototype.zoomFactor=1.2;mxGraph.prototype.keepSelectionVisibleOnZoom=!1;mxGraph.prototype.centerZoom=!0;mxGraph.prototype.resetViewOnRootChange=!0;mxGraph.prototype.resetEdgesOnResize=!1;
+mxGraph.prototype.resetEdgesOnMove=!1;mxGraph.prototype.resetEdgesOnConnect=!0;mxGraph.prototype.allowLoops=!1;mxGraph.prototype.defaultLoopStyle=mxEdgeStyle.Loop;mxGraph.prototype.multigraph=!0;mxGraph.prototype.connectableEdges=!1;mxGraph.prototype.allowDanglingEdges=!0;mxGraph.prototype.cloneInvalidEdges=!1;mxGraph.prototype.disconnectOnMove=!0;mxGraph.prototype.labelsVisible=!0;mxGraph.prototype.htmlLabels=!1;mxGraph.prototype.swimlaneSelectionEnabled=!0;mxGraph.prototype.swimlaneNes [...]
+mxGraph.prototype.swimlaneIndicatorColorAttribute=mxConstants.STYLE_FILLCOLOR;mxGraph.prototype.imageBundles=null;mxGraph.prototype.minFitScale=.1;mxGraph.prototype.maxFitScale=8;mxGraph.prototype.panDx=0;mxGraph.prototype.panDy=0;mxGraph.prototype.collapsedImage=new mxImage(mxClient.imageBasePath+"/collapsed.gif",9,9);mxGraph.prototype.expandedImage=new mxImage(mxClient.imageBasePath+"/expanded.gif",9,9);
+mxGraph.prototype.warningImage=new mxImage(mxClient.imageBasePath+"/warning"+(mxClient.IS_MAC?".png":".gif"),16,16);mxGraph.prototype.alreadyConnectedResource="none"!=mxClient.language?"alreadyConnected":"";mxGraph.prototype.containsValidationErrorsResource="none"!=mxClient.language?"containsValidationErrors":"";mxGraph.prototype.collapseExpandResource="none"!=mxClient.language?"collapse-expand":"";
+mxGraph.prototype.init=function(a){this.container=a;this.cellEditor=this.createCellEditor();this.view.init();this.sizeDidChange();mxEvent.addListener(a,"mouseleave",mxUtils.bind(this,function(){null!=this.tooltipHandler&&this.tooltipHandler.hide()}));mxClient.IS_IE&&(mxEvent.addListener(window,"unload",mxUtils.bind(this,function(){this.destroy()})),mxEvent.addListener(a,"selectstart",mxUtils.bind(this,function(a){return this.isEditing()||!this.isMouseDown&&!mxEvent.isShiftDown(a)})));8== [...]
+a.insertAdjacentHTML("beforeend","<"+mxClient.VML_PREFIX+':group style="DISPLAY: none;"></'+mxClient.VML_PREFIX+":group>")};
+mxGraph.prototype.createHandlers=function(){this.tooltipHandler=this.createTooltipHandler();this.tooltipHandler.setEnabled(!1);this.selectionCellsHandler=this.createSelectionCellsHandler();this.connectionHandler=this.createConnectionHandler();this.connectionHandler.setEnabled(!1);this.graphHandler=this.createGraphHandler();this.panningHandler=this.createPanningHandler();this.panningHandler.panningEnabled=!1;this.popupMenuHandler=this.createPopupMenuHandler()};
+mxGraph.prototype.createTooltipHandler=function(){return new mxTooltipHandler(this)};mxGraph.prototype.createSelectionCellsHandler=function(){return new mxSelectionCellsHandler(this)};mxGraph.prototype.createConnectionHandler=function(){return new mxConnectionHandler(this)};mxGraph.prototype.createGraphHandler=function(){return new mxGraphHandler(this)};mxGraph.prototype.createPanningHandler=function(){return new mxPanningHandler(this)};mxGraph.prototype.createPopupMenuHandler=function() [...]
+mxGraph.prototype.createSelectionModel=function(){return new mxGraphSelectionModel(this)};mxGraph.prototype.createStylesheet=function(){return new mxStylesheet};mxGraph.prototype.createGraphView=function(){return new mxGraphView(this)};mxGraph.prototype.createCellRenderer=function(){return new mxCellRenderer};mxGraph.prototype.createCellEditor=function(){return new mxCellEditor(this)};mxGraph.prototype.getModel=function(){return this.model};mxGraph.prototype.getView=function(){return thi [...]
+mxGraph.prototype.getStylesheet=function(){return this.stylesheet};mxGraph.prototype.setStylesheet=function(a){this.stylesheet=a};mxGraph.prototype.getSelectionModel=function(){return this.selectionModel};mxGraph.prototype.setSelectionModel=function(a){this.selectionModel=a};
+mxGraph.prototype.getSelectionCellsForChanges=function(a){for(var b=[],c=0;c<a.length;c++){var d=a[c];if(d.constructor!=mxRootChange){var e=null;d instanceof mxChildChange&&null==d.previous?e=d.child:null!=d.cell&&d.cell instanceof mxCell&&(e=d.cell);null!=e&&0>mxUtils.indexOf(b,e)&&b.push(e)}}return this.getModel().getTopmostCells(b)};
+mxGraph.prototype.graphModelChanged=function(a){for(var b=0;b<a.length;b++)this.processChange(a[b]);this.removeSelectionCells(this.getRemovedCellsForChanges(a));this.view.validate();this.sizeDidChange()};mxGraph.prototype.getRemovedCellsForChanges=function(a){for(var b=[],c=0;c<a.length;c++){var d=a[c];if(d instanceof mxRootChange)break;else d instanceof mxChildChange?null!=d.previous&&null==d.parent&&(b=b.concat(this.model.getDescendants(d.child))):d instanceof mxVisibleChange&&(b=b.con [...]
+mxGraph.prototype.processChange=function(a){if(a instanceof mxRootChange)this.clearSelection(),this.setDefaultParent(null),this.removeStateForCell(a.previous),this.resetViewOnRootChange&&(this.view.scale=1,this.view.translate.x=0,this.view.translate.y=0),this.fireEvent(new mxEventObject(mxEvent.ROOT));else if(a instanceof mxChildChange){var b=this.model.getParent(a.child);this.view.invalidate(a.child,!0,!0);if(null==b||this.isCellCollapsed(b))this.view.invalidate(a.child,!0,!0),this.remo [...]
+this.view.currentRoot==a.child&&this.home();b!=a.previous&&(null!=b&&this.view.invalidate(b,!1,!1),null!=a.previous&&this.view.invalidate(a.previous,!1,!1))}else a instanceof mxTerminalChange||a instanceof mxGeometryChange?(a instanceof mxTerminalChange||null==a.previous&&null!=a.geometry||null!=a.previous&&!a.previous.equals(a.geometry))&&this.view.invalidate(a.cell):a instanceof mxValueChange?this.view.invalidate(a.cell,!1,!1):a instanceof mxStyleChange?(this.view.invalidate(a.cell,!0, [...]
+null!=a&&(a.style=null)):null!=a.cell&&a.cell instanceof mxCell&&this.removeStateForCell(a.cell)};mxGraph.prototype.removeStateForCell=function(a){for(var b=this.model.getChildCount(a),c=0;c<b;c++)this.removeStateForCell(this.model.getChildAt(a,c));this.view.invalidate(a,!1,!0);this.view.removeState(a)};
+mxGraph.prototype.addCellOverlay=function(a,b){null==a.overlays&&(a.overlays=[]);a.overlays.push(b);var c=this.view.getState(a);null!=c&&this.cellRenderer.redraw(c);this.fireEvent(new mxEventObject(mxEvent.ADD_OVERLAY,"cell",a,"overlay",b));return b};mxGraph.prototype.getCellOverlays=function(a){return a.overlays};
+mxGraph.prototype.removeCellOverlay=function(a,b){if(null==b)this.removeCellOverlays(a);else{var c=mxUtils.indexOf(a.overlays,b);0<=c?(a.overlays.splice(c,1),0==a.overlays.length&&(a.overlays=null),c=this.view.getState(a),null!=c&&this.cellRenderer.redraw(c),this.fireEvent(new mxEventObject(mxEvent.REMOVE_OVERLAY,"cell",a,"overlay",b))):b=null}return b};
+mxGraph.prototype.removeCellOverlays=function(a){var b=a.overlays;if(null!=b){a.overlays=null;var c=this.view.getState(a);null!=c&&this.cellRenderer.redraw(c);for(c=0;c<b.length;c++)this.fireEvent(new mxEventObject(mxEvent.REMOVE_OVERLAY,"cell",a,"overlay",b[c]))}return b};mxGraph.prototype.clearCellOverlays=function(a){a=null!=a?a:this.model.getRoot();this.removeCellOverlays(a);for(var b=this.model.getChildCount(a),c=0;c<b;c++){var d=this.model.getChildAt(a,c);this.clearCellOverlays(d)}};
+mxGraph.prototype.setCellWarning=function(a,b,c,d){if(null!=b&&0<b.length)return c=null!=c?c:this.warningImage,b=new mxCellOverlay(c,"<font color=red>"+b+"</font>"),d&&b.addListener(mxEvent.CLICK,mxUtils.bind(this,function(b,c){this.isEnabled()&&this.setSelectionCell(a)})),this.addCellOverlay(a,b);this.removeCellOverlays(a);return null};mxGraph.prototype.startEditing=function(a){this.startEditingAtCell(null,a)};
+mxGraph.prototype.startEditingAtCell=function(a,b){null!=b&&mxEvent.isMultiTouchEvent(b)||(null==a&&(a=this.getSelectionCell(),null==a||this.isCellEditable(a)||(a=null)),null!=a&&(this.fireEvent(new mxEventObject(mxEvent.START_EDITING,"cell",a,"event",b)),this.cellEditor.startEditing(a,b),this.fireEvent(new mxEventObject(mxEvent.EDITING_STARTED,"cell",a,"event",b))))};mxGraph.prototype.getEditingValue=function(a,b){return this.convertValueToString(a)};
+mxGraph.prototype.stopEditing=function(a){this.cellEditor.stopEditing(a);this.fireEvent(new mxEventObject(mxEvent.EDITING_STOPPED,"cancel",a))};mxGraph.prototype.labelChanged=function(a,b,c){this.model.beginUpdate();try{var d=a.value;this.cellLabelChanged(a,b,this.isAutoSizeCell(a));this.fireEvent(new mxEventObject(mxEvent.LABEL_CHANGED,"cell",a,"value",b,"old",d,"event",c))}finally{this.model.endUpdate()}return a};
+mxGraph.prototype.cellLabelChanged=function(a,b,c){this.model.beginUpdate();try{this.model.setValue(a,b),c&&this.cellSizeUpdated(a,!1)}finally{this.model.endUpdate()}};mxGraph.prototype.escape=function(a){this.fireEvent(new mxEventObject(mxEvent.ESCAPE,"event",a))};
+mxGraph.prototype.click=function(a){var b=a.getEvent(),c=a.getCell(),d=new mxEventObject(mxEvent.CLICK,"event",b,"cell",c);a.isConsumed()&&d.consume();this.fireEvent(d);if(this.isEnabled()&&!mxEvent.isConsumed(b)&&!d.isConsumed())if(null!=c){if(this.isTransparentClickEvent(b)){var e=!1;a=this.getCellAt(a.graphX,a.graphY,null,null,null,mxUtils.bind(this,function(a){a=this.isCellSelected(a.cell);e=e||a;return!e||a}));null!=a&&(c=a)}this.selectCellForEvent(c,b)}else c=null,this.isSwimlaneSe [...]
+(c=this.getSwimlaneAt(a.getGraphX(),a.getGraphY())),null!=c?this.selectCellForEvent(c,b):this.isToggleEvent(b)||this.clearSelection()};mxGraph.prototype.dblClick=function(a,b){var c=new mxEventObject(mxEvent.DOUBLE_CLICK,"event",a,"cell",b);this.fireEvent(c);!this.isEnabled()||mxEvent.isConsumed(a)||c.isConsumed()||null==b||!this.isCellEditable(b)||this.isEditing(b)||(this.startEditingAtCell(b,a),mxEvent.consume(a))};
+mxGraph.prototype.tapAndHold=function(a){var b=a.getEvent(),c=new mxEventObject(mxEvent.TAP_AND_HOLD,"event",b,"cell",a.getCell());this.fireEvent(c);c.isConsumed()&&(this.panningHandler.panningTrigger=!1);this.isEnabled()&&!mxEvent.isConsumed(b)&&!c.isConsumed()&&this.connectionHandler.isEnabled()&&(b=this.view.getState(this.connectionHandler.marker.getCell(a)),null!=b&&(this.connectionHandler.marker.currentColor=this.connectionHandler.marker.validColor,this.connectionHandler.marker.mark [...]
+this.connectionHandler.marker.mark(),this.connectionHandler.first=new mxPoint(a.getGraphX(),a.getGraphY()),this.connectionHandler.edgeState=this.connectionHandler.createEdgeState(a),this.connectionHandler.previous=b,this.connectionHandler.fireEvent(new mxEventObject(mxEvent.START,"state",this.connectionHandler.previous))))};
+mxGraph.prototype.scrollPointToVisible=function(a,b,c,d){if(this.timerAutoScroll||!this.ignoreScrollbars&&!mxUtils.hasScrollbars(this.container))this.allowAutoPanning&&!this.panningHandler.isActive()&&(null==this.panningManager&&(this.panningManager=this.createPanningManager()),this.panningManager.panTo(a+this.panDx,b+this.panDy));else{var e=this.container;d=null!=d?d:20;if(a>=e.scrollLeft&&b>=e.scrollTop&&a<=e.scrollLeft+e.clientWidth&&b<=e.scrollTop+e.clientHeight){var f=e.scrollLeft+e [...]
+a;if(f<d){if(a=e.scrollLeft,e.scrollLeft+=d-f,c&&a==e.scrollLeft){if(this.dialect==mxConstants.DIALECT_SVG){a=this.view.getDrawPane().ownerSVGElement;var g=this.container.scrollWidth+d-f}else g=Math.max(e.clientWidth,e.scrollWidth)+d-f,a=this.view.getCanvas();a.style.width=g+"px";e.scrollLeft+=d-f}}else f=a-e.scrollLeft,f<d&&(e.scrollLeft-=d-f);f=e.scrollTop+e.clientHeight-b;f<d?(a=e.scrollTop,e.scrollTop+=d-f,a==e.scrollTop&&c&&(this.dialect==mxConstants.DIALECT_SVG?(a=this.view.getDraw [...]
+b=this.container.scrollHeight+d-f):(b=Math.max(e.clientHeight,e.scrollHeight)+d-f,a=this.view.getCanvas()),a.style.height=b+"px",e.scrollTop+=d-f)):(f=b-e.scrollTop,f<d&&(e.scrollTop-=d-f))}}};mxGraph.prototype.createPanningManager=function(){return new mxPanningManager(this)};
+mxGraph.prototype.getBorderSizes=function(){var a=mxUtils.getCurrentStyle(this.container);return new mxRectangle(mxUtils.parseCssNumber(a.paddingLeft)+("none"!=a.borderLeftStyle?mxUtils.parseCssNumber(a.borderLeftWidth):0),mxUtils.parseCssNumber(a.paddingTop)+("none"!=a.borderTopStyle?mxUtils.parseCssNumber(a.borderTopWidth):0),mxUtils.parseCssNumber(a.paddingRight)+("none"!=a.borderRightStyle?mxUtils.parseCssNumber(a.borderRightWidth):0),mxUtils.parseCssNumber(a.paddingBottom)+("none"!= [...]
+mxUtils.parseCssNumber(a.borderBottomWidth):0))};mxGraph.prototype.getPreferredPageSize=function(a,b,c){a=this.view.translate;var d=this.pageFormat,e=this.pageScale,d=new mxRectangle(0,0,Math.ceil(d.width*e),Math.ceil(d.height*e));return new mxRectangle(0,0,(this.pageBreaksVisible?Math.ceil(b/d.width):1)*d.width+2+a.x,(this.pageBreaksVisible?Math.ceil(c/d.height):1)*d.height+2+a.y)};
+mxGraph.prototype.fit=function(a,b,c,d,e,f){if(null!=this.container){a=null!=a?a:this.getBorder();b=null!=b?b:!1;c=null!=c?c:0;d=null!=d?d:!0;e=null!=e?e:!1;f=null!=f?f:!1;var g=this.getBorderSizes(),k=this.container.offsetWidth-g.x-g.width-1,l=this.container.offsetHeight-g.y-g.height-1,g=this.view.getGraphBounds();if(0<g.width&&0<g.height){b&&null!=g.x&&null!=g.y&&(g=g.clone(),g.width+=g.x,g.height+=g.y,g.x=0,g.y=0);var m=this.view.scale,n=g.width/m,p=g.height/m;null!=this.backgroundIma [...]
+this.backgroundImage.width-g.x/m),p=Math.max(p,this.backgroundImage.height-g.y/m));var q=(b?a:2*a)+c,k=k-q,l=l-q;e=e?l/p:f?k/n:Math.min(k/n,l/p);null!=this.minFitScale&&(e=Math.max(e,this.minFitScale));null!=this.maxFitScale&&(e=Math.min(e,this.maxFitScale));if(d)b?this.view.scale!=e&&this.view.setScale(e):mxUtils.hasScrollbars(this.container)?(this.view.setScale(e),a=this.getGraphBounds(),null!=a.x&&(this.container.scrollLeft=a.x),null!=a.y&&(this.container.scrollTop=a.y)):this.view.sca [...]
+null!=g.x?Math.floor(this.view.translate.x-g.x/m+a/e+c/2):a,null!=g.y?Math.floor(this.view.translate.y-g.y/m+a/e+c/2):a);else return e}}return this.view.scale};
+mxGraph.prototype.sizeDidChange=function(){var a=this.getGraphBounds();if(null!=this.container){var b=this.getBorder(),c=Math.max(0,a.x+a.width+b),b=Math.max(0,a.y+a.height+b);null!=this.minimumContainerSize&&(c=Math.max(c,this.minimumContainerSize.width),b=Math.max(b,this.minimumContainerSize.height));this.resizeContainer&&this.doResizeContainer(c,b);if(this.preferPageSize||!mxClient.IS_IE&&this.pageVisible){var d=this.getPreferredPageSize(a,Math.max(1,c),Math.max(1,b));null!=d&&(c=d.wi [...]
+b=d.height*this.view.scale)}null!=this.minimumGraphSize&&(c=Math.max(c,this.minimumGraphSize.width*this.view.scale),b=Math.max(b,this.minimumGraphSize.height*this.view.scale));c=Math.ceil(c);b=Math.ceil(b);this.dialect==mxConstants.DIALECT_SVG?(d=this.view.getDrawPane().ownerSVGElement,d.style.minWidth=Math.max(1,c)+"px",d.style.minHeight=Math.max(1,b)+"px",d.style.width="100%",d.style.height="100%"):mxClient.IS_QUIRKS?this.view.updateHtmlCanvasSize(Math.max(1,c),Math.max(1,b)):(this.vie [...]
+Math.max(1,c)+"px",this.view.canvas.style.minHeight=Math.max(1,b)+"px");this.updatePageBreaks(this.pageBreaksVisible,c,b)}this.fireEvent(new mxEventObject(mxEvent.SIZE,"bounds",a))};mxGraph.prototype.doResizeContainer=function(a,b){null!=this.maximumContainerSize&&(a=Math.min(this.maximumContainerSize.width,a),b=Math.min(this.maximumContainerSize.height,b));this.container.style.width=Math.ceil(a)+"px";this.container.style.height=Math.ceil(b)+"px"};
+mxGraph.prototype.updatePageBreaks=function(a,b,c){b=this.view.scale;c=this.view.translate;var d=this.pageFormat,e=b*this.pageScale,f=new mxRectangle(0,0,d.width*e,d.height*e),d=mxRectangle.fromRectangle(this.getGraphBounds());d.width=Math.max(1,d.width);d.height=Math.max(1,d.height);f.x=Math.floor((d.x-c.x*b)/f.width)*f.width+c.x*b;f.y=Math.floor((d.y-c.y*b)/f.height)*f.height+c.y*b;d.width=Math.ceil((d.width+(d.x-f.x))/f.width)*f.width;d.height=Math.ceil((d.height+(d.y-f.y))/f.height)* [...]
+var g=(a=a&&Math.min(f.width,f.height)>this.minPageBreakDist)?Math.ceil(d.height/f.height)+1:0,k=a?Math.ceil(d.width/f.width)+1:0,l=(k-1)*f.width,m=(g-1)*f.height;null==this.horizontalPageBreaks&&0<g&&(this.horizontalPageBreaks=[]);null==this.verticalPageBreaks&&0<k&&(this.verticalPageBreaks=[]);a=mxUtils.bind(this,function(a){if(null!=a){for(var b=a==this.horizontalPageBreaks?g:k,c=0;c<=b;c++){var d=a==this.horizontalPageBreaks?[new mxPoint(Math.round(f.x),Math.round(f.y+c*f.height)),ne [...]
+l),Math.round(f.y+c*f.height))]:[new mxPoint(Math.round(f.x+c*f.width),Math.round(f.y)),new mxPoint(Math.round(f.x+c*f.width),Math.round(f.y+m))];null!=a[c]?(a[c].points=d,a[c].redraw()):(d=new mxPolyline(d,this.pageBreakColor),d.dialect=this.dialect,d.pointerEvents=!1,d.isDashed=this.pageBreakDashed,d.init(this.view.backgroundPane),d.redraw(),a[c]=d)}for(c=b;c<a.length;c++)a[c].destroy();a.splice(b,a.length-b)}});a(this.horizontalPageBreaks);a(this.verticalPageBreaks)};
+mxGraph.prototype.getCellStyle=function(a){var b=this.model.getStyle(a);a=this.model.isEdge(a)?this.stylesheet.getDefaultEdgeStyle():this.stylesheet.getDefaultVertexStyle();null!=b&&(a=this.postProcessCellStyle(this.stylesheet.getCellStyle(b,a)));null==a&&(a=mxGraph.prototype.EMPTY_ARRAY);return a};
+mxGraph.prototype.postProcessCellStyle=function(a){if(null!=a){var b=a[mxConstants.STYLE_IMAGE],c=this.getImageFromBundles(b);null!=c?a[mxConstants.STYLE_IMAGE]=c:c=b;null!=c&&"data:image/"==c.substring(0,11)&&("data:image/svg+xml,<"==c.substring(0,20)?c=c.substring(0,19)+encodeURIComponent(c.substring(19)):"data:image/svg+xml,%3C"!=c.substring(0,22)&&(b=c.indexOf(","),0<b&&";base64,"!=c.substring(b-7,b+1)&&(c=c.substring(0,b)+";base64,"+c.substring(b+1))),a[mxConstants.STYLE_IMAGE]=c)}r [...]
+mxGraph.prototype.setCellStyle=function(a,b){b=b||this.getSelectionCells();if(null!=b){this.model.beginUpdate();try{for(var c=0;c<b.length;c++)this.model.setStyle(b[c],a)}finally{this.model.endUpdate()}}};mxGraph.prototype.toggleCellStyle=function(a,b,c){c=c||this.getSelectionCell();return this.toggleCellStyles(a,b,[c])};
+mxGraph.prototype.toggleCellStyles=function(a,b,c){b=null!=b?b:!1;c=c||this.getSelectionCells();var d=null;if(null!=c&&0<c.length){var e=this.view.getState(c[0]),e=null!=e?e.style:this.getCellStyle(c[0]);null!=e&&(d=mxUtils.getValue(e,a,b)?0:1,this.setCellStyles(a,d,c))}return d};mxGraph.prototype.setCellStyles=function(a,b,c){c=c||this.getSelectionCells();mxUtils.setCellStyles(this.model,c,a,b)};mxGraph.prototype.toggleCellStyleFlags=function(a,b,c){this.setCellStyleFlags(a,b,null,c)};
+mxGraph.prototype.setCellStyleFlags=function(a,b,c,d){d=d||this.getSelectionCells();if(null!=d&&0<d.length){if(null==c){var e=this.view.getState(d[0]),e=null!=e?e.style:this.getCellStyle(d[0]);null!=e&&(c=(parseInt(e[a]||0)&b)!=b)}mxUtils.setCellStyleFlags(this.model,d,a,b,c)}};
+mxGraph.prototype.alignCells=function(a,b,c){null==b&&(b=this.getSelectionCells());if(null!=b&&1<b.length){if(null==c)for(var d=0;d<b.length;d++){var e=this.view.getState(b[d]);if(null!=e&&!this.model.isEdge(b[d]))if(null==c)if(a==mxConstants.ALIGN_CENTER){c=e.x+e.width/2;break}else if(a==mxConstants.ALIGN_RIGHT)c=e.x+e.width;else if(a==mxConstants.ALIGN_TOP)c=e.y;else if(a==mxConstants.ALIGN_MIDDLE){c=e.y+e.height/2;break}else c=a==mxConstants.ALIGN_BOTTOM?e.y+e.height:e.x;else c=a==mxC [...]
+Math.max(c,e.x+e.width):a==mxConstants.ALIGN_TOP?Math.min(c,e.y):a==mxConstants.ALIGN_BOTTOM?Math.max(c,e.y+e.height):Math.min(c,e.x)}if(null!=c){var f=this.view.scale;this.model.beginUpdate();try{for(d=0;d<b.length;d++)if(e=this.view.getState(b[d]),null!=e){var g=this.getCellGeometry(b[d]);null==g||this.model.isEdge(b[d])||(g=g.clone(),a==mxConstants.ALIGN_CENTER?g.x+=(c-e.x-e.width/2)/f:a==mxConstants.ALIGN_RIGHT?g.x+=(c-e.x-e.width)/f:a==mxConstants.ALIGN_TOP?g.y+=(c-e.y)/f:a==mxConst [...]
+g.y+=(c-e.y-e.height/2)/f:a==mxConstants.ALIGN_BOTTOM?g.y+=(c-e.y-e.height)/f:g.x+=(c-e.x)/f,this.resizeCell(b[d],g))}this.fireEvent(new mxEventObject(mxEvent.ALIGN_CELLS,"align",a,"cells",b))}finally{this.model.endUpdate()}}}return b};
+mxGraph.prototype.flipEdge=function(a){if(null!=a&&null!=this.alternateEdgeStyle){this.model.beginUpdate();try{var b=this.model.getStyle(a);null==b||0==b.length?this.model.setStyle(a,this.alternateEdgeStyle):this.model.setStyle(a,null);this.resetEdge(a);this.fireEvent(new mxEventObject(mxEvent.FLIP_EDGE,"edge",a))}finally{this.model.endUpdate()}}return a};mxGraph.prototype.addImageBundle=function(a){this.imageBundles.push(a)};
+mxGraph.prototype.removeImageBundle=function(a){for(var b=[],c=0;c<this.imageBundles.length;c++)this.imageBundles[c]!=a&&b.push(this.imageBundles[c]);this.imageBundles=b};mxGraph.prototype.getImageFromBundles=function(a){if(null!=a)for(var b=0;b<this.imageBundles.length;b++){var c=this.imageBundles[b].getImage(a);if(null!=c)return c}return null};
+mxGraph.prototype.orderCells=function(a,b){null==b&&(b=mxUtils.sortCells(this.getSelectionCells(),!0));this.model.beginUpdate();try{this.cellsOrdered(b,a),this.fireEvent(new mxEventObject(mxEvent.ORDER_CELLS,"back",a,"cells",b))}finally{this.model.endUpdate()}return b};
+mxGraph.prototype.cellsOrdered=function(a,b){if(null!=a){this.model.beginUpdate();try{for(var c=0;c<a.length;c++){var d=this.model.getParent(a[c]);b?this.model.add(d,a[c],c):this.model.add(d,a[c],this.model.getChildCount(d)-1)}this.fireEvent(new mxEventObject(mxEvent.CELLS_ORDERED,"back",b,"cells",a))}finally{this.model.endUpdate()}}};
+mxGraph.prototype.groupCells=function(a,b,c){null==c&&(c=mxUtils.sortCells(this.getSelectionCells(),!0));c=this.getCellsForGroup(c);null==a&&(a=this.createGroupCell(c));var d=this.getBoundsForGroup(a,c,b);if(0<c.length&&null!=d){var e=this.model.getParent(a);null==e&&(e=this.model.getParent(c[0]));this.model.beginUpdate();try{null==this.getCellGeometry(a)&&this.model.setGeometry(a,new mxGeometry);var f=this.model.getChildCount(e);this.cellsAdded([a],e,f,null,null,!1,!1,!1);f=this.model.g [...]
+this.cellsAdded(c,a,f,null,null,!1,!1,!1);this.cellsMoved(c,-d.x,-d.y,!1,!1,!1);this.cellsResized([a],[d],!1);this.fireEvent(new mxEventObject(mxEvent.GROUP_CELLS,"group",a,"border",b,"cells",c))}finally{this.model.endUpdate()}}return a};mxGraph.prototype.getCellsForGroup=function(a){var b=[];if(null!=a&&0<a.length){var c=this.model.getParent(a[0]);b.push(a[0]);for(var d=1;d<a.length;d++)this.model.getParent(a[d])==c&&b.push(a[d])}return b};
+mxGraph.prototype.getBoundsForGroup=function(a,b,c){b=this.getBoundingBoxFromGeometry(b,!0);null!=b&&(this.isSwimlane(a)&&(a=this.getStartSize(a),b.x-=a.width,b.y-=a.height,b.width+=a.width,b.height+=a.height),null!=c&&(b.x-=c,b.y-=c,b.width+=2*c,b.height+=2*c));return b};mxGraph.prototype.createGroupCell=function(a){a=new mxCell("");a.setVertex(!0);a.setConnectable(!1);return a};
+mxGraph.prototype.ungroupCells=function(a){var b=[];if(null==a){a=this.getSelectionCells();for(var c=[],d=0;d<a.length;d++)0<this.model.getChildCount(a[d])&&c.push(a[d]);a=c}if(null!=a&&0<a.length){this.model.beginUpdate();try{for(d=0;d<a.length;d++){var e=this.model.getChildren(a[d]);if(null!=e&&0<e.length){var e=e.slice(),f=this.model.getParent(a[d]),g=this.model.getChildCount(f);this.cellsAdded(e,f,g,null,null,!0);b=b.concat(e)}}this.removeCellsAfterUngroup(a);this.fireEvent(new mxEve [...]
+"cells",a))}finally{this.model.endUpdate()}}return b};mxGraph.prototype.removeCellsAfterUngroup=function(a){this.cellsRemoved(this.addAllEdges(a))};mxGraph.prototype.removeCellsFromParent=function(a){null==a&&(a=this.getSelectionCells());this.model.beginUpdate();try{var b=this.getDefaultParent(),c=this.model.getChildCount(b);this.cellsAdded(a,b,c,null,null,!0);this.fireEvent(new mxEventObject(mxEvent.REMOVE_CELLS_FROM_PARENT,"cells",a))}finally{this.model.endUpdate()}return a};
+mxGraph.prototype.updateGroupBounds=function(a,b,c,d,e,f,g){null==a&&(a=this.getSelectionCells());b=null!=b?b:0;c=null!=c?c:!1;d=null!=d?d:0;e=null!=e?e:0;f=null!=f?f:0;g=null!=g?g:0;this.model.beginUpdate();try{for(var k=a.length-1;0<=k;k--){var l=this.getCellGeometry(a[k]);if(null!=l){var m=this.getChildCells(a[k]);if(null!=m&&0<m.length){var n=this.getBoundingBoxFromGeometry(m,!0);if(null!=n&&0<n.width&&0<n.height){var p=0,q=0;if(this.isSwimlane(a[k]))var r=this.getStartSize(a[k]),p=r [...]
+r.height;l=l.clone();c&&(l.x=Math.round(l.x+n.x-b-p-g),l.y=Math.round(l.y+n.y-b-q-d));l.width=Math.round(n.width+2*b+p+g+e);l.height=Math.round(n.height+2*b+q+d+f);this.model.setGeometry(a[k],l);this.moveCells(m,b+p-n.x+g,b+q-n.y+d)}}}}}finally{this.model.endUpdate()}return a};
+mxGraph.prototype.getBoundingBox=function(a){var b=null;if(null!=a&&0<a.length)for(var c=0;c<a.length;c++)if(this.model.isVertex(a[c])||this.model.isEdge(a[c])){var d=this.view.getBoundingBox(this.view.getState(a[c]),!0);null!=d&&(null==b?b=mxRectangle.fromRectangle(d):b.add(d))}return b};
+mxGraph.prototype.cloneCells=function(a,b,c){b=null!=b?b:!0;var d=null;if(null!=a){for(var e=new mxDictionary,d=[],f=0;f<a.length;f++)e.put(a[f],!0),d.push(a[f]);if(0<d.length)for(var g=this.view.scale,k=this.view.translate,d=this.model.cloneCells(a,!0,c),f=0;f<a.length;f++)if(!b&&this.model.isEdge(d[f])&&null!=this.getEdgeValidationError(d[f],this.model.getTerminal(d[f],!0),this.model.getTerminal(d[f],!1)))d[f]=null;else{var l=this.model.getGeometry(d[f]);if(null!=l){var m=this.view.get [...]
+n=this.view.getState(this.model.getParent(a[f]));if(null!=m&&null!=n)if(c=n.origin.x,n=n.origin.y,this.model.isEdge(d[f])){for(var m=m.absolutePoints,p=this.model.getTerminal(a[f],!0);null!=p&&!e.get(p);)p=this.model.getParent(p);null==p&&l.setTerminalPoint(new mxPoint(m[0].x/g-k.x,m[0].y/g-k.y),!0);for(p=this.model.getTerminal(a[f],!1);null!=p&&!e.get(p);)p=this.model.getParent(p);null==p&&(p=m.length-1,l.setTerminalPoint(new mxPoint(m[p].x/g-k.x,m[p].y/g-k.y),!1));l=l.points;if(null!=l [...]
+l.length;m++)l[m].x+=c,l[m].y+=n}else l.translate(c,n)}}else d=[]}return d};mxGraph.prototype.insertVertex=function(a,b,c,d,e,f,g,k,l){b=this.createVertex(a,b,c,d,e,f,g,k,l);return this.addCell(b,a)};mxGraph.prototype.createVertex=function(a,b,c,d,e,f,g,k,l){a=new mxGeometry(d,e,f,g);a.relative=null!=l?l:!1;c=new mxCell(c,a,k);c.setId(b);c.setVertex(!0);c.setConnectable(!0);return c};mxGraph.prototype.insertEdge=function(a,b,c,d,e,f){b=this.createEdge(a,b,c,d,e,f);return this.addEdge(b,a,d,e)};
+mxGraph.prototype.createEdge=function(a,b,c,d,e,f){a=new mxCell(c,new mxGeometry,f);a.setId(b);a.setEdge(!0);a.geometry.relative=!0;return a};mxGraph.prototype.addEdge=function(a,b,c,d,e){return this.addCell(a,b,e,c,d)};mxGraph.prototype.addCell=function(a,b,c,d,e){return this.addCells([a],b,c,d,e)[0]};
+mxGraph.prototype.addCells=function(a,b,c,d,e){null==b&&(b=this.getDefaultParent());null==c&&(c=this.model.getChildCount(b));this.model.beginUpdate();try{this.cellsAdded(a,b,c,d,e,!1,!0),this.fireEvent(new mxEventObject(mxEvent.ADD_CELLS,"cells",a,"parent",b,"index",c,"source",d,"target",e))}finally{this.model.endUpdate()}return a};
+mxGraph.prototype.cellsAdded=function(a,b,c,d,e,f,g,k){if(null!=a&&null!=b&&null!=c){this.model.beginUpdate();try{for(var l=f?this.view.getState(b):null,m=null!=l?l.origin:null,n=new mxPoint(0,0),l=0;l<a.length;l++)if(null==a[l])c--;else{var p=this.model.getParent(a[l]);if(null!=m&&a[l]!=b&&b!=p){var q=this.view.getState(p),r=null!=q?q.origin:n,t=this.model.getGeometry(a[l]);if(null!=t){var u=r.x-m.x,x=r.y-m.y,t=t.clone();t.translate(u,x);t.relative||!this.model.isVertex(a[l])||this.isAl [...]
+(t.x=Math.max(0,t.x),t.y=Math.max(0,t.y));this.model.setGeometry(a[l],t)}}b==p&&c+l>this.model.getChildCount(b)&&c--;this.model.add(b,a[l],c+l);this.autoSizeCellsOnAdd&&this.autoSizeCell(a[l],!0);(null==k||k)&&this.isExtendParentsOnAdd(a[l])&&this.isExtendParent(a[l])&&this.extendParent(a[l]);(null==g||g)&&this.constrainChild(a[l]);null!=d&&this.cellConnected(a[l],d,!0);null!=e&&this.cellConnected(a[l],e,!1)}this.fireEvent(new mxEventObject(mxEvent.CELLS_ADDED,"cells",a,"parent",b,"index [...]
+d,"target",e,"absolute",f))}finally{this.model.endUpdate()}}};mxGraph.prototype.autoSizeCell=function(a,b){if(null!=b?b:1)for(var c=this.model.getChildCount(a),d=0;d<c;d++)this.autoSizeCell(this.model.getChildAt(a,d));this.getModel().isVertex(a)&&this.isAutoSizeCell(a)&&this.updateCellSize(a)};
+mxGraph.prototype.removeCells=function(a,b){b=null!=b?b:!0;null==a&&(a=this.getDeletableCells(this.getSelectionCells()));b&&(a=this.getDeletableCells(this.addAllEdges(a)));this.model.beginUpdate();try{this.cellsRemoved(a),this.fireEvent(new mxEventObject(mxEvent.REMOVE_CELLS,"cells",a,"includeEdges",b))}finally{this.model.endUpdate()}return a};
+mxGraph.prototype.cellsRemoved=function(a){if(null!=a&&0<a.length){var b=this.view.scale,c=this.view.translate;this.model.beginUpdate();try{for(var d=new mxDictionary,e=0;e<a.length;e++)d.put(a[e],!0);for(e=0;e<a.length;e++){for(var f=this.getAllEdges([a[e]]),g=mxUtils.bind(this,function(d,g){var l=this.model.getGeometry(d);if(null!=l){var m=this.view.getState(d);if(null!=m){for(var q=m.getVisibleTerminal(g),r=!1;null!=q;){if(a[e]==q){r=!0;break}q=this.model.getParent(q)}if(r){var q=c.x, [...]
+null!=t&&this.model.isVertex(t.cell)&&(q=t.x/b,r=t.y/b);l=l.clone();m=m.absolutePoints;t=g?0:m.length-1;l.setTerminalPoint(new mxPoint(m[t].x/b-q,m[t].y/b-r),g);this.model.setTerminal(f[k],null,g);this.model.setGeometry(f[k],l)}}}}),k=0;k<f.length;k++)d.get(f[k])||(g(f[k],!0),g(f[k],!1));this.model.remove(a[e])}this.fireEvent(new mxEventObject(mxEvent.CELLS_REMOVED,"cells",a))}finally{this.model.endUpdate()}}};
+mxGraph.prototype.splitEdge=function(a,b,c,d,e){d=d||0;e=e||0;var f=this.model.getParent(a),g=this.model.getTerminal(a,!0);this.model.beginUpdate();try{if(null==c){c=this.cloneCells([a])[0];var k=this.view.getState(a),l=this.getCellGeometry(c);if(null!=l&&null!=l.points&&null!=k){var m=this.view.translate,n=this.view.scale,p=mxUtils.findNearestSegment(k,(d+m.x)*n,(e+m.y)*n);l.points=l.points.slice(0,p);l=this.getCellGeometry(a);null!=l&&null!=l.points&&(l=l.clone(),l.points=l.points.slic [...]
+l))}}this.cellsMoved(b,d,e,!1,!1);this.cellsAdded(b,f,this.model.getChildCount(f),null,null,!0);this.cellsAdded([c],f,this.model.getChildCount(f),g,b[0],!1);this.cellConnected(a,b[0],!0);this.fireEvent(new mxEventObject(mxEvent.SPLIT_EDGE,"edge",a,"cells",b,"newEdge",c,"dx",d,"dy",e))}finally{this.model.endUpdate()}return c};
+mxGraph.prototype.toggleCells=function(a,b,c){null==b&&(b=this.getSelectionCells());c&&(b=this.addAllEdges(b));this.model.beginUpdate();try{this.cellsToggled(b,a),this.fireEvent(new mxEventObject(mxEvent.TOGGLE_CELLS,"show",a,"cells",b,"includeEdges",c))}finally{this.model.endUpdate()}return b};mxGraph.prototype.cellsToggled=function(a,b){if(null!=a&&0<a.length){this.model.beginUpdate();try{for(var c=0;c<a.length;c++)this.model.setVisible(a[c],b)}finally{this.model.endUpdate()}}};
+mxGraph.prototype.foldCells=function(a,b,c,d,e){b=null!=b?b:!1;null==c&&(c=this.getFoldableCells(this.getSelectionCells(),a));this.stopEditing(!1);this.model.beginUpdate();try{this.cellsFolded(c,a,b,d),this.fireEvent(new mxEventObject(mxEvent.FOLD_CELLS,"collapse",a,"recurse",b,"cells",c))}finally{this.model.endUpdate()}return c};
+mxGraph.prototype.cellsFolded=function(a,b,c,d){if(null!=a&&0<a.length){this.model.beginUpdate();try{for(var e=0;e<a.length;e++)if((!d||this.isCellFoldable(a[e],b))&&b!=this.isCellCollapsed(a[e])){this.model.setCollapsed(a[e],b);this.swapBounds(a[e],b);this.isExtendParent(a[e])&&this.extendParent(a[e]);if(c){var f=this.model.getChildren(a[e]);this.foldCells(f,b,c)}this.constrainChild(a[e])}this.fireEvent(new mxEventObject(mxEvent.CELLS_FOLDED,"cells",a,"collapse",b,"recurse",c))}finally{ [...]
+mxGraph.prototype.swapBounds=function(a,b){if(null!=a){var c=this.model.getGeometry(a);null!=c&&(c=c.clone(),this.updateAlternateBounds(a,c,b),c.swap(),this.model.setGeometry(a,c))}};
+mxGraph.prototype.updateAlternateBounds=function(a,b,c){if(null!=a&&null!=b){c=this.view.getState(a);c=null!=c?c.style:this.getCellStyle(a);if(null==b.alternateBounds){var d=b;this.collapseToPreferredSize&&(a=this.getPreferredSizeForCell(a),null!=a&&(d=a,a=mxUtils.getValue(c,mxConstants.STYLE_STARTSIZE),0<a&&(d.height=Math.max(d.height,a))));b.alternateBounds=new mxRectangle(0,0,d.width,d.height)}if(null!=b.alternateBounds){b.alternateBounds.x=b.x;b.alternateBounds.y=b.y;var e=mxUtils.to [...]
+0);0!=e&&(a=b.alternateBounds.getCenterX()-b.getCenterX(),c=b.alternateBounds.getCenterY()-b.getCenterY(),d=Math.cos(e),e=Math.sin(e),b.alternateBounds.x+=d*a-e*c-a,b.alternateBounds.y+=e*a+d*c-c)}}};mxGraph.prototype.addAllEdges=function(a){var b=a.slice();return mxUtils.removeDuplicates(b.concat(this.getAllEdges(a)))};
+mxGraph.prototype.getAllEdges=function(a){var b=[];if(null!=a)for(var c=0;c<a.length;c++){for(var d=this.model.getEdgeCount(a[c]),e=0;e<d;e++)b.push(this.model.getEdgeAt(a[c],e));d=this.model.getChildren(a[c]);b=b.concat(this.getAllEdges(d))}return b};mxGraph.prototype.updateCellSize=function(a,b){b=null!=b?b:!1;this.model.beginUpdate();try{this.cellSizeUpdated(a,b),this.fireEvent(new mxEventObject(mxEvent.UPDATE_CELL_SIZE,"cell",a,"ignoreChildren",b))}finally{this.model.endUpdate()}return a};
+mxGraph.prototype.cellSizeUpdated=function(a,b){if(null!=a){this.model.beginUpdate();try{var c=this.getPreferredSizeForCell(a),d=this.model.getGeometry(a);if(null!=c&&null!=d){var e=this.isCellCollapsed(a),d=d.clone();if(this.isSwimlane(a)){var f=this.view.getState(a),g=null!=f?f.style:this.getCellStyle(a),k=this.model.getStyle(a);null==k&&(k="");mxUtils.getValue(g,mxConstants.STYLE_HORIZONTAL,!0)?(k=mxUtils.setStyle(k,mxConstants.STYLE_STARTSIZE,c.height+8),e&&(d.height=c.height+8),d.wi [...]
+(k=mxUtils.setStyle(k,mxConstants.STYLE_STARTSIZE,c.width+8),e&&(d.width=c.width+8),d.height=c.height);this.model.setStyle(a,k)}else d.width=c.width,d.height=c.height;if(!b&&!e){var l=this.view.getBounds(this.model.getChildren(a));if(null!=l){var m=this.view.translate,n=this.view.scale,p=(l.y+l.height)/n-d.y-m.y;d.width=Math.max(d.width,(l.x+l.width)/n-d.x-m.x);d.height=Math.max(d.height,p)}}this.cellsResized([a],[d],!1)}}finally{this.model.endUpdate()}}};
+mxGraph.prototype.getPreferredSizeForCell=function(a){var b=null;if(null!=a){var c=this.view.getState(a)||this.view.createState(a),d=c.style;if(!this.model.isEdge(a)){var e=d[mxConstants.STYLE_FONTSIZE]||mxConstants.DEFAULT_FONTSIZE;a=b=0;null==this.getImage(c)&&null==d[mxConstants.STYLE_IMAGE]||d[mxConstants.STYLE_SHAPE]!=mxConstants.SHAPE_LABEL||(d[mxConstants.STYLE_VERTICAL_ALIGN]==mxConstants.ALIGN_MIDDLE&&(b+=parseFloat(d[mxConstants.STYLE_IMAGE_WIDTH])||mxLabel.prototype.imageSize) [...]
+mxConstants.ALIGN_CENTER&&(a+=parseFloat(d[mxConstants.STYLE_IMAGE_HEIGHT])||mxLabel.prototype.imageSize));b+=2*(d[mxConstants.STYLE_SPACING]||0);b+=d[mxConstants.STYLE_SPACING_LEFT]||0;b+=d[mxConstants.STYLE_SPACING_RIGHT]||0;a+=2*(d[mxConstants.STYLE_SPACING]||0);a+=d[mxConstants.STYLE_SPACING_TOP]||0;a+=d[mxConstants.STYLE_SPACING_BOTTOM]||0;var f=this.getFoldingImage(c);null!=f&&(b+=f.width+8);f=this.cellRenderer.getLabelValue(c);null!=f&&0<f.length?(this.isHtmlLabel(c.cell)||(f=mxUt [...]
+f=f.replace(/\n/g,"<br>"),e=mxUtils.getSizeForString(f,e,d[mxConstants.STYLE_FONTFAMILY]),c=e.width+b,a=e.height+a,mxUtils.getValue(d,mxConstants.STYLE_HORIZONTAL,!0)||(d=a,a=c,c=d),this.gridEnabled&&(c=this.snap(c+this.gridSize/2),a=this.snap(a+this.gridSize/2)),b=new mxRectangle(0,0,c,a)):(d=4*this.gridSize,b=new mxRectangle(0,0,d,d))}}return b};mxGraph.prototype.resizeCell=function(a,b,c){return this.resizeCells([a],[b],c)[0]};
+mxGraph.prototype.resizeCells=function(a,b,c){c=null!=c?c:this.isRecursiveResize();this.model.beginUpdate();try{this.cellsResized(a,b,c),this.fireEvent(new mxEventObject(mxEvent.RESIZE_CELLS,"cells",a,"bounds",b))}finally{this.model.endUpdate()}return a};
+mxGraph.prototype.cellsResized=function(a,b,c){c=null!=c?c:!1;if(null!=a&&null!=b&&a.length==b.length){this.model.beginUpdate();try{for(var d=0;d<a.length;d++)this.cellResized(a[d],b[d],!1,c),this.isExtendParent(a[d])&&this.extendParent(a[d]),this.constrainChild(a[d]);this.resetEdgesOnResize&&this.resetEdges(a);this.fireEvent(new mxEventObject(mxEvent.CELLS_RESIZED,"cells",a,"bounds",b))}finally{this.model.endUpdate()}}};
+mxGraph.prototype.cellResized=function(a,b,c,d){var e=this.model.getGeometry(a);if(null!=e&&(e.x!=b.x||e.y!=b.y||e.width!=b.width||e.height!=b.height)){e=e.clone();!c&&e.relative?(c=e.offset,null!=c&&(c.x+=b.x-e.x,c.y+=b.y-e.y)):(e.x=b.x,e.y=b.y);e.width=b.width;e.height=b.height;e.relative||!this.model.isVertex(a)||this.isAllowNegativeCoordinates()||(e.x=Math.max(0,e.x),e.y=Math.max(0,e.y));this.model.beginUpdate();try{d&&this.resizeChildCells(a,e),this.model.setGeometry(a,e),this.const [...]
+mxGraph.prototype.resizeChildCells=function(a,b){for(var c=this.model.getGeometry(a),d=b.width/c.width,c=b.height/c.height,e=this.model.getChildCount(a),f=0;f<e;f++)this.scaleCell(this.model.getChildAt(a,f),d,c,!0)};mxGraph.prototype.constrainChildCells=function(a){for(var b=this.model.getChildCount(a),c=0;c<b;c++)this.constrainChild(this.model.getChildAt(a,c))};
+mxGraph.prototype.scaleCell=function(a,b,c,d){var e=this.model.getGeometry(a);if(null!=e){var f=this.view.getState(a),f=null!=f?f.style:this.getCellStyle(a),e=e.clone(),g=e.x,k=e.y,l=e.width,m=e.height;e.scale(b,c,"fixed"==f[mxConstants.STYLE_ASPECT]);"1"==f[mxConstants.STYLE_RESIZE_WIDTH]?e.width=l*b:"0"==f[mxConstants.STYLE_RESIZE_WIDTH]&&(e.width=l);"1"==f[mxConstants.STYLE_RESIZE_HEIGHT]?e.height=m*c:"0"==f[mxConstants.STYLE_RESIZE_HEIGHT]&&(e.height=m);this.isCellMovable(a)||(e.x=g, [...]
+(e.width=l,e.height=m);this.model.isVertex(a)?this.cellResized(a,e,!0,d):this.model.setGeometry(a,e)}};mxGraph.prototype.extendParent=function(a){if(null!=a){var b=this.model.getParent(a),c=this.getCellGeometry(b);null==b||null==c||this.isCellCollapsed(b)||(a=this.getCellGeometry(a),null!=a&&!a.relative&&(c.width<a.x+a.width||c.height<a.y+a.height)&&(c=c.clone(),c.width=Math.max(c.width,a.x+a.width),c.height=Math.max(c.height,a.y+a.height),this.cellsResized([b],[c],!1)))}};
+mxGraph.prototype.importCells=function(a,b,c,d,e,f){return this.moveCells(a,b,c,!0,d,e,f)};
+mxGraph.prototype.moveCells=function(a,b,c,d,e,f,g){b=null!=b?b:0;c=null!=c?c:0;d=null!=d?d:!1;if(null!=a&&(0!=b||0!=c||d||null!=e)){a=this.model.getTopmostCells(a);this.model.beginUpdate();try{for(var k=new mxDictionary,l=0;l<a.length;l++)k.put(a[l],!0);for(var m=mxUtils.bind(this,function(a){for(;null!=a;){if(k.get(a))return!0;a=this.model.getParent(a)}return!1}),n=[],l=0;l<a.length;l++){var p=this.getCellGeometry(a[l]),q=this.model.getParent(a[l]);null!=p&&p.relative&&this.model.isEdg [...]
+!0))||m(this.model.getTerminal(q,!1)))||n.push(a[l])}a=n;d&&(a=this.cloneCells(a,this.isCloneInvalidEdges(),g),null==e&&(e=this.getDefaultParent()));var r=this.isAllowNegativeCoordinates();null!=e&&this.setAllowNegativeCoordinates(!0);this.cellsMoved(a,b,c,!d&&this.isDisconnectOnMove()&&this.isAllowDanglingEdges(),null==e,this.isExtendParentsOnMove()&&null==e);this.setAllowNegativeCoordinates(r);if(null!=e){var t=this.model.getChildCount(e);this.cellsAdded(a,e,t,null,null,!0)}this.fireEv [...]
+"cells",a,"dx",b,"dy",c,"clone",d,"target",e,"event",f))}finally{this.model.endUpdate()}}return a};
+mxGraph.prototype.cellsMoved=function(a,b,c,d,e,f){if(null!=a&&(0!=b||0!=c)){f=null!=f?f:!1;this.model.beginUpdate();try{d&&this.disconnectGraph(a);for(var g=0;g<a.length;g++)this.translateCell(a[g],b,c),f&&this.isExtendParent(a[g])?this.extendParent(a[g]):e&&this.constrainChild(a[g]);this.resetEdgesOnMove&&this.resetEdges(a);this.fireEvent(new mxEventObject(mxEvent.CELLS_MOVED,"cells",a,"dx",b,"dy",c,"disconnect",d))}finally{this.model.endUpdate()}}};
+mxGraph.prototype.translateCell=function(a,b,c){var d=this.model.getGeometry(a);if(null!=d){b=parseFloat(b);c=parseFloat(c);d=d.clone();d.translate(b,c);d.relative||!this.model.isVertex(a)||this.isAllowNegativeCoordinates()||(d.x=Math.max(0,parseFloat(d.x)),d.y=Math.max(0,parseFloat(d.y)));if(d.relative&&!this.model.isEdge(a)){var e=this.model.getParent(a),f=0;this.model.isVertex(e)&&(f=this.view.getState(e),e=null!=f?f.style:this.getCellStyle(e),f=mxUtils.getValue(e,mxConstants.STYLE_RO [...]
+0!=f&&(f=mxUtils.toRadians(-f),e=Math.cos(f),f=Math.sin(f),c=mxUtils.getRotatedPoint(new mxPoint(b,c),e,f,new mxPoint(0,0)),b=c.x,c=c.y);null==d.offset?d.offset=new mxPoint(b,c):(d.offset.x=parseFloat(d.offset.x)+b,d.offset.y=parseFloat(d.offset.y)+c)}this.model.setGeometry(a,d)}};
+mxGraph.prototype.getCellContainmentArea=function(a){if(null!=a&&!this.model.isEdge(a)){var b=this.model.getParent(a);if(null!=b&&b!=this.getDefaultParent()){var c=this.model.getGeometry(b);if(null!=c){var d=a=0,e=c.width,c=c.height;if(this.isSwimlane(b)){var f=this.getStartSize(b),g=this.view.getState(b),k=null!=g?g.style:this.getCellStyle(b),b=mxUtils.getValue(k,mxConstants.STYLE_DIRECTION,mxConstants.DIRECTION_EAST),g=1==mxUtils.getValue(k,mxConstants.STYLE_FLIPH,0),k=1==mxUtils.getVa [...]
+0);if(b==mxConstants.DIRECTION_SOUTH||b==mxConstants.DIRECTION_NORTH){var l=f.width;f.width=f.height;f.height=l}if(b==mxConstants.DIRECTION_EAST&&!k||b==mxConstants.DIRECTION_NORTH&&!g||b==mxConstants.DIRECTION_WEST&&k||b==mxConstants.DIRECTION_SOUTH&&g)a=f.width,d=f.height;e-=f.width;c-=f.height}return new mxRectangle(a,d,e,c)}}}return null};mxGraph.prototype.getMaximumGraphBounds=function(){return this.maximumGraphBounds};
+mxGraph.prototype.constrainChild=function(a,b){if(null!=a){var c=this.getCellGeometry(a);if(null!=c&&(this.isConstrainRelativeChildren()||!c.relative)){var d=this.model.getParent(a);this.getCellGeometry(d);var e=this.getMaximumGraphBounds();null!=e&&(d=this.getBoundingBoxFromGeometry([d],!1),null!=d&&(e=mxRectangle.fromRectangle(e),e.x-=d.x,e.y-=d.y));if(this.isConstrainChild(a)&&(d=this.getCellContainmentArea(a),null!=d)){var f=this.getOverlap(a);0<f&&(d=mxRectangle.fromRectangle(d),d.x [...]
+f,d.y-=d.height*f,d.width+=2*d.width*f,d.height+=2*d.height*f);null==e?e=d:(e=mxRectangle.fromRectangle(e),e.intersect(d))}if(null!=e){d=[a];if(!this.isCellCollapsed(a))for(var f=this.model.getDescendants(a),g=0;g<f.length;g++)this.isCellVisible(f[g])&&d.push(f[g]);d=this.getBoundingBoxFromGeometry(d,!1);if(null!=d){c=c.clone();f=0;c.width>e.width&&(f=c.width-e.width,c.width-=f);d.x+d.width>e.x+e.width&&(f-=d.x+d.width-e.x-e.width-f);g=0;c.height>e.height&&(g=c.height-e.height,c.height-= [...]
+e.y+e.height&&(g-=d.y+d.height-e.y-e.height-g);d.x<e.x&&(f-=d.x-e.x);d.y<e.y&&(g-=d.y-e.y);if(0!=f||0!=g)c.relative?(null==c.offset&&(c.offset=new mxPoint),c.offset.x+=f,c.offset.y+=g):(c.x+=f,c.y+=g);this.model.setGeometry(a,c)}}}}};
+mxGraph.prototype.resetEdges=function(a){if(null!=a){for(var b=new mxDictionary,c=0;c<a.length;c++)b.put(a[c],!0);this.model.beginUpdate();try{for(c=0;c<a.length;c++){var d=this.model.getEdges(a[c]);if(null!=d)for(var e=0;e<d.length;e++){var f=this.view.getState(d[e]),g=null!=f?f.getVisibleTerminal(!0):this.view.getVisibleTerminal(d[e],!0),k=null!=f?f.getVisibleTerminal(!1):this.view.getVisibleTerminal(d[e],!1);b.get(g)&&b.get(k)||this.resetEdge(d[e])}this.resetEdges(this.model.getChildr [...]
+mxGraph.prototype.resetEdge=function(a){var b=this.model.getGeometry(a);null!=b&&null!=b.points&&0<b.points.length&&(b=b.clone(),b.points=[],this.model.setGeometry(a,b));return a};
+mxGraph.prototype.getOutlineConstraint=function(a,b,c){if(null!=b.shape){c=this.view.getPerimeterBounds(b);var d=b.style[mxConstants.STYLE_DIRECTION];if(d==mxConstants.DIRECTION_NORTH||d==mxConstants.DIRECTION_SOUTH){c.x+=c.width/2-c.height/2;c.y+=c.height/2-c.width/2;var e=c.width;c.width=c.height;c.height=e}var f=mxUtils.toRadians(b.shape.getShapeRotation());if(0!=f){var e=Math.cos(-f),f=Math.sin(-f),g=new mxPoint(c.getCenterX(),c.getCenterY());a=mxUtils.getRotatedPoint(a,e,f,g)}var g= [...]
+0;if(this.getModel().isVertex(b.cell)){var m=b.style[mxConstants.STYLE_FLIPH],n=b.style[mxConstants.STYLE_FLIPV];null!=b.shape&&null!=b.shape.stencil&&(m=1==mxUtils.getValue(b.style,"stencilFlipH",0)||m,n=1==mxUtils.getValue(b.style,"stencilFlipV",0)||n);if(d==mxConstants.DIRECTION_NORTH||d==mxConstants.DIRECTION_SOUTH)e=m,m=n,n=e;m&&(f=-1,k=-c.width);n&&(g=-1,l=-c.height)}a=new mxPoint((a.x-c.x)*f-k+c.x,(a.y-c.y)*g-l+c.y);return new mxConnectionConstraint(new mxPoint(Math.round(1E3*(a.x [...]
+1E3,Math.round(1E3*(a.y-c.y)/c.height)/1E3),!1)}return null};mxGraph.prototype.getAllConnectionConstraints=function(a,b){return null!=a&&null!=a.shape&&null!=a.shape.stencil?a.shape.stencil.constraints:null};
+mxGraph.prototype.getConnectionConstraint=function(a,b,c){b=null;var d=a.style[c?mxConstants.STYLE_EXIT_X:mxConstants.STYLE_ENTRY_X];if(null!=d){var e=a.style[c?mxConstants.STYLE_EXIT_Y:mxConstants.STYLE_ENTRY_Y];null!=e&&(b=new mxPoint(parseFloat(d),parseFloat(e)))}d=!1;null!=b&&(d=mxUtils.getValue(a.style,c?mxConstants.STYLE_EXIT_PERIMETER:mxConstants.STYLE_ENTRY_PERIMETER,!0));return new mxConnectionConstraint(b,d)};
+mxGraph.prototype.setConnectionConstraint=function(a,b,c,d){if(null!=d){this.model.beginUpdate();try{null==d||null==d.point?(this.setCellStyles(c?mxConstants.STYLE_EXIT_X:mxConstants.STYLE_ENTRY_X,null,[a]),this.setCellStyles(c?mxConstants.STYLE_EXIT_Y:mxConstants.STYLE_ENTRY_Y,null,[a]),this.setCellStyles(c?mxConstants.STYLE_EXIT_PERIMETER:mxConstants.STYLE_ENTRY_PERIMETER,null,[a])):null!=d.point&&(this.setCellStyles(c?mxConstants.STYLE_EXIT_X:mxConstants.STYLE_ENTRY_X,d.point.x,[a]),t [...]
+mxConstants.STYLE_EXIT_Y:mxConstants.STYLE_ENTRY_Y,d.point.y,[a]),d.perimeter?this.setCellStyles(c?mxConstants.STYLE_EXIT_PERIMETER:mxConstants.STYLE_ENTRY_PERIMETER,null,[a]):this.setCellStyles(c?mxConstants.STYLE_EXIT_PERIMETER:mxConstants.STYLE_ENTRY_PERIMETER,"0",[a]))}finally{this.model.endUpdate()}}};
+mxGraph.prototype.getConnectionPoint=function(a,b){var c=null;if(null!=a&&null!=b.point){var d=this.view.getPerimeterBounds(a),e=new mxPoint(d.getCenterX(),d.getCenterY()),c=a.style[mxConstants.STYLE_DIRECTION],f=0;null!=c&&(c==mxConstants.DIRECTION_NORTH?f+=270:c==mxConstants.DIRECTION_WEST?f+=180:c==mxConstants.DIRECTION_SOUTH&&(f+=90),c!=mxConstants.DIRECTION_NORTH&&c!=mxConstants.DIRECTION_SOUTH||d.rotate90());var c=new mxPoint(d.x+b.point.x*d.width,d.y+b.point.y*d.height),g=a.style[ [...]
+0;if(b.perimeter){if(0!=f){var k=d=0;90==f?k=1:180==f?d=-1:270==f&&(k=-1);c=mxUtils.getRotatedPoint(c,d,k,e)}c=this.view.getPerimeterPoint(a,c,!1)}else g+=f,this.getModel().isVertex(a.cell)&&(f=1==a.style[mxConstants.STYLE_FLIPH],k=1==a.style[mxConstants.STYLE_FLIPV],null!=a.shape&&null!=a.shape.stencil&&(f=1==mxUtils.getValue(a.style,"stencilFlipH",0)||f,k=1==mxUtils.getValue(a.style,"stencilFlipV",0)||k),f&&(c.x=2*d.getCenterX()-c.x),k&&(c.y=2*d.getCenterY()-c.y));0!=g&&null!=c&&(g=mxU [...]
+d=Math.cos(g),k=Math.sin(g),c=mxUtils.getRotatedPoint(c,d,k,e))}null!=c&&(c.x=Math.round(c.x),c.y=Math.round(c.y));return c};mxGraph.prototype.connectCell=function(a,b,c,d){this.model.beginUpdate();try{var e=this.model.getTerminal(a,c);this.cellConnected(a,b,c,d);this.fireEvent(new mxEventObject(mxEvent.CONNECT_CELL,"edge",a,"terminal",b,"source",c,"previous",e))}finally{this.model.endUpdate()}return a};
+mxGraph.prototype.cellConnected=function(a,b,c,d){if(null!=a){this.model.beginUpdate();try{var e=this.model.getTerminal(a,c);this.setConnectionConstraint(a,b,c,d);this.isPortsEnabled()&&(d=null,this.isPort(b)&&(d=b.getId(),b=this.getTerminalForPort(b,c)),this.setCellStyles(c?mxConstants.STYLE_SOURCE_PORT:mxConstants.STYLE_TARGET_PORT,d,[a]));this.model.setTerminal(a,b,c);this.resetEdgesOnConnect&&this.resetEdge(a);this.fireEvent(new mxEventObject(mxEvent.CELL_CONNECTED,"edge",a,"terminal [...]
+c,"previous",e))}finally{this.model.endUpdate()}}};
+mxGraph.prototype.disconnectGraph=function(a){if(null!=a){this.model.beginUpdate();try{for(var b=this.view.scale,c=this.view.translate,d=new mxDictionary,e=0;e<a.length;e++)d.put(a[e],!0);for(e=0;e<a.length;e++)if(this.model.isEdge(a[e])){var f=this.model.getGeometry(a[e]);if(null!=f){var g=this.view.getState(a[e]),k=this.view.getState(this.model.getParent(a[e]));if(null!=g&&null!=k){var f=f.clone(),l=-k.origin.x,m=-k.origin.y,n=g.absolutePoints,p=this.model.getTerminal(a[e],!0);if(null! [...]
+p,!0)){for(;null!=p&&!d.get(p);)p=this.model.getParent(p);null==p&&(f.setTerminalPoint(new mxPoint(n[0].x/b-c.x+l,n[0].y/b-c.y+m),!0),this.model.setTerminal(a[e],null,!0))}var q=this.model.getTerminal(a[e],!1);if(null!=q&&this.isCellDisconnectable(a[e],q,!1)){for(;null!=q&&!d.get(q);)q=this.model.getParent(q);if(null==q){var r=n.length-1;f.setTerminalPoint(new mxPoint(n[r].x/b-c.x+l,n[r].y/b-c.y+m),!1);this.model.setTerminal(a[e],null,!1)}}this.model.setGeometry(a[e],f)}}}}finally{this.m [...]
+mxGraph.prototype.getCurrentRoot=function(){return this.view.currentRoot};mxGraph.prototype.getTranslateForRoot=function(a){return null};mxGraph.prototype.isPort=function(a){return!1};mxGraph.prototype.getTerminalForPort=function(a,b){return this.model.getParent(a)};mxGraph.prototype.getChildOffsetForCell=function(a){return null};mxGraph.prototype.enterGroup=function(a){a=a||this.getSelectionCell();null!=a&&this.isValidRoot(a)&&(this.view.setCurrentRoot(a),this.clearSelection())};
+mxGraph.prototype.exitGroup=function(){var a=this.model.getRoot(),b=this.getCurrentRoot();if(null!=b){for(var c=this.model.getParent(b);c!=a&&!this.isValidRoot(c)&&this.model.getParent(c)!=a;)c=this.model.getParent(c);c==a||this.model.getParent(c)==a?this.view.setCurrentRoot(null):this.view.setCurrentRoot(c);null!=this.view.getState(b)&&this.setSelectionCell(b)}};mxGraph.prototype.home=function(){var a=this.getCurrentRoot();null!=a&&(this.view.setCurrentRoot(null),null!=this.view.getStat [...]
+mxGraph.prototype.isValidRoot=function(a){return null!=a};mxGraph.prototype.getGraphBounds=function(){return this.view.getGraphBounds()};mxGraph.prototype.getCellBounds=function(a,b,c){var d=[a];b&&(d=d.concat(this.model.getEdges(a)));d=this.view.getBounds(d);if(c){c=this.model.getChildCount(a);for(var e=0;e<c;e++){var f=this.getCellBounds(this.model.getChildAt(a,e),b,!0);null!=d?d.add(f):d=f}}return d};
+mxGraph.prototype.getBoundingBoxFromGeometry=function(a,b){b=null!=b?b:!1;var c=null;if(null!=a)for(var d=0;d<a.length;d++)if(b||this.model.isVertex(a[d])){var e=this.getCellGeometry(a[d]);if(null!=e){var f=null;if(this.model.isEdge(a[d])){f=function(a){null!=a&&(null==g?g=new mxRectangle(a.x,a.y,0,0):g.add(new mxRectangle(a.x,a.y,0,0)))};null==this.model.getTerminal(a[d],!0)&&f(e.getTerminalPoint(!0));null==this.model.getTerminal(a[d],!1)&&f(e.getTerminalPoint(!1));e=e.points;if(null!=e [...]
+new mxRectangle(e[0].x,e[0].y,0,0),k=1;k<e.length;k++)f(e[k]);f=g}else k=this.model.getParent(a[d]),e.relative?this.model.isVertex(k)&&k!=this.view.currentRoot&&(g=this.getBoundingBoxFromGeometry([k],!1),null!=g&&(f=new mxRectangle(e.x*g.width,e.y*g.height,e.width,e.height),0<=mxUtils.indexOf(a,k)&&(f.x+=g.x,f.y+=g.y))):(f=mxRectangle.fromRectangle(e),this.model.isVertex(k)&&0<=mxUtils.indexOf(a,k)&&(g=this.getBoundingBoxFromGeometry([k],!1),null!=g&&(f.x+=g.x,f.y+=g.y))),null!=f&&null!= [...]
+(f.x+=e.offset.x,f.y+=e.offset.y);null!=f&&(null==c?c=mxRectangle.fromRectangle(f):c.add(f))}}return c};mxGraph.prototype.refresh=function(a){this.view.clear(a,null==a);this.view.validate();this.sizeDidChange();this.fireEvent(new mxEventObject(mxEvent.REFRESH))};mxGraph.prototype.snap=function(a){this.gridEnabled&&(a=Math.round(a/this.gridSize)*this.gridSize);return a};
+mxGraph.prototype.panGraph=function(a,b){if(this.useScrollbarsForPanning&&mxUtils.hasScrollbars(this.container))this.container.scrollLeft=-a,this.container.scrollTop=-b;else{var c=this.view.getCanvas();if(this.dialect==mxConstants.DIALECT_SVG)if(0==a&&0==b){if(mxClient.IS_IE?c.setAttribute("transform","translate("+a+","+b+")"):c.removeAttribute("transform"),null!=this.shiftPreview1){for(var d=this.shiftPreview1.firstChild;null!=d;){var e=d.nextSibling;this.container.appendChild(d);d=e}nu [...]
+this.shiftPreview1.parentNode.removeChild(this.shiftPreview1);this.shiftPreview1=null;this.container.appendChild(c.parentNode);for(d=this.shiftPreview2.firstChild;null!=d;)e=d.nextSibling,this.container.appendChild(d),d=e;null!=this.shiftPreview2.parentNode&&this.shiftPreview2.parentNode.removeChild(this.shiftPreview2);this.shiftPreview2=null}}else{c.setAttribute("transform","translate("+a+","+b+")");if(null==this.shiftPreview1){this.shiftPreview1=document.createElement("div");this.shift [...]
+"absolute";this.shiftPreview1.style.overflow="visible";this.shiftPreview2=document.createElement("div");this.shiftPreview2.style.position="absolute";this.shiftPreview2.style.overflow="visible";for(var f=this.shiftPreview1,d=this.container.firstChild;null!=d;)e=d.nextSibling,d!=c.parentNode?f.appendChild(d):f=this.shiftPreview2,d=e;null!=this.shiftPreview1.firstChild&&this.container.insertBefore(this.shiftPreview1,c.parentNode);null!=this.shiftPreview2.firstChild&&this.container.appendChi [...]
+a+"px";this.shiftPreview1.style.top=b+"px";this.shiftPreview2.style.left=a+"px";this.shiftPreview2.style.top=b+"px"}else c.style.left=a+"px",c.style.top=b+"px";this.panDx=a;this.panDy=b;this.fireEvent(new mxEventObject(mxEvent.PAN))}};mxGraph.prototype.zoomIn=function(){this.zoom(this.zoomFactor)};mxGraph.prototype.zoomOut=function(){this.zoom(1/this.zoomFactor)};
+mxGraph.prototype.zoomActual=function(){1==this.view.scale?this.view.setTranslate(0,0):(this.view.translate.x=0,this.view.translate.y=0,this.view.setScale(1))};mxGraph.prototype.zoomTo=function(a,b){this.zoom(a/this.view.scale,b)};
+mxGraph.prototype.center=function(a,b,c,d){a=null!=a?a:!0;b=null!=b?b:!0;c=null!=c?c:.5;d=null!=d?d:.5;var e=mxUtils.hasScrollbars(this.container),f=this.container.clientWidth,g=this.container.clientHeight,k=this.getGraphBounds(),l=this.view.translate,m=this.view.scale,n=a?f-k.width:0,p=b?g-k.height:0;e?(k.x-=l.x,k.y-=l.y,a=this.container.scrollWidth,b=this.container.scrollHeight,a>f&&(n=0),b>g&&(p=0),this.view.setTranslate(Math.floor(n/2-k.x),Math.floor(p/2-k.y)),this.container.scrollLe [...]
+2,this.container.scrollTop=(b-g)/2):this.view.setTranslate(a?Math.floor(l.x-k.x*m+n*c/m):l.x,b?Math.floor(l.y-k.y*m+p*d/m):l.y)};
+mxGraph.prototype.zoom=function(a,b){b=null!=b?b:this.centerZoom;var c=Math.round(this.view.scale*a*100)/100,d=this.view.getState(this.getSelectionCell());a=c/this.view.scale;if(this.keepSelectionVisibleOnZoom&&null!=d)d=new mxRectangle(d.x*a,d.y*a,d.width*a,d.height*a),this.view.scale=c,this.scrollRectToVisible(d)||(this.view.revalidate(),this.view.setScale(c));else if(d=mxUtils.hasScrollbars(this.container),b&&!d){var d=this.container.offsetWidth,e=this.container.offsetHeight;if(1<a)va [...]
+(2*c),d=d*-f,e=e*-f;else f=(1/a-1)/(2*this.view.scale),d*=f,e*=f;this.view.scaleAndTranslate(c,this.view.translate.x+d,this.view.translate.y+e)}else{var f=this.view.translate.x,g=this.view.translate.y,k=this.container.scrollLeft,l=this.container.scrollTop;this.view.setScale(c);d&&(e=d=0,b&&(d=this.container.offsetWidth*(a-1)/2,e=this.container.offsetHeight*(a-1)/2),this.container.scrollLeft=(this.view.translate.x-f)*this.view.scale+Math.round(k*a+d),this.container.scrollTop=(this.view.tr [...]
+g)*this.view.scale+Math.round(l*a+e))}};
+mxGraph.prototype.zoomToRect=function(a){var b=this.container.clientWidth/a.width/(this.container.clientHeight/a.height);a.x=Math.max(0,a.x);a.y=Math.max(0,a.y);var c=Math.min(this.container.scrollWidth,a.x+a.width),d=Math.min(this.container.scrollHeight,a.y+a.height);a.width=c-a.x;a.height=d-a.y;1>b?(b=a.height/b,c=(b-a.height)/2,a.height=b,a.y-=Math.min(a.y,c),d=Math.min(this.container.scrollHeight,a.y+a.height),a.height=d-a.y):(b*=a.width,c=(b-a.width)/2,a.width=b,a.x-=Math.min(a.x,c) [...]
+a.x+a.width),a.width=c-a.x);b=this.container.clientWidth/a.width;c=this.view.scale*b;mxUtils.hasScrollbars(this.container)?(this.view.setScale(c),this.container.scrollLeft=Math.round(a.x*b),this.container.scrollTop=Math.round(a.y*b)):this.view.scaleAndTranslate(c,this.view.translate.x-a.x/this.view.scale,this.view.translate.y-a.y/this.view.scale)};
+mxGraph.prototype.scrollCellToVisible=function(a,b){var c=-this.view.translate.x,d=-this.view.translate.y,e=this.view.getState(a);null!=e&&(c=new mxRectangle(c+e.x,d+e.y,e.width,e.height),b&&null!=this.container&&(d=this.container.clientWidth,e=this.container.clientHeight,c.x=c.getCenterX()-d/2,c.width=d,c.y=c.getCenterY()-e/2,c.height=e),d=new mxPoint(this.view.translate.x,this.view.translate.y),this.scrollRectToVisible(c)&&(c=new mxPoint(this.view.translate.x,this.view.translate.y),thi [...]
+d.x,this.view.translate.y=d.y,this.view.setTranslate(c.x,c.y)))};
+mxGraph.prototype.scrollRectToVisible=function(a){var b=!1;if(null!=a){var c=this.container.offsetWidth,d=this.container.offsetHeight,e=Math.min(c,a.width),f=Math.min(d,a.height);if(mxUtils.hasScrollbars(this.container)){c=this.container;a.x+=this.view.translate.x;a.y+=this.view.translate.y;var g=c.scrollLeft-a.x,d=Math.max(g-c.scrollLeft,0);0<g?c.scrollLeft-=g+2:(g=a.x+e-c.scrollLeft-c.clientWidth,0<g&&(c.scrollLeft+=g+2));e=c.scrollTop-a.y;g=Math.max(0,e-c.scrollTop);0<e?c.scrollTop-=e [...]
+f-c.scrollTop-c.clientHeight,0<e&&(c.scrollTop+=e+2));this.useScrollbarsForPanning||0==d&&0==g||this.view.setTranslate(d,g)}else{var g=-this.view.translate.x,k=-this.view.translate.y,l=this.view.scale;a.x+e>g+c&&(this.view.translate.x-=(a.x+e-c-g)/l,b=!0);a.y+f>k+d&&(this.view.translate.y-=(a.y+f-d-k)/l,b=!0);a.x<g&&(this.view.translate.x+=(g-a.x)/l,b=!0);a.y<k&&(this.view.translate.y+=(k-a.y)/l,b=!0);b&&(this.view.refresh(),null!=this.selectionCellsHandler&&this.selectionCellsHandler.re [...]
+mxGraph.prototype.getCellGeometry=function(a){return this.model.getGeometry(a)};mxGraph.prototype.isCellVisible=function(a){return this.model.isVisible(a)};mxGraph.prototype.isCellCollapsed=function(a){return this.model.isCollapsed(a)};mxGraph.prototype.isCellConnectable=function(a){return this.model.isConnectable(a)};
+mxGraph.prototype.isOrthogonal=function(a){var b=a.style[mxConstants.STYLE_ORTHOGONAL];if(null!=b)return b;a=this.view.getEdgeStyle(a);return a==mxEdgeStyle.SegmentConnector||a==mxEdgeStyle.ElbowConnector||a==mxEdgeStyle.SideToSide||a==mxEdgeStyle.TopToBottom||a==mxEdgeStyle.EntityRelation||a==mxEdgeStyle.OrthConnector};mxGraph.prototype.isLoop=function(a){var b=a.getVisibleTerminalState(!0);a=a.getVisibleTerminalState(!1);return null!=b&&b==a};mxGraph.prototype.isCloneEvent=function(a){ [...]
+mxGraph.prototype.isTransparentClickEvent=function(a){return!1};mxGraph.prototype.isToggleEvent=function(a){return mxClient.IS_MAC?mxEvent.isMetaDown(a):mxEvent.isControlDown(a)};mxGraph.prototype.isGridEnabledEvent=function(a){return null!=a&&!mxEvent.isAltDown(a)};mxGraph.prototype.isConstrainedEvent=function(a){return mxEvent.isShiftDown(a)};mxGraph.prototype.isIgnoreTerminalEvent=function(a){return!1};mxGraph.prototype.validationAlert=function(a){mxUtils.alert(a)};
+mxGraph.prototype.isEdgeValid=function(a,b,c){return null==this.getEdgeValidationError(a,b,c)};
+mxGraph.prototype.getEdgeValidationError=function(a,b,c){if(null!=a&&!this.isAllowDanglingEdges()&&(null==b||null==c))return"";if(null!=a&&null==this.model.getTerminal(a,!0)&&null==this.model.getTerminal(a,!1))return null;if(!this.allowLoops&&b==c&&null!=b||!this.isValidConnection(b,c))return"";if(null!=b&&null!=c){var d="";if(!this.multigraph){var e=this.model.getEdgesBetween(b,c,!0);if(1<e.length||1==e.length&&e[0]!=a)d+=(mxResources.get(this.alreadyConnectedResource)||this.alreadyConn [...]
+"\n"}var e=this.model.getDirectedEdgeCount(b,!0,a),f=this.model.getDirectedEdgeCount(c,!1,a);if(null!=this.multiplicities)for(var g=0;g<this.multiplicities.length;g++){var k=this.multiplicities[g].check(this,a,b,c,e,f);null!=k&&(d+=k)}k=this.validateEdge(a,b,c);null!=k&&(d+=k);return 0<d.length?d:null}return this.allowDanglingEdges?null:""};mxGraph.prototype.validateEdge=function(a,b,c){return null};
+mxGraph.prototype.validateGraph=function(a,b){a=null!=a?a:this.model.getRoot();b=null!=b?b:{};for(var c=!0,d=this.model.getChildCount(a),e=0;e<d;e++){var f=this.model.getChildAt(a,e),g=b;this.isValidRoot(f)&&(g={});g=this.validateGraph(f,g);null!=g?this.setCellWarning(f,g.replace(/\n/g,"<br>")):this.setCellWarning(f,null);c=c&&null==g}d="";this.isCellCollapsed(a)&&!c&&(d+=(mxResources.get(this.containsValidationErrorsResource)||this.containsValidationErrorsResource)+"\n");d=this.model.is [...]
+(this.getEdgeValidationError(a,this.model.getTerminal(a,!0),this.model.getTerminal(a,!1))||""):d+(this.getCellValidationError(a)||"");e=this.validateCell(a,b);null!=e&&(d+=e);null==this.model.getParent(a)&&this.view.validate();return 0<d.length||!c?d:null};
+mxGraph.prototype.getCellValidationError=function(a){var b=this.model.getDirectedEdgeCount(a,!0),c=this.model.getDirectedEdgeCount(a,!1);a=this.model.getValue(a);var d="";if(null!=this.multiplicities)for(var e=0;e<this.multiplicities.length;e++){var f=this.multiplicities[e];f.source&&mxUtils.isNode(a,f.type,f.attr,f.value)&&(b>f.max||b<f.min)?d+=f.countError+"\n":!f.source&&mxUtils.isNode(a,f.type,f.attr,f.value)&&(c>f.max||c<f.min)&&(d+=f.countError+"\n")}return 0<d.length?d:null};
+mxGraph.prototype.validateCell=function(a,b){return null};mxGraph.prototype.getBackgroundImage=function(){return this.backgroundImage};mxGraph.prototype.setBackgroundImage=function(a){this.backgroundImage=a};mxGraph.prototype.getFoldingImage=function(a){if(null!=a&&this.foldingEnabled&&!this.getModel().isEdge(a.cell)){var b=this.isCellCollapsed(a.cell);if(this.isCellFoldable(a.cell,!b))return b?this.collapsedImage:this.expandedImage}return null};
+mxGraph.prototype.convertValueToString=function(a){a=this.model.getValue(a);if(null!=a){if(mxUtils.isNode(a))return a.nodeName;if("function"==typeof a.toString)return a.toString()}return""};mxGraph.prototype.getLabel=function(a){var b="";if(this.labelsVisible&&null!=a){var c=this.view.getState(a),c=null!=c?c.style:this.getCellStyle(a);mxUtils.getValue(c,mxConstants.STYLE_NOLABEL,!1)||(b=this.convertValueToString(a))}return b};mxGraph.prototype.isHtmlLabel=function(a){return this.isHtmlLa [...]
+mxGraph.prototype.isHtmlLabels=function(){return this.htmlLabels};mxGraph.prototype.setHtmlLabels=function(a){this.htmlLabels=a};mxGraph.prototype.isWrapping=function(a){var b=this.view.getState(a);a=null!=b?b.style:this.getCellStyle(a);return null!=a?"wrap"==a[mxConstants.STYLE_WHITE_SPACE]:!1};mxGraph.prototype.isLabelClipped=function(a){var b=this.view.getState(a);a=null!=b?b.style:this.getCellStyle(a);return null!=a?"hidden"==a[mxConstants.STYLE_OVERFLOW]:!1};
+mxGraph.prototype.getTooltip=function(a,b,c,d){var e=null;null!=a&&(null==a.control||b!=a.control.node&&b.parentNode!=a.control.node||(e=this.collapseExpandResource,e=mxUtils.htmlEntities(mxResources.get(e)||e).replace(/\\n/g,"<br>")),null==e&&null!=a.overlays&&a.overlays.visit(function(a,c){null!=e||b!=c.node&&b.parentNode!=c.node||(e=c.overlay.toString())}),null==e&&(c=this.selectionCellsHandler.getHandler(a.cell),null!=c&&"function"==typeof c.getTooltipForNode&&(e=c.getTooltipForNode( [...]
+e&&(e=this.getTooltipForCell(a.cell)));return e};mxGraph.prototype.getTooltipForCell=function(a){return null!=a&&null!=a.getTooltip?a.getTooltip():this.convertValueToString(a)};mxGraph.prototype.getCursorForMouseEvent=function(a){return this.getCursorForCell(a.getCell())};mxGraph.prototype.getCursorForCell=function(a){return null};
+mxGraph.prototype.getStartSize=function(a){var b=new mxRectangle,c=this.view.getState(a);a=null!=c?c.style:this.getCellStyle(a);null!=a&&(c=parseInt(mxUtils.getValue(a,mxConstants.STYLE_STARTSIZE,mxConstants.DEFAULT_STARTSIZE)),mxUtils.getValue(a,mxConstants.STYLE_HORIZONTAL,!0)?b.height=c:b.width=c);return b};mxGraph.prototype.getImage=function(a){return null!=a&&null!=a.style?a.style[mxConstants.STYLE_IMAGE]:null};
+mxGraph.prototype.getVerticalAlign=function(a){return null!=a&&null!=a.style?a.style[mxConstants.STYLE_VERTICAL_ALIGN]||mxConstants.ALIGN_MIDDLE:null};mxGraph.prototype.getIndicatorColor=function(a){return null!=a&&null!=a.style?a.style[mxConstants.STYLE_INDICATOR_COLOR]:null};mxGraph.prototype.getIndicatorGradientColor=function(a){return null!=a&&null!=a.style?a.style[mxConstants.STYLE_INDICATOR_GRADIENTCOLOR]:null};
+mxGraph.prototype.getIndicatorShape=function(a){return null!=a&&null!=a.style?a.style[mxConstants.STYLE_INDICATOR_SHAPE]:null};mxGraph.prototype.getIndicatorImage=function(a){return null!=a&&null!=a.style?a.style[mxConstants.STYLE_INDICATOR_IMAGE]:null};mxGraph.prototype.getBorder=function(){return this.border};mxGraph.prototype.setBorder=function(a){this.border=a};
+mxGraph.prototype.isSwimlane=function(a){if(null!=a&&this.model.getParent(a)!=this.model.getRoot()){var b=this.view.getState(a),b=null!=b?b.style:this.getCellStyle(a);if(null!=b&&!this.model.isEdge(a))return b[mxConstants.STYLE_SHAPE]==mxConstants.SHAPE_SWIMLANE}return!1};mxGraph.prototype.isResizeContainer=function(){return this.resizeContainer};mxGraph.prototype.setResizeContainer=function(a){this.resizeContainer=a};mxGraph.prototype.isEnabled=function(){return this.enabled};
+mxGraph.prototype.setEnabled=function(a){this.enabled=a};mxGraph.prototype.isEscapeEnabled=function(){return this.escapeEnabled};mxGraph.prototype.setEscapeEnabled=function(a){this.escapeEnabled=a};mxGraph.prototype.isInvokesStopCellEditing=function(){return this.invokesStopCellEditing};mxGraph.prototype.setInvokesStopCellEditing=function(a){this.invokesStopCellEditing=a};mxGraph.prototype.isEnterStopsCellEditing=function(){return this.enterStopsCellEditing};
+mxGraph.prototype.setEnterStopsCellEditing=function(a){this.enterStopsCellEditing=a};mxGraph.prototype.isCellLocked=function(a){var b=this.model.getGeometry(a);return this.isCellsLocked()||null!=b&&this.model.isVertex(a)&&b.relative};mxGraph.prototype.isCellsLocked=function(){return this.cellsLocked};mxGraph.prototype.setCellsLocked=function(a){this.cellsLocked=a};mxGraph.prototype.getCloneableCells=function(a){return this.model.filterCells(a,mxUtils.bind(this,function(a){return this.isC [...]
+mxGraph.prototype.isCellCloneable=function(a){var b=this.view.getState(a);a=null!=b?b.style:this.getCellStyle(a);return this.isCellsCloneable()&&0!=a[mxConstants.STYLE_CLONEABLE]};mxGraph.prototype.isCellsCloneable=function(){return this.cellsCloneable};mxGraph.prototype.setCellsCloneable=function(a){this.cellsCloneable=a};mxGraph.prototype.getExportableCells=function(a){return this.model.filterCells(a,mxUtils.bind(this,function(a){return this.canExportCell(a)}))};
+mxGraph.prototype.canExportCell=function(a){return this.exportEnabled};mxGraph.prototype.getImportableCells=function(a){return this.model.filterCells(a,mxUtils.bind(this,function(a){return this.canImportCell(a)}))};mxGraph.prototype.canImportCell=function(a){return this.importEnabled};mxGraph.prototype.isCellSelectable=function(a){return this.isCellsSelectable()};mxGraph.prototype.isCellsSelectable=function(){return this.cellsSelectable};
+mxGraph.prototype.setCellsSelectable=function(a){this.cellsSelectable=a};mxGraph.prototype.getDeletableCells=function(a){return this.model.filterCells(a,mxUtils.bind(this,function(a){return this.isCellDeletable(a)}))};mxGraph.prototype.isCellDeletable=function(a){var b=this.view.getState(a);a=null!=b?b.style:this.getCellStyle(a);return this.isCellsDeletable()&&0!=a[mxConstants.STYLE_DELETABLE]};mxGraph.prototype.isCellsDeletable=function(){return this.cellsDeletable};
+mxGraph.prototype.setCellsDeletable=function(a){this.cellsDeletable=a};mxGraph.prototype.isLabelMovable=function(a){return!this.isCellLocked(a)&&(this.model.isEdge(a)&&this.edgeLabelsMovable||this.model.isVertex(a)&&this.vertexLabelsMovable)};mxGraph.prototype.isCellRotatable=function(a){var b=this.view.getState(a);return 0!=(null!=b?b.style:this.getCellStyle(a))[mxConstants.STYLE_ROTATABLE]};mxGraph.prototype.getMovableCells=function(a){return this.model.filterCells(a,mxUtils.bind(this, [...]
+mxGraph.prototype.isCellMovable=function(a){var b=this.view.getState(a),b=null!=b?b.style:this.getCellStyle(a);return this.isCellsMovable()&&!this.isCellLocked(a)&&0!=b[mxConstants.STYLE_MOVABLE]};mxGraph.prototype.isCellsMovable=function(){return this.cellsMovable};mxGraph.prototype.setCellsMovable=function(a){this.cellsMovable=a};mxGraph.prototype.isGridEnabled=function(){return this.gridEnabled};mxGraph.prototype.setGridEnabled=function(a){this.gridEnabled=a};mxGraph.prototype.isPorts [...]
+mxGraph.prototype.setPortsEnabled=function(a){this.portsEnabled=a};mxGraph.prototype.getGridSize=function(){return this.gridSize};mxGraph.prototype.setGridSize=function(a){this.gridSize=a};mxGraph.prototype.getTolerance=function(){return this.tolerance};mxGraph.prototype.setTolerance=function(a){this.tolerance=a};mxGraph.prototype.isVertexLabelsMovable=function(){return this.vertexLabelsMovable};mxGraph.prototype.setVertexLabelsMovable=function(a){this.vertexLabelsMovable=a};
+mxGraph.prototype.isEdgeLabelsMovable=function(){return this.edgeLabelsMovable};mxGraph.prototype.setEdgeLabelsMovable=function(a){this.edgeLabelsMovable=a};mxGraph.prototype.isSwimlaneNesting=function(){return this.swimlaneNesting};mxGraph.prototype.setSwimlaneNesting=function(a){this.swimlaneNesting=a};mxGraph.prototype.isSwimlaneSelectionEnabled=function(){return this.swimlaneSelectionEnabled};mxGraph.prototype.setSwimlaneSelectionEnabled=function(a){this.swimlaneSelectionEnabled=a};
+mxGraph.prototype.isMultigraph=function(){return this.multigraph};mxGraph.prototype.setMultigraph=function(a){this.multigraph=a};mxGraph.prototype.isAllowLoops=function(){return this.allowLoops};mxGraph.prototype.setAllowDanglingEdges=function(a){this.allowDanglingEdges=a};mxGraph.prototype.isAllowDanglingEdges=function(){return this.allowDanglingEdges};mxGraph.prototype.setConnectableEdges=function(a){this.connectableEdges=a};mxGraph.prototype.isConnectableEdges=function(){return this.c [...]
+mxGraph.prototype.setCloneInvalidEdges=function(a){this.cloneInvalidEdges=a};mxGraph.prototype.isCloneInvalidEdges=function(){return this.cloneInvalidEdges};mxGraph.prototype.setAllowLoops=function(a){this.allowLoops=a};mxGraph.prototype.isDisconnectOnMove=function(){return this.disconnectOnMove};mxGraph.prototype.setDisconnectOnMove=function(a){this.disconnectOnMove=a};mxGraph.prototype.isDropEnabled=function(){return this.dropEnabled};
+mxGraph.prototype.setDropEnabled=function(a){this.dropEnabled=a};mxGraph.prototype.isSplitEnabled=function(){return this.splitEnabled};mxGraph.prototype.setSplitEnabled=function(a){this.splitEnabled=a};mxGraph.prototype.isCellResizable=function(a){var b=this.view.getState(a),b=null!=b?b.style:this.getCellStyle(a);return this.isCellsResizable()&&!this.isCellLocked(a)&&"0"!=mxUtils.getValue(b,mxConstants.STYLE_RESIZABLE,"1")};mxGraph.prototype.isCellsResizable=function(){return this.cellsR [...]
+mxGraph.prototype.setCellsResizable=function(a){this.cellsResizable=a};mxGraph.prototype.isTerminalPointMovable=function(a,b){return!0};mxGraph.prototype.isCellBendable=function(a){var b=this.view.getState(a),b=null!=b?b.style:this.getCellStyle(a);return this.isCellsBendable()&&!this.isCellLocked(a)&&0!=b[mxConstants.STYLE_BENDABLE]};mxGraph.prototype.isCellsBendable=function(){return this.cellsBendable};mxGraph.prototype.setCellsBendable=function(a){this.cellsBendable=a};
+mxGraph.prototype.isCellEditable=function(a){var b=this.view.getState(a),b=null!=b?b.style:this.getCellStyle(a);return this.isCellsEditable()&&!this.isCellLocked(a)&&0!=b[mxConstants.STYLE_EDITABLE]};mxGraph.prototype.isCellsEditable=function(){return this.cellsEditable};mxGraph.prototype.setCellsEditable=function(a){this.cellsEditable=a};mxGraph.prototype.isCellDisconnectable=function(a,b,c){return this.isCellsDisconnectable()&&!this.isCellLocked(a)};mxGraph.prototype.isCellsDisconnecta [...]
+mxGraph.prototype.setCellsDisconnectable=function(a){this.cellsDisconnectable=a};mxGraph.prototype.isValidSource=function(a){return null==a&&this.allowDanglingEdges||null!=a&&(!this.model.isEdge(a)||this.connectableEdges)&&this.isCellConnectable(a)};mxGraph.prototype.isValidTarget=function(a){return this.isValidSource(a)};mxGraph.prototype.isValidConnection=function(a,b){return this.isValidSource(a)&&this.isValidTarget(b)};mxGraph.prototype.setConnectable=function(a){this.connectionHandl [...]
+mxGraph.prototype.isConnectable=function(a){return this.connectionHandler.isEnabled()};mxGraph.prototype.setTooltips=function(a){this.tooltipHandler.setEnabled(a)};mxGraph.prototype.setPanning=function(a){this.panningHandler.panningEnabled=a};mxGraph.prototype.isEditing=function(a){if(null!=this.cellEditor){var b=this.cellEditor.getEditingCell();return null==a?null!=b:a==b}return!1};
+mxGraph.prototype.isAutoSizeCell=function(a){var b=this.view.getState(a);a=null!=b?b.style:this.getCellStyle(a);return this.isAutoSizeCells()||1==a[mxConstants.STYLE_AUTOSIZE]};mxGraph.prototype.isAutoSizeCells=function(){return this.autoSizeCells};mxGraph.prototype.setAutoSizeCells=function(a){this.autoSizeCells=a};mxGraph.prototype.isExtendParent=function(a){return!this.getModel().isEdge(a)&&this.isExtendParents()};mxGraph.prototype.isExtendParents=function(){return this.extendParents};
+mxGraph.prototype.setExtendParents=function(a){this.extendParents=a};mxGraph.prototype.isExtendParentsOnAdd=function(a){return this.extendParentsOnAdd};mxGraph.prototype.setExtendParentsOnAdd=function(a){this.extendParentsOnAdd=a};mxGraph.prototype.isExtendParentsOnMove=function(){return this.extendParentsOnMove};mxGraph.prototype.setExtendParentsOnMove=function(a){this.extendParentsOnMove=a};mxGraph.prototype.isRecursiveResize=function(a){return this.recursiveResize};
+mxGraph.prototype.setRecursiveResize=function(a){this.recursiveResize=a};mxGraph.prototype.isConstrainChild=function(a){return this.isConstrainChildren()&&!this.getModel().isEdge(this.getModel().getParent(a))};mxGraph.prototype.isConstrainChildren=function(){return this.constrainChildren};mxGraph.prototype.setConstrainChildren=function(a){this.constrainChildren=a};mxGraph.prototype.isConstrainRelativeChildren=function(){return this.constrainRelativeChildren};
+mxGraph.prototype.setConstrainRelativeChildren=function(a){this.constrainRelativeChildren=a};mxGraph.prototype.isAllowNegativeCoordinates=function(){return this.allowNegativeCoordinates};mxGraph.prototype.setAllowNegativeCoordinates=function(a){this.allowNegativeCoordinates=a};mxGraph.prototype.getOverlap=function(a){return this.isAllowOverlapParent(a)?this.defaultOverlap:0};mxGraph.prototype.isAllowOverlapParent=function(a){return!1};
+mxGraph.prototype.getFoldableCells=function(a,b){return this.model.filterCells(a,mxUtils.bind(this,function(a){return this.isCellFoldable(a,b)}))};mxGraph.prototype.isCellFoldable=function(a,b){var c=this.view.getState(a),c=null!=c?c.style:this.getCellStyle(a);return 0<this.model.getChildCount(a)&&0!=c[mxConstants.STYLE_FOLDABLE]};
+mxGraph.prototype.isValidDropTarget=function(a,b,c){return null!=a&&(this.isSplitEnabled()&&this.isSplitTarget(a,b,c)||!this.model.isEdge(a)&&(this.isSwimlane(a)||0<this.model.getChildCount(a)&&!this.isCellCollapsed(a)))};
+mxGraph.prototype.isSplitTarget=function(a,b,c){return this.model.isEdge(a)&&null!=b&&1==b.length&&this.isCellConnectable(b[0])&&null==this.getEdgeValidationError(a,this.model.getTerminal(a,!0),b[0])?(c=this.model.getTerminal(a,!0),a=this.model.getTerminal(a,!1),!this.model.isAncestor(b[0],c)&&!this.model.isAncestor(b[0],a)):!1};
+mxGraph.prototype.getDropTarget=function(a,b,c,d){if(!this.isSwimlaneNesting())for(var e=0;e<a.length;e++)if(this.isSwimlane(a[e]))return null;e=mxUtils.convertPoint(this.container,mxEvent.getClientX(b),mxEvent.getClientY(b));e.x-=this.panDx;e.y-=this.panDy;e=this.getSwimlaneAt(e.x,e.y);if(null==c)c=e;else if(null!=e){for(var f=this.model.getParent(e);null!=f&&this.isSwimlane(f)&&f!=c;)f=this.model.getParent(f);f==c&&(c=e)}for(;null!=c&&!this.isValidDropTarget(c,a,b)&&!this.model.isLayer [...]
+if(null==d||!d)for(var g=c;null!=g&&0>mxUtils.indexOf(a,g);)g=this.model.getParent(g);return this.model.isLayer(c)||null!=g?null:c};mxGraph.prototype.getDefaultParent=function(){var a=this.getCurrentRoot();null==a&&(a=this.defaultParent,null==a&&(a=this.model.getRoot(),a=this.model.getChildAt(a,0)));return a};mxGraph.prototype.setDefaultParent=function(a){this.defaultParent=a};mxGraph.prototype.getSwimlane=function(a){for(;null!=a&&!this.isSwimlane(a);)a=this.model.getParent(a);return a};
+mxGraph.prototype.getSwimlaneAt=function(a,b,c){c=c||this.getDefaultParent();if(null!=c)for(var d=this.model.getChildCount(c),e=0;e<d;e++){var f=this.model.getChildAt(c,e),g=this.getSwimlaneAt(a,b,f);if(null!=g)return g;if(this.isSwimlane(f)&&(g=this.view.getState(f),this.intersects(g,a,b)))return f}return null};
+mxGraph.prototype.getCellAt=function(a,b,c,d,e,f){d=null!=d?d:!0;e=null!=e?e:!0;null==c&&(c=this.getCurrentRoot(),null==c&&(c=this.getModel().getRoot()));if(null!=c)for(var g=this.model.getChildCount(c)-1;0<=g;g--){var k=this.model.getChildAt(c,g),l=this.getCellAt(a,b,k,d,e,f);if(null!=l)return l;if(this.isCellVisible(k)&&(e&&this.model.isEdge(k)||d&&this.model.isVertex(k))&&(l=this.view.getState(k),null!=l&&(null==f||!f(l,a,b))&&this.intersects(l,a,b)))return k}return null};
+mxGraph.prototype.intersects=function(a,b,c){if(null!=a){var d=a.absolutePoints;if(null!=d){a=this.tolerance*this.tolerance;for(var e=d[0],f=1;f<d.length;f++){var g=d[f];if(mxUtils.ptSegDistSq(e.x,e.y,g.x,g.y,b,c)<=a)return!0;e=g}}else if(e=mxUtils.toRadians(mxUtils.getValue(a.style,mxConstants.STYLE_ROTATION)||0),0!=e&&(d=Math.cos(-e),e=Math.sin(-e),f=new mxPoint(a.getCenterX(),a.getCenterY()),e=mxUtils.getRotatedPoint(new mxPoint(b,c),d,e,f),b=e.x,c=e.y),mxUtils.contains(a,b,c))return! [...]
+mxGraph.prototype.hitsSwimlaneContent=function(a,b,c){var d=this.getView().getState(a);a=this.getStartSize(a);if(null!=d){var e=this.getView().getScale();b-=d.x;c-=d.y;if(0<a.width&&0<b&&b>a.width*e||0<a.height&&0<c&&c>a.height*e)return!0}return!1};mxGraph.prototype.getChildVertices=function(a){return this.getChildCells(a,!0,!1)};mxGraph.prototype.getChildEdges=function(a){return this.getChildCells(a,!1,!0)};
+mxGraph.prototype.getChildCells=function(a,b,c){a=null!=a?a:this.getDefaultParent();a=this.model.getChildCells(a,null!=b?b:!1,null!=c?c:!1);b=[];for(c=0;c<a.length;c++)this.isCellVisible(a[c])&&b.push(a[c]);return b};mxGraph.prototype.getConnections=function(a,b){return this.getEdges(a,b,!0,!0,!1)};mxGraph.prototype.getIncomingEdges=function(a,b){return this.getEdges(a,b,!0,!1,!1)};mxGraph.prototype.getOutgoingEdges=function(a,b){return this.getEdges(a,b,!1,!0,!1)};
+mxGraph.prototype.getEdges=function(a,b,c,d,e,f){c=null!=c?c:!0;d=null!=d?d:!0;e=null!=e?e:!0;f=null!=f?f:!1;for(var g=[],k=this.isCellCollapsed(a),l=this.model.getChildCount(a),m=0;m<l;m++){var n=this.model.getChildAt(a,m);if(k||!this.isCellVisible(n))g=g.concat(this.model.getEdges(n,c,d))}g=g.concat(this.model.getEdges(a,c,d));k=[];for(m=0;m<g.length;m++)n=this.view.getState(g[m]),l=null!=n?n.getVisibleTerminal(!0):this.view.getVisibleTerminal(g[m],!0),n=null!=n?n.getVisibleTerminal(!1 [...]
+!1),(e&&l==n||l!=n&&(c&&n==a&&(null==b||this.isValidAncestor(l,b,f))||d&&l==a&&(null==b||this.isValidAncestor(n,b,f))))&&k.push(g[m]);return k};mxGraph.prototype.isValidAncestor=function(a,b,c){return c?this.model.isAncestor(b,a):this.model.getParent(a)==b};
+mxGraph.prototype.getOpposites=function(a,b,c,d){c=null!=c?c:!0;d=null!=d?d:!0;var e=[],f=new mxDictionary;if(null!=a)for(var g=0;g<a.length;g++){var k=this.view.getState(a[g]),l=null!=k?k.getVisibleTerminal(!0):this.view.getVisibleTerminal(a[g],!0),k=null!=k?k.getVisibleTerminal(!1):this.view.getVisibleTerminal(a[g],!1);l==b&&null!=k&&k!=b&&d?f.get(k)||(f.put(k,!0),e.push(k)):k==b&&null!=l&&l!=b&&c&&!f.get(l)&&(f.put(l,!0),e.push(l))}return e};
+mxGraph.prototype.getEdgesBetween=function(a,b,c){c=null!=c?c:!1;for(var d=this.getEdges(a),e=[],f=0;f<d.length;f++){var g=this.view.getState(d[f]),k=null!=g?g.getVisibleTerminal(!0):this.view.getVisibleTerminal(d[f],!0),g=null!=g?g.getVisibleTerminal(!1):this.view.getVisibleTerminal(d[f],!1);(k==a&&g==b||!c&&k==b&&g==a)&&e.push(d[f])}return e};
+mxGraph.prototype.getPointForEvent=function(a,b){var c=mxUtils.convertPoint(this.container,mxEvent.getClientX(a),mxEvent.getClientY(a)),d=this.view.scale,e=this.view.translate,f=0!=b?this.gridSize/2:0;c.x=this.snap(c.x/d-e.x-f);c.y=this.snap(c.y/d-e.y-f);return c};
+mxGraph.prototype.getCells=function(a,b,c,d,e,f){f=null!=f?f:[];if(0<c||0<d){var g=this.getModel(),k=a+c,l=b+d;null==e&&(e=this.getCurrentRoot(),null==e&&(e=g.getRoot()));if(null!=e)for(var m=g.getChildCount(e),n=0;n<m;n++){var p=g.getChildAt(e,n),q=this.view.getState(p);if(null!=q&&this.isCellVisible(p)){var r=mxUtils.getValue(q.style,mxConstants.STYLE_ROTATION)||0;0!=r&&(q=mxUtils.getBoundingBox(q,r));(g.isEdge(p)||g.isVertex(p))&&q.x>=a&&q.y+q.height<=l&&q.y>=b&&q.x+q.width<=k?f.push( [...]
+b,c,d,p,f)}}}return f};mxGraph.prototype.getCellsBeyond=function(a,b,c,d,e){var f=[];if(d||e)if(null==c&&(c=this.getDefaultParent()),null!=c)for(var g=this.model.getChildCount(c),k=0;k<g;k++){var l=this.model.getChildAt(c,k),m=this.view.getState(l);this.isCellVisible(l)&&null!=m&&(!d||m.x>=a)&&(!e||m.y>=b)&&f.push(l)}return f};
+mxGraph.prototype.findTreeRoots=function(a,b,c){b=null!=b?b:!1;c=null!=c?c:!1;var d=[];if(null!=a){for(var e=this.getModel(),f=e.getChildCount(a),g=null,k=0,l=0;l<f;l++){var m=e.getChildAt(a,l);if(this.model.isVertex(m)&&this.isCellVisible(m)){for(var n=this.getConnections(m,b?a:null),p=0,q=0,r=0;r<n.length;r++)this.view.getVisibleTerminal(n[r],!0)==m?p++:q++;(c&&0==p&&0<q||!c&&0==q&&0<p)&&d.push(m);n=c?q-p:p-q;n>k&&(k=n,g=m)}}0==d.length&&null!=g&&d.push(g)}return d};
+mxGraph.prototype.traverse=function(a,b,c,d,e,f){if(null!=c&&null!=a&&(b=null!=b?b:!0,f=null!=f?f:!1,e=e||new mxDictionary,!e.get(a)&&(e.put(a,!0),d=c(a,d),null==d||d))&&(d=this.model.getEdgeCount(a),0<d))for(var g=0;g<d;g++){var k=this.model.getEdgeAt(a,g),l=this.model.getTerminal(k,!0)==a;b&&!f!=l||(l=this.model.getTerminal(k,!l),this.traverse(l,b,c,k,e,f))}};mxGraph.prototype.isCellSelected=function(a){return this.getSelectionModel().isSelected(a)};mxGraph.prototype.isSelectionEmpty=f [...]
+mxGraph.prototype.clearSelection=function(){return this.getSelectionModel().clear()};mxGraph.prototype.getSelectionCount=function(){return this.getSelectionModel().cells.length};mxGraph.prototype.getSelectionCell=function(){return this.getSelectionModel().cells[0]};mxGraph.prototype.getSelectionCells=function(){return this.getSelectionModel().cells.slice()};mxGraph.prototype.setSelectionCell=function(a){this.getSelectionModel().setCell(a)};mxGraph.prototype.setSelectionCells=function(a){ [...]
+mxGraph.prototype.addSelectionCell=function(a){this.getSelectionModel().addCell(a)};mxGraph.prototype.addSelectionCells=function(a){this.getSelectionModel().addCells(a)};mxGraph.prototype.removeSelectionCell=function(a){this.getSelectionModel().removeCell(a)};mxGraph.prototype.removeSelectionCells=function(a){this.getSelectionModel().removeCells(a)};mxGraph.prototype.selectRegion=function(a,b){var c=this.getCells(a.x,a.y,a.width,a.height);this.selectCellsForEvent(c,b);return c};
+mxGraph.prototype.selectNextCell=function(){this.selectCell(!0)};mxGraph.prototype.selectPreviousCell=function(){this.selectCell()};mxGraph.prototype.selectParentCell=function(){this.selectCell(!1,!0)};mxGraph.prototype.selectChildCell=function(){this.selectCell(!1,!1,!0)};
+mxGraph.prototype.selectCell=function(a,b,c){var d=this.selectionModel,e=0<d.cells.length?d.cells[0]:null;1<d.cells.length&&d.clear();var d=null!=e?this.model.getParent(e):this.getDefaultParent(),f=this.model.getChildCount(d);null==e&&0<f?(a=this.model.getChildAt(d,0),this.setSelectionCell(a)):null!=e&&!b||null==this.view.getState(d)||null==this.model.getGeometry(d)?null!=e&&c?0<this.model.getChildCount(e)&&(a=this.model.getChildAt(e,0),this.setSelectionCell(a)):0<f&&(b=d.getIndex(e),a?( [...]
+b%f)):(b--,a=this.model.getChildAt(d,0>b?f-1:b)),this.setSelectionCell(a)):this.getCurrentRoot()!=d&&this.setSelectionCell(d)};mxGraph.prototype.selectAll=function(a,b){a=a||this.getDefaultParent();var c=b?this.model.filterDescendants(function(b){return b!=a},a):this.model.getChildren(a);null!=c&&this.setSelectionCells(c)};mxGraph.prototype.selectVertices=function(a){this.selectCells(!0,!1,a)};mxGraph.prototype.selectEdges=function(a){this.selectCells(!1,!0,a)};
+mxGraph.prototype.selectCells=function(a,b,c){c=c||this.getDefaultParent();var d=mxUtils.bind(this,function(c){return null!=this.view.getState(c)&&(0==this.model.getChildCount(c)&&this.model.isVertex(c)&&a&&!this.model.isEdge(this.model.getParent(c))||this.model.isEdge(c)&&b)});c=this.model.filterDescendants(d,c);this.setSelectionCells(c)};
+mxGraph.prototype.selectCellForEvent=function(a,b){var c=this.isCellSelected(a);this.isToggleEvent(b)?c?this.removeSelectionCell(a):this.addSelectionCell(a):c&&1==this.getSelectionCount()||this.setSelectionCell(a)};mxGraph.prototype.selectCellsForEvent=function(a,b){this.isToggleEvent(b)?this.addSelectionCells(a):this.setSelectionCells(a)};
+mxGraph.prototype.createHandler=function(a){var b=null;if(null!=a)if(this.model.isEdge(a.cell))var b=a.getVisibleTerminalState(!0),c=a.getVisibleTerminalState(!1),d=this.getCellGeometry(a.cell),b=this.view.getEdgeStyle(a,null!=d?d.points:null,b,c),b=this.createEdgeHandler(a,b);else b=this.createVertexHandler(a);return b};mxGraph.prototype.createVertexHandler=function(a){return new mxVertexHandler(a)};
+mxGraph.prototype.createEdgeHandler=function(a,b){return b==mxEdgeStyle.Loop||b==mxEdgeStyle.ElbowConnector||b==mxEdgeStyle.SideToSide||b==mxEdgeStyle.TopToBottom?this.createElbowEdgeHandler(a):b==mxEdgeStyle.SegmentConnector||b==mxEdgeStyle.OrthConnector?this.createEdgeSegmentHandler(a):new mxEdgeHandler(a)};mxGraph.prototype.createEdgeSegmentHandler=function(a){return new mxEdgeSegmentHandler(a)};mxGraph.prototype.createElbowEdgeHandler=function(a){return new mxElbowEdgeHandler(a)};
+mxGraph.prototype.addMouseListener=function(a){null==this.mouseListeners&&(this.mouseListeners=[]);this.mouseListeners.push(a)};mxGraph.prototype.removeMouseListener=function(a){if(null!=this.mouseListeners)for(var b=0;b<this.mouseListeners.length;b++)if(this.mouseListeners[b]==a){this.mouseListeners.splice(b,1);break}};
+mxGraph.prototype.updateMouseEvent=function(a,b){if(null==a.graphX||null==a.graphY){var c=mxUtils.convertPoint(this.container,a.getX(),a.getY());a.graphX=c.x-this.panDx;a.graphY=c.y-this.panDy;null==a.getCell()&&this.isMouseDown&&b==mxEvent.MOUSE_MOVE&&(a.state=this.view.getState(this.getCellAt(c.x,c.y,null,null,null,function(a){return null==a.shape||a.shape.paintBackground!=mxRectangleShape.prototype.paintBackground||"1"==mxUtils.getValue(a.style,mxConstants.STYLE_POINTER_EVENTS,"1")||n [...]
+a.shape.fill!=mxConstants.NONE})))}return a};mxGraph.prototype.getStateForTouchEvent=function(a){var b=mxEvent.getClientX(a);a=mxEvent.getClientY(a);b=mxUtils.convertPoint(this.container,b,a);return this.view.getState(this.getCellAt(b.x,b.y))};
+mxGraph.prototype.isEventIgnored=function(a,b,c){var d=mxEvent.isMouseEvent(b.getEvent()),e=!1;b.getEvent()==this.lastEvent?e=!0:this.lastEvent=b.getEvent();null!=this.eventSource&&a!=mxEvent.MOUSE_MOVE?(mxEvent.removeGestureListeners(this.eventSource,null,this.mouseMoveRedirect,this.mouseUpRedirect),this.eventSource=this.mouseUpRedirect=this.mouseMoveRedirect=null):null!=this.eventSource&&b.getSource()!=this.eventSource?e=!0:!mxClient.IS_TOUCH||a!=mxEvent.MOUSE_DOWN||d||mxEvent.isPenEve [...]
+(this.eventSource=b.getSource(),this.mouseMoveRedirect=mxUtils.bind(this,function(a){this.fireMouseEvent(mxEvent.MOUSE_MOVE,new mxMouseEvent(a,this.getStateForTouchEvent(a)))}),this.mouseUpRedirect=mxUtils.bind(this,function(a){this.fireMouseEvent(mxEvent.MOUSE_UP,new mxMouseEvent(a,this.getStateForTouchEvent(a)))}),mxEvent.addGestureListeners(this.eventSource,null,this.mouseMoveRedirect,this.mouseUpRedirect));this.isSyntheticEventIgnored(a,b,c)&&(e=!0);if(!mxEvent.isPopupTrigger(this.la [...]
+a!=mxEvent.MOUSE_MOVE&&2==this.lastEvent.detail)return!0;a==mxEvent.MOUSE_UP&&this.isMouseDown?this.isMouseDown=!1:a!=mxEvent.MOUSE_DOWN||this.isMouseDown?!e&&((!mxClient.IS_FF||a!=mxEvent.MOUSE_MOVE)&&this.isMouseDown&&this.isMouseTrigger!=d||a==mxEvent.MOUSE_DOWN&&this.isMouseDown||a==mxEvent.MOUSE_UP&&!this.isMouseDown)&&(e=!0):(this.isMouseDown=!0,this.isMouseTrigger=d);e||a!=mxEvent.MOUSE_DOWN||(this.lastMouseX=b.getX(),this.lastMouseY=b.getY());return e};
+mxGraph.prototype.isSyntheticEventIgnored=function(a,b,c){c=!1;b=mxEvent.isMouseEvent(b.getEvent());this.ignoreMouseEvents&&b&&a!=mxEvent.MOUSE_MOVE?(this.ignoreMouseEvents=a!=mxEvent.MOUSE_UP,c=!0):mxClient.IS_FF&&!b&&a==mxEvent.MOUSE_UP&&(this.ignoreMouseEvents=!0);return c};
+mxGraph.prototype.isEventSourceIgnored=function(a,b){var c=b.getSource(),d=null!=c.nodeName?c.nodeName.toLowerCase():"",e=!mxEvent.isMouseEvent(b.getEvent())||mxEvent.isLeftMouseButton(b.getEvent());return a==mxEvent.MOUSE_DOWN&&e&&("select"==d||"option"==d||"input"==d&&"checkbox"!=c.type&&"radio"!=c.type&&"button"!=c.type&&"submit"!=c.type&&"file"!=c.type)};mxGraph.prototype.getEventState=function(a){return a};
+mxGraph.prototype.fireMouseEvent=function(a,b,c){if(this.isEventSourceIgnored(a,b))null!=this.tooltipHandler&&this.tooltipHandler.hide();else{null==c&&(c=this);b=this.updateMouseEvent(b,a);if(!this.nativeDblClickEnabled&&!mxEvent.isPopupTrigger(b.getEvent())||this.doubleTapEnabled&&mxClient.IS_TOUCH&&(mxEvent.isTouchEvent(b.getEvent())||mxEvent.isPenEvent(b.getEvent()))){var d=(new Date).getTime();if(!mxClient.IS_QUIRKS&&a==mxEvent.MOUSE_DOWN||mxClient.IS_QUIRKS&&a==mxEvent.MOUSE_UP&&!th [...]
+this.lastTouchEvent&&this.lastTouchEvent!=b.getEvent()&&d-this.lastTouchTime<this.doubleTapTimeout&&Math.abs(this.lastTouchX-b.getX())<this.doubleTapTolerance&&Math.abs(this.lastTouchY-b.getY())<this.doubleTapTolerance&&2>this.doubleClickCounter){if(this.doubleClickCounter++,d=!1,a==mxEvent.MOUSE_UP?b.getCell()==this.lastTouchCell&&null!=this.lastTouchCell&&(this.lastTouchTime=0,d=this.lastTouchCell,this.lastTouchCell=null,mxClient.IS_QUIRKS&&b.getSource().fireEvent("ondblclick"),this.db [...]
+d),d=!0):(this.fireDoubleClick=!0,this.lastTouchTime=0),!mxClient.IS_QUIRKS||d){mxEvent.consume(b.getEvent());return}}else{if(null==this.lastTouchEvent||this.lastTouchEvent!=b.getEvent())this.lastTouchCell=b.getCell(),this.lastTouchX=b.getX(),this.lastTouchY=b.getY(),this.lastTouchTime=d,this.lastTouchEvent=b.getEvent(),this.doubleClickCounter=0}else if((this.isMouseDown||a==mxEvent.MOUSE_UP)&&this.fireDoubleClick){this.fireDoubleClick=!1;d=this.lastTouchCell;this.lastTouchCell=null;this [...]
+!1;(null!=d||(mxEvent.isTouchEvent(b.getEvent())||mxEvent.isPenEvent(b.getEvent()))&&(mxClient.IS_GC||mxClient.IS_SF))&&Math.abs(this.lastTouchX-b.getX())<this.doubleTapTolerance&&Math.abs(this.lastTouchY-b.getY())<this.doubleTapTolerance?this.dblClick(b.getEvent(),d):mxEvent.consume(b.getEvent());return}}if(!this.isEventIgnored(a,b,c)){b.state=this.getEventState(b.getState());this.fireEvent(new mxEventObject(mxEvent.FIRE_MOUSE_EVENT,"eventName",a,"event",b));if(mxClient.IS_OP||mxClient. [...]
+mxClient.IS_IE11||mxClient.IS_IE&&mxClient.IS_SVG||b.getEvent().target!=this.container){if(a==mxEvent.MOUSE_MOVE&&this.isMouseDown&&this.autoScroll&&!mxEvent.isMultiTouchEvent(b.getEvent))this.scrollPointToVisible(b.getGraphX(),b.getGraphY(),this.autoExtend);else if(a==mxEvent.MOUSE_UP&&this.ignoreScrollbars&&this.translateToScrollPosition&&(0!=this.container.scrollLeft||0!=this.container.scrollTop)){var d=this.view.scale,e=this.view.translate;this.view.setTranslate(e.x-this.container.sc [...]
+d,e.y-this.container.scrollTop/d);this.container.scrollLeft=0;this.container.scrollTop=0}if(null!=this.mouseListeners)for(d=[c,b],b.getEvent().preventDefault||(b.getEvent().returnValue=!0),e=0;e<this.mouseListeners.length;e++){var f=this.mouseListeners[e];a==mxEvent.MOUSE_DOWN?f.mouseDown.apply(f,d):a==mxEvent.MOUSE_MOVE?f.mouseMove.apply(f,d):a==mxEvent.MOUSE_UP&&f.mouseUp.apply(f,d)}a==mxEvent.MOUSE_UP&&this.click(b)}(mxEvent.isTouchEvent(b.getEvent())||mxEvent.isPenEvent(b.getEvent()) [...]
+this.tapAndHoldEnabled&&!this.tapAndHoldInProgress?(this.tapAndHoldInProgress=!0,this.initialTouchX=b.getGraphX(),this.initialTouchY=b.getGraphY(),this.tapAndHoldThread&&window.clearTimeout(this.tapAndHoldThread),this.tapAndHoldThread=window.setTimeout(mxUtils.bind(this,function(){this.tapAndHoldValid&&this.tapAndHold(b);this.tapAndHoldValid=this.tapAndHoldInProgress=!1}),this.tapAndHoldDelay),this.tapAndHoldValid=!0):a==mxEvent.MOUSE_UP?this.tapAndHoldValid=this.tapAndHoldInProgress=!1: [...]
+(this.tapAndHoldValid=Math.abs(this.initialTouchX-b.getGraphX())<this.tolerance&&Math.abs(this.initialTouchY-b.getGraphY())<this.tolerance);a==mxEvent.MOUSE_DOWN&&this.isEditing()&&!this.cellEditor.isEventSource(b.getEvent())&&this.stopEditing(!this.isInvokesStopCellEditing());this.consumeMouseEvent(a,b,c)}}};mxGraph.prototype.consumeMouseEvent=function(a,b,c){a==mxEvent.MOUSE_DOWN&&mxEvent.isTouchEvent(b.getEvent())&&b.consume(!1)};
+mxGraph.prototype.fireGestureEvent=function(a,b){this.lastTouchTime=0;this.fireEvent(new mxEventObject(mxEvent.GESTURE,"event",a,"cell",b))};
+mxGraph.prototype.destroy=function(){this.destroyed||(this.destroyed=!0,null!=this.tooltipHandler&&this.tooltipHandler.destroy(),null!=this.selectionCellsHandler&&this.selectionCellsHandler.destroy(),null!=this.panningHandler&&this.panningHandler.destroy(),null!=this.popupMenuHandler&&this.popupMenuHandler.destroy(),null!=this.connectionHandler&&this.connectionHandler.destroy(),null!=this.graphHandler&&this.graphHandler.destroy(),null!=this.cellEditor&&this.cellEditor.destroy(),null!=thi [...]
+null!=this.model&&null!=this.graphModelChangeListener&&(this.model.removeListener(this.graphModelChangeListener),this.graphModelChangeListener=null),this.container=null)};function mxCellOverlay(a,b,c,d,e,f){this.image=a;this.tooltip=b;this.align=null!=c?c:this.align;this.verticalAlign=null!=d?d:this.verticalAlign;this.offset=null!=e?e:new mxPoint;this.cursor=null!=f?f:"help"}mxCellOverlay.prototype=new mxEventSource;mxCellOverlay.prototype.constructor=mxCellOverlay;mxCellOverlay.prototyp [...]
+mxCellOverlay.prototype.tooltip=null;mxCellOverlay.prototype.align=mxConstants.ALIGN_RIGHT;mxCellOverlay.prototype.verticalAlign=mxConstants.ALIGN_BOTTOM;mxCellOverlay.prototype.offset=null;mxCellOverlay.prototype.cursor=null;mxCellOverlay.prototype.defaultOverlap=.5;
+mxCellOverlay.prototype.getBounds=function(a){var b=a.view.graph.getModel().isEdge(a.cell),c=a.view.scale,d=this.image.width,e=this.image.height;if(b)if(b=a.absolutePoints,1==b.length%2)b=b[Math.floor(b.length/2)];else{var f=b.length/2;a=b[f-1];b=b[f];b=new mxPoint(a.x+(b.x-a.x)/2,a.y+(b.y-a.y)/2)}else b=new mxPoint,b.x=this.align==mxConstants.ALIGN_LEFT?a.x:this.align==mxConstants.ALIGN_CENTER?a.x+a.width/2:a.x+a.width,b.y=this.verticalAlign==mxConstants.ALIGN_TOP?a.y:this.verticalAlign [...]
+a.y+a.height/2:a.y+a.height;return new mxRectangle(Math.round(b.x-(d*this.defaultOverlap-this.offset.x)*c),Math.round(b.y-(e*this.defaultOverlap-this.offset.y)*c),d*c,e*c)};mxCellOverlay.prototype.toString=function(){return this.tooltip};function mxOutline(a,b){this.source=a;null!=b&&this.init(b)}mxOutline.prototype.source=null;mxOutline.prototype.outline=null;mxOutline.prototype.graphRenderHint=mxConstants.RENDERING_HINT_FASTER;mxOutline.prototype.enabled=!0;mxOutline.prototype.showView [...]
+mxOutline.prototype.border=10;mxOutline.prototype.sizerSize=8;mxOutline.prototype.labelsVisible=!1;mxOutline.prototype.updateOnPan=!1;mxOutline.prototype.sizerImage=null;mxOutline.prototype.minScale=1E-4;mxOutline.prototype.suspended=!1;mxOutline.prototype.forceVmlHandles=8==document.documentMode;mxOutline.prototype.createGraph=function(a){a=new mxGraph(a,this.source.getModel(),this.graphRenderHint,this.source.getStylesheet());a.foldingEnabled=!1;a.autoScroll=!1;return a};
+mxOutline.prototype.init=function(a){this.outline=this.createGraph(a);var b=this.outline.graphModelChanged;this.outline.graphModelChanged=mxUtils.bind(this,function(a){this.suspended||null==this.outline||b.apply(this.outline,arguments)});mxClient.IS_SVG&&(a=this.outline.getView().getCanvas().parentNode,a.setAttribute("shape-rendering","optimizeSpeed"),a.setAttribute("image-rendering","optimizeSpeed"));this.outline.labelsVisible=this.labelsVisible;this.outline.setEnabled(!1);this.updateHa [...]
+function(a,b){this.suspended||this.active||this.update()});this.source.getModel().addListener(mxEvent.CHANGE,this.updateHandler);this.outline.addMouseListener(this);a=this.source.getView();a.addListener(mxEvent.SCALE,this.updateHandler);a.addListener(mxEvent.TRANSLATE,this.updateHandler);a.addListener(mxEvent.SCALE_AND_TRANSLATE,this.updateHandler);a.addListener(mxEvent.DOWN,this.updateHandler);a.addListener(mxEvent.UP,this.updateHandler);mxEvent.addListener(this.source.container,"scroll [...]
+this.panHandler=mxUtils.bind(this,function(a){this.updateOnPan&&this.updateHandler.apply(this,arguments)});this.source.addListener(mxEvent.PAN,this.panHandler);this.refreshHandler=mxUtils.bind(this,function(a){this.outline.setStylesheet(this.source.getStylesheet());this.outline.refresh()});this.source.addListener(mxEvent.REFRESH,this.refreshHandler);this.bounds=new mxRectangle(0,0,0,0);this.selectionBorder=new mxRectangleShape(this.bounds,null,mxConstants.OUTLINE_COLOR,mxConstants.OUTLIN [...]
+this.selectionBorder.dialect=this.outline.dialect;this.forceVmlHandles&&(this.selectionBorder.isHtmlAllowed=function(){return!1});this.selectionBorder.init(this.outline.getView().getOverlayPane());a=mxUtils.bind(this,function(a){var b=mxEvent.getSource(a),c=mxUtils.bind(this,function(a){this.outline.fireMouseEvent(mxEvent.MOUSE_MOVE,new mxMouseEvent(a))}),f=mxUtils.bind(this,function(a){mxEvent.removeGestureListeners(b,null,c,f);this.outline.fireMouseEvent(mxEvent.MOUSE_UP,new mxMouseEve [...]
+mxEvent.addGestureListeners(b,null,c,f);this.outline.fireMouseEvent(mxEvent.MOUSE_DOWN,new mxMouseEvent(a))});mxEvent.addGestureListeners(this.selectionBorder.node,a);this.sizer=this.createSizer();this.forceVmlHandles&&(this.sizer.isHtmlAllowed=function(){return!1});this.sizer.init(this.outline.getView().getOverlayPane());this.enabled&&(this.sizer.node.style.cursor="nwse-resize");mxEvent.addGestureListeners(this.sizer.node,a);this.selectionBorder.node.style.display=this.showViewport?"":" [...]
+this.selectionBorder.node.style.display;this.selectionBorder.node.style.cursor="move";this.update(!1)};mxOutline.prototype.isEnabled=function(){return this.enabled};mxOutline.prototype.setEnabled=function(a){this.enabled=a};mxOutline.prototype.setZoomEnabled=function(a){this.sizer.node.style.visibility=a?"visible":"hidden"};mxOutline.prototype.refresh=function(){this.update(!0)};
+mxOutline.prototype.createSizer=function(){var a=null!=this.sizerImage?new mxImageShape(new mxRectangle(0,0,this.sizerImage.width,this.sizerImage.height),this.sizerImage.src):new mxRectangleShape(new mxRectangle(0,0,this.sizerSize,this.sizerSize),mxConstants.OUTLINE_HANDLE_FILLCOLOR,mxConstants.OUTLINE_HANDLE_STROKECOLOR);a.dialect=this.outline.dialect;return a};mxOutline.prototype.getSourceContainerSize=function(){return new mxRectangle(0,0,this.source.container.scrollWidth,this.source. [...]
+mxOutline.prototype.getOutlineOffset=function(a){return null};mxOutline.prototype.getSourceGraphBounds=function(){return this.source.getGraphBounds()};
+mxOutline.prototype.update=function(a){if(null!=this.source&&null!=this.outline){var b=this.source.view.scale,c=this.getSourceGraphBounds(),c=new mxRectangle(c.x/b+this.source.panDx,c.y/b+this.source.panDy,c.width/b,c.height/b),d=new mxRectangle(0,0,this.source.container.clientWidth/b,this.source.container.clientHeight/b),e=c.clone();e.add(d);d=this.getSourceContainerSize();b=Math.min(Math.max(0,this.outline.container.clientWidth-this.border)/Math.max(d.width/b,e.width),Math.max(0,this.o [...]
+this.border)/Math.max(d.height/b,e.height));d=isNaN(b)?this.minScale:Math.max(this.minScale,b);if(0<d){this.outline.getView().scale!=d&&(this.outline.getView().scale=d,a=!0);b=this.outline.getView();b.currentRoot!=this.source.getView().currentRoot&&b.setCurrentRoot(this.source.getView().currentRoot);var e=this.source.view.translate,f=e.x+this.source.panDx,g=e.y+this.source.panDy,d=this.getOutlineOffset(d);null!=d&&(f+=d.x,g+=d.y);0>c.x&&(f-=c.x);0>c.y&&(g-=c.y);if(b.translate.x!=f||b.tra [...]
+g)b.translate.x=f,b.translate.y=g,a=!0;var c=b.translate,d=this.source.getView().scale,f=d/b.scale,g=1/b.scale,k=this.source.container;this.bounds=new mxRectangle((c.x-e.x-this.source.panDx)/g,(c.y-e.y-this.source.panDy)/g,k.clientWidth/f,k.clientHeight/f);this.bounds.x+=this.source.container.scrollLeft*b.scale/d;this.bounds.y+=this.source.container.scrollTop*b.scale/d;c=this.selectionBorder.bounds;if(c.x!=this.bounds.x||c.y!=this.bounds.y||c.width!=this.bounds.width||c.height!=this.boun [...]
+this.bounds,this.selectionBorder.redraw();c=this.sizer.bounds;b=new mxRectangle(this.bounds.x+this.bounds.width-c.width/2,this.bounds.y+this.bounds.height-c.height/2,c.width,c.height);if(c.x!=b.x||c.y!=b.y||c.width!=b.width||c.height!=b.height)this.sizer.bounds=b,"hidden"!=this.sizer.node.style.visibility&&this.sizer.redraw();a&&this.outline.view.revalidate()}}};
+mxOutline.prototype.mouseDown=function(a,b){if(this.enabled&&this.showViewport){var c=mxEvent.isMouseEvent(b.getEvent())?0:this.source.tolerance,c=this.source.allowHandleBoundsCheck&&(mxClient.IS_IE||0<c)?new mxRectangle(b.getGraphX()-c,b.getGraphY()-c,2*c,2*c):null;this.zoom=b.isSource(this.sizer)||null!=c&&mxUtils.intersects(shape.bounds,c);this.startX=b.getX();this.startY=b.getY();this.active=!0;this.source.useScrollbarsForPanning&&mxUtils.hasScrollbars(this.source.container)?(this.dx [...]
+this.dy0=this.source.container.scrollTop):this.dy0=this.dx0=0}b.consume()};
+mxOutline.prototype.mouseMove=function(a,b){if(this.active){this.selectionBorder.node.style.display=this.showViewport?"":"none";this.sizer.node.style.display=this.selectionBorder.node.style.display;var c=this.getTranslateForEvent(b),d=c.x,e=c.y;if(this.zoom)c=this.source.container,e=d/(c.clientWidth/c.clientHeight),c=new mxRectangle(this.bounds.x,this.bounds.y,Math.max(1,this.bounds.width+d),Math.max(1,this.bounds.height+e)),this.selectionBorder.bounds=c,this.selectionBorder.redraw();els [...]
+c=new mxRectangle(this.bounds.x+d,this.bounds.y+e,this.bounds.width,this.bounds.height);this.selectionBorder.bounds=c;this.selectionBorder.redraw();d=d/f*this.source.getView().scale;e=e/f*this.source.getView().scale;this.source.panGraph(-d-this.dx0,-e-this.dy0)}d=this.sizer.bounds;this.sizer.bounds=new mxRectangle(c.x+c.width-d.width/2,c.y+c.height-d.height/2,d.width,d.height);"hidden"!=this.sizer.node.style.visibility&&this.sizer.redraw();b.consume()}};
+mxOutline.prototype.getTranslateForEvent=function(a){return new mxPoint(a.getX()-this.startX,a.getY()-this.startY)};
+mxOutline.prototype.mouseUp=function(a,b){if(this.active){var c=this.getTranslateForEvent(b),d=c.x,c=c.y;if(0<Math.abs(d)||0<Math.abs(c)){if(this.zoom){var c=this.selectionBorder.bounds.width,e=this.source.getView().scale;this.source.zoomTo(Math.max(this.minScale,e-d*e/c),!1)}else this.source.useScrollbarsForPanning&&mxUtils.hasScrollbars(this.source.container)||(this.source.panGraph(0,0),d/=this.outline.getView().scale,c/=this.outline.getView().scale,e=this.source.getView().translate,th [...]
+d,e.y-c));this.update();b.consume()}this.index=null;this.active=!1}};
+mxOutline.prototype.destroy=function(){null!=this.source&&(this.source.removeListener(this.panHandler),this.source.removeListener(this.refreshHandler),this.source.getModel().removeListener(this.updateHandler),this.source.getView().removeListener(this.updateHandler),mxEvent.addListener(this.source.container,"scroll",this.updateHandler),this.source=null);null!=this.outline&&(this.outline.removeMouseListener(this),this.outline.destroy(),this.outline=null);null!=this.selectionBorder&&(this.s [...]
+this.selectionBorder=null);null!=this.sizer&&(this.sizer.destroy(),this.sizer=null)};function mxMultiplicity(a,b,c,d,e,f,g,k,l,m){this.source=a;this.type=b;this.attr=c;this.value=d;this.min=null!=e?e:0;this.max=null!=f?f:"n";this.validNeighbors=g;this.countError=mxResources.get(k)||k;this.typeError=mxResources.get(l)||l;this.validNeighborsAllowed=null!=m?m:!0}mxMultiplicity.prototype.type=null;mxMultiplicity.prototype.attr=null;mxMultiplicity.prototype.value=null;mxMultiplicity.prototype [...]
+mxMultiplicity.prototype.min=null;mxMultiplicity.prototype.max=null;mxMultiplicity.prototype.validNeighbors=null;mxMultiplicity.prototype.validNeighborsAllowed=!0;mxMultiplicity.prototype.countError=null;mxMultiplicity.prototype.typeError=null;
+mxMultiplicity.prototype.check=function(a,b,c,d,e,f){var g="";if(this.source&&this.checkTerminal(a,c,b)||!this.source&&this.checkTerminal(a,d,b))null!=this.countError&&(this.source&&(0==this.max||e>=this.max)||!this.source&&(0==this.max||f>=this.max))&&(g+=this.countError+"\n"),null!=this.validNeighbors&&null!=this.typeError&&0<this.validNeighbors.length&&(this.checkNeighbors(a,b,c,d)||(g+=this.typeError+"\n"));return 0<g.length?g:null};
+mxMultiplicity.prototype.checkNeighbors=function(a,b,c,d){b=a.model.getValue(c);d=a.model.getValue(d);c=!this.validNeighborsAllowed;for(var e=this.validNeighbors,f=0;f<e.length;f++)if(this.source&&this.checkType(a,d,e[f])){c=this.validNeighborsAllowed;break}else if(!this.source&&this.checkType(a,b,e[f])){c=this.validNeighborsAllowed;break}return c};mxMultiplicity.prototype.checkTerminal=function(a,b,c){b=a.model.getValue(b);return this.checkType(a,b,this.type,this.attr,this.value)};
+mxMultiplicity.prototype.checkType=function(a,b,c,d,e){return null!=b?isNaN(b.nodeType)?b==c:mxUtils.isNode(b,c,d,e):!1};function mxLayoutManager(a){this.undoHandler=mxUtils.bind(this,function(a,c){this.isEnabled()&&this.beforeUndo(c.getProperty("edit"))});this.moveHandler=mxUtils.bind(this,function(a,c){this.isEnabled()&&this.cellsMoved(c.getProperty("cells"),c.getProperty("event"))});this.setGraph(a)}mxLayoutManager.prototype=new mxEventSource;mxLayoutManager.prototype.constructor=mxLa [...]
+mxLayoutManager.prototype.graph=null;mxLayoutManager.prototype.bubbling=!0;mxLayoutManager.prototype.enabled=!0;mxLayoutManager.prototype.updateHandler=null;mxLayoutManager.prototype.moveHandler=null;mxLayoutManager.prototype.isEnabled=function(){return this.enabled};mxLayoutManager.prototype.setEnabled=function(a){this.enabled=a};mxLayoutManager.prototype.isBubbling=function(){return this.bubbling};mxLayoutManager.prototype.setBubbling=function(a){this.bubbling=a};
+mxLayoutManager.prototype.getGraph=function(){return this.graph};mxLayoutManager.prototype.setGraph=function(a){if(null!=this.graph){var b=this.graph.getModel();b.removeListener(this.undoHandler);this.graph.removeListener(this.moveHandler)}this.graph=a;null!=this.graph&&(b=this.graph.getModel(),b.addListener(mxEvent.BEFORE_UNDO,this.undoHandler),this.graph.addListener(mxEvent.MOVE_CELLS,this.moveHandler))};mxLayoutManager.prototype.getLayout=function(a){return null};
+mxLayoutManager.prototype.beforeUndo=function(a){a=this.getCellsForChanges(a.changes);for(var b=this.getGraph().getModel(),c=[],d=0;d<a.length;d++)c=c.concat(b.getDescendants(a[d]));a=c;if(this.isBubbling())for(c=b.getParents(a);0<c.length;)a=a.concat(c),c=b.getParents(c);this.executeLayoutForCells(a)};mxLayoutManager.prototype.executeLayoutForCells=function(a){a=mxUtils.sortCells(a,!0);a=a.concat(a.slice().reverse());this.layoutCells(a)};
+mxLayoutManager.prototype.cellsMoved=function(a,b){if(null!=a&&null!=b)for(var c=mxUtils.convertPoint(this.getGraph().container,mxEvent.getClientX(b),mxEvent.getClientY(b)),d=this.getGraph().getModel(),e=0;e<a.length;e++){var f=d.getParent(a[e]);0>mxUtils.indexOf(a,f)&&(f=this.getLayout(f),null!=f&&f.moveCell(a[e],c.x,c.y))}};
+mxLayoutManager.prototype.getCellsForChanges=function(a){for(var b=new mxDictionary,c=[],d=0;d<a.length;d++){var e=a[d];if(e instanceof mxRootChange)return[];for(var e=this.getCellsForChange(e),f=0;f<e.length;f++)null==e[f]||b.get(e[f])||(b.put(e[f],!0),c.push(e[f]))}return c};
+mxLayoutManager.prototype.getCellsForChange=function(a){var b=this.getGraph().getModel();return a instanceof mxChildChange?[a.child,a.previous,b.getParent(a.child)]:a instanceof mxTerminalChange||a instanceof mxGeometryChange?[a.cell,b.getParent(a.cell)]:a instanceof mxVisibleChange||a instanceof mxStyleChange?[a.cell]:[]};
+mxLayoutManager.prototype.layoutCells=function(a){if(0<a.length){var b=this.getGraph().getModel();b.beginUpdate();try{for(var c=null,d=0;d<a.length;d++)a[d]!=b.getRoot()&&a[d]!=c&&this.executeLayout(this.getLayout(a[d]),a[d])&&(c=a[d]);this.fireEvent(new mxEventObject(mxEvent.LAYOUT_CELLS,"cells",a))}finally{b.endUpdate()}}};mxLayoutManager.prototype.executeLayout=function(a,b){var c=!1;null!=a&&null!=b&&(a.execute(b),c=!0);return c};mxLayoutManager.prototype.destroy=function(){this.setG [...]
+function mxSwimlaneManager(a,b,c,d){this.horizontal=null!=b?b:!0;this.addEnabled=null!=c?c:!0;this.resizeEnabled=null!=d?d:!0;this.addHandler=mxUtils.bind(this,function(a,b){this.isEnabled()&&this.isAddEnabled()&&this.cellsAdded(b.getProperty("cells"))});this.resizeHandler=mxUtils.bind(this,function(a,b){this.isEnabled()&&this.isResizeEnabled()&&this.cellsResized(b.getProperty("cells"))});this.setGraph(a)}mxSwimlaneManager.prototype=new mxEventSource;mxSwimlaneManager.prototype.construct [...]
+mxSwimlaneManager.prototype.graph=null;mxSwimlaneManager.prototype.enabled=!0;mxSwimlaneManager.prototype.horizontal=!0;mxSwimlaneManager.prototype.addEnabled=!0;mxSwimlaneManager.prototype.resizeEnabled=!0;mxSwimlaneManager.prototype.addHandler=null;mxSwimlaneManager.prototype.resizeHandler=null;mxSwimlaneManager.prototype.isEnabled=function(){return this.enabled};mxSwimlaneManager.prototype.setEnabled=function(a){this.enabled=a};mxSwimlaneManager.prototype.isHorizontal=function(){retur [...]
+mxSwimlaneManager.prototype.setHorizontal=function(a){this.horizontal=a};mxSwimlaneManager.prototype.isAddEnabled=function(){return this.addEnabled};mxSwimlaneManager.prototype.setAddEnabled=function(a){this.addEnabled=a};mxSwimlaneManager.prototype.isResizeEnabled=function(){return this.resizeEnabled};mxSwimlaneManager.prototype.setResizeEnabled=function(a){this.resizeEnabled=a};mxSwimlaneManager.prototype.getGraph=function(){return this.graph};
+mxSwimlaneManager.prototype.setGraph=function(a){null!=this.graph&&(this.graph.removeListener(this.addHandler),this.graph.removeListener(this.resizeHandler));this.graph=a;null!=this.graph&&(this.graph.addListener(mxEvent.ADD_CELLS,this.addHandler),this.graph.addListener(mxEvent.CELLS_RESIZED,this.resizeHandler))};mxSwimlaneManager.prototype.isSwimlaneIgnored=function(a){return!this.getGraph().isSwimlane(a)};
+mxSwimlaneManager.prototype.isCellHorizontal=function(a){return this.graph.isSwimlane(a)?(a=this.graph.getCellStyle(a),1==mxUtils.getValue(a,mxConstants.STYLE_HORIZONTAL,1)):!this.isHorizontal()};mxSwimlaneManager.prototype.cellsAdded=function(a){if(null!=a){var b=this.getGraph().getModel();b.beginUpdate();try{for(var c=0;c<a.length;c++)this.isSwimlaneIgnored(a[c])||this.swimlaneAdded(a[c])}finally{b.endUpdate()}}};
+mxSwimlaneManager.prototype.swimlaneAdded=function(a){for(var b=this.getGraph().getModel(),c=b.getParent(a),d=b.getChildCount(c),e=null,f=0;f<d;f++){var g=b.getChildAt(c,f);if(g!=a&&!this.isSwimlaneIgnored(g)&&(e=b.getGeometry(g),null!=e))break}null!=e&&(b=null!=c?this.isCellHorizontal(c):this.horizontal,this.resizeSwimlane(a,e.width,e.height,b))};
+mxSwimlaneManager.prototype.cellsResized=function(a){if(null!=a){var b=this.getGraph().getModel();b.beginUpdate();try{for(var c=0;c<a.length;c++)if(!this.isSwimlaneIgnored(a[c])){var d=b.getGeometry(a[c]);if(null!=d){for(var e=new mxRectangle(0,0,d.width,d.height),f=a[c],g=f;null!=g;){var f=g,g=b.getParent(g),k=this.graph.isSwimlane(g)?this.graph.getStartSize(g):new mxRectangle;e.width+=k.width;e.height+=k.height}var l=null!=g?this.isCellHorizontal(g):this.horizontal;this.resizeSwimlane( [...]
+e.height,l)}}}finally{b.endUpdate()}}};
+mxSwimlaneManager.prototype.resizeSwimlane=function(a,b,c,d){var e=this.getGraph().getModel();e.beginUpdate();try{var f=this.isCellHorizontal(a);if(!this.isSwimlaneIgnored(a)){var g=e.getGeometry(a);null!=g&&(d&&g.height!=c||!d&&g.width!=b)&&(g=g.clone(),d?g.height=c:g.width=b,e.setGeometry(a,g))}var k=this.graph.isSwimlane(a)?this.graph.getStartSize(a):new mxRectangle;b-=k.width;c-=k.height;var l=e.getChildCount(a);for(d=0;d<l;d++){var m=e.getChildAt(a,d);this.resizeSwimlane(m,b,c,f)}}f [...]
+mxSwimlaneManager.prototype.destroy=function(){this.setGraph(null)};
+function mxTemporaryCellStates(a,b,c,d){b=null!=b?b:1;this.view=a;this.oldValidateCellState=a.validateCellState;this.oldBounds=a.getGraphBounds();this.oldStates=a.getStates();this.oldScale=a.getScale();var e=this;a.validateCellState=function(b,c){return null==b||null==d||d(b)?e.oldValidateCellState.apply(a,arguments):null};a.setStates(new mxDictionary);a.setScale(b);if(null!=c){a.resetValidationState();b=null;for(var f=0;f<c.length;f++){var g=a.getBoundingBox(a.validateCellState(a.valida [...]
+null==b?b=g:b.add(g)}a.setGraphBounds(b||new mxRectangle)}}mxTemporaryCellStates.prototype.view=null;mxTemporaryCellStates.prototype.oldStates=null;mxTemporaryCellStates.prototype.oldBounds=null;mxTemporaryCellStates.prototype.oldScale=null;mxTemporaryCellStates.prototype.destroy=function(){this.view.setScale(this.oldScale);this.view.setStates(this.oldStates);this.view.setGraphBounds(this.oldBounds);this.view.validateCellState=this.oldValidateCellState};
+function mxCellStatePreview(a){this.deltas=new mxDictionary;this.graph=a}mxCellStatePreview.prototype.graph=null;mxCellStatePreview.prototype.deltas=null;mxCellStatePreview.prototype.count=0;mxCellStatePreview.prototype.isEmpty=function(){return 0==this.count};
+mxCellStatePreview.prototype.moveState=function(a,b,c,d,e){d=null!=d?d:!0;e=null!=e?e:!0;var f=this.deltas.get(a.cell);null==f?(f={point:new mxPoint(b,c),state:a},this.deltas.put(a.cell,f),this.count++):d?(f.point.x+=b,f.point.y+=c):(f.point.x=b,f.point.y=c);e&&this.addEdges(a);return f.point};
+mxCellStatePreview.prototype.show=function(a){this.deltas.visit(mxUtils.bind(this,function(a,c){this.translateState(c.state,c.point.x,c.point.y)}));this.deltas.visit(mxUtils.bind(this,function(b,c){this.revalidateState(c.state,c.point.x,c.point.y,a)}))};
+mxCellStatePreview.prototype.translateState=function(a,b,c){if(null!=a){var d=this.graph.getModel();if(d.isVertex(a.cell)){a.view.updateCellState(a);var e=d.getGeometry(a.cell);0==b&&0==c||null==e||e.relative&&null==this.deltas.get(a.cell)||(a.x+=b,a.y+=c)}for(var e=d.getChildCount(a.cell),f=0;f<e;f++)this.translateState(a.view.getState(d.getChildAt(a.cell,f)),b,c)}};
+mxCellStatePreview.prototype.revalidateState=function(a,b,c,d){if(null!=a){var e=this.graph.getModel();e.isEdge(a.cell)&&a.view.updateCellState(a);var f=this.graph.getCellGeometry(a.cell),g=a.view.getState(e.getParent(a.cell));0==b&&0==c||null==f||!f.relative||!e.isVertex(a.cell)||null!=g&&!e.isVertex(g.cell)&&null==this.deltas.get(a.cell)||(a.x+=b,a.y+=c);this.graph.cellRenderer.redraw(a);null!=d&&d(a);f=e.getChildCount(a.cell);for(g=0;g<f;g++)this.revalidateState(this.graph.view.getSta [...]
+g)),b,c,d)}};mxCellStatePreview.prototype.addEdges=function(a){for(var b=this.graph.getModel(),c=b.getEdgeCount(a.cell),d=0;d<c;d++){var e=a.view.getState(b.getEdgeAt(a.cell,d));null!=e&&this.moveState(e,0,0)}};function mxConnectionConstraint(a,b,c){this.point=a;this.perimeter=null!=b?b:!0;this.name=c}mxConnectionConstraint.prototype.point=null;mxConnectionConstraint.prototype.perimeter=null;mxConnectionConstraint.prototype.name=null;
+function mxGraphHandler(a){this.graph=a;this.graph.addMouseListener(this);this.panHandler=mxUtils.bind(this,function(){this.updatePreviewShape();this.updateHint()});this.graph.addListener(mxEvent.PAN,this.panHandler);this.escapeHandler=mxUtils.bind(this,function(a,c){this.reset()});this.graph.addListener(mxEvent.ESCAPE,this.escapeHandler)}mxGraphHandler.prototype.graph=null;mxGraphHandler.prototype.maxCells=mxClient.IS_IE?20:50;mxGraphHandler.prototype.enabled=!0;
+mxGraphHandler.prototype.highlightEnabled=!0;mxGraphHandler.prototype.cloneEnabled=!0;mxGraphHandler.prototype.moveEnabled=!0;mxGraphHandler.prototype.guidesEnabled=!1;mxGraphHandler.prototype.guide=null;mxGraphHandler.prototype.currentDx=null;mxGraphHandler.prototype.currentDy=null;mxGraphHandler.prototype.updateCursor=!0;mxGraphHandler.prototype.selectEnabled=!0;mxGraphHandler.prototype.removeCellsFromParent=!0;mxGraphHandler.prototype.connectOnDrop=!1;mxGraphHandler.prototype.scrollOn [...]
+mxGraphHandler.prototype.minimumSize=6;mxGraphHandler.prototype.previewColor="black";mxGraphHandler.prototype.htmlPreview=!1;mxGraphHandler.prototype.shape=null;mxGraphHandler.prototype.scaleGrid=!1;mxGraphHandler.prototype.rotationEnabled=!0;mxGraphHandler.prototype.isEnabled=function(){return this.enabled};mxGraphHandler.prototype.setEnabled=function(a){this.enabled=a};mxGraphHandler.prototype.isCloneEnabled=function(){return this.cloneEnabled};
+mxGraphHandler.prototype.setCloneEnabled=function(a){this.cloneEnabled=a};mxGraphHandler.prototype.isMoveEnabled=function(){return this.moveEnabled};mxGraphHandler.prototype.setMoveEnabled=function(a){this.moveEnabled=a};mxGraphHandler.prototype.isSelectEnabled=function(){return this.selectEnabled};mxGraphHandler.prototype.setSelectEnabled=function(a){this.selectEnabled=a};mxGraphHandler.prototype.isRemoveCellsFromParent=function(){return this.removeCellsFromParent};
+mxGraphHandler.prototype.setRemoveCellsFromParent=function(a){this.removeCellsFromParent=a};mxGraphHandler.prototype.getInitialCellForEvent=function(a){return a.getCell()};mxGraphHandler.prototype.isDelayedSelection=function(a,b){return this.graph.isCellSelected(a)};mxGraphHandler.prototype.consumeMouseEvent=function(a,b){b.consume()};
+mxGraphHandler.prototype.mouseDown=function(a,b){if(!b.isConsumed()&&this.isEnabled()&&this.graph.isEnabled()&&null!=b.getState()&&!mxEvent.isMultiTouchEvent(b.getEvent())){var c=this.getInitialCellForEvent(b);this.delayedSelection=this.isDelayedSelection(c,b);this.cell=null;this.isSelectEnabled()&&!this.delayedSelection&&this.graph.selectCellForEvent(c,b.getEvent());if(this.isMoveEnabled()){var d=this.graph.model,e=d.getGeometry(c);this.graph.isCellMovable(c)&&(!d.isEdge(c)||1<this.grap [...]
+null!=e.points&&0<e.points.length||null==d.getTerminal(c,!0)||null==d.getTerminal(c,!1)||this.graph.allowDanglingEdges||this.graph.isCloneEvent(b.getEvent())&&this.graph.isCellsCloneable())?this.start(c,b.getX(),b.getY()):this.delayedSelection&&(this.cell=c);this.cellWasClicked=!0;this.consumeMouseEvent(mxEvent.MOUSE_DOWN,b)}}};
+mxGraphHandler.prototype.getGuideStates=function(){var a=this.graph.getDefaultParent(),b=this.graph.getModel(),c=mxUtils.bind(this,function(a){return null!=this.graph.view.getState(a)&&b.isVertex(a)&&null!=b.getGeometry(a)&&!b.getGeometry(a).relative});return this.graph.view.getCellStates(b.filterDescendants(c,a))};mxGraphHandler.prototype.getCells=function(a){return!this.delayedSelection&&this.graph.isCellMovable(a)?[a]:this.graph.getMovableCells(this.graph.getSelectionCells())};
+mxGraphHandler.prototype.getPreviewBounds=function(a){a=this.getBoundingBox(a);null!=a&&(a.width=Math.max(0,a.width-1),a.height=Math.max(0,a.height-1),a.width<this.minimumSize?(a.x-=(this.minimumSize-a.width)/2,a.width=this.minimumSize):(a.x=Math.round(a.x),a.width=Math.ceil(a.width)),a.height<this.minimumSize?(a.y-=(this.minimumSize-a.height)/2,a.height=this.minimumSize):(a.y=Math.round(a.y),a.height=Math.ceil(a.height)));return a};
+mxGraphHandler.prototype.getBoundingBox=function(a){var b=null;if(null!=a&&0<a.length)for(var c=this.graph.getModel(),d=0;d<a.length;d++)if(c.isVertex(a[d])||c.isEdge(a[d])){var e=this.graph.view.getState(a[d]);if(null!=e){var f=e;c.isVertex(a[d])&&null!=e.shape&&null!=e.shape.boundingBox&&(f=e.shape.boundingBox);null==b?b=mxRectangle.fromRectangle(f):b.add(f)}}return b};
+mxGraphHandler.prototype.createPreviewShape=function(a){a=new mxRectangleShape(a,null,this.previewColor);a.isDashed=!0;this.htmlPreview?(a.dialect=mxConstants.DIALECT_STRICTHTML,a.init(this.graph.container)):(a.dialect=this.graph.dialect!=mxConstants.DIALECT_SVG?mxConstants.DIALECT_VML:mxConstants.DIALECT_SVG,a.init(this.graph.getView().getOverlayPane()),a.pointerEvents=!1,mxClient.IS_IOS&&(a.getSvgScreenOffset=function(){return 0}));return a};
+mxGraphHandler.prototype.start=function(a,b,c){this.cell=a;this.first=mxUtils.convertPoint(this.graph.container,b,c);this.cells=this.getCells(this.cell);this.bounds=this.graph.getView().getBounds(this.cells);this.pBounds=this.getPreviewBounds(this.cells);this.guidesEnabled&&(this.guide=new mxGuide(this.graph,this.getGuideStates()))};mxGraphHandler.prototype.useGuidesForEvent=function(a){return null!=this.guide?this.guide.isEnabledForEvent(a.getEvent()):!0};
+mxGraphHandler.prototype.snap=function(a){var b=this.scaleGrid?this.graph.view.scale:1;a.x=this.graph.snap(a.x/b)*b;a.y=this.graph.snap(a.y/b)*b;return a};mxGraphHandler.prototype.getDelta=function(a){a=mxUtils.convertPoint(this.graph.container,a.getX(),a.getY());var b=this.graph.view.scale;return new mxPoint(this.roundLength((a.x-this.first.x)/b)*b,this.roundLength((a.y-this.first.y)/b)*b)};mxGraphHandler.prototype.updateHint=function(a){};mxGraphHandler.prototype.removeHint=function(){};
+mxGraphHandler.prototype.roundLength=function(a){return Math.round(a)};
+mxGraphHandler.prototype.mouseMove=function(a,b){var c=this.graph;if(!b.isConsumed()&&c.isMouseDown&&null!=this.cell&&null!=this.first&&null!=this.bounds)if(mxEvent.isMultiTouchEvent(b.getEvent()))this.reset();else{var d=this.getDelta(b),e=d.x,d=d.y,f=c.tolerance;if(null!=this.shape||Math.abs(e)>f||Math.abs(d)>f){null==this.highlight&&(this.highlight=new mxCellHighlight(this.graph,mxConstants.DROP_TARGET_COLOR,3));null==this.shape&&(this.shape=this.createPreviewShape(this.bounds));var g= [...]
+f=!0;if(null!=this.guide&&this.useGuidesForEvent(b))d=this.guide.move(this.bounds,new mxPoint(e,d),g),f=!1,e=d.x,d=d.y;else if(g)var k=c.getView().translate,l=c.getView().scale,g=this.bounds.x-(c.snap(this.bounds.x/l-k.x)+k.x)*l,k=this.bounds.y-(c.snap(this.bounds.y/l-k.y)+k.y)*l,d=this.snap(new mxPoint(e,d)),e=d.x-g,d=d.y-k;null!=this.guide&&f&&this.guide.hide();c.isConstrainedEvent(b.getEvent())&&(Math.abs(e)>Math.abs(d)?d=0:e=0);this.currentDx=e;this.currentDy=d;this.updatePreviewShap [...]
+d=b.getCell();g=c.isCloneEvent(b.getEvent())&&c.isCellsCloneable()&&this.isCloneEnabled();c.isDropEnabled()&&this.highlightEnabled&&(f=c.getDropTarget(this.cells,b.getEvent(),d,g));e=c.getView().getState(f);k=!1;null==e||c.model.getParent(this.cell)==f&&!g?(this.target=null,this.connectOnDrop&&null!=d&&1==this.cells.length&&c.getModel().isVertex(d)&&c.isCellConnectable(d)&&(e=c.getView().getState(d),null!=e&&(c=null==c.getEdgeValidationError(null,this.cell,d)?mxConstants.VALID_COLOR:mxCo [...]
+this.setHighlightColor(c),k=!0))):(this.target!=f&&(this.target=f,this.setHighlightColor(mxConstants.DROP_TARGET_COLOR)),k=!0);null!=e&&k?this.highlight.highlight(e):this.highlight.hide()}this.updateHint(b);this.consumeMouseEvent(mxEvent.MOUSE_MOVE,b);mxEvent.consume(b.getEvent())}else!this.isMoveEnabled()&&!this.isCloneEnabled()||!this.updateCursor||b.isConsumed()||null==b.getState()||c.isMouseDown||(e=c.getCursorForMouseEvent(b),null==e&&c.isEnabled()&&c.isCellMovable(b.getCell())&&(e= [...]
+mxConstants.CURSOR_MOVABLE_EDGE:mxConstants.CURSOR_MOVABLE_VERTEX),null!=b.sourceState&&b.sourceState.setCursor(e))};mxGraphHandler.prototype.updatePreviewShape=function(){null!=this.shape&&(this.shape.bounds=new mxRectangle(Math.round(this.pBounds.x+this.currentDx-this.graph.panDx),Math.round(this.pBounds.y+this.currentDy-this.graph.panDy),this.pBounds.width,this.pBounds.height),this.shape.redraw())};mxGraphHandler.prototype.setHighlightColor=function(a){null!=this.highlight&&this.highl [...]
+mxGraphHandler.prototype.mouseUp=function(a,b){if(!b.isConsumed()){var c=this.graph;if(null!=this.cell&&null!=this.first&&null!=this.shape&&null!=this.currentDx&&null!=this.currentDy){var d=b.getCell();if(this.connectOnDrop&&null==this.target&&null!=d&&c.getModel().isVertex(d)&&c.isCellConnectable(d)&&c.isEdgeValid(null,this.cell,d))c.connectionHandler.connect(this.cell,d,b.getEvent());else{var d=c.isCloneEvent(b.getEvent())&&c.isCellsCloneable()&&this.isCloneEnabled(),e=c.getView().scal [...]
+e),e=this.roundLength(this.currentDy/e),g=this.target;c.isSplitEnabled()&&c.isSplitTarget(g,this.cells,b.getEvent())?c.splitEdge(g,this.cells,null,f,e):this.moveCells(this.cells,f,e,d,this.target,b.getEvent())}}else this.isSelectEnabled()&&this.delayedSelection&&null!=this.cell&&this.selectDelayed(b)}this.cellWasClicked&&this.consumeMouseEvent(mxEvent.MOUSE_UP,b);this.reset()};
+mxGraphHandler.prototype.selectDelayed=function(a){this.graph.isCellSelected(this.cell)&&this.graph.popupMenuHandler.isPopupTrigger(a)||this.graph.selectCellForEvent(this.cell,a.getEvent())};mxGraphHandler.prototype.reset=function(){this.destroyShapes();this.removeHint();this.delayedSelection=this.cellWasClicked=!1;this.target=this.cell=this.first=this.guides=this.currentDy=this.currentDx=null};
+mxGraphHandler.prototype.shouldRemoveCellsFromParent=function(a,b,c){if(this.graph.getModel().isVertex(a)&&(a=this.graph.getView().getState(a),null!=a)){c=mxUtils.convertPoint(this.graph.container,mxEvent.getClientX(c),mxEvent.getClientY(c));var d=mxUtils.toRadians(mxUtils.getValue(a.style,mxConstants.STYLE_ROTATION)||0);if(0!=d){b=Math.cos(-d);var d=Math.sin(-d),e=new mxPoint(a.getCenterX(),a.getCenterY());c=mxUtils.getRotatedPoint(c,b,d,e)}return!mxUtils.contains(a,c.x,c.y)}return!1};
+mxGraphHandler.prototype.moveCells=function(a,b,c,d,e,f){d&&(a=this.graph.getCloneableCells(a));null==e&&this.isRemoveCellsFromParent()&&this.shouldRemoveCellsFromParent(this.graph.getModel().getParent(this.cell),a,f)&&(e=this.graph.getDefaultParent());a=this.graph.moveCells(a,b-this.graph.panDx/this.graph.view.scale,c-this.graph.panDy/this.graph.view.scale,d,e,f);this.isSelectEnabled()&&this.scrollOnMove&&this.graph.scrollCellToVisible(a[0]);d&&this.graph.setSelectionCells(a)};
+mxGraphHandler.prototype.destroyShapes=function(){null!=this.shape&&(this.shape.destroy(),this.shape=null);null!=this.guide&&(this.guide.destroy(),this.guide=null);null!=this.highlight&&(this.highlight.destroy(),this.highlight=null)};mxGraphHandler.prototype.destroy=function(){this.graph.removeMouseListener(this);this.graph.removeListener(this.panHandler);null!=this.escapeHandler&&(this.graph.removeListener(this.escapeHandler),this.escapeHandler=null);this.destroyShapes();this.removeHint()};
+function mxPanningHandler(a){null!=a&&(this.graph=a,this.graph.addMouseListener(this),this.forcePanningHandler=mxUtils.bind(this,function(a,c){var b=c.getProperty("eventName"),e=c.getProperty("event");b==mxEvent.MOUSE_DOWN&&this.isForcePanningEvent(e)&&(this.start(e),this.active=!0,this.fireEvent(new mxEventObject(mxEvent.PAN_START,"event",e)),e.consume())}),this.graph.addListener(mxEvent.FIRE_MOUSE_EVENT,this.forcePanningHandler),this.gestureHandler=mxUtils.bind(this,function(a,c){if(th [...]
+c.getProperty("event");mxEvent.isConsumed(b)||"gesturestart"!=b.type?"gestureend"==b.type&&null!=this.initialScale&&(this.initialScale=null):(this.initialScale=this.graph.view.scale,this.active||null==this.mouseDownEvent||(this.start(this.mouseDownEvent),this.mouseDownEvent=null));if(null!=this.initialScale){var e=Math.round(this.initialScale*b.scale*100)/100;null!=this.minScale&&(e=Math.max(this.minScale,e));null!=this.maxScale&&(e=Math.min(this.maxScale,e));this.graph.view.scale!=e&&(t [...]
+mxEvent.consume(b))}}}),this.graph.addListener(mxEvent.GESTURE,this.gestureHandler))}mxPanningHandler.prototype=new mxEventSource;mxPanningHandler.prototype.constructor=mxPanningHandler;mxPanningHandler.prototype.graph=null;mxPanningHandler.prototype.useLeftButtonForPanning=!1;mxPanningHandler.prototype.usePopupTrigger=!0;mxPanningHandler.prototype.ignoreCell=!1;mxPanningHandler.prototype.previewEnabled=!0;mxPanningHandler.prototype.useGrid=!1;mxPanningHandler.prototype.panningEnabled=!0;
+mxPanningHandler.prototype.pinchEnabled=!0;mxPanningHandler.prototype.maxScale=8;mxPanningHandler.prototype.minScale=.01;mxPanningHandler.prototype.dx=null;mxPanningHandler.prototype.dy=null;mxPanningHandler.prototype.startX=0;mxPanningHandler.prototype.startY=0;mxPanningHandler.prototype.isActive=function(){return this.active||null!=this.initialScale};mxPanningHandler.prototype.isPanningEnabled=function(){return this.panningEnabled};
+mxPanningHandler.prototype.setPanningEnabled=function(a){this.panningEnabled=a};mxPanningHandler.prototype.isPinchEnabled=function(){return this.pinchEnabled};mxPanningHandler.prototype.setPinchEnabled=function(a){this.pinchEnabled=a};mxPanningHandler.prototype.isPanningTrigger=function(a){var b=a.getEvent();return this.useLeftButtonForPanning&&null==a.getState()&&mxEvent.isLeftMouseButton(b)||mxEvent.isControlDown(b)&&mxEvent.isShiftDown(b)||this.usePopupTrigger&&mxEvent.isPopupTrigger(b)};
+mxPanningHandler.prototype.isForcePanningEvent=function(a){return this.ignoreCell||mxEvent.isMultiTouchEvent(a.getEvent())};mxPanningHandler.prototype.mouseDown=function(a,b){this.mouseDownEvent=b;!b.isConsumed()&&this.isPanningEnabled()&&!this.active&&this.isPanningTrigger(b)&&(this.start(b),this.consumePanningTrigger(b))};
+mxPanningHandler.prototype.start=function(a){this.dx0=-this.graph.container.scrollLeft;this.dy0=-this.graph.container.scrollTop;this.startX=a.getX();this.startY=a.getY();this.dy=this.dx=null;this.panningTrigger=!0};mxPanningHandler.prototype.consumePanningTrigger=function(a){a.consume()};
+mxPanningHandler.prototype.mouseMove=function(a,b){this.dx=b.getX()-this.startX;this.dy=b.getY()-this.startY;if(this.active)this.previewEnabled&&(this.useGrid&&(this.dx=this.graph.snap(this.dx),this.dy=this.graph.snap(this.dy)),this.graph.panGraph(this.dx+this.dx0,this.dy+this.dy0)),this.fireEvent(new mxEventObject(mxEvent.PAN,"event",b));else if(this.panningTrigger){var c=this.active;this.active=Math.abs(this.dx)>this.graph.tolerance||Math.abs(this.dy)>this.graph.tolerance;!c&&this.acti [...]
+"event",b))}(this.active||this.panningTrigger)&&b.consume()};
+mxPanningHandler.prototype.mouseUp=function(a,b){if(this.active){if(null!=this.dx&&null!=this.dy){if(!this.graph.useScrollbarsForPanning||!mxUtils.hasScrollbars(this.graph.container)){var c=this.graph.getView().scale,d=this.graph.getView().translate;this.graph.panGraph(0,0);this.panGraph(d.x+this.dx/c,d.y+this.dy/c)}b.consume()}this.fireEvent(new mxEventObject(mxEvent.PAN_END,"event",b))}this.panningTrigger=!1;this.mouseDownEvent=null;this.active=!1;this.dy=this.dx=null};
+mxPanningHandler.prototype.panGraph=function(a,b){this.graph.getView().setTranslate(a,b)};mxPanningHandler.prototype.destroy=function(){this.graph.removeMouseListener(this);this.graph.removeListener(this.forcePanningHandler);this.graph.removeListener(this.gestureHandler)};
+function mxPopupMenuHandler(a,b){null!=a&&(this.graph=a,this.factoryMethod=b,this.graph.addMouseListener(this),this.gestureHandler=mxUtils.bind(this,function(a,b){this.inTolerance=!1}),this.graph.addListener(mxEvent.GESTURE,this.gestureHandler),this.init())}mxPopupMenuHandler.prototype=new mxPopupMenu;mxPopupMenuHandler.prototype.constructor=mxPopupMenuHandler;mxPopupMenuHandler.prototype.graph=null;mxPopupMenuHandler.prototype.selectOnPopup=!0;
+mxPopupMenuHandler.prototype.clearSelectionOnBackground=!0;mxPopupMenuHandler.prototype.triggerX=null;mxPopupMenuHandler.prototype.triggerY=null;mxPopupMenuHandler.prototype.screenX=null;mxPopupMenuHandler.prototype.screenY=null;mxPopupMenuHandler.prototype.init=function(){mxPopupMenu.prototype.init.apply(this);mxEvent.addGestureListeners(this.div,mxUtils.bind(this,function(a){this.graph.tooltipHandler.hide()}))};mxPopupMenuHandler.prototype.isSelectOnPopup=function(a){return this.select [...]
+mxPopupMenuHandler.prototype.mouseDown=function(a,b){this.isEnabled()&&!mxEvent.isMultiTouchEvent(b.getEvent())&&(this.hideMenu(),this.triggerX=b.getGraphX(),this.triggerY=b.getGraphY(),this.screenX=mxEvent.getMainEvent(b.getEvent()).screenX,this.screenY=mxEvent.getMainEvent(b.getEvent()).screenY,this.popupTrigger=this.isPopupTrigger(b),this.inTolerance=!0)};
+mxPopupMenuHandler.prototype.mouseMove=function(a,b){this.inTolerance&&null!=this.screenX&&null!=this.screenY&&(Math.abs(mxEvent.getMainEvent(b.getEvent()).screenX-this.screenX)>this.graph.tolerance||Math.abs(mxEvent.getMainEvent(b.getEvent()).screenY-this.screenY)>this.graph.tolerance)&&(this.inTolerance=!1)};
+mxPopupMenuHandler.prototype.mouseUp=function(a,b){if(this.popupTrigger&&this.inTolerance&&null!=this.triggerX&&null!=this.triggerY){var c=this.getCellForPopupEvent(b);this.graph.isEnabled()&&this.isSelectOnPopup(b)&&null!=c&&!this.graph.isCellSelected(c)?this.graph.setSelectionCell(c):this.clearSelectionOnBackground&&null==c&&this.graph.clearSelection();this.graph.tooltipHandler.hide();var d=mxUtils.getScrollOrigin();this.popup(b.getX()+d.x+1,b.getY()+d.y+1,c,b.getEvent());b.consume()}t [...]
+this.popupTrigger=!1};mxPopupMenuHandler.prototype.getCellForPopupEvent=function(a){return a.getCell()};mxPopupMenuHandler.prototype.destroy=function(){this.graph.removeMouseListener(this);this.graph.removeListener(this.gestureHandler);mxPopupMenu.prototype.destroy.apply(this)};
+function mxCellMarker(a,b,c,d){mxEventSource.call(this);null!=a&&(this.graph=a,this.validColor=null!=b?b:mxConstants.DEFAULT_VALID_COLOR,this.invalidColor=null!=b?c:mxConstants.DEFAULT_INVALID_COLOR,this.hotspot=null!=d?d:mxConstants.DEFAULT_HOTSPOT,this.highlight=new mxCellHighlight(a))}mxUtils.extend(mxCellMarker,mxEventSource);mxCellMarker.prototype.graph=null;mxCellMarker.prototype.enabled=!0;mxCellMarker.prototype.hotspot=mxConstants.DEFAULT_HOTSPOT;mxCellMarker.prototype.hotspotEna [...]
+mxCellMarker.prototype.validColor=null;mxCellMarker.prototype.invalidColor=null;mxCellMarker.prototype.currentColor=null;mxCellMarker.prototype.validState=null;mxCellMarker.prototype.markedState=null;mxCellMarker.prototype.setEnabled=function(a){this.enabled=a};mxCellMarker.prototype.isEnabled=function(){return this.enabled};mxCellMarker.prototype.setHotspot=function(a){this.hotspot=a};mxCellMarker.prototype.getHotspot=function(){return this.hotspot};
+mxCellMarker.prototype.setHotspotEnabled=function(a){this.hotspotEnabled=a};mxCellMarker.prototype.isHotspotEnabled=function(){return this.hotspotEnabled};mxCellMarker.prototype.hasValidState=function(){return null!=this.validState};mxCellMarker.prototype.getValidState=function(){return this.validState};mxCellMarker.prototype.getMarkedState=function(){return this.markedState};mxCellMarker.prototype.reset=function(){this.validState=null;null!=this.markedState&&(this.markedState=null,this. [...]
+mxCellMarker.prototype.process=function(a){var b=null;this.isEnabled()&&(b=this.getState(a),this.setCurrentState(b,a));return b};mxCellMarker.prototype.setCurrentState=function(a,b,c){var d=null!=a?this.isValidState(a):!1;c=null!=c?c:this.getMarkerColor(b.getEvent(),a,d);this.validState=d?a:null;if(a!=this.markedState||c!=this.currentColor)this.currentColor=c,null!=a&&null!=this.currentColor?(this.markedState=a,this.mark()):null!=this.markedState&&(this.markedState=null,this.unmark())};
+mxCellMarker.prototype.markCell=function(a,b){var c=this.graph.getView().getState(a);null!=c&&(this.currentColor=null!=b?b:this.validColor,this.markedState=c,this.mark())};mxCellMarker.prototype.mark=function(){this.highlight.setHighlightColor(this.currentColor);this.highlight.highlight(this.markedState);this.fireEvent(new mxEventObject(mxEvent.MARK,"state",this.markedState))};mxCellMarker.prototype.unmark=function(){this.mark()};mxCellMarker.prototype.isValidState=function(a){return!0};
+mxCellMarker.prototype.getMarkerColor=function(a,b,c){return c?this.validColor:this.invalidColor};mxCellMarker.prototype.getState=function(a){var b=this.graph.getView(),c=this.getCell(a),b=this.getStateToMark(b.getState(c));return null!=b&&this.intersects(b,a)?b:null};mxCellMarker.prototype.getCell=function(a){return a.getCell()};mxCellMarker.prototype.getStateToMark=function(a){return a};
+mxCellMarker.prototype.intersects=function(a,b){return this.hotspotEnabled?mxUtils.intersectsHotspot(a,b.getGraphX(),b.getGraphY(),this.hotspot,mxConstants.MIN_HOTSPOT_SIZE,mxConstants.MAX_HOTSPOT_SIZE):!0};mxCellMarker.prototype.destroy=function(){this.graph.getView().removeListener(this.resetHandler);this.graph.getModel().removeListener(this.resetHandler);this.highlight.destroy()};
+function mxSelectionCellsHandler(a){mxEventSource.call(this);this.graph=a;this.handlers=new mxDictionary;this.graph.addMouseListener(this);this.refreshHandler=mxUtils.bind(this,function(a,c){this.isEnabled()&&this.refresh()});this.graph.getSelectionModel().addListener(mxEvent.CHANGE,this.refreshHandler);this.graph.getModel().addListener(mxEvent.CHANGE,this.refreshHandler);this.graph.getView().addListener(mxEvent.SCALE,this.refreshHandler);this.graph.getView().addListener(mxEvent.TRANSLAT [...]
+this.graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE,this.refreshHandler);this.graph.getView().addListener(mxEvent.DOWN,this.refreshHandler);this.graph.getView().addListener(mxEvent.UP,this.refreshHandler)}mxUtils.extend(mxSelectionCellsHandler,mxEventSource);mxSelectionCellsHandler.prototype.graph=null;mxSelectionCellsHandler.prototype.enabled=!0;mxSelectionCellsHandler.prototype.refreshHandler=null;mxSelectionCellsHandler.prototype.maxHandlers=100;
+mxSelectionCellsHandler.prototype.handlers=null;mxSelectionCellsHandler.prototype.isEnabled=function(){return this.enabled};mxSelectionCellsHandler.prototype.setEnabled=function(a){this.enabled=a};mxSelectionCellsHandler.prototype.getHandler=function(a){return this.handlers.get(a)};mxSelectionCellsHandler.prototype.reset=function(){this.handlers.visit(function(a,b){b.reset.apply(b)})};
+mxSelectionCellsHandler.prototype.refresh=function(){var a=this.handlers;this.handlers=new mxDictionary;for(var b=this.graph.getSelectionCells(),c=0;c<b.length;c++){var d=this.graph.view.getState(b[c]);if(null!=d){var e=a.remove(b[c]);null!=e&&(e.state!=d?(e.destroy(),e=null):(null!=e.refresh&&e.refresh(),e.redraw()));null==e&&(e=this.graph.createHandler(d),this.fireEvent(new mxEventObject(mxEvent.ADD,"state",d)));null!=e&&this.handlers.put(b[c],e)}}a.visit(mxUtils.bind(this,function(a,b [...]
+"state",b.state));b.destroy()}))};mxSelectionCellsHandler.prototype.updateHandler=function(a){var b=this.handlers.remove(a.cell);null!=b&&(b.destroy(),b=this.graph.createHandler(a),null!=b&&this.handlers.put(a.cell,b))};mxSelectionCellsHandler.prototype.mouseDown=function(a,b){if(this.graph.isEnabled()&&this.isEnabled()){var c=[a,b];this.handlers.visit(function(a,b){b.mouseDown.apply(b,c)})}};
+mxSelectionCellsHandler.prototype.mouseMove=function(a,b){if(this.graph.isEnabled()&&this.isEnabled()){var c=[a,b];this.handlers.visit(function(a,b){b.mouseMove.apply(b,c)})}};mxSelectionCellsHandler.prototype.mouseUp=function(a,b){if(this.graph.isEnabled()&&this.isEnabled()){var c=[a,b];this.handlers.visit(function(a,b){b.mouseUp.apply(b,c)})}};
+mxSelectionCellsHandler.prototype.destroy=function(){this.graph.removeMouseListener(this);null!=this.refreshHandler&&(this.graph.getSelectionModel().removeListener(this.refreshHandler),this.graph.getModel().removeListener(this.refreshHandler),this.graph.getView().removeListener(this.refreshHandler),this.refreshHandler=null)};
+function mxConnectionHandler(a,b){mxEventSource.call(this);null!=a&&(this.graph=a,this.factoryMethod=b,this.init(),this.escapeHandler=mxUtils.bind(this,function(a,b){this.reset()}),this.graph.addListener(mxEvent.ESCAPE,this.escapeHandler))}mxUtils.extend(mxConnectionHandler,mxEventSource);mxConnectionHandler.prototype.graph=null;mxConnectionHandler.prototype.factoryMethod=!0;mxConnectionHandler.prototype.moveIconFront=!1;mxConnectionHandler.prototype.moveIconBack=!1;
+mxConnectionHandler.prototype.connectImage=null;mxConnectionHandler.prototype.targetConnectImage=!1;mxConnectionHandler.prototype.enabled=!0;mxConnectionHandler.prototype.select=!0;mxConnectionHandler.prototype.createTarget=!1;mxConnectionHandler.prototype.marker=null;mxConnectionHandler.prototype.constraintHandler=null;mxConnectionHandler.prototype.error=null;mxConnectionHandler.prototype.waypointsEnabled=!1;mxConnectionHandler.prototype.ignoreMouseDown=!1;mxConnectionHandler.prototype. [...]
+mxConnectionHandler.prototype.connectIconOffset=new mxPoint(0,mxConstants.TOOLTIP_VERTICAL_OFFSET);mxConnectionHandler.prototype.edgeState=null;mxConnectionHandler.prototype.changeHandler=null;mxConnectionHandler.prototype.drillHandler=null;mxConnectionHandler.prototype.mouseDownCounter=0;mxConnectionHandler.prototype.movePreviewAway=mxClient.IS_VML;mxConnectionHandler.prototype.outlineConnect=!1;mxConnectionHandler.prototype.livePreview=!1;mxConnectionHandler.prototype.cursor=null;
+mxConnectionHandler.prototype.insertBeforeSource=!1;mxConnectionHandler.prototype.isEnabled=function(){return this.enabled};mxConnectionHandler.prototype.setEnabled=function(a){this.enabled=a};mxConnectionHandler.prototype.isInsertBefore=function(a,b,c,d,e){return this.insertBeforeSource&&b!=c};mxConnectionHandler.prototype.isCreateTarget=function(a){return this.createTarget};mxConnectionHandler.prototype.setCreateTarget=function(a){this.createTarget=a};
+mxConnectionHandler.prototype.createShape=function(){var a=this.livePreview&&null!=this.edgeState?this.graph.cellRenderer.createShape(this.edgeState):new mxPolyline([],mxConstants.INVALID_COLOR);a.dialect=this.graph.dialect!=mxConstants.DIALECT_SVG?mxConstants.DIALECT_VML:mxConstants.DIALECT_SVG;a.scale=this.graph.view.scale;a.pointerEvents=!1;a.isDashed=!0;a.init(this.graph.getView().getOverlayPane());mxEvent.redirectMouseEvents(a.node,this.graph,null);return a};
+mxConnectionHandler.prototype.init=function(){this.graph.addMouseListener(this);this.marker=this.createMarker();this.constraintHandler=new mxConstraintHandler(this.graph);this.changeHandler=mxUtils.bind(this,function(a){null!=this.iconState&&(this.iconState=this.graph.getView().getState(this.iconState.cell));null!=this.iconState?(this.redrawIcons(this.icons,this.iconState),this.constraintHandler.reset()):null!=this.previous&&null==this.graph.view.getState(this.previous.cell)&&this.reset( [...]
+this.changeHandler);this.graph.getView().addListener(mxEvent.SCALE,this.changeHandler);this.graph.getView().addListener(mxEvent.TRANSLATE,this.changeHandler);this.graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE,this.changeHandler);this.drillHandler=mxUtils.bind(this,function(a){this.reset()});this.graph.addListener(mxEvent.START_EDITING,this.drillHandler);this.graph.getView().addListener(mxEvent.DOWN,this.drillHandler);this.graph.getView().addListener(mxEvent.UP,this.drillHandler)};
+mxConnectionHandler.prototype.isConnectableCell=function(a){return!0};
+mxConnectionHandler.prototype.createMarker=function(){var a=new mxCellMarker(this.graph);a.hotspotEnabled=!0;a.getCell=mxUtils.bind(this,function(b){var c=mxCellMarker.prototype.getCell.apply(a,arguments);this.error=null;null==c&&null!=this.currentPoint&&(c=this.graph.getCellAt(this.currentPoint.x,this.currentPoint.y));if(null!=c&&!this.graph.isCellConnectable(c)){var d=this.graph.getModel().getParent(c);this.graph.getModel().isVertex(d)&&this.graph.isCellConnectable(d)&&(c=d)}if(this.gr [...]
+null!=this.currentPoint&&this.graph.hitsSwimlaneContent(c,this.currentPoint.x,this.currentPoint.y)||!this.isConnectableCell(c))c=null;null!=c?this.isConnecting()?null!=this.previous&&(this.error=this.validateConnection(this.previous.cell,c),null!=this.error&&0==this.error.length&&(c=null,this.isCreateTarget(b.getEvent())&&(this.error=null))):this.isValidSource(c,b)||(c=null):!this.isConnecting()||this.isCreateTarget(b.getEvent())||this.graph.allowDanglingEdges||(this.error="");return c}) [...]
+mxUtils.bind(this,function(b){return this.isConnecting()?null==this.error:mxCellMarker.prototype.isValidState.apply(a,arguments)});a.getMarkerColor=mxUtils.bind(this,function(b,c,d){return null==this.connectImage||this.isConnecting()?mxCellMarker.prototype.getMarkerColor.apply(a,arguments):null});a.intersects=mxUtils.bind(this,function(b,c){return null!=this.connectImage||this.isConnecting()?!0:mxCellMarker.prototype.intersects.apply(a,arguments)});return a};
+mxConnectionHandler.prototype.start=function(a,b,c,d){this.previous=a;this.first=new mxPoint(b,c);this.edgeState=null!=d?d:this.createEdgeState(null);this.marker.currentColor=this.marker.validColor;this.marker.markedState=a;this.marker.mark();this.fireEvent(new mxEventObject(mxEvent.START,"state",this.previous))};mxConnectionHandler.prototype.isConnecting=function(){return null!=this.first&&null!=this.shape};mxConnectionHandler.prototype.isValidSource=function(a,b){return this.graph.isVa [...]
+mxConnectionHandler.prototype.isValidTarget=function(a){return!0};mxConnectionHandler.prototype.validateConnection=function(a,b){return this.isValidTarget(b)?this.graph.getEdgeValidationError(null,a,b):""};mxConnectionHandler.prototype.getConnectImage=function(a){return this.connectImage};mxConnectionHandler.prototype.isMoveIconToFrontForState=function(a){return null!=a.text&&a.text.node.parentNode==this.graph.container?!0:this.moveIconFront};
+mxConnectionHandler.prototype.createIcons=function(a){var b=this.getConnectImage(a);if(null!=b&&null!=a){this.iconState=a;var c=[],d=new mxRectangle(0,0,b.width,b.height),e=new mxImageShape(d,b.src,null,null,0);e.preserveImageAspect=!1;this.isMoveIconToFrontForState(a)?(e.dialect=mxConstants.DIALECT_STRICTHTML,e.init(this.graph.container)):(e.dialect=this.graph.dialect==mxConstants.DIALECT_SVG?mxConstants.DIALECT_SVG:mxConstants.DIALECT_VML,e.init(this.graph.getView().getOverlayPane()),t [...]
+null!=e.node.previousSibling&&e.node.parentNode.insertBefore(e.node,e.node.parentNode.firstChild));e.node.style.cursor=mxConstants.CURSOR_CONNECT;var f=mxUtils.bind(this,function(){return null!=this.currentState?this.currentState:a}),b=mxUtils.bind(this,function(a){mxEvent.isConsumed(a)||(this.icon=e,this.graph.fireMouseEvent(mxEvent.MOUSE_DOWN,new mxMouseEvent(a,f())))});mxEvent.redirectMouseEvents(e.node,this.graph,f,b);c.push(e);this.redrawIcons(c,this.iconState);return c}return null};
+mxConnectionHandler.prototype.redrawIcons=function(a,b){if(null!=a&&null!=a[0]&&null!=b){var c=this.getIconPosition(a[0],b);a[0].bounds.x=c.x;a[0].bounds.y=c.y;a[0].redraw()}};
+mxConnectionHandler.prototype.getIconPosition=function(a,b){var c=this.graph.getView().scale,d=b.getCenterX(),e=b.getCenterY();if(this.graph.isSwimlane(b.cell)){var f=this.graph.getStartSize(b.cell),d=0!=f.width?b.x+f.width*c/2:d,e=0!=f.height?b.y+f.height*c/2:e,f=mxUtils.toRadians(mxUtils.getValue(b.style,mxConstants.STYLE_ROTATION)||0);if(0!=f)var c=Math.cos(f),f=Math.sin(f),g=new mxPoint(b.getCenterX(),b.getCenterY()),e=mxUtils.getRotatedPoint(new mxPoint(d,e),c,f,g),d=e.x,e=e.y}retur [...]
+a.bounds.width/2,e-a.bounds.height/2)};mxConnectionHandler.prototype.destroyIcons=function(){if(null!=this.icons){for(var a=0;a<this.icons.length;a++)this.icons[a].destroy();this.iconState=this.selectedIcon=this.icon=this.icons=null}};mxConnectionHandler.prototype.isStartEvent=function(a){return null!=this.constraintHandler.currentFocus&&null!=this.constraintHandler.currentConstraint||null!=this.previous&&null==this.error&&(null==this.icons||null!=this.icons&&null!=this.icon)};
+mxConnectionHandler.prototype.mouseDown=function(a,b){this.mouseDownCounter++;if(this.isEnabled()&&this.graph.isEnabled()&&!b.isConsumed()&&!this.isConnecting()&&this.isStartEvent(b)){null!=this.constraintHandler.currentConstraint&&null!=this.constraintHandler.currentFocus&&null!=this.constraintHandler.currentPoint?(this.sourceConstraint=this.constraintHandler.currentConstraint,this.previous=this.constraintHandler.currentFocus,this.first=this.constraintHandler.currentPoint.clone()):this. [...]
+b.getGraphY());this.edgeState=this.createEdgeState(b);this.mouseDownCounter=1;this.waypointsEnabled&&null==this.shape&&(this.waypoints=null,this.shape=this.createShape(),null!=this.edgeState&&this.shape.apply(this.edgeState));if(null==this.previous&&null!=this.edgeState){var c=this.graph.getPointForEvent(b.getEvent());this.edgeState.cell.geometry.setTerminalPoint(c,!0)}this.fireEvent(new mxEventObject(mxEvent.START,"state",this.previous));b.consume()}this.selectedIcon=this.icon;this.icon=null};
+mxConnectionHandler.prototype.isImmediateConnectSource=function(a){return!this.graph.isCellMovable(a.cell)};mxConnectionHandler.prototype.createEdgeState=function(a){return null};
+mxConnectionHandler.prototype.isOutlineConnectEvent=function(a){var b=mxUtils.getOffset(this.graph.container),c=a.getEvent(),d=mxEvent.getClientX(c),c=mxEvent.getClientY(c),e=document.documentElement,f=this.currentPoint.x-this.graph.container.scrollLeft+b.x-((window.pageXOffset||e.scrollLeft)-(e.clientLeft||0)),b=this.currentPoint.y-this.graph.container.scrollTop+b.y-((window.pageYOffset||e.scrollTop)-(e.clientTop||0));return this.outlineConnect&&!mxEvent.isShiftDown(a.getEvent())&&(a.is [...]
+mxEvent.isAltDown(a.getEvent())&&null!=a.getState()||this.marker.highlight.isHighlightAt(d,c)||(f!=d||b!=c)&&null==a.getState()&&this.marker.highlight.isHighlightAt(f,b))};
+mxConnectionHandler.prototype.updateCurrentState=function(a,b){this.constraintHandler.update(a,null==this.first,!1,null==this.first||a.isSource(this.marker.highlight.shape)?null:b);if(null!=this.constraintHandler.currentFocus&&null!=this.constraintHandler.currentConstraint)null!=this.marker.highlight&&null!=this.marker.highlight.state&&this.marker.highlight.state.cell==this.constraintHandler.currentFocus.cell?"transparent"!=this.marker.highlight.shape.stroke&&(this.marker.highlight.shape [...]
+this.marker.highlight.repaint()):this.marker.markCell(this.constraintHandler.currentFocus.cell,"transparent"),null!=this.previous&&(this.error=this.validateConnection(this.previous.cell,this.constraintHandler.currentFocus.cell),null==this.error?this.currentState=this.constraintHandler.currentFocus:this.constraintHandler.reset());else{this.graph.isIgnoreTerminalEvent(a.getEvent())?(this.marker.reset(),this.currentState=null):(this.marker.process(a),this.currentState=this.marker.getValidSt [...]
+this.isOutlineConnectEvent(a);null!=this.currentState&&c&&(a.isSource(this.marker.highlight.shape)&&(b=new mxPoint(a.getGraphX(),a.getGraphY())),c=this.graph.getOutlineConstraint(b,this.currentState,a),this.constraintHandler.setFocus(a,this.currentState,!1),this.constraintHandler.currentConstraint=c,this.constraintHandler.currentPoint=b);this.outlineConnect&&null!=this.marker.highlight&&null!=this.marker.highlight.shape&&(c=this.graph.view.scale,null!=this.constraintHandler.currentConstr [...]
+this.constraintHandler.currentFocus?(this.marker.highlight.shape.stroke=mxConstants.OUTLINE_HIGHLIGHT_COLOR,this.marker.highlight.shape.strokewidth=mxConstants.OUTLINE_HIGHLIGHT_STROKEWIDTH/c/c,this.marker.highlight.repaint()):this.marker.hasValidState()&&(this.marker.getValidState()!=a.getState()?(this.marker.highlight.shape.stroke="transparent",this.currentState=null):this.marker.highlight.shape.stroke=mxConstants.DEFAULT_VALID_COLOR,this.marker.highlight.shape.strokewidth=mxConstants. [...]
+c/c,this.marker.highlight.repaint()))}};mxConnectionHandler.prototype.convertWaypoint=function(a){var b=this.graph.getView().getScale(),c=this.graph.getView().getTranslate();a.x=a.x/b-c.x;a.y=a.y/b-c.y};
+mxConnectionHandler.prototype.snapToPreview=function(a,b){if(!mxEvent.isAltDown(a.getEvent())&&null!=this.previous){var c=this.graph.gridSize*this.graph.view.scale/2,d=null!=this.sourceConstraint?this.first:new mxPoint(this.previous.getCenterX(),this.previous.getCenterY());Math.abs(d.x-a.getGraphX())<c&&(b.x=d.x);Math.abs(d.y-a.getGraphY())<c&&(b.y=d.y)}};
+mxConnectionHandler.prototype.mouseMove=function(a,b){if(b.isConsumed()||!this.ignoreMouseDown&&null==this.first&&this.graph.isMouseDown)this.constraintHandler.reset();else{this.isEnabled()||null==this.currentState||(this.destroyIcons(),this.currentState=null);var c=this.graph.getView(),d=c.scale,e=c.translate,c=new mxPoint(b.getGraphX(),b.getGraphY());this.error=null;this.graph.isGridEnabledEvent(b.getEvent())&&(c=new mxPoint((this.graph.snap(c.x/d-e.x)+e.x)*d,(this.graph.snap(c.y/d-e.y [...]
+this.snapToPreview(b,c);this.currentPoint=c;(null!=this.first||this.isEnabled()&&this.graph.isEnabled())&&this.updateCurrentState(b,c);if(null!=this.first){var f=null,d=c;null!=this.constraintHandler.currentConstraint&&null!=this.constraintHandler.currentFocus&&null!=this.constraintHandler.currentPoint?(f=this.constraintHandler.currentConstraint,d=this.constraintHandler.currentPoint.clone()):null!=this.previous&&!this.graph.isIgnoreTerminalEvent(b.getEvent())&&mxEvent.isShiftDown(b.getEv [...]
+c.x)<Math.abs(this.previous.getCenterY()-c.y)?c.x=this.previous.getCenterX():c.y=this.previous.getCenterY());e=this.first;if(null!=this.selectedIcon){var g=this.selectedIcon.bounds.width,k=this.selectedIcon.bounds.height;null!=this.currentState&&this.targetConnectImage?(g=this.getIconPosition(this.selectedIcon,this.currentState),this.selectedIcon.bounds.x=g.x,this.selectedIcon.bounds.y=g.y):(g=new mxRectangle(b.getGraphX()+this.connectIconOffset.x,b.getGraphY()+this.connectIconOffset.y,g [...]
+g);this.selectedIcon.redraw()}null!=this.edgeState?(this.updateEdgeState(d,f),d=this.edgeState.absolutePoints[this.edgeState.absolutePoints.length-1],e=this.edgeState.absolutePoints[0]):(null!=this.currentState&&null==this.constraintHandler.currentConstraint&&(g=this.getTargetPerimeterPoint(this.currentState,b),null!=g&&(d=g)),null==this.sourceConstraint&&null!=this.previous&&(g=this.getSourcePerimeterPoint(this.previous,null!=this.waypoints&&0<this.waypoints.length?this.waypoints[0]:d,b [...]
+(e=g)));if(null==this.currentState&&this.movePreviewAway){g=e;null!=this.edgeState&&2<=this.edgeState.absolutePoints.length&&(f=this.edgeState.absolutePoints[this.edgeState.absolutePoints.length-2],null!=f&&(g=f));f=d.x-g.x;g=d.y-g.y;k=Math.sqrt(f*f+g*g);if(0==k)return;this.originalPoint=d.clone();d.x-=4*f/k;d.y-=4*g/k}else this.originalPoint=null;null==this.shape&&(f=Math.abs(c.x-this.first.x),g=Math.abs(c.y-this.first.y),f>this.graph.tolerance||g>this.graph.tolerance)&&(this.shape=this [...]
+null!=this.edgeState&&this.shape.apply(this.edgeState),this.updateCurrentState(b,c));null!=this.shape&&(null!=this.edgeState?this.shape.points=this.edgeState.absolutePoints:(c=[e],null!=this.waypoints&&(c=c.concat(this.waypoints)),c.push(d),this.shape.points=c),this.drawPreview());null!=this.cursor&&(this.graph.container.style.cursor=this.cursor);mxEvent.consume(b.getEvent());b.consume()}else this.isEnabled()&&this.graph.isEnabled()?this.previous!=this.currentState&&null==this.edgeState? [...]
+null!=this.currentState&&null==this.error&&null==this.constraintHandler.currentConstraint&&(this.icons=this.createIcons(this.currentState),null==this.icons&&(this.currentState.setCursor(mxConstants.CURSOR_CONNECT),b.consume())),this.previous=this.currentState):this.previous!=this.currentState||null==this.currentState||null!=this.icons||this.graph.isMouseDown||b.consume():this.constraintHandler.reset();if(!this.graph.isMouseDown&&null!=this.currentState&&null!=this.icons){c=!1;d=b.getSour [...]
+0;e<this.icons.length&&!c;e++)c=d==this.icons[e].node||d.parentNode==this.icons[e].node;c||this.updateIcons(this.currentState,this.icons,b)}}};
+mxConnectionHandler.prototype.updateEdgeState=function(a,b){null!=this.sourceConstraint&&null!=this.sourceConstraint.point&&(this.edgeState.style[mxConstants.STYLE_EXIT_X]=this.sourceConstraint.point.x,this.edgeState.style[mxConstants.STYLE_EXIT_Y]=this.sourceConstraint.point.y);null!=b&&null!=b.point?(this.edgeState.style[mxConstants.STYLE_ENTRY_X]=b.point.x,this.edgeState.style[mxConstants.STYLE_ENTRY_Y]=b.point.y):(delete this.edgeState.style[mxConstants.STYLE_ENTRY_X],delete this.edg [...]
+this.edgeState.absolutePoints=[null,null!=this.currentState?null:a];this.graph.view.updateFixedTerminalPoint(this.edgeState,this.previous,!0,this.sourceConstraint);null!=this.currentState&&(null==b&&(b=this.graph.getConnectionConstraint(this.edgeState,this.previous,!1)),this.edgeState.setAbsoluteTerminalPoint(null,!1),this.graph.view.updateFixedTerminalPoint(this.edgeState,this.currentState,!1,b));var c=null;if(null!=this.waypoints)for(var c=[],d=0;d<this.waypoints.length;d++){var e=this [...]
+this.convertWaypoint(e);c[d]=e}this.graph.view.updatePoints(this.edgeState,c,this.previous,this.currentState);this.graph.view.updateFloatingTerminalPoints(this.edgeState,this.previous,this.currentState)};
+mxConnectionHandler.prototype.getTargetPerimeterPoint=function(a,b){var c=null,d=a.view,e=d.getPerimeterFunction(a);if(null!=e){var f=null!=this.waypoints&&0<this.waypoints.length?this.waypoints[this.waypoints.length-1]:new mxPoint(this.previous.getCenterX(),this.previous.getCenterY()),d=e(d.getPerimeterBounds(a),this.edgeState,f,!1);null!=d&&(c=d)}else c=new mxPoint(a.getCenterX(),a.getCenterY());return c};
+mxConnectionHandler.prototype.getSourcePerimeterPoint=function(a,b,c){c=null;var d=a.view,e=d.getPerimeterFunction(a),f=new mxPoint(a.getCenterX(),a.getCenterY());if(null!=e){var g=mxUtils.getValue(a.style,mxConstants.STYLE_ROTATION,0),k=Math.PI/180*-g;0!=g&&(b=mxUtils.getRotatedPoint(new mxPoint(b.x,b.y),Math.cos(k),Math.sin(k),f));a=e(d.getPerimeterBounds(a),a,b,!1);null!=a&&(0!=g&&(a=mxUtils.getRotatedPoint(new mxPoint(a.x,a.y),Math.cos(-k),Math.sin(-k),f)),c=a)}else c=f;return c};
+mxConnectionHandler.prototype.updateIcons=function(a,b,c){};mxConnectionHandler.prototype.isStopEvent=function(a){return null!=a.getState()};
+mxConnectionHandler.prototype.addWaypointForEvent=function(a){var b=mxUtils.convertPoint(this.graph.container,a.getX(),a.getY()),c=Math.abs(b.x-this.first.x),b=Math.abs(b.y-this.first.y);if(null!=this.waypoints||1<this.mouseDownCounter&&(c>this.graph.tolerance||b>this.graph.tolerance))null==this.waypoints&&(this.waypoints=[]),c=this.graph.view.scale,b=new mxPoint(this.graph.snap(a.getGraphX()/c)*c,this.graph.snap(a.getGraphY()/c)*c),this.waypoints.push(b)};
+mxConnectionHandler.prototype.mouseUp=function(a,b){if(!b.isConsumed()&&this.isConnecting()){if(this.waypointsEnabled&&!this.isStopEvent(b)){this.addWaypointForEvent(b);b.consume();return}if(null==this.error){var c=null!=this.previous?this.previous.cell:null,d=null;null!=this.constraintHandler.currentConstraint&&null!=this.constraintHandler.currentFocus&&(d=this.constraintHandler.currentFocus.cell);null==d&&null!=this.currentState&&(d=this.currentState.cell);this.connect(c,d,b.getEvent() [...]
+this.previous&&null!=this.marker.validState&&this.previous.cell==this.marker.validState.cell&&this.graph.selectCellForEvent(this.marker.source,evt),0<this.error.length&&this.graph.validationAlert(this.error);this.destroyIcons();b.consume()}null!=this.first&&this.reset()};
+mxConnectionHandler.prototype.reset=function(){null!=this.shape&&(this.shape.destroy(),this.shape=null);null!=this.cursor&&null!=this.graph.container&&(this.graph.container.style.cursor="");this.destroyIcons();this.marker.reset();this.constraintHandler.reset();this.sourceConstraint=this.error=this.previous=this.edgeState=this.currentPoint=this.originalPoint=null;this.mouseDownCounter=0;this.first=null;this.fireEvent(new mxEventObject(mxEvent.RESET))};
+mxConnectionHandler.prototype.drawPreview=function(){this.updatePreview(null==this.error);this.shape.redraw()};mxConnectionHandler.prototype.updatePreview=function(a){this.shape.strokewidth=this.getEdgeWidth(a);this.shape.stroke=this.getEdgeColor(a)};mxConnectionHandler.prototype.getEdgeColor=function(a){return a?mxConstants.VALID_COLOR:mxConstants.INVALID_COLOR};mxConnectionHandler.prototype.getEdgeWidth=function(a){return a?3:1};
+mxConnectionHandler.prototype.connect=function(a,b,c,d){if(null!=b||this.isCreateTarget(c)||this.graph.allowDanglingEdges){var e=this.graph.getModel(),f=!1,g=null;e.beginUpdate();try{if(null!=a&&null==b&&!this.graph.isIgnoreTerminalEvent(c)&&this.isCreateTarget(c)&&(b=this.createTargetVertex(c,a),null!=b)){d=this.graph.getDropTarget([b],c,d);f=!0;if(null!=d&&this.graph.getModel().isEdge(d))d=this.graph.getDefaultParent();else{var k=this.graph.getView().getState(d);if(null!=k){var l=e.get [...]
+l.x-=k.origin.x;l.y-=k.origin.y}}this.graph.addCell(b,d)}var m=this.graph.getDefaultParent();null!=a&&null!=b&&e.getParent(a)==e.getParent(b)&&e.getParent(e.getParent(a))!=e.getRoot()&&(m=e.getParent(a),null!=a.geometry&&a.geometry.relative&&null!=b.geometry&&b.geometry.relative&&(m=e.getParent(m)));var n=k=null;null!=this.edgeState&&(k=this.edgeState.cell.value,n=this.edgeState.cell.style);g=this.insertEdge(m,null,k,a,b,n);if(null!=g){this.graph.setConnectionConstraint(g,a,!0,this.sourc [...]
+this.graph.setConnectionConstraint(g,b,!1,this.constraintHandler.currentConstraint);null!=this.edgeState&&e.setGeometry(g,this.edgeState.cell.geometry);e.getParent(a);if(this.isInsertBefore(g,a,b,c,d)){m=null;for(l=a;null!=l.parent&&null!=l.geometry&&l.geometry.relative&&l.parent!=g.parent;)l=this.graph.model.getParent(l);null!=l&&null!=l.parent&&l.parent==g.parent&&(m=l.parent.getIndex(l),l.parent.insert(g,m))}var p=e.getGeometry(g);null==p&&(p=new mxGeometry,p.relative=!0,e.setGeometry [...]
+this.waypoints&&0<this.waypoints.length){var q=this.graph.view.scale,r=this.graph.view.translate;p.points=[];for(a=0;a<this.waypoints.length;a++){var t=this.waypoints[a];p.points.push(new mxPoint(t.x/q-r.x,t.y/q-r.y))}}if(null==b){var u=this.graph.view.translate,q=this.graph.view.scale,t=null!=this.originalPoint?new mxPoint(this.originalPoint.x/q-u.x,this.originalPoint.y/q-u.y):new mxPoint(this.currentPoint.x/q-u.x,this.currentPoint.y/q-u.y);t.x-=this.graph.panDx/this.graph.view.scale;t. [...]
+this.graph.view.scale;p.setTerminalPoint(t,!1)}this.fireEvent(new mxEventObject(mxEvent.CONNECT,"cell",g,"terminal",b,"event",c,"target",d,"terminalInserted",f))}}catch(x){mxLog.show(),mxLog.debug(x.message)}finally{e.endUpdate()}this.select&&this.selectCells(g,f?b:null)}};mxConnectionHandler.prototype.selectCells=function(a,b){this.graph.setSelectionCell(a)};
+mxConnectionHandler.prototype.insertEdge=function(a,b,c,d,e,f){if(null==this.factoryMethod)return this.graph.insertEdge(a,b,c,d,e,f);b=this.createEdge(c,d,e,f);return b=this.graph.addEdge(b,a,d,e)};
+mxConnectionHandler.prototype.createTargetVertex=function(a,b){for(var c=this.graph.getCellGeometry(b);null!=c&&c.relative;)b=this.graph.getModel().getParent(b),c=this.graph.getCellGeometry(b);var d=this.graph.cloneCells([b])[0],c=this.graph.getModel().getGeometry(d);if(null!=c){var e=this.graph.view.translate,f=this.graph.view.scale,g=new mxPoint(this.currentPoint.x/f-e.x,this.currentPoint.y/f-e.y);c.x=Math.round(g.x-c.width/2-this.graph.panDx/f);c.y=Math.round(g.y-c.height/2-this.graph [...]
+g=this.getAlignmentTolerance();if(0<g){var k=this.graph.view.getState(b);if(null!=k){var l=k.x/f-e.x,e=k.y/f-e.y;Math.abs(l-c.x)<=g&&(c.x=Math.round(l));Math.abs(e-c.y)<=g&&(c.y=Math.round(e))}}}return d};mxConnectionHandler.prototype.getAlignmentTolerance=function(a){return this.graph.isGridEnabled()?this.graph.gridSize/2:this.graph.tolerance};
+mxConnectionHandler.prototype.createEdge=function(a,b,c,d){var e=null;null!=this.factoryMethod&&(e=this.factoryMethod(b,c,d));null==e&&(e=new mxCell(a||""),e.setEdge(!0),e.setStyle(d),a=new mxGeometry,a.relative=!0,e.setGeometry(a));return e};
+mxConnectionHandler.prototype.destroy=function(){this.graph.removeMouseListener(this);null!=this.shape&&(this.shape.destroy(),this.shape=null);null!=this.marker&&(this.marker.destroy(),this.marker=null);null!=this.constraintHandler&&(this.constraintHandler.destroy(),this.constraintHandler=null);null!=this.changeHandler&&(this.graph.getModel().removeListener(this.changeHandler),this.graph.getView().removeListener(this.changeHandler),this.changeHandler=null);null!=this.drillHandler&&(this. [...]
+this.graph.getView().removeListener(this.drillHandler),this.drillHandler=null);null!=this.escapeHandler&&(this.graph.removeListener(this.escapeHandler),this.escapeHandler=null)};
+function mxConstraintHandler(a){this.graph=a;this.resetHandler=mxUtils.bind(this,function(a,c){null!=this.currentFocus&&null==this.graph.view.getState(this.currentFocus.cell)?this.reset():this.redraw()});this.graph.model.addListener(mxEvent.CHANGE,this.resetHandler);this.graph.view.addListener(mxEvent.SCALE,this.resetHandler);this.graph.addListener(mxEvent.ROOT,this.resetHandler)}mxConstraintHandler.prototype.pointImage=new mxImage(mxClient.imageBasePath+"/point.gif",5,5);
+mxConstraintHandler.prototype.graph=null;mxConstraintHandler.prototype.enabled=!0;mxConstraintHandler.prototype.highlightColor=mxConstants.DEFAULT_VALID_COLOR;mxConstraintHandler.prototype.isEnabled=function(){return this.enabled};mxConstraintHandler.prototype.setEnabled=function(a){this.enabled=a};
+mxConstraintHandler.prototype.reset=function(){if(null!=this.focusIcons){for(var a=0;a<this.focusIcons.length;a++)this.focusIcons[a].destroy();this.focusIcons=null}null!=this.focusHighlight&&(this.focusHighlight.destroy(),this.focusHighlight=null);this.focusPoints=this.currentFocus=this.currentPoint=this.currentFocusArea=this.currentConstraint=null};mxConstraintHandler.prototype.getTolerance=function(a){return this.graph.getTolerance()};
+mxConstraintHandler.prototype.getImageForConstraint=function(a,b,c){return this.pointImage};mxConstraintHandler.prototype.isEventIgnored=function(a,b){return!1};mxConstraintHandler.prototype.isStateIgnored=function(a,b){return!1};mxConstraintHandler.prototype.destroyIcons=function(){if(null!=this.focusIcons){for(var a=0;a<this.focusIcons.length;a++)this.focusIcons[a].destroy();this.focusPoints=this.focusIcons=null}};
+mxConstraintHandler.prototype.destroyFocusHighlight=function(){null!=this.focusHighlight&&(this.focusHighlight.destroy(),this.focusHighlight=null)};mxConstraintHandler.prototype.isKeepFocusEvent=function(a){return mxEvent.isShiftDown(a.getEvent())};
+mxConstraintHandler.prototype.getCellForEvent=function(a,b){var c=a.getCell();null!=c||null==b||a.getGraphX()==b.x&&a.getGraphY()==b.y||(c=this.graph.getCellAt(b.x,b.y));if(null!=c&&!this.graph.isCellConnectable(c)){var d=this.graph.getModel().getParent(c);this.graph.getModel().isVertex(d)&&this.graph.isCellConnectable(d)&&(c=d)}return c};
+mxConstraintHandler.prototype.update=function(a,b,c,d){if(this.isEnabled()&&!this.isEventIgnored(a)){null==this.mouseleaveHandler&&null!=this.graph.container&&(this.mouseleaveHandler=mxUtils.bind(this,function(){this.reset()}),mxEvent.addListener(this.graph.container,"mouseleave",this.resetHandler));var e=this.getTolerance(a),f=null!=d?d.x:a.getGraphX(),g=null!=d?d.y:a.getGraphY(),f=new mxRectangle(f-e,g-e,2*e,2*e),e=new mxRectangle(a.getGraphX()-e,a.getGraphY()-e,2*e,2*e),k=this.graph.v [...]
+d));this.isKeepFocusEvent(a)||null!=this.currentFocusArea&&null!=this.currentFocus&&null==k&&this.graph.getModel().isVertex(this.currentFocus.cell)&&mxUtils.intersects(this.currentFocusArea,e)||k==this.currentFocus||(this.currentFocus=this.currentFocusArea=null,this.setFocus(a,k,b));a=this.currentPoint=this.currentConstraint=null;if(null!=this.focusIcons&&null!=this.constraints&&(null==k||this.currentFocus==k))for(var g=e.getCenterX(),l=e.getCenterY(),m=0;m<this.focusIcons.length;m++){va [...]
+p=l-this.focusIcons[m].bounds.getCenterY(),n=n*n+p*p;if((this.intersects(this.focusIcons[m],e,b,c)||null!=d&&this.intersects(this.focusIcons[m],f,b,c))&&(null==a||n<a)){this.currentConstraint=this.constraints[m];this.currentPoint=this.focusPoints[m];a=n;n=this.focusIcons[m].bounds.clone();n.grow(mxConstants.HIGHLIGHT_SIZE);mxClient.IS_IE&&(n.grow(1),--n.width,--n.height);if(null==this.focusHighlight){p=this.createHighlightShape();p.dialect=this.graph.dialect==mxConstants.DIALECT_SVG?mxCo [...]
+mxConstants.DIALECT_VML;p.pointerEvents=!1;p.init(this.graph.getView().getOverlayPane());this.focusHighlight=p;var q=mxUtils.bind(this,function(){return null!=this.currentFocus?this.currentFocus:k});mxEvent.redirectMouseEvents(p.node,this.graph,q)}this.focusHighlight.bounds=n;this.focusHighlight.redraw()}}null==this.currentConstraint&&this.destroyFocusHighlight()}else this.currentPoint=this.currentFocus=this.currentConstraint=null};
+mxConstraintHandler.prototype.redraw=function(){if(null!=this.currentFocus&&null!=this.constraints&&null!=this.focusIcons){var a=this.graph.view.getState(this.currentFocus.cell);this.currentFocus=a;this.currentFocusArea=new mxRectangle(a.x,a.y,a.width,a.height);for(var b=0;b<this.constraints.length;b++){var c=this.graph.getConnectionPoint(a,this.constraints[b]),d=this.getImageForConstraint(a,this.constraints[b],c),d=new mxRectangle(Math.round(c.x-d.width/2),Math.round(c.y-d.height/2),d.w [...]
+this.focusIcons[b].bounds=d;this.focusIcons[b].redraw();this.currentFocusArea.add(this.focusIcons[b].bounds);this.focusPoints[b]=c}}};
+mxConstraintHandler.prototype.setFocus=function(a,b,c){this.constraints=null!=b&&!this.isStateIgnored(b,c)&&this.graph.isCellConnectable(b.cell)?this.isEnabled()?this.graph.getAllConnectionConstraints(b,c)||[]:[]:null;if(null!=this.constraints){this.currentFocus=b;this.currentFocusArea=new mxRectangle(b.x,b.y,b.width,b.height);if(null!=this.focusIcons){for(c=0;c<this.focusIcons.length;c++)this.focusIcons[c].destroy();this.focusPoints=this.focusIcons=null}this.focusPoints=[];this.focusIco [...]
+0;c<this.constraints.length;c++){var d=this.graph.getConnectionPoint(b,this.constraints[c]),e=this.getImageForConstraint(b,this.constraints[c],d),f=e.src,e=new mxRectangle(Math.round(d.x-e.width/2),Math.round(d.y-e.height/2),e.width,e.height),f=new mxImageShape(e,f);f.dialect=this.graph.dialect!=mxConstants.DIALECT_SVG?mxConstants.DIALECT_MIXEDHTML:mxConstants.DIALECT_SVG;f.preserveImageAspect=!1;f.init(this.graph.getView().getDecoratorPane());(mxClient.IS_QUIRKS||8==document.documentMod [...]
+"dragstart",function(a){mxEvent.consume(a);return!1});null!=f.node.previousSibling&&f.node.parentNode.insertBefore(f.node,f.node.parentNode.firstChild);e=mxUtils.bind(this,function(){return null!=this.currentFocus?this.currentFocus:b});f.redraw();mxEvent.redirectMouseEvents(f.node,this.graph,e);this.currentFocusArea.add(f.bounds);this.focusIcons.push(f);this.focusPoints.push(d)}this.currentFocusArea.grow(this.getTolerance(a))}else this.destroyIcons(),this.destroyFocusHighlight()};
+mxConstraintHandler.prototype.createHighlightShape=function(){var a=new mxRectangleShape(null,this.highlightColor,this.highlightColor,mxConstants.HIGHLIGHT_STROKEWIDTH);a.opacity=mxConstants.HIGHLIGHT_OPACITY;return a};mxConstraintHandler.prototype.intersects=function(a,b,c,d){return mxUtils.intersects(a.bounds,b)};
+mxConstraintHandler.prototype.destroy=function(){this.reset();null!=this.resetHandler&&(this.graph.model.removeListener(this.resetHandler),this.graph.view.removeListener(this.resetHandler),this.graph.removeListener(this.resetHandler),this.resetHandler=null);null!=this.mouseleaveHandler&&null!=this.graph.container&&(mxEvent.removeListener(this.graph.container,"mouseleave",this.mouseleaveHandler),this.mouseleaveHandler=null)};
+function mxRubberband(a){null!=a&&(this.graph=a,this.graph.addMouseListener(this),this.forceRubberbandHandler=mxUtils.bind(this,function(a,c){var b=c.getProperty("eventName"),e=c.getProperty("event");if(b==mxEvent.MOUSE_DOWN&&this.isForceRubberbandEvent(e)){var b=mxUtils.getOffset(this.graph.container),f=mxUtils.getScrollOrigin(this.graph.container);f.x-=b.x;f.y-=b.y;this.start(e.getX()+f.x,e.getY()+f.y);e.consume(!1)}}),this.graph.addListener(mxEvent.FIRE_MOUSE_EVENT,this.forceRubberban [...]
+this.panHandler=mxUtils.bind(this,function(){this.repaint()}),this.graph.addListener(mxEvent.PAN,this.panHandler),this.gestureHandler=mxUtils.bind(this,function(a,c){null!=this.first&&this.reset()}),this.graph.addListener(mxEvent.GESTURE,this.gestureHandler),mxClient.IS_IE&&mxEvent.addListener(window,"unload",mxUtils.bind(this,function(){this.destroy()})))}mxRubberband.prototype.defaultOpacity=20;mxRubberband.prototype.enabled=!0;mxRubberband.prototype.div=null;mxRubberband.prototype.sha [...]
+mxRubberband.prototype.currentX=0;mxRubberband.prototype.currentY=0;mxRubberband.prototype.isEnabled=function(){return this.enabled};mxRubberband.prototype.setEnabled=function(a){this.enabled=a};mxRubberband.prototype.isForceRubberbandEvent=function(a){return mxEvent.isAltDown(a.getEvent())};
+mxRubberband.prototype.mouseDown=function(a,b){if(!b.isConsumed()&&this.isEnabled()&&this.graph.isEnabled()&&null==b.getState()&&!mxEvent.isMultiTouchEvent(b.getEvent())){var c=mxUtils.getOffset(this.graph.container),d=mxUtils.getScrollOrigin(this.graph.container);d.x-=c.x;d.y-=c.y;this.start(b.getX()+d.x,b.getY()+d.y);b.consume(!1)}};
+mxRubberband.prototype.start=function(a,b){function c(a){a=new mxMouseEvent(a);var b=mxUtils.convertPoint(d,a.getX(),a.getY());a.graphX=b.x;a.graphY=b.y;return a}this.first=new mxPoint(a,b);var d=this.graph.container;this.dragHandler=mxUtils.bind(this,function(a){this.mouseMove(this.graph,c(a))});this.dropHandler=mxUtils.bind(this,function(a){this.mouseUp(this.graph,c(a))});mxClient.IS_FF&&mxEvent.addGestureListeners(document,null,this.dragHandler,this.dropHandler)};
+mxRubberband.prototype.mouseMove=function(a,b){if(!b.isConsumed()&&null!=this.first){var c=mxUtils.getScrollOrigin(this.graph.container),d=mxUtils.getOffset(this.graph.container);c.x-=d.x;c.y-=d.y;var d=b.getX()+c.x,c=b.getY()+c.y,e=this.first.x-d,f=this.first.y-c,g=this.graph.tolerance;if(null!=this.div||Math.abs(e)>g||Math.abs(f)>g)null==this.div&&(this.div=this.createShape()),mxUtils.clearSelection(),this.update(d,c),b.consume()}};
+mxRubberband.prototype.createShape=function(){null==this.sharedDiv&&(this.sharedDiv=document.createElement("div"),this.sharedDiv.className="mxRubberband",mxUtils.setOpacity(this.sharedDiv,this.defaultOpacity));this.graph.container.appendChild(this.sharedDiv);return this.sharedDiv};mxRubberband.prototype.isActive=function(a,b){return null!=this.div&&"none"!=this.div.style.display};mxRubberband.prototype.mouseUp=function(a,b){var c=this.isActive();this.reset();c&&(this.execute(b.getEvent() [...]
+mxRubberband.prototype.execute=function(a){var b=new mxRectangle(this.x,this.y,this.width,this.height);this.graph.selectRegion(b,a)};mxRubberband.prototype.reset=function(){null!=this.div&&this.div.parentNode.removeChild(this.div);mxEvent.removeGestureListeners(document,null,this.dragHandler,this.dropHandler);this.dropHandler=this.dragHandler=null;this.currentY=this.currentX=0;this.div=this.first=null};mxRubberband.prototype.update=function(a,b){this.currentX=a;this.currentY=b;this.repaint()};
+mxRubberband.prototype.repaint=function(){if(null!=this.div){var a=this.currentX-this.graph.panDx,b=this.currentY-this.graph.panDy;this.x=Math.min(this.first.x,a);this.y=Math.min(this.first.y,b);this.width=Math.max(this.first.x,a)-this.x;this.height=Math.max(this.first.y,b)-this.y;a=mxClient.IS_VML?this.graph.panDy:0;this.div.style.left=this.x+(mxClient.IS_VML?this.graph.panDx:0)+"px";this.div.style.top=this.y+a+"px";this.div.style.width=Math.max(1,this.width)+"px";this.div.style.height= [...]
+this.height)+"px"}};mxRubberband.prototype.destroy=function(){this.destroyed||(this.destroyed=!0,this.graph.removeMouseListener(this),this.graph.removeListener(this.forceRubberbandHandler),this.graph.removeListener(this.panHandler),this.reset(),null!=this.sharedDiv&&(this.sharedDiv=null))};function mxHandle(a,b,c){this.graph=a.view.graph;this.state=a;this.cursor=null!=b?b:this.cursor;this.image=null!=c?c:this.image;this.init()}mxHandle.prototype.cursor="default";mxHandle.prototype.image=null;
+mxHandle.prototype.ignoreGrid=!1;mxHandle.prototype.getPosition=function(a){};mxHandle.prototype.setPosition=function(a,b,c){};mxHandle.prototype.execute=function(){};mxHandle.prototype.copyStyle=function(a){this.graph.setCellStyles(a,this.state.style[a],[this.state.cell])};
+mxHandle.prototype.processEvent=function(a){var b=this.graph.view.scale,c=this.graph.view.translate,c=new mxPoint(a.getGraphX()/b-c.x,a.getGraphY()/b-c.y);null!=this.shape&&null!=this.shape.bounds&&(c.x-=this.shape.bounds.width/b/4,c.y-=this.shape.bounds.height/b/4);var b=-mxUtils.toRadians(this.getRotation()),d=-mxUtils.toRadians(this.getTotalRotation())-b,c=this.flipPoint(this.rotatePoint(this.snapPoint(this.rotatePoint(c,b),this.ignoreGrid||!this.graph.isGridEnabledEvent(a.getEvent()) [...]
+c,a);this.positionChanged();this.redraw()};mxHandle.prototype.positionChanged=function(){null!=this.state.text&&this.state.text.apply(this.state);null!=this.state.shape&&this.state.shape.apply(this.state);this.state.unscaledWidth=null;this.graph.cellRenderer.redraw(this.state,!0)};mxHandle.prototype.getRotation=function(){return null!=this.state.shape?this.state.shape.getRotation():0};
+mxHandle.prototype.getTotalRotation=function(){return null!=this.state.shape?this.state.shape.getShapeRotation():0};mxHandle.prototype.init=function(){var a=this.isHtmlRequired();null!=this.image?(this.shape=new mxImageShape(new mxRectangle(0,0,this.image.width,this.image.height),this.image.src),this.shape.preserveImageAspect=!1):this.shape=this.createShape(a);this.initShape(a)};
+mxHandle.prototype.createShape=function(a){a=new mxRectangle(0,0,mxConstants.HANDLE_SIZE,mxConstants.HANDLE_SIZE);return new mxRectangleShape(a,mxConstants.HANDLE_FILLCOLOR,mxConstants.HANDLE_STROKECOLOR)};
+mxHandle.prototype.initShape=function(a){a&&this.shape.isHtmlAllowed()?(this.shape.dialect=mxConstants.DIALECT_STRICTHTML,this.shape.init(this.graph.container)):(this.shape.dialect=this.graph.dialect!=mxConstants.DIALECT_SVG?mxConstants.DIALECT_MIXEDHTML:mxConstants.DIALECT_SVG,null!=this.cursor&&this.shape.init(this.graph.getView().getOverlayPane()));mxEvent.redirectMouseEvents(this.shape.node,this.graph,this.state);this.shape.node.style.cursor=this.cursor};
+mxHandle.prototype.redraw=function(){if(null!=this.shape&&null!=this.state.shape){var a=this.getPosition(this.state.getPaintBounds());if(null!=a){var b=mxUtils.toRadians(this.getTotalRotation()),a=this.rotatePoint(this.flipPoint(a),b),b=this.graph.view.scale,c=this.graph.view.translate;this.shape.bounds.x=Math.floor((a.x+c.x)*b-this.shape.bounds.width/2);this.shape.bounds.y=Math.floor((a.y+c.y)*b-this.shape.bounds.height/2);this.shape.redraw()}}};
+mxHandle.prototype.isHtmlRequired=function(){return null!=this.state.text&&this.state.text.node.parentNode==this.graph.container};mxHandle.prototype.rotatePoint=function(a,b){var c=this.state.getCellBounds(),c=new mxPoint(c.getCenterX(),c.getCenterY());return mxUtils.getRotatedPoint(a,Math.cos(b),Math.sin(b),c)};
+mxHandle.prototype.flipPoint=function(a){if(null!=this.state.shape){var b=this.state.getCellBounds();this.state.shape.flipH&&(a.x=2*b.x+b.width-a.x);this.state.shape.flipV&&(a.y=2*b.y+b.height-a.y)}return a};mxHandle.prototype.snapPoint=function(a,b){b||(a.x=this.graph.snap(a.x),a.y=this.graph.snap(a.y));return a};mxHandle.prototype.setVisible=function(a){null!=this.shape&&null!=this.shape.node&&(this.shape.node.style.display=a?"":"none")};
+mxHandle.prototype.reset=function(){this.setVisible(!0);this.state.style=this.graph.getCellStyle(this.state.cell);this.positionChanged()};mxHandle.prototype.destroy=function(){null!=this.shape&&(this.shape.destroy(),this.shape=null)};
+function mxVertexHandler(a){null!=a&&(this.state=a,this.init(),this.escapeHandler=mxUtils.bind(this,function(a,c){this.livePreview&&null!=this.index&&(this.state.view.graph.cellRenderer.redraw(this.state,!0),this.state.view.invalidate(this.state.cell),this.state.invalid=!1,this.state.view.validate());this.reset()}),this.state.view.graph.addListener(mxEvent.ESCAPE,this.escapeHandler))}mxVertexHandler.prototype.graph=null;mxVertexHandler.prototype.state=null;mxVertexHandler.prototype.singl [...]
+mxVertexHandler.prototype.index=null;mxVertexHandler.prototype.allowHandleBoundsCheck=!0;mxVertexHandler.prototype.handleImage=null;mxVertexHandler.prototype.tolerance=0;mxVertexHandler.prototype.rotationEnabled=!1;mxVertexHandler.prototype.parentHighlightEnabled=!1;mxVertexHandler.prototype.rotationRaster=!0;mxVertexHandler.prototype.rotationCursor="crosshair";mxVertexHandler.prototype.livePreview=!1;mxVertexHandler.prototype.manageSizers=!1;mxVertexHandler.prototype.constrainGroupByChi [...]
+mxVertexHandler.prototype.rotationHandleVSpacing=-16;mxVertexHandler.prototype.horizontalOffset=0;mxVertexHandler.prototype.verticalOffset=0;
+mxVertexHandler.prototype.init=function(){this.graph=this.state.view.graph;this.selectionBounds=this.getSelectionBounds(this.state);this.bounds=new mxRectangle(this.selectionBounds.x,this.selectionBounds.y,this.selectionBounds.width,this.selectionBounds.height);this.selectionBorder=this.createSelectionShape(this.bounds);this.selectionBorder.dialect=this.graph.dialect!=mxConstants.DIALECT_SVG?mxConstants.DIALECT_VML:mxConstants.DIALECT_SVG;this.selectionBorder.pointerEvents=!1;this.select [...]
+Number(this.state.style[mxConstants.STYLE_ROTATION]||"0");this.selectionBorder.init(this.graph.getView().getOverlayPane());mxEvent.redirectMouseEvents(this.selectionBorder.node,this.graph,this.state);this.graph.isCellMovable(this.state.cell)&&this.selectionBorder.setCursor(mxConstants.CURSOR_MOVABLE_VERTEX);if(0>=mxGraphHandler.prototype.maxCells||this.graph.getSelectionCount()<mxGraphHandler.prototype.maxCells){var a=this.graph.isCellResizable(this.state.cell);this.sizers=[];if(a||this. [...]
+2<=this.state.width&&2<=this.state.height){var b=0;a&&(this.singleSizer||(this.sizers.push(this.createSizer("nw-resize",b++)),this.sizers.push(this.createSizer("n-resize",b++)),this.sizers.push(this.createSizer("ne-resize",b++)),this.sizers.push(this.createSizer("w-resize",b++)),this.sizers.push(this.createSizer("e-resize",b++)),this.sizers.push(this.createSizer("sw-resize",b++)),this.sizers.push(this.createSizer("s-resize",b++))),this.sizers.push(this.createSizer("se-resize",b++)));a=th [...]
+null==a||a.relative||this.graph.isSwimlane(this.state.cell)||!this.graph.isLabelMovable(this.state.cell)||(this.labelShape=this.createSizer(mxConstants.CURSOR_LABEL_HANDLE,mxEvent.LABEL_HANDLE,mxConstants.LABEL_HANDLE_SIZE,mxConstants.LABEL_HANDLE_FILLCOLOR),this.sizers.push(this.labelShape))}else this.graph.isCellMovable(this.state.cell)&&!this.graph.isCellResizable(this.state.cell)&&2>this.state.width&&2>this.state.height&&(this.labelShape=this.createSizer(mxConstants.CURSOR_MOVABLE_VE [...]
+null,mxConstants.LABEL_HANDLE_FILLCOLOR),this.sizers.push(this.labelShape))}this.isRotationHandleVisible()&&(this.rotationShape=this.createSizer(this.rotationCursor,mxEvent.ROTATION_HANDLE,mxConstants.HANDLE_SIZE+3,mxConstants.HANDLE_FILLCOLOR),this.sizers.push(this.rotationShape));this.customHandles=this.createCustomHandles();this.redraw();this.constrainGroupByChildren&&this.updateMinBounds()};
+mxVertexHandler.prototype.isRotationHandleVisible=function(){return this.graph.isEnabled()&&this.rotationEnabled&&this.graph.isCellRotatable(this.state.cell)&&(0>=mxGraphHandler.prototype.maxCells||this.graph.getSelectionCount()<mxGraphHandler.prototype.maxCells)&&2<=this.state.width&&2<=this.state.height};mxVertexHandler.prototype.isConstrainedEvent=function(a){return mxEvent.isShiftDown(a.getEvent())||"fixed"==this.state.style[mxConstants.STYLE_ASPECT]};
+mxVertexHandler.prototype.isCenteredEvent=function(a,b){return!1};mxVertexHandler.prototype.createCustomHandles=function(){return null};
+mxVertexHandler.prototype.updateMinBounds=function(){var a=this.graph.getChildCells(this.state.cell);if(0<a.length&&(this.minBounds=this.graph.view.getBounds(a),null!=this.minBounds)){var a=this.state.view.scale,b=this.state.view.translate;this.minBounds.x-=this.state.x;this.minBounds.y-=this.state.y;this.minBounds.x/=a;this.minBounds.y/=a;this.minBounds.width/=a;this.minBounds.height/=a;this.x0=this.state.x/a-b.x;this.y0=this.state.y/a-b.y}};
+mxVertexHandler.prototype.getSelectionBounds=function(a){return new mxRectangle(Math.round(a.x),Math.round(a.y),Math.round(a.width),Math.round(a.height))};mxVertexHandler.prototype.createParentHighlightShape=function(a){return this.createSelectionShape(a)};mxVertexHandler.prototype.createSelectionShape=function(a){a=new mxRectangleShape(a,null,this.getSelectionColor());a.strokewidth=this.getSelectionStrokeWidth();a.isDashed=this.isSelectionDashed();return a};
+mxVertexHandler.prototype.getSelectionColor=function(){return mxConstants.VERTEX_SELECTION_COLOR};mxVertexHandler.prototype.getSelectionStrokeWidth=function(){return mxConstants.VERTEX_SELECTION_STROKEWIDTH};mxVertexHandler.prototype.isSelectionDashed=function(){return mxConstants.VERTEX_SELECTION_DASHED};
+mxVertexHandler.prototype.createSizer=function(a,b,c,d){c=c||mxConstants.HANDLE_SIZE;c=new mxRectangle(0,0,c,c);d=this.createSizerShape(c,b,d);d.isHtmlAllowed()&&null!=this.state.text&&this.state.text.node.parentNode==this.graph.container?(--d.bounds.height,--d.bounds.width,d.dialect=mxConstants.DIALECT_STRICTHTML,d.init(this.graph.container)):(d.dialect=this.graph.dialect!=mxConstants.DIALECT_SVG?mxConstants.DIALECT_MIXEDHTML:mxConstants.DIALECT_SVG,d.init(this.graph.getView().getOverla [...]
+mxEvent.redirectMouseEvents(d.node,this.graph,this.state);this.graph.isEnabled()&&d.setCursor(a);this.isSizerVisible(b)||(d.visible=!1);return d};mxVertexHandler.prototype.isSizerVisible=function(a){return!0};
+mxVertexHandler.prototype.createSizerShape=function(a,b,c){return null!=this.handleImage?(a=new mxRectangle(a.x,a.y,this.handleImage.width,this.handleImage.height),a=new mxImageShape(a,this.handleImage.src),a.preserveImageAspect=!1,a):b==mxEvent.ROTATION_HANDLE?new mxEllipse(a,c||mxConstants.HANDLE_FILLCOLOR,mxConstants.HANDLE_STROKECOLOR):new mxRectangleShape(a,c||mxConstants.HANDLE_FILLCOLOR,mxConstants.HANDLE_STROKECOLOR)};
+mxVertexHandler.prototype.moveSizerTo=function(a,b,c){null!=a&&(a.bounds.x=Math.floor(b-a.bounds.width/2),a.bounds.y=Math.floor(c-a.bounds.height/2),null!=a.node&&"none"!=a.node.style.display&&a.redraw())};
+mxVertexHandler.prototype.getHandleForEvent=function(a){function b(b){return null!=b&&(a.isSource(b)||null!=d&&mxUtils.intersects(b.bounds,d)&&"none"!=b.node.style.display&&"hidden"!=b.node.style.visibility)}var c=mxEvent.isMouseEvent(a.getEvent())?1:this.tolerance,d=this.allowHandleBoundsCheck&&(mxClient.IS_IE||0<c)?new mxRectangle(a.getGraphX()-c,a.getGraphY()-c,2*c,2*c):null;if(null!=this.customHandles&&this.isCustomHandleEvent(a))for(c=this.customHandles.length-1;0<=c;c--)if(b(this.c [...]
+c;if(b(this.rotationShape))return mxEvent.ROTATION_HANDLE;if(b(this.labelShape))return mxEvent.LABEL_HANDLE;if(null!=this.sizers)for(c=0;c<this.sizers.length;c++)if(b(this.sizers[c]))return c;return null};mxVertexHandler.prototype.isCustomHandleEvent=function(a){return!0};
+mxVertexHandler.prototype.mouseDown=function(a,b){var c=mxEvent.isMouseEvent(b.getEvent())?0:this.tolerance;!b.isConsumed()&&this.graph.isEnabled()&&(0<c||b.getState()==this.state)&&(c=this.getHandleForEvent(b),null!=c&&(this.start(b.getGraphX(),b.getGraphY(),c),b.consume()))};mxVertexHandler.prototype.isLivePreviewBorder=function(){return null!=this.state.shape&&null==this.state.shape.fill&&null==this.state.shape.stroke};
+mxVertexHandler.prototype.start=function(a,b,c){this.inTolerance=!0;this.childOffsetY=this.childOffsetX=0;this.index=c;this.startX=a;this.startY=b;a=this.state.view.graph.model;b=a.getParent(this.state.cell);this.state.view.currentRoot!=b&&(a.isVertex(b)||a.isEdge(b))&&(this.parentState=this.state.view.graph.view.getState(b));this.selectionBorder.node.style.display=c==mxEvent.ROTATION_HANDLE?"inline":"none";if(!this.livePreview||this.isLivePreviewBorder())this.preview=this.createSelectio [...]
+mxClient.IS_SVG&&0!=Number(this.state.style[mxConstants.STYLE_ROTATION]||"0")||null==this.state.text||this.state.text.node.parentNode!=this.graph.container?(this.preview.dialect=this.graph.dialect!=mxConstants.DIALECT_SVG?mxConstants.DIALECT_VML:mxConstants.DIALECT_SVG,this.preview.init(this.graph.view.getOverlayPane())):(this.preview.dialect=mxConstants.DIALECT_STRICTHTML,this.preview.init(this.graph.container));if(this.livePreview)for(this.hideSizers(),c==mxEvent.ROTATION_HANDLE?this.r [...]
+"":c==mxEvent.LABEL_HANDLE?this.labelShape.node.style.display="":null!=this.sizers&&null!=this.sizers[c]?this.sizers[c].node.style.display="":c<=mxEvent.CUSTOM_HANDLE&&null!=this.customHandles&&this.customHandles[mxEvent.CUSTOM_HANDLE-c].setVisible(!0),c=this.graph.getEdges(this.state.cell),this.edgeHandlers=[],a=0;a<c.length;a++)b=this.graph.selectionCellsHandler.getHandler(c[a]),null!=b&&this.edgeHandlers.push(b)};
+mxVertexHandler.prototype.setHandlesVisible=function(a){if(null!=this.sizers)for(var b=0;b<this.sizers.length;b++)this.sizers[b].node.style.display=a?"":"none";if(null!=this.customHandles)for(b=0;b<this.customHandles.length;b++)this.customHandles[b].setVisible(a)};mxVertexHandler.prototype.hideSizers=function(){this.setHandlesVisible(!1)};
+mxVertexHandler.prototype.checkTolerance=function(a){this.inTolerance&&null!=this.startX&&null!=this.startY&&(mxEvent.isMouseEvent(a.getEvent())||Math.abs(a.getGraphX()-this.startX)>this.graph.tolerance||Math.abs(a.getGraphY()-this.startY)>this.graph.tolerance)&&(this.inTolerance=!1)};mxVertexHandler.prototype.updateHint=function(a){};mxVertexHandler.prototype.removeHint=function(){};mxVertexHandler.prototype.roundAngle=function(a){return Math.round(10*a)/10};
+mxVertexHandler.prototype.roundLength=function(a){return Math.round(a)};
+mxVertexHandler.prototype.mouseMove=function(a,b){b.isConsumed()||null==this.index?this.graph.isMouseDown||null==this.getHandleForEvent(b)||b.consume(!1):(this.checkTolerance(b),this.inTolerance||(this.index<=mxEvent.CUSTOM_HANDLE?null!=this.customHandles&&(this.customHandles[mxEvent.CUSTOM_HANDLE-this.index].processEvent(b),this.customHandles[mxEvent.CUSTOM_HANDLE-this.index].active=!0):this.index==mxEvent.LABEL_HANDLE?this.moveLabel(b):this.index==mxEvent.ROTATION_HANDLE?this.rotateVer [...]
+this.updateHint(b)),b.consume())};mxVertexHandler.prototype.moveLabel=function(a){var b=new mxPoint(a.getGraphX(),a.getGraphY()),c=this.graph.view.translate,d=this.graph.view.scale;this.graph.isGridEnabledEvent(a.getEvent())&&(b.x=(this.graph.snap(b.x/d-c.x)+c.x)*d,b.y=(this.graph.snap(b.y/d-c.y)+c.y)*d);this.moveSizerTo(this.sizers[null!=this.rotationShape?this.sizers.length-2:this.sizers.length-1],b.x,b.y)};
+mxVertexHandler.prototype.rotateVertex=function(a){var b=new mxPoint(a.getGraphX(),a.getGraphY()),c=this.state.x+this.state.width/2-b.x,d=this.state.y+this.state.height/2-b.y;this.currentAlpha=0!=c?180*Math.atan(d/c)/Math.PI+90:0>d?180:0;0<c&&(this.currentAlpha-=180);this.rotationRaster&&this.graph.isGridEnabledEvent(a.getEvent())?(c=b.x-this.state.getCenterX(),d=b.y-this.state.getCenterY(),a=Math.max(1,5*Math.min(3,Math.max(0,Math.round(80/Math.abs(3*Math.abs(Math.sqrt(c*c+d*d)-20)))))) [...]
+Math.round(this.currentAlpha/a)*a):this.currentAlpha=this.roundAngle(this.currentAlpha);this.selectionBorder.rotation=this.currentAlpha;this.selectionBorder.redraw();this.livePreview&&this.redrawHandles()};
+mxVertexHandler.prototype.resizeVertex=function(a){var b=new mxPoint(this.state.getCenterX(),this.state.getCenterY()),c=mxUtils.toRadians(this.state.style[mxConstants.STYLE_ROTATION]||"0"),d=new mxPoint(a.getGraphX(),a.getGraphY()),e=this.graph.view.translate,f=this.graph.view.scale,g=Math.cos(-c),k=Math.sin(-c),l=d.x-this.startX,d=d.y-this.startY,m=k*l+g*d,l=g*l-k*d,d=m,g=this.graph.getCellGeometry(this.state.cell);this.unscaledBounds=this.union(g,l/f,d/f,this.index,this.graph.isGridEna [...]
+1,new mxPoint(0,0),this.isConstrainedEvent(a),this.isCenteredEvent(this.state,a));g.relative||(k=this.graph.getMaximumGraphBounds(),null!=k&&null!=this.parentState&&(k=mxRectangle.fromRectangle(k),k.x-=(this.parentState.x-e.x*f)/f,k.y-=(this.parentState.y-e.y*f)/f),this.graph.isConstrainChild(this.state.cell)&&(l=this.graph.getCellContainmentArea(this.state.cell),null!=l&&(d=this.graph.getOverlap(this.state.cell),0<d&&(l=mxRectangle.fromRectangle(l),l.x-=l.width*d,l.y-=l.height*d,l.width [...]
+d,l.height+=2*l.height*d),null==k?k=l:(k=mxRectangle.fromRectangle(k),k.intersect(l)))),null!=k&&(this.unscaledBounds.x<k.x&&(this.unscaledBounds.width-=k.x-this.unscaledBounds.x,this.unscaledBounds.x=k.x),this.unscaledBounds.y<k.y&&(this.unscaledBounds.height-=k.y-this.unscaledBounds.y,this.unscaledBounds.y=k.y),this.unscaledBounds.x+this.unscaledBounds.width>k.x+k.width&&(this.unscaledBounds.width-=this.unscaledBounds.x+this.unscaledBounds.width-k.x-k.width),this.unscaledBounds.y+this. [...]
+k.y+k.height&&(this.unscaledBounds.height-=this.unscaledBounds.y+this.unscaledBounds.height-k.y-k.height)));this.bounds=new mxRectangle((null!=this.parentState?this.parentState.x:e.x*f)+this.unscaledBounds.x*f,(null!=this.parentState?this.parentState.y:e.y*f)+this.unscaledBounds.y*f,this.unscaledBounds.width*f,this.unscaledBounds.height*f);g.relative&&null!=this.parentState&&(this.bounds.x+=this.state.x-this.parentState.x,this.bounds.y+=this.state.y-this.parentState.y);g=Math.cos(c);k=Ma [...]
+c=new mxPoint(this.bounds.getCenterX(),this.bounds.getCenterY());l=c.x-b.x;d=c.y-b.y;b=g*l-k*d-l;c=k*l+g*d-d;l=this.bounds.x-this.state.x;d=this.bounds.y-this.state.y;e=g*l-k*d;g=k*l+g*d;this.bounds.x+=b;this.bounds.y+=c;this.unscaledBounds.x=this.roundLength(this.unscaledBounds.x+b/f);this.unscaledBounds.y=this.roundLength(this.unscaledBounds.y+c/f);this.unscaledBounds.width=this.roundLength(this.unscaledBounds.width);this.unscaledBounds.height=this.roundLength(this.unscaledBounds.heigh [...]
+0==b&&0==c?this.childOffsetY=this.childOffsetX=0:(this.childOffsetX=this.state.x-this.bounds.x+e,this.childOffsetY=this.state.y-this.bounds.y+g);this.livePreview&&this.updateLivePreview(a);null!=this.preview&&this.drawPreview()};
+mxVertexHandler.prototype.updateLivePreview=function(a){var b=this.graph.view.scale,c=this.graph.view.translate;a=this.state.clone();this.state.x=this.bounds.x;this.state.y=this.bounds.y;this.state.origin=new mxPoint(this.state.x/b-c.x,this.state.y/b-c.y);this.state.width=this.bounds.width;this.state.height=this.bounds.height;this.state.unscaledWidth=null;b=this.state.absoluteOffset;new mxPoint(b.x,b.y);this.state.absoluteOffset.x=0;this.state.absoluteOffset.y=0;b=this.graph.getCellGeome [...]
+null!=b&&(c=b.offset||this.EMPTY_POINT,null==c||b.relative||(this.state.absoluteOffset.x=this.state.view.scale*c.x,this.state.absoluteOffset.y=this.state.view.scale*c.y),this.state.view.updateVertexLabelOffset(this.state));this.state.view.graph.cellRenderer.redraw(this.state,!0);this.state.view.invalidate(this.state.cell);this.state.invalid=!1;this.state.view.validate();this.redrawHandles();this.state.setState(a)};
+mxVertexHandler.prototype.mouseUp=function(a,b){if(null!=this.index&&null!=this.state){var c=new mxPoint(b.getGraphX(),b.getGraphY());this.graph.getModel().beginUpdate();try{if(this.index<=mxEvent.CUSTOM_HANDLE)null!=this.customHandles&&(this.customHandles[mxEvent.CUSTOM_HANDLE-this.index].active=!1,this.customHandles[mxEvent.CUSTOM_HANDLE-this.index].execute());else if(this.index==mxEvent.ROTATION_HANDLE)if(null!=this.currentAlpha){var d=this.currentAlpha-(this.state.style[mxConstants.S [...]
+0);0!=d&&this.rotateCell(this.state.cell,d)}else this.rotateClick();else{var e=this.graph.isGridEnabledEvent(b.getEvent()),f=mxUtils.toRadians(this.state.style[mxConstants.STYLE_ROTATION]||"0"),g=Math.cos(-f),k=Math.sin(-f),l=c.x-this.startX,m=c.y-this.startY,c=k*l+g*m,l=g*l-k*m,m=c,n=this.graph.view.scale,p=this.isRecursiveResize(this.state,b);this.resizeCell(this.state.cell,this.roundLength(l/n),this.roundLength(m/n),this.index,e,this.isConstrainedEvent(b),p)}}finally{this.graph.getMod [...]
+this.reset()}};mxVertexHandler.prototype.isRecursiveResize=function(a,b){return this.graph.isRecursiveResize(this.state)};mxVertexHandler.prototype.rotateClick=function(){};
+mxVertexHandler.prototype.rotateCell=function(a,b,c){if(0!=b){var d=this.graph.getModel();if(d.isVertex(a)||d.isEdge(a)){if(!d.isEdge(a)){var e=this.graph.view.getState(a),e=null!=e?e.style:this.graph.getCellStyle(a);null!=e&&this.graph.setCellStyles(mxConstants.STYLE_ROTATION,(e[mxConstants.STYLE_ROTATION]||0)+b,[a])}e=this.graph.getCellGeometry(a);if(null!=e){var f=this.graph.getCellGeometry(c);null==f||d.isEdge(c)||(e=e.clone(),e.rotate(b,new mxPoint(f.width/2,f.height/2)),d.setGeomet [...]
+if(d.isVertex(a)&&!e.relative||d.isEdge(a))for(c=d.getChildCount(a),e=0;e<c;e++)this.rotateCell(d.getChildAt(a,e),b,a)}}}};
+mxVertexHandler.prototype.reset=function(){null!=this.sizers&&null!=this.index&&null!=this.sizers[this.index]&&"none"==this.sizers[this.index].node.style.display&&(this.sizers[this.index].node.style.display="");this.index=this.inTolerance=this.currentAlpha=null;null!=this.preview&&(this.preview.destroy(),this.preview=null);if(this.livePreview&&null!=this.sizers)for(var a=0;a<this.sizers.length;a++)null!=this.sizers[a]&&(this.sizers[a].node.style.display="");if(null!=this.customHandles)fo [...]
+(this.customHandles[a].active=!1,this.customHandles[a].reset()):this.customHandles[a].setVisible(!0);null!=this.selectionBorder&&(this.selectionBorder.node.style.display="inline",this.selectionBounds=this.getSelectionBounds(this.state),this.bounds=new mxRectangle(this.selectionBounds.x,this.selectionBounds.y,this.selectionBounds.width,this.selectionBounds.height),this.drawPreview());this.removeHint();this.redrawHandles();this.unscaledBounds=this.edgeHandlers=null};
+mxVertexHandler.prototype.resizeCell=function(a,b,c,d,e,f,g){e=this.graph.model.getGeometry(a);null!=e&&(d==mxEvent.LABEL_HANDLE?(c=this.graph.view.scale,b=Math.round((this.labelShape.bounds.getCenterX()-this.startX)/c),c=Math.round((this.labelShape.bounds.getCenterY()-this.startY)/c),e=e.clone(),null==e.offset?e.offset=new mxPoint(b,c):(e.offset.x+=b,e.offset.y+=c),this.graph.model.setGeometry(a,e)):null!=this.unscaledBounds&&(c=this.graph.view.scale,0==this.childOffsetX&&0==this.childO [...]
+Math.round(this.childOffsetX/c),Math.round(this.childOffsetY/c)),this.graph.resizeCell(a,this.unscaledBounds,g)))};mxVertexHandler.prototype.moveChildren=function(a,b,c){for(var d=this.graph.getModel(),e=d.getChildCount(a),f=0;f<e;f++){var g=d.getChildAt(a,f),k=this.graph.getCellGeometry(g);null!=k&&(k=k.clone(),k.translate(b,c),d.setGeometry(g,k))}};
+mxVertexHandler.prototype.union=function(a,b,c,d,e,f,g,k,l){if(this.singleSizer)return d=a.x+a.width+b,g=a.y+a.height+c,e&&(d=this.graph.snap(d/f)*f,g=this.graph.snap(g/f)*f),f=new mxRectangle(a.x,a.y,0,0),f.add(new mxRectangle(d,g,0,0)),f;var m=a.width,n=a.height,p=a.x-g.x*f,q=p+m;a=a.y-g.y*f;var r=a+n,t=p+m/2,u=a+n/2;4<d?(r+=c,e&&(r=this.graph.snap(r/f)*f)):3>d&&(a+=c,e&&(a=this.graph.snap(a/f)*f));if(0==d||3==d||5==d)p+=b,e&&(p=this.graph.snap(p/f)*f);else if(2==d||4==d||7==d)q+=b,e&& [...]
+f)*f);e=q-p;c=r-a;k&&(k=this.graph.getCellGeometry(this.state.cell),null!=k&&(k=k.width/k.height,1==d||2==d||7==d||6==d?e=c*k:c=e/k,0==d&&(p=q-e,a=r-c)));l&&(e+=e-m,c+=c-n,p+=t-(p+e/2),a+=u-(a+c/2));0>e&&(p+=e,e=Math.abs(e));0>c&&(a+=c,c=Math.abs(c));d=new mxRectangle(p+g.x*f,a+g.y*f,e,c);null!=this.minBounds&&(d.width=Math.max(d.width,this.minBounds.x*f+this.minBounds.width*f+Math.max(0,this.x0*f-d.x)),d.height=Math.max(d.height,this.minBounds.y*f+this.minBounds.height*f+Math.max(0,this [...]
+return d};mxVertexHandler.prototype.redraw=function(){this.selectionBounds=this.getSelectionBounds(this.state);this.bounds=new mxRectangle(this.selectionBounds.x,this.selectionBounds.y,this.selectionBounds.width,this.selectionBounds.height);this.redrawHandles();this.drawPreview()};
+mxVertexHandler.prototype.getHandlePadding=function(){var a=new mxPoint(0,0),b=this.tolerance;null!=this.sizers&&0<this.sizers.length&&null!=this.sizers[0]&&(this.bounds.width<2*this.sizers[0].bounds.width+2*b||this.bounds.height<2*this.sizers[0].bounds.height+2*b)&&(b/=2,a.x=this.sizers[0].bounds.width+b,a.y=this.sizers[0].bounds.height+b);return a};
+mxVertexHandler.prototype.redrawHandles=function(){var a=this.tolerance;this.verticalOffset=this.horizontalOffset=0;var b=this.bounds;if(null!=this.sizers&&0<this.sizers.length&&null!=this.sizers[0]){if(null==this.index&&this.manageSizers&&8<=this.sizers.length){var c=this.getHandlePadding();this.horizontalOffset=c.x;this.verticalOffset=c.y;if(0!=this.horizontalOffset||0!=this.verticalOffset)b=new mxRectangle(b.x,b.y,b.width,b.height),b.x-=this.horizontalOffset/2,b.width+=this.horizontal [...]
+this.verticalOffset/2,b.height+=this.verticalOffset;8<=this.sizers.length&&(b.width<2*this.sizers[0].bounds.width+2*a||b.height<2*this.sizers[0].bounds.height+2*a?(this.sizers[0].node.style.display="none",this.sizers[2].node.style.display="none",this.sizers[5].node.style.display="none",this.sizers[7].node.style.display="none"):(this.sizers[0].node.style.display="",this.sizers[2].node.style.display="",this.sizers[5].node.style.display="",this.sizers[7].node.style.display=""))}a=b.x+b.widt [...]
+if(this.singleSizer)this.moveSizerTo(this.sizers[0],a,c);else{var d=b.x+b.width/2,e=b.y+b.height/2;if(8<=this.sizers.length){var f="nw-resize n-resize ne-resize e-resize se-resize s-resize sw-resize w-resize".split(" "),g=mxUtils.toRadians(this.state.style[mxConstants.STYLE_ROTATION]||"0"),k=Math.cos(g),l=Math.sin(g),g=Math.round(4*g/Math.PI),m=new mxPoint(b.getCenterX(),b.getCenterY()),n=mxUtils.getRotatedPoint(new mxPoint(b.x,b.y),k,l,m);this.moveSizerTo(this.sizers[0],n.x,n.y);this.si [...]
+g,f.length)]);n.x=d;n.y=b.y;n=mxUtils.getRotatedPoint(n,k,l,m);this.moveSizerTo(this.sizers[1],n.x,n.y);this.sizers[1].setCursor(f[mxUtils.mod(1+g,f.length)]);n.x=a;n.y=b.y;n=mxUtils.getRotatedPoint(n,k,l,m);this.moveSizerTo(this.sizers[2],n.x,n.y);this.sizers[2].setCursor(f[mxUtils.mod(2+g,f.length)]);n.x=b.x;n.y=e;n=mxUtils.getRotatedPoint(n,k,l,m);this.moveSizerTo(this.sizers[3],n.x,n.y);this.sizers[3].setCursor(f[mxUtils.mod(7+g,f.length)]);n.x=a;n.y=e;n=mxUtils.getRotatedPoint(n,k,l [...]
+n.x,n.y);this.sizers[4].setCursor(f[mxUtils.mod(3+g,f.length)]);n.x=b.x;n.y=c;n=mxUtils.getRotatedPoint(n,k,l,m);this.moveSizerTo(this.sizers[5],n.x,n.y);this.sizers[5].setCursor(f[mxUtils.mod(6+g,f.length)]);n.x=d;n.y=c;n=mxUtils.getRotatedPoint(n,k,l,m);this.moveSizerTo(this.sizers[6],n.x,n.y);this.sizers[6].setCursor(f[mxUtils.mod(5+g,f.length)]);n.x=a;n.y=c;n=mxUtils.getRotatedPoint(n,k,l,m);this.moveSizerTo(this.sizers[7],n.x,n.y);this.sizers[7].setCursor(f[mxUtils.mod(4+g,f.length) [...]
+d+this.state.absoluteOffset.x,e+this.state.absoluteOffset.y)}else 2<=this.state.width&&2<=this.state.height?this.moveSizerTo(this.sizers[0],d+this.state.absoluteOffset.x,e+this.state.absoluteOffset.y):this.moveSizerTo(this.sizers[0],this.state.x,this.state.y)}}null!=this.rotationShape&&(g=mxUtils.toRadians(null!=this.currentAlpha?this.currentAlpha:this.state.style[mxConstants.STYLE_ROTATION]||"0"),k=Math.cos(g),l=Math.sin(g),m=new mxPoint(this.state.getCenterX(),this.state.getCenterY()), [...]
+b.width/2,b.y+this.rotationHandleVSpacing),k,l,m),null!=this.rotationShape.node&&this.moveSizerTo(this.rotationShape,n.x,n.y));null!=this.selectionBorder&&(this.selectionBorder.rotation=Number(this.state.style[mxConstants.STYLE_ROTATION]||"0"));if(null!=this.edgeHandlers)for(b=0;b<this.edgeHandlers.length;b++)this.edgeHandlers[b].redraw();if(null!=this.customHandles)for(b=0;b<this.customHandles.length;b++)a=this.customHandles[b].shape.node.style.display,this.customHandles[b].redraw(),thi [...]
+a;this.updateParentHighlight()};
+mxVertexHandler.prototype.updateParentHighlight=function(){if(null!=this.selectionBorder)if(null!=this.parentHighlight){var a=this.graph.model.getParent(this.state.cell);if(this.graph.model.isVertex(a)){var a=this.graph.view.getState(a),b=this.parentHighlight.bounds;null==a||b.x==a.x&&b.y==a.y&&b.width==a.width&&b.height==a.height||(this.parentHighlight.bounds=a,this.parentHighlight.redraw())}else this.parentHighlight.destroy(),this.parentHighlight=null}else this.parentHighlightEnabled&& [...]
+this.graph.model.isVertex(a)&&(a=this.graph.view.getState(a),null!=a&&(this.parentHighlight=this.createParentHighlightShape(a),this.parentHighlight.dialect=this.graph.dialect!=mxConstants.DIALECT_SVG?mxConstants.DIALECT_VML:mxConstants.DIALECT_SVG,this.parentHighlight.pointerEvents=!1,this.parentHighlight.rotation=Number(a.style[mxConstants.STYLE_ROTATION]||"0"),this.parentHighlight.init(this.graph.getView().getOverlayPane()))))};
+mxVertexHandler.prototype.drawPreview=function(){null!=this.preview&&(this.preview.bounds=this.bounds,this.preview.node.parentNode==this.graph.container&&(this.preview.bounds.width=Math.max(0,this.preview.bounds.width-1),this.preview.bounds.height=Math.max(0,this.preview.bounds.height-1)),this.preview.rotation=Number(this.state.style[mxConstants.STYLE_ROTATION]||"0"),this.preview.redraw());this.selectionBorder.bounds=this.bounds;this.selectionBorder.redraw();null!=this.parentHighlight&&t [...]
+mxVertexHandler.prototype.destroy=function(){null!=this.escapeHandler&&(this.state.view.graph.removeListener(this.escapeHandler),this.escapeHandler=null);null!=this.preview&&(this.preview.destroy(),this.preview=null);null!=this.parentHighlight&&(this.parentHighlight.destroy(),this.parentHighlight=null);null!=this.selectionBorder&&(this.selectionBorder.destroy(),this.selectionBorder=null);this.labelShape=null;this.removeHint();if(null!=this.sizers){for(var a=0;a<this.sizers.length;a++)thi [...]
+this.sizers=null}if(null!=this.customHandles){for(a=0;a<this.customHandles.length;a++)this.customHandles[a].destroy();this.customHandles=null}};function mxEdgeHandler(a){null!=a&&(this.state=a,this.init(),this.escapeHandler=mxUtils.bind(this,function(a,c){this.reset()}),this.state.view.graph.addListener(mxEvent.ESCAPE,this.escapeHandler))}mxEdgeHandler.prototype.graph=null;mxEdgeHandler.prototype.state=null;mxEdgeHandler.prototype.marker=null;mxEdgeHandler.prototype.constraintHandler=null;
+mxEdgeHandler.prototype.error=null;mxEdgeHandler.prototype.shape=null;mxEdgeHandler.prototype.bends=null;mxEdgeHandler.prototype.labelShape=null;mxEdgeHandler.prototype.cloneEnabled=!0;mxEdgeHandler.prototype.addEnabled=!1;mxEdgeHandler.prototype.removeEnabled=!1;mxEdgeHandler.prototype.dblClickRemoveEnabled=!1;mxEdgeHandler.prototype.mergeRemoveEnabled=!1;mxEdgeHandler.prototype.straightRemoveEnabled=!1;mxEdgeHandler.prototype.virtualBendsEnabled=!1;mxEdgeHandler.prototype.virtualBendOp [...]
+mxEdgeHandler.prototype.parentHighlightEnabled=!1;mxEdgeHandler.prototype.preferHtml=!1;mxEdgeHandler.prototype.allowHandleBoundsCheck=!0;mxEdgeHandler.prototype.snapToTerminals=!1;mxEdgeHandler.prototype.handleImage=null;mxEdgeHandler.prototype.tolerance=0;mxEdgeHandler.prototype.outlineConnect=!1;mxEdgeHandler.prototype.manageLabelHandle=!1;
+mxEdgeHandler.prototype.init=function(){this.graph=this.state.view.graph;this.marker=this.createMarker();this.constraintHandler=new mxConstraintHandler(this.graph);this.points=[];this.abspoints=this.getSelectionPoints(this.state);this.shape=this.createSelectionShape(this.abspoints);this.shape.dialect=this.graph.dialect!=mxConstants.DIALECT_SVG?mxConstants.DIALECT_MIXEDHTML:mxConstants.DIALECT_SVG;this.shape.init(this.graph.getView().getOverlayPane());this.shape.pointerEvents=!1;this.shap [...]
+mxEvent.redirectMouseEvents(this.shape.node,this.graph,this.state);this.preferHtml=null!=this.state.text&&this.state.text.node.parentNode==this.graph.container;if(!this.preferHtml){var a=this.state.getVisibleTerminalState(!0);null!=a&&(this.preferHtml=null!=a.text&&a.text.node.parentNode==this.graph.container);this.preferHtml||(a=this.state.getVisibleTerminalState(!1),null!=a&&(this.preferHtml=null!=a.text&&a.text.node.parentNode==this.graph.container))}this.parentHighlightEnabled&&(a=th [...]
+this.graph.model.isVertex(a)&&(a=this.graph.view.getState(a),null!=a&&(this.parentHighlight=this.createParentHighlightShape(a),this.parentHighlight.dialect=this.graph.dialect!=mxConstants.DIALECT_SVG?mxConstants.DIALECT_VML:mxConstants.DIALECT_SVG,this.parentHighlight.pointerEvents=!1,this.parentHighlight.rotation=Number(a.style[mxConstants.STYLE_ROTATION]||"0"),this.parentHighlight.init(this.graph.getView().getOverlayPane()))));if(this.graph.getSelectionCount()<mxGraphHandler.prototype. [...]
+0>=mxGraphHandler.prototype.maxCells)this.bends=this.createBends(),this.isVirtualBendsEnabled()&&(this.virtualBends=this.createVirtualBends());this.label=new mxPoint(this.state.absoluteOffset.x,this.state.absoluteOffset.y);this.labelShape=this.createLabelHandleShape();this.initBend(this.labelShape);this.labelShape.setCursor(mxConstants.CURSOR_LABEL_HANDLE);this.customHandles=this.createCustomHandles();this.redraw()};mxEdgeHandler.prototype.createCustomHandles=function(){return null};
+mxEdgeHandler.prototype.isVirtualBendsEnabled=function(a){return this.virtualBendsEnabled&&(null==this.state.style[mxConstants.STYLE_EDGE]||this.state.style[mxConstants.STYLE_EDGE]==mxConstants.NONE||1==this.state.style[mxConstants.STYLE_NOEDGESTYLE])&&"arrow"!=mxUtils.getValue(this.state.style,mxConstants.STYLE_SHAPE,null)};mxEdgeHandler.prototype.isAddPointEvent=function(a){return mxEvent.isShiftDown(a)};mxEdgeHandler.prototype.isRemovePointEvent=function(a){return mxEvent.isShiftDown(a)};
+mxEdgeHandler.prototype.getSelectionPoints=function(a){return a.absolutePoints};mxEdgeHandler.prototype.createParentHighlightShape=function(a){a=new mxRectangleShape(a,null,this.getSelectionColor());a.strokewidth=this.getSelectionStrokeWidth();a.isDashed=this.isSelectionDashed();return a};mxEdgeHandler.prototype.createSelectionShape=function(a){a=new this.state.shape.constructor;a.outline=!0;a.apply(this.state);a.isDashed=this.isSelectionDashed();a.stroke=this.getSelectionColor();a.isSha [...]
+mxEdgeHandler.prototype.getSelectionColor=function(){return mxConstants.EDGE_SELECTION_COLOR};mxEdgeHandler.prototype.getSelectionStrokeWidth=function(){return mxConstants.EDGE_SELECTION_STROKEWIDTH};mxEdgeHandler.prototype.isSelectionDashed=function(){return mxConstants.EDGE_SELECTION_DASHED};mxEdgeHandler.prototype.isConnectableCell=function(a){return!0};mxEdgeHandler.prototype.getCellAt=function(a,b){return this.outlineConnect?null:this.graph.getCellAt(a,b)};
+mxEdgeHandler.prototype.createMarker=function(){var a=new mxCellMarker(this.graph),b=this;a.getCell=function(a){var c=mxCellMarker.prototype.getCell.apply(this,arguments);c!=b.state.cell&&null!=c||null==b.currentPoint||(c=b.graph.getCellAt(b.currentPoint.x,b.currentPoint.y));if(null!=c&&!this.graph.isCellConnectable(c)){var e=this.graph.getModel().getParent(c);this.graph.getModel().isVertex(e)&&this.graph.isCellConnectable(e)&&(c=e)}e=b.graph.getModel();if(this.graph.isSwimlane(c)&&null! [...]
+this.graph.hitsSwimlaneContent(c,b.currentPoint.x,b.currentPoint.y)||!b.isConnectableCell(c)||c==b.state.cell||null!=c&&!b.graph.connectableEdges&&e.isEdge(c)||e.isAncestor(b.state.cell,c))c=null;this.graph.isCellConnectable(c)||(c=null);return c};a.isValidState=function(a){var c=b.graph.getModel(),c=b.graph.view.getTerminalPort(a,b.graph.view.getState(c.getTerminal(b.state.cell,!b.isSource)),!b.isSource),c=null!=c?c.cell:null;b.error=b.validateConnection(b.isSource?a.cell:c,b.isSource?c [...]
+return null==b.error};return a};mxEdgeHandler.prototype.validateConnection=function(a,b){return this.graph.getEdgeValidationError(this.state.cell,a,b)};
+mxEdgeHandler.prototype.createBends=function(){for(var a=this.state.cell,b=[],c=0;c<this.abspoints.length;c++)if(this.isHandleVisible(c)){var d=c==this.abspoints.length-1,e=0==c||d;(e||this.graph.isCellBendable(a))&&mxUtils.bind(this,function(a){var d=this.createHandleShape(a);this.initBend(d,mxUtils.bind(this,mxUtils.bind(this,function(){this.dblClickRemoveEnabled&&this.removePoint(this.state,a)})));this.isHandleEnabled(c)&&d.setCursor(e?mxConstants.CURSOR_TERMINAL_HANDLE:mxConstants.CU [...]
+b.push(d);e||(this.points.push(new mxPoint(0,0)),d.node.style.visibility="hidden")})(c)}return b};mxEdgeHandler.prototype.createVirtualBends=function(){var a=[];if(this.graph.isCellBendable(this.state.cell))for(var b=1;b<this.abspoints.length;b++)mxUtils.bind(this,function(b){this.initBend(b);b.setCursor(mxConstants.CURSOR_VIRTUAL_BEND_HANDLE);a.push(b)})(this.createHandleShape());return a};mxEdgeHandler.prototype.isHandleEnabled=function(a){return!0};
+mxEdgeHandler.prototype.isHandleVisible=function(a){var b=this.state.getVisibleTerminalState(!0),c=this.state.getVisibleTerminalState(!1),d=this.graph.getCellGeometry(this.state.cell);return(null!=d?this.graph.view.getEdgeStyle(this.state,d.points,b,c):null)!=mxEdgeStyle.EntityRelation||0==a||a==this.abspoints.length-1};
+mxEdgeHandler.prototype.createHandleShape=function(a){if(null!=this.handleImage)return a=new mxImageShape(new mxRectangle(0,0,this.handleImage.width,this.handleImage.height),this.handleImage.src),a.preserveImageAspect=!1,a;a=mxConstants.HANDLE_SIZE;this.preferHtml&&--a;return new mxRectangleShape(new mxRectangle(0,0,a,a),mxConstants.HANDLE_FILLCOLOR,mxConstants.HANDLE_STROKECOLOR)};
+mxEdgeHandler.prototype.createLabelHandleShape=function(){if(null!=this.labelHandleImage){var a=new mxImageShape(new mxRectangle(0,0,this.labelHandleImage.width,this.labelHandleImage.height),this.labelHandleImage.src);a.preserveImageAspect=!1;return a}a=mxConstants.LABEL_HANDLE_SIZE;return new mxRectangleShape(new mxRectangle(0,0,a,a),mxConstants.LABEL_HANDLE_FILLCOLOR,mxConstants.HANDLE_STROKECOLOR)};
+mxEdgeHandler.prototype.initBend=function(a,b){this.preferHtml?(a.dialect=mxConstants.DIALECT_STRICTHTML,a.init(this.graph.container)):(a.dialect=this.graph.dialect!=mxConstants.DIALECT_SVG?mxConstants.DIALECT_MIXEDHTML:mxConstants.DIALECT_SVG,a.init(this.graph.getView().getOverlayPane()));mxEvent.redirectMouseEvents(a.node,this.graph,this.state,null,null,null,b);(mxClient.IS_QUIRKS||8==document.documentMode)&&mxEvent.addListener(a.node,"dragstart",function(a){mxEvent.consume(a);return!1 [...]
+a.node.setAttribute("pointer-events","none")};
+mxEdgeHandler.prototype.getHandleForEvent=function(a){function b(b){if(null!=b&&"none"!=b.node.style.display&&"hidden"!=b.node.style.visibility&&(a.isSource(b)||null!=d&&mxUtils.intersects(b.bounds,d))){var c=a.getGraphX()-b.bounds.getCenterX();b=a.getGraphY()-b.bounds.getCenterY();c=c*c+b*b;if(null==e||c<=e)return e=c,!0}return!1}var c=mxEvent.isMouseEvent(a.getEvent())?1:this.tolerance,d=this.allowHandleBoundsCheck&&(mxClient.IS_IE||0<c)?new mxRectangle(a.getGraphX()-c,a.getGraphY()-c, [...]
+null,e=null,c=null;if(null!=this.customHandles&&this.isCustomHandleEvent(a))for(var f=this.customHandles.length-1;0<=f;f--)if(b(this.customHandles[f].shape))return mxEvent.CUSTOM_HANDLE-f;if(a.isSource(this.state.text)||b(this.labelShape))c=mxEvent.LABEL_HANDLE;if(null!=this.bends)for(f=0;f<this.bends.length;f++)b(this.bends[f])&&(c=f);if(null!=this.virtualBends&&this.isAddVirtualBendEvent(a))for(f=0;f<this.virtualBends.length;f++)b(this.virtualBends[f])&&(c=mxEvent.VIRTUAL_HANDLE-f);return c};
+mxEdgeHandler.prototype.isAddVirtualBendEvent=function(a){return!0};mxEdgeHandler.prototype.isCustomHandleEvent=function(a){return!0};
+mxEdgeHandler.prototype.mouseDown=function(a,b){var c=this.getHandleForEvent(b);if(null!=this.bends&&null!=this.bends[c]){var d=this.bends[c].bounds;this.snapPoint=new mxPoint(d.getCenterX(),d.getCenterY())}if(this.addEnabled&&null==c&&this.isAddPointEvent(b.getEvent()))this.addPoint(this.state,b.getEvent()),b.consume();else if(null!=c&&!b.isConsumed()&&this.graph.isEnabled()){if(this.removeEnabled&&this.isRemovePointEvent(b.getEvent()))this.removePoint(this.state,c);else if(c!=mxEvent.L [...]
+this.graph.isLabelMovable(b.getCell()))c<=mxEvent.VIRTUAL_HANDLE&&mxUtils.setOpacity(this.virtualBends[mxEvent.VIRTUAL_HANDLE-c].node,100),this.start(b.getX(),b.getY(),c);b.consume()}};
+mxEdgeHandler.prototype.start=function(a,b,c){this.startX=a;this.startY=b;this.isSource=null==this.bends?!1:0==c;this.isTarget=null==this.bends?!1:c==this.bends.length-1;this.isLabel=c==mxEvent.LABEL_HANDLE;if(this.isSource||this.isTarget){if(a=this.state.cell,b=this.graph.model.getTerminal(a,this.isSource),null==b&&this.graph.isTerminalPointMovable(a,this.isSource)||null!=b&&this.graph.isCellDisconnectable(a,b,this.isSource))this.index=c}else this.index=c;if(this.index<=mxEvent.CUSTOM_H [...]
+mxEvent.VIRTUAL_HANDLE&&null!=this.customHandles)for(c=0;c<this.customHandles.length;c++)c!=mxEvent.CUSTOM_HANDLE-this.index&&this.customHandles[c].setVisible(!1)};mxEdgeHandler.prototype.clonePreviewState=function(a,b){return this.state.clone()};mxEdgeHandler.prototype.getSnapToTerminalTolerance=function(){return this.graph.gridSize*this.graph.view.scale/2};mxEdgeHandler.prototype.updateHint=function(a,b){};mxEdgeHandler.prototype.removeHint=function(){};mxEdgeHandler.prototype.roundLen [...]
+mxEdgeHandler.prototype.isSnapToTerminalsEvent=function(a){return this.snapToTerminals&&!mxEvent.isAltDown(a.getEvent())};
+mxEdgeHandler.prototype.getPointForEvent=function(a){var b=this.graph.getView(),c=b.scale,d=new mxPoint(this.roundLength(a.getGraphX()/c)*c,this.roundLength(a.getGraphY()/c)*c),e=this.getSnapToTerminalTolerance(),f=!1,g=!1;if(0<e&&this.isSnapToTerminalsEvent(a)){var k=function(a){null!=a&&l.call(this,new mxPoint(b.getRoutingCenterX(a),b.getRoutingCenterY(a)))},l=function(a){if(null!=a){var b=a.x;Math.abs(d.x-b)<e&&(d.x=b,f=!0);a=a.y;Math.abs(d.y-a)<e&&(d.y=a,g=!0)}};k.call(this,this.stat [...]
+k.call(this,this.state.getVisibleTerminalState(!1));if(null!=this.state.absolutePoints)for(k=0;k<this.state.absolutePoints.length;k++)l.call(this,this.state.absolutePoints[k])}this.graph.isGridEnabledEvent(a.getEvent())&&(a=b.translate,f||(d.x=(this.graph.snap(d.x/c-a.x)+a.x)*c),g||(d.y=(this.graph.snap(d.y/c-a.y)+a.y)*c));return d};
+mxEdgeHandler.prototype.getPreviewTerminalState=function(a){this.constraintHandler.update(a,this.isSource,!0,a.isSource(this.marker.highlight.shape)?null:this.currentPoint);if(null!=this.constraintHandler.currentFocus&&null!=this.constraintHandler.currentConstraint)return null!=this.marker.highlight&&null!=this.marker.highlight.state&&this.marker.highlight.state.cell==this.constraintHandler.currentFocus.cell?"transparent"!=this.marker.highlight.shape.stroke&&(this.marker.highlight.shape. [...]
+this.marker.highlight.repaint()):this.marker.markCell(this.constraintHandler.currentFocus.cell,"transparent"),a=this.graph.getModel(),a=this.graph.view.getTerminalPort(this.state,this.graph.view.getState(a.getTerminal(this.state.cell,!this.isSource)),!this.isSource),a=null!=a?a.cell:null,this.error=this.validateConnection(this.isSource?this.constraintHandler.currentFocus.cell:a,this.isSource?a:this.constraintHandler.currentFocus.cell),a=null,null==this.error?a=this.constraintHandler.curr [...]
+this.constraintHandler.reset(),a;if(this.graph.isIgnoreTerminalEvent(a.getEvent()))return this.marker.reset(),null;this.marker.process(a);return this.marker.getValidState()};
+mxEdgeHandler.prototype.getPreviewPoints=function(a,b){var c=this.graph.getCellGeometry(this.state.cell),c=null!=c.points?c.points.slice():null,d=new mxPoint(a.x,a.y),e=null;if(this.isSource||this.isTarget)this.graph.resetEdgesOnConnect&&(c=null);else if(this.convertPoint(d,!1),null==c)c=[d];else{this.index<=mxEvent.VIRTUAL_HANDLE&&c.splice(mxEvent.VIRTUAL_HANDLE-this.index,0,d);if(!this.isSource&&!this.isTarget){for(var f=0;f<this.bends.length;f++)if(f!=this.index){var g=this.bends[f];n [...]
+a.x,a.y)&&(this.index<=mxEvent.VIRTUAL_HANDLE?c.splice(mxEvent.VIRTUAL_HANDLE-this.index,1):c.splice(this.index-1,1),e=c)}if(null==e&&this.straightRemoveEnabled&&(null==b||!mxEvent.isAltDown(b.getEvent()))){f=this.graph.tolerance*this.graph.tolerance;g=this.state.absolutePoints.slice();g[this.index]=a;var k=this.state.getVisibleTerminalState(!0);if(null!=k){var l=this.graph.getConnectionConstraint(this.state,k,!0);if(null==l||null==this.graph.getConnectionPoint(k,l))g[0]=new mxPoint(k.vi [...]
+k.view.getRoutingCenterY(k))}k=this.state.getVisibleTerminalState(!1);null!=k&&(l=this.graph.getConnectionConstraint(this.state,k,!1),null==l||null==this.graph.getConnectionPoint(k,l))&&(g[g.length-1]=new mxPoint(k.view.getRoutingCenterX(k),k.view.getRoutingCenterY(k)));l=this.index;0<l&&l<g.length-1&&mxUtils.ptSegDistSq(g[l-1].x,g[l-1].y,g[l+1].x,g[l+1].y,a.x,a.y)<f&&(c.splice(l-1,1),e=c)}}null==e&&this.index>mxEvent.VIRTUAL_HANDLE&&(c[this.index-1]=d)}return null!=e?e:c};
+mxEdgeHandler.prototype.isOutlineConnectEvent=function(a){var b=mxUtils.getOffset(this.graph.container),c=a.getEvent(),d=mxEvent.getClientX(c),c=mxEvent.getClientY(c),e=document.documentElement,f=this.currentPoint.x-this.graph.container.scrollLeft+b.x-((window.pageXOffset||e.scrollLeft)-(e.clientLeft||0)),b=this.currentPoint.y-this.graph.container.scrollTop+b.y-((window.pageYOffset||e.scrollTop)-(e.clientTop||0));return this.outlineConnect&&!mxEvent.isShiftDown(a.getEvent())&&(a.isSource [...]
+mxEvent.isAltDown(a.getEvent())&&null!=a.getState()||this.marker.highlight.isHighlightAt(d,c)||(f!=d||b!=c)&&null==a.getState()&&this.marker.highlight.isHighlightAt(f,b))};
+mxEdgeHandler.prototype.updatePreviewState=function(a,b,c,d,e){var f=this.isSource?c:this.state.getVisibleTerminalState(!0),g=this.isTarget?c:this.state.getVisibleTerminalState(!1),k=this.graph.getConnectionConstraint(a,f,!0),l=this.graph.getConnectionConstraint(a,g,!1),m=this.constraintHandler.currentConstraint;null==m&&e&&(null!=c?(d.isSource(this.marker.highlight.shape)&&(b=new mxPoint(d.getGraphX(),d.getGraphY())),m=this.graph.getOutlineConstraint(b,c,d),this.constraintHandler.setFoc [...]
+this.constraintHandler.currentConstraint=m,this.constraintHandler.currentPoint=b):m=new mxConnectionConstraint);if(this.outlineConnect&&null!=this.marker.highlight&&null!=this.marker.highlight.shape){var n=this.graph.view.scale;null!=this.constraintHandler.currentConstraint&&null!=this.constraintHandler.currentFocus?(this.marker.highlight.shape.stroke=e?mxConstants.OUTLINE_HIGHLIGHT_COLOR:"transparent",this.marker.highlight.shape.strokewidth=mxConstants.OUTLINE_HIGHLIGHT_STROKEWIDTH/n/n, [...]
+this.marker.hasValidState()&&(this.marker.highlight.shape.stroke=this.marker.getValidState()==d.getState()?mxConstants.DEFAULT_VALID_COLOR:"transparent",this.marker.highlight.shape.strokewidth=mxConstants.HIGHLIGHT_STROKEWIDTH/n/n,this.marker.highlight.repaint())}this.isSource?k=m:this.isTarget&&(l=m);if(this.isSource||this.isTarget)null!=m&&null!=m.point?(a.style[this.isSource?mxConstants.STYLE_EXIT_X:mxConstants.STYLE_ENTRY_X]=m.point.x,a.style[this.isSource?mxConstants.STYLE_EXIT_Y:mx [...]
+m.point.y):(delete a.style[this.isSource?mxConstants.STYLE_EXIT_X:mxConstants.STYLE_ENTRY_X],delete a.style[this.isSource?mxConstants.STYLE_EXIT_Y:mxConstants.STYLE_ENTRY_Y]);a.setVisibleTerminalState(f,!0);a.setVisibleTerminalState(g,!1);this.isSource&&null==f||a.view.updateFixedTerminalPoint(a,f,!0,k);this.isTarget&&null==g||a.view.updateFixedTerminalPoint(a,g,!1,l);(this.isSource||this.isTarget)&&null==c&&(a.setAbsoluteTerminalPoint(b,this.isSource),null==this.marker.getMarkedState()& [...]
+this.graph.allowDanglingEdges?null:""));a.view.updatePoints(a,this.points,f,g);a.view.updateFloatingTerminalPoints(a,f,g)};
+mxEdgeHandler.prototype.mouseMove=function(a,b){if(null!=this.index&&null!=this.marker){this.currentPoint=this.getPointForEvent(b);this.error=null;!this.graph.isIgnoreTerminalEvent(b.getEvent())&&mxEvent.isShiftDown(b.getEvent())&&null!=this.snapPoint&&(Math.abs(this.snapPoint.x-this.currentPoint.x)<Math.abs(this.snapPoint.y-this.currentPoint.y)?this.currentPoint.x=this.snapPoint.x:this.currentPoint.y=this.snapPoint.y);if(this.index<=mxEvent.CUSTOM_HANDLE&&this.index>mxEvent.VIRTUAL_HAND [...]
+this.customHandles&&this.customHandles[mxEvent.CUSTOM_HANDLE-this.index].processEvent(b);else if(this.isLabel)this.label.x=this.currentPoint.x,this.label.y=this.currentPoint.y;else{this.points=this.getPreviewPoints(this.currentPoint,b);var c=this.isSource||this.isTarget?this.getPreviewTerminalState(b):null;if(null!=this.constraintHandler.currentConstraint&&null!=this.constraintHandler.currentFocus&&null!=this.constraintHandler.currentPoint)this.currentPoint=this.constraintHandler.current [...]
+else if(this.outlineConnect){var d=this.isSource||this.isTarget?this.isOutlineConnectEvent(b):!1;d?c=this.marker.highlight.state:null!=c&&c!=b.getState()&&null!=this.marker.highlight.shape&&(this.marker.highlight.shape.stroke="transparent",this.marker.highlight.repaint(),c=null)}var e=this.clonePreviewState(this.currentPoint,null!=c?c.cell:null);this.updatePreviewState(e,this.currentPoint,c,b,d);this.setPreviewColor(null==this.error?this.marker.validColor:this.marker.invalidColor);this.a [...]
+e.absolutePoints;this.active=!0}this.updateHint(b,this.currentPoint);this.drawPreview();mxEvent.consume(b.getEvent());b.consume()}else mxClient.IS_IE&&null!=this.getHandleForEvent(b)&&b.consume(!1)};
+mxEdgeHandler.prototype.mouseUp=function(a,b){if(null!=this.index&&null!=this.marker){var c=this.state.cell;if(b.getX()!=this.startX||b.getY()!=this.startY){var d=!this.graph.isIgnoreTerminalEvent(b.getEvent())&&this.graph.isCloneEvent(b.getEvent())&&this.cloneEnabled&&this.graph.isCellsCloneable();if(null!=this.error)0<this.error.length&&this.graph.validationAlert(this.error);else if(this.index<=mxEvent.CUSTOM_HANDLE&&this.index>mxEvent.VIRTUAL_HANDLE){if(null!=this.customHandles){d=thi [...]
+d.beginUpdate();try{this.customHandles[mxEvent.CUSTOM_HANDLE-this.index].execute()}finally{d.endUpdate()}}}else if(this.isLabel)this.moveLabel(this.state,this.label.x,this.label.y);else if(this.isSource||this.isTarget){var e=null;null!=this.constraintHandler.currentConstraint&&null!=this.constraintHandler.currentFocus&&(e=this.constraintHandler.currentFocus.cell);null==e&&this.marker.hasValidState()&&null!=this.marker.highlight&&null!=this.marker.highlight.shape&&"transparent"!=this.mark [...]
+"white"!=this.marker.highlight.shape.stroke&&(e=this.marker.validState.cell);if(null!=e)c=this.connect(c,e,this.isSource,d,b);else if(this.graph.isAllowDanglingEdges()){e=this.abspoints[this.isSource?0:this.abspoints.length-1];e.x=this.roundLength(e.x/this.graph.view.scale-this.graph.view.translate.x);e.y=this.roundLength(e.y/this.graph.view.scale-this.graph.view.translate.y);var f=this.graph.getView().getState(this.graph.getModel().getParent(c));null!=f&&(e.x-=f.origin.x,e.y-=f.origin.y [...]
+this.graph.view.scale;e.y-=this.graph.panDy/this.graph.view.scale;c=this.changeTerminalPoint(c,e,this.isSource,d)}}else this.active?c=this.changePoints(c,this.points,d):(this.graph.getView().invalidate(this.state.cell),this.graph.getView().validate(this.state.cell))}null!=this.marker&&(this.reset(),c!=this.state.cell&&this.graph.setSelectionCell(c));b.consume()}};
+mxEdgeHandler.prototype.reset=function(){this.snapPoint=this.points=this.label=this.index=this.error=null;this.isTarget=this.isSource=this.isLabel=this.active=!1;if(this.livePreview&&null!=this.sizers)for(var a=0;a<this.sizers.length;a++)null!=this.sizers[a]&&(this.sizers[a].node.style.display="");null!=this.marker&&this.marker.reset();null!=this.constraintHandler&&this.constraintHandler.reset();if(null!=this.customHandles)for(a=0;a<this.customHandles.length;a++)this.customHandles[a].res [...]
+this.removeHint();this.redraw()};mxEdgeHandler.prototype.setPreviewColor=function(a){null!=this.shape&&(this.shape.stroke=a)};mxEdgeHandler.prototype.convertPoint=function(a,b){var c=this.graph.getView().getScale(),d=this.graph.getView().getTranslate();b&&(a.x=this.graph.snap(a.x),a.y=this.graph.snap(a.y));a.x=Math.round(a.x/c-d.x);a.y=Math.round(a.y/c-d.y);c=this.graph.getView().getState(this.graph.getModel().getParent(this.state.cell));null!=c&&(a.x-=c.origin.x,a.y-=c.origin.y);return a};
+mxEdgeHandler.prototype.moveLabel=function(a,b,c){var d=this.graph.getModel(),e=d.getGeometry(a.cell);if(null!=e){var f=this.graph.getView().scale,e=e.clone();if(e.relative){var g=this.graph.getView().getRelativePoint(a,b,c);e.x=Math.round(1E4*g.x)/1E4;e.y=Math.round(g.y);e.offset=new mxPoint(0,0);g=this.graph.view.getPoint(a,e);e.offset=new mxPoint(Math.round((b-g.x)/f),Math.round((c-g.y)/f))}else{var k=a.absolutePoints,g=k[0],k=k[k.length-1];null!=g&&null!=k&&(e.offset=new mxPoint(Math [...]
+(g.x+(k.x-g.x)/2))/f),Math.round((c-(g.y+(k.y-g.y)/2))/f)),e.x=0,e.y=0)}d.setGeometry(a.cell,e)}};mxEdgeHandler.prototype.connect=function(a,b,c,d,e){e=this.graph.getModel();var f=e.getParent(a);e.beginUpdate();try{if(d){var g=this.graph.cloneCells([a])[0];e.add(f,g,e.getChildCount(f));var k=e.getTerminal(a,!c);this.graph.connectCell(g,k,!c);a=g}var l=this.constraintHandler.currentConstraint;null==l&&(l=new mxConnectionConstraint);this.graph.connectCell(a,b,c,l)}finally{e.endUpdate()}return a};
+mxEdgeHandler.prototype.changeTerminalPoint=function(a,b,c,d){var e=this.graph.getModel();e.beginUpdate();try{if(d){var f=e.getParent(a),g=e.getTerminal(a,!c);a=this.graph.cloneCells([a])[0];e.add(f,a,e.getChildCount(f));e.setTerminal(a,g,!c)}var k=e.getGeometry(a);null!=k&&(k=k.clone(),k.setTerminalPoint(b,c),e.setGeometry(a,k),this.graph.connectCell(a,null,c,new mxConnectionConstraint))}finally{e.endUpdate()}return a};
+mxEdgeHandler.prototype.changePoints=function(a,b,c){var d=this.graph.getModel();d.beginUpdate();try{if(c){var e=d.getParent(a),f=d.getTerminal(a,!0),g=d.getTerminal(a,!1);a=this.graph.cloneCells([a])[0];d.add(e,a,d.getChildCount(e));d.setTerminal(a,f,!0);d.setTerminal(a,g,!1)}var k=d.getGeometry(a);null!=k&&(k=k.clone(),k.points=b,d.setGeometry(a,k))}finally{d.endUpdate()}return a};
+mxEdgeHandler.prototype.addPoint=function(a,b){var c=mxUtils.convertPoint(this.graph.container,mxEvent.getClientX(b),mxEvent.getClientY(b)),d=this.graph.isGridEnabledEvent(b);this.convertPoint(c,d);this.addPointAt(a,c.x,c.y);mxEvent.consume(b)};
+mxEdgeHandler.prototype.addPointAt=function(a,b,c){var d=this.graph.getCellGeometry(a.cell);b=new mxPoint(b,c);if(null!=d){var d=d.clone(),e=this.graph.view.translate;c=this.graph.view.scale;var e=new mxPoint(e.x*c,e.y*c),f=this.graph.model.getParent(this.state.cell);this.graph.model.isVertex(f)&&(e=this.graph.view.getState(f),e=new mxPoint(e.x,e.y));c=mxUtils.findNearestSegment(a,b.x*c+e.x,b.y*c+e.y);null==d.points?d.points=[b]:d.points.splice(c,0,b);this.graph.getModel().setGeometry(a. [...]
+this.redraw()}};mxEdgeHandler.prototype.removePoint=function(a,b){if(0<b&&b<this.abspoints.length-1){var c=this.graph.getCellGeometry(this.state.cell);null!=c&&null!=c.points&&(c=c.clone(),c.points.splice(b-1,1),this.graph.getModel().setGeometry(a.cell,c),this.refresh(),this.redraw())}};
+mxEdgeHandler.prototype.getHandleFillColor=function(a){a=0==a;var b=this.state.cell,c=this.graph.getModel().getTerminal(b,a),d=mxConstants.HANDLE_FILLCOLOR;null!=c&&!this.graph.isCellDisconnectable(b,c,a)||null==c&&!this.graph.isTerminalPointMovable(b,a)?d=mxConstants.LOCKED_HANDLE_FILLCOLOR:null!=c&&this.graph.isCellDisconnectable(b,c,a)&&(d=mxConstants.CONNECT_HANDLE_FILLCOLOR);return d};
+mxEdgeHandler.prototype.redraw=function(){this.abspoints=this.state.absolutePoints.slice();this.redrawHandles();var a=this.graph.getModel().getGeometry(this.state.cell).points;if(null!=this.bends&&0<this.bends.length&&null!=a){null==this.points&&(this.points=[]);for(var b=1;b<this.bends.length-1;b++)null!=this.bends[b]&&null!=this.abspoints[b]&&(this.points[b-1]=a[b-1])}this.drawPreview()};
+mxEdgeHandler.prototype.redrawHandles=function(){var a=this.state.cell,b=this.labelShape.bounds;this.label=new mxPoint(this.state.absoluteOffset.x,this.state.absoluteOffset.y);this.labelShape.bounds=new mxRectangle(Math.round(this.label.x-b.width/2),Math.round(this.label.y-b.height/2),b.width,b.height);b=this.graph.getLabel(a);this.labelShape.visible=null!=b&&0<b.length&&this.graph.isLabelMovable(a);if(null!=this.bends&&0<this.bends.length){var c=this.abspoints.length-1,a=this.abspoints[ [...]
+e=a.y,b=this.bends[0].bounds;this.bends[0].bounds=new mxRectangle(Math.floor(d-b.width/2),Math.floor(e-b.height/2),b.width,b.height);this.bends[0].fill=this.getHandleFillColor(0);this.bends[0].redraw();this.manageLabelHandle&&this.checkLabelHandle(this.bends[0].bounds);var c=this.abspoints[c],d=c.x,e=c.y,f=this.bends.length-1,b=this.bends[f].bounds;this.bends[f].bounds=new mxRectangle(Math.floor(d-b.width/2),Math.floor(e-b.height/2),b.width,b.height);this.bends[f].fill=this.getHandleFill [...]
+this.bends[f].redraw();this.manageLabelHandle&&this.checkLabelHandle(this.bends[f].bounds);this.redrawInnerBends(a,c)}if(null!=this.abspoints&&null!=this.virtualBends&&0<this.virtualBends.length)for(a=this.abspoints[0],c=0;c<this.virtualBends.length;c++)null!=this.virtualBends[c]&&null!=this.abspoints[c+1]&&(d=this.abspoints[c+1],b=this.virtualBends[c],b.bounds=new mxRectangle(Math.floor(a.x+(d.x-a.x)/2-b.bounds.width/2),Math.floor(a.y+(d.y-a.y)/2-b.bounds.height/2),b.bounds.width,b.boun [...]
+b.redraw(),mxUtils.setOpacity(b.node,this.virtualBendOpacity),a=d,this.manageLabelHandle&&this.checkLabelHandle(b.bounds));null!=this.labelShape&&this.labelShape.redraw();if(null!=this.customHandles)for(c=0;c<this.customHandles.length;c++)this.customHandles[c].redraw()};
+mxEdgeHandler.prototype.setHandlesVisible=function(a){if(null!=this.bends)for(var b=0;b<this.bends.length;b++)this.bends[b].node.style.display=a?"":"none";if(null!=this.virtualBends)for(b=0;b<this.virtualBends.length;b++)this.virtualBends[b].node.style.display=a?"":"none";null!=this.labelShape&&(this.labelShape.node.style.display=a?"":"none");if(null!=this.customHandles)for(b=0;b<this.customHandles.length;b++)this.customHandles[b].setVisible(a)};
+mxEdgeHandler.prototype.redrawInnerBends=function(a,b){for(var c=1;c<this.bends.length-1;c++)if(null!=this.bends[c])if(null!=this.abspoints[c]){var d=this.abspoints[c].x,e=this.abspoints[c].y,f=this.bends[c].bounds;this.bends[c].node.style.visibility="visible";this.bends[c].bounds=new mxRectangle(Math.round(d-f.width/2),Math.round(e-f.height/2),f.width,f.height);this.manageLabelHandle?this.checkLabelHandle(this.bends[c].bounds):null==this.handleImage&&this.labelShape.visible&&mxUtils.int [...]
+this.labelShape.bounds)&&(w=mxConstants.HANDLE_SIZE+3,h=mxConstants.HANDLE_SIZE+3,this.bends[c].bounds=new mxRectangle(Math.round(d-w/2),Math.round(e-h/2),w,h));this.bends[c].redraw()}else this.bends[c].destroy(),this.bends[c]=null};mxEdgeHandler.prototype.checkLabelHandle=function(a){if(null!=this.labelShape){var b=this.labelShape.bounds;mxUtils.intersects(a,b)&&(a.getCenterY()<b.getCenterY()?b.y=a.y+a.height:b.y=a.y-b.height)}};
+mxEdgeHandler.prototype.drawPreview=function(){if(this.isLabel){var a=this.labelShape.bounds,a=new mxRectangle(Math.round(this.label.x-a.width/2),Math.round(this.label.y-a.height/2),a.width,a.height);this.labelShape.bounds=a;this.labelShape.redraw()}else null!=this.shape&&(this.shape.apply(this.state),this.shape.points=this.abspoints,this.shape.scale=this.state.view.scale,this.shape.isDashed=this.isSelectionDashed(),this.shape.stroke=this.getSelectionColor(),this.shape.strokewidth=this.g [...]
+this.shape.scale/this.shape.scale,this.shape.isShadow=!1,this.shape.redraw());null!=this.parentHighlight&&this.parentHighlight.redraw()};
+mxEdgeHandler.prototype.refresh=function(){this.abspoints=this.getSelectionPoints(this.state);this.points=[];null!=this.shape&&(this.shape.points=this.abspoints);null!=this.bends&&(this.destroyBends(this.bends),this.bends=this.createBends());null!=this.virtualBends&&(this.destroyBends(this.virtualBends),this.virtualBends=this.createVirtualBends());null!=this.customHandles&&(this.destroyBends(this.customHandles),this.customHandles=this.createCustomHandles());null!=this.labelShape&&null!=t [...]
+null!=this.labelShape.node.parentNode&&this.labelShape.node.parentNode.appendChild(this.labelShape.node)};mxEdgeHandler.prototype.destroyBends=function(a){if(null!=a)for(var b=0;b<a.length;b++)null!=a[b]&&a[b].destroy()};
+mxEdgeHandler.prototype.destroy=function(){null!=this.escapeHandler&&(this.state.view.graph.removeListener(this.escapeHandler),this.escapeHandler=null);null!=this.marker&&(this.marker.destroy(),this.marker=null);null!=this.shape&&(this.shape.destroy(),this.shape=null);null!=this.parentHighlight&&(this.parentHighlight.destroy(),this.parentHighlight=null);null!=this.labelShape&&(this.labelShape.destroy(),this.labelShape=null);null!=this.constraintHandler&&(this.constraintHandler.destroy(), [...]
+null);this.destroyBends(this.virtualBends);this.virtualBends=null;this.destroyBends(this.customHandles);this.customHandles=null;this.destroyBends(this.bends);this.bends=null;this.removeHint()};function mxElbowEdgeHandler(a){mxEdgeHandler.call(this,a)}mxUtils.extend(mxElbowEdgeHandler,mxEdgeHandler);mxElbowEdgeHandler.prototype.flipEnabled=!0;mxElbowEdgeHandler.prototype.doubleClickOrientationResource="none"!=mxClient.language?"doubleClickOrientation":"";
+mxElbowEdgeHandler.prototype.createBends=function(){var a=[],b=this.createHandleShape(0);this.initBend(b);b.setCursor(mxConstants.CURSOR_TERMINAL_HANDLE);a.push(b);a.push(this.createVirtualBend(mxUtils.bind(this,function(a){!mxEvent.isConsumed(a)&&this.flipEnabled&&(this.graph.flipEdge(this.state.cell,a),mxEvent.consume(a))})));this.points.push(new mxPoint(0,0));b=this.createHandleShape(2);this.initBend(b);b.setCursor(mxConstants.CURSOR_TERMINAL_HANDLE);a.push(b);return a};
+mxElbowEdgeHandler.prototype.createVirtualBend=function(a){var b=this.createHandleShape();this.initBend(b,a);b.setCursor(this.getCursorForBend());this.graph.isCellBendable(this.state.cell)||(b.node.style.display="none");return b};
+mxElbowEdgeHandler.prototype.getCursorForBend=function(){return this.state.style[mxConstants.STYLE_EDGE]==mxEdgeStyle.TopToBottom||this.state.style[mxConstants.STYLE_EDGE]==mxConstants.EDGESTYLE_TOPTOBOTTOM||(this.state.style[mxConstants.STYLE_EDGE]==mxEdgeStyle.ElbowConnector||this.state.style[mxConstants.STYLE_EDGE]==mxConstants.EDGESTYLE_ELBOW)&&this.state.style[mxConstants.STYLE_ELBOW]==mxConstants.ELBOW_VERTICAL?"row-resize":"col-resize"};
+mxElbowEdgeHandler.prototype.getTooltipForNode=function(a){var b=null;null==this.bends||null==this.bends[1]||a!=this.bends[1].node&&a.parentNode!=this.bends[1].node||(b=this.doubleClickOrientationResource,b=mxResources.get(b)||b);return b};
+mxElbowEdgeHandler.prototype.convertPoint=function(a,b){var c=this.graph.getView().getScale(),d=this.graph.getView().getTranslate(),e=this.state.origin;b&&(a.x=this.graph.snap(a.x),a.y=this.graph.snap(a.y));a.x=Math.round(a.x/c-d.x-e.x);a.y=Math.round(a.y/c-d.y-e.y);return a};
+mxElbowEdgeHandler.prototype.redrawInnerBends=function(a,b){var c=this.graph.getModel().getGeometry(this.state.cell),d=this.state.absolutePoints,e=null;1<d.length?(a=d[1],b=d[d.length-2]):null!=c.points&&0<c.points.length&&(e=d[0]);e=null==e?new mxPoint(a.x+(b.x-a.x)/2,a.y+(b.y-a.y)/2):new mxPoint(this.graph.getView().scale*(e.x+this.graph.getView().translate.x+this.state.origin.x),this.graph.getView().scale*(e.y+this.graph.getView().translate.y+this.state.origin.y));d=this.bends[1].boun [...]
+d=d.height;c=new mxRectangle(Math.round(e.x-c/2),Math.round(e.y-d/2),c,d);this.manageLabelHandle?this.checkLabelHandle(c):null==this.handleImage&&this.labelShape.visible&&mxUtils.intersects(c,this.labelShape.bounds)&&(c=mxConstants.HANDLE_SIZE+3,d=mxConstants.HANDLE_SIZE+3,c=new mxRectangle(Math.floor(e.x-c/2),Math.floor(e.y-d/2),c,d));this.bends[1].bounds=c;this.bends[1].redraw();this.manageLabelHandle&&this.checkLabelHandle(this.bends[1].bounds)};
+function mxEdgeSegmentHandler(a){mxEdgeHandler.call(this,a)}mxUtils.extend(mxEdgeSegmentHandler,mxElbowEdgeHandler);mxEdgeSegmentHandler.prototype.getCurrentPoints=function(){var a=this.state.absolutePoints;if(null!=a&&(2==a.length||3==a.length&&(a[0].x==a[1].x&&a[1].x==a[2].x||a[0].y==a[1].y&&a[1].y==a[2].y)))var b=a[0].x+(a[a.length-1].x-a[0].x)/2,c=a[0].y+(a[a.length-1].y-a[0].y)/2,a=[a[0],new mxPoint(b,c),new mxPoint(b,c),a[a.length-1]];return a};
+mxEdgeSegmentHandler.prototype.getPreviewPoints=function(a){if(this.isSource||this.isTarget)return mxElbowEdgeHandler.prototype.getPreviewPoints.apply(this,arguments);var b=this.getCurrentPoints(),c=this.convertPoint(b[0].clone(),!1);a=this.convertPoint(a.clone(),!1);for(var d=[],e=1;e<b.length;e++){var f=this.convertPoint(b[e].clone(),!1);e==this.index&&(0==Math.round(c.x-f.x)&&(c.x=a.x,f.x=a.x),0==Math.round(c.y-f.y)&&(c.y=a.y,f.y=a.y));e<b.length-1&&d.push(f);c=f}if(1==d.length){var b [...]
+c=this.state.getVisibleTerminalState(!1),f=this.state.view.getScale(),g=this.state.view.getTranslate(),e=d[0].x*f+g.x,f=d[0].y*f+g.y;if(null!=b&&mxUtils.contains(b,e,f)||null!=c&&mxUtils.contains(c,e,f))d=[a,a]}return d};
+mxEdgeSegmentHandler.prototype.updatePreviewState=function(a,b,c,d){mxEdgeHandler.prototype.updatePreviewState.apply(this,arguments);if(!this.isSource&&!this.isTarget){b=this.convertPoint(b.clone(),!1);for(var e=a.absolutePoints,f=e[0],g=e[1],k=[],l=2;l<e.length;l++){var m=e[l];0==Math.round(f.x-g.x)&&0==Math.round(g.x-m.x)||0==Math.round(f.y-g.y)&&0==Math.round(g.y-m.y)||k.push(this.convertPoint(g.clone(),!1));f=g;g=m}f=this.state.getVisibleTerminalState(!0);g=this.state.getVisibleTermi [...]
+l=this.state.absolutePoints;if(0==k.length&&(0==Math.round(e[0].x-e[e.length-1].x)||0==Math.round(e[0].y-e[e.length-1].y)))k=[b,b];else if(5==e.length&&2==k.length&&null!=f&&null!=g&&null!=l&&0==Math.round(l[0].x-l[l.length-1].x)){var k=this.graph.getView(),l=k.getScale(),m=k.getTranslate(),e=k.getRoutingCenterY(f)/l-m.y,n=this.graph.getConnectionConstraint(a,f,!0);null!=n&&(n=this.graph.getConnectionPoint(f,n),null!=n&&(this.convertPoint(n,!1),e=n.y));k=k.getRoutingCenterY(g)/l-m.y;if(l [...]
+g,!1))n=this.graph.getConnectionPoint(g,l),null!=n&&(this.convertPoint(n,!1),k=n.y);k=[new mxPoint(b.x,e),new mxPoint(b.x,k)]}this.points=k;a.view.updateFixedTerminalPoints(a,f,g);a.view.updatePoints(a,this.points,f,g);a.view.updateFloatingTerminalPoints(a,f,g)}};
+mxEdgeSegmentHandler.prototype.connect=function(a,b,c,d,e){for(var f=this.abspoints,g=f[0],k=f[1],l=[],m=2;m<f.length;m++){var n=f[m];0==Math.round(g.x-k.x)&&0==Math.round(k.x-n.x)||0==Math.round(g.y-k.y)&&0==Math.round(k.y-n.y)||l.push(this.convertPoint(k.clone(),!1));g=k;k=n}f=this.graph.getModel();f.beginUpdate();try{var p=f.getGeometry(a);null!=p&&(p=p.clone(),p.points=l,f.setGeometry(a,p));a=mxEdgeHandler.prototype.connect.apply(this,arguments)}finally{f.endUpdate()}return a};
+mxEdgeSegmentHandler.prototype.getTooltipForNode=function(a){return null};mxEdgeSegmentHandler.prototype.start=function(a,b,c){mxEdgeHandler.prototype.start.apply(this,arguments);null==this.bends[c]||this.isSource||this.isTarget||mxUtils.setOpacity(this.bends[c].node,100)};
+mxEdgeSegmentHandler.prototype.createBends=function(){var a=[],b=this.createHandleShape(0);this.initBend(b);b.setCursor(mxConstants.CURSOR_TERMINAL_HANDLE);a.push(b);var c=this.getCurrentPoints();if(this.graph.isCellBendable(this.state.cell)){null==this.points&&(this.points=[]);for(var d=0;d<c.length-1;d++){b=this.createVirtualBend();a.push(b);var e=0==Math.round(c[d].x-c[d+1].x);0==Math.round(c[d].y-c[d+1].y)&&d<c.length-2&&(e=0==Math.round(c[d].x-c[d+2].x));b.setCursor(e?"col-resize":" [...]
+this.points.push(new mxPoint(0,0))}}b=this.createHandleShape(c.length);this.initBend(b);b.setCursor(mxConstants.CURSOR_TERMINAL_HANDLE);a.push(b);return a};mxEdgeSegmentHandler.prototype.redraw=function(){this.refresh();mxEdgeHandler.prototype.redraw.apply(this,arguments)};
+mxEdgeSegmentHandler.prototype.redrawInnerBends=function(a,b){if(this.graph.isCellBendable(this.state.cell)){var c=this.getCurrentPoints();if(null!=c&&1<c.length){var d=!1;if(4==c.length&&0==Math.round(c[1].x-c[2].x)&&0==Math.round(c[1].y-c[2].y))if(d=!0,0==Math.round(c[0].y-c[c.length-1].y)){var e=c[0].x+(c[c.length-1].x-c[0].x)/2;c[1]=new mxPoint(e,c[1].y);c[2]=new mxPoint(e,c[2].y)}else e=c[0].y+(c[c.length-1].y-c[0].y)/2,c[1]=new mxPoint(c[1].x,e),c[2]=new mxPoint(c[2].x,e);for(e=0;e [...]
+1;e++)if(null!=this.bends[e+1]){a=c[e];b=c[e+1];var f=new mxPoint(a.x+(b.x-a.x)/2,a.y+(b.y-a.y)/2),g=this.bends[e+1].bounds;this.bends[e+1].bounds=new mxRectangle(Math.floor(f.x-g.width/2),Math.floor(f.y-g.height/2),g.width,g.height);this.bends[e+1].redraw();this.manageLabelHandle&&this.checkLabelHandle(this.bends[e+1].bounds)}d&&(mxUtils.setOpacity(this.bends[1].node,this.virtualBendOpacity),mxUtils.setOpacity(this.bends[3].node,this.virtualBendOpacity))}}};
+function mxKeyHandler(a,b){null!=a&&(this.graph=a,this.target=b||document.documentElement,this.normalKeys=[],this.shiftKeys=[],this.controlKeys=[],this.controlShiftKeys=[],this.keydownHandler=mxUtils.bind(this,function(a){this.keyDown(a)}),mxEvent.addListener(this.target,"keydown",this.keydownHandler),mxClient.IS_IE&&mxEvent.addListener(window,"unload",mxUtils.bind(this,function(){this.destroy()})))}mxKeyHandler.prototype.graph=null;mxKeyHandler.prototype.target=null;
+mxKeyHandler.prototype.normalKeys=null;mxKeyHandler.prototype.shiftKeys=null;mxKeyHandler.prototype.controlKeys=null;mxKeyHandler.prototype.controlShiftKeys=null;mxKeyHandler.prototype.enabled=!0;mxKeyHandler.prototype.isEnabled=function(){return this.enabled};mxKeyHandler.prototype.setEnabled=function(a){this.enabled=a};mxKeyHandler.prototype.bindKey=function(a,b){this.normalKeys[a]=b};mxKeyHandler.prototype.bindShiftKey=function(a,b){this.shiftKeys[a]=b};
+mxKeyHandler.prototype.bindControlKey=function(a,b){this.controlKeys[a]=b};mxKeyHandler.prototype.bindControlShiftKey=function(a,b){this.controlShiftKeys[a]=b};mxKeyHandler.prototype.isControlDown=function(a){return mxEvent.isControlDown(a)};mxKeyHandler.prototype.getFunction=function(a){return null==a||mxEvent.isAltDown(a)?null:this.isControlDown(a)?mxEvent.isShiftDown(a)?this.controlShiftKeys[a.keyCode]:this.controlKeys[a.keyCode]:mxEvent.isShiftDown(a)?this.shiftKeys[a.keyCode]:this.n [...]
+mxKeyHandler.prototype.isGraphEvent=function(a){var b=mxEvent.getSource(a);return b==this.target||b.parentNode==this.target||null!=this.graph.cellEditor&&this.graph.cellEditor.isEventSource(a)?!0:mxUtils.isAncestorNode(this.graph.container,b)};mxKeyHandler.prototype.keyDown=function(a){if(this.isEnabledForEvent(a))if(27==a.keyCode)this.escape(a);else if(!this.isEventIgnored(a)){var b=this.getFunction(a);null!=b&&(b(a),mxEvent.consume(a))}};
+mxKeyHandler.prototype.isEnabledForEvent=function(a){return this.graph.isEnabled()&&!mxEvent.isConsumed(a)&&this.isGraphEvent(a)&&this.isEnabled()};mxKeyHandler.prototype.isEventIgnored=function(a){return this.graph.isEditing()};mxKeyHandler.prototype.escape=function(a){this.graph.isEscapeEnabled()&&this.graph.escape(a)};
+mxKeyHandler.prototype.destroy=function(){null!=this.target&&null!=this.keydownHandler&&(mxEvent.removeListener(this.target,"keydown",this.keydownHandler),this.keydownHandler=null);this.target=null};function mxTooltipHandler(a,b){null!=a&&(this.graph=a,this.delay=b||500,this.graph.addMouseListener(this))}mxTooltipHandler.prototype.zIndex=10005;mxTooltipHandler.prototype.graph=null;mxTooltipHandler.prototype.delay=null;mxTooltipHandler.prototype.ignoreTouchEvents=!0;
+mxTooltipHandler.prototype.hideOnHover=!1;mxTooltipHandler.prototype.destroyed=!1;mxTooltipHandler.prototype.enabled=!0;mxTooltipHandler.prototype.isEnabled=function(){return this.enabled};mxTooltipHandler.prototype.setEnabled=function(a){this.enabled=a};mxTooltipHandler.prototype.isHideOnHover=function(){return this.hideOnHover};mxTooltipHandler.prototype.setHideOnHover=function(a){this.hideOnHover=a};
+mxTooltipHandler.prototype.init=function(){null!=document.body&&(this.div=document.createElement("div"),this.div.className="mxTooltip",this.div.style.visibility="hidden",document.body.appendChild(this.div),mxEvent.addGestureListeners(this.div,mxUtils.bind(this,function(a){this.hideTooltip()})))};mxTooltipHandler.prototype.mouseDown=function(a,b){this.reset(b,!1);this.hideTooltip()};
+mxTooltipHandler.prototype.mouseMove=function(a,b){if(b.getX()!=this.lastX||b.getY()!=this.lastY)this.reset(b,!0),(this.isHideOnHover()||b.getState()!=this.state||b.getSource()!=this.node&&(!this.stateSource||null!=b.getState()&&this.stateSource==(b.isSource(b.getState().shape)||!b.isSource(b.getState().text))))&&this.hideTooltip();this.lastX=b.getX();this.lastY=b.getY()};mxTooltipHandler.prototype.mouseUp=function(a,b){this.reset(b,!0);this.hideTooltip()};
+mxTooltipHandler.prototype.resetTimer=function(){null!=this.thread&&(window.clearTimeout(this.thread),this.thread=null)};
+mxTooltipHandler.prototype.reset=function(a,b){if(!this.ignoreTouchEvents||mxEvent.isMouseEvent(a.getEvent()))if(this.resetTimer(),b&&this.isEnabled()&&null!=a.getState()&&(null==this.div||"hidden"==this.div.style.visibility)){var c=a.getState(),d=a.getSource(),e=a.getX(),f=a.getY(),g=a.isSource(c.shape)||a.isSource(c.text);this.thread=window.setTimeout(mxUtils.bind(this,function(){if(!this.graph.isEditing()&&!this.graph.popupMenuHandler.isMenuShowing()&&!this.graph.isMouseDown){var a=th [...]
+d,e,f);this.show(a,e,f);this.state=c;this.node=d;this.stateSource=g}}),this.delay)}};mxTooltipHandler.prototype.hide=function(){this.resetTimer();this.hideTooltip()};mxTooltipHandler.prototype.hideTooltip=function(){null!=this.div&&(this.div.style.visibility="hidden",this.div.innerHTML="")};
+mxTooltipHandler.prototype.show=function(a,b,c){if(!this.destroyed&&null!=a&&0<a.length){null==this.div&&this.init();var d=mxUtils.getScrollOrigin();this.div.style.zIndex=this.zIndex;this.div.style.left=b+d.x+"px";this.div.style.top=c+mxConstants.TOOLTIP_VERTICAL_OFFSET+d.y+"px";mxUtils.isNode(a)?(this.div.innerHTML="",this.div.appendChild(a)):this.div.innerHTML=a.replace(/\n/g,"<br>");this.div.style.visibility="";mxUtils.fit(this.div)}};
+mxTooltipHandler.prototype.destroy=function(){this.destroyed||(this.graph.removeMouseListener(this),mxEvent.release(this.div),null!=this.div&&null!=this.div.parentNode&&this.div.parentNode.removeChild(this.div),this.destroyed=!0,this.div=null)};function mxCellTracker(a,b,c){mxCellMarker.call(this,a,b);this.graph.addMouseListener(this);null!=c&&(this.getCell=c);mxClient.IS_IE&&mxEvent.addListener(window,"unload",mxUtils.bind(this,function(){this.destroy()}))}mxUtils.extend(mxCellTracker,m [...]
+mxCellTracker.prototype.mouseDown=function(a,b){};mxCellTracker.prototype.mouseMove=function(a,b){this.isEnabled()&&this.process(b)};mxCellTracker.prototype.mouseUp=function(a,b){};mxCellTracker.prototype.destroy=function(){this.destroyed||(this.destroyed=!0,this.graph.removeMouseListener(this),mxCellMarker.prototype.destroy.apply(this))};
+function mxCellHighlight(a,b,c,d){null!=a&&(this.graph=a,this.highlightColor=null!=b?b:mxConstants.DEFAULT_VALID_COLOR,this.strokeWidth=null!=c?c:mxConstants.HIGHLIGHT_STROKEWIDTH,this.dashed=null!=d?d:!1,this.opacity=mxConstants.HIGHLIGHT_OPACITY,this.repaintHandler=mxUtils.bind(this,function(){if(null!=this.state){var a=this.graph.view.getState(this.state.cell);null==a?this.hide():(this.state=a,this.repaint())}}),this.graph.getView().addListener(mxEvent.SCALE,this.repaintHandler),this. [...]
+this.repaintHandler),this.graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE,this.repaintHandler),this.graph.getModel().addListener(mxEvent.CHANGE,this.repaintHandler),this.resetHandler=mxUtils.bind(this,function(){this.hide()}),this.graph.getView().addListener(mxEvent.DOWN,this.resetHandler),this.graph.getView().addListener(mxEvent.UP,this.resetHandler))}mxCellHighlight.prototype.keepOnTop=!1;mxCellHighlight.prototype.graph=!0;mxCellHighlight.prototype.state=null;
+mxCellHighlight.prototype.spacing=2;mxCellHighlight.prototype.resetHandler=null;mxCellHighlight.prototype.setHighlightColor=function(a){this.highlightColor=a;null!=this.shape&&(this.shape.stroke=a)};mxCellHighlight.prototype.drawHighlight=function(){this.shape=this.createShape();this.repaint();this.keepOnTop||this.shape.node.parentNode.firstChild==this.shape.node||this.shape.node.parentNode.insertBefore(this.shape.node,this.shape.node.parentNode.firstChild)};
+mxCellHighlight.prototype.createShape=function(){var a=this.graph.cellRenderer.createShape(this.state);a.svgStrokeTolerance=this.graph.tolerance;a.points=this.state.absolutePoints;a.apply(this.state);a.stroke=this.highlightColor;a.opacity=this.opacity;a.isDashed=this.dashed;a.isShadow=!1;a.dialect=this.graph.dialect!=mxConstants.DIALECT_SVG?mxConstants.DIALECT_VML:mxConstants.DIALECT_SVG;a.init(this.graph.getView().getOverlayPane());mxEvent.redirectMouseEvents(a.node,this.graph,this.stat [...]
+mxConstants.DIALECT_SVG?a.pointerEvents=!1:a.svgPointerEvents="stroke";return a};mxCellHighlight.prototype.getStrokeWidth=function(a){return this.strokeWidth};
+mxCellHighlight.prototype.repaint=function(){if(null!=this.state&&null!=this.shape){this.shape.scale=this.state.view.scale;this.graph.model.isEdge(this.state.cell)?(this.shape.strokewidth=this.getStrokeWidth(),this.shape.points=this.state.absolutePoints,this.shape.outline=!1):(this.shape.bounds=new mxRectangle(this.state.x-this.spacing,this.state.y-this.spacing,this.state.width+2*this.spacing,this.state.height+2*this.spacing),this.shape.rotation=Number(this.state.style[mxConstants.STYLE_ [...]
+"0"),this.shape.strokewidth=this.getStrokeWidth()/this.state.view.scale,this.shape.outline=!0);null!=this.state.shape&&this.shape.setCursor(this.state.shape.getCursor());if(mxClient.IS_QUIRKS||8==document.documentMode)"transparent"==this.shape.stroke?(this.shape.stroke="white",this.shape.opacity=1):this.shape.opacity=this.opacity;this.shape.redraw()}};mxCellHighlight.prototype.hide=function(){this.highlight(null)};
+mxCellHighlight.prototype.highlight=function(a){this.state!=a&&(null!=this.shape&&(this.shape.destroy(),this.shape=null),this.state=a,null!=this.state&&this.drawHighlight())};mxCellHighlight.prototype.isHighlightAt=function(a,b){var c=!1;if(null!=this.shape&&null!=document.elementFromPoint&&!mxClient.IS_QUIRKS)for(var d=document.elementFromPoint(a,b);null!=d;){if(d==this.shape.node){c=!0;break}d=d.parentNode}return c};
+mxCellHighlight.prototype.destroy=function(){this.graph.getView().removeListener(this.resetHandler);this.graph.getView().removeListener(this.repaintHandler);this.graph.getModel().removeListener(this.repaintHandler);null!=this.shape&&(this.shape.destroy(),this.shape=null)};
+function mxDefaultKeyHandler(a){if(null!=a){this.editor=a;this.handler=new mxKeyHandler(a.graph);var b=this.handler.escape;this.handler.escape=function(c){b.apply(this,arguments);a.hideProperties();a.fireEvent(new mxEventObject(mxEvent.ESCAPE,"event",c))}}}mxDefaultKeyHandler.prototype.editor=null;mxDefaultKeyHandler.prototype.handler=null;
+mxDefaultKeyHandler.prototype.bindAction=function(a,b,c){var d=mxUtils.bind(this,function(){this.editor.execute(b)});c?this.handler.bindControlKey(a,d):this.handler.bindKey(a,d)};mxDefaultKeyHandler.prototype.destroy=function(){this.handler.destroy();this.handler=null};function mxDefaultPopupMenu(a){this.config=a}mxDefaultPopupMenu.prototype.imageBasePath=null;mxDefaultPopupMenu.prototype.config=null;
+mxDefaultPopupMenu.prototype.createMenu=function(a,b,c,d){if(null!=this.config){var e=this.createConditions(a,c,d);this.addItems(a,b,c,d,e,this.config.firstChild,null)}};
+mxDefaultPopupMenu.prototype.addItems=function(a,b,c,d,e,f,g){for(var k=!1;null!=f;){if("add"==f.nodeName){var l=f.getAttribute("if");if(null==l||e[l]){var l=f.getAttribute("as"),l=mxResources.get(l)||l,m=mxUtils.eval(mxUtils.getTextContent(f)),n=f.getAttribute("action"),p=f.getAttribute("icon"),q=f.getAttribute("iconCls"),r=f.getAttribute("enabled-if"),r=null==r||e[r];k&&(b.addSeparator(g),k=!1);null!=p&&this.imageBasePath&&(p=this.imageBasePath+p);l=this.addAction(b,a,l,p,m,n,c,g,q,r); [...]
+b,c,d,e,f.firstChild,l)}}else"separator"==f.nodeName&&(k=!0);f=f.nextSibling}};mxDefaultPopupMenu.prototype.addAction=function(a,b,c,d,e,f,g,k,l,m){return a.addItem(c,d,function(a){"function"==typeof e&&e.call(b,b,g,a);null!=f&&b.execute(f,g,a)},k,l,m)};
+mxDefaultPopupMenu.prototype.createConditions=function(a,b,c){var d=a.graph.getModel(),e=d.getChildCount(b),f=[];f.nocell=null==b;f.ncells=1<a.graph.getSelectionCount();f.notRoot=d.getRoot()!=d.getParent(a.graph.getDefaultParent());f.cell=null!=b;d=null!=b&&1==a.graph.getSelectionCount();f.nonEmpty=d&&0<e;f.expandable=d&&a.graph.isCellFoldable(b,!1);f.collapsable=d&&a.graph.isCellFoldable(b,!0);f.validRoot=d&&a.graph.isValidRoot(b);f.emptyValidRoot=f.validRoot&&0==e;f.swimlane=d&&a.graph [...]
+e=this.config.getElementsByTagName("condition");for(d=0;d<e.length;d++){var g=mxUtils.eval(mxUtils.getTextContent(e[d])),k=e[d].getAttribute("name");null!=k&&"function"==typeof g&&(f[k]=g(a,b,c))}return f};function mxDefaultToolbar(a,b){this.editor=b;null!=a&&null!=b&&this.init(a)}mxDefaultToolbar.prototype.editor=null;mxDefaultToolbar.prototype.toolbar=null;mxDefaultToolbar.prototype.resetHandler=null;mxDefaultToolbar.prototype.spacing=4;mxDefaultToolbar.prototype.connectOnDrop=!1;
+mxDefaultToolbar.prototype.init=function(a){null!=a&&(this.toolbar=new mxToolbar(a),this.toolbar.addListener(mxEvent.SELECT,mxUtils.bind(this,function(a,c){var b=c.getProperty("function");this.editor.insertFunction=null!=b?mxUtils.bind(this,function(){b.apply(this,arguments);this.toolbar.resetMode()}):null})),this.resetHandler=mxUtils.bind(this,function(){null!=this.toolbar&&this.toolbar.resetMode(!0)}),this.editor.graph.addListener(mxEvent.DOUBLE_CLICK,this.resetHandler),this.editor.add [...]
+this.resetHandler))};mxDefaultToolbar.prototype.addItem=function(a,b,c,d){var e=mxUtils.bind(this,function(){null!=c&&0<c.length&&this.editor.execute(c)});return this.toolbar.addItem(a,b,e,d)};mxDefaultToolbar.prototype.addSeparator=function(a){a=a||mxClient.imageBasePath+"/separator.gif";this.toolbar.addSeparator(a)};mxDefaultToolbar.prototype.addCombo=function(){return this.toolbar.addCombo()};mxDefaultToolbar.prototype.addActionCombo=function(a){return this.toolbar.addActionCombo(a)};
+mxDefaultToolbar.prototype.addActionOption=function(a,b,c){var d=mxUtils.bind(this,function(){this.editor.execute(c)});this.addOption(a,b,d)};mxDefaultToolbar.prototype.addOption=function(a,b,c){return this.toolbar.addOption(a,b,c)};mxDefaultToolbar.prototype.addMode=function(a,b,c,d,e){var f=mxUtils.bind(this,function(){this.editor.setMode(c);null!=e&&e(this.editor)});return this.toolbar.addSwitchMode(a,b,f,d)};
+mxDefaultToolbar.prototype.addPrototype=function(a,b,c,d,e,f){var g=mxUtils.bind(this,function(){return"function"==typeof c?c():null!=c?this.editor.graph.cloneCells([c])[0]:null}),k=mxUtils.bind(this,function(a,b){"function"==typeof e?e(this.editor,g(),a,b):this.drop(g(),a,b);this.toolbar.resetMode();mxEvent.consume(a)});a=this.toolbar.addMode(a,b,k,d,null,f);this.installDropHandler(a,function(a,b,c){k(b,c)});return a};
+mxDefaultToolbar.prototype.drop=function(a,b,c){var d=this.editor.graph,e=d.getModel();if(null!=c&&!e.isEdge(c)&&this.connectOnDrop&&d.isCellConnectable(c))this.connect(a,b,c);else{for(;null!=c&&!d.isValidDropTarget(c,[a],b);)c=e.getParent(c);this.insert(a,b,c)}};
+mxDefaultToolbar.prototype.insert=function(a,b,c){var d=this.editor.graph;if(d.canImportCell(a)){var e=mxEvent.getClientX(b),f=mxEvent.getClientY(b),e=mxUtils.convertPoint(d.container,e,f);return d.isSplitEnabled()&&d.isSplitTarget(c,[a],b)?d.splitEdge(c,[a],null,e.x,e.y):this.editor.addVertex(c,a,e.x,e.y)}return null};
+mxDefaultToolbar.prototype.connect=function(a,b,c){b=this.editor.graph;var d=b.getModel();if(null!=c&&b.isCellConnectable(a)&&b.isEdgeValid(null,c,a)){var e=null;d.beginUpdate();try{var f=d.getGeometry(c),g=d.getGeometry(a).clone();g.x=f.x+(f.width-g.width)/2;g.y=f.y+(f.height-g.height)/2;var k=this.spacing*b.gridSize,l=20*d.getDirectedEdgeCount(c,!0);this.editor.horizontalFlow?g.x+=(g.width+f.width)/2+k+l:g.y+=(g.height+f.height)/2+k+l;a.setGeometry(g);var m=d.getParent(c);b.addCell(a,m [...]
+e=this.editor.createEdge(c,a);if(null==d.getGeometry(e)){var n=new mxGeometry;n.relative=!0;d.setGeometry(e,n)}b.addEdge(e,m,c,a)}finally{d.endUpdate()}b.setSelectionCells([a,e]);b.scrollCellToVisible(a)}};
+mxDefaultToolbar.prototype.installDropHandler=function(a,b){var c=document.createElement("img");c.setAttribute("src",a.getAttribute("src"));var d=mxUtils.bind(this,function(e){c.style.width=2*a.offsetWidth+"px";c.style.height=2*a.offsetHeight+"px";mxUtils.makeDraggable(a,this.editor.graph,b,c);mxEvent.removeListener(c,"load",d)});mxClient.IS_IE?d():mxEvent.addListener(c,"load",d)};
+mxDefaultToolbar.prototype.destroy=function(){null!=this.resetHandler&&(this.editor.graph.removeListener("dblclick",this.resetHandler),this.editor.removeListener("escape",this.resetHandler),this.resetHandler=null);null!=this.toolbar&&(this.toolbar.destroy(),this.toolbar=null)};
+function mxEditor(a){this.actions=[];this.addActions();if(null!=document.body){this.cycleAttributeValues=[];this.popupHandler=new mxDefaultPopupMenu;this.undoManager=new mxUndoManager;this.graph=this.createGraph();this.toolbar=this.createToolbar();this.keyHandler=new mxDefaultKeyHandler(this);this.configure(a);this.graph.swimlaneIndicatorColorAttribute=this.cycleAttributeName;if(null!=this.onInit)this.onInit();mxClient.IS_IE&&mxEvent.addListener(window,"unload",mxUtils.bind(this,function [...]
+mxLoadResources&&mxResources.add(mxClient.basePath+"/resources/editor");mxEditor.prototype=new mxEventSource;mxEditor.prototype.constructor=mxEditor;mxEditor.prototype.askZoomResource="none"!=mxClient.language?"askZoom":"";mxEditor.prototype.lastSavedResource="none"!=mxClient.language?"lastSaved":"";mxEditor.prototype.currentFileResource="none"!=mxClient.language?"currentFile":"";mxEditor.prototype.propertiesResource="none"!=mxClient.language?"properties":"";
+mxEditor.prototype.tasksResource="none"!=mxClient.language?"tasks":"";mxEditor.prototype.helpResource="none"!=mxClient.language?"help":"";mxEditor.prototype.outlineResource="none"!=mxClient.language?"outline":"";mxEditor.prototype.outline=null;mxEditor.prototype.graph=null;mxEditor.prototype.graphRenderHint=null;mxEditor.prototype.toolbar=null;mxEditor.prototype.status=null;mxEditor.prototype.popupHandler=null;mxEditor.prototype.undoManager=null;mxEditor.prototype.keyHandler=null;
+mxEditor.prototype.actions=null;mxEditor.prototype.dblClickAction="edit";mxEditor.prototype.swimlaneRequired=!1;mxEditor.prototype.disableContextMenu=!0;mxEditor.prototype.insertFunction=null;mxEditor.prototype.forcedInserting=!1;mxEditor.prototype.templates=null;mxEditor.prototype.defaultEdge=null;mxEditor.prototype.defaultEdgeStyle=null;mxEditor.prototype.defaultGroup=null;mxEditor.prototype.groupBorderSize=null;mxEditor.prototype.filename=null;mxEditor.prototype.linefeed="&#xa;";
+mxEditor.prototype.postParameterName="xml";mxEditor.prototype.escapePostData=!0;mxEditor.prototype.urlPost=null;mxEditor.prototype.urlImage=null;mxEditor.prototype.horizontalFlow=!1;mxEditor.prototype.layoutDiagram=!1;mxEditor.prototype.swimlaneSpacing=0;mxEditor.prototype.maintainSwimlanes=!1;mxEditor.prototype.layoutSwimlanes=!1;mxEditor.prototype.cycleAttributeValues=null;mxEditor.prototype.cycleAttributeIndex=0;mxEditor.prototype.cycleAttributeName="fillColor";mxEditor.prototype.tasks=null;
+mxEditor.prototype.tasksWindowImage=null;mxEditor.prototype.tasksTop=20;mxEditor.prototype.help=null;mxEditor.prototype.helpWindowImage=null;mxEditor.prototype.urlHelp=null;mxEditor.prototype.helpWidth=300;mxEditor.prototype.helpHeight=260;mxEditor.prototype.propertiesWidth=240;mxEditor.prototype.propertiesHeight=null;mxEditor.prototype.movePropertiesDialog=!1;mxEditor.prototype.validating=!1;mxEditor.prototype.modified=!1;mxEditor.prototype.isModified=function(){return this.modified};
+mxEditor.prototype.setModified=function(a){this.modified=a};
+mxEditor.prototype.addActions=function(){this.addAction("save",function(a){a.save()});this.addAction("print",function(a){(new mxPrintPreview(a.graph,1)).open()});this.addAction("show",function(a){mxUtils.show(a.graph,null,10,10)});this.addAction("exportImage",function(a){var b=a.getUrlImage();if(null==b||mxClient.IS_LOCAL)a.execute("show");else{var c=mxUtils.getViewXml(a.graph,1),c=mxUtils.getXml(c,"\n");mxUtils.submit(b,a.postParameterName+"="+encodeURIComponent(c),document,"_blank")}}) [...]
+function(a){a.graph.refresh()});this.addAction("cut",function(a){a.graph.isEnabled()&&mxClipboard.cut(a.graph)});this.addAction("copy",function(a){a.graph.isEnabled()&&mxClipboard.copy(a.graph)});this.addAction("paste",function(a){a.graph.isEnabled()&&mxClipboard.paste(a.graph)});this.addAction("delete",function(a){a.graph.isEnabled()&&a.graph.removeCells()});this.addAction("group",function(a){a.graph.isEnabled()&&a.graph.setSelectionCell(a.groupCells())});this.addAction("ungroup",functi [...]
+a.graph.setSelectionCells(a.graph.ungroupCells())});this.addAction("removeFromParent",function(a){a.graph.isEnabled()&&a.graph.removeCellsFromParent()});this.addAction("undo",function(a){a.graph.isEnabled()&&a.undo()});this.addAction("redo",function(a){a.graph.isEnabled()&&a.redo()});this.addAction("zoomIn",function(a){a.graph.zoomIn()});this.addAction("zoomOut",function(a){a.graph.zoomOut()});this.addAction("actualSize",function(a){a.graph.zoomActual()});this.addAction("fit",function(a) [...]
+this.addAction("showProperties",function(a,b){a.showProperties(b)});this.addAction("selectAll",function(a){a.graph.isEnabled()&&a.graph.selectAll()});this.addAction("selectNone",function(a){a.graph.isEnabled()&&a.graph.clearSelection()});this.addAction("selectVertices",function(a){a.graph.isEnabled()&&a.graph.selectVertices()});this.addAction("selectEdges",function(a){a.graph.isEnabled()&&a.graph.selectEdges()});this.addAction("edit",function(a,b){a.graph.isEnabled()&&a.graph.isCellEdita [...]
+this.addAction("toBack",function(a,b){a.graph.isEnabled()&&a.graph.orderCells(!0)});this.addAction("toFront",function(a,b){a.graph.isEnabled()&&a.graph.orderCells(!1)});this.addAction("enterGroup",function(a,b){a.graph.enterGroup(b)});this.addAction("exitGroup",function(a){a.graph.exitGroup()});this.addAction("home",function(a){a.graph.home()});this.addAction("selectPrevious",function(a){a.graph.isEnabled()&&a.graph.selectPreviousCell()});this.addAction("selectNext",function(a){a.graph.i [...]
+a.graph.selectNextCell()});this.addAction("selectParent",function(a){a.graph.isEnabled()&&a.graph.selectParentCell()});this.addAction("selectChild",function(a){a.graph.isEnabled()&&a.graph.selectChildCell()});this.addAction("collapse",function(a){a.graph.isEnabled()&&a.graph.foldCells(!0)});this.addAction("collapseAll",function(a){if(a.graph.isEnabled()){var b=a.graph.getChildVertices();a.graph.foldCells(!0,!1,b)}});this.addAction("expand",function(a){a.graph.isEnabled()&&a.graph.foldCel [...]
+this.addAction("expandAll",function(a){if(a.graph.isEnabled()){var b=a.graph.getChildVertices();a.graph.foldCells(!1,!1,b)}});this.addAction("bold",function(a){a.graph.isEnabled()&&a.graph.toggleCellStyleFlags(mxConstants.STYLE_FONTSTYLE,mxConstants.FONT_BOLD)});this.addAction("italic",function(a){a.graph.isEnabled()&&a.graph.toggleCellStyleFlags(mxConstants.STYLE_FONTSTYLE,mxConstants.FONT_ITALIC)});this.addAction("underline",function(a){a.graph.isEnabled()&&a.graph.toggleCellStyleFlags [...]
+mxConstants.FONT_UNDERLINE)});this.addAction("alignCellsLeft",function(a){a.graph.isEnabled()&&a.graph.alignCells(mxConstants.ALIGN_LEFT)});this.addAction("alignCellsCenter",function(a){a.graph.isEnabled()&&a.graph.alignCells(mxConstants.ALIGN_CENTER)});this.addAction("alignCellsRight",function(a){a.graph.isEnabled()&&a.graph.alignCells(mxConstants.ALIGN_RIGHT)});this.addAction("alignCellsTop",function(a){a.graph.isEnabled()&&a.graph.alignCells(mxConstants.ALIGN_TOP)});this.addAction("al [...]
+function(a){a.graph.isEnabled()&&a.graph.alignCells(mxConstants.ALIGN_MIDDLE)});this.addAction("alignCellsBottom",function(a){a.graph.isEnabled()&&a.graph.alignCells(mxConstants.ALIGN_BOTTOM)});this.addAction("alignFontLeft",function(a){a.graph.setCellStyles(mxConstants.STYLE_ALIGN,mxConstants.ALIGN_LEFT)});this.addAction("alignFontCenter",function(a){a.graph.isEnabled()&&a.graph.setCellStyles(mxConstants.STYLE_ALIGN,mxConstants.ALIGN_CENTER)});this.addAction("alignFontRight",function(a) [...]
+a.graph.setCellStyles(mxConstants.STYLE_ALIGN,mxConstants.ALIGN_RIGHT)});this.addAction("alignFontTop",function(a){a.graph.isEnabled()&&a.graph.setCellStyles(mxConstants.STYLE_VERTICAL_ALIGN,mxConstants.ALIGN_TOP)});this.addAction("alignFontMiddle",function(a){a.graph.isEnabled()&&a.graph.setCellStyles(mxConstants.STYLE_VERTICAL_ALIGN,mxConstants.ALIGN_MIDDLE)});this.addAction("alignFontBottom",function(a){a.graph.isEnabled()&&a.graph.setCellStyles(mxConstants.STYLE_VERTICAL_ALIGN,mxCons [...]
+this.addAction("zoom",function(a){var b=100*a.graph.getView().scale,b=parseFloat(mxUtils.prompt(mxResources.get(a.askZoomResource)||a.askZoomResource,b))/100;isNaN(b)||a.graph.getView().setScale(b)});this.addAction("toggleTasks",function(a){null!=a.tasks?a.tasks.setVisible(!a.tasks.isVisible()):a.showTasks()});this.addAction("toggleHelp",function(a){null!=a.help?a.help.setVisible(!a.help.isVisible()):a.showHelp()});this.addAction("toggleOutline",function(a){null==a.outline?a.showOutline( [...]
+this.addAction("toggleConsole",function(a){mxLog.setVisible(!mxLog.isVisible())})};mxEditor.prototype.configure=function(a){null!=a&&((new mxCodec(a.ownerDocument)).decode(a,this),this.resetHistory())};mxEditor.prototype.resetFirstTime=function(){document.cookie="mxgraph=seen; expires=Fri, 27 Jul 2001 02:47:11 UTC; path=/"};mxEditor.prototype.resetHistory=function(){this.lastSnapshot=(new Date).getTime();this.undoManager.clear();this.ignoredChanges=0;this.setModified(!1)};
+mxEditor.prototype.addAction=function(a,b){this.actions[a]=b};mxEditor.prototype.execute=function(a,b,c){var d=this.actions[a];if(null!=d)try{var e=arguments;e[0]=this;d.apply(this,e)}catch(f){throw mxUtils.error("Cannot execute "+a+": "+f.message,280,!0),f;}else mxUtils.error("Cannot find action "+a,280,!0)};mxEditor.prototype.addTemplate=function(a,b){this.templates[a]=b};mxEditor.prototype.getTemplate=function(a){return this.templates[a]};
+mxEditor.prototype.createGraph=function(){var a=new mxGraph(null,null,this.graphRenderHint);a.setTooltips(!0);a.setPanning(!0);this.installDblClickHandler(a);this.installUndoHandler(a);this.installDrillHandler(a);this.installChangeHandler(a);this.installInsertHandler(a);a.popupMenuHandler.factoryMethod=mxUtils.bind(this,function(a,c,d){return this.createPopupMenu(a,c,d)});a.connectionHandler.factoryMethod=mxUtils.bind(this,function(a,c){return this.createEdge(a,c)});this.createSwimlaneMa [...]
+this.createLayoutManager(a);return a};mxEditor.prototype.createSwimlaneManager=function(a){a=new mxSwimlaneManager(a,!1);a.isHorizontal=mxUtils.bind(this,function(){return this.horizontalFlow});a.isEnabled=mxUtils.bind(this,function(){return this.maintainSwimlanes});return a};
+mxEditor.prototype.createLayoutManager=function(a){var b=new mxLayoutManager(a),c=this;b.getLayout=function(b){var d=null,f=c.graph.getModel();null!=f.getParent(b)&&(c.layoutSwimlanes&&a.isSwimlane(b)?(null==c.swimlaneLayout&&(c.swimlaneLayout=c.createSwimlaneLayout()),d=c.swimlaneLayout):c.layoutDiagram&&(a.isValidRoot(b)||null==f.getParent(f.getParent(b)))&&(null==c.diagramLayout&&(c.diagramLayout=c.createDiagramLayout()),d=c.diagramLayout));return d};return b};
+mxEditor.prototype.setGraphContainer=function(a){null==this.graph.container&&(this.graph.init(a),this.rubberband=new mxRubberband(this.graph),this.disableContextMenu&&mxEvent.disableContextMenu(a),mxClient.IS_QUIRKS&&new mxDivResizer(a))};mxEditor.prototype.installDblClickHandler=function(a){a.addListener(mxEvent.DOUBLE_CLICK,mxUtils.bind(this,function(b,c){var d=c.getProperty("cell");null!=d&&a.isEnabled()&&null!=this.dblClickAction&&(this.execute(this.dblClickAction,d),c.consume())}))};
+mxEditor.prototype.installUndoHandler=function(a){var b=mxUtils.bind(this,function(a,b){var c=b.getProperty("edit");this.undoManager.undoableEditHappened(c)});a.getModel().addListener(mxEvent.UNDO,b);a.getView().addListener(mxEvent.UNDO,b);b=function(b,d){var c=d.getProperty("edit").changes;a.setSelectionCells(a.getSelectionCellsForChanges(c))};this.undoManager.addListener(mxEvent.UNDO,b);this.undoManager.addListener(mxEvent.REDO,b)};
+mxEditor.prototype.installDrillHandler=function(a){var b=mxUtils.bind(this,function(a){this.fireEvent(new mxEventObject(mxEvent.ROOT))});a.getView().addListener(mxEvent.DOWN,b);a.getView().addListener(mxEvent.UP,b)};
+mxEditor.prototype.installChangeHandler=function(a){var b=mxUtils.bind(this,function(b,d){this.setModified(!0);1==this.validating&&a.validateGraph();for(var c=d.getProperty("edit").changes,f=0;f<c.length;f++){var g=c[f];if(g instanceof mxRootChange||g instanceof mxValueChange&&g.cell==this.graph.model.root||g instanceof mxCellAttributeChange&&g.cell==this.graph.model.root){this.fireEvent(new mxEventObject(mxEvent.ROOT));break}}});a.getModel().addListener(mxEvent.CHANGE,b)};
+mxEditor.prototype.installInsertHandler=function(a){var b=this;a.addMouseListener({mouseDown:function(a,d){null==b.insertFunction||d.isPopupTrigger()||!b.forcedInserting&&null!=d.getState()||(b.graph.clearSelection(),b.insertFunction(d.getEvent(),d.getCell()),this.isActive=!0,d.consume())},mouseMove:function(a,b){this.isActive&&b.consume()},mouseUp:function(a,b){this.isActive&&(this.isActive=!1,b.consume())}})};
+mxEditor.prototype.createDiagramLayout=function(){var a=this.graph.gridSize,b=new mxStackLayout(this.graph,!this.horizontalFlow,this.swimlaneSpacing,2*a,2*a);b.isVertexIgnored=function(a){return!b.graph.isSwimlane(a)};return b};mxEditor.prototype.createSwimlaneLayout=function(){return new mxCompactTreeLayout(this.graph,this.horizontalFlow)};mxEditor.prototype.createToolbar=function(){return new mxDefaultToolbar(null,this)};
+mxEditor.prototype.setToolbarContainer=function(a){this.toolbar.init(a);mxClient.IS_QUIRKS&&new mxDivResizer(a)};
+mxEditor.prototype.setStatusContainer=function(a){null==this.status&&(this.status=a,this.addListener(mxEvent.SAVE,mxUtils.bind(this,function(){var a=(new Date).toLocaleString();this.setStatus((mxResources.get(this.lastSavedResource)||this.lastSavedResource)+": "+a)})),this.addListener(mxEvent.OPEN,mxUtils.bind(this,function(){this.setStatus((mxResources.get(this.currentFileResource)||this.currentFileResource)+": "+this.filename)})),mxClient.IS_QUIRKS&&new mxDivResizer(a))};
+mxEditor.prototype.setStatus=function(a){null!=this.status&&null!=a&&(this.status.innerHTML=a)};mxEditor.prototype.setTitleContainer=function(a){this.addListener(mxEvent.ROOT,mxUtils.bind(this,function(b){a.innerHTML=this.getTitle()}));mxClient.IS_QUIRKS&&new mxDivResizer(a)};mxEditor.prototype.treeLayout=function(a,b){null!=a&&(new mxCompactTreeLayout(this.graph,b)).execute(a)};
+mxEditor.prototype.getTitle=function(){for(var a="",b=this.graph,c=b.getCurrentRoot();null!=c&&null!=b.getModel().getParent(b.getModel().getParent(c));)b.isValidRoot(c)&&(a=" > "+b.convertValueToString(c)+a),c=b.getModel().getParent(c);return this.getRootTitle()+a};mxEditor.prototype.getRootTitle=function(){var a=this.graph.getModel().getRoot();return this.graph.convertValueToString(a)};mxEditor.prototype.undo=function(){this.undoManager.undo()};mxEditor.prototype.redo=function(){this.un [...]
+mxEditor.prototype.groupCells=function(){var a=null!=this.groupBorderSize?this.groupBorderSize:this.graph.gridSize;return this.graph.groupCells(this.createGroup(),a)};mxEditor.prototype.createGroup=function(){return this.graph.getModel().cloneCell(this.defaultGroup)};mxEditor.prototype.open=function(a){if(null!=a){var b=mxUtils.load(a).getXml();this.readGraphModel(b.documentElement);this.filename=a;this.fireEvent(new mxEventObject(mxEvent.OPEN,"filename",a))}};
+mxEditor.prototype.readGraphModel=function(a){(new mxCodec(a.ownerDocument)).decode(a,this.graph.getModel());this.resetHistory()};mxEditor.prototype.save=function(a,b){a=a||this.getUrlPost();if(null!=a&&0<a.length){var c=this.writeGraphModel(b);this.postDiagram(a,c);this.setModified(!1)}this.fireEvent(new mxEventObject(mxEvent.SAVE,"url",a))};
+mxEditor.prototype.postDiagram=function(a,b){this.escapePostData&&(b=encodeURIComponent(b));mxUtils.post(a,this.postParameterName+"="+b,mxUtils.bind(this,function(c){this.fireEvent(new mxEventObject(mxEvent.POST,"request",c,"url",a,"data",b))}))};mxEditor.prototype.writeGraphModel=function(a){a=null!=a?a:this.linefeed;var b=(new mxCodec).encode(this.graph.getModel());return mxUtils.getXml(b,a)};mxEditor.prototype.getUrlPost=function(){return this.urlPost};mxEditor.prototype.getUrlImage=f [...]
+mxEditor.prototype.swapStyles=function(a,b){var c=this.graph.getStylesheet().styles[b];this.graph.getView().getStylesheet().putCellStyle(b,this.graph.getStylesheet().styles[a]);this.graph.getStylesheet().putCellStyle(a,c);this.graph.refresh()};
+mxEditor.prototype.showProperties=function(a){a=a||this.graph.getSelectionCell();null==a&&(a=this.graph.getCurrentRoot(),null==a&&(a=this.graph.getModel().getRoot()));if(null!=a){this.graph.stopEditing(!0);var b=mxUtils.getOffset(this.graph.container),c=b.x+10,b=b.y;if(null==this.properties||this.movePropertiesDialog){var d=this.graph.getCellBounds(a);null!=d&&(c+=d.x+Math.min(200,d.width),b+=d.y)}else c=this.properties.getX(),b=this.properties.getY();this.hideProperties();a=this.createP [...]
+null!=a&&(this.properties=new mxWindow(mxResources.get(this.propertiesResource)||this.propertiesResource,a,c,b,this.propertiesWidth,this.propertiesHeight,!1),this.properties.setVisible(!0))}};mxEditor.prototype.isPropertiesVisible=function(){return null!=this.properties};
+mxEditor.prototype.createProperties=function(a){var b=this.graph.getModel(),c=b.getValue(a);if(mxUtils.isNode(c)){var d=new mxForm("properties");d.addText("ID",a.getId()).setAttribute("readonly","true");var e=null,f=null,g=null,k=null,l=null;b.isVertex(a)&&(e=b.getGeometry(a),null!=e&&(f=d.addText("top",e.y),g=d.addText("left",e.x),k=d.addText("width",e.width),l=d.addText("height",e.height)));for(var m=b.getStyle(a),n=d.addText("Style",m||""),p=c.attributes,q=[],c=0;c<p.length;c++)q[c]=d [...]
+p[c].value,"label"==p[c].nodeName?4:2);c=mxUtils.bind(this,function(){this.hideProperties();b.beginUpdate();try{null!=e&&(e=e.clone(),e.x=parseFloat(g.value),e.y=parseFloat(f.value),e.width=parseFloat(k.value),e.height=parseFloat(l.value),b.setGeometry(a,e));0<n.value.length?b.setStyle(a,n.value):b.setStyle(a,null);for(var c=0;c<p.length;c++){var d=new mxCellAttributeChange(a,p[c].nodeName,q[c].value);b.execute(d)}this.graph.isAutoSizeCell(a)&&this.graph.updateCellSize(a)}finally{b.endUp [...]
+m=mxUtils.bind(this,function(){this.hideProperties()});d.addButtons(c,m);return d.table}return null};mxEditor.prototype.hideProperties=function(){null!=this.properties&&(this.properties.destroy(),this.properties=null)};
+mxEditor.prototype.showTasks=function(){if(null==this.tasks){var a=document.createElement("div");a.style.padding="4px";a.style.paddingLeft="20px";var b=document.body.clientWidth,b=new mxWindow(mxResources.get(this.tasksResource)||this.tasksResource,a,b-220,this.tasksTop,200);b.setClosable(!0);b.destroyOnClose=!1;var c=mxUtils.bind(this,function(b){mxEvent.release(a);a.innerHTML="";this.createTasks(a)});this.graph.getModel().addListener(mxEvent.CHANGE,c);this.graph.getSelectionModel().add [...]
+c);this.graph.addListener(mxEvent.ROOT,c);null!=this.tasksWindowImage&&b.setImage(this.tasksWindowImage);this.tasks=b;this.createTasks(a)}this.tasks.setVisible(!0)};mxEditor.prototype.refreshTasks=function(a){null!=this.tasks&&(a=this.tasks.content,mxEvent.release(a),a.innerHTML="",this.createTasks(a))};mxEditor.prototype.createTasks=function(a){};
+mxEditor.prototype.showHelp=function(a){if(null==this.help){var b=document.createElement("iframe");b.setAttribute("src",mxResources.get("urlHelp")||this.urlHelp);b.setAttribute("height","100%");b.setAttribute("width","100%");b.setAttribute("frameBorder","0");b.style.backgroundColor="white";a=document.body.clientWidth;var c=document.body.clientHeight||document.documentElement.clientHeight,d=new mxWindow(mxResources.get(this.helpResource)||this.helpResource,b,(a-this.helpWidth)/2,(c-this.h [...]
+3,this.helpWidth,this.helpHeight);d.setMaximizable(!0);d.setClosable(!0);d.destroyOnClose=!1;d.setResizable(!0);null!=this.helpWindowImage&&d.setImage(this.helpWindowImage);mxClient.IS_NS&&(a=function(a){b.setAttribute("height",d.div.offsetHeight-26+"px")},d.addListener(mxEvent.RESIZE_END,a),d.addListener(mxEvent.MAXIMIZE,a),d.addListener(mxEvent.NORMALIZE,a),d.addListener(mxEvent.SHOW,a));this.help=d}this.help.setVisible(!0)};
+mxEditor.prototype.showOutline=function(){if(null==this.outline){var a=document.createElement("div");a.style.overflow="hidden";a.style.position="relative";a.style.width="100%";a.style.height="100%";a.style.background="white";a.style.cursor="move";8==document.documentMode&&(a.style.filter="progid:DXImageTransform.Microsoft.alpha(opacity=100)");var b=new mxWindow(mxResources.get(this.outlineResource)||this.outlineResource,a,600,480,200,200,!1),c=new mxOutline(this.graph,a);b.setClosable(!0 [...]
+b.destroyOnClose=!1;b.addListener(mxEvent.RESIZE_END,function(){c.update()});this.outline=b;this.outline.outline=c}this.outline.setVisible(!0);this.outline.outline.update(!0)};mxEditor.prototype.setMode=function(a){"select"==a?(this.graph.panningHandler.useLeftButtonForPanning=!1,this.graph.setConnectable(!1)):"connect"==a?(this.graph.panningHandler.useLeftButtonForPanning=!1,this.graph.setConnectable(!0)):"pan"==a&&(this.graph.panningHandler.useLeftButtonForPanning=!0,this.graph.setConn [...]
+mxEditor.prototype.createPopupMenu=function(a,b,c){this.popupHandler.createMenu(this,a,b,c)};mxEditor.prototype.createEdge=function(a,b){var c;if(null!=this.defaultEdge)c=this.graph.getModel().cloneCell(this.defaultEdge);else{c=new mxCell("");c.setEdge(!0);var d=new mxGeometry;d.relative=!0;c.setGeometry(d)}d=this.getEdgeStyle();null!=d&&c.setStyle(d);return c};mxEditor.prototype.getEdgeStyle=function(){return this.defaultEdgeStyle};
+mxEditor.prototype.consumeCycleAttribute=function(a){return null!=this.cycleAttributeValues&&0<this.cycleAttributeValues.length&&this.graph.isSwimlane(a)?this.cycleAttributeValues[this.cycleAttributeIndex++%this.cycleAttributeValues.length]:null};mxEditor.prototype.cycleAttribute=function(a){if(null!=this.cycleAttributeName){var b=this.consumeCycleAttribute(a);null!=b&&a.setStyle(a.getStyle()+";"+this.cycleAttributeName+"="+b)}};
+mxEditor.prototype.addVertex=function(a,b,c,d){for(var e=this.graph.getModel();null!=a&&!this.graph.isValidDropTarget(a);)a=e.getParent(a);a=null!=a?a:this.graph.getSwimlaneAt(c,d);var f=this.graph.getView().scale,g=e.getGeometry(b),k=e.getGeometry(a);if(this.graph.isSwimlane(b)&&!this.graph.swimlaneNesting)a=null;else{if(null==a&&this.swimlaneRequired)return null;if(null!=a&&null!=k){var l=this.graph.getView().getState(a);if(null!=l){if(c-=l.origin.x*f,d-=l.origin.y*f,this.graph.isConst [...]
+g.width,m=g.height,n=l.x+l.width;c+k>n&&(c-=c+k-n);n=l.y+l.height;d+m>n&&(d-=d+m-n)}}else null!=k&&(c-=k.x*f,d-=k.y*f)}}g=g.clone();g.x=this.graph.snap(c/f-this.graph.getView().translate.x-this.graph.gridSize/2);g.y=this.graph.snap(d/f-this.graph.getView().translate.y-this.graph.gridSize/2);b.setGeometry(g);null==a&&(a=this.graph.getDefaultParent());this.cycleAttribute(b);this.fireEvent(new mxEventObject(mxEvent.BEFORE_ADD_VERTEX,"vertex",b,"parent",a));e.beginUpdate();try{b=this.graph.a [...]
+a),null!=b&&(this.graph.constrainChild(b),this.fireEvent(new mxEventObject(mxEvent.ADD_VERTEX,"vertex",b)))}finally{e.endUpdate()}null!=b&&(this.graph.setSelectionCell(b),this.graph.scrollCellToVisible(b),this.fireEvent(new mxEventObject(mxEvent.AFTER_ADD_VERTEX,"vertex",b)));return b};
+mxEditor.prototype.destroy=function(){this.destroyed||(this.destroyed=!0,null!=this.tasks&&this.tasks.destroy(),null!=this.outline&&this.outline.destroy(),null!=this.properties&&this.properties.destroy(),null!=this.keyHandler&&this.keyHandler.destroy(),null!=this.rubberband&&this.rubberband.destroy(),null!=this.toolbar&&this.toolbar.destroy(),null!=this.graph&&this.graph.destroy(),this.templates=this.status=null)};
+var mxCodecRegistry={codecs:[],aliases:[],register:function(a){if(null!=a){var b=a.getName();mxCodecRegistry.codecs[b]=a;var c=mxUtils.getFunctionName(a.template.constructor);c!=b&&mxCodecRegistry.addAlias(c,b)}return a},addAlias:function(a,b){mxCodecRegistry.aliases[a]=b},getCodec:function(a){var b=null;if(null!=a){var b=mxUtils.getFunctionName(a),c=mxCodecRegistry.aliases[b];null!=c&&(b=c);b=mxCodecRegistry.codecs[b];if(null==b)try{b=new mxObjectCodec(new a),mxCodecRegistry.register(b) [...]
+function mxCodec(a){this.document=a||mxUtils.createXmlDocument();this.objects=[]}mxCodec.prototype.document=null;mxCodec.prototype.objects=null;mxCodec.prototype.elements=null;mxCodec.prototype.encodeDefaults=!1;mxCodec.prototype.putObject=function(a,b){return this.objects[a]=b};mxCodec.prototype.getObject=function(a){var b=null;null!=a&&(b=this.objects[a],null==b&&(b=this.lookup(a),null==b&&(a=this.getElementById(a),null!=a&&(b=this.decode(a)))));return b};mxCodec.prototype.lookup=funct [...]
+mxCodec.prototype.getElementById=function(a){if(null==this.elements){if(null==this.document.documentElement)throw Error("mxCodec constructor needs document parameter");this.elements={};this.addElement(this.document.documentElement)}return this.elements[a]};mxCodec.prototype.addElement=function(a){if(a.nodeType==mxConstants.NODETYPE_ELEMENT){var b=a.getAttribute("id");null!=b&&null==this.elements[b]&&(this.elements[b]=a)}for(a=a.firstChild;null!=a;)this.addElement(a),a=a.nextSibling};
+mxCodec.prototype.getId=function(a){var b=null;null!=a&&(b=this.reference(a),null==b&&a instanceof mxCell&&(b=a.getId(),null==b&&(b=mxCellPath.create(a),0==b.length&&(b="root"))));return b};mxCodec.prototype.reference=function(a){return null};mxCodec.prototype.encode=function(a){var b=null;if(null!=a&&null!=a.constructor){var c=mxCodecRegistry.getCodec(a.constructor);null!=c?b=c.encode(this,a):mxUtils.isNode(a)?b=mxUtils.importNode(this.document,a,!0):mxLog.warn("mxCodec.encode: No codec [...]
+mxCodec.prototype.decode=function(a,b){var c=null;if(null!=a&&a.nodeType==mxConstants.NODETYPE_ELEMENT){c=null;try{c=window[a.nodeName]}catch(d){}c=mxCodecRegistry.getCodec(c);null!=c?c=c.decode(this,a,b):(c=a.cloneNode(!0),c.removeAttribute("as"))}return c};mxCodec.prototype.encodeCell=function(a,b,c){b.appendChild(this.encode(a));if(null==c||c){c=a.getChildCount();for(var d=0;d<c;d++)this.encodeCell(a.getChildAt(d),b)}};
+mxCodec.prototype.isCellCodec=function(a){return null!=a&&"function"==typeof a.isCellCodec?a.isCellCodec():!1};mxCodec.prototype.decodeCell=function(a,b){b=null!=b?b:!0;var c=null;if(null!=a&&a.nodeType==mxConstants.NODETYPE_ELEMENT){c=mxCodecRegistry.getCodec(a.nodeName);if(!this.isCellCodec(c))for(var d=a.firstChild;null!=d&&!this.isCellCodec(c);)c=mxCodecRegistry.getCodec(d.nodeName),d=d.nextSibling;this.isCellCodec(c)||(c=mxCodecRegistry.getCodec(mxCell));c=c.decode(this,a);b&&this.i [...]
+mxCodec.prototype.insertIntoGraph=function(a){var b=a.parent,c=a.getTerminal(!0),d=a.getTerminal(!1);a.setTerminal(null,!1);a.setTerminal(null,!0);a.parent=null;null!=b&&b.insert(a);null!=c&&c.insertEdge(a,!0);null!=d&&d.insertEdge(a,!1)};mxCodec.prototype.setAttribute=function(a,b,c){null!=b&&null!=c&&a.setAttribute(b,c)};
+function mxObjectCodec(a,b,c,d){this.template=a;this.exclude=null!=b?b:[];this.idrefs=null!=c?c:[];this.mapping=null!=d?d:[];this.reverse={};for(var e in this.mapping)this.reverse[this.mapping[e]]=e}mxObjectCodec.allowEval=!1;mxObjectCodec.prototype.template=null;mxObjectCodec.prototype.exclude=null;mxObjectCodec.prototype.idrefs=null;mxObjectCodec.prototype.mapping=null;mxObjectCodec.prototype.reverse=null;mxObjectCodec.prototype.getName=function(){return mxUtils.getFunctionName(this.te [...]
+mxObjectCodec.prototype.cloneTemplate=function(){return new this.template.constructor};mxObjectCodec.prototype.getFieldName=function(a){if(null!=a){var b=this.reverse[a];null!=b&&(a=b)}return a};mxObjectCodec.prototype.getAttributeName=function(a){if(null!=a){var b=this.mapping[a];null!=b&&(a=b)}return a};mxObjectCodec.prototype.isExcluded=function(a,b,c,d){return b==mxObjectIdentity.FIELD_NAME||0<=mxUtils.indexOf(this.exclude,b)};
+mxObjectCodec.prototype.isReference=function(a,b,c,d){return 0<=mxUtils.indexOf(this.idrefs,b)};mxObjectCodec.prototype.encode=function(a,b){var c=a.document.createElement(this.getName());b=this.beforeEncode(a,b,c);this.encodeObject(a,b,c);return this.afterEncode(a,b,c)};mxObjectCodec.prototype.encodeObject=function(a,b,c){a.setAttribute(c,"id",a.getId(b));for(var d in b){var e=d,f=b[e];null==f||this.isExcluded(b,e,f,!0)||(mxUtils.isInteger(e)&&(e=null),this.encodeValue(a,b,e,f,c))}};
+mxObjectCodec.prototype.encodeValue=function(a,b,c,d,e){if(null!=d){if(this.isReference(b,c,d,!0)){var f=a.getId(d);if(null==f){mxLog.warn("mxObjectCodec.encode: No ID for "+this.getName()+"."+c+"="+d);return}d=f}f=this.template[c];if(null==c||a.encodeDefaults||f!=d)c=this.getAttributeName(c),this.writeAttribute(a,b,c,d,e)}};mxObjectCodec.prototype.writeAttribute=function(a,b,c,d,e){"object"!=typeof d?this.writePrimitiveAttribute(a,b,c,d,e):this.writeComplexAttribute(a,b,c,d,e)};
+mxObjectCodec.prototype.writePrimitiveAttribute=function(a,b,c,d,e){d=this.convertAttributeToXml(a,b,c,d,e);null==c?(b=a.document.createElement("add"),"function"==typeof d?b.appendChild(a.document.createTextNode(d)):a.setAttribute(b,"value",d),e.appendChild(b)):"function"!=typeof d&&a.setAttribute(e,c,d)};
+mxObjectCodec.prototype.writeComplexAttribute=function(a,b,c,d,e){a=a.encode(d);null!=a?(null!=c&&a.setAttribute("as",c),e.appendChild(a)):mxLog.warn("mxObjectCodec.encode: No node for "+this.getName()+"."+c+": "+d)};mxObjectCodec.prototype.convertAttributeToXml=function(a,b,c,d){this.isBooleanAttribute(a,b,c,d)&&(d=1==d?"1":"0");return d};mxObjectCodec.prototype.isBooleanAttribute=function(a,b,c,d){return"undefined"==typeof d.length&&(1==d||0==d)};
+mxObjectCodec.prototype.convertAttributeFromXml=function(a,b,c){var d=b.value;this.isNumericAttribute(a,b,c)&&(d=parseFloat(d));return d};mxObjectCodec.prototype.isNumericAttribute=function(a,b,c){return mxUtils.isNumeric(b.value)};mxObjectCodec.prototype.beforeEncode=function(a,b,c){return b};mxObjectCodec.prototype.afterEncode=function(a,b,c){return c};
+mxObjectCodec.prototype.decode=function(a,b,c){var d=b.getAttribute("id"),e=a.objects[d];null==e&&(e=c||this.cloneTemplate(),null!=d&&a.putObject(d,e));b=this.beforeDecode(a,b,e);this.decodeNode(a,b,e);return this.afterDecode(a,b,e)};mxObjectCodec.prototype.decodeNode=function(a,b,c){null!=b&&(this.decodeAttributes(a,b,c),this.decodeChildren(a,b,c))};mxObjectCodec.prototype.decodeAttributes=function(a,b,c){b=b.attributes;if(null!=b)for(var d=0;d<b.length;d++)this.decodeAttribute(a,b[d],c)};
+mxObjectCodec.prototype.isIgnoredAttribute=function(a,b,c){return"as"==b.nodeName||"id"==b.nodeName};mxObjectCodec.prototype.decodeAttribute=function(a,b,c){if(!this.isIgnoredAttribute(a,b,c)){var d=b.nodeName;b=this.convertAttributeFromXml(a,b,c);var e=this.getFieldName(d);if(this.isReference(c,e,b,!1)){a=a.getObject(b);if(null==a){mxLog.warn("mxObjectCodec.decode: No object for "+this.getName()+"."+d+"="+b);return}b=a}this.isExcluded(c,d,b,!1)||(c[d]=b)}};
+mxObjectCodec.prototype.decodeChildren=function(a,b,c){for(b=b.firstChild;null!=b;){var d=b.nextSibling;b.nodeType!=mxConstants.NODETYPE_ELEMENT||this.processInclude(a,b,c)||this.decodeChild(a,b,c);b=d}};
+mxObjectCodec.prototype.decodeChild=function(a,b,c){var d=this.getFieldName(b.getAttribute("as"));if(null==d||!this.isExcluded(c,d,b,!1)){var e=this.getFieldTemplate(c,d,b);"add"==b.nodeName?(a=b.getAttribute("value"),null==a&&mxObjectCodec.allowEval&&(a=mxUtils.eval(mxUtils.getTextContent(b)))):a=a.decode(b,e);this.addObjectValue(c,d,a,e)}};mxObjectCodec.prototype.getFieldTemplate=function(a,b,c){a=a[b];a instanceof Array&&0<a.length&&(a=null);return a};
+mxObjectCodec.prototype.addObjectValue=function(a,b,c,d){null!=c&&c!=d&&(null!=b&&0<b.length?a[b]=c:a.push(c))};mxObjectCodec.prototype.processInclude=function(a,b,c){if("include"==b.nodeName){b=b.getAttribute("name");if(null!=b)try{var d=mxUtils.load(b).getDocumentElement();null!=d&&a.decode(d,c)}catch(e){}return!0}return!1};mxObjectCodec.prototype.beforeDecode=function(a,b,c){return b};mxObjectCodec.prototype.afterDecode=function(a,b,c){return c};
+mxCodecRegistry.register(function(){var a=new mxObjectCodec(new mxCell,["children","edges","overlays","mxTransient"],["parent","source","target"]);a.isCellCodec=function(){return!0};a.isNumericAttribute=function(a,c,d){return"value"!==c.nodeName&&mxObjectCodec.prototype.isNumericAttribute.apply(this,arguments)};a.isExcluded=function(a,c,d,e){return mxObjectCodec.prototype.isExcluded.apply(this,arguments)||e&&"value"==c&&d.nodeType==mxConstants.NODETYPE_ELEMENT};a.afterEncode=function(a,c [...]
+c.value&&c.value.nodeType==mxConstants.NODETYPE_ELEMENT){var b=d;d=mxUtils.importNode(a.document,c.value,!0);d.appendChild(b);a=b.getAttribute("id");d.setAttribute("id",a);b.removeAttribute("id")}return d};a.beforeDecode=function(a,c,d){var b=c.cloneNode(!0),f=this.getName();c.nodeName!=f?(b=c.getElementsByTagName(f)[0],null!=b&&b.parentNode==c?(mxUtils.removeWhitespace(b,!0),mxUtils.removeWhitespace(b,!1),b.parentNode.removeChild(b)):b=null,d.value=c.cloneNode(!0),c=d.value.getAttribute [...]
+c&&(d.setId(c),d.value.removeAttribute("id"))):d.setId(c.getAttribute("id"));if(null!=b)for(c=0;c<this.idrefs.length;c++){var f=this.idrefs[c],g=b.getAttribute(f);if(null!=g){b.removeAttribute(f);var k=a.objects[g]||a.lookup(g);null==k&&(g=a.getElementById(g),null!=g&&(k=(mxCodecRegistry.codecs[g.nodeName]||this).decode(a,g)));d[f]=k}}return b};return a}());
+mxCodecRegistry.register(function(){var a=new mxObjectCodec(new mxGraphModel);a.encodeObject=function(a,c,d){var b=a.document.createElement("root");a.encodeCell(c.getRoot(),b);d.appendChild(b)};a.decodeChild=function(a,c,d){"root"==c.nodeName?this.decodeRoot(a,c,d):mxObjectCodec.prototype.decodeChild.apply(this,arguments)};a.decodeRoot=function(a,c,d){var b=null;for(c=c.firstChild;null!=c;){var f=a.decodeCell(c);null!=f&&null==f.getParent()&&(b=f);c=c.nextSibling}null!=b&&d.setRoot(b)};r [...]
+mxCodecRegistry.register(function(){var a=new mxObjectCodec(new mxRootChange,["model","previous","root"]);a.afterEncode=function(a,c,d){a.encodeCell(c.root,d);return d};a.beforeDecode=function(a,c,d){if(null!=c.firstChild&&c.firstChild.nodeType==mxConstants.NODETYPE_ELEMENT){c=c.cloneNode(!0);var b=c.firstChild;d.root=a.decodeCell(b,!1);d=b.nextSibling;b.parentNode.removeChild(b);for(b=d;null!=b;)d=b.nextSibling,a.decodeCell(b),b.parentNode.removeChild(b),b=d}return c};a.afterDecode=func [...]
+d){d.previous=d.root;return d};return a}());
+mxCodecRegistry.register(function(){var a=new mxObjectCodec(new mxChildChange,["model","child","previousIndex"],["parent","previous"]);a.isReference=function(a,c,d,e){return"child"!=c||null==a.previous&&e?0<=mxUtils.indexOf(this.idrefs,c):!0};a.afterEncode=function(a,c,d){this.isReference(c,"child",c.child,!0)?d.setAttribute("child",a.getId(c.child)):a.encodeCell(c.child,d);return d};a.beforeDecode=function(a,c,d){if(null!=c.firstChild&&c.firstChild.nodeType==mxConstants.NODETYPE_ELEMENT [...]
+var b=c.firstChild;d.child=a.decodeCell(b,!1);d=b.nextSibling;b.parentNode.removeChild(b);for(b=d;null!=b;){d=b.nextSibling;if(b.nodeType==mxConstants.NODETYPE_ELEMENT){var f=b.getAttribute("id");null==a.lookup(f)&&a.decodeCell(b)}b.parentNode.removeChild(b);b=d}}else b=c.getAttribute("child"),d.child=a.getObject(b);return c};a.afterDecode=function(a,c,d){d.child.parent=d.previous;d.previous=d.parent;d.previousIndex=d.index;return d};return a}());
+mxCodecRegistry.register(function(){var a=new mxObjectCodec(new mxTerminalChange,["model","previous"],["cell","terminal"]);a.afterDecode=function(a,c,d){d.previous=d.terminal;return d};return a}());var mxGenericChangeCodec=function(a,b){var c=new mxObjectCodec(a,["model","previous"],["cell"]);c.afterDecode=function(a,c,f){mxUtils.isNode(f.cell)&&(f.cell=a.decodeCell(f.cell,!1));f.previous=f[b];return f};return c};mxCodecRegistry.register(mxGenericChangeCodec(new mxValueChange,"value"));
+mxCodecRegistry.register(mxGenericChangeCodec(new mxStyleChange,"style"));mxCodecRegistry.register(mxGenericChangeCodec(new mxGeometryChange,"geometry"));mxCodecRegistry.register(mxGenericChangeCodec(new mxCollapseChange,"collapsed"));mxCodecRegistry.register(mxGenericChangeCodec(new mxVisibleChange,"visible"));mxCodecRegistry.register(mxGenericChangeCodec(new mxCellAttributeChange,"value"));mxCodecRegistry.register(function(){return new mxObjectCodec(new mxGraph,"graphListeners eventLis [...]
+mxCodecRegistry.register(function(){var a=new mxObjectCodec(new mxGraphView);a.encode=function(a,c){return this.encodeCell(a,c,c.graph.getModel().getRoot())};a.encodeCell=function(a,c,d){var b=c.graph.getModel(),f=c.getState(d),g=b.getParent(d);if(null==g||null!=f){var k=b.getChildCount(d),l=c.graph.getCellGeometry(d),m=null;g==b.getRoot()?m="layer":null==g?m="graph":b.isEdge(d)?m="edge":0<k&&null!=l?m="group":b.isVertex(d)&&(m="vertex");if(null!=m){var n=a.document.createElement(m);null [...]
+(n.setAttribute("label",c.graph.getLabel(d)),c.graph.isHtmlLabel(d)&&n.setAttribute("html",!0));if(null==g){var p=c.getGraphBounds();null!=p&&(n.setAttribute("x",Math.round(p.x)),n.setAttribute("y",Math.round(p.y)),n.setAttribute("width",Math.round(p.width)),n.setAttribute("height",Math.round(p.height)));n.setAttribute("scale",c.scale)}else if(null!=f&&null!=l){for(p in f.style)g=f.style[p],"function"==typeof g&&"object"==typeof g&&(g=mxStyleRegistry.getName(g)),null!=g&&"function"!=type [...]
+typeof g&&n.setAttribute(p,g);g=f.absolutePoints;if(null!=g&&0<g.length){l=Math.round(g[0].x)+","+Math.round(g[0].y);for(p=1;p<g.length;p++)l+=" "+Math.round(g[p].x)+","+Math.round(g[p].y);n.setAttribute("points",l)}else n.setAttribute("x",Math.round(f.x)),n.setAttribute("y",Math.round(f.y)),n.setAttribute("width",Math.round(f.width)),n.setAttribute("height",Math.round(f.height));p=f.absoluteOffset;null!=p&&(0!=p.x&&n.setAttribute("dx",Math.round(p.x)),0!=p.y&&n.setAttribute("dy",Math.ro [...]
+0;p<k;p++)f=this.encodeCell(a,c,b.getChildAt(d,p)),null!=f&&n.appendChild(f)}}return n};return a}());
+var mxStylesheetCodec=mxCodecRegistry.register(function(){var a=new mxObjectCodec(new mxStylesheet);a.encode=function(a,c){var b=a.document.createElement(this.getName()),e;for(e in c.styles){var f=c.styles[e],g=a.document.createElement("add");if(null!=e){g.setAttribute("as",e);for(var k in f){var l=this.getStringValue(k,f[k]);if(null!=l){var m=a.document.createElement("add");m.setAttribute("value",l);m.setAttribute("as",k);g.appendChild(m)}}0<g.childNodes.length&&b.appendChild(g)}}return [...]
+function(a,c){var b=typeof c;"function"==b?c=mxStyleRegistry.getName(style[j]):"object"==b&&(c=null);return c};a.decode=function(a,c,d){d=d||new this.template.constructor;var b=c.getAttribute("id");null!=b&&(a.objects[b]=d);for(c=c.firstChild;null!=c;){if(!this.processInclude(a,c,d)&&"add"==c.nodeName&&(b=c.getAttribute("as"),null!=b)){var f=c.getAttribute("extend"),g=null!=f?mxUtils.clone(d.styles[f]):null;null==g&&(null!=f&&mxLog.warn("mxStylesheetCodec.decode: stylesheet "+f+" not fou [...]
+g={});for(f=c.firstChild;null!=f;){if(f.nodeType==mxConstants.NODETYPE_ELEMENT){var k=f.getAttribute("as");if("add"==f.nodeName){var l=mxUtils.getTextContent(f);null!=l&&0<l.length&&mxStylesheetCodec.allowEval?l=mxUtils.eval(l):(l=f.getAttribute("value"),mxUtils.isNumeric(l)&&(l=parseFloat(l)));null!=l&&(g[k]=l)}else"remove"==f.nodeName&&delete g[k]}f=f.nextSibling}d.putCellStyle(b,g)}c=c.nextSibling}return d};return a}());mxStylesheetCodec.allowEval=!0;
+mxCodecRegistry.register(function(){var a=new mxObjectCodec(new mxDefaultKeyHandler);a.encode=function(a,c){return null};a.decode=function(a,c,d){if(null!=d)for(c=c.firstChild;null!=c;){if(!this.processInclude(a,c,d)&&"add"==c.nodeName){var b=c.getAttribute("as"),f=c.getAttribute("action"),g=c.getAttribute("control");d.bindAction(b,f,g)}c=c.nextSibling}return d};return a}());
+var mxDefaultToolbarCodec=mxCodecRegistry.register(function(){var a=new mxObjectCodec(new mxDefaultToolbar);a.encode=function(a,c){return null};a.decode=function(a,c,d){if(null!=d){var b=d.editor;for(c=c.firstChild;null!=c;){if(c.nodeType==mxConstants.NODETYPE_ELEMENT&&!this.processInclude(a,c,d))if("separator"==c.nodeName)d.addSeparator();else if("br"==c.nodeName)d.toolbar.addBreak();else if("hr"==c.nodeName)d.toolbar.addLine();else if("add"==c.nodeName){var f=c.getAttribute("as"),f=mxR [...]
+f,g=c.getAttribute("icon"),k=c.getAttribute("pressedIcon"),l=c.getAttribute("action"),m=c.getAttribute("mode"),n=c.getAttribute("template"),p="0"!=c.getAttribute("toggle"),q=mxUtils.getTextContent(c),r=null;if(null!=l)r=d.addItem(f,g,l,k);else if(null!=m)var t=mxDefaultToolbarCodec.allowEval?mxUtils.eval(q):null,r=d.addMode(f,g,m,k,t);else if(null!=n||null!=q&&0<q.length)r=b.templates[n],n=c.getAttribute("style"),null!=r&&null!=n&&(r=b.graph.cloneCells([r])[0],r.setStyle(n)),n=null,null! [...]
+mxDefaultToolbarCodec.allowEval&&(n=mxUtils.eval(q)),r=d.addPrototype(f,g,r,k,n,p);else if(k=mxUtils.getChildNodes(c),0<k.length)if(null==g)for(n=d.addActionCombo(f),f=0;f<k.length;f++)p=k[f],"separator"==p.nodeName?d.addOption(n,"---"):"add"==p.nodeName&&(g=p.getAttribute("as"),p=p.getAttribute("action"),d.addActionOption(n,g,p));else{var u=null,x=d.addPrototype(f,g,function(){var a=b.templates[u.value];if(null!=a){var a=a.clone(),c=u.options[u.selectedIndex].cellStyle;null!=c&&a.setSty [...]
+a+" not found");return null},null,null,p),u=d.addCombo();mxEvent.addListener(u,"change",function(){d.toolbar.selectMode(x,function(a){a=mxUtils.convertPoint(b.graph.container,mxEvent.getClientX(a),mxEvent.getClientY(a));return b.addVertex(null,t(),a.x,a.y)});d.toolbar.noReset=!1});for(f=0;f<k.length;f++)p=k[f],"separator"==p.nodeName?d.addOption(u,"---"):"add"==p.nodeName&&(g=p.getAttribute("as"),q=p.getAttribute("template"),d.addOption(u,g,q||n).cellStyle=p.getAttribute("style"))}null!= [...]
+null!=n&&0<n.length&&r.setAttribute("id",n))}c=c.nextSibling}}return d};return a}());mxDefaultToolbarCodec.allowEval=!0;mxCodecRegistry.register(function(){var a=new mxObjectCodec(new mxDefaultPopupMenu);a.encode=function(a,c){return null};a.decode=function(a,c,d){var b=c.getElementsByTagName("include")[0];null!=b?this.processInclude(a,b,d):null!=d&&(d.config=c);return d};return a}());
+mxCodecRegistry.register(function(){var a=new mxObjectCodec(new mxEditor,"modified lastSnapshot ignoredChanges undoManager graphContainer toolbarContainer".split(" "));a.afterDecode=function(a,c,d){a=c.getAttribute("defaultEdge");null!=a&&(c.removeAttribute("defaultEdge"),d.defaultEdge=d.templates[a]);a=c.getAttribute("defaultGroup");null!=a&&(c.removeAttribute("defaultGroup"),d.defaultGroup=d.templates[a]);return d};a.decodeChild=function(a,c,d){if("Array"==c.nodeName){if("templates"==c [...]
+c,d);return}}else if("ui"==c.nodeName){this.decodeUi(a,c,d);return}mxObjectCodec.prototype.decodeChild.apply(this,arguments)};a.decodeUi=function(a,c,d){for(a=c.firstChild;null!=a;){if("add"==a.nodeName){c=a.getAttribute("as");var b=a.getAttribute("element"),f=a.getAttribute("style");if(null!=b)b=document.getElementById(b),null!=b&&null!=f&&(b.style.cssText+=";"+f);else{var g=parseInt(a.getAttribute("x")),k=parseInt(a.getAttribute("y")),l=a.getAttribute("width"),m=a.getAttribute("height" [...]
+b.style.cssText=f;(new mxWindow(mxResources.get(c)||c,b,g,k,l,m,!1,!0)).setVisible(!0)}"graph"==c?d.setGraphContainer(b):"toolbar"==c?d.setToolbarContainer(b):"title"==c?d.setTitleContainer(b):"status"==c?d.setStatusContainer(b):"map"==c&&d.setMapContainer(b)}else"resource"==a.nodeName?mxResources.add(a.getAttribute("basename")):"stylesheet"==a.nodeName&&mxClient.link("stylesheet",a.getAttribute("name"));a=a.nextSibling}};a.decodeTemplates=function(a,c,d){null==d.templates&&(d.templates= [...]
+for(var b=0;b<c.length;b++){for(var f=c[b].getAttribute("as"),g=c[b].firstChild;null!=g&&1!=g.nodeType;)g=g.nextSibling;null!=g&&(d.templates[f]=a.decodeCell(g))}};return a}());
\ No newline at end of file
diff --git a/airavata-kubernetes/workflow-composer/src/css/common.css b/airavata-kubernetes/workflow-composer/src/css/common.css
new file mode 100644
index 0000000..1733f6f
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/css/common.css
@@ -0,0 +1,160 @@
+div.mxRubberband {
+	position: absolute;
+	overflow: hidden;
+	border-style: solid;
+	border-width: 1px;
+	border-color: #0000FF;
+	background: #0077FF;
+}
+.mxCellEditor {
+	background: url('../images/transparent.gif');
+	border-color: transparent;
+	border-style: solid;
+	position: absolute;
+	overflow: visible;
+	word-wrap: normal;
+	border-width: 0;
+	min-width: 1px;
+	resize: none;
+	padding: 0px;
+	margin: 0px;
+}
+.mxPlainTextEditor * {
+	padding: 0px;
+	margin: 0px;
+}
+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:1px solid #c3c3c3;
+	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;
+	position: relative;
+}
+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;
+}
+table.mxPopupMenu {
+	border-collapse: collapse;
+	margin-top: 1px;
+	margin-bottom: 1px;
+}
+tr.mxPopupMenuItem {
+	color: black;
+	cursor: pointer;
+}
+tr.mxPopupMenuItemHover {
+	background-color: #000066;
+	color: #FFFFFF;
+	cursor: pointer;
+}
+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;
+}
+.mxDisabled {
+	opacity: 0.2 !important;
+	cursor:default !important;
+}
diff --git a/airavata-kubernetes/workflow-composer/src/css/explorer.css b/airavata-kubernetes/workflow-composer/src/css/explorer.css
new file mode 100644
index 0000000..50e704f
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/css/explorer.css
@@ -0,0 +1,18 @@
+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;
+}
+.mxDisabled {
+	filter:alpha(opacity=20) !important;
+}
diff --git a/airavata-kubernetes/workflow-composer/src/icons/copy.png b/airavata-kubernetes/workflow-composer/src/icons/copy.png
new file mode 100755
index 0000000..b6efb58
Binary files /dev/null and b/airavata-kubernetes/workflow-composer/src/icons/copy.png differ
diff --git a/airavata-kubernetes/workflow-composer/src/icons/http.png b/airavata-kubernetes/workflow-composer/src/icons/http.png
new file mode 100755
index 0000000..8cbd863
Binary files /dev/null and b/airavata-kubernetes/workflow-composer/src/icons/http.png differ
diff --git a/airavata-kubernetes/workflow-composer/src/icons/parallel.png b/airavata-kubernetes/workflow-composer/src/icons/parallel.png
new file mode 100755
index 0000000..4e10b7c
Binary files /dev/null and b/airavata-kubernetes/workflow-composer/src/icons/parallel.png differ
diff --git a/airavata-kubernetes/workflow-composer/src/icons/s3.png b/airavata-kubernetes/workflow-composer/src/icons/s3.png
new file mode 100755
index 0000000..0845fff
Binary files /dev/null and b/airavata-kubernetes/workflow-composer/src/icons/s3.png differ
diff --git a/airavata-kubernetes/workflow-composer/src/icons/ssh.png b/airavata-kubernetes/workflow-composer/src/icons/ssh.png
new file mode 100755
index 0000000..5e9f91d
Binary files /dev/null and b/airavata-kubernetes/workflow-composer/src/icons/ssh.png differ
diff --git a/airavata-kubernetes/workflow-composer/src/icons/start.png b/airavata-kubernetes/workflow-composer/src/icons/start.png
new file mode 100755
index 0000000..871fe36
Binary files /dev/null and b/airavata-kubernetes/workflow-composer/src/icons/start.png differ
diff --git a/airavata-kubernetes/workflow-composer/src/icons/stop.png b/airavata-kubernetes/workflow-composer/src/icons/stop.png
new file mode 100755
index 0000000..6cc9ace
Binary files /dev/null and b/airavata-kubernetes/workflow-composer/src/icons/stop.png differ
diff --git a/airavata-kubernetes/workflow-composer/src/images/button.gif b/airavata-kubernetes/workflow-composer/src/images/button.gif
new file mode 100644
index 0000000..ad55cab
Binary files /dev/null and b/airavata-kubernetes/workflow-composer/src/images/button.gif differ
diff --git a/airavata-kubernetes/workflow-composer/src/images/close.gif b/airavata-kubernetes/workflow-composer/src/images/close.gif
new file mode 100644
index 0000000..1069e94
Binary files /dev/null and b/airavata-kubernetes/workflow-composer/src/images/close.gif differ
diff --git a/airavata-kubernetes/workflow-composer/src/images/collapsed.gif b/airavata-kubernetes/workflow-composer/src/images/collapsed.gif
new file mode 100644
index 0000000..0276444
Binary files /dev/null and b/airavata-kubernetes/workflow-composer/src/images/collapsed.gif differ
diff --git a/airavata-kubernetes/workflow-composer/src/images/error.gif b/airavata-kubernetes/workflow-composer/src/images/error.gif
new file mode 100644
index 0000000..14e1aee
Binary files /dev/null and b/airavata-kubernetes/workflow-composer/src/images/error.gif differ
diff --git a/airavata-kubernetes/workflow-composer/src/images/expanded.gif b/airavata-kubernetes/workflow-composer/src/images/expanded.gif
new file mode 100644
index 0000000..3767b0b
Binary files /dev/null and b/airavata-kubernetes/workflow-composer/src/images/expanded.gif differ
diff --git a/airavata-kubernetes/workflow-composer/src/images/maximize.gif b/airavata-kubernetes/workflow-composer/src/images/maximize.gif
new file mode 100644
index 0000000..e27cf3e
Binary files /dev/null and b/airavata-kubernetes/workflow-composer/src/images/maximize.gif differ
diff --git a/airavata-kubernetes/workflow-composer/src/images/minimize.gif b/airavata-kubernetes/workflow-composer/src/images/minimize.gif
new file mode 100644
index 0000000..1e95e7c
Binary files /dev/null and b/airavata-kubernetes/workflow-composer/src/images/minimize.gif differ
diff --git a/airavata-kubernetes/workflow-composer/src/images/normalize.gif b/airavata-kubernetes/workflow-composer/src/images/normalize.gif
new file mode 100644
index 0000000..34a8d30
Binary files /dev/null and b/airavata-kubernetes/workflow-composer/src/images/normalize.gif differ
diff --git a/airavata-kubernetes/workflow-composer/src/images/point.gif b/airavata-kubernetes/workflow-composer/src/images/point.gif
new file mode 100644
index 0000000..9074c39
Binary files /dev/null and b/airavata-kubernetes/workflow-composer/src/images/point.gif differ
diff --git a/airavata-kubernetes/workflow-composer/src/images/resize.gif b/airavata-kubernetes/workflow-composer/src/images/resize.gif
new file mode 100644
index 0000000..ff558db
Binary files /dev/null and b/airavata-kubernetes/workflow-composer/src/images/resize.gif differ
diff --git a/airavata-kubernetes/workflow-composer/src/images/separator.gif b/airavata-kubernetes/workflow-composer/src/images/separator.gif
new file mode 100644
index 0000000..5c1b895
Binary files /dev/null and b/airavata-kubernetes/workflow-composer/src/images/separator.gif differ
diff --git a/airavata-kubernetes/workflow-composer/src/images/submenu.gif b/airavata-kubernetes/workflow-composer/src/images/submenu.gif
new file mode 100644
index 0000000..ffe7617
Binary files /dev/null and b/airavata-kubernetes/workflow-composer/src/images/submenu.gif differ
diff --git a/airavata-kubernetes/workflow-composer/src/images/transparent.gif b/airavata-kubernetes/workflow-composer/src/images/transparent.gif
new file mode 100644
index 0000000..76040f2
Binary files /dev/null and b/airavata-kubernetes/workflow-composer/src/images/transparent.gif differ
diff --git a/airavata-kubernetes/workflow-composer/src/images/warning.gif b/airavata-kubernetes/workflow-composer/src/images/warning.gif
new file mode 100644
index 0000000..705235f
Binary files /dev/null and b/airavata-kubernetes/workflow-composer/src/images/warning.gif differ
diff --git a/airavata-kubernetes/workflow-composer/src/images/warning.png b/airavata-kubernetes/workflow-composer/src/images/warning.png
new file mode 100644
index 0000000..2f78789
Binary files /dev/null and b/airavata-kubernetes/workflow-composer/src/images/warning.png differ
diff --git a/airavata-kubernetes/workflow-composer/src/images/window-title.gif b/airavata-kubernetes/workflow-composer/src/images/window-title.gif
new file mode 100644
index 0000000..231def8
Binary files /dev/null and b/airavata-kubernetes/workflow-composer/src/images/window-title.gif differ
diff --git a/airavata-kubernetes/workflow-composer/src/images/window.gif b/airavata-kubernetes/workflow-composer/src/images/window.gif
new file mode 100644
index 0000000..6631c4f
Binary files /dev/null and b/airavata-kubernetes/workflow-composer/src/images/window.gif differ
diff --git a/airavata-kubernetes/workflow-composer/src/js/components.js b/airavata-kubernetes/workflow-composer/src/js/components.js
new file mode 100644
index 0000000..b3e186f
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/components.js
@@ -0,0 +1,53 @@
+function fetchComponent(name, doc) {
+    if (name == "SSH") {
+        var pe = doc.createElement('ProcessingElement');
+        pe.setAttribute('name', 'SSH Processing element');
+        pe.setAttribute('Type', 'PROCESSING_ELEMENT');
+        pe.setAttribute('Compute-host', '192.168.1.112');
+        pe.setAttribute('User', 'root');
+        pe.setAttribute('Password', 'password');
+        pe.setAttribute('Command', '');
+        pe.setAttribute('Arguments', '');
+        pe.setAttribute("out-1", "Output");
+        pe.setAttribute("out-2", "Error");
+        pe.setAttribute("in-1", "Input");
+        return pe;
+    } else if (name == "CP") {
+        var pe = doc.createElement('ProcessingElement');
+        pe.setAttribute('name', 'Copy Processing element');
+        pe.setAttribute('type', 'PROCESSING_ELEMENT');
+        pe.setAttribute("out-1", "Output");
+        pe.setAttribute("out-2", "Error");
+        pe.setAttribute("in-1", "Input");
+        return pe;
+    } else if (name == "S3") {
+        var pe = doc.createElement('ProcessingElement');
+        pe.setAttribute('name', 'S3 Processing element');
+        pe.setAttribute('type', 'PROCESSING_ELEMENT');
+        pe.setAttribute("out-1", "Output");
+        pe.setAttribute("out-2", "Error");
+        pe.setAttribute("in-1", "Input");
+        return pe;
+    } else if (name == "START") {
+        var pe = doc.createElement('ProcessingElement');
+        pe.setAttribute('name', 'Start Operation');
+        pe.setAttribute('type', 'OPERATION');
+        pe.setAttribute("out-1", "Output");
+        return pe;
+    } else if (name == "STOP") {
+        var pe = doc.createElement('ProcessingElement');
+        pe.setAttribute('name', 'Stop Operation');
+        pe.setAttribute('type', 'OPERATION');
+        pe.setAttribute("in-1", "Input");
+        return pe;
+    } else if (name = "PARALLEL") {
+        var pe = doc.createElement('ProcessingElement');
+        pe.setAttribute('name', 'Parallel Operation');
+        pe.setAttribute('type', 'OPERATION');
+        pe.setAttribute("out-1", "Output1");
+        pe.setAttribute("out-2", "Output2");
+        pe.setAttribute("in-1", "Input");
+        return pe;
+    }
+
+}
\ No newline at end of file
diff --git a/airavata-kubernetes/workflow-composer/src/js/editor/mxDefaultKeyHandler.js b/airavata-kubernetes/workflow-composer/src/js/editor/mxDefaultKeyHandler.js
new file mode 100644
index 0000000..237dea4
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/editor/mxDefaultKeyHandler.js
@@ -0,0 +1,126 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxDefaultKeyHandler
+ *
+ * Binds keycodes to actionnames in an editor. This aggregates an internal
+ * <handler> and extends the implementation of <mxKeyHandler.escape> to not
+ * only cancel the editing, but also hide the properties dialog and fire an
+ * <mxEditor.escape> event via <editor>. An instance of this class is created
+ * by <mxEditor> and stored in <mxEditor.keyHandler>.
+ * 
+ * Example:
+ * 
+ * Bind the delete key to the delete action in an existing editor.
+ * 
+ * (code)
+ * var keyHandler = new mxDefaultKeyHandler(editor);
+ * keyHandler.bindAction(46, 'delete');
+ * (end)
+ *
+ * Codec:
+ * 
+ * This class uses the <mxDefaultKeyHandlerCodec> to read configuration
+ * data into an existing instance. See <mxDefaultKeyHandlerCodec> for a
+ * description of the configuration format.
+ * 
+ * Keycodes:
+ * 
+ * See <mxKeyHandler>.
+ * 
+ * An <mxEvent.ESCAPE> event is fired via the editor if the escape key is
+ * pressed.
+ * 
+ * Constructor: mxDefaultKeyHandler
+ *
+ * Constructs a new default key handler for the <mxEditor.graph> in the
+ * given <mxEditor>. (The editor may be null if a prototypical instance for
+ * a <mxDefaultKeyHandlerCodec> is created.)
+ * 
+ * Parameters:
+ * 
+ * editor - Reference to the enclosing <mxEditor>.
+ */
+function mxDefaultKeyHandler(editor)
+{
+	if (editor != null)
+	{
+		this.editor = editor;
+		this.handler = new mxKeyHandler(editor.graph);
+		
+		// Extends the escape function of the internal key
+		// handle to hide the properties dialog and fire
+		// the escape event via the editor instance
+		var old = this.handler.escape;
+		
+		this.handler.escape = function(evt)
+		{
+			old.apply(this, arguments);
+			editor.hideProperties();
+			editor.fireEvent(new mxEventObject(mxEvent.ESCAPE, 'event', evt));
+		};
+	}
+};
+	
+/**
+ * Variable: editor
+ *
+ * Reference to the enclosing <mxEditor>.
+ */
+mxDefaultKeyHandler.prototype.editor = null;
+
+/**
+ * Variable: handler
+ *
+ * Holds the <mxKeyHandler> for key event handling.
+ */
+mxDefaultKeyHandler.prototype.handler = null;
+
+/**
+ * Function: bindAction
+ *
+ * Binds the specified keycode to the given action in <editor>. The
+ * optional control flag specifies if the control key must be pressed
+ * to trigger the action.
+ *
+ * Parameters:
+ *
+ * code - Integer that specifies the keycode.
+ * action - Name of the action to execute in <editor>.
+ * control - Optional boolean that specifies if control must be pressed.
+ * Default is false.
+ */
+mxDefaultKeyHandler.prototype.bindAction = function (code, action, control)
+{
+	var keyHandler = mxUtils.bind(this, function()
+	{
+		this.editor.execute(action);
+	});
+
+	// Binds the function to control-down keycode
+	if (control)
+	{
+		this.handler.bindControlKey(code, keyHandler);
+	}
+
+	// Binds the function to the normal keycode
+	else
+	{
+		this.handler.bindKey(code, keyHandler);				
+	}
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the <handler> associated with this object. This does normally
+ * not need to be called, the <handler> is destroyed automatically when the
+ * window unloads (in IE) by <mxEditor>.
+ */
+mxDefaultKeyHandler.prototype.destroy = function ()
+{
+	this.handler.destroy();
+	this.handler = null;
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/editor/mxDefaultPopupMenu.js b/airavata-kubernetes/workflow-composer/src/js/editor/mxDefaultPopupMenu.js
new file mode 100644
index 0000000..2f2e6e7
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/editor/mxDefaultPopupMenu.js
@@ -0,0 +1,306 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxDefaultPopupMenu
+ *
+ * Creates popupmenus for mouse events. This object holds an XML node
+ * which is a description of the popup menu to be created. In
+ * <createMenu>, the configuration is applied to the context and
+ * the resulting menu items are added to the menu dynamically. See
+ * <createMenu> for a description of the configuration format.
+ * 
+ * This class does not create the DOM nodes required for the popup menu, it
+ * only parses an XML description to invoke the respective methods on an
+ * <mxPopupMenu> each time the menu is displayed.
+ *
+ * Codec:
+ * 
+ * This class uses the <mxDefaultPopupMenuCodec> to read configuration
+ * data into an existing instance, however, the actual parsing is done
+ * by this class during program execution, so the format is described
+ * below.
+ * 
+ * Constructor: mxDefaultPopupMenu
+ *
+ * Constructs a new popupmenu-factory based on given configuration.
+ *
+ * Paramaters:
+ *
+ * config - XML node that contains the configuration data.
+ */
+function mxDefaultPopupMenu(config)
+{
+	this.config = config;
+};
+
+/**
+ * Variable: imageBasePath
+ *
+ * Base path for all icon attributes in the config. Default is null.
+ */
+mxDefaultPopupMenu.prototype.imageBasePath = null;
+
+/**
+ * Variable: config
+ *
+ * XML node used as the description of new menu items. This node is
+ * used in <createMenu> to dynamically create the menu items if their
+ * respective conditions evaluate to true for the given arguments.
+ */
+mxDefaultPopupMenu.prototype.config = null;
+
+/**
+ * Function: createMenu
+ *
+ * This function is called from <mxEditor> to add items to the
+ * given menu based on <config>. The config is a sequence of
+ * the following nodes and attributes.
+ *
+ * Child Nodes: 
+ *
+ * add - Adds a new menu item. See below for attributes.
+ * separator - Adds a separator. No attributes.
+ * condition - Adds a custom condition. Name attribute.
+ * 
+ * The add-node may have a child node that defines a function to be invoked
+ * before the action is executed (or instead of an action to be executed).
+ *
+ * Attributes:
+ *
+ * as - Resource key for the label (needs entry in property file).
+ * action - Name of the action to execute in enclosing editor.
+ * icon - Optional icon (relative/absolute URL).
+ * iconCls - Optional CSS class for the icon.
+ * if - Optional name of condition that must be true (see below).
+ * enabled-if - Optional name of condition that specifies if the menu item
+ * should be enabled.
+ * name - Name of custom condition. Only for condition nodes.
+ *
+ * Conditions:
+ *
+ * nocell - No cell under the mouse.
+ * ncells - More than one cell selected.
+ * notRoot - Drilling position is other than home.
+ * cell - Cell under the mouse.
+ * notEmpty - Exactly one cell with children under mouse.
+ * expandable - Exactly one expandable cell under mouse.
+ * collapsable - Exactly one collapsable cell under mouse.
+ * validRoot - Exactly one cell which is a possible root under mouse.
+ * swimlane - Exactly one cell which is a swimlane under mouse.
+ *
+ * Example:
+ *
+ * To add a new item for a given action to the popupmenu:
+ * 
+ * (code)
+ * <mxDefaultPopupMenu as="popupHandler">
+ *   <add as="delete" action="delete" icon="images/delete.gif" if="cell"/>
+ * </mxDefaultPopupMenu>
+ * (end)
+ * 
+ * To add a new item for a custom function:
+ * 
+ * (code)
+ * <mxDefaultPopupMenu as="popupHandler">
+ *   <add as="action1"><![CDATA[
+ *		function (editor, cell, evt)
+ *		{
+ *			editor.execute('action1', cell, 'myArg');
+ *		}
+ *   ]]></add>
+ * </mxDefaultPopupMenu>
+ * (end)
+ * 
+ * The above example invokes action1 with an additional third argument via
+ * the editor instance. The third argument is passed to the function that
+ * defines action1. If the add-node has no action-attribute, then only the
+ * function defined in the text content is executed, otherwise first the
+ * function and then the action defined in the action-attribute is
+ * executed. The function in the text content has 3 arguments, namely the
+ * <mxEditor> instance, the <mxCell> instance under the mouse, and the
+ * native mouse event.
+ *
+ * Custom Conditions:
+ *
+ * To add a new condition for popupmenu items:
+ *  
+ * (code)
+ * <condition name="condition1"><![CDATA[
+ *   function (editor, cell, evt)
+ *   {
+ *     return cell != null;
+ *   }
+ * ]]></condition>
+ * (end)
+ * 
+ * The new condition can then be used in any item as follows:
+ * 
+ * (code)
+ * <add as="action1" action="action1" icon="action1.gif" if="condition1"/>
+ * (end)
+ * 
+ * The order in which the items and conditions appear is not significant as
+ * all connditions are evaluated before any items are created.
+ * 
+ * Parameters:
+ *
+ * editor - Enclosing <mxEditor> instance.
+ * menu - <mxPopupMenu> that is used for adding items and separators.
+ * cell - Optional <mxCell> which is under the mousepointer.
+ * evt - Optional mouse event which triggered the menu. 
+ */
+mxDefaultPopupMenu.prototype.createMenu = function(editor, menu, cell, evt)
+{
+	if (this.config != null)
+	{
+		var conditions = this.createConditions(editor, cell, evt);
+		var item = this.config.firstChild;
+
+		this.addItems(editor, menu, cell, evt, conditions, item, null);
+	}
+};
+
+/**
+ * Function: addItems
+ * 
+ * Recursively adds the given items and all of its children into the given menu.
+ * 
+ * Parameters:
+ *
+ * editor - Enclosing <mxEditor> instance.
+ * menu - <mxPopupMenu> that is used for adding items and separators.
+ * cell - Optional <mxCell> which is under the mousepointer.
+ * evt - Optional mouse event which triggered the menu.
+ * conditions - Array of names boolean conditions.
+ * item - XML node that represents the current menu item.
+ * parent - DOM node that represents the parent menu item.
+ */
+mxDefaultPopupMenu.prototype.addItems = function(editor, menu, cell, evt, conditions, item, parent)
+{
+	var addSeparator = false;
+	
+	while (item != null)
+	{
+		if (item.nodeName == 'add')
+		{
+			var condition = item.getAttribute('if');
+			
+			if (condition == null || conditions[condition])
+			{
+				var as = item.getAttribute('as');
+				as = mxResources.get(as) || as;
+				var funct = mxUtils.eval(mxUtils.getTextContent(item));
+				var action = item.getAttribute('action');
+				var icon = item.getAttribute('icon');
+				var iconCls = item.getAttribute('iconCls');
+				var enabledCond = item.getAttribute('enabled-if');
+				var enabled = enabledCond == null || conditions[enabledCond];
+				
+				if (addSeparator)
+				{
+					menu.addSeparator(parent);
+					addSeparator = false;
+				}
+				
+				if (icon != null && this.imageBasePath)
+				{
+					icon = this.imageBasePath + icon;
+				}
+				
+				var row = this.addAction(menu, editor, as, icon, funct, action, cell, parent, iconCls, enabled);
+				this.addItems(editor, menu, cell, evt, conditions, item.firstChild, row);
+			}
+		}
+		else if (item.nodeName == 'separator')
+		{
+			addSeparator = true;
+		}
+		
+		item = item.nextSibling;
+	}
+};
+
+/**
+ * Function: addAction
+ *
+ * Helper method to bind an action to a new menu item.
+ * 
+ * Parameters:
+ *
+ * menu - <mxPopupMenu> that is used for adding items and separators.
+ * editor - Enclosing <mxEditor> instance.
+ * lab - String that represents the label of the menu item.
+ * icon - Optional URL that represents the icon of the menu item.
+ * action - Optional name of the action to execute in the given editor.
+ * funct - Optional function to execute before the optional action. The
+ * function takes an <mxEditor>, the <mxCell> under the mouse and the
+ * mouse event that triggered the call.
+ * cell - Optional <mxCell> to use as an argument for the action.
+ * parent - DOM node that represents the parent menu item.
+ * iconCls - Optional CSS class for the menu icon.
+ * enabled - Optional boolean that specifies if the menu item is enabled.
+ * Default is true.
+ */
+mxDefaultPopupMenu.prototype.addAction = function(menu, editor, lab, icon, funct, action, cell, parent, iconCls, enabled)
+{
+	var clickHandler = function(evt)
+	{
+		if (typeof(funct) == 'function')
+		{
+			funct.call(editor, editor, cell, evt);
+		}
+		
+		if (action != null)
+		{
+			editor.execute(action, cell, evt);
+		}
+	};
+	
+	return menu.addItem(lab, icon, clickHandler, parent, iconCls, enabled);
+};
+
+/**
+ * Function: createConditions
+ * 
+ * Evaluates the default conditions for the given context.
+ */
+mxDefaultPopupMenu.prototype.createConditions = function(editor, cell, evt)
+{
+	// Creates array with conditions
+	var model = editor.graph.getModel();
+	var childCount = model.getChildCount(cell);
+	
+	// Adds some frequently used conditions
+	var conditions = [];
+	conditions['nocell'] = cell == null;
+	conditions['ncells'] = editor.graph.getSelectionCount() > 1;
+	conditions['notRoot'] = model.getRoot() !=
+		model.getParent(editor.graph.getDefaultParent());
+	conditions['cell'] = cell != null;
+	
+	var isCell = cell != null && editor.graph.getSelectionCount() == 1;
+	conditions['nonEmpty'] = isCell && childCount > 0;
+	conditions['expandable'] = isCell && editor.graph.isCellFoldable(cell, false);
+	conditions['collapsable'] = isCell && editor.graph.isCellFoldable(cell, true);
+	conditions['validRoot'] = isCell && editor.graph.isValidRoot(cell);
+	conditions['emptyValidRoot'] = conditions['validRoot'] && childCount == 0;
+	conditions['swimlane'] = isCell && editor.graph.isSwimlane(cell);
+
+	// Evaluates dynamic conditions from config file
+	var condNodes = this.config.getElementsByTagName('condition');
+	
+	for (var i=0; i<condNodes.length; i++)
+	{
+		var funct = mxUtils.eval(mxUtils.getTextContent(condNodes[i]));
+		var name = condNodes[i].getAttribute('name');
+		
+		if (name != null && typeof(funct) == 'function')
+		{
+			conditions[name] = funct(editor, cell, evt);
+		}
+	}
+	
+	return conditions;
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/editor/mxDefaultToolbar.js b/airavata-kubernetes/workflow-composer/src/js/editor/mxDefaultToolbar.js
new file mode 100644
index 0000000..8a7f2b6
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/editor/mxDefaultToolbar.js
@@ -0,0 +1,564 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxDefaultToolbar
+ *
+ * Toolbar for the editor. This modifies the state of the graph
+ * or inserts new cells upon mouse clicks.
+ * 
+ * Example:
+ * 
+ * Create a toolbar with a button to copy the selection into the clipboard,
+ * and a combo box with one action to paste the selection from the clipboard
+ * into the graph.
+ * 
+ * (code)
+ * var toolbar = new mxDefaultToolbar(container, editor);
+ * toolbar.addItem('Copy', null, 'copy');
+ * 
+ * var combo = toolbar.addActionCombo('More actions...');
+ * toolbar.addActionOption(combo, 'Paste', 'paste');
+ * (end) 
+ *
+ * Codec:
+ * 
+ * This class uses the <mxDefaultToolbarCodec> to read configuration
+ * data into an existing instance. See <mxDefaultToolbarCodec> for a
+ * description of the configuration format.
+ * 
+ * Constructor: mxDefaultToolbar
+ *
+ * Constructs a new toolbar for the given container and editor. The
+ * container and editor may be null if a prototypical instance for a
+ * <mxDefaultKeyHandlerCodec> is created.
+ *
+ * Parameters:
+ *
+ * container - DOM node that contains the toolbar.
+ * editor - Reference to the enclosing <mxEditor>. 
+ */
+function mxDefaultToolbar(container, editor)
+{
+	this.editor = editor;
+
+	if (container != null && editor != null)
+	{
+		this.init(container);
+	}
+};
+	
+/**
+ * Variable: editor
+ *
+ * Reference to the enclosing <mxEditor>.
+ */
+mxDefaultToolbar.prototype.editor = null;
+
+/**
+ * Variable: toolbar
+ *
+ * Holds the internal <mxToolbar>.
+ */
+mxDefaultToolbar.prototype.toolbar = null;
+
+/**
+ * Variable: resetHandler
+ *
+ * Reference to the function used to reset the <toolbar>.
+ */
+mxDefaultToolbar.prototype.resetHandler = null;
+
+/**
+ * Variable: spacing
+ *
+ * Defines the spacing between existing and new vertices in
+ * gridSize units when a new vertex is dropped on an existing
+ * cell. Default is 4 (40 pixels).
+ */
+mxDefaultToolbar.prototype.spacing = 4;
+
+/**
+ * Variable: connectOnDrop
+ * 
+ * Specifies if elements should be connected if new cells are dropped onto
+ * connectable elements. Default is false.
+ */
+mxDefaultToolbar.prototype.connectOnDrop = false;
+
+/**
+ * Variable: init
+ * 
+ * Constructs the <toolbar> for the given container and installs a listener
+ * that updates the <mxEditor.insertFunction> on <editor> if an item is
+ * selected in the toolbar. This assumes that <editor> is not null.
+ *
+ * Parameters:
+ *
+ * container - DOM node that contains the toolbar.
+ */
+mxDefaultToolbar.prototype.init = function(container)
+{
+	if (container != null)
+	{
+		this.toolbar = new mxToolbar(container);
+		
+		// Installs the insert function in the editor if an item is
+		// selected in the toolbar
+		this.toolbar.addListener(mxEvent.SELECT, mxUtils.bind(this, function(sender, evt)
+		{
+			var funct = evt.getProperty('function');
+			
+			if (funct != null)
+			{
+				this.editor.insertFunction = mxUtils.bind(this, function()
+				{
+					funct.apply(this, arguments);
+					this.toolbar.resetMode();
+				});
+			}
+			else
+			{
+				this.editor.insertFunction = null;
+			}
+		}));
+		
+		// Resets the selected tool after a doubleclick or escape keystroke
+		this.resetHandler = mxUtils.bind(this, function()
+		{
+			if (this.toolbar != null)
+			{
+				this.toolbar.resetMode(true);
+			}
+		});
+
+		this.editor.graph.addListener(mxEvent.DOUBLE_CLICK, this.resetHandler);
+		this.editor.addListener(mxEvent.ESCAPE, this.resetHandler);
+	}
+};
+
+/**
+ * Function: addItem
+ *
+ * Adds a new item that executes the given action in <editor>. The title,
+ * icon and pressedIcon are used to display the toolbar item.
+ * 
+ * Parameters:
+ *
+ * title - String that represents the title (tooltip) for the item.
+ * icon - URL of the icon to be used for displaying the item.
+ * action - Name of the action to execute when the item is clicked.
+ * pressed - Optional URL of the icon for the pressed state.
+ */
+mxDefaultToolbar.prototype.addItem = function(title, icon, action, pressed)
+{
+	var clickHandler = mxUtils.bind(this, function()
+	{
+		if (action != null && action.length > 0)
+		{
+			this.editor.execute(action);
+		}
+	});
+	
+	return this.toolbar.addItem(title, icon, clickHandler, pressed);
+};
+
+/**
+ * Function: addSeparator
+ *
+ * Adds a vertical separator using the optional icon.
+ * 
+ * Parameters:
+ * 
+ * icon - Optional URL of the icon that represents the vertical separator.
+ * Default is <mxClient.imageBasePath> + '/separator.gif'.
+ */
+mxDefaultToolbar.prototype.addSeparator = function(icon)
+{
+	icon = icon || mxClient.imageBasePath + '/separator.gif';
+	this.toolbar.addSeparator(icon);
+};
+	
+/**
+ * Function: addCombo
+ *
+ * Helper method to invoke <mxToolbar.addCombo> on <toolbar> and return the
+ * resulting DOM node.
+ */
+mxDefaultToolbar.prototype.addCombo = function()
+{
+	return this.toolbar.addCombo();
+};
+		
+/**
+ * Function: addActionCombo
+ *
+ * Helper method to invoke <mxToolbar.addActionCombo> on <toolbar> using
+ * the given title and return the resulting DOM node.
+ * 
+ * Parameters:
+ * 
+ * title - String that represents the title of the combo.
+ */
+mxDefaultToolbar.prototype.addActionCombo = function(title)
+{
+	return this.toolbar.addActionCombo(title);
+};
+
+/**
+ * Function: addActionOption
+ *
+ * Binds the given action to a option with the specified label in the
+ * given combo. Combo is an object returned from an earlier call to
+ * <addCombo> or <addActionCombo>.
+ * 
+ * Parameters:
+ * 
+ * combo - DOM node that represents the combo box.
+ * title - String that represents the title of the combo.
+ * action - Name of the action to execute in <editor>.
+ */
+mxDefaultToolbar.prototype.addActionOption = function(combo, title, action)
+{
+	var clickHandler = mxUtils.bind(this, function()
+	{
+		this.editor.execute(action);
+	});
+	
+	this.addOption(combo, title, clickHandler);
+};
+
+/**
+ * Function: addOption
+ *
+ * Helper method to invoke <mxToolbar.addOption> on <toolbar> and return
+ * the resulting DOM node that represents the option.
+ * 
+ * Parameters:
+ * 
+ * combo - DOM node that represents the combo box.
+ * title - String that represents the title of the combo.
+ * value - Object that represents the value of the option.
+ */
+mxDefaultToolbar.prototype.addOption = function(combo, title, value)
+{
+	return this.toolbar.addOption(combo, title, value);
+};
+	
+/**
+ * Function: addMode
+ *
+ * Creates an item for selecting the given mode in the <editor>'s graph.
+ * Supported modenames are select, connect and pan.
+ * 
+ * Parameters:
+ * 
+ * title - String that represents the title of the item.
+ * icon - URL of the icon that represents the item.
+ * mode - String that represents the mode name to be used in
+ * <mxEditor.setMode>.
+ * pressed - Optional URL of the icon that represents the pressed state.
+ * funct - Optional JavaScript function that takes the <mxEditor> as the
+ * first and only argument that is executed after the mode has been
+ * selected.
+ */
+mxDefaultToolbar.prototype.addMode = function(title, icon, mode, pressed, funct)
+{
+	var clickHandler = mxUtils.bind(this, function()
+	{
+		this.editor.setMode(mode);
+		
+		if (funct != null)
+		{
+			funct(this.editor);
+		}
+	});
+	
+	return this.toolbar.addSwitchMode(title, icon, clickHandler, pressed);
+};
+
+/**
+ * Function: addPrototype
+ *
+ * Creates an item for inserting a clone of the specified prototype cell into
+ * the <editor>'s graph. The ptype may either be a cell or a function that
+ * returns a cell.
+ * 
+ * Parameters:
+ * 
+ * title - String that represents the title of the item.
+ * icon - URL of the icon that represents the item.
+ * ptype - Function or object that represents the prototype cell. If ptype
+ * is a function then it is invoked with no arguments to create new
+ * instances.
+ * pressed - Optional URL of the icon that represents the pressed state.
+ * insert - Optional JavaScript function that handles an insert of the new
+ * cell. This function takes the <mxEditor>, new cell to be inserted, mouse
+ * event and optional <mxCell> under the mouse pointer as arguments.
+ * toggle - Optional boolean that specifies if the item can be toggled.
+ * Default is true.
+ */
+mxDefaultToolbar.prototype.addPrototype = function(title, icon, ptype, pressed, insert, toggle)
+{
+	// Creates a wrapper function that is in charge of constructing
+	// the new cell instance to be inserted into the graph
+	var factory = mxUtils.bind(this, function()
+	{
+		if (typeof(ptype) == 'function')
+		{
+			return ptype();
+		}
+		else if (ptype != null)
+		{
+			return this.editor.graph.cloneCells([ptype])[0];
+		}
+		
+		return null;
+	});
+	
+	// Defines the function for a click event on the graph
+	// after this item has been selected in the toolbar
+	var clickHandler = mxUtils.bind(this, function(evt, cell)
+	{
+		if (typeof(insert) == 'function')
+		{
+			insert(this.editor, factory(), evt, cell);
+		}
+		else
+		{
+			this.drop(factory(), evt, cell);
+		}
+		
+		this.toolbar.resetMode();
+		mxEvent.consume(evt);
+	});
+	
+	var img = this.toolbar.addMode(title, icon, clickHandler, pressed, null, toggle);
+				
+	// Creates a wrapper function that calls the click handler without
+	// the graph argument
+	var dropHandler = function(graph, evt, cell)
+	{
+		clickHandler(evt, cell);
+	};
+	
+	this.installDropHandler(img, dropHandler);
+	
+	return img;
+};
+
+/**
+ * Function: drop
+ * 
+ * Handles a drop from a toolbar item to the graph. The given vertex
+ * represents the new cell to be inserted. This invokes <insert> or
+ * <connect> depending on the given target cell.
+ * 
+ * Parameters:
+ * 
+ * vertex - <mxCell> to be inserted.
+ * evt - Mouse event that represents the drop.
+ * target - Optional <mxCell> that represents the drop target.
+ */
+mxDefaultToolbar.prototype.drop = function(vertex, evt, target)
+{
+	var graph = this.editor.graph;
+	var model = graph.getModel();
+	
+	if (target == null ||
+		model.isEdge(target) ||
+		!this.connectOnDrop ||
+		!graph.isCellConnectable(target))
+	{
+		while (target != null &&
+			!graph.isValidDropTarget(target, [vertex], evt))
+		{
+			target = model.getParent(target);
+		}
+		
+		this.insert(vertex, evt, target);
+	}
+	else
+	{
+		this.connect(vertex, evt, target);
+	}
+};
+
+/**
+ * Function: insert
+ *
+ * Handles a drop by inserting the given vertex into the given parent cell
+ * or the default parent if no parent is specified.
+ * 
+ * Parameters:
+ * 
+ * vertex - <mxCell> to be inserted.
+ * evt - Mouse event that represents the drop.
+ * parent - Optional <mxCell> that represents the parent.
+ */
+mxDefaultToolbar.prototype.insert = function(vertex, evt, target)
+{
+	var graph = this.editor.graph;
+	
+	if (graph.canImportCell(vertex))
+	{
+		var x = mxEvent.getClientX(evt);
+		var y = mxEvent.getClientY(evt);
+		var pt = mxUtils.convertPoint(graph.container, x, y);
+		
+		// Splits the target edge or inserts into target group
+		if (graph.isSplitEnabled() &&
+			graph.isSplitTarget(target, [vertex], evt))
+		{
+			return graph.splitEdge(target, [vertex], null, pt.x, pt.y);
+		}
+		else
+		{
+			return this.editor.addVertex(target, vertex, pt.x, pt.y);
+		}
+	}
+	
+	return null;
+};
+
+/**
+ * Function: connect
+ * 
+ * Handles a drop by connecting the given vertex to the given source cell.
+ * 
+ * vertex - <mxCell> to be inserted.
+ * evt - Mouse event that represents the drop.
+ * source - Optional <mxCell> that represents the source terminal.
+ */
+mxDefaultToolbar.prototype.connect = function(vertex, evt, source)
+{
+	var graph = this.editor.graph;
+	var model = graph.getModel();
+	
+	if (source != null &&
+		graph.isCellConnectable(vertex) &&
+		graph.isEdgeValid(null, source, vertex))
+	{
+		var edge = null;
+
+		model.beginUpdate();
+		try
+		{
+			var geo = model.getGeometry(source);
+			var g = model.getGeometry(vertex).clone();
+			
+			// Moves the vertex away from the drop target that will
+			// be used as the source for the new connection
+			g.x = geo.x + (geo.width - g.width) / 2;
+			g.y = geo.y + (geo.height - g.height) / 2;
+			
+			var step = this.spacing * graph.gridSize;
+			var dist = model.getDirectedEdgeCount(source, true) * 20;
+			
+			if (this.editor.horizontalFlow)
+			{
+				g.x += (g.width + geo.width) / 2 + step + dist;
+			}
+			else
+			{
+				g.y += (g.height + geo.height) / 2 + step + dist;
+			}
+			
+			vertex.setGeometry(g);
+			
+			// Fires two add-events with the code below - should be fixed
+			// to only fire one add event for both inserts
+			var parent = model.getParent(source);
+			graph.addCell(vertex, parent);
+			graph.constrainChild(vertex);
+
+			// Creates the edge using the editor instance and calls
+			// the second function that fires an add event
+			edge = this.editor.createEdge(source, vertex);
+			
+			if (model.getGeometry(edge) == null)
+			{
+				var edgeGeometry = new mxGeometry();
+				edgeGeometry.relative = true;
+				
+				model.setGeometry(edge, edgeGeometry);
+			}
+			
+			graph.addEdge(edge, parent, source, vertex);
+		}
+		finally
+		{
+			model.endUpdate();
+		}
+		
+		graph.setSelectionCells([vertex, edge]);
+		graph.scrollCellToVisible(vertex);
+	}
+};
+
+/**
+ * Function: installDropHandler
+ * 
+ * Makes the given img draggable using the given function for handling a
+ * drop event.
+ * 
+ * Parameters:
+ * 
+ * img - DOM node that represents the image.
+ * dropHandler - Function that handles a drop of the image.
+ */
+mxDefaultToolbar.prototype.installDropHandler = function (img, dropHandler)
+{
+	var sprite = document.createElement('img');
+	sprite.setAttribute('src', img.getAttribute('src'));
+
+	// Handles delayed loading of the images
+	var loader = mxUtils.bind(this, function(evt)
+	{
+		// Preview uses the image node with double size. Later this can be
+		// changed to use a separate preview and guides, but for this the
+		// dropHandler must use the additional x- and y-arguments and the
+		// dragsource which makeDraggable returns much be configured to
+		// use guides via mxDragSource.isGuidesEnabled.
+		sprite.style.width = (2 * img.offsetWidth) + 'px';
+		sprite.style.height = (2 * img.offsetHeight) + 'px';
+
+		mxUtils.makeDraggable(img, this.editor.graph, dropHandler,
+			sprite);
+		mxEvent.removeListener(sprite, 'load', loader);
+	});
+
+	if (mxClient.IS_IE)
+	{
+		loader();
+	}
+	else
+	{
+		mxEvent.addListener(sprite, 'load', loader);
+	}	
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the <toolbar> associated with this object and removes all
+ * installed listeners. This does normally not need to be called, the
+ * <toolbar> is destroyed automatically when the window unloads (in IE) by
+ * <mxEditor>.
+ */
+mxDefaultToolbar.prototype.destroy = function ()
+{
+	if (this.resetHandler != null)
+	{
+		this.editor.graph.removeListener('dblclick', this.resetHandler);
+		this.editor.removeListener('escape', this.resetHandler);
+		this.resetHandler = null;
+	}
+	
+	if (this.toolbar != null)
+	{
+		this.toolbar.destroy();
+		this.toolbar = null;
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/editor/mxEditor.js b/airavata-kubernetes/workflow-composer/src/js/editor/mxEditor.js
new file mode 100644
index 0000000..3aec641
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/editor/mxEditor.js
@@ -0,0 +1,3114 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxEditor
+ *
+ * Extends <mxEventSource> to implement a application wrapper for a graph that
+ * adds <actions>, I/O using <mxCodec>, auto-layout using <mxLayoutManager>,
+ * command history using <undoManager>, and standard dialogs and widgets, eg.
+ * properties, help, outline, toolbar, and popupmenu. It also adds <templates>
+ * to be used as cells in toolbars, auto-validation using the <validation>
+ * flag, attribute cycling using <cycleAttributeValues>, higher-level events
+ * such as <root>, and backend integration using <urlPost> and <urlImage>. 
+ * 
+ * Actions:
+ * 
+ * Actions are functions stored in the <actions> array under their names. The
+ * functions take the <mxEditor> as the first, and an optional <mxCell> as the
+ * second argument and are invoked using <execute>. Any additional arguments
+ * passed to execute are passed on to the action as-is.
+ * 
+ * A list of built-in actions is available in the <addActions> description.
+ * 
+ * Read/write Diagrams:
+ * 
+ * To read a diagram from an XML string, for example from a textfield within the 
+ * page, the following code is used:
+ * 
+ * (code)
+ * var doc = mxUtils.parseXML(xmlString);
+ * var node = doc.documentElement;
+ * editor.readGraphModel(node);
+ * (end)
+ * 
+ * For reading a diagram from a remote location, use the <open> method.
+ * 
+ * To save diagrams in XML on a server, you can set the <urlPost> variable. 
+ * This variable will be used in <getUrlPost> to construct a URL for the post 
+ * request that is issued in the <save> method. The post request contains the 
+ * XML representation of the diagram as returned by <writeGraphModel> in the 
+ * xml parameter.
+ * 
+ * On the server side, the post request is processed using standard
+ * technologies such as Java Servlets, CGI, .NET or ASP.
+ * 
+ * Here are some examples of processing a post request in various languages.
+ * 
+ * - Java: URLDecoder.decode(request.getParameter("xml"), "UTF-8").replace("\n", "&#xa;")
+ * 
+ * Note that the linefeeds should only be replaced if the XML is
+ * processed in Java, for example when creating an image, but not
+ * if the XML is passed back to the client-side.
+ * 
+ * - .NET: HttpUtility.UrlDecode(context.Request.Params["xml"])
+ * - PHP: urldecode($_POST["xml"])
+ * 
+ * Creating images:
+ * 
+ * A backend (Java, PHP or C#) is required for creating images. The
+ * distribution contains an example for each backend (ImageHandler.java,
+ * ImageHandler.cs and graph.php). More information about using a backend
+ * to create images can be found in the readme.html files. Note that the
+ * preview is implemented using VML/SVG in the browser and does not require
+ * a backend. The backend is only required to creates images (bitmaps).
+ * 
+ * Special characters:
+ * 
+ * Note There are five characters that should always appear in XML content as
+ * escapes, so that they do not interact with the syntax of the markup. These
+ * are part of the language for all documents based on XML and for HTML.
+ * 
+ * - &lt; (<)
+ * - &gt; (>)
+ * - &amp; (&)
+ * - &quot; (")
+ * - &apos; (')
+ * 
+ * Although it is part of the XML language, &apos; is not defined in HTML.
+ * For this reason the XHTML specification recommends instead the use of
+ * &#39; if text may be passed to a HTML user agent.
+ * 
+ * If you are having problems with special characters on the server-side then
+ * you may want to try the <escapePostData> flag.
+ * 
+ * For converting decimal escape sequences inside strings, a user has provided
+ * us with the following function:
+ * 
+ * (code)
+ * function html2js(text)
+ * {
+ *   var entitySearch = /&#[0-9]+;/;
+ *   var entity;
+ *   
+ *   while (entity = entitySearch.exec(text))
+ *   {
+ *     var charCode = entity[0].substring(2, entity[0].length -1);
+ *     text = text.substring(0, entity.index)
+ *            + String.fromCharCode(charCode)
+ *            + text.substring(entity.index + entity[0].length);
+ *   }
+ *   
+ *   return text;
+ * }
+ * (end)
+ * 
+ * Otherwise try using hex escape sequences and the built-in unescape function
+ * for converting such strings.
+ * 
+ * Local Files:
+ * 
+ * For saving and opening local files, no standardized method exists that
+ * works across all browsers. The recommended way of dealing with local files
+ * is to create a backend that streams the XML data back to the browser (echo)
+ * as an attachment so that a Save-dialog is displayed on the client-side and
+ * the file can be saved to the local disk.
+ * 
+ * For example, in PHP the code that does this looks as follows.
+ * 
+ * (code)
+ * $xml = stripslashes($_POST["xml"]);
+ * header("Content-Disposition: attachment; filename=\"diagram.xml\"");
+ * echo($xml);
+ * (end)
+ * 
+ * To open a local file, the file should be uploaded via a form in the browser
+ * and then opened from the server in the editor.
+ * 
+ * Cell Properties:
+ * 
+ * The properties displayed in the properties dialog are the attributes and 
+ * values of the cell's user object, which is an XML node. The XML node is 
+ * defined in the templates section of the config file.
+ * 
+ * The templates are stored in <mxEditor.templates> and contain cells which
+ * are cloned at insertion time to create new vertices by use of drag and
+ * drop from the toolbar. Each entry in the toolbar for adding a new vertex
+ * must refer to an existing template.
+ * 
+ * In the following example, the task node is a business object and only the 
+ * mxCell node and its mxGeometry child contain graph information:
+ * 
+ * (code)
+ * <Task label="Task" description="">
+ *   <mxCell vertex="true">
+ *     <mxGeometry as="geometry" width="72" height="32"/>
+ *   </mxCell>
+ * </Task> 
+ * (end)
+ * 
+ * The idea is that the XML representation is inverse from the in-memory 
+ * representation: The outer XML node is the user object and the inner node is 
+ * the cell. This means the user object of the cell is the Task node with no 
+ * children for the above example:
+ * 
+ * (code)
+ * <Task label="Task" description=""/>
+ * (end)
+ * 
+ * The Task node can have any tag name, attributes and child nodes. The 
+ * <mxCodec> will use the XML hierarchy as the user object, while removing the 
+ * "known annotations", such as the mxCell node. At save-time the cell data 
+ * will be "merged" back into the user object. The user object is only modified 
+ * via the properties dialog during the lifecycle of the cell.
+ * 
+ * In the default implementation of <createProperties>, the user object's
+ * attributes are put into a form for editing. Attributes are changed using
+ * the <mxCellAttributeChange> action in the model. The dialog can be replaced 
+ * by overriding the <createProperties> hook or by replacing the showProperties
+ * action in <actions>. Alternatively, the entry in the config file's popupmenu
+ * section can be modified to invoke a different action.
+ * 
+ * If you want to displey the properties dialog on a doubleclick, you can set
+ * <mxEditor.dblClickAction> to showProperties as follows:
+ * 
+ * (code)
+ * editor.dblClickAction = 'showProperties';
+ * (end)
+ * 
+ * Popupmenu and Toolbar:
+ * 
+ * The toolbar and popupmenu are typically configured using the respective
+ * sections in the config file, that is, the popupmenu is defined as follows:
+ * 
+ * (code)
+ * <mxEditor>
+ *   <mxDefaultPopupMenu as="popupHandler">
+ * 		<add as="cut" action="cut" icon="images/cut.gif"/>
+ *      ...
+ * (end)
+ * 
+ * New entries can be added to the toolbar by inserting an add-node into the
+ * above configuration. Existing entries may be removed and changed by
+ * modifying or removing the respective entries in the configuration.
+ * The configuration is read by the <mxDefaultPopupMenuCodec>, the format of the
+ * configuration is explained in <mxDefaultPopupMenu.decode>.
+ * 
+ * The toolbar is defined in the mxDefaultToolbar section. Items can be added
+ * and removed in this section.
+ * 
+ * (code)
+ * <mxEditor>
+ *   <mxDefaultToolbar>
+ *     <add as="save" action="save" icon="images/save.gif"/>
+ *     <add as="Swimlane" template="swimlane" icon="images/swimlane.gif"/>
+ *     ...
+ * (end)
+ * 
+ * The format of the configuration is described in
+ * <mxDefaultToolbarCodec.decode>.
+ * 
+ * Ids:
+ * 
+ * For the IDs, there is an implicit behaviour in <mxCodec>: It moves the Id
+ * from the cell to the user object at encoding time and vice versa at decoding
+ * time. For example, if the Task node from above has an id attribute, then
+ * the <mxCell.id> of the corresponding cell will have this value. If there
+ * is no Id collision in the model, then the cell may be retrieved using this
+ * Id with the <mxGraphModel.getCell> function. If there is a collision, a new
+ * Id will be created for the cell using <mxGraphModel.createId>. At encoding
+ * time, this new Id will replace the value previously stored under the id
+ * attribute in the Task node.
+ * 
+ * See <mxEditorCodec>, <mxDefaultToolbarCodec> and <mxDefaultPopupMenuCodec>
+ * for information about configuring the editor and user interface.
+ * 
+ * Programmatically inserting cells:
+ * 
+ * For inserting a new cell, say, by clicking a button in the document,
+ * the following code can be used. This requires an reference to the editor.
+ * 
+ * (code)
+ * var userObject = new Object();
+ * var parent = editor.graph.getDefaultParent();
+ * var model = editor.graph.model;
+ * model.beginUpdate();
+ * try
+ * {
+ *   editor.graph.insertVertex(parent, null, userObject, 20, 20, 80, 30);
+ * }
+ * finally
+ * {
+ *   model.endUpdate();
+ * }
+ * (end)
+ * 
+ * If a template cell from the config file should be inserted, then a clone
+ * of the template can be created as follows. The clone is then inserted using
+ * the add function instead of addVertex.
+ * 
+ * (code)
+ * var template = editor.templates['task'];
+ * var clone = editor.graph.model.cloneCell(template);
+ * (end)
+ * 
+ * Resources:
+ *
+ * resources/editor - Language resources for mxEditor
+ *
+ * Callback: onInit
+ *
+ * Called from within the constructor. In the callback,
+ * "this" refers to the editor instance.
+ *
+ * Cookie: mxgraph=seen
+ *
+ * Set when the editor is started. Never expires. Use
+ * <resetFirstTime> to reset this cookie. This cookie
+ * only exists if <onInit> is implemented.
+ *
+ * Event: mxEvent.OPEN
+ *
+ * Fires after a file was opened in <open>. The <code>filename</code> property
+ * contains the filename that was used. The same value is also available in
+ * <filename>.
+ *
+ * Event: mxEvent.SAVE
+ *
+ * Fires after the current file was saved in <save>. The <code>url</code>
+ * property contains the URL that was used for saving.
+ *
+ * Event: mxEvent.POST
+ * 
+ * Fires if a successful response was received in <postDiagram>. The
+ * <code>request</code> property contains the <mxXmlRequest>, the
+ * <code>url</code> and <code>data</code> properties contain the URL and the
+ * data that were used in the post request. 
+ *
+ * Event: mxEvent.ROOT
+ *
+ * Fires when the current root has changed, or when the title of the current
+ * root has changed. This event has no properties.
+ *
+ * Event: mxEvent.BEFORE_ADD_VERTEX
+ * 
+ * Fires before a vertex is added in <addVertex>. The <code>vertex</code>
+ * property contains the new vertex and the <code>parent</code> property
+ * contains its parent.
+ * 
+ * Event: mxEvent.ADD_VERTEX
+ * 
+ * Fires between begin- and endUpdate in <addVertex>. The <code>vertex</code>
+ * property contains the vertex that is being inserted.
+ * 
+ * Event: mxEvent.AFTER_ADD_VERTEX
+ * 
+ * Fires after a vertex was inserted and selected in <addVertex>. The
+ * <code>vertex</code> property contains the new vertex.
+ * 
+ * Example:
+ * 
+ * For starting an in-place edit after a new vertex has been added to the
+ * graph, the following code can be used.
+ * 
+ * (code)
+ * editor.addListener(mxEvent.AFTER_ADD_VERTEX, function(sender, evt)
+ * {
+ *   var vertex = evt.getProperty('vertex');
+ * 
+ *   if (editor.graph.isCellEditable(vertex))
+ *   {
+ *   	editor.graph.startEditingAtCell(vertex);
+ *   }
+ * });
+ * (end)
+ * 
+ * Event: mxEvent.ESCAPE
+ * 
+ * Fires when the escape key is pressed. The <code>event</code> property
+ * contains the key event.
+ * 
+ * Constructor: mxEditor
+ *
+ * Constructs a new editor. This function invokes the <onInit> callback
+ * upon completion.
+ *
+ * Example:
+ *
+ * (code)
+ * var config = mxUtils.load('config/diagrameditor.xml').getDocumentElement();
+ * var editor = new mxEditor(config);
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * config - Optional XML node that contains the configuration.
+ */
+function mxEditor(config)
+{
+	this.actions = [];
+	this.addActions();
+
+	// Executes the following only if a document has been instanciated.
+	// That is, don't execute when the editorcodec is setup.
+	if (document.body != null)
+	{
+		// Defines instance fields
+		this.cycleAttributeValues = [];
+		this.popupHandler = new mxDefaultPopupMenu();
+		this.undoManager = new mxUndoManager();
+
+		// Creates the graph and toolbar without the containers
+		this.graph = this.createGraph();
+		this.toolbar = this.createToolbar();
+
+		// Creates the global keyhandler (requires graph instance)
+		this.keyHandler = new mxDefaultKeyHandler(this);
+
+		// Configures the editor using the URI
+		// which was passed to the ctor
+		this.configure(config);
+		
+		// Assigns the swimlaneIndicatorColorAttribute on the graph
+		this.graph.swimlaneIndicatorColorAttribute = this.cycleAttributeName;
+
+		// Checks if the <onInit> hook has been set
+		if (this.onInit != null)
+		{
+			// Invokes the <onInit> hook
+			this.onInit();
+		}
+		
+		// Automatic deallocation of memory
+		if (mxClient.IS_IE)
+		{
+			mxEvent.addListener(window, 'unload', mxUtils.bind(this, function()
+			{
+				this.destroy();
+			}));
+		}
+	}
+};
+
+/**
+ * Installs the required language resources at class
+ * loading time.
+ */
+if (mxLoadResources)
+{
+	mxResources.add(mxClient.basePath+'/resources/editor');
+}
+
+/**
+ * Extends mxEventSource.
+ */
+mxEditor.prototype = new mxEventSource();
+mxEditor.prototype.constructor = mxEditor;
+
+/**
+ * Group: Controls and Handlers
+ */
+	
+/**
+ * Variable: askZoomResource
+ * 
+ * Specifies the resource key for the zoom dialog. If the resource for this
+ * key does not exist then the value is used as the error message. Default
+ * is 'askZoom'.
+ */
+mxEditor.prototype.askZoomResource = (mxClient.language != 'none') ? 'askZoom' : '';
+	
+/**
+ * Variable: lastSavedResource
+ * 
+ * Specifies the resource key for the last saved info. If the resource for
+ * this key does not exist then the value is used as the error message.
+ * Default is 'lastSaved'.
+ */
+mxEditor.prototype.lastSavedResource = (mxClient.language != 'none') ? 'lastSaved' : '';
+	
+/**
+ * Variable: currentFileResource
+ * 
+ * Specifies the resource key for the current file info. If the resource for
+ * this key does not exist then the value is used as the error message.
+ * Default is 'lastSaved'.
+ */
+mxEditor.prototype.currentFileResource = (mxClient.language != 'none') ? 'currentFile' : '';
+	
+/**
+ * Variable: propertiesResource
+ * 
+ * Specifies the resource key for the properties window title. If the
+ * resource for this key does not exist then the value is used as the
+ * error message. Default is 'properties'.
+ */
+mxEditor.prototype.propertiesResource = (mxClient.language != 'none') ? 'properties' : '';
+	
+/**
+ * Variable: tasksResource
+ * 
+ * Specifies the resource key for the tasks window title. If the
+ * resource for this key does not exist then the value is used as the
+ * error message. Default is 'tasks'.
+ */
+mxEditor.prototype.tasksResource = (mxClient.language != 'none') ? 'tasks' : '';
+	
+/**
+ * Variable: helpResource
+ * 
+ * Specifies the resource key for the help window title. If the
+ * resource for this key does not exist then the value is used as the
+ * error message. Default is 'help'.
+ */
+mxEditor.prototype.helpResource = (mxClient.language != 'none') ? 'help' : '';
+	
+/**
+ * Variable: outlineResource
+ * 
+ * Specifies the resource key for the outline window title. If the
+ * resource for this key does not exist then the value is used as the
+ * error message. Default is 'outline'.
+ */
+mxEditor.prototype.outlineResource = (mxClient.language != 'none') ? 'outline' : '';
+	
+/**
+ * Variable: outline
+ * 
+ * Reference to the <mxWindow> that contains the outline. The <mxOutline>
+ * is stored in outline.outline.
+ */
+mxEditor.prototype.outline = null;
+
+/**
+ * Variable: graph
+ *
+ * Holds a <mxGraph> for displaying the diagram. The graph
+ * is created in <setGraphContainer>.
+ */
+mxEditor.prototype.graph = null;
+
+/**
+ * Variable: graphRenderHint
+ *
+ * Holds the render hint used for creating the
+ * graph in <setGraphContainer>. See <mxGraph>.
+ * Default is null.
+ */
+mxEditor.prototype.graphRenderHint = null;
+
+/**
+ * Variable: toolbar
+ *
+ * Holds a <mxDefaultToolbar> for displaying the toolbar. The
+ * toolbar is created in <setToolbarContainer>.
+ */
+mxEditor.prototype.toolbar = null;
+
+/**
+ * Variable: status
+ *
+ * DOM container that holds the statusbar. Default is null.
+ * Use <setStatusContainer> to set this value.
+ */
+mxEditor.prototype.status = null;
+
+/**
+ * Variable: popupHandler
+ *
+ * Holds a <mxDefaultPopupMenu> for displaying
+ * popupmenus.
+ */
+mxEditor.prototype.popupHandler = null;
+
+/**
+ * Variable: undoManager
+ *
+ * Holds an <mxUndoManager> for the command history.
+ */
+mxEditor.prototype.undoManager = null;
+
+/**
+ * Variable: keyHandler
+ *
+ * Holds a <mxDefaultKeyHandler> for handling keyboard events.
+ * The handler is created in <setGraphContainer>.
+ */
+mxEditor.prototype.keyHandler = null;
+
+/**
+ * Group: Actions and Options
+ */
+
+/**
+ * Variable: actions
+ *
+ * Maps from actionnames to actions, which are functions taking
+ * the editor and the cell as arguments. Use <addAction>
+ * to add or replace an action and <execute> to execute an action
+ * by name, passing the cell to be operated upon as the second
+ * argument.
+ */
+mxEditor.prototype.actions = null;
+
+/**
+ * Variable: dblClickAction
+ *
+ * Specifies the name of the action to be executed
+ * when a cell is double clicked. Default is edit.
+ * 
+ * To handle a singleclick, use the following code.
+ * 
+ * (code)
+ * editor.graph.addListener(mxEvent.CLICK, function(sender, evt)
+ * {
+ *   var e = evt.getProperty('event');
+ *   var cell = evt.getProperty('cell');
+ * 
+ *   if (cell != null && !e.isConsumed())
+ *   {
+ *     // Do something useful with cell...
+ *     e.consume();
+ *   }
+ * });
+ * (end)
+ */
+mxEditor.prototype.dblClickAction = 'edit';
+
+/**
+ * Variable: swimlaneRequired
+ * 
+ * Specifies if new cells must be inserted
+ * into an existing swimlane. Otherwise, cells
+ * that are not swimlanes can be inserted as
+ * top-level cells. Default is false.
+ */
+mxEditor.prototype.swimlaneRequired = false;
+
+/**
+ * Variable: disableContextMenu
+ *
+ * Specifies if the context menu should be disabled in the graph container.
+ * Default is true.
+ */
+mxEditor.prototype.disableContextMenu = true;
+
+/**
+ * Group: Templates
+ */
+
+/**
+ * Variable: insertFunction
+ *
+ * Specifies the function to be used for inserting new
+ * cells into the graph. This is assigned from the
+ * <mxDefaultToolbar> if a vertex-tool is clicked.
+ */
+mxEditor.prototype.insertFunction = null;
+
+/**
+ * Variable: forcedInserting
+ *
+ * Specifies if a new cell should be inserted on a single
+ * click even using <insertFunction> if there is a cell 
+ * under the mousepointer, otherwise the cell under the 
+ * mousepointer is selected. Default is false.
+ */
+mxEditor.prototype.forcedInserting = false;
+
+/**
+ * Variable: templates
+ * 
+ * Maps from names to protoype cells to be used
+ * in the toolbar for inserting new cells into
+ * the diagram.
+ */
+mxEditor.prototype.templates = null;
+
+/**
+ * Variable: defaultEdge
+ * 
+ * Prototype edge cell that is used for creating
+ * new edges.
+ */
+mxEditor.prototype.defaultEdge = null;
+
+/**
+ * Variable: defaultEdgeStyle
+ * 
+ * Specifies the edge style to be returned in <getEdgeStyle>.
+ * Default is null.
+ */
+mxEditor.prototype.defaultEdgeStyle = null;
+
+/**
+ * Variable: defaultGroup
+ * 
+ * Prototype group cell that is used for creating
+ * new groups.
+ */
+mxEditor.prototype.defaultGroup = null;
+
+/**
+ * Variable: graphRenderHint
+ *
+ * Default size for the border of new groups. If null,
+ * then then <mxGraph.gridSize> is used. Default is
+ * null.
+ */
+mxEditor.prototype.groupBorderSize = null;
+
+/**
+ * Group: Backend Integration
+ */
+
+/**
+ * Variable: filename
+ *
+ * Contains the URL of the last opened file as a string.
+ * Default is null.
+ */
+mxEditor.prototype.filename = null;
+
+/**
+ * Variable: lineFeed
+ *
+ * Character to be used for encoding linefeeds in <save>. Default is '&#xa;'.
+ */
+mxEditor.prototype.linefeed = '&#xa;';
+
+/**
+ * Variable: postParameterName
+ *
+ * Specifies if the name of the post parameter that contains the diagram
+ * data in a post request to the server. Default is xml.
+ */
+mxEditor.prototype.postParameterName = 'xml';
+
+/**
+ * Variable: escapePostData
+ *
+ * Specifies if the data in the post request for saving a diagram
+ * should be converted using encodeURIComponent. Default is true.
+ */
+mxEditor.prototype.escapePostData = true;
+
+/**
+ * Variable: urlPost
+ *
+ * Specifies the URL to be used for posting the diagram
+ * to a backend in <save>.
+ */
+mxEditor.prototype.urlPost = null;
+
+/**
+ * Variable: urlImage
+ *
+ * Specifies the URL to be used for creating a bitmap of
+ * the graph in the image action.
+ */
+mxEditor.prototype.urlImage = null;
+
+/**
+ * Group: Autolayout
+ */
+
+/**
+ * Variable: horizontalFlow
+ *
+ * Specifies the direction of the flow
+ * in the diagram. This is used in the
+ * layout algorithms. Default is false,
+ * ie. vertical flow.
+ */
+mxEditor.prototype.horizontalFlow = false;
+
+/**
+ * Variable: layoutDiagram
+ *
+ * Specifies if the top-level elements in the
+ * diagram should be layed out using a vertical
+ * or horizontal stack depending on the setting
+ * of <horizontalFlow>. The spacing between the
+ * swimlanes is specified by <swimlaneSpacing>.
+ * Default is false.
+ * 
+ * If the top-level elements are swimlanes, then
+ * the intra-swimlane layout is activated by
+ * the <layoutSwimlanes> switch.
+ */
+mxEditor.prototype.layoutDiagram = false;
+
+/**
+ * Variable: swimlaneSpacing
+ *
+ * Specifies the spacing between swimlanes if
+ * automatic layout is turned on in
+ * <layoutDiagram>. Default is 0.
+ */
+mxEditor.prototype.swimlaneSpacing = 0;
+
+/**
+ * Variable: maintainSwimlanes
+ * 
+ * Specifies if the swimlanes should be kept at the same
+ * width or height depending on the setting of
+ * <horizontalFlow>.  Default is false.
+ * 
+ * For horizontal flows, all swimlanes
+ * have the same height and for vertical flows, all swimlanes
+ * have the same width. Furthermore, the swimlanes are
+ * automatically "stacked" if <layoutDiagram> is true.
+ */
+mxEditor.prototype.maintainSwimlanes = false;
+
+/**
+ * Variable: layoutSwimlanes
+ *
+ * Specifies if the children of swimlanes should
+ * be layed out, either vertically or horizontally
+ * depending on <horizontalFlow>.
+ * Default is false.
+ */
+mxEditor.prototype.layoutSwimlanes = false;
+
+/**
+ * Group: Attribute Cycling
+ */
+ 
+/**
+ * Variable: cycleAttributeValues
+ * 
+ * Specifies the attribute values to be cycled when
+ * inserting new swimlanes. Default is an empty
+ * array.
+ */
+mxEditor.prototype.cycleAttributeValues = null;
+
+/**
+ * Variable: cycleAttributeIndex
+ * 
+ * Index of the last consumed attribute index. If a new
+ * swimlane is inserted, then the <cycleAttributeValues>
+ * at this index will be used as the value for
+ * <cycleAttributeName>. Default is 0.
+ */
+mxEditor.prototype.cycleAttributeIndex = 0;
+
+/**
+ * Variable: cycleAttributeName
+ * 
+ * Name of the attribute to be assigned a <cycleAttributeValues>
+ * when inserting new swimlanes. Default is fillColor.
+ */
+mxEditor.prototype.cycleAttributeName = 'fillColor';
+
+/**
+ * Group: Windows
+ */
+
+/**
+ * Variable: tasks
+ * 
+ * Holds the <mxWindow> created in <showTasks>.
+ */
+mxEditor.prototype.tasks = null;
+
+/**
+ * Variable: tasksWindowImage
+ *
+ * Icon for the tasks window.
+ */
+mxEditor.prototype.tasksWindowImage = null;
+
+/**
+ * Variable: tasksTop
+ * 
+ * Specifies the top coordinate of the tasks window in pixels.
+ * Default is 20.
+ */
+mxEditor.prototype.tasksTop = 20;
+
+/**
+ * Variable: help
+ * 
+ * Holds the <mxWindow> created in <showHelp>.
+ */
+mxEditor.prototype.help = null;
+
+/**
+ * Variable: helpWindowImage
+ *
+ * Icon for the help window.
+ */
+mxEditor.prototype.helpWindowImage = null;
+
+/**
+ * Variable: urlHelp
+ *
+ * Specifies the URL to be used for the contents of the
+ * Online Help window. This is usually specified in the
+ * resources file under urlHelp for language-specific
+ * online help support.
+ */
+mxEditor.prototype.urlHelp = null;
+
+/**
+ * Variable: helpWidth
+ * 
+ * Specifies the width of the help window in pixels.
+ * Default is 300.
+ */
+mxEditor.prototype.helpWidth = 300;
+	
+/**
+ * Variable: helpWidth
+ * 
+ * Specifies the width of the help window in pixels.
+ * Default is 260.
+ */
+mxEditor.prototype.helpHeight = 260;
+
+/**
+ * Variable: propertiesWidth
+ * 
+ * Specifies the width of the properties window in pixels.
+ * Default is 240.
+ */
+mxEditor.prototype.propertiesWidth = 240;
+		
+/**
+ * Variable: propertiesHeight
+ * 
+ * Specifies the height of the properties window in pixels.
+ * If no height is specified then the window will be automatically
+ * sized to fit its contents. Default is null.
+ */
+mxEditor.prototype.propertiesHeight = null;
+		
+/**
+ * Variable: movePropertiesDialog
+ *
+ * Specifies if the properties dialog should be automatically
+ * moved near the cell it is displayed for, otherwise the
+ * dialog is not moved. This value is only taken into 
+ * account if the dialog is already visible. Default is false.
+ */
+mxEditor.prototype.movePropertiesDialog = false;
+
+/**
+ * Variable: validating
+ *
+ * Specifies if <mxGraph.validateGraph> should automatically be invoked after
+ * each change. Default is false.
+ */
+mxEditor.prototype.validating = false;
+
+/**
+ * Variable: modified
+ *
+ * True if the graph has been modified since it was last saved.
+ */
+mxEditor.prototype.modified = false;
+
+/**
+ * Function: isModified
+ * 
+ * Returns <modified>.
+ */
+mxEditor.prototype.isModified = function ()
+{
+	return this.modified;
+};
+
+/**
+ * Function: setModified
+ * 
+ * Sets <modified> to the specified boolean value.
+ */
+mxEditor.prototype.setModified = function (value)
+{
+	this.modified = value;
+};
+
+/**
+ * Function: addActions
+ *
+ * Adds the built-in actions to the editor instance.
+ *
+ * save - Saves the graph using <urlPost>.
+ * print - Shows the graph in a new print preview window.
+ * show - Shows the graph in a new window.
+ * exportImage - Shows the graph as a bitmap image using <getUrlImage>.
+ * refresh - Refreshes the graph's display.
+ * cut - Copies the current selection into the clipboard
+ * and removes it from the graph.
+ * copy - Copies the current selection into the clipboard.
+ * paste - Pastes the clipboard into the graph.
+ * delete - Removes the current selection from the graph.
+ * group - Puts the current selection into a new group.
+ * ungroup - Removes the selected groups and selects the children.
+ * undo - Undoes the last change on the graph model.
+ * redo - Redoes the last change on the graph model.
+ * zoom - Sets the zoom via a dialog.
+ * zoomIn - Zooms into the graph.
+ * zoomOut - Zooms out of the graph
+ * actualSize - Resets the scale and translation on the graph.
+ * fit - Changes the scale so that the graph fits into the window.
+ * showProperties - Shows the properties dialog.
+ * selectAll - Selects all cells.
+ * selectNone - Clears the selection.
+ * selectVertices - Selects all vertices.
+ * selectEdges = Selects all edges.
+ * edit - Starts editing the current selection cell.
+ * enterGroup - Drills down into the current selection cell.
+ * exitGroup - Moves up in the drilling hierachy
+ * home - Moves to the topmost parent in the drilling hierarchy
+ * selectPrevious - Selects the previous cell.
+ * selectNext - Selects the next cell.
+ * selectParent - Selects the parent of the selection cell.
+ * selectChild - Selects the first child of the selection cell.
+ * collapse - Collapses the currently selected cells.
+ * expand - Expands the currently selected cells.
+ * bold - Toggle bold text style.
+ * italic - Toggle italic text style.
+ * underline - Toggle underline text style.
+ * alignCellsLeft - Aligns the selection cells at the left.
+ * alignCellsCenter - Aligns the selection cells in the center.
+ * alignCellsRight - Aligns the selection cells at the right.
+ * alignCellsTop - Aligns the selection cells at the top.
+ * alignCellsMiddle - Aligns the selection cells in the middle.
+ * alignCellsBottom - Aligns the selection cells at the bottom.
+ * alignFontLeft - Sets the horizontal text alignment to left.
+ * alignFontCenter - Sets the horizontal text alignment to center.
+ * alignFontRight - Sets the horizontal text alignment to right.
+ * alignFontTop - Sets the vertical text alignment to top.
+ * alignFontMiddle - Sets the vertical text alignment to middle.
+ * alignFontBottom - Sets the vertical text alignment to bottom.
+ * toggleTasks - Shows or hides the tasks window.
+ * toggleHelp - Shows or hides the help window.
+ * toggleOutline - Shows or hides the outline window.
+ * toggleConsole - Shows or hides the console window.
+ */
+mxEditor.prototype.addActions = function ()
+{
+	this.addAction('save', function(editor)
+	{
+		editor.save();
+	});
+	
+	this.addAction('print', function(editor)
+	{
+		var preview = new mxPrintPreview(editor.graph, 1);
+		preview.open();
+	});
+	
+	this.addAction('show', function(editor)
+	{
+		mxUtils.show(editor.graph, null, 10, 10);
+	});
+
+	this.addAction('exportImage', function(editor)
+	{
+		var url = editor.getUrlImage();
+		
+		if (url == null || mxClient.IS_LOCAL)
+		{
+			editor.execute('show');
+		}
+		else
+		{
+			var node = mxUtils.getViewXml(editor.graph, 1);
+			var xml = mxUtils.getXml(node, '\n');
+
+			mxUtils.submit(url, editor.postParameterName + '=' +
+				encodeURIComponent(xml), document, '_blank');
+		}
+	});
+	
+	this.addAction('refresh', function(editor)
+	{
+		editor.graph.refresh();
+	});
+	
+	this.addAction('cut', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			mxClipboard.cut(editor.graph);
+		}
+	});
+	
+	this.addAction('copy', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			mxClipboard.copy(editor.graph);
+		}
+	});
+	
+	this.addAction('paste', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			mxClipboard.paste(editor.graph);
+		}
+	});
+	
+	this.addAction('delete', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.removeCells();
+		}
+	});
+	
+	this.addAction('group', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.setSelectionCell(editor.groupCells());
+		}
+	});
+	
+	this.addAction('ungroup', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.setSelectionCells(editor.graph.ungroupCells());
+		}
+	});
+	
+	this.addAction('removeFromParent', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.removeCellsFromParent();
+		}
+	});
+	
+	this.addAction('undo', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.undo();
+		}
+	});
+	
+	this.addAction('redo', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.redo();
+		}
+	});
+	
+	this.addAction('zoomIn', function(editor)
+	{
+		editor.graph.zoomIn();
+	});
+	
+	this.addAction('zoomOut', function(editor)
+	{
+		editor.graph.zoomOut();
+	});
+	
+	this.addAction('actualSize', function(editor)
+	{
+		editor.graph.zoomActual();
+	});
+	
+	this.addAction('fit', function(editor)
+	{
+		editor.graph.fit();
+	});
+	
+	this.addAction('showProperties', function(editor, cell)
+	{
+		editor.showProperties(cell);
+	});
+	
+	this.addAction('selectAll', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.selectAll();
+		}
+	});
+	
+	this.addAction('selectNone', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.clearSelection();
+		}
+	});
+	
+	this.addAction('selectVertices', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.selectVertices();
+		}
+	});
+	
+	this.addAction('selectEdges', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.selectEdges();
+		}
+	});
+	
+	this.addAction('edit', function(editor, cell)
+	{
+		if (editor.graph.isEnabled() &&
+			editor.graph.isCellEditable(cell))
+		{
+			editor.graph.startEditingAtCell(cell);
+		}
+	});
+	
+	this.addAction('toBack', function(editor, cell)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.orderCells(true);
+		}
+	});
+	
+	this.addAction('toFront', function(editor, cell)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.orderCells(false);
+		}
+	});
+	
+	this.addAction('enterGroup', function(editor, cell)
+	{
+		editor.graph.enterGroup(cell);
+	});
+	
+	this.addAction('exitGroup', function(editor)
+	{
+		editor.graph.exitGroup();
+	});
+	
+	this.addAction('home', function(editor)
+	{
+		editor.graph.home();
+	});
+	
+	this.addAction('selectPrevious', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.selectPreviousCell();
+		}
+	});
+	
+	this.addAction('selectNext', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.selectNextCell();
+		}
+	});
+	
+	this.addAction('selectParent', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.selectParentCell();
+		}
+	});
+	
+	this.addAction('selectChild', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.selectChildCell();
+		}
+	});
+	
+	this.addAction('collapse', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.foldCells(true);
+		}
+	});
+	
+	this.addAction('collapseAll', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			var cells = editor.graph.getChildVertices();
+			editor.graph.foldCells(true, false, cells);
+		}
+	});
+	
+	this.addAction('expand', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.foldCells(false);
+		}
+	});
+	
+	this.addAction('expandAll', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			var cells = editor.graph.getChildVertices();
+			editor.graph.foldCells(false, false, cells);
+		}
+	});
+	
+	this.addAction('bold', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.toggleCellStyleFlags(
+				mxConstants.STYLE_FONTSTYLE,
+				mxConstants.FONT_BOLD);
+		}
+	});
+	
+	this.addAction('italic', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.toggleCellStyleFlags(
+				mxConstants.STYLE_FONTSTYLE,
+				mxConstants.FONT_ITALIC);
+		}
+	});
+	
+	this.addAction('underline', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.toggleCellStyleFlags(
+				mxConstants.STYLE_FONTSTYLE,
+				mxConstants.FONT_UNDERLINE);
+		}
+	});
+
+	this.addAction('alignCellsLeft', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.alignCells(mxConstants.ALIGN_LEFT);
+		}
+	});
+	
+	this.addAction('alignCellsCenter', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.alignCells(mxConstants.ALIGN_CENTER);
+		}
+	});
+	
+	this.addAction('alignCellsRight', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.alignCells(mxConstants.ALIGN_RIGHT);
+		}
+	});
+	
+	this.addAction('alignCellsTop', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.alignCells(mxConstants.ALIGN_TOP);
+		}
+	});
+	
+	this.addAction('alignCellsMiddle', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.alignCells(mxConstants.ALIGN_MIDDLE);
+		}
+	});
+	
+	this.addAction('alignCellsBottom', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.alignCells(mxConstants.ALIGN_BOTTOM);
+		}
+	});
+	
+	this.addAction('alignFontLeft', function(editor)
+	{
+		
+		editor.graph.setCellStyles(
+			mxConstants.STYLE_ALIGN,
+			mxConstants.ALIGN_LEFT);
+	});
+	
+	this.addAction('alignFontCenter', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.setCellStyles(
+				mxConstants.STYLE_ALIGN,
+				mxConstants.ALIGN_CENTER);
+		}
+	});
+	
+	this.addAction('alignFontRight', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.setCellStyles(
+				mxConstants.STYLE_ALIGN,
+				mxConstants.ALIGN_RIGHT);
+		}
+	});
+	
+	this.addAction('alignFontTop', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.setCellStyles(
+				mxConstants.STYLE_VERTICAL_ALIGN,
+				mxConstants.ALIGN_TOP);
+		}
+	});
+	
+	this.addAction('alignFontMiddle', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.setCellStyles(
+				mxConstants.STYLE_VERTICAL_ALIGN,
+				mxConstants.ALIGN_MIDDLE);
+		}
+	});
+	
+	this.addAction('alignFontBottom', function(editor)
+	{
+		if (editor.graph.isEnabled())
+		{
+			editor.graph.setCellStyles(
+				mxConstants.STYLE_VERTICAL_ALIGN,
+				mxConstants.ALIGN_BOTTOM);
+		}
+	});
+	
+	this.addAction('zoom', function(editor)
+	{
+		var current = editor.graph.getView().scale*100;
+		var scale = parseFloat(mxUtils.prompt(
+			mxResources.get(editor.askZoomResource) ||
+			editor.askZoomResource,
+			current))/100;
+
+		if (!isNaN(scale))
+		{
+			editor.graph.getView().setScale(scale);
+		}
+	});
+	
+	this.addAction('toggleTasks', function(editor)
+	{
+		if (editor.tasks != null)
+		{
+			editor.tasks.setVisible(!editor.tasks.isVisible());
+		}
+		else
+		{
+			editor.showTasks();
+		}
+	});
+	
+	this.addAction('toggleHelp', function(editor)
+	{
+		if (editor.help != null)
+		{
+			editor.help.setVisible(!editor.help.isVisible());
+		}
+		else
+		{
+			editor.showHelp();
+		}
+	});
+	
+	this.addAction('toggleOutline', function(editor)
+	{
+		if (editor.outline == null)
+		{
+			editor.showOutline();
+		}
+		else
+		{
+			editor.outline.setVisible(!editor.outline.isVisible());
+		}
+	});
+	
+	this.addAction('toggleConsole', function(editor)
+	{
+		mxLog.setVisible(!mxLog.isVisible());
+	});
+};
+
+/**
+ * Function: configure
+ *
+ * Configures the editor using the specified node. To load the
+ * configuration from a given URL the following code can be used to obtain
+ * the XML node.
+ * 
+ * (code)
+ * var node = mxUtils.load(url).getDocumentElement();
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * node - XML node that contains the configuration.
+ */
+mxEditor.prototype.configure = function (node)
+{
+	if (node != null)
+	{
+		// Creates a decoder for the XML data
+		// and uses it to configure the editor
+		var dec = new mxCodec(node.ownerDocument);
+		dec.decode(node, this);
+		
+		// Resets the counters, modified state and
+		// command history
+		this.resetHistory();
+	}
+};
+
+/**
+ * Function: resetFirstTime
+ * 
+ * Resets the cookie that is used to remember if the editor has already
+ * been used.
+ */
+mxEditor.prototype.resetFirstTime = function ()
+{
+	document.cookie =
+		'mxgraph=seen; expires=Fri, 27 Jul 2001 02:47:11 UTC; path=/';
+};
+
+/**
+ * Function: resetHistory
+ * 
+ * Resets the command history, modified state and counters.
+ */
+mxEditor.prototype.resetHistory = function ()
+{
+	this.lastSnapshot = new Date().getTime();
+	this.undoManager.clear();
+	this.ignoredChanges = 0;
+	this.setModified(false);
+};
+
+/**
+ * Function: addAction
+ * 
+ * Binds the specified actionname to the specified function.
+ * 
+ * Parameters:
+ * 
+ * actionname - String that specifies the name of the action
+ * to be added.
+ * funct - Function that implements the new action. The first
+ * argument of the function is the editor it is used
+ * with, the second argument is the cell it operates
+ * upon.
+ * 
+ * Example:
+ * (code)
+ * editor.addAction('test', function(editor, cell)
+ * {
+ * 		mxUtils.alert("test "+cell);
+ * });
+ * (end)
+ */
+mxEditor.prototype.addAction = function (actionname, funct)
+{
+	this.actions[actionname] = funct;
+};
+
+/**
+ * Function: execute
+ * 
+ * Executes the function with the given name in <actions> passing the
+ * editor instance and given cell as the first and second argument. All
+ * additional arguments are passed to the action as well. This method
+ * contains a try-catch block and displays an error message if an action
+ * causes an exception. The exception is re-thrown after the error
+ * message was displayed.
+ * 
+ * Example:
+ * 
+ * (code)
+ * editor.execute("showProperties", cell);
+ * (end)
+ */
+mxEditor.prototype.execute = function (actionname, cell, evt)
+{
+	var action = this.actions[actionname];
+	
+	if (action != null)
+	{
+		try
+		{
+			// Creates the array of arguments by replacing the actionname
+			// with the editor instance in the args of this function
+			var args = arguments;
+			args[0] = this;
+			
+			// Invokes the function on the editor using the args
+			action.apply(this, args);
+		}
+		catch (e)
+		{
+			mxUtils.error('Cannot execute ' + actionname +
+				': ' + e.message, 280, true);
+			
+			throw e;
+		}
+	}
+	else
+	{
+		mxUtils.error('Cannot find action '+actionname, 280, true);
+	}
+};
+
+/**
+ * Function: addTemplate
+ * 
+ * Adds the specified template under the given name in <templates>.
+ */
+mxEditor.prototype.addTemplate = function (name, template)
+{
+	this.templates[name] = template;
+};
+
+/**
+ * Function: getTemplate
+ * 
+ * Returns the template for the given name.
+ */
+mxEditor.prototype.getTemplate = function (name)
+{
+	return this.templates[name];
+};
+
+/**
+ * Function: createGraph
+ * 
+ * Creates the <graph> for the editor. The graph is created with no
+ * container and is initialized from <setGraphContainer>.
+ */
+mxEditor.prototype.createGraph = function ()
+{
+	var graph = new mxGraph(null, null, this.graphRenderHint);
+	
+	// Enables rubberband, tooltips, panning
+	graph.setTooltips(true);
+	graph.setPanning(true);
+
+	// Overrides the dblclick method on the graph to
+	// invoke the dblClickAction for a cell and reset
+	// the selection tool in the toolbar
+	this.installDblClickHandler(graph);
+	
+	// Installs the command history
+	this.installUndoHandler(graph);
+
+	// Installs the handlers for the root event
+	this.installDrillHandler(graph);
+	
+	// Installs the handler for validation
+	this.installChangeHandler(graph);
+
+	// Installs the handler for calling the
+	// insert function and consume the
+	// event if an insert function is defined
+	this.installInsertHandler(graph);
+
+	// Redirects the function for creating the
+	// popupmenu items
+	graph.popupMenuHandler.factoryMethod =
+		mxUtils.bind(this, function(menu, cell, evt)
+		{
+			return this.createPopupMenu(menu, cell, evt);
+		});
+
+	// Redirects the function for creating
+	// new connections in the diagram
+	graph.connectionHandler.factoryMethod =
+		mxUtils.bind(this, function(source, target)
+		{
+			return this.createEdge(source, target);
+		});
+	
+	// Maintains swimlanes and installs autolayout
+	this.createSwimlaneManager(graph);
+	this.createLayoutManager(graph);
+	
+	return graph;
+};
+
+/**
+ * Function: createSwimlaneManager
+ * 
+ * Sets the graph's container using <mxGraph.init>.
+ */
+mxEditor.prototype.createSwimlaneManager = function (graph)
+{
+	var swimlaneMgr = new mxSwimlaneManager(graph, false);
+
+	swimlaneMgr.isHorizontal = mxUtils.bind(this, function()
+	{
+		return this.horizontalFlow;
+	});
+	
+	swimlaneMgr.isEnabled = mxUtils.bind(this, function()
+	{
+		return this.maintainSwimlanes;
+	});
+	
+	return swimlaneMgr;
+};
+
+/**
+ * Function: createLayoutManager
+ * 
+ * Creates a layout manager for the swimlane and diagram layouts, that
+ * is, the locally defined inter- and intraswimlane layouts.
+ */
+mxEditor.prototype.createLayoutManager = function (graph)
+{
+	var layoutMgr = new mxLayoutManager(graph);
+	
+	var self = this; // closure
+	layoutMgr.getLayout = function(cell)
+	{
+		var layout = null;
+		var model = self.graph.getModel();
+		
+		if (model.getParent(cell) != null)
+		{
+			// Executes the swimlane layout if a child of
+			// a swimlane has been changed. The layout is
+			// lazy created in createSwimlaneLayout.
+			if (self.layoutSwimlanes &&
+				graph.isSwimlane(cell))
+			{
+				if (self.swimlaneLayout == null)
+				{
+					self.swimlaneLayout = self.createSwimlaneLayout();
+				}
+				
+				layout = self.swimlaneLayout;
+			}
+			
+			// Executes the diagram layout if the modified
+			// cell is a top-level cell. The layout is
+			// lazy created in createDiagramLayout.
+			else if (self.layoutDiagram &&
+				(graph.isValidRoot(cell) ||
+				model.getParent(model.getParent(cell)) == null))
+			{
+				if (self.diagramLayout == null)
+				{
+					self.diagramLayout = self.createDiagramLayout();
+				}
+				
+				layout = self.diagramLayout;
+			}
+		}
+			
+		return layout;
+	};
+	
+	return layoutMgr;
+};
+
+/**
+ * Function: setGraphContainer
+ * 
+ * Sets the graph's container using <mxGraph.init>.
+ */
+mxEditor.prototype.setGraphContainer = function (container)
+{
+	if (this.graph.container == null)
+	{
+		// Creates the graph instance inside the given container and render hint
+		//this.graph = new mxGraph(container, null, this.graphRenderHint);
+		this.graph.init(container);
+
+		// Install rubberband selection as the last
+		// action handler in the chain
+		this.rubberband = new mxRubberband(this.graph);
+
+		// Disables the context menu
+		if (this.disableContextMenu)
+		{
+			mxEvent.disableContextMenu(container);
+		}
+
+		// Workaround for stylesheet directives in IE
+		if (mxClient.IS_QUIRKS)
+		{
+			new mxDivResizer(container);
+		}
+	}
+};
+
+/**
+ * Function: installDblClickHandler
+ * 
+ * Overrides <mxGraph.dblClick> to invoke <dblClickAction>
+ * on a cell and reset the selection tool in the toolbar.
+ */
+mxEditor.prototype.installDblClickHandler = function (graph)
+{
+	// Installs a listener for double click events
+	graph.addListener(mxEvent.DOUBLE_CLICK,
+		mxUtils.bind(this, function(sender, evt)
+		{
+			var cell = evt.getProperty('cell');
+			
+			if (cell != null &&
+				graph.isEnabled() &&
+				this.dblClickAction != null)
+			{
+				this.execute(this.dblClickAction, cell);
+				evt.consume();
+			}
+		})
+	);
+};
+		
+/**
+ * Function: installUndoHandler
+ * 
+ * Adds the <undoManager> to the graph model and the view.
+ */
+mxEditor.prototype.installUndoHandler = function (graph)
+{				
+	var listener = mxUtils.bind(this, function(sender, evt)
+	{
+		var edit = evt.getProperty('edit');
+		this.undoManager.undoableEditHappened(edit);
+	});
+	
+	graph.getModel().addListener(mxEvent.UNDO, listener);
+	graph.getView().addListener(mxEvent.UNDO, listener);
+
+	// Keeps the selection state in sync
+	var undoHandler = function(sender, evt)
+	{
+		var changes = evt.getProperty('edit').changes;
+		graph.setSelectionCells(graph.getSelectionCellsForChanges(changes));
+	};
+	
+	this.undoManager.addListener(mxEvent.UNDO, undoHandler);
+	this.undoManager.addListener(mxEvent.REDO, undoHandler);
+};
+		
+/**
+ * Function: installDrillHandler
+ * 
+ * Installs listeners for dispatching the <root> event.
+ */
+mxEditor.prototype.installDrillHandler = function (graph)
+{				
+	var listener = mxUtils.bind(this, function(sender)
+	{
+		this.fireEvent(new mxEventObject(mxEvent.ROOT));
+	});
+	
+	graph.getView().addListener(mxEvent.DOWN, listener);
+	graph.getView().addListener(mxEvent.UP, listener);
+};
+
+/**
+ * Function: installChangeHandler
+ * 
+ * Installs the listeners required to automatically validate
+ * the graph. On each change of the root, this implementation
+ * fires a <root> event.
+ */
+mxEditor.prototype.installChangeHandler = function (graph)
+{
+	var listener = mxUtils.bind(this, function(sender, evt)
+	{
+		// Updates the modified state
+		this.setModified(true);
+
+		// Automatically validates the graph
+		// after each change
+		if (this.validating == true)
+		{
+			graph.validateGraph();
+		}
+
+		// Checks if the root has been changed
+		var changes = evt.getProperty('edit').changes;
+		
+		for (var i = 0; i < changes.length; i++)
+		{
+			var change = changes[i];
+			
+			if (change instanceof mxRootChange ||
+				(change instanceof mxValueChange &&
+				change.cell == this.graph.model.root) ||
+				(change instanceof mxCellAttributeChange &&
+				change.cell == this.graph.model.root))
+			{
+				this.fireEvent(new mxEventObject(mxEvent.ROOT));
+				break;
+			}
+		}
+	});
+	
+	graph.getModel().addListener(mxEvent.CHANGE, listener);
+};
+
+/**
+ * Function: installInsertHandler
+ * 
+ * Installs the handler for invoking <insertFunction> if
+ * one is defined.
+ */
+mxEditor.prototype.installInsertHandler = function (graph)
+{
+	var self = this; // closure
+	var insertHandler =
+	{
+		mouseDown: function(sender, me)
+		{
+			if (self.insertFunction != null &&
+				!me.isPopupTrigger() &&
+				(self.forcedInserting ||
+				me.getState() == null))
+			{
+				self.graph.clearSelection();
+				self.insertFunction(me.getEvent(), me.getCell());
+
+				// Consumes the rest of the events
+				// for this gesture (down, move, up)
+				this.isActive = true;
+				me.consume();
+			}
+		},
+		
+		mouseMove: function(sender, me)
+		{
+			if (this.isActive)
+			{
+				me.consume();
+			}
+		},
+		
+		mouseUp: function(sender, me)
+		{
+			if (this.isActive)
+			{
+				this.isActive = false;
+				me.consume();
+			}
+		}
+	};
+	
+	graph.addMouseListener(insertHandler);
+};
+
+/**
+ * Function: createDiagramLayout
+ * 
+ * Creates the layout instance used to layout the
+ * swimlanes in the diagram.
+ */
+mxEditor.prototype.createDiagramLayout = function ()
+{
+	var gs = this.graph.gridSize;
+	var layout = new mxStackLayout(this.graph, !this.horizontalFlow,
+		 this.swimlaneSpacing, 2*gs, 2*gs);
+	
+	// Overrides isIgnored to only take into account swimlanes
+	layout.isVertexIgnored = function(cell)
+	{
+		return !layout.graph.isSwimlane(cell);
+	};
+	
+	return layout;
+};
+
+/**
+ * Function: createSwimlaneLayout
+ * 
+ * Creates the layout instance used to layout the
+ * children of each swimlane.
+ */
+mxEditor.prototype.createSwimlaneLayout = function ()
+{
+	return new mxCompactTreeLayout(this.graph, this.horizontalFlow);
+};
+
+/**
+ * Function: createToolbar
+ * 
+ * Creates the <toolbar> with no container.
+ */
+mxEditor.prototype.createToolbar = function ()
+{
+	return new mxDefaultToolbar(null, this);
+};
+
+/**
+ * Function: setToolbarContainer
+ * 
+ * Initializes the toolbar for the given container.
+ */
+mxEditor.prototype.setToolbarContainer = function (container)
+{
+	this.toolbar.init(container);
+	
+	// Workaround for stylesheet directives in IE
+	if (mxClient.IS_QUIRKS)
+	{
+		new mxDivResizer(container);
+	}
+};
+
+/**
+ * Function: setStatusContainer
+ * 
+ * Creates the <status> using the specified container.
+ * 
+ * This implementation adds listeners in the editor to 
+ * display the last saved time and the current filename 
+ * in the status bar.
+ * 
+ * Parameters:
+ * 
+ * container - DOM node that will contain the statusbar.
+ */
+mxEditor.prototype.setStatusContainer = function (container)
+{
+	if (this.status == null)
+	{
+		this.status = container;
+		
+		// Prints the last saved time in the status bar
+		// when files are saved
+		this.addListener(mxEvent.SAVE, mxUtils.bind(this, function()
+		{
+			var tstamp = new Date().toLocaleString();
+			this.setStatus((mxResources.get(this.lastSavedResource) ||
+				this.lastSavedResource)+': '+tstamp);
+		}));
+		
+		// Updates the statusbar to display the filename
+		// when new files are opened
+		this.addListener(mxEvent.OPEN, mxUtils.bind(this, function()
+		{
+			this.setStatus((mxResources.get(this.currentFileResource) ||
+				this.currentFileResource)+': '+this.filename);
+		}));
+		
+		// Workaround for stylesheet directives in IE
+		if (mxClient.IS_QUIRKS)
+		{
+			new mxDivResizer(container);
+		}
+	}
+};
+
+/**
+ * Function: setStatus
+ * 
+ * Display the specified message in the status bar.
+ * 
+ * Parameters:
+ * 
+ * message - String the specified the message to
+ * be displayed.
+ */
+mxEditor.prototype.setStatus = function (message)
+{
+	if (this.status != null && message != null)
+	{
+		this.status.innerHTML = message;
+	}
+};
+
+/**
+ * Function: setTitleContainer
+ * 
+ * Creates a listener to update the inner HTML of the
+ * specified DOM node with the value of <getTitle>.
+ * 
+ * Parameters:
+ * 
+ * container - DOM node that will contain the title.
+ */
+mxEditor.prototype.setTitleContainer = function (container)
+{
+	this.addListener(mxEvent.ROOT, mxUtils.bind(this, function(sender)
+	{
+		container.innerHTML = this.getTitle();
+	}));
+
+	// Workaround for stylesheet directives in IE
+	if (mxClient.IS_QUIRKS)
+	{
+		new mxDivResizer(container);
+	}
+};
+
+/**
+ * Function: treeLayout
+ * 
+ * Executes a vertical or horizontal compact tree layout
+ * using the specified cell as an argument. The cell may
+ * either be a group or the root of a tree.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to use in the compact tree layout.
+ * horizontal - Optional boolean to specify the tree's
+ * orientation. Default is true.
+ */
+mxEditor.prototype.treeLayout = function (cell, horizontal)
+{
+	if (cell != null)
+	{
+		var layout = new mxCompactTreeLayout(this.graph, horizontal);
+		layout.execute(cell);
+	}
+};
+
+/**
+ * Function: getTitle
+ * 
+ * Returns the string value for the current root of the
+ * diagram.
+ */
+mxEditor.prototype.getTitle = function ()
+{
+	var title = '';
+	var graph = this.graph;
+	var cell = graph.getCurrentRoot();
+	
+	while (cell != null &&
+		   graph.getModel().getParent(
+				graph.getModel().getParent(cell)) != null)
+	{
+		// Append each label of a valid root
+		if (graph.isValidRoot(cell))
+		{
+			title = ' > ' +
+			graph.convertValueToString(cell) + title;
+		}
+		
+		cell = graph.getModel().getParent(cell);
+	}
+	
+	var prefix = this.getRootTitle();
+	
+	return prefix + title;
+};
+
+/**
+ * Function: getRootTitle
+ * 
+ * Returns the string value of the root cell in
+ * <mxGraph.model>.
+ */
+mxEditor.prototype.getRootTitle = function ()
+{
+	var root = this.graph.getModel().getRoot();
+	return this.graph.convertValueToString(root);
+};
+
+/**
+ * Function: undo
+ * 
+ * Undo the last change in <graph>.
+ */
+mxEditor.prototype.undo = function ()
+{
+	this.undoManager.undo();
+};
+
+/**
+ * Function: redo
+ * 
+ * Redo the last change in <graph>.
+ */
+mxEditor.prototype.redo = function ()
+{
+	this.undoManager.redo();
+};
+
+/**
+ * Function: groupCells
+ * 
+ * Invokes <createGroup> to create a new group cell and the invokes
+ * <mxGraph.groupCells>, using the grid size of the graph as the spacing
+ * in the group's content area.
+ */
+mxEditor.prototype.groupCells = function ()
+{
+	var border = (this.groupBorderSize != null) ?
+		this.groupBorderSize :
+		this.graph.gridSize;
+	return this.graph.groupCells(this.createGroup(), border);
+};
+
+/**
+ * Function: createGroup
+ * 
+ * Creates and returns a clone of <defaultGroup> to be used
+ * as a new group cell in <group>.
+ */
+mxEditor.prototype.createGroup = function ()
+{
+	var model = this.graph.getModel();
+	
+	return model.cloneCell(this.defaultGroup);
+};
+
+/**
+ * Function: open
+ * 
+ * Opens the specified file synchronously and parses it using
+ * <readGraphModel>. It updates <filename> and fires an <open>-event after
+ * the file has been opened. Exceptions should be handled as follows:
+ * 
+ * (code)
+ * try
+ * {
+ *   editor.open(filename);
+ * }
+ * catch (e)
+ * {
+ *   mxUtils.error('Cannot open ' + filename +
+ *     ': ' + e.message, 280, true);
+ * }
+ * (end)
+ *
+ * Parameters:
+ * 
+ * filename - URL of the file to be opened.
+ */
+mxEditor.prototype.open = function (filename)
+{
+	if (filename != null)
+	{
+		var xml = mxUtils.load(filename).getXml();
+		this.readGraphModel(xml.documentElement);
+		this.filename = filename;
+		
+		this.fireEvent(new mxEventObject(mxEvent.OPEN, 'filename', filename));
+	}
+};
+
+/**
+ * Function: readGraphModel
+ * 
+ * Reads the specified XML node into the existing graph model and resets
+ * the command history and modified state.
+ */
+mxEditor.prototype.readGraphModel = function (node)
+{
+	var dec = new mxCodec(node.ownerDocument);
+	dec.decode(node, this.graph.getModel());
+	this.resetHistory();
+};
+
+/**
+ * Function: save
+ * 
+ * Posts the string returned by <writeGraphModel> to the given URL or the
+ * URL returned by <getUrlPost>. The actual posting is carried out by
+ * <postDiagram>. If the URL is null then the resulting XML will be
+ * displayed using <mxUtils.popup>. Exceptions should be handled as
+ * follows:
+ * 
+ * (code)
+ * try
+ * {
+ *   editor.save();
+ * }
+ * catch (e)
+ * {
+ *   mxUtils.error('Cannot save : ' + e.message, 280, true);
+ * }
+ * (end)
+ */
+mxEditor.prototype.save = function (url, linefeed)
+{
+	// Gets the URL to post the data to
+	url = url || this.getUrlPost();
+
+	// Posts the data if the URL is not empty
+	if (url != null && url.length > 0)
+	{
+		var data = this.writeGraphModel(linefeed);
+		this.postDiagram(url, data);
+		
+		// Resets the modified flag
+		this.setModified(false);
+	}
+	
+	// Dispatches a save event
+	this.fireEvent(new mxEventObject(mxEvent.SAVE, 'url', url));
+};
+
+/**
+ * Function: postDiagram
+ * 
+ * Hook for subclassers to override the posting of a diagram
+ * represented by the given node to the given URL. This fires
+ * an asynchronous <post> event if the diagram has been posted.
+ * 
+ * Example:
+ * 
+ * To replace the diagram with the diagram in the response, use the
+ * following code.
+ * 
+ * (code)
+ * editor.addListener(mxEvent.POST, function(sender, evt)
+ * {
+ *   // Process response (replace diagram)
+ *   var req = evt.getProperty('request');
+ *   var root = req.getDocumentElement();
+ *   editor.graph.readGraphModel(root)
+ * });
+ * (end)
+ */
+mxEditor.prototype.postDiagram = function (url, data)
+{
+	if (this.escapePostData)
+	{
+		data = encodeURIComponent(data);
+	}
+
+	mxUtils.post(url, this.postParameterName+'='+data,
+		mxUtils.bind(this, function(req)
+		{
+			this.fireEvent(new mxEventObject(mxEvent.POST,
+				'request', req, 'url', url, 'data', data));
+		})
+	);
+};
+
+/**
+ * Function: writeGraphModel
+ * 
+ * Hook to create the string representation of the diagram. The default
+ * implementation uses an <mxCodec> to encode the graph model as
+ * follows:
+ * 
+ * (code)
+ * var enc = new mxCodec();
+ * var node = enc.encode(this.graph.getModel());
+ * return mxUtils.getXml(node, this.linefeed);
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * linefeed - Optional character to be used as the linefeed. Default is
+ * <linefeed>.
+ */
+mxEditor.prototype.writeGraphModel = function (linefeed)
+{
+	linefeed = (linefeed != null) ? linefeed : this.linefeed;
+	var enc = new mxCodec();
+	var node = enc.encode(this.graph.getModel());
+
+	return mxUtils.getXml(node, linefeed);
+};
+
+/**
+ * Function: getUrlPost
+ * 
+ * Returns the URL to post the diagram to. This is used
+ * in <save>. The default implementation returns <urlPost>,
+ * adding <code>?draft=true</code>.
+ */
+mxEditor.prototype.getUrlPost = function ()
+{
+	return this.urlPost;
+};
+
+/**
+ * Function: getUrlImage
+ * 
+ * Returns the URL to create the image with. This is typically
+ * the URL of a backend which accepts an XML representation
+ * of a graph view to create an image. The function is used
+ * in the image action to create an image. This implementation
+ * returns <urlImage>.
+ */
+mxEditor.prototype.getUrlImage = function ()
+{
+	return this.urlImage;
+};
+
+/**
+ * Function: swapStyles
+ * 
+ * Swaps the styles for the given names in the graph's
+ * stylesheet and refreshes the graph.
+ */
+mxEditor.prototype.swapStyles = function (first, second)
+{
+	var style = this.graph.getStylesheet().styles[second];
+	this.graph.getView().getStylesheet().putCellStyle(
+		second, this.graph.getStylesheet().styles[first]);
+	this.graph.getStylesheet().putCellStyle(first, style);
+	this.graph.refresh();
+};
+
+/**
+ * Function: showProperties
+ * 
+ * Creates and shows the properties dialog for the given
+ * cell. The content area of the dialog is created using
+ * <createProperties>.
+ */
+mxEditor.prototype.showProperties = function (cell)
+{
+	cell = cell || this.graph.getSelectionCell();
+	
+	// Uses the root node for the properties dialog
+	// if not cell was passed in and no cell is
+	// selected
+	if (cell == null)
+	{
+		cell = this.graph.getCurrentRoot();
+		
+		if (cell == null)
+		{
+			cell = this.graph.getModel().getRoot();
+		}
+	}
+	
+	if (cell != null)
+	{
+		// Makes sure there is no in-place editor in the
+		// graph and computes the location of the dialog
+		this.graph.stopEditing(true);
+
+		var offset = mxUtils.getOffset(this.graph.container);
+		var x = offset.x+10;
+		var y = offset.y;
+		
+		// Avoids moving the dialog if it is alredy open
+		if (this.properties != null && !this.movePropertiesDialog)
+		{
+			x = this.properties.getX();
+			y = this.properties.getY();
+		}
+		
+		// Places the dialog near the cell for which it
+		// displays the properties
+		else
+		{
+			var bounds = this.graph.getCellBounds(cell);
+			
+			if (bounds != null)
+			{
+				x += bounds.x+Math.min(200, bounds.width);
+				y += bounds.y;				
+			}			
+		}
+		
+		// Hides the existing properties dialog and creates a new one with the
+		// contents created in the hook method
+		this.hideProperties();
+		var node = this.createProperties(cell);
+		
+		if (node != null)
+		{
+			// Displays the contents in a window and stores a reference to the
+			// window for later hiding of the window
+			this.properties = new mxWindow(mxResources.get(this.propertiesResource) ||
+				this.propertiesResource, node, x, y, this.propertiesWidth, this.propertiesHeight, false);
+			this.properties.setVisible(true);
+		}
+	}
+};
+
+/**
+ * Function: isPropertiesVisible
+ * 
+ * Returns true if the properties dialog is currently visible.
+ */
+mxEditor.prototype.isPropertiesVisible = function ()
+{
+	return this.properties != null;
+};
+
+/**
+ * Function: createProperties
+ * 
+ * Creates and returns the DOM node that represents the contents
+ * of the properties dialog for the given cell. This implementation
+ * works for user objects that are XML nodes and display all the
+ * node attributes in a form.
+ */
+mxEditor.prototype.createProperties = function (cell)
+{
+	var model = this.graph.getModel();
+	var value = model.getValue(cell);
+	
+	if (mxUtils.isNode(value))
+	{
+		// Creates a form for the user object inside
+		// the cell
+		var form = new mxForm('properties');
+		
+		// Adds a readonly field for the cell id
+		var id = form.addText('ID', cell.getId());
+		id.setAttribute('readonly', 'true');
+
+		var geo = null;
+		var yField = null;
+		var xField = null;
+		var widthField = null;
+		var heightField = null;
+
+		// Adds fields for the location and size
+		if (model.isVertex(cell))
+		{
+			geo = model.getGeometry(cell);
+			
+			if (geo != null)
+			{
+				yField = form.addText('top', geo.y);
+				xField = form.addText('left', geo.x);
+				widthField = form.addText('width', geo.width);
+				heightField = form.addText('height', geo.height);
+			}
+		}
+		
+		// Adds a field for the cell style			
+		var tmp = model.getStyle(cell);
+		var style = form.addText('Style', tmp || '');
+		
+		// Creates textareas for each attribute of the
+		// user object within the cell
+		var attrs = value.attributes;
+		var texts = [];
+		
+		for (var i = 0; i < attrs.length; i++)
+		{
+			// Creates a textarea with more lines for
+			// the cell label
+			var val = attrs[i].value;
+			texts[i] = form.addTextarea(attrs[i].nodeName, val,
+				(attrs[i].nodeName == 'label') ? 4 : 2);
+		}
+		
+		// Adds an OK and Cancel button to the dialog
+		// contents and implements the respective
+		// actions below
+		
+		// Defines the function to be executed when the
+		// OK button is pressed in the dialog
+		var okFunction = mxUtils.bind(this, function()
+		{
+			// Hides the dialog
+			this.hideProperties();
+			
+			// Supports undo for the changes on the underlying
+			// XML structure / XML node attribute changes.
+			model.beginUpdate();
+			try
+			{
+				if (geo != null)
+				{
+					geo = geo.clone();
+					
+					geo.x = parseFloat(xField.value);
+					geo.y = parseFloat(yField.value);
+					geo.width = parseFloat(widthField.value);
+					geo.height = parseFloat(heightField.value);
+					
+					model.setGeometry(cell, geo);
+				}
+				
+				// Applies the style
+				if (style.value.length > 0)
+				{
+					model.setStyle(cell, style.value);
+				}
+				else
+				{
+					model.setStyle(cell, null);
+				}
+				
+				// Creates an undoable change for each
+				// attribute and executes it using the
+				// model, which will also make the change
+				// part of the current transaction
+				for (var i=0; i<attrs.length; i++)
+				{
+					var edit = new mxCellAttributeChange(
+						cell, attrs[i].nodeName,
+						texts[i].value);
+					model.execute(edit);
+				}
+				
+				// Checks if the graph wants cells to 
+				// be automatically sized and updates
+				// the size as an undoable step if
+				// the feature is enabled
+				if (this.graph.isAutoSizeCell(cell))
+				{
+					this.graph.updateCellSize(cell);
+				}
+			}
+			finally
+			{
+				model.endUpdate();
+			}
+		});
+		
+		// Defines the function to be executed when the
+		// Cancel button is pressed in the dialog
+		var cancelFunction = mxUtils.bind(this, function()
+		{
+			// Hides the dialog
+			this.hideProperties();
+		});
+		
+		form.addButtons(okFunction, cancelFunction);
+		
+		return form.table;
+	}
+
+	return null;
+};
+
+/**
+ * Function: hideProperties
+ * 
+ * Hides the properties dialog.
+ */
+mxEditor.prototype.hideProperties = function ()
+{
+	if (this.properties != null)
+	{
+		this.properties.destroy();
+		this.properties = null;
+	}
+};
+
+/**
+ * Function: showTasks
+ * 
+ * Shows the tasks window. The tasks window is created using <createTasks>. The
+ * default width of the window is 200 pixels, the y-coordinate of the location
+ * can be specifies in <tasksTop> and the x-coordinate is right aligned with a
+ * 20 pixel offset from the right border. To change the location of the tasks
+ * window, the following code can be used:
+ * 
+ * (code)
+ * var oldShowTasks = mxEditor.prototype.showTasks;
+ * mxEditor.prototype.showTasks = function()
+ * {
+ *   oldShowTasks.apply(this, arguments); // "supercall"
+ *   
+ *   if (this.tasks != null)
+ *   {
+ *     this.tasks.setLocation(10, 10);
+ *   }
+ * };
+ * (end)
+ */
+mxEditor.prototype.showTasks = function ()
+{
+	if (this.tasks == null)
+	{
+		var div = document.createElement('div');
+		div.style.padding = '4px';
+		div.style.paddingLeft = '20px';
+		var w = document.body.clientWidth;
+		var wnd = new mxWindow(
+			mxResources.get(this.tasksResource) ||
+			this.tasksResource,
+			div, w - 220, this.tasksTop, 200);
+		wnd.setClosable(true);
+		wnd.destroyOnClose = false;
+		
+		// Installs a function to update the contents
+		// of the tasks window on every change of the
+		// model, selection or root.
+		var funct = mxUtils.bind(this, function(sender)
+		{
+			mxEvent.release(div);
+			div.innerHTML = '';
+			this.createTasks(div);
+		});
+		
+		this.graph.getModel().addListener(mxEvent.CHANGE, funct);
+		this.graph.getSelectionModel().addListener(mxEvent.CHANGE, funct);
+		this.graph.addListener(mxEvent.ROOT, funct);
+		
+		// Assigns the icon to the tasks window
+		if (this.tasksWindowImage != null)
+		{
+			wnd.setImage(this.tasksWindowImage);
+		}
+		
+		this.tasks = wnd;
+		this.createTasks(div);
+	}
+	
+	this.tasks.setVisible(true);
+};
+		
+/**
+ * Function: refreshTasks
+ * 
+ * Updates the contents of the tasks window using <createTasks>.
+ */
+mxEditor.prototype.refreshTasks = function (div)
+{
+	if (this.tasks != null)
+	{
+		var div = this.tasks.content;
+		mxEvent.release(div);
+		div.innerHTML = '';
+		this.createTasks(div);
+	}
+};
+		
+/**
+ * Function: createTasks
+ * 
+ * Updates the contents of the given DOM node to
+ * display the tasks associated with the current
+ * editor state. This is invoked whenever there
+ * is a possible change of state in the editor.
+ * Default implementation is empty.
+ */
+mxEditor.prototype.createTasks = function (div)
+{
+	// override
+};
+	
+/**
+ * Function: showHelp
+ * 
+ * Shows the help window. If the help window does not exist
+ * then it is created using an iframe pointing to the resource
+ * for the <code>urlHelp</code> key or <urlHelp> if the resource
+ * is undefined.
+ */
+mxEditor.prototype.showHelp = function (tasks)
+{
+	if (this.help == null)
+	{
+		var frame = document.createElement('iframe');
+		frame.setAttribute('src', mxResources.get('urlHelp') || this.urlHelp);
+		frame.setAttribute('height', '100%');
+		frame.setAttribute('width', '100%');
+		frame.setAttribute('frameBorder', '0');
+		frame.style.backgroundColor = 'white';
+	
+		var w = document.body.clientWidth;
+		var h = (document.body.clientHeight || document.documentElement.clientHeight);
+		
+		var wnd = new mxWindow(mxResources.get(this.helpResource) || this.helpResource,
+			frame, (w-this.helpWidth)/2, (h-this.helpHeight)/3, this.helpWidth, this.helpHeight);
+		wnd.setMaximizable(true);
+		wnd.setClosable(true);
+		wnd.destroyOnClose = false;
+		wnd.setResizable(true);
+
+		// Assigns the icon to the help window
+		if (this.helpWindowImage != null)
+		{
+			wnd.setImage(this.helpWindowImage);
+		}
+		
+		// Workaround for ignored iframe height 100% in FF
+		if (mxClient.IS_NS)
+		{
+			var handler = function(sender)
+			{
+				var h = wnd.div.offsetHeight;
+				frame.setAttribute('height', (h-26)+'px');
+			};
+			
+			wnd.addListener(mxEvent.RESIZE_END, handler);
+			wnd.addListener(mxEvent.MAXIMIZE, handler);
+			wnd.addListener(mxEvent.NORMALIZE, handler);
+			wnd.addListener(mxEvent.SHOW, handler);
+		}
+		
+		this.help = wnd;
+	}
+	
+	this.help.setVisible(true);
+};
+
+/**
+ * Function: showOutline
+ * 
+ * Shows the outline window. If the window does not exist, then it is
+ * created using an <mxOutline>.
+ */
+mxEditor.prototype.showOutline = function ()
+{
+	var create = this.outline == null;
+	
+	if (create)
+	{
+		var div = document.createElement('div');
+		
+		div.style.overflow = 'hidden';
+		div.style.position = 'relative';
+		div.style.width = '100%';
+		div.style.height = '100%';
+		div.style.background = 'white';
+		div.style.cursor = 'move';
+		
+		if (document.documentMode == 8)
+		{
+			div.style.filter = 'progid:DXImageTransform.Microsoft.alpha(opacity=100)';
+		}
+		
+		var wnd = new mxWindow(
+			mxResources.get(this.outlineResource) ||
+			this.outlineResource,
+			div, 600, 480, 200, 200, false);
+				
+		// Creates the outline in the specified div
+		// and links it to the existing graph
+		var outline = new mxOutline(this.graph, div);			
+		wnd.setClosable(true);
+		wnd.setResizable(true);
+		wnd.destroyOnClose = false;
+		
+		wnd.addListener(mxEvent.RESIZE_END, function()
+		{
+			outline.update();
+		});
+		
+		this.outline = wnd;
+		this.outline.outline = outline;
+	}
+	
+	// Finally shows the outline
+	this.outline.setVisible(true);
+	this.outline.outline.update(true);
+};
+		
+/**
+ * Function: setMode
+ *
+ * Puts the graph into the specified mode. The following modenames are
+ * supported:
+ * 
+ * select - Selects using the left mouse button, new connections
+ * are disabled.
+ * connect - Selects using the left mouse button or creates new
+ * connections if mouse over cell hotspot. See <mxConnectionHandler>.
+ * pan - Pans using the left mouse button, new connections are disabled.
+ */
+mxEditor.prototype.setMode = function(modename)
+{
+	if (modename == 'select')
+	{
+		this.graph.panningHandler.useLeftButtonForPanning = false;
+		this.graph.setConnectable(false);
+	}
+	else if (modename == 'connect')
+	{
+		this.graph.panningHandler.useLeftButtonForPanning = false;
+		this.graph.setConnectable(true);
+	}
+	else if (modename == 'pan')
+	{
+		this.graph.panningHandler.useLeftButtonForPanning = true;
+		this.graph.setConnectable(false);
+	}
+};
+
+/**
+ * Function: createPopupMenu
+ * 
+ * Uses <popupHandler> to create the menu in the graph's
+ * panning handler. The redirection is setup in
+ * <setToolbarContainer>.
+ */
+mxEditor.prototype.createPopupMenu = function (menu, cell, evt)
+{
+	this.popupHandler.createMenu(this, menu, cell, evt);
+};
+
+/**
+ * Function: createEdge
+ * 
+ * Uses <defaultEdge> as the prototype for creating new edges
+ * in the connection handler of the graph. The style of the
+ * edge will be overridden with the value returned by
+ * <getEdgeStyle>.
+ */
+mxEditor.prototype.createEdge = function (source, target)
+{
+	// Clones the defaultedge prototype
+	var e = null;
+	
+	if (this.defaultEdge != null)
+	{
+		var model = this.graph.getModel();
+		e = model.cloneCell(this.defaultEdge);
+	}
+	else
+	{
+		e = new mxCell('');
+		e.setEdge(true);
+		
+		var geo = new mxGeometry();
+		geo.relative = true;
+		e.setGeometry(geo);
+	}
+	
+	// Overrides the edge style
+	var style = this.getEdgeStyle();
+	
+	if (style != null)
+	{
+		e.setStyle(style);
+	}
+	
+	return e;
+};
+
+/**
+ * Function: getEdgeStyle
+ * 
+ * Returns a string identifying the style of new edges.
+ * The function is used in <createEdge> when new edges
+ * are created in the graph.
+ */
+mxEditor.prototype.getEdgeStyle = function ()
+{
+	return this.defaultEdgeStyle;
+};
+
+/**
+ * Function: consumeCycleAttribute
+ * 
+ * Returns the next attribute in <cycleAttributeValues>
+ * or null, if not attribute should be used in the
+ * specified cell.
+ */
+mxEditor.prototype.consumeCycleAttribute = function (cell)
+{
+	return (this.cycleAttributeValues != null &&
+		this.cycleAttributeValues.length > 0 &&
+		this.graph.isSwimlane(cell)) ?
+		this.cycleAttributeValues[this.cycleAttributeIndex++ %
+			this.cycleAttributeValues.length] : null;
+};
+
+/**
+ * Function: cycleAttribute
+ * 
+ * Uses the returned value from <consumeCycleAttribute>
+ * as the value for the <cycleAttributeName> key in
+ * the given cell's style.
+ */
+mxEditor.prototype.cycleAttribute = function (cell)
+{
+	if (this.cycleAttributeName != null)
+	{
+		var value = this.consumeCycleAttribute(cell);
+		
+		if (value != null)
+		{
+			cell.setStyle(cell.getStyle()+';'+
+				this.cycleAttributeName+'='+value);
+		}
+	}
+};
+
+/**
+ * Function: addVertex
+ * 
+ * Adds the given vertex as a child of parent at the specified
+ * x and y coordinate and fires an <addVertex> event.
+ */
+mxEditor.prototype.addVertex = function (parent, vertex, x, y)
+{
+	var model = this.graph.getModel();
+	
+	while (parent != null && !this.graph.isValidDropTarget(parent))
+	{
+		parent = model.getParent(parent);
+	}
+	
+	parent = (parent != null) ? parent : this.graph.getSwimlaneAt(x, y);
+	var scale = this.graph.getView().scale;
+	
+	var geo = model.getGeometry(vertex);
+	var pgeo = model.getGeometry(parent);
+	
+	if (this.graph.isSwimlane(vertex) &&
+		!this.graph.swimlaneNesting)
+	{
+		parent = null;
+	}
+	else if (parent == null && this.swimlaneRequired)
+	{
+		return null;
+	}
+	else if (parent != null && pgeo != null)
+	{
+		// Keeps vertex inside parent
+		var state = this.graph.getView().getState(parent);
+		
+		if (state != null)
+		{			
+			x -= state.origin.x * scale;
+			y -= state.origin.y * scale;
+			
+			if (this.graph.isConstrainedMoving)
+			{
+				var width = geo.width;
+				var height = geo.height;				
+				var tmp = state.x+state.width;
+				
+				if (x+width > tmp)
+				{
+					x -= x+width - tmp;
+				}
+				
+				tmp = state.y+state.height;
+				
+				if (y+height > tmp)
+				{
+					y -= y+height - tmp;
+				}
+			}
+		}
+		else if (pgeo != null)
+		{
+			x -= pgeo.x*scale;
+			y -= pgeo.y*scale;
+		}
+	}
+	
+	geo = geo.clone();
+	geo.x = this.graph.snap(x / scale -
+		this.graph.getView().translate.x -
+		this.graph.gridSize/2);
+	geo.y = this.graph.snap(y / scale -
+		this.graph.getView().translate.y -
+		this.graph.gridSize/2);
+	vertex.setGeometry(geo);
+	
+	if (parent == null)
+	{
+		parent = this.graph.getDefaultParent();
+	}
+
+	this.cycleAttribute(vertex);
+	this.fireEvent(new mxEventObject(mxEvent.BEFORE_ADD_VERTEX,
+			'vertex', vertex, 'parent', parent));
+
+	model.beginUpdate();
+	try
+	{
+		vertex = this.graph.addCell(vertex, parent);
+		
+		if (vertex != null)
+		{
+			this.graph.constrainChild(vertex);
+			
+			this.fireEvent(new mxEventObject(mxEvent.ADD_VERTEX, 'vertex', vertex));
+		}
+	}
+	finally
+	{
+		model.endUpdate();
+	}
+	
+	if (vertex != null)
+	{
+		this.graph.setSelectionCell(vertex);
+		this.graph.scrollCellToVisible(vertex);
+		this.fireEvent(new mxEventObject(mxEvent.AFTER_ADD_VERTEX, 'vertex', vertex));
+	}
+	
+	return vertex;
+};
+
+/**
+ * Function: destroy
+ * 
+ * Removes the editor and all its associated resources. This does not
+ * normally need to be called, it is called automatically when the window
+ * unloads.
+ */
+mxEditor.prototype.destroy = function ()
+{
+	if (!this.destroyed)
+	{
+		this.destroyed = true;
+
+		if (this.tasks != null)
+		{
+			this.tasks.destroy();
+		}
+		
+		if (this.outline != null)
+		{
+			this.outline.destroy();
+		}
+		
+		if (this.properties != null)
+		{
+			this.properties.destroy();
+		}
+		
+		if (this.keyHandler != null)
+		{
+			this.keyHandler.destroy();
+		}
+		
+		if (this.rubberband != null)
+		{
+			this.rubberband.destroy();
+		}
+		
+		if (this.toolbar != null)
+		{
+			this.toolbar.destroy();
+		}
+		
+		if (this.graph != null)
+		{
+			this.graph.destroy();
+		}
+	
+		this.status = null;
+		this.templates = null;
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/handler/mxCellHighlight.js b/airavata-kubernetes/workflow-composer/src/js/handler/mxCellHighlight.js
new file mode 100644
index 0000000..937386a
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/handler/mxCellHighlight.js
@@ -0,0 +1,314 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxCellHighlight
+ * 
+ * A helper class to highlight cells. Here is an example for a given cell.
+ * 
+ * (code)
+ * var highlight = new mxCellHighlight(graph, '#ff0000', 2);
+ * highlight.highlight(graph.view.getState(cell)));
+ * (end)
+ * 
+ * Constructor: mxCellHighlight
+ * 
+ * Constructs a cell highlight.
+ */
+function mxCellHighlight(graph, highlightColor, strokeWidth, dashed)
+{
+	if (graph != null)
+	{
+		this.graph = graph;
+		this.highlightColor = (highlightColor != null) ? highlightColor : mxConstants.DEFAULT_VALID_COLOR;
+		this.strokeWidth = (strokeWidth != null) ? strokeWidth : mxConstants.HIGHLIGHT_STROKEWIDTH;
+		this.dashed = (dashed != null) ? dashed : false;
+		this.opacity = mxConstants.HIGHLIGHT_OPACITY;
+
+		// Updates the marker if the graph changes
+		this.repaintHandler = mxUtils.bind(this, function()
+		{
+			// Updates reference to state
+			if (this.state != null)
+			{
+				var tmp = this.graph.view.getState(this.state.cell);
+				
+				if (tmp == null)
+				{
+					this.hide();
+				}
+				else
+				{
+					this.state = tmp;
+					this.repaint();
+				}
+			}
+		});
+
+		this.graph.getView().addListener(mxEvent.SCALE, this.repaintHandler);
+		this.graph.getView().addListener(mxEvent.TRANSLATE, this.repaintHandler);
+		this.graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE, this.repaintHandler);
+		this.graph.getModel().addListener(mxEvent.CHANGE, this.repaintHandler);
+		
+		// Hides the marker if the current root changes
+		this.resetHandler = mxUtils.bind(this, function()
+		{
+			this.hide();
+		});
+
+		this.graph.getView().addListener(mxEvent.DOWN, this.resetHandler);
+		this.graph.getView().addListener(mxEvent.UP, this.resetHandler);
+	}
+};
+
+/**
+ * Variable: keepOnTop
+ * 
+ * Specifies if the highlights should appear on top of everything
+ * else in the overlay pane. Default is false.
+ */
+mxCellHighlight.prototype.keepOnTop = false;
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxCellHighlight.prototype.graph = true;
+
+/**
+ * Variable: state
+ * 
+ * Reference to the <mxCellState>.
+ */
+mxCellHighlight.prototype.state = null;
+
+/**
+ * Variable: spacing
+ * 
+ * Specifies the spacing between the highlight for vertices and the vertex.
+ * Default is 2.
+ */
+mxCellHighlight.prototype.spacing = 2;
+
+/**
+ * Variable: resetHandler
+ * 
+ * Holds the handler that automatically invokes reset if the highlight
+ * should be hidden.
+ */
+mxCellHighlight.prototype.resetHandler = null;
+
+/**
+ * Function: setHighlightColor
+ * 
+ * Sets the color of the rectangle used to highlight drop targets.
+ * 
+ * Parameters:
+ * 
+ * color - String that represents the new highlight color.
+ */
+mxCellHighlight.prototype.setHighlightColor = function(color)
+{
+	this.highlightColor = color;
+	
+	if (this.shape != null)
+	{
+		this.shape.stroke = color;
+	}
+};
+
+/**
+ * Function: drawHighlight
+ * 
+ * Creates and returns the highlight shape for the given state.
+ */
+mxCellHighlight.prototype.drawHighlight = function()
+{
+	this.shape = this.createShape();
+	this.repaint();
+
+	if (!this.keepOnTop && this.shape.node.parentNode.firstChild != this.shape.node)
+	{
+		this.shape.node.parentNode.insertBefore(this.shape.node, this.shape.node.parentNode.firstChild);
+	}
+};
+
+/**
+ * Function: createShape
+ * 
+ * Creates and returns the highlight shape for the given state.
+ */
+mxCellHighlight.prototype.createShape = function()
+{
+	var shape = this.graph.cellRenderer.createShape(this.state);
+	
+	shape.svgStrokeTolerance = this.graph.tolerance;
+	shape.points = this.state.absolutePoints;
+	shape.apply(this.state);
+	shape.stroke = this.highlightColor;
+	shape.opacity = this.opacity;
+	shape.isDashed = this.dashed;
+	shape.isShadow = false;
+	
+	shape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+	shape.init(this.graph.getView().getOverlayPane());
+	mxEvent.redirectMouseEvents(shape.node, this.graph, this.state);
+	
+	if (this.graph.dialect != mxConstants.DIALECT_SVG)
+	{
+		shape.pointerEvents = false;
+	}
+	else
+	{
+		shape.svgPointerEvents = 'stroke';
+	}
+	
+	return shape;
+};
+
+/**
+ * Function: repaint
+ * 
+ * Updates the highlight after a change of the model or view.
+ */
+mxCellHighlight.prototype.getStrokeWidth = function(state)
+{
+	return this.strokeWidth;
+};
+
+/**
+ * Function: repaint
+ * 
+ * Updates the highlight after a change of the model or view.
+ */
+mxCellHighlight.prototype.repaint = function()
+{
+	if (this.state != null && this.shape != null)
+	{
+		this.shape.scale = this.state.view.scale;
+		
+		if (this.graph.model.isEdge(this.state.cell))
+		{
+			this.shape.strokewidth = this.getStrokeWidth();
+			this.shape.points = this.state.absolutePoints;
+			this.shape.outline = false;
+		}
+		else
+		{
+			this.shape.bounds = new mxRectangle(this.state.x - this.spacing, this.state.y - this.spacing,
+					this.state.width + 2 * this.spacing, this.state.height + 2 * this.spacing);
+			this.shape.rotation = Number(this.state.style[mxConstants.STYLE_ROTATION] || '0');
+			this.shape.strokewidth = this.getStrokeWidth() / this.state.view.scale;
+			this.shape.outline = true;
+		}
+
+		// Uses cursor from shape in highlight
+		if (this.state.shape != null)
+		{
+			this.shape.setCursor(this.state.shape.getCursor());
+		}
+		
+		// Workaround for event transparency in VML with transparent color
+		// is to use a non-transparent color with near zero opacity
+		if (mxClient.IS_QUIRKS || document.documentMode == 8)
+		{
+			if (this.shape.stroke == 'transparent')
+			{
+				// KNOWN: Quirks mode does not seem to catch events if
+				// we do not force an update of the DOM via a change such
+				// as mxLog.debug. Since IE6 is EOL we do not add a fix.
+				this.shape.stroke = 'white';
+				this.shape.opacity = 1;
+			}
+			else
+			{
+				this.shape.opacity = this.opacity;
+			}
+		}
+		
+		this.shape.redraw();
+	}
+};
+
+/**
+ * Function: hide
+ * 
+ * Resets the state of the cell marker.
+ */
+mxCellHighlight.prototype.hide = function()
+{
+	this.highlight(null);
+};
+
+/**
+ * Function: mark
+ * 
+ * Marks the <markedState> and fires a <mark> event.
+ */
+mxCellHighlight.prototype.highlight = function(state)
+{
+	if (this.state != state)
+	{
+		if (this.shape != null)
+		{
+			this.shape.destroy();
+			this.shape = null;
+		}
+
+		this.state = state;
+		
+		if (this.state != null)
+		{
+			this.drawHighlight();
+		}
+	}
+};
+
+/**
+ * Function: isHighlightAt
+ * 
+ * Returns true if this highlight is at the given position.
+ */
+mxCellHighlight.prototype.isHighlightAt = function(x, y)
+{
+	var hit = false;
+	
+	// Quirks mode is currently not supported as it used a different coordinate system
+	if (this.shape != null && document.elementFromPoint != null && !mxClient.IS_QUIRKS)
+	{
+		var elt = document.elementFromPoint(x, y);
+
+		while (elt != null)
+		{
+			if (elt == this.shape.node)
+			{
+				hit = true;
+				break;
+			}
+			
+			elt = elt.parentNode;
+		}
+	}
+	
+	return hit;
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the handler and all its resources and DOM nodes.
+ */
+mxCellHighlight.prototype.destroy = function()
+{
+	this.graph.getView().removeListener(this.resetHandler);
+	this.graph.getView().removeListener(this.repaintHandler);
+	this.graph.getModel().removeListener(this.repaintHandler);
+	
+	if (this.shape != null)
+	{
+		this.shape.destroy();
+		this.shape = null;
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/handler/mxCellMarker.js b/airavata-kubernetes/workflow-composer/src/js/handler/mxCellMarker.js
new file mode 100644
index 0000000..10037c8
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/handler/mxCellMarker.js
@@ -0,0 +1,430 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxCellMarker
+ * 
+ * A helper class to process mouse locations and highlight cells.
+ * 
+ * Helper class to highlight cells. To add a cell marker to an existing graph
+ * for highlighting all cells, the following code is used:
+ * 
+ * (code)
+ * var marker = new mxCellMarker(graph);
+ * graph.addMouseListener({
+ *   mouseDown: function() {},
+ *   mouseMove: function(sender, me)
+ *   {
+ *     marker.process(me);
+ *   },
+ *   mouseUp: function() {}
+ * });
+ * (end)
+ *
+ * Event: mxEvent.MARK
+ * 
+ * Fires after a cell has been marked or unmarked. The <code>state</code>
+ * property contains the marked <mxCellState> or null if no state is marked.
+ * 
+ * Constructor: mxCellMarker
+ * 
+ * Constructs a new cell marker.
+ * 
+ * Parameters:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ * validColor - Optional marker color for valid states. Default is
+ * <mxConstants.DEFAULT_VALID_COLOR>.
+ * invalidColor - Optional marker color for invalid states. Default is
+ * <mxConstants.DEFAULT_INVALID_COLOR>.
+ * hotspot - Portion of the width and hight where a state intersects a
+ * given coordinate pair. A value of 0 means always highlight. Default is
+ * <mxConstants.DEFAULT_HOTSPOT>.
+ */
+function mxCellMarker(graph, validColor, invalidColor, hotspot)
+{
+	mxEventSource.call(this);
+	
+	if (graph != null)
+	{
+		this.graph = graph;
+		this.validColor = (validColor != null) ? validColor : mxConstants.DEFAULT_VALID_COLOR;
+		this.invalidColor = (validColor != null) ? invalidColor : mxConstants.DEFAULT_INVALID_COLOR;
+		this.hotspot = (hotspot != null) ? hotspot : mxConstants.DEFAULT_HOTSPOT;
+		
+		this.highlight = new mxCellHighlight(graph);
+	}
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxUtils.extend(mxCellMarker, mxEventSource);
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxCellMarker.prototype.graph = null;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies if the marker is enabled. Default is true.
+ */
+mxCellMarker.prototype.enabled = true;
+
+/**
+ * Variable: hotspot
+ * 
+ * Specifies the portion of the width and height that should trigger
+ * a highlight. The area around the center of the cell to be marked is used
+ * as the hotspot. Possible values are between 0 and 1. Default is
+ * mxConstants.DEFAULT_HOTSPOT.
+ */
+mxCellMarker.prototype.hotspot = mxConstants.DEFAULT_HOTSPOT; 
+
+/**
+ * Variable: hotspotEnabled
+ * 
+ * Specifies if the hotspot is enabled. Default is false.
+ */
+mxCellMarker.prototype.hotspotEnabled = false;
+
+/**
+ * Variable: validColor
+ * 
+ * Holds the valid marker color.
+ */
+mxCellMarker.prototype.validColor = null;
+
+/**
+ * Variable: invalidColor
+ * 
+ * Holds the invalid marker color.
+ */
+mxCellMarker.prototype.invalidColor = null;
+
+/**
+ * Variable: currentColor
+ * 
+ * Holds the current marker color.
+ */
+mxCellMarker.prototype.currentColor = null;
+
+/**
+ * Variable: validState
+ * 
+ * Holds the marked <mxCellState> if it is valid.
+ */
+mxCellMarker.prototype.validState = null; 
+
+/**
+ * Variable: markedState
+ * 
+ * Holds the marked <mxCellState>.
+ */
+mxCellMarker.prototype.markedState = null;
+
+/**
+ * Function: setEnabled
+ * 
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ * 
+ * Parameters:
+ * 
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxCellMarker.prototype.setEnabled = function(enabled)
+{
+	this.enabled = enabled;
+};
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxCellMarker.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+
+/**
+ * Function: setHotspot
+ * 
+ * Sets the <hotspot>.
+ */
+mxCellMarker.prototype.setHotspot = function(hotspot)
+{
+	this.hotspot = hotspot;
+};
+
+/**
+ * Function: getHotspot
+ * 
+ * Returns the <hotspot>.
+ */
+mxCellMarker.prototype.getHotspot = function()
+{
+	return this.hotspot;
+};
+
+/**
+ * Function: setHotspotEnabled
+ * 
+ * Specifies whether the hotspot should be used in <intersects>.
+ */
+mxCellMarker.prototype.setHotspotEnabled = function(enabled)
+{
+	this.hotspotEnabled = enabled;
+};
+
+/**
+ * Function: isHotspotEnabled
+ * 
+ * Returns true if hotspot is used in <intersects>.
+ */
+mxCellMarker.prototype.isHotspotEnabled = function()
+{
+	return this.hotspotEnabled;
+};
+
+/**
+ * Function: hasValidState
+ * 
+ * Returns true if <validState> is not null.
+ */
+mxCellMarker.prototype.hasValidState = function()
+{
+	return this.validState != null;
+};
+
+/**
+ * Function: getValidState
+ * 
+ * Returns the <validState>.
+ */
+mxCellMarker.prototype.getValidState = function()
+{
+	return this.validState;
+};
+
+/**
+ * Function: getMarkedState
+ * 
+ * Returns the <markedState>.
+ */
+mxCellMarker.prototype.getMarkedState = function()
+{
+	return this.markedState;
+};
+
+/**
+ * Function: reset
+ * 
+ * Resets the state of the cell marker.
+ */
+mxCellMarker.prototype.reset = function()
+{
+	this.validState = null;
+	
+	if (this.markedState != null)
+	{
+		this.markedState = null;
+		this.unmark();
+	}
+};
+
+/**
+ * Function: process
+ * 
+ * Processes the given event and cell and marks the state returned by
+ * <getState> with the color returned by <getMarkerColor>. If the
+ * markerColor is not null, then the state is stored in <markedState>. If
+ * <isValidState> returns true, then the state is stored in <validState>
+ * regardless of the marker color. The state is returned regardless of the
+ * marker color and valid state. 
+ */
+mxCellMarker.prototype.process = function(me)
+{
+	var state = null;
+	
+	if (this.isEnabled())
+	{
+		state = this.getState(me);
+		this.setCurrentState(state, me);
+	}
+	
+	return state;
+};
+
+/**
+ * Function: setCurrentState
+ * 
+ * Sets and marks the current valid state.
+ */
+mxCellMarker.prototype.setCurrentState = function(state, me, color)
+{
+	var isValid = (state != null) ? this.isValidState(state) : false;
+	color = (color != null) ? color : this.getMarkerColor(me.getEvent(), state, isValid);
+	
+	if (isValid)
+	{
+		this.validState = state;
+	}
+	else
+	{
+		this.validState = null;
+	}
+	
+	if (state != this.markedState || color != this.currentColor)
+	{
+		this.currentColor = color;
+		
+		if (state != null && this.currentColor != null)
+		{
+			this.markedState = state;
+			this.mark();		
+		}
+		else if (this.markedState != null)
+		{
+			this.markedState = null;
+			this.unmark();
+		}
+	}
+};
+
+/**
+ * Function: markCell
+ * 
+ * Marks the given cell using the given color, or <validColor> if no color is specified.
+ */
+mxCellMarker.prototype.markCell = function(cell, color)
+{
+	var state = this.graph.getView().getState(cell);
+	
+	if (state != null)
+	{
+		this.currentColor = (color != null) ? color : this.validColor;
+		this.markedState = state;
+		this.mark();
+	}
+};
+
+/**
+ * Function: mark
+ * 
+ * Marks the <markedState> and fires a <mark> event.
+ */
+mxCellMarker.prototype.mark = function()
+{
+	this.highlight.setHighlightColor(this.currentColor);
+	this.highlight.highlight(this.markedState);
+	this.fireEvent(new mxEventObject(mxEvent.MARK, 'state', this.markedState));
+};
+
+/**
+ * Function: unmark
+ * 
+ * Hides the marker and fires a <mark> event.
+ */
+mxCellMarker.prototype.unmark = function()
+{
+	this.mark();
+};
+
+/**
+ * Function: isValidState
+ * 
+ * Returns true if the given <mxCellState> is a valid state. If this
+ * returns true, then the state is stored in <validState>. The return value
+ * of this method is used as the argument for <getMarkerColor>.
+ */
+mxCellMarker.prototype.isValidState = function(state)
+{
+	return true;
+};
+
+/**
+ * Function: getMarkerColor
+ * 
+ * Returns the valid- or invalidColor depending on the value of isValid.
+ * The given <mxCellState> is ignored by this implementation.
+ */
+mxCellMarker.prototype.getMarkerColor = function(evt, state, isValid)
+{
+	return (isValid) ? this.validColor : this.invalidColor;
+};
+
+/**
+ * Function: getState
+ * 
+ * Uses <getCell>, <getStateToMark> and <intersects> to return the
+ * <mxCellState> for the given <mxMouseEvent>.
+ */
+mxCellMarker.prototype.getState = function(me)
+{
+	var view = this.graph.getView();
+	var cell = this.getCell(me);
+	var state = this.getStateToMark(view.getState(cell));
+
+	return (state != null && this.intersects(state, me)) ? state : null;
+};
+
+/**
+ * Function: getCell
+ * 
+ * Returns the <mxCell> for the given event and cell. This returns the
+ * given cell.
+ */
+mxCellMarker.prototype.getCell = function(me)
+{
+	return me.getCell();
+};
+
+/**
+ * Function: getStateToMark
+ * 
+ * Returns the <mxCellState> to be marked for the given <mxCellState> under
+ * the mouse. This returns the given state.
+ */
+mxCellMarker.prototype.getStateToMark = function(state)
+{
+	return state;
+};
+
+/**
+ * Function: intersects
+ * 
+ * Returns true if the given coordinate pair intersects the given state.
+ * This returns true if the <hotspot> is 0 or the coordinates are inside
+ * the hotspot for the given cell state.
+ */
+mxCellMarker.prototype.intersects = function(state, me)
+{
+	if (this.hotspotEnabled)
+	{
+		return mxUtils.intersectsHotspot(state, me.getGraphX(), me.getGraphY(),
+			this.hotspot, mxConstants.MIN_HOTSPOT_SIZE,
+			mxConstants.MAX_HOTSPOT_SIZE);
+	}
+	
+	return true;
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the handler and all its resources and DOM nodes.
+ */
+mxCellMarker.prototype.destroy = function()
+{
+	this.graph.getView().removeListener(this.resetHandler);
+	this.graph.getModel().removeListener(this.resetHandler);
+	this.highlight.destroy();
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/handler/mxCellTracker.js b/airavata-kubernetes/workflow-composer/src/js/handler/mxCellTracker.js
new file mode 100644
index 0000000..9f0c8bb
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/handler/mxCellTracker.js
@@ -0,0 +1,145 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxCellTracker
+ * 
+ * Event handler that highlights cells. Inherits from <mxCellMarker>.
+ * 
+ * Example:
+ * 
+ * (code)
+ * new mxCellTracker(graph, '#00FF00');
+ * (end)
+ * 
+ * For detecting dragEnter, dragOver and dragLeave on cells, the following
+ * code can be used:
+ * 
+ * (code)
+ * graph.addMouseListener(
+ * {
+ *   cell: null,
+ *   mouseDown: function(sender, me) { },
+ *   mouseMove: function(sender, me)
+ *   {
+ *     var tmp = me.getCell();
+ *     
+ *     if (tmp != this.cell)
+ *     {
+ *       if (this.cell != null)
+ *       {
+ *         this.dragLeave(me.getEvent(), this.cell);
+ *       }
+ *       
+ *       this.cell = tmp;
+ *       
+ *       if (this.cell != null)
+ *       {
+ *         this.dragEnter(me.getEvent(), this.cell);
+ *       }
+ *     }
+ *     
+ *     if (this.cell != null)
+ *     {
+ *       this.dragOver(me.getEvent(), this.cell);
+ *     }
+ *   },
+ *   mouseUp: function(sender, me) { },
+ *   dragEnter: function(evt, cell)
+ *   {
+ *     mxLog.debug('dragEnter', cell.value);
+ *   },
+ *   dragOver: function(evt, cell)
+ *   {
+ *     mxLog.debug('dragOver', cell.value);
+ *   },
+ *   dragLeave: function(evt, cell)
+ *   {
+ *     mxLog.debug('dragLeave', cell.value);
+ *   }
+ * });
+ * (end)
+ * 
+ * Constructor: mxCellTracker
+ * 
+ * Constructs an event handler that highlights cells.
+ * 
+ * Parameters:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ * color - Color of the highlight. Default is blue.
+ * funct - Optional JavaScript function that is used to override
+ * <mxCellMarker.getCell>.
+ */
+function mxCellTracker(graph, color, funct)
+{
+	mxCellMarker.call(this, graph, color);
+
+	this.graph.addMouseListener(this);
+	
+	if (funct != null)
+	{
+		this.getCell = funct;
+	}
+	
+	// Automatic deallocation of memory
+	if (mxClient.IS_IE)
+	{
+		mxEvent.addListener(window, 'unload', mxUtils.bind(this, function()
+		{
+			this.destroy();
+		}));
+	}
+};
+
+/**
+ * Extends mxCellMarker.
+ */
+mxUtils.extend(mxCellTracker, mxCellMarker);
+
+/**
+ * Function: mouseDown
+ * 
+ * Ignores the event. The event is not consumed.
+ */
+mxCellTracker.prototype.mouseDown = function(sender, me) { };
+
+/**
+ * Function: mouseMove
+ * 
+ * Handles the event by highlighting the cell under the mousepointer if it
+ * is over the hotspot region of the cell.
+ */
+mxCellTracker.prototype.mouseMove = function(sender, me)
+{
+	if (this.isEnabled())
+	{
+		this.process(me);
+	}
+};
+
+/**
+ * Function: mouseUp
+ * 
+ * Handles the event by reseting the highlight.
+ */
+mxCellTracker.prototype.mouseUp = function(sender, me) { };
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the object and all its resources and DOM nodes. This doesn't
+ * normally need to be called. It is called automatically when the window
+ * unloads.
+ */
+mxCellTracker.prototype.destroy = function()
+{
+	if (!this.destroyed)
+	{
+		this.destroyed = true;
+
+		this.graph.removeMouseListener(this);
+		mxCellMarker.prototype.destroy.apply(this);
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/handler/mxConnectionHandler.js b/airavata-kubernetes/workflow-composer/src/js/handler/mxConnectionHandler.js
new file mode 100644
index 0000000..2eade4d
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/handler/mxConnectionHandler.js
@@ -0,0 +1,2204 @@
+/**
+ * Copyright (c) 2006-2016, JGraph Ltd
+ * Copyright (c) 2006-2016, Gaudenz Alder
+ */
+/**
+ * Class: mxConnectionHandler
+ *
+ * Graph event handler that creates new connections. Uses <mxTerminalMarker>
+ * for finding and highlighting the source and target vertices and
+ * <factoryMethod> to create the edge instance. This handler is built-into
+ * <mxGraph.connectionHandler> and enabled using <mxGraph.setConnectable>.
+ *
+ * Example:
+ * 
+ * (code)
+ * new mxConnectionHandler(graph, function(source, target, style)
+ * {
+ *   edge = new mxCell('', new mxGeometry());
+ *   edge.setEdge(true);
+ *   edge.setStyle(style);
+ *   edge.geometry.relative = true;
+ *   return edge;
+ * });
+ * (end)
+ * 
+ * Here is an alternative solution that just sets a specific user object for
+ * new edges by overriding <insertEdge>.
+ *
+ * (code)
+ * mxConnectionHandlerInsertEdge = mxConnectionHandler.prototype.insertEdge;
+ * mxConnectionHandler.prototype.insertEdge = function(parent, id, value, source, target, style)
+ * {
+ *   value = 'Test';
+ * 
+ *   return mxConnectionHandlerInsertEdge.apply(this, arguments);
+ * };
+ * (end)
+ * 
+ * Using images to trigger connections:
+ * 
+ * This handler uses mxTerminalMarker to find the source and target cell for
+ * the new connection and creates a new edge using <connect>. The new edge is
+ * created using <createEdge> which in turn uses <factoryMethod> or creates a
+ * new default edge.
+ * 
+ * The handler uses a "highlight-paradigm" for indicating if a cell is being
+ * used as a source or target terminal, as seen in other diagramming products.
+ * In order to allow both, moving and connecting cells at the same time,
+ * <mxConstants.DEFAULT_HOTSPOT> is used in the handler to determine the hotspot
+ * of a cell, that is, the region of the cell which is used to trigger a new
+ * connection. The constant is a value between 0 and 1 that specifies the
+ * amount of the width and height around the center to be used for the hotspot
+ * of a cell and its default value is 0.5. In addition,
+ * <mxConstants.MIN_HOTSPOT_SIZE> defines the minimum number of pixels for the
+ * width and height of the hotspot.
+ * 
+ * This solution, while standards compliant, may be somewhat confusing because
+ * there is no visual indicator for the hotspot and the highlight is seen to
+ * switch on and off while the mouse is being moved in and out. Furthermore,
+ * this paradigm does not allow to create different connections depending on
+ * the highlighted hotspot as there is only one hotspot per cell and it
+ * normally does not allow cells to be moved and connected at the same time as
+ * there is no clear indication of the connectable area of the cell.
+ * 
+ * To come across these issues, the handle has an additional <createIcons> hook
+ * with a default implementation that allows to create one icon to be used to
+ * trigger new connections. If this icon is specified, then new connections can
+ * only be created if the image is clicked while the cell is being highlighted.
+ * The <createIcons> hook may be overridden to create more than one
+ * <mxImageShape> for creating new connections, but the default implementation
+ * supports one image and is used as follows:
+ * 
+ * In order to display the "connect image" whenever the mouse is over the cell,
+ * an DEFAULT_HOTSPOT of 1 should be used:
+ * 
+ * (code)
+ * mxConstants.DEFAULT_HOTSPOT = 1;
+ * (end)
+ * 
+ * In order to avoid confusion with the highlighting, the highlight color
+ * should not be used with a connect image:
+ * 
+ * (code)
+ * mxConstants.HIGHLIGHT_COLOR = null;
+ * (end)
+ * 
+ * To install the image, the connectImage field of the mxConnectionHandler must
+ * be assigned a new <mxImage> instance:
+ * 
+ * (code)
+ * mxConnectionHandler.prototype.connectImage = new mxImage('images/green-dot.gif', 14, 14);
+ * (end)
+ * 
+ * This will use the green-dot.gif with a width and height of 14 pixels as the
+ * image to trigger new connections. In createIcons the icon field of the
+ * handler will be set in order to remember the icon that has been clicked for
+ * creating the new connection. This field will be available under selectedIcon
+ * in the connect method, which may be overridden to take the icon that
+ * triggered the new connection into account. This is useful if more than one
+ * icon may be used to create a connection.
+ *
+ * Group: Events
+ * 
+ * Event: mxEvent.START
+ * 
+ * Fires when a new connection is being created by the user. The <code>state</code>
+ * property contains the state of the source cell.
+ * 
+ * Event: mxEvent.CONNECT
+ * 
+ * Fires between begin- and endUpdate in <connect>. The <code>cell</code>
+ * property contains the inserted edge, the <code>event</code> and <code>target</code> 
+ * properties contain the respective arguments that were passed to <connect> (where
+ * target corresponds to the dropTarget argument). Finally, the <code>terminal</code>
+ * property corresponds to the target argument in <connect> or the clone of the source
+ * terminal if <createTarget> is enabled.
+ * 
+ * Note that the target is the cell under the mouse where the mouse button was released.
+ * Depending on the logic in the handler, this doesn't necessarily have to be the target
+ * of the inserted edge. To print the source, target or any optional ports IDs that the
+ * edge is connected to, the following code can be used. To get more details about the
+ * actual connection point, <mxGraph.getConnectionConstraint> can be used. To resolve
+ * the port IDs, use <mxGraphModel.getCell>.
+ * 
+ * (code)
+ * graph.connectionHandler.addListener(mxEvent.CONNECT, function(sender, evt)
+ * {
+ *   var edge = evt.getProperty('cell');
+ *   var source = graph.getModel().getTerminal(edge, true);
+ *   var target = graph.getModel().getTerminal(edge, false);
+ *   
+ *   var style = graph.getCellStyle(edge);
+ *   var sourcePortId = style[mxConstants.STYLE_SOURCE_PORT];
+ *   var targetPortId = style[mxConstants.STYLE_TARGET_PORT];
+ *   
+ *   mxLog.show();
+ *   mxLog.debug('connect', edge, source.id, target.id, sourcePortId, targetPortId);
+ * });
+ * (end)
+ *
+ * Event: mxEvent.RESET
+ * 
+ * Fires when the <reset> method is invoked.
+ *
+ * Constructor: mxConnectionHandler
+ *
+ * Constructs an event handler that connects vertices using the specified
+ * factory method to create the new edges. Modify
+ * <mxConstants.ACTIVE_REGION> to setup the region on a cell which triggers
+ * the creation of a new connection or use connect icons as explained
+ * above.
+ * 
+ * Parameters:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ * factoryMethod - Optional function to create the edge. The function takes
+ * the source and target <mxCell> as the first and second argument and an
+ * optional cell style from the preview as the third argument. It returns
+ * the <mxCell> that represents the new edge.
+ */
+function mxConnectionHandler(graph, factoryMethod)
+{
+	mxEventSource.call(this);
+	
+	if (graph != null)
+	{
+		this.graph = graph;
+		this.factoryMethod = factoryMethod;
+		this.init();
+		
+		// Handles escape keystrokes
+		this.escapeHandler = mxUtils.bind(this, function(sender, evt)
+		{
+			this.reset();
+		});
+		
+		this.graph.addListener(mxEvent.ESCAPE, this.escapeHandler);
+	}
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxUtils.extend(mxConnectionHandler, mxEventSource);
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxConnectionHandler.prototype.graph = null;
+
+/**
+ * Variable: factoryMethod
+ * 
+ * Function that is used for creating new edges. The function takes the
+ * source and target <mxCell> as the first and second argument and returns
+ * a new <mxCell> that represents the edge. This is used in <createEdge>.
+ */
+mxConnectionHandler.prototype.factoryMethod = true;
+
+/**
+ * Variable: moveIconFront
+ * 
+ * Specifies if icons should be displayed inside the graph container instead
+ * of the overlay pane. This is used for HTML labels on vertices which hide
+ * the connect icon. This has precendence over <moveIconBack> when set
+ * to true. Default is false.
+ */
+mxConnectionHandler.prototype.moveIconFront = false;
+
+/**
+ * Variable: moveIconBack
+ * 
+ * Specifies if icons should be moved to the back of the overlay pane. This can
+ * be set to true if the icons of the connection handler conflict with other
+ * handles, such as the vertex label move handle. Default is false.
+ */
+mxConnectionHandler.prototype.moveIconBack = false;
+
+/**
+ * Variable: connectImage
+ * 
+ * <mxImage> that is used to trigger the creation of a new connection. This
+ * is used in <createIcons>. Default is null.
+ */
+mxConnectionHandler.prototype.connectImage = null;
+
+/**
+ * Variable: targetConnectImage
+ * 
+ * Specifies if the connect icon should be centered on the target state
+ * while connections are being previewed. Default is false.
+ */
+mxConnectionHandler.prototype.targetConnectImage = false;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies if events are handled. Default is true.
+ */
+mxConnectionHandler.prototype.enabled = true;
+
+/**
+ * Variable: select
+ * 
+ * Specifies if new edges should be selected. Default is true.
+ */
+mxConnectionHandler.prototype.select = true;
+
+/**
+ * Variable: createTarget
+ * 
+ * Specifies if <createTargetVertex> should be called if no target was under the
+ * mouse for the new connection. Setting this to true means the connection
+ * will be drawn as valid if no target is under the mouse, and
+ * <createTargetVertex> will be called before the connection is created between
+ * the source cell and the newly created vertex in <createTargetVertex>, which
+ * can be overridden to create a new target. Default is false.
+ */
+mxConnectionHandler.prototype.createTarget = false;
+
+/**
+ * Variable: marker
+ * 
+ * Holds the <mxTerminalMarker> used for finding source and target cells.
+ */
+mxConnectionHandler.prototype.marker = null;
+
+/**
+ * Variable: constraintHandler
+ * 
+ * Holds the <mxConstraintHandler> used for drawing and highlighting
+ * constraints.
+ */
+mxConnectionHandler.prototype.constraintHandler = null;
+
+/**
+ * Variable: error
+ * 
+ * Holds the current validation error while connections are being created.
+ */
+mxConnectionHandler.prototype.error = null;
+
+/**
+ * Variable: waypointsEnabled
+ * 
+ * Specifies if single clicks should add waypoints on the new edge. Default is
+ * false.
+ */
+mxConnectionHandler.prototype.waypointsEnabled = false;
+
+/**
+ * Variable: ignoreMouseDown
+ * 
+ * Specifies if the connection handler should ignore the state of the mouse
+ * button when highlighting the source. Default is false, that is, the
+ * handler only highlights the source if no button is being pressed.
+ */
+mxConnectionHandler.prototype.ignoreMouseDown = false;
+
+/**
+ * Variable: first
+ * 
+ * Holds the <mxPoint> where the mouseDown took place while the handler is
+ * active.
+ */
+mxConnectionHandler.prototype.first = null;
+
+/**
+ * Variable: connectIconOffset
+ * 
+ * Holds the offset for connect icons during connection preview.
+ * Default is mxPoint(0, <mxConstants.TOOLTIP_VERTICAL_OFFSET>).
+ * Note that placing the icon under the mouse pointer with an
+ * offset of (0,0) will affect hit detection.
+ */
+mxConnectionHandler.prototype.connectIconOffset = new mxPoint(0, mxConstants.TOOLTIP_VERTICAL_OFFSET);
+
+/**
+ * Variable: edgeState
+ * 
+ * Optional <mxCellState> that represents the preview edge while the
+ * handler is active. This is created in <createEdgeState>.
+ */
+mxConnectionHandler.prototype.edgeState = null;
+
+/**
+ * Variable: changeHandler
+ * 
+ * Holds the change event listener for later removal.
+ */
+mxConnectionHandler.prototype.changeHandler = null;
+
+/**
+ * Variable: drillHandler
+ * 
+ * Holds the drill event listener for later removal.
+ */
+mxConnectionHandler.prototype.drillHandler = null;
+
+/**
+ * Variable: mouseDownCounter
+ * 
+ * Counts the number of mouseDown events since the start. The initial mouse
+ * down event counts as 1.
+ */
+mxConnectionHandler.prototype.mouseDownCounter = 0;
+
+/**
+ * Variable: movePreviewAway
+ * 
+ * Switch to enable moving the preview away from the mousepointer. This is required in browsers
+ * where the preview cannot be made transparent to events and if the built-in hit detection on
+ * the HTML elements in the page should be used. Default is the value of <mxClient.IS_VML>.
+ */
+mxConnectionHandler.prototype.movePreviewAway = mxClient.IS_VML;
+
+/**
+ * Variable: outlineConnect
+ * 
+ * Specifies if connections to the outline of a highlighted target should be
+ * enabled. This will allow to place the connection point along the outline of
+ * the highlighted target. Default is false.
+ */
+mxConnectionHandler.prototype.outlineConnect = false;
+
+/**
+ * Variable: livePreview
+ * 
+ * Specifies if the actual shape of the edge state should be used for the preview.
+ * Default is false. (Ignored if no edge state is created in <createEdgeState>.)
+ */
+mxConnectionHandler.prototype.livePreview = false;
+
+/**
+ * Variable: cursor
+ * 
+ * Specifies the cursor to be used while the handler is active. Default is null.
+ */
+mxConnectionHandler.prototype.cursor = null;
+
+/**
+ * Variable: insertBeforeSource
+ * 
+ * Specifies if new edges should be inserted before the source vertex in the
+ * cell hierarchy. Default is false for backwards compatibility.
+ */
+mxConnectionHandler.prototype.insertBeforeSource = false;
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxConnectionHandler.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+	
+/**
+ * Function: setEnabled
+ * 
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ * 
+ * Parameters:
+ * 
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxConnectionHandler.prototype.setEnabled = function(enabled)
+{
+	this.enabled = enabled;
+};
+
+/**
+ * Function: isInsertBefore
+ * 
+ * Returns <insertBeforeSource> for non-loops and false for loops.
+ *
+ * Parameters:
+ * 
+ * edge - <mxCell> that represents the edge to be inserted.
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ * evt - Mousedown event of the connect gesture.
+ * dropTarget - <mxCell> that represents the cell under the mouse when it was
+ * released.
+ */
+mxConnectionHandler.prototype.isInsertBefore = function(edge, source, target, evt, dropTarget)
+{
+	return this.insertBeforeSource && source != target;
+};
+
+/**
+ * Function: isCreateTarget
+ * 
+ * Returns <createTarget>.
+ *
+ * Parameters:
+ *
+ * evt - Current active native pointer event.
+ */
+mxConnectionHandler.prototype.isCreateTarget = function(evt)
+{
+	return this.createTarget;
+};
+
+/**
+ * Function: setCreateTarget
+ * 
+ * Sets <createTarget>.
+ */
+mxConnectionHandler.prototype.setCreateTarget = function(value)
+{
+	this.createTarget = value;
+};
+
+/**
+ * Function: createShape
+ * 
+ * Creates the preview shape for new connections.
+ */
+mxConnectionHandler.prototype.createShape = function()
+{
+	// Creates the edge preview
+	var shape = (this.livePreview && this.edgeState != null) ?
+		this.graph.cellRenderer.createShape(this.edgeState) :
+		new mxPolyline([], mxConstants.INVALID_COLOR);
+	shape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
+		mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+	shape.scale = this.graph.view.scale;
+	shape.pointerEvents = false;
+	shape.isDashed = true;
+	shape.init(this.graph.getView().getOverlayPane());
+	mxEvent.redirectMouseEvents(shape.node, this.graph, null);
+
+	return shape;
+};
+
+/**
+ * Function: init
+ * 
+ * Initializes the shapes required for this connection handler. This should
+ * be invoked if <mxGraph.container> is assigned after the connection
+ * handler has been created.
+ */
+mxConnectionHandler.prototype.init = function()
+{
+	this.graph.addMouseListener(this);
+	this.marker = this.createMarker();
+	this.constraintHandler = new mxConstraintHandler(this.graph);
+
+	// Redraws the icons if the graph changes
+	this.changeHandler = mxUtils.bind(this, function(sender)
+	{
+		if (this.iconState != null)
+		{
+			this.iconState = this.graph.getView().getState(this.iconState.cell);
+		}
+		
+		if (this.iconState != null)
+		{
+			this.redrawIcons(this.icons, this.iconState);
+			this.constraintHandler.reset();
+		}
+		else if (this.previous != null && this.graph.view.getState(this.previous.cell) == null)
+		{
+			this.reset();
+		}
+	});
+	
+	this.graph.getModel().addListener(mxEvent.CHANGE, this.changeHandler);
+	this.graph.getView().addListener(mxEvent.SCALE, this.changeHandler);
+	this.graph.getView().addListener(mxEvent.TRANSLATE, this.changeHandler);
+	this.graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE, this.changeHandler);
+	
+	// Removes the icon if we step into/up or start editing
+	this.drillHandler = mxUtils.bind(this, function(sender)
+	{
+		this.reset();
+	});
+	
+	this.graph.addListener(mxEvent.START_EDITING, this.drillHandler);
+	this.graph.getView().addListener(mxEvent.DOWN, this.drillHandler);
+	this.graph.getView().addListener(mxEvent.UP, this.drillHandler);
+};
+
+/**
+ * Function: isConnectableCell
+ * 
+ * Returns true if the given cell is connectable. This is a hook to
+ * disable floating connections. This implementation returns true.
+ */
+mxConnectionHandler.prototype.isConnectableCell = function(cell)
+{
+	return true;
+};
+
+/**
+ * Function: createMarker
+ * 
+ * Creates and returns the <mxCellMarker> used in <marker>.
+ */
+mxConnectionHandler.prototype.createMarker = function()
+{
+	var marker = new mxCellMarker(this.graph);
+	marker.hotspotEnabled = true;
+
+	// Overrides to return cell at location only if valid (so that
+	// there is no highlight for invalid cells)
+	marker.getCell = mxUtils.bind(this, function(me)
+	{
+		var cell = mxCellMarker.prototype.getCell.apply(marker, arguments);
+		this.error = null;
+		
+		// Checks for cell at preview point (with grid)
+		if (cell == null && this.currentPoint != null)
+		{
+			cell = this.graph.getCellAt(this.currentPoint.x, this.currentPoint.y);
+		}
+		
+		// Uses connectable parent vertex if one exists
+		if (cell != null && !this.graph.isCellConnectable(cell))
+		{
+			var parent = this.graph.getModel().getParent(cell);
+			
+			if (this.graph.getModel().isVertex(parent) && this.graph.isCellConnectable(parent))
+			{
+				cell = parent;
+			}
+		}
+		
+		if ((this.graph.isSwimlane(cell) && this.currentPoint != null &&
+			this.graph.hitsSwimlaneContent(cell, this.currentPoint.x, this.currentPoint.y)) ||
+			!this.isConnectableCell(cell))
+		{
+			cell = null;
+		}
+		
+		if (cell != null)
+		{
+			if (this.isConnecting())
+			{
+				if (this.previous != null)
+				{
+					this.error = this.validateConnection(this.previous.cell, cell);
+					
+					if (this.error != null && this.error.length == 0)
+					{
+						cell = null;
+						
+						// Enables create target inside groups
+						if (this.isCreateTarget(me.getEvent()))
+						{
+							this.error = null;
+						}
+					}
+				}
+			}
+			else if (!this.isValidSource(cell, me))
+			{
+				cell = null;
+			}
+		}
+		else if (this.isConnecting() && !this.isCreateTarget(me.getEvent()) &&
+				!this.graph.allowDanglingEdges)
+		{
+			this.error = '';
+		}
+
+		return cell;
+	});
+
+	// Sets the highlight color according to validateConnection
+	marker.isValidState = mxUtils.bind(this, function(state)
+	{
+		if (this.isConnecting())
+		{
+			return this.error == null;
+		}
+		else
+		{
+			return mxCellMarker.prototype.isValidState.apply(marker, arguments);
+		}
+	});
+
+	// Overrides to use marker color only in highlight mode or for
+	// target selection
+	marker.getMarkerColor = mxUtils.bind(this, function(evt, state, isValid)
+	{
+		return (this.connectImage == null || this.isConnecting()) ?
+			mxCellMarker.prototype.getMarkerColor.apply(marker, arguments) :
+			null;
+	});
+
+	// Overrides to use hotspot only for source selection otherwise
+	// intersects always returns true when over a cell
+	marker.intersects = mxUtils.bind(this, function(state, evt)
+	{
+		if (this.connectImage != null || this.isConnecting())
+		{
+			return true;
+		}
+		
+		return mxCellMarker.prototype.intersects.apply(marker, arguments);
+	});
+
+	return marker;
+};
+
+/**
+ * Function: start
+ * 
+ * Starts a new connection for the given state and coordinates.
+ */
+mxConnectionHandler.prototype.start = function(state, x, y, edgeState)
+{
+	this.previous = state;
+	this.first = new mxPoint(x, y);
+	this.edgeState = (edgeState != null) ? edgeState : this.createEdgeState(null);
+	
+	// Marks the source state
+	this.marker.currentColor = this.marker.validColor;
+	this.marker.markedState = state;
+	this.marker.mark();
+
+	this.fireEvent(new mxEventObject(mxEvent.START, 'state', this.previous));
+};
+
+/**
+ * Function: isConnecting
+ * 
+ * Returns true if the source terminal has been clicked and a new
+ * connection is currently being previewed.
+ */
+mxConnectionHandler.prototype.isConnecting = function()
+{
+	return this.first != null && this.shape != null;
+};
+
+/**
+ * Function: isValidSource
+ * 
+ * Returns <mxGraph.isValidSource> for the given source terminal.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the source terminal.
+ * me - <mxMouseEvent> that is associated with this call.
+ */
+mxConnectionHandler.prototype.isValidSource = function(cell, me)
+{
+	return this.graph.isValidSource(cell);
+};
+
+/**
+ * Function: isValidTarget
+ * 
+ * Returns true. The call to <mxGraph.isValidTarget> is implicit by calling
+ * <mxGraph.getEdgeValidationError> in <validateConnection>. This is an
+ * additional hook for disabling certain targets in this specific handler.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the target terminal.
+ */
+mxConnectionHandler.prototype.isValidTarget = function(cell)
+{
+	return true;
+};
+
+/**
+ * Function: validateConnection
+ * 
+ * Returns the error message or an empty string if the connection for the
+ * given source target pair is not valid. Otherwise it returns null. This
+ * implementation uses <mxGraph.getEdgeValidationError>.
+ * 
+ * Parameters:
+ * 
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ */
+mxConnectionHandler.prototype.validateConnection = function(source, target)
+{
+	if (!this.isValidTarget(target))
+	{
+		return '';
+	}
+	
+	return this.graph.getEdgeValidationError(null, source, target);
+};
+
+/**
+ * Function: getConnectImage
+ * 
+ * Hook to return the <mxImage> used for the connection icon of the given
+ * <mxCellState>. This implementation returns <connectImage>.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose connect image should be returned.
+ */
+mxConnectionHandler.prototype.getConnectImage = function(state)
+{
+	return this.connectImage;
+};
+
+/**
+ * Function: isMoveIconToFrontForState
+ * 
+ * Returns true if the state has a HTML label in the graph's container, otherwise
+ * it returns <moveIconFront>.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose connect icons should be returned.
+ */
+mxConnectionHandler.prototype.isMoveIconToFrontForState = function(state)
+{
+	if (state.text != null && state.text.node.parentNode == this.graph.container)
+	{
+		return true;
+	}
+	
+	return this.moveIconFront;
+};
+
+/**
+ * Function: createIcons
+ * 
+ * Creates the array <mxImageShapes> that represent the connect icons for
+ * the given <mxCellState>.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose connect icons should be returned.
+ */
+mxConnectionHandler.prototype.createIcons = function(state)
+{
+	var image = this.getConnectImage(state);
+	
+	if (image != null && state != null)
+	{
+		this.iconState = state;
+		var icons = [];
+
+		// Cannot use HTML for the connect icons because the icon receives all
+		// mouse move events in IE, must use VML and SVG instead even if the
+		// connect-icon appears behind the selection border and the selection
+		// border consumes the events before the icon gets a chance
+		var bounds = new mxRectangle(0, 0, image.width, image.height);
+		var icon = new mxImageShape(bounds, image.src, null, null, 0);
+		icon.preserveImageAspect = false;
+		
+		if (this.isMoveIconToFrontForState(state))
+		{
+			icon.dialect = mxConstants.DIALECT_STRICTHTML;
+			icon.init(this.graph.container);
+		}
+		else
+		{
+			icon.dialect = (this.graph.dialect == mxConstants.DIALECT_SVG) ?
+				mxConstants.DIALECT_SVG : mxConstants.DIALECT_VML;
+			icon.init(this.graph.getView().getOverlayPane());
+
+			// Move the icon back in the overlay pane
+			if (this.moveIconBack && icon.node.previousSibling != null)
+			{
+				icon.node.parentNode.insertBefore(icon.node, icon.node.parentNode.firstChild);
+			}
+		}
+
+		icon.node.style.cursor = mxConstants.CURSOR_CONNECT;
+
+		// Events transparency
+		var getState = mxUtils.bind(this, function()
+		{
+			return (this.currentState != null) ? this.currentState : state;
+		});
+		
+		// Updates the local icon before firing the mouse down event.
+		var mouseDown = mxUtils.bind(this, function(evt)
+		{
+			if (!mxEvent.isConsumed(evt))
+			{
+				this.icon = icon;
+				this.graph.fireMouseEvent(mxEvent.MOUSE_DOWN,
+					new mxMouseEvent(evt, getState()));
+			}
+		});
+
+		mxEvent.redirectMouseEvents(icon.node, this.graph, getState, mouseDown);
+		
+		icons.push(icon);
+		this.redrawIcons(icons, this.iconState);
+		
+		return icons;
+	}
+	
+	return null;
+};
+
+/**
+ * Function: redrawIcons
+ * 
+ * Redraws the given array of <mxImageShapes>.
+ * 
+ * Parameters:
+ * 
+ * icons - Optional array of <mxImageShapes> to be redrawn.
+ */
+mxConnectionHandler.prototype.redrawIcons = function(icons, state)
+{
+	if (icons != null && icons[0] != null && state != null)
+	{
+		var pos = this.getIconPosition(icons[0], state);
+		icons[0].bounds.x = pos.x;
+		icons[0].bounds.y = pos.y;
+		icons[0].redraw();
+	}
+};
+
+/**
+ * Function: redrawIcons
+ * 
+ * Redraws the given array of <mxImageShapes>.
+ * 
+ * Parameters:
+ * 
+ * icons - Optional array of <mxImageShapes> to be redrawn.
+ */
+mxConnectionHandler.prototype.getIconPosition = function(icon, state)
+{
+	var scale = this.graph.getView().scale;
+	var cx = state.getCenterX();
+	var cy = state.getCenterY();
+	
+	if (this.graph.isSwimlane(state.cell))
+	{
+		var size = this.graph.getStartSize(state.cell);
+		
+		cx = (size.width != 0) ? state.x + size.width * scale / 2 : cx;
+		cy = (size.height != 0) ? state.y + size.height * scale / 2 : cy;
+		
+		var alpha = mxUtils.toRadians(mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION) || 0);
+		
+		if (alpha != 0)
+		{
+			var cos = Math.cos(alpha);
+			var sin = Math.sin(alpha);
+			var ct = new mxPoint(state.getCenterX(), state.getCenterY());
+			var pt = mxUtils.getRotatedPoint(new mxPoint(cx, cy), cos, sin, ct);
+			cx = pt.x;
+			cy = pt.y;
+		}
+	}
+
+	return new mxPoint(cx - icon.bounds.width / 2,
+			cy - icon.bounds.height / 2);
+};
+
+/**
+ * Function: destroyIcons
+ * 
+ * Destroys the connect icons and resets the respective state.
+ */
+mxConnectionHandler.prototype.destroyIcons = function()
+{
+	if (this.icons != null)
+	{
+		for (var i = 0; i < this.icons.length; i++)
+		{
+			this.icons[i].destroy();
+		}
+		
+		this.icons = null;
+		this.icon = null;
+		this.selectedIcon = null;
+		this.iconState = null;
+	}
+};
+
+/**
+ * Function: isStartEvent
+ * 
+ * Returns true if the given mouse down event should start this handler. The
+ * This implementation returns true if the event does not force marquee
+ * selection, and the currentConstraint and currentFocus of the
+ * <constraintHandler> are not null, or <previous> and <error> are not null and
+ * <icons> is null or <icons> and <icon> are not null.
+ */
+mxConnectionHandler.prototype.isStartEvent = function(me)
+{
+	return ((this.constraintHandler.currentFocus != null && this.constraintHandler.currentConstraint != null) ||
+		(this.previous != null && this.error == null && (this.icons == null || (this.icons != null &&
+		this.icon != null))));
+};
+
+/**
+ * Function: mouseDown
+ * 
+ * Handles the event by initiating a new connection.
+ */
+mxConnectionHandler.prototype.mouseDown = function(sender, me)
+{
+	this.mouseDownCounter++;
+	
+	if (this.isEnabled() && this.graph.isEnabled() && !me.isConsumed() &&
+		!this.isConnecting() && this.isStartEvent(me))
+	{
+		if (this.constraintHandler.currentConstraint != null &&
+			this.constraintHandler.currentFocus != null &&
+			this.constraintHandler.currentPoint != null)
+		{
+			this.sourceConstraint = this.constraintHandler.currentConstraint;
+			this.previous = this.constraintHandler.currentFocus;
+			this.first = this.constraintHandler.currentPoint.clone();
+		}
+		else
+		{
+			// Stores the location of the initial mousedown
+			this.first = new mxPoint(me.getGraphX(), me.getGraphY());
+		}
+	
+		this.edgeState = this.createEdgeState(me);
+		this.mouseDownCounter = 1;
+		
+		if (this.waypointsEnabled && this.shape == null)
+		{
+			this.waypoints = null;
+			this.shape = this.createShape();
+			
+			if (this.edgeState != null)
+			{
+				this.shape.apply(this.edgeState);
+			}
+		}
+
+		// Stores the starting point in the geometry of the preview
+		if (this.previous == null && this.edgeState != null)
+		{
+			var pt = this.graph.getPointForEvent(me.getEvent());
+			this.edgeState.cell.geometry.setTerminalPoint(pt, true);
+		}
+		
+		this.fireEvent(new mxEventObject(mxEvent.START, 'state', this.previous));
+
+		me.consume();
+	}
+
+	this.selectedIcon = this.icon;
+	this.icon = null;
+};
+
+/**
+ * Function: isImmediateConnectSource
+ * 
+ * Returns true if a tap on the given source state should immediately start
+ * connecting. This implementation returns true if the state is not movable
+ * in the graph. 
+ */
+mxConnectionHandler.prototype.isImmediateConnectSource = function(state)
+{
+	return !this.graph.isCellMovable(state.cell);
+};
+
+/**
+ * Function: createEdgeState
+ * 
+ * Hook to return an <mxCellState> which may be used during the preview.
+ * This implementation returns null.
+ * 
+ * Use the following code to create a preview for an existing edge style:
+ * 
+ * (code)
+ * graph.connectionHandler.createEdgeState = function(me)
+ * {
+ *   var edge = graph.createEdge(null, null, null, null, null, 'edgeStyle=elbowEdgeStyle');
+ *   
+ *   return new mxCellState(this.graph.view, edge, this.graph.getCellStyle(edge));
+ * };
+ * (end)
+ */
+mxConnectionHandler.prototype.createEdgeState = function(me)
+{
+	return null;
+};
+
+/**
+ * Function: isOutlineConnectEvent
+ * 
+ * Returns true if <outlineConnect> is true and the source of the event is the outline shape
+ * or shift is pressed.
+ */
+mxConnectionHandler.prototype.isOutlineConnectEvent = function(me)
+{
+	var offset = mxUtils.getOffset(this.graph.container);
+	var evt = me.getEvent();
+	
+	var clientX = mxEvent.getClientX(evt);
+	var clientY = mxEvent.getClientY(evt);
+	
+	var doc = document.documentElement;
+	var left = (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0);
+	var top = (window.pageYOffset || doc.scrollTop)  - (doc.clientTop || 0);
+	
+	var gridX = this.currentPoint.x - this.graph.container.scrollLeft + offset.x - left;
+	var gridY = this.currentPoint.y - this.graph.container.scrollTop + offset.y - top;
+
+	return this.outlineConnect && !mxEvent.isShiftDown(me.getEvent()) &&
+		(me.isSource(this.marker.highlight.shape) ||
+		(mxEvent.isAltDown(me.getEvent()) && me.getState() != null) ||
+		this.marker.highlight.isHighlightAt(clientX, clientY) ||
+		((gridX != clientX || gridY != clientY) && me.getState() == null &&
+		this.marker.highlight.isHighlightAt(gridX, gridY)));
+};
+
+/**
+ * Function: updateCurrentState
+ * 
+ * Updates the current state for a given mouse move event by using
+ * the <marker>.
+ */
+mxConnectionHandler.prototype.updateCurrentState = function(me, point)
+{
+	this.constraintHandler.update(me, this.first == null, false, (this.first == null ||
+		me.isSource(this.marker.highlight.shape)) ? null : point);
+	
+	if (this.constraintHandler.currentFocus != null && this.constraintHandler.currentConstraint != null)
+	{
+		// Handles special case where grid is large and connection point is at actual point in which
+		// case the outline is not followed as long as we're < gridSize / 2 away from that point
+		if (this.marker.highlight != null && this.marker.highlight.state != null &&
+			this.marker.highlight.state.cell == this.constraintHandler.currentFocus.cell)
+		{
+			// Direct repaint needed if cell already highlighted
+			if (this.marker.highlight.shape.stroke != 'transparent')
+			{
+				this.marker.highlight.shape.stroke = 'transparent';
+				this.marker.highlight.repaint();
+			}
+		}
+		else
+		{
+			this.marker.markCell(this.constraintHandler.currentFocus.cell, 'transparent');
+		}
+
+		// Updates validation state
+		if (this.previous != null)
+		{
+			this.error = this.validateConnection(this.previous.cell, this.constraintHandler.currentFocus.cell);
+			
+			if (this.error == null)
+			{
+				this.currentState = this.constraintHandler.currentFocus;
+			}
+			else
+			{
+				this.constraintHandler.reset();
+			}
+		}
+	}
+	else
+	{
+		if (this.graph.isIgnoreTerminalEvent(me.getEvent()))
+		{
+			this.marker.reset();
+			this.currentState = null;
+		}
+		else
+		{
+			this.marker.process(me);
+			this.currentState = this.marker.getValidState();
+		}
+
+		var outline = this.isOutlineConnectEvent(me);
+		
+		if (this.currentState != null && outline)
+		{
+			// Handles special case where mouse is on outline away from actual end point
+			// in which case the grid is ignored and mouse point is used instead
+			if (me.isSource(this.marker.highlight.shape))
+			{
+				point = new mxPoint(me.getGraphX(), me.getGraphY());
+			}
+			
+			var constraint = this.graph.getOutlineConstraint(point, this.currentState, me);
+			this.constraintHandler.setFocus(me, this.currentState, false);
+			this.constraintHandler.currentConstraint = constraint;
+			this.constraintHandler.currentPoint = point;
+		}
+
+		if (this.outlineConnect)
+		{
+			if (this.marker.highlight != null && this.marker.highlight.shape != null)
+			{
+				var s = this.graph.view.scale;
+				
+				if (this.constraintHandler.currentConstraint != null &&
+					this.constraintHandler.currentFocus != null)
+				{
+					this.marker.highlight.shape.stroke = mxConstants.OUTLINE_HIGHLIGHT_COLOR;
+					this.marker.highlight.shape.strokewidth = mxConstants.OUTLINE_HIGHLIGHT_STROKEWIDTH / s / s;
+					this.marker.highlight.repaint();
+				} 
+				else if (this.marker.hasValidState())
+				{
+					// Handles special case where actual end point of edge and current mouse point
+					// are not equal (due to grid snapping) and there is no hit on shape or highlight
+					if (this.marker.getValidState() != me.getState())
+					{
+						this.marker.highlight.shape.stroke = 'transparent';
+						this.currentState = null;
+					}
+					else
+					{
+						this.marker.highlight.shape.stroke = mxConstants.DEFAULT_VALID_COLOR;
+					}
+	
+					this.marker.highlight.shape.strokewidth = mxConstants.HIGHLIGHT_STROKEWIDTH / s / s;
+					this.marker.highlight.repaint();
+				}
+			}
+		}
+	}
+};
+
+/**
+ * Function: convertWaypoint
+ * 
+ * Converts the given point from screen coordinates to model coordinates.
+ */
+mxConnectionHandler.prototype.convertWaypoint = function(point)
+{
+	var scale = this.graph.getView().getScale();
+	var tr = this.graph.getView().getTranslate();
+	
+	point.x = point.x / scale - tr.x;
+	point.y = point.y / scale - tr.y;
+};
+
+/**
+ * Function: snapToPreview
+ * 
+ * Called to snap the given point to the current preview. This snaps to the
+ * first point of the preview if alt is not pressed.
+ */
+mxConnectionHandler.prototype.snapToPreview = function(me, point)
+{
+	if (!mxEvent.isAltDown(me.getEvent()) && this.previous != null)
+	{
+		var tol = this.graph.gridSize * this.graph.view.scale / 2;	
+		var tmp = (this.sourceConstraint != null) ? this.first :
+			new mxPoint(this.previous.getCenterX(), this.previous.getCenterY());
+
+		if (Math.abs(tmp.x - me.getGraphX()) < tol)
+		{
+			point.x = tmp.x;
+		}
+		
+		if (Math.abs(tmp.y - me.getGraphY()) < tol)
+		{
+			point.y = tmp.y;
+		}
+	}	
+};
+
+/**
+ * Function: mouseMove
+ * 
+ * Handles the event by updating the preview edge or by highlighting
+ * a possible source or target terminal.
+ */
+mxConnectionHandler.prototype.mouseMove = function(sender, me)
+{
+	if (!me.isConsumed() && (this.ignoreMouseDown || this.first != null || !this.graph.isMouseDown))
+	{
+		// Handles special case when handler is disabled during highlight
+		if (!this.isEnabled() && this.currentState != null)
+		{
+			this.destroyIcons();
+			this.currentState = null;
+		}
+
+		var view = this.graph.getView();
+		var scale = view.scale;
+		var tr = view.translate;
+		var point = new mxPoint(me.getGraphX(), me.getGraphY());
+		this.error = null;
+
+		if (this.graph.isGridEnabledEvent(me.getEvent()))
+		{
+			point = new mxPoint((this.graph.snap(point.x / scale - tr.x) + tr.x) * scale,
+				(this.graph.snap(point.y / scale - tr.y) + tr.y) * scale);
+		}
+		
+		this.snapToPreview(me, point);
+		this.currentPoint = point;
+		
+		if (this.first != null || (this.isEnabled() && this.graph.isEnabled()))
+		{
+			this.updateCurrentState(me, point);
+		}
+
+		if (this.first != null)
+		{
+			var constraint = null;
+			var current = point;
+			
+			// Uses the current point from the constraint handler if available
+			if (this.constraintHandler.currentConstraint != null &&
+				this.constraintHandler.currentFocus != null &&
+				this.constraintHandler.currentPoint != null)
+			{
+				constraint = this.constraintHandler.currentConstraint;
+				current = this.constraintHandler.currentPoint.clone();
+			}
+			else if (this.previous != null && !this.graph.isIgnoreTerminalEvent(me.getEvent()) && mxEvent.isShiftDown(me.getEvent()))
+			{
+				if (Math.abs(this.previous.getCenterX() - point.x) < Math.abs(this.previous.getCenterY() - point.y))
+				{
+					point.x = this.previous.getCenterX();
+				}
+				else
+				{
+					point.y = this.previous.getCenterY();
+				}
+			}
+			
+			var pt2 = this.first;
+			
+			// Moves the connect icon with the mouse
+			if (this.selectedIcon != null)
+			{
+				var w = this.selectedIcon.bounds.width;
+				var h = this.selectedIcon.bounds.height;
+				
+				if (this.currentState != null && this.targetConnectImage)
+				{
+					var pos = this.getIconPosition(this.selectedIcon, this.currentState);
+					this.selectedIcon.bounds.x = pos.x;
+					this.selectedIcon.bounds.y = pos.y;
+				}
+				else
+				{
+					var bounds = new mxRectangle(me.getGraphX() + this.connectIconOffset.x,
+						me.getGraphY() + this.connectIconOffset.y, w, h);
+					this.selectedIcon.bounds = bounds;
+				}
+				
+				this.selectedIcon.redraw();
+			}
+
+			// Uses edge state to compute the terminal points
+			if (this.edgeState != null)
+			{
+				this.updateEdgeState(current, constraint);
+				current = this.edgeState.absolutePoints[this.edgeState.absolutePoints.length - 1];
+				pt2 = this.edgeState.absolutePoints[0];
+			}
+			else
+			{
+				if (this.currentState != null)
+				{
+					if (this.constraintHandler.currentConstraint == null)
+					{
+						var tmp = this.getTargetPerimeterPoint(this.currentState, me);
+						
+						if (tmp != null)
+						{
+							current = tmp;
+						}
+					}
+				}
+				
+				// Computes the source perimeter point
+				if (this.sourceConstraint == null && this.previous != null)
+				{
+					var next = (this.waypoints != null && this.waypoints.length > 0) ?
+							this.waypoints[0] : current;
+					var tmp = this.getSourcePerimeterPoint(this.previous, next, me);
+					
+					if (tmp != null)
+					{
+						pt2 = tmp;
+					}
+				}
+			}
+
+			// Makes sure the cell under the mousepointer can be detected
+			// by moving the preview shape away from the mouse. This
+			// makes sure the preview shape does not prevent the detection
+			// of the cell under the mousepointer even for slow gestures.
+			if (this.currentState == null && this.movePreviewAway)
+			{
+				var tmp = pt2; 
+				
+				if (this.edgeState != null && this.edgeState.absolutePoints.length >= 2)
+				{
+					var tmp2 = this.edgeState.absolutePoints[this.edgeState.absolutePoints.length - 2];
+					
+					if (tmp2 != null)
+					{
+						tmp = tmp2;
+					}
+				}
+				
+				var dx = current.x - tmp.x;
+				var dy = current.y - tmp.y;
+				
+				var len = Math.sqrt(dx * dx + dy * dy);
+				
+				if (len == 0)
+				{
+					return;
+				}
+
+				// Stores old point to reuse when creating edge
+				this.originalPoint = current.clone();
+				current.x -= dx * 4 / len;
+				current.y -= dy * 4 / len;
+			}
+			else
+			{
+				this.originalPoint = null;
+			}
+			
+			// Creates the preview shape (lazy)
+			if (this.shape == null)
+			{
+				var dx = Math.abs(point.x - this.first.x);
+				var dy = Math.abs(point.y - this.first.y);
+
+				if (dx > this.graph.tolerance || dy > this.graph.tolerance)
+				{
+					this.shape = this.createShape();
+
+					if (this.edgeState != null)
+					{
+						this.shape.apply(this.edgeState);
+					}
+					
+					// Revalidates current connection
+					this.updateCurrentState(me, point);
+				}
+			}
+
+			// Updates the points in the preview edge
+			if (this.shape != null)
+			{
+				if (this.edgeState != null)
+				{
+					this.shape.points = this.edgeState.absolutePoints;
+				}
+				else
+				{
+					var pts = [pt2];
+					
+					if (this.waypoints != null)
+					{
+						pts = pts.concat(this.waypoints);
+					}
+
+					pts.push(current);
+					this.shape.points = pts;
+				}
+				
+				this.drawPreview();
+			}
+			
+			// Makes sure endpoint of edge is visible during connect
+			if (this.cursor != null)
+			{
+				this.graph.container.style.cursor = this.cursor;
+			}
+			
+			mxEvent.consume(me.getEvent());
+			me.consume();
+		}
+		else if (!this.isEnabled() || !this.graph.isEnabled())
+		{
+			this.constraintHandler.reset();
+		}
+		else if (this.previous != this.currentState && this.edgeState == null)
+		{
+			this.destroyIcons();
+			
+			// Sets the cursor on the current shape				
+			if (this.currentState != null && this.error == null && this.constraintHandler.currentConstraint == null)
+			{
+				this.icons = this.createIcons(this.currentState);
+
+				if (this.icons == null)
+				{
+					this.currentState.setCursor(mxConstants.CURSOR_CONNECT);
+					me.consume();
+				}
+			}
+
+			this.previous = this.currentState;
+		}
+		else if (this.previous == this.currentState && this.currentState != null && this.icons == null &&
+			!this.graph.isMouseDown)
+		{
+			// Makes sure that no cursors are changed
+			me.consume();
+		}
+
+		if (!this.graph.isMouseDown && this.currentState != null && this.icons != null)
+		{
+			var hitsIcon = false;
+			var target = me.getSource();
+			
+			for (var i = 0; i < this.icons.length && !hitsIcon; i++)
+			{
+				hitsIcon = target == this.icons[i].node || target.parentNode == this.icons[i].node;
+			}
+
+			if (!hitsIcon)
+			{
+				this.updateIcons(this.currentState, this.icons, me);
+			}
+		}
+	}
+	else
+	{
+		this.constraintHandler.reset();
+	}
+};
+
+/**
+ * Function: updateEdgeState
+ * 
+ * Updates <edgeState>.
+ */
+mxConnectionHandler.prototype.updateEdgeState = function(current, constraint)
+{
+	// TODO: Use generic method for writing constraint to style
+	if (this.sourceConstraint != null && this.sourceConstraint.point != null)
+	{
+		this.edgeState.style[mxConstants.STYLE_EXIT_X] = this.sourceConstraint.point.x;
+		this.edgeState.style[mxConstants.STYLE_EXIT_Y] = this.sourceConstraint.point.y;
+	}
+
+	if (constraint != null && constraint.point != null)
+	{
+		this.edgeState.style[mxConstants.STYLE_ENTRY_X] = constraint.point.x;
+		this.edgeState.style[mxConstants.STYLE_ENTRY_Y] = constraint.point.y;
+	}
+	else
+	{
+		delete this.edgeState.style[mxConstants.STYLE_ENTRY_X];
+		delete this.edgeState.style[mxConstants.STYLE_ENTRY_Y];
+	}
+	
+	this.edgeState.absolutePoints = [null, (this.currentState != null) ? null : current];
+	this.graph.view.updateFixedTerminalPoint(this.edgeState, this.previous, true, this.sourceConstraint);
+	
+	if (this.currentState != null)
+	{
+		if (constraint == null)
+		{
+			constraint = this.graph.getConnectionConstraint(this.edgeState, this.previous, false);
+		}
+		
+		this.edgeState.setAbsoluteTerminalPoint(null, false);
+		this.graph.view.updateFixedTerminalPoint(this.edgeState, this.currentState, false, constraint);
+	}
+	
+	// Scales and translates the waypoints to the model
+	var realPoints = null;
+	
+	if (this.waypoints != null)
+	{
+		realPoints = [];
+		
+		for (var i = 0; i < this.waypoints.length; i++)
+		{
+			var pt = this.waypoints[i].clone();
+			this.convertWaypoint(pt);
+			realPoints[i] = pt;
+		}
+	}
+	
+	this.graph.view.updatePoints(this.edgeState, realPoints, this.previous, this.currentState);
+	this.graph.view.updateFloatingTerminalPoints(this.edgeState, this.previous, this.currentState);
+};
+
+/**
+ * Function: getTargetPerimeterPoint
+ * 
+ * Returns the perimeter point for the given target state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> that represents the target cell state.
+ * me - <mxMouseEvent> that represents the mouse move.
+ */
+mxConnectionHandler.prototype.getTargetPerimeterPoint = function(state, me)
+{
+	var result = null;
+	var view = state.view;
+	var targetPerimeter = view.getPerimeterFunction(state);
+	
+	if (targetPerimeter != null)
+	{
+		var next = (this.waypoints != null && this.waypoints.length > 0) ?
+				this.waypoints[this.waypoints.length - 1] :
+				new mxPoint(this.previous.getCenterX(), this.previous.getCenterY());
+		var tmp = targetPerimeter(view.getPerimeterBounds(state),
+			this.edgeState, next, false);
+			
+		if (tmp != null)
+		{
+			result = tmp;
+		}
+	}
+	else
+	{
+		result = new mxPoint(state.getCenterX(), state.getCenterY());
+	}
+	
+	return result;
+};
+
+/**
+ * Function: getSourcePerimeterPoint
+ * 
+ * Hook to update the icon position(s) based on a mouseOver event. This is
+ * an empty implementation.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> that represents the target cell state.
+ * next - <mxPoint> that represents the next point along the previewed edge.
+ * me - <mxMouseEvent> that represents the mouse move.
+ */
+mxConnectionHandler.prototype.getSourcePerimeterPoint = function(state, next, me)
+{
+	var result = null;
+	var view = state.view;
+	var sourcePerimeter = view.getPerimeterFunction(state);
+	var c = new mxPoint(state.getCenterX(), state.getCenterY());
+	
+	if (sourcePerimeter != null)
+	{
+		var theta = mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION, 0);
+		var rad = -theta * (Math.PI / 180);
+		
+		if (theta != 0)
+		{
+			next = mxUtils.getRotatedPoint(new mxPoint(next.x, next.y), Math.cos(rad), Math.sin(rad), c);
+		}
+		
+		var tmp = sourcePerimeter(view.getPerimeterBounds(state), state, next, false);
+			
+		if (tmp != null)
+		{
+			if (theta != 0)
+			{
+				tmp = mxUtils.getRotatedPoint(new mxPoint(tmp.x, tmp.y), Math.cos(-rad), Math.sin(-rad), c);
+			}
+			
+			result = tmp;
+		}
+	}
+	else
+	{
+		result = c;
+	}
+	
+	return result;
+};
+
+
+/**
+ * Function: updateIcons
+ * 
+ * Hook to update the icon position(s) based on a mouseOver event. This is
+ * an empty implementation.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> under the mouse.
+ * icons - Array of currently displayed icons.
+ * me - <mxMouseEvent> that contains the mouse event.
+ */
+mxConnectionHandler.prototype.updateIcons = function(state, icons, me)
+{
+	// empty
+};
+
+/**
+ * Function: isStopEvent
+ * 
+ * Returns true if the given mouse up event should stop this handler. The
+ * connection will be created if <error> is null. Note that this is only
+ * called if <waypointsEnabled> is true. This implemtation returns true
+ * if there is a cell state in the given event.
+ */
+mxConnectionHandler.prototype.isStopEvent = function(me)
+{
+	return me.getState() != null;
+};
+
+/**
+ * Function: addWaypoint
+ * 
+ * Adds the waypoint for the given event to <waypoints>.
+ */
+mxConnectionHandler.prototype.addWaypointForEvent = function(me)
+{
+	var point = mxUtils.convertPoint(this.graph.container, me.getX(), me.getY());
+	var dx = Math.abs(point.x - this.first.x);
+	var dy = Math.abs(point.y - this.first.y);
+	var addPoint = this.waypoints != null || (this.mouseDownCounter > 1 &&
+			(dx > this.graph.tolerance || dy > this.graph.tolerance));
+
+	if (addPoint)
+	{
+		if (this.waypoints == null)
+		{
+			this.waypoints = [];
+		}
+		
+		var scale = this.graph.view.scale;
+		var point = new mxPoint(this.graph.snap(me.getGraphX() / scale) * scale,
+				this.graph.snap(me.getGraphY() / scale) * scale);
+		this.waypoints.push(point);
+	}
+};
+
+/**
+ * Function: mouseUp
+ * 
+ * Handles the event by inserting the new connection.
+ */
+mxConnectionHandler.prototype.mouseUp = function(sender, me)
+{
+	if (!me.isConsumed() && this.isConnecting())
+	{
+		if (this.waypointsEnabled && !this.isStopEvent(me))
+		{
+			this.addWaypointForEvent(me);
+			me.consume();
+			
+			return;
+		}
+		
+		// Inserts the edge if no validation error exists
+		if (this.error == null)
+		{
+			var source = (this.previous != null) ? this.previous.cell : null;
+			var target = null;
+			
+			if (this.constraintHandler.currentConstraint != null &&
+				this.constraintHandler.currentFocus != null)
+			{
+				target = this.constraintHandler.currentFocus.cell;
+			}
+			
+			if (target == null && this.currentState != null)
+			{
+				target = this.currentState.cell;
+			}
+			
+			this.connect(source, target, me.getEvent(), me.getCell());
+		}
+		else
+		{
+			// Selects the source terminal for self-references
+			if (this.previous != null && this.marker.validState != null &&
+				this.previous.cell == this.marker.validState.cell)
+			{
+				this.graph.selectCellForEvent(this.marker.source, evt);
+			}
+			
+			// Displays the error message if it is not an empty string,
+			// for empty error messages, the event is silently dropped
+			if (this.error.length > 0)
+			{
+				this.graph.validationAlert(this.error);
+			}
+		}
+		
+		// Redraws the connect icons and resets the handler state
+		this.destroyIcons();
+		me.consume();
+	}
+
+	if (this.first != null)
+	{
+		this.reset();
+	}
+};
+
+/**
+ * Function: reset
+ * 
+ * Resets the state of this handler.
+ */
+mxConnectionHandler.prototype.reset = function()
+{
+	if (this.shape != null)
+	{
+		this.shape.destroy();
+		this.shape = null;
+	}
+	
+	// Resets the cursor on the container
+	if (this.cursor != null && this.graph.container != null)
+	{
+		this.graph.container.style.cursor = '';
+	}
+	
+	this.destroyIcons();
+	this.marker.reset();
+	this.constraintHandler.reset();
+	this.originalPoint = null;
+	this.currentPoint = null;
+	this.edgeState = null;
+	this.previous = null;
+	this.error = null;
+	this.sourceConstraint = null;
+	this.mouseDownCounter = 0;
+	this.first = null;
+
+	this.fireEvent(new mxEventObject(mxEvent.RESET));
+};
+
+/**
+ * Function: drawPreview
+ * 
+ * Redraws the preview edge using the color and width returned by
+ * <getEdgeColor> and <getEdgeWidth>.
+ */
+mxConnectionHandler.prototype.drawPreview = function()
+{
+	this.updatePreview(this.error == null);
+	this.shape.redraw();
+};
+
+/**
+ * Function: getEdgeColor
+ * 
+ * Returns the color used to draw the preview edge. This returns green if
+ * there is no edge validation error and red otherwise.
+ * 
+ * Parameters:
+ * 
+ * valid - Boolean indicating if the color for a valid edge should be
+ * returned.
+ */
+mxConnectionHandler.prototype.updatePreview = function(valid)
+{
+	this.shape.strokewidth = this.getEdgeWidth(valid);
+	this.shape.stroke = this.getEdgeColor(valid);
+};
+
+/**
+ * Function: getEdgeColor
+ * 
+ * Returns the color used to draw the preview edge. This returns green if
+ * there is no edge validation error and red otherwise.
+ * 
+ * Parameters:
+ * 
+ * valid - Boolean indicating if the color for a valid edge should be
+ * returned.
+ */
+mxConnectionHandler.prototype.getEdgeColor = function(valid)
+{
+	return (valid) ? mxConstants.VALID_COLOR : mxConstants.INVALID_COLOR;
+};
+	
+/**
+ * Function: getEdgeWidth
+ * 
+ * Returns the width used to draw the preview edge. This returns 3 if
+ * there is no edge validation error and 1 otherwise.
+ * 
+ * Parameters:
+ * 
+ * valid - Boolean indicating if the width for a valid edge should be
+ * returned.
+ */
+mxConnectionHandler.prototype.getEdgeWidth = function(valid)
+{
+	return (valid) ? 3 : 1;
+};
+
+/**
+ * Function: connect
+ * 
+ * Connects the given source and target using a new edge. This
+ * implementation uses <createEdge> to create the edge.
+ * 
+ * Parameters:
+ * 
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ * evt - Mousedown event of the connect gesture.
+ * dropTarget - <mxCell> that represents the cell under the mouse when it was
+ * released.
+ */
+mxConnectionHandler.prototype.connect = function(source, target, evt, dropTarget)
+{
+	if (target != null || this.isCreateTarget(evt) || this.graph.allowDanglingEdges)
+	{
+		// Uses the common parent of source and target or
+		// the default parent to insert the edge
+		var model = this.graph.getModel();
+		var terminalInserted = false;
+		var edge = null;
+
+		model.beginUpdate();
+		try
+		{
+			if (source != null && target == null && !this.graph.isIgnoreTerminalEvent(evt) && this.isCreateTarget(evt))
+			{
+				target = this.createTargetVertex(evt, source);
+				
+				if (target != null)
+				{
+					dropTarget = this.graph.getDropTarget([target], evt, dropTarget);
+					terminalInserted = true;
+					
+					// Disables edges as drop targets if the target cell was created
+					// FIXME: Should not shift if vertex was aligned (same in Java)
+					if (dropTarget == null || !this.graph.getModel().isEdge(dropTarget))
+					{
+						var pstate = this.graph.getView().getState(dropTarget);
+						
+						if (pstate != null)
+						{
+							var tmp = model.getGeometry(target);
+							tmp.x -= pstate.origin.x;
+							tmp.y -= pstate.origin.y;
+						}
+					}
+					else
+					{
+						dropTarget = this.graph.getDefaultParent();
+					}
+						
+					this.graph.addCell(target, dropTarget);
+				}
+			}
+
+			var parent = this.graph.getDefaultParent();
+
+			if (source != null && target != null &&
+				model.getParent(source) == model.getParent(target) &&
+				model.getParent(model.getParent(source)) != model.getRoot())
+			{
+				parent = model.getParent(source);
+
+				if ((source.geometry != null && source.geometry.relative) &&
+					(target.geometry != null && target.geometry.relative))
+				{
+					parent = model.getParent(parent);
+				}
+			}
+			
+			// Uses the value of the preview edge state for inserting
+			// the new edge into the graph
+			var value = null;
+			var style = null;
+			
+			if (this.edgeState != null)
+			{
+				value = this.edgeState.cell.value;
+				style = this.edgeState.cell.style;
+			}
+
+			edge = this.insertEdge(parent, null, value, source, target, style);
+			
+			if (edge != null)
+			{
+				// Updates the connection constraints
+				this.graph.setConnectionConstraint(edge, source, true, this.sourceConstraint);
+				this.graph.setConnectionConstraint(edge, target, false, this.constraintHandler.currentConstraint);
+				
+				// Uses geometry of the preview edge state
+				if (this.edgeState != null)
+				{
+					model.setGeometry(edge, this.edgeState.cell.geometry);
+				}
+				
+				var parent = model.getParent(source);
+				
+				// Inserts edge before source
+				if (this.isInsertBefore(edge, source, target, evt, dropTarget))
+				{
+					var index = null;
+					var tmp = source;
+
+					while (tmp.parent != null && tmp.geometry != null &&
+						tmp.geometry.relative && tmp.parent != edge.parent)
+					{
+						tmp = this.graph.model.getParent(tmp);
+					}
+
+					if (tmp != null && tmp.parent != null && tmp.parent == edge.parent)
+					{
+						var index = tmp.parent.getIndex(tmp);
+						tmp.parent.insert(edge, index);
+					}
+				}
+				
+				// Makes sure the edge has a non-null, relative geometry
+				var geo = model.getGeometry(edge);
+
+				if (geo == null)
+				{
+					geo = new mxGeometry();
+					geo.relative = true;
+					
+					model.setGeometry(edge, geo);
+				}
+				
+				// Uses scaled waypoints in geometry
+				if (this.waypoints != null && this.waypoints.length > 0)
+				{
+					var s = this.graph.view.scale;
+					var tr = this.graph.view.translate;
+					geo.points = [];
+					
+					for (var i = 0; i < this.waypoints.length; i++)
+					{
+						var pt = this.waypoints[i];
+						geo.points.push(new mxPoint(pt.x / s - tr.x, pt.y / s - tr.y));
+					}
+				}
+
+				if (target == null)
+				{
+					var t = this.graph.view.translate;
+					var s = this.graph.view.scale;
+					var pt = (this.originalPoint != null) ?
+							new mxPoint(this.originalPoint.x / s - t.x, this.originalPoint.y / s - t.y) :
+						new mxPoint(this.currentPoint.x / s - t.x, this.currentPoint.y / s - t.y);
+					pt.x -= this.graph.panDx / this.graph.view.scale;
+					pt.y -= this.graph.panDy / this.graph.view.scale;
+					geo.setTerminalPoint(pt, false);
+				}
+				
+				this.fireEvent(new mxEventObject(mxEvent.CONNECT, 'cell', edge, 'terminal', target,
+					'event', evt, 'target', dropTarget, 'terminalInserted', terminalInserted));
+			}
+		}
+		catch (e)
+		{
+			mxLog.show();
+			mxLog.debug(e.message);
+		}
+		finally
+		{
+			model.endUpdate();
+		}
+		
+		if (this.select)
+		{
+			this.selectCells(edge, (terminalInserted) ? target : null);
+		}
+	}
+};
+
+/**
+ * Function: selectCells
+ * 
+ * Selects the given edge after adding a new connection. The target argument
+ * contains the target vertex if one has been inserted.
+ */
+mxConnectionHandler.prototype.selectCells = function(edge, target)
+{
+	this.graph.setSelectionCell(edge);
+};
+
+/**
+ * Function: insertEdge
+ * 
+ * Creates, inserts and returns the new edge for the given parameters. This
+ * implementation does only use <createEdge> if <factoryMethod> is defined,
+ * otherwise <mxGraph.insertEdge> will be used.
+ */
+mxConnectionHandler.prototype.insertEdge = function(parent, id, value, source, target, style)
+{
+	if (this.factoryMethod == null)
+	{
+		return this.graph.insertEdge(parent, id, value, source, target, style);
+	}
+	else
+	{
+		var edge = this.createEdge(value, source, target, style);
+		edge = this.graph.addEdge(edge, parent, source, target);
+		
+		return edge;
+	}
+};
+
+/**
+ * Function: createTargetVertex
+ * 
+ * Hook method for creating new vertices on the fly if no target was
+ * under the mouse. This is only called if <createTarget> is true and
+ * returns null.
+ * 
+ * Parameters:
+ * 
+ * evt - Mousedown event of the connect gesture.
+ * source - <mxCell> that represents the source terminal.
+ */
+mxConnectionHandler.prototype.createTargetVertex = function(evt, source)
+{
+	// Uses the first non-relative source
+	var geo = this.graph.getCellGeometry(source);
+	
+	while (geo != null && geo.relative)
+	{
+		source = this.graph.getModel().getParent(source);
+		geo = this.graph.getCellGeometry(source);
+	}
+	
+	var clone = this.graph.cloneCells([source])[0];
+	var geo = this.graph.getModel().getGeometry(clone);
+	
+	if (geo != null)
+	{
+		var t = this.graph.view.translate;
+		var s = this.graph.view.scale;
+		var point = new mxPoint(this.currentPoint.x / s - t.x, this.currentPoint.y / s - t.y);
+		geo.x = Math.round(point.x - geo.width / 2 - this.graph.panDx / s);
+		geo.y = Math.round(point.y - geo.height / 2 - this.graph.panDy / s);
+
+		// Aligns with source if within certain tolerance
+		var tol = this.getAlignmentTolerance();
+		
+		if (tol > 0)
+		{
+			var sourceState = this.graph.view.getState(source);
+			
+			if (sourceState != null)
+			{
+				var x = sourceState.x / s - t.x;
+				var y = sourceState.y / s - t.y;
+				
+				if (Math.abs(x - geo.x) <= tol)
+				{
+					geo.x = Math.round(x);
+				}
+				
+				if (Math.abs(y - geo.y) <= tol)
+				{
+					geo.y = Math.round(y);
+				}
+			}
+		}
+	}
+
+	return clone;		
+};
+
+/**
+ * Function: getAlignmentTolerance
+ * 
+ * Returns the tolerance for aligning new targets to sources. This returns the grid size / 2.
+ */
+mxConnectionHandler.prototype.getAlignmentTolerance = function(evt)
+{
+	return (this.graph.isGridEnabled()) ? this.graph.gridSize / 2 : this.graph.tolerance;
+};
+
+/**
+ * Function: createEdge
+ * 
+ * Creates and returns a new edge using <factoryMethod> if one exists. If
+ * no factory method is defined, then a new default edge is returned. The
+ * source and target arguments are informal, the actual connection is
+ * setup later by the caller of this function.
+ * 
+ * Parameters:
+ * 
+ * value - Value to be used for creating the edge.
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ * style - Optional style from the preview edge.
+ */
+mxConnectionHandler.prototype.createEdge = function(value, source, target, style)
+{
+	var edge = null;
+	
+	// Creates a new edge using the factoryMethod
+	if (this.factoryMethod != null)
+	{
+		edge = this.factoryMethod(source, target, style);
+	}
+	
+	if (edge == null)
+	{
+		edge = new mxCell(value || '');
+		edge.setEdge(true);
+		edge.setStyle(style);
+		
+		var geo = new mxGeometry();
+		geo.relative = true;
+		edge.setGeometry(geo);
+	}
+
+	return edge;
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the handler and all its resources and DOM nodes. This should be
+ * called on all instances. It is called automatically for the built-in
+ * instance created for each <mxGraph>.
+ */
+mxConnectionHandler.prototype.destroy = function()
+{
+	this.graph.removeMouseListener(this);
+	
+	if (this.shape != null)
+	{
+		this.shape.destroy();
+		this.shape = null;
+	}
+	
+	if (this.marker != null)
+	{
+		this.marker.destroy();
+		this.marker = null;
+	}
+
+	if (this.constraintHandler != null)
+	{
+		this.constraintHandler.destroy();
+		this.constraintHandler = null;
+	}
+
+	if (this.changeHandler != null)
+	{
+		this.graph.getModel().removeListener(this.changeHandler);
+		this.graph.getView().removeListener(this.changeHandler);
+		this.changeHandler = null;
+	}
+	
+	if (this.drillHandler != null)
+	{
+		this.graph.removeListener(this.drillHandler);
+		this.graph.getView().removeListener(this.drillHandler);
+		this.drillHandler = null;
+	}
+	
+	if (this.escapeHandler != null)
+	{
+		this.graph.removeListener(this.escapeHandler);
+		this.escapeHandler = null;
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/handler/mxConstraintHandler.js b/airavata-kubernetes/workflow-composer/src/js/handler/mxConstraintHandler.js
new file mode 100644
index 0000000..2ea07a2
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/handler/mxConstraintHandler.js
@@ -0,0 +1,520 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxConstraintHandler
+ *
+ * Handles constraints on connection targets. This class is in charge of
+ * showing fixed points when the mouse is over a vertex and handles constraints
+ * to establish new connections.
+ *
+ * Constructor: mxConstraintHandler
+ *
+ * Constructs an new constraint handler.
+ * 
+ * Parameters:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ * factoryMethod - Optional function to create the edge. The function takes
+ * the source and target <mxCell> as the first and second argument and
+ * returns the <mxCell> that represents the new edge.
+ */
+function mxConstraintHandler(graph)
+{
+	this.graph = graph;
+	
+	// Adds a graph model listener to update the current focus on changes
+	this.resetHandler = mxUtils.bind(this, function(sender, evt)
+	{
+		if (this.currentFocus != null && this.graph.view.getState(this.currentFocus.cell) == null)
+		{
+			this.reset();
+		}
+		else
+		{
+			this.redraw();
+		}
+	});
+	
+	this.graph.model.addListener(mxEvent.CHANGE, this.resetHandler);
+	this.graph.view.addListener(mxEvent.SCALE, this.resetHandler);
+	this.graph.addListener(mxEvent.ROOT, this.resetHandler);
+};
+
+/**
+ * Variable: pointImage
+ * 
+ * <mxImage> to be used as the image for fixed connection points.
+ */
+mxConstraintHandler.prototype.pointImage = new mxImage(mxClient.imageBasePath + '/point.gif', 5, 5);
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxConstraintHandler.prototype.graph = null;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies if events are handled. Default is true.
+ */
+mxConstraintHandler.prototype.enabled = true;
+
+/**
+ * Variable: highlightColor
+ * 
+ * Specifies the color for the highlight. Default is <mxConstants.DEFAULT_VALID_COLOR>.
+ */
+mxConstraintHandler.prototype.highlightColor = mxConstants.DEFAULT_VALID_COLOR;
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxConstraintHandler.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+	
+/**
+ * Function: setEnabled
+ * 
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ * 
+ * Parameters:
+ * 
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxConstraintHandler.prototype.setEnabled = function(enabled)
+{
+	this.enabled = enabled;
+};
+
+/**
+ * Function: reset
+ * 
+ * Resets the state of this handler.
+ */
+mxConstraintHandler.prototype.reset = function()
+{
+	if (this.focusIcons != null)
+	{
+		for (var i = 0; i < this.focusIcons.length; i++)
+		{
+			this.focusIcons[i].destroy();
+		}
+		
+		this.focusIcons = null;
+	}
+	
+	if (this.focusHighlight != null)
+	{
+		this.focusHighlight.destroy();
+		this.focusHighlight = null;
+	}
+	
+	this.currentConstraint = null;
+	this.currentFocusArea = null;
+	this.currentPoint = null;
+	this.currentFocus = null;
+	this.focusPoints = null;
+};
+
+/**
+ * Function: getTolerance
+ * 
+ * Returns the tolerance to be used for intersecting connection points. This
+ * implementation returns <mxGraph.tolerance>.
+ * 
+ * Parameters:
+ * 
+ * me - <mxMouseEvent> whose tolerance should be returned.
+ */
+mxConstraintHandler.prototype.getTolerance = function(me)
+{
+	return this.graph.getTolerance();
+};
+
+/**
+ * Function: getImageForConstraint
+ * 
+ * Returns the tolerance to be used for intersecting connection points.
+ */
+mxConstraintHandler.prototype.getImageForConstraint = function(state, constraint, point)
+{
+	return this.pointImage;
+};
+
+/**
+ * Function: isEventIgnored
+ * 
+ * Returns true if the given <mxMouseEvent> should be ignored in <update>. This
+ * implementation always returns false.
+ */
+mxConstraintHandler.prototype.isEventIgnored = function(me, source)
+{
+	return false;
+};
+
+/**
+ * Function: isStateIgnored
+ * 
+ * Returns true if the given state should be ignored. This always returns false.
+ */
+mxConstraintHandler.prototype.isStateIgnored = function(state, source)
+{
+	return false;
+};
+
+/**
+ * Function: destroyIcons
+ * 
+ * Destroys the <focusIcons> if they exist.
+ */
+mxConstraintHandler.prototype.destroyIcons = function()
+{
+	if (this.focusIcons != null)
+	{
+		for (var i = 0; i < this.focusIcons.length; i++)
+		{
+			this.focusIcons[i].destroy();
+		}
+		
+		this.focusIcons = null;
+		this.focusPoints = null;
+	}
+};
+
+/**
+ * Function: destroyFocusHighlight
+ * 
+ * Destroys the <focusHighlight> if one exists.
+ */
+mxConstraintHandler.prototype.destroyFocusHighlight = function()
+{
+	if (this.focusHighlight != null)
+	{
+		this.focusHighlight.destroy();
+		this.focusHighlight = null;
+	}
+};
+
+/**
+ * Function: isKeepFocusEvent
+ * 
+ * Returns true if the current focused state should not be changed for the given event.
+ * This returns true if shift and alt are pressed.
+ */
+mxConstraintHandler.prototype.isKeepFocusEvent = function(me)
+{
+	return mxEvent.isShiftDown(me.getEvent());
+};
+
+/**
+ * Function: getCellForEvent
+ * 
+ * Returns the cell for the given event.
+ */
+mxConstraintHandler.prototype.getCellForEvent = function(me, point)
+{
+	var cell = me.getCell();
+	
+	// Gets cell under actual point if different from event location
+	if (cell == null && point != null && (me.getGraphX() != point.x || me.getGraphY() != point.y))
+	{
+		cell = this.graph.getCellAt(point.x, point.y);
+	}
+	
+	// Uses connectable parent vertex if one exists
+	if (cell != null && !this.graph.isCellConnectable(cell))
+	{
+		var parent = this.graph.getModel().getParent(cell);
+		
+		if (this.graph.getModel().isVertex(parent) && this.graph.isCellConnectable(parent))
+		{
+			cell = parent;
+		}
+	}
+	
+	return cell;
+};
+
+/**
+ * Function: update
+ * 
+ * Updates the state of this handler based on the given <mxMouseEvent>.
+ * Source is a boolean indicating if the cell is a source or target.
+ */
+mxConstraintHandler.prototype.update = function(me, source, existingEdge, point)
+{
+	if (this.isEnabled() && !this.isEventIgnored(me))
+	{
+		// Lazy installation of mouseleave handler
+		if (this.mouseleaveHandler == null && this.graph.container != null)
+		{
+			this.mouseleaveHandler = mxUtils.bind(this, function()
+			{
+				this.reset();
+			});
+
+			mxEvent.addListener(this.graph.container, 'mouseleave', this.resetHandler);	
+		}
+		
+		var tol = this.getTolerance(me);
+		var x = (point != null) ? point.x : me.getGraphX();
+		var y = (point != null) ? point.y : me.getGraphY();
+		var grid = new mxRectangle(x - tol, y - tol, 2 * tol, 2 * tol);
+		var mouse = new mxRectangle(me.getGraphX() - tol, me.getGraphY() - tol, 2 * tol, 2 * tol);
+		var state = this.graph.view.getState(this.getCellForEvent(me, point));
+
+		// Keeps focus icons visible while over vertex bounds and no other cell under mouse or shift is pressed
+		if (!this.isKeepFocusEvent(me) && (this.currentFocusArea == null || this.currentFocus == null ||
+			(state != null) || !this.graph.getModel().isVertex(this.currentFocus.cell) ||
+			!mxUtils.intersects(this.currentFocusArea, mouse)) && (state != this.currentFocus))
+		{
+			this.currentFocusArea = null;
+			this.currentFocus = null;
+			this.setFocus(me, state, source);
+		}
+
+		this.currentConstraint = null;
+		this.currentPoint = null;
+		var minDistSq = null;
+		
+		if (this.focusIcons != null && this.constraints != null &&
+			(state == null || this.currentFocus == state))
+		{
+			var cx = mouse.getCenterX();
+			var cy = mouse.getCenterY();
+			
+			for (var i = 0; i < this.focusIcons.length; i++)
+			{
+				var dx = cx - this.focusIcons[i].bounds.getCenterX();
+				var dy = cy - this.focusIcons[i].bounds.getCenterY();
+				var tmp = dx * dx + dy * dy;
+				
+				if ((this.intersects(this.focusIcons[i], mouse, source, existingEdge) || (point != null &&
+					this.intersects(this.focusIcons[i], grid, source, existingEdge))) &&
+					(minDistSq == null || tmp < minDistSq))
+				{
+					this.currentConstraint = this.constraints[i];
+					this.currentPoint = this.focusPoints[i];
+					minDistSq = tmp;
+					
+					var tmp = this.focusIcons[i].bounds.clone();
+					tmp.grow(mxConstants.HIGHLIGHT_SIZE);
+					
+					if (mxClient.IS_IE)
+					{
+						tmp.grow(1);
+						tmp.width -= 1;
+						tmp.height -= 1;
+					}
+					
+					if (this.focusHighlight == null)
+					{
+						var hl = this.createHighlightShape();
+						hl.dialect = (this.graph.dialect == mxConstants.DIALECT_SVG) ?
+								mxConstants.DIALECT_SVG : mxConstants.DIALECT_VML;
+						hl.pointerEvents = false;
+
+						hl.init(this.graph.getView().getOverlayPane());
+						this.focusHighlight = hl;
+						
+						var getState = mxUtils.bind(this, function()
+						{
+							return (this.currentFocus != null) ? this.currentFocus : state;
+						});
+	
+						mxEvent.redirectMouseEvents(hl.node, this.graph, getState);
+					}
+
+					this.focusHighlight.bounds = tmp;
+					this.focusHighlight.redraw();
+				}
+			}
+		}
+		
+		if (this.currentConstraint == null)
+		{
+			this.destroyFocusHighlight();
+		}
+	}
+	else
+	{
+		this.currentConstraint = null;
+		this.currentFocus = null;
+		this.currentPoint = null;
+	}
+};
+
+/**
+ * Function: redraw
+ * 
+ * Transfers the focus to the given state as a source or target terminal. If
+ * the handler is not enabled then the outline is painted, but the constraints
+ * are ignored.
+ */
+mxConstraintHandler.prototype.redraw = function()
+{
+	if (this.currentFocus != null && this.constraints != null && this.focusIcons != null)
+	{
+		var state = this.graph.view.getState(this.currentFocus.cell);
+		this.currentFocus = state;
+		this.currentFocusArea = new mxRectangle(state.x, state.y, state.width, state.height);
+		
+		for (var i = 0; i < this.constraints.length; i++)
+		{
+			var cp = this.graph.getConnectionPoint(state, this.constraints[i]);
+			var img = this.getImageForConstraint(state, this.constraints[i], cp);
+
+			var bounds = new mxRectangle(Math.round(cp.x - img.width / 2),
+				Math.round(cp.y - img.height / 2), img.width, img.height);
+			this.focusIcons[i].bounds = bounds;
+			this.focusIcons[i].redraw();
+			this.currentFocusArea.add(this.focusIcons[i].bounds);
+			this.focusPoints[i] = cp;
+		}
+	}	
+};
+
+/**
+ * Function: setFocus
+ * 
+ * Transfers the focus to the given state as a source or target terminal. If
+ * the handler is not enabled then the outline is painted, but the constraints
+ * are ignored.
+ */
+mxConstraintHandler.prototype.setFocus = function(me, state, source)
+{
+	this.constraints = (state != null && !this.isStateIgnored(state, source) &&
+		this.graph.isCellConnectable(state.cell)) ? ((this.isEnabled()) ?
+		(this.graph.getAllConnectionConstraints(state, source) || []) : []) : null;
+
+	// Only uses cells which have constraints
+	if (this.constraints != null)
+	{
+		this.currentFocus = state;
+		this.currentFocusArea = new mxRectangle(state.x, state.y, state.width, state.height);
+		
+		if (this.focusIcons != null)
+		{
+			for (var i = 0; i < this.focusIcons.length; i++)
+			{
+				this.focusIcons[i].destroy();
+			}
+			
+			this.focusIcons = null;
+			this.focusPoints = null;
+		}
+		
+		this.focusPoints = [];
+		this.focusIcons = [];
+		
+		for (var i = 0; i < this.constraints.length; i++)
+		{
+			var cp = this.graph.getConnectionPoint(state, this.constraints[i]);
+			var img = this.getImageForConstraint(state, this.constraints[i], cp);
+
+			var src = img.src;
+			var bounds = new mxRectangle(Math.round(cp.x - img.width / 2),
+				Math.round(cp.y - img.height / 2), img.width, img.height);
+			var icon = new mxImageShape(bounds, src);
+			icon.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
+					mxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG;
+			icon.preserveImageAspect = false;
+			icon.init(this.graph.getView().getDecoratorPane());
+			
+			// Fixes lost event tracking for images in quirks / IE8 standards
+			if (mxClient.IS_QUIRKS || document.documentMode == 8)
+			{
+				mxEvent.addListener(icon.node, 'dragstart', function(evt)
+				{
+					mxEvent.consume(evt);
+					
+					return false;
+				});
+			}
+			
+			// Move the icon behind all other overlays
+			if (icon.node.previousSibling != null)
+			{
+				icon.node.parentNode.insertBefore(icon.node, icon.node.parentNode.firstChild);
+			}
+
+			var getState = mxUtils.bind(this, function()
+			{
+				return (this.currentFocus != null) ? this.currentFocus : state;
+			});
+			
+			icon.redraw();
+
+			mxEvent.redirectMouseEvents(icon.node, this.graph, getState);
+			this.currentFocusArea.add(icon.bounds);
+			this.focusIcons.push(icon);
+			this.focusPoints.push(cp);
+		}
+		
+		this.currentFocusArea.grow(this.getTolerance(me));
+	}
+	else
+	{
+		this.destroyIcons();
+		this.destroyFocusHighlight();
+	}
+};
+
+/**
+ * Function: createHighlightShape
+ * 
+ * Create the shape used to paint the highlight.
+ * 
+ * Returns true if the given icon intersects the given point.
+ */
+mxConstraintHandler.prototype.createHighlightShape = function()
+{
+	var hl = new mxRectangleShape(null, this.highlightColor, this.highlightColor, mxConstants.HIGHLIGHT_STROKEWIDTH);
+	hl.opacity = mxConstants.HIGHLIGHT_OPACITY;
+	
+	return hl;
+};
+
+/**
+ * Function: intersects
+ * 
+ * Returns true if the given icon intersects the given rectangle.
+ */
+mxConstraintHandler.prototype.intersects = function(icon, mouse, source, existingEdge)
+{
+	return mxUtils.intersects(icon.bounds, mouse);
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroy this handler.
+ */
+mxConstraintHandler.prototype.destroy = function()
+{
+	this.reset();
+	
+	if (this.resetHandler != null)
+	{
+		this.graph.model.removeListener(this.resetHandler);
+		this.graph.view.removeListener(this.resetHandler);
+		this.graph.removeListener(this.resetHandler);
+		this.resetHandler = null;
+	}
+	
+	if (this.mouseleaveHandler != null && this.graph.container != null)
+	{
+		mxEvent.removeListener(this.graph.container, 'mouseleave', this.mouseleaveHandler);
+		this.mouseleaveHandler = null;
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/handler/mxEdgeHandler.js b/airavata-kubernetes/workflow-composer/src/js/handler/mxEdgeHandler.js
new file mode 100644
index 0000000..8dd7761
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/handler/mxEdgeHandler.js
@@ -0,0 +1,2409 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxEdgeHandler
+ *
+ * Graph event handler that reconnects edges and modifies control points and
+ * the edge label location. Uses <mxTerminalMarker> for finding and
+ * highlighting new source and target vertices. This handler is automatically
+ * created in <mxGraph.createHandler> for each selected edge.
+ * 
+ * To enable adding/removing control points, the following code can be used:
+ * 
+ * (code)
+ * mxEdgeHandler.prototype.addEnabled = true;
+ * mxEdgeHandler.prototype.removeEnabled = true;
+ * (end)
+ * 
+ * Note: This experimental feature is not recommended for production use.
+ * 
+ * Constructor: mxEdgeHandler
+ *
+ * Constructs an edge handler for the specified <mxCellState>.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> of the cell to be handled.
+ */
+function mxEdgeHandler(state)
+{
+	if (state != null)
+	{
+		this.state = state;
+		this.init();
+		
+		// Handles escape keystrokes
+		this.escapeHandler = mxUtils.bind(this, function(sender, evt)
+		{
+			this.reset();
+		});
+		
+		this.state.view.graph.addListener(mxEvent.ESCAPE, this.escapeHandler);
+	}
+};
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxEdgeHandler.prototype.graph = null;
+
+/**
+ * Variable: state
+ * 
+ * Reference to the <mxCellState> being modified.
+ */
+mxEdgeHandler.prototype.state = null;
+
+/**
+ * Variable: marker
+ * 
+ * Holds the <mxTerminalMarker> which is used for highlighting terminals.
+ */
+mxEdgeHandler.prototype.marker = null;
+
+/**
+ * Variable: constraintHandler
+ * 
+ * Holds the <mxConstraintHandler> used for drawing and highlighting
+ * constraints.
+ */
+mxEdgeHandler.prototype.constraintHandler = null;
+
+/**
+ * Variable: error
+ * 
+ * Holds the current validation error while a connection is being changed.
+ */
+mxEdgeHandler.prototype.error = null;
+
+/**
+ * Variable: shape
+ * 
+ * Holds the <mxShape> that represents the preview edge.
+ */
+mxEdgeHandler.prototype.shape = null;
+
+/**
+ * Variable: bends
+ * 
+ * Holds the <mxShapes> that represent the points.
+ */
+mxEdgeHandler.prototype.bends = null;
+
+/**
+ * Variable: labelShape
+ * 
+ * Holds the <mxShape> that represents the label position.
+ */
+mxEdgeHandler.prototype.labelShape = null;
+
+/**
+ * Variable: cloneEnabled
+ * 
+ * Specifies if cloning by control-drag is enabled. Default is true.
+ */
+mxEdgeHandler.prototype.cloneEnabled = true;
+
+/**
+ * Variable: addEnabled
+ * 
+ * Specifies if adding bends by shift-click is enabled. Default is false.
+ * Note: This experimental feature is not recommended for production use.
+ */
+mxEdgeHandler.prototype.addEnabled = false;
+
+/**
+ * Variable: removeEnabled
+ * 
+ * Specifies if removing bends by shift-click is enabled. Default is false.
+ * Note: This experimental feature is not recommended for production use.
+ */
+mxEdgeHandler.prototype.removeEnabled = false;
+
+/**
+ * Variable: dblClickRemoveEnabled
+ * 
+ * Specifies if removing bends by double click is enabled. Default is false.
+ */
+mxEdgeHandler.prototype.dblClickRemoveEnabled = false;
+
+/**
+ * Variable: mergeRemoveEnabled
+ * 
+ * Specifies if removing bends by dropping them on other bends is enabled.
+ * Default is false.
+ */
+mxEdgeHandler.prototype.mergeRemoveEnabled = false;
+
+/**
+ * Variable: straightRemoveEnabled
+ * 
+ * Specifies if removing bends by creating straight segments should be enabled.
+ * If enabled, this can be overridden by holding down the alt key while moving.
+ * Default is false.
+ */
+mxEdgeHandler.prototype.straightRemoveEnabled = false;
+
+/**
+ * Variable: virtualBendsEnabled
+ * 
+ * Specifies if virtual bends should be added in the center of each
+ * segments. These bends can then be used to add new waypoints.
+ * Default is false.
+ */
+mxEdgeHandler.prototype.virtualBendsEnabled = false;
+
+/**
+ * Variable: virtualBendOpacity
+ * 
+ * Opacity to be used for virtual bends (see <virtualBendsEnabled>).
+ * Default is 20.
+ */
+mxEdgeHandler.prototype.virtualBendOpacity = 20;
+
+/**
+ * Variable: parentHighlightEnabled
+ * 
+ * Specifies if the parent should be highlighted if a child cell is selected.
+ * Default is false.
+ */
+mxEdgeHandler.prototype.parentHighlightEnabled = false;
+
+/**
+ * Variable: preferHtml
+ * 
+ * Specifies if bends should be added to the graph container. This is updated
+ * in <init> based on whether the edge or one of its terminals has an HTML
+ * label in the container.
+ */
+mxEdgeHandler.prototype.preferHtml = false;
+
+/**
+ * Variable: allowHandleBoundsCheck
+ * 
+ * Specifies if the bounds of handles should be used for hit-detection in IE
+ * Default is true.
+ */
+mxEdgeHandler.prototype.allowHandleBoundsCheck = true;
+
+/**
+ * Variable: snapToTerminals
+ * 
+ * Specifies if waypoints should snap to the routing centers of terminals.
+ * Default is false.
+ */
+mxEdgeHandler.prototype.snapToTerminals = false;
+
+/**
+ * Variable: handleImage
+ * 
+ * Optional <mxImage> to be used as handles. Default is null.
+ */
+mxEdgeHandler.prototype.handleImage = null;
+
+/**
+ * Variable: tolerance
+ * 
+ * Optional tolerance for hit-detection in <getHandleForEvent>. Default is 0.
+ */
+mxEdgeHandler.prototype.tolerance = 0;
+
+/**
+ * Variable: outlineConnect
+ * 
+ * Specifies if connections to the outline of a highlighted target should be
+ * enabled. This will allow to place the connection point along the outline of
+ * the highlighted target. Default is false.
+ */
+mxEdgeHandler.prototype.outlineConnect = false;
+
+/**
+ * Variable: manageLabelHandle
+ * 
+ * Specifies if the label handle should be moved if it intersects with another
+ * handle. Uses <checkLabelHandle> for checking and moving. Default is false.
+ */
+mxEdgeHandler.prototype.manageLabelHandle = false;
+
+/**
+ * Function: init
+ * 
+ * Initializes the shapes required for this edge handler.
+ */
+mxEdgeHandler.prototype.init = function()
+{
+	this.graph = this.state.view.graph;
+	this.marker = this.createMarker();
+	this.constraintHandler = new mxConstraintHandler(this.graph);
+	
+	// Clones the original points from the cell
+	// and makes sure at least one point exists
+	this.points = [];
+	
+	// Uses the absolute points of the state
+	// for the initial configuration and preview
+	this.abspoints = this.getSelectionPoints(this.state);
+	this.shape = this.createSelectionShape(this.abspoints);
+	this.shape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
+		mxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG;
+	this.shape.init(this.graph.getView().getOverlayPane());
+	this.shape.pointerEvents = false;
+	this.shape.setCursor(mxConstants.CURSOR_MOVABLE_EDGE);
+	mxEvent.redirectMouseEvents(this.shape.node, this.graph, this.state);
+
+	// Updates preferHtml
+	this.preferHtml = this.state.text != null &&
+		this.state.text.node.parentNode == this.graph.container;
+	
+	if (!this.preferHtml)
+	{
+		// Checks source terminal
+		var sourceState = this.state.getVisibleTerminalState(true);
+		
+		if (sourceState != null)
+		{
+			this.preferHtml = sourceState.text != null &&
+				sourceState.text.node.parentNode == this.graph.container;
+		}
+		
+		if (!this.preferHtml)
+		{
+			// Checks target terminal
+			var targetState = this.state.getVisibleTerminalState(false);
+			
+			if (targetState != null)
+			{
+				this.preferHtml = targetState.text != null &&
+				targetState.text.node.parentNode == this.graph.container;
+			}
+		}
+	}
+	
+	// Adds highlight for parent group
+	if (this.parentHighlightEnabled)
+	{
+		var parent = this.graph.model.getParent(this.state.cell);
+		
+		if (this.graph.model.isVertex(parent))
+		{
+			var pstate = this.graph.view.getState(parent);
+			
+			if (pstate != null)
+			{
+				this.parentHighlight = this.createParentHighlightShape(pstate);
+				// VML dialect required here for event transparency in IE
+				this.parentHighlight.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+				this.parentHighlight.pointerEvents = false;
+				this.parentHighlight.rotation = Number(pstate.style[mxConstants.STYLE_ROTATION] || '0');
+				this.parentHighlight.init(this.graph.getView().getOverlayPane());
+			}
+		}
+	}
+	
+	// Creates bends for the non-routed absolute points
+	// or bends that don't correspond to points
+	if (this.graph.getSelectionCount() < mxGraphHandler.prototype.maxCells ||
+		mxGraphHandler.prototype.maxCells <= 0)
+	{
+		this.bends = this.createBends();
+
+		if (this.isVirtualBendsEnabled())
+		{
+			this.virtualBends = this.createVirtualBends();
+		}
+	}
+
+	// Adds a rectangular handle for the label position
+	this.label = new mxPoint(this.state.absoluteOffset.x, this.state.absoluteOffset.y);
+	this.labelShape = this.createLabelHandleShape();
+	this.initBend(this.labelShape);
+	this.labelShape.setCursor(mxConstants.CURSOR_LABEL_HANDLE);
+	
+	this.customHandles = this.createCustomHandles();
+	
+	this.redraw();
+};
+
+/**
+ * Function: createCustomHandles
+ * 
+ * Returns an array of custom handles. This implementation returns null.
+ */
+mxEdgeHandler.prototype.createCustomHandles = function()
+{
+	return null;
+};
+
+/**
+ * Function: isVirtualBendsEnabled
+ * 
+ * Returns true if virtual bends should be added. This returns true if
+ * <virtualBendsEnabled> is true and the current style allows and
+ * renders custom waypoints.
+ */
+mxEdgeHandler.prototype.isVirtualBendsEnabled = function(evt)
+{
+	return this.virtualBendsEnabled && (this.state.style[mxConstants.STYLE_EDGE] == null ||
+			this.state.style[mxConstants.STYLE_EDGE] == mxConstants.NONE ||
+			this.state.style[mxConstants.STYLE_NOEDGESTYLE] == 1)  &&
+			mxUtils.getValue(this.state.style, mxConstants.STYLE_SHAPE, null) != 'arrow';
+};
+
+/**
+ * Function: isAddPointEvent
+ * 
+ * Returns true if the given event is a trigger to add a new point. This
+ * implementation returns true if shift is pressed.
+ */
+mxEdgeHandler.prototype.isAddPointEvent = function(evt)
+{
+	return mxEvent.isShiftDown(evt);
+};
+
+/**
+ * Function: isRemovePointEvent
+ * 
+ * Returns true if the given event is a trigger to remove a point. This
+ * implementation returns true if shift is pressed.
+ */
+mxEdgeHandler.prototype.isRemovePointEvent = function(evt)
+{
+	return mxEvent.isShiftDown(evt);
+};
+
+/**
+ * Function: getSelectionPoints
+ * 
+ * Returns the list of points that defines the selection stroke.
+ */
+mxEdgeHandler.prototype.getSelectionPoints = function(state)
+{
+	return state.absolutePoints;
+};
+
+/**
+ * Function: createSelectionShape
+ * 
+ * Creates the shape used to draw the selection border.
+ */
+mxEdgeHandler.prototype.createParentHighlightShape = function(bounds)
+{
+	var shape = new mxRectangleShape(bounds, null, this.getSelectionColor());
+	shape.strokewidth = this.getSelectionStrokeWidth();
+	shape.isDashed = this.isSelectionDashed();
+	
+	return shape;
+};
+
+/**
+ * Function: createSelectionShape
+ * 
+ * Creates the shape used to draw the selection border.
+ */
+mxEdgeHandler.prototype.createSelectionShape = function(points)
+{
+	var shape = new this.state.shape.constructor();
+	shape.outline = true;
+	shape.apply(this.state);
+	
+	shape.isDashed = this.isSelectionDashed();
+	shape.stroke = this.getSelectionColor();
+	shape.isShadow = false;
+	
+	return shape;
+};
+
+/**
+ * Function: getSelectionColor
+ * 
+ * Returns <mxConstants.EDGE_SELECTION_COLOR>.
+ */
+mxEdgeHandler.prototype.getSelectionColor = function()
+{
+	return mxConstants.EDGE_SELECTION_COLOR;
+};
+
+/**
+ * Function: getSelectionStrokeWidth
+ * 
+ * Returns <mxConstants.EDGE_SELECTION_STROKEWIDTH>.
+ */
+mxEdgeHandler.prototype.getSelectionStrokeWidth = function()
+{
+	return mxConstants.EDGE_SELECTION_STROKEWIDTH;
+};
+
+/**
+ * Function: isSelectionDashed
+ * 
+ * Returns <mxConstants.EDGE_SELECTION_DASHED>.
+ */
+mxEdgeHandler.prototype.isSelectionDashed = function()
+{
+	return mxConstants.EDGE_SELECTION_DASHED;
+};
+
+/**
+ * Function: isConnectableCell
+ * 
+ * Returns true if the given cell is connectable. This is a hook to
+ * disable floating connections. This implementation returns true.
+ */
+mxEdgeHandler.prototype.isConnectableCell = function(cell)
+{
+	return true;
+};
+
+/**
+ * Function: getCellAt
+ * 
+ * Creates and returns the <mxCellMarker> used in <marker>.
+ */
+mxEdgeHandler.prototype.getCellAt = function(x, y)
+{
+	return (!this.outlineConnect) ? this.graph.getCellAt(x, y) : null;
+};
+
+/**
+ * Function: createMarker
+ * 
+ * Creates and returns the <mxCellMarker> used in <marker>.
+ */
+mxEdgeHandler.prototype.createMarker = function()
+{
+	var marker = new mxCellMarker(this.graph);
+	var self = this; // closure
+
+	// Only returns edges if they are connectable and never returns
+	// the edge that is currently being modified
+	marker.getCell = function(me)
+	{
+		var cell = mxCellMarker.prototype.getCell.apply(this, arguments);
+
+		// Checks for cell at preview point (with grid)
+		if ((cell == self.state.cell || cell == null) && self.currentPoint != null)
+		{
+			cell = self.graph.getCellAt(self.currentPoint.x, self.currentPoint.y);
+		}
+		
+		// Uses connectable parent vertex if one exists
+		if (cell != null && !this.graph.isCellConnectable(cell))
+		{
+			var parent = this.graph.getModel().getParent(cell);
+			
+			if (this.graph.getModel().isVertex(parent) && this.graph.isCellConnectable(parent))
+			{
+				cell = parent;
+			}
+		}
+		
+		var model = self.graph.getModel();
+		
+		if ((this.graph.isSwimlane(cell) && self.currentPoint != null &&
+			this.graph.hitsSwimlaneContent(cell, self.currentPoint.x, self.currentPoint.y)) ||
+			(!self.isConnectableCell(cell)) || (cell == self.state.cell ||
+			(cell != null && !self.graph.connectableEdges && model.isEdge(cell))) ||
+			model.isAncestor(self.state.cell, cell))
+		{
+			cell = null;
+		}
+		
+		if (!this.graph.isCellConnectable(cell))
+		{
+			cell = null;
+		}
+		
+		return cell;
+	};
+
+	// Sets the highlight color according to validateConnection
+	marker.isValidState = function(state)
+	{
+		var model = self.graph.getModel();
+		var other = self.graph.view.getTerminalPort(state,
+			self.graph.view.getState(model.getTerminal(self.state.cell,
+			!self.isSource)), !self.isSource);
+		var otherCell = (other != null) ? other.cell : null;
+		var source = (self.isSource) ? state.cell : otherCell;
+		var target = (self.isSource) ? otherCell : state.cell;
+		
+		// Updates the error message of the handler
+		self.error = self.validateConnection(source, target);
+
+		return self.error == null;
+	};
+	
+	return marker;
+};
+
+/**
+ * Function: validateConnection
+ * 
+ * Returns the error message or an empty string if the connection for the
+ * given source, target pair is not valid. Otherwise it returns null. This
+ * implementation uses <mxGraph.getEdgeValidationError>.
+ * 
+ * Parameters:
+ * 
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ */
+mxEdgeHandler.prototype.validateConnection = function(source, target)
+{
+	return this.graph.getEdgeValidationError(this.state.cell, source, target);
+};
+
+/**
+ * Function: createBends
+ * 
+ * Creates and returns the bends used for modifying the edge. This is
+ * typically an array of <mxRectangleShapes>.
+ */
+ mxEdgeHandler.prototype.createBends = function()
+ {
+	var cell = this.state.cell;
+	var bends = [];
+
+	for (var i = 0; i < this.abspoints.length; i++)
+	{
+		if (this.isHandleVisible(i))
+		{
+			var source = i == 0;
+			var target = i == this.abspoints.length - 1;
+			var terminal = source || target;
+
+			if (terminal || this.graph.isCellBendable(cell))
+			{
+				(mxUtils.bind(this, function(index)
+				{
+					var bend = this.createHandleShape(index);
+					this.initBend(bend, mxUtils.bind(this, mxUtils.bind(this, function()
+					{
+						if (this.dblClickRemoveEnabled)
+						{
+							this.removePoint(this.state, index);
+						}
+					})));
+	
+					if (this.isHandleEnabled(i))
+					{
+						bend.setCursor((terminal) ? mxConstants.CURSOR_TERMINAL_HANDLE : mxConstants.CURSOR_BEND_HANDLE);
+					}
+					
+					bends.push(bend);
+				
+					if (!terminal)
+					{
+						this.points.push(new mxPoint(0,0));
+						bend.node.style.visibility = 'hidden';
+					}
+				}))(i);
+			}
+		}
+	}
+
+	return bends;
+};
+
+/**
+ * Function: createVirtualBends
+ * 
+ * Creates and returns the bends used for modifying the edge. This is
+ * typically an array of <mxRectangleShapes>.
+ */
+ mxEdgeHandler.prototype.createVirtualBends = function()
+ {
+	var cell = this.state.cell;
+	var last = this.abspoints[0];
+	var bends = [];
+
+	if (this.graph.isCellBendable(cell))
+	{
+		for (var i = 1; i < this.abspoints.length; i++)
+		{
+			(mxUtils.bind(this, function(bend)
+			{
+				this.initBend(bend);
+				bend.setCursor(mxConstants.CURSOR_VIRTUAL_BEND_HANDLE);
+				bends.push(bend);
+			}))(this.createHandleShape());
+		}
+	}
+
+	return bends;
+};
+
+/**
+ * Function: isHandleEnabled
+ * 
+ * Creates the shape used to display the given bend.
+ */
+mxEdgeHandler.prototype.isHandleEnabled = function(index)
+{
+	return true;
+};
+
+/**
+ * Function: isHandleVisible
+ * 
+ * Returns true if the handle at the given index is visible.
+ */
+mxEdgeHandler.prototype.isHandleVisible = function(index)
+{
+	var source = this.state.getVisibleTerminalState(true);
+	var target = this.state.getVisibleTerminalState(false);
+	var geo = this.graph.getCellGeometry(this.state.cell);
+	var edgeStyle = (geo != null) ? this.graph.view.getEdgeStyle(this.state, geo.points, source, target) : null;
+
+	return edgeStyle != mxEdgeStyle.EntityRelation || index == 0 || index == this.abspoints.length - 1;
+};
+
+/**
+ * Function: createHandleShape
+ * 
+ * Creates the shape used to display the given bend. Note that the index may be
+ * null for special cases, such as when called from
+ * <mxElbowEdgeHandler.createVirtualBend>. Only images and rectangles should be
+ * returned if support for HTML labels with not foreign objects is required.
+ * Index if null for virtual handles.
+ */
+mxEdgeHandler.prototype.createHandleShape = function(index)
+{
+	if (this.handleImage != null)
+	{
+		var shape = new mxImageShape(new mxRectangle(0, 0, this.handleImage.width, this.handleImage.height), this.handleImage.src);
+		
+		// Allows HTML rendering of the images
+		shape.preserveImageAspect = false;
+
+		return shape;
+	}
+	else
+	{
+		var s = mxConstants.HANDLE_SIZE;
+		
+		if (this.preferHtml)
+		{
+			s -= 1;
+		}
+		
+		return new mxRectangleShape(new mxRectangle(0, 0, s, s), mxConstants.HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR);
+	}
+};
+
+/**
+ * Function: createLabelHandleShape
+ * 
+ * Creates the shape used to display the the label handle.
+ */
+mxEdgeHandler.prototype.createLabelHandleShape = function()
+{
+	if (this.labelHandleImage != null)
+	{
+		var shape = new mxImageShape(new mxRectangle(0, 0, this.labelHandleImage.width, this.labelHandleImage.height), this.labelHandleImage.src);
+		
+		// Allows HTML rendering of the images
+		shape.preserveImageAspect = false;
+
+		return shape;
+	}
+	else
+	{
+		var s = mxConstants.LABEL_HANDLE_SIZE;
+		return new mxRectangleShape(new mxRectangle(0, 0, s, s), mxConstants.LABEL_HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR);
+	}
+};
+
+/**
+ * Function: initBend
+ * 
+ * Helper method to initialize the given bend.
+ * 
+ * Parameters:
+ * 
+ * bend - <mxShape> that represents the bend to be initialized.
+ */
+mxEdgeHandler.prototype.initBend = function(bend, dblClick)
+{
+	if (this.preferHtml)
+	{
+		bend.dialect = mxConstants.DIALECT_STRICTHTML;
+		bend.init(this.graph.container);
+	}
+	else
+	{
+		bend.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
+			mxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG;
+		bend.init(this.graph.getView().getOverlayPane());
+	}
+
+	mxEvent.redirectMouseEvents(bend.node, this.graph, this.state,
+			null, null, null, dblClick);
+	
+	// Fixes lost event tracking for images in quirks / IE8 standards
+	if (mxClient.IS_QUIRKS || document.documentMode == 8)
+	{
+		mxEvent.addListener(bend.node, 'dragstart', function(evt)
+		{
+			mxEvent.consume(evt);
+			
+			return false;
+		});
+	}
+	
+	if (mxClient.IS_TOUCH)
+	{
+		bend.node.setAttribute('pointer-events', 'none');
+	}
+};
+
+/**
+ * Function: getHandleForEvent
+ * 
+ * Returns the index of the handle for the given event.
+ */
+mxEdgeHandler.prototype.getHandleForEvent = function(me)
+{
+	// Connection highlight may consume events before they reach sizer handle
+	var tol = (!mxEvent.isMouseEvent(me.getEvent())) ? this.tolerance : 1;
+	var hit = (this.allowHandleBoundsCheck && (mxClient.IS_IE || tol > 0)) ?
+		new mxRectangle(me.getGraphX() - tol, me.getGraphY() - tol, 2 * tol, 2 * tol) : null;
+	var minDistSq = null;
+	var result = null;
+
+	function checkShape(shape)
+	{
+		if (shape != null && shape.node.style.display != 'none' && shape.node.style.visibility != 'hidden' &&
+			(me.isSource(shape) || (hit != null && mxUtils.intersects(shape.bounds, hit))))
+		{
+			var dx = me.getGraphX() - shape.bounds.getCenterX();
+			var dy = me.getGraphY() - shape.bounds.getCenterY();
+			var tmp = dx * dx + dy * dy;
+			
+			if (minDistSq == null || tmp <= minDistSq)
+			{
+				minDistSq = tmp;
+			
+				return true;
+			}
+		}
+		
+		return false;
+	}
+	
+	if (this.customHandles != null && this.isCustomHandleEvent(me))
+	{
+		// Inverse loop order to match display order
+		for (var i = this.customHandles.length - 1; i >= 0; i--)
+		{
+			if (checkShape(this.customHandles[i].shape))
+			{
+				// LATER: Return reference to active shape
+				return mxEvent.CUSTOM_HANDLE - i;
+			}
+		}
+	}
+
+	if (me.isSource(this.state.text) || checkShape(this.labelShape))
+	{
+		result = mxEvent.LABEL_HANDLE;
+	}
+	
+	if (this.bends != null)
+	{
+		for (var i = 0; i < this.bends.length; i++)
+		{
+			if (checkShape(this.bends[i]))
+			{
+				result = i;
+			}
+		}
+	}
+	
+	if (this.virtualBends != null && this.isAddVirtualBendEvent(me))
+	{
+		for (var i = 0; i < this.virtualBends.length; i++)
+		{
+			if (checkShape(this.virtualBends[i]))
+			{
+				result = mxEvent.VIRTUAL_HANDLE - i;
+			}
+		}
+	}
+
+	return result;
+};
+
+/**
+ * Function: isAddVirtualBendEvent
+ * 
+ * Returns true if the given event allows virtual bends to be added. This
+ * implementation returns true.
+ */
+mxEdgeHandler.prototype.isAddVirtualBendEvent = function(me)
+{
+	return true;
+};
+
+/**
+ * Function: isCustomHandleEvent
+ * 
+ * Returns true if the given event allows custom handles to be changed. This
+ * implementation returns true.
+ */
+mxEdgeHandler.prototype.isCustomHandleEvent = function(me)
+{
+	return true;
+};
+
+/**
+ * Function: mouseDown
+ * 
+ * Handles the event by checking if a special element of the handler
+ * was clicked, in which case the index parameter is non-null. The
+ * indices may be one of <LABEL_HANDLE> or the number of the respective
+ * control point. The source and target points are used for reconnecting
+ * the edge.
+ */
+mxEdgeHandler.prototype.mouseDown = function(sender, me)
+{
+	var handle = this.getHandleForEvent(me);
+	
+	if (this.bends != null && this.bends[handle] != null)
+	{
+		var b = this.bends[handle].bounds;
+		this.snapPoint = new mxPoint(b.getCenterX(), b.getCenterY());
+	}
+	
+	if (this.addEnabled && handle == null && this.isAddPointEvent(me.getEvent()))
+	{
+		this.addPoint(this.state, me.getEvent());
+		me.consume();
+	}
+	else if (handle != null && !me.isConsumed() && this.graph.isEnabled())
+	{
+		if (this.removeEnabled && this.isRemovePointEvent(me.getEvent()))
+		{
+			this.removePoint(this.state, handle);
+		}
+		else if (handle != mxEvent.LABEL_HANDLE || this.graph.isLabelMovable(me.getCell()))
+		{
+			if (handle <= mxEvent.VIRTUAL_HANDLE)
+			{
+				mxUtils.setOpacity(this.virtualBends[mxEvent.VIRTUAL_HANDLE - handle].node, 100);
+			}
+			
+			this.start(me.getX(), me.getY(), handle);
+		}
+		
+		me.consume();
+	}
+};
+
+/**
+ * Function: start
+ * 
+ * Starts the handling of the mouse gesture.
+ */
+mxEdgeHandler.prototype.start = function(x, y, index)
+{
+	this.startX = x;
+	this.startY = y;
+
+	this.isSource = (this.bends == null) ? false : index == 0;
+	this.isTarget = (this.bends == null) ? false : index == this.bends.length - 1;
+	this.isLabel = index == mxEvent.LABEL_HANDLE;
+
+	if (this.isSource || this.isTarget)
+	{
+		var cell = this.state.cell;
+		var terminal = this.graph.model.getTerminal(cell, this.isSource);
+
+		if ((terminal == null && this.graph.isTerminalPointMovable(cell, this.isSource)) ||
+			(terminal != null && this.graph.isCellDisconnectable(cell, terminal, this.isSource)))
+		{
+			this.index = index;
+		}
+	}
+	else
+	{
+		this.index = index;
+	}
+	
+	// Hides other custom handles
+	if (this.index <= mxEvent.CUSTOM_HANDLE && this.index > mxEvent.VIRTUAL_HANDLE)
+	{
+		if (this.customHandles != null)
+		{
+			for (var i = 0; i < this.customHandles.length; i++)
+			{
+				if (i != mxEvent.CUSTOM_HANDLE - this.index)
+				{
+					this.customHandles[i].setVisible(false);
+				}
+			}
+		}
+	}
+};
+
+/**
+ * Function: clonePreviewState
+ * 
+ * Returns a clone of the current preview state for the given point and terminal.
+ */
+mxEdgeHandler.prototype.clonePreviewState = function(point, terminal)
+{
+	return this.state.clone();
+};
+
+/**
+ * Function: getSnapToTerminalTolerance
+ * 
+ * Returns the tolerance for the guides. Default value is
+ * gridSize * scale / 2.
+ */
+mxEdgeHandler.prototype.getSnapToTerminalTolerance = function()
+{
+	return this.graph.gridSize * this.graph.view.scale / 2;
+};
+
+/**
+ * Function: updateHint
+ * 
+ * Hook for subclassers do show details while the handler is active.
+ */
+mxEdgeHandler.prototype.updateHint = function(me, point) { };
+
+/**
+ * Function: removeHint
+ * 
+ * Hooks for subclassers to hide details when the handler gets inactive.
+ */
+mxEdgeHandler.prototype.removeHint = function() { };
+
+/**
+ * Function: roundLength
+ * 
+ * Hook for rounding the unscaled width or height. This uses Math.round.
+ */
+mxEdgeHandler.prototype.roundLength = function(length)
+{
+	return Math.round(length);
+};
+
+/**
+ * Function: isSnapToTerminalsEvent
+ * 
+ * Returns true if <snapToTerminals> is true and if alt is not pressed.
+ */
+mxEdgeHandler.prototype.isSnapToTerminalsEvent = function(me)
+{
+	return this.snapToTerminals && !mxEvent.isAltDown(me.getEvent());
+};
+
+/**
+ * Function: getPointForEvent
+ * 
+ * Returns the point for the given event.
+ */
+mxEdgeHandler.prototype.getPointForEvent = function(me)
+{
+	var view = this.graph.getView();
+	var scale = view.scale;
+	var point = new mxPoint(this.roundLength(me.getGraphX() / scale) * scale,
+		this.roundLength(me.getGraphY() / scale) * scale);
+	
+	var tt = this.getSnapToTerminalTolerance();
+	var overrideX = false;
+	var overrideY = false;		
+	
+	if (tt > 0 && this.isSnapToTerminalsEvent(me))
+	{
+		function snapToPoint(pt)
+		{
+			if (pt != null)
+			{
+				var x = pt.x;
+
+				if (Math.abs(point.x - x) < tt)
+				{
+					point.x = x;
+					overrideX = true;
+				}
+				
+				var y = pt.y;
+
+				if (Math.abs(point.y - y) < tt)
+				{
+					point.y = y;
+					overrideY = true;
+				}
+			}
+		}
+		
+		// Temporary function
+		function snapToTerminal(terminal)
+		{
+			if (terminal != null)
+			{
+				snapToPoint.call(this, new mxPoint(view.getRoutingCenterX(terminal),
+						view.getRoutingCenterY(terminal)));
+			}
+		};
+
+		snapToTerminal.call(this, this.state.getVisibleTerminalState(true));
+		snapToTerminal.call(this, this.state.getVisibleTerminalState(false));
+
+		if (this.state.absolutePoints != null)
+		{
+			for (var i = 0; i < this.state.absolutePoints.length; i++)
+			{
+				snapToPoint.call(this, this.state.absolutePoints[i]);
+			}
+		}
+	}
+
+	if (this.graph.isGridEnabledEvent(me.getEvent()))
+	{
+		var tr = view.translate;
+		
+		if (!overrideX)
+		{
+			point.x = (this.graph.snap(point.x / scale - tr.x) + tr.x) * scale;
+		}
+		
+		if (!overrideY)
+		{
+			point.y = (this.graph.snap(point.y / scale - tr.y) + tr.y) * scale;
+		}
+	}
+	
+	return point;
+};
+
+/**
+ * Function: getPreviewTerminalState
+ * 
+ * Updates the given preview state taking into account the state of the constraint handler.
+ */
+mxEdgeHandler.prototype.getPreviewTerminalState = function(me)
+{
+	this.constraintHandler.update(me, this.isSource, true, me.isSource(this.marker.highlight.shape) ? null : this.currentPoint);
+	
+	if (this.constraintHandler.currentFocus != null && this.constraintHandler.currentConstraint != null)
+	{
+		// Handles special case where grid is large and connection point is at actual point in which
+		// case the outline is not followed as long as we're < gridSize / 2 away from that point
+		if (this.marker.highlight != null && this.marker.highlight.state != null &&
+			this.marker.highlight.state.cell == this.constraintHandler.currentFocus.cell)
+		{
+			// Direct repaint needed if cell already highlighted
+			if (this.marker.highlight.shape.stroke != 'transparent')
+			{
+				this.marker.highlight.shape.stroke = 'transparent';
+				this.marker.highlight.repaint();
+			}
+		}
+		else
+		{
+			this.marker.markCell(this.constraintHandler.currentFocus.cell, 'transparent');
+		}
+		
+		var model = this.graph.getModel();
+		var other = this.graph.view.getTerminalPort(this.state,
+				this.graph.view.getState(model.getTerminal(this.state.cell,
+			!this.isSource)), !this.isSource);
+		var otherCell = (other != null) ? other.cell : null;
+		var source = (this.isSource) ? this.constraintHandler.currentFocus.cell : otherCell;
+		var target = (this.isSource) ? otherCell : this.constraintHandler.currentFocus.cell;
+		
+		// Updates the error message of the handler
+		this.error = this.validateConnection(source, target);
+		var result = null;
+		
+		if (this.error == null)
+		{
+			result = this.constraintHandler.currentFocus;
+		}
+		else
+		{
+			this.constraintHandler.reset();
+		}
+		
+		return result;
+	}
+	else if (!this.graph.isIgnoreTerminalEvent(me.getEvent()))
+	{
+		this.marker.process(me);
+
+		return this.marker.getValidState();
+	}
+	else
+	{
+		this.marker.reset();
+		
+		return null;
+	}
+};
+
+/**
+ * Function: getPreviewPoints
+ * 
+ * Updates the given preview state taking into account the state of the constraint handler.
+ * 
+ * Parameters:
+ * 
+ * pt - <mxPoint> that contains the current pointer position.
+ * me - Optional <mxMouseEvent> that contains the current event.
+ */
+mxEdgeHandler.prototype.getPreviewPoints = function(pt, me)
+{
+	var geometry = this.graph.getCellGeometry(this.state.cell);
+	var points = (geometry.points != null) ? geometry.points.slice() : null;
+	var point = new mxPoint(pt.x, pt.y);
+	var result = null;
+	
+	if (!this.isSource && !this.isTarget)
+	{
+		this.convertPoint(point, false);
+		
+		if (points == null)
+		{
+			points = [point];
+		}
+		else
+		{
+			// Adds point from virtual bend
+			if (this.index <= mxEvent.VIRTUAL_HANDLE)
+			{
+				points.splice(mxEvent.VIRTUAL_HANDLE - this.index, 0, point);
+			}
+
+			// Removes point if dragged on terminal point
+			if (!this.isSource && !this.isTarget)
+			{
+				for (var i = 0; i < this.bends.length; i++)
+				{
+					if (i != this.index)
+					{
+						var bend = this.bends[i];
+						
+						if (bend != null && mxUtils.contains(bend.bounds, pt.x, pt.y))
+						{
+							if (this.index <= mxEvent.VIRTUAL_HANDLE)
+							{
+								points.splice(mxEvent.VIRTUAL_HANDLE - this.index, 1);
+							}
+							else
+							{
+								points.splice(this.index - 1, 1);
+							}
+							
+							result = points;
+						}
+					}
+				}
+				
+				// Removes point if user tries to straighten a segment
+				if (result == null && this.straightRemoveEnabled && (me == null || !mxEvent.isAltDown(me.getEvent())))
+				{
+					var tol = this.graph.tolerance * this.graph.tolerance;
+					var abs = this.state.absolutePoints.slice();
+					abs[this.index] = pt;
+					
+					// Handes special case where removing waypoint affects tolerance (flickering)
+					var src = this.state.getVisibleTerminalState(true);
+					
+					if (src != null)
+					{
+						var c = this.graph.getConnectionConstraint(this.state, src, true);
+						
+						// Checks if point is not fixed
+						if (c == null || this.graph.getConnectionPoint(src, c) == null)
+						{
+							abs[0] = new mxPoint(src.view.getRoutingCenterX(src), src.view.getRoutingCenterY(src));
+						}
+					}
+					
+					var trg = this.state.getVisibleTerminalState(false);
+					
+					if (trg != null)
+					{
+						var c = this.graph.getConnectionConstraint(this.state, trg, false);
+						
+						// Checks if point is not fixed
+						if (c == null || this.graph.getConnectionPoint(trg, c) == null)
+						{
+							abs[abs.length - 1] = new mxPoint(trg.view.getRoutingCenterX(trg), trg.view.getRoutingCenterY(trg));
+						}
+					}
+
+					function checkRemove(idx, tmp)
+					{
+						if (idx > 0 && idx < abs.length - 1 &&
+							mxUtils.ptSegDistSq(abs[idx - 1].x, abs[idx - 1].y,
+								abs[idx + 1].x, abs[idx + 1].y, tmp.x, tmp.y) < tol)
+						{
+							points.splice(idx - 1, 1);
+							result = points;
+						}
+					};
+					
+					// LATER: Check if other points can be removed if a segment is made straight
+					checkRemove(this.index, pt);
+				}
+			}
+			
+			// Updates existing point
+			if (result == null && this.index > mxEvent.VIRTUAL_HANDLE)
+			{
+				points[this.index - 1] = point;
+			}
+		}
+	}
+	else if (this.graph.resetEdgesOnConnect)
+	{
+		points = null;
+	}
+	
+	return (result != null) ? result : points;
+};
+
+/**
+ * Function: isOutlineConnectEvent
+ * 
+ * Returns true if <outlineConnect> is true and the source of the event is the outline shape
+ * or shift is pressed.
+ */
+mxEdgeHandler.prototype.isOutlineConnectEvent = function(me)
+{
+	var offset = mxUtils.getOffset(this.graph.container);
+	var evt = me.getEvent();
+	
+	var clientX = mxEvent.getClientX(evt);
+	var clientY = mxEvent.getClientY(evt);
+	
+	var doc = document.documentElement;
+	var left = (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0);
+	var top = (window.pageYOffset || doc.scrollTop)  - (doc.clientTop || 0);
+	
+	var gridX = this.currentPoint.x - this.graph.container.scrollLeft + offset.x - left;
+	var gridY = this.currentPoint.y - this.graph.container.scrollTop + offset.y - top;
+
+	return this.outlineConnect && !mxEvent.isShiftDown(me.getEvent()) &&
+		(me.isSource(this.marker.highlight.shape) ||
+		(mxEvent.isAltDown(me.getEvent()) && me.getState() != null) ||
+		this.marker.highlight.isHighlightAt(clientX, clientY) ||
+		((gridX != clientX || gridY != clientY) && me.getState() == null &&
+		this.marker.highlight.isHighlightAt(gridX, gridY)));
+};
+
+/**
+ * Function: updatePreviewState
+ * 
+ * Updates the given preview state taking into account the state of the constraint handler.
+ */
+mxEdgeHandler.prototype.updatePreviewState = function(edge, point, terminalState, me, outline)
+{
+	// Computes the points for the edge style and terminals
+	var sourceState = (this.isSource) ? terminalState : this.state.getVisibleTerminalState(true);
+	var targetState = (this.isTarget) ? terminalState : this.state.getVisibleTerminalState(false);
+	
+	var sourceConstraint = this.graph.getConnectionConstraint(edge, sourceState, true);
+	var targetConstraint = this.graph.getConnectionConstraint(edge, targetState, false);
+
+	var constraint = this.constraintHandler.currentConstraint;
+
+	if (constraint == null && outline)
+	{
+		if (terminalState != null)
+		{
+			// Handles special case where mouse is on outline away from actual end point
+			// in which case the grid is ignored and mouse point is used instead
+			if (me.isSource(this.marker.highlight.shape))
+			{
+				point = new mxPoint(me.getGraphX(), me.getGraphY());
+			}
+			
+			constraint = this.graph.getOutlineConstraint(point, terminalState, me);
+			this.constraintHandler.setFocus(me, terminalState, this.isSource);
+			this.constraintHandler.currentConstraint = constraint;
+			this.constraintHandler.currentPoint = point;
+		}
+		else
+		{
+			constraint = new mxConnectionConstraint();
+		}
+	}
+	
+	if (this.outlineConnect && this.marker.highlight != null && this.marker.highlight.shape != null)
+	{
+		var s = this.graph.view.scale;
+		
+		if (this.constraintHandler.currentConstraint != null &&
+			this.constraintHandler.currentFocus != null)
+		{
+			this.marker.highlight.shape.stroke = (outline) ? mxConstants.OUTLINE_HIGHLIGHT_COLOR : 'transparent';
+			this.marker.highlight.shape.strokewidth = mxConstants.OUTLINE_HIGHLIGHT_STROKEWIDTH / s / s;
+			this.marker.highlight.repaint();
+		}
+		else if (this.marker.hasValidState())
+		{
+			this.marker.highlight.shape.stroke = (this.marker.getValidState() == me.getState()) ?
+				mxConstants.DEFAULT_VALID_COLOR : 'transparent';
+			this.marker.highlight.shape.strokewidth = mxConstants.HIGHLIGHT_STROKEWIDTH / s / s;
+			this.marker.highlight.repaint();
+		}
+	}
+	
+	if (this.isSource)
+	{
+		sourceConstraint = constraint;
+	}
+	else if (this.isTarget)
+	{
+		targetConstraint = constraint;
+	}
+	
+	if (this.isSource || this.isTarget)
+	{
+		if (constraint != null && constraint.point != null)
+		{
+			edge.style[(this.isSource) ? mxConstants.STYLE_EXIT_X : mxConstants.STYLE_ENTRY_X] = constraint.point.x;
+			edge.style[(this.isSource) ? mxConstants.STYLE_EXIT_Y : mxConstants.STYLE_ENTRY_Y] = constraint.point.y;
+		}
+		else
+		{
+			delete edge.style[(this.isSource) ? mxConstants.STYLE_EXIT_X : mxConstants.STYLE_ENTRY_X];
+			delete edge.style[(this.isSource) ? mxConstants.STYLE_EXIT_Y : mxConstants.STYLE_ENTRY_Y];
+		}
+	}
+	
+	edge.setVisibleTerminalState(sourceState, true);
+	edge.setVisibleTerminalState(targetState, false);
+	
+	if (!this.isSource || sourceState != null)
+	{
+		edge.view.updateFixedTerminalPoint(edge, sourceState, true, sourceConstraint);
+	}
+	
+	if (!this.isTarget || targetState != null)
+	{
+		edge.view.updateFixedTerminalPoint(edge, targetState, false, targetConstraint);
+	}
+	
+	if ((this.isSource || this.isTarget) && terminalState == null)
+	{
+		edge.setAbsoluteTerminalPoint(point, this.isSource);
+
+		if (this.marker.getMarkedState() == null)
+		{
+			this.error = (this.graph.allowDanglingEdges) ? null : '';
+		}
+	}
+	
+	edge.view.updatePoints(edge, this.points, sourceState, targetState);
+	edge.view.updateFloatingTerminalPoints(edge, sourceState, targetState);
+};
+
+/**
+ * Function: mouseMove
+ * 
+ * Handles the event by updating the preview.
+ */
+mxEdgeHandler.prototype.mouseMove = function(sender, me)
+{
+	if (this.index != null && this.marker != null)
+	{
+		this.currentPoint = this.getPointForEvent(me);
+		this.error = null;
+		
+		// Uses the current point from the constraint handler if available
+		if (!this.graph.isIgnoreTerminalEvent(me.getEvent()) && mxEvent.isShiftDown(me.getEvent()) && this.snapPoint != null)
+		{
+			if (Math.abs(this.snapPoint.x - this.currentPoint.x) < Math.abs(this.snapPoint.y - this.currentPoint.y))
+			{
+				this.currentPoint.x = this.snapPoint.x;
+			}
+			else
+			{
+				this.currentPoint.y = this.snapPoint.y;
+			}
+		}
+		
+		if (this.index <= mxEvent.CUSTOM_HANDLE && this.index > mxEvent.VIRTUAL_HANDLE)
+		{
+			if (this.customHandles != null)
+			{
+				this.customHandles[mxEvent.CUSTOM_HANDLE - this.index].processEvent(me);
+			}
+		}
+		else if (this.isLabel)
+		{
+			this.label.x = this.currentPoint.x;
+			this.label.y = this.currentPoint.y;
+		}
+		else
+		{
+			this.points = this.getPreviewPoints(this.currentPoint, me);
+			var terminalState = (this.isSource || this.isTarget) ? this.getPreviewTerminalState(me) : null;
+
+			if (this.constraintHandler.currentConstraint != null &&
+				this.constraintHandler.currentFocus != null &&
+				this.constraintHandler.currentPoint != null)
+			{
+				this.currentPoint = this.constraintHandler.currentPoint.clone();
+			}
+			else if (this.outlineConnect)
+			{
+				// Need to check outline before cloning terminal state
+				var outline = (this.isSource || this.isTarget) ? this.isOutlineConnectEvent(me) : false
+						
+				if (outline)
+				{
+					terminalState = this.marker.highlight.state;
+				}
+				else if (terminalState != null && terminalState != me.getState() && this.marker.highlight.shape != null)
+				{
+					this.marker.highlight.shape.stroke = 'transparent';
+					this.marker.highlight.repaint();
+					terminalState = null;
+				}
+			}
+			
+			var clone = this.clonePreviewState(this.currentPoint, (terminalState != null) ? terminalState.cell : null);
+			this.updatePreviewState(clone, this.currentPoint, terminalState, me, outline);
+
+			// Sets the color of the preview to valid or invalid, updates the
+			// points of the preview and redraws
+			var color = (this.error == null) ? this.marker.validColor : this.marker.invalidColor;
+			this.setPreviewColor(color);
+			this.abspoints = clone.absolutePoints;
+			this.active = true;
+		}
+
+		// This should go before calling isOutlineConnectEvent above. As a workaround
+		// we add an offset of gridSize to the hint to avoid problem with hit detection
+		// in highlight.isHighlightAt (which uses comonentFromPoint)
+		this.updateHint(me, this.currentPoint);
+		this.drawPreview();
+		mxEvent.consume(me.getEvent());
+		me.consume();
+	}
+	// Workaround for disabling the connect highlight when over handle
+	else if (mxClient.IS_IE && this.getHandleForEvent(me) != null)
+	{
+		me.consume(false);
+	}
+};
+
+/**
+ * Function: mouseUp
+ * 
+ * Handles the event to applying the previewed changes on the edge by
+ * using <moveLabel>, <connect> or <changePoints>.
+ */
+mxEdgeHandler.prototype.mouseUp = function(sender, me)
+{
+	// Workaround for wrong event source in Webkit
+	if (this.index != null && this.marker != null)
+	{
+		var edge = this.state.cell;
+		
+		// Ignores event if mouse has not been moved
+		if (me.getX() != this.startX || me.getY() != this.startY)
+		{
+			var clone = !this.graph.isIgnoreTerminalEvent(me.getEvent()) && this.graph.isCloneEvent(me.getEvent()) &&
+				this.cloneEnabled && this.graph.isCellsCloneable();
+			
+			// Displays the reason for not carriying out the change
+			// if there is an error message with non-zero length
+			if (this.error != null)
+			{
+				if (this.error.length > 0)
+				{
+					this.graph.validationAlert(this.error);
+				}
+			}
+			else if (this.index <= mxEvent.CUSTOM_HANDLE && this.index > mxEvent.VIRTUAL_HANDLE)
+			{
+				if (this.customHandles != null)
+				{
+					var model = this.graph.getModel();
+					
+					model.beginUpdate();
+					try
+					{
+						this.customHandles[mxEvent.CUSTOM_HANDLE - this.index].execute();
+					}
+					finally
+					{
+						model.endUpdate();
+					}
+				}
+			}
+			else if (this.isLabel)
+			{
+				this.moveLabel(this.state, this.label.x, this.label.y);
+			}
+			else if (this.isSource || this.isTarget)
+			{
+				var terminal = null;
+				
+				if (this.constraintHandler.currentConstraint != null &&
+					this.constraintHandler.currentFocus != null)
+				{
+					terminal = this.constraintHandler.currentFocus.cell;
+				}
+				
+				if (terminal == null && this.marker.hasValidState() && this.marker.highlight != null &&
+					this.marker.highlight.shape != null &&
+					this.marker.highlight.shape.stroke != 'transparent' &&
+					this.marker.highlight.shape.stroke != 'white')
+				{
+					terminal = this.marker.validState.cell;
+				}
+				
+				if (terminal != null)
+				{
+					edge = this.connect(edge, terminal, this.isSource, clone, me);
+				}
+				else if (this.graph.isAllowDanglingEdges())
+				{
+					var pt = this.abspoints[(this.isSource) ? 0 : this.abspoints.length - 1];
+					pt.x = this.roundLength(pt.x / this.graph.view.scale - this.graph.view.translate.x);
+					pt.y = this.roundLength(pt.y / this.graph.view.scale - this.graph.view.translate.y);
+
+					var pstate = this.graph.getView().getState(
+							this.graph.getModel().getParent(edge));
+							
+					if (pstate != null)
+					{
+						pt.x -= pstate.origin.x;
+						pt.y -= pstate.origin.y;
+					}
+					
+					pt.x -= this.graph.panDx / this.graph.view.scale;
+					pt.y -= this.graph.panDy / this.graph.view.scale;
+										
+					// Destroys and recreates this handler
+					edge = this.changeTerminalPoint(edge, pt, this.isSource, clone);
+				}
+			}
+			else if (this.active)
+			{
+				edge = this.changePoints(edge, this.points, clone);
+			}
+			else
+			{
+				this.graph.getView().invalidate(this.state.cell);
+				this.graph.getView().validate(this.state.cell);						
+			}
+		}
+		
+		// Resets the preview color the state of the handler if this
+		// handler has not been recreated
+		if (this.marker != null)
+		{
+			this.reset();
+
+			// Updates the selection if the edge has been cloned
+			if (edge != this.state.cell)
+			{
+				this.graph.setSelectionCell(edge);
+			}
+		}
+
+		me.consume();
+	}
+};
+
+/**
+ * Function: reset
+ * 
+ * Resets the state of this handler.
+ */
+mxEdgeHandler.prototype.reset = function()
+{
+	this.error = null;
+	this.index = null;
+	this.label = null;
+	this.points = null;
+	this.snapPoint = null;
+	this.active = false;
+	this.isLabel = false;
+	this.isSource = false;
+	this.isTarget = false;
+	
+	if (this.livePreview && this.sizers != null)
+	{
+		for (var i = 0; i < this.sizers.length; i++)
+		{
+			if (this.sizers[i] != null)
+			{
+				this.sizers[i].node.style.display = '';
+			}
+		}
+	}
+
+	if (this.marker != null)
+	{
+		this.marker.reset();
+	}
+	
+	if (this.constraintHandler != null)
+	{
+		this.constraintHandler.reset();
+	}
+	
+	if (this.customHandles != null)
+	{
+		for (var i = 0; i < this.customHandles.length; i++)
+		{
+			this.customHandles[i].reset();
+		}
+	}
+
+	this.setPreviewColor(mxConstants.EDGE_SELECTION_COLOR);
+	this.removeHint();
+	this.redraw();
+};
+
+/**
+ * Function: setPreviewColor
+ * 
+ * Sets the color of the preview to the given value.
+ */
+mxEdgeHandler.prototype.setPreviewColor = function(color)
+{
+	if (this.shape != null)
+	{
+		this.shape.stroke = color;
+	}
+};
+
+
+/**
+ * Function: convertPoint
+ * 
+ * Converts the given point in-place from screen to unscaled, untranslated
+ * graph coordinates and applies the grid. Returns the given, modified
+ * point instance.
+ * 
+ * Parameters:
+ * 
+ * point - <mxPoint> to be converted.
+ * gridEnabled - Boolean that specifies if the grid should be applied.
+ */
+mxEdgeHandler.prototype.convertPoint = function(point, gridEnabled)
+{
+	var scale = this.graph.getView().getScale();
+	var tr = this.graph.getView().getTranslate();
+		
+	if (gridEnabled)
+	{
+		point.x = this.graph.snap(point.x);
+		point.y = this.graph.snap(point.y);
+	}
+	
+	point.x = Math.round(point.x / scale - tr.x);
+	point.y = Math.round(point.y / scale - tr.y);
+
+	var pstate = this.graph.getView().getState(
+		this.graph.getModel().getParent(this.state.cell));
+
+	if (pstate != null)
+	{
+		point.x -= pstate.origin.x;
+		point.y -= pstate.origin.y;
+	}
+
+	return point;
+};
+
+/**
+ * Function: moveLabel
+ * 
+ * Changes the coordinates for the label of the given edge.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> that represents the edge.
+ * x - Integer that specifies the x-coordinate of the new location.
+ * y - Integer that specifies the y-coordinate of the new location.
+ */
+mxEdgeHandler.prototype.moveLabel = function(edgeState, x, y)
+{
+	var model = this.graph.getModel();
+	var geometry = model.getGeometry(edgeState.cell);
+	
+	if (geometry != null)
+	{
+		var scale = this.graph.getView().scale;
+		geometry = geometry.clone();
+		
+		if (geometry.relative)
+		{
+			// Resets the relative location stored inside the geometry
+			var pt = this.graph.getView().getRelativePoint(edgeState, x, y);
+			geometry.x = Math.round(pt.x * 10000) / 10000;
+			geometry.y = Math.round(pt.y);
+			
+			// Resets the offset inside the geometry to find the offset
+			// from the resulting point
+			geometry.offset = new mxPoint(0, 0);
+			var pt = this.graph.view.getPoint(edgeState, geometry);
+			geometry.offset = new mxPoint(Math.round((x - pt.x) / scale), Math.round((y - pt.y) / scale));
+		}
+		else
+		{
+			var points = edgeState.absolutePoints;
+			var p0 = points[0];
+			var pe = points[points.length - 1];
+			
+			if (p0 != null && pe != null)
+			{
+				var cx = p0.x + (pe.x - p0.x) / 2;
+				var cy = p0.y + (pe.y - p0.y) / 2;
+				
+				geometry.offset = new mxPoint(Math.round((x - cx) / scale), Math.round((y - cy) / scale));
+				geometry.x = 0;
+				geometry.y = 0;
+			}
+		}
+
+		model.setGeometry(edgeState.cell, geometry);
+	}
+};
+
+/**
+ * Function: connect
+ * 
+ * Changes the terminal or terminal point of the given edge in the graph
+ * model.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> that represents the edge to be reconnected.
+ * terminal - <mxCell> that represents the new terminal.
+ * isSource - Boolean indicating if the new terminal is the source or
+ * target terminal.
+ * isClone - Boolean indicating if the new connection should be a clone of
+ * the old edge.
+ * me - <mxMouseEvent> that contains the mouse up event.
+ */
+mxEdgeHandler.prototype.connect = function(edge, terminal, isSource, isClone, me)
+{
+	var model = this.graph.getModel();
+	var parent = model.getParent(edge);
+	
+	model.beginUpdate();
+	try
+	{
+		// Clones and adds the cell
+		if (isClone)
+		{
+			var clone = this.graph.cloneCells([edge])[0];
+			model.add(parent, clone, model.getChildCount(parent));
+			
+			var other = model.getTerminal(edge, !isSource);
+			this.graph.connectCell(clone, other, !isSource);
+			
+			edge = clone;
+		}
+
+		var constraint = this.constraintHandler.currentConstraint;
+		
+		if (constraint == null)
+		{
+			constraint = new mxConnectionConstraint();
+		}
+
+		this.graph.connectCell(edge, terminal, isSource, constraint);
+	}
+	finally
+	{
+		model.endUpdate();
+	}
+	
+	return edge;
+};
+
+/**
+ * Function: changeTerminalPoint
+ * 
+ * Changes the terminal point of the given edge.
+ */
+mxEdgeHandler.prototype.changeTerminalPoint = function(edge, point, isSource, clone)
+{
+	var model = this.graph.getModel();
+
+	model.beginUpdate();
+	try
+	{
+		if (clone)
+		{
+			var parent = model.getParent(edge);
+			var terminal = model.getTerminal(edge, !isSource);
+			edge = this.graph.cloneCells([edge])[0];
+			model.add(parent, edge, model.getChildCount(parent));
+			model.setTerminal(edge, terminal, !isSource);
+		}
+
+		var geo = model.getGeometry(edge);
+		
+		if (geo != null)
+		{
+			geo = geo.clone();
+			geo.setTerminalPoint(point, isSource);
+			model.setGeometry(edge, geo);
+			this.graph.connectCell(edge, null, isSource, new mxConnectionConstraint());
+		}
+	}
+	finally
+	{
+		model.endUpdate();
+	}
+	
+	return edge;
+};
+
+/**
+ * Function: changePoints
+ * 
+ * Changes the control points of the given edge in the graph model.
+ */
+mxEdgeHandler.prototype.changePoints = function(edge, points, clone)
+{
+	var model = this.graph.getModel();
+	model.beginUpdate();
+	try
+	{
+		if (clone)
+		{
+			var parent = model.getParent(edge);
+			var source = model.getTerminal(edge, true);
+			var target = model.getTerminal(edge, false);
+			edge = this.graph.cloneCells([edge])[0];
+			model.add(parent, edge, model.getChildCount(parent));
+			model.setTerminal(edge, source, true);
+			model.setTerminal(edge, target, false);
+		}
+		
+		var geo = model.getGeometry(edge);
+		
+		if (geo != null)
+		{
+			geo = geo.clone();
+			geo.points = points;
+			
+			model.setGeometry(edge, geo);
+		}
+	}
+	finally
+	{
+		model.endUpdate();
+	}
+	
+	return edge;
+};
+
+/**
+ * Function: addPoint
+ * 
+ * Adds a control point for the given state and event.
+ */
+mxEdgeHandler.prototype.addPoint = function(state, evt)
+{
+	var pt = mxUtils.convertPoint(this.graph.container, mxEvent.getClientX(evt),
+			mxEvent.getClientY(evt));
+	var gridEnabled = this.graph.isGridEnabledEvent(evt);
+	this.convertPoint(pt, gridEnabled);
+	this.addPointAt(state, pt.x, pt.y);
+	mxEvent.consume(evt);
+};
+
+/**
+ * Function: addPointAt
+ * 
+ * Adds a control point at the given point.
+ */
+mxEdgeHandler.prototype.addPointAt = function(state, x, y)
+{
+	var geo = this.graph.getCellGeometry(state.cell);
+	var pt = new mxPoint(x, y);
+	
+	if (geo != null)
+	{
+		geo = geo.clone();
+		var t = this.graph.view.translate;
+		var s = this.graph.view.scale;
+		var offset = new mxPoint(t.x * s, t.y * s);
+		
+		var parent = this.graph.model.getParent(this.state.cell);
+		
+		if (this.graph.model.isVertex(parent))
+		{
+			var pState = this.graph.view.getState(parent);
+			offset = new mxPoint(pState.x, pState.y);
+		}
+		
+		var index = mxUtils.findNearestSegment(state, pt.x * s + offset.x, pt.y * s + offset.y);
+
+		if (geo.points == null)
+		{
+			geo.points = [pt];
+		}
+		else
+		{
+			geo.points.splice(index, 0, pt);
+		}
+		
+		this.graph.getModel().setGeometry(state.cell, geo);
+		this.refresh();	
+		this.redraw();
+	}
+};
+
+/**
+ * Function: removePoint
+ * 
+ * Removes the control point at the given index from the given state.
+ */
+mxEdgeHandler.prototype.removePoint = function(state, index)
+{
+	if (index > 0 && index < this.abspoints.length - 1)
+	{
+		var geo = this.graph.getCellGeometry(this.state.cell);
+		
+		if (geo != null && geo.points != null)
+		{
+			geo = geo.clone();
+			geo.points.splice(index - 1, 1);
+			this.graph.getModel().setGeometry(state.cell, geo);
+			this.refresh();
+			this.redraw();
+		}
+	}
+};
+
+/**
+ * Function: getHandleFillColor
+ * 
+ * Returns the fillcolor for the handle at the given index.
+ */
+mxEdgeHandler.prototype.getHandleFillColor = function(index)
+{
+	var isSource = index == 0;
+	var cell = this.state.cell;
+	var terminal = this.graph.getModel().getTerminal(cell, isSource);
+	var color = mxConstants.HANDLE_FILLCOLOR;
+	
+	if ((terminal != null && !this.graph.isCellDisconnectable(cell, terminal, isSource)) ||
+		(terminal == null && !this.graph.isTerminalPointMovable(cell, isSource)))
+	{
+		color = mxConstants.LOCKED_HANDLE_FILLCOLOR;
+	}
+	else if (terminal != null && this.graph.isCellDisconnectable(cell, terminal, isSource))
+	{
+		color = mxConstants.CONNECT_HANDLE_FILLCOLOR;
+	}
+	
+	return color;
+};
+
+/**
+ * Function: redraw
+ * 
+ * Redraws the preview, and the bends- and label control points.
+ */
+mxEdgeHandler.prototype.redraw = function()
+{
+	this.abspoints = this.state.absolutePoints.slice();
+	this.redrawHandles();
+	
+	var g = this.graph.getModel().getGeometry(this.state.cell);
+	var pts = g.points;
+
+	if (this.bends != null && this.bends.length > 0)
+	{
+		if (pts != null)
+		{
+			if (this.points == null)
+			{
+				this.points = [];
+			}
+			
+			for (var i = 1; i < this.bends.length - 1; i++)
+			{
+				if (this.bends[i] != null && this.abspoints[i] != null)
+				{
+					this.points[i - 1] = pts[i - 1];
+				}
+			}
+		}
+	}
+
+	this.drawPreview();
+};
+
+/**
+ * Function: redrawHandles
+ * 
+ * Redraws the handles.
+ */
+mxEdgeHandler.prototype.redrawHandles = function()
+{
+	var cell = this.state.cell;
+
+	// Updates the handle for the label position
+	var b = this.labelShape.bounds;
+	this.label = new mxPoint(this.state.absoluteOffset.x, this.state.absoluteOffset.y);
+	this.labelShape.bounds = new mxRectangle(Math.round(this.label.x - b.width / 2),
+		Math.round(this.label.y - b.height / 2), b.width, b.height);
+
+	// Shows or hides the label handle depending on the label
+	var lab = this.graph.getLabel(cell);
+	this.labelShape.visible = (lab != null && lab.length > 0 && this.graph.isLabelMovable(cell));
+	
+	if (this.bends != null && this.bends.length > 0)
+	{
+		var n = this.abspoints.length - 1;
+		
+		var p0 = this.abspoints[0];
+		var x0 = p0.x;
+		var y0 = p0.y;
+		
+		b = this.bends[0].bounds;
+		this.bends[0].bounds = new mxRectangle(Math.floor(x0 - b.width / 2),
+				Math.floor(y0 - b.height / 2), b.width, b.height);
+		this.bends[0].fill = this.getHandleFillColor(0);
+		this.bends[0].redraw();
+		
+		if (this.manageLabelHandle)
+		{
+			this.checkLabelHandle(this.bends[0].bounds);
+		}
+				
+		var pe = this.abspoints[n];
+		var xn = pe.x;
+		var yn = pe.y;
+		
+		var bn = this.bends.length - 1;
+		b = this.bends[bn].bounds;
+		this.bends[bn].bounds = new mxRectangle(Math.floor(xn - b.width / 2),
+				Math.floor(yn - b.height / 2), b.width, b.height);
+		this.bends[bn].fill = this.getHandleFillColor(bn);
+		this.bends[bn].redraw();
+				
+		if (this.manageLabelHandle)
+		{
+			this.checkLabelHandle(this.bends[bn].bounds);
+		}
+		
+		this.redrawInnerBends(p0, pe);
+	}
+
+	if (this.abspoints != null && this.virtualBends != null && this.virtualBends.length > 0)
+	{
+		var last = this.abspoints[0];
+		
+		for (var i = 0; i < this.virtualBends.length; i++)
+		{
+			if (this.virtualBends[i] != null && this.abspoints[i + 1] != null)
+			{
+				var pt = this.abspoints[i + 1];
+				var b = this.virtualBends[i];
+				var x = last.x + (pt.x - last.x) / 2;
+				var y = last.y + (pt.y - last.y) / 2;
+				b.bounds = new mxRectangle(Math.floor(x - b.bounds.width / 2),
+						Math.floor(y - b.bounds.height / 2), b.bounds.width, b.bounds.height);
+				b.redraw();
+				mxUtils.setOpacity(b.node, this.virtualBendOpacity);
+				last = pt;
+				
+				if (this.manageLabelHandle)
+				{
+					this.checkLabelHandle(b.bounds);
+				}
+			}
+		}
+	}
+	
+	if (this.labelShape != null)
+	{
+		this.labelShape.redraw();
+	}
+	
+	if (this.customHandles != null)
+	{
+		for (var i = 0; i < this.customHandles.length; i++)
+		{
+			this.customHandles[i].redraw();
+		}
+	}
+};
+
+/**
+ * Function: hideHandles
+ * 
+ * Shortcut to <hideSizers>.
+ */
+mxEdgeHandler.prototype.setHandlesVisible = function(visible)
+{
+	if (this.bends != null)
+	{
+		for (var i = 0; i < this.bends.length; i++)
+		{
+			this.bends[i].node.style.display = (visible) ? '' : 'none';
+		}
+	}
+	
+	if (this.virtualBends != null)
+	{
+		for (var i = 0; i < this.virtualBends.length; i++)
+		{
+			this.virtualBends[i].node.style.display = (visible) ? '' : 'none';
+		}
+	}
+
+	if (this.labelShape != null)
+	{
+		this.labelShape.node.style.display = (visible) ? '' : 'none';
+	}
+	
+	if (this.customHandles != null)
+	{
+		for (var i = 0; i < this.customHandles.length; i++)
+		{
+			this.customHandles[i].setVisible(visible);
+		}
+	}
+};
+
+/**
+ * Function: redrawInnerBends
+ * 
+ * Updates and redraws the inner bends.
+ * 
+ * Parameters:
+ * 
+ * p0 - <mxPoint> that represents the location of the first point.
+ * pe - <mxPoint> that represents the location of the last point.
+ */
+mxEdgeHandler.prototype.redrawInnerBends = function(p0, pe)
+{
+	for (var i = 1; i < this.bends.length - 1; i++)
+	{
+		if (this.bends[i] != null)
+		{
+			if (this.abspoints[i] != null)
+			{
+				var x = this.abspoints[i].x;
+				var y = this.abspoints[i].y;
+				
+				var b = this.bends[i].bounds;
+				this.bends[i].node.style.visibility = 'visible';
+				this.bends[i].bounds = new mxRectangle(Math.round(x - b.width / 2),
+						Math.round(y - b.height / 2), b.width, b.height);
+				
+				if (this.manageLabelHandle)
+				{
+					this.checkLabelHandle(this.bends[i].bounds);
+				}
+				else if (this.handleImage == null && this.labelShape.visible && mxUtils.intersects(this.bends[i].bounds, this.labelShape.bounds))
+				{
+					w = mxConstants.HANDLE_SIZE + 3;
+					h = mxConstants.HANDLE_SIZE + 3;
+					this.bends[i].bounds = new mxRectangle(Math.round(x - w / 2), Math.round(y - h / 2), w, h);
+				}
+				
+				this.bends[i].redraw();
+			}
+			else
+			{
+				this.bends[i].destroy();
+				this.bends[i] = null;
+			}
+		}
+	}
+};
+
+/**
+ * Function: checkLabelHandle
+ * 
+ * Checks if the label handle intersects the given bounds and moves it if it
+ * intersects.
+ */
+mxEdgeHandler.prototype.checkLabelHandle = function(b)
+{
+	if (this.labelShape != null)
+	{
+		var b2 = this.labelShape.bounds;
+		
+		if (mxUtils.intersects(b, b2))
+		{
+			if (b.getCenterY() < b2.getCenterY())
+			{
+				b2.y = b.y + b.height;
+			}
+			else
+			{
+				b2.y = b.y - b2.height;
+			}
+		}
+	}
+};
+
+/**
+ * Function: drawPreview
+ * 
+ * Redraws the preview.
+ */
+mxEdgeHandler.prototype.drawPreview = function()
+{
+	if (this.isLabel)
+	{
+		var b = this.labelShape.bounds;
+		var bounds = new mxRectangle(Math.round(this.label.x - b.width / 2),
+				Math.round(this.label.y - b.height / 2), b.width, b.height);
+		this.labelShape.bounds = bounds;
+		this.labelShape.redraw();
+	}
+	else if (this.shape != null)
+	{
+		this.shape.apply(this.state);
+		this.shape.points = this.abspoints;
+		this.shape.scale = this.state.view.scale;
+		this.shape.isDashed = this.isSelectionDashed();
+		this.shape.stroke = this.getSelectionColor();
+		this.shape.strokewidth = this.getSelectionStrokeWidth() / this.shape.scale / this.shape.scale;
+		this.shape.isShadow = false;
+		this.shape.redraw();
+	}
+	
+	if (this.parentHighlight != null)
+	{
+		this.parentHighlight.redraw();
+	}
+};
+
+/**
+ * Function: refresh
+ * 
+ * Refreshes the bends of this handler.
+ */
+mxEdgeHandler.prototype.refresh = function()
+{
+	this.abspoints = this.getSelectionPoints(this.state);
+	this.points = [];
+
+	if (this.shape != null)
+	{
+		this.shape.points = this.abspoints;
+	}
+	
+	if (this.bends != null)
+	{
+		this.destroyBends(this.bends);
+		this.bends = this.createBends();
+	}
+	
+	if (this.virtualBends != null)
+	{
+		this.destroyBends(this.virtualBends);
+		this.virtualBends = this.createVirtualBends();
+	}
+	
+	if (this.customHandles != null)
+	{
+		this.destroyBends(this.customHandles);
+		this.customHandles = this.createCustomHandles();
+	}
+	
+	// Puts label node on top of bends
+	if (this.labelShape != null && this.labelShape.node != null && this.labelShape.node.parentNode != null)
+	{
+		this.labelShape.node.parentNode.appendChild(this.labelShape.node);
+	}
+};
+
+/**
+ * Function: destroyBends
+ * 
+ * Destroys all elements in <bends>.
+ */
+mxEdgeHandler.prototype.destroyBends = function(bends)
+{
+	if (bends != null)
+	{
+		for (var i = 0; i < bends.length; i++)
+		{
+			if (bends[i] != null)
+			{
+				bends[i].destroy();
+			}
+		}
+	}
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the handler and all its resources and DOM nodes. This does
+ * normally not need to be called as handlers are destroyed automatically
+ * when the corresponding cell is deselected.
+ */
+mxEdgeHandler.prototype.destroy = function()
+{
+	if (this.escapeHandler != null)
+	{
+		this.state.view.graph.removeListener(this.escapeHandler);
+		this.escapeHandler = null;
+	}
+	
+	if (this.marker != null)
+	{
+		this.marker.destroy();
+		this.marker = null;
+	}
+	
+	if (this.shape != null)
+	{
+		this.shape.destroy();
+		this.shape = null;
+	}
+	
+	if (this.parentHighlight != null)
+	{
+		this.parentHighlight.destroy();
+		this.parentHighlight = null;
+	}
+	
+	if (this.labelShape != null)
+	{
+		this.labelShape.destroy();
+		this.labelShape = null;
+	}
+
+	if (this.constraintHandler != null)
+	{
+		this.constraintHandler.destroy();
+		this.constraintHandler = null;
+	}
+	
+	this.destroyBends(this.virtualBends);
+	this.virtualBends = null;
+	
+	this.destroyBends(this.customHandles);
+	this.customHandles = null;
+
+	this.destroyBends(this.bends);
+	this.bends = null;
+	
+	this.removeHint();
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/handler/mxEdgeSegmentHandler.js b/airavata-kubernetes/workflow-composer/src/js/handler/mxEdgeSegmentHandler.js
new file mode 100644
index 0000000..513344e
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/handler/mxEdgeSegmentHandler.js
@@ -0,0 +1,401 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+function mxEdgeSegmentHandler(state)
+{
+	mxEdgeHandler.call(this, state);
+};
+
+/**
+ * Extends mxEdgeHandler.
+ */
+mxUtils.extend(mxEdgeSegmentHandler, mxElbowEdgeHandler);
+
+/**
+ * Function: getCurrentPoints
+ * 
+ * Returns the current absolute points.
+ */
+mxEdgeSegmentHandler.prototype.getCurrentPoints = function()
+{
+	var pts = this.state.absolutePoints;
+	
+	if (pts != null)
+	{
+		// Special case for straight edges where we add a virtual middle handle for moving the edge
+		if (pts.length == 2 || (pts.length == 3 && (pts[0].x == pts[1].x && pts[1].x == pts[2].x ||
+				pts[0].y == pts[1].y && pts[1].y == pts[2].y)))
+		{
+			var cx = pts[0].x + (pts[pts.length - 1].x - pts[0].x) / 2;
+			var cy = pts[0].y + (pts[pts.length - 1].y - pts[0].y) / 2;
+			
+			pts = [pts[0], new mxPoint(cx, cy), new mxPoint(cx, cy), pts[pts.length - 1]];	
+		}
+	}
+
+	return pts;
+};
+
+/**
+ * Function: getPreviewPoints
+ * 
+ * Updates the given preview state taking into account the state of the constraint handler.
+ */
+mxEdgeSegmentHandler.prototype.getPreviewPoints = function(point)
+{
+	if (this.isSource || this.isTarget)
+	{
+		return mxElbowEdgeHandler.prototype.getPreviewPoints.apply(this, arguments);
+	}
+	else
+	{
+		var pts = this.getCurrentPoints();
+		var last = this.convertPoint(pts[0].clone(), false);
+		point = this.convertPoint(point.clone(), false);
+		var result = [];
+
+		for (var i = 1; i < pts.length; i++)
+		{
+			var pt = this.convertPoint(pts[i].clone(), false);
+			
+			if (i == this.index)
+			{
+				if (Math.round(last.x - pt.x) == 0)
+		 		{
+					last.x = point.x;
+					pt.x = point.x;
+		 		}
+		 		
+				if (Math.round(last.y - pt.y) == 0)
+		 		{
+		 			last.y = point.y;
+		 			pt.y = point.y;
+		 		}
+			}
+
+			if (i < pts.length - 1)
+			{
+				result.push(pt);
+			}
+
+			last = pt;
+		}
+		
+		// Replaces single point that intersects with source or target
+		if (result.length == 1)
+		{
+			var source = this.state.getVisibleTerminalState(true);
+			var target = this.state.getVisibleTerminalState(false);
+			var scale = this.state.view.getScale();
+			var tr = this.state.view.getTranslate();
+			
+			var x = result[0].x * scale + tr.x;
+			var y = result[0].y * scale + tr.y;
+			
+			if ((source != null && mxUtils.contains(source, x, y)) ||
+				(target != null && mxUtils.contains(target, x, y)))
+			{
+				result = [point, point];
+			}
+		}
+
+		return result;
+	}
+};
+
+/**
+ * Function: updatePreviewState
+ * 
+ * Overridden to perform optimization of the edge style result.
+ */
+mxEdgeSegmentHandler.prototype.updatePreviewState = function(edge, point, terminalState, me)
+{
+	mxEdgeHandler.prototype.updatePreviewState.apply(this, arguments);
+
+	// Checks and corrects preview by running edge style again
+	if (!this.isSource && !this.isTarget)
+	{
+		point = this.convertPoint(point.clone(), false);
+		var pts = edge.absolutePoints;
+		var pt0 = pts[0];
+		var pt1 = pts[1];
+
+		var result = [];
+		
+		for (var i = 2; i < pts.length; i++)
+		{
+			var pt2 = pts[i];
+		
+			// Merges adjacent segments only if more than 2 to allow for straight edges
+			if ((Math.round(pt0.x - pt1.x) != 0 || Math.round(pt1.x - pt2.x) != 0) &&
+				(Math.round(pt0.y - pt1.y) != 0 || Math.round(pt1.y - pt2.y) != 0))
+			{
+				result.push(this.convertPoint(pt1.clone(), false));
+			}
+
+			pt0 = pt1;
+			pt1 = pt2;
+		}
+		
+		var source = this.state.getVisibleTerminalState(true);
+		var target = this.state.getVisibleTerminalState(false);
+		var rpts = this.state.absolutePoints;
+		
+		// A straight line is represented by 3 handles
+		if (result.length == 0 && (Math.round(pts[0].x - pts[pts.length - 1].x) == 0 ||
+			Math.round(pts[0].y - pts[pts.length - 1].y) == 0))
+		{
+			result = [point, point];
+		}
+		// Handles special case of transitions from straight vertical to routed
+		else if (pts.length == 5 && result.length == 2 && source != null && target != null &&
+				rpts != null && Math.round(rpts[0].x - rpts[rpts.length - 1].x) == 0)
+		{
+			var view = this.graph.getView();
+			var scale = view.getScale();
+			var tr = view.getTranslate();
+			
+			var y0 = view.getRoutingCenterY(source) / scale - tr.y;
+			
+			// Use fixed connection point y-coordinate if one exists
+			var sc = this.graph.getConnectionConstraint(edge, source, true);
+			
+			if (sc != null)
+			{
+				var pt = this.graph.getConnectionPoint(source, sc);
+				
+				if (pt != null)
+				{
+					this.convertPoint(pt, false);
+					y0 = pt.y;
+				}
+			}
+			
+			var ye = view.getRoutingCenterY(target) / scale - tr.y;
+			
+			// Use fixed connection point y-coordinate if one exists
+			var tc = this.graph.getConnectionConstraint(edge, target, false);
+			
+			if (tc)
+			{
+				var pt = this.graph.getConnectionPoint(target, tc);
+				
+				if (pt != null)
+				{
+					this.convertPoint(pt, false);
+					ye = pt.y;
+				}
+			}
+			
+			result = [new mxPoint(point.x, y0), new mxPoint(point.x, ye)];
+		}
+
+		this.points = result;
+
+		// LATER: Check if points and result are different
+		edge.view.updateFixedTerminalPoints(edge, source, target);
+		edge.view.updatePoints(edge, this.points, source, target);
+		edge.view.updateFloatingTerminalPoints(edge, source, target);
+	}
+};
+
+/**
+ * Overriden to merge edge segments.
+ */
+mxEdgeSegmentHandler.prototype.connect = function(edge, terminal, isSource, isClone, me)
+{
+	// Merges adjacent edge segments
+	var pts = this.abspoints;
+	var pt0 = pts[0];
+	var pt1 = pts[1];
+	var result = [];
+	
+	for (var i = 2; i < pts.length; i++)
+	{
+		var pt2 = pts[i];
+	
+		// Merges adjacent segments only if more than 2 to allow for straight edges
+		if ((Math.round(pt0.x - pt1.x) != 0 || Math.round(pt1.x - pt2.x) != 0) &&
+			(Math.round(pt0.y - pt1.y) != 0 || Math.round(pt1.y - pt2.y) != 0))
+		{
+			result.push(this.convertPoint(pt1.clone(), false));
+		}
+
+		pt0 = pt1;
+		pt1 = pt2;
+	}
+	
+	var model = this.graph.getModel();
+	
+	model.beginUpdate();
+	try
+	{
+		var geo = model.getGeometry(edge);
+		
+		if (geo != null)
+		{
+			geo = geo.clone();
+			geo.points = result;
+			
+			model.setGeometry(edge, geo);
+		}
+		
+		edge = mxEdgeHandler.prototype.connect.apply(this, arguments);
+	}
+	finally
+	{
+		model.endUpdate();
+	}
+	
+	return edge;
+};
+
+/**
+ * Function: getTooltipForNode
+ * 
+ * Returns no tooltips.
+ */
+mxEdgeSegmentHandler.prototype.getTooltipForNode = function(node)
+{
+	return null;
+};
+
+/**
+ * Function: createBends
+ * 
+ * Adds custom bends for the center of each segment.
+ */
+mxEdgeSegmentHandler.prototype.start = function(x, y, index)
+{
+	mxEdgeHandler.prototype.start.apply(this, arguments);
+	
+	if (this.bends[index] != null && !this.isSource && !this.isTarget)
+	{
+		mxUtils.setOpacity(this.bends[index].node, 100);
+	}
+};
+
+/**
+ * Function: createBends
+ * 
+ * Adds custom bends for the center of each segment.
+ */
+mxEdgeSegmentHandler.prototype.createBends = function()
+{
+	var bends = [];
+	
+	// Source
+	var bend = this.createHandleShape(0);
+	this.initBend(bend);
+	bend.setCursor(mxConstants.CURSOR_TERMINAL_HANDLE);
+	bends.push(bend);
+
+	var pts = this.getCurrentPoints();
+
+	// Waypoints (segment handles)
+	if (this.graph.isCellBendable(this.state.cell))
+	{
+		if (this.points == null)
+		{
+			this.points = [];
+		}
+
+		for (var i = 0; i < pts.length - 1; i++)
+		{
+			bend = this.createVirtualBend();
+			bends.push(bend);
+			var horizontal = Math.round(pts[i].x - pts[i + 1].x) == 0;
+			
+			// Special case where dy is 0 as well
+			if (Math.round(pts[i].y - pts[i + 1].y) == 0 && i < pts.length - 2)
+			{
+				horizontal = Math.round(pts[i].x - pts[i + 2].x) == 0;
+			}
+			
+			bend.setCursor((horizontal) ? 'col-resize' : 'row-resize');
+			this.points.push(new mxPoint(0,0));
+		}
+	}
+
+	// Target
+	var bend = this.createHandleShape(pts.length);
+	this.initBend(bend);
+	bend.setCursor(mxConstants.CURSOR_TERMINAL_HANDLE);
+	bends.push(bend);
+
+	return bends;
+};
+
+/**
+ * Function: redraw
+ * 
+ * Overridden to invoke <refresh> before the redraw.
+ */
+mxEdgeSegmentHandler.prototype.redraw = function()
+{
+	this.refresh();
+	mxEdgeHandler.prototype.redraw.apply(this, arguments);
+};
+
+/**
+ * Function: redrawInnerBends
+ * 
+ * Updates the position of the custom bends.
+ */
+mxEdgeSegmentHandler.prototype.redrawInnerBends = function(p0, pe)
+{
+	if (this.graph.isCellBendable(this.state.cell))
+	{
+		var pts = this.getCurrentPoints();
+		
+		if (pts != null && pts.length > 1)
+		{
+			var straight = false;
+			
+			// Puts handle in the center of straight edges
+			if (pts.length == 4 && Math.round(pts[1].x - pts[2].x) == 0 && Math.round(pts[1].y - pts[2].y) == 0)
+			{
+				straight = true;
+				
+				if (Math.round(pts[0].y - pts[pts.length - 1].y) == 0)
+				{
+					var cx = pts[0].x + (pts[pts.length - 1].x - pts[0].x) / 2;
+					pts[1] = new mxPoint(cx, pts[1].y);
+					pts[2] = new mxPoint(cx, pts[2].y);
+				}
+				else
+				{
+					var cy = pts[0].y + (pts[pts.length - 1].y - pts[0].y) / 2;
+					pts[1] = new mxPoint(pts[1].x, cy);
+					pts[2] = new mxPoint(pts[2].x, cy);
+				}
+			}
+			
+			for (var i = 0; i < pts.length - 1; i++)
+			{
+				if (this.bends[i + 1] != null)
+				{
+		 			var p0 = pts[i];
+	 				var pe = pts[i + 1];
+			 		var pt = new mxPoint(p0.x + (pe.x - p0.x) / 2, p0.y + (pe.y - p0.y) / 2);
+			 		var b = this.bends[i + 1].bounds;
+			 		this.bends[i + 1].bounds = new mxRectangle(Math.floor(pt.x - b.width / 2),
+			 				Math.floor(pt.y - b.height / 2), b.width, b.height);
+				 	this.bends[i + 1].redraw();
+				 	
+				 	if (this.manageLabelHandle)
+					{
+						this.checkLabelHandle(this.bends[i + 1].bounds);
+					}
+				}
+			}
+			
+			if (straight)
+			{
+				mxUtils.setOpacity(this.bends[1].node, this.virtualBendOpacity);
+				mxUtils.setOpacity(this.bends[3].node, this.virtualBendOpacity);
+			}
+		}
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/handler/mxElbowEdgeHandler.js b/airavata-kubernetes/workflow-composer/src/js/handler/mxElbowEdgeHandler.js
new file mode 100644
index 0000000..e408f04
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/handler/mxElbowEdgeHandler.js
@@ -0,0 +1,229 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxElbowEdgeHandler
+ *
+ * Graph event handler that reconnects edges and modifies control points and
+ * the edge label location. Uses <mxTerminalMarker> for finding and
+ * highlighting new source and target vertices. This handler is automatically
+ * created in <mxGraph.createHandler>. It extends <mxEdgeHandler>.
+ * 
+ * Constructor: mxEdgeHandler
+ *
+ * Constructs an edge handler for the specified <mxCellState>.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> of the cell to be modified.
+ */
+function mxElbowEdgeHandler(state)
+{
+	mxEdgeHandler.call(this, state);
+};
+
+/**
+ * Extends mxEdgeHandler.
+ */
+mxUtils.extend(mxElbowEdgeHandler, mxEdgeHandler);
+
+/**
+ * Specifies if a double click on the middle handle should call
+ * <mxGraph.flipEdge>. Default is true.
+ */
+mxElbowEdgeHandler.prototype.flipEnabled = true;
+
+/**
+ * Variable: doubleClickOrientationResource
+ * 
+ * Specifies the resource key for the tooltip to be displayed on the single
+ * control point for routed edges. If the resource for this key does not
+ * exist then the value is used as the error message. Default is
+ * 'doubleClickOrientation'.
+ */
+mxElbowEdgeHandler.prototype.doubleClickOrientationResource =
+	(mxClient.language != 'none') ? 'doubleClickOrientation' : '';
+
+/**
+ * Function: createBends
+ * 
+ * Overrides <mxEdgeHandler.createBends> to create custom bends.
+ */
+ mxElbowEdgeHandler.prototype.createBends = function()
+ {
+	var bends = [];
+	
+	// Source
+	var bend = this.createHandleShape(0);
+	this.initBend(bend);
+	bend.setCursor(mxConstants.CURSOR_TERMINAL_HANDLE);
+	bends.push(bend);
+
+	// Virtual
+	bends.push(this.createVirtualBend(mxUtils.bind(this, function(evt)
+	{
+		if (!mxEvent.isConsumed(evt) && this.flipEnabled)
+		{
+			this.graph.flipEdge(this.state.cell, evt);
+			mxEvent.consume(evt);
+		}
+	})));
+	this.points.push(new mxPoint(0,0));
+
+	// Target
+	bend = this.createHandleShape(2);
+	this.initBend(bend);
+	bend.setCursor(mxConstants.CURSOR_TERMINAL_HANDLE);
+	bends.push(bend);
+	
+	return bends;
+ };
+
+/**
+ * Function: createVirtualBend
+ * 
+ * Creates a virtual bend that supports double clicking and calls
+ * <mxGraph.flipEdge>.
+ */
+mxElbowEdgeHandler.prototype.createVirtualBend = function(dblClickHandler)
+{
+	var bend = this.createHandleShape();
+	this.initBend(bend, dblClickHandler);
+
+	bend.setCursor(this.getCursorForBend());
+
+	if (!this.graph.isCellBendable(this.state.cell))
+	{
+		bend.node.style.display = 'none';
+	}
+
+	return bend;
+};
+
+/**
+ * Function: getCursorForBend
+ * 
+ * Returns the cursor to be used for the bend.
+ */
+mxElbowEdgeHandler.prototype.getCursorForBend = function()
+{
+	return (this.state.style[mxConstants.STYLE_EDGE] == mxEdgeStyle.TopToBottom ||
+		this.state.style[mxConstants.STYLE_EDGE] == mxConstants.EDGESTYLE_TOPTOBOTTOM ||
+		((this.state.style[mxConstants.STYLE_EDGE] == mxEdgeStyle.ElbowConnector ||
+		this.state.style[mxConstants.STYLE_EDGE] == mxConstants.EDGESTYLE_ELBOW)&&
+		this.state.style[mxConstants.STYLE_ELBOW] == mxConstants.ELBOW_VERTICAL)) ? 
+		'row-resize' : 'col-resize';
+};
+
+/**
+ * Function: getTooltipForNode
+ * 
+ * Returns the tooltip for the given node.
+ */
+mxElbowEdgeHandler.prototype.getTooltipForNode = function(node)
+{
+	var tip = null;
+	
+	if (this.bends != null && this.bends[1] != null && (node == this.bends[1].node ||
+		node.parentNode == this.bends[1].node))
+	{
+		tip = this.doubleClickOrientationResource;
+		tip = mxResources.get(tip) || tip; // translate
+	}
+
+	return tip;
+};
+
+/**
+ * Function: convertPoint
+ * 
+ * Converts the given point in-place from screen to unscaled, untranslated
+ * graph coordinates and applies the grid.
+ * 
+ * Parameters:
+ * 
+ * point - <mxPoint> to be converted.
+ * gridEnabled - Boolean that specifies if the grid should be applied.
+ */
+mxElbowEdgeHandler.prototype.convertPoint = function(point, gridEnabled)
+{
+	var scale = this.graph.getView().getScale();
+	var tr = this.graph.getView().getTranslate();
+	var origin = this.state.origin;
+	
+	if (gridEnabled)
+	{
+		point.x = this.graph.snap(point.x);
+		point.y = this.graph.snap(point.y);
+	}
+	
+	point.x = Math.round(point.x / scale - tr.x - origin.x);
+	point.y = Math.round(point.y / scale - tr.y - origin.y);
+	
+	return point;
+};
+
+/**
+ * Function: redrawInnerBends
+ * 
+ * Updates and redraws the inner bends.
+ * 
+ * Parameters:
+ * 
+ * p0 - <mxPoint> that represents the location of the first point.
+ * pe - <mxPoint> that represents the location of the last point.
+ */
+mxElbowEdgeHandler.prototype.redrawInnerBends = function(p0, pe)
+{
+	var g = this.graph.getModel().getGeometry(this.state.cell);
+	var pts = this.state.absolutePoints;
+	var pt = null;
+
+	// Keeps the virtual bend on the edge shape
+	if (pts.length > 1)
+	{
+		p0 = pts[1];
+		pe = pts[pts.length - 2];
+	}
+	else if (g.points != null && g.points.length > 0)
+	{
+		pt = pts[0];
+	}
+	
+	if (pt == null)
+	{
+		pt = new mxPoint(p0.x + (pe.x - p0.x) / 2, p0.y + (pe.y - p0.y) / 2);
+	}
+	else
+	{
+		pt = new mxPoint(this.graph.getView().scale * (pt.x + this.graph.getView().translate.x + this.state.origin.x),
+				this.graph.getView().scale * (pt.y + this.graph.getView().translate.y + this.state.origin.y));
+	}
+
+	// Makes handle slightly bigger if the yellow  label handle
+	// exists and intersects this green handle
+	var b = this.bends[1].bounds;
+	var w = b.width;
+	var h = b.height;
+	var bounds = new mxRectangle(Math.round(pt.x - w / 2), Math.round(pt.y - h / 2), w, h);
+
+	if (this.manageLabelHandle)
+	{
+		this.checkLabelHandle(bounds);
+	}
+	else if (this.handleImage == null && this.labelShape.visible && mxUtils.intersects(bounds, this.labelShape.bounds))
+	{
+		w = mxConstants.HANDLE_SIZE + 3;
+		h = mxConstants.HANDLE_SIZE + 3;
+		bounds = new mxRectangle(Math.floor(pt.x - w / 2), Math.floor(pt.y - h / 2), w, h);
+	}
+
+	this.bends[1].bounds = bounds;
+	this.bends[1].redraw();
+	
+	if (this.manageLabelHandle)
+	{
+		this.checkLabelHandle(this.bends[1].bounds);
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/handler/mxGraphHandler.js b/airavata-kubernetes/workflow-composer/src/js/handler/mxGraphHandler.js
new file mode 100644
index 0000000..0619d67
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/handler/mxGraphHandler.js
@@ -0,0 +1,1074 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxGraphHandler
+ * 
+ * Graph event handler that handles selection. Individual cells are handled
+ * separately using <mxVertexHandler> or one of the edge handlers. These
+ * handlers are created using <mxGraph.createHandler> in
+ * <mxGraphSelectionModel.cellAdded>.
+ * 
+ * To avoid the container to scroll a moved cell into view, set
+ * <scrollAfterMove> to false.
+ * 
+ * Constructor: mxGraphHandler
+ * 
+ * Constructs an event handler that creates handles for the
+ * selection cells.
+ * 
+ * Parameters:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ */
+function mxGraphHandler(graph)
+{
+	this.graph = graph;
+	this.graph.addMouseListener(this);
+	
+	// Repaints the handler after autoscroll
+	this.panHandler = mxUtils.bind(this, function()
+	{
+		this.updatePreviewShape();
+		this.updateHint();
+	});
+	
+	this.graph.addListener(mxEvent.PAN, this.panHandler);
+	
+	// Handles escape keystrokes
+	this.escapeHandler = mxUtils.bind(this, function(sender, evt)
+	{
+		this.reset();
+	});
+	
+	this.graph.addListener(mxEvent.ESCAPE, this.escapeHandler);
+};
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxGraphHandler.prototype.graph = null;
+
+/**
+ * Variable: maxCells
+ * 
+ * Defines the maximum number of cells to paint subhandles
+ * for. Default is 50 for Firefox and 20 for IE. Set this
+ * to 0 if you want an unlimited number of handles to be
+ * displayed. This is only recommended if the number of
+ * cells in the graph is limited to a small number, eg.
+ * 500.
+ */
+mxGraphHandler.prototype.maxCells = (mxClient.IS_IE) ? 20 : 50;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies if events are handled. Default is true.
+ */
+mxGraphHandler.prototype.enabled = true;
+
+/**
+ * Variable: highlightEnabled
+ * 
+ * Specifies if drop targets under the mouse should be enabled. Default is
+ * true.
+ */
+mxGraphHandler.prototype.highlightEnabled = true;
+
+/**
+ * Variable: cloneEnabled
+ * 
+ * Specifies if cloning by control-drag is enabled. Default is true.
+ */
+mxGraphHandler.prototype.cloneEnabled = true;
+
+/**
+ * Variable: moveEnabled
+ * 
+ * Specifies if moving is enabled. Default is true.
+ */
+mxGraphHandler.prototype.moveEnabled = true;
+
+/**
+ * Variable: guidesEnabled
+ * 
+ * Specifies if other cells should be used for snapping the right, center or
+ * left side of the current selection. Default is false.
+ */
+mxGraphHandler.prototype.guidesEnabled = false;
+
+/**
+ * Variable: guide
+ * 
+ * Holds the <mxGuide> instance that is used for alignment.
+ */
+mxGraphHandler.prototype.guide = null;
+
+/**
+ * Variable: currentDx
+ * 
+ * Stores the x-coordinate of the current mouse move.
+ */
+mxGraphHandler.prototype.currentDx = null;
+
+/**
+ * Variable: currentDy
+ * 
+ * Stores the y-coordinate of the current mouse move.
+ */
+mxGraphHandler.prototype.currentDy = null;
+
+/**
+ * Variable: updateCursor
+ * 
+ * Specifies if a move cursor should be shown if the mouse is over a movable
+ * cell. Default is true.
+ */
+mxGraphHandler.prototype.updateCursor = true;
+
+/**
+ * Variable: selectEnabled
+ * 
+ * Specifies if selecting is enabled. Default is true.
+ */
+mxGraphHandler.prototype.selectEnabled = true;
+
+/**
+ * Variable: removeCellsFromParent
+ * 
+ * Specifies if cells may be moved out of their parents. Default is true.
+ */
+mxGraphHandler.prototype.removeCellsFromParent = true;
+
+/**
+ * Variable: connectOnDrop
+ * 
+ * Specifies if drop events are interpreted as new connections if no other
+ * drop action is defined. Default is false.
+ */
+mxGraphHandler.prototype.connectOnDrop = false;
+
+/**
+ * Variable: scrollOnMove
+ * 
+ * Specifies if the view should be scrolled so that a moved cell is
+ * visible. Default is true.
+ */
+mxGraphHandler.prototype.scrollOnMove = true;
+
+/**
+ * Variable: minimumSize
+ * 
+ * Specifies the minimum number of pixels for the width and height of a
+ * selection border. Default is 6.
+ */
+mxGraphHandler.prototype.minimumSize = 6;
+
+/**
+ * Variable: previewColor
+ * 
+ * Specifies the color of the preview shape. Default is black.
+ */
+mxGraphHandler.prototype.previewColor = 'black';
+
+/**
+ * Variable: htmlPreview
+ * 
+ * Specifies if the graph container should be used for preview. If this is used
+ * then drop target detection relies entirely on <mxGraph.getCellAt> because
+ * the HTML preview does not "let events through". Default is false.
+ */
+mxGraphHandler.prototype.htmlPreview = false;
+
+/**
+ * Variable: shape
+ * 
+ * Reference to the <mxShape> that represents the preview.
+ */
+mxGraphHandler.prototype.shape = null;
+
+/**
+ * Variable: scaleGrid
+ * 
+ * Specifies if the grid should be scaled. Default is false.
+ */
+mxGraphHandler.prototype.scaleGrid = false;
+
+/**
+ * Variable: rotationEnabled
+ * 
+ * Specifies if the bounding box should allow for rotation. Default is true.
+ */
+mxGraphHandler.prototype.rotationEnabled = true;
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns <enabled>.
+ */
+mxGraphHandler.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ * 
+ * Sets <enabled>.
+ */
+mxGraphHandler.prototype.setEnabled = function(value)
+{
+	this.enabled = value;
+};
+
+/**
+ * Function: isCloneEnabled
+ * 
+ * Returns <cloneEnabled>.
+ */
+mxGraphHandler.prototype.isCloneEnabled = function()
+{
+	return this.cloneEnabled;
+};
+
+/**
+ * Function: setCloneEnabled
+ * 
+ * Sets <cloneEnabled>.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean that specifies the new clone enabled state.
+ */
+mxGraphHandler.prototype.setCloneEnabled = function(value)
+{
+	this.cloneEnabled = value;
+};
+
+/**
+ * Function: isMoveEnabled
+ * 
+ * Returns <moveEnabled>.
+ */
+mxGraphHandler.prototype.isMoveEnabled = function()
+{
+	return this.moveEnabled;
+};
+
+/**
+ * Function: setMoveEnabled
+ * 
+ * Sets <moveEnabled>.
+ */
+mxGraphHandler.prototype.setMoveEnabled = function(value)
+{
+	this.moveEnabled = value;
+};
+
+/**
+ * Function: isSelectEnabled
+ * 
+ * Returns <selectEnabled>.
+ */
+mxGraphHandler.prototype.isSelectEnabled = function()
+{
+	return this.selectEnabled;
+};
+
+/**
+ * Function: setSelectEnabled
+ * 
+ * Sets <selectEnabled>.
+ */
+mxGraphHandler.prototype.setSelectEnabled = function(value)
+{
+	this.selectEnabled = value;
+};
+
+/**
+ * Function: isRemoveCellsFromParent
+ * 
+ * Returns <removeCellsFromParent>.
+ */
+mxGraphHandler.prototype.isRemoveCellsFromParent = function()
+{
+	return this.removeCellsFromParent;
+};
+
+/**
+ * Function: setRemoveCellsFromParent
+ * 
+ * Sets <removeCellsFromParent>.
+ */
+mxGraphHandler.prototype.setRemoveCellsFromParent = function(value)
+{
+	this.removeCellsFromParent = value;
+};
+
+/**
+ * Function: getInitialCellForEvent
+ * 
+ * Hook to return initial cell for the given event.
+ */
+mxGraphHandler.prototype.getInitialCellForEvent = function(me)
+{
+	return me.getCell();
+};
+
+/**
+ * Function: isDelayedSelection
+ * 
+ * Hook to return true for delayed selections.
+ */
+mxGraphHandler.prototype.isDelayedSelection = function(cell, me)
+{
+	return this.graph.isCellSelected(cell);
+};
+
+/**
+ * Function: consumeMouseEvent
+ * 
+ * Consumes the given mouse event. NOTE: This may be used to enable click
+ * events for links in labels on iOS as follows as consuming the initial
+ * touchStart disables firing the subsequent click evnent on the link.
+ * 
+ * <code>
+ * mxGraphHandler.prototype.consumeMouseEvent = function(evtName, me)
+ * {
+ *   var source = mxEvent.getSource(me.getEvent());
+ *   
+ *   if (!mxEvent.isTouchEvent(me.getEvent()) || source.nodeName != 'A')
+ *   {
+ *     me.consume();
+ *   }
+ * }
+ * </code>
+ */
+mxGraphHandler.prototype.consumeMouseEvent = function(evtName, me)
+{
+	me.consume();
+};
+
+/**
+ * Function: mouseDown
+ * 
+ * Handles the event by selecing the given cell and creating a handle for
+ * it. By consuming the event all subsequent events of the gesture are
+ * redirected to this handler.
+ */
+mxGraphHandler.prototype.mouseDown = function(sender, me)
+{
+	if (!me.isConsumed() && this.isEnabled() && this.graph.isEnabled() &&
+		me.getState() != null && !mxEvent.isMultiTouchEvent(me.getEvent()))
+	{
+		var cell = this.getInitialCellForEvent(me);
+		this.delayedSelection = this.isDelayedSelection(cell, me);
+		this.cell = null;
+		
+		if (this.isSelectEnabled() && !this.delayedSelection)
+		{
+			this.graph.selectCellForEvent(cell, me.getEvent());
+		}
+
+		if (this.isMoveEnabled())
+		{
+			var model = this.graph.model;
+			var geo = model.getGeometry(cell);
+
+			if (this.graph.isCellMovable(cell) && ((!model.isEdge(cell) || this.graph.getSelectionCount() > 1 ||
+				(geo.points != null && geo.points.length > 0) || model.getTerminal(cell, true) == null ||
+				model.getTerminal(cell, false) == null) || this.graph.allowDanglingEdges || 
+				(this.graph.isCloneEvent(me.getEvent()) && this.graph.isCellsCloneable())))
+			{
+				this.start(cell, me.getX(), me.getY());
+			}
+			else if (this.delayedSelection)
+			{
+				this.cell = cell;
+			}
+
+			this.cellWasClicked = true;
+			this.consumeMouseEvent(mxEvent.MOUSE_DOWN, me);
+		}
+	}
+};
+
+/**
+ * Function: getGuideStates
+ * 
+ * Creates an array of cell states which should be used as guides.
+ */
+mxGraphHandler.prototype.getGuideStates = function()
+{
+	var parent = this.graph.getDefaultParent();
+	var model = this.graph.getModel();
+	
+	var filter = mxUtils.bind(this, function(cell)
+	{
+		return this.graph.view.getState(cell) != null &&
+			model.isVertex(cell) &&
+			model.getGeometry(cell) != null &&
+			!model.getGeometry(cell).relative;
+	});
+	
+	return this.graph.view.getCellStates(model.filterDescendants(filter, parent));
+};
+
+/**
+ * Function: getCells
+ * 
+ * Returns the cells to be modified by this handler. This implementation
+ * returns all selection cells that are movable, or the given initial cell if
+ * the given cell is not selected and movable. This handles the case of moving
+ * unselectable or unselected cells.
+ * 
+ * Parameters:
+ * 
+ * initialCell - <mxCell> that triggered this handler.
+ */
+mxGraphHandler.prototype.getCells = function(initialCell)
+{
+	if (!this.delayedSelection && this.graph.isCellMovable(initialCell))
+	{
+		return [initialCell];
+	}
+	else
+	{
+		return this.graph.getMovableCells(this.graph.getSelectionCells());
+	}
+};
+
+/**
+ * Function: getPreviewBounds
+ * 
+ * Returns the <mxRectangle> used as the preview bounds for
+ * moving the given cells.
+ */
+mxGraphHandler.prototype.getPreviewBounds = function(cells)
+{
+	var bounds = this.getBoundingBox(cells);
+	
+	if (bounds != null)
+	{
+		// Corrects width and height
+		bounds.width = Math.max(0, bounds.width - 1);
+		bounds.height = Math.max(0, bounds.height - 1);
+		
+		if (bounds.width < this.minimumSize)
+		{
+			var dx = this.minimumSize - bounds.width;
+			bounds.x -= dx / 2;
+			bounds.width = this.minimumSize;
+		}
+		else
+		{
+			bounds.x = Math.round(bounds.x);
+			bounds.width = Math.ceil(bounds.width);
+		}
+		
+		var tr = this.graph.view.translate;
+		var s = this.graph.view.scale;
+		
+		if (bounds.height < this.minimumSize)
+		{
+			var dy = this.minimumSize - bounds.height;
+			bounds.y -= dy / 2;
+			bounds.height = this.minimumSize;
+		}
+		else
+		{
+			bounds.y = Math.round(bounds.y);
+			bounds.height = Math.ceil(bounds.height);
+		}
+	}
+	
+	return bounds;
+};
+
+/**
+ * Function: getBoundingBox
+ * 
+ * Returns the union of the <mxCellStates> for the given array of <mxCells>.
+ * For vertices, this method uses the bounding box of the corresponding shape
+ * if one exists. The bounding box of the corresponding text label and all
+ * controls and overlays are ignored. See also: <mxGraphView.getBounds> and
+ * <mxGraph.getBoundingBox>.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> whose bounding box should be returned.
+ */
+mxGraphHandler.prototype.getBoundingBox = function(cells)
+{
+	var result = null;
+	
+	if (cells != null && cells.length > 0)
+	{
+		var model = this.graph.getModel();
+		
+		for (var i = 0; i < cells.length; i++)
+		{
+			if (model.isVertex(cells[i]) || model.isEdge(cells[i]))
+			{
+				var state = this.graph.view.getState(cells[i]);
+			
+				if (state != null)
+				{
+					var bbox = state;
+					
+					if (model.isVertex(cells[i]) && state.shape != null && state.shape.boundingBox != null)
+					{
+						bbox = state.shape.boundingBox;
+					}
+					
+					if (result == null)
+					{
+						result = mxRectangle.fromRectangle(bbox);
+					}
+					else
+					{
+						result.add(bbox);
+					}
+				}
+			}
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: createPreviewShape
+ * 
+ * Creates the shape used to draw the preview for the given bounds.
+ */
+mxGraphHandler.prototype.createPreviewShape = function(bounds)
+{
+	var shape = new mxRectangleShape(bounds, null, this.previewColor);
+	shape.isDashed = true;
+	
+	if (this.htmlPreview)
+	{
+		shape.dialect = mxConstants.DIALECT_STRICTHTML;
+		shape.init(this.graph.container);
+	}
+	else
+	{
+		// Makes sure to use either VML or SVG shapes in order to implement
+		// event-transparency on the background area of the rectangle since
+		// HTML shapes do not let mouseevents through even when transparent
+		shape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
+			mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+		shape.init(this.graph.getView().getOverlayPane());
+		shape.pointerEvents = false;
+		
+		// Workaround for artifacts on iOS
+		if (mxClient.IS_IOS)
+		{
+			shape.getSvgScreenOffset = function()
+			{
+				return 0;
+			};
+		}
+	}
+	
+	return shape;
+};
+
+/**
+ * Function: start
+ * 
+ * Starts the handling of the mouse gesture.
+ */
+mxGraphHandler.prototype.start = function(cell, x, y)
+{
+	this.cell = cell;
+	this.first = mxUtils.convertPoint(this.graph.container, x, y);
+	this.cells = this.getCells(this.cell);
+	this.bounds = this.graph.getView().getBounds(this.cells);
+	this.pBounds = this.getPreviewBounds(this.cells);
+
+	if (this.guidesEnabled)
+	{
+		this.guide = new mxGuide(this.graph, this.getGuideStates());
+	}
+};
+
+/**
+ * Function: useGuidesForEvent
+ * 
+ * Returns true if the guides should be used for the given <mxMouseEvent>.
+ * This implementation returns <mxGuide.isEnabledForEvent>.
+ */
+mxGraphHandler.prototype.useGuidesForEvent = function(me)
+{
+	return (this.guide != null) ? this.guide.isEnabledForEvent(me.getEvent()) : true;
+};
+
+
+/**
+ * Function: snap
+ * 
+ * Snaps the given vector to the grid and returns the given mxPoint instance.
+ */
+mxGraphHandler.prototype.snap = function(vector)
+{
+	var scale = (this.scaleGrid) ? this.graph.view.scale : 1;
+	
+	vector.x = this.graph.snap(vector.x / scale) * scale;
+	vector.y = this.graph.snap(vector.y / scale) * scale;
+	
+	return vector;
+};
+
+/**
+ * Function: getDelta
+ * 
+ * Returns an <mxPoint> that represents the vector for moving the cells
+ * for the given <mxMouseEvent>.
+ */
+mxGraphHandler.prototype.getDelta = function(me)
+{
+	var point = mxUtils.convertPoint(this.graph.container, me.getX(), me.getY());
+	var s = this.graph.view.scale;
+	
+	return new mxPoint(this.roundLength((point.x - this.first.x) / s) * s,
+		this.roundLength((point.y - this.first.y) / s) * s);
+};
+
+/**
+ * Function: updateHint
+ * 
+ * Hook for subclassers do show details while the handler is active.
+ */
+mxGraphHandler.prototype.updateHint = function(me) { };
+
+/**
+ * Function: removeHint
+ * 
+ * Hooks for subclassers to hide details when the handler gets inactive.
+ */
+mxGraphHandler.prototype.removeHint = function() { };
+
+/**
+ * Function: roundLength
+ * 
+ * Hook for rounding the unscaled vector. This uses Math.round.
+ */
+mxGraphHandler.prototype.roundLength = function(length)
+{
+	return Math.round(length);
+};
+
+/**
+ * Function: mouseMove
+ * 
+ * Handles the event by highlighting possible drop targets and updating the
+ * preview.
+ */
+mxGraphHandler.prototype.mouseMove = function(sender, me)
+{
+	var graph = this.graph;
+
+	if (!me.isConsumed() && graph.isMouseDown && this.cell != null &&
+		this.first != null && this.bounds != null)
+	{
+		// Stops moving if a multi touch event is received
+		if (mxEvent.isMultiTouchEvent(me.getEvent()))
+		{
+			this.reset();
+			return;
+		}
+		
+		var delta = this.getDelta(me);
+		var dx = delta.x;
+		var dy = delta.y;
+		var tol = graph.tolerance;
+
+		if (this.shape != null || Math.abs(dx) > tol || Math.abs(dy) > tol)
+		{
+			// Highlight is used for highlighting drop targets
+			if (this.highlight == null)
+			{
+				this.highlight = new mxCellHighlight(this.graph,
+					mxConstants.DROP_TARGET_COLOR, 3);
+			}
+			
+			if (this.shape == null)
+			{
+				this.shape = this.createPreviewShape(this.bounds);
+			}
+			
+			var gridEnabled = graph.isGridEnabledEvent(me.getEvent());
+			var hideGuide = true;
+			
+			if (this.guide != null && this.useGuidesForEvent(me))
+			{
+				delta = this.guide.move(this.bounds, new mxPoint(dx, dy), gridEnabled);
+				hideGuide = false;
+				dx = delta.x;
+				dy = delta.y;
+			}
+			else if (gridEnabled)
+			{
+				var trx = graph.getView().translate;
+				var scale = graph.getView().scale;				
+				
+				var tx = this.bounds.x - (graph.snap(this.bounds.x / scale - trx.x) + trx.x) * scale;
+				var ty = this.bounds.y - (graph.snap(this.bounds.y / scale - trx.y) + trx.y) * scale;
+				var v = this.snap(new mxPoint(dx, dy));
+			
+				dx = v.x - tx;
+				dy = v.y - ty;
+			}
+			
+			if (this.guide != null && hideGuide)
+			{
+				this.guide.hide();
+			}
+
+			// Constrained movement if shift key is pressed
+			if (graph.isConstrainedEvent(me.getEvent()))
+			{
+				if (Math.abs(dx) > Math.abs(dy))
+				{
+					dy = 0;
+				}
+				else
+				{
+					dx = 0;
+				}
+			}
+
+			this.currentDx = dx;
+			this.currentDy = dy;
+			this.updatePreviewShape();
+
+			var target = null;
+			var cell = me.getCell();
+
+			var clone = graph.isCloneEvent(me.getEvent()) && graph.isCellsCloneable() && this.isCloneEnabled();
+			
+			if (graph.isDropEnabled() && this.highlightEnabled)
+			{
+				// Contains a call to getCellAt to find the cell under the mouse
+				target = graph.getDropTarget(this.cells, me.getEvent(), cell, clone);
+			}
+
+			var state = graph.getView().getState(target);
+			var highlight = false;
+			
+			if (state != null && (graph.model.getParent(this.cell) != target || clone))
+			{
+			    if (this.target != target)
+			    {
+				    this.target = target;
+				    this.setHighlightColor(mxConstants.DROP_TARGET_COLOR);
+				}
+			    
+			    highlight = true;
+			}
+			else
+			{
+				this.target = null;
+
+				if (this.connectOnDrop && cell != null && this.cells.length == 1 &&
+					graph.getModel().isVertex(cell) && graph.isCellConnectable(cell))
+				{
+					state = graph.getView().getState(cell);
+					
+					if (state != null)
+					{
+						var error = graph.getEdgeValidationError(null, this.cell, cell);
+						var color = (error == null) ?
+							mxConstants.VALID_COLOR :
+							mxConstants.INVALID_CONNECT_TARGET_COLOR;
+						this.setHighlightColor(color);
+						highlight = true;
+					}
+				}
+			}
+			
+			if (state != null && highlight)
+			{
+				this.highlight.highlight(state);
+			}
+			else
+			{
+				this.highlight.hide();
+			}
+		}
+
+		this.updateHint(me);
+		this.consumeMouseEvent(mxEvent.MOUSE_MOVE, me);
+		
+		// Cancels the bubbling of events to the container so
+		// that the droptarget is not reset due to an mouseMove
+		// fired on the container with no associated state.
+		mxEvent.consume(me.getEvent());
+	}
+	else if ((this.isMoveEnabled() || this.isCloneEnabled()) && this.updateCursor &&
+		!me.isConsumed() && me.getState() != null && !graph.isMouseDown)
+	{
+		var cursor = graph.getCursorForMouseEvent(me);
+		
+		if (cursor == null && graph.isEnabled() && graph.isCellMovable(me.getCell()))
+		{
+			if (graph.getModel().isEdge(me.getCell()))
+			{
+				cursor = mxConstants.CURSOR_MOVABLE_EDGE;
+			}
+			else
+			{
+				cursor = mxConstants.CURSOR_MOVABLE_VERTEX;
+			}
+		}
+
+		// Sets the cursor on the original source state under the mouse
+		// instead of the event source state which can be the parent
+		if (me.sourceState != null)
+		{
+			me.sourceState.setCursor(cursor);
+		}
+	}
+};
+
+/**
+ * Function: updatePreviewShape
+ * 
+ * Updates the bounds of the preview shape.
+ */
+mxGraphHandler.prototype.updatePreviewShape = function()
+{
+	if (this.shape != null)
+	{
+		this.shape.bounds = new mxRectangle(Math.round(this.pBounds.x + this.currentDx - this.graph.panDx),
+				Math.round(this.pBounds.y + this.currentDy - this.graph.panDy), this.pBounds.width, this.pBounds.height);
+		this.shape.redraw();
+	}
+};
+
+/**
+ * Function: setHighlightColor
+ * 
+ * Sets the color of the rectangle used to highlight drop targets.
+ * 
+ * Parameters:
+ * 
+ * color - String that represents the new highlight color.
+ */
+mxGraphHandler.prototype.setHighlightColor = function(color)
+{
+	if (this.highlight != null)
+	{
+		this.highlight.setHighlightColor(color);
+	}
+};
+
+/**
+ * Function: mouseUp
+ * 
+ * Handles the event by applying the changes to the selection cells.
+ */
+mxGraphHandler.prototype.mouseUp = function(sender, me)
+{
+	if (!me.isConsumed())
+	{
+		var graph = this.graph;
+		
+		if (this.cell != null && this.first != null && this.shape != null &&
+			this.currentDx != null && this.currentDy != null)
+		{
+			var cell = me.getCell();
+			
+			if (this.connectOnDrop && this.target == null && cell != null && graph.getModel().isVertex(cell) &&
+				graph.isCellConnectable(cell) && graph.isEdgeValid(null, this.cell, cell))
+			{
+				graph.connectionHandler.connect(this.cell, cell, me.getEvent());
+			}
+			else
+			{
+				var clone = graph.isCloneEvent(me.getEvent()) && graph.isCellsCloneable() && this.isCloneEnabled();
+				var scale = graph.getView().scale;
+				var dx = this.roundLength(this.currentDx / scale);
+				var dy = this.roundLength(this.currentDy / scale);
+				var target = this.target;
+				
+				if (graph.isSplitEnabled() && graph.isSplitTarget(target, this.cells, me.getEvent()))
+				{
+					graph.splitEdge(target, this.cells, null, dx, dy);
+				}
+				else
+				{
+					this.moveCells(this.cells, dx, dy, clone, this.target, me.getEvent());
+				}
+			}
+		}
+		else if (this.isSelectEnabled() && this.delayedSelection && this.cell != null)
+		{
+			this.selectDelayed(me);
+		}
+	}
+
+	// Consumes the event if a cell was initially clicked
+	if (this.cellWasClicked)
+	{
+		this.consumeMouseEvent(mxEvent.MOUSE_UP, me);
+	}
+
+	this.reset();
+};
+
+/**
+ * Function: selectDelayed
+ * 
+ * Implements the delayed selection for the given mouse event.
+ */
+mxGraphHandler.prototype.selectDelayed = function(me)
+{
+	if (!this.graph.isCellSelected(this.cell) || !this.graph.popupMenuHandler.isPopupTrigger(me))
+	{
+		this.graph.selectCellForEvent(this.cell, me.getEvent());
+	}
+};
+
+/**
+ * Function: reset
+ * 
+ * Resets the state of this handler.
+ */
+mxGraphHandler.prototype.reset = function()
+{
+	this.destroyShapes();
+	this.removeHint();
+	
+	this.cellWasClicked = false;
+	this.delayedSelection = false;
+	this.currentDx = null;
+	this.currentDy = null;
+	this.guides = null;
+	this.first = null;
+	this.cell = null;
+	this.target = null;
+};
+
+/**
+ * Function: shouldRemoveCellsFromParent
+ * 
+ * Returns true if the given cells should be removed from the parent for the specified
+ * mousereleased event.
+ */
+mxGraphHandler.prototype.shouldRemoveCellsFromParent = function(parent, cells, evt)
+{
+	if (this.graph.getModel().isVertex(parent))
+	{
+		var pState = this.graph.getView().getState(parent);
+		
+		if (pState != null)
+		{
+			var pt = mxUtils.convertPoint(this.graph.container,
+				mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+			var alpha = mxUtils.toRadians(mxUtils.getValue(pState.style, mxConstants.STYLE_ROTATION) || 0);
+			
+			if (alpha != 0)
+			{
+				var cos = Math.cos(-alpha);
+				var sin = Math.sin(-alpha);
+				var cx = new mxPoint(pState.getCenterX(), pState.getCenterY());
+				pt = mxUtils.getRotatedPoint(pt, cos, sin, cx);
+			}
+		
+			return !mxUtils.contains(pState, pt.x, pt.y);
+		}
+	}
+	
+	return false;
+};
+
+/**
+ * Function: moveCells
+ * 
+ * Moves the given cells by the specified amount.
+ */
+mxGraphHandler.prototype.moveCells = function(cells, dx, dy, clone, target, evt)
+{
+	if (clone)
+	{
+		cells = this.graph.getCloneableCells(cells);
+	}
+	
+	// Removes cells from parent
+	if (target == null && this.isRemoveCellsFromParent() &&
+		this.shouldRemoveCellsFromParent(this.graph.getModel().getParent(this.cell), cells, evt))
+	{
+		target = this.graph.getDefaultParent();
+	}
+	
+	// Passes all selected cells in order to correctly clone or move into
+	// the target cell. The method checks for each cell if its movable.
+	cells = this.graph.moveCells(cells, dx - this.graph.panDx / this.graph.view.scale,
+			dy - this.graph.panDy / this.graph.view.scale, clone, target, evt);
+	
+	if (this.isSelectEnabled() && this.scrollOnMove)
+	{
+		this.graph.scrollCellToVisible(cells[0]);
+	}
+			
+	// Selects the new cells if cells have been cloned
+	if (clone)
+	{
+		this.graph.setSelectionCells(cells);
+	}
+};
+
+/**
+ * Function: destroyShapes
+ * 
+ * Destroy the preview and highlight shapes.
+ */
+mxGraphHandler.prototype.destroyShapes = function()
+{
+	// Destroys the preview dashed rectangle
+	if (this.shape != null)
+	{
+		this.shape.destroy();
+		this.shape = null;
+	}
+	
+	if (this.guide != null)
+	{
+		this.guide.destroy();
+		this.guide = null;
+	}
+	
+	// Destroys the drop target highlight
+	if (this.highlight != null)
+	{
+		this.highlight.destroy();
+		this.highlight = null;
+	}
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the handler and all its resources and DOM nodes.
+ */
+mxGraphHandler.prototype.destroy = function()
+{
+	this.graph.removeMouseListener(this);
+	this.graph.removeListener(this.panHandler);
+	
+	if (this.escapeHandler != null)
+	{
+		this.graph.removeListener(this.escapeHandler);
+		this.escapeHandler = null;
+	}
+	
+	this.destroyShapes();
+	this.removeHint();
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/handler/mxHandle.js b/airavata-kubernetes/workflow-composer/src/js/handler/mxHandle.js
new file mode 100644
index 0000000..5564925
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/handler/mxHandle.js
@@ -0,0 +1,353 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxHandle
+ * 
+ * Implements a single custom handle for vertices.
+ * 
+ * Constructor: mxHandle
+ * 
+ * Constructs a new handle for the given state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> of the cell to be handled.
+ */
+function mxHandle(state, cursor, image)
+{
+	this.graph = state.view.graph;
+	this.state = state;
+	this.cursor = (cursor != null) ? cursor : this.cursor;
+	this.image = (image != null) ? image : this.image;
+	this.init();
+};
+
+/**
+ * Variable: cursor
+ * 
+ * Specifies the cursor to be used for this handle. Default is 'default'.
+ */
+mxHandle.prototype.cursor = 'default';
+
+/**
+ * Variable: image
+ * 
+ * Specifies the <mxImage> to be used to render the handle. Default is null.
+ */
+mxHandle.prototype.image = null;
+
+/**
+ * Variable: image
+ * 
+ * Specifies the <mxImage> to be used to render the handle. Default is null.
+ */
+mxHandle.prototype.ignoreGrid = false;
+
+/**
+ * Function: getPosition
+ * 
+ * Hook for subclassers to return the current position of the handle.
+ */
+mxHandle.prototype.getPosition = function(bounds) { };
+
+/**
+ * Function: setPosition
+ * 
+ * Hooks for subclassers to update the style in the <state>.
+ */
+mxHandle.prototype.setPosition = function(bounds, pt, me) { };
+
+/**
+ * Function: execute
+ * 
+ * Hook for subclassers to execute the handle.
+ */
+mxHandle.prototype.execute = function() { };
+
+/**
+ * Function: copyStyle
+ * 
+ * Sets the cell style with the given name to the corresponding value in <state>.
+ */
+mxHandle.prototype.copyStyle = function(key)
+{
+	this.graph.setCellStyles(key, this.state.style[key], [this.state.cell]);
+};
+
+/**
+ * Function: processEvent
+ * 
+ * Processes the given <mxMouseEvent> and invokes <setPosition>.
+ */
+mxHandle.prototype.processEvent = function(me)
+{
+	var scale = this.graph.view.scale;
+	var tr = this.graph.view.translate;
+	var pt = new mxPoint(me.getGraphX() / scale - tr.x, me.getGraphY() / scale - tr.y);
+	
+	// Center shape on mouse cursor
+	if (this.shape != null && this.shape.bounds != null)
+	{
+		pt.x -= this.shape.bounds.width / scale / 4;
+		pt.y -= this.shape.bounds.height / scale / 4;
+	}
+
+	// Snaps to grid for the rotated position then applies the rotation for the direction after that
+	var alpha1 = -mxUtils.toRadians(this.getRotation());
+	var alpha2 = -mxUtils.toRadians(this.getTotalRotation()) - alpha1;
+	pt = this.flipPoint(this.rotatePoint(this.snapPoint(this.rotatePoint(pt, alpha1),
+			this.ignoreGrid || !this.graph.isGridEnabledEvent(me.getEvent())), alpha2));
+	this.setPosition(this.state.getPaintBounds(), pt, me);
+	this.positionChanged();
+	this.redraw();
+};
+
+/**
+ * Function: positionChanged
+ * 
+ * Called after <setPosition> has been called in <processEvent>. This repaints
+ * the state using <mxCellRenderer>.
+ */
+mxHandle.prototype.positionChanged = function()
+{
+	if (this.state.text != null)
+	{
+		this.state.text.apply(this.state);
+	}
+	
+	if (this.state.shape != null)
+	{
+		this.state.shape.apply(this.state);
+	}
+	
+	// Needed to force update of text bounds
+	this.state.unscaledWidth = null;
+	this.graph.cellRenderer.redraw(this.state, true);
+};
+
+/**
+ * Function: getRotation
+ * 
+ * Returns the rotation defined in the style of the cell.
+ */
+mxHandle.prototype.getRotation = function()
+{
+	if (this.state.shape != null)
+	{
+		return this.state.shape.getRotation();
+	}
+	
+	return 0;
+};
+
+/**
+ * Function: getTotalRotation
+ * 
+ * Returns the rotation from the style and the rotation from the direction of
+ * the cell.
+ */
+mxHandle.prototype.getTotalRotation = function()
+{
+	if (this.state.shape != null)
+	{
+		return this.state.shape.getShapeRotation();
+	}
+	
+	return 0;
+};
+
+/**
+ * Function: init
+ * 
+ * Creates and initializes the shapes required for this handle.
+ */
+mxHandle.prototype.init = function()
+{
+	var html = this.isHtmlRequired();
+	
+	if (this.image != null)
+	{
+		this.shape = new mxImageShape(new mxRectangle(0, 0, this.image.width, this.image.height), this.image.src);
+		this.shape.preserveImageAspect = false;
+	}
+	else
+	{
+		this.shape = this.createShape(html);
+	}
+	
+	this.initShape(html);
+};
+
+/**
+ * Function: createShape
+ * 
+ * Creates and returns the shape for this handle.
+ */
+mxHandle.prototype.createShape = function(html)
+{
+	var bounds = new mxRectangle(0, 0, mxConstants.HANDLE_SIZE, mxConstants.HANDLE_SIZE);
+	
+	return new mxRectangleShape(bounds, mxConstants.HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR);
+};
+
+/**
+ * Function: initShape
+ * 
+ * Initializes <shape> and sets its cursor.
+ */
+mxHandle.prototype.initShape = function(html)
+{
+	if (html && this.shape.isHtmlAllowed())
+	{
+		this.shape.dialect = mxConstants.DIALECT_STRICTHTML;
+		this.shape.init(this.graph.container);
+	}
+	else
+	{
+		this.shape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? mxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG;
+		
+		if (this.cursor != null)
+		{
+			this.shape.init(this.graph.getView().getOverlayPane());
+		}
+	}
+
+	mxEvent.redirectMouseEvents(this.shape.node, this.graph, this.state);
+	this.shape.node.style.cursor = this.cursor;
+};
+
+/**
+ * Function: redraw
+ * 
+ * Renders the shape for this handle.
+ */
+mxHandle.prototype.redraw = function()
+{
+	if (this.shape != null && this.state.shape != null)
+	{
+		var pt = this.getPosition(this.state.getPaintBounds());
+		
+		if (pt != null)
+		{
+			var alpha = mxUtils.toRadians(this.getTotalRotation());
+			pt = this.rotatePoint(this.flipPoint(pt), alpha);
+	
+			var scale = this.graph.view.scale;
+			var tr = this.graph.view.translate;
+			this.shape.bounds.x = Math.floor((pt.x + tr.x) * scale - this.shape.bounds.width / 2);
+			this.shape.bounds.y = Math.floor((pt.y + tr.y) * scale - this.shape.bounds.height / 2);
+			
+			// Needed to force update of text bounds
+			this.shape.redraw();
+		}
+	}
+};
+
+/**
+ * Function: isHtmlRequired
+ * 
+ * Returns true if this handle should be rendered in HTML. This returns true if
+ * the text node is in the graph container.
+ */
+mxHandle.prototype.isHtmlRequired = function()
+{
+	return this.state.text != null && this.state.text.node.parentNode == this.graph.container;
+};
+
+/**
+ * Function: rotatePoint
+ * 
+ * Rotates the point by the given angle.
+ */
+mxHandle.prototype.rotatePoint = function(pt, alpha)
+{
+	var bounds = this.state.getCellBounds();
+	var cx = new mxPoint(bounds.getCenterX(), bounds.getCenterY());
+	var cos = Math.cos(alpha);
+	var sin = Math.sin(alpha); 
+
+	return mxUtils.getRotatedPoint(pt, cos, sin, cx);
+};
+
+/**
+ * Function: flipPoint
+ * 
+ * Flips the given point vertically and/or horizontally.
+ */
+mxHandle.prototype.flipPoint = function(pt)
+{
+	if (this.state.shape != null)
+	{
+		var bounds = this.state.getCellBounds();
+		
+		if (this.state.shape.flipH)
+		{
+			pt.x = 2 * bounds.x + bounds.width - pt.x;
+		}
+		
+		if (this.state.shape.flipV)
+		{
+			pt.y = 2 * bounds.y + bounds.height - pt.y;
+		}
+	}
+	
+	return pt;
+};
+
+/**
+ * Function: snapPoint
+ * 
+ * Snaps the given point to the grid if ignore is false. This modifies
+ * the given point in-place and also returns it.
+ */
+mxHandle.prototype.snapPoint = function(pt, ignore)
+{
+	if (!ignore)
+	{
+		pt.x = this.graph.snap(pt.x);
+		pt.y = this.graph.snap(pt.y);
+	}
+	
+	return pt;
+};
+
+/**
+ * Function: setVisible
+ * 
+ * Shows or hides this handle.
+ */
+mxHandle.prototype.setVisible = function(visible)
+{
+	if (this.shape != null && this.shape.node != null)
+	{
+		this.shape.node.style.display = (visible) ? '' : 'none';
+	}
+};
+
+/**
+ * Function: reset
+ * 
+ * Resets the state of this handle by setting its visibility to true.
+ */
+mxHandle.prototype.reset = function()
+{
+	this.setVisible(true);
+	this.state.style = this.graph.getCellStyle(this.state.cell);
+	this.positionChanged();
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys this handle.
+ */
+mxHandle.prototype.destroy = function()
+{
+	if (this.shape != null)
+	{
+		this.shape.destroy();
+		this.shape = null;
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/handler/mxKeyHandler.js b/airavata-kubernetes/workflow-composer/src/js/handler/mxKeyHandler.js
new file mode 100644
index 0000000..6a391f0
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/handler/mxKeyHandler.js
@@ -0,0 +1,428 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxKeyHandler
+ *
+ * Event handler that listens to keystroke events. This is not a singleton,
+ * however, it is normally only required once if the target is the document
+ * element (default).
+ * 
+ * This handler installs a key event listener in the topmost DOM node and
+ * processes all events that originate from descandants of <mxGraph.container>
+ * or from the topmost DOM node. The latter means that all unhandled keystrokes
+ * are handled by this object regardless of the focused state of the <graph>.
+ * 
+ * Example:
+ * 
+ * The following example creates a key handler that listens to the delete key
+ * (46) and deletes the selection cells if the graph is enabled.
+ * 
+ * (code)
+ * var keyHandler = new mxKeyHandler(graph);
+ * keyHandler.bindKey(46, function(evt)
+ * {
+ *   if (graph.isEnabled())
+ *   {
+ *     graph.removeCells();
+ *   }
+ * });
+ * (end)
+ * 
+ * Keycodes:
+ * 
+ * See http://tinyurl.com/yp8jgl or http://tinyurl.com/229yqw for a list of
+ * keycodes or install a key event listener into the document element and print
+ * the key codes of the respective events to the console.
+ * 
+ * To support the Command key and the Control key on the Mac, the following
+ * code can be used.
+ *
+ * (code)
+ * keyHandler.getFunction = function(evt)
+ * {
+ *   if (evt != null)
+ *   {
+ *     return (mxEvent.isControlDown(evt) || (mxClient.IS_MAC && evt.metaKey)) ? this.controlKeys[evt.keyCode] : this.normalKeys[evt.keyCode];
+ *   }
+ *   
+ *   return null;
+ * };
+ * (end)
+ * 
+ * Constructor: mxKeyHandler
+ *
+ * Constructs an event handler that executes functions bound to specific
+ * keystrokes.
+ * 
+ * Parameters:
+ * 
+ * graph - Reference to the associated <mxGraph>.
+ * target - Optional reference to the event target. If null, the document
+ * element is used as the event target, that is, the object where the key
+ * event listener is installed.
+ */
+function mxKeyHandler(graph, target)
+{
+	if (graph != null)
+	{
+		this.graph = graph;
+		this.target = target || document.documentElement;
+		
+		// Creates the arrays to map from keycodes to functions
+		this.normalKeys = [];
+		this.shiftKeys = [];
+		this.controlKeys = [];
+		this.controlShiftKeys = [];
+		
+		this.keydownHandler = mxUtils.bind(this, function(evt)
+		{
+			this.keyDown(evt);
+		});
+
+		// Installs the keystroke listener in the target
+		mxEvent.addListener(this.target, 'keydown', this.keydownHandler);
+		
+		// Automatically deallocates memory in IE
+		if (mxClient.IS_IE)
+		{
+			mxEvent.addListener(window, 'unload',
+				mxUtils.bind(this, function()
+				{
+					this.destroy();
+				})
+			);
+		}
+	}
+};
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the <mxGraph> associated with this handler.
+ */
+mxKeyHandler.prototype.graph = null;
+
+/**
+ * Variable: target
+ * 
+ * Reference to the target DOM, that is, the DOM node where the key event
+ * listeners are installed.
+ */
+mxKeyHandler.prototype.target = null;
+
+/**
+ * Variable: normalKeys
+ * 
+ * Maps from keycodes to functions for non-pressed control keys.
+ */
+mxKeyHandler.prototype.normalKeys = null;
+
+/**
+ * Variable: shiftKeys
+ * 
+ * Maps from keycodes to functions for pressed shift keys.
+ */
+mxKeyHandler.prototype.shiftKeys = null;
+
+/**
+ * Variable: controlKeys
+ * 
+ * Maps from keycodes to functions for pressed control keys.
+ */
+mxKeyHandler.prototype.controlKeys = null;
+
+/**
+ * Variable: controlShiftKeys
+ * 
+ * Maps from keycodes to functions for pressed control and shift keys.
+ */
+mxKeyHandler.prototype.controlShiftKeys = null;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies if events are handled. Default is true.
+ */
+mxKeyHandler.prototype.enabled = true;
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns true if events are handled. This implementation returns
+ * <enabled>.
+ */
+mxKeyHandler.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ * 
+ * Enables or disables event handling by updating <enabled>.
+ * 
+ * Parameters:
+ * 
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxKeyHandler.prototype.setEnabled = function(enabled)
+{
+	this.enabled = enabled;
+};
+
+/**
+ * Function: bindKey
+ * 
+ * Binds the specified keycode to the given function. This binding is used
+ * if the control key is not pressed.
+ * 
+ * Parameters:
+ *
+ * code - Integer that specifies the keycode.
+ * funct - JavaScript function that takes the key event as an argument.
+ */
+mxKeyHandler.prototype.bindKey = function(code, funct)
+{
+	this.normalKeys[code] = funct;
+};
+
+/**
+ * Function: bindShiftKey
+ * 
+ * Binds the specified keycode to the given function. This binding is used
+ * if the shift key is pressed.
+ * 
+ * Parameters:
+ *
+ * code - Integer that specifies the keycode.
+ * funct - JavaScript function that takes the key event as an argument.
+ */
+mxKeyHandler.prototype.bindShiftKey = function(code, funct)
+{
+	this.shiftKeys[code] = funct;
+};
+
+/**
+ * Function: bindControlKey
+ * 
+ * Binds the specified keycode to the given function. This binding is used
+ * if the control key is pressed.
+ * 
+ * Parameters:
+ *
+ * code - Integer that specifies the keycode.
+ * funct - JavaScript function that takes the key event as an argument.
+ */
+mxKeyHandler.prototype.bindControlKey = function(code, funct)
+{
+	this.controlKeys[code] = funct;
+};
+
+/**
+ * Function: bindControlShiftKey
+ * 
+ * Binds the specified keycode to the given function. This binding is used
+ * if the control and shift key are pressed.
+ * 
+ * Parameters:
+ *
+ * code - Integer that specifies the keycode.
+ * funct - JavaScript function that takes the key event as an argument.
+ */
+mxKeyHandler.prototype.bindControlShiftKey = function(code, funct)
+{
+	this.controlShiftKeys[code] = funct;
+};
+
+/**
+ * Function: isControlDown
+ * 
+ * Returns true if the control key is pressed. This uses <mxEvent.isControlDown>.
+ * 
+ * Parameters:
+ * 
+ * evt - Key event whose control key pressed state should be returned.
+ */
+mxKeyHandler.prototype.isControlDown = function(evt)
+{
+	return mxEvent.isControlDown(evt);
+};
+
+/**
+ * Function: getFunction
+ * 
+ * Returns the function associated with the given key event or null if no
+ * function is associated with the given event.
+ * 
+ * Parameters:
+ * 
+ * evt - Key event whose associated function should be returned.
+ */
+mxKeyHandler.prototype.getFunction = function(evt)
+{
+	if (evt != null && !mxEvent.isAltDown(evt))
+	{
+		if (this.isControlDown(evt))
+		{
+			if (mxEvent.isShiftDown(evt))
+			{
+				return this.controlShiftKeys[evt.keyCode];
+			}
+			else
+			{
+				return this.controlKeys[evt.keyCode];
+			}
+		}
+		else
+		{
+			if (mxEvent.isShiftDown(evt))
+			{
+				return this.shiftKeys[evt.keyCode];
+			}
+			else
+			{
+				return this.normalKeys[evt.keyCode];
+			}
+		}
+	}
+	
+	return null;
+};
+	
+/**
+ * Function: isGraphEvent
+ * 
+ * Returns true if the event should be processed by this handler, that is,
+ * if the event source is either the target, one of its direct children, a
+ * descendant of the <mxGraph.container>, or the <mxGraph.cellEditor> of the
+ * <graph>.
+ * 
+ * Parameters:
+ * 
+ * evt - Key event that represents the keystroke.
+ */
+mxKeyHandler.prototype.isGraphEvent = function(evt)
+{
+	var source = mxEvent.getSource(evt);
+	
+	// Accepts events from the target object or
+	// in-place editing inside graph
+	if ((source == this.target || source.parentNode == this.target) ||
+		(this.graph.cellEditor != null && this.graph.cellEditor.isEventSource(evt)))
+	{
+		return true;
+	}
+	
+	// Accepts events from inside the container
+	return mxUtils.isAncestorNode(this.graph.container, source);
+};
+
+/**
+ * Function: keyDown
+ * 
+ * Handles the event by invoking the function bound to the respective keystroke
+ * if <isEnabledForEvent> returns true for the given event and if
+ * <isEventIgnored> returns false, except for escape for which
+ * <isEventIgnored> is not invoked.
+ * 
+ * Parameters:
+ * 
+ * evt - Key event that represents the keystroke.
+ */
+mxKeyHandler.prototype.keyDown = function(evt)
+{
+	if (this.isEnabledForEvent(evt))
+	{
+		// Cancels the editing if escape is pressed
+		if (evt.keyCode == 27 /* Escape */)
+		{
+			this.escape(evt);
+		}
+		
+		// Invokes the function for the keystroke
+		else if (!this.isEventIgnored(evt))
+		{
+			var boundFunction = this.getFunction(evt);
+			
+			if (boundFunction != null)
+			{
+				boundFunction(evt);
+				mxEvent.consume(evt);
+			}
+		}
+	}
+};
+
+/**
+ * Function: isEnabledForEvent
+ * 
+ * Returns true if the given event should be handled. <isEventIgnored> is
+ * called later if the event is not an escape key stroke, in which case
+ * <escape> is called. This implementation returns true if <isEnabled>
+ * returns true for both, this handler and <graph>, if the event is not
+ * consumed and if <isGraphEvent> returns true.
+ * 
+ * Parameters:
+ * 
+ * evt - Key event that represents the keystroke.
+ */
+mxKeyHandler.prototype.isEnabledForEvent = function(evt)
+{
+	return (this.graph.isEnabled() && !mxEvent.isConsumed(evt) &&
+		this.isGraphEvent(evt) && this.isEnabled());
+};
+
+/**
+ * Function: isEventIgnored
+ * 
+ * Returns true if the given keystroke should be ignored. This returns
+ * graph.isEditing().
+ * 
+ * Parameters:
+ * 
+ * evt - Key event that represents the keystroke.
+ */
+mxKeyHandler.prototype.isEventIgnored = function(evt)
+{
+	return this.graph.isEditing();
+};
+
+/**
+ * Function: escape
+ * 
+ * Hook to process ESCAPE keystrokes. This implementation invokes
+ * <mxGraph.stopEditing> to cancel the current editing, connecting
+ * and/or other ongoing modifications.
+ * 
+ * Parameters:
+ * 
+ * evt - Key event that represents the keystroke. Possible keycode in this
+ * case is 27 (ESCAPE).
+ */
+mxKeyHandler.prototype.escape = function(evt)
+{
+	if (this.graph.isEscapeEnabled())
+	{
+		this.graph.escape(evt);
+	}
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the handler and all its references into the DOM. This does
+ * normally not need to be called, it is called automatically when the
+ * window unloads (in IE).
+ */
+mxKeyHandler.prototype.destroy = function()
+{
+	if (this.target != null && this.keydownHandler != null)
+	{
+		mxEvent.removeListener(this.target, 'keydown', this.keydownHandler);
+		this.keydownHandler = null;
+	}
+	
+	this.target = null;
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/handler/mxPanningHandler.js b/airavata-kubernetes/workflow-composer/src/js/handler/mxPanningHandler.js
new file mode 100644
index 0000000..189e676
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/handler/mxPanningHandler.js
@@ -0,0 +1,462 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxPanningHandler
+ * 
+ * Event handler that pans and creates popupmenus. To use the left
+ * mousebutton for panning without interfering with cell moving and
+ * resizing, use <isUseLeftButton> and <isIgnoreCell>. For grid size
+ * steps while panning, use <useGrid>. This handler is built-into
+ * <mxGraph.panningHandler> and enabled using <mxGraph.setPanning>.
+ * 
+ * Constructor: mxPanningHandler
+ * 
+ * Constructs an event handler that creates a <mxPopupMenu>
+ * and pans the graph.
+ *
+ * Event: mxEvent.PAN_START
+ *
+ * Fires when the panning handler changes its <active> state to true. The
+ * <code>event</code> property contains the corresponding <mxMouseEvent>.
+ *
+ * Event: mxEvent.PAN
+ *
+ * Fires while handle is processing events. The <code>event</code> property contains
+ * the corresponding <mxMouseEvent>.
+ *
+ * Event: mxEvent.PAN_END
+ *
+ * Fires when the panning handler changes its <active> state to false. The
+ * <code>event</code> property contains the corresponding <mxMouseEvent>.
+ */
+function mxPanningHandler(graph)
+{
+	if (graph != null)
+	{
+		this.graph = graph;
+		this.graph.addMouseListener(this);
+
+		// Handles force panning event
+		this.forcePanningHandler = mxUtils.bind(this, function(sender, evt)
+		{
+			var evtName = evt.getProperty('eventName');
+			var me = evt.getProperty('event');
+			
+			if (evtName == mxEvent.MOUSE_DOWN && this.isForcePanningEvent(me))
+			{
+				this.start(me);
+				this.active = true;
+				this.fireEvent(new mxEventObject(mxEvent.PAN_START, 'event', me));
+				me.consume();
+			}
+		});
+
+		this.graph.addListener(mxEvent.FIRE_MOUSE_EVENT, this.forcePanningHandler);
+		
+		// Handles pinch gestures
+		this.gestureHandler = mxUtils.bind(this, function(sender, eo)
+		{
+			if (this.isPinchEnabled())
+			{
+				var evt = eo.getProperty('event');
+				
+				if (!mxEvent.isConsumed(evt) && evt.type == 'gesturestart')
+				{
+					this.initialScale = this.graph.view.scale;
+				
+					// Forces start of panning when pinch gesture starts
+					if (!this.active && this.mouseDownEvent != null)
+					{
+						this.start(this.mouseDownEvent);
+						this.mouseDownEvent = null;
+					}
+				}
+				else if (evt.type == 'gestureend' && this.initialScale != null)
+				{
+					this.initialScale = null;
+				}
+				
+				if (this.initialScale != null)
+				{
+					var value = Math.round(this.initialScale * evt.scale * 100) / 100;
+					
+					if (this.minScale != null)
+					{
+						value = Math.max(this.minScale, value);
+					}
+					
+					if (this.maxScale != null)
+					{
+						value = Math.min(this.maxScale, value);
+					}
+	
+					if (this.graph.view.scale != value)
+					{
+						this.graph.zoomTo(value);
+						mxEvent.consume(evt);
+					}
+				}
+			}
+		});
+		
+		this.graph.addListener(mxEvent.GESTURE, this.gestureHandler);
+	}
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxPanningHandler.prototype = new mxEventSource();
+mxPanningHandler.prototype.constructor = mxPanningHandler;
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxPanningHandler.prototype.graph = null;
+
+/**
+ * Variable: useLeftButtonForPanning
+ * 
+ * Specifies if panning should be active for the left mouse button.
+ * Setting this to true may conflict with <mxRubberband>. Default is false.
+ */
+mxPanningHandler.prototype.useLeftButtonForPanning = false;
+
+/**
+ * Variable: usePopupTrigger
+ * 
+ * Specifies if <mxEvent.isPopupTrigger> should also be used for panning.
+ */
+mxPanningHandler.prototype.usePopupTrigger = true;
+
+/**
+ * Variable: ignoreCell
+ * 
+ * Specifies if panning should be active even if there is a cell under the
+ * mousepointer. Default is false.
+ */
+mxPanningHandler.prototype.ignoreCell = false;
+
+/**
+ * Variable: previewEnabled
+ * 
+ * Specifies if the panning should be previewed. Default is true.
+ */
+mxPanningHandler.prototype.previewEnabled = true;
+
+/**
+ * Variable: useGrid
+ * 
+ * Specifies if the panning steps should be aligned to the grid size.
+ * Default is false.
+ */
+mxPanningHandler.prototype.useGrid = false;
+
+/**
+ * Variable: panningEnabled
+ * 
+ * Specifies if panning should be enabled. Default is true.
+ */
+mxPanningHandler.prototype.panningEnabled = true;
+
+/**
+ * Variable: pinchEnabled
+ * 
+ * Specifies if pinch gestures should be handled as zoom. Default is true.
+ */
+mxPanningHandler.prototype.pinchEnabled = true;
+
+/**
+ * Variable: maxScale
+ * 
+ * Specifies the maximum scale. Default is 8.
+ */
+mxPanningHandler.prototype.maxScale = 8;
+
+/**
+ * Variable: minScale
+ * 
+ * Specifies the minimum scale. Default is 0.01.
+ */
+mxPanningHandler.prototype.minScale = 0.01;
+
+/**
+ * Variable: dx
+ * 
+ * Holds the current horizontal offset.
+ */
+mxPanningHandler.prototype.dx = null;
+
+/**
+ * Variable: dy
+ * 
+ * Holds the current vertical offset.
+ */
+mxPanningHandler.prototype.dy = null;
+
+/**
+ * Variable: startX
+ * 
+ * Holds the x-coordinate of the start point.
+ */
+mxPanningHandler.prototype.startX = 0;
+
+/**
+ * Variable: startY
+ * 
+ * Holds the y-coordinate of the start point.
+ */
+mxPanningHandler.prototype.startY = 0;
+
+/**
+ * Function: isActive
+ * 
+ * Returns true if the handler is currently active.
+ */
+mxPanningHandler.prototype.isActive = function()
+{
+	return this.active || this.initialScale != null;
+};
+
+/**
+ * Function: isPanningEnabled
+ * 
+ * Returns <panningEnabled>.
+ */
+mxPanningHandler.prototype.isPanningEnabled = function()
+{
+	return this.panningEnabled;
+};
+
+/**
+ * Function: setPanningEnabled
+ * 
+ * Sets <panningEnabled>.
+ */
+mxPanningHandler.prototype.setPanningEnabled = function(value)
+{
+	this.panningEnabled = value;
+};
+
+/**
+ * Function: isPinchEnabled
+ * 
+ * Returns <pinchEnabled>.
+ */
+mxPanningHandler.prototype.isPinchEnabled = function()
+{
+	return this.pinchEnabled;
+};
+
+/**
+ * Function: setPinchEnabled
+ * 
+ * Sets <pinchEnabled>.
+ */
+mxPanningHandler.prototype.setPinchEnabled = function(value)
+{
+	this.pinchEnabled = value;
+};
+
+/**
+ * Function: isPanningTrigger
+ * 
+ * Returns true if the given event is a panning trigger for the optional
+ * given cell. This returns true if control-shift is pressed or if
+ * <usePopupTrigger> is true and the event is a popup trigger.
+ */
+mxPanningHandler.prototype.isPanningTrigger = function(me)
+{
+	var evt = me.getEvent();
+	
+	return (this.useLeftButtonForPanning && me.getState() == null &&
+			mxEvent.isLeftMouseButton(evt)) || (mxEvent.isControlDown(evt) &&
+			mxEvent.isShiftDown(evt)) || (this.usePopupTrigger && mxEvent.isPopupTrigger(evt));
+};
+
+/**
+ * Function: isForcePanningEvent
+ * 
+ * Returns true if the given <mxMouseEvent> should start panning. This
+ * implementation always returns true if <ignoreCell> is true or for
+ * multi touch events.
+ */
+mxPanningHandler.prototype.isForcePanningEvent = function(me)
+{
+	return this.ignoreCell || mxEvent.isMultiTouchEvent(me.getEvent());
+};
+
+/**
+ * Function: mouseDown
+ * 
+ * Handles the event by initiating the panning. By consuming the event all
+ * subsequent events of the gesture are redirected to this handler.
+ */
+mxPanningHandler.prototype.mouseDown = function(sender, me)
+{
+	this.mouseDownEvent = me;
+	
+	if (!me.isConsumed() && this.isPanningEnabled() && !this.active && this.isPanningTrigger(me))
+	{
+		this.start(me);
+		this.consumePanningTrigger(me);
+	}
+};
+
+/**
+ * Function: start
+ * 
+ * Starts panning at the given event.
+ */
+mxPanningHandler.prototype.start = function(me)
+{
+	this.dx0 = -this.graph.container.scrollLeft;
+	this.dy0 = -this.graph.container.scrollTop;
+
+	// Stores the location of the trigger event
+	this.startX = me.getX();
+	this.startY = me.getY();
+	this.dx = null;
+	this.dy = null;
+	
+	this.panningTrigger = true;
+};
+
+/**
+ * Function: consumePanningTrigger
+ * 
+ * Consumes the given <mxMouseEvent> if it was a panning trigger in
+ * <mouseDown>. The default is to invoke <mxMouseEvent.consume>. Note that this
+ * will block any further event processing. If you haven't disabled built-in
+ * context menus and require immediate selection of the cell on mouseDown in
+ * Safari and/or on the Mac, then use the following code:
+ * 
+ * (code)
+ * mxPanningHandler.prototype.consumePanningTrigger = function(me)
+ * {
+ *   if (me.evt.preventDefault)
+ *   {
+ *     me.evt.preventDefault();
+ *   }
+ *   
+ *   // Stops event processing in IE
+ *   me.evt.returnValue = false;
+ *   
+ *   // Sets local consumed state
+ *   if (!mxClient.IS_SF && !mxClient.IS_MAC)
+ *   {
+ *     me.consumed = true;
+ *   }
+ * };
+ * (end)
+ */
+mxPanningHandler.prototype.consumePanningTrigger = function(me)
+{
+	me.consume();
+};
+
+/**
+ * Function: mouseMove
+ * 
+ * Handles the event by updating the panning on the graph.
+ */
+mxPanningHandler.prototype.mouseMove = function(sender, me)
+{
+	this.dx = me.getX() - this.startX;
+	this.dy = me.getY() - this.startY;
+	
+	if (this.active)
+	{
+		if (this.previewEnabled)
+		{
+			// Applies the grid to the panning steps
+			if (this.useGrid)
+			{
+				this.dx = this.graph.snap(this.dx);
+				this.dy = this.graph.snap(this.dy);
+			}
+			
+			this.graph.panGraph(this.dx + this.dx0, this.dy + this.dy0);
+		}
+
+		this.fireEvent(new mxEventObject(mxEvent.PAN, 'event', me));
+	}
+	else if (this.panningTrigger)
+	{
+		var tmp = this.active;
+
+		// Panning is activated only if the mouse is moved
+		// beyond the graph tolerance
+		this.active = Math.abs(this.dx) > this.graph.tolerance || Math.abs(this.dy) > this.graph.tolerance;
+
+		if (!tmp && this.active)
+		{
+			this.fireEvent(new mxEventObject(mxEvent.PAN_START, 'event', me));
+		}
+	}
+	
+	if (this.active || this.panningTrigger)
+	{
+		me.consume();
+	}
+};
+
+/**
+ * Function: mouseUp
+ * 
+ * Handles the event by setting the translation on the view or showing the
+ * popupmenu.
+ */
+mxPanningHandler.prototype.mouseUp = function(sender, me)
+{
+	if (this.active)
+	{
+		if (this.dx != null && this.dy != null)
+		{
+			// Ignores if scrollbars have been used for panning
+			if (!this.graph.useScrollbarsForPanning || !mxUtils.hasScrollbars(this.graph.container))
+			{
+				var scale = this.graph.getView().scale;
+				var t = this.graph.getView().translate;
+				this.graph.panGraph(0, 0);
+				this.panGraph(t.x + this.dx / scale, t.y + this.dy / scale);
+			}
+			
+			me.consume();
+		}
+		
+		this.fireEvent(new mxEventObject(mxEvent.PAN_END, 'event', me));
+	}
+	
+	this.panningTrigger = false;
+	this.mouseDownEvent = null;
+	this.active = false;
+	this.dx = null;
+	this.dy = null;
+};
+
+/**
+ * Function: panGraph
+ * 
+ * Pans <graph> by the given amount.
+ */
+mxPanningHandler.prototype.panGraph = function(dx, dy)
+{
+	this.graph.getView().setTranslate(dx, dy);
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the handler and all its resources and DOM nodes.
+ */
+mxPanningHandler.prototype.destroy = function()
+{
+	this.graph.removeMouseListener(this);
+	this.graph.removeListener(this.forcePanningHandler);
+	this.graph.removeListener(this.gestureHandler);
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/handler/mxPopupMenuHandler.js b/airavata-kubernetes/workflow-composer/src/js/handler/mxPopupMenuHandler.js
new file mode 100644
index 0000000..2388319
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/handler/mxPopupMenuHandler.js
@@ -0,0 +1,218 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxPopupMenuHandler
+ * 
+ * Event handler that creates popupmenus.
+ * 
+ * Constructor: mxPopupMenuHandler
+ * 
+ * Constructs an event handler that creates a <mxPopupMenu>.
+ */
+function mxPopupMenuHandler(graph, factoryMethod)
+{
+	if (graph != null)
+	{
+		this.graph = graph;
+		this.factoryMethod = factoryMethod;
+		this.graph.addMouseListener(this);
+		
+		// Does not show menu if any touch gestures take place after the trigger
+		this.gestureHandler = mxUtils.bind(this, function(sender, eo)
+		{
+			this.inTolerance = false;
+		});
+		
+		this.graph.addListener(mxEvent.GESTURE, this.gestureHandler);
+		
+		this.init();
+	}
+};
+
+/**
+ * Extends mxPopupMenu.
+ */
+mxPopupMenuHandler.prototype = new mxPopupMenu();
+mxPopupMenuHandler.prototype.constructor = mxPopupMenuHandler;
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxPopupMenuHandler.prototype.graph = null;
+
+/**
+ * Variable: selectOnPopup
+ * 
+ * Specifies if cells should be selected if a popupmenu is displayed for
+ * them. Default is true.
+ */
+mxPopupMenuHandler.prototype.selectOnPopup = true;
+
+/**
+ * Variable: clearSelectionOnBackground
+ * 
+ * Specifies if cells should be deselected if a popupmenu is displayed for
+ * the diagram background. Default is true.
+ */
+mxPopupMenuHandler.prototype.clearSelectionOnBackground = true;
+
+/**
+ * Variable: triggerX
+ * 
+ * X-coordinate of the mouse down event.
+ */
+mxPopupMenuHandler.prototype.triggerX = null;
+
+/**
+ * Variable: triggerY
+ * 
+ * Y-coordinate of the mouse down event.
+ */
+mxPopupMenuHandler.prototype.triggerY = null;
+
+/**
+ * Variable: screenX
+ * 
+ * Screen X-coordinate of the mouse down event.
+ */
+mxPopupMenuHandler.prototype.screenX = null;
+
+/**
+ * Variable: screenY
+ * 
+ * Screen Y-coordinate of the mouse down event.
+ */
+mxPopupMenuHandler.prototype.screenY = null;
+
+/**
+ * Function: init
+ * 
+ * Initializes the shapes required for this vertex handler.
+ */
+mxPopupMenuHandler.prototype.init = function()
+{
+	// Supercall
+	mxPopupMenu.prototype.init.apply(this);
+
+	// Hides the tooltip if the mouse is over
+	// the context menu
+	mxEvent.addGestureListeners(this.div, mxUtils.bind(this, function(evt)
+	{
+		this.graph.tooltipHandler.hide();
+	}));
+};
+
+/**
+ * Function: isSelectOnPopup
+ * 
+ * Hook for returning if a cell should be selected for a given <mxMouseEvent>.
+ * This implementation returns <selectOnPopup>.
+ */
+mxPopupMenuHandler.prototype.isSelectOnPopup = function(me)
+{
+	return this.selectOnPopup;
+};
+
+/**
+ * Function: mouseDown
+ * 
+ * Handles the event by initiating the panning. By consuming the event all
+ * subsequent events of the gesture are redirected to this handler.
+ */
+mxPopupMenuHandler.prototype.mouseDown = function(sender, me)
+{
+	if (this.isEnabled() && !mxEvent.isMultiTouchEvent(me.getEvent()))
+	{
+		// Hides the popupmenu if is is being displayed
+		this.hideMenu();
+		this.triggerX = me.getGraphX();
+		this.triggerY = me.getGraphY();
+		this.screenX = mxEvent.getMainEvent(me.getEvent()).screenX;
+		this.screenY = mxEvent.getMainEvent(me.getEvent()).screenY;
+		this.popupTrigger = this.isPopupTrigger(me);
+		this.inTolerance = true;
+	}
+};
+
+/**
+ * Function: mouseMove
+ * 
+ * Handles the event by updating the panning on the graph.
+ */
+mxPopupMenuHandler.prototype.mouseMove = function(sender, me)
+{
+	// Popup trigger may change on mouseUp so ignore it
+	if (this.inTolerance && this.screenX != null && this.screenY != null)
+	{
+		if (Math.abs(mxEvent.getMainEvent(me.getEvent()).screenX - this.screenX) > this.graph.tolerance ||
+			Math.abs(mxEvent.getMainEvent(me.getEvent()).screenY - this.screenY) > this.graph.tolerance)
+		{
+			this.inTolerance = false;
+		}
+	}
+};
+
+/**
+ * Function: mouseUp
+ * 
+ * Handles the event by setting the translation on the view or showing the
+ * popupmenu.
+ */
+mxPopupMenuHandler.prototype.mouseUp = function(sender, me)
+{
+	if (this.popupTrigger && this.inTolerance && this.triggerX != null && this.triggerY != null)
+	{
+		var cell = this.getCellForPopupEvent(me);
+
+		// Selects the cell for which the context menu is being displayed
+		if (this.graph.isEnabled() && this.isSelectOnPopup(me) &&
+			cell != null && !this.graph.isCellSelected(cell))
+		{
+			this.graph.setSelectionCell(cell);
+		}
+		else if (this.clearSelectionOnBackground && cell == null)
+		{
+			this.graph.clearSelection();
+		}
+		
+		// Hides the tooltip if there is one
+		this.graph.tooltipHandler.hide();
+
+		// Menu is shifted by 1 pixel so that the mouse up event
+		// is routed via the underlying shape instead of the DIV
+		var origin = mxUtils.getScrollOrigin();
+		this.popup(me.getX() + origin.x + 1, me.getY() + origin.y + 1, cell, me.getEvent());
+		me.consume();
+	}
+	
+	this.popupTrigger = false;
+	this.inTolerance = false;
+};
+
+/**
+ * Function: getCellForPopupEvent
+ * 
+ * Hook to return the cell for the mouse up popup trigger handling.
+ */
+mxPopupMenuHandler.prototype.getCellForPopupEvent = function(me)
+{
+	return me.getCell();
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the handler and all its resources and DOM nodes.
+ */
+mxPopupMenuHandler.prototype.destroy = function()
+{
+	this.graph.removeMouseListener(this);
+	this.graph.removeListener(this.gestureHandler);
+	
+	// Supercall
+	mxPopupMenu.prototype.destroy.apply(this);
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/handler/mxRubberband.js b/airavata-kubernetes/workflow-composer/src/js/handler/mxRubberband.js
new file mode 100644
index 0000000..8d29fdb
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/handler/mxRubberband.js
@@ -0,0 +1,401 @@
+/**
+ * Copyright (c) 2006-2016, JGraph Ltd
+ * Copyright (c) 2006-2016, Gaudenz Alder
+ */
+/**
+ * Class: mxRubberband
+ * 
+ * Event handler that selects rectangular regions. This is not built-into
+ * <mxGraph>. To enable rubberband selection in a graph, use the following code.
+ * 
+ * Example:
+ * 
+ * (code)
+ * var rubberband = new mxRubberband(graph);
+ * (end)
+ * 
+ * Constructor: mxRubberband
+ * 
+ * Constructs an event handler that selects rectangular regions in the graph
+ * using rubberband selection.
+ */
+function mxRubberband(graph)
+{
+	if (graph != null)
+	{
+		this.graph = graph;
+		this.graph.addMouseListener(this);
+
+		// Handles force rubberband event
+		this.forceRubberbandHandler = mxUtils.bind(this, function(sender, evt)
+		{
+			var evtName = evt.getProperty('eventName');
+			var me = evt.getProperty('event');
+			
+			if (evtName == mxEvent.MOUSE_DOWN && this.isForceRubberbandEvent(me))
+			{
+				var offset = mxUtils.getOffset(this.graph.container);
+				var origin = mxUtils.getScrollOrigin(this.graph.container);
+				origin.x -= offset.x;
+				origin.y -= offset.y;
+				this.start(me.getX() + origin.x, me.getY() + origin.y);
+				me.consume(false);
+			}
+		});
+		
+		this.graph.addListener(mxEvent.FIRE_MOUSE_EVENT, this.forceRubberbandHandler);
+		
+		// Repaints the marquee after autoscroll
+		this.panHandler = mxUtils.bind(this, function()
+		{
+			this.repaint();
+		});
+		
+		this.graph.addListener(mxEvent.PAN, this.panHandler);
+		
+		// Does not show menu if any touch gestures take place after the trigger
+		this.gestureHandler = mxUtils.bind(this, function(sender, eo)
+		{
+			if (this.first != null)
+			{
+				this.reset();
+			}
+		});
+		
+		this.graph.addListener(mxEvent.GESTURE, this.gestureHandler);
+		
+		// Automatic deallocation of memory
+		if (mxClient.IS_IE)
+		{
+			mxEvent.addListener(window, 'unload',
+				mxUtils.bind(this, function()
+				{
+					this.destroy();
+				})
+			);
+		}
+	}
+};
+
+/**
+ * Variable: defaultOpacity
+ * 
+ * Specifies the default opacity to be used for the rubberband div. Default
+ * is 20.
+ */
+mxRubberband.prototype.defaultOpacity = 20;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies if events are handled. Default is true.
+ */
+mxRubberband.prototype.enabled = true;
+
+/**
+ * Variable: div
+ * 
+ * Holds the DIV element which is currently visible.
+ */
+mxRubberband.prototype.div = null;
+
+/**
+ * Variable: sharedDiv
+ * 
+ * Holds the DIV element which is used to display the rubberband.
+ */
+mxRubberband.prototype.sharedDiv = null;
+
+/**
+ * Variable: currentX
+ * 
+ * Holds the value of the x argument in the last call to <update>.
+ */
+mxRubberband.prototype.currentX = 0;
+
+/**
+ * Variable: currentY
+ * 
+ * Holds the value of the y argument in the last call to <update>.
+ */
+mxRubberband.prototype.currentY = 0;
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns true if events are handled. This implementation returns
+ * <enabled>.
+ */
+mxRubberband.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+		
+/**
+ * Function: setEnabled
+ * 
+ * Enables or disables event handling. This implementation updates
+ * <enabled>.
+ */
+mxRubberband.prototype.setEnabled = function(enabled)
+{
+	this.enabled = enabled;
+};
+
+/**
+ * Function: isForceRubberbandEvent
+ * 
+ * Returns true if the given <mxMouseEvent> should start rubberband selection.
+ * This implementation returns true if the alt key is pressed.
+ */
+mxRubberband.prototype.isForceRubberbandEvent = function(me)
+{
+	return mxEvent.isAltDown(me.getEvent());
+};
+
+/**
+ * Function: mouseDown
+ * 
+ * Handles the event by initiating a rubberband selection. By consuming the
+ * event all subsequent events of the gesture are redirected to this
+ * handler.
+ */
+mxRubberband.prototype.mouseDown = function(sender, me)
+{
+	if (!me.isConsumed() && this.isEnabled() && this.graph.isEnabled() &&
+		me.getState() == null && !mxEvent.isMultiTouchEvent(me.getEvent()))
+	{
+		var offset = mxUtils.getOffset(this.graph.container);
+		var origin = mxUtils.getScrollOrigin(this.graph.container);
+		origin.x -= offset.x;
+		origin.y -= offset.y;
+		this.start(me.getX() + origin.x, me.getY() + origin.y);
+
+		// Does not prevent the default for this event so that the
+		// event processing chain is still executed even if we start
+		// rubberbanding. This is required eg. in ExtJs to hide the
+		// current context menu. In mouseMove we'll make sure we're
+		// not selecting anything while we're rubberbanding.
+		me.consume(false);
+	}
+};
+
+/**
+ * Function: start
+ * 
+ * Sets the start point for the rubberband selection.
+ */
+mxRubberband.prototype.start = function(x, y)
+{
+	this.first = new mxPoint(x, y);
+
+	var container = this.graph.container;
+	
+	function createMouseEvent(evt)
+	{
+		var me = new mxMouseEvent(evt);
+		var pt = mxUtils.convertPoint(container, me.getX(), me.getY());
+		
+		me.graphX = pt.x;
+		me.graphY = pt.y;
+		
+		return me;
+	};
+
+	this.dragHandler = mxUtils.bind(this, function(evt)
+	{
+		this.mouseMove(this.graph, createMouseEvent(evt));
+	});
+
+	this.dropHandler = mxUtils.bind(this, function(evt)
+	{
+		this.mouseUp(this.graph, createMouseEvent(evt));
+	});
+
+	// Workaround for rubberband stopping if the mouse leaves the container in Firefox
+	if (mxClient.IS_FF)
+	{
+		mxEvent.addGestureListeners(document, null, this.dragHandler, this.dropHandler);
+	}
+};
+
+/**
+ * Function: mouseMove
+ * 
+ * Handles the event by updating therubberband selection.
+ */
+mxRubberband.prototype.mouseMove = function(sender, me)
+{
+	if (!me.isConsumed() && this.first != null)
+	{
+		var origin = mxUtils.getScrollOrigin(this.graph.container);
+		var offset = mxUtils.getOffset(this.graph.container);
+		origin.x -= offset.x;
+		origin.y -= offset.y;
+		var x = me.getX() + origin.x;
+		var y = me.getY() + origin.y;
+		var dx = this.first.x - x;
+		var dy = this.first.y - y;
+		var tol = this.graph.tolerance;
+		
+		if (this.div != null || Math.abs(dx) > tol ||  Math.abs(dy) > tol)
+		{
+			if (this.div == null)
+			{
+				this.div = this.createShape();
+			}
+			
+			// Clears selection while rubberbanding. This is required because
+			// the event is not consumed in mouseDown.
+			mxUtils.clearSelection();
+			
+			this.update(x, y);
+			me.consume();
+		}
+	}
+};
+
+/**
+ * Function: createShape
+ * 
+ * Creates the rubberband selection shape.
+ */
+mxRubberband.prototype.createShape = function()
+{
+	if (this.sharedDiv == null)
+	{
+		this.sharedDiv = document.createElement('div');
+		this.sharedDiv.className = 'mxRubberband';
+		mxUtils.setOpacity(this.sharedDiv, this.defaultOpacity);
+	}
+
+	this.graph.container.appendChild(this.sharedDiv);
+		
+	return this.sharedDiv;
+};
+
+/**
+ * Function: isActive
+ * 
+ * Returns true if this handler is active.
+ */
+mxRubberband.prototype.isActive = function(sender, me)
+{
+	return this.div != null && this.div.style.display != 'none';
+};
+
+/**
+ * Function: mouseUp
+ * 
+ * Handles the event by selecting the region of the rubberband using
+ * <mxGraph.selectRegion>.
+ */
+mxRubberband.prototype.mouseUp = function(sender, me)
+{
+	var active = this.isActive();
+	this.reset();
+	
+	if (active)
+	{
+		this.execute(me.getEvent());
+		me.consume();
+	}
+};
+
+/**
+ * Function: execute
+ * 
+ * Resets the state of this handler and selects the current region
+ * for the given event.
+ */
+mxRubberband.prototype.execute = function(evt)
+{
+	var rect = new mxRectangle(this.x, this.y, this.width, this.height);
+	this.graph.selectRegion(rect, evt);
+};
+
+/**
+ * Function: reset
+ * 
+ * Resets the state of the rubberband selection.
+ */
+mxRubberband.prototype.reset = function()
+{
+	if (this.div != null)
+	{
+		this.div.parentNode.removeChild(this.div);
+	}
+
+	mxEvent.removeGestureListeners(document, null, this.dragHandler, this.dropHandler);
+	this.dragHandler = null;
+	this.dropHandler = null;
+	
+	this.currentX = 0;
+	this.currentY = 0;
+	this.first = null;
+	this.div = null;
+};
+
+/**
+ * Function: update
+ * 
+ * Sets <currentX> and <currentY> and calls <repaint>.
+ */
+mxRubberband.prototype.update = function(x, y)
+{
+	this.currentX = x;
+	this.currentY = y;
+	
+	this.repaint();
+};
+
+/**
+ * Function: repaint
+ * 
+ * Computes the bounding box and updates the style of the <div>.
+ */
+mxRubberband.prototype.repaint = function()
+{
+	if (this.div != null)
+	{
+		var x = this.currentX - this.graph.panDx;
+		var y = this.currentY - this.graph.panDy;
+		
+		this.x = Math.min(this.first.x, x);
+		this.y = Math.min(this.first.y, y);
+		this.width = Math.max(this.first.x, x) - this.x;
+		this.height =  Math.max(this.first.y, y) - this.y;
+
+		var dx = (mxClient.IS_VML) ? this.graph.panDx : 0;
+		var dy = (mxClient.IS_VML) ? this.graph.panDy : 0;
+		
+		this.div.style.left = (this.x + dx) + 'px';
+		this.div.style.top = (this.y + dy) + 'px';
+		this.div.style.width = Math.max(1, this.width) + 'px';
+		this.div.style.height = Math.max(1, this.height) + 'px';
+	}
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the handler and all its resources and DOM nodes. This does
+ * normally not need to be called, it is called automatically when the
+ * window unloads.
+ */
+mxRubberband.prototype.destroy = function()
+{
+	if (!this.destroyed)
+	{
+		this.destroyed = true;
+		this.graph.removeMouseListener(this);
+		this.graph.removeListener(this.forceRubberbandHandler);
+		this.graph.removeListener(this.panHandler);
+		this.reset();
+		
+		if (this.sharedDiv != null)
+		{
+			this.sharedDiv = null;
+		}
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/handler/mxSelectionCellsHandler.js b/airavata-kubernetes/workflow-composer/src/js/handler/mxSelectionCellsHandler.js
new file mode 100644
index 0000000..432e237
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/handler/mxSelectionCellsHandler.js
@@ -0,0 +1,287 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxSelectionCellsHandler
+ * 
+ * An event handler that manages cell handlers and invokes their mouse event
+ * processing functions.
+ * 
+ * Group: Events
+ * 
+ * Event: mxEvent.ADD
+ * 
+ * Fires if a cell has been added to the selection. The <code>state</code>
+ * property contains the <mxCellState> that has been added.
+ * 
+ * Event: mxEvent.REMOVE
+ * 
+ * Fires if a cell has been remove from the selection. The <code>state</code>
+ * property contains the <mxCellState> that has been removed.
+ * 
+ * Parameters:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ */
+function mxSelectionCellsHandler(graph)
+{
+	mxEventSource.call(this);
+	
+	this.graph = graph;
+	this.handlers = new mxDictionary();
+	this.graph.addMouseListener(this);
+	
+	this.refreshHandler = mxUtils.bind(this, function(sender, evt)
+	{
+		if (this.isEnabled())
+		{
+			this.refresh();
+		}
+	});
+	
+	this.graph.getSelectionModel().addListener(mxEvent.CHANGE, this.refreshHandler);
+	this.graph.getModel().addListener(mxEvent.CHANGE, this.refreshHandler);
+	this.graph.getView().addListener(mxEvent.SCALE, this.refreshHandler);
+	this.graph.getView().addListener(mxEvent.TRANSLATE, this.refreshHandler);
+	this.graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE, this.refreshHandler);
+	this.graph.getView().addListener(mxEvent.DOWN, this.refreshHandler);
+	this.graph.getView().addListener(mxEvent.UP, this.refreshHandler);
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxUtils.extend(mxSelectionCellsHandler, mxEventSource);
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxSelectionCellsHandler.prototype.graph = null;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies if events are handled. Default is true.
+ */
+mxSelectionCellsHandler.prototype.enabled = true;
+
+/**
+ * Variable: refreshHandler
+ * 
+ * Keeps a reference to an event listener for later removal.
+ */
+mxSelectionCellsHandler.prototype.refreshHandler = null;
+
+/**
+ * Variable: maxHandlers
+ * 
+ * Defines the maximum number of handlers to paint individually. Default is 100.
+ */
+mxSelectionCellsHandler.prototype.maxHandlers = 100;
+
+/**
+ * Variable: handlers
+ * 
+ * <mxDictionary> that maps from cells to handlers.
+ */
+mxSelectionCellsHandler.prototype.handlers = null;
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns <enabled>.
+ */
+mxSelectionCellsHandler.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ * 
+ * Sets <enabled>.
+ */
+mxSelectionCellsHandler.prototype.setEnabled = function(value)
+{
+	this.enabled = value;
+};
+
+/**
+ * Function: getHandler
+ * 
+ * Returns the handler for the given cell.
+ */
+mxSelectionCellsHandler.prototype.getHandler = function(cell)
+{
+	return this.handlers.get(cell);
+};
+
+/**
+ * Function: reset
+ * 
+ * Resets all handlers.
+ */
+mxSelectionCellsHandler.prototype.reset = function()
+{
+	this.handlers.visit(function(key, handler)
+	{
+		handler.reset.apply(handler);
+	});
+};
+
+/**
+ * Function: refresh
+ * 
+ * Reloads or updates all handlers.
+ */
+mxSelectionCellsHandler.prototype.refresh = function()
+{
+	// Removes all existing handlers
+	var oldHandlers = this.handlers;
+	this.handlers = new mxDictionary();
+	
+	// Creates handles for all selection cells
+	var tmp = this.graph.getSelectionCells();
+
+	for (var i = 0; i < tmp.length; i++)
+	{
+		var state = this.graph.view.getState(tmp[i]);
+
+		if (state != null)
+		{
+			var handler = oldHandlers.remove(tmp[i]);
+
+			if (handler != null)
+			{
+				if (handler.state != state)
+				{
+					handler.destroy();
+					handler = null;
+				}
+				else
+				{
+					if (handler.refresh != null)
+					{
+						handler.refresh();
+					}
+					
+					handler.redraw();
+				}
+			}
+			
+			if (handler == null)
+			{
+				handler = this.graph.createHandler(state);
+				this.fireEvent(new mxEventObject(mxEvent.ADD, 'state', state));
+			}
+			
+			if (handler != null)
+			{
+				this.handlers.put(tmp[i], handler);
+			}
+		}
+	}
+	
+	// Destroys all unused handlers
+	oldHandlers.visit(mxUtils.bind(this, function(key, handler)
+	{
+		this.fireEvent(new mxEventObject(mxEvent.REMOVE, 'state', handler.state));
+		handler.destroy();
+	}));
+};
+
+/**
+ * Function: updateHandler
+ * 
+ * Updates the handler for the given shape if one exists.
+ */
+mxSelectionCellsHandler.prototype.updateHandler = function(state)
+{
+	var handler = this.handlers.remove(state.cell);
+	
+	if (handler != null)
+	{
+		handler.destroy();
+		handler = this.graph.createHandler(state);
+		
+		if (handler != null)
+		{
+			this.handlers.put(state.cell, handler);
+		}
+	}
+};
+
+/**
+ * Function: mouseDown
+ * 
+ * Redirects the given event to the handlers.
+ */
+mxSelectionCellsHandler.prototype.mouseDown = function(sender, me)
+{
+	if (this.graph.isEnabled() && this.isEnabled())
+	{
+		var args = [sender, me];
+
+		this.handlers.visit(function(key, handler)
+		{
+			handler.mouseDown.apply(handler, args);
+		});
+	}
+};
+
+/**
+ * Function: mouseMove
+ * 
+ * Redirects the given event to the handlers.
+ */
+mxSelectionCellsHandler.prototype.mouseMove = function(sender, me)
+{
+	if (this.graph.isEnabled() && this.isEnabled())
+	{
+		var args = [sender, me];
+
+		this.handlers.visit(function(key, handler)
+		{
+			handler.mouseMove.apply(handler, args);
+		});
+	}
+};
+
+/**
+ * Function: mouseUp
+ * 
+ * Redirects the given event to the handlers.
+ */
+mxSelectionCellsHandler.prototype.mouseUp = function(sender, me)
+{
+	if (this.graph.isEnabled() && this.isEnabled())
+	{
+		var args = [sender, me];
+
+		this.handlers.visit(function(key, handler)
+		{
+			handler.mouseUp.apply(handler, args);
+		});
+	}
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the handler and all its resources and DOM nodes.
+ */
+mxSelectionCellsHandler.prototype.destroy = function()
+{
+	this.graph.removeMouseListener(this);
+	
+	if (this.refreshHandler != null)
+	{
+		this.graph.getSelectionModel().removeListener(this.refreshHandler);
+		this.graph.getModel().removeListener(this.refreshHandler);
+		this.graph.getView().removeListener(this.refreshHandler);
+		this.refreshHandler = null;
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/handler/mxTooltipHandler.js b/airavata-kubernetes/workflow-composer/src/js/handler/mxTooltipHandler.js
new file mode 100644
index 0000000..4237e0c
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/handler/mxTooltipHandler.js
@@ -0,0 +1,337 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxTooltipHandler
+ * 
+ * Graph event handler that displays tooltips. <mxGraph.getTooltip> is used to
+ * get the tooltip for a cell or handle. This handler is built-into
+ * <mxGraph.tooltipHandler> and enabled using <mxGraph.setTooltips>.
+ *
+ * Example:
+ * 
+ * (code>
+ * new mxTooltipHandler(graph);
+ * (end)
+ * 
+ * Constructor: mxTooltipHandler
+ * 
+ * Constructs an event handler that displays tooltips with the specified
+ * delay (in milliseconds). If no delay is specified then a default delay
+ * of 500 ms (0.5 sec) is used.
+ * 
+ * Parameters:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ * delay - Optional delay in milliseconds.
+ */
+function mxTooltipHandler(graph, delay)
+{
+	if (graph != null)
+	{
+		this.graph = graph;
+		this.delay = delay || 500;
+		this.graph.addMouseListener(this);
+	}
+};
+
+/**
+ * Variable: zIndex
+ * 
+ * Specifies the zIndex for the tooltip and its shadow. Default is 10005.
+ */
+mxTooltipHandler.prototype.zIndex = 10005;
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxTooltipHandler.prototype.graph = null;
+
+/**
+ * Variable: delay
+ * 
+ * Delay to show the tooltip in milliseconds. Default is 500.
+ */
+mxTooltipHandler.prototype.delay = null;
+
+/**
+ * Variable: ignoreTouchEvents
+ * 
+ * Specifies if touch and pen events should be ignored. Default is true.
+ */
+mxTooltipHandler.prototype.ignoreTouchEvents = true;
+
+/**
+ * Variable: hideOnHover
+ * 
+ * Specifies if the tooltip should be hidden if the mouse is moved over the
+ * current cell. Default is false.
+ */
+mxTooltipHandler.prototype.hideOnHover = false;
+
+/**
+ * Variable: destroyed
+ * 
+ * True if this handler was destroyed using <destroy>.
+ */
+mxTooltipHandler.prototype.destroyed = false;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies if events are handled. Default is true.
+ */
+mxTooltipHandler.prototype.enabled = true;
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxTooltipHandler.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ * 
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ */
+mxTooltipHandler.prototype.setEnabled = function(enabled)
+{
+	this.enabled = enabled;
+};
+
+/**
+ * Function: isHideOnHover
+ * 
+ * Returns <hideOnHover>.
+ */
+mxTooltipHandler.prototype.isHideOnHover = function()
+{
+	return this.hideOnHover;
+};
+
+/**
+ * Function: setHideOnHover
+ * 
+ * Sets <hideOnHover>.
+ */
+mxTooltipHandler.prototype.setHideOnHover = function(value)
+{
+	this.hideOnHover = value;
+};
+
+/**
+ * Function: init
+ * 
+ * Initializes the DOM nodes required for this tooltip handler.
+ */
+mxTooltipHandler.prototype.init = function()
+{
+	if (document.body != null)
+	{
+		this.div = document.createElement('div');
+		this.div.className = 'mxTooltip';
+		this.div.style.visibility = 'hidden';
+
+		document.body.appendChild(this.div);
+
+		mxEvent.addGestureListeners(this.div, mxUtils.bind(this, function(evt)
+		{
+			this.hideTooltip();
+		}));
+	}
+};
+
+/**
+ * Function: mouseDown
+ * 
+ * Handles the event by initiating a rubberband selection. By consuming the
+ * event all subsequent events of the gesture are redirected to this
+ * handler.
+ */
+mxTooltipHandler.prototype.mouseDown = function(sender, me)
+{
+	this.reset(me, false);
+	this.hideTooltip();
+};
+
+/**
+ * Function: mouseMove
+ * 
+ * Handles the event by updating the rubberband selection.
+ */
+mxTooltipHandler.prototype.mouseMove = function(sender, me)
+{
+	if (me.getX() != this.lastX || me.getY() != this.lastY)
+	{
+		this.reset(me, true);
+		
+		if (this.isHideOnHover() || me.getState() != this.state || (me.getSource() != this.node &&
+			(!this.stateSource || (me.getState() != null && this.stateSource ==
+			(me.isSource(me.getState().shape) || !me.isSource(me.getState().text))))))
+		{
+			this.hideTooltip();
+		}
+	}
+	
+	this.lastX = me.getX();
+	this.lastY = me.getY();
+};
+
+/**
+ * Function: mouseUp
+ * 
+ * Handles the event by resetting the tooltip timer or hiding the existing
+ * tooltip.
+ */
+mxTooltipHandler.prototype.mouseUp = function(sender, me)
+{
+	this.reset(me, true);
+	this.hideTooltip();
+};
+
+
+/**
+ * Function: resetTimer
+ * 
+ * Resets the timer.
+ */
+mxTooltipHandler.prototype.resetTimer = function()
+{
+	if (this.thread != null)
+	{
+		window.clearTimeout(this.thread);
+		this.thread = null;
+	}
+};
+
+/**
+ * Function: reset
+ * 
+ * Resets and/or restarts the timer to trigger the display of the tooltip.
+ */
+mxTooltipHandler.prototype.reset = function(me, restart)
+{
+	if (!this.ignoreTouchEvents || mxEvent.isMouseEvent(me.getEvent()))
+	{
+		this.resetTimer();
+		
+		if (restart && this.isEnabled() && me.getState() != null && (this.div == null ||
+			this.div.style.visibility == 'hidden'))
+		{
+			var state = me.getState();
+			var node = me.getSource();
+			var x = me.getX();
+			var y = me.getY();
+			var stateSource = me.isSource(state.shape) || me.isSource(state.text);
+	
+			this.thread = window.setTimeout(mxUtils.bind(this, function()
+			{
+				if (!this.graph.isEditing() && !this.graph.popupMenuHandler.isMenuShowing() && !this.graph.isMouseDown)
+				{
+					// Uses information from inside event cause using the event at
+					// this (delayed) point in time is not possible in IE as it no
+					// longer contains the required information (member not found)
+					var tip = this.graph.getTooltip(state, node, x, y);
+					this.show(tip, x, y);
+					this.state = state;
+					this.node = node;
+					this.stateSource = stateSource;
+				}
+			}), this.delay);
+		}
+	}
+};
+
+/**
+ * Function: hide
+ * 
+ * Hides the tooltip and resets the timer.
+ */
+mxTooltipHandler.prototype.hide = function()
+{
+	this.resetTimer();
+	this.hideTooltip();
+};
+
+/**
+ * Function: hideTooltip
+ * 
+ * Hides the tooltip.
+ */
+mxTooltipHandler.prototype.hideTooltip = function()
+{
+	if (this.div != null)
+	{
+		this.div.style.visibility = 'hidden';
+		this.div.innerHTML = '';
+	}
+};
+
+/**
+ * Function: show
+ * 
+ * Shows the tooltip for the specified cell and optional index at the
+ * specified location (with a vertical offset of 10 pixels).
+ */
+mxTooltipHandler.prototype.show = function(tip, x, y)
+{
+	if (!this.destroyed && tip != null && tip.length > 0)
+	{
+		// Initializes the DOM nodes if required
+		if (this.div == null)
+		{
+			this.init();
+		}
+		
+		var origin = mxUtils.getScrollOrigin();
+
+		this.div.style.zIndex = this.zIndex;
+		this.div.style.left = (x + origin.x) + 'px';
+		this.div.style.top = (y + mxConstants.TOOLTIP_VERTICAL_OFFSET +
+			origin.y) + 'px';
+
+		if (!mxUtils.isNode(tip))
+		{	
+			this.div.innerHTML = tip.replace(/\n/g, '<br>');
+		}
+		else
+		{
+			this.div.innerHTML = '';
+			this.div.appendChild(tip);
+		}
+		
+		this.div.style.visibility = '';
+		mxUtils.fit(this.div);
+	}
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the handler and all its resources and DOM nodes.
+ */
+mxTooltipHandler.prototype.destroy = function()
+{
+	if (!this.destroyed)
+	{
+		this.graph.removeMouseListener(this);
+		mxEvent.release(this.div);
+		
+		if (this.div != null && this.div.parentNode != null)
+		{
+			this.div.parentNode.removeChild(this.div);
+		}
+		
+		this.destroyed = true;
+		this.div = null;
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/handler/mxVertexHandler.js b/airavata-kubernetes/workflow-composer/src/js/handler/mxVertexHandler.js
new file mode 100644
index 0000000..7669d8c
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/handler/mxVertexHandler.js
@@ -0,0 +1,1950 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxVertexHandler
+ * 
+ * Event handler for resizing cells. This handler is automatically created in
+ * <mxGraph.createHandler>.
+ * 
+ * Constructor: mxVertexHandler
+ * 
+ * Constructs an event handler that allows to resize vertices
+ * and groups.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> of the cell to be resized.
+ */
+function mxVertexHandler(state)
+{
+	if (state != null)
+	{
+		this.state = state;
+		this.init();
+		
+		// Handles escape keystrokes
+		this.escapeHandler = mxUtils.bind(this, function(sender, evt)
+		{
+			if (this.livePreview && this.index != null)
+			{
+				// Redraws the live preview
+				this.state.view.graph.cellRenderer.redraw(this.state, true);
+				
+				// Redraws connected edges
+				this.state.view.invalidate(this.state.cell);
+				this.state.invalid = false;
+				this.state.view.validate();
+			}
+			
+			this.reset();
+		});
+		
+		this.state.view.graph.addListener(mxEvent.ESCAPE, this.escapeHandler);
+	}
+};
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxVertexHandler.prototype.graph = null;
+
+/**
+ * Variable: state
+ * 
+ * Reference to the <mxCellState> being modified.
+ */
+mxVertexHandler.prototype.state = null;
+
+/**
+ * Variable: singleSizer
+ * 
+ * Specifies if only one sizer handle at the bottom, right corner should be
+ * used. Default is false.
+ */
+mxVertexHandler.prototype.singleSizer = false;
+
+/**
+ * Variable: index
+ * 
+ * Holds the index of the current handle.
+ */
+mxVertexHandler.prototype.index = null;
+
+/**
+ * Variable: allowHandleBoundsCheck
+ * 
+ * Specifies if the bounds of handles should be used for hit-detection in IE or
+ * if <tolerance> > 0. Default is true.
+ */
+mxVertexHandler.prototype.allowHandleBoundsCheck = true;
+
+/**
+ * Variable: handleImage
+ * 
+ * Optional <mxImage> to be used as handles. Default is null.
+ */
+mxVertexHandler.prototype.handleImage = null;
+
+/**
+ * Variable: tolerance
+ * 
+ * Optional tolerance for hit-detection in <getHandleForEvent>. Default is 0.
+ */
+mxVertexHandler.prototype.tolerance = 0;
+
+/**
+ * Variable: rotationEnabled
+ * 
+ * Specifies if a rotation handle should be visible. Default is false.
+ */
+mxVertexHandler.prototype.rotationEnabled = false;
+
+/**
+ * Variable: parentHighlightEnabled
+ * 
+ * Specifies if the parent should be highlighted if a child cell is selected.
+ * Default is false.
+ */
+mxVertexHandler.prototype.parentHighlightEnabled = false;
+
+/**
+ * Variable: rotationRaster
+ * 
+ * Specifies if rotation steps should be "rasterized" depening on the distance
+ * to the handle. Default is true.
+ */
+mxVertexHandler.prototype.rotationRaster = true;
+
+/**
+ * Variable: rotationCursor
+ * 
+ * Specifies the cursor for the rotation handle. Default is 'crosshair'.
+ */
+mxVertexHandler.prototype.rotationCursor = 'crosshair';
+
+/**
+ * Variable: livePreview
+ * 
+ * Specifies if resize should change the cell in-place. This is an experimental
+ * feature for non-touch devices. Default is false.
+ */
+mxVertexHandler.prototype.livePreview = false;
+
+/**
+ * Variable: manageSizers
+ * 
+ * Specifies if sizers should be hidden and spaced if the vertex is small.
+ * Default is false.
+ */
+mxVertexHandler.prototype.manageSizers = false;
+
+/**
+ * Variable: constrainGroupByChildren
+ * 
+ * Specifies if the size of groups should be constrained by the children.
+ * Default is false.
+ */
+mxVertexHandler.prototype.constrainGroupByChildren = false;
+
+/**
+ * Variable: rotationHandleVSpacing
+ * 
+ * Vertical spacing for rotation icon. Default is -16.
+ */
+mxVertexHandler.prototype.rotationHandleVSpacing = -16;
+
+/**
+ * Variable: horizontalOffset
+ * 
+ * The horizontal offset for the handles. This is updated in <redrawHandles>
+ * if <manageSizers> is true and the sizers are offset horizontally.
+ */
+mxVertexHandler.prototype.horizontalOffset = 0;
+
+/**
+ * Variable: verticalOffset
+ * 
+ * The horizontal offset for the handles. This is updated in <redrawHandles>
+ * if <manageSizers> is true and the sizers are offset vertically.
+ */
+mxVertexHandler.prototype.verticalOffset = 0;
+
+/**
+ * Function: init
+ * 
+ * Initializes the shapes required for this vertex handler.
+ */
+mxVertexHandler.prototype.init = function()
+{
+	this.graph = this.state.view.graph;
+	this.selectionBounds = this.getSelectionBounds(this.state);
+	this.bounds = new mxRectangle(this.selectionBounds.x, this.selectionBounds.y, this.selectionBounds.width, this.selectionBounds.height);
+	this.selectionBorder = this.createSelectionShape(this.bounds);
+	// VML dialect required here for event transparency in IE
+	this.selectionBorder.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+	this.selectionBorder.pointerEvents = false;
+	this.selectionBorder.rotation = Number(this.state.style[mxConstants.STYLE_ROTATION] || '0');
+	this.selectionBorder.init(this.graph.getView().getOverlayPane());
+	mxEvent.redirectMouseEvents(this.selectionBorder.node, this.graph, this.state);
+	
+	if (this.graph.isCellMovable(this.state.cell))
+	{
+		this.selectionBorder.setCursor(mxConstants.CURSOR_MOVABLE_VERTEX);
+	}
+
+	// Adds the sizer handles
+	if (mxGraphHandler.prototype.maxCells <= 0 || this.graph.getSelectionCount() < mxGraphHandler.prototype.maxCells)
+	{
+		var resizable = this.graph.isCellResizable(this.state.cell);
+		this.sizers = [];
+
+		if (resizable || (this.graph.isLabelMovable(this.state.cell) &&
+			this.state.width >= 2 && this.state.height >= 2))
+		{
+			var i = 0;
+
+			if (resizable)
+			{
+				if (!this.singleSizer)
+				{
+					this.sizers.push(this.createSizer('nw-resize', i++));
+					this.sizers.push(this.createSizer('n-resize', i++));
+					this.sizers.push(this.createSizer('ne-resize', i++));
+					this.sizers.push(this.createSizer('w-resize', i++));
+					this.sizers.push(this.createSizer('e-resize', i++));
+					this.sizers.push(this.createSizer('sw-resize', i++));
+					this.sizers.push(this.createSizer('s-resize', i++));
+				}
+				
+				this.sizers.push(this.createSizer('se-resize', i++));
+			}
+			
+			var geo = this.graph.model.getGeometry(this.state.cell);
+			
+			if (geo != null && !geo.relative && !this.graph.isSwimlane(this.state.cell) &&
+				this.graph.isLabelMovable(this.state.cell))
+			{
+				// Marks this as the label handle for getHandleForEvent
+				this.labelShape = this.createSizer(mxConstants.CURSOR_LABEL_HANDLE, mxEvent.LABEL_HANDLE, mxConstants.LABEL_HANDLE_SIZE, mxConstants.LABEL_HANDLE_FILLCOLOR);
+				this.sizers.push(this.labelShape);
+			}
+		}
+		else if (this.graph.isCellMovable(this.state.cell) && !this.graph.isCellResizable(this.state.cell) &&
+			this.state.width < 2 && this.state.height < 2)
+		{
+			this.labelShape = this.createSizer(mxConstants.CURSOR_MOVABLE_VERTEX,
+				mxEvent.LABEL_HANDLE, null, mxConstants.LABEL_HANDLE_FILLCOLOR);
+			this.sizers.push(this.labelShape);
+		}
+	}
+	
+	// Adds the rotation handler
+	if (this.isRotationHandleVisible())
+	{
+		this.rotationShape = this.createSizer(this.rotationCursor, mxEvent.ROTATION_HANDLE,
+			mxConstants.HANDLE_SIZE + 3, mxConstants.HANDLE_FILLCOLOR);
+		this.sizers.push(this.rotationShape);
+	}
+
+	this.customHandles = this.createCustomHandles();
+	this.redraw();
+	
+	if (this.constrainGroupByChildren)
+	{
+		this.updateMinBounds();
+	}
+};
+
+/**
+ * Function: isRotationHandleVisible
+ * 
+ * Returns true if the rotation handle should be showing.
+ */
+mxVertexHandler.prototype.isRotationHandleVisible = function()
+{
+	return this.graph.isEnabled() && this.rotationEnabled && this.graph.isCellRotatable(this.state.cell) &&
+		(mxGraphHandler.prototype.maxCells <= 0 || this.graph.getSelectionCount() < mxGraphHandler.prototype.maxCells) &&
+		this.state.width >= 2 && this.state.height >= 2;
+};
+
+/**
+ * Function: isConstrainedEvent
+ * 
+ * Returns true if the aspect ratio if the cell should be maintained.
+ */
+mxVertexHandler.prototype.isConstrainedEvent = function(me)
+{
+	return mxEvent.isShiftDown(me.getEvent()) || this.state.style[mxConstants.STYLE_ASPECT] == 'fixed';
+};
+
+/**
+ * Function: isCenteredEvent
+ * 
+ * Returns true if the center of the vertex should be maintained during the resize.
+ */
+mxVertexHandler.prototype.isCenteredEvent = function(state, me)
+{
+	return false;
+};
+
+/**
+ * Function: createCustomHandles
+ * 
+ * Returns an array of custom handles. This implementation returns null.
+ */
+mxVertexHandler.prototype.createCustomHandles = function()
+{
+	return null;
+};
+
+/**
+ * Function: updateMinBounds
+ * 
+ * Initializes the shapes required for this vertex handler.
+ */
+mxVertexHandler.prototype.updateMinBounds = function()
+{
+	var children = this.graph.getChildCells(this.state.cell);
+	
+	if (children.length > 0)
+	{
+		this.minBounds = this.graph.view.getBounds(children);
+		
+		if (this.minBounds != null)
+		{
+			var s = this.state.view.scale;
+			var t = this.state.view.translate;
+
+			this.minBounds.x -= this.state.x;
+			this.minBounds.y -= this.state.y;
+			this.minBounds.x /= s;
+			this.minBounds.y /= s;
+			this.minBounds.width /= s;
+			this.minBounds.height /= s;
+			this.x0 = this.state.x / s - t.x;
+			this.y0 = this.state.y / s - t.y;
+		}
+	}
+};
+
+/**
+ * Function: getSelectionBounds
+ * 
+ * Returns the mxRectangle that defines the bounds of the selection
+ * border.
+ */
+mxVertexHandler.prototype.getSelectionBounds = function(state)
+{
+	return new mxRectangle(Math.round(state.x), Math.round(state.y), Math.round(state.width), Math.round(state.height));
+};
+
+/**
+ * Function: createParentHighlightShape
+ * 
+ * Creates the shape used to draw the selection border.
+ */
+mxVertexHandler.prototype.createParentHighlightShape = function(bounds)
+{
+	return this.createSelectionShape(bounds);
+};
+
+/**
+ * Function: createSelectionShape
+ * 
+ * Creates the shape used to draw the selection border.
+ */
+mxVertexHandler.prototype.createSelectionShape = function(bounds)
+{
+	var shape = new mxRectangleShape(bounds, null, this.getSelectionColor());
+	shape.strokewidth = this.getSelectionStrokeWidth();
+	shape.isDashed = this.isSelectionDashed();
+	
+	return shape;
+};
+
+/**
+ * Function: getSelectionColor
+ * 
+ * Returns <mxConstants.VERTEX_SELECTION_COLOR>.
+ */
+mxVertexHandler.prototype.getSelectionColor = function()
+{
+	return mxConstants.VERTEX_SELECTION_COLOR;
+};
+
+/**
+ * Function: getSelectionStrokeWidth
+ * 
+ * Returns <mxConstants.VERTEX_SELECTION_STROKEWIDTH>.
+ */
+mxVertexHandler.prototype.getSelectionStrokeWidth = function()
+{
+	return mxConstants.VERTEX_SELECTION_STROKEWIDTH;
+};
+
+/**
+ * Function: isSelectionDashed
+ * 
+ * Returns <mxConstants.VERTEX_SELECTION_DASHED>.
+ */
+mxVertexHandler.prototype.isSelectionDashed = function()
+{
+	return mxConstants.VERTEX_SELECTION_DASHED;
+};
+
+/**
+ * Function: createSizer
+ * 
+ * Creates a sizer handle for the specified cursor and index and returns
+ * the new <mxRectangleShape> that represents the handle.
+ */
+mxVertexHandler.prototype.createSizer = function(cursor, index, size, fillColor)
+{
+	size = size || mxConstants.HANDLE_SIZE;
+	
+	var bounds = new mxRectangle(0, 0, size, size);
+	var sizer = this.createSizerShape(bounds, index, fillColor);
+
+	if (sizer.isHtmlAllowed() && this.state.text != null && this.state.text.node.parentNode == this.graph.container)
+	{
+		sizer.bounds.height -= 1;
+		sizer.bounds.width -= 1;
+		sizer.dialect = mxConstants.DIALECT_STRICTHTML;
+		sizer.init(this.graph.container);
+	}
+	else
+	{
+		sizer.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
+				mxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG;
+		sizer.init(this.graph.getView().getOverlayPane());
+	}
+
+	mxEvent.redirectMouseEvents(sizer.node, this.graph, this.state);
+	
+	if (this.graph.isEnabled())
+	{
+		sizer.setCursor(cursor);
+	}
+	
+	if (!this.isSizerVisible(index))
+	{
+		sizer.visible = false;
+	}
+	
+	return sizer;
+};
+
+/**
+ * Function: isSizerVisible
+ * 
+ * Returns true if the sizer for the given index is visible.
+ * This returns true for all given indices.
+ */
+mxVertexHandler.prototype.isSizerVisible = function(index)
+{
+	return true;
+};
+
+/**
+ * Function: createSizerShape
+ * 
+ * Creates the shape used for the sizer handle for the specified bounds an
+ * index. Only images and rectangles should be returned if support for HTML
+ * labels with not foreign objects is required.
+ */
+mxVertexHandler.prototype.createSizerShape = function(bounds, index, fillColor)
+{
+	if (this.handleImage != null)
+	{
+		bounds = new mxRectangle(bounds.x, bounds.y, this.handleImage.width, this.handleImage.height);
+		var shape = new mxImageShape(bounds, this.handleImage.src);
+		
+		// Allows HTML rendering of the images
+		shape.preserveImageAspect = false;
+
+		return shape;
+	}
+	else if (index == mxEvent.ROTATION_HANDLE)
+	{
+		return new mxEllipse(bounds, fillColor || mxConstants.HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR);
+	}
+	else
+	{
+		return new mxRectangleShape(bounds, fillColor || mxConstants.HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR);
+	}
+};
+
+/**
+ * Function: createBounds
+ * 
+ * Helper method to create an <mxRectangle> around the given centerpoint
+ * with a width and height of 2*s or 6, if no s is given.
+ */
+mxVertexHandler.prototype.moveSizerTo = function(shape, x, y)
+{
+	if (shape != null)
+	{
+		shape.bounds.x = Math.floor(x - shape.bounds.width / 2);
+		shape.bounds.y = Math.floor(y - shape.bounds.height / 2);
+		
+		// Fixes visible inactive handles in VML
+		if (shape.node != null && shape.node.style.display != 'none')
+		{
+			shape.redraw();
+		}
+	}
+};
+
+/**
+ * Function: getHandleForEvent
+ * 
+ * Returns the index of the handle for the given event. This returns the index
+ * of the sizer from where the event originated or <mxEvent.LABEL_INDEX>.
+ */
+mxVertexHandler.prototype.getHandleForEvent = function(me)
+{
+	// Connection highlight may consume events before they reach sizer handle
+	var tol = (!mxEvent.isMouseEvent(me.getEvent())) ? this.tolerance : 1;
+	var hit = (this.allowHandleBoundsCheck && (mxClient.IS_IE || tol > 0)) ?
+		new mxRectangle(me.getGraphX() - tol, me.getGraphY() - tol, 2 * tol, 2 * tol) : null;
+	
+	function checkShape(shape)
+	{
+		return shape != null && (me.isSource(shape) || (hit != null && mxUtils.intersects(shape.bounds, hit) &&
+			shape.node.style.display != 'none' && shape.node.style.visibility != 'hidden'));
+	}
+
+	if (this.customHandles != null && this.isCustomHandleEvent(me))
+	{
+		// Inverse loop order to match display order
+		for (var i = this.customHandles.length - 1; i >= 0; i--)
+		{
+			if (checkShape(this.customHandles[i].shape))
+			{
+				// LATER: Return reference to active shape
+				return mxEvent.CUSTOM_HANDLE - i;
+			}
+		}
+	}
+
+	if (checkShape(this.rotationShape))
+	{
+		return mxEvent.ROTATION_HANDLE;
+	}
+	else if (checkShape(this.labelShape))
+	{
+		return mxEvent.LABEL_HANDLE;
+	}
+	
+	if (this.sizers != null)
+	{
+		for (var i = 0; i < this.sizers.length; i++)
+		{
+			if (checkShape(this.sizers[i]))
+			{
+				return i;
+			}
+		}
+	}
+
+	return null;
+};
+
+/**
+ * Function: isCustomHandleEvent
+ * 
+ * Returns true if the given event allows custom handles to be changed. This
+ * implementation returns true.
+ */
+mxVertexHandler.prototype.isCustomHandleEvent = function(me)
+{
+	return true;
+};
+
+/**
+ * Function: mouseDown
+ * 
+ * Handles the event if a handle has been clicked. By consuming the
+ * event all subsequent events of the gesture are redirected to this
+ * handler.
+ */
+mxVertexHandler.prototype.mouseDown = function(sender, me)
+{
+	var tol = (!mxEvent.isMouseEvent(me.getEvent())) ? this.tolerance : 0;
+	
+	if (!me.isConsumed() && this.graph.isEnabled() && (tol > 0 || me.getState() == this.state))
+	{
+		var handle = this.getHandleForEvent(me);
+
+		if (handle != null)
+		{
+			this.start(me.getGraphX(), me.getGraphY(), handle);
+			me.consume();
+		}
+	}
+};
+
+/**
+ * Function: isLivePreviewBorder
+ * 
+ * Called if <livePreview> is enabled to check if a border should be painted.
+ * This implementation returns true if the shape is transparent.
+ */
+mxVertexHandler.prototype.isLivePreviewBorder = function()
+{
+	return this.state.shape != null && this.state.shape.fill == null && this.state.shape.stroke == null;
+};
+
+/**
+ * Function: start
+ * 
+ * Starts the handling of the mouse gesture.
+ */
+mxVertexHandler.prototype.start = function(x, y, index)
+{
+	this.inTolerance = true;
+	this.childOffsetX = 0;
+	this.childOffsetY = 0;
+	this.index = index;
+	this.startX = x;
+	this.startY = y;
+	
+	// Saves reference to parent state
+	var model = this.state.view.graph.model;
+	var parent = model.getParent(this.state.cell);
+	
+	if (this.state.view.currentRoot != parent && (model.isVertex(parent) || model.isEdge(parent)))
+	{
+		this.parentState = this.state.view.graph.view.getState(parent);
+	}
+	
+	// Creates a preview that can be on top of any HTML label
+	this.selectionBorder.node.style.display = (index == mxEvent.ROTATION_HANDLE) ? 'inline' : 'none';
+	
+	// Creates the border that represents the new bounds
+	if (!this.livePreview || this.isLivePreviewBorder())
+	{
+		this.preview = this.createSelectionShape(this.bounds);
+		
+		if (!(mxClient.IS_SVG && Number(this.state.style[mxConstants.STYLE_ROTATION] || '0') != 0) &&
+			this.state.text != null && this.state.text.node.parentNode == this.graph.container)
+		{
+			this.preview.dialect = mxConstants.DIALECT_STRICTHTML;
+			this.preview.init(this.graph.container);
+		}
+		else
+		{
+			this.preview.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
+					mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+			this.preview.init(this.graph.view.getOverlayPane());
+		}
+	}
+	
+	// Prepares the handles for live preview
+	if (this.livePreview)
+	{
+		this.hideSizers();
+		
+		if (index == mxEvent.ROTATION_HANDLE)
+		{
+			this.rotationShape.node.style.display = '';
+		}
+		else if (index == mxEvent.LABEL_HANDLE)
+		{
+			this.labelShape.node.style.display = '';
+		}
+		else if (this.sizers != null && this.sizers[index] != null)
+		{
+			this.sizers[index].node.style.display = '';
+		}
+		else if (index <= mxEvent.CUSTOM_HANDLE && this.customHandles != null)
+		{
+			this.customHandles[mxEvent.CUSTOM_HANDLE - index].setVisible(true);
+		}
+		
+		// Gets the array of connected edge handlers for redrawing
+		var edges = this.graph.getEdges(this.state.cell);
+		this.edgeHandlers = [];
+		
+		for (var i = 0; i < edges.length; i++)
+		{
+			var handler = this.graph.selectionCellsHandler.getHandler(edges[i]);
+			
+			if (handler != null)
+			{
+				this.edgeHandlers.push(handler);
+			}
+		}
+	}
+};
+
+/**
+ * Function: hideHandles
+ * 
+ * Shortcut to <hideSizers>.
+ */
+mxVertexHandler.prototype.setHandlesVisible = function(visible)
+{
+	if (this.sizers != null)
+	{
+		for (var i = 0; i < this.sizers.length; i++)
+		{
+			this.sizers[i].node.style.display = (visible) ? '' : 'none';
+		}
+	}
+
+	if (this.customHandles != null)
+	{
+		for (var i = 0; i < this.customHandles.length; i++)
+		{
+			this.customHandles[i].setVisible(visible);
+		}
+	}
+};
+
+/**
+ * Function: hideSizers
+ * 
+ * Hides all sizers except.
+ * 
+ * Starts the handling of the mouse gesture.
+ */
+mxVertexHandler.prototype.hideSizers = function()
+{
+	this.setHandlesVisible(false);
+};
+
+/**
+ * Function: checkTolerance
+ * 
+ * Checks if the coordinates for the given event are within the
+ * <mxGraph.tolerance>. If the event is a mouse event then the tolerance is
+ * ignored.
+ */
+mxVertexHandler.prototype.checkTolerance = function(me)
+{
+	if (this.inTolerance && this.startX != null && this.startY != null)
+	{
+		if (mxEvent.isMouseEvent(me.getEvent()) ||
+			Math.abs(me.getGraphX() - this.startX) > this.graph.tolerance ||
+			Math.abs(me.getGraphY() - this.startY) > this.graph.tolerance)
+		{
+			this.inTolerance = false;
+		}
+	}
+};
+
+/**
+ * Function: updateHint
+ * 
+ * Hook for subclassers do show details while the handler is active.
+ */
+mxVertexHandler.prototype.updateHint = function(me) { };
+
+/**
+ * Function: removeHint
+ * 
+ * Hooks for subclassers to hide details when the handler gets inactive.
+ */
+mxVertexHandler.prototype.removeHint = function() { };
+
+/**
+ * Function: roundAngle
+ * 
+ * Hook for rounding the angle. This uses Math.round.
+ */
+mxVertexHandler.prototype.roundAngle = function(angle)
+{
+	return Math.round(angle * 10) / 10;
+};
+
+/**
+ * Function: roundLength
+ * 
+ * Hook for rounding the unscaled width or height. This uses Math.round.
+ */
+mxVertexHandler.prototype.roundLength = function(length)
+{
+	return Math.round(length);
+};
+
+/**
+ * Function: mouseMove
+ * 
+ * Handles the event by updating the preview.
+ */
+mxVertexHandler.prototype.mouseMove = function(sender, me)
+{
+	if (!me.isConsumed() && this.index != null)
+	{
+		// Checks tolerance for ignoring single clicks
+		this.checkTolerance(me);
+
+		if (!this.inTolerance)
+		{
+			if (this.index <= mxEvent.CUSTOM_HANDLE)
+			{
+				if (this.customHandles != null)
+				{
+					this.customHandles[mxEvent.CUSTOM_HANDLE - this.index].processEvent(me);
+					this.customHandles[mxEvent.CUSTOM_HANDLE - this.index].active = true;
+				}
+			}
+			else if (this.index == mxEvent.LABEL_HANDLE)
+			{
+				this.moveLabel(me);
+			}
+			else if (this.index == mxEvent.ROTATION_HANDLE)
+			{
+				this.rotateVertex(me);
+			}
+			else
+			{
+				this.resizeVertex(me);
+			}
+
+			this.updateHint(me);
+		}
+		
+		me.consume();
+	}
+	// Workaround for disabling the connect highlight when over handle
+	else if (!this.graph.isMouseDown && this.getHandleForEvent(me) != null)
+	{
+		me.consume(false);
+	}
+};
+
+/**
+ * Function: rotateVertex
+ * 
+ * Rotates the vertex.
+ */
+mxVertexHandler.prototype.moveLabel = function(me)
+{
+	var point = new mxPoint(me.getGraphX(), me.getGraphY());
+	var tr = this.graph.view.translate;
+	var scale = this.graph.view.scale;
+	
+	if (this.graph.isGridEnabledEvent(me.getEvent()))
+	{
+		point.x = (this.graph.snap(point.x / scale - tr.x) + tr.x) * scale;
+		point.y = (this.graph.snap(point.y / scale - tr.y) + tr.y) * scale;
+	}
+
+	var index = (this.rotationShape != null) ? this.sizers.length - 2 : this.sizers.length - 1;
+	this.moveSizerTo(this.sizers[index], point.x, point.y);
+};
+
+/**
+ * Function: rotateVertex
+ * 
+ * Rotates the vertex.
+ */
+mxVertexHandler.prototype.rotateVertex = function(me)
+{
+	var point = new mxPoint(me.getGraphX(), me.getGraphY());
+	var dx = this.state.x + this.state.width / 2 - point.x;
+	var dy = this.state.y + this.state.height / 2 - point.y;
+	this.currentAlpha = (dx != 0) ? Math.atan(dy / dx) * 180 / Math.PI + 90 : ((dy < 0) ? 180 : 0);
+	
+	if (dx > 0)
+	{
+		this.currentAlpha -= 180;
+	}
+
+	// Rotation raster
+	if (this.rotationRaster && this.graph.isGridEnabledEvent(me.getEvent()))
+	{
+		var dx = point.x - this.state.getCenterX();
+		var dy = point.y - this.state.getCenterY();
+		var dist = Math.abs(Math.sqrt(dx * dx + dy * dy) - 20) * 3;
+		var raster = Math.max(1, 5 * Math.min(3, Math.max(0, Math.round(80 / Math.abs(dist)))));
+		
+		this.currentAlpha = Math.round(this.currentAlpha / raster) * raster;
+	}
+	else
+	{
+		this.currentAlpha = this.roundAngle(this.currentAlpha);
+	}
+
+	this.selectionBorder.rotation = this.currentAlpha;
+	this.selectionBorder.redraw();
+					
+	if (this.livePreview)
+	{
+		this.redrawHandles();
+	}
+};
+
+/**
+ * Function: rotateVertex
+ * 
+ * Rotates the vertex.
+ */
+mxVertexHandler.prototype.resizeVertex = function(me)
+{
+	var ct = new mxPoint(this.state.getCenterX(), this.state.getCenterY());
+	var alpha = mxUtils.toRadians(this.state.style[mxConstants.STYLE_ROTATION] || '0');
+	var point = new mxPoint(me.getGraphX(), me.getGraphY());
+	var tr = this.graph.view.translate;
+	var scale = this.graph.view.scale;
+	var cos = Math.cos(-alpha);
+	var sin = Math.sin(-alpha);
+	
+	var dx = point.x - this.startX;
+	var dy = point.y - this.startY;
+
+	// Rotates vector for mouse gesture
+	var tx = cos * dx - sin * dy;
+	var ty = sin * dx + cos * dy;
+	
+	dx = tx;
+	dy = ty;
+
+	var geo = this.graph.getCellGeometry(this.state.cell);
+	this.unscaledBounds = this.union(geo, dx / scale, dy / scale, this.index,
+		this.graph.isGridEnabledEvent(me.getEvent()), 1,
+		new mxPoint(0, 0), this.isConstrainedEvent(me),
+		this.isCenteredEvent(this.state, me));
+	
+	// Keeps vertex within maximum graph or parent bounds
+	if (!geo.relative)
+	{
+		var max = this.graph.getMaximumGraphBounds();
+		
+		// Handles child cells
+		if (max != null && this.parentState != null)
+		{
+			max = mxRectangle.fromRectangle(max);
+			
+			max.x -= (this.parentState.x - tr.x * scale) / scale;
+			max.y -= (this.parentState.y - tr.y * scale) / scale;
+		}
+		
+		if (this.graph.isConstrainChild(this.state.cell))
+		{
+			var tmp = this.graph.getCellContainmentArea(this.state.cell);
+			
+			if (tmp != null)
+			{
+				var overlap = this.graph.getOverlap(this.state.cell);
+				
+				if (overlap > 0)
+				{
+					tmp = mxRectangle.fromRectangle(tmp);
+					
+					tmp.x -= tmp.width * overlap;
+					tmp.y -= tmp.height * overlap;
+					tmp.width += 2 * tmp.width * overlap;
+					tmp.height += 2 * tmp.height * overlap;
+				}
+				
+				if (max == null)
+				{
+					max = tmp;
+				}
+				else
+				{
+					max = mxRectangle.fromRectangle(max);
+					max.intersect(tmp);
+				}
+			}
+		}
+	
+		if (max != null)
+		{
+			if (this.unscaledBounds.x < max.x)
+			{
+				this.unscaledBounds.width -= max.x - this.unscaledBounds.x;
+				this.unscaledBounds.x = max.x;
+			}
+			
+			if (this.unscaledBounds.y < max.y)
+			{
+				this.unscaledBounds.height -= max.y - this.unscaledBounds.y;
+				this.unscaledBounds.y = max.y;
+			}
+			
+			if (this.unscaledBounds.x + this.unscaledBounds.width > max.x + max.width)
+			{
+				this.unscaledBounds.width -= this.unscaledBounds.x +
+					this.unscaledBounds.width - max.x - max.width;
+			}
+			
+			if (this.unscaledBounds.y + this.unscaledBounds.height > max.y + max.height)
+			{
+				this.unscaledBounds.height -= this.unscaledBounds.y +
+					this.unscaledBounds.height - max.y - max.height;
+			}
+		}
+	}
+	
+	this.bounds = new mxRectangle(((this.parentState != null) ? this.parentState.x : tr.x * scale) +
+		(this.unscaledBounds.x) * scale, ((this.parentState != null) ? this.parentState.y : tr.y * scale) +
+		(this.unscaledBounds.y) * scale, this.unscaledBounds.width * scale, this.unscaledBounds.height * scale);
+
+	if (geo.relative && this.parentState != null)
+	{
+		this.bounds.x += this.state.x - this.parentState.x;
+		this.bounds.y += this.state.y - this.parentState.y;
+	}
+
+	cos = Math.cos(alpha);
+	sin = Math.sin(alpha);
+	
+	var c2 = new mxPoint(this.bounds.getCenterX(), this.bounds.getCenterY());
+
+	var dx = c2.x - ct.x;
+	var dy = c2.y - ct.y;
+	
+	var dx2 = cos * dx - sin * dy;
+	var dy2 = sin * dx + cos * dy;
+	
+	var dx3 = dx2 - dx;
+	var dy3 = dy2 - dy;
+	
+	var dx4 = this.bounds.x - this.state.x;
+	var dy4 = this.bounds.y - this.state.y;
+	
+	var dx5 = cos * dx4 - sin * dy4;
+	var dy5 = sin * dx4 + cos * dy4;
+	
+	this.bounds.x += dx3;
+	this.bounds.y += dy3;
+	
+	// Rounds unscaled bounds to int
+	this.unscaledBounds.x = this.roundLength(this.unscaledBounds.x + dx3 / scale);
+	this.unscaledBounds.y = this.roundLength(this.unscaledBounds.y + dy3 / scale);
+	this.unscaledBounds.width = this.roundLength(this.unscaledBounds.width);
+	this.unscaledBounds.height = this.roundLength(this.unscaledBounds.height);
+	
+	// Shifts the children according to parent offset
+	if (!this.graph.isCellCollapsed(this.state.cell) && (dx3 != 0 || dy3 != 0))
+	{
+		this.childOffsetX = this.state.x - this.bounds.x + dx5;
+		this.childOffsetY = this.state.y - this.bounds.y + dy5;
+	}
+	else
+	{
+		this.childOffsetX = 0;
+		this.childOffsetY = 0;
+	}
+	
+	if (this.livePreview)
+	{
+		this.updateLivePreview(me);
+	}
+	
+	if (this.preview != null)
+	{
+		this.drawPreview();
+	}
+};
+
+/**
+ * Function: updateLivePreview
+ * 
+ * Repaints the live preview.
+ */
+mxVertexHandler.prototype.updateLivePreview = function(me)
+{
+	// TODO: Apply child offset to children in live preview
+	var scale = this.graph.view.scale;
+	var tr = this.graph.view.translate;
+	
+	// Saves current state
+	var tempState = this.state.clone();
+
+	// Temporarily changes size and origin
+	this.state.x = this.bounds.x;
+	this.state.y = this.bounds.y;
+	this.state.origin = new mxPoint(this.state.x / scale - tr.x, this.state.y / scale - tr.y);
+	this.state.width = this.bounds.width;
+	this.state.height = this.bounds.height;
+	
+	// Needed to force update of text bounds
+	this.state.unscaledWidth = null;
+	
+	// Redraws cell and handles
+	var off = this.state.absoluteOffset;
+	off = new mxPoint(off.x, off.y);
+
+	// Required to store and reset absolute offset for updating label position
+	this.state.absoluteOffset.x = 0;
+	this.state.absoluteOffset.y = 0;
+	var geo = this.graph.getCellGeometry(this.state.cell);				
+
+	if (geo != null)
+	{
+		var offset = geo.offset || this.EMPTY_POINT;
+
+		if (offset != null && !geo.relative)
+		{
+			this.state.absoluteOffset.x = this.state.view.scale * offset.x;
+			this.state.absoluteOffset.y = this.state.view.scale * offset.y;
+		}
+		
+		this.state.view.updateVertexLabelOffset(this.state);
+	}
+	
+	// Draws the live preview
+	this.state.view.graph.cellRenderer.redraw(this.state, true);
+	
+	// Redraws connected edges TODO: Include child edges
+	this.state.view.invalidate(this.state.cell);
+	this.state.invalid = false;
+	this.state.view.validate();
+	this.redrawHandles();
+	
+	// Restores current state
+	this.state.setState(tempState);
+};
+
+/**
+ * Function: mouseUp
+ * 
+ * Handles the event by applying the changes to the geometry.
+ */
+mxVertexHandler.prototype.mouseUp = function(sender, me)
+{
+	if (this.index != null && this.state != null)
+	{
+		var point = new mxPoint(me.getGraphX(), me.getGraphY());
+
+		this.graph.getModel().beginUpdate();
+		try
+		{
+			if (this.index <= mxEvent.CUSTOM_HANDLE)
+			{
+				if (this.customHandles != null)
+				{
+					this.customHandles[mxEvent.CUSTOM_HANDLE - this.index].active = false;
+					this.customHandles[mxEvent.CUSTOM_HANDLE - this.index].execute();
+				}
+			}
+			else if (this.index == mxEvent.ROTATION_HANDLE)
+			{
+				if (this.currentAlpha != null)
+				{
+					var delta = this.currentAlpha - (this.state.style[mxConstants.STYLE_ROTATION] || 0);
+					
+					if (delta != 0)
+					{
+						this.rotateCell(this.state.cell, delta);
+					}
+				}
+				else
+				{
+					this.rotateClick();
+				}
+			}
+			else
+			{
+				var gridEnabled = this.graph.isGridEnabledEvent(me.getEvent());
+				var alpha = mxUtils.toRadians(this.state.style[mxConstants.STYLE_ROTATION] || '0');
+				var cos = Math.cos(-alpha);
+				var sin = Math.sin(-alpha);
+				
+				var dx = point.x - this.startX;
+				var dy = point.y - this.startY;
+				
+				// Rotates vector for mouse gesture
+				var tx = cos * dx - sin * dy;
+				var ty = sin * dx + cos * dy;
+				
+				dx = tx;
+				dy = ty;
+				
+				var s = this.graph.view.scale;
+				var recurse = this.isRecursiveResize(this.state, me);
+				this.resizeCell(this.state.cell, this.roundLength(dx / s), this.roundLength(dy / s),
+					this.index, gridEnabled, this.isConstrainedEvent(me), recurse);
+			}
+		}
+		finally
+		{
+			this.graph.getModel().endUpdate();
+		}
+
+		me.consume();
+		this.reset();
+	}
+};
+
+/**
+ * Function: rotateCell
+ * 
+ * Rotates the given cell to the given rotation.
+ */
+mxVertexHandler.prototype.isRecursiveResize = function(state, me)
+{
+	return this.graph.isRecursiveResize(this.state);
+};
+
+/**
+ * Function: rotateClick
+ * 
+ * Hook for subclassers to implement a single click on the rotation handle.
+ * This code is executed as part of the model transaction. This implementation
+ * is empty.
+ */
+mxVertexHandler.prototype.rotateClick = function() { };
+
+/**
+ * Function: rotateCell
+ * 
+ * Rotates the given cell and its children by the given angle in degrees.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to be rotated.
+ * angle - Angle in degrees.
+ */
+mxVertexHandler.prototype.rotateCell = function(cell, angle, parent)
+{
+	if (angle != 0)
+	{
+		var model = this.graph.getModel();
+
+		if (model.isVertex(cell) || model.isEdge(cell))
+		{
+			if (!model.isEdge(cell))
+			{
+				var state = this.graph.view.getState(cell);
+				var style = (state != null) ? state.style : this.graph.getCellStyle(cell);
+		
+				if (style != null)
+				{
+					var total = (style[mxConstants.STYLE_ROTATION] || 0) + angle;
+					this.graph.setCellStyles(mxConstants.STYLE_ROTATION, total, [cell]);
+				}
+			}
+			
+			var geo = this.graph.getCellGeometry(cell);
+			
+			if (geo != null)
+			{
+				var pgeo = this.graph.getCellGeometry(parent);
+				
+				if (pgeo != null && !model.isEdge(parent))
+				{
+					geo = geo.clone();
+					geo.rotate(angle, new mxPoint(pgeo.width / 2, pgeo.height / 2));
+					model.setGeometry(cell, geo);
+				}
+				
+				if ((model.isVertex(cell) && !geo.relative) || model.isEdge(cell))
+				{
+					// Recursive rotation
+					var childCount = model.getChildCount(cell);
+					
+					for (var i = 0; i < childCount; i++)
+					{
+						this.rotateCell(model.getChildAt(cell, i), angle, cell);
+					}
+				}
+			}
+		}
+	}
+};
+
+/**
+ * Function: reset
+ * 
+ * Resets the state of this handler.
+ */
+mxVertexHandler.prototype.reset = function()
+{
+	if (this.sizers != null && this.index != null && this.sizers[this.index] != null &&
+		this.sizers[this.index].node.style.display == 'none')
+	{
+		this.sizers[this.index].node.style.display = '';
+	}
+
+	this.currentAlpha = null;
+	this.inTolerance = null;
+	this.index = null;
+
+	// TODO: Reset and redraw cell states for live preview
+	if (this.preview != null)
+	{
+		this.preview.destroy();
+		this.preview = null;
+	}
+
+	if (this.livePreview && this.sizers != null)
+	{
+		for (var i = 0; i < this.sizers.length; i++)
+		{
+			if (this.sizers[i] != null)
+			{
+				this.sizers[i].node.style.display = '';
+			}
+		}
+	}
+
+	if (this.customHandles != null)
+	{
+		for (var i = 0; i < this.customHandles.length; i++)
+		{
+			if (this.customHandles[i].active)
+			{
+				this.customHandles[i].active = false;
+				this.customHandles[i].reset();
+			}
+			else
+			{
+				this.customHandles[i].setVisible(true);
+			}
+		}
+	}
+	
+	// Checks if handler has been destroyed
+	if (this.selectionBorder != null)
+	{
+		this.selectionBorder.node.style.display = 'inline';
+		this.selectionBounds = this.getSelectionBounds(this.state);
+		this.bounds = new mxRectangle(this.selectionBounds.x, this.selectionBounds.y,
+			this.selectionBounds.width, this.selectionBounds.height);
+		this.drawPreview();
+	}
+
+	this.removeHint();
+	this.redrawHandles();
+	this.edgeHandlers = null;
+	this.unscaledBounds = null;
+};
+
+/**
+ * Function: resizeCell
+ * 
+ * Uses the given vector to change the bounds of the given cell
+ * in the graph using <mxGraph.resizeCell>.
+ */
+mxVertexHandler.prototype.resizeCell = function(cell, dx, dy, index, gridEnabled, constrained, recurse)
+{
+	var geo = this.graph.model.getGeometry(cell);
+	
+	if (geo != null)
+	{
+		if (index == mxEvent.LABEL_HANDLE)
+		{
+			var scale = this.graph.view.scale;
+			dx = Math.round((this.labelShape.bounds.getCenterX() - this.startX) / scale);
+			dy = Math.round((this.labelShape.bounds.getCenterY() - this.startY) / scale);
+			
+			geo = geo.clone();
+			
+			if (geo.offset == null)
+			{
+				geo.offset = new mxPoint(dx, dy);
+			}
+			else
+			{
+				geo.offset.x += dx;
+				geo.offset.y += dy;
+			}
+			
+			this.graph.model.setGeometry(cell, geo);
+		}
+		else if (this.unscaledBounds != null)
+		{
+			var scale = this.graph.view.scale;
+
+			if (this.childOffsetX != 0 || this.childOffsetY != 0)
+			{
+				this.moveChildren(cell, Math.round(this.childOffsetX / scale), Math.round(this.childOffsetY / scale));
+			}
+
+			this.graph.resizeCell(cell, this.unscaledBounds, recurse);
+		}
+	}
+};
+
+/**
+ * Function: moveChildren
+ * 
+ * Moves the children of the given cell by the given vector.
+ */
+mxVertexHandler.prototype.moveChildren = function(cell, dx, dy)
+{
+	var model = this.graph.getModel();
+	var childCount = model.getChildCount(cell);
+	
+	for (var i = 0; i < childCount; i++)
+	{
+		var child = model.getChildAt(cell, i);
+		var geo = this.graph.getCellGeometry(child);
+		
+		if (geo != null)
+		{
+			geo = geo.clone();
+			geo.translate(dx, dy);
+			model.setGeometry(child, geo);
+		}
+	}
+};
+/**
+ * Function: union
+ * 
+ * Returns the union of the given bounds and location for the specified
+ * handle index.
+ * 
+ * To override this to limit the size of vertex via a minWidth/-Height style,
+ * the following code can be used.
+ * 
+ * (code)
+ * var vertexHandlerUnion = mxVertexHandler.prototype.union;
+ * mxVertexHandler.prototype.union = function(bounds, dx, dy, index, gridEnabled, scale, tr, constrained)
+ * {
+ *   var result = vertexHandlerUnion.apply(this, arguments);
+ *   
+ *   result.width = Math.max(result.width, mxUtils.getNumber(this.state.style, 'minWidth', 0));
+ *   result.height = Math.max(result.height, mxUtils.getNumber(this.state.style, 'minHeight', 0));
+ *   
+ *   return result;
+ * };
+ * (end)
+ * 
+ * The minWidth/-Height style can then be used as follows:
+ * 
+ * (code)
+ * graph.insertVertex(parent, null, 'Hello,', 20, 20, 80, 30, 'minWidth=100;minHeight=100;');
+ * (end)
+ * 
+ * To override this to update the height for a wrapped text if the width of a vertex is
+ * changed, the following can be used.
+ * 
+ * (code)
+ * var mxVertexHandlerUnion = mxVertexHandler.prototype.union;
+ * mxVertexHandler.prototype.union = function(bounds, dx, dy, index, gridEnabled, scale, tr, constrained)
+ * {
+ *   var result = mxVertexHandlerUnion.apply(this, arguments);
+ *   var s = this.state;
+ *   
+ *   if (this.graph.isHtmlLabel(s.cell) && (index == 3 || index == 4) &&
+ *       s.text != null && s.style[mxConstants.STYLE_WHITE_SPACE] == 'wrap')
+ *   {
+ *     var label = this.graph.getLabel(s.cell);
+ *     var fontSize = mxUtils.getNumber(s.style, mxConstants.STYLE_FONTSIZE, mxConstants.DEFAULT_FONTSIZE);
+ *     var ww = result.width / s.view.scale - s.text.spacingRight - s.text.spacingLeft
+ *     
+ *     result.height = mxUtils.getSizeForString(label, fontSize, s.style[mxConstants.STYLE_FONTFAMILY], ww).height;
+ *   }
+ *   
+ *   return result;
+ * };
+ * (end)
+ */
+mxVertexHandler.prototype.union = function(bounds, dx, dy, index, gridEnabled, scale, tr, constrained, centered)
+{
+	if (this.singleSizer)
+	{
+		var x = bounds.x + bounds.width + dx;
+		var y = bounds.y + bounds.height + dy;
+		
+		if (gridEnabled)
+		{
+			x = this.graph.snap(x / scale) * scale;
+			y = this.graph.snap(y / scale) * scale;
+		}
+		
+		var rect = new mxRectangle(bounds.x, bounds.y, 0, 0);
+		rect.add(new mxRectangle(x, y, 0, 0));
+		
+		return rect;
+	}
+	else
+	{
+		var w0 = bounds.width;
+		var h0 = bounds.height;
+		var left = bounds.x - tr.x * scale;
+		var right = left + w0;
+		var top = bounds.y - tr.y * scale;
+		var bottom = top + h0;
+		
+		var cx = left + w0 / 2;
+		var cy = top + h0 / 2;
+		
+		if (index > 4 /* Bottom Row */)
+		{
+			bottom = bottom + dy;
+			
+			if (gridEnabled)
+			{
+				bottom = this.graph.snap(bottom / scale) * scale;
+			}
+		}
+		else if (index < 3 /* Top Row */)
+		{
+			top = top + dy;
+			
+			if (gridEnabled)
+			{
+				top = this.graph.snap(top / scale) * scale;
+			}
+		}
+		
+		if (index == 0 || index == 3 || index == 5 /* Left */)
+		{
+			left += dx;
+			
+			if (gridEnabled)
+			{
+				left = this.graph.snap(left / scale) * scale;
+			}
+		}
+		else if (index == 2 || index == 4 || index == 7 /* Right */)
+		{
+			right += dx;
+			
+			if (gridEnabled)
+			{
+				right = this.graph.snap(right / scale) * scale;
+			}
+		}
+		
+		var width = right - left;
+		var height = bottom - top;
+
+		if (constrained)
+		{
+			var geo = this.graph.getCellGeometry(this.state.cell);
+
+			if (geo != null)
+			{
+				var aspect = geo.width / geo.height;
+				
+				if (index== 1 || index== 2 || index == 7 || index == 6)
+				{
+					width = height * aspect;
+				}
+				else
+				{
+					height = width / aspect;
+				}
+				
+				if (index == 0)
+				{
+					left = right - width;
+					top = bottom - height;
+				}
+			}
+		}
+
+		if (centered)
+		{
+			width += (width - w0);
+			height += (height - h0);
+			
+			var cdx = cx - (left + width / 2);
+			var cdy = cy - (top + height / 2);
+
+			left += cdx;
+			top += cdy;
+			right += cdx;
+			bottom += cdy;
+		}
+
+		// Flips over left side
+		if (width < 0)
+		{
+			left += width;
+			width = Math.abs(width);
+		}
+		
+		// Flips over top side
+		if (height < 0)
+		{
+			top += height;
+			height = Math.abs(height);
+		}
+
+		var result = new mxRectangle(left + tr.x * scale, top + tr.y * scale, width, height);
+		
+		if (this.minBounds != null)
+		{
+			result.width = Math.max(result.width, this.minBounds.x * scale + this.minBounds.width * scale +
+				Math.max(0, this.x0 * scale - result.x));
+			result.height = Math.max(result.height, this.minBounds.y * scale + this.minBounds.height * scale +
+				Math.max(0, this.y0 * scale - result.y));
+		}
+		
+		return result;
+	}
+};
+
+/**
+ * Function: redraw
+ * 
+ * Redraws the handles and the preview.
+ */
+mxVertexHandler.prototype.redraw = function()
+{
+	this.selectionBounds = this.getSelectionBounds(this.state);
+	this.bounds = new mxRectangle(this.selectionBounds.x, this.selectionBounds.y, this.selectionBounds.width, this.selectionBounds.height);
+	
+	this.redrawHandles();
+	this.drawPreview();
+};
+
+/**
+ * Returns the padding to be used for drawing handles for the current <bounds>.
+ */
+mxVertexHandler.prototype.getHandlePadding = function()
+{
+	// KNOWN: Tolerance depends on event type (eg. 0 for mouse events)
+	var result = new mxPoint(0, 0);
+	var tol = this.tolerance;
+
+	if (this.sizers != null && this.sizers.length > 0 && this.sizers[0] != null &&
+		(this.bounds.width < 2 * this.sizers[0].bounds.width + 2 * tol ||
+		this.bounds.height < 2 * this.sizers[0].bounds.height + 2 * tol))
+	{
+		tol /= 2;
+		
+		result.x = this.sizers[0].bounds.width + tol;
+		result.y = this.sizers[0].bounds.height + tol;
+	}
+	
+	return result;
+};
+
+/**
+ * Function: redrawHandles
+ * 
+ * Redraws the handles. To hide certain handles the following code can be used.
+ * 
+ * (code)
+ * mxVertexHandler.prototype.redrawHandles = function()
+ * {
+ *   mxVertexHandlerRedrawHandles.apply(this, arguments);
+ *   
+ *   if (this.sizers != null && this.sizers.length > 7)
+ *   {
+ *     this.sizers[1].node.style.display = 'none';
+ *     this.sizers[6].node.style.display = 'none';
+ *   }
+ * };
+ * (end)
+ */
+mxVertexHandler.prototype.redrawHandles = function()
+{
+	var tol = this.tolerance;
+	this.horizontalOffset = 0;
+	this.verticalOffset = 0;
+	var s = this.bounds;
+
+	if (this.sizers != null && this.sizers.length > 0 && this.sizers[0] != null)
+	{
+		if (this.index == null && this.manageSizers && this.sizers.length >= 8)
+		{
+			// KNOWN: Tolerance depends on event type (eg. 0 for mouse events)
+			var padding = this.getHandlePadding();
+			this.horizontalOffset = padding.x;
+			this.verticalOffset = padding.y;
+			
+			if (this.horizontalOffset != 0 || this.verticalOffset != 0)
+			{
+				s = new mxRectangle(s.x, s.y, s.width, s.height);
+
+				s.x -= this.horizontalOffset / 2;
+				s.width += this.horizontalOffset;
+				s.y -= this.verticalOffset / 2;
+				s.height += this.verticalOffset;
+			}
+			
+			if (this.sizers.length >= 8)
+			{
+				if ((s.width < 2 * this.sizers[0].bounds.width + 2 * tol) ||
+					(s.height < 2 * this.sizers[0].bounds.height + 2 * tol))
+				{
+					this.sizers[0].node.style.display = 'none';
+					this.sizers[2].node.style.display = 'none';
+					this.sizers[5].node.style.display = 'none';
+					this.sizers[7].node.style.display = 'none';
+				}
+				else
+				{
+					this.sizers[0].node.style.display = '';
+					this.sizers[2].node.style.display = '';
+					this.sizers[5].node.style.display = '';
+					this.sizers[7].node.style.display = '';
+				}
+			}
+		}
+
+		var r = s.x + s.width;
+		var b = s.y + s.height;
+		
+		if (this.singleSizer)
+		{
+			this.moveSizerTo(this.sizers[0], r, b);
+		}
+		else
+		{
+			var cx = s.x + s.width / 2;
+			var cy = s.y + s.height / 2;
+			
+			if (this.sizers.length >= 8)
+			{
+				var crs = ['nw-resize', 'n-resize', 'ne-resize', 'e-resize', 'se-resize', 's-resize', 'sw-resize', 'w-resize'];
+				
+				var alpha = mxUtils.toRadians(this.state.style[mxConstants.STYLE_ROTATION] || '0');
+				var cos = Math.cos(alpha);
+				var sin = Math.sin(alpha);
+				
+				var da = Math.round(alpha * 4 / Math.PI);
+				
+				var ct = new mxPoint(s.getCenterX(), s.getCenterY());
+				var pt = mxUtils.getRotatedPoint(new mxPoint(s.x, s.y), cos, sin, ct);
+				
+				this.moveSizerTo(this.sizers[0], pt.x, pt.y);
+				this.sizers[0].setCursor(crs[mxUtils.mod(0 + da, crs.length)]);
+				
+				pt.x = cx;
+				pt.y = s.y;
+				pt = mxUtils.getRotatedPoint(pt, cos, sin, ct);
+				
+				this.moveSizerTo(this.sizers[1], pt.x, pt.y);
+				this.sizers[1].setCursor(crs[mxUtils.mod(1 + da, crs.length)]);
+				
+				pt.x = r;
+				pt.y = s.y;
+				pt = mxUtils.getRotatedPoint(pt, cos, sin, ct);
+				
+				this.moveSizerTo(this.sizers[2], pt.x, pt.y);
+				this.sizers[2].setCursor(crs[mxUtils.mod(2 + da, crs.length)]);
+				
+				pt.x = s.x;
+				pt.y = cy;
+				pt = mxUtils.getRotatedPoint(pt, cos, sin, ct);
+				
+				this.moveSizerTo(this.sizers[3], pt.x, pt.y);
+				this.sizers[3].setCursor(crs[mxUtils.mod(7 + da, crs.length)]);
+
+				pt.x = r;
+				pt.y = cy;
+				pt = mxUtils.getRotatedPoint(pt, cos, sin, ct);
+				
+				this.moveSizerTo(this.sizers[4], pt.x, pt.y);
+				this.sizers[4].setCursor(crs[mxUtils.mod(3 + da, crs.length)]);
+
+				pt.x = s.x;
+				pt.y = b;
+				pt = mxUtils.getRotatedPoint(pt, cos, sin, ct);
+				
+				this.moveSizerTo(this.sizers[5], pt.x, pt.y);
+				this.sizers[5].setCursor(crs[mxUtils.mod(6 + da, crs.length)]);
+
+				pt.x = cx;
+				pt.y = b;
+				pt = mxUtils.getRotatedPoint(pt, cos, sin, ct);
+				
+				this.moveSizerTo(this.sizers[6], pt.x, pt.y);
+				this.sizers[6].setCursor(crs[mxUtils.mod(5 + da, crs.length)]);
+
+				pt.x = r;
+				pt.y = b;
+				pt = mxUtils.getRotatedPoint(pt, cos, sin, ct);
+				
+				this.moveSizerTo(this.sizers[7], pt.x, pt.y);
+				this.sizers[7].setCursor(crs[mxUtils.mod(4 + da, crs.length)]);
+				
+				this.moveSizerTo(this.sizers[8], cx + this.state.absoluteOffset.x, cy + this.state.absoluteOffset.y);
+			}
+			else if (this.state.width >= 2 && this.state.height >= 2)
+			{
+				this.moveSizerTo(this.sizers[0], cx + this.state.absoluteOffset.x, cy + this.state.absoluteOffset.y);
+			}
+			else
+			{
+				this.moveSizerTo(this.sizers[0], this.state.x, this.state.y);
+			}
+		}
+	}
+
+	if (this.rotationShape != null)
+	{
+		var alpha = mxUtils.toRadians((this.currentAlpha != null) ? this.currentAlpha : this.state.style[mxConstants.STYLE_ROTATION] || '0');
+		var cos = Math.cos(alpha);
+		var sin = Math.sin(alpha);
+		
+		var ct = new mxPoint(this.state.getCenterX(), this.state.getCenterY());
+		var pt = mxUtils.getRotatedPoint(new mxPoint(s.x + s.width / 2, s.y + this.rotationHandleVSpacing), cos, sin, ct);
+
+		if (this.rotationShape.node != null)
+		{
+			this.moveSizerTo(this.rotationShape, pt.x, pt.y);
+		}
+	}
+	
+	if (this.selectionBorder != null)
+	{
+		this.selectionBorder.rotation = Number(this.state.style[mxConstants.STYLE_ROTATION] || '0');
+	}
+	
+	if (this.edgeHandlers != null)
+	{		
+		for (var i = 0; i < this.edgeHandlers.length; i++)
+		{
+			this.edgeHandlers[i].redraw();
+		}
+	}
+
+	if (this.customHandles != null)
+	{
+		for (var i = 0; i < this.customHandles.length; i++)
+		{
+			var temp = this.customHandles[i].shape.node.style.display;
+			this.customHandles[i].redraw();
+			this.customHandles[i].shape.node.style.display = temp;
+		}
+	}
+
+	this.updateParentHighlight();
+};
+
+/**
+ * Function: updateParentHighlight
+ * 
+ * Updates the highlight of the parent if <parentHighlightEnabled> is true.
+ */
+mxVertexHandler.prototype.updateParentHighlight = function()
+{
+	// If not destroyed
+	if (this.selectionBorder != null)
+	{
+		if (this.parentHighlight != null)
+		{
+			var parent = this.graph.model.getParent(this.state.cell);
+	
+			if (this.graph.model.isVertex(parent))
+			{
+				var pstate = this.graph.view.getState(parent);
+				var b = this.parentHighlight.bounds;
+				
+				if (pstate != null && (b.x != pstate.x || b.y != pstate.y ||
+					b.width != pstate.width || b.height != pstate.height))
+				{
+					this.parentHighlight.bounds = pstate;
+					this.parentHighlight.redraw();
+				}
+			}
+			else
+			{
+				this.parentHighlight.destroy();
+				this.parentHighlight = null;
+			}
+		}
+		else if (this.parentHighlightEnabled)
+		{
+			var parent = this.graph.model.getParent(this.state.cell);
+			
+			if (this.graph.model.isVertex(parent))
+			{
+				var pstate = this.graph.view.getState(parent);
+				
+				if (pstate != null)
+				{
+					this.parentHighlight = this.createParentHighlightShape(pstate);
+					// VML dialect required here for event transparency in IE
+					this.parentHighlight.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+					this.parentHighlight.pointerEvents = false;
+					this.parentHighlight.rotation = Number(pstate.style[mxConstants.STYLE_ROTATION] || '0');
+					this.parentHighlight.init(this.graph.getView().getOverlayPane());
+				}
+			}
+		}
+	}
+};
+
+/**
+ * Function: drawPreview
+ * 
+ * Redraws the preview.
+ */
+mxVertexHandler.prototype.drawPreview = function()
+{
+	if (this.preview != null)
+	{
+		this.preview.bounds = this.bounds;
+		
+		if (this.preview.node.parentNode == this.graph.container)
+		{
+			this.preview.bounds.width = Math.max(0, this.preview.bounds.width - 1);
+			this.preview.bounds.height = Math.max(0, this.preview.bounds.height - 1);
+		}
+	
+		this.preview.rotation = Number(this.state.style[mxConstants.STYLE_ROTATION] || '0');
+		this.preview.redraw();
+	}
+	
+	this.selectionBorder.bounds = this.bounds;
+	this.selectionBorder.redraw();
+	
+	if (this.parentHighlight != null)
+	{
+		this.parentHighlight.redraw();
+	}
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the handler and all its resources and DOM nodes.
+ */
+mxVertexHandler.prototype.destroy = function()
+{
+	if (this.escapeHandler != null)
+	{
+		this.state.view.graph.removeListener(this.escapeHandler);
+		this.escapeHandler = null;
+	}
+	
+	if (this.preview != null)
+	{
+		this.preview.destroy();
+		this.preview = null;
+	}
+	
+	if (this.parentHighlight != null)
+	{
+		this.parentHighlight.destroy();
+		this.parentHighlight = null;
+	}
+	
+	if (this.selectionBorder != null)
+	{
+		this.selectionBorder.destroy();
+		this.selectionBorder = null;
+	}
+	
+	this.labelShape = null;
+	this.removeHint();
+
+	if (this.sizers != null)
+	{
+		for (var i = 0; i < this.sizers.length; i++)
+		{
+			this.sizers[i].destroy();
+		}
+		
+		this.sizers = null;
+	}
+
+	if (this.customHandles != null)
+	{
+		for (var i = 0; i < this.customHandles.length; i++)
+		{
+			this.customHandles[i].destroy();
+		}
+		
+		this.customHandles = null;
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/index.txt b/airavata-kubernetes/workflow-composer/src/js/index.txt
new file mode 100644
index 0000000..f3631d6
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/index.txt
@@ -0,0 +1,316 @@
+Document: API Specification
+
+Overview:
+
+  This JavaScript library is divided into 8 packages. The top-level <mxClient>
+  class includes (or dynamically imports) everything else. The current version
+  is stored in <mxClient.VERSION>.
+
+  The *editor* package provides the classes required to implement a diagram
+  editor. The main class in this package is <mxEditor>.
+  
+  The *view* and *model* packages implement the graph component, represented
+  by <mxGraph>. It refers to a <mxGraphModel> which contains <mxCell>s and
+  caches the state of the cells in a <mxGraphView>. The cells are painted
+  using a <mxCellRenderer> based on the appearance defined in <mxStylesheet>.
+  Undo history is implemented in <mxUndoManager>. To display an icon on the
+  graph, <mxCellOverlay> may be used. Validation rules are defined with
+  <mxMultiplicity>.
+  
+  The *handler*, *layout* and *shape* packages contain event listeners,
+  layout algorithms and shapes, respectively. The graph event listeners
+  include <mxRubberband> for rubberband selection, <mxTooltipHandler>
+  for tooltips and <mxGraphHandler> for  basic cell modifications.
+  <mxCompactTreeLayout> implements a tree layout algorithm, and the 
+  shape package provides various shapes, which are subclasses of
+  <mxShape>.
+  
+  The *util* package provides utility classes including <mxClipboard> for
+  copy-paste, <mxDatatransfer> for drag-and-drop, <mxConstants> for keys and
+  values of stylesheets, <mxEvent> and <mxUtils> for cross-browser
+  event-handling and general purpose functions, <mxResources> for
+  internationalization and <mxLog> for console output.
+
+  The *io* package implements a generic <mxObjectCodec> for turning
+  JavaScript objects into XML. The main class is <mxCodec>.
+  <mxCodecRegistry> is the global registry for custom codecs.
+  
+Events:
+
+  There are three different types of events, namely native DOM events,
+  <mxEventObjects> which are fired in an <mxEventSource>, and <mxMouseEvents>
+  which are fired in <mxGraph>.
+
+  Some helper methods for handling native events are provided in <mxEvent>. It
+  also takes care of resolving cycles between DOM nodes and JavaScript event
+  handlers, which can lead to memory leaks in IE6.
+  
+  Most custom events in mxGraph are implemented using <mxEventSource>. Its
+  listeners are functions that take a sender and <mxEventObject>. Additionally,
+  the <mxGraph> class fires special <mxMouseEvents> which are handled using
+  mouse listeners, which are objects that provide a mousedown, mousemove and
+  mouseup method.
+  
+  Events in <mxEventSource> are fired using <mxEventSource.fireEvent>.
+  Listeners are added and removed using <mxEventSource.addListener> and
+  <mxEventSource.removeListener>. <mxMouseEvents> in <mxGraph> are fired using
+  <mxGraph.fireMouseEvent>. Listeners are added and removed using
+  <mxGraph.addMouseListener> and <mxGraph.removeMouseListener>, respectively.
+  
+Key bindings:
+  
+  The following key bindings are defined for mouse events in the client across
+  all browsers and platforms:
+  
+  - Control-Drag: Duplicates (clones) selected cells
+  - Shift-Rightlick: Shows the context menu
+  - Alt-Click: Forces rubberband (aka. marquee)
+  - Control-Select: Toggles the selection state
+  - Shift-Drag: Constrains the offset to one direction
+  - Shift-Control-Drag: Panning (also Shift-Rightdrag)
+  
+Configuration:
+
+  The following global variables may be defined before the client is loaded to
+  specify its language or base path, respectively.
+  
+  - mxBasePath: Specifies the path in <mxClient.basePath>.
+  - mxImageBasePath: Specifies the path in <mxClient.imageBasePath>.
+  - mxLanguage: Specifies the language for resources in <mxClient.language>.
+  - mxDefaultLanguage: Specifies the default language in <mxClient.defaultLanguage>.
+  - mxLoadResources: Specifies if any resources should be loaded. Default is true.
+  - mxLoadStylesheets: Specifies if any stylesheets should be loaded. Default is true.
+
+Reserved Words:
+
+  The mx prefix is used for all classes and objects in mxGraph. The mx prefix
+  can be seen as the global namespace for all JavaScript code in mxGraph. The
+  following fieldnames should not be used in objects.
+  
+  - *mxObjectId*: If the object is used with mxObjectIdentity
+  - *as*: If the object is a field of another object
+  - *id*: If the object is an idref in a codec
+  - *mxListenerList*: Added to DOM nodes when used with <mxEvent>
+  - *window._mxDynamicCode*: Temporarily used to load code in Safari and Chrome
+  (see <mxClient.include>).
+  - *_mxJavaScriptExpression*: Global variable that is temporarily used to
+  evaluate code in Safari, Opera, Firefox 3 and IE (see <mxUtils.eval>).
+
+Files:
+
+  The library contains these relative filenames. All filenames are relative
+  to <mxClient.basePath>.
+  
+Built-in Images:
+  
+  All images are loaded from the <mxClient.imageBasePath>, 
+  which you can change to reflect your environment. The image variables can 
+  also be changed individually.
+  
+  - mxGraph.prototype.collapsedImage
+  - mxGraph.prototype.expandedImage
+  - mxGraph.prototype.warningImage
+  - mxWindow.prototype.closeImage
+  - mxWindow.prototype.minimizeImage
+  - mxWindow.prototype.normalizeImage
+  - mxWindow.prototype.maximizeImage
+  - mxWindow.prototype.resizeImage
+  - mxPopupMenu.prototype.submenuImage
+  - mxUtils.errorImage
+  - mxConstraintHandler.prototype.pointImage
+
+  The basename of the warning image (images/warning without extension) used in 
+  <mxGraph.setCellWarning> is defined in <mxGraph.warningImage>.
+
+Resources:
+  
+  The <mxEditor> and <mxGraph> classes add the following resources to
+  <mxResources> at class loading time:
+
+  - resources/editor*.properties
+  - resources/graph*.properties
+  
+  By default, the library ships with English and German resource files.
+
+Images:
+
+  Recommendations for using images. Use GIF images (256 color palette) in HTML
+  elements (such as the toolbar and context menu), and PNG images (24 bit) for
+  all images which appear inside the graph component.
+  
+  - For PNG images inside HTML elements, Internet Explorer will ignore any 
+    transparency information.
+  - For GIF images inside the graph, Firefox on the Mac will display strange 
+    colors. Furthermore, only the first image for animated GIFs is displayed 
+    on the Mac.
+    
+  For faster image rendering during application runtime, images can be
+  prefetched using the following code:
+  
+  (code)
+  var image = new Image();
+  image.src = url_to_image;
+  (end)
+
+Deployment:
+
+  The client is added to the page using the following script tag inside the
+  head of a document:
+
+  (code)
+  <script type="text/javascript" src="js/mxClient.js"></script>
+  (end)
+
+  The deployment version of the mxClient.js file contains all required code
+  in a single file. For deployment, the complete javascript/src directory is
+  required.
+  
+Source Code:
+
+  If you are a source code customer and you wish to develop using the 
+  full source code, the commented source code is shipped in the 
+  javascript/devel/source.zip file. It contains one file for each class 
+  in mxGraph. To use the source code the source.zip file must be 
+  uncompressed and the mxClient.js URL in the HTML  page must be changed 
+  to reference the uncompressed mxClient.js from the source.zip file.
+
+Compression:
+ 
+  When using Apache2 with mod_deflate, you can use the following directive
+  in src/js/.htaccess to speedup the loading of the JavaScript sources:
+  
+  (code)
+  SetOutputFilter DEFLATE
+  (end)
+
+Classes:
+  
+  There are two types of "classes" in mxGraph: classes and singletons (where
+  only one instance exists). Singletons are mapped to global objects where the
+  variable name equals the classname. For example mxConstants is an object with
+  all the constants defined as object fields. Normal classes are mapped to a
+  constructor function and a prototype which defines the instance fields and
+  methods. For example, <mxEditor> is a function and mxEditor.prototype is the
+  prototype for the object that the mxEditor function creates. The mx prefix is
+  a convention that is used for all classes in the mxGraph package to avoid
+  conflicts with other objects in the global namespace.
+
+Subclassing:
+
+  For subclassing, the superclass must provide a constructor that is either
+  parameterless or handles an invocation with no arguments. Furthermore, the
+  special constructor field must be redefined after extending the prototype.
+  For example, the superclass of mxEditor is <mxEventSource>. This is
+  represented in JavaScript by first "inheriting" all fields and methods from
+  the superclass by assigning the prototype to an instance of the superclass,
+  eg. mxEditor.prototype = new mxEventSource() and redefining the constructor
+  field using mxEditor.prototype.constructor = mxEditor. The latter rule is
+  applied so that the type of an object can be retrieved via the name of it�s
+  constructor using mxUtils.getFunctionName(obj.constructor).
+
+Constructor:
+
+  For subclassing in mxGraph, the same scheme should be applied. For example,
+  for subclassing the <mxGraph> class, first a constructor must be defined for
+  the new class. The constructor calls the super constructor with any arguments
+  that it may have using the call function on the mxGraph function object,
+  passing along explitely each argument:
+
+  (code)
+  function MyGraph(container)
+  {
+    mxGraph.call(this, container);
+  }
+  (end)
+  
+  The prototype of MyGraph inherits from mxGraph as follows. As usual, the
+  constructor is redefined after extending the superclass:
+
+  (code)
+  MyGraph.prototype = new mxGraph();
+  MyGraph.prototype.constructor = MyGraph;
+  (end)
+  
+  You may want to define the codec associated for the class after the above
+  code. This code will be executed at class loading time and makes sure the
+  same codec is used to encode instances of mxGraph and MyGraph.
+
+  (code)
+  var codec = mxCodecRegistry.getCodec(mxGraph);
+  codec.template = new MyGraph();
+  mxCodecRegistry.register(codec);
+  (end)
+  
+Functions:
+
+  In the prototype for MyGraph, functions of mxGraph can then be extended as
+  follows.
+  
+  (code)
+  MyGraph.prototype.isCellSelectable = function(cell)
+  {
+    var selectable = mxGraph.prototype.isSelectable.apply(this, arguments);
+
+    var geo = this.model.getGeometry(cell);
+    return selectable && (geo == null || !geo.relative);
+  }
+  (end)
+  
+  The supercall in the first line is optional. It is done using the apply
+  function on the isSelectable function object of the mxGraph prototype, using
+  the special this and arguments variables as parameters. Calls to the
+  superclass function are only possible if the function is not replaced in the
+  superclass as follows, which is another way of �subclassing� in JavaScript.
+
+  (code)
+  mxGraph.prototype.isCellSelectable = function(cell)
+  {
+    var geo = this.model.getGeometry(cell);
+    return selectable &&
+        (geo == null ||
+        !geo.relative);
+  }
+  (end)
+
+  The above scheme is useful if a function definition needs to be replaced
+  completely.
+  
+  In order to add new functions and fields to the subclass, the following code
+  is used. The example below adds a new function to return the XML
+  representation of the graph model:
+
+  (code)
+  MyGraph.prototype.getXml = function()
+  {
+    var enc = new mxCodec();
+    return enc.encode(this.getModel());
+  }
+  (end)
+  
+Variables:
+
+  Likewise, a new field is declared and defined as follows.
+
+  (code)
+  MyGraph.prototype.myField = 'Hello, World!';
+  (end)
+  
+  Note that the value assigned to myField is created only once, that is, all
+  instances of MyGraph share the same value. If you require instance-specific
+  values, then the field must be defined in the constructor instead.
+
+  (code)
+  function MyGraph(container)
+  {
+    mxGraph.call(this, container);
+    
+    this.myField = new Array();
+  }
+  (end)
+
+  Finally, a new instance of MyGraph is created using the following code, where
+  container is a DOM node that acts as a container for the graph view:
+
+  (code)
+  var graph = new MyGraph(container);
+  (end)
diff --git a/airavata-kubernetes/workflow-composer/src/js/io/mxCellCodec.js b/airavata-kubernetes/workflow-composer/src/js/io/mxCellCodec.js
new file mode 100644
index 0000000..253c96f
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/io/mxCellCodec.js
@@ -0,0 +1,189 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+mxCodecRegistry.register(function()
+{
+	/**
+	 * Class: mxCellCodec
+	 *
+	 * Codec for <mxCell>s. This class is created and registered
+	 * dynamically at load time and used implicitely via <mxCodec>
+	 * and the <mxCodecRegistry>.
+	 *
+	 * Transient Fields:
+	 *
+	 * - children
+	 * - edges
+	 * - overlays
+	 * - mxTransient
+	 *
+	 * Reference Fields:
+	 *
+	 * - parent
+	 * - source
+	 * - target
+	 * 
+	 * Transient fields can be added using the following code:
+	 * 
+	 * mxCodecRegistry.getCodec(mxCell).exclude.push('name_of_field');
+	 * 
+	 * To subclass <mxCell>, replace the template and add an alias as
+	 * follows.
+	 * 
+	 * (code)
+	 * function CustomCell(value, geometry, style)
+	 * {
+	 *   mxCell.apply(this, arguments);
+	 * }
+	 * 
+	 * mxUtils.extend(CustomCell, mxCell);
+	 * 
+	 * mxCodecRegistry.getCodec(mxCell).template = new CustomCell();
+	 * mxCodecRegistry.addAlias('CustomCell', 'mxCell');
+	 * (end)
+	 */
+	var codec = new mxObjectCodec(new mxCell(),
+		['children', 'edges', 'overlays', 'mxTransient'],
+		['parent', 'source', 'target']);
+
+	/**
+	 * Function: isCellCodec
+	 *
+	 * Returns true since this is a cell codec.
+	 */
+	codec.isCellCodec = function()
+	{
+		return true;
+	};
+
+	/**
+	 * Overidden to disable conversion of value to number.
+	 */
+	codec.isNumericAttribute = function(dec, attr, obj)
+	{
+		return attr.nodeName !== 'value' && mxObjectCodec.prototype.isNumericAttribute.apply(this, arguments);
+	};
+	
+	/**
+	 * Function: isExcluded
+	 *
+	 * Excludes user objects that are XML nodes.
+	 */ 
+	codec.isExcluded = function(obj, attr, value, isWrite)
+	{
+		return mxObjectCodec.prototype.isExcluded.apply(this, arguments) ||
+			(isWrite && attr == 'value' &&
+			value.nodeType == mxConstants.NODETYPE_ELEMENT);
+	};
+	
+	/**
+	 * Function: afterEncode
+	 *
+	 * Encodes an <mxCell> and wraps the XML up inside the
+	 * XML of the user object (inversion).
+	 */
+	codec.afterEncode = function(enc, obj, node)
+	{
+		if (obj.value != null && obj.value.nodeType == mxConstants.NODETYPE_ELEMENT)
+		{
+			// Wraps the graphical annotation up in the user object (inversion)
+			// by putting the result of the default encoding into a clone of the
+			// user object (node type 1) and returning this cloned user object.
+			var tmp = node;
+			node = mxUtils.importNode(enc.document, obj.value, true);
+			node.appendChild(tmp);
+			
+			// Moves the id attribute to the outermost XML node, namely the
+			// node which denotes the object boundaries in the file.
+			var id = tmp.getAttribute('id');
+			node.setAttribute('id', id);
+			tmp.removeAttribute('id');
+		}
+
+		return node;
+	};
+
+	/**
+	 * Function: beforeDecode
+	 *
+	 * Decodes an <mxCell> and uses the enclosing XML node as
+	 * the user object for the cell (inversion).
+	 */
+	codec.beforeDecode = function(dec, node, obj)
+	{
+		var inner = node.cloneNode(true);
+		var classname = this.getName();
+		
+		if (node.nodeName != classname)
+		{
+			// Passes the inner graphical annotation node to the
+			// object codec for further processing of the cell.
+			var tmp = node.getElementsByTagName(classname)[0];
+			
+			if (tmp != null && tmp.parentNode == node)
+			{
+				mxUtils.removeWhitespace(tmp, true);
+				mxUtils.removeWhitespace(tmp, false);
+				tmp.parentNode.removeChild(tmp);
+				inner = tmp;
+			}
+			else
+			{
+				inner = null;
+			}
+			
+			// Creates the user object out of the XML node
+			obj.value = node.cloneNode(true);
+			var id = obj.value.getAttribute('id');
+			
+			if (id != null)
+			{
+				obj.setId(id);
+				obj.value.removeAttribute('id');
+			}
+		}
+		else
+		{
+			// Uses ID from XML file as ID for cell in model
+			obj.setId(node.getAttribute('id'));
+		}
+			
+		// Preprocesses and removes all Id-references in order to use the
+		// correct encoder (this) for the known references to cells (all).
+		if (inner != null)
+		{
+			for (var i = 0; i < this.idrefs.length; i++)
+			{
+				var attr = this.idrefs[i];
+				var ref = inner.getAttribute(attr);
+				
+				if (ref != null)
+				{
+					inner.removeAttribute(attr);
+					var object = dec.objects[ref] || dec.lookup(ref);
+					
+					if (object == null)
+					{
+						// Needs to decode forward reference
+						var element = dec.getElementById(ref);
+						
+						if (element != null)
+						{
+							var decoder = mxCodecRegistry.codecs[element.nodeName] || this;
+							object = decoder.decode(dec, element);
+						}
+					}
+					
+					obj[attr] = object;
+				}
+			}
+		}
+		
+		return inner;
+	};
+
+	// Returns the codec into the registry
+	return codec;
+
+}());
diff --git a/airavata-kubernetes/workflow-composer/src/js/io/mxChildChangeCodec.js b/airavata-kubernetes/workflow-composer/src/js/io/mxChildChangeCodec.js
new file mode 100644
index 0000000..92d0c76
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/io/mxChildChangeCodec.js
@@ -0,0 +1,149 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+mxCodecRegistry.register(function()
+{
+	/**
+	 * Class: mxChildChangeCodec
+	 *
+	 * Codec for <mxChildChange>s. This class is created and registered
+	 * dynamically at load time and used implicitely via <mxCodec> and
+	 * the <mxCodecRegistry>.
+	 *
+	 * Transient Fields:
+	 *
+	 * - model
+	 * - previous
+	 * - previousIndex
+	 * - child
+	 *
+	 * Reference Fields:
+	 *
+	 * - parent
+	 */
+	var codec = new mxObjectCodec(new mxChildChange(),
+		['model', 'child', 'previousIndex'],
+		['parent', 'previous']);
+
+	/**
+	 * Function: isReference
+	 *
+	 * Returns true for the child attribute if the child
+	 * cell had a previous parent or if we're reading the
+	 * child as an attribute rather than a child node, in
+	 * which case it's always a reference.
+	 */
+	codec.isReference = function(obj, attr, value, isWrite)
+	{
+		if (attr == 'child' &&
+			(obj.previous != null ||
+			!isWrite))
+		{
+			return true;
+		}
+		
+		return mxUtils.indexOf(this.idrefs, attr) >= 0;
+	};
+
+	/**
+	 * Function: afterEncode
+	 *
+	 * Encodes the child recusively and adds the result
+	 * to the given node.
+	 */
+	codec.afterEncode = function(enc, obj, node)
+	{
+		if (this.isReference(obj, 'child',  obj.child, true))
+		{
+			// Encodes as reference (id)
+			node.setAttribute('child', enc.getId(obj.child));
+		}
+		else
+		{
+			// At this point, the encoder is no longer able to know which cells
+			// are new, so we have to encode the complete cell hierarchy and
+			// ignore the ones that are already there at decoding time. Note:
+			// This can only be resolved by moving the notify event into the
+			// execute of the edit.
+			enc.encodeCell(obj.child, node);
+		}
+		
+		return node;
+	};
+
+	/**
+	 * Function: beforeDecode
+	 *
+	 * Decodes the any child nodes as using the respective
+	 * codec from the registry.
+	 */
+	codec.beforeDecode = function(dec, node, obj)
+	{
+		if (node.firstChild != null &&
+			node.firstChild.nodeType == mxConstants.NODETYPE_ELEMENT)
+		{
+			// Makes sure the original node isn't modified
+			node = node.cloneNode(true);
+			
+			var tmp = node.firstChild;
+			obj.child = dec.decodeCell(tmp, false);
+
+			var tmp2 = tmp.nextSibling;
+			tmp.parentNode.removeChild(tmp);
+			tmp = tmp2;
+			
+			while (tmp != null)
+			{
+				tmp2 = tmp.nextSibling;
+				
+				if (tmp.nodeType == mxConstants.NODETYPE_ELEMENT)
+				{
+					// Ignores all existing cells because those do not need to
+					// be re-inserted into the model. Since the encoded version
+					// of these cells contains the new parent, this would leave
+					// to an inconsistent state on the model (ie. a parent
+					// change without a call to parentForCellChanged).
+					var id = tmp.getAttribute('id');
+					
+					if (dec.lookup(id) == null)
+					{
+						dec.decodeCell(tmp);
+					}
+				}
+				
+				tmp.parentNode.removeChild(tmp);
+				tmp = tmp2;
+			}
+		}
+		else
+		{
+			var childRef = node.getAttribute('child');
+			obj.child = dec.getObject(childRef);
+		}
+		
+		return node;
+	};
+	
+	/**
+	 * Function: afterDecode
+	 *
+	 * Restores object state in the child change.
+	 */
+	codec.afterDecode = function(dec, node, obj)
+	{
+		// Cells are encoded here after a complete transaction so the previous
+		// parent must be restored on the cell for the case where the cell was
+		// added. This is needed for the local model to identify the cell as a
+		// new cell and register the ID.
+		obj.child.parent = obj.previous;
+		obj.previous = obj.parent;
+		obj.previousIndex = obj.index;
+
+		return obj;
+	};
+
+	// Returns the codec into the registry
+	return codec;
+
+}());
diff --git a/airavata-kubernetes/workflow-composer/src/js/io/mxCodec.js b/airavata-kubernetes/workflow-composer/src/js/io/mxCodec.js
new file mode 100644
index 0000000..b308387
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/io/mxCodec.js
@@ -0,0 +1,596 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxCodec
+ *
+ * XML codec for JavaScript object graphs. See <mxObjectCodec> for a
+ * description of the general encoding/decoding scheme. This class uses the
+ * codecs registered in <mxCodecRegistry> for encoding/decoding each object.
+ * 
+ * References:
+ * 
+ * In order to resolve references, especially forward references, the mxCodec
+ * constructor must be given the document that contains the referenced
+ * elements.
+ *
+ * Examples:
+ *
+ * The following code is used to encode a graph model.
+ *
+ * (code)
+ * var encoder = new mxCodec();
+ * var result = encoder.encode(graph.getModel());
+ * var xml = mxUtils.getXml(result);
+ * (end)
+ * 
+ * Example:
+ * 
+ * Using the code below, an XML document is decoded into an existing model. The
+ * document may be obtained using one of the functions in mxUtils for loading
+ * an XML file, eg. <mxUtils.get>, or using <mxUtils.parseXml> for parsing an
+ * XML string.
+ * 
+ * (code)
+ * var doc = mxUtils.parseXml(xmlString);
+ * var codec = new mxCodec(doc);
+ * codec.decode(doc.documentElement, graph.getModel());
+ * (end)
+ * 
+ * Example:
+ * 
+ * This example demonstrates parsing a list of isolated cells into an existing
+ * graph model. Note that the cells do not have a parent reference so they can
+ * be added anywhere in the cell hierarchy after parsing.
+ * 
+ * (code)
+ * var xml = '<root><mxCell id="2" value="Hello," vertex="1"><mxGeometry x="20" y="20" width="80" height="30" as="geometry"/></mxCell><mxCell id="3" value="World!" vertex="1"><mxGeometry x="200" y="150" width="80" height="30" as="geometry"/></mxCell><mxCell id="4" value="" edge="1" source="2" target="3"><mxGeometry relative="1" as="geometry"/></mxCell></root>';
+ * var doc = mxUtils.parseXml(xml);
+ * var codec = new mxCodec(doc);
+ * var elt = doc.documentElement.firstChild;
+ * var cells = [];
+ * 
+ * while (elt != null)
+ * {
+ *   cells.push(codec.decode(elt));
+ *   elt = elt.nextSibling;
+ * }
+ * 
+ * graph.addCells(cells);
+ * (end)
+ * 
+ * Example:
+ * 
+ * Using the following code, the selection cells of a graph are encoded and the
+ * output is displayed in a dialog box.
+ * 
+ * (code)
+ * var enc = new mxCodec();
+ * var cells = graph.getSelectionCells();
+ * mxUtils.alert(mxUtils.getPrettyXml(enc.encode(cells)));
+ * (end)
+ * 
+ * Newlines in the XML can be converted to <br>, in which case a '<br>' argument
+ * must be passed to <mxUtils.getXml> as the second argument.
+ * 
+ * Debugging:
+ * 
+ * For debugging I/O you can use the following code to get the sequence of
+ * encoded objects:
+ * 
+ * (code)
+ * var oldEncode = mxCodec.prototype.encode;
+ * mxCodec.prototype.encode = function(obj)
+ * {
+ *   mxLog.show();
+ *   mxLog.debug('mxCodec.encode: obj='+mxUtils.getFunctionName(obj.constructor));
+ *   
+ *   return oldEncode.apply(this, arguments);
+ * };
+ * (end)
+ * 
+ * Note that the I/O system adds object codecs for new object automatically. For
+ * decoding those objects, the constructor should be written as follows:
+ * 
+ * (code)
+ * var MyObj = function(name)
+ * {
+ *   // ...
+ * };
+ * (end)
+ * 
+ * Constructor: mxCodec
+ *
+ * Constructs an XML encoder/decoder for the specified
+ * owner document.
+ *
+ * Parameters:
+ *
+ * document - Optional XML document that contains the data.
+ * If no document is specified then a new document is created
+ * using <mxUtils.createXmlDocument>.
+ */
+function mxCodec(document)
+{
+	this.document = document || mxUtils.createXmlDocument();
+	this.objects = [];
+};
+
+/**
+ * Variable: document
+ *
+ * The owner document of the codec.
+ */
+mxCodec.prototype.document = null;
+
+/**
+ * Variable: objects
+ *
+ * Maps from IDs to objects.
+ */
+mxCodec.prototype.objects = null;
+
+/**
+ * Variable: elements
+ * 
+ * Lookup table for resolving IDs to elements.
+ */
+mxCodec.prototype.elements = null;
+
+/**
+ * Variable: encodeDefaults
+ *
+ * Specifies if default values should be encoded. Default is false.
+ */
+mxCodec.prototype.encodeDefaults = false;
+
+
+/**
+ * Function: putObject
+ * 
+ * Assoiates the given object with the given ID and returns the given object.
+ * 
+ * Parameters
+ * 
+ * id - ID for the object to be associated with.
+ * obj - Object to be associated with the ID.
+ */
+mxCodec.prototype.putObject = function(id, obj)
+{
+	this.objects[id] = obj;
+	
+	return obj;
+};
+
+/**
+ * Function: getObject
+ *
+ * Returns the decoded object for the element with the specified ID in
+ * <document>. If the object is not known then <lookup> is used to find an
+ * object. If no object is found, then the element with the respective ID
+ * from the document is parsed using <decode>.
+ */
+mxCodec.prototype.getObject = function(id)
+{
+	var obj = null;
+
+	if (id != null)
+	{
+		obj = this.objects[id];
+		
+		if (obj == null)
+		{
+			obj = this.lookup(id);
+			
+			if (obj == null)
+			{
+				var node = this.getElementById(id);
+				
+				if (node != null)
+				{
+					obj = this.decode(node);
+				}
+			}
+		}
+	}
+	
+	return obj;
+};
+
+/**
+ * Function: lookup
+ *
+ * Hook for subclassers to implement a custom lookup mechanism for cell IDs.
+ * This implementation always returns null.
+ *
+ * Example:
+ *
+ * (code)
+ * var codec = new mxCodec();
+ * codec.lookup = function(id)
+ * {
+ *   return model.getCell(id);
+ * };
+ * (end)
+ *
+ * Parameters:
+ *
+ * id - ID of the object to be returned.
+ */
+mxCodec.prototype.lookup = function(id)
+{
+	return null;
+};
+
+/**
+ * Function: getElementById
+ *
+ * Returns the element with the given ID from <document>.
+ *
+ * Parameters:
+ *
+ * id - String that contains the ID.
+ */
+mxCodec.prototype.getElementById = function(id)
+{
+	if (this.elements == null)
+	{
+		// Throws custom error for cases where a reference should be resolved
+		// in an empty document. This happens if an XML node is decoded without
+		// passing the owner document to the codec constructor.
+		if (this.document.documentElement == null)
+		{
+			throw new Error('mxCodec constructor needs document parameter');
+		}
+		
+		this.elements = new Object();
+		this.addElement(this.document.documentElement);
+	}
+	
+	return this.elements[id];
+};
+
+/**
+ * Function: addElement
+ *
+ * Adds the given element to <elements> if it has an ID.
+ */
+mxCodec.prototype.addElement = function(node)
+{
+	if (node.nodeType == mxConstants.NODETYPE_ELEMENT)
+	{
+		var id = node.getAttribute('id');
+		
+		if (id != null && this.elements[id] == null)
+		{
+			this.elements[id] = node;
+		}
+	}
+	
+	node = node.firstChild;
+	
+	while (node != null)
+	{
+		this.addElement(node);
+		node = node.nextSibling;
+	}
+};
+
+/**
+ * Function: getId
+ *
+ * Returns the ID of the specified object. This implementation
+ * calls <reference> first and if that returns null handles
+ * the object as an <mxCell> by returning their IDs using
+ * <mxCell.getId>. If no ID exists for the given cell, then
+ * an on-the-fly ID is generated using <mxCellPath.create>.
+ *
+ * Parameters:
+ *
+ * obj - Object to return the ID for.
+ */
+mxCodec.prototype.getId = function(obj)
+{
+	var id = null;
+	
+	if (obj != null)
+	{
+		id = this.reference(obj);
+		
+		if (id == null && obj instanceof mxCell)
+		{
+			id = obj.getId();
+			
+			if (id == null)
+			{
+				// Uses an on-the-fly Id
+				id = mxCellPath.create(obj);
+				
+				if (id.length == 0)
+				{
+					id = 'root';
+				}
+			}
+		}
+	}
+	
+	return id;
+};
+
+/**
+ * Function: reference
+ *
+ * Hook for subclassers to implement a custom method
+ * for retrieving IDs from objects. This implementation
+ * always returns null.
+ *
+ * Example:
+ *
+ * (code)
+ * var codec = new mxCodec();
+ * codec.reference = function(obj)
+ * {
+ *   return obj.getCustomId();
+ * };
+ * (end)
+ *
+ * Parameters:
+ *
+ * obj - Object whose ID should be returned.
+ */
+mxCodec.prototype.reference = function(obj)
+{
+	return null;
+};
+
+/**
+ * Function: encode
+ *
+ * Encodes the specified object and returns the resulting
+ * XML node.
+ *
+ * Parameters:
+ *
+ * obj - Object to be encoded. 
+ */
+mxCodec.prototype.encode = function(obj)
+{
+	var node = null;
+	
+	if (obj != null && obj.constructor != null)
+	{
+		var enc = mxCodecRegistry.getCodec(obj.constructor);
+		
+		if (enc != null)
+		{
+			node = enc.encode(this, obj);
+		}
+		else
+		{
+			if (mxUtils.isNode(obj))
+			{
+				node = mxUtils.importNode(this.document, obj, true);
+			}
+			else
+			{
+	    		mxLog.warn('mxCodec.encode: No codec for ' + mxUtils.getFunctionName(obj.constructor));
+			}
+		}
+	}
+	
+	return node;
+};
+
+/**
+ * Function: decode
+ *
+ * Decodes the given XML node. The optional "into"
+ * argument specifies an existing object to be
+ * used. If no object is given, then a new instance
+ * is created using the constructor from the codec.
+ *
+ * The function returns the passed in object or
+ * the new instance if no object was given.
+ *
+ * Parameters:
+ *
+ * node - XML node to be decoded.
+ * into - Optional object to be decodec into.
+ */
+mxCodec.prototype.decode = function(node, into)
+{
+	var obj = null;
+	
+	if (node != null && node.nodeType == mxConstants.NODETYPE_ELEMENT)
+	{
+		var ctor = null;
+		
+		try
+		{
+			ctor = window[node.nodeName];
+		}
+		catch (err)
+		{
+			// ignore
+		}
+		
+		var dec = mxCodecRegistry.getCodec(ctor);
+		
+		if (dec != null)
+		{
+			obj = dec.decode(this, node, into);
+		}
+		else
+		{
+			obj = node.cloneNode(true);
+			obj.removeAttribute('as');
+		}
+	}
+	
+	return obj;
+};
+
+/**
+ * Function: encodeCell
+ *
+ * Encoding of cell hierarchies is built-into the core, but
+ * is a higher-level function that needs to be explicitely
+ * used by the respective object encoders (eg. <mxModelCodec>,
+ * <mxChildChangeCodec> and <mxRootChangeCodec>). This
+ * implementation writes the given cell and its children as a
+ * (flat) sequence into the given node. The children are not
+ * encoded if the optional includeChildren is false. The
+ * function is in charge of adding the result into the
+ * given node and has no return value.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to be encoded.
+ * node - Parent XML node to add the encoded cell into.
+ * includeChildren - Optional boolean indicating if the
+ * function should include all descendents. Default is true. 
+ */
+mxCodec.prototype.encodeCell = function(cell, node, includeChildren)
+{
+	node.appendChild(this.encode(cell));
+	
+	if (includeChildren == null || includeChildren)
+	{
+		var childCount = cell.getChildCount();
+		
+		for (var i = 0; i < childCount; i++)
+		{
+			this.encodeCell(cell.getChildAt(i), node);
+		}
+	}
+};
+
+/**
+ * Function: isCellCodec
+ * 
+ * Returns true if the given codec is a cell codec. This uses
+ * <mxCellCodec.isCellCodec> to check if the codec is of the
+ * given type.
+ */
+mxCodec.prototype.isCellCodec = function(codec)
+{
+	if (codec != null && typeof(codec.isCellCodec) == 'function')
+	{
+		return codec.isCellCodec();
+	}
+	
+	return false;
+};
+
+/**
+ * Function: decodeCell
+ *
+ * Decodes cells that have been encoded using inversion, ie.
+ * where the user object is the enclosing node in the XML,
+ * and restores the group and graph structure in the cells.
+ * Returns a new <mxCell> instance that represents the
+ * given node.
+ *
+ * Parameters:
+ *
+ * node - XML node that contains the cell data.
+ * restoreStructures - Optional boolean indicating whether
+ * the graph structure should be restored by calling insert
+ * and insertEdge on the parent and terminals, respectively.
+ * Default is true.
+ */
+mxCodec.prototype.decodeCell = function(node, restoreStructures)
+{
+	restoreStructures = (restoreStructures != null) ? restoreStructures : true;
+	var cell = null;
+	
+	if (node != null && node.nodeType == mxConstants.NODETYPE_ELEMENT)
+	{
+		// Tries to find a codec for the given node name. If that does
+		// not return a codec then the node is the user object (an XML node
+		// that contains the mxCell, aka inversion).
+		var decoder = mxCodecRegistry.getCodec(node.nodeName);
+		
+		// Tries to find the codec for the cell inside the user object.
+		// This assumes all node names inside the user object are either
+		// not registered or they correspond to a class for cells.
+		if (!this.isCellCodec(decoder))
+		{
+			var child = node.firstChild;
+			
+			while (child != null && !this.isCellCodec(decoder))
+			{
+				decoder = mxCodecRegistry.getCodec(child.nodeName);
+				child = child.nextSibling;
+			}
+		}
+		
+		if (!this.isCellCodec(decoder))
+		{
+			decoder = mxCodecRegistry.getCodec(mxCell);
+		}
+
+		cell = decoder.decode(this, node);
+		
+		if (restoreStructures)
+		{
+			this.insertIntoGraph(cell);
+		}
+	}
+	
+	return cell;
+};
+
+/**
+ * Function: insertIntoGraph
+ *
+ * Inserts the given cell into its parent and terminal cells.
+ */
+mxCodec.prototype.insertIntoGraph = function(cell)
+{
+	var parent = cell.parent;
+	var source = cell.getTerminal(true);
+	var target = cell.getTerminal(false);
+
+	// Fixes possible inconsistencies during insert into graph
+	cell.setTerminal(null, false);
+	cell.setTerminal(null, true);
+	cell.parent = null;
+	
+	if (parent != null)
+	{
+		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/airavata-kubernetes/workflow-composer/src/js/io/mxCodecRegistry.js b/airavata-kubernetes/workflow-composer/src/js/io/mxCodecRegistry.js
new file mode 100644
index 0000000..42ebcd7
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/io/mxCodecRegistry.js
@@ -0,0 +1,137 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+var mxCodecRegistry =
+{
+	/**
+	 * Class: mxCodecRegistry
+	 *
+	 * Singleton class that acts as a global registry for codecs.
+	 *
+	 * Adding an <mxCodec>:
+	 *
+	 * 1. Define a default codec with a new instance of the 
+	 * object to be handled.
+	 *
+	 * (code)
+	 * var codec = new mxObjectCodec(new mxGraphModel());
+	 * (end)
+	 *
+	 * 2. Define the functions required for encoding and decoding
+	 * objects.
+	 *
+	 * (code)
+	 * codec.encode = function(enc, obj) { ... }
+	 * codec.decode = function(dec, node, into) { ... }
+	 * (end)
+	 *
+	 * 3. Register the codec in the <mxCodecRegistry>.
+	 *
+	 * (code)
+	 * mxCodecRegistry.register(codec);
+	 * (end)
+	 *
+	 * <mxObjectCodec.decode> may be used to either create a new 
+	 * instance of an object or to configure an existing instance, 
+	 * in which case the into argument points to the existing
+	 * object. In this case, we say the codec "configures" the
+	 * object.
+	 * 
+	 * Variable: codecs
+	 *
+	 * Maps from constructor names to codecs.
+	 */
+	codecs: [],
+	
+	/**
+	 * Variable: aliases
+	 *
+	 * Maps from classnames to codecnames.
+	 */
+	aliases: [],
+
+	/**
+	 * Function: register
+	 *
+	 * Registers a new codec and associates the name of the template
+	 * constructor in the codec with the codec object.
+	 *
+	 * Parameters:
+	 *
+	 * codec - <mxObjectCodec> to be registered.
+	 */
+	register: function(codec)
+	{
+		if (codec != null)
+		{
+			var name = codec.getName();
+			mxCodecRegistry.codecs[name] = codec;
+			
+			var classname = mxUtils.getFunctionName(codec.template.constructor);
+
+			if (classname != name)
+			{
+				mxCodecRegistry.addAlias(classname, name);
+			}
+		}
+
+		return codec;
+	},
+
+	/**
+	 * Function: addAlias
+	 *
+	 * Adds an alias for mapping a classname to a codecname.
+	 */
+	addAlias: function(classname, codecname)
+	{
+		mxCodecRegistry.aliases[classname] = codecname;
+	},
+
+	/**
+	 * Function: getCodec
+	 *
+	 * Returns a codec that handles objects that are constructed
+	 * using the given constructor.
+	 *
+	 * Parameters:
+	 *
+	 * ctor - JavaScript constructor function. 
+	 */
+	getCodec: function(ctor)
+	{
+		var codec = null;
+		
+		if (ctor != null)
+		{
+			var name = mxUtils.getFunctionName(ctor);
+			var tmp = mxCodecRegistry.aliases[name];
+			
+			if (tmp != null)
+			{
+				name = tmp;
+			}
+			
+			codec = mxCodecRegistry.codecs[name];
+			
+			// Registers a new default codec for the given constructor
+			// if no codec has been previously defined.
+			if (codec == null)
+			{
+				try
+				{
+					codec = new mxObjectCodec(new ctor());
+					mxCodecRegistry.register(codec);
+				}
+				catch (e)
+				{
+					// ignore
+				}
+			}
+		}
+		
+		return codec;
+	}
+
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/io/mxDefaultKeyHandlerCodec.js b/airavata-kubernetes/workflow-composer/src/js/io/mxDefaultKeyHandlerCodec.js
new file mode 100644
index 0000000..9a18579
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/io/mxDefaultKeyHandlerCodec.js
@@ -0,0 +1,88 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+mxCodecRegistry.register(function()
+{
+	/**
+	 * Class: mxDefaultKeyHandlerCodec
+	 *
+	 * Custom codec for configuring <mxDefaultKeyHandler>s. This class is created
+	 * and registered dynamically at load time and used implicitely via
+	 * <mxCodec> and the <mxCodecRegistry>. This codec only reads configuration
+	 * data for existing key handlers, it does not encode or create key handlers.
+	 */
+	var codec = new mxObjectCodec(new mxDefaultKeyHandler());
+
+	/**
+	 * Function: encode
+	 *
+	 * Returns null.
+	 */
+	codec.encode = function(enc, obj)
+	{
+		return null;
+	};
+	
+	/**
+	 * Function: decode
+	 *
+	 * Reads a sequence of the following child nodes
+	 * and attributes:
+	 *
+	 * Child Nodes:
+	 *
+	 * add - Binds a keystroke to an actionname.
+	 *
+	 * Attributes:
+	 *
+	 * as - Keycode.
+	 * action - Actionname to execute in editor.
+	 * control - Optional boolean indicating if
+	 * 		the control key must be pressed.
+	 *
+	 * Example:
+	 *
+	 * (code)
+	 * <mxDefaultKeyHandler as="keyHandler">
+	 *   <add as="88" control="true" action="cut"/>
+	 *   <add as="67" control="true" action="copy"/>
+	 *   <add as="86" control="true" action="paste"/>
+	 * </mxDefaultKeyHandler>
+	 * (end)
+	 *
+	 * The keycodes are for the x, c and v keys.
+	 *
+	 * See also: <mxDefaultKeyHandler.bindAction>,
+	 * http://www.js-examples.com/page/tutorials__key_codes.html
+	 */
+	codec.decode = function(dec, node, into)
+	{
+		if (into != null)
+		{
+			var editor = into.editor;
+			node = node.firstChild;
+			
+			while (node != null)
+			{
+				if (!this.processInclude(dec, node, into) &&
+					node.nodeName == 'add')
+				{
+					var as = node.getAttribute('as');
+					var action = node.getAttribute('action');
+					var control = node.getAttribute('control');
+					
+					into.bindAction(as, action, control);
+				}
+				
+				node = node.nextSibling;
+			}
+		}
+		
+		return into;
+	};
+
+	// Returns the codec into the registry
+	return codec;
+
+}());
diff --git a/airavata-kubernetes/workflow-composer/src/js/io/mxDefaultPopupMenuCodec.js b/airavata-kubernetes/workflow-composer/src/js/io/mxDefaultPopupMenuCodec.js
new file mode 100644
index 0000000..7a62ac2
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/io/mxDefaultPopupMenuCodec.js
@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+mxCodecRegistry.register(function()
+{
+	/**
+	 * Class: mxDefaultPopupMenuCodec
+	 *
+	 * Custom codec for configuring <mxDefaultPopupMenu>s. This class is created
+	 * and registered dynamically at load time and used implicitely via
+	 * <mxCodec> and the <mxCodecRegistry>. This codec only reads configuration
+	 * data for existing popup menus, it does not encode or create menus. Note
+	 * that this codec only passes the configuration node to the popup menu,
+	 * which uses the config to dynamically create menus. See
+	 * <mxDefaultPopupMenu.createMenu>.
+	 */
+	var codec = new mxObjectCodec(new mxDefaultPopupMenu());
+
+	/**
+	 * Function: encode
+	 *
+	 * Returns null.
+	 */
+	codec.encode = function(enc, obj)
+	{
+		return null;
+	};
+	
+	/**
+	 * Function: decode
+	 *
+	 * Uses the given node as the config for <mxDefaultPopupMenu>.
+	 */
+	codec.decode = function(dec, node, into)
+	{
+		var inc = node.getElementsByTagName('include')[0];
+		
+		if (inc != null)
+		{
+			this.processInclude(dec, inc, into);
+		}
+		else if (into != null)
+		{
+			into.config = node;
+		}
+		
+		return into;
+	};
+	
+	// Returns the codec into the registry
+	return codec;
+
+}());
diff --git a/airavata-kubernetes/workflow-composer/src/js/io/mxDefaultToolbarCodec.js b/airavata-kubernetes/workflow-composer/src/js/io/mxDefaultToolbarCodec.js
new file mode 100644
index 0000000..6157fd3
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/io/mxDefaultToolbarCodec.js
@@ -0,0 +1,312 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxDefaultToolbarCodec
+ *
+ * Custom codec for configuring <mxDefaultToolbar>s. This class is created
+ * and registered dynamically at load time and used implicitely via
+ * <mxCodec> and the <mxCodecRegistry>. This codec only reads configuration
+ * data for existing toolbars handlers, it does not encode or create toolbars.
+ */
+var mxDefaultToolbarCodec = mxCodecRegistry.register(function()
+{
+	var codec = new mxObjectCodec(new mxDefaultToolbar());
+
+	/**
+	 * Function: encode
+	 *
+	 * Returns null.
+	 */
+	codec.encode = function(enc, obj)
+	{
+		return null;
+	};
+	
+	/**
+	 * Function: decode
+	 *
+	 * Reads a sequence of the following child nodes
+	 * and attributes:
+	 *
+	 * Child Nodes:
+	 *
+	 * add - Adds a new item to the toolbar. See below for attributes.
+	 * separator - Adds a vertical separator. No attributes.
+	 * hr - Adds a horizontal separator. No attributes.
+	 * br - Adds a linefeed. No attributes. 
+	 *
+	 * Attributes:
+	 *
+	 * as - Resource key for the label.
+	 * action - Name of the action to execute in enclosing editor.
+	 * mode - Modename (see below).
+	 * template - Template name for cell insertion.
+	 * style - Optional style to override the template style.
+	 * icon - Icon (relative/absolute URL).
+	 * pressedIcon - Optional icon for pressed state (relative/absolute URL).
+	 * id - Optional ID to be used for the created DOM element.
+	 * toggle - Optional 0 or 1 to disable toggling of the element. Default is
+	 * 1 (true).
+	 *
+	 * The action, mode and template attributes are mutually exclusive. The
+	 * style can only be used with the template attribute. The add node may
+	 * contain another sequence of add nodes with as and action attributes
+	 * to create a combo box in the toolbar. If the icon is specified then
+	 * a list of the child node is expected to have its template attribute
+	 * set and the action is ignored instead.
+	 * 
+	 * Nodes with a specified template may define a function to be used for
+	 * inserting the cloned template into the graph. Here is an example of such
+	 * a node:
+	 * 
+	 * (code)
+	 * <add as="Swimlane" template="swimlane" icon="images/swimlane.gif"><![CDATA[
+	 *   function (editor, cell, evt, targetCell)
+	 *   {
+	 *     var pt = mxUtils.convertPoint(
+	 *       editor.graph.container, mxEvent.getClientX(evt),
+	 *         mxEvent.getClientY(evt));
+	 *     return editor.addVertex(targetCell, cell, pt.x, pt.y);
+	 *   }
+	 * ]]></add>
+	 * (end)
+	 * 
+	 * In the above function, editor is the enclosing <mxEditor> instance, cell
+	 * is the clone of the template, evt is the mouse event that represents the
+	 * drop and targetCell is the cell under the mousepointer where the drop
+	 * occurred. The targetCell is retrieved using <mxGraph.getCellAt>.
+	 *
+	 * Futhermore, nodes with the mode attribute may define a function to
+	 * be executed upon selection of the respective toolbar icon. In the
+	 * example below, the default edge style is set when this specific
+	 * connect-mode is activated:
+	 *
+	 * (code)
+	 * <add as="connect" mode="connect"><![CDATA[
+	 *   function (editor)
+	 *   {
+	 *     if (editor.defaultEdge != null)
+	 *     {
+	 *       editor.defaultEdge.style = 'straightEdge';
+	 *     }
+	 *   }
+	 * ]]></add>
+	 * (end)
+	 * 
+	 * Both functions require <mxDefaultToolbarCodec.allowEval> to be set to true.
+	 *
+	 * Modes:
+	 *
+	 * select - Left mouse button used for rubberband- & cell-selection.
+	 * connect - Allows connecting vertices by inserting new edges.
+	 * pan - Disables selection and switches to panning on the left button.
+	 *
+	 * Example:
+	 *
+	 * To add items to the toolbar:
+	 * 
+	 * (code)
+	 * <mxDefaultToolbar as="toolbar">
+	 *   <add as="save" action="save" icon="images/save.gif"/>
+	 *   <br/><hr/>
+	 *   <add as="select" mode="select" icon="images/select.gif"/>
+	 *   <add as="connect" mode="connect" icon="images/connect.gif"/>
+	 * </mxDefaultToolbar>
+	 * (end)
+	 */
+	codec.decode = function(dec, node, into)
+	{
+		if (into != null)
+		{
+			var editor = into.editor;
+			node = node.firstChild;
+			
+			while (node != null)
+			{
+				if (node.nodeType == mxConstants.NODETYPE_ELEMENT)
+				{
+					if (!this.processInclude(dec, node, into))
+					{
+						if (node.nodeName == 'separator')
+						{
+							into.addSeparator();
+						}
+						else if (node.nodeName == 'br')
+						{
+							into.toolbar.addBreak();
+						}
+						else if (node.nodeName == 'hr')
+						{
+							into.toolbar.addLine();
+						}
+						else if (node.nodeName == 'add')
+						{
+							var as = node.getAttribute('as');
+							as = mxResources.get(as) || as;
+							var icon = node.getAttribute('icon');
+							var pressedIcon = node.getAttribute('pressedIcon');
+							var action = node.getAttribute('action');
+							var mode = node.getAttribute('mode');
+							var template = node.getAttribute('template');
+							var toggle = node.getAttribute('toggle') != '0';
+							var text = mxUtils.getTextContent(node);
+							var elt = null;
+
+							if (action != null)
+							{
+								elt = into.addItem(as, icon, action, pressedIcon);
+							}
+							else if (mode != null)
+							{
+								var funct = (mxDefaultToolbarCodec.allowEval) ? mxUtils.eval(text) : null;
+								elt = into.addMode(as, icon, mode, pressedIcon, funct);
+							}
+							else if (template != null || (text != null && text.length > 0))
+							{
+								var cell = editor.templates[template];
+								var style = node.getAttribute('style');
+								
+								if (cell != null && style != null)
+								{
+									cell = editor.graph.cloneCells([cell])[0];
+									cell.setStyle(style);
+								}
+								
+								var insertFunction = null;
+								
+								if (text != null && text.length > 0 && mxDefaultToolbarCodec.allowEval)
+								{
+									insertFunction = mxUtils.eval(text);
+								}
+								
+								elt = into.addPrototype(as, icon, cell, pressedIcon, insertFunction, toggle);
+							}
+							else
+							{
+								var children = mxUtils.getChildNodes(node);
+								
+								if (children.length > 0)
+								{
+									if (icon == null)
+									{
+										var combo = into.addActionCombo(as);
+										
+										for (var i=0; i<children.length; i++)
+										{
+											var child = children[i];
+											
+											if (child.nodeName == 'separator')
+											{
+												into.addOption(combo, '---');
+											}
+											else if (child.nodeName == 'add')
+											{
+												var lab = child.getAttribute('as');
+												var act = child.getAttribute('action');
+												into.addActionOption(combo, lab, act);
+											}
+										}
+									}
+									else
+									{
+										var select = null;
+										var create = function()
+										{
+											var template = editor.templates[select.value];
+											
+											if (template != null)
+											{
+												var clone = template.clone();
+												var style = select.options[select.selectedIndex].cellStyle;
+												
+												if (style != null)
+												{
+													clone.setStyle(style);
+												}
+												
+												return clone;
+											}
+											else
+											{
+												mxLog.warn('Template '+template+' not found');
+											}
+											
+											return null;
+										};
+										
+										var img = into.addPrototype(as, icon, create, null, null, toggle);
+										select = into.addCombo();
+										
+										// Selects the toolbar icon if a selection change
+										// is made in the corresponding combobox.
+										mxEvent.addListener(select, 'change', function()
+										{
+											into.toolbar.selectMode(img, function(evt)
+											{
+												var pt = mxUtils.convertPoint(editor.graph.container,
+													mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+												
+												return editor.addVertex(null, funct(), pt.x, pt.y);
+											});
+											
+											into.toolbar.noReset = false;
+										});
+										
+										// Adds the entries to the combobox
+										for (var i=0; i<children.length; i++)
+										{
+											var child = children[i];
+											
+											if (child.nodeName == 'separator')
+											{
+												into.addOption(select, '---');
+											}
+											else if (child.nodeName == 'add')
+											{
+												var lab = child.getAttribute('as');
+												var tmp = child.getAttribute('template');
+												var option = into.addOption(select, lab, tmp || template);
+												option.cellStyle = child.getAttribute('style');
+											}
+										}
+										
+									}
+								}
+							}
+							
+							// Assigns an ID to the created element to access it later.
+							if (elt != null)
+							{
+								var id = node.getAttribute('id');
+								
+								if (id != null && id.length > 0)
+								{
+									elt.setAttribute('id', id);
+								}
+							}
+						}
+					}
+				}
+				
+				node = node.nextSibling;
+			}
+		}
+		
+		return into;
+	};
+	
+	// Returns the codec into the registry
+	return codec;
+
+}());
+
+/**
+ * Variable: allowEval
+ * 
+ * Static global switch that specifies if the use of eval is allowed for
+ * evaluating text content. Default is true. Set this to false if stylesheets
+ * may contain user input
+ */
+mxDefaultToolbarCodec.allowEval = true;
diff --git a/airavata-kubernetes/workflow-composer/src/js/io/mxEditorCodec.js b/airavata-kubernetes/workflow-composer/src/js/io/mxEditorCodec.js
new file mode 100644
index 0000000..47ce585
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/io/mxEditorCodec.js
@@ -0,0 +1,245 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+mxCodecRegistry.register(function()
+{
+	/**
+	 * Class: mxEditorCodec
+	 *
+	 * Codec for <mxEditor>s. This class is created and registered
+	 * dynamically at load time and used implicitely via <mxCodec>
+	 * and the <mxCodecRegistry>.
+	 *
+	 * Transient Fields:
+	 *
+	 * - modified
+	 * - lastSnapshot
+	 * - ignoredChanges
+	 * - undoManager
+	 * - graphContainer
+	 * - toolbarContainer
+	 */
+	var codec = new mxObjectCodec(new mxEditor(),
+		['modified', 'lastSnapshot', 'ignoredChanges',
+		'undoManager', 'graphContainer', 'toolbarContainer']);
+
+	/**
+	 * Function: beforeDecode
+	 *
+	 * Decodes the ui-part of the configuration node by reading
+	 * a sequence of the following child nodes and attributes
+	 * and passes the control to the default decoding mechanism:
+	 *
+	 * Child Nodes:
+	 *
+	 * stylesheet - Adds a CSS stylesheet to the document.
+	 * resource - Adds the basename of a resource bundle.
+	 * add - Creates or configures a known UI element.
+	 *
+	 * These elements may appear in any order given that the
+	 * graph UI element is added before the toolbar element
+	 * (see Known Keys).
+	 *
+	 * Attributes:
+	 *
+	 * as - Key for the UI element (see below).
+	 * element - ID for the element in the document.
+	 * style - CSS style to be used for the element or window.
+	 * x - X coordinate for the new window.
+	 * y - Y coordinate for the new window.
+	 * width - Width for the new window.
+	 * height - Optional height for the new window.
+	 * name - Name of the stylesheet (absolute/relative URL).
+	 * basename - Basename of the resource bundle (see <mxResources>).
+	 *
+	 * The x, y, width and height attributes are used to create a new
+	 * <mxWindow> if the element attribute is not specified in an add
+	 * node. The name and basename are only used in the stylesheet and
+	 * resource nodes, respectively.
+	 *
+	 * Known Keys:
+	 *
+	 * graph - Main graph element (see <mxEditor.setGraphContainer>).
+	 * title - Title element (see <mxEditor.setTitleContainer>).
+	 * toolbar - Toolbar element (see <mxEditor.setToolbarContainer>).
+	 * status - Status bar element (see <mxEditor.setStatusContainer>).
+	 *
+	 * Example:
+	 *
+	 * (code)
+	 * <ui>
+	 *   <stylesheet name="css/process.css"/>
+	 *   <resource basename="resources/app"/>
+	 *   <add as="graph" element="graph"
+	 *     style="left:70px;right:20px;top:20px;bottom:40px"/>
+	 *   <add as="status" element="status"/>
+	 *   <add as="toolbar" x="10" y="20" width="54"/>
+	 * </ui>
+	 * (end)
+	 */
+	codec.afterDecode = function(dec, node, obj)
+	{
+		// Assigns the specified templates for edges
+		var defaultEdge = node.getAttribute('defaultEdge');
+		
+		if (defaultEdge != null)
+		{
+			node.removeAttribute('defaultEdge');
+			obj.defaultEdge = obj.templates[defaultEdge];
+		}
+
+		// Assigns the specified templates for groups
+		var defaultGroup = node.getAttribute('defaultGroup');
+		
+		if (defaultGroup != null)
+		{
+			node.removeAttribute('defaultGroup');
+			obj.defaultGroup = obj.templates[defaultGroup];
+		}
+
+		return obj;
+	};
+	
+	/**
+	 * Function: decodeChild
+	 * 
+	 * Overrides decode child to handle special child nodes.
+	 */	
+	codec.decodeChild = function(dec, child, obj)
+	{
+		if (child.nodeName == 'Array')
+		{
+			var role = child.getAttribute('as');
+			
+			if (role == 'templates')
+			{
+				this.decodeTemplates(dec, child, obj);
+				return;
+			}
+		}
+		else if (child.nodeName == 'ui')
+		{
+			this.decodeUi(dec, child, obj);
+			return;
+		}
+		
+		mxObjectCodec.prototype.decodeChild.apply(this, arguments);
+	};
+		
+	/**
+	 * Function: decodeTemplates
+	 *
+	 * Decodes the cells from the given node as templates.
+	 */
+	codec.decodeUi = function(dec, node, editor)
+	{
+		var tmp = node.firstChild;
+		while (tmp != null)
+		{
+			if (tmp.nodeName == 'add')
+			{
+				var as = tmp.getAttribute('as');
+				var elt = tmp.getAttribute('element');
+				var style = tmp.getAttribute('style');
+				var element = null;
+
+				if (elt != null)
+				{
+					element = document.getElementById(elt);
+					
+					if (element != null && style != null)
+					{
+						element.style.cssText += ';' + style;
+					}
+				}
+				else
+				{
+					var x = parseInt(tmp.getAttribute('x'));
+					var y = parseInt(tmp.getAttribute('y'));
+					var width = tmp.getAttribute('width');
+					var height = tmp.getAttribute('height');
+
+					// Creates a new window around the element
+					element = document.createElement('div');
+					element.style.cssText = style;
+					
+					var wnd = new mxWindow(mxResources.get(as) || as,
+						element, x, y, width, height, false, true);
+					wnd.setVisible(true);
+				}
+				
+				// TODO: Make more generic
+				if (as == 'graph')
+				{
+					editor.setGraphContainer(element);
+				}
+				else if (as == 'toolbar')
+				{
+					editor.setToolbarContainer(element);
+				}
+				else if (as == 'title')
+				{
+					editor.setTitleContainer(element);
+				}
+				else if (as == 'status')
+				{
+					editor.setStatusContainer(element);
+				}
+				else if (as == 'map')
+				{
+					editor.setMapContainer(element);
+				}
+			}
+			else if (tmp.nodeName == 'resource')
+			{
+				mxResources.add(tmp.getAttribute('basename'));
+			}
+			else if (tmp.nodeName == 'stylesheet')
+			{
+				mxClient.link('stylesheet', tmp.getAttribute('name'));
+			}
+			
+			tmp = tmp.nextSibling;
+		}	
+	};
+	
+	/**
+	 * Function: decodeTemplates
+	 *
+	 * Decodes the cells from the given node as templates.
+	 */
+	codec.decodeTemplates = function(dec, node, editor)
+	{
+		if (editor.templates == null)
+		{
+			editor.templates = [];
+		}
+		
+		var children = mxUtils.getChildNodes(node);
+		for (var j=0; j<children.length; j++)
+		{
+			var name = children[j].getAttribute('as');
+			var child = children[j].firstChild;
+			
+			while (child != null && child.nodeType != 1)
+			{
+				child = child.nextSibling;
+			}
+			
+			if (child != null)
+			{
+				// LATER: Only single cells means you need
+				// to group multiple cells within another
+				// cell. This should be changed to support
+				// arrays of cells, or the wrapper must
+				// be automatically handled in this class.
+				editor.templates[name] = dec.decodeCell(child);
+			}
+		}
+	};
+	
+	// Returns the codec into the registry
+	return codec;
+
+}());
diff --git a/airavata-kubernetes/workflow-composer/src/js/io/mxGenericChangeCodec.js b/airavata-kubernetes/workflow-composer/src/js/io/mxGenericChangeCodec.js
new file mode 100644
index 0000000..8ecc82a
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/io/mxGenericChangeCodec.js
@@ -0,0 +1,64 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxGenericChangeCodec
+ *
+ * Codec for <mxValueChange>s, <mxStyleChange>s, <mxGeometryChange>s,
+ * <mxCollapseChange>s and <mxVisibleChange>s. This class is created
+ * and registered dynamically at load time and used implicitely
+ * via <mxCodec> and the <mxCodecRegistry>.
+ *
+ * Transient Fields:
+ *
+ * - model
+ * - previous
+ *
+ * Reference Fields:
+ *
+ * - cell
+ * 
+ * Constructor: mxGenericChangeCodec
+ *
+ * Factory function that creates a <mxObjectCodec> for
+ * the specified change and fieldname.
+ *
+ * Parameters:
+ *
+ * obj - An instance of the change object.
+ * variable - The fieldname for the change data.
+ */
+var mxGenericChangeCodec = function(obj, variable)
+{
+	var codec = new mxObjectCodec(obj,  ['model', 'previous'], ['cell']);
+
+	/**
+	 * Function: afterDecode
+	 *
+	 * Restores the state by assigning the previous value.
+	 */
+	codec.afterDecode = function(dec, node, obj)
+	{
+		// Allows forward references in sessions. This is a workaround
+		// for the sequence of edits in mxGraph.moveCells and cellsAdded.
+		if (mxUtils.isNode(obj.cell))
+		{
+			obj.cell = dec.decodeCell(obj.cell, false);
+		}
+
+		obj.previous = obj[variable];
+
+		return obj;
+	};
+	
+	return codec;
+};
+
+// Registers the codecs
+mxCodecRegistry.register(mxGenericChangeCodec(new mxValueChange(), 'value'));
+mxCodecRegistry.register(mxGenericChangeCodec(new mxStyleChange(), 'style'));
+mxCodecRegistry.register(mxGenericChangeCodec(new mxGeometryChange(), 'geometry'));
+mxCodecRegistry.register(mxGenericChangeCodec(new mxCollapseChange(), 'collapsed'));
+mxCodecRegistry.register(mxGenericChangeCodec(new mxVisibleChange(), 'visible'));
+mxCodecRegistry.register(mxGenericChangeCodec(new mxCellAttributeChange(), 'value'));
diff --git a/airavata-kubernetes/workflow-composer/src/js/io/mxGraphCodec.js b/airavata-kubernetes/workflow-composer/src/js/io/mxGraphCodec.js
new file mode 100644
index 0000000..f3e7a56
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/io/mxGraphCodec.js
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+mxCodecRegistry.register(function()
+{
+	/**
+	 * Class: mxGraphCodec
+	 *
+	 * Codec for <mxGraph>s. This class is created and registered
+	 * dynamically at load time and used implicitely via <mxCodec>
+	 * and the <mxCodecRegistry>.
+	 *
+	 * Transient Fields:
+	 *
+	 * - graphListeners
+	 * - eventListeners
+	 * - view
+	 * - container
+	 * - cellRenderer
+	 * - editor
+	 * - selection
+	 */
+	return new mxObjectCodec(new mxGraph(),
+		['graphListeners', 'eventListeners', 'view', 'container',
+		'cellRenderer', 'editor', 'selection']);
+
+}());
diff --git a/airavata-kubernetes/workflow-composer/src/js/io/mxGraphViewCodec.js b/airavata-kubernetes/workflow-composer/src/js/io/mxGraphViewCodec.js
new file mode 100644
index 0000000..c3023de
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/io/mxGraphViewCodec.js
@@ -0,0 +1,197 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+mxCodecRegistry.register(function()
+{
+	/**
+	 * Class: mxGraphViewCodec
+	 *
+	 * Custom encoder for <mxGraphView>s. This class is created
+	 * and registered dynamically at load time and used implicitely via
+	 * <mxCodec> and the <mxCodecRegistry>. This codec only writes views
+	 * into a XML format that can be used to create an image for
+	 * the graph, that is, it contains absolute coordinates with
+	 * computed perimeters, edge styles and cell styles.
+	 */
+	var codec = new mxObjectCodec(new mxGraphView());
+
+	/**
+	 * Function: encode
+	 *
+	 * Encodes the given <mxGraphView> using <encodeCell>
+	 * starting at the model's root. This returns the
+	 * top-level graph node of the recursive encoding.
+	 */
+	codec.encode = function(enc, view)
+	{
+		return this.encodeCell(enc, view,
+			view.graph.getModel().getRoot());
+	};
+
+	/**
+	 * Function: encodeCell
+	 *
+	 * Recursively encodes the specifed cell. Uses layer
+	 * as the default nodename. If the cell's parent is
+	 * null, then graph is used for the nodename. If
+	 * <mxGraphModel.isEdge> returns true for the cell,
+	 * then edge is used for the nodename, else if
+	 * <mxGraphModel.isVertex> returns true for the cell,
+	 * then vertex is used for the nodename.
+	 *
+	 * <mxGraph.getLabel> is used to create the label
+	 * attribute for the cell. For graph nodes and vertices
+	 * the bounds are encoded into x, y, width and height.
+	 * For edges the points are encoded into a points
+	 * attribute as a space-separated list of comma-separated
+	 * coordinate pairs (eg. x0,y0 x1,y1 ... xn,yn). All
+	 * values from the cell style are added as attribute
+	 * values to the node. 
+	 */
+	codec.encodeCell = function(enc, view, cell)
+	{
+		var model = view.graph.getModel();
+		var state = view.getState(cell);
+		var parent = model.getParent(cell);
+		
+		if (parent == null || state != null)
+		{
+			var childCount = model.getChildCount(cell);
+			var geo = view.graph.getCellGeometry(cell);
+			var name = null;
+			
+			if (parent == model.getRoot())
+			{
+				name = 'layer';
+			}
+			else if (parent == null)
+			{
+				name = 'graph';
+			}
+			else if (model.isEdge(cell))
+			{
+				name = 'edge';
+			}
+			else if (childCount > 0 && geo != null)
+			{
+				name = 'group';
+			}
+			else if (model.isVertex(cell))
+			{
+				name = 'vertex';
+			}
+			
+			if (name != null)
+			{
+				var node = enc.document.createElement(name);
+				var lab = view.graph.getLabel(cell);
+				
+				if (lab != null)
+				{
+					node.setAttribute('label', view.graph.getLabel(cell));
+					
+					if (view.graph.isHtmlLabel(cell))
+					{
+						node.setAttribute('html', true);
+					}
+				}
+		
+				if (parent == null)
+				{
+					var bounds = view.getGraphBounds();
+					
+					if (bounds != null)
+					{
+						node.setAttribute('x', Math.round(bounds.x));
+						node.setAttribute('y', Math.round(bounds.y));
+						node.setAttribute('width', Math.round(bounds.width));
+						node.setAttribute('height', Math.round(bounds.height));
+					}
+					
+					node.setAttribute('scale', view.scale);
+				}
+				else if (state != null && geo != null)
+				{
+					// Writes each key, value in the style pair to an attribute
+				    for (var i in state.style)
+				    {
+				    	var value = state.style[i];
+		
+				    	// Tries to turn objects and functions into strings
+					    if (typeof(value) == 'function' &&
+							typeof(value) == 'object')
+						{
+					    	value = mxStyleRegistry.getName(value);
+				        }
+				    	
+				    	if (value != null &&
+				    		typeof(value) != 'function' &&
+							typeof(value) != 'object')
+						{
+							node.setAttribute(i, value);
+				        }
+				    }
+				    
+					var abs = state.absolutePoints;
+					
+					// Writes the list of points into one attribute
+					if (abs != null && abs.length > 0)
+					{
+						var pts = Math.round(abs[0].x) + ',' + Math.round(abs[0].y);
+		
+						for (var i=1; i<abs.length; i++)
+						{
+							pts += ' ' + Math.round(abs[i].x) + ',' +
+								Math.round(abs[i].y);
+						}
+		
+						node.setAttribute('points', pts);
+					}
+					
+					// Writes the bounds into 4 attributes
+					else
+					{
+						node.setAttribute('x', Math.round(state.x));
+						node.setAttribute('y', Math.round(state.y));
+						node.setAttribute('width', Math.round(state.width));
+						node.setAttribute('height', Math.round(state.height));				
+					}
+		
+					var offset = state.absoluteOffset;
+					
+					// Writes the offset into 2 attributes
+					if (offset != null)
+					{
+						if (offset.x != 0)
+						{
+							node.setAttribute('dx', Math.round(offset.x));
+						}
+						
+						if (offset.y != 0)
+						{
+							node.setAttribute('dy', Math.round(offset.y));
+						}
+					}
+				}
+		
+				for (var i=0; i<childCount; i++)
+				{
+					var childNode = this.encodeCell(enc,
+							view, model.getChildAt(cell, i));
+					
+					if (childNode != null)
+					{
+						node.appendChild(childNode);
+					}
+				}
+			}
+		}
+		
+		return node;
+	};
+
+	// Returns the codec into the registry
+	return codec;
+
+}());
diff --git a/airavata-kubernetes/workflow-composer/src/js/io/mxModelCodec.js b/airavata-kubernetes/workflow-composer/src/js/io/mxModelCodec.js
new file mode 100644
index 0000000..85ec4ce
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/io/mxModelCodec.js
@@ -0,0 +1,80 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+mxCodecRegistry.register(function()
+{
+	/**
+	 * Class: mxModelCodec
+	 *
+	 * Codec for <mxGraphModel>s. This class is created and registered
+	 * dynamically at load time and used implicitely via <mxCodec>
+	 * and the <mxCodecRegistry>.
+	 */
+	var codec = new mxObjectCodec(new mxGraphModel());
+
+	/**
+	 * Function: encodeObject
+	 *
+	 * Encodes the given <mxGraphModel> by writing a (flat) XML sequence of
+	 * cell nodes as produced by the <mxCellCodec>. The sequence is
+	 * wrapped-up in a node with the name root.
+	 */
+	codec.encodeObject = function(enc, obj, node)
+	{
+		var rootNode = enc.document.createElement('root');
+		enc.encodeCell(obj.getRoot(), rootNode);
+		node.appendChild(rootNode);
+	};
+
+	/**
+	 * Function: decodeChild
+	 * 
+	 * Overrides decode child to handle special child nodes.
+	 */	
+	codec.decodeChild = function(dec, child, obj)
+	{
+		if (child.nodeName == 'root')
+		{
+			this.decodeRoot(dec, child, obj);
+		}
+		else
+		{
+			mxObjectCodec.prototype.decodeChild.apply(this, arguments);
+		}
+	};
+
+	/**
+	 * Function: decodeRoot
+	 *
+	 * Reads the cells into the graph model. All cells
+	 * are children of the root element in the node.
+	 */
+	codec.decodeRoot = function(dec, root, model)
+	{
+		var rootCell = null;
+		var tmp = root.firstChild;
+		
+		while (tmp != null)
+		{
+			var cell = dec.decodeCell(tmp);
+			
+			if (cell != null && cell.getParent() == null)
+			{
+				rootCell = cell;
+			}
+			
+			tmp = tmp.nextSibling;
+		}
+
+		// Sets the root on the model if one has been decoded
+		if (rootCell != null)
+		{
+			model.setRoot(rootCell);
+		}
+	};
+
+	// Returns the codec into the registry
+	return codec;
+
+}());
diff --git a/airavata-kubernetes/workflow-composer/src/js/io/mxObjectCodec.js b/airavata-kubernetes/workflow-composer/src/js/io/mxObjectCodec.js
new file mode 100644
index 0000000..ac69318
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/io/mxObjectCodec.js
@@ -0,0 +1,1077 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxObjectCodec
+ *
+ * Generic codec for JavaScript objects that implements a mapping between
+ * JavaScript objects and XML nodes that maps each field or element to an
+ * attribute or child node, and vice versa.
+ * 
+ * Atomic Values:
+ * 
+ * Consider the following example.
+ * 
+ * (code)
+ * var obj = new Object();
+ * obj.foo = "Foo";
+ * obj.bar = "Bar";
+ * (end)
+ * 
+ * This object is encoded into an XML node using the following.
+ * 
+ * (code)
+ * var enc = new mxCodec();
+ * var node = enc.encode(obj);
+ * (end)
+ * 
+ * The output of the encoding may be viewed using <mxLog> as follows.
+ * 
+ * (code)
+ * mxLog.show();
+ * mxLog.debug(mxUtils.getPrettyXml(node));
+ * (end)
+ * 
+ * Finally, the result of the encoding looks as follows.
+ * 
+ * (code)
+ * <Object foo="Foo" bar="Bar"/>
+ * (end)
+ * 
+ * In the above output, the foo and bar fields have been mapped to attributes
+ * with the same names, and the name of the constructor was used for the
+ * nodename.
+ * 
+ * Booleans:
+ *
+ * Since booleans are numbers in JavaScript, all boolean values are encoded
+ * into 1 for true and 0 for false. The decoder also accepts the string true
+ * and false for boolean values.
+ * 
+ * Objects:
+ * 
+ * The above scheme is applied to all atomic fields, that is, to all non-object
+ * fields of an object. For object fields, a child node is created with a
+ * special attribute that contains the fieldname. This special attribute is
+ * called "as" and hence, as is a reserved word that should not be used for a
+ * fieldname.
+ * 
+ * Consider the following example where foo is an object and bar is an atomic
+ * property of foo.
+ * 
+ * (code)
+ * var obj = {foo: {bar: "Bar"}};
+ * (end)
+ * 
+ * This will be mapped to the following XML structure by mxObjectCodec.
+ * 
+ * (code)
+ * <Object>
+ *   <Object bar="Bar" as="foo"/>
+ * </Object>
+ * (end)
+ * 
+ * In the above output, the inner Object node contains the as-attribute that
+ * specifies the fieldname in the enclosing object. That is, the field foo was
+ * mapped to a child node with an as-attribute that has the value foo.
+ * 
+ * Arrays:
+ * 
+ * Arrays are special objects that are either associative, in which case each
+ * key, value pair is treated like a field where the key is the fieldname, or
+ * they are a sequence of atomic values and objects, which is mapped to a
+ * sequence of child nodes. For object elements, the above scheme is applied
+ * without the use of the special as-attribute for creating each child. For
+ * atomic elements, a special add-node is created with the value stored in the
+ * value-attribute.
+ * 
+ * For example, the following array contains one atomic value and one object
+ * with a field called bar. Furthermore it contains two associative entries
+ * called bar with an atomic value, and foo with an object value.
+ * 
+ * (code)
+ * var obj = ["Bar", {bar: "Bar"}];
+ * obj["bar"] = "Bar";
+ * obj["foo"] = {bar: "Bar"};
+ * (end)
+ * 
+ * This array is represented by the following XML nodes.
+ * 
+ * (code)
+ * <Array bar="Bar">
+ *   <add value="Bar"/>
+ *   <Object bar="Bar"/>
+ *   <Object bar="Bar" as="foo"/>
+ * </Array>
+ * (end)
+ * 
+ * The Array node name is the name of the constructor. The additional
+ * as-attribute in the last child contains the key of the associative entry,
+ * whereas the second last child is part of the array sequence and does not
+ * have an as-attribute.
+ * 
+ * References:
+ * 
+ * Objects may be represented as child nodes or attributes with ID values,
+ * which are used to lookup the object in a table within <mxCodec>. The
+ * <isReference> function is in charge of deciding if a specific field should
+ * be encoded as a reference or not. Its default implementation returns true if
+ * the fieldname is in <idrefs>, an array of strings that is used to configure
+ * the <mxObjectCodec>.
+ * 
+ * Using this approach, the mapping does not guarantee that the referenced
+ * object itself exists in the document. The fields that are encoded as
+ * references must be carefully chosen to make sure all referenced objects
+ * exist in the document, or may be resolved by some other means if necessary.
+ * 
+ * For example, in the case of the graph model all cells are stored in a tree
+ * whose root is referenced by the model's root field. A tree is a structure
+ * that is well suited for an XML representation, however, the additional edges
+ * in the graph model have a reference to a source and target cell, which are
+ * also contained in the tree. To handle this case, the source and target cell
+ * of an edge are treated as references, whereas the children are treated as
+ * objects. Since all cells are contained in the tree and no edge references a
+ * source or target outside the tree, this setup makes sure all referenced
+ * objects are contained in the document.
+ * 
+ * In the case of a tree structure we must further avoid infinite recursion by
+ * ignoring the parent reference of each child. This is done by returning true
+ * in <isExcluded>, whose default implementation uses the array of excluded
+ * fieldnames passed to the mxObjectCodec constructor.
+ * 
+ * References are only used for cells in mxGraph. For defining other
+ * referencable object types, the codec must be able to work out the ID of an
+ * object. This is done by implementing <mxCodec.reference>. For decoding a
+ * reference, the XML node with the respective id-attribute is fetched from the
+ * document, decoded, and stored in a lookup table for later reference. For
+ * looking up external objects, <mxCodec.lookup> may be implemented.
+ * 
+ * Expressions:
+ * 
+ * For decoding JavaScript expressions, the add-node may be used with a text
+ * content that contains the JavaScript expression. For example, the following
+ * creates a field called foo in the enclosing object and assigns it the value
+ * of <mxConstants.ALIGN_LEFT>.
+ * 
+ * (code)
+ * <Object>
+ *   <add as="foo">mxConstants.ALIGN_LEFT</add>
+ * </Object>
+ * (end)
+ * 
+ * The resulting object has a field called foo with the value "left". Its XML
+ * representation looks as follows.
+ * 
+ * (code)
+ * <Object foo="left"/>
+ * (end)
+ * 
+ * This means the expression is evaluated at decoding time and the result of
+ * the evaluation is stored in the respective field. Valid expressions are all
+ * JavaScript expressions, including function definitions, which are mapped to
+ * functions on the resulting object.
+ * 
+ * Expressions are only evaluated if <allowEval> is true.
+ * 
+ * Constructor: mxObjectCodec
+ *
+ * Constructs a new codec for the specified template object.
+ * The variables in the optional exclude array are ignored by
+ * the codec. Variables in the optional idrefs array are
+ * turned into references in the XML. The optional mapping
+ * may be used to map from variable names to XML attributes.
+ * The argument is created as follows:
+ *
+ * (code)
+ * var mapping = new Object();
+ * mapping['variableName'] = 'attribute-name';
+ * (end)
+ *
+ * Parameters:
+ *
+ * template - Prototypical instance of the object to be
+ * encoded/decoded.
+ * exclude - Optional array of fieldnames to be ignored.
+ * idrefs - Optional array of fieldnames to be converted to/from
+ * references.
+ * mapping - Optional mapping from field- to attributenames.
+ */
+function mxObjectCodec(template, exclude, idrefs, mapping)
+{
+	this.template = template;
+	
+	this.exclude = (exclude != null) ? exclude : [];
+	this.idrefs = (idrefs != null) ? idrefs : [];
+	this.mapping = (mapping != null) ? mapping : [];
+	
+	this.reverse = new Object();
+	
+	for (var i in this.mapping)
+	{
+		this.reverse[this.mapping[i]] = i;
+	}
+};
+
+/**
+ * Variable: allowEval
+ *
+ * Static global switch that specifies if expressions in arrays are allowed.
+ * Default is false. NOTE: Enabling this carries a possible security risk.
+ */
+mxObjectCodec.allowEval = false;
+
+/**
+ * Variable: template
+ *
+ * Holds the template object associated with this codec.
+ */
+mxObjectCodec.prototype.template = null;
+
+/**
+ * Variable: exclude
+ *
+ * Array containing the variable names that should be
+ * ignored by the codec.
+ */
+mxObjectCodec.prototype.exclude = null;
+
+/**
+ * Variable: idrefs
+ *
+ * Array containing the variable names that should be
+ * turned into or converted from references. See
+ * <mxCodec.getId> and <mxCodec.getObject>.
+ */
+mxObjectCodec.prototype.idrefs = null;
+
+/**
+ * Variable: mapping
+ *
+ * Maps from from fieldnames to XML attribute names.
+ */
+mxObjectCodec.prototype.mapping = null;
+
+/**
+ * Variable: reverse
+ *
+ * Maps from from XML attribute names to fieldnames.
+ */
+mxObjectCodec.prototype.reverse = null;
+
+/**
+ * Function: getName
+ * 
+ * Returns the name used for the nodenames and lookup of the codec when
+ * classes are encoded and nodes are decoded. For classes to work with
+ * this the codec registry automatically adds an alias for the classname
+ * if that is different than what this returns. The default implementation
+ * returns the classname of the template class.
+ */
+mxObjectCodec.prototype.getName = function()
+{
+	return mxUtils.getFunctionName(this.template.constructor);
+};
+
+/**
+ * Function: cloneTemplate
+ * 
+ * Returns a new instance of the template for this codec.
+ */
+mxObjectCodec.prototype.cloneTemplate = function()
+{
+	return new this.template.constructor();
+};
+
+/**
+ * Function: getFieldName
+ * 
+ * Returns the fieldname for the given attributename.
+ * Looks up the value in the <reverse> mapping or returns
+ * the input if there is no reverse mapping for the
+ * given name.
+ */
+mxObjectCodec.prototype.getFieldName = function(attributename)
+{
+	if (attributename != null)
+	{
+		var mapped = this.reverse[attributename];
+		
+		if (mapped != null)
+		{
+			attributename = mapped;
+		}
+	}
+	
+	return attributename;
+};
+
+/**
+ * Function: getAttributeName
+ * 
+ * Returns the attributename for the given fieldname.
+ * Looks up the value in the <mapping> or returns
+ * the input if there is no mapping for the
+ * given name.
+ */
+mxObjectCodec.prototype.getAttributeName = function(fieldname)
+{
+	if (fieldname != null)
+	{
+		var mapped = this.mapping[fieldname];
+		
+		if (mapped != null)
+		{
+			fieldname = mapped;
+		}
+	}
+	
+	return fieldname;
+};
+
+/**
+ * Function: isExcluded
+ *
+ * Returns true if the given attribute is to be ignored by the codec. This
+ * implementation returns true if the given fieldname is in <exclude> or
+ * if the fieldname equals <mxObjectIdentity.FIELD_NAME>.
+ *
+ * Parameters:
+ *
+ * obj - Object instance that contains the field.
+ * attr - Fieldname of the field.
+ * value - Value of the field.
+ * write - Boolean indicating if the field is being encoded or decoded.
+ * Write is true if the field is being encoded, else it is being decoded.
+ */
+mxObjectCodec.prototype.isExcluded = function(obj, attr, value, write)
+{
+	return attr == mxObjectIdentity.FIELD_NAME ||
+		mxUtils.indexOf(this.exclude, attr) >= 0;
+};
+
+/**
+ * Function: isReference
+ *
+ * Returns true if the given fieldname is to be treated
+ * as a textual reference (ID). This implementation returns
+ * true if the given fieldname is in <idrefs>.
+ *
+ * Parameters:
+ *
+ * obj - Object instance that contains the field.
+ * attr - Fieldname of the field.
+ * value - Value of the field. 
+ * write - Boolean indicating if the field is being encoded or decoded.
+ * Write is true if the field is being encoded, else it is being decoded.
+ */
+mxObjectCodec.prototype.isReference = function(obj, attr, value, write)
+{
+	return mxUtils.indexOf(this.idrefs, attr) >= 0;
+};
+
+/**
+ * Function: encode
+ *
+ * Encodes the specified object and returns a node
+ * representing then given object. Calls <beforeEncode>
+ * after creating the node and <afterEncode> with the 
+ * resulting node after processing.
+ *
+ * Enc is a reference to the calling encoder. It is used
+ * to encode complex objects and create references.
+ *
+ * This implementation encodes all variables of an
+ * object according to the following rules:
+ *
+ * - If the variable name is in <exclude> then it is ignored.
+ * - If the variable name is in <idrefs> then <mxCodec.getId>
+ * is used to replace the object with its ID.
+ * - The variable name is mapped using <mapping>.
+ * - If obj is an array and the variable name is numeric
+ * (ie. an index) then it is not encoded.
+ * - If the value is an object, then the codec is used to
+ * create a child node with the variable name encoded into
+ * the "as" attribute.
+ * - Else, if <encodeDefaults> is true or the value differs
+ * from the template value, then ...
+ * - ... if obj is not an array, then the value is mapped to
+ * an attribute.
+ * - ... else if obj is an array, the value is mapped to an
+ * add child with a value attribute or a text child node,
+ * if the value is a function.
+ *
+ * If no ID exists for a variable in <idrefs> or if an object
+ * cannot be encoded, a warning is issued using <mxLog.warn>.
+ *
+ * Returns the resulting XML node that represents the given
+ * object.
+ *
+ * Parameters:
+ *
+ * enc - <mxCodec> that controls the encoding process.
+ * obj - Object to be encoded.
+ */
+mxObjectCodec.prototype.encode = function(enc, obj)
+{
+	var node = enc.document.createElement(this.getName());
+	
+	obj = this.beforeEncode(enc, obj, node);
+	this.encodeObject(enc, obj, node);
+	
+	return this.afterEncode(enc, obj, node);
+};
+	
+/**
+ * Function: encodeObject
+ *
+ * Encodes the value of each member in then given obj into the given node using
+ * <encodeValue>.
+ * 
+ * Parameters:
+ *
+ * enc - <mxCodec> that controls the encoding process.
+ * obj - Object to be encoded.
+ * node - XML node that contains the encoded object.
+ */
+mxObjectCodec.prototype.encodeObject = function(enc, obj, node)
+{
+	enc.setAttribute(node, 'id', enc.getId(obj));
+	
+    for (var i in obj)
+    {
+		var name = i;
+		var value = obj[name];
+		
+    	if (value != null && !this.isExcluded(obj, name, value, true))
+    	{
+    		if (mxUtils.isInteger(name))
+    		{
+    			name = null;
+    		}
+    		
+    		this.encodeValue(enc, obj, name, value, node);
+    	}
+    }
+};
+
+/**
+ * Function: encodeValue
+ * 
+ * Converts the given value according to the mappings
+ * and id-refs in this codec and uses <writeAttribute>
+ * to write the attribute into the given node.
+ * 
+ * Parameters:
+ *
+ * enc - <mxCodec> that controls the encoding process.
+ * obj - Object whose property is going to be encoded.
+ * name - XML node that contains the encoded object.
+ * value - Value of the property to be encoded.
+ * node - XML node that contains the encoded object.
+ */
+mxObjectCodec.prototype.encodeValue = function(enc, obj, name, value, node)
+{
+	if (value != null)
+	{
+		if (this.isReference(obj, name, value, true))
+		{
+			var tmp = enc.getId(value);
+			
+			if (tmp == null)
+			{
+		    	mxLog.warn('mxObjectCodec.encode: No ID for ' +
+		    		this.getName() + '.' + name + '=' + value);
+		    	return; // exit
+		    }
+		    
+		    value = tmp;
+		}
+
+		var defaultValue = this.template[name];
+		
+		// Checks if the value is a default value and
+		// the name is correct
+		if (name == null || enc.encodeDefaults || defaultValue != value)
+		{
+			name = this.getAttributeName(name);
+			this.writeAttribute(enc, obj, name, value, node);	
+		}
+	}
+};
+
+/**
+ * Function: writeAttribute
+ * 
+ * Writes the given value into node using <writePrimitiveAttribute>
+ * or <writeComplexAttribute> depending on the type of the value.
+ */
+mxObjectCodec.prototype.writeAttribute = function(enc, obj, name, value, node)
+{
+	if (typeof(value) != 'object' /* primitive type */)
+	{
+		this.writePrimitiveAttribute(enc, obj, name, value, node);
+	}
+	else /* complex type */
+	{
+		this.writeComplexAttribute(enc, obj, name, value, node);
+	}
+};
+
+/**
+ * Function: writePrimitiveAttribute
+ * 
+ * Writes the given value as an attribute of the given node.
+ */
+mxObjectCodec.prototype.writePrimitiveAttribute = function(enc, obj, name, value, node)
+{
+	value = this.convertAttributeToXml(enc, obj, name, value, node);
+	
+	if (name == null)
+	{
+		var child = enc.document.createElement('add');
+		
+		if (typeof(value) == 'function')
+		{
+    		child.appendChild(enc.document.createTextNode(value));
+    	}
+    	else
+    	{
+    		enc.setAttribute(child, 'value', value);
+    	}
+    	
+		node.appendChild(child);
+	}
+	else if (typeof(value) != 'function')
+	{
+    	enc.setAttribute(node, name, value);
+	}		
+};
+	
+/**
+ * Function: writeComplexAttribute
+ * 
+ * Writes the given value as a child node of the given node.
+ */
+mxObjectCodec.prototype.writeComplexAttribute = function(enc, obj, name, value, node)
+{
+	var child = enc.encode(value);
+	
+	if (child != null)
+	{
+		if (name != null)
+		{
+    		child.setAttribute('as', name);
+    	}
+    	
+    	node.appendChild(child);
+	}
+	else
+	{
+		mxLog.warn('mxObjectCodec.encode: No node for ' + this.getName() + '.' + name + ': ' + value);
+	}
+};
+
+/**
+ * Function: convertAttributeToXml
+ * 
+ * Converts true to "1" and false to "0" is <isBooleanAttribute> returns true.
+ * All other values are not converted.
+ * 
+ * Parameters:
+ *
+ * enc - <mxCodec> that controls the encoding process.
+ * obj - Objec to convert the attribute for.
+ * name - Name of the attribute to be converted.
+ * value - Value to be converted.
+ */
+mxObjectCodec.prototype.convertAttributeToXml = function(enc, obj, name, value)
+{
+	// Makes sure to encode boolean values as numeric values
+	if (this.isBooleanAttribute(enc, obj, name, value))
+	{	
+		// Checks if the value is true (do not use the value as is, because
+		// this would check if the value is not null, so 0 would be true)
+		value = (value == true) ? '1' : '0';
+	}
+	
+	return value;
+};
+
+/**
+ * Function: isBooleanAttribute
+ * 
+ * Returns true if the given object attribute is a boolean value.
+ * 
+ * Parameters:
+ *
+ * enc - <mxCodec> that controls the encoding process.
+ * obj - Objec to convert the attribute for.
+ * name - Name of the attribute to be converted.
+ * value - Value of the attribute to be converted.
+ */
+mxObjectCodec.prototype.isBooleanAttribute = function(enc, obj, name, value)
+{
+	return (typeof(value.length) == 'undefined' && (value == true || value == false));
+};
+
+/**
+ * Function: convertAttributeFromXml
+ * 
+ * Converts booleans and numeric values to the respective types. Values are
+ * numeric if <isNumericAttribute> returns true.
+ * 
+ * Parameters:
+ *
+ * dec - <mxCodec> that controls the decoding process.
+ * attr - XML attribute to be converted.
+ * obj - Objec to convert the attribute for.
+ */
+mxObjectCodec.prototype.convertAttributeFromXml = function(dec, attr, obj)
+{
+	var value = attr.value;
+	
+	if (this.isNumericAttribute(dec, attr, obj))
+	{
+		value = parseFloat(value);
+	}
+	
+	return value;
+};
+
+/**
+ * Function: isNumericAttribute
+ * 
+ * Returns true if the given XML attribute is a numeric value.
+ * 
+ * Parameters:
+ *
+ * dec - <mxCodec> that controls the decoding process.
+ * attr - XML attribute to be converted.
+ * obj - Objec to convert the attribute for.
+ */
+mxObjectCodec.prototype.isNumericAttribute = function(dec, attr, obj)
+{
+	return mxUtils.isNumeric(attr.value);
+};
+
+/**
+ * Function: beforeEncode
+ *
+ * Hook for subclassers to pre-process the object before
+ * encoding. This returns the input object. The return
+ * value of this function is used in <encode> to perform
+ * the default encoding into the given node.
+ *
+ * Parameters:
+ *
+ * enc - <mxCodec> that controls the encoding process.
+ * obj - Object to be encoded.
+ * node - XML node to encode the object into.
+ */
+mxObjectCodec.prototype.beforeEncode = function(enc, obj, node)
+{
+	return obj;
+};
+
+/**
+ * Function: afterEncode
+ *
+ * Hook for subclassers to post-process the node
+ * for the given object after encoding and return the
+ * post-processed node. This implementation returns 
+ * the input node. The return value of this method
+ * is returned to the encoder from <encode>.
+ *
+ * Parameters:
+ *
+ * enc - <mxCodec> that controls the encoding process.
+ * obj - Object to be encoded.
+ * node - XML node that represents the default encoding.
+ */
+mxObjectCodec.prototype.afterEncode = function(enc, obj, node)
+{
+	return node;
+};
+
+/**
+ * Function: decode
+ *
+ * Parses the given node into the object or returns a new object
+ * representing the given node.
+ *
+ * Dec is a reference to the calling decoder. It is used to decode
+ * complex objects and resolve references.
+ *
+ * If a node has an id attribute then the object cache is checked for the
+ * object. If the object is not yet in the cache then it is constructed
+ * using the constructor of <template> and cached in <mxCodec.objects>.
+ *
+ * This implementation decodes all attributes and childs of a node
+ * according to the following rules:
+ *
+ * - If the variable name is in <exclude> or if the attribute name is "id"
+ * or "as" then it is ignored.
+ * - If the variable name is in <idrefs> then <mxCodec.getObject> is used
+ * to replace the reference with an object.
+ * - The variable name is mapped using a reverse <mapping>.
+ * - If the value has a child node, then the codec is used to create a
+ * child object with the variable name taken from the "as" attribute.
+ * - If the object is an array and the variable name is empty then the
+ * value or child object is appended to the array.
+ * - If an add child has no value or the object is not an array then
+ * the child text content is evaluated using <mxUtils.eval>.
+ *
+ * For add nodes where the object is not an array and the variable name
+ * is defined, the default mechanism is used, allowing to override/add
+ * methods as follows:
+ *
+ * (code)
+ * <Object>
+ *   <add as="hello"><![CDATA[
+ *     function(arg1) {
+ *       mxUtils.alert('Hello '+arg1);
+ *     }
+ *   ]]></add>
+ * </Object>
+ * (end) 
+ *
+ * If no object exists for an ID in <idrefs> a warning is issued
+ * using <mxLog.warn>.
+ *
+ * Returns the resulting object that represents the given XML node
+ * or the object given to the method as the into parameter.
+ *
+ * Parameters:
+ *
+ * dec - <mxCodec> that controls the decoding process.
+ * node - XML node to be decoded.
+ * into - Optional objec to encode the node into.
+ */
+mxObjectCodec.prototype.decode = function(dec, node, into)
+{
+	var id = node.getAttribute('id');
+	var obj = dec.objects[id];
+	
+	if (obj == null)
+	{
+		obj = into || this.cloneTemplate();
+		
+		if (id != null)
+		{
+			dec.putObject(id, obj);
+		}
+	}
+	
+	node = this.beforeDecode(dec, node, obj);
+	this.decodeNode(dec, node, obj);
+	
+    return this.afterDecode(dec, node, obj);
+};	
+
+/**
+ * Function: decodeNode
+ * 
+ * Calls <decodeAttributes> and <decodeChildren> for the given node.
+ * 
+ * Parameters:
+ *
+ * dec - <mxCodec> that controls the decoding process.
+ * node - XML node to be decoded.
+ * obj - Objec to encode the node into.
+ */	
+mxObjectCodec.prototype.decodeNode = function(dec, node, obj)
+{
+	if (node != null)
+	{
+		this.decodeAttributes(dec, node, obj);
+		this.decodeChildren(dec, node, obj);
+	}
+};
+
+/**
+ * Function: decodeAttributes
+ * 
+ * Decodes all attributes of the given node using <decodeAttribute>.
+ * 
+ * Parameters:
+ *
+ * dec - <mxCodec> that controls the decoding process.
+ * node - XML node to be decoded.
+ * obj - Objec to encode the node into.
+ */	
+mxObjectCodec.prototype.decodeAttributes = function(dec, node, obj)
+{
+	var attrs = node.attributes;
+	
+	if (attrs != null)
+	{
+		for (var i = 0; i < attrs.length; i++)
+		{
+			this.decodeAttribute(dec, attrs[i], obj);
+		}
+	}
+};
+
+/**
+ * Function: isIgnoredAttribute
+ * 
+ * Returns true if the given attribute should be ignored. This implementation
+ * returns true if the attribute name is "as" or "id".
+ * 
+ * Parameters:
+ *
+ * dec - <mxCodec> that controls the decoding process.
+ * attr - XML attribute to be decoded.
+ * obj - Objec to encode the attribute into.
+ */	
+mxObjectCodec.prototype.isIgnoredAttribute = function(dec, attr, obj)
+{
+	return attr.nodeName == 'as' || attr.nodeName == 'id';
+};
+
+/**
+ * Function: decodeAttribute
+ * 
+ * Reads the given attribute into the specified object.
+ * 
+ * Parameters:
+ *
+ * dec - <mxCodec> that controls the decoding process.
+ * attr - XML attribute to be decoded.
+ * obj - Objec to encode the attribute into.
+ */	
+mxObjectCodec.prototype.decodeAttribute = function(dec, attr, obj)
+{
+	if (!this.isIgnoredAttribute(dec, attr, obj))
+	{
+		var name = attr.nodeName;
+		
+		// Converts the string true and false to their boolean values.
+		// This may require an additional check on the obj to see if
+		// the existing field is a boolean value or uninitialized, in
+		// which case we may want to convert true and false to a string.
+		var value = this.convertAttributeFromXml(dec, attr, obj);
+		var fieldname = this.getFieldName(name);
+		
+		if (this.isReference(obj, fieldname, value, false))
+		{
+			var tmp = dec.getObject(value);
+			
+			if (tmp == null)
+			{
+		    	mxLog.warn('mxObjectCodec.decode: No object for ' +
+		    		this.getName() + '.' + name + '=' + value);
+		    	return; // exit
+		    }
+		    
+		    value = tmp;
+		}
+
+		if (!this.isExcluded(obj, name, value, false))
+		{
+			//mxLog.debug(mxUtils.getFunctionName(obj.constructor)+'.'+name+'='+value);
+			obj[name] = value;
+		}
+	}
+};
+
+/**
+ * Function: decodeChildren
+ * 
+ * Decodes all children of the given node using <decodeChild>.
+ * 
+ * Parameters:
+ *
+ * dec - <mxCodec> that controls the decoding process.
+ * node - XML node to be decoded.
+ * obj - Objec to encode the node into.
+ */	
+mxObjectCodec.prototype.decodeChildren = function(dec, node, obj)
+{
+	var child = node.firstChild;
+	
+	while (child != null)
+	{
+		var tmp = child.nextSibling;
+		
+		if (child.nodeType == mxConstants.NODETYPE_ELEMENT &&
+			!this.processInclude(dec, child, obj))
+		{
+			this.decodeChild(dec, child, obj);
+		}
+		
+		child = tmp;
+	}
+};
+
+/**
+ * Function: decodeChild
+ * 
+ * Reads the specified child into the given object.
+ * 
+ * Parameters:
+ *
+ * dec - <mxCodec> that controls the decoding process.
+ * child - XML child element to be decoded.
+ * obj - Objec to encode the node into.
+ */	
+mxObjectCodec.prototype.decodeChild = function(dec, child, obj)
+{
+	var fieldname = this.getFieldName(child.getAttribute('as'));
+	
+	if (fieldname == null || !this.isExcluded(obj, fieldname, child, false))
+	{
+		var template = this.getFieldTemplate(obj, fieldname, child);
+		var value = null;
+		
+		if (child.nodeName == 'add')
+		{
+			value = child.getAttribute('value');
+			
+			if (value == null && mxObjectCodec.allowEval)
+			{
+				value = mxUtils.eval(mxUtils.getTextContent(child));
+			}
+		}
+		else
+		{
+			value = dec.decode(child, template);
+		}
+
+		this.addObjectValue(obj, fieldname, value, template);
+	}
+};
+
+/**
+ * Function: getFieldTemplate
+ * 
+ * Returns the template instance for the given field. This returns the
+ * value of the field, null if the value is an array or an empty collection
+ * if the value is a collection. The value is then used to populate the
+ * field for a new instance. For strongly typed languages it may be
+ * required to override this to return the correct collection instance
+ * based on the encoded child.
+ */	
+mxObjectCodec.prototype.getFieldTemplate = function(obj, fieldname, child)
+{
+	var template = obj[fieldname];
+	
+	// Non-empty arrays are replaced completely
+    if (template instanceof Array && template.length > 0)
+    {
+        template = null;
+    }
+    
+    return template;
+};
+
+/**
+ * Function: addObjectValue
+ * 
+ * Sets the decoded child node as a value of the given object. If the
+ * object is a map, then the value is added with the given fieldname as a
+ * key. If the fieldname is not empty, then setFieldValue is called or
+ * else, if the object is a collection, the value is added to the
+ * collection. For strongly typed languages it may be required to
+ * override this with the correct code to add an entry to an object.
+ */	
+mxObjectCodec.prototype.addObjectValue = function(obj, fieldname, value, template)
+{
+	if (value != null && value != template)
+	{
+		if (fieldname != null && fieldname.length > 0)
+		{
+			obj[fieldname] = value;
+		}
+		else
+		{
+			obj.push(value);
+		}
+		//mxLog.debug('Decoded '+mxUtils.getFunctionName(obj.constructor)+'.'+fieldname+': '+value);
+	}
+};
+
+/**
+ * Function: processInclude
+ *
+ * Returns true if the given node is an include directive and
+ * executes the include by decoding the XML document. Returns
+ * false if the given node is not an include directive.
+ *
+ * Parameters:
+ *
+ * dec - <mxCodec> that controls the encoding/decoding process.
+ * node - XML node to be checked.
+ * into - Optional object to pass-thru to the codec.
+ */
+mxObjectCodec.prototype.processInclude = function(dec, node, into)
+{
+	if (node.nodeName == 'include')
+	{
+		var name = node.getAttribute('name');
+		
+		if (name != null)
+		{
+			try
+			{
+				var xml = mxUtils.load(name).getDocumentElement();
+				
+				if (xml != null)
+				{
+					dec.decode(xml, into);
+				}
+			}
+			catch (e)
+			{
+				// ignore
+			}
+		}
+		
+		return true;
+	}
+	
+	return false;
+};
+
+/**
+ * Function: beforeDecode
+ *
+ * Hook for subclassers to pre-process the node for
+ * the specified object and return the node to be
+ * used for further processing by <decode>.
+ * The object is created based on the template in the 
+ * calling method and is never null. This implementation
+ * returns the input node. The return value of this
+ * function is used in <decode> to perform
+ * the default decoding into the given object.
+ *
+ * Parameters:
+ *
+ * dec - <mxCodec> that controls the decoding process.
+ * node - XML node to be decoded.
+ * obj - Object to encode the node into.
+ */
+mxObjectCodec.prototype.beforeDecode = function(dec, node, obj)
+{
+	return node;
+};
+
+/**
+ * Function: afterDecode
+ *
+ * Hook for subclassers to post-process the object after
+ * decoding. This implementation returns the given object
+ * without any changes. The return value of this method
+ * is returned to the decoder from <decode>.
+ *
+ * Parameters:
+ *
+ * enc - <mxCodec> that controls the encoding process.
+ * node - XML node to be decoded.
+ * obj - Object that represents the default decoding.
+ */
+mxObjectCodec.prototype.afterDecode = function(dec, node, obj)
+{
+	return obj;
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/io/mxRootChangeCodec.js b/airavata-kubernetes/workflow-composer/src/js/io/mxRootChangeCodec.js
new file mode 100644
index 0000000..bf8c47d
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/io/mxRootChangeCodec.js
@@ -0,0 +1,83 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+mxCodecRegistry.register(function()
+{
+	/**
+	 * Class: mxRootChangeCodec
+	 *
+	 * Codec for <mxRootChange>s. This class is created and registered
+	 * dynamically at load time and used implicitely via <mxCodec> and
+	 * the <mxCodecRegistry>.
+	 *
+	 * Transient Fields:
+	 *
+	 * - model
+	 * - previous
+	 * - root
+	 */
+	var codec = new mxObjectCodec(new mxRootChange(),
+		['model', 'previous', 'root']);
+
+	/**
+	 * Function: onEncode
+	 *
+	 * Encodes the child recursively.
+	 */
+	codec.afterEncode = function(enc, obj, node)
+	{
+		enc.encodeCell(obj.root, node);
+		
+		return node;
+	};
+
+	/**
+	 * Function: beforeDecode
+	 *
+	 * Decodes the optional children as cells
+	 * using the respective decoder.
+	 */
+	codec.beforeDecode = function(dec, node, obj)
+	{
+		if (node.firstChild != null &&
+			node.firstChild.nodeType == mxConstants.NODETYPE_ELEMENT)
+		{
+			// Makes sure the original node isn't modified
+			node = node.cloneNode(true);
+			
+			var tmp = node.firstChild;
+			obj.root = dec.decodeCell(tmp, false);
+
+			var tmp2 = tmp.nextSibling;
+			tmp.parentNode.removeChild(tmp);
+			tmp = tmp2;
+		
+			while (tmp != null)
+			{
+				tmp2 = tmp.nextSibling;
+				dec.decodeCell(tmp);
+				tmp.parentNode.removeChild(tmp);
+				tmp = tmp2;
+			}
+		}
+		
+		return node;
+	};
+	
+	/**
+	 * Function: afterDecode
+	 *
+	 * Restores the state by assigning the previous value.
+	 */
+	codec.afterDecode = function(dec, node, obj)
+	{
+		obj.previous = obj.root;
+		
+		return obj;
+	};
+
+	// Returns the codec into the registry
+	return codec;
+
+}());
diff --git a/airavata-kubernetes/workflow-composer/src/js/io/mxStylesheetCodec.js b/airavata-kubernetes/workflow-composer/src/js/io/mxStylesheetCodec.js
new file mode 100644
index 0000000..8899116
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/io/mxStylesheetCodec.js
@@ -0,0 +1,217 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxStylesheetCodec
+ *
+ * Codec for <mxStylesheet>s. This class is created and registered
+ * dynamically at load time and used implicitely via <mxCodec>
+ * and the <mxCodecRegistry>.
+ */
+var mxStylesheetCodec = mxCodecRegistry.register(function()
+{
+	var codec = new mxObjectCodec(new mxStylesheet());
+
+	/**
+	 * Function: encode
+	 *
+	 * Encodes a stylesheet. See <decode> for a description of the
+	 * format.
+	 */
+	codec.encode = function(enc, obj)
+	{
+		var node = enc.document.createElement(this.getName());
+		
+		for (var i in obj.styles)
+		{
+			var style = obj.styles[i];
+			var styleNode = enc.document.createElement('add');
+			
+			if (i != null)
+			{
+				styleNode.setAttribute('as', i);
+				
+				for (var j in style)
+				{
+					var value = this.getStringValue(j, style[j]);
+					
+					if (value != null)
+					{
+						var entry = enc.document.createElement('add');
+						entry.setAttribute('value', value);
+						entry.setAttribute('as', j);
+						styleNode.appendChild(entry);
+					}
+				}
+				
+				if (styleNode.childNodes.length > 0)
+				{
+					node.appendChild(styleNode);
+				}
+			}
+		}
+		
+	    return node;
+	};
+
+	/**
+	 * Function: getStringValue
+	 *
+	 * Returns the string for encoding the given value.
+	 */
+	codec.getStringValue = function(key, value)
+	{
+		var type = typeof(value);
+		
+		if (type == 'function')
+		{
+			value = mxStyleRegistry.getName(style[j]);
+		}
+		else if (type == 'object')
+		{
+			value = null;
+		}
+		
+		return value;
+	};
+	
+	/**
+	 * Function: decode
+	 *
+	 * Reads a sequence of the following child nodes
+	 * and attributes:
+	 *
+	 * Child Nodes:
+	 *
+	 * add - Adds a new style.
+	 *
+	 * Attributes:
+	 *
+	 * as - Name of the style.
+	 * extend - Name of the style to inherit from.
+	 *
+	 * Each node contains another sequence of add and remove nodes with the following
+	 * attributes:
+	 *
+	 * as - Name of the style (see <mxConstants>).
+	 * value - Value for the style.
+	 *
+	 * Instead of the value-attribute, one can put Javascript expressions into
+	 * the node as follows if <mxStylesheetCodec.allowEval> is true:
+	 * <add as="perimeter">mxPerimeter.RectanglePerimeter</add>
+	 *
+	 * A remove node will remove the entry with the name given in the as-attribute
+	 * from the style.
+	 * 
+	 * Example:
+	 *
+	 * (code)
+	 * <mxStylesheet as="stylesheet">
+	 *   <add as="text">
+	 *     <add as="fontSize" value="12"/>
+	 *   </add>
+	 *   <add as="defaultVertex" extend="text">
+	 *     <add as="shape" value="rectangle"/>
+	 *   </add>
+	 * </mxStylesheet>
+	 * (end)
+	 */
+	codec.decode = function(dec, node, into)
+	{
+		var obj = into || new this.template.constructor();
+		var id = node.getAttribute('id');
+		
+		if (id != null)
+		{
+			dec.objects[id] = obj;
+		}
+		
+		node = node.firstChild;
+		
+		while (node != null)
+		{
+			if (!this.processInclude(dec, node, obj) && node.nodeName == 'add')
+			{
+				var as = node.getAttribute('as');
+				
+				if (as != null)
+				{
+					var extend = node.getAttribute('extend');
+					var style = (extend != null) ? mxUtils.clone(obj.styles[extend]) : null;
+					
+					if (style == null)
+					{
+						if (extend != null)
+						{
+							mxLog.warn('mxStylesheetCodec.decode: stylesheet ' +
+								extend + ' not found to extend');
+						}
+						
+						style = new Object();
+					}
+					
+					var entry = node.firstChild;
+					
+					while (entry != null)
+					{
+						if (entry.nodeType == mxConstants.NODETYPE_ELEMENT)
+						{
+						 	var key = entry.getAttribute('as');
+						 	
+						 	if (entry.nodeName == 'add')
+						 	{
+							 	var text = mxUtils.getTextContent(entry);
+							 	var value = null;
+							 	
+							 	if (text != null && text.length > 0 && mxStylesheetCodec.allowEval)
+							 	{
+							 		value = mxUtils.eval(text);
+							 	}
+							 	else
+							 	{
+							 		value = entry.getAttribute('value');
+							 		
+							 		if (mxUtils.isNumeric(value))
+							 		{
+										value = parseFloat(value);
+									}
+							 	}
+
+							 	if (value != null)
+							 	{
+							 		style[key] = value;
+							 	}
+						 	}
+						 	else if (entry.nodeName == 'remove')
+						 	{
+						 		delete style[key];
+						 	}
+						}
+						
+						entry = entry.nextSibling;
+					}
+					
+					obj.putCellStyle(as, style);
+				}
+			}
+			
+			node = node.nextSibling;
+		}
+		
+		return obj;
+	};
+
+	// Returns the codec into the registry
+	return codec;
+
+}());
+
+/**
+ * Variable: allowEval
+ * 
+ * Static global switch that specifies if the use of eval is allowed for
+ * evaluating text content. Default is true. Set this to false if stylesheets
+ * may contain user input.
+ */
+mxStylesheetCodec.allowEval = true;
diff --git a/airavata-kubernetes/workflow-composer/src/js/io/mxTerminalChangeCodec.js b/airavata-kubernetes/workflow-composer/src/js/io/mxTerminalChangeCodec.js
new file mode 100644
index 0000000..b65f47f
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/io/mxTerminalChangeCodec.js
@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+mxCodecRegistry.register(function()
+{
+	/**
+	 * Class: mxTerminalChangeCodec
+	 *
+	 * Codec for <mxTerminalChange>s. This class is created and registered
+	 * dynamically at load time and used implicitely via <mxCodec> and
+	 * the <mxCodecRegistry>.
+	 *
+	 * Transient Fields:
+	 *
+	 * - model
+	 * - previous
+	 *
+	 * Reference Fields:
+	 *
+	 * - cell
+	 * - terminal
+	 */
+	var codec = new mxObjectCodec(new mxTerminalChange(),
+		['model', 'previous'], ['cell', 'terminal']);
+
+	/**
+	 * Function: afterDecode
+	 *
+	 * Restores the state by assigning the previous value.
+	 */
+	codec.afterDecode = function(dec, node, obj)
+	{
+		obj.previous = obj.terminal;
+		
+		return obj;
+	};
+
+	// Returns the codec into the registry
+	return codec;
+
+}());
diff --git a/airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/model/mxGraphAbstractHierarchyCell.js b/airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/model/mxGraphAbstractHierarchyCell.js
new file mode 100644
index 0000000..fd81b0b
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/model/mxGraphAbstractHierarchyCell.js
@@ -0,0 +1,206 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxGraphAbstractHierarchyCell
+ * 
+ * An abstraction of an internal hierarchy node or edge
+ * 
+ * Constructor: mxGraphAbstractHierarchyCell
+ *
+ * Constructs a new hierarchical layout algorithm.
+ *
+ * Arguments:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ * deterministic - Optional boolean that specifies if this layout should be
+ * deterministic. Default is true.
+ */
+function mxGraphAbstractHierarchyCell()
+{
+	this.x = [];
+	this.y = [];
+	this.temp = [];
+};
+
+/**
+ * Variable: maxRank
+ * 
+ * The maximum rank this cell occupies. Default is -1.
+ */
+mxGraphAbstractHierarchyCell.prototype.maxRank = -1;
+
+/**
+ * Variable: minRank
+ * 
+ * The minimum rank this cell occupies. Default is -1.
+ */
+mxGraphAbstractHierarchyCell.prototype.minRank = -1;
+
+/**
+ * Variable: x
+ * 
+ * The x position of this cell for each layer it occupies
+ */
+mxGraphAbstractHierarchyCell.prototype.x = null;
+
+/**
+ * Variable: y
+ * 
+ * The y position of this cell for each layer it occupies
+ */
+mxGraphAbstractHierarchyCell.prototype.y = null;
+
+/**
+ * Variable: width
+ * 
+ * The width of this cell
+ */
+mxGraphAbstractHierarchyCell.prototype.width = 0;
+
+/**
+ * Variable: height
+ * 
+ * The height of this cell
+ */
+mxGraphAbstractHierarchyCell.prototype.height = 0;
+
+/**
+ * Variable: nextLayerConnectedCells
+ * 
+ * A cached version of the cells this cell connects to on the next layer up
+ */
+mxGraphAbstractHierarchyCell.prototype.nextLayerConnectedCells = null;
+
+/**
+ * Variable: previousLayerConnectedCells
+ * 
+ * A cached version of the cells this cell connects to on the next layer down
+ */
+mxGraphAbstractHierarchyCell.prototype.previousLayerConnectedCells = null;
+
+/**
+ * Variable: temp
+ * 
+ * Temporary variable for general use. Generally, try to avoid
+ * carrying information between stages. Currently, the longest
+ * path layering sets temp to the rank position in fixRanks()
+ * and the crossing reduction uses this. This meant temp couldn't
+ * be used for hashing the nodes in the model dfs and so hashCode
+ * was created
+ */
+mxGraphAbstractHierarchyCell.prototype.temp = null;
+
+/**
+ * Function: getNextLayerConnectedCells
+ * 
+ * Returns the cells this cell connects to on the next layer up
+ */
+mxGraphAbstractHierarchyCell.prototype.getNextLayerConnectedCells = function(layer)
+{
+	return null;
+};
+
+/**
+ * Function: getPreviousLayerConnectedCells
+ * 
+ * Returns the cells this cell connects to on the next layer down
+ */
+mxGraphAbstractHierarchyCell.prototype.getPreviousLayerConnectedCells = function(layer)
+{
+	return null;
+};
+
+/**
+ * Function: isEdge
+ * 
+ * Returns whether or not this cell is an edge
+ */
+mxGraphAbstractHierarchyCell.prototype.isEdge = function()
+{
+	return false;
+};
+
+/**
+ * Function: isVertex
+ * 
+ * Returns whether or not this cell is a node
+ */
+mxGraphAbstractHierarchyCell.prototype.isVertex = function()
+{
+	return false;
+};
+
+/**
+ * Function: getGeneralPurposeVariable
+ * 
+ * Gets the value of temp for the specified layer
+ */
+mxGraphAbstractHierarchyCell.prototype.getGeneralPurposeVariable = function(layer)
+{
+	return null;
+};
+
+/**
+ * Function: setGeneralPurposeVariable
+ * 
+ * Set the value of temp for the specified layer
+ */
+mxGraphAbstractHierarchyCell.prototype.setGeneralPurposeVariable = function(layer, value)
+{
+	return null;
+};
+
+/**
+ * Function: setX
+ * 
+ * Set the value of x for the specified layer
+ */
+mxGraphAbstractHierarchyCell.prototype.setX = function(layer, value)
+{
+	if (this.isVertex())
+	{
+		this.x[0] = value;
+	}
+	else if (this.isEdge())
+	{
+		this.x[layer - this.minRank - 1] = value;
+	}
+};
+
+/**
+ * Function: getX
+ * 
+ * Gets the value of x on the specified layer
+ */
+mxGraphAbstractHierarchyCell.prototype.getX = function(layer)
+{
+	if (this.isVertex())
+	{
+		return this.x[0];
+	}
+	else if (this.isEdge())
+	{
+		return this.x[layer - this.minRank - 1];
+	}
+
+	return 0.0;
+};
+
+/**
+ * Function: setY
+ * 
+ * Set the value of y for the specified layer
+ */
+mxGraphAbstractHierarchyCell.prototype.setY = function(layer, value)
+{
+	if (this.isVertex())
+	{
+		this.y[0] = value;
+	}
+	else if (this.isEdge())
+	{
+		this.y[layer -this. minRank - 1] = value;
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/model/mxGraphHierarchyEdge.js b/airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/model/mxGraphHierarchyEdge.js
new file mode 100644
index 0000000..0f81cbb
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/model/mxGraphHierarchyEdge.js
@@ -0,0 +1,187 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxGraphHierarchyEdge
+ * 
+ * An abstraction of a hierarchical edge for the hierarchy layout
+ * 
+ * Constructor: mxGraphHierarchyEdge
+ *
+ * Constructs a hierarchy edge
+ *
+ * Arguments:
+ * 
+ * edges - a list of real graph edges this abstraction represents
+ */
+function mxGraphHierarchyEdge(edges)
+{
+	mxGraphAbstractHierarchyCell.apply(this, arguments);
+	this.edges = edges;
+	this.ids = [];
+	
+	for (var i = 0; i < edges.length; i++)
+	{
+		this.ids.push(mxObjectIdentity.get(edges[i]));
+	}
+};
+
+/**
+ * Extends mxGraphAbstractHierarchyCell.
+ */
+mxGraphHierarchyEdge.prototype = new mxGraphAbstractHierarchyCell();
+mxGraphHierarchyEdge.prototype.constructor = mxGraphHierarchyEdge;
+
+/**
+ * Variable: edges
+ * 
+ * The graph edge(s) this object represents. Parallel edges are all grouped
+ * together within one hierarchy edge.
+ */
+mxGraphHierarchyEdge.prototype.edges = null;
+
+/**
+ * Variable: ids
+ * 
+ * The object identities of the wrapped cells
+ */
+mxGraphHierarchyEdge.prototype.ids = null;
+
+/**
+ * Variable: source
+ * 
+ * The node this edge is sourced at
+ */
+mxGraphHierarchyEdge.prototype.source = null;
+
+/**
+ * Variable: target
+ * 
+ * The node this edge targets
+ */
+mxGraphHierarchyEdge.prototype.target = null;
+
+/**
+ * Variable: isReversed
+ * 
+ * Whether or not the direction of this edge has been reversed
+ * internally to create a DAG for the hierarchical layout
+ */
+mxGraphHierarchyEdge.prototype.isReversed = false;
+
+/**
+ * Function: invert
+ * 
+ * Inverts the direction of this internal edge(s)
+ */
+mxGraphHierarchyEdge.prototype.invert = function(layer)
+{
+	var temp = this.source;
+	this.source = this.target;
+	this.target = temp;
+	this.isReversed = !this.isReversed;
+};
+
+/**
+ * Function: getNextLayerConnectedCells
+ * 
+ * Returns the cells this cell connects to on the next layer up
+ */
+mxGraphHierarchyEdge.prototype.getNextLayerConnectedCells = function(layer)
+{
+	if (this.nextLayerConnectedCells == null)
+	{
+		this.nextLayerConnectedCells = [];
+		
+		for (var i = 0; i < this.temp.length; i++)
+		{
+			this.nextLayerConnectedCells[i] = [];
+			
+			if (i == this.temp.length - 1)
+			{
+				this.nextLayerConnectedCells[i].push(this.source);
+			}
+			else
+			{
+				this.nextLayerConnectedCells[i].push(this);
+			}
+		}
+	}
+	
+	return this.nextLayerConnectedCells[layer - this.minRank - 1];
+};
+
+/**
+ * Function: getPreviousLayerConnectedCells
+ * 
+ * Returns the cells this cell connects to on the next layer down
+ */
+mxGraphHierarchyEdge.prototype.getPreviousLayerConnectedCells = function(layer)
+{
+	if (this.previousLayerConnectedCells == null)
+	{
+		this.previousLayerConnectedCells = [];
+
+		for (var i = 0; i < this.temp.length; i++)
+		{
+			this.previousLayerConnectedCells[i] = [];
+			
+			if (i == 0)
+			{
+				this.previousLayerConnectedCells[i].push(this.target);
+			}
+			else
+			{
+				this.previousLayerConnectedCells[i].push(this);
+			}
+		}
+	}
+
+	return this.previousLayerConnectedCells[layer - this.minRank - 1];
+};
+
+/**
+ * Function: isEdge
+ * 
+ * Returns true.
+ */
+mxGraphHierarchyEdge.prototype.isEdge = function()
+{
+	return true;
+};
+
+/**
+ * Function: getGeneralPurposeVariable
+ * 
+ * Gets the value of temp for the specified layer
+ */
+mxGraphHierarchyEdge.prototype.getGeneralPurposeVariable = function(layer)
+{
+	return this.temp[layer - this.minRank - 1];
+};
+
+/**
+ * Function: setGeneralPurposeVariable
+ * 
+ * Set the value of temp for the specified layer
+ */
+mxGraphHierarchyEdge.prototype.setGeneralPurposeVariable = function(layer, value)
+{
+	this.temp[layer - this.minRank - 1] = value;
+};
+
+/**
+ * Function: getCoreCell
+ * 
+ * Gets the first core edge associated with this wrapper
+ */
+mxGraphHierarchyEdge.prototype.getCoreCell = function()
+{
+	if (this.edges != null && this.edges.length > 0)
+	{
+		return this.edges[0];
+	}
+	
+	return null;
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/model/mxGraphHierarchyModel.js b/airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/model/mxGraphHierarchyModel.js
new file mode 100644
index 0000000..de37e09
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/model/mxGraphHierarchyModel.js
@@ -0,0 +1,681 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxGraphHierarchyModel
+ *
+ * Internal model of a hierarchical graph. This model stores nodes and edges
+ * equivalent to the real graph nodes and edges, but also stores the rank of the
+ * cells, the order within the ranks and the new candidate locations of cells.
+ * The internal model also reverses edge direction were appropriate , ignores
+ * self-loop and groups parallels together under one edge object.
+ *
+ * Constructor: mxGraphHierarchyModel
+ *
+ * Creates an internal ordered graph model using the vertices passed in. If
+ * there are any, leftward edge need to be inverted in the internal model
+ *
+ * Arguments:
+ *
+ * graph - the facade describing the graph to be operated on
+ * vertices - the vertices for this hierarchy
+ * ordered - whether or not the vertices are already ordered
+ * deterministic - whether or not this layout should be deterministic on each
+ * tightenToSource - whether or not to tighten vertices towards the sources
+ * scanRanksFromSinks - Whether rank assignment is from the sinks or sources.
+ * usage
+ */
+function mxGraphHierarchyModel(layout, vertices, roots, parent, tightenToSource)
+{
+	var graph = layout.getGraph();
+	this.tightenToSource = tightenToSource;
+	this.roots = roots;
+	this.parent = parent;
+
+	// map of cells to internal cell needed for second run through
+	// to setup the sink of edges correctly
+	this.vertexMapper = new mxDictionary();
+	this.edgeMapper = new mxDictionary();
+	this.maxRank = 0;
+	var internalVertices = [];
+
+	if (vertices == null)
+	{
+		vertices = this.graph.getChildVertices(parent);
+	}
+
+	this.maxRank = this.SOURCESCANSTARTRANK;
+	// map of cells to internal cell needed for second run through
+	// to setup the sink of edges correctly. Guess size by number
+	// of edges is roughly same as number of vertices.
+	this.createInternalCells(layout, vertices, internalVertices);
+
+	// Go through edges set their sink values. Also check the
+	// ordering if and invert edges if necessary
+	for (var i = 0; i < vertices.length; i++)
+	{
+		var edges = internalVertices[i].connectsAsSource;
+
+		for (var j = 0; j < edges.length; j++)
+		{
+			var internalEdge = edges[j];
+			var realEdges = internalEdge.edges;
+
+			// Only need to process the first real edge, since
+			// all the edges connect to the same other vertex
+			if (realEdges != null && realEdges.length > 0)
+			{
+				var realEdge = realEdges[0];
+				var targetCell = layout.getVisibleTerminal(
+						realEdge, false);
+				var internalTargetCell = this.vertexMapper.get(targetCell);
+
+				if (internalVertices[i] == internalTargetCell)
+				{
+					// If there are parallel edges going between two vertices and not all are in the same direction
+					// you can have navigated across one direction when doing the cycle reversal that isn't the same
+					// direction as the first real edge in the array above. When that happens the if above catches
+					// that and we correct the target cell before continuing.
+					// This branch only detects this single case
+					targetCell = layout.getVisibleTerminal(
+							realEdge, true);
+					internalTargetCell = this.vertexMapper.get(targetCell);
+				}
+				
+				if (internalTargetCell != null
+						&& internalVertices[i] != internalTargetCell)
+				{
+					internalEdge.target = internalTargetCell;
+
+					if (internalTargetCell.connectsAsTarget.length == 0)
+					{
+						internalTargetCell.connectsAsTarget = [];
+					}
+
+					if (mxUtils.indexOf(internalTargetCell.connectsAsTarget, internalEdge) < 0)
+					{
+						internalTargetCell.connectsAsTarget.push(internalEdge);
+					}
+				}
+			}
+		}
+
+		// Use the temp variable in the internal nodes to mark this
+		// internal vertex as having been visited.
+		internalVertices[i].temp[0] = 1;
+	}
+};
+
+/**
+ * Variable: maxRank
+ *
+ * Stores the largest rank number allocated
+ */
+mxGraphHierarchyModel.prototype.maxRank = null;
+
+/**
+ * Variable: vertexMapper
+ *
+ * Map from graph vertices to internal model nodes.
+ */
+mxGraphHierarchyModel.prototype.vertexMapper = null;
+
+/**
+ * Variable: edgeMapper
+ *
+ * Map from graph edges to internal model edges
+ */
+mxGraphHierarchyModel.prototype.edgeMapper = null;
+
+/**
+ * Variable: ranks
+ *
+ * Mapping from rank number to actual rank
+ */
+mxGraphHierarchyModel.prototype.ranks = null;
+
+/**
+ * Variable: roots
+ *
+ * Store of roots of this hierarchy model, these are real graph cells, not
+ * internal cells
+ */
+mxGraphHierarchyModel.prototype.roots = null;
+
+/**
+ * Variable: parent
+ *
+ * The parent cell whose children are being laid out
+ */
+mxGraphHierarchyModel.prototype.parent = null;
+
+/**
+ * Variable: dfsCount
+ *
+ * Count of the number of times the ancestor dfs has been used.
+ */
+mxGraphHierarchyModel.prototype.dfsCount = 0;
+
+/**
+ * Variable: SOURCESCANSTARTRANK
+ *
+ * High value to start source layering scan rank value from.
+ */
+mxGraphHierarchyModel.prototype.SOURCESCANSTARTRANK = 100000000;
+
+/**
+ * Variable: tightenToSource
+ *
+ * Whether or not to tighten the assigned ranks of vertices up towards
+ * the source cells.
+ */
+mxGraphHierarchyModel.prototype.tightenToSource = false;
+
+/**
+ * Function: createInternalCells
+ *
+ * Creates all edges in the internal model
+ *
+ * Parameters:
+ *
+ * layout - Reference to the <mxHierarchicalLayout> algorithm.
+ * vertices - Array of <mxCells> that represent the vertices whom are to
+ * have an internal representation created.
+ * internalVertices - The array of <mxGraphHierarchyNodes> to have their
+ * information filled in using the real vertices.
+ */
+mxGraphHierarchyModel.prototype.createInternalCells = function(layout, vertices, internalVertices)
+{
+	var graph = layout.getGraph();
+
+	// Create internal edges
+	for (var i = 0; i < vertices.length; i++)
+	{
+		internalVertices[i] = new mxGraphHierarchyNode(vertices[i]);
+		this.vertexMapper.put(vertices[i], internalVertices[i]);
+
+		// If the layout is deterministic, order the cells
+		//List outgoingCells = graph.getNeighbours(vertices[i], deterministic);
+		var conns = layout.getEdges(vertices[i]);
+		internalVertices[i].connectsAsSource = [];
+
+		// Create internal edges, but don't do any rank assignment yet
+		// First use the information from the greedy cycle remover to
+		// invert the leftward edges internally
+		for (var j = 0; j < conns.length; j++)
+		{
+			var cell = layout.getVisibleTerminal(conns[j], false);
+
+			// Looking for outgoing edges only
+			if (cell != vertices[i] && layout.graph.model.isVertex(cell) &&
+					!layout.isVertexIgnored(cell))
+			{
+				// We process all edge between this source and its targets
+				// If there are edges going both ways, we need to collect
+				// them all into one internal edges to avoid looping problems
+				// later. We assume this direction (source -> target) is the 
+				// natural direction if at least half the edges are going in
+				// that direction.
+
+				// The check below for edges[0] being in the vertex mapper is
+				// in case we've processed this the other way around
+				// (target -> source) and the number of edges in each direction
+				// are the same. All the graph edges will have been assigned to
+				// an internal edge going the other way, so we don't want to 
+				// process them again
+				var undirectedEdges = layout.getEdgesBetween(vertices[i],
+						cell, false);
+				var directedEdges = layout.getEdgesBetween(vertices[i],
+						cell, true);
+				
+				if (undirectedEdges != null &&
+						undirectedEdges.length > 0 &&
+						this.edgeMapper.get(undirectedEdges[0]) == null &&
+						directedEdges.length * 2 >= undirectedEdges.length)
+				{
+					var internalEdge = new mxGraphHierarchyEdge(undirectedEdges);
+
+					for (var k = 0; k < undirectedEdges.length; k++)
+					{
+						var edge = undirectedEdges[k];
+						this.edgeMapper.put(edge, internalEdge);
+
+						// Resets all point on the edge and disables the edge style
+						// without deleting it from the cell style
+						graph.resetEdge(edge);
+
+					    if (layout.disableEdgeStyle)
+					    {
+					    	layout.setEdgeStyleEnabled(edge, false);
+					    	layout.setOrthogonalEdge(edge,true);
+					    }
+					}
+
+					internalEdge.source = internalVertices[i];
+
+					if (mxUtils.indexOf(internalVertices[i].connectsAsSource, internalEdge) < 0)
+					{
+						internalVertices[i].connectsAsSource.push(internalEdge);
+					}
+				}
+			}
+		}
+
+		// Ensure temp variable is cleared from any previous use
+		internalVertices[i].temp[0] = 0;
+	}
+};
+
+/**
+ * Function: initialRank
+ *
+ * Basic determination of minimum layer ranking by working from from sources
+ * or sinks and working through each node in the relevant edge direction.
+ * Starting at the sinks is basically a longest path layering algorithm.
+*/
+mxGraphHierarchyModel.prototype.initialRank = function()
+{
+	var startNodes = [];
+
+	if (this.roots != null)
+	{
+		for (var i = 0; i < this.roots.length; i++)
+		{
+			var internalNode = this.vertexMapper.get(this.roots[i]);
+
+			if (internalNode != null)
+			{
+				startNodes.push(internalNode);
+			}
+		}
+	}
+
+	var internalNodes = this.vertexMapper.getValues();
+	
+	for (var i=0; i < internalNodes.length; i++)
+	{
+		// Mark the node as not having had a layer assigned
+		internalNodes[i].temp[0] = -1;
+	}
+
+	var startNodesCopy = startNodes.slice();
+
+	while (startNodes.length > 0)
+	{
+		var internalNode = startNodes[0];
+		var layerDeterminingEdges;
+		var edgesToBeMarked;
+
+		layerDeterminingEdges = internalNode.connectsAsTarget;
+		edgesToBeMarked = internalNode.connectsAsSource;
+
+		// flag to keep track of whether or not all layer determining
+		// edges have been scanned
+		var allEdgesScanned = true;
+
+		// Work out the layer of this node from the layer determining
+		// edges. The minimum layer number of any node connected by one of
+		// the layer determining edges variable
+		var minimumLayer = this.SOURCESCANSTARTRANK;
+
+		for (var i = 0; i < layerDeterminingEdges.length; i++)
+		{
+			var internalEdge = layerDeterminingEdges[i];
+
+			if (internalEdge.temp[0] == 5270620)
+			{
+				// This edge has been scanned, get the layer of the
+				// node on the other end
+				var otherNode = internalEdge.source;
+				minimumLayer = Math.min(minimumLayer, otherNode.temp[0] - 1);
+			}
+			else
+			{
+				allEdgesScanned = false;
+
+				break;
+			}
+		}
+
+		// If all edge have been scanned, assign the layer, mark all
+		// edges in the other direction and remove from the nodes list
+		if (allEdgesScanned)
+		{
+			internalNode.temp[0] = minimumLayer;
+			this.maxRank = Math.min(this.maxRank, minimumLayer);
+
+			if (edgesToBeMarked != null)
+			{
+				for (var i = 0; i < edgesToBeMarked.length; i++)
+				{
+					var internalEdge = edgesToBeMarked[i];
+
+					// Assign unique stamp ( y/m/d/h )
+					internalEdge.temp[0] = 5270620;
+
+					// Add node on other end of edge to LinkedList of
+					// nodes to be analysed
+					var otherNode = internalEdge.target;
+
+					// Only add node if it hasn't been assigned a layer
+					if (otherNode.temp[0] == -1)
+					{
+						startNodes.push(otherNode);
+
+						// Mark this other node as neither being
+						// unassigned nor assigned so it isn't
+						// added to this list again, but it's
+						// layer isn't used in any calculation.
+						otherNode.temp[0] = -2;
+					}
+				}
+			}
+
+			startNodes.shift();
+		}
+		else
+		{
+			// Not all the edges have been scanned, get to the back of
+			// the class and put the dunces cap on
+			var removedCell = startNodes.shift();
+			startNodes.push(internalNode);
+
+			if (removedCell == internalNode && startNodes.length == 1)
+			{
+				// This is an error condition, we can't get out of
+				// this loop. It could happen for more than one node
+				// but that's a lot harder to detect. Log the error
+				// TODO make log comment
+				break;
+			}
+		}
+	}
+
+	// Normalize the ranks down from their large starting value to place
+	// at least 1 sink on layer 0
+	for (var i=0; i < internalNodes.length; i++)
+	{
+		// Mark the node as not having had a layer assigned
+		internalNodes[i].temp[0] -= this.maxRank;
+	}
+	
+	// Tighten the rank 0 nodes as far as possible
+	for ( var i = 0; i < startNodesCopy.length; i++)
+	{
+		var internalNode = startNodesCopy[i];
+		var currentMaxLayer = 0;
+		var layerDeterminingEdges = internalNode.connectsAsSource;
+
+		for ( var j = 0; j < layerDeterminingEdges.length; j++)
+		{
+			var internalEdge = layerDeterminingEdges[j];
+			var otherNode = internalEdge.target;
+			internalNode.temp[0] = Math.max(currentMaxLayer,
+					otherNode.temp[0] + 1);
+			currentMaxLayer = internalNode.temp[0];
+		}
+	}
+	
+	// Reset the maxRank to that which would be expected for a from-sink
+	// scan
+	this.maxRank = this.SOURCESCANSTARTRANK - this.maxRank;
+};
+
+/**
+ * Function: fixRanks
+ *
+ * Fixes the layer assignments to the values stored in the nodes. Also needs
+ * to create dummy nodes for edges that cross layers.
+ */
+mxGraphHierarchyModel.prototype.fixRanks = function()
+{
+	var rankList = [];
+	this.ranks = [];
+
+	for (var i = 0; i < this.maxRank + 1; i++)
+	{
+		rankList[i] = [];
+		this.ranks[i] = rankList[i];
+	}
+
+	// Perform a DFS to obtain an initial ordering for each rank.
+	// Without doing this you would end up having to process
+	// crossings for a standard tree.
+	var rootsArray = null;
+
+	if (this.roots != null)
+	{
+		var oldRootsArray = this.roots;
+		rootsArray = [];
+
+		for (var i = 0; i < oldRootsArray.length; i++)
+		{
+			var cell = oldRootsArray[i];
+			var internalNode = this.vertexMapper.get(cell);
+			rootsArray[i] = internalNode;
+		}
+	}
+
+	this.visit(function(parent, node, edge, layer, seen)
+	{
+		if (seen == 0 && node.maxRank < 0 && node.minRank < 0)
+		{
+			rankList[node.temp[0]].push(node);
+			node.maxRank = node.temp[0];
+			node.minRank = node.temp[0];
+
+			// Set temp[0] to the nodes position in the rank
+			node.temp[0] = rankList[node.maxRank].length - 1;
+		}
+
+		if (parent != null && edge != null)
+		{
+			var parentToCellRankDifference = parent.maxRank - node.maxRank;
+
+			if (parentToCellRankDifference > 1)
+			{
+				// There are ranks in between the parent and current cell
+				edge.maxRank = parent.maxRank;
+				edge.minRank = node.maxRank;
+				edge.temp = [];
+				edge.x = [];
+				edge.y = [];
+
+				for (var i = edge.minRank + 1; i < edge.maxRank; i++)
+				{
+					// The connecting edge must be added to the
+					// appropriate ranks
+					rankList[i].push(edge);
+					edge.setGeneralPurposeVariable(i, rankList[i]
+							.length - 1);
+				}
+			}
+		}
+	}, rootsArray, false, null);
+};
+
+/**
+ * Function: visit
+ *
+ * A depth first search through the internal heirarchy model.
+ *
+ * Parameters:
+ *
+ * visitor - The visitor function pattern to be called for each node.
+ * trackAncestors - Whether or not the search is to keep track all nodes
+ * directly above this one in the search path.
+ */
+mxGraphHierarchyModel.prototype.visit = function(visitor, dfsRoots, trackAncestors, seenNodes)
+{
+	// Run dfs through on all roots
+	if (dfsRoots != null)
+	{
+		for (var i = 0; i < dfsRoots.length; i++)
+		{
+			var internalNode = dfsRoots[i];
+
+			if (internalNode != null)
+			{
+				if (seenNodes == null)
+				{
+					seenNodes = new Object();
+				}
+
+				if (trackAncestors)
+				{
+					// Set up hash code for root
+					internalNode.hashCode = [];
+					internalNode.hashCode[0] = this.dfsCount;
+					internalNode.hashCode[1] = i;
+					this.extendedDfs(null, internalNode, null, visitor, seenNodes,
+							internalNode.hashCode, i, 0);
+				}
+				else
+				{
+					this.dfs(null, internalNode, null, visitor, seenNodes, 0);
+				}
+			}
+		}
+
+		this.dfsCount++;
+	}
+};
+
+/**
+ * Function: dfs
+ *
+ * Performs a depth first search on the internal hierarchy model
+ *
+ * Parameters:
+ *
+ * parent - the parent internal node of the current internal node
+ * root - the current internal node
+ * connectingEdge - the internal edge connecting the internal node and the parent
+ * internal node, if any
+ * visitor - the visitor pattern to be called for each node
+ * seen - a set of all nodes seen by this dfs a set of all of the
+ * ancestor node of the current node
+ * layer - the layer on the dfs tree ( not the same as the model ranks )
+ */
+mxGraphHierarchyModel.prototype.dfs = function(parent, root, connectingEdge, visitor, seen, layer)
+{
+	if (root != null)
+	{
+		var rootId = root.id;
+
+		if (seen[rootId] == null)
+		{
+			seen[rootId] = root;
+			visitor(parent, root, connectingEdge, layer, 0);
+
+			// Copy the connects as source list so that visitors
+			// can change the original for edge direction inversions
+			var outgoingEdges = root.connectsAsSource.slice();
+			
+			for (var i = 0; i< outgoingEdges.length; i++)
+			{
+				var internalEdge = outgoingEdges[i];
+				var targetNode = internalEdge.target;
+
+				// Root check is O(|roots|)
+				this.dfs(root, targetNode, internalEdge, visitor, seen,
+						layer + 1);
+			}
+		}
+		else
+		{
+			// Use the int field to indicate this node has been seen
+			visitor(parent, root, connectingEdge, layer, 1);
+		}
+	}
+};
+
+/**
+ * Function: extendedDfs
+ *
+ * Performs a depth first search on the internal hierarchy model. This dfs
+ * extends the default version by keeping track of cells ancestors, but it
+ * should be only used when necessary because of it can be computationally
+ * intensive for deep searches.
+ *
+ * Parameters:
+ *
+ * parent - the parent internal node of the current internal node
+ * root - the current internal node
+ * connectingEdge - the internal edge connecting the internal node and the parent
+ * internal node, if any
+ * visitor - the visitor pattern to be called for each node
+ * seen - a set of all nodes seen by this dfs
+ * ancestors - the parent hash code
+ * childHash - the new hash code for this node
+ * layer - the layer on the dfs tree ( not the same as the model ranks )
+ */
+mxGraphHierarchyModel.prototype.extendedDfs = function(parent, root, connectingEdge, visitor, seen, ancestors, childHash, layer)
+{
+	// Explanation of custom hash set. Previously, the ancestors variable
+	// was passed through the dfs as a HashSet. The ancestors were copied
+	// into a new HashSet and when the new child was processed it was also
+	// added to the set. If the current node was in its ancestor list it
+	// meant there is a cycle in the graph and this information is passed
+	// to the visitor.visit() in the seen parameter. The HashSet clone was
+	// very expensive on CPU so a custom hash was developed using primitive
+	// types. temp[] couldn't be used so hashCode[] was added to each node.
+	// Each new child adds another int to the array, copying the prefix
+	// from its parent. Child of the same parent add different ints (the
+	// limit is therefore 2^32 children per parent...). If a node has a
+	// child with the hashCode already set then the child code is compared
+	// to the same portion of the current nodes array. If they match there
+	// is a loop.
+	// Note that the basic mechanism would only allow for 1 use of this
+	// functionality, so the root nodes have two ints. The second int is
+	// incremented through each node root and the first is incremented
+	// through each run of the dfs algorithm (therefore the dfs is not
+	// thread safe). The hash code of each node is set if not already set,
+	// or if the first int does not match that of the current run.
+	if (root != null)
+	{
+		if (parent != null)
+		{
+			// Form this nodes hash code if necessary, that is, if the
+			// hashCode variable has not been initialized or if the
+			// start of the parent hash code does not equal the start of
+			// this nodes hash code, indicating the code was set on a
+			// previous run of this dfs.
+			if (root.hashCode == null ||
+				root.hashCode[0] != parent.hashCode[0])
+			{
+				var hashCodeLength = parent.hashCode.length + 1;
+				root.hashCode = parent.hashCode.slice();
+				root.hashCode[hashCodeLength - 1] = childHash;
+			}
+		}
+
+		var rootId = root.id;
+
+		if (seen[rootId] == null)
+		{
+			seen[rootId] = root;
+			visitor(parent, root, connectingEdge, layer, 0);
+
+			// Copy the connects as source list so that visitors
+			// can change the original for edge direction inversions
+			var outgoingEdges = root.connectsAsSource.slice();
+
+			for (var i = 0; i < outgoingEdges.length; i++)
+			{
+				var internalEdge = outgoingEdges[i];
+				var targetNode = internalEdge.target;
+
+				// Root check is O(|roots|)
+				this.extendedDfs(root, targetNode, internalEdge, visitor, seen,
+						root.hashCode, i, layer + 1);
+			}
+		}
+		else
+		{
+			// Use the int field to indicate this node has been seen
+			visitor(parent, root, connectingEdge, layer, 1);
+		}
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/model/mxGraphHierarchyNode.js b/airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/model/mxGraphHierarchyNode.js
new file mode 100644
index 0000000..1dbb8be
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/model/mxGraphHierarchyNode.js
@@ -0,0 +1,220 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxGraphHierarchyNode
+ * 
+ * An abstraction of a hierarchical edge for the hierarchy layout
+ * 
+ * Constructor: mxGraphHierarchyNode
+ *
+ * Constructs an internal node to represent the specified real graph cell
+ *
+ * Arguments:
+ * 
+ * cell - the real graph cell this node represents
+ */
+function mxGraphHierarchyNode(cell)
+{
+	mxGraphAbstractHierarchyCell.apply(this, arguments);
+	this.cell = cell;
+	this.id = mxObjectIdentity.get(cell);
+	this.connectsAsTarget = [];
+	this.connectsAsSource = [];
+};
+
+/**
+ * Extends mxGraphAbstractHierarchyCell.
+ */
+mxGraphHierarchyNode.prototype = new mxGraphAbstractHierarchyCell();
+mxGraphHierarchyNode.prototype.constructor = mxGraphHierarchyNode;
+
+/**
+ * Variable: cell
+ * 
+ * The graph cell this object represents.
+ */
+mxGraphHierarchyNode.prototype.cell = null;
+
+/**
+ * Variable: id
+ * 
+ * The object identity of the wrapped cell
+ */
+mxGraphHierarchyNode.prototype.id = null;
+
+/**
+ * Variable: connectsAsTarget
+ * 
+ * Collection of hierarchy edges that have this node as a target
+ */
+mxGraphHierarchyNode.prototype.connectsAsTarget = null;
+
+/**
+ * Variable: connectsAsSource
+ * 
+ * Collection of hierarchy edges that have this node as a source
+ */
+mxGraphHierarchyNode.prototype.connectsAsSource = null;
+
+/**
+ * Variable: hashCode
+ * 
+ * Assigns a unique hashcode for each node. Used by the model dfs instead
+ * of copying HashSets
+ */
+mxGraphHierarchyNode.prototype.hashCode = false;
+
+/**
+ * Function: getRankValue
+ * 
+ * Returns the integer value of the layer that this node resides in
+ */
+mxGraphHierarchyNode.prototype.getRankValue = function(layer)
+{
+	return this.maxRank;
+};
+
+/**
+ * Function: getNextLayerConnectedCells
+ * 
+ * Returns the cells this cell connects to on the next layer up
+ */
+mxGraphHierarchyNode.prototype.getNextLayerConnectedCells = function(layer)
+{
+	if (this.nextLayerConnectedCells == null)
+	{
+		this.nextLayerConnectedCells = [];
+		this.nextLayerConnectedCells[0] = [];
+		
+		for (var i = 0; i < this.connectsAsTarget.length; i++)
+		{
+			var edge = this.connectsAsTarget[i];
+
+			if (edge.maxRank == -1 || edge.maxRank == layer + 1)
+			{
+				// Either edge is not in any rank or
+				// no dummy nodes in edge, add node of other side of edge
+				this.nextLayerConnectedCells[0].push(edge.source);
+			}
+			else
+			{
+				// Edge spans at least two layers, add edge
+				this.nextLayerConnectedCells[0].push(edge);
+			}
+		}
+	}
+
+	return this.nextLayerConnectedCells[0];
+};
+
+/**
+ * Function: getPreviousLayerConnectedCells
+ * 
+ * Returns the cells this cell connects to on the next layer down
+ */
+mxGraphHierarchyNode.prototype.getPreviousLayerConnectedCells = function(layer)
+{
+	if (this.previousLayerConnectedCells == null)
+	{
+		this.previousLayerConnectedCells = [];
+		this.previousLayerConnectedCells[0] = [];
+		
+		for (var i = 0; i < this.connectsAsSource.length; i++)
+		{
+			var edge = this.connectsAsSource[i];
+
+			if (edge.minRank == -1 || edge.minRank == layer - 1)
+			{
+				// No dummy nodes in edge, add node of other side of edge
+				this.previousLayerConnectedCells[0].push(edge.target);
+			}
+			else
+			{
+				// Edge spans at least two layers, add edge
+				this.previousLayerConnectedCells[0].push(edge);
+			}
+		}
+	}
+
+	return this.previousLayerConnectedCells[0];
+};
+
+/**
+ * Function: isVertex
+ * 
+ * Returns true.
+ */
+mxGraphHierarchyNode.prototype.isVertex = function()
+{
+	return true;
+};
+
+/**
+ * Function: getGeneralPurposeVariable
+ * 
+ * Gets the value of temp for the specified layer
+ */
+mxGraphHierarchyNode.prototype.getGeneralPurposeVariable = function(layer)
+{
+	return this.temp[0];
+};
+
+/**
+ * Function: setGeneralPurposeVariable
+ * 
+ * Set the value of temp for the specified layer
+ */
+mxGraphHierarchyNode.prototype.setGeneralPurposeVariable = function(layer, value)
+{
+	this.temp[0] = value;
+};
+
+/**
+ * Function: isAncestor
+ */
+mxGraphHierarchyNode.prototype.isAncestor = function(otherNode)
+{
+	// Firstly, the hash code of this node needs to be shorter than the
+	// other node
+	if (otherNode != null && this.hashCode != null && otherNode.hashCode != null
+			&& this.hashCode.length < otherNode.hashCode.length)
+	{
+		if (this.hashCode == otherNode.hashCode)
+		{
+			return true;
+		}
+		
+		if (this.hashCode == null || this.hashCode == null)
+		{
+			return false;
+		}
+		
+		// Secondly, this hash code must match the start of the other
+		// node's hash code. Arrays.equals cannot be used here since
+		// the arrays are different length, and we do not want to
+		// perform another array copy.
+		for (var i = 0; i < this.hashCode.length; i++)
+		{
+			if (this.hashCode[i] != otherNode.hashCode[i])
+			{
+				return false;
+			}
+		}
+
+		return true;
+	}
+
+	return false;
+};
+
+/**
+ * Function: getCoreCell
+ * 
+ * Gets the core vertex associated with this wrapper
+ */
+mxGraphHierarchyNode.prototype.getCoreCell = function()
+{
+	return this.cell;
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/model/mxSwimlaneModel.js b/airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/model/mxSwimlaneModel.js
new file mode 100644
index 0000000..52873c6
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/model/mxSwimlaneModel.js
@@ -0,0 +1,801 @@
+/**
+ * Copyright (c) 2006-2016, JGraph Ltd
+ * Copyright (c) 2006-2016, Gaudenz Alder
+ */
+/**
+ * Class: mxSwimlaneModel
+ *
+ * Internal model of a hierarchical graph. This model stores nodes and edges
+ * equivalent to the real graph nodes and edges, but also stores the rank of the
+ * cells, the order within the ranks and the new candidate locations of cells.
+ * The internal model also reverses edge direction were appropriate , ignores
+ * self-loop and groups parallels together under one edge object.
+ *
+ * Constructor: mxSwimlaneModel
+ *
+ * Creates an internal ordered graph model using the vertices passed in. If
+ * there are any, leftward edge need to be inverted in the internal model
+ *
+ * Arguments:
+ *
+ * graph - the facade describing the graph to be operated on
+ * vertices - the vertices for this hierarchy
+ * ordered - whether or not the vertices are already ordered
+ * deterministic - whether or not this layout should be deterministic on each
+ * tightenToSource - whether or not to tighten vertices towards the sources
+ * scanRanksFromSinks - Whether rank assignment is from the sinks or sources.
+ * usage
+ */
+function mxSwimlaneModel(layout, vertices, roots, parent, tightenToSource)
+{
+	var graph = layout.getGraph();
+	this.tightenToSource = tightenToSource;
+	this.roots = roots;
+	this.parent = parent;
+
+	// map of cells to internal cell needed for second run through
+	// to setup the sink of edges correctly
+	this.vertexMapper = new mxDictionary();
+	this.edgeMapper = new mxDictionary();
+	this.maxRank = 0;
+	var internalVertices = [];
+
+	if (vertices == null)
+	{
+		vertices = this.graph.getChildVertices(parent);
+	}
+
+	this.maxRank = this.SOURCESCANSTARTRANK;
+	// map of cells to internal cell needed for second run through
+	// to setup the sink of edges correctly. Guess size by number
+	// of edges is roughly same as number of vertices.
+	this.createInternalCells(layout, vertices, internalVertices);
+
+	// Go through edges set their sink values. Also check the
+	// ordering if and invert edges if necessary
+	for (var i = 0; i < vertices.length; i++)
+	{
+		var edges = internalVertices[i].connectsAsSource;
+
+		for (var j = 0; j < edges.length; j++)
+		{
+			var internalEdge = edges[j];
+			var realEdges = internalEdge.edges;
+
+			// Only need to process the first real edge, since
+			// all the edges connect to the same other vertex
+			if (realEdges != null && realEdges.length > 0)
+			{
+				var realEdge = realEdges[0];
+				var targetCell = layout.getVisibleTerminal(
+						realEdge, false);
+				var internalTargetCell = this.vertexMapper.get(targetCell);
+
+				if (internalVertices[i] == internalTargetCell)
+				{
+					// If there are parallel edges going between two vertices and not all are in the same direction
+					// you can have navigated across one direction when doing the cycle reversal that isn't the same
+					// direction as the first real edge in the array above. When that happens the if above catches
+					// that and we correct the target cell before continuing.
+					// This branch only detects this single case
+					targetCell = layout.getVisibleTerminal(
+							realEdge, true);
+					internalTargetCell = this.vertexMapper.get(targetCell);
+				}
+
+				if (internalTargetCell != null
+						&& internalVertices[i] != internalTargetCell)
+				{
+					internalEdge.target = internalTargetCell;
+
+					if (internalTargetCell.connectsAsTarget.length == 0)
+					{
+						internalTargetCell.connectsAsTarget = [];
+					}
+
+					if (mxUtils.indexOf(internalTargetCell.connectsAsTarget, internalEdge) < 0)
+					{
+						internalTargetCell.connectsAsTarget.push(internalEdge);
+					}
+				}
+			}
+		}
+
+		// Use the temp variable in the internal nodes to mark this
+		// internal vertex as having been visited.
+		internalVertices[i].temp[0] = 1;
+	}
+};
+
+/**
+ * Variable: maxRank
+ *
+ * Stores the largest rank number allocated
+ */
+mxSwimlaneModel.prototype.maxRank = null;
+
+/**
+ * Variable: vertexMapper
+ *
+ * Map from graph vertices to internal model nodes.
+ */
+mxSwimlaneModel.prototype.vertexMapper = null;
+
+/**
+ * Variable: edgeMapper
+ *
+ * Map from graph edges to internal model edges
+ */
+mxSwimlaneModel.prototype.edgeMapper = null;
+
+/**
+ * Variable: ranks
+ *
+ * Mapping from rank number to actual rank
+ */
+mxSwimlaneModel.prototype.ranks = null;
+
+/**
+ * Variable: roots
+ *
+ * Store of roots of this hierarchy model, these are real graph cells, not
+ * internal cells
+ */
+mxSwimlaneModel.prototype.roots = null;
+
+/**
+ * Variable: parent
+ *
+ * The parent cell whose children are being laid out
+ */
+mxSwimlaneModel.prototype.parent = null;
+
+/**
+ * Variable: dfsCount
+ *
+ * Count of the number of times the ancestor dfs has been used.
+ */
+mxSwimlaneModel.prototype.dfsCount = 0;
+
+/**
+ * Variable: SOURCESCANSTARTRANK
+ *
+ * High value to start source layering scan rank value from.
+ */
+mxSwimlaneModel.prototype.SOURCESCANSTARTRANK = 100000000;
+
+/**
+ * Variable: tightenToSource
+ *
+ * Whether or not to tighten the assigned ranks of vertices up towards
+ * the source cells.
+ */
+mxGraphHierarchyModel.prototype.tightenToSource = false;
+
+/**
+ * Variable: ranksPerGroup
+ *
+ * An array of the number of ranks within each swimlane
+ */
+mxSwimlaneModel.prototype.ranksPerGroup = null;
+
+/**
+ * Function: createInternalCells
+ *
+ * Creates all edges in the internal model
+ *
+ * Parameters:
+ *
+ * layout - Reference to the <mxHierarchicalLayout> algorithm.
+ * vertices - Array of <mxCells> that represent the vertices whom are to
+ * have an internal representation created.
+ * internalVertices - The array of <mxGraphHierarchyNodes> to have their
+ * information filled in using the real vertices.
+ */
+mxSwimlaneModel.prototype.createInternalCells = function(layout, vertices, internalVertices)
+{
+	var graph = layout.getGraph();
+	var swimlanes = layout.swimlanes;
+
+	// Create internal edges
+	for (var i = 0; i < vertices.length; i++)
+	{
+		internalVertices[i] = new mxGraphHierarchyNode(vertices[i]);
+		this.vertexMapper.put(vertices[i], internalVertices[i]);
+		internalVertices[i].swimlaneIndex = -1;
+
+		for (var ii = 0; ii < swimlanes.length; ii++)
+		{
+			if (graph.model.getParent(vertices[i]) == swimlanes[ii])
+			{
+				internalVertices[i].swimlaneIndex = ii;
+				break;
+			}
+		}
+
+		// If the layout is deterministic, order the cells
+		//List outgoingCells = graph.getNeighbours(vertices[i], deterministic);
+		var conns = layout.getEdges(vertices[i]);
+		internalVertices[i].connectsAsSource = [];
+
+		// Create internal edges, but don't do any rank assignment yet
+		// First use the information from the greedy cycle remover to
+		// invert the leftward edges internally
+		for (var j = 0; j < conns.length; j++)
+		{
+			var cell = layout.getVisibleTerminal(conns[j], false);
+
+			// Looking for outgoing edges only
+			if (cell != vertices[i] && layout.graph.model.isVertex(cell) &&
+					!layout.isVertexIgnored(cell))
+			{
+				// We process all edge between this source and its targets
+				// If there are edges going both ways, we need to collect
+				// them all into one internal edges to avoid looping problems
+				// later. We assume this direction (source -> target) is the 
+				// natural direction if at least half the edges are going in
+				// that direction.
+
+				// The check below for edges[0] being in the vertex mapper is
+				// in case we've processed this the other way around
+				// (target -> source) and the number of edges in each direction
+				// are the same. All the graph edges will have been assigned to
+				// an internal edge going the other way, so we don't want to 
+				// process them again
+				var undirectedEdges = layout.getEdgesBetween(vertices[i],
+						cell, false);
+				var directedEdges = layout.getEdgesBetween(vertices[i],
+						cell, true);
+				
+				if (undirectedEdges != null &&
+						undirectedEdges.length > 0 &&
+						this.edgeMapper.get(undirectedEdges[0]) == null &&
+						directedEdges.length * 2 >= undirectedEdges.length)
+				{
+					var internalEdge = new mxGraphHierarchyEdge(undirectedEdges);
+
+					for (var k = 0; k < undirectedEdges.length; k++)
+					{
+						var edge = undirectedEdges[k];
+						this.edgeMapper.put(edge, internalEdge);
+
+						// Resets all point on the edge and disables the edge style
+						// without deleting it from the cell style
+						graph.resetEdge(edge);
+
+					    if (layout.disableEdgeStyle)
+					    {
+					    	layout.setEdgeStyleEnabled(edge, false);
+					    	layout.setOrthogonalEdge(edge,true);
+					    }
+					}
+
+					internalEdge.source = internalVertices[i];
+
+					if (mxUtils.indexOf(internalVertices[i].connectsAsSource, internalEdge) < 0)
+					{
+						internalVertices[i].connectsAsSource.push(internalEdge);
+					}
+				}
+			}
+		}
+
+		// Ensure temp variable is cleared from any previous use
+		internalVertices[i].temp[0] = 0;
+	}
+};
+
+/**
+ * Function: initialRank
+ *
+ * Basic determination of minimum layer ranking by working from from sources
+ * or sinks and working through each node in the relevant edge direction.
+ * Starting at the sinks is basically a longest path layering algorithm.
+*/
+mxSwimlaneModel.prototype.initialRank = function()
+{
+	this.ranksPerGroup = [];
+	
+	var startNodes = [];
+	var seen = new Object();
+
+	if (this.roots != null)
+	{
+		for (var i = 0; i < this.roots.length; i++)
+		{
+			var internalNode = this.vertexMapper.get(this.roots[i]);
+			this.maxChainDfs(null, internalNode, null, seen, 0);
+
+			if (internalNode != null)
+			{
+				startNodes.push(internalNode);
+			}
+		}
+	}
+
+	// Calculate the lower and upper rank bounds of each swimlane
+	var lowerRank = [];
+	var upperRank = [];
+	
+	for (var i = this.ranksPerGroup.length - 1; i >= 0; i--)
+	{
+		if (i == this.ranksPerGroup.length - 1)
+		{
+			lowerRank[i] = 0;
+		}
+		else
+		{
+			lowerRank[i] = upperRank[i+1] + 1;
+		}
+		
+		upperRank[i] = lowerRank[i] + this.ranksPerGroup[i];
+	}
+	
+	this.maxRank = upperRank[0];
+
+	var internalNodes = this.vertexMapper.getValues();
+	
+	for (var i=0; i < internalNodes.length; i++)
+	{
+		// Mark the node as not having had a layer assigned
+		internalNodes[i].temp[0] = -1;
+	}
+
+	var startNodesCopy = startNodes.slice();
+	
+	while (startNodes.length > 0)
+	{
+		var internalNode = startNodes[0];
+		var layerDeterminingEdges;
+		var edgesToBeMarked;
+
+		layerDeterminingEdges = internalNode.connectsAsTarget;
+		edgesToBeMarked = internalNode.connectsAsSource;
+
+		// flag to keep track of whether or not all layer determining
+		// edges have been scanned
+		var allEdgesScanned = true;
+
+		// Work out the layer of this node from the layer determining
+		// edges. The minimum layer number of any node connected by one of
+		// the layer determining edges variable
+		var minimumLayer = upperRank[0];
+
+		for (var i = 0; i < layerDeterminingEdges.length; i++)
+		{
+			var internalEdge = layerDeterminingEdges[i];
+
+			if (internalEdge.temp[0] == 5270620)
+			{
+				// This edge has been scanned, get the layer of the
+				// node on the other end
+				var otherNode = internalEdge.source;
+				minimumLayer = Math.min(minimumLayer, otherNode.temp[0] - 1);
+			}
+			else
+			{
+				allEdgesScanned = false;
+
+				break;
+			}
+		}
+
+		// If all edge have been scanned, assign the layer, mark all
+		// edges in the other direction and remove from the nodes list
+		if (allEdgesScanned)
+		{
+			if (minimumLayer > upperRank[internalNode.swimlaneIndex])
+			{
+				minimumLayer = upperRank[internalNode.swimlaneIndex];
+			}
+
+			internalNode.temp[0] = minimumLayer;
+
+			if (edgesToBeMarked != null)
+			{
+				for (var i = 0; i < edgesToBeMarked.length; i++)
+				{
+					var internalEdge = edgesToBeMarked[i];
+
+					// Assign unique stamp ( y/m/d/h )
+					internalEdge.temp[0] = 5270620;
+
+					// Add node on other end of edge to LinkedList of
+					// nodes to be analysed
+					var otherNode = internalEdge.target;
+
+					// Only add node if it hasn't been assigned a layer
+					if (otherNode.temp[0] == -1)
+					{
+						startNodes.push(otherNode);
+
+						// Mark this other node as neither being
+						// unassigned nor assigned so it isn't
+						// added to this list again, but it's
+						// layer isn't used in any calculation.
+						otherNode.temp[0] = -2;
+					}
+				}
+			}
+
+			startNodes.shift();
+		}
+		else
+		{
+			// Not all the edges have been scanned, get to the back of
+			// the class and put the dunces cap on
+			var removedCell = startNodes.shift();
+			startNodes.push(internalNode);
+
+			if (removedCell == internalNode && startNodes.length == 1)
+			{
+				// This is an error condition, we can't get out of
+				// this loop. It could happen for more than one node
+				// but that's a lot harder to detect. Log the error
+				// TODO make log comment
+				break;
+			}
+		}
+	}
+
+	// Normalize the ranks down from their large starting value to place
+	// at least 1 sink on layer 0
+//	for (var key in this.vertexMapper)
+//	{
+//		var internalNode = this.vertexMapper[key];
+//		// Mark the node as not having had a layer assigned
+//		internalNode.temp[0] -= this.maxRank;
+//	}
+	
+	// Tighten the rank 0 nodes as far as possible
+//	for ( var i = 0; i < startNodesCopy.length; i++)
+//	{
+//		var internalNode = startNodesCopy[i];
+//		var currentMaxLayer = 0;
+//		var layerDeterminingEdges = internalNode.connectsAsSource;
+//
+//		for ( var j = 0; j < layerDeterminingEdges.length; j++)
+//		{
+//			var internalEdge = layerDeterminingEdges[j];
+//			var otherNode = internalEdge.target;
+//			internalNode.temp[0] = Math.max(currentMaxLayer,
+//					otherNode.temp[0] + 1);
+//			currentMaxLayer = internalNode.temp[0];
+//		}
+//	}
+};
+
+/**
+ * Function: maxChainDfs
+ *
+ * Performs a depth first search on the internal hierarchy model. This dfs
+ * extends the default version by keeping track of chains within groups.
+ * Any cycles should be removed prior to running, but previously seen cells
+ * are ignored.
+ *
+ * Parameters:
+ *
+ * parent - the parent internal node of the current internal node
+ * root - the current internal node
+ * connectingEdge - the internal edge connecting the internal node and the parent
+ * internal node, if any
+ * seen - a set of all nodes seen by this dfs
+ * chainCount - the number of edges in the chain of vertices going through
+ * the current swimlane
+ */
+mxSwimlaneModel.prototype.maxChainDfs = function(parent, root, connectingEdge, seen, chainCount)
+{
+	if (root != null)
+	{
+		var rootId = mxCellPath.create(root.cell);
+
+		if (seen[rootId] == null)
+		{
+			seen[rootId] = root;
+			var slIndex = root.swimlaneIndex;
+			
+			if (this.ranksPerGroup[slIndex] == null || this.ranksPerGroup[slIndex] < chainCount)
+			{
+				this.ranksPerGroup[slIndex] = chainCount;
+			}
+
+			// Copy the connects as source list so that visitors
+			// can change the original for edge direction inversions
+			var outgoingEdges = root.connectsAsSource.slice();
+
+			for (var i = 0; i < outgoingEdges.length; i++)
+			{
+				var internalEdge = outgoingEdges[i];
+				var targetNode = internalEdge.target;
+
+				// Only navigate in source->target direction within the same
+				// swimlane, or from a lower index swimlane to a higher one
+				if (root.swimlaneIndex < targetNode.swimlaneIndex)
+				{
+					this.maxChainDfs(root, targetNode, internalEdge, mxUtils.clone(seen, null , true), 0);
+				}
+				else if (root.swimlaneIndex == targetNode.swimlaneIndex)
+				{
+					this.maxChainDfs(root, targetNode, internalEdge, mxUtils.clone(seen, null , true), chainCount + 1);
+				}
+			}
+		}
+	}
+};
+
+/**
+ * Function: fixRanks
+ *
+ * Fixes the layer assignments to the values stored in the nodes. Also needs
+ * to create dummy nodes for edges that cross layers.
+ */
+mxSwimlaneModel.prototype.fixRanks = function()
+{
+	var rankList = [];
+	this.ranks = [];
+
+	for (var i = 0; i < this.maxRank + 1; i++)
+	{
+		rankList[i] = [];
+		this.ranks[i] = rankList[i];
+	}
+
+	// Perform a DFS to obtain an initial ordering for each rank.
+	// Without doing this you would end up having to process
+	// crossings for a standard tree.
+	var rootsArray = null;
+
+	if (this.roots != null)
+	{
+		var oldRootsArray = this.roots;
+		rootsArray = [];
+
+		for (var i = 0; i < oldRootsArray.length; i++)
+		{
+			var cell = oldRootsArray[i];
+			var internalNode = this.vertexMapper.get(cell);
+			rootsArray[i] = internalNode;
+		}
+	}
+
+	this.visit(function(parent, node, edge, layer, seen)
+	{
+		if (seen == 0 && node.maxRank < 0 && node.minRank < 0)
+		{
+			rankList[node.temp[0]].push(node);
+			node.maxRank = node.temp[0];
+			node.minRank = node.temp[0];
+
+			// Set temp[0] to the nodes position in the rank
+			node.temp[0] = rankList[node.maxRank].length - 1;
+		}
+
+		if (parent != null && edge != null)
+		{
+			var parentToCellRankDifference = parent.maxRank - node.maxRank;
+
+			if (parentToCellRankDifference > 1)
+			{
+				// There are ranks in between the parent and current cell
+				edge.maxRank = parent.maxRank;
+				edge.minRank = node.maxRank;
+				edge.temp = [];
+				edge.x = [];
+				edge.y = [];
+
+				for (var i = edge.minRank + 1; i < edge.maxRank; i++)
+				{
+					// The connecting edge must be added to the
+					// appropriate ranks
+					rankList[i].push(edge);
+					edge.setGeneralPurposeVariable(i, rankList[i]
+							.length - 1);
+				}
+			}
+		}
+	}, rootsArray, false, null);
+};
+
+/**
+ * Function: visit
+ *
+ * A depth first search through the internal heirarchy model.
+ *
+ * Parameters:
+ *
+ * visitor - The visitor function pattern to be called for each node.
+ * trackAncestors - Whether or not the search is to keep track all nodes
+ * directly above this one in the search path.
+ */
+mxSwimlaneModel.prototype.visit = function(visitor, dfsRoots, trackAncestors, seenNodes)
+{
+	// Run dfs through on all roots
+	if (dfsRoots != null)
+	{
+		for (var i = 0; i < dfsRoots.length; i++)
+		{
+			var internalNode = dfsRoots[i];
+
+			if (internalNode != null)
+			{
+				if (seenNodes == null)
+				{
+					seenNodes = new Object();
+				}
+
+				if (trackAncestors)
+				{
+					// Set up hash code for root
+					internalNode.hashCode = [];
+					internalNode.hashCode[0] = this.dfsCount;
+					internalNode.hashCode[1] = i;
+					this.extendedDfs(null, internalNode, null, visitor, seenNodes,
+							internalNode.hashCode, i, 0);
+				}
+				else
+				{
+					this.dfs(null, internalNode, null, visitor, seenNodes, 0);
+				}
+			}
+		}
+
+		this.dfsCount++;
+	}
+};
+
+/**
+ * Function: dfs
+ *
+ * Performs a depth first search on the internal hierarchy model
+ *
+ * Parameters:
+ *
+ * parent - the parent internal node of the current internal node
+ * root - the current internal node
+ * connectingEdge - the internal edge connecting the internal node and the parent
+ * internal node, if any
+ * visitor - the visitor pattern to be called for each node
+ * seen - a set of all nodes seen by this dfs a set of all of the
+ * ancestor node of the current node
+ * layer - the layer on the dfs tree ( not the same as the model ranks )
+ */
+mxSwimlaneModel.prototype.dfs = function(parent, root, connectingEdge, visitor, seen, layer)
+{
+	if (root != null)
+	{
+		var rootId = root.id;
+
+		if (seen[rootId] == null)
+		{
+			seen[rootId] = root;
+			visitor(parent, root, connectingEdge, layer, 0);
+
+			// Copy the connects as source list so that visitors
+			// can change the original for edge direction inversions
+			var outgoingEdges = root.connectsAsSource.slice();
+			
+			for (var i = 0; i< outgoingEdges.length; i++)
+			{
+				var internalEdge = outgoingEdges[i];
+				var targetNode = internalEdge.target;
+
+				// Root check is O(|roots|)
+				this.dfs(root, targetNode, internalEdge, visitor, seen,
+						layer + 1);
+			}
+		}
+		else
+		{
+			// Use the int field to indicate this node has been seen
+			visitor(parent, root, connectingEdge, layer, 1);
+		}
+	}
+};
+
+/**
+ * Function: extendedDfs
+ *
+ * Performs a depth first search on the internal hierarchy model. This dfs
+ * extends the default version by keeping track of cells ancestors, but it
+ * should be only used when necessary because of it can be computationally
+ * intensive for deep searches.
+ *
+ * Parameters:
+ *
+ * parent - the parent internal node of the current internal node
+ * root - the current internal node
+ * connectingEdge - the internal edge connecting the internal node and the parent
+ * internal node, if any
+ * visitor - the visitor pattern to be called for each node
+ * seen - a set of all nodes seen by this dfs
+ * ancestors - the parent hash code
+ * childHash - the new hash code for this node
+ * layer - the layer on the dfs tree ( not the same as the model ranks )
+ */
+mxSwimlaneModel.prototype.extendedDfs = function(parent, root, connectingEdge, visitor, seen, ancestors, childHash, layer)
+{
+	// Explanation of custom hash set. Previously, the ancestors variable
+	// was passed through the dfs as a HashSet. The ancestors were copied
+	// into a new HashSet and when the new child was processed it was also
+	// added to the set. If the current node was in its ancestor list it
+	// meant there is a cycle in the graph and this information is passed
+	// to the visitor.visit() in the seen parameter. The HashSet clone was
+	// very expensive on CPU so a custom hash was developed using primitive
+	// types. temp[] couldn't be used so hashCode[] was added to each node.
+	// Each new child adds another int to the array, copying the prefix
+	// from its parent. Child of the same parent add different ints (the
+	// limit is therefore 2^32 children per parent...). If a node has a
+	// child with the hashCode already set then the child code is compared
+	// to the same portion of the current nodes array. If they match there
+	// is a loop.
+	// Note that the basic mechanism would only allow for 1 use of this
+	// functionality, so the root nodes have two ints. The second int is
+	// incremented through each node root and the first is incremented
+	// through each run of the dfs algorithm (therefore the dfs is not
+	// thread safe). The hash code of each node is set if not already set,
+	// or if the first int does not match that of the current run.
+	if (root != null)
+	{
+		if (parent != null)
+		{
+			// Form this nodes hash code if necessary, that is, if the
+			// hashCode variable has not been initialized or if the
+			// start of the parent hash code does not equal the start of
+			// this nodes hash code, indicating the code was set on a
+			// previous run of this dfs.
+			if (root.hashCode == null ||
+				root.hashCode[0] != parent.hashCode[0])
+			{
+				var hashCodeLength = parent.hashCode.length + 1;
+				root.hashCode = parent.hashCode.slice();
+				root.hashCode[hashCodeLength - 1] = childHash;
+			}
+		}
+
+		var rootId = root.id;
+
+		if (seen[rootId] == null)
+		{
+			seen[rootId] = root;
+			visitor(parent, root, connectingEdge, layer, 0);
+
+			// Copy the connects as source list so that visitors
+			// can change the original for edge direction inversions
+			var outgoingEdges = root.connectsAsSource.slice();
+			var incomingEdges = root.connectsAsTarget.slice();
+
+			for (var i = 0; i < outgoingEdges.length; i++)
+			{
+				var internalEdge = outgoingEdges[i];
+				var targetNode = internalEdge.target;
+				
+				// Only navigate in source->target direction within the same
+				// swimlane, or from a lower index swimlane to a higher one
+				if (root.swimlaneIndex <= targetNode.swimlaneIndex)
+				{
+					this.extendedDfs(root, targetNode, internalEdge, visitor, seen,
+							root.hashCode, i, layer + 1);
+				}
+			}
+			
+			for (var i = 0; i < incomingEdges.length; i++)
+			{
+				var internalEdge = incomingEdges[i];
+				var targetNode = internalEdge.source;
+
+				// Only navigate in target->source direction from a lower index 
+				// swimlane to a higher one
+				if (root.swimlaneIndex < targetNode.swimlaneIndex)
+				{
+					this.extendedDfs(root, targetNode, internalEdge, visitor, seen,
+							root.hashCode, i, layer + 1);
+				}
+			}
+		}
+		else
+		{
+			// Use the int field to indicate this node has been seen
+			visitor(parent, root, connectingEdge, layer, 1);
+		}
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/mxHierarchicalLayout.js b/airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/mxHierarchicalLayout.js
new file mode 100644
index 0000000..deb60b5
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/mxHierarchicalLayout.js
@@ -0,0 +1,846 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxHierarchicalLayout
+ * 
+ * A hierarchical layout algorithm.
+ * 
+ * Constructor: mxHierarchicalLayout
+ *
+ * Constructs a new hierarchical layout algorithm.
+ *
+ * Arguments:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ * orientation - Optional constant that defines the orientation of this
+ * layout.
+ * deterministic - Optional boolean that specifies if this layout should be
+ * deterministic. Default is true.
+ */
+function mxHierarchicalLayout(graph, orientation, deterministic)
+{
+	mxGraphLayout.call(this, graph);
+	this.orientation = (orientation != null) ? orientation : mxConstants.DIRECTION_NORTH;
+	this.deterministic = (deterministic != null) ? deterministic : true;
+};
+
+var mxHierarchicalEdgeStyle =
+{
+	ORTHOGONAL: 1,
+	POLYLINE: 2,
+	STRAIGHT: 3,
+	CURVE: 4
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxHierarchicalLayout.prototype = new mxGraphLayout();
+mxHierarchicalLayout.prototype.constructor = mxHierarchicalLayout;
+
+/**
+ * Variable: roots
+ * 
+ * Holds the array of <mxCell> that this layout contains.
+ */
+mxHierarchicalLayout.prototype.roots = null;
+
+/**
+ * Variable: resizeParent
+ * 
+ * Specifies if the parent should be resized after the layout so that it
+ * contains all the child cells. Default is false. See also <parentBorder>.
+ */
+mxHierarchicalLayout.prototype.resizeParent = false;
+
+/**
+ * Variable: maintainParentLocation
+ * 
+ * Specifies if the parent location should be maintained, so that the
+ * top, left corner stays the same before and after execution of
+ * the layout. Default is false for backwards compatibility.
+ */
+mxHierarchicalLayout.prototype.maintainParentLocation = false;
+
+/**
+ * Variable: moveParent
+ * 
+ * Specifies if the parent should be moved if <resizeParent> is enabled.
+ * Default is false.
+ */
+mxHierarchicalLayout.prototype.moveParent = false;
+
+/**
+ * Variable: parentBorder
+ * 
+ * The border to be added around the children if the parent is to be
+ * resized using <resizeParent>. Default is 0.
+ */
+mxHierarchicalLayout.prototype.parentBorder = 0;
+
+/**
+ * Variable: intraCellSpacing
+ * 
+ * The spacing buffer added between cells on the same layer. Default is 30.
+ */
+mxHierarchicalLayout.prototype.intraCellSpacing = 30;
+
+/**
+ * Variable: interRankCellSpacing
+ * 
+ * The spacing buffer added between cell on adjacent layers. Default is 50.
+ */
+mxHierarchicalLayout.prototype.interRankCellSpacing = 100;
+
+/**
+ * Variable: interHierarchySpacing
+ * 
+ * The spacing buffer between unconnected hierarchies. Default is 60.
+ */
+mxHierarchicalLayout.prototype.interHierarchySpacing = 60;
+
+/**
+ * Variable: parallelEdgeSpacing
+ * 
+ * The distance between each parallel edge on each ranks for long edges
+ */
+mxHierarchicalLayout.prototype.parallelEdgeSpacing = 10;
+
+/**
+ * Variable: orientation
+ * 
+ * The position of the root node(s) relative to the laid out graph in.
+ * Default is <mxConstants.DIRECTION_NORTH>.
+ */
+mxHierarchicalLayout.prototype.orientation = mxConstants.DIRECTION_NORTH;
+
+/**
+ * Variable: fineTuning
+ * 
+ * Whether or not to perform local optimisations and iterate multiple times
+ * through the algorithm. Default is true.
+ */
+mxHierarchicalLayout.prototype.fineTuning = true;
+
+/**
+ * 
+ * Variable: tightenToSource
+ * 
+ * Whether or not to tighten the assigned ranks of vertices up towards
+ * the source cells.
+ */
+mxHierarchicalLayout.prototype.tightenToSource = true;
+
+/**
+ * Variable: disableEdgeStyle
+ * 
+ * Specifies if the STYLE_NOEDGESTYLE flag should be set on edges that are
+ * modified by the result. Default is true.
+ */
+mxHierarchicalLayout.prototype.disableEdgeStyle = true;
+
+/**
+ * Variable: traverseAncestors
+ * 
+ * Whether or not to drill into child cells and layout in reverse
+ * group order. This also cause the layout to navigate edges whose 
+ * terminal vertices  * have different parents but are in the same 
+ * ancestry chain
+ */
+mxHierarchicalLayout.prototype.traverseAncestors = true;
+
+/**
+ * Variable: model
+ * 
+ * The internal <mxGraphHierarchyModel> formed of the layout.
+ */
+mxHierarchicalLayout.prototype.model = null;
+
+/**
+ * Variable: edgesSet
+ * 
+ * A cache of edges whose source terminal is the key
+ */
+mxHierarchicalLayout.prototype.edgesCache = null;
+
+/**
+ * Variable: edgesSet
+ * 
+ * A cache of edges whose source terminal is the key
+ */
+mxHierarchicalLayout.prototype.edgeSourceTermCache = null;
+
+/**
+ * Variable: edgesSet
+ * 
+ * A cache of edges whose source terminal is the key
+ */
+mxHierarchicalLayout.prototype.edgesTargetTermCache = null;
+
+/**
+ * Variable: edgeStyle
+ * 
+ * The style to apply between cell layers to edge segments
+ */
+mxHierarchicalLayout.prototype.edgeStyle = mxHierarchicalEdgeStyle.POLYLINE;
+
+/**
+ * Function: getModel
+ * 
+ * Returns the internal <mxGraphHierarchyModel> for this layout algorithm.
+ */
+mxHierarchicalLayout.prototype.getModel = function()
+{
+	return this.model;
+};
+
+/**
+ * Function: execute
+ * 
+ * Executes the layout for the children of the specified parent.
+ * 
+ * Parameters:
+ * 
+ * parent - Parent <mxCell> that contains the children to be laid out.
+ * roots - Optional starting roots of the layout.
+ */
+mxHierarchicalLayout.prototype.execute = function(parent, roots)
+{
+	this.parent = parent;
+	var model = this.graph.model;
+	this.edgesCache = new mxDictionary();
+	this.edgeSourceTermCache = new mxDictionary();
+	this.edgesTargetTermCache = new mxDictionary();
+
+	if (roots != null && !(roots instanceof Array))
+	{
+		roots = [roots];
+	}
+	
+	// If the roots are set and the parent is set, only
+	// use the roots that are some dependent of the that
+	// parent.
+	// If just the root are set, use them as-is
+	// If just the parent is set use it's immediate
+	// children as the initial set
+
+	if (roots == null && parent == null)
+	{
+		// TODO indicate the problem
+		return;
+	}
+	
+	//  Maintaining parent location
+	this.parentX = null;
+	this.parentY = null;
+	
+	if (parent != this.root && model.isVertex(parent) != null && this.maintainParentLocation)
+	{
+		var geo = this.graph.getCellGeometry(parent);
+		
+		if (geo != null)
+		{
+			this.parentX = geo.x;
+			this.parentY = geo.y;
+		}
+	}
+	
+	if (roots != null)
+	{
+		var rootsCopy = [];
+
+		for (var i = 0; i < roots.length; i++)
+		{
+			var ancestor = parent != null ? model.isAncestor(parent, roots[i]) : true;
+			
+			if (ancestor && model.isVertex(roots[i]))
+			{
+				rootsCopy.push(roots[i]);
+			}
+		}
+
+		this.roots = rootsCopy;
+	}
+	
+	model.beginUpdate();
+	try
+	{
+		this.run(parent);
+		
+		if (this.resizeParent && !this.graph.isCellCollapsed(parent))
+		{
+			this.graph.updateGroupBounds([parent], this.parentBorder, this.moveParent);
+		}
+		
+		// Maintaining parent location
+		if (this.parentX != null && this.parentY != null)
+		{
+			var geo = this.graph.getCellGeometry(parent);
+			
+			if (geo != null)
+			{
+				geo = geo.clone();
+				geo.x = this.parentX;
+				geo.y = this.parentY;
+				model.setGeometry(parent, geo);
+			}
+		}
+	}
+	finally
+	{
+		model.endUpdate();
+	}
+};
+
+/**
+ * Function: findRoots
+ * 
+ * Returns all visible children in the given parent which do not have
+ * incoming edges. If the result is empty then the children with the
+ * maximum difference between incoming and outgoing edges are returned.
+ * This takes into account edges that are being promoted to the given
+ * root due to invisible children or collapsed cells.
+ * 
+ * Parameters:
+ * 
+ * parent - <mxCell> whose children should be checked.
+ * vertices - array of vertices to limit search to
+ */
+mxHierarchicalLayout.prototype.findRoots = function(parent, vertices)
+{
+	var roots = [];
+	
+	if (parent != null && vertices != null)
+	{
+		var model = this.graph.model;
+		var best = null;
+		var maxDiff = -100000;
+		
+		for (var i in vertices)
+		{
+			var cell = vertices[i];
+
+			if (model.isVertex(cell) && this.graph.isCellVisible(cell))
+			{
+				var conns = this.getEdges(cell);
+				var fanOut = 0;
+				var fanIn = 0;
+
+				for (var k = 0; k < conns.length; k++)
+				{
+					var src = this.getVisibleTerminal(conns[k], true);
+
+					if (src == cell)
+					{
+						fanOut++;
+					}
+					else
+					{
+						fanIn++;
+					}
+				}
+
+				if (fanIn == 0 && fanOut > 0)
+				{
+					roots.push(cell);
+				}
+
+				var diff = fanOut - fanIn;
+
+				if (diff > maxDiff)
+				{
+					maxDiff = diff;
+					best = cell;
+				}
+			}
+		}
+		
+		if (roots.length == 0 && best != null)
+		{
+			roots.push(best);
+		}
+	}
+	
+	return roots;
+};
+
+/**
+ * Function: getEdges
+ * 
+ * Returns the connected edges for the given cell.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose edges should be returned.
+ */
+mxHierarchicalLayout.prototype.getEdges = function(cell)
+{
+	var cachedEdges = this.edgesCache.get(cell);
+	
+	if (cachedEdges != null)
+	{
+		return cachedEdges;
+	}
+
+	var model = this.graph.model;
+	var edges = [];
+	var isCollapsed = this.graph.isCellCollapsed(cell);
+	var childCount = model.getChildCount(cell);
+
+	for (var i = 0; i < childCount; i++)
+	{
+		var child = model.getChildAt(cell, i);
+
+		if (this.isPort(child))
+		{
+			edges = edges.concat(model.getEdges(child, true, true));
+		}
+		else if (isCollapsed || !this.graph.isCellVisible(child))
+		{
+			edges = edges.concat(model.getEdges(child, true, true));
+		}
+	}
+
+	edges = edges.concat(model.getEdges(cell, true, true));
+	var result = [];
+	
+	for (var i = 0; i < edges.length; i++)
+	{
+		var source = this.getVisibleTerminal(edges[i], true);
+		var target = this.getVisibleTerminal(edges[i], false);
+		
+		if ((source == target) || ((source != target) && ((target == cell && (this.parent == null || this.graph.isValidAncestor(source, this.parent, this.traverseAncestors))) ||
+			(source == cell && (this.parent == null ||
+					this.graph.isValidAncestor(target, this.parent, this.traverseAncestors))))))
+		{
+			result.push(edges[i]);
+		}
+	}
+
+	this.edgesCache.put(cell, result);
+
+	return result;
+};
+
+/**
+ * Function: getVisibleTerminal
+ * 
+ * Helper function to return visible terminal for edge allowing for ports
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> whose edges should be returned.
+ * source - Boolean that specifies whether the source or target terminal is to be returned
+ */
+mxHierarchicalLayout.prototype.getVisibleTerminal = function(edge, source)
+{
+	var terminalCache = this.edgesTargetTermCache;
+	
+	if (source)
+	{
+		terminalCache = this.edgeSourceTermCache;
+	}
+
+	var term = terminalCache.get(edge);
+
+	if (term != null)
+	{
+		return term;
+	}
+
+	var state = this.graph.view.getState(edge);
+	
+	var terminal = (state != null) ? state.getVisibleTerminal(source) : this.graph.view.getVisibleTerminal(edge, source);
+	
+	if (terminal == null)
+	{
+		terminal = (state != null) ? state.getVisibleTerminal(source) : this.graph.view.getVisibleTerminal(edge, source);
+	}
+
+	if (terminal != null)
+	{
+		if (this.isPort(terminal))
+		{
+			terminal = this.graph.model.getParent(terminal);
+		}
+		
+		terminalCache.put(edge, terminal);
+	}
+
+	return terminal;
+};
+
+/**
+ * Function: run
+ * 
+ * The API method used to exercise the layout upon the graph description
+ * and produce a separate description of the vertex position and edge
+ * routing changes made. It runs each stage of the layout that has been
+ * created.
+ */
+mxHierarchicalLayout.prototype.run = function(parent)
+{
+	// Separate out unconnected hierarchies
+	var hierarchyVertices = [];
+	var allVertexSet = [];
+
+	if (this.roots == null && parent != null)
+	{
+		var filledVertexSet = Object();
+		this.filterDescendants(parent, filledVertexSet);
+
+		this.roots = [];
+		var filledVertexSetEmpty = true;
+
+		// Poor man's isSetEmpty
+		for (var key in filledVertexSet)
+		{
+			if (filledVertexSet[key] != null)
+			{
+				filledVertexSetEmpty = false;
+				break;
+			}
+		}
+
+		while (!filledVertexSetEmpty)
+		{
+			var candidateRoots = this.findRoots(parent, filledVertexSet);
+			
+			// If the candidate root is an unconnected group cell, remove it from
+			// the layout. We may need a custom set that holds such groups and forces
+			// them to be processed for resizing and/or moving.
+			
+
+			for (var i = 0; i < candidateRoots.length; i++)
+			{
+				var vertexSet = Object();
+				hierarchyVertices.push(vertexSet);
+
+				this.traverse(candidateRoots[i], true, null, allVertexSet, vertexSet,
+						hierarchyVertices, filledVertexSet);
+			}
+
+			for (var i = 0; i < candidateRoots.length; i++)
+			{
+				this.roots.push(candidateRoots[i]);
+			}
+			
+			filledVertexSetEmpty = true;
+			
+			// Poor man's isSetEmpty
+			for (var key in filledVertexSet)
+			{
+				if (filledVertexSet[key] != null)
+				{
+					filledVertexSetEmpty = false;
+					break;
+				}
+			}
+		}
+	}
+	else
+	{
+		// Find vertex set as directed traversal from roots
+
+		for (var i = 0; i < this.roots.length; i++)
+		{
+			var vertexSet = Object();
+			hierarchyVertices.push(vertexSet);
+
+			this.traverse(this.roots[i], true, null, allVertexSet, vertexSet,
+					hierarchyVertices, null);
+		}
+	}
+
+	// Iterate through the result removing parents who have children in this layout
+	
+	// Perform a layout for each seperate hierarchy
+	// Track initial coordinate x-positioning
+	var initialX = 0;
+
+	for (var i = 0; i < hierarchyVertices.length; i++)
+	{
+		var vertexSet = hierarchyVertices[i];
+		var tmp = [];
+		
+		for (var key in vertexSet)
+		{
+			tmp.push(vertexSet[key]);
+		}
+		
+		this.model = new mxGraphHierarchyModel(this, tmp, this.roots,
+			parent, this.tightenToSource);
+
+		this.cycleStage(parent);
+		this.layeringStage();
+		
+		this.crossingStage(parent);
+		initialX = this.placementStage(initialX, parent);
+	}
+};
+
+/**
+ * Function: filterDescendants
+ * 
+ * Creates an array of descendant cells
+ */
+mxHierarchicalLayout.prototype.filterDescendants = function(cell, result)
+{
+	var model = this.graph.model;
+
+	if (model.isVertex(cell) && cell != this.parent && this.graph.isCellVisible(cell))
+	{
+		result[mxObjectIdentity.get(cell)] = cell;
+	}
+
+	if (this.traverseAncestors || cell == this.parent
+			&& this.graph.isCellVisible(cell))
+	{
+		var childCount = model.getChildCount(cell);
+
+		for (var i = 0; i < childCount; i++)
+		{
+			var child = model.getChildAt(cell, i);
+			
+			// Ignore ports in the layout vertex list, they are dealt with
+			// in the traversal mechanisms
+			if (!this.isPort(child))
+			{
+				this.filterDescendants(child, result);
+			}
+		}
+	}
+};
+
+/**
+ * Function: isPort
+ * 
+ * Returns true if the given cell is a "port", that is, when connecting to
+ * it, its parent is the connecting vertex in terms of graph traversal
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the port.
+ */
+mxHierarchicalLayout.prototype.isPort = function(cell)
+{
+	if (cell.geometry.relative)
+	{
+		return true;
+	}
+	
+	return false;
+};
+
+/**
+ * Function: getEdgesBetween
+ * 
+ * Returns the edges between the given source and target. This takes into
+ * account collapsed and invisible cells and ports.
+ * 
+ * Parameters:
+ * 
+ * source -
+ * target -
+ * directed -
+ */
+mxHierarchicalLayout.prototype.getEdgesBetween = function(source, target, directed)
+{
+	directed = (directed != null) ? directed : false;
+	var edges = this.getEdges(source);
+	var result = [];
+
+	// Checks if the edge is connected to the correct
+	// cell and returns the first match
+	for (var i = 0; i < edges.length; i++)
+	{
+		var src = this.getVisibleTerminal(edges[i], true);
+		var trg = this.getVisibleTerminal(edges[i], false);
+
+		if ((src == source && trg == target) || (!directed && src == target && trg == source))
+		{
+			result.push(edges[i]);
+		}
+	}
+
+	return result;
+};
+
+/**
+ * Traverses the (directed) graph invoking the given function for each
+ * visited vertex and edge. The function is invoked with the current vertex
+ * and the incoming edge as a parameter. This implementation makes sure
+ * each vertex is only visited once. The function may return false if the
+ * traversal should stop at the given vertex.
+ * 
+ * Parameters:
+ * 
+ * vertex - <mxCell> that represents the vertex where the traversal starts.
+ * directed - boolean indicating if edges should only be traversed
+ * from source to target. Default is true.
+ * edge - Optional <mxCell> that represents the incoming edge. This is
+ * null for the first step of the traversal.
+ * allVertices - Array of cell paths for the visited cells.
+ */
+mxHierarchicalLayout.prototype.traverse = function(vertex, directed, edge, allVertices, currentComp,
+											hierarchyVertices, filledVertexSet)
+{
+	if (vertex != null && allVertices != null)
+	{
+		// Has this vertex been seen before in any traversal
+		// And if the filled vertex set is populated, only 
+		// process vertices in that it contains
+		var vertexID = mxObjectIdentity.get(vertex);
+		
+		if ((allVertices[vertexID] == null)
+				&& (filledVertexSet == null ? true : filledVertexSet[vertexID] != null))
+		{
+			if (currentComp[vertexID] == null)
+			{
+				currentComp[vertexID] = vertex;
+			}
+			if (allVertices[vertexID] == null)
+			{
+				allVertices[vertexID] = vertex;
+			}
+
+			if (filledVertexSet !== null)
+			{
+				delete filledVertexSet[vertexID];
+			}
+
+			var edges = this.getEdges(vertex);
+			var edgeIsSource = [];
+
+			for (var i = 0; i < edges.length; i++)
+			{
+				edgeIsSource[i] = (this.getVisibleTerminal(edges[i], true) == vertex);
+			}
+
+			for (var i = 0; i < edges.length; i++)
+			{
+				if (!directed || edgeIsSource[i])
+				{
+					var next = this.getVisibleTerminal(edges[i], !edgeIsSource[i]);
+					
+					// Check whether there are more edges incoming from the target vertex than outgoing
+					// The hierarchical model treats bi-directional parallel edges as being sourced
+					// from the more "sourced" terminal. If the directions are equal in number, the direction
+					// is that of the natural direction from the roots of the layout.
+					// The checks below are slightly more verbose than need be for performance reasons
+					var netCount = 1;
+
+					for (var j = 0; j < edges.length; j++)
+					{
+						if (j == i)
+						{
+							continue;
+						}
+						else
+						{
+							var isSource2 = edgeIsSource[j];
+							var otherTerm = this.getVisibleTerminal(edges[j], !isSource2);
+							
+							if (otherTerm == next)
+							{
+								if (isSource2)
+								{
+									netCount++;
+								}
+								else
+								{
+									netCount--;
+								}
+							}
+						}
+					}
+
+					if (netCount >= 0)
+					{
+						currentComp = this.traverse(next, directed, edges[i], allVertices,
+							currentComp, hierarchyVertices,
+							filledVertexSet);
+					}
+				}
+			}
+		}
+		else
+		{
+			if (currentComp[vertexID] == null)
+			{
+				// We've seen this vertex before, but not in the current component
+				// This component and the one it's in need to be merged
+
+				for (var i = 0; i < hierarchyVertices.length; i++)
+				{
+					var comp = hierarchyVertices[i];
+
+					if (comp[vertexID] != null)
+					{
+						for (var key in comp)
+						{
+							currentComp[key] = comp[key];
+						}
+						
+						// Remove the current component from the hierarchy set
+						hierarchyVertices.splice(i, 1);
+						return currentComp;
+					}
+				}
+			}
+		}
+	}
+	
+	return currentComp;
+};
+
+/**
+ * Function: cycleStage
+ * 
+ * Executes the cycle stage using mxMinimumCycleRemover.
+ */
+mxHierarchicalLayout.prototype.cycleStage = function(parent)
+{
+	var cycleStage = new mxMinimumCycleRemover(this);
+	cycleStage.execute(parent);
+};
+
+/**
+ * Function: layeringStage
+ * 
+ * Implements first stage of a Sugiyama layout.
+ */
+mxHierarchicalLayout.prototype.layeringStage = function()
+{
+	this.model.initialRank();
+	this.model.fixRanks();
+};
+
+/**
+ * Function: crossingStage
+ * 
+ * Executes the crossing stage using mxMedianHybridCrossingReduction.
+ */
+mxHierarchicalLayout.prototype.crossingStage = function(parent)
+{
+	var crossingStage = new mxMedianHybridCrossingReduction(this);
+	crossingStage.execute(parent);
+};
+
+/**
+ * Function: placementStage
+ * 
+ * Executes the placement stage using mxCoordinateAssignment.
+ */
+mxHierarchicalLayout.prototype.placementStage = function(initialX, parent)
+{
+	var placementStage = new mxCoordinateAssignment(this, this.intraCellSpacing,
+			this.interRankCellSpacing, this.orientation, initialX,
+			this.parallelEdgeSpacing);
+	placementStage.fineTuning = this.fineTuning;
+	placementStage.execute(parent);
+	
+	return placementStage.limitX + this.interHierarchySpacing;
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/mxSwimlaneLayout.js b/airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/mxSwimlaneLayout.js
new file mode 100644
index 0000000..669beef
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/mxSwimlaneLayout.js
@@ -0,0 +1,937 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxSwimlaneLayout
+ * 
+ * A hierarchical layout algorithm.
+ * 
+ * Constructor: mxSwimlaneLayout
+ *
+ * Constructs a new hierarchical layout algorithm.
+ *
+ * Arguments:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ * orientation - Optional constant that defines the orientation of this
+ * layout.
+ * deterministic - Optional boolean that specifies if this layout should be
+ * deterministic. Default is true.
+ */
+function mxSwimlaneLayout(graph, orientation, deterministic)
+{
+	mxGraphLayout.call(this, graph);
+	this.orientation = (orientation != null) ? orientation : mxConstants.DIRECTION_NORTH;
+	this.deterministic = (deterministic != null) ? deterministic : true;
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxSwimlaneLayout.prototype = new mxGraphLayout();
+mxSwimlaneLayout.prototype.constructor = mxSwimlaneLayout;
+
+/**
+ * Variable: roots
+ * 
+ * Holds the array of <mxCell> that this layout contains.
+ */
+mxSwimlaneLayout.prototype.roots = null;
+
+/**
+ * Variable: swimlanes
+ * 
+ * Holds the array of <mxCell> of the ordered swimlanes to lay out
+ */
+mxSwimlaneLayout.prototype.swimlanes = null;
+
+/**
+ * Variable: dummyVertices
+ * 
+ * Holds an array of <mxCell> of dummy vertices inserted during the layout
+ * to pad out empty swimlanes
+ */
+mxSwimlaneLayout.prototype.dummyVertices = null;
+
+/**
+ * Variable: dummyVertexWidth
+ * 
+ * The cell width of any dummy vertices inserted
+ */
+mxSwimlaneLayout.prototype.dummyVertexWidth = 50;
+
+/**
+ * Variable: resizeParent
+ * 
+ * Specifies if the parent should be resized after the layout so that it
+ * contains all the child cells. Default is false. See also <parentBorder>.
+ */
+mxSwimlaneLayout.prototype.resizeParent = false;
+
+/**
+ * Variable: maintainParentLocation
+ * 
+ * Specifies if the parent location should be maintained, so that the
+ * top, left corner stays the same before and after execution of
+ * the layout. Default is false for backwards compatibility.
+ */
+mxSwimlaneLayout.prototype.maintainParentLocation = false;
+
+/**
+ * Variable: moveParent
+ * 
+ * Specifies if the parent should be moved if <resizeParent> is enabled.
+ * Default is false.
+ */
+mxSwimlaneLayout.prototype.moveParent = false;
+
+/**
+ * Variable: parentBorder
+ * 
+ * The border to be added around the children if the parent is to be
+ * resized using <resizeParent>. Default is 0.
+ */
+mxSwimlaneLayout.prototype.parentBorder = 30;
+
+/**
+ * Variable: intraCellSpacing
+ * 
+ * The spacing buffer added between cells on the same layer. Default is 30.
+ */
+mxSwimlaneLayout.prototype.intraCellSpacing = 30;
+
+/**
+ * Variable: interRankCellSpacing
+ * 
+ * The spacing buffer added between cell on adjacent layers. Default is 50.
+ */
+mxSwimlaneLayout.prototype.interRankCellSpacing = 100;
+
+/**
+ * Variable: interHierarchySpacing
+ * 
+ * The spacing buffer between unconnected hierarchies. Default is 60.
+ */
+mxSwimlaneLayout.prototype.interHierarchySpacing = 60;
+
+/**
+ * Variable: parallelEdgeSpacing
+ * 
+ * The distance between each parallel edge on each ranks for long edges
+ */
+mxSwimlaneLayout.prototype.parallelEdgeSpacing = 10;
+
+/**
+ * Variable: orientation
+ * 
+ * The position of the root node(s) relative to the laid out graph in.
+ * Default is <mxConstants.DIRECTION_NORTH>.
+ */
+mxSwimlaneLayout.prototype.orientation = mxConstants.DIRECTION_NORTH;
+
+/**
+ * Variable: fineTuning
+ * 
+ * Whether or not to perform local optimisations and iterate multiple times
+ * through the algorithm. Default is true.
+ */
+mxSwimlaneLayout.prototype.fineTuning = true;
+
+/**
+ * 
+ * Variable: tightenToSource
+ * 
+ * Whether or not to tighten the assigned ranks of vertices up towards
+ * the source cells.
+ */
+mxSwimlaneLayout.prototype.tightenToSource = true;
+
+/**
+ * Variable: disableEdgeStyle
+ * 
+ * Specifies if the STYLE_NOEDGESTYLE flag should be set on edges that are
+ * modified by the result. Default is true.
+ */
+mxSwimlaneLayout.prototype.disableEdgeStyle = true;
+
+/**
+ * Variable: traverseAncestors
+ * 
+ * Whether or not to drill into child cells and layout in reverse
+ * group order. This also cause the layout to navigate edges whose 
+ * terminal vertices  * have different parents but are in the same 
+ * ancestry chain
+ */
+mxSwimlaneLayout.prototype.traverseAncestors = true;
+
+/**
+ * Variable: model
+ * 
+ * The internal <mxSwimlaneModel> formed of the layout.
+ */
+mxSwimlaneLayout.prototype.model = null;
+
+/**
+ * Variable: edgesSet
+ * 
+ * A cache of edges whose source terminal is the key
+ */
+mxSwimlaneLayout.prototype.edgesCache = null;
+
+/**
+ * Variable: edgesSet
+ * 
+ * A cache of edges whose source terminal is the key
+ */
+mxHierarchicalLayout.prototype.edgeSourceTermCache = null;
+
+/**
+ * Variable: edgesSet
+ * 
+ * A cache of edges whose source terminal is the key
+ */
+mxHierarchicalLayout.prototype.edgesTargetTermCache = null;
+
+/**
+ * Variable: edgeStyle
+ * 
+ * The style to apply between cell layers to edge segments
+ */
+mxHierarchicalLayout.prototype.edgeStyle = mxHierarchicalEdgeStyle.POLYLINE;
+
+/**
+ * Function: getModel
+ * 
+ * Returns the internal <mxSwimlaneModel> for this layout algorithm.
+ */
+mxSwimlaneLayout.prototype.getModel = function()
+{
+	return this.model;
+};
+
+/**
+ * Function: execute
+ * 
+ * Executes the layout for the children of the specified parent.
+ * 
+ * Parameters:
+ * 
+ * parent - Parent <mxCell> that contains the children to be laid out.
+ * swimlanes - Ordered array of swimlanes to be laid out
+ */
+mxSwimlaneLayout.prototype.execute = function(parent, swimlanes)
+{
+	this.parent = parent;
+	var model = this.graph.model;
+	this.edgesCache = new mxDictionary();
+	this.edgeSourceTermCache = new mxDictionary();
+	this.edgesTargetTermCache = new mxDictionary();
+
+	// If the roots are set and the parent is set, only
+	// use the roots that are some dependent of the that
+	// parent.
+	// If just the root are set, use them as-is
+	// If just the parent is set use it's immediate
+	// children as the initial set
+
+	if (swimlanes == null || swimlanes.length < 1)
+	{
+		// TODO indicate the problem
+		return;
+	}
+
+	if (parent == null)
+	{
+		parent = model.getParent(swimlanes[0]);
+	}
+
+	//  Maintaining parent location
+	this.parentX = null;
+	this.parentY = null;
+	
+	if (parent != this.root && model.isVertex(parent) != null && this.maintainParentLocation)
+	{
+		var geo = this.graph.getCellGeometry(parent);
+		
+		if (geo != null)
+		{
+			this.parentX = geo.x;
+			this.parentY = geo.y;
+		}
+	}
+
+	this.swimlanes = swimlanes;
+	this.dummyVertices = [];
+	// Check the swimlanes all have vertices
+	// in them
+	for (var i = 0; i < swimlanes.length; i++)
+	{
+		var children = this.graph.getChildCells(swimlanes[i]);
+		
+		if (children == null || children.length == 0)
+		{
+			var vertex = this.graph.insertVertex(swimlanes[i], null, null, 0, 0, this.dummyVertexWidth, 0);
+			this.dummyVertices.push(vertex);
+		}
+	}
+	
+	model.beginUpdate();
+	try
+	{
+		this.run(parent);
+		
+		if (this.resizeParent && !this.graph.isCellCollapsed(parent))
+		{
+			this.graph.updateGroupBounds([parent], this.parentBorder, this.moveParent);
+		}
+		
+		// Maintaining parent location
+		if (this.parentX != null && this.parentY != null)
+		{
+			var geo = this.graph.getCellGeometry(parent);
+			
+			if (geo != null)
+			{
+				geo = geo.clone();
+				geo.x = this.parentX;
+				geo.y = this.parentY;
+				model.setGeometry(parent, geo);
+			}
+		}
+
+		this.graph.removeCells(this.dummyVertices);
+	}
+	finally
+	{
+		model.endUpdate();
+	}
+};
+
+/**
+ * Function: updateGroupBounds
+ * 
+ * Updates the bounds of the given array of groups so that it includes
+ * all child vertices.
+ * 
+ */
+mxSwimlaneLayout.prototype.updateGroupBounds = function()
+{
+	// Get all vertices and edge in the layout
+	var cells = [];
+	var model = this.model;
+	
+	for (var key in model.edgeMapper)
+	{
+		var edge = model.edgeMapper[key];
+		
+		for (var i = 0; i < edge.edges.length; i++)
+		{
+			cells.push(edge.edges[i]);
+		}
+	}
+	
+	var layoutBounds = this.graph.getBoundingBoxFromGeometry(cells, true);
+	var childBounds = [];
+
+	for (var i = 0; i < this.swimlanes.length; i++)
+	{
+		var lane = this.swimlanes[i];
+		var geo = this.graph.getCellGeometry(lane);
+		
+		if (geo != null)
+		{
+			var children = this.graph.getChildCells(lane);
+			
+			var size = (this.graph.isSwimlane(lane)) ?
+					this.graph.getStartSize(lane) : new mxRectangle();
+
+			var bounds = this.graph.getBoundingBoxFromGeometry(children);
+			childBounds[i] = bounds;
+			var childrenY = bounds.y + geo.y - size.height - this.parentBorder;
+			var maxChildrenY = bounds.y + geo.y + bounds.height;
+
+			if (layoutBounds == null)
+			{
+				layoutBounds = new mxRectangle(0, childrenY, 0, maxChildrenY - childrenY);
+			}
+			else
+			{
+				layoutBounds.y = Math.min(layoutBounds.y, childrenY);
+				var maxY = Math.max(layoutBounds.y + layoutBounds.height, maxChildrenY);
+				layoutBounds.height = maxY - layoutBounds.y;
+			}
+		}
+	}
+
+	
+	for (var i = 0; i < this.swimlanes.length; i++)
+	{
+		var lane = this.swimlanes[i];
+		var geo = this.graph.getCellGeometry(lane);
+		
+		if (geo != null)
+		{
+			var children = this.graph.getChildCells(lane);
+			
+			var size = (this.graph.isSwimlane(lane)) ?
+					this.graph.getStartSize(lane) : new mxRectangle();
+
+			var newGeo = geo.clone();
+			
+			var leftGroupBorder = (i == 0) ? this.parentBorder : this.interRankCellSpacing/2;
+			newGeo.x += childBounds[i].x - size.width - leftGroupBorder;
+			newGeo.y = newGeo.y + layoutBounds.y - geo.y - this.parentBorder;
+			
+			newGeo.width = childBounds[i].width + size.width + this.interRankCellSpacing/2 + leftGroupBorder;
+			newGeo.height = layoutBounds.height + size.height + 2 * this.parentBorder;
+			
+			this.graph.model.setGeometry(lane, newGeo);
+			this.graph.moveCells(children, -childBounds[i].x + size.width + leftGroupBorder, 
+					geo.y - layoutBounds.y + this.parentBorder);
+		}
+	}
+};
+
+/**
+ * Function: findRoots
+ * 
+ * Returns all visible children in the given parent which do not have
+ * incoming edges. If the result is empty then the children with the
+ * maximum difference between incoming and outgoing edges are returned.
+ * This takes into account edges that are being promoted to the given
+ * root due to invisible children or collapsed cells.
+ * 
+ * Parameters:
+ * 
+ * parent - <mxCell> whose children should be checked.
+ * vertices - array of vertices to limit search to
+ */
+mxSwimlaneLayout.prototype.findRoots = function(parent, vertices)
+{
+	var roots = [];
+	
+	if (parent != null && vertices != null)
+	{
+		var model = this.graph.model;
+		var best = null;
+		var maxDiff = -100000;
+		
+		for (var i in vertices)
+		{
+			var cell = vertices[i];
+
+			if (cell != null && model.isVertex(cell) && this.graph.isCellVisible(cell) && model.isAncestor(parent, cell))
+			{
+				var conns = this.getEdges(cell);
+				var fanOut = 0;
+				var fanIn = 0;
+
+				for (var k = 0; k < conns.length; k++)
+				{
+					var src = this.getVisibleTerminal(conns[k], true);
+
+					if (src == cell)
+					{
+						// Only count connection within this swimlane
+						var other = this.getVisibleTerminal(conns[k], false);
+						
+						if (model.isAncestor(parent, other))
+						{
+							fanOut++;
+						}
+					}
+					else if (model.isAncestor(parent, src))
+					{
+						fanIn++;
+					}
+				}
+
+				if (fanIn == 0 && fanOut > 0)
+				{
+					roots.push(cell);
+				}
+
+				var diff = fanOut - fanIn;
+
+				if (diff > maxDiff)
+				{
+					maxDiff = diff;
+					best = cell;
+				}
+			}
+		}
+		
+		if (roots.length == 0 && best != null)
+		{
+			roots.push(best);
+		}
+	}
+	
+	return roots;
+};
+
+/**
+ * Function: getEdges
+ * 
+ * Returns the connected edges for the given cell.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose edges should be returned.
+ */
+mxSwimlaneLayout.prototype.getEdges = function(cell)
+{
+	var cachedEdges = this.edgesCache.get(cell);
+	
+	if (cachedEdges != null)
+	{
+		return cachedEdges;
+	}
+
+	var model = this.graph.model;
+	var edges = [];
+	var isCollapsed = this.graph.isCellCollapsed(cell);
+	var childCount = model.getChildCount(cell);
+
+	for (var i = 0; i < childCount; i++)
+	{
+		var child = model.getChildAt(cell, i);
+
+		if (this.isPort(child))
+		{
+			edges = edges.concat(model.getEdges(child, true, true));
+		}
+		else if (isCollapsed || !this.graph.isCellVisible(child))
+		{
+			edges = edges.concat(model.getEdges(child, true, true));
+		}
+	}
+
+	edges = edges.concat(model.getEdges(cell, true, true));
+	var result = [];
+	
+	for (var i = 0; i < edges.length; i++)
+	{
+		var source = this.getVisibleTerminal(edges[i], true);
+		var target = this.getVisibleTerminal(edges[i], false);
+		
+		if ((source == target) || ((source != target) && ((target == cell && (this.parent == null || this.graph.isValidAncestor(source, this.parent, this.traverseAncestors))) ||
+			(source == cell && (this.parent == null ||
+					this.graph.isValidAncestor(target, this.parent, this.traverseAncestors))))))
+		{
+			result.push(edges[i]);
+		}
+	}
+
+	this.edgesCache.put(cell, result);
+
+	return result;
+};
+
+/**
+ * Function: getVisibleTerminal
+ * 
+ * Helper function to return visible terminal for edge allowing for ports
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> whose edges should be returned.
+ * source - Boolean that specifies whether the source or target terminal is to be returned
+ */
+mxSwimlaneLayout.prototype.getVisibleTerminal = function(edge, source)
+{
+	var terminalCache = this.edgesTargetTermCache;
+	
+	if (source)
+	{
+		terminalCache = this.edgeSourceTermCache;
+	}
+
+	var term = terminalCache.get(edge);
+
+	if (term != null)
+	{
+		return term;
+	}
+
+	var state = this.graph.view.getState(edge);
+	
+	var terminal = (state != null) ? state.getVisibleTerminal(source) : this.graph.view.getVisibleTerminal(edge, source);
+	
+	if (terminal == null)
+	{
+		terminal = (state != null) ? state.getVisibleTerminal(source) : this.graph.view.getVisibleTerminal(edge, source);
+	}
+
+	if (terminal != null)
+	{
+		if (this.isPort(terminal))
+		{
+			terminal = this.graph.model.getParent(terminal);
+		}
+		
+		terminalCache.put(edge, terminal);
+	}
+
+	return terminal;
+};
+
+/**
+ * Function: run
+ * 
+ * The API method used to exercise the layout upon the graph description
+ * and produce a separate description of the vertex position and edge
+ * routing changes made. It runs each stage of the layout that has been
+ * created.
+ */
+mxSwimlaneLayout.prototype.run = function(parent)
+{
+	// Separate out unconnected hierarchies
+	var hierarchyVertices = [];
+	var allVertexSet = [];
+
+	if (this.swimlanes != null && this.swimlanes.length > 0 && parent != null)
+	{
+		var filledVertexSet = Object();
+		
+		for (var i = 0; i < this.swimlanes.length; i++)
+		{
+			this.filterDescendants(this.swimlanes[i], filledVertexSet);
+		}
+
+		this.roots = [];
+		var filledVertexSetEmpty = true;
+
+		// Poor man's isSetEmpty
+		for (var key in filledVertexSet)
+		{
+			if (filledVertexSet[key] != null)
+			{
+				filledVertexSetEmpty = false;
+				break;
+			}
+		}
+
+		// Only test for candidates in each swimlane in order
+		var laneCounter = 0;
+
+		while (!filledVertexSetEmpty && laneCounter < this.swimlanes.length)
+		{
+			var candidateRoots = this.findRoots(this.swimlanes[laneCounter], filledVertexSet);
+			
+			if (candidateRoots.length == 0)
+			{
+				laneCounter++;
+				continue;
+			}
+			
+			// If the candidate root is an unconnected group cell, remove it from
+			// the layout. We may need a custom set that holds such groups and forces
+			// them to be processed for resizing and/or moving.
+			for (var i = 0; i < candidateRoots.length; i++)
+			{
+				var vertexSet = Object();
+				hierarchyVertices.push(vertexSet);
+
+				this.traverse(candidateRoots[i], true, null, allVertexSet, vertexSet,
+						hierarchyVertices, filledVertexSet, laneCounter);
+			}
+
+			for (var i = 0; i < candidateRoots.length; i++)
+			{
+				this.roots.push(candidateRoots[i]);
+			}
+			
+			filledVertexSetEmpty = true;
+			
+			// Poor man's isSetEmpty
+			for (var key in filledVertexSet)
+			{
+				if (filledVertexSet[key] != null)
+				{
+					filledVertexSetEmpty = false;
+					break;
+				}
+			}
+		}
+	}
+	else
+	{
+		// Find vertex set as directed traversal from roots
+
+		for (var i = 0; i < this.roots.length; i++)
+		{
+			var vertexSet = Object();
+			hierarchyVertices.push(vertexSet);
+
+			this.traverse(this.roots[i], true, null, allVertexSet, vertexSet,
+					hierarchyVertices, null);
+		}
+	}
+
+	var tmp = [];
+	
+	for (var key in allVertexSet)
+	{
+		tmp.push(allVertexSet[key]);
+	}
+	
+	this.model = new mxSwimlaneModel(this, tmp, this.roots,
+		parent, this.tightenToSource);
+
+	this.cycleStage(parent);
+	this.layeringStage();
+	
+	this.crossingStage(parent);
+	initialX = this.placementStage(0, parent);
+};
+
+/**
+ * Function: filterDescendants
+ * 
+ * Creates an array of descendant cells
+ */
+mxSwimlaneLayout.prototype.filterDescendants = function(cell, result)
+{
+	var model = this.graph.model;
+
+	if (model.isVertex(cell) && cell != this.parent && model.getParent(cell) != this.parent && this.graph.isCellVisible(cell))
+	{
+		result[mxObjectIdentity.get(cell)] = cell;
+	}
+
+	if (this.traverseAncestors || cell == this.parent
+			&& this.graph.isCellVisible(cell))
+	{
+		var childCount = model.getChildCount(cell);
+
+		for (var i = 0; i < childCount; i++)
+		{
+			var child = model.getChildAt(cell, i);
+			
+			// Ignore ports in the layout vertex list, they are dealt with
+			// in the traversal mechanisms
+			if (!this.isPort(child))
+			{
+				this.filterDescendants(child, result);
+			}
+		}
+	}
+};
+
+/**
+ * Function: isPort
+ * 
+ * Returns true if the given cell is a "port", that is, when connecting to
+ * it, its parent is the connecting vertex in terms of graph traversal
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the port.
+ */
+mxSwimlaneLayout.prototype.isPort = function(cell)
+{
+	if (cell.geometry.relative)
+	{
+		return true;
+	}
+	
+	return false;
+};
+
+/**
+ * Function: getEdgesBetween
+ * 
+ * Returns the edges between the given source and target. This takes into
+ * account collapsed and invisible cells and ports.
+ * 
+ * Parameters:
+ * 
+ * source -
+ * target -
+ * directed -
+ */
+mxSwimlaneLayout.prototype.getEdgesBetween = function(source, target, directed)
+{
+	directed = (directed != null) ? directed : false;
+	var edges = this.getEdges(source);
+	var result = [];
+
+	// Checks if the edge is connected to the correct
+	// cell and returns the first match
+	for (var i = 0; i < edges.length; i++)
+	{
+		var src = this.getVisibleTerminal(edges[i], true);
+		var trg = this.getVisibleTerminal(edges[i], false);
+
+		if ((src == source && trg == target) || (!directed && src == target && trg == source))
+		{
+			result.push(edges[i]);
+		}
+	}
+
+	return result;
+};
+
+/**
+ * Traverses the (directed) graph invoking the given function for each
+ * visited vertex and edge. The function is invoked with the current vertex
+ * and the incoming edge as a parameter. This implementation makes sure
+ * each vertex is only visited once. The function may return false if the
+ * traversal should stop at the given vertex.
+ * 
+ * Parameters:
+ * 
+ * vertex - <mxCell> that represents the vertex where the traversal starts.
+ * directed - boolean indicating if edges should only be traversed
+ * from source to target. Default is true.
+ * edge - Optional <mxCell> that represents the incoming edge. This is
+ * null for the first step of the traversal.
+ * allVertices - Array of cell paths for the visited cells.
+ * swimlaneIndex - the laid out order index of the swimlane vertex is contained in
+ */
+mxSwimlaneLayout.prototype.traverse = function(vertex, directed, edge, allVertices, currentComp,
+											hierarchyVertices, filledVertexSet, swimlaneIndex)
+{
+	if (vertex != null && allVertices != null)
+	{
+		// Has this vertex been seen before in any traversal
+		// And if the filled vertex set is populated, only 
+		// process vertices in that it contains
+		var vertexID = mxObjectIdentity.get(vertex);
+		
+		if ((allVertices[vertexID] == null)
+				&& (filledVertexSet == null ? true : filledVertexSet[vertexID] != null))
+		{
+			if (currentComp[vertexID] == null)
+			{
+				currentComp[vertexID] = vertex;
+			}
+			if (allVertices[vertexID] == null)
+			{
+				allVertices[vertexID] = vertex;
+			}
+
+			if (filledVertexSet !== null)
+			{
+				delete filledVertexSet[vertexID];
+			}
+
+			var edges = this.getEdges(vertex);
+			var model = this.graph.model;
+
+			for (var i = 0; i < edges.length; i++)
+			{
+				var otherVertex = this.getVisibleTerminal(edges[i], true);
+				var isSource = otherVertex == vertex;
+				
+				if (isSource)
+				{
+					otherVertex = this.getVisibleTerminal(edges[i], false);
+				}
+
+				var otherIndex = 0;
+				// Get the swimlane index of the other terminal
+				for (otherIndex = 0; otherIndex < this.swimlanes.length; otherIndex++)
+				{
+					if (model.isAncestor(this.swimlanes[otherIndex], otherVertex))
+					{
+						break;
+					}
+				}
+				
+				if (otherIndex >= this.swimlanes.length)
+				{
+					continue;
+				}
+
+				// Traverse if the other vertex is within the same swimlane as
+				// as the current vertex, or if the swimlane index of the other
+				// vertex is greater than that of this vertex
+				if ((otherIndex > swimlaneIndex) ||
+						((!directed || isSource) && otherIndex == swimlaneIndex))
+				{
+					currentComp = this.traverse(otherVertex, directed, edges[i], allVertices,
+							currentComp, hierarchyVertices,
+							filledVertexSet, otherIndex);
+				}
+			}
+		}
+		else
+		{
+			if (currentComp[vertexID] == null)
+			{
+				// We've seen this vertex before, but not in the current component
+				// This component and the one it's in need to be merged
+				for (var i = 0; i < hierarchyVertices.length; i++)
+				{
+					var comp = hierarchyVertices[i];
+
+					if (comp[vertexID] != null)
+					{
+						for (var key in comp)
+						{
+							currentComp[key] = comp[key];
+						}
+						
+						// Remove the current component from the hierarchy set
+						hierarchyVertices.splice(i, 1);
+						return currentComp;
+					}
+				}
+			}
+		}
+	}
+	
+	return currentComp;
+};
+
+/**
+ * Function: cycleStage
+ * 
+ * Executes the cycle stage using mxMinimumCycleRemover.
+ */
+mxSwimlaneLayout.prototype.cycleStage = function(parent)
+{
+	var cycleStage = new mxSwimlaneOrdering(this);
+	cycleStage.execute(parent);
+};
+
+/**
+ * Function: layeringStage
+ * 
+ * Implements first stage of a Sugiyama layout.
+ */
+mxSwimlaneLayout.prototype.layeringStage = function()
+{
+	this.model.initialRank();
+	this.model.fixRanks();
+};
+
+/**
+ * Function: crossingStage
+ * 
+ * Executes the crossing stage using mxMedianHybridCrossingReduction.
+ */
+mxSwimlaneLayout.prototype.crossingStage = function(parent)
+{
+	var crossingStage = new mxMedianHybridCrossingReduction(this);
+	crossingStage.execute(parent);
+};
+
+/**
+ * Function: placementStage
+ * 
+ * Executes the placement stage using mxCoordinateAssignment.
+ */
+mxSwimlaneLayout.prototype.placementStage = function(initialX, parent)
+{
+	var placementStage = new mxCoordinateAssignment(this, this.intraCellSpacing,
+			this.interRankCellSpacing, this.orientation, initialX,
+			this.parallelEdgeSpacing);
+	placementStage.fineTuning = this.fineTuning;
+	placementStage.execute(parent);
+	
+	return placementStage.limitX + this.interHierarchySpacing;
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/stage/mxCoordinateAssignment.js b/airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/stage/mxCoordinateAssignment.js
new file mode 100644
index 0000000..70c9bbd
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/stage/mxCoordinateAssignment.js
@@ -0,0 +1,1830 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxCoordinateAssignment
+ * 
+ * Sets the horizontal locations of node and edge dummy nodes on each layer.
+ * Uses median down and up weighings as well as heuristics to straighten edges as
+ * far as possible.
+ * 
+ * Constructor: mxCoordinateAssignment
+ *
+ * Creates a coordinate assignment.
+ * 
+ * Arguments:
+ * 
+ * intraCellSpacing - the minimum buffer between cells on the same rank
+ * interRankCellSpacing - the minimum distance between cells on adjacent ranks
+ * orientation - the position of the root node(s) relative to the graph
+ * initialX - the leftmost coordinate node placement starts at
+ */
+function mxCoordinateAssignment(layout, intraCellSpacing, interRankCellSpacing,
+	orientation, initialX, parallelEdgeSpacing)
+{
+	this.layout = layout;
+	this.intraCellSpacing = intraCellSpacing;
+	this.interRankCellSpacing = interRankCellSpacing;
+	this.orientation = orientation;
+	this.initialX = initialX;
+	this.parallelEdgeSpacing = parallelEdgeSpacing;
+};
+
+/**
+ * Extends mxHierarchicalLayoutStage.
+ */
+mxCoordinateAssignment.prototype = new mxHierarchicalLayoutStage();
+mxCoordinateAssignment.prototype.constructor = mxCoordinateAssignment;
+
+/**
+ * Variable: layout
+ * 
+ * Reference to the enclosing <mxHierarchicalLayout>.
+ */
+mxCoordinateAssignment.prototype.layout = null;
+
+/**
+ * Variable: intraCellSpacing
+ * 
+ * The minimum buffer between cells on the same rank. Default is 30.
+ */
+mxCoordinateAssignment.prototype.intraCellSpacing = 30;
+
+/**
+ * Variable: interRankCellSpacing
+ * 
+ * The minimum distance between cells on adjacent ranks. Default is 10.
+ */
+mxCoordinateAssignment.prototype.interRankCellSpacing = 100;
+
+/**
+ * Variable: parallelEdgeSpacing
+ * 
+ * The distance between each parallel edge on each ranks for long edges.
+ * Default is 10.
+ */
+mxCoordinateAssignment.prototype.parallelEdgeSpacing = 10;
+
+/**
+ * Variable: maxIterations
+ * 
+ * The number of heuristic iterations to run. Default is 8.
+ */
+mxCoordinateAssignment.prototype.maxIterations = 8;
+
+/**
+ * Variable: prefHozEdgeSep
+ * 
+ * The preferred horizontal distance between edges exiting a vertex
+ */
+mxCoordinateAssignment.prototype.prefHozEdgeSep = 5;
+
+/**
+ * Variable: prefVertEdgeOff
+ * 
+ * The preferred vertical offset between edges exiting a vertex
+ */
+mxCoordinateAssignment.prototype.prefVertEdgeOff = 2;
+
+/**
+ * Variable: minEdgeJetty
+ * 
+ * The minimum distance for an edge jetty from a vertex
+ */
+mxCoordinateAssignment.prototype.minEdgeJetty = 12;
+
+/**
+ * Variable: channelBuffer
+ * 
+ * The size of the vertical buffer in the center of inter-rank channels
+ * where edge control points should not be placed
+ */
+mxCoordinateAssignment.prototype.channelBuffer = 4;
+
+/**
+ * Variable: jettyPositions
+ * 
+ * Map of internal edges and (x,y) pair of positions of the start and end jetty
+ * for that edge where it connects to the source and target vertices.
+ * Note this should technically be a WeakHashMap, but since JS does not
+ * have an equivalent, housekeeping must be performed before using.
+ * i.e. check all edges are still in the model and clear the values.
+ * Note that the y co-ord is the offset of the jetty, not the
+ * absolute point
+ */
+mxCoordinateAssignment.prototype.jettyPositions = null;
+
+/**
+ * Variable: orientation
+ * 
+ * The position of the root ( start ) node(s) relative to the rest of the
+ * laid out graph. Default is <mxConstants.DIRECTION_NORTH>.
+ */
+mxCoordinateAssignment.prototype.orientation = mxConstants.DIRECTION_NORTH;
+
+/**
+ * Variable: initialX
+ * 
+ * The minimum x position node placement starts at
+ */
+mxCoordinateAssignment.prototype.initialX = null;
+
+/**
+ * Variable: limitX
+ * 
+ * The maximum x value this positioning lays up to
+ */
+mxCoordinateAssignment.prototype.limitX = null;
+
+/**
+ * Variable: currentXDelta
+ * 
+ * The sum of x-displacements for the current iteration
+ */
+mxCoordinateAssignment.prototype.currentXDelta = null;
+
+/**
+ * Variable: widestRank
+ * 
+ * The rank that has the widest x position
+ */
+mxCoordinateAssignment.prototype.widestRank = null;
+
+/**
+ * Variable: rankTopY
+ * 
+ * Internal cache of top-most values of Y for each rank
+ */
+mxCoordinateAssignment.prototype.rankTopY = null;
+
+/**
+ * Variable: rankBottomY
+ * 
+ * Internal cache of bottom-most value of Y for each rank
+ */
+mxCoordinateAssignment.prototype.rankBottomY = null;
+
+/**
+ * Variable: widestRankValue
+ * 
+ * The X-coordinate of the edge of the widest rank
+ */
+mxCoordinateAssignment.prototype.widestRankValue = null;
+
+/**
+ * Variable: rankWidths
+ * 
+ * The width of all the ranks
+ */
+mxCoordinateAssignment.prototype.rankWidths = null;
+
+/**
+ * Variable: rankY
+ * 
+ * The Y-coordinate of all the ranks
+ */
+mxCoordinateAssignment.prototype.rankY = null;
+
+/**
+ * Variable: fineTuning
+ * 
+ * Whether or not to perform local optimisations and iterate multiple times
+ * through the algorithm. Default is true.
+ */
+mxCoordinateAssignment.prototype.fineTuning = true;
+
+/**
+ * Variable: 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 = Object();
+	var model = this.layout.getModel();
+	this.currentXDelta = 0.0;
+
+	this.initialCoords(this.layout.getGraph(), model);
+	
+//	this.printStatus();
+	
+	if (this.fineTuning)
+	{
+		this.minNode(model);
+	}
+	
+	var bestXDelta = 100000000.0;
+	
+	if (this.fineTuning)
+	{
+		for (var i = 0; i < this.maxIterations; i++)
+		{
+//			this.printStatus();
+		
+			// Median Heuristic
+			if (i != 0)
+			{
+				this.medianPos(i, model);
+				this.minNode(model);
+			}
+			
+			// if the total offset is less for the current positioning,
+			// there are less heavily angled edges and so the current
+			// positioning is used
+			if (this.currentXDelta < bestXDelta)
+			{
+				for (var j = 0; j < model.ranks.length; j++)
+				{
+					var rank = model.ranks[j];
+					
+					for (var k = 0; k < rank.length; k++)
+					{
+						var cell = rank[k];
+						cell.setX(j, cell.getGeneralPurposeVariable(j));
+					}
+				}
+				
+				bestXDelta = this.currentXDelta;
+			}
+			else
+			{
+				// Restore the best positions
+				for (var j = 0; j < model.ranks.length; j++)
+				{
+					var rank = model.ranks[j];
+					
+					for (var k = 0; k < rank.length; k++)
+					{
+						var cell = rank[k];
+						cell.setGeneralPurposeVariable(j, cell.getX(j));
+					}
+				}
+			}
+			
+			this.minPath(this.layout.getGraph(), model);
+			
+			this.currentXDelta = 0;
+		}
+	}
+	
+	this.setCellLocations(this.layout.getGraph(), model);
+};
+
+/**
+ * Function: minNode
+ * 
+ * Performs one median positioning sweep in both directions
+ */
+mxCoordinateAssignment.prototype.minNode = function(model)
+{
+	// Queue all nodes
+	var nodeList = [];
+	
+	// Need to be able to map from cell to cellWrapper
+	var map = new mxDictionary();
+	var rank = [];
+	
+	for (var i = 0; i <= model.maxRank; i++)
+	{
+		rank[i] = model.ranks[i];
+		
+		for (var j = 0; j < rank[i].length; j++)
+		{
+			// Use the weight to store the rank and visited to store whether
+			// or not the cell is in the list
+			var node = rank[i][j];
+			var nodeWrapper = new WeightedCellSorter(node, i);
+			nodeWrapper.rankIndex = j;
+			nodeWrapper.visited = true;
+			nodeList.push(nodeWrapper);
+			
+			map.put(node, nodeWrapper);
+		}
+	}
+	
+	// Set a limit of the maximum number of times we will access the queue
+	// in case a loop appears
+	var maxTries = nodeList.length * 10;
+	var count = 0;
+	
+	// Don't move cell within this value of their median
+	var tolerance = 1;
+	
+	while (nodeList.length > 0 && count <= maxTries)
+	{
+		var cellWrapper = nodeList.shift();
+		var cell = cellWrapper.cell;
+		
+		var rankValue = cellWrapper.weightedValue;
+		var rankIndex = parseInt(cellWrapper.rankIndex);
+		
+		var nextLayerConnectedCells = cell.getNextLayerConnectedCells(rankValue);
+		var previousLayerConnectedCells = cell.getPreviousLayerConnectedCells(rankValue);
+		
+		var numNextLayerConnected = nextLayerConnectedCells.length;
+		var numPreviousLayerConnected = previousLayerConnectedCells.length;
+
+		var medianNextLevel = this.medianXValue(nextLayerConnectedCells,
+				rankValue + 1);
+		var medianPreviousLevel = this.medianXValue(previousLayerConnectedCells,
+				rankValue - 1);
+
+		var numConnectedNeighbours = numNextLayerConnected
+				+ numPreviousLayerConnected;
+		var currentPosition = cell.getGeneralPurposeVariable(rankValue);
+		var cellMedian = currentPosition;
+		
+		if (numConnectedNeighbours > 0)
+		{
+			cellMedian = (medianNextLevel * numNextLayerConnected + medianPreviousLevel
+					* numPreviousLayerConnected)
+					/ numConnectedNeighbours;
+		}
+
+		// Flag storing whether or not position has changed
+		var positionChanged = false;
+		
+		if (cellMedian < currentPosition - tolerance)
+		{
+			if (rankIndex == 0)
+			{
+				cell.setGeneralPurposeVariable(rankValue, cellMedian);
+				positionChanged = true;
+			}
+			else
+			{
+				var leftCell = rank[rankValue][rankIndex - 1];
+				var leftLimit = leftCell
+						.getGeneralPurposeVariable(rankValue);
+				leftLimit = leftLimit + leftCell.width / 2
+						+ this.intraCellSpacing + cell.width / 2;
+
+				if (leftLimit < cellMedian)
+				{
+					cell.setGeneralPurposeVariable(rankValue, cellMedian);
+					positionChanged = true;
+				}
+				else if (leftLimit < cell
+						.getGeneralPurposeVariable(rankValue)
+						- tolerance)
+				{
+					cell.setGeneralPurposeVariable(rankValue, leftLimit);
+					positionChanged = true;
+				}
+			}
+		}
+		else if (cellMedian > currentPosition + tolerance)
+		{
+			var rankSize = rank[rankValue].length;
+			
+			if (rankIndex == rankSize - 1)
+			{
+				cell.setGeneralPurposeVariable(rankValue, cellMedian);
+				positionChanged = true;
+			}
+			else
+			{
+				var rightCell = rank[rankValue][rankIndex + 1];
+				var rightLimit = rightCell
+						.getGeneralPurposeVariable(rankValue);
+				rightLimit = rightLimit - rightCell.width / 2
+						- this.intraCellSpacing - cell.width / 2;
+				
+				if (rightLimit > cellMedian)
+				{
+					cell.setGeneralPurposeVariable(rankValue, cellMedian);
+					positionChanged = true;
+				}
+				else if (rightLimit > cell
+						.getGeneralPurposeVariable(rankValue)
+						+ tolerance)
+				{
+					cell.setGeneralPurposeVariable(rankValue, rightLimit);
+					positionChanged = true;
+				}
+			}
+		}
+		
+		if (positionChanged)
+		{
+			// Add connected nodes to map and list
+			for (var i = 0; i < nextLayerConnectedCells.length; i++)
+			{
+				var connectedCell = nextLayerConnectedCells[i];
+				var connectedCellWrapper = map.get(connectedCell);
+				
+				if (connectedCellWrapper != null)
+				{
+					if (connectedCellWrapper.visited == false)
+					{
+						connectedCellWrapper.visited = true;
+						nodeList.push(connectedCellWrapper);
+					}
+				}
+			}
+
+			// Add connected nodes to map and list
+			for (var i = 0; i < previousLayerConnectedCells.length; i++)
+			{
+				var connectedCell = previousLayerConnectedCells[i];
+				var connectedCellWrapper = map.get(connectedCell);
+
+				if (connectedCellWrapper != null)
+				{
+					if (connectedCellWrapper.visited == false)
+					{
+						connectedCellWrapper.visited = true;
+						nodeList.push(connectedCellWrapper);
+					}
+				}
+			}
+		}
+		
+		cellWrapper.visited = false;
+		count++;
+	}
+};
+
+/**
+ * Function: medianPos
+ * 
+ * Performs one median positioning sweep in one direction
+ * 
+ * Parameters:
+ * 
+ * i - the iteration of the whole process
+ * model - an internal model of the hierarchical layout
+ */
+mxCoordinateAssignment.prototype.medianPos = function(i, model)
+{
+	// Reverse sweep direction each time through this method
+	var downwardSweep = (i % 2 == 0);
+	
+	if (downwardSweep)
+	{
+		for (var j = model.maxRank; j > 0; j--)
+		{
+			this.rankMedianPosition(j - 1, model, j);
+		}
+	}
+	else
+	{
+		for (var j = 0; j < model.maxRank - 1; j++)
+		{
+			this.rankMedianPosition(j + 1, model, j);
+		}
+	}
+};
+
+/**
+ * Function: rankMedianPosition
+ * 
+ * Performs median minimisation over one rank.
+ * 
+ * Parameters:
+ * 
+ * rankValue - the layer number of this rank
+ * model - an internal model of the hierarchical layout
+ * nextRankValue - the layer number whose connected cels are to be laid out
+ * relative to
+ */
+mxCoordinateAssignment.prototype.rankMedianPosition = function(rankValue, model, nextRankValue)
+{
+	var rank = model.ranks[rankValue];
+
+	// Form an array of the order in which the cell are to be processed
+	// , the order is given by the weighted sum of the in or out edges,
+	// depending on whether we're traveling up or down the hierarchy.
+	var weightedValues = [];
+	var cellMap = new Object();
+
+	for (var i = 0; i < rank.length; i++)
+	{
+		var currentCell = rank[i];
+		weightedValues[i] = new WeightedCellSorter();
+		weightedValues[i].cell = currentCell;
+		weightedValues[i].rankIndex = i;
+		cellMap[currentCell.id] = weightedValues[i];
+		var nextLayerConnectedCells = null;
+		
+		if (nextRankValue < rankValue)
+		{
+			nextLayerConnectedCells = currentCell
+					.getPreviousLayerConnectedCells(rankValue);
+		}
+		else
+		{
+			nextLayerConnectedCells = currentCell
+					.getNextLayerConnectedCells(rankValue);
+		}
+
+		// Calculate the weighing based on this node type and those this
+		// node is connected to on the next layer
+		weightedValues[i].weightedValue = this.calculatedWeightedValue(
+				currentCell, nextLayerConnectedCells);
+	}
+
+	weightedValues.sort(WeightedCellSorter.prototype.compare);
+
+	// Set the new position of each node within the rank using
+	// its temp variable
+	
+	for (var i = 0; i < weightedValues.length; i++)
+	{
+		var numConnectionsNextLevel = 0;
+		var cell = weightedValues[i].cell;
+		var nextLayerConnectedCells = null;
+		var medianNextLevel = 0;
+
+		if (nextRankValue < rankValue)
+		{
+			nextLayerConnectedCells = cell.getPreviousLayerConnectedCells(
+					rankValue).slice();
+		}
+		else
+		{
+			nextLayerConnectedCells = cell.getNextLayerConnectedCells(
+					rankValue).slice();
+		}
+
+		if (nextLayerConnectedCells != null)
+		{
+			numConnectionsNextLevel = nextLayerConnectedCells.length;
+			
+			if (numConnectionsNextLevel > 0)
+			{
+				medianNextLevel = this.medianXValue(nextLayerConnectedCells,
+						nextRankValue);
+			}
+			else
+			{
+				// For case of no connections on the next level set the
+				// median to be the current position and try to be
+				// positioned there
+				medianNextLevel = cell.getGeneralPurposeVariable(rankValue);
+			}
+		}
+
+		var leftBuffer = 0.0;
+		var leftLimit = -100000000.0;
+		
+		for (var j = weightedValues[i].rankIndex - 1; j >= 0;)
+		{
+			var weightedValue = cellMap[rank[j].id];
+			
+			if (weightedValue != null)
+			{
+				var leftCell = weightedValue.cell;
+				
+				if (weightedValue.visited)
+				{
+					// The left limit is the right hand limit of that
+					// cell plus any allowance for unallocated cells
+					// in-between
+					leftLimit = leftCell
+							.getGeneralPurposeVariable(rankValue)
+							+ leftCell.width
+							/ 2.0
+							+ this.intraCellSpacing
+							+ leftBuffer + cell.width / 2.0;
+					j = -1;
+				}
+				else
+				{
+					leftBuffer += leftCell.width + this.intraCellSpacing;
+					j--;
+				}
+			}
+		}
+
+		var rightBuffer = 0.0;
+		var rightLimit = 100000000.0;
+		
+		for (var j = weightedValues[i].rankIndex + 1; j < weightedValues.length;)
+		{
+			var weightedValue = cellMap[rank[j].id];
+			
+			if (weightedValue != null)
+			{
+				var rightCell = weightedValue.cell;
+				
+				if (weightedValue.visited)
+				{
+					// The left limit is the right hand limit of that
+					// cell plus any allowance for unallocated cells
+					// in-between
+					rightLimit = rightCell
+							.getGeneralPurposeVariable(rankValue)
+							- rightCell.width
+							/ 2.0
+							- this.intraCellSpacing
+							- rightBuffer - cell.width / 2.0;
+					j = weightedValues.length;
+				}
+				else
+				{
+					rightBuffer += rightCell.width + this.intraCellSpacing;
+					j++;
+				}
+			}
+		}
+		
+		if (medianNextLevel >= leftLimit && medianNextLevel <= rightLimit)
+		{
+			cell.setGeneralPurposeVariable(rankValue, medianNextLevel);
+		}
+		else if (medianNextLevel < leftLimit)
+		{
+			// Couldn't place at median value, place as close to that
+			// value as possible
+			cell.setGeneralPurposeVariable(rankValue, leftLimit);
+			this.currentXDelta += leftLimit - medianNextLevel;
+		}
+		else if (medianNextLevel > rightLimit)
+		{
+			// Couldn't place at median value, place as close to that
+			// value as possible
+			cell.setGeneralPurposeVariable(rankValue, rightLimit);
+			this.currentXDelta += medianNextLevel - rightLimit;
+		}
+
+		weightedValues[i].visited = true;
+	}
+};
+
+/**
+ * Function: calculatedWeightedValue
+ * 
+ * Calculates the priority the specified cell has based on the type of its
+ * cell and the cells it is connected to on the next layer
+ * 
+ * Parameters:
+ * 
+ * currentCell - the cell whose weight is to be calculated
+ * collection - the cells the specified cell is connected to
+ */
+mxCoordinateAssignment.prototype.calculatedWeightedValue = function(currentCell, collection)
+{
+	var totalWeight = 0;
+	
+	for (var i = 0; i < collection.length; i++)
+	{
+		var cell = collection[i];
+
+		if (currentCell.isVertex() && cell.isVertex())
+		{
+			totalWeight++;
+		}
+		else if (currentCell.isEdge() && cell.isEdge())
+		{
+			totalWeight += 8;
+		}
+		else
+		{
+			totalWeight += 2;
+		}
+	}
+
+	return totalWeight;
+};
+
+/**
+ * Function: medianXValue
+ * 
+ * Calculates the median position of the connected cell on the specified
+ * rank
+ * 
+ * Parameters:
+ * 
+ * connectedCells - the cells the candidate connects to on this level
+ * rankValue - the layer number of this rank
+ */
+mxCoordinateAssignment.prototype.medianXValue = function(connectedCells, rankValue)
+{
+	if (connectedCells.length == 0)
+	{
+		return 0;
+	}
+
+	var medianValues = [];
+
+	for (var i = 0; i < connectedCells.length; i++)
+	{
+		medianValues[i] = connectedCells[i].getGeneralPurposeVariable(rankValue);
+	}
+
+	medianValues.sort(function(a,b){return a - b;});
+	
+	if (connectedCells.length % 2 == 1)
+	{
+		// For odd numbers of adjacent vertices return the median
+		return medianValues[Math.floor(connectedCells.length / 2)];
+	}
+	else
+	{
+		var medianPoint = connectedCells.length / 2;
+		var leftMedian = medianValues[medianPoint - 1];
+		var rightMedian = medianValues[medianPoint];
+
+		return ((leftMedian + rightMedian) / 2);
+	}
+};
+
+/**
+ * Function: initialCoords
+ * 
+ * Sets up the layout in an initial positioning. The ranks are all centered
+ * as much as possible along the middle vertex in each rank. The other cells
+ * are then placed as close as possible on either side.
+ * 
+ * Parameters:
+ * 
+ * facade - the facade describing the input graph
+ * model - an internal model of the hierarchical layout
+ */
+mxCoordinateAssignment.prototype.initialCoords = function(facade, model)
+{
+	this.calculateWidestRank(facade, model);
+
+	// Sweep up and down from the widest rank
+	for (var i = this.widestRank; i >= 0; i--)
+	{
+		if (i < model.maxRank)
+		{
+			this.rankCoordinates(i, facade, model);
+		}
+	}
+
+	for (var i = this.widestRank+1; i <= model.maxRank; i++)
+	{
+		if (i > 0)
+		{
+			this.rankCoordinates(i, facade, model);
+		}
+	}
+};
+
+/**
+ * Function: rankCoordinates
+ * 
+ * Sets up the layout in an initial positioning. All the first cells in each
+ * rank are moved to the left and the rest of the rank inserted as close
+ * together as their size and buffering permits. This method works on just
+ * the specified rank.
+ * 
+ * Parameters:
+ * 
+ * rankValue - the current rank being processed
+ * graph - the facade describing the input graph
+ * model - an internal model of the hierarchical layout
+ */
+mxCoordinateAssignment.prototype.rankCoordinates = function(rankValue, graph, model)
+{
+	var rank = model.ranks[rankValue];
+	var maxY = 0.0;
+	var localX = this.initialX + (this.widestRankValue - this.rankWidths[rankValue])
+			/ 2;
+
+	// Store whether or not any of the cells' bounds were unavailable so
+	// to only issue the warning once for all cells
+	var boundsWarning = false;
+	
+	for (var i = 0; i < rank.length; i++)
+	{
+		var node = rank[i];
+		
+		if (node.isVertex())
+		{
+			var bounds = this.layout.getVertexBounds(node.cell);
+
+			if (bounds != null)
+			{
+				if (this.orientation == mxConstants.DIRECTION_NORTH ||
+					this.orientation == mxConstants.DIRECTION_SOUTH)
+				{
+					node.width = bounds.width;
+					node.height = bounds.height;
+				}
+				else
+				{
+					node.width = bounds.height;
+					node.height = bounds.width;
+				}
+			}
+			else
+			{
+				boundsWarning = true;
+			}
+
+			maxY = Math.max(maxY, node.height);
+		}
+		else if (node.isEdge())
+		{
+			// The width is the number of additional parallel edges
+			// time the parallel edge spacing
+			var numEdges = 1;
+
+			if (node.edges != null)
+			{
+				numEdges = node.edges.length;
+			}
+			else
+			{
+				mxLog.warn('edge.edges is null');
+			}
+
+			node.width = (numEdges - 1) * this.parallelEdgeSpacing;
+		}
+
+		// Set the initial x-value as being the best result so far
+		localX += node.width / 2.0;
+		node.setX(rankValue, localX);
+		node.setGeneralPurposeVariable(rankValue, localX);
+		localX += node.width / 2.0;
+		localX += this.intraCellSpacing;
+	}
+
+	if (boundsWarning == true)
+	{
+		mxLog.warn('At least one cell has no bounds');
+	}
+};
+
+/**
+ * Function: calculateWidestRank
+ * 
+ * Calculates the width rank in the hierarchy. Also set the y value of each
+ * rank whilst performing the calculation
+ * 
+ * Parameters:
+ * 
+ * graph - the facade describing the input graph
+ * model - an internal model of the hierarchical layout
+ */
+mxCoordinateAssignment.prototype.calculateWidestRank = function(graph, model)
+{
+	// Starting y co-ordinate
+	var y = -this.interRankCellSpacing;
+	
+	// Track the widest cell on the last rank since the y
+	// difference depends on it
+	var lastRankMaxCellHeight = 0.0;
+	this.rankWidths = [];
+	this.rankY = [];
+
+	for (var rankValue = model.maxRank; rankValue >= 0; rankValue--)
+	{
+		// Keep track of the widest cell on this rank
+		var maxCellHeight = 0.0;
+		var rank = model.ranks[rankValue];
+		var localX = this.initialX;
+
+		// Store whether or not any of the cells' bounds were unavailable so
+		// to only issue the warning once for all cells
+		var boundsWarning = false;
+		
+		for (var i = 0; i < rank.length; i++)
+		{
+			var node = rank[i];
+
+			if (node.isVertex())
+			{
+				var bounds = this.layout.getVertexBounds(node.cell);
+
+				if (bounds != null)
+				{
+					if (this.orientation == mxConstants.DIRECTION_NORTH ||
+						this.orientation == mxConstants.DIRECTION_SOUTH)
+					{
+						node.width = bounds.width;
+						node.height = bounds.height;
+					}
+					else
+					{
+						node.width = bounds.height;
+						node.height = bounds.width;
+					}
+				}
+				else
+				{
+					boundsWarning = true;
+				}
+
+				maxCellHeight = Math.max(maxCellHeight, node.height);
+			}
+			else if (node.isEdge())
+			{
+				// The width is the number of additional parallel edges
+				// time the parallel edge spacing
+				var numEdges = 1;
+
+				if (node.edges != null)
+				{
+					numEdges = node.edges.length;
+				}
+				else
+				{
+					mxLog.warn('edge.edges is null');
+				}
+
+				node.width = (numEdges - 1) * this.parallelEdgeSpacing;
+			}
+
+			// Set the initial x-value as being the best result so far
+			localX += node.width / 2.0;
+			node.setX(rankValue, localX);
+			node.setGeneralPurposeVariable(rankValue, localX);
+			localX += node.width / 2.0;
+			localX += this.intraCellSpacing;
+
+			if (localX > this.widestRankValue)
+			{
+				this.widestRankValue = localX;
+				this.widestRank = rankValue;
+			}
+
+			this.rankWidths[rankValue] = localX;
+		}
+
+		if (boundsWarning == true)
+		{
+			mxLog.warn('At least one cell has no bounds');
+		}
+
+		this.rankY[rankValue] = y;
+		var distanceToNextRank = maxCellHeight / 2.0
+				+ lastRankMaxCellHeight / 2.0 + this.interRankCellSpacing;
+		lastRankMaxCellHeight = maxCellHeight;
+
+		if (this.orientation == mxConstants.DIRECTION_NORTH ||
+			this.orientation == mxConstants.DIRECTION_WEST)
+		{
+			y += distanceToNextRank;
+		}
+		else
+		{
+			y -= distanceToNextRank;
+		}
+
+		for (var i = 0; i < rank.length; i++)
+		{
+			var cell = rank[i];
+			cell.setY(rankValue, y);
+		}
+	}
+};
+
+/**
+ * Function: minPath
+ * 
+ * Straightens out chains of virtual nodes where possibleacade to those stored after this layout
+ * processing step has completed.
+ * 
+ * Parameters:
+ *
+ * graph - the facade describing the input graph
+ * model - an internal model of the hierarchical layout
+ */
+mxCoordinateAssignment.prototype.minPath = function(graph, model)
+{
+	// Work down and up each edge with at least 2 control points
+	// trying to straighten each one out. If the same number of
+	// straight segments are formed in both directions, the 
+	// preferred direction used is the one where the final
+	// control points have the least offset from the connectable 
+	// region of the terminating vertices
+	var edges = model.edgeMapper.getValues();
+	
+	for (var j = 0; j < edges.length; j++)
+	{
+		var cell = edges[j];
+		
+		if (cell.maxRank - cell.minRank - 1 < 1)
+		{
+			continue;
+		}
+
+		// At least two virtual nodes in the edge
+		// Check first whether the edge is already straight
+		var referenceX = cell
+				.getGeneralPurposeVariable(cell.minRank + 1);
+		var edgeStraight = true;
+		var refSegCount = 0;
+		
+		for (var i = cell.minRank + 2; i < cell.maxRank; i++)
+		{
+			var x = cell.getGeneralPurposeVariable(i);
+
+			if (referenceX != x)
+			{
+				edgeStraight = false;
+				referenceX = x;
+			}
+			else
+			{
+				refSegCount++;
+			}
+		}
+
+		if (!edgeStraight)
+		{
+			var upSegCount = 0;
+			var downSegCount = 0;
+			var upXPositions = [];
+			var downXPositions = [];
+
+			var currentX = cell.getGeneralPurposeVariable(cell.minRank + 1);
+
+			for (var i = cell.minRank + 1; i < cell.maxRank - 1; i++)
+			{
+				// Attempt to straight out the control point on the
+				// next segment up with the current control point.
+				var nextX = cell.getX(i + 1);
+
+				if (currentX == nextX)
+				{
+					upXPositions[i - cell.minRank - 1] = currentX;
+					upSegCount++;
+				}
+				else if (this.repositionValid(model, cell, i + 1, currentX))
+				{
+					upXPositions[i - cell.minRank - 1] = currentX;
+					upSegCount++;
+					// Leave currentX at same value
+				}
+				else
+				{
+					upXPositions[i - cell.minRank - 1] = nextX;
+					currentX = nextX;
+				}				
+			}
+
+			currentX = cell.getX(i);
+
+			for (var i = cell.maxRank - 1; i > cell.minRank + 1; i--)
+			{
+				// Attempt to straight out the control point on the
+				// next segment down with the current control point.
+				var nextX = cell.getX(i - 1);
+
+				if (currentX == nextX)
+				{
+					downXPositions[i - cell.minRank - 2] = currentX;
+					downSegCount++;
+				}
+				else if (this.repositionValid(model, cell, i - 1, currentX))
+				{
+					downXPositions[i - cell.minRank - 2] = currentX;
+					downSegCount++;
+					// Leave currentX at same value
+				}
+				else
+				{
+					downXPositions[i - cell.minRank - 2] = cell.getX(i-1);
+					currentX = nextX;
+				}
+			}
+
+			if (downSegCount > refSegCount || upSegCount > refSegCount)
+			{
+				if (downSegCount >= upSegCount)
+				{
+					// Apply down calculation values
+					for (var i = cell.maxRank - 2; i > cell.minRank; i--)
+					{
+						cell.setX(i, downXPositions[i - cell.minRank - 1]);
+					}
+				}
+				else if (upSegCount > downSegCount)
+				{
+					// Apply up calculation values
+					for (var i = cell.minRank + 2; i < cell.maxRank; i++)
+					{
+						cell.setX(i, upXPositions[i - cell.minRank - 2]);
+					}
+				}
+				else
+				{
+					// Neither direction provided a favourable result
+					// But both calculations are better than the
+					// existing solution, so apply the one with minimal
+					// offset to attached vertices at either end.
+				}
+			}
+		}
+	}
+};
+
+/**
+ * Function: repositionValid
+ * 
+ * Determines whether or not a node may be moved to the specified x 
+ * position on the specified rank
+ * 
+ * Parameters:
+ *
+ * model - the layout model
+ * cell - the cell being analysed
+ * rank - the layer of the cell
+ * position - the x position being sought
+ */
+mxCoordinateAssignment.prototype.repositionValid = function(model, cell, rank, position)
+{
+	var rankArray = model.ranks[rank];
+	var rankIndex = -1;
+
+	for (var i = 0; i < rankArray.length; i++)
+	{
+		if (cell == rankArray[i])
+		{
+			rankIndex = i;
+			break;
+		}
+	}
+
+	if (rankIndex < 0)
+	{
+		return false;
+	}
+
+	var currentX = cell.getGeneralPurposeVariable(rank);
+
+	if (position < currentX)
+	{
+		// Trying to move node to the left.
+		if (rankIndex == 0)
+		{
+			// Left-most node, can move anywhere
+			return true;
+		}
+
+		var leftCell = rankArray[rankIndex - 1];
+		var leftLimit = leftCell.getGeneralPurposeVariable(rank);
+		leftLimit = leftLimit + leftCell.width / 2
+				+ this.intraCellSpacing + cell.width / 2;
+
+		if (leftLimit <= position)
+		{
+			return true;
+		}
+		else
+		{
+			return false;
+		}
+	}
+	else if (position > currentX)
+	{
+		// Trying to move node to the right.
+		if (rankIndex == rankArray.length - 1)
+		{
+			// Right-most node, can move anywhere
+			return true;
+		}
+
+		var rightCell = rankArray[rankIndex + 1];
+		var rightLimit = rightCell.getGeneralPurposeVariable(rank);
+		rightLimit = rightLimit - rightCell.width / 2
+				- this.intraCellSpacing - cell.width / 2;
+
+		if (rightLimit >= position)
+		{
+			return true;
+		}
+		else
+		{
+			return false;
+		}
+	}
+
+	return true;
+};
+
+/**
+ * Function: setCellLocations
+ * 
+ * Sets the cell locations in the facade to those stored after this layout
+ * processing step has completed.
+ * 
+ * Parameters:
+ *
+ * graph - the input graph
+ * model - the layout model
+ */
+mxCoordinateAssignment.prototype.setCellLocations = function(graph, model)
+{
+	this.rankTopY = [];
+	this.rankBottomY = [];
+
+	for (var i = 0; i < model.ranks.length; i++)
+	{
+		this.rankTopY[i] = Number.MAX_VALUE;
+		this.rankBottomY[i] = -Number.MAX_VALUE;
+	}
+	
+	var vertices = model.vertexMapper.getValues();
+
+	// Process vertices all first, since they define the lower and 
+	// limits of each rank. Between these limits lie the channels
+	// where the edges can be routed across the graph
+
+	for (var i = 0; i < vertices.length; i++)
+	{
+		this.setVertexLocation(vertices[i]);
+	}
+	
+	// Post process edge styles. Needs the vertex locations set for initial
+	// values of the top and bottoms of each rank
+	if (this.layout.edgeStyle == mxHierarchicalEdgeStyle.ORTHOGONAL
+			|| this.layout.edgeStyle == mxHierarchicalEdgeStyle.POLYLINE
+			|| this.layout.edgeStyle == mxHierarchicalEdgeStyle.CURVE)
+	{
+		this.localEdgeProcessing(model);
+	}
+
+	var edges = model.edgeMapper.getValues();
+
+	for (var i = 0; i < edges.length; i++)
+	{
+		this.setEdgePosition(edges[i]);
+	}
+};
+
+/**
+ * Function: localEdgeProcessing
+ * 
+ * Separates the x position of edges as they connect to vertices
+ * 
+ * Parameters:
+ *
+ * model - the layout model
+ */
+mxCoordinateAssignment.prototype.localEdgeProcessing = function(model)
+{
+	// Iterate through each vertex, look at the edges connected in
+	// both directions.
+	for (var rankIndex = 0; rankIndex < model.ranks.length; rankIndex++)
+	{
+		var rank = model.ranks[rankIndex];
+
+		for (var cellIndex = 0; cellIndex < rank.length; cellIndex++)
+		{
+			var cell = rank[cellIndex];
+
+			if (cell.isVertex())
+			{
+				var currentCells = cell.getPreviousLayerConnectedCells(rankIndex);
+
+				var currentRank = rankIndex - 1;
+
+				// Two loops, last connected cells, and next
+				for (var k = 0; k < 2; k++)
+				{
+					if (currentRank > -1
+							&& currentRank < model.ranks.length
+							&& currentCells != null
+							&& currentCells.length > 0)
+					{
+						var sortedCells = [];
+
+						for (var j = 0; j < currentCells.length; j++)
+						{
+							var sorter = new WeightedCellSorter(
+									currentCells[j], currentCells[j].getX(currentRank));
+							sortedCells.push(sorter);
+						}
+
+						sortedCells.sort(WeightedCellSorter.prototype.compare);
+
+						var leftLimit = cell.x[0] - cell.width / 2;
+						var rightLimit = leftLimit + cell.width;
+
+						// Connected edge count starts at 1 to allow for buffer
+						// with edge of vertex
+						var connectedEdgeCount = 0;
+						var connectedEdgeGroupCount = 0;
+						var connectedEdges = [];
+						// Calculate width requirements for all connected edges
+						for (var j = 0; j < sortedCells.length; j++)
+						{
+							var innerCell = sortedCells[j].cell;
+							var connections;
+
+							if (innerCell.isVertex())
+							{
+								// Get the connecting edge
+								if (k == 0)
+								{
+									connections = cell.connectsAsSource;
+
+								}
+								else
+								{
+									connections = cell.connectsAsTarget;
+								}
+
+								for (var connIndex = 0; connIndex < connections.length; connIndex++)
+								{
+									if (connections[connIndex].source == innerCell
+											|| connections[connIndex].target == innerCell)
+									{
+										connectedEdgeCount += connections[connIndex].edges
+												.length;
+										connectedEdgeGroupCount++;
+
+										connectedEdges.push(connections[connIndex]);
+									}
+								}
+							}
+							else
+							{
+								connectedEdgeCount += innerCell.edges.length;
+								connectedEdgeGroupCount++;
+								connectedEdges.push(innerCell);
+							}
+						}
+
+						var requiredWidth = (connectedEdgeCount + 1)
+								* this.prefHozEdgeSep;
+
+						// Add a buffer on the edges of the vertex if the edge count allows
+						if (cell.width > requiredWidth
+								+ (2 * this.prefHozEdgeSep))
+						{
+							leftLimit += this.prefHozEdgeSep;
+							rightLimit -= this.prefHozEdgeSep;
+						}
+
+						var availableWidth = rightLimit - leftLimit;
+						var edgeSpacing = availableWidth / connectedEdgeCount;
+
+						var currentX = leftLimit + edgeSpacing / 2.0;
+						var currentYOffset = this.minEdgeJetty - this.prefVertEdgeOff;
+						var maxYOffset = 0;
+
+						for (var j = 0; j < connectedEdges.length; j++)
+						{
+							var numActualEdges = connectedEdges[j].edges
+									.length;
+							var pos = this.jettyPositions[connectedEdges[j].ids[0]];
+							
+							if (pos == null)
+							{
+								pos = [];
+								this.jettyPositions[connectedEdges[j].ids[0]] = pos;
+							}
+
+							if (j < connectedEdgeCount / 2)
+							{
+								currentYOffset += this.prefVertEdgeOff;
+							}
+							else if (j > connectedEdgeCount / 2)
+							{
+								currentYOffset -= this.prefVertEdgeOff;
+							}
+							// Ignore the case if equals, this means the second of 2
+							// jettys with the same y (even number of edges)
+
+							for (var m = 0; m < numActualEdges; m++)
+							{
+								pos[m * 4 + k * 2] = currentX;
+								currentX += edgeSpacing;
+								pos[m * 4 + k * 2 + 1] = currentYOffset;
+							}
+							
+							maxYOffset = Math.max(maxYOffset,
+									currentYOffset);
+						}
+					}
+
+					currentCells = cell.getNextLayerConnectedCells(rankIndex);
+
+					currentRank = rankIndex + 1;
+				}
+			}
+		}
+	}
+};
+
+/**
+ * Function: setEdgePosition
+ * 
+ * Fixes the control points
+ */
+mxCoordinateAssignment.prototype.setEdgePosition = function(cell)
+{
+	// For parallel edges we need to seperate out the points a
+	// little
+	var offsetX = 0;
+	// Only set the edge control points once
+
+	if (cell.temp[0] != 101207)
+	{
+		var maxRank = cell.maxRank;
+		var minRank = cell.minRank;
+		
+		if (maxRank == minRank)
+		{
+			maxRank = cell.source.maxRank;
+			minRank = cell.target.minRank;
+		}
+		
+		var parallelEdgeCount = 0;
+		var jettys = this.jettyPositions[cell.ids[0]];
+
+		var source = cell.isReversed ? cell.target.cell : cell.source.cell;
+		var graph = this.layout.graph;
+		var layoutReversed = this.orientation == mxConstants.DIRECTION_EAST
+				|| this.orientation == mxConstants.DIRECTION_SOUTH;
+
+		for (var i = 0; i < cell.edges.length; i++)
+		{
+			var realEdge = cell.edges[i];
+			var realSource = this.layout.getVisibleTerminal(realEdge, true);
+
+			//List oldPoints = graph.getPoints(realEdge);
+			var newPoints = [];
+
+			// Single length reversed edges end up with the jettys in the wrong
+			// places. Since single length edges only have jettys, not segment
+			// control points, we just say the edge isn't reversed in this section
+			var reversed = cell.isReversed;
+			
+			if (realSource != source)
+			{
+				// The real edges include all core model edges and these can go
+				// in both directions. If the source of the hierarchical model edge
+				// isn't the source of the specific real edge in this iteration
+				// treat if as reversed
+				reversed = !reversed;
+			}
+
+			// First jetty of edge
+			if (jettys != null)
+			{
+				var arrayOffset = reversed ? 2 : 0;
+				var y = reversed ?
+						(layoutReversed ? this.rankBottomY[minRank] : this.rankTopY[minRank]) :
+							(layoutReversed ? this.rankTopY[maxRank] : this.rankBottomY[maxRank]);
+				var jetty = jettys[parallelEdgeCount * 4 + 1 + arrayOffset];
+				
+				if (reversed != layoutReversed)
+				{
+					jetty = -jetty;
+				}
+				
+				y += jetty;
+				var x = jettys[parallelEdgeCount * 4 + arrayOffset];
+				
+				var modelSource = graph.model.getTerminal(realEdge, true);
+
+				if (this.layout.isPort(modelSource) && graph.model.getParent(modelSource) == realSource)
+				{
+					var state = graph.view.getState(modelSource);
+					
+					if (state != null)
+					{
+						x = state.x;
+					}
+					else
+					{
+						x = realSource.geometry.x + cell.source.width * modelSource.geometry.x;
+					}
+				}
+
+				if (this.orientation == mxConstants.DIRECTION_NORTH
+						|| this.orientation == mxConstants.DIRECTION_SOUTH)
+				{
+					newPoints.push(new mxPoint(x, y));
+					
+					if (this.layout.edgeStyle == mxHierarchicalEdgeStyle.CURVE)
+					{
+						newPoints.push(new mxPoint(x, y + jetty));
+					}
+				}
+				else
+				{
+					newPoints.push(new mxPoint(y, x));
+					
+					if (this.layout.edgeStyle == mxHierarchicalEdgeStyle.CURVE)
+					{
+						newPoints.push(new mxPoint(y + jetty, x));
+					}
+				}
+			}
+
+			// Declare variables to define loop through edge points and 
+			// change direction if edge is reversed
+
+			var loopStart = cell.x.length - 1;
+			var loopLimit = -1;
+			var loopDelta = -1;
+			var currentRank = cell.maxRank - 1;
+
+			if (reversed)
+			{
+				loopStart = 0;
+				loopLimit = cell.x.length;
+				loopDelta = 1;
+				currentRank = cell.minRank + 1;
+			}
+			// Reversed edges need the points inserted in
+			// reverse order
+			for (var j = loopStart; (cell.maxRank != cell.minRank) && j != loopLimit; j += loopDelta)
+			{
+				// The horizontal position in a vertical layout
+				var positionX = cell.x[j] + offsetX;
+
+				// Work out the vertical positions in a vertical layout
+				// in the edge buffer channels above and below this rank
+				var topChannelY = (this.rankTopY[currentRank] + this.rankBottomY[currentRank + 1]) / 2.0;
+				var bottomChannelY = (this.rankTopY[currentRank - 1] + this.rankBottomY[currentRank]) / 2.0;
+
+				if (reversed)
+				{
+					var tmp = topChannelY;
+					topChannelY = bottomChannelY;
+					bottomChannelY = tmp;
+				}
+
+				if (this.orientation == mxConstants.DIRECTION_NORTH ||
+					this.orientation == mxConstants.DIRECTION_SOUTH)
+				{
+					newPoints.push(new mxPoint(positionX, topChannelY));
+					newPoints.push(new mxPoint(positionX, bottomChannelY));
+				}
+				else
+				{
+					newPoints.push(new mxPoint(topChannelY, positionX));
+					newPoints.push(new mxPoint(bottomChannelY, positionX));
+				}
+
+				this.limitX = Math.max(this.limitX, positionX);
+				currentRank += loopDelta;
+			}
+
+			// Second jetty of edge
+			if (jettys != null)
+			{
+				var arrayOffset = reversed ? 2 : 0;
+				var rankY = reversed ?
+						(layoutReversed ? this.rankTopY[maxRank] : this.rankBottomY[maxRank]) :
+							(layoutReversed ? this.rankBottomY[minRank] : this.rankTopY[minRank]);
+				var jetty = jettys[parallelEdgeCount * 4 + 3 - arrayOffset];
+				
+				if (reversed != layoutReversed)
+				{
+					jetty = -jetty;
+				}
+				var y = rankY - jetty;
+				var x = jettys[parallelEdgeCount * 4 + 2 - arrayOffset];
+				
+				var modelTarget = graph.model.getTerminal(realEdge, false);
+				var realTarget = this.layout.getVisibleTerminal(realEdge, false);
+
+				if (this.layout.isPort(modelTarget) && graph.model.getParent(modelTarget) == realTarget)
+				{
+					var state = graph.view.getState(modelTarget);
+					
+					if (state != null)
+					{
+						x = state.x;
+					}
+					else
+					{
+						x = realTarget.geometry.x + cell.target.width * modelTarget.geometry.x;
+					}
+				}
+
+				if (this.orientation == mxConstants.DIRECTION_NORTH ||
+						this.orientation == mxConstants.DIRECTION_SOUTH)
+				{
+					if (this.layout.edgeStyle == mxHierarchicalEdgeStyle.CURVE)
+					{
+						newPoints.push(new mxPoint(x, y - jetty));
+					}
+
+					newPoints.push(new mxPoint(x, y));
+				}
+				else
+				{
+					if (this.layout.edgeStyle == mxHierarchicalEdgeStyle.CURVE)
+					{
+						newPoints.push(new mxPoint(y - jetty, x));
+					}
+
+					newPoints.push(new mxPoint(y, x));
+				}
+			}
+
+			if (cell.isReversed)
+			{
+				this.processReversedEdge(cell, realEdge);
+			}
+
+			this.layout.setEdgePoints(realEdge, newPoints);
+
+			// Increase offset so next edge is drawn next to
+			// this one
+			if (offsetX == 0.0)
+			{
+				offsetX = this.parallelEdgeSpacing;
+			}
+			else if (offsetX > 0)
+			{
+				offsetX = -offsetX;
+			}
+			else
+			{
+				offsetX = -offsetX + this.parallelEdgeSpacing;
+			}
+			
+			parallelEdgeCount++;
+		}
+
+		cell.temp[0] = 101207;
+	}
+};
+
+
+/**
+ * Function: setVertexLocation
+ * 
+ * Fixes the position of the specified vertex.
+ * 
+ * Parameters:
+ * 
+ * cell - the vertex to position
+ */
+mxCoordinateAssignment.prototype.setVertexLocation = function(cell)
+{
+	var realCell = cell.cell;
+	var positionX = cell.x[0] - cell.width / 2;
+	var positionY = cell.y[0] - cell.height / 2;
+
+	this.rankTopY[cell.minRank] = Math.min(this.rankTopY[cell.minRank], positionY);
+	this.rankBottomY[cell.minRank] = Math.max(this.rankBottomY[cell.minRank],
+			positionY + cell.height);
+
+	if (this.orientation == mxConstants.DIRECTION_NORTH ||
+		this.orientation == mxConstants.DIRECTION_SOUTH)
+	{
+		this.layout.setVertexLocation(realCell, positionX, positionY);
+	}
+	else
+	{
+		this.layout.setVertexLocation(realCell, positionY, positionX);
+	}
+
+	this.limitX = Math.max(this.limitX, positionX + cell.width);
+};
+
+/**
+ * Function: processReversedEdge
+ * 
+ * Hook to add additional processing
+ * 
+ * Parameters:
+ * 
+ * edge - the hierarchical model edge
+ * realEdge - the real edge in the graph
+ */
+mxCoordinateAssignment.prototype.processReversedEdge = function(graph, model)
+{
+	// hook for subclassers
+};
+
+/**
+ * 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/airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/stage/mxHierarchicalLayoutStage.js b/airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/stage/mxHierarchicalLayoutStage.js
new file mode 100644
index 0000000..605846b
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/stage/mxHierarchicalLayoutStage.js
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxHierarchicalLayoutStage
+ * 
+ * The specific layout interface for hierarchical layouts. It adds a
+ * <code>run</code> method with a parameter for the hierarchical layout model
+ * that is shared between the layout stages.
+ * 
+ * Constructor: mxHierarchicalLayoutStage
+ *
+ * Constructs a new hierarchical layout stage.
+ */
+function mxHierarchicalLayoutStage() { };
+
+/**
+ * Function: execute
+ * 
+ * Takes the graph detail and configuration information within the facade
+ * and creates the resulting laid out graph within that facade for further
+ * use.
+ */
+mxHierarchicalLayoutStage.prototype.execute = function(parent) { };
diff --git a/airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/stage/mxMedianHybridCrossingReduction.js b/airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/stage/mxMedianHybridCrossingReduction.js
new file mode 100644
index 0000000..139079d
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/stage/mxMedianHybridCrossingReduction.js
@@ -0,0 +1,675 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxMedianHybridCrossingReduction
+ * 
+ * Sets the horizontal locations of node and edge dummy nodes on each layer.
+ * Uses median down and up weighings as well heuristic to straighten edges as
+ * far as possible.
+ * 
+ * Constructor: mxMedianHybridCrossingReduction
+ *
+ * Creates a coordinate assignment.
+ * 
+ * Arguments:
+ * 
+ * intraCellSpacing - the minimum buffer between cells on the same rank
+ * interRankCellSpacing - the minimum distance between cells on adjacent ranks
+ * orientation - the position of the root node(s) relative to the graph
+ * initialX - the leftmost coordinate node placement starts at
+ */
+function mxMedianHybridCrossingReduction(layout)
+{
+	this.layout = layout;
+};
+
+/**
+ * Extends mxMedianHybridCrossingReduction.
+ */
+mxMedianHybridCrossingReduction.prototype = new mxHierarchicalLayoutStage();
+mxMedianHybridCrossingReduction.prototype.constructor = mxMedianHybridCrossingReduction;
+
+/**
+ * Variable: layout
+ * 
+ * Reference to the enclosing <mxHierarchicalLayout>.
+ */
+mxMedianHybridCrossingReduction.prototype.layout = null;
+
+/**
+ * Variable: maxIterations
+ * 
+ * The maximum number of iterations to perform whilst reducing edge
+ * crossings. Default is 24.
+ */
+mxMedianHybridCrossingReduction.prototype.maxIterations = 24;
+
+/**
+ * Variable: nestedBestRanks
+ * 
+ * Stores each rank as a collection of cells in the best order found for
+ * each layer so far
+ */
+mxMedianHybridCrossingReduction.prototype.nestedBestRanks = null;
+
+/**
+ * Variable: currentBestCrossings
+ * 
+ * The total number of crossings found in the best configuration so far
+ */
+mxMedianHybridCrossingReduction.prototype.currentBestCrossings = 0;
+
+/**
+ * Variable: iterationsWithoutImprovement
+ * 
+ * The total number of crossings found in the best configuration so far
+ */
+mxMedianHybridCrossingReduction.prototype.iterationsWithoutImprovement = 0;
+
+/**
+ * Variable: maxNoImprovementIterations
+ * 
+ * The total number of crossings found in the best configuration so far
+ */
+mxMedianHybridCrossingReduction.prototype.maxNoImprovementIterations = 2;
+
+/**
+ * Function: execute
+ * 
+ * Performs a vertex ordering within ranks as described by Gansner et al
+ * 1993
+ */
+mxMedianHybridCrossingReduction.prototype.execute = function(parent)
+{
+	var model = this.layout.getModel();
+
+	// Stores initial ordering as being the best one found so far
+	this.nestedBestRanks = [];
+	
+	for (var i = 0; i < model.ranks.length; i++)
+	{
+		this.nestedBestRanks[i] = model.ranks[i].slice();
+	}
+
+	var iterationsWithoutImprovement = 0;
+	var currentBestCrossings = this.calculateCrossings(model);
+
+	for (var i = 0; i < this.maxIterations &&
+		iterationsWithoutImprovement < this.maxNoImprovementIterations; i++)
+	{
+		this.weightedMedian(i, model);
+		this.transpose(i, model);
+		var candidateCrossings = this.calculateCrossings(model);
+
+		if (candidateCrossings < currentBestCrossings)
+		{
+			currentBestCrossings = candidateCrossings;
+			iterationsWithoutImprovement = 0;
+
+			// Store the current rankings as the best ones
+			for (var j = 0; j < this.nestedBestRanks.length; j++)
+			{
+				var rank = model.ranks[j];
+
+				for (var k = 0; k < rank.length; k++)
+				{
+					var cell = rank[k];
+					this.nestedBestRanks[j][cell.getGeneralPurposeVariable(j)] = cell;
+				}
+			}
+		}
+		else
+		{
+			// Increase count of iterations where we haven't improved the
+			// layout
+			iterationsWithoutImprovement++;
+
+			// Restore the best values to the cells
+			for (var j = 0; j < this.nestedBestRanks.length; j++)
+			{
+				var rank = model.ranks[j];
+				
+				for (var k = 0; k < rank.length; k++)
+				{
+					var cell = rank[k];
+					cell.setGeneralPurposeVariable(j, k);
+				}
+			}
+		}
+		
+		if (currentBestCrossings == 0)
+		{
+			// Do nothing further
+			break;
+		}
+	}
+
+	// Store the best rankings but in the model
+	var ranks = [];
+	var rankList = [];
+
+	for (var i = 0; i < model.maxRank + 1; i++)
+	{
+		rankList[i] = [];
+		ranks[i] = rankList[i];
+	}
+
+	for (var i = 0; i < this.nestedBestRanks.length; i++)
+	{
+		for (var j = 0; j < this.nestedBestRanks[i].length; j++)
+		{
+			rankList[i].push(this.nestedBestRanks[i][j]);
+		}
+	}
+
+	model.ranks = ranks;
+};
+
+
+/**
+ * Function: calculateCrossings
+ * 
+ * Calculates the total number of edge crossing in the current graph.
+ * Returns the current number of edge crossings in the hierarchy graph
+ * model in the current candidate layout
+ * 
+ * Parameters:
+ * 
+ * model - the internal model describing the hierarchy
+ */
+mxMedianHybridCrossingReduction.prototype.calculateCrossings = function(model)
+{
+	var numRanks = model.ranks.length;
+	var totalCrossings = 0;
+
+	for (var i = 1; i < numRanks; i++)
+	{
+		totalCrossings += this.calculateRankCrossing(i, model);
+	}
+	
+	return totalCrossings;
+};
+
+/**
+ * Function: calculateRankCrossing
+ * 
+ * Calculates the number of edges crossings between the specified rank and
+ * the rank below it. Returns the number of edges crossings with the rank
+ * beneath
+ * 
+ * Parameters:
+ * 
+ * i -  the topmost rank of the pair ( higher rank value )
+ * model - the internal model describing the hierarchy
+ */
+mxMedianHybridCrossingReduction.prototype.calculateRankCrossing = function(i, model)
+{
+	var totalCrossings = 0;
+	var rank = model.ranks[i];
+	var previousRank = model.ranks[i - 1];
+
+	var tmpIndices = [];
+
+	// Iterate over the top rank and fill in the connection information
+	for (var j = 0; j < rank.length; j++)
+	{
+		var node = rank[j];
+		var rankPosition = node.getGeneralPurposeVariable(i);
+		var connectedCells = node.getPreviousLayerConnectedCells(i);
+		var nodeIndices = [];
+
+		for (var k = 0; k < connectedCells.length; k++)
+		{
+			var connectedNode = connectedCells[k];
+			var otherCellRankPosition = connectedNode.getGeneralPurposeVariable(i - 1);
+			nodeIndices.push(otherCellRankPosition);
+		}
+		
+		nodeIndices.sort(function(x, y) { return x - y; });
+		tmpIndices[rankPosition] = nodeIndices;
+	}
+	
+	var indices = [];
+
+	for (var j = 0; j < tmpIndices.length; j++)
+	{
+		indices = indices.concat(tmpIndices[j]);
+	}
+
+	var firstIndex = 1;
+	
+	while (firstIndex < previousRank.length)
+	{
+		firstIndex <<= 1;
+	}
+
+	var treeSize = 2 * firstIndex - 1;
+	firstIndex -= 1;
+
+	var tree = [];
+	
+	for (var j = 0; j < treeSize; ++j)
+	{
+		tree[j] = 0;
+	}
+
+	for (var j = 0; j < indices.length; j++)
+	{
+		var index = indices[j];
+	    var treeIndex = index + firstIndex;
+	    ++tree[treeIndex];
+	    
+	    while (treeIndex > 0)
+	    {
+	    	if (treeIndex % 2)
+	    	{
+	    		totalCrossings += tree[treeIndex + 1];
+	    	}
+	      
+	    	treeIndex = (treeIndex - 1) >> 1;
+	    	++tree[treeIndex];
+	    }
+	}
+
+	return totalCrossings;
+};
+
+/**
+ * Function: transpose
+ * 
+ * Takes each possible adjacent cell pair on each rank and checks if
+ * swapping them around reduces the number of crossing
+ * 
+ * Parameters:
+ * 
+ * mainLoopIteration - the iteration number of the main loop
+ * model - the internal model describing the hierarchy
+ */
+mxMedianHybridCrossingReduction.prototype.transpose = function(mainLoopIteration, model)
+{
+	var improved = true;
+
+	// Track the number of iterations in case of looping
+	var count = 0;
+	var maxCount = 10;
+	while (improved && count++ < maxCount)
+	{
+		// On certain iterations allow allow swapping of cell pairs with
+		// equal edge crossings switched or not switched. This help to
+		// nudge a stuck layout into a lower crossing total.
+		var nudge = mainLoopIteration % 2 == 1 && count % 2 == 1;
+		improved = false;
+		
+		for (var i = 0; i < model.ranks.length; i++)
+		{
+			var rank = model.ranks[i];
+			var orderedCells = [];
+			
+			for (var j = 0; j < rank.length; j++)
+			{
+				var cell = rank[j];
+				var tempRank = cell.getGeneralPurposeVariable(i);
+				
+				// FIXME: Workaround to avoid negative tempRanks
+				if (tempRank < 0)
+				{
+					tempRank = j;
+				}
+				orderedCells[tempRank] = cell;
+			}
+			
+			var leftCellAboveConnections = null;
+			var leftCellBelowConnections = null;
+			var rightCellAboveConnections = null;
+			var rightCellBelowConnections = null;
+			
+			var leftAbovePositions = null;
+			var leftBelowPositions = null;
+			var rightAbovePositions = null;
+			var rightBelowPositions = null;
+			
+			var leftCell = null;
+			var rightCell = null;
+
+			for (var j = 0; j < (rank.length - 1); j++)
+			{
+				// For each intra-rank adjacent pair of cells
+				// see if swapping them around would reduce the
+				// number of edges crossing they cause in total
+				// On every cell pair except the first on each rank, we
+				// can save processing using the previous values for the
+				// right cell on the new left cell
+				if (j == 0)
+				{
+					leftCell = orderedCells[j];
+					leftCellAboveConnections = leftCell
+							.getNextLayerConnectedCells(i);
+					leftCellBelowConnections = leftCell
+							.getPreviousLayerConnectedCells(i);
+					leftAbovePositions = [];
+					leftBelowPositions = [];
+					
+					for (var k = 0; k < leftCellAboveConnections.length; k++)
+					{
+						leftAbovePositions[k] = leftCellAboveConnections[k].getGeneralPurposeVariable(i + 1);
+					}
+					
+					for (var k = 0; k < leftCellBelowConnections.length; k++)
+					{
+						leftBelowPositions[k] = leftCellBelowConnections[k].getGeneralPurposeVariable(i - 1);
+					}
+				}
+				else
+				{
+					leftCellAboveConnections = rightCellAboveConnections;
+					leftCellBelowConnections = rightCellBelowConnections;
+					leftAbovePositions = rightAbovePositions;
+					leftBelowPositions = rightBelowPositions;
+					leftCell = rightCell;
+				}
+				
+				rightCell = orderedCells[j + 1];
+				rightCellAboveConnections = rightCell
+						.getNextLayerConnectedCells(i);
+				rightCellBelowConnections = rightCell
+						.getPreviousLayerConnectedCells(i);
+
+				rightAbovePositions = [];
+				rightBelowPositions = [];
+
+				for (var k = 0; k < rightCellAboveConnections.length; k++)
+				{
+					rightAbovePositions[k] = rightCellAboveConnections[k].getGeneralPurposeVariable(i + 1);
+				}
+				
+				for (var k = 0; k < rightCellBelowConnections.length; k++)
+				{
+					rightBelowPositions[k] = rightCellBelowConnections[k].getGeneralPurposeVariable(i - 1);
+				}
+
+				var totalCurrentCrossings = 0;
+				var totalSwitchedCrossings = 0;
+				
+				for (var k = 0; k < leftAbovePositions.length; k++)
+				{
+					for (var ik = 0; ik < rightAbovePositions.length; ik++)
+					{
+						if (leftAbovePositions[k] > rightAbovePositions[ik])
+						{
+							totalCurrentCrossings++;
+						}
+
+						if (leftAbovePositions[k] < rightAbovePositions[ik])
+						{
+							totalSwitchedCrossings++;
+						}
+					}
+				}
+				
+				for (var k = 0; k < leftBelowPositions.length; k++)
+				{
+					for (var ik = 0; ik < rightBelowPositions.length; ik++)
+					{
+						if (leftBelowPositions[k] > rightBelowPositions[ik])
+						{
+							totalCurrentCrossings++;
+						}
+
+						if (leftBelowPositions[k] < rightBelowPositions[ik])
+						{
+							totalSwitchedCrossings++;
+						}
+					}
+				}
+				
+				if ((totalSwitchedCrossings < totalCurrentCrossings) ||
+					(totalSwitchedCrossings == totalCurrentCrossings &&
+					nudge))
+				{
+					var temp = leftCell.getGeneralPurposeVariable(i);
+					leftCell.setGeneralPurposeVariable(i, rightCell
+							.getGeneralPurposeVariable(i));
+					rightCell.setGeneralPurposeVariable(i, temp);
+
+					// With this pair exchanged we have to switch all of
+					// values for the left cell to the right cell so the
+					// next iteration for this rank uses it as the left
+					// cell again
+					rightCellAboveConnections = leftCellAboveConnections;
+					rightCellBelowConnections = leftCellBelowConnections;
+					rightAbovePositions = leftAbovePositions;
+					rightBelowPositions = leftBelowPositions;
+					rightCell = leftCell;
+					
+					if (!nudge)
+					{
+						// Don't count nudges as improvement or we'll end
+						// up stuck in two combinations and not finishing
+						// as early as we should
+						improved = true;
+					}
+				}
+			}
+		}
+	}
+};
+
+/**
+ * Function: weightedMedian
+ * 
+ * Sweeps up or down the layout attempting to minimise the median placement
+ * of connected cells on adjacent ranks
+ * 
+ * Parameters:
+ * 
+ * iteration - the iteration number of the main loop
+ * model - the internal model describing the hierarchy
+ */
+mxMedianHybridCrossingReduction.prototype.weightedMedian = function(iteration, model)
+{
+	// Reverse sweep direction each time through this method
+	var downwardSweep = (iteration % 2 == 0);
+	if (downwardSweep)
+	{
+		for (var j = model.maxRank - 1; j >= 0; j--)
+		{
+			this.medianRank(j, downwardSweep);
+		}
+	}
+	else
+	{
+		for (var j = 1; j < model.maxRank; j++)
+		{
+			this.medianRank(j, downwardSweep);
+		}
+	}
+};
+
+/**
+ * Function: medianRank
+ * 
+ * Attempts to minimise the median placement of connected cells on this rank
+ * and one of the adjacent ranks
+ * 
+ * Parameters:
+ * 
+ * rankValue - the layer number of this rank
+ * downwardSweep - whether or not this is a downward sweep through the graph
+ */
+mxMedianHybridCrossingReduction.prototype.medianRank = function(rankValue, downwardSweep)
+{
+	var numCellsForRank = this.nestedBestRanks[rankValue].length;
+	var medianValues = [];
+	var reservedPositions = [];
+
+	for (var i = 0; i < numCellsForRank; i++)
+	{
+		var cell = this.nestedBestRanks[rankValue][i];
+		var sorterEntry = new MedianCellSorter();
+		sorterEntry.cell = cell;
+
+		// Flip whether or not equal medians are flipped on up and down
+		// sweeps
+		// TODO re-implement some kind of nudge
+		// medianValues[i].nudge = !downwardSweep;
+		var nextLevelConnectedCells;
+		
+		if (downwardSweep)
+		{
+			nextLevelConnectedCells = cell
+					.getNextLayerConnectedCells(rankValue);
+		}
+		else
+		{
+			nextLevelConnectedCells = cell
+					.getPreviousLayerConnectedCells(rankValue);
+		}
+		
+		var nextRankValue;
+		
+		if (downwardSweep)
+		{
+			nextRankValue = rankValue + 1;
+		}
+		else
+		{
+			nextRankValue = rankValue - 1;
+		}
+
+		if (nextLevelConnectedCells != null
+				&& nextLevelConnectedCells.length != 0)
+		{
+			sorterEntry.medianValue = this.medianValue(
+					nextLevelConnectedCells, nextRankValue);
+			medianValues.push(sorterEntry);
+		}
+		else
+		{
+			// Nodes with no adjacent vertices are flagged in the reserved array
+			// to indicate they should be left in their current position.
+			reservedPositions[cell.getGeneralPurposeVariable(rankValue)] = true;
+		}
+	}
+	
+	medianValues.sort(MedianCellSorter.prototype.compare);
+	
+	// Set the new position of each node within the rank using
+	// its temp variable
+	for (var i = 0; i < numCellsForRank; i++)
+	{
+		if (reservedPositions[i] == null)
+		{
+			var cell = medianValues.shift().cell;
+			cell.setGeneralPurposeVariable(rankValue, i);
+		}
+	}
+};
+
+/**
+ * Function: medianValue
+ * 
+ * Calculates the median rank order positioning for the specified cell using
+ * the connected cells on the specified rank. Returns the median rank
+ * ordering value of the connected cells
+ * 
+ * Parameters:
+ * 
+ * connectedCells - the cells on the specified rank connected to the
+ * specified cell
+ * rankValue - the rank that the connected cell lie upon
+ */
+mxMedianHybridCrossingReduction.prototype.medianValue = function(connectedCells, rankValue)
+{
+	var medianValues = [];
+	var arrayCount = 0;
+	
+	for (var i = 0; i < connectedCells.length; i++)
+	{
+		var cell = connectedCells[i];
+		medianValues[arrayCount++] = cell.getGeneralPurposeVariable(rankValue);
+	}
+
+	// Sort() sorts lexicographically by default (i.e. 11 before 9) so force
+	// numerical order sort
+	medianValues.sort(function(a,b){return a - b;});
+	
+	if (arrayCount % 2 == 1)
+	{
+		// For odd numbers of adjacent vertices return the median
+		return medianValues[Math.floor(arrayCount / 2)];
+	}
+	else if (arrayCount == 2)
+	{
+		return ((medianValues[0] + medianValues[1]) / 2.0);
+	}
+	else
+	{
+		var medianPoint = arrayCount / 2;
+		var leftMedian = medianValues[medianPoint - 1] - medianValues[0];
+		var rightMedian = medianValues[arrayCount - 1]
+				- medianValues[medianPoint];
+
+		return (medianValues[medianPoint - 1] * rightMedian + medianValues[medianPoint]
+				* leftMedian)
+				/ (leftMedian + rightMedian);
+	}
+};
+
+/**
+ * Class: MedianCellSorter
+ * 
+ * A utility class used to track cells whilst sorting occurs on the median
+ * values. Does not violate (x.compareTo(y)==0) == (x.equals(y))
+ *
+ * Constructor: MedianCellSorter
+ * 
+ * Constructs a new median cell sorter.
+ */
+function MedianCellSorter()
+{
+	// empty
+};
+
+/**
+ * Variable: medianValue
+ * 
+ * The weighted value of the cell stored.
+ */
+MedianCellSorter.prototype.medianValue = 0;
+
+/**
+ * Variable: cell
+ * 
+ * The cell whose median value is being calculated
+ */
+MedianCellSorter.prototype.cell = false;
+
+/**
+ * Function: compare
+ * 
+ * Compares two MedianCellSorters.
+ */
+MedianCellSorter.prototype.compare = function(a, b)
+{
+	if (a != null && b != null)
+	{
+		if (b.medianValue > a.medianValue)
+		{
+			return -1;
+		}
+		else if (b.medianValue < a.medianValue)
+		{
+			return 1;
+		}
+		else
+		{
+			return 0;
+		}
+	}
+	else
+	{
+		return 0;
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/stage/mxMinimumCycleRemover.js b/airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/stage/mxMinimumCycleRemover.js
new file mode 100644
index 0000000..7046755
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/stage/mxMinimumCycleRemover.js
@@ -0,0 +1,108 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxMinimumCycleRemover
+ * 
+ * An implementation of the first stage of the Sugiyama layout. Straightforward
+ * longest path calculation of layer assignment
+ * 
+ * Constructor: mxMinimumCycleRemover
+ *
+ * Creates a cycle remover for the given internal model.
+ */
+function mxMinimumCycleRemover(layout)
+{
+	this.layout = layout;
+};
+
+/**
+ * Extends mxHierarchicalLayoutStage.
+ */
+mxMinimumCycleRemover.prototype = new mxHierarchicalLayoutStage();
+mxMinimumCycleRemover.prototype.constructor = mxMinimumCycleRemover;
+
+/**
+ * Variable: layout
+ * 
+ * Reference to the enclosing <mxHierarchicalLayout>.
+ */
+mxMinimumCycleRemover.prototype.layout = null;
+
+/**
+ * Function: execute
+ * 
+ * Takes the graph detail and configuration information within the facade
+ * and creates the resulting laid out graph within that facade for further
+ * use.
+ */
+mxMinimumCycleRemover.prototype.execute = function(parent)
+{
+	var model = this.layout.getModel();
+	var seenNodes = new Object();
+	var unseenNodesArray = model.vertexMapper.getValues();
+	var unseenNodes = new Object();
+	
+	for (var i = 0; i < unseenNodesArray.length; i++)
+	{
+		unseenNodes[unseenNodesArray[i].id] = unseenNodesArray[i];
+	}
+	
+	// Perform a dfs through the internal model. If a cycle is found,
+	// reverse it.
+	var rootsArray = null;
+	
+	if (model.roots != null)
+	{
+		var modelRoots = model.roots;
+		rootsArray = [];
+		
+		for (var i = 0; i < modelRoots.length; i++)
+		{
+			rootsArray[i] = model.vertexMapper.get(modelRoots[i]);
+		}
+	}
+
+	model.visit(function(parent, node, connectingEdge, layer, seen)
+	{
+		// Check if the cell is in it's own ancestor list, if so
+		// invert the connecting edge and reverse the target/source
+		// relationship to that edge in the parent and the cell
+		if (node.isAncestor(parent))
+		{
+			connectingEdge.invert();
+			mxUtils.remove(connectingEdge, parent.connectsAsSource);
+			parent.connectsAsTarget.push(connectingEdge);
+			mxUtils.remove(connectingEdge, node.connectsAsTarget);
+			node.connectsAsSource.push(connectingEdge);
+		}
+		
+		seenNodes[node.id] = node;
+		delete unseenNodes[node.id];
+	}, rootsArray, true, null);
+
+	// If there are any nodes that should be nodes that the dfs can miss
+	// these need to be processed with the dfs and the roots assigned
+	// correctly to form a correct internal model
+	var seenNodesCopy = mxUtils.clone(seenNodes, null, true);
+
+	// Pick a random cell and dfs from it
+	model.visit(function(parent, node, connectingEdge, layer, seen)
+	{
+		// Check if the cell is in it's own ancestor list, if so
+		// invert the connecting edge and reverse the target/source
+		// relationship to that edge in the parent and the cell
+		if (node.isAncestor(parent))
+		{
+			connectingEdge.invert();
+			mxUtils.remove(connectingEdge, parent.connectsAsSource);
+			node.connectsAsSource.push(connectingEdge);
+			parent.connectsAsTarget.push(connectingEdge);
+			mxUtils.remove(connectingEdge, node.connectsAsTarget);
+		}
+		
+		seenNodes[node.id] = node;
+		delete unseenNodes[node.id];
+	}, unseenNodes, true, seenNodesCopy);
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/stage/mxSwimlaneOrdering.js b/airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/stage/mxSwimlaneOrdering.js
new file mode 100644
index 0000000..5c71f40
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/layout/hierarchical/stage/mxSwimlaneOrdering.js
@@ -0,0 +1,96 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxSwimlaneOrdering
+ * 
+ * An implementation of the first stage of the Sugiyama layout. Straightforward
+ * longest path calculation of layer assignment
+ * 
+ * Constructor: mxSwimlaneOrdering
+ *
+ * Creates a cycle remover for the given internal model.
+ */
+function mxSwimlaneOrdering(layout)
+{
+	this.layout = layout;
+};
+
+/**
+ * Extends mxHierarchicalLayoutStage.
+ */
+mxSwimlaneOrdering.prototype = new mxHierarchicalLayoutStage();
+mxSwimlaneOrdering.prototype.constructor = mxSwimlaneOrdering;
+
+/**
+ * Variable: layout
+ * 
+ * Reference to the enclosing <mxHierarchicalLayout>.
+ */
+mxSwimlaneOrdering.prototype.layout = null;
+
+/**
+ * Function: execute
+ * 
+ * Takes the graph detail and configuration information within the facade
+ * and creates the resulting laid out graph within that facade for further
+ * use.
+ */
+mxSwimlaneOrdering.prototype.execute = function(parent)
+{
+	var model = this.layout.getModel();
+	var seenNodes = new Object();
+	var unseenNodes = mxUtils.clone(model.vertexMapper, null, true);
+	
+	// Perform a dfs through the internal model. If a cycle is found,
+	// reverse it.
+	var rootsArray = null;
+	
+	if (model.roots != null)
+	{
+		var modelRoots = model.roots;
+		rootsArray = [];
+		
+		for (var i = 0; i < modelRoots.length; i++)
+		{
+			var nodeId = mxCellPath.create(modelRoots[i]);
+			rootsArray[i] = model.vertexMapper.get(modelRoots[i]);
+		}
+	}
+
+	model.visit(function(parent, node, connectingEdge, layer, seen)
+	{
+		// Check if the cell is in it's own ancestor list, if so
+		// invert the connecting edge and reverse the target/source
+		// relationship to that edge in the parent and the cell
+		// Ancestor hashes only line up within a swimlane
+		var isAncestor = parent != null && parent.swimlaneIndex == node.swimlaneIndex && node.isAncestor(parent);
+
+		// If the source->target swimlane indices go from higher to
+		// lower, the edge is reverse
+		var reversedOverSwimlane = parent != null && connectingEdge != null &&
+						parent.swimlaneIndex < node.swimlaneIndex && connectingEdge.source == node;
+
+		if (isAncestor)
+		{
+			connectingEdge.invert();
+			mxUtils.remove(connectingEdge, parent.connectsAsSource);
+			node.connectsAsSource.push(connectingEdge);
+			parent.connectsAsTarget.push(connectingEdge);
+			mxUtils.remove(connectingEdge, node.connectsAsTarget);
+		}
+		else if (reversedOverSwimlane)
+		{
+			connectingEdge.invert();
+			mxUtils.remove(connectingEdge, parent.connectsAsTarget);
+			node.connectsAsTarget.push(connectingEdge);
+			parent.connectsAsSource.push(connectingEdge);
+			mxUtils.remove(connectingEdge, node.connectsAsSource);
+		}
+		
+		var cellId = mxCellPath.create(node.cell);
+		seenNodes[cellId] = node;
+		delete unseenNodes[cellId];
+	}, rootsArray, true, null);
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/layout/mxCircleLayout.js b/airavata-kubernetes/workflow-composer/src/js/layout/mxCircleLayout.js
new file mode 100644
index 0000000..de82c53
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/layout/mxCircleLayout.js
@@ -0,0 +1,203 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxCircleLayout
+ * 
+ * Extends <mxGraphLayout> to implement a circluar layout for a given radius.
+ * The vertices do not need to be connected for this layout to work and all
+ * connections between vertices are not taken into account.
+ * 
+ * Example:
+ * 
+ * (code)
+ * var layout = new mxCircleLayout(graph);
+ * layout.execute(graph.getDefaultParent());
+ * (end)
+ * 
+ * Constructor: mxCircleLayout
+ *
+ * Constructs a new circular layout for the specified radius.
+ *
+ * Arguments:
+ * 
+ * graph - <mxGraph> that contains the cells.
+ * radius - Optional radius as an int. Default is 100.
+ */
+function mxCircleLayout(graph, radius)
+{
+	mxGraphLayout.call(this, graph);
+	this.radius = (radius != null) ? radius : 100;
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxCircleLayout.prototype = new mxGraphLayout();
+mxCircleLayout.prototype.constructor = mxCircleLayout;
+
+/**
+ * Variable: radius
+ * 
+ * Integer specifying the size of the radius. Default is 100.
+ */
+mxCircleLayout.prototype.radius = null;
+
+/**
+ * Variable: moveCircle
+ * 
+ * Boolean specifying if the circle should be moved to the top,
+ * left corner specified by <x0> and <y0>. Default is false.
+ */
+mxCircleLayout.prototype.moveCircle = false;
+
+/**
+ * Variable: x0
+ * 
+ * Integer specifying the left coordinate of the circle.
+ * Default is 0.
+ */
+mxCircleLayout.prototype.x0 = 0;
+
+/**
+ * Variable: y0
+ * 
+ * Integer specifying the top coordinate of the circle.
+ * Default is 0.
+ */
+mxCircleLayout.prototype.y0 = 0;
+
+/**
+ * Variable: resetEdges
+ * 
+ * Specifies if all edge points of traversed edges should be removed.
+ * Default is true.
+ */
+mxCircleLayout.prototype.resetEdges = true;
+
+/**
+ * Variable: disableEdgeStyle
+ * 
+ * Specifies if the STYLE_NOEDGESTYLE flag should be set on edges that are
+ * modified by the result. Default is true.
+ */
+mxCircleLayout.prototype.disableEdgeStyle = true;
+
+/**
+ * Function: execute
+ * 
+ * Implements <mxGraphLayout.execute>.
+ */
+mxCircleLayout.prototype.execute = function(parent)
+{
+	var model = this.graph.getModel();
+
+	// Moves the vertices to build a circle. Makes sure the
+	// radius is large enough for the vertices to not
+	// overlap
+	model.beginUpdate();
+	try
+	{
+		// Gets all vertices inside the parent and finds
+		// the maximum dimension of the largest vertex
+		var max = 0;
+		var top = null;
+		var left = null;
+		var vertices = [];
+		var childCount = model.getChildCount(parent);
+		
+		for (var i = 0; i < childCount; i++)
+		{
+			var cell = model.getChildAt(parent, i);
+			
+			if (!this.isVertexIgnored(cell))
+			{
+				vertices.push(cell);
+				var bounds = this.getVertexBounds(cell);
+				
+				if (top == null)
+				{
+					top = bounds.y;
+				}
+				else
+				{
+					top = Math.min(top, bounds.y);
+				}
+				
+				if (left == null)
+				{
+					left = bounds.x;
+				}
+				else
+				{
+					left = Math.min(left, bounds.x);
+				}
+				
+				max = Math.max(max, Math.max(bounds.width, bounds.height));
+			}
+			else if (!this.isEdgeIgnored(cell))
+			{
+				// Resets the points on the traversed edge
+				if (this.resetEdges)
+				{
+					this.graph.resetEdge(cell);
+				}
+
+			    if (this.disableEdgeStyle)
+			    {
+			    	this.setEdgeStyleEnabled(cell, false);
+			    }
+			}
+		}
+		
+		var r = this.getRadius(vertices.length, max);
+
+		// Moves the circle to the specified origin
+		if (this.moveCircle)
+		{
+			left = this.x0;
+			top = this.y0;
+		}
+		
+		this.circle(vertices, r, left, top);
+	}
+	finally
+	{
+		model.endUpdate();
+	}
+};
+
+/**
+ * Function: getRadius
+ * 
+ * Returns the radius to be used for the given vertex count. Max is the maximum
+ * width or height of all vertices in the layout.
+ */
+mxCircleLayout.prototype.getRadius = function(count, max)
+{
+	return Math.max(count * max / Math.PI, this.radius);
+};
+
+/**
+ * Function: circle
+ * 
+ * Executes the circular layout for the specified array
+ * of vertices and the given radius. This is called from
+ * <execute>.
+ */
+mxCircleLayout.prototype.circle = function(vertices, r, left, top)
+{
+	var vertexCount = vertices.length;
+	var phi = 2 * Math.PI / vertexCount;
+	
+	for (var i = 0; i < vertexCount; i++)
+	{
+		if (this.isVertexMovable(vertices[i]))
+		{
+			this.setVertexLocation(vertices[i],
+				left + r + r * Math.sin(i*phi),
+				top + r + r * Math.cos(i*phi));
+		}
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/layout/mxCompactTreeLayout.js b/airavata-kubernetes/workflow-composer/src/js/layout/mxCompactTreeLayout.js
new file mode 100644
index 0000000..2bda6ed
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/layout/mxCompactTreeLayout.js
@@ -0,0 +1,1203 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxCompactTreeLayout
+ * 
+ * Extends <mxGraphLayout> to implement a compact tree (Moen) algorithm. This
+ * layout is suitable for graphs that have no cycles (trees). Vertices that are
+ * not connected to the tree will be ignored by this layout.
+ * 
+ * Example:
+ * 
+ * (code)
+ * var layout = new mxCompactTreeLayout(graph);
+ * layout.execute(graph.getDefaultParent());
+ * (end)
+ * 
+ * Constructor: mxCompactTreeLayout
+ * 
+ * Constructs a new compact tree layout for the specified graph
+ * and orientation.
+ */
+function mxCompactTreeLayout(graph, horizontal, invert)
+{
+	mxGraphLayout.call(this, graph);
+	this.horizontal = (horizontal != null) ? horizontal : true;
+	this.invert = (invert != null) ? invert : false;
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxCompactTreeLayout.prototype = new mxGraphLayout();
+mxCompactTreeLayout.prototype.constructor = mxCompactTreeLayout;
+
+/**
+ * Variable: horizontal
+ *
+ * Specifies the orientation of the layout. Default is true.
+ */
+mxCompactTreeLayout.prototype.horizontal = null;	 
+
+/**
+ * Variable: invert
+ *
+ * Specifies if edge directions should be inverted. Default is false.
+ */
+mxCompactTreeLayout.prototype.invert = null;	 
+
+/**
+ * Variable: resizeParent
+ * 
+ * If the parents should be resized to match the width/height of the
+ * children. Default is true.
+ */
+mxCompactTreeLayout.prototype.resizeParent = true;
+
+/**
+ * Variable: maintainParentLocation
+ * 
+ * Specifies if the parent location should be maintained, so that the
+ * top, left corner stays the same before and after execution of
+ * the layout. Default is false for backwards compatibility.
+ */
+mxCompactTreeLayout.prototype.maintainParentLocation = false;
+
+/**
+ * Variable: groupPadding
+ * 
+ * Padding added to resized parents. Default is 10.
+ */
+mxCompactTreeLayout.prototype.groupPadding = 10;
+
+/**
+ * Variable: groupPaddingTop
+ * 
+ * Top padding added to resized parents. Default is 0.
+ */
+mxCompactTreeLayout.prototype.groupPaddingTop = 0;
+
+/**
+ * Variable: groupPaddingRight
+ * 
+ * Right padding added to resized parents. Default is 0.
+ */
+mxCompactTreeLayout.prototype.groupPaddingRight = 0;
+
+/**
+ * Variable: groupPaddingBottom
+ * 
+ * Bottom padding added to resized parents. Default is 0.
+ */
+mxCompactTreeLayout.prototype.groupPaddingBottom = 0;
+
+/**
+ * Variable: groupPaddingLeft
+ * 
+ * Left padding added to resized parents. Default is 0.
+ */
+mxCompactTreeLayout.prototype.groupPaddingLeft = 0;
+
+/**
+ * Variable: parentsChanged
+ *
+ * A set of the parents that need updating based on children
+ * process as part of the layout.
+ */
+mxCompactTreeLayout.prototype.parentsChanged = null;
+
+/**
+ * Variable: moveTree
+ * 
+ * Specifies if the tree should be moved to the top, left corner
+ * if it is inside a top-level layer. Default is false.
+ */
+mxCompactTreeLayout.prototype.moveTree = false;
+
+/**
+ * Variable: visited
+ * 
+ * Specifies if the tree should be moved to the top, left corner
+ * if it is inside a top-level layer. Default is false.
+ */
+mxCompactTreeLayout.prototype.visited = null;
+
+/**
+ * Variable: levelDistance
+ *
+ * Holds the levelDistance. Default is 10.
+ */
+mxCompactTreeLayout.prototype.levelDistance = 10;
+
+/**
+ * Variable: nodeDistance
+ *
+ * Holds the nodeDistance. Default is 20.
+ */
+mxCompactTreeLayout.prototype.nodeDistance = 20;
+
+/**
+ * Variable: resetEdges
+ * 
+ * Specifies if all edge points of traversed edges should be removed.
+ * Default is true.
+ */
+mxCompactTreeLayout.prototype.resetEdges = true;
+
+/**
+ * Variable: prefHozEdgeSep
+ * 
+ * The preferred horizontal distance between edges exiting a vertex.
+ */
+mxCompactTreeLayout.prototype.prefHozEdgeSep = 5;
+
+/**
+ * Variable: prefVertEdgeOff
+ * 
+ * The preferred vertical offset between edges exiting a vertex.
+ */
+mxCompactTreeLayout.prototype.prefVertEdgeOff = 4;
+
+/**
+ * Variable: minEdgeJetty
+ * 
+ * The minimum distance for an edge jetty from a vertex.
+ */
+mxCompactTreeLayout.prototype.minEdgeJetty = 8;
+
+/**
+ * Variable: channelBuffer
+ * 
+ * The size of the vertical buffer in the center of inter-rank channels
+ * where edge control points should not be placed.
+ */
+mxCompactTreeLayout.prototype.channelBuffer = 4;
+
+/**
+ * Variable: edgeRouting
+ * 
+ * Whether or not to apply the internal tree edge routing.
+ */
+mxCompactTreeLayout.prototype.edgeRouting = true;
+
+/**
+ * Variable: sortEdges
+ * 
+ * Specifies if edges should be sorted according to the order of their
+ * opposite terminal cell in the model.
+ */
+mxCompactTreeLayout.prototype.sortEdges = false;
+
+/**
+ * Variable: alignRanks
+ * 
+ * Whether or not the tops of cells in each rank should be aligned
+ * across the rank
+ */
+mxCompactTreeLayout.prototype.alignRanks = false;
+
+/**
+ * Variable: maxRankHeight
+ * 
+ * An array of the maximum height of cells (relative to the layout direction)
+ * per rank
+ */
+mxCompactTreeLayout.prototype.maxRankHeight = null;
+
+/**
+ * Variable: root
+ * 
+ * The cell to use as the root of the tree
+ */
+mxCompactTreeLayout.prototype.root = null;
+
+/**
+ * Variable: node
+ * 
+ * The internal node representation of the root cell. Do not set directly
+ * , this value is only exposed to assist with post-processing functionality
+ */
+mxCompactTreeLayout.prototype.node = null;
+
+/**
+ * Function: isVertexIgnored
+ * 
+ * Returns a boolean indicating if the given <mxCell> should be ignored as a
+ * vertex. This returns true if the cell has no connections.
+ * 
+ * Parameters:
+ * 
+ * vertex - <mxCell> whose ignored state should be returned.
+ */
+mxCompactTreeLayout.prototype.isVertexIgnored = function(vertex)
+{
+	return mxGraphLayout.prototype.isVertexIgnored.apply(this, arguments) ||
+		this.graph.getConnections(vertex).length == 0;
+};
+
+/**
+ * Function: isHorizontal
+ * 
+ * Returns <horizontal>.
+ */
+mxCompactTreeLayout.prototype.isHorizontal = function()
+{
+	return this.horizontal;
+};
+
+/**
+ * Function: execute
+ * 
+ * Implements <mxGraphLayout.execute>.
+ * 
+ * If the parent has any connected edges, then it is used as the root of
+ * the tree. Else, <mxGraph.findTreeRoots> will be used to find a suitable
+ * root node within the set of children of the given parent.
+ * 
+ * Parameters:
+ * 
+ * parent - <mxCell> whose children should be laid out.
+ * root - Optional <mxCell> that will be used as the root of the tree.
+ * Overrides <root> if specified.
+ */
+mxCompactTreeLayout.prototype.execute = function(parent, root)
+{
+	this.parent = parent;
+	var model = this.graph.getModel();
+
+	if (root == null)
+	{
+		// Takes the parent as the root if it has outgoing edges
+		if (this.graph.getEdges(parent, model.getParent(parent),
+			this.invert, !this.invert, false).length > 0)
+		{
+			this.root = parent;
+		}
+		
+		// Tries to find a suitable root in the parent's
+		// children
+		else
+		{
+			var roots = this.graph.findTreeRoots(parent, true, this.invert);
+			
+			if (roots.length > 0)
+			{
+				for (var i = 0; i < roots.length; i++)
+				{
+					if (!this.isVertexIgnored(roots[i]) &&
+						this.graph.getEdges(roots[i], null,
+							this.invert, !this.invert, false).length > 0)
+					{
+						this.root = roots[i];
+						break;
+					}
+				}
+			}
+		}
+	}
+	else
+	{
+		this.root = root;
+	}
+	
+	if (this.root != null)
+	{
+		if (this.resizeParent)
+		{
+			this.parentsChanged = new Object();
+		}
+		else
+		{
+			this.parentsChanged = null;
+		}
+
+		//  Maintaining parent location
+		this.parentX = null;
+		this.parentY = null;
+		
+		if (parent != this.root && model.isVertex(parent) != null && this.maintainParentLocation)
+		{
+			var geo = this.graph.getCellGeometry(parent);
+			
+			if (geo != null)
+			{
+				this.parentX = geo.x;
+				this.parentY = geo.y;
+			}
+		}
+		
+		model.beginUpdate();
+		
+		try
+		{
+			this.visited = new Object();
+			this.node = this.dfs(this.root, parent);
+			
+			if (this.alignRanks)
+			{
+				this.maxRankHeight = [];
+				this.findRankHeights(this.node, 0);
+				this.setCellHeights(this.node, 0);
+			}
+			
+			if (this.node != null)
+			{
+				this.layout(this.node);
+				var x0 = this.graph.gridSize;
+				var y0 = x0;
+				
+				if (!this.moveTree)
+				{
+					var g = this.getVertexBounds(this.root);
+					
+					if (g != null)
+					{
+						x0 = g.x;
+						y0 = g.y;
+					}
+				}
+				
+				var bounds = null;
+				
+				if (this.isHorizontal())
+				{
+					bounds = this.horizontalLayout(this.node, x0, y0);
+				}
+				else
+				{
+					bounds = this.verticalLayout(this.node, null, x0, y0);
+				}
+
+				if (bounds != null)
+				{
+					var dx = 0;
+					var dy = 0;
+
+					if (bounds.x < 0)
+					{
+						dx = Math.abs(x0 - bounds.x);
+					}
+
+					if (bounds.y < 0)
+					{
+						dy = Math.abs(y0 - bounds.y);	
+					}
+
+					if (dx != 0 || dy != 0)
+					{
+						this.moveNode(this.node, dx, dy);
+					}
+					
+					if (this.resizeParent)
+					{
+						this.adjustParents();
+					}
+
+					if (this.edgeRouting)
+					{
+						// Iterate through all edges setting their positions
+						this.localEdgeProcessing(this.node);
+					}
+				}
+				
+				// Maintaining parent location
+				if (this.parentX != null && this.parentY != null)
+				{
+					var geo = this.graph.getCellGeometry(parent);
+					
+					if (geo != null)
+					{
+						geo = geo.clone();
+						geo.x = this.parentX;
+						geo.y = this.parentY;
+						model.setGeometry(parent, geo);
+					}
+				}
+			}
+		}
+		finally
+		{
+			model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: moveNode
+ * 
+ * Moves the specified node and all of its children by the given amount.
+ */
+mxCompactTreeLayout.prototype.moveNode = function(node, dx, dy)
+{
+	node.x += dx;
+	node.y += dy;
+	this.apply(node);
+	
+	var child = node.child;
+	
+	while (child != null)
+	{
+		this.moveNode(child, dx, dy);
+		child = child.next;
+	}
+};
+
+
+/**
+ * Function: sortOutgoingEdges
+ * 
+ * Called if <sortEdges> is true to sort the array of outgoing edges in place.
+ */
+mxCompactTreeLayout.prototype.sortOutgoingEdges = function(source, edges)
+{
+	var lookup = new mxDictionary();
+	
+	edges.sort(function(e1, e2)
+	{
+		var end1 = e1.getTerminal(e1.getTerminal(false) == source);
+		var p1 = lookup.get(end1);
+		
+		if (p1 == null)
+		{
+			p1 = mxCellPath.create(end1).split(mxCellPath.PATH_SEPARATOR);
+			lookup.put(end1, p1);
+		}
+
+		var end2 = e2.getTerminal(e2.getTerminal(false) == source);
+		var p2 = lookup.get(end2);
+		
+		if (p2 == null)
+		{
+			p2 = mxCellPath.create(end2).split(mxCellPath.PATH_SEPARATOR);
+			lookup.put(end2, p2);
+		}
+
+		return mxCellPath.compare(p1, p2);
+	});
+};
+
+/**
+ * Function: findRankHeights
+ * 
+ * Stores the maximum height (relative to the layout
+ * direction) of cells in each rank
+ */
+mxCompactTreeLayout.prototype.findRankHeights = function(node, rank)
+{
+	if (this.maxRankHeight[rank] == null || this.maxRankHeight[rank] < node.height)
+	{
+		this.maxRankHeight[rank] = node.height;
+	}
+
+	var child = node.child;
+	
+	while (child != null)
+	{
+		this.findRankHeights(child, rank + 1);
+		child = child.next;
+	}
+};
+
+/**
+ * Function: setCellHeights
+ * 
+ * Set the cells heights (relative to the layout
+ * direction) when the tops of each rank are to be aligned
+ */
+mxCompactTreeLayout.prototype.setCellHeights = function(node, rank)
+{
+	if (this.maxRankHeight[rank] != null && this.maxRankHeight[rank] > node.height)
+	{
+		node.height = this.maxRankHeight[rank];
+	}
+
+	var child = node.child;
+	
+	while (child != null)
+	{
+		this.setCellHeights(child, rank + 1);
+		child = child.next;
+	}
+};
+
+/**
+ * Function: dfs
+ * 
+ * Does a depth first search starting at the specified cell.
+ * Makes sure the specified parent is never left by the
+ * algorithm.
+ */
+mxCompactTreeLayout.prototype.dfs = function(cell, parent)
+{
+	var id = mxCellPath.create(cell);
+	var node = null;
+	
+	if (cell != null && this.visited[id] == null && !this.isVertexIgnored(cell))
+	{
+		this.visited[id] = cell;
+		node = this.createNode(cell);
+
+		var model = this.graph.getModel();
+		var prev = null;
+		var out = this.graph.getEdges(cell, parent, this.invert, !this.invert, false, true);
+		var view = this.graph.getView();
+		
+		if (this.sortEdges)
+		{
+			this.sortOutgoingEdges(cell, out);
+		}
+
+		for (var i = 0; i < out.length; i++)
+		{
+			var edge = out[i];
+			
+			if (!this.isEdgeIgnored(edge))
+			{
+				// Resets the points on the traversed edge
+				if (this.resetEdges)
+				{
+					this.setEdgePoints(edge, null);
+				}
+				
+				if (this.edgeRouting)
+				{
+					this.setEdgeStyleEnabled(edge, false);
+					this.setEdgePoints(edge, null);
+				}
+				
+				// Checks if terminal in same swimlane
+				var state = view.getState(edge);
+				var target = (state != null) ? state.getVisibleTerminal(this.invert) : view.getVisibleTerminal(edge, this.invert);
+				var tmp = this.dfs(target, parent);
+				
+				if (tmp != null && model.getGeometry(target) != null)
+				{
+					if (prev == null)
+					{
+						node.child = tmp;
+					}
+					else
+					{
+						prev.next = tmp;
+					}
+					
+					prev = tmp;
+				}
+			}
+		}
+	}
+	
+	return node;
+};
+
+/**
+ * Function: layout
+ * 
+ * Starts the actual compact tree layout algorithm
+ * at the given node.
+ */
+mxCompactTreeLayout.prototype.layout = function(node)
+{
+	if (node != null)
+	{
+		var child = node.child;
+		
+		while (child != null)
+		{
+			this.layout(child);
+			child = child.next;
+		}
+		
+		if (node.child != null)
+		{
+			this.attachParent(node, this.join(node));
+		}
+		else
+		{
+			this.layoutLeaf(node);
+		}
+	}
+};
+
+/**
+ * Function: horizontalLayout
+ */
+mxCompactTreeLayout.prototype.horizontalLayout = function(node, x0, y0, bounds)
+{
+	node.x += x0 + node.offsetX;
+	node.y += y0 + node.offsetY;
+	bounds = this.apply(node, bounds);
+	var child = node.child;
+	
+	if (child != null)
+	{
+		bounds = this.horizontalLayout(child, node.x, node.y, bounds);
+		var siblingOffset = node.y + child.offsetY;
+		var s = child.next;
+		
+		while (s != null)
+		{
+			bounds = this.horizontalLayout(s, node.x + child.offsetX, siblingOffset, bounds);
+			siblingOffset += s.offsetY;
+			s = s.next;
+		}
+	}
+	
+	return bounds;
+};
+	
+/**
+ * Function: verticalLayout
+ */
+mxCompactTreeLayout.prototype.verticalLayout = function(node, parent, x0, y0, bounds)
+{
+	node.x += x0 + node.offsetY;
+	node.y += y0 + node.offsetX;
+	bounds = this.apply(node, bounds);
+	var child = node.child;
+	
+	if (child != null)
+	{
+		bounds = this.verticalLayout(child, node, node.x, node.y, bounds);
+		var siblingOffset = node.x + child.offsetY;
+		var s = child.next;
+		
+		while (s != null)
+		{
+			bounds = this.verticalLayout(s, node, siblingOffset, node.y + child.offsetX, bounds);
+			siblingOffset += s.offsetY;
+			s = s.next;
+		}
+	}
+	
+	return bounds;
+};
+
+/**
+ * Function: attachParent
+ */
+mxCompactTreeLayout.prototype.attachParent = function(node, height)
+{
+	var x = this.nodeDistance + this.levelDistance;
+	var y2 = (height - node.width) / 2 - this.nodeDistance;
+	var y1 = y2 + node.width + 2 * this.nodeDistance - height;
+	
+	node.child.offsetX = x + node.height;
+	node.child.offsetY = y1;
+	
+	node.contour.upperHead = this.createLine(node.height, 0,
+		this.createLine(x, y1, node.contour.upperHead));
+	node.contour.lowerHead = this.createLine(node.height, 0,
+		this.createLine(x, y2, node.contour.lowerHead));
+};
+
+/**
+ * Function: layoutLeaf
+ */
+mxCompactTreeLayout.prototype.layoutLeaf = function(node)
+{
+	var dist = 2 * this.nodeDistance;
+	
+	node.contour.upperTail = this.createLine(
+		node.height + dist, 0);
+	node.contour.upperHead = node.contour.upperTail;
+	node.contour.lowerTail = this.createLine(
+		0, -node.width - dist);
+	node.contour.lowerHead = this.createLine(
+		node.height + dist, 0, node.contour.lowerTail);
+};
+
+/**
+ * Function: join
+ */
+mxCompactTreeLayout.prototype.join = function(node)
+{
+	var dist = 2 * this.nodeDistance;
+	
+	var child = node.child;
+	node.contour = child.contour;
+	var h = child.width + dist;
+	var sum = h;
+	child = child.next;
+	
+	while (child != null)
+	{
+		var d = this.merge(node.contour, child.contour);
+		child.offsetY = d + h;
+		child.offsetX = 0;
+		h = child.width + dist;
+		sum += d + h;
+		child = child.next;
+	}
+	
+	return sum;
+};
+
+/**
+ * Function: merge
+ */
+mxCompactTreeLayout.prototype.merge = function(p1, p2)
+{
+	var x = 0;
+	var y = 0;
+	var total = 0;
+	
+	var upper = p1.lowerHead;
+	var lower = p2.upperHead;
+	
+	while (lower != null && upper != null)
+	{
+		var d = this.offset(x, y, lower.dx, lower.dy,
+			upper.dx, upper.dy);
+		y += d;
+		total += d;
+		
+		if (x + lower.dx <= upper.dx)
+		{
+			x += lower.dx;
+			y += lower.dy;
+			lower = lower.next;
+		}
+		else
+		{				
+			x -= upper.dx;
+			y -= upper.dy;
+			upper = upper.next;
+		}
+	}
+	
+	if (lower != null)
+	{
+		var b = this.bridge(p1.upperTail, 0, 0, lower, x, y);
+		p1.upperTail = (b.next != null) ? p2.upperTail : b;
+		p1.lowerTail = p2.lowerTail;
+	}
+	else
+	{
+		var b = this.bridge(p2.lowerTail, x, y, upper, 0, 0);
+		
+		if (b.next == null)
+		{
+			p1.lowerTail = b;
+		}
+	}
+	
+	p1.lowerHead = p2.lowerHead;
+	
+	return total;
+};
+
+/**
+ * Function: offset
+ */
+mxCompactTreeLayout.prototype.offset = function(p1, p2, a1, a2, b1, b2)
+{
+	var d = 0;
+	
+	if (b1 <= p1 || p1 + a1 <= 0)
+	{
+		return 0;
+	}
+
+	var t = b1 * a2 - a1 * b2;
+	
+	if (t > 0)
+	{
+		if (p1 < 0)
+		{
+			var s = p1 * a2;
+			d = s / a1 - p2;
+		}
+		else if (p1 > 0)
+		{
+			var s = p1 * b2;
+			d = s / b1 - p2;
+		}
+		else
+		{
+			d = -p2;
+		}
+	}
+	else if (b1 < p1 + a1)
+	{
+		var s = (b1 - p1) * a2;
+		d = b2 - (p2 + s / a1);
+	}
+	else if (b1 > p1 + a1)
+	{
+		var s = (a1 + p1) * b2;
+		d = s / b1 - (p2 + a2);
+	}
+	else
+	{
+		d = b2 - (p2 + a2);
+	}
+
+	if (d > 0)
+	{
+		return d;
+	}
+	else
+	{
+		return 0;
+	}
+};
+
+/**
+ * Function: bridge
+ */
+mxCompactTreeLayout.prototype.bridge = function(line1, x1, y1, line2, x2, y2)
+{
+	var dx = x2 + line2.dx - x1;
+	var dy = 0;
+	var s = 0;
+	
+	if (line2.dx == 0)
+	{
+		dy = line2.dy;
+	}
+	else
+	{
+		s = dx * line2.dy;
+		dy = s / line2.dx;
+	}
+	
+	var r = this.createLine(dx, dy, line2.next);
+	line1.next = this.createLine(0, y2 + line2.dy - dy - y1, r);
+	
+	return r;
+};
+
+/**
+ * Function: createNode
+ */
+mxCompactTreeLayout.prototype.createNode = function(cell)
+{
+	var node = new Object();
+	node.cell = cell;
+	node.x = 0;
+	node.y = 0;
+	node.width = 0;
+	node.height = 0;
+	
+	var geo = this.getVertexBounds(cell);
+	
+	if (geo != null)
+	{
+		if (this.isHorizontal())
+		{
+			node.width = geo.height;
+			node.height = geo.width;			
+		}
+		else
+		{
+			node.width = geo.width;
+			node.height = geo.height;
+		}
+	}
+	
+	node.offsetX = 0;
+	node.offsetY = 0;
+	node.contour = new Object();
+	
+	return node;
+};
+
+/**
+ * Function: apply
+ */
+mxCompactTreeLayout.prototype.apply = function(node, bounds)
+{
+	var model = this.graph.getModel();
+	var cell = node.cell;
+	var g = model.getGeometry(cell);
+
+	if (cell != null && g != null)
+	{
+		if (this.isVertexMovable(cell))
+		{
+			g = this.setVertexLocation(cell, node.x, node.y);
+			
+			if (this.resizeParent)
+			{
+				var parent = model.getParent(cell);
+				var id = mxCellPath.create(parent);
+				
+				// Implements set semantic
+				if (this.parentsChanged[id] == null)
+				{
+					this.parentsChanged[id] = parent;					
+				}
+			}
+		}
+		
+		if (bounds == null)
+		{
+			bounds = new mxRectangle(g.x, g.y, g.width, g.height);
+		}
+		else
+		{
+			bounds = new mxRectangle(Math.min(bounds.x, g.x),
+				Math.min(bounds.y, g.y),
+				Math.max(bounds.x + bounds.width, g.x + g.width),
+				Math.max(bounds.y + bounds.height, g.y + g.height));
+		}
+	}
+	
+	return bounds;
+};
+
+/**
+ * Function: createLine
+ */
+mxCompactTreeLayout.prototype.createLine = function(dx, dy, next)
+{
+	var line = new Object();
+	line.dx = dx;
+	line.dy = dy;
+	line.next = next;
+	
+	return line;
+};
+
+/**
+ * Function: adjustParents
+ * 
+ * Adjust parent cells whose child geometries have changed. The default 
+ * implementation adjusts the group to just fit around the children with 
+ * a padding.
+ */
+mxCompactTreeLayout.prototype.adjustParents = function()
+{
+	var tmp = [];
+	
+	for (var id in this.parentsChanged)
+	{
+		tmp.push(this.parentsChanged[id]);
+	}
+	
+	this.arrangeGroups(mxUtils.sortCells(tmp, true), this.groupPadding, this.groupPaddingTop,
+		this.groupPaddingRight, this.groupPaddingBottom, this.groupPaddingLeft);
+};
+
+/**
+ * Function: localEdgeProcessing
+ *
+ * Moves the specified node and all of its children by the given amount.
+ */
+mxCompactTreeLayout.prototype.localEdgeProcessing = function(node)
+{
+	this.processNodeOutgoing(node);
+	var child = node.child;
+
+	while (child != null)
+	{
+		this.localEdgeProcessing(child);
+		child = child.next;
+	}
+};
+
+/**
+ * Function: 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;
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/layout/mxCompositeLayout.js b/airavata-kubernetes/workflow-composer/src/js/layout/mxCompositeLayout.js
new file mode 100644
index 0000000..8e3e116
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/layout/mxCompositeLayout.js
@@ -0,0 +1,101 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxCompositeLayout
+ * 
+ * Allows to compose multiple layouts into a single layout. The master layout
+ * is the layout that handles move operations if another layout than the first
+ * element in <layouts> should be used. The <master> layout is not executed as
+ * the code assumes that it is part of <layouts>.
+ * 
+ * Example:
+ * (code)
+ * var first = new mxFastOrganicLayout(graph);
+ * var second = new mxParallelEdgeLayout(graph);
+ * var layout = new mxCompositeLayout(graph, [first, second], first);
+ * layout.execute(graph.getDefaultParent());
+ * (end)
+ * 
+ * Constructor: mxCompositeLayout
+ *
+ * Constructs a new layout using the given layouts. The graph instance is
+ * required for creating the transaction that contains all layouts.
+ *
+ * Arguments:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ * layouts - Array of <mxGraphLayouts>.
+ * master - Optional layout that handles moves. If no layout is given then
+ * the first layout of the above array is used to handle moves.
+ */
+function mxCompositeLayout(graph, layouts, master)
+{
+	mxGraphLayout.call(this, graph);
+	this.layouts = layouts;
+	this.master = master;
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxCompositeLayout.prototype = new mxGraphLayout();
+mxCompositeLayout.prototype.constructor = mxCompositeLayout;
+	
+/**
+ * Variable: layouts
+ * 
+ * Holds the array of <mxGraphLayouts> that this layout contains.
+ */
+mxCompositeLayout.prototype.layouts = null;
+
+/**
+ * Variable: layouts
+ * 
+ * Reference to the <mxGraphLayouts> that handles moves. If this is null
+ * then the first layout in <layouts> is used.
+ */
+mxCompositeLayout.prototype.master = null;
+
+/**
+ * Function: moveCell
+ * 
+ * Implements <mxGraphLayout.moveCell> by calling move on <master> or the first
+ * layout in <layouts>.
+ */
+mxCompositeLayout.prototype.moveCell = function(cell, x, y)
+{
+	if (this.master != null)
+	{
+		this.master.move.apply(this.master, arguments);
+	}
+	else
+	{
+		this.layouts[0].move.apply(this.layouts[0], arguments);
+	}
+};
+
+/**
+ * Function: execute
+ * 
+ * Implements <mxGraphLayout.execute> by executing all <layouts> in a
+ * single transaction.
+ */
+mxCompositeLayout.prototype.execute = function(parent)
+{
+	var model = this.graph.getModel();
+	
+	model.beginUpdate();
+	try
+	{
+		for (var i = 0; i < this.layouts.length; i++)
+		{
+			this.layouts[i].execute.apply(this.layouts[i], arguments);
+		}
+	}
+	finally
+	{
+		model.endUpdate();
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/layout/mxEdgeLabelLayout.js b/airavata-kubernetes/workflow-composer/src/js/layout/mxEdgeLabelLayout.js
new file mode 100644
index 0000000..bfba27e
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/layout/mxEdgeLabelLayout.js
@@ -0,0 +1,165 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxEdgeLabelLayout
+ * 
+ * Extends <mxGraphLayout> to implement an edge label layout. This layout
+ * makes use of cell states, which means the graph must be validated in
+ * a graph view (so that the label bounds are available) before this layout
+ * can be executed.
+ * 
+ * Example:
+ * 
+ * (code)
+ * var layout = new mxEdgeLabelLayout(graph);
+ * layout.execute(graph.getDefaultParent());
+ * (end)
+ * 
+ * Constructor: mxEdgeLabelLayout
+ *
+ * Constructs a new edge label layout.
+ *
+ * Arguments:
+ * 
+ * graph - <mxGraph> that contains the cells.
+ */
+function mxEdgeLabelLayout(graph, radius)
+{
+	mxGraphLayout.call(this, graph);
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxEdgeLabelLayout.prototype = new mxGraphLayout();
+mxEdgeLabelLayout.prototype.constructor = mxEdgeLabelLayout;
+
+/**
+ * Function: execute
+ * 
+ * Implements <mxGraphLayout.execute>.
+ */
+mxEdgeLabelLayout.prototype.execute = function(parent)
+{
+	var view = this.graph.view;
+	var model = this.graph.getModel();
+	
+	// Gets all vertices and edges inside the parent
+	var edges = [];
+	var vertices = [];
+	var childCount = model.getChildCount(parent);
+	
+	for (var i = 0; i < childCount; i++)
+	{
+		var cell = model.getChildAt(parent, i);
+		var state = view.getState(cell);
+		
+		if (state != null)
+		{
+			if (!this.isVertexIgnored(cell))
+			{
+				vertices.push(state);
+			}
+			else if (!this.isEdgeIgnored(cell))
+			{
+				edges.push(state);
+			}
+		}
+	}
+	
+	this.placeLabels(vertices, edges);
+};
+
+/**
+ * Function: placeLabels
+ * 
+ * Places the labels of the given edges.
+ */
+mxEdgeLabelLayout.prototype.placeLabels = function(v, e)
+{
+	var model = this.graph.getModel();
+	
+	// Moves the vertices to build a circle. Makes sure the
+	// radius is large enough for the vertices to not
+	// overlap
+	model.beginUpdate();
+	try
+	{
+		for (var i = 0; i < e.length; i++)
+		{
+			var edge = e[i];
+			
+			if (edge != null && edge.text != null &&
+				edge.text.boundingBox != null)
+			{
+				for (var j = 0; j < v.length; j++)
+				{
+					var vertex = v[j];
+					
+					if (vertex != null)
+					{
+						this.avoid(edge, vertex);
+					}
+				}
+			}
+		}
+	}
+	finally
+	{
+		model.endUpdate();
+	}
+};
+
+/**
+ * Function: avoid
+ * 
+ * Places the labels of the given edges.
+ */
+mxEdgeLabelLayout.prototype.avoid = function(edge, vertex)
+{
+	var model = this.graph.getModel();
+	var labRect = edge.text.boundingBox;
+	
+	if (mxUtils.intersects(labRect, vertex))
+	{
+		var dy1 = -labRect.y - labRect.height + vertex.y;
+		var dy2 = -labRect.y + vertex.y + vertex.height;
+		
+		var dy = (Math.abs(dy1) < Math.abs(dy2)) ? dy1 : dy2;
+		
+		var dx1 = -labRect.x - labRect.width + vertex.x;
+		var dx2 = -labRect.x + vertex.x + vertex.width;
+	
+		var dx = (Math.abs(dx1) < Math.abs(dx2)) ? dx1 : dx2;
+		
+		if (Math.abs(dx) < Math.abs(dy))
+		{
+			dy = 0;
+		}
+		else
+		{
+			dx = 0;
+		}
+	
+		var g = model.getGeometry(edge.cell);
+		
+		if (g != null)
+		{
+			g = g.clone();
+			
+			if (g.offset != null)
+			{
+				g.offset.x += dx;
+				g.offset.y += dy;
+			}
+			else
+			{
+				g.offset = new mxPoint(dx, dy);
+			}
+			
+			model.setGeometry(edge.cell, g);
+		}
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/layout/mxFastOrganicLayout.js b/airavata-kubernetes/workflow-composer/src/js/layout/mxFastOrganicLayout.js
new file mode 100644
index 0000000..779bf69
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/layout/mxFastOrganicLayout.js
@@ -0,0 +1,591 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxFastOrganicLayout
+ * 
+ * Extends <mxGraphLayout> to implement a fast organic layout algorithm.
+ * The vertices need to be connected for this layout to work, vertices
+ * with no connections are ignored.
+ * 
+ * Example:
+ * 
+ * (code)
+ * var layout = new mxFastOrganicLayout(graph);
+ * layout.execute(graph.getDefaultParent());
+ * (end)
+ * 
+ * Constructor: mxCompactTreeLayout
+ * 
+ * Constructs a new fast organic layout for the specified graph.
+ */
+function mxFastOrganicLayout(graph)
+{
+	mxGraphLayout.call(this, graph);
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxFastOrganicLayout.prototype = new mxGraphLayout();
+mxFastOrganicLayout.prototype.constructor = mxFastOrganicLayout;
+
+/**
+ * Variable: useInputOrigin
+ * 
+ * Specifies if the top left corner of the input cells should be the origin
+ * of the layout result. Default is true.
+ */
+mxFastOrganicLayout.prototype.useInputOrigin = true;
+
+/**
+ * Variable: resetEdges
+ * 
+ * Specifies if all edge points of traversed edges should be removed.
+ * Default is true.
+ */
+mxFastOrganicLayout.prototype.resetEdges = true;
+
+/**
+ * Variable: disableEdgeStyle
+ * 
+ * Specifies if the STYLE_NOEDGESTYLE flag should be set on edges that are
+ * modified by the result. Default is true.
+ */
+mxFastOrganicLayout.prototype.disableEdgeStyle = true;
+
+/**
+ * Variable: forceConstant
+ * 
+ * The force constant by which the attractive forces are divided and the
+ * replusive forces are multiple by the square of. The value equates to the
+ * average radius there is of free space around each node. Default is 50.
+ */
+mxFastOrganicLayout.prototype.forceConstant = 50;
+
+/**
+ * Variable: forceConstantSquared
+ * 
+ * Cache of <forceConstant>^2 for performance.
+ */
+mxFastOrganicLayout.prototype.forceConstantSquared = 0;
+
+/**
+ * Variable: minDistanceLimit
+ * 
+ * Minimal distance limit. Default is 2. Prevents of
+ * dividing by zero.
+ */
+mxFastOrganicLayout.prototype.minDistanceLimit = 2;
+
+/**
+ * Variable: minDistanceLimit
+ * 
+ * Minimal distance limit. Default is 2. Prevents of
+ * dividing by zero.
+ */
+mxFastOrganicLayout.prototype.maxDistanceLimit = 500;
+
+/**
+ * Variable: minDistanceLimitSquared
+ * 
+ * Cached version of <minDistanceLimit> squared.
+ */
+mxFastOrganicLayout.prototype.minDistanceLimitSquared = 4;
+
+/**
+ * Variable: initialTemp
+ * 
+ * Start value of temperature. Default is 200.
+ */
+mxFastOrganicLayout.prototype.initialTemp = 200;
+
+/**
+ * Variable: temperature
+ * 
+ * Temperature to limit displacement at later stages of layout.
+ */
+mxFastOrganicLayout.prototype.temperature = 0;
+
+/**
+ * Variable: maxIterations
+ * 
+ * Total number of iterations to run the layout though.
+ */
+mxFastOrganicLayout.prototype.maxIterations = 0;
+
+/**
+ * Variable: iteration
+ * 
+ * Current iteration count.
+ */
+mxFastOrganicLayout.prototype.iteration = 0;
+
+/**
+ * Variable: vertexArray
+ * 
+ * An array of all vertices to be laid out.
+ */
+mxFastOrganicLayout.prototype.vertexArray;
+
+/**
+ * Variable: dispX
+ * 
+ * An array of locally stored X co-ordinate displacements for the vertices.
+ */
+mxFastOrganicLayout.prototype.dispX;
+
+/**
+ * Variable: dispY
+ * 
+ * An array of locally stored Y co-ordinate displacements for the vertices.
+ */
+mxFastOrganicLayout.prototype.dispY;
+
+/**
+ * Variable: cellLocation
+ * 
+ * An array of locally stored co-ordinate positions for the vertices.
+ */
+mxFastOrganicLayout.prototype.cellLocation;
+
+/**
+ * Variable: radius
+ * 
+ * The approximate radius of each cell, nodes only.
+ */
+mxFastOrganicLayout.prototype.radius;
+
+/**
+ * Variable: radiusSquared
+ * 
+ * The approximate radius squared of each cell, nodes only.
+ */
+mxFastOrganicLayout.prototype.radiusSquared;
+
+/**
+ * Variable: isMoveable
+ * 
+ * Array of booleans representing the movable states of the vertices.
+ */
+mxFastOrganicLayout.prototype.isMoveable;
+
+/**
+ * Variable: neighbours
+ * 
+ * Local copy of cell neighbours.
+ */
+mxFastOrganicLayout.prototype.neighbours;
+
+/**
+ * Variable: indices
+ * 
+ * Hashtable from cells to local indices.
+ */
+mxFastOrganicLayout.prototype.indices;
+
+/**
+ * Variable: allowedToRun
+ * 
+ * Boolean flag that specifies if the layout is allowed to run. If this is
+ * set to false, then the layout exits in the following iteration.
+ */
+mxFastOrganicLayout.prototype.allowedToRun = true;
+
+/**
+ * Function: isVertexIgnored
+ * 
+ * Returns a boolean indicating if the given <mxCell> should be ignored as a
+ * vertex. This returns true if the cell has no connections.
+ * 
+ * Parameters:
+ * 
+ * vertex - <mxCell> whose ignored state should be returned.
+ */
+mxFastOrganicLayout.prototype.isVertexIgnored = function(vertex)
+{
+	return mxGraphLayout.prototype.isVertexIgnored.apply(this, arguments) ||
+		this.graph.getConnections(vertex).length == 0;
+};
+
+/**
+ * Function: execute
+ * 
+ * Implements <mxGraphLayout.execute>. This operates on all children of the
+ * given parent where <isVertexIgnored> returns false.
+ */
+mxFastOrganicLayout.prototype.execute = function(parent)
+{
+	var model = this.graph.getModel();
+	this.vertexArray = [];
+	var cells = this.graph.getChildVertices(parent);
+	
+	for (var i = 0; i < cells.length; i++)
+	{
+		if (!this.isVertexIgnored(cells[i]))
+		{
+			this.vertexArray.push(cells[i]);
+		}
+	}
+	
+	var initialBounds = (this.useInputOrigin) ?
+			this.graph.getBoundingBoxFromGeometry(this.vertexArray) :
+				null;
+	var n = this.vertexArray.length;
+
+	this.indices = [];
+	this.dispX = [];
+	this.dispY = [];
+	this.cellLocation = [];
+	this.isMoveable = [];
+	this.neighbours = [];
+	this.radius = [];
+	this.radiusSquared = [];
+
+	if (this.forceConstant < 0.001)
+	{
+		this.forceConstant = 0.001;
+	}
+
+	this.forceConstantSquared = this.forceConstant * this.forceConstant;
+
+	// Create a map of vertices first. This is required for the array of
+	// arrays called neighbours which holds, for each vertex, a list of
+	// ints which represents the neighbours cells to that vertex as
+	// the indices into vertexArray
+	for (var i = 0; i < this.vertexArray.length; i++)
+	{
+		var vertex = this.vertexArray[i];
+		this.cellLocation[i] = [];
+		
+		// Set up the mapping from array indices to cells
+		var id = mxObjectIdentity.get(vertex);
+		this.indices[id] = i;
+		var bounds = this.getVertexBounds(vertex);
+
+		// Set the X,Y value of the internal version of the cell to
+		// the center point of the vertex for better positioning
+		var width = bounds.width;
+		var height = bounds.height;
+		
+		// Randomize (0, 0) locations
+		var x = bounds.x;
+		var y = bounds.y;
+		
+		this.cellLocation[i][0] = x + width / 2.0;
+		this.cellLocation[i][1] = y + height / 2.0;
+		this.radius[i] = Math.min(width, height);
+		this.radiusSquared[i] = this.radius[i] * this.radius[i];
+	}
+
+	// Moves cell location back to top-left from center locations used in
+	// algorithm, resetting the edge points is part of the transaction
+	model.beginUpdate();
+	try
+	{
+		for (var i = 0; i < n; i++)
+		{
+			this.dispX[i] = 0;
+			this.dispY[i] = 0;
+			this.isMoveable[i] = this.isVertexMovable(this.vertexArray[i]);
+
+			// Get lists of neighbours to all vertices, translate the cells
+			// obtained in indices into vertexArray and store as an array
+			// against the orginial cell index
+			var edges = this.graph.getConnections(this.vertexArray[i], parent);
+			var cells = this.graph.getOpposites(edges, this.vertexArray[i]);
+			this.neighbours[i] = [];
+
+			for (var j = 0; j < cells.length; j++)
+			{
+				// Resets the points on the traversed edge
+				if (this.resetEdges)
+				{
+					this.graph.resetEdge(edges[j]);
+				}
+
+			    if (this.disableEdgeStyle)
+			    {
+			    	this.setEdgeStyleEnabled(edges[j], false);
+			    }
+
+				// Looks the cell up in the indices dictionary
+				var id = mxObjectIdentity.get(cells[j]);
+				var index = this.indices[id];
+
+				// Check the connected cell in part of the vertex list to be
+				// acted on by this layout
+				if (index != null)
+				{
+					this.neighbours[i][j] = index;
+				}
+
+				// Else if index of the other cell doesn't correspond to
+				// any cell listed to be acted upon in this layout. Set
+				// the index to the value of this vertex (a dummy self-loop)
+				// so the attraction force of the edge is not calculated
+				else
+				{
+					this.neighbours[i][j] = i;
+				}
+			}
+		}
+		this.temperature = this.initialTemp;
+
+		// If max number of iterations has not been set, guess it
+		if (this.maxIterations == 0)
+		{
+			this.maxIterations = 20 * Math.sqrt(n);
+		}
+		
+		// Main iteration loop
+		for (this.iteration = 0; this.iteration < this.maxIterations; this.iteration++)
+		{
+			if (!this.allowedToRun)
+			{
+				return;
+			}
+			
+			// Calculate repulsive forces on all vertices
+			this.calcRepulsion();
+
+			// Calculate attractive forces through edges
+			this.calcAttraction();
+
+			this.calcPositions();
+			this.reduceTemperature();
+		}
+
+		var minx = null;
+		var miny = null;
+		
+		for (var i = 0; i < this.vertexArray.length; i++)
+		{
+			var vertex = this.vertexArray[i];
+			
+			if (this.isVertexMovable(vertex))
+			{
+				var bounds = this.getVertexBounds(vertex);
+				
+				if (bounds != null)
+				{
+					this.cellLocation[i][0] -= bounds.width / 2.0;
+					this.cellLocation[i][1] -= bounds.height / 2.0;
+					
+					var x = this.graph.snap(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/airavata-kubernetes/workflow-composer/src/js/layout/mxGraphLayout.js b/airavata-kubernetes/workflow-composer/src/js/layout/mxGraphLayout.js
new file mode 100644
index 0000000..7a59a3e
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/layout/mxGraphLayout.js
@@ -0,0 +1,461 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxGraphLayout
+ * 
+ * Base class for all layout algorithms in mxGraph. Main public functions are
+ * <move> for handling a moved cell within a layouted parent, and <execute> for
+ * running the layout on a given parent cell.
+ *
+ * Known Subclasses:
+ *
+ * <mxCircleLayout>, <mxCompactTreeLayout>, <mxCompositeLayout>,
+ * <mxFastOrganicLayout>, <mxParallelEdgeLayout>, <mxPartitionLayout>,
+ * <mxStackLayout>
+ * 
+ * Constructor: mxGraphLayout
+ *
+ * Constructs a new layout using the given layouts.
+ *
+ * Arguments:
+ * 
+ * graph - Enclosing 
+ */
+function mxGraphLayout(graph)
+{
+	this.graph = graph;
+};
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxGraphLayout.prototype.graph = null;
+
+/**
+ * Variable: useBoundingBox
+ *
+ * Boolean indicating if the bounding box of the label should be used if
+ * its available. Default is true.
+ */
+mxGraphLayout.prototype.useBoundingBox = true;
+
+/**
+ * Variable: parent
+ *
+ * The parent cell of the layout, if any
+ */
+mxGraphLayout.prototype.parent = null;
+
+/**
+ * Function: moveCell
+ * 
+ * Notified when a cell is being moved in a parent that has automatic
+ * layout to update the cell state (eg. index) so that the outcome of the
+ * layout will position the vertex as close to the point (x, y) as
+ * possible.
+ * 
+ * Empty implementation.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> which has been moved.
+ * x - X-coordinate of the new cell location.
+ * y - Y-coordinate of the new cell location.
+ */
+mxGraphLayout.prototype.moveCell = function(cell, x, y) { };
+
+/**
+ * Function: execute
+ * 
+ * Executes the layout algorithm for the children of the given parent.
+ * 
+ * Parameters:
+ * 
+ * parent - <mxCell> whose children should be layed out.
+ */
+mxGraphLayout.prototype.execute = function(parent) { };
+
+/**
+ * Function: getGraph
+ * 
+ * Returns the graph that this layout operates on.
+ */
+mxGraphLayout.prototype.getGraph = function()
+{
+	return this.graph;
+};
+
+/**
+ * Function: getConstraint
+ * 
+ * Returns the constraint for the given key and cell. The optional edge and
+ * source arguments are used to return inbound and outgoing routing-
+ * constraints for the given edge and vertex. This implementation always
+ * returns the value for the given key in the style of the given cell.
+ * 
+ * Parameters:
+ * 
+ * key - Key of the constraint to be returned.
+ * cell - <mxCell> whose constraint should be returned.
+ * edge - Optional <mxCell> that represents the connection whose constraint
+ * should be returned. Default is null.
+ * source - Optional boolean that specifies if the connection is incoming
+ * or outgoing. Default is null.
+ */
+mxGraphLayout.prototype.getConstraint = function(key, cell, edge, source)
+{
+	var state = this.graph.view.getState(cell);
+	var style = (state != null) ? state.style : this.graph.getCellStyle(cell);
+	
+	return (style != null) ? style[key] : null;
+};
+
+/**
+ * Function: traverse
+ * 
+ * Traverses the (directed) graph invoking the given function for each
+ * visited vertex and edge. The function is invoked with the current vertex
+ * and the incoming edge as a parameter. This implementation makes sure
+ * each vertex is only visited once. The function may return false if the
+ * traversal should stop at the given vertex.
+ * 
+ * Example:
+ * 
+ * (code)
+ * mxLog.show();
+ * var cell = graph.getSelectionCell();
+ * graph.traverse(cell, false, function(vertex, edge)
+ * {
+ *   mxLog.debug(graph.getLabel(vertex));
+ * });
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * vertex - <mxCell> that represents the vertex where the traversal starts.
+ * directed - Optional boolean indicating if edges should only be traversed
+ * from source to target. Default is true.
+ * func - Visitor function that takes the current vertex and the incoming
+ * edge as arguments. The traversal stops if the function returns false.
+ * edge - Optional <mxCell> that represents the incoming edge. This is
+ * null for the first step of the traversal.
+ * visited - Optional <mxDictionary> of cell paths for the visited cells.
+ */
+mxGraphLayout.traverse = function(vertex, directed, func, edge, visited)
+{
+	if (func != null && vertex != null)
+	{
+		directed = (directed != null) ? directed : true;
+		visited = visited || new mxDictionary();
+		
+		if (!visited.get(vertex))
+		{
+			visited.put(vertex, true);
+			var result = func(vertex, edge);
+			
+			if (result == null || result)
+			{
+				var edgeCount = this.graph.model.getEdgeCount(vertex);
+				
+				if (edgeCount > 0)
+				{
+					for (var i = 0; i < edgeCount; i++)
+					{
+						var e = this.graph.model.getEdgeAt(vertex, i);
+						var isSource = this.graph.model.getTerminal(e, true) == vertex;
+												
+						if (!directed || isSource)
+						{
+							var next = this.graph.view.getVisibleTerminal(e, !isSource);
+							this.traverse(next, directed, func, e, visited);
+						}
+					}
+				}
+			}
+		}
+	}
+};
+
+/**
+ * Function: isVertexMovable
+ * 
+ * Returns a boolean indicating if the given <mxCell> is movable or
+ * bendable by the algorithm. This implementation returns true if the given
+ * cell is movable in the graph.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose movable state should be returned.
+ */
+mxGraphLayout.prototype.isVertexMovable = function(cell)
+{
+	return this.graph.isCellMovable(cell);
+};
+
+/**
+ * Function: isVertexIgnored
+ * 
+ * Returns a boolean indicating if the given <mxCell> should be ignored by
+ * the algorithm. This implementation returns false for all vertices.
+ * 
+ * Parameters:
+ * 
+ * vertex - <mxCell> whose ignored state should be returned.
+ */
+mxGraphLayout.prototype.isVertexIgnored = function(vertex)
+{
+	return !this.graph.getModel().isVertex(vertex) ||
+		!this.graph.isCellVisible(vertex);
+};
+
+/**
+ * Function: isEdgeIgnored
+ * 
+ * Returns a boolean indicating if the given <mxCell> should be ignored by
+ * the algorithm. This implementation returns false for all vertices.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose ignored state should be returned.
+ */
+mxGraphLayout.prototype.isEdgeIgnored = function(edge)
+{
+	var model = this.graph.getModel();
+	
+	return !model.isEdge(edge) ||
+		!this.graph.isCellVisible(edge) ||
+		model.getTerminal(edge, true) == null ||
+		model.getTerminal(edge, false) == null;
+};
+
+/**
+ * Function: setEdgeStyleEnabled
+ * 
+ * Disables or enables the edge style of the given edge.
+ */
+mxGraphLayout.prototype.setEdgeStyleEnabled = function(edge, value)
+{
+	this.graph.setCellStyles(mxConstants.STYLE_NOEDGESTYLE,
+			(value) ? '0' : '1', [edge]);
+};
+
+/**
+ * Function: setOrthogonalEdge
+ * 
+ * Disables or enables orthogonal end segments of the given edge.
+ */
+mxGraphLayout.prototype.setOrthogonalEdge = function(edge, value)
+{
+	this.graph.setCellStyles(mxConstants.STYLE_ORTHOGONAL,
+			(value) ? '1' : '0', [edge]);
+};
+
+/**
+ * Function: getParentOffset
+ * 
+ * Determines the offset of the given parent to the parent
+ * of the layout
+ */
+mxGraphLayout.prototype.getParentOffset = function(parent)
+{
+	var result = new mxPoint();
+
+	if (parent != null && parent != this.parent)
+	{
+		var model = this.graph.getModel();
+
+		if (model.isAncestor(this.parent, parent))
+		{
+			var parentGeo = model.getGeometry(parent);
+
+			while (parent != this.parent)
+			{
+				result.x = result.x + parentGeo.x;
+				result.y = result.y + parentGeo.y;
+
+				parent = model.getParent(parent);;
+				parentGeo = model.getGeometry(parent);
+			}
+		}
+	}
+
+	return result;
+};
+
+/**
+ * Function: setEdgePoints
+ * 
+ * Replaces the array of mxPoints in the geometry of the given edge
+ * with the given array of mxPoints.
+ */
+mxGraphLayout.prototype.setEdgePoints = function(edge, points)
+{
+	if (edge != null)
+	{
+		var model = this.graph.model;
+		var geometry = model.getGeometry(edge);
+
+		if (geometry == null)
+		{
+			geometry = new mxGeometry();
+			geometry.setRelative(true);
+		}
+		else
+		{
+			geometry = geometry.clone();
+		}
+
+		if (this.parent != null && points != null)
+		{
+			var parent = model.getParent(edge);
+
+			var parentOffset = this.getParentOffset(parent);
+
+			for (var i = 0; i < points.length; i++)
+			{
+				points[i].x = points[i].x - parentOffset.x;
+				points[i].y = points[i].y - parentOffset.y;
+			}
+		}
+
+		geometry.points = points;
+		model.setGeometry(edge, geometry);
+	}
+};
+
+/**
+ * Function: setVertexLocation
+ * 
+ * Sets the new position of the given cell taking into account the size of
+ * the bounding box if <useBoundingBox> is true. The change is only carried
+ * out if the new location is not equal to the existing location, otherwise
+ * the geometry is not replaced with an updated instance. The new or old
+ * bounds are returned (including overlapping labels).
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose geometry is to be set.
+ * x - Integer that defines the x-coordinate of the new location.
+ * y - Integer that defines the y-coordinate of the new location.
+ */
+mxGraphLayout.prototype.setVertexLocation = function(cell, x, y)
+{
+	var model = this.graph.getModel();
+	var geometry = model.getGeometry(cell);
+	var result = null;
+	
+	if (geometry != null)
+	{
+		result = new mxRectangle(x, y, geometry.width, geometry.height);
+		
+		// Checks for oversize labels and shifts the result
+		// TODO: Use mxUtils.getStringSize for label bounds
+		if (this.useBoundingBox)
+		{
+			var state = this.graph.getView().getState(cell);
+			
+			if (state != null && state.text != null && state.text.boundingBox != null)
+			{
+				var scale = this.graph.getView().scale;
+				var box = state.text.boundingBox;
+				
+				if (state.text.boundingBox.x < state.x)
+				{
+					x += (state.x - box.x) / scale;
+					result.width = box.width;
+				}
+				
+				if (state.text.boundingBox.y < state.y)
+				{
+					y += (state.y - box.y) / scale;
+					result.height = box.height;
+				}
+			}
+		}
+
+		if (this.parent != null)
+		{
+			var parent = model.getParent(cell);
+
+			if (parent != null && parent != this.parent)
+			{
+				var parentOffset = this.getParentOffset(parent);
+
+				x = x - parentOffset.x;
+				y = y - parentOffset.y;
+			}
+		}
+
+		if (geometry.x != x || geometry.y != y)
+		{
+			geometry = geometry.clone();
+			geometry.x = x;
+			geometry.y = y;
+			
+			model.setGeometry(cell, geometry);
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: getVertexBounds
+ * 
+ * Returns an <mxRectangle> that defines the bounds of the given cell or
+ * the bounding box if <useBoundingBox> is true.
+ */
+mxGraphLayout.prototype.getVertexBounds = function(cell)
+{
+	var geo = this.graph.getModel().getGeometry(cell);
+
+	// Checks for oversize label bounding box and corrects
+	// the return value accordingly
+	// TODO: Use mxUtils.getStringSize for label bounds
+	if (this.useBoundingBox)
+	{
+		var state = this.graph.getView().getState(cell);
+
+		if (state != null && state.text != null && state.text.boundingBox != null)
+		{
+			var scale = this.graph.getView().scale;
+			var tmp = state.text.boundingBox;
+
+			var dx0 = Math.max(state.x - tmp.x, 0) / scale;
+			var dy0 = Math.max(state.y - tmp.y, 0) / scale;
+			var dx1 = Math.max((tmp.x + tmp.width) - (state.x + state.width), 0) / scale;
+  			var dy1 = Math.max((tmp.y + tmp.height) - (state.y + state.height), 0) / scale;
+
+			geo = new mxRectangle(geo.x - dx0, geo.y - dy0, geo.width + dx0 + dx1, geo.height + dy0 + dy1);
+		}
+	}
+
+	if (this.parent != null)
+	{
+		var parent = this.graph.getModel().getParent(cell);
+		geo = geo.clone();
+
+		if (parent != null && parent != this.parent)
+		{
+			var parentOffset = this.getParentOffset(parent);
+			geo.x = geo.x + parentOffset.x;
+			geo.y = geo.y + parentOffset.y;
+		}
+	}
+
+	return new mxRectangle(geo.x, geo.y, geo.width, geo.height);
+};
+
+/**
+ * Function: arrangeGroups
+ * 
+ * Shortcut to <mxGraph.updateGroupBounds> with moveGroup set to true.
+ */
+mxGraphLayout.prototype.arrangeGroups = function(cells, border, topBorder, rightBorder, bottomBorder, leftBorder)
+{
+	return this.graph.updateGroupBounds(cells, border, true, topBorder, rightBorder, bottomBorder, leftBorder);
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/layout/mxParallelEdgeLayout.js b/airavata-kubernetes/workflow-composer/src/js/layout/mxParallelEdgeLayout.js
new file mode 100644
index 0000000..73d436a
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/layout/mxParallelEdgeLayout.js
@@ -0,0 +1,225 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxParallelEdgeLayout
+ * 
+ * Extends <mxGraphLayout> for arranging parallel edges. This layout works
+ * on edges for all pairs of vertices where there is more than one edge
+ * connecting the latter.
+ * 
+ * Example:
+ * 
+ * (code)
+ * var layout = new mxParallelEdgeLayout(graph);
+ * layout.execute(graph.getDefaultParent());
+ * (end)
+ * 
+ * To run the layout for the parallel edges of a changed edge only, the
+ * following code can be used.
+ * 
+ * (code)
+ * var layout = new mxParallelEdgeLayout(graph);
+ * 
+ * graph.addListener(mxEvent.CELL_CONNECTED, function(sender, evt)
+ * {
+ *   var model = graph.getModel();
+ *   var edge = evt.getProperty('edge');
+ *   var src = model.getTerminal(edge, true);
+ *   var trg = model.getTerminal(edge, false);
+ *   
+ *   layout.isEdgeIgnored = function(edge2)
+ *   {
+ *     var src2 = model.getTerminal(edge2, true);
+ *     var trg2 = model.getTerminal(edge2, false);
+ *     
+ *     return !(model.isEdge(edge2) && ((src == src2 && trg == trg2) || (src == trg2 && trg == src2)));
+ *   };
+ *   
+ *   layout.execute(graph.getDefaultParent());
+ * });
+ * (end)
+ * 
+ * Constructor: mxCompactTreeLayout
+ * 
+ * Constructs a new fast organic layout for the specified graph.
+ */
+function mxParallelEdgeLayout(graph)
+{
+	mxGraphLayout.call(this, graph);
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxParallelEdgeLayout.prototype = new mxGraphLayout();
+mxParallelEdgeLayout.prototype.constructor = mxParallelEdgeLayout;
+
+/**
+ * Variable: spacing
+ * 
+ * Defines the spacing between the parallels. Default is 20.
+ */
+mxParallelEdgeLayout.prototype.spacing = 20;
+
+/**
+ * Function: execute
+ * 
+ * Implements <mxGraphLayout.execute>.
+ */
+mxParallelEdgeLayout.prototype.execute = function(parent)
+{
+	var lookup = this.findParallels(parent);
+	
+	this.graph.model.beginUpdate();	
+	try
+	{
+		for (var i in lookup)
+		{
+			var parallels = lookup[i];
+
+			if (parallels.length > 1)
+			{
+				this.layout(parallels);
+			}
+		}
+	}
+	finally
+	{
+		this.graph.model.endUpdate();
+	}
+};
+
+/**
+ * Function: findParallels
+ * 
+ * Finds the parallel edges in the given parent.
+ */
+mxParallelEdgeLayout.prototype.findParallels = function(parent)
+{
+	var model = this.graph.getModel();
+	var lookup = [];
+	var childCount = model.getChildCount(parent);
+	
+	for (var i = 0; i < childCount; i++)
+	{
+		var child = model.getChildAt(parent, i);
+		
+		if (!this.isEdgeIgnored(child))
+		{
+			var id = this.getEdgeId(child);
+			
+			if (id != null)
+			{
+				if (lookup[id] == null)
+				{
+					lookup[id] = [];
+				}
+				
+				lookup[id].push(child);
+			}
+		}
+	}
+	
+	return lookup;
+};
+
+/**
+ * Function: getEdgeId
+ * 
+ * Returns a unique ID for the given edge. The id is independent of the
+ * edge direction and is built using the visible terminal of the given
+ * edge.
+ */
+mxParallelEdgeLayout.prototype.getEdgeId = function(edge)
+{
+	var view = this.graph.getView();
+	
+	// Cannot used cached visible terminal because this could be triggered in BEFORE_UNDO
+	var src = view.getVisibleTerminal(edge, true);
+	var trg = view.getVisibleTerminal(edge, false);
+
+	if (src != null && trg != null)
+	{
+		src = mxObjectIdentity.get(src);
+		trg = mxObjectIdentity.get(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 view = this.graph.getView();
+	var model = this.graph.getModel();
+	var src = model.getGeometry(view.getVisibleTerminal(edge, true));
+	var trg = model.getGeometry(view.getVisibleTerminal(edge, false));
+	
+	// Routes multiple loops
+	if (src == trg)
+	{
+		var x0 = src.x + src.width + this.spacing;
+		var y0 = src.y + src.height / 2;
+
+		for (var i = 0; i < parallels.length; i++)
+		{
+			this.route(parallels[i], x0, y0);
+			x0 += this.spacing;
+		}
+	}
+	else if (src != null && trg != null)
+	{
+		// Routes parallel edges
+		var scx = src.x + src.width / 2;
+		var scy = src.y + src.height / 2;
+		
+		var tcx = trg.x + trg.width / 2;
+		var tcy = trg.y + trg.height / 2;
+		
+		var dx = tcx - scx;
+		var dy = tcy - scy;
+
+		var len = Math.sqrt(dx * dx + dy * dy);
+		
+		if (len > 0)
+		{
+			var x0 = scx + dx / 2;
+			var y0 = scy + dy / 2;
+			
+			var nx = dy * this.spacing / len;
+			var ny = dx * this.spacing / len;
+			
+			x0 += nx * (parallels.length - 1) / 2;
+			y0 -= ny * (parallels.length - 1) / 2;
+	
+			for (var i = 0; i < parallels.length; i++)
+			{
+				this.route(parallels[i], x0, y0);
+				x0 -= nx;
+				y0 += ny;
+			}
+		}
+	}
+};
+
+/**
+ * Function: route
+ * 
+ * Routes the given edge via the given point.
+ */
+mxParallelEdgeLayout.prototype.route = function(edge, x, y)
+{
+	if (this.graph.isCellMovable(edge))
+	{
+		this.setEdgePoints(edge, [new mxPoint(x, y)]);
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/layout/mxPartitionLayout.js b/airavata-kubernetes/workflow-composer/src/js/layout/mxPartitionLayout.js
new file mode 100644
index 0000000..ad66f54
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/layout/mxPartitionLayout.js
@@ -0,0 +1,240 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxPartitionLayout
+ * 
+ * Extends <mxGraphLayout> for partitioning the parent cell vertically or
+ * horizontally by filling the complete area with the child cells. A horizontal
+ * layout partitions the height of the given parent whereas a a non-horizontal
+ * layout partitions the width. If the parent is a layer (that is, a child of
+ * the root node), then the current graph size is partitioned. The children do
+ * not need to be connected for this layout to work.
+ * 
+ * Example:
+ * 
+ * (code)
+ * var layout = new mxPartitionLayout(graph, true, 10, 20);
+ * layout.execute(graph.getDefaultParent());
+ * (end)
+ * 
+ * Constructor: mxPartitionLayout
+ * 
+ * Constructs a new stack layout layout for the specified graph,
+ * spacing, orientation and offset.
+ */
+function mxPartitionLayout(graph, horizontal, spacing, border)
+{
+	mxGraphLayout.call(this, graph);
+	this.horizontal = (horizontal != null) ? horizontal : true;
+	this.spacing = spacing || 0;
+	this.border = border || 0;
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxPartitionLayout.prototype = new mxGraphLayout();
+mxPartitionLayout.prototype.constructor = mxPartitionLayout;
+
+/**
+ * Variable: horizontal
+ * 
+ * Boolean indicating the direction in which the space is partitioned.
+ * Default is true.
+ */
+mxPartitionLayout.prototype.horizontal = null;
+
+/**
+ * Variable: spacing
+ * 
+ * Integer that specifies the absolute spacing in pixels between the
+ * children. Default is 0.
+ */
+mxPartitionLayout.prototype.spacing = null;
+
+/**
+ * Variable: border
+ * 
+ * Integer that specifies the absolute inset in pixels for the parent that
+ * contains the children. Default is 0.
+ */
+mxPartitionLayout.prototype.border = null;
+
+/**
+ * Variable: resizeVertices
+ * 
+ * Boolean that specifies if vertices should be resized. Default is true.
+ */
+mxPartitionLayout.prototype.resizeVertices = true;
+
+/**
+ * Function: isHorizontal
+ * 
+ * Returns <horizontal>.
+ */
+mxPartitionLayout.prototype.isHorizontal = function()
+{
+	return this.horizontal;
+};
+
+/**
+ * Function: moveCell
+ * 
+ * Implements <mxGraphLayout.moveCell>.
+ */
+mxPartitionLayout.prototype.moveCell = function(cell, x, y)
+{
+	var model = this.graph.getModel();
+	var parent = model.getParent(cell);
+	
+	if (cell != null &&
+		parent != null)
+	{
+		var i = 0;
+		var last = 0;
+		var childCount = model.getChildCount(parent);
+		
+		// Finds index of the closest swimlane
+		// TODO: Take into account the orientation
+		for (i = 0; i < childCount; i++)
+		{
+			var child = model.getChildAt(parent, i);
+			var bounds = this.getVertexBounds(child);
+			
+			if (bounds != null)
+			{
+				var tmp = bounds.x + bounds.width / 2;
+				
+				if (last < x && tmp > x)
+				{
+					break;
+				}
+				
+				last = tmp;
+			}
+		}
+		
+		// Changes child order in parent
+		var idx = parent.getIndex(cell);
+		idx = Math.max(0, i - ((i > idx) ? 1 : 0));
+		
+		model.add(parent, cell, idx);
+	}
+};
+
+/**
+ * Function: execute
+ * 
+ * Implements <mxGraphLayout.execute>. All children where <isVertexIgnored>
+ * returns false and <isVertexMovable> returns true are modified.
+ */
+mxPartitionLayout.prototype.execute = function(parent)
+{
+	var horizontal = this.isHorizontal();
+	var model = this.graph.getModel();
+	var pgeo = model.getGeometry(parent);
+	
+	// Handles special case where the parent is either a layer with no
+	// geometry or the current root of the view in which case the size
+	// of the graph's container will be used.
+	if (this.graph.container != null &&
+		((pgeo == null &&
+		model.isLayer(parent)) ||
+		parent == this.graph.getView().currentRoot))
+	{
+		var width = this.graph.container.offsetWidth - 1;
+		var height = this.graph.container.offsetHeight - 1;
+		pgeo = new mxRectangle(0, 0, width, height);
+	}
+
+	if (pgeo != null)
+	{
+		var children = [];
+		var childCount = model.getChildCount(parent);
+		
+		for (var i = 0; i < childCount; i++)
+		{
+			var child = model.getChildAt(parent, i);
+			
+			if (!this.isVertexIgnored(child) &&
+				this.isVertexMovable(child))
+			{
+				children.push(child);
+			}
+		}
+		
+		var n = children.length;
+
+		if (n > 0)
+		{
+			var x0 = this.border;
+			var y0 = this.border;
+			var other = (horizontal) ? pgeo.height : pgeo.width;
+			other -= 2 * this.border;
+
+			var size = (this.graph.isSwimlane(parent)) ?
+				this.graph.getStartSize(parent) :
+				new mxRectangle();
+
+			other -= (horizontal) ? size.height : size.width;
+			x0 = x0 + size.width;
+			y0 = y0 + size.height;
+
+			var tmp = this.border + (n - 1) * this.spacing;
+			var value = (horizontal) ?
+				((pgeo.width - x0 - tmp) / n) :
+				((pgeo.height - y0 - tmp) / n);
+			
+			// Avoids negative values, that is values where the sum of the
+			// spacing plus the border is larger then the available space
+			if (value > 0)
+			{
+				model.beginUpdate();
+				try
+				{
+					for (var i = 0; i < n; i++)
+					{
+						var child = children[i];
+						var geo = model.getGeometry(child);
+					
+						if (geo != null)
+						{
+							geo = geo.clone();
+							geo.x = x0;
+							geo.y = y0;
+
+							if (horizontal)
+							{
+								if (this.resizeVertices)
+								{
+									geo.width = value;
+									geo.height = other;
+								}
+								
+								x0 += value + this.spacing;
+							}
+							else
+							{
+								if (this.resizeVertices)
+								{
+									geo.height = value;
+									geo.width = other;
+								}
+								
+								y0 += value + this.spacing;
+							}
+
+							model.setGeometry(child, geo);
+						}
+					}
+				}
+				finally
+				{
+					model.endUpdate();
+				}
+			}
+		}
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/layout/mxRadialTreeLayout.js b/airavata-kubernetes/workflow-composer/src/js/layout/mxRadialTreeLayout.js
new file mode 100644
index 0000000..427c366
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/layout/mxRadialTreeLayout.js
@@ -0,0 +1,317 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxRadialTreeLayout
+ * 
+ * Extends <mxGraphLayout> to implement a radial tree algorithm. This
+ * layout is suitable for graphs that have no cycles (trees). Vertices that are
+ * not connected to the tree will be ignored by this layout.
+ * 
+ * Example:
+ * 
+ * (code)
+ * var layout = new mxRadialTreeLayout(graph);
+ * layout.execute(graph.getDefaultParent());
+ * (end)
+ * 
+ * Constructor: mxRadialTreeLayout
+ * 
+ * Constructs a new radial tree layout for the specified graph
+ */
+function mxRadialTreeLayout(graph)
+{
+	mxCompactTreeLayout.call(this, graph , false);
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxUtils.extend(mxRadialTreeLayout, mxCompactTreeLayout);
+
+/**
+ * Variable: angleOffset
+ *
+ * The initial offset to compute the angle position.
+ */
+mxRadialTreeLayout.prototype.angleOffset = 0.5;
+
+/**
+ * Variable: rootx
+ *
+ * The X co-ordinate of the root cell
+ */
+mxRadialTreeLayout.prototype.rootx = 0;
+
+/**
+ * Variable: rooty
+ *
+ * The Y co-ordinate of the root cell
+ */
+mxRadialTreeLayout.prototype.rooty = 0;
+
+/**
+ * Variable: levelDistance
+ *
+ * Holds the levelDistance. Default is 120.
+ */
+mxRadialTreeLayout.prototype.levelDistance = 120;
+
+/**
+ * Variable: nodeDistance
+ *
+ * Holds the nodeDistance. Default is 10.
+ */
+mxRadialTreeLayout.prototype.nodeDistance = 10;
+
+/**
+ * Variable: autoRadius
+ * 
+ * Specifies if the radios should be computed automatically
+ */
+mxRadialTreeLayout.prototype.autoRadius = false;
+
+/**
+ * Variable: sortEdges
+ * 
+ * Specifies if edges should be sorted according to the order of their
+ * opposite terminal cell in the model.
+ */
+mxRadialTreeLayout.prototype.sortEdges = false;
+
+/**
+ * Variable: rowMinX
+ * 
+ * Array of leftmost x coordinate of each row
+ */
+mxRadialTreeLayout.prototype.rowMinX = [];
+
+/**
+ * Variable: rowMaxX
+ * 
+ * Array of rightmost x coordinate of each row
+ */
+mxRadialTreeLayout.prototype.rowMaxX = [];
+
+/**
+ * Variable: rowMinCenX
+ * 
+ * Array of x coordinate of leftmost vertex of each row
+ */
+mxRadialTreeLayout.prototype.rowMinCenX = [];
+
+/**
+ * Variable: rowMaxCenX
+ * 
+ * Array of x coordinate of rightmost vertex of each row
+ */
+mxRadialTreeLayout.prototype.rowMaxCenX = [];
+
+/**
+ * Variable: rowRadi
+ * 
+ * Array of y deltas of each row behind root vertex, also the radius in the tree
+ */
+mxRadialTreeLayout.prototype.rowRadi = [];
+
+/**
+ * Variable: row
+ * 
+ * Array of vertices on each row
+ */
+mxRadialTreeLayout.prototype.row = [];
+
+/**
+ * Function: isVertexIgnored
+ * 
+ * Returns a boolean indicating if the given <mxCell> should be ignored as a
+ * vertex. This returns true if the cell has no connections.
+ * 
+ * Parameters:
+ * 
+ * vertex - <mxCell> whose ignored state should be returned.
+ */
+mxRadialTreeLayout.prototype.isVertexIgnored = function(vertex)
+{
+	return mxGraphLayout.prototype.isVertexIgnored.apply(this, arguments) ||
+		this.graph.getConnections(vertex).length == 0;
+};
+
+/**
+ * Function: execute
+ * 
+ * Implements <mxGraphLayout.execute>.
+ * 
+ * If the parent has any connected edges, then it is used as the root of
+ * the tree. Else, <mxGraph.findTreeRoots> will be used to find a suitable
+ * root node within the set of children of the given parent.
+ * 
+ * Parameters:
+ * 
+ * parent - <mxCell> whose children should be laid out.
+ * root - Optional <mxCell> that will be used as the root of the tree.
+ */
+mxRadialTreeLayout.prototype.execute = function(parent, root)
+{
+	this.parent = parent;
+	
+	this.useBoundingBox = false;
+	this.edgeRouting = false;
+	//this.horizontal = false;
+
+	mxCompactTreeLayout.prototype.execute.apply(this, arguments);
+	
+	var bounds = null;
+	var rootBounds = this.getVertexBounds(this.root);
+	this.centerX = rootBounds.x + rootBounds.width / 2;
+	this.centerY = rootBounds.y + rootBounds.height / 2;
+
+	// Calculate the bounds of the involved vertices directly from the values set in the compact tree
+	for (var vertex in this.visited)
+	{
+		var vertexBounds = this.getVertexBounds(this.visited[vertex]);
+		bounds = (bounds != null) ? bounds : vertexBounds.clone();
+		bounds.add(vertexBounds);
+	}
+	
+	this.calcRowDims([this.node], 0);
+	
+	var maxLeftGrad = 0;
+	var maxRightGrad = 0;
+
+	// Find the steepest left and right gradients
+	for (var i = 0; i < this.row.length; i++)
+	{
+		var leftGrad = (this.centerX - this.rowMinX[i] - this.nodeDistance) / this.rowRadi[i];
+		var rightGrad = (this.rowMaxX[i] - this.centerX - this.nodeDistance) / this.rowRadi[i];
+		
+		maxLeftGrad = Math.max (maxLeftGrad, leftGrad);
+		maxRightGrad = Math.max (maxRightGrad, rightGrad);
+	}
+	
+	// Extend out row so they meet the maximum gradient and convert to polar co-ords
+	for (var i = 0; i < this.row.length; i++)
+	{
+		var xLeftLimit = this.centerX - this.nodeDistance - maxLeftGrad * this.rowRadi[i];
+		var xRightLimit = this.centerX + this.nodeDistance + maxRightGrad * this.rowRadi[i];
+		var fullWidth = xRightLimit - xLeftLimit;
+		
+		for (var j = 0; j < this.row[i].length; j ++)
+		{
+			var row = this.row[i];
+			var node = row[j];
+			var vertexBounds = this.getVertexBounds(node.cell);
+			var xProportion = (vertexBounds.x + vertexBounds.width / 2 - xLeftLimit) / (fullWidth);
+			var theta =  2 * Math.PI * xProportion;
+			node.theta = theta;
+		}
+	}
+
+	// Post-process from outside inwards to try to align parents with children
+	for (var i = this.row.length - 2; i >= 0; i--)
+	{
+		var row = this.row[i];
+		
+		for (var j = 0; j < row.length; j++)
+		{
+			var node = row[j];
+			var child = node.child;
+			var counter = 0;
+			var totalTheta = 0;
+			
+			while (child != null)
+			{
+				totalTheta += child.theta;
+				counter++;
+				child = child.next;
+			}
+			
+			if (counter > 0)
+			{
+				var averTheta = totalTheta / counter;
+				
+				if (averTheta > node.theta && j < row.length - 1)
+				{
+					var nextTheta = row[j+1].theta;
+					node.theta = Math.min (averTheta, nextTheta - Math.PI/10);
+				}
+				else if (averTheta < node.theta && j > 0 )
+				{
+					var lastTheta = row[j-1].theta;
+					node.theta = Math.max (averTheta, lastTheta + Math.PI/10);
+				}
+			}
+		}
+	}
+	
+	// Set locations
+	for (var i = 0; i < this.row.length; i++)
+	{
+		for (var j = 0; j < this.row[i].length; j ++)
+		{
+			var row = this.row[i];
+			var node = row[j];
+			var vertexBounds = this.getVertexBounds(node.cell);
+			this.setVertexLocation(node.cell,
+									this.centerX - vertexBounds.width / 2 + this.rowRadi[i] * Math.cos(node.theta),
+									this.centerY - vertexBounds.height / 2 + this.rowRadi[i] * Math.sin(node.theta));
+		}
+	}
+};
+
+/**
+ * Function: calcRowDims
+ * 
+ * Recursive function to calculate the dimensions of each row
+ * 
+ * Parameters:
+ * 
+ * row - Array of internal nodes, the children of which are to be processed.
+ * rowNum - Integer indicating which row is being processed.
+ */
+mxRadialTreeLayout.prototype.calcRowDims = function(row, rowNum)
+{
+	if (row == null || row.length == 0)
+	{
+		return;
+	}
+
+	// Place root's children proportionally around the first level
+	this.rowMinX[rowNum] = this.centerX;
+	this.rowMaxX[rowNum] = this.centerX;
+	this.rowMinCenX[rowNum] = this.centerX;
+	this.rowMaxCenX[rowNum] = this.centerX;
+	this.row[rowNum] = [];
+
+	var rowHasChildren = false;
+
+	for (var i = 0; i < row.length; i++)
+	{
+		var child = row[i] != null ? row[i].child : null;
+
+		while (child != null)
+		{
+			var cell = child.cell;
+			vertexBounds = this.getVertexBounds(cell);
+			
+			this.rowMinX[rowNum] = Math.min(vertexBounds.x, this.rowMinX[rowNum]);
+			this.rowMaxX[rowNum] = Math.max(vertexBounds.x + vertexBounds.width, this.rowMaxX[rowNum]);
+			this.rowMinCenX[rowNum] = Math.min(vertexBounds.x + vertexBounds.width / 2, this.rowMinCenX[rowNum]);
+			this.rowMaxCenX[rowNum] = Math.max(vertexBounds.x + vertexBounds.width / 2, this.rowMaxCenX[rowNum]);
+			this.rowRadi[rowNum] = vertexBounds.y - this.getVertexBounds(this.root).y;
+	
+			if (child.child != null)
+			{
+				rowHasChildren = true;
+			}
+			this.row[rowNum].push(child);
+			child = child.next;
+		}
+	}
+	
+	if (rowHasChildren)
+	{
+		this.calcRowDims(this.row[rowNum], rowNum + 1);
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/layout/mxStackLayout.js b/airavata-kubernetes/workflow-composer/src/js/layout/mxStackLayout.js
new file mode 100644
index 0000000..ed46a5b
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/layout/mxStackLayout.js
@@ -0,0 +1,515 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxStackLayout
+ * 
+ * Extends <mxGraphLayout> to create a horizontal or vertical stack of the
+ * child vertices. The children do not need to be connected for this layout
+ * to work.
+ * 
+ * Example:
+ * 
+ * (code)
+ * var layout = new mxStackLayout(graph, true);
+ * layout.execute(graph.getDefaultParent());
+ * (end)
+ * 
+ * Constructor: mxStackLayout
+ * 
+ * Constructs a new stack layout layout for the specified graph,
+ * spacing, orientation and offset.
+ */
+function mxStackLayout(graph, horizontal, spacing, x0, y0, border)
+{
+	mxGraphLayout.call(this, graph);
+	this.horizontal = (horizontal != null) ? horizontal : true;
+	this.spacing = (spacing != null) ? spacing : 0;
+	this.x0 = (x0 != null) ? x0 : 0;
+	this.y0 = (y0 != null) ? y0 : 0;
+	this.border = (border != null) ? border : 0;
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxStackLayout.prototype = new mxGraphLayout();
+mxStackLayout.prototype.constructor = mxStackLayout;
+
+/**
+ * Variable: horizontal
+ *
+ * Specifies the orientation of the layout. Default is true.
+ */
+mxStackLayout.prototype.horizontal = null;
+
+/**
+ * Variable: spacing
+ *
+ * Specifies the spacing between the cells. Default is 0.
+ */
+mxStackLayout.prototype.spacing = null;
+
+/**
+ * Variable: x0
+ *
+ * Specifies the horizontal origin of the layout. Default is 0.
+ */
+mxStackLayout.prototype.x0 = null;
+
+/**
+ * Variable: y0
+ *
+ * Specifies the vertical origin of the layout. Default is 0.
+ */
+mxStackLayout.prototype.y0 = null;
+
+/**
+ * Variable: border
+ *
+ * Border to be added if fill is true. Default is 0.
+ */
+mxStackLayout.prototype.border = 0;
+
+/**
+ * Variable: marginTop
+ * 
+ * Top margin for the child area. Default is 0.
+ */
+mxStackLayout.prototype.marginTop = 0;
+
+/**
+ * Variable: marginLeft
+ * 
+ * Top margin for the child area. Default is 0.
+ */
+mxStackLayout.prototype.marginLeft = 0;
+
+/**
+ * Variable: marginRight
+ * 
+ * Top margin for the child area. Default is 0.
+ */
+mxStackLayout.prototype.marginRight = 0;
+
+/**
+ * Variable: marginBottom
+ * 
+ * Top margin for the child area. Default is 0.
+ */
+mxStackLayout.prototype.marginBottom = 0;
+
+/**
+ * Variable: keepFirstLocation
+ * 
+ * Boolean indicating if the location of the first cell should be
+ * kept, that is, it will not be moved to x0 or y0.
+ */
+mxStackLayout.prototype.keepFirstLocation = false;
+
+/**
+ * Variable: fill
+ * 
+ * Boolean indicating if dimension should be changed to fill out the parent
+ * cell. Default is false.
+ */
+mxStackLayout.prototype.fill = false;
+	
+/**
+ * Variable: resizeParent
+ * 
+ * If the parent should be resized to match the width/height of the
+ * stack. Default is false.
+ */
+mxStackLayout.prototype.resizeParent = false;
+
+/**
+ * Variable: resizeParentMax
+ * 
+ * Use maximum of existing value and new value for resize of parent.
+ * Default is false.
+ */
+mxStackLayout.prototype.resizeParentMax = false;
+
+/**
+ * Variable: resizeLast
+ * 
+ * If the last element should be resized to fill out the parent. Default is
+ * false. If <resizeParent> is true then this is ignored.
+ */
+mxStackLayout.prototype.resizeLast = false;
+
+/**
+ * Variable: wrap
+ * 
+ * Value at which a new column or row should be created. Default is null.
+ */
+mxStackLayout.prototype.wrap = null;
+
+/**
+ * Variable: borderCollapse
+ * 
+ * If the strokeWidth should be ignored. Default is true.
+ */
+mxStackLayout.prototype.borderCollapse = true;
+
+/**
+ * Function: isHorizontal
+ * 
+ * Returns <horizontal>.
+ */
+mxStackLayout.prototype.isHorizontal = function()
+{
+	return this.horizontal;
+};
+
+/**
+ * Function: moveCell
+ * 
+ * Implements <mxGraphLayout.moveCell>.
+ */
+mxStackLayout.prototype.moveCell = function(cell, x, y)
+{
+	var model = this.graph.getModel();
+	var parent = model.getParent(cell);
+	var horizontal = this.isHorizontal();
+	
+	if (cell != null && parent != null)
+	{
+		var i = 0;
+		var last = 0;
+		var childCount = model.getChildCount(parent);
+		var value = (horizontal) ? x : y;
+		var pstate = this.graph.getView().getState(parent);
+
+		if (pstate != null)
+		{
+			value -= (horizontal) ? pstate.x : pstate.y;
+		}
+		
+		value /= this.graph.view.scale;
+		
+		for (i = 0; i < childCount; i++)
+		{
+			var child = model.getChildAt(parent, i);
+			
+			if (child != cell)
+			{
+				var bounds = model.getGeometry(child);
+				
+				if (bounds != null)
+				{
+					var tmp = (horizontal) ?
+						bounds.x + bounds.width / 2 :
+						bounds.y + bounds.height / 2;
+					
+					if (last <= value && tmp > value)
+					{
+						break;
+					}
+					
+					last = tmp;
+				}
+			}
+		}
+
+		// Changes child order in parent
+		var idx = parent.getIndex(cell);
+		idx = Math.max(0, i - ((i > idx) ? 1 : 0));
+
+		model.add(parent, cell, idx);
+	}
+};
+
+/**
+ * Function: getParentSize
+ * 
+ * Returns the size for the parent container or the size of the graph
+ * container if the parent is a layer or the root of the model.
+ */
+mxStackLayout.prototype.getParentSize = function(parent)
+{
+	var model = this.graph.getModel();			
+	var pgeo = model.getGeometry(parent);
+	
+	// Handles special case where the parent is either a layer with no
+	// geometry or the current root of the view in which case the size
+	// of the graph's container will be used.
+	if (this.graph.container != null && ((pgeo == null &&
+		model.isLayer(parent)) || parent == this.graph.getView().currentRoot))
+	{
+		var width = this.graph.container.offsetWidth - 1;
+		var height = this.graph.container.offsetHeight - 1;
+		pgeo = new mxRectangle(0, 0, width, height);
+	}
+	
+	return pgeo;
+};
+
+/**
+ * Function: execute
+ * 
+ * Implements <mxGraphLayout.execute>.
+ * 
+ * Only children where <isVertexIgnored> returns false are taken into
+ * account.
+ */
+mxStackLayout.prototype.execute = function(parent)
+{
+	if (parent != null)
+	{
+		var pgeo = this.getParentSize(parent);
+		var horizontal = this.isHorizontal();
+		var model = this.graph.getModel();	
+		var fillValue = null;
+		
+		if (pgeo != null)
+		{
+			fillValue = (horizontal) ? pgeo.height - this.marginTop - this.marginBottom :
+				pgeo.width - this.marginLeft - this.marginRight;
+		}
+		
+		fillValue -= 2 * this.spacing + 2 * this.border;
+		var x0 = this.x0 + this.border + this.marginLeft;
+		var y0 = this.y0 + this.border + this.marginTop;
+		
+		// Handles swimlane start size
+		if (this.graph.isSwimlane(parent))
+		{
+			// Uses computed style to get latest 
+			var style = this.graph.getCellStyle(parent);
+			var start = mxUtils.getNumber(style, mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE);
+			var horz = mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, true) == 1;
+
+			if (pgeo != null)
+			{
+				if (horz)
+				{
+					start = Math.min(start, pgeo.height);
+				}
+				else
+				{
+					start = Math.min(start, pgeo.width);
+				}
+			}
+			
+			if (horizontal == horz)
+			{
+				fillValue -= start;
+			}
+
+			if (horz)
+			{
+				y0 += start;
+			}
+			else
+			{
+				x0 += start;
+			}
+		}
+
+		model.beginUpdate();
+		try
+		{
+			var tmp = 0;
+			var last = null;
+			var lastValue = 0;
+			var lastChild = null;
+			var 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);
+						var sw = 0;
+						
+						if (!this.borderCollapse)
+						{
+							var childStyle = this.graph.getCellStyle(child);
+							sw = mxUtils.getNumber(childStyle, mxConstants.STYLE_STROKEWIDTH, 1);
+						}
+						
+						if (last != null)
+						{
+							if (horizontal)
+							{
+								geo.x = lastValue + this.spacing + Math.floor(sw / 2);
+							}
+							else
+							{
+								geo.y = lastValue + this.spacing + Math.floor(sw / 2);
+							}
+						}
+						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 != null)
+						{
+							if (horizontal)
+							{
+								geo.height = fillValue;
+							}
+							else
+							{
+								geo.width = fillValue;									
+							}
+						}
+						
+						this.setChildGeometry(child, geo);
+						lastChild = child;
+						last = geo;
+						
+						if (horizontal)
+						{
+							lastValue = last.x + last.width + Math.floor(sw / 2);
+						}
+						else
+						{
+							lastValue = last.y + last.height + Math.floor(sw / 2);
+						}
+					}
+				}
+			}
+
+			if (this.resizeParent && pgeo != null && last != null && !this.graph.isCellCollapsed(parent))
+			{
+				this.updateParentGeometry(parent, pgeo, last);
+			}
+			else if (this.resizeLast && pgeo != null && last != null && lastChild != null)
+			{
+				if (horizontal)
+				{
+					last.width = pgeo.width - last.x - this.spacing - this.marginRight - this.marginLeft;
+				}
+				else
+				{
+					last.height = pgeo.height - last.y - this.spacing - this.marginBottom;
+				}
+				
+				this.setChildGeometry(lastChild, last);
+			}
+		}
+		finally
+		{
+			model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: execute
+ * 
+ * Implements <mxGraphLayout.execute>.
+ * 
+ * Only children where <isVertexIgnored> returns false are taken into
+ * account.
+ */
+mxStackLayout.prototype.setChildGeometry = function(child, geo)
+{
+	var geo2 = this.graph.getCellGeometry(child);
+	
+	if (geo2 == null || geo.x != geo2.x || geo.y != geo2.y ||
+		geo.width != geo2.width || geo.height != geo2.height)
+	{
+		this.graph.getModel().setGeometry(child, geo);
+	}
+};
+
+/**
+ * Function: execute
+ * 
+ * Implements <mxGraphLayout.execute>.
+ * 
+ * Only children where <isVertexIgnored> returns false are taken into
+ * account.
+ */
+mxStackLayout.prototype.updateParentGeometry = function(parent, pgeo, last)
+{
+	var horizontal = this.isHorizontal();
+	var model = this.graph.getModel();	
+
+	var pgeo2 = pgeo.clone();
+	
+	if (horizontal)
+	{
+		var tmp = last.x + last.width + this.spacing + this.marginRight;
+		
+		if (this.resizeParentMax)
+		{
+			pgeo2.width = Math.max(pgeo2.width, tmp);
+		}
+		else
+		{
+			pgeo2.width = tmp;
+		}
+	}
+	else
+	{
+		var tmp = last.y + last.height + this.spacing + this.marginBottom;
+		
+		if (this.resizeParentMax)
+		{
+			pgeo2.height = Math.max(pgeo2.height, tmp);
+		}
+		else
+		{
+			pgeo2.height = tmp;
+		}
+	}
+	
+	if (pgeo.x != pgeo2.x || pgeo.y != pgeo2.y ||
+		pgeo.width != pgeo2.width || pgeo.height != pgeo2.height)
+	{
+		model.setGeometry(parent, pgeo2);
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/model/mxCell.js b/airavata-kubernetes/workflow-composer/src/js/model/mxCell.js
new file mode 100644
index 0000000..73e94ba
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/model/mxCell.js
@@ -0,0 +1,825 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxCell
+ *
+ * Cells are the elements of the graph model. They represent the state
+ * of the groups, vertices and edges in a graph.
+ * 
+ * Custom attributes:
+ * 
+ * For custom attributes we recommend using an XML node as the value of a cell.
+ * The following code can be used to create a cell with an XML node as the
+ * value:
+ * 
+ * (code)
+ * var doc = mxUtils.createXmlDocument();
+ * var node = doc.createElement('MyNode')
+ * node.setAttribute('label', 'MyLabel');
+ * node.setAttribute('attribute1', 'value1');
+ * graph.insertVertex(graph.getDefaultParent(), null, node, 40, 40, 80, 30);
+ * (end)
+ * 
+ * For the label to work, <mxGraph.convertValueToString> and
+ * <mxGraph.cellLabelChanged> should be overridden as follows:
+ * 
+ * (code)
+ * graph.convertValueToString = function(cell)
+ * {
+ *   if (mxUtils.isNode(cell.value))
+ *   {
+ *     return cell.getAttribute('label', '')
+ *   }
+ * };
+ * 
+ * var cellLabelChanged = graph.cellLabelChanged;
+ * graph.cellLabelChanged = function(cell, newValue, autoSize)
+ * {
+ *   if (mxUtils.isNode(cell.value))
+ *   {
+ *     // Clones the value for correct undo/redo
+ *     var elt = cell.value.cloneNode(true);
+ *     elt.setAttribute('label', newValue);
+ *     newValue = elt;
+ *   }
+ *   
+ *   cellLabelChanged.apply(this, arguments);
+ * };
+ * (end)
+ * 
+ * Callback: onInit
+ *
+ * Called from within the constructor.
+ * 
+ * Constructor: mxCell
+ *
+ * Constructs a new cell to be used in a graph model.
+ * This method invokes <onInit> upon completion.
+ * 
+ * Parameters:
+ * 
+ * value - Optional object that represents the cell value.
+ * geometry - Optional <mxGeometry> that specifies the geometry.
+ * style - Optional formatted string that defines the style.
+ */
+function mxCell(value, geometry, style)
+{
+	this.value = value;
+	this.setGeometry(geometry);
+	this.setStyle(style);
+	
+	if (this.onInit != null)
+	{
+		this.onInit();
+	}
+};
+
+/**
+ * Variable: id
+ *
+ * Holds the Id. Default is null.
+ */
+mxCell.prototype.id = null;
+
+/**
+ * Variable: value
+ *
+ * Holds the user object. Default is null.
+ */
+mxCell.prototype.value = null;
+
+/**
+ * Variable: geometry
+ *
+ * Holds the <mxGeometry>. Default is null.
+ */
+mxCell.prototype.geometry = null;
+
+/**
+ * Variable: style
+ *
+ * Holds the style as a string of the form [(stylename|key=value);]. Default is
+ * null.
+ */
+mxCell.prototype.style = null;
+
+/**
+ * Variable: vertex
+ *
+ * Specifies whether the cell is a vertex. Default is false.
+ */
+mxCell.prototype.vertex = false;
+
+/**
+ * Variable: edge
+ *
+ * Specifies whether the cell is an edge. Default is false.
+ */
+mxCell.prototype.edge = false;
+
+/**
+ * Variable: connectable
+ *
+ * Specifies whether the cell is connectable. Default is true.
+ */
+mxCell.prototype.connectable = true;
+
+/**
+ * Variable: visible
+ *
+ * Specifies whether the cell is visible. Default is true.
+ */
+mxCell.prototype.visible = true;
+
+/**
+ * Variable: collapsed
+ *
+ * Specifies whether the cell is collapsed. Default is false.
+ */
+mxCell.prototype.collapsed = false;
+
+/**
+ * Variable: parent
+ *
+ * Reference to the parent cell.
+ */
+mxCell.prototype.parent = null;
+
+/**
+ * Variable: source
+ *
+ * Reference to the source terminal.
+ */
+mxCell.prototype.source = null;
+
+/**
+ * Variable: target
+ *
+ * Reference to the target terminal.
+ */
+mxCell.prototype.target = null;
+
+/**
+ * Variable: children
+ *
+ * Holds the child cells.
+ */
+mxCell.prototype.children = null;
+
+/**
+ * Variable: edges
+ *
+ * Holds the edges.
+ */
+mxCell.prototype.edges = null;
+
+/**
+ * Variable: mxTransient
+ *
+ * List of members that should not be cloned inside <clone>. This field is
+ * passed to <mxUtils.clone> and is not made persistent in <mxCellCodec>.
+ * This is not a convention for all classes, it is only used in this class
+ * to mark transient fields since transient modifiers are not supported by
+ * the language.
+ */
+mxCell.prototype.mxTransient = ['id', 'value', 'parent', 'source',
+                                'target', 'children', 'edges'];
+
+/**
+ * Function: getId
+ *
+ * Returns the Id of the cell as a string.
+ */
+mxCell.prototype.getId = function()
+{
+	return this.id;
+};
+		
+/**
+ * Function: setId
+ *
+ * Sets the Id of the cell to the given string.
+ */
+mxCell.prototype.setId = function(id)
+{
+	this.id = id;
+};
+
+/**
+ * Function: getValue
+ *
+ * Returns the user object of the cell. The user
+ * object is stored in <value>.
+ */
+mxCell.prototype.getValue = function()
+{
+	return this.value;
+};
+		
+/**
+ * Function: setValue
+ *
+ * Sets the user object of the cell. The user object
+ * is stored in <value>.
+ */
+mxCell.prototype.setValue = function(value)
+{
+	this.value = value;
+};
+
+/**
+ * Function: valueChanged
+ *
+ * Changes the user object after an in-place edit
+ * and returns the previous value. This implementation
+ * replaces the user object with the given value and
+ * returns the old user object.
+ */
+mxCell.prototype.valueChanged = function(newValue)
+{
+	var previous = this.getValue();
+	this.setValue(newValue);
+	
+	return previous;
+};
+
+/**
+ * Function: getGeometry
+ *
+ * Returns the <mxGeometry> that describes the <geometry>.
+ */
+mxCell.prototype.getGeometry = function()
+{
+	return this.geometry;
+};
+
+/**
+ * Function: setGeometry
+ *
+ * Sets the <mxGeometry> to be used as the <geometry>.
+ */
+mxCell.prototype.setGeometry = function(geometry)
+{
+	this.geometry = geometry;
+};
+
+/**
+ * Function: getStyle
+ *
+ * Returns a string that describes the <style>.
+ */
+mxCell.prototype.getStyle = function()
+{
+	return this.style;
+};
+
+/**
+ * Function: setStyle
+ *
+ * Sets the string to be used as the <style>.
+ */
+mxCell.prototype.setStyle = function(style)
+{
+	this.style = style;
+};
+
+/**
+ * Function: isVertex
+ *
+ * Returns true if the cell is a vertex.
+ */
+mxCell.prototype.isVertex = function()
+{
+	return this.vertex != 0;
+};
+
+/**
+ * Function: setVertex
+ *
+ * Specifies if the cell is a vertex. This should only be assigned at
+ * construction of the cell and not be changed during its lifecycle.
+ * 
+ * Parameters:
+ * 
+ * vertex - Boolean that specifies if the cell is a vertex.
+ */
+mxCell.prototype.setVertex = function(vertex)
+{
+	this.vertex = vertex;
+};
+
+/**
+ * Function: isEdge
+ *
+ * Returns true if the cell is an edge.
+ */
+mxCell.prototype.isEdge = function()
+{
+	return this.edge != 0;
+};
+	
+/**
+ * Function: setEdge
+ * 
+ * Specifies if the cell is an edge. This should only be assigned at
+ * construction of the cell and not be changed during its lifecycle.
+ * 
+ * Parameters:
+ * 
+ * edge - Boolean that specifies if the cell is an edge.
+ */
+mxCell.prototype.setEdge = function(edge)
+{
+	this.edge = edge;
+};
+
+/**
+ * Function: isConnectable
+ *
+ * Returns true if the cell is connectable.
+ */
+mxCell.prototype.isConnectable = function()
+{
+	return this.connectable != 0;
+};
+
+/**
+ * Function: setConnectable
+ *
+ * Sets the connectable state.
+ * 
+ * Parameters:
+ * 
+ * connectable - Boolean that specifies the new connectable state.
+ */
+mxCell.prototype.setConnectable = function(connectable)
+{
+	this.connectable = connectable;
+};
+
+/**
+ * Function: isVisible
+ *
+ * Returns true if the cell is visibile.
+ */
+mxCell.prototype.isVisible = function()
+{
+	return this.visible != 0;
+};
+
+/**
+ * Function: setVisible
+ *
+ * Specifies if the cell is visible.
+ * 
+ * Parameters:
+ * 
+ * visible - Boolean that specifies the new visible state.
+ */
+mxCell.prototype.setVisible = function(visible)
+{
+	this.visible = visible;
+};
+
+/**
+ * Function: isCollapsed
+ *
+ * Returns true if the cell is collapsed.
+ */
+mxCell.prototype.isCollapsed = function()
+{
+	return this.collapsed != 0;
+};
+
+/**
+ * Function: setCollapsed
+ *
+ * Sets the collapsed state.
+ * 
+ * Parameters:
+ * 
+ * collapsed - Boolean that specifies the new collapsed state.
+ */
+mxCell.prototype.setCollapsed = function(collapsed)
+{
+	this.collapsed = collapsed;
+};
+
+/**
+ * Function: getParent
+ *
+ * Returns the cell's parent.
+ */
+mxCell.prototype.getParent = function()
+{
+	return this.parent;
+};
+
+/**
+ * Function: setParent
+ *
+ * Sets the parent cell.
+ * 
+ * Parameters:
+ * 
+ * parent - <mxCell> that represents the new parent.
+ */
+mxCell.prototype.setParent = function(parent)
+{
+	this.parent = parent;
+};
+
+/**
+ * Function: getTerminal
+ *
+ * Returns the source or target terminal.
+ * 
+ * Parameters:
+ * 
+ * source - Boolean that specifies if the source terminal should be
+ * returned.
+ */
+mxCell.prototype.getTerminal = function(source)
+{
+	return (source) ? this.source : this.target;
+};
+
+/**
+ * Function: setTerminal
+ *
+ * Sets the source or target terminal and returns the new terminal.
+ * 
+ * Parameters:
+ * 
+ * terminal - <mxCell> that represents the new source or target terminal.
+ * isSource - Boolean that specifies if the source or target terminal
+ * should be set.
+ */
+mxCell.prototype.setTerminal = function(terminal, isSource)
+{
+	if (isSource)
+	{
+		this.source = terminal;
+	}
+	else
+	{
+		this.target = terminal;
+	}
+	
+	return terminal;
+};
+
+/**
+ * Function: getChildCount
+ *
+ * Returns the number of child cells.
+ */
+mxCell.prototype.getChildCount = function()
+{
+	return (this.children == null) ? 0 : this.children.length;
+};
+
+/**
+ * Function: getIndex
+ *
+ * Returns the index of the specified child in the child array.
+ * 
+ * Parameters:
+ * 
+ * child - Child whose index should be returned.
+ */
+mxCell.prototype.getIndex = function(child)
+{
+	return mxUtils.indexOf(this.children, child);
+};
+
+/**
+ * Function: getChildAt
+ *
+ * Returns the child at the specified index.
+ * 
+ * Parameters:
+ * 
+ * index - Integer that specifies the child to be returned.
+ */
+mxCell.prototype.getChildAt = function(index)
+{
+	return (this.children == null) ? null : this.children[index];
+};
+
+/**
+ * Function: insert
+ *
+ * Inserts the specified child into the child array at the specified index
+ * and updates the parent reference of the child. If not childIndex is
+ * specified then the child is appended to the child array. Returns the
+ * inserted child.
+ * 
+ * Parameters:
+ * 
+ * child - <mxCell> to be inserted or appended to the child array.
+ * index - Optional integer that specifies the index at which the child
+ * should be inserted into the child array.
+ */
+mxCell.prototype.insert = function(child, index)
+{
+	if (child != null)
+	{
+		if (index == null)
+		{
+			index = this.getChildCount();
+			
+			if (child.getParent() == this)
+			{
+				index--;
+			}
+		}
+
+		child.removeFromParent();
+		child.setParent(this);
+		
+		if (this.children == null)
+		{
+			this.children = [];
+			this.children.push(child);
+		}
+		else
+		{
+			this.children.splice(index, 0, child);
+		}
+	}
+	
+	return child;
+};
+
+/**
+ * Function: remove
+ *
+ * Removes the child at the specified index from the child array and
+ * returns the child that was removed. Will remove the parent reference of
+ * the child.
+ * 
+ * Parameters:
+ * 
+ * index - Integer that specifies the index of the child to be
+ * removed.
+ */
+mxCell.prototype.remove = function(index)
+{
+	var child = null;
+	
+	if (this.children != null && index >= 0)
+	{
+		child = this.getChildAt(index);
+		
+		if (child != null)
+		{
+			this.children.splice(index, 1);
+			child.setParent(null);
+		}
+	}
+	
+	return child;
+};
+
+/**
+ * Function: removeFromParent
+ *
+ * Removes the cell from its parent.
+ */
+mxCell.prototype.removeFromParent = function()
+{
+	if (this.parent != null)
+	{
+		var index = this.parent.getIndex(this);
+		this.parent.remove(index);
+	}
+};
+
+/**
+ * Function: getEdgeCount
+ *
+ * Returns the number of edges in the edge array.
+ */
+mxCell.prototype.getEdgeCount = function()
+{
+	return (this.edges == null) ? 0 : this.edges.length;
+};
+
+/**
+ * Function: getEdgeIndex
+ *
+ * Returns the index of the specified edge in <edges>.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> whose index in <edges> should be returned.
+ */
+mxCell.prototype.getEdgeIndex = function(edge)
+{
+	return mxUtils.indexOf(this.edges, edge);
+};
+
+/**
+ * Function: getEdgeAt
+ *
+ * Returns the edge at the specified index in <edges>.
+ * 
+ * Parameters:
+ * 
+ * index - Integer that specifies the index of the edge to be returned.
+ */
+mxCell.prototype.getEdgeAt = function(index)
+{
+	return (this.edges == null) ? null : this.edges[index];
+};
+
+/**
+ * Function: insertEdge
+ *
+ * Inserts the specified edge into the edge array and returns the edge.
+ * Will update the respective terminal reference of the edge.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> to be inserted into the edge array.
+ * isOutgoing - Boolean that specifies if the edge is outgoing.
+ */
+mxCell.prototype.insertEdge = function(edge, isOutgoing)
+{
+	if (edge != null)
+	{
+		edge.removeFromTerminal(isOutgoing);
+		edge.setTerminal(this, isOutgoing);
+		
+		if (this.edges == null ||
+			edge.getTerminal(!isOutgoing) != this ||
+			mxUtils.indexOf(this.edges, edge) < 0)
+		{
+			if (this.edges == null)
+			{
+				this.edges = [];
+			}
+			
+			this.edges.push(edge);
+		}
+	}
+	
+	return edge;
+};
+
+/**
+ * Function: removeEdge
+ *
+ * Removes the specified edge from the edge array and returns the edge.
+ * Will remove the respective terminal reference from the edge.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> to be removed from the edge array.
+ * isOutgoing - Boolean that specifies if the edge is outgoing.
+ */
+mxCell.prototype.removeEdge = function(edge, isOutgoing)
+{
+	if (edge != null)
+	{
+		if (edge.getTerminal(!isOutgoing) != this &&
+			this.edges != null)
+		{
+			var index = this.getEdgeIndex(edge);
+			
+			if (index >= 0)
+			{
+				this.edges.splice(index, 1);
+			}
+		}
+		
+		edge.setTerminal(null, isOutgoing);
+	}
+	
+	return edge;
+};
+
+/**
+ * Function: removeFromTerminal
+ *
+ * Removes the edge from its source or target terminal.
+ * 
+ * Parameters:
+ * 
+ * isSource - Boolean that specifies if the edge should be removed from its
+ * source or target terminal.
+ */
+mxCell.prototype.removeFromTerminal = function(isSource)
+{
+	var terminal = this.getTerminal(isSource);
+	
+	if (terminal != null)
+	{
+		terminal.removeEdge(this, isSource);
+	}
+};
+
+/**
+ * Function: hasAttribute
+ * 
+ * Returns true if the user object is an XML node that contains the given
+ * attribute.
+ * 
+ * Parameters:
+ * 
+ * name - Name of the attribute.
+ */
+mxCell.prototype.hasAttribute = function(name)
+{
+	var userObject = this.getValue();
+	
+	return (userObject != null &&
+		userObject.nodeType == mxConstants.NODETYPE_ELEMENT && userObject.hasAttribute) ?
+		userObject.hasAttribute(name) : userObject.getAttribute(name) != null;
+};
+
+/**
+ * Function: getAttribute
+ *
+ * Returns the specified attribute from the user object if it is an XML
+ * node.
+ * 
+ * Parameters:
+ * 
+ * name - Name of the attribute whose value should be returned.
+ * defaultValue - Optional default value to use if the attribute has no
+ * value.
+ */
+mxCell.prototype.getAttribute = function(name, defaultValue)
+{
+	var userObject = this.getValue();
+	
+	var val = (userObject != null &&
+		userObject.nodeType == mxConstants.NODETYPE_ELEMENT) ?
+		userObject.getAttribute(name) : null;
+		
+	return val || defaultValue;
+};
+
+/**
+ * Function: setAttribute
+ *
+ * Sets the specified attribute on the user object if it is an XML node.
+ * 
+ * Parameters:
+ * 
+ * name - Name of the attribute whose value should be set.
+ * value - New value of the attribute.
+ */
+mxCell.prototype.setAttribute = function(name, value)
+{
+	var userObject = this.getValue();
+	
+	if (userObject != null &&
+		userObject.nodeType == mxConstants.NODETYPE_ELEMENT)
+	{
+		userObject.setAttribute(name, value);
+	}
+};
+
+/**
+ * Function: clone
+ *
+ * Returns a clone of the cell. Uses <cloneValue> to clone
+ * the user object. All fields in <mxTransient> are ignored
+ * during the cloning.
+ */
+mxCell.prototype.clone = function()
+{
+	var clone = mxUtils.clone(this, this.mxTransient);
+	clone.setValue(this.cloneValue());
+	
+	return clone;
+};
+
+/**
+ * Function: cloneValue
+ *
+ * Returns a clone of the cell's user object.
+ */
+mxCell.prototype.cloneValue = function()
+{
+	var value = this.getValue();
+	
+	if (value != null)
+	{
+		if (typeof(value.clone) == 'function')
+		{
+			value = value.clone();
+		}
+		else if (!isNaN(value.nodeType))
+		{
+			value = value.cloneNode(true);
+		}
+	}
+	
+	return value;
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/model/mxCellPath.js b/airavata-kubernetes/workflow-composer/src/js/model/mxCellPath.js
new file mode 100644
index 0000000..c51a823
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/model/mxCellPath.js
@@ -0,0 +1,163 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+var mxCellPath =
+{
+
+	/**
+	 * Class: mxCellPath
+	 * 
+	 * Implements a mechanism for temporary cell Ids.
+	 * 
+	 * Variable: PATH_SEPARATOR
+	 * 
+	 * Defines the separator between the path components. Default is ".".
+	 */
+	PATH_SEPARATOR: '.',
+	
+	/**
+	 * Function: create
+	 * 
+	 * Creates the cell path for the given cell. The cell path is a
+	 * concatenation of the indices of all ancestors on the (finite) path to
+	 * the root, eg. "0.0.0.1".
+	 * 
+	 * Parameters:
+	 * 
+	 * cell - Cell whose path should be returned.
+	 */
+	create: function(cell)
+	{
+		var result = '';
+		
+		if (cell != null)
+		{
+			var parent = cell.getParent();
+			
+			while (parent != null)
+			{
+				var index = parent.getIndex(cell);
+				result = index + mxCellPath.PATH_SEPARATOR + result;
+				
+				cell = parent;
+				parent = cell.getParent();
+			}
+		}
+		
+		// Removes trailing separator
+		var n = result.length;
+		
+		if (n > 1)
+		{
+			result = result.substring(0, n - 1);
+		}
+		
+		return result;
+	},
+	
+	/**
+	 * Function: getParentPath
+	 * 
+	 * Returns the path for the parent of the cell represented by the given
+	 * path. Returns null if the given path has no parent.
+	 * 
+	 * Parameters:
+	 * 
+	 * path - Path whose parent path should be returned.
+	 */
+	getParentPath: function(path)
+	{
+		if (path != null)
+		{
+			var index = path.lastIndexOf(mxCellPath.PATH_SEPARATOR);
+
+			if (index >= 0)
+			{
+				return path.substring(0, index);
+			}
+			else if (path.length > 0)
+			{
+				return '';
+			}
+		}
+
+		return null;
+	},
+
+	/**
+	 * Function: resolve
+	 * 
+	 * Returns the cell for the specified cell path using the given root as the
+	 * root of the path.
+	 * 
+	 * Parameters:
+	 * 
+	 * root - Root cell of the path to be resolved.
+	 * path - String that defines the path.
+	 */
+	resolve: function(root, path)
+	{
+		var parent = root;
+		
+		if (path != null)
+		{
+			var tokens = path.split(mxCellPath.PATH_SEPARATOR);
+			
+			for (var i=0; i<tokens.length; i++)
+			{
+				parent = parent.getChildAt(parseInt(tokens[i]));
+			}
+		}
+		
+		return parent;
+	},
+	
+	/**
+	 * Function: compare
+	 * 
+	 * Compares the given cell paths and returns -1 if p1 is smaller, 0 if
+	 * p1 is equal and 1 if p1 is greater than p2.
+	 */
+	compare: function(p1, p2)
+	{
+		var min = Math.min(p1.length, p2.length);
+		var comp = 0;
+		
+		for (var i = 0; i < min; i++)
+		{
+			if (p1[i] != p2[i])
+			{
+				if (p1[i].length == 0 ||
+					p2[i].length == 0)
+				{
+					comp = (p1[i] == p2[i]) ? 0 : ((p1[i] > p2[i]) ? 1 : -1);
+				}
+				else
+				{
+					var t1 = parseInt(p1[i]);
+					var t2 = parseInt(p2[i]);
+					
+					comp = (t1 == t2) ? 0 : ((t1 > t2) ? 1 : -1);
+				}
+				
+				break;
+			}
+		}
+		
+		// Compares path length if both paths are equal to this point
+		if (comp == 0)
+		{
+			var t1 = p1.length;
+			var t2 = p2.length;
+			
+			if (t1 != t2)
+			{
+				comp = (t1 > t2) ? 1 : -1;
+			}
+		}
+		
+		return comp;
+	}
+
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/model/mxGeometry.js b/airavata-kubernetes/workflow-composer/src/js/model/mxGeometry.js
new file mode 100644
index 0000000..8d9278a
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/model/mxGeometry.js
@@ -0,0 +1,415 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxGeometry
+ * 
+ * Extends <mxRectangle> to represent the geometry of a cell.
+ * 
+ * For vertices, the geometry consists of the x- and y-location, and the width
+ * and height. For edges, the geometry consists of the optional terminal- and
+ * control points. The terminal points are only required if an edge is
+ * unconnected, and are stored in the sourcePoint> and <targetPoint>
+ * variables, respectively.
+ * 
+ * Example:
+ * 
+ * If an edge is unconnected, that is, it has no source or target terminal,
+ * then a geometry with terminal points for a new edge can be defined as
+ * follows.
+ * 
+ * (code)
+ * geometry.setTerminalPoint(new mxPoint(x1, y1), true);
+ * geometry.points = [new mxPoint(x2, y2)];
+ * geometry.setTerminalPoint(new mxPoint(x3, y3), false);
+ * (end)
+ * 
+ * Control points are used regardless of the connected state of an edge and may
+ * be ignored or interpreted differently depending on the edge's <mxEdgeStyle>.
+ * 
+ * To disable automatic reset of control points after a cell has been moved or
+ * resized, the the <mxGraph.resizeEdgesOnMove> and
+ * <mxGraph.resetEdgesOnResize> may be used.
+ *
+ * Edge Labels:
+ * 
+ * Using the x- and y-coordinates of a cell's geometry, it is possible to
+ * position the label on edges on a specific location on the actual edge shape
+ * as it appears on the screen. The x-coordinate of an edge's geometry is used
+ * to describe the distance from the center of the edge from -1 to 1 with 0
+ * being the center of the edge and the default value. The y-coordinate of an
+ * edge's geometry is used to describe the absolute, orthogonal distance in
+ * pixels from that point. In addition, the <mxGeometry.offset> is used as an
+ * absolute offset vector from the resulting point.
+ * 
+ * This coordinate system is applied if <relative> is true, otherwise the
+ * offset defines the absolute vector from the edge's center point to the
+ * label and the values for <x> and <y> are ignored.
+ * 
+ * The width and height parameter for edge geometries can be used to set the
+ * label width and height (eg. for word wrapping).
+ * 
+ * Ports:
+ * 
+ * The term "port" refers to a relatively positioned, connectable child cell,
+ * which is used to specify the connection between the parent and another cell
+ * in the graph. Ports are typically modeled as vertices with relative
+ * geometries.
+ * 
+ * Offsets:
+ * 
+ * The <offset> field is interpreted in 3 different ways, depending on the cell
+ * and the geometry. For edges, the offset defines the absolute offset for the
+ * edge label. For relative geometries, the offset defines the absolute offset
+ * for the origin (top, left corner) of the vertex, otherwise the offset
+ * defines the absolute offset for the label inside the vertex or group.
+ * 
+ * Constructor: mxGeometry
+ *
+ * Constructs a new object to describe the size and location of a vertex or
+ * the control points of an edge.
+ */
+function mxGeometry(x, y, width, height)
+{
+	mxRectangle.call(this, x, y, width, height);
+};
+
+/**
+ * Extends mxRectangle.
+ */
+mxGeometry.prototype = new mxRectangle();
+mxGeometry.prototype.constructor = mxGeometry;
+
+/**
+ * Variable: TRANSLATE_CONTROL_POINTS
+ * 
+ * Global switch to translate the points in translate. Default is true.
+ */
+mxGeometry.prototype.TRANSLATE_CONTROL_POINTS = true;
+
+/**
+ * Variable: alternateBounds
+ *
+ * Stores alternate values for x, y, width and height in a rectangle. See
+ * <swap> to exchange the values. Default is null.
+ */
+mxGeometry.prototype.alternateBounds = null;
+
+/**
+ * Variable: sourcePoint
+ *
+ * Defines the source <mxPoint> of the edge. This is used if the
+ * corresponding edge does not have a source vertex. Otherwise it is
+ * ignored. Default is  null.
+ */
+mxGeometry.prototype.sourcePoint = null;
+
+/**
+ * Variable: targetPoint
+ *
+ * Defines the target <mxPoint> of the edge. This is used if the
+ * corresponding edge does not have a target vertex. Otherwise it is
+ * ignored. Default is null.
+ */
+mxGeometry.prototype.targetPoint = null;
+
+/**
+ * Variable: points
+ *
+ * Array of <mxPoints> which specifies the control points along the edge.
+ * These points are the intermediate points on the edge, for the endpoints
+ * use <targetPoint> and <sourcePoint> or set the terminals of the edge to
+ * a non-null value. Default is null.
+ */
+mxGeometry.prototype.points = null;
+
+/**
+ * Variable: offset
+ *
+ * For edges, this holds the offset (in pixels) from the position defined
+ * by <x> and <y> on the edge. For relative geometries (for vertices), this
+ * defines the absolute offset from the point defined by the relative
+ * coordinates. For absolute geometries (for vertices), this defines the
+ * offset for the label. Default is null.
+ */
+mxGeometry.prototype.offset = null;
+
+/**
+ * Variable: relative
+ *
+ * Specifies if the coordinates in the geometry are to be interpreted as
+ * relative coordinates. For edges, this is used to define the location of
+ * the edge label relative to the edge as rendered on the display. For
+ * vertices, this specifies the relative location inside the bounds of the
+ * parent cell.
+ * 
+ * If this is false, then the coordinates are relative to the origin of the
+ * parent cell or, for edges, the edge label position is relative to the
+ * center of the edge as rendered on screen.
+ * 
+ * Default is false.
+ */
+mxGeometry.prototype.relative = false;
+
+/**
+ * Function: swap
+ * 
+ * Swaps the x, y, width and height with the values stored in
+ * <alternateBounds> and puts the previous values into <alternateBounds> as
+ * a rectangle. This operation is carried-out in-place, that is, using the
+ * existing geometry instance. If this operation is called during a graph
+ * model transactional change, then the geometry should be cloned before
+ * calling this method and setting the geometry of the cell using
+ * <mxGraphModel.setGeometry>.
+ */
+mxGeometry.prototype.swap = function()
+{
+	if (this.alternateBounds != null)
+	{
+		var old = new mxRectangle(
+			this.x, this.y, this.width, this.height);
+
+		this.x = this.alternateBounds.x;
+		this.y = this.alternateBounds.y;
+		this.width = this.alternateBounds.width;
+		this.height = this.alternateBounds.height;
+
+		this.alternateBounds = old;
+	}
+};
+
+/**
+ * Function: getTerminalPoint
+ * 
+ * Returns the <mxPoint> representing the source or target point of this
+ * edge. This is only used if the edge has no source or target vertex.
+ * 
+ * Parameters:
+ * 
+ * isSource - Boolean that specifies if the source or target point
+ * should be returned.
+ */
+mxGeometry.prototype.getTerminalPoint = function(isSource)
+{
+	return (isSource) ? this.sourcePoint : this.targetPoint;
+};
+
+/**
+ * Function: setTerminalPoint
+ * 
+ * Sets the <sourcePoint> or <targetPoint> to the given <mxPoint> and
+ * returns the new point.
+ * 
+ * Parameters:
+ * 
+ * point - Point to be used as the new source or target point.
+ * isSource - Boolean that specifies if the source or target point
+ * should be set.
+ */
+mxGeometry.prototype.setTerminalPoint = function(point, isSource)
+{
+	if (isSource)
+	{
+		this.sourcePoint = point;
+	}
+	else
+	{
+		this.targetPoint = point;
+	}
+	
+	return point;
+};
+
+/**
+ * Function: rotate
+ * 
+ * Rotates the geometry by the given angle around the given center. That is,
+ * <x> and <y> of the geometry, the <sourcePoint>, <targetPoint> and all
+ * <points> are translated by the given amount. <x> and <y> are only
+ * translated if <relative> is false.
+ * 
+ * Parameters:
+ * 
+ * angle - Number that specifies the rotation angle in degrees.
+ * cx - <mxPoint> that specifies the center of the rotation.
+ */
+mxGeometry.prototype.rotate = function(angle, cx)
+{
+	var rad = mxUtils.toRadians(angle);
+	var cos = Math.cos(rad);
+	var sin = Math.sin(rad);
+	
+	// Rotates the geometry
+	if (!this.relative)
+	{
+		var ct = new mxPoint(this.getCenterX(), this.getCenterY());
+		var pt = mxUtils.getRotatedPoint(ct, cos, sin, cx);
+		
+		this.x = Math.round(pt.x - this.width / 2);
+		this.y = Math.round(pt.y - this.height / 2);
+	}
+
+	// Rotates the source point
+	if (this.sourcePoint != null)
+	{
+		var pt = mxUtils.getRotatedPoint(this.sourcePoint, cos, sin, cx);
+		this.sourcePoint.x = Math.round(pt.x);
+		this.sourcePoint.y = Math.round(pt.y);
+	}
+	
+	// Translates the target point
+	if (this.targetPoint != null)
+	{
+		var pt = mxUtils.getRotatedPoint(this.targetPoint, cos, sin, cx);
+		this.targetPoint.x = Math.round(pt.x);
+		this.targetPoint.y = Math.round(pt.y);	
+	}
+	
+	// Translate the control points
+	if (this.points != null)
+	{
+		for (var i = 0; i < this.points.length; i++)
+		{
+			if (this.points[i] != null)
+			{
+				var pt = mxUtils.getRotatedPoint(this.points[i], cos, sin, cx);
+				this.points[i].x = Math.round(pt.x);
+				this.points[i].y = Math.round(pt.y);
+			}
+		}
+	}
+};
+
+/**
+ * Function: translate
+ * 
+ * Translates the geometry by the specified amount. That is, <x> and <y> of the
+ * geometry, the <sourcePoint>, <targetPoint> and all <points> are translated
+ * by the given amount. <x> and <y> are only translated if <relative> is false.
+ * If <TRANSLATE_CONTROL_POINTS> is false, then <points> are not modified by
+ * this function.
+ * 
+ * Parameters:
+ * 
+ * dx - Number that specifies the x-coordinate of the translation.
+ * dy - Number that specifies the y-coordinate of the translation.
+ */
+mxGeometry.prototype.translate = function(dx, dy)
+{
+	dx = parseFloat(dx);
+	dy = parseFloat(dy);
+	
+	// Translates the geometry
+	if (!this.relative)
+	{
+		this.x = parseFloat(this.x) + dx;
+		this.y = parseFloat(this.y) + dy;
+	}
+
+	// Translates the source point
+	if (this.sourcePoint != null)
+	{
+		this.sourcePoint.x = parseFloat(this.sourcePoint.x) + dx;
+		this.sourcePoint.y = parseFloat(this.sourcePoint.y) + dy;
+	}
+	
+	// Translates the target point
+	if (this.targetPoint != null)
+	{
+		this.targetPoint.x = parseFloat(this.targetPoint.x) + dx;
+		this.targetPoint.y = parseFloat(this.targetPoint.y) + dy;		
+	}
+
+	// Translate the control points
+	if (this.TRANSLATE_CONTROL_POINTS && this.points != null)
+	{
+		for (var i = 0; i < this.points.length; i++)
+		{
+			if (this.points[i] != null)
+			{
+				this.points[i].x = parseFloat(this.points[i].x) + dx;
+				this.points[i].y = parseFloat(this.points[i].y) + dy;
+			}
+		}
+	}
+};
+
+/**
+ * Function: scale
+ * 
+ * Scales the geometry by the given amount. That is, <x> and <y> of the
+ * geometry, the <sourcePoint>, <targetPoint> and all <points> are scaled
+ * by the given amount. <x>, <y>, <width> and <height> are only scaled if
+ * <relative> is false. If <fixedAspect> is true, then the smaller value
+ * is used to scale the width and the height.
+ * 
+ * Parameters:
+ * 
+ * sx - Number that specifies the horizontal scale factor.
+ * sy - Number that specifies the vertical scale factor.
+ * fixedAspect - Optional boolean to keep the aspect ratio fixed.
+ */
+mxGeometry.prototype.scale = function(sx, sy, fixedAspect)
+{
+	sx = parseFloat(sx);
+	sy = parseFloat(sy);
+
+	// Translates the source point
+	if (this.sourcePoint != null)
+	{
+		this.sourcePoint.x = parseFloat(this.sourcePoint.x) * sx;
+		this.sourcePoint.y = parseFloat(this.sourcePoint.y) * sy;
+	}
+	
+	// Translates the target point
+	if (this.targetPoint != null)
+	{
+		this.targetPoint.x = parseFloat(this.targetPoint.x) * sx;
+		this.targetPoint.y = parseFloat(this.targetPoint.y) * sy;		
+	}
+
+	// Translate the control points
+	if (this.points != null)
+	{
+		for (var i = 0; i < this.points.length; i++)
+		{
+			if (this.points[i] != null)
+			{
+				this.points[i].x = parseFloat(this.points[i].x) * sx;
+				this.points[i].y = parseFloat(this.points[i].y) * sy;
+			}
+		}
+	}
+	
+	// Translates the geometry
+	if (!this.relative)
+	{
+		this.x = parseFloat(this.x) * sx;
+		this.y = parseFloat(this.y) * sy;
+
+		if (fixedAspect)
+		{
+			sy = sx = Math.min(sx, sy);
+		}
+		
+		this.width = parseFloat(this.width) * sx;
+		this.height = parseFloat(this.height) * sy;
+	}
+};
+
+/**
+ * Function: equals
+ * 
+ * Returns true if the given object equals this geometry.
+ */
+mxGeometry.prototype.equals = function(obj)
+{
+	return mxRectangle.prototype.equals.apply(this, arguments) &&
+		this.relative == obj.relative &&
+		((this.sourcePoint == null && obj.sourcePoint == null) || (this.sourcePoint != null && this.sourcePoint.equals(obj.sourcePoint))) &&
+		((this.targetPoint == null && obj.targetPoint == null) || (this.targetPoint != null && this.targetPoint.equals(obj.targetPoint))) &&
+		((this.points == null && obj.points == null) || (this.points != null && mxUtils.equalPoints(this.points, obj.points))) &&
+		((this.alternateBounds == null && obj.alternateBounds == null) || (this.alternateBounds != null && this.alternateBounds.equals(obj.alternateBounds))) &&
+		((this.offset == null && obj.offset == null) || (this.offset != null && this.offset.equals(obj.offset)));
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/model/mxGraphModel.js b/airavata-kubernetes/workflow-composer/src/js/model/mxGraphModel.js
new file mode 100644
index 0000000..1401bac
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/model/mxGraphModel.js
@@ -0,0 +1,2667 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxGraphModel
+ * 
+ * Extends <mxEventSource> to implement a graph model. The graph model acts as
+ * a wrapper around the cells which are in charge of storing the actual graph
+ * datastructure. The model acts as a transactional wrapper with event
+ * notification for all changes, whereas the cells contain the atomic
+ * operations for updating the actual datastructure.
+ * 
+ * Layers:
+ * 
+ * The cell hierarchy in the model must have a top-level root cell which
+ * contains the layers (typically one default layer), which in turn contain the
+ * top-level cells of the layers. This means each cell is contained in a layer.
+ * If no layers are required, then all new cells should be added to the default
+ * layer.
+ * 
+ * Layers are useful for hiding and showing groups of cells, or for placing
+ * groups of cells on top of other cells in the display. To identify a layer,
+ * the <isLayer> function is used. It returns true if the parent of the given
+ * cell is the root of the model.
+ * 
+ * Events:
+ * 
+ * See events section for more details. There is a new set of events for
+ * tracking transactional changes as they happen. The events are called
+ * startEdit for the initial beginUpdate, executed for each executed change
+ * and endEdit for the terminal endUpdate. The executed event contains a
+ * property called change which represents the change after execution.
+ * 
+ * Encoding the model:
+ * 
+ * To encode a graph model, use the following code:
+ * 
+ * (code)
+ * var enc = new mxCodec();
+ * var node = enc.encode(graph.getModel());
+ * (end)
+ * 
+ * This will create an XML node that contains all the model information.
+ * 
+ * Encoding and decoding changes:
+ * 
+ * For the encoding of changes, a graph model listener is required that encodes
+ * each change from the given array of changes.
+ * 
+ * (code)
+ * model.addListener(mxEvent.CHANGE, function(sender, evt)
+ * {
+ *   var changes = evt.getProperty('edit').changes;
+ *   var nodes = [];
+ *   var codec = new mxCodec();
+ * 
+ *   for (var i = 0; i < changes.length; i++)
+ *   {
+ *     nodes.push(codec.encode(changes[i]));
+ *   }
+ *   // do something with the nodes
+ * });
+ * (end)
+ * 
+ * For the decoding and execution of changes, the codec needs a lookup function
+ * that allows it to resolve cell IDs as follows:
+ * 
+ * (code)
+ * var codec = new mxCodec();
+ * codec.lookup = function(id)
+ * {
+ *   return model.getCell(id);
+ * }
+ * (end)
+ * 
+ * For each encoded change (represented by a node), the following code can be
+ * used to carry out the decoding and create a change object.
+ * 
+ * (code)
+ * var changes = [];
+ * var change = codec.decode(node);
+ * change.model = model;
+ * change.execute();
+ * changes.push(change);
+ * (end)
+ * 
+ * The changes can then be dispatched using the model as follows.
+ * 
+ * (code)
+ * var edit = new mxUndoableEdit(model, false);
+ * edit.changes = changes;
+ * 
+ * edit.notify = function()
+ * {
+ *   edit.source.fireEvent(new mxEventObject(mxEvent.CHANGE,
+ *   	'edit', edit, 'changes', edit.changes));
+ *   edit.source.fireEvent(new mxEventObject(mxEvent.NOTIFY,
+ *   	'edit', edit, 'changes', edit.changes));
+ * }
+ * 
+ * model.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit));
+ * model.fireEvent(new mxEventObject(mxEvent.CHANGE,
+ * 		'edit', edit, 'changes', changes));
+ * (end)
+ *
+ * Event: mxEvent.CHANGE
+ *
+ * Fires when an undoable edit is dispatched. The <code>edit</code> property
+ * contains the <mxUndoableEdit>. The <code>changes</code> property contains
+ * the array of atomic changes inside the undoable edit. The changes property
+ * is <strong>deprecated</strong>, please use edit.changes instead.
+ *
+ * Example:
+ * 
+ * For finding newly inserted cells, the following code can be used:
+ * 
+ * (code)
+ * graph.model.addListener(mxEvent.CHANGE, function(sender, evt)
+ * {
+ *   var changes = evt.getProperty('edit').changes;
+ * 
+ *   for (var i = 0; i < changes.length; i++)
+ *   {
+ *     var change = changes[i];
+ *     
+ *     if (change instanceof mxChildChange &&
+ *       change.change.previous == null)
+ *     {
+ *       graph.startEditingAtCell(change.child);
+ *       break;
+ *     }
+ *   }
+ * });
+ * (end)
+ * 
+ * 
+ * Event: mxEvent.NOTIFY
+ *
+ * Same as <mxEvent.CHANGE>, this event can be used for classes that need to
+ * implement a sync mechanism between this model and, say, a remote model. In
+ * such a setup, only local changes should trigger a notify event and all
+ * changes should trigger a change event.
+ * 
+ * Event: mxEvent.EXECUTE
+ * 
+ * Fires between begin- and endUpdate and after an atomic change was executed
+ * in the model. The <code>change</code> property contains the atomic change
+ * that was executed.
+ * 
+ * Event: mxEvent.EXECUTED
+ * 
+ * Fires between START_EDIT and END_EDIT after an atomic change was executed.
+ * The <code>change</code> property contains the change that was executed.
+ *
+ * Event: mxEvent.BEGIN_UPDATE
+ *
+ * Fires after the <updateLevel> was incremented in <beginUpdate>. This event
+ * contains no properties.
+ * 
+ * Event: mxEvent.START_EDIT
+ *
+ * Fires after the <updateLevel> was changed from 0 to 1. This event
+ * contains no properties.
+ * 
+ * Event: mxEvent.END_UPDATE
+ * 
+ * Fires after the <updateLevel> was decreased in <endUpdate> but before any
+ * notification or change dispatching. The <code>edit</code> property contains
+ * the <currentEdit>.
+ * 
+ * Event: mxEvent.END_EDIT
+ *
+ * Fires after the <updateLevel> was changed from 1 to 0. This event
+ * contains no properties.
+ * 
+ * Event: mxEvent.BEFORE_UNDO
+ * 
+ * Fires before the change is dispatched after the update level has reached 0
+ * in <endUpdate>. The <code>edit</code> property contains the <curreneEdit>.
+ * 
+ * Event: mxEvent.UNDO
+ * 
+ * Fires after the change was dispatched in <endUpdate>. The <code>edit</code>
+ * property contains the <currentEdit>.
+ * 
+ * Constructor: mxGraphModel
+ * 
+ * Constructs a new graph model. If no root is specified then a new root
+ * <mxCell> with a default layer is created.
+ * 
+ * Parameters:
+ * 
+ * root - <mxCell> that represents the root cell.
+ */
+function mxGraphModel(root)
+{
+	this.currentEdit = this.createUndoableEdit();
+	
+	if (root != null)
+	{
+		this.setRoot(root);
+	}
+	else
+	{
+		this.clear();
+	}
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxGraphModel.prototype = new mxEventSource();
+mxGraphModel.prototype.constructor = mxGraphModel;
+
+/**
+ * Variable: root
+ * 
+ * Holds the root cell, which in turn contains the cells that represent the
+ * layers of the diagram as child cells. That is, the actual elements of the
+ * diagram are supposed to live in the third generation of cells and below.
+ */
+mxGraphModel.prototype.root = null;
+
+/**
+ * Variable: cells
+ * 
+ * Maps from Ids to cells.
+ */
+mxGraphModel.prototype.cells = null;
+
+/**
+ * Variable: maintainEdgeParent
+ * 
+ * Specifies if edges should automatically be moved into the nearest common
+ * ancestor of their terminals. Default is true.
+ */
+mxGraphModel.prototype.maintainEdgeParent = true;
+
+/**
+ * Variable: ignoreRelativeEdgeParent
+ * 
+ * Specifies if relative edge parents should be ignored for finding the nearest
+ * common ancestors of an edge's terminals. Default is true.
+ */
+mxGraphModel.prototype.ignoreRelativeEdgeParent = true;
+
+/**
+ * Variable: createIds
+ * 
+ * Specifies if the model should automatically create Ids for new cells.
+ * Default is true.
+ */
+mxGraphModel.prototype.createIds = true;
+
+/**
+ * Variable: prefix
+ * 
+ * Defines the prefix of new Ids. Default is an empty string.
+ */
+mxGraphModel.prototype.prefix = '';
+
+/**
+ * Variable: postfix
+ * 
+ * Defines the postfix of new Ids. Default is an empty string.
+ */
+mxGraphModel.prototype.postfix = '';
+
+/**
+ * Variable: nextId
+ * 
+ * Specifies the next Id to be created. Initial value is 0.
+ */
+mxGraphModel.prototype.nextId = 0;
+
+/**
+ * Variable: currentEdit
+ * 
+ * Holds the changes for the current transaction. If the transaction is
+ * closed then a new object is created for this variable using
+ * <createUndoableEdit>.
+ */
+mxGraphModel.prototype.currentEdit = null;
+
+/**
+ * Variable: updateLevel
+ * 
+ * Counter for the depth of nested transactions. Each call to <beginUpdate>
+ * will increment this number and each call to <endUpdate> will decrement
+ * it. When the counter reaches 0, the transaction is closed and the
+ * respective events are fired. Initial value is 0.
+ */
+mxGraphModel.prototype.updateLevel = 0;
+
+/**
+ * Variable: endingUpdate
+ * 
+ * True if the program flow is currently inside endUpdate.
+ */
+mxGraphModel.prototype.endingUpdate = false;
+
+/**
+ * Function: clear
+ *
+ * Sets a new root using <createRoot>.
+ */
+mxGraphModel.prototype.clear = function()
+{
+	this.setRoot(this.createRoot());
+};
+
+/**
+ * Function: isCreateIds
+ *
+ * Returns <createIds>.
+ */
+mxGraphModel.prototype.isCreateIds = function()
+{
+	return this.createIds;
+};
+
+/**
+ * Function: setCreateIds
+ *
+ * Sets <createIds>.
+ */
+mxGraphModel.prototype.setCreateIds = function(value)
+{
+	this.createIds = value;
+};
+
+/**
+ * Function: createRoot
+ *
+ * Creates a new root cell with a default layer (child 0).
+ */
+mxGraphModel.prototype.createRoot = function()
+{
+	var cell = new mxCell();
+	cell.insert(new mxCell());
+	
+	return cell;
+};
+
+/**
+ * Function: getCell
+ *
+ * Returns the <mxCell> for the specified Id or null if no cell can be
+ * found for the given Id.
+ *
+ * Parameters:
+ * 
+ * id - A string representing the Id of the cell.
+ */
+mxGraphModel.prototype.getCell = function(id)
+{
+	return (this.cells != null) ? this.cells[id] : null;
+};
+
+/**
+ * Function: filterCells
+ * 
+ * Returns the cells from the given array where the given filter function
+ * returns true.
+ */
+mxGraphModel.prototype.filterCells = function(cells, filter)
+{
+	var result = null;
+	
+	if (cells != null)
+	{
+		result = [];
+		
+		for (var i = 0; i < cells.length; i++)
+		{
+			if (filter(cells[i]))
+			{
+				result.push(cells[i]);
+			}
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: getDescendants
+ * 
+ * Returns all descendants of the given cell and the cell itself in an array.
+ * 
+ * Parameters:
+ * 
+ * parent - <mxCell> whose descendants should be returned.
+ */
+mxGraphModel.prototype.getDescendants = function(parent)
+{
+	return this.filterDescendants(null, parent);
+};
+
+/**
+ * Function: filterDescendants
+ * 
+ * Visits all cells recursively and applies the specified filter function
+ * to each cell. If the function returns true then the cell is added
+ * to the resulting array. The parent and result paramters are optional.
+ * If parent is not specified then the recursion starts at <root>.
+ * 
+ * Example:
+ * The following example extracts all vertices from a given model:
+ * (code)
+ * var filter = function(cell)
+ * {
+ * 	return model.isVertex(cell);
+ * }
+ * var vertices = model.filterDescendants(filter);
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * filter - JavaScript function that takes an <mxCell> as an argument
+ * and returns a boolean.
+ * parent - Optional <mxCell> that is used as the root of the recursion.
+ */
+mxGraphModel.prototype.filterDescendants = function(filter, parent)
+{
+	// Creates a new array for storing the result
+	var result = [];
+
+	// Recursion starts at the root of the model
+	parent = parent || this.getRoot();
+	
+	// Checks if the filter returns true for the cell
+	// and adds it to the result array
+	if (filter == null || filter(parent))
+	{
+		result.push(parent);
+	}
+	
+	// Visits the children of the cell
+	var childCount = this.getChildCount(parent);
+	
+	for (var i = 0; i < childCount; i++)
+	{
+		var child = this.getChildAt(parent, i);
+		result = result.concat(this.filterDescendants(filter, child));
+	}
+
+	return result;
+};
+
+/**
+ * Function: getRoot
+ * 
+ * Returns the root of the model or the topmost parent of the given cell.
+ *
+ * Parameters:
+ * 
+ * cell - Optional <mxCell> that specifies the child.
+ */
+mxGraphModel.prototype.getRoot = function(cell)
+{
+	var root = cell || this.root;
+	
+	if (cell != null)
+	{
+		while (cell != null)
+		{
+			root = cell;
+			cell = this.getParent(cell);
+		}
+	}
+	
+	return root;
+};
+
+/**
+ * Function: setRoot
+ * 
+ * Sets the <root> of the model using <mxRootChange> and adds the change to
+ * the current transaction. This resets all datastructures in the model and
+ * is the preferred way of clearing an existing model. Returns the new
+ * root.
+ * 
+ * Example:
+ * 
+ * (code)
+ * var root = new mxCell();
+ * root.insert(new mxCell());
+ * model.setRoot(root);
+ * (end)
+ *
+ * Parameters:
+ * 
+ * root - <mxCell> that specifies the new root.
+ */
+mxGraphModel.prototype.setRoot = function(root)
+{
+	this.execute(new mxRootChange(this, root));
+	
+	return root;
+};
+
+/**
+ * Function: rootChanged
+ * 
+ * Inner callback to change the root of the model and update the internal
+ * datastructures, such as <cells> and <nextId>. Returns the previous root.
+ *
+ * Parameters:
+ * 
+ * root - <mxCell> that specifies the new root.
+ */
+mxGraphModel.prototype.rootChanged = function(root)
+{
+	var oldRoot = this.root;
+	this.root = root;
+	
+	// Resets counters and datastructures
+	this.nextId = 0;
+	this.cells = null;
+	this.cellAdded(root);
+	
+	return oldRoot;
+};
+
+/**
+ * Function: isRoot
+ * 
+ * Returns true if the given cell is the root of the model and a non-null
+ * value.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the possible root.
+ */
+mxGraphModel.prototype.isRoot = function(cell)
+{
+	return cell != null && this.root == cell;
+};
+
+/**
+ * Function: isLayer
+ * 
+ * Returns true if <isRoot> returns true for the parent of the given cell.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the possible layer.
+ */
+mxGraphModel.prototype.isLayer = function(cell)
+{
+	return this.isRoot(this.getParent(cell));
+};
+
+/**
+ * Function: isAncestor
+ * 
+ * Returns true if the given parent is an ancestor of the given child.
+ *
+ * Parameters:
+ * 
+ * parent - <mxCell> that specifies the parent.
+ * child - <mxCell> that specifies the child.
+ */
+mxGraphModel.prototype.isAncestor = function(parent, child)
+{
+	while (child != null && child != parent)
+	{
+		child = this.getParent(child);
+	}
+	
+	return child == parent;
+};
+
+/**
+ * Function: contains
+ * 
+ * Returns true if the model contains the given <mxCell>.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> that specifies the cell.
+ */
+mxGraphModel.prototype.contains = function(cell)
+{
+	return this.isAncestor(this.root, cell);
+};
+
+/**
+ * Function: getParent
+ * 
+ * Returns the parent of the given cell.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> whose parent should be returned.
+ */
+mxGraphModel.prototype.getParent = function(cell)
+{
+	return (cell != null) ? cell.getParent() : null;
+};
+
+/**
+ * Function: add
+ * 
+ * Adds the specified child to the parent at the given index using
+ * <mxChildChange> and adds the change to the current transaction. If no
+ * index is specified then the child is appended to the parent's array of
+ * children. Returns the inserted child.
+ * 
+ * Parameters:
+ * 
+ * parent - <mxCell> that specifies the parent to contain the child.
+ * child - <mxCell> that specifies the child to be inserted.
+ * index - Optional integer that specifies the index of the child.
+ */
+mxGraphModel.prototype.add = function(parent, child, index)
+{
+	if (child != parent && parent != null && child != null)
+	{	
+		// Appends the child if no index was specified
+		if (index == null)
+		{
+			index = this.getChildCount(parent);
+		}
+		
+		var parentChanged = parent != this.getParent(child);
+		this.execute(new mxChildChange(this, parent, child, index));
+
+		// Maintains the edges parents by moving the edges
+		// into the nearest common ancestor of its
+		// terminals
+		if (this.maintainEdgeParent && parentChanged)
+		{
+			this.updateEdgeParents(child);
+		}
+	}
+	
+	return child;
+};
+
+/**
+ * Function: cellAdded
+ * 
+ * Inner callback to update <cells> when a cell has been added. This
+ * implementation resolves collisions by creating new Ids. To change the
+ * ID of a cell after it was inserted into the model, use the following
+ * code:
+ * 
+ * (code
+ * delete model.cells[cell.getId()];
+ * cell.setId(newId);
+ * model.cells[cell.getId()] = cell;
+ * (end)
+ *
+ * If the change of the ID should be part of the command history, then the
+ * cell should be removed from the model and a clone with the new ID should
+ * be reinserted into the model instead.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> that specifies the cell that has been added.
+ */
+mxGraphModel.prototype.cellAdded = function(cell)
+{
+	if (cell != null)
+	{
+		// Creates an Id for the cell if not Id exists
+		if (cell.getId() == null && this.createIds)
+		{
+			cell.setId(this.createId(cell));
+		}
+		
+		if (cell.getId() != null)
+		{
+			var collision = this.getCell(cell.getId());
+			
+			if (collision != cell)
+			{	
+				// Creates new Id for the cell
+				// as long as there is a collision
+				while (collision != null)
+				{
+					cell.setId(this.createId(cell));
+					collision = this.getCell(cell.getId());
+				}
+				
+				// Lazily creates the cells dictionary
+				if (this.cells == null)
+				{
+					this.cells = new Object();
+				}
+				
+				this.cells[cell.getId()] = cell;
+			}
+		}
+		
+		// Makes sure IDs of deleted cells are not reused
+		if (mxUtils.isNumeric(cell.getId()))
+		{
+			this.nextId = Math.max(this.nextId, cell.getId());
+		}
+		
+		// Recursively processes child cells
+		var childCount = this.getChildCount(cell);
+		
+		for (var i=0; i<childCount; i++)
+		{
+			this.cellAdded(this.getChildAt(cell, i));
+		}
+	}
+};
+
+/**
+ * Function: createId
+ * 
+ * Hook method to create an Id for the specified cell. This implementation
+ * concatenates <prefix>, id and <postfix> to create the Id and increments
+ * <nextId>. The cell is ignored by this implementation, but can be used in
+ * overridden methods to prefix the Ids with eg. the cell type.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to create the Id for.
+ */
+mxGraphModel.prototype.createId = function(cell)
+{
+	var id = this.nextId;
+	this.nextId++;
+	
+	return this.prefix + id + this.postfix;
+};
+
+/**
+ * Function: updateEdgeParents
+ * 
+ * Updates the parent for all edges that are connected to cell or one of
+ * its descendants using <updateEdgeParent>.
+ */
+mxGraphModel.prototype.updateEdgeParents = function(cell, root)
+{
+	// Gets the topmost node of the hierarchy
+	root = root || this.getRoot(cell);
+	
+	// Updates edges on children first
+	var childCount = this.getChildCount(cell);
+	
+	for (var i = 0; i < childCount; i++)
+	{
+		var child = this.getChildAt(cell, i);
+		this.updateEdgeParents(child, root);
+	}
+	
+	// Updates the parents of all connected edges
+	var edgeCount = this.getEdgeCount(cell);
+	var edges = [];
+
+	for (var i = 0; i < edgeCount; i++)
+	{
+		edges.push(this.getEdgeAt(cell, i));
+	}
+	
+	for (var i = 0; i < edges.length; i++)
+	{
+		var edge = edges[i];
+		
+		// Updates edge parent if edge and child have
+		// a common root node (does not need to be the
+		// model root node)
+		if (this.isAncestor(root, edge))
+		{
+			this.updateEdgeParent(edge, root);
+		}
+	}
+};
+
+/**
+ * Function: updateEdgeParent
+ *
+ * Inner callback to update the parent of the specified <mxCell> to the
+ * nearest-common-ancestor of its two terminals.
+ *
+ * Parameters:
+ * 
+ * edge - <mxCell> that specifies the edge.
+ * root - <mxCell> that represents the current root of the model.
+ */
+mxGraphModel.prototype.updateEdgeParent = function(edge, root)
+{
+	var source = this.getTerminal(edge, true);
+	var target = this.getTerminal(edge, false);
+	var cell = null;
+	
+	// Uses the first non-relative descendants of the source terminal
+	while (source != null && !this.isEdge(source) &&
+		source.geometry != null && source.geometry.relative)
+	{
+		source = this.getParent(source);
+	}
+	
+	// Uses the first non-relative descendants of the target terminal
+	while (target != null && this.ignoreRelativeEdgeParent &&
+		!this.isEdge(target) && target.geometry != null && 
+		target.geometry.relative)
+	{
+		target = this.getParent(target);
+	}
+	
+	if (this.isAncestor(root, source) && this.isAncestor(root, target))
+	{
+		if (source == target)
+		{
+			cell = this.getParent(source);
+		}
+		else
+		{
+			cell = this.getNearestCommonAncestor(source, target);
+		}
+
+		if (cell != null && (this.getParent(cell) != this.root ||
+			this.isAncestor(cell, edge)) && this.getParent(edge) != cell)
+		{
+			var geo = this.getGeometry(edge);
+			
+			if (geo != null)
+			{
+				var origin1 = this.getOrigin(this.getParent(edge));
+				var origin2 = this.getOrigin(cell);
+				
+				var dx = origin2.x - origin1.x;
+				var dy = origin2.y - origin1.y;
+				
+				geo = geo.clone();
+				geo.translate(-dx, -dy);
+				this.setGeometry(edge, geo);
+			}
+
+			this.add(cell, edge, this.getChildCount(cell));
+		}
+	}
+};
+
+/**
+ * Function: getOrigin
+ * 
+ * Returns the absolute, accumulated origin for the children inside the
+ * given parent as an <mxPoint>.
+ */
+mxGraphModel.prototype.getOrigin = function(cell)
+{
+	var result = null;
+	
+	if (cell != null)
+	{
+		result = this.getOrigin(this.getParent(cell));
+		
+		if (!this.isEdge(cell))
+		{
+			var geo = this.getGeometry(cell);
+			
+			if (geo != null)
+			{
+				result.x += geo.x;
+				result.y += geo.y;
+			}
+		}
+	}
+	else
+	{
+		result = new mxPoint();
+	}
+	
+	return result;
+};
+
+/**
+ * Function: getNearestCommonAncestor
+ * 
+ * Returns the nearest common ancestor for the specified cells.
+ *
+ * Parameters:
+ * 
+ * cell1 - <mxCell> that specifies the first cell in the tree.
+ * cell2 - <mxCell> that specifies the second cell in the tree.
+ */
+mxGraphModel.prototype.getNearestCommonAncestor = function(cell1, cell2)
+{
+	if (cell1 != null && cell2 != null)
+	{		
+		// Creates the cell path for the second cell
+		var path = mxCellPath.create(cell2);
+
+		if (path != null && path.length > 0)
+		{
+			// Bubbles through the ancestors of the first
+			// cell to find the nearest common ancestor.
+			var cell = cell1;
+			var current = mxCellPath.create(cell);
+			
+			// Inverts arguments
+			if (path.length < current.length)
+			{
+				cell = cell2;
+				var tmp = current;
+				current = path;
+				path = tmp;
+			}
+			
+			while (cell != null)
+			{
+				var parent = this.getParent(cell);
+				
+				// Checks if the cell path is equal to the beginning of the given cell path
+				if (path.indexOf(current + mxCellPath.PATH_SEPARATOR) == 0 && parent != null)
+				{
+					return cell;
+				}
+				
+				current = mxCellPath.getParentPath(current);
+				cell = parent;
+			}
+		}
+	}
+	
+	return null;
+};
+
+/**
+ * Function: remove
+ * 
+ * Removes the specified cell from the model using <mxChildChange> and adds
+ * the change to the current transaction. This operation will remove the
+ * cell and all of its children from the model. Returns the removed cell.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> that should be removed.
+ */
+mxGraphModel.prototype.remove = function(cell)
+{
+	if (cell == this.root)
+	{
+		this.setRoot(null);
+	}
+	else if (this.getParent(cell) != null)
+	{
+		this.execute(new mxChildChange(this, null, cell));
+	}
+	
+	return cell;
+};
+
+/**
+ * Function: cellRemoved
+ * 
+ * Inner callback to update <cells> when a cell has been removed.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> that specifies the cell that has been removed.
+ */
+mxGraphModel.prototype.cellRemoved = function(cell)
+{
+	if (cell != null && this.cells != null)
+	{
+		// Recursively processes child cells
+		var childCount = this.getChildCount(cell);
+		
+		for (var i = childCount - 1; i >= 0; i--)
+		{
+			this.cellRemoved(this.getChildAt(cell, i));
+		}
+		
+		// Removes the dictionary entry for the cell
+		if (this.cells != null && cell.getId() != null)
+		{
+			delete this.cells[cell.getId()];
+		}
+	}
+};
+
+/**
+ * Function: parentForCellChanged
+ * 
+ * Inner callback to update the parent of a cell using <mxCell.insert>
+ * on the parent and return the previous parent.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> to update the parent for.
+ * parent - <mxCell> that specifies the new parent of the cell.
+ * index - Optional integer that defines the index of the child
+ * in the parent's child array.
+ */
+mxGraphModel.prototype.parentForCellChanged = function(cell, parent, index)
+{
+	var previous = this.getParent(cell);
+	
+	if (parent != null)
+	{
+		if (parent != previous || previous.getIndex(cell) != index)
+		{
+			parent.insert(cell, index);
+		}
+	}
+	else if (previous != null)
+	{
+		var oldIndex = previous.getIndex(cell);
+		previous.remove(oldIndex);
+	}
+	
+	// Checks if the previous parent was already in the
+	// model and avoids calling cellAdded if it was.
+	if (!this.contains(previous) && parent != null)
+	{
+		this.cellAdded(cell);
+	}
+	else if (parent == null)
+	{
+		this.cellRemoved(cell);
+	}
+	
+	return previous;
+};
+
+/**
+ * Function: getChildCount
+ *
+ * Returns the number of children in the given cell.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> whose number of children should be returned.
+ */
+mxGraphModel.prototype.getChildCount = function(cell)
+{
+	return (cell != null) ? cell.getChildCount() : 0;
+};
+
+/**
+ * Function: getChildAt
+ *
+ * Returns the child of the given <mxCell> at the given index.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the parent.
+ * index - Integer that specifies the index of the child to be returned.
+ */
+mxGraphModel.prototype.getChildAt = function(cell, index)
+{
+	return (cell != null) ? cell.getChildAt(index) : null;
+};
+
+/**
+ * Function: getChildren
+ * 
+ * Returns all children of the given <mxCell> as an array of <mxCells>. The
+ * return value should be only be read.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> the represents the parent.
+ */
+mxGraphModel.prototype.getChildren = function(cell)
+{
+	return (cell != null) ? cell.children : null;
+};
+	
+/**
+ * Function: getChildVertices
+ * 
+ * Returns the child vertices of the given parent.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> whose child vertices should be returned.
+ */
+mxGraphModel.prototype.getChildVertices = function(parent)
+{
+	return this.getChildCells(parent, true, false);
+};
+		
+/**
+ * Function: getChildEdges
+ * 
+ * Returns the child edges of the given parent.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> whose child edges should be returned.
+ */
+mxGraphModel.prototype.getChildEdges = function(parent)
+{
+	return this.getChildCells(parent, false, true);
+};
+
+/**
+ * Function: getChildCells
+ * 
+ * Returns the children of the given cell that are vertices and/or edges
+ * depending on the arguments.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> the represents the parent.
+ * vertices - Boolean indicating if child vertices should be returned.
+ * Default is false.
+ * edges - Boolean indicating if child edges should be returned.
+ * Default is false.
+ */
+mxGraphModel.prototype.getChildCells = function(parent, vertices, edges)
+{
+	vertices = (vertices != null) ? vertices : false;
+	edges = (edges != null) ? edges : false;
+	
+	var childCount = this.getChildCount(parent);
+	var result = [];
+
+	for (var i = 0; i < childCount; i++)
+	{
+		var child = this.getChildAt(parent, i);
+
+		if ((!edges && !vertices) || (edges && this.isEdge(child)) ||
+			(vertices && this.isVertex(child)))
+		{
+			result.push(child);
+		}
+	}
+
+	return result;
+};
+		
+/**
+ * Function: getTerminal
+ * 
+ * Returns the source or target <mxCell> of the given edge depending on the
+ * value of the boolean parameter.
+ *
+ * Parameters:
+ * 
+ * edge - <mxCell> that specifies the edge.
+ * isSource - Boolean indicating which end of the edge should be returned.
+ */
+mxGraphModel.prototype.getTerminal = function(edge, isSource)
+{
+	return (edge != null) ? edge.getTerminal(isSource) : null;
+};
+
+/**
+ * Function: setTerminal
+ * 
+ * Sets the source or target terminal of the given <mxCell> using
+ * <mxTerminalChange> and adds the change to the current transaction.
+ * This implementation updates the parent of the edge using <updateEdgeParent>
+ * if required.
+ *
+ * Parameters:
+ * 
+ * edge - <mxCell> that specifies the edge.
+ * terminal - <mxCell> that specifies the new terminal.
+ * isSource - Boolean indicating if the terminal is the new source or
+ * target terminal of the edge.
+ */
+mxGraphModel.prototype.setTerminal = function(edge, terminal, isSource)
+{
+	var terminalChanged = terminal != this.getTerminal(edge, isSource);
+	this.execute(new mxTerminalChange(this, edge, terminal, isSource));
+	
+	if (this.maintainEdgeParent && terminalChanged)
+	{
+		this.updateEdgeParent(edge, this.getRoot());
+	}
+	
+	return terminal;
+};
+	
+/**
+ * Function: setTerminals
+ * 
+ * Sets the source and target <mxCell> of the given <mxCell> in a single
+ * transaction using <setTerminal> for each end of the edge.
+ *
+ * Parameters:
+ * 
+ * edge - <mxCell> that specifies the edge.
+ * source - <mxCell> that specifies the new source terminal.
+ * target - <mxCell> that specifies the new target terminal.
+ */
+mxGraphModel.prototype.setTerminals = function(edge, source, target)
+{
+	this.beginUpdate();
+	try
+	{
+		this.setTerminal(edge, source, true);
+		this.setTerminal(edge, target, false);
+	}
+	finally
+	{
+		this.endUpdate();
+	}
+};
+
+/**
+ * Function: terminalForCellChanged
+ * 
+ * Inner helper function to update the terminal of the edge using
+ * <mxCell.insertEdge> and return the previous terminal.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> that specifies the edge to be updated.
+ * terminal - <mxCell> that specifies the new terminal.
+ * isSource - Boolean indicating if the terminal is the new source or
+ * target terminal of the edge.
+ */
+mxGraphModel.prototype.terminalForCellChanged = function(edge, terminal, isSource)
+{
+	var previous = this.getTerminal(edge, isSource);
+	
+	if (terminal != null)
+	{
+		terminal.insertEdge(edge, isSource);
+	}
+	else if (previous != null)
+	{
+		previous.removeEdge(edge, isSource);
+	}
+	
+	return previous;
+};
+
+/**
+ * Function: getEdgeCount
+ * 
+ * Returns the number of distinct edges connected to the given cell.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the vertex.
+ */
+mxGraphModel.prototype.getEdgeCount = function(cell)
+{
+	return (cell != null) ? cell.getEdgeCount() : 0;
+};
+
+/**
+ * Function: getEdgeAt
+ * 
+ * Returns the edge of cell at the given index.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> that specifies the vertex.
+ * index - Integer that specifies the index of the edge
+ * to return.
+ */
+mxGraphModel.prototype.getEdgeAt = function(cell, index)
+{
+	return (cell != null) ? cell.getEdgeAt(index) : null;
+};
+	
+/**
+ * Function: getDirectedEdgeCount
+ * 
+ * Returns the number of incoming or outgoing edges, ignoring the given
+ * edge.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose edge count should be returned.
+ * outgoing - Boolean that specifies if the number of outgoing or
+ * incoming edges should be returned.
+ * ignoredEdge - <mxCell> that represents an edge to be ignored.
+ */
+mxGraphModel.prototype.getDirectedEdgeCount = function(cell, outgoing, ignoredEdge)
+{
+	var count = 0;
+	var edgeCount = this.getEdgeCount(cell);
+
+	for (var i = 0; i < edgeCount; i++)
+	{
+		var edge = this.getEdgeAt(cell, i);
+
+		if (edge != ignoredEdge && this.getTerminal(edge, outgoing) == cell)
+		{
+			count++;
+		}
+	}
+
+	return count;
+};
+
+/**
+ * Function: getConnections
+ * 
+ * Returns all edges of the given cell without loops.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose edges should be returned.
+ * 
+ */
+mxGraphModel.prototype.getConnections = function(cell)
+{
+	return this.getEdges(cell, true, true, false);
+};
+
+/**
+ * Function: getIncomingEdges
+ * 
+ * Returns the incoming edges of the given cell without loops.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose incoming edges should be returned.
+ * 
+ */
+mxGraphModel.prototype.getIncomingEdges = function(cell)
+{
+	return this.getEdges(cell, true, false, false);
+};
+
+/**
+ * Function: getOutgoingEdges
+ * 
+ * Returns the outgoing edges of the given cell without loops.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose outgoing edges should be returned.
+ * 
+ */
+mxGraphModel.prototype.getOutgoingEdges = function(cell)
+{
+	return this.getEdges(cell, false, true, false);
+};
+
+/**
+ * Function: getEdges
+ * 
+ * Returns all distinct edges connected to this cell as a new array of
+ * <mxCells>. If at least one of incoming or outgoing is true, then loops
+ * are ignored, otherwise if both are false, then all edges connected to
+ * the given cell are returned including loops.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that specifies the cell.
+ * incoming - Optional boolean that specifies if incoming edges should be
+ * returned. Default is true.
+ * outgoing - Optional boolean that specifies if outgoing edges should be
+ * returned. Default is true.
+ * includeLoops - Optional boolean that specifies if loops should be returned.
+ * Default is true. 
+ */
+mxGraphModel.prototype.getEdges = function(cell, incoming, outgoing, includeLoops)
+{
+	incoming = (incoming != null) ? incoming : true;
+	outgoing = (outgoing != null) ? outgoing : true;
+	includeLoops = (includeLoops != null) ? includeLoops : true;
+	
+	var edgeCount = this.getEdgeCount(cell);
+	var result = [];
+
+	for (var i = 0; i < edgeCount; i++)
+	{
+		var edge = this.getEdgeAt(cell, i);
+		var source = this.getTerminal(edge, true);
+		var target = this.getTerminal(edge, false);
+
+		if ((includeLoops && source == target) || ((source != target) && ((incoming && target == cell) ||
+			(outgoing && source == cell))))
+		{
+			result.push(edge);
+		}
+	}
+
+	return result;
+};
+
+/**
+ * Function: getEdgesBetween
+ * 
+ * Returns all edges between the given source and target pair. If directed
+ * is true, then only edges from the source to the target are returned,
+ * otherwise, all edges between the two cells are returned.
+ * 
+ * Parameters:
+ * 
+ * source - <mxCell> that defines the source terminal of the edge to be
+ * returned.
+ * target - <mxCell> that defines the target terminal of the edge to be
+ * returned.
+ * directed - Optional boolean that specifies if the direction of the
+ * edge should be taken into account. Default is false.
+ */
+mxGraphModel.prototype.getEdgesBetween = function(source, target, directed)
+{
+	directed = (directed != null) ? directed : false;
+	
+	var tmp1 = this.getEdgeCount(source);
+	var tmp2 = this.getEdgeCount(target);
+	
+	// Assumes the source has less connected edges
+	var terminal = source;
+	var edgeCount = tmp1;
+	
+	// Uses the smaller array of connected edges
+	// for searching the edge
+	if (tmp2 < tmp1)
+	{
+		edgeCount = tmp2;
+		terminal = target;
+	}
+	
+	var result = [];
+	
+	// Checks if the edge is connected to the correct
+	// cell and returns the first match
+	for (var i = 0; i < edgeCount; i++)
+	{
+		var edge = this.getEdgeAt(terminal, i);
+		var src = this.getTerminal(edge, true);
+		var trg = this.getTerminal(edge, false);
+		var directedMatch = (src == source) && (trg == target);
+		var oppositeMatch = (trg == source) && (src == target);
+
+		if (directedMatch || (!directed && oppositeMatch))
+		{
+			result.push(edge);
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: getOpposites
+ * 
+ * Returns all opposite vertices wrt terminal for the given edges, only
+ * returning sources and/or targets as specified. The result is returned
+ * as an array of <mxCells>.
+ * 
+ * Parameters:
+ * 
+ * edges - Array of <mxCells> that contain the edges to be examined.
+ * terminal - <mxCell> that specifies the known end of the edges.
+ * sources - Boolean that specifies if source terminals should be contained
+ * in the result. Default is true.
+ * targets - Boolean that specifies if target terminals should be contained
+ * in the result. Default is true.
+ */
+mxGraphModel.prototype.getOpposites = function(edges, terminal, sources, targets)
+{
+	sources = (sources != null) ? sources : true;
+	targets = (targets != null) ? targets : true;
+	
+	var terminals = [];
+	
+	if (edges != null)
+	{
+		for (var i = 0; i < edges.length; i++)
+		{
+			var source = this.getTerminal(edges[i], true);
+			var target = this.getTerminal(edges[i], false);
+			
+			// Checks if the terminal is the source of
+			// the edge and if the target should be
+			// stored in the result
+			if (source == terminal && target != null && target != terminal && targets)
+			{
+				terminals.push(target);
+			}
+			
+			// Checks if the terminal is the taget of
+			// the edge and if the source should be
+			// stored in the result
+			else if (target == terminal && source != null && source != terminal && sources)
+			{
+				terminals.push(source);
+			}
+		}
+	}
+	
+	return terminals;
+};
+
+/**
+ * Function: getTopmostCells
+ * 
+ * Returns the topmost cells of the hierarchy in an array that contains no
+ * descendants for each <mxCell> that it contains. Duplicates should be
+ * removed in the cells array to improve performance.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> whose topmost ancestors should be returned.
+ */
+mxGraphModel.prototype.getTopmostCells = function(cells)
+{
+	var dict = new mxDictionary();
+	var tmp = [];
+	
+	for (var i = 0; i < cells.length; i++)
+	{
+		dict.put(cells[i], true);
+	}
+	
+	for (var i = 0; i < cells.length; i++)
+	{
+		var cell = cells[i];
+		var topmost = true;
+		var parent = this.getParent(cell);
+		
+		while (parent != null)
+		{
+			if (dict.get(parent))
+			{
+				topmost = false;
+				break;
+			}
+			
+			parent = this.getParent(parent);
+		}
+		
+		if (topmost)
+		{
+			tmp.push(cell);
+		}
+	}
+	
+	return tmp;
+};
+
+/**
+ * Function: isVertex
+ * 
+ * Returns true if the given cell is a vertex.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the possible vertex.
+ */
+mxGraphModel.prototype.isVertex = function(cell)
+{
+	return (cell != null) ? cell.isVertex() : false;
+};
+
+/**
+ * Function: isEdge
+ * 
+ * Returns true if the given cell is an edge.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the possible edge.
+ */
+mxGraphModel.prototype.isEdge = function(cell)
+{
+	return (cell != null) ? cell.isEdge() : false;
+};
+
+/**
+ * Function: isConnectable
+ * 
+ * Returns true if the given <mxCell> is connectable. If <edgesConnectable>
+ * is false, then this function returns false for all edges else it returns
+ * the return value of <mxCell.isConnectable>.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> whose connectable state should be returned.
+ */
+mxGraphModel.prototype.isConnectable = function(cell)
+{
+	return (cell != null) ? cell.isConnectable() : false;
+};
+
+/**
+ * Function: getValue
+ * 
+ * Returns the user object of the given <mxCell> using <mxCell.getValue>.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> whose user object should be returned.
+ */
+mxGraphModel.prototype.getValue = function(cell)
+{
+	return (cell != null) ? cell.getValue() : null;
+};
+
+/**
+ * Function: setValue
+ * 
+ * Sets the user object of then given <mxCell> using <mxValueChange>
+ * and adds the change to the current transaction.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> whose user object should be changed.
+ * value - Object that defines the new user object.
+ */
+mxGraphModel.prototype.setValue = function(cell, value)
+{
+	this.execute(new mxValueChange(this, cell, value));
+	
+	return value;
+};
+
+/**
+ * Function: valueForCellChanged
+ * 
+ * Inner callback to update the user object of the given <mxCell>
+ * using <mxCell.valueChanged> and return the previous value,
+ * that is, the return value of <mxCell.valueChanged>.
+ * 
+ * To change a specific attribute in an XML node, the following code can be
+ * used.
+ * 
+ * (code)
+ * graph.getModel().valueForCellChanged = function(cell, value)
+ * {
+ *   var previous = cell.value.getAttribute('label');
+ *   cell.value.setAttribute('label', value);
+ *   
+ *   return previous;
+ * };
+ * (end) 
+ */
+mxGraphModel.prototype.valueForCellChanged = function(cell, value)
+{
+	return cell.valueChanged(value);
+};
+
+/**
+ * Function: getGeometry
+ * 
+ * Returns the <mxGeometry> of the given <mxCell>.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> whose geometry should be returned.
+ */
+mxGraphModel.prototype.getGeometry = function(cell)
+{
+	return (cell != null) ? cell.getGeometry() : null;
+};
+
+/**
+ * Function: setGeometry
+ * 
+ * Sets the <mxGeometry> of the given <mxCell>. The actual update
+ * of the cell is carried out in <geometryForCellChanged>. The
+ * <mxGeometryChange> action is used to encapsulate the change.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose geometry should be changed.
+ * geometry - <mxGeometry> that defines the new geometry.
+ */
+mxGraphModel.prototype.setGeometry = function(cell, geometry)
+{
+	if (geometry != this.getGeometry(cell))
+	{
+		this.execute(new mxGeometryChange(this, cell, geometry));
+	}
+	
+	return geometry;
+};
+
+/**
+ * Function: geometryForCellChanged
+ * 
+ * Inner callback to update the <mxGeometry> of the given <mxCell> using
+ * <mxCell.setGeometry> and return the previous <mxGeometry>.
+ */
+mxGraphModel.prototype.geometryForCellChanged = function(cell, geometry)
+{
+	var previous = this.getGeometry(cell);
+	cell.setGeometry(geometry);
+	
+	return previous;
+};
+
+/**
+ * Function: getStyle
+ * 
+ * Returns the style of the given <mxCell>.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> whose style should be returned.
+ */
+mxGraphModel.prototype.getStyle = function(cell)
+{
+	return (cell != null) ? cell.getStyle() : null;
+};
+
+/**
+ * Function: setStyle
+ * 
+ * Sets the style of the given <mxCell> using <mxStyleChange> and
+ * adds the change to the current transaction.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> whose style should be changed.
+ * style - String of the form [stylename;|key=value;] to specify
+ * the new cell style.
+ */
+mxGraphModel.prototype.setStyle = function(cell, style)
+{
+	if (style != this.getStyle(cell))
+	{
+		this.execute(new mxStyleChange(this, cell, style));
+	}
+	
+	return style;
+};
+
+/**
+ * Function: styleForCellChanged
+ * 
+ * Inner callback to update the style of the given <mxCell>
+ * using <mxCell.setStyle> and return the previous style.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> that specifies the cell to be updated.
+ * style - String of the form [stylename;|key=value;] to specify
+ * the new cell style.
+ */
+mxGraphModel.prototype.styleForCellChanged = function(cell, style)
+{
+	var previous = this.getStyle(cell);
+	cell.setStyle(style);
+	
+	return previous;
+};
+
+/**
+ * Function: isCollapsed
+ * 
+ * Returns true if the given <mxCell> is collapsed.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> whose collapsed state should be returned.
+ */
+mxGraphModel.prototype.isCollapsed = function(cell)
+{
+	return (cell != null) ? cell.isCollapsed() : false;
+};
+
+/**
+ * Function: setCollapsed
+ * 
+ * Sets the collapsed state of the given <mxCell> using <mxCollapseChange>
+ * and adds the change to the current transaction.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> whose collapsed state should be changed.
+ * collapsed - Boolean that specifies the new collpased state.
+ */
+mxGraphModel.prototype.setCollapsed = function(cell, collapsed)
+{
+	if (collapsed != this.isCollapsed(cell))
+	{
+		this.execute(new mxCollapseChange(this, cell, collapsed));
+	}
+	
+	return collapsed;
+};
+	
+/**
+ * Function: collapsedStateForCellChanged
+ *
+ * Inner callback to update the collapsed state of the
+ * given <mxCell> using <mxCell.setCollapsed> and return
+ * the previous collapsed state.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> that specifies the cell to be updated.
+ * collapsed - Boolean that specifies the new collpased state.
+ */
+mxGraphModel.prototype.collapsedStateForCellChanged = function(cell, collapsed)
+{
+	var previous = this.isCollapsed(cell);
+	cell.setCollapsed(collapsed);
+	
+	return previous;
+};
+
+/**
+ * Function: isVisible
+ * 
+ * Returns true if the given <mxCell> is visible.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose visible state should be returned.
+ */
+mxGraphModel.prototype.isVisible = function(cell)
+{
+	return (cell != null) ? cell.isVisible() : false;
+};
+
+/**
+ * Function: setVisible
+ * 
+ * Sets the visible state of the given <mxCell> using <mxVisibleChange> and
+ * adds the change to the current transaction.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> whose visible state should be changed.
+ * visible - Boolean that specifies the new visible state.
+ */
+mxGraphModel.prototype.setVisible = function(cell, visible)
+{
+	if (visible != this.isVisible(cell))
+	{
+		this.execute(new mxVisibleChange(this, cell, visible));
+	}
+	
+	return visible;
+};
+	
+/**
+ * Function: visibleStateForCellChanged
+ *
+ * Inner callback to update the visible state of the
+ * given <mxCell> using <mxCell.setCollapsed> and return
+ * the previous visible state.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> that specifies the cell to be updated.
+ * visible - Boolean that specifies the new visible state.
+ */
+mxGraphModel.prototype.visibleStateForCellChanged = function(cell, visible)
+{
+	var previous = this.isVisible(cell);
+	cell.setVisible(visible);
+	
+	return previous;
+};
+
+/**
+ * Function: execute
+ * 
+ * Executes the given edit and fires events if required. The edit object
+ * requires an execute function which is invoked. The edit is added to the
+ * <currentEdit> between <beginUpdate> and <endUpdate> calls, so that
+ * events will be fired if this execute is an individual transaction, that
+ * is, if no previous <beginUpdate> calls have been made without calling
+ * <endUpdate>. This implementation fires an <execute> event before
+ * executing the given change.
+ * 
+ * Parameters:
+ * 
+ * change - Object that described the change.
+ */
+mxGraphModel.prototype.execute = function(change)
+{
+	change.execute();
+	this.beginUpdate();
+	this.currentEdit.add(change);
+	this.fireEvent(new mxEventObject(mxEvent.EXECUTE, 'change', change));
+	// New global executed event
+	this.fireEvent(new mxEventObject(mxEvent.EXECUTED, 'change', change));
+	this.endUpdate();
+};
+
+/**
+ * Function: beginUpdate
+ * 
+ * Increments the <updateLevel> by one. The event notification
+ * is queued until <updateLevel> reaches 0 by use of
+ * <endUpdate>.
+ *
+ * All changes on <mxGraphModel> are transactional,
+ * that is, they are executed in a single undoable change
+ * on the model (without transaction isolation).
+ * Therefore, if you want to combine any
+ * number of changes into a single undoable change,
+ * you should group any two or more API calls that
+ * modify the graph model between <beginUpdate>
+ * and <endUpdate> calls as shown here:
+ * 
+ * (code)
+ * var model = graph.getModel();
+ * var parent = graph.getDefaultParent();
+ * var index = model.getChildCount(parent);
+ * model.beginUpdate();
+ * try
+ * {
+ *   model.add(parent, v1, index);
+ *   model.add(parent, v2, index+1);
+ * }
+ * finally
+ * {
+ *   model.endUpdate();
+ * }
+ * (end)
+ * 
+ * Of course there is a shortcut for appending a
+ * sequence of cells into the default parent:
+ * 
+ * (code)
+ * graph.addCells([v1, v2]).
+ * (end)
+ */
+mxGraphModel.prototype.beginUpdate = function()
+{
+	this.updateLevel++;
+	this.fireEvent(new mxEventObject(mxEvent.BEGIN_UPDATE));
+	
+	if (this.updateLevel == 1)
+	{
+		this.fireEvent(new mxEventObject(mxEvent.START_EDIT));
+	}
+};
+
+/**
+ * Function: endUpdate
+ * 
+ * Decrements the <updateLevel> by one and fires an <undo>
+ * event if the <updateLevel> reaches 0. This function
+ * indirectly fires a <change> event by invoking the notify
+ * function on the <currentEdit> und then creates a new
+ * <currentEdit> using <createUndoableEdit>.
+ *
+ * The <undo> event is fired only once per edit, whereas
+ * the <change> event is fired whenever the notify
+ * function is invoked, that is, on undo and redo of
+ * the edit.
+ */
+mxGraphModel.prototype.endUpdate = function()
+{
+	this.updateLevel--;
+	
+	if (this.updateLevel == 0)
+	{
+		this.fireEvent(new mxEventObject(mxEvent.END_EDIT));
+	}
+	
+	if (!this.endingUpdate)
+	{
+		this.endingUpdate = this.updateLevel == 0;
+		this.fireEvent(new mxEventObject(mxEvent.END_UPDATE, 'edit', this.currentEdit));
+
+		try
+		{		
+			if (this.endingUpdate && !this.currentEdit.isEmpty())
+			{
+				this.fireEvent(new mxEventObject(mxEvent.BEFORE_UNDO, 'edit', this.currentEdit));
+				var tmp = this.currentEdit;
+				this.currentEdit = this.createUndoableEdit();
+				tmp.notify();
+				this.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', tmp));
+			}
+		}
+		finally
+		{
+			this.endingUpdate = false;
+		}
+	}
+};
+
+/**
+ * Function: createUndoableEdit
+ * 
+ * Creates a new <mxUndoableEdit> that implements the
+ * notify function to fire a <change> and <notify> event
+ * through the <mxUndoableEdit>'s source.
+ */
+mxGraphModel.prototype.createUndoableEdit = function()
+{
+	var edit = new mxUndoableEdit(this, true);
+	
+	edit.notify = function()
+	{
+		// LATER: Remove changes property (deprecated)
+		edit.source.fireEvent(new mxEventObject(mxEvent.CHANGE,
+			'edit', edit, 'changes', edit.changes));
+		edit.source.fireEvent(new mxEventObject(mxEvent.NOTIFY,
+			'edit', edit, 'changes', edit.changes));
+	};
+	
+	return edit;
+};
+
+/**
+ * Function: mergeChildren
+ * 
+ * Merges the children of the given cell into the given target cell inside
+ * this model. All cells are cloned unless there is a corresponding cell in
+ * the model with the same id, in which case the source cell is ignored and
+ * all edges are connected to the corresponding cell in this model. Edges
+ * are considered to have no identity and are always cloned unless the
+ * cloneAllEdges flag is set to false, in which case edges with the same
+ * id in the target model are reconnected to reflect the terminals of the
+ * source edges.
+ */
+mxGraphModel.prototype.mergeChildren = function(from, to, cloneAllEdges)
+{
+	cloneAllEdges = (cloneAllEdges != null) ? cloneAllEdges : true;
+	
+	this.beginUpdate();
+	try
+	{
+		var mapping = new Object();
+		this.mergeChildrenImpl(from, to, cloneAllEdges, mapping);
+		
+		// Post-processes all edges in the mapping and
+		// reconnects the terminals to the corresponding
+		// cells in the target model
+		for (var key in mapping)
+		{
+			var cell = mapping[key];
+			var terminal = this.getTerminal(cell, true);
+
+			if (terminal != null)
+			{
+				terminal = mapping[mxCellPath.create(terminal)];
+				this.setTerminal(cell, terminal, true);
+			}
+			
+			terminal = this.getTerminal(cell, false);
+			
+			if (terminal != null)
+			{
+				terminal = mapping[mxCellPath.create(terminal)];
+				this.setTerminal(cell, terminal, false);
+			}
+		}
+	}
+	finally
+	{
+		this.endUpdate();
+	}
+};
+
+/**
+ * Function: mergeChildren
+ * 
+ * Clones the children of the source cell into the given target cell in
+ * this model and adds an entry to the mapping that maps from the source
+ * cell to the target cell with the same id or the clone of the source cell
+ * that was inserted into this model.
+ */
+mxGraphModel.prototype.mergeChildrenImpl = function(from, to, cloneAllEdges, mapping)
+{
+	this.beginUpdate();
+	try
+	{
+		var childCount = from.getChildCount();
+		
+		for (var i = 0; i < childCount; i++)
+		{
+			var cell = from.getChildAt(i);
+			
+			if (typeof(cell.getId) == 'function')
+			{
+				var id = cell.getId();
+				var target = (id != null && (!this.isEdge(cell) || !cloneAllEdges)) ?
+						this.getCell(id) : null;
+				
+				// Clones and adds the child if no cell exists for the id
+				if (target == null)
+				{
+					var clone = cell.clone();
+					clone.setId(id);
+					
+					// Sets the terminals from the original cell to the clone
+					// because the lookup uses strings not cells in JS
+					clone.setTerminal(cell.getTerminal(true), true);
+					clone.setTerminal(cell.getTerminal(false), false);
+					
+					// Do *NOT* use model.add as this will move the edge away
+					// from the parent in updateEdgeParent if maintainEdgeParent
+					// is enabled in the target model
+					target = to.insert(clone);
+					this.cellAdded(target);
+				}
+				
+				// Stores the mapping for later reconnecting edges
+				mapping[mxCellPath.create(cell)] = target;
+				
+				// Recurses
+				this.mergeChildrenImpl(cell, target, cloneAllEdges, mapping);
+			}
+		}
+	}
+	finally
+	{
+		this.endUpdate();
+	}
+};
+
+/**
+ * Function: getParents
+ * 
+ * Returns an array that represents the set (no duplicates) of all parents
+ * for the given array of cells.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of cells whose parents should be returned.
+ */
+mxGraphModel.prototype.getParents = function(cells)
+{
+	var parents = [];
+	
+	if (cells != null)
+	{
+		var dict = new mxDictionary();
+		
+		for (var i = 0; i < cells.length; i++)
+		{
+			var parent = this.getParent(cells[i]);
+			
+			if (parent != null && !dict.get(parent))
+			{
+				dict.put(parent, true);
+				parents.push(parent);
+			}
+		}
+	}
+	
+	return parents;
+};
+
+//
+// Cell Cloning
+//
+
+/**
+ * Function: cloneCell
+ * 
+ * Returns a deep clone of the given <mxCell> (including
+ * the children) which is created using <cloneCells>.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> to be cloned.
+ */
+mxGraphModel.prototype.cloneCell = function(cell)
+{
+	if (cell != null)
+	{
+		return this.cloneCells([cell], true)[0];
+	}
+	
+	return null;
+};
+
+/**
+ * Function: cloneCells
+ * 
+ * Returns an array of clones for the given array of <mxCells>.
+ * Depending on the value of includeChildren, a deep clone is created for
+ * each cell. Connections are restored based if the corresponding
+ * cell is contained in the passed in array.
+ *
+ * Parameters:
+ * 
+ * cells - Array of <mxCell> to be cloned.
+ * includeChildren - Boolean indicating if the cells should be cloned
+ * with all descendants.
+ * mapping - Optional mapping for existing clones.
+ */
+mxGraphModel.prototype.cloneCells = function(cells, includeChildren, mapping)
+{
+	mapping = (mapping != null) ? mapping : new Object();
+	var clones = [];
+	
+	for (var i = 0; i < cells.length; i++)
+	{
+		if (cells[i] != null)
+		{
+			clones.push(this.cloneCellImpl(cells[i], mapping, includeChildren));
+		}
+		else
+		{
+			clones.push(null);
+		}
+	}
+	
+	for (var i = 0; i < clones.length; i++)
+	{
+		if (clones[i] != null)
+		{
+			this.restoreClone(clones[i], cells[i], mapping);
+		}
+	}
+	
+	return clones;
+};
+			
+/**
+ * Function: cloneCellImpl
+ * 
+ * Inner helper method for cloning cells recursively.
+ */
+mxGraphModel.prototype.cloneCellImpl = function(cell, mapping, includeChildren)
+{
+	var clone = this.cellCloned(cell);
+	
+	// Stores the clone in the lookup table
+	mapping[mxObjectIdentity.get(cell)] = clone;
+	
+	if (includeChildren)
+	{
+		var childCount = this.getChildCount(cell);
+		
+		for (var i = 0; i < childCount; i++)
+		{
+			var cloneChild = this.cloneCellImpl(
+				this.getChildAt(cell, i), mapping, true);
+			clone.insert(cloneChild);
+		}
+	}
+	
+	return clone;
+};
+
+/**
+ * Function: cellCloned
+ * 
+ * Hook for cloning the cell. This returns cell.clone() or
+ * any possible exceptions.
+ */
+mxGraphModel.prototype.cellCloned = function(cell)
+{
+	return cell.clone();
+};
+
+/**
+ * Function: restoreClone
+ * 
+ * Inner helper method for restoring the connections in
+ * a network of cloned cells.
+ */
+mxGraphModel.prototype.restoreClone = function(clone, cell, mapping)
+{
+	var source = this.getTerminal(cell, true);
+	
+	if (source != null)
+	{
+		var tmp = mapping[mxObjectIdentity.get(source)];
+		
+		if (tmp != null)
+		{
+			tmp.insertEdge(clone, true);
+		}
+	}
+	
+	var target = this.getTerminal(cell, false);
+	
+	if (target != null)
+	{
+		var tmp = mapping[mxObjectIdentity.get(target)];
+		
+		if (tmp != null)
+		{	
+			tmp.insertEdge(clone, false);
+		}
+	}
+	
+	var childCount = this.getChildCount(clone);
+	
+	for (var i = 0; i < childCount; i++)
+	{
+		this.restoreClone(this.getChildAt(clone, i),
+			this.getChildAt(cell, i), mapping);
+	}
+};
+
+//
+// Atomic changes
+//
+
+/**
+ * Class: mxRootChange
+ * 
+ * Action to change the root in a model.
+ *
+ * Constructor: mxRootChange
+ * 
+ * Constructs a change of the root in the
+ * specified model.
+ */
+function mxRootChange(model, root)
+{
+	this.model = model;
+	this.root = root;
+	this.previous = root;
+};
+
+/**
+ * Function: execute
+ * 
+ * Carries out a change of the root using
+ * <mxGraphModel.rootChanged>.
+ */
+mxRootChange.prototype.execute = function()
+{
+	this.root = this.previous;
+	this.previous = this.model.rootChanged(this.previous);
+};
+
+/**
+ * Class: mxChildChange
+ * 
+ * Action to add or remove a child in a model.
+ *
+ * Constructor: mxChildChange
+ * 
+ * Constructs a change of a child in the
+ * specified model.
+ */
+function mxChildChange(model, parent, child, index)
+{
+	this.model = model;
+	this.parent = parent;
+	this.previous = parent;
+	this.child = child;
+	this.index = index;
+	this.previousIndex = index;
+};
+
+/**
+ * Function: execute
+ * 
+ * Changes the parent of <child> using
+ * <mxGraphModel.parentForCellChanged> and
+ * removes or restores the cell's
+ * connections.
+ */
+mxChildChange.prototype.execute = function()
+{
+	var tmp = this.model.getParent(this.child);
+	var tmp2 = (tmp != null) ? tmp.getIndex(this.child) : 0;
+	
+	if (this.previous == null)
+	{
+		this.connect(this.child, false);
+	}
+	
+	tmp = this.model.parentForCellChanged(
+		this.child, this.previous, this.previousIndex);
+		
+	if (this.previous != null)
+	{
+		this.connect(this.child, true);
+	}
+	
+	this.parent = this.previous;
+	this.previous = tmp;
+	this.index = this.previousIndex;
+	this.previousIndex = tmp2;
+};
+
+/**
+ * Function: disconnect
+ * 
+ * Disconnects the given cell recursively from its
+ * terminals and stores the previous terminal in the
+ * cell's terminals.
+ */
+mxChildChange.prototype.connect = function(cell, isConnect)
+{
+	isConnect = (isConnect != null) ? isConnect : true;
+	
+	var source = cell.getTerminal(true);
+	var target = cell.getTerminal(false);
+	
+	if (source != null)
+	{
+		if (isConnect)
+		{
+			this.model.terminalForCellChanged(cell, source, true);
+		}
+		else
+		{
+			this.model.terminalForCellChanged(cell, null, true);
+		}
+	}
+	
+	if (target != null)
+	{
+		if (isConnect)
+		{
+			this.model.terminalForCellChanged(cell, target, false);
+		}
+		else
+		{
+			this.model.terminalForCellChanged(cell, null, false);
+		}
+	}
+	
+	cell.setTerminal(source, true);
+	cell.setTerminal(target, false);
+	
+	var childCount = this.model.getChildCount(cell);
+	
+	for (var i=0; i<childCount; i++)
+	{
+		this.connect(this.model.getChildAt(cell, i), isConnect);
+	}
+};
+
+/**
+ * Class: mxTerminalChange
+ * 
+ * Action to change a terminal in a model.
+ *
+ * Constructor: mxTerminalChange
+ * 
+ * Constructs a change of a terminal in the 
+ * specified model.
+ */
+function mxTerminalChange(model, cell, terminal, source)
+{
+	this.model = model;
+	this.cell = cell;
+	this.terminal = terminal;
+	this.previous = terminal;
+	this.source = source;
+};
+
+/**
+ * Function: execute
+ * 
+ * Changes the terminal of <cell> to <previous> using
+ * <mxGraphModel.terminalForCellChanged>.
+ */
+mxTerminalChange.prototype.execute = function()
+{
+	this.terminal = this.previous;
+	this.previous = this.model.terminalForCellChanged(
+		this.cell, this.previous, this.source);
+};
+
+/**
+ * Class: mxValueChange
+ * 
+ * Action to change a user object in a model.
+ *
+ * Constructor: mxValueChange
+ * 
+ * Constructs a change of a user object in the 
+ * specified model.
+ */
+function mxValueChange(model, cell, value)
+{
+	this.model = model;
+	this.cell = cell;
+	this.value = value;
+	this.previous = value;
+};
+
+/**
+ * Function: execute
+ * 
+ * Changes the value of <cell> to <previous> using
+ * <mxGraphModel.valueForCellChanged>.
+ */
+mxValueChange.prototype.execute = function()
+{
+	this.value = this.previous;
+	this.previous = this.model.valueForCellChanged(
+		this.cell, this.previous);
+};
+
+/**
+ * Class: mxStyleChange
+ * 
+ * Action to change a cell's style in a model.
+ *
+ * Constructor: mxStyleChange
+ * 
+ * Constructs a change of a style in the
+ * specified model.
+ */
+function mxStyleChange(model, cell, style)
+{
+	this.model = model;
+	this.cell = cell;
+	this.style = style;
+	this.previous = style;
+};
+
+/**
+ * Function: execute
+ * 
+ * Changes the style of <cell> to <previous> using
+ * <mxGraphModel.styleForCellChanged>.
+ */
+mxStyleChange.prototype.execute = function()
+{
+	this.style = this.previous;
+	this.previous = this.model.styleForCellChanged(
+		this.cell, this.previous);
+};
+
+/**
+ * Class: mxGeometryChange
+ * 
+ * Action to change a cell's geometry in a model.
+ *
+ * Constructor: mxGeometryChange
+ * 
+ * Constructs a change of a geometry in the
+ * specified model.
+ */
+function mxGeometryChange(model, cell, geometry)
+{
+	this.model = model;
+	this.cell = cell;
+	this.geometry = geometry;
+	this.previous = geometry;
+};
+
+/**
+ * Function: execute
+ * 
+ * Changes the geometry of <cell> ro <previous> using
+ * <mxGraphModel.geometryForCellChanged>.
+ */
+mxGeometryChange.prototype.execute = function()
+{
+	this.geometry = this.previous;
+	this.previous = this.model.geometryForCellChanged(
+		this.cell, this.previous);
+};
+
+/**
+ * Class: mxCollapseChange
+ * 
+ * Action to change a cell's collapsed state in a model.
+ *
+ * Constructor: mxCollapseChange
+ * 
+ * Constructs a change of a collapsed state in the
+ * specified model.
+ */
+function mxCollapseChange(model, cell, collapsed)
+{
+	this.model = model;
+	this.cell = cell;
+	this.collapsed = collapsed;
+	this.previous = collapsed;
+};
+
+/**
+ * Function: execute
+ * 
+ * Changes the collapsed state of <cell> to <previous> using
+ * <mxGraphModel.collapsedStateForCellChanged>.
+ */
+mxCollapseChange.prototype.execute = function()
+{
+	this.collapsed = this.previous;
+	this.previous = this.model.collapsedStateForCellChanged(
+		this.cell, this.previous);
+};
+
+/**
+ * Class: mxVisibleChange
+ * 
+ * Action to change a cell's visible state in a model.
+ *
+ * Constructor: mxVisibleChange
+ * 
+ * Constructs a change of a visible state in the
+ * specified model.
+ */
+function mxVisibleChange(model, cell, visible)
+{
+	this.model = model;
+	this.cell = cell;
+	this.visible = visible;
+	this.previous = visible;
+};
+
+/**
+ * Function: execute
+ * 
+ * Changes the visible state of <cell> to <previous> using
+ * <mxGraphModel.visibleStateForCellChanged>.
+ */
+mxVisibleChange.prototype.execute = function()
+{
+	this.visible = this.previous;
+	this.previous = this.model.visibleStateForCellChanged(
+		this.cell, this.previous);
+};
+
+/**
+ * Class: mxCellAttributeChange
+ * 
+ * Action to change the attribute of a cell's user object.
+ * There is no method on the graph model that uses this
+ * action. To use the action, you can use the code shown
+ * in the example below.
+ * 
+ * Example:
+ * 
+ * To change the attributeName in the cell's user object
+ * to attributeValue, use the following code:
+ * 
+ * (code)
+ * model.beginUpdate();
+ * try
+ * {
+ *   var edit = new mxCellAttributeChange(
+ *     cell, attributeName, attributeValue);
+ *   model.execute(edit);
+ * }
+ * finally
+ * {
+ *   model.endUpdate();
+ * } 
+ * (end)
+ *
+ * Constructor: mxCellAttributeChange
+ * 
+ * Constructs a change of a attribute of the DOM node
+ * stored as the value of the given <mxCell>.
+ */
+function mxCellAttributeChange(cell, attribute, value)
+{
+	this.cell = cell;
+	this.attribute = attribute;
+	this.value = value;
+	this.previous = value;
+};
+
+/**
+ * Function: execute
+ * 
+ * Changes the attribute of the cell's user object by
+ * using <mxCell.setAttribute>.
+ */
+mxCellAttributeChange.prototype.execute = function()
+{
+	var tmp = this.cell.getAttribute(this.attribute);
+	
+	if (this.previous == null)
+	{
+		this.cell.value.removeAttribute(this.attribute);
+	}
+	else
+	{
+		this.cell.setAttribute(this.attribute, this.previous);
+	}
+	
+	this.previous = tmp;
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/mxClient.js b/airavata-kubernetes/workflow-composer/src/js/mxClient.js
new file mode 100644
index 0000000..2e9e892
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/mxClient.js
@@ -0,0 +1,769 @@
+/**
+ * Copyright (c) 2006-2017, JGraph Ltd
+ * Copyright (c) 2006-2017, Gaudenz Alder
+ */
+var mxClient =
+{
+	/**
+	 * Class: mxClient
+	 *
+	 * Bootstrapping mechanism for the mxGraph thin client. The production version
+	 * of this file contains all code required to run the mxGraph thin client, as
+	 * well as global constants to identify the browser and operating system in
+	 * use. You may have to load chrome://global/content/contentAreaUtils.js in
+	 * your page to disable certain security restrictions in Mozilla.
+	 * 
+	 * Variable: VERSION
+	 *
+	 * Contains the current version of the mxGraph library. The strings that
+	 * communicate versions of mxGraph use the following format.
+	 * 
+	 * versionMajor.versionMinor.buildNumber.revisionNumber
+	 * 
+	 * Current version is 3.7.5.
+	 */
+	VERSION: '3.7.5',
+
+	/**
+	 * Variable: IS_IE
+	 *
+	 * True if the current browser is Internet Explorer 10 or below. Use <mxClient.IS_IE11>
+	 * to detect IE 11.
+	 */
+	IS_IE: navigator.userAgent.indexOf('MSIE') >= 0,
+
+	/**
+	 * Variable: IS_IE6
+	 *
+	 * True if the current browser is Internet Explorer 6.x.
+	 */
+	IS_IE6: navigator.userAgent.indexOf('MSIE 6') >= 0,
+
+	/**
+	 * Variable: IS_IE11
+	 *
+	 * True if the current browser is Internet Explorer 11.x.
+	 */
+	IS_IE11: !!navigator.userAgent.match(/Trident\/7\./),
+
+	/**
+	 * Variable: IS_EDGE
+	 *
+	 * True if the current browser is Microsoft Edge.
+	 */
+	IS_EDGE: !!navigator.userAgent.match(/Edge\//),
+
+	/**
+	 * Variable: IS_QUIRKS
+	 *
+	 * True if the current browser is Internet Explorer and it is in quirks mode.
+	 */
+	IS_QUIRKS: navigator.userAgent.indexOf('MSIE') >= 0 && (document.documentMode == null || document.documentMode == 5),
+
+	/**
+	 * Variable: IS_EM
+	 * 
+	 * True if the browser is IE11 in enterprise mode (IE8 standards mode).
+	 */
+	IS_EM: 'spellcheck' in document.createElement('textarea') && document.documentMode == 8,
+
+	/**
+	 * Variable: VML_PREFIX
+	 * 
+	 * Prefix for VML namespace in node names. Default is 'v'.
+	 */
+	VML_PREFIX: 'v',
+
+	/**
+	 * Variable: OFFICE_PREFIX
+	 * 
+	 * Prefix for VML office namespace in node names. Default is 'o'.
+	 */
+	OFFICE_PREFIX: 'o',
+
+	/**
+	 * Variable: IS_NS
+	 *
+	 * True if the current browser is Netscape (including Firefox).
+	 */
+  	IS_NS: navigator.userAgent.indexOf('Mozilla/') >= 0 &&
+  		navigator.userAgent.indexOf('MSIE') < 0 &&
+  		navigator.userAgent.indexOf('Edge/') < 0,
+
+	/**
+	 * Variable: IS_OP
+	 *
+	 * True if the current browser is Opera.
+	 */
+  	IS_OP: navigator.userAgent.indexOf('Opera/') >= 0 ||
+  		navigator.userAgent.indexOf('OPR/') >= 0,
+
+	/**
+	 * Variable: IS_OT
+	 *
+	 * True if -o-transform is available as a CSS style, ie for Opera browsers
+	 * based on a Presto engine with version 2.5 or later.
+	 */
+  	IS_OT: navigator.userAgent.indexOf('Presto/') >= 0 &&
+  		navigator.userAgent.indexOf('Presto/2.4.') < 0 &&
+  		navigator.userAgent.indexOf('Presto/2.3.') < 0 &&
+  		navigator.userAgent.indexOf('Presto/2.2.') < 0 &&
+  		navigator.userAgent.indexOf('Presto/2.1.') < 0 &&
+  		navigator.userAgent.indexOf('Presto/2.0.') < 0 &&
+  		navigator.userAgent.indexOf('Presto/1.') < 0,
+  	
+	/**
+	 * Variable: IS_SF
+	 *
+	 * True if the current browser is Safari.
+	 */
+  	IS_SF: navigator.userAgent.indexOf('AppleWebKit/') >= 0 &&
+  		navigator.userAgent.indexOf('Chrome/') < 0 &&
+  		navigator.userAgent.indexOf('Edge/') < 0,
+  	
+	/**
+	 * Variable: IS_IOS
+	 * 
+	 * Returns true if the user agent is an iPad, iPhone or iPod.
+	 */
+  	IS_IOS: (navigator.userAgent.match(/(iPad|iPhone|iPod)/g) ? true : false),
+  		
+	/**
+	 * Variable: IS_GC
+	 *
+	 * True if the current browser is Google Chrome.
+	 */
+  	IS_GC: navigator.userAgent.indexOf('Chrome/') >= 0 &&
+		navigator.userAgent.indexOf('Edge/') < 0,
+	
+	/**
+	 * Variable: IS_CHROMEAPP
+	 *
+	 * True if the this is running inside a Chrome App.
+	 */
+  	IS_CHROMEAPP: window.chrome != null && chrome.app != null && chrome.app.runtime != null,
+		
+	/**
+	 * Variable: IS_FF
+	 *
+	 * True if the current browser is Firefox.
+	 */
+  	IS_FF: navigator.userAgent.indexOf('Firefox/') >= 0,
+  	
+	/**
+	 * Variable: IS_MT
+	 *
+	 * True if -moz-transform is available as a CSS style. This is the case
+	 * for all Firefox-based browsers newer than or equal 3, such as Camino,
+	 * Iceweasel, Seamonkey and Iceape.
+	 */
+  	IS_MT: (navigator.userAgent.indexOf('Firefox/') >= 0 &&
+		navigator.userAgent.indexOf('Firefox/1.') < 0 &&
+  		navigator.userAgent.indexOf('Firefox/2.') < 0) ||
+  		(navigator.userAgent.indexOf('Iceweasel/') >= 0 &&
+  		navigator.userAgent.indexOf('Iceweasel/1.') < 0 &&
+  		navigator.userAgent.indexOf('Iceweasel/2.') < 0) ||
+  		(navigator.userAgent.indexOf('SeaMonkey/') >= 0 &&
+  		navigator.userAgent.indexOf('SeaMonkey/1.') < 0) ||
+  		(navigator.userAgent.indexOf('Iceape/') >= 0 &&
+  		navigator.userAgent.indexOf('Iceape/1.') < 0),
+
+	/**
+	 * Variable: IS_SVG
+	 *
+	 * True if the browser supports SVG.
+	 */
+  	IS_SVG: navigator.userAgent.indexOf('Firefox/') >= 0 || // FF and Camino
+	  	navigator.userAgent.indexOf('Iceweasel/') >= 0 || // Firefox on Debian
+	  	navigator.userAgent.indexOf('Seamonkey/') >= 0 || // Firefox-based
+	  	navigator.userAgent.indexOf('Iceape/') >= 0 || // Seamonkey on Debian
+	  	navigator.userAgent.indexOf('Galeon/') >= 0 || // Gnome Browser (old)
+	  	navigator.userAgent.indexOf('Epiphany/') >= 0 || // Gnome Browser (new)
+	  	navigator.userAgent.indexOf('AppleWebKit/') >= 0 || // Safari/Google Chrome
+	  	navigator.userAgent.indexOf('Gecko/') >= 0 || // Netscape/Gecko
+	  	navigator.userAgent.indexOf('Opera/') >= 0 || // Opera
+	  	(document.documentMode != null && document.documentMode >= 9), // IE9+
+
+	/**
+	 * Variable: NO_FO
+	 *
+	 * True if foreignObject support is not available. This is the case for
+	 * Opera, older SVG-based browsers and all versions of IE.
+	 */
+  	NO_FO: !document.createElementNS || document.createElementNS('http://www.w3.org/2000/svg',
+  		'foreignObject') != '[object SVGForeignObjectElement]' || navigator.userAgent.indexOf('Opera/') >= 0,
+
+	/**
+	 * Variable: IS_VML
+	 *
+	 * True if the browser supports VML.
+	 */
+  	IS_VML: navigator.appName.toUpperCase() == 'MICROSOFT INTERNET EXPLORER',
+
+	/**
+	 * Variable: IS_WIN
+	 *
+	 * True if the client is a Windows.
+	 */
+  	IS_WIN: navigator.appVersion.indexOf('Win') > 0,
+
+	/**
+	 * Variable: IS_MAC
+	 *
+	 * True if the client is a Mac.
+	 */
+  	IS_MAC: navigator.appVersion.indexOf('Mac') > 0,
+
+	/**
+	 * Variable: IS_TOUCH
+	 * 
+	 * True if this device supports touchstart/-move/-end events (Apple iOS,
+	 * Android, Chromebook and Chrome Browser on touch-enabled devices).
+	 */
+  	IS_TOUCH: 'ontouchstart' in document.documentElement,
+
+	/**
+	 * Variable: IS_POINTER
+	 * 
+	 * True if this device supports Microsoft pointer events (always false on Macs).
+	 */
+  	IS_POINTER: window.PointerEvent != null && !(navigator.appVersion.indexOf('Mac') > 0),
+
+	/**
+	 * Variable: IS_LOCAL
+	 *
+	 * True if the documents location does not start with http:// or https://.
+	 */
+  	IS_LOCAL: document.location.href.indexOf('http://') < 0 &&
+  			  document.location.href.indexOf('https://') < 0,
+
+	/**
+	 * Function: isBrowserSupported
+	 *
+	 * Returns true if the current browser is supported, that is, if
+	 * <mxClient.IS_VML> or <mxClient.IS_SVG> is true.
+	 * 
+	 * Example:
+	 * 
+	 * (code)
+	 * if (!mxClient.isBrowserSupported())
+	 * {
+	 *   mxUtils.error('Browser is not supported!', 200, false);
+	 * }
+	 * (end)
+	 */
+	isBrowserSupported: function()
+	{
+		return mxClient.IS_VML || mxClient.IS_SVG;
+	},
+
+	/**
+	 * Function: link
+	 *
+	 * Adds a link node to the head of the document. Use this
+	 * to add a stylesheet to the page as follows:
+	 *
+	 * (code)
+	 * mxClient.link('stylesheet', filename);
+	 * (end)
+	 *
+	 * where filename is the (relative) URL of the stylesheet. The charset
+	 * is hardcoded to ISO-8859-1 and the type is text/css.
+	 * 
+	 * Parameters:
+	 * 
+	 * rel - String that represents the rel attribute of the link node.
+	 * href - String that represents the href attribute of the link node.
+	 * doc - Optional parent document of the link node.
+	 */
+	link: function(rel, href, doc)
+	{
+		doc = doc || document;
+
+		// Workaround for Operation Aborted in IE6 if base tag is used in head
+		if (mxClient.IS_IE6)
+		{
+			doc.write('<link rel="' + rel + '" href="' + href + '" charset="UTF-8" type="text/css"/>');
+		}
+		else
+		{	
+			var link = doc.createElement('link');
+			
+			link.setAttribute('rel', rel);
+			link.setAttribute('href', href);
+			link.setAttribute('charset', 'UTF-8');
+			link.setAttribute('type', 'text/css');
+			
+			var head = doc.getElementsByTagName('head')[0];
+	   		head.appendChild(link);
+		}
+	},
+	
+	/**
+	 * Function: include
+	 *
+	 * Dynamically adds a script node to the document header.
+	 * 
+	 * In production environments, the includes are resolved in the mxClient.js
+	 * file to reduce the number of requests required for client startup. This
+	 * function should only be used in development environments, but not in
+	 * production systems.
+	 */
+	include: function(src)
+	{
+		document.write('<script src="'+src+'"></script>');
+	},
+	
+	/**
+	 * Function: dispose
+	 * 
+	 * Frees up memory in IE by resolving cyclic dependencies between the DOM
+	 * and the JavaScript objects.
+	 */
+	dispose: function()
+	{
+		// Cleans all objects where listeners have been added
+		for (var i = 0; i < mxEvent.objects.length; i++)
+		{
+			if (mxEvent.objects[i].mxListenerList != null)
+			{
+				mxEvent.removeAllListeners(mxEvent.objects[i]);
+			}
+		}
+	}
+
+};
+
+/**
+ * Variable: mxLoadResources
+ * 
+ * Optional global config variable to toggle loading of the two resource files
+ * in <mxGraph> and <mxEditor>. Default is true. NOTE: This is a global variable,
+ * not a variable of mxClient.
+ *
+ * (code)
+ * <script type="text/javascript">
+ * 		var mxLoadResources = false;
+ * </script>
+ * <script type="text/javascript" src="/path/to/core/directory/js/mxClient.js"></script>
+ * (end)
+ */
+if (typeof(mxLoadResources) == 'undefined')
+{
+	mxLoadResources = true;
+}
+
+/**
+ * Variable: mxForceIncludes
+ * 
+ * Optional global config variable to force loading the JavaScript files in
+ * development mode. Default is undefined. NOTE: This is a global variable,
+ * not a variable of mxClient.
+ *
+ * (code)
+ * <script type="text/javascript">
+ * 		var mxLoadResources = true;
+ * </script>
+ * <script type="text/javascript" src="/path/to/core/directory/js/mxClient.js"></script>
+ * (end)
+ */
+if (typeof(mxForceIncludes) == 'undefined')
+{
+	mxForceIncludes = false;
+}
+
+/**
+ * Variable: mxResourceExtension
+ * 
+ * Optional global config variable to specify the extension of resource files.
+ * Default is true. NOTE: This is a global variable, not a variable of mxClient.
+ *
+ * (code)
+ * <script type="text/javascript">
+ * 		var mxResourceExtension = '.txt';
+ * </script>
+ * <script type="text/javascript" src="/path/to/core/directory/js/mxClient.js"></script>
+ * (end)
+ */
+if (typeof(mxResourceExtension) == 'undefined')
+{
+	mxResourceExtension = '.txt';
+}
+
+/**
+ * Variable: mxLoadStylesheets
+ * 
+ * Optional global config variable to toggle loading of the CSS files when
+ * the library is initialized. Default is true. NOTE: This is a global variable,
+ * not a variable of mxClient.
+ *
+ * (code)
+ * <script type="text/javascript">
+ * 		var mxLoadStylesheets = false;
+ * </script>
+ * <script type="text/javascript" src="/path/to/core/directory/js/mxClient.js"></script>
+ * (end)
+ */
+if (typeof(mxLoadStylesheets) == 'undefined')
+{
+	mxLoadStylesheets = true;
+}
+
+/**
+ * Variable: basePath
+ *
+ * Basepath for all URLs in the core without trailing slash. Default is '.'.
+ * Set mxBasePath prior to loading the mxClient library as follows to override
+ * this setting:
+ *
+ * (code)
+ * <script type="text/javascript">
+ * 		mxBasePath = '/path/to/core/directory';
+ * </script>
+ * <script type="text/javascript" src="/path/to/core/directory/js/mxClient.js"></script>
+ * (end)
+ * 
+ * When using a relative path, the path is relative to the URL of the page that
+ * contains the assignment. Trailing slashes are automatically removed.
+ */
+if (typeof(mxBasePath) != 'undefined' && mxBasePath.length > 0)
+{
+	// Adds a trailing slash if required
+	if (mxBasePath.substring(mxBasePath.length - 1) == '/')
+	{
+		mxBasePath = mxBasePath.substring(0, mxBasePath.length - 1);
+	}
+
+	mxClient.basePath = mxBasePath;
+}
+else
+{
+	mxClient.basePath = '.';
+}
+
+/**
+ * Variable: imageBasePath
+ *
+ * Basepath for all images URLs in the core without trailing slash. Default is
+ * <mxClient.basePath> + '/images'. Set mxImageBasePath prior to loading the
+ * mxClient library as follows to override this setting:
+ *
+ * (code)
+ * <script type="text/javascript">
+ * 		mxImageBasePath = '/path/to/image/directory';
+ * </script>
+ * <script type="text/javascript" src="/path/to/core/directory/js/mxClient.js"></script>
+ * (end)
+ * 
+ * When using a relative path, the path is relative to the URL of the page that
+ * contains the assignment. Trailing slashes are automatically removed.
+ */
+if (typeof(mxImageBasePath) != 'undefined' && mxImageBasePath.length > 0)
+{
+	// Adds a trailing slash if required
+	if (mxImageBasePath.substring(mxImageBasePath.length - 1) == '/')
+	{
+		mxImageBasePath = mxImageBasePath.substring(0, mxImageBasePath.length - 1);
+	}
+
+	mxClient.imageBasePath = mxImageBasePath;
+}
+else
+{
+	mxClient.imageBasePath = mxClient.basePath + '/images';	
+}
+
+/**
+ * Variable: language
+ *
+ * Defines the language of the client, eg. en for english, de for german etc.
+ * The special value 'none' will disable all built-in internationalization and
+ * resource loading. See <mxResources.getSpecialBundle> for handling identifiers
+ * with and without a dash.
+ * 
+ * Set mxLanguage prior to loading the mxClient library as follows to override
+ * this setting:
+ *
+ * (code)
+ * <script type="text/javascript">
+ * 		mxLanguage = 'en';
+ * </script>
+ * <script type="text/javascript" src="js/mxClient.js"></script>
+ * (end)
+ * 
+ * If internationalization is disabled, then the following variables should be
+ * overridden to reflect the current language of the system. These variables are
+ * cleared when i18n is disabled.
+ * <mxEditor.askZoomResource>, <mxEditor.lastSavedResource>,
+ * <mxEditor.currentFileResource>, <mxEditor.propertiesResource>,
+ * <mxEditor.tasksResource>, <mxEditor.helpResource>, <mxEditor.outlineResource>,
+ * <mxElbowEdgeHandler.doubleClickOrientationResource>, <mxUtils.errorResource>,
+ * <mxUtils.closeResource>, <mxGraphSelectionModel.doneResource>,
+ * <mxGraphSelectionModel.updatingSelectionResource>, <mxGraphView.doneResource>,
+ * <mxGraphView.updatingDocumentResource>, <mxCellRenderer.collapseExpandResource>,
+ * <mxGraph.containsValidationErrorsResource> and
+ * <mxGraph.alreadyConnectedResource>.
+ */
+if (typeof(mxLanguage) != 'undefined' && mxLanguage != null)
+{
+	mxClient.language = mxLanguage;
+}
+else
+{
+	mxClient.language = (mxClient.IS_IE) ? navigator.userLanguage : navigator.language;
+}
+
+/**
+ * Variable: defaultLanguage
+ * 
+ * Defines the default language which is used in the common resource files. Any
+ * resources for this language will only load the common resource file, but not
+ * the language-specific resource file. Default is 'en'.
+ * 
+ * Set mxDefaultLanguage prior to loading the mxClient library as follows to override
+ * this setting:
+ *
+ * (code)
+ * <script type="text/javascript">
+ * 		mxDefaultLanguage = 'de';
+ * </script>
+ * <script type="text/javascript" src="js/mxClient.js"></script>
+ * (end)
+ */
+if (typeof(mxDefaultLanguage) != 'undefined' && mxDefaultLanguage != null)
+{
+	mxClient.defaultLanguage = mxDefaultLanguage;
+}
+else
+{
+	mxClient.defaultLanguage = 'en';
+}
+
+// Adds all required stylesheets and namespaces
+if (mxLoadStylesheets)
+{
+	mxClient.link('stylesheet', mxClient.basePath + '/css/common.css');
+}
+
+/**
+ * Variable: languages
+ *
+ * Defines the optional array of all supported language extensions. The default
+ * language does not have to be part of this list. See
+ * <mxResources.isLanguageSupported>.
+ *
+ * (code)
+ * <script type="text/javascript">
+ * 		mxLanguages = ['de', 'it', 'fr'];
+ * </script>
+ * <script type="text/javascript" src="js/mxClient.js"></script>
+ * (end)
+ * 
+ * This is used to avoid unnecessary requests to language files, ie. if a 404
+ * will be returned.
+ */
+if (typeof(mxLanguages) != 'undefined' && mxLanguages != null)
+{
+	mxClient.languages = mxLanguages;
+}
+
+// Adds required namespaces, stylesheets and memory handling for older IE browsers
+if (mxClient.IS_VML)
+{
+	if (mxClient.IS_SVG)
+	{
+		mxClient.IS_VML = false;
+	}
+	else
+	{
+		// Enables support for IE8 standards mode. Note that this requires all attributes for VML
+		// elements to be set using direct notation, ie. node.attr = value. The use of setAttribute
+		// is not possible.
+		if (document.documentMode == 8)
+		{
+			document.namespaces.add(mxClient.VML_PREFIX, 'urn:schemas-microsoft-com:vml', '#default#VML');
+			document.namespaces.add(mxClient.OFFICE_PREFIX, 'urn:schemas-microsoft-com:office:office', '#default#VML');
+		}
+		else
+		{
+			document.namespaces.add(mxClient.VML_PREFIX, 'urn:schemas-microsoft-com:vml');
+			document.namespaces.add(mxClient.OFFICE_PREFIX, 'urn:schemas-microsoft-com:office:office');
+		}
+
+		// Workaround for limited number of stylesheets in IE (does not work in standards mode)
+		if (mxClient.IS_QUIRKS && document.styleSheets.length >= 30)
+		{
+			(function()
+			{
+				var node = document.createElement('style');
+				node.type = 'text/css';
+				node.styleSheet.cssText = mxClient.VML_PREFIX + '\\:*{behavior:url(#default#VML)}' +
+		        	mxClient.OFFICE_PREFIX + '\\:*{behavior:url(#default#VML)}';
+		        document.getElementsByTagName('head')[0].appendChild(node);
+			})();
+		}
+		else
+		{
+			document.createStyleSheet().cssText = mxClient.VML_PREFIX + '\\:*{behavior:url(#default#VML)}' +
+		    	mxClient.OFFICE_PREFIX + '\\:*{behavior:url(#default#VML)}';
+		}
+	    
+	    if (mxLoadStylesheets)
+	    {
+	    	mxClient.link('stylesheet', mxClient.basePath + '/css/explorer.css');
+	    }
+	
+		// Cleans up resources when the application terminates
+		window.attachEvent('onunload', mxClient.dispose);
+	}
+}
+
+// PREPROCESSOR-REMOVE-START
+// If script is loaded via CommonJS, do not write <script> tags to the page
+// for dependencies. These are already included in the build.
+if (mxForceIncludes || !(typeof module === 'object' && module.exports != null))
+{
+// PREPROCESSOR-REMOVE-END
+	mxClient.include(mxClient.basePath+'/js/util/mxLog.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxObjectIdentity.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxDictionary.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxResources.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxPoint.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxRectangle.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxEffects.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxUtils.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxConstants.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxEventObject.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxMouseEvent.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxEventSource.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxEvent.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxXmlRequest.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxClipboard.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxWindow.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxForm.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxImage.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxDivResizer.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxDragSource.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxToolbar.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxUndoableEdit.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxUndoManager.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxUrlConverter.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxPanningManager.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxPopupMenu.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxAutoSaveManager.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxAnimation.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxMorphing.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxImageBundle.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxImageExport.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxAbstractCanvas2D.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxXmlCanvas2D.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxSvgCanvas2D.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxVmlCanvas2D.js');
+	mxClient.include(mxClient.basePath+'/js/util/mxGuide.js');
+	mxClient.include(mxClient.basePath+'/js/shape/mxStencil.js');
+	mxClient.include(mxClient.basePath+'/js/shape/mxShape.js');
+	mxClient.include(mxClient.basePath+'/js/shape/mxStencilRegistry.js');
+	mxClient.include(mxClient.basePath+'/js/shape/mxMarker.js');
+	mxClient.include(mxClient.basePath+'/js/shape/mxActor.js');
+	mxClient.include(mxClient.basePath+'/js/shape/mxCloud.js');
+	mxClient.include(mxClient.basePath+'/js/shape/mxRectangleShape.js');
+	mxClient.include(mxClient.basePath+'/js/shape/mxEllipse.js');
+	mxClient.include(mxClient.basePath+'/js/shape/mxDoubleEllipse.js');
+	mxClient.include(mxClient.basePath+'/js/shape/mxRhombus.js');
+	mxClient.include(mxClient.basePath+'/js/shape/mxPolyline.js');
+	mxClient.include(mxClient.basePath+'/js/shape/mxArrow.js');
+	mxClient.include(mxClient.basePath+'/js/shape/mxArrowConnector.js');
+	mxClient.include(mxClient.basePath+'/js/shape/mxText.js');
+	mxClient.include(mxClient.basePath+'/js/shape/mxTriangle.js');
+	mxClient.include(mxClient.basePath+'/js/shape/mxHexagon.js');
+	mxClient.include(mxClient.basePath+'/js/shape/mxLine.js');
+	mxClient.include(mxClient.basePath+'/js/shape/mxImageShape.js');
+	mxClient.include(mxClient.basePath+'/js/shape/mxLabel.js');
+	mxClient.include(mxClient.basePath+'/js/shape/mxCylinder.js');
+	mxClient.include(mxClient.basePath+'/js/shape/mxConnector.js');
+	mxClient.include(mxClient.basePath+'/js/shape/mxSwimlane.js');
+	mxClient.include(mxClient.basePath+'/js/layout/mxGraphLayout.js');
+	mxClient.include(mxClient.basePath+'/js/layout/mxStackLayout.js');
+	mxClient.include(mxClient.basePath+'/js/layout/mxPartitionLayout.js');
+	mxClient.include(mxClient.basePath+'/js/layout/mxCompactTreeLayout.js');
+	mxClient.include(mxClient.basePath+'/js/layout/mxRadialTreeLayout.js');
+	mxClient.include(mxClient.basePath+'/js/layout/mxFastOrganicLayout.js');
+	mxClient.include(mxClient.basePath+'/js/layout/mxCircleLayout.js');
+	mxClient.include(mxClient.basePath+'/js/layout/mxParallelEdgeLayout.js');
+	mxClient.include(mxClient.basePath+'/js/layout/mxCompositeLayout.js');
+	mxClient.include(mxClient.basePath+'/js/layout/mxEdgeLabelLayout.js');
+	mxClient.include(mxClient.basePath+'/js/layout/hierarchical/model/mxGraphAbstractHierarchyCell.js');
+	mxClient.include(mxClient.basePath+'/js/layout/hierarchical/model/mxGraphHierarchyNode.js');
+	mxClient.include(mxClient.basePath+'/js/layout/hierarchical/model/mxGraphHierarchyEdge.js');
+	mxClient.include(mxClient.basePath+'/js/layout/hierarchical/model/mxGraphHierarchyModel.js');
+	mxClient.include(mxClient.basePath+'/js/layout/hierarchical/model/mxSwimlaneModel.js');
+	mxClient.include(mxClient.basePath+'/js/layout/hierarchical/stage/mxHierarchicalLayoutStage.js');
+	mxClient.include(mxClient.basePath+'/js/layout/hierarchical/stage/mxMedianHybridCrossingReduction.js');
+	mxClient.include(mxClient.basePath+'/js/layout/hierarchical/stage/mxMinimumCycleRemover.js');
+	mxClient.include(mxClient.basePath+'/js/layout/hierarchical/stage/mxCoordinateAssignment.js');
+	mxClient.include(mxClient.basePath+'/js/layout/hierarchical/stage/mxSwimlaneOrdering.js');
+	mxClient.include(mxClient.basePath+'/js/layout/hierarchical/mxHierarchicalLayout.js');
+	mxClient.include(mxClient.basePath+'/js/layout/hierarchical/mxSwimlaneLayout.js');
+	mxClient.include(mxClient.basePath+'/js/model/mxGraphModel.js');
+	mxClient.include(mxClient.basePath+'/js/model/mxCell.js');
+	mxClient.include(mxClient.basePath+'/js/model/mxGeometry.js');
+	mxClient.include(mxClient.basePath+'/js/model/mxCellPath.js');
+	mxClient.include(mxClient.basePath+'/js/view/mxPerimeter.js');
+	mxClient.include(mxClient.basePath+'/js/view/mxPrintPreview.js');
+	mxClient.include(mxClient.basePath+'/js/view/mxStylesheet.js');
+	mxClient.include(mxClient.basePath+'/js/view/mxCellState.js');
+	mxClient.include(mxClient.basePath+'/js/view/mxGraphSelectionModel.js');
+	mxClient.include(mxClient.basePath+'/js/view/mxCellEditor.js');
+	mxClient.include(mxClient.basePath+'/js/view/mxCellRenderer.js');
+	mxClient.include(mxClient.basePath+'/js/view/mxEdgeStyle.js');
+	mxClient.include(mxClient.basePath+'/js/view/mxStyleRegistry.js');
+	mxClient.include(mxClient.basePath+'/js/view/mxGraphView.js');
+	mxClient.include(mxClient.basePath+'/js/view/mxGraph.js');
+	mxClient.include(mxClient.basePath+'/js/view/mxCellOverlay.js');
+	mxClient.include(mxClient.basePath+'/js/view/mxOutline.js');
+	mxClient.include(mxClient.basePath+'/js/view/mxMultiplicity.js');
+	mxClient.include(mxClient.basePath+'/js/view/mxLayoutManager.js');
+	mxClient.include(mxClient.basePath+'/js/view/mxSwimlaneManager.js');
+	mxClient.include(mxClient.basePath+'/js/view/mxTemporaryCellStates.js');
+	mxClient.include(mxClient.basePath+'/js/view/mxCellStatePreview.js');
+	mxClient.include(mxClient.basePath+'/js/view/mxConnectionConstraint.js');
+	mxClient.include(mxClient.basePath+'/js/handler/mxGraphHandler.js');
+	mxClient.include(mxClient.basePath+'/js/handler/mxPanningHandler.js');
+	mxClient.include(mxClient.basePath+'/js/handler/mxPopupMenuHandler.js');
+	mxClient.include(mxClient.basePath+'/js/handler/mxCellMarker.js');
+	mxClient.include(mxClient.basePath+'/js/handler/mxSelectionCellsHandler.js');
+	mxClient.include(mxClient.basePath+'/js/handler/mxConnectionHandler.js');
+	mxClient.include(mxClient.basePath+'/js/handler/mxConstraintHandler.js');
+	mxClient.include(mxClient.basePath+'/js/handler/mxRubberband.js');
+	mxClient.include(mxClient.basePath+'/js/handler/mxHandle.js');
+	mxClient.include(mxClient.basePath+'/js/handler/mxVertexHandler.js');
+	mxClient.include(mxClient.basePath+'/js/handler/mxEdgeHandler.js');
+	mxClient.include(mxClient.basePath+'/js/handler/mxElbowEdgeHandler.js');
+	mxClient.include(mxClient.basePath+'/js/handler/mxEdgeSegmentHandler.js');
+	mxClient.include(mxClient.basePath+'/js/handler/mxKeyHandler.js');
+	mxClient.include(mxClient.basePath+'/js/handler/mxTooltipHandler.js');
+	mxClient.include(mxClient.basePath+'/js/handler/mxCellTracker.js');
+	mxClient.include(mxClient.basePath+'/js/handler/mxCellHighlight.js');
+	mxClient.include(mxClient.basePath+'/js/editor/mxDefaultKeyHandler.js');
+	mxClient.include(mxClient.basePath+'/js/editor/mxDefaultPopupMenu.js');
+	mxClient.include(mxClient.basePath+'/js/editor/mxDefaultToolbar.js');
+	mxClient.include(mxClient.basePath+'/js/editor/mxEditor.js');
+	mxClient.include(mxClient.basePath+'/js/io/mxCodecRegistry.js');
+	mxClient.include(mxClient.basePath+'/js/io/mxCodec.js');
+	mxClient.include(mxClient.basePath+'/js/io/mxObjectCodec.js');
+	mxClient.include(mxClient.basePath+'/js/io/mxCellCodec.js');
+	mxClient.include(mxClient.basePath+'/js/io/mxModelCodec.js');
+	mxClient.include(mxClient.basePath+'/js/io/mxRootChangeCodec.js');
+	mxClient.include(mxClient.basePath+'/js/io/mxChildChangeCodec.js');
+	mxClient.include(mxClient.basePath+'/js/io/mxTerminalChangeCodec.js');
+	mxClient.include(mxClient.basePath+'/js/io/mxGenericChangeCodec.js');
+	mxClient.include(mxClient.basePath+'/js/io/mxGraphCodec.js');
+	mxClient.include(mxClient.basePath+'/js/io/mxGraphViewCodec.js');
+	mxClient.include(mxClient.basePath+'/js/io/mxStylesheetCodec.js');
+	mxClient.include(mxClient.basePath+'/js/io/mxDefaultKeyHandlerCodec.js');
+	mxClient.include(mxClient.basePath+'/js/io/mxDefaultToolbarCodec.js');
+	mxClient.include(mxClient.basePath+'/js/io/mxDefaultPopupMenuCodec.js');
+	mxClient.include(mxClient.basePath+'/js/io/mxEditorCodec.js');
+// PREPROCESSOR-REMOVE-START
+}
+// PREPROCESSOR-REMOVE-END
diff --git a/airavata-kubernetes/workflow-composer/src/js/shape/mxActor.js b/airavata-kubernetes/workflow-composer/src/js/shape/mxActor.js
new file mode 100644
index 0000000..19a8080
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/shape/mxActor.js
@@ -0,0 +1,86 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxActor
+ *
+ * Extends <mxShape> to implement an actor shape. If a custom shape with one
+ * filled area is needed, then this shape's <redrawPath> should be overridden.
+ * 
+ * Example:
+ * 
+ * (code)
+ * function SampleShape() { }
+ * 
+ * SampleShape.prototype = new mxActor();
+ * SampleShape.prototype.constructor = vsAseShape;
+ * 
+ * mxCellRenderer.prototype.defaultShapes['sample'] = SampleShape;
+ * SampleShape.prototype.redrawPath = function(path, x, y, w, h)
+ * {
+ *   path.moveTo(0, 0);
+ *   path.lineTo(w, h);
+ *   // ...
+ *   path.close();
+ * }
+ * (end)
+ * 
+ * This shape is registered under <mxConstants.SHAPE_ACTOR> in
+ * <mxCellRenderer>.
+ * 
+ * Constructor: mxActor
+ *
+ * Constructs a new actor shape.
+ * 
+ * Parameters:
+ * 
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxActor(bounds, fill, stroke, strokewidth)
+{
+	mxShape.call(this);
+	this.bounds = bounds;
+	this.fill = fill;
+	this.stroke = stroke;
+	this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxUtils.extend(mxActor, mxShape);
+
+/**
+ * Function: paintVertexShape
+ * 
+ * Redirects to redrawPath for subclasses to work.
+ */
+mxActor.prototype.paintVertexShape = function(c, x, y, w, h)
+{
+	c.translate(x, y);
+	c.begin();
+	this.redrawPath(c, x, y, w, h);
+	c.fillAndStroke();
+};
+
+/**
+ * Function: redrawPath
+ *
+ * Draws the path for this shape.
+ */
+mxActor.prototype.redrawPath = function(c, x, y, w, h)
+{
+	var width = w/3;
+	c.moveTo(0, h);
+	c.curveTo(0, 3 * h / 5, 0, 2 * h / 5, w / 2, 2 * h / 5);
+	c.curveTo(w / 2 - width, 2 * h / 5, w / 2 - width, 0, w / 2, 0);
+	c.curveTo(w / 2 + width, 0, w / 2 + width, 2 * h / 5, w / 2, 2 * h / 5);
+	c.curveTo(w, 2 * h / 5, w, 3 * h / 5, w, h);
+	c.close();
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/shape/mxArrow.js b/airavata-kubernetes/workflow-composer/src/js/shape/mxArrow.js
new file mode 100644
index 0000000..c5c4402
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/shape/mxArrow.js
@@ -0,0 +1,115 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxArrow
+ *
+ * Extends <mxShape> to implement an arrow shape. (The shape
+ * is used to represent edges, not vertices.)
+ * This shape is registered under <mxConstants.SHAPE_ARROW>
+ * in <mxCellRenderer>.
+ * 
+ * Constructor: mxArrow
+ *
+ * Constructs a new arrow shape.
+ * 
+ * Parameters:
+ * 
+ * points - Array of <mxPoints> that define the points. This is stored in
+ * <mxShape.points>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ * arrowWidth - Optional integer that defines the arrow width. Default is
+ * <mxConstants.ARROW_WIDTH>. This is stored in <arrowWidth>.
+ * spacing - Optional integer that defines the spacing between the arrow shape
+ * and its endpoints. Default is <mxConstants.ARROW_SPACING>. This is stored in
+ * <spacing>.
+ * endSize - Optional integer that defines the size of the arrowhead. Default
+ * is <mxConstants.ARROW_SIZE>. This is stored in <endSize>.
+ */
+function mxArrow(points, fill, stroke, strokewidth, arrowWidth, spacing, endSize)
+{
+	mxShape.call(this);
+	this.points = points;
+	this.fill = fill;
+	this.stroke = stroke;
+	this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+	this.arrowWidth = (arrowWidth != null) ? arrowWidth : mxConstants.ARROW_WIDTH;
+	this.spacing = (spacing != null) ? spacing : mxConstants.ARROW_SPACING;
+	this.endSize = (endSize != null) ? endSize : mxConstants.ARROW_SIZE;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxUtils.extend(mxArrow, mxShape);
+
+/**
+ * Function: augmentBoundingBox
+ *
+ * Augments the bounding box with the edge width and markers.
+ */
+mxArrow.prototype.augmentBoundingBox = function(bbox)
+{
+	mxShape.prototype.augmentBoundingBox.apply(this, arguments);
+	
+	var w = Math.max(this.arrowWidth, this.endSize);
+	bbox.grow((w / 2 + this.strokewidth) * this.scale);
+};
+
+/**
+ * Function: paintEdgeShape
+ * 
+ * Paints the line shape.
+ */
+mxArrow.prototype.paintEdgeShape = function(c, pts)
+{
+	// Geometry of arrow
+	var spacing =  mxConstants.ARROW_SPACING;
+	var width = mxConstants.ARROW_WIDTH;
+	var arrow = mxConstants.ARROW_SIZE;
+
+	// Base vector (between end points)
+	var p0 = pts[0];
+	var pe = pts[pts.length - 1];
+	var dx = pe.x - p0.x;
+	var dy = pe.y - p0.y;
+	var dist = Math.sqrt(dx * dx + dy * dy);
+	var length = dist - 2 * spacing - arrow;
+	
+	// Computes the norm and the inverse norm
+	var nx = dx / dist;
+	var ny = dy / dist;
+	var basex = length * nx;
+	var basey = length * ny;
+	var floorx = width * ny/3;
+	var floory = -width * nx/3;
+	
+	// Computes points
+	var p0x = p0.x - floorx / 2 + spacing * nx;
+	var p0y = p0.y - floory / 2 + spacing * ny;
+	var p1x = p0x + floorx;
+	var p1y = p0y + floory;
+	var p2x = p1x + basex;
+	var p2y = p1y + basey;
+	var p3x = p2x + floorx;
+	var p3y = p2y + floory;
+	// p4 not necessary
+	var p5x = p3x - 3 * floorx;
+	var p5y = p3y - 3 * floory;
+	
+	c.begin();
+	c.moveTo(p0x, p0y);
+	c.lineTo(p1x, p1y);
+	c.lineTo(p2x, p2y);
+	c.lineTo(p3x, p3y);
+	c.lineTo(pe.x - spacing * nx, pe.y - spacing * ny);
+	c.lineTo(p5x, p5y);
+	c.lineTo(p5x + floorx, p5y + floory);
+	c.close();
+
+	c.fillAndStroke();
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/shape/mxArrowConnector.js b/airavata-kubernetes/workflow-composer/src/js/shape/mxArrowConnector.js
new file mode 100644
index 0000000..2abae89
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/shape/mxArrowConnector.js
@@ -0,0 +1,485 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxArrowConnector
+ *
+ * Extends <mxShape> to implement an new rounded arrow shape with support for
+ * waypoints and double arrows. (The shape is used to represent edges, not
+ * vertices.) This shape is registered under <mxConstants.SHAPE_ARROW_CONNECTOR>
+ * in <mxCellRenderer>.
+ * 
+ * Constructor: mxArrowConnector
+ *
+ * Constructs a new arrow shape.
+ * 
+ * Parameters:
+ * 
+ * points - Array of <mxPoints> that define the points. This is stored in
+ * <mxShape.points>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ * arrowWidth - Optional integer that defines the arrow width. Default is
+ * <mxConstants.ARROW_WIDTH>. This is stored in <arrowWidth>.
+ * spacing - Optional integer that defines the spacing between the arrow shape
+ * and its endpoints. Default is <mxConstants.ARROW_SPACING>. This is stored in
+ * <spacing>.
+ * endSize - Optional integer that defines the size of the arrowhead. Default
+ * is <mxConstants.ARROW_SIZE>. This is stored in <endSize>.
+ */
+function mxArrowConnector(points, fill, stroke, strokewidth, arrowWidth, spacing, endSize)
+{
+	mxShape.call(this);
+	this.points = points;
+	this.fill = fill;
+	this.stroke = stroke;
+	this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+	this.arrowWidth = (arrowWidth != null) ? arrowWidth : mxConstants.ARROW_WIDTH;
+	this.arrowSpacing = (spacing != null) ? spacing : mxConstants.ARROW_SPACING;
+	this.startSize = mxConstants.ARROW_SIZE / 5;
+	this.endSize = mxConstants.ARROW_SIZE / 5;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxUtils.extend(mxArrowConnector, mxShape);
+
+/**
+ * Variable: useSvgBoundingBox
+ * 
+ * Allows to use the SVG bounding box in SVG. Default is false for performance
+ * reasons.
+ */
+mxArrowConnector.prototype.useSvgBoundingBox = true;
+
+/**
+ * Variable: resetStyles
+ * 
+ * Overrides mxShape to reset spacing.
+ */
+mxArrowConnector.prototype.resetStyles = function()
+{
+	mxShape.prototype.resetStyles.apply(this, arguments);
+	
+	this.arrowSpacing = mxConstants.ARROW_SPACING;
+};
+
+/**
+ * Overrides apply to get smooth transition from default start- and endsize.
+ */
+mxArrowConnector.prototype.apply = function(state)
+{
+	mxShape.prototype.apply.apply(this, arguments);
+
+	if (this.style != null)
+	{
+		this.startSize = mxUtils.getNumber(this.style, mxConstants.STYLE_STARTSIZE, mxConstants.ARROW_SIZE / 5) * 3;
+		this.endSize = mxUtils.getNumber(this.style, mxConstants.STYLE_ENDSIZE, mxConstants.ARROW_SIZE / 5) * 3;
+	}
+};
+
+/**
+ * Function: augmentBoundingBox
+ *
+ * Augments the bounding box with the edge width and markers.
+ */
+mxArrowConnector.prototype.augmentBoundingBox = function(bbox)
+{
+	mxShape.prototype.augmentBoundingBox.apply(this, arguments);
+	
+	var w = this.getEdgeWidth();
+	
+	if (this.isMarkerStart())
+	{
+		w = Math.max(w, this.getStartArrowWidth());
+	}
+	
+	if (this.isMarkerEnd())
+	{
+		w = Math.max(w, this.getEndArrowWidth());
+	}
+	
+	bbox.grow((w / 2 + this.strokewidth) * this.scale);
+};
+
+/**
+ * Function: paintEdgeShape
+ * 
+ * Paints the line shape.
+ */
+mxArrowConnector.prototype.paintEdgeShape = function(c, pts)
+{
+	// Geometry of arrow
+	var strokeWidth = this.strokewidth;
+	
+	if (this.outline)
+	{
+		strokeWidth = Math.max(1, mxUtils.getNumber(this.style, mxConstants.STYLE_STROKEWIDTH, this.strokewidth));
+	}
+	
+	var startWidth = this.getStartArrowWidth() + strokeWidth;
+	var endWidth = this.getEndArrowWidth() + strokeWidth;
+	var edgeWidth = this.outline ? this.getEdgeWidth() + strokeWidth : this.getEdgeWidth();
+	var openEnded = this.isOpenEnded();
+	var markerStart = this.isMarkerStart();
+	var markerEnd = this.isMarkerEnd();
+	var spacing = (openEnded) ? 0 : this.arrowSpacing + strokeWidth / 2;
+	var startSize = this.startSize + strokeWidth;
+	var endSize = this.endSize + strokeWidth;
+	var isRounded = this.isArrowRounded();
+	
+	// Base vector (between first points)
+	var pe = pts[pts.length - 1];
+
+	// Finds first non-overlapping point
+	var i0 = 1;
+	
+	while (i0 < pts.length - 1 && pts[i0].x == pts[0].x && pts[i0].y == pts[0].y)
+	{
+		i0++;
+	}
+	
+	var dx = pts[i0].x - pts[0].x;
+	var dy = pts[i0].y - pts[0].y;
+	var dist = Math.sqrt(dx * dx + dy * dy);
+	
+	if (dist == 0)
+	{
+		return;
+	}
+	
+	// Computes the norm and the inverse norm
+	var nx = dx / dist;
+	var nx2, nx1 = nx;
+	var ny = dy / dist;
+	var ny2, ny1 = ny;
+	var orthx = edgeWidth * ny;
+	var orthy = -edgeWidth * nx;
+	
+	// Stores the inbound function calls in reverse order in fns
+	var fns = [];
+	
+	if (isRounded)
+	{
+		c.setLineJoin('round');
+	}
+	else if (pts.length > 2)
+	{
+		// Only mitre if there are waypoints
+		c.setMiterLimit(1.42);
+	}
+
+	c.begin();
+
+	var startNx = nx;
+	var startNy = ny;
+
+	if (markerStart && !openEnded)
+	{
+		this.paintMarker(c, pts[0].x, pts[0].y, nx, ny, startSize, startWidth, edgeWidth, spacing, true);
+	}
+	else
+	{
+		var outStartX = pts[0].x + orthx / 2 + spacing * nx;
+		var outStartY = pts[0].y + orthy / 2 + spacing * ny;
+		var inEndX = pts[0].x - orthx / 2 + spacing * nx;
+		var inEndY = pts[0].y - orthy / 2 + spacing * ny;
+		
+		if (openEnded)
+		{
+			c.moveTo(outStartX, outStartY);
+			
+			fns.push(function()
+			{
+				c.lineTo(inEndX, inEndY);
+			});
+		}
+		else
+		{
+			c.moveTo(inEndX, inEndY);
+			c.lineTo(outStartX, outStartY);
+		}
+	}
+	
+	var dx1 = 0;
+	var dy1 = 0;
+	var dist1 = 0;
+
+	for (var i = 0; i < pts.length - 2; i++)
+	{
+		// Work out in which direction the line is bending
+		var pos = mxUtils.relativeCcw(pts[i].x, pts[i].y, pts[i+1].x, pts[i+1].y, pts[i+2].x, pts[i+2].y);
+
+		dx1 = pts[i+2].x - pts[i+1].x;
+		dy1 = pts[i+2].y - pts[i+1].y;
+
+		dist1 = Math.sqrt(dx1 * dx1 + dy1 * dy1);
+		
+		if (dist1 != 0)
+		{
+			nx1 = dx1 / dist1;
+			ny1 = dy1 / dist1;
+			
+			var tmp1 = nx * nx1 + ny * ny1;
+			tmp = Math.max(Math.sqrt((tmp1 + 1) / 2), 0.04);
+			
+			// Work out the normal orthogonal to the line through the control point and the edge sides intersection
+			nx2 = (nx + nx1);
+			ny2 = (ny + ny1);
+	
+			var dist2 = Math.sqrt(nx2 * nx2 + ny2 * ny2);
+			
+			if (dist2 != 0)
+			{
+				nx2 = nx2 / dist2;
+				ny2 = ny2 / dist2;
+				
+				// Higher strokewidths require a larger minimum bend, 0.35 covers all but the most extreme cases
+				var strokeWidthFactor = Math.max(tmp, Math.min(this.strokewidth / 200 + 0.04, 0.35));
+				var angleFactor = (pos != 0 && isRounded) ? Math.max(0.1, strokeWidthFactor) : Math.max(tmp, 0.06);
+
+				var outX = pts[i+1].x + ny2 * edgeWidth / 2 / angleFactor;
+				var outY = pts[i+1].y - nx2 * edgeWidth / 2 / angleFactor;
+				var inX = pts[i+1].x - ny2 * edgeWidth / 2 / angleFactor;
+				var inY = pts[i+1].y + nx2 * edgeWidth / 2 / angleFactor;
+				
+				if (pos == 0 || !isRounded)
+				{
+					// If the two segments are aligned, or if we're not drawing curved sections between segments
+					// just draw straight to the intersection point
+					c.lineTo(outX, outY);
+					
+					(function(x, y)
+					{
+						fns.push(function()
+						{
+							c.lineTo(x, y);
+						});
+					})(inX, inY);
+				}
+				else if (pos == -1)
+				{
+					var c1x = inX + ny * edgeWidth;
+					var c1y = inY - nx * edgeWidth;
+					var c2x = inX + ny1 * edgeWidth;
+					var c2y = inY - nx1 * edgeWidth;
+					c.lineTo(c1x, c1y);
+					c.quadTo(outX, outY, c2x, c2y);
+					
+					(function(x, y)
+					{
+						fns.push(function()
+						{
+							c.lineTo(x, y);
+						});
+					})(inX, inY);
+				}
+				else
+				{
+					c.lineTo(outX, outY);
+					
+					(function(x, y)
+					{
+						var c1x = outX - ny * edgeWidth;
+						var c1y = outY + nx * edgeWidth;
+						var c2x = outX - ny1 * edgeWidth;
+						var c2y = outY + nx1 * edgeWidth;
+						
+						fns.push(function()
+						{
+							c.quadTo(x, y, c1x, c1y);
+						});
+						fns.push(function()
+						{
+							c.lineTo(c2x, c2y);
+						});
+					})(inX, inY);
+				}
+				
+				nx = nx1;
+				ny = ny1;
+			}
+		}
+	}
+	
+	orthx = edgeWidth * ny1;
+	orthy = - edgeWidth * nx1;
+
+	if (markerEnd && !openEnded)
+	{
+		this.paintMarker(c, pe.x, pe.y, -nx, -ny, endSize, endWidth, edgeWidth, spacing, false);
+	}
+	else
+	{
+		c.lineTo(pe.x - spacing * nx1 + orthx / 2, pe.y - spacing * ny1 + orthy / 2);
+		
+		var inStartX = pe.x - spacing * nx1 - orthx / 2;
+		var inStartY = pe.y - spacing * ny1 - orthy / 2;
+
+		if (!openEnded)
+		{
+			c.lineTo(inStartX, inStartY);
+		}
+		else
+		{
+			c.moveTo(inStartX, inStartY);
+			
+			fns.splice(0, 0, function()
+			{
+				c.moveTo(inStartX, inStartY);
+			});
+		}
+	}
+	
+	for (var i = fns.length - 1; i >= 0; i--)
+	{
+		fns[i]();
+	}
+
+	if (openEnded)
+	{
+		c.end();
+		c.stroke();
+	}
+	else
+	{
+		c.close();
+		c.fillAndStroke();
+	}
+	
+	// Workaround for shadow on top of base arrow
+	c.setShadow(false);
+	
+	// Need to redraw the markers without the low miter limit
+	c.setMiterLimit(4);
+	
+	if (isRounded)
+	{
+		c.setLineJoin('flat');
+	}
+
+	if (pts.length > 2)
+	{
+		// Only to repaint markers if no waypoints
+		// Need to redraw the markers without the low miter limit
+		c.setMiterLimit(4);
+		if (markerStart && !openEnded)
+		{
+			c.begin();
+			this.paintMarker(c, pts[0].x, pts[0].y, startNx, startNy, startSize, startWidth, edgeWidth, spacing, true);
+			c.stroke();
+			c.end();
+		}
+		
+		if (markerEnd && !openEnded)
+		{
+			c.begin();
+			this.paintMarker(c, pe.x, pe.y, -nx, -ny, endSize, endWidth, edgeWidth, spacing, true);
+			c.stroke();
+			c.end();
+		}
+	}
+};
+
+/**
+ * Function: paintEdgeShape
+ * 
+ * Paints the line shape.
+ */
+mxArrowConnector.prototype.paintMarker = function(c, ptX, ptY, nx, ny, size, arrowWidth, edgeWidth, spacing, initialMove)
+{
+	var widthArrowRatio = edgeWidth / arrowWidth;
+	var orthx = edgeWidth * ny / 2;
+	var orthy = -edgeWidth * nx / 2;
+
+	var spaceX = (spacing + size) * nx;
+	var spaceY = (spacing + size) * ny;
+
+	if (initialMove)
+	{
+		c.moveTo(ptX - orthx + spaceX, ptY - orthy + spaceY);
+	}
+	else
+	{
+		c.lineTo(ptX - orthx + spaceX, ptY - orthy + spaceY);
+	}
+
+	c.lineTo(ptX - orthx / widthArrowRatio + spaceX, ptY - orthy / widthArrowRatio + spaceY);
+	c.lineTo(ptX + spacing * nx, ptY + spacing * ny);
+	c.lineTo(ptX + orthx / widthArrowRatio + spaceX, ptY + orthy / widthArrowRatio + spaceY);
+	c.lineTo(ptX + orthx + spaceX, ptY + orthy + spaceY);
+}
+
+/**
+ * Function: isArrowRounded
+ * 
+ * Returns wether the arrow is rounded
+ */
+mxArrowConnector.prototype.isArrowRounded = function()
+{
+	return this.isRounded;
+};
+
+/**
+ * Function: getStartArrowWidth
+ * 
+ * Returns the width of the start arrow
+ */
+mxArrowConnector.prototype.getStartArrowWidth = function()
+{
+	return mxConstants.ARROW_WIDTH;
+};
+
+/**
+ * Function: getEndArrowWidth
+ * 
+ * Returns the width of the end arrow
+ */
+mxArrowConnector.prototype.getEndArrowWidth = function()
+{
+	return mxConstants.ARROW_WIDTH;
+};
+
+/**
+ * Function: getEdgeWidth
+ * 
+ * Returns the width of the body of the edge
+ */
+mxArrowConnector.prototype.getEdgeWidth = function()
+{
+	return mxConstants.ARROW_WIDTH / 3;
+};
+
+/**
+ * Function: isOpenEnded
+ * 
+ * Returns whether the ends of the shape are drawn
+ */
+mxArrowConnector.prototype.isOpenEnded = function()
+{
+	return false;
+};
+
+/**
+ * Function: isMarkerStart
+ * 
+ * Returns whether the start marker is drawn
+ */
+mxArrowConnector.prototype.isMarkerStart = function()
+{
+	return (mxUtils.getValue(this.style, mxConstants.STYLE_STARTARROW, mxConstants.NONE) != mxConstants.NONE);
+};
+
+/**
+ * Function: isMarkerEnd
+ * 
+ * Returns whether the end marker is drawn
+ */
+mxArrowConnector.prototype.isMarkerEnd = function()
+{
+	return (mxUtils.getValue(this.style, mxConstants.STYLE_ENDARROW, mxConstants.NONE) != mxConstants.NONE);
+};
\ No newline at end of file
diff --git a/airavata-kubernetes/workflow-composer/src/js/shape/mxCloud.js b/airavata-kubernetes/workflow-composer/src/js/shape/mxCloud.js
new file mode 100644
index 0000000..fb1f931
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/shape/mxCloud.js
@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxCloud
+ *
+ * Extends <mxActor> to implement a cloud shape.
+ * 
+ * This shape is registered under <mxConstants.SHAPE_CLOUD> in
+ * <mxCellRenderer>.
+ * 
+ * Constructor: mxCloud
+ *
+ * Constructs a new cloud shape.
+ * 
+ * Parameters:
+ * 
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxCloud(bounds, fill, stroke, strokewidth)
+{
+	mxActor.call(this);
+	this.bounds = bounds;
+	this.fill = fill;
+	this.stroke = stroke;
+	this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxActor.
+ */
+mxUtils.extend(mxCloud, mxActor);
+
+/**
+ * Function: redrawPath
+ *
+ * Draws the path for this shape.
+ */
+mxCloud.prototype.redrawPath = function(c, x, y, w, h)
+{
+	c.moveTo(0.25 * w, 0.25 * h);
+	c.curveTo(0.05 * w, 0.25 * h, 0, 0.5 * h, 0.16 * w, 0.55 * h);
+	c.curveTo(0, 0.66 * h, 0.18 * w, 0.9 * h, 0.31 * w, 0.8 * h);
+	c.curveTo(0.4 * w, h, 0.7 * w, h, 0.8 * w, 0.8 * h);
+	c.curveTo(w, 0.8 * h, w, 0.6 * h, 0.875 * w, 0.5 * h);
+	c.curveTo(w, 0.3 * h, 0.8 * w, 0.1 * h, 0.625 * w, 0.2 * h);
+	c.curveTo(0.5 * w, 0.05 * h, 0.3 * w, 0.05 * h, 0.25 * w, 0.25 * h);
+	c.close();
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/shape/mxConnector.js b/airavata-kubernetes/workflow-composer/src/js/shape/mxConnector.js
new file mode 100644
index 0000000..42968a0
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/shape/mxConnector.js
@@ -0,0 +1,149 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxConnector
+ * 
+ * Extends <mxShape> to implement a connector shape. The connector
+ * shape allows for arrow heads on either side.
+ * 
+ * This shape is registered under <mxConstants.SHAPE_CONNECTOR> in
+ * <mxCellRenderer>.
+ * 
+ * Constructor: mxConnector
+ * 
+ * Constructs a new connector shape.
+ * 
+ * Parameters:
+ * 
+ * points - Array of <mxPoints> that define the points. This is stored in
+ * <mxShape.points>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * Default is 'black'.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxConnector(points, stroke, strokewidth)
+{
+	mxPolyline.call(this, points, stroke, strokewidth);
+};
+
+/**
+ * Extends mxPolyline.
+ */
+mxUtils.extend(mxConnector, mxPolyline);
+
+/**
+ * Function: updateBoundingBox
+ *
+ * Updates the <boundingBox> for this shape using <createBoundingBox> and
+ * <augmentBoundingBox> and stores the result in <boundingBox>.
+ */
+mxConnector.prototype.updateBoundingBox = function()
+{
+	this.useSvgBoundingBox = this.style != null && this.style[mxConstants.STYLE_CURVED] == 1;
+	mxShape.prototype.updateBoundingBox.apply(this, arguments);
+};
+
+/**
+ * Function: paintEdgeShape
+ * 
+ * Paints the line shape.
+ */
+mxConnector.prototype.paintEdgeShape = function(c, pts)
+{
+	// The indirection via functions for markers is needed in
+	// order to apply the offsets before painting the line and
+	// paint the markers after painting the line.
+	var sourceMarker = this.createMarker(c, pts, true);
+	var targetMarker = this.createMarker(c, pts, false);
+
+	mxPolyline.prototype.paintEdgeShape.apply(this, arguments);
+	
+	// Disables shadows, dashed styles and fixes fill color for markers
+	c.setFillColor(this.stroke);
+	c.setShadow(false);
+	c.setDashed(false);
+	
+	if (sourceMarker != null)
+	{
+		sourceMarker();
+	}
+	
+	if (targetMarker != null)
+	{
+		targetMarker();
+	}
+};
+
+/**
+ * Function: createMarker
+ * 
+ * Prepares the marker by adding offsets in pts and returning a function to
+ * paint the marker.
+ */
+mxConnector.prototype.createMarker = function(c, pts, source)
+{
+	var result = null;
+	var n = pts.length;
+	var type = mxUtils.getValue(this.style, (source) ? mxConstants.STYLE_STARTARROW : mxConstants.STYLE_ENDARROW);
+	var p0 = (source) ? pts[1] : pts[n - 2];
+	var pe = (source) ? pts[0] : pts[n - 1];
+	
+	if (type != null && p0 != null && pe != null)
+	{
+		var count = 1;
+		
+		// Uses next non-overlapping point
+		while (count < n - 1 && Math.round(p0.x - pe.x) == 0 && Math.round(p0.y - pe.y) == 0)
+		{
+			p0 = (source) ? pts[1 + count] : pts[n - 2 - count];
+			count++;
+		}
+	
+		// Computes the norm and the inverse norm
+		var dx = pe.x - p0.x;
+		var dy = pe.y - p0.y;
+	
+		var dist = Math.max(1, Math.sqrt(dx * dx + dy * dy));
+		
+		var unitX = dx / dist;
+		var unitY = dy / dist;
+	
+		var size = mxUtils.getNumber(this.style, (source) ? mxConstants.STYLE_STARTSIZE : mxConstants.STYLE_ENDSIZE, mxConstants.DEFAULT_MARKERSIZE);
+		
+		// Allow for stroke width in the end point used and the 
+		// orthogonal vectors describing the direction of the marker
+		var filled = this.style[(source) ? mxConstants.STYLE_STARTFILL : mxConstants.STYLE_ENDFILL] != 0;
+		
+		result = mxMarker.createMarker(c, this, type, pe, unitX, unitY, size, source, this.strokewidth, filled);
+	}
+	
+	return result;
+};
+
+/**
+ * Function: augmentBoundingBox
+ *
+ * Augments the bounding box with the strokewidth and shadow offsets.
+ */
+mxConnector.prototype.augmentBoundingBox = function(bbox)
+{
+	mxShape.prototype.augmentBoundingBox.apply(this, arguments);
+	
+	// Adds marker sizes
+	var size = 0;
+	
+	if (mxUtils.getValue(this.style, mxConstants.STYLE_STARTARROW, mxConstants.NONE) != mxConstants.NONE)
+	{
+		size = mxUtils.getNumber(this.style, mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_MARKERSIZE) + 1;
+	}
+	
+	if (mxUtils.getValue(this.style, mxConstants.STYLE_ENDARROW, mxConstants.NONE) != mxConstants.NONE)
+	{
+		size = Math.max(size, mxUtils.getNumber(this.style, mxConstants.STYLE_ENDSIZE, mxConstants.DEFAULT_MARKERSIZE)) + 1;
+	}
+	
+	bbox.grow(size * this.scale);
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/shape/mxCylinder.js b/airavata-kubernetes/workflow-composer/src/js/shape/mxCylinder.js
new file mode 100644
index 0000000..3eef14b
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/shape/mxCylinder.js
@@ -0,0 +1,105 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxCylinder
+ *
+ * Extends <mxShape> to implement an cylinder shape. If a
+ * custom shape with one filled area and an overlay path is
+ * needed, then this shape's <redrawPath> should be overridden.
+ * This shape is registered under <mxConstants.SHAPE_CYLINDER>
+ * in <mxCellRenderer>.
+ * 
+ * Constructor: mxCylinder
+ *
+ * Constructs a new cylinder shape.
+ * 
+ * Parameters:
+ * 
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxCylinder(bounds, fill, stroke, strokewidth)
+{
+	mxShape.call(this);
+	this.bounds = bounds;
+	this.fill = fill;
+	this.stroke = stroke;
+	this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxUtils.extend(mxCylinder, mxShape);
+
+/**
+ * Variable: maxHeight
+ *
+ * Defines the maximum height of the top and bottom part
+ * of the cylinder shape.
+ */
+mxCylinder.prototype.maxHeight = 40;
+
+/**
+ * Variable: svgStrokeTolerance
+ *
+ * Sets stroke tolerance to 0 for SVG.
+ */
+mxCylinder.prototype.svgStrokeTolerance = 0;
+
+/**
+ * Function: paintVertexShape
+ * 
+ * Redirects to redrawPath for subclasses to work.
+ */
+mxCylinder.prototype.paintVertexShape = function(c, x, y, w, h)
+{
+	c.translate(x, y);
+	c.begin();
+	this.redrawPath(c, x, y, w, h, false);
+	c.fillAndStroke();
+	
+	c.setShadow(false);
+	
+	c.begin();
+	this.redrawPath(c, x, y, w, h, true);
+	c.stroke();
+};
+
+/**
+ * Function: redrawPath
+ *
+ * Draws the path for this shape.
+ */
+mxCylinder.prototype.redrawPath = function(c, x, y, w, h, isForeground)
+{
+	var dy = Math.min(this.maxHeight, Math.round(h / 5));
+	
+	if ((isForeground && this.fill != null) || (!isForeground && this.fill == null))
+	{
+		c.moveTo(0, dy);
+		c.curveTo(0, 2 * dy, w, 2 * dy, w, dy);
+		
+		// Needs separate shapes for correct hit-detection
+		if (!isForeground)
+		{
+			c.stroke();
+			c.begin();
+		}
+	}
+	
+	if (!isForeground)
+	{
+		c.moveTo(0, dy);
+		c.curveTo(0, -dy / 3, w, -dy / 3, w, dy);
+		c.lineTo(w, h - dy);
+		c.curveTo(w, h + dy / 3, 0, h + dy / 3, 0, h - dy);
+		c.close();
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/shape/mxDoubleEllipse.js b/airavata-kubernetes/workflow-composer/src/js/shape/mxDoubleEllipse.js
new file mode 100644
index 0000000..7ac95bb
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/shape/mxDoubleEllipse.js
@@ -0,0 +1,114 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxDoubleEllipse
+ *
+ * Extends <mxShape> to implement a double ellipse shape. This shape is
+ * registered under <mxConstants.SHAPE_DOUBLE_ELLIPSE> in <mxCellRenderer>.
+ * Use the following override to only fill the inner ellipse in this shape:
+ * 
+ * (code)
+ * mxDoubleEllipse.prototype.paintVertexShape = function(c, x, y, w, h)
+ * {
+ *   c.ellipse(x, y, w, h);
+ *   c.stroke();
+ *   
+ *   var inset = mxUtils.getValue(this.style, mxConstants.STYLE_MARGIN, Math.min(3 + this.strokewidth, Math.min(w / 5, h / 5)));
+ *   x += inset;
+ *   y += inset;
+ *   w -= 2 * inset;
+ *   h -= 2 * inset;
+ *   
+ *   if (w > 0 && h > 0)
+ *   {
+ *     c.ellipse(x, y, w, h);
+ *   }
+ *   
+ *   c.fillAndStroke();
+ * };
+ * (end)
+ * 
+ * Constructor: mxDoubleEllipse
+ *
+ * Constructs a new ellipse shape.
+ *
+ * Parameters:
+ *
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxDoubleEllipse(bounds, fill, stroke, strokewidth)
+{
+	mxShape.call(this);
+	this.bounds = bounds;
+	this.fill = fill;
+	this.stroke = stroke;
+	this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxUtils.extend(mxDoubleEllipse, mxShape);
+
+/**
+ * Variable: vmlScale
+ * 
+ * Scale for improving the precision of VML rendering. Default is 10.
+ */
+mxDoubleEllipse.prototype.vmlScale = 10;
+
+/**
+ * Function: paintBackground
+ * 
+ * Paints the background.
+ */
+mxDoubleEllipse.prototype.paintBackground = function(c, x, y, w, h)
+{
+	c.ellipse(x, y, w, h);
+	c.fillAndStroke();
+};
+
+/**
+ * Function: paintForeground
+ * 
+ * Paints the foreground.
+ */
+mxDoubleEllipse.prototype.paintForeground = function(c, x, y, w, h)
+{
+	if (!this.outline)
+	{
+		var margin = mxUtils.getValue(this.style, mxConstants.STYLE_MARGIN, Math.min(3 + this.strokewidth, Math.min(w / 5, h / 5)));
+		x += margin;
+		y += margin;
+		w -= 2 * margin;
+		h -= 2 * margin;
+		
+		// FIXME: Rounding issues in IE8 standards mode (not in 1.x)
+		if (w > 0 && h > 0)
+		{
+			c.ellipse(x, y, w, h);
+		}
+		
+		c.stroke();
+	}
+};
+
+/**
+ * Function: getLabelBounds
+ * 
+ * Returns the bounds for the label.
+ */
+mxDoubleEllipse.prototype.getLabelBounds = function(rect)
+{
+	var margin = (mxUtils.getValue(this.style, mxConstants.STYLE_MARGIN, Math.min(3 + this.strokewidth,
+			Math.min(rect.width / 5 / this.scale, rect.height / 5 / this.scale)))) * this.scale;
+
+	return new mxRectangle(rect.x + margin, rect.y + margin, rect.width - 2 * margin, rect.height - 2 * margin);
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/shape/mxEllipse.js b/airavata-kubernetes/workflow-composer/src/js/shape/mxEllipse.js
new file mode 100644
index 0000000..aef8df7
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/shape/mxEllipse.js
@@ -0,0 +1,48 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxEllipse
+ *
+ * Extends <mxShape> to implement an ellipse shape.
+ * This shape is registered under <mxConstants.SHAPE_ELLIPSE>
+ * in <mxCellRenderer>.
+ * 
+ * Constructor: mxEllipse
+ *
+ * Constructs a new ellipse shape.
+ * 
+ * Parameters:
+ * 
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxEllipse(bounds, fill, stroke, strokewidth)
+{
+	mxShape.call(this);
+	this.bounds = bounds;
+	this.fill = fill;
+	this.stroke = stroke;
+	this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxUtils.extend(mxEllipse, mxShape);
+
+/**
+ * Function: paintVertexShape
+ * 
+ * Paints the ellipse shape.
+ */
+mxEllipse.prototype.paintVertexShape = function(c, x, y, w, h)
+{
+	c.ellipse(x, y, w, h);
+	c.fillAndStroke();
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/shape/mxHexagon.js b/airavata-kubernetes/workflow-composer/src/js/shape/mxHexagon.js
new file mode 100644
index 0000000..83f4fd6
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/shape/mxHexagon.js
@@ -0,0 +1,34 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxHexagon
+ * 
+ * Implementation of the hexagon shape.
+ * 
+ * Constructor: mxHexagon
+ *
+ * Constructs a new hexagon shape.
+ */
+function mxHexagon()
+{
+	mxActor.call(this);
+};
+
+/**
+ * Extends mxActor.
+ */
+mxUtils.extend(mxHexagon, mxActor);
+
+/**
+ * Function: redrawPath
+ *
+ * Draws the path for this shape.
+ */
+mxHexagon.prototype.redrawPath = function(c, x, y, w, h)
+{
+	var arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
+	this.addPoints(c, [new mxPoint(0.25 * w, 0), new mxPoint(0.75 * w, 0), new mxPoint(w, 0.5 * h), new mxPoint(0.75 * w, h),
+	                   new mxPoint(0.25 * w, h), new mxPoint(0, 0.5 * h)], this.isRounded, arcSize, true);
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/shape/mxImageShape.js b/airavata-kubernetes/workflow-composer/src/js/shape/mxImageShape.js
new file mode 100644
index 0000000..123b64f
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/shape/mxImageShape.js
@@ -0,0 +1,233 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxImageShape
+ *
+ * Extends <mxShape> to implement an image shape. This shape is registered
+ * under <mxConstants.SHAPE_IMAGE> in <mxCellRenderer>.
+ * 
+ * Constructor: mxImageShape
+ * 
+ * Constructs a new image shape.
+ * 
+ * Parameters:
+ * 
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * image - String that specifies the URL of the image. This is stored in
+ * <image>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 0. This is stored in <strokewidth>.
+ */
+function mxImageShape(bounds, image, fill, stroke, strokewidth)
+{
+	mxShape.call(this);
+	this.bounds = bounds;
+	this.image = image;
+	this.fill = fill;
+	this.stroke = stroke;
+	this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+	this.shadow = false;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxUtils.extend(mxImageShape, mxRectangleShape);
+
+/**
+ * Variable: preserveImageAspect
+ *
+ * Switch to preserve image aspect. Default is true.
+ */
+mxImageShape.prototype.preserveImageAspect = true;
+
+/**
+ * Function: getSvgScreenOffset
+ * 
+ * Disables offset in IE9 for crisper image output.
+ */
+mxImageShape.prototype.getSvgScreenOffset = function()
+{
+	return 0;
+};
+
+/**
+ * Function: apply
+ * 
+ * Overrides <mxShape.apply> to replace the fill and stroke colors with the
+ * respective values from <mxConstants.STYLE_IMAGE_BACKGROUND> and
+ * <mxConstants.STYLE_IMAGE_BORDER>.
+ * 
+ * Applies the style of the given <mxCellState> to the shape. This
+ * implementation assigns the following styles to local fields:
+ * 
+ * - <mxConstants.STYLE_IMAGE_BACKGROUND> => fill
+ * - <mxConstants.STYLE_IMAGE_BORDER> => stroke
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> of the corresponding cell.
+ */
+mxImageShape.prototype.apply = function(state)
+{
+	mxShape.prototype.apply.apply(this, arguments);
+	
+	this.fill = null;
+	this.stroke = null;
+	this.gradient = null;
+	
+	if (this.style != null)
+	{
+		this.preserveImageAspect = mxUtils.getNumber(this.style, mxConstants.STYLE_IMAGE_ASPECT, 1) == 1;
+		
+		// Legacy support for imageFlipH/V
+		this.flipH = this.flipH || mxUtils.getValue(this.style, 'imageFlipH', 0) == 1;
+		this.flipV = this.flipV || mxUtils.getValue(this.style, 'imageFlipV', 0) == 1;
+	}
+};
+
+/**
+ * Function: isHtmlAllowed
+ * 
+ * Returns true if HTML is allowed for this shape. This implementation always
+ * returns false.
+ */
+mxImageShape.prototype.isHtmlAllowed = function()
+{
+	return !this.preserveImageAspect;
+};
+
+/**
+ * Function: createHtml
+ *
+ * Creates and returns the HTML DOM node(s) to represent
+ * this shape. This implementation falls back to <createVml>
+ * so that the HTML creation is optional.
+ */
+mxImageShape.prototype.createHtml = function()
+{
+	var node = document.createElement('div');
+	node.style.position = 'absolute';
+
+	return node;
+};
+
+/**
+ * Function: paintVertexShape
+ * 
+ * Generic background painting implementation.
+ */
+mxImageShape.prototype.paintVertexShape = function(c, x, y, w, h)
+{
+	if (this.image != null)
+	{
+		var fill = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_BACKGROUND, null);
+		var stroke = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_BORDER, null);
+		
+		if (fill != null)
+		{
+			// Stroke rendering required for shadow
+			c.setFillColor(fill);
+			c.setStrokeColor(stroke);
+			c.rect(x, y, w, h);
+			c.fillAndStroke();
+		}
+
+		// FlipH/V are implicit via mxShape.updateTransform
+		c.image(x, y, w, h, this.image, this.preserveImageAspect, false, false);
+		
+		var stroke = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_BORDER, null);
+		
+		if (stroke != null)
+		{
+			c.setShadow(false);
+			c.setStrokeColor(stroke);
+			c.rect(x, y, w, h);
+			c.stroke();
+		}
+	}
+	else
+	{
+		mxRectangleShape.prototype.paintBackground.apply(this, arguments);
+	}
+};
+
+/**
+ * Function: redraw
+ * 
+ * Overrides <mxShape.redraw> to preserve the aspect ratio of images.
+ */
+mxImageShape.prototype.redrawHtmlShape = function()
+{
+	this.node.style.left = Math.round(this.bounds.x) + 'px';
+	this.node.style.top = Math.round(this.bounds.y) + 'px';
+	this.node.style.width = Math.max(0, Math.round(this.bounds.width)) + 'px';
+	this.node.style.height = Math.max(0, Math.round(this.bounds.height)) + 'px';
+	this.node.innerHTML = '';
+
+	if (this.image != null)
+	{
+		var fill = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_BACKGROUND, '');
+		var stroke = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_BORDER, '');
+		this.node.style.backgroundColor = fill;
+		this.node.style.borderColor = stroke;
+		
+		// VML image supports PNG in IE6
+		var useVml = mxClient.IS_IE6 || ((document.documentMode == null || document.documentMode <= 8) && this.rotation != 0);
+		var img = document.createElement((useVml) ? mxClient.VML_PREFIX + ':image' : 'img');
+		img.setAttribute('border', '0');
+		img.style.position = 'absolute';
+		img.src = this.image;
+
+		var filter = (this.opacity < 100) ? 'alpha(opacity=' + this.opacity + ')' : '';
+		this.node.style.filter = filter;
+		
+		if (this.flipH && this.flipV)
+		{
+			filter += 'progid:DXImageTransform.Microsoft.BasicImage(rotation=2)';
+		}
+		else if (this.flipH)
+		{
+			filter += 'progid:DXImageTransform.Microsoft.BasicImage(mirror=1)';
+		}
+		else if (this.flipV)
+		{
+			filter += 'progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)';
+		}
+
+		if (img.style.filter != filter)
+		{
+			img.style.filter = filter;
+		}
+
+		if (img.nodeName == 'image')
+		{
+			img.style.rotation = this.rotation;
+		}
+		else if (this.rotation != 0)
+		{
+			// LATER: Add flipV/H support
+			mxUtils.setPrefixedStyle(img.style, 'transform', 'rotate(' + this.rotation + 'deg)');
+		}
+		else
+		{
+			mxUtils.setPrefixedStyle(img.style, 'transform', '');
+		}
+
+		// Known problem: IE clips top line of image for certain angles
+		img.style.width = this.node.style.width;
+		img.style.height = this.node.style.height;
+		
+		this.node.style.backgroundImage = '';
+		this.node.appendChild(img);
+	}
+	else
+	{
+		this.setTransparentBackgroundImage(this.node);
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/shape/mxLabel.js b/airavata-kubernetes/workflow-composer/src/js/shape/mxLabel.js
new file mode 100644
index 0000000..f9305b1
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/shape/mxLabel.js
@@ -0,0 +1,275 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxLabel
+ *
+ * Extends <mxShape> to implement an image shape with a label.
+ * This shape is registered under <mxConstants.SHAPE_LABEL> in
+ * <mxCellRenderer>.
+ * 
+ * Constructor: mxLabel
+ *
+ * Constructs a new label shape.
+ * 
+ * Parameters:
+ * 
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxLabel(bounds, fill, stroke, strokewidth)
+{
+	mxRectangleShape.call(this, bounds, fill, stroke, strokewidth);
+};
+
+/**
+ * Extends mxShape.
+ */
+mxUtils.extend(mxLabel, mxRectangleShape);
+
+/**
+ * Variable: imageSize
+ *
+ * Default width and height for the image. Default is
+ * <mxConstants.DEFAULT_IMAGESIZE>.
+ */
+mxLabel.prototype.imageSize = mxConstants.DEFAULT_IMAGESIZE;
+
+/**
+ * Variable: spacing
+ *
+ * Default value for image spacing. Default is 2.
+ */
+mxLabel.prototype.spacing = 2;
+
+/**
+ * Variable: indicatorSize
+ *
+ * Default width and height for the indicicator. Default is 10.
+ */
+mxLabel.prototype.indicatorSize = 10;
+
+/**
+ * Variable: indicatorSpacing
+ *
+ * Default spacing between image and indicator. Default is 2.
+ */
+mxLabel.prototype.indicatorSpacing = 2;
+
+/**
+ * Function: init
+ *
+ * Initializes the shape and the <indicator>.
+ */
+mxLabel.prototype.init = function(container)
+{
+	mxShape.prototype.init.apply(this, arguments);
+
+	if (this.indicatorShape != null)
+	{
+		this.indicator = new this.indicatorShape();
+		this.indicator.dialect = this.dialect;
+		this.indicator.init(this.node);
+	}
+};
+
+/**
+ * Function: redraw
+ *
+ * Reconfigures this shape. This will update the colors of the indicator
+ * and reconfigure it if required.
+ */
+mxLabel.prototype.redraw = function()
+{
+	if (this.indicator != null)
+	{
+		this.indicator.fill = this.indicatorColor;
+		this.indicator.stroke = this.indicatorStrokeColor;
+		this.indicator.gradient = this.indicatorGradientColor;
+		this.indicator.direction = this.indicatorDirection;
+	}
+	
+	mxShape.prototype.redraw.apply(this, arguments);
+};
+
+/**
+ * Function: isHtmlAllowed
+ *
+ * Returns true for non-rounded, non-rotated shapes with no glass gradient and
+ * no indicator shape.
+ */
+mxLabel.prototype.isHtmlAllowed = function()
+{
+	return mxRectangleShape.prototype.isHtmlAllowed.apply(this, arguments) &&
+		this.indicatorColor == null && this.indicatorShape == null;
+};
+
+/**
+ * Function: paintForeground
+ * 
+ * Generic background painting implementation.
+ */
+mxLabel.prototype.paintForeground = function(c, x, y, w, h)
+{
+	this.paintImage(c, x, y, w, h);
+	this.paintIndicator(c, x, y, w, h);
+	
+	mxRectangleShape.prototype.paintForeground.apply(this, arguments);
+};
+
+/**
+ * Function: paintImage
+ * 
+ * Generic background painting implementation.
+ */
+mxLabel.prototype.paintImage = function(c, x, y, w, h)
+{
+	if (this.image != null)
+	{
+		var bounds = this.getImageBounds(x, y, w, h);
+		c.image(bounds.x, bounds.y, bounds.width, bounds.height, this.image, false, false, false);
+	}
+};
+
+/**
+ * Function: getImageBounds
+ * 
+ * Generic background painting implementation.
+ */
+mxLabel.prototype.getImageBounds = function(x, y, w, h)
+{
+	var align = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_ALIGN, mxConstants.ALIGN_LEFT);
+	var valign = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_VERTICAL_ALIGN, mxConstants.ALIGN_MIDDLE);
+	var width = mxUtils.getNumber(this.style, mxConstants.STYLE_IMAGE_WIDTH, mxConstants.DEFAULT_IMAGESIZE);
+	var height = mxUtils.getNumber(this.style, mxConstants.STYLE_IMAGE_HEIGHT, mxConstants.DEFAULT_IMAGESIZE);
+	var spacing = mxUtils.getNumber(this.style, mxConstants.STYLE_SPACING, this.spacing) + 5;
+
+	if (align == mxConstants.ALIGN_CENTER)
+	{
+		x += (w - width) / 2;
+	}
+	else if (align == mxConstants.ALIGN_RIGHT)
+	{
+		x += w - width - spacing;
+	}
+	else // default is left
+	{
+		x += spacing;
+	}
+
+	if (valign == mxConstants.ALIGN_TOP)
+	{
+		y += spacing;
+	}
+	else if (valign == mxConstants.ALIGN_BOTTOM)
+	{
+		y += h - height - spacing;
+	}
+	else // default is middle
+	{
+		y += (h - height) / 2;
+	}
+	
+	return new mxRectangle(x, y, width, height);
+};
+
+/**
+ * Function: paintIndicator
+ * 
+ * Generic background painting implementation.
+ */
+mxLabel.prototype.paintIndicator = function(c, x, y, w, h)
+{
+	if (this.indicator != null)
+	{
+		this.indicator.bounds = this.getIndicatorBounds(x, y, w, h);
+		this.indicator.paint(c);
+	}
+	else if (this.indicatorImage != null)
+	{
+		var bounds = this.getIndicatorBounds(x, y, w, h);
+		c.image(bounds.x, bounds.y, bounds.width, bounds.height, this.indicatorImage, false, false, false);
+	}
+};
+
+/**
+ * Function: getIndicatorBounds
+ * 
+ * Generic background painting implementation.
+ */
+mxLabel.prototype.getIndicatorBounds = function(x, y, w, h)
+{
+	var align = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_ALIGN, mxConstants.ALIGN_LEFT);
+	var valign = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_VERTICAL_ALIGN, mxConstants.ALIGN_MIDDLE);
+	var width = mxUtils.getNumber(this.style, mxConstants.STYLE_INDICATOR_WIDTH, this.indicatorSize);
+	var height = mxUtils.getNumber(this.style, mxConstants.STYLE_INDICATOR_HEIGHT, this.indicatorSize);
+	var spacing = this.spacing + 5;		
+	
+	if (align == mxConstants.ALIGN_RIGHT)
+	{
+		x += w - width - spacing;
+	}
+	else if (align == mxConstants.ALIGN_CENTER)
+	{
+		x += (w - width) / 2;
+	}
+	else // default is left
+	{
+		x += spacing;
+	}
+	
+	if (valign == mxConstants.ALIGN_BOTTOM)
+	{
+		y += h - height - spacing;
+	}
+	else if (valign == mxConstants.ALIGN_TOP)
+	{
+		y += spacing;
+	}
+	else // default is middle
+	{
+		y += (h - height) / 2;
+	}
+	
+	return new mxRectangle(x, y, width, height);
+};
+/**
+ * Function: redrawHtmlShape
+ * 
+ * Generic background painting implementation.
+ */
+mxLabel.prototype.redrawHtmlShape = function()
+{
+	mxRectangleShape.prototype.redrawHtmlShape.apply(this, arguments);
+	
+	// Removes all children
+	while(this.node.hasChildNodes())
+	{
+		this.node.removeChild(this.node.lastChild);
+	}
+	
+	if (this.image != null)
+	{
+		var node = document.createElement('img');
+		node.style.position = 'relative';
+		node.setAttribute('border', '0');
+		
+		var bounds = this.getImageBounds(this.bounds.x, this.bounds.y, this.bounds.width, this.bounds.height);
+		bounds.x -= this.bounds.x;
+		bounds.y -= this.bounds.y;
+
+		node.style.left = Math.round(bounds.x) + 'px';
+		node.style.top = Math.round(bounds.y) + 'px';
+		node.style.width = Math.round(bounds.width) + 'px';
+		node.style.height = Math.round(bounds.height) + 'px';
+		
+		node.src = this.image;
+		
+		this.node.appendChild(node);
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/shape/mxLine.js b/airavata-kubernetes/workflow-composer/src/js/shape/mxLine.js
new file mode 100644
index 0000000..a154cb3
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/shape/mxLine.js
@@ -0,0 +1,51 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxLine
+ *
+ * Extends <mxShape> to implement a horizontal line shape.
+ * This shape is registered under <mxConstants.SHAPE_LINE> in
+ * <mxCellRenderer>.
+ * 
+ * Constructor: mxLine
+ *
+ * Constructs a new line shape.
+ * 
+ * Parameters:
+ * 
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * stroke - String that defines the stroke color. Default is 'black'. This is
+ * stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxLine(bounds, stroke, strokewidth)
+{
+	mxShape.call(this);
+	this.bounds = bounds;
+	this.stroke = stroke;
+	this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxUtils.extend(mxLine, mxShape);
+
+/**
+ * Function: paintVertexShape
+ * 
+ * Redirects to redrawPath for subclasses to work.
+ */
+mxLine.prototype.paintVertexShape = function(c, x, y, w, h)
+{
+	var mid = y + h / 2;
+
+	c.begin();
+	c.moveTo(x, mid);
+	c.lineTo(x + w, mid);
+	c.stroke();
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/shape/mxMarker.js b/airavata-kubernetes/workflow-composer/src/js/shape/mxMarker.js
new file mode 100644
index 0000000..1004eb5
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/shape/mxMarker.js
@@ -0,0 +1,208 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+var mxMarker =
+{
+	/**
+	 * Class: mxMarker
+	 * 
+	 * A static class that implements all markers for VML and SVG using a
+	 * registry. NOTE: The signatures in this class will change.
+	 * 
+	 * Variable: markers
+	 * 
+	 * Maps from markers names to functions to paint the markers.
+	 */
+	markers: [],
+	
+	/**
+	 * Function: addMarker
+	 * 
+	 * Adds a factory method that updates a given endpoint and returns a
+	 * function to paint the marker onto the given canvas.
+	 */
+	addMarker: function(type, funct)
+	{
+		mxMarker.markers[type] = funct;
+	},
+	
+	/**
+	 * Function: createMarker
+	 * 
+	 * Returns a function to paint the given marker.
+	 */
+	createMarker: function(canvas, shape, type, pe, unitX, unitY, size, source, sw, filled)
+	{
+		var funct = mxMarker.markers[type];
+		
+		return (funct != null) ? funct(canvas, shape, type, pe, unitX, unitY, size, source, sw, filled) : null;
+	}
+
+};
+
+/**
+ * Adds the classic and block marker factory method.
+ */
+(function()
+{
+	function createArrow(widthFactor)
+	{
+		widthFactor = (widthFactor != null) ? widthFactor : 2;
+		
+		return function(canvas, shape, type, pe, unitX, unitY, size, source, sw, filled)
+		{
+			// The angle of the forward facing arrow sides against the x axis is
+			// 26.565 degrees, 1/sin(26.565) = 2.236 / 2 = 1.118 ( / 2 allows for
+			// only half the strokewidth is processed ).
+			var endOffsetX = unitX * sw * 1.118;
+			var endOffsetY = unitY * sw * 1.118;
+			
+			unitX = unitX * (size + sw);
+			unitY = unitY * (size + sw);
+	
+			var pt = pe.clone();
+			pt.x -= endOffsetX;
+			pt.y -= endOffsetY;
+			
+			var f = (type != mxConstants.ARROW_CLASSIC && type != mxConstants.ARROW_CLASSIC_THIN) ? 1 : 3 / 4;
+			pe.x += -unitX * f - endOffsetX;
+			pe.y += -unitY * f - endOffsetY;
+			
+			return function()
+			{
+				canvas.begin();
+				canvas.moveTo(pt.x, pt.y);
+				canvas.lineTo(pt.x - unitX - unitY / widthFactor, pt.y - unitY + unitX / widthFactor);
+			
+				if (type == mxConstants.ARROW_CLASSIC || type == mxConstants.ARROW_CLASSIC_THIN)
+				{
+					canvas.lineTo(pt.x - unitX * 3 / 4, pt.y - unitY * 3 / 4);
+				}
+			
+				canvas.lineTo(pt.x + unitY / widthFactor - unitX, pt.y - unitY - unitX / widthFactor);
+				canvas.close();
+	
+				if (filled)
+				{
+					canvas.fillAndStroke();
+				}
+				else
+				{
+					canvas.stroke();
+				}
+			};
+		}
+	};
+	
+	mxMarker.addMarker('classic', createArrow(2));
+	mxMarker.addMarker('classicThin', createArrow(3));
+	mxMarker.addMarker('block', createArrow(2));
+	mxMarker.addMarker('blockThin', createArrow(3));
+	
+	function createOpenArrow(widthFactor)
+	{
+		widthFactor = (widthFactor != null) ? widthFactor : 2;
+		
+		return function(canvas, shape, type, pe, unitX, unitY, size, source, sw, filled)
+		{
+			// The angle of the forward facing arrow sides against the x axis is
+			// 26.565 degrees, 1/sin(26.565) = 2.236 / 2 = 1.118 ( / 2 allows for
+			// only half the strokewidth is processed ).
+			var endOffsetX = unitX * sw * 1.118;
+			var endOffsetY = unitY * sw * 1.118;
+			
+			unitX = unitX * (size + sw);
+			unitY = unitY * (size + sw);
+			
+			var pt = pe.clone();
+			pt.x -= endOffsetX;
+			pt.y -= endOffsetY;
+			
+			pe.x += -endOffsetX * 2;
+			pe.y += -endOffsetY * 2;
+
+			return function()
+			{
+				canvas.begin();
+				canvas.moveTo(pt.x - unitX - unitY / widthFactor, pt.y - unitY + unitX / widthFactor);
+				canvas.lineTo(pt.x, pt.y);
+				canvas.lineTo(pt.x + unitY / widthFactor - unitX, pt.y - unitY - unitX / widthFactor);
+				canvas.stroke();
+			};
+		}
+	};
+	
+	mxMarker.addMarker('open', createOpenArrow(2));
+	mxMarker.addMarker('openThin', createOpenArrow(3));
+	
+	mxMarker.addMarker('oval', function(canvas, shape, type, pe, unitX, unitY, size, source, sw, filled)
+	{
+		var a = size / 2;
+		
+		var pt = pe.clone();
+		pe.x -= unitX * a;
+		pe.y -= unitY * a;
+
+		return function()
+		{
+			canvas.ellipse(pt.x - a, pt.y - a, size, size);
+						
+			if (filled)
+			{
+				canvas.fillAndStroke();
+			}
+			else
+			{
+				canvas.stroke();
+			}
+		};
+	});
+
+	function diamond(canvas, shape, type, pe, unitX, unitY, size, source, sw, filled)
+	{
+		// The angle of the forward facing arrow sides against the x axis is
+		// 45 degrees, 1/sin(45) = 1.4142 / 2 = 0.7071 ( / 2 allows for
+		// only half the strokewidth is processed ). Or 0.9862 for thin diamond.
+		// Note these values and the tk variable below are dependent, update
+		// both together (saves trig hard coding it).
+		var swFactor = (type == mxConstants.ARROW_DIAMOND) ?  0.7071 : 0.9862;
+		var endOffsetX = unitX * sw * swFactor;
+		var endOffsetY = unitY * sw * swFactor;
+		
+		unitX = unitX * (size + sw);
+		unitY = unitY * (size + sw);
+		
+		var pt = pe.clone();
+		pt.x -= endOffsetX;
+		pt.y -= endOffsetY;
+		
+		pe.x += -unitX - endOffsetX;
+		pe.y += -unitY - endOffsetY;
+		
+		// thickness factor for diamond
+		var tk = ((type == mxConstants.ARROW_DIAMOND) ?  2 : 3.4);
+		
+		return function()
+		{
+			canvas.begin();
+			canvas.moveTo(pt.x, pt.y);
+			canvas.lineTo(pt.x - unitX / 2 - unitY / tk, pt.y + unitX / tk - unitY / 2);
+			canvas.lineTo(pt.x - unitX, pt.y - unitY);
+			canvas.lineTo(pt.x - unitX / 2 + unitY / tk, pt.y - unitY / 2 - unitX / tk);
+			canvas.close();
+			
+			if (filled)
+			{
+				canvas.fillAndStroke();
+			}
+			else
+			{
+				canvas.stroke();
+			}
+		};
+	};
+
+	mxMarker.addMarker('diamond', diamond);
+	mxMarker.addMarker('diamondThin', diamond);
+})();
diff --git a/airavata-kubernetes/workflow-composer/src/js/shape/mxPolyline.js b/airavata-kubernetes/workflow-composer/src/js/shape/mxPolyline.js
new file mode 100644
index 0000000..794e896
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/shape/mxPolyline.js
@@ -0,0 +1,127 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxPolyline
+ *
+ * Extends <mxShape> to implement a polyline (a line with multiple points).
+ * This shape is registered under <mxConstants.SHAPE_POLYLINE> in
+ * <mxCellRenderer>.
+ * 
+ * Constructor: mxPolyline
+ *
+ * Constructs a new polyline shape.
+ * 
+ * Parameters:
+ * 
+ * points - Array of <mxPoints> that define the points. This is stored in
+ * <mxShape.points>.
+ * stroke - String that defines the stroke color. Default is 'black'. This is
+ * stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxPolyline(points, stroke, strokewidth)
+{
+	mxShape.call(this);
+	this.points = points;
+	this.stroke = stroke;
+	this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxUtils.extend(mxPolyline, mxShape);
+
+/**
+ * Function: getRotation
+ * 
+ * Returns 0.
+ */
+mxPolyline.prototype.getRotation = function()
+{
+	return 0;
+};
+
+/**
+ * Function: getShapeRotation
+ * 
+ * Returns 0.
+ */
+mxPolyline.prototype.getShapeRotation = function()
+{
+	return 0;
+};
+
+/**
+ * Function: isPaintBoundsInverted
+ * 
+ * Returns false.
+ */
+mxPolyline.prototype.isPaintBoundsInverted = function()
+{
+	return false;
+};
+
+/**
+ * Function: paintEdgeShape
+ * 
+ * Paints the line shape.
+ */
+mxPolyline.prototype.paintEdgeShape = function(c, pts)
+{
+	if (this.style == null || this.style[mxConstants.STYLE_CURVED] != 1)
+	{
+		this.paintLine(c, pts, this.isRounded);
+	}
+	else
+	{
+		this.paintCurvedLine(c, pts);
+	}
+};
+
+/**
+ * Function: paintLine
+ * 
+ * Paints the line shape.
+ */
+mxPolyline.prototype.paintLine = function(c, pts, rounded)
+{
+	var arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
+	c.begin();
+	this.addPoints(c, pts, rounded, arcSize, false);
+	c.stroke();
+};
+
+/**
+ * Function: paintLine
+ * 
+ * Paints the line shape.
+ */
+mxPolyline.prototype.paintCurvedLine = function(c, pts)
+{
+	c.begin();
+	
+	var pt = pts[0];
+	var n = pts.length;
+	
+	c.moveTo(pt.x, pt.y);
+	
+	for (var i = 1; i < n - 2; i++)
+	{
+		var p0 = pts[i];
+		var p1 = pts[i + 1];
+		var ix = (p0.x + p1.x) / 2;
+		var iy = (p0.y + p1.y) / 2;
+		
+		c.quadTo(p0.x, p0.y, ix, iy);
+	}
+	
+	var p0 = pts[n - 2];
+	var p1 = pts[n - 1];
+	
+	c.quadTo(p0.x, p0.y, p1.x, p1.y);
+	c.stroke();
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/shape/mxRectangleShape.js b/airavata-kubernetes/workflow-composer/src/js/shape/mxRectangleShape.js
new file mode 100644
index 0000000..da61e4b
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/shape/mxRectangleShape.js
@@ -0,0 +1,117 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxRectangleShape
+ *
+ * Extends <mxShape> to implement a rectangle shape.
+ * This shape is registered under <mxConstants.SHAPE_RECTANGLE>
+ * in <mxCellRenderer>.
+ * 
+ * Constructor: mxRectangleShape
+ *
+ * Constructs a new rectangle shape.
+ * 
+ * Parameters:
+ * 
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxRectangleShape(bounds, fill, stroke, strokewidth)
+{
+	mxShape.call(this);
+	this.bounds = bounds;
+	this.fill = fill;
+	this.stroke = stroke;
+	this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxUtils.extend(mxRectangleShape, mxShape);
+
+/**
+ * Function: isHtmlAllowed
+ *
+ * Returns true for non-rounded, non-rotated shapes with no glass gradient.
+ */
+mxRectangleShape.prototype.isHtmlAllowed = function()
+{
+	var events = true;
+	
+	if (this.style != null)
+	{
+		events = mxUtils.getValue(this.style, mxConstants.STYLE_POINTER_EVENTS, '1') == '1';		
+	}
+	
+	return !this.isRounded && !this.glass && this.rotation == 0 && (events ||
+		(this.fill != null && this.fill != mxConstants.NONE));
+};
+
+/**
+ * Function: paintBackground
+ * 
+ * Generic background painting implementation.
+ */
+mxRectangleShape.prototype.paintBackground = function(c, x, y, w, h)
+{
+	var events = true;
+	
+	if (this.style != null)
+	{
+		events = mxUtils.getValue(this.style, mxConstants.STYLE_POINTER_EVENTS, '1') == '1';		
+	}
+	
+	if (events || (this.fill != null && this.fill != mxConstants.NONE) ||
+		(this.stroke != null && this.stroke != mxConstants.NONE))
+	{
+		if (!events && (this.fill == null || this.fill == mxConstants.NONE))
+		{
+			c.pointerEvents = false;
+		}
+		
+		if (this.isRounded)
+		{
+			var r = 0;
+			
+			if (mxUtils.getValue(this.style, mxConstants.STYLE_ABSOLUTE_ARCSIZE, 0) == '1')
+			{
+				r = Math.min(w / 2, Math.min(h / 2, mxUtils.getValue(this.style,
+					mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2));
+			}
+			else
+			{
+				var f = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE,
+					mxConstants.RECTANGLE_ROUNDING_FACTOR * 100) / 100;
+				r = Math.min(w * f, h * f);
+			}
+			
+			c.roundrect(x, y, w, h, r, r);
+		}
+		else
+		{
+			c.rect(x, y, w, h);
+		}
+			
+		c.fillAndStroke();
+	}
+};
+
+/**
+ * Function: paintForeground
+ * 
+ * Generic background painting implementation.
+ */
+mxRectangleShape.prototype.paintForeground = function(c, x, y, w, h)
+{
+	if (this.glass && !this.outline && this.fill != null && this.fill != mxConstants.NONE)
+	{
+		this.paintGlassEffect(c, x, y, w, h, this.getArcSize(w + this.strokewidth, h + this.strokewidth));
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/shape/mxRhombus.js b/airavata-kubernetes/workflow-composer/src/js/shape/mxRhombus.js
new file mode 100644
index 0000000..4bc2962
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/shape/mxRhombus.js
@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxRhombus
+ *
+ * Extends <mxShape> to implement a rhombus (aka diamond) shape.
+ * This shape is registered under <mxConstants.SHAPE_RHOMBUS>
+ * in <mxCellRenderer>.
+ * 
+ * Constructor: mxRhombus
+ *
+ * Constructs a new rhombus shape.
+ * 
+ * Parameters:
+ * 
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxRhombus(bounds, fill, stroke, strokewidth)
+{
+	mxShape.call(this);
+	this.bounds = bounds;
+	this.fill = fill;
+	this.stroke = stroke;
+	this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxUtils.extend(mxRhombus, mxShape);
+
+/**
+ * Function: paintVertexShape
+ * 
+ * Generic painting implementation.
+ */
+mxRhombus.prototype.paintVertexShape = function(c, x, y, w, h)
+{
+	var hw = w / 2;
+	var hh = h / 2;
+	
+	var arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
+	c.begin();
+	this.addPoints(c, [new mxPoint(x + hw, y), new mxPoint(x + w, y + hh), new mxPoint(x + hw, y + h),
+	     new mxPoint(x, y + hh)], this.isRounded, arcSize, true);
+	c.fillAndStroke();
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/shape/mxShape.js b/airavata-kubernetes/workflow-composer/src/js/shape/mxShape.js
new file mode 100644
index 0000000..506d6d8
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/shape/mxShape.js
@@ -0,0 +1,1604 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxShape
+ *
+ * Base class for all shapes. A shape in mxGraph is a
+ * separate implementation for SVG, VML and HTML. Which
+ * implementation to use is controlled by the <dialect>
+ * property which is assigned from within the <mxCellRenderer>
+ * when the shape is created. The dialect must be assigned
+ * for a shape, and it does normally depend on the browser and
+ * the confiuration of the graph (see <mxGraph> rendering hint).
+ *
+ * For each supported shape in SVG and VML, a corresponding
+ * shape exists in mxGraph, namely for text, image, rectangle,
+ * rhombus, ellipse and polyline. The other shapes are a
+ * combination of these shapes (eg. label and swimlane)
+ * or they consist of one or more (filled) path objects
+ * (eg. actor and cylinder). The HTML implementation is
+ * optional but may be required for a HTML-only view of
+ * the graph.
+ *
+ * Custom Shapes:
+ *
+ * To extend from this class, the basic code looks as follows.
+ * In the special case where the custom shape consists only of
+ * one filled region or one filled region and an additional stroke
+ * the <mxActor> and <mxCylinder> should be subclassed,
+ * respectively.
+ *
+ * (code)
+ * function CustomShape() { }
+ * 
+ * CustomShape.prototype = new mxShape();
+ * CustomShape.prototype.constructor = CustomShape; 
+ * (end)
+ *
+ * To register a custom shape in an existing graph instance,
+ * one must register the shape under a new name in the graph's
+ * cell renderer as follows:
+ *
+ * (code)
+ * mxCellRenderer.registerShape('customShape', CustomShape);
+ * (end)
+ *
+ * The second argument is the name of the constructor.
+ *
+ * In order to use the shape you can refer to the given name above
+ * in a stylesheet. For example, to change the shape for the default
+ * vertex style, the following code is used:
+ *
+ * (code)
+ * var style = graph.getStylesheet().getDefaultVertexStyle();
+ * style[mxConstants.STYLE_SHAPE] = 'customShape';
+ * (end)
+ * 
+ * Constructor: mxShape
+ *
+ * Constructs a new shape.
+ */
+function mxShape(stencil)
+{
+	this.stencil = stencil;
+	this.initStyles();
+};
+
+/**
+ * Variable: dialect
+ *
+ * Holds the dialect in which the shape is to be painted.
+ * This can be one of the DIALECT constants in <mxConstants>.
+ */
+mxShape.prototype.dialect = null;
+
+/**
+ * Variable: scale
+ *
+ * Holds the scale in which the shape is being painted.
+ */
+mxShape.prototype.scale = 1;
+
+/**
+ * Variable: antiAlias
+ * 
+ * Rendering hint for configuring the canvas.
+ */
+mxShape.prototype.antiAlias = true;
+
+/**
+ * Variable: bounds
+ *
+ * Holds the <mxRectangle> that specifies the bounds of this shape.
+ */
+mxShape.prototype.bounds = null;
+
+/**
+ * Variable: points
+ *
+ * Holds the array of <mxPoints> that specify the points of this shape.
+ */
+mxShape.prototype.points = null;
+
+/**
+ * Variable: node
+ *
+ * Holds the outermost DOM node that represents this shape.
+ */
+mxShape.prototype.node = null;
+ 
+/**
+ * Variable: state
+ * 
+ * Optional reference to the corresponding <mxCellState>.
+ */
+mxShape.prototype.state = null;
+
+/**
+ * Variable: style
+ *
+ * Optional reference to the style of the corresponding <mxCellState>.
+ */
+mxShape.prototype.style = null;
+
+/**
+ * Variable: boundingBox
+ *
+ * Contains the bounding box of the shape, that is, the smallest rectangle
+ * that includes all pixels of the shape.
+ */
+mxShape.prototype.boundingBox = null;
+
+/**
+ * Variable: stencil
+ *
+ * Holds the <mxStencil> that defines the shape.
+ */
+mxShape.prototype.stencil = null;
+
+/**
+ * Variable: svgStrokeTolerance
+ *
+ * Event-tolerance for SVG strokes (in px). Default is 8. This is only passed
+ * to the canvas in <createSvgCanvas> if <pointerEvents> is true.
+ */
+mxShape.prototype.svgStrokeTolerance = 8;
+
+/**
+ * Variable: pointerEvents
+ * 
+ * Specifies if pointer events should be handled. Default is true.
+ */
+mxShape.prototype.pointerEvents = true;
+
+/**
+ * Variable: svgPointerEvents
+ * 
+ * Specifies if pointer events should be handled. Default is true.
+ */
+mxShape.prototype.svgPointerEvents = 'all';
+
+/**
+ * Variable: shapePointerEvents
+ * 
+ * Specifies if pointer events outside of shape should be handled. Default
+ * is false.
+ */
+mxShape.prototype.shapePointerEvents = false;
+
+/**
+ * Variable: stencilPointerEvents
+ * 
+ * Specifies if pointer events outside of stencils should be handled. Default
+ * is false. Set this to true for backwards compatibility with the 1.x branch.
+ */
+mxShape.prototype.stencilPointerEvents = false;
+
+/**
+ * Variable: vmlScale
+ * 
+ * Scale for improving the precision of VML rendering. Default is 1.
+ */
+mxShape.prototype.vmlScale = 1;
+
+/**
+ * Variable: outline
+ * 
+ * Specifies if the shape should be drawn as an outline. This disables all
+ * fill colors and can be used to disable other drawing states that should
+ * not be painted for outlines. Default is false. This should be set before
+ * calling <apply>.
+ */
+mxShape.prototype.outline = false;
+
+/**
+ * Variable: visible
+ * 
+ * Specifies if the shape is visible. Default is true.
+ */
+mxShape.prototype.visible = true;
+
+/**
+ * Variable: useSvgBoundingBox
+ * 
+ * Allows to use the SVG bounding box in SVG. Default is false for performance
+ * reasons.
+ */
+mxShape.prototype.useSvgBoundingBox = false;
+
+/**
+ * Function: init
+ *
+ * Initializes the shape by creaing the DOM node using <create>
+ * and adding it into the given container.
+ *
+ * Parameters:
+ *
+ * container - DOM node that will contain the shape.
+ */
+mxShape.prototype.init = function(container)
+{
+	if (this.node == null)
+	{
+		this.node = this.create(container);
+		
+		if (container != null)
+		{
+			container.appendChild(this.node);
+		}
+	}
+};
+
+/**
+ * Function: initStyles
+ *
+ * Sets the styles to their default values.
+ */
+mxShape.prototype.initStyles = function(container)
+{
+	this.strokewidth = 1;
+	this.rotation = 0;
+	this.opacity = 100;
+	this.fillOpacity = 100;
+	this.strokeOpacity = 100;
+	this.flipH = false;
+	this.flipV = false;
+};
+
+/**
+ * Function: isParseVml
+ * 
+ * Specifies if any VML should be added via insertAdjacentHtml to the DOM. This
+ * is only needed in IE8 and only if the shape contains VML markup. This method
+ * returns true.
+ */
+mxShape.prototype.isParseVml = function()
+{
+	return true;
+};
+
+/**
+ * Function: isHtmlAllowed
+ * 
+ * Returns true if HTML is allowed for this shape. This implementation always
+ * returns false.
+ */
+mxShape.prototype.isHtmlAllowed = function()
+{
+	return false;
+};
+
+/**
+ * Function: getSvgScreenOffset
+ * 
+ * Returns 0, or 0.5 if <strokewidth> % 2 == 1.
+ */
+mxShape.prototype.getSvgScreenOffset = function()
+{
+	var sw = this.stencil && this.stencil.strokewidth != 'inherit' ? Number(this.stencil.strokewidth) : this.strokewidth;
+	
+	return (mxUtils.mod(Math.max(1, Math.round(sw * this.scale)), 2) == 1) ? 0.5 : 0;
+};
+
+/**
+ * Function: create
+ *
+ * Creates and returns the DOM node(s) for the shape in
+ * the given container. This implementation invokes
+ * <createSvg>, <createHtml> or <createVml> depending
+ * on the <dialect> and style settings.
+ *
+ * Parameters:
+ *
+ * container - DOM node that will contain the shape.
+ */
+mxShape.prototype.create = function(container)
+{
+	var node = null;
+	
+	if (container != null && container.ownerSVGElement != null)
+	{
+		node = this.createSvg(container);
+	}
+	else if (document.documentMode == 8 || !mxClient.IS_VML ||
+		(this.dialect != mxConstants.DIALECT_VML && this.isHtmlAllowed()))
+	{
+		node = this.createHtml(container);
+	}
+	else
+	{
+		node = this.createVml(container);
+	}
+	
+	return node;
+};
+
+/**
+ * Function: createSvg
+ *
+ * Creates and returns the SVG node(s) to represent this shape.
+ */
+mxShape.prototype.createSvg = function()
+{
+	return document.createElementNS(mxConstants.NS_SVG, 'g');
+};
+
+/**
+ * Function: createVml
+ *
+ * Creates and returns the VML node to represent this shape.
+ */
+mxShape.prototype.createVml = function()
+{
+	var node = document.createElement(mxClient.VML_PREFIX + ':group');
+	node.style.position = 'absolute';
+	
+	return node;
+};
+
+/**
+ * Function: createHtml
+ *
+ * Creates and returns the HTML DOM node(s) to represent
+ * this shape. This implementation falls back to <createVml>
+ * so that the HTML creation is optional.
+ */
+mxShape.prototype.createHtml = function()
+{
+	var node = document.createElement('div');
+	node.style.position = 'absolute';
+	
+	return node;
+};
+
+/**
+ * Function: reconfigure
+ *
+ * Reconfigures this shape. This will update the colors etc in
+ * addition to the bounds or points.
+ */
+mxShape.prototype.reconfigure = function()
+{
+	this.redraw();
+};
+
+/**
+ * Function: redraw
+ *
+ * Creates and returns the SVG node(s) to represent this shape.
+ */
+mxShape.prototype.redraw = function()
+{
+	this.updateBoundsFromPoints();
+	
+	if (this.visible && this.checkBounds())
+	{
+		this.node.style.visibility = 'visible';
+		this.clear();
+		
+		if (this.node.nodeName == 'DIV' && (this.isHtmlAllowed() || !mxClient.IS_VML))
+		{
+			this.redrawHtmlShape();
+		}
+		else
+		{	
+			this.redrawShape();
+		}
+
+		this.updateBoundingBox();
+	}
+	else
+	{
+		this.node.style.visibility = 'hidden';
+		this.boundingBox = null;
+	}
+};
+
+/**
+ * Function: clear
+ * 
+ * Removes all child nodes and resets all CSS.
+ */
+mxShape.prototype.clear = function()
+{
+	if (this.node.ownerSVGElement != null)
+	{
+		while (this.node.lastChild != null)
+		{
+			this.node.removeChild(this.node.lastChild);
+		}
+	}
+	else
+	{
+		this.node.style.cssText = 'position:absolute;' + ((this.cursor != null) ?
+			('cursor:' + this.cursor + ';') : '');
+		this.node.innerHTML = '';
+	}
+};
+
+/**
+ * Function: updateBoundsFromPoints
+ * 
+ * Updates the bounds based on the points.
+ */
+mxShape.prototype.updateBoundsFromPoints = function()
+{
+	var pts = this.points;
+	
+	if (pts != null && pts.length > 0 && pts[0] != null)
+	{
+		this.bounds = new mxRectangle(Number(pts[0].x), Number(pts[0].y), 1, 1);
+		
+		for (var i = 1; i < this.points.length; i++)
+		{
+			if (pts[i] != null)
+			{
+				this.bounds.add(new mxRectangle(Number(pts[i].x), Number(pts[i].y), 1, 1));
+			}
+		}
+	}
+};
+
+/**
+ * Function: getLabelBounds
+ * 
+ * Returns the <mxRectangle> for the label bounds of this shape, based on the
+ * given scaled and translated bounds of the shape. This method should not
+ * change the rectangle in-place. This implementation returns the given rect.
+ */
+mxShape.prototype.getLabelBounds = function(rect)
+{
+	var d = mxUtils.getValue(this.style, mxConstants.STYLE_DIRECTION, mxConstants.DIRECTION_EAST);
+	var bounds = rect;
+	
+	// Normalizes argument for getLabelMargins hook
+	if (d != mxConstants.DIRECTION_SOUTH && d != mxConstants.DIRECTION_NORTH &&
+		this.state != null && this.state.text != null &&
+		this.state.text.isPaintBoundsInverted())
+	{
+		bounds = bounds.clone();
+		var tmp = bounds.width;
+		bounds.width = bounds.height;
+		bounds.height = tmp;
+	}
+		
+	var m = this.getLabelMargins(bounds);
+	
+	if (m != null)
+	{
+		var flipH = mxUtils.getValue(this.style, mxConstants.STYLE_FLIPH, false) == '1';
+		var flipV = mxUtils.getValue(this.style, mxConstants.STYLE_FLIPV, false) == '1';
+		
+		// Handles special case for vertical labels
+		if (this.state != null && this.state.text != null &&
+			this.state.text.isPaintBoundsInverted())
+		{
+			var tmp = m.x;
+			m.x = m.height;
+			m.height = m.width;
+			m.width = m.y;
+			m.y = tmp;
+
+			tmp = flipH;
+			flipH = flipV;
+			flipV = tmp;
+		}
+		
+		return mxUtils.getDirectedBounds(rect, m, this.style, flipH, flipV);
+	}
+	
+	return rect;
+};
+
+/**
+ * Function: getLabelMargins
+ * 
+ * Returns the scaled top, left, bottom and right margin to be used for
+ * computing the label bounds as an <mxRectangle>, where the bottom and right
+ * margin are defined in the width and height of the rectangle, respectively.
+ */
+mxShape.prototype.getLabelMargins= function(rect)
+{
+	return null;
+};
+
+/**
+ * Function: checkBounds
+ * 
+ * Returns true if the bounds are not null and all of its variables are numeric.
+ */
+mxShape.prototype.checkBounds = function()
+{
+	return (!isNaN(this.scale) && isFinite(this.scale) && this.scale > 0 &&
+			this.bounds != null && !isNaN(this.bounds.x) && !isNaN(this.bounds.y) &&
+			!isNaN(this.bounds.width) && !isNaN(this.bounds.height) &&
+			this.bounds.width > 0 && this.bounds.height > 0);
+};
+
+/**
+ * Function: createVmlGroup
+ *
+ * Returns the temporary element used for rendering in IE8 standards mode.
+ */
+mxShape.prototype.createVmlGroup = function()
+{
+	var node = document.createElement(mxClient.VML_PREFIX + ':group');
+	node.style.position = 'absolute';
+	node.style.width = this.node.style.width;
+	node.style.height = this.node.style.height;
+	
+	return node;
+};
+
+/**
+ * Function: redrawShape
+ *
+ * Updates the SVG or VML shape.
+ */
+mxShape.prototype.redrawShape = function()
+{
+	var canvas = this.createCanvas();
+	
+	if (canvas != null)
+	{
+		// Specifies if events should be handled
+		canvas.pointerEvents = this.pointerEvents;
+	
+		this.paint(canvas);
+	
+		if (this.node != canvas.root)
+		{
+			// Forces parsing in IE8 standards mode - slow! avoid
+			this.node.insertAdjacentHTML('beforeend', canvas.root.outerHTML);
+		}
+	
+		if (this.node.nodeName == 'DIV' && document.documentMode == 8)
+		{
+			// Makes DIV transparent to events for IE8 in IE8 standards
+			// mode (Note: Does not work for IE9 in IE8 standards mode
+			// and not for IE11 in enterprise mode)
+			this.node.style.filter = '';
+			
+			// Adds event transparency in IE8 standards
+			mxUtils.addTransparentBackgroundFilter(this.node);
+		}
+		
+		this.destroyCanvas(canvas);
+	}
+};
+
+/**
+ * Function: createCanvas
+ * 
+ * Creates a new canvas for drawing this shape. May return null.
+ */
+mxShape.prototype.createCanvas = function()
+{
+	var canvas = null;
+	
+	// LATER: Check if reusing existing DOM nodes improves performance
+	if (this.node.ownerSVGElement != null)
+	{
+		canvas = this.createSvgCanvas();
+	}
+	else if (mxClient.IS_VML)
+	{
+		this.updateVmlContainer();
+		canvas = this.createVmlCanvas();
+	}
+	
+	if (canvas != null && this.outline)
+	{
+		canvas.setStrokeWidth(this.strokewidth);
+		canvas.setStrokeColor(this.stroke);
+		
+		if (this.isDashed != null)
+		{
+			canvas.setDashed(this.isDashed);
+		}
+		
+		canvas.setStrokeWidth = function() {};
+		canvas.setStrokeColor = function() {};
+		canvas.setFillColor = function() {};
+		canvas.setGradient = function() {};
+		canvas.setDashed = function() {};
+	}
+
+	return canvas;
+};
+
+/**
+ * Function: createSvgCanvas
+ * 
+ * Creates and returns an <mxSvgCanvas2D> for rendering this shape.
+ */
+mxShape.prototype.createSvgCanvas = function()
+{
+	var canvas = new mxSvgCanvas2D(this.node, false);
+	canvas.strokeTolerance = (this.pointerEvents) ? this.svgStrokeTolerance : 0;
+	canvas.pointerEventsValue = this.svgPointerEvents;
+	canvas.blockImagePointerEvents = mxClient.IS_FF;
+	var off = this.getSvgScreenOffset();
+
+	if (off != 0)
+	{
+		this.node.setAttribute('transform', 'translate(' + off + ',' + off + ')');
+	}
+	else
+	{
+		this.node.removeAttribute('transform');
+	}
+	
+	if (!this.antiAlias)
+	{
+		// Rounds all numbers in the SVG output to integers
+		canvas.format = function(value)
+		{
+			return Math.round(parseFloat(value));
+		};
+	}
+	
+	return canvas;
+};
+
+/**
+ * Function: createVmlCanvas
+ * 
+ * Creates and returns an <mxVmlCanvas2D> for rendering this shape.
+ */
+mxShape.prototype.createVmlCanvas = function()
+{
+	// Workaround for VML rendering bug in IE8 standards mode
+	var node = (document.documentMode == 8 && this.isParseVml()) ? this.createVmlGroup() : this.node;
+	var canvas = new mxVmlCanvas2D(node, false);
+	
+	if (node.tagUrn != '')
+	{
+		var w = Math.max(1, Math.round(this.bounds.width));
+		var h = Math.max(1, Math.round(this.bounds.height));
+		node.coordsize = (w * this.vmlScale) + ',' + (h * this.vmlScale);
+		canvas.scale(this.vmlScale);
+		canvas.vmlScale = this.vmlScale;
+	}
+
+	// Painting relative to top, left shape corner
+	var s = this.scale;
+	canvas.translate(-Math.round(this.bounds.x / s), -Math.round(this.bounds.y / s));
+	
+	return canvas;
+};
+
+/**
+ * Function: updateVmlContainer
+ * 
+ * Updates the bounds of the VML container.
+ */
+mxShape.prototype.updateVmlContainer = function()
+{
+	this.node.style.left = Math.round(this.bounds.x) + 'px';
+	this.node.style.top = Math.round(this.bounds.y) + 'px';
+	var w = Math.max(1, Math.round(this.bounds.width));
+	var h = Math.max(1, Math.round(this.bounds.height));
+	this.node.style.width = w + 'px';
+	this.node.style.height = h + 'px';
+	this.node.style.overflow = 'visible';
+};
+
+/**
+ * Function: redrawHtml
+ *
+ * Allow optimization by replacing VML with HTML.
+ */
+mxShape.prototype.redrawHtmlShape = function()
+{
+	// LATER: Refactor methods
+	this.updateHtmlBounds(this.node);
+	this.updateHtmlFilters(this.node);
+	this.updateHtmlColors(this.node);
+};
+
+/**
+ * Function: updateHtmlFilters
+ *
+ * Allow optimization by replacing VML with HTML.
+ */
+mxShape.prototype.updateHtmlFilters = function(node)
+{
+	var f = '';
+	
+	if (this.opacity < 100)
+	{
+		f += 'alpha(opacity=' + (this.opacity) + ')';
+	}
+	
+	if (this.isShadow)
+	{
+		// FIXME: Cannot implement shadow transparency with filter
+		f += 'progid:DXImageTransform.Microsoft.dropShadow (' +
+			'OffX=\'' + Math.round(mxConstants.SHADOW_OFFSET_X * this.scale) + '\', ' +
+			'OffY=\'' + Math.round(mxConstants.SHADOW_OFFSET_Y * this.scale) + '\', ' +
+			'Color=\'' + mxConstants.VML_SHADOWCOLOR + '\')';
+	}
+	
+	if (this.fill != null && this.fill != mxConstants.NONE && this.gradient && this.gradient != mxConstants.NONE)
+	{
+		var start = this.fill;
+		var end = this.gradient;
+		var type = '0';
+		
+		var lookup = {east:0,south:1,west:2,north:3};
+		var dir = (this.direction != null) ? lookup[this.direction] : 0;
+		
+		if (this.gradientDirection != null)
+		{
+			dir = mxUtils.mod(dir + lookup[this.gradientDirection] - 1, 4);
+		}
+
+		if (dir == 1)
+		{
+			type = '1';
+			var tmp = start;
+			start = end;
+			end = tmp;
+		}
+		else if (dir == 2)
+		{
+			var tmp = start;
+			start = end;
+			end = tmp;
+		}
+		else if (dir == 3)
+		{
+			type = '1';
+		}
+		
+		f += 'progid:DXImageTransform.Microsoft.gradient(' +
+			'startColorStr=\'' + start + '\', endColorStr=\'' + end +
+			'\', gradientType=\'' + type + '\')';
+	}
+
+	node.style.filter = f;
+};
+
+/**
+ * Function: mixedModeHtml
+ *
+ * Allow optimization by replacing VML with HTML.
+ */
+mxShape.prototype.updateHtmlColors = function(node)
+{
+	var color = this.stroke;
+	
+	if (color != null && color != mxConstants.NONE)
+	{
+		node.style.borderColor = color;
+
+		if (this.isDashed)
+		{
+			node.style.borderStyle = 'dashed';
+		}
+		else if (this.strokewidth > 0)
+		{
+			node.style.borderStyle = 'solid';
+		}
+
+		node.style.borderWidth = Math.max(1, Math.ceil(this.strokewidth * this.scale)) + 'px';
+	}
+	else
+	{
+		node.style.borderWidth = '0px';
+	}
+
+	color = (this.outline) ? null : this.fill;
+	
+	if (color != null && color != mxConstants.NONE)
+	{
+		node.style.backgroundColor = color;
+		node.style.backgroundImage = 'none';
+	}
+	else if (this.pointerEvents)
+	{
+		 node.style.backgroundColor = 'transparent';
+	}
+	else if (document.documentMode == 8)
+	{
+		mxUtils.addTransparentBackgroundFilter(node);
+	}
+	else
+	{
+		this.setTransparentBackgroundImage(node);
+	}
+};
+
+/**
+ * Function: mixedModeHtml
+ *
+ * Allow optimization by replacing VML with HTML.
+ */
+mxShape.prototype.updateHtmlBounds = function(node)
+{
+	var sw = (document.documentMode >= 9) ? 0 : Math.ceil(this.strokewidth * this.scale);
+	node.style.borderWidth = Math.max(1, sw) + 'px';
+	node.style.overflow = 'hidden';
+	
+	node.style.left = Math.round(this.bounds.x - sw / 2) + 'px';
+	node.style.top = Math.round(this.bounds.y - sw / 2) + 'px';
+
+	if (document.compatMode == 'CSS1Compat')
+	{
+		sw = -sw;
+	}
+	
+	node.style.width = Math.round(Math.max(0, this.bounds.width + sw)) + 'px';
+	node.style.height = Math.round(Math.max(0, this.bounds.height + sw)) + 'px';
+};
+
+/**
+ * Function: destroyCanvas
+ * 
+ * Destroys the given canvas which was used for drawing. This implementation
+ * increments the reference counts on all shared gradients used in the canvas.
+ */
+mxShape.prototype.destroyCanvas = function(canvas)
+{
+	// Manages reference counts
+	if (canvas instanceof mxSvgCanvas2D)
+	{
+		// Increments ref counts
+		for (var key in canvas.gradients)
+		{
+			var gradient = canvas.gradients[key];
+			
+			if (gradient != null)
+			{
+				gradient.mxRefCount = (gradient.mxRefCount || 0) + 1;
+			}
+		}
+		
+		this.releaseSvgGradients(this.oldGradients);
+		this.oldGradients = canvas.gradients;
+	}
+};
+
+/**
+ * Function: paint
+ * 
+ * Generic rendering code.
+ */
+mxShape.prototype.paint = function(c)
+{
+	// Scale is passed-through to canvas
+	var s = this.scale;
+	var x = this.bounds.x / s;
+	var y = this.bounds.y / s;
+	var w = this.bounds.width / s;
+	var h = this.bounds.height / s;
+
+	if (this.isPaintBoundsInverted())
+	{
+		var t = (w - h) / 2;
+		x += t;
+		y -= t;
+		var tmp = w;
+		w = h;
+		h = tmp;
+	}
+	
+	this.updateTransform(c, x, y, w, h);
+	this.configureCanvas(c, x, y, w, h);
+
+	// Adds background rectangle to capture events
+	var bg = null;
+	
+	if ((this.stencil == null && this.points == null && this.shapePointerEvents) ||
+		(this.stencil != null && this.stencilPointerEvents))
+	{
+		var bb = this.createBoundingBox();
+		
+		if (this.dialect == mxConstants.DIALECT_SVG)
+		{
+			bg = this.createTransparentSvgRectangle(bb.x, bb.y, bb.width, bb.height);
+			this.node.appendChild(bg);
+		}
+		else
+		{
+			var rect = c.createRect('rect', bb.x / s, bb.y / s, bb.width / s, bb.height / s);
+			rect.appendChild(c.createTransparentFill());
+			rect.stroked = 'false';
+			c.root.appendChild(rect);
+		}
+	}
+
+	if (this.stencil != null)
+	{
+		this.stencil.drawShape(c, this, x, y, w, h);
+	}
+	else
+	{
+		// Stencils have separate strokewidth
+		c.setStrokeWidth(this.strokewidth);
+		
+		if (this.points != null)
+		{
+			// Paints edge shape
+			var pts = [];
+			
+			for (var i = 0; i < this.points.length; i++)
+			{
+				if (this.points[i] != null)
+				{
+					pts.push(new mxPoint(this.points[i].x / s, this.points[i].y / s));
+				}
+			}
+
+			this.paintEdgeShape(c, pts);
+		}
+		else
+		{
+			// Paints vertex shape
+			this.paintVertexShape(c, x, y, w, h);
+		}
+	}
+	
+	if (bg != null && c.state != null && c.state.transform != null)
+	{
+		bg.setAttribute('transform', c.state.transform);
+	}
+};
+
+/**
+ * Function: configureCanvas
+ * 
+ * Sets the state of the canvas for drawing the shape.
+ */
+mxShape.prototype.configureCanvas = function(c, x, y, w, h)
+{
+	var dash = null;
+	
+	if (this.style != null)
+	{
+		dash = this.style['dashPattern'];		
+	}
+
+	c.setAlpha(this.opacity / 100);
+	c.setFillAlpha(this.fillOpacity / 100);
+	c.setStrokeAlpha(this.strokeOpacity / 100);
+
+	// Sets alpha, colors and gradients
+	if (this.isShadow != null)
+	{
+		c.setShadow(this.isShadow);
+	}
+	
+	// Dash pattern
+	if (this.isDashed != null)
+	{
+		c.setDashed(this.isDashed, (this.style != null) ?
+			mxUtils.getValue(this.style, mxConstants.STYLE_FIX_DASH, false) == 1 : false);
+	}
+
+	if (dash != null)
+	{
+		c.setDashPattern(dash);
+	}
+
+	if (this.fill != null && this.fill != mxConstants.NONE && this.gradient && this.gradient != mxConstants.NONE)
+	{
+		var b = this.getGradientBounds(c, x, y, w, h);
+		c.setGradient(this.fill, this.gradient, b.x, b.y, b.width, b.height, this.gradientDirection);
+	}
+	else
+	{
+		c.setFillColor(this.fill);
+	}
+
+	c.setStrokeColor(this.stroke);
+};
+
+/**
+ * Function: getGradientBounds
+ * 
+ * Returns the bounding box for the gradient box for this shape.
+ */
+mxShape.prototype.getGradientBounds = function(c, x, y, w, h)
+{
+	return new mxRectangle(x, y, w, h);
+};
+
+/**
+ * Function: updateTransform
+ * 
+ * Sets the scale and rotation on the given canvas.
+ */
+mxShape.prototype.updateTransform = function(c, x, y, w, h)
+{
+	// NOTE: Currently, scale is implemented in state and canvas. This will
+	// move to canvas in a later version, so that the states are unscaled
+	// and untranslated and do not need an update after zooming or panning.
+	c.scale(this.scale);
+	c.rotate(this.getShapeRotation(), this.flipH, this.flipV, x + w / 2, y + h / 2);
+};
+
+/**
+ * Function: paintVertexShape
+ * 
+ * Paints the vertex shape.
+ */
+mxShape.prototype.paintVertexShape = function(c, x, y, w, h)
+{
+	this.paintBackground(c, x, y, w, h);
+	c.setShadow(false);
+	this.paintForeground(c, x, y, w, h);
+};
+
+/**
+ * Function: paintBackground
+ * 
+ * Hook for subclassers. This implementation is empty.
+ */
+mxShape.prototype.paintBackground = function(c, x, y, w, h) { };
+
+/**
+ * Function: paintForeground
+ * 
+ * Hook for subclassers. This implementation is empty.
+ */
+mxShape.prototype.paintForeground = function(c, x, y, w, h) { };
+
+/**
+ * Function: paintEdgeShape
+ * 
+ * Hook for subclassers. This implementation is empty.
+ */
+mxShape.prototype.paintEdgeShape = function(c, pts) { };
+
+/**
+ * Function: getArcSize
+ * 
+ * Returns the arc size for the given dimension.
+ */
+mxShape.prototype.getArcSize = function(w, h)
+{
+	var r = 0;
+	
+	if (mxUtils.getValue(this.style, mxConstants.STYLE_ABSOLUTE_ARCSIZE, 0) == '1')
+	{
+		r = Math.min(w / 2, Math.min(h / 2, mxUtils.getValue(this.style,
+			mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2));
+	}
+	else
+	{
+		var f = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE,
+			mxConstants.RECTANGLE_ROUNDING_FACTOR * 100) / 100;
+		r = Math.min(w * f, h * f);
+	}
+	
+	return r;
+};
+
+/**
+ * Function: paintGlassEffect
+ * 
+ * Paints the glass gradient effect.
+ */
+mxShape.prototype.paintGlassEffect = function(c, x, y, w, h, arc)
+{
+	var sw = Math.ceil(this.strokewidth / 2);
+	var size = 0.4;
+	
+	c.setGradient('#ffffff', '#ffffff', x, y, w, h * 0.6, 'south', 0.9, 0.1);
+	c.begin();
+	arc += 2 * sw;
+		
+	if (this.isRounded)
+	{
+		c.moveTo(x - sw + arc, y - sw);
+		c.quadTo(x - sw, y - sw, x - sw, y - sw + arc);
+		c.lineTo(x - sw, y + h * size);
+		c.quadTo(x + w * 0.5, y + h * 0.7, x + w + sw, y + h * size);
+		c.lineTo(x + w + sw, y - sw + arc);
+		c.quadTo(x + w + sw, y - sw, x + w + sw - arc, y - sw);
+	}
+	else
+	{
+		c.moveTo(x - sw, y - sw);
+		c.lineTo(x - sw, y + h * size);
+		c.quadTo(x + w * 0.5, y + h * 0.7, x + w + sw, y + h * size);
+		c.lineTo(x + w + sw, y - sw);
+	}
+	
+	c.close();
+	c.fill();
+};
+
+/**
+ * Function: addPoints
+ * 
+ * Paints the given points with rounded corners.
+ */
+mxShape.prototype.addPoints = function(c, pts, rounded, arcSize, close, exclude, initialMove)
+{
+	if (pts != null && pts.length > 0)
+	{
+		initialMove = (initialMove != null) ? initialMove : true;
+		var pe = pts[pts.length - 1];
+		
+		// Adds virtual waypoint in the center between start and end point
+		if (close && rounded)
+		{
+			pts = pts.slice();
+			var p0 = pts[0];
+			var wp = new mxPoint(pe.x + (p0.x - pe.x) / 2, pe.y + (p0.y - pe.y) / 2);
+			pts.splice(0, 0, wp);
+		}
+	
+		var pt = pts[0];
+		var i = 1;
+	
+		// Draws the line segments
+		if (initialMove)
+		{
+			c.moveTo(pt.x, pt.y);
+		}
+		else
+		{
+			c.lineTo(pt.x, pt.y);
+		}
+		
+		while (i < ((close) ? pts.length : pts.length - 1))
+		{
+			var tmp = pts[mxUtils.mod(i, pts.length)];
+			var dx = pt.x - tmp.x;
+			var dy = pt.y - tmp.y;
+	
+			if (rounded && (dx != 0 || dy != 0) && (exclude == null || mxUtils.indexOf(exclude, i - 1) < 0))
+			{
+				// Draws a line from the last point to the current
+				// point with a spacing of size off the current point
+				// into direction of the last point
+				var dist = Math.sqrt(dx * dx + dy * dy);
+				var nx1 = dx * Math.min(arcSize, dist / 2) / dist;
+				var ny1 = dy * Math.min(arcSize, dist / 2) / dist;
+	
+				var x1 = tmp.x + nx1;
+				var y1 = tmp.y + ny1;
+				c.lineTo(x1, y1);
+	
+				// Draws a curve from the last point to the current
+				// point with a spacing of size off the current point
+				// into direction of the next point
+				var next = pts[mxUtils.mod(i + 1, pts.length)];
+				
+				// Uses next non-overlapping point
+				while (i < pts.length - 2 && Math.round(next.x - tmp.x) == 0 && Math.round(next.y - tmp.y) == 0)
+				{
+					next = pts[mxUtils.mod(i + 2, pts.length)];
+					i++;
+				}
+				
+				dx = next.x - tmp.x;
+				dy = next.y - tmp.y;
+	
+				dist = Math.max(1, Math.sqrt(dx * dx + dy * dy));
+				var nx2 = dx * Math.min(arcSize, dist / 2) / dist;
+				var ny2 = dy * Math.min(arcSize, dist / 2) / dist;
+	
+				var x2 = tmp.x + nx2;
+				var y2 = tmp.y + ny2;
+	
+				c.quadTo(tmp.x, tmp.y, x2, y2);
+				tmp = new mxPoint(x2, y2);
+			}
+			else
+			{
+				c.lineTo(tmp.x, tmp.y);
+			}
+	
+			pt = tmp;
+			i++;
+		}
+	
+		if (close)
+		{
+			c.close();
+		}
+		else
+		{
+			c.lineTo(pe.x, pe.y);
+		}
+	}
+};
+
+/**
+ * Function: resetStyles
+ * 
+ * Resets all styles.
+ */
+mxShape.prototype.resetStyles = function()
+{
+	this.initStyles();
+
+	this.spacing = 0;
+	
+	delete this.fill;
+	delete this.gradient;
+	delete this.gradientDirection;
+	delete this.stroke;
+	delete this.startSize;
+	delete this.endSize;
+	delete this.startArrow;
+	delete this.endArrow;
+	delete this.direction;
+	delete this.isShadow;
+	delete this.isDashed;
+	delete this.isRounded;
+	delete this.glass;
+};
+
+/**
+ * Function: apply
+ * 
+ * Applies the style of the given <mxCellState> to the shape. This
+ * implementation assigns the following styles to local fields:
+ * 
+ * - <mxConstants.STYLE_FILLCOLOR> => fill
+ * - <mxConstants.STYLE_GRADIENTCOLOR> => gradient
+ * - <mxConstants.STYLE_GRADIENT_DIRECTION> => gradientDirection
+ * - <mxConstants.STYLE_OPACITY> => opacity
+ * - <mxConstants.STYLE_FILL_OPACITY> => fillOpacity
+ * - <mxConstants.STYLE_STROKE_OPACITY> => strokeOpacity
+ * - <mxConstants.STYLE_STROKECOLOR> => stroke
+ * - <mxConstants.STYLE_STROKEWIDTH> => strokewidth
+ * - <mxConstants.STYLE_SHADOW> => isShadow
+ * - <mxConstants.STYLE_DASHED> => isDashed
+ * - <mxConstants.STYLE_SPACING> => spacing
+ * - <mxConstants.STYLE_STARTSIZE> => startSize
+ * - <mxConstants.STYLE_ENDSIZE> => endSize
+ * - <mxConstants.STYLE_ROUNDED> => isRounded
+ * - <mxConstants.STYLE_STARTARROW> => startArrow
+ * - <mxConstants.STYLE_ENDARROW> => endArrow
+ * - <mxConstants.STYLE_ROTATION> => rotation
+ * - <mxConstants.STYLE_DIRECTION> => direction
+ * - <mxConstants.STYLE_GLASS> => glass
+ *
+ * This keeps a reference to the <style>. If you need to keep a reference to
+ * the cell, you can override this method and store a local reference to
+ * state.cell or the <mxCellState> itself. If <outline> should be true, make
+ * sure to set it before calling this method.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> of the corresponding cell.
+ */
+mxShape.prototype.apply = function(state)
+{
+	this.state = state;
+	this.style = state.style;
+
+	if (this.style != null)
+	{
+		this.fill = mxUtils.getValue(this.style, mxConstants.STYLE_FILLCOLOR, this.fill);
+		this.gradient = mxUtils.getValue(this.style, mxConstants.STYLE_GRADIENTCOLOR, this.gradient);
+		this.gradientDirection = mxUtils.getValue(this.style, mxConstants.STYLE_GRADIENT_DIRECTION, this.gradientDirection);
+		this.opacity = mxUtils.getValue(this.style, mxConstants.STYLE_OPACITY, this.opacity);
+		this.fillOpacity = mxUtils.getValue(this.style, mxConstants.STYLE_FILL_OPACITY, this.fillOpacity);
+		this.strokeOpacity = mxUtils.getValue(this.style, mxConstants.STYLE_STROKE_OPACITY, this.strokeOpacity);
+		this.stroke = mxUtils.getValue(this.style, mxConstants.STYLE_STROKECOLOR, this.stroke);
+		this.strokewidth = mxUtils.getNumber(this.style, mxConstants.STYLE_STROKEWIDTH, this.strokewidth);
+		this.spacing = mxUtils.getValue(this.style, mxConstants.STYLE_SPACING, this.spacing);
+		this.startSize = mxUtils.getNumber(this.style, mxConstants.STYLE_STARTSIZE, this.startSize);
+		this.endSize = mxUtils.getNumber(this.style, mxConstants.STYLE_ENDSIZE, this.endSize);
+		this.startArrow = mxUtils.getValue(this.style, mxConstants.STYLE_STARTARROW, this.startArrow);
+		this.endArrow = mxUtils.getValue(this.style, mxConstants.STYLE_ENDARROW, this.endArrow);
+		this.rotation = mxUtils.getValue(this.style, mxConstants.STYLE_ROTATION, this.rotation);
+		this.direction = mxUtils.getValue(this.style, mxConstants.STYLE_DIRECTION, this.direction);
+		this.flipH = mxUtils.getValue(this.style, mxConstants.STYLE_FLIPH, 0) == 1;
+		this.flipV = mxUtils.getValue(this.style, mxConstants.STYLE_FLIPV, 0) == 1;	
+		
+		// Legacy support for stencilFlipH/V
+		if (this.stencil != null)
+		{
+			this.flipH = mxUtils.getValue(this.style, 'stencilFlipH', 0) == 1 || this.flipH;
+			this.flipV = mxUtils.getValue(this.style, 'stencilFlipV', 0) == 1 || this.flipV;
+		}
+		
+		if (this.direction == mxConstants.DIRECTION_NORTH || this.direction == mxConstants.DIRECTION_SOUTH)
+		{
+			var tmp = this.flipH;
+			this.flipH = this.flipV;
+			this.flipV = tmp;
+		}
+
+		this.isShadow = mxUtils.getValue(this.style, mxConstants.STYLE_SHADOW, this.isShadow) == 1;
+		this.isDashed = mxUtils.getValue(this.style, mxConstants.STYLE_DASHED, this.isDashed) == 1;
+		this.isRounded = mxUtils.getValue(this.style, mxConstants.STYLE_ROUNDED, this.isRounded) == 1;
+		this.glass = mxUtils.getValue(this.style, mxConstants.STYLE_GLASS, this.glass) == 1;
+		
+		if (this.fill == mxConstants.NONE)
+		{
+			this.fill = null;
+		}
+
+		if (this.gradient == mxConstants.NONE)
+		{
+			this.gradient = null;
+		}
+
+		if (this.stroke == mxConstants.NONE)
+		{
+			this.stroke = null;
+		}
+	}
+};
+
+/**
+ * Function: setCursor
+ * 
+ * Sets the cursor on the given shape.
+ *
+ * Parameters:
+ *
+ * cursor - The cursor to be used.
+ */
+mxShape.prototype.setCursor = function(cursor)
+{
+	if (cursor == null)
+	{
+		cursor = '';
+	}
+	
+	this.cursor = cursor;
+
+	if (this.node != null)
+	{
+		this.node.style.cursor = cursor;
+	}
+};
+
+/**
+ * Function: getCursor
+ * 
+ * Returns the current cursor.
+ */
+mxShape.prototype.getCursor = function()
+{
+	return this.cursor;
+};
+
+/**
+ * Function: updateBoundingBox
+ *
+ * Updates the <boundingBox> for this shape using <createBoundingBox> and
+ * <augmentBoundingBox> and stores the result in <boundingBox>.
+ */
+mxShape.prototype.updateBoundingBox = function()
+{
+	// Tries to get bounding box from SVG subsystem
+	// LATER: Use getBoundingClientRect for fallback in VML
+	if (this.useSvgBoundingBox && this.node != null && this.node.ownerSVGElement != null)
+	{
+		try
+		{
+			var b = this.node.getBBox();
+	
+			if (b.width > 0 && b.height > 0)
+			{
+				this.boundingBox = new mxRectangle(b.x, b.y, b.width, b.height);
+				
+				// Adds strokeWidth
+				this.boundingBox.grow(this.strokewidth * this.scale / 2);
+				
+				return;
+			}
+		}
+		catch(e)
+		{
+			// fallback to code below
+		}
+	}
+
+	if (this.bounds != null)
+	{
+		var bbox = this.createBoundingBox();
+		
+		if (bbox != null)
+		{
+			this.augmentBoundingBox(bbox);
+			var rot = this.getShapeRotation();
+			
+			if (rot != 0)
+			{
+				bbox = mxUtils.getBoundingBox(bbox, rot);
+			}
+		}
+
+		this.boundingBox = bbox;
+	}
+};
+
+/**
+ * Function: createBoundingBox
+ *
+ * Returns a new rectangle that represents the bounding box of the bare shape
+ * with no shadows or strokewidths.
+ */
+mxShape.prototype.createBoundingBox = function()
+{
+	var bb = this.bounds.clone();
+
+	if ((this.stencil != null && (this.direction == mxConstants.DIRECTION_NORTH ||
+		this.direction == mxConstants.DIRECTION_SOUTH)) || this.isPaintBoundsInverted())
+	{
+		bb.rotate90();
+	}
+	
+	return bb;
+};
+
+/**
+ * Function: augmentBoundingBox
+ *
+ * Augments the bounding box with the strokewidth and shadow offsets.
+ */
+mxShape.prototype.augmentBoundingBox = function(bbox)
+{
+	if (this.isShadow)
+	{
+		bbox.width += Math.ceil(mxConstants.SHADOW_OFFSET_X * this.scale);
+		bbox.height += Math.ceil(mxConstants.SHADOW_OFFSET_Y * this.scale);
+	}
+	
+	// Adds strokeWidth
+	bbox.grow(this.strokewidth * this.scale / 2);
+};
+
+/**
+ * Function: isPaintBoundsInverted
+ * 
+ * Returns true if the bounds should be inverted.
+ */
+mxShape.prototype.isPaintBoundsInverted = function()
+{
+	// Stencil implements inversion via aspect
+	return this.stencil == null && (this.direction == mxConstants.DIRECTION_NORTH ||
+			this.direction == mxConstants.DIRECTION_SOUTH);
+};
+
+/**
+ * Function: getRotation
+ * 
+ * Returns the rotation from the style.
+ */
+mxShape.prototype.getRotation = function()
+{
+	return (this.rotation != null) ? this.rotation : 0;
+};
+
+/**
+ * Function: getTextRotation
+ * 
+ * Returns the rotation for the text label.
+ */
+mxShape.prototype.getTextRotation = function()
+{
+	var rot = this.getRotation();
+	
+	if (mxUtils.getValue(this.style, mxConstants.STYLE_HORIZONTAL, 1) != 1)
+	{
+		rot += mxText.prototype.verticalTextRotation;
+	}
+	
+	return rot;
+};
+
+/**
+ * Function: getShapeRotation
+ * 
+ * Returns the actual rotation of the shape.
+ */
+mxShape.prototype.getShapeRotation = function()
+{
+	var rot = this.getRotation();
+	
+	if (this.direction != null)
+	{
+		if (this.direction == mxConstants.DIRECTION_NORTH)
+		{
+			rot += 270;
+		}
+		else if (this.direction == mxConstants.DIRECTION_WEST)
+		{
+			rot += 180;
+		}
+		else if (this.direction == mxConstants.DIRECTION_SOUTH)
+		{
+			rot += 90;
+		}
+	}
+	
+	return rot;
+};
+
+/**
+ * Function: createTransparentSvgRectangle
+ * 
+ * Adds a transparent rectangle that catches all events.
+ */
+mxShape.prototype.createTransparentSvgRectangle = function(x, y, w, h)
+{
+	var rect = document.createElementNS(mxConstants.NS_SVG, 'rect');
+	rect.setAttribute('x', x);
+	rect.setAttribute('y', y);
+	rect.setAttribute('width', w);
+	rect.setAttribute('height', h);
+	rect.setAttribute('fill', 'none');
+	rect.setAttribute('stroke', 'none');
+	rect.setAttribute('pointer-events', 'all');
+	
+	return rect;
+};
+
+/**
+ * Function: setTransparentBackgroundImage
+ * 
+ * Sets a transparent background CSS style to catch all events.
+ * 
+ * Paints the line shape.
+ */
+mxShape.prototype.setTransparentBackgroundImage = function(node)
+{
+	node.style.backgroundImage = 'url(\'' + mxClient.imageBasePath + '/transparent.gif\')';
+};
+
+/**
+ * Function: releaseSvgGradients
+ * 
+ * Paints the line shape.
+ */
+mxShape.prototype.releaseSvgGradients = function(grads)
+{
+	if (grads != null)
+	{
+		for (var key in grads)
+		{
+			var gradient = grads[key];
+			
+			if (gradient != null)
+			{
+				gradient.mxRefCount = (gradient.mxRefCount || 0) - 1;
+				
+				if (gradient.mxRefCount == 0 && gradient.parentNode != null)
+				{
+					gradient.parentNode.removeChild(gradient);
+				}
+			}
+		}
+	}
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the shape by removing it from the DOM and releasing the DOM
+ * node associated with the shape using <mxEvent.release>.
+ */
+mxShape.prototype.destroy = function()
+{
+	if (this.node != null)
+	{
+		mxEvent.release(this.node);
+		
+		if (this.node.parentNode != null)
+		{
+			this.node.parentNode.removeChild(this.node);
+		}
+		
+		this.node = null;
+	}
+	
+	// Decrements refCount and removes unused
+	this.releaseSvgGradients(this.oldGradients);
+	this.oldGradients = null;
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/shape/mxStencil.js b/airavata-kubernetes/workflow-composer/src/js/shape/mxStencil.js
new file mode 100644
index 0000000..3622cd8
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/shape/mxStencil.js
@@ -0,0 +1,761 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxStencil
+ *
+ * Implements a generic shape which is based on a XML node as a description.
+ * 
+ * shape:
+ * 
+ * The outer element is *shape*, that has attributes:
+ * 
+ * - "name", string, required. The stencil name that uniquely identifies the shape.
+ * - "w" and "h" are optional decimal view bounds. This defines your co-ordinate
+ * system for the graphics operations in the shape. The default is 100,100.
+ * - "aspect", optional string. Either "variable", the default, or "fixed". Fixed
+ * means always render the shape with the aspect ratio defined by the ratio w/h.
+ * Variable causes the ratio to match that of the geometry of the current vertex.
+ * - "strokewidth", optional string. Either an integer or the string "inherit".
+ * "inherit" indicates that the strokeWidth of the cell is only changed on scaling,
+ * not on resizing. Default is "1".
+ * If numeric values are used, the strokeWidth of the cell is changed on both
+ * scaling and resizing and the value defines the multiple that is applied to
+ * the width.
+ * 
+ * connections:
+ * 
+ * If you want to define specific fixed connection points on the shape use the
+ * *connections* element. Each *constraint* element within connections defines
+ * a fixed connection point on the shape. Constraints have attributes:
+ * 
+ * - "perimeter", required. 1 or 0. 0 sets the connection point where specified
+ * by x,y. 1 Causes the position of the connection point to be extrapolated from
+ * the center of the shape, through x,y to the point of intersection with the
+ * perimeter of the shape.
+ * - "x" and "y" are the position of the fixed point relative to the bounds of
+ * the shape. They can be automatically adjusted if perimeter=1. So, (0,0) is top
+ * left, (0.5,0.5) the center, (1,0.5) the center of the right hand edge of the
+ * bounds, etc. Values may be less than 0 or greater than 1 to be positioned
+ * outside of the shape.
+ * - "name", optional string. A unique identifier for the port on the shape.
+ * 
+ * background and foreground:
+ * 
+ * The path of the graphics drawing is split into two elements, *foreground* and
+ * *background*. The split is to define which part any shadow applied to the shape
+ * is derived from (the background). This, generally, means the background is the
+ * line tracing of the outside of the shape, but not always.
+ * 
+ * Any stroke, fill or fillstroke of a background must be the first element of the
+ * foreground element, they must not be used within *background*. If the background
+ * is empty, this is not required.
+ * 
+ * Because the background cannot have any fill or stroke, it can contain only one
+ * *path*, *rect*, *roundrect* or *ellipse* element (or none). It can also not
+ * include *image*, *text* or *include-shape*.
+ * 
+ * Note that the state, styling and drawing in mxGraph stencils is very close in
+ * design to that of HTML 5 canvas. Tutorials on this subject, if you're not
+ * familiar with the topic, will give a good high-level introduction to the
+ * concepts used.
+ * 
+ * State:
+ * 
+ * Rendering within the foreground and background elements has the concept of
+ * state. There are two types of operations other than state save/load, styling
+ * and drawing. The styling operations change the current state, so you can save
+ * the current state with <save/> and pull the last saved state from the state
+ * stack using <restore/>.
+ * 
+ * Styling:
+ * 
+ * The elements that change colors within the current state all take a hash
+ * prefixed hex color code ("#FFEA80").
+ * 
+ * - *strokecolor*, this sets the color that drawing paths will be rendered in
+ * when a stroke or fillstroke command is issued.
+ * - *fillcolor*, this sets the color that the inside of closed paths will be
+ * rendered in when a fill or fillstroke command is issued.
+ * - *fontcolor*, this sets the color that fonts are rendered in when text is drawn.
+ * 
+ * *alpha* defines the degree of transparency used between 1.0 for fully opaque
+ * and 0.0 for fully transparent.
+ * 
+ * *strokewidth* defines the integer thickness of drawing elements rendered by
+ * stroking. Use fixed="1" to apply the value as-is, without scaling.
+ * 
+ * *dashed* is "1" for dashing enabled and "0" for disabled.
+ * 
+ * When *dashed* is enabled the current dash pattern, defined by *dashpattern*,
+ * is used on strokes. dashpattern is a sequence of space separated "on, off"
+ * lengths that define what distance to paint the stroke for, then what distance
+ * to paint nothing for, repeat... The default is "3 3". You could define a more
+ * complex pattern with "5 3 2 6", for example. Generally, it makes sense to have
+ * an even number of elements in the dashpattern, but that's not required.
+ * 
+ * *linejoin*, *linecap* and *miterlimit* are best explained by the Mozilla page
+ * on Canvas styling (about halfway down). The values are all the same except we
+ * use "flat" for linecap, instead of Canvas' "butt".
+ * 
+ * For font styling there are.
+ * 
+ * - *fontsize*, an integer,
+ * - *fontstyle*, an ORed bit pattern of bold (1), italic (2) and underline (4),
+ * i.e bold underline is "5".
+ * - *fontfamily*, is a string defining the typeface to be used.
+ * 
+ * Drawing:
+ * 
+ * Most drawing is contained within a *path* element. Again, the graphic
+ * primitives are very similar to that of HTML 5 canvas.
+ * 
+ * - *move* to attributes required decimals (x,y).
+ * - *line* to attributes required decimals (x,y).
+ * - *quad* to required decimals (x2,y2) via control point required decimals
+ * (x1,y1).
+ * - *curve* to required decimals (x3,y3), via control points required decimals
+ * (x1,y1) and (x2,y2).
+ * - *arc*, this doesn't follow the HTML Canvas signatures, instead it's a copy
+ * of the SVG arc command. The SVG specification documentation gives the best
+ * description of its behaviors. The attributes are named identically, they are
+ * decimals and all required.
+ * - *close* ends the current subpath and causes an automatic straight line to
+ * be drawn from the current point to the initial point of the current subpath.
+ * 
+ * Complex drawing:
+ * 
+ * In addition to the graphics primitive operations there are non-primitive
+ * operations. These provide an easy method to draw some basic shapes.
+ * 
+ * - *rect*, attributes "x", "y", "w", "h", all required decimals
+ * - *roundrect*, attributes "x", "y", "w", "h", all required decimals. Also
+ * "arcsize" an optional decimal attribute defining how large, the corner curves
+ * are.
+ * - *ellipse*, attributes "x", "y", "w", "h", all required decimals.
+ * 
+ * Note that these 3 shapes and all paths must be followed by either a fill,
+ * stroke, or fillstroke.
+ * 
+ * Text:
+ * 
+ * *text* elements have the following attributes.
+ * 
+ * - "str", the text string to display, required.
+ * - "x" and "y", the decimal location (x,y) of the text element, required.
+ * - "align", the horizontal alignment of the text element, either "left",
+ * "center" or "right". Optional, default is "left".
+ * - "valign", the vertical alignment of the text element, either "top", "middle"
+ * or "bottom". Optional, default is "top".
+ * - "localized", 0 or 1, if 1 then the "str" actually contains a key to use to
+ * fetch the value out of mxResources. Optional, default is
+ * <mxStencil.defaultLocalized>.
+ * - "vertical", 0 or 1, if 1 the label is rendered vertically (rotated by 90
+ * degrees). Optional, default is 0.
+ * - "rotation", angle in degrees (0 to 360). The angle to rotate the text by.
+ * Optional, default is 0.
+ * - "align-shape", 0 or 1, if 0 ignore the rotation of the shape when setting
+ * the text rotation. Optional, default is 1.
+ * 
+ * If <allowEval> is true, then the text content of the this element can define
+ * a function which is invoked with the shape as the only argument and returns
+ * the value for the text element (ignored if the str attribute is not null).
+ * 
+ * Images:
+ * 
+ * *image* elements can either be external URLs, or data URIs, where supported
+ * (not in IE 7-). Attributes are:
+ * 
+ * - "src", required string. Either a data URI or URL.
+ * - "x", "y", required decimals. The (x,y) position of the image.
+ * - "w", "h", required decimals. The width and height of the image.
+ * - "flipH" and "flipV", optional 0 or 1. Whether to flip the image along the
+ * horizontal/vertical axis. Default is 0 for both.
+ * 
+ * If <allowEval> is true, then the text content of the this element can define
+ * a function which is invoked with the shape as the only argument and returns
+ * the value for the image source (ignored if the src attribute is not null).
+ * 
+ * Sub-shapes:
+ * 
+ * *include-shape* allow stencils to be rendered within the current stencil by
+ * referencing the sub-stencil by name. Attributes are:
+ * 
+ * - "name", required string. The unique shape name of the stencil.
+ * - "x", "y", "w", "h", required decimals. The (x,y) position of the sub-shape
+ * and its width and height.
+ * 
+ * Constructor: mxStencil
+ * 
+ * Constructs a new generic shape by setting <desc> to the given XML node and
+ * invoking <parseDescription> and <parseConstraints>.
+ * 
+ * Parameters:
+ * 
+ * desc - XML node that contains the stencil description.
+ */
+function mxStencil(desc)
+{
+	this.desc = desc;
+	this.parseDescription();
+	this.parseConstraints();
+};
+
+/**
+ * Variable: defaultLocalized
+ * 
+ * Static global variable that specifies the default value for the localized
+ * attribute of the text element. Default is false.
+ */
+mxStencil.defaultLocalized = false;
+
+/**
+ * Function: allowEval
+ * 
+ * Static global switch that specifies if the use of eval is allowed for
+ * evaluating text content and images. Default is false. Set this to true
+ * if stencils can not contain user input.
+ */
+mxStencil.allowEval = false;
+
+/**
+ * Variable: desc
+ *
+ * Holds the XML node with the stencil description.
+ */
+mxStencil.prototype.desc = null;
+
+/**
+ * Variable: constraints
+ * 
+ * Holds an array of <mxConnectionConstraints> as defined in the shape.
+ */
+mxStencil.prototype.constraints = null;
+
+/**
+ * Variable: aspect
+ *
+ * Holds the aspect of the shape. Default is 'auto'.
+ */
+mxStencil.prototype.aspect = null;
+
+/**
+ * Variable: w0
+ *
+ * Holds the width of the shape. Default is 100.
+ */
+mxStencil.prototype.w0 = null;
+
+/**
+ * Variable: h0
+ *
+ * Holds the height of the shape. Default is 100.
+ */
+mxStencil.prototype.h0 = null;
+
+/**
+ * Variable: bgNodes
+ *
+ * Holds the XML node with the stencil description.
+ */
+mxStencil.prototype.bgNode = null;
+
+/**
+ * Variable: fgNodes
+ *
+ * Holds the XML node with the stencil description.
+ */
+mxStencil.prototype.fgNode = null;
+
+/**
+ * Variable: strokewidth
+ *
+ * Holds the strokewidth direction from the description.
+ */
+mxStencil.prototype.strokewidth = null;
+
+/**
+ * Function: parseDescription
+ *
+ * Reads <w0>, <h0>, <aspect>, <bgNodes> and <fgNodes> from <desc>.
+ */
+mxStencil.prototype.parseDescription = function()
+{
+	// LATER: Preprocess nodes for faster painting
+	this.fgNode = this.desc.getElementsByTagName('foreground')[0];
+	this.bgNode = this.desc.getElementsByTagName('background')[0];
+	this.w0 = Number(this.desc.getAttribute('w') || 100);
+	this.h0 = Number(this.desc.getAttribute('h') || 100);
+	
+	// Possible values for aspect are: variable and fixed where
+	// variable means fill the available space and fixed means
+	// use w0 and h0 to compute the aspect.
+	var aspect = this.desc.getAttribute('aspect');
+	this.aspect = (aspect != null) ? aspect : 'variable';
+	
+	// Possible values for strokewidth are all numbers and "inherit"
+	// where the inherit means take the value from the style (ie. the
+	// user-defined stroke-width). Note that the strokewidth is scaled
+	// by the minimum scaling that is used to draw the shape (sx, sy).
+	var sw = this.desc.getAttribute('strokewidth');
+	this.strokewidth = (sw != null) ? sw : '1';
+};
+
+/**
+ * Function: parseConstraints
+ *
+ * Reads the constraints from <desc> into <constraints> using
+ * <parseConstraint>.
+ */
+mxStencil.prototype.parseConstraints = function()
+{
+	var conns = this.desc.getElementsByTagName('connections')[0];
+	
+	if (conns != null)
+	{
+		var tmp = mxUtils.getChildNodes(conns);
+		
+		if (tmp != null && tmp.length > 0)
+		{
+			this.constraints = [];
+			
+			for (var i = 0; i < tmp.length; i++)
+			{
+				this.constraints.push(this.parseConstraint(tmp[i]));
+			}
+		}
+	}
+};
+
+/**
+ * Function: parseConstraint
+ *
+ * Parses the given XML node and returns its <mxConnectionConstraint>.
+ */
+mxStencil.prototype.parseConstraint = function(node)
+{
+	var x = Number(node.getAttribute('x'));
+	var y = Number(node.getAttribute('y'));
+	var perimeter = node.getAttribute('perimeter') == '1';
+	var name = node.getAttribute('name');
+	
+	return new mxConnectionConstraint(new mxPoint(x, y), perimeter, name);
+};
+
+/**
+ * Function: evaluateTextAttribute
+ * 
+ * Gets the given attribute as a text. The return value from <evaluateAttribute>
+ * is used as a key to <mxResources.get> if the localized attribute in the text
+ * node is 1 or if <defaultLocalized> is true.
+ */
+mxStencil.prototype.evaluateTextAttribute = function(node, attribute, shape)
+{
+	var result = this.evaluateAttribute(node, attribute, shape);
+	var loc = node.getAttribute('localized');
+	
+	if ((mxStencil.defaultLocalized && loc == null) || loc == '1')
+	{
+		result = mxResources.get(result);
+	}
+
+	return result;
+};
+
+/**
+ * Function: evaluateAttribute
+ *
+ * Gets the attribute for the given name from the given node. If the attribute
+ * does not exist then the text content of the node is evaluated and if it is
+ * a function it is invoked with <shape> as the only argument and the return
+ * value is used as the attribute value to be returned.
+ */
+mxStencil.prototype.evaluateAttribute = function(node, attribute, shape)
+{
+	var result = node.getAttribute(attribute);
+	
+	if (result == null)
+	{
+		var text = mxUtils.getTextContent(node);
+		
+		if (text != null && mxStencil.allowEval)
+		{
+			var funct = mxUtils.eval(text);
+			
+			if (typeof(funct) == 'function')
+			{
+				result = funct(shape);
+			}
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: drawShape
+ *
+ * Draws this stencil inside the given bounds.
+ */
+mxStencil.prototype.drawShape = function(canvas, shape, x, y, w, h)
+{
+	// TODO: Internal structure (array of special structs?), relative and absolute
+	// coordinates (eg. note shape, process vs star, actor etc.), text rendering
+	// and non-proportional scaling, how to implement pluggable edge shapes
+	// (start, segment, end blocks), pluggable markers, how to implement
+	// swimlanes (title area) with this API, add icon, horizontal/vertical
+	// label, indicator for all shapes, rotation
+	var direction = mxUtils.getValue(shape.style, mxConstants.STYLE_DIRECTION, null);
+	var aspect = this.computeAspect(shape.style, x, y, w, h, direction);
+	var minScale = Math.min(aspect.width, aspect.height);
+	var sw = (this.strokewidth == 'inherit') ?
+			Number(mxUtils.getNumber(shape.style, mxConstants.STYLE_STROKEWIDTH, 1)) :
+			Number(this.strokewidth) * minScale;
+	canvas.setStrokeWidth(sw);
+
+	this.drawChildren(canvas, shape, x, y, w, h, this.bgNode, aspect, false);
+	this.drawChildren(canvas, shape, x, y, w, h, this.fgNode, aspect, true);
+};
+
+/**
+ * Function: drawChildren
+ *
+ * Draws this stencil inside the given bounds.
+ */
+mxStencil.prototype.drawChildren = function(canvas, shape, x, y, w, h, node, aspect, disableShadow)
+{
+	if (node != null && w > 0 && h > 0)
+	{
+		var tmp = node.firstChild;
+		
+		while (tmp != null)
+		{
+			if (tmp.nodeType == mxConstants.NODETYPE_ELEMENT)
+			{
+				this.drawNode(canvas, shape, tmp, aspect, disableShadow);
+			}
+			
+			tmp = tmp.nextSibling;
+		}
+	}
+};
+
+/**
+ * Function: computeAspect
+ *
+ * Returns a rectangle that contains the offset in x and y and the horizontal
+ * and vertical scale in width and height used to draw this shape inside the
+ * given <mxRectangle>.
+ * 
+ * Parameters:
+ * 
+ * shape - <mxShape> to be drawn.
+ * bounds - <mxRectangle> that should contain the stencil.
+ * direction - Optional direction of the shape to be darwn.
+ */
+mxStencil.prototype.computeAspect = function(shape, x, y, w, h, direction)
+{
+	var x0 = x;
+	var y0 = y;
+	var sx = w / this.w0;
+	var sy = h / this.h0;
+	
+	var inverse = (direction == mxConstants.DIRECTION_NORTH || direction == mxConstants.DIRECTION_SOUTH);
+
+	if (inverse)
+	{
+		sy = w / this.h0;
+		sx = h / this.w0;
+		
+		var delta = (w - h) / 2;
+
+		x0 += delta;
+		y0 -= delta;
+	}
+
+	if (this.aspect == 'fixed')
+	{
+		sy = Math.min(sx, sy);
+		sx = sy;
+		
+		// Centers the shape inside the available space
+		if (inverse)
+		{
+			x0 += (h - this.w0 * sx) / 2;
+			y0 += (w - this.h0 * sy) / 2;
+		}
+		else
+		{
+			x0 += (w - this.w0 * sx) / 2;
+			y0 += (h - this.h0 * sy) / 2;
+		}
+	}
+
+	return new mxRectangle(x0, y0, sx, sy);
+};
+
+/**
+ * Function: drawNode
+ *
+ * Draws this stencil inside the given bounds.
+ */
+mxStencil.prototype.drawNode = function(canvas, shape, node, aspect, disableShadow)
+{
+	var name = node.nodeName;
+	var x0 = aspect.x;
+	var y0 = aspect.y;
+	var sx = aspect.width;
+	var sy = aspect.height;
+	var minScale = Math.min(sx, sy);
+
+	if (name == 'save')
+	{
+		canvas.save();
+	}
+	else if (name == 'restore')
+	{
+		canvas.restore();
+	}
+	else if (name == 'path')
+	{
+		canvas.begin();
+
+		// Renders the elements inside the given path
+		var childNode = node.firstChild;
+		
+		while (childNode != null)
+		{
+			if (childNode.nodeType == mxConstants.NODETYPE_ELEMENT)
+			{
+				this.drawNode(canvas, shape, childNode, aspect, disableShadow);
+			}
+			
+			childNode = childNode.nextSibling;
+		}
+	}
+	else if (name == 'close')
+	{
+		canvas.close();
+	}
+	else if (name == 'move')
+	{
+		canvas.moveTo(x0 + Number(node.getAttribute('x')) * sx, y0 + Number(node.getAttribute('y')) * sy);
+	}
+	else if (name == 'line')
+	{
+		canvas.lineTo(x0 + Number(node.getAttribute('x')) * sx, y0 + Number(node.getAttribute('y')) * sy);
+	}
+	else if (name == 'quad')
+	{
+		canvas.quadTo(x0 + Number(node.getAttribute('x1')) * sx,
+				y0 + Number(node.getAttribute('y1')) * sy,
+				x0 + Number(node.getAttribute('x2')) * sx,
+				y0 + Number(node.getAttribute('y2')) * sy);
+	}
+	else if (name == 'curve')
+	{
+		canvas.curveTo(x0 + Number(node.getAttribute('x1')) * sx,
+				y0 + Number(node.getAttribute('y1')) * sy,
+				x0 + Number(node.getAttribute('x2')) * sx,
+				y0 + Number(node.getAttribute('y2')) * sy,
+				x0 + Number(node.getAttribute('x3')) * sx,
+				y0 + Number(node.getAttribute('y3')) * sy);
+	}
+	else if (name == 'arc')
+	{
+		canvas.arcTo(Number(node.getAttribute('rx')) * sx,
+				Number(node.getAttribute('ry')) * sy,
+				Number(node.getAttribute('x-axis-rotation')),
+				Number(node.getAttribute('large-arc-flag')),
+				Number(node.getAttribute('sweep-flag')),
+				x0 + Number(node.getAttribute('x')) * sx,
+				y0 + Number(node.getAttribute('y')) * sy);
+	}
+	else if (name == 'rect')
+	{
+		canvas.rect(x0 + Number(node.getAttribute('x')) * sx,
+				y0 + Number(node.getAttribute('y')) * sy,
+				Number(node.getAttribute('w')) * sx,
+				Number(node.getAttribute('h')) * sy);
+	}
+	else if (name == 'roundrect')
+	{
+		var arcsize = Number(node.getAttribute('arcsize'));
+
+		if (arcsize == 0)
+		{
+			arcsize = mxConstants.RECTANGLE_ROUNDING_FACTOR * 100;
+		}
+		
+		var w = Number(node.getAttribute('w')) * sx;
+		var h = Number(node.getAttribute('h')) * sy;
+		var factor = Number(arcsize) / 100;
+		var r = Math.min(w * factor, h * factor);
+		
+		canvas.roundrect(x0 + Number(node.getAttribute('x')) * sx,
+				y0 + Number(node.getAttribute('y')) * sy,
+				w, h, r, r);
+	}
+	else if (name == 'ellipse')
+	{
+		canvas.ellipse(x0 + Number(node.getAttribute('x')) * sx,
+			y0 + Number(node.getAttribute('y')) * sy,
+			Number(node.getAttribute('w')) * sx,
+			Number(node.getAttribute('h')) * sy);
+	}
+	else if (name == 'image')
+	{
+		if (!shape.outline)
+		{
+			var src = this.evaluateAttribute(node, 'src', shape);
+			
+			canvas.image(x0 + Number(node.getAttribute('x')) * sx,
+				y0 + Number(node.getAttribute('y')) * sy,
+				Number(node.getAttribute('w')) * sx,
+				Number(node.getAttribute('h')) * sy,
+				src, false, node.getAttribute('flipH') == '1',
+				node.getAttribute('flipV') == '1');
+		}
+	}
+	else if (name == 'text')
+	{
+		if (!shape.outline)
+		{
+			var str = this.evaluateTextAttribute(node, 'str', shape);
+			var rotation = node.getAttribute('vertical') == '1' ? -90 : 0;
+			
+			if (node.getAttribute('align-shape') == '0')
+			{
+				var dr = shape.rotation;
+	
+				// Depends on flipping
+				var flipH = mxUtils.getValue(shape.style, mxConstants.STYLE_FLIPH, 0) == 1;
+				var flipV = mxUtils.getValue(shape.style, mxConstants.STYLE_FLIPV, 0) == 1;
+				
+				if (flipH && flipV)
+				{
+					rotation -= dr;
+				}
+				else if (flipH || flipV)
+				{
+					rotation += dr;
+				}
+				else
+				{
+					rotation -= dr;
+				}
+			}
+	
+			rotation -= node.getAttribute('rotation');
+	
+			canvas.text(x0 + Number(node.getAttribute('x')) * sx,
+					y0 + Number(node.getAttribute('y')) * sy,
+					0, 0, str, node.getAttribute('align') || 'left',
+					node.getAttribute('valign') || 'top', false, '',
+					null, false, rotation);
+		}
+	}
+	else if (name == 'include-shape')
+	{
+		var stencil = mxStencilRegistry.getStencil(node.getAttribute('name'));
+		
+		if (stencil != null)
+		{
+			var x = x0 + Number(node.getAttribute('x')) * sx;
+			var y = y0 + Number(node.getAttribute('y')) * sy;
+			var w = Number(node.getAttribute('w')) * sx;
+			var h = Number(node.getAttribute('h')) * sy;
+			
+			stencil.drawShape(canvas, shape, x, y, w, h);
+		}
+	}
+	else if (name == 'fillstroke')
+	{
+		canvas.fillAndStroke();
+	}
+	else if (name == 'fill')
+	{
+		canvas.fill();
+	}
+	else if (name == 'stroke')
+	{
+		canvas.stroke();
+	}
+	else if (name == 'strokewidth')
+	{
+		var s = (node.getAttribute('fixed') == '1') ? 1 : minScale;
+		canvas.setStrokeWidth(Number(node.getAttribute('width')) * s);
+	}
+	else if (name == 'dashed')
+	{
+		canvas.setDashed(node.getAttribute('dashed') == '1');
+	}
+	else if (name == 'dashpattern')
+	{
+		var value = node.getAttribute('pattern');
+		
+		if (value != null)
+		{
+			var tmp = value.split(' ');
+			var pat = [];
+			
+			for (var i = 0; i < tmp.length; i++)
+			{
+				if (tmp[i].length > 0)
+				{
+					pat.push(Number(tmp[i]) * minScale);
+				}
+			}
+			
+			value = pat.join(' ');
+			canvas.setDashPattern(value);
+		}
+	}
+	else if (name == 'strokecolor')
+	{
+		canvas.setStrokeColor(node.getAttribute('color'));
+	}
+	else if (name == 'linecap')
+	{
+		canvas.setLineCap(node.getAttribute('cap'));
+	}
+	else if (name == 'linejoin')
+	{
+		canvas.setLineJoin(node.getAttribute('join'));
+	}
+	else if (name == 'miterlimit')
+	{
+		canvas.setMiterLimit(Number(node.getAttribute('limit')));
+	}
+	else if (name == 'fillcolor')
+	{
+		canvas.setFillColor(node.getAttribute('color'));
+	}
+	else if (name == 'alpha')
+	{
+		canvas.setAlpha(node.getAttribute('alpha'));
+	}
+	else if (name == 'fontcolor')
+	{
+		canvas.setFontColor(node.getAttribute('color'));
+	}
+	else if (name == 'fontstyle')
+	{
+		canvas.setFontStyle(node.getAttribute('style'));
+	}
+	else if (name == 'fontfamily')
+	{
+		canvas.setFontFamily(node.getAttribute('family'));
+	}
+	else if (name == 'fontsize')
+	{
+		canvas.setFontSize(Number(node.getAttribute('size')) * minScale);
+	}
+	
+	if (disableShadow && (name == 'fillstroke' || name == 'fill' || name == 'stroke'))
+	{
+		disableShadow = false;
+		canvas.setShadow(false);
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/shape/mxStencilRegistry.js b/airavata-kubernetes/workflow-composer/src/js/shape/mxStencilRegistry.js
new file mode 100644
index 0000000..744275f
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/shape/mxStencilRegistry.js
@@ -0,0 +1,53 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ * 
+ * Code to add stencils.
+ * 
+ * (code)
+ * var req = mxUtils.load('test/stencils.xml');
+ * var root = req.getDocumentElement();
+ * var shape = root.firstChild;
+ * 
+ * while (shape != null)
+ * {
+ * 	 if (shape.nodeType == mxConstants.NODETYPE_ELEMENT)
+ *   {
+ *     mxStencilRegistry.addStencil(shape.getAttribute('name'), new mxStencil(shape));
+ *   }
+ *   
+ *   shape = shape.nextSibling;
+ * }
+ * (end)
+ */
+var mxStencilRegistry =
+{
+	/**
+	 * Class: mxStencilRegistry
+	 * 
+	 * A singleton class that provides a registry for stencils and the methods
+	 * for painting those stencils onto a canvas or into a DOM.
+	 */
+	stencils: {},
+	
+	/**
+	 * Function: addStencil
+	 * 
+	 * Adds the given <mxStencil>.
+	 */
+	addStencil: function(name, stencil)
+	{
+		mxStencilRegistry.stencils[name] = stencil;
+	},
+	
+	/**
+	 * Function: getStencil
+	 * 
+	 * Returns the <mxStencil> for the given name.
+	 */
+	getStencil: function(name)
+	{
+		return mxStencilRegistry.stencils[name];
+	}
+
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/shape/mxSwimlane.js b/airavata-kubernetes/workflow-composer/src/js/shape/mxSwimlane.js
new file mode 100644
index 0000000..b2abf82
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/shape/mxSwimlane.js
@@ -0,0 +1,410 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxSwimlane
+ *
+ * Extends <mxShape> to implement a swimlane shape. This shape is registered
+ * under <mxConstants.SHAPE_SWIMLANE> in <mxCellRenderer>. Use the
+ * <mxConstants.STYLE_STYLE_STARTSIZE> to define the size of the title
+ * region, <mxConstants.STYLE_SWIMLANE_FILLCOLOR> for the content area fill,
+ * <mxConstants.STYLE_SEPARATORCOLOR> to draw an additional vertical separator
+ * and <mxConstants.STYLE_SWIMLANE_LINE> to hide the line between the title
+ * region and the content area. The <mxConstants.STYLE_HORIZONTAL> affects
+ * the orientation of this shape, not only its label.
+ * 
+ * Constructor: mxSwimlane
+ *
+ * Constructs a new swimlane shape.
+ * 
+ * Parameters:
+ * 
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxSwimlane(bounds, fill, stroke, strokewidth)
+{
+	mxShape.call(this);
+	this.bounds = bounds;
+	this.fill = fill;
+	this.stroke = stroke;
+	this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxUtils.extend(mxSwimlane, mxShape);
+
+/**
+ * Variable: imageSize
+ *
+ * Default imagewidth and imageheight if an image but no imagewidth
+ * and imageheight are defined in the style. Value is 16.
+ */
+mxSwimlane.prototype.imageSize = 16;
+
+/**
+ * Function: getGradientBounds
+ * 
+ * Returns the bounding box for the gradient box for this shape.
+ */
+mxSwimlane.prototype.getTitleSize = function()
+{
+	return Math.max(0, mxUtils.getValue(this.style, mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE));
+};
+
+/**
+ * Function: getGradientBounds
+ * 
+ * Returns the bounding box for the gradient box for this shape.
+ */
+mxSwimlane.prototype.getLabelBounds = function(rect)
+{
+	var start = this.getTitleSize();
+	var bounds = new mxRectangle(rect.x, rect.y, rect.width, rect.height);
+	var horizontal = this.isHorizontal();
+	
+	var flipH = mxUtils.getValue(this.style, mxConstants.STYLE_FLIPH, 0) == 1;
+	var flipV = mxUtils.getValue(this.style, mxConstants.STYLE_FLIPV, 0) == 1;	
+	
+	// East is default
+	var shapeVertical = (this.direction == mxConstants.DIRECTION_NORTH ||
+			this.direction == mxConstants.DIRECTION_SOUTH);
+	var realHorizontal = horizontal == !shapeVertical;
+	
+	var realFlipH = !realHorizontal && flipH != (this.direction == mxConstants.DIRECTION_SOUTH ||
+			this.direction == mxConstants.DIRECTION_WEST);
+	var realFlipV = realHorizontal && flipV != (this.direction == mxConstants.DIRECTION_SOUTH ||
+			this.direction == mxConstants.DIRECTION_WEST);
+
+	// Shape is horizontal
+	if (!shapeVertical)
+	{
+		var tmp = Math.min(bounds.height, start * this.scale);
+
+		if (realFlipH || realFlipV)
+		{
+			bounds.y += bounds.height - tmp;
+		}
+
+		bounds.height = tmp;
+	}
+	else
+	{
+		var tmp = Math.min(bounds.width, start * this.scale);
+		
+		if (realFlipH || realFlipV)
+		{
+			bounds.x += bounds.width - tmp;	
+		}
+
+		bounds.width = tmp;
+	}
+	
+	return bounds;
+};
+
+/**
+ * Function: getGradientBounds
+ * 
+ * Returns the bounding box for the gradient box for this shape.
+ */
+mxSwimlane.prototype.getGradientBounds = function(c, x, y, w, h)
+{
+	var start = this.getTitleSize();
+	
+	if (this.isHorizontal())
+	{
+		start = Math.min(start, h);
+		return new mxRectangle(x, y, w, start);
+	}
+	else
+	{
+		start = Math.min(start, w);
+		return new mxRectangle(x, y, start, h);
+	}
+};
+
+/**
+ * Function: getArcSize
+ * 
+ * Returns the arcsize for the swimlane.
+ */
+mxSwimlane.prototype.getArcSize = function(w, h, start)
+{
+	var f = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.RECTANGLE_ROUNDING_FACTOR * 100) / 100;
+
+	return start * f * 3; 
+};
+
+/**
+ * Function: paintVertexShape
+ *
+ * Paints the swimlane vertex shape.
+ */
+mxSwimlane.prototype.isHorizontal = function()
+{
+	return mxUtils.getValue(this.style, mxConstants.STYLE_HORIZONTAL, 1) == 1;
+};
+
+/**
+ * Function: paintVertexShape
+ *
+ * Paints the swimlane vertex shape.
+ */
+mxSwimlane.prototype.paintVertexShape = function(c, x, y, w, h)
+{
+	var start = this.getTitleSize();
+	var fill = mxUtils.getValue(this.style, mxConstants.STYLE_SWIMLANE_FILLCOLOR, mxConstants.NONE);
+	var swimlaneLine = mxUtils.getValue(this.style, mxConstants.STYLE_SWIMLANE_LINE, 1) == 1;
+	var r = 0;
+	
+	if (this.isHorizontal())
+	{
+		start = Math.min(start, h);
+	}
+	else
+	{
+		start = Math.min(start, w);
+	}
+	
+	c.translate(x, y);
+	
+	if (!this.isRounded)
+	{
+		this.paintSwimlane(c, x, y, w, h, start, fill, swimlaneLine);
+	}
+	else
+	{
+		r = this.getArcSize(w, h, start);
+		this.paintRoundedSwimlane(c, x, y, w, h, start, r, fill, swimlaneLine);
+	}
+	
+	var sep = mxUtils.getValue(this.style, mxConstants.STYLE_SEPARATORCOLOR, mxConstants.NONE);
+	this.paintSeparator(c, x, y, w, h, start, sep);
+
+	if (this.image != null)
+	{
+		var bounds = this.getImageBounds(x, y, w, h);
+		c.image(bounds.x - x, bounds.y - y, bounds.width, bounds.height,
+				this.image, false, false, false);
+	}
+	
+	if (this.glass)
+	{
+		c.setShadow(false);
+		this.paintGlassEffect(c, 0, 0, w, start, r);
+	}
+};
+
+/**
+ * Function: paintSwimlane
+ *
+ * Paints the swimlane vertex shape.
+ */
+mxSwimlane.prototype.paintSwimlane = function(c, x, y, w, h, start, fill, swimlaneLine)
+{
+	if (fill != mxConstants.NONE)
+	{
+		c.save();
+		c.setFillColor(fill);
+		c.rect(0, 0, w, h);
+		c.fillAndStroke();
+		c.restore();
+		c.setShadow(false);
+	}
+
+	c.begin();
+	
+	if (this.isHorizontal())
+	{
+		c.moveTo(0, start);
+		c.lineTo(0, 0);
+		c.lineTo(w, 0);
+		c.lineTo(w, start);
+		
+		if (swimlaneLine || start >= h)
+		{
+			c.close();
+		}
+		
+		c.fillAndStroke();
+		
+		// Transparent content area
+		if (start < h && fill == mxConstants.NONE)
+		{
+			c.pointerEvents = false;
+			
+			c.begin();
+			c.moveTo(0, start);
+			c.lineTo(0, h);
+			c.lineTo(w, h);
+			c.lineTo(w, start);
+			c.stroke();
+		}
+	}
+	else
+	{
+		c.moveTo(start, 0);
+		c.lineTo(0, 0);
+		c.lineTo(0, h);
+		c.lineTo(start, h);
+		
+		if (swimlaneLine || start >= w)
+		{
+			c.close();
+		}
+		
+		c.fillAndStroke();
+		
+		// Transparent content area
+		if (start < w && fill == mxConstants.NONE)
+		{
+			c.pointerEvents = false;
+			
+			c.begin();
+			c.moveTo(start, 0);
+			c.lineTo(w, 0);
+			c.lineTo(w, h);
+			c.lineTo(start, h);
+			c.stroke();
+		}
+	}
+};
+
+/**
+ * Function: paintRoundedSwimlane
+ *
+ * Paints the swimlane vertex shape.
+ */
+mxSwimlane.prototype.paintRoundedSwimlane = function(c, x, y, w, h, start, r, fill, swimlaneLine)
+{
+	r = Math.min(h - start, Math.min(start, r));
+	
+	if (fill != mxConstants.NONE)
+	{
+		c.save();
+		c.setFillColor(fill);
+		c.roundrect(0, 0, w, h, r, r);
+		c.fillAndStroke();
+		c.restore();
+		c.setShadow(false);
+	}
+	
+	c.begin();
+	
+	if (this.isHorizontal())
+	{
+		c.moveTo(w, start);
+		c.lineTo(w, r);
+		c.quadTo(w, 0, w - Math.min(w / 2, r), 0);
+		c.lineTo(Math.min(w / 2, r), 0);
+		c.quadTo(0, 0, 0, r);
+		c.lineTo(0, start);
+		
+		if (swimlaneLine || start >= h)
+		{
+			c.close();
+		}
+	
+		c.fillAndStroke();
+		
+		// Transparent content area
+		if (start < h && fill == mxConstants.NONE)
+		{
+			c.pointerEvents = false;
+			
+			c.begin();
+			c.moveTo(0, start);
+			c.lineTo(0, h - r);
+			c.quadTo(0, h, Math.min(w / 2, r), h);
+			c.lineTo(w - Math.min(w / 2, r), h);
+			c.quadTo(w, h, w, h - r);
+			c.lineTo(w, start);
+			c.stroke();
+		}
+	}
+	else
+	{
+		c.moveTo(start, 0);
+		c.lineTo(r, 0);
+		c.quadTo(0, 0, 0, Math.min(h / 2, r));
+		c.lineTo(0, h - Math.min(h / 2, r));
+		c.quadTo(0, h, r, h);
+		c.lineTo(start, h);
+		
+		if (swimlaneLine || start >= w)
+		{
+			c.close();
+		}
+	
+		c.fillAndStroke();
+		
+		// Transparent content area
+		if (start < w && fill == mxConstants.NONE)
+		{
+			c.pointerEvents = false;
+			
+			c.begin();
+			c.moveTo(start, h);
+			c.lineTo(w - r, h);
+			c.quadTo(w, h, w, h - Math.min(h / 2, r));
+			c.lineTo(w, Math.min(h / 2, r));
+			c.quadTo(w, 0, w - r, 0);
+			c.lineTo(start, 0);
+			c.stroke();
+		}
+	}
+};
+
+/**
+ * Function: paintSwimlane
+ *
+ * Paints the swimlane vertex shape.
+ */
+mxSwimlane.prototype.paintSeparator = function(c, x, y, w, h, start, color)
+{
+	if (color != mxConstants.NONE)
+	{
+		c.setStrokeColor(color);
+		c.setDashed(true);
+		c.begin();
+		
+		if (this.isHorizontal())
+		{
+			c.moveTo(w, start);
+			c.lineTo(w, h);
+		}
+		else
+		{
+			c.moveTo(start, 0);
+			c.lineTo(w, 0);
+		}
+		
+		c.stroke();
+		c.setDashed(false);
+	}
+};
+
+/**
+ * Function: getImageBounds
+ *
+ * Paints the swimlane vertex shape.
+ */
+mxSwimlane.prototype.getImageBounds = function(x, y, w, h)
+{
+	if (this.isHorizontal())
+	{
+		return new mxRectangle(x + w - this.imageSize, y, this.imageSize, this.imageSize);
+	}
+	else
+	{
+		return new mxRectangle(x, y, this.imageSize, this.imageSize);
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/shape/mxText.js b/airavata-kubernetes/workflow-composer/src/js/shape/mxText.js
new file mode 100644
index 0000000..a574998
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/shape/mxText.js
@@ -0,0 +1,1263 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxText
+ *
+ * Extends <mxShape> to implement a text shape. To change vertical text from
+ * bottom to top to top to bottom, the following code can be used:
+ * 
+ * (code)
+ * mxText.prototype.verticalTextRotation = 90;
+ * (end)
+ * 
+ * Constructor: mxText
+ *
+ * Constructs a new text shape.
+ * 
+ * Parameters:
+ * 
+ * value - String that represents the text to be displayed. This is stored in
+ * <value>.
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * align - Specifies the horizontal alignment. Default is ''. This is stored in
+ * <align>.
+ * valign - Specifies the vertical alignment. Default is ''. This is stored in
+ * <valign>.
+ * color - String that specifies the text color. Default is 'black'. This is
+ * stored in <color>.
+ * family - String that specifies the font family. Default is
+ * <mxConstants.DEFAULT_FONTFAMILY>. This is stored in <family>.
+ * size - Integer that specifies the font size. Default is
+ * <mxConstants.DEFAULT_FONTSIZE>. This is stored in <size>.
+ * fontStyle - Specifies the font style. Default is 0. This is stored in
+ * <fontStyle>.
+ * spacing - Integer that specifies the global spacing. Default is 2. This is
+ * stored in <spacing>.
+ * spacingTop - Integer that specifies the top spacing. Default is 0. The
+ * sum of the spacing and this is stored in <spacingTop>.
+ * spacingRight - Integer that specifies the right spacing. Default is 0. The
+ * sum of the spacing and this is stored in <spacingRight>.
+ * spacingBottom - Integer that specifies the bottom spacing. Default is 0.The
+ * sum of the spacing and this is stored in <spacingBottom>.
+ * spacingLeft - Integer that specifies the left spacing. Default is 0. The
+ * sum of the spacing and this is stored in <spacingLeft>.
+ * horizontal - Boolean that specifies if the label is horizontal. Default is
+ * true. This is stored in <horizontal>.
+ * background - String that specifies the background color. Default is null.
+ * This is stored in <background>.
+ * border - String that specifies the label border color. Default is null.
+ * This is stored in <border>.
+ * wrap - Specifies if word-wrapping should be enabled. Default is false.
+ * This is stored in <wrap>.
+ * clipped - Specifies if the label should be clipped. Default is false.
+ * This is stored in <clipped>.
+ * overflow - Value of the overflow style. Default is 'visible'.
+ */
+function mxText(value, bounds, align, valign, color,
+	family,	size, fontStyle, spacing, spacingTop, spacingRight,
+	spacingBottom, spacingLeft, horizontal, background, border,
+	wrap, clipped, overflow, labelPadding, textDirection)
+{
+	mxShape.call(this);
+	this.value = value;
+	this.bounds = bounds;
+	this.color = (color != null) ? color : 'black';
+	this.align = (align != null) ? align : mxConstants.ALIGN_CENTER;
+	this.valign = (valign != null) ? valign : mxConstants.ALIGN_MIDDLE;
+	this.family = (family != null) ? family : mxConstants.DEFAULT_FONTFAMILY;
+	this.size = (size != null) ? size : mxConstants.DEFAULT_FONTSIZE;
+	this.fontStyle = (fontStyle != null) ? fontStyle : mxConstants.DEFAULT_FONTSTYLE;
+	this.spacing = parseInt(spacing || 2);
+	this.spacingTop = this.spacing + parseInt(spacingTop || 0);
+	this.spacingRight = this.spacing + parseInt(spacingRight || 0);
+	this.spacingBottom = this.spacing + parseInt(spacingBottom || 0);
+	this.spacingLeft = this.spacing + parseInt(spacingLeft || 0);
+	this.horizontal = (horizontal != null) ? horizontal : true;
+	this.background = background;
+	this.border = border;
+	this.wrap = (wrap != null) ? wrap : false;
+	this.clipped = (clipped != null) ? clipped : false;
+	this.overflow = (overflow != null) ? overflow : 'visible';
+	this.labelPadding = (labelPadding != null) ? labelPadding : 0;
+	this.textDirection = textDirection;
+	this.rotation = 0;
+	this.updateMargin();
+};
+
+/**
+ * Extends mxShape.
+ */
+mxUtils.extend(mxText, mxShape);
+
+/**
+ * Variable: baseSpacingTop
+ * 
+ * Specifies the spacing to be added to the top spacing. Default is 0. Use the
+ * value 5 here to get the same label positions as in mxGraph 1.x.
+ */
+mxText.prototype.baseSpacingTop = 0;
+
+/**
+ * Variable: baseSpacingBottom
+ * 
+ * Specifies the spacing to be added to the bottom spacing. Default is 0. Use the
+ * value 1 here to get the same label positions as in mxGraph 1.x.
+ */
+mxText.prototype.baseSpacingBottom = 0;
+
+/**
+ * Variable: baseSpacingLeft
+ * 
+ * Specifies the spacing to be added to the left spacing. Default is 0.
+ */
+mxText.prototype.baseSpacingLeft = 0;
+
+/**
+ * Variable: baseSpacingRight
+ * 
+ * Specifies the spacing to be added to the right spacing. Default is 0.
+ */
+mxText.prototype.baseSpacingRight = 0;
+
+/**
+ * Variable: replaceLinefeeds
+ * 
+ * Specifies if linefeeds in HTML labels should be replaced with BR tags.
+ * Default is true.
+ */
+mxText.prototype.replaceLinefeeds = true;
+
+/**
+ * Variable: verticalTextRotation
+ * 
+ * Rotation for vertical text. Default is -90 (bottom to top).
+ */
+mxText.prototype.verticalTextRotation = -90;
+
+/**
+ * Variable: ignoreClippedStringSize
+ * 
+ * Specifies if the string size should be measured in <updateBoundingBox> if
+ * the label is clipped and the label position is center and middle. If this is
+ * true, then the bounding box will be set to <bounds>. Default is true.
+ * <ignoreStringSize> has precedence over this switch.
+ */
+mxText.prototype.ignoreClippedStringSize = true;
+
+/**
+ * Variable: ignoreStringSize
+ * 
+ * Specifies if the actual string size should be measured. If disabled the
+ * boundingBox will not ignore the actual size of the string, otherwise
+ * <bounds> will be used instead. Default is false.
+ */
+mxText.prototype.ignoreStringSize = false;
+
+/**
+ * Variable: textWidthPadding
+ * 
+ * Specifies the padding to be added to the text width for the bounding box.
+ * This is needed to make sure no clipping is applied to borders. Default is 4
+ * for IE 8 standards mode and 3 for all others.
+ */
+mxText.prototype.textWidthPadding = (document.documentMode == 8 && !mxClient.IS_EM) ? 4 : 3;
+
+/**
+ * Variable: lastValue
+ * 
+ * Contains the last rendered text value. Used for caching.
+ */
+mxText.prototype.lastValue = null;
+
+/**
+ * Variable: cacheEnabled
+ * 
+ * Specifies if caching for HTML labels should be enabled. Default is true.
+ */
+mxText.prototype.cacheEnabled = true;
+
+/**
+ * Function: isParseVml
+ * 
+ * Text shapes do not contain VML markup and do not need to be parsed. This
+ * method returns false to speed up rendering in IE8.
+ */
+mxText.prototype.isParseVml = function()
+{
+	return false;
+};
+
+/**
+ * Function: isHtmlAllowed
+ * 
+ * Returns true if HTML is allowed for this shape. This implementation returns
+ * true if the browser is not in IE8 standards mode.
+ */
+mxText.prototype.isHtmlAllowed = function()
+{
+	return document.documentMode != 8 || mxClient.IS_EM;
+};
+
+/**
+ * Function: getSvgScreenOffset
+ * 
+ * Disables offset in IE9 for crisper image output.
+ */
+mxText.prototype.getSvgScreenOffset = function()
+{
+	return 0;
+};
+
+/**
+ * Function: checkBounds
+ * 
+ * Returns true if the bounds are not null and all of its variables are numeric.
+ */
+mxText.prototype.checkBounds = function()
+{
+	return (!isNaN(this.scale) && isFinite(this.scale) && this.scale > 0 &&
+			this.bounds != null && !isNaN(this.bounds.x) && !isNaN(this.bounds.y) &&
+			!isNaN(this.bounds.width) && !isNaN(this.bounds.height));
+};
+
+/**
+ * Function: paint
+ * 
+ * Generic rendering code.
+ */
+mxText.prototype.paint = function(c, update)
+{
+	// Scale is passed-through to canvas
+	var s = this.scale;
+	var x = this.bounds.x / s;
+	var y = this.bounds.y / s;
+	var w = this.bounds.width / s;
+	var h = this.bounds.height / s;
+	
+	this.updateTransform(c, x, y, w, h);
+	this.configureCanvas(c, x, y, w, h);
+
+	var unscaledWidth = (this.state != null) ? this.state.unscaledWidth : null;
+
+	if (update)
+	{
+		if (this.node.firstChild != null && (unscaledWidth == null ||
+			this.lastUnscaledWidth != unscaledWidth))
+		{
+			c.invalidateCachedOffsetSize(this.node);
+		}
+
+		c.updateText(x, y, w, h, this.align, this.valign, this.wrap, this.overflow,
+				this.clipped, this.getTextRotation(), this.node);
+	}
+	else
+	{
+		// Checks if text contains HTML markup
+		var realHtml = mxUtils.isNode(this.value) || this.dialect == mxConstants.DIALECT_STRICTHTML;
+		
+		// Always renders labels as HTML in VML
+		var fmt = (realHtml || c instanceof mxVmlCanvas2D) ? 'html' : '';
+		var val = this.value;
+		
+		if (!realHtml && fmt == 'html')
+		{
+			val =  mxUtils.htmlEntities(val, false);
+		}
+		
+		if (fmt == 'html' && !mxUtils.isNode(this.value))
+		{
+			val = mxUtils.replaceTrailingNewlines(val, '<div><br></div>');			
+		}
+		
+		// Handles trailing newlines to make sure they are visible in rendering output
+		val = (!mxUtils.isNode(this.value) && this.replaceLinefeeds && fmt == 'html') ?
+			val.replace(/\n/g, '<br/>') : val;
+			
+		var dir = this.textDirection;
+	
+		if (dir == mxConstants.TEXT_DIRECTION_AUTO && !realHtml)
+		{
+			dir = this.getAutoDirection();
+		}
+		
+		if (dir != mxConstants.TEXT_DIRECTION_LTR && dir != mxConstants.TEXT_DIRECTION_RTL)
+		{
+			dir = null;
+		}
+	
+		c.text(x, y, w, h, val, this.align, this.valign, this.wrap, fmt, this.overflow,
+			this.clipped, this.getTextRotation(), dir);
+	}
+	
+	// Needs to invalidate the cached offset widths if the geometry changes
+	this.lastUnscaledWidth = unscaledWidth;
+};
+
+/**
+ * Function: redraw
+ * 
+ * Renders the text using the given DOM nodes.
+ */
+mxText.prototype.redraw = function()
+{
+	if (this.visible && this.checkBounds() && this.cacheEnabled && this.lastValue == this.value &&
+		(mxUtils.isNode(this.value) || this.dialect == mxConstants.DIALECT_STRICTHTML))
+	{
+		if (this.node.nodeName == 'DIV' && (this.isHtmlAllowed() || !mxClient.IS_VML))
+		{
+			this.updateSize(this.node, (this.state == null || this.state.view.textDiv == null));
+
+			if (mxClient.IS_IE && (document.documentMode == null || document.documentMode <= 8))
+			{
+				this.updateHtmlFilter();
+			}
+			else
+			{
+				this.updateHtmlTransform();
+			}
+			
+			this.updateBoundingBox();
+		}
+		else
+		{
+			var canvas = this.createCanvas();
+
+			if (canvas != null && canvas.updateText != null &&
+				canvas.invalidateCachedOffsetSize != null)
+			{
+				this.paint(canvas, true);
+				this.destroyCanvas(canvas);
+				this.updateBoundingBox();
+			}
+			else
+			{
+				// Fallback if canvas does not support updateText (VML)
+				mxShape.prototype.redraw.apply(this, arguments);
+			}
+		}
+	}
+	else
+	{
+		mxShape.prototype.redraw.apply(this, arguments);
+		
+		if (mxUtils.isNode(this.value) || this.dialect == mxConstants.DIALECT_STRICTHTML)
+		{
+			this.lastValue = this.value;
+		}
+		else
+		{
+			this.lastValue = null;
+		}
+	}
+};
+
+/**
+ * Function: resetStyles
+ * 
+ * Resets all styles.
+ */
+mxText.prototype.resetStyles = function()
+{
+	mxShape.prototype.resetStyles.apply(this, arguments);
+	
+	this.color = 'black';
+	this.align = mxConstants.ALIGN_CENTER;
+	this.valign = mxConstants.ALIGN_MIDDLE;
+	this.family = mxConstants.DEFAULT_FONTFAMILY;
+	this.size = mxConstants.DEFAULT_FONTSIZE;
+	this.fontStyle = mxConstants.DEFAULT_FONTSTYLE;
+	this.spacing = 2;
+	this.spacingTop = 2;
+	this.spacingRight = 2;
+	this.spacingBottom = 2;
+	this.spacingLeft = 2;
+	this.horizontal = true;
+	delete this.background;
+	delete this.border;
+	this.textDirection = mxConstants.DEFAULT_TEXT_DIRECTION;
+	delete this.margin;
+};
+
+/**
+ * Function: apply
+ * 
+ * Extends mxShape to update the text styles.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> of the corresponding cell.
+ */
+mxText.prototype.apply = function(state)
+{
+	var old = this.spacing;
+	mxShape.prototype.apply.apply(this, arguments);
+	
+	if (this.style != null)
+	{
+		this.fontStyle = mxUtils.getValue(this.style, mxConstants.STYLE_FONTSTYLE, this.fontStyle);
+		this.family = mxUtils.getValue(this.style, mxConstants.STYLE_FONTFAMILY, this.family);
+		this.size = mxUtils.getValue(this.style, mxConstants.STYLE_FONTSIZE, this.size);
+		this.color = mxUtils.getValue(this.style, mxConstants.STYLE_FONTCOLOR, this.color);
+		this.align = mxUtils.getValue(this.style, mxConstants.STYLE_ALIGN, this.align);
+		this.valign = mxUtils.getValue(this.style, mxConstants.STYLE_VERTICAL_ALIGN, this.valign);
+		this.spacing = parseInt(mxUtils.getValue(this.style, mxConstants.STYLE_SPACING, this.spacing));
+		this.spacingTop = parseInt(mxUtils.getValue(this.style, mxConstants.STYLE_SPACING_TOP, this.spacingTop - old)) + this.spacing;
+		this.spacingRight = parseInt(mxUtils.getValue(this.style, mxConstants.STYLE_SPACING_RIGHT, this.spacingRight - old)) + this.spacing;
+		this.spacingBottom = parseInt(mxUtils.getValue(this.style, mxConstants.STYLE_SPACING_BOTTOM, this.spacingBottom - old)) + this.spacing;
+		this.spacingLeft = parseInt(mxUtils.getValue(this.style, mxConstants.STYLE_SPACING_LEFT, this.spacingLeft - old)) + this.spacing;
+		this.horizontal = mxUtils.getValue(this.style, mxConstants.STYLE_HORIZONTAL, this.horizontal);
+		this.background = mxUtils.getValue(this.style, mxConstants.STYLE_LABEL_BACKGROUNDCOLOR, this.background);
+		this.border = mxUtils.getValue(this.style, mxConstants.STYLE_LABEL_BORDERCOLOR, this.border);
+		this.textDirection = mxUtils.getValue(this.style, mxConstants.STYLE_TEXT_DIRECTION, mxConstants.DEFAULT_TEXT_DIRECTION);
+		this.opacity = mxUtils.getValue(this.style, mxConstants.STYLE_TEXT_OPACITY, 100);
+		this.updateMargin();
+	}
+	
+	this.flipV = null;
+	this.flipH = null;
+};
+
+/**
+ * Function: getAutoDirection
+ * 
+ * Used to determine the automatic text direction. Returns
+ * <mxConstants.TEXT_DIRECTION_LTR> or <mxConstants.TEXT_DIRECTION_RTL>
+ * depending on the contents of <value>. This is not invoked for HTML, wrapped
+ * content or if <value> is a DOM node.
+ */
+mxText.prototype.getAutoDirection = function()
+{
+	// Looks for strong (directional) characters
+	var tmp = /[A-Za-z\u05d0-\u065f\u066a-\u06ef\u06fa-\u07ff\ufb1d-\ufdff\ufe70-\ufefc]/.exec(this.value);
+	
+	// Returns the direction defined by the character
+	return (tmp != null && tmp.length > 0 && tmp[0] > 'z') ?
+		mxConstants.TEXT_DIRECTION_RTL : mxConstants.TEXT_DIRECTION_LTR;
+};
+
+/**
+ * Function: updateBoundingBox
+ *
+ * Updates the <boundingBox> for this shape using the given node and position.
+ */
+mxText.prototype.updateBoundingBox = function()
+{
+	var node = this.node;
+	this.boundingBox = this.bounds.clone();
+	var rot = this.getTextRotation();
+	
+	var h = (this.style != null) ? mxUtils.getValue(this.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER) : null;
+	var v = (this.style != null) ? mxUtils.getValue(this.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE) : null;
+
+	if (!this.ignoreStringSize && node != null && this.overflow != 'fill' && (!this.clipped ||
+		!this.ignoreClippedStringSize || h != mxConstants.ALIGN_CENTER || v != mxConstants.ALIGN_MIDDLE))
+	{
+		var ow = null;
+		var oh = null;
+		
+		if (node.ownerSVGElement != null)
+		{
+			if (node.firstChild != null && node.firstChild.firstChild != null &&
+				node.firstChild.firstChild.nodeName == 'foreignObject')
+			{
+				node = node.firstChild.firstChild;
+				ow = parseInt(node.getAttribute('width')) * this.scale;
+				oh = parseInt(node.getAttribute('height')) * this.scale;
+			}
+			else
+			{
+				try
+				{
+					var b = node.getBBox();
+					
+					// Workaround for bounding box of empty string
+					if (typeof(this.value) == 'string' && mxUtils.trim(this.value) == 0)
+					{
+						this.boundingBox = null;
+					}
+					else if (b.width == 0 && b.height == 0)
+					{
+						this.boundingBox = null;
+					}
+					else
+					{
+						this.boundingBox = new mxRectangle(b.x, b.y, b.width, b.height);
+					}
+					
+					return;
+				}
+				catch (e)
+				{
+					// Ignores NS_ERROR_FAILURE in FF if container display is none.
+				}
+			}
+		}
+		else
+		{
+			var td = (this.state != null) ? this.state.view.textDiv : null;
+
+			// Use cached offset size
+			if (this.offsetWidth != null && this.offsetHeight != null)
+			{
+				ow = this.offsetWidth * this.scale;
+				oh = this.offsetHeight * this.scale;
+			}
+			else
+			{
+				// Cannot get node size while container hidden so a
+				// shared temporary DIV is used for text measuring
+				if (td != null)
+				{
+					this.updateFont(td);
+					this.updateSize(td, false);
+					this.updateInnerHtml(td);
+
+					node = td;
+				}
+				
+				var sizeDiv = node;
+
+				if (document.documentMode == 8 && !mxClient.IS_EM)
+				{
+					var w = Math.round(this.bounds.width / this.scale);
+	
+					if (this.wrap && w > 0)
+					{
+						node.style.wordWrap = mxConstants.WORD_WRAP;
+						node.style.whiteSpace = 'normal';
+
+						if (node.style.wordWrap != 'break-word')
+						{
+							// Innermost DIV is used for measuring text
+							var divs = sizeDiv.getElementsByTagName('div');
+							
+							if (divs.length > 0)
+							{
+								sizeDiv = divs[divs.length - 1];
+							}
+							
+							ow = sizeDiv.offsetWidth + 2;
+							divs = this.node.getElementsByTagName('div');
+							
+							if (this.clipped)
+							{
+								ow = Math.min(w, ow);
+							}
+							
+							// Second last DIV width must be updated in DOM tree
+							if (divs.length > 1)
+							{
+								divs[divs.length - 2].style.width = ow + 'px';
+							}
+						}
+					}
+					else
+					{
+						node.style.whiteSpace = 'nowrap';
+					}
+				}
+				else if (sizeDiv.firstChild != null && sizeDiv.firstChild.nodeName == 'DIV')
+				{
+					sizeDiv = sizeDiv.firstChild;
+				}
+
+				this.offsetWidth = sizeDiv.offsetWidth + this.textWidthPadding;
+				this.offsetHeight = sizeDiv.offsetHeight;
+				
+				ow = this.offsetWidth * this.scale;
+				oh = this.offsetHeight * this.scale;
+			}
+		}
+
+		if (ow != null && oh != null)
+		{	
+			this.boundingBox = new mxRectangle(this.bounds.x,
+				this.bounds.y, ow, oh);
+		}
+	}
+
+	if (this.boundingBox != null)
+	{
+		if (rot != 0)
+		{
+			// Accounts for pre-rotated x and y
+			var bbox = mxUtils.getBoundingBox(new mxRectangle(
+				this.margin.x * this.boundingBox.width,
+				this.margin.y * this.boundingBox.height,
+				this.boundingBox.width, this.boundingBox.height),
+				rot, new mxPoint(0, 0));
+			
+			this.unrotatedBoundingBox = mxRectangle.fromRectangle(this.boundingBox);
+			this.unrotatedBoundingBox.x += this.margin.x * this.unrotatedBoundingBox.width;
+			this.unrotatedBoundingBox.y += this.margin.y * this.unrotatedBoundingBox.height;
+			
+			this.boundingBox.x += bbox.x;
+			this.boundingBox.y += bbox.y;
+			this.boundingBox.width = bbox.width;
+			this.boundingBox.height = bbox.height;
+		}
+		else
+		{
+			this.boundingBox.x += this.margin.x * this.boundingBox.width;
+			this.boundingBox.y += this.margin.y * this.boundingBox.height;
+			this.unrotatedBoundingBox = null;
+		}
+	}
+};
+
+/**
+ * Function: getShapeRotation
+ * 
+ * Returns 0 to avoid using rotation in the canvas via updateTransform.
+ */
+mxText.prototype.getShapeRotation = function()
+{
+	return 0;
+};
+
+/**
+ * Function: getTextRotation
+ * 
+ * Returns the rotation for the text label of the corresponding shape.
+ */
+mxText.prototype.getTextRotation = function()
+{
+	return (this.state != null && this.state.shape != null) ? this.state.shape.getTextRotation() : 0;
+};
+
+/**
+ * Function: isPaintBoundsInverted
+ * 
+ * Inverts the bounds if <mxShape.isBoundsInverted> returns true or if the
+ * horizontal style is false.
+ */
+mxText.prototype.isPaintBoundsInverted = function()
+{
+	return !this.horizontal && this.state != null && this.state.view.graph.model.isVertex(this.state.cell);
+};
+
+/**
+ * Function: configureCanvas
+ * 
+ * Sets the state of the canvas for drawing the shape.
+ */
+mxText.prototype.configureCanvas = function(c, x, y, w, h)
+{
+	mxShape.prototype.configureCanvas.apply(this, arguments);
+	
+	c.setFontColor(this.color);
+	c.setFontBackgroundColor(this.background);
+	c.setFontBorderColor(this.border);
+	c.setFontFamily(this.family);
+	c.setFontSize(this.size);
+	c.setFontStyle(this.fontStyle);
+};
+
+/**
+ * Function: updateVmlContainer
+ * 
+ * Sets the width and height of the container to 1px.
+ */
+mxText.prototype.updateVmlContainer = function()
+{
+	this.node.style.left = Math.round(this.bounds.x) + 'px';
+	this.node.style.top = Math.round(this.bounds.y) + 'px';
+	this.node.style.width = '1px';
+	this.node.style.height = '1px';
+	this.node.style.overflow = 'visible';
+};
+
+/**
+ * Function: redrawHtmlShape
+ *
+ * Updates the HTML node(s) to reflect the latest bounds and scale.
+ */
+mxText.prototype.redrawHtmlShape = function()
+{
+	var style = this.node.style;
+
+	// Resets CSS styles
+	style.whiteSpace = 'normal';
+	style.overflow = '';
+	style.width = '';
+	style.height = '';
+	
+	this.updateValue();
+	this.updateFont(this.node);
+	this.updateSize(this.node, (this.state == null || this.state.view.textDiv == null));
+	
+	this.offsetWidth = null;
+	this.offsetHeight = null;
+
+	if (mxClient.IS_IE && (document.documentMode == null || document.documentMode <= 8))
+	{
+		this.updateHtmlFilter();
+	}
+	else
+	{
+		this.updateHtmlTransform();
+	}
+};
+
+/**
+ * Function: updateHtmlTransform
+ *
+ * Returns the spacing as an <mxPoint>.
+ */
+mxText.prototype.updateHtmlTransform = function()
+{
+	var theta = this.getTextRotation();
+	var style = this.node.style;
+	var dx = this.margin.x;
+	var dy = this.margin.y;
+	
+	if (theta != 0)
+	{
+		mxUtils.setPrefixedStyle(style, 'transformOrigin', (-dx * 100) + '%' + ' ' + (-dy * 100) + '%');
+		mxUtils.setPrefixedStyle(style, 'transform', 'translate(' + (dx * 100) + '%' + ',' + (dy * 100) + '%)' +
+			'scale(' + this.scale + ') rotate(' + theta + 'deg)');
+	}
+	else
+	{
+		mxUtils.setPrefixedStyle(style, 'transformOrigin', '0% 0%');
+		mxUtils.setPrefixedStyle(style, 'transform', 'scale(' + this.scale + ')' +
+			'translate(' + (dx * 100) + '%' + ',' + (dy * 100) + '%)');
+	}
+
+	style.left = Math.round(this.bounds.x - Math.ceil(dx * ((this.overflow != 'fill' &&
+		this.overflow != 'width') ? 3 : 1))) + 'px';
+	style.top = Math.round(this.bounds.y - dy * ((this.overflow != 'fill') ? 3 : 1)) + 'px';
+	
+	if (this.opacity < 100)
+	{
+		style.opacity = this.opacity / 100;
+	}
+	else
+	{
+		style.opacity = '';
+	}
+};
+
+/**
+ * Function: setInnerHtml
+ * 
+ * Sets the inner HTML of the given element to the <value>.
+ */
+mxText.prototype.updateInnerHtml = function(elt)
+{
+	if (mxUtils.isNode(this.value))
+	{
+		elt.innerHTML = this.value.outerHTML;
+	}
+	else
+	{
+		var val = this.value;
+		
+		if (this.dialect != mxConstants.DIALECT_STRICTHTML)
+		{
+			// LATER: Can be cached in updateValue
+			val = mxUtils.htmlEntities(val, false);
+		}
+		
+		// Handles trailing newlines to make sure they are visible in rendering output
+		val = mxUtils.replaceTrailingNewlines(val, '<div>&nbsp;</div>');
+		val = (this.replaceLinefeeds) ? val.replace(/\n/g, '<br/>') : val;
+		val = '<div style="display:inline-block;_display:inline;">' + val + '</div>';
+		
+		elt.innerHTML = val;
+	}
+};
+
+/**
+ * Function: updateHtmlFilter
+ *
+ * Rotated text rendering quality is bad for IE9 quirks/IE8 standards
+ */
+mxText.prototype.updateHtmlFilter = function()
+{
+	var style = this.node.style;
+	var dx = this.margin.x;
+	var dy = this.margin.y;
+	var s = this.scale;
+	
+	// Resets filter before getting offsetWidth
+	mxUtils.setOpacity(this.node, this.opacity);
+	
+	// Adds 1 to match table height in 1.x
+	var ow = 0;
+	var oh = 0;
+	var td = (this.state != null) ? this.state.view.textDiv : null;
+	var sizeDiv = this.node;
+	
+	// Fallback for hidden text rendering in IE quirks mode
+	if (td != null)
+	{
+		td.style.overflow = '';
+		td.style.height = '';
+		td.style.width = '';
+		
+		this.updateFont(td);
+		this.updateSize(td, false);
+		this.updateInnerHtml(td);
+		
+		var w = Math.round(this.bounds.width / this.scale);
+
+		if (this.wrap && w > 0)
+		{
+			td.style.whiteSpace = 'normal';
+			td.style.wordWrap = mxConstants.WORD_WRAP;
+			ow = w;
+			
+			if (this.clipped)
+			{
+				ow = Math.min(ow, this.bounds.width);
+			}
+
+			td.style.width = ow + 'px';
+		}
+		else
+		{
+			td.style.whiteSpace = 'nowrap';
+		}
+		
+		sizeDiv = td;
+		
+		if (sizeDiv.firstChild != null && sizeDiv.firstChild.nodeName == 'DIV')
+		{
+			sizeDiv = sizeDiv.firstChild;
+			
+			if (this.wrap && td.style.wordWrap == 'break-word')
+			{
+				sizeDiv.style.width = '100%';
+			}
+		}
+
+		// Required to update the height of the text box after wrapping width is known 
+		if (!this.clipped && this.wrap && w > 0)
+		{
+			ow = sizeDiv.offsetWidth + this.textWidthPadding;
+			td.style.width = ow + 'px';
+		}
+		
+		oh = sizeDiv.offsetHeight + 2;
+		
+		if (mxClient.IS_QUIRKS && this.border != null && this.border != mxConstants.NONE)
+		{
+			oh += 3;
+		}
+	}
+	else if (sizeDiv.firstChild != null && sizeDiv.firstChild.nodeName == 'DIV')
+	{
+		sizeDiv = sizeDiv.firstChild;
+		oh = sizeDiv.offsetHeight;
+	}
+
+	ow = sizeDiv.offsetWidth + this.textWidthPadding;
+	
+	if (this.clipped)
+	{
+		oh = Math.min(oh, this.bounds.height);
+	}
+
+	var w = this.bounds.width / s;
+	var h = this.bounds.height / s;
+
+	// Handles special case for live preview with no wrapper DIV and no textDiv
+	if (this.overflow == 'fill')
+	{
+		oh = h;
+		ow = w;
+	}
+	else if (this.overflow == 'width')
+	{
+		oh = sizeDiv.scrollHeight;
+		ow = w;
+	}
+	
+	// Stores for later use
+	this.offsetWidth = ow;
+	this.offsetHeight = oh;
+	
+	// Simulates max-height CSS in quirks mode
+	if (mxClient.IS_QUIRKS && (this.clipped || (this.overflow == 'width' && h > 0)))
+	{
+		h = Math.min(h, oh);
+		style.height = Math.round(h) + 'px';
+	}
+	else
+	{
+		h = oh;
+	}
+
+	if (this.overflow != 'fill' && this.overflow != 'width')
+	{
+		if (this.clipped)
+		{
+			ow = Math.min(w, ow);
+		}
+		
+		w = ow;
+
+		// Simulates max-width CSS in quirks mode
+		if ((mxClient.IS_QUIRKS && this.clipped) || this.wrap)
+		{
+			style.width = Math.round(w) + 'px';
+		}
+	}
+
+	h *= s;
+	w *= s;
+	
+	// Rotation case is handled via VML canvas
+	var rad = this.getTextRotation() * (Math.PI / 180);
+	
+	// Precalculate cos and sin for the rotation
+	var real_cos = parseFloat(parseFloat(Math.cos(rad)).toFixed(8));
+	var real_sin = parseFloat(parseFloat(Math.sin(-rad)).toFixed(8));
+
+	rad %= 2 * Math.PI;
+	
+	if (rad < 0)
+	{
+		rad += 2 * Math.PI;
+	}
+	
+	rad %= Math.PI;
+	
+	if (rad > Math.PI / 2)
+	{
+		rad = Math.PI - rad;
+	}
+	
+	var cos = Math.cos(rad);
+	var sin = Math.sin(-rad);
+
+	var tx = w * -(dx + 0.5);
+	var ty = h * -(dy + 0.5);
+
+	var top_fix = (h - h * cos + w * sin) / 2 + real_sin * tx - real_cos * ty;
+	var left_fix = (w - w * cos + h * sin) / 2 - real_cos * tx - real_sin * ty;
+	
+	if (rad != 0)
+	{
+		var f = 'progid:DXImageTransform.Microsoft.Matrix(M11=' + real_cos + ', M12='+
+			real_sin + ', M21=' + (-real_sin) + ', M22=' + real_cos + ', sizingMethod=\'auto expand\')';
+		
+		if (style.filter != null && style.filter.length > 0)
+		{
+			style.filter += ' ' + f;
+		}
+		else
+		{
+			style.filter = f;
+		}
+	}
+	
+	// Workaround for rendering offsets
+	var dy = 0;
+	
+	if (this.overflow != 'fill' && mxClient.IS_QUIRKS)
+	{
+		if (this.valign == mxConstants.ALIGN_TOP)
+		{
+			dy -= 1;
+		}
+		else if (this.valign == mxConstants.ALIGN_BOTTOM)
+		{
+			dy += 2;
+		}
+		else
+		{
+			dy += 1;
+		}
+	}
+
+	style.zoom = s;
+	style.left = Math.round(this.bounds.x + left_fix - w / 2) + 'px';
+	style.top = Math.round(this.bounds.y + top_fix - h / 2 + dy) + 'px';
+};
+
+/**
+ * Function: updateValue
+ *
+ * Updates the HTML node(s) to reflect the latest bounds and scale.
+ */
+mxText.prototype.updateValue = function()
+{
+	if (mxUtils.isNode(this.value))
+	{
+		this.node.innerHTML = '';
+		this.node.appendChild(this.value);
+	}
+	else
+	{
+		var val = this.value;
+		
+		if (this.dialect != mxConstants.DIALECT_STRICTHTML)
+		{
+			val = mxUtils.htmlEntities(val, false);
+		}
+		
+		// Handles trailing newlines to make sure they are visible in rendering output
+		val = mxUtils.replaceTrailingNewlines(val, '<div><br></div>');
+		val = (this.replaceLinefeeds) ? val.replace(/\n/g, '<br/>') : val;
+		var bg = (this.background != null && this.background != mxConstants.NONE) ? this.background : null;
+		var bd = (this.border != null && this.border != mxConstants.NONE) ? this.border : null;
+
+		if (this.overflow == 'fill' || this.overflow == 'width')
+		{
+			if (bg != null)
+			{
+				this.node.style.backgroundColor = bg;
+			}
+			
+			if (bd != null)
+			{
+				this.node.style.border = '1px solid ' + bd;
+			}
+		}
+		else
+		{
+			var css = '';
+			
+			if (bg != null)
+			{
+				css += 'background-color:' + bg + ';';
+			}
+			
+			if (bd != null)
+			{
+				css += 'border:1px solid ' + bd + ';';
+			}
+			
+			// Wrapper DIV for background, zoom needed for inline in quirks
+			// and to measure wrapped font sizes in all browsers
+			// FIXME: Background size in quirks mode for wrapped text
+			var lh = (mxConstants.ABSOLUTE_LINE_HEIGHT) ? (this.size * mxConstants.LINE_HEIGHT) + 'px' :
+				mxConstants.LINE_HEIGHT;
+			val = '<div style="zoom:1;' + css + 'display:inline-block;_display:inline;text-decoration:inherit;' +
+				'padding-bottom:1px;padding-right:1px;line-height:' + lh + '">' + val + '</div>';
+		}
+
+		this.node.innerHTML = val;
+		
+		// Sets text direction
+		var divs = this.node.getElementsByTagName('div');
+		
+		if (divs.length > 0)
+		{
+			var dir = this.textDirection;
+
+			if (dir == mxConstants.TEXT_DIRECTION_AUTO && this.dialect != mxConstants.DIALECT_STRICTHTML)
+			{
+				dir = this.getAutoDirection();
+			}
+			
+			if (dir == mxConstants.TEXT_DIRECTION_LTR || dir == mxConstants.TEXT_DIRECTION_RTL)
+			{
+				divs[divs.length - 1].setAttribute('dir', dir);
+			}
+			else
+			{
+				divs[divs.length - 1].removeAttribute('dir');
+			}
+		}
+	}
+};
+
+/**
+ * Function: updateFont
+ *
+ * Updates the HTML node(s) to reflect the latest bounds and scale.
+ */
+mxText.prototype.updateFont = function(node)
+{
+	var style = node.style;
+	
+	style.lineHeight = (mxConstants.ABSOLUTE_LINE_HEIGHT) ? (this.size * mxConstants.LINE_HEIGHT) + 'px' : mxConstants.LINE_HEIGHT;
+	style.fontSize = this.size + 'px';
+	style.fontFamily = this.family;
+	style.verticalAlign = 'top';
+	style.color = this.color;
+	
+	if ((this.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
+	{
+		style.fontWeight = 'bold';
+	}
+	else
+	{
+		style.fontWeight = '';
+	}
+
+	if ((this.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
+	{
+		style.fontStyle = 'italic';
+	}
+	else
+	{
+		style.fontStyle = '';
+	}
+	
+	if ((this.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)
+	{
+		style.textDecoration = 'underline';
+	}
+	else
+	{
+		style.textDecoration = '';
+	}
+	
+	if (this.align == mxConstants.ALIGN_CENTER)
+	{
+		style.textAlign = 'center';
+	}
+	else if (this.align == mxConstants.ALIGN_RIGHT)
+	{
+		style.textAlign = 'right';
+	}
+	else
+	{
+		style.textAlign = 'left';
+	}
+};
+
+/**
+ * Function: updateSize
+ *
+ * Updates the HTML node(s) to reflect the latest bounds and scale.
+ */
+mxText.prototype.updateSize = function(node, enableWrap)
+{
+	var w = Math.max(0, Math.round(this.bounds.width / this.scale));
+	var h = Math.max(0, Math.round(this.bounds.height / this.scale));
+	var style = node.style;
+	
+	// NOTE: Do not use maxWidth here because wrapping will
+	// go wrong if the cell is outside of the viewable area
+	if (this.clipped)
+	{
+		style.overflow = 'hidden';
+		
+		if (!mxClient.IS_QUIRKS)
+		{
+			style.maxHeight = h + 'px';
+			style.maxWidth = w + 'px';
+		}
+		else
+		{
+			style.width = w + 'px';
+		}
+	}
+	else if (this.overflow == 'fill')
+	{
+		style.width = (w + 1) + 'px';
+		style.height = (h + 1) + 'px';
+		style.overflow = 'hidden';
+	}
+	else if (this.overflow == 'width')
+	{
+		style.width = (w + 1) + 'px';
+		style.maxHeight = (h + 1) + 'px';
+		style.overflow = 'hidden';
+	}
+	
+	if (this.wrap && w > 0)
+	{
+		style.wordWrap = mxConstants.WORD_WRAP;
+		style.whiteSpace = 'normal';
+		style.width = w + 'px';
+
+		if (enableWrap && this.overflow != 'fill' && this.overflow != 'width')
+		{
+			var sizeDiv = node;
+			
+			if (sizeDiv.firstChild != null && sizeDiv.firstChild.nodeName == 'DIV')
+			{
+				sizeDiv = sizeDiv.firstChild;
+				
+				if (node.style.wordWrap == 'break-word')
+				{
+					sizeDiv.style.width = '100%';
+				}
+			}
+			
+			var tmp = sizeDiv.offsetWidth;
+			
+			// Workaround for text measuring in hidden containers
+			if (tmp == 0)
+			{
+				var prev = node.parentNode;
+				node.style.visibility = 'hidden';
+				document.body.appendChild(node);
+				tmp = sizeDiv.offsetWidth;
+				node.style.visibility = '';
+				prev.appendChild(node);
+			}
+
+			tmp += 3;
+			
+			if (this.clipped)
+			{
+				tmp = Math.min(tmp, w);
+			}
+			
+			style.width = tmp + 'px';
+		}
+	}
+	else
+	{
+		style.whiteSpace = 'nowrap';
+	}
+};
+
+/**
+ * Function: getMargin
+ *
+ * Returns the spacing as an <mxPoint>.
+ */
+mxText.prototype.updateMargin = function()
+{
+	this.margin = mxUtils.getAlignmentAsPoint(this.align, this.valign);
+};
+
+/**
+ * Function: getSpacing
+ *
+ * Returns the spacing as an <mxPoint>.
+ */
+mxText.prototype.getSpacing = function()
+{
+	var dx = 0;
+	var dy = 0;
+
+	if (this.align == mxConstants.ALIGN_CENTER)
+	{
+		dx = (this.spacingLeft - this.spacingRight) / 2;
+	}
+	else if (this.align == mxConstants.ALIGN_RIGHT)
+	{
+		dx = -this.spacingRight - this.baseSpacingRight;
+	}
+	else
+	{
+		dx = this.spacingLeft + this.baseSpacingLeft;
+	}
+
+	if (this.valign == mxConstants.ALIGN_MIDDLE)
+	{
+		dy = (this.spacingTop - this.spacingBottom) / 2;
+	}
+	else if (this.valign == mxConstants.ALIGN_BOTTOM)
+	{
+		dy = -this.spacingBottom - this.baseSpacingBottom;;
+	}
+	else
+	{
+		dy = this.spacingTop + this.baseSpacingTop;
+	}
+	
+	return new mxPoint(dx, dy);
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/shape/mxTriangle.js b/airavata-kubernetes/workflow-composer/src/js/shape/mxTriangle.js
new file mode 100644
index 0000000..4c345e6
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/shape/mxTriangle.js
@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxTriangle
+ * 
+ * Implementation of the triangle shape.
+ * 
+ * Constructor: mxTriangle
+ *
+ * Constructs a new triangle shape.
+ */
+function mxTriangle()
+{
+	mxActor.call(this);
+};
+
+/**
+ * Extends mxActor.
+ */
+mxUtils.extend(mxTriangle, mxActor);
+
+/**
+ * Function: redrawPath
+ *
+ * Draws the path for this shape.
+ */
+mxTriangle.prototype.redrawPath = function(c, x, y, w, h)
+{
+	var arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
+	this.addPoints(c, [new mxPoint(0, 0), new mxPoint(w, 0.5 * h), new mxPoint(0, h)], this.isRounded, arcSize, true);
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/util/mxAbstractCanvas2D.js b/airavata-kubernetes/workflow-composer/src/js/util/mxAbstractCanvas2D.js
new file mode 100644
index 0000000..e9447f3
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/util/mxAbstractCanvas2D.js
@@ -0,0 +1,642 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxAbstractCanvas2D
+ *
+ * Base class for all canvases. A description of the public API is available in <mxXmlCanvas2D>.
+ * All color values of <mxConstants.NONE> will be converted to null in the state.
+ * 
+ * Constructor: mxAbstractCanvas2D
+ *
+ * Constructs a new abstract canvas.
+ */
+function mxAbstractCanvas2D()
+{
+	/**
+	 * Variable: converter
+	 * 
+	 * Holds the <mxUrlConverter> to convert image URLs.
+	 */
+	this.converter = this.createUrlConverter();
+	
+	this.reset();
+};
+
+/**
+ * Variable: state
+ * 
+ * Holds the current state.
+ */
+mxAbstractCanvas2D.prototype.state = null;
+
+/**
+ * Variable: states
+ * 
+ * Stack of states.
+ */
+mxAbstractCanvas2D.prototype.states = null;
+
+/**
+ * Variable: path
+ * 
+ * Holds the current path as an array.
+ */
+mxAbstractCanvas2D.prototype.path = null;
+
+/**
+ * Variable: rotateHtml
+ * 
+ * Switch for rotation of HTML. Default is false.
+ */
+mxAbstractCanvas2D.prototype.rotateHtml = true;
+
+/**
+ * Variable: lastX
+ * 
+ * Holds the last x coordinate.
+ */
+mxAbstractCanvas2D.prototype.lastX = 0;
+
+/**
+ * Variable: lastY
+ * 
+ * Holds the last y coordinate.
+ */
+mxAbstractCanvas2D.prototype.lastY = 0;
+
+/**
+ * Variable: moveOp
+ * 
+ * Contains the string used for moving in paths. Default is 'M'.
+ */
+mxAbstractCanvas2D.prototype.moveOp = 'M';
+
+/**
+ * Variable: lineOp
+ * 
+ * Contains the string used for moving in paths. Default is 'L'.
+ */
+mxAbstractCanvas2D.prototype.lineOp = 'L';
+
+/**
+ * Variable: quadOp
+ * 
+ * Contains the string used for quadratic paths. Default is 'Q'.
+ */
+mxAbstractCanvas2D.prototype.quadOp = 'Q';
+
+/**
+ * Variable: curveOp
+ * 
+ * Contains the string used for bezier curves. Default is 'C'.
+ */
+mxAbstractCanvas2D.prototype.curveOp = 'C';
+
+/**
+ * Variable: closeOp
+ * 
+ * Holds the operator for closing curves. Default is 'Z'.
+ */
+mxAbstractCanvas2D.prototype.closeOp = 'Z';
+
+/**
+ * Variable: pointerEvents
+ * 
+ * Boolean value that specifies if events should be handled. Default is false.
+ */
+mxAbstractCanvas2D.prototype.pointerEvents = false;
+
+/**
+ * Function: createUrlConverter
+ * 
+ * Create a new <mxUrlConverter> and returns it.
+ */
+mxAbstractCanvas2D.prototype.createUrlConverter = function()
+{
+	return new mxUrlConverter();
+};
+
+/**
+ * Function: reset
+ * 
+ * Resets the state of this canvas.
+ */
+mxAbstractCanvas2D.prototype.reset = function()
+{
+	this.state = this.createState();
+	this.states = [];
+};
+
+/**
+ * Function: createState
+ * 
+ * Creates the state of the this canvas.
+ */
+mxAbstractCanvas2D.prototype.createState = function()
+{
+	return {
+		dx: 0,
+		dy: 0,
+		scale: 1,
+		alpha: 1,
+		fillAlpha: 1,
+		strokeAlpha: 1,
+		fillColor: null,
+		gradientFillAlpha: 1,
+		gradientColor: null,
+		gradientAlpha: 1,
+		gradientDirection: null,
+		strokeColor: null,
+		strokeWidth: 1,
+		dashed: false,
+		dashPattern: '3 3',
+		fixDash: false,
+		lineCap: 'flat',
+		lineJoin: 'miter',
+		miterLimit: 10,
+		fontColor: '#000000',
+		fontBackgroundColor: null,
+		fontBorderColor: null,
+		fontSize: mxConstants.DEFAULT_FONTSIZE,
+		fontFamily: mxConstants.DEFAULT_FONTFAMILY,
+		fontStyle: 0,
+		shadow: false,
+		shadowColor: mxConstants.SHADOWCOLOR,
+		shadowAlpha: mxConstants.SHADOW_OPACITY,
+		shadowDx: mxConstants.SHADOW_OFFSET_X,
+		shadowDy: mxConstants.SHADOW_OFFSET_Y,
+		rotation: 0,
+		rotationCx: 0,
+		rotationCy: 0
+	};
+};
+
+/**
+ * Function: format
+ * 
+ * Rounds all numbers to integers.
+ */
+mxAbstractCanvas2D.prototype.format = function(value)
+{
+	return Math.round(parseFloat(value));
+};
+
+/**
+ * Function: addOp
+ * 
+ * Adds the given operation to the path.
+ */
+mxAbstractCanvas2D.prototype.addOp = function()
+{
+	if (this.path != null)
+	{
+		this.path.push(arguments[0]);
+		
+		if (arguments.length > 2)
+		{
+			var s = this.state;
+
+			for (var i = 2; i < arguments.length; i += 2)
+			{
+				this.lastX = arguments[i - 1];
+				this.lastY = arguments[i];
+				
+				this.path.push(this.format((this.lastX + s.dx) * s.scale));
+				this.path.push(this.format((this.lastY + s.dy) * s.scale));
+			}
+		}
+	}
+};
+
+/**
+ * Function: rotatePoint
+ * 
+ * Rotates the given point and returns the result as an <mxPoint>.
+ */
+mxAbstractCanvas2D.prototype.rotatePoint = function(x, y, theta, cx, cy)
+{
+	var rad = theta * (Math.PI / 180);
+	
+	return mxUtils.getRotatedPoint(new mxPoint(x, y), Math.cos(rad),
+		Math.sin(rad), new mxPoint(cx, cy));
+};
+
+/**
+ * Function: save
+ * 
+ * Saves the current state.
+ */
+mxAbstractCanvas2D.prototype.save = function()
+{
+	this.states.push(this.state);
+	this.state = mxUtils.clone(this.state);
+};
+
+/**
+ * Function: restore
+ * 
+ * Restores the current state.
+ */
+mxAbstractCanvas2D.prototype.restore = function()
+{
+	if (this.states.length > 0)
+	{
+		this.state = this.states.pop();
+	}
+};
+
+/**
+ * Function: setLink
+ * 
+ * Sets the current link. Hook for subclassers.
+ */
+mxAbstractCanvas2D.prototype.setLink = function(link)
+{
+	// nop
+};
+
+/**
+ * Function: scale
+ * 
+ * Scales the current state.
+ */
+mxAbstractCanvas2D.prototype.scale = function(value)
+{
+	this.state.scale *= value;
+	this.state.strokeWidth *= value;
+};
+
+/**
+ * Function: translate
+ * 
+ * Translates the current state.
+ */
+mxAbstractCanvas2D.prototype.translate = function(dx, dy)
+{
+	this.state.dx += dx;
+	this.state.dy += dy;
+};
+
+/**
+ * Function: rotate
+ * 
+ * Rotates the current state.
+ */
+mxAbstractCanvas2D.prototype.rotate = function(theta, flipH, flipV, cx, cy)
+{
+	// nop
+};
+
+/**
+ * Function: setAlpha
+ * 
+ * Sets the current alpha.
+ */
+mxAbstractCanvas2D.prototype.setAlpha = function(value)
+{
+	this.state.alpha = value;
+};
+
+/**
+ * Function: setFillAlpha
+ * 
+ * Sets the current solid fill alpha.
+ */
+mxAbstractCanvas2D.prototype.setFillAlpha = function(value)
+{
+	this.state.fillAlpha = value;
+};
+
+/**
+ * Function: setStrokeAlpha
+ * 
+ * Sets the current stroke alpha.
+ */
+mxAbstractCanvas2D.prototype.setStrokeAlpha = function(value)
+{
+	this.state.strokeAlpha = value;
+};
+
+/**
+ * Function: setFillColor
+ * 
+ * Sets the current fill color.
+ */
+mxAbstractCanvas2D.prototype.setFillColor = function(value)
+{
+	if (value == mxConstants.NONE)
+	{
+		value = null;
+	}
+	
+	this.state.fillColor = value;
+	this.state.gradientColor = null;
+};
+
+/**
+ * Function: setGradient
+ * 
+ * Sets the current gradient.
+ */
+mxAbstractCanvas2D.prototype.setGradient = function(color1, color2, x, y, w, h, direction, alpha1, alpha2)
+{
+	var s = this.state;
+	s.fillColor = color1;
+	s.gradientFillAlpha = (alpha1 != null) ? alpha1 : 1;
+	s.gradientColor = color2;
+	s.gradientAlpha = (alpha2 != null) ? alpha2 : 1;
+	s.gradientDirection = direction;
+};
+
+/**
+ * Function: setStrokeColor
+ * 
+ * Sets the current stroke color.
+ */
+mxAbstractCanvas2D.prototype.setStrokeColor = function(value)
+{
+	if (value == mxConstants.NONE)
+	{
+		value = null;
+	}
+	
+	this.state.strokeColor = value;
+};
+
+/**
+ * Function: setStrokeWidth
+ * 
+ * Sets the current stroke width.
+ */
+mxAbstractCanvas2D.prototype.setStrokeWidth = function(value)
+{
+	this.state.strokeWidth = value;
+};
+
+/**
+ * Function: setDashed
+ * 
+ * Enables or disables dashed lines.
+ */
+mxAbstractCanvas2D.prototype.setDashed = function(value, fixDash)
+{
+	this.state.dashed = value;
+	this.state.fixDash = fixDash;
+};
+
+/**
+ * Function: setDashPattern
+ * 
+ * Sets the current dash pattern.
+ */
+mxAbstractCanvas2D.prototype.setDashPattern = function(value)
+{
+	this.state.dashPattern = value;
+};
+
+/**
+ * Function: setLineCap
+ * 
+ * Sets the current line cap.
+ */
+mxAbstractCanvas2D.prototype.setLineCap = function(value)
+{
+	this.state.lineCap = value;
+};
+
+/**
+ * Function: setLineJoin
+ * 
+ * Sets the current line join.
+ */
+mxAbstractCanvas2D.prototype.setLineJoin = function(value)
+{
+	this.state.lineJoin = value;
+};
+
+/**
+ * Function: setMiterLimit
+ * 
+ * Sets the current miter limit.
+ */
+mxAbstractCanvas2D.prototype.setMiterLimit = function(value)
+{
+	this.state.miterLimit = value;
+};
+
+/**
+ * Function: setFontColor
+ * 
+ * Sets the current font color.
+ */
+mxAbstractCanvas2D.prototype.setFontColor = function(value)
+{
+	if (value == mxConstants.NONE)
+	{
+		value = null;
+	}
+	
+	this.state.fontColor = value;
+};
+
+/**
+ * Function: setFontColor
+ * 
+ * Sets the current font color.
+ */
+mxAbstractCanvas2D.prototype.setFontBackgroundColor = function(value)
+{
+	if (value == mxConstants.NONE)
+	{
+		value = null;
+	}
+	
+	this.state.fontBackgroundColor = value;
+};
+
+/**
+ * Function: setFontColor
+ * 
+ * Sets the current font color.
+ */
+mxAbstractCanvas2D.prototype.setFontBorderColor = function(value)
+{
+	if (value == mxConstants.NONE)
+	{
+		value = null;
+	}
+	
+	this.state.fontBorderColor = value;
+};
+
+/**
+ * Function: setFontSize
+ * 
+ * Sets the current font size.
+ */
+mxAbstractCanvas2D.prototype.setFontSize = function(value)
+{
+	this.state.fontSize = parseFloat(value);
+};
+
+/**
+ * Function: setFontFamily
+ * 
+ * Sets the current font family.
+ */
+mxAbstractCanvas2D.prototype.setFontFamily = function(value)
+{
+	this.state.fontFamily = value;
+};
+
+/**
+ * Function: setFontStyle
+ * 
+ * Sets the current font style.
+ */
+mxAbstractCanvas2D.prototype.setFontStyle = function(value)
+{
+	if (value == null)
+	{
+		value = 0;
+	}
+	
+	this.state.fontStyle = value;
+};
+
+/**
+ * Function: setShadow
+ * 
+ * Enables or disables and configures the current shadow.
+ */
+mxAbstractCanvas2D.prototype.setShadow = function(enabled)
+{
+	this.state.shadow = enabled;
+};
+
+/**
+ * Function: setShadowColor
+ * 
+ * Enables or disables and configures the current shadow.
+ */
+mxAbstractCanvas2D.prototype.setShadowColor = function(value)
+{
+	if (value == mxConstants.NONE)
+	{
+		value = null;
+	}
+	
+	this.state.shadowColor = value;
+};
+
+/**
+ * Function: setShadowAlpha
+ * 
+ * Enables or disables and configures the current shadow.
+ */
+mxAbstractCanvas2D.prototype.setShadowAlpha = function(value)
+{
+	this.state.shadowAlpha = value;
+};
+
+/**
+ * Function: setShadowOffset
+ * 
+ * Enables or disables and configures the current shadow.
+ */
+mxAbstractCanvas2D.prototype.setShadowOffset = function(dx, dy)
+{
+	this.state.shadowDx = dx;
+	this.state.shadowDy = dy;
+};
+
+/**
+ * Function: begin
+ * 
+ * Starts a new path.
+ */
+mxAbstractCanvas2D.prototype.begin = function()
+{
+	this.lastX = 0;
+	this.lastY = 0;
+	this.path = [];
+};
+
+/**
+ * Function: moveTo
+ * 
+ *  Moves the current path the given coordinates.
+ */
+mxAbstractCanvas2D.prototype.moveTo = function(x, y)
+{
+	this.addOp(this.moveOp, x, y);
+};
+
+/**
+ * Function: lineTo
+ * 
+ * Draws a line to the given coordinates. Uses moveTo with the op argument.
+ */
+mxAbstractCanvas2D.prototype.lineTo = function(x, y)
+{
+	this.addOp(this.lineOp, x, y);
+};
+
+/**
+ * Function: quadTo
+ * 
+ * Adds a quadratic curve to the current path.
+ */
+mxAbstractCanvas2D.prototype.quadTo = function(x1, y1, x2, y2)
+{
+	this.addOp(this.quadOp, x1, y1, x2, y2);
+};
+
+/**
+ * Function: curveTo
+ * 
+ * Adds a bezier curve to the current path.
+ */
+mxAbstractCanvas2D.prototype.curveTo = function(x1, y1, x2, y2, x3, y3)
+{
+	this.addOp(this.curveOp, x1, y1, x2, y2, x3, y3);
+};
+
+/**
+ * Function: arcTo
+ * 
+ * Adds the given arc to the current path. This is a synthetic operation that
+ * is broken down into curves.
+ */
+mxAbstractCanvas2D.prototype.arcTo = function(rx, ry, angle, largeArcFlag, sweepFlag, x, y)
+{
+	var curves = mxUtils.arcToCurves(this.lastX, this.lastY, rx, ry, angle, largeArcFlag, sweepFlag, x, y);
+	
+	if (curves != null)
+	{
+		for (var i = 0; i < curves.length; i += 6) 
+		{
+			this.curveTo(curves[i], curves[i + 1], curves[i + 2],
+				curves[i + 3], curves[i + 4], curves[i + 5]);
+		}
+	}
+};
+
+/**
+ * Function: close
+ * 
+ * Closes the current path.
+ */
+mxAbstractCanvas2D.prototype.close = function(x1, y1, x2, y2, x3, y3)
+{
+	this.addOp(this.closeOp);
+};
+
+/**
+ * Function: end
+ * 
+ * Empty implementation for backwards compatibility. This will be removed.
+ */
+mxAbstractCanvas2D.prototype.end = function() { };
diff --git a/airavata-kubernetes/workflow-composer/src/js/util/mxAnimation.js b/airavata-kubernetes/workflow-composer/src/js/util/mxAnimation.js
new file mode 100644
index 0000000..eabd9c3
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/util/mxAnimation.js
@@ -0,0 +1,92 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ *
+ * Class: mxAnimation
+ * 
+ * Implements a basic animation in JavaScript.
+ * 
+ * Constructor: mxAnimation
+ * 
+ * Constructs an animation.
+ * 
+ * Parameters:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ */
+function mxAnimation(delay)
+{
+	this.delay = (delay != null) ? delay : 20;
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxAnimation.prototype = new mxEventSource();
+mxAnimation.prototype.constructor = mxAnimation;
+
+/**
+ * Variable: delay
+ * 
+ * Specifies the delay between the animation steps. Defaul is 30ms.
+ */
+mxAnimation.prototype.delay = null;
+
+/**
+ * Variable: thread
+ * 
+ * Reference to the thread while the animation is running.
+ */
+mxAnimation.prototype.thread = null;
+
+/**
+ * Function: isRunning
+ * 
+ * Returns true if the animation is running.
+ */
+mxAnimation.prototype.isRunning = function()
+{
+	return this.thread != null;
+};
+
+/**
+ * Function: startAnimation
+ *
+ * Starts the animation by repeatedly invoking updateAnimation.
+ */
+mxAnimation.prototype.startAnimation = function()
+{
+	if (this.thread == null)
+	{
+		this.thread = window.setInterval(mxUtils.bind(this, this.updateAnimation), this.delay);
+	}
+};
+
+/**
+ * Function: updateAnimation
+ *
+ * Hook for subclassers to implement the animation. Invoke stopAnimation
+ * when finished, startAnimation to resume. This is called whenever the
+ * timer fires and fires an mxEvent.EXECUTE event with no properties.
+ */
+mxAnimation.prototype.updateAnimation = function()
+{
+	this.fireEvent(new mxEventObject(mxEvent.EXECUTE));
+};
+
+/**
+ * Function: stopAnimation
+ *
+ * Stops the animation by deleting the timer and fires an <mxEvent.DONE>.
+ */
+mxAnimation.prototype.stopAnimation = function()
+{
+	if (this.thread != null)
+	{
+		window.clearInterval(this.thread);
+		this.thread = null;
+		this.fireEvent(new mxEventObject(mxEvent.DONE));
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/util/mxAutoSaveManager.js b/airavata-kubernetes/workflow-composer/src/js/util/mxAutoSaveManager.js
new file mode 100644
index 0000000..ba9a41f
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/util/mxAutoSaveManager.js
@@ -0,0 +1,213 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxAutoSaveManager
+ * 
+ * Manager for automatically saving diagrams. The <save> hook must be
+ * implemented.
+ * 
+ * Example:
+ * 
+ * (code)
+ * var mgr = new mxAutoSaveManager(editor.graph);
+ * mgr.save = function()
+ * {
+ *   mxLog.show();
+ *   mxLog.debug('save');
+ * };
+ * (end)
+ * 
+ * Constructor: mxAutoSaveManager
+ *
+ * Constructs a new automatic layout for the given graph.
+ *
+ * Arguments:
+ * 
+ * graph - Reference to the enclosing graph. 
+ */
+function mxAutoSaveManager(graph)
+{
+	// Notifies the manager of a change
+	this.changeHandler = mxUtils.bind(this, function(sender, evt)
+	{
+		if (this.isEnabled())
+		{
+			this.graphModelChanged(evt.getProperty('edit').changes);
+		}
+	});
+
+	this.setGraph(graph);
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxAutoSaveManager.prototype = new mxEventSource();
+mxAutoSaveManager.prototype.constructor = mxAutoSaveManager;
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxAutoSaveManager.prototype.graph = null;
+
+/**
+ * Variable: autoSaveDelay
+ * 
+ * Minimum amount of seconds between two consecutive autosaves. Eg. a
+ * value of 1 (s) means the graph is not stored more than once per second.
+ * Default is 10.
+ */
+mxAutoSaveManager.prototype.autoSaveDelay = 10;
+
+/**
+ * Variable: autoSaveThrottle
+ * 
+ * Minimum amount of seconds between two consecutive autosaves triggered by
+ * more than <autoSaveThreshhold> changes within a timespan of less than
+ * <autoSaveDelay> seconds. Eg. a value of 1 (s) means the graph is not
+ * stored more than once per second even if there are more than
+ * <autoSaveThreshold> changes within that timespan. Default is 2.
+ */
+mxAutoSaveManager.prototype.autoSaveThrottle = 2;
+
+/**
+ * Variable: autoSaveThreshold
+ * 
+ * Minimum amount of ignored changes before an autosave. Eg. a value of 2
+ * means after 2 change of the graph model the autosave will trigger if the
+ * condition below is true. Default is 5.
+ */
+mxAutoSaveManager.prototype.autoSaveThreshold = 5;
+
+/**
+ * Variable: ignoredChanges
+ * 
+ * Counter for ignored changes in autosave.
+ */
+mxAutoSaveManager.prototype.ignoredChanges = 0;
+
+/**
+ * Variable: lastSnapshot
+ * 
+ * Used for autosaving. See <autosave>.
+ */
+mxAutoSaveManager.prototype.lastSnapshot = 0;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies if event handling is enabled. Default is true.
+ */
+mxAutoSaveManager.prototype.enabled = true;
+
+/**
+ * Variable: changeHandler
+ * 
+ * Holds the function that handles graph model changes.
+ */
+mxAutoSaveManager.prototype.changeHandler = null;
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxAutoSaveManager.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ * 
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ * 
+ * Parameters:
+ * 
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxAutoSaveManager.prototype.setEnabled = function(value)
+{
+	this.enabled = value;
+};
+
+/**
+ * Function: setGraph
+ * 
+ * Sets the graph that the layouts operate on.
+ */
+mxAutoSaveManager.prototype.setGraph = function(graph)
+{
+	if (this.graph != null)
+	{
+		this.graph.getModel().removeListener(this.changeHandler);
+	}
+	
+	this.graph = graph;
+	
+	if (this.graph != null)
+	{
+		this.graph.getModel().addListener(mxEvent.CHANGE, this.changeHandler);
+	}
+};
+
+/**
+ * Function: save
+ * 
+ * Empty hook that is called if the graph should be saved.
+ */
+mxAutoSaveManager.prototype.save = function()
+{
+	// empty
+};
+
+/**
+ * Function: graphModelChanged
+ * 
+ * Invoked when the graph model has changed.
+ */
+mxAutoSaveManager.prototype.graphModelChanged = function(changes)
+{
+	var now = new Date().getTime();
+	var dt = (now - this.lastSnapshot) / 1000;
+	
+	if (dt > this.autoSaveDelay ||
+		(this.ignoredChanges >= this.autoSaveThreshold &&
+		 dt > this.autoSaveThrottle))
+	{
+		this.save();
+		this.reset();
+	}
+	else
+	{
+		// Increments the number of ignored changes
+		this.ignoredChanges++;
+	}
+};
+
+/**
+ * Function: reset
+ * 
+ * Resets all counters.
+ */
+mxAutoSaveManager.prototype.reset = function()
+{
+	this.lastSnapshot = new Date().getTime();
+	this.ignoredChanges = 0;
+};
+
+/**
+ * Function: destroy
+ * 
+ * Removes all handlers from the <graph> and deletes the reference to it.
+ */
+mxAutoSaveManager.prototype.destroy = function()
+{
+	this.setGraph(null);
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/util/mxClipboard.js b/airavata-kubernetes/workflow-composer/src/js/util/mxClipboard.js
new file mode 100644
index 0000000..454e2e7
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/util/mxClipboard.js
@@ -0,0 +1,221 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+var mxClipboard =
+{
+	/**
+	 * Class: mxClipboard
+	 * 
+	 * Singleton that implements a clipboard for graph cells.
+	 *
+	 * Example:
+	 * 
+	 * (code)
+	 * mxClipboard.copy(graph);
+	 * mxClipboard.paste(graph2);
+	 * (end)
+	 *
+	 * This copies the selection cells from the graph to the clipboard and
+	 * pastes them into graph2.
+	 * 
+	 * For fine-grained control of the clipboard data the <mxGraph.canExportCell>
+	 * and <mxGraph.canImportCell> functions can be overridden.
+	 * 
+	 * To restore previous parents for pasted cells, the implementation for
+	 * <copy> and <paste> can be changed as follows.
+	 * 
+	 * (code)
+	 * mxClipboard.copy = function(graph, cells)
+	 * {
+	 *   cells = cells || graph.getSelectionCells();
+	 *   var result = graph.getExportableCells(cells);
+	 *   
+	 *   mxClipboard.parents = new Object();
+	 *   
+	 *   for (var i = 0; i < result.length; i++)
+	 *   {
+	 *     mxClipboard.parents[i] = graph.model.getParent(cells[i]);
+	 *   }
+	 *   
+	 *   mxClipboard.insertCount = 1;
+	 *   mxClipboard.setCells(graph.cloneCells(result));
+	 *   
+	 *   return result;
+	 * };
+	 * 
+	 * mxClipboard.paste = function(graph)
+	 * {
+	 *   if (!mxClipboard.isEmpty())
+	 *   {
+	 *     var cells = graph.getImportableCells(mxClipboard.getCells());
+	 *     var delta = mxClipboard.insertCount * mxClipboard.STEPSIZE;
+	 *     var parent = graph.getDefaultParent();
+	 *     
+	 *     graph.model.beginUpdate();
+	 *     try
+	 *     {
+	 *       for (var i = 0; i < cells.length; i++)
+	 *       {
+	 *         var tmp = (mxClipboard.parents != null && graph.model.contains(mxClipboard.parents[i])) ?
+	 *              mxClipboard.parents[i] : parent;
+	 *         cells[i] = graph.importCells([cells[i]], delta, delta, tmp)[0];
+	 *       }
+	 *     }
+	 *     finally
+	 *     {
+	 *       graph.model.endUpdate();
+	 *     }
+	 *     
+	 *     // Increments the counter and selects the inserted cells
+	 *     mxClipboard.insertCount++;
+	 *     graph.setSelectionCells(cells);
+	 *   }
+	 * };
+	 * (end)
+	 * 
+	 * Variable: STEPSIZE
+	 * 
+	 * Defines the step size to offset the cells after each paste operation.
+	 * Default is 10.
+	 */
+	STEPSIZE: 10,
+
+	/**
+	 * Variable: insertCount
+	 * 
+	 * Counts the number of times the clipboard data has been inserted.
+	 */
+	insertCount: 1,
+
+	/**
+	 * Variable: cells
+	 * 
+	 * Holds the array of <mxCells> currently in the clipboard.
+	 */
+	cells: null,
+
+	/**
+	 * Function: setCells
+	 * 
+	 * Sets the cells in the clipboard. Fires a <mxEvent.CHANGE> event.
+	 */
+	setCells: function(cells)
+	{
+		mxClipboard.cells = cells;
+	},
+
+	/**
+	 * Function: getCells
+	 * 
+	 * Returns  the cells in the clipboard.
+	 */
+	getCells: function()
+	{
+		return mxClipboard.cells;
+	},
+	
+	/**
+	 * Function: isEmpty
+	 * 
+	 * Returns true if the clipboard currently has not data stored.
+	 */
+	isEmpty: function()
+	{
+		return mxClipboard.getCells() == null;
+	},
+	
+	/**
+	 * Function: cut
+	 * 
+	 * Cuts the given array of <mxCells> from the specified graph.
+	 * If cells is null then the selection cells of the graph will
+	 * be used. Returns the cells that have been cut from the graph.
+	 *
+	 * Parameters:
+	 * 
+	 * graph - <mxGraph> that contains the cells to be cut.
+	 * cells - Optional array of <mxCells> to be cut.
+	 */
+	cut: function(graph, cells)
+	{
+		cells = mxClipboard.copy(graph, cells);
+		mxClipboard.insertCount = 0;
+		mxClipboard.removeCells(graph, cells);
+		
+		return cells;
+	},
+
+	/**
+	 * Function: removeCells
+	 * 
+	 * Hook to remove the given cells from the given graph after
+	 * a cut operation.
+	 *
+	 * Parameters:
+	 * 
+	 * graph - <mxGraph> that contains the cells to be cut.
+	 * cells - Array of <mxCells> to be cut.
+	 */
+	removeCells: function(graph, cells)
+	{
+		graph.removeCells(cells);
+	},
+
+	/**
+	 * Function: copy
+	 * 
+	 * Copies the given array of <mxCells> from the specified
+	 * graph to <cells>. Returns the original array of cells that has
+	 * been cloned. Descendants of cells in the array are ignored.
+	 * 
+	 * Parameters:
+	 * 
+	 * graph - <mxGraph> that contains the cells to be copied.
+	 * cells - Optional array of <mxCells> to be copied.
+	 */
+	copy: function(graph, cells)
+	{
+		cells = cells || graph.getSelectionCells();
+		var result = graph.getExportableCells(graph.model.getTopmostCells(cells));
+		mxClipboard.insertCount = 1;
+		mxClipboard.setCells(graph.cloneCells(result));
+
+		return result;
+	},
+
+	/**
+	 * Function: paste
+	 * 
+	 * Pastes the <cells> into the specified graph restoring
+	 * the relation to <parents>, if possible. If the parents
+	 * are no longer in the graph or invisible then the
+	 * cells are added to the graph's default or into the
+	 * swimlane under the cell's new location if one exists.
+	 * The cells are added to the graph using <mxGraph.importCells>
+	 * and returned.
+	 * 
+	 * Parameters:
+	 * 
+	 * graph - <mxGraph> to paste the <cells> into.
+	 */
+	paste: function(graph)
+	{
+		var cells = null;
+		
+		if (!mxClipboard.isEmpty())
+		{
+			cells = graph.getImportableCells(mxClipboard.getCells());
+			var delta = mxClipboard.insertCount * mxClipboard.STEPSIZE;
+			var parent = graph.getDefaultParent();
+			cells = graph.importCells(cells, delta, delta, parent);
+			
+			// Increments the counter and selects the inserted cells
+			mxClipboard.insertCount++;
+			graph.setSelectionCells(cells);
+		}
+		
+		return cells;
+	}
+
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/util/mxConstants.js b/airavata-kubernetes/workflow-composer/src/js/util/mxConstants.js
new file mode 100644
index 0000000..c448644
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/util/mxConstants.js
@@ -0,0 +1,2275 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+ var mxConstants =
+ {
+	/**
+	 * Class: mxConstants
+	 * 
+	 * Defines various global constants.
+	 * 
+	 * Variable: DEFAULT_HOTSPOT
+	 * 
+	 * Defines the portion of the cell which is to be used as a connectable
+	 * region. Default is 0.3. Possible values are 0 < x <= 1. 
+	 */
+	DEFAULT_HOTSPOT: 0.3,
+
+	/**
+	 * Variable: MIN_HOTSPOT_SIZE
+	 * 
+	 * Defines the minimum size in pixels of the portion of the cell which is
+	 * to be used as a connectable region. Default is 8.
+	 */
+	MIN_HOTSPOT_SIZE: 8,
+
+	/**
+	 * Variable: MAX_HOTSPOT_SIZE
+	 * 
+	 * Defines the maximum size in pixels of the portion of the cell which is
+	 * to be used as a connectable region. Use 0 for no maximum. Default is 0.
+	 */
+	MAX_HOTSPOT_SIZE: 0,
+
+	/**
+	 * Variable: RENDERING_HINT_EXACT
+	 * 
+	 * Defines the exact rendering hint.
+	 */
+	RENDERING_HINT_EXACT: 'exact',
+
+	/**
+	 * Variable: RENDERING_HINT_FASTER
+	 * 
+	 * Defines the faster rendering hint.
+	 */
+	RENDERING_HINT_FASTER: 'faster',
+
+	/**
+	 * Variable: RENDERING_HINT_FASTEST
+	 * 
+	 * Defines the fastest rendering hint.
+	 */
+	RENDERING_HINT_FASTEST: 'fastest',
+
+	/**
+	 * Variable: DIALECT_SVG
+	 * 
+	 * Defines the SVG display dialect name.
+	 */
+	DIALECT_SVG: 'svg',
+
+	/**
+	 * Variable: DIALECT_VML
+	 * 
+	 * Defines the VML display dialect name.
+	 */
+	DIALECT_VML: 'vml',
+
+	/**
+	 * Variable: DIALECT_MIXEDHTML
+	 * 
+	 * Defines the mixed HTML display dialect name.
+	 */
+	DIALECT_MIXEDHTML: 'mixedHtml',
+
+	/**
+	 * Variable: DIALECT_PREFERHTML
+	 * 
+	 * Defines the preferred HTML display dialect name.
+	 */
+	DIALECT_PREFERHTML: 'preferHtml',
+
+	/**
+	 * Variable: DIALECT_STRICTHTML
+	 * 
+	 * Defines the strict HTML display dialect.
+	 */
+	DIALECT_STRICTHTML: 'strictHtml',
+
+	/**
+	 * Variable: NS_SVG
+	 * 
+	 * Defines the SVG namespace.
+	 */
+	NS_SVG: 'http://www.w3.org/2000/svg',
+
+	/**
+	 * Variable: NS_XHTML
+	 * 
+	 * Defines the XHTML namespace.
+	 */
+	NS_XHTML: 'http://www.w3.org/1999/xhtml',
+
+	/**
+	 * Variable: NS_XLINK
+	 * 
+	 * Defines the XLink namespace.
+	 */
+	NS_XLINK: 'http://www.w3.org/1999/xlink',
+
+	/**
+	 * Variable: SHADOWCOLOR
+	 * 
+	 * Defines the color to be used to draw shadows in shapes and windows.
+	 * Default is gray.
+	 */
+	SHADOWCOLOR: 'gray',
+
+	/**
+	 * Variable: VML_SHADOWCOLOR
+	 * 
+	 * Used for shadow color in filters where transparency is not supported
+	 * (Microsoft Internet Explorer). Default is gray.
+	 */
+	VML_SHADOWCOLOR: 'gray',
+
+	/**
+	 * Variable: SHADOW_OFFSET_X
+	 * 
+	 * Specifies the x-offset of the shadow. Default is 2.
+	 */
+	SHADOW_OFFSET_X: 2,
+
+	/**
+	 * Variable: SHADOW_OFFSET_Y
+	 * 
+	 * Specifies the y-offset of the shadow. Default is 3.
+	 */
+	SHADOW_OFFSET_Y: 3,
+	
+	/**
+	 * Variable: SHADOW_OPACITY
+	 * 
+	 * Defines the opacity for shadows. Default is 1.
+	 */
+	SHADOW_OPACITY: 1,
+ 
+	/**
+	 * Variable: NODETYPE_ELEMENT
+	 * 
+	 * DOM node of type ELEMENT.
+	 */
+	NODETYPE_ELEMENT: 1,
+
+	/**
+	 * Variable: NODETYPE_ATTRIBUTE
+	 * 
+	 * DOM node of type ATTRIBUTE.
+	 */
+	NODETYPE_ATTRIBUTE: 2,
+
+	/**
+	 * Variable: NODETYPE_TEXT
+	 * 
+	 * DOM node of type TEXT.
+	 */
+	NODETYPE_TEXT: 3,
+
+	/**
+	 * Variable: NODETYPE_CDATA
+	 * 
+	 * DOM node of type CDATA.
+	 */
+	NODETYPE_CDATA: 4,
+	
+	/**
+	 * Variable: NODETYPE_ENTITY_REFERENCE
+	 * 
+	 * DOM node of type ENTITY_REFERENCE.
+	 */
+	NODETYPE_ENTITY_REFERENCE: 5,
+
+	/**
+	 * Variable: NODETYPE_ENTITY
+	 * 
+	 * DOM node of type ENTITY.
+	 */
+	NODETYPE_ENTITY: 6,
+
+	/**
+	 * Variable: NODETYPE_PROCESSING_INSTRUCTION
+	 * 
+	 * DOM node of type PROCESSING_INSTRUCTION.
+	 */
+	NODETYPE_PROCESSING_INSTRUCTION: 7,
+
+	/**
+	 * Variable: NODETYPE_COMMENT
+	 * 
+	 * DOM node of type COMMENT.
+	 */
+	NODETYPE_COMMENT: 8,
+		
+	/**
+	 * Variable: NODETYPE_DOCUMENT
+	 * 
+	 * DOM node of type DOCUMENT.
+	 */
+	NODETYPE_DOCUMENT: 9,
+
+	/**
+	 * Variable: NODETYPE_DOCUMENTTYPE
+	 * 
+	 * DOM node of type DOCUMENTTYPE.
+	 */
+	NODETYPE_DOCUMENTTYPE: 10,
+
+	/**
+	 * Variable: NODETYPE_DOCUMENT_FRAGMENT
+	 * 
+	 * DOM node of type DOCUMENT_FRAGMENT.
+	 */
+	NODETYPE_DOCUMENT_FRAGMENT: 11,
+
+	/**
+	 * Variable: NODETYPE_NOTATION
+	 * 
+	 * DOM node of type NOTATION.
+	 */
+	NODETYPE_NOTATION: 12,
+	
+	/**
+	 * Variable: TOOLTIP_VERTICAL_OFFSET
+	 * 
+	 * Defines the vertical offset for the tooltip.
+	 * Default is 16.
+	 */
+	TOOLTIP_VERTICAL_OFFSET: 16,
+
+	/**
+	 * Variable: DEFAULT_VALID_COLOR
+	 * 
+	 * Specifies the default valid color. Default is #0000FF.
+	 */
+	DEFAULT_VALID_COLOR: '#00FF00',
+
+	/**
+	 * Variable: DEFAULT_INVALID_COLOR
+	 * 
+	 * Specifies the default invalid color. Default is #FF0000.
+	 */
+	DEFAULT_INVALID_COLOR: '#FF0000',
+
+	/**
+	 * Variable: OUTLINE_HIGHLIGHT_COLOR
+	 * 
+	 * Specifies the default highlight color for shape outlines.
+	 * Default is #0000FF. This is used in <mxEdgeHandler>.
+	 */
+	OUTLINE_HIGHLIGHT_COLOR: '#00FF00',
+
+	/**
+	 * Variable: OUTLINE_HIGHLIGHT_COLOR
+	 * 
+	 * Defines the strokewidth to be used for shape outlines.
+	 * Default is 5. This is used in <mxEdgeHandler>.
+	 */
+	OUTLINE_HIGHLIGHT_STROKEWIDTH: 5,
+
+	/**
+	 * Variable: HIGHLIGHT_STROKEWIDTH
+	 * 
+	 * Defines the strokewidth to be used for the highlights.
+	 * Default is 3.
+	 */
+	HIGHLIGHT_STROKEWIDTH: 3,
+
+	/**
+	 * Variable: CONSTRAINT_HIGHLIGHT_SIZE
+	 * 
+	 * Size of the constraint highlight (in px). Default is 2.
+	 */
+	HIGHLIGHT_SIZE: 2,
+	
+	/**
+	 * Variable: HIGHLIGHT_OPACITY
+	 * 
+	 * Opacity (in %) used for the highlights (including outline).
+	 * Default is 100.
+	 */
+	HIGHLIGHT_OPACITY: 100,
+	
+	/**
+	 * Variable: CURSOR_MOVABLE_VERTEX
+	 * 
+	 * Defines the cursor for a movable vertex. Default is 'move'.
+	 */
+	CURSOR_MOVABLE_VERTEX: 'move',
+	
+	/**
+	 * Variable: CURSOR_MOVABLE_EDGE
+	 * 
+	 * Defines the cursor for a movable edge. Default is 'move'.
+	 */
+	CURSOR_MOVABLE_EDGE: 'move',
+	
+	/**
+	 * Variable: CURSOR_LABEL_HANDLE
+	 * 
+	 * Defines the cursor for a movable label. Default is 'default'.
+	 */
+	CURSOR_LABEL_HANDLE: 'default',
+	
+	/**
+	 * Variable: CURSOR_TERMINAL_HANDLE
+	 * 
+	 * Defines the cursor for a terminal handle. Default is 'pointer'.
+	 */
+	CURSOR_TERMINAL_HANDLE: 'pointer',
+	
+	/**
+	 * Variable: CURSOR_BEND_HANDLE
+	 * 
+	 * Defines the cursor for a movable bend. Default is 'crosshair'.
+	 */
+	CURSOR_BEND_HANDLE: 'crosshair',
+
+	/**
+	 * Variable: CURSOR_VIRTUAL_BEND_HANDLE
+	 * 
+	 * Defines the cursor for a movable bend. Default is 'crosshair'.
+	 */
+	CURSOR_VIRTUAL_BEND_HANDLE: 'crosshair',
+	
+	/**
+	 * Variable: CURSOR_CONNECT
+	 * 
+	 * Defines the cursor for a connectable state. Default is 'pointer'.
+	 */
+	CURSOR_CONNECT: 'pointer',
+
+	/**
+	 * Variable: HIGHLIGHT_COLOR
+	 * 
+	 * Defines the color to be used for the cell highlighting.
+	 * Use 'none' for no color. Default is #00FF00.
+	 */
+	HIGHLIGHT_COLOR: '#00FF00',
+
+	/**
+	 * Variable: TARGET_HIGHLIGHT_COLOR
+	 * 
+	 * Defines the color to be used for highlighting a target cell for a new
+	 * or changed connection. Note that this may be either a source or
+	 * target terminal in the graph. Use 'none' for no color.
+	 * Default is #0000FF.
+	 */
+	CONNECT_TARGET_COLOR: '#0000FF',
+
+	/**
+	 * Variable: INVALID_CONNECT_TARGET_COLOR
+	 * 
+	 * Defines the color to be used for highlighting a invalid target cells
+	 * for a new or changed connections. Note that this may be either a source
+	 * or target terminal in the graph. Use 'none' for no color. Default is
+	 * #FF0000.
+	 */
+	INVALID_CONNECT_TARGET_COLOR: '#FF0000',
+
+	/**
+	 * Variable: DROP_TARGET_COLOR
+	 * 
+	 * Defines the color to be used for the highlighting target parent cells
+	 * (for drag and drop). Use 'none' for no color. Default is #0000FF.
+	 */
+	DROP_TARGET_COLOR: '#0000FF',
+
+	/**
+	 * Variable: VALID_COLOR
+	 * 
+	 * Defines the color to be used for the coloring valid connection
+	 * previews. Use 'none' for no color. Default is #FF0000.
+	 */
+	VALID_COLOR: '#00FF00',
+
+	/**
+	 * Variable: INVALID_COLOR
+	 * 
+	 * Defines the color to be used for the coloring invalid connection
+	 * previews. Use 'none' for no color. Default is #FF0000.
+	 */
+	INVALID_COLOR: '#FF0000',
+
+	/**
+	 * Variable: EDGE_SELECTION_COLOR
+	 * 
+	 * Defines the color to be used for the selection border of edges. Use
+	 * 'none' for no color. Default is #00FF00.
+	 */
+	EDGE_SELECTION_COLOR: '#00FF00',
+
+	/**
+	 * Variable: VERTEX_SELECTION_COLOR
+	 * 
+	 * Defines the color to be used for the selection border of vertices. Use
+	 * 'none' for no color. Default is #00FF00.
+	 */
+	VERTEX_SELECTION_COLOR: '#00FF00',
+
+	/**
+	 * Variable: VERTEX_SELECTION_STROKEWIDTH
+	 * 
+	 * Defines the strokewidth to be used for vertex selections.
+	 * Default is 1.
+	 */
+	VERTEX_SELECTION_STROKEWIDTH: 1,
+
+	/**
+	 * Variable: EDGE_SELECTION_STROKEWIDTH
+	 * 
+	 * Defines the strokewidth to be used for edge selections.
+	 * Default is 1.
+	 */
+	EDGE_SELECTION_STROKEWIDTH: 1,
+
+	/**
+	 * Variable: SELECTION_DASHED
+	 * 
+	 * Defines the dashed state to be used for the vertex selection
+	 * border. Default is true.
+	 */
+	VERTEX_SELECTION_DASHED: true,
+
+	/**
+	 * Variable: SELECTION_DASHED
+	 * 
+	 * Defines the dashed state to be used for the edge selection
+	 * border. Default is true.
+	 */
+	EDGE_SELECTION_DASHED: true,
+
+	/**
+	 * Variable: GUIDE_COLOR
+	 * 
+	 * Defines the color to be used for the guidelines in mxGraphHandler.
+	 * Default is #FF0000.
+	 */
+	GUIDE_COLOR: '#FF0000',
+
+	/**
+	 * Variable: GUIDE_STROKEWIDTH
+	 * 
+	 * Defines the strokewidth to be used for the guidelines in mxGraphHandler.
+	 * Default is 1.
+	 */
+	GUIDE_STROKEWIDTH: 1,
+
+	/**
+	 * Variable: OUTLINE_COLOR
+	 * 
+	 * Defines the color to be used for the outline rectangle
+	 * border.  Use 'none' for no color. Default is #0099FF.
+	 */
+	OUTLINE_COLOR: '#0099FF',
+
+	/**
+	 * Variable: OUTLINE_STROKEWIDTH
+	 * 
+	 * Defines the strokewidth to be used for the outline rectangle
+	 * stroke width. Default is 3.
+	 */
+	OUTLINE_STROKEWIDTH: (mxClient.IS_IE) ? 2 : 3,
+
+	/**
+	 * Variable: HANDLE_SIZE
+	 * 
+	 * Defines the default size for handles. Default is 6.
+	 */
+	HANDLE_SIZE: 6,
+
+	/**
+	 * Variable: LABEL_HANDLE_SIZE
+	 * 
+	 * Defines the default size for label handles. Default is 4.
+	 */
+	LABEL_HANDLE_SIZE: 4,
+
+	/**
+	 * Variable: HANDLE_FILLCOLOR
+	 * 
+	 * Defines the color to be used for the handle fill color. Use 'none' for
+	 * no color. Default is #00FF00 (green).
+	 */
+	HANDLE_FILLCOLOR: '#00FF00',
+
+	/**
+	 * Variable: HANDLE_STROKECOLOR
+	 * 
+	 * Defines the color to be used for the handle stroke color. Use 'none' for
+	 * no color. Default is black.
+	 */
+	HANDLE_STROKECOLOR: 'black',
+
+	/**
+	 * Variable: LABEL_HANDLE_FILLCOLOR
+	 * 
+	 * Defines the color to be used for the label handle fill color. Use 'none'
+	 * for no color. Default is yellow.
+	 */
+	LABEL_HANDLE_FILLCOLOR: 'yellow',
+
+	/**
+	 * Variable: CONNECT_HANDLE_FILLCOLOR
+	 * 
+	 * Defines the color to be used for the connect handle fill color. Use
+	 * 'none' for no color. Default is #0000FF (blue).
+	 */
+	CONNECT_HANDLE_FILLCOLOR: '#0000FF',
+
+	/**
+	 * Variable: LOCKED_HANDLE_FILLCOLOR
+	 * 
+	 * Defines the color to be used for the locked handle fill color. Use
+	 * 'none' for no color. Default is #FF0000 (red).
+	 */
+	LOCKED_HANDLE_FILLCOLOR: '#FF0000',
+
+	/**
+	 * Variable: OUTLINE_HANDLE_FILLCOLOR
+	 * 
+	 * Defines the color to be used for the outline sizer fill color. Use
+	 * 'none' for no color. Default is #00FFFF.
+	 */
+	OUTLINE_HANDLE_FILLCOLOR: '#00FFFF',
+
+	/**
+	 * Variable: OUTLINE_HANDLE_STROKECOLOR
+	 * 
+	 * Defines the color to be used for the outline sizer stroke color. Use
+	 * 'none' for no color. Default is #0033FF.
+	 */
+	OUTLINE_HANDLE_STROKECOLOR: '#0033FF',
+
+	/**
+	 * Variable: DEFAULT_FONTFAMILY
+	 * 
+	 * Defines the default family for all fonts. Default is Arial,Helvetica.
+	 */
+	DEFAULT_FONTFAMILY: 'Arial,Helvetica',
+
+	/**
+	 * Variable: DEFAULT_FONTSIZE
+	 * 
+	 * Defines the default size (in px). Default is 11.
+	 */
+	DEFAULT_FONTSIZE: 11,
+
+	/**
+	 * Variable: DEFAULT_TEXT_DIRECTION
+	 * 
+	 * Defines the default value for the <STYLE_TEXT_DIRECTION> if no value is
+	 * defined for it in the style. Default value is an empty string which means
+	 * the default system setting is used and no direction is set.
+	 */
+	DEFAULT_TEXT_DIRECTION: '',
+
+	/**
+	 * Variable: LINE_HEIGHT
+	 * 
+	 * Defines the default line height for text labels. Default is 1.2.
+	 */
+	LINE_HEIGHT: 1.2,
+
+	/**
+	 * Variable: WORD_WRAP
+	 * 
+	 * Defines the CSS value for the word-wrap property. Default is "normal".
+	 * Change this to "break-word" to allow long words to be able to be broken
+	 * and wrap onto the next line.
+	 */
+	WORD_WRAP: 'normal',
+
+	/**
+	 * Variable: ABSOLUTE_LINE_HEIGHT
+	 * 
+	 * Specifies if absolute line heights should be used (px) in CSS. Default
+	 * is false. Set this to true for backwards compatibility.
+	 */
+	ABSOLUTE_LINE_HEIGHT: false,
+
+	/**
+	 * Variable: DEFAULT_FONTSTYLE
+	 * 
+	 * Defines the default style for all fonts. Default is 0. This can be set
+	 * to any combination of font styles as follows.
+	 * 
+	 * (code)
+	 * mxConstants.DEFAULT_FONTSTYLE = mxConstants.FONT_BOLD | mxConstants.FONT_ITALIC;
+	 * (end)
+	 */
+	DEFAULT_FONTSTYLE: 0,
+
+	/**
+	 * Variable: DEFAULT_STARTSIZE
+	 * 
+	 * Defines the default start size for swimlanes. Default is 40.
+	 */
+	DEFAULT_STARTSIZE: 40,
+
+	/**
+	 * Variable: DEFAULT_MARKERSIZE
+	 * 
+	 * Defines the default size for all markers. Default is 6.
+	 */
+	DEFAULT_MARKERSIZE: 6,
+
+	/**
+	 * Variable: DEFAULT_IMAGESIZE
+	 * 
+	 * Defines the default width and height for images used in the
+	 * label shape. Default is 24.
+	 */
+	DEFAULT_IMAGESIZE: 24,
+
+	/**
+	 * Variable: ENTITY_SEGMENT
+	 * 
+	 * Defines the length of the horizontal segment of an Entity Relation.
+	 * This can be overridden using <mxConstants.STYLE_SEGMENT> style.
+	 * Default is 30.
+	 */
+	ENTITY_SEGMENT: 30,
+
+	/**
+	 * Variable: RECTANGLE_ROUNDING_FACTOR
+	 * 
+	 * Defines the rounding factor for rounded rectangles in percent between
+	 * 0 and 1. Values should be smaller than 0.5. Default is 0.15.
+	 */
+	RECTANGLE_ROUNDING_FACTOR: 0.15,
+
+	/**
+	 * Variable: LINE_ARCSIZE
+	 * 
+	 * Defines the size of the arcs for rounded edges. Default is 20.
+	 */
+	LINE_ARCSIZE: 20,
+
+	/**
+	 * Variable: ARROW_SPACING
+	 * 
+	 * Defines the spacing between the arrow shape and its terminals. Default is 0.
+	 */
+	ARROW_SPACING: 0,
+
+	/**
+	 * Variable: ARROW_WIDTH
+	 * 
+	 * Defines the width of the arrow shape. Default is 30.
+	 */
+	ARROW_WIDTH: 30,
+
+	/**
+	 * Variable: ARROW_SIZE
+	 * 
+	 * Defines the size of the arrowhead in the arrow shape. Default is 30.
+	 */
+	ARROW_SIZE: 30,
+
+	/**
+	 * Variable: PAGE_FORMAT_A4_PORTRAIT
+	 * 
+	 * Defines the rectangle for the A4 portrait page format. The dimensions
+	 * of this page format are 826x1169 pixels.
+	 */
+	PAGE_FORMAT_A4_PORTRAIT: new mxRectangle(0, 0, 827, 1169),
+
+	/**
+	 * Variable: PAGE_FORMAT_A4_PORTRAIT
+	 * 
+	 * Defines the rectangle for the A4 portrait page format. The dimensions
+	 * of this page format are 826x1169 pixels.
+	 */
+	PAGE_FORMAT_A4_LANDSCAPE: new mxRectangle(0, 0, 1169, 827),
+
+	/**
+	 * Variable: PAGE_FORMAT_LETTER_PORTRAIT
+	 * 
+	 * Defines the rectangle for the Letter portrait page format. The
+	 * dimensions of this page format are 850x1100 pixels.
+	 */
+	PAGE_FORMAT_LETTER_PORTRAIT: new mxRectangle(0, 0, 850, 1100),
+
+	/**
+	 * Variable: PAGE_FORMAT_LETTER_PORTRAIT
+	 * 
+	 * Defines the rectangle for the Letter portrait page format. The dimensions
+	 * of this page format are 850x1100 pixels.
+	 */
+	PAGE_FORMAT_LETTER_LANDSCAPE: new mxRectangle(0, 0, 1100, 850),
+
+	/**
+	 * Variable: NONE
+	 * 
+	 * Defines the value for none. Default is "none".
+	 */
+	NONE: 'none',
+
+	/**
+	 * Variable: STYLE_PERIMETER
+	 * 
+	 * Defines the key for the perimeter style. This is a function that defines
+	 * the perimeter around a particular shape. Possible values are the
+	 * functions defined in <mxPerimeter>. Alternatively, the constants in this
+	 * class that start with "PERIMETER_" may be used to access
+	 * perimeter styles in <mxStyleRegistry>. Value is "perimeter".
+	 */
+	STYLE_PERIMETER: 'perimeter',
+	
+	/**
+	 * Variable: STYLE_SOURCE_PORT
+	 * 
+	 * Defines the ID of the cell that should be used for computing the
+	 * perimeter point of the source for an edge. This allows for graphically
+	 * connecting to a cell while keeping the actual terminal of the edge.
+	 * Value is "sourcePort".
+	 */
+	STYLE_SOURCE_PORT: 'sourcePort',
+	
+	/**
+	 * Variable: STYLE_TARGET_PORT
+	 * 
+	 * Defines the ID of the cell that should be used for computing the
+	 * perimeter point of the target for an edge. This allows for graphically
+	 * connecting to a cell while keeping the actual terminal of the edge.
+	 * Value is "targetPort".
+	 */
+	STYLE_TARGET_PORT: 'targetPort',
+
+	/**
+	 * Variable: STYLE_PORT_CONSTRAINT
+	 * 
+	 * Defines the direction(s) that edges are allowed to connect to cells in.
+	 * Possible values are "DIRECTION_NORTH, DIRECTION_SOUTH, 
+	 * DIRECTION_EAST" and "DIRECTION_WEST". Value is
+	 * "portConstraint".
+	 */
+	STYLE_PORT_CONSTRAINT: 'portConstraint',
+
+	/**
+	 * Variable: STYLE_PORT_CONSTRAINT_ROTATION
+	 * 
+	 * Define whether port constraint directions are rotated with vertex
+	 * rotation. 0 (default) causes port constraints to remain absolute, 
+	 * relative to the graph, 1 causes the constraints to rotate with
+	 * the vertex. Value is "portConstraintRotation".
+	 */
+	STYLE_PORT_CONSTRAINT_ROTATION: 'portConstraintRotation',
+
+	/**
+	 * Variable: STYLE_SOURCE_PORT_CONSTRAINT
+	 * 
+	 * Defines the direction(s) that edges are allowed to connect to sources in.
+	 * Possible values are "DIRECTION_NORTH, DIRECTION_SOUTH, DIRECTION_EAST"
+	 * and "DIRECTION_WEST". Value is "sourcePortConstraint".
+	 */
+	STYLE_SOURCE_PORT_CONSTRAINT: 'sourcePortConstraint',
+
+	/**
+	 * Variable: STYLE_TARGET_PORT_CONSTRAINT
+	 * 
+	 * Defines the direction(s) that edges are allowed to connect to targets in.
+	 * Possible values are "DIRECTION_NORTH, DIRECTION_SOUTH, DIRECTION_EAST"
+	 * and "DIRECTION_WEST". Value is "targetPortConstraint".
+	 */
+	STYLE_TARGET_PORT_CONSTRAINT: 'targetPortConstraint',
+
+	/**
+	 * Variable: STYLE_OPACITY
+	 * 
+	 * Defines the key for the opacity style. The type of the value is 
+	 * numeric and the possible range is 0-100. Value is "opacity".
+	 */
+	STYLE_OPACITY: 'opacity',
+
+	/**
+	 * Variable: STYLE_FILL_OPACITY
+	 * 
+	 * Defines the key for the fill opacity style. The type of the value is 
+	 * numeric and the possible range is 0-100. Value is "fillOpacity".
+	 */
+	STYLE_FILL_OPACITY: 'fillOpacity',
+
+	/**
+	 * Variable: STYLE_STROKE_OPACITY
+	 * 
+	 * Defines the key for the stroke opacity style. The type of the value is 
+	 * numeric and the possible range is 0-100. Value is "strokeOpacity".
+	 */
+	STYLE_STROKE_OPACITY: 'strokeOpacity',
+
+	/**
+	 * Variable: STYLE_TEXT_OPACITY
+	 * 
+	 * Defines the key for the text opacity style. The type of the value is 
+	 * numeric and the possible range is 0-100. Value is "textOpacity".
+	 */
+	STYLE_TEXT_OPACITY: 'textOpacity',
+
+	/**
+	 * Variable: STYLE_TEXT_DIRECTION
+	 * 
+	 * Defines the key for the text direction style. Possible values are
+	 * "TEXT_DIRECTION_DEFAULT, TEXT_DIRECTION_AUTO, TEXT_DIRECTION_LTR"
+	 * and "TEXT_DIRECTION_RTL". Value is "textDirection".
+	 * The default value for the style is defined in <DEFAULT_TEXT_DIRECTION>.
+	 * It is used is no value is defined for this key in a given style. This is
+	 * an experimental style that is currently ignored in the backends.
+	 */
+	STYLE_TEXT_DIRECTION: 'textDirection',
+
+	/**
+	 * Variable: STYLE_OVERFLOW
+	 * 
+	 * Defines the key for the overflow style. Possible values are 'visible',
+	 * 'hidden', 'fill' and 'width'. The default value is 'visible'. This value
+	 * specifies how overlapping vertex labels are handled. A value of
+	 * 'visible' will show the complete label. A value of 'hidden' will clip
+	 * the label so that it does not overlap the vertex bounds. A value of
+	 * 'fill' will use the vertex bounds and a value of 'width' will use the
+	 * the vertex width for the label. See <mxGraph.isLabelClipped>. Note that
+	 * the vertical alignment is ignored for overflow fill and for horizontal
+	 * alignment, left should be used to avoid pixel offsets in Internet Explorer
+	 * 11 and earlier or if foreignObjects are disabled. Value is "overflow".
+	 */
+	STYLE_OVERFLOW: 'overflow',
+
+	/**
+	 * Variable: STYLE_ORTHOGONAL
+	 * 
+	 * Defines if the connection points on either end of the edge should be
+	 * computed so that the edge is vertical or horizontal if possible and
+	 * if the point is not at a fixed location. Default is false. This is
+	 * used in <mxGraph.isOrthogonal>, which also returns true if the edgeStyle
+	 * of the edge is an elbow or entity. Value is "orthogonal".
+	 */
+	STYLE_ORTHOGONAL: 'orthogonal',
+
+	/**
+	 * Variable: STYLE_EXIT_X
+	 * 
+	 * Defines the key for the horizontal relative coordinate connection point
+	 * of an edge with its source terminal. Value is "exitX".
+	 */
+	STYLE_EXIT_X: 'exitX',
+
+	/**
+	 * Variable: STYLE_EXIT_Y
+	 * 
+	 * Defines the key for the vertical relative coordinate connection point
+	 * of an edge with its source terminal. Value is "exitY".
+	 */
+	STYLE_EXIT_Y: 'exitY',
+
+	/**
+	 * Variable: STYLE_EXIT_PERIMETER
+	 * 
+	 * Defines if the perimeter should be used to find the exact entry point
+	 * along the perimeter of the source. Possible values are 0 (false) and
+	 * 1 (true). Default is 1 (true). Value is "exitPerimeter".
+	 */
+	STYLE_EXIT_PERIMETER: 'exitPerimeter',
+
+	/**
+	 * Variable: STYLE_ENTRY_X
+	 * 
+	 * Defines the key for the horizontal relative coordinate connection point
+	 * of an edge with its target terminal. Value is "entryX".
+	 */
+	STYLE_ENTRY_X: 'entryX',
+
+	/**
+	 * Variable: STYLE_ENTRY_Y
+	 * 
+	 * Defines the key for the vertical relative coordinate connection point
+	 * of an edge with its target terminal. Value is "entryY".
+	 */
+	STYLE_ENTRY_Y: 'entryY',
+
+	/**
+	 * Variable: STYLE_ENTRY_PERIMETER
+	 * 
+	 * Defines if the perimeter should be used to find the exact entry point
+	 * along the perimeter of the target. Possible values are 0 (false) and
+	 * 1 (true). Default is 1 (true). Value is "entryPerimeter".
+	 */
+	STYLE_ENTRY_PERIMETER: 'entryPerimeter',
+
+	/**
+	 * Variable: STYLE_WHITE_SPACE
+	 * 
+	 * Defines the key for the white-space style. Possible values are 'nowrap'
+	 * and 'wrap'. The default value is 'nowrap'. This value specifies how
+	 * white-space inside a HTML vertex label should be handled. A value of
+	 * 'nowrap' means the text will never wrap to the next line until a
+	 * linefeed is encountered. A value of 'wrap' means text will wrap when
+	 * necessary. This style is only used for HTML labels.
+	 * See <mxGraph.isWrapping>. Value is "whiteSpace".
+	 */
+	STYLE_WHITE_SPACE: 'whiteSpace',
+
+	/**
+	 * Variable: STYLE_ROTATION
+	 * 
+	 * Defines the key for the rotation style. The type of the value is 
+	 * numeric and the possible range is 0-360. Value is "rotation".
+	 */
+	STYLE_ROTATION: 'rotation',
+
+	/**
+	 * Variable: STYLE_FILLCOLOR
+	 * 
+	 * Defines the key for the fill color. Possible values are all HTML color
+	 * names or HEX codes, as well as special keywords such as 'swimlane,
+	 * 'inherit' or 'indicated' to use the color code of a related cell or the
+	 * indicator shape. Value is "fillColor".
+	 */
+	STYLE_FILLCOLOR: 'fillColor',
+
+	/**
+	 * Variable: STYLE_POINTER_EVENTS
+	 * 
+	 * Specifies if pointer events should be fired on transparent backgrounds.
+	 * This style is currently only supported in <mxRectangleShape>. Default
+	 * is true. Value is "pointerEvents". This is typically set to
+	 * false in groups where the transparent part should allow any underlying
+	 * cells to be clickable.
+	 */
+	STYLE_POINTER_EVENTS: 'pointerEvents',
+
+	/**
+	 * Variable: STYLE_SWIMLANE_FILLCOLOR
+	 * 
+	 * Defines the key for the fill color of the swimlane background. Possible
+	 * values are all HTML color names or HEX codes. Default is no background.
+	 * Value is "swimlaneFillColor".
+	 */
+	STYLE_SWIMLANE_FILLCOLOR: 'swimlaneFillColor',
+
+	/**
+	 * Variable: STYLE_MARGIN
+	 * 
+	 * Defines the key for the margin between the ellipses in the double ellipse shape.
+	 * Possible values are all positive numbers. Value is "margin".
+	 */
+	STYLE_MARGIN: 'margin',
+
+	/**
+	 * Variable: STYLE_GRADIENTCOLOR
+	 * 
+	 * Defines the key for the gradient color. Possible values are all HTML color
+	 * names or HEX codes, as well as special keywords such as 'swimlane,
+	 * 'inherit' or 'indicated' to use the color code of a related cell or the
+	 * indicator shape. This is ignored if no fill color is defined. Value is
+	 * "gradientColor".
+	 */
+	STYLE_GRADIENTCOLOR: 'gradientColor',
+
+	/**
+	 * Variable: STYLE_GRADIENT_DIRECTION
+	 * 
+	 * Defines the key for the gradient direction. Possible values are
+	 * <DIRECTION_EAST>, <DIRECTION_WEST>, <DIRECTION_NORTH> and
+	 * <DIRECTION_SOUTH>. Default is <DIRECTION_SOUTH>. Generally, and by
+	 * default in mxGraph, gradient painting is done from the value of
+	 * <STYLE_FILLCOLOR> to the value of <STYLE_GRADIENTCOLOR>. Taking the
+	 * example of <DIRECTION_NORTH>, this means <STYLE_FILLCOLOR> color at the 
+	 * bottom of paint pattern and <STYLE_GRADIENTCOLOR> at top, with a
+	 * gradient in-between. Value is "gradientDirection".
+	 */
+	STYLE_GRADIENT_DIRECTION: 'gradientDirection',
+
+	/**
+	 * Variable: STYLE_STROKECOLOR
+	 * 
+	 * Defines the key for the strokeColor style. Possible values are all HTML
+	 * color names or HEX codes, as well as special keywords such as 'swimlane,
+	 * 'inherit', 'indicated' to use the color code of a related cell or the
+	 * indicator shape or 'none' for no color. Value is "strokeColor".
+	 */
+	STYLE_STROKECOLOR: 'strokeColor',
+
+	/**
+	 * Variable: STYLE_SEPARATORCOLOR
+	 * 
+	 * Defines the key for the separatorColor style. Possible values are all
+	 * HTML color names or HEX codes. This style is only used for
+	 * <SHAPE_SWIMLANE> shapes. Value is "separatorColor".
+	 */
+	STYLE_SEPARATORCOLOR: 'separatorColor',
+
+	/**
+	 * Variable: STYLE_STROKEWIDTH
+	 * 
+	 * Defines the key for the strokeWidth style. The type of the value is 
+	 * numeric and the possible range is any non-negative value larger or equal
+	 * to 1. The value defines the stroke width in pixels. Note: To hide a
+	 * stroke use strokeColor none. Value is "strokeWidth".
+	 */
+	STYLE_STROKEWIDTH: 'strokeWidth',
+
+	/**
+	 * Variable: STYLE_ALIGN
+	 * 
+	 * Defines the key for the align style. Possible values are <ALIGN_LEFT>,
+	 * <ALIGN_CENTER> and <ALIGN_RIGHT>. This value defines how the lines of
+	 * the label are horizontally aligned. <ALIGN_LEFT> mean label text lines
+	 * are aligned to left of the label bounds, <ALIGN_RIGHT> to the right of
+	 * the label bounds and <ALIGN_CENTER> means the center of the text lines
+	 * are aligned in the center of the label bounds. Note this value doesn't
+	 * affect the positioning of the overall label bounds relative to the
+	 * vertex, to move the label bounds horizontally, use
+	 * <STYLE_LABEL_POSITION>. Value is "align".
+	 */
+	STYLE_ALIGN: 'align',
+
+	/**
+	 * Variable: STYLE_VERTICAL_ALIGN
+	 * 
+	 * Defines the key for the verticalAlign style. Possible values are
+	 * <ALIGN_TOP>, <ALIGN_MIDDLE> and <ALIGN_BOTTOM>. This value defines how
+	 * the lines of the label are vertically aligned. <ALIGN_TOP> means the
+	 * topmost label text line is aligned against the top of the label bounds,
+	 * <ALIGN_BOTTOM> means the bottom-most label text line is aligned against
+	 * the bottom of the label bounds and <ALIGN_MIDDLE> means there is equal
+	 * spacing between the topmost text label line and the top of the label
+	 * bounds and the bottom-most text label line and the bottom of the label
+	 * bounds. Note this value doesn't affect the positioning of the overall
+	 * label bounds relative to the vertex, to move the label bounds
+	 * vertically, use <STYLE_VERTICAL_LABEL_POSITION>. Value is "verticalAlign".
+	 */
+	STYLE_VERTICAL_ALIGN: 'verticalAlign',
+
+	/**
+	 * Variable: STYLE_LABEL_WIDTH
+	 * 
+	 * Defines the key for the width of the label if the label position is not
+	 * center. Value is "labelWidth".
+	 */
+	STYLE_LABEL_WIDTH: 'labelWidth',
+
+	/**
+	 * Variable: STYLE_LABEL_POSITION
+	 * 
+	 * Defines the key for the horizontal label position of vertices. Possible
+	 * values are <ALIGN_LEFT>, <ALIGN_CENTER> and <ALIGN_RIGHT>. Default is
+	 * <ALIGN_CENTER>. The label align defines the position of the label
+	 * relative to the cell. <ALIGN_LEFT> means the entire label bounds is
+	 * placed completely just to the left of the vertex, <ALIGN_RIGHT> means
+	 * adjust to the right and <ALIGN_CENTER> means the label bounds are
+	 * vertically aligned with the bounds of the vertex. Note this value
+	 * doesn't affect the positioning of label within the label bounds, to move
+	 * the label horizontally within the label bounds, use <STYLE_ALIGN>.
+	 * Value is "labelPosition".
+	 */
+	STYLE_LABEL_POSITION: 'labelPosition',
+
+	/**
+	 * Variable: STYLE_VERTICAL_LABEL_POSITION
+	 * 
+	 * Defines the key for the vertical label position of vertices. Possible
+	 * values are <ALIGN_TOP>, <ALIGN_BOTTOM> and <ALIGN_MIDDLE>. Default is
+	 * <ALIGN_MIDDLE>. The label align defines the position of the label
+	 * relative to the cell. <ALIGN_TOP> means the entire label bounds is
+	 * placed completely just on the top of the vertex, <ALIGN_BOTTOM> means
+	 * adjust on the bottom and <ALIGN_MIDDLE> means the label bounds are
+	 * horizontally aligned with the bounds of the vertex. Note this value
+	 * doesn't affect the positioning of label within the label bounds, to move
+	 * the label vertically within the label bounds, use
+	 * <STYLE_VERTICAL_ALIGN>. Value is "verticalLabelPosition".
+	 */
+	STYLE_VERTICAL_LABEL_POSITION: 'verticalLabelPosition',
+	
+	/**
+	 * Variable: STYLE_IMAGE_ASPECT
+	 * 
+	 * Defines the key for the image aspect style. Possible values are 0 (do
+	 * not preserve aspect) or 1 (keep aspect). This is only used in
+	 * <mxImageShape>. Default is 1. Value is "imageAspect".
+	 */
+	STYLE_IMAGE_ASPECT: 'imageAspect',
+
+	/**
+	 * Variable: STYLE_IMAGE_ALIGN
+	 * 
+	 * Defines the key for the align style. Possible values are <ALIGN_LEFT>,
+	 * <ALIGN_CENTER> and <ALIGN_RIGHT>. The value defines how any image in the
+	 * vertex label is aligned horizontally within the label bounds of a
+	 * <SHAPE_LABEL> shape. Value is "imageAlign".
+	 */
+	STYLE_IMAGE_ALIGN: 'imageAlign',
+
+	/**
+	 * Variable: STYLE_IMAGE_VERTICAL_ALIGN
+	 * 
+	 * Defines the key for the verticalAlign style. Possible values are
+	 * <ALIGN_TOP>, <ALIGN_MIDDLE> and <ALIGN_BOTTOM>. The value defines how
+	 * any image in the vertex label is aligned vertically within the label
+	 * bounds of a <SHAPE_LABEL> shape. Value is "imageVerticalAlign".
+	 */
+	STYLE_IMAGE_VERTICAL_ALIGN: 'imageVerticalAlign',
+
+	/**
+	 * Variable: STYLE_GLASS
+	 * 
+	 * Defines the key for the glass style. Possible values are 0 (disabled) and
+	 * 1(enabled). The default value is 0. This is used in <mxLabel>. Value is
+	 * "glass".
+	 */
+	STYLE_GLASS: 'glass',
+
+	/**
+	 * Variable: STYLE_IMAGE
+	 * 
+	 * Defines the key for the image style. Possible values are any image URL,
+	 * the type of the value is String. This is the path to the image that is
+	 * to be displayed within the label of a vertex. Data URLs should use the
+	 * following format: data:image/png,xyz where xyz is the base64 encoded
+	 * data (without the "base64"-prefix). Note that Data URLs are only
+	 * supported in modern browsers. Value is "image".
+	 */
+	STYLE_IMAGE: 'image',
+
+	/**
+	 * Variable: STYLE_IMAGE_WIDTH
+	 * 
+	 * Defines the key for the imageWidth style. The type of this value is
+	 * int, the value is the image width in pixels and must be greater than 0.
+	 * Value is "imageWidth".
+	 */
+	STYLE_IMAGE_WIDTH: 'imageWidth',
+
+	/**
+	 * Variable: STYLE_IMAGE_HEIGHT
+	 * 
+	 * Defines the key for the imageHeight style. The type of this value is
+	 * int, the value is the image height in pixels and must be greater than 0.
+	 * Value is "imageHeight".
+	 */
+	STYLE_IMAGE_HEIGHT: 'imageHeight',
+
+	/**
+	 * Variable: STYLE_IMAGE_BACKGROUND
+	 * 
+	 * Defines the key for the image background color. This style is only used
+	 * in <mxImageShape>. Possible values are all HTML color names or HEX
+	 * codes. Value is "imageBackground".
+	 */
+	STYLE_IMAGE_BACKGROUND: 'imageBackground',
+
+	/**
+	 * Variable: STYLE_IMAGE_BORDER
+	 * 
+	 * Defines the key for the image border color. This style is only used in
+	 * <mxImageShape>. Possible values are all HTML color names or HEX codes.
+	 * Value is "imageBorder".
+	 */
+	STYLE_IMAGE_BORDER: 'imageBorder',
+
+	/**
+	 * Variable: STYLE_FLIPH
+	 * 
+	 * Defines the key for the horizontal image flip. This style is only used
+	 * in <mxImageShape>. Possible values are 0 and 1. Default is 0. Value is
+	 * "flipH".
+	 */
+	STYLE_FLIPH: 'flipH',
+
+	/**
+	 * Variable: STYLE_FLIPV
+	 * 
+	 * Defines the key for the vertical flip. Possible values are 0 and 1.
+	 * Default is 0. Value is "flipV".
+	 */
+	STYLE_FLIPV: 'flipV',
+
+	/**
+	 * Variable: STYLE_NOLABEL
+	 * 
+	 * Defines the key for the noLabel style. If this is true then no label is
+	 * visible for a given cell. Possible values are true or false (1 or 0).
+	 * Default is false. Value is "noLabel".
+	 */
+	STYLE_NOLABEL: 'noLabel',
+
+	/**
+	 * Variable: STYLE_NOEDGESTYLE
+	 * 
+	 * Defines the key for the noEdgeStyle style. If this is true then no edge
+	 * style is applied for a given edge. Possible values are true or false
+	 * (1 or 0). Default is false. Value is "noEdgeStyle".
+	 */
+	STYLE_NOEDGESTYLE: 'noEdgeStyle',
+
+	/**
+	 * Variable: STYLE_LABEL_BACKGROUNDCOLOR
+	 * 
+	 * Defines the key for the label background color. Possible values are all
+	 * HTML color names or HEX codes. Value is "labelBackgroundColor".
+	 */
+	STYLE_LABEL_BACKGROUNDCOLOR: 'labelBackgroundColor',
+
+	/**
+	 * Variable: STYLE_LABEL_BORDERCOLOR
+	 * 
+	 * Defines the key for the label border color. Possible values are all
+	 * HTML color names or HEX codes. Value is "labelBorderColor".
+	 */
+	STYLE_LABEL_BORDERCOLOR: 'labelBorderColor',
+
+	/**
+	 * Variable: STYLE_LABEL_PADDING
+	 * 
+	 * Defines the key for the label padding, ie. the space between the label
+	 * border and the label. Value is "labelPadding".
+	 */
+	STYLE_LABEL_PADDING: 'labelPadding',
+
+	/**
+	 * Variable: STYLE_INDICATOR_SHAPE
+	 * 
+	 * Defines the key for the indicator shape used within an <mxLabel>.
+	 * Possible values are all SHAPE_* constants or the names of any new
+	 * shapes. The indicatorShape has precedence over the indicatorImage.
+	 * Value is "indicatorShape".
+	 */
+	STYLE_INDICATOR_SHAPE: 'indicatorShape',
+
+	/**
+	 * Variable: STYLE_INDICATOR_IMAGE
+	 * 
+	 * Defines the key for the indicator image used within an <mxLabel>.
+	 * Possible values are all image URLs. The indicatorShape has
+	 * precedence over the indicatorImage. Value is "indicatorImage".
+	 */
+	STYLE_INDICATOR_IMAGE: 'indicatorImage',
+
+	/**
+	 * Variable: STYLE_INDICATOR_COLOR
+	 * 
+	 * Defines the key for the indicatorColor style. Possible values are all
+	 * HTML color names or HEX codes, as well as the special 'swimlane' keyword
+	 * to refer to the color of the parent swimlane if one exists. Value is
+	 * "indicatorColor".
+	 */
+	STYLE_INDICATOR_COLOR: 'indicatorColor',
+
+	/**
+	 * Variable: STYLE_INDICATOR_STROKECOLOR
+	 * 
+	 * Defines the key for the indicator stroke color in <mxLabel>.
+	 * Possible values are all color codes. Value is "indicatorStrokeColor".
+	 */
+	STYLE_INDICATOR_STROKECOLOR: 'indicatorStrokeColor',
+
+	/**
+	 * Variable: STYLE_INDICATOR_GRADIENTCOLOR
+	 * 
+	 * Defines the key for the indicatorGradientColor style. Possible values
+	 * are all HTML color names or HEX codes. This style is only supported in
+	 * <SHAPE_LABEL> shapes. Value is "indicatorGradientColor".
+	 */
+	STYLE_INDICATOR_GRADIENTCOLOR: 'indicatorGradientColor',
+
+	/**
+	 * Variable: STYLE_INDICATOR_SPACING
+	 * 
+	 * The defines the key for the spacing between the label and the
+	 * indicator in <mxLabel>. Possible values are in pixels. Value is
+	 * "indicatorSpacing".
+	 */
+	STYLE_INDICATOR_SPACING: 'indicatorSpacing',
+
+	/**
+	 * Variable: STYLE_INDICATOR_WIDTH
+	 * 
+	 * Defines the key for the indicator width. Possible values start at 0 (in
+	 * pixels). Value is "indicatorWidth".
+	 */
+	STYLE_INDICATOR_WIDTH: 'indicatorWidth',
+
+	/**
+	 * Variable: STYLE_INDICATOR_HEIGHT
+	 * 
+	 * Defines the key for the indicator height. Possible values start at 0 (in
+	 * pixels). Value is "indicatorHeight".
+	 */
+	STYLE_INDICATOR_HEIGHT: 'indicatorHeight',
+
+	/**
+	 * Variable: STYLE_INDICATOR_DIRECTION
+	 * 
+	 * Defines the key for the indicatorDirection style. The direction style is
+	 * used to specify the direction of certain shapes (eg. <mxTriangle>).
+	 * Possible values are <DIRECTION_EAST> (default), <DIRECTION_WEST>,
+	 * <DIRECTION_NORTH> and <DIRECTION_SOUTH>. Value is "indicatorDirection".
+	 */
+	STYLE_INDICATOR_DIRECTION: 'indicatorDirection',
+
+	/**
+	 * Variable: STYLE_SHADOW
+	 * 
+	 * Defines the key for the shadow style. The type of the value is Boolean.
+	 * Value is "shadow".
+	 */
+	STYLE_SHADOW: 'shadow',
+	
+	/**
+	 * Variable: STYLE_SEGMENT
+	 * 
+	 * Defines the key for the segment style. The type of this value is float
+	 * and the value represents the size of the horizontal segment of the
+	 * entity relation style. Default is ENTITY_SEGMENT. Value is "segment".
+	 */
+	STYLE_SEGMENT: 'segment',
+	
+	/**
+	 * Variable: STYLE_ENDARROW
+	 *
+	 * Defines the key for the end arrow marker. Possible values are all
+	 * constants with an ARROW-prefix. This is only used in <mxConnector>.
+	 * Value is "endArrow".
+	 *
+	 * Example:
+	 * (code)
+	 * style[mxConstants.STYLE_ENDARROW] = mxConstants.ARROW_CLASSIC;
+	 * (end)
+	 */
+	STYLE_ENDARROW: 'endArrow',
+
+	/**
+	 * Variable: STYLE_STARTARROW
+	 * 
+	 * Defines the key for the start arrow marker. Possible values are all
+	 * constants with an ARROW-prefix. This is only used in <mxConnector>.
+	 * See <STYLE_ENDARROW>. Value is "startArrow".
+	 */
+	STYLE_STARTARROW: 'startArrow',
+
+	/**
+	 * Variable: STYLE_ENDSIZE
+	 * 
+	 * Defines the key for the endSize style. The type of this value is numeric
+	 * and the value represents the size of the end marker in pixels. Value is
+	 * "endSize".
+	 */
+	STYLE_ENDSIZE: 'endSize',
+
+	/**
+	 * Variable: STYLE_STARTSIZE
+	 * 
+	 * Defines the key for the startSize style. The type of this value is
+	 * numeric and the value represents the size of the start marker or the
+	 * size of the swimlane title region depending on the shape it is used for.
+	 * Value is "startSize".
+	 */
+	STYLE_STARTSIZE: 'startSize',
+
+	/**
+	 * Variable: STYLE_SWIMLANE_LINE
+	 * 
+	 * Defines the key for the swimlaneLine style. This style specifies whether
+	 * the line between the title regio of a swimlane should be visible. Use 0
+	 * for hidden or 1 (default) for visible. Value is "swimlaneLine".
+	 */
+	STYLE_SWIMLANE_LINE: 'swimlaneLine',
+
+	/**
+	 * Variable: STYLE_ENDFILL
+	 * 
+	 * Defines the key for the endFill style. Use 0 for no fill or 1 (default)
+	 * for fill. (This style is only exported via <mxImageExport>.) Value is
+	 * "endFill".
+	 */
+	STYLE_ENDFILL: 'endFill',
+
+	/**
+	 * Variable: STYLE_STARTFILL
+	 * 
+	 * Defines the key for the startFill style. Use 0 for no fill or 1 (default)
+	 * for fill. (This style is only exported via <mxImageExport>.) Value is
+	 * "startFill".
+	 */
+	STYLE_STARTFILL: 'startFill',
+
+	/**
+	 * Variable: STYLE_DASHED
+	 * 
+	 * Defines the key for the dashed style. Use 0 (default) for non-dashed or 1
+	 * for dashed. Value is "dashed".
+	 */
+	STYLE_DASHED: 'dashed',
+
+	/**
+	 * Defines the key for the dashed pattern style in SVG and image exports.
+	 * The type of this value is a space separated list of numbers that specify
+	 * a custom-defined dash pattern. Dash styles are defined in terms of the
+	 * length of the dash (the drawn part of the stroke) and the length of the
+	 * space between the dashes. The lengths are relative to the line width: a
+	 * length of "1" is equal to the line width. VML ignores this style and
+	 * uses dashStyle instead as defined in the VML specification. This style
+	 * is only used in the <mxConnector> shape. Value is "dashPattern".
+	 */
+	STYLE_DASH_PATTERN: 'dashPattern',
+
+	/**
+	 * Variable: STYLE_FIX_DASH
+	 * 
+	 * Defines the key for the fixDash style. Use 0 (default) for dash patterns
+	 * that depend on the linewidth and 1 for dash patterns that ignore the
+	 * line width. Value is "fixDash".
+	 */
+	STYLE_FIX_DASH: 'fixDash',
+
+	/**
+	 * Variable: STYLE_ROUNDED
+	 * 
+	 * Defines the key for the rounded style. The type of this value is
+	 * Boolean. For edges this determines whether or not joins between edges
+	 * segments are smoothed to a rounded finish. For vertices that have the
+	 * rectangle shape, this determines whether or not the rectangle is
+	 * rounded. Use 0 (default) for non-rounded or 1 for rounded. Value is
+	 * "rounded".
+	 */
+	STYLE_ROUNDED: 'rounded',
+
+	/**
+	 * Variable: STYLE_CURVED
+	 * 
+	 * Defines the key for the curved style. The type of this value is
+	 * Boolean. It is only applicable for connector shapes. Use 0 (default)
+	 * for non-curved or 1 for curved. Value is "curved".
+	 */
+	STYLE_CURVED: 'curved',
+
+	/**
+	 * Variable: STYLE_ARCSIZE
+	 * 
+	 * Defines the rounding factor for a rounded rectangle in percent (without
+	 * the percent sign). Possible values are between 0 and 100. If this value
+	 * is not specified then RECTANGLE_ROUNDING_FACTOR * 100 is used. For
+	 * edges, this defines the absolute size of rounded corners in pixels. If
+	 * this values is not specified then LINE_ARCSIZE is used.
+	 * (This style is only exported via <mxImageExport>.) Value is "arcSize".
+	 */
+	STYLE_ARCSIZE: 'arcSize',
+
+	/**
+	 * Variable: STYLE_ABSOLUTE_ARCSIZE
+	 * 
+	 * Defines the key for the absolute arc size style. This specifies if
+	 * arcSize for rectangles is abolute or relative. Possible values are 1
+	 * and 0 (default). Value is "absoluteArcSize".
+	 */
+	STYLE_ABSOLUTE_ARCSIZE: 'absoluteArcSize',
+
+	/**
+	 * Variable: STYLE_SOURCE_PERIMETER_SPACING
+	 * 
+	 * Defines the key for the source perimeter spacing. The type of this value
+	 * is numeric. This is the distance between the source connection point of
+	 * an edge and the perimeter of the source vertex in pixels. This style
+	 * only applies to edges. Value is "sourcePerimeterSpacing".
+	 */
+	STYLE_SOURCE_PERIMETER_SPACING: 'sourcePerimeterSpacing',
+
+	/**
+	 * Variable: STYLE_TARGET_PERIMETER_SPACING
+	 * 
+	 * Defines the key for the target perimeter spacing. The type of this value
+	 * is numeric. This is the distance between the target connection point of
+	 * an edge and the perimeter of the target vertex in pixels. This style
+	 * only applies to edges. Value is "targetPerimeterSpacing".
+	 */
+	STYLE_TARGET_PERIMETER_SPACING: 'targetPerimeterSpacing',
+
+	/**
+	 * Variable: STYLE_PERIMETER_SPACING
+	 * 
+	 * Defines the key for the perimeter spacing. This is the distance between
+	 * the connection point and the perimeter in pixels. When used in a vertex
+	 * style, this applies to all incoming edges to floating ports (edges that
+	 * terminate on the perimeter of the vertex). When used in an edge style,
+	 * this spacing applies to the source and target separately, if they
+	 * terminate in floating ports (on the perimeter of the vertex). Value is
+	 * "perimeterSpacing".
+	 */
+	STYLE_PERIMETER_SPACING: 'perimeterSpacing',
+
+	/**
+	 * Variable: STYLE_SPACING
+	 * 
+	 * Defines the key for the spacing. The value represents the spacing, in
+	 * pixels, added to each side of a label in a vertex (style applies to
+	 * vertices only). Value is "spacing".
+	 */
+	STYLE_SPACING: 'spacing',
+
+	/**
+	 * Variable: STYLE_SPACING_TOP
+	 * 
+	 * Defines the key for the spacingTop style. The value represents the
+	 * spacing, in pixels, added to the top side of a label in a vertex (style
+	 * applies to vertices only). Value is "spacingTop".
+	 */
+	STYLE_SPACING_TOP: 'spacingTop',
+
+	/**
+	 * Variable: STYLE_SPACING_LEFT
+	 * 
+	 * Defines the key for the spacingLeft style. The value represents the
+	 * spacing, in pixels, added to the left side of a label in a vertex (style
+	 * applies to vertices only). Value is "spacingLeft".
+	 */
+	STYLE_SPACING_LEFT: 'spacingLeft',
+
+	/**
+	 * Variable: STYLE_SPACING_BOTTOM
+	 * 
+	 * Defines the key for the spacingBottom style The value represents the
+	 * spacing, in pixels, added to the bottom side of a label in a vertex
+	 * (style applies to vertices only). Value is "spacingBottom".
+	 */
+	STYLE_SPACING_BOTTOM: 'spacingBottom',
+
+	/**
+	 * Variable: STYLE_SPACING_RIGHT
+	 * 
+	 * Defines the key for the spacingRight style The value represents the
+	 * spacing, in pixels, added to the right side of a label in a vertex (style
+	 * applies to vertices only). Value is "spacingRight".
+	 */
+	STYLE_SPACING_RIGHT: 'spacingRight',
+
+	/**
+	 * Variable: STYLE_HORIZONTAL
+	 * 
+	 * Defines the key for the horizontal style. Possible values are
+	 * true or false. This value only applies to vertices. If the <STYLE_SHAPE>
+	 * is "SHAPE_SWIMLANE" a value of false indicates that the
+	 * swimlane should be drawn vertically, true indicates to draw it
+	 * horizontally. If the shape style does not indicate that this vertex is a
+	 * swimlane, this value affects only whether the label is drawn
+	 * horizontally or vertically. Value is "horizontal".
+	 */
+	STYLE_HORIZONTAL: 'horizontal',
+
+	/**
+	 * Variable: STYLE_DIRECTION
+	 * 
+	 * Defines the key for the direction style. The direction style is used
+	 * to specify the direction of certain shapes (eg. <mxTriangle>).
+	 * Possible values are <DIRECTION_EAST> (default), <DIRECTION_WEST>,
+	 * <DIRECTION_NORTH> and <DIRECTION_SOUTH>. Value is "direction".
+	 */
+	STYLE_DIRECTION: 'direction',
+
+	/**
+	 * Variable: STYLE_ELBOW
+	 * 
+	 * Defines the key for the elbow style. Possible values are
+	 * <ELBOW_HORIZONTAL> and <ELBOW_VERTICAL>. Default is <ELBOW_HORIZONTAL>.
+	 * This defines how the three segment orthogonal edge style leaves its
+	 * terminal vertices. The vertical style leaves the terminal vertices at
+	 * the top and bottom sides. Value is "elbow".
+	 */
+	STYLE_ELBOW: 'elbow',
+
+	/**
+	 * Variable: STYLE_FONTCOLOR
+	 * 
+	 * Defines the key for the fontColor style. Possible values are all HTML
+	 * color names or HEX codes. Value is "fontColor".
+	 */
+	STYLE_FONTCOLOR: 'fontColor',
+
+	/**
+	 * Variable: STYLE_FONTFAMILY
+	 * 
+	 * Defines the key for the fontFamily style. Possible values are names such
+	 * as Arial; Dialog; Verdana; Times New Roman. The value is of type String.
+	 * Value is fontFamily.
+	 */
+	STYLE_FONTFAMILY: 'fontFamily',
+
+	/**
+	 * Variable: STYLE_FONTSIZE
+	 * 
+	 * Defines the key for the fontSize style (in px). The type of the value
+	 * is int. Value is "fontSize".
+	 */
+	STYLE_FONTSIZE: 'fontSize',
+
+	/**
+	 * Variable: STYLE_FONTSTYLE
+	 * 
+	 * Defines the key for the fontStyle style. Values may be any logical AND
+	 * (sum) of <FONT_BOLD>, <FONT_ITALIC> and <FONT_UNDERLINE>.
+	 * The type of the value is int. Value is "fontStyle".
+	 */
+	STYLE_FONTSTYLE: 'fontStyle',
+	
+	/**
+	 * Variable: STYLE_ASPECT
+	 * 
+	 * Defines the key for the aspect style. Possible values are empty or fixed.
+	 * If fixed is used then the aspect ratio of the cell will be maintained
+	 * when resizing. Default is empty. Value is "aspect".
+	 */
+	STYLE_ASPECT: 'aspect',
+
+	/**
+	 * Variable: STYLE_AUTOSIZE
+	 * 
+	 * Defines the key for the autosize style. This specifies if a cell should be
+	 * resized automatically if the value has changed. Possible values are 0 or 1.
+	 * Default is 0. See <mxGraph.isAutoSizeCell>. This is normally combined with
+	 * <STYLE_RESIZABLE> to disable manual sizing. Value is "autosize".
+	 */
+	STYLE_AUTOSIZE: 'autosize',
+
+	/**
+	 * Variable: STYLE_FOLDABLE
+	 * 
+	 * Defines the key for the foldable style. This specifies if a cell is foldable
+	 * using a folding icon. Possible values are 0 or 1. Default is 1. See
+	 * <mxGraph.isCellFoldable>. Value is "foldable".
+	 */
+	STYLE_FOLDABLE: 'foldable',
+
+	/**
+	 * Variable: STYLE_EDITABLE
+	 * 
+	 * Defines the key for the editable style. This specifies if the value of
+	 * a cell can be edited using the in-place editor. Possible values are 0 or
+	 * 1. Default is 1. See <mxGraph.isCellEditable>. Value is "editable".
+	 */
+	STYLE_EDITABLE: 'editable',
+
+	/**
+	 * Variable: STYLE_BENDABLE
+	 * 
+	 * Defines the key for the bendable style. This specifies if the control
+	 * points of an edge can be moved. Possible values are 0 or 1. Default is
+	 * 1. See <mxGraph.isCellBendable>. Value is "bendable".
+	 */
+	STYLE_BENDABLE: 'bendable',
+
+	/**
+	 * Variable: STYLE_MOVABLE
+	 * 
+	 * Defines the key for the movable style. This specifies if a cell can
+	 * be moved. Possible values are 0 or 1. Default is 1. See
+	 * <mxGraph.isCellMovable>. Value is "movable".
+	 */
+	STYLE_MOVABLE: 'movable',
+
+	/**
+	 * Variable: STYLE_RESIZABLE
+	 * 
+	 * Defines the key for the resizable style. This specifies if a cell can
+	 * be resized. Possible values are 0 or 1. Default is 1. See
+	 * <mxGraph.isCellResizable>. Value is "resizable".
+	 */
+	STYLE_RESIZABLE: 'resizable',
+
+	/**
+	 * Variable: STYLE_RESIZE_WIDTH
+	 * 
+	 * Defines the key for the resizeWidth style. This specifies if a cell's
+	 * width is resized if the parent is resized. If this is 1 then the width
+	 * will be resized even if the cell's geometry is relative. If this is 0
+	 * then the cell's width will not be resized. Default is not defined. Value
+	 * is "resizeWidth".
+	 */
+	STYLE_RESIZE_WIDTH: 'resizeWidth',
+
+	/**
+	 * Variable: STYLE_RESIZE_WIDTH
+	 * 
+	 * Defines the key for the resizeHeight style. This specifies if a cell's
+	 * height if resize if the parent is resized. If this is 1 then the height
+	 * will be resized even if the cell's geometry is relative. If this is 0
+	 * then the cell's height will not be resized. Default is not defined. Value
+	 * is "resizeHeight".
+	 */
+	STYLE_RESIZE_HEIGHT: 'resizeHeight',
+
+	/**
+	 * Variable: STYLE_ROTATABLE
+	 * 
+	 * Defines the key for the rotatable style. This specifies if a cell can
+	 * be rotated. Possible values are 0 or 1. Default is 1. See
+	 * <mxGraph.isCellRotatable>. Value is "rotatable".
+	 */
+	STYLE_ROTATABLE: 'rotatable',
+
+	/**
+	 * Variable: STYLE_CLONEABLE
+	 * 
+	 * Defines the key for the cloneable style. This specifies if a cell can
+	 * be cloned. Possible values are 0 or 1. Default is 1. See
+	 * <mxGraph.isCellCloneable>. Value is "cloneable".
+	 */
+	STYLE_CLONEABLE: 'cloneable',
+
+	/**
+	 * Variable: STYLE_DELETABLE
+	 * 
+	 * Defines the key for the deletable style. This specifies if a cell can be
+	 * deleted. Possible values are 0 or 1. Default is 1. See
+	 * <mxGraph.isCellDeletable>. Value is "deletable".
+	 */
+	STYLE_DELETABLE: 'deletable',
+
+	/**
+	 * Variable: STYLE_SHAPE
+	 * 
+	 * Defines the key for the shape. Possible values are all constants with
+	 * a SHAPE-prefix or any newly defined shape names. Value is "shape".
+	 */
+	STYLE_SHAPE: 'shape',
+
+	/**
+	 * Variable: STYLE_EDGE
+	 * 
+	 * Defines the key for the edge style. Possible values are the functions
+	 * defined in <mxEdgeStyle>. Value is "edgeStyle".
+	 */
+	STYLE_EDGE: 'edgeStyle',
+
+	/**
+	 * Variable: STYLE_JETTY_SIZE
+	 * 
+	 * Defines the key for the jetty size in <mxEdgeStyle.OrthConnector>.
+	 * Default is 10. Possible values are all numeric values or "auto".
+	 * Value is "jettySize".
+	 */
+	STYLE_JETTY_SIZE: 'jettySize',
+
+	/**
+	 * Variable: STYLE_SOURCE_JETTY_SIZE
+	 * 
+	 * Defines the key for the jetty size in <mxEdgeStyle.OrthConnector>.
+	 * Default is 10. Possible values are numeric values or "auto". This has
+	 * precedence over <STYLE_JETTY_SIZE>. Value is "sourceJettySize".
+	 */
+	STYLE_SOURCE_JETTY_SIZE: 'sourceJettySize',
+
+	/**
+	 * Variable: targetJettySize
+	 * 
+	 * Defines the key for the jetty size in <mxEdgeStyle.OrthConnector>.
+	 * Default is 10. Possible values are numeric values or "auto". This has
+	 * precedence over <STYLE_JETTY_SIZE>. Value is "targetJettySize".
+	 */
+	STYLE_TARGET_JETTY_SIZE: 'targetJettySize',
+
+	/**
+	 * Variable: STYLE_LOOP
+	 * 
+	 * Defines the key for the loop style. Possible values are the functions
+	 * defined in <mxEdgeStyle>. Value is "loopStyle".
+	 */
+	STYLE_LOOP: 'loopStyle',
+
+	/**
+	 * Variable: STYLE_ORTHOGONAL_LOOP
+	 * 
+	 * Defines the key for the orthogonal loop style. Possible values are 0 and
+	 * 1. Default is 0. Value is "orthogonalLoop". Use this style to specify
+	 * if loops should be routed using an orthogonal router. Currently, this
+	 * uses <mxEdgeStyle.OrthConnector> but will be replaced with a dedicated
+	 * orthogonal loop router in later releases.
+	 */
+	STYLE_ORTHOGONAL_LOOP: 'orthogonalLoop',
+
+	/**
+	 * Variable: STYLE_ROUTING_CENTER_X
+	 * 
+	 * Defines the key for the horizontal routing center. Possible values are
+	 * between -0.5 and 0.5. This is the relative offset from the center used
+	 * for connecting edges. The type of this value is numeric. Value is
+	 * "routingCenterX".
+	 */
+	STYLE_ROUTING_CENTER_X: 'routingCenterX',
+
+	/**
+	 * Variable: STYLE_ROUTING_CENTER_Y
+	 * 
+	 * Defines the key for the vertical routing center. Possible values are
+	 * between -0.5 and 0.5. This is the relative offset from the center used
+	 * for connecting edges. The type of this value is numeric. Value is
+	 * "routingCenterY".
+	 */
+	STYLE_ROUTING_CENTER_Y: 'routingCenterY',
+
+	/**
+	 * Variable: FONT_BOLD
+	 * 
+	 * Constant for bold fonts. Default is 1.
+	 */
+	FONT_BOLD: 1,
+
+	/**
+	 * Variable: FONT_ITALIC
+	 * 
+	 * Constant for italic fonts. Default is 2.
+	 */
+	FONT_ITALIC: 2,
+
+	/**
+	 * Variable: FONT_UNDERLINE
+	 * 
+	 * Constant for underlined fonts. Default is 4.
+	 */
+	FONT_UNDERLINE: 4,
+
+	/**
+	 * Variable: SHAPE_RECTANGLE
+	 * 
+	 * Name under which <mxRectangleShape> is registered in <mxCellRenderer>.
+	 * Default is rectangle.
+	 */
+	SHAPE_RECTANGLE: 'rectangle',
+
+	/**
+	 * Variable: SHAPE_ELLIPSE
+	 * 
+	 * Name under which <mxEllipse> is registered in <mxCellRenderer>.
+	 * Default is ellipse.
+	 */
+	SHAPE_ELLIPSE: 'ellipse',
+
+	/**
+	 * Variable: SHAPE_DOUBLE_ELLIPSE
+	 * 
+	 * Name under which <mxDoubleEllipse> is registered in <mxCellRenderer>.
+	 * Default is doubleEllipse.
+	 */
+	SHAPE_DOUBLE_ELLIPSE: 'doubleEllipse',
+
+	/**
+	 * Variable: SHAPE_RHOMBUS
+	 * 
+	 * Name under which <mxRhombus> is registered in <mxCellRenderer>.
+	 * Default is rhombus.
+	 */
+	SHAPE_RHOMBUS: 'rhombus',
+
+	/**
+	 * Variable: SHAPE_LINE
+	 * 
+	 * Name under which <mxLine> is registered in <mxCellRenderer>.
+	 * Default is line.
+	 */
+	SHAPE_LINE: 'line',
+
+	/**
+	 * Variable: SHAPE_IMAGE
+	 * 
+	 * Name under which <mxImageShape> is registered in <mxCellRenderer>.
+	 * Default is image.
+	 */
+	SHAPE_IMAGE: 'image',
+	
+	/**
+	 * Variable: SHAPE_ARROW
+	 * 
+	 * Name under which <mxArrow> is registered in <mxCellRenderer>.
+	 * Default is arrow.
+	 */
+	SHAPE_ARROW: 'arrow',
+	
+	/**
+	 * Variable: SHAPE_ARROW_CONNECTOR
+	 * 
+	 * Name under which <mxArrowConnector> is registered in <mxCellRenderer>.
+	 * Default is arrowConnector.
+	 */
+	SHAPE_ARROW_CONNECTOR: 'arrowConnector',
+	
+	/**
+	 * Variable: SHAPE_LABEL
+	 * 
+	 * Name under which <mxLabel> is registered in <mxCellRenderer>.
+	 * Default is label.
+	 */
+	SHAPE_LABEL: 'label',
+	
+	/**
+	 * Variable: SHAPE_CYLINDER
+	 * 
+	 * Name under which <mxCylinder> is registered in <mxCellRenderer>.
+	 * Default is cylinder.
+	 */
+	SHAPE_CYLINDER: 'cylinder',
+	
+	/**
+	 * Variable: SHAPE_SWIMLANE
+	 * 
+	 * Name under which <mxSwimlane> is registered in <mxCellRenderer>.
+	 * Default is swimlane.
+	 */
+	SHAPE_SWIMLANE: 'swimlane',
+		
+	/**
+	 * Variable: SHAPE_CONNECTOR
+	 * 
+	 * Name under which <mxConnector> is registered in <mxCellRenderer>.
+	 * Default is connector.
+	 */
+	SHAPE_CONNECTOR: 'connector',
+
+	/**
+	 * Variable: SHAPE_ACTOR
+	 * 
+	 * Name under which <mxActor> is registered in <mxCellRenderer>.
+	 * Default is actor.
+	 */
+	SHAPE_ACTOR: 'actor',
+		
+	/**
+	 * Variable: SHAPE_CLOUD
+	 * 
+	 * Name under which <mxCloud> is registered in <mxCellRenderer>.
+	 * Default is cloud.
+	 */
+	SHAPE_CLOUD: 'cloud',
+		
+	/**
+	 * Variable: SHAPE_TRIANGLE
+	 * 
+	 * Name under which <mxTriangle> is registered in <mxCellRenderer>.
+	 * Default is triangle.
+	 */
+	SHAPE_TRIANGLE: 'triangle',
+		
+	/**
+	 * Variable: SHAPE_HEXAGON
+	 * 
+	 * Name under which <mxHexagon> is registered in <mxCellRenderer>.
+	 * Default is hexagon.
+	 */
+	SHAPE_HEXAGON: 'hexagon',
+
+	/**
+	 * Variable: ARROW_CLASSIC
+	 * 
+	 * Constant for classic arrow markers.
+	 */
+	ARROW_CLASSIC: 'classic',
+
+	/**
+	 * Variable: ARROW_CLASSIC_THIN
+	 * 
+	 * Constant for thin classic arrow markers.
+	 */
+	ARROW_CLASSIC_THIN: 'classicThin',
+
+	/**
+	 * Variable: ARROW_BLOCK
+	 * 
+	 * Constant for block arrow markers.
+	 */
+	ARROW_BLOCK: 'block',
+
+	/**
+	 * Variable: ARROW_BLOCK_THIN
+	 * 
+	 * Constant for thin block arrow markers.
+	 */
+	ARROW_BLOCK_THIN: 'blockThin',
+
+	/**
+	 * Variable: ARROW_OPEN
+	 * 
+	 * Constant for open arrow markers.
+	 */
+	ARROW_OPEN: 'open',
+
+	/**
+	 * Variable: ARROW_OPEN_THIN
+	 * 
+	 * Constant for thin open arrow markers.
+	 */
+	ARROW_OPEN_THIN: 'openThin',
+
+	/**
+	 * Variable: ARROW_OVAL
+	 * 
+	 * Constant for oval arrow markers.
+	 */
+	ARROW_OVAL: 'oval',
+
+	/**
+	 * Variable: ARROW_DIAMOND
+	 * 
+	 * Constant for diamond arrow markers.
+	 */
+	ARROW_DIAMOND: 'diamond',
+
+	/**
+	 * Variable: ARROW_DIAMOND_THIN
+	 * 
+	 * Constant for thin diamond arrow markers.
+	 */
+	ARROW_DIAMOND_THIN: 'diamondThin',
+
+	/**
+	 * Variable: ALIGN_LEFT
+	 * 
+	 * Constant for left horizontal alignment. Default is left.
+	 */
+	ALIGN_LEFT: 'left',
+
+	/**
+	 * Variable: ALIGN_CENTER
+	 * 
+	 * Constant for center horizontal alignment. Default is center.
+	 */
+	ALIGN_CENTER: 'center',
+
+	/**
+	 * Variable: ALIGN_RIGHT
+	 * 
+	 * Constant for right horizontal alignment. Default is right.
+	 */
+	ALIGN_RIGHT: 'right',
+
+	/**
+	 * Variable: ALIGN_TOP
+	 * 
+	 * Constant for top vertical alignment. Default is top.
+	 */
+	ALIGN_TOP: 'top',
+
+	/**
+	 * Variable: ALIGN_MIDDLE
+	 * 
+	 * Constant for middle vertical alignment. Default is middle.
+	 */
+	ALIGN_MIDDLE: 'middle',
+
+	/**
+	 * Variable: ALIGN_BOTTOM
+	 * 
+	 * Constant for bottom vertical alignment. Default is bottom.
+	 */
+	ALIGN_BOTTOM: 'bottom',
+
+	/**
+	 * Variable: DIRECTION_NORTH
+	 * 
+	 * Constant for direction north. Default is north.
+	 */
+	DIRECTION_NORTH: 'north',
+
+	/**
+	 * Variable: DIRECTION_SOUTH
+	 * 
+	 * Constant for direction south. Default is south.
+	 */
+	DIRECTION_SOUTH: 'south',
+
+	/**
+	 * Variable: DIRECTION_EAST
+	 * 
+	 * Constant for direction east. Default is east.
+	 */
+	DIRECTION_EAST: 'east',
+
+	/**
+	 * Variable: DIRECTION_WEST
+	 * 
+	 * Constant for direction west. Default is west.
+	 */
+	DIRECTION_WEST: 'west',
+
+	/**
+	 * Variable: TEXT_DIRECTION_DEFAULT
+	 * 
+	 * Constant for text direction default. Default is an empty string. Use
+	 * this value to use the default text direction of the operating system. 
+	 */
+	TEXT_DIRECTION_DEFAULT: '',
+
+	/**
+	 * Variable: TEXT_DIRECTION_AUTO
+	 * 
+	 * Constant for text direction automatic. Default is auto. Use this value
+	 * to find the direction for a given text with <mxText.getAutoDirection>. 
+	 */
+	TEXT_DIRECTION_AUTO: 'auto',
+
+	/**
+	 * Variable: TEXT_DIRECTION_LTR
+	 * 
+	 * Constant for text direction left to right. Default is ltr. Use this
+	 * value for left to right text direction.
+	 */
+	TEXT_DIRECTION_LTR: 'ltr',
+
+	/**
+	 * Variable: TEXT_DIRECTION_RTL
+	 * 
+	 * Constant for text direction right to left. Default is rtl. Use this
+	 * value for right to left text direction.
+	 */
+	TEXT_DIRECTION_RTL: 'rtl',
+
+	/**
+	 * Variable: DIRECTION_MASK_NONE
+	 * 
+	 * Constant for no direction.
+	 */
+	DIRECTION_MASK_NONE: 0,
+
+	/**
+	 * Variable: DIRECTION_MASK_WEST
+	 * 
+	 * Bitwise mask for west direction.
+	 */
+	DIRECTION_MASK_WEST: 1,
+	
+	/**
+	 * Variable: DIRECTION_MASK_NORTH
+	 * 
+	 * Bitwise mask for north direction.
+	 */
+	DIRECTION_MASK_NORTH: 2,
+
+	/**
+	 * Variable: DIRECTION_MASK_SOUTH
+	 * 
+	 * Bitwise mask for south direction.
+	 */
+	DIRECTION_MASK_SOUTH: 4,
+
+	/**
+	 * Variable: DIRECTION_MASK_EAST
+	 * 
+	 * Bitwise mask for east direction.
+	 */
+	DIRECTION_MASK_EAST: 8,
+	
+	/**
+	 * Variable: DIRECTION_MASK_ALL
+	 * 
+	 * Bitwise mask for all directions.
+	 */
+	DIRECTION_MASK_ALL: 15,
+
+	/**
+	 * Variable: ELBOW_VERTICAL
+	 * 
+	 * Constant for elbow vertical. Default is horizontal.
+	 */
+	ELBOW_VERTICAL: 'vertical',
+
+	/**
+	 * Variable: ELBOW_HORIZONTAL
+	 * 
+	 * Constant for elbow horizontal. Default is horizontal.
+	 */
+	ELBOW_HORIZONTAL: 'horizontal',
+
+	/**
+	 * Variable: EDGESTYLE_ELBOW
+	 * 
+	 * Name of the elbow edge style. Can be used as a string value
+	 * for the STYLE_EDGE style.
+	 */
+	EDGESTYLE_ELBOW: 'elbowEdgeStyle',
+
+	/**
+	 * Variable: EDGESTYLE_ENTITY_RELATION
+	 * 
+	 * Name of the entity relation edge style. Can be used as a string value
+	 * for the STYLE_EDGE style.
+	 */
+	EDGESTYLE_ENTITY_RELATION: 'entityRelationEdgeStyle',
+
+	/**
+	 * Variable: EDGESTYLE_LOOP
+	 * 
+	 * Name of the loop edge style. Can be used as a string value
+	 * for the STYLE_EDGE style.
+	 */
+	EDGESTYLE_LOOP: 'loopEdgeStyle',
+
+	/**
+	 * Variable: EDGESTYLE_SIDETOSIDE
+	 * 
+	 * Name of the side to side edge style. Can be used as a string value
+	 * for the STYLE_EDGE style.
+	 */
+	EDGESTYLE_SIDETOSIDE: 'sideToSideEdgeStyle',
+
+	/**
+	 * Variable: EDGESTYLE_TOPTOBOTTOM
+	 * 
+	 * Name of the top to bottom edge style. Can be used as a string value
+	 * for the STYLE_EDGE style.
+	 */
+	EDGESTYLE_TOPTOBOTTOM: 'topToBottomEdgeStyle',
+
+	/**
+	 * Variable: EDGESTYLE_ORTHOGONAL
+	 * 
+	 * Name of the generic orthogonal edge style. Can be used as a string value
+	 * for the STYLE_EDGE style.
+	 */
+	EDGESTYLE_ORTHOGONAL: 'orthogonalEdgeStyle',
+
+	/**
+	 * Variable: EDGESTYLE_SEGMENT
+	 * 
+	 * Name of the generic segment edge style. Can be used as a string value
+	 * for the STYLE_EDGE style.
+	 */
+	EDGESTYLE_SEGMENT: 'segmentEdgeStyle',
+ 
+	/**
+	 * Variable: PERIMETER_ELLIPSE
+	 * 
+	 * Name of the ellipse perimeter. Can be used as a string value
+	 * for the STYLE_PERIMETER style.
+	 */
+	PERIMETER_ELLIPSE: 'ellipsePerimeter',
+
+	/**
+	 * Variable: PERIMETER_RECTANGLE
+	 *
+	 * Name of the rectangle perimeter. Can be used as a string value
+	 * for the STYLE_PERIMETER style.
+	 */
+	PERIMETER_RECTANGLE: 'rectanglePerimeter',
+
+	/**
+	 * Variable: PERIMETER_RHOMBUS
+	 * 
+	 * Name of the rhombus perimeter. Can be used as a string value
+	 * for the STYLE_PERIMETER style.
+	 */
+	PERIMETER_RHOMBUS: 'rhombusPerimeter',
+
+	/**
+	 * Variable: PERIMETER_HEXAGON
+	 * 
+	 * Name of the hexagon perimeter. Can be used as a string value 
+	 * for the STYLE_PERIMETER style.
+	 */
+	PERIMETER_HEXAGON: 'hexagonPerimeter',
+
+	/**
+	 * Variable: PERIMETER_TRIANGLE
+	 * 
+	 * Name of the triangle perimeter. Can be used as a string value
+	 * for the STYLE_PERIMETER style.
+	 */
+	PERIMETER_TRIANGLE: 'trianglePerimeter'
+
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/util/mxDictionary.js b/airavata-kubernetes/workflow-composer/src/js/util/mxDictionary.js
new file mode 100644
index 0000000..536dafa
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/util/mxDictionary.js
@@ -0,0 +1,130 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxDictionary
+ *
+ * A wrapper class for an associative array with object keys. Note: This
+ * implementation uses <mxObjectIdentitiy> to turn object keys into strings.
+ * 
+ * Constructor: mxEventSource
+ *
+ * Constructs a new dictionary which allows object to be used as keys.
+ */
+function mxDictionary()
+{
+	this.clear();
+};
+
+/**
+ * Function: map
+ *
+ * Stores the (key, value) pairs in this dictionary.
+ */
+mxDictionary.prototype.map = null;
+
+/**
+ * Function: clear
+ *
+ * Clears the dictionary.
+ */
+mxDictionary.prototype.clear = function()
+{
+	this.map = {};
+};
+
+/**
+ * Function: get
+ *
+ * Returns the value for the given key.
+ */
+mxDictionary.prototype.get = function(key)
+{
+	var id = mxObjectIdentity.get(key);
+	
+	return this.map[id];
+};
+
+/**
+ * Function: put
+ *
+ * Stores the value under the given key and returns the previous
+ * value for that key.
+ */
+mxDictionary.prototype.put = function(key, value)
+{
+	var id = mxObjectIdentity.get(key);
+	var previous = this.map[id];
+	this.map[id] = value;
+	
+	return previous;
+};
+
+/**
+ * Function: remove
+ *
+ * Removes the value for the given key and returns the value that
+ * has been removed.
+ */
+mxDictionary.prototype.remove = function(key)
+{
+	var id = mxObjectIdentity.get(key);
+	var previous = this.map[id];
+	delete this.map[id];
+	
+	return previous;
+};
+
+/**
+ * Function: getKeys
+ *
+ * Returns all keys as an array.
+ */
+mxDictionary.prototype.getKeys = function()
+{
+	var result = [];
+	
+	for (var key in this.map)
+	{
+		result.push(key);
+	}
+	
+	return result;
+};
+
+/**
+ * Function: getValues
+ *
+ * Returns all values as an array.
+ */
+mxDictionary.prototype.getValues = function()
+{
+	var result = [];
+	
+	for (var key in this.map)
+	{
+		result.push(this.map[key]);
+	}
+	
+	return result;
+};
+
+/**
+ * Function: visit
+ *
+ * Visits all entries in the dictionary using the given function with the
+ * following signature: function(key, value) where key is a string and
+ * value is an object.
+ * 
+ * Parameters:
+ * 
+ * visitor - A function that takes the key and value as arguments.
+ */
+mxDictionary.prototype.visit = function(visitor)
+{
+	for (var key in this.map)
+	{
+		visitor(key, this.map[key]);
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/util/mxDivResizer.js b/airavata-kubernetes/workflow-composer/src/js/util/mxDivResizer.js
new file mode 100644
index 0000000..0089796
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/util/mxDivResizer.js
@@ -0,0 +1,151 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxDivResizer
+ * 
+ * Maintains the size of a div element in Internet Explorer. This is a
+ * workaround for the right and bottom style being ignored in IE.
+ * 
+ * If you need a div to cover the scrollwidth and -height of a document,
+ * then you can use this class as follows:
+ * 
+ * (code)
+ * var resizer = new mxDivResizer(background);
+ * resizer.getDocumentHeight = function()
+ * {
+ *   return document.body.scrollHeight;
+ * }
+ * resizer.getDocumentWidth = function()
+ * {
+ *   return document.body.scrollWidth;
+ * }
+ * resizer.resize();
+ * (end)
+ * 
+ * Constructor: mxDivResizer
+ * 
+ * Constructs an object that maintains the size of a div
+ * element when the window is being resized. This is only
+ * required for Internet Explorer as it ignores the respective
+ * stylesheet information for DIV elements.
+ * 
+ * Parameters:
+ * 
+ * div - Reference to the DOM node whose size should be maintained.
+ * container - Optional Container that contains the div. Default is the
+ * window.
+ */
+function mxDivResizer(div, container)
+{
+	if (div.nodeName.toLowerCase() == 'div')
+	{
+		if (container == null)
+		{
+			container = window;
+		}
+
+		this.div = div;
+		var style = mxUtils.getCurrentStyle(div);
+		
+		if (style != null)
+		{
+			this.resizeWidth = style.width == 'auto';
+			this.resizeHeight = style.height == 'auto';
+		}
+		
+		mxEvent.addListener(container, 'resize',
+			mxUtils.bind(this, function(evt)
+			{
+				if (!this.handlingResize)
+				{
+					this.handlingResize = true;
+					this.resize();
+					this.handlingResize = false;
+				}
+			})
+		);
+		
+		this.resize();
+	}
+};
+
+/**
+ * Function: resizeWidth
+ * 
+ * Boolean specifying if the width should be updated.
+ */
+mxDivResizer.prototype.resizeWidth = true;
+
+/**
+ * Function: resizeHeight
+ * 
+ * Boolean specifying if the height should be updated.
+ */
+mxDivResizer.prototype.resizeHeight = true;
+
+/**
+ * Function: handlingResize
+ * 
+ * Boolean specifying if the width should be updated.
+ */
+mxDivResizer.prototype.handlingResize = false;
+
+/**
+ * Function: resize
+ * 
+ * Updates the style of the DIV after the window has been resized.
+ */
+mxDivResizer.prototype.resize = function()
+{
+	var w = this.getDocumentWidth();
+	var h = this.getDocumentHeight();
+
+	var l = parseInt(this.div.style.left);
+	var r = parseInt(this.div.style.right);
+	var t = parseInt(this.div.style.top);
+	var b = parseInt(this.div.style.bottom);
+	
+	if (this.resizeWidth &&
+		!isNaN(l) &&
+		!isNaN(r) &&
+		l >= 0 &&
+		r >= 0 &&
+		w - r - l > 0)
+	{
+		this.div.style.width = (w - r - l)+'px';
+	}
+	
+	if (this.resizeHeight &&
+		!isNaN(t) &&
+		!isNaN(b) &&
+		t >= 0 &&
+		b >= 0 &&
+		h - t - b > 0)
+	{
+		this.div.style.height = (h - t - b)+'px';
+	}
+};
+
+/**
+ * Function: getDocumentWidth
+ * 
+ * Hook for subclassers to return the width of the document (without
+ * scrollbars).
+ */
+mxDivResizer.prototype.getDocumentWidth = function()
+{
+	return document.body.clientWidth;
+};
+
+/**
+ * Function: getDocumentHeight
+ * 
+ * Hook for subclassers to return the height of the document (without
+ * scrollbars).
+ */
+mxDivResizer.prototype.getDocumentHeight = function()
+{
+	return document.body.clientHeight;
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/util/mxDragSource.js b/airavata-kubernetes/workflow-composer/src/js/util/mxDragSource.js
new file mode 100644
index 0000000..1670489
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/util/mxDragSource.js
@@ -0,0 +1,679 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxDragSource
+ * 
+ * Wrapper to create a drag source from a DOM element so that the element can
+ * be dragged over a graph and dropped into the graph as a new cell.
+ * 
+ * Problem is that in the dropHandler the current preview location is not
+ * available, so the preview and the dropHandler must match.
+ * 
+ * Constructor: mxDragSource
+ * 
+ * Constructs a new drag source for the given element.
+ */
+function mxDragSource(element, dropHandler)
+{
+	this.element = element;
+	this.dropHandler = dropHandler;
+	
+	// Handles a drag gesture on the element
+	mxEvent.addGestureListeners(element, mxUtils.bind(this, function(evt)
+	{
+		this.mouseDown(evt);
+	}));
+	
+	// Prevents native drag and drop
+	mxEvent.addListener(element, 'dragstart', function(evt)
+	{
+		mxEvent.consume(evt);
+	});
+	
+	this.eventConsumer = function(sender, evt)
+	{
+		var evtName = evt.getProperty('eventName');
+		var me = evt.getProperty('event');
+		
+		if (evtName != mxEvent.MOUSE_DOWN)
+		{
+			me.consume();
+		}
+	};
+};
+
+/**
+ * Variable: element
+ *
+ * Reference to the DOM node which was made draggable.
+ */
+mxDragSource.prototype.element = null;
+
+/**
+ * Variable: dropHandler
+ *
+ * Holds the DOM node that is used to represent the drag preview. If this is
+ * null then the source element will be cloned and used for the drag preview.
+ */
+mxDragSource.prototype.dropHandler = null;
+
+/**
+ * Variable: dragOffset
+ *
+ * <mxPoint> that specifies the offset of the <dragElement>. Default is null.
+ */
+mxDragSource.prototype.dragOffset = null;
+
+/**
+ * Variable: dragElement
+ *
+ * Holds the DOM node that is used to represent the drag preview. If this is
+ * null then the source element will be cloned and used for the drag preview.
+ */
+mxDragSource.prototype.dragElement = null;
+
+/**
+ * Variable: previewElement
+ *
+ * Optional <mxRectangle> that specifies the unscaled size of the preview.
+ */
+mxDragSource.prototype.previewElement = null;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies if this drag source is enabled. Default is true.
+ */
+mxDragSource.prototype.enabled = true;
+
+/**
+ * Variable: currentGraph
+ *
+ * Reference to the <mxGraph> that is the current drop target.
+ */
+mxDragSource.prototype.currentGraph = null;
+
+/**
+ * Variable: currentDropTarget
+ *
+ * Holds the current drop target under the mouse.
+ */
+mxDragSource.prototype.currentDropTarget = null;
+
+/**
+ * Variable: currentPoint
+ *
+ * Holds the current drop location.
+ */
+mxDragSource.prototype.currentPoint = null;
+
+/**
+ * Variable: currentGuide
+ *
+ * Holds an <mxGuide> for the <currentGraph> if <dragPreview> is not null.
+ */
+mxDragSource.prototype.currentGuide = null;
+
+/**
+ * Variable: currentGuide
+ *
+ * Holds an <mxGuide> for the <currentGraph> if <dragPreview> is not null.
+ */
+mxDragSource.prototype.currentHighlight = null;
+
+/**
+ * Variable: autoscroll
+ *
+ * Specifies if the graph should scroll automatically. Default is true.
+ */
+mxDragSource.prototype.autoscroll = true;
+
+/**
+ * Variable: guidesEnabled
+ *
+ * Specifies if <mxGuide> should be enabled. Default is true.
+ */
+mxDragSource.prototype.guidesEnabled = true;
+
+/**
+ * Variable: gridEnabled
+ *
+ * Specifies if the grid should be allowed. Default is true.
+ */
+mxDragSource.prototype.gridEnabled = true;
+
+/**
+ * Variable: highlightDropTargets
+ *
+ * Specifies if drop targets should be highlighted. Default is true.
+ */
+mxDragSource.prototype.highlightDropTargets = true;
+
+/**
+ * Variable: dragElementZIndex
+ * 
+ * ZIndex for the drag element. Default is 100.
+ */
+mxDragSource.prototype.dragElementZIndex = 100;
+
+/**
+ * Variable: dragElementOpacity
+ * 
+ * Opacity of the drag element in %. Default is 70.
+ */
+mxDragSource.prototype.dragElementOpacity = 70;
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns <enabled>.
+ */
+mxDragSource.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ * 
+ * Sets <enabled>.
+ */
+mxDragSource.prototype.setEnabled = function(value)
+{
+	this.enabled = value;
+};
+
+/**
+ * Function: isGuidesEnabled
+ * 
+ * Returns <guidesEnabled>.
+ */
+mxDragSource.prototype.isGuidesEnabled = function()
+{
+	return this.guidesEnabled;
+};
+
+/**
+ * Function: setGuidesEnabled
+ * 
+ * Sets <guidesEnabled>.
+ */
+mxDragSource.prototype.setGuidesEnabled = function(value)
+{
+	this.guidesEnabled = value;
+};
+
+/**
+ * Function: isGridEnabled
+ * 
+ * Returns <gridEnabled>.
+ */
+mxDragSource.prototype.isGridEnabled = function()
+{
+	return this.gridEnabled;
+};
+
+/**
+ * Function: setGridEnabled
+ * 
+ * Sets <gridEnabled>.
+ */
+mxDragSource.prototype.setGridEnabled = function(value)
+{
+	this.gridEnabled = value;
+};
+
+/**
+ * Function: getGraphForEvent
+ * 
+ * Returns the graph for the given mouse event. This implementation returns
+ * null.
+ */
+mxDragSource.prototype.getGraphForEvent = function(evt)
+{
+	return null;
+};
+
+/**
+ * Function: getDropTarget
+ * 
+ * Returns the drop target for the given graph and coordinates. This
+ * implementation uses <mxGraph.getCellAt>.
+ */
+mxDragSource.prototype.getDropTarget = function(graph, x, y, evt)
+{
+	return graph.getCellAt(x, y);
+};
+
+/**
+ * Function: createDragElement
+ * 
+ * Creates and returns a clone of the <dragElementPrototype> or the <element>
+ * if the former is not defined.
+ */
+mxDragSource.prototype.createDragElement = function(evt)
+{
+	return this.element.cloneNode(true);
+};
+
+/**
+ * Function: createPreviewElement
+ * 
+ * Creates and returns an element which can be used as a preview in the given
+ * graph.
+ */
+mxDragSource.prototype.createPreviewElement = function(graph)
+{
+	return null;
+};
+
+/**
+ * Function: isActive
+ * 
+ * Returns true if this drag source is active.
+ */
+mxDragSource.prototype.isActive = function()
+{
+	return this.mouseMoveHandler != null;
+};
+
+/**
+ * Function: reset
+ * 
+ * Stops and removes everything and restores the state of the object.
+ */
+mxDragSource.prototype.reset = function()
+{
+	if (this.currentGraph != null)
+	{
+		this.dragExit(this.currentGraph);
+		this.currentGraph = null;
+	}
+	
+	this.removeDragElement();
+	this.removeListeners();
+	this.stopDrag();
+};
+
+/**
+ * Function: mouseDown
+ * 
+ * Returns the drop target for the given graph and coordinates. This
+ * implementation uses <mxGraph.getCellAt>.
+ * 
+ * To ignore popup menu events for a drag source, this function can be
+ * overridden as follows.
+ * 
+ * (code)
+ * var mouseDown = dragSource.mouseDown;
+ * 
+ * dragSource.mouseDown = function(evt)
+ * {
+ *   if (!mxEvent.isPopupTrigger(evt))
+ *   {
+ *     mouseDown.apply(this, arguments);
+ *   }
+ * };
+ * (end)
+ */
+mxDragSource.prototype.mouseDown = function(evt)
+{
+	if (this.enabled && !mxEvent.isConsumed(evt) && this.mouseMoveHandler == null)
+	{
+		this.startDrag(evt);
+		this.mouseMoveHandler = mxUtils.bind(this, this.mouseMove);
+		this.mouseUpHandler = mxUtils.bind(this, this.mouseUp);		
+		mxEvent.addGestureListeners(document, null, this.mouseMoveHandler, this.mouseUpHandler);
+		
+		if (mxClient.IS_TOUCH && !mxEvent.isMouseEvent(evt))
+		{
+			this.eventSource = mxEvent.getSource(evt);
+			mxEvent.addGestureListeners(this.eventSource, null, this.mouseMoveHandler, this.mouseUpHandler);
+		}
+	}
+};
+
+/**
+ * Function: startDrag
+ * 
+ * Creates the <dragElement> using <createDragElement>.
+ */
+mxDragSource.prototype.startDrag = function(evt)
+{
+	this.dragElement = this.createDragElement(evt);
+	this.dragElement.style.position = 'absolute';
+	this.dragElement.style.zIndex = this.dragElementZIndex;
+	mxUtils.setOpacity(this.dragElement, this.dragElementOpacity);
+};
+
+/**
+ * Function: stopDrag
+ * 
+ * Invokes <removeDragElement>.
+ */
+mxDragSource.prototype.stopDrag = function()
+{
+	// LATER: This used to have a mouse event. If that is still needed we need to add another
+	// final call to the DnD protocol to add a cleanup step in the case of escape press, which
+	// is not associated with a mouse event and which currently calles this method.
+	this.removeDragElement();
+};
+
+/**
+ * Function: removeDragElement
+ * 
+ * Removes and destroys the <dragElement>.
+ */
+mxDragSource.prototype.removeDragElement = function()
+{
+	if (this.dragElement != null)
+	{
+		if (this.dragElement.parentNode != null)
+		{
+			this.dragElement.parentNode.removeChild(this.dragElement);
+		}
+		
+		this.dragElement = null;
+	}
+};
+
+/**
+ * Function: graphContainsEvent
+ * 
+ * Returns true if the given graph contains the given event.
+ */
+mxDragSource.prototype.graphContainsEvent = function(graph, evt)
+{
+	var x = mxEvent.getClientX(evt);
+	var y = mxEvent.getClientY(evt);
+	var offset = mxUtils.getOffset(graph.container);
+	var origin = mxUtils.getScrollOrigin();
+
+	// Checks if event is inside the bounds of the graph container
+	return x >= offset.x - origin.x && y >= offset.y - origin.y &&
+		x <= offset.x - origin.x + graph.container.offsetWidth &&
+		y <= offset.y - origin.y + graph.container.offsetHeight;
+};
+
+/**
+ * Function: mouseMove
+ * 
+ * Gets the graph for the given event using <getGraphForEvent>, updates the
+ * <currentGraph>, calling <dragEnter> and <dragExit> on the new and old graph,
+ * respectively, and invokes <dragOver> if <currentGraph> is not null.
+ */
+mxDragSource.prototype.mouseMove = function(evt)
+{
+	var graph = this.getGraphForEvent(evt);
+	
+	// Checks if event is inside the bounds of the graph container
+	if (graph != null && !this.graphContainsEvent(graph, evt))
+	{
+		graph = null;
+	}
+
+	if (graph != this.currentGraph)
+	{
+		if (this.currentGraph != null)
+		{
+			this.dragExit(this.currentGraph, evt);
+		}
+		
+		this.currentGraph = graph;
+		
+		if (this.currentGraph != null)
+		{
+			this.dragEnter(this.currentGraph, evt);
+		}
+	}
+	
+	if (this.currentGraph != null)
+	{
+		this.dragOver(this.currentGraph, evt);
+	}
+
+	if (this.dragElement != null && (this.previewElement == null || this.previewElement.style.visibility != 'visible'))
+	{
+		var x = mxEvent.getClientX(evt);
+		var y = mxEvent.getClientY(evt);
+		
+		if (this.dragElement.parentNode == null)
+		{
+			document.body.appendChild(this.dragElement);
+		}
+
+		this.dragElement.style.visibility = 'visible';
+		
+		if (this.dragOffset != null)
+		{
+			x += this.dragOffset.x;
+			y += this.dragOffset.y;
+		}
+		
+		var offset = mxUtils.getDocumentScrollOrigin(document);
+		
+		this.dragElement.style.left = (x + offset.x) + 'px';
+		this.dragElement.style.top = (y + offset.y) + 'px';
+	}
+	else if (this.dragElement != null)
+	{
+		this.dragElement.style.visibility = 'hidden';
+	}
+	
+	mxEvent.consume(evt);
+};
+
+/**
+ * Function: mouseUp
+ * 
+ * Processes the mouse up event and invokes <drop>, <dragExit> and <stopDrag>
+ * as required.
+ */
+mxDragSource.prototype.mouseUp = function(evt)
+{
+	if (this.currentGraph != null)
+	{
+		if (this.currentPoint != null && (this.previewElement == null ||
+			this.previewElement.style.visibility != 'hidden'))
+		{
+			var scale = this.currentGraph.view.scale;
+			var tr = this.currentGraph.view.translate;
+			var x = this.currentPoint.x / scale - tr.x;
+			var y = this.currentPoint.y / scale - tr.y;
+			
+			this.drop(this.currentGraph, evt, this.currentDropTarget, x, y);
+		}
+		
+		this.dragExit(this.currentGraph);
+		this.currentGraph = null;
+	}
+
+	this.stopDrag();
+	this.removeListeners();
+	
+	mxEvent.consume(evt);
+};
+
+/**
+ * Function: removeListeners
+ * 
+ * Actives the given graph as a drop target.
+ */
+mxDragSource.prototype.removeListeners = function()
+{
+	if (this.eventSource != null)
+	{
+		mxEvent.removeGestureListeners(this.eventSource, null, this.mouseMoveHandler, this.mouseUpHandler);
+		this.eventSource = null;
+	}
+	
+	mxEvent.removeGestureListeners(document, null, this.mouseMoveHandler, this.mouseUpHandler);
+	this.mouseMoveHandler = null;
+	this.mouseUpHandler = null;
+};
+
+/**
+ * Function: dragEnter
+ * 
+ * Actives the given graph as a drop target.
+ */
+mxDragSource.prototype.dragEnter = function(graph, evt)
+{
+	graph.isMouseDown = true;
+	graph.isMouseTrigger = mxEvent.isMouseEvent(evt);
+	this.previewElement = this.createPreviewElement(graph);
+	
+	// Guide is only needed if preview element is used
+	if (this.isGuidesEnabled() && this.previewElement != null)
+	{
+		this.currentGuide = new mxGuide(graph, graph.graphHandler.getGuideStates());
+	}
+	
+	if (this.highlightDropTargets)
+	{
+		this.currentHighlight = new mxCellHighlight(graph, mxConstants.DROP_TARGET_COLOR);
+	}
+	
+	// Consumes all events in the current graph before they are fired
+	graph.addListener(mxEvent.FIRE_MOUSE_EVENT, this.eventConsumer);
+};
+
+/**
+ * Function: dragExit
+ * 
+ * Deactivates the given graph as a drop target.
+ */
+mxDragSource.prototype.dragExit = function(graph, evt)
+{
+	this.currentDropTarget = null;
+	this.currentPoint = null;
+	graph.isMouseDown = false;
+	
+	// Consumes all events in the current graph before they are fired
+	graph.removeListener(this.eventConsumer);
+	
+	if (this.previewElement != null)
+	{
+		if (this.previewElement.parentNode != null)
+		{
+			this.previewElement.parentNode.removeChild(this.previewElement);
+		}
+		
+		this.previewElement = null;
+	}
+	
+	if (this.currentGuide != null)
+	{
+		this.currentGuide.destroy();
+		this.currentGuide = null;
+	}
+	
+	if (this.currentHighlight != null)
+	{
+		this.currentHighlight.destroy();
+		this.currentHighlight = null;
+	}
+};
+
+/**
+ * Function: dragOver
+ * 
+ * Implements autoscroll, updates the <currentPoint>, highlights any drop
+ * targets and updates the preview.
+ */
+mxDragSource.prototype.dragOver = function(graph, evt)
+{
+	var offset = mxUtils.getOffset(graph.container);
+	var origin = mxUtils.getScrollOrigin(graph.container);
+	var x = mxEvent.getClientX(evt) - offset.x + origin.x - graph.panDx;
+	var y = mxEvent.getClientY(evt) - offset.y + origin.y - graph.panDy;
+
+	if (graph.autoScroll && (this.autoscroll == null || this.autoscroll))
+	{
+		graph.scrollPointToVisible(x, y, graph.autoExtend);
+	}
+
+	// Highlights the drop target under the mouse
+	if (this.currentHighlight != null && graph.isDropEnabled())
+	{
+		this.currentDropTarget = this.getDropTarget(graph, x, y, evt);
+		var state = graph.getView().getState(this.currentDropTarget);
+		this.currentHighlight.highlight(state);
+	}
+
+	// Updates the location of the preview
+	if (this.previewElement != null)
+	{
+		if (this.previewElement.parentNode == null)
+		{
+			graph.container.appendChild(this.previewElement);
+			
+			this.previewElement.style.zIndex = '3';
+			this.previewElement.style.position = 'absolute';
+		}
+		
+		var gridEnabled = this.isGridEnabled() && graph.isGridEnabledEvent(evt);
+		var hideGuide = true;
+
+		// Grid and guides
+		if (this.currentGuide != null && this.currentGuide.isEnabledForEvent(evt))
+		{
+			// LATER: HTML preview appears smaller than SVG preview
+			var w = parseInt(this.previewElement.style.width);
+			var h = parseInt(this.previewElement.style.height);
+			var bounds = new mxRectangle(0, 0, w, h);
+			var delta = new mxPoint(x, y);
+			delta = this.currentGuide.move(bounds, delta, gridEnabled);
+			hideGuide = false;
+			x = delta.x;
+			y = delta.y;
+		}
+		else if (gridEnabled)
+		{
+			var scale = graph.view.scale;
+			var tr = graph.view.translate;
+			var off = graph.gridSize / 2;
+			x = (graph.snap(x / scale - tr.x - off) + tr.x) * scale;
+			y = (graph.snap(y / scale - tr.y - off) + tr.y) * scale;
+		}
+		
+		if (this.currentGuide != null && hideGuide)
+		{
+			this.currentGuide.hide();
+		}
+		
+		if (this.previewOffset != null)
+		{
+			x += this.previewOffset.x;
+			y += this.previewOffset.y;
+		}
+
+		this.previewElement.style.left = Math.round(x) + 'px';
+		this.previewElement.style.top = Math.round(y) + 'px';
+		this.previewElement.style.visibility = 'visible';
+	}
+	
+	this.currentPoint = new mxPoint(x, y);
+};
+
+/**
+ * Function: drop
+ * 
+ * Returns the drop target for the given graph and coordinates. This
+ * implementation uses <mxGraph.getCellAt>.
+ */
+mxDragSource.prototype.drop = function(graph, evt, dropTarget, x, y)
+{
+	this.dropHandler(graph, evt, dropTarget, x, y);
+	
+	// Had to move this to after the insert because it will
+	// affect the scrollbars of the window in IE to try and
+	// make the complete container visible.
+	// LATER: Should be made optional.
+	if (graph.container.style.visibility != 'hidden')
+	{
+		graph.container.focus();
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/util/mxEffects.js b/airavata-kubernetes/workflow-composer/src/js/util/mxEffects.js
new file mode 100644
index 0000000..f7a1891
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/util/mxEffects.js
@@ -0,0 +1,211 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+var mxEffects =
+{
+
+	/**
+	 * Class: mxEffects
+	 * 
+	 * Provides animation effects.
+	 */
+
+	/**
+	 * Function: animateChanges
+	 * 
+	 * Asynchronous animated move operation. See also: <mxMorphing>.
+	 * 
+	 * Example:
+	 * 
+	 * (code)
+	 * graph.model.addListener(mxEvent.CHANGE, function(sender, evt)
+	 * {
+	 *   var changes = evt.getProperty('edit').changes;
+	 * 
+	 *   if (changes.length < 10)
+	 *   {
+	 *     mxEffects.animateChanges(graph, changes);
+	 *   }
+	 * });
+	 * (end)
+	 * 
+	 * Parameters:
+	 * 
+	 * graph - <mxGraph> that received the changes.
+	 * changes - Array of changes to be animated.
+	 * done - Optional function argument that is invoked after the
+	 * last step of the animation.
+	 */
+	animateChanges: function(graph, changes, done)
+	{
+		var maxStep = 10;
+		var step = 0;
+
+		var animate = function() 
+		{
+			var isRequired = false;
+			
+			for (var i = 0; i < changes.length; i++)
+			{
+				var change = changes[i];
+				
+				if (change instanceof mxGeometryChange ||
+					change instanceof mxTerminalChange ||
+					change instanceof mxValueChange ||
+					change instanceof mxChildChange ||
+					change instanceof mxStyleChange)
+				{
+					var state = graph.getView().getState(change.cell || change.child, false);
+					
+					if (state != null)
+					{
+						isRequired = true;
+					
+						if (change.constructor != mxGeometryChange || graph.model.isEdge(change.cell))
+						{
+							mxUtils.setOpacity(state.shape.node, 100 * step / maxStep);
+						}
+						else
+						{
+							var scale = graph.getView().scale;					
+
+							var dx = (change.geometry.x - change.previous.x) * scale;
+							var dy = (change.geometry.y - change.previous.y) * scale;
+							
+							var sx = (change.geometry.width - change.previous.width) * scale;
+							var sy = (change.geometry.height - change.previous.height) * scale;
+							
+							if (step == 0)
+							{
+								state.x -= dx;
+								state.y -= dy;
+								state.width -= sx;
+								state.height -= sy;
+							}
+							else
+							{
+								state.x += dx / maxStep;
+								state.y += dy / maxStep;
+								state.width += sx / maxStep;
+								state.height += sy / maxStep;
+							}
+							
+							graph.cellRenderer.redraw(state);
+							
+							// Fades all connected edges and children
+							mxEffects.cascadeOpacity(graph, change.cell, 100 * step / maxStep);
+						}
+					}
+				}
+			}
+
+			if (step < maxStep && isRequired)
+			{
+				step++;
+				window.setTimeout(animate, delay);
+			}
+			else if (done != null)
+			{
+				done();
+			}
+		};
+		
+		var delay = 30;
+		animate();
+	},
+    
+	/**
+	 * Function: cascadeOpacity
+	 * 
+	 * Sets the opacity on the given cell and its descendants.
+	 * 
+	 * Parameters:
+	 * 
+	 * graph - <mxGraph> that contains the cells.
+	 * cell - <mxCell> to set the opacity for.
+	 * opacity - New value for the opacity in %.
+	 */
+    cascadeOpacity: function(graph, cell, opacity)
+	{
+		// Fades all children
+		var childCount = graph.model.getChildCount(cell);
+		
+		for (var i=0; i<childCount; i++)
+		{
+			var child = graph.model.getChildAt(cell, i);
+			var childState = graph.getView().getState(child);
+			
+			if (childState != null)
+			{
+				mxUtils.setOpacity(childState.shape.node, opacity);
+				mxEffects.cascadeOpacity(graph, child, opacity);
+			}
+		}
+		
+		// Fades all connected edges
+		var edges = graph.model.getEdges(cell);
+		
+		if (edges != null)
+		{
+			for (var i=0; i<edges.length; i++)
+			{
+				var edgeState = graph.getView().getState(edges[i]);
+				
+				if (edgeState != null)
+				{
+					mxUtils.setOpacity(edgeState.shape.node, opacity);
+				}
+			}
+		}
+	},
+
+	/**
+	 * Function: fadeOut
+	 * 
+	 * Asynchronous fade-out operation.
+	 */
+	fadeOut: function(node, from, remove, step, delay, isEnabled)
+	{
+		step = step || 40;
+		delay = delay || 30;
+		
+		var opacity = from || 100;
+		
+		mxUtils.setOpacity(node, opacity);
+		
+		if (isEnabled || isEnabled == null)
+		{
+			var f = function()
+			{
+			    opacity = Math.max(opacity-step, 0);
+				mxUtils.setOpacity(node, opacity);
+				
+				if (opacity > 0)
+				{
+					window.setTimeout(f, delay);
+				}
+				else
+				{
+					node.style.visibility = 'hidden';
+					
+					if (remove && node.parentNode)
+					{
+						node.parentNode.removeChild(node);
+					}
+				}
+			};
+			window.setTimeout(f, delay);
+		}
+		else
+		{
+			node.style.visibility = 'hidden';
+			
+			if (remove && node.parentNode)
+			{
+				node.parentNode.removeChild(node);
+			}
+		}
+	}
+
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/util/mxEvent.js b/airavata-kubernetes/workflow-composer/src/js/util/mxEvent.js
new file mode 100644
index 0000000..ae1143f
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/util/mxEvent.js
@@ -0,0 +1,1406 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+var mxEvent =
+{
+
+	/**
+	 * Class: mxEvent
+	 * 
+	 * Cross-browser DOM event support. For internal event handling,
+	 * <mxEventSource> and the graph event dispatch loop in <mxGraph> are used.
+	 * 
+	 * Memory Leaks:
+	 * 
+	 * Use this class for adding and removing listeners to/from DOM nodes. The
+	 * <removeAllListeners> function is provided to remove all listeners that
+	 * have been added using <addListener>. The function should be invoked when
+	 * the last reference is removed in the JavaScript code, typically when the
+	 * referenced DOM node is removed from the DOM, and helps to reduce memory
+	 * leaks in IE6.
+	 * 
+	 * Variable: objects
+	 * 
+	 * Contains all objects where any listener was added using <addListener>.
+	 * This is used to reduce memory leaks in IE, see <mxClient.dispose>.
+	 */
+	objects: [],
+
+	 /**
+	  * Function: addListener
+	  * 
+	  * Binds the function to the specified event on the given element. Use
+	  * <mxUtils.bind> in order to bind the "this" keyword inside the function
+	  * to a given execution scope.
+	  */
+	addListener: function()
+	{
+		var updateListenerList = function(element, eventName, funct)
+		{
+			if (element.mxListenerList == null)
+			{
+				element.mxListenerList = [];
+				mxEvent.objects.push(element);
+			}
+			
+			var entry = {name: eventName, f: funct};
+			element.mxListenerList.push(entry);
+		};
+		
+		if (window.addEventListener)
+		{
+			return function(element, eventName, funct)
+			{
+				element.addEventListener(eventName, funct, false);
+				updateListenerList(element, eventName, funct);
+			};
+		}
+		else
+		{
+			return function(element, eventName, funct)
+			{
+				element.attachEvent('on' + eventName, funct);
+				updateListenerList(element, eventName, funct);				
+			};
+		}
+	}(),
+
+	/**
+	 * Function: removeListener
+	 *
+	 * Removes the specified listener from the given element.
+	 */
+	removeListener: function()
+	{
+		var updateListener = function(element, eventName, funct)
+		{
+			if (element.mxListenerList != null)
+			{
+				var listenerCount = element.mxListenerList.length;
+				
+				for (var i = 0; i < listenerCount; i++)
+				{
+					var entry = element.mxListenerList[i];
+					
+					if (entry.f == funct)
+					{
+						element.mxListenerList.splice(i, 1);
+						break;
+					}
+				}
+				
+				if (element.mxListenerList.length == 0)
+				{
+					element.mxListenerList = null;
+					
+					var idx = mxUtils.indexOf(mxEvent.objects, element);
+					
+					if (idx >= 0)
+					{
+						mxEvent.objects.splice(idx, 1);
+					}
+				}
+			}
+		};
+		
+		if (window.removeEventListener)
+		{
+			return function(element, eventName, funct)
+			{
+				element.removeEventListener(eventName, funct, false);
+				updateListener(element, eventName, funct);
+			};
+		}
+		else
+		{
+			return function(element, eventName, funct)
+			{
+				element.detachEvent('on' + eventName, funct);
+				updateListener(element, eventName, funct);
+			};
+		}
+	}(),
+
+	/**
+	 * Function: removeAllListeners
+	 * 
+	 * Removes all listeners from the given element.
+	 */
+	removeAllListeners: function(element)
+	{
+		var list = element.mxListenerList;
+
+		if (list != null)
+		{
+			while (list.length > 0)
+			{
+				var entry = list[0];
+				mxEvent.removeListener(element, entry.name, entry.f);
+			}
+		}
+	},
+	
+	/**
+	 * Function: addGestureListeners
+	 * 
+	 * Adds the given listeners for touch, mouse and/or pointer events. If
+	 * <mxClient.IS_POINTER> is true then pointer events will be registered,
+	 * else the respective mouse events will be registered. If <mxClient.IS_POINTER>
+	 * is false and <mxClient.IS_TOUCH> is true then the respective touch events
+	 * will be registered as well as the mouse events.
+	 */
+	addGestureListeners: function(node, startListener, moveListener, endListener)
+	{
+		if (startListener != null)
+		{
+			mxEvent.addListener(node, (mxClient.IS_POINTER) ? 'pointerdown' : 'mousedown', startListener);
+		}
+		
+		if (moveListener != null)
+		{
+			mxEvent.addListener(node, (mxClient.IS_POINTER) ? 'pointermove' : 'mousemove', moveListener);
+		}
+		
+		if (endListener != null)
+		{
+			mxEvent.addListener(node, (mxClient.IS_POINTER) ? 'pointerup' : 'mouseup', endListener);
+		}
+		
+		if (!mxClient.IS_POINTER && mxClient.IS_TOUCH)
+		{
+			if (startListener != null)
+			{
+				mxEvent.addListener(node, 'touchstart', startListener);
+			}
+			
+			if (moveListener != null)
+			{
+				mxEvent.addListener(node, 'touchmove', moveListener);
+			}
+			
+			if (endListener != null)
+			{
+				mxEvent.addListener(node, 'touchend', endListener);
+			}
+		}
+	},
+	
+	/**
+	 * Function: removeGestureListeners
+	 * 
+	 * Removes the given listeners from mousedown, mousemove, mouseup and the
+	 * respective touch events if <mxClient.IS_TOUCH> is true.
+	 */
+	removeGestureListeners: function(node, startListener, moveListener, endListener)
+	{
+		if (startListener != null)
+		{
+			mxEvent.removeListener(node, (mxClient.IS_POINTER) ? 'pointerdown' : 'mousedown', startListener);
+		}
+		
+		if (moveListener != null)
+		{
+			mxEvent.removeListener(node, (mxClient.IS_POINTER) ? 'pointermove' : 'mousemove', moveListener);
+		}
+		
+		if (endListener != null)
+		{
+			mxEvent.removeListener(node, (mxClient.IS_POINTER) ? 'pointerup' : 'mouseup', endListener);
+		}
+		
+		if (!mxClient.IS_POINTER && mxClient.IS_TOUCH)
+		{
+			if (startListener != null)
+			{
+				mxEvent.removeListener(node, 'touchstart', startListener);
+			}
+			
+			if (moveListener != null)
+			{
+				mxEvent.removeListener(node, 'touchmove', moveListener);
+			}
+			
+			if (endListener != null)
+			{
+				mxEvent.removeListener(node, 'touchend', endListener);
+			}
+		}
+	},
+	
+	/**
+	 * Function: redirectMouseEvents
+	 *
+	 * Redirects the mouse events from the given DOM node to the graph dispatch
+	 * loop using the event and given state as event arguments. State can
+	 * either be an instance of <mxCellState> or a function that returns an
+	 * <mxCellState>. The down, move, up and dblClick arguments are optional
+	 * functions that take the trigger event as arguments and replace the
+	 * default behaviour.
+	 */
+	redirectMouseEvents: function(node, graph, state, down, move, up, dblClick)
+	{
+		var getState = function(evt)
+		{
+			return (typeof(state) == 'function') ? state(evt) : state;
+		};
+		
+		mxEvent.addGestureListeners(node, function (evt)
+		{
+			if (down != null)
+			{
+				down(evt);
+			}
+			else if (!mxEvent.isConsumed(evt))
+			{
+				graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt, getState(evt)));
+			}
+		},
+		function (evt)
+		{
+			if (move != null)
+			{
+				move(evt);
+			}
+			else if (!mxEvent.isConsumed(evt))
+			{
+				graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, getState(evt)));
+			}
+		},
+		function (evt)
+		{
+			if (up != null)
+			{
+				up(evt);
+			}
+			else if (!mxEvent.isConsumed(evt))
+			{
+				graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt, getState(evt)));
+			}
+		});
+
+		mxEvent.addListener(node, 'dblclick', function (evt)
+		{
+			if (dblClick != null)
+			{
+				dblClick(evt);
+			}
+			else if (!mxEvent.isConsumed(evt))
+			{
+				var tmp = getState(evt);
+				graph.dblClick(evt, (tmp != null) ? tmp.cell : null);
+			}
+		});
+	},
+
+	/**
+	 * Function: release
+	 * 
+	 * Removes the known listeners from the given DOM node and its descendants.
+	 * 
+	 * Parameters:
+	 * 
+	 * element - DOM node to remove the listeners from.
+	 */
+	release: function(element)
+	{
+		if (element != null)
+		{
+			mxEvent.removeAllListeners(element);
+			
+			var children = element.childNodes;
+			
+			if (children != null)
+			{
+		        var childCount = children.length;
+		        
+		        for (var i = 0; i < childCount; i += 1)
+		        {
+		        	mxEvent.release(children[i]);
+		        }
+		    }
+		}
+	},
+
+	/**
+	 * Function: addMouseWheelListener
+	 * 
+	 * Installs the given function as a handler for mouse wheel events. The
+	 * function has two arguments: the mouse event and a boolean that specifies
+	 * if the wheel was moved up or down.
+	 * 
+	 * This has been tested with IE 6 and 7, Firefox (all versions), Opera and
+	 * Safari. It does currently not work on Safari for Mac.
+	 * 
+	 * Example:
+	 * 
+	 * (code)
+	 * mxEvent.addMouseWheelListener(function (evt, up)
+	 * {
+	 *   mxLog.show();
+	 *   mxLog.debug('mouseWheel: up='+up);
+	 * });
+	 *(end)
+	 * 
+	 * Parameters:
+	 * 
+	 * funct - Handler function that takes the event argument and a boolean up
+	 * argument for the mousewheel direction.
+	 */
+	addMouseWheelListener: function(funct)
+	{
+		if (funct != null)
+		{
+			var wheelHandler = function(evt)
+			{
+				// IE does not give an event object but the
+				// global event object is the mousewheel event
+				// at this point in time.
+				if (evt == null)
+				{
+					evt = window.event;
+				}
+			
+				var delta = 0;
+				
+				if (mxClient.IS_FF)
+				{
+					delta = -evt.detail / 2;
+				}
+				else
+				{
+					delta = evt.wheelDelta / 120;
+				}
+				
+				// Handles the event using the given function
+				if (delta != 0)
+				{
+					funct(evt, delta > 0);
+				}
+			};
+	
+			// Webkit has NS event API, but IE event name and details 
+			if (mxClient.IS_NS && document.documentMode == null)
+			{
+				var eventName = (mxClient.IS_SF || 	mxClient.IS_GC) ? 'mousewheel' : 'DOMMouseScroll';
+				mxEvent.addListener(window, eventName, wheelHandler);
+			}
+			else
+			{
+				mxEvent.addListener(document, 'mousewheel', wheelHandler);
+			}
+		}
+	},
+	
+	/**
+	 * Function: disableContextMenu
+	 *
+	 * Disables the context menu for the given element.
+	 */
+	disableContextMenu: function()
+	{
+		if (mxClient.IS_IE && (typeof(document.documentMode) === 'undefined' || document.documentMode < 9))
+		{
+			return function(element)
+			{
+				mxEvent.addListener(element, 'contextmenu', function()
+				{
+					return false;
+				});
+			};
+		}
+		else
+		{
+			return function(element)
+			{
+				element.setAttribute('oncontextmenu', 'return false;');
+			};		
+		}
+	}(),
+	
+	/**
+	 * Function: getSource
+	 * 
+	 * Returns the event's target or srcElement depending on the browser.
+	 */
+	getSource: function(evt)
+	{
+		return (evt.srcElement != null) ? evt.srcElement : evt.target;
+	},
+
+	/**
+	 * Function: isConsumed
+	 * 
+	 * Returns true if the event has been consumed using <consume>.
+	 */
+	isConsumed: function(evt)
+	{
+		return evt.isConsumed != null && evt.isConsumed;
+	},
+
+	/**
+	 * Function: isTouchEvent
+	 * 
+	 * Returns true if the event was generated using a touch device (not a pen or mouse).
+	 */
+	isTouchEvent: function(evt)
+	{
+		return (evt.pointerType != null) ? (evt.pointerType == 'touch' || evt.pointerType ===
+			evt.MSPOINTER_TYPE_TOUCH) : ((evt.mozInputSource != null) ?
+					evt.mozInputSource == 5 : evt.type.indexOf('touch') == 0);
+	},
+
+	/**
+	 * Function: isPenEvent
+	 * 
+	 * Returns true if the event was generated using a pen (not a touch device or mouse).
+	 */
+	isPenEvent: function(evt)
+	{
+		return (evt.pointerType != null) ? (evt.pointerType == 'pen' || evt.pointerType ===
+			evt.MSPOINTER_TYPE_PEN) : ((evt.mozInputSource != null) ?
+					evt.mozInputSource == 2 : evt.type.indexOf('pen') == 0);
+	},
+
+	/**
+	 * Function: isMultiTouchEvent
+	 * 
+	 * Returns true if the event was generated using a touch device (not a pen or mouse).
+	 */
+	isMultiTouchEvent: function(evt)
+	{
+		return (evt.type != null && evt.type.indexOf('touch') == 0 && evt.touches != null && evt.touches.length > 1);
+	},
+
+	/**
+	 * Function: isMouseEvent
+	 * 
+	 * Returns true if the event was generated using a mouse (not a pen or touch device).
+	 */
+	isMouseEvent: function(evt)
+	{
+		return (evt.pointerType != null) ? (evt.pointerType == 'mouse' || evt.pointerType ===
+			evt.MSPOINTER_TYPE_MOUSE) : ((evt.mozInputSource != null) ?
+				evt.mozInputSource == 1 : evt.type.indexOf('mouse') == 0);
+	},
+	
+	/**
+	 * Function: isLeftMouseButton
+	 * 
+	 * Returns true if the left mouse button is pressed for the given event.
+	 * To check if a button is pressed during a mouseMove you should use the
+	 * <mxGraph.isMouseDown> property. Note that this returns true in Firefox
+	 * for control+left-click on the Mac.
+	 */
+	isLeftMouseButton: function(evt)
+	{
+		// Special case for mousemove and mousedown we check the buttons
+		// if it exists because which is 0 even if no button is pressed
+		if ('buttons' in evt && (evt.type == 'mousedown' || evt.type == 'mousemove'))
+		{
+			return evt.buttons == 1;
+		}
+		else if ('which' in evt)
+		{
+	        return evt.which === 1;
+	    }
+		else
+		{
+	        return evt.button === 1;
+	    }
+	},
+	
+	/**
+	 * Function: isMiddleMouseButton
+	 * 
+	 * Returns true if the middle mouse button is pressed for the given event.
+	 * To check if a button is pressed during a mouseMove you should use the
+	 * <mxGraph.isMouseDown> property.
+	 */
+	isMiddleMouseButton: function(evt)
+	{
+		if ('which' in evt)
+		{
+	        return evt.which === 2;
+	    }
+		else
+		{
+	        return evt.button === 4;
+	    }
+	},
+	
+	/**
+	 * Function: isRightMouseButton
+	 * 
+	 * Returns true if the right mouse button was pressed. Note that this
+	 * button might not be available on some systems. For handling a popup
+	 * trigger <isPopupTrigger> should be used.
+	 */
+	isRightMouseButton: function(evt)
+	{
+		if ('which' in evt)
+		{
+	        return evt.which === 3;
+	    }
+		else
+		{
+	        return evt.button === 2;
+	    }
+	},
+
+	/**
+	 * Function: isPopupTrigger
+	 * 
+	 * Returns true if the event is a popup trigger. This implementation
+	 * returns true if the right button or the left button and control was
+	 * pressed on a Mac.
+	 */
+	isPopupTrigger: function(evt)
+	{
+		return mxEvent.isRightMouseButton(evt) || (mxClient.IS_MAC && mxEvent.isControlDown(evt) &&
+			!mxEvent.isShiftDown(evt) && !mxEvent.isMetaDown(evt) && !mxEvent.isAltDown(evt));
+	},
+
+	/**
+	 * Function: isShiftDown
+	 * 
+	 * Returns true if the shift key is pressed for the given event.
+	 */
+	isShiftDown: function(evt)
+	{
+		return (evt != null) ? evt.shiftKey : false;
+	},
+
+	/**
+	 * Function: isAltDown
+	 * 
+	 * Returns true if the alt key is pressed for the given event.
+	 */
+	isAltDown: function(evt)
+	{
+		return (evt != null) ? evt.altKey : false;
+	},
+
+	/**
+	 * Function: isControlDown
+	 * 
+	 * Returns true if the control key is pressed for the given event.
+	 */
+	isControlDown: function(evt)
+	{
+		return (evt != null) ? evt.ctrlKey : false;
+	},
+
+	/**
+	 * Function: isMetaDown
+	 * 
+	 * Returns true if the meta key is pressed for the given event.
+	 */
+	isMetaDown: function(evt)
+	{
+		return (evt != null) ? evt.metaKey : false;
+	},
+
+	/**
+	 * Function: getMainEvent
+	 * 
+	 * Returns the touch or mouse event that contains the mouse coordinates.
+	 */
+	getMainEvent: function(e)
+	{
+		if ((e.type == 'touchstart' || e.type == 'touchmove') && e.touches != null && e.touches[0] != null)
+		{
+			e = e.touches[0];
+		}
+		else if (e.type == 'touchend' && e.changedTouches != null && e.changedTouches[0] != null)
+		{
+			e = e.changedTouches[0];
+		}
+		
+		return e;
+	},
+	
+	/**
+	 * Function: getClientX
+	 * 
+	 * Returns true if the meta key is pressed for the given event.
+	 */
+	getClientX: function(e)
+	{
+		return mxEvent.getMainEvent(e).clientX;
+	},
+
+	/**
+	 * Function: getClientY
+	 * 
+	 * Returns true if the meta key is pressed for the given event.
+	 */
+	getClientY: function(e)
+	{
+		return mxEvent.getMainEvent(e).clientY;
+	},
+
+	/**
+	 * Function: consume
+	 * 
+	 * Consumes the given event.
+	 * 
+	 * Parameters:
+	 * 
+	 * evt - Native event to be consumed.
+	 * preventDefault - Optional boolean to prevent the default for the event.
+	 * Default is true.
+	 * stopPropagation - Option boolean to stop event propagation. Default is
+	 * true.
+	 */
+	consume: function(evt, preventDefault, stopPropagation)
+	{
+		preventDefault = (preventDefault != null) ? preventDefault : true;
+		stopPropagation = (stopPropagation != null) ? stopPropagation : true;
+		
+		if (preventDefault)
+		{
+			if (evt.preventDefault)
+			{
+				if (stopPropagation)
+				{
+					evt.stopPropagation();
+				}
+				
+				evt.preventDefault();
+			}
+			else if (stopPropagation)
+			{
+				evt.cancelBubble = true;
+			}
+		}
+
+		// Opera
+		evt.isConsumed = true;
+
+		// Other browsers
+		if (!evt.preventDefault)
+		{
+			evt.returnValue = false;
+		}
+	},
+	
+	//
+	// Special handles in mouse events
+	//
+	
+	/**
+	 * Variable: LABEL_HANDLE
+	 * 
+	 * Index for the label handle in an mxMouseEvent. This should be a negative
+	 * value that does not interfere with any possible handle indices. Default
+	 * is -1.
+	 */
+	LABEL_HANDLE: -1,
+	
+	/**
+	 * Variable: ROTATION_HANDLE
+	 * 
+	 * Index for the rotation handle in an mxMouseEvent. This should be a
+	 * negative value that does not interfere with any possible handle indices.
+	 * Default is -2.
+	 */
+	ROTATION_HANDLE: -2,
+	
+	/**
+	 * Variable: CUSTOM_HANDLE
+	 * 
+	 * Start index for the custom handles in an mxMouseEvent. This should be a
+	 * negative value and is the start index which is decremented for each
+	 * custom handle. Default is -100.
+	 */
+	CUSTOM_HANDLE: -100,
+	
+	/**
+	 * Variable: VIRTUAL_HANDLE
+	 * 
+	 * Start index for the virtual handles in an mxMouseEvent. This should be a
+	 * negative value and is the start index which is decremented for each
+	 * virtual handle. Default is -100000. This assumes that there are no more
+	 * than VIRTUAL_HANDLE - CUSTOM_HANDLE custom handles.
+	 * 
+	 */
+	VIRTUAL_HANDLE: -100000,
+	
+	//
+	// Event names
+	//
+	
+	/**
+	 * Variable: MOUSE_DOWN
+	 *
+	 * Specifies the event name for mouseDown.
+	 */
+	MOUSE_DOWN: 'mouseDown',
+	
+	/**
+	 * Variable: MOUSE_MOVE
+	 *
+	 * Specifies the event name for mouseMove. 
+	 */
+	MOUSE_MOVE: 'mouseMove',
+	
+	/**
+	 * Variable: MOUSE_UP
+	 *
+	 * Specifies the event name for mouseUp. 
+	 */
+	MOUSE_UP: 'mouseUp',
+
+	/**
+	 * Variable: ACTIVATE
+	 *
+	 * Specifies the event name for activate.
+	 */
+	ACTIVATE: 'activate',
+
+	/**
+	 * Variable: RESIZE_START
+	 *
+	 * Specifies the event name for resizeStart.
+	 */
+	RESIZE_START: 'resizeStart',
+
+	/**
+	 * Variable: RESIZE
+	 *
+	 * Specifies the event name for resize.
+	 */
+	RESIZE: 'resize',
+
+	/**
+	 * Variable: RESIZE_END
+	 *
+	 * Specifies the event name for resizeEnd.
+	 */
+	RESIZE_END: 'resizeEnd',
+
+	/**
+	 * Variable: MOVE_START
+	 *
+	 * Specifies the event name for moveStart.
+	 */
+	MOVE_START: 'moveStart',
+
+	/**
+	 * Variable: MOVE
+	 *
+	 * Specifies the event name for move.
+	 */
+	MOVE: 'move',
+
+	/**
+	 * Variable: MOVE_END
+	 *
+	 * Specifies the event name for moveEnd.
+	 */
+	MOVE_END: 'moveEnd',
+
+	/**
+	 * Variable: PAN_START
+	 *
+	 * Specifies the event name for panStart.
+	 */
+	PAN_START: 'panStart',
+
+	/**
+	 * Variable: PAN
+	 *
+	 * Specifies the event name for pan.
+	 */
+	PAN: 'pan',
+
+	/**
+	 * Variable: PAN_END
+	 *
+	 * Specifies the event name for panEnd.
+	 */
+	PAN_END: 'panEnd',
+
+	/**
+	 * Variable: MINIMIZE
+	 *
+	 * Specifies the event name for minimize.
+	 */
+	MINIMIZE: 'minimize',
+
+	/**
+	 * Variable: NORMALIZE
+	 *
+	 * Specifies the event name for normalize.
+	 */
+	NORMALIZE: 'normalize',
+
+	/**
+	 * Variable: MAXIMIZE
+	 *
+	 * Specifies the event name for maximize.
+	 */
+	MAXIMIZE: 'maximize',
+
+	/**
+	 * Variable: HIDE
+	 *
+	 * Specifies the event name for hide.
+	 */
+	HIDE: 'hide',
+
+	/**
+	 * Variable: SHOW
+	 *
+	 * Specifies the event name for show.
+	 */
+	SHOW: 'show',
+
+	/**
+	 * Variable: CLOSE
+	 *
+	 * Specifies the event name for close.
+	 */
+	CLOSE: 'close',
+
+	/**
+	 * Variable: DESTROY
+	 *
+	 * Specifies the event name for destroy.
+	 */
+	DESTROY: 'destroy',
+
+	/**
+	 * Variable: REFRESH
+	 *
+	 * Specifies the event name for refresh.
+	 */
+	REFRESH: 'refresh',
+
+	/**
+	 * Variable: SIZE
+	 *
+	 * Specifies the event name for size.
+	 */
+	SIZE: 'size',
+	
+	/**
+	 * Variable: SELECT
+	 *
+	 * Specifies the event name for select.
+	 */
+	SELECT: 'select',
+
+	/**
+	 * Variable: FIRED
+	 *
+	 * Specifies the event name for fired.
+	 */
+	FIRED: 'fired',
+
+	/**
+	 * Variable: FIRE_MOUSE_EVENT
+	 *
+	 * Specifies the event name for fireMouseEvent.
+	 */
+	FIRE_MOUSE_EVENT: 'fireMouseEvent',
+
+	/**
+	 * Variable: GESTURE
+	 *
+	 * Specifies the event name for gesture.
+	 */
+	GESTURE: 'gesture',
+
+	/**
+	 * Variable: TAP_AND_HOLD
+	 *
+	 * Specifies the event name for tapAndHold.
+	 */
+	TAP_AND_HOLD: 'tapAndHold',
+
+	/**
+	 * Variable: GET
+	 *
+	 * Specifies the event name for get.
+	 */
+	GET: 'get',
+
+	/**
+	 * Variable: RECEIVE
+	 *
+	 * Specifies the event name for receive.
+	 */
+	RECEIVE: 'receive',
+
+	/**
+	 * Variable: CONNECT
+	 *
+	 * Specifies the event name for connect.
+	 */
+	CONNECT: 'connect',
+
+	/**
+	 * Variable: DISCONNECT
+	 *
+	 * Specifies the event name for disconnect.
+	 */
+	DISCONNECT: 'disconnect',
+
+	/**
+	 * Variable: SUSPEND
+	 *
+	 * Specifies the event name for suspend.
+	 */
+	SUSPEND: 'suspend',
+
+	/**
+	 * Variable: RESUME
+	 *
+	 * Specifies the event name for suspend.
+	 */
+	RESUME: 'resume',
+
+	/**
+	 * Variable: MARK
+	 *
+	 * Specifies the event name for mark.
+	 */
+	MARK: 'mark',
+
+	/**
+	 * Variable: ROOT
+	 *
+	 * Specifies the event name for root.
+	 */
+	ROOT: 'root',
+
+	/**
+	 * Variable: POST
+	 *
+	 * Specifies the event name for post.
+	 */
+	POST: 'post',
+
+	/**
+	 * Variable: OPEN
+	 *
+	 * Specifies the event name for open.
+	 */
+	OPEN: 'open',
+
+	/**
+	 * Variable: SAVE
+	 *
+	 * Specifies the event name for open.
+	 */
+	SAVE: 'save',
+
+	/**
+	 * Variable: BEFORE_ADD_VERTEX
+	 *
+	 * Specifies the event name for beforeAddVertex.
+	 */
+	BEFORE_ADD_VERTEX: 'beforeAddVertex',
+
+	/**
+	 * Variable: ADD_VERTEX
+	 *
+	 * Specifies the event name for addVertex.
+	 */
+	ADD_VERTEX: 'addVertex',
+
+	/**
+	 * Variable: AFTER_ADD_VERTEX
+	 *
+	 * Specifies the event name for afterAddVertex.
+	 */
+	AFTER_ADD_VERTEX: 'afterAddVertex',
+
+	/**
+	 * Variable: DONE
+	 *
+	 * Specifies the event name for done.
+	 */
+	DONE: 'done',
+
+	/**
+	 * Variable: EXECUTE
+	 *
+	 * Specifies the event name for execute.
+	 */
+	EXECUTE: 'execute',
+
+	/**
+	 * Variable: EXECUTED
+	 *
+	 * Specifies the event name for executed.
+	 */
+	EXECUTED: 'executed',
+
+	/**
+	 * Variable: BEGIN_UPDATE
+	 *
+	 * Specifies the event name for beginUpdate.
+	 */
+	BEGIN_UPDATE: 'beginUpdate',
+
+	/**
+	 * Variable: START_EDIT
+	 *
+	 * Specifies the event name for startEdit.
+	 */
+	START_EDIT: 'startEdit',
+
+	/**
+	 * Variable: END_UPDATE
+	 *
+	 * Specifies the event name for endUpdate.
+	 */
+	END_UPDATE: 'endUpdate',
+
+	/**
+	 * Variable: END_EDIT
+	 *
+	 * Specifies the event name for endEdit.
+	 */
+	END_EDIT: 'endEdit',
+
+	/**
+	 * Variable: BEFORE_UNDO
+	 *
+	 * Specifies the event name for beforeUndo.
+	 */
+	BEFORE_UNDO: 'beforeUndo',
+
+	/**
+	 * Variable: UNDO
+	 *
+	 * Specifies the event name for undo.
+	 */
+	UNDO: 'undo',
+
+	/**
+	 * Variable: REDO
+	 *
+	 * Specifies the event name for redo.
+	 */
+	REDO: 'redo',
+
+	/**
+	 * Variable: CHANGE
+	 *
+	 * Specifies the event name for change.
+	 */
+	CHANGE: 'change',
+
+	/**
+	 * Variable: NOTIFY
+	 *
+	 * Specifies the event name for notify.
+	 */
+	NOTIFY: 'notify',
+
+	/**
+	 * Variable: LAYOUT_CELLS
+	 *
+	 * Specifies the event name for layoutCells.
+	 */
+	LAYOUT_CELLS: 'layoutCells',
+
+	/**
+	 * Variable: CLICK
+	 *
+	 * Specifies the event name for click.
+	 */
+	CLICK: 'click',
+
+	/**
+	 * Variable: SCALE
+	 *
+	 * Specifies the event name for scale.
+	 */
+	SCALE: 'scale',
+
+	/**
+	 * Variable: TRANSLATE
+	 *
+	 * Specifies the event name for translate.
+	 */
+	TRANSLATE: 'translate',
+
+	/**
+	 * Variable: SCALE_AND_TRANSLATE
+	 *
+	 * Specifies the event name for scaleAndTranslate.
+	 */
+	SCALE_AND_TRANSLATE: 'scaleAndTranslate',
+
+	/**
+	 * Variable: UP
+	 *
+	 * Specifies the event name for up.
+	 */
+	UP: 'up',
+
+	/**
+	 * Variable: DOWN
+	 *
+	 * Specifies the event name for down.
+	 */
+	DOWN: 'down',
+
+	/**
+	 * Variable: ADD
+	 *
+	 * Specifies the event name for add.
+	 */
+	ADD: 'add',
+
+	/**
+	 * Variable: REMOVE
+	 *
+	 * Specifies the event name for remove.
+	 */
+	REMOVE: 'remove',
+	
+	/**
+	 * Variable: CLEAR
+	 *
+	 * Specifies the event name for clear.
+	 */
+	CLEAR: 'clear',
+
+	/**
+	 * Variable: ADD_CELLS
+	 *
+	 * Specifies the event name for addCells.
+	 */
+	ADD_CELLS: 'addCells',
+
+	/**
+	 * Variable: CELLS_ADDED
+	 *
+	 * Specifies the event name for cellsAdded.
+	 */
+	CELLS_ADDED: 'cellsAdded',
+
+	/**
+	 * Variable: MOVE_CELLS
+	 *
+	 * Specifies the event name for moveCells.
+	 */
+	MOVE_CELLS: 'moveCells',
+
+	/**
+	 * Variable: CELLS_MOVED
+	 *
+	 * Specifies the event name for cellsMoved.
+	 */
+	CELLS_MOVED: 'cellsMoved',
+
+	/**
+	 * Variable: RESIZE_CELLS
+	 *
+	 * Specifies the event name for resizeCells.
+	 */
+	RESIZE_CELLS: 'resizeCells',
+
+	/**
+	 * Variable: CELLS_RESIZED
+	 *
+	 * Specifies the event name for cellsResized.
+	 */
+	CELLS_RESIZED: 'cellsResized',
+
+	/**
+	 * Variable: TOGGLE_CELLS
+	 *
+	 * Specifies the event name for toggleCells.
+	 */
+	TOGGLE_CELLS: 'toggleCells',
+
+	/**
+	 * Variable: CELLS_TOGGLED
+	 *
+	 * Specifies the event name for cellsToggled.
+	 */
+	CELLS_TOGGLED: 'cellsToggled',
+
+	/**
+	 * Variable: ORDER_CELLS
+	 *
+	 * Specifies the event name for orderCells.
+	 */
+	ORDER_CELLS: 'orderCells',
+
+	/**
+	 * Variable: CELLS_ORDERED
+	 *
+	 * Specifies the event name for cellsOrdered.
+	 */
+	CELLS_ORDERED: 'cellsOrdered',
+
+	/**
+	 * Variable: REMOVE_CELLS
+	 *
+	 * Specifies the event name for removeCells.
+	 */
+	REMOVE_CELLS: 'removeCells',
+
+	/**
+	 * Variable: CELLS_REMOVED
+	 *
+	 * Specifies the event name for cellsRemoved.
+	 */
+	CELLS_REMOVED: 'cellsRemoved',
+
+	/**
+	 * Variable: GROUP_CELLS
+	 *
+	 * Specifies the event name for groupCells.
+	 */
+	GROUP_CELLS: 'groupCells',
+
+	/**
+	 * Variable: UNGROUP_CELLS
+	 *
+	 * Specifies the event name for ungroupCells.
+	 */
+	UNGROUP_CELLS: 'ungroupCells',
+
+	/**
+	 * Variable: REMOVE_CELLS_FROM_PARENT
+	 *
+	 * Specifies the event name for removeCellsFromParent.
+	 */
+	REMOVE_CELLS_FROM_PARENT: 'removeCellsFromParent',
+
+	/**
+	 * Variable: FOLD_CELLS
+	 *
+	 * Specifies the event name for foldCells.
+	 */
+	FOLD_CELLS: 'foldCells',
+
+	/**
+	 * Variable: CELLS_FOLDED
+	 *
+	 * Specifies the event name for cellsFolded.
+	 */
+	CELLS_FOLDED: 'cellsFolded',
+
+	/**
+	 * Variable: ALIGN_CELLS
+	 *
+	 * Specifies the event name for alignCells.
+	 */
+	ALIGN_CELLS: 'alignCells',
+
+	/**
+	 * Variable: LABEL_CHANGED
+	 *
+	 * Specifies the event name for labelChanged.
+	 */
+	LABEL_CHANGED: 'labelChanged',
+
+	/**
+	 * Variable: CONNECT_CELL
+	 *
+	 * Specifies the event name for connectCell.
+	 */
+	CONNECT_CELL: 'connectCell',
+
+	/**
+	 * Variable: CELL_CONNECTED
+	 *
+	 * Specifies the event name for cellConnected.
+	 */
+	CELL_CONNECTED: 'cellConnected',
+
+	/**
+	 * Variable: SPLIT_EDGE
+	 *
+	 * Specifies the event name for splitEdge.
+	 */
+	SPLIT_EDGE: 'splitEdge',
+
+	/**
+	 * Variable: FLIP_EDGE
+	 *
+	 * Specifies the event name for flipEdge.
+	 */
+	FLIP_EDGE: 'flipEdge',
+
+	/**
+	 * Variable: START_EDITING
+	 *
+	 * Specifies the event name for startEditing.
+	 */
+	START_EDITING: 'startEditing',
+
+	/**
+	 * Variable: EDITING_STARTED
+	 *
+	 * Specifies the event name for editingStarted.
+	 */
+	EDITING_STARTED: 'editingStarted',
+
+	/**
+	 * Variable: EDITING_STOPPED
+	 *
+	 * Specifies the event name for editingStopped.
+	 */
+	EDITING_STOPPED: 'editingStopped',
+
+	/**
+	 * Variable: ADD_OVERLAY
+	 *
+	 * Specifies the event name for addOverlay.
+	 */
+	ADD_OVERLAY: 'addOverlay',
+
+	/**
+	 * Variable: REMOVE_OVERLAY
+	 *
+	 * Specifies the event name for removeOverlay.
+	 */
+	REMOVE_OVERLAY: 'removeOverlay',
+
+	/**
+	 * Variable: UPDATE_CELL_SIZE
+	 *
+	 * Specifies the event name for updateCellSize.
+	 */
+	UPDATE_CELL_SIZE: 'updateCellSize',
+
+	/**
+	 * Variable: ESCAPE
+	 *
+	 * Specifies the event name for escape.
+	 */
+	ESCAPE: 'escape',
+
+	/**
+	 * Variable: DOUBLE_CLICK
+	 *
+	 * Specifies the event name for doubleClick.
+	 */
+	DOUBLE_CLICK: 'doubleClick',
+
+	/**
+	 * Variable: START
+	 *
+	 * Specifies the event name for start.
+	 */
+	START: 'start',
+
+	/**
+	 * Variable: RESET
+	 *
+	 * Specifies the event name for reset.
+	 */
+	RESET: 'reset'
+
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/util/mxEventObject.js b/airavata-kubernetes/workflow-composer/src/js/util/mxEventObject.js
new file mode 100644
index 0000000..fd7cb6c
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/util/mxEventObject.js
@@ -0,0 +1,111 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxEventObject
+ * 
+ * The mxEventObject is a wrapper for all properties of a single event.
+ * Additionally, it also offers functions to consume the event and check if it
+ * was consumed as follows:
+ * 
+ * (code)
+ * evt.consume();
+ * INV: evt.isConsumed() == true
+ * (end)
+ * 
+ * Constructor: mxEventObject
+ *
+ * Constructs a new event object with the specified name. An optional
+ * sequence of key, value pairs can be appended to define properties.
+ * 
+ * Example:
+ *
+ * (code)
+ * new mxEventObject("eventName", key1, val1, .., keyN, valN)
+ * (end)
+ */
+function mxEventObject(name)
+{
+	this.name = name;
+	this.properties = [];
+	
+	for (var i = 1; i < arguments.length; i += 2)
+	{
+		if (arguments[i + 1] != null)
+		{
+			this.properties[arguments[i]] = arguments[i + 1];
+		}
+	}
+};
+
+/**
+ * Variable: name
+ *
+ * Holds the name.
+ */
+mxEventObject.prototype.name = null;
+
+/**
+ * Variable: properties
+ *
+ * Holds the properties as an associative array.
+ */
+mxEventObject.prototype.properties = null;
+
+/**
+ * Variable: consumed
+ *
+ * Holds the consumed state. Default is false.
+ */
+mxEventObject.prototype.consumed = false;
+
+/**
+ * Function: getName
+ * 
+ * Returns <name>.
+ */
+mxEventObject.prototype.getName = function()
+{
+	return this.name;
+};
+
+/**
+ * Function: getProperties
+ * 
+ * Returns <properties>.
+ */
+mxEventObject.prototype.getProperties = function()
+{
+	return this.properties;
+};
+
+/**
+ * Function: getProperty
+ * 
+ * Returns the property for the given key.
+ */
+mxEventObject.prototype.getProperty = function(key)
+{
+	return this.properties[key];
+};
+
+/**
+ * Function: isConsumed
+ *
+ * Returns true if the event has been consumed.
+ */
+mxEventObject.prototype.isConsumed = function()
+{
+	return this.consumed;
+};
+
+/**
+ * Function: consume
+ *
+ * Consumes the event.
+ */
+mxEventObject.prototype.consume = function()
+{
+	this.consumed = true;
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/util/mxEventSource.js b/airavata-kubernetes/workflow-composer/src/js/util/mxEventSource.js
new file mode 100644
index 0000000..7646ff8
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/util/mxEventSource.js
@@ -0,0 +1,189 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxEventSource
+ *
+ * Base class for objects that dispatch named events. To create a subclass that
+ * inherits from mxEventSource, the following code is used.
+ *
+ * (code)
+ * function MyClass() { };
+ *
+ * MyClass.prototype = new mxEventSource();
+ * MyClass.prototype.constructor = MyClass;
+ * (end)
+ *
+ * Known Subclasses:
+ *
+ * <mxGraphModel>, <mxGraph>, <mxGraphView>, <mxEditor>, <mxCellOverlay>,
+ * <mxToolbar>, <mxWindow>
+ * 
+ * Constructor: mxEventSource
+ *
+ * Constructs a new event source.
+ */
+function mxEventSource(eventSource)
+{
+	this.setEventSource(eventSource);
+};
+
+/**
+ * Variable: eventListeners
+ *
+ * Holds the event names and associated listeners in an array. The array
+ * contains the event name followed by the respective listener for each
+ * registered listener.
+ */
+mxEventSource.prototype.eventListeners = null;
+
+/**
+ * Variable: eventsEnabled
+ *
+ * Specifies if events can be fired. Default is true.
+ */
+mxEventSource.prototype.eventsEnabled = true;
+
+/**
+ * Variable: eventSource
+ *
+ * Optional source for events. Default is null.
+ */
+mxEventSource.prototype.eventSource = null;
+
+/**
+ * Function: isEventsEnabled
+ * 
+ * Returns <eventsEnabled>.
+ */
+mxEventSource.prototype.isEventsEnabled = function()
+{
+	return this.eventsEnabled;
+};
+
+/**
+ * Function: setEventsEnabled
+ * 
+ * Sets <eventsEnabled>.
+ */
+mxEventSource.prototype.setEventsEnabled = function(value)
+{
+	this.eventsEnabled = value;
+};
+
+/**
+ * Function: getEventSource
+ * 
+ * Returns <eventSource>.
+ */
+mxEventSource.prototype.getEventSource = function()
+{
+	return this.eventSource;
+};
+
+/**
+ * Function: setEventSource
+ * 
+ * Sets <eventSource>.
+ */
+mxEventSource.prototype.setEventSource = function(value)
+{
+	this.eventSource = value;
+};
+
+/**
+ * Function: addListener
+ *
+ * Binds the specified function to the given event name. If no event name
+ * is given, then the listener is registered for all events.
+ * 
+ * The parameters of the listener are the sender and an <mxEventObject>.
+ */
+mxEventSource.prototype.addListener = function(name, funct)
+{
+	if (this.eventListeners == null)
+	{
+		this.eventListeners = [];
+	}
+	
+	this.eventListeners.push(name);
+	this.eventListeners.push(funct);
+};
+
+/**
+ * Function: removeListener
+ *
+ * Removes all occurrences of the given listener from <eventListeners>.
+ */
+mxEventSource.prototype.removeListener = function(funct)
+{
+	if (this.eventListeners != null)
+	{
+		var i = 0;
+		
+		while (i < this.eventListeners.length)
+		{
+			if (this.eventListeners[i+1] == funct)
+			{
+				this.eventListeners.splice(i, 2);
+			}
+			else
+			{
+				i += 2;
+			}
+		}
+	}
+};
+
+/**
+ * Function: fireEvent
+ *
+ * Dispatches the given event to the listeners which are registered for
+ * the event. The sender argument is optional. The current execution scope
+ * ("this") is used for the listener invocation (see <mxUtils.bind>).
+ *
+ * Example:
+ *
+ * (code)
+ * fireEvent(new mxEventObject("eventName", key1, val1, .., keyN, valN))
+ * (end)
+ * 
+ * Parameters:
+ *
+ * evt - <mxEventObject> that represents the event.
+ * sender - Optional sender to be passed to the listener. Default value is
+ * the return value of <getEventSource>.
+ */
+mxEventSource.prototype.fireEvent = function(evt, sender)
+{
+	if (this.eventListeners != null && this.isEventsEnabled())
+	{
+		if (evt == null)
+		{
+			evt = new mxEventObject();
+		}
+		
+		if (sender == null)
+		{
+			sender = this.getEventSource();
+		}
+
+		if (sender == null)
+		{
+			sender = this;
+		}
+
+		var args = [sender, evt];
+		
+		for (var i = 0; i < this.eventListeners.length; i += 2)
+		{
+			var listen = this.eventListeners[i];
+			
+			if (listen == null || listen == evt.getName())
+			{
+				this.eventListeners[i+1].apply(this, args);
+			}
+		}
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/util/mxForm.js b/airavata-kubernetes/workflow-composer/src/js/util/mxForm.js
new file mode 100644
index 0000000..afb0a6d
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/util/mxForm.js
@@ -0,0 +1,202 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxForm
+ * 
+ * A simple class for creating HTML forms.
+ * 
+ * Constructor: mxForm
+ * 
+ * Creates a HTML table using the specified classname.
+ */
+function mxForm(className)
+{
+	this.table = document.createElement('table');
+	this.table.className = className;
+	this.body = document.createElement('tbody');
+	
+	this.table.appendChild(this.body);
+};
+
+/**
+ * Variable: table
+ * 
+ * Holds the DOM node that represents the table.
+ */
+mxForm.prototype.table = null;
+
+/**
+ * Variable: body
+ * 
+ * Holds the DOM node that represents the tbody (table body). New rows
+ * can be added to this object using DOM API.
+ */
+mxForm.prototype.body = false;
+
+/**
+ * Function: getTable
+ * 
+ * Returns the table that contains this form.
+ */
+mxForm.prototype.getTable = function()
+{
+	return this.table;
+};
+
+/**
+ * Function: addButtons
+ * 
+ * Helper method to add an OK and Cancel button using the respective
+ * functions.
+ */
+mxForm.prototype.addButtons = function(okFunct, cancelFunct)
+{
+	var tr = document.createElement('tr');
+	var td = document.createElement('td');
+	tr.appendChild(td);
+	td = document.createElement('td');
+
+	// Adds the ok button
+	var button = document.createElement('button');
+	mxUtils.write(button, mxResources.get('ok') || 'OK');
+	td.appendChild(button);
+
+	mxEvent.addListener(button, 'click', function()
+	{
+		okFunct();
+	});
+	
+	// Adds the cancel button
+	button = document.createElement('button');
+	mxUtils.write(button, mxResources.get('cancel') || 'Cancel');
+	td.appendChild(button);
+	
+	mxEvent.addListener(button, 'click', function()
+	{
+		cancelFunct();
+	});
+	
+	tr.appendChild(td);
+	this.body.appendChild(tr);
+};
+
+/**
+ * Function: addText
+ * 
+ * Adds an input for the given name, type and value and returns it.
+ */
+mxForm.prototype.addText = function(name, value, type)
+{
+	var input = document.createElement('input');
+	
+	input.setAttribute('type', type || 'text');
+	input.value = value;
+	
+	return this.addField(name, input);
+};
+
+/**
+ * Function: addCheckbox
+ * 
+ * Adds a checkbox for the given name and value and returns the textfield.
+ */
+mxForm.prototype.addCheckbox = function(name, value)
+{
+	var input = document.createElement('input');
+	
+	input.setAttribute('type', 'checkbox');
+	this.addField(name, input);
+
+	// IE can only change the checked value if the input is inside the DOM
+	if (value)
+	{
+		input.checked = true;
+	}
+
+	return input;
+};
+
+/**
+ * Function: addTextarea
+ * 
+ * Adds a textarea for the given name and value and returns the textarea.
+ */
+mxForm.prototype.addTextarea = function(name, value, rows)
+{
+	var input = document.createElement('textarea');
+	
+	if (mxClient.IS_NS)
+	{
+		rows--;
+	}
+	
+	input.setAttribute('rows', rows || 2);
+	input.value = value;
+	
+	return this.addField(name, input);
+};
+
+/**
+ * Function: addCombo
+ * 
+ * Adds a combo for the given name and returns the combo.
+ */
+mxForm.prototype.addCombo = function(name, isMultiSelect, size)
+{
+	var select = document.createElement('select');
+	
+	if (size != null)
+	{
+		select.setAttribute('size', size);
+	}
+	
+	if (isMultiSelect)
+	{
+		select.setAttribute('multiple', 'true');
+	}
+	
+	return this.addField(name, select);
+};
+
+/**
+ * Function: addOption
+ * 
+ * Adds an option for the given label to the specified combo.
+ */
+mxForm.prototype.addOption = function(combo, label, value, isSelected)
+{
+	var option = document.createElement('option');
+	
+	mxUtils.writeln(option, label);
+	option.setAttribute('value', value);
+	
+	if (isSelected)
+	{
+		option.setAttribute('selected', isSelected);
+	}
+	
+	combo.appendChild(option);
+};
+
+/**
+ * Function: addField
+ * 
+ * Adds a new row with the name and the input field in two columns and
+ * returns the given input.
+ */
+mxForm.prototype.addField = function(name, input)
+{
+	var tr = document.createElement('tr');
+	var td = document.createElement('td');
+	mxUtils.write(td, name);
+	tr.appendChild(td);
+	
+	td = document.createElement('td');
+	td.appendChild(input);
+	tr.appendChild(td);
+	this.body.appendChild(tr);
+	
+	return input;
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/util/mxGuide.js b/airavata-kubernetes/workflow-composer/src/js/util/mxGuide.js
new file mode 100644
index 0000000..773affc
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/util/mxGuide.js
@@ -0,0 +1,401 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxGuide
+ *
+ * Implements the alignment of selection cells to other cells in the graph.
+ * 
+ * Constructor: mxGuide
+ * 
+ * Constructs a new guide object.
+ */
+function mxGuide(graph, states)
+{
+	this.graph = graph;
+	this.setStates(states);
+};
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph> instance.
+ */
+mxGuide.prototype.graph = null;
+
+/**
+ * Variable: states
+ * 
+ * Contains the <mxCellStates> that are used for alignment.
+ */
+mxGuide.prototype.states = null;
+
+/**
+ * Variable: horizontal
+ *
+ * Specifies if horizontal guides are enabled. Default is true.
+ */
+mxGuide.prototype.horizontal = true;
+
+/**
+ * Variable: vertical
+ *
+ * Specifies if vertical guides are enabled. Default is true.
+ */
+mxGuide.prototype.vertical = true;
+
+/**
+ * Variable: vertical
+ *
+ * Holds the <mxShape> for the horizontal guide.
+ */
+mxGuide.prototype.guideX = null;
+
+/**
+ * Variable: vertical
+ *
+ * Holds the <mxShape> for the vertical guide.
+ */
+mxGuide.prototype.guideY = null;
+
+/**
+ * Function: setStates
+ * 
+ * Sets the <mxCellStates> that should be used for alignment.
+ */
+mxGuide.prototype.setStates = function(states)
+{
+	this.states = states;
+};
+
+/**
+ * Function: isEnabledForEvent
+ * 
+ * Returns true if the guide should be enabled for the given native event. This
+ * implementation always returns true.
+ */
+mxGuide.prototype.isEnabledForEvent = function(evt)
+{
+	return true;
+};
+
+/**
+ * Function: getGuideTolerance
+ * 
+ * Returns the tolerance for the guides. Default value is gridSize / 2.
+ */
+mxGuide.prototype.getGuideTolerance = function()
+{
+	return this.graph.gridSize / 2;
+};
+
+/**
+ * Function: createGuideShape
+ * 
+ * Returns the mxShape to be used for painting the respective guide. This
+ * implementation returns a new, dashed and crisp <mxPolyline> using
+ * <mxConstants.GUIDE_COLOR> and <mxConstants.GUIDE_STROKEWIDTH> as the format.
+ * 
+ * Parameters:
+ * 
+ * horizontal - Boolean that specifies which guide should be created.
+ */
+mxGuide.prototype.createGuideShape = function(horizontal)
+{
+	var guide = new mxPolyline([], mxConstants.GUIDE_COLOR, mxConstants.GUIDE_STROKEWIDTH);
+	guide.isDashed = true;
+	
+	return guide;
+};
+
+/**
+ * Function: move
+ * 
+ * Moves the <bounds> by the given <mxPoint> and returnt the snapped point.
+ */
+mxGuide.prototype.move = function(bounds, delta, gridEnabled)
+{
+	if (this.states != null && (this.horizontal || this.vertical) && bounds != null && delta != null)
+	{
+		var trx = this.graph.getView().translate;
+		var scale = this.graph.getView().scale;
+		var dx = delta.x;
+		var dy = delta.y;
+		
+		var overrideX = false;
+		var stateX = null;
+		var valueX = null;
+		var overrideY = false;
+		var stateY = null;
+		var valueY = null;
+		
+		var tt = this.getGuideTolerance();
+		var ttX = tt;
+		var ttY = tt;
+		
+		var b = bounds.clone();
+		b.x += delta.x;
+		b.y += delta.y;
+		
+		var left = b.x;
+		var right = b.x + b.width;
+		var center = b.getCenterX();
+		var top = b.y;
+		var bottom = b.y + b.height;
+		var middle = b.getCenterY();
+	
+		// Snaps the left, center and right to the given x-coordinate
+		function snapX(x, state)
+		{
+			x += this.graph.panDx;
+			var override = false;
+			
+			if (Math.abs(x - center) < ttX)
+			{
+				dx = x - bounds.getCenterX();
+				ttX = Math.abs(x - center);
+				override = true;
+			}
+			else if (Math.abs(x - left) < ttX)
+			{
+				dx = x - bounds.x;
+				ttX = Math.abs(x - left);
+				override = true;
+			}
+			else if (Math.abs(x - right) < ttX)
+			{
+				dx = x - bounds.x - bounds.width;
+				ttX = Math.abs(x - right);
+				override = true;
+			}
+			
+			if (override)
+			{
+				stateX = state;
+				valueX = Math.round(x - this.graph.panDx);
+				
+				if (this.guideX == null)
+				{
+					this.guideX = this.createGuideShape(true);
+					
+					// Makes sure to use either VML or SVG shapes in order to implement
+					// event-transparency on the background area of the rectangle since
+					// HTML shapes do not let mouseevents through even when transparent
+					this.guideX.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
+						mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+					this.guideX.pointerEvents = false;
+					this.guideX.init(this.graph.getView().getOverlayPane());
+				}
+			}
+			
+			overrideX = overrideX || override;
+		};
+		
+		// Snaps the top, middle or bottom to the given y-coordinate
+		function snapY(y)
+		{
+			y += this.graph.panDy;
+			var override = false;
+			
+			if (Math.abs(y - middle) < ttY)
+			{
+				dy = y - bounds.getCenterY();
+				ttY = Math.abs(y -  middle);
+				override = true;
+			}
+			else if (Math.abs(y - top) < ttY)
+			{
+				dy = y - bounds.y;
+				ttY = Math.abs(y - top);
+				override = true;
+			}
+			else if (Math.abs(y - bottom) < ttY)
+			{
+				dy = y - bounds.y - bounds.height;
+				ttY = Math.abs(y - bottom);
+				override = true;
+			}
+			
+			if (override)
+			{
+				stateY = state;
+				valueY = Math.round(y - this.graph.panDy);
+				
+				if (this.guideY == null)
+				{
+					this.guideY = this.createGuideShape(false);
+					
+					// Makes sure to use either VML or SVG shapes in order to implement
+					// event-transparency on the background area of the rectangle since
+					// HTML shapes do not let mouseevents through even when transparent
+					this.guideY.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
+						mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+					this.guideY.pointerEvents = false;
+					this.guideY.init(this.graph.getView().getOverlayPane());
+				}
+			}
+			
+			overrideY = overrideY || override;
+		};
+		
+		for (var i = 0; i < this.states.length; i++)
+		{
+			var state =  this.states[i];
+			
+			if (state != null)
+			{
+				// Align x
+				if (this.horizontal)
+				{
+					snapX.call(this, state.getCenterX(), state);
+					snapX.call(this, state.x, state);
+					snapX.call(this, state.x + state.width, state);
+				}
+	
+				// Align y
+				if (this.vertical)
+				{
+					snapY.call(this, state.getCenterY(), state);
+					snapY.call(this, state.y, state);
+					snapY.call(this, state.y + state.height, state);
+				}
+			}
+		}
+
+		// Moves cells that are off-grid back to the grid on move
+		if (gridEnabled)
+		{
+			if (!overrideX)
+			{
+				var tx = bounds.x - (this.graph.snap(bounds.x /
+					scale - trx.x) + trx.x) * scale;
+				dx = this.graph.snap(dx / scale) * scale - tx;
+			}
+			
+			if (!overrideY)
+			{
+				var ty = bounds.y - (this.graph.snap(bounds.y /
+					scale - trx.y) + trx.y) * scale;
+				dy = this.graph.snap(dy / scale) * scale - ty;
+			}
+		}
+		
+		// Redraws the guides
+		var c = this.graph.container;
+		
+		if (!overrideX && this.guideX != null)
+		{
+			this.guideX.node.style.visibility = 'hidden';
+		}
+		else if (this.guideX != null)
+		{
+			if (stateX != null && bounds != null)
+			{
+				minY = Math.min(bounds.y + dy - this.graph.panDy, stateX.y);
+				maxY = Math.max(bounds.y + bounds.height + dy - this.graph.panDy, stateX.y + stateX.height);
+			}
+			
+			if (minY != null && maxY != null)
+			{
+				this.guideX.points = [new mxPoint(valueX, minY), new mxPoint(valueX, maxY)];
+			}
+			else
+			{
+				this.guideX.points = [new mxPoint(valueX, -this.graph.panDy), new mxPoint(valueX, c.scrollHeight - 3 - this.graph.panDy)];
+			}
+			
+			this.guideX.stroke = this.getGuideColor(stateX, true);
+			this.guideX.node.style.visibility = 'visible';
+			this.guideX.redraw();
+		}
+		
+		if (!overrideY && this.guideY != null)
+		{
+			this.guideY.node.style.visibility = 'hidden';
+		}
+		else if (this.guideY != null)
+		{
+			if (stateY != null && bounds != null)
+			{
+				minX = Math.min(bounds.x + dx - this.graph.panDx, stateY.x);
+				maxX = Math.max(bounds.x + bounds.width + dx - this.graph.panDx, stateY.x + stateY.width);
+			}
+			
+			if (minX != null && maxX != null)
+			{
+				this.guideY.points = [new mxPoint(minX, valueY), new mxPoint(maxX, valueY)];
+			}
+			else
+			{
+				this.guideY.points = [new mxPoint(-this.graph.panDx, valueY), new mxPoint(c.scrollWidth - 3 - this.graph.panDx, valueY)];
+			}
+			
+			this.guideY.stroke = this.getGuideColor(stateY, false);
+			this.guideY.node.style.visibility = 'visible';
+			this.guideY.redraw();
+		}
+		
+		delta = new mxPoint(dx, dy);
+	}
+	
+	return delta;
+};
+
+/**
+ * Function: hide
+ * 
+ * Hides all current guides.
+ */
+mxGuide.prototype.getGuideColor = function(state, horizontal)
+{
+	return mxConstants.GUIDE_COLOR;
+};
+
+/**
+ * Function: hide
+ * 
+ * Hides all current guides.
+ */
+mxGuide.prototype.hide = function()
+{
+	this.setVisible(false);
+};
+
+/**
+ * Function: setVisible
+ * 
+ * Shows or hides the current guides.
+ */
+mxGuide.prototype.setVisible = function(visible)
+{
+	if (this.guideX != null)
+	{
+		this.guideX.node.style.visibility = (visible) ? 'visible' : 'hidden';
+	}
+	
+	if (this.guideY != null)
+	{
+		this.guideY.node.style.visibility = (visible) ? 'visible' : 'hidden';
+	}
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys all resources that this object uses.
+ */
+mxGuide.prototype.destroy = function()
+{
+	if (this.guideX != null)
+	{
+		this.guideX.destroy();
+		this.guideX = null;
+	}
+	
+	if (this.guideY != null)
+	{
+		this.guideY.destroy();
+		this.guideY = null;
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/util/mxImage.js b/airavata-kubernetes/workflow-composer/src/js/util/mxImage.js
new file mode 100644
index 0000000..83bfc03
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/util/mxImage.js
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxImage
+ *
+ * Encapsulates the URL, width and height of an image.
+ * 
+ * Constructor: mxImage
+ * 
+ * Constructs a new image.
+ */
+function mxImage(src, width, height)
+{
+	this.src = src;
+	this.width = width;
+	this.height = height;
+};
+
+/**
+ * Variable: src
+ *
+ * String that specifies the URL of the image.
+ */
+mxImage.prototype.src = null;
+
+/**
+ * Variable: width
+ *
+ * Integer that specifies the width of the image.
+ */
+mxImage.prototype.width = null;
+
+/**
+ * Variable: height
+ *
+ * Integer that specifies the height of the image.
+ */
+mxImage.prototype.height = null;
diff --git a/airavata-kubernetes/workflow-composer/src/js/util/mxImageBundle.js b/airavata-kubernetes/workflow-composer/src/js/util/mxImageBundle.js
new file mode 100644
index 0000000..c9bb3a5
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/util/mxImageBundle.js
@@ -0,0 +1,103 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxImageBundle
+ *
+ * Maps from keys to base64 encoded images or file locations. All values must
+ * be URLs or use the format data:image/format followed by a comma and the base64
+ * encoded image data, eg. "data:image/gif,XYZ", where XYZ is the base64 encoded
+ * image data.
+ * 
+ * To add a new image bundle to an existing graph, the following code is used:
+ * 
+ * (code)
+ * var bundle = new mxImageBundle(alt);
+ * bundle.putImage('myImage', 'data:image/gif,R0lGODlhEAAQAMIGAAAAAICAAICAgP' +
+ *   '//AOzp2O3r2////////yH+FUNyZWF0ZWQgd2l0aCBUaGUgR0lNUAAh+QQBCgAHACwAAAAA' +
+ *   'EAAQAAADTXi63AowynnAMDfjPUDlnAAJhmeBFxAEloliKltWmiYCQvfVr6lBPB1ggxN1hi' +
+ *   'laSSASFQpIV5HJBDyHpqK2ejVRm2AAgZCdmCGO9CIBADs=', fallback);
+ * bundle.putImage('mySvgImage', 'data:image/svg+xml,' + encodeURIComponent(
+ *   '<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%">' +
+ *   '<linearGradient id="gradient"><stop offset="10%" stop-color="#F00"/>' +
+ *   '<stop offset="90%" stop-color="#fcc"/></linearGradient>' +
+ *   '<rect fill="url(#gradient)" width="100%" height="100%"/></svg>'), fallback);
+ * graph.addImageBundle(bundle);
+ * (end);
+ * 
+ * Alt is an optional boolean (default is false) that specifies if the value
+ * or the fallback should be returned in <getImage>.
+ * 
+ * The image can then be referenced in any cell style using image=myImage.
+ * If you are using mxOutline, you should use the same image bundles in the
+ * graph that renders the outline.
+ * 
+ * The keys for images are resolved in <mxGraph.postProcessCellStyle> and
+ * turned into a data URI if the returned value has a short data URI format
+ * as specified above.
+ * 
+ * A typical value for the fallback is a MTHML link as defined in RFC 2557.
+ * Note that this format requires a file to be dynamically created on the
+ * server-side, or the page that contains the graph to be modified to contain
+ * the resources, this can be done by adding a comment that contains the
+ * resource in the HEAD section of the page after the title tag.
+ * 
+ * This type of fallback mechanism should be used in IE6 and IE7. IE8 does
+ * support data URIs, but the maximum size is limited to 32 KB, which means
+ * all data URIs should be limited to 32 KB.
+ */
+function mxImageBundle(alt)
+{
+	this.images = [];
+	this.alt = (alt != null) ? alt : false;
+};
+
+/**
+ * Variable: images
+ * 
+ * Maps from keys to images.
+ */
+mxImageBundle.prototype.images = null;
+
+/**
+ * Variable: alt
+ * 
+ * Specifies if the fallback representation should be returned.
+ */
+mxImageBundle.prototype.images = null;
+
+/**
+ * Function: putImage
+ * 
+ * Adds the specified entry to the map. The entry is an object with a value and
+ * fallback property as specified in the arguments.
+ */
+mxImageBundle.prototype.putImage = function(key, value, fallback)
+{
+	this.images[key] = {value: value, fallback: fallback};
+};
+
+/**
+ * Function: getImage
+ * 
+ * Returns the value for the given key. This returns the value
+ * or fallback, depending on <alt>. The fallback is returned if
+ * <alt> is true, the value is returned otherwise.
+ */
+mxImageBundle.prototype.getImage = function(key)
+{
+	var result = null;
+	
+	if (key != null)
+	{
+		var img = this.images[key];
+		
+		if (img != null)
+		{
+			result = (this.alt) ? img.fallback : img.value;
+		}
+	}
+	
+	return result;
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/util/mxImageExport.js b/airavata-kubernetes/workflow-composer/src/js/util/mxImageExport.js
new file mode 100644
index 0000000..368f8c0
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/util/mxImageExport.js
@@ -0,0 +1,175 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxImageExport
+ * 
+ * Creates a new image export instance to be used with an export canvas. Here
+ * is an example that uses this class to create an image via a backend using
+ * <mxXmlExportCanvas>.
+ * 
+ * (code)
+ * var xmlDoc = mxUtils.createXmlDocument();
+ * var root = xmlDoc.createElement('output');
+ * xmlDoc.appendChild(root);
+ * 
+ * var xmlCanvas = new mxXmlCanvas2D(root);
+ * var imgExport = new mxImageExport();
+ * imgExport.drawState(graph.getView().getState(graph.model.root), xmlCanvas);
+ * 
+ * var bounds = graph.getGraphBounds();
+ * var w = Math.ceil(bounds.x + bounds.width);
+ * var h = Math.ceil(bounds.y + bounds.height);
+ * 
+ * var xml = mxUtils.getXml(root);
+ * new mxXmlRequest('export', 'format=png&w=' + w +
+ * 		'&h=' + h + '&bg=#F9F7ED&xml=' + encodeURIComponent(xml))
+ * 		.simulate(document, '_blank');
+ * (end)
+ * 
+ * Constructor: mxImageExport
+ * 
+ * Constructs a new image export.
+ */
+function mxImageExport() { };
+
+/**
+ * Variable: includeOverlays
+ * 
+ * Specifies if overlays should be included in the export. Default is false.
+ */
+mxImageExport.prototype.includeOverlays = false;
+
+/**
+ * Function: drawState
+ * 
+ * Draws the given state and all its descendants to the given canvas.
+ */
+mxImageExport.prototype.drawState = function(state, canvas)
+{
+	if (state != null)
+	{
+		this.visitStatesRecursive(state, canvas, mxUtils.bind(this, function()
+		{
+			this.drawCellState.apply(this, arguments);
+		}));
+				
+		// Paints the overlays
+		if (this.includeOverlays)
+		{
+			this.visitStatesRecursive(state, canvas, mxUtils.bind(this, function()
+			{
+				this.drawOverlays.apply(this, arguments);
+			}));
+		}
+	}
+};
+
+/**
+ * Function: drawState
+ * 
+ * Draws the given state and all its descendants to the given canvas.
+ */
+mxImageExport.prototype.visitStatesRecursive = function(state, canvas, visitor)
+{
+	if (state != null)
+	{
+		visitor(state, canvas);
+		
+		var graph = state.view.graph;
+		var childCount = graph.model.getChildCount(state.cell);
+		
+		for (var i = 0; i < childCount; i++)
+		{
+			var childState = graph.view.getState(graph.model.getChildAt(state.cell, i));
+			this.visitStatesRecursive(childState, canvas, visitor);
+		}
+	}
+};
+
+/**
+ * Function: getLinkForCellState
+ * 
+ * Returns the link for the given cell state and canvas. This returns null.
+ */
+mxImageExport.prototype.getLinkForCellState = function(state, canvas)
+{
+	return null;
+};
+
+/**
+ * Function: drawCellState
+ * 
+ * Draws the given state to the given canvas.
+ */
+mxImageExport.prototype.drawCellState = function(state, canvas)
+{
+	// Experimental feature
+	var link = this.getLinkForCellState(state, canvas);
+	
+	if (link != null)
+	{
+		canvas.setLink(link);
+	}
+	
+	// Paints the shape and text
+	this.drawShape(state, canvas);
+	this.drawText(state, canvas);
+
+	if (link != null)
+	{
+		canvas.setLink(null);
+	}
+};
+
+/**
+ * Function: drawShape
+ * 
+ * Draws the shape of the given state.
+ */
+mxImageExport.prototype.drawShape = function(state, canvas)
+{
+	if (state.shape instanceof mxShape && state.shape.checkBounds())
+	{
+		canvas.save();
+		state.shape.paint(canvas);
+		canvas.restore();
+	}
+};
+
+/**
+ * Function: drawText
+ * 
+ * Draws the text of the given state.
+ */
+mxImageExport.prototype.drawText = function(state, canvas)
+{
+	if (state.text != null && state.text.checkBounds())
+	{
+		canvas.save();
+		state.text.paint(canvas);
+		canvas.restore();
+	}
+};
+
+/**
+ * Function: drawOverlays
+ * 
+ * Draws the overlays for the given state. This is called if <includeOverlays>
+ * is true.
+ */
+mxImageExport.prototype.drawOverlays = function(state, canvas)
+{
+	if (state.overlays != null)
+	{
+		state.overlays.visit(function(id, shape)
+		{
+			if (shape instanceof mxShape)
+			{
+				shape.paint(canvas);
+			}
+		});
+	}
+};
+
diff --git a/airavata-kubernetes/workflow-composer/src/js/util/mxLog.js b/airavata-kubernetes/workflow-composer/src/js/util/mxLog.js
new file mode 100644
index 0000000..28595fd
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/util/mxLog.js
@@ -0,0 +1,413 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+var mxLog =
+{
+	/**
+	 * Class: mxLog
+	 * 
+	 * A singleton class that implements a simple console.
+	 * 
+	 * Variable: consoleName
+	 * 
+	 * Specifies the name of the console window. Default is 'Console'.
+	 */
+	consoleName: 'Console',
+	
+	/**
+	 * Variable: TRACE
+	 * 
+	 * Specified if the output for <enter> and <leave> should be visible in the
+	 * console. Default is false.
+	 */
+	TRACE: false,
+
+	/**
+	 * Variable: DEBUG
+	 * 
+	 * Specifies if the output for <debug> should be visible in the console.
+	 * Default is true.
+	 */
+	DEBUG: true,
+
+	/**
+	 * Variable: WARN
+	 * 
+	 * Specifies if the output for <warn> should be visible in the console.
+	 * Default is true.
+	 */
+	WARN: true,
+
+	/**
+	 * Variable: buffer
+	 * 
+	 * Buffer for pre-initialized content.
+	 */
+	buffer: '',
+	
+	/**
+	 * Function: init
+	 *
+	 * Initializes the DOM node for the console. This requires document.body to
+	 * point to a non-null value. This is called from within <setVisible> if the
+	 * log has not yet been initialized.
+	 */
+	init: function()
+	{
+		if (mxLog.window == null && document.body != null)
+		{
+			var title = mxLog.consoleName + ' - mxGraph ' + mxClient.VERSION;
+
+			// Creates a table that maintains the layout
+			var table = document.createElement('table');
+			table.setAttribute('width', '100%');
+			table.setAttribute('height', '100%');
+
+			var tbody = document.createElement('tbody');
+			var tr = document.createElement('tr');
+			var td = document.createElement('td');
+			td.style.verticalAlign = 'top';
+				
+			// Adds the actual console as a textarea
+			mxLog.textarea = document.createElement('textarea');
+			mxLog.textarea.setAttribute('wrap', 'off');
+			mxLog.textarea.setAttribute('readOnly', 'true');
+			mxLog.textarea.style.height = '100%';
+			mxLog.textarea.style.resize = 'none';
+			mxLog.textarea.value = mxLog.buffer;
+
+			// Workaround for wrong width in standards mode
+			if (mxClient.IS_NS && document.compatMode != 'BackCompat')
+			{
+				mxLog.textarea.style.width = '99%';
+			}
+			else
+			{
+				mxLog.textarea.style.width = '100%';
+			}
+			
+			td.appendChild(mxLog.textarea);
+			tr.appendChild(td);
+			tbody.appendChild(tr);
+
+			// Creates the container div
+			tr = document.createElement('tr');
+			mxLog.td = document.createElement('td');
+			mxLog.td.style.verticalAlign = 'top';
+			mxLog.td.setAttribute('height', '30px');
+			
+			tr.appendChild(mxLog.td);
+			tbody.appendChild(tr);
+			table.appendChild(tbody);
+
+			// Adds various debugging buttons
+			mxLog.addButton('Info', function (evt)
+			{
+				mxLog.info();
+			});
+		
+			mxLog.addButton('DOM', function (evt)
+			{
+				var content = mxUtils.getInnerHtml(document.body);
+				mxLog.debug(content);
+			});
+	
+			mxLog.addButton('Trace', function (evt)
+			{
+				mxLog.TRACE = !mxLog.TRACE;
+				
+				if (mxLog.TRACE)
+				{
+					mxLog.debug('Tracing enabled');
+				}
+				else
+				{
+					mxLog.debug('Tracing disabled');
+				}
+			});	
+
+			mxLog.addButton('Copy', function (evt)
+			{
+				try
+				{
+					mxUtils.copy(mxLog.textarea.value);
+				}
+				catch (err)
+				{
+					mxUtils.alert(err);
+				}
+			});			
+
+			mxLog.addButton('Show', function (evt)
+			{
+				try
+				{
+					mxUtils.popup(mxLog.textarea.value);
+				}
+				catch (err)
+				{
+					mxUtils.alert(err);
+				}
+			});	
+			
+			mxLog.addButton('Clear', function (evt)
+			{
+				mxLog.textarea.value = '';
+			});
+
+			// Cross-browser code to get window size
+			var h = 0;
+			var w = 0;
+			
+			if (typeof(window.innerWidth) === 'number')
+			{
+				h = window.innerHeight;
+				w = window.innerWidth;
+			}
+			else
+			{
+				h = (document.documentElement.clientHeight || document.body.clientHeight);
+				w = document.body.clientWidth;
+			}
+
+			mxLog.window = new mxWindow(title, table, Math.max(0, w - 320), Math.max(0, h - 210), 300, 160);
+			mxLog.window.setMaximizable(true);
+			mxLog.window.setScrollable(false);
+			mxLog.window.setResizable(true);
+			mxLog.window.setClosable(true);
+			mxLog.window.destroyOnClose = false;
+			
+			// Workaround for ignored textarea height in various setups
+			if (((mxClient.IS_NS || mxClient.IS_IE) && !mxClient.IS_GC &&
+				!mxClient.IS_SF && document.compatMode != 'BackCompat') ||
+				document.documentMode == 11)
+			{
+				var elt = mxLog.window.getElement();
+				
+				var resizeHandler = function(sender, evt)
+				{
+					mxLog.textarea.style.height = Math.max(0, elt.offsetHeight - 70) + 'px';
+				}; 
+				
+				mxLog.window.addListener(mxEvent.RESIZE_END, resizeHandler);
+				mxLog.window.addListener(mxEvent.MAXIMIZE, resizeHandler);
+				mxLog.window.addListener(mxEvent.NORMALIZE, resizeHandler);
+
+				mxLog.textarea.style.height = '92px';
+			}
+		}
+	},
+	
+	/**
+	 * Function: info
+	 * 
+	 * Writes the current navigator information to the console.
+	 */
+	info: function()
+	{
+		mxLog.writeln(mxUtils.toString(navigator));
+	},
+			
+	/**
+	 * Function: addButton
+	 * 
+	 * Adds a button to the console using the given label and function.
+	 */
+	addButton: function(lab, funct)
+	{
+		var button = document.createElement('button');
+		mxUtils.write(button, lab);
+		mxEvent.addListener(button, 'click', funct);
+		mxLog.td.appendChild(button);
+	},
+				
+	/**
+	 * Function: isVisible
+	 * 
+	 * Returns true if the console is visible.
+	 */
+	isVisible: function()
+	{
+		if (mxLog.window != null)
+		{
+			return mxLog.window.isVisible();
+		}
+		
+		return false;
+	},
+	
+
+	/**
+	 * Function: show
+	 * 
+	 * Shows the console.
+	 */
+	show: function()
+	{
+		mxLog.setVisible(true);
+	},
+
+	/**
+	 * Function: setVisible
+	 * 
+	 * Shows or hides the console.
+	 */
+	setVisible: function(visible)
+	{
+		if (mxLog.window == null)
+		{
+			mxLog.init();
+		}
+
+		if (mxLog.window != null)
+		{
+			mxLog.window.setVisible(visible);
+		}
+	},
+
+	/**
+	 * Function: enter
+	 * 
+	 * Writes the specified string to the console
+	 * if <TRACE> is true and returns the current 
+	 * time in milliseconds.
+	 *
+	 * Example:
+	 * 
+	 * (code)
+	 * mxLog.show();
+	 * var t0 = mxLog.enter('Hello');
+	 * // Do something
+	 * mxLog.leave('World!', t0);
+	 * (end)
+	 */
+	enter: function(string)
+	{
+		if (mxLog.TRACE)
+		{
+			mxLog.writeln('Entering '+string);
+			
+			return new Date().getTime();
+		}
+	},
+
+	/**
+	 * Function: leave
+	 * 
+	 * Writes the specified string to the console
+	 * if <TRACE> is true and computes the difference
+	 * between the current time and t0 in milliseconds.
+	 * See <enter> for an example.
+	 */
+	leave: function(string, t0)
+	{
+		if (mxLog.TRACE)
+		{
+			var dt = (t0 != 0) ? ' ('+(new Date().getTime() - t0)+' ms)' : '';
+			mxLog.writeln('Leaving '+string+dt);
+		}
+	},
+	
+	/**
+	 * Function: debug
+	 * 
+	 * Adds all arguments to the console if <DEBUG> is enabled.
+	 *
+	 * Example:
+	 * 
+	 * (code)
+	 * mxLog.show();
+	 * mxLog.debug('Hello, World!');
+	 * (end)
+	 */
+	debug: function()
+	{
+		if (mxLog.DEBUG)
+		{
+			mxLog.writeln.apply(this, arguments);
+		}
+	},
+	
+	/**
+	 * Function: warn
+	 * 
+	 * Adds all arguments to the console if <WARN> is enabled.
+	 *
+	 * Example:
+	 * 
+	 * (code)
+	 * mxLog.show();
+	 * mxLog.warn('Hello, World!');
+	 * (end)
+	 */
+	warn: function()
+	{
+		if (mxLog.WARN)
+		{
+			mxLog.writeln.apply(this, arguments);
+		}
+	},
+
+	/**
+	 * Function: write
+	 * 
+	 * Adds the specified strings to the console.
+	 */
+	write: function()
+	{
+		var string = '';
+		
+		for (var i = 0; i < arguments.length; i++)
+		{
+			string += arguments[i];
+			
+			if (i < arguments.length - 1)
+			{
+				string += ' ';
+			}
+		}
+		
+		if (mxLog.textarea != null)
+		{
+			mxLog.textarea.value = mxLog.textarea.value + string;
+
+			// Workaround for no update in Presto 2.5.22 (Opera 10.5)
+			if (navigator.userAgent.indexOf('Presto/2.5') >= 0)
+			{
+				mxLog.textarea.style.visibility = 'hidden';
+				mxLog.textarea.style.visibility = 'visible';
+			}
+			
+			mxLog.textarea.scrollTop = mxLog.textarea.scrollHeight;
+		}
+		else
+		{
+			mxLog.buffer += string;
+		}
+	},
+	
+	/**
+	 * Function: writeln
+	 * 
+	 * Adds the specified strings to the console, appending a linefeed at the
+	 * end of each string.
+	 */
+	writeln: function()
+	{
+		var string = '';
+		
+		for (var i = 0; i < arguments.length; i++)
+		{
+			string += arguments[i];
+			
+			if (i < arguments.length - 1)
+			{
+				string += ' ';
+			}
+		}
+
+		mxLog.write(string + '\n');
+	}
+	
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/util/mxMorphing.js b/airavata-kubernetes/workflow-composer/src/js/util/mxMorphing.js
new file mode 100644
index 0000000..4cbdc70
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/util/mxMorphing.js
@@ -0,0 +1,248 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ *
+ * Class: mxMorphing
+ * 
+ * Implements animation for morphing cells. Here is an example of
+ * using this class for animating the result of a layout algorithm:
+ * 
+ * (code)
+ * graph.getModel().beginUpdate();
+ * try
+ * {
+ *   var circleLayout = new mxCircleLayout(graph);
+ *   circleLayout.execute(graph.getDefaultParent());
+ * }
+ * finally
+ * {
+ *   var morph = new mxMorphing(graph);
+ *   morph.addListener(mxEvent.DONE, function()
+ *   {
+ *     graph.getModel().endUpdate();
+ *   });
+ *   
+ *   morph.startAnimation();
+ * }
+ * (end)
+ * 
+ * Constructor: mxMorphing
+ * 
+ * Constructs an animation.
+ * 
+ * Parameters:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ * steps - Optional number of steps in the morphing animation. Default is 6.
+ * ease - Optional easing constant for the animation. Default is 1.5.
+ * delay - Optional delay between the animation steps. Passed to <mxAnimation>.
+ */
+function mxMorphing(graph, steps, ease, delay)
+{
+	mxAnimation.call(this, delay);
+	this.graph = graph;
+	this.steps = (steps != null) ? steps : 6;
+	this.ease = (ease != null) ? ease : 1.5;
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxMorphing.prototype = new mxAnimation();
+mxMorphing.prototype.constructor = mxMorphing;
+
+/**
+ * Variable: graph
+ * 
+ * Specifies the delay between the animation steps. Defaul is 30ms.
+ */
+mxMorphing.prototype.graph = null;
+
+/**
+ * Variable: steps
+ * 
+ * Specifies the maximum number of steps for the morphing.
+ */
+mxMorphing.prototype.steps = null;
+
+/**
+ * Variable: step
+ * 
+ * Contains the current step.
+ */
+mxMorphing.prototype.step = 0;
+
+/**
+ * Variable: ease
+ * 
+ * Ease-off for movement towards the given vector. Larger values are
+ * slower and smoother. Default is 4.
+ */
+mxMorphing.prototype.ease = null;
+
+/**
+ * Variable: cells
+ * 
+ * Optional array of cells to be animated. If this is not specified
+ * then all cells are checked and animated if they have been moved
+ * in the current transaction.
+ */
+mxMorphing.prototype.cells = null;
+
+/**
+ * Function: updateAnimation
+ *
+ * Animation step.
+ */
+mxMorphing.prototype.updateAnimation = function()
+{
+	var move = new mxCellStatePreview(this.graph);
+
+	if (this.cells != null)
+	{
+		// Animates the given cells individually without recursion
+		for (var i = 0; i < this.cells.length; i++)
+		{
+			this.animateCell(this.cells[i], move, false);
+		}
+	}
+	else
+	{
+		// Animates all changed cells by using recursion to find
+		// the changed cells but not for the animation itself
+		this.animateCell(this.graph.getModel().getRoot(), move, true);
+	}
+	
+	this.show(move);
+	
+	if (move.isEmpty() || this.step++ >= this.steps)
+	{
+		this.stopAnimation();
+	}
+};
+
+/**
+ * Function: show
+ *
+ * Shows the changes in the given <mxCellStatePreview>.
+ */
+mxMorphing.prototype.show = function(move)
+{
+	move.show();
+};
+
+/**
+ * Function: animateCell
+ *
+ * Animates the given cell state using <mxCellStatePreview.moveState>.
+ */
+mxMorphing.prototype.animateCell = function(cell, move, recurse)
+{
+	var state = this.graph.getView().getState(cell);
+	var delta = null;
+
+	if (state != null)
+	{
+		// Moves the animated state from where it will be after the model
+		// change by subtracting the given delta vector from that location
+		delta = this.getDelta(state);
+
+		if (this.graph.getModel().isVertex(cell) && (delta.x != 0 || delta.y != 0))
+		{
+			var translate = this.graph.view.getTranslate();
+			var scale = this.graph.view.getScale();
+			
+			delta.x += translate.x * scale;
+			delta.y += translate.y * scale;
+			
+			move.moveState(state, -delta.x / this.ease, -delta.y / this.ease);
+		}
+	}
+	
+	if (recurse && !this.stopRecursion(state, delta))
+	{
+		var childCount = this.graph.getModel().getChildCount(cell);
+
+		for (var i = 0; i < childCount; i++)
+		{
+			this.animateCell(this.graph.getModel().getChildAt(cell, i), move, recurse);
+		}
+	}
+};
+
+/**
+ * Function: stopRecursion
+ *
+ * Returns true if the animation should not recursively find more
+ * deltas for children if the given parent state has been animated.
+ */
+mxMorphing.prototype.stopRecursion = function(state, delta)
+{
+	return delta != null && (delta.x != 0 || delta.y != 0);
+};
+
+/**
+ * Function: getDelta
+ *
+ * Returns the vector between the current rendered state and the future
+ * location of the state after the display will be updated.
+ */
+mxMorphing.prototype.getDelta = function(state)
+{
+	var origin = this.getOriginForCell(state.cell);
+	var translate = this.graph.getView().getTranslate();
+	var scale = this.graph.getView().getScale();
+	var x = state.x / scale - translate.x;
+	var y = state.y / scale - translate.y;
+
+	return new mxPoint((origin.x - x) * scale, (origin.y - y) * scale);
+};
+
+/**
+ * Function: getOriginForCell
+ *
+ * Returns the top, left corner of the given cell. TODO: Improve performance
+ * by using caching inside this method as the result per cell never changes
+ * during the lifecycle of this object.
+ */
+mxMorphing.prototype.getOriginForCell = function(cell)
+{
+	var result = null;
+	
+	if (cell != null)
+	{
+		var parent = this.graph.getModel().getParent(cell);
+		var geo = this.graph.getCellGeometry(cell);
+		result = this.getOriginForCell(parent);
+		
+		// TODO: Handle offsets
+		if (geo != null)
+		{
+			if (geo.relative)
+			{
+				var pgeo = this.graph.getCellGeometry(parent);
+				
+				if (pgeo != null)
+				{
+					result.x += geo.x * pgeo.width;
+					result.y += geo.y * pgeo.height;
+				}
+			}
+			else
+			{
+				result.x += geo.x;
+				result.y += geo.y;
+			}
+		}
+	}
+	
+	if (result == null)
+	{
+		var t = this.graph.view.getTranslate();
+		result = new mxPoint(-t.x, -t.y);
+	}
+	
+	return result;
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/util/mxMouseEvent.js b/airavata-kubernetes/workflow-composer/src/js/util/mxMouseEvent.js
new file mode 100644
index 0000000..875bac4
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/util/mxMouseEvent.js
@@ -0,0 +1,244 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxMouseEvent
+ * 
+ * Base class for all mouse events in mxGraph. A listener for this event should
+ * implement the following methods:
+ * 
+ * (code)
+ * graph.addMouseListener(
+ * {
+ *   mouseDown: function(sender, evt)
+ *   {
+ *     mxLog.debug('mouseDown');
+ *   },
+ *   mouseMove: function(sender, evt)
+ *   {
+ *     mxLog.debug('mouseMove');
+ *   },
+ *   mouseUp: function(sender, evt)
+ *   {
+ *     mxLog.debug('mouseUp');
+ *   }
+ * });
+ * (end)
+ * 
+ * Constructor: mxMouseEvent
+ *
+ * Constructs a new event object for the given arguments.
+ * 
+ * Parameters:
+ * 
+ * evt - Native mouse event.
+ * state - Optional <mxCellState> under the mouse.
+ * 
+ */
+function mxMouseEvent(evt, state)
+{
+	this.evt = evt;
+	this.state = state;
+	this.sourceState = state;
+};
+
+/**
+ * Variable: consumed
+ *
+ * Holds the consumed state of this event.
+ */
+mxMouseEvent.prototype.consumed = false;
+
+/**
+ * Variable: evt
+ *
+ * Holds the inner event object.
+ */
+mxMouseEvent.prototype.evt = null;
+
+/**
+ * Variable: graphX
+ *
+ * Holds the x-coordinate of the event in the graph. This value is set in
+ * <mxGraph.fireMouseEvent>.
+ */
+mxMouseEvent.prototype.graphX = null;
+
+/**
+ * Variable: graphY
+ *
+ * Holds the y-coordinate of the event in the graph. This value is set in
+ * <mxGraph.fireMouseEvent>.
+ */
+mxMouseEvent.prototype.graphY = null;
+
+/**
+ * Variable: state
+ *
+ * Holds the optional <mxCellState> associated with this event.
+ */
+mxMouseEvent.prototype.state = null;
+
+/**
+ * Variable: sourceState
+ * 
+ * Holds the <mxCellState> that was passed to the constructor. This can be
+ * different from <state> depending on the result of <mxGraph.getEventState>.
+ */
+mxMouseEvent.prototype.sourceState = null;
+
+/**
+ * Function: getEvent
+ * 
+ * Returns <evt>.
+ */
+mxMouseEvent.prototype.getEvent = function()
+{
+	return this.evt;
+};
+
+/**
+ * Function: getSource
+ * 
+ * Returns the target DOM element using <mxEvent.getSource> for <evt>.
+ */
+mxMouseEvent.prototype.getSource = function()
+{
+	return mxEvent.getSource(this.evt);
+};
+
+/**
+ * Function: isSource
+ * 
+ * Returns true if the given <mxShape> is the source of <evt>.
+ */
+mxMouseEvent.prototype.isSource = function(shape)
+{
+	if (shape != null)
+	{
+		return mxUtils.isAncestorNode(shape.node, this.getSource());
+	}
+	
+	return false;
+};
+
+/**
+ * Function: getX
+ * 
+ * Returns <evt.clientX>.
+ */
+mxMouseEvent.prototype.getX = function()
+{
+	return mxEvent.getClientX(this.getEvent());
+};
+
+/**
+ * Function: getY
+ * 
+ * Returns <evt.clientY>.
+ */
+mxMouseEvent.prototype.getY = function()
+{
+	return mxEvent.getClientY(this.getEvent());
+};
+
+/**
+ * Function: getGraphX
+ * 
+ * Returns <graphX>.
+ */
+mxMouseEvent.prototype.getGraphX = function()
+{
+	return this.graphX;
+};
+
+/**
+ * Function: getGraphY
+ * 
+ * Returns <graphY>.
+ */
+mxMouseEvent.prototype.getGraphY = function()
+{
+	return this.graphY;
+};
+
+/**
+ * Function: getState
+ * 
+ * Returns <state>.
+ */
+mxMouseEvent.prototype.getState = function()
+{
+	return this.state;
+};
+
+/**
+ * Function: getCell
+ * 
+ * Returns the <mxCell> in <state> is not null.
+ */
+mxMouseEvent.prototype.getCell = function()
+{
+	var state = this.getState();
+	
+	if (state != null)
+	{
+		return state.cell;
+	}
+	
+	return null;
+};
+
+/**
+ * Function: isPopupTrigger
+ *
+ * Returns true if the event is a popup trigger.
+ */
+mxMouseEvent.prototype.isPopupTrigger = function()
+{
+	return mxEvent.isPopupTrigger(this.getEvent());
+};
+
+/**
+ * Function: isConsumed
+ *
+ * Returns <consumed>.
+ */
+mxMouseEvent.prototype.isConsumed = function()
+{
+	return this.consumed;
+};
+
+/**
+ * Function: consume
+ *
+ * Sets <consumed> to true and invokes preventDefault on the native event
+ * if such a method is defined. This is used mainly to avoid the cursor from
+ * being changed to a text cursor in Webkit. You can use the preventDefault
+ * flag to disable this functionality.
+ * 
+ * Parameters:
+ * 
+ * preventDefault - Specifies if the native event should be canceled. Default
+ * is true.
+ */
+mxMouseEvent.prototype.consume = function(preventDefault)
+{
+	preventDefault = (preventDefault != null) ? preventDefault : true;
+	
+	if (preventDefault && this.evt.preventDefault)
+	{
+		this.evt.preventDefault();
+	}
+
+	// Workaround for images being dragged in IE
+	// Does not change returnValue in Opera
+	if (mxClient.IS_IE)
+	{
+		this.evt.returnValue = true;
+	}
+
+	// Sets local consumed state
+	this.consumed = true;
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/util/mxObjectIdentity.js b/airavata-kubernetes/workflow-composer/src/js/util/mxObjectIdentity.js
new file mode 100644
index 0000000..457eee8
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/util/mxObjectIdentity.js
@@ -0,0 +1,72 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+var mxObjectIdentity =
+{
+	/**
+	 * Class: mxObjectIdentity
+	 * 
+	 * Identity for JavaScript objects and functions. This is implemented using
+	 * a simple incrementing counter which is stored in each object under
+	 * <FIELD_NAME>.
+	 * 
+	 * The identity for an object does not change during its lifecycle.
+	 * 
+	 * Variable: FIELD_NAME
+	 * 
+	 * Name of the field to be used to store the object ID. Default is
+	 * <code>mxObjectId</code>.
+	 */
+	FIELD_NAME: 'mxObjectId',
+
+	/**
+	 * Variable: counter
+	 * 
+	 * Current counter.
+	 */
+	counter: 0,
+
+	/**
+	 * Function: get
+	 * 
+	 * Returns the ID for the given object or function or null if no object
+	 * is specified.
+	 */
+	get: function(obj)
+	{
+		if (obj != null)
+		{
+			if (obj[mxObjectIdentity.FIELD_NAME] == null)
+			{
+				if (typeof obj === 'object')
+				{
+					var ctor = mxUtils.getFunctionName(obj.constructor);
+					obj[mxObjectIdentity.FIELD_NAME] = ctor + '#' + mxObjectIdentity.counter++;
+				}
+				else if (typeof obj === 'function')
+				{
+					obj[mxObjectIdentity.FIELD_NAME] = 'Function#' + mxObjectIdentity.counter++;
+				}
+			}
+			
+			return obj[mxObjectIdentity.FIELD_NAME];
+		}
+		
+		return null;
+	},
+
+	/**
+	 * Function: clear
+	 * 
+	 * Deletes the ID from the given object or function.
+	 */
+	clear: function(obj)
+	{
+		if (typeof(obj) === 'object' || typeof obj === 'function')
+		{
+			delete obj[mxObjectIdentity.FIELD_NAME];
+		}
+	}
+
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/util/mxPanningManager.js b/airavata-kubernetes/workflow-composer/src/js/util/mxPanningManager.js
new file mode 100644
index 0000000..0a3f815
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/util/mxPanningManager.js
@@ -0,0 +1,262 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxPanningManager
+ *
+ * Implements a handler for panning.
+ */
+function mxPanningManager(graph)
+{
+	this.thread = null;
+	this.active = false;
+	this.tdx = 0;
+	this.tdy = 0;
+	this.t0x = 0;
+	this.t0y = 0;
+	this.dx = 0;
+	this.dy = 0;
+	this.scrollbars = false;
+	this.scrollLeft = 0;
+	this.scrollTop = 0;
+	
+	this.mouseListener =
+	{
+	    mouseDown: function(sender, me) { },
+	    mouseMove: function(sender, me) { },
+	    mouseUp: mxUtils.bind(this, function(sender, me)
+	    {
+	    	if (this.active)
+	    	{
+	    		this.stop();
+	    	}
+	    })
+	};
+	
+	graph.addMouseListener(this.mouseListener);
+	
+	// Stops scrolling on every mouseup anywhere in the document
+	mxEvent.addListener(document, 'mouseup', mxUtils.bind(this, function()
+	{
+    	if (this.active)
+    	{
+    		this.stop();
+    	}
+	}));
+	
+	var createThread = mxUtils.bind(this, function()
+	{
+    	this.scrollbars = mxUtils.hasScrollbars(graph.container);
+    	this.scrollLeft = graph.container.scrollLeft;
+    	this.scrollTop = graph.container.scrollTop;
+
+    	return window.setInterval(mxUtils.bind(this, function()
+		{
+			this.tdx -= this.dx;
+			this.tdy -= this.dy;
+
+			if (this.scrollbars)
+			{
+				var left = -graph.container.scrollLeft - Math.ceil(this.dx);
+				var top = -graph.container.scrollTop - Math.ceil(this.dy);
+				graph.panGraph(left, top);
+				graph.panDx = this.scrollLeft - graph.container.scrollLeft;
+				graph.panDy = this.scrollTop - graph.container.scrollTop;
+				graph.fireEvent(new mxEventObject(mxEvent.PAN));
+				// TODO: Implement graph.autoExtend
+			}
+			else
+			{
+				graph.panGraph(this.getDx(), this.getDy());
+			}
+		}), this.delay);
+	});
+	
+	this.isActive = function()
+	{
+		return active;
+	};
+	
+	this.getDx = function()
+	{
+		return Math.round(this.tdx);
+	};
+	
+	this.getDy = function()
+	{
+		return Math.round(this.tdy);
+	};
+	
+	this.start = function()
+	{
+		this.t0x = graph.view.translate.x;
+		this.t0y = graph.view.translate.y;
+		this.active = true;
+	};
+	
+	this.panTo = function(x, y, w, h)
+	{
+		if (!this.active)
+		{
+			this.start();
+		}
+		
+    	this.scrollLeft = graph.container.scrollLeft;
+    	this.scrollTop = graph.container.scrollTop;
+		
+		w = (w != null) ? w : 0;
+		h = (h != null) ? h : 0;
+		
+		var c = graph.container;
+		this.dx = x + w - c.scrollLeft - c.clientWidth;
+		
+		if (this.dx < 0 && Math.abs(this.dx) < this.border)
+		{
+			this.dx = this.border + this.dx;
+		}
+		else if (this.handleMouseOut)
+		{
+			this.dx = Math.max(this.dx, 0);
+		}
+		else
+		{
+			this.dx = 0;
+		}
+		
+		if (this.dx == 0)
+		{
+			this.dx = x - c.scrollLeft;
+			
+			if (this.dx > 0 && this.dx < this.border)
+			{
+				this.dx = this.dx - this.border;
+			}
+			else if (this.handleMouseOut)
+			{
+				this.dx = Math.min(0, this.dx);
+			}
+			else
+			{
+				this.dx = 0;
+			}
+		}
+		
+		this.dy = y + h - c.scrollTop - c.clientHeight;
+
+		if (this.dy < 0 && Math.abs(this.dy) < this.border)
+		{
+			this.dy = this.border + this.dy;
+		}
+		else if (this.handleMouseOut)
+		{
+			this.dy = Math.max(this.dy, 0);
+		}
+		else
+		{
+			this.dy = 0;
+		}
+		
+		if (this.dy == 0)
+		{
+			this.dy = y - c.scrollTop;
+			
+			if (this.dy > 0 && this.dy < this.border)
+			{
+				this.dy = this.dy - this.border;
+			}
+			else if (this.handleMouseOut)
+			{
+				this.dy = Math.min(0, this.dy);
+			} 
+			else
+			{
+				this.dy = 0;
+			}
+		}
+		
+		if (this.dx != 0 || this.dy != 0)
+		{
+			this.dx *= this.damper;
+			this.dy *= this.damper;
+			
+			if (this.thread == null)
+			{
+				this.thread = createThread();
+			}
+		}
+		else if (this.thread != null)
+		{
+			window.clearInterval(this.thread);
+			this.thread = null;
+		}
+	};
+	
+	this.stop = function()
+	{
+		if (this.active)
+		{
+			this.active = false;
+		
+			if (this.thread != null)
+	    	{
+				window.clearInterval(this.thread);
+				this.thread = null;
+	    	}
+			
+			this.tdx = 0;
+			this.tdy = 0;
+			
+			if (!this.scrollbars)
+			{
+				var px = graph.panDx;
+				var py = graph.panDy;
+		    	
+		    	if (px != 0 || py != 0)
+		    	{
+		    		graph.panGraph(0, 0);
+			    	graph.view.setTranslate(this.t0x + px / graph.view.scale, this.t0y + py / graph.view.scale);
+		    	}
+			}
+			else
+			{
+				graph.panDx = 0;
+				graph.panDy = 0;
+				graph.fireEvent(new mxEventObject(mxEvent.PAN));
+			}
+		}
+	};
+	
+	this.destroy = function()
+	{
+		graph.removeMouseListener(this.mouseListener);
+	};
+};
+
+/**
+ * Variable: damper
+ * 
+ * Damper value for the panning. Default is 1/6.
+ */
+mxPanningManager.prototype.damper = 1/6;
+
+/**
+ * Variable: delay
+ * 
+ * Delay in milliseconds for the panning. Default is 10.
+ */
+mxPanningManager.prototype.delay = 10;
+
+/**
+ * Variable: handleMouseOut
+ * 
+ * Specifies if mouse events outside of the component should be handled. Default is true. 
+ */
+mxPanningManager.prototype.handleMouseOut = true;
+
+/**
+ * Variable: border
+ * 
+ * Border to handle automatic panning inside the component. Default is 0 (disabled).
+ */
+mxPanningManager.prototype.border = 0;
diff --git a/airavata-kubernetes/workflow-composer/src/js/util/mxPoint.js b/airavata-kubernetes/workflow-composer/src/js/util/mxPoint.js
new file mode 100644
index 0000000..e7d300a
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/util/mxPoint.js
@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxPoint
+ *
+ * Implements a 2-dimensional vector with double precision coordinates.
+ * 
+ * Constructor: mxPoint
+ *
+ * Constructs a new point for the optional x and y coordinates. If no
+ * coordinates are given, then the default values for <x> and <y> are used.
+ */
+function mxPoint(x, y)
+{
+	this.x = (x != null) ? x : 0;
+	this.y = (y != null) ? y : 0;
+};
+
+/**
+ * Variable: x
+ *
+ * Holds the x-coordinate of the point. Default is 0.
+ */
+mxPoint.prototype.x = null;
+
+/**
+ * Variable: y
+ *
+ * Holds the y-coordinate of the point. Default is 0.
+ */
+mxPoint.prototype.y = null;
+
+/**
+ * Function: equals
+ * 
+ * Returns true if the given object equals this point.
+ */
+mxPoint.prototype.equals = function(obj)
+{
+	return obj != null && obj.x == this.x && obj.y == this.y;
+};
+
+/**
+ * Function: clone
+ *
+ * Returns a clone of this <mxPoint>.
+ */
+mxPoint.prototype.clone = function()
+{
+	// Handles subclasses as well
+	return mxUtils.clone(this);
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/util/mxPopupMenu.js b/airavata-kubernetes/workflow-composer/src/js/util/mxPopupMenu.js
new file mode 100644
index 0000000..82777a2
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/util/mxPopupMenu.js
@@ -0,0 +1,613 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxPopupMenu
+ * 
+ * Basic popup menu. To add a vertical scrollbar to a given submenu, the
+ * following code can be used.
+ * 
+ * (code)
+ * var mxPopupMenuShowMenu = mxPopupMenu.prototype.showMenu;
+ * mxPopupMenu.prototype.showMenu = function()
+ * {
+ *   mxPopupMenuShowMenu.apply(this, arguments);
+ *   
+ *   this.div.style.overflowY = 'auto';
+ *   this.div.style.overflowX = 'hidden';
+ *   this.div.style.maxHeight = '160px';
+ * };
+ * (end)
+ * 
+ * Constructor: mxPopupMenu
+ * 
+ * Constructs a popupmenu.
+ * 
+ * Event: mxEvent.SHOW
+ *
+ * Fires after the menu has been shown in <popup>.
+ */
+function mxPopupMenu(factoryMethod)
+{
+	this.factoryMethod = factoryMethod;
+	
+	if (factoryMethod != null)
+	{
+		this.init();
+	}
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxPopupMenu.prototype = new mxEventSource();
+mxPopupMenu.prototype.constructor = mxPopupMenu;
+
+/**
+ * Variable: submenuImage
+ * 
+ * URL of the image to be used for the submenu icon.
+ */
+mxPopupMenu.prototype.submenuImage = mxClient.imageBasePath + '/submenu.gif';
+
+/**
+ * Variable: zIndex
+ * 
+ * Specifies the zIndex for the popupmenu and its shadow. Default is 1006.
+ */
+mxPopupMenu.prototype.zIndex = 10006;
+
+/**
+ * Variable: factoryMethod
+ * 
+ * Function that is used to create the popup menu. The function takes the
+ * current panning handler, the <mxCell> under the mouse and the mouse
+ * event that triggered the call as arguments.
+ */
+mxPopupMenu.prototype.factoryMethod = null;
+
+/**
+ * Variable: useLeftButtonForPopup
+ * 
+ * Specifies if popupmenus should be activated by clicking the left mouse
+ * button. Default is false.
+ */
+mxPopupMenu.prototype.useLeftButtonForPopup = false;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies if events are handled. Default is true.
+ */
+mxPopupMenu.prototype.enabled = true;
+
+/**
+ * Variable: itemCount
+ * 
+ * Contains the number of times <addItem> has been called for a new menu.
+ */
+mxPopupMenu.prototype.itemCount = 0;
+
+/**
+ * Variable: autoExpand
+ * 
+ * Specifies if submenus should be expanded on mouseover. Default is false.
+ */
+mxPopupMenu.prototype.autoExpand = false;
+
+/**
+ * Variable: smartSeparators
+ * 
+ * Specifies if separators should only be added if a menu item follows them.
+ * Default is false.
+ */
+mxPopupMenu.prototype.smartSeparators = false;
+
+/**
+ * Variable: labels
+ * 
+ * Specifies if any labels should be visible. Default is true.
+ */
+mxPopupMenu.prototype.labels = true;
+
+/**
+ * Function: init
+ * 
+ * Initializes the shapes required for this vertex handler.
+ */
+mxPopupMenu.prototype.init = function()
+{
+	// Adds the inner table
+	this.table = document.createElement('table');
+	this.table.className = 'mxPopupMenu';
+	
+	this.tbody = document.createElement('tbody');
+	this.table.appendChild(this.tbody);
+
+	// Adds the outer div
+	this.div = document.createElement('div');
+	this.div.className = 'mxPopupMenu';
+	this.div.style.display = 'inline';
+	this.div.style.zIndex = this.zIndex;
+	this.div.appendChild(this.table);
+
+	// Disables the context menu on the outer div
+	mxEvent.disableContextMenu(this.div);
+};
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxPopupMenu.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+	
+/**
+ * Function: setEnabled
+ * 
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ */
+mxPopupMenu.prototype.setEnabled = function(enabled)
+{
+	this.enabled = enabled;
+};
+
+/**
+ * Function: isPopupTrigger
+ * 
+ * Returns true if the given event is a popupmenu trigger for the optional
+ * given cell.
+ * 
+ * Parameters:
+ * 
+ * me - <mxMouseEvent> that represents the mouse event.
+ */
+mxPopupMenu.prototype.isPopupTrigger = function(me)
+{
+	return me.isPopupTrigger() || (this.useLeftButtonForPopup && mxEvent.isLeftMouseButton(me.getEvent()));
+};
+
+/**
+ * Function: addItem
+ * 
+ * Adds the given item to the given parent item. If no parent item is specified
+ * then the item is added to the top-level menu. The return value may be used
+ * as the parent argument, ie. as a submenu item. The return value is the table
+ * row that represents the item.
+ * 
+ * Paramters:
+ * 
+ * title - String that represents the title of the menu item.
+ * image - Optional URL for the image icon.
+ * funct - Function associated that takes a mouseup or touchend event.
+ * parent - Optional item returned by <addItem>.
+ * iconCls - Optional string that represents the CSS class for the image icon.
+ * IconsCls is ignored if image is given.
+ * enabled - Optional boolean indicating if the item is enabled. Default is true.
+ * active - Optional boolean indicating if the menu should implement any event handling.
+ * Default is true.
+ */
+mxPopupMenu.prototype.addItem = function(title, image, funct, parent, iconCls, enabled, active)
+{
+	parent = parent || this;
+	this.itemCount++;
+	
+	// Smart separators only added if element contains items
+	if (parent.willAddSeparator)
+	{
+		if (parent.containsItems)
+		{
+			this.addSeparator(parent, true);
+		}
+
+		parent.willAddSeparator = false;
+	}
+
+	parent.containsItems = true;
+	var tr = document.createElement('tr');
+	tr.className = 'mxPopupMenuItem';
+	var col1 = document.createElement('td');
+	col1.className = 'mxPopupMenuIcon';
+
+	// Adds the given image into the first column
+	if (image != null)
+	{
+		var img = document.createElement('img');
+		img.src = image;
+		col1.appendChild(img);
+	}
+	else if (iconCls != null)
+	{
+		var div = document.createElement('div');
+		div.className = iconCls;
+		col1.appendChild(div);
+	}
+	
+	tr.appendChild(col1);
+	
+	if (this.labels)
+	{
+		var col2 = document.createElement('td');
+		col2.className = 'mxPopupMenuItem' +
+			((enabled != null && !enabled) ? ' mxDisabled' : '');
+		
+		mxUtils.write(col2, title);
+		col2.align = 'left';
+		tr.appendChild(col2);
+	
+		var col3 = document.createElement('td');
+		col3.className = 'mxPopupMenuItem' +
+			((enabled != null && !enabled) ? ' mxDisabled' : '');
+		col3.style.paddingRight = '6px';
+		col3.style.textAlign = 'right';
+		
+		tr.appendChild(col3);
+		
+		if (parent.div == null)
+		{
+			this.createSubmenu(parent);
+		}
+	}
+	
+	parent.tbody.appendChild(tr);
+
+	if (active != false && enabled != false)
+	{
+		var currentSelection = null;
+		
+		mxEvent.addGestureListeners(tr,
+			mxUtils.bind(this, function(evt)
+			{
+				this.eventReceiver = tr;
+				
+				if (parent.activeRow != tr && parent.activeRow != parent)
+				{
+					if (parent.activeRow != null && parent.activeRow.div.parentNode != null)
+					{
+						this.hideSubmenu(parent);
+					}
+					
+					if (tr.div != null)
+					{
+						this.showSubmenu(parent, tr);
+						parent.activeRow = tr;
+					}
+				}
+				
+				// Workaround for lost current selection in page because of focus in IE
+				if (mxClient.IS_QUIRKS || document.documentMode == 8)
+				{
+					currentSelection = document.selection.createRange();
+				}
+				
+				mxEvent.consume(evt);
+			}),
+			mxUtils.bind(this, function(evt)
+			{
+				if (parent.activeRow != tr && parent.activeRow != parent)
+				{
+					if (parent.activeRow != null && parent.activeRow.div.parentNode != null)
+					{
+						this.hideSubmenu(parent);
+					}
+					
+					if (this.autoExpand && tr.div != null)
+					{
+						this.showSubmenu(parent, tr);
+						parent.activeRow = tr;
+					}
+				}
+		
+				// Sets hover style because TR in IE doesn't have hover
+				tr.className = 'mxPopupMenuItemHover';
+			}),
+			mxUtils.bind(this, function(evt)
+			{
+				// EventReceiver avoids clicks on a submenu item
+				// which has just been shown in the mousedown
+				if (this.eventReceiver == tr)
+				{
+					if (parent.activeRow != tr)
+					{
+						this.hideMenu();
+					}
+					
+					// Workaround for lost current selection in page because of focus in IE
+					if (currentSelection != null)
+					{
+						// Workaround for "unspecified error" in IE8 standards
+						try
+						{
+							currentSelection.select();
+						}
+						catch (e)
+						{
+							// ignore
+						}
+
+						currentSelection = null;
+					}
+					
+					if (funct != null)
+					{
+						funct(evt);
+					}
+				}
+				
+				this.eventReceiver = null;
+				mxEvent.consume(evt);
+			})
+		);
+	
+		// Resets hover style because TR in IE doesn't have hover
+		mxEvent.addListener(tr, 'mouseout',
+			mxUtils.bind(this, function(evt)
+			{
+				tr.className = 'mxPopupMenuItem';
+			})
+		);
+	}
+	
+	return tr;
+};
+
+/**
+ * Adds a checkmark to the given menuitem.
+ */
+mxPopupMenu.prototype.addCheckmark = function(item, img)
+{
+	var td = item.firstChild.nextSibling;
+	td.style.backgroundImage = 'url(\'' + img + '\')';
+	td.style.backgroundRepeat = 'no-repeat';
+	td.style.backgroundPosition = '2px 50%';
+};
+
+/**
+ * Function: createSubmenu
+ * 
+ * Creates the nodes required to add submenu items inside the given parent
+ * item. This is called in <addItem> if a parent item is used for the first
+ * time. This adds various DOM nodes and a <submenuImage> to the parent.
+ * 
+ * Parameters:
+ * 
+ * parent - An item returned by <addItem>.
+ */
+mxPopupMenu.prototype.createSubmenu = function(parent)
+{
+	parent.table = document.createElement('table');
+	parent.table.className = 'mxPopupMenu';
+
+	parent.tbody = document.createElement('tbody');
+	parent.table.appendChild(parent.tbody);
+
+	parent.div = document.createElement('div');
+	parent.div.className = 'mxPopupMenu';
+
+	parent.div.style.position = 'absolute';
+	parent.div.style.display = 'inline';
+	parent.div.style.zIndex = this.zIndex;
+	
+	parent.div.appendChild(parent.table);
+	
+	var img = document.createElement('img');
+	img.setAttribute('src', this.submenuImage);
+	
+	// Last column of the submenu item in the parent menu
+	td = parent.firstChild.nextSibling.nextSibling;
+	td.appendChild(img);
+};
+
+/**
+ * Function: showSubmenu
+ * 
+ * Shows the submenu inside the given parent row.
+ */
+mxPopupMenu.prototype.showSubmenu = function(parent, row)
+{
+	if (row.div != null)
+	{
+		row.div.style.left = (parent.div.offsetLeft +
+			row.offsetLeft+row.offsetWidth - 1) + 'px';
+		row.div.style.top = (parent.div.offsetTop+row.offsetTop) + 'px';
+		document.body.appendChild(row.div);
+		
+		// Moves the submenu to the left side if there is no space
+		var left = parseInt(row.div.offsetLeft);
+		var width = parseInt(row.div.offsetWidth);
+		var offset = mxUtils.getDocumentScrollOrigin(document);
+		
+		var b = document.body;
+		var d = document.documentElement;
+		
+		var right = offset.x + (b.clientWidth || d.clientWidth);
+		
+		if (left + width > right)
+		{
+			row.div.style.left = Math.max(0, (parent.div.offsetLeft - width + ((mxClient.IS_IE) ? 6 : -6))) + 'px';
+		}
+		
+		mxUtils.fit(row.div);
+	}
+};
+
+/**
+ * Function: addSeparator
+ * 
+ * Adds a horizontal separator in the given parent item or the top-level menu
+ * if no parent is specified.
+ * 
+ * Parameters:
+ * 
+ * parent - Optional item returned by <addItem>.
+ * force - Optional boolean to ignore <smartSeparators>. Default is false.
+ */
+mxPopupMenu.prototype.addSeparator = function(parent, force)
+{
+	parent = parent || this;
+	
+	if (this.smartSeparators && !force)
+	{
+		parent.willAddSeparator = true;
+	}
+	else if (parent.tbody != null)
+	{
+		parent.willAddSeparator = false;
+		var tr = document.createElement('tr');
+		
+		var col1 = document.createElement('td');
+		col1.className = 'mxPopupMenuIcon';
+		col1.style.padding = '0 0 0 0px';
+		
+		tr.appendChild(col1);
+		
+		var col2 = document.createElement('td');
+		col2.style.padding = '0 0 0 0px';
+		col2.setAttribute('colSpan', '2');
+	
+		var hr = document.createElement('hr');
+		hr.setAttribute('size', '1');
+		col2.appendChild(hr);
+		
+		tr.appendChild(col2);
+		
+		parent.tbody.appendChild(tr);
+	}
+};
+
+/**
+ * Function: popup
+ * 
+ * Shows the popup menu for the given event and cell.
+ * 
+ * Example:
+ * 
+ * (code)
+ * graph.panningHandler.popup = function(x, y, cell, evt)
+ * {
+ *   mxUtils.alert('Hello, World!');
+ * }
+ * (end)
+ */
+mxPopupMenu.prototype.popup = function(x, y, cell, evt)
+{
+	if (this.div != null && this.tbody != null && this.factoryMethod != null)
+	{
+		this.div.style.left = x + 'px';
+		this.div.style.top = y + 'px';
+		
+		// Removes all child nodes from the existing menu
+		while (this.tbody.firstChild != null)
+		{
+			mxEvent.release(this.tbody.firstChild);
+			this.tbody.removeChild(this.tbody.firstChild);
+		}
+		
+		this.itemCount = 0;
+		this.factoryMethod(this, cell, evt);
+		
+		if (this.itemCount > 0)
+		{
+			this.showMenu();
+			this.fireEvent(new mxEventObject(mxEvent.SHOW));
+		}
+	}
+};
+
+/**
+ * Function: isMenuShowing
+ * 
+ * Returns true if the menu is showing.
+ */
+mxPopupMenu.prototype.isMenuShowing = function()
+{
+	return this.div != null && this.div.parentNode == document.body;
+};
+
+/**
+ * Function: showMenu
+ * 
+ * Shows the menu.
+ */
+mxPopupMenu.prototype.showMenu = function()
+{
+	// Disables filter-based shadow in IE9 standards mode
+	if (document.documentMode >= 9)
+	{
+		this.div.style.filter = 'none';
+	}
+	
+	// Fits the div inside the viewport
+	document.body.appendChild(this.div);
+	mxUtils.fit(this.div);
+};
+
+/**
+ * Function: hideMenu
+ * 
+ * Removes the menu and all submenus.
+ */
+mxPopupMenu.prototype.hideMenu = function()
+{
+	if (this.div != null)
+	{
+		if (this.div.parentNode != null)
+		{
+			this.div.parentNode.removeChild(this.div);
+		}
+		
+		this.hideSubmenu(this);
+		this.containsItems = false;
+		this.fireEvent(new mxEventObject(mxEvent.HIDE));
+	}
+};
+
+/**
+ * Function: hideSubmenu
+ * 
+ * Removes all submenus inside the given parent.
+ * 
+ * Parameters:
+ * 
+ * parent - An item returned by <addItem>.
+ */
+mxPopupMenu.prototype.hideSubmenu = function(parent)
+{
+	if (parent.activeRow != null)
+	{
+		this.hideSubmenu(parent.activeRow);
+		
+		if (parent.activeRow.div.parentNode != null)
+		{
+			parent.activeRow.div.parentNode.removeChild(parent.activeRow.div);
+		}
+		
+		parent.activeRow = null;
+	}
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the handler and all its resources and DOM nodes.
+ */
+mxPopupMenu.prototype.destroy = function()
+{
+	if (this.div != null)
+	{
+		mxEvent.release(this.div);
+		
+		if (this.div.parentNode != null)
+		{
+			this.div.parentNode.removeChild(this.div);
+		}
+		
+		this.div = null;
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/util/mxRectangle.js b/airavata-kubernetes/workflow-composer/src/js/util/mxRectangle.js
new file mode 100644
index 0000000..b935e5a
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/util/mxRectangle.js
@@ -0,0 +1,179 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxRectangle
+ *
+ * Extends <mxPoint> to implement a 2-dimensional rectangle with double
+ * precision coordinates.
+ * 
+ * Constructor: mxRectangle
+ *
+ * Constructs a new rectangle for the optional parameters. If no parameters
+ * are given then the respective default values are used.
+ */
+function mxRectangle(x, y, width, height)
+{
+	mxPoint.call(this, x, y);
+
+	this.width = (width != null) ? width : 0;
+	this.height = (height != null) ? height : 0;
+};
+
+/**
+ * Extends mxPoint.
+ */
+mxRectangle.prototype = new mxPoint();
+mxRectangle.prototype.constructor = mxRectangle;
+
+/**
+ * Variable: width
+ *
+ * Holds the width of the rectangle. Default is 0.
+ */
+mxRectangle.prototype.width = null;
+
+/**
+ * Variable: height
+ *
+ * Holds the height of the rectangle. Default is 0.
+ */
+mxRectangle.prototype.height = null;
+
+/**
+ * Function: setRect
+ * 
+ * Sets this rectangle to the specified values
+ */
+mxRectangle.prototype.setRect = function(x, y, w, h)
+{
+    this.x = x;
+    this.y = y;
+    this.width = w;
+    this.height = h;
+};
+
+/**
+ * Function: getCenterX
+ * 
+ * Returns the x-coordinate of the center point.
+ */
+mxRectangle.prototype.getCenterX = function ()
+{
+	return this.x + this.width/2;
+};
+
+/**
+ * Function: getCenterY
+ * 
+ * Returns the y-coordinate of the center point.
+ */
+mxRectangle.prototype.getCenterY = function ()
+{
+	return this.y + this.height/2;
+};
+
+/**
+ * Function: add
+ *
+ * Adds the given rectangle to this rectangle.
+ */
+mxRectangle.prototype.add = function(rect)
+{
+	if (rect != null)
+	{
+		var minX = Math.min(this.x, rect.x);
+		var minY = Math.min(this.y, rect.y);
+		var maxX = Math.max(this.x + this.width, rect.x + rect.width);
+		var maxY = Math.max(this.y + this.height, rect.y + rect.height);
+		
+		this.x = minX;
+		this.y = minY;
+		this.width = maxX - minX;
+		this.height = maxY - minY;
+	}
+};
+
+/**
+ * Function: intersect
+ * 
+ * Changes this rectangle to where it overlaps with the given rectangle.
+ */
+mxRectangle.prototype.intersect = function(rect)
+{
+	if (rect != null)
+	{
+		var r1 = this.x + this.width;
+		var r2 = rect.x + rect.width;
+		
+		var b1 = this.y + this.height;
+		var b2 = rect.y + rect.height;
+		
+		this.x = Math.max(this.x, rect.x);
+		this.y = Math.max(this.y, rect.y);
+		this.width = Math.min(r1, r2) - this.x;
+		this.height = Math.min(b1, b2) - this.y;
+	}
+};
+
+/**
+ * Function: grow
+ *
+ * Grows the rectangle by the given amount, that is, this method subtracts
+ * the given amount from the x- and y-coordinates and adds twice the amount
+ * to the width and height.
+ */
+mxRectangle.prototype.grow = function(amount)
+{
+	this.x -= amount;
+	this.y -= amount;
+	this.width += 2 * amount;
+	this.height += 2 * amount;
+};
+
+/**
+ * Function: getPoint
+ * 
+ * Returns the top, left corner as a new <mxPoint>.
+ */
+mxRectangle.prototype.getPoint = function()
+{
+	return new mxPoint(this.x, this.y);
+};
+
+/**
+ * Function: rotate90
+ * 
+ * Rotates this rectangle by 90 degree around its center point.
+ */
+mxRectangle.prototype.rotate90 = function()
+{
+	var t = (this.width - this.height) / 2;
+	this.x += t;
+	this.y -= t;
+	var tmp = this.width;
+	this.width = this.height;
+	this.height = tmp;
+};
+
+/**
+ * Function: equals
+ * 
+ * Returns true if the given object equals this rectangle.
+ */
+mxRectangle.prototype.equals = function(obj)
+{
+	return obj != null && obj.x == this.x && obj.y == this.y &&
+		obj.width == this.width && obj.height == this.height;
+};
+
+/**
+ * Function: fromRectangle
+ * 
+ * Returns a new <mxRectangle> which is a copy of the given rectangle.
+ */
+mxRectangle.fromRectangle = function(rect)
+{
+	return new mxRectangle(rect.x, rect.y, rect.width, rect.height);
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/util/mxResources.js b/airavata-kubernetes/workflow-composer/src/js/util/mxResources.js
new file mode 100644
index 0000000..54612b6
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/util/mxResources.js
@@ -0,0 +1,450 @@
+/**
+ * Copyright (c) 2006-2016, JGraph Ltd
+ * Copyright (c) 2006-2016, Gaudenz Alder
+ */
+var mxResources =
+{
+	/**
+	 * Class: mxResources
+	 * 
+	 * Implements internationalization. You can provide any number of 
+	 * resource files on the server using the following format for the 
+	 * filename: name[-en].properties. The en stands for any lowercase 
+	 * 2-character language shortcut (eg. de for german, fr for french).
+	 *
+	 * If the optional language extension is omitted, then the file is used as a 
+	 * default resource which is loaded in all cases. If a properties file for a 
+	 * specific language exists, then it is used to override the settings in the 
+	 * default resource. All entries in the file are of the form key=value. The
+	 * values may then be accessed in code via <get>. Lines without 
+	 * equal signs in the properties files are ignored.
+	 *
+	 * Resource files may either be added programmatically using
+	 * <add> or via a resource tag in the UI section of the 
+	 * editor configuration file, eg:
+	 * 
+	 * (code)
+	 * <mxEditor>
+	 *   <ui>
+	 *     <resource basename="examples/resources/mxWorkflow"/>
+	 * (end)
+	 * 
+	 * The above element will load examples/resources/mxWorkflow.properties as well
+	 * as the language specific file for the current language, if it exists.
+	 * 
+	 * Values may contain placeholders of the form {1}...{n} where each placeholder
+	 * is replaced with the value of the corresponding array element in the params
+	 * argument passed to <mxResources.get>. The placeholder {1} maps to the first
+	 * element in the array (at index 0).
+	 * 
+	 * See <mxClient.language> for more information on specifying the default
+	 * language or disabling all loading of resources.
+	 * 
+	 * Lines that start with a # sign will be ignored.
+	 * 
+	 * Special characters
+	 * 
+	 * To use unicode characters, use the standard notation (eg. \u8fd1) or %u as a
+	 * prefix (eg. %u20AC will display a Euro sign). For normal hex encoded strings,
+	 * use % as a prefix, eg. %F6 will display a "o umlaut" (&ouml;).
+	 * 
+	 * See <resourcesEncoded> to disable this. If you disable this, make sure that
+	 * your files are UTF-8 encoded.
+	 * 
+	 * Asynchronous loading
+	 * 
+	 * By default, the core adds two resource files synchronously at load time.
+	 * To load these files asynchronously, set <mxLoadResources> to false
+	 * before loading mxClient.js and use <mxResources.loadResources> instead.
+	 * 
+	 * Variable: resources
+	 * 
+	 * Associative array that maps from keys to values.
+	 */
+	resources: [],
+
+	/**
+	 * Variable: extension
+	 * 
+	 * Specifies the extension used for language files. Default is <mxResourceExtension>.
+	 */
+	extension: mxResourceExtension,
+
+	/**
+	 * Variable: resourcesEncoded
+	 * 
+	 * Specifies whether or not values in resource files are encoded with \u or
+	 * percentage. Default is false.
+	 */
+	resourcesEncoded: false,
+
+	/**
+	 * Variable: loadDefaultBundle
+	 * 
+	 * Specifies if the default file for a given basename should be loaded.
+	 * Default is true.
+	 */
+	loadDefaultBundle: true,
+
+	/**
+	 * Variable: loadDefaultBundle
+	 * 
+	 * Specifies if the specific language file file for a given basename should
+	 * be loaded. Default is true.
+	 */
+	loadSpecialBundle: true,
+
+	/**
+	 * Function: isLanguageSupported
+	 * 
+	 * Hook for subclassers to disable support for a given language. This
+	 * implementation returns true if lan is in <mxClient.languages>.
+	 * 
+	 * Parameters:
+	 *
+	 * lan - The current language.
+	 */
+	isLanguageSupported: function(lan)
+	{
+		if (mxClient.languages != null)
+		{
+			return mxUtils.indexOf(mxClient.languages, lan) >= 0;
+		}
+		
+		return true;
+	},
+
+	/**
+	 * Function: getDefaultBundle
+	 * 
+	 * Hook for subclassers to return the URL for the special bundle. This
+	 * implementation returns basename + <extension> or null if
+	 * <loadDefaultBundle> is false.
+	 * 
+	 * Parameters:
+	 * 
+	 * basename - The basename for which the file should be loaded.
+	 * lan - The current language.
+	 */
+	getDefaultBundle: function(basename, lan)
+	{
+		if (mxResources.loadDefaultBundle || !mxResources.isLanguageSupported(lan))
+		{
+			return basename + mxResources.extension;
+		}
+		else
+		{
+			return null;
+		}
+	},
+
+	/**
+	 * Function: getSpecialBundle
+	 * 
+	 * Hook for subclassers to return the URL for the special bundle. This
+	 * implementation returns basename + '_' + lan + <extension> or null if
+	 * <loadSpecialBundle> is false or lan equals <mxClient.defaultLanguage>.
+	 * 
+	 * If <mxResources.languages> is not null and <mxClient.language> contains
+	 * a dash, then this method checks if <isLanguageSupported> returns true
+	 * for the full language (including the dash). If that returns false the
+	 * first part of the language (up to the dash) will be tried as an extension.
+	 * 
+	 * If <mxResources.language> is null then the first part of the language is
+	 * used to maintain backwards compatibility.
+	 * 
+	 * Parameters:
+	 * 
+	 * basename - The basename for which the file should be loaded.
+	 * lan - The language for which the file should be loaded.
+	 */
+	getSpecialBundle: function(basename, lan)
+	{
+		if (mxClient.languages == null || !this.isLanguageSupported(lan))
+		{
+			var dash = lan.indexOf('-');
+			
+			if (dash > 0)
+			{
+				lan = lan.substring(0, dash);
+			}
+		}
+
+		if (mxResources.loadSpecialBundle && mxResources.isLanguageSupported(lan) && lan != mxClient.defaultLanguage)
+		{
+			return basename + '_' + lan + mxResources.extension;
+		}
+		else
+		{
+			return null;
+		}
+	},
+
+	/**
+	 * Function: add
+	 * 
+	 * Adds the default and current language properties file for the specified
+	 * basename. Existing keys are overridden as new files are added. If no
+	 * callback is used then the request is synchronous.
+	 *
+	 * Example:
+	 * 
+	 * At application startup, additional resources may be 
+	 * added using the following code:
+	 * 
+	 * (code)
+	 * mxResources.add('resources/editor');
+	 * (end)
+	 * 
+	 * Parameters:
+	 * 
+	 * basename - The basename for which the file should be loaded.
+	 * lan - The language for which the file should be loaded.
+	 * callback - Optional callback for asynchronous loading.
+	 */
+	add: function(basename, lan, callback)
+	{
+		lan = (lan != null) ? lan : ((mxClient.language != null) ?
+			mxClient.language.toLowerCase() : mxConstants.NONE);
+		
+		if (lan != mxConstants.NONE)
+		{
+			var defaultBundle = mxResources.getDefaultBundle(basename, lan);
+			var specialBundle = mxResources.getSpecialBundle(basename, lan);
+			
+			var loadSpecialBundle = function()
+			{
+				if (specialBundle != null)
+				{
+					if (callback)
+					{
+						mxUtils.get(specialBundle, function(req)
+						{
+							mxResources.parse(req.getText());
+							callback();
+						}, function()
+						{
+							callback();
+						});
+					}
+					else
+					{
+						try
+						{
+					   		var req = mxUtils.load(specialBundle);
+					   		
+					   		if (req.isReady())
+					   		{
+					 	   		mxResources.parse(req.getText());
+					   		}
+				   		}
+				   		catch (e)
+				   		{
+				   			// ignore
+					   	}
+					}
+				}
+				else if (callback != null)
+				{
+					callback();
+				}
+			}
+			
+			if (defaultBundle != null)
+			{
+				if (callback)
+				{
+					mxUtils.get(defaultBundle, function(req)
+					{
+						mxResources.parse(req.getText());
+						loadSpecialBundle();
+					}, function()
+					{
+						loadSpecialBundle();
+					});
+				}
+				else
+				{
+					try
+					{
+				   		var req = mxUtils.load(defaultBundle);
+				   		
+				   		if (req.isReady())
+				   		{
+				 	   		mxResources.parse(req.getText());
+				   		}
+				   		
+				   		loadSpecialBundle();
+				  	}
+				  	catch (e)
+				  	{
+				  		// ignore
+				  	}
+				}
+			}
+			else
+			{
+				// Overlays the language specific file (_lan-extension)
+				loadSpecialBundle();
+			}
+		}
+	},
+
+	/**
+	 * Function: parse
+	 * 
+	 * Parses the key, value pairs in the specified
+	 * text and stores them as local resources.
+	 */
+	parse: function(text)
+	{
+		if (text != null)
+		{
+			var lines = text.split('\n');
+			
+			for (var i = 0; i < lines.length; i++)
+			{
+				if (lines[i].charAt(0) != '#')
+				{
+					var index = lines[i].indexOf('=');
+					
+					if (index > 0)
+					{
+						var key = lines[i].substring(0, index);
+						var idx = lines[i].length;
+						
+						if (lines[i].charCodeAt(idx - 1) == 13)
+						{
+							idx--;
+						}
+						
+						var value = lines[i].substring(index + 1, idx);
+						
+						if (this.resourcesEncoded)
+						{
+							value = value.replace(/\\(?=u[a-fA-F\d]{4})/g,"%");
+							mxResources.resources[key] = unescape(value);
+						}
+						else
+						{
+							mxResources.resources[key] = value;
+						}
+					}
+				}
+			}
+		}
+	},
+
+	/**
+	 * Function: get
+	 * 
+	 * Returns the value for the specified resource key.
+	 *
+	 * Example:
+	 * To read the value for 'welomeMessage', use the following:
+	 * (code)
+	 * var result = mxResources.get('welcomeMessage') || '';
+	 * (end)
+	 *
+	 * This would require an entry of the following form in
+	 * one of the English language resource files:
+	 * (code)
+	 * welcomeMessage=Welcome to mxGraph!
+	 * (end)
+	 * 
+	 * The part behind the || is the string value to be used if the given
+	 * resource is not available.
+	 * 
+	 * Parameters:
+	 * 
+	 * key - String that represents the key of the resource to be returned.
+	 * params - Array of the values for the placeholders of the form {1}...{n}
+	 * to be replaced with in the resulting string.
+	 * defaultValue - Optional string that specifies the default return value.
+	 */
+	get: function(key, params, defaultValue)
+	{
+		var value = mxResources.resources[key];
+		
+		// Applies the default value if no resource was found
+		if (value == null)
+		{
+			value = defaultValue;
+		}
+		
+		// Replaces the placeholders with the values in the array
+		if (value != null && params != null)
+		{
+			value = mxResources.replacePlaceholders(value, params);
+		}
+		
+		return value;
+	},
+
+	/**
+	 * Function: replacePlaceholders
+	 * 
+	 * Replaces the given placeholders with the given parameters.
+	 * 
+	 * Parameters:
+	 * 
+	 * value - String that contains the placeholders.
+	 * params - Array of the values for the placeholders of the form {1}...{n}
+	 * to be replaced with in the resulting string.
+	 */
+	replacePlaceholders: function(value, params)
+	{
+		var result = [];
+		var index = null;
+		
+		for (var i = 0; i < value.length; i++)
+		{
+			var c = value.charAt(i);
+
+			if (c == '{')
+			{
+				index = '';
+			}
+			else if (index != null && 	c == '}')
+			{
+				index = parseInt(index)-1;
+				
+				if (index >= 0 && index < params.length)
+				{
+					result.push(params[index]);
+				}
+				
+				index = null;
+			}
+			else if (index != null)
+			{
+				index += c;
+			}
+			else
+			{
+				result.push(c);
+			}
+		}
+		
+		return result.join('');
+	},
+
+	/**
+	 * Function: loadResources
+	 * 
+	 * Loads all required resources asynchronously. Use this to load the graph and
+	 * editor resources if <mxLoadResources> is false.
+	 * 
+	 * Parameters:
+	 * 
+	 * callback - Callback function for asynchronous loading.
+	 */
+	loadResources: function(callback)
+	{
+		mxResources.add(mxClient.basePath+'/resources/editor', null, function()
+		{
+			mxResources.add(mxClient.basePath+'/resources/graph', null, callback);
+		});
+	}
+
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/util/mxSvgCanvas2D.js b/airavata-kubernetes/workflow-composer/src/js/util/mxSvgCanvas2D.js
new file mode 100644
index 0000000..28ae6d9
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/util/mxSvgCanvas2D.js
@@ -0,0 +1,2179 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxSvgCanvas2D
+ *
+ * Extends <mxAbstractCanvas2D> to implement a canvas for SVG. This canvas writes all
+ * calls as SVG output to the given SVG root node.
+ * 
+ * (code)
+ * var svgDoc = mxUtils.createXmlDocument();
+ * var root = (svgDoc.createElementNS != null) ?
+ * 		svgDoc.createElementNS(mxConstants.NS_SVG, 'svg') : svgDoc.createElement('svg');
+ * 
+ * if (svgDoc.createElementNS == null)
+ * {
+ *   root.setAttribute('xmlns', mxConstants.NS_SVG);
+ *   root.setAttribute('xmlns:xlink', mxConstants.NS_XLINK);
+ * }
+ * else
+ * {
+ *   root.setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xlink', mxConstants.NS_XLINK);
+ * }
+ * 
+ * var bounds = graph.getGraphBounds();
+ * root.setAttribute('width', (bounds.x + bounds.width + 4) + 'px');
+ * root.setAttribute('height', (bounds.y + bounds.height + 4) + 'px');
+ * root.setAttribute('version', '1.1');
+ * 
+ * svgDoc.appendChild(root);
+ * 
+ * var svgCanvas = new mxSvgCanvas2D(root);
+ * (end)
+ * 
+ * A description of the public API is available in <mxXmlCanvas2D>.
+ * 
+ * To disable anti-aliasing in the output, use the following code.
+ * 
+ * (code)
+ * graph.view.canvas.ownerSVGElement.setAttribute('shape-rendering', 'crispEdges');
+ * (end)
+ * 
+ * Or set the respective attribute in the SVG element directly.
+ * 
+ * Constructor: mxSvgCanvas2D
+ *
+ * Constructs a new SVG canvas.
+ * 
+ * Parameters:
+ * 
+ * root - SVG container for the output.
+ * styleEnabled - Optional boolean that specifies if a style section should be
+ * added. The style section sets the default font-size, font-family and
+ * stroke-miterlimit globally. Default is false.
+ */
+function mxSvgCanvas2D(root, styleEnabled)
+{
+	mxAbstractCanvas2D.call(this);
+
+	/**
+	 * Variable: root
+	 * 
+	 * Reference to the container for the SVG content.
+	 */
+	this.root = root;
+
+	/**
+	 * Variable: gradients
+	 * 
+	 * Local cache of gradients for quick lookups.
+	 */
+	this.gradients = [];
+
+	/**
+	 * Variable: defs
+	 * 
+	 * Reference to the defs section of the SVG document. Only for export.
+	 */
+	this.defs = null;
+	
+	/**
+	 * Variable: styleEnabled
+	 * 
+	 * Stores the value of styleEnabled passed to the constructor.
+	 */
+	this.styleEnabled = (styleEnabled != null) ? styleEnabled : false;
+	
+	var svg = null;
+	
+	// Adds optional defs section for export
+	if (root.ownerDocument != document)
+	{
+		var node = root;
+
+		// Finds owner SVG element in XML DOM
+		while (node != null && node.nodeName != 'svg')
+		{
+			node = node.parentNode;
+		}
+		
+		svg = node;
+	}
+
+	if (svg != null)
+	{
+		// Tries to get existing defs section
+		var tmp = svg.getElementsByTagName('defs');
+		
+		if (tmp.length > 0)
+		{
+			this.defs = svg.getElementsByTagName('defs')[0];
+		}
+		
+		// Adds defs section if none exists
+		if (this.defs == null)
+		{
+			this.defs = this.createElement('defs');
+			
+			if (svg.firstChild != null)
+			{
+				svg.insertBefore(this.defs, svg.firstChild);
+			}
+			else
+			{
+				svg.appendChild(this.defs);
+			}
+		}
+
+		// Adds stylesheet
+		if (this.styleEnabled)
+		{
+			this.defs.appendChild(this.createStyle());
+		}
+	}
+};
+
+/**
+ * Extends mxAbstractCanvas2D
+ */
+mxUtils.extend(mxSvgCanvas2D, mxAbstractCanvas2D);
+
+/**
+ * Capability check for DOM parser.
+ */
+(function()
+{
+	mxSvgCanvas2D.prototype.useDomParser = !mxClient.IS_IE && typeof DOMParser === 'function' && typeof XMLSerializer === 'function';
+	
+	if (mxSvgCanvas2D.prototype.useDomParser)
+	{
+		// Checks using a generic test text if the parsing actually works. This is a workaround
+		// for older browsers where the capability check returns true but the parsing fails.
+		try
+		{
+			var doc = new DOMParser().parseFromString('test text', 'text/html');
+			mxSvgCanvas2D.prototype.useDomParser = doc != null;
+		}
+		catch (e)
+		{
+			mxSvgCanvas2D.prototype.useDomParser = false;
+		}
+	}
+})();
+
+/**
+ * Variable: path
+ * 
+ * Holds the current DOM node.
+ */
+mxSvgCanvas2D.prototype.node = null;
+
+/**
+ * Variable: matchHtmlAlignment
+ * 
+ * Specifies if plain text output should match the vertical HTML alignment.
+ * Defaul is true.
+ */
+mxSvgCanvas2D.prototype.matchHtmlAlignment = true;
+
+/**
+ * Variable: textEnabled
+ * 
+ * Specifies if text output should be enabled. Default is true.
+ */
+mxSvgCanvas2D.prototype.textEnabled = true;
+
+/**
+ * Variable: foEnabled
+ * 
+ * Specifies if use of foreignObject for HTML markup is allowed. Default is true.
+ */
+mxSvgCanvas2D.prototype.foEnabled = true;
+
+/**
+ * Variable: foAltText
+ * 
+ * Specifies the fallback text for unsupported foreignObjects in exported
+ * documents. Default is '[Object]'. If this is set to null then no fallback
+ * text is added to the exported document.
+ */
+mxSvgCanvas2D.prototype.foAltText = '[Object]';
+
+/**
+ * Variable: foOffset
+ * 
+ * Offset to be used for foreignObjects.
+ */
+mxSvgCanvas2D.prototype.foOffset = 0;
+
+/**
+ * Variable: textOffset
+ * 
+ * Offset to be used for text elements.
+ */
+mxSvgCanvas2D.prototype.textOffset = 0;
+
+/**
+ * Variable: imageOffset
+ * 
+ * Offset to be used for image elements.
+ */
+mxSvgCanvas2D.prototype.imageOffset = 0;
+
+/**
+ * Variable: strokeTolerance
+ * 
+ * Adds transparent paths for strokes.
+ */
+mxSvgCanvas2D.prototype.strokeTolerance = 0;
+
+/**
+ * Variable: refCount
+ * 
+ * Local counter for references in SVG export.
+ */
+mxSvgCanvas2D.prototype.refCount = 0;
+
+/**
+ * Variable: blockImagePointerEvents
+ * 
+ * Specifies if a transparent rectangle should be added on top of images to absorb
+ * all pointer events. Default is false. This is only needed in Firefox to disable
+ * control-clicks on images.
+ */
+mxSvgCanvas2D.prototype.blockImagePointerEvents = false;
+
+/**
+ * Variable: lineHeightCorrection
+ * 
+ * Correction factor for <mxConstants.LINE_HEIGHT> in HTML output. Default is 1.
+ */
+mxSvgCanvas2D.prototype.lineHeightCorrection = 1;
+
+/**
+ * Variable: pointerEventsValue
+ * 
+ * Default value for active pointer events. Default is all.
+ */
+mxSvgCanvas2D.prototype.pointerEventsValue = 'all';
+
+/**
+ * Variable: fontMetricsPadding
+ * 
+ * Padding to be added for text that is not wrapped to account for differences
+ * in font metrics on different platforms in pixels. Default is 10.
+ */
+mxSvgCanvas2D.prototype.fontMetricsPadding = 10;
+
+/**
+ * Variable: cacheOffsetSize
+ * 
+ * Specifies if offsetWidth and offsetHeight should be cached. Default is true.
+ * This is used to speed up repaint of text in <updateText>.
+ */
+mxSvgCanvas2D.prototype.cacheOffsetSize = true;
+
+/**
+ * Function: format
+ * 
+ * Rounds all numbers to 2 decimal points.
+ */
+mxSvgCanvas2D.prototype.format = function(value)
+{
+	return parseFloat(parseFloat(value).toFixed(2));
+};
+
+/**
+ * Function: getBaseUrl
+ * 
+ * Returns the URL of the page without the hash part. This needs to use href to
+ * include any search part with no params (ie question mark alone). This is a
+ * workaround for the fact that window.location.search is empty if there is
+ * no search string behind the question mark.
+ */
+mxSvgCanvas2D.prototype.getBaseUrl = function()
+{
+	var href = window.location.href;
+	var hash = href.lastIndexOf('#');
+	
+	if (hash > 0)
+	{
+		href = href.substring(0, hash);
+	}
+	
+	return href;
+};
+
+/**
+ * Function: reset
+ * 
+ * Returns any offsets for rendering pixels.
+ */
+mxSvgCanvas2D.prototype.reset = function()
+{
+	mxAbstractCanvas2D.prototype.reset.apply(this, arguments);
+	this.gradients = [];
+};
+
+/**
+ * Function: createStyle
+ * 
+ * Creates the optional style section.
+ */
+mxSvgCanvas2D.prototype.createStyle = function(x)
+{
+	var style = this.createElement('style');
+	style.setAttribute('type', 'text/css');
+	mxUtils.write(style, 'svg{font-family:' + mxConstants.DEFAULT_FONTFAMILY +
+			';font-size:' + mxConstants.DEFAULT_FONTSIZE +
+			';fill:none;stroke-miterlimit:10}');
+	
+	return style;
+};
+
+/**
+ * Function: createElement
+ * 
+ * Private helper function to create SVG elements
+ */
+mxSvgCanvas2D.prototype.createElement = function(tagName, namespace)
+{
+	if (this.root.ownerDocument.createElementNS != null)
+	{
+		return this.root.ownerDocument.createElementNS(namespace || mxConstants.NS_SVG, tagName);
+	}
+	else
+	{
+		var elt = this.root.ownerDocument.createElement(tagName);
+		
+		if (namespace != null)
+		{
+			elt.setAttribute('xmlns', namespace);
+		}
+		
+		return elt;
+	}
+};
+
+/**
+ * Function: getAlternateContent
+ * 
+ * Returns the alternate content for the given foreignObject.
+ */
+mxSvgCanvas2D.prototype.createAlternateContent = function(fo, x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation)
+{
+	if (this.foAltText != null)
+	{
+		var s = this.state;
+		var alt = this.createElement('text');
+		alt.setAttribute('x', Math.round(w / 2));
+		alt.setAttribute('y', Math.round((h + s.fontSize) / 2));
+		alt.setAttribute('fill', s.fontColor || 'black');
+		alt.setAttribute('text-anchor', 'middle');
+		alt.setAttribute('font-size', s.fontSize + 'px');
+		alt.setAttribute('font-family', s.fontFamily);
+		
+		if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
+		{
+			alt.setAttribute('font-weight', 'bold');
+		}
+		
+		if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
+		{
+			alt.setAttribute('font-style', 'italic');
+		}
+		
+		if ((s.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)
+		{
+			alt.setAttribute('text-decoration', 'underline');
+		}
+		
+		mxUtils.write(alt, this.foAltText);
+		
+		return alt;
+	}
+	else
+	{
+		return null;
+	}
+};
+
+/**
+ * Function: createGradientId
+ * 
+ * Private helper function to create SVG elements
+ */
+mxSvgCanvas2D.prototype.createGradientId = function(start, end, alpha1, alpha2, direction)
+{
+	// Removes illegal characters from gradient ID
+	if (start.charAt(0) == '#')
+	{
+		start = start.substring(1);
+	}
+	
+	if (end.charAt(0) == '#')
+	{
+		end = end.substring(1);
+	}
+	
+	// Workaround for gradient IDs not working in Safari 5 / Chrome 6
+	// if they contain uppercase characters
+	start = start.toLowerCase() + '-' + alpha1;
+	end = end.toLowerCase() + '-' + alpha2;
+
+	// Wrong gradient directions possible?
+	var dir = null;
+	
+	if (direction == null || direction == mxConstants.DIRECTION_SOUTH)
+	{
+		dir = 's';
+	}
+	else if (direction == mxConstants.DIRECTION_EAST)
+	{
+		dir = 'e';
+	}
+	else
+	{
+		var tmp = start;
+		start = end;
+		end = tmp;
+		
+		if (direction == mxConstants.DIRECTION_NORTH)
+		{
+			dir = 's';
+		}
+		else if (direction == mxConstants.DIRECTION_WEST)
+		{
+			dir = 'e';
+		}
+	}
+	
+	return 'mx-gradient-' + start + '-' + end + '-' + dir;
+};
+
+/**
+ * Function: getSvgGradient
+ * 
+ * Private helper function to create SVG elements
+ */
+mxSvgCanvas2D.prototype.getSvgGradient = function(start, end, alpha1, alpha2, direction)
+{
+	var id = this.createGradientId(start, end, alpha1, alpha2, direction);
+	var gradient = this.gradients[id];
+	
+	if (gradient == null)
+	{
+		var svg = this.root.ownerSVGElement;
+
+		var counter = 0;
+		var tmpId = id + '-' + counter;
+
+		if (svg != null)
+		{
+			gradient = svg.ownerDocument.getElementById(tmpId);
+			
+			while (gradient != null && gradient.ownerSVGElement != svg)
+			{
+				tmpId = id + '-' + counter++;
+				gradient = svg.ownerDocument.getElementById(tmpId);
+			}
+		}
+		else
+		{
+			// Uses shorter IDs for export
+			tmpId = 'id' + (++this.refCount);
+		}
+		
+		if (gradient == null)
+		{
+			gradient = this.createSvgGradient(start, end, alpha1, alpha2, direction);
+			gradient.setAttribute('id', tmpId);
+			
+			if (this.defs != null)
+			{
+				this.defs.appendChild(gradient);
+			}
+			else
+			{
+				svg.appendChild(gradient);
+			}
+		}
+
+		this.gradients[id] = gradient;
+	}
+
+	return gradient.getAttribute('id');
+};
+
+/**
+ * Function: createSvgGradient
+ * 
+ * Creates the given SVG gradient.
+ */
+mxSvgCanvas2D.prototype.createSvgGradient = function(start, end, alpha1, alpha2, direction)
+{
+	var gradient = this.createElement('linearGradient');
+	gradient.setAttribute('x1', '0%');
+	gradient.setAttribute('y1', '0%');
+	gradient.setAttribute('x2', '0%');
+	gradient.setAttribute('y2', '0%');
+	
+	if (direction == null || direction == mxConstants.DIRECTION_SOUTH)
+	{
+		gradient.setAttribute('y2', '100%');
+	}
+	else if (direction == mxConstants.DIRECTION_EAST)
+	{
+		gradient.setAttribute('x2', '100%');
+	}
+	else if (direction == mxConstants.DIRECTION_NORTH)
+	{
+		gradient.setAttribute('y1', '100%');
+	}
+	else if (direction == mxConstants.DIRECTION_WEST)
+	{
+		gradient.setAttribute('x1', '100%');
+	}
+	
+	var op = (alpha1 < 1) ? ';stop-opacity:' + alpha1 : '';
+	
+	var stop = this.createElement('stop');
+	stop.setAttribute('offset', '0%');
+	stop.setAttribute('style', 'stop-color:' + start + op);
+	gradient.appendChild(stop);
+	
+	op = (alpha2 < 1) ? ';stop-opacity:' + alpha2 : '';
+	
+	stop = this.createElement('stop');
+	stop.setAttribute('offset', '100%');
+	stop.setAttribute('style', 'stop-color:' + end + op);
+	gradient.appendChild(stop);
+	
+	return gradient;
+};
+
+/**
+ * Function: addNode
+ * 
+ * Private helper function to create SVG elements
+ */
+mxSvgCanvas2D.prototype.addNode = function(filled, stroked)
+{
+	var node = this.node;
+	var s = this.state;
+
+	if (node != null)
+	{
+		if (node.nodeName == 'path')
+		{
+			// Checks if the path is not empty
+			if (this.path != null && this.path.length > 0)
+			{
+				node.setAttribute('d', this.path.join(' '));
+			}
+			else
+			{
+				return;
+			}
+		}
+
+		if (filled && s.fillColor != null)
+		{
+			this.updateFill();
+		}
+		else if (!this.styleEnabled)
+		{
+			// Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=814952
+			if (node.nodeName == 'ellipse' && mxClient.IS_FF)
+			{
+				node.setAttribute('fill', 'transparent');
+			}
+			else
+			{
+				node.setAttribute('fill', 'none');
+			}
+			
+			// Sets the actual filled state for stroke tolerance
+			filled = false;
+		}
+		
+		if (stroked && s.strokeColor != null)
+		{
+			this.updateStroke();
+		}
+		else if (!this.styleEnabled)
+		{
+			node.setAttribute('stroke', 'none');
+		}
+		
+		if (s.transform != null && s.transform.length > 0)
+		{
+			node.setAttribute('transform', s.transform);
+		}
+		
+		if (s.shadow)
+		{
+			this.root.appendChild(this.createShadow(node));
+		}
+	
+		// Adds stroke tolerance
+		if (this.strokeTolerance > 0 && !filled)
+		{
+			this.root.appendChild(this.createTolerance(node));
+		}
+
+		// Adds pointer events
+		if (this.pointerEvents && (node.nodeName != 'path' ||
+			this.path[this.path.length - 1] == this.closeOp))
+		{
+			node.setAttribute('pointer-events', this.pointerEventsValue);
+		}
+		// Enables clicks for nodes inside a link element
+		else if (!this.pointerEvents && this.originalRoot == null)
+		{
+			node.setAttribute('pointer-events', 'none');
+		}
+		
+		// Removes invisible nodes from output if they don't handle events
+		if ((node.nodeName != 'rect' && node.nodeName != 'path' && node.nodeName != 'ellipse') ||
+			(node.getAttribute('fill') != 'none' && node.getAttribute('fill') != 'transparent') ||
+			node.getAttribute('stroke') != 'none' || node.getAttribute('pointer-events') != 'none')
+		{
+			// LATER: Update existing DOM for performance		
+			this.root.appendChild(node);
+		}
+		
+		this.node = null;
+	}
+};
+
+/**
+ * Function: updateFill
+ * 
+ * Transfers the stroke attributes from <state> to <node>.
+ */
+mxSvgCanvas2D.prototype.updateFill = function()
+{
+	var s = this.state;
+	
+	if (s.alpha < 1 || s.fillAlpha < 1)
+	{
+		this.node.setAttribute('fill-opacity', s.alpha * s.fillAlpha);
+	}
+	
+	if (s.fillColor != null)
+	{
+		if (s.gradientColor != null)
+		{
+			var id = this.getSvgGradient(s.fillColor, s.gradientColor, s.gradientFillAlpha, s.gradientAlpha, s.gradientDirection);
+			
+			if (!mxClient.IS_CHROME_APP && !mxClient.IS_IE && !mxClient.IS_IE11 &&
+				!mxClient.IS_EDGE && this.root.ownerDocument == document)
+			{
+				// Workaround for potential base tag and brackets must be escaped
+				var base = this.getBaseUrl().replace(/([\(\)])/g, '\\$1');
+				this.node.setAttribute('fill', 'url(' + base + '#' + id + ')');
+			}
+			else
+			{
+				this.node.setAttribute('fill', 'url(#' + id + ')');
+			}
+		}
+		else
+		{
+			this.node.setAttribute('fill', s.fillColor.toLowerCase());
+		}
+	}
+};
+
+/**
+ * Function: getCurrentStrokeWidth
+ * 
+ * Returns the current stroke width (>= 1), ie. max(1, this.format(this.state.strokeWidth * this.state.scale)).
+ */
+mxSvgCanvas2D.prototype.getCurrentStrokeWidth = function()
+{
+	return Math.max(1, this.format(this.state.strokeWidth * this.state.scale));
+};
+
+/**
+ * Function: updateStroke
+ * 
+ * Transfers the stroke attributes from <state> to <node>.
+ */
+mxSvgCanvas2D.prototype.updateStroke = function()
+{
+	var s = this.state;
+
+	this.node.setAttribute('stroke', s.strokeColor.toLowerCase());
+	
+	if (s.alpha < 1 || s.strokeAlpha < 1)
+	{
+		this.node.setAttribute('stroke-opacity', s.alpha * s.strokeAlpha);
+	}
+	
+	var sw = this.getCurrentStrokeWidth();
+	
+	if (sw != 1)
+	{
+		this.node.setAttribute('stroke-width', sw);
+	}
+	
+	if (this.node.nodeName == 'path')
+	{
+		this.updateStrokeAttributes();
+	}
+	
+	if (s.dashed)
+	{
+		this.node.setAttribute('stroke-dasharray', this.createDashPattern(
+			((s.fixDash) ? 1 : s.strokeWidth) * s.scale));
+	}
+};
+
+/**
+ * Function: updateStrokeAttributes
+ * 
+ * Transfers the stroke attributes from <state> to <node>.
+ */
+mxSvgCanvas2D.prototype.updateStrokeAttributes = function()
+{
+	var s = this.state;
+	
+	// Linejoin miter is default in SVG
+	if (s.lineJoin != null && s.lineJoin != 'miter')
+	{
+		this.node.setAttribute('stroke-linejoin', s.lineJoin);
+	}
+	
+	if (s.lineCap != null)
+	{
+		// flat is called butt in SVG
+		var value = s.lineCap;
+		
+		if (value == 'flat')
+		{
+			value = 'butt';
+		}
+		
+		// Linecap butt is default in SVG
+		if (value != 'butt')
+		{
+			this.node.setAttribute('stroke-linecap', value);
+		}
+	}
+	
+	// Miterlimit 10 is default in our document
+	if (s.miterLimit != null && (!this.styleEnabled || s.miterLimit != 10))
+	{
+		this.node.setAttribute('stroke-miterlimit', s.miterLimit);
+	}
+};
+
+/**
+ * Function: createDashPattern
+ * 
+ * Creates the SVG dash pattern for the given state.
+ */
+mxSvgCanvas2D.prototype.createDashPattern = function(scale)
+{
+	var pat = [];
+	
+	if (typeof(this.state.dashPattern) === 'string')
+	{
+		var dash = this.state.dashPattern.split(' ');
+		
+		if (dash.length > 0)
+		{
+			for (var i = 0; i < dash.length; i++)
+			{
+				pat[i] = Number(dash[i]) * scale;
+			}
+		}
+	}
+	
+	return pat.join(' ');
+};
+
+/**
+ * Function: createTolerance
+ * 
+ * Creates a hit detection tolerance shape for the given node.
+ */
+mxSvgCanvas2D.prototype.createTolerance = function(node)
+{
+	var tol = node.cloneNode(true);
+	var sw = parseFloat(tol.getAttribute('stroke-width') || 1) + this.strokeTolerance;
+	tol.setAttribute('pointer-events', 'stroke');
+	tol.setAttribute('visibility', 'hidden');
+	tol.removeAttribute('stroke-dasharray');
+	tol.setAttribute('stroke-width', sw);
+	tol.setAttribute('fill', 'none');
+	
+	// Workaround for Opera ignoring the visiblity attribute above while
+	// other browsers need a stroke color to perform the hit-detection but
+	// do not ignore the visibility attribute. Side-effect is that Opera's
+	// hit detection for horizontal/vertical edges seems to ignore the tol.
+	tol.setAttribute('stroke', (mxClient.IS_OT) ? 'none' : 'white');
+	
+	return tol;
+};
+
+/**
+ * Function: createShadow
+ * 
+ * Creates a shadow for the given node.
+ */
+mxSvgCanvas2D.prototype.createShadow = function(node)
+{
+	var shadow = node.cloneNode(true);
+	var s = this.state;
+
+	// Firefox uses transparent for no fill in ellipses
+	if (shadow.getAttribute('fill') != 'none' && (!mxClient.IS_FF || shadow.getAttribute('fill') != 'transparent'))
+	{
+		shadow.setAttribute('fill', s.shadowColor);
+	}
+	
+	if (shadow.getAttribute('stroke') != 'none')
+	{
+		shadow.setAttribute('stroke', s.shadowColor);
+	}
+
+	shadow.setAttribute('transform', 'translate(' + this.format(s.shadowDx * s.scale) +
+		',' + this.format(s.shadowDy * s.scale) + ')' + (s.transform || ''));
+	shadow.setAttribute('opacity', s.shadowAlpha);
+	
+	return shadow;
+};
+
+/**
+ * Function: setLink
+ * 
+ * Experimental implementation for hyperlinks.
+ */
+mxSvgCanvas2D.prototype.setLink = function(link)
+{
+	if (link == null)
+	{
+		this.root = this.originalRoot;
+	}
+	else
+	{
+		this.originalRoot = this.root;
+		
+		var node = this.createElement('a');
+		
+		// Workaround for implicit namespace handling in HTML5 export, IE adds NS1 namespace so use code below
+		// in all IE versions except quirks mode. KNOWN: Adds xlink namespace to each image tag in output.
+		if (node.setAttributeNS == null || (this.root.ownerDocument != document && document.documentMode == null))
+		{
+			node.setAttribute('xlink:href', link);
+		}
+		else
+		{
+			node.setAttributeNS(mxConstants.NS_XLINK, 'xlink:href', link);
+		}
+		
+		this.root.appendChild(node);
+		this.root = node;
+	}
+};
+
+/**
+ * Function: rotate
+ * 
+ * Sets the rotation of the canvas. Note that rotation cannot be concatenated.
+ */
+mxSvgCanvas2D.prototype.rotate = function(theta, flipH, flipV, cx, cy)
+{
+	if (theta != 0 || flipH || flipV)
+	{
+		var s = this.state;
+		cx += s.dx;
+		cy += s.dy;
+	
+		cx *= s.scale;
+		cy *= s.scale;
+
+		s.transform = s.transform || '';
+		
+		// This implementation uses custom scale/translate and built-in rotation
+		// Rotation state is part of the AffineTransform in state.transform
+		if (flipH && flipV)
+		{
+			theta += 180;
+		}
+		else if (flipH != flipV)
+		{
+			var tx = (flipH) ? cx : 0;
+			var sx = (flipH) ? -1 : 1;
+	
+			var ty = (flipV) ? cy : 0;
+			var sy = (flipV) ? -1 : 1;
+
+			s.transform += 'translate(' + this.format(tx) + ',' + this.format(ty) + ')' +
+				'scale(' + this.format(sx) + ',' + this.format(sy) + ')' +
+				'translate(' + this.format(-tx) + ',' + this.format(-ty) + ')';
+		}
+		
+		if (flipH ? !flipV : flipV)
+		{
+			theta *= -1;
+		}
+		
+		if (theta != 0)
+		{
+			s.transform += 'rotate(' + this.format(theta) + ',' + this.format(cx) + ',' + this.format(cy) + ')';
+		}
+		
+		s.rotation = s.rotation + theta;
+		s.rotationCx = cx;
+		s.rotationCy = cy;
+	}
+};
+
+/**
+ * Function: begin
+ * 
+ * Extends superclass to create path.
+ */
+mxSvgCanvas2D.prototype.begin = function()
+{
+	mxAbstractCanvas2D.prototype.begin.apply(this, arguments);
+	this.node = this.createElement('path');
+};
+
+/**
+ * Function: rect
+ * 
+ * Private helper function to create SVG elements
+ */
+mxSvgCanvas2D.prototype.rect = function(x, y, w, h)
+{
+	var s = this.state;
+	var n = this.createElement('rect');
+	n.setAttribute('x', this.format((x + s.dx) * s.scale));
+	n.setAttribute('y', this.format((y + s.dy) * s.scale));
+	n.setAttribute('width', this.format(w * s.scale));
+	n.setAttribute('height', this.format(h * s.scale));
+	
+	this.node = n;
+};
+
+/**
+ * Function: roundrect
+ * 
+ * Private helper function to create SVG elements
+ */
+mxSvgCanvas2D.prototype.roundrect = function(x, y, w, h, dx, dy)
+{
+	this.rect(x, y, w, h);
+	
+	if (dx > 0)
+	{
+		this.node.setAttribute('rx', this.format(dx * this.state.scale));
+	}
+	
+	if (dy > 0)
+	{
+		this.node.setAttribute('ry', this.format(dy * this.state.scale));
+	}
+};
+
+/**
+ * Function: ellipse
+ * 
+ * Private helper function to create SVG elements
+ */
+mxSvgCanvas2D.prototype.ellipse = function(x, y, w, h)
+{
+	var s = this.state;
+	var n = this.createElement('ellipse');
+	// No rounding for consistent output with 1.x
+	n.setAttribute('cx', Math.round((x + w / 2 + s.dx) * s.scale));
+	n.setAttribute('cy', Math.round((y + h / 2 + s.dy) * s.scale));
+	n.setAttribute('rx', w / 2 * s.scale);
+	n.setAttribute('ry', h / 2 * s.scale);
+	this.node = n;
+};
+
+/**
+ * Function: image
+ * 
+ * Private helper function to create SVG elements
+ */
+mxSvgCanvas2D.prototype.image = function(x, y, w, h, src, aspect, flipH, flipV)
+{
+	src = this.converter.convert(src);
+	
+	// LATER: Add option for embedding images as base64.
+	aspect = (aspect != null) ? aspect : true;
+	flipH = (flipH != null) ? flipH : false;
+	flipV = (flipV != null) ? flipV : false;
+	
+	var s = this.state;
+	x += s.dx;
+	y += s.dy;
+	
+	var node = this.createElement('image');
+	node.setAttribute('x', this.format(x * s.scale) + this.imageOffset);
+	node.setAttribute('y', this.format(y * s.scale) + this.imageOffset);
+	node.setAttribute('width', this.format(w * s.scale));
+	node.setAttribute('height', this.format(h * s.scale));
+	
+	// Workaround for missing namespace support
+	if (node.setAttributeNS == null)
+	{
+		node.setAttribute('xlink:href', src);
+	}
+	else
+	{
+		node.setAttributeNS(mxConstants.NS_XLINK, 'xlink:href', src);
+	}
+	
+	if (!aspect)
+	{
+		node.setAttribute('preserveAspectRatio', 'none');
+	}
+
+	if (s.alpha < 1 || s.fillAlpha < 1)
+	{
+		node.setAttribute('opacity', s.alpha * s.fillAlpha);
+	}
+	
+	var tr = this.state.transform || '';
+	
+	if (flipH || flipV)
+	{
+		var sx = 1;
+		var sy = 1;
+		var dx = 0;
+		var dy = 0;
+		
+		if (flipH)
+		{
+			sx = -1;
+			dx = -w - 2 * x;
+		}
+		
+		if (flipV)
+		{
+			sy = -1;
+			dy = -h - 2 * y;
+		}
+		
+		// Adds image tansformation to existing transform
+		tr += 'scale(' + sx + ',' + sy + ')translate(' + (dx * s.scale) + ',' + (dy * s.scale) + ')';
+	}
+
+	if (tr.length > 0)
+	{
+		node.setAttribute('transform', tr);
+	}
+	
+	if (!this.pointerEvents)
+	{
+		node.setAttribute('pointer-events', 'none');
+	}
+	
+	this.root.appendChild(node);
+	
+	// Disables control-clicks on images in Firefox to open in new tab
+	// by putting a rect in the foreground that absorbs all events and
+	// disabling all pointer-events on the original image tag.
+	if (this.blockImagePointerEvents)
+	{
+		node.setAttribute('style', 'pointer-events:none');
+		
+		node = this.createElement('rect');
+		node.setAttribute('visibility', 'hidden');
+		node.setAttribute('pointer-events', 'fill');
+		node.setAttribute('x', this.format(x * s.scale));
+		node.setAttribute('y', this.format(y * s.scale));
+		node.setAttribute('width', this.format(w * s.scale));
+		node.setAttribute('height', this.format(h * s.scale));
+		this.root.appendChild(node);
+	}
+};
+
+/**
+ * Function: convertHtml
+ * 
+ * Converts the given HTML string to XHTML.
+ */
+mxSvgCanvas2D.prototype.convertHtml = function(val)
+{
+	if (this.useDomParser)
+	{
+		var doc = new DOMParser().parseFromString(val, 'text/html');
+
+		if (doc != null)
+		{
+			val = new XMLSerializer().serializeToString(doc.body);
+			
+			// Extracts body content from DOM
+			if (val.substring(0, 5) == '<body')
+			{
+				val = val.substring(val.indexOf('>', 5) + 1);
+			}
+			
+			if (val.substring(val.length - 7, val.length) == '</body>')
+			{
+				val = val.substring(0, val.length - 7);
+			}
+		}
+	}
+	else if (document.implementation != null && document.implementation.createDocument != null)
+	{
+		var xd = document.implementation.createDocument('http://www.w3.org/1999/xhtml', 'html', null);
+		var xb = xd.createElement('body');
+		xd.documentElement.appendChild(xb);
+		
+		var div = document.createElement('div');
+		div.innerHTML = val;
+		var child = div.firstChild;
+		
+		while (child != null)
+		{
+			var next = child.nextSibling;
+			xb.appendChild(xd.adoptNode(child));
+			child = next;
+		}
+		
+		return xb.innerHTML;
+	}
+	else
+	{
+		var ta = document.createElement('textarea');
+		
+		// Handles special HTML entities < and > and double escaping
+		// and converts unclosed br, hr and img tags to XHTML
+		// LATER: Convert all unclosed tags
+		ta.innerHTML = val.replace(/&amp;/g, '&amp;amp;').
+			replace(/&#60;/g, '&amp;lt;').replace(/&#62;/g, '&amp;gt;').
+			replace(/&lt;/g, '&amp;lt;').replace(/&gt;/g, '&amp;gt;').
+			replace(/</g, '&lt;').replace(/>/g, '&gt;');
+		val = ta.value.replace(/&/g, '&amp;').replace(/&amp;lt;/g, '&lt;').
+			replace(/&amp;gt;/g, '&gt;').replace(/&amp;amp;/g, '&amp;').
+			replace(/<br>/g, '<br />').replace(/<hr>/g, '<hr />').
+			replace(/(<img[^>]+)>/gm, "$1 />");
+	}
+	
+	return val;
+};
+
+/**
+ * Function: createDiv
+ * 
+ * Private helper function to create SVG elements
+ */
+mxSvgCanvas2D.prototype.createDiv = function(str, align, valign, style, overflow)
+{
+	var s = this.state;
+
+	// Inline block for rendering HTML background over SVG in Safari
+	var lh = (mxConstants.ABSOLUTE_LINE_HEIGHT) ? (s.fontSize * mxConstants.LINE_HEIGHT) + 'px' :
+		(mxConstants.LINE_HEIGHT * this.lineHeightCorrection);
+	
+	style = 'display:inline-block;font-size:' + s.fontSize + 'px;font-family:' + s.fontFamily +
+		';color:' + s.fontColor + ';line-height:' + lh + ';' + style;
+
+	if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
+	{
+		style += 'font-weight:bold;';
+	}
+
+	if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
+	{
+		style += 'font-style:italic;';
+	}
+	
+	if ((s.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)
+	{
+		style += 'text-decoration:underline;';
+	}
+	
+	if (align == mxConstants.ALIGN_CENTER)
+	{
+		style += 'text-align:center;';
+	}
+	else if (align == mxConstants.ALIGN_RIGHT)
+	{
+		style += 'text-align:right;';
+	}
+
+	var css = '';
+	
+	if (s.fontBackgroundColor != null)
+	{
+		css += 'background-color:' + s.fontBackgroundColor + ';';
+	}
+	
+	if (s.fontBorderColor != null)
+	{
+		css += 'border:1px solid ' + s.fontBorderColor + ';';
+	}
+	
+	var val = str;
+	
+	if (!mxUtils.isNode(val))
+	{
+		val = this.convertHtml(val);
+		
+		if (overflow != 'fill' && overflow != 'width')
+		{
+			// Inner div always needed to measure wrapped text
+			val = '<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;' + css + '">' + val + '</div>';
+		}
+		else
+		{
+			style += css;
+		}
+	}
+
+	// Uses DOM API where available. This cannot be used in IE to avoid
+	// an opening and two (!) closing TBODY tags being added to tables.
+	if (!mxClient.IS_IE && document.createElementNS)
+	{
+		var div = document.createElementNS('http://www.w3.org/1999/xhtml', 'div');
+		div.setAttribute('style', style);
+		
+		if (mxUtils.isNode(val))
+		{
+			// Creates a copy for export
+			if (this.root.ownerDocument != document)
+			{
+				div.appendChild(val.cloneNode(true));
+			}
+			else
+			{
+				div.appendChild(val);
+			}
+		}
+		else
+		{
+			div.innerHTML = val;
+		}
+		
+		return div;
+	}
+	else
+	{
+		// Serializes for export
+		if (mxUtils.isNode(val) && this.root.ownerDocument != document)
+		{
+			val = val.outerHTML;
+		}
+
+		// NOTE: FF 3.6 crashes if content CSS contains "height:100%"
+		return mxUtils.parseXml('<div xmlns="http://www.w3.org/1999/xhtml" style="' + style + 
+			'">' + val + '</div>').documentElement;
+	}
+};
+
+/**
+ * Invalidates the cached offset size for the given node.
+ */
+mxSvgCanvas2D.prototype.invalidateCachedOffsetSize = function(node)
+{
+	delete node.firstChild.mxCachedOffsetWidth;
+	delete node.firstChild.mxCachedFinalOffsetWidth;
+	delete node.firstChild.mxCachedFinalOffsetHeight;
+};
+
+/**
+ * Updates existing DOM nodes for text rendering. LATER: Merge common parts with text function below.
+ */
+mxSvgCanvas2D.prototype.updateText = function(x, y, w, h, align, valign, wrap, overflow, clip, rotation, node)
+{
+	if (node != null && node.firstChild != null && node.firstChild.firstChild != null &&
+		node.firstChild.firstChild.firstChild != null)
+	{
+		// Uses outer group for opacity and transforms to
+		// fix rendering order in Chrome
+		var group = node.firstChild;
+		var fo = group.firstChild;
+		var div = fo.firstChild;
+
+		rotation = (rotation != null) ? rotation : 0;
+		
+		var s = this.state;
+		x += s.dx;
+		y += s.dy;
+		
+		if (clip)
+		{
+			div.style.maxHeight = Math.round(h) + 'px';
+			div.style.maxWidth = Math.round(w) + 'px';
+		}
+		else if (overflow == 'fill')
+		{
+			div.style.width = Math.round(w + 1) + 'px';
+			div.style.height = Math.round(h + 1) + 'px';
+		}
+		else if (overflow == 'width')
+		{
+			div.style.width = Math.round(w + 1) + 'px';
+			
+			if (h > 0)
+			{
+				div.style.maxHeight = Math.round(h) + 'px';
+			}
+		}
+
+		if (wrap && w > 0)
+		{
+			div.style.width = Math.round(w + 1) + 'px';
+		}
+		
+		// Code that depends on the size which is computed after
+		// the element was added to the DOM.
+		var ow = 0;
+		var oh = 0;
+		
+		// Padding avoids clipping on border and wrapping for differing font metrics on platforms
+		var padX = 2;
+		var padY = 2;
+
+		var sizeDiv = div;
+		
+		if (sizeDiv.firstChild != null && sizeDiv.firstChild.nodeName == 'DIV')
+		{
+			sizeDiv = sizeDiv.firstChild;
+		}
+		
+		var tmp = (group.mxCachedOffsetWidth != null) ? group.mxCachedOffsetWidth : sizeDiv.offsetWidth;
+		ow = tmp + padX;
+
+		// Recomputes the height of the element for wrapped width
+		if (wrap && overflow != 'fill')
+		{
+			if (clip)
+			{
+				ow = Math.min(ow, w);
+			}
+			
+			div.style.width = ow + 'px';
+		}
+		
+		ow = ((group.mxCachedFinalOffsetWidth != null) ? group.mxCachedFinalOffsetWidth :
+			sizeDiv.offsetWidth) + padX;
+		oh = ((group.mxCachedFinalOffsetHeight != null) ? group.mxCachedFinalOffsetHeight :
+			sizeDiv.offsetHeight) - 2;
+
+		if (clip)
+		{
+			oh = Math.min(oh, h);
+			ow = Math.min(ow, w);
+		}
+
+		if (overflow == 'width')
+		{
+			h = oh;
+		}
+		else if (overflow != 'fill')
+		{
+			w = ow;
+			h = oh;
+		}
+
+		var dx = 0;
+		var dy = 0;
+
+		if (align == mxConstants.ALIGN_CENTER)
+		{
+			dx -= w / 2;
+		}
+		else if (align == mxConstants.ALIGN_RIGHT)
+		{
+			dx -= w;
+		}
+		
+		x += dx;
+		
+		// FIXME: LINE_HEIGHT not ideal for all text sizes, fix for export
+		if (valign == mxConstants.ALIGN_MIDDLE)
+		{
+			dy -= h / 2;
+		}
+		else if (valign == mxConstants.ALIGN_BOTTOM)
+		{
+			dy -= h;
+		}
+		
+		// Workaround for rendering offsets
+		// TODO: Check if export needs these fixes, too
+		if (overflow != 'fill' && mxClient.IS_FF && mxClient.IS_WIN)
+		{
+			dy -= 2;
+		}
+		
+		y += dy;
+
+		var tr = (s.scale != 1) ? 'scale(' + s.scale + ')' : '';
+
+		if (s.rotation != 0 && this.rotateHtml)
+		{
+			tr += 'rotate(' + (s.rotation) + ',' + (w / 2) + ',' + (h / 2) + ')';
+			var pt = this.rotatePoint((x + w / 2) * s.scale, (y + h / 2) * s.scale,
+				s.rotation, s.rotationCx, s.rotationCy);
+			x = pt.x - w * s.scale / 2;
+			y = pt.y - h * s.scale / 2;
+		}
+		else
+		{
+			x *= s.scale;
+			y *= s.scale;
+		}
+
+		if (rotation != 0)
+		{
+			tr += 'rotate(' + (rotation) + ',' + (-dx) + ',' + (-dy) + ')';
+		}
+
+		group.setAttribute('transform', 'translate(' + Math.round(x) + ',' + Math.round(y) + ')' + tr);
+		fo.setAttribute('width', Math.round(Math.max(1, w)));
+		fo.setAttribute('height', Math.round(Math.max(1, h)));
+	}
+};
+
+/**
+ * Function: text
+ * 
+ * Paints the given text. Possible values for format are empty string for plain
+ * text and html for HTML markup. Note that HTML markup is only supported if
+ * foreignObject is supported and <foEnabled> is true. (This means IE9 and later
+ * does currently not support HTML text as part of shapes.)
+ */
+mxSvgCanvas2D.prototype.text = function(x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation, dir)
+{
+	if (this.textEnabled && str != null)
+	{
+		rotation = (rotation != null) ? rotation : 0;
+		
+		var s = this.state;
+		x += s.dx;
+		y += s.dy;
+		
+		if (this.foEnabled && format == 'html')
+		{
+			var style = 'vertical-align:top;';
+			
+			if (clip)
+			{
+				style += 'overflow:hidden;max-height:' + Math.round(h) + 'px;max-width:' + Math.round(w) + 'px;';
+			}
+			else if (overflow == 'fill')
+			{
+				style += 'width:' + Math.round(w + 1) + 'px;height:' + Math.round(h + 1) + 'px;overflow:hidden;';
+			}
+			else if (overflow == 'width')
+			{
+				style += 'width:' + Math.round(w + 1) + 'px;';
+				
+				if (h > 0)
+				{
+					style += 'max-height:' + Math.round(h) + 'px;overflow:hidden;';
+				}
+			}
+
+			if (wrap && w > 0)
+			{
+				style += 'width:' + Math.round(w + 1) + 'px;white-space:normal;word-wrap:' +
+					mxConstants.WORD_WRAP + ';';
+			}
+			else
+			{
+				style += 'white-space:nowrap;';
+			}
+			
+			// Uses outer group for opacity and transforms to
+			// fix rendering order in Chrome
+			var group = this.createElement('g');
+			
+			if (s.alpha < 1)
+			{
+				group.setAttribute('opacity', s.alpha);
+			}
+
+			var fo = this.createElement('foreignObject');
+			fo.setAttribute('style', 'overflow:visible;');
+			fo.setAttribute('pointer-events', 'all');
+			
+			var div = this.createDiv(str, align, valign, style, overflow);
+			
+			// Ignores invalid XHTML labels
+			if (div == null)
+			{
+				return;
+			}
+			else if (dir != null)
+			{
+				div.setAttribute('dir', dir);
+			}
+
+			group.appendChild(fo);
+			this.root.appendChild(group);
+			
+			// Code that depends on the size which is computed after
+			// the element was added to the DOM.
+			var ow = 0;
+			var oh = 0;
+			
+			// Padding avoids clipping on border and wrapping for differing font metrics on platforms
+			var padX = 2;
+			var padY = 2;
+
+			// NOTE: IE is always export as it does not support foreign objects
+			if (mxClient.IS_IE && (document.documentMode == 9 || !mxClient.IS_SVG))
+			{
+				// Handles non-standard namespace for getting size in IE
+				var clone = document.createElement('div');
+				
+				clone.style.cssText = div.getAttribute('style');
+				clone.style.display = (mxClient.IS_QUIRKS) ? 'inline' : 'inline-block';
+				clone.style.position = 'absolute';
+				clone.style.visibility = 'hidden';
+
+				// Inner DIV is needed for text measuring
+				var div2 = document.createElement('div');
+				div2.style.display = (mxClient.IS_QUIRKS) ? 'inline' : 'inline-block';
+				div2.style.wordWrap = mxConstants.WORD_WRAP;
+				div2.innerHTML = (mxUtils.isNode(str)) ? str.outerHTML : str;
+				clone.appendChild(div2);
+
+				document.body.appendChild(clone);
+
+				// Workaround for different box models
+				if (document.documentMode != 8 && document.documentMode != 9 && s.fontBorderColor != null)
+				{
+					padX += 2;
+					padY += 2;
+				}
+
+				if (wrap && w > 0)
+				{
+					var tmp = div2.offsetWidth;
+					
+					// Workaround for adding padding twice in IE8/IE9 standards mode if label is wrapped
+					var padDx = 0;
+					
+					// For export, if no wrapping occurs, we add a large padding to make
+					// sure there is no wrapping even if the text metrics are different.
+					// This adds support for text metrics on different operating systems.
+					// Disables wrapping if text is not wrapped for given width
+					if (!clip && wrap && w > 0 && this.root.ownerDocument != document && overflow != 'fill')
+					{
+						var ws = clone.style.whiteSpace;
+						div2.style.whiteSpace = 'nowrap';
+						
+						if (tmp < div2.offsetWidth)
+						{
+							clone.style.whiteSpace = ws;
+						}
+					}
+					
+					if (clip)
+					{
+						tmp = Math.min(tmp, w);
+					}
+					
+					clone.style.width = tmp + 'px';
+	
+					// Padding avoids clipping on border
+					ow = div2.offsetWidth + padX + padDx;
+					oh = div2.offsetHeight + padY;
+					
+					// Overrides the width of the DIV via XML DOM by using the
+					// clone DOM style, getting the CSS text for that and
+					// then setting that on the DIV via setAttribute
+					clone.style.display = 'inline-block';
+					clone.style.position = '';
+					clone.style.visibility = '';
+					clone.style.width = ow + 'px';
+					
+					div.setAttribute('style', clone.style.cssText);
+				}
+				else
+				{
+					// Padding avoids clipping on border
+					ow = div2.offsetWidth + padX;
+					oh = div2.offsetHeight + padY;
+				}
+
+				clone.parentNode.removeChild(clone);
+				fo.appendChild(div);
+			}
+			else
+			{
+				// Uses document for text measuring during export
+				if (this.root.ownerDocument != document)
+				{
+					div.style.visibility = 'hidden';
+					document.body.appendChild(div);
+				}
+				else
+				{
+					fo.appendChild(div);
+				}
+
+				var sizeDiv = div;
+				
+				if (sizeDiv.firstChild != null && sizeDiv.firstChild.nodeName == 'DIV')
+				{
+					sizeDiv = sizeDiv.firstChild;
+					
+					if (wrap && div.style.wordWrap == 'break-word')
+					{
+						sizeDiv.style.width = '100%';
+					}
+				}
+				
+				var tmp = sizeDiv.offsetWidth;
+				
+				// Workaround for text measuring in hidden containers
+				if (tmp == 0 && div.parentNode == fo)
+				{
+					div.style.visibility = 'hidden';
+					document.body.appendChild(div);
+					
+					tmp = sizeDiv.offsetWidth;
+				}
+				
+				if (this.cacheOffsetSize)
+				{
+					group.mxCachedOffsetWidth = tmp;
+				}
+				
+				// Disables wrapping if text is not wrapped for given width
+				if (!clip && wrap && w > 0 && this.root.ownerDocument != document &&
+					overflow != 'fill' && overflow != 'width')
+				{
+					var ws = div.style.whiteSpace;
+					div.style.whiteSpace = 'nowrap';
+					
+					if (tmp < sizeDiv.offsetWidth)
+					{
+						div.style.whiteSpace = ws;
+					}
+				}
+
+				ow = tmp + padX - 1;
+
+				// Recomputes the height of the element for wrapped width
+				if (wrap && overflow != 'fill' && overflow != 'width')
+				{
+					if (clip)
+					{
+						ow = Math.min(ow, w);
+					}
+					
+					div.style.width = ow + 'px';
+				}
+
+				ow = sizeDiv.offsetWidth;
+				oh = sizeDiv.offsetHeight;
+				
+				if (this.cacheOffsetSize)
+				{
+					group.mxCachedFinalOffsetWidth = ow;
+					group.mxCachedFinalOffsetHeight = oh;
+				}
+
+				oh -= padY;
+				
+				if (div.parentNode != fo)
+				{
+					fo.appendChild(div);
+					div.style.visibility = '';
+				}
+			}
+
+			if (clip)
+			{
+				oh = Math.min(oh, h);
+				ow = Math.min(ow, w);
+			}
+
+			if (overflow == 'width')
+			{
+				h = oh;
+			}
+			else if (overflow != 'fill')
+			{
+				w = ow;
+				h = oh;
+			}
+
+			if (s.alpha < 1)
+			{
+				group.setAttribute('opacity', s.alpha);
+			}
+			
+			var dx = 0;
+			var dy = 0;
+
+			if (align == mxConstants.ALIGN_CENTER)
+			{
+				dx -= w / 2;
+			}
+			else if (align == mxConstants.ALIGN_RIGHT)
+			{
+				dx -= w;
+			}
+			
+			x += dx;
+			
+			// FIXME: LINE_HEIGHT not ideal for all text sizes, fix for export
+			if (valign == mxConstants.ALIGN_MIDDLE)
+			{
+				dy -= h / 2;
+			}
+			else if (valign == mxConstants.ALIGN_BOTTOM)
+			{
+				dy -= h;
+			}
+			
+			// Workaround for rendering offsets
+			// TODO: Check if export needs these fixes, too
+			//if (this.root.ownerDocument == document)
+			if (overflow != 'fill' && mxClient.IS_FF && mxClient.IS_WIN)
+			{
+				dy -= 2;
+			}
+			
+			y += dy;
+
+			var tr = (s.scale != 1) ? 'scale(' + s.scale + ')' : '';
+
+			if (s.rotation != 0 && this.rotateHtml)
+			{
+				tr += 'rotate(' + (s.rotation) + ',' + (w / 2) + ',' + (h / 2) + ')';
+				var pt = this.rotatePoint((x + w / 2) * s.scale, (y + h / 2) * s.scale,
+					s.rotation, s.rotationCx, s.rotationCy);
+				x = pt.x - w * s.scale / 2;
+				y = pt.y - h * s.scale / 2;
+			}
+			else
+			{
+				x *= s.scale;
+				y *= s.scale;
+			}
+
+			if (rotation != 0)
+			{
+				tr += 'rotate(' + (rotation) + ',' + (-dx) + ',' + (-dy) + ')';
+			}
+
+			group.setAttribute('transform', 'translate(' + (Math.round(x) + this.foOffset) + ',' +
+				(Math.round(y) + this.foOffset) + ')' + tr);
+			fo.setAttribute('width', Math.round(Math.max(1, w)));
+			fo.setAttribute('height', Math.round(Math.max(1, h)));
+			
+			// Adds alternate content if foreignObject not supported in viewer
+			if (this.root.ownerDocument != document)
+			{
+				var alt = this.createAlternateContent(fo, x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation);
+				
+				if (alt != null)
+				{
+					fo.setAttribute('requiredFeatures', 'http://www.w3.org/TR/SVG11/feature#Extensibility');
+					var sw = this.createElement('switch');
+					sw.appendChild(fo);
+					sw.appendChild(alt);
+					group.appendChild(sw);
+				}
+			}
+		}
+		else
+		{
+			this.plainText(x, y, w, h, str, align, valign, wrap, overflow, clip, rotation, dir);
+		}
+	}
+};
+
+/**
+ * Function: createClip
+ * 
+ * Creates a clip for the given coordinates.
+ */
+mxSvgCanvas2D.prototype.createClip = function(x, y, w, h)
+{
+	x = Math.round(x);
+	y = Math.round(y);
+	w = Math.round(w);
+	h = Math.round(h);
+	
+	var id = 'mx-clip-' + x + '-' + y + '-' + w + '-' + h;
+
+	var counter = 0;
+	var tmp = id + '-' + counter;
+	
+	// Resolves ID conflicts
+	while (document.getElementById(tmp) != null)
+	{
+		tmp = id + '-' + (++counter);
+	}
+	
+	clip = this.createElement('clipPath');
+	clip.setAttribute('id', tmp);
+	
+	var rect = this.createElement('rect');
+	rect.setAttribute('x', x);
+	rect.setAttribute('y', y);
+	rect.setAttribute('width', w);
+	rect.setAttribute('height', h);
+		
+	clip.appendChild(rect);
+	
+	return clip;
+};
+
+/**
+ * Function: text
+ * 
+ * Paints the given text. Possible values for format are empty string for
+ * plain text and html for HTML markup.
+ */
+mxSvgCanvas2D.prototype.plainText = function(x, y, w, h, str, align, valign, wrap, overflow, clip, rotation, dir)
+{
+	rotation = (rotation != null) ? rotation : 0;
+	var s = this.state;
+	var size = s.fontSize;
+	var node = this.createElement('g');
+	var tr = s.transform || '';
+	this.updateFont(node);
+	
+	// Non-rotated text
+	if (rotation != 0)
+	{
+		tr += 'rotate(' + rotation  + ',' + this.format(x * s.scale) + ',' + this.format(y * s.scale) + ')';
+	}
+	
+	if (dir != null)
+	{
+		node.setAttribute('direction', dir);
+	}
+
+	if (clip && w > 0 && h > 0)
+	{
+		var cx = x;
+		var cy = y;
+		
+		if (align == mxConstants.ALIGN_CENTER)
+		{
+			cx -= w / 2;
+		}
+		else if (align == mxConstants.ALIGN_RIGHT)
+		{
+			cx -= w;
+		}
+		
+		if (overflow != 'fill')
+		{
+			if (valign == mxConstants.ALIGN_MIDDLE)
+			{
+				cy -= h / 2;
+			}
+			else if (valign == mxConstants.ALIGN_BOTTOM)
+			{
+				cy -= h;
+			}
+		}
+		
+		// LATER: Remove spacing from clip rectangle
+		var c = this.createClip(cx * s.scale - 2, cy * s.scale - 2, w * s.scale + 4, h * s.scale + 4);
+		
+		if (this.defs != null)
+		{
+			this.defs.appendChild(c);
+		}
+		else
+		{
+			// Makes sure clip is removed with referencing node
+			this.root.appendChild(c);
+		}
+		
+		if (!mxClient.IS_CHROME_APP && !mxClient.IS_IE && !mxClient.IS_IE11 &&
+			!mxClient.IS_EDGE && this.root.ownerDocument == document)
+		{
+			// Workaround for potential base tag
+			var base = this.getBaseUrl().replace(/([\(\)])/g, '\\$1');
+			node.setAttribute('clip-path', 'url(' + base + '#' + c.getAttribute('id') + ')');
+		}
+		else
+		{
+			node.setAttribute('clip-path', 'url(#' + c.getAttribute('id') + ')');
+		}
+	}
+
+	// Default is left
+	var anchor = (align == mxConstants.ALIGN_RIGHT) ? 'end' :
+					(align == mxConstants.ALIGN_CENTER) ? 'middle' :
+					'start';
+
+	// Text-anchor start is default in SVG
+	if (anchor != 'start')
+	{
+		node.setAttribute('text-anchor', anchor);
+	}
+	
+	if (!this.styleEnabled || size != mxConstants.DEFAULT_FONTSIZE)
+	{
+		node.setAttribute('font-size', (size * s.scale) + 'px');
+	}
+	
+	if (tr.length > 0)
+	{
+		node.setAttribute('transform', tr);
+	}
+	
+	if (s.alpha < 1)
+	{
+		node.setAttribute('opacity', s.alpha);
+	}
+	
+	var lines = str.split('\n');
+	var lh = Math.round(size * mxConstants.LINE_HEIGHT);
+	var textHeight = size + (lines.length - 1) * lh;
+
+	var cy = y + size - 1;
+
+	if (valign == mxConstants.ALIGN_MIDDLE)
+	{
+		if (overflow == 'fill')
+		{
+			cy -= h / 2;
+		}
+		else
+		{
+			var dy = ((this.matchHtmlAlignment && clip && h > 0) ? Math.min(textHeight, h) : textHeight) / 2;
+			cy -= dy + 1;
+		}
+	}
+	else if (valign == mxConstants.ALIGN_BOTTOM)
+	{
+		if (overflow == 'fill')
+		{
+			cy -= h;
+		}
+		else
+		{
+			var dy = (this.matchHtmlAlignment && clip && h > 0) ? Math.min(textHeight, h) : textHeight;
+			cy -= dy + 2;
+		}
+	}
+
+	for (var i = 0; i < lines.length; i++)
+	{
+		// Workaround for bounding box of empty lines and spaces
+		if (lines[i].length > 0 && mxUtils.trim(lines[i]).length > 0)
+		{
+			var text = this.createElement('text');
+			// LATER: Match horizontal HTML alignment
+			text.setAttribute('x', this.format(x * s.scale) + this.textOffset);
+			text.setAttribute('y', this.format(cy * s.scale) + this.textOffset);
+			
+			mxUtils.write(text, lines[i]);
+			node.appendChild(text);
+		}
+
+		cy += lh;
+	}
+
+	this.root.appendChild(node);
+	this.addTextBackground(node, str, x, y, w, (overflow == 'fill') ? h : textHeight, align, valign, overflow);
+};
+
+/**
+ * Function: updateFont
+ * 
+ * Updates the text properties for the given node. (NOTE: For this to work in
+ * IE, the given node must be a text or tspan element.)
+ */
+mxSvgCanvas2D.prototype.updateFont = function(node)
+{
+	var s = this.state;
+
+	node.setAttribute('fill', s.fontColor);
+	
+	if (!this.styleEnabled || s.fontFamily != mxConstants.DEFAULT_FONTFAMILY)
+	{
+		node.setAttribute('font-family', s.fontFamily);
+	}
+
+	if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
+	{
+		node.setAttribute('font-weight', 'bold');
+	}
+
+	if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
+	{
+		node.setAttribute('font-style', 'italic');
+	}
+	
+	if ((s.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)
+	{
+		node.setAttribute('text-decoration', 'underline');
+	}
+};
+
+/**
+ * Function: addTextBackground
+ * 
+ * Background color and border
+ */
+mxSvgCanvas2D.prototype.addTextBackground = function(node, str, x, y, w, h, align, valign, overflow)
+{
+	var s = this.state;
+
+	if (s.fontBackgroundColor != null || s.fontBorderColor != null)
+	{
+		var bbox = null;
+		
+		if (overflow == 'fill' || overflow == 'width')
+		{
+			if (align == mxConstants.ALIGN_CENTER)
+			{
+				x -= w / 2;
+			}
+			else if (align == mxConstants.ALIGN_RIGHT)
+			{
+				x -= w;
+			}
+			
+			if (valign == mxConstants.ALIGN_MIDDLE)
+			{
+				y -= h / 2;
+			}
+			else if (valign == mxConstants.ALIGN_BOTTOM)
+			{
+				y -= h;
+			}
+			
+			bbox = new mxRectangle((x + 1) * s.scale, y * s.scale, (w - 2) * s.scale, (h + 2) * s.scale);
+		}
+		else if (node.getBBox != null && this.root.ownerDocument == document)
+		{
+			// Uses getBBox only if inside document for correct size
+			try
+			{
+				bbox = node.getBBox();
+				var ie = mxClient.IS_IE && mxClient.IS_SVG;
+				bbox = new mxRectangle(bbox.x, bbox.y + ((ie) ? 0 : 1), bbox.width, bbox.height + ((ie) ? 1 : 0));
+			}
+			catch (e)
+			{
+				// Ignores NS_ERROR_FAILURE in FF if container display is none.
+			}
+		}
+		else
+		{
+			// Computes size if not in document or no getBBox available
+			var div = document.createElement('div');
+
+			// Wrapping and clipping can be ignored here
+			div.style.lineHeight = (mxConstants.ABSOLUTE_LINE_HEIGHT) ? (s.fontSize * mxConstants.LINE_HEIGHT) + 'px' : mxConstants.LINE_HEIGHT;
+			div.style.fontSize = s.fontSize + 'px';
+			div.style.fontFamily = s.fontFamily;
+			div.style.whiteSpace = 'nowrap';
+			div.style.position = 'absolute';
+			div.style.visibility = 'hidden';
+			div.style.display = (mxClient.IS_QUIRKS) ? 'inline' : 'inline-block';
+			div.style.zoom = '1';
+			
+			if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
+			{
+				div.style.fontWeight = 'bold';
+			}
+
+			if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
+			{
+				div.style.fontStyle = 'italic';
+			}
+			
+			str = mxUtils.htmlEntities(str, false);
+			div.innerHTML = str.replace(/\n/g, '<br/>');
+			
+			document.body.appendChild(div);
+			var w = div.offsetWidth;
+			var h = div.offsetHeight;
+			div.parentNode.removeChild(div);
+			
+			if (align == mxConstants.ALIGN_CENTER)
+			{
+				x -= w / 2;
+			}
+			else if (align == mxConstants.ALIGN_RIGHT)
+			{
+				x -= w;
+			}
+			
+			if (valign == mxConstants.ALIGN_MIDDLE)
+			{
+				y -= h / 2;
+			}
+			else if (valign == mxConstants.ALIGN_BOTTOM)
+			{
+				y -= h;
+			}
+			
+			bbox = new mxRectangle((x + 1) * s.scale, (y + 2) * s.scale, w * s.scale, (h + 1) * s.scale);
+		}
+		
+		if (bbox != null)
+		{
+			var n = this.createElement('rect');
+			n.setAttribute('fill', s.fontBackgroundColor || 'none');
+			n.setAttribute('stroke', s.fontBorderColor || 'none');
+			n.setAttribute('x', Math.floor(bbox.x - 1));
+			n.setAttribute('y', Math.floor(bbox.y - 1));
+			n.setAttribute('width', Math.ceil(bbox.width + 2));
+			n.setAttribute('height', Math.ceil(bbox.height));
+
+			var sw = (s.fontBorderColor != null) ? Math.max(1, this.format(s.scale)) : 0;
+			n.setAttribute('stroke-width', sw);
+			
+			// Workaround for crisp rendering - only required if not exporting
+			if (this.root.ownerDocument == document && mxUtils.mod(sw, 2) == 1)
+			{
+				n.setAttribute('transform', 'translate(0.5, 0.5)');
+			}
+			
+			node.insertBefore(n, node.firstChild);
+		}
+	}
+};
+
+/**
+ * Function: stroke
+ * 
+ * Paints the outline of the current path.
+ */
+mxSvgCanvas2D.prototype.stroke = function()
+{
+	this.addNode(false, true);
+};
+
+/**
+ * Function: fill
+ * 
+ * Fills the current path.
+ */
+mxSvgCanvas2D.prototype.fill = function()
+{
+	this.addNode(true, false);
+};
+
+/**
+ * Function: fillAndStroke
+ * 
+ * Fills and paints the outline of the current path.
+ */
+mxSvgCanvas2D.prototype.fillAndStroke = function()
+{
+	this.addNode(true, true);
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/util/mxToolbar.js b/airavata-kubernetes/workflow-composer/src/js/util/mxToolbar.js
new file mode 100644
index 0000000..cb90ced
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/util/mxToolbar.js
@@ -0,0 +1,527 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxToolbar
+ * 
+ * Creates a toolbar inside a given DOM node. The toolbar may contain icons,
+ * buttons and combo boxes.
+ * 
+ * Event: mxEvent.SELECT
+ * 
+ * Fires when an item was selected in the toolbar. The <code>function</code>
+ * property contains the function that was selected in <selectMode>.
+ * 
+ * Constructor: mxToolbar
+ * 
+ * Constructs a toolbar in the specified container.
+ *
+ * Parameters:
+ *
+ * container - DOM node that contains the toolbar.
+ */
+function mxToolbar(container)
+{
+	this.container = container;
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxToolbar.prototype = new mxEventSource();
+mxToolbar.prototype.constructor = mxToolbar;
+
+/**
+ * Variable: container
+ * 
+ * Reference to the DOM nodes that contains the toolbar.
+ */
+mxToolbar.prototype.container = null;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies if events are handled. Default is true.
+ */
+mxToolbar.prototype.enabled = true;
+
+/**
+ * Variable: noReset
+ * 
+ * Specifies if <resetMode> requires a forced flag of true for resetting
+ * the current mode in the toolbar. Default is false. This is set to true
+ * if the toolbar item is double clicked to avoid a reset after a single
+ * use of the item.
+ */
+mxToolbar.prototype.noReset = false;
+
+/**
+ * Variable: updateDefaultMode
+ * 
+ * Boolean indicating if the default mode should be the last selected
+ * switch mode or the first inserted switch mode. Default is true, that
+ * is the last selected switch mode is the default mode. The default mode
+ * is the mode to be selected after a reset of the toolbar. If this is
+ * false, then the default mode is the first inserted mode item regardless
+ * of what was last selected. Otherwise, the selected item after a reset is
+ * the previously selected item.
+ */
+mxToolbar.prototype.updateDefaultMode = true;
+
+/**
+ * Function: addItem
+ * 
+ * Adds the given function as an image with the specified title and icon
+ * and returns the new image node.
+ * 
+ * Parameters:
+ * 
+ * title - Optional string that is used as the tooltip.
+ * icon - Optional URL of the image to be used. If no URL is given, then a
+ * button is created.
+ * funct - Function to execute on a mouse click.
+ * pressedIcon - Optional URL of the pressed image. Default is a gray
+ * background.
+ * style - Optional style classname. Default is mxToolbarItem.
+ * factoryMethod - Optional factory method for popup menu, eg.
+ * function(menu, evt, cell) { menu.addItem('Hello, World!'); }
+ */
+mxToolbar.prototype.addItem = function(title, icon, funct, pressedIcon, style, factoryMethod)
+{
+	var img = document.createElement((icon != null) ? 'img' : 'button');
+	var initialClassName = style || ((factoryMethod != null) ?
+			'mxToolbarMode' : 'mxToolbarItem');
+	img.className = initialClassName;
+	img.setAttribute('src', icon);
+	
+	if (title != null)
+	{
+		if (icon != null)
+		{
+			img.setAttribute('title', title);
+		}
+		else
+		{
+			mxUtils.write(img, title);
+		}
+	}
+	
+	this.container.appendChild(img);
+
+	// Invokes the function on a click on the toolbar item
+	if (funct != null)
+	{
+		mxEvent.addListener(img, 'click', funct);
+		
+		if (mxClient.IS_TOUCH)
+		{
+			mxEvent.addListener(img, 'touchend', funct);
+		}
+	}
+
+	var mouseHandler = mxUtils.bind(this, function(evt)
+	{
+		if (pressedIcon != null)
+		{
+			img.setAttribute('src', icon);
+		}
+		else
+		{
+			img.style.backgroundColor = '';
+		}
+	});
+
+	// Highlights the toolbar item with a gray background
+	// while it is being clicked with the mouse
+	mxEvent.addGestureListeners(img, mxUtils.bind(this, function(evt)
+	{
+		if (pressedIcon != null)
+		{
+			img.setAttribute('src', pressedIcon);
+		}
+		else
+		{
+			img.style.backgroundColor = 'gray';
+		}
+		
+		// Popup Menu
+		if (factoryMethod != null)
+		{
+			if (this.menu == null)
+			{
+				this.menu = new mxPopupMenu();
+				this.menu.init();
+			}
+			
+			var last = this.currentImg;
+			
+			if (this.menu.isMenuShowing())
+			{
+				this.menu.hideMenu();
+			}
+			
+			if (last != img)
+			{
+				// Redirects factory method to local factory method
+				this.currentImg = img;
+				this.menu.factoryMethod = factoryMethod;
+				
+				var point = new mxPoint(
+					img.offsetLeft,
+					img.offsetTop + img.offsetHeight);
+				this.menu.popup(point.x, point.y, null, evt);
+
+				// Sets and overrides to restore classname
+				if (this.menu.isMenuShowing())
+				{
+					img.className = initialClassName + 'Selected';
+					
+					this.menu.hideMenu = function()
+					{
+						mxPopupMenu.prototype.hideMenu.apply(this);
+						img.className = initialClassName;
+						this.currentImg = null;
+					};
+				}
+			}
+		}
+	}), null, mouseHandler);
+
+	mxEvent.addListener(img, 'mouseout', mouseHandler);
+	
+	return img;
+};
+
+/**
+ * Function: addCombo
+ * 
+ * Adds and returns a new SELECT element using the given style. The element
+ * is placed inside a DIV with the mxToolbarComboContainer style classname.
+ * 
+ * Parameters:
+ * 
+ * style - Optional style classname. Default is mxToolbarCombo.
+ */
+mxToolbar.prototype.addCombo = function(style)
+{
+	var div = document.createElement('div');
+	div.style.display = 'inline';
+	div.className = 'mxToolbarComboContainer';
+	
+	var select = document.createElement('select');
+	select.className = style || 'mxToolbarCombo';
+	div.appendChild(select);
+	
+	this.container.appendChild(div);
+	
+	return select;
+};
+
+/**
+ * Function: addCombo
+ * 
+ * Adds and returns a new SELECT element using the given title as the
+ * default element. The selection is reset to this element after each
+ * change.
+ * 
+ * Parameters:
+ * 
+ * title - String that specifies the title of the default element.
+ * style - Optional style classname. Default is mxToolbarCombo.
+ */
+mxToolbar.prototype.addActionCombo = function(title, style)
+{
+	var select = document.createElement('select');
+	select.className = style || 'mxToolbarCombo';
+	this.addOption(select, title, null);
+	
+	mxEvent.addListener(select, 'change', function(evt)
+	{
+		var value = select.options[select.selectedIndex];
+		select.selectedIndex = 0;
+		
+		if (value.funct != null)
+		{
+			value.funct(evt);
+		}
+	});
+	
+	this.container.appendChild(select);
+	
+	return select;
+};
+
+/**
+ * Function: addOption
+ * 
+ * Adds and returns a new OPTION element inside the given SELECT element.
+ * If the given value is a function then it is stored in the option's funct
+ * field.
+ * 
+ * Parameters:
+ * 
+ * combo - SELECT element that will contain the new entry.
+ * title - String that specifies the title of the option.
+ * value - Specifies the value associated with this option.
+ */
+mxToolbar.prototype.addOption = function(combo, title, value)
+{
+	var option = document.createElement('option');
+	mxUtils.writeln(option, title);
+	
+	if (typeof(value) == 'function')
+	{
+		option.funct = value;
+	}
+	else
+	{
+		option.setAttribute('value', value);
+	}
+	
+	combo.appendChild(option);
+	
+	return option;
+};
+
+/**
+ * Function: addSwitchMode
+ * 
+ * Adds a new selectable item to the toolbar. Only one switch mode item may
+ * be selected at a time. The currently selected item is the default item
+ * after a reset of the toolbar.
+ */
+mxToolbar.prototype.addSwitchMode = function(title, icon, funct, pressedIcon, style)
+{
+	var img = document.createElement('img');
+	img.initialClassName = style || 'mxToolbarMode';
+	img.className = img.initialClassName;
+	img.setAttribute('src', icon);
+	img.altIcon = pressedIcon;
+	
+	if (title != null)
+	{
+		img.setAttribute('title', title);
+	}
+	
+	mxEvent.addListener(img, 'click', mxUtils.bind(this, function(evt)
+	{
+		var tmp = this.selectedMode.altIcon;
+		
+		if (tmp != null)
+		{
+			this.selectedMode.altIcon = this.selectedMode.getAttribute('src');
+			this.selectedMode.setAttribute('src', tmp);
+		}
+		else
+		{
+			this.selectedMode.className = this.selectedMode.initialClassName;
+		}
+		
+		if (this.updateDefaultMode)
+		{
+			this.defaultMode = img;
+		}
+		
+		this.selectedMode = img;
+		
+		var tmp = img.altIcon;
+		
+		if (tmp != null)
+		{
+			img.altIcon = img.getAttribute('src');
+			img.setAttribute('src', tmp);
+		}
+		else
+		{
+			img.className = img.initialClassName+'Selected';
+		}
+		
+		this.fireEvent(new mxEventObject(mxEvent.SELECT));
+		funct();
+	}));
+	
+	this.container.appendChild(img);
+	
+	if (this.defaultMode == null)
+	{
+		this.defaultMode = img;
+		
+		// Function should fire only once so
+		// do not pass it with the select event
+		this.selectMode(img);
+		funct();
+	}
+	
+	return img;
+};
+
+/**
+ * Function: addMode
+ * 
+ * Adds a new item to the toolbar. The selection is typically reset after
+ * the item has been consumed, for example by adding a new vertex to the
+ * graph. The reset is not carried out if the item is double clicked.
+ * 
+ * The function argument uses the following signature: funct(evt, cell) where
+ * evt is the native mouse event and cell is the cell under the mouse.
+ */
+mxToolbar.prototype.addMode = function(title, icon, funct, pressedIcon, style, toggle)
+{
+	toggle = (toggle != null) ? toggle : true;
+	var img = document.createElement((icon != null) ? 'img' : 'button');
+	
+	img.initialClassName = style || 'mxToolbarMode';
+	img.className = img.initialClassName;
+	img.setAttribute('src', icon);
+	img.altIcon = pressedIcon;
+
+	if (title != null)
+	{
+		img.setAttribute('title', title);
+	}
+	
+	if (this.enabled && toggle)
+	{
+		mxEvent.addListener(img, 'click', mxUtils.bind(this, function(evt)
+		{
+			this.selectMode(img, funct);
+			this.noReset = false;
+		}));
+		
+		mxEvent.addListener(img, 'dblclick', mxUtils.bind(this, function(evt)
+		{
+			this.selectMode(img, funct);
+			this.noReset = true;
+		}));
+		
+		if (this.defaultMode == null)
+		{
+			this.defaultMode = img;
+			this.defaultFunction = funct;
+			this.selectMode(img, funct);
+		}
+	}
+
+	this.container.appendChild(img);					
+
+	return img;
+};
+
+/**
+ * Function: selectMode
+ * 
+ * Resets the state of the previously selected mode and displays the given
+ * DOM node as selected. This function fires a select event with the given
+ * function as a parameter.
+ */
+mxToolbar.prototype.selectMode = function(domNode, funct)
+{
+	if (this.selectedMode != domNode)
+	{
+		if (this.selectedMode != null)
+		{
+			var tmp = this.selectedMode.altIcon;
+			
+			if (tmp != null)
+			{
+				this.selectedMode.altIcon = this.selectedMode.getAttribute('src');
+				this.selectedMode.setAttribute('src', tmp);
+			}
+			else
+			{
+				this.selectedMode.className = this.selectedMode.initialClassName;
+			}
+		}
+		
+		this.selectedMode = domNode;
+		var tmp = this.selectedMode.altIcon;
+		
+		if (tmp != null)
+		{
+			this.selectedMode.altIcon = this.selectedMode.getAttribute('src');
+			this.selectedMode.setAttribute('src', tmp);
+		}
+		else
+		{
+			this.selectedMode.className = this.selectedMode.initialClassName+'Selected';
+		}
+		
+		this.fireEvent(new mxEventObject(mxEvent.SELECT, "function", funct));
+	}
+};
+
+/**
+ * Function: resetMode
+ * 
+ * Selects the default mode and resets the state of the previously selected
+ * mode.
+ */
+mxToolbar.prototype.resetMode = function(forced)
+{
+	if ((forced || !this.noReset) && this.selectedMode != this.defaultMode)
+	{
+		// The last selected switch mode will be activated
+		// so the function was already executed and is
+		// no longer required here
+		this.selectMode(this.defaultMode, this.defaultFunction);
+	}
+};
+
+/**
+ * Function: addSeparator
+ * 
+ * Adds the specifies image as a separator.
+ * 
+ * Parameters:
+ * 
+ * icon - URL of the separator icon.
+ */
+mxToolbar.prototype.addSeparator = function(icon)
+{
+	return this.addItem(null, icon, null);
+};
+
+/**
+ * Function: addBreak
+ * 
+ * Adds a break to the container.
+ */
+mxToolbar.prototype.addBreak = function()
+{
+	mxUtils.br(this.container);
+};
+
+/**
+ * Function: addLine
+ * 
+ * Adds a horizontal line to the container.
+ */
+mxToolbar.prototype.addLine = function()
+{
+	var hr = document.createElement('hr');
+	
+	hr.style.marginRight = '6px';
+	hr.setAttribute('size', '1');
+	
+	this.container.appendChild(hr);
+};
+
+/**
+ * Function: destroy
+ * 
+ * Removes the toolbar and all its associated resources.
+ */
+mxToolbar.prototype.destroy = function ()
+{
+	mxEvent.release(this.container);
+	this.container = null;
+	this.defaultMode = null;
+	this.defaultFunction = null;
+	this.selectedMode = null;
+	
+	if (this.menu != null)
+	{
+		this.menu.destroy();
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/util/mxUndoManager.js b/airavata-kubernetes/workflow-composer/src/js/util/mxUndoManager.js
new file mode 100644
index 0000000..749561c
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/util/mxUndoManager.js
@@ -0,0 +1,229 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxUndoManager
+ *
+ * Implements a command history. When changing the graph model, an
+ * <mxUndoableChange> object is created at the start of the transaction (when
+ * model.beginUpdate is called). All atomic changes are then added to this
+ * object until the last model.endUpdate call, at which point the
+ * <mxUndoableEdit> is dispatched in an event, and added to the history inside
+ * <mxUndoManager>. This is done by an event listener in
+ * <mxEditor.installUndoHandler>.
+ * 
+ * Each atomic change of the model is represented by an object (eg.
+ * <mxRootChange>, <mxChildChange>, <mxTerminalChange> etc) which contains the
+ * complete undo information. The <mxUndoManager> also listens to the
+ * <mxGraphView> and stores it's changes to the current root as insignificant
+ * undoable changes, so that drilling (step into, step up) is undone.
+ * 
+ * This means when you execute an atomic change on the model, then change the
+ * current root on the view and click undo, the change of the root will be
+ * undone together with the change of the model so that the display represents
+ * the state at which the model was changed. However, these changes are not
+ * transmitted for sharing as they do not represent a state change.
+ *
+ * Example:
+ * 
+ * When adding an undo manager to a graph, make sure to add it
+ * to the model and the view as well to maintain a consistent
+ * display across multiple undo/redo steps.
+ *
+ * (code)
+ * var undoManager = new mxUndoManager();
+ * var listener = function(sender, evt)
+ * {
+ *   undoManager.undoableEditHappened(evt.getProperty('edit'));
+ * };
+ * graph.getModel().addListener(mxEvent.UNDO, listener);
+ * graph.getView().addListener(mxEvent.UNDO, listener);
+ * (end)
+ * 
+ * The code creates a function that informs the undoManager
+ * of an undoable edit and binds it to the undo event of
+ * <mxGraphModel> and <mxGraphView> using
+ * <mxEventSource.addListener>.
+ * 
+ * Event: mxEvent.CLEAR
+ * 
+ * Fires after <clear> was invoked. This event has no properties.
+ * 
+ * Event: mxEvent.UNDO
+ * 
+ * Fires afer a significant edit was undone in <undo>. The <code>edit</code>
+ * property contains the <mxUndoableEdit> that was undone.
+ * 
+ * Event: mxEvent.REDO
+ * 
+ * Fires afer a significant edit was redone in <redo>. The <code>edit</code>
+ * property contains the <mxUndoableEdit> that was redone.
+ * 
+ * Event: mxEvent.ADD
+ * 
+ * Fires after an undoable edit was added to the history. The <code>edit</code>
+ * property contains the <mxUndoableEdit> that was added.
+ * 
+ * Constructor: mxUndoManager
+ *
+ * Constructs a new undo manager with the given history size. If no history
+ * size is given, then a default size of 100 steps is used.
+ */
+function mxUndoManager(size)
+{
+	this.size = (size != null) ? size : 100;
+	this.clear();
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxUndoManager.prototype = new mxEventSource();
+mxUndoManager.prototype.constructor = mxUndoManager;
+
+/**
+ * Variable: size
+ * 
+ * Maximum command history size. 0 means unlimited history. Default is
+ * 100.
+ */
+mxUndoManager.prototype.size = null;
+
+/**
+ * Variable: history
+ * 
+ * Array that contains the steps of the command history.
+ */
+mxUndoManager.prototype.history = null;
+
+/**
+ * Variable: indexOfNextAdd
+ * 
+ * Index of the element to be added next.
+ */
+mxUndoManager.prototype.indexOfNextAdd = 0;
+
+/**
+ * Function: isEmpty
+ * 
+ * Returns true if the history is empty.
+ */
+mxUndoManager.prototype.isEmpty = function()
+{
+	return this.history.length == 0;
+};
+
+/**
+ * Function: clear
+ * 
+ * Clears the command history.
+ */
+mxUndoManager.prototype.clear = function()
+{
+	this.history = [];
+	this.indexOfNextAdd = 0;
+	this.fireEvent(new mxEventObject(mxEvent.CLEAR));
+};
+
+/**
+ * Function: canUndo
+ * 
+ * Returns true if an undo is possible.
+ */
+mxUndoManager.prototype.canUndo = function()
+{
+	return this.indexOfNextAdd > 0;
+};
+
+/**
+ * Function: undo
+ * 
+ * Undoes the last change.
+ */
+mxUndoManager.prototype.undo = function()
+{
+    while (this.indexOfNextAdd > 0)
+    {
+        var edit = this.history[--this.indexOfNextAdd];
+        edit.undo();
+
+		if (edit.isSignificant())
+        {
+        	this.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit));
+            break;
+        }
+    }
+};
+
+/**
+ * Function: canRedo
+ * 
+ * Returns true if a redo is possible.
+ */
+mxUndoManager.prototype.canRedo = function()
+{
+	return this.indexOfNextAdd < this.history.length;
+};
+
+/**
+ * Function: redo
+ * 
+ * Redoes the last change.
+ */
+mxUndoManager.prototype.redo = function()
+{
+    var n = this.history.length;
+    
+    while (this.indexOfNextAdd < n)
+    {
+        var edit =  this.history[this.indexOfNextAdd++];
+        edit.redo();
+        
+        if (edit.isSignificant())
+        {
+        	this.fireEvent(new mxEventObject(mxEvent.REDO, 'edit', edit));
+            break;
+        }
+    }
+};
+
+/**
+ * Function: undoableEditHappened
+ * 
+ * Method to be called to add new undoable edits to the <history>.
+ */
+mxUndoManager.prototype.undoableEditHappened = function(undoableEdit)
+{
+	this.trim();
+	
+	if (this.size > 0 &&
+		this.size == this.history.length)
+	{
+		this.history.shift();
+	}
+	
+	this.history.push(undoableEdit);
+	this.indexOfNextAdd = this.history.length;
+	this.fireEvent(new mxEventObject(mxEvent.ADD, 'edit', undoableEdit));
+};
+
+/**
+ * Function: trim
+ * 
+ * Removes all pending steps after <indexOfNextAdd> from the history,
+ * invoking die on each edit. This is called from <undoableEditHappened>.
+ */
+mxUndoManager.prototype.trim = function()
+{
+	if (this.history.length > this.indexOfNextAdd)
+	{
+		var edits = this.history.splice(this.indexOfNextAdd,
+			this.history.length - this.indexOfNextAdd);
+			
+		for (var i = 0; i < edits.length; i++)
+		{
+			edits[i].die();
+		}
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/util/mxUndoableEdit.js b/airavata-kubernetes/workflow-composer/src/js/util/mxUndoableEdit.js
new file mode 100644
index 0000000..b12f830
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/util/mxUndoableEdit.js
@@ -0,0 +1,213 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxUndoableEdit
+ * 
+ * Implements a composite undoable edit. Here is an example for a custom change
+ * which gets executed via the model:
+ * 
+ * (code)
+ * function CustomChange(model, name)
+ * {
+ *   this.model = model;
+ *   this.name = name;
+ *   this.previous = name;
+ * };
+ * 
+ * CustomChange.prototype.execute = function()
+ * {
+ *   var tmp = this.model.name;
+ *   this.model.name = this.previous;
+ *   this.previous = tmp;
+ * };
+ * 
+ * var name = prompt('Enter name');
+ * graph.model.execute(new CustomChange(graph.model, name));
+ * (end)
+ * 
+ * Event: mxEvent.EXECUTED
+ * 
+ * Fires between START_EDIT and END_EDIT after an atomic change was executed.
+ * The <code>change</code> property contains the change that was executed.
+ * 
+ * Event: mxEvent.START_EDIT
+ * 
+ * Fires before a set of changes will be executed in <undo> or <redo>.
+ * This event contains no properties.
+ * 
+ * Event: mxEvent.END_EDIT
+ *
+ * Fires after a set of changeswas executed in <undo> or <redo>.
+ * This event contains no properties.
+ * 
+ * Constructor: mxUndoableEdit
+ * 
+ * Constructs a new undoable edit for the given source.
+ */
+function mxUndoableEdit(source, significant)
+{
+	this.source = source;
+	this.changes = [];
+	this.significant = (significant != null) ? significant : true;
+};
+
+/**
+ * Variable: source
+ * 
+ * Specifies the source of the edit.
+ */
+mxUndoableEdit.prototype.source = null;
+
+/**
+ * Variable: changes
+ * 
+ * Array that contains the changes that make up this edit. The changes are
+ * expected to either have an undo and redo function, or an execute
+ * function. Default is an empty array.
+ */
+mxUndoableEdit.prototype.changes = null;
+
+/**
+ * Variable: significant
+ * 
+ * Specifies if the undoable change is significant.
+ * Default is true.
+ */
+mxUndoableEdit.prototype.significant = null;
+
+/**
+ * Variable: undone
+ * 
+ * Specifies if this edit has been undone. Default is false.
+ */
+mxUndoableEdit.prototype.undone = false;
+
+/**
+ * Variable: redone
+ * 
+ * Specifies if this edit has been redone. Default is false.
+ */
+mxUndoableEdit.prototype.redone = false;
+
+/**
+ * Function: isEmpty
+ * 
+ * Returns true if the this edit contains no changes.
+ */
+mxUndoableEdit.prototype.isEmpty = function()
+{
+	return this.changes.length == 0;
+};
+
+/**
+ * Function: isSignificant
+ * 
+ * Returns <significant>.
+ */
+mxUndoableEdit.prototype.isSignificant = function()
+{
+	return this.significant;
+};
+
+/**
+ * Function: add
+ * 
+ * Adds the specified change to this edit. The change is an object that is
+ * expected to either have an undo and redo, or an execute function.
+ */
+mxUndoableEdit.prototype.add = function(change)
+{
+	this.changes.push(change);
+};
+
+/**
+ * Function: notify
+ * 
+ * Hook to notify any listeners of the changes after an <undo> or <redo>
+ * has been carried out. This implementation is empty.
+ */
+mxUndoableEdit.prototype.notify = function() { };
+
+/**
+ * Function: die
+ * 
+ * Hook to free resources after the edit has been removed from the command
+ * history. This implementation is empty.
+ */
+mxUndoableEdit.prototype.die = function() { };
+
+/**
+ * Function: undo
+ * 
+ * Undoes all changes in this edit.
+ */
+mxUndoableEdit.prototype.undo = function()
+{
+	if (!this.undone)
+	{
+		this.source.fireEvent(new mxEventObject(mxEvent.START_EDIT));
+		var count = this.changes.length;
+		
+		for (var i = count - 1; i >= 0; i--)
+		{
+			var change = this.changes[i];
+			
+			if (change.execute != null)
+			{
+				change.execute();
+			}
+			else if (change.undo != null)
+			{
+				change.undo();
+			}
+			
+			// New global executed event
+			this.source.fireEvent(new mxEventObject(mxEvent.EXECUTED, 'change', change));
+		}
+		
+		this.undone = true;
+		this.redone = false;
+		this.source.fireEvent(new mxEventObject(mxEvent.END_EDIT));
+	}
+	
+	this.notify();
+};
+
+/**
+ * Function: redo
+ * 
+ * Redoes all changes in this edit.
+ */
+mxUndoableEdit.prototype.redo = function()
+{
+	if (!this.redone)
+	{
+		this.source.fireEvent(new mxEventObject(mxEvent.START_EDIT));
+		var count = this.changes.length;
+		
+		for (var i = 0; i < count; i++)
+		{
+			var change = this.changes[i];
+			
+			if (change.execute != null)
+			{
+				change.execute();
+			}
+			else if (change.redo != null)
+			{
+				change.redo();
+			}
+			
+			// New global executed event
+			this.source.fireEvent(new mxEventObject(mxEvent.EXECUTED, 'change', change));
+		}
+		
+		this.undone = false;
+		this.redone = true;
+		this.source.fireEvent(new mxEventObject(mxEvent.END_EDIT));
+	}
+	
+	this.notify();
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/util/mxUrlConverter.js b/airavata-kubernetes/workflow-composer/src/js/util/mxUrlConverter.js
new file mode 100644
index 0000000..6aae50e
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/util/mxUrlConverter.js
@@ -0,0 +1,151 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ *
+ * Class: mxUrlConverter
+ * 
+ * Converts relative and absolute URLs to absolute URLs with protocol and domain.
+ */
+var mxUrlConverter = function()
+{
+	// Empty constructor
+};
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies if the converter is enabled. Default is true.
+ */
+mxUrlConverter.prototype.enabled = true;
+
+/**
+ * Variable: baseUrl
+ * 
+ * Specifies the base URL to be used as a prefix for relative URLs.
+ */
+mxUrlConverter.prototype.baseUrl = null;
+
+/**
+ * Variable: baseDomain
+ * 
+ * Specifies the base domain to be used as a prefix for absolute URLs.
+ */
+mxUrlConverter.prototype.baseDomain = null;
+
+/**
+ * Function: updateBaseUrl
+ * 
+ * Private helper function to update the base URL.
+ */
+mxUrlConverter.prototype.updateBaseUrl = function()
+{
+	this.baseDomain = location.protocol + '//' + location.host;
+	this.baseUrl = this.baseDomain + location.pathname;
+	var tmp = this.baseUrl.lastIndexOf('/');
+	
+	// Strips filename etc
+	if (tmp > 0)
+	{
+		this.baseUrl = this.baseUrl.substring(0, tmp + 1);
+	}
+};
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns <enabled>.
+ */
+mxUrlConverter.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ * 
+ * Sets <enabled>.
+ */
+mxUrlConverter.prototype.setEnabled = function(value)
+{
+	this.enabled = value;
+};
+
+/**
+ * Function: getBaseUrl
+ * 
+ * Returns <baseUrl>.
+ */
+mxUrlConverter.prototype.getBaseUrl = function()
+{
+	return this.baseUrl;
+};
+
+/**
+ * Function: setBaseUrl
+ * 
+ * Sets <baseUrl>.
+ */
+mxUrlConverter.prototype.setBaseUrl = function(value)
+{
+	this.baseUrl = value;
+};
+
+/**
+ * Function: getBaseDomain
+ * 
+ * Returns <baseDomain>.
+ */
+mxUrlConverter.prototype.getBaseDomain = function()
+{
+	return this.baseDomain;
+},
+
+/**
+ * Function: setBaseDomain
+ * 
+ * Sets <baseDomain>.
+ */
+mxUrlConverter.prototype.setBaseDomain = function(value)
+{
+	this.baseDomain = value;
+},
+
+/**
+ * Function: isRelativeUrl
+ * 
+ * Returns true if the given URL is relative.
+ */
+mxUrlConverter.prototype.isRelativeUrl = function(url)
+{
+	return url.substring(0, 2) != '//' && url.substring(0, 7) != 'http://' && url.substring(0, 8) != 'https://' && url.substring(0, 10) != 'data:image';
+};
+
+/**
+ * Function: convert
+ * 
+ * Converts the given URL to an absolute URL with protol and domain.
+ * Relative URLs are first converted to absolute URLs.
+ */
+mxUrlConverter.prototype.convert = function(url)
+{
+	if (this.isEnabled() && this.isRelativeUrl(url))
+	{
+		if (this.getBaseUrl() == null)
+		{
+			this.updateBaseUrl();
+		}
+		
+		if (url.charAt(0) == '/')
+		{
+			url = this.getBaseDomain() + url;
+		}
+		else
+		{
+			url = this.getBaseUrl() + url;
+		}
+	}
+	
+	return url;
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/util/mxUtils.js b/airavata-kubernetes/workflow-composer/src/js/util/mxUtils.js
new file mode 100644
index 0000000..ef5a268
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/util/mxUtils.js
@@ -0,0 +1,4353 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+var mxUtils =
+{
+	/**
+	 * Class: mxUtils
+	 * 
+	 * A singleton class that provides cross-browser helper methods.
+	 * This is a global functionality. To access the functions in this
+	 * class, use the global classname appended by the functionname.
+	 * You may have to load chrome://global/content/contentAreaUtils.js
+	 * to disable certain security restrictions in Mozilla for the <open>,
+	 * <save>, <saveAs> and <copy> function.
+	 * 
+	 * For example, the following code displays an error message:
+	 * 
+	 * (code)
+	 * mxUtils.error('Browser is not supported!', 200, false);
+	 * (end)
+	 * 
+	 * Variable: errorResource
+	 * 
+	 * Specifies the resource key for the title of the error window. If the
+	 * resource for this key does not exist then the value is used as
+	 * the title. Default is 'error'.
+	 */
+	errorResource: (mxClient.language != 'none') ? 'error' : '',
+	
+	/**
+	 * Variable: closeResource
+	 * 
+	 * Specifies the resource key for the label of the close button. If the
+	 * resource for this key does not exist then the value is used as
+	 * the label. Default is 'close'.
+	 */
+	closeResource: (mxClient.language != 'none') ? 'close' : '',
+
+	/**
+	 * Variable: errorImage
+	 * 
+	 * Defines the image used for error dialogs.
+	 */
+	errorImage: mxClient.imageBasePath + '/error.gif',
+	
+	/**
+	 * Function: removeCursors
+	 * 
+	 * Removes the cursors from the style of the given DOM node and its
+	 * descendants.
+	 * 
+	 * Parameters:
+	 * 
+	 * element - DOM node to remove the cursor style from.
+	 */
+	removeCursors: function(element)
+	{
+		if (element.style != null)
+		{
+			element.style.cursor = '';
+		}
+		
+		var children = element.childNodes;
+		
+		if (children != null)
+		{
+	        var childCount = children.length;
+	        
+	        for (var i = 0; i < childCount; i += 1)
+	        {
+	            mxUtils.removeCursors(children[i]);
+	        }
+	    }
+	},
+
+	/**
+	 * Function: getCurrentStyle
+	 * 
+	 * Returns the current style of the specified element.
+	 * 
+	 * Parameters:
+	 * 
+	 * element - DOM node whose current style should be returned.
+	 */
+	getCurrentStyle: function()
+	{
+		if (mxClient.IS_IE)
+		{
+			return function(element)
+			{
+				return (element != null) ? element.currentStyle : null;
+			};
+		}
+		else
+		{
+			return function(element)
+			{
+				return (element != null) ?
+					window.getComputedStyle(element, '') :
+					null;
+			};
+		}
+	}(),
+	
+	/**
+	 * Function: parseCssNumber
+	 * 
+	 * Parses the given CSS numeric value adding handling for the values thin,
+	 * medium and thick (2, 4 and 6).
+	 */
+	parseCssNumber: function(value)
+	{
+		if (value == 'thin')
+		{
+			value = '2';
+		}
+		else if (value == 'medium')
+		{
+			value = '4';
+		}
+		else if (value == 'thick')
+		{
+			value = '6';
+		}
+		
+		value = parseFloat(value);
+		
+		if (isNaN(value))
+		{
+			value = 0;
+		}
+		
+		return value;
+	},
+
+	/**
+	 * Function: setPrefixedStyle
+	 * 
+	 * Adds the given style with the standard name and an optional vendor prefix for the current
+	 * browser.
+	 * 
+	 * (code)
+	 * mxUtils.setPrefixedStyle(node.style, 'transformOrigin', '0% 0%');
+	 * (end)
+	 */
+	setPrefixedStyle: function()
+	{
+		var prefix = null;
+		
+		if (mxClient.IS_OT)
+		{
+			prefix = 'O';
+		}
+		else if (mxClient.IS_SF || mxClient.IS_GC)
+		{
+			prefix = 'Webkit';
+		}
+		else if (mxClient.IS_MT)
+		{
+			prefix = 'Moz';
+		}
+		else if (mxClient.IS_IE && document.documentMode >= 9 && document.documentMode < 10)
+		{
+			prefix = 'ms';
+		}
+
+		return function(style, name, value)
+		{
+			style[name] = value;
+			
+			if (prefix != null && name.length > 0)
+			{
+				name = prefix + name.substring(0, 1).toUpperCase() + name.substring(1);
+				style[name] = value;
+			}
+		};
+	}(),
+	
+	/**
+	 * Function: hasScrollbars
+	 * 
+	 * Returns true if the overflow CSS property of the given node is either
+	 * scroll or auto.
+	 * 
+	 * Parameters:
+	 * 
+	 * node - DOM node whose style should be checked for scrollbars.
+	 */
+	hasScrollbars: function(node)
+	{
+		var style = mxUtils.getCurrentStyle(node);
+
+		return style != null && (style.overflow == 'scroll' || style.overflow == 'auto');
+	},
+	
+	/**
+	 * Function: bind
+	 * 
+	 * Returns a wrapper function that locks the execution scope of the given
+	 * function to the specified scope. Inside funct, the "this" keyword
+	 * becomes a reference to that scope.
+	 */
+	bind: function(scope, funct)
+	{
+		return function()
+		{
+			return funct.apply(scope, arguments);
+		};
+	},
+	
+	/**
+	 * Function: eval
+	 * 
+	 * Evaluates the given expression using eval and returns the JavaScript
+	 * object that represents the expression result. Supports evaluation of
+	 * expressions that define functions and returns the function object for
+	 * these expressions.
+	 * 
+	 * Parameters:
+	 * 
+	 * expr - A string that represents a JavaScript expression.
+	 */
+	eval: function(expr)
+	{
+		var result = null;
+
+		if (expr.indexOf('function') >= 0)
+		{
+			try
+			{
+				eval('var _mxJavaScriptExpression='+expr);
+				result = _mxJavaScriptExpression;
+				// TODO: Use delete here?
+				_mxJavaScriptExpression = null;
+			}
+			catch (e)
+			{
+				mxLog.warn(e.message + ' while evaluating ' + expr);
+			}
+		}
+		else
+		{
+			try
+			{
+				result = eval(expr);
+			}
+			catch (e)
+			{
+				mxLog.warn(e.message + ' while evaluating ' + expr);
+			}
+		}
+		
+		return result;
+	},
+	
+	/**
+	 * Function: findNode
+	 * 
+	 * Returns the first node where attr equals value.
+	 * This implementation does not use XPath.
+	 */
+	findNode: function(node, attr, value)
+	{
+		if (node.nodeType == mxConstants.NODETYPE_ELEMENT)
+		{
+			var tmp = node.getAttribute(attr);
+	
+			if (tmp != null && tmp == value)
+			{
+				return node;
+			}
+		}
+		
+		node = node.firstChild;
+		
+		while (node != null)
+		{
+			var result = mxUtils.findNode(node, attr, value);
+			
+			if (result != null)
+			{
+				return result;
+			}
+			
+			node = node.nextSibling;
+		}
+		
+		return null;
+	},
+
+	/**
+	 * Function: getFunctionName
+	 * 
+	 * Returns the name for the given function.
+	 * 
+	 * Parameters:
+	 * 
+	 * f - JavaScript object that represents a function.
+	 */
+	getFunctionName: function(f)
+	{
+		var str = null;
+
+		if (f != null)
+		{
+			if (f.name != null)
+			{
+				str = f.name;
+			}
+			else
+			{
+				str = mxUtils.trim(f.toString());
+				
+				if (/^function\s/.test(str))
+				{
+					str = mxUtils.ltrim(str.substring(9));
+					var idx2 = str.indexOf('(');
+					
+					if (idx2 > 0)
+					{
+						str = str.substring(0, idx2);
+					}
+				}
+			}
+		}
+		
+		return str;
+	},
+
+	/**
+	 * Function: indexOf
+	 * 
+	 * Returns the index of obj in array or -1 if the array does not contain
+	 * the given object.
+	 * 
+	 * Parameters:
+	 * 
+	 * array - Array to check for the given obj.
+	 * obj - Object to find in the given array.
+	 */
+	indexOf: function(array, obj)
+	{
+		if (array != null && obj != null)
+		{
+			for (var i = 0; i < array.length; i++)
+			{
+				if (array[i] == obj)
+				{
+					return i;
+				}
+			}
+		}
+		
+		return -1;
+	},
+
+	/**
+	 * Function: forEach
+	 * 
+	 * Calls the given function for each element of the given array and returns
+	 * the array.
+	 * 
+	 * Parameters:
+	 * 
+	 * array - Array that contains the elements.
+	 * fn - Function to be called for each object.
+	 */
+	forEach: function(array, fn)
+	{
+		if (array != null && fn != null)
+		{
+			for (var i = 0; i < array.length; i++)
+			{
+				fn(array[i]);
+			}
+		}
+		
+		return array;
+	},
+
+	/**
+	 * Function: remove
+	 * 
+	 * Removes all occurrences of the given object in the given array or
+	 * object. If there are multiple occurrences of the object, be they
+	 * associative or as an array entry, all occurrences are removed from
+	 * the array or deleted from the object. By removing the object from
+	 * the array, all elements following the removed element are shifted
+	 * by one step towards the beginning of the array.
+	 * 
+	 * The length of arrays is not modified inside this function.
+	 * 
+	 * Parameters:
+	 * 
+	 * obj - Object to find in the given array.
+	 * array - Array to check for the given obj.
+	 */
+	remove: function(obj, array)
+	{
+		var result = null;
+		
+		if (typeof(array) == 'object')
+		{
+			var index = mxUtils.indexOf(array, obj);
+			
+			while (index >= 0)
+			{
+				array.splice(index, 1);
+				result = obj;
+				index = mxUtils.indexOf(array, obj);
+			}
+		}
+
+		for (var key in array)
+		{
+			if (array[key] == obj)
+			{
+				delete array[key];
+				result = obj;
+			}
+		}
+		
+		return result;
+	},
+	
+	/**
+	 * Function: isNode
+	 * 
+	 * Returns true if the given value is an XML node with the node name
+	 * and if the optional attribute has the specified value.
+	 * 
+	 * This implementation assumes that the given value is a DOM node if the
+	 * nodeType property is numeric, that is, if isNaN returns false for
+	 * value.nodeType.
+	 * 
+	 * Parameters:
+	 * 
+	 * value - Object that should be examined as a node.
+	 * nodeName - String that specifies the node name.
+	 * attributeName - Optional attribute name to check.
+	 * attributeValue - Optional attribute value to check.
+	 */
+	 isNode: function(value, nodeName, attributeName, attributeValue)
+	 {
+	 	if (value != null && !isNaN(value.nodeType) && (nodeName == null ||
+	 		value.nodeName.toLowerCase() == nodeName.toLowerCase()))
+ 		{
+ 			return attributeName == null ||
+ 				value.getAttribute(attributeName) == attributeValue;
+ 		}
+	 	
+	 	return false;
+	 },
+	
+	/**
+	 * Function: isAncestorNode
+	 * 
+	 * Returns true if the given ancestor is an ancestor of the
+	 * given DOM node in the DOM. This also returns true if the
+	 * child is the ancestor.
+	 * 
+	 * Parameters:
+	 * 
+	 * ancestor - DOM node that represents the ancestor.
+	 * child - DOM node that represents the child.
+	 */
+	 isAncestorNode: function(ancestor, child)
+	 {
+	 	var parent = child;
+	 	
+	 	while (parent != null)
+	 	{
+	 		if (parent == ancestor)
+	 		{
+	 			return true;
+	 		}
+
+	 		parent = parent.parentNode;
+	 	}
+	 	
+	 	return false;
+	 },
+
+	/**
+	 * Function: getChildNodes
+	 * 
+	 * Returns an array of child nodes that are of the given node type.
+	 * 
+	 * Parameters:
+	 * 
+	 * node - Parent DOM node to return the children from.
+	 * nodeType - Optional node type to return. Default is
+	 * <mxConstants.NODETYPE_ELEMENT>.
+	 */
+	getChildNodes: function(node, nodeType)
+	{
+		nodeType = nodeType || mxConstants.NODETYPE_ELEMENT;
+		
+		var children = [];
+		var tmp = node.firstChild;
+		
+		while (tmp != null)
+		{
+			if (tmp.nodeType == nodeType)
+			{
+				children.push(tmp);
+			}
+			
+			tmp = tmp.nextSibling;
+		}
+		
+		return children;
+	},
+
+	/**
+	 * Function: importNode
+	 * 
+	 * Cross browser implementation for document.importNode. Uses document.importNode
+	 * in all browsers but IE, where the node is cloned by creating a new node and
+	 * copying all attributes and children into it using importNode, recursively.
+	 * 
+	 * Parameters:
+	 * 
+	 * doc - Document to import the node into.
+	 * node - Node to be imported.
+	 * allChildren - If all children should be imported.
+	 */
+	importNode: function(doc, node, allChildren)
+	{
+		if (mxClient.IS_IE && (document.documentMode == null || document.documentMode < 10))
+		{
+			switch (node.nodeType)
+			{
+				case 1: /* element */
+				{
+					var newNode = doc.createElement(node.nodeName);
+					
+					if (node.attributes && node.attributes.length > 0)
+					{
+						for (var i = 0; i < node.attributes.length; i++)
+						{
+							newNode.setAttribute(node.attributes[i].nodeName,
+								node.getAttribute(node.attributes[i].nodeName));
+						}
+						
+						if (allChildren && node.childNodes && node.childNodes.length > 0)
+						{
+							for (var i = 0; i < node.childNodes.length; i++)
+							{
+								newNode.appendChild(mxUtils.importNode(doc, node.childNodes[i], allChildren));
+							}
+						}
+					}
+					
+					return newNode;
+					break;
+				}
+				case 3: /* text */
+			    case 4: /* cdata-section */
+			    case 8: /* comment */
+			    {
+			      return doc.createTextNode(node.value);
+			      break;
+			    }
+			};
+		}
+		else
+		{
+			return doc.importNode(node, allChildren);
+		}
+	},
+
+	/**
+	 * Function: createXmlDocument
+	 * 
+	 * Returns a new, empty XML document.
+	 */
+	createXmlDocument: function()
+	{
+		var doc = null;
+		
+		if (document.implementation && document.implementation.createDocument)
+		{
+			doc = document.implementation.createDocument('', '', null);
+		}
+		else if (window.ActiveXObject)
+		{
+			doc = new ActiveXObject('Microsoft.XMLDOM');
+	 	}
+	 	
+	 	return doc;
+	},
+
+	/**
+	 * Function: parseXml
+	 * 
+	 * Parses the specified XML string into a new XML document and returns the
+	 * new document.
+	 * 
+	 * Example:
+	 * 
+	 * (code)
+	 * var doc = mxUtils.parseXml(
+	 *   '<mxGraphModel><root><MyDiagram id="0"><mxCell/></MyDiagram>'+
+	 *   '<MyLayer id="1"><mxCell parent="0" /></MyLayer><MyObject id="2">'+
+	 *   '<mxCell style="strokeColor=blue;fillColor=red" parent="1" vertex="1">'+
+	 *   '<mxGeometry x="10" y="10" width="80" height="30" as="geometry"/>'+
+	 *   '</mxCell></MyObject></root></mxGraphModel>');
+	 * (end)
+	 * 
+	 * Parameters:
+	 * 
+	 * xml - String that contains the XML data.
+	 */
+	parseXml: function()
+	{
+		if (window.DOMParser)
+		{
+			return function(xml)
+			{
+				var parser = new DOMParser();
+				
+				return parser.parseFromString(xml, 'text/xml');
+			};
+		}
+		else // IE<=9
+		{
+			return function(xml)
+			{
+				var result = mxUtils.createXmlDocument();
+				result.async = false;
+				// Workaround for parsing errors with SVG DTD
+				result.validateOnParse = false;
+				result.resolveExternals = false;
+				result.loadXML(xml);
+				
+				return result;
+			};
+		}
+	}(),
+
+	/**
+	 * Function: clearSelection
+	 * 
+	 * Clears the current selection in the page.
+	 */
+	clearSelection: function()
+	{
+		if (document.selection)
+		{
+			return function()
+			{
+				document.selection.empty();
+			};
+		}
+		else if (window.getSelection)
+		{
+			return function()
+			{
+				window.getSelection().removeAllRanges();
+			};
+		}
+		else
+		{
+			return function() { };
+		}
+	}(),
+
+	/**
+	 * Function: getPrettyXML
+	 * 
+	 * Returns a pretty printed string that represents the XML tree for the
+	 * given node. This method should only be used to print XML for reading,
+	 * use <getXml> instead to obtain a string for processing.
+	 * 
+	 * Parameters:
+	 * 
+	 * node - DOM node to return the XML for.
+	 * tab - Optional string that specifies the indentation for one level.
+	 * Default is two spaces.
+	 * indent - Optional string that represents the current indentation.
+	 * Default is an empty string.
+	 */
+	getPrettyXml: function(node, tab, indent)
+	{
+		var result = [];
+		
+		if (node != null)
+		{
+			tab = tab || '  ';
+			indent = indent || '';
+			
+			if (node.nodeType == mxConstants.NODETYPE_TEXT)
+			{
+				result.push(node.value);
+			}
+			else
+			{
+				result.push(indent + '<' + node.nodeName);
+				
+				// Creates the string with the node attributes
+				// and converts all HTML entities in the values
+				var attrs = node.attributes;
+				
+				if (attrs != null)
+				{
+					for (var i = 0; i < attrs.length; i++)
+					{
+						var val = mxUtils.htmlEntities(attrs[i].value);
+						result.push(' ' + attrs[i].nodeName + '="' + val + '"');
+					}
+				}
+
+				// Recursively creates the XML string for each
+				// child nodes and appends it here with an
+				// indentation
+				var tmp = node.firstChild;
+				
+				if (tmp != null)
+				{
+					result.push('>\n');
+					
+					while (tmp != null)
+					{
+						result.push(mxUtils.getPrettyXml(tmp, tab, indent + tab));
+						tmp = tmp.nextSibling;
+					}
+					
+					result.push(indent + '</'+node.nodeName + '>\n');
+				}
+				else
+				{
+					result.push('/>\n');
+				}
+			}
+		}
+		
+		return result.join('');
+	},
+	
+	/**
+	 * Function: removeWhitespace
+	 * 
+	 * Removes the sibling text nodes for the given node that only consists
+	 * of tabs, newlines and spaces.
+	 * 
+	 * Parameters:
+	 * 
+	 * node - DOM node whose siblings should be removed.
+	 * before - Optional boolean that specifies the direction of the traversal.
+	 */
+	removeWhitespace: function(node, before)
+	{
+		var tmp = (before) ? node.previousSibling : node.nextSibling;
+		
+		while (tmp != null && tmp.nodeType == mxConstants.NODETYPE_TEXT)
+		{
+			var next = (before) ? tmp.previousSibling : tmp.nextSibling;
+			var text = mxUtils.getTextContent(tmp);
+			
+			if (mxUtils.trim(text).length == 0)
+			{
+				tmp.parentNode.removeChild(tmp);
+			}
+			
+			tmp = next;
+		}
+	},
+	
+	/**
+	 * Function: htmlEntities
+	 * 
+	 * Replaces characters (less than, greater than, newlines and quotes) with
+	 * their HTML entities in the given string and returns the result.
+	 * 
+	 * Parameters:
+	 * 
+	 * s - String that contains the characters to be converted.
+	 * newline - If newlines should be replaced. Default is true.
+	 */
+	htmlEntities: function(s, newline)
+	{
+		s = String(s || '');
+		
+		s = s.replace(/&/g,'&amp;'); // 38 26
+		s = s.replace(/"/g,'&quot;'); // 34 22
+		s = s.replace(/\'/g,'&#39;'); // 39 27
+		s = s.replace(/</g,'&lt;'); // 60 3C
+		s = s.replace(/>/g,'&gt;'); // 62 3E
+
+		if (newline == null || newline)
+		{
+			s = s.replace(/\n/g, '&#xa;');
+		}
+		
+		return s;
+	},
+	
+	/**
+	 * Function: isVml
+	 * 
+	 * Returns true if the given node is in the VML namespace.
+	 * 
+	 * Parameters:
+	 * 
+	 * node - DOM node whose tag urn should be checked.
+	 */
+	isVml: function(node)
+	{
+		return node != null && node.tagUrn == 'urn:schemas-microsoft-com:vml';
+	},
+
+	/**
+	 * Function: getXml
+	 * 
+	 * Returns the XML content of the specified node. For Internet Explorer,
+	 * all \r\n\t[\t]* are removed from the XML string and the remaining \r\n
+	 * are replaced by \n. All \n are then replaced with linefeed, or &#xa; if
+	 * no linefeed is defined.
+	 * 
+	 * Parameters:
+	 * 
+	 * node - DOM node to return the XML for.
+	 * linefeed - Optional string that linefeeds are converted into. Default is
+	 * &#xa;
+	 */
+	getXml: function(node, linefeed)
+	{
+		var xml = '';
+
+		if (window.XMLSerializer != null)
+		{
+			var xmlSerializer = new XMLSerializer();
+			xml = xmlSerializer.serializeToString(node);     
+		}
+		else if (node.xml != null)
+		{
+			xml = node.xml.replace(/\r\n\t[\t]*/g, '').
+				replace(/>\r\n/g, '>').
+				replace(/\r\n/g, '\n');
+		}
+
+		// Replaces linefeeds with HTML Entities.
+		linefeed = linefeed || '&#xa;';
+		xml = xml.replace(/\n/g, linefeed);
+		  
+		return xml;
+	},
+	
+	/**
+	 * Function: extractTextWithWhitespace
+	 * 
+	 * Returns the text content of the specified node.
+	 * 
+	 * Parameters:
+	 * 
+	 * elems - DOM nodes to return the text for.
+	 */
+	extractTextWithWhitespace: function(elems)
+	{
+	    // Known block elements for handling linefeeds (list is not complete)
+		var blocks = ['BLOCKQUOTE', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'OL', 'P', 'PRE', 'TABLE', 'UL'];
+		var ret = [];
+		
+		function doExtract(elts)
+		{
+			// Single break should be ignored
+			if (elts.length == 1 && (elts[0].nodeName == 'BR' ||
+				elts[0].innerHTML == '\n'))
+			{
+				return;
+			}
+			
+		    for (var i = 0; i < elts.length; i++)
+		    {
+		        var elem = elts[i];
+
+				// DIV with a br or linefeed forces a linefeed
+				if (elem.nodeName == 'BR' || elem.innerHTML == '\n' ||
+					((elts.length == 1 || i == 0) && (elem.nodeName == 'DIV' &&
+					elem.innerHTML.toLowerCase() == '<br>')))
+		    	{
+	    			ret.push('\n');
+		    	}
+				else
+				{
+			        if (elem.nodeType === 3 || elem.nodeType === 4)
+			        {
+			        	if (elem.nodeValue.length > 0)
+			        	{
+			        		ret.push(elem.nodeValue);
+			        	}
+			        }
+			        else if (elem.nodeType !== 8 && elem.childNodes.length > 0)
+					{
+						doExtract(elem.childNodes);
+					}
+			        
+	        		if (i < elts.length - 1 && mxUtils.indexOf(blocks, elts[i + 1].nodeName) >= 0)
+	        		{
+	        			ret.push('\n');		
+	        		}
+				}
+		    }
+		};
+		
+		doExtract(elems);
+	    
+	    return ret.join('');
+	},
+
+	/**
+	 * Function: replaceTrailingNewlines
+	 * 
+	 * Replaces each trailing newline with the given pattern.
+	 */
+	replaceTrailingNewlines: function(str, pattern)
+	{
+		// LATER: Check is this can be done with a regular expression
+		var postfix = '';
+		
+		while (str.length > 0 && str.charAt(str.length - 1) == '\n')
+		{
+			str = str.substring(0, str.length - 1);
+			postfix += pattern;
+		}
+		
+		return str + postfix;
+	},
+
+	/**
+	 * Function: getTextContent
+	 * 
+	 * Returns the text content of the specified node.
+	 * 
+	 * Parameters:
+	 * 
+	 * node - DOM node to return the text content for.
+	 */
+	getTextContent: function(node)
+	{
+		if (node.innerText !== undefined)
+		{
+			return node.innerText;
+		}
+		else
+		{
+			return (node != null) ? node[(node.textContent === undefined) ? 'text' : 'textContent'] : '';
+		}
+	},
+	
+	/**
+	 * Function: setTextContent
+	 * 
+	 * Sets the text content of the specified node.
+	 * 
+	 * Parameters:
+	 * 
+	 * node - DOM node to set the text content for.
+	 * text - String that represents the text content.
+	 */
+	setTextContent: function(node, text)
+	{
+		if (node.innerText !== undefined)
+		{
+			node.innerText = text;
+		}
+		else
+		{
+			node[(node.textContent === undefined) ? 'text' : 'textContent'] = text;
+		}
+	},
+	
+	/**
+	 * Function: getInnerHtml
+	 * 
+	 * Returns the inner HTML for the given node as a string or an empty string
+	 * if no node was specified. The inner HTML is the text representing all
+	 * children of the node, but not the node itself.
+	 * 
+	 * Parameters:
+	 * 
+	 * node - DOM node to return the inner HTML for.
+	 */
+	getInnerHtml: function()
+	{
+		if (mxClient.IS_IE)
+		{
+			return function(node)
+			{
+				if (node != null)
+				{
+					return node.innerHTML;
+				}
+				
+				return '';
+			};
+		}
+		else
+		{
+			return function(node)
+			{
+				if (node != null)
+				{
+					var serializer = new XMLSerializer();
+					return serializer.serializeToString(node);
+				}
+				
+				return '';
+			};
+		}
+	}(),
+
+	/**
+	 * Function: getOuterHtml
+	 * 
+	 * Returns the outer HTML for the given node as a string or an empty
+	 * string if no node was specified. The outer HTML is the text representing
+	 * all children of the node including the node itself.
+	 * 
+	 * Parameters:
+	 * 
+	 * node - DOM node to return the outer HTML for.
+	 */
+	getOuterHtml: function()
+	{
+		if (mxClient.IS_IE)
+		{
+			return function(node)
+			{
+				if (node != null)
+				{
+					if (node.outerHTML != null)
+					{
+						return node.outerHTML;
+					}
+					else
+					{
+						var tmp = [];
+						tmp.push('<'+node.nodeName);
+						
+						var attrs = node.attributes;
+						
+						if (attrs != null)
+						{
+							for (var i = 0; i < attrs.length; i++)
+							{
+								var value = attrs[i].value;
+								
+								if (value != null && value.length > 0)
+								{
+									tmp.push(' ');
+									tmp.push(attrs[i].nodeName);
+									tmp.push('="');
+									tmp.push(value);
+									tmp.push('"');
+								}
+							}
+						}
+						
+						if (node.innerHTML.length == 0)
+						{
+							tmp.push('/>');
+						}
+						else
+						{
+							tmp.push('>');
+							tmp.push(node.innerHTML);
+							tmp.push('</'+node.nodeName+'>');
+						}
+						
+						return tmp.join('');
+					}
+				}
+				
+				return '';
+			};
+		}
+		else
+		{
+			return function(node)
+			{
+				if (node != null)
+				{
+					var serializer = new XMLSerializer();
+					return serializer.serializeToString(node);
+				}
+				
+				return '';
+			};
+		}
+	}(),
+	
+	/**
+	 * Function: write
+	 * 
+	 * Creates a text node for the given string and appends it to the given
+	 * parent. Returns the text node.
+	 * 
+	 * Parameters:
+	 * 
+	 * parent - DOM node to append the text node to.
+	 * text - String representing the text to be added.
+	 */
+	write: function(parent, text)
+	{
+		var doc = parent.ownerDocument;
+		var node = doc.createTextNode(text);
+		
+		if (parent != null)
+		{
+			parent.appendChild(node);
+		}
+		
+		return node;
+	},
+	
+	/**
+	 * Function: writeln
+	 * 
+	 * Creates a text node for the given string and appends it to the given
+	 * parent with an additional linefeed. Returns the text node.
+	 * 
+	 * Parameters:
+	 * 
+	 * parent - DOM node to append the text node to.
+	 * text - String representing the text to be added.
+	 */
+	writeln: function(parent, text)
+	{
+		var doc = parent.ownerDocument;
+		var node = doc.createTextNode(text);
+		
+		if (parent != null)
+		{
+			parent.appendChild(node);
+			parent.appendChild(document.createElement('br'));
+		}
+		
+		return node;
+	},
+	
+	/**
+	 * Function: br
+	 * 
+	 * Appends a linebreak to the given parent and returns the linebreak.
+	 * 
+	 * Parameters:
+	 * 
+	 * parent - DOM node to append the linebreak to.
+	 */
+	br: function(parent, count)
+	{
+		count = count || 1;
+		var br = null;
+		
+		for (var i = 0; i < count; i++)
+		{
+			if (parent != null)
+			{
+				br = parent.ownerDocument.createElement('br');
+				parent.appendChild(br);
+			}
+		}
+		
+		return br;
+	},
+		
+	/**
+	 * Function: button
+	 * 
+	 * Returns a new button with the given level and function as an onclick
+	 * event handler.
+	 * 
+	 * (code)
+	 * document.body.appendChild(mxUtils.button('Test', function(evt)
+	 * {
+	 *   alert('Hello, World!');
+	 * }));
+	 * (end)
+	 * 
+	 * Parameters:
+	 * 
+	 * label - String that represents the label of the button.
+	 * funct - Function to be called if the button is pressed.
+	 * doc - Optional document to be used for creating the button. Default is the
+	 * current document.
+	 */
+	button: function(label, funct, doc)
+	{
+		doc = (doc != null) ? doc : document;
+		
+		var button = doc.createElement('button');
+		mxUtils.write(button, label);
+
+		mxEvent.addListener(button, 'click', function(evt)
+		{
+			funct(evt);
+		});
+		
+		return button;
+	},
+	
+	/**
+	 * Function: para
+	 * 
+	 * Appends a new paragraph with the given text to the specified parent and
+	 * returns the paragraph.
+	 * 
+	 * Parameters:
+	 * 
+	 * parent - DOM node to append the text node to.
+	 * text - String representing the text for the new paragraph.
+	 */
+	para: function(parent, text)
+	{
+		var p = document.createElement('p');
+		mxUtils.write(p, text);
+
+		if (parent != null)
+		{
+			parent.appendChild(p);
+		}
+		
+		return p;
+	},
+
+	/**
+	 * Function: addTransparentBackgroundFilter
+	 * 
+	 * Adds a transparent background to the filter of the given node. This
+	 * background can be used in IE8 standards mode (native IE8 only) to pass
+	 * events through the node.
+	 */
+	addTransparentBackgroundFilter: function(node)
+	{
+		node.style.filter += 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'' +
+			mxClient.imageBasePath + '/transparent.gif\', sizingMethod=\'scale\')';
+	},
+
+	/**
+	 * Function: linkAction
+	 * 
+	 * Adds a hyperlink to the specified parent that invokes action on the
+	 * specified editor.
+	 * 
+	 * Parameters:
+	 * 
+	 * parent - DOM node to contain the new link.
+	 * text - String that is used as the link label.
+	 * editor - <mxEditor> that will execute the action.
+	 * action - String that defines the name of the action to be executed.
+	 * pad - Optional left-padding for the link. Default is 0.
+	 */
+	linkAction: function(parent, text, editor, action, pad)
+	{
+		return mxUtils.link(parent, text, function()
+		{
+			editor.execute(action);
+		}, pad);
+	},
+
+	/**
+	 * Function: linkInvoke
+	 * 
+	 * Adds a hyperlink to the specified parent that invokes the specified
+	 * function on the editor passing along the specified argument. The
+	 * function name is the name of a function of the editor instance,
+	 * not an action name.
+	 * 
+	 * Parameters:
+	 * 
+	 * parent - DOM node to contain the new link.
+	 * text - String that is used as the link label.
+	 * editor - <mxEditor> instance to execute the function on.
+	 * functName - String that represents the name of the function.
+	 * arg - Object that represents the argument to the function.
+	 * pad - Optional left-padding for the link. Default is 0.
+	 */
+	linkInvoke: function(parent, text, editor, functName, arg, pad)
+	{
+		return mxUtils.link(parent, text, function()
+		{
+			editor[functName](arg);
+		}, pad);
+	},
+	
+	/**
+	 * Function: link
+	 * 
+	 * Adds a hyperlink to the specified parent and invokes the given function
+	 * when the link is clicked.
+	 * 
+	 * Parameters:
+	 * 
+	 * parent - DOM node to contain the new link.
+	 * text - String that is used as the link label.
+	 * funct - Function to execute when the link is clicked.
+	 * pad - Optional left-padding for the link. Default is 0.
+	 */
+	link: function(parent, text, funct, pad)
+	{
+		var a = document.createElement('span');
+		
+		a.style.color = 'blue';
+		a.style.textDecoration = 'underline';
+		a.style.cursor = 'pointer';
+		
+		if (pad != null)
+		{
+			a.style.paddingLeft = pad+'px';
+		}
+		
+		mxEvent.addListener(a, 'click', funct);
+		mxUtils.write(a, text);
+		
+		if (parent != null)
+		{
+			parent.appendChild(a);
+		}
+		
+		return a;
+	},
+
+	/**
+	 * Function: fit
+	 * 
+	 * Makes sure the given node is inside the visible area of the window. This
+	 * is done by setting the left and top in the style. 
+	 */
+	fit: function(node)
+	{
+		var left = parseInt(node.offsetLeft);
+		var width = parseInt(node.offsetWidth);
+			
+		var offset = mxUtils.getDocumentScrollOrigin(node.ownerDocument);
+		var sl = offset.x;
+		var st = offset.y;
+
+		var b = document.body;
+		var d = document.documentElement;
+		var right = (sl) + (b.clientWidth || d.clientWidth);
+		
+		if (left + width > right)
+		{
+			node.style.left = Math.max(sl, right - width) + 'px';
+		}
+		
+		var top = parseInt(node.offsetTop);
+		var height = parseInt(node.offsetHeight);
+		
+		var bottom = st + Math.max(b.clientHeight || 0, d.clientHeight);
+		
+		if (top + height > bottom)
+		{
+			node.style.top = Math.max(st, bottom - height) + 'px';
+		}
+	},
+
+	/**
+	 * Function: load
+	 * 
+	 * Loads the specified URL *synchronously* and returns the <mxXmlRequest>.
+	 * Throws an exception if the file cannot be loaded. See <mxUtils.get> for
+	 * an asynchronous implementation.
+	 *
+	 * Example:
+	 * 
+	 * (code)
+	 * try
+	 * {
+	 *   var req = mxUtils.load(filename);
+	 *   var root = req.getDocumentElement();
+	 *   // Process XML DOM...
+	 * }
+	 * catch (ex)
+	 * {
+	 *   mxUtils.alert('Cannot load '+filename+': '+ex);
+	 * }
+	 * (end)
+	 * 
+	 * Parameters:
+	 * 
+	 * url - URL to get the data from.
+	 */
+	load: function(url)
+	{
+		var req = new mxXmlRequest(url, null, 'GET', false);
+		req.send();
+		
+		return req;
+	},
+
+	/**
+	 * Function: get
+	 * 
+	 * Loads the specified URL *asynchronously* and invokes the given functions
+	 * depending on the request status. Returns the <mxXmlRequest> in use. Both
+	 * functions take the <mxXmlRequest> as the only parameter. See
+	 * <mxUtils.load> for a synchronous implementation.
+	 *
+	 * Example:
+	 * 
+	 * (code)
+	 * mxUtils.get(url, function(req)
+	 * {
+	 *    var node = req.getDocumentElement();
+	 *    // Process XML DOM...
+	 * });
+	 * (end)
+	 * 
+	 * So for example, to load a diagram into an existing graph model, the
+	 * following code is used.
+	 * 
+	 * (code)
+	 * mxUtils.get(url, function(req)
+	 * {
+	 *   var node = req.getDocumentElement();
+	 *   var dec = new mxCodec(node.ownerDocument);
+	 *   dec.decode(node, graph.getModel());
+	 * });
+	 * (end)
+	 * 
+	 * Parameters:
+	 * 
+	 * url - URL to get the data from.
+	 * onload - Optional function to execute for a successful response.
+	 * onerror - Optional function to execute on error.
+	 * binary - Optional boolean parameter that specifies if the request is
+	 * binary.
+	 * timeout - Optional timeout in ms before calling ontimeout.
+	 * ontimeout - Optional function to execute on timeout.
+	 */
+	get: function(url, onload, onerror, binary, timeout, ontimeout)
+	{
+		var req = new mxXmlRequest(url, null, 'GET');
+		
+		if (binary != null)
+		{
+			req.setBinary(binary);
+		}
+		
+		req.send(onload, onerror, timeout, ontimeout);
+		
+		return req;
+	},
+
+	/**
+	 * Function: getAll
+	 * 
+	 * Loads the URLs in the given array *asynchronously* and invokes the given function
+	 * if all requests returned with a valid 2xx status. The error handler is invoked
+	 * once on the first error or invalid response.
+	 *
+	 * Parameters:
+	 * 
+	 * urls - Array of URLs to be loaded.
+	 * onload - Callback with array of <mxXmlRequests>.
+	 * onerror - Optional function to execute on error.
+	 */
+	getAll: function(urls, onload, onerror)
+	{
+		var remain = urls.length;
+		var result = [];
+		var errors = 0;
+		var err = function()
+		{
+			if (errors == 0 && onerror != null)
+			{
+				onerror();
+			}
+
+			errors++;
+		};
+		
+		for (var i = 0; i < urls.length; i++)
+		{
+			(function(url, index)
+			{
+				mxUtils.get(url, function(req)
+				{
+					var status = req.getStatus();
+					
+					if (status < 200 || status > 299)
+					{
+						err();
+					}
+					else
+					{
+						result[index] = req;
+						remain--;
+						
+						if (remain == 0)
+						{
+							onload(result);
+						}
+					}
+				}, err);
+			})(urls[i], i);
+		}
+		
+		if (remain == 0)
+		{
+			onload(result);			
+		}
+	},
+	
+	/**
+	 * Function: post
+	 * 
+	 * Posts the specified params to the given URL *asynchronously* and invokes
+	 * the given functions depending on the request status. Returns the
+	 * <mxXmlRequest> in use. Both functions take the <mxXmlRequest> as the
+	 * only parameter. Make sure to use encodeURIComponent for the parameter
+	 * values.
+	 *
+	 * Example:
+	 * 
+	 * (code)
+	 * mxUtils.post(url, 'key=value', function(req)
+	 * {
+	 * 	mxUtils.alert('Ready: '+req.isReady()+' Status: '+req.getStatus());
+	 *  // Process req.getDocumentElement() using DOM API if OK...
+	 * });
+	 * (end)
+	 * 
+	 * Parameters:
+	 * 
+	 * url - URL to get the data from.
+	 * params - Parameters for the post request.
+	 * onload - Optional function to execute for a successful response.
+	 * onerror - Optional function to execute on error.
+	 */
+	post: function(url, params, onload, onerror)
+	{
+		return new mxXmlRequest(url, params).send(onload, onerror);
+	},
+	
+	/**
+	 * Function: submit
+	 * 
+	 * Submits the given parameters to the specified URL using
+	 * <mxXmlRequest.simulate> and returns the <mxXmlRequest>.
+	 * Make sure to use encodeURIComponent for the parameter
+	 * values.
+	 * 
+	 * Parameters:
+	 * 
+	 * url - URL to get the data from.
+	 * params - Parameters for the form.
+	 * doc - Document to create the form in.
+	 * target - Target to send the form result to.
+	 */
+	submit: function(url, params, doc, target)
+	{
+		return new mxXmlRequest(url, params).simulate(doc, target);
+	},
+	
+	/**
+	 * Function: loadInto
+	 * 
+	 * Loads the specified URL *asynchronously* into the specified document,
+	 * invoking onload after the document has been loaded. This implementation
+	 * does not use <mxXmlRequest>, but the document.load method.
+	 * 
+	 * Parameters:
+	 * 
+	 * url - URL to get the data from.
+	 * doc - The document to load the URL into.
+	 * onload - Function to execute when the URL has been loaded.
+	 */
+	loadInto: function(url, doc, onload)
+	{
+		if (mxClient.IS_IE)
+		{
+			doc.onreadystatechange = function ()
+			{
+				if (doc.readyState == 4)
+				{
+					onload();
+				}
+			};
+		}
+		else
+		{
+			doc.addEventListener('load', onload, false);
+		}
+		
+		doc.load(url);
+	},
+	
+	/**
+	 * Function: getValue
+	 * 
+	 * Returns the value for the given key in the given associative array or
+	 * the given default value if the value is null.
+	 * 
+	 * Parameters:
+	 * 
+	 * array - Associative array that contains the value for the key.
+	 * key - Key whose value should be returned.
+	 * defaultValue - Value to be returned if the value for the given
+	 * key is null.
+	 */
+	getValue: function(array, key, defaultValue)
+	{
+		var value = (array != null) ? array[key] : null;
+
+		if (value == null)
+		{
+			value = defaultValue;			
+		}
+		
+		return value;
+	},
+	
+	/**
+	 * Function: getNumber
+	 * 
+	 * Returns the numeric value for the given key in the given associative
+	 * array or the given default value (or 0) if the value is null. The value
+	 * is converted to a numeric value using the Number function.
+	 * 
+	 * Parameters:
+	 * 
+	 * array - Associative array that contains the value for the key.
+	 * key - Key whose value should be returned.
+	 * defaultValue - Value to be returned if the value for the given
+	 * key is null. Default is 0.
+	 */
+	getNumber: function(array, key, defaultValue)
+	{
+		var value = (array != null) ? array[key] : null;
+
+		if (value == null)
+		{
+			value = defaultValue || 0;			
+		}
+		
+		return Number(value);
+	},
+	
+	/**
+	 * Function: getColor
+	 * 
+	 * Returns the color value for the given key in the given associative
+	 * array or the given default value if the value is null. If the value
+	 * is <mxConstants.NONE> then null is returned.
+	 * 
+	 * Parameters:
+	 * 
+	 * array - Associative array that contains the value for the key.
+	 * key - Key whose value should be returned.
+	 * defaultValue - Value to be returned if the value for the given
+	 * key is null. Default is null.
+	 */
+	getColor: function(array, key, defaultValue)
+	{
+		var value = (array != null) ? array[key] : null;
+
+		if (value == null)
+		{
+			value = defaultValue;
+		}
+		else if (value == mxConstants.NONE)
+		{
+			value = null;
+		}
+		
+		return value;
+	},
+
+	/**
+	 * Function: clone
+	 * 
+	 * Recursively clones the specified object ignoring all fieldnames in the
+	 * given array of transient fields. <mxObjectIdentity.FIELD_NAME> is always
+	 * ignored by this function.
+	 * 
+	 * Parameters:
+	 * 
+	 * obj - Object to be cloned.
+	 * transients - Optional array of strings representing the fieldname to be
+	 * ignored.
+	 * shallow - Optional boolean argument to specify if a shallow clone should
+	 * be created, that is, one where all object references are not cloned or,
+	 * in other words, one where only atomic (strings, numbers) values are
+	 * cloned. Default is false.
+	 */
+	clone: function(obj, transients, shallow)
+	{
+		shallow = (shallow != null) ? shallow : false;
+		var clone = null;
+		
+		if (obj != null && typeof(obj.constructor) == 'function')
+		{
+			clone = new obj.constructor();
+			
+		    for (var i in obj)
+		    {
+		    	if (i != mxObjectIdentity.FIELD_NAME && (transients == null ||
+		    		mxUtils.indexOf(transients, i) < 0))
+		    	{
+			    	if (!shallow && typeof(obj[i]) == 'object')
+			    	{
+			            clone[i] = mxUtils.clone(obj[i]);
+			        }
+			        else
+			        {
+			            clone[i] = obj[i];
+			        }
+				}
+		    }
+		}
+		
+	    return clone;
+	},
+
+	/**
+	 * Function: equalPoints
+	 * 
+	 * Compares all mxPoints in the given lists.
+	 * 
+	 * Parameters:
+	 * 
+	 * a - Array of <mxPoints> to be compared.
+	 * b - Array of <mxPoints> to be compared.
+	 */
+	equalPoints: function(a, b)
+	{
+		if ((a == null && b != null) || (a != null && b == null) ||
+			(a != null && b != null && a.length != b.length))
+		{
+			return false;
+		}
+		else if (a != null && b != null)
+		{
+			for (var i = 0; i < a.length; i++)
+			{
+				if (a[i] == b[i] || (a[i] != null && !a[i].equals(b[i])))
+				{
+					return false;
+				}
+			}
+		}
+		
+		return true;
+	},
+
+	/**
+	 * Function: equalEntries
+	 * 
+	 * Returns true if all properties of the given objects are equal. Values
+	 * with NaN are equal to NaN and unequal to any other value.
+	 * 
+	 * Parameters:
+	 * 
+	 * a - First object to be compared.
+	 * b - Second object to be compared.
+	 */
+	equalEntries: function(a, b)
+	{
+		if ((a == null && b != null) || (a != null && b == null) ||
+			(a != null && b != null && a.length != b.length))
+		{
+			return false;
+		}
+		else if (a != null && b != null)
+		{
+			// Counts keys in b to check if all values have been compared
+			var count = 0;
+			
+			for (var key in b)
+			{
+				count++;
+			}
+			
+			for (var key in a)
+			{
+				count--
+				
+				if ((!mxUtils.isNaN(a[key]) || !mxUtils.isNaN(b[key])) && a[key] != b[key])
+				{
+					return false;
+				}
+			}
+		}
+		
+		return count == 0;
+	},
+	
+	/**
+	 * Function: removeDuplicates
+	 * 
+	 * Removes all duplicates from the given array.
+	 */
+	removeDuplicates: function(arr)
+	{
+		var dict = new mxDictionary();
+		var result = [];
+		
+		for (var i = 0; i < arr.length; i++)
+		{
+			if (!dict.get(arr[i]))
+			{
+				result.push(arr[i]);
+				dict.put(arr[i], true);
+			}
+		}
+
+		return result;
+	},
+	
+	/**
+	 * Function: isNaN
+	 *
+	 * Returns true if the given value is of type number and isNaN returns true.
+	 */
+	isNaN: function(value)
+	{
+		return typeof(value) == 'number' && isNaN(value);
+	},
+	
+	/**
+	 * Function: extend
+	 *
+	 * Assigns a copy of the superclass prototype to the subclass prototype.
+	 * Note that this does not call the constructor of the superclass at this
+	 * point, the superclass constructor should be called explicitely in the
+	 * subclass constructor. Below is an example.
+	 * 
+	 * (code)
+	 * MyGraph = function(container, model, renderHint, stylesheet)
+	 * {
+	 *   mxGraph.call(this, container, model, renderHint, stylesheet);
+	 * }
+	 * 
+	 * mxUtils.extend(MyGraph, mxGraph);
+	 * (end)
+	 * 
+	 * Parameters:
+	 * 
+	 * ctor - Constructor of the subclass.
+	 * superCtor - Constructor of the superclass.
+	 */
+	extend: function(ctor, superCtor)
+	{
+		var f = function() {};
+		f.prototype = superCtor.prototype;
+		
+		ctor.prototype = new f();
+		ctor.prototype.constructor = ctor;
+	},
+
+	/**
+	 * Function: toString
+	 * 
+	 * Returns a textual representation of the specified object.
+	 * 
+	 * Parameters:
+	 * 
+	 * obj - Object to return the string representation for.
+	 */
+	toString: function(obj)
+	{
+	    var output = '';
+	    
+	    for (var i in obj)
+	    {
+	    	try
+	    	{
+			    if (obj[i] == null)
+			    {
+		            output += i + ' = [null]\n';
+			    }
+			    else if (typeof(obj[i]) == 'function')
+			    {
+		            output += i + ' => [Function]\n';
+		        }
+		        else if (typeof(obj[i]) == 'object')
+		        {
+		        	var ctor = mxUtils.getFunctionName(obj[i].constructor); 
+		            output += i + ' => [' + ctor + ']\n';
+		        }
+		        else
+		        {
+		            output += i + ' = ' + obj[i] + '\n';
+		        }
+	    	}
+	    	catch (e)
+	    	{
+	    		output += i + '=' + e.message;
+	    	}
+	    }
+	    
+	    return output;
+	},
+
+	/**
+	 * Function: toRadians
+	 * 
+	 * Converts the given degree to radians.
+	 */
+	toRadians: function(deg)
+	{
+		return Math.PI * deg / 180;
+	},
+
+	/**
+	 * Function: toDegree
+	 * 
+	 * Converts the given radians to degree.
+	 */
+	toDegree: function(rad)
+	{
+		return rad * 180 / Math.PI;
+	},
+	
+	/**
+	 * Function: arcToCurves
+	 * 
+	 * Converts the given arc to a series of curves.
+	 */
+	arcToCurves: function(x0, y0, r1, r2, angle, largeArcFlag, sweepFlag, x, y)
+	{
+		x -= x0;
+		y -= y0;
+		
+        if (r1 === 0 || r2 === 0) 
+        {
+        	return result;
+        }
+        
+        var fS = sweepFlag;
+        var psai = angle;
+        r1 = Math.abs(r1);
+        r2 = Math.abs(r2);
+        var ctx = -x / 2;
+        var cty = -y / 2;
+        var cpsi = Math.cos(psai * Math.PI / 180);
+        var spsi = Math.sin(psai * Math.PI / 180);
+        var rxd = cpsi * ctx + spsi * cty;
+        var ryd = -1 * spsi * ctx + cpsi * cty;
+        var rxdd = rxd * rxd;
+        var rydd = ryd * ryd;
+        var r1x = r1 * r1;
+        var r2y = r2 * r2;
+        var lamda = rxdd / r1x + rydd / r2y;
+        var sds;
+        
+        if (lamda > 1) 
+        {
+        	r1 = Math.sqrt(lamda) * r1;
+        	r2 = Math.sqrt(lamda) * r2;
+        	sds = 0;
+        }  
+        else
+        {
+        	var seif = 1;
+            
+        	if (largeArcFlag === fS) 
+        	{
+        		seif = -1;
+        	}
+            
+        	sds = seif * Math.sqrt((r1x * r2y - r1x * rydd - r2y * rxdd) / (r1x * rydd + r2y * rxdd));
+        }
+        
+        var txd = sds * r1 * ryd / r2;
+        var tyd = -1 * sds * r2 * rxd / r1;
+        var tx = cpsi * txd - spsi * tyd + x / 2;
+        var ty = spsi * txd + cpsi * tyd + y / 2;
+        var rad = Math.atan2((ryd - tyd) / r2, (rxd - txd) / r1) - Math.atan2(0, 1);
+        var s1 = (rad >= 0) ? rad : 2 * Math.PI + rad;
+        rad = Math.atan2((-ryd - tyd) / r2, (-rxd - txd) / r1) - Math.atan2((ryd - tyd) / r2, (rxd - txd) / r1);
+        var dr = (rad >= 0) ? rad : 2 * Math.PI + rad;
+        
+        if (fS == 0 && dr > 0) 
+        {
+        	dr -= 2 * Math.PI;
+        }
+        else if (fS != 0 && dr < 0) 
+        {
+        	dr += 2 * Math.PI;
+        }
+        
+        var sse = dr * 2 / Math.PI;
+        var seg = Math.ceil(sse < 0 ? -1 * sse : sse);
+        var segr = dr / seg;
+        var t = 8/3 * Math.sin(segr / 4) * Math.sin(segr / 4) / Math.sin(segr / 2);
+        var cpsir1 = cpsi * r1;
+        var cpsir2 = cpsi * r2;
+        var spsir1 = spsi * r1;
+        var spsir2 = spsi * r2;
+        var mc = Math.cos(s1);
+        var ms = Math.sin(s1);
+        var x2 = -t * (cpsir1 * ms + spsir2 * mc);
+        var y2 = -t * (spsir1 * ms - cpsir2 * mc);
+        var x3 = 0;
+        var y3 = 0;
+
+		var result = [];
+        
+        for (var n = 0; n < seg; ++n) 
+        {
+            s1 += segr;
+            mc = Math.cos(s1);
+            ms = Math.sin(s1);
+            
+            x3 = cpsir1 * mc - spsir2 * ms + tx;
+            y3 = spsir1 * mc + cpsir2 * ms + ty;
+            var dx = -t * (cpsir1 * ms + spsir2 * mc);
+            var dy = -t * (spsir1 * ms - cpsir2 * mc);
+            
+            // CurveTo updates x0, y0 so need to restore it
+            var index = n * 6;
+            result[index] = Number(x2 + x0);
+            result[index + 1] = Number(y2 + y0);
+            result[index + 2] = Number(x3 - dx + x0);
+            result[index + 3] = Number(y3 - dy + y0);
+            result[index + 4] = Number(x3 + x0);
+            result[index + 5] = Number(y3 + y0);
+            
+			x2 = x3 + dx;
+            y2 = y3 + dy;
+        }
+        
+        return result;
+	},
+
+	/**
+	 * Function: getBoundingBox
+	 * 
+	 * Returns the bounding box for the rotated rectangle.
+	 * 
+	 * Parameters:
+	 * 
+	 * rect - <mxRectangle> to be rotated.
+	 * angle - Number that represents the angle (in degrees).
+	 * cx - Optional <mxPoint> that represents the rotation center. If no
+	 * rotation center is given then the center of rect is used.
+	 */
+	getBoundingBox: function(rect, rotation, cx)
+	{
+        var result = null;
+
+        if (rect != null && rotation != null && rotation != 0)
+        {
+            var rad = mxUtils.toRadians(rotation);
+            var cos = Math.cos(rad);
+            var sin = Math.sin(rad);
+
+            cx = (cx != null) ? cx : new mxPoint(rect.x + rect.width / 2, rect.y  + rect.height / 2);
+
+            var p1 = new mxPoint(rect.x, rect.y);
+            var p2 = new mxPoint(rect.x + rect.width, rect.y);
+            var p3 = new mxPoint(p2.x, rect.y + rect.height);
+            var p4 = new mxPoint(rect.x, p3.y);
+
+            p1 = mxUtils.getRotatedPoint(p1, cos, sin, cx);
+            p2 = mxUtils.getRotatedPoint(p2, cos, sin, cx);
+            p3 = mxUtils.getRotatedPoint(p3, cos, sin, cx);
+            p4 = mxUtils.getRotatedPoint(p4, cos, sin, cx);
+
+            result = new mxRectangle(p1.x, p1.y, 0, 0);
+            result.add(new mxRectangle(p2.x, p2.y, 0, 0));
+            result.add(new mxRectangle(p3.x, p3.y, 0, 0));
+            result.add(new mxRectangle(p4.x, p4.y, 0, 0));
+        }
+
+        return result;
+	},
+
+	/**
+	 * Function: getRotatedPoint
+	 * 
+	 * Rotates the given point by the given cos and sin.
+	 */
+	getRotatedPoint: function(pt, cos, sin, c)
+	{
+		c = (c != null) ? c : new mxPoint();
+		var x = pt.x - c.x;
+		var y = pt.y - c.y;
+
+		var x1 = x * cos - y * sin;
+		var y1 = y * cos + x * sin;
+
+		return new mxPoint(x1 + c.x, y1 + c.y);
+	},
+	
+	/**
+	 * Returns an integer mask of the port constraints of the given map
+	 * @param dict the style map to determine the port constraints for
+	 * @param defaultValue Default value to return if the key is undefined.
+	 * @return the mask of port constraint directions
+	 * 
+	 * Parameters:
+	 * 
+	 * terminal - <mxCelState> that represents the terminal.
+	 * edge - <mxCellState> that represents the edge.
+	 * source - Boolean that specifies if the terminal is the source terminal.
+	 * defaultValue - Default value to be returned.
+	 */
+	getPortConstraints: function(terminal, edge, source, defaultValue)
+	{
+		var value = mxUtils.getValue(terminal.style, mxConstants.STYLE_PORT_CONSTRAINT,
+			mxUtils.getValue(edge.style, (source) ? mxConstants.STYLE_SOURCE_PORT_CONSTRAINT :
+				mxConstants.STYLE_TARGET_PORT_CONSTRAINT, null));
+		
+		if (value == null)
+		{
+			return defaultValue;
+		}
+		else
+		{
+			var directions = value.toString();
+			var returnValue = mxConstants.DIRECTION_MASK_NONE;
+			var constraintRotationEnabled = mxUtils.getValue(terminal.style, mxConstants.STYLE_PORT_CONSTRAINT_ROTATION, 0);
+			var rotation = 0;
+			
+			if (constraintRotationEnabled == 1)
+			{
+				rotation = mxUtils.getValue(terminal.style, mxConstants.STYLE_ROTATION, 0);
+			}
+			
+			var quad = 0;
+
+			if (rotation > 45)
+			{
+				quad = 1;
+				
+				if (rotation >= 135)
+				{
+					quad = 2;
+				}
+			}
+			else if (rotation < -45)
+			{
+				quad = 3;
+				
+				if (rotation <= -135)
+				{
+					quad = 2;
+				}
+			}
+
+			if (directions.indexOf(mxConstants.DIRECTION_NORTH) >= 0)
+			{
+				switch (quad)
+				{
+					case 0:
+						returnValue |= mxConstants.DIRECTION_MASK_NORTH;
+						break;
+					case 1:
+						returnValue |= mxConstants.DIRECTION_MASK_EAST;
+						break;
+					case 2:
+						returnValue |= mxConstants.DIRECTION_MASK_SOUTH;
+						break;
+					case 3:
+						returnValue |= mxConstants.DIRECTION_MASK_WEST;
+						break;
+				}
+			}
+			if (directions.indexOf(mxConstants.DIRECTION_WEST) >= 0)
+			{
+				switch (quad)
+				{
+					case 0:
+						returnValue |= mxConstants.DIRECTION_MASK_WEST;
+						break;
+					case 1:
+						returnValue |= mxConstants.DIRECTION_MASK_NORTH;
+						break;
+					case 2:
+						returnValue |= mxConstants.DIRECTION_MASK_EAST;
+						break;
+					case 3:
+						returnValue |= mxConstants.DIRECTION_MASK_SOUTH;
+						break;
+				}
+			}
+			if (directions.indexOf(mxConstants.DIRECTION_SOUTH) >= 0)
+			{
+				switch (quad)
+				{
+					case 0:
+						returnValue |= mxConstants.DIRECTION_MASK_SOUTH;
+						break;
+					case 1:
+						returnValue |= mxConstants.DIRECTION_MASK_WEST;
+						break;
+					case 2:
+						returnValue |= mxConstants.DIRECTION_MASK_NORTH;
+						break;
+					case 3:
+						returnValue |= mxConstants.DIRECTION_MASK_EAST;
+						break;
+				}
+			}
+			if (directions.indexOf(mxConstants.DIRECTION_EAST) >= 0)
+			{
+				switch (quad)
+				{
+					case 0:
+						returnValue |= mxConstants.DIRECTION_MASK_EAST;
+						break;
+					case 1:
+						returnValue |= mxConstants.DIRECTION_MASK_SOUTH;
+						break;
+					case 2:
+						returnValue |= mxConstants.DIRECTION_MASK_WEST;
+						break;
+					case 3:
+						returnValue |= mxConstants.DIRECTION_MASK_NORTH;
+						break;
+				}
+			}
+
+			return returnValue;
+		}
+	},
+	
+	/**
+	 * Function: reversePortConstraints
+	 * 
+	 * Reverse the port constraint bitmask. For example, north | east
+	 * becomes south | west
+	 */
+	reversePortConstraints: function(constraint)
+	{
+		var result = 0;
+		
+		result = (constraint & mxConstants.DIRECTION_MASK_WEST) << 3;
+		result |= (constraint & mxConstants.DIRECTION_MASK_NORTH) << 1;
+		result |= (constraint & mxConstants.DIRECTION_MASK_SOUTH) >> 1;
+		result |= (constraint & mxConstants.DIRECTION_MASK_EAST) >> 3;
+		
+		return result;
+	},
+	
+	/**
+	 * Function: findNearestSegment
+	 * 
+	 * Finds the index of the nearest segment on the given cell state for
+	 * the specified coordinate pair.
+	 */
+	findNearestSegment: function(state, x, y)
+	{
+		var index = -1;
+		
+		if (state.absolutePoints.length > 0)
+		{
+			var last = state.absolutePoints[0];
+			var min = null;
+			
+			for (var i = 1; i < state.absolutePoints.length; i++)
+			{
+				var current = state.absolutePoints[i];
+				var dist = mxUtils.ptSegDistSq(last.x, last.y,
+					current.x, current.y, x, y);
+				
+				if (min == null || dist < min)
+				{
+					min = dist;
+					index = i - 1;
+				}
+
+				last = current;
+			}
+		}
+		
+		return index;
+	},
+
+	/**
+	 * Function: getDirectedBounds
+	 * 
+	 * Adds the given margins to the given rectangle and rotates and flips the
+	 * rectangle according to the respective styles in style.
+	 */
+	getDirectedBounds: function (rect, m, style, flipH, flipV)
+	{
+		var d = mxUtils.getValue(style, mxConstants.STYLE_DIRECTION, mxConstants.DIRECTION_EAST);
+		flipH = (flipH != null) ? flipH : mxUtils.getValue(style, mxConstants.STYLE_FLIPH, false);
+		flipV = (flipV != null) ? flipV : mxUtils.getValue(style, mxConstants.STYLE_FLIPV, false);
+
+		m.x = Math.round(Math.max(0, Math.min(rect.width, m.x)));
+		m.y = Math.round(Math.max(0, Math.min(rect.height, m.y)));
+		m.width = Math.round(Math.max(0, Math.min(rect.width, m.width)));
+		m.height = Math.round(Math.max(0, Math.min(rect.height, m.height)));
+		
+		if ((flipV && (d == mxConstants.DIRECTION_SOUTH || d == mxConstants.DIRECTION_NORTH)) ||
+			(flipH && (d == mxConstants.DIRECTION_EAST || d == mxConstants.DIRECTION_WEST)))
+		{
+			var tmp = m.x;
+			m.x = m.width;
+			m.width = tmp;
+		}
+			
+		if ((flipH && (d == mxConstants.DIRECTION_SOUTH || d == mxConstants.DIRECTION_NORTH)) ||
+			(flipV && (d == mxConstants.DIRECTION_EAST || d == mxConstants.DIRECTION_WEST)))
+		{
+			var tmp = m.y;
+			m.y = m.height;
+			m.height = tmp;
+		}
+		
+		var m2 = mxRectangle.fromRectangle(m);
+		
+		if (d == mxConstants.DIRECTION_SOUTH)
+		{
+			m2.y = m.x;
+			m2.x = m.height;
+			m2.width = m.y;
+			m2.height = m.width;
+		}
+		else if (d == mxConstants.DIRECTION_WEST)
+		{
+			m2.y = m.height;
+			m2.x = m.width;
+			m2.width = m.x;
+			m2.height = m.y;
+		}
+		else if (d == mxConstants.DIRECTION_NORTH)
+		{
+			m2.y = m.width;
+			m2.x = m.y;
+			m2.width = m.height;
+			m2.height = m.x;
+		}
+		
+		return new mxRectangle(rect.x + m2.x, rect.y + m2.y, rect.width - m2.width - m2.x, rect.height - m2.height - m2.y);
+	},
+
+	/**
+	 * Function: getPerimeterPoint
+	 * 
+	 * Returns the intersection between the polygon defined by the array of
+	 * points and the line between center and point.
+	 */
+	getPerimeterPoint: function (pts, center, point)
+	{
+		var min = null;
+		
+		for (var i = 0; i < pts.length - 1; i++)
+		{
+			var pt = mxUtils.intersection(pts[i].x, pts[i].y, pts[i + 1].x, pts[i + 1].y,
+				center.x, center.y, point.x, point.y);
+			
+			if (pt != null)
+			{
+				var dx = point.x - pt.x;
+				var dy = point.y - pt.y;
+				var ip = {p: pt, distSq: dy * dy + dx * dx};
+				
+				if (ip != null && (min == null || min.distSq > ip.distSq))
+				{
+					min = ip;
+				}
+			}
+		}
+		
+		return (min != null) ? min.p : null;
+	},
+
+	/**
+	 * Function: rectangleIntersectsSegment
+	 * 
+	 * Returns true if the given rectangle intersects the given segment.
+	 * 
+	 * Parameters:
+	 * 
+	 * bounds - <mxRectangle> that represents the rectangle.
+	 * p1 - <mxPoint> that represents the first point of the segment.
+	 * p2 - <mxPoint> that represents the second point of the segment.
+	 */
+	rectangleIntersectsSegment: function(bounds, p1, p2)
+	{
+		var top = bounds.y;
+		var left = bounds.x;
+		var bottom = top + bounds.height;
+		var right = left + bounds.width;
+			
+		// Find min and max X for the segment
+		var minX = p1.x;
+		var maxX = p2.x;
+		
+		if (p1.x > p2.x)
+		{
+		  minX = p2.x;
+		  maxX = p1.x;
+		}
+		
+		// Find the intersection of the segment's and rectangle's x-projections
+		if (maxX > right)
+		{
+		  maxX = right;
+		}
+		
+		if (minX < left)
+		{
+		  minX = left;
+		}
+		
+		if (minX > maxX) // If their projections do not intersect return false
+		{
+		  return false;
+		}
+		
+		// Find corresponding min and max Y for min and max X we found before
+		var minY = p1.y;
+		var maxY = p2.y;
+		var dx = p2.x - p1.x;
+		
+		if (Math.abs(dx) > 0.0000001)
+		{
+		  var a = (p2.y - p1.y) / dx;
+		  var b = p1.y - a * p1.x;
+		  minY = a * minX + b;
+		  maxY = a * maxX + b;
+		}
+		
+		if (minY > maxY)
+		{
+		  var tmp = maxY;
+		  maxY = minY;
+		  minY = tmp;
+		}
+		
+		// Find the intersection of the segment's and rectangle's y-projections
+		if (maxY > bottom)
+		{
+		  maxY = bottom;
+		}
+		
+		if (minY < top)
+		{
+		  minY = top;
+		}
+		
+		if (minY > maxY) // If Y-projections do not intersect return false
+		{
+		  return false;
+		}
+		
+		return true;
+	},
+	
+	/**
+	 * Function: contains
+	 * 
+	 * Returns true if the specified point (x, y) is contained in the given rectangle.
+	 * 
+	 * Parameters:
+	 * 
+	 * bounds - <mxRectangle> that represents the area.
+	 * x - X-coordinate of the point.
+	 * y - Y-coordinate of the point.
+	 */
+	contains: function(bounds, x, y)
+	{
+		return (bounds.x <= x && bounds.x + bounds.width >= x &&
+				bounds.y <= y && bounds.y + bounds.height >= y);
+	},
+
+	/**
+	 * Function: intersects
+	 * 
+	 * Returns true if the two rectangles intersect.
+	 * 
+	 * Parameters:
+	 * 
+	 * a - <mxRectangle> to be checked for intersection.
+	 * b - <mxRectangle> to be checked for intersection.
+	 */
+	intersects: function(a, b)
+	{
+		var tw = a.width;
+		var th = a.height;
+		var rw = b.width;
+		var rh = b.height;
+		
+		if (rw <= 0 || rh <= 0 || tw <= 0 || th <= 0)
+		{
+		    return false;
+		}
+		
+		var tx = a.x;
+		var ty = a.y;
+		var rx = b.x;
+		var ry = b.y;
+		
+		rw += rx;
+		rh += ry;
+		tw += tx;
+		th += ty;
+
+		return ((rw < rx || rw > tx) &&
+			(rh < ry || rh > ty) &&
+			(tw < tx || tw > rx) &&
+			(th < ty || th > ry));
+	},
+
+	/**
+	 * Function: intersects
+	 * 
+	 * Returns true if the two rectangles intersect.
+	 * 
+	 * Parameters:
+	 * 
+	 * a - <mxRectangle> to be checked for intersection.
+	 * b - <mxRectangle> to be checked for intersection.
+	 */
+	intersectsHotspot: function(state, x, y, hotspot, min, max)
+	{
+		hotspot = (hotspot != null) ? hotspot : 1;
+		min = (min != null) ? min : 0;
+		max = (max != null) ? max : 0;
+		
+		if (hotspot > 0)
+		{
+			var cx = state.getCenterX();
+			var cy = state.getCenterY();
+			var w = state.width;
+			var h = state.height;
+			
+			var start = mxUtils.getValue(state.style, mxConstants.STYLE_STARTSIZE) * state.view.scale;
+
+			if (start > 0)
+			{
+				if (mxUtils.getValue(state.style, mxConstants.STYLE_HORIZONTAL, true))
+				{
+					cy = state.y + start / 2;
+					h = start;
+				}
+				else
+				{
+					cx = state.x + start / 2;
+					w = start;
+				}
+			}
+
+			w = Math.max(min, w * hotspot);
+			h = Math.max(min, h * hotspot);
+			
+			if (max > 0)
+			{
+				w = Math.min(w, max);
+				h = Math.min(h, max);
+			}
+			
+			var rect = new mxRectangle(cx - w / 2, cy - h / 2, w, h);
+			var alpha = mxUtils.toRadians(mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION) || 0);
+			
+			if (alpha != 0)
+			{
+				var cos = Math.cos(-alpha);
+				var sin = Math.sin(-alpha);
+				var cx = new mxPoint(state.getCenterX(), state.getCenterY());
+				var pt = mxUtils.getRotatedPoint(new mxPoint(x, y), cos, sin, cx);
+				x = pt.x;
+				y = pt.y;
+			}
+			
+			return mxUtils.contains(rect, x, y);			
+		}
+		
+		return true;
+	},
+
+	/**
+	 * Function: getOffset
+	 * 
+	 * Returns the offset for the specified container as an <mxPoint>. The
+	 * offset is the distance from the top left corner of the container to the
+	 * top left corner of the document.
+	 * 
+	 * Parameters:
+	 * 
+	 * container - DOM node to return the offset for.
+	 * scollOffset - Optional boolean to add the scroll offset of the document.
+	 * Default is false.
+	 */
+	getOffset: function(container, scrollOffset)
+	{
+		var offsetLeft = 0;
+		var offsetTop = 0;
+		
+		// Ignores document scroll origin for fixed elements
+		var fixed = false;
+		var node = container;
+		var b = document.body;
+		var d = document.documentElement;
+
+		while (node != null && node != b && node != d && !fixed)
+		{
+			var style = mxUtils.getCurrentStyle(node);
+			
+			if (style != null)
+			{
+				fixed = fixed || style.position == 'fixed';
+			}
+			
+			node = node.parentNode;
+		}
+		
+		if (!scrollOffset && !fixed)
+		{
+			var offset = mxUtils.getDocumentScrollOrigin(container.ownerDocument);
+			offsetLeft += offset.x;
+			offsetTop += offset.y;
+		}
+		
+		var r = container.getBoundingClientRect();
+		
+		if (r != null)
+		{
+			offsetLeft += r.left;
+			offsetTop += r.top;
+		}
+		
+		return new mxPoint(offsetLeft, offsetTop);
+	},
+
+	/**
+	 * Function: getDocumentScrollOrigin
+	 * 
+	 * Returns the scroll origin of the given document or the current document
+	 * if no document is given.
+	 */
+	getDocumentScrollOrigin: function(doc)
+	{
+		if (mxClient.IS_QUIRKS)
+		{
+			return new mxPoint(doc.body.scrollLeft, doc.body.scrollTop);
+		}
+		else
+		{
+			var wnd = doc.defaultView || doc.parentWindow;
+			
+			var x = (wnd != null && window.pageXOffset !== undefined) ? window.pageXOffset : (document.documentElement || document.body.parentNode || document.body).scrollLeft;
+			var y = (wnd != null && window.pageYOffset !== undefined) ? window.pageYOffset : (document.documentElement || document.body.parentNode || document.body).scrollTop;
+			
+			return new mxPoint(x, y);
+		}
+	},
+	
+	/**
+	 * Function: getScrollOrigin
+	 * 
+	 * Returns the top, left corner of the viewrect as an <mxPoint>.
+	 * 
+	 * Parameters:
+	 * 
+	 * node - DOM node whose scroll origin should be returned.
+	 * includeAncestors - Whether the scroll origin of the ancestors should be
+	 * included. Default is false.
+	 * includeDocument - Whether the scroll origin of the document should be
+	 * included. Default is true.
+	 */
+	getScrollOrigin: function(node, includeAncestors, includeDocument)
+	{
+		includeAncestors = (includeAncestors != null) ? includeAncestors : false;
+		includeDocument = (includeDocument != null) ? includeDocument : true;
+		
+		var doc = (node != null) ? node.ownerDocument : document;
+		var b = doc.body;
+		var d = doc.documentElement;
+		var result = new mxPoint();
+		var fixed = false;
+
+		while (node != null && node != b && node != d)
+		{
+			if (!isNaN(node.scrollLeft) && !isNaN(node.scrollTop))
+			{
+				result.x += node.scrollLeft;
+				result.y += node.scrollTop;
+			}
+			
+			var style = mxUtils.getCurrentStyle(node);
+			
+			if (style != null)
+			{
+				fixed = fixed || style.position == 'fixed';
+			}
+
+			node = (includeAncestors) ? node.parentNode : null;
+		}
+
+		if (!fixed && includeDocument)
+		{
+			var origin = mxUtils.getDocumentScrollOrigin(doc);
+
+			result.x += origin.x;
+			result.y += origin.y;
+		}
+		
+		return result;
+	},
+
+	/**
+	 * Function: convertPoint
+	 * 
+	 * Converts the specified point (x, y) using the offset of the specified
+	 * container and returns a new <mxPoint> with the result.
+	 * 
+	 * (code)
+	 * var pt = mxUtils.convertPoint(graph.container,
+	 *   mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+	 * (end)
+	 * 
+	 * Parameters:
+	 * 
+	 * container - DOM node to use for the offset.
+	 * x - X-coordinate of the point to be converted.
+	 * y - Y-coordinate of the point to be converted.
+	 */
+	convertPoint: function(container, x, y)
+	{
+		var origin = mxUtils.getScrollOrigin(container, false);
+		var offset = mxUtils.getOffset(container);
+
+		offset.x -= origin.x;
+		offset.y -= origin.y;
+		
+		return new mxPoint(x - offset.x, y - offset.y);
+	},
+	
+	/**
+	 * Function: ltrim
+	 * 
+	 * Strips all whitespaces from the beginning of the string. Without the
+	 * second parameter, this will trim these characters:
+	 * 
+	 * - " " (ASCII 32 (0x20)), an ordinary space
+	 * - "\t" (ASCII 9 (0x09)), a tab
+	 * - "\n" (ASCII 10 (0x0A)), a new line (line feed)
+	 * - "\r" (ASCII 13 (0x0D)), a carriage return
+	 * - "\0" (ASCII 0 (0x00)), the NUL-byte
+	 * - "\x0B" (ASCII 11 (0x0B)), a vertical tab
+	 */
+	ltrim: function(str, chars)
+	{
+		chars = chars || "\\s";
+		
+		return (str != null) ? str.replace(new RegExp("^[" + chars + "]+", "g"), "") : null;
+	},
+	
+	/**
+	 * Function: rtrim
+	 * 
+	 * Strips all whitespaces from the end of the string. Without the second
+	 * parameter, this will trim these characters:
+	 * 
+	 * - " " (ASCII 32 (0x20)), an ordinary space
+	 * - "\t" (ASCII 9 (0x09)), a tab
+	 * - "\n" (ASCII 10 (0x0A)), a new line (line feed)
+	 * - "\r" (ASCII 13 (0x0D)), a carriage return
+	 * - "\0" (ASCII 0 (0x00)), the NUL-byte
+	 * - "\x0B" (ASCII 11 (0x0B)), a vertical tab
+	 */
+	rtrim: function(str, chars)
+	{
+		chars = chars || "\\s";
+		
+		return (str != null) ? str.replace(new RegExp("[" + chars + "]+$", "g"), "") : null;
+	},
+	
+	/**
+	 * Function: trim
+	 * 
+	 * Strips all whitespaces from both end of the string.
+	 * Without the second parameter, Javascript function will trim these
+	 * characters:
+	 * 
+	 * - " " (ASCII 32 (0x20)), an ordinary space
+	 * - "\t" (ASCII 9 (0x09)), a tab
+	 * - "\n" (ASCII 10 (0x0A)), a new line (line feed)
+	 * - "\r" (ASCII 13 (0x0D)), a carriage return
+	 * - "\0" (ASCII 0 (0x00)), the NUL-byte
+	 * - "\x0B" (ASCII 11 (0x0B)), a vertical tab
+	 */
+	trim: function(str, chars)
+	{
+		return mxUtils.ltrim(mxUtils.rtrim(str, chars), chars);
+	},
+	
+	/**
+	 * Function: isNumeric
+	 * 
+	 * Returns true if the specified value is numeric, that is, if it is not
+	 * null, not an empty string, not a HEX number and isNaN returns false.
+	 * 
+	 * Parameters:
+	 * 
+	 * n - String representing the possibly numeric value.
+	 */
+	isNumeric: function(n)
+	{
+		return !isNaN(parseFloat(n)) && isFinite(n) && (typeof(n) != 'string' || n.toLowerCase().indexOf('0x') < 0);
+	},
+
+	/**
+	 * Function: isInteger
+	 * 
+	 * Returns true if the given value is an valid integer number.
+	 * 
+	 * Parameters:
+	 * 
+	 * n - String representing the possibly numeric value.
+	 */
+	isInteger: function(n)
+	{
+		return String(parseInt(n)) === String(n);
+	},
+
+	/**
+	 * Function: mod
+	 * 
+	 * Returns the remainder of division of n by m. You should use this instead
+	 * of the built-in operation as the built-in operation does not properly
+	 * handle negative numbers.
+	 */
+	mod: function(n, m)
+	{
+		return ((n % m) + m) % m;
+	},
+
+	/**
+	 * Function: intersection
+	 * 
+	 * Returns the intersection of two lines as an <mxPoint>.
+	 * 
+	 * Parameters:
+	 * 
+	 * x0 - X-coordinate of the first line's startpoint.
+	 * y0 - X-coordinate of the first line's startpoint.
+	 * x1 - X-coordinate of the first line's endpoint.
+	 * y1 - Y-coordinate of the first line's endpoint.
+	 * x2 - X-coordinate of the second line's startpoint.
+	 * y2 - Y-coordinate of the second line's startpoint.
+	 * x3 - X-coordinate of the second line's endpoint.
+	 * y3 - Y-coordinate of the second line's endpoint.
+	 */
+	intersection: function (x0, y0, x1, y1, x2, y2, x3, y3)
+	{
+		var denom = ((y3 - y2) * (x1 - x0)) - ((x3 - x2) * (y1 - y0));
+		var nume_a = ((x3 - x2) * (y0 - y2)) - ((y3 - y2) * (x0 - x2));
+		var nume_b = ((x1 - x0) * (y0 - y2)) - ((y1 - y0) * (x0 - x2));
+
+		var ua = nume_a / denom;
+		var ub = nume_b / denom;
+		
+		if(ua >= 0.0 && ua <= 1.0 && ub >= 0.0 && ub <= 1.0)
+		{
+			// Get the intersection point
+			var x = x0 + ua * (x1 - x0);
+			var y = y0 + ua * (y1 - y0);
+			
+			return new mxPoint(x, y);
+		}
+		
+		// No intersection
+		return null;
+	},
+	
+	/**
+	 * Function: ptSegDistSq
+	 * 
+	 * Returns the square distance between a segment and a point. To get the
+	 * distance between a point and a line (with infinite length) use
+	 * <mxUtils.ptLineDist>.
+	 * 
+	 * Parameters:
+	 * 
+	 * x1 - X-coordinate of the startpoint of the segment.
+	 * y1 - Y-coordinate of the startpoint of the segment.
+	 * x2 - X-coordinate of the endpoint of the segment.
+	 * y2 - Y-coordinate of the endpoint of the segment.
+	 * px - X-coordinate of the point.
+	 * py - Y-coordinate of the point.
+	 */
+	ptSegDistSq: function(x1, y1, x2, y2, px, py)
+    {
+		x2 -= x1;
+		y2 -= y1;
+
+		px -= x1;
+		py -= y1;
+
+		var dotprod = px * x2 + py * y2;
+		var projlenSq;
+
+		if (dotprod <= 0.0)
+		{
+		    projlenSq = 0.0;
+		}
+		else
+		{
+		    px = x2 - px;
+		    py = y2 - py;
+		    dotprod = px * x2 + py * y2;
+
+		    if (dotprod <= 0.0)
+		    {
+				projlenSq = 0.0;
+		    }
+		    else
+		    {
+				projlenSq = dotprod * dotprod / (x2 * x2 + y2 * y2);
+		    }
+		}
+
+		var lenSq = px * px + py * py - projlenSq;
+		
+		if (lenSq < 0)
+		{
+		    lenSq = 0;
+		}
+		
+		return lenSq;
+    },
+	
+	/**
+	 * Function: ptLineDist
+	 * 
+	 * Returns the distance between a line defined by two points and a point.
+	 * To get the distance between a point and a segment (with a specific
+	 * length) use <mxUtils.ptSeqDistSq>.
+	 * 
+	 * Parameters:
+	 * 
+	 * x1 - X-coordinate of point 1 of the line.
+	 * y1 - Y-coordinate of point 1 of the line.
+	 * x2 - X-coordinate of point 1 of the line.
+	 * y2 - Y-coordinate of point 1 of the line.
+	 * px - X-coordinate of the point.
+	 * py - Y-coordinate of the point.
+	 */
+    ptLineDist: function(x1, y1, x2, y2, px, py)
+    {
+		return Math.abs((y2 - y1) * px - (x2 - x1) * py + x2 * y1 - y2 * x1) /
+			Math.sqrt((y2 - y1) * (y2 - y1) + (x2 - x1) * (x2 - x1));
+    },
+    	
+	/**
+	 * Function: relativeCcw
+	 * 
+	 * Returns 1 if the given point on the right side of the segment, 0 if its
+	 * on the segment, and -1 if the point is on the left side of the segment.
+	 * 
+	 * Parameters:
+	 * 
+	 * x1 - X-coordinate of the startpoint of the segment.
+	 * y1 - Y-coordinate of the startpoint of the segment.
+	 * x2 - X-coordinate of the endpoint of the segment.
+	 * y2 - Y-coordinate of the endpoint of the segment.
+	 * px - X-coordinate of the point.
+	 * py - Y-coordinate of the point.
+	 */
+	relativeCcw: function(x1, y1, x2, y2, px, py)
+    {
+		x2 -= x1;
+		y2 -= y1;
+		px -= x1;
+		py -= y1;
+		var ccw = px * y2 - py * x2;
+		
+		if (ccw == 0.0)
+		{
+		    ccw = px * x2 + py * y2;
+		    
+		    if (ccw > 0.0)
+		    {
+				px -= x2;
+				py -= y2;
+				ccw = px * x2 + py * y2;
+				
+				if (ccw < 0.0)
+				{
+				    ccw = 0.0;
+				}
+		    }
+		}
+		
+		return (ccw < 0.0) ? -1 : ((ccw > 0.0) ? 1 : 0);
+    },
+    
+	/**
+	 * Function: animateChanges
+	 * 
+	 * See <mxEffects.animateChanges>. This is for backwards compatibility and
+	 * will be removed later.
+	 */
+	animateChanges: function(graph, changes)
+	{
+		// LATER: Deprecated, remove this function
+    	mxEffects.animateChanges.apply(this, arguments);
+	},
+    
+	/**
+	 * Function: cascadeOpacity
+	 * 
+	 * See <mxEffects.cascadeOpacity>. This is for backwards compatibility and
+	 * will be removed later.
+	 */
+    cascadeOpacity: function(graph, cell, opacity)
+	{
+		mxEffects.cascadeOpacity.apply(this, arguments);
+	},
+
+	/**
+	 * Function: fadeOut
+	 * 
+	 * See <mxEffects.fadeOut>. This is for backwards compatibility and
+	 * will be removed later.
+	 */
+	fadeOut: function(node, from, remove, step, delay, isEnabled)
+	{
+		mxEffects.fadeOut.apply(this, arguments);
+	},
+	
+	/**
+	 * Function: setOpacity
+	 * 
+	 * Sets the opacity of the specified DOM node to the given value in %.
+	 * 
+	 * Parameters:
+	 * 
+	 * node - DOM node to set the opacity for.
+	 * value - Opacity in %. Possible values are between 0 and 100.
+	 */
+	setOpacity: function(node, value)
+	{
+		if (mxUtils.isVml(node))
+		{
+	    	if (value >= 100)
+	    	{
+	    		node.style.filter = '';
+	    	}
+	    	else
+	    	{
+	    		// TODO: Why is the division by 5 needed in VML?
+			    node.style.filter = 'alpha(opacity=' + (value/5) + ')';
+	    	}
+		}
+		else if (mxClient.IS_IE && (typeof(document.documentMode) === 'undefined' || document.documentMode < 9))
+	    {
+	    	if (value >= 100)
+	    	{
+	    		node.style.filter = '';
+	    	}
+	    	else
+	    	{
+			    node.style.filter = 'alpha(opacity=' + value + ')';
+	    	}
+		}
+		else
+		{
+		    node.style.opacity = (value / 100);
+		}
+	},
+
+	/**
+	 * Function: createImage
+	 * 
+	 * Creates and returns an image (IMG node) or VML image (v:image) in IE6 in
+	 * quirks mode.
+	 * 
+	 * Parameters:
+	 * 
+	 * src - URL that points to the image to be displayed.
+	 */
+	createImage: function(src)
+	{
+        var imageNode = null;
+        
+		if (mxClient.IS_IE6 && document.compatMode != 'CSS1Compat')
+		{
+        	imageNode = document.createElement(mxClient.VML_PREFIX + ':image');
+        	imageNode.setAttribute('src', src);
+        	imageNode.style.borderStyle = 'none';
+        }
+		else
+		{
+			imageNode = document.createElement('img');
+			imageNode.setAttribute('src', src);
+			imageNode.setAttribute('border', '0');
+		}
+		
+		return imageNode;
+	},
+
+	/**
+	 * Function: sortCells
+	 * 
+	 * Sorts the given cells according to the order in the cell hierarchy.
+	 * Ascending is optional and defaults to true.
+	 */
+	sortCells: function(cells, ascending)
+	{
+		ascending = (ascending != null) ? ascending : true;
+		var lookup = new mxDictionary();
+		cells.sort(function(o1, o2)
+		{
+			var p1 = lookup.get(o1);
+			
+			if (p1 == null)
+			{
+				p1 = mxCellPath.create(o1).split(mxCellPath.PATH_SEPARATOR);
+				lookup.put(o1, p1);
+			}
+			
+			var p2 = lookup.get(o2);
+			
+			if (p2 == null)
+			{
+				p2 = mxCellPath.create(o2).split(mxCellPath.PATH_SEPARATOR);
+				lookup.put(o2, p2);
+			}
+			
+			var comp = mxCellPath.compare(p1, p2);
+			
+			return (comp == 0) ? 0 : (((comp > 0) == ascending) ? 1 : -1);
+		});
+		
+		return cells;
+	},
+
+	/**
+	 * Function: getStylename
+	 * 
+	 * Returns the stylename in a style of the form [(stylename|key=value);] or
+	 * an empty string if the given style does not contain a stylename.
+	 * 
+	 * Parameters:
+	 * 
+	 * style - String of the form [(stylename|key=value);].
+	 */
+	getStylename: function(style)
+	{
+		if (style != null)
+		{
+			var pairs = style.split(';');
+			var stylename = pairs[0];
+			
+			if (stylename.indexOf('=') < 0)
+			{
+				return stylename;
+			}
+		}
+				
+		return '';
+	},
+
+	/**
+	 * Function: getStylenames
+	 * 
+	 * Returns the stylenames in a style of the form [(stylename|key=value);]
+	 * or an empty array if the given style does not contain any stylenames.
+	 * 
+	 * Parameters:
+	 * 
+	 * style - String of the form [(stylename|key=value);].
+	 */
+	getStylenames: function(style)
+	{
+		var result = [];
+		
+		if (style != null)
+		{
+			var pairs = style.split(';');
+			
+			for (var i = 0; i < pairs.length; i++)
+			{
+				if (pairs[i].indexOf('=') < 0)
+				{
+					result.push(pairs[i]);
+				}
+			}
+		}
+				
+		return result;
+	},
+
+	/**
+	 * Function: indexOfStylename
+	 * 
+	 * Returns the index of the given stylename in the given style. This
+	 * returns -1 if the given stylename does not occur (as a stylename) in the
+	 * given style, otherwise it returns the index of the first character.
+	 */
+	indexOfStylename: function(style, stylename)
+	{
+		if (style != null && stylename != null)
+		{
+			var tokens = style.split(';');
+			var pos = 0;
+			
+			for (var i = 0; i < tokens.length; i++)
+			{
+				if (tokens[i] == stylename)
+				{
+					return pos;
+				}
+				
+				pos += tokens[i].length + 1;
+			}
+		}
+
+		return -1;
+	},
+	
+	/**
+	 * Function: addStylename
+	 * 
+	 * Adds the specified stylename to the given style if it does not already
+	 * contain the stylename.
+	 */
+	addStylename: function(style, stylename)
+	{
+		if (mxUtils.indexOfStylename(style, stylename) < 0)
+		{
+			if (style == null)
+			{
+				style = '';
+			}
+			else if (style.length > 0 && style.charAt(style.length - 1) != ';')
+			{
+				style += ';';
+			}
+			
+			style += stylename;
+		}
+		
+		return style;
+	},
+	
+	/**
+	 * Function: removeStylename
+	 * 
+	 * Removes all occurrences of the specified stylename in the given style
+	 * and returns the updated style. Trailing semicolons are not preserved.
+	 */
+	removeStylename: function(style, stylename)
+	{
+		var result = [];
+		
+		if (style != null)
+		{
+			var tokens = style.split(';');
+			
+			for (var i = 0; i < tokens.length; i++)
+			{
+				if (tokens[i] != stylename)
+				{
+					result.push(tokens[i]);
+				}
+			}
+		}
+		
+		return result.join(';');
+	},
+	
+	/**
+	 * Function: removeAllStylenames
+	 * 
+	 * Removes all stylenames from the given style and returns the updated
+	 * style.
+	 */
+	removeAllStylenames: function(style)
+	{
+		var result = [];
+		
+		if (style != null)
+		{
+			var tokens = style.split(';');
+			
+			for (var i = 0; i < tokens.length; i++)
+			{
+				// Keeps the key, value assignments
+				if (tokens[i].indexOf('=') >= 0)
+				{
+					result.push(tokens[i]);
+				}
+			}
+		}
+		
+		return result.join(';');
+	},
+
+	/**
+	 * Function: setCellStyles
+	 * 
+	 * Assigns the value for the given key in the styles of the given cells, or
+	 * removes the key from the styles if the value is null.
+	 * 
+	 * Parameters:
+	 * 
+	 * model - <mxGraphModel> to execute the transaction in.
+	 * cells - Array of <mxCells> to be updated.
+	 * key - Key of the style to be changed.
+	 * value - New value for the given key.
+	 */
+	setCellStyles: function(model, cells, key, value)
+	{
+		if (cells != null && cells.length > 0)
+		{
+			model.beginUpdate();
+			try
+			{
+				for (var i = 0; i < cells.length; i++)
+				{
+					if (cells[i] != null)
+					{
+						var style = mxUtils.setStyle(model.getStyle(cells[i]), key, value);
+						model.setStyle(cells[i], style);
+					}
+				}
+			}
+			finally
+			{
+				model.endUpdate();
+			}
+		}
+	},
+	
+	/**
+	 * Function: setStyle
+	 * 
+	 * Adds or removes the given key, value pair to the style and returns the
+	 * new style. If value is null or zero length then the key is removed from
+	 * the style. This is for cell styles, not for CSS styles.
+	 * 
+	 * Parameters:
+	 * 
+	 * style - String of the form [(stylename|key=value);].
+	 * key - Key of the style to be changed.
+	 * value - New value for the given key.
+	 */
+	setStyle: function(style, key, value)
+	{
+		var isValue = value != null && (typeof(value.length) == 'undefined' || value.length > 0);
+		
+		if (style == null || style.length == 0)
+		{
+			if (isValue)
+			{
+				style = key + '=' + value + ';';
+			}
+		}
+		else
+		{
+			if (style.substring(0, key.length + 1) == key + '=')
+			{
+				var next = style.indexOf(';');
+				
+				if (isValue)
+				{
+					style = key + '=' + value + ((next < 0) ? ';' : style.substring(next));
+				}
+				else
+				{
+					style = (next < 0 || next == style.length - 1) ? '' : style.substring(next + 1);
+				}
+			}
+			else
+			{
+				var index = style.indexOf(';' + key + '=');
+				
+				if (index < 0)
+				{
+					if (isValue)
+					{
+						var sep = (style.charAt(style.length - 1) == ';') ? '' : ';';
+						style = style + sep + key + '=' + value + ';';
+					}
+				}
+				else
+				{
+					var next = style.indexOf(';', index + 1);
+					
+					if (isValue)
+					{
+						style = style.substring(0, index + 1) + key + '=' + value + ((next < 0) ? ';' : style.substring(next));
+					}
+					else
+					{
+						style = style.substring(0, index) + ((next < 0) ? ';' : style.substring(next));
+					}
+				}
+			}
+		}
+		
+		return style;
+	},
+
+	/**
+	 * Function: setCellStyleFlags
+	 * 
+	 * Sets or toggles the flag bit for the given key in the cell's styles.
+	 * If value is null then the flag is toggled.
+	 * 
+	 * Example:
+	 * 
+	 * (code)
+	 * var cells = graph.getSelectionCells();
+	 * mxUtils.setCellStyleFlags(graph.model,
+	 * 			cells,
+	 * 			mxConstants.STYLE_FONTSTYLE,
+	 * 			mxConstants.FONT_BOLD);
+	 * (end)
+	 * 
+	 * Toggles the bold font style.
+	 * 
+	 * Parameters:
+	 * 
+	 * model - <mxGraphModel> that contains the cells.
+	 * cells - Array of <mxCells> to change the style for.
+	 * key - Key of the style to be changed.
+	 * flag - Integer for the bit to be changed.
+	 * value - Optional boolean value for the flag.
+	 */
+	setCellStyleFlags: function(model, cells, key, flag, value)
+	{
+		if (cells != null && cells.length > 0)
+		{
+			model.beginUpdate();
+			try
+			{
+				for (var i = 0; i < cells.length; i++)
+				{
+					if (cells[i] != null)
+					{
+						var style = mxUtils.setStyleFlag(
+							model.getStyle(cells[i]),
+							key, flag, value);
+						model.setStyle(cells[i], style);
+					}
+				}
+			}
+			finally
+			{
+				model.endUpdate();
+			}
+		}
+	},
+	
+	/**
+	 * Function: setStyleFlag
+	 * 
+	 * Sets or removes the given key from the specified style and returns the
+	 * new style. If value is null then the flag is toggled.
+	 * 
+	 * Parameters:
+	 * 
+	 * style - String of the form [(stylename|key=value);].
+	 * key - Key of the style to be changed.
+	 * flag - Integer for the bit to be changed.
+	 * value - Optional boolean value for the given flag.
+	 */
+	setStyleFlag: function(style, key, flag, value)
+	{
+		if (style == null || style.length == 0)
+		{
+			if (value || value == null)
+			{
+				style = key+'='+flag;
+			}
+			else
+			{
+				style = key+'=0';
+			}
+		}
+		else
+		{
+			var index = style.indexOf(key+'=');
+			
+			if (index < 0)
+			{
+				var sep = (style.charAt(style.length-1) == ';') ? '' : ';';
+
+				if (value || value == null)
+				{
+					style = style + sep + key + '=' + flag;
+				}
+				else
+				{
+					style = style + sep + key + '=0';
+				}
+			}
+			else
+			{
+				var cont = style.indexOf(';', index);
+				var tmp = '';
+				
+				if (cont < 0)
+				{
+					tmp  = style.substring(index+key.length+1);
+				}
+				else
+				{
+					tmp = style.substring(index+key.length+1, cont);
+				}
+				
+				if (value == null)
+				{
+					tmp = parseInt(tmp) ^ flag;
+				}
+				else if (value)
+				{
+					tmp = parseInt(tmp) | flag;
+				}
+				else
+				{
+					tmp = parseInt(tmp) & ~flag;
+				}
+				
+				style = style.substring(0, index) + key + '=' + tmp +
+					((cont >= 0) ? style.substring(cont) : '');
+			}
+		}
+		
+		return style;
+	},
+	
+	/**
+	 * Function: getAlignmentAsPoint
+	 * 
+	 * Returns an <mxPoint> that represents the horizontal and vertical alignment
+	 * for numeric computations. X is -0.5 for center, -1 for right and 0 for
+	 * left alignment. Y is -0.5 for middle, -1 for bottom and 0 for top
+	 * alignment. Default values for missing arguments is top, left.
+	 */
+	getAlignmentAsPoint: function(align, valign)
+	{
+		var dx = 0;
+		var dy = 0;
+		
+		// Horizontal alignment
+		if (align == mxConstants.ALIGN_CENTER)
+		{
+			dx = -0.5;
+		}
+		else if (align == mxConstants.ALIGN_RIGHT)
+		{
+			dx = -1;
+		}
+
+		// Vertical alignment
+		if (valign == mxConstants.ALIGN_MIDDLE)
+		{
+			dy = -0.5;
+		}
+		else if (valign == mxConstants.ALIGN_BOTTOM)
+		{
+			dy = -1;
+		}
+		
+		return new mxPoint(dx, dy);
+	},
+	
+	/**
+	 * Function: getSizeForString
+	 * 
+	 * Returns an <mxRectangle> with the size (width and height in pixels) of
+	 * the given string. The string may contain HTML markup. Newlines should be
+	 * converted to <br> before calling this method. The caller is responsible
+	 * for sanitizing the HTML markup.
+	 * 
+	 * Example:
+	 * 
+	 * (code)
+	 * var label = graph.getLabel(cell).replace(/\n/g, "<br>");
+	 * var size = graph.getSizeForString(label);
+	 * (end)
+	 * 
+	 * Parameters:
+	 * 
+	 * text - String whose size should be returned.
+	 * fontSize - Integer that specifies the font size in pixels. Default is
+	 * <mxConstants.DEFAULT_FONTSIZE>.
+	 * fontFamily - String that specifies the name of the font family. Default
+	 * is <mxConstants.DEFAULT_FONTFAMILY>.
+	 * textWidth - Optional width for text wrapping.
+	 */
+	getSizeForString: function(text, fontSize, fontFamily, textWidth)
+	{
+		fontSize = (fontSize != null) ? fontSize : mxConstants.DEFAULT_FONTSIZE;
+		fontFamily = (fontFamily != null) ? fontFamily : mxConstants.DEFAULT_FONTFAMILY;
+		var div = document.createElement('div');
+		
+		// Sets the font size and family
+		div.style.fontFamily = fontFamily;
+		div.style.fontSize = Math.round(fontSize) + 'px';
+		div.style.lineHeight = Math.round(fontSize * mxConstants.LINE_HEIGHT) + 'px';
+		
+		// Disables block layout and outside wrapping and hides the div
+		div.style.position = 'absolute';
+		div.style.visibility = 'hidden';
+		div.style.display = (mxClient.IS_QUIRKS) ? 'inline' : 'inline-block';
+		div.style.zoom = '1';
+		
+		if (textWidth != null)
+		{
+			div.style.width = textWidth + 'px';
+			div.style.whiteSpace = 'normal';
+		}
+		else
+		{
+			div.style.whiteSpace = 'nowrap';
+		}
+		
+		// Adds the text and inserts into DOM for updating of size
+		div.innerHTML = text;
+		document.body.appendChild(div);
+		
+		// Gets the size and removes from DOM
+		var size = new mxRectangle(0, 0, div.offsetWidth, div.offsetHeight);
+		document.body.removeChild(div);
+		
+		return size;
+	},
+	
+	/**
+	 * Function: getViewXml
+	 */
+	getViewXml: function(graph, scale, cells, x0, y0)
+	{
+		x0 = (x0 != null) ? x0 : 0;
+		y0 = (y0 != null) ? y0 : 0;
+		scale = (scale != null) ? scale : 1;
+
+		if (cells == null)
+		{
+			var model = graph.getModel();
+			cells = [model.getRoot()];
+		}
+		
+		var view = graph.getView();
+		var result = null;
+
+		// Disables events on the view
+		var eventsEnabled = view.isEventsEnabled();
+		view.setEventsEnabled(false);
+
+		// Workaround for label bounds not taken into account for image export.
+		// Creates a temporary draw pane which is used for rendering the text.
+		// Text rendering is required for finding the bounds of the labels.
+		var drawPane = view.drawPane;
+		var overlayPane = view.overlayPane;
+
+		if (graph.dialect == mxConstants.DIALECT_SVG)
+		{
+			view.drawPane = document.createElementNS(mxConstants.NS_SVG, 'g');
+			view.canvas.appendChild(view.drawPane);
+
+			// Redirects cell overlays into temporary container
+			view.overlayPane = document.createElementNS(mxConstants.NS_SVG, 'g');
+			view.canvas.appendChild(view.overlayPane);
+		}
+		else
+		{
+			view.drawPane = view.drawPane.cloneNode(false);
+			view.canvas.appendChild(view.drawPane);
+			
+			// Redirects cell overlays into temporary container
+			view.overlayPane = view.overlayPane.cloneNode(false);
+			view.canvas.appendChild(view.overlayPane);
+		}
+
+		// Resets the translation
+		var translate = view.getTranslate();
+		view.translate = new mxPoint(x0, y0);
+
+		// Creates the temporary cell states in the view
+		var temp = new mxTemporaryCellStates(graph.getView(), scale, cells);
+
+		try
+		{
+			var enc = new mxCodec();
+			result = enc.encode(graph.getView());
+		}
+		finally
+		{
+			temp.destroy();
+			view.translate = translate;
+			view.canvas.removeChild(view.drawPane);
+			view.canvas.removeChild(view.overlayPane);
+			view.drawPane = drawPane;
+			view.overlayPane = overlayPane;
+			view.setEventsEnabled(eventsEnabled);
+		}
+
+		return result;
+	},
+	
+	/**
+	 * Function: getScaleForPageCount
+	 * 
+	 * Returns the scale to be used for printing the graph with the given
+	 * bounds across the specifies number of pages with the given format. The
+	 * scale is always computed such that it given the given amount or fewer
+	 * pages in the print output. See <mxPrintPreview> for an example.
+	 * 
+	 * Parameters:
+	 * 
+	 * pageCount - Specifies the number of pages in the print output.
+	 * graph - <mxGraph> that should be printed.
+	 * pageFormat - Optional <mxRectangle> that specifies the page format.
+	 * Default is <mxConstants.PAGE_FORMAT_A4_PORTRAIT>.
+	 * border - The border along each side of every page.
+	 */
+	getScaleForPageCount: function(pageCount, graph, pageFormat, border)
+	{
+		if (pageCount < 1)
+		{
+			// We can't work with less than 1 page, return no scale
+			// change
+			return 1;
+		}
+		
+		pageFormat = (pageFormat != null) ? pageFormat : mxConstants.PAGE_FORMAT_A4_PORTRAIT;
+		border = (border != null) ? border : 0;
+		
+		var availablePageWidth = pageFormat.width - (border * 2);
+		var availablePageHeight = pageFormat.height - (border * 2);
+
+		// Work out the number of pages required if the
+		// graph is not scaled.
+		var graphBounds = graph.getGraphBounds().clone();
+		var sc = graph.getView().getScale();
+		graphBounds.width /= sc;
+		graphBounds.height /= sc;
+		var graphWidth = graphBounds.width;
+		var graphHeight = graphBounds.height;
+
+		var scale = 1;
+		
+		// The ratio of the width/height for each printer page
+		var pageFormatAspectRatio = availablePageWidth / availablePageHeight;
+		// The ratio of the width/height for the graph to be printer
+		var graphAspectRatio = graphWidth / graphHeight;
+		
+		// The ratio of horizontal pages / vertical pages for this 
+		// graph to maintain its aspect ratio on this page format
+		var pagesAspectRatio = graphAspectRatio / pageFormatAspectRatio;
+		
+		// Factor the square root of the page count up and down 
+		// by the pages aspect ratio to obtain a horizontal and 
+		// vertical page count that adds up to the page count
+		// and has the correct aspect ratio
+		var pageRoot = Math.sqrt(pageCount);
+		var pagesAspectRatioSqrt = Math.sqrt(pagesAspectRatio);
+		var numRowPages = pageRoot * pagesAspectRatioSqrt;
+		var numColumnPages = pageRoot / pagesAspectRatioSqrt;
+
+		// These value are rarely more than 2 rounding downs away from
+		// a total that meets the page count. In cases of one being less 
+		// than 1 page, the other value can be too high and take more iterations 
+		// In this case, just change that value to be the page count, since 
+		// we know the other value is 1
+		if (numRowPages < 1 && numColumnPages > pageCount)
+		{
+			var scaleChange = numColumnPages / pageCount;
+			numColumnPages = pageCount;
+			numRowPages /= scaleChange;
+		}
+		
+		if (numColumnPages < 1 && numRowPages > pageCount)
+		{
+			var scaleChange = numRowPages / pageCount;
+			numRowPages = pageCount;
+			numColumnPages /= scaleChange;
+		}		
+
+		var currentTotalPages = Math.ceil(numRowPages) * Math.ceil(numColumnPages);
+
+		var numLoops = 0;
+		
+		// Iterate through while the rounded up number of pages comes to
+		// a total greater than the required number
+		while (currentTotalPages > pageCount)
+		{
+			// Round down the page count (rows or columns) that is
+			// closest to its next integer down in percentage terms.
+			// i.e. Reduce the page total by reducing the total
+			// page area by the least possible amount
+
+			var roundRowDownProportion = Math.floor(numRowPages) / numRowPages;
+			var roundColumnDownProportion = Math.floor(numColumnPages) / numColumnPages;
+			
+			// If the round down proportion is, work out the proportion to
+			// round down to 1 page less
+			if (roundRowDownProportion == 1)
+			{
+				roundRowDownProportion = Math.floor(numRowPages-1) / numRowPages;
+			}
+			if (roundColumnDownProportion == 1)
+			{
+				roundColumnDownProportion = Math.floor(numColumnPages-1) / numColumnPages;
+			}
+			
+			// Check which rounding down is smaller, but in the case of very small roundings
+			// try the other dimension instead
+			var scaleChange = 1;
+			
+			// Use the higher of the two values
+			if (roundRowDownProportion > roundColumnDownProportion)
+			{
+				scaleChange = roundRowDownProportion;
+			}
+			else
+			{
+				scaleChange = roundColumnDownProportion;
+			}
+
+			numRowPages = numRowPages * scaleChange;
+			numColumnPages = numColumnPages * scaleChange;
+			currentTotalPages = Math.ceil(numRowPages) * Math.ceil(numColumnPages);
+			
+			numLoops++;
+			
+			if (numLoops > 10)
+			{
+				break;
+			}
+		}
+
+		// Work out the scale from the number of row pages required
+		// The column pages will give the same value
+		var posterWidth = availablePageWidth * numRowPages;
+		scale = posterWidth / graphWidth;
+		
+		// Allow for rounding errors
+		return scale * 0.99999;
+	},
+	
+	/**
+	 * Function: show
+	 * 
+	 * Copies the styles and the markup from the graph's container into the
+	 * given document and removes all cursor styles. The document is returned.
+	 * 
+	 * This function should be called from within the document with the graph.
+	 * If you experience problems with missing stylesheets in IE then try adding
+	 * the domain to the trusted sites.
+	 * 
+	 * Parameters:
+	 * 
+	 * graph - <mxGraph> to be copied.
+	 * doc - Document where the new graph is created.
+	 * x0 - X-coordinate of the graph view origin. Default is 0.
+	 * y0 - Y-coordinate of the graph view origin. Default is 0.
+	 * w - Optional width of the graph view.
+	 * h - Optional height of the graph view.
+	 */
+	show: function(graph, doc, x0, y0, w, h)
+	{
+		x0 = (x0 != null) ? x0 : 0;
+		y0 = (y0 != null) ? y0 : 0;
+		
+		if (doc == null)
+		{
+			var wnd = window.open();
+			doc = wnd.document;
+		}
+		else
+		{
+			doc.open();
+		}
+
+		// Workaround for missing print output in IE9 standards
+		if (document.documentMode == 9)
+		{
+			doc.writeln('<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=9"><![endif]-->');
+		}
+		
+		var bounds = graph.getGraphBounds();
+		var dx = Math.ceil(x0 - bounds.x);
+		var dy = Math.ceil(y0 - bounds.y);
+		
+		if (w == null)
+		{
+			w = Math.ceil(bounds.width + x0) + Math.ceil(Math.ceil(bounds.x) - bounds.x);
+		}
+		
+		if (h == null)
+		{
+			h = Math.ceil(bounds.height + y0) + Math.ceil(Math.ceil(bounds.y) - bounds.y);
+		}
+		
+		// Needs a special way of creating the page so that no click is required
+		// to refresh the contents after the external CSS styles have been loaded.
+		// To avoid a click or programmatic refresh, the styleSheets[].cssText
+		// property is copied over from the original document.
+		if (mxClient.IS_IE || document.documentMode == 11)
+		{
+			var html = '<html><head>';
+
+			var base = document.getElementsByTagName('base');
+			
+			for (var i = 0; i < base.length; i++)
+			{
+				html += base[i].outerHTML;
+			}
+
+			html += '<style>';
+
+			// Copies the stylesheets without having to load them again
+			for (var i = 0; i < document.styleSheets.length; i++)
+			{
+				try
+				{
+					html += document.styleSheets[i].cssText;
+				}
+				catch (e)
+				{
+					// ignore security exception
+				}
+			}
+
+			html += '</style></head><body style="margin:0px;">';
+			
+			// Copies the contents of the graph container
+			html += '<div style="position:absolute;overflow:hidden;width:' + w + 'px;height:' + h + 'px;"><div style="position:relative;left:' + dx + 'px;top:' + dy + 'px;">';
+			html += graph.container.innerHTML;
+			html += '</div></div></body><html>';
+
+			doc.writeln(html);
+			doc.close();
+		}
+		else
+		{
+			doc.writeln('<html><head>');
+			
+			var base = document.getElementsByTagName('base');
+			
+			for (var i = 0; i < base.length; i++)
+			{
+				doc.writeln(mxUtils.getOuterHtml(base[i]));
+			}
+			
+			var links = document.getElementsByTagName('link');
+			
+			for (var i = 0; i < links.length; i++)
+			{
+				doc.writeln(mxUtils.getOuterHtml(links[i]));
+			}
+	
+			var styles = document.getElementsByTagName('style');
+			
+			for (var i = 0; i < styles.length; i++)
+			{
+				doc.writeln(mxUtils.getOuterHtml(styles[i]));
+			}
+
+			doc.writeln('</head><body style="margin:0px;"></body></html>');
+			doc.close();
+
+			var outer = doc.createElement('div');
+			outer.position = 'absolute';
+			outer.overflow = 'hidden';
+			outer.style.width = w + 'px';
+			outer.style.height = h + 'px';
+
+			// Required for HTML labels if foreignObjects are disabled
+			var div = doc.createElement('div');
+			div.style.position = 'absolute';
+			div.style.left = dx + 'px';
+			div.style.top = dy + 'px';
+
+			var node = graph.container.firstChild;
+			var svg = null;
+			
+			while (node != null)
+			{
+				var clone = node.cloneNode(true);
+				
+				if (node == graph.view.drawPane.ownerSVGElement)
+				{
+					outer.appendChild(clone);
+					svg = clone;
+				}
+				else
+				{
+					div.appendChild(clone);
+				}
+				
+				node = node.nextSibling;
+			}
+
+			doc.body.appendChild(outer);
+			
+			if (div.firstChild != null)
+			{
+				doc.body.appendChild(div);
+			}
+						
+			if (svg != null)
+			{
+				svg.style.minWidth = '';
+				svg.style.minHeight = '';
+				svg.firstChild.setAttribute('transform', 'translate(' + dx + ',' + dy + ')');
+			}
+		}
+		
+		mxUtils.removeCursors(doc.body);
+	
+		return doc;
+	},
+	
+	/**
+	 * Function: printScreen
+	 * 
+	 * Prints the specified graph using a new window and the built-in print
+	 * dialog.
+	 * 
+	 * This function should be called from within the document with the graph.
+	 * 
+	 * Parameters:
+	 * 
+	 * graph - <mxGraph> to be printed.
+	 */
+	printScreen: function(graph)
+	{
+		var wnd = window.open();
+		var bounds = graph.getGraphBounds();
+		mxUtils.show(graph, wnd.document);
+		
+		var print = function()
+		{
+			wnd.focus();
+			wnd.print();
+			wnd.close();
+		};
+		
+		// Workaround for Google Chrome which needs a bit of a
+		// delay in order to render the SVG contents
+		if (mxClient.IS_GC)
+		{
+			wnd.setTimeout(print, 500);
+		}
+		else
+		{
+			print();
+		}
+	},
+	
+	/**
+	 * Function: popup
+	 * 
+	 * Shows the specified text content in a new <mxWindow> or a new browser
+	 * window if isInternalWindow is false.
+	 * 
+	 * Parameters:
+	 * 
+	 * content - String that specifies the text to be displayed.
+	 * isInternalWindow - Optional boolean indicating if an mxWindow should be
+	 * used instead of a new browser window. Default is false.
+	 */
+	popup: function(content, isInternalWindow)
+	{
+	   	if (isInternalWindow)
+	   	{
+			var div = document.createElement('div');
+			
+			div.style.overflow = 'scroll';
+			div.style.width = '636px';
+			div.style.height = '460px';
+			
+			var pre = document.createElement('pre');
+		    pre.innerHTML = mxUtils.htmlEntities(content, false).
+		    	replace(/\n/g,'<br>').replace(/ /g, '&nbsp;');
+			
+			div.appendChild(pre);
+			
+			var w = document.body.clientWidth;
+			var h = Math.max(document.body.clientHeight || 0, document.documentElement.clientHeight)
+			var wnd = new mxWindow('Popup Window', div,
+				w/2-320, h/2-240, 640, 480, false, true);
+
+			wnd.setClosable(true);
+			wnd.setVisible(true);
+		}
+		else
+		{
+			// Wraps up the XML content in a textarea
+			if (mxClient.IS_NS)
+			{
+			    var wnd = window.open();
+				wnd.document.writeln('<pre>'+mxUtils.htmlEntities(content)+'</pre');
+			   	wnd.document.close();
+			}
+			else
+			{
+			    var wnd = window.open();
+			    var pre = wnd.document.createElement('pre');
+			    pre.innerHTML = mxUtils.htmlEntities(content, false).
+			    	replace(/\n/g,'<br>').replace(/ /g, '&nbsp;');
+			   	wnd.document.body.appendChild(pre);
+			}
+	   	}
+	},
+	
+	/**
+	 * Function: alert
+	 * 
+	 * Displayss the given alert in a new dialog. This implementation uses the
+	 * built-in alert function. This is used to display validation errors when
+	 * connections cannot be changed or created.
+	 * 
+	 * Parameters:
+	 * 
+	 * message - String specifying the message to be displayed.
+	 */
+	alert: function(message)
+	{
+		alert(message);
+	},
+	
+	/**
+	 * Function: prompt
+	 * 
+	 * Displays the given message in a prompt dialog. This implementation uses
+	 * the built-in prompt function.
+	 * 
+	 * Parameters:
+	 * 
+	 * message - String specifying the message to be displayed.
+	 * defaultValue - Optional string specifying the default value.
+	 */
+	prompt: function(message, defaultValue)
+	{
+		return prompt(message, (defaultValue != null) ? defaultValue : '');
+	},
+	
+	/**
+	 * Function: confirm
+	 * 
+	 * Displays the given message in a confirm dialog. This implementation uses
+	 * the built-in confirm function.
+	 * 
+	 * Parameters:
+	 * 
+	 * message - String specifying the message to be displayed.
+	 */
+	confirm: function(message)
+	{
+		return confirm(message);
+	},
+
+	/**
+	 * Function: error
+	 * 
+	 * Displays the given error message in a new <mxWindow> of the given width.
+	 * If close is true then an additional close button is added to the window.
+	 * The optional icon specifies the icon to be used for the window. Default
+	 * is <mxUtils.errorImage>.
+	 * 
+	 * Parameters:
+	 * 
+	 * message - String specifying the message to be displayed.
+	 * width - Integer specifying the width of the window.
+	 * close - Optional boolean indicating whether to add a close button.
+	 * icon - Optional icon for the window decoration.
+	 */
+	error: function(message, width, close, icon)
+	{
+		var div = document.createElement('div');
+		div.style.padding = '20px';
+
+		var img = document.createElement('img');
+		img.setAttribute('src', icon || mxUtils.errorImage);
+		img.setAttribute('valign', 'bottom');
+		img.style.verticalAlign = 'middle';
+		div.appendChild(img);
+
+		div.appendChild(document.createTextNode('\u00a0')); // &nbsp;
+		div.appendChild(document.createTextNode('\u00a0')); // &nbsp;
+		div.appendChild(document.createTextNode('\u00a0')); // &nbsp;
+		mxUtils.write(div, message);
+
+		var w = document.body.clientWidth;
+		var h = (document.body.clientHeight || document.documentElement.clientHeight);
+		var warn = new mxWindow(mxResources.get(mxUtils.errorResource) ||
+			mxUtils.errorResource, div, (w-width)/2, h/4, width, null,
+			false, true);
+
+		if (close)
+		{
+			mxUtils.br(div);
+			
+			var tmp = document.createElement('p');
+			var button = document.createElement('button');
+
+			if (mxClient.IS_IE)
+			{
+				button.style.cssText = 'float:right';
+			}
+			else
+			{
+				button.setAttribute('style', 'float:right');
+			}
+
+			mxEvent.addListener(button, 'click', function(evt)
+			{
+				warn.destroy();
+			});
+
+			mxUtils.write(button, mxResources.get(mxUtils.closeResource) ||
+				mxUtils.closeResource);
+			
+			tmp.appendChild(button);
+			div.appendChild(tmp);
+			
+			mxUtils.br(div);
+			
+			warn.setClosable(true);
+		}
+		
+		warn.setVisible(true);
+		
+		return warn;
+	},
+
+	/**
+	 * Function: makeDraggable
+	 * 
+	 * Configures the given DOM element to act as a drag source for the
+	 * specified graph. Returns a a new <mxDragSource>. If
+	 * <mxDragSource.guideEnabled> is enabled then the x and y arguments must
+	 * be used in funct to match the preview location.
+	 * 
+	 * Example:
+	 * 
+	 * (code)
+	 * var funct = function(graph, evt, cell, x, y)
+	 * {
+	 *   if (graph.canImportCell(cell))
+	 *   {
+	 *     var parent = graph.getDefaultParent();
+	 *     var vertex = null;
+	 *     
+	 *     graph.getModel().beginUpdate();
+	 *     try
+	 *     {
+	 * 	     vertex = graph.insertVertex(parent, null, 'Hello', x, y, 80, 30);
+	 *     }
+	 *     finally
+	 *     {
+	 *       graph.getModel().endUpdate();
+	 *     }
+	 *
+	 *     graph.setSelectionCell(vertex);
+	 *   }
+	 * }
+	 * 
+	 * var img = document.createElement('img');
+	 * img.setAttribute('src', 'editors/images/rectangle.gif');
+	 * img.style.position = 'absolute';
+	 * img.style.left = '0px';
+	 * img.style.top = '0px';
+	 * img.style.width = '16px';
+	 * img.style.height = '16px';
+	 * 
+	 * var dragImage = img.cloneNode(true);
+	 * dragImage.style.width = '32px';
+	 * dragImage.style.height = '32px';
+	 * mxUtils.makeDraggable(img, graph, funct, dragImage);
+	 * document.body.appendChild(img);
+	 * (end)
+	 * 
+	 * Parameters:
+	 * 
+	 * element - DOM element to make draggable.
+	 * graphF - <mxGraph> that acts as the drop target or a function that takes a
+	 * mouse event and returns the current <mxGraph>.
+	 * funct - Function to execute on a successful drop.
+	 * dragElement - Optional DOM node to be used for the drag preview.
+	 * dx - Optional horizontal offset between the cursor and the drag
+	 * preview.
+	 * dy - Optional vertical offset between the cursor and the drag
+	 * preview.
+	 * autoscroll - Optional boolean that specifies if autoscroll should be
+	 * used. Default is mxGraph.autoscroll.
+	 * scalePreview - Optional boolean that specifies if the preview element
+	 * should be scaled according to the graph scale. If this is true, then
+	 * the offsets will also be scaled. Default is false.
+	 * highlightDropTargets - Optional boolean that specifies if dropTargets
+	 * should be highlighted. Default is true.
+	 * getDropTarget - Optional function to return the drop target for a given
+	 * location (x, y). Default is mxGraph.getCellAt.
+	 */
+	makeDraggable: function(element, graphF, funct, dragElement, dx, dy, autoscroll,
+			scalePreview, highlightDropTargets, getDropTarget)
+	{
+		var dragSource = new mxDragSource(element, funct);
+		dragSource.dragOffset = new mxPoint((dx != null) ? dx : 0,
+			(dy != null) ? dy : mxConstants.TOOLTIP_VERTICAL_OFFSET);
+		dragSource.autoscroll = autoscroll;
+		
+		// Cannot enable this by default. This needs to be enabled in the caller
+		// if the funct argument uses the new x- and y-arguments.
+		dragSource.setGuidesEnabled(false);
+		
+		if (highlightDropTargets != null)
+		{
+			dragSource.highlightDropTargets = highlightDropTargets;
+		}
+		
+		// Overrides function to find drop target cell
+		if (getDropTarget != null)
+		{
+			dragSource.getDropTarget = getDropTarget;
+		}
+		
+		// Overrides function to get current graph
+		dragSource.getGraphForEvent = function(evt)
+		{
+			return (typeof(graphF) == 'function') ? graphF(evt) : graphF;
+		};
+		
+		// Translates switches into dragSource customizations
+		if (dragElement != null)
+		{
+			dragSource.createDragElement = function()
+			{
+				return dragElement.cloneNode(true);
+			};
+			
+			if (scalePreview)
+			{
+				dragSource.createPreviewElement = function(graph)
+				{
+					var elt = dragElement.cloneNode(true);
+
+					var w = parseInt(elt.style.width);
+					var h = parseInt(elt.style.height);
+					elt.style.width = Math.round(w * graph.view.scale) + 'px';
+					elt.style.height = Math.round(h * graph.view.scale) + 'px';
+					
+					return elt;
+				};
+			}
+		}
+		
+		return dragSource;
+	}
+
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/util/mxVmlCanvas2D.js b/airavata-kubernetes/workflow-composer/src/js/util/mxVmlCanvas2D.js
new file mode 100644
index 0000000..2b3d600
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/util/mxVmlCanvas2D.js
@@ -0,0 +1,1102 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ *
+ * Class: mxVmlCanvas2D
+ * 
+ * Implements a canvas to be used for rendering VML. Here is an example of implementing a
+ * fallback for SVG images which are not supported in VML-based browsers.
+ * 
+ * (code)
+ * var mxVmlCanvas2DImage = mxVmlCanvas2D.prototype.image;
+ * mxVmlCanvas2D.prototype.image = function(x, y, w, h, src, aspect, flipH, flipV)
+ * {
+ *   if (src.substring(src.length - 4, src.length) == '.svg')
+ *   {
+ *     src = 'http://www.jgraph.com/images/mxgraph.gif';
+ *   }
+ *   
+ *   mxVmlCanvas2DImage.apply(this, arguments);
+ * };
+ * (end)
+ * 
+ * To disable anti-aliasing in the output, use the following code.
+ * 
+ * (code)
+ * document.createStyleSheet().cssText = mxClient.VML_PREFIX + '\\:*{antialias:false;)}';
+ * (end)
+ * 
+ * A description of the public API is available in <mxXmlCanvas2D>. Note that
+ * there is a known issue in VML where gradients are painted using the outer
+ * bounding box of rotated shapes, not the actual bounds of the shape. See
+ * also <text> for plain text label restrictions in shapes for VML.
+ */
+var mxVmlCanvas2D = function(root)
+{
+	mxAbstractCanvas2D.call(this);
+
+	/**
+	 * Variable: root
+	 * 
+	 * Reference to the container for the SVG content.
+	 */
+	this.root = root;
+};
+
+/**
+ * Extends mxAbstractCanvas2D
+ */
+mxUtils.extend(mxVmlCanvas2D, mxAbstractCanvas2D);
+
+/**
+ * Variable: path
+ * 
+ * Holds the current DOM node.
+ */
+mxVmlCanvas2D.prototype.node = null;
+
+/**
+ * Variable: textEnabled
+ * 
+ * Specifies if text output should be enabledetB. Default is true.
+ */
+mxVmlCanvas2D.prototype.textEnabled = true;
+
+/**
+ * Variable: moveOp
+ * 
+ * Contains the string used for moving in paths. Default is 'm'.
+ */
+mxVmlCanvas2D.prototype.moveOp = 'm';
+
+/**
+ * Variable: lineOp
+ * 
+ * Contains the string used for moving in paths. Default is 'l'.
+ */
+mxVmlCanvas2D.prototype.lineOp = 'l';
+
+/**
+ * Variable: curveOp
+ * 
+ * Contains the string used for bezier curves. Default is 'c'.
+ */
+mxVmlCanvas2D.prototype.curveOp = 'c';
+
+/**
+ * Variable: closeOp
+ * 
+ * Holds the operator for closing curves. Default is 'x e'.
+ */
+mxVmlCanvas2D.prototype.closeOp = 'x';
+
+/**
+ * Variable: rotatedHtmlBackground
+ * 
+ * Background color for rotated HTML. Default is ''. This can be set to eg.
+ * white to improve rendering of rotated text in VML for IE9.
+ */
+mxVmlCanvas2D.prototype.rotatedHtmlBackground = '';
+
+/**
+ * Variable: vmlScale
+ * 
+ * Specifies the scale used to draw VML shapes.
+ */
+mxVmlCanvas2D.prototype.vmlScale = 1;
+
+/**
+ * Function: createElement
+ * 
+ * Creates the given element using the document.
+ */
+mxVmlCanvas2D.prototype.createElement = function(name)
+{
+	return document.createElement(name);
+};
+
+/**
+ * Function: createVmlElement
+ * 
+ * Creates a new element using <createElement> and prefixes the given name with
+ * <mxClient.VML_PREFIX>.
+ */
+mxVmlCanvas2D.prototype.createVmlElement = function(name)
+{
+	return this.createElement(mxClient.VML_PREFIX + ':' + name);
+};
+
+/**
+ * Function: addNode
+ * 
+ * Adds the current node to the <root>.
+ */
+mxVmlCanvas2D.prototype.addNode = function(filled, stroked)
+{
+	var node = this.node;
+	var s = this.state;
+	
+	if (node != null)
+	{
+		if (node.nodeName == 'shape')
+		{
+			// Checks if the path is not empty
+			if (this.path != null && this.path.length > 0)
+			{
+				node.path = this.path.join(' ') + ' e';
+				node.style.width = this.root.style.width;
+				node.style.height = this.root.style.height;
+				node.coordsize = parseInt(node.style.width) + ' ' + parseInt(node.style.height);
+			}
+			else
+			{
+				return;
+			}
+		}
+
+		node.strokeweight = this.format(Math.max(1, s.strokeWidth * s.scale / this.vmlScale)) + 'px';
+		
+		if (s.shadow)
+		{
+			this.root.appendChild(this.createShadow(node,
+				filled && s.fillColor != null,
+				stroked && s.strokeColor != null));
+		}
+		
+		if (stroked && s.strokeColor != null)
+		{
+			node.stroked = 'true';
+			node.strokecolor = s.strokeColor;
+		}
+		else
+		{
+			node.stroked = 'false';
+		}
+
+		node.appendChild(this.createStroke());
+
+		if (filled && s.fillColor != null)
+		{
+			node.appendChild(this.createFill());
+		}
+		else if (this.pointerEvents && (node.nodeName != 'shape' ||
+			this.path[this.path.length - 1] == this.closeOp))
+		{
+			node.appendChild(this.createTransparentFill());
+		}
+		else
+		{
+			node.filled = 'false';
+		}
+
+		// LATER: Update existing DOM for performance
+		this.root.appendChild(node);
+	}
+};
+
+/**
+ * Function: createTransparentFill
+ * 
+ * Creates a transparent fill.
+ */
+mxVmlCanvas2D.prototype.createTransparentFill = function()
+{
+	var fill = this.createVmlElement('fill');
+	fill.src = mxClient.imageBasePath + '/transparent.gif';
+	fill.type = 'tile';
+	
+	return fill;
+};
+
+/**
+ * Function: createFill
+ * 
+ * Creates a fill for the current state.
+ */
+mxVmlCanvas2D.prototype.createFill = function()
+{
+	var s = this.state;
+	
+	// Gradients in foregrounds not supported because special gradients
+	// with bounds must be created for each element in graphics-canvases
+	var fill = this.createVmlElement('fill');
+	fill.color = s.fillColor;
+
+	if (s.gradientColor != null)
+	{
+		fill.type = 'gradient';
+		fill.method = 'none';
+		fill.color2 = s.gradientColor;
+		var angle = 180 - s.rotation;
+		
+		if (s.gradientDirection == mxConstants.DIRECTION_WEST)
+		{
+			angle -= 90 + ((this.root.style.flip == 'x') ? 180 : 0);
+		}
+		else if (s.gradientDirection == mxConstants.DIRECTION_EAST)
+		{
+			angle += 90 + ((this.root.style.flip == 'x') ? 180 : 0);
+		}
+		else if (s.gradientDirection == mxConstants.DIRECTION_NORTH)
+		{
+			angle -= 180 + ((this.root.style.flip == 'y') ? -180 : 0);
+		}
+		else
+		{
+			 angle += ((this.root.style.flip == 'y') ? -180 : 0);
+		}
+		
+		if (this.root.style.flip == 'x' || this.root.style.flip == 'y')
+		{
+			angle *= -1;
+		}
+
+		// LATER: Fix outer bounding box for rotated shapes used in VML.
+		fill.angle = mxUtils.mod(angle, 360);
+		fill.opacity = (s.alpha * s.gradientFillAlpha * 100) + '%';
+		fill.setAttribute(mxClient.OFFICE_PREFIX + ':opacity2', (s.alpha * s.gradientAlpha * 100) + '%');
+	}
+	else if (s.alpha < 1 || s.fillAlpha < 1)
+	{
+		fill.opacity = (s.alpha * s.fillAlpha * 100) + '%';			
+	}
+	
+	return fill;
+};
+/**
+ * Function: createStroke
+ * 
+ * Creates a fill for the current state.
+ */
+mxVmlCanvas2D.prototype.createStroke = function()
+{
+	var s = this.state;
+	var stroke = this.createVmlElement('stroke');
+	stroke.endcap = s.lineCap || 'flat';
+	stroke.joinstyle = s.lineJoin || 'miter';
+	stroke.miterlimit = s.miterLimit || '10';
+	
+	if (s.alpha < 1 || s.strokeAlpha < 1)
+	{
+		stroke.opacity = (s.alpha * s.strokeAlpha * 100) + '%';
+	}
+	
+	if (s.dashed)
+	{
+		stroke.dashstyle = this.getVmlDashStyle();
+	}
+	
+	return stroke;
+};
+
+/**
+ * Function: getVmlDashPattern
+ * 
+ * Returns a VML dash pattern for the current dashPattern.
+ * See http://msdn.microsoft.com/en-us/library/bb264085(v=vs.85).aspx
+ */
+mxVmlCanvas2D.prototype.getVmlDashStyle = function()
+{
+	var result = 'dash';
+	
+	if (typeof(this.state.dashPattern) === 'string')
+	{
+		var tok = this.state.dashPattern.split(' ');
+		
+		if (tok.length > 0 && tok[0] == 1)
+		{
+			result = '0 2';
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: createShadow
+ * 
+ * Creates a shadow for the given node.
+ */
+mxVmlCanvas2D.prototype.createShadow = function(node, filled, stroked)
+{
+	var s = this.state;
+	var rad = -s.rotation * (Math.PI / 180);
+	var cos = Math.cos(rad);
+	var sin = Math.sin(rad);
+
+	var dx = s.shadowDx * s.scale;
+	var dy = s.shadowDy * s.scale;
+
+	if (this.root.style.flip == 'x')
+	{
+		dx *= -1;
+	}
+	else if (this.root.style.flip == 'y')
+	{
+		dy *= -1;
+	}
+	
+	var shadow = node.cloneNode(true);
+	shadow.style.marginLeft = Math.round(dx * cos - dy * sin) + 'px';
+	shadow.style.marginTop = Math.round(dx * sin + dy * cos) + 'px';
+
+	// Workaround for wrong cloning in IE8 standards mode
+	if (document.documentMode == 8)
+	{
+		shadow.strokeweight = node.strokeweight;
+		
+		if (node.nodeName == 'shape')
+		{
+			shadow.path = this.path.join(' ') + ' e';
+			shadow.style.width = this.root.style.width;
+			shadow.style.height = this.root.style.height;
+			shadow.coordsize = parseInt(node.style.width) + ' ' + parseInt(node.style.height);
+		}
+	}
+	
+	if (stroked)
+	{
+		shadow.strokecolor = s.shadowColor;
+		shadow.appendChild(this.createShadowStroke());
+	}
+	else
+	{
+		shadow.stroked = 'false';
+	}
+	
+	if (filled)
+	{
+		shadow.appendChild(this.createShadowFill());
+	}
+	else
+	{
+		shadow.filled = 'false';
+	}
+	
+	return shadow;
+};
+
+/**
+ * Function: createShadowFill
+ * 
+ * Creates the fill for the shadow.
+ */
+mxVmlCanvas2D.prototype.createShadowFill = function()
+{
+	var fill = this.createVmlElement('fill');
+	fill.color = this.state.shadowColor;
+	fill.opacity = (this.state.alpha * this.state.shadowAlpha * 100) + '%';
+	
+	return fill;
+};
+
+/**
+ * Function: createShadowStroke
+ * 
+ * Creates the stroke for the shadow.
+ */
+mxVmlCanvas2D.prototype.createShadowStroke = function()
+{
+	var stroke = this.createStroke();
+	stroke.opacity = (this.state.alpha * this.state.shadowAlpha * 100) + '%';
+	
+	return stroke;
+};
+
+/**
+ * Function: rotate
+ * 
+ * Sets the rotation of the canvas. Note that rotation cannot be concatenated.
+ */
+mxVmlCanvas2D.prototype.rotate = function(theta, flipH, flipV, cx, cy)
+{
+	if (flipH && flipV)
+	{
+		theta += 180;
+	}
+	else if (flipH)
+	{
+		this.root.style.flip = 'x';
+	}
+	else if (flipV)
+	{
+		this.root.style.flip = 'y';
+	}
+
+	if (flipH ? !flipV : flipV)
+	{
+		theta *= -1;
+	}
+
+	this.root.style.rotation = theta;
+	this.state.rotation = this.state.rotation + theta;
+	this.state.rotationCx = cx;
+	this.state.rotationCy = cy;
+};
+
+/**
+ * Function: begin
+ * 
+ * Extends superclass to create path.
+ */
+mxVmlCanvas2D.prototype.begin = function()
+{
+	mxAbstractCanvas2D.prototype.begin.apply(this, arguments);
+	this.node = this.createVmlElement('shape');
+	this.node.style.position = 'absolute';
+};
+
+/**
+ * Function: quadTo
+ * 
+ * Replaces quadratic curve with bezier curve in VML.
+ */
+mxVmlCanvas2D.prototype.quadTo = function(x1, y1, x2, y2)
+{
+	var s = this.state;
+
+	var cpx0 = (this.lastX + s.dx) * s.scale;
+	var cpy0 = (this.lastY + s.dy) * s.scale;
+	var qpx1 = (x1 + s.dx) * s.scale;
+	var qpy1 = (y1 + s.dy) * s.scale;
+	var cpx3 = (x2 + s.dx) * s.scale;
+	var cpy3 = (y2 + s.dy) * s.scale;
+	
+	var cpx1 = cpx0 + 2/3 * (qpx1 - cpx0);
+	var cpy1 = cpy0 + 2/3 * (qpy1 - cpy0);
+	
+	var cpx2 = cpx3 + 2/3 * (qpx1 - cpx3);
+	var cpy2 = cpy3 + 2/3 * (qpy1 - cpy3);
+	
+	this.path.push('c ' + this.format(cpx1) + ' ' + this.format(cpy1) +
+			' ' + this.format(cpx2) + ' ' + this.format(cpy2) +
+			' ' + this.format(cpx3) + ' ' + this.format(cpy3));
+	this.lastX = (cpx3 / s.scale) - s.dx;
+	this.lastY = (cpy3 / s.scale) - s.dy;
+	
+};
+
+/**
+ * Function: createRect
+ * 
+ * Sets the glass gradient.
+ */
+mxVmlCanvas2D.prototype.createRect = function(nodeName, x, y, w, h)
+{
+	var s = this.state;
+	var n = this.createVmlElement(nodeName);
+	n.style.position = 'absolute';
+	n.style.left = this.format((x + s.dx) * s.scale) + 'px';
+	n.style.top = this.format((y + s.dy) * s.scale) + 'px';
+	n.style.width = this.format(w * s.scale) + 'px';
+	n.style.height = this.format(h * s.scale) + 'px';
+	
+	return n;
+};
+
+/**
+ * Function: rect
+ * 
+ * Sets the current path to a rectangle.
+ */
+mxVmlCanvas2D.prototype.rect = function(x, y, w, h)
+{
+	this.node = this.createRect('rect', x, y, w, h);
+};
+
+/**
+ * Function: roundrect
+ * 
+ * Sets the current path to a rounded rectangle.
+ */
+mxVmlCanvas2D.prototype.roundrect = function(x, y, w, h, dx, dy)
+{
+	this.node = this.createRect('roundrect', x, y, w, h);
+	// SetAttribute needed here for IE8
+	this.node.setAttribute('arcsize', Math.max(dx * 100 / w, dy * 100 / h) + '%');
+};
+
+/**
+ * Function: ellipse
+ * 
+ * Sets the current path to an ellipse.
+ */
+mxVmlCanvas2D.prototype.ellipse = function(x, y, w, h)
+{
+	this.node = this.createRect('oval', x, y, w, h);
+};
+
+/**
+ * Function: image
+ * 
+ * Paints an image.
+ */
+mxVmlCanvas2D.prototype.image = function(x, y, w, h, src, aspect, flipH, flipV)
+{
+	var node = null;
+	
+	if (!aspect)
+	{
+		node = this.createRect('image', x, y, w, h);
+		node.src = src;
+	}
+	else
+	{
+		// Uses fill with aspect to avoid asynchronous update of size
+		node = this.createRect('rect', x, y, w, h);
+		node.stroked = 'false';
+		
+		// Handles image aspect via fill
+		var fill = this.createVmlElement('fill');
+		fill.aspect = (aspect) ? 'atmost' : 'ignore';
+		fill.rotate = 'true';
+		fill.type = 'frame';
+		fill.src = src;
+
+		node.appendChild(fill);
+	}
+	
+	if (flipH && flipV)
+	{
+		node.style.rotation = '180';
+	}
+	else if (flipH)
+	{
+		node.style.flip = 'x';
+	}
+	else if (flipV)
+	{
+		node.style.flip = 'y';
+	}
+	
+	if (this.state.alpha < 1 || this.state.fillAlpha < 1)
+	{
+		// KNOWN: Borders around transparent images in IE<9. Using fill.opacity
+		// fixes this problem by adding a white background in all IE versions.
+		node.style.filter += 'alpha(opacity=' + (this.state.alpha * this.state.fillAlpha * 100) + ')';
+	}
+
+	this.root.appendChild(node);
+};
+
+/**
+ * Function: createText
+ * 
+ * Creates the innermost element that contains the HTML text.
+ */
+mxVmlCanvas2D.prototype.createDiv = function(str, align, valign, overflow)
+{
+	var div = this.createElement('div');
+	var state = this.state;
+
+	var css = '';
+	
+	if (state.fontBackgroundColor != null)
+	{
+		css += 'background-color:' + state.fontBackgroundColor + ';';
+	}
+	
+	if (state.fontBorderColor != null)
+	{
+		css += 'border:1px solid ' + state.fontBorderColor + ';';
+	}
+	
+	if (mxUtils.isNode(str))
+	{
+		div.appendChild(str);
+	}
+	else
+	{
+		if (overflow != 'fill' && overflow != 'width')
+		{
+			var div2 = this.createElement('div');
+			div2.style.cssText = css;
+			div2.style.display = (mxClient.IS_QUIRKS) ? 'inline' : 'inline-block';
+			div2.style.zoom = '1';
+			div2.style.textDecoration = 'inherit';
+			div2.innerHTML = str;
+			div.appendChild(div2);
+		}
+		else
+		{
+			div.style.cssText = css;
+			div.innerHTML = str;
+		}
+	}
+	
+	var style = div.style;
+
+	style.fontSize = (state.fontSize / this.vmlScale) + 'px';
+	style.fontFamily = state.fontFamily;
+	style.color = state.fontColor;
+	style.verticalAlign = 'top';
+	style.textAlign = align || 'left';
+	style.lineHeight = (mxConstants.ABSOLUTE_LINE_HEIGHT) ? (state.fontSize * mxConstants.LINE_HEIGHT / this.vmlScale) + 'px' : mxConstants.LINE_HEIGHT;
+
+	if ((state.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
+	{
+		style.fontWeight = 'bold';
+	}
+
+	if ((state.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
+	{
+		style.fontStyle = 'italic';
+	}
+	
+	if ((state.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)
+	{
+		style.textDecoration = 'underline';
+	}
+	
+	return div;
+};
+
+/**
+ * Function: text
+ * 
+ * Paints the given text. Possible values for format are empty string for plain
+ * text and html for HTML markup. Clipping, text background and border are not
+ * supported for plain text in VML.
+ */
+mxVmlCanvas2D.prototype.text = function(x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation, dir)
+{
+	if (this.textEnabled && str != null)
+	{
+		var s = this.state;
+		
+		if (format == 'html')
+		{
+			if (s.rotation != null)
+			{
+				var pt = this.rotatePoint(x, y, s.rotation, s.rotationCx, s.rotationCy);
+				
+				x = pt.x;
+				y = pt.y;
+			}
+
+			if (document.documentMode == 8 && !mxClient.IS_EM)
+			{
+				x += s.dx;
+				y += s.dy;
+				
+				// Workaround for rendering offsets
+				if (overflow != 'fill' && valign == mxConstants.ALIGN_TOP)
+				{
+					y -= 1;
+				}
+			}
+			else
+			{
+				x *= s.scale;
+				y *= s.scale;
+			}
+
+			// Adds event transparency in IE8 standards without the transparent background
+			// filter which cannot be used due to bugs in the zoomed bounding box (too slow)
+			// FIXME: No event transparency if inside v:rect (ie part of shape)
+			// KNOWN: Offset wrong for rotated text with word that are longer than the wrapping
+			// width in IE8 because real width of text cannot be determined here.
+			// This should be fixed in mxText.updateBoundingBox by calling before this and
+			// passing the real width to this method if not clipped and wrapped.
+			var abs = (document.documentMode == 8 && !mxClient.IS_EM) ? this.createVmlElement('group') : this.createElement('div');
+			abs.style.position = 'absolute';
+			abs.style.display = 'inline';
+			abs.style.left = this.format(x) + 'px';
+			abs.style.top = this.format(y) + 'px';
+			abs.style.zoom = s.scale;
+
+			var box = this.createElement('div');
+			box.style.position = 'relative';
+			box.style.display = 'inline';
+			
+			var margin = mxUtils.getAlignmentAsPoint(align, valign);
+			var dx = margin.x;
+			var dy = margin.y;
+
+			var div = this.createDiv(str, align, valign, overflow);
+			var inner = this.createElement('div');
+			
+			if (dir != null)
+			{
+				div.setAttribute('dir', dir);
+			}
+
+			if (wrap && w > 0)
+			{
+				if (!clip)
+				{
+					div.style.width = Math.round(w) + 'px';
+				}
+				
+				div.style.wordWrap = mxConstants.WORD_WRAP;
+				div.style.whiteSpace = 'normal';
+				
+				// LATER: Check if other cases need to be handled
+				if (div.style.wordWrap == 'break-word')
+				{
+					var tmp = div;
+					
+					if (tmp.firstChild != null && tmp.firstChild.nodeName == 'DIV')
+					{
+						tmp.firstChild.style.width = '100%';
+					}
+				}
+			}
+			else
+			{
+				div.style.whiteSpace = 'nowrap';
+			}
+			
+			var rot = s.rotation + (rotation || 0);
+			
+			if (this.rotateHtml && rot != 0)
+			{
+				inner.style.display = 'inline';
+				inner.style.zoom = '1';
+				inner.appendChild(div);
+
+				// Box not needed for rendering in IE8 standards
+				if (document.documentMode == 8 && !mxClient.IS_EM && this.root.nodeName != 'DIV')
+				{
+					box.appendChild(inner);
+					abs.appendChild(box);
+				}
+				else
+				{
+					abs.appendChild(inner);
+				}
+			}
+			else if (document.documentMode == 8 && !mxClient.IS_EM)
+			{
+				box.appendChild(div);
+				abs.appendChild(box);
+			}
+			else
+			{
+				div.style.display = 'inline';
+				abs.appendChild(div);
+			}
+			
+			// Inserts the node into the DOM
+			if (this.root.nodeName != 'DIV')
+			{
+				// Rectangle to fix position in group
+				var rect = this.createVmlElement('rect');
+				rect.stroked = 'false';
+				rect.filled = 'false';
+
+				rect.appendChild(abs);
+				this.root.appendChild(rect);
+			}
+			else
+			{
+				this.root.appendChild(abs);
+			}
+			
+			if (clip)
+			{
+				div.style.overflow = 'hidden';
+				div.style.width = Math.round(w) + 'px';
+				
+				if (!mxClient.IS_QUIRKS)
+				{
+					div.style.maxHeight = Math.round(h) + 'px';
+				}
+			}
+			else if (overflow == 'fill')
+			{
+				// KNOWN: Affects horizontal alignment in quirks
+				// but fill should only be used with align=left
+				div.style.overflow = 'hidden';
+				div.style.width = (Math.max(0, w) + 1) + 'px';
+				div.style.height = (Math.max(0, h) + 1) + 'px';
+			}
+			else if (overflow == 'width')
+			{
+				// KNOWN: Affects horizontal alignment in quirks
+				// but fill should only be used with align=left
+				div.style.overflow = 'hidden';
+				div.style.width = (Math.max(0, w) + 1) + 'px';
+				div.style.maxHeight = (Math.max(0, h) + 1) + 'px';
+			}
+			
+			if (this.rotateHtml && rot != 0)
+			{
+				var rad = rot * (Math.PI / 180);
+				
+				// Precalculate cos and sin for the rotation
+				var real_cos = parseFloat(parseFloat(Math.cos(rad)).toFixed(8));
+				var real_sin = parseFloat(parseFloat(Math.sin(-rad)).toFixed(8));
+
+				rad %= 2 * Math.PI;
+				if (rad < 0) rad += 2 * Math.PI;
+				rad %= Math.PI;
+				if (rad > Math.PI / 2) rad = Math.PI - rad;
+				
+				var cos = Math.cos(rad);
+				var sin = Math.sin(rad);
+
+				// Adds div to document to measure size
+				if (document.documentMode == 8 && !mxClient.IS_EM)
+				{
+					div.style.display = 'inline-block';
+					inner.style.display = 'inline-block';
+					box.style.display = 'inline-block';
+				}
+				
+				div.style.visibility = 'hidden';
+				div.style.position = 'absolute';
+				document.body.appendChild(div);
+				
+				var sizeDiv = div;
+				
+				if (sizeDiv.firstChild != null && sizeDiv.firstChild.nodeName == 'DIV')
+				{
+					sizeDiv = sizeDiv.firstChild;
+				}
+				
+				var tmp = sizeDiv.offsetWidth + 3;
+				var oh = sizeDiv.offsetHeight;
+				
+				if (clip)
+				{
+					w = Math.min(w, tmp);
+					oh = Math.min(oh, h);
+				}
+				else
+				{
+					w = tmp;
+				}
+
+				// Handles words that are longer than the given wrapping width
+				if (wrap)
+				{
+					div.style.width = w + 'px';
+				}
+				
+				// Simulates max-height in quirks
+				if (mxClient.IS_QUIRKS && (clip || overflow == 'width') && oh > h)
+				{
+					oh = h;
+					
+					// Quirks does not support maxHeight
+					div.style.height = oh + 'px';
+				}
+				
+				h = oh;
+
+				var top_fix = (h - h * cos + w * -sin) / 2 - real_sin * w * (dx + 0.5) + real_cos * h * (dy + 0.5);
+				var left_fix = (w - w * cos + h * -sin) / 2 + real_cos * w * (dx + 0.5) + real_sin * h * (dy + 0.5);
+
+				if (abs.nodeName == 'group' && this.root.nodeName == 'DIV')
+				{
+					// Workaround for bug where group gets moved away if left and top are non-zero in IE8 standards
+					var pos = this.createElement('div');
+					pos.style.display = 'inline-block';
+					pos.style.position = 'absolute';
+					pos.style.left = this.format(x + (left_fix - w / 2) * s.scale) + 'px';
+					pos.style.top = this.format(y + (top_fix - h / 2) * s.scale) + 'px';
+					
+					abs.parentNode.appendChild(pos);
+					pos.appendChild(abs);
+				}
+				else
+				{
+					var sc = (document.documentMode == 8 && !mxClient.IS_EM) ? 1 : s.scale;
+					
+					abs.style.left = this.format(x + (left_fix - w / 2) * sc) + 'px';
+					abs.style.top = this.format(y + (top_fix - h / 2) * sc) + 'px';
+				}
+				
+				// KNOWN: Rotated text rendering quality is bad for IE9 quirks
+				inner.style.filter = "progid:DXImageTransform.Microsoft.Matrix(M11="+real_cos+", M12="+
+					real_sin+", M21="+(-real_sin)+", M22="+real_cos+", sizingMethod='auto expand')";
+				inner.style.backgroundColor = this.rotatedHtmlBackground;
+				
+				if (this.state.alpha < 1)
+				{
+					inner.style.filter += 'alpha(opacity=' + (this.state.alpha * 100) + ')';
+				}
+
+				// Restore parent node for DIV
+				inner.appendChild(div);
+				div.style.position = '';
+				div.style.visibility = '';
+			}
+			else if (document.documentMode != 8 || mxClient.IS_EM)
+			{
+				div.style.verticalAlign = 'top';
+				
+				if (this.state.alpha < 1)
+				{
+					abs.style.filter = 'alpha(opacity=' + (this.state.alpha * 100) + ')';
+				}
+				
+				// Adds div to document to measure size
+				var divParent = div.parentNode;
+				div.style.visibility = 'hidden';
+				document.body.appendChild(div);
+				
+				w = div.offsetWidth;
+				var oh = div.offsetHeight;
+				
+				// Simulates max-height in quirks
+				if (mxClient.IS_QUIRKS && clip && oh > h)
+				{
+					oh = h;
+					
+					// Quirks does not support maxHeight
+					div.style.height = oh + 'px';
+				}
+				
+				h = oh;
+				
+				div.style.visibility = '';
+				divParent.appendChild(div);
+				
+				abs.style.left = this.format(x + w * dx * this.state.scale) + 'px';
+				abs.style.top = this.format(y + h * dy * this.state.scale) + 'px';
+			}
+			else
+			{
+				if (this.state.alpha < 1)
+				{
+					div.style.filter = 'alpha(opacity=' + (this.state.alpha * 100) + ')';
+				}
+				
+				// Faster rendering in IE8 without offsetWidth/Height
+				box.style.left = (dx * 100) + '%';
+				box.style.top = (dy * 100) + '%';
+			}
+		}
+		else
+		{
+			this.plainText(x, y, w, h, mxUtils.htmlEntities(str, false), align, valign, wrap, format, overflow, clip, rotation, dir);
+		}
+	}
+};
+
+/**
+ * Function: plainText
+ * 
+ * Paints the outline of the current path.
+ */
+mxVmlCanvas2D.prototype.plainText = function(x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation, dir)
+{
+	// TextDirection is ignored since this code is not used (format is always HTML in the text function)
+	var s = this.state;
+	x = (x + s.dx) * s.scale;
+	y = (y + s.dy) * s.scale;
+	
+	var node = this.createVmlElement('shape');
+	node.style.width = '1px';
+	node.style.height = '1px';
+	node.stroked = 'false';
+
+	var fill = this.createVmlElement('fill');
+	fill.color = s.fontColor;
+	fill.opacity = (s.alpha * 100) + '%';
+	node.appendChild(fill);
+	
+	var path = this.createVmlElement('path');
+	path.textpathok = 'true';
+	path.v = 'm ' + this.format(0) + ' ' + this.format(0) + ' l ' + this.format(1) + ' ' + this.format(0);
+	
+	node.appendChild(path);
+	
+	// KNOWN: Font family and text decoration ignored
+	var tp = this.createVmlElement('textpath');
+	tp.style.cssText = 'v-text-align:' + align;
+	tp.style.align = align;
+	tp.style.fontFamily = s.fontFamily;
+	tp.string = str;
+	tp.on = 'true';
+	
+	// Scale via fontsize instead of node.style.zoom for correct offsets in IE8
+	var size = s.fontSize * s.scale / this.vmlScale;
+	tp.style.fontSize = size + 'px';
+	
+	// Bold
+	if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
+	{
+		tp.style.fontWeight = 'bold';
+	}
+	
+	// Italic
+	if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
+	{
+		tp.style.fontStyle = 'italic';
+	}
+
+	// Underline
+	if ((s.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)
+	{
+		tp.style.textDecoration = 'underline';
+	}
+
+	var lines = str.split('\n');
+	var textHeight = size + (lines.length - 1) * size * mxConstants.LINE_HEIGHT;
+	var dx = 0;
+	var dy = 0;
+
+	if (valign == mxConstants.ALIGN_BOTTOM)
+	{
+		dy = - textHeight / 2;
+	}
+	else if (valign != mxConstants.ALIGN_MIDDLE) // top
+	{
+		dy = textHeight / 2;
+	}
+
+	if (rotation != null)
+	{
+		node.style.rotation = rotation;
+		var rad = rotation * (Math.PI / 180);
+		dx = Math.sin(rad) * dy;
+		dy = Math.cos(rad) * dy;
+	}
+
+	// FIXME: Clipping is relative to bounding box
+	/*if (clip)
+	{
+		node.style.clip = 'rect(0px ' + this.format(w) + 'px ' + this.format(h) + 'px 0px)';
+	}*/
+	
+	node.appendChild(tp);
+	node.style.left = this.format(x - dx) + 'px';
+	node.style.top = this.format(y + dy) + 'px';
+	
+	this.root.appendChild(node);
+};
+
+/**
+ * Function: stroke
+ * 
+ * Paints the outline of the current path.
+ */
+mxVmlCanvas2D.prototype.stroke = function()
+{
+	this.addNode(false, true);
+};
+
+/**
+ * Function: fill
+ * 
+ * Fills the current path.
+ */
+mxVmlCanvas2D.prototype.fill = function()
+{
+	this.addNode(true, false);
+};
+
+/**
+ * Function: fillAndStroke
+ * 
+ * Fills and paints the outline of the current path.
+ */
+mxVmlCanvas2D.prototype.fillAndStroke = function()
+{
+	this.addNode(true, true);
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/util/mxWindow.js b/airavata-kubernetes/workflow-composer/src/js/util/mxWindow.js
new file mode 100644
index 0000000..7d95f49
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/util/mxWindow.js
@@ -0,0 +1,1130 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxWindow
+ * 
+ * Basic window inside a document.
+ * 
+ * Examples:
+ * 
+ * Creating a simple window.
+ *
+ * (code)
+ * var tb = document.createElement('div');
+ * var wnd = new mxWindow('Title', tb, 100, 100, 200, 200, true, true);
+ * wnd.setVisible(true); 
+ * (end)
+ *
+ * Creating a window that contains an iframe. 
+ * 
+ * (code)
+ * var frame = document.createElement('iframe');
+ * frame.setAttribute('width', '192px');
+ * frame.setAttribute('height', '172px');
+ * frame.setAttribute('src', 'http://www.example.com/');
+ * frame.style.backgroundColor = 'white';
+ * 
+ * var w = document.body.clientWidth;
+ * var h = (document.body.clientHeight || document.documentElement.clientHeight);
+ * var wnd = new mxWindow('Title', frame, (w-200)/2, (h-200)/3, 200, 200);
+ * wnd.setVisible(true);
+ * (end)
+ * 
+ * To limit the movement of a window, eg. to keep it from being moved beyond
+ * the top, left corner the following method can be overridden (recommended):
+ * 
+ * (code)
+ * wnd.setLocation = function(x, y)
+ * {
+ *   x = Math.max(0, x);
+ *   y = Math.max(0, y);
+ *   mxWindow.prototype.setLocation.apply(this, arguments);
+ * };
+ * (end)
+ * 
+ * Or the following event handler can be used:
+ * 
+ * (code)
+ * wnd.addListener(mxEvent.MOVE, function(e)
+ * {
+ *   wnd.setLocation(Math.max(0, wnd.getX()), Math.max(0, wnd.getY()));
+ * });
+ * (end)
+ * 
+ * To keep a window inside the current window:
+ * 
+ * (code)
+ * mxEvent.addListener(window, 'resize', mxUtils.bind(this, function()
+ * {
+ *   var iw = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
+ *   var ih = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
+ *   
+ *   var x = this.window.getX();
+ *   var y = this.window.getY();
+ *   
+ *   if (x + this.window.table.clientWidth > iw)
+ *   {
+ *     x = Math.max(0, iw - this.window.table.clientWidth);
+ *   }
+ *   
+ *   if (y + this.window.table.clientHeight > ih)
+ *   {
+ *     y = Math.max(0, ih - this.window.table.clientHeight);
+ *   }
+ *   
+ *   if (this.window.getX() != x || this.window.getY() != y)
+ *   {
+ *     this.window.setLocation(x, y);
+ *   }
+ * }));
+ * (end)
+ *
+ * Event: mxEvent.MOVE_START
+ *
+ * Fires before the window is moved. The <code>event</code> property contains
+ * the corresponding mouse event.
+ *
+ * Event: mxEvent.MOVE
+ *
+ * Fires while the window is being moved. The <code>event</code> property
+ * contains the corresponding mouse event.
+ *
+ * Event: mxEvent.MOVE_END
+ *
+ * Fires after the window is moved. The <code>event</code> property contains
+ * the corresponding mouse event.
+ *
+ * Event: mxEvent.RESIZE_START
+ *
+ * Fires before the window is resized. The <code>event</code> property contains
+ * the corresponding mouse event.
+ *
+ * Event: mxEvent.RESIZE
+ *
+ * Fires while the window is being resized. The <code>event</code> property
+ * contains the corresponding mouse event.
+ *
+ * Event: mxEvent.RESIZE_END
+ *
+ * Fires after the window is resized. The <code>event</code> property contains
+ * the corresponding mouse event.
+ *
+ * Event: mxEvent.MAXIMIZE
+ * 
+ * Fires after the window is maximized. The <code>event</code> property
+ * contains the corresponding mouse event.
+ * 
+ * Event: mxEvent.MINIMIZE
+ * 
+ * Fires after the window is minimized. The <code>event</code> property
+ * contains the corresponding mouse event.
+ * 
+ * Event: mxEvent.NORMALIZE
+ * 
+ * Fires after the window is normalized, that is, it returned from
+ * maximized or minimized state. The <code>event</code> property contains the
+ * corresponding mouse event.
+ *  
+ * Event: mxEvent.ACTIVATE
+ * 
+ * Fires after a window is activated. The <code>previousWindow</code> property
+ * contains the previous window. The event sender is the active window.
+ * 
+ * Event: mxEvent.SHOW
+ * 
+ * Fires after the window is shown. This event has no properties.
+ * 
+ * Event: mxEvent.HIDE
+ * 
+ * Fires after the window is hidden. This event has no properties.
+ * 
+ * Event: mxEvent.CLOSE
+ * 
+ * Fires before the window is closed. The <code>event</code> property contains
+ * the corresponding mouse event.
+ * 
+ * Event: mxEvent.DESTROY
+ * 
+ * Fires before the window is destroyed. This event has no properties.
+ * 
+ * Constructor: mxWindow
+ * 
+ * Constructs a new window with the given dimension and title to display
+ * the specified content. The window elements use the given style as a
+ * prefix for the classnames of the respective window elements, namely,
+ * the window title and window pane. The respective postfixes are appended
+ * to the given stylename as follows:
+ * 
+ *   style - Base style for the window.
+ *   style+Title - Style for the window title.
+ *   style+Pane - Style for the window pane.
+ * 
+ * The default value for style is mxWindow, resulting in the following
+ * classnames for the window elements: mxWindow, mxWindowTitle and
+ * mxWindowPane.
+ * 
+ * If replaceNode is given then the window replaces the given DOM node in
+ * the document.
+ * 
+ * Parameters:
+ * 
+ * title - String that represents the title of the new window.
+ * content - DOM node that is used as the window content.
+ * x - X-coordinate of the window location.
+ * y - Y-coordinate of the window location.
+ * width - Width of the window.
+ * height - Optional height of the window. Default is to match the height
+ * of the content at the specified width.
+ * minimizable - Optional boolean indicating if the window is minimizable.
+ * Default is true.
+ * movable - Optional boolean indicating if the window is movable. Default
+ * is true.
+ * replaceNode - Optional DOM node that the window should replace.
+ * style - Optional base classname for the window elements. Default is
+ * mxWindow.
+ */
+function mxWindow(title, content, x, y, width, height, minimizable, movable, replaceNode, style)
+{
+	if (content != null)
+	{
+		minimizable = (minimizable != null) ? minimizable : true;
+		this.content = content;
+		this.init(x, y, width, height, style);
+		
+		this.installMaximizeHandler();
+		this.installMinimizeHandler();
+		this.installCloseHandler();
+		this.setMinimizable(minimizable);
+		this.setTitle(title);
+		
+		if (movable == null || movable)
+		{
+			this.installMoveHandler();
+		}
+
+		if (replaceNode != null && replaceNode.parentNode != null)
+		{
+			replaceNode.parentNode.replaceChild(this.div, replaceNode);
+		}
+		else
+		{
+			document.body.appendChild(this.div);
+		}
+	}
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxWindow.prototype = new mxEventSource();
+mxWindow.prototype.constructor = mxWindow;
+
+/**
+ * Variable: closeImage
+ * 
+ * URL of the image to be used for the close icon in the titlebar.
+ */
+mxWindow.prototype.closeImage = mxClient.imageBasePath + '/close.gif';
+
+/**
+ * Variable: minimizeImage
+ * 
+ * URL of the image to be used for the minimize icon in the titlebar.
+ */
+mxWindow.prototype.minimizeImage = mxClient.imageBasePath + '/minimize.gif';
+	
+/**
+ * Variable: normalizeImage
+ * 
+ * URL of the image to be used for the normalize icon in the titlebar.
+ */
+mxWindow.prototype.normalizeImage = mxClient.imageBasePath + '/normalize.gif';
+	
+/**
+ * Variable: maximizeImage
+ * 
+ * URL of the image to be used for the maximize icon in the titlebar.
+ */
+mxWindow.prototype.maximizeImage = mxClient.imageBasePath + '/maximize.gif';
+
+/**
+ * Variable: normalizeImage
+ * 
+ * URL of the image to be used for the resize icon.
+ */
+mxWindow.prototype.resizeImage = mxClient.imageBasePath + '/resize.gif';
+
+/**
+ * Variable: visible
+ * 
+ * Boolean flag that represents the visible state of the window.
+ */
+mxWindow.prototype.visible = false;
+
+/**
+ * Variable: minimumSize
+ * 
+ * <mxRectangle> that specifies the minimum width and height of the window.
+ * Default is (50, 40).
+ */
+mxWindow.prototype.minimumSize = new mxRectangle(0, 0, 50, 40);
+
+/**
+ * Variable: destroyOnClose
+ * 
+ * Specifies if the window should be destroyed when it is closed. If this
+ * is false then the window is hidden using <setVisible>. Default is true.
+ */
+mxWindow.prototype.destroyOnClose = true;
+
+/**
+ * Variable: contentHeightCorrection
+ * 
+ * Defines the correction factor for computing the height of the contentWrapper.
+ * Default is 6 for IE 7/8 standards mode and 2 for all other browsers and modes.
+ */
+mxWindow.prototype.contentHeightCorrection = (document.documentMode == 8 || document.documentMode == 7) ? 6 : 2;
+
+/**
+ * Variable: title
+ * 
+ * Reference to the DOM node (TD) that contains the title.
+ */
+mxWindow.prototype.title = null;
+
+/**
+ * Variable: content
+ * 
+ * Reference to the DOM node that represents the window content.
+ */
+mxWindow.prototype.content = null;
+
+/**
+ * Function: init
+ * 
+ * Initializes the DOM tree that represents the window.
+ */
+mxWindow.prototype.init = function(x, y, width, height, style)
+{
+	style = (style != null) ? style : 'mxWindow';
+	
+	this.div = document.createElement('div');
+	this.div.className = style;
+
+	this.div.style.left = x + 'px';
+	this.div.style.top = y + 'px';
+	this.table = document.createElement('table');
+	this.table.className = style;
+
+	// Disables built-in pan and zoom in IE10 and later
+	if (mxClient.IS_POINTER)
+	{
+		this.div.style.touchAction = 'none';
+	}
+	
+	// Workaround for table size problems in FF
+	if (width != null)
+	{
+		if (!mxClient.IS_QUIRKS)
+		{
+			this.div.style.width = width + 'px'; 
+		}
+		
+		this.table.style.width = width + 'px';
+	} 
+	
+	if (height != null)
+	{
+		if (!mxClient.IS_QUIRKS)
+		{
+			this.div.style.height = height + 'px';
+		}
+		
+		this.table.style.height = height + 'px';
+	}		
+	
+	// Creates title row
+	var tbody = document.createElement('tbody');
+	var tr = document.createElement('tr');
+	
+	this.title = document.createElement('td');
+	this.title.className = style + 'Title';
+	
+	this.buttons = document.createElement('div');
+	this.buttons.style.position = 'absolute';
+	this.buttons.style.display = 'inline-block';
+	this.buttons.style.right = '4px';
+	this.buttons.style.top = '5px';
+	this.title.appendChild(this.buttons);
+	
+	tr.appendChild(this.title);
+	tbody.appendChild(tr);
+	
+	// Creates content row and table cell
+	tr = document.createElement('tr');
+	this.td = document.createElement('td');
+	this.td.className = style + 'Pane';
+	
+	if (document.documentMode == 7)
+	{
+		this.td.style.height = '100%';
+	}
+
+	this.contentWrapper = document.createElement('div');
+	this.contentWrapper.className = style + 'Pane';
+	this.contentWrapper.style.width = '100%';
+	this.contentWrapper.appendChild(this.content);
+
+	// Workaround for div around div restricts height
+	// of inner div if outerdiv has hidden overflow
+	if (mxClient.IS_QUIRKS || this.content.nodeName.toUpperCase() != 'DIV')
+	{
+		this.contentWrapper.style.height = '100%';
+	}
+
+	// Puts all content into the DOM
+	this.td.appendChild(this.contentWrapper);
+	tr.appendChild(this.td);
+	tbody.appendChild(tr);
+	this.table.appendChild(tbody);
+	this.div.appendChild(this.table);
+	
+	// Puts the window on top of other windows when clicked
+	var activator = mxUtils.bind(this, function(evt)
+	{
+		this.activate();
+	});
+	
+	mxEvent.addGestureListeners(this.title, activator);
+	mxEvent.addGestureListeners(this.table, activator);
+
+	this.hide();
+};
+
+/**
+ * Function: setTitle
+ * 
+ * Sets the window title to the given string. HTML markup inside the title
+ * will be escaped.
+ */
+mxWindow.prototype.setTitle = function(title)
+{
+	// Removes all text content nodes (normally just one)
+	var child = this.title.firstChild;
+	
+	while (child != null)
+	{
+		var next = child.nextSibling;
+		
+		if (child.nodeType == mxConstants.NODETYPE_TEXT)
+		{
+			child.parentNode.removeChild(child);
+		}
+		
+		child = next;
+	}
+	
+	mxUtils.write(this.title, title || '');
+	this.title.appendChild(this.buttons);
+};
+
+/**
+ * Function: setScrollable
+ * 
+ * Sets if the window contents should be scrollable.
+ */
+mxWindow.prototype.setScrollable = function(scrollable)
+{
+	// Workaround for hang in Presto 2.5.22 (Opera 10.5)
+	if (navigator.userAgent.indexOf('Presto/2.5') < 0)
+	{
+		if (scrollable)
+		{
+			this.contentWrapper.style.overflow = 'auto';
+		}
+		else
+		{
+			this.contentWrapper.style.overflow = 'hidden';
+		}
+	}
+};
+
+/**
+ * Function: activate
+ * 
+ * Puts the window on top of all other windows.
+ */
+mxWindow.prototype.activate = function()
+{
+	if (mxWindow.activeWindow != this)
+	{
+		var style = mxUtils.getCurrentStyle(this.getElement());
+		var index = (style != null) ? style.zIndex : 3;
+
+		if (mxWindow.activeWindow)
+		{
+			var elt = mxWindow.activeWindow.getElement();
+			
+			if (elt != null && elt.style != null)
+			{
+				elt.style.zIndex = index;
+			}
+		}
+		
+		var previousWindow = mxWindow.activeWindow;
+		this.getElement().style.zIndex = parseInt(index) + 1;
+		mxWindow.activeWindow = this;
+		
+		this.fireEvent(new mxEventObject(mxEvent.ACTIVATE, 'previousWindow', previousWindow));
+	}
+};
+
+/**
+ * Function: getElement
+ * 
+ * Returuns the outermost DOM node that makes up the window.
+ */
+mxWindow.prototype.getElement = function()
+{
+	return this.div;
+};
+
+/**
+ * Function: fit
+ * 
+ * Makes sure the window is inside the client area of the window.
+ */
+mxWindow.prototype.fit = function()
+{
+	mxUtils.fit(this.div);
+};
+
+/**
+ * Function: isResizable
+ * 
+ * Returns true if the window is resizable.
+ */
+mxWindow.prototype.isResizable = function()
+{
+	if (this.resize != null)
+	{
+		return this.resize.style.display != 'none';
+	}
+	
+	return false;
+};
+
+/**
+ * Function: setResizable
+ * 
+ * Sets if the window should be resizable. To avoid interference with some
+ * built-in features of IE10 and later, the use of the following code is
+ * recommended if there are resizable <mxWindow>s in the page:
+ * 
+ * (code)
+ * if (mxClient.IS_POINTER)
+ * {
+ *   document.body.style.msTouchAction = 'none';
+ * }
+ * (end)
+ */
+mxWindow.prototype.setResizable = function(resizable)
+{
+	if (resizable)
+	{
+		if (this.resize == null)
+		{
+			this.resize = document.createElement('img');
+			this.resize.style.position = 'absolute';
+			this.resize.style.bottom = '2px';
+			this.resize.style.right = '2px';
+
+			this.resize.setAttribute('src', mxClient.imageBasePath + '/resize.gif');
+			this.resize.style.cursor = 'nw-resize';
+			
+			var startX = null;
+			var startY = null;
+			var width = null;
+			var height = null;
+			
+			var start = mxUtils.bind(this, function(evt)
+			{
+				// LATER: pointerdown starting on border of resize does start
+				// the drag operation but does not fire consecutive events via
+				// one of the listeners below (does pan instead).
+				// Workaround: document.body.style.msTouchAction = 'none'
+				this.activate();
+				startX = mxEvent.getClientX(evt);
+				startY = mxEvent.getClientY(evt);
+				width = this.div.offsetWidth;
+				height = this.div.offsetHeight;
+				
+				mxEvent.addGestureListeners(document, null, dragHandler, dropHandler);
+				this.fireEvent(new mxEventObject(mxEvent.RESIZE_START, 'event', evt));
+				mxEvent.consume(evt);
+			});
+
+			// Adds a temporary pair of listeners to intercept
+			// the gesture event in the document
+			var dragHandler = mxUtils.bind(this, function(evt)
+			{
+				if (startX != null && startY != null)
+				{
+					var dx = mxEvent.getClientX(evt) - startX;
+					var dy = mxEvent.getClientY(evt) - startY;
+	
+					this.setSize(width + dx, height + dy);
+	
+					this.fireEvent(new mxEventObject(mxEvent.RESIZE, 'event', evt));
+					mxEvent.consume(evt);
+				}
+			});
+			
+			var dropHandler = mxUtils.bind(this, function(evt)
+			{
+				if (startX != null && startY != null)
+				{
+					startX = null;
+					startY = null;
+					mxEvent.removeGestureListeners(document, null, dragHandler, dropHandler);
+					this.fireEvent(new mxEventObject(mxEvent.RESIZE_END, 'event', evt));
+					mxEvent.consume(evt);
+				}
+			});
+			
+			mxEvent.addGestureListeners(this.resize, start, dragHandler, dropHandler);
+			this.div.appendChild(this.resize);
+		}
+		else 
+		{
+			this.resize.style.display = 'inline';
+		}
+	}
+	else if (this.resize != null)
+	{
+		this.resize.style.display = 'none';
+	}
+};
+	
+/**
+ * Function: setSize
+ * 
+ * Sets the size of the window.
+ */
+mxWindow.prototype.setSize = function(width, height)
+{
+	width = Math.max(this.minimumSize.width, width);
+	height = Math.max(this.minimumSize.height, height);
+
+	// Workaround for table size problems in FF
+	if (!mxClient.IS_QUIRKS)
+	{
+		this.div.style.width =  width + 'px';
+		this.div.style.height = height + 'px';
+	}
+	
+	this.table.style.width =  width + 'px';
+	this.table.style.height = height + 'px';
+
+	if (!mxClient.IS_QUIRKS)
+	{
+		this.contentWrapper.style.height = (this.div.offsetHeight -
+			this.title.offsetHeight - this.contentHeightCorrection) + 'px';
+	}
+};
+	
+/**
+ * Function: setMinimizable
+ * 
+ * Sets if the window is minimizable.
+ */
+mxWindow.prototype.setMinimizable = function(minimizable)
+{
+	this.minimize.style.display = (minimizable) ? '' : 'none';
+};
+
+/**
+ * Function: getMinimumSize
+ * 
+ * Returns an <mxRectangle> that specifies the size for the minimized window.
+ * A width or height of 0 means keep the existing width or height. This
+ * implementation returns the height of the window title and keeps the width.
+ */
+mxWindow.prototype.getMinimumSize = function()
+{
+	return new mxRectangle(0, 0, 0, this.title.offsetHeight);
+};
+
+/**
+ * Function: installMinimizeHandler
+ * 
+ * Installs the event listeners required for minimizing the window.
+ */
+mxWindow.prototype.installMinimizeHandler = function()
+{
+	this.minimize = document.createElement('img');
+	
+	this.minimize.setAttribute('src', this.minimizeImage);
+	this.minimize.setAttribute('title', 'Minimize');
+	this.minimize.style.cursor = 'pointer';
+	this.minimize.style.marginLeft = '2px';
+	this.minimize.style.display = 'none';
+	
+	this.buttons.appendChild(this.minimize);
+	
+	var minimized = false;
+	var maxDisplay = null;
+	var height = null;
+
+	var funct = mxUtils.bind(this, function(evt)
+	{
+		this.activate();
+		
+		if (!minimized)
+		{
+			minimized = true;
+			
+			this.minimize.setAttribute('src', this.normalizeImage);
+			this.minimize.setAttribute('title', 'Normalize');
+			this.contentWrapper.style.display = 'none';
+			maxDisplay = this.maximize.style.display;
+			
+			this.maximize.style.display = 'none';
+			height = this.table.style.height;
+			
+			var minSize = this.getMinimumSize();
+			
+			if (minSize.height > 0)
+			{
+				if (!mxClient.IS_QUIRKS)
+				{
+					this.div.style.height = minSize.height + 'px';
+				}
+				
+				this.table.style.height = minSize.height + 'px';
+			}
+			
+			if (minSize.width > 0)
+			{
+				if (!mxClient.IS_QUIRKS)
+				{
+					this.div.style.width = minSize.width + 'px';
+				}
+				
+				this.table.style.width = minSize.width + 'px';
+			}
+			
+			if (this.resize != null)
+			{
+				this.resize.style.visibility = 'hidden';
+			}
+			
+			this.fireEvent(new mxEventObject(mxEvent.MINIMIZE, 'event', evt));
+		}
+		else
+		{
+			minimized = false;
+			
+			this.minimize.setAttribute('src', this.minimizeImage);
+			this.minimize.setAttribute('title', 'Minimize');
+			this.contentWrapper.style.display = ''; // default
+			this.maximize.style.display = maxDisplay;
+			
+			if (!mxClient.IS_QUIRKS)
+			{
+				this.div.style.height = height;
+			}
+			
+			this.table.style.height = height;
+
+			if (this.resize != null)
+			{
+				this.resize.style.visibility = '';
+			}
+			
+			this.fireEvent(new mxEventObject(mxEvent.NORMALIZE, 'event', evt));
+		}
+		
+		mxEvent.consume(evt);
+	});
+	
+	mxEvent.addGestureListeners(this.minimize, funct);
+};
+	
+/**
+ * Function: setMaximizable
+ * 
+ * Sets if the window is maximizable.
+ */
+mxWindow.prototype.setMaximizable = function(maximizable)
+{
+	this.maximize.style.display = (maximizable) ? '' : 'none';
+};
+
+/**
+ * Function: installMaximizeHandler
+ * 
+ * Installs the event listeners required for maximizing the window.
+ */
+mxWindow.prototype.installMaximizeHandler = function()
+{
+	this.maximize = document.createElement('img');
+	
+	this.maximize.setAttribute('src', this.maximizeImage);
+	this.maximize.setAttribute('title', 'Maximize');
+	this.maximize.style.cursor = 'default';
+	this.maximize.style.marginLeft = '2px';
+	this.maximize.style.cursor = 'pointer';
+	this.maximize.style.display = 'none';
+	
+	this.buttons.appendChild(this.maximize);
+	
+	var maximized = false;
+	var x = null;
+	var y = null;
+	var height = null;
+	var width = null;
+	var minDisplay = null;
+
+	var funct = mxUtils.bind(this, function(evt)
+	{
+		this.activate();
+		
+		if (this.maximize.style.display != 'none')
+		{
+			if (!maximized)
+			{
+				maximized = true;
+				
+				this.maximize.setAttribute('src', this.normalizeImage);
+				this.maximize.setAttribute('title', 'Normalize');
+				this.contentWrapper.style.display = '';
+				minDisplay = this.minimize.style.display;
+				this.minimize.style.display = 'none';
+				
+				// Saves window state
+				x = parseInt(this.div.style.left);
+				y = parseInt(this.div.style.top);
+				height = this.table.style.height;
+				width = this.table.style.width;
+
+				this.div.style.left = '0px';
+				this.div.style.top = '0px';
+				var docHeight = Math.max(document.body.clientHeight || 0, document.documentElement.clientHeight || 0);
+
+				if (!mxClient.IS_QUIRKS)
+				{
+					this.div.style.width = (document.body.clientWidth - 2) + 'px';
+					this.div.style.height = (docHeight - 2) + 'px';
+				}
+
+				this.table.style.width = (document.body.clientWidth - 2) + 'px';
+				this.table.style.height = (docHeight - 2) + 'px';
+				
+				if (this.resize != null)
+				{
+					this.resize.style.visibility = 'hidden';
+				}
+
+				if (!mxClient.IS_QUIRKS)
+				{
+					var style = mxUtils.getCurrentStyle(this.contentWrapper);
+		
+					if (style.overflow == 'auto' || this.resize != null)
+					{
+						this.contentWrapper.style.height = (this.div.offsetHeight -
+							this.title.offsetHeight - this.contentHeightCorrection) + 'px';
+					}
+				}
+
+				this.fireEvent(new mxEventObject(mxEvent.MAXIMIZE, 'event', evt));
+			}
+			else
+			{
+				maximized = false;
+				
+				this.maximize.setAttribute('src', this.maximizeImage);
+				this.maximize.setAttribute('title', 'Maximize');
+				this.contentWrapper.style.display = '';
+				this.minimize.style.display = minDisplay;
+
+				// Restores window state
+				this.div.style.left = x+'px';
+				this.div.style.top = y+'px';
+				
+				if (!mxClient.IS_QUIRKS)
+				{
+					this.div.style.height = height;
+					this.div.style.width = width;
+
+					var style = mxUtils.getCurrentStyle(this.contentWrapper);
+		
+					if (style.overflow == 'auto' || this.resize != null)
+					{
+						this.contentWrapper.style.height = (this.div.offsetHeight -
+							this.title.offsetHeight - this.contentHeightCorrection) + 'px';
+					}
+				}
+				
+				this.table.style.height = height;
+				this.table.style.width = width;
+
+				if (this.resize != null)
+				{
+					this.resize.style.visibility = '';
+				}
+				
+				this.fireEvent(new mxEventObject(mxEvent.NORMALIZE, 'event', evt));
+			}
+			
+			mxEvent.consume(evt);
+		}
+	});
+	
+	mxEvent.addGestureListeners(this.maximize, funct);
+	mxEvent.addListener(this.title, 'dblclick', funct);
+};
+	
+/**
+ * Function: installMoveHandler
+ * 
+ * Installs the event listeners required for moving the window.
+ */
+mxWindow.prototype.installMoveHandler = function()
+{
+	this.title.style.cursor = 'move';
+	
+	mxEvent.addGestureListeners(this.title,
+		mxUtils.bind(this, function(evt)
+		{
+			var startX = mxEvent.getClientX(evt);
+			var startY = mxEvent.getClientY(evt);
+			var x = this.getX();
+			var y = this.getY();
+						
+			// Adds a temporary pair of listeners to intercept
+			// the gesture event in the document
+			var dragHandler = mxUtils.bind(this, function(evt)
+			{
+				var dx = mxEvent.getClientX(evt) - startX;
+				var dy = mxEvent.getClientY(evt) - startY;
+				this.setLocation(x + dx, y + dy);
+				this.fireEvent(new mxEventObject(mxEvent.MOVE, 'event', evt));
+				mxEvent.consume(evt);
+			});
+			
+			var dropHandler = mxUtils.bind(this, function(evt)
+			{
+				mxEvent.removeGestureListeners(document, null, dragHandler, dropHandler);
+				this.fireEvent(new mxEventObject(mxEvent.MOVE_END, 'event', evt));
+				mxEvent.consume(evt);
+			});
+			
+			mxEvent.addGestureListeners(document, null, dragHandler, dropHandler);
+			this.fireEvent(new mxEventObject(mxEvent.MOVE_START, 'event', evt));
+			mxEvent.consume(evt);
+		}));
+	
+	// Disables built-in pan and zoom in IE10 and later
+	if (mxClient.IS_POINTER)
+	{
+		this.title.style.touchAction = 'none';
+	}
+};
+
+/**
+ * Function: setLocation
+ * 
+ * Sets the upper, left corner of the window.
+ */
+ mxWindow.prototype.setLocation = function(x, y)
+ {
+	this.div.style.left = x + 'px';
+	this.div.style.top = y + 'px';
+ };
+
+/**
+ * Function: getX
+ *
+ * Returns the current position on the x-axis.
+ */
+mxWindow.prototype.getX = function()
+{
+	return parseInt(this.div.style.left);
+};
+
+/**
+ * Function: getY
+ *
+ * Returns the current position on the y-axis.
+ */
+mxWindow.prototype.getY = function()
+{
+	return parseInt(this.div.style.top);
+};
+
+/**
+ * Function: installCloseHandler
+ *
+ * Adds the <closeImage> as a new image node in <closeImg> and installs the
+ * <close> event.
+ */
+mxWindow.prototype.installCloseHandler = function()
+{
+	this.closeImg = document.createElement('img');
+	
+	this.closeImg.setAttribute('src', this.closeImage);
+	this.closeImg.setAttribute('title', 'Close');
+	this.closeImg.style.marginLeft = '2px';
+	this.closeImg.style.cursor = 'pointer';
+	this.closeImg.style.display = 'none';
+	
+	this.buttons.appendChild(this.closeImg);
+
+	mxEvent.addGestureListeners(this.closeImg,
+		mxUtils.bind(this, function(evt)
+		{
+			this.fireEvent(new mxEventObject(mxEvent.CLOSE, 'event', evt));
+			
+			if (this.destroyOnClose)
+			{
+				this.destroy();
+			}
+			else
+			{
+				this.setVisible(false);
+			}
+			
+			mxEvent.consume(evt);
+		}));
+};
+
+/**
+ * Function: setImage
+ * 
+ * Sets the image associated with the window.
+ * 
+ * Parameters:
+ * 
+ * image - URL of the image to be used.
+ */
+mxWindow.prototype.setImage = function(image)
+{
+	this.image = document.createElement('img');
+	this.image.setAttribute('src', image);
+	this.image.setAttribute('align', 'left');
+	this.image.style.marginRight = '4px';
+	this.image.style.marginLeft = '0px';
+	this.image.style.marginTop = '-2px';
+	
+	this.title.insertBefore(this.image, this.title.firstChild);
+};
+
+/**
+ * Function: setClosable
+ * 
+ * Sets the image associated with the window.
+ * 
+ * Parameters:
+ * 
+ * closable - Boolean specifying if the window should be closable.
+ */
+mxWindow.prototype.setClosable = function(closable)
+{
+	this.closeImg.style.display = (closable) ? '' : 'none';
+};
+
+/**
+ * Function: isVisible
+ * 
+ * Returns true if the window is visible.
+ */
+mxWindow.prototype.isVisible = function()
+{
+	if (this.div != null)
+	{
+		return this.div.style.display != 'none';
+	}
+	
+	return false;
+};
+
+/**
+ * Function: setVisible
+ *
+ * Shows or hides the window depending on the given flag.
+ * 
+ * Parameters:
+ * 
+ * visible - Boolean indicating if the window should be made visible.
+ */
+mxWindow.prototype.setVisible = function(visible)
+{
+	if (this.div != null && this.isVisible() != visible)
+	{
+		if (visible)
+		{
+			this.show();
+		}
+		else
+		{
+			this.hide();
+		}
+	}
+};
+
+/**
+ * Function: show
+ *
+ * Shows the window.
+ */
+mxWindow.prototype.show = function()
+{
+	this.div.style.display = '';
+	this.activate();
+	
+	var style = mxUtils.getCurrentStyle(this.contentWrapper);
+	
+	if (!mxClient.IS_QUIRKS && (style.overflow == 'auto' || this.resize != null))
+	{
+		this.contentWrapper.style.height = (this.div.offsetHeight -
+				this.title.offsetHeight - this.contentHeightCorrection) + 'px';
+	}
+	
+	this.fireEvent(new mxEventObject(mxEvent.SHOW));
+};
+
+/**
+ * Function: hide
+ *
+ * Hides the window.
+ */
+mxWindow.prototype.hide = function()
+{
+	this.div.style.display = 'none';
+	this.fireEvent(new mxEventObject(mxEvent.HIDE));
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the window and removes all associated resources. Fires a
+ * <destroy> event prior to destroying the window.
+ */
+mxWindow.prototype.destroy = function()
+{
+	this.fireEvent(new mxEventObject(mxEvent.DESTROY));
+	
+	if (this.div != null)
+	{
+		mxEvent.release(this.div);
+		this.div.parentNode.removeChild(this.div);
+		this.div = null;
+	}
+	
+	this.title = null;
+	this.content = null;
+	this.contentWrapper = null;
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/util/mxXmlCanvas2D.js b/airavata-kubernetes/workflow-composer/src/js/util/mxXmlCanvas2D.js
new file mode 100644
index 0000000..493ead6
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/util/mxXmlCanvas2D.js
@@ -0,0 +1,1217 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxXmlCanvas2D
+ *
+ * Base class for all canvases. The following methods make up the public
+ * interface of the canvas 2D for all painting in mxGraph:
+ * 
+ * - <save>, <restore>
+ * - <scale>, <translate>, <rotate>
+ * - <setAlpha>, <setFillAlpha>, <setStrokeAlpha>, <setFillColor>, <setGradient>,
+ *   <setStrokeColor>, <setStrokeWidth>, <setDashed>, <setDashPattern>, <setLineCap>, 
+ *   <setLineJoin>, <setMiterLimit>
+ * - <setFontColor>, <setFontBackgroundColor>, <setFontBorderColor>, <setFontSize>,
+ *   <setFontFamily>, <setFontStyle>
+ * - <setShadow>, <setShadowColor>, <setShadowAlpha>, <setShadowOffset>
+ * - <rect>, <roundrect>, <ellipse>, <image>, <text>
+ * - <begin>, <moveTo>, <lineTo>, <quadTo>, <curveTo>
+ * - <stroke>, <fill>, <fillAndStroke>
+ * 
+ * <mxAbstractCanvas2D.arcTo> is an additional method for drawing paths. This is
+ * a synthetic method, meaning that it is turned into a sequence of curves by
+ * default. Subclassers may add native support for arcs.
+ * 
+ * Constructor: mxXmlCanvas2D
+ *
+ * Constructs a new abstract canvas.
+ */
+function mxXmlCanvas2D(root)
+{
+	mxAbstractCanvas2D.call(this);
+
+	/**
+	 * Variable: root
+	 * 
+	 * Reference to the container for the SVG content.
+	 */
+	this.root = root;
+
+	// Writes default settings;
+	this.writeDefaults();
+};
+
+/**
+ * Extends mxAbstractCanvas2D
+ */
+mxUtils.extend(mxXmlCanvas2D, mxAbstractCanvas2D);
+
+/**
+ * Variable: textEnabled
+ * 
+ * Specifies if text output should be enabled. Default is true.
+ */
+mxXmlCanvas2D.prototype.textEnabled = true;
+
+/**
+ * Variable: compressed
+ * 
+ * Specifies if the output should be compressed by removing redundant calls.
+ * Default is true.
+ */
+mxXmlCanvas2D.prototype.compressed = true;
+
+/**
+ * Function: writeDefaults
+ * 
+ * Writes the rendering defaults to <root>:
+ */
+mxXmlCanvas2D.prototype.writeDefaults = function()
+{
+	var elem;
+	
+	// Writes font defaults
+	elem = this.createElement('fontfamily');
+	elem.setAttribute('family', mxConstants.DEFAULT_FONTFAMILY);
+	this.root.appendChild(elem);
+	
+	elem = this.createElement('fontsize');
+	elem.setAttribute('size', mxConstants.DEFAULT_FONTSIZE);
+	this.root.appendChild(elem);
+	
+	// Writes shadow defaults
+	elem = this.createElement('shadowcolor');
+	elem.setAttribute('color', mxConstants.SHADOWCOLOR);
+	this.root.appendChild(elem);
+	
+	elem = this.createElement('shadowalpha');
+	elem.setAttribute('alpha', mxConstants.SHADOW_OPACITY);
+	this.root.appendChild(elem);
+	
+	elem = this.createElement('shadowoffset');
+	elem.setAttribute('dx', mxConstants.SHADOW_OFFSET_X);
+	elem.setAttribute('dy', mxConstants.SHADOW_OFFSET_Y);
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: format
+ * 
+ * Returns a formatted number with 2 decimal places.
+ */
+mxXmlCanvas2D.prototype.format = function(value)
+{
+	return parseFloat(parseFloat(value).toFixed(2));
+};
+
+/**
+ * Function: createElement
+ * 
+ * Creates the given element using the owner document of <root>.
+ */
+mxXmlCanvas2D.prototype.createElement = function(name)
+{
+	return this.root.ownerDocument.createElement(name);
+};
+
+/**
+ * Function: save
+ * 
+ * Saves the drawing state.
+ */
+mxXmlCanvas2D.prototype.save = function()
+{
+	if (this.compressed)
+	{
+		mxAbstractCanvas2D.prototype.save.apply(this, arguments);
+	}
+	
+	this.root.appendChild(this.createElement('save'));
+};
+
+/**
+ * Function: restore
+ * 
+ * Restores the drawing state.
+ */
+mxXmlCanvas2D.prototype.restore = function()
+{
+	if (this.compressed)
+	{
+		mxAbstractCanvas2D.prototype.restore.apply(this, arguments);
+	}
+	
+	this.root.appendChild(this.createElement('restore'));
+};
+
+/**
+ * Function: scale
+ * 
+ * Scales the output.
+ * 
+ * Parameters:
+ * 
+ * scale - Number that represents the scale where 1 is equal to 100%.
+ */
+mxXmlCanvas2D.prototype.scale = function(value)
+{
+        var elem = this.createElement('scale');
+        elem.setAttribute('scale', value);
+        this.root.appendChild(elem);
+};
+
+/**
+ * Function: translate
+ * 
+ * Translates the output.
+ * 
+ * Parameters:
+ * 
+ * dx - Number that specifies the horizontal translation.
+ * dy - Number that specifies the vertical translation.
+ */
+mxXmlCanvas2D.prototype.translate = function(dx, dy)
+{
+	var elem = this.createElement('translate');
+	elem.setAttribute('dx', this.format(dx));
+	elem.setAttribute('dy', this.format(dy));
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: rotate
+ * 
+ * Rotates and/or flips the output around a given center. (Note: Due to
+ * limitations in VML, the rotation cannot be concatenated.)
+ * 
+ * Parameters:
+ * 
+ * theta - Number that represents the angle of the rotation (in degrees).
+ * flipH - Boolean indicating if the output should be flipped horizontally.
+ * flipV - Boolean indicating if the output should be flipped vertically.
+ * cx - Number that represents the x-coordinate of the rotation center.
+ * cy - Number that represents the y-coordinate of the rotation center.
+ */
+mxXmlCanvas2D.prototype.rotate = function(theta, flipH, flipV, cx, cy)
+{
+	var elem = this.createElement('rotate');
+	
+	if (theta != 0 || flipH || flipV)
+	{
+		elem.setAttribute('theta', this.format(theta));
+		elem.setAttribute('flipH', (flipH) ? '1' : '0');
+		elem.setAttribute('flipV', (flipV) ? '1' : '0');
+		elem.setAttribute('cx', this.format(cx));
+		elem.setAttribute('cy', this.format(cy));
+		this.root.appendChild(elem);
+	}
+};
+
+/**
+ * Function: setAlpha
+ * 
+ * Sets the current alpha.
+ * 
+ * Parameters:
+ * 
+ * value - Number that represents the new alpha. Possible values are between
+ * 1 (opaque) and 0 (transparent).
+ */
+mxXmlCanvas2D.prototype.setAlpha = function(value)
+{
+	if (this.compressed)
+	{
+		if (this.state.alpha == value)
+		{
+			return;
+		}
+		
+		mxAbstractCanvas2D.prototype.setAlpha.apply(this, arguments);
+	}
+	
+	var elem = this.createElement('alpha');
+	elem.setAttribute('alpha', this.format(value));
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: setFillAlpha
+ * 
+ * Sets the current fill alpha.
+ * 
+ * Parameters:
+ * 
+ * value - Number that represents the new fill alpha. Possible values are between
+ * 1 (opaque) and 0 (transparent).
+ */
+mxXmlCanvas2D.prototype.setFillAlpha = function(value)
+{
+	if (this.compressed)
+	{
+		if (this.state.fillAlpha == value)
+		{
+			return;
+		}
+		
+		mxAbstractCanvas2D.prototype.setFillAlpha.apply(this, arguments);
+	}
+	
+	var elem = this.createElement('fillalpha');
+	elem.setAttribute('alpha', this.format(value));
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: setStrokeAlpha
+ * 
+ * Sets the current stroke alpha.
+ * 
+ * Parameters:
+ * 
+ * value - Number that represents the new stroke alpha. Possible values are between
+ * 1 (opaque) and 0 (transparent).
+ */
+mxXmlCanvas2D.prototype.setStrokeAlpha = function(value)
+{
+	if (this.compressed)
+	{
+		if (this.state.strokeAlpha == value)
+		{
+			return;
+		}
+		
+		mxAbstractCanvas2D.prototype.setStrokeAlpha.apply(this, arguments);
+	}
+	
+	var elem = this.createElement('strokealpha');
+	elem.setAttribute('alpha', this.format(value));
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: setFillColor
+ * 
+ * Sets the current fill color.
+ * 
+ * Parameters:
+ * 
+ * value - Hexadecimal representation of the color or 'none'.
+ */
+mxXmlCanvas2D.prototype.setFillColor = function(value)
+{
+	if (value == mxConstants.NONE)
+	{
+		value = null;
+	}
+	
+	if (this.compressed)
+	{
+		if (this.state.fillColor == value)
+		{
+			return;
+		}
+		
+		mxAbstractCanvas2D.prototype.setFillColor.apply(this, arguments);
+	}
+	
+	var elem = this.createElement('fillcolor');
+	elem.setAttribute('color', (value != null) ? value : mxConstants.NONE);
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: setGradient
+ * 
+ * Sets the gradient. Note that the coordinates may be ignored by some implementations.
+ * 
+ * Parameters:
+ * 
+ * color1 - Hexadecimal representation of the start color.
+ * color2 - Hexadecimal representation of the end color.
+ * x - X-coordinate of the gradient region.
+ * y - y-coordinate of the gradient region.
+ * w - Width of the gradient region.
+ * h - Height of the gradient region.
+ * direction - One of <mxConstants.DIRECTION_NORTH>, <mxConstants.DIRECTION_EAST>,
+ * <mxConstants.DIRECTION_SOUTH> or <mxConstants.DIRECTION_WEST>.
+ * alpha1 - Optional alpha of the start color. Default is 1. Possible values
+ * are between 1 (opaque) and 0 (transparent).
+ * alpha2 - Optional alpha of the end color. Default is 1. Possible values
+ * are between 1 (opaque) and 0 (transparent).
+ */
+mxXmlCanvas2D.prototype.setGradient = function(color1, color2, x, y, w, h, direction, alpha1, alpha2)
+{
+	if (color1 != null && color2 != null)
+	{
+		mxAbstractCanvas2D.prototype.setGradient.apply(this, arguments);
+		
+		var elem = this.createElement('gradient');
+		elem.setAttribute('c1', color1);
+		elem.setAttribute('c2', color2);
+		elem.setAttribute('x', this.format(x));
+		elem.setAttribute('y', this.format(y));
+		elem.setAttribute('w', this.format(w));
+		elem.setAttribute('h', this.format(h));
+		
+		// Default direction is south
+		if (direction != null)
+		{
+			elem.setAttribute('direction', direction);
+		}
+		
+		if (alpha1 != null)
+		{
+			elem.setAttribute('alpha1', alpha1);
+		}
+		
+		if (alpha2 != null)
+		{
+			elem.setAttribute('alpha2', alpha2);
+		}
+		
+		this.root.appendChild(elem);
+	}
+};
+
+/**
+ * Function: setStrokeColor
+ * 
+ * Sets the current stroke color.
+ * 
+ * Parameters:
+ * 
+ * value - Hexadecimal representation of the color or 'none'.
+ */
+mxXmlCanvas2D.prototype.setStrokeColor = function(value)
+{
+	if (value == mxConstants.NONE)
+	{
+		value = null;
+	}
+	
+	if (this.compressed)
+	{
+		if (this.state.strokeColor == value)
+		{
+			return;
+		}
+		
+		mxAbstractCanvas2D.prototype.setStrokeColor.apply(this, arguments);
+	}
+	
+	var elem = this.createElement('strokecolor');
+	elem.setAttribute('color', (value != null) ? value : mxConstants.NONE);
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: setStrokeWidth
+ * 
+ * Sets the current stroke width.
+ * 
+ * Parameters:
+ * 
+ * value - Numeric representation of the stroke width.
+ */
+mxXmlCanvas2D.prototype.setStrokeWidth = function(value)
+{
+	if (this.compressed)
+	{
+		if (this.state.strokeWidth == value)
+		{
+			return;
+		}
+		
+		mxAbstractCanvas2D.prototype.setStrokeWidth.apply(this, arguments);
+	}
+	
+	var elem = this.createElement('strokewidth');
+	elem.setAttribute('width', this.format(value));
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: setDashed
+ * 
+ * Enables or disables dashed lines.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean that specifies if dashed lines should be enabled.
+ * value - Boolean that specifies if the stroke width should be ignored
+ * for the dash pattern. Default is false.
+ */
+mxXmlCanvas2D.prototype.setDashed = function(value, fixDash)
+{
+	if (this.compressed)
+	{
+		if (this.state.dashed == value)
+		{
+			return;
+		}
+		
+		mxAbstractCanvas2D.prototype.setDashed.apply(this, arguments);
+	}
+	
+	var elem = this.createElement('dashed');
+	elem.setAttribute('dashed', (value) ? '1' : '0');
+	
+	if (fixDash != null)
+	{
+		elem.setAttribute('fixDash', (fixDash) ? '1' : '0');
+	}
+	
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: setDashPattern
+ * 
+ * Sets the current dash pattern. Default is '3 3'.
+ * 
+ * Parameters:
+ * 
+ * value - String that represents the dash pattern, which is a sequence of
+ * numbers defining the length of the dashes and the length of the spaces
+ * between the dashes. The lengths are relative to the line width - a length
+ * of 1 is equals to the line width.
+ */
+mxXmlCanvas2D.prototype.setDashPattern = function(value)
+{
+	if (this.compressed)
+	{
+		if (this.state.dashPattern == value)
+		{
+			return;
+		}
+		
+		mxAbstractCanvas2D.prototype.setDashPattern.apply(this, arguments);
+	}
+	
+	var elem = this.createElement('dashpattern');
+	elem.setAttribute('pattern', value);
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: setLineCap
+ * 
+ * Sets the line cap. Default is 'flat' which corresponds to 'butt' in SVG.
+ * 
+ * Parameters:
+ * 
+ * value - String that represents the line cap. Possible values are flat, round
+ * and square.
+ */
+mxXmlCanvas2D.prototype.setLineCap = function(value)
+{
+	if (this.compressed)
+	{
+		if (this.state.lineCap == value)
+		{
+			return;
+		}
+		
+		mxAbstractCanvas2D.prototype.setLineCap.apply(this, arguments);
+	}
+	
+	var elem = this.createElement('linecap');
+	elem.setAttribute('cap', value);
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: setLineJoin
+ * 
+ * Sets the line join. Default is 'miter'.
+ * 
+ * Parameters:
+ * 
+ * value - String that represents the line join. Possible values are miter,
+ * round and bevel.
+ */
+mxXmlCanvas2D.prototype.setLineJoin = function(value)
+{
+	if (this.compressed)
+	{
+		if (this.state.lineJoin == value)
+		{
+			return;
+		}
+		
+		mxAbstractCanvas2D.prototype.setLineJoin.apply(this, arguments);
+	}
+	
+	var elem = this.createElement('linejoin');
+	elem.setAttribute('join', value);
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: setMiterLimit
+ * 
+ * Sets the miter limit. Default is 10.
+ * 
+ * Parameters:
+ * 
+ * value - Number that represents the miter limit.
+ */
+mxXmlCanvas2D.prototype.setMiterLimit = function(value)
+{
+	if (this.compressed)
+	{
+		if (this.state.miterLimit == value)
+		{
+			return;
+		}
+		
+		mxAbstractCanvas2D.prototype.setMiterLimit.apply(this, arguments);
+	}
+	
+	var elem = this.createElement('miterlimit');
+	elem.setAttribute('limit', value);
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: setFontColor
+ * 
+ * Sets the current font color. Default is '#000000'.
+ * 
+ * Parameters:
+ * 
+ * value - Hexadecimal representation of the color or 'none'.
+ */
+mxXmlCanvas2D.prototype.setFontColor = function(value)
+{
+	if (this.textEnabled)
+	{
+		if (value == mxConstants.NONE)
+		{
+			value = null;
+		}
+		
+		if (this.compressed)
+		{
+			if (this.state.fontColor == value)
+			{
+				return;
+			}
+			
+			mxAbstractCanvas2D.prototype.setFontColor.apply(this, arguments);
+		}
+		
+		var elem = this.createElement('fontcolor');
+		elem.setAttribute('color', (value != null) ? value : mxConstants.NONE);
+		this.root.appendChild(elem);
+	}
+};
+
+/**
+ * Function: setFontBackgroundColor
+ * 
+ * Sets the current font background color.
+ * 
+ * Parameters:
+ * 
+ * value - Hexadecimal representation of the color or 'none'.
+ */
+mxXmlCanvas2D.prototype.setFontBackgroundColor = function(value)
+{
+	if (this.textEnabled)
+	{
+		if (value == mxConstants.NONE)
+		{
+			value = null;
+		}
+		
+		if (this.compressed)
+		{
+			if (this.state.fontBackgroundColor == value)
+			{
+				return;
+			}
+			
+			mxAbstractCanvas2D.prototype.setFontBackgroundColor.apply(this, arguments);
+		}
+
+		var elem = this.createElement('fontbackgroundcolor');
+		elem.setAttribute('color', (value != null) ? value : mxConstants.NONE);
+		this.root.appendChild(elem);
+	}
+};
+
+/**
+ * Function: setFontBorderColor
+ * 
+ * Sets the current font border color.
+ * 
+ * Parameters:
+ * 
+ * value - Hexadecimal representation of the color or 'none'.
+ */
+mxXmlCanvas2D.prototype.setFontBorderColor = function(value)
+{
+	if (this.textEnabled)
+	{
+		if (value == mxConstants.NONE)
+		{
+			value = null;
+		}
+		
+		if (this.compressed)
+		{
+			if (this.state.fontBorderColor == value)
+			{
+				return;
+			}
+			
+			mxAbstractCanvas2D.prototype.setFontBorderColor.apply(this, arguments);
+		}
+		
+		var elem = this.createElement('fontbordercolor');
+		elem.setAttribute('color', (value != null) ? value : mxConstants.NONE);
+		this.root.appendChild(elem);
+	}
+};
+
+/**
+ * Function: setFontSize
+ * 
+ * Sets the current font size. Default is <mxConstants.DEFAULT_FONTSIZE>.
+ * 
+ * Parameters:
+ * 
+ * value - Numeric representation of the font size.
+ */
+mxXmlCanvas2D.prototype.setFontSize = function(value)
+{
+	if (this.textEnabled)
+	{
+		if (this.compressed)
+		{
+			if (this.state.fontSize == value)
+			{
+				return;
+			}
+			
+			mxAbstractCanvas2D.prototype.setFontSize.apply(this, arguments);
+		}
+		
+		var elem = this.createElement('fontsize');
+		elem.setAttribute('size', value);
+		this.root.appendChild(elem);
+	}
+};
+
+/**
+ * Function: setFontFamily
+ * 
+ * Sets the current font family. Default is <mxConstants.DEFAULT_FONTFAMILY>.
+ * 
+ * Parameters:
+ * 
+ * value - String representation of the font family. This handles the same
+ * values as the CSS font-family property.
+ */
+mxXmlCanvas2D.prototype.setFontFamily = function(value)
+{
+	if (this.textEnabled)
+	{
+		if (this.compressed)
+		{
+			if (this.state.fontFamily == value)
+			{
+				return;
+			}
+			
+			mxAbstractCanvas2D.prototype.setFontFamily.apply(this, arguments);
+		}
+		
+		var elem = this.createElement('fontfamily');
+		elem.setAttribute('family', value);
+		this.root.appendChild(elem);
+	}
+};
+
+/**
+ * Function: setFontStyle
+ * 
+ * Sets the current font style.
+ * 
+ * Parameters:
+ * 
+ * value - Numeric representation of the font family. This is the sum of the
+ * font styles from <mxConstants>.
+ */
+mxXmlCanvas2D.prototype.setFontStyle = function(value)
+{
+	if (this.textEnabled)
+	{
+		if (value == null)
+		{
+			value = 0;
+		}
+		
+		if (this.compressed)
+		{
+			if (this.state.fontStyle == value)
+			{
+				return;
+			}
+			
+			mxAbstractCanvas2D.prototype.setFontStyle.apply(this, arguments);
+		}
+		
+		var elem = this.createElement('fontstyle');
+		elem.setAttribute('style', value);
+		this.root.appendChild(elem);
+	}
+};
+
+/**
+ * Function: setShadow
+ * 
+ * Enables or disables shadows.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean that specifies if shadows should be enabled.
+ */
+mxXmlCanvas2D.prototype.setShadow = function(value)
+{
+	if (this.compressed)
+	{
+		if (this.state.shadow == value)
+		{
+			return;
+		}
+		
+		mxAbstractCanvas2D.prototype.setShadow.apply(this, arguments);
+	}
+	
+	var elem = this.createElement('shadow');
+	elem.setAttribute('enabled', (value) ? '1' : '0');
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: setShadowColor
+ * 
+ * Sets the current shadow color. Default is <mxConstants.SHADOWCOLOR>.
+ * 
+ * Parameters:
+ * 
+ * value - Hexadecimal representation of the color or 'none'.
+ */
+mxXmlCanvas2D.prototype.setShadowColor = function(value)
+{
+	if (this.compressed)
+	{
+		if (value == mxConstants.NONE)
+		{
+			value = null;
+		}
+		
+		if (this.state.shadowColor == value)
+		{
+			return;
+		}
+		
+		mxAbstractCanvas2D.prototype.setShadowColor.apply(this, arguments);
+	}
+	
+	var elem = this.createElement('shadowcolor');
+	elem.setAttribute('color', (value != null) ? value : mxConstants.NONE);
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: setShadowAlpha
+ * 
+ * Sets the current shadows alpha. Default is <mxConstants.SHADOW_OPACITY>.
+ * 
+ * Parameters:
+ * 
+ * value - Number that represents the new alpha. Possible values are between
+ * 1 (opaque) and 0 (transparent).
+ */
+mxXmlCanvas2D.prototype.setShadowAlpha = function(value)
+{
+	if (this.compressed)
+	{
+		if (this.state.shadowAlpha == value)
+		{
+			return;
+		}
+		
+		mxAbstractCanvas2D.prototype.setShadowAlpha.apply(this, arguments);
+	}
+	
+	var elem = this.createElement('shadowalpha');
+	elem.setAttribute('alpha', value);
+	this.root.appendChild(elem);
+	
+};
+
+/**
+ * Function: setShadowOffset
+ * 
+ * Sets the current shadow offset.
+ * 
+ * Parameters:
+ * 
+ * dx - Number that represents the horizontal offset of the shadow.
+ * dy - Number that represents the vertical offset of the shadow.
+ */
+mxXmlCanvas2D.prototype.setShadowOffset = function(dx, dy)
+{
+	if (this.compressed)
+	{
+		if (this.state.shadowDx == dx && this.state.shadowDy == dy)
+		{
+			return;
+		}
+		
+		mxAbstractCanvas2D.prototype.setShadowOffset.apply(this, arguments);
+	}
+	
+	var elem = this.createElement('shadowoffset');
+	elem.setAttribute('dx', dx);
+	elem.setAttribute('dy', dy);
+	this.root.appendChild(elem);
+	
+};
+
+/**
+ * Function: rect
+ * 
+ * Puts a rectangle into the drawing buffer.
+ * 
+ * Parameters:
+ * 
+ * x - Number that represents the x-coordinate of the rectangle.
+ * y - Number that represents the y-coordinate of the rectangle.
+ * w - Number that represents the width of the rectangle.
+ * h - Number that represents the height of the rectangle.
+ */
+mxXmlCanvas2D.prototype.rect = function(x, y, w, h)
+{
+	var elem = this.createElement('rect');
+	elem.setAttribute('x', this.format(x));
+	elem.setAttribute('y', this.format(y));
+	elem.setAttribute('w', this.format(w));
+	elem.setAttribute('h', this.format(h));
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: roundrect
+ * 
+ * Puts a rounded rectangle into the drawing buffer.
+ * 
+ * Parameters:
+ * 
+ * x - Number that represents the x-coordinate of the rectangle.
+ * y - Number that represents the y-coordinate of the rectangle.
+ * w - Number that represents the width of the rectangle.
+ * h - Number that represents the height of the rectangle.
+ * dx - Number that represents the horizontal rounding.
+ * dy - Number that represents the vertical rounding.
+ */
+mxXmlCanvas2D.prototype.roundrect = function(x, y, w, h, dx, dy)
+{
+	var elem = this.createElement('roundrect');
+	elem.setAttribute('x', this.format(x));
+	elem.setAttribute('y', this.format(y));
+	elem.setAttribute('w', this.format(w));
+	elem.setAttribute('h', this.format(h));
+	elem.setAttribute('dx', this.format(dx));
+	elem.setAttribute('dy', this.format(dy));
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: ellipse
+ * 
+ * Puts an ellipse into the drawing buffer.
+ * 
+ * Parameters:
+ * 
+ * x - Number that represents the x-coordinate of the ellipse.
+ * y - Number that represents the y-coordinate of the ellipse.
+ * w - Number that represents the width of the ellipse.
+ * h - Number that represents the height of the ellipse.
+ */
+mxXmlCanvas2D.prototype.ellipse = function(x, y, w, h)
+{
+	var elem = this.createElement('ellipse');
+	elem.setAttribute('x', this.format(x));
+	elem.setAttribute('y', this.format(y));
+	elem.setAttribute('w', this.format(w));
+	elem.setAttribute('h', this.format(h));
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: image
+ * 
+ * Paints an image.
+ * 
+ * Parameters:
+ * 
+ * x - Number that represents the x-coordinate of the image.
+ * y - Number that represents the y-coordinate of the image.
+ * w - Number that represents the width of the image.
+ * h - Number that represents the height of the image.
+ * src - String that specifies the URL of the image.
+ * aspect - Boolean indicating if the aspect of the image should be preserved.
+ * flipH - Boolean indicating if the image should be flipped horizontally.
+ * flipV - Boolean indicating if the image should be flipped vertically.
+ */
+mxXmlCanvas2D.prototype.image = function(x, y, w, h, src, aspect, flipH, flipV)
+{
+	src = this.converter.convert(src);
+	
+	// LATER: Add option for embedding images as base64.
+	var elem = this.createElement('image');
+	elem.setAttribute('x', this.format(x));
+	elem.setAttribute('y', this.format(y));
+	elem.setAttribute('w', this.format(w));
+	elem.setAttribute('h', this.format(h));
+	elem.setAttribute('src', src);
+	elem.setAttribute('aspect', (aspect) ? '1' : '0');
+	elem.setAttribute('flipH', (flipH) ? '1' : '0');
+	elem.setAttribute('flipV', (flipV) ? '1' : '0');
+	this.root.appendChild(elem);
+};
+
+/**
+ * Function: begin
+ * 
+ * Starts a new path and puts it into the drawing buffer.
+ */
+mxXmlCanvas2D.prototype.begin = function()
+{
+	this.root.appendChild(this.createElement('begin'));
+	this.lastX = 0;
+	this.lastY = 0;
+};
+
+/**
+ * Function: moveTo
+ * 
+ * Moves the current path the given point.
+ * 
+ * Parameters:
+ * 
+ * x - Number that represents the x-coordinate of the point.
+ * y - Number that represents the y-coordinate of the point.
+ */
+mxXmlCanvas2D.prototype.moveTo = function(x, y)
+{
+	var elem = this.createElement('move');
+	elem.setAttribute('x', this.format(x));
+	elem.setAttribute('y', this.format(y));
+	this.root.appendChild(elem);
+	this.lastX = x;
+	this.lastY = y;
+};
+
+/**
+ * Function: lineTo
+ * 
+ * Draws a line to the given coordinates.
+ * 
+ * Parameters:
+ * 
+ * x - Number that represents the x-coordinate of the endpoint.
+ * y - Number that represents the y-coordinate of the endpoint.
+ */
+mxXmlCanvas2D.prototype.lineTo = function(x, y)
+{
+	var elem = this.createElement('line');
+	elem.setAttribute('x', this.format(x));
+	elem.setAttribute('y', this.format(y));
+	this.root.appendChild(elem);
+	this.lastX = x;
+	this.lastY = y;
+};
+
+/**
+ * Function: quadTo
+ * 
+ * Adds a quadratic curve to the current path.
+ * 
+ * Parameters:
+ * 
+ * x1 - Number that represents the x-coordinate of the control point.
+ * y1 - Number that represents the y-coordinate of the control point.
+ * x2 - Number that represents the x-coordinate of the endpoint.
+ * y2 - Number that represents the y-coordinate of the endpoint.
+ */
+mxXmlCanvas2D.prototype.quadTo = function(x1, y1, x2, y2)
+{
+	var elem = this.createElement('quad');
+	elem.setAttribute('x1', this.format(x1));
+	elem.setAttribute('y1', this.format(y1));
+	elem.setAttribute('x2', this.format(x2));
+	elem.setAttribute('y2', this.format(y2));
+	this.root.appendChild(elem);
+	this.lastX = x2;
+	this.lastY = y2;
+};
+
+/**
+ * Function: curveTo
+ * 
+ * Adds a bezier curve to the current path.
+ * 
+ * Parameters:
+ * 
+ * x1 - Number that represents the x-coordinate of the first control point.
+ * y1 - Number that represents the y-coordinate of the first control point.
+ * x2 - Number that represents the x-coordinate of the second control point.
+ * y2 - Number that represents the y-coordinate of the second control point.
+ * x3 - Number that represents the x-coordinate of the endpoint.
+ * y3 - Number that represents the y-coordinate of the endpoint.
+ */
+mxXmlCanvas2D.prototype.curveTo = function(x1, y1, x2, y2, x3, y3)
+{
+	var elem = this.createElement('curve');
+	elem.setAttribute('x1', this.format(x1));
+	elem.setAttribute('y1', this.format(y1));
+	elem.setAttribute('x2', this.format(x2));
+	elem.setAttribute('y2', this.format(y2));
+	elem.setAttribute('x3', this.format(x3));
+	elem.setAttribute('y3', this.format(y3));
+	this.root.appendChild(elem);
+	this.lastX = x3;
+	this.lastY = y3;
+};
+
+/**
+ * Function: close
+ * 
+ * Closes the current path.
+ */
+mxXmlCanvas2D.prototype.close = function()
+{
+	this.root.appendChild(this.createElement('close'));
+};
+
+/**
+ * Function: text
+ * 
+ * Paints the given text. Possible values for format are empty string for
+ * plain text and html for HTML markup. Background and border color as well
+ * as clipping is not available in plain text labels for VML. HTML labels
+ * are not available as part of shapes with no foreignObject support in SVG
+ * (eg. IE9, IE10).
+ * 
+ * Parameters:
+ * 
+ * x - Number that represents the x-coordinate of the text.
+ * y - Number that represents the y-coordinate of the text.
+ * w - Number that represents the available width for the text or 0 for automatic width.
+ * h - Number that represents the available height for the text or 0 for automatic height.
+ * str - String that specifies the text to be painted.
+ * align - String that represents the horizontal alignment.
+ * valign - String that represents the vertical alignment.
+ * wrap - Boolean that specifies if word-wrapping is enabled. Requires w > 0.
+ * format - Empty string for plain text or 'html' for HTML markup.
+ * overflow - Specifies the overflow behaviour of the label. Requires w > 0 and/or h > 0.
+ * clip - Boolean that specifies if the label should be clipped. Requires w > 0 and/or h > 0.
+ * rotation - Number that specifies the angle of the rotation around the anchor point of the text.
+ * dir - Optional string that specifies the text direction. Possible values are rtl and lrt.
+ */
+mxXmlCanvas2D.prototype.text = function(x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation, dir)
+{
+	if (this.textEnabled && str != null)
+	{
+		if (mxUtils.isNode(str))
+		{
+			str = mxUtils.getOuterHtml(str);
+		}
+		
+		var elem = this.createElement('text');
+		elem.setAttribute('x', this.format(x));
+		elem.setAttribute('y', this.format(y));
+		elem.setAttribute('w', this.format(w));
+		elem.setAttribute('h', this.format(h));
+		elem.setAttribute('str', str);
+		
+		if (align != null)
+		{
+			elem.setAttribute('align', align);
+		}
+		
+		if (valign != null)
+		{
+			elem.setAttribute('valign', valign);
+		}
+		
+		elem.setAttribute('wrap', (wrap) ? '1' : '0');
+		
+		if (format == null)
+		{
+			format = '';
+		}
+		
+		elem.setAttribute('format', format);
+		
+		if (overflow != null)
+		{
+			elem.setAttribute('overflow', overflow);
+		}
+		
+		if (clip != null)
+		{
+			elem.setAttribute('clip', (clip) ? '1' : '0');
+		}
+		
+		if (rotation != null)
+		{
+			elem.setAttribute('rotation', rotation);
+		}
+		
+		if (dir != null)
+		{
+			elem.setAttribute('dir', dir);
+		}
+		
+		this.root.appendChild(elem);
+	}
+};
+
+/**
+ * Function: stroke
+ * 
+ * Paints the outline of the current drawing buffer.
+ */
+mxXmlCanvas2D.prototype.stroke = function()
+{
+	this.root.appendChild(this.createElement('stroke'));
+};
+
+/**
+ * Function: fill
+ * 
+ * Fills the current drawing buffer.
+ */
+mxXmlCanvas2D.prototype.fill = function()
+{
+	this.root.appendChild(this.createElement('fill'));
+};
+
+/**
+ * Function: fillAndStroke
+ * 
+ * Fills the current drawing buffer and its outline.
+ */
+mxXmlCanvas2D.prototype.fillAndStroke = function()
+{
+	this.root.appendChild(this.createElement('fillstroke'));
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/util/mxXmlRequest.js b/airavata-kubernetes/workflow-composer/src/js/util/mxXmlRequest.js
new file mode 100644
index 0000000..4dccf1f
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/util/mxXmlRequest.js
@@ -0,0 +1,463 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxXmlRequest
+ * 
+ * XML HTTP request wrapper. See also: <mxUtils.get>, <mxUtils.post> and
+ * <mxUtils.load>. This class provides a cross-browser abstraction for Ajax
+ * requests.
+ * 
+ * Encoding:
+ * 
+ * For encoding parameter values, the built-in encodeURIComponent JavaScript
+ * method must be used. For automatic encoding of post data in <mxEditor> the
+ * <mxEditor.escapePostData> switch can be set to true (default). The encoding
+ * will be carried out using the conte type of the page. That is, the page
+ * containting the editor should contain a meta tag in the header, eg.
+ * <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ * 
+ * Example:
+ * 
+ * (code)
+ * var onload = function(req)
+ * {
+ *   mxUtils.alert(req.getDocumentElement());
+ * }
+ * 
+ * var onerror = function(req)
+ * {
+ *   mxUtils.alert('Error');
+ * }
+ * new mxXmlRequest(url, 'key=value').send(onload, onerror);
+ * (end)
+ * 
+ * Sends an asynchronous POST request to the specified URL.
+ * 
+ * Example:
+ * 
+ * (code)
+ * var req = new mxXmlRequest(url, 'key=value', 'POST', false);
+ * req.send();
+ * mxUtils.alert(req.getDocumentElement());
+ * (end)
+ * 
+ * Sends a synchronous POST request to the specified URL.
+ * 
+ * Example:
+ * 
+ * (code)
+ * var encoder = new mxCodec();
+ * var result = encoder.encode(graph.getModel());
+ * var xml = encodeURIComponent(mxUtils.getXml(result));
+ * new mxXmlRequest(url, 'xml='+xml).send();
+ * (end)
+ * 
+ * Sends an encoded graph model to the specified URL using xml as the
+ * parameter name. The parameter can then be retrieved in C# as follows:
+ * 
+ * (code)
+ * string xml = HttpUtility.UrlDecode(context.Request.Params["xml"]);
+ * (end)
+ * 
+ * Or in Java as follows:
+ * 
+ * (code)
+ * String xml = URLDecoder.decode(request.getParameter("xml"), "UTF-8").replace("\n", "&#xa;");
+ * (end)
+ *
+ * Note that the linefeeds should only be replaced if the XML is
+ * processed in Java, for example when creating an image.
+ * 
+ * Constructor: mxXmlRequest
+ * 
+ * Constructs an XML HTTP request.
+ * 
+ * Parameters:
+ * 
+ * url - Target URL of the request.
+ * params - Form encoded parameters to send with a POST request.
+ * method - String that specifies the request method. Possible values are
+ * POST and GET. Default is POST.
+ * async - Boolean specifying if an asynchronous request should be used.
+ * Default is true.
+ * username - String specifying the username to be used for the request.
+ * password - String specifying the password to be used for the request.
+ */
+function mxXmlRequest(url, params, method, async, username, password)
+{
+	this.url = url;
+	this.params = params;
+	this.method = method || 'POST';
+	this.async = (async != null) ? async : true;
+	this.username = username;
+	this.password = password;
+};
+
+/**
+ * Variable: url
+ * 
+ * Holds the target URL of the request.
+ */
+mxXmlRequest.prototype.url = null;
+
+/**
+ * Variable: params
+ * 
+ * Holds the form encoded data for the POST request.
+ */
+mxXmlRequest.prototype.params = null;
+
+/**
+ * Variable: method
+ * 
+ * Specifies the request method. Possible values are POST and GET. Default
+ * is POST.
+ */
+mxXmlRequest.prototype.method = null;
+
+/**
+ * Variable: async
+ * 
+ * Boolean indicating if the request is asynchronous.
+ */
+mxXmlRequest.prototype.async = null;
+
+/**
+ * Variable: binary
+ * 
+ * Boolean indicating if the request is binary. This option is ignored in IE.
+ * In all other browsers the requested mime type is set to
+ * text/plain; charset=x-user-defined. Default is false.
+ */
+mxXmlRequest.prototype.binary = false;
+
+/**
+ * Variable: withCredentials
+ * 
+ * Specifies if withCredentials should be used in HTML5-compliant browsers. Default is
+ * false.
+ */
+mxXmlRequest.prototype.withCredentials = false;
+
+/**
+ * Variable: username
+ * 
+ * Specifies the username to be used for authentication.
+ */
+mxXmlRequest.prototype.username = null;
+
+/**
+ * Variable: password
+ * 
+ * Specifies the password to be used for authentication.
+ */
+mxXmlRequest.prototype.password = null;
+
+/**
+ * Variable: request
+ * 
+ * Holds the inner, browser-specific request object.
+ */
+mxXmlRequest.prototype.request = null;
+
+/**
+ * Variable: decodeSimulateValues
+ * 
+ * Specifies if request values should be decoded as URIs before setting the
+ * textarea value in <simulate>. Defaults to false for backwards compatibility,
+ * to avoid another decode on the server this should be set to true.
+ */
+mxXmlRequest.prototype.decodeSimulateValues = false;
+
+/**
+ * Function: isBinary
+ * 
+ * Returns <binary>.
+ */
+mxXmlRequest.prototype.isBinary = function()
+{
+	return this.binary;
+};
+
+/**
+ * Function: setBinary
+ * 
+ * Sets <binary>.
+ */
+mxXmlRequest.prototype.setBinary = function(value)
+{
+	this.binary = value;
+};
+
+/**
+ * Function: getText
+ * 
+ * Returns the response as a string.
+ */
+mxXmlRequest.prototype.getText = function()
+{
+	return this.request.responseText;
+};
+
+/**
+ * Function: isReady
+ * 
+ * Returns true if the response is ready.
+ */
+mxXmlRequest.prototype.isReady = function()
+{
+	return this.request.readyState == 4;
+};
+
+/**
+ * Function: getDocumentElement
+ * 
+ * Returns the document element of the response XML document.
+ */
+mxXmlRequest.prototype.getDocumentElement = function()
+{
+	var doc = this.getXml();
+	
+	if (doc != null)
+	{
+		return doc.documentElement;
+	}
+	
+	return null;
+};
+
+/**
+ * Function: getXml
+ * 
+ * Returns the response as an XML document. Use <getDocumentElement> to get
+ * the document element of the XML document.
+ */
+mxXmlRequest.prototype.getXml = function()
+{
+	var xml = this.request.responseXML;
+	
+	// Handles missing response headers in IE, the first condition handles
+	// the case where responseXML is there, but using its nodes leads to
+	// type errors in the mxCellCodec when putting the nodes into a new
+	// document. This happens in IE9 standards mode and with XML user
+	// objects only, as they are used directly as values in cells.
+	if (document.documentMode >= 9 || xml == null || xml.documentElement == null)
+	{
+		xml = mxUtils.parseXml(this.request.responseText);
+	}
+	
+	return xml;
+};
+
+/**
+ * Function: getText
+ * 
+ * Returns the response as a string.
+ */
+mxXmlRequest.prototype.getText = function()
+{
+	return this.request.responseText;
+};
+
+/**
+ * Function: getStatus
+ * 
+ * Returns the status as a number, eg. 404 for "Not found" or 200 for "OK".
+ * Note: The NS_ERROR_NOT_AVAILABLE for invalid responses cannot be cought.
+ */
+mxXmlRequest.prototype.getStatus = function()
+{
+	return this.request.status;
+};
+
+/**
+ * Function: create
+ * 
+ * Creates and returns the inner <request> object.
+ */
+mxXmlRequest.prototype.create = function()
+{
+	if (window.XMLHttpRequest)
+	{
+		return function()
+		{
+			var req = new XMLHttpRequest();
+			
+			// TODO: Check for overrideMimeType required here?
+			if (this.isBinary() && req.overrideMimeType)
+			{
+				req.overrideMimeType('text/plain; charset=x-user-defined');
+			}
+
+			return req;
+		};
+	}
+	else if (typeof(ActiveXObject) != 'undefined')
+	{
+		return function()
+		{
+			// TODO: Implement binary option
+			return new ActiveXObject('Microsoft.XMLHTTP');
+		};
+	}
+}();
+
+/**
+ * Function: send
+ * 
+ * Send the <request> to the target URL using the specified functions to
+ * process the response asychronously.
+ * 
+ * Parameters:
+ * 
+ * onload - Function to be invoked if a successful response was received.
+ * onerror - Function to be called on any error.
+ * timeout - Optional timeout in ms before calling ontimeout.
+ * ontimeout - Optional function to execute on timeout.
+ */
+mxXmlRequest.prototype.send = function(onload, onerror, timeout, ontimeout)
+{
+	this.request = this.create();
+	
+	if (this.request != null)
+	{
+		if (onload != null)
+		{
+			this.request.onreadystatechange = mxUtils.bind(this, function()
+			{
+				if (this.isReady())
+				{
+					onload(this);
+					this.onreadystatechaange = null;
+				}
+			});
+		}
+
+		this.request.open(this.method, this.url, this.async,
+			this.username, this.password);
+		this.setRequestHeaders(this.request, this.params);
+		
+		if (window.XMLHttpRequest && this.withCredentials)
+		{
+			this.request.withCredentials = 'true';
+		}
+		
+		if (!mxClient.IS_QUIRKS && (document.documentMode == null || document.documentMode > 9) &&
+			window.XMLHttpRequest && timeout != null && ontimeout != null)
+		{
+			this.request.timeout = timeout;
+			this.request.ontimeout = ontimeout;
+		}
+				
+		this.request.send(this.params);
+	}
+};
+
+/**
+ * Function: setRequestHeaders
+ * 
+ * Sets the headers for the given request and parameters. This sets the
+ * content-type to application/x-www-form-urlencoded if any params exist.
+ * 
+ * Example:
+ * 
+ * (code)
+ * request.setRequestHeaders = function(request, params)
+ * {
+ *   if (params != null)
+ *   {
+ *     request.setRequestHeader('Content-Type',
+ *             'multipart/form-data');
+ *     request.setRequestHeader('Content-Length',
+ *             params.length);
+ *   }
+ * };
+ * (end)
+ * 
+ * Use the code above before calling <send> if you require a
+ * multipart/form-data request.   
+ */
+mxXmlRequest.prototype.setRequestHeaders = function(request, params)
+{
+	if (params != null)
+	{
+		request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+	}
+};
+
+/**
+ * Function: simulate
+ * 
+ * Creates and posts a request to the given target URL using a dynamically
+ * created form inside the given document.
+ * 
+ * Parameters:
+ * 
+ * docs - Document that contains the form element.
+ * target - Target to send the form result to.
+ */
+mxXmlRequest.prototype.simulate = function(doc, target)
+{
+	doc = doc || document;
+	var old = null;
+
+	if (doc == document)
+	{
+		old = window.onbeforeunload;		
+		window.onbeforeunload = null;
+	}
+			
+	var form = doc.createElement('form');
+	form.setAttribute('method', this.method);
+	form.setAttribute('action', this.url);
+
+	if (target != null)
+	{
+		form.setAttribute('target', target);
+	}
+
+	form.style.display = 'none';
+	form.style.visibility = 'hidden';
+	
+	var pars = (this.params.indexOf('&') > 0) ?
+		this.params.split('&') :
+		this.params.split();
+
+	// Adds the parameters as textareas to the form
+	for (var i=0; i<pars.length; i++)
+	{
+		var pos = pars[i].indexOf('=');
+		
+		if (pos > 0)
+		{
+			var name = pars[i].substring(0, pos);
+			var value = pars[i].substring(pos+1);
+			
+			if (this.decodeSimulateValues)
+			{
+				value = decodeURIComponent(value);
+			}
+			
+			var textarea = doc.createElement('textarea');
+			textarea.setAttribute('wrap', 'off');
+			textarea.setAttribute('name', name);
+			mxUtils.write(textarea, value);
+			form.appendChild(textarea);
+		}
+	}
+	
+	doc.body.appendChild(form);
+	form.submit();
+	
+	if (form.parentNode != null)
+	{
+		form.parentNode.removeChild(form);
+	}
+
+	if (old != null)
+	{		
+		window.onbeforeunload = old;
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/view/mxCellEditor.js b/airavata-kubernetes/workflow-composer/src/js/view/mxCellEditor.js
new file mode 100644
index 0000000..7db84a7
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/view/mxCellEditor.js
@@ -0,0 +1,1069 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxCellEditor
+ *
+ * In-place editor for the graph. To control this editor, use
+ * <mxGraph.invokesStopCellEditing>, <mxGraph.enterStopsCellEditing> and
+ * <mxGraph.escapeEnabled>. If <mxGraph.enterStopsCellEditing> is true then
+ * ctrl-enter or shift-enter can be used to create a linefeed. The F2 and
+ * escape keys can always be used to stop editing.
+ * 
+ * To customize the location of the textbox in the graph, override
+ * <getEditorBounds> as follows:
+ * 
+ * (code)
+ * graph.cellEditor.getEditorBounds = function(state)
+ * {
+ *   var result = mxCellEditor.prototype.getEditorBounds.apply(this, arguments);
+ *   
+ *   if (this.graph.getModel().isEdge(state.cell))
+ *   {
+ *     result.x = state.getCenterX() - result.width / 2;
+ *     result.y = state.getCenterY() - result.height / 2;
+ *   }
+ *   
+ *   return result;
+ * };
+ * (end)
+ * 
+ * Note that this hook is only called if <autoSize> is false. If <autoSize> is true,
+ * then <mxShape.getLabelBounds> is used to compute the current bounds of the textbox.
+ * 
+ * The textarea uses the mxCellEditor CSS class. You can modify this class in
+ * your custom CSS. Note: You should modify the CSS after loading the client
+ * in the page.
+ *
+ * Example:
+ * 
+ * To only allow numeric input in the in-place editor, use the following code.
+ *
+ * (code)
+ * var text = graph.cellEditor.textarea;
+ * 
+ * mxEvent.addListener(text, 'keydown', function (evt)
+ * {
+ *   if (!(evt.keyCode >= 48 && evt.keyCode <= 57) &&
+ *       !(evt.keyCode >= 96 && evt.keyCode <= 105))
+ *   {
+ *     mxEvent.consume(evt);
+ *   }
+ * }); 
+ * (end)
+ * 
+ * Placeholder:
+ * 
+ * To implement a placeholder for cells without a label, use the
+ * <emptyLabelText> variable.
+ * 
+ * Resize in Chrome:
+ * 
+ * Resize of the textarea is disabled by default. If you want to enable
+ * this feature extend <init> and set this.textarea.style.resize = ''.
+ * 
+ * To start editing on a key press event, the container of the graph
+ * should have focus or a focusable parent should be used to add the
+ * key press handler as follows.
+ * 
+ * (code)
+ * mxEvent.addListener(graph.container, 'keypress', mxUtils.bind(this, function(evt)
+ * {
+ *   if (!graph.isEditing() && !graph.isSelectionEmpty() && evt.which !== 0 &&
+ *       !mxEvent.isAltDown(evt) && !mxEvent.isControlDown(evt) && !mxEvent.isMetaDown(evt))
+ *   {
+ *     graph.startEditing();
+ *     
+ *     if (mxClient.IS_FF)
+ *     {
+ *       graph.cellEditor.textarea.value = String.fromCharCode(evt.which);
+ *     }
+ *   }
+ * }));
+ * (end)
+ * 
+ * To allow focus for a DIV, and hence to receive key press events, some browsers
+ * require it to have a valid tabindex attribute. In this case the following
+ * code may be used to keep the container focused.
+ * 
+ * (code)
+ * var graphFireMouseEvent = graph.fireMouseEvent;
+ * graph.fireMouseEvent = function(evtName, me, sender)
+ * {
+ *   if (evtName == mxEvent.MOUSE_DOWN)
+ *   {
+ *     this.container.focus();
+ *   }
+ *   
+ *   graphFireMouseEvent.apply(this, arguments);
+ * };
+ * (end)
+ *
+ * Constructor: mxCellEditor
+ *
+ * Constructs a new in-place editor for the specified graph.
+ * 
+ * Parameters:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ */
+function mxCellEditor(graph)
+{
+	this.graph = graph;
+	
+	// Stops editing after zoom changes
+	this.zoomHandler = mxUtils.bind(this, function()
+	{
+		if (this.graph.isEditing())
+		{
+			this.resize();
+		}
+	});
+	
+	this.graph.view.addListener(mxEvent.SCALE, this.zoomHandler);
+	this.graph.view.addListener(mxEvent.SCALE_AND_TRANSLATE, this.zoomHandler);
+	
+	// Adds handling of deleted cells while editing
+	this.changeHandler = mxUtils.bind(this, function(sender)
+	{
+		if (this.editingCell != null && this.graph.getView().getState(this.editingCell) == null)
+		{
+			this.stopEditing(true);
+		}
+	});
+
+	this.graph.getModel().addListener(mxEvent.CHANGE, this.changeHandler);
+};
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxCellEditor.prototype.graph = null;
+
+/**
+ * Variable: textarea
+ *
+ * Holds the DIV that is used for text editing. Note that this may be null before the first
+ * edit. Instantiated in <init>.
+ */
+mxCellEditor.prototype.textarea = null;
+
+/**
+ * Variable: editingCell
+ * 
+ * Reference to the <mxCell> that is currently being edited.
+ */
+mxCellEditor.prototype.editingCell = null;
+
+/**
+ * Variable: trigger
+ * 
+ * Reference to the event that was used to start editing.
+ */
+mxCellEditor.prototype.trigger = null;
+
+/**
+ * Variable: modified
+ * 
+ * Specifies if the label has been modified.
+ */
+mxCellEditor.prototype.modified = false;
+
+/**
+ * Variable: autoSize
+ * 
+ * Specifies if the textarea should be resized while the text is being edited.
+ * Default is true.
+ */
+mxCellEditor.prototype.autoSize = true;
+
+/**
+ * Variable: selectText
+ * 
+ * Specifies if the text should be selected when editing starts. Default is
+ * true.
+ */
+mxCellEditor.prototype.selectText = true;
+
+/**
+ * Variable: emptyLabelText
+ * 
+ * Text to be displayed for empty labels. Default is '' or '<br>' in Firefox as
+ * a workaround for the missing cursor bug for empty content editable. This can
+ * be set to eg. "[Type Here]" to easier visualize editing of empty labels. The
+ * value is only displayed before the first keystroke and is never used as the
+ * actual editing value.
+ */
+mxCellEditor.prototype.emptyLabelText = (mxClient.IS_FF) ? '<br>' : '';
+
+/**
+ * Variable: escapeCancelsEditing
+ * 
+ * If true, pressing the escape key will stop editing and not accept the new
+ * value. Change this to false to accept the new value on escape, and cancel
+ * editing on Shift+Escape instead. Default is true.
+ */
+mxCellEditor.prototype.escapeCancelsEditing = true;
+
+/**
+ * Variable: textNode
+ * 
+ * Reference to the label DOM node that has been hidden.
+ */
+mxCellEditor.prototype.textNode = '';
+
+/**
+ * Variable: zIndex
+ * 
+ * Specifies the zIndex for the textarea. Default is 5.
+ */
+mxCellEditor.prototype.zIndex = 5;
+
+/**
+ * Variable: minResize
+ * 
+ * Defines the minimum width and height to be used in <resize>. Default is 0x20px.
+ */
+mxCellEditor.prototype.minResize = new mxRectangle(0, 20);
+
+/**
+ * Variable: wordWrapPadding
+ * 
+ * Correction factor for word wrapping width. Default is 2 in quirks, 0 in IE
+ * 11 and 1 in all other browsers and modes.
+ */
+mxCellEditor.prototype.wordWrapPadding = (mxClient.IS_QUIRKS) ? 2 : (!mxClient.IS_IE11) ? 1 : 0;
+
+/**
+ * Variable: blurEnabled
+ *
+ * If <focusLost> should be called if <textarea> loses the focus. Default is false.
+ */
+mxCellEditor.prototype.blurEnabled = false;
+
+/**
+ * Variable: initialValue
+ * 
+ * Holds the initial editing value to check if the current value was modified.
+ */
+mxCellEditor.prototype.initialValue = null;
+
+/**
+ * Function: init
+ *
+ * Creates the <textarea> and installs the event listeners. The key handler
+ * updates the <modified> state.
+ */
+mxCellEditor.prototype.init = function ()
+{
+	this.textarea = document.createElement('div');
+	this.textarea.className = 'mxCellEditor mxPlainTextEditor';
+	this.textarea.contentEditable = true;
+	
+	// Workaround for selection outside of DIV if height is 0
+	if (mxClient.IS_GC)
+	{
+		this.textarea.style.minHeight = '1em';
+	}
+	
+	this.installListeners(this.textarea);
+};
+
+/**
+ * Function: applyValue
+ * 
+ * Called in <stopEditing> if cancel is false to invoke <mxGraph.labelChanged>.
+ */
+mxCellEditor.prototype.applyValue = function(state, value)
+{
+	this.graph.labelChanged(state.cell, value, this.trigger);
+};
+
+/**
+ * Function: getInitialValue
+ * 
+ * Gets the initial editing value for the given cell.
+ */
+mxCellEditor.prototype.getInitialValue = function(state, trigger)
+{
+	var result = mxUtils.htmlEntities(this.graph.getEditingValue(state.cell, trigger), false);
+	
+    // Workaround for trailing line breaks being ignored in the editor
+	if (!mxClient.IS_QUIRKS && document.documentMode != 8 && document.documentMode != 9 &&
+		document.documentMode != 10)
+	{
+		result = mxUtils.replaceTrailingNewlines(result, '<div><br></div>');
+	}
+    
+    return result.replace(/\n/g, '<br>');
+};
+
+/**
+ * Function: getCurrentValue
+ * 
+ * Returns the current editing value.
+ */
+mxCellEditor.prototype.getCurrentValue = function(state)
+{
+	return mxUtils.extractTextWithWhitespace(this.textarea.childNodes);
+};
+
+/**
+ * Function: installListeners
+ * 
+ * Installs listeners for focus, change and standard key event handling.
+ */
+mxCellEditor.prototype.installListeners = function(elt)
+{
+	// Applies value if focus is lost
+	mxEvent.addListener(elt, 'blur', mxUtils.bind(this, function(evt)
+	{
+		if (this.blurEnabled)
+		{
+			this.focusLost(evt);
+		}
+	}));
+
+	// Updates modified state and handles placeholder text
+	mxEvent.addListener(elt, 'keydown', mxUtils.bind(this, function(evt)
+	{
+		if (!mxEvent.isConsumed(evt))
+		{
+			if (this.isStopEditingEvent(evt))
+			{
+				this.graph.stopEditing(false);
+				mxEvent.consume(evt);
+			}
+			else if (evt.keyCode == 27 /* Escape */)
+			{
+				this.graph.stopEditing(this.escapeCancelsEditing || mxEvent.isShiftDown(evt));
+				mxEvent.consume(evt);
+			}
+		}
+	}));
+
+	// Keypress only fires if printable key was pressed and handles removing the empty placeholder
+	var keypressHandler = mxUtils.bind(this, function(evt)
+	{
+		if (this.editingCell != null)
+		{
+			// Clears the initial empty label on the first keystroke
+			// and workaround for FF which fires keypress for delete and backspace
+			if (this.clearOnChange && elt.innerHTML == this.getEmptyLabelText() &&
+				(!mxClient.IS_FF || (evt.keyCode != 8 /* Backspace */ && evt.keyCode != 46 /* Delete */)))
+			{
+				this.clearOnChange = false;
+				elt.innerHTML = '';
+			}
+		}
+	});
+
+	mxEvent.addListener(elt, 'keypress', keypressHandler);
+	mxEvent.addListener(elt, 'paste', keypressHandler);
+	
+	// Handler for updating the empty label text value after a change
+	var keyupHandler = mxUtils.bind(this, function(evt)
+	{
+		if (this.editingCell != null)
+		{
+			// Uses an optional text value for sempty labels which is cleared
+			// when the first keystroke appears. This makes it easier to see
+			// that a label is being edited even if the label is empty.
+			// In Safari and FF, an empty text is represented by <BR> which isn't enough to force a valid size
+			if (this.textarea.innerHTML.length == 0 || this.textarea.innerHTML == '<br>')
+			{
+				this.textarea.innerHTML = this.getEmptyLabelText();
+				this.clearOnChange = this.textarea.innerHTML.length > 0;
+			}
+			else
+			{
+				this.clearOnChange = false;
+			}
+		}
+	});
+
+	mxEvent.addListener(elt, (!mxClient.IS_IE11 && !mxClient.IS_IE) ? 'input' : 'keyup', keyupHandler);
+	mxEvent.addListener(elt, 'cut', keyupHandler);
+	mxEvent.addListener(elt, 'paste', keyupHandler);
+
+	// Adds automatic resizing of the textbox while typing using input, keyup and/or DOM change events
+	var evtName = (!mxClient.IS_IE11 && !mxClient.IS_IE) ? 'input' : 'keydown';
+	
+	var resizeHandler = mxUtils.bind(this, function(evt)
+	{
+		if (this.editingCell != null && this.autoSize && !mxEvent.isConsumed(evt))
+		{
+			// Asynchronous is needed for keydown and shows better results for input events overall
+			// (ie non-blocking and cases where the offsetWidth/-Height was wrong at this time)
+			if (this.resizeThread != null)
+			{
+				window.clearTimeout(this.resizeThread);
+			}
+			
+			this.resizeThread = window.setTimeout(mxUtils.bind(this, function()
+			{
+				this.resizeThread = null;
+				this.resize();
+			}), 0);
+		}
+	});
+	
+	mxEvent.addListener(elt, evtName, resizeHandler);
+
+	if (document.documentMode >= 9)
+	{
+		mxEvent.addListener(elt, 'DOMNodeRemoved', resizeHandler);
+		mxEvent.addListener(elt, 'DOMNodeInserted', resizeHandler);
+	}
+	else
+	{
+		mxEvent.addListener(elt, 'cut', resizeHandler);
+		mxEvent.addListener(elt, 'paste', resizeHandler);
+	}
+};
+
+/**
+ * Function: isStopEditingEvent
+ * 
+ * Returns true if the given keydown event should stop cell editing. This
+ * returns true if F2 is pressed of if <mxGraph.enterStopsCellEditing> is true
+ * and enter is pressed without control or shift.
+ */
+mxCellEditor.prototype.isStopEditingEvent = function(evt)
+{
+	return evt.keyCode == 113 /* F2 */ || (this.graph.isEnterStopsCellEditing() &&
+		evt.keyCode == 13 /* Enter */ && !mxEvent.isControlDown(evt) &&
+		!mxEvent.isShiftDown(evt));
+};
+
+/**
+ * Function: isEventSource
+ * 
+ * Returns true if this editor is the source for the given native event.
+ */
+mxCellEditor.prototype.isEventSource = function(evt)
+{
+	return mxEvent.getSource(evt) == this.textarea;
+};
+
+/**
+ * Function: resize
+ * 
+ * Returns <modified>.
+ */
+mxCellEditor.prototype.resize = function()
+{
+	var state = this.graph.getView().getState(this.editingCell);
+	
+	if (state == null)
+	{
+		this.stopEditing(true);
+	}
+	else if (this.textarea != null)
+	{
+		var isEdge = this.graph.getModel().isEdge(state.cell);
+ 		var scale = this.graph.getView().scale;
+ 		var m = null;
+		
+		if (!this.autoSize || (state.style[mxConstants.STYLE_OVERFLOW] == 'fill'))
+		{
+			// Specifies the bounds of the editor box
+			this.bounds = this.getEditorBounds(state);
+			this.textarea.style.width = Math.round(this.bounds.width / scale) + 'px';
+			this.textarea.style.height = Math.round(this.bounds.height / scale) + 'px';
+			
+			// FIXME: Offset when scaled
+			if (document.documentMode == 8 || mxClient.IS_QUIRKS)
+			{
+				this.textarea.style.left = Math.round(this.bounds.x) + 'px';
+				this.textarea.style.top = Math.round(this.bounds.y) + 'px';
+			}
+			else
+			{
+				this.textarea.style.left = Math.max(0, Math.round(this.bounds.x + 1)) + 'px';
+				this.textarea.style.top = Math.max(0, Math.round(this.bounds.y + 1)) + 'px';
+			}
+			
+			// Installs native word wrapping and avoids word wrap for empty label placeholder
+			if (this.graph.isWrapping(state.cell) && (this.bounds.width >= 2 || this.bounds.height >= 2) &&
+				this.textarea.innerHTML != this.getEmptyLabelText())
+			{
+				this.textarea.style.wordWrap = mxConstants.WORD_WRAP;
+				this.textarea.style.whiteSpace = 'normal';
+				
+				if (state.style[mxConstants.STYLE_OVERFLOW] != 'fill')
+				{
+					this.textarea.style.width = Math.round(this.bounds.width / scale) + this.wordWrapPadding + 'px';
+				}
+			}
+			else
+			{
+				this.textarea.style.whiteSpace = 'nowrap';
+				
+				if (state.style[mxConstants.STYLE_OVERFLOW] != 'fill')
+				{
+					this.textarea.style.width = '';
+				}
+			}
+		}
+		else
+	 	{
+	 		var lw = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_WIDTH, null);
+			m = (state.text != null) ? state.text.margin : null;
+			
+			if (m == null)
+			{
+				m = mxUtils.getAlignmentAsPoint(mxUtils.getValue(state.style, mxConstants.STYLE_ALIGN, mxConstants.ALIGN_CENTER),
+						mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_ALIGN, mxConstants.ALIGN_MIDDLE));
+			}
+			
+	 		if (isEdge)
+			{
+				this.bounds = new mxRectangle(state.absoluteOffset.x, state.absoluteOffset.y, 0, 0);
+				
+				if (lw != null)
+			 	{
+					var tmp = (parseFloat(lw) + 2) * scale;
+					this.bounds.width = tmp;
+					this.bounds.x += m.x * tmp;
+			 	}
+			}
+			else
+			{
+				var bds = mxRectangle.fromRectangle(state);
+				var hpos = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);
+				var vpos = mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);
+
+				bds = (state.shape != null && hpos == mxConstants.ALIGN_CENTER && vpos == mxConstants.ALIGN_MIDDLE) ? state.shape.getLabelBounds(bds) : bds;
+			 	
+			 	if (lw != null)
+			 	{
+			 		bds.width = parseFloat(lw) * scale;
+			 	}
+			 	
+			 	if (!state.view.graph.cellRenderer.legacySpacing || state.style[mxConstants.STYLE_OVERFLOW] != 'width')
+			 	{
+					var spacing = parseInt(state.style[mxConstants.STYLE_SPACING] || 2) * scale;
+					var spacingTop = (parseInt(state.style[mxConstants.STYLE_SPACING_TOP] || 0) + mxText.prototype.baseSpacingTop) * scale + spacing;
+					var spacingRight = (parseInt(state.style[mxConstants.STYLE_SPACING_RIGHT] || 0) + mxText.prototype.baseSpacingRight) * scale + spacing;
+					var spacingBottom = (parseInt(state.style[mxConstants.STYLE_SPACING_BOTTOM] || 0) + mxText.prototype.baseSpacingBottom) * scale + spacing;
+					var spacingLeft = (parseInt(state.style[mxConstants.STYLE_SPACING_LEFT] || 0) + mxText.prototype.baseSpacingLeft) * scale + spacing;
+					
+					var hpos = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);
+					var vpos = mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);
+
+					bds = new mxRectangle(bds.x + spacingLeft, bds.y + spacingTop,
+						bds.width - ((hpos == mxConstants.ALIGN_CENTER && lw == null) ? (spacingLeft + spacingRight) : 0),
+						bds.height - ((vpos == mxConstants.ALIGN_MIDDLE) ? (spacingTop + spacingBottom) : 0));
+			 	}
+
+				this.bounds = new mxRectangle(bds.x + state.absoluteOffset.x, bds.y + state.absoluteOffset.y, bds.width, bds.height);
+			}
+
+			// Needed for word wrap inside text blocks with oversize lines to match the final result where
+	 		// the width of the longest line is used as the reference for text alignment in the cell
+	 		// TODO: Fix word wrapping preview for edge labels in helloworld.html
+			if (this.graph.isWrapping(state.cell) && (this.bounds.width >= 2 || this.bounds.height >= 2) &&
+				this.textarea.innerHTML != this.getEmptyLabelText())
+			{
+				this.textarea.style.wordWrap = mxConstants.WORD_WRAP;
+				this.textarea.style.whiteSpace = 'normal';
+				
+		 		// Forces automatic reflow if text is removed from an oversize label and normal word wrap
+				var tmp = Math.round(this.bounds.width / ((document.documentMode == 8) ? scale : scale)) + this.wordWrapPadding;
+				this.textarea.style.width = tmp + 'px';
+				
+				if (this.textarea.scrollWidth > tmp)
+				{
+					this.textarea.style.width = this.textarea.scrollWidth + 'px';
+				}
+			}
+			else
+			{
+				// KNOWN: Trailing cursor in IE9 quirks mode is not visible
+				this.textarea.style.whiteSpace = 'nowrap';
+				this.textarea.style.width = '';
+			}
+			
+			// LATER: Keep in visible area, add fine tuning for pixel precision
+			// Workaround for wrong measuring in IE8 standards
+			if (document.documentMode == 8)
+			{
+				this.textarea.style.zoom = '1';
+				this.textarea.style.height = 'auto';
+			}
+			
+			var ow = this.textarea.scrollWidth;
+			var oh = this.textarea.scrollHeight;
+			
+			// TODO: Update CSS width and height if smaller than minResize or remove minResize
+			//if (this.minResize != null)
+			//{
+			//	ow = Math.max(ow, this.minResize.width);
+			//	oh = Math.max(oh, this.minResize.height);
+			//}
+			
+			// LATER: Keep in visible area, add fine tuning for pixel precision
+			if (document.documentMode == 8)
+			{
+				// LATER: Scaled wrapping and position is wrong in IE8
+				this.textarea.style.left = Math.max(0, Math.ceil((this.bounds.x - m.x * (this.bounds.width - (ow + 1) * scale) + ow * (scale - 1) * 0 + (m.x + 0.5) * 2) / scale)) + 'px';
+				this.textarea.style.top = Math.max(0, Math.ceil((this.bounds.y - m.y * (this.bounds.height - (oh + 0.5) * scale) + oh * (scale - 1) * 0 + Math.abs(m.y + 0.5) * 1) / scale)) + 'px';
+				// Workaround for wrong event handling width and height
+				this.textarea.style.width = Math.round(ow * scale) + 'px';
+				this.textarea.style.height = Math.round(oh * scale) + 'px';
+			}
+			else if (mxClient.IS_QUIRKS)
+			{			
+				this.textarea.style.left = Math.max(0, Math.ceil(this.bounds.x - m.x * (this.bounds.width - (ow + 1) * scale) + ow * (scale - 1) * 0 + (m.x + 0.5) * 2)) + 'px';
+				this.textarea.style.top = Math.max(0, Math.ceil(this.bounds.y - m.y * (this.bounds.height - (oh + 0.5) * scale) + oh * (scale - 1) * 0 + Math.abs(m.y + 0.5) * 1)) + 'px';
+			}
+			else
+			{
+				this.textarea.style.left = Math.max(0, Math.round(this.bounds.x - m.x * (this.bounds.width - 2)) + 1) + 'px';
+				this.textarea.style.top = Math.max(0, Math.round(this.bounds.y - m.y * (this.bounds.height - 4) + ((m.y == -1) ? 3 : 0)) + 1) + 'px';
+			}
+	 	}
+
+		if (mxClient.IS_VML)
+		{
+			this.textarea.style.zoom = scale;
+		}
+		else
+		{
+			mxUtils.setPrefixedStyle(this.textarea.style, 'transformOrigin', '0px 0px');
+			mxUtils.setPrefixedStyle(this.textarea.style, 'transform',
+				'scale(' + scale + ',' + scale + ')' + ((m == null) ? '' :
+				' translate(' + (m.x * 100) + '%,' + (m.y * 100) + '%)'));
+		}
+	}
+};
+
+/**
+ * Function: focusLost
+ *
+ * Called if the textarea has lost focus.
+ */
+mxCellEditor.prototype.focusLost = function()
+{
+	this.stopEditing(!this.graph.isInvokesStopCellEditing());
+};
+
+/**
+ * Function: getBackgroundColor
+ * 
+ * Returns the background color for the in-place editor. This implementation
+ * always returns null.
+ */
+mxCellEditor.prototype.getBackgroundColor = function(state)
+{
+	return null;
+};
+
+/**
+ * Function: startEditing
+ *
+ * Starts the editor for the given cell.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to start editing.
+ * trigger - Optional mouse event that triggered the editor.
+ */
+mxCellEditor.prototype.startEditing = function(cell, trigger)
+{
+	this.stopEditing(true);
+	
+	// Creates new textarea instance
+	if (this.textarea == null)
+	{
+		this.init();
+	}
+	
+	if (this.graph.tooltipHandler != null)
+	{
+		this.graph.tooltipHandler.hideTooltip();
+	}
+	
+	var state = this.graph.getView().getState(cell);
+	
+	if (state != null)
+	{
+		// Configures the style of the in-place editor
+		var scale = this.graph.getView().scale;
+		var size = mxUtils.getValue(state.style, mxConstants.STYLE_FONTSIZE, mxConstants.DEFAULT_FONTSIZE);
+		var family = mxUtils.getValue(state.style, mxConstants.STYLE_FONTFAMILY, mxConstants.DEFAULT_FONTFAMILY);
+		var color = mxUtils.getValue(state.style, mxConstants.STYLE_FONTCOLOR, 'black');
+		var align = mxUtils.getValue(state.style, mxConstants.STYLE_ALIGN, mxConstants.ALIGN_LEFT);
+		var bold = (mxUtils.getValue(state.style, mxConstants.STYLE_FONTSTYLE, 0) &
+				mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD;
+		var italic = (mxUtils.getValue(state.style, mxConstants.STYLE_FONTSTYLE, 0) &
+				mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC;
+		var uline = (mxUtils.getValue(state.style, mxConstants.STYLE_FONTSTYLE, 0) &
+				mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE;
+		
+		this.textarea.style.lineHeight = (mxConstants.ABSOLUTE_LINE_HEIGHT) ? Math.round(size * mxConstants.LINE_HEIGHT) + 'px' : mxConstants.LINE_HEIGHT;
+		this.textarea.style.backgroundColor = this.getBackgroundColor(state);
+		this.textarea.style.textDecoration = (uline) ? 'underline' : '';
+		this.textarea.style.fontWeight = (bold) ? 'bold' : 'normal';
+		this.textarea.style.fontStyle = (italic) ? 'italic' : '';
+		this.textarea.style.fontSize = Math.round(size) + 'px';
+		this.textarea.style.zIndex = this.zIndex;
+		this.textarea.style.fontFamily = family;
+		this.textarea.style.textAlign = align;
+		this.textarea.style.outline = 'none';
+		this.textarea.style.color = color;
+		
+		var dir = this.textDirection = mxUtils.getValue(state.style, mxConstants.STYLE_TEXT_DIRECTION, mxConstants.DEFAULT_TEXT_DIRECTION);
+		
+		if (dir == mxConstants.TEXT_DIRECTION_AUTO)
+		{
+			if (state != null && state.text != null && state.text.dialect != mxConstants.DIALECT_STRICTHTML &&
+				!mxUtils.isNode(state.text.value))
+			{
+				dir = state.text.getAutoDirection();
+			}
+		}
+		
+		if (dir == mxConstants.TEXT_DIRECTION_LTR || dir == mxConstants.TEXT_DIRECTION_RTL)
+		{
+			this.textarea.setAttribute('dir', dir);
+		}
+		else
+		{
+			this.textarea.removeAttribute('dir');
+		}
+
+		// Sets the initial editing value
+		this.textarea.innerHTML = this.getInitialValue(state, trigger) || '';
+		this.initialValue = this.textarea.innerHTML;
+
+		// Uses an optional text value for empty labels which is cleared
+		// when the first keystroke appears. This makes it easier to see
+		// that a label is being edited even if the label is empty.
+		if (this.textarea.innerHTML.length == 0 || this.textarea.innerHTML == '<br>')
+		{
+			this.textarea.innerHTML = this.getEmptyLabelText();
+			this.clearOnChange = true;
+		}
+		else
+		{
+			this.clearOnChange = this.textarea.innerHTML == this.getEmptyLabelText();
+		}
+
+		this.graph.container.appendChild(this.textarea);
+		
+		// Update this after firing all potential events that could update the cleanOnChange flag
+		this.editingCell = cell;
+		this.trigger = trigger;
+		this.textNode = null;
+
+		if (state.text != null && this.isHideLabel(state))
+		{
+			this.textNode = state.text.node;
+			this.textNode.style.visibility = 'hidden';
+		}
+
+		// Workaround for initial offsetHeight not ready for heading in markup
+		if (this.autoSize && (this.graph.model.isEdge(state.cell) || state.style[mxConstants.STYLE_OVERFLOW] != 'fill'))
+		{
+			window.setTimeout(mxUtils.bind(this, function()
+			{
+				this.resize();
+			}), 0);
+		}
+		
+		this.resize();
+		
+		// Workaround for NS_ERROR_FAILURE in FF
+		try
+		{
+			// Prefers blinking cursor over no selected text if empty
+			this.textarea.focus();
+			
+			if (this.isSelectText() && this.textarea.innerHTML.length > 0 &&
+				(this.textarea.innerHTML != this.getEmptyLabelText() || !this.clearOnChange))
+			{
+				document.execCommand('selectAll', false, null);
+			}
+		}
+		catch (e)
+		{
+			// ignore
+		}
+	}
+};
+
+/**
+ * Function: isSelectText
+ * 
+ * Returns <selectText>.
+ */
+mxCellEditor.prototype.isSelectText = function()
+{
+	return this.selectText;
+};
+
+/**
+ * Function: stopEditing
+ *
+ * Stops the editor and applies the value if cancel is false.
+ */
+mxCellEditor.prototype.stopEditing = function(cancel)
+{
+	cancel = cancel || false;
+	
+	if (this.editingCell != null)
+	{
+		if (this.textNode != null)
+		{
+			this.textNode.style.visibility = 'visible';
+			this.textNode = null;
+		}
+
+		var state = (!cancel) ? this.graph.view.getState(this.editingCell) : null;
+
+		var initial = this.initialValue;
+		this.initialValue = null;
+		this.editingCell = null;
+		this.trigger = null;
+		this.bounds = null;
+		this.textarea.blur();
+		
+		if (this.textarea.parentNode != null)
+		{
+			this.textarea.parentNode.removeChild(this.textarea);
+		}
+		
+		if (this.clearOnChange && this.textarea.innerHTML == this.getEmptyLabelText())
+		{
+			this.textarea.innerHTML = '';
+			this.clearOnChange = false;
+		}
+		
+		if (state != null && this.textarea.innerHTML != initial)
+		{
+			this.prepareTextarea();
+			var value = this.getCurrentValue(state);
+			
+			if (value != null)
+			{
+				this.applyValue(state, value);
+			}
+		}
+		
+		// Forces new instance on next edit for undo history reset
+		mxEvent.release(this.textarea);
+		this.textarea = null;
+	}
+};
+
+/**
+ * Function: prepareTextarea
+ * 
+ * Prepares the textarea for getting its value in <stopEditing>.
+ * This implementation removes the extra trailing linefeed in Firefox.
+ */
+mxCellEditor.prototype.prepareTextarea = function()
+{
+	if (mxClient.IS_FF && this.textarea.lastChild != null &&
+		this.textarea.lastChild.nodeName == 'BR')
+	{
+		this.textarea.removeChild(this.textarea.lastChild);
+	}
+};
+
+/**
+ * Function: isHideLabel
+ * 
+ * Returns true if the label should be hidden while the cell is being
+ * edited.
+ */
+mxCellEditor.prototype.isHideLabel = function(state)
+{
+	return true;
+};
+
+/**
+ * Function: getMinimumSize
+ * 
+ * Returns the minimum width and height for editing the given state.
+ */
+mxCellEditor.prototype.getMinimumSize = function(state)
+{
+	var scale = this.graph.getView().scale;
+	
+	return new mxRectangle(0, 0, (state.text == null) ? 30 : state.text.size * scale + 20,
+			(this.textarea.style.textAlign == 'left') ? 120 : 40);
+};
+
+/**
+ * Function: getEditorBounds
+ * 
+ * Returns the <mxRectangle> that defines the bounds of the editor.
+ */
+mxCellEditor.prototype.getEditorBounds = function(state)
+{
+	var isEdge = this.graph.getModel().isEdge(state.cell);
+	var scale = this.graph.getView().scale;
+	var minSize = this.getMinimumSize(state);
+	var minWidth = minSize.width;
+ 	var minHeight = minSize.height;
+ 	var result = null;
+ 	
+ 	if (!isEdge && state.view.graph.cellRenderer.legacySpacing && state.style[mxConstants.STYLE_OVERFLOW] == 'fill')
+ 	{
+ 		result = state.shape.getLabelBounds(mxRectangle.fromRectangle(state));
+ 	}
+ 	else
+ 	{
+		var spacing = parseInt(state.style[mxConstants.STYLE_SPACING] || 0) * scale;
+		var spacingTop = (parseInt(state.style[mxConstants.STYLE_SPACING_TOP] || 0) + mxText.prototype.baseSpacingTop) * scale + spacing;
+		var spacingRight = (parseInt(state.style[mxConstants.STYLE_SPACING_RIGHT] || 0) + mxText.prototype.baseSpacingRight) * scale + spacing;
+		var spacingBottom = (parseInt(state.style[mxConstants.STYLE_SPACING_BOTTOM] || 0) + mxText.prototype.baseSpacingBottom) * scale + spacing;
+		var spacingLeft = (parseInt(state.style[mxConstants.STYLE_SPACING_LEFT] || 0) + mxText.prototype.baseSpacingLeft) * scale + spacing;
+	
+	 	result = new mxRectangle(state.x, state.y,
+	 		 Math.max(minWidth, state.width - spacingLeft - spacingRight),
+	 		 Math.max(minHeight, state.height - spacingTop - spacingBottom));
+		var hpos = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);
+		var vpos = mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);
+		
+		result = (state.shape != null && hpos == mxConstants.ALIGN_CENTER && vpos == mxConstants.ALIGN_MIDDLE) ? state.shape.getLabelBounds(result) : result;
+	
+		if (isEdge)
+		{
+			result.x = state.absoluteOffset.x;
+			result.y = state.absoluteOffset.y;
+	
+			if (state.text != null && state.text.boundingBox != null)
+			{
+				// Workaround for label containing just spaces in which case
+				// the bounding box location contains negative numbers 
+				if (state.text.boundingBox.x > 0)
+				{
+					result.x = state.text.boundingBox.x;
+				}
+				
+				if (state.text.boundingBox.y > 0)
+				{
+					result.y = state.text.boundingBox.y;
+				}
+			}
+		}
+		else if (state.text != null && state.text.boundingBox != null)
+		{
+			result.x = Math.min(result.x, state.text.boundingBox.x);
+			result.y = Math.min(result.y, state.text.boundingBox.y);
+		}
+	
+		result.x += spacingLeft;
+		result.y += spacingTop;
+	
+		if (state.text != null && state.text.boundingBox != null)
+		{
+			if (!isEdge)
+			{
+				result.width = Math.max(result.width, state.text.boundingBox.width);
+				result.height = Math.max(result.height, state.text.boundingBox.height);
+			}
+			else
+			{
+				result.width = Math.max(minWidth, state.text.boundingBox.width);
+				result.height = Math.max(minHeight, state.text.boundingBox.height);
+			}
+		}
+		
+		// Applies the horizontal and vertical label positions
+		if (this.graph.getModel().isVertex(state.cell))
+		{
+			var horizontal = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);
+	
+			if (horizontal == mxConstants.ALIGN_LEFT)
+			{
+				result.x -= state.width;
+			}
+			else if (horizontal == mxConstants.ALIGN_RIGHT)
+			{
+				result.x += state.width;
+			}
+	
+			var vertical = mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);
+	
+			if (vertical == mxConstants.ALIGN_TOP)
+			{
+				result.y -= state.height;
+			}
+			else if (vertical == mxConstants.ALIGN_BOTTOM)
+			{
+				result.y += state.height;
+			}
+		}
+ 	}
+ 	
+ 	return new mxRectangle(Math.round(result.x), Math.round(result.y), Math.round(result.width), Math.round(result.height));
+};
+
+/**
+ * Function: getEmptyLabelText
+ *
+ * Returns the initial label value to be used of the label of the given
+ * cell is empty. This label is displayed and cleared on the first keystroke.
+ * This implementation returns <emptyLabelText>.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> for which a text for an empty editing box should be
+ * returned.
+ */
+mxCellEditor.prototype.getEmptyLabelText = function (cell)
+{
+	return this.emptyLabelText;
+};
+
+/**
+ * Function: getEditingCell
+ *
+ * Returns the cell that is currently being edited or null if no cell is
+ * being edited.
+ */
+mxCellEditor.prototype.getEditingCell = function ()
+{
+	return this.editingCell;
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the editor and removes all associated resources.
+ */
+mxCellEditor.prototype.destroy = function ()
+{
+	if (this.textarea != null)
+	{
+		mxEvent.release(this.textarea);
+		
+		if (this.textarea.parentNode != null)
+		{
+			this.textarea.parentNode.removeChild(this.textarea);
+		}
+		
+		this.textarea = null;
+
+	}
+			
+	if (this.changeHandler != null)
+	{
+		this.graph.getModel().removeListener(this.changeHandler);
+		this.changeHandler = null;
+	}
+
+	if (this.zoomHandler)
+	{
+		this.graph.view.removeListener(this.zoomHandler);
+		this.zoomHandler = null;
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/view/mxCellOverlay.js b/airavata-kubernetes/workflow-composer/src/js/view/mxCellOverlay.js
new file mode 100644
index 0000000..42debbb
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/view/mxCellOverlay.js
@@ -0,0 +1,233 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxCellOverlay
+ *
+ * Extends <mxEventSource> to implement a graph overlay, represented by an icon
+ * and a tooltip. Overlays can handle and fire <click> events and are added to
+ * the graph using <mxGraph.addCellOverlay>, and removed using
+ * <mxGraph.removeCellOverlay>, or <mxGraph.removeCellOverlays> to remove all overlays.
+ * The <mxGraph.getCellOverlays> function returns the array of overlays for a given
+ * cell in a graph. If multiple overlays exist for the same cell, then
+ * <getBounds> should be overridden in at least one of the overlays.
+ * 
+ * Overlays appear on top of all cells in a special layer. If this is not
+ * desirable, then the image must be rendered as part of the shape or label of
+ * the cell instead.
+ *
+ * Example:
+ * 
+ * The following adds a new overlays for a given vertex and selects the cell
+ * if the overlay is clicked.
+ *
+ * (code)
+ * var overlay = new mxCellOverlay(img, html);
+ * graph.addCellOverlay(vertex, overlay);
+ * overlay.addListener(mxEvent.CLICK, function(sender, evt)
+ * {
+ *   var cell = evt.getProperty('cell');
+ *   graph.setSelectionCell(cell);
+ * });
+ * (end)
+ * 
+ * For cell overlays to be printed use <mxPrintPreview.printOverlays>.
+ *
+ * Event: mxEvent.CLICK
+ *
+ * Fires when the user clicks on the overlay. The <code>event</code> property
+ * contains the corresponding mouse event and the <code>cell</code> property
+ * contains the cell. For touch devices this is fired if the element receives
+ * a touchend event.
+ * 
+ * Constructor: mxCellOverlay
+ *
+ * Constructs a new overlay using the given image and tooltip.
+ * 
+ * Parameters:
+ * 
+ * image - <mxImage> that represents the icon to be displayed.
+ * tooltip - Optional string that specifies the tooltip.
+ * align - Optional horizontal alignment for the overlay. Possible
+ * values are <ALIGN_LEFT>, <ALIGN_CENTER> and <ALIGN_RIGHT>
+ * (default).
+ * verticalAlign - Vertical alignment for the overlay. Possible
+ * values are <ALIGN_TOP>, <ALIGN_MIDDLE> and <ALIGN_BOTTOM>
+ * (default).
+ */
+function mxCellOverlay(image, tooltip, align, verticalAlign, offset, cursor)
+{
+	this.image = image;
+	this.tooltip = tooltip;
+	this.align = (align != null) ? align : this.align;
+	this.verticalAlign = (verticalAlign != null) ? verticalAlign : this.verticalAlign;
+	this.offset = (offset != null) ? offset : new mxPoint();
+	this.cursor = (cursor != null) ? cursor : 'help';
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxCellOverlay.prototype = new mxEventSource();
+mxCellOverlay.prototype.constructor = mxCellOverlay;
+
+/**
+ * Variable: image
+ *
+ * Holds the <mxImage> to be used as the icon.
+ */
+mxCellOverlay.prototype.image = null;
+
+/**
+ * Variable: tooltip
+ * 
+ * Holds the optional string to be used as the tooltip.
+ */
+mxCellOverlay.prototype.tooltip = null;
+
+/**
+ * Variable: align
+ * 
+ * Holds the horizontal alignment for the overlay. Default is
+ * <mxConstants.ALIGN_RIGHT>. For edges, the overlay always appears in the
+ * center of the edge.
+ */
+mxCellOverlay.prototype.align = mxConstants.ALIGN_RIGHT;
+
+/**
+ * Variable: verticalAlign
+ * 
+ * Holds the vertical alignment for the overlay. Default is
+ * <mxConstants.ALIGN_BOTTOM>. For edges, the overlay always appears in the
+ * center of the edge.
+ */
+mxCellOverlay.prototype.verticalAlign = mxConstants.ALIGN_BOTTOM;
+
+/**
+ * Variable: offset
+ * 
+ * Holds the offset as an <mxPoint>. The offset will be scaled according to the
+ * current scale.
+ */
+mxCellOverlay.prototype.offset = null;
+
+/**
+ * Variable: cursor
+ * 
+ * Holds the cursor for the overlay. Default is 'help'.
+ */
+mxCellOverlay.prototype.cursor = null;
+
+/**
+ * Variable: defaultOverlap
+ * 
+ * Defines the overlapping for the overlay, that is, the proportional distance
+ * from the origin to the point defined by the alignment. Default is 0.5.
+ */
+mxCellOverlay.prototype.defaultOverlap = 0.5;
+
+/**
+ * Function: getBounds
+ * 
+ * Returns the bounds of the overlay for the given <mxCellState> as an
+ * <mxRectangle>. This should be overridden when using multiple overlays
+ * per cell so that the overlays do not overlap.
+ * 
+ * The following example will place the overlay along an edge (where
+ * x=[-1..1] from the start to the end of the edge and y is the
+ * orthogonal offset in px).
+ * 
+ * (code)
+ * overlay.getBounds = function(state)
+ * {
+ *   var bounds = mxCellOverlay.prototype.getBounds.apply(this, arguments);
+ *   
+ *   if (state.view.graph.getModel().isEdge(state.cell))
+ *   {
+ *     var pt = state.view.getPoint(state, {x: 0, y: 0, relative: true});
+ *     
+ *     bounds.x = pt.x - bounds.width / 2;
+ *     bounds.y = pt.y - bounds.height / 2;
+ *   }
+ *   
+ *   return bounds;
+ * };
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> that represents the current state of the
+ * associated cell.
+ */
+mxCellOverlay.prototype.getBounds = function(state)
+{
+	var isEdge = state.view.graph.getModel().isEdge(state.cell);
+	var s = state.view.scale;
+	var pt = null;
+
+	var w = this.image.width;
+	var h = this.image.height;
+	
+	if (isEdge)
+	{
+		var pts = state.absolutePoints;
+		
+		if (pts.length % 2 == 1)
+		{
+			pt = pts[Math.floor(pts.length / 2)];
+		}
+		else
+		{
+			var idx = pts.length / 2;
+			var p0 = pts[idx-1];
+			var p1 = pts[idx];
+			pt = new mxPoint(p0.x + (p1.x - p0.x) / 2,
+				p0.y + (p1.y - p0.y) / 2);
+		}
+	}
+	else
+	{
+		pt = new mxPoint();
+		
+		if (this.align == mxConstants.ALIGN_LEFT)
+		{
+			pt.x = state.x;
+		}
+		else if (this.align == mxConstants.ALIGN_CENTER)
+		{
+			pt.x = state.x + state.width / 2;
+		}
+		else
+		{
+			pt.x = state.x + state.width;
+		}
+		
+		if (this.verticalAlign == mxConstants.ALIGN_TOP)
+		{
+			pt.y = state.y;
+		}
+		else if (this.verticalAlign == mxConstants.ALIGN_MIDDLE)
+		{
+			pt.y = state.y + state.height / 2;
+		}
+		else
+		{
+			pt.y = state.y + state.height;
+		}
+	}
+
+	return new mxRectangle(Math.round(pt.x - (w * this.defaultOverlap - this.offset.x) * s),
+		Math.round(pt.y - (h * this.defaultOverlap - this.offset.y) * s), w * s, h * s);
+};
+
+/**
+ * Function: toString
+ * 
+ * Returns the textual representation of the overlay to be used as the
+ * tooltip. This implementation returns <tooltip>.
+ */
+mxCellOverlay.prototype.toString = function()
+{
+	return this.tooltip;
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/view/mxCellRenderer.js b/airavata-kubernetes/workflow-composer/src/js/view/mxCellRenderer.js
new file mode 100644
index 0000000..d36b303
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/view/mxCellRenderer.js
@@ -0,0 +1,1553 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxCellRenderer
+ * 
+ * Renders cells into a document object model. The <defaultShapes> is a global
+ * map of shapename, constructor pairs that is used in all instances. You can
+ * get a list of all available shape names using the following code.
+ * 
+ * In general the cell renderer is in charge of creating, redrawing and
+ * destroying the shape and label associated with a cell state, as well as
+ * some other graphical objects, namely controls and overlays. The shape
+ * hieararchy in the display (ie. the hierarchy in which the DOM nodes
+ * appear in the document) does not reflect the cell hierarchy. The shapes
+ * are a (flat) sequence of shapes and labels inside the draw pane of the
+ * graph view, with some exceptions, namely the HTML labels being placed
+ * directly inside the graph container for certain browsers.
+ * 
+ * (code)
+ * mxLog.show();
+ * for (var i in mxCellRenderer.prototype.defaultShapes)
+ * {
+ *   mxLog.debug(i);
+ * }
+ * (end)
+ *
+ * Constructor: mxCellRenderer
+ * 
+ * Constructs a new cell renderer with the following built-in shapes:
+ * arrow, rectangle, ellipse, rhombus, image, line, label, cylinder,
+ * swimlane, connector, actor and cloud.
+ */
+function mxCellRenderer() { };
+
+/**
+ * Variable: defaultEdgeShape
+ * 
+ * Defines the default shape for edges. Default is <mxConnector>.
+ */
+mxCellRenderer.prototype.defaultEdgeShape = mxConnector;
+
+/**
+ * Variable: defaultVertexShape
+ * 
+ * Defines the default shape for vertices. Default is <mxRectangleShape>.
+ */
+mxCellRenderer.prototype.defaultVertexShape = mxRectangleShape;
+
+/**
+ * Variable: defaultTextShape
+ * 
+ * Defines the default shape for labels. Default is <mxText>.
+ */
+mxCellRenderer.prototype.defaultTextShape = mxText;
+
+/**
+ * Variable: legacyControlPosition
+ * 
+ * Specifies if the folding icon should ignore the horizontal
+ * orientation of a swimlane. Default is true.
+ */
+mxCellRenderer.prototype.legacyControlPosition = true;
+
+/**
+ * Variable: legacySpacing
+ * 
+ * Specifies if spacing and label position should be ignored if overflow is
+ * fill or width. Default is true for backwards compatiblity.
+ */
+mxCellRenderer.prototype.legacySpacing = true;
+
+/**
+ * Variable: defaultShapes
+ * 
+ * Static array that contains the globally registered shapes which are
+ * known to all instances of this class. For adding new shapes you should
+ * use the static <mxCellRenderer.registerShape> function.
+ */
+mxCellRenderer.prototype.defaultShapes = new Object();
+
+/**
+ * Variable: antiAlias
+ * 
+ * Anti-aliasing option for new shapes. Default is true.
+ */
+mxCellRenderer.prototype.antiAlias = true;
+
+/**
+ * Variable: forceControlClickHandler
+ * 
+ * Specifies if the enabled state of the graph should be ignored in the control
+ * click handler (to allow folding in disabled graphs). Default is false.
+ */
+mxCellRenderer.prototype.forceControlClickHandler = false;
+
+/**
+ * Function: registerShape
+ * 
+ * Registers the given constructor under the specified key in this instance
+ * of the renderer.
+ * 
+ * Example:
+ * 
+ * (code)
+ * mxCellRenderer.registerShape(mxConstants.SHAPE_RECTANGLE, mxRectangleShape);
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * key - String representing the shape name.
+ * shape - Constructor of the <mxShape> subclass.
+ */
+mxCellRenderer.registerShape = function(key, shape)
+{
+	mxCellRenderer.prototype.defaultShapes[key] = shape;
+};
+
+// Adds default shapes into the default shapes array
+mxCellRenderer.registerShape(mxConstants.SHAPE_RECTANGLE, mxRectangleShape);
+mxCellRenderer.registerShape(mxConstants.SHAPE_ELLIPSE, mxEllipse);
+mxCellRenderer.registerShape(mxConstants.SHAPE_RHOMBUS, mxRhombus);
+mxCellRenderer.registerShape(mxConstants.SHAPE_CYLINDER, mxCylinder);
+mxCellRenderer.registerShape(mxConstants.SHAPE_CONNECTOR, mxConnector);
+mxCellRenderer.registerShape(mxConstants.SHAPE_ACTOR, mxActor);
+mxCellRenderer.registerShape(mxConstants.SHAPE_TRIANGLE, mxTriangle);
+mxCellRenderer.registerShape(mxConstants.SHAPE_HEXAGON, mxHexagon);
+mxCellRenderer.registerShape(mxConstants.SHAPE_CLOUD, mxCloud);
+mxCellRenderer.registerShape(mxConstants.SHAPE_LINE, mxLine);
+mxCellRenderer.registerShape(mxConstants.SHAPE_ARROW, mxArrow);
+mxCellRenderer.registerShape(mxConstants.SHAPE_ARROW_CONNECTOR, mxArrowConnector);
+mxCellRenderer.registerShape(mxConstants.SHAPE_DOUBLE_ELLIPSE, mxDoubleEllipse);
+mxCellRenderer.registerShape(mxConstants.SHAPE_SWIMLANE, mxSwimlane);
+mxCellRenderer.registerShape(mxConstants.SHAPE_IMAGE, mxImageShape);
+mxCellRenderer.registerShape(mxConstants.SHAPE_LABEL, mxLabel);
+
+/**
+ * Function: initializeShape
+ * 
+ * Initializes the shape in the given state by calling its init method with
+ * the correct container after configuring it using <configureShape>.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> for which the shape should be initialized.
+ */
+mxCellRenderer.prototype.initializeShape = function(state)
+{
+	state.shape.dialect = state.view.graph.dialect;
+	this.configureShape(state);
+	state.shape.init(state.view.getDrawPane());
+};
+
+/**
+ * Function: createShape
+ * 
+ * Creates and returns the shape for the given cell state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> for which the shape should be created.
+ */
+mxCellRenderer.prototype.createShape = function(state)
+{
+	var shape = null;
+	
+	if (state.style != null)
+	{
+		// Checks if there is a stencil for the name and creates
+		// a shape instance for the stencil if one exists
+		var stencil = mxStencilRegistry.getStencil(state.style[mxConstants.STYLE_SHAPE]);
+		
+		if (stencil != null)
+		{
+			shape = new mxShape(stencil);
+		}
+		else
+		{
+			var ctor = this.getShapeConstructor(state);
+			shape = new ctor();
+		}
+	}
+	
+	return shape;
+};
+
+/**
+ * Function: createIndicatorShape
+ * 
+ * Creates the indicator shape for the given cell state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> for which the indicator shape should be created.
+ */
+mxCellRenderer.prototype.createIndicatorShape = function(state)
+{
+	state.shape.indicatorShape = this.getShape(state.view.graph.getIndicatorShape(state));
+};
+
+/**
+ * Function: getShape
+ * 
+ * Returns the shape for the given name from <defaultShapes>.
+ */
+mxCellRenderer.prototype.getShape = function(name)
+{
+	return (name != null) ? mxCellRenderer.prototype.defaultShapes[name] : null;
+};
+
+/**
+ * Function: getShapeConstructor
+ * 
+ * Returns the constructor to be used for creating the shape.
+ */
+mxCellRenderer.prototype.getShapeConstructor = function(state)
+{
+	var ctor = this.getShape(state.style[mxConstants.STYLE_SHAPE]);
+	
+	if (ctor == null)
+	{
+		ctor = (state.view.graph.getModel().isEdge(state.cell)) ?
+			this.defaultEdgeShape : this.defaultVertexShape;
+	}
+	
+	return ctor;
+};
+
+/**
+ * Function: configureShape
+ * 
+ * Configures the shape for the given cell state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> for which the shape should be configured.
+ */
+mxCellRenderer.prototype.configureShape = function(state)
+{
+	state.shape.apply(state);
+	state.shape.image = state.view.graph.getImage(state);
+	state.shape.indicatorColor = state.view.graph.getIndicatorColor(state);
+	state.shape.indicatorStrokeColor = state.style[mxConstants.STYLE_INDICATOR_STROKECOLOR];
+	state.shape.indicatorGradientColor = state.view.graph.getIndicatorGradientColor(state);
+	state.shape.indicatorDirection = state.style[mxConstants.STYLE_INDICATOR_DIRECTION];
+	state.shape.indicatorImage = state.view.graph.getIndicatorImage(state);
+
+	this.postConfigureShape(state);
+};
+
+/**
+ * Function: postConfigureShape
+ * 
+ * Replaces any reserved words used for attributes, eg. inherit,
+ * indicated or swimlane for colors in the shape for the given state.
+ * This implementation resolves these keywords on the fill, stroke
+ * and gradient color keys.
+ */
+mxCellRenderer.prototype.postConfigureShape = function(state)
+{
+	if (state.shape != null)
+	{
+		this.resolveColor(state, 'indicatorColor', mxConstants.STYLE_FILLCOLOR);
+		this.resolveColor(state, 'indicatorGradientColor', mxConstants.STYLE_GRADIENTCOLOR);
+		this.resolveColor(state, 'fill', mxConstants.STYLE_FILLCOLOR);
+		this.resolveColor(state, 'stroke', mxConstants.STYLE_STROKECOLOR);
+		this.resolveColor(state, 'gradient', mxConstants.STYLE_GRADIENTCOLOR);
+	}
+};
+
+/**
+ * Function: resolveColor
+ * 
+ * Resolves special keywords 'inherit', 'indicated' and 'swimlane' and sets
+ * the respective color on the shape.
+ */
+mxCellRenderer.prototype.resolveColor = function(state, field, key)
+{
+	var value = state.shape[field];
+	var graph = state.view.graph;
+	var referenced = null;
+	
+	if (value == 'inherit')
+	{
+		referenced = graph.model.getParent(state.cell);
+	}
+	else if (value == 'swimlane')
+	{
+		if (graph.model.getTerminal(state.cell, false) != null)
+		{
+			referenced = graph.model.getTerminal(state.cell, false);
+		}
+		else
+		{
+			referenced = state.cell;
+		}
+		
+		referenced = graph.getSwimlane(referenced);
+		key = graph.swimlaneIndicatorColorAttribute;
+	}
+	else if (value == 'indicated')
+	{
+		state.shape[field] = state.shape.indicatorColor;
+	}
+	
+	if (referenced != null)
+	{
+		var rstate = graph.getView().getState(referenced);
+		state.shape[field] = null;
+
+		if (rstate != null)
+		{
+			if (rstate.shape != null && field != 'indicatorColor')
+			{
+				state.shape[field] = rstate.shape[field];
+			}
+			else
+			{
+				state.shape[field] = rstate.style[key];
+			}
+		}
+	}
+};
+
+/**
+ * Function: getLabelValue
+ * 
+ * Returns the value to be used for the label.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> for which the label should be created.
+ */
+mxCellRenderer.prototype.getLabelValue = function(state)
+{
+	return state.view.graph.getLabel(state.cell);
+};
+
+/**
+ * Function: createLabel
+ * 
+ * Creates the label for the given cell state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> for which the label should be created.
+ */
+mxCellRenderer.prototype.createLabel = function(state, value)
+{
+	var graph = state.view.graph;
+	var isEdge = graph.getModel().isEdge(state.cell);
+	
+	if (state.style[mxConstants.STYLE_FONTSIZE] > 0 || state.style[mxConstants.STYLE_FONTSIZE] == null)
+	{
+		// Avoids using DOM node for empty labels
+		var isForceHtml = (graph.isHtmlLabel(state.cell) || (value != null && mxUtils.isNode(value)));
+
+		state.text = new this.defaultTextShape(value, new mxRectangle(),
+				(state.style[mxConstants.STYLE_ALIGN] || mxConstants.ALIGN_CENTER),
+				graph.getVerticalAlign(state),
+				state.style[mxConstants.STYLE_FONTCOLOR],
+				state.style[mxConstants.STYLE_FONTFAMILY],
+				state.style[mxConstants.STYLE_FONTSIZE],
+				state.style[mxConstants.STYLE_FONTSTYLE],
+				state.style[mxConstants.STYLE_SPACING],
+				state.style[mxConstants.STYLE_SPACING_TOP],
+				state.style[mxConstants.STYLE_SPACING_RIGHT],
+				state.style[mxConstants.STYLE_SPACING_BOTTOM],
+				state.style[mxConstants.STYLE_SPACING_LEFT],
+				state.style[mxConstants.STYLE_HORIZONTAL],
+				state.style[mxConstants.STYLE_LABEL_BACKGROUNDCOLOR],
+				state.style[mxConstants.STYLE_LABEL_BORDERCOLOR],
+				graph.isWrapping(state.cell) && graph.isHtmlLabel(state.cell),
+				graph.isLabelClipped(state.cell),
+				state.style[mxConstants.STYLE_OVERFLOW],
+				state.style[mxConstants.STYLE_LABEL_PADDING],
+				mxUtils.getValue(state.style, mxConstants.STYLE_TEXT_DIRECTION, mxConstants.DEFAULT_TEXT_DIRECTION));
+		state.text.opacity = mxUtils.getValue(state.style, mxConstants.STYLE_TEXT_OPACITY, 100);
+		state.text.dialect = (isForceHtml) ? mxConstants.DIALECT_STRICTHTML : state.view.graph.dialect;
+		state.text.style = state.style;
+		state.text.state = state;
+		this.initializeLabel(state, state.text);
+		
+		// Workaround for touch devices routing all events for a mouse gesture
+		// (down, move, up) via the initial DOM node. IE additionally redirects
+		// the event via the initial DOM node but the event source is the node
+		// under the mouse, so we need to check if this is the case and force
+		// getCellAt for the subsequent mouseMoves and the final mouseUp.
+		var forceGetCell = false;
+		
+		var getState = function(evt)
+		{
+			var result = state;
+
+			if (mxClient.IS_TOUCH || forceGetCell)
+			{
+				var x = mxEvent.getClientX(evt);
+				var y = mxEvent.getClientY(evt);
+				
+				// Dispatches the drop event to the graph which
+				// consumes and executes the source function
+				var pt = mxUtils.convertPoint(graph.container, x, y);
+				result = graph.view.getState(graph.getCellAt(pt.x, pt.y));
+			}
+			
+			return result;
+		};
+		
+		// TODO: Add handling for special touch device gestures
+		mxEvent.addGestureListeners(state.text.node,
+			mxUtils.bind(this, function(evt)
+			{
+				if (this.isLabelEvent(state, evt))
+				{
+					graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt, state));
+					forceGetCell = graph.dialect != mxConstants.DIALECT_SVG &&
+						mxEvent.getSource(evt).nodeName == 'IMG';
+				}
+			}),
+			mxUtils.bind(this, function(evt)
+			{
+				if (this.isLabelEvent(state, evt))
+				{
+					graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, getState(evt)));
+				}
+			}),
+			mxUtils.bind(this, function(evt)
+			{
+				if (this.isLabelEvent(state, evt))
+				{
+					graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt, getState(evt)));
+					forceGetCell = false;
+				}
+			})
+		);
+
+		// Uses double click timeout in mxGraph for quirks mode
+		if (graph.nativeDblClickEnabled)
+		{
+			mxEvent.addListener(state.text.node, 'dblclick',
+				mxUtils.bind(this, function(evt)
+				{
+					if (this.isLabelEvent(state, evt))
+					{
+						graph.dblClick(evt, state.cell);
+						mxEvent.consume(evt);
+					}
+				})
+			);
+		}
+	}
+};
+
+/**
+ * Function: initializeLabel
+ * 
+ * Initiailzes the label with a suitable container.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose label should be initialized.
+ */
+mxCellRenderer.prototype.initializeLabel = function(state, shape)
+{
+	if (mxClient.IS_SVG && mxClient.NO_FO && shape.dialect != mxConstants.DIALECT_SVG)
+	{
+		shape.init(state.view.graph.container);
+	}
+	else
+	{
+		shape.init(state.view.getDrawPane());
+	}
+};
+
+/**
+ * Function: createCellOverlays
+ * 
+ * Creates the actual shape for showing the overlay for the given cell state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> for which the overlay should be created.
+ */
+mxCellRenderer.prototype.createCellOverlays = function(state)
+{
+	var graph = state.view.graph;
+	var overlays = graph.getCellOverlays(state.cell);
+	var dict = null;
+	
+	if (overlays != null)
+	{
+		dict = new mxDictionary();
+		
+		for (var i = 0; i < overlays.length; i++)
+		{
+			var shape = (state.overlays != null) ? state.overlays.remove(overlays[i]) : null;
+			
+			if (shape == null)
+			{
+				var tmp = new mxImageShape(new mxRectangle(), overlays[i].image.src);
+				tmp.dialect = state.view.graph.dialect;
+				tmp.preserveImageAspect = false;
+				tmp.overlay = overlays[i];
+				this.initializeOverlay(state, tmp);
+				this.installCellOverlayListeners(state, overlays[i], tmp);
+	
+				if (overlays[i].cursor != null)
+				{
+					tmp.node.style.cursor = overlays[i].cursor;
+				}
+				
+				dict.put(overlays[i], tmp);
+			}
+			else
+			{
+				dict.put(overlays[i], shape);
+			}
+		}
+	}
+	
+	// Removes unused
+	if (state.overlays != null)
+	{
+		state.overlays.visit(function(id, shape)
+		{
+			shape.destroy();
+		});
+	}
+	
+	state.overlays = dict;
+};
+
+/**
+ * Function: initializeOverlay
+ * 
+ * Initializes the given overlay.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> for which the overlay should be created.
+ * overlay - <mxImageShape> that represents the overlay.
+ */
+mxCellRenderer.prototype.initializeOverlay = function(state, overlay)
+{
+	overlay.init(state.view.getOverlayPane());
+};
+
+/**
+ * Function: installOverlayListeners
+ * 
+ * Installs the listeners for the given <mxCellState>, <mxCellOverlay> and
+ * <mxShape> that represents the overlay.
+ */
+mxCellRenderer.prototype.installCellOverlayListeners = function(state, overlay, shape)
+{
+	var graph  = state.view.graph;
+	
+	mxEvent.addListener(shape.node, 'click', function (evt)
+	{
+		if (graph.isEditing())
+		{
+			graph.stopEditing(!graph.isInvokesStopCellEditing());
+		}
+		
+		overlay.fireEvent(new mxEventObject(mxEvent.CLICK,
+				'event', evt, 'cell', state.cell));
+	});
+	
+	mxEvent.addGestureListeners(shape.node,
+		function (evt)
+		{
+			mxEvent.consume(evt);
+		},
+		function (evt)
+		{
+			graph.fireMouseEvent(mxEvent.MOUSE_MOVE,
+				new mxMouseEvent(evt, state));
+		});
+	
+	if (mxClient.IS_TOUCH)
+	{
+		mxEvent.addListener(shape.node, 'touchend', function (evt)
+		{
+			overlay.fireEvent(new mxEventObject(mxEvent.CLICK,
+					'event', evt, 'cell', state.cell));
+		});
+	}
+};
+
+/**
+ * Function: createControl
+ * 
+ * Creates the control for the given cell state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> for which the control should be created.
+ */
+mxCellRenderer.prototype.createControl = function(state)
+{
+	var graph = state.view.graph;
+	var image = graph.getFoldingImage(state);
+	
+	if (graph.foldingEnabled && image != null)
+	{
+		if (state.control == null)
+		{
+			var b = new mxRectangle(0, 0, image.width, image.height);
+			state.control = new mxImageShape(b, image.src);
+			state.control.preserveImageAspect = false;
+			state.control.dialect = graph.dialect;
+
+			this.initControl(state, state.control, true, this.createControlClickHandler(state));
+		}
+	}
+	else if (state.control != null)
+	{
+		state.control.destroy();
+		state.control = null;
+	}
+};
+
+/**
+ * Function: createControlClickHandler
+ * 
+ * Hook for creating the click handler for the folding icon.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose control click handler should be returned.
+ */
+mxCellRenderer.prototype.createControlClickHandler = function(state)
+{
+	var graph = state.view.graph;
+	
+	return mxUtils.bind(this, function (evt)
+	{
+		if (this.forceControlClickHandler || graph.isEnabled())
+		{
+			var collapse = !graph.isCellCollapsed(state.cell);
+			graph.foldCells(collapse, false, [state.cell], null, evt);
+			mxEvent.consume(evt);
+		}
+	});
+};
+
+/**
+ * Function: initControl
+ * 
+ * Initializes the given control and returns the corresponding DOM node.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> for which the control should be initialized.
+ * control - <mxShape> to be initialized.
+ * handleEvents - Boolean indicating if mousedown and mousemove should fire events via the graph.
+ * clickHandler - Optional function to implement clicks on the control.
+ */
+mxCellRenderer.prototype.initControl = function(state, control, handleEvents, clickHandler)
+{
+	var graph = state.view.graph;
+	
+	// In the special case where the label is in HTML and the display is SVG the image
+	// should go into the graph container directly in order to be clickable. Otherwise
+	// it is obscured by the HTML label that overlaps the cell.
+	var isForceHtml = graph.isHtmlLabel(state.cell) && mxClient.NO_FO &&
+		graph.dialect == mxConstants.DIALECT_SVG;
+
+	if (isForceHtml)
+	{
+		control.dialect = mxConstants.DIALECT_PREFERHTML;
+		control.init(graph.container);
+		control.node.style.zIndex = 1;
+	}
+	else
+	{
+		control.init(state.view.getOverlayPane());
+	}
+
+	var node = control.innerNode || control.node;
+	
+	// Workaround for missing click event on iOS is to check tolerance below
+	if (clickHandler != null && !mxClient.IS_IOS)
+	{
+		if (graph.isEnabled())
+		{
+			node.style.cursor = 'pointer';
+		}
+		
+		mxEvent.addListener(node, 'click', clickHandler);
+	}
+	
+	if (handleEvents)
+	{
+		var first = null;
+
+		mxEvent.addGestureListeners(node,
+			function (evt)
+			{
+				first = new mxPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+				graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt, state));
+				mxEvent.consume(evt);
+			},
+			function (evt)
+			{
+				graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, state));
+			},
+			function (evt)
+			{
+				graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt, state));
+				mxEvent.consume(evt);
+			});
+		
+		// Uses capture phase for event interception to stop bubble phase
+		if (clickHandler != null && mxClient.IS_IOS)
+		{
+			node.addEventListener('touchend', function(evt)
+			{
+				if (first != null)
+				{
+					var tol = graph.tolerance;
+					
+					if (Math.abs(first.x - mxEvent.getClientX(evt)) < tol &&
+						Math.abs(first.y - mxEvent.getClientY(evt)) < tol)
+					{
+						clickHandler.call(clickHandler, evt);
+						mxEvent.consume(evt);
+					}
+				}
+			}, true);
+		}
+	}
+	
+	return node;
+};
+
+/**
+ * Function: isShapeEvent
+ * 
+ * Returns true if the event is for the shape of the given state. This
+ * implementation always returns true.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose shape fired the event.
+ * evt - Mouse event which was fired.
+ */
+mxCellRenderer.prototype.isShapeEvent = function(state, evt)
+{
+	return true;
+};
+
+/**
+ * Function: isLabelEvent
+ * 
+ * Returns true if the event is for the label of the given state. This
+ * implementation always returns true.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose label fired the event.
+ * evt - Mouse event which was fired.
+ */
+mxCellRenderer.prototype.isLabelEvent = function(state, evt)
+{
+	return true;
+};
+
+/**
+ * Function: installListeners
+ * 
+ * Installs the event listeners for the given cell state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> for which the event listeners should be isntalled.
+ */
+mxCellRenderer.prototype.installListeners = function(state)
+{
+	var graph = state.view.graph;
+
+	// Workaround for touch devices routing all events for a mouse
+	// gesture (down, move, up) via the initial DOM node. Same for
+	// HTML images in all IE versions (VML images are working).
+	var getState = function(evt)
+	{
+		var result = state;
+		
+		if ((graph.dialect != mxConstants.DIALECT_SVG && mxEvent.getSource(evt).nodeName == 'IMG') || mxClient.IS_TOUCH)
+		{
+			var x = mxEvent.getClientX(evt);
+			var y = mxEvent.getClientY(evt);
+			
+			// Dispatches the drop event to the graph which
+			// consumes and executes the source function
+			var pt = mxUtils.convertPoint(graph.container, x, y);
+			result = graph.view.getState(graph.getCellAt(pt.x, pt.y));
+		}
+		
+		return result;
+	};
+
+	mxEvent.addGestureListeners(state.shape.node,
+		mxUtils.bind(this, function(evt)
+		{
+			if (this.isShapeEvent(state, evt))
+			{
+				graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt, state));
+			}
+		}),
+		mxUtils.bind(this, function(evt)
+		{
+			if (this.isShapeEvent(state, evt))
+			{
+				graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, getState(evt)));
+			}
+		}),
+		mxUtils.bind(this, function(evt)
+		{
+			if (this.isShapeEvent(state, evt))
+			{
+				graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt, getState(evt)));
+			}
+		})
+	);
+	
+	// Uses double click timeout in mxGraph for quirks mode
+	if (graph.nativeDblClickEnabled)
+	{
+		mxEvent.addListener(state.shape.node, 'dblclick',
+			mxUtils.bind(this, function(evt)
+			{
+				if (this.isShapeEvent(state, evt))
+				{
+					graph.dblClick(evt, state.cell);
+					mxEvent.consume(evt);
+				}
+			})
+		);
+	}
+};
+
+/**
+ * Function: redrawLabel
+ * 
+ * Redraws the label for the given cell state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose label should be redrawn.
+ */
+mxCellRenderer.prototype.redrawLabel = function(state, forced)
+{
+	var value = this.getLabelValue(state);
+	
+	if (state.text == null && value != null && (mxUtils.isNode(value) || value.length > 0))
+	{
+		this.createLabel(state, value);
+	}
+	else if (state.text != null && (value == null || value.length == 0))
+	{
+		state.text.destroy();
+		state.text = null;
+	}
+
+	if (state.text != null)
+	{
+		var graph = state.view.graph;
+
+		// Forced is true if the style has changed, so to get the updated
+		// result in getLabelBounds we apply the new style to the shape
+		if (forced)
+		{
+
+			// Checks if a full repaint is needed
+			if (state.text.lastValue != null && this.isTextShapeInvalid(state, state.text))
+			{
+				// Forces a full repaint
+				state.text.lastValue = null;
+			}
+			
+			state.text.resetStyles();
+			state.text.apply(state);
+			
+			// Special case where value is obtained via hook in graph
+			state.text.valign = graph.getVerticalAlign(state);
+		}
+		
+		var bounds = this.getLabelBounds(state);
+		var wrapping = graph.isWrapping(state.cell);
+		var clipping = graph.isLabelClipped(state.cell);
+		var isForceHtml = (state.view.graph.isHtmlLabel(state.cell) || (value != null && mxUtils.isNode(value)));
+		var dialect = (isForceHtml) ? mxConstants.DIALECT_STRICTHTML : state.view.graph.dialect;
+
+		// Text is a special case where change of dialect is possible at runtime
+		var overflow = state.style[mxConstants.STYLE_OVERFLOW] || 'visible';
+		
+		if (forced || state.text.value != value || state.text.isWrapping != wrapping ||
+			state.text.overflow != overflow || state.text.isClipping != clipping ||
+			state.text.scale != this.getTextScale(state) || state.text.dialect != dialect ||
+			!state.text.bounds.equals(bounds))
+		{
+			state.text.dialect = dialect;
+			state.text.value = value;
+			state.text.bounds = bounds;
+			state.text.scale = this.getTextScale(state);
+			state.text.wrap = wrapping;
+			state.text.clipped = clipping;
+			state.text.overflow = overflow;
+			
+			// Preserves visible state
+			var vis = state.text.node.style.visibility;
+			this.redrawLabelShape(state.text);
+			state.text.node.style.visibility = vis;
+		}
+	}
+};
+
+/**
+ * Function: isTextShapeInvalid
+ * 
+ * Returns true if the style for the text shape has changed.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose label should be checked.
+ * shape - <mxText> shape to be checked.
+ */
+mxCellRenderer.prototype.isTextShapeInvalid = function(state, shape)
+{
+	function check(property, stylename, defaultValue)
+	{
+		// Workaround for spacing added to directional spacing
+		if (stylename == 'spacingTop' || stylename == 'spacingRight' ||
+			stylename == 'spacingBottom' || stylename == 'spacingLeft')
+		{
+			result = parseFloat(shape[property]) - parseFloat(shape.spacing) !=
+				(state.style[stylename] || defaultValue);
+		}
+		else
+		{
+			result = shape[property] != (state.style[stylename] || defaultValue);
+		}
+		
+		return result;
+	};
+
+	return check('fontStyle', mxConstants.STYLE_FONTSTYLE, mxConstants.DEFAULT_FONTSTYLE) ||
+		check('family', mxConstants.STYLE_FONTFAMILY, mxConstants.DEFAULT_FONTFAMILY) ||
+		check('size', mxConstants.STYLE_FONTSIZE, mxConstants.DEFAULT_FONTSIZE) ||
+		check('color', mxConstants.STYLE_FONTCOLOR, 'black') ||
+		check('align', mxConstants.STYLE_ALIGN, '') ||
+		check('valign', mxConstants.STYLE_VERTICAL_ALIGN, '') ||
+		check('spacing', mxConstants.STYLE_SPACING, 2) ||
+		check('spacingTop', mxConstants.STYLE_SPACING_TOP, 0) ||
+		check('spacingRight', mxConstants.STYLE_SPACING_RIGHT, 0) ||
+		check('spacingBottom', mxConstants.STYLE_SPACING_BOTTOM, 0) ||
+		check('spacingLeft', mxConstants.STYLE_SPACING_LEFT, 0) ||
+		check('horizontal', mxConstants.STYLE_HORIZONTAL, true) ||
+		check('background', mxConstants.STYLE_LABEL_BACKGROUNDCOLOR) ||
+		check('border', mxConstants.STYLE_LABEL_BORDERCOLOR) ||
+		check('opacity', mxConstants.STYLE_TEXT_OPACITY, 100) ||
+		check('textDirection', mxConstants.STYLE_TEXT_DIRECTION, mxConstants.DEFAULT_TEXT_DIRECTION);
+};
+
+/**
+ * Function: redrawLabelShape
+ * 
+ * Called to invoked redraw on the given text shape.
+ * 
+ * Parameters:
+ * 
+ * shape - <mxText> shape to be redrawn.
+ */
+mxCellRenderer.prototype.redrawLabelShape = function(shape)
+{
+	shape.redraw();
+};
+
+/**
+ * Function: getTextScale
+ * 
+ * Returns the scaling used for the label of the given state
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose label scale should be returned.
+ */
+mxCellRenderer.prototype.getTextScale = function(state)
+{
+	return state.view.scale;
+};
+
+/**
+ * Function: getLabelBounds
+ * 
+ * Returns the bounds to be used to draw the label of the given state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose label bounds should be returned.
+ */
+mxCellRenderer.prototype.getLabelBounds = function(state)
+{
+	var graph = state.view.graph;
+	var scale = state.view.scale;
+	var isEdge = graph.getModel().isEdge(state.cell);
+	var bounds = new mxRectangle(state.absoluteOffset.x, state.absoluteOffset.y);
+
+	if (isEdge)
+	{
+		var spacing = state.text.getSpacing();
+		bounds.x += spacing.x * scale;
+		bounds.y += spacing.y * scale;
+		
+		var geo = graph.getCellGeometry(state.cell);
+		
+		if (geo != null)
+		{
+			bounds.width = Math.max(0, geo.width * scale);
+			bounds.height = Math.max(0, geo.height * scale);
+		}
+	}
+	else
+	{
+		// Inverts label position
+		if (state.text.isPaintBoundsInverted())
+		{
+			var tmp = bounds.x;
+			bounds.x = bounds.y;
+			bounds.y = tmp;
+		}
+		
+		bounds.x += state.x;
+		bounds.y += state.y;
+		
+		// Minimum of 1 fixes alignment bug in HTML labels
+		bounds.width = Math.max(1, state.width);
+		bounds.height = Math.max(1, state.height);
+
+		var sc = mxUtils.getValue(state.style, mxConstants.STYLE_STROKECOLOR, mxConstants.NONE);
+		
+		if (sc != mxConstants.NONE && sc != '')
+		{
+			var s = parseFloat(mxUtils.getValue(state.style, mxConstants.STYLE_STROKEWIDTH, 1)) * scale;
+			var dx = 1 + Math.floor((s - 1) / 2);
+			var dh = Math.floor(s + 1);
+			
+			bounds.x += dx;
+			bounds.y += dx;
+			bounds.width -= dh;
+			bounds.height -= dh;
+		}
+	}
+
+	if (state.text.isPaintBoundsInverted())
+	{
+		// Rotates around center of state
+		var t = (state.width - state.height) / 2;
+		bounds.x += t;
+		bounds.y -= t;
+		var tmp = bounds.width;
+		bounds.width = bounds.height;
+		bounds.height = tmp;
+	}
+	
+	// Shape can modify its label bounds
+	if (state.shape != null)
+	{
+		var hpos = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);
+		var vpos = mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);
+		
+		if (hpos == mxConstants.ALIGN_CENTER && vpos == mxConstants.ALIGN_MIDDLE)
+		{
+			bounds = state.shape.getLabelBounds(bounds);
+		}
+	}
+	
+	// Label width style overrides actual label width
+	var lw = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_WIDTH, null);
+	
+	if (lw != null)
+	{
+		bounds.width = parseFloat(lw) * scale;
+	}
+	
+	if (!isEdge)
+	{
+		this.rotateLabelBounds(state, bounds);
+	}
+	
+	return bounds;
+};
+
+/**
+ * Function: rotateLabelBounds
+ * 
+ * Adds the shape rotation to the given label bounds and
+ * applies the alignment and offsets.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose label bounds should be rotated.
+ * bounds - <mxRectangle> the rectangle to be rotated.
+ */
+mxCellRenderer.prototype.rotateLabelBounds = function(state, bounds)
+{
+	bounds.y -= state.text.margin.y * bounds.height;
+	bounds.x -= state.text.margin.x * bounds.width;
+	
+	if (!this.legacySpacing || (state.style[mxConstants.STYLE_OVERFLOW] != 'fill' && state.style[mxConstants.STYLE_OVERFLOW] != 'width'))
+	{
+		var s = state.view.scale;
+		var spacing = state.text.getSpacing();
+		bounds.x += spacing.x * s;
+		bounds.y += spacing.y * s;
+		
+		var hpos = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);
+		var vpos = mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);
+		var lw = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_WIDTH, null);
+		
+		bounds.width = Math.max(0, bounds.width - ((hpos == mxConstants.ALIGN_CENTER && lw == null) ? (state.text.spacingLeft * s + state.text.spacingRight * s) : 0));
+		bounds.height = Math.max(0, bounds.height - ((vpos == mxConstants.ALIGN_MIDDLE) ? (state.text.spacingTop * s + state.text.spacingBottom * s) : 0));
+	}
+
+	var theta = state.text.getTextRotation();
+
+	// Only needed if rotated around another center
+	if (theta != 0 && state != null && state.view.graph.model.isVertex(state.cell))
+	{
+		var cx = state.getCenterX();
+		var cy = state.getCenterY();
+		
+		if (bounds.x != cx || bounds.y != cy)
+		{
+			var rad = theta * (Math.PI / 180);
+			pt = mxUtils.getRotatedPoint(new mxPoint(bounds.x, bounds.y),
+					Math.cos(rad), Math.sin(rad), new mxPoint(cx, cy));
+			
+			bounds.x = pt.x;
+			bounds.y = pt.y;
+		}
+	}
+};
+
+/**
+ * Function: redrawCellOverlays
+ * 
+ * Redraws the overlays for the given cell state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose overlays should be redrawn.
+ */
+mxCellRenderer.prototype.redrawCellOverlays = function(state, forced)
+{
+	this.createCellOverlays(state);
+
+	if (state.overlays != null)
+	{
+		var rot = mxUtils.mod(mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION, 0), 90);
+        var rad = mxUtils.toRadians(rot);
+        var cos = Math.cos(rad);
+        var sin = Math.sin(rad);
+		
+		state.overlays.visit(function(id, shape)
+		{
+			var bounds = shape.overlay.getBounds(state);
+		
+			if (!state.view.graph.getModel().isEdge(state.cell))
+			{
+				if (state.shape != null && rot != 0)
+				{
+					var cx = bounds.getCenterX();
+					var cy = bounds.getCenterY();
+
+					var point = mxUtils.getRotatedPoint(new mxPoint(cx, cy), cos, sin,
+			        		new mxPoint(state.getCenterX(), state.getCenterY()));
+
+			        cx = point.x;
+			        cy = point.y;
+			        bounds.x = Math.round(cx - bounds.width / 2);
+			        bounds.y = Math.round(cy - bounds.height / 2);
+				}
+			}
+			
+			if (forced || shape.bounds == null || shape.scale != state.view.scale ||
+				!shape.bounds.equals(bounds))
+			{
+				shape.bounds = bounds;
+				shape.scale = state.view.scale;
+				shape.redraw();
+			}
+		});
+	}
+};
+
+/**
+ * Function: redrawControl
+ * 
+ * Redraws the control for the given cell state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose control should be redrawn.
+ */
+mxCellRenderer.prototype.redrawControl = function(state, forced)
+{
+	var image = state.view.graph.getFoldingImage(state);
+	
+	if (state.control != null && image != null)
+	{
+		var bounds = this.getControlBounds(state, image.width, image.height);
+		var r = (this.legacyControlPosition) ?
+				mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION, 0) :
+				state.shape.getTextRotation();
+		var s = state.view.scale;
+		
+		if (forced || state.control.scale != s || !state.control.bounds.equals(bounds) ||
+			state.control.rotation != r)
+		{
+			state.control.rotation = r;
+			state.control.bounds = bounds;
+			state.control.scale = s;
+			
+			state.control.redraw();
+		}
+	}
+};
+
+/**
+ * Function: getControlBounds
+ * 
+ * Returns the bounds to be used to draw the control (folding icon) of the
+ * given state.
+ */
+mxCellRenderer.prototype.getControlBounds = function(state, w, h)
+{
+	if (state.control != null)
+	{
+		var s = state.view.scale;
+		var cx = state.getCenterX();
+		var cy = state.getCenterY();
+	
+		if (!state.view.graph.getModel().isEdge(state.cell))
+		{
+			cx = state.x + w * s;
+			cy = state.y + h * s;
+			
+			if (state.shape != null)
+			{
+				// TODO: Factor out common code
+				var rot = state.shape.getShapeRotation();
+				
+				if (this.legacyControlPosition)
+				{
+					rot = mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION, 0);
+				}
+				else
+				{
+					if (state.shape.isPaintBoundsInverted())
+					{
+						var t = (state.width - state.height) / 2;
+						cx += t;
+						cy -= t;
+					}
+				}
+				
+				if (rot != 0)
+				{
+			        var rad = mxUtils.toRadians(rot);
+			        var cos = Math.cos(rad);
+			        var sin = Math.sin(rad);
+			        
+			        var point = mxUtils.getRotatedPoint(new mxPoint(cx, cy), cos, sin,
+			        		new mxPoint(state.getCenterX(), state.getCenterY()));
+			        cx = point.x;
+			        cy = point.y;
+				}
+			}
+		}
+		
+		return (state.view.graph.getModel().isEdge(state.cell)) ? 
+			new mxRectangle(Math.round(cx - w / 2 * s), Math.round(cy - h / 2 * s), Math.round(w * s), Math.round(h * s))
+			: new mxRectangle(Math.round(cx - w / 2 * s), Math.round(cy - h / 2 * s), Math.round(w * s), Math.round(h * s));
+	}
+	
+	return null;
+};
+
+/**
+ * Function: insertStateAfter
+ * 
+ * Inserts the given array of <mxShapes> after the given nodes in the DOM.
+ * 
+ * Parameters:
+ * 
+ * shapes - Array of <mxShapes> to be inserted.
+ * node - Node in <drawPane> after which the shapes should be inserted.
+ * htmlNode - Node in the graph container after which the shapes should be inserted that
+ * will not go into the <drawPane> (eg. HTML labels without foreignObjects).
+ */
+mxCellRenderer.prototype.insertStateAfter = function(state, node, htmlNode)
+{
+	var shapes = this.getShapesForState(state);
+	
+	for (var i = 0; i < shapes.length; i++)
+	{
+		if (shapes[i] != null && shapes[i].node != null)
+		{
+			var html = shapes[i].node.parentNode != state.view.getDrawPane() &&
+				shapes[i].node.parentNode != state.view.getOverlayPane();
+			var temp = (html) ? htmlNode : node;
+			
+			if (temp != null && temp.nextSibling != shapes[i].node)
+			{
+				if (temp.nextSibling == null)
+				{
+					temp.parentNode.appendChild(shapes[i].node);
+				}
+				else
+				{
+					temp.parentNode.insertBefore(shapes[i].node, temp.nextSibling);
+				}
+			}
+			else if (temp == null)
+			{
+				// Special case: First HTML node should be first sibling after canvas
+				if (shapes[i].node.parentNode == state.view.graph.container)
+				{
+					var canvas = state.view.canvas;
+					
+					while (canvas != null && canvas.parentNode != state.view.graph.container)
+					{
+						canvas = canvas.parentNode;
+					}
+					
+					if (canvas != null && canvas.nextSibling != null)
+					{
+						if (canvas.nextSibling != shapes[i].node)
+						{
+							shapes[i].node.parentNode.insertBefore(shapes[i].node, canvas.nextSibling);
+						}
+					}
+					else
+					{
+						shapes[i].node.parentNode.appendChild(shapes[i].node);
+					}
+				}
+				else if (shapes[i].node.parentNode.firstChild != null && shapes[i].node.parentNode.firstChild != shapes[i].node)
+				{
+					// Inserts the node as the first child of the parent to implement the order
+					shapes[i].node.parentNode.insertBefore(shapes[i].node, shapes[i].node.parentNode.firstChild);
+				}
+			}
+			
+			if (html)
+			{
+				htmlNode = shapes[i].node;
+			}
+			else
+			{
+				node = shapes[i].node;
+			}
+		}
+	}
+
+	return [node, htmlNode];
+};
+
+/**
+ * Function: getShapesForState
+ * 
+ * Returns the <mxShapes> for the given cell state in the order in which they should
+ * appear in the DOM.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose shapes should be returned.
+ */
+mxCellRenderer.prototype.getShapesForState = function(state)
+{
+	return [state.shape, state.text, state.control];
+};
+
+/**
+ * Function: redraw
+ * 
+ * Updates the bounds or points and scale of the shapes for the given cell
+ * state. This is called in mxGraphView.validatePoints as the last step of
+ * updating all cells.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> for which the shapes should be updated.
+ * force - Optional boolean that specifies if the cell should be reconfiured
+ * and redrawn without any additional checks.
+ * rendering - Optional boolean that specifies if the cell should actually
+ * be drawn into the DOM. If this is false then redraw and/or reconfigure
+ * will not be called on the shape.
+ */
+mxCellRenderer.prototype.redraw = function(state, force, rendering)
+{
+	var shapeChanged = this.redrawShape(state, force, rendering);
+	
+	if (state.shape != null && (rendering == null || rendering))
+	{
+		this.redrawLabel(state, shapeChanged);
+		this.redrawCellOverlays(state, shapeChanged);
+		this.redrawControl(state, shapeChanged);
+	}
+};
+
+/**
+ * Function: redrawShape
+ * 
+ * Redraws the shape for the given cell state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose label should be redrawn.
+ */
+mxCellRenderer.prototype.redrawShape = function(state, force, rendering)
+{
+	var model = state.view.graph.model;
+	var shapeChanged = false;
+
+	// Forces creation of new shape if shape style has changed
+	if (state.shape != null && state.shape.style != null && state.style != null &&
+		state.shape.style[mxConstants.STYLE_SHAPE] != state.style[mxConstants.STYLE_SHAPE])
+	{
+		state.shape.destroy();
+		state.shape = null;
+	}
+	
+	if (state.shape == null && state.view.graph.container != null &&
+		state.cell != state.view.currentRoot &&
+		(model.isVertex(state.cell) || model.isEdge(state.cell)))
+	{
+		state.shape = this.createShape(state);
+		
+		if (state.shape != null)
+		{
+			state.shape.antiAlias = this.antiAlias;
+	
+			this.createIndicatorShape(state);
+			this.initializeShape(state);
+			this.createCellOverlays(state);
+			this.installListeners(state);
+			
+			// Forces a refresh of the handler of one exists
+			state.view.graph.selectionCellsHandler.updateHandler(state);
+		}
+	}
+	else if (state.shape != null && !mxUtils.equalEntries(state.shape.style, state.style))
+	{
+		state.shape.resetStyles();
+		this.configureShape(state);
+		// LATER: Ignore update for realtime to fix reset of current gesture
+		state.view.graph.selectionCellsHandler.updateHandler(state);
+		force = true;
+	}
+
+	if (state.shape != null)
+	{
+		// Handles changes of the collapse icon
+		this.createControl(state);
+		
+		// Redraws the cell if required, ignores changes to bounds if points are
+		// defined as the bounds are updated for the given points inside the shape
+		if (force || this.isShapeInvalid(state, state.shape))
+		{
+			if (state.absolutePoints != null)
+			{
+				state.shape.points = state.absolutePoints.slice();
+				state.shape.bounds = null;
+			}
+			else
+			{
+				state.shape.points = null;
+				state.shape.bounds = new mxRectangle(state.x, state.y, state.width, state.height);
+			}
+
+			state.shape.scale = state.view.scale;
+			
+			if (rendering == null || rendering)
+			{
+				state.shape.redraw();
+			}
+			else
+			{
+				state.shape.updateBoundingBox();
+			}
+			
+			shapeChanged = true;
+		}
+	}
+
+	return shapeChanged;
+};
+
+/**
+ * Function: isShapeInvalid
+ * 
+ * Returns true if the given shape must be repainted.
+ */
+mxCellRenderer.prototype.isShapeInvalid = function(state, shape)
+{
+	return shape.bounds == null || shape.scale != state.view.scale ||
+		(state.absolutePoints == null && !shape.bounds.equals(state)) ||
+		(state.absolutePoints != null && !mxUtils.equalPoints(shape.points, state.absolutePoints))
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the shapes associated with the given cell state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> for which the shapes should be destroyed.
+ */
+mxCellRenderer.prototype.destroy = function(state)
+{
+	if (state.shape != null)
+	{
+		if (state.text != null)
+		{		
+			state.text.destroy();
+			state.text = null;
+		}
+		
+		if (state.overlays != null)
+		{
+			state.overlays.visit(function(id, shape)
+			{
+				shape.destroy();
+			});
+			
+			state.overlays = null;
+		}
+
+		if (state.control != null)
+		{
+			state.control.destroy();
+			state.control = null;
+		}
+		
+		state.shape.destroy();
+		state.shape = null;
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/view/mxCellState.js b/airavata-kubernetes/workflow-composer/src/js/view/mxCellState.js
new file mode 100644
index 0000000..8f563a7
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/view/mxCellState.js
@@ -0,0 +1,431 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxCellState
+ * 
+ * Represents the current state of a cell in a given <mxGraphView>.
+ * 
+ * For edges, the edge label position is stored in <absoluteOffset>.
+ * 
+ * The size for oversize labels can be retrieved using the boundingBox property
+ * of the <text> field as shown below.
+ * 
+ * (code)
+ * var bbox = (state.text != null) ? state.text.boundingBox : null;
+ * (end)
+ * 
+ * Constructor: mxCellState
+ * 
+ * Constructs a new object that represents the current state of the given
+ * cell in the specified view.
+ * 
+ * Parameters:
+ * 
+ * view - <mxGraphView> that contains the state.
+ * cell - <mxCell> that this state represents.
+ * style - Array of key, value pairs that constitute the style.
+ */
+function mxCellState(view, cell, style)
+{
+	this.view = view;
+	this.cell = cell;
+	this.style = style;
+	
+	this.origin = new mxPoint();
+	this.absoluteOffset = new mxPoint();
+};
+
+/**
+ * Extends mxRectangle.
+ */
+mxCellState.prototype = new mxRectangle();
+mxCellState.prototype.constructor = mxCellState;
+
+/**
+ * Variable: view
+ * 
+ * Reference to the enclosing <mxGraphView>.
+ */
+mxCellState.prototype.view = null;
+
+/**
+ * Variable: cell
+ *
+ * Reference to the <mxCell> that is represented by this state.
+ */
+mxCellState.prototype.cell = null;
+
+/**
+ * Variable: style
+ * 
+ * Contains an array of key, value pairs that represent the style of the
+ * cell.
+ */
+mxCellState.prototype.style = null;
+
+/**
+ * Variable: invalid
+ * 
+ * Specifies if the state is invalid. Default is true.
+ */
+mxCellState.prototype.invalid = true;
+
+/**
+ * Variable: origin
+ *
+ * <mxPoint> that holds the origin for all child cells. Default is a new
+ * empty <mxPoint>.
+ */
+mxCellState.prototype.origin = null;
+
+/**
+ * Variable: absolutePoints
+ * 
+ * Holds an array of <mxPoints> that represent the absolute points of an
+ * edge.
+ */
+mxCellState.prototype.absolutePoints = null;
+
+/**
+ * Variable: absoluteOffset
+ *
+ * <mxPoint> that holds the absolute offset. For edges, this is the
+ * absolute coordinates of the label position. For vertices, this is the
+ * offset of the label relative to the top, left corner of the vertex. 
+ */
+mxCellState.prototype.absoluteOffset = null;
+
+/**
+ * Variable: visibleSourceState
+ * 
+ * Caches the visible source terminal state.
+ */
+mxCellState.prototype.visibleSourceState = null;
+
+/**
+ * Variable: visibleTargetState
+ * 
+ * Caches the visible target terminal state.
+ */
+mxCellState.prototype.visibleTargetState = null;
+
+/**
+ * Variable: terminalDistance
+ * 
+ * Caches the distance between the end points for an edge.
+ */
+mxCellState.prototype.terminalDistance = 0;
+
+/**
+ * Variable: length
+ *
+ * Caches the length of an edge.
+ */
+mxCellState.prototype.length = 0;
+
+/**
+ * Variable: segments
+ * 
+ * Array of numbers that represent the cached length of each segment of the
+ * edge.
+ */
+mxCellState.prototype.segments = null;
+
+/**
+ * Variable: shape
+ * 
+ * Holds the <mxShape> that represents the cell graphically.
+ */
+mxCellState.prototype.shape = null;
+
+/**
+ * Variable: text
+ * 
+ * Holds the <mxText> that represents the label of the cell. Thi smay be
+ * null if the cell has no label.
+ */
+mxCellState.prototype.text = null;
+
+/**
+ * Variable: unscaledWidth
+ * 
+ * Holds the unscaled width of the state.
+ */
+mxCellState.prototype.unscaledWidth = null;
+
+/**
+ * Function: getPerimeterBounds
+ * 
+ * Returns the <mxRectangle> that should be used as the perimeter of the
+ * cell.
+ * 
+ * Parameters:
+ * 
+ * border - Optional border to be added around the perimeter bounds.
+ * bounds - Optional <mxRectangle> to be used as the initial bounds.
+ */
+mxCellState.prototype.getPerimeterBounds = function(border, bounds)
+{
+	border = border || 0;
+	bounds = (bounds != null) ? bounds : new mxRectangle(this.x, this.y, this.width, this.height);
+	
+	if (this.shape != null && this.shape.stencil != null && this.shape.stencil.aspect == 'fixed')
+	{
+		var aspect = this.shape.stencil.computeAspect(this.style, bounds.x, bounds.y, bounds.width, bounds.height);
+		
+		bounds.x = aspect.x;
+		bounds.y = aspect.y;
+		bounds.width = this.shape.stencil.w0 * aspect.width;
+		bounds.height = this.shape.stencil.h0 * aspect.height;
+	}
+	
+	if (border != 0)
+	{
+		bounds.grow(border);
+	}
+	
+	return bounds;
+};
+
+/**
+ * Function: setAbsoluteTerminalPoint
+ * 
+ * Sets the first or last point in <absolutePoints> depending on isSource.
+ * 
+ * Parameters:
+ * 
+ * point - <mxPoint> that represents the terminal point.
+ * isSource - Boolean that specifies if the first or last point should
+ * be assigned.
+ */
+mxCellState.prototype.setAbsoluteTerminalPoint = function(point, isSource)
+{
+	if (isSource)
+	{
+		if (this.absolutePoints == null)
+		{
+			this.absolutePoints = [];
+		}
+		
+		if (this.absolutePoints.length == 0)
+		{
+			this.absolutePoints.push(point);
+		}
+		else
+		{
+			this.absolutePoints[0] = point;
+		}
+	}
+	else
+	{
+		if (this.absolutePoints == null)
+		{
+			this.absolutePoints = [];
+			this.absolutePoints.push(null);
+			this.absolutePoints.push(point);
+		}
+		else if (this.absolutePoints.length == 1)
+		{
+			this.absolutePoints.push(point);
+		}
+		else
+		{
+			this.absolutePoints[this.absolutePoints.length - 1] = point;
+		}
+	}
+};
+
+/**
+ * Function: setCursor
+ * 
+ * Sets the given cursor on the shape and text shape.
+ */
+mxCellState.prototype.setCursor = function(cursor)
+{
+	if (this.shape != null)
+	{
+		this.shape.setCursor(cursor);
+	}
+	
+	if (this.text != null)
+	{
+		this.text.setCursor(cursor);
+	}
+};
+
+/**
+ * Function: getVisibleTerminal
+ * 
+ * Returns the visible source or target terminal cell.
+ * 
+ * Parameters:
+ * 
+ * source - Boolean that specifies if the source or target cell should be
+ * returned.
+ */
+mxCellState.prototype.getVisibleTerminal = function(source)
+{
+	var tmp = this.getVisibleTerminalState(source);
+	
+	return (tmp != null) ? tmp.cell : null;
+};
+
+/**
+ * Function: getVisibleTerminalState
+ * 
+ * Returns the visible source or target terminal state.
+ * 
+ * Parameters:
+ * 
+ * source - Boolean that specifies if the source or target state should be
+ * returned.
+ */
+mxCellState.prototype.getVisibleTerminalState = function(source)
+{
+	return (source) ? this.visibleSourceState : this.visibleTargetState;
+};
+
+/**
+ * Function: setVisibleTerminalState
+ * 
+ * Sets the visible source or target terminal state.
+ * 
+ * Parameters:
+ * 
+ * terminalState - <mxCellState> that represents the terminal.
+ * source - Boolean that specifies if the source or target state should be set.
+ */
+mxCellState.prototype.setVisibleTerminalState = function(terminalState, source)
+{
+	if (source)
+	{
+		this.visibleSourceState = terminalState;
+	}
+	else
+	{
+		this.visibleTargetState = terminalState;
+	}
+};
+
+/**
+ * Function: getCellBounds
+ * 
+ * Returns the unscaled, untranslated bounds.
+ */
+mxCellState.prototype.getCellBounds = function()
+{
+	return this.cellBounds;
+};
+
+/**
+ * Function: getPaintBounds
+ * 
+ * Returns the unscaled, untranslated paint bounds. This is the same as
+ * <getCellBounds> but with a 90 degree rotation if the shape's
+ * isPaintBoundsInverted returns true.
+ */
+mxCellState.prototype.getPaintBounds = function()
+{
+	return this.paintBounds;
+};
+
+/**
+ * Function: updateCachedBounds
+ * 
+ * Updates the cellBounds and paintBounds.
+ */
+mxCellState.prototype.updateCachedBounds = function()
+{
+	var tr = this.view.translate;
+	var s = this.view.scale;
+	this.cellBounds = new mxRectangle(this.x / s - tr.x, this.y / s - tr.y, this.width / s, this.height / s);
+	this.paintBounds = mxRectangle.fromRectangle(this.cellBounds);
+	
+	if (this.shape != null && this.shape.isPaintBoundsInverted())
+	{
+		this.paintBounds.rotate90();
+	}
+};
+
+/**
+ * Destructor: setState
+ * 
+ * Copies all fields from the given state to this state.
+ */
+mxCellState.prototype.setState = function(state)
+{
+	this.view = state.view;
+	this.cell = state.cell;
+	this.style = state.style;
+	this.absolutePoints = state.absolutePoints;
+	this.origin = state.origin;
+	this.absoluteOffset = state.absoluteOffset;
+	this.boundingBox = state.boundingBox;
+	this.terminalDistance = state.terminalDistance;
+	this.segments = state.segments;
+	this.length = state.length;
+	this.x = state.x;
+	this.y = state.y;
+	this.width = state.width;
+	this.height = state.height;
+	this.unscaledWidth = state.unscaledWidth;
+};
+
+/**
+ * Function: clone
+ *
+ * Returns a clone of this <mxPoint>.
+ */
+mxCellState.prototype.clone = function()
+{
+ 	var clone = new mxCellState(this.view, this.cell, this.style);
+
+	// Clones the absolute points
+	if (this.absolutePoints != null)
+	{
+		clone.absolutePoints = [];
+		
+		for (var i = 0; i < this.absolutePoints.length; i++)
+		{
+			clone.absolutePoints[i] = this.absolutePoints[i].clone();
+		}
+	}
+
+	if (this.origin != null)
+	{
+		clone.origin = this.origin.clone();
+	}
+
+	if (this.absoluteOffset != null)
+	{
+		clone.absoluteOffset = this.absoluteOffset.clone();
+	}
+
+	if (this.boundingBox != null)
+	{
+		clone.boundingBox = this.boundingBox.clone();
+	}
+
+	clone.terminalDistance = this.terminalDistance;
+	clone.segments = this.segments;
+	clone.length = this.length;
+	clone.x = this.x;
+	clone.y = this.y;
+	clone.width = this.width;
+	clone.height = this.height;
+	clone.unscaledWidth = this.unscaledWidth;
+	
+	return clone;
+};
+
+/**
+ * Destructor: destroy
+ * 
+ * Destroys the state and all associated resources.
+ */
+mxCellState.prototype.destroy = function()
+{
+	this.view.graph.cellRenderer.destroy(this);
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/view/mxCellStatePreview.js b/airavata-kubernetes/workflow-composer/src/js/view/mxCellStatePreview.js
new file mode 100644
index 0000000..7b0924d
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/view/mxCellStatePreview.js
@@ -0,0 +1,203 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ *
+ * Class: mxCellStatePreview
+ * 
+ * Implements a live preview for moving cells.
+ * 
+ * Constructor: mxCellStatePreview
+ * 
+ * Constructs a move preview for the given graph.
+ * 
+ * Parameters:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ */
+function mxCellStatePreview(graph)
+{
+	this.deltas = new mxDictionary();
+	this.graph = graph;
+};
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxCellStatePreview.prototype.graph = null;
+
+/**
+ * Variable: deltas
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxCellStatePreview.prototype.deltas = null;
+
+/**
+ * Variable: count
+ * 
+ * Contains the number of entries in the map.
+ */
+mxCellStatePreview.prototype.count = 0;
+
+/**
+ * Function: isEmpty
+ * 
+ * Returns true if this contains no entries.
+ */
+mxCellStatePreview.prototype.isEmpty = function()
+{
+	return this.count == 0;
+};
+
+/**
+ * Function: moveState
+ */
+mxCellStatePreview.prototype.moveState = function(state, dx, dy, add, includeEdges)
+{
+	add = (add != null) ? add : true;
+	includeEdges = (includeEdges != null) ? includeEdges : true;
+	
+	var delta = this.deltas.get(state.cell);
+
+	if (delta == null)
+	{
+		// Note: Deltas stores the point and the state since the key is a string.
+		delta = {point: new mxPoint(dx, dy), state: state};
+		this.deltas.put(state.cell, delta);
+		this.count++;
+	}
+	else if (add)
+	{
+		delta.point.x += dx;
+		delta.point.y += dy;
+	}
+	else
+	{
+		delta.point.x = dx;
+		delta.point.y = dy;
+	}
+	
+	if (includeEdges)
+	{
+		this.addEdges(state);
+	}
+	
+	return delta.point;
+};
+
+/**
+ * Function: show
+ */
+mxCellStatePreview.prototype.show = function(visitor)
+{
+	this.deltas.visit(mxUtils.bind(this, function(key, delta)
+	{
+		this.translateState(delta.state, delta.point.x, delta.point.y);
+	}));
+	
+	this.deltas.visit(mxUtils.bind(this, function(key, delta)
+	{
+		this.revalidateState(delta.state, delta.point.x, delta.point.y, visitor);
+	}));
+};
+
+/**
+ * Function: translateState
+ */
+mxCellStatePreview.prototype.translateState = function(state, dx, dy)
+{
+	if (state != null)
+	{
+		var model = this.graph.getModel();
+		
+		if (model.isVertex(state.cell))
+		{
+			state.view.updateCellState(state);
+			var geo = model.getGeometry(state.cell);
+			
+			// Moves selection cells and non-relative vertices in
+			// the first phase so that edge terminal points will
+			// be updated in the second phase
+			if ((dx != 0 || dy != 0) && geo != null && (!geo.relative || this.deltas.get(state.cell) != null))
+			{
+				state.x += dx;
+				state.y += dy;
+			}
+		}
+	    
+	    var childCount = model.getChildCount(state.cell);
+	    
+	    for (var i = 0; i < childCount; i++)
+	    {
+	    	this.translateState(state.view.getState(model.getChildAt(state.cell, i)), dx, dy);
+	    }
+	}
+};
+
+/**
+ * Function: revalidateState
+ */
+mxCellStatePreview.prototype.revalidateState = function(state, dx, dy, visitor)
+{
+	if (state != null)
+	{
+		var model = this.graph.getModel();
+		
+		// Updates the edge terminal points and restores the
+		// (relative) positions of any (relative) children
+		if (model.isEdge(state.cell))
+		{
+			state.view.updateCellState(state);
+		}
+
+		var geo = this.graph.getCellGeometry(state.cell);
+		var pState = state.view.getState(model.getParent(state.cell));
+		
+		// Moves selection vertices which are relative
+		if ((dx != 0 || dy != 0) && geo != null && geo.relative &&
+			model.isVertex(state.cell) && (pState == null ||
+			model.isVertex(pState.cell) || this.deltas.get(state.cell) != null))
+		{
+			state.x += dx;
+			state.y += dy;
+		}
+		
+		this.graph.cellRenderer.redraw(state);
+	
+		// Invokes the visitor on the given state
+		if (visitor != null)
+		{
+			visitor(state);
+		}
+						
+	    var childCount = model.getChildCount(state.cell);
+	    
+	    for (var i = 0; i < childCount; i++)
+	    {
+	    	this.revalidateState(this.graph.view.getState(model.getChildAt(state.cell, i)), dx, dy, visitor);
+	    }
+	}
+};
+
+/**
+ * Function: addEdges
+ */
+mxCellStatePreview.prototype.addEdges = function(state)
+{
+	var model = this.graph.getModel();
+	var edgeCount = model.getEdgeCount(state.cell);
+
+	for (var i = 0; i < edgeCount; i++)
+	{
+		var s = state.view.getState(model.getEdgeAt(state.cell, i));
+
+		if (s != null)
+		{
+			this.moveState(s, 0, 0);
+		}
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/view/mxConnectionConstraint.js b/airavata-kubernetes/workflow-composer/src/js/view/mxConnectionConstraint.js
new file mode 100644
index 0000000..53d7ab8
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/view/mxConnectionConstraint.js
@@ -0,0 +1,50 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxConnectionConstraint
+ * 
+ * Defines an object that contains the constraints about how to connect one
+ * side of an edge to its terminal.
+ * 
+ * Constructor: mxConnectionConstraint
+ * 
+ * Constructs a new connection constraint for the given point and boolean
+ * arguments.
+ * 
+ * Parameters:
+ * 
+ * point - Optional <mxPoint> that specifies the fixed location of the point
+ * in relative coordinates. Default is null.
+ * perimeter - Optional boolean that specifies if the fixed point should be
+ * projected onto the perimeter of the terminal. Default is true.
+ */
+function mxConnectionConstraint(point, perimeter, name)
+{
+	this.point = point;
+	this.perimeter = (perimeter != null) ? perimeter : true;
+	this.name = name;
+};
+
+/**
+ * Variable: point
+ * 
+ * <mxPoint> that specifies the fixed location of the connection point.
+ */
+mxConnectionConstraint.prototype.point = null;
+
+/**
+ * Variable: perimeter
+ * 
+ * Boolean that specifies if the point should be projected onto the perimeter
+ * of the terminal.
+ */
+mxConnectionConstraint.prototype.perimeter = null;
+
+/**
+ * Variable: name
+ * 
+ * Optional string that specifies the name of the constraint.
+ */
+mxConnectionConstraint.prototype.name = null;
diff --git a/airavata-kubernetes/workflow-composer/src/js/view/mxEdgeStyle.js b/airavata-kubernetes/workflow-composer/src/js/view/mxEdgeStyle.js
new file mode 100644
index 0000000..38a75b8
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/view/mxEdgeStyle.js
@@ -0,0 +1,1569 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+var mxEdgeStyle =
+{
+	/**
+	 * Class: mxEdgeStyle
+	 * 
+	 * Provides various edge styles to be used as the values for
+	 * <mxConstants.STYLE_EDGE> in a cell style.
+	 *
+	 * Example:
+	 * 
+	 * (code)
+	 * var style = stylesheet.getDefaultEdgeStyle();
+	 * style[mxConstants.STYLE_EDGE] = mxEdgeStyle.ElbowConnector;
+	 * (end)
+	 * 
+	 * Sets the default edge style to <ElbowConnector>.
+	 * 
+	 * Custom edge style:
+	 * 
+	 * To write a custom edge style, a function must be added to the mxEdgeStyle
+	 * object as follows:
+	 * 
+	 * (code)
+	 * mxEdgeStyle.MyStyle = function(state, source, target, points, result)
+	 * {
+	 *   if (source != null && target != null)
+	 *   {
+	 *     var pt = new mxPoint(target.getCenterX(), source.getCenterY());
+	 * 
+	 *     if (mxUtils.contains(source, pt.x, pt.y))
+	 *     {
+	 *       pt.y = source.y + source.height;
+	 *     }
+	 * 
+	 *     result.push(pt);
+	 *   }
+	 * };
+	 * (end)
+	 * 
+	 * In the above example, a right angle is created using a point on the
+	 * horizontal center of the target vertex and the vertical center of the source
+	 * vertex. The code checks if that point intersects the source vertex and makes
+	 * the edge straight if it does. The point is then added into the result array,
+	 * which acts as the return value of the function.
+	 *
+	 * The new edge style should then be registered in the <mxStyleRegistry> as follows:
+	 * (code)
+	 * mxStyleRegistry.putValue('myEdgeStyle', mxEdgeStyle.MyStyle);
+	 * (end)
+	 * 
+	 * The custom edge style above can now be used in a specific edge as follows:
+	 * 
+	 * (code)
+	 * model.setStyle(edge, 'edgeStyle=myEdgeStyle');
+	 * (end)
+	 * 
+	 * Note that the key of the <mxStyleRegistry> entry for the function should
+	 * be used in string values, unless <mxGraphView.allowEval> is true, in
+	 * which case you can also use mxEdgeStyle.MyStyle for the value in the
+	 * cell style above.
+	 * 
+	 * Or it can be used for all edges in the graph as follows:
+	 * 
+	 * (code)
+	 * var style = graph.getStylesheet().getDefaultEdgeStyle();
+	 * style[mxConstants.STYLE_EDGE] = mxEdgeStyle.MyStyle;
+	 * (end)
+	 * 
+	 * Note that the object can be used directly when programmatically setting
+	 * the value, but the key in the <mxStyleRegistry> should be used when
+	 * setting the value via a key, value pair in a cell style.
+	 * 
+	 * Function: EntityRelation
+	 * 
+	 * Implements an entity relation style for edges (as used in database
+	 * schema diagrams). At the time the function is called, the result
+	 * array contains a placeholder (null) for the first absolute point,
+	 * that is, the point where the edge and source terminal are connected.
+	 * The implementation of the style then adds all intermediate waypoints
+	 * except for the last point, that is, the connection point between the
+	 * edge and the target terminal. The first ant the last point in the
+	 * result array are then replaced with mxPoints that take into account
+	 * the terminal's perimeter and next point on the edge.
+	 *
+	 * Parameters:
+	 * 
+	 * state - <mxCellState> that represents the edge to be updated.
+	 * source - <mxCellState> that represents the source terminal.
+	 * target - <mxCellState> that represents the target terminal.
+	 * points - List of relative control points.
+	 * result - Array of <mxPoints> that represent the actual points of the
+	 * edge.
+	 */
+	 EntityRelation: function (state, source, target, points, result)
+	 {
+		var view = state.view;
+	 	var graph = view.graph;
+	 	var segment = mxUtils.getValue(state.style,
+	 			mxConstants.STYLE_SEGMENT,
+	 			mxConstants.ENTITY_SEGMENT) * view.scale;
+	 	
+		var pts = state.absolutePoints;
+		var p0 = pts[0];
+		var pe = pts[pts.length-1];
+
+	 	var isSourceLeft = false;
+
+		if (p0 != null)
+		{
+			source = new mxCellState();
+			source.x = p0.x;
+			source.y = p0.y;
+		}
+		else if (source != null)
+		{
+			var constraint = mxUtils.getPortConstraints(source, state, true, mxConstants.DIRECTION_MASK_NONE);
+			
+			if (constraint != mxConstants.DIRECTION_MASK_NONE && constraint != mxConstants.DIRECTION_MASK_WEST +
+				mxConstants.DIRECTION_MASK_EAST)
+			{
+				isSourceLeft = constraint == mxConstants.DIRECTION_MASK_WEST;
+			}
+			else
+			{
+			 	var sourceGeometry = graph.getCellGeometry(source.cell);
+		
+			 	if (sourceGeometry.relative)
+			 	{
+			 		isSourceLeft = sourceGeometry.x <= 0.5;
+			 	}
+			 	else if (target != null)
+			 	{
+			 		isSourceLeft = target.x + target.width < source.x;
+			 	}
+			}
+		}
+		else
+		{
+			return;
+		}
+	 	
+	 	var isTargetLeft = true;
+
+		if (pe != null)
+		{
+			target = new mxCellState();
+			target.x = pe.x;
+			target.y = pe.y;
+		}
+		else if (target != null)
+	 	{
+			var constraint = mxUtils.getPortConstraints(target, state, false, mxConstants.DIRECTION_MASK_NONE);
+
+			if (constraint != mxConstants.DIRECTION_MASK_NONE && constraint != mxConstants.DIRECTION_MASK_WEST +
+				mxConstants.DIRECTION_MASK_EAST)
+			{
+				isTargetLeft = constraint == mxConstants.DIRECTION_MASK_WEST;
+			}
+			else
+			{
+			 	var targetGeometry = graph.getCellGeometry(target.cell);
+	
+			 	if (targetGeometry.relative)
+			 	{
+			 		isTargetLeft = targetGeometry.x <= 0.5;
+			 	}
+			 	else if (source != null)
+			 	{
+			 		isTargetLeft = source.x + source.width < target.x;
+			 	}
+			}
+	 	}
+		
+		if (source != null && target != null)
+		{
+			var x0 = (isSourceLeft) ? source.x : source.x + source.width;
+			var y0 = view.getRoutingCenterY(source);
+			
+			var xe = (isTargetLeft) ? target.x : target.x + target.width;
+			var ye = view.getRoutingCenterY(target);
+	
+			var seg = segment;
+	
+			var dx = (isSourceLeft) ? -seg : seg;
+			var dep = new mxPoint(x0 + dx, y0);
+					
+			dx = (isTargetLeft) ? -seg : seg;
+			var arr = new mxPoint(xe + dx, ye);
+	
+			// Adds intermediate points if both go out on same side
+			if (isSourceLeft == isTargetLeft)
+			{
+				var x = (isSourceLeft) ?
+					Math.min(x0, xe)-segment :
+					Math.max(x0, xe)+segment;
+	
+				result.push(new mxPoint(x, y0));
+				result.push(new mxPoint(x, ye));
+			}
+			else if ((dep.x < arr.x) == isSourceLeft)
+			{
+				var midY = y0 + (ye - y0) / 2;
+	
+				result.push(dep);
+				result.push(new mxPoint(dep.x, midY));
+				result.push(new mxPoint(arr.x, midY));
+				result.push(arr);
+			}
+			else
+			{
+				result.push(dep);
+				result.push(arr);
+			}
+		}
+	 },
+
+	 /**
+	 * Function: Loop
+	 * 
+	 * Implements a self-reference, aka. loop.
+	 */
+	Loop: function (state, source, target, points, result)
+	{
+		var pts = state.absolutePoints;
+		
+		var p0 = pts[0];
+		var pe = pts[pts.length-1];
+
+		if (p0 != null && pe != null)
+		{
+			if (points != null && points.length > 0)
+			{
+				for (var i = 0; i < points.length; i++)
+				{
+					var pt = points[i];
+					pt = state.view.transformControlPoint(state, pt);
+					result.push(new mxPoint(pt.x, pt.y));
+				}
+			}
+
+			return;
+		}
+		
+		if (source != null)
+		{
+			var view = state.view;
+			var graph = view.graph;
+			var pt = (points != null && points.length > 0) ? points[0] : null;
+
+			if (pt != null)
+			{
+				pt = view.transformControlPoint(state, pt);
+					
+				if (mxUtils.contains(source, pt.x, pt.y))
+				{
+					pt = null;
+				}
+			}
+			
+			var x = 0;
+			var dx = 0;
+			var y = 0;
+			var dy = 0;
+			
+		 	var seg = mxUtils.getValue(state.style, mxConstants.STYLE_SEGMENT,
+		 		graph.gridSize) * view.scale;
+			var dir = mxUtils.getValue(state.style, mxConstants.STYLE_DIRECTION,
+				mxConstants.DIRECTION_WEST);
+			
+			if (dir == mxConstants.DIRECTION_NORTH ||
+				dir == mxConstants.DIRECTION_SOUTH)
+			{
+				x = view.getRoutingCenterX(source);
+				dx = seg;
+			}
+			else
+			{
+				y = view.getRoutingCenterY(source);
+				dy = seg;
+			}
+			
+			if (pt == null ||
+				pt.x < source.x ||
+				pt.x > source.x + source.width)
+			{
+				if (pt != null)
+				{
+					x = pt.x;
+					dy = Math.max(Math.abs(y - pt.y), dy);
+				}
+				else
+				{
+					if (dir == mxConstants.DIRECTION_NORTH)
+					{
+						y = source.y - 2 * dx;
+					}
+					else if (dir == mxConstants.DIRECTION_SOUTH)
+					{
+						y = source.y + source.height + 2 * dx;
+					}
+					else if (dir == mxConstants.DIRECTION_EAST)
+					{
+						x = source.x - 2 * dy;
+					}
+					else
+					{
+						x = source.x + source.width + 2 * dy;
+					}
+				}
+			}
+			else if (pt != null)
+			{
+				x = view.getRoutingCenterX(source);
+				dx = Math.max(Math.abs(x - pt.x), dy);
+				y = pt.y;
+				dy = 0;
+			}
+			
+			result.push(new mxPoint(x - dx, y - dy));
+			result.push(new mxPoint(x + dx, y + dy));
+		}
+	},
+	
+	/**
+	 * Function: ElbowConnector
+	 * 
+	 * Uses either <SideToSide> or <TopToBottom> depending on the horizontal
+	 * flag in the cell style. <SideToSide> is used if horizontal is true or
+	 * unspecified. See <EntityRelation> for a description of the
+	 * parameters.
+	 */
+	ElbowConnector: function (state, source, target, points, result)
+	{
+		var pt = (points != null && points.length > 0) ? points[0] : null;
+
+		var vertical = false;
+		var horizontal = false;
+		
+		if (source != null && target != null)
+		{
+			if (pt != null)
+			{
+				var left = Math.min(source.x, target.x);
+				var right = Math.max(source.x + source.width,
+					target.x + target.width);
+	
+				var top = Math.min(source.y, target.y);
+				var bottom = Math.max(source.y + source.height,
+					target.y + target.height);
+
+				pt = state.view.transformControlPoint(state, pt);
+					
+				vertical = pt.y < top || pt.y > bottom;
+				horizontal = pt.x < left || pt.x > right;
+			}
+			else
+			{
+				var left = Math.max(source.x, target.x);
+				var right = Math.min(source.x + source.width,
+					target.x + target.width);
+					
+				vertical = left == right;
+				
+				if (!vertical)
+				{
+					var top = Math.max(source.y, target.y);
+					var bottom = Math.min(source.y + source.height,
+						target.y + target.height);
+						
+					horizontal = top == bottom;
+				}
+			}
+		}
+
+		if (!horizontal && (vertical ||
+			state.style[mxConstants.STYLE_ELBOW] == mxConstants.ELBOW_VERTICAL))
+		{
+			mxEdgeStyle.TopToBottom(state, source, target, points, result);
+		}
+		else
+		{
+			mxEdgeStyle.SideToSide(state, source, target, points, result);
+		}
+	},
+
+	/**
+	 * Function: SideToSide
+	 * 
+	 * Implements a vertical elbow edge. See <EntityRelation> for a description
+	 * of the parameters.
+	 */
+	SideToSide: function (state, source, target, points, result)
+	{
+		var view = state.view;
+		var pt = (points != null && points.length > 0) ? points[0] : null;
+		var pts = state.absolutePoints;
+		var p0 = pts[0];
+		var pe = pts[pts.length-1];
+		
+		if (pt != null)
+		{
+			pt = view.transformControlPoint(state, pt);
+		}
+		
+		if (p0 != null)
+		{
+			source = new mxCellState();
+			source.x = p0.x;
+			source.y = p0.y;
+		}
+		
+		if (pe != null)
+		{
+			target = new mxCellState();
+			target.x = pe.x;
+			target.y = pe.y;
+		}
+		
+		if (source != null && target != null)
+		{
+			var l = Math.max(source.x, target.x);
+			var r = Math.min(source.x + source.width,
+							 target.x + target.width);
+	
+			var x = (pt != null) ? pt.x : Math.round(r + (l - r) / 2);
+	
+			var y1 = view.getRoutingCenterY(source);
+			var y2 = view.getRoutingCenterY(target);
+	
+			if (pt != null)
+			{
+				if (pt.y >= source.y && pt.y <= source.y + source.height)
+				{
+					y1 = pt.y;
+				}
+				
+				if (pt.y >= target.y && pt.y <= target.y + target.height)
+				{
+					y2 = pt.y;
+				}
+			}
+			
+			if (!mxUtils.contains(target, x, y1) &&
+				!mxUtils.contains(source, x, y1))
+			{
+				result.push(new mxPoint(x,  y1));
+			}
+	
+			if (!mxUtils.contains(target, x, y2) &&
+				!mxUtils.contains(source, x, y2))
+			{
+				result.push(new mxPoint(x, y2));
+			}
+	
+			if (result.length == 1)
+			{
+				if (pt != null)
+				{
+					if (!mxUtils.contains(target, x, pt.y) &&
+						!mxUtils.contains(source, x, pt.y))
+					{
+						result.push(new mxPoint(x, pt.y));
+					}
+				}
+				else
+				{	
+					var t = Math.max(source.y, target.y);
+					var b = Math.min(source.y + source.height,
+							 target.y + target.height);
+						 
+					result.push(new mxPoint(x, t + (b - t) / 2));
+				}
+			}
+		}
+	},
+
+	/**
+	 * Function: TopToBottom
+	 * 
+	 * Implements a horizontal elbow edge. See <EntityRelation> for a
+	 * description of the parameters.
+	 */
+	TopToBottom: function(state, source, target, points, result)
+	{
+		var view = state.view;
+		var pt = (points != null && points.length > 0) ? points[0] : null;
+		var pts = state.absolutePoints;
+		var p0 = pts[0];
+		var pe = pts[pts.length-1];
+		
+		if (pt != null)
+		{
+			pt = view.transformControlPoint(state, pt);
+		}
+		
+		if (p0 != null)
+		{
+			source = new mxCellState();
+			source.x = p0.x;
+			source.y = p0.y;
+		}
+		
+		if (pe != null)
+		{
+			target = new mxCellState();
+			target.x = pe.x;
+			target.y = pe.y;
+		}
+
+		if (source != null && target != null)
+		{
+			var t = Math.max(source.y, target.y);
+			var b = Math.min(source.y + source.height,
+							 target.y + target.height);
+	
+			var x = view.getRoutingCenterX(source);
+			
+			if (pt != null &&
+				pt.x >= source.x &&
+				pt.x <= source.x + source.width)
+			{
+				x = pt.x;
+			}
+			
+			var y = (pt != null) ? pt.y : Math.round(b + (t - b) / 2);
+			
+			if (!mxUtils.contains(target, x, y) &&
+				!mxUtils.contains(source, x, y))
+			{
+				result.push(new mxPoint(x, y));						
+			}
+			
+			if (pt != null &&
+				pt.x >= target.x &&
+				pt.x <= target.x + target.width)
+			{
+				x = pt.x;
+			}
+			else
+			{
+				x = view.getRoutingCenterX(target);
+			}
+			
+			if (!mxUtils.contains(target, x, y) &&
+				!mxUtils.contains(source, x, y))
+			{
+				result.push(new mxPoint(x, y));						
+			}
+			
+			if (result.length == 1)
+			{
+				if (pt != null && result.length == 1)
+				{
+					if (!mxUtils.contains(target, pt.x, y) &&
+						!mxUtils.contains(source, pt.x, y))
+					{
+						result.push(new mxPoint(pt.x, y));
+					}
+				}
+				else
+				{
+					var l = Math.max(source.x, target.x);
+					var r = Math.min(source.x + source.width,
+							 target.x + target.width);
+						 
+					result.push(new mxPoint(l + (r - l) / 2, y));
+				}
+			}
+		}
+	},
+
+	/**
+	 * Function: SegmentConnector
+	 * 
+	 * Implements an orthogonal edge style. Use <mxEdgeSegmentHandler>
+	 * as an interactive handler for this style.
+	 */
+	SegmentConnector: function(state, source, target, hints, result)
+	{
+		// Creates array of all way- and terminalpoints
+		var pts = state.absolutePoints;
+		var tol = Math.max(1, state.view.scale);
+		
+		// Whether the first segment outgoing from the source end is horizontal
+		var lastPushed = (result.length > 0) ? result[0] : null;
+		var horizontal = true;
+		var hint = null;
+		
+		// Adds waypoints only if outside of tolerance
+		function pushPoint(pt)
+		{
+			if (lastPushed == null || Math.abs(lastPushed.x - pt.x) >= tol || Math.abs(lastPushed.y - pt.y) >= tol)
+			{
+				result.push(pt);
+				lastPushed = pt;
+			}
+			
+			return lastPushed;
+		};
+
+		// Adds the first point
+		var pt = pts[0];
+		
+		if (pt == null && source != null)
+		{
+			pt = new mxPoint(state.view.getRoutingCenterX(source), state.view.getRoutingCenterY(source));
+		}
+		else if (pt != null)
+		{
+			pt = pt.clone();
+		}
+		
+		pt.x = Math.round(pt.x);
+		pt.y = Math.round(pt.y);
+		
+		var lastInx = pts.length - 1;
+
+		// Adds the waypoints
+		if (hints != null && hints.length > 0)
+		{
+			// Converts all hints and removes nulls
+			var newHints = [];
+			
+			for (var i = 0; i < hints.length; i++)
+			{
+				var tmp = state.view.transformControlPoint(state, hints[i]);
+				
+				if (tmp != null)
+				{
+					tmp.x = Math.round(tmp.x);
+					tmp.y = Math.round(tmp.y);
+					newHints.push(tmp);
+				}
+			}
+			
+			if (newHints.length == 0)
+			{
+				return;
+			}
+			
+			hints = newHints;
+			
+			// Aligns source and target hint to fixed points
+			if (pt != null && hints[0] != null)
+			{
+				if (Math.abs(hints[0].x - pt.x) < tol)
+				{
+					hints[0].x = pt.x;
+				}
+				
+				if (Math.abs(hints[0].y - pt.y) < tol)
+				{
+					hints[0].y = pt.y;
+				}
+			}
+			
+			var pe = pts[lastInx];
+			
+			if (pe != null && hints[hints.length - 1] != null)
+			{
+				if (Math.abs(hints[hints.length - 1].x - pe.x) < tol)
+				{
+					hints[hints.length - 1].x = pe.x;
+				}
+				
+				if (Math.abs(hints[hints.length - 1].y - pe.y) < tol)
+				{
+					hints[hints.length - 1].y = pe.y;
+				}
+			}
+			
+			hint = hints[0];
+
+			var currentTerm = source;
+			var currentPt = pts[0];
+			var hozChan = false;
+			var vertChan = false;
+			var currentHint = hint;
+			
+			if (currentPt != null)
+			{
+				currentPt.x = Math.round(currentPt.x);
+				currentPt.y = Math.round(currentPt.y);
+				currentTerm = null;
+			}
+			
+			// Check for alignment with fixed points and with channels
+			// at source and target segments only
+			for (var i = 0; i < 2; i++)
+			{
+				var fixedVertAlign = currentPt != null && currentPt.x == currentHint.x;
+				var fixedHozAlign = currentPt != null && currentPt.y == currentHint.y;
+				
+				var inHozChan = currentTerm != null && (currentHint.y >= currentTerm.y &&
+						currentHint.y <= currentTerm.y + currentTerm.height);
+				var inVertChan = currentTerm != null && (currentHint.x >= currentTerm.x &&
+						currentHint.x <= currentTerm.x + currentTerm.width);
+
+				hozChan = fixedHozAlign || (currentPt == null && inHozChan);
+				vertChan = fixedVertAlign || (currentPt == null && inVertChan);
+				
+				// If the current hint falls in both the hor and vert channels in the case
+				// of a floating port, or if the hint is exactly co-incident with a 
+				// fixed point, ignore the source and try to work out the orientation
+				// from the target end
+				if (i==0 && ((hozChan && vertChan) || (fixedVertAlign && fixedHozAlign)))
+				{
+				}
+				else
+				{
+					if (currentPt != null && (!fixedHozAlign && !fixedVertAlign) && (inHozChan || inVertChan)) 
+					{
+						horizontal = inHozChan ? false : true;
+						break;
+					}
+			
+					if (vertChan || hozChan)
+					{
+						horizontal = hozChan;
+						
+						if (i == 1)
+						{
+							// Work back from target end
+							horizontal = hints.length % 2 == 0 ? hozChan : vertChan;
+						}
+	
+						break;
+					}
+				}
+				
+				currentTerm = target;
+				currentPt = pts[lastInx];
+				
+				if (currentPt != null)
+				{
+					currentPt.x = Math.round(currentPt.x);
+					currentPt.y = Math.round(currentPt.y);
+					currentTerm = null;
+				}
+				
+				currentHint = hints[hints.length - 1];
+				
+				if (fixedVertAlign && fixedHozAlign)
+				{
+					hints = hints.slice(1);
+				}
+			}
+
+			if (horizontal && ((pts[0] != null && pts[0].y != hint.y) ||
+				(pts[0] == null && source != null &&
+				(hint.y < source.y || hint.y > source.y + source.height))))
+			{
+				pushPoint(new mxPoint(pt.x, hint.y));
+			}
+			else if (!horizontal && ((pts[0] != null && pts[0].x != hint.x) ||
+					(pts[0] == null && source != null &&
+					(hint.x < source.x || hint.x > source.x + source.width))))
+			{
+				pushPoint(new mxPoint(hint.x, pt.y));
+			}
+			
+			if (horizontal)
+			{
+				pt.y = hint.y;
+			}
+			else
+			{
+				pt.x = hint.x;
+			}
+		
+			for (var i = 0; i < hints.length; i++)
+			{
+				horizontal = !horizontal;
+				hint = hints[i];
+				
+//				mxLog.show();
+//				mxLog.debug('hint', i, hint.x, hint.y);
+				
+				if (horizontal)
+				{
+					pt.y = hint.y;
+				}
+				else
+				{
+					pt.x = hint.x;
+				}
+		
+				pushPoint(pt.clone());
+			}
+		}
+		else
+		{
+			hint = pt;
+			// FIXME: First click in connect preview toggles orientation
+			horizontal = true;
+		}
+
+		// Adds the last point
+		pt = pts[lastInx];
+
+		if (pt == null && target != null)
+		{
+			pt = new mxPoint(state.view.getRoutingCenterX(target), state.view.getRoutingCenterY(target));
+		}
+		
+		if (pt != null)
+		{
+			pt.x = Math.round(pt.x);
+			pt.y = Math.round(pt.y);
+			
+			if (hint != null)
+			{
+				if (horizontal && ((pts[lastInx] != null && pts[lastInx].y != hint.y) ||
+					(pts[lastInx] == null && target != null &&
+					(hint.y < target.y || hint.y > target.y + target.height))))
+				{
+					pushPoint(new mxPoint(pt.x, hint.y));
+				}
+				else if (!horizontal && ((pts[lastInx] != null && pts[lastInx].x != hint.x) ||
+						(pts[lastInx] == null && target != null &&
+						(hint.x < target.x || hint.x > target.x + target.width))))
+				{
+					pushPoint(new mxPoint(hint.x, pt.y));
+				}
+			}
+		}
+		
+		// Removes bends inside the source terminal for floating ports
+		if (pts[0] == null && source != null)
+		{
+			while (result.length > 1 && result[1] != null &&
+				mxUtils.contains(source, result[1].x, result[1].y))
+			{
+				result.splice(1, 1);
+			}
+		}
+		
+		// Removes bends inside the target terminal
+		if (pts[lastInx] == null && target != null)
+		{
+			while (result.length > 1 && result[result.length - 1] != null &&
+				mxUtils.contains(target, result[result.length - 1].x, result[result.length - 1].y))
+			{
+				result.splice(result.length - 1, 1);
+			}
+		}
+		
+		// Removes last point if inside tolerance with end point
+		if (pe != null && result[result.length - 1] != null &&
+			Math.abs(pe.x - result[result.length - 1].x) < tol &&
+			Math.abs(pe.y - result[result.length - 1].y) < tol)
+		{
+			result.splice(result.length - 1, 1);
+			
+			// Lines up second last point in result with end point
+			if (result[result.length - 1] != null)
+			{
+				if (Math.abs(result[result.length - 1].x - pe.x) < tol)
+				{
+					result[result.length - 1].x = pe.x;
+				}
+				
+				if (Math.abs(result[result.length - 1].y - pe.y) < tol)
+				{
+					result[result.length - 1].y = pe.y;
+				}
+			}
+		}
+	},
+	
+	orthBuffer: 10,
+	
+	orthPointsFallback: true,
+
+	dirVectors: [ [ -1, 0 ],
+			[ 0, -1 ], [ 1, 0 ], [ 0, 1 ], [ -1, 0 ], [ 0, -1 ], [ 1, 0 ] ],
+
+	wayPoints1: [ [ 0, 0], [ 0, 0],  [ 0, 0], [ 0, 0], [ 0, 0],  [ 0, 0],
+	              [ 0, 0],  [ 0, 0], [ 0, 0],  [ 0, 0], [ 0, 0],  [ 0, 0] ],
+
+	routePatterns: [
+		[ [ 513, 2308, 2081, 2562 ], [ 513, 1090, 514, 2184, 2114, 2561 ],
+			[ 513, 1090, 514, 2564, 2184, 2562 ],
+			[ 513, 2308, 2561, 1090, 514, 2568, 2308 ] ],
+	[ [ 514, 1057, 513, 2308, 2081, 2562 ], [ 514, 2184, 2114, 2561 ],
+			[ 514, 2184, 2562, 1057, 513, 2564, 2184 ],
+			[ 514, 1057, 513, 2568, 2308, 2561 ] ],
+	[ [ 1090, 514, 1057, 513, 2308, 2081, 2562 ], [ 2114, 2561 ],
+			[ 1090, 2562, 1057, 513, 2564, 2184 ],
+			[ 1090, 514, 1057, 513, 2308, 2561, 2568 ] ],
+	[ [ 2081, 2562 ], [ 1057, 513, 1090, 514, 2184, 2114, 2561 ],
+			[ 1057, 513, 1090, 514, 2184, 2562, 2564 ],
+			[ 1057, 2561, 1090, 514, 2568, 2308 ] ] ],
+	
+	inlineRoutePatterns: [
+			[ null, [ 2114, 2568 ], null, null ],
+			[ null, [ 514, 2081, 2114, 2568 ] , null, null ],
+			[ null, [ 2114, 2561 ], null, null ],
+			[ [ 2081, 2562 ], [ 1057, 2114, 2568 ],
+					[ 2184, 2562 ],
+					null ] ],
+	vertexSeperations: [],
+
+	limits: [
+	       [ 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
+	       [ 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ],
+
+	LEFT_MASK: 32,
+
+	TOP_MASK: 64,
+
+	RIGHT_MASK: 128,
+
+	BOTTOM_MASK: 256,
+
+	LEFT: 1,
+
+	TOP: 2,
+
+	RIGHT: 4,
+
+	BOTTOM: 8,
+
+	// TODO remove magic numbers
+	SIDE_MASK: 480,
+	//mxEdgeStyle.LEFT_MASK | mxEdgeStyle.TOP_MASK | mxEdgeStyle.RIGHT_MASK
+	//| mxEdgeStyle.BOTTOM_MASK,
+
+	CENTER_MASK: 512,
+
+	SOURCE_MASK: 1024,
+
+	TARGET_MASK: 2048,
+
+	VERTEX_MASK: 3072,
+	// mxEdgeStyle.SOURCE_MASK | mxEdgeStyle.TARGET_MASK,
+	
+	getJettySize: function(state, source, target, points, isSource)
+	{
+		var value = mxUtils.getValue(state.style, (isSource) ? mxConstants.STYLE_SOURCE_JETTY_SIZE :
+			mxConstants.STYLE_TARGET_JETTY_SIZE, mxUtils.getValue(state.style,
+					mxConstants.STYLE_JETTY_SIZE, mxEdgeStyle.orthBuffer));
+		
+		if (value == 'auto')
+		{
+			// Computes the automatic jetty size
+			var type = mxUtils.getValue(state.style, (isSource) ? mxConstants.STYLE_STARTARROW : mxConstants.STYLE_ENDARROW, mxConstants.NONE);
+			
+			if (type != mxConstants.NONE)
+			{
+				var size = mxUtils.getNumber(state.style, (isSource) ? mxConstants.STYLE_STARTSIZE : mxConstants.STYLE_ENDSIZE, mxConstants.DEFAULT_MARKERSIZE);
+				value = Math.max(2, Math.ceil((size + mxEdgeStyle.orthBuffer) / mxEdgeStyle.orthBuffer)) * mxEdgeStyle.orthBuffer;
+			}
+			else
+			{
+				value = 2 * mxEdgeStyle.orthBuffer;
+			}
+		}
+		
+		return value;
+	},
+
+	/**
+	 * Function: OrthConnector
+	 * 
+	 * Implements a local orthogonal router between the given
+	 * cells.
+	 * 
+	 * Parameters:
+	 * 
+	 * state - <mxCellState> that represents the edge to be updated.
+	 * source - <mxCellState> that represents the source terminal.
+	 * target - <mxCellState> that represents the target terminal.
+	 * points - List of relative control points.
+	 * result - Array of <mxPoints> that represent the actual points of the
+	 * edge.
+	 * 
+	 */
+	OrthConnector: function(state, source, target, points, result)
+	{
+		var graph = state.view.graph;
+		var sourceEdge = source == null ? false : graph.getModel().isEdge(source.cell);
+		var targetEdge = target == null ? false : graph.getModel().isEdge(target.cell);
+
+		var pts = state.absolutePoints;
+		var p0 = pts[0];
+		var pe = pts[pts.length-1];
+
+		var sourceX = source != null ? source.x : p0.x;
+		var sourceY = source != null ? source.y : p0.y;
+		var sourceWidth = source != null ? source.width : 0;
+		var sourceHeight = source != null ? source.height : 0;
+		
+		var targetX = target != null ? target.x : pe.x;
+		var targetY = target != null ? target.y : pe.y;
+		var targetWidth = target != null ? target.width : 0;
+		var targetHeight = target != null ? target.height : 0;
+
+		var scaledSourceBuffer = state.view.scale * mxEdgeStyle.getJettySize(state, source, target, points, true);
+		var scaledTargetBuffer = state.view.scale * mxEdgeStyle.getJettySize(state, source, target, points, false);
+		
+		// Workaround for loop routing within buffer zone
+		if (source != null && target == source)
+		{
+			scaledTargetBuffer = Math.max(scaledSourceBuffer, scaledTargetBuffer);
+			scaledSourceBuffer = scaledTargetBuffer;
+		}
+		
+		var totalBuffer = scaledTargetBuffer + scaledSourceBuffer;
+		var tooShort = false;
+		
+		// Checks minimum distance for fixed points and falls back to segment connector
+		if (p0 != null && pe != null)
+		{
+			var dx = pe.x - p0.x;
+			var dy = pe.y - p0.y;
+			
+			tooShort = dx * dx + dy * dy < totalBuffer * totalBuffer;
+		}
+
+		if (tooShort || (mxEdgeStyle.orthPointsFallback && (points != null &&
+			points.length > 0)) || sourceEdge || targetEdge)
+		{
+			mxEdgeStyle.SegmentConnector(state, source, target, points, result);
+			
+			return;
+		}
+
+		// Determine the side(s) of the source and target vertices
+		// that the edge may connect to
+		// portConstraint [source, target]
+		var portConstraint = [mxConstants.DIRECTION_MASK_ALL, mxConstants.DIRECTION_MASK_ALL];
+		var rotation = 0;
+		
+		if (source != null)
+		{
+			portConstraint[0] = mxUtils.getPortConstraints(source, state, true, 
+					mxConstants.DIRECTION_MASK_ALL);
+			rotation = mxUtils.getValue(source.style, mxConstants.STYLE_ROTATION, 0);
+			
+			if (rotation != 0)
+			{
+				var newRect = mxUtils.getBoundingBox(new mxRectangle(sourceX, sourceY, sourceWidth, sourceHeight), rotation);
+				sourceX = newRect.x; 
+				sourceY = newRect.y;
+				sourceWidth = newRect.width;
+				sourceHeight = newRect.height;
+			}
+		}
+
+		if (target != null)
+		{
+			portConstraint[1] = mxUtils.getPortConstraints(target, state, false,
+				mxConstants.DIRECTION_MASK_ALL);
+			rotation = mxUtils.getValue(target.style, mxConstants.STYLE_ROTATION, 0);
+
+			if (rotation != 0)
+			{
+				var newRect = mxUtils.getBoundingBox(new mxRectangle(targetX, targetY, targetWidth, targetHeight), rotation);
+				targetX = newRect.x;
+				targetY = newRect.y;
+				targetWidth = newRect.width;
+				targetHeight = newRect.height;
+			}
+		}
+
+		// Avoids floating point number errors
+		sourceX = Math.round(sourceX * 10) / 10;
+		sourceY = Math.round(sourceY * 10) / 10;
+		sourceWidth = Math.round(sourceWidth * 10) / 10;
+		sourceHeight = Math.round(sourceHeight * 10) / 10;
+		
+		targetX = Math.round(targetX * 10) / 10;
+		targetY = Math.round(targetY * 10) / 10;
+		targetWidth = Math.round(targetWidth * 10) / 10;
+		targetHeight = Math.round(targetHeight * 10) / 10;
+		
+		var dir = [0, 0];
+
+		// Work out which faces of the vertices present against each other
+		// in a way that would allow a 3-segment connection if port constraints
+		// permitted.
+		// geo -> [source, target] [x, y, width, height]
+		var geo = [ [sourceX, sourceY, sourceWidth, sourceHeight] ,
+		            [targetX, targetY, targetWidth, targetHeight] ];
+		var buffer = [scaledSourceBuffer, scaledTargetBuffer];
+
+		for (var i = 0; i < 2; i++)
+		{
+			mxEdgeStyle.limits[i][1] = geo[i][0] - buffer[i];
+			mxEdgeStyle.limits[i][2] = geo[i][1] - buffer[i];
+			mxEdgeStyle.limits[i][4] = geo[i][0] + geo[i][2] + buffer[i];
+			mxEdgeStyle.limits[i][8] = geo[i][1] + geo[i][3] + buffer[i];
+		}
+		
+		// Work out which quad the target is in
+		var sourceCenX = geo[0][0] + geo[0][2] / 2.0;
+		var sourceCenY = geo[0][1] + geo[0][3] / 2.0;
+		var targetCenX = geo[1][0] + geo[1][2] / 2.0;
+		var targetCenY = geo[1][1] + geo[1][3] / 2.0;
+		
+		var dx = sourceCenX - targetCenX;
+		var dy = sourceCenY - targetCenY;
+
+		var quad = 0;
+
+		if (dx < 0)
+		{
+			if (dy < 0)
+			{
+				quad = 2;
+			}
+			else
+			{
+				quad = 1;
+			}
+		}
+		else
+		{
+			if (dy <= 0)
+			{
+				quad = 3;
+				
+				// Special case on x = 0 and negative y
+				if (dx == 0)
+				{
+					quad = 2;
+				}
+			}
+		}
+
+		// Check for connection constraints
+		var currentTerm = null;
+		
+		if (source != null)
+		{
+			currentTerm = p0;
+		}
+
+		var constraint = [ [0.5, 0.5] , [0.5, 0.5] ];
+
+		for (var i = 0; i < 2; i++)
+		{
+			if (currentTerm != null)
+			{
+				constraint[i][0] = (currentTerm.x - geo[i][0]) / geo[i][2];
+				
+				if (Math.abs(currentTerm.x - geo[i][0]) <= 1)
+				{
+					dir[i] = mxConstants.DIRECTION_MASK_WEST;
+				}
+				else if (Math.abs(currentTerm.x - geo[i][0] - geo[i][2]) <= 1)
+				{
+					dir[i] = mxConstants.DIRECTION_MASK_EAST;
+				}
+
+				constraint[i][1] = (currentTerm.y - geo[i][1]) / geo[i][3];
+
+				if (Math.abs(currentTerm.y - geo[i][1]) <= 1)
+				{
+					dir[i] = mxConstants.DIRECTION_MASK_NORTH;
+				}
+				else if (Math.abs(currentTerm.y - geo[i][1] - geo[i][3]) <= 1)
+				{
+					dir[i] = mxConstants.DIRECTION_MASK_SOUTH;
+				}
+			}
+
+			currentTerm = null;
+			
+			if (target != null)
+			{
+				currentTerm = pe;
+			}
+		}
+
+		var sourceTopDist = geo[0][1] - (geo[1][1] + geo[1][3]);
+		var sourceLeftDist = geo[0][0] - (geo[1][0] + geo[1][2]);
+		var sourceBottomDist = geo[1][1] - (geo[0][1] + geo[0][3]);
+		var sourceRightDist = geo[1][0] - (geo[0][0] + geo[0][2]);
+
+		mxEdgeStyle.vertexSeperations[1] = Math.max(sourceLeftDist - totalBuffer, 0);
+		mxEdgeStyle.vertexSeperations[2] = Math.max(sourceTopDist - totalBuffer, 0);
+		mxEdgeStyle.vertexSeperations[4] = Math.max(sourceBottomDist - totalBuffer, 0);
+		mxEdgeStyle.vertexSeperations[3] = Math.max(sourceRightDist - totalBuffer, 0);
+				
+		//==============================================================
+		// Start of source and target direction determination
+
+		// Work through the preferred orientations by relative positioning
+		// of the vertices and list them in preferred and available order
+		
+		var dirPref = [];
+		var horPref = [];
+		var vertPref = [];
+
+		horPref[0] = (sourceLeftDist >= sourceRightDist) ? mxConstants.DIRECTION_MASK_WEST
+				: mxConstants.DIRECTION_MASK_EAST;
+		vertPref[0] = (sourceTopDist >= sourceBottomDist) ? mxConstants.DIRECTION_MASK_NORTH
+				: mxConstants.DIRECTION_MASK_SOUTH;
+
+		horPref[1] = mxUtils.reversePortConstraints(horPref[0]);
+		vertPref[1] = mxUtils.reversePortConstraints(vertPref[0]);
+		
+		var preferredHorizDist = sourceLeftDist >= sourceRightDist ? sourceLeftDist
+				: sourceRightDist;
+		var preferredVertDist = sourceTopDist >= sourceBottomDist ? sourceTopDist
+				: sourceBottomDist;
+
+		var prefOrdering = [ [0, 0] , [0, 0] ];
+		var preferredOrderSet = false;
+
+		// If the preferred port isn't available, switch it
+		for (var i = 0; i < 2; i++)
+		{
+			if (dir[i] != 0x0)
+			{
+				continue;
+			}
+
+			if ((horPref[i] & portConstraint[i]) == 0)
+			{
+				horPref[i] = mxUtils.reversePortConstraints(horPref[i]);
+			}
+
+			if ((vertPref[i] & portConstraint[i]) == 0)
+			{
+				vertPref[i] = mxUtils
+						.reversePortConstraints(vertPref[i]);
+			}
+
+			prefOrdering[i][0] = vertPref[i];
+			prefOrdering[i][1] = horPref[i];
+		}
+
+		if (preferredVertDist > 0
+				&& preferredHorizDist > 0)
+		{
+			// Possibility of two segment edge connection
+			if (((horPref[0] & portConstraint[0]) > 0)
+					&& ((vertPref[1] & portConstraint[1]) > 0))
+			{
+				prefOrdering[0][0] = horPref[0];
+				prefOrdering[0][1] = vertPref[0];
+				prefOrdering[1][0] = vertPref[1];
+				prefOrdering[1][1] = horPref[1];
+				preferredOrderSet = true;
+			}
+			else if (((vertPref[0] & portConstraint[0]) > 0)
+					&& ((horPref[1] & portConstraint[1]) > 0))
+			{
+				prefOrdering[0][0] = vertPref[0];
+				prefOrdering[0][1] = horPref[0];
+				prefOrdering[1][0] = horPref[1];
+				prefOrdering[1][1] = vertPref[1];
+				preferredOrderSet = true;
+			}
+		}
+		
+		if (preferredVertDist > 0 && !preferredOrderSet)
+		{
+			prefOrdering[0][0] = vertPref[0];
+			prefOrdering[0][1] = horPref[0];
+			prefOrdering[1][0] = vertPref[1];
+			prefOrdering[1][1] = horPref[1];
+			preferredOrderSet = true;
+
+		}
+		
+		if (preferredHorizDist > 0 && !preferredOrderSet)
+		{
+			prefOrdering[0][0] = horPref[0];
+			prefOrdering[0][1] = vertPref[0];
+			prefOrdering[1][0] = horPref[1];
+			prefOrdering[1][1] = vertPref[1];
+			preferredOrderSet = true;
+		}
+
+		// The source and target prefs are now an ordered list of
+		// the preferred port selections
+		// It the list can contain gaps, compact it
+
+		for (var i = 0; i < 2; i++)
+		{
+			if (dir[i] != 0x0)
+			{
+				continue;
+			}
+
+			if ((prefOrdering[i][0] & portConstraint[i]) == 0)
+			{
+				prefOrdering[i][0] = prefOrdering[i][1];
+			}
+
+			dirPref[i] = prefOrdering[i][0] & portConstraint[i];
+			dirPref[i] |= (prefOrdering[i][1] & portConstraint[i]) << 8;
+			dirPref[i] |= (prefOrdering[1 - i][i] & portConstraint[i]) << 16;
+			dirPref[i] |= (prefOrdering[1 - i][1 - i] & portConstraint[i]) << 24;
+
+			if ((dirPref[i] & 0xF) == 0)
+			{
+				dirPref[i] = dirPref[i] << 8;
+			}
+			
+			if ((dirPref[i] & 0xF00) == 0)
+			{
+				dirPref[i] = (dirPref[i] & 0xF) | dirPref[i] >> 8;
+			}
+			
+			if ((dirPref[i] & 0xF0000) == 0)
+			{
+				dirPref[i] = (dirPref[i] & 0xFFFF)
+						| ((dirPref[i] & 0xF000000) >> 8);
+			}
+
+			dir[i] = dirPref[i] & 0xF;
+
+			if (portConstraint[i] == mxConstants.DIRECTION_MASK_WEST
+					|| portConstraint[i] == mxConstants.DIRECTION_MASK_NORTH
+					|| portConstraint[i] == mxConstants.DIRECTION_MASK_EAST
+					|| portConstraint[i] == mxConstants.DIRECTION_MASK_SOUTH)
+			{
+				dir[i] = portConstraint[i];
+			}
+		}
+
+		//==============================================================
+		// End of source and target direction determination
+
+		var sourceIndex = dir[0] == mxConstants.DIRECTION_MASK_EAST ? 3
+				: dir[0];
+		var targetIndex = dir[1] == mxConstants.DIRECTION_MASK_EAST ? 3
+				: dir[1];
+
+		sourceIndex -= quad;
+		targetIndex -= quad;
+
+		if (sourceIndex < 1)
+		{
+			sourceIndex += 4;
+		}
+		
+		if (targetIndex < 1)
+		{
+			targetIndex += 4;
+		}
+
+		var routePattern = mxEdgeStyle.routePatterns[sourceIndex - 1][targetIndex - 1];
+
+		mxEdgeStyle.wayPoints1[0][0] = geo[0][0];
+		mxEdgeStyle.wayPoints1[0][1] = geo[0][1];
+
+		switch (dir[0])
+		{
+			case mxConstants.DIRECTION_MASK_WEST:
+				mxEdgeStyle.wayPoints1[0][0] -= scaledSourceBuffer;
+				mxEdgeStyle.wayPoints1[0][1] += constraint[0][1] * geo[0][3];
+				break;
+			case mxConstants.DIRECTION_MASK_SOUTH:
+				mxEdgeStyle.wayPoints1[0][0] += constraint[0][0] * geo[0][2];
+				mxEdgeStyle.wayPoints1[0][1] += geo[0][3] + scaledSourceBuffer;
+				break;
+			case mxConstants.DIRECTION_MASK_EAST:
+				mxEdgeStyle.wayPoints1[0][0] += geo[0][2] + scaledSourceBuffer;
+				mxEdgeStyle.wayPoints1[0][1] += constraint[0][1] * geo[0][3];
+				break;
+			case mxConstants.DIRECTION_MASK_NORTH:
+				mxEdgeStyle.wayPoints1[0][0] += constraint[0][0] * geo[0][2];
+				mxEdgeStyle.wayPoints1[0][1] -= scaledSourceBuffer;
+				break;
+		}
+
+		var currentIndex = 0;
+
+		// Orientation, 0 horizontal, 1 vertical
+		var lastOrientation = (dir[0] & (mxConstants.DIRECTION_MASK_EAST | mxConstants.DIRECTION_MASK_WEST)) > 0 ? 0
+				: 1;
+		var initialOrientation = lastOrientation;
+		var currentOrientation = 0;
+
+		for (var i = 0; i < routePattern.length; i++)
+		{
+			var nextDirection = routePattern[i] & 0xF;
+
+			// Rotate the index of this direction by the quad
+			// to get the real direction
+			var directionIndex = nextDirection == mxConstants.DIRECTION_MASK_EAST ? 3
+					: nextDirection;
+
+			directionIndex += quad;
+
+			if (directionIndex > 4)
+			{
+				directionIndex -= 4;
+			}
+
+			var direction = mxEdgeStyle.dirVectors[directionIndex - 1];
+
+			currentOrientation = (directionIndex % 2 > 0) ? 0 : 1;
+			// Only update the current index if the point moved
+			// in the direction of the current segment move,
+			// otherwise the same point is moved until there is 
+			// a segment direction change
+			if (currentOrientation != lastOrientation)
+			{
+				currentIndex++;
+				// Copy the previous way point into the new one
+				// We can't base the new position on index - 1
+				// because sometime elbows turn out not to exist,
+				// then we'd have to rewind.
+				mxEdgeStyle.wayPoints1[currentIndex][0] = mxEdgeStyle.wayPoints1[currentIndex - 1][0];
+				mxEdgeStyle.wayPoints1[currentIndex][1] = mxEdgeStyle.wayPoints1[currentIndex - 1][1];
+			}
+
+			var tar = (routePattern[i] & mxEdgeStyle.TARGET_MASK) > 0;
+			var sou = (routePattern[i] & mxEdgeStyle.SOURCE_MASK) > 0;
+			var side = (routePattern[i] & mxEdgeStyle.SIDE_MASK) >> 5;
+			side = side << quad;
+
+			if (side > 0xF)
+			{
+				side = side >> 4;
+			}
+
+			var center = (routePattern[i] & mxEdgeStyle.CENTER_MASK) > 0;
+
+			if ((sou || tar) && side < 9)
+			{
+				var limit = 0;
+				var souTar = sou ? 0 : 1;
+
+				if (center && currentOrientation == 0)
+				{
+					limit = geo[souTar][0] + constraint[souTar][0] * geo[souTar][2];
+				}
+				else if (center)
+				{
+					limit = geo[souTar][1] + constraint[souTar][1] * geo[souTar][3];
+				}
+				else
+				{
+					limit = mxEdgeStyle.limits[souTar][side];
+				}
+				
+				if (currentOrientation == 0)
+				{
+					var lastX = mxEdgeStyle.wayPoints1[currentIndex][0];
+					var deltaX = (limit - lastX) * direction[0];
+
+					if (deltaX > 0)
+					{
+						mxEdgeStyle.wayPoints1[currentIndex][0] += direction[0]
+								* deltaX;
+					}
+				}
+				else
+				{
+					var lastY = mxEdgeStyle.wayPoints1[currentIndex][1];
+					var deltaY = (limit - lastY) * direction[1];
+
+					if (deltaY > 0)
+					{
+						mxEdgeStyle.wayPoints1[currentIndex][1] += direction[1]
+								* deltaY;
+					}
+				}
+			}
+
+			else if (center)
+			{
+				// Which center we're travelling to depend on the current direction
+				mxEdgeStyle.wayPoints1[currentIndex][0] += direction[0]
+						* Math.abs(mxEdgeStyle.vertexSeperations[directionIndex] / 2);
+				mxEdgeStyle.wayPoints1[currentIndex][1] += direction[1]
+						* Math.abs(mxEdgeStyle.vertexSeperations[directionIndex] / 2);
+			}
+
+			if (currentIndex > 0
+					&& mxEdgeStyle.wayPoints1[currentIndex][currentOrientation] == mxEdgeStyle.wayPoints1[currentIndex - 1][currentOrientation])
+			{
+				currentIndex--;
+			}
+			else
+			{
+				lastOrientation = currentOrientation;
+			}
+		}
+
+		for (var i = 0; i <= currentIndex; i++)
+		{
+			if (i == currentIndex)
+			{
+				// Last point can cause last segment to be in
+				// same direction as jetty/approach. If so,
+				// check the number of points is consistent
+				// with the relative orientation of source and target
+				// jx. Same orientation requires an even
+				// number of turns (points), different requires
+				// odd.
+				var targetOrientation = (dir[1] & (mxConstants.DIRECTION_MASK_EAST | mxConstants.DIRECTION_MASK_WEST)) > 0 ? 0
+						: 1;
+				var sameOrient = targetOrientation == initialOrientation ? 0 : 1;
+
+				// (currentIndex + 1) % 2 is 0 for even number of points,
+				// 1 for odd
+				if (sameOrient != (currentIndex + 1) % 2)
+				{
+					// The last point isn't required
+					break;
+				}
+			}
+			
+			result.push(new mxPoint(Math.round(mxEdgeStyle.wayPoints1[i][0]), Math.round(mxEdgeStyle.wayPoints1[i][1])));
+		}
+		
+		// Removes duplicates
+		var index = 1;
+		
+		while (index < result.length)
+		{
+			if (result[index - 1] == null || result[index] == null ||
+				result[index - 1].x != result[index].x ||
+				result[index - 1].y != result[index].y)
+			{
+				index++;
+			}
+			else
+			{
+				result.splice(index, 1);
+			}
+		}
+	},
+	
+	getRoutePattern: function(dir, quad, dx, dy)
+	{
+		var sourceIndex = dir[0] == mxConstants.DIRECTION_MASK_EAST ? 3
+				: dir[0];
+		var targetIndex = dir[1] == mxConstants.DIRECTION_MASK_EAST ? 3
+				: dir[1];
+
+		sourceIndex -= quad;
+		targetIndex -= quad;
+
+		if (sourceIndex < 1)
+		{
+			sourceIndex += 4;
+		}
+		if (targetIndex < 1)
+		{
+			targetIndex += 4;
+		}
+
+		var result = routePatterns[sourceIndex - 1][targetIndex - 1];
+
+		if (dx == 0 || dy == 0)
+		{
+			if (inlineRoutePatterns[sourceIndex - 1][targetIndex - 1] != null)
+			{
+				result = inlineRoutePatterns[sourceIndex - 1][targetIndex - 1];
+			}
+		}
+
+		return result;
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/view/mxGraph.js b/airavata-kubernetes/workflow-composer/src/js/view/mxGraph.js
new file mode 100644
index 0000000..6d2ab86
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/view/mxGraph.js
@@ -0,0 +1,12768 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxGraph
+ *
+ * Extends <mxEventSource> to implement a graph component for
+ * the browser. This is the main class of the package. To activate
+ * panning and connections use <setPanning> and <setConnectable>.
+ * For rubberband selection you must create a new instance of
+ * <mxRubberband>. The following listeners are added to
+ * <mouseListeners> by default:
+ * 
+ * - <tooltipHandler>: <mxTooltipHandler> that displays tooltips
+ * - <panningHandler>: <mxPanningHandler> for panning and popup menus
+ * - <connectionHandler>: <mxConnectionHandler> for creating connections
+ * - <graphHandler>: <mxGraphHandler> for moving and cloning cells
+ * 
+ * These listeners will be called in the above order if they are enabled.
+ *
+ * Background Images:
+ * 
+ * To display a background image, set the image, image width and
+ * image height using <setBackgroundImage>. If one of the
+ * above values has changed then the <view>'s <mxGraphView.validate>
+ * should be invoked.
+ * 
+ * Cell Images:
+ * 
+ * To use images in cells, a shape must be specified in the default
+ * vertex style (or any named style). Possible shapes are
+ * <mxConstants.SHAPE_IMAGE> and <mxConstants.SHAPE_LABEL>.
+ * The code to change the shape used in the default vertex style,
+ * the following code is used:
+ * 
+ * (code)
+ * var style = graph.getStylesheet().getDefaultVertexStyle();
+ * style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_IMAGE;
+ * (end)
+ * 
+ * For the default vertex style, the image to be displayed can be
+ * specified in a cell's style using the <mxConstants.STYLE_IMAGE>
+ * key and the image URL as a value, for example:
+ * 
+ * (code)
+ * image=http://www.example.com/image.gif
+ * (end)
+ * 
+ * For a named style, the the stylename must be the first element
+ * of the cell style:
+ * 
+ * (code)
+ * stylename;image=http://www.example.com/image.gif
+ * (end)
+ * 
+ * A cell style can have any number of key=value pairs added, divided
+ * by a semicolon as follows:
+ * 
+ * (code)
+ * [stylename;|key=value;]
+ * (end)
+ *
+ * Labels:
+ * 
+ * The cell labels are defined by <getLabel> which uses <convertValueToString>
+ * if <labelsVisible> is true. If a label must be rendered as HTML markup, then
+ * <isHtmlLabel> should return true for the respective cell. If all labels
+ * contain HTML markup, <htmlLabels> can be set to true. NOTE: Enabling HTML
+ * labels carries a possible security risk (see the section on security in
+ * the manual).
+ * 
+ * If wrapping is needed for a label, then <isHtmlLabel> and <isWrapping> must
+ * return true for the cell whose label should be wrapped. See <isWrapping> for
+ * an example.
+ * 
+ * If clipping is needed to keep the rendering of a HTML label inside the
+ * bounds of its vertex, then <isClipping> should return true for the
+ * respective cell.
+ * 
+ * By default, edge labels are movable and vertex labels are fixed. This can be
+ * changed by setting <edgeLabelsMovable> and <vertexLabelsMovable>, or by
+ * overriding <isLabelMovable>.
+ *
+ * In-place Editing:
+ * 
+ * In-place editing is started with a doubleclick or by typing F2.
+ * Programmatically, <edit> is used to check if the cell is editable
+ * (<isCellEditable>) and call <startEditingAtCell>, which invokes
+ * <mxCellEditor.startEditing>. The editor uses the value returned
+ * by <getEditingValue> as the editing value.
+ * 
+ * After in-place editing, <labelChanged> is called, which invokes
+ * <mxGraphModel.setValue>, which in turn calls
+ * <mxGraphModel.valueForCellChanged> via <mxValueChange>.
+ * 
+ * The event that triggers in-place editing is passed through to the
+ * <cellEditor>, which may take special actions depending on the type of the
+ * event or mouse location, and is also passed to <getEditingValue>. The event
+ * is then passed back to the event processing functions which can perform
+ * specific actions based on the trigger event.
+ * 
+ * Tooltips:
+ * 
+ * Tooltips are implemented by <getTooltip>, which calls <getTooltipForCell>
+ * if a cell is under the mousepointer. The default implementation checks if
+ * the cell has a getTooltip function and calls it if it exists. Hence, in order
+ * to provide custom tooltips, the cell must provide a getTooltip function, or 
+ * one of the two above functions must be overridden.
+ * 
+ * Typically, for custom cell tooltips, the latter function is overridden as
+ * follows:
+ * 
+ * (code)
+ * graph.getTooltipForCell = function(cell)
+ * {
+ *   var label = this.convertValueToString(cell);
+ *   return 'Tooltip for '+label;
+ * }
+ * (end)
+ * 
+ * When using a config file, the function is overridden in the mxGraph section
+ * using the following entry:
+ * 
+ * (code)
+ * <add as="getTooltipForCell"><![CDATA[
+ *   function(cell)
+ *   {
+ *     var label = this.convertValueToString(cell);
+ *     return 'Tooltip for '+label;
+ *   }
+ * ]]></add>
+ * (end)
+ * 
+ * "this" refers to the graph in the implementation, so for example to check if 
+ * a cell is an edge, you use this.getModel().isEdge(cell)
+ *
+ * For replacing the default implementation of <getTooltipForCell> (rather than 
+ * replacing the function on a specific instance), the following code should be 
+ * used after loading the JavaScript files, but before creating a new mxGraph 
+ * instance using <mxGraph>:
+ * 
+ * (code)
+ * mxGraph.prototype.getTooltipForCell = function(cell)
+ * {
+ *   var label = this.convertValueToString(cell);
+ *   return 'Tooltip for '+label;
+ * }
+ * (end)
+ * 
+ * Shapes & Styles:
+ * 
+ * The implementation of new shapes is demonstrated in the examples. We'll assume
+ * that we have implemented a custom shape with the name BoxShape which we want
+ * to use for drawing vertices. To use this shape, it must first be registered in
+ * the cell renderer as follows:
+ * 
+ * (code)
+ * mxCellRenderer.registerShape('box', BoxShape);
+ * (end)
+ * 
+ * The code registers the BoxShape constructor under the name box in the cell
+ * renderer of the graph. The shape can now be referenced using the shape-key in
+ * a style definition. (The cell renderer contains a set of additional shapes,
+ * namely one for each constant with a SHAPE-prefix in <mxConstants>.)
+ *
+ * Styles are a collection of key, value pairs and a stylesheet is a collection
+ * of named styles. The names are referenced by the cellstyle, which is stored
+ * in <mxCell.style> with the following format: [stylename;|key=value;]. The
+ * string is resolved to a collection of key, value pairs, where the keys are
+ * overridden with the values in the string.
+ *
+ * When introducing a new shape, the name under which the shape is registered
+ * must be used in the stylesheet. There are three ways of doing this:
+ * 
+ *   - By changing the default style, so that all vertices will use the new
+ * 		shape
+ *   - By defining a new style, so that only vertices with the respective
+ * 		cellstyle will use the new shape
+ *   - By using shape=box in the cellstyle's optional list of key, value pairs
+ * 		to be overridden
+ *
+ * In the first case, the code to fetch and modify the default style for
+ * vertices is as follows:
+ * 
+ * (code)
+ * var style = graph.getStylesheet().getDefaultVertexStyle();
+ * style[mxConstants.STYLE_SHAPE] = 'box';
+ * (end)
+ * 
+ * The code takes the default vertex style, which is used for all vertices that
+ * do not have a specific cellstyle, and modifies the value for the shape-key
+ * in-place to use the new BoxShape for drawing vertices. This is done by
+ * assigning the box value in the second line, which refers to the name of the
+ * BoxShape in the cell renderer.
+ * 
+ * In the second case, a collection of key, value pairs is created and then
+ * added to the stylesheet under a new name. In order to distinguish the
+ * shapename and the stylename we'll use boxstyle for the stylename:
+ * 
+ * (code)
+ * var style = new Object();
+ * style[mxConstants.STYLE_SHAPE] = 'box';
+ * style[mxConstants.STYLE_STROKECOLOR] = '#000000';
+ * style[mxConstants.STYLE_FONTCOLOR] = '#000000';
+ * graph.getStylesheet().putCellStyle('boxstyle', style);
+ * (end)
+ * 
+ * The code adds a new style with the name boxstyle to the stylesheet. To use
+ * this style with a cell, it must be referenced from the cellstyle as follows:
+ * 
+ * (code)
+ * var vertex = graph.insertVertex(parent, null, 'Hello, World!', 20, 20, 80, 20,
+ * 				'boxstyle');
+ * (end)
+ * 
+ * To summarize, each new shape must be registered in the <mxCellRenderer> with
+ * a unique name. That name is then used as the value of the shape-key in a
+ * default or custom style. If there are multiple custom shapes, then there
+ * should be a separate style for each shape.
+ * 
+ * Inheriting Styles:
+ * 
+ * For fill-, stroke-, gradient- and indicatorColors special keywords can be
+ * used. The inherit keyword for one of these colors will inherit the color
+ * for the same key from the parent cell. The swimlane keyword does the same,
+ * but inherits from the nearest swimlane in the ancestor hierarchy. Finally,
+ * the indicated keyword will use the color of the indicator as the color for
+ * the given key.
+ * 
+ * Scrollbars:
+ * 
+ * The <containers> overflow CSS property defines if scrollbars are used to
+ * display the graph. For values of 'auto' or 'scroll', the scrollbars will
+ * be shown. Note that the <resizeContainer> flag is normally not used
+ * together with scrollbars, as it will resize the container to match the
+ * size of the graph after each change.
+ * 
+ * Multiplicities and Validation:
+ * 
+ * To control the possible connections in mxGraph, <getEdgeValidationError> is
+ * used. The default implementation of the function uses <multiplicities>,
+ * which is an array of <mxMultiplicity>. Using this class allows to establish
+ * simple multiplicities, which are enforced by the graph.
+ * 
+ * The <mxMultiplicity> uses <mxCell.is> to determine for which terminals it
+ * applies. The default implementation of <mxCell.is> works with DOM nodes (XML
+ * nodes) and checks if the given type parameter matches the nodeName of the
+ * node (case insensitive). Optionally, an attributename and value can be
+ * specified which are also checked.
+ * 
+ * <getEdgeValidationError> is called whenever the connectivity of an edge
+ * changes. It returns an empty string or an error message if the edge is
+ * invalid or null if the edge is valid. If the returned string is not empty
+ * then it is displayed as an error message.
+ * 
+ * <mxMultiplicity> allows to specify the multiplicity between a terminal and
+ * its possible neighbors. For example, if any rectangle may only be connected
+ * to, say, a maximum of two circles you can add the following rule to
+ * <multiplicities>:
+ * 
+ * (code)
+ * graph.multiplicities.push(new mxMultiplicity(
+ *   true, 'rectangle', null, null, 0, 2, ['circle'],
+ *   'Only 2 targets allowed',
+ *   'Only shape targets allowed'));
+ * (end)
+ * 
+ * This will display the first error message whenever a rectangle is connected
+ * to more than two circles and the second error message if a rectangle is
+ * connected to anything but a circle.
+ * 
+ * For certain multiplicities, such as a minimum of 1 connection, which cannot
+ * be enforced at cell creation time (unless the cell is created together with
+ * the connection), mxGraph offers <validate> which checks all multiplicities
+ * for all cells and displays the respective error messages in an overlay icon
+ * on the cells.
+ * 
+ * If a cell is collapsed and contains validation errors, a respective warning
+ * icon is attached to the collapsed cell.
+ * 
+ * Auto-Layout:
+ * 
+ * For automatic layout, the <getLayout> hook is provided in <mxLayoutManager>.
+ * It can be overridden to return a layout algorithm for the children of a
+ * given cell.
+ * 
+ * Unconnected edges:
+ * 
+ * The default values for all switches are designed to meet the requirements of
+ * general diagram drawing applications. A very typical set of settings to
+ * avoid edges that are not connected is the following:
+ * 
+ * (code)
+ * graph.setAllowDanglingEdges(false);
+ * graph.setDisconnectOnMove(false);
+ * (end)
+ * 
+ * Setting the <cloneInvalidEdges> switch to true is optional. This switch
+ * controls if edges are inserted after a copy, paste or clone-drag if they are
+ * invalid. For example, edges are invalid if copied or control-dragged without 
+ * having selected the corresponding terminals and allowDanglingEdges is
+ * false, in which case the edges will not be cloned if the switch is false.
+ * 
+ * Output:
+ * 
+ * To produce an XML representation for a diagram, the following code can be
+ * used.
+ * 
+ * (code)
+ * var enc = new mxCodec(mxUtils.createXmlDocument());
+ * var node = enc.encode(graph.getModel());
+ * (end)
+ * 
+ * This will produce an XML node than can be handled using the DOM API or
+ * turned into a string representation using the following code:
+ * 
+ * (code)
+ * var xml = mxUtils.getXml(node);
+ * (end)
+ * 
+ * To obtain a formatted string, mxUtils.getPrettyXml can be used instead.
+ * 
+ * This string can now be stored in a local persistent storage (for example
+ * using Google Gears) or it can be passed to a backend using mxUtils.post as
+ * follows. The url variable is the URL of the Java servlet, PHP page or HTTP
+ * handler, depending on the server.
+ * 
+ * (code)
+ * var xmlString = encodeURIComponent(mxUtils.getXml(node));
+ * mxUtils.post(url, 'xml='+xmlString, function(req)
+ * {
+ *   // Process server response using req of type mxXmlRequest
+ * });
+ * (end)
+ * 
+ * Input:
+ * 
+ * To load an XML representation of a diagram into an existing graph object
+ * mxUtils.load can be used as follows. The url variable is the URL of the Java
+ * servlet, PHP page or HTTP handler that produces the XML string.
+ * 
+ * (code)
+ * var xmlDoc = mxUtils.load(url).getXml();
+ * var node = xmlDoc.documentElement;
+ * var dec = new mxCodec(node.ownerDocument);
+ * dec.decode(node, graph.getModel());
+ * (end)
+ * 
+ * For creating a page that loads the client and a diagram using a single
+ * request please refer to the deployment examples in the backends.
+ * 
+ * Functional dependencies:
+ * 
+ * (see images/callgraph.png)
+ * 
+ * Resources:
+ *
+ * resources/graph - Language resources for mxGraph
+ *
+ * Group: Events
+ * 
+ * Event: mxEvent.ROOT
+ * 
+ * Fires if the root in the model has changed. This event has no properties.
+ * 
+ * Event: mxEvent.ALIGN_CELLS
+ * 
+ * Fires between begin- and endUpdate in <alignCells>. The <code>cells</code>
+ * and <code>align</code> properties contain the respective arguments that were
+ * passed to <alignCells>.
+ *
+ * Event: mxEvent.FLIP_EDGE
+ *
+ * Fires between begin- and endUpdate in <flipEdge>. The <code>edge</code>
+ * property contains the edge passed to <flipEdge>.
+ * 
+ * Event: mxEvent.ORDER_CELLS
+ * 
+ * Fires between begin- and endUpdate in <orderCells>. The <code>cells</code>
+ * and <code>back</code> properties contain the respective arguments that were
+ * passed to <orderCells>.
+ *
+ * Event: mxEvent.CELLS_ORDERED
+ *
+ * Fires between begin- and endUpdate in <cellsOrdered>. The <code>cells</code>
+ * and <code>back</code> arguments contain the respective arguments that were
+ * passed to <cellsOrdered>.
+ * 
+ * Event: mxEvent.GROUP_CELLS
+ * 
+ * Fires between begin- and endUpdate in <groupCells>. The <code>group</code>,
+ * <code>cells</code> and <code>border</code> arguments contain the respective
+ * arguments that were passed to <groupCells>.
+ * 
+ * Event: mxEvent.UNGROUP_CELLS
+ * 
+ * Fires between begin- and endUpdate in <ungroupCells>. The <code>cells</code>
+ * property contains the array of cells that was passed to <ungroupCells>.
+ * 
+ * Event: mxEvent.REMOVE_CELLS_FROM_PARENT
+ * 
+ * Fires between begin- and endUpdate in <removeCellsFromParent>. The
+ * <code>cells</code> property contains the array of cells that was passed to
+ * <removeCellsFromParent>.
+ * 
+ * Event: mxEvent.ADD_CELLS
+ * 
+ * Fires between begin- and endUpdate in <addCells>. The <code>cells</code>,
+ * <code>parent</code>, <code>index</code>, <code>source</code> and
+ * <code>target</code> properties contain the respective arguments that were
+ * passed to <addCells>.
+ * 
+ * Event: mxEvent.CELLS_ADDED
+ * 
+ * Fires between begin- and endUpdate in <cellsAdded>. The <code>cells</code>,
+ * <code>parent</code>, <code>index</code>, <code>source</code>,
+ * <code>target</code> and <code>absolute</code> properties contain the
+ * respective arguments that were passed to <cellsAdded>.
+ * 
+ * Event: mxEvent.REMOVE_CELLS
+ * 
+ * Fires between begin- and endUpdate in <removeCells>. The <code>cells</code>
+ * and <code>includeEdges</code> arguments contain the respective arguments
+ * that were passed to <removeCells>.
+ * 
+ * Event: mxEvent.CELLS_REMOVED
+ * 
+ * Fires between begin- and endUpdate in <cellsRemoved>. The <code>cells</code>
+ * argument contains the array of cells that was removed.
+ * 
+ * Event: mxEvent.SPLIT_EDGE
+ * 
+ * Fires between begin- and endUpdate in <splitEdge>. The <code>edge</code>
+ * property contains the edge to be splitted, the <code>cells</code>,
+ * <code>newEdge</code>, <code>dx</code> and <code>dy</code> properties contain
+ * the respective arguments that were passed to <splitEdge>.
+ * 
+ * Event: mxEvent.TOGGLE_CELLS
+ * 
+ * Fires between begin- and endUpdate in <toggleCells>. The <code>show</code>,
+ * <code>cells</code> and <code>includeEdges</code> properties contain the
+ * respective arguments that were passed to <toggleCells>.
+ * 
+ * Event: mxEvent.FOLD_CELLS
+ * 
+ * Fires between begin- and endUpdate in <foldCells>. The
+ * <code>collapse</code>, <code>cells</code> and <code>recurse</code>
+ * properties contain the respective arguments that were passed to <foldCells>.
+ * 
+ * Event: mxEvent.CELLS_FOLDED
+ * 
+ * Fires between begin- and endUpdate in cellsFolded. The
+ * <code>collapse</code>, <code>cells</code> and <code>recurse</code>
+ * properties contain the respective arguments that were passed to
+ * <cellsFolded>.
+ * 
+ * Event: mxEvent.UPDATE_CELL_SIZE
+ * 
+ * Fires between begin- and endUpdate in <updateCellSize>. The
+ * <code>cell</code> and <code>ignoreChildren</code> properties contain the
+ * respective arguments that were passed to <updateCellSize>.
+ * 
+ * Event: mxEvent.RESIZE_CELLS
+ * 
+ * Fires between begin- and endUpdate in <resizeCells>. The <code>cells</code>
+ * and <code>bounds</code> properties contain the respective arguments that
+ * were passed to <resizeCells>.
+ * 
+ * Event: mxEvent.CELLS_RESIZED
+ * 
+ * Fires between begin- and endUpdate in <cellsResized>. The <code>cells</code>
+ * and <code>bounds</code> properties contain the respective arguments that
+ * were passed to <cellsResized>.
+ * 
+ * Event: mxEvent.MOVE_CELLS
+ * 
+ * Fires between begin- and endUpdate in <moveCells>. The <code>cells</code>,
+ * <code>dx</code>, <code>dy</code>, <code>clone</code>, <code>target</code>
+ * and <code>event</code> properties contain the respective arguments that
+ * were passed to <moveCells>.
+ * 
+ * Event: mxEvent.CELLS_MOVED
+ * 
+ * Fires between begin- and endUpdate in <cellsMoved>. The <code>cells</code>,
+ * <code>dx</code>, <code>dy</code> and <code>disconnect</code> properties
+ * contain the respective arguments that were passed to <cellsMoved>.
+ * 
+ * Event: mxEvent.CONNECT_CELL
+ * 
+ * Fires between begin- and endUpdate in <connectCell>. The <code>edge</code>,
+ * <code>terminal</code> and <code>source</code> properties contain the
+ * respective arguments that were passed to <connectCell>.
+ * 
+ * Event: mxEvent.CELL_CONNECTED
+ * 
+ * Fires between begin- and endUpdate in <cellConnected>. The
+ * <code>edge</code>, <code>terminal</code> and <code>source</code> properties
+ * contain the respective arguments that were passed to <cellConnected>.
+ * 
+ * Event: mxEvent.REFRESH
+ * 
+ * Fires after <refresh> was executed. This event has no properties.
+ *
+ * Event: mxEvent.CLICK
+ * 
+ * Fires in <click> after a click event. The <code>event</code> property
+ * contains the original mouse event and <code>cell</code> property contains
+ * the cell under the mouse or null if the background was clicked.
+ * 
+ * Event: mxEvent.DOUBLE_CLICK
+ *
+ * Fires in <dblClick> after a double click. The <code>event</code> property
+ * contains the original mouse event and the <code>cell</code> property
+ * contains the cell under the mouse or null if the background was clicked.
+ * 
+ * Event: mxEvent.GESTURE
+ *
+ * Fires in <fireGestureEvent> after a touch gesture. The <code>event</code>
+ * property contains the original gesture end event and the <code>cell</code>
+ * property contains the optional cell associated with the gesture.
+ *
+ * Event: mxEvent.TAP_AND_HOLD
+ *
+ * Fires in <tapAndHold> if a tap and hold event was detected. The <code>event</code>
+ * property contains the initial touch event and the <code>cell</code> property
+ * contains the cell under the mouse or null if the background was clicked.
+ *
+ * Event: mxEvent.FIRE_MOUSE_EVENT
+ *
+ * Fires in <fireMouseEvent> before the mouse listeners are invoked. The
+ * <code>eventName</code> property contains the event name and the
+ * <code>event</code> property contains the <mxMouseEvent>.
+ *
+ * Event: mxEvent.SIZE
+ *
+ * Fires after <sizeDidChange> was executed. The <code>bounds</code> property
+ * contains the new graph bounds.
+ *
+ * Event: mxEvent.START_EDITING
+ *
+ * Fires before the in-place editor starts in <startEditingAtCell>. The
+ * <code>cell</code> property contains the cell that is being edited and the
+ * <code>event</code> property contains the optional event argument that was
+ * passed to <startEditingAtCell>.
+ * 
+ * Event: mxEvent.EDITING_STARTED
+ *
+ * Fires after the in-place editor starts in <startEditingAtCell>. The
+ * <code>cell</code> property contains the cell that is being edited and the
+ * <code>event</code> property contains the optional event argument that was
+ * passed to <startEditingAtCell>.
+ * 
+ * Event: mxEvent.EDITING_STOPPED
+ *
+ * Fires after the in-place editor stops in <stopEditing>.
+ *
+ * Event: mxEvent.LABEL_CHANGED
+ *
+ * Fires between begin- and endUpdate in <cellLabelChanged>. The
+ * <code>cell</code> property contains the cell, the <code>value</code>
+ * property contains the new value for the cell, the <code>old</code> property
+ * contains the old value and the optional <code>event</code> property contains
+ * the mouse event that started the edit.
+ * 
+ * Event: mxEvent.ADD_OVERLAY
+ *
+ * Fires after an overlay is added in <addCellOverlay>. The <code>cell</code>
+ * property contains the cell and the <code>overlay</code> property contains
+ * the <mxCellOverlay> that was added.
+ *
+ * Event: mxEvent.REMOVE_OVERLAY
+ *
+ * Fires after an overlay is removed in <removeCellOverlay> and
+ * <removeCellOverlays>. The <code>cell</code> property contains the cell and
+ * the <code>overlay</code> property contains the <mxCellOverlay> that was
+ * removed.
+ * 
+ * Constructor: mxGraph
+ * 
+ * Constructs a new mxGraph in the specified container. Model is an optional
+ * mxGraphModel. If no model is provided, a new mxGraphModel instance is 
+ * used as the model. The container must have a valid owner document prior 
+ * to calling this function in Internet Explorer. RenderHint is a string to
+ * affect the display performance and rendering in IE, but not in SVG-based 
+ * browsers. The parameter is mapped to <dialect>, which may 
+ * be one of <mxConstants.DIALECT_SVG> for SVG-based browsers, 
+ * <mxConstants.DIALECT_STRICTHTML> for fastest display mode,
+ * <mxConstants.DIALECT_PREFERHTML> for faster display mode,
+ * <mxConstants.DIALECT_MIXEDHTML> for fast and <mxConstants.DIALECT_VML> 
+ * for exact display mode (slowest). The dialects are defined in mxConstants.
+ * The default values are DIALECT_SVG for SVG-based browsers and
+ * DIALECT_MIXED for IE.
+ *
+ * The possible values for the renderingHint parameter are explained below:
+ * 
+ * fast - The parameter is based on the fact that the display performance is 
+ * highly improved in IE if the VML is not contained within a VML group 
+ * element. The lack of a group element only slightly affects the display while 
+ * panning, but improves the performance by almost a factor of 2, while keeping 
+ * the display sufficiently accurate. This also allows to render certain shapes as HTML 
+ * if the display accuracy is not affected, which is implemented by 
+ * <mxShape.isMixedModeHtml>. This is the default setting and is mapped to
+ * DIALECT_MIXEDHTML.
+ * faster - Same as fast, but more expensive shapes are avoided. This is 
+ * controlled by <mxShape.preferModeHtml>. The default implementation will 
+ * avoid gradients and rounded rectangles, but more significant shapes, such 
+ * as rhombus, ellipse, actor and cylinder will be rendered accurately. This 
+ * setting is mapped to DIALECT_PREFERHTML.
+ * fastest - Almost anything will be rendered in Html. This allows for 
+ * rectangles, labels and images. This setting is mapped to
+ * DIALECT_STRICTHTML.
+ * exact - If accurate panning is required and if the diagram is small (up
+ * to 100 cells), then this value should be used. In this mode, a group is 
+ * created that contains the VML. This allows for accurate panning and is 
+ * mapped to DIALECT_VML.
+ *
+ * Example:
+ * 
+ * To create a graph inside a DOM node with an id of graph:
+ * (code)
+ * var container = document.getElementById('graph');
+ * var graph = new mxGraph(container);
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * container - Optional DOM node that acts as a container for the graph.
+ * If this is null then the container can be initialized later using
+ * <init>.
+ * model - Optional <mxGraphModel> that constitutes the graph data.
+ * renderHint - Optional string that specifies the display accuracy and
+ * performance. Default is mxConstants.DIALECT_MIXEDHTML (for IE).
+ * stylesheet - Optional <mxStylesheet> to be used in the graph.
+ */
+function mxGraph(container, model, renderHint, stylesheet)
+{
+	// Initializes the variable in case the prototype has been
+	// modified to hold some listeners (which is possible because
+	// the createHandlers call is executed regardless of the
+	// arguments passed into the ctor).
+	this.mouseListeners = null;
+	
+	// Converts the renderHint into a dialect
+	this.renderHint = renderHint;
+
+	if (mxClient.IS_SVG)
+	{
+		this.dialect = mxConstants.DIALECT_SVG;
+	}
+	else if (renderHint == mxConstants.RENDERING_HINT_EXACT && mxClient.IS_VML)
+	{
+		this.dialect = mxConstants.DIALECT_VML;
+	}
+	else if (renderHint == mxConstants.RENDERING_HINT_FASTEST)
+	{
+		this.dialect = mxConstants.DIALECT_STRICTHTML;
+	}
+	else if (renderHint == mxConstants.RENDERING_HINT_FASTER)
+	{
+		this.dialect = mxConstants.DIALECT_PREFERHTML;
+	}
+	else // default for VML
+	{
+		this.dialect = mxConstants.DIALECT_MIXEDHTML;
+	}
+	
+	// Initializes the main members that do not require a container
+	this.model = (model != null) ? model : new mxGraphModel();
+	this.multiplicities = [];
+	this.imageBundles = [];
+	this.cellRenderer = this.createCellRenderer();
+	this.setSelectionModel(this.createSelectionModel());
+	this.setStylesheet((stylesheet != null) ? stylesheet : this.createStylesheet());
+	this.view = this.createGraphView();
+	
+	// Adds a graph model listener to update the view
+	this.graphModelChangeListener = mxUtils.bind(this, function(sender, evt)
+	{
+		this.graphModelChanged(evt.getProperty('edit').changes);
+	});
+	
+	this.model.addListener(mxEvent.CHANGE, this.graphModelChangeListener);
+
+	// Installs basic event handlers with disabled default settings.
+	this.createHandlers();
+	
+	// Initializes the display if a container was specified
+	if (container != null)
+	{
+		this.init(container);
+	}
+	
+	this.view.revalidate();
+};
+
+/**
+ * Installs the required language resources at class
+ * loading time.
+ */
+if (mxLoadResources)
+{
+	mxResources.add(mxClient.basePath+'/resources/graph');
+}
+
+/**
+ * Extends mxEventSource.
+ */
+mxGraph.prototype = new mxEventSource();
+mxGraph.prototype.constructor = mxGraph;
+
+/**
+ * Variable: EMPTY_ARRAY
+ *
+ * Immutable empty array instance.
+ */
+mxGraph.prototype.EMPTY_ARRAY = [];
+
+/**
+ * Group: Variables
+ */
+
+/**
+ * Variable: mouseListeners
+ * 
+ * Holds the mouse event listeners. See <fireMouseEvent>.
+ */
+mxGraph.prototype.mouseListeners = null;
+
+/**
+ * Variable: isMouseDown
+ * 
+ * Holds the state of the mouse button.
+ */
+mxGraph.prototype.isMouseDown = false;
+
+/**
+ * Variable: model
+ * 
+ * Holds the <mxGraphModel> that contains the cells to be displayed.
+ */
+mxGraph.prototype.model = null;
+
+/**
+ * Variable: view
+ * 
+ * Holds the <mxGraphView> that caches the <mxCellStates> for the cells.
+ */
+mxGraph.prototype.view = null;
+
+/**
+ * Variable: stylesheet
+ * 
+ * Holds the <mxStylesheet> that defines the appearance of the cells.
+ * 
+ * 
+ * Example:
+ * 
+ * Use the following code to read a stylesheet into an existing graph.
+ * 
+ * (code)
+ * var req = mxUtils.load('stylesheet.xml');
+ * var root = req.getDocumentElement();
+ * var dec = new mxCodec(root.ownerDocument);
+ * dec.decode(root, graph.stylesheet);
+ * (end)
+ */
+mxGraph.prototype.stylesheet = null;
+	
+/**
+ * Variable: selectionModel
+ * 
+ * Holds the <mxGraphSelectionModel> that models the current selection.
+ */
+mxGraph.prototype.selectionModel = null;
+
+/**
+ * Variable: cellEditor
+ * 
+ * Holds the <mxCellEditor> that is used as the in-place editing.
+ */
+mxGraph.prototype.cellEditor = null;
+
+/**
+ * Variable: cellRenderer
+ * 
+ * Holds the <mxCellRenderer> for rendering the cells in the graph.
+ */
+mxGraph.prototype.cellRenderer = null;
+
+/**
+ * Variable: multiplicities
+ * 
+ * An array of <mxMultiplicities> describing the allowed
+ * connections in a graph.
+ */
+mxGraph.prototype.multiplicities = null;
+
+/**
+ * Variable: renderHint
+ * 
+ * RenderHint as it was passed to the constructor.
+ */
+mxGraph.prototype.renderHint = null;
+
+/**
+ * Variable: dialect
+ * 
+ * Dialect to be used for drawing the graph. Possible values are all
+ * constants in <mxConstants> with a DIALECT-prefix.
+ */
+mxGraph.prototype.dialect = null;
+
+/**
+ * Variable: gridSize
+ * 
+ * Specifies the grid size. Default is 10.
+ */
+mxGraph.prototype.gridSize = 10;
+	
+/**
+ * Variable: gridEnabled
+ * 
+ * Specifies if the grid is enabled. This is used in <snap>. Default is
+ * true.
+ */
+mxGraph.prototype.gridEnabled = true;
+
+/**
+ * Variable: portsEnabled
+ * 
+ * Specifies if ports are enabled. This is used in <cellConnected> to update
+ * the respective style. Default is true.
+ */
+mxGraph.prototype.portsEnabled = true;
+
+/**
+ * Variable: nativeDoubleClickEnabled
+ * 
+ * Specifies if native double click events should be detected. Default is true.
+ */
+mxGraph.prototype.nativeDblClickEnabled = true;
+
+/**
+ * Variable: doubleTapEnabled
+ * 
+ * Specifies if double taps on touch-based devices should be handled as a
+ * double click. Default is true.
+ */
+mxGraph.prototype.doubleTapEnabled = true;
+
+/**
+ * Variable: doubleTapTimeout
+ * 
+ * Specifies the timeout for double taps and non-native double clicks. Default
+ * is 500 ms.
+ */
+mxGraph.prototype.doubleTapTimeout = 500;
+
+/**
+ * Variable: doubleTapTolerance
+ * 
+ * Specifies the tolerance for double taps and double clicks in quirks mode.
+ * Default is 25 pixels.
+ */
+mxGraph.prototype.doubleTapTolerance = 25;
+
+/**
+ * Variable: lastTouchX
+ * 
+ * Holds the x-coordinate of the last touch event for double tap detection.
+ */
+mxGraph.prototype.lastTouchY = 0;
+
+/**
+ * Variable: lastTouchX
+ * 
+ * Holds the y-coordinate of the last touch event for double tap detection.
+ */
+mxGraph.prototype.lastTouchY = 0;
+
+/**
+ * Variable: lastTouchTime
+ * 
+ * Holds the time of the last touch event for double click detection.
+ */
+mxGraph.prototype.lastTouchTime = 0;
+
+/**
+ * Variable: tapAndHoldEnabled
+ * 
+ * Specifies if tap and hold should be used for starting connections on touch-based
+ * devices. Default is true.
+ */
+mxGraph.prototype.tapAndHoldEnabled = true;
+
+/**
+ * Variable: tapAndHoldDelay
+ * 
+ * Specifies the time for a tap and hold. Default is 500 ms.
+ */
+mxGraph.prototype.tapAndHoldDelay = 500;
+
+/**
+ * Variable: tapAndHoldInProgress
+ * 
+ * True if the timer for tap and hold events is running.
+ */
+mxGraph.prototype.tapAndHoldInProgress = false;
+
+/**
+ * Variable: tapAndHoldValid
+ * 
+ * True as long as the timer is running and the touch events
+ * stay within the given <tapAndHoldTolerance>.
+ */
+mxGraph.prototype.tapAndHoldValid = false;
+
+/**
+ * Variable: initialTouchX
+ * 
+ * Holds the x-coordinate of the intial touch event for tap and hold.
+ */
+mxGraph.prototype.initialTouchX = 0;
+
+/**
+ * Variable: initialTouchY
+ * 
+ * Holds the y-coordinate of the intial touch event for tap and hold.
+ */
+mxGraph.prototype.initialTouchY = 0;
+
+/**
+ * Variable: tolerance
+ * 
+ * Tolerance for a move to be handled as a single click.
+ * Default is 4 pixels.
+ */
+mxGraph.prototype.tolerance = 4;
+
+/**
+ * Variable: defaultOverlap
+ * 
+ * Value returned by <getOverlap> if <isAllowOverlapParent> returns
+ * true for the given cell. <getOverlap> is used in <constrainChild> if
+ * <isConstrainChild> returns true. The value specifies the
+ * portion of the child which is allowed to overlap the parent.
+ */
+mxGraph.prototype.defaultOverlap = 0.5;
+
+/**
+ * Variable: defaultParent
+ * 
+ * Specifies the default parent to be used to insert new cells.
+ * This is used in <getDefaultParent>. Default is null.
+ */
+mxGraph.prototype.defaultParent = null;
+
+/**
+ * Variable: alternateEdgeStyle
+ * 
+ * Specifies the alternate edge style to be used if the main control point
+ * on an edge is being doubleclicked. Default is null.
+ */
+mxGraph.prototype.alternateEdgeStyle = null;
+
+/**
+ * Variable: backgroundImage
+ *
+ * Specifies the <mxImage> to be returned by <getBackgroundImage>. Default
+ * is null.
+ * 
+ * Example:
+ *
+ * (code)
+ * var img = new mxImage('http://www.example.com/maps/examplemap.jpg', 1024, 768);
+ * graph.setBackgroundImage(img);
+ * graph.view.validate();
+ * (end)
+ */
+mxGraph.prototype.backgroundImage = null;
+
+/**
+ * Variable: pageVisible
+ *
+ * Specifies if the background page should be visible. Default is false.
+ * Not yet implemented.
+ */
+mxGraph.prototype.pageVisible = false;
+
+/**
+ * Variable: pageBreaksVisible
+ * 
+ * Specifies if a dashed line should be drawn between multiple pages. Default
+ * is false. If you change this value while a graph is being displayed then you
+ * should call <sizeDidChange> to force an update of the display.
+ */
+mxGraph.prototype.pageBreaksVisible = false;
+
+/**
+ * Variable: pageBreakColor
+ * 
+ * Specifies the color for page breaks. Default is 'gray'.
+ */
+mxGraph.prototype.pageBreakColor = 'gray';
+
+/**
+ * Variable: pageBreakDashed
+ * 
+ * Specifies the page breaks should be dashed. Default is true.
+ */
+mxGraph.prototype.pageBreakDashed = true;
+
+/**
+ * Variable: minPageBreakDist
+ * 
+ * Specifies the minimum distance for page breaks to be visible. Default is
+ * 20 (in pixels).
+ */
+mxGraph.prototype.minPageBreakDist = 20;
+
+/**
+ * Variable: preferPageSize
+ * 
+ * Specifies if the graph size should be rounded to the next page number in
+ * <sizeDidChange>. This is only used if the graph container has scrollbars.
+ * Default is false.
+ */
+mxGraph.prototype.preferPageSize = false;
+
+/**
+ * Variable: pageFormat
+ *
+ * Specifies the page format for the background page. Default is
+ * <mxConstants.PAGE_FORMAT_A4_PORTRAIT>. This is used as the default in
+ * <mxPrintPreview> and for painting the background page if <pageVisible> is
+ * true and the pagebreaks if <pageBreaksVisible> is true.
+ */
+mxGraph.prototype.pageFormat = mxConstants.PAGE_FORMAT_A4_PORTRAIT;
+
+/**
+ * Variable: pageScale
+ *
+ * Specifies the scale of the background page. Default is 1.5.
+ * Not yet implemented.
+ */
+mxGraph.prototype.pageScale = 1.5;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies the return value for <isEnabled>. Default is true.
+ */
+mxGraph.prototype.enabled = true;
+
+/**
+ * Variable: escapeEnabled
+ * 
+ * Specifies if <mxKeyHandler> should invoke <escape> when the escape key
+ * is pressed. Default is true.
+ */
+mxGraph.prototype.escapeEnabled = true;
+
+/**
+ * Variable: invokesStopCellEditing
+ * 
+ * If true, when editing is to be stopped by way of selection changing,
+ * data in diagram changing or other means stopCellEditing is invoked, and
+ * changes are saved. This is implemented in a focus handler in
+ * <mxCellEditor>. Default is true.
+ */
+mxGraph.prototype.invokesStopCellEditing = true;
+
+/**
+ * Variable: enterStopsCellEditing
+ * 
+ * If true, pressing the enter key without pressing control or shift will stop
+ * editing and accept the new value. This is used in <mxCellEditor> to stop
+ * cell editing. Note: You can always use F2 and escape to stop editing.
+ * Default is false.
+ */
+mxGraph.prototype.enterStopsCellEditing = false;
+
+/**
+ * Variable: useScrollbarsForPanning
+ * 
+ * Specifies if scrollbars should be used for panning in <panGraph> if
+ * any scrollbars are available. If scrollbars are enabled in CSS, but no
+ * scrollbars appear because the graph is smaller than the container size,
+ * then no panning occurs if this is true. Default is true.
+ */
+mxGraph.prototype.useScrollbarsForPanning = true;
+
+/**
+ * Variable: exportEnabled
+ * 
+ * Specifies the return value for <canExportCell>. Default is true.
+ */
+mxGraph.prototype.exportEnabled = true;
+
+/**
+ * Variable: importEnabled
+ * 
+ * Specifies the return value for <canImportCell>. Default is true.
+ */
+mxGraph.prototype.importEnabled = true;
+
+/**
+ * Variable: cellsLocked
+ * 
+ * Specifies the return value for <isCellLocked>. Default is false.
+ */
+mxGraph.prototype.cellsLocked = false;
+
+/**
+ * Variable: cellsCloneable
+ * 
+ * Specifies the return value for <isCellCloneable>. Default is true.
+ */
+mxGraph.prototype.cellsCloneable = true;
+
+/**
+ * Variable: foldingEnabled
+ * 
+ * Specifies if folding (collapse and expand via an image icon in the graph
+ * should be enabled). Default is true.
+ */
+mxGraph.prototype.foldingEnabled = true;
+
+/**
+ * Variable: cellsEditable
+ * 
+ * Specifies the return value for <isCellEditable>. Default is true.
+ */
+mxGraph.prototype.cellsEditable = true;
+		
+/**
+ * Variable: cellsDeletable
+ * 
+ * Specifies the return value for <isCellDeletable>. Default is true.
+ */
+mxGraph.prototype.cellsDeletable = true;
+
+/**
+ * Variable: cellsMovable
+ * 
+ * Specifies the return value for <isCellMovable>. Default is true.
+ */
+mxGraph.prototype.cellsMovable = true;
+	
+/**
+ * Variable: edgeLabelsMovable
+ * 
+ * Specifies the return value for edges in <isLabelMovable>. Default is true.
+ */
+mxGraph.prototype.edgeLabelsMovable = true;
+	
+/**
+ * Variable: vertexLabelsMovable
+ * 
+ * Specifies the return value for vertices in <isLabelMovable>. Default is false.
+ */
+mxGraph.prototype.vertexLabelsMovable = false;
+
+/**
+ * Variable: dropEnabled
+ * 
+ * Specifies the return value for <isDropEnabled>. Default is false.
+ */
+mxGraph.prototype.dropEnabled = false;
+
+/**
+ * Variable: splitEnabled
+ * 
+ * Specifies if dropping onto edges should be enabled. This is ignored if
+ * <dropEnabled> is false. If enabled, it will call <splitEdge> to carry
+ * out the drop operation. Default is true.
+ */
+mxGraph.prototype.splitEnabled = true;
+
+/**
+ * Variable: cellsResizable
+ * 
+ * Specifies the return value for <isCellResizable>. Default is true.
+ */
+mxGraph.prototype.cellsResizable = true;
+
+/**
+ * Variable: cellsBendable
+ * 
+ * Specifies the return value for <isCellsBendable>. Default is true.
+ */
+mxGraph.prototype.cellsBendable = true;
+
+/**
+ * Variable: cellsSelectable
+ * 
+ * Specifies the return value for <isCellSelectable>. Default is true.
+ */
+mxGraph.prototype.cellsSelectable = true;
+
+/**
+ * Variable: cellsDisconnectable
+ * 
+ * Specifies the return value for <isCellDisconntable>. Default is true.
+ */
+mxGraph.prototype.cellsDisconnectable = true;
+
+/**
+ * Variable: autoSizeCells
+ * 
+ * Specifies if the graph should automatically update the cell size after an
+ * edit. This is used in <isAutoSizeCell>. Default is false.
+ */
+mxGraph.prototype.autoSizeCells = false;
+
+/**
+ * Variable: autoSizeCellsOnAdd
+ * 
+ * Specifies if autoSize style should be applied when cells are added. Default is false.
+ */
+mxGraph.prototype.autoSizeCellsOnAdd = false;
+
+/**
+ * Variable: autoScroll
+ * 
+ * Specifies if the graph should automatically scroll if the mouse goes near
+ * the container edge while dragging. This is only taken into account if the
+ * container has scrollbars. Default is true.
+ * 
+ * If you need this to work without scrollbars then set <ignoreScrollbars> to
+ * true. Please consult the <ignoreScrollbars> for details. In general, with
+ * no scrollbars, the use of <allowAutoPanning> is recommended.
+ */
+mxGraph.prototype.autoScroll = true;
+
+/**
+ * Variable: ignoreScrollbars
+ * 
+ * Specifies if the graph should automatically scroll regardless of the
+ * scrollbars. This will scroll the container using positive values for
+ * scroll positions (ie usually only rightwards and downwards). To avoid
+ * possible conflicts with panning, set <translateToScrollPosition> to true.
+ */
+mxGraph.prototype.ignoreScrollbars = false;
+
+/**
+ * Variable: translateToScrollPosition
+ * 
+ * Specifies if the graph should automatically convert the current scroll
+ * position to a translate in the graph view when a mouseUp event is received.
+ * This can be used to avoid conflicts when using <autoScroll> and
+ * <ignoreScrollbars> with no scrollbars in the container.
+ */
+mxGraph.prototype.translateToScrollPosition = false;
+
+/**
+ * Variable: timerAutoScroll
+ * 
+ * Specifies if autoscrolling should be carried out via mxPanningManager even
+ * if the container has scrollbars. This disables <scrollPointToVisible> and
+ * uses <mxPanningManager> instead. If this is true then <autoExtend> is
+ * disabled. It should only be used with a scroll buffer or when scollbars
+ * are visible and scrollable in all directions. Default is false.
+ */
+mxGraph.prototype.timerAutoScroll = false;
+
+/**
+ * Variable: allowAutoPanning
+ * 
+ * Specifies if panning via <panGraph> should be allowed to implement autoscroll
+ * if no scrollbars are available in <scrollPointToVisible>. To enable panning
+ * inside the container, near the edge, set <mxPanningManager.border> to a
+ * positive value. Default is false.
+ */
+mxGraph.prototype.allowAutoPanning = false;
+
+/**
+ * Variable: autoExtend
+ * 
+ * Specifies if the size of the graph should be automatically extended if the
+ * mouse goes near the container edge while dragging. This is only taken into
+ * account if the container has scrollbars. Default is true. See <autoScroll>.
+ */
+mxGraph.prototype.autoExtend = true;
+
+/**
+ * Variable: maximumGraphBounds
+ * 
+ * <mxRectangle> that specifies the area in which all cells in the diagram
+ * should be placed. Uses in <getMaximumGraphBounds>. Use a width or height of
+ * 0 if you only want to give a upper, left corner.
+ */
+mxGraph.prototype.maximumGraphBounds = null;
+
+/**
+ * Variable: minimumGraphSize
+ * 
+ * <mxRectangle> that specifies the minimum size of the graph. This is ignored
+ * if the graph container has no scrollbars. Default is null.
+ */
+mxGraph.prototype.minimumGraphSize = null;
+
+/**
+ * Variable: minimumContainerSize
+ * 
+ * <mxRectangle> that specifies the minimum size of the <container> if
+ * <resizeContainer> is true.
+ */
+mxGraph.prototype.minimumContainerSize = null;
+		
+/**
+ * Variable: maximumContainerSize
+ * 
+ * <mxRectangle> that specifies the maximum size of the container if
+ * <resizeContainer> is true.
+ */
+mxGraph.prototype.maximumContainerSize = null;
+
+/**
+ * Variable: resizeContainer
+ * 
+ * Specifies if the container should be resized to the graph size when
+ * the graph size has changed. Default is false.
+ */
+mxGraph.prototype.resizeContainer = false;
+
+/**
+ * Variable: border
+ * 
+ * Border to be added to the bottom and right side when the container is
+ * being resized after the graph has been changed. Default is 0.
+ */
+mxGraph.prototype.border = 0;
+		
+/**
+ * Variable: keepEdgesInForeground
+ * 
+ * Specifies if edges should appear in the foreground regardless of their order
+ * in the model. If <keepEdgesInForeground> and <keepEdgesInBackground> are
+ * both true then the normal order is applied. Default is false.
+ */
+mxGraph.prototype.keepEdgesInForeground = false;
+
+/**
+ * Variable: keepEdgesInBackground
+ * 
+ * Specifies if edges should appear in the background regardless of their order
+ * in the model. If <keepEdgesInForeground> and <keepEdgesInBackground> are
+ * both true then the normal order is applied. Default is false.
+ */
+mxGraph.prototype.keepEdgesInBackground = false;
+
+/**
+ * Variable: allowNegativeCoordinates
+ * 
+ * Specifies if negative coordinates for vertices are allowed. Default is true.
+ */
+mxGraph.prototype.allowNegativeCoordinates = true;
+
+/**
+ * Variable: constrainChildren
+ * 
+ * Specifies if a child should be constrained inside the parent bounds after a
+ * move or resize of the child. Default is true.
+ */
+mxGraph.prototype.constrainChildren = true;
+
+/**
+ * Variable: constrainRelativeChildren
+ * 
+ * Specifies if child cells with relative geometries should be constrained
+ * inside the parent bounds, if <constrainChildren> is true, and/or the
+ * <maximumGraphBounds>. Default is false.
+ */
+mxGraph.prototype.constrainRelativeChildren = false;
+
+/**
+ * Variable: extendParents
+ * 
+ * Specifies if a parent should contain the child bounds after a resize of
+ * the child. Default is true. This has precedence over <constrainChildren>.
+ */
+mxGraph.prototype.extendParents = true;
+
+/**
+ * Variable: extendParentsOnAdd
+ * 
+ * Specifies if parents should be extended according to the <extendParents>
+ * switch if cells are added. Default is true.
+ */
+mxGraph.prototype.extendParentsOnAdd = true;
+
+/**
+ * Variable: extendParentsOnAdd
+ * 
+ * Specifies if parents should be extended according to the <extendParents>
+ * switch if cells are added. Default is false for backwards compatiblity.
+ */
+mxGraph.prototype.extendParentsOnMove = false;
+
+/**
+ * Variable: recursiveResize
+ * 
+ * Specifies the return value for <isRecursiveResize>. Default is
+ * false for backwards compatiblity.
+ */
+mxGraph.prototype.recursiveResize = false;
+
+/**
+ * Variable: collapseToPreferredSize
+ * 
+ * Specifies if the cell size should be changed to the preferred size when
+ * a cell is first collapsed. Default is true.
+ */
+mxGraph.prototype.collapseToPreferredSize = true;
+
+/**
+ * Variable: zoomFactor
+ * 
+ * Specifies the factor used for <zoomIn> and <zoomOut>. Default is 1.2
+ * (120%).
+ */
+mxGraph.prototype.zoomFactor = 1.2;
+
+/**
+ * Variable: keepSelectionVisibleOnZoom
+ * 
+ * Specifies if the viewport should automatically contain the selection cells
+ * after a zoom operation. Default is false.
+ */
+mxGraph.prototype.keepSelectionVisibleOnZoom = false;
+
+/**
+ * Variable: centerZoom
+ * 
+ * Specifies if the zoom operations should go into the center of the actual
+ * diagram rather than going from top, left. Default is true.
+ */
+mxGraph.prototype.centerZoom = true;
+
+/**
+ * Variable: resetViewOnRootChange
+ * 
+ * Specifies if the scale and translate should be reset if the root changes in
+ * the model. Default is true.
+ */
+mxGraph.prototype.resetViewOnRootChange = true;
+
+/**
+ * Variable: resetEdgesOnResize
+ * 
+ * Specifies if edge control points should be reset after the resize of a
+ * connected cell. Default is false.
+ */
+mxGraph.prototype.resetEdgesOnResize = false;
+
+/**
+ * Variable: resetEdgesOnMove
+ * 
+ * Specifies if edge control points should be reset after the move of a
+ * connected cell. Default is false.
+ */
+mxGraph.prototype.resetEdgesOnMove = false;
+
+/**
+ * Variable: resetEdgesOnConnect
+ * 
+ * Specifies if edge control points should be reset after the the edge has been
+ * reconnected. Default is true.
+ */
+mxGraph.prototype.resetEdgesOnConnect = true;
+
+/**
+ * Variable: allowLoops
+ * 
+ * Specifies if loops (aka self-references) are allowed. Default is false.
+ */
+mxGraph.prototype.allowLoops = false;
+	
+/**
+ * Variable: defaultLoopStyle
+ * 
+ * <mxEdgeStyle> to be used for loops. This is a fallback for loops if the
+ * <mxConstants.STYLE_LOOP> is undefined. Default is <mxEdgeStyle.Loop>.
+ */
+mxGraph.prototype.defaultLoopStyle = mxEdgeStyle.Loop;
+
+/**
+ * Variable: multigraph
+ * 
+ * Specifies if multiple edges in the same direction between the same pair of
+ * vertices are allowed. Default is true.
+ */
+mxGraph.prototype.multigraph = true;
+
+/**
+ * Variable: connectableEdges
+ * 
+ * Specifies if edges are connectable. Default is false. This overrides the
+ * connectable field in edges.
+ */
+mxGraph.prototype.connectableEdges = false;
+
+/**
+ * Variable: allowDanglingEdges
+ * 
+ * Specifies if edges with disconnected terminals are allowed in the graph.
+ * Default is true.
+ */
+mxGraph.prototype.allowDanglingEdges = true;
+
+/**
+ * Variable: cloneInvalidEdges
+ * 
+ * Specifies if edges that are cloned should be validated and only inserted
+ * if they are valid. Default is true.
+ */
+mxGraph.prototype.cloneInvalidEdges = false;
+
+/**
+ * Variable: disconnectOnMove
+ * 
+ * Specifies if edges should be disconnected from their terminals when they
+ * are moved. Default is true.
+ */
+mxGraph.prototype.disconnectOnMove = true;
+
+/**
+ * Variable: labelsVisible
+ * 
+ * Specifies if labels should be visible. This is used in <getLabel>. Default
+ * is true.
+ */
+mxGraph.prototype.labelsVisible = true;
+	
+/**
+ * Variable: htmlLabels
+ * 
+ * Specifies the return value for <isHtmlLabel>. Default is false.
+ */
+mxGraph.prototype.htmlLabels = false;
+
+/**
+ * Variable: swimlaneSelectionEnabled
+ * 
+ * Specifies if swimlanes should be selectable via the content if the
+ * mouse is released. Default is true.
+ */
+mxGraph.prototype.swimlaneSelectionEnabled = true;
+
+/**
+ * Variable: swimlaneNesting
+ * 
+ * Specifies if nesting of swimlanes is allowed. Default is true.
+ */
+mxGraph.prototype.swimlaneNesting = true;
+	
+/**
+ * Variable: swimlaneIndicatorColorAttribute
+ * 
+ * The attribute used to find the color for the indicator if the indicator
+ * color is set to 'swimlane'. Default is <mxConstants.STYLE_FILLCOLOR>.
+ */
+mxGraph.prototype.swimlaneIndicatorColorAttribute = mxConstants.STYLE_FILLCOLOR;
+
+/**
+ * Variable: imageBundles
+ * 
+ * Holds the list of image bundles.
+ */
+mxGraph.prototype.imageBundles = null;
+
+/**
+ * Variable: minFitScale
+ * 
+ * Specifies the minimum scale to be applied in <fit>. Default is 0.1. Set this
+ * to null to allow any value.
+ */
+mxGraph.prototype.minFitScale = 0.1;
+
+/**
+ * Variable: maxFitScale
+ * 
+ * Specifies the maximum scale to be applied in <fit>. Default is 8. Set this
+ * to null to allow any value.
+ */
+mxGraph.prototype.maxFitScale = 8;
+
+/**
+ * Variable: panDx
+ * 
+ * Current horizontal panning value. Default is 0.
+ */
+mxGraph.prototype.panDx = 0;
+
+/**
+ * Variable: panDy
+ * 
+ * Current vertical panning value. Default is 0.
+ */
+mxGraph.prototype.panDy = 0;
+
+/**
+ * Variable: collapsedImage
+ * 
+ * Specifies the <mxImage> to indicate a collapsed state.
+ * Default value is mxClient.imageBasePath + '/collapsed.gif'
+ */
+mxGraph.prototype.collapsedImage = new mxImage(mxClient.imageBasePath + '/collapsed.gif', 9, 9);
+
+/**
+ * Variable: expandedImage
+ * 
+ * Specifies the <mxImage> to indicate a expanded state.
+ * Default value is mxClient.imageBasePath + '/expanded.gif'
+ */
+mxGraph.prototype.expandedImage = new mxImage(mxClient.imageBasePath + '/expanded.gif', 9, 9);
+
+/**
+ * Variable: warningImage
+ * 
+ * Specifies the <mxImage> for the image to be used to display a warning
+ * overlay. See <setCellWarning>. Default value is mxClient.imageBasePath +
+ * '/warning'.  The extension for the image depends on the platform. It is
+ * '.png' on the Mac and '.gif' on all other platforms.
+ */
+mxGraph.prototype.warningImage = new mxImage(mxClient.imageBasePath + '/warning'+
+	((mxClient.IS_MAC) ? '.png' : '.gif'), 16, 16);
+
+/**
+ * Variable: alreadyConnectedResource
+ * 
+ * Specifies the resource key for the error message to be displayed in
+ * non-multigraphs when two vertices are already connected. If the resource
+ * for this key does not exist then the value is used as the error message.
+ * Default is 'alreadyConnected'.
+ */
+mxGraph.prototype.alreadyConnectedResource = (mxClient.language != 'none') ? 'alreadyConnected' : '';
+
+/**
+ * Variable: containsValidationErrorsResource
+ * 
+ * Specifies the resource key for the warning message to be displayed when
+ * a collapsed cell contains validation errors. If the resource for this
+ * key does not exist then the value is used as the warning message.
+ * Default is 'containsValidationErrors'.
+ */
+mxGraph.prototype.containsValidationErrorsResource = (mxClient.language != 'none') ? 'containsValidationErrors' : '';
+
+/**
+ * Variable: collapseExpandResource
+ * 
+ * Specifies the resource key for the tooltip on the collapse/expand icon.
+ * If the resource for this key does not exist then the value is used as
+ * the tooltip. Default is 'collapse-expand'.
+ */
+mxGraph.prototype.collapseExpandResource = (mxClient.language != 'none') ? 'collapse-expand' : '';
+
+/**
+ * Function: init
+ * 
+ * Initializes the <container> and creates the respective datastructures.
+ * 
+ * Parameters:
+ * 
+ * container - DOM node that will contain the graph display.
+ */
+mxGraph.prototype.init = function(container)
+{
+	this.container = container;
+	
+	// Initializes the in-place editor
+	this.cellEditor = this.createCellEditor();	
+
+	// Initializes the container using the view
+	this.view.init();
+	
+	// Updates the size of the container for the current graph
+	this.sizeDidChange();
+	
+	// Hides tooltips and resets tooltip timer if mouse leaves container
+	mxEvent.addListener(container, 'mouseleave', mxUtils.bind(this, function()
+	{
+		if (this.tooltipHandler != null)
+		{
+			this.tooltipHandler.hide();
+		}
+	}));
+
+	// Automatic deallocation of memory
+	if (mxClient.IS_IE)
+	{
+		mxEvent.addListener(window, 'unload', mxUtils.bind(this, function()
+		{
+			this.destroy();
+		}));
+		
+		// Disable shift-click for text
+		mxEvent.addListener(container, 'selectstart',
+			mxUtils.bind(this, function(evt)
+			{
+				return this.isEditing() || (!this.isMouseDown && !mxEvent.isShiftDown(evt));
+			})
+		);
+	}
+	
+	// Workaround for missing last shape and connect preview in IE8 standards
+	// mode if no initial graph displayed or no label for shape defined
+	if (document.documentMode == 8)
+	{
+		container.insertAdjacentHTML('beforeend', '<' + mxClient.VML_PREFIX + ':group' +
+			' style="DISPLAY: none;"></' + mxClient.VML_PREFIX + ':group>');
+	}
+};
+
+/**
+ * Function: createHandlers
+ * 
+ * Creates the tooltip-, panning-, connection- and graph-handler (in this
+ * order). This is called in the constructor before <init> is called.
+ */
+mxGraph.prototype.createHandlers = function()
+{
+	this.tooltipHandler = this.createTooltipHandler();
+	this.tooltipHandler.setEnabled(false);
+	this.selectionCellsHandler = this.createSelectionCellsHandler();
+	this.connectionHandler = this.createConnectionHandler();
+	this.connectionHandler.setEnabled(false);
+	this.graphHandler = this.createGraphHandler();
+	this.panningHandler = this.createPanningHandler();
+	this.panningHandler.panningEnabled = false;
+	this.popupMenuHandler = this.createPopupMenuHandler();
+};
+
+/**
+ * Function: createTooltipHandler
+ * 
+ * Creates and returns a new <mxTooltipHandler> to be used in this graph.
+ */
+mxGraph.prototype.createTooltipHandler = function()
+{
+	return new mxTooltipHandler(this);
+};
+
+/**
+ * Function: createSelectionCellsHandler
+ * 
+ * Creates and returns a new <mxTooltipHandler> to be used in this graph.
+ */
+mxGraph.prototype.createSelectionCellsHandler = function()
+{
+	return new mxSelectionCellsHandler(this);
+};
+
+/**
+ * Function: createConnectionHandler
+ * 
+ * Creates and returns a new <mxConnectionHandler> to be used in this graph.
+ */
+mxGraph.prototype.createConnectionHandler = function()
+{
+	return new mxConnectionHandler(this);
+};
+
+/**
+ * Function: createGraphHandler
+ * 
+ * Creates and returns a new <mxGraphHandler> to be used in this graph.
+ */
+mxGraph.prototype.createGraphHandler = function()
+{
+	return new mxGraphHandler(this);
+};
+
+/**
+ * Function: createPanningHandler
+ * 
+ * Creates and returns a new <mxPanningHandler> to be used in this graph.
+ */
+mxGraph.prototype.createPanningHandler = function()
+{
+	return new mxPanningHandler(this);
+};
+
+/**
+ * Function: createPopupMenuHandler
+ * 
+ * Creates and returns a new <mxPopupMenuHandler> to be used in this graph.
+ */
+mxGraph.prototype.createPopupMenuHandler = function()
+{
+	return new mxPopupMenuHandler(this);
+};
+
+/**
+ * Function: createSelectionModel
+ * 
+ * Creates a new <mxGraphSelectionModel> to be used in this graph.
+ */
+mxGraph.prototype.createSelectionModel = function()
+{
+	return new mxGraphSelectionModel(this);
+};
+
+/**
+ * Function: createStylesheet
+ * 
+ * Creates a new <mxGraphSelectionModel> to be used in this graph.
+ */
+mxGraph.prototype.createStylesheet = function()
+{
+	return new mxStylesheet();
+};
+
+/**
+ * Function: createGraphView
+ * 
+ * Creates a new <mxGraphView> to be used in this graph.
+ */
+mxGraph.prototype.createGraphView = function()
+{
+	return new mxGraphView(this);
+};
+ 
+/**
+ * Function: createCellRenderer
+ * 
+ * Creates a new <mxCellRenderer> to be used in this graph.
+ */
+mxGraph.prototype.createCellRenderer = function()
+{
+	return new mxCellRenderer();
+};
+
+/**
+ * Function: createCellEditor
+ * 
+ * Creates a new <mxCellEditor> to be used in this graph.
+ */
+mxGraph.prototype.createCellEditor = function()
+{
+	return new mxCellEditor(this);
+};
+
+/**
+ * Function: getModel
+ * 
+ * Returns the <mxGraphModel> that contains the cells.
+ */
+mxGraph.prototype.getModel = function()
+{
+	return this.model;
+};
+
+/**
+ * Function: getView
+ * 
+ * Returns the <mxGraphView> that contains the <mxCellStates>.
+ */
+mxGraph.prototype.getView = function()
+{
+	return this.view;
+};
+
+/**
+ * Function: getStylesheet
+ * 
+ * Returns the <mxStylesheet> that defines the style.
+ */
+mxGraph.prototype.getStylesheet = function()
+{
+	return this.stylesheet;
+};
+
+/**
+ * Function: setStylesheet
+ * 
+ * Sets the <mxStylesheet> that defines the style.
+ */
+mxGraph.prototype.setStylesheet = function(stylesheet)
+{
+	this.stylesheet = stylesheet;
+};
+
+/**
+ * Function: getSelectionModel
+ * 
+ * Returns the <mxGraphSelectionModel> that contains the selection.
+ */
+mxGraph.prototype.getSelectionModel = function()
+{
+	return this.selectionModel;
+};
+
+/**
+ * Function: setSelectionModel
+ * 
+ * Sets the <mxSelectionModel> that contains the selection.
+ */
+mxGraph.prototype.setSelectionModel = function(selectionModel)
+{
+	this.selectionModel = selectionModel;
+};
+
+/**
+ * Function: getSelectionCellsForChanges
+ * 
+ * Returns the cells to be selected for the given array of changes.
+ */
+mxGraph.prototype.getSelectionCellsForChanges = function(changes)
+{
+	var cells = [];
+	
+	for (var i = 0; i < changes.length; i++)
+	{
+		var change = changes[i];
+		
+		if (change.constructor != mxRootChange)
+		{
+			var cell = null;
+
+			if (change instanceof mxChildChange && change.previous == null)
+			{
+				cell = change.child;
+			}
+			else if (change.cell != null && change.cell instanceof mxCell)
+			{
+				cell = change.cell;
+			}
+			
+			if (cell != null && mxUtils.indexOf(cells, cell) < 0)
+			{
+				cells.push(cell);
+			}
+		}
+	}
+	
+	return this.getModel().getTopmostCells(cells);
+};
+
+/**
+ * Function: graphModelChanged
+ * 
+ * Called when the graph model changes. Invokes <processChange> on each
+ * item of the given array to update the view accordingly.
+ * 
+ * Parameters:
+ * 
+ * changes - Array that contains the individual changes.
+ */
+mxGraph.prototype.graphModelChanged = function(changes)
+{
+	for (var i = 0; i < changes.length; i++)
+	{
+		this.processChange(changes[i]);
+	}
+	
+	this.removeSelectionCells(this.getRemovedCellsForChanges(changes));
+	
+	this.view.validate();
+	this.sizeDidChange();
+};
+
+/**
+ * Function: getRemovedCellsForChanges
+ * 
+ * Returns the cells that have been removed from the model.
+ */
+mxGraph.prototype.getRemovedCellsForChanges = function(changes)
+{
+	var result = [];
+	
+	for (var i = 0; i < changes.length; i++)
+	{
+		var change = changes[i];
+		
+		// Resets the view settings, removes all cells and clears
+		// the selection if the root changes.
+		if (change instanceof mxRootChange)
+		{
+			break;
+		}
+		else if (change instanceof mxChildChange)
+		{
+			if (change.previous != null && change.parent == null)
+			{
+				result = result.concat(this.model.getDescendants(change.child));
+			}
+		}
+		else if (change instanceof mxVisibleChange)
+		{
+			result = result.concat(this.model.getDescendants(change.cell));
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: processChange
+ * 
+ * Processes the given change and invalidates the respective cached data
+ * in <view>. This fires a <root> event if the root has changed in the
+ * model.
+ * 
+ * Parameters:
+ * 
+ * change - Object that represents the change on the model.
+ */
+mxGraph.prototype.processChange = function(change)
+{
+	// Resets the view settings, removes all cells and clears
+	// the selection if the root changes.
+	if (change instanceof mxRootChange)
+	{
+		this.clearSelection();
+		this.setDefaultParent(null);
+		this.removeStateForCell(change.previous);
+		
+		if (this.resetViewOnRootChange)
+		{
+			this.view.scale = 1;
+			this.view.translate.x = 0;
+			this.view.translate.y = 0;
+		}
+
+		this.fireEvent(new mxEventObject(mxEvent.ROOT));
+	}
+	
+	// Adds or removes a child to the view by online invaliding
+	// the minimal required portions of the cache, namely, the
+	// old and new parent and the child.
+	else if (change instanceof mxChildChange)
+	{
+		var newParent = this.model.getParent(change.child);
+		this.view.invalidate(change.child, true, true);
+
+		if (newParent == null || this.isCellCollapsed(newParent))
+		{
+			this.view.invalidate(change.child, true, true);
+			this.removeStateForCell(change.child);
+			
+			// Handles special case of current root of view being removed
+			if (this.view.currentRoot == change.child)
+			{
+				this.home();
+			}
+		}
+ 
+		if (newParent != change.previous)
+		{
+			// Refreshes the collapse/expand icons on the parents
+			if (newParent != null)
+			{
+				this.view.invalidate(newParent, false, false);
+			}
+			
+			if (change.previous != null)
+			{
+				this.view.invalidate(change.previous, false, false);
+			}
+		}
+	}
+
+	// Handles two special cases where the shape does not need to be
+	// recreated from scratch, it only needs to be invalidated.
+	else if (change instanceof mxTerminalChange || change instanceof mxGeometryChange)
+	{
+		// Checks if the geometry has changed to avoid unnessecary revalidation
+		if (change instanceof mxTerminalChange || ((change.previous == null && change.geometry != null) ||
+			(change.previous != null && !change.previous.equals(change.geometry))))
+		{
+			this.view.invalidate(change.cell);
+		}
+	}
+
+	// Handles two special cases where only the shape, but no
+	// descendants need to be recreated
+	else if (change instanceof mxValueChange)
+	{
+		this.view.invalidate(change.cell, false, false);
+	}
+	
+	// Requires a new mxShape in JavaScript
+	else if (change instanceof mxStyleChange)
+	{
+		this.view.invalidate(change.cell, true, true);
+		var state = this.view.getState(change.cell);
+		
+		if (state != null)
+		{
+			state.style = null;
+		}
+	}
+	
+	// Removes the state from the cache by default
+	else if (change.cell != null && change.cell instanceof mxCell)
+	{
+		this.removeStateForCell(change.cell);
+	}
+};
+
+/**
+ * Function: removeStateForCell
+ * 
+ * Removes all cached information for the given cell and its descendants.
+ * This is called when a cell was removed from the model.
+ * 
+ * Paramters:
+ * 
+ * cell - <mxCell> that was removed from the model.
+ */
+mxGraph.prototype.removeStateForCell = function(cell)
+{
+	var childCount = this.model.getChildCount(cell);
+	
+	for (var i = 0; i < childCount; i++)
+	{
+		this.removeStateForCell(this.model.getChildAt(cell, i));
+	}
+
+	this.view.invalidate(cell, false, true);
+	this.view.removeState(cell);
+};
+
+/**
+ * Group: Overlays
+ */
+
+/**
+ * Function: addCellOverlay
+ * 
+ * Adds an <mxCellOverlay> for the specified cell. This method fires an
+ * <addoverlay> event and returns the new <mxCellOverlay>.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to add the overlay for.
+ * overlay - <mxCellOverlay> to be added for the cell.
+ */
+mxGraph.prototype.addCellOverlay = function(cell, overlay)
+{
+	if (cell.overlays == null)
+	{
+		cell.overlays = [];
+	}
+	
+	cell.overlays.push(overlay);
+
+	var state = this.view.getState(cell);
+
+	// Immediately updates the cell display if the state exists
+	if (state != null)
+	{
+		this.cellRenderer.redraw(state);
+	}
+	
+	this.fireEvent(new mxEventObject(mxEvent.ADD_OVERLAY,
+			'cell', cell, 'overlay', overlay));
+	
+	return overlay;
+};
+
+/**
+ * Function: getCellOverlays
+ * 
+ * Returns the array of <mxCellOverlays> for the given cell or null, if
+ * no overlays are defined.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose overlays should be returned.
+ */
+mxGraph.prototype.getCellOverlays = function(cell)
+{
+	return cell.overlays;
+};
+
+/**
+ * Function: removeCellOverlay
+ * 
+ * Removes and returns the given <mxCellOverlay> from the given cell. This
+ * method fires a <removeoverlay> event. If no overlay is given, then all
+ * overlays are removed using <removeOverlays>.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose overlay should be removed.
+ * overlay - Optional <mxCellOverlay> to be removed.
+ */
+mxGraph.prototype.removeCellOverlay = function(cell, overlay)
+{
+	if (overlay == null)
+	{
+		this.removeCellOverlays(cell);
+	}
+	else
+	{
+		var index = mxUtils.indexOf(cell.overlays, overlay);
+		
+		if (index >= 0)
+		{
+			cell.overlays.splice(index, 1);
+			
+			if (cell.overlays.length == 0)
+			{
+				cell.overlays = null;
+			}
+			
+			// Immediately updates the cell display if the state exists
+			var state = this.view.getState(cell);
+			
+			if (state != null)
+			{
+				this.cellRenderer.redraw(state);
+			}
+			
+			this.fireEvent(new mxEventObject(mxEvent.REMOVE_OVERLAY,
+					'cell', cell, 'overlay', overlay));	
+		}
+		else
+		{
+			overlay = null;
+		}
+	}
+	
+	return overlay;
+};
+
+/**
+ * Function: removeCellOverlays
+ * 
+ * Removes all <mxCellOverlays> from the given cell. This method
+ * fires a <removeoverlay> event for each <mxCellOverlay> and returns
+ * the array of <mxCellOverlays> that was removed from the cell.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose overlays should be removed
+ */
+mxGraph.prototype.removeCellOverlays = function(cell)
+{
+	var overlays = cell.overlays;
+	
+	if (overlays != null)
+	{
+		cell.overlays = null;
+		
+		// Immediately updates the cell display if the state exists
+		var state = this.view.getState(cell);
+		
+		if (state != null)
+		{
+			this.cellRenderer.redraw(state);
+		}
+		
+		for (var i = 0; i < overlays.length; i++)
+		{
+			this.fireEvent(new mxEventObject(mxEvent.REMOVE_OVERLAY,
+					'cell', cell, 'overlay', overlays[i]));
+		}
+	}
+	
+	return overlays;
+};
+
+/**
+ * Function: clearCellOverlays
+ * 
+ * Removes all <mxCellOverlays> in the graph for the given cell and all its
+ * descendants. If no cell is specified then all overlays are removed from
+ * the graph. This implementation uses <removeCellOverlays> to remove the
+ * overlays from the individual cells.
+ * 
+ * Parameters:
+ * 
+ * cell - Optional <mxCell> that represents the root of the subtree to
+ * remove the overlays from. Default is the root in the model.
+ */
+mxGraph.prototype.clearCellOverlays = function(cell)
+{
+	cell = (cell != null) ? cell : this.model.getRoot();
+	this.removeCellOverlays(cell);
+	
+	// Recursively removes all overlays from the children
+	var childCount = this.model.getChildCount(cell);
+	
+	for (var i = 0; i < childCount; i++)
+	{
+		var child = this.model.getChildAt(cell, i);
+		this.clearCellOverlays(child); // recurse
+	}
+};
+
+/**
+ * Function: setCellWarning
+ * 
+ * Creates an overlay for the given cell using the warning and image or
+ * <warningImage> and returns the new <mxCellOverlay>. The warning is
+ * displayed as a tooltip in a red font and may contain HTML markup. If
+ * the warning is null or a zero length string, then all overlays are
+ * removed from the cell.
+ * 
+ * Example:
+ * 
+ * (code)
+ * graph.setCellWarning(cell, '<b>Warning:</b>: Hello, World!');
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose warning should be set.
+ * warning - String that represents the warning to be displayed.
+ * img - Optional <mxImage> to be used for the overlay. Default is
+ * <warningImage>.
+ * isSelect - Optional boolean indicating if a click on the overlay
+ * should select the corresponding cell. Default is false.
+ */
+mxGraph.prototype.setCellWarning = function(cell, warning, img, isSelect)
+{
+	if (warning != null && warning.length > 0)
+	{
+		img = (img != null) ? img : this.warningImage;
+		
+		// Creates the overlay with the image and warning
+		var overlay = new mxCellOverlay(img,
+			'<font color=red>'+warning+'</font>');
+		
+		// Adds a handler for single mouseclicks to select the cell
+		if (isSelect)
+		{
+			overlay.addListener(mxEvent.CLICK,
+				mxUtils.bind(this, function(sender, evt)
+				{
+					if (this.isEnabled())
+					{
+						this.setSelectionCell(cell);
+					}
+				})
+			);
+		}
+		
+		// Sets and returns the overlay in the graph
+		return this.addCellOverlay(cell, overlay);
+	}
+	else
+	{
+		this.removeCellOverlays(cell);
+	}
+	
+	return null;
+};
+
+/**
+ * Group: In-place editing
+ */
+
+/**
+ * Function: startEditing
+ * 
+ * Calls <startEditingAtCell> using the given cell or the first selection
+ * cell.
+ * 
+ * Parameters:
+ * 
+ * evt - Optional mouse event that triggered the editing.
+ */
+mxGraph.prototype.startEditing = function(evt)
+{
+	this.startEditingAtCell(null, evt);
+};
+
+/**
+ * Function: startEditingAtCell
+ * 
+ * Fires a <startEditing> event and invokes <mxCellEditor.startEditing>
+ * on <editor>. After editing was started, a <editingStarted> event is
+ * fired.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to start the in-place editor for.
+ * evt - Optional mouse event that triggered the editing.
+ */
+mxGraph.prototype.startEditingAtCell = function(cell, evt)
+{
+	if (evt == null || !mxEvent.isMultiTouchEvent(evt))
+	{
+		if (cell == null)
+		{
+			cell = this.getSelectionCell();
+			
+			if (cell != null && !this.isCellEditable(cell))
+			{
+				cell = null;
+			}
+		}
+	
+		if (cell != null)
+		{
+			this.fireEvent(new mxEventObject(mxEvent.START_EDITING,
+					'cell', cell, 'event', evt));
+			this.cellEditor.startEditing(cell, evt);
+			this.fireEvent(new mxEventObject(mxEvent.EDITING_STARTED,
+					'cell', cell, 'event', evt));
+		}
+	}
+};
+
+/**
+ * Function: getEditingValue
+ * 
+ * Returns the initial value for in-place editing. This implementation
+ * returns <convertValueToString> for the given cell. If this function is
+ * overridden, then <mxGraphModel.valueForCellChanged> should take care
+ * of correctly storing the actual new value inside the user object.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> for which the initial editing value should be returned.
+ * evt - Optional mouse event that triggered the editor.
+ */
+mxGraph.prototype.getEditingValue = function(cell, evt)
+{
+	return this.convertValueToString(cell);
+};
+
+/**
+ * Function: stopEditing
+ * 
+ * Stops the current editing  and fires a <editingStopped> event.
+ * 
+ * Parameters:
+ * 
+ * cancel - Boolean that specifies if the current editing value
+ * should be stored.
+ */
+mxGraph.prototype.stopEditing = function(cancel)
+{
+	this.cellEditor.stopEditing(cancel);
+	this.fireEvent(new mxEventObject(mxEvent.EDITING_STOPPED, 'cancel', cancel));
+};
+
+/**
+ * Function: labelChanged
+ * 
+ * Sets the label of the specified cell to the given value using
+ * <cellLabelChanged> and fires <mxEvent.LABEL_CHANGED> while the
+ * transaction is in progress. Returns the cell whose label was changed.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose label should be changed.
+ * value - New label to be assigned.
+ * evt - Optional event that triggered the change.
+ */
+mxGraph.prototype.labelChanged = function(cell, value, evt)
+{
+	this.model.beginUpdate();
+	try
+	{
+		var old = cell.value;
+		this.cellLabelChanged(cell, value, this.isAutoSizeCell(cell));
+		this.fireEvent(new mxEventObject(mxEvent.LABEL_CHANGED,
+			'cell', cell, 'value', value, 'old', old, 'event', evt));
+	}
+	finally
+	{
+		this.model.endUpdate();
+	}
+	
+	return cell;
+};
+
+/**
+ * Function: cellLabelChanged
+ * 
+ * Sets the new label for a cell. If autoSize is true then
+ * <cellSizeUpdated> will be called.
+ * 
+ * In the following example, the function is extended to map changes to
+ * attributes in an XML node, as shown in <convertValueToString>.
+ * Alternatively, the handling of this can be implemented as shown in
+ * <mxGraphModel.valueForCellChanged> without the need to clone the
+ * user object.
+ * 
+ * (code)
+ * var graphCellLabelChanged = graph.cellLabelChanged;
+ * graph.cellLabelChanged = function(cell, newValue, autoSize)
+ * {
+ * 	// Cloned for correct undo/redo
+ * 	var elt = cell.value.cloneNode(true);
+ *  elt.setAttribute('label', newValue);
+ *  
+ *  newValue = elt;
+ *  graphCellLabelChanged.apply(this, arguments);
+ * };
+ * (end) 
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose label should be changed.
+ * value - New label to be assigned.
+ * autoSize - Boolean that specifies if <cellSizeUpdated> should be called.
+ */
+mxGraph.prototype.cellLabelChanged = function(cell, value, autoSize)
+{
+	this.model.beginUpdate();
+	try
+	{
+		this.model.setValue(cell, value);
+		
+		if (autoSize)
+		{
+			this.cellSizeUpdated(cell, false);
+		}
+	}
+	finally
+	{
+		this.model.endUpdate();
+	}
+};
+
+/**
+ * Group: Event processing
+ */
+
+/**
+ * Function: escape
+ * 
+ * Processes an escape keystroke.
+ * 
+ * Parameters:
+ * 
+ * evt - Mouseevent that represents the keystroke.
+ */
+mxGraph.prototype.escape = function(evt)
+{
+	this.fireEvent(new mxEventObject(mxEvent.ESCAPE, 'event', evt));
+};
+
+/**
+ * Function: click
+ * 
+ * Processes a singleclick on an optional cell and fires a <click> event.
+ * The click event is fired initially. If the graph is enabled and the
+ * event has not been consumed, then the cell is selected using
+ * <selectCellForEvent> or the selection is cleared using
+ * <clearSelection>. The events consumed state is set to true if the
+ * corresponding <mxMouseEvent> has been consumed.
+ *
+ * To handle a click event, use the following code.
+ * 
+ * (code)
+ * graph.addListener(mxEvent.CLICK, function(sender, evt)
+ * {
+ *   var e = evt.getProperty('event'); // mouse event
+ *   var cell = evt.getProperty('cell'); // cell may be null
+ *   
+ *   if (cell != null)
+ *   {
+ *     // Do something useful with cell and consume the event
+ *     evt.consume();
+ *   }
+ * });
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * me - <mxMouseEvent> that represents the single click.
+ */
+mxGraph.prototype.click = function(me)
+{
+	var evt = me.getEvent();
+	var cell = me.getCell();
+	var mxe = new mxEventObject(mxEvent.CLICK, 'event', evt, 'cell', cell);
+	
+	if (me.isConsumed())
+	{
+		mxe.consume();
+	}
+	
+	this.fireEvent(mxe);
+	
+	// Handles the event if it has not been consumed
+	if (this.isEnabled() && !mxEvent.isConsumed(evt) && !mxe.isConsumed())
+	{
+		if (cell != null)
+		{
+			if (this.isTransparentClickEvent(evt))
+			{
+				var active = false;
+				
+				var tmp = this.getCellAt(me.graphX, me.graphY, null, null, null, mxUtils.bind(this, function(state)
+				{
+					var selected = this.isCellSelected(state.cell);
+					active = active || selected;
+					
+					return !active || selected;
+				}));
+				
+				if (tmp != null)
+				{
+					cell = tmp;
+				}
+			}
+			
+			this.selectCellForEvent(cell, evt);
+		}
+		else
+		{
+			var swimlane = null;
+			
+			if (this.isSwimlaneSelectionEnabled())
+			{
+				// Gets the swimlane at the location (includes
+				// content area of swimlanes)
+				swimlane = this.getSwimlaneAt(me.getGraphX(), me.getGraphY());
+			}
+
+			// Selects the swimlane and consumes the event
+			if (swimlane != null)
+			{
+				this.selectCellForEvent(swimlane, evt);
+			}
+			
+			// Ignores the event if the control key is pressed
+			else if (!this.isToggleEvent(evt))
+			{
+				this.clearSelection();
+			}
+		}
+	}
+};
+
+/**
+ * Function: dblClick
+ * 
+ * Processes a doubleclick on an optional cell and fires a <dblclick>
+ * event. The event is fired initially. If the graph is enabled and the
+ * event has not been consumed, then <edit> is called with the given
+ * cell. The event is ignored if no cell was specified.
+ *
+ * Example for overriding this method.
+ *
+ * (code)
+ * graph.dblClick = function(evt, cell)
+ * {
+ *   var mxe = new mxEventObject(mxEvent.DOUBLE_CLICK, 'event', evt, 'cell', cell);
+ *   this.fireEvent(mxe);
+ *   
+ *   if (this.isEnabled() && !mxEvent.isConsumed(evt) && !mxe.isConsumed())
+ *   {
+ * 	   mxUtils.alert('Hello, World!');
+ *     mxe.consume();
+ *   }
+ * }
+ * (end)
+ * 
+ * Example listener for this event.
+ * 
+ * (code)
+ * graph.addListener(mxEvent.DOUBLE_CLICK, function(sender, evt)
+ * {
+ *   var cell = evt.getProperty('cell');
+ *   // do something with the cell and consume the
+ *   // event to prevent in-place editing from start
+ * });
+ * (end) 
+ * 
+ * Parameters:
+ * 
+ * evt - Mouseevent that represents the doubleclick.
+ * cell - Optional <mxCell> under the mousepointer.
+ */
+mxGraph.prototype.dblClick = function(evt, cell)
+{
+	var mxe = new mxEventObject(mxEvent.DOUBLE_CLICK, 'event', evt, 'cell', cell);
+	this.fireEvent(mxe);
+	
+	// Handles the event if it has not been consumed
+	if (this.isEnabled() && !mxEvent.isConsumed(evt) && !mxe.isConsumed() &&
+		cell != null && this.isCellEditable(cell) && !this.isEditing(cell))
+	{
+		this.startEditingAtCell(cell, evt);
+		mxEvent.consume(evt);
+	}
+};
+
+/**
+ * Function: tapAndHold
+ * 
+ * Handles the <mxMouseEvent> by highlighting the <mxCellState>.
+ * 
+ * Parameters:
+ * 
+ * me - <mxMouseEvent> that represents the touch event.
+ * state - Optional <mxCellState> that is associated with the event.
+ */
+mxGraph.prototype.tapAndHold = function(me)
+{
+	var evt = me.getEvent();
+	var mxe = new mxEventObject(mxEvent.TAP_AND_HOLD, 'event', evt, 'cell', me.getCell());
+
+	// LATER: Check if event should be consumed if me is consumed
+	this.fireEvent(mxe);
+
+	if (mxe.isConsumed())
+	{
+		// Resets the state of the panning handler
+		this.panningHandler.panningTrigger = false;
+	}
+	
+	// Handles the event if it has not been consumed
+	if (this.isEnabled() && !mxEvent.isConsumed(evt) && !mxe.isConsumed() && this.connectionHandler.isEnabled())
+	{
+		var state = this.view.getState(this.connectionHandler.marker.getCell(me));
+
+		if (state != null)
+		{
+			this.connectionHandler.marker.currentColor = this.connectionHandler.marker.validColor;
+			this.connectionHandler.marker.markedState = state;
+			this.connectionHandler.marker.mark();
+			
+			this.connectionHandler.first = new mxPoint(me.getGraphX(), me.getGraphY());
+			this.connectionHandler.edgeState = this.connectionHandler.createEdgeState(me);
+			this.connectionHandler.previous = state;
+			this.connectionHandler.fireEvent(new mxEventObject(mxEvent.START, 'state', this.connectionHandler.previous));
+		}
+	}
+};
+
+/**
+ * Function: scrollPointToVisible
+ * 
+ * Scrolls the graph to the given point, extending the graph container if
+ * specified.
+ */
+mxGraph.prototype.scrollPointToVisible = function(x, y, extend, border)
+{
+	if (!this.timerAutoScroll && (this.ignoreScrollbars || mxUtils.hasScrollbars(this.container)))
+	{
+		var c = this.container;
+		border = (border != null) ? border : 20;
+		
+		if (x >= c.scrollLeft && y >= c.scrollTop && x <= c.scrollLeft + c.clientWidth &&
+			y <= c.scrollTop + c.clientHeight)
+		{
+			var dx = c.scrollLeft + c.clientWidth - x;
+			
+			if (dx < border)
+			{
+				var old = c.scrollLeft;
+				c.scrollLeft += border - dx;
+
+				// Automatically extends the canvas size to the bottom, right
+				// if the event is outside of the canvas and the edge of the
+				// canvas has been reached. Notes: Needs fix for IE.
+				if (extend && old == c.scrollLeft)
+				{
+					if (this.dialect == mxConstants.DIALECT_SVG)
+					{
+						var root = this.view.getDrawPane().ownerSVGElement;
+						var width = this.container.scrollWidth + border - dx;
+						
+						// Updates the clipping region. This is an expensive
+						// operation that should not be executed too often.
+						root.style.width = width + 'px';
+					}
+					else
+					{
+						var width = Math.max(c.clientWidth, c.scrollWidth) + border - dx;
+						var canvas = this.view.getCanvas();
+						canvas.style.width = width + 'px';
+					}
+					
+					c.scrollLeft += border - dx;
+				}
+			}
+			else
+			{
+				dx = x - c.scrollLeft;
+				
+				if (dx < border)
+				{
+					c.scrollLeft -= border - dx;
+				}
+			}
+			
+			var dy = c.scrollTop + c.clientHeight - y;
+			
+			if (dy < border)
+			{
+				var old = c.scrollTop;
+				c.scrollTop += border - dy;
+
+				if (old == c.scrollTop && extend)
+				{
+					if (this.dialect == mxConstants.DIALECT_SVG)
+					{
+						var root = this.view.getDrawPane().ownerSVGElement;
+						var height = this.container.scrollHeight + border - dy;
+						
+						// Updates the clipping region. This is an expensive
+						// operation that should not be executed too often.
+						root.style.height = height + 'px';
+					}
+					else
+					{
+						var height = Math.max(c.clientHeight, c.scrollHeight) + border - dy;
+						var canvas = this.view.getCanvas();
+						canvas.style.height = height + 'px';
+					}
+					
+					c.scrollTop += border - dy;
+				}
+			}
+			else
+			{
+				dy = y - c.scrollTop;
+				
+				if (dy < border)
+				{
+					c.scrollTop -= border - dy;
+				}
+			}
+		}
+	}
+	else if (this.allowAutoPanning && !this.panningHandler.isActive())
+	{
+		if (this.panningManager == null)
+		{
+			this.panningManager = this.createPanningManager();
+		}
+
+		this.panningManager.panTo(x + this.panDx, y + this.panDy);
+	}
+};
+
+
+/**
+ * Function: createPanningManager
+ * 
+ * Creates and returns an <mxPanningManager>.
+ */
+mxGraph.prototype.createPanningManager = function()
+{
+	return new mxPanningManager(this);
+};
+
+/**
+ * Function: getBorderSizes
+ * 
+ * Returns the size of the border and padding on all four sides of the
+ * container. The left, top, right and bottom borders are stored in the x, y,
+ * width and height of the returned <mxRectangle>, respectively.
+ */
+mxGraph.prototype.getBorderSizes = function()
+{
+	var css = mxUtils.getCurrentStyle(this.container);
+	
+	return new mxRectangle(mxUtils.parseCssNumber(css.paddingLeft) +
+			((css.borderLeftStyle != 'none') ? mxUtils.parseCssNumber(css.borderLeftWidth) : 0),
+		mxUtils.parseCssNumber(css.paddingTop) +
+			((css.borderTopStyle != 'none') ? mxUtils.parseCssNumber(css.borderTopWidth) : 0),
+		mxUtils.parseCssNumber(css.paddingRight) +
+			((css.borderRightStyle != 'none') ? mxUtils.parseCssNumber(css.borderRightWidth) : 0),
+		mxUtils.parseCssNumber(css.paddingBottom) +
+			((css.borderBottomStyle != 'none') ? mxUtils.parseCssNumber(css.borderBottomWidth) : 0));
+};
+
+/**
+ * Function: getPreferredPageSize
+ * 
+ * Returns the preferred size of the background page if <preferPageSize> is true.
+ */
+mxGraph.prototype.getPreferredPageSize = function(bounds, width, height)
+{
+	var scale = this.view.scale;
+	var tr = this.view.translate;
+	var fmt = this.pageFormat;
+	var ps = this.pageScale;
+	var page = new mxRectangle(0, 0, Math.ceil(fmt.width * ps), Math.ceil(fmt.height * ps));
+	
+	var hCount = (this.pageBreaksVisible) ? Math.ceil(width / page.width) : 1;
+	var vCount = (this.pageBreaksVisible) ? Math.ceil(height / page.height) : 1;
+	
+	return new mxRectangle(0, 0, hCount * page.width + 2 + tr.x, vCount * page.height + 2 + tr.y);
+};
+
+/**
+ * Function: fit
+ *
+ * Scales the graph such that the complete diagram fits into <container> and
+ * returns the current scale in the view. To fit an initial graph prior to
+ * rendering, set <mxGraphView.rendering> to false prior to changing the model
+ * and execute the following after changing the model.
+ * 
+ * (code)
+ * graph.fit();
+ * graph.view.rendering = true;
+ * graph.refresh();
+ * (end)
+ * 
+ * To fit and center the graph, the following code can be used.
+ * 
+ * (code)
+ * var margin = 2;
+ * var max = 3;
+ * 
+ * var bounds = graph.getGraphBounds();
+ * var cw = graph.container.clientWidth - margin;
+ * var ch = graph.container.clientHeight - margin;
+ * var w = bounds.width / graph.view.scale;
+ * var h = bounds.height / graph.view.scale;
+ * var s = Math.min(max, Math.min(cw / w, ch / h));
+ * 
+ * graph.view.scaleAndTranslate(s,
+ *   (margin + cw - w * s) / (2 * s) - bounds.x / graph.view.scale,
+ *   (margin + ch - h * s) / (2 * s) - bounds.y / graph.view.scale);
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * border - Optional number that specifies the border. Default is <border>.
+ * keepOrigin - Optional boolean that specifies if the translate should be
+ * changed. Default is false.
+ * margin - Optional margin in pixels. Default is 0.
+ * enabled - Optional boolean that specifies if the scale should be set or
+ * just returned. Default is true.
+ * ignoreWidth - Optional boolean that specifies if the width should be
+ * ignored. Default is false.
+ * ignoreHeight - Optional boolean that specifies if the height should be
+ * ignored. Default is false.
+ */
+mxGraph.prototype.fit = function(border, keepOrigin, margin, enabled, ignoreWidth, ignoreHeight)
+{
+	if (this.container != null)
+	{
+		border = (border != null) ? border : this.getBorder();
+		keepOrigin = (keepOrigin != null) ? keepOrigin : false;
+		margin = (margin != null) ? margin : 0;
+		enabled = (enabled != null) ? enabled : true;
+		ignoreWidth = (ignoreWidth != null) ? ignoreWidth : false;
+		ignoreHeight = (ignoreHeight != null) ? ignoreHeight : false;
+		
+		// Adds spacing and border from css
+		var cssBorder = this.getBorderSizes();
+		var w1 = this.container.offsetWidth - cssBorder.x - cssBorder.width - 1;
+		var h1 = this.container.offsetHeight - cssBorder.y - cssBorder.height - 1;
+		var bounds = this.view.getGraphBounds();
+		
+		if (bounds.width > 0 && bounds.height > 0)
+		{
+			if (keepOrigin && bounds.x != null && bounds.y != null)
+			{
+				bounds = bounds.clone();
+				bounds.width += bounds.x;
+				bounds.height += bounds.y;
+				bounds.x = 0;
+				bounds.y = 0;
+			}
+			
+			// LATER: Use unscaled bounding boxes to fix rounding errors
+			var s = this.view.scale;
+			var w2 = bounds.width / s;
+			var h2 = bounds.height / s;
+			
+			// Fits to the size of the background image if required
+			if (this.backgroundImage != null)
+			{
+				w2 = Math.max(w2, this.backgroundImage.width - bounds.x / s);
+				h2 = Math.max(h2, this.backgroundImage.height - bounds.y / s);
+			}
+			
+			var b = ((keepOrigin) ? border : 2 * border) + margin;
+
+			w1 -= b;
+			h1 -= b;
+			
+			var s2 = (((ignoreWidth) ? h1 / h2 : (ignoreHeight) ? w1 / w2 :
+				Math.min(w1 / w2, h1 / h2)));
+			
+			if (this.minFitScale != null)
+			{
+				s2 = Math.max(s2, this.minFitScale);
+			}
+			
+			if (this.maxFitScale != null)
+			{
+				s2 = Math.min(s2, this.maxFitScale);
+			}
+	
+			if (enabled)
+			{
+				if (!keepOrigin)
+				{
+					if (!mxUtils.hasScrollbars(this.container))
+					{
+						var x0 = (bounds.x != null) ? Math.floor(this.view.translate.x - bounds.x / s + border / s2 + margin / 2) : border;
+						var y0 = (bounds.y != null) ? Math.floor(this.view.translate.y - bounds.y / s + border / s2 + margin / 2) : border;
+
+						this.view.scaleAndTranslate(s2, x0, y0);
+					}
+					else
+					{
+						this.view.setScale(s2);
+						var b2 = this.getGraphBounds();
+						
+						if (b2.x != null)
+						{
+							this.container.scrollLeft = b2.x;
+						}
+						
+						if (b2.y != null)
+						{
+							this.container.scrollTop = b2.y;
+						}
+					}
+				}
+				else if (this.view.scale != s2)
+				{
+					this.view.setScale(s2);
+				}
+			}
+			else
+			{
+				return s2;
+			}
+		}
+	}
+
+	return this.view.scale;
+};
+
+/**
+ * Function: sizeDidChange
+ * 
+ * Called when the size of the graph has changed. This implementation fires
+ * a <size> event after updating the clipping region of the SVG element in
+ * SVG-bases browsers.
+ */
+mxGraph.prototype.sizeDidChange = function()
+{
+	var bounds = this.getGraphBounds();
+	
+	if (this.container != null)
+	{
+		var border = this.getBorder();
+		
+		var width = Math.max(0, bounds.x + bounds.width + border);
+		var height = Math.max(0, bounds.y + bounds.height + border);
+		
+		if (this.minimumContainerSize != null)
+		{
+			width = Math.max(width, this.minimumContainerSize.width);
+			height = Math.max(height, this.minimumContainerSize.height);
+		}
+
+		if (this.resizeContainer)
+		{
+			this.doResizeContainer(width, height);
+		}
+
+		if (this.preferPageSize || (!mxClient.IS_IE && this.pageVisible))
+		{
+			var size = this.getPreferredPageSize(bounds, Math.max(1, width), Math.max(1, height));
+			
+			if (size != null)
+			{
+				width = size.width * this.view.scale;
+				height = size.height * this.view.scale;
+			}
+		}
+		
+		if (this.minimumGraphSize != null)
+		{
+			width = Math.max(width, this.minimumGraphSize.width * this.view.scale);
+			height = Math.max(height, this.minimumGraphSize.height * this.view.scale);
+		}
+
+		width = Math.ceil(width);
+		height = Math.ceil(height);
+
+		if (this.dialect == mxConstants.DIALECT_SVG)
+		{
+			var root = this.view.getDrawPane().ownerSVGElement;
+			
+			root.style.minWidth = Math.max(1, width) + 'px';
+			root.style.minHeight = Math.max(1, height) + 'px';
+			root.style.width = '100%';
+			root.style.height = '100%';
+		}
+		else
+		{
+			if (mxClient.IS_QUIRKS)
+			{
+				// Quirks mode does not support minWidth/-Height
+				this.view.updateHtmlCanvasSize(Math.max(1, width), Math.max(1, height));
+			}
+			else
+			{
+				this.view.canvas.style.minWidth = Math.max(1, width) + 'px';
+				this.view.canvas.style.minHeight = Math.max(1, height) + 'px';
+			}
+		}
+		
+		this.updatePageBreaks(this.pageBreaksVisible, width, height);
+	}
+
+	this.fireEvent(new mxEventObject(mxEvent.SIZE, 'bounds', bounds));
+};
+
+/**
+ * Function: doResizeContainer
+ * 
+ * Resizes the container for the given graph width and height.
+ */
+mxGraph.prototype.doResizeContainer = function(width, height)
+{
+	if (this.maximumContainerSize != null)
+	{
+		width = Math.min(this.maximumContainerSize.width, width);
+		height = Math.min(this.maximumContainerSize.height, height);
+	}
+
+	this.container.style.width = Math.ceil(width) + 'px';
+	this.container.style.height = Math.ceil(height) + 'px';
+};
+
+/**
+ * Function: updatePageBreaks
+ * 
+ * Invokes from <sizeDidChange> to redraw the page breaks.
+ * 
+ * Parameters:
+ * 
+ * visible - Boolean that specifies if page breaks should be shown.
+ * width - Specifies the width of the container in pixels.
+ * height - Specifies the height of the container in pixels.
+ */
+mxGraph.prototype.updatePageBreaks = function(visible, width, height)
+{
+	var scale = this.view.scale;
+	var tr = this.view.translate;
+	var fmt = this.pageFormat;
+	var ps = scale * this.pageScale;
+	var bounds = new mxRectangle(0, 0, fmt.width * ps, fmt.height * ps);
+
+	var gb = mxRectangle.fromRectangle(this.getGraphBounds());
+	gb.width = Math.max(1, gb.width);
+	gb.height = Math.max(1, gb.height);
+	
+	bounds.x = Math.floor((gb.x - tr.x * scale) / bounds.width) * bounds.width + tr.x * scale;
+	bounds.y = Math.floor((gb.y - tr.y * scale) / bounds.height) * bounds.height + tr.y * scale;
+	
+	gb.width = Math.ceil((gb.width + (gb.x - bounds.x)) / bounds.width) * bounds.width;
+	gb.height = Math.ceil((gb.height + (gb.y - bounds.y)) / bounds.height) * bounds.height;
+	
+	// Does not show page breaks if the scale is too small
+	visible = visible && Math.min(bounds.width, bounds.height) > this.minPageBreakDist;
+
+	var horizontalCount = (visible) ? Math.ceil(gb.height / bounds.height) + 1 : 0;
+	var verticalCount = (visible) ? Math.ceil(gb.width / bounds.width) + 1 : 0;
+	var right = (verticalCount - 1) * bounds.width;
+	var bottom = (horizontalCount - 1) * bounds.height;
+	
+	if (this.horizontalPageBreaks == null && horizontalCount > 0)
+	{
+		this.horizontalPageBreaks = [];
+	}
+
+	if (this.verticalPageBreaks == null && verticalCount > 0)
+	{
+		this.verticalPageBreaks = [];
+	}
+	
+	var drawPageBreaks = mxUtils.bind(this, function(breaks)
+	{
+		if (breaks != null)
+		{
+			var count = (breaks == this.horizontalPageBreaks) ? horizontalCount : verticalCount; 
+			
+			for (var i = 0; i <= count; i++)
+			{
+				var pts = (breaks == this.horizontalPageBreaks) ?
+					[new mxPoint(Math.round(bounds.x), Math.round(bounds.y + i * bounds.height)),
+			         new mxPoint(Math.round(bounds.x + right), Math.round(bounds.y + i * bounds.height))] :
+			        [new mxPoint(Math.round(bounds.x + i * bounds.width), Math.round(bounds.y)),
+			         new mxPoint(Math.round(bounds.x + i * bounds.width), Math.round(bounds.y + bottom))];
+
+				if (breaks[i] != null)
+				{
+					breaks[i].points = pts;
+					breaks[i].redraw();
+				}
+				else
+				{
+					var pageBreak = new mxPolyline(pts, this.pageBreakColor);
+					pageBreak.dialect = this.dialect;
+					pageBreak.pointerEvents = false;
+					pageBreak.isDashed = this.pageBreakDashed;
+					pageBreak.init(this.view.backgroundPane);
+					pageBreak.redraw();
+					
+					breaks[i] = pageBreak;
+				}
+			}
+			
+			for (var i = count; i < breaks.length; i++)
+			{
+				breaks[i].destroy();
+			}
+			
+			breaks.splice(count, breaks.length - count);
+		}
+	});
+	
+	drawPageBreaks(this.horizontalPageBreaks);
+	drawPageBreaks(this.verticalPageBreaks);
+};
+
+/**
+ * Group: Cell styles
+ */
+
+/**
+ * Function: getCellStyle
+ * 
+ * Returns an array of key, value pairs representing the cell style for the
+ * given cell. If no string is defined in the model that specifies the
+ * style, then the default style for the cell is returned or <EMPTY_ARRAY>,
+ * if not style can be found. Note: You should try and get the cell state
+ * for the given cell and use the cached style in the state before using
+ * this method.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose style should be returned as an array.
+ */
+mxGraph.prototype.getCellStyle = function(cell)
+{
+	var stylename = this.model.getStyle(cell);
+	var style = null;
+	
+	// Gets the default style for the cell
+	if (this.model.isEdge(cell))
+	{
+		style = this.stylesheet.getDefaultEdgeStyle();
+	}
+	else
+	{
+		style = this.stylesheet.getDefaultVertexStyle();
+	}
+	
+	// Resolves the stylename using the above as the default
+	if (stylename != null)
+	{
+		style = this.postProcessCellStyle(this.stylesheet.getCellStyle(stylename, style));
+	}
+	
+	// Returns a non-null value if no style can be found
+	if (style == null)
+	{
+		style = mxGraph.prototype.EMPTY_ARRAY;
+	}
+	
+	return style;
+};
+
+/**
+ * Function: postProcessCellStyle
+ * 
+ * Tries to resolve the value for the image style in the image bundles and
+ * turns short data URIs as defined in mxImageBundle to data URIs as
+ * defined in RFC 2397 of the IETF.
+ */
+mxGraph.prototype.postProcessCellStyle = function(style)
+{
+	if (style != null)
+	{
+		var key = style[mxConstants.STYLE_IMAGE];
+		var image = this.getImageFromBundles(key);
+
+		if (image != null)
+		{
+			style[mxConstants.STYLE_IMAGE] = image;
+		}
+		else
+		{
+			image = key;
+		}
+		
+		// Converts short data uris to normal data uris
+		if (image != null && image.substring(0, 11) == 'data:image/')
+		{
+			if (image.substring(0, 20) == 'data:image/svg+xml,<')
+			{
+				// Required for FF and IE11
+				image = image.substring(0, 19) + encodeURIComponent(image.substring(19));
+			}
+			else if (image.substring(0, 22) != 'data:image/svg+xml,%3C')
+			{
+				var comma = image.indexOf(',');
+				
+				// Adds base64 encoding prefix if needed
+				if (comma > 0 && image.substring(comma - 7, comma + 1) != ';base64,')
+				{
+					image = image.substring(0, comma) + ';base64,'
+						+ image.substring(comma + 1);
+				}
+			}
+			
+			style[mxConstants.STYLE_IMAGE] = image;
+		}
+	}
+
+	return style;
+};
+
+/**
+ * Function: setCellStyle
+ * 
+ * Sets the style of the specified cells. If no cells are given, then the
+ * selection cells are changed.
+ * 
+ * Parameters:
+ * 
+ * style - String representing the new style of the cells.
+ * cells - Optional array of <mxCells> to set the style for. Default is the
+ * selection cells.
+ */
+mxGraph.prototype.setCellStyle = function(style, cells)
+{
+	cells = cells || this.getSelectionCells();
+	
+	if (cells != null)
+	{
+		this.model.beginUpdate();
+		try
+		{
+			for (var i = 0; i < cells.length; i++)
+			{
+				this.model.setStyle(cells[i], style);
+			}
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: toggleCellStyle
+ * 
+ * Toggles the boolean value for the given key in the style of the given cell
+ * and returns the new value as 0 or 1. If no cell is specified then the
+ * selection cell is used.
+ * 
+ * Parameter:
+ * 
+ * key - String representing the key for the boolean value to be toggled.
+ * defaultValue - Optional boolean default value if no value is defined.
+ * Default is false.
+ * cell - Optional <mxCell> whose style should be modified. Default is
+ * the selection cell.
+ */
+mxGraph.prototype.toggleCellStyle = function(key, defaultValue, cell)
+{
+	cell = cell || this.getSelectionCell();
+	
+	return this.toggleCellStyles(key, defaultValue, [cell]);
+};
+
+/**
+ * Function: toggleCellStyles
+ * 
+ * Toggles the boolean value for the given key in the style of the given cells
+ * and returns the new value as 0 or 1. If no cells are specified, then the
+ * selection cells are used. For example, this can be used to toggle
+ * <mxConstants.STYLE_ROUNDED> or any other style with a boolean value.
+ * 
+ * Parameter:
+ * 
+ * key - String representing the key for the boolean value to be toggled.
+ * defaultValue - Optional boolean default value if no value is defined.
+ * Default is false.
+ * cells - Optional array of <mxCells> whose styles should be modified.
+ * Default is the selection cells.
+ */
+mxGraph.prototype.toggleCellStyles = function(key, defaultValue, cells)
+{
+	defaultValue = (defaultValue != null) ? defaultValue : false;
+	cells = cells || this.getSelectionCells();
+	var value = null;
+	
+	if (cells != null && cells.length > 0)
+	{
+		var state = this.view.getState(cells[0]);
+		var style = (state != null) ? state.style : this.getCellStyle(cells[0]);
+		
+		if (style != null)
+		{
+			value = (mxUtils.getValue(style, key, defaultValue)) ? 0 : 1;
+			this.setCellStyles(key, value, cells);
+		}
+	}
+	
+	return value;
+};
+
+/**
+ * Function: setCellStyles
+ * 
+ * Sets the key to value in the styles of the given cells. This will modify
+ * the existing cell styles in-place and override any existing assignment
+ * for the given key. If no cells are specified, then the selection cells
+ * are changed. If no value is specified, then the respective key is
+ * removed from the styles.
+ * 
+ * Parameters:
+ * 
+ * key - String representing the key to be assigned.
+ * value - String representing the new value for the key.
+ * cells - Optional array of <mxCells> to change the style for. Default is
+ * the selection cells.
+ */
+mxGraph.prototype.setCellStyles = function(key, value, cells)
+{
+	cells = cells || this.getSelectionCells();
+	mxUtils.setCellStyles(this.model, cells, key, value);
+};
+
+/**
+ * Function: toggleCellStyleFlags
+ * 
+ * Toggles the given bit for the given key in the styles of the specified
+ * cells.
+ * 
+ * Parameters:
+ * 
+ * key - String representing the key to toggle the flag in.
+ * flag - Integer that represents the bit to be toggled.
+ * cells - Optional array of <mxCells> to change the style for. Default is
+ * the selection cells.
+ */
+mxGraph.prototype.toggleCellStyleFlags = function(key, flag, cells)
+{
+	this.setCellStyleFlags(key, flag, null, cells);
+};
+
+/**
+ * Function: setCellStyleFlags
+ * 
+ * Sets or toggles the given bit for the given key in the styles of the
+ * specified cells.
+ * 
+ * Parameters:
+ * 
+ * key - String representing the key to toggle the flag in.
+ * flag - Integer that represents the bit to be toggled.
+ * value - Boolean value to be used or null if the value should be toggled.
+ * cells - Optional array of <mxCells> to change the style for. Default is
+ * the selection cells.
+ */
+mxGraph.prototype.setCellStyleFlags = function(key, flag, value, cells)
+{
+	cells = cells || this.getSelectionCells();
+	
+	if (cells != null && cells.length > 0)
+	{
+		if (value == null)
+		{
+			var state = this.view.getState(cells[0]);
+			var style = (state != null) ? state.style : this.getCellStyle(cells[0]);
+			
+			if (style != null)
+			{
+				var current = parseInt(style[key] || 0);
+				value = !((current & flag) == flag);
+			}
+		}
+
+		mxUtils.setCellStyleFlags(this.model, cells, key, flag, value);
+	}
+};
+
+/**
+ * Group: Cell alignment and orientation
+ */
+
+/**
+ * Function: alignCells
+ * 
+ * Aligns the given cells vertically or horizontally according to the given
+ * alignment using the optional parameter as the coordinate.
+ * 
+ * Parameters:
+ * 
+ * align - Specifies the alignment. Possible values are all constants in
+ * mxConstants with an ALIGN prefix.
+ * cells - Array of <mxCells> to be aligned.
+ * param - Optional coordinate for the alignment.
+ */
+mxGraph.prototype.alignCells = function(align, cells, param)
+{
+	if (cells == null)
+	{
+		cells = this.getSelectionCells();
+	}
+	
+	if (cells != null && cells.length > 1)
+	{
+		// Finds the required coordinate for the alignment
+		if (param == null)
+		{
+			for (var i = 0; i < cells.length; i++)
+			{
+				var state = this.view.getState(cells[i]);
+				
+				if (state != null && !this.model.isEdge(cells[i]))
+				{
+					if (param == null)
+					{
+						if (align == mxConstants.ALIGN_CENTER)
+						{
+							param = state.x + state.width / 2;
+							break;
+						}
+						else if (align == mxConstants.ALIGN_RIGHT)
+						{
+							param = state.x + state.width;
+						}
+						else if (align == mxConstants.ALIGN_TOP)
+						{
+							param = state.y;
+						}
+						else if (align == mxConstants.ALIGN_MIDDLE)
+						{
+							param = state.y + state.height / 2;
+							break;
+						}
+						else if (align == mxConstants.ALIGN_BOTTOM)
+						{
+							param = state.y + state.height;
+						}
+						else
+						{
+							param = state.x;
+						}
+					}
+					else
+					{
+						if (align == mxConstants.ALIGN_RIGHT)
+						{
+							param = Math.max(param, state.x + state.width);
+						}
+						else if (align == mxConstants.ALIGN_TOP)
+						{
+							param = Math.min(param, state.y);
+						}
+						else if (align == mxConstants.ALIGN_BOTTOM)
+						{
+							param = Math.max(param, state.y + state.height);
+						}
+						else
+						{
+							param = Math.min(param, state.x);
+						}
+					}
+				}
+			}
+		}
+
+		// Aligns the cells to the coordinate
+		if (param != null)
+		{
+			var s = this.view.scale;
+
+			this.model.beginUpdate();
+			try
+			{
+				for (var i = 0; i < cells.length; i++)
+				{
+					var state = this.view.getState(cells[i]);
+					
+					if (state != null)
+					{
+						var geo = this.getCellGeometry(cells[i]);
+						
+						if (geo != null && !this.model.isEdge(cells[i]))
+						{
+							geo = geo.clone();
+							
+							if (align == mxConstants.ALIGN_CENTER)
+							{
+								geo.x += (param - state.x - state.width / 2) / s;
+							}
+							else if (align == mxConstants.ALIGN_RIGHT)
+							{
+								geo.x += (param - state.x - state.width) / s;
+							}
+							else if (align == mxConstants.ALIGN_TOP)
+							{
+								geo.y += (param - state.y) / s;
+							}
+							else if (align == mxConstants.ALIGN_MIDDLE)
+							{
+								geo.y += (param - state.y - state.height / 2) / s;
+							}
+							else if (align == mxConstants.ALIGN_BOTTOM)
+							{
+								geo.y += (param - state.y - state.height) / s;
+							}
+							else
+							{
+								geo.x += (param - state.x) / s;
+							}
+							
+							this.resizeCell(cells[i], geo);
+						}
+					}
+				}
+				
+				this.fireEvent(new mxEventObject(mxEvent.ALIGN_CELLS,
+						'align', align, 'cells', cells));
+			}
+			finally
+			{
+				this.model.endUpdate();
+			}
+		}
+	}
+	
+	return cells;
+};
+
+/**
+ * Function: flipEdge
+ * 
+ * Toggles the style of the given edge between null (or empty) and
+ * <alternateEdgeStyle>. This method fires <mxEvent.FLIP_EDGE> while the
+ * transaction is in progress. Returns the edge that was flipped.
+ * 
+ * Here is an example that overrides this implementation to invert the
+ * value of <mxConstants.STYLE_ELBOW> without removing any existing styles.
+ * 
+ * (code)
+ * graph.flipEdge = function(edge)
+ * {
+ *   if (edge != null)
+ *   {
+ *     var state = this.view.getState(edge);
+ *     var style = (state != null) ? state.style : this.getCellStyle(edge);
+ *     
+ *     if (style != null)
+ *     {
+ *       var elbow = mxUtils.getValue(style, mxConstants.STYLE_ELBOW,
+ *           mxConstants.ELBOW_HORIZONTAL);
+ *       var value = (elbow == mxConstants.ELBOW_HORIZONTAL) ?
+ *           mxConstants.ELBOW_VERTICAL : mxConstants.ELBOW_HORIZONTAL;
+ *       this.setCellStyles(mxConstants.STYLE_ELBOW, value, [edge]);
+ *     }
+ *   }
+ * };
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> whose style should be changed.
+ */
+mxGraph.prototype.flipEdge = function(edge)
+{
+	if (edge != null &&
+		this.alternateEdgeStyle != null)
+	{
+		this.model.beginUpdate();
+		try
+		{
+			var style = this.model.getStyle(edge);
+
+			if (style == null || style.length == 0)
+			{
+				this.model.setStyle(edge, this.alternateEdgeStyle);
+			}
+			else
+			{
+				this.model.setStyle(edge, null);
+			}
+
+			// Removes all existing control points
+			this.resetEdge(edge);
+			this.fireEvent(new mxEventObject(mxEvent.FLIP_EDGE, 'edge', edge));
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+
+	return edge;
+};
+
+/**
+ * Function: addImageBundle
+ *
+ * Adds the specified <mxImageBundle>.
+ */
+mxGraph.prototype.addImageBundle = function(bundle)
+{
+	this.imageBundles.push(bundle);
+};
+
+/**
+ * Function: removeImageBundle
+ * 
+ * Removes the specified <mxImageBundle>.
+ */
+mxGraph.prototype.removeImageBundle = function(bundle)
+{
+	var tmp = [];
+	
+	for (var i = 0; i < this.imageBundles.length; i++)
+	{
+		if (this.imageBundles[i] != bundle)
+		{
+			tmp.push(this.imageBundles[i]);
+		}
+	}
+	
+	this.imageBundles = tmp;
+};
+
+/**
+ * Function: getImageFromBundles
+ *
+ * Searches all <imageBundles> for the specified key and returns the value
+ * for the first match or null if the key is not found.
+ */
+mxGraph.prototype.getImageFromBundles = function(key)
+{
+	if (key != null)
+	{
+		for (var i = 0; i < this.imageBundles.length; i++)
+		{
+			var image = this.imageBundles[i].getImage(key);
+			
+			if (image != null)
+			{
+				return image;
+			}
+		}
+	}
+	
+	return null;
+};
+
+/**
+ * Group: Order
+ */
+
+/**
+ * Function: orderCells
+ * 
+ * Moves the given cells to the front or back. The change is carried out
+ * using <cellsOrdered>. This method fires <mxEvent.ORDER_CELLS> while the
+ * transaction is in progress.
+ * 
+ * Parameters:
+ * 
+ * back - Boolean that specifies if the cells should be moved to back.
+ * cells - Array of <mxCells> to move to the background. If null is
+ * specified then the selection cells are used.
+ */
+mxGraph.prototype.orderCells = function(back, cells)
+{
+	if (cells == null)
+	{
+		cells = mxUtils.sortCells(this.getSelectionCells(), true);
+	}
+
+	this.model.beginUpdate();
+	try
+	{
+		this.cellsOrdered(cells, back);
+		this.fireEvent(new mxEventObject(mxEvent.ORDER_CELLS,
+				'back', back, 'cells', cells));
+	}
+	finally
+	{
+		this.model.endUpdate();
+	}
+
+	return cells;
+};
+
+/**
+ * Function: cellsOrdered
+ * 
+ * Moves the given cells to the front or back. This method fires
+ * <mxEvent.CELLS_ORDERED> while the transaction is in progress.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> whose order should be changed.
+ * back - Boolean that specifies if the cells should be moved to back.
+ */
+mxGraph.prototype.cellsOrdered = function(cells, back)
+{
+	if (cells != null)
+	{
+		this.model.beginUpdate();
+		try
+		{
+			for (var i = 0; i < cells.length; i++)
+			{
+				var parent = this.model.getParent(cells[i]);
+
+				if (back)
+				{
+					this.model.add(parent, cells[i], i);
+				}
+				else
+				{
+					this.model.add(parent, cells[i],
+							this.model.getChildCount(parent) - 1);
+				}
+			}
+			
+			this.fireEvent(new mxEventObject(mxEvent.CELLS_ORDERED,
+					'back', back, 'cells', cells));
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Group: Grouping
+ */
+
+/**
+ * Function: groupCells
+ * 
+ * Adds the cells into the given group. The change is carried out using
+ * <cellsAdded>, <cellsMoved> and <cellsResized>. This method fires
+ * <mxEvent.GROUP_CELLS> while the transaction is in progress. Returns the
+ * new group. A group is only created if there is at least one entry in the
+ * given array of cells.
+ * 
+ * Parameters:
+ * 
+ * group - <mxCell> that represents the target group. If null is specified
+ * then a new group is created using <createGroupCell>.
+ * border - Optional integer that specifies the border between the child
+ * area and the group bounds. Default is 0.
+ * cells - Optional array of <mxCells> to be grouped. If null is specified
+ * then the selection cells are used.
+ */
+mxGraph.prototype.groupCells = function(group, border, cells)
+{
+	if (cells == null)
+	{
+		cells = mxUtils.sortCells(this.getSelectionCells(), true);
+	}
+
+	cells = this.getCellsForGroup(cells);
+
+	if (group == null)
+	{
+		group = this.createGroupCell(cells);
+	}
+
+	var bounds = this.getBoundsForGroup(group, cells, border);
+
+	if (cells.length > 0 && bounds != null)
+	{
+		// Uses parent of group or previous parent of first child
+		var parent = this.model.getParent(group);
+		
+		if (parent == null)
+		{
+			parent = this.model.getParent(cells[0]);
+		}
+
+		this.model.beginUpdate();
+		try
+		{
+			// Checks if the group has a geometry and
+			// creates one if one does not exist
+			if (this.getCellGeometry(group) == null)
+			{
+				this.model.setGeometry(group, new mxGeometry());
+			}
+
+			// Adds the group into the parent
+			var index = this.model.getChildCount(parent);
+			this.cellsAdded([group], parent, index, null, null, false, false, false);
+
+			// Adds the children into the group and moves
+			index = this.model.getChildCount(group);
+			this.cellsAdded(cells, group, index, null, null, false, false, false);
+			this.cellsMoved(cells, -bounds.x, -bounds.y, false, false, false);
+
+			// Resizes the group
+			this.cellsResized([group], [bounds], false);
+
+			this.fireEvent(new mxEventObject(mxEvent.GROUP_CELLS,
+					'group', group, 'border', border, 'cells', cells));
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+
+	return group;
+};
+
+/**
+ * Function: getCellsForGroup
+ * 
+ * Returns the cells with the same parent as the first cell
+ * in the given array.
+ */
+mxGraph.prototype.getCellsForGroup = function(cells)
+{
+	var result = [];
+
+	if (cells != null && cells.length > 0)
+	{
+		var parent = this.model.getParent(cells[0]);
+		result.push(cells[0]);
+
+		// Filters selection cells with the same parent
+		for (var i = 1; i < cells.length; i++)
+		{
+			if (this.model.getParent(cells[i]) == parent)
+			{
+				result.push(cells[i]);
+			}
+		}
+	}
+
+	return result;
+};
+
+/**
+ * Function: getBoundsForGroup
+ * 
+ * Returns the bounds to be used for the given group and children.
+ */
+mxGraph.prototype.getBoundsForGroup = function(group, children, border)
+{
+	var result = this.getBoundingBoxFromGeometry(children, true);
+	
+	if (result != null)
+	{
+		if (this.isSwimlane(group))
+		{
+			var size = this.getStartSize(group);
+			
+			result.x -= size.width;
+			result.y -= size.height;
+			result.width += size.width;
+			result.height += size.height;
+		}
+		
+		// Adds the border
+		if (border != null)
+		{
+			result.x -= border;
+			result.y -= border;
+			result.width += 2 * border;
+			result.height += 2 * border;
+		}
+	}			
+	
+	return result;
+};
+
+/**
+ * Function: createGroupCell
+ * 
+ * Hook for creating the group cell to hold the given array of <mxCells> if
+ * no group cell was given to the <group> function.
+ * 
+ * The following code can be used to set the style of new group cells.
+ * 
+ * (code)
+ * var graphCreateGroupCell = graph.createGroupCell;
+ * graph.createGroupCell = function(cells)
+ * {
+ *   var group = graphCreateGroupCell.apply(this, arguments);
+ *   group.setStyle('group');
+ *   
+ *   return group;
+ * };
+ */
+mxGraph.prototype.createGroupCell = function(cells)
+{
+	var group = new mxCell('');
+	group.setVertex(true);
+	group.setConnectable(false);
+	
+	return group;
+};
+
+/**
+ * Function: ungroupCells
+ * 
+ * Ungroups the given cells by moving the children the children to their
+ * parents parent and removing the empty groups. Returns the children that
+ * have been removed from the groups.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of cells to be ungrouped. If null is specified then the
+ * selection cells are used.
+ */
+mxGraph.prototype.ungroupCells = function(cells)
+{
+	var result = [];
+	
+	if (cells == null)
+	{
+		cells = this.getSelectionCells();
+
+		// Finds the cells with children
+		var tmp = [];
+		
+		for (var i = 0; i < cells.length; i++)
+		{
+			if (this.model.getChildCount(cells[i]) > 0)
+			{
+				tmp.push(cells[i]);
+			}
+		}
+
+		cells = tmp;
+	}
+	
+	if (cells != null && cells.length > 0)
+	{
+		this.model.beginUpdate();
+		try
+		{
+			for (var i = 0; i < cells.length; i++)
+			{
+				var children = this.model.getChildren(cells[i]);
+				
+				if (children != null && children.length > 0)
+				{
+					children = children.slice();
+					var parent = this.model.getParent(cells[i]);
+					var index = this.model.getChildCount(parent);
+
+					this.cellsAdded(children, parent, index, null, null, true);
+					result = result.concat(children);
+				}
+			}
+
+			this.removeCellsAfterUngroup(cells);
+			this.fireEvent(new mxEventObject(mxEvent.UNGROUP_CELLS, 'cells', cells));
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: removeCellsAfterUngroup
+ * 
+ * Hook to remove the groups after <ungroupCells>.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> that were ungrouped.
+ */
+mxGraph.prototype.removeCellsAfterUngroup = function(cells)
+{
+	this.cellsRemoved(this.addAllEdges(cells));
+};
+
+/**
+ * Function: removeCellsFromParent
+ * 
+ * Removes the specified cells from their parents and adds them to the
+ * default parent. Returns the cells that were removed from their parents.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> to be removed from their parents.
+ */
+mxGraph.prototype.removeCellsFromParent = function(cells)
+{
+	if (cells == null)
+	{
+		cells = this.getSelectionCells();
+	}
+	
+	this.model.beginUpdate();
+	try
+	{
+		var parent = this.getDefaultParent();
+		var index = this.model.getChildCount(parent);
+
+		this.cellsAdded(cells, parent, index, null, null, true);
+		this.fireEvent(new mxEventObject(mxEvent.REMOVE_CELLS_FROM_PARENT, 'cells', cells));
+	}
+	finally
+	{
+		this.model.endUpdate();
+	}
+
+	return cells;
+};
+
+/**
+ * Function: updateGroupBounds
+ * 
+ * Updates the bounds of the given groups to include all children and returns
+ * the passed-in cells. Call this with the groups in parent to child order,
+ * top-most group first, the cells are processed in reverse order and cells
+ * with no children are ignored.
+ * 
+ * Parameters:
+ * 
+ * cells - The groups whose bounds should be updated. If this is null, then
+ * the selection cells are used.
+ * border - Optional border to be added in the group. Default is 0.
+ * moveGroup - Optional boolean that allows the group to be moved. Default
+ * is false.
+ * topBorder - Optional top border to be added in the group. Default is 0.
+ * rightBorder - Optional top border to be added in the group. Default is 0.
+ * bottomBorder - Optional top border to be added in the group. Default is 0.
+ * leftBorder - Optional top border to be added in the group. Default is 0.
+ */
+mxGraph.prototype.updateGroupBounds = function(cells, border, moveGroup, topBorder, rightBorder, bottomBorder, leftBorder)
+{
+	if (cells == null)
+	{
+		cells = this.getSelectionCells();
+	}
+	
+	border = (border != null) ? border : 0;
+	moveGroup = (moveGroup != null) ? moveGroup : false;
+	topBorder = (topBorder != null) ? topBorder : 0;
+	rightBorder = (rightBorder != null) ? rightBorder : 0;
+	bottomBorder = (bottomBorder != null) ? bottomBorder : 0;
+	leftBorder = (leftBorder != null) ? leftBorder : 0;
+
+	this.model.beginUpdate();
+	try
+	{
+		for (var i = cells.length - 1; i >= 0; i--)
+		{
+			var geo = this.getCellGeometry(cells[i]);
+			
+			if (geo != null)
+			{
+				var children = this.getChildCells(cells[i]);
+				
+				if (children != null && children.length > 0)
+				{
+					var bounds = this.getBoundingBoxFromGeometry(children, true);
+					
+					if (bounds != null && bounds.width > 0 && bounds.height > 0)
+					{
+						var left = 0;
+						var top = 0;
+						
+						// Adds the size of the title area for swimlanes
+						if (this.isSwimlane(cells[i]))
+						{
+							var size = this.getStartSize(cells[i]);
+							left = size.width;
+							top = size.height;
+						}
+						
+						geo = geo.clone();
+						
+						if (moveGroup)
+						{
+							geo.x = Math.round(geo.x + bounds.x - border - left - leftBorder);
+							geo.y = Math.round(geo.y + bounds.y - border - top - topBorder);
+						}
+						
+						geo.width = Math.round(bounds.width + 2 * border + left + leftBorder + rightBorder);
+						geo.height = Math.round(bounds.height + 2 * border + top + topBorder + bottomBorder);
+						
+						this.model.setGeometry(cells[i], geo);
+						this.moveCells(children, border + left - bounds.x + leftBorder,
+								border + top - bounds.y + topBorder);
+					}
+				}
+			}
+		}
+	}
+	finally
+	{
+		this.model.endUpdate();
+	}
+
+	return cells;
+};
+
+/**
+ * Function: getBoundingBox
+ * 
+ * Returns the bounding box for the given array of <mxCells>. The bounding box for
+ * each cell and its descendants is computed using <mxGraphView.getBoundingBox>.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> whose bounding box should be returned.
+ */
+mxGraph.prototype.getBoundingBox = function(cells)
+{
+	var result = null;
+	
+	if (cells != null && cells.length > 0)
+	{
+		for (var i = 0; i < cells.length; i++)
+		{
+			if (this.model.isVertex(cells[i]) || this.model.isEdge(cells[i]))
+			{
+				var bbox = this.view.getBoundingBox(this.view.getState(cells[i]), true);
+			
+				if (bbox != null)
+				{
+					if (result == null)
+					{
+						result = mxRectangle.fromRectangle(bbox);
+					}
+					else
+					{
+						result.add(bbox);
+					}
+				}
+			}
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Group: Cell cloning, insertion and removal
+ */
+
+/**
+ * Function: cloneCells
+ * 
+ * Returns the clones for the given cells. The clones are created recursively
+ * using <mxGraphModel.cloneCells>. If the terminal of an edge is not in the
+ * given array, then the respective end is assigned a terminal point and the
+ * terminal is removed.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> to be cloned.
+ * allowInvalidEdges - Optional boolean that specifies if invalid edges
+ * should be cloned. Default is true.
+ * mapping - Optional mapping for existing clones.
+ */
+mxGraph.prototype.cloneCells = function(cells, allowInvalidEdges, mapping)
+{
+	allowInvalidEdges = (allowInvalidEdges != null) ? allowInvalidEdges : true;
+	var clones = null;
+	
+	if (cells != null)
+	{
+		// Creates a dictionary for fast lookups
+		var dict = new mxDictionary();
+		var tmp = [];
+		
+		for (var i = 0; i < cells.length; i++)
+		{
+			dict.put(cells[i], true);
+			tmp.push(cells[i]);
+		}
+		
+		if (tmp.length > 0)
+		{
+			var scale = this.view.scale;
+			var trans = this.view.translate;
+			clones = this.model.cloneCells(cells, true, mapping);
+		
+			for (var i = 0; i < cells.length; i++)
+			{
+				if (!allowInvalidEdges && this.model.isEdge(clones[i]) &&
+					this.getEdgeValidationError(clones[i],
+						this.model.getTerminal(clones[i], true),
+						this.model.getTerminal(clones[i], false)) != null)
+				{
+					clones[i] = null;
+				}
+				else
+				{
+					var g = this.model.getGeometry(clones[i]);
+					
+					if (g != null)
+					{
+						var state = this.view.getState(cells[i]);
+						var pstate = this.view.getState(this.model.getParent(cells[i]));
+						
+						if (state != null && pstate != null)
+						{
+							var dx = pstate.origin.x;
+							var dy = pstate.origin.y;
+							
+							if (this.model.isEdge(clones[i]))
+							{
+								var pts = state.absolutePoints;
+								
+								// Checks if the source is cloned or sets the terminal point
+								var src = this.model.getTerminal(cells[i], true);
+								
+								while (src != null && !dict.get(src))
+								{
+									src = this.model.getParent(src);
+								}
+								
+								if (src == null)
+								{
+									g.setTerminalPoint(
+										new mxPoint(pts[0].x / scale - trans.x,
+											pts[0].y / scale - trans.y), true);
+								}
+								
+								// Checks if the target is cloned or sets the terminal point
+								var trg = this.model.getTerminal(cells[i], false);
+								
+								while (trg != null && !dict.get(trg))
+								{
+									trg = this.model.getParent(trg);
+								}
+								
+								if (trg == null)
+								{
+									var n = pts.length - 1;
+									g.setTerminalPoint(
+										new mxPoint(pts[n].x / scale - trans.x,
+											pts[n].y / scale - trans.y), false);
+								}
+								
+								// Translates the control points
+								var points = g.points;
+								
+								if (points != null)
+								{
+									for (var j = 0; j < points.length; j++)
+									{
+										points[j].x += dx;
+										points[j].y += dy;
+									}
+								}
+							}
+							else
+							{
+								g.translate(dx, dy);
+							}
+						}
+					}
+				}
+			}
+		}
+		else
+		{
+			clones = [];
+		}
+	}
+	
+	return clones;
+};
+
+/**
+ * Function: insertVertex
+ * 
+ * Adds a new vertex into the given parent <mxCell> using value as the user
+ * object and the given coordinates as the <mxGeometry> of the new vertex.
+ * The id and style are used for the respective properties of the new
+ * <mxCell>, which is returned.
+ *
+ * When adding new vertices from a mouse event, one should take into
+ * account the offset of the graph container and the scale and translation
+ * of the view in order to find the correct unscaled, untranslated
+ * coordinates using <mxGraph.getPointForEvent> as follows:
+ * 
+ * (code)
+ * var pt = graph.getPointForEvent(evt);
+ * var parent = graph.getDefaultParent();
+ * graph.insertVertex(parent, null,
+ * 			'Hello, World!', x, y, 220, 30);
+ * (end)
+ * 
+ * For adding image cells, the style parameter can be assigned as
+ * 
+ * (code)
+ * stylename;image=imageUrl
+ * (end)
+ * 
+ * See <mxGraph> for more information on using images.
+ *
+ * Parameters:
+ * 
+ * parent - <mxCell> that specifies the parent of the new vertex.
+ * id - Optional string that defines the Id of the new vertex.
+ * value - Object to be used as the user object.
+ * x - Integer that defines the x coordinate of the vertex.
+ * y - Integer that defines the y coordinate of the vertex.
+ * width - Integer that defines the width of the vertex.
+ * height - Integer that defines the height of the vertex.
+ * style - Optional string that defines the cell style.
+ * relative - Optional boolean that specifies if the geometry is relative.
+ * Default is false.
+ */
+mxGraph.prototype.insertVertex = function(parent, id, value,
+	x, y, width, height, style, relative)
+{
+	var vertex = this.createVertex(parent, id, value, x, y, width, height, style, relative);
+
+	return this.addCell(vertex, parent);
+};
+
+/**
+ * Function: createVertex
+ * 
+ * Hook method that creates the new vertex for <insertVertex>.
+ */
+mxGraph.prototype.createVertex = function(parent, id, value,
+		x, y, width, height, style, relative)
+{
+	// Creates the geometry for the vertex
+	var geometry = new mxGeometry(x, y, width, height);
+	geometry.relative = (relative != null) ? relative : false;
+	
+	// Creates the vertex
+	var vertex = new mxCell(value, geometry, style);
+	vertex.setId(id);
+	vertex.setVertex(true);
+	vertex.setConnectable(true);
+	
+	return vertex;
+};
+	
+/**
+ * Function: insertEdge
+ * 
+ * Adds a new edge into the given parent <mxCell> using value as the user
+ * object and the given source and target as the terminals of the new edge.
+ * The id and style are used for the respective properties of the new
+ * <mxCell>, which is returned.
+ *
+ * Parameters:
+ * 
+ * parent - <mxCell> that specifies the parent of the new edge.
+ * id - Optional string that defines the Id of the new edge.
+ * value - JavaScript object to be used as the user object.
+ * source - <mxCell> that defines the source of the edge.
+ * target - <mxCell> that defines the target of the edge.
+ * style - Optional string that defines the cell style.
+ */
+mxGraph.prototype.insertEdge = function(parent, id, value, source, target, style)
+{
+	var edge = this.createEdge(parent, id, value, source, target, style);
+	
+	return this.addEdge(edge, parent, source, target);
+};
+
+/**
+ * Function: createEdge
+ * 
+ * Hook method that creates the new edge for <insertEdge>. This
+ * implementation does not set the source and target of the edge, these
+ * are set when the edge is added to the model.
+ * 
+ */
+mxGraph.prototype.createEdge = function(parent, id, value, source, target, style)
+{
+	// Creates the edge
+	var edge = new mxCell(value, new mxGeometry(), style);
+	edge.setId(id);
+	edge.setEdge(true);
+	edge.geometry.relative = true;
+	
+	return edge;
+};
+
+/**
+ * Function: addEdge
+ * 
+ * Adds the edge to the parent and connects it to the given source and
+ * target terminals. This is a shortcut method. Returns the edge that was
+ * added.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> to be inserted into the given parent.
+ * parent - <mxCell> that represents the new parent. If no parent is
+ * given then the default parent is used.
+ * source - Optional <mxCell> that represents the source terminal.
+ * target - Optional <mxCell> that represents the target terminal.
+ * index - Optional index to insert the cells at. Default is to append.
+ */
+mxGraph.prototype.addEdge = function(edge, parent, source, target, index)
+{
+	return this.addCell(edge, parent, index, source, target);
+};
+
+/**
+ * Function: addCell
+ * 
+ * Adds the cell to the parent and connects it to the given source and
+ * target terminals. This is a shortcut method. Returns the cell that was
+ * added.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to be inserted into the given parent.
+ * parent - <mxCell> that represents the new parent. If no parent is
+ * given then the default parent is used.
+ * index - Optional index to insert the cells at. Default is to append.
+ * source - Optional <mxCell> that represents the source terminal.
+ * target - Optional <mxCell> that represents the target terminal.
+ */
+mxGraph.prototype.addCell = function(cell, parent, index, source, target)
+{
+	return this.addCells([cell], parent, index, source, target)[0];
+};
+
+/**
+ * Function: addCells
+ * 
+ * Adds the cells to the parent at the given index, connecting each cell to
+ * the optional source and target terminal. The change is carried out using
+ * <cellsAdded>. This method fires <mxEvent.ADD_CELLS> while the
+ * transaction is in progress. Returns the cells that were added.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> to be inserted.
+ * parent - <mxCell> that represents the new parent. If no parent is
+ * given then the default parent is used.
+ * index - Optional index to insert the cells at. Default is to append.
+ * source - Optional source <mxCell> for all inserted cells.
+ * target - Optional target <mxCell> for all inserted cells.
+ */
+mxGraph.prototype.addCells = function(cells, parent, index, source, target)
+{
+	if (parent == null)
+	{
+		parent = this.getDefaultParent();
+	}
+	
+	if (index == null)
+	{
+		index = this.model.getChildCount(parent);
+	}
+	
+	this.model.beginUpdate();
+	try
+	{
+		this.cellsAdded(cells, parent, index, source, target, false, true);
+		this.fireEvent(new mxEventObject(mxEvent.ADD_CELLS, 'cells', cells,
+				'parent', parent, 'index', index, 'source', source, 'target', target));
+	}
+	finally
+	{
+		this.model.endUpdate();
+	}
+
+	return cells;
+};
+
+/**
+ * Function: cellsAdded
+ * 
+ * Adds the specified cells to the given parent. This method fires
+ * <mxEvent.CELLS_ADDED> while the transaction is in progress.
+ */
+mxGraph.prototype.cellsAdded = function(cells, parent, index, source, target, absolute, constrain, extend)
+{
+	if (cells != null && parent != null && index != null)
+	{
+		this.model.beginUpdate();
+		try
+		{
+			var parentState = (absolute) ? this.view.getState(parent) : null;
+			var o1 = (parentState != null) ? parentState.origin : null;
+			var zero = new mxPoint(0, 0);
+
+			for (var i = 0; i < cells.length; i++)
+			{
+				if (cells[i] == null)
+				{
+					index--;
+				}
+				else
+				{
+					var previous = this.model.getParent(cells[i]);
+	
+					// Keeps the cell at its absolute location
+					if (o1 != null && cells[i] != parent && parent != previous)
+					{
+						var oldState = this.view.getState(previous);
+						var o2 = (oldState != null) ? oldState.origin : zero;
+						var geo = this.model.getGeometry(cells[i]);
+	
+						if (geo != null)
+						{
+							var dx = o2.x - o1.x;
+							var dy = o2.y - o1.y;
+	
+							// FIXME: Cells should always be inserted first before any other edit
+							// to avoid forward references in sessions.
+							geo = geo.clone();
+							geo.translate(dx, dy);
+							
+							if (!geo.relative && this.model.isVertex(cells[i]) &&
+								!this.isAllowNegativeCoordinates())
+							{
+								geo.x = Math.max(0, geo.x);
+								geo.y = Math.max(0, geo.y);
+							}
+							
+							this.model.setGeometry(cells[i], geo);
+						}
+					}
+	
+					// Decrements all following indices
+					// if cell is already in parent
+					if (parent == previous && index + i > this.model.getChildCount(parent))
+					{
+						index--;
+					}
+
+					this.model.add(parent, cells[i], index + i);
+					
+					if (this.autoSizeCellsOnAdd)
+					{
+						this.autoSizeCell(cells[i], true);
+					}
+
+					// Extends the parent or constrains the child
+					if ((extend == null || extend) &&
+						this.isExtendParentsOnAdd(cells[i]) && this.isExtendParent(cells[i]))
+					{
+						this.extendParent(cells[i]);
+					}
+					
+					// Additionally constrains the child after extending the parent
+					if (constrain == null || constrain)
+					{
+						this.constrainChild(cells[i]);
+					}
+					
+					// Sets the source terminal
+					if (source != null)
+					{
+						this.cellConnected(cells[i], source, true);
+					}
+					
+					// Sets the target terminal
+					if (target != null)
+					{
+						this.cellConnected(cells[i], target, false);
+					}
+				}
+			}
+			
+			this.fireEvent(new mxEventObject(mxEvent.CELLS_ADDED, 'cells', cells,
+				'parent', parent, 'index', index, 'source', source, 'target', target,
+				'absolute', absolute));
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: autoSizeCell
+ * 
+ * Resizes the specified cell to just fit around the its label and/or children
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCells> to be resized.
+ * recurse - Optional boolean which specifies if all descendants should be
+ * autosized. Default is true.
+ */
+mxGraph.prototype.autoSizeCell = function(cell, recurse)
+{
+	recurse = (recurse != null) ? recurse : true;
+	
+	if (recurse)
+	{
+		var childCount = this.model.getChildCount(cell);
+		
+		for (var i = 0; i < childCount; i++)
+		{
+			this.autoSizeCell(this.model.getChildAt(cell, i));
+		}
+	}
+
+	if (this.getModel().isVertex(cell) && this.isAutoSizeCell(cell))
+	{
+		this.updateCellSize(cell);
+	}
+};
+
+/**
+ * Function: removeCells
+ * 
+ * Removes the given cells from the graph including all connected edges if
+ * includeEdges is true. The change is carried out using <cellsRemoved>.
+ * This method fires <mxEvent.REMOVE_CELLS> while the transaction is in
+ * progress. The removed cells are returned as an array.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> to remove. If null is specified then the
+ * selection cells which are deletable are used.
+ * includeEdges - Optional boolean which specifies if all connected edges
+ * should be removed as well. Default is true.
+ */
+mxGraph.prototype.removeCells = function(cells, includeEdges)
+{
+	includeEdges = (includeEdges != null) ? includeEdges : true;
+	
+	if (cells == null)
+	{
+		cells = this.getDeletableCells(this.getSelectionCells());
+	}
+
+	// Adds all edges to the cells
+	if (includeEdges)
+	{
+		// FIXME: Remove duplicate cells in result or do not add if
+		// in cells or descendant of cells
+		cells = this.getDeletableCells(this.addAllEdges(cells));
+	}
+
+	this.model.beginUpdate();
+	try
+	{
+		this.cellsRemoved(cells);
+		this.fireEvent(new mxEventObject(mxEvent.REMOVE_CELLS, 
+				'cells', cells, 'includeEdges', includeEdges));
+	}
+	finally
+	{
+		this.model.endUpdate();
+	}
+	
+	return cells;
+};
+
+/**
+ * Function: cellsRemoved
+ * 
+ * Removes the given cells from the model. This method fires
+ * <mxEvent.CELLS_REMOVED> while the transaction is in progress.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> to remove.
+ */
+mxGraph.prototype.cellsRemoved = function(cells)
+{
+	if (cells != null && cells.length > 0)
+	{
+		var scale = this.view.scale;
+		var tr = this.view.translate;
+		
+		this.model.beginUpdate();
+		try
+		{
+			// Creates hashtable for faster lookup
+			var dict = new mxDictionary();
+			
+			for (var i = 0; i < cells.length; i++)
+			{
+				dict.put(cells[i], true);
+			}
+			
+			for (var i = 0; i < cells.length; i++)
+			{
+				// Disconnects edges which are not in cells
+				var edges = this.getAllEdges([cells[i]]);
+				
+				var disconnectTerminal = mxUtils.bind(this, function(edge, source)
+				{
+					var geo = this.model.getGeometry(edge);
+
+					if (geo != null)
+					{
+						var state = this.view.getState(edge);
+								
+						if (state != null)
+						{
+							// Checks which side of the edge is being disconnected
+							var tmp = state.getVisibleTerminal(source);
+							var connected = false;
+							
+							while (tmp != null)
+							{
+								if (cells[i] == tmp)
+								{
+									connected = true;
+									break;
+								}
+								
+								tmp = this.model.getParent(tmp);
+							}
+							
+							if (connected)
+							{
+								var dx = tr.x;
+								var dy = tr.y;
+								var parentState = this.view.getState(this.model.getParent(edge));
+								
+								if (parentState != null && this.model.isVertex(parentState.cell))
+								{
+									dx = parentState.x / scale;
+									dy = parentState.y / scale;
+								}
+								
+								geo = geo.clone();
+								var pts = state.absolutePoints;
+								var n = (source) ? 0 : pts.length - 1;
+								geo.setTerminalPoint(new mxPoint(pts[n].x / scale - dx, pts[n].y / scale - dy), source);
+								this.model.setTerminal(edges[j], null, source);
+								this.model.setGeometry(edges[j], geo);
+							}
+						}
+					}
+				});
+				
+				for (var j = 0; j < edges.length; j++)
+				{
+					if (!dict.get(edges[j]))
+					{
+						disconnectTerminal(edges[j], true);
+						disconnectTerminal(edges[j], false);
+					}
+				}
+
+				this.model.remove(cells[i]);
+			}
+			
+			this.fireEvent(new mxEventObject(mxEvent.CELLS_REMOVED, 'cells', cells));
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: splitEdge
+ * 
+ * Splits the given edge by adding the newEdge between the previous source
+ * and the given cell and reconnecting the source of the given edge to the
+ * given cell. This method fires <mxEvent.SPLIT_EDGE> while the transaction
+ * is in progress. Returns the new edge that was inserted.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> that represents the edge to be splitted.
+ * cells - <mxCells> that represents the cells to insert into the edge.
+ * newEdge - <mxCell> that represents the edge to be inserted.
+ * dx - Optional integer that specifies the vector to move the cells.
+ * dy - Optional integer that specifies the vector to move the cells.
+ */
+mxGraph.prototype.splitEdge = function(edge, cells, newEdge, dx, dy)
+{
+	dx = dx || 0;
+	dy = dy || 0;
+
+	var parent = this.model.getParent(edge);
+	var source = this.model.getTerminal(edge, true);
+
+	this.model.beginUpdate();
+	try
+	{
+		if (newEdge == null)
+		{
+			newEdge = this.cloneCells([edge])[0];
+			
+			// Removes waypoints before/after new cell
+			var state = this.view.getState(edge);
+			var geo = this.getCellGeometry(newEdge);
+			
+			if (geo != null && geo.points != null && state != null)
+			{
+				var t = this.view.translate;
+				var s = this.view.scale;
+				var idx = mxUtils.findNearestSegment(state, (dx + t.x) * s, (dy + t.y) * s);
+				geo.points = geo.points.slice(0, idx);
+								
+				geo = this.getCellGeometry(edge);
+				
+				if (geo != null && geo.points != null)
+				{
+					geo = geo.clone();
+					geo.points = geo.points.slice(idx);
+					this.model.setGeometry(edge, geo);
+				}
+			}
+		}
+		
+		this.cellsMoved(cells, dx, dy, false, false);
+		this.cellsAdded(cells, parent, this.model.getChildCount(parent), null, null,
+				true);
+		this.cellsAdded([newEdge], parent, this.model.getChildCount(parent),
+				source, cells[0], false);
+		this.cellConnected(edge, cells[0], true);
+		this.fireEvent(new mxEventObject(mxEvent.SPLIT_EDGE, 'edge', edge,
+				'cells', cells, 'newEdge', newEdge, 'dx', dx, 'dy', dy));
+	}
+	finally
+	{
+		this.model.endUpdate();
+	}
+
+	return newEdge;
+};
+
+/**
+ * Group: Cell visibility
+ */
+
+/**
+ * Function: toggleCells
+ * 
+ * Sets the visible state of the specified cells and all connected edges
+ * if includeEdges is true. The change is carried out using <cellsToggled>.
+ * This method fires <mxEvent.TOGGLE_CELLS> while the transaction is in
+ * progress. Returns the cells whose visible state was changed.
+ * 
+ * Parameters:
+ * 
+ * show - Boolean that specifies the visible state to be assigned.
+ * cells - Array of <mxCells> whose visible state should be changed. If
+ * null is specified then the selection cells are used.
+ * includeEdges - Optional boolean indicating if the visible state of all
+ * connected edges should be changed as well. Default is true.
+ */
+mxGraph.prototype.toggleCells = function(show, cells, includeEdges)
+{
+	if (cells == null)
+	{
+		cells = this.getSelectionCells();
+	}
+
+	// Adds all connected edges recursively
+	if (includeEdges)
+	{
+		cells = this.addAllEdges(cells);
+	}
+
+	this.model.beginUpdate();
+	try
+	{
+		this.cellsToggled(cells, show);
+		this.fireEvent(new mxEventObject(mxEvent.TOGGLE_CELLS,
+			'show', show, 'cells', cells, 'includeEdges', includeEdges));
+	}
+	finally
+	{
+		this.model.endUpdate();
+	}
+
+	return cells;
+};
+
+/**
+ * Function: cellsToggled
+ * 
+ * Sets the visible state of the specified cells.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> whose visible state should be changed.
+ * show - Boolean that specifies the visible state to be assigned.
+ */
+mxGraph.prototype.cellsToggled = function(cells, show)
+{
+	if (cells != null && cells.length > 0)
+	{
+		this.model.beginUpdate();
+		try
+		{
+			for (var i = 0; i < cells.length; i++)
+			{
+				this.model.setVisible(cells[i], show);
+			}
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Group: Folding
+ */
+
+/**
+ * Function: foldCells
+ * 
+ * Sets the collapsed state of the specified cells and all descendants
+ * if recurse is true. The change is carried out using <cellsFolded>.
+ * This method fires <mxEvent.FOLD_CELLS> while the transaction is in
+ * progress. Returns the cells whose collapsed state was changed.
+ * 
+ * Parameters:
+ * 
+ * collapsed - Boolean indicating the collapsed state to be assigned.
+ * recurse - Optional boolean indicating if the collapsed state of all
+ * descendants should be set. Default is false.
+ * cells - Array of <mxCells> whose collapsed state should be set. If
+ * null is specified then the foldable selection cells are used.
+ * checkFoldable - Optional boolean indicating of isCellFoldable should be
+ * checked. Default is false.
+ * evt - Optional native event that triggered the invocation.
+ */
+mxGraph.prototype.foldCells = function(collapse, recurse, cells, checkFoldable, evt)
+{
+	recurse = (recurse != null) ? recurse : false;
+	
+	if (cells == null)
+	{
+		cells = this.getFoldableCells(this.getSelectionCells(), collapse);
+	}
+
+	this.stopEditing(false);
+
+	this.model.beginUpdate();
+	try
+	{
+		this.cellsFolded(cells, collapse, recurse, checkFoldable);
+		this.fireEvent(new mxEventObject(mxEvent.FOLD_CELLS,
+			'collapse', collapse, 'recurse', recurse, 'cells', cells));
+	}
+	finally
+	{
+		this.model.endUpdate();
+	}
+
+	return cells;
+};
+
+/**
+ * Function: cellsFolded
+ * 
+ * Sets the collapsed state of the specified cells. This method fires
+ * <mxEvent.CELLS_FOLDED> while the transaction is in progress. Returns the
+ * cells whose collapsed state was changed.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> whose collapsed state should be set.
+ * collapsed - Boolean indicating the collapsed state to be assigned.
+ * recurse - Boolean indicating if the collapsed state of all descendants
+ * should be set.
+ * checkFoldable - Optional boolean indicating of isCellFoldable should be
+ * checked. Default is false.
+ */
+mxGraph.prototype.cellsFolded = function(cells, collapse, recurse, checkFoldable)
+{
+	if (cells != null && cells.length > 0)
+	{
+		this.model.beginUpdate();
+		try
+		{
+			for (var i = 0; i < cells.length; i++)
+			{
+				if ((!checkFoldable || this.isCellFoldable(cells[i], collapse)) &&
+					collapse != this.isCellCollapsed(cells[i]))
+				{
+					this.model.setCollapsed(cells[i], collapse);
+					this.swapBounds(cells[i], collapse);
+
+					if (this.isExtendParent(cells[i]))
+					{
+						this.extendParent(cells[i]);
+					}
+
+					if (recurse)
+					{
+						var children = this.model.getChildren(cells[i]);
+						this.foldCells(children, collapse, recurse);
+					}
+					
+					this.constrainChild(cells[i]);
+				}
+			}
+			
+			this.fireEvent(new mxEventObject(mxEvent.CELLS_FOLDED,
+				'cells', cells, 'collapse', collapse, 'recurse', recurse));
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: swapBounds
+ * 
+ * Swaps the alternate and the actual bounds in the geometry of the given
+ * cell invoking <updateAlternateBounds> before carrying out the swap.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> for which the bounds should be swapped.
+ * willCollapse - Boolean indicating if the cell is going to be collapsed.
+ */
+mxGraph.prototype.swapBounds = function(cell, willCollapse)
+{
+	if (cell != null)
+	{
+		var geo = this.model.getGeometry(cell);
+		
+		if (geo != null)
+		{
+			geo = geo.clone();
+			
+			this.updateAlternateBounds(cell, geo, willCollapse);
+			geo.swap();
+			
+			this.model.setGeometry(cell, geo);
+		}
+	}
+};
+
+/**
+ * Function: updateAlternateBounds
+ * 
+ * Updates or sets the alternate bounds in the given geometry for the given
+ * cell depending on whether the cell is going to be collapsed. If no
+ * alternate bounds are defined in the geometry and
+ * <collapseToPreferredSize> is true, then the preferred size is used for
+ * the alternate bounds. The top, left corner is always kept at the same
+ * location.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> for which the geometry is being udpated.
+ * g - <mxGeometry> for which the alternate bounds should be updated.
+ * willCollapse - Boolean indicating if the cell is going to be collapsed.
+ */
+mxGraph.prototype.updateAlternateBounds = function(cell, geo, willCollapse)
+{
+	if (cell != null && geo != null)
+	{
+		var state = this.view.getState(cell);
+		var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+		if (geo.alternateBounds == null)
+		{
+			var bounds = geo;
+			
+			if (this.collapseToPreferredSize)
+			{
+				var tmp = this.getPreferredSizeForCell(cell);
+				
+				if (tmp != null)
+				{
+					bounds = tmp;
+
+					var startSize = mxUtils.getValue(style, mxConstants.STYLE_STARTSIZE);
+
+					if (startSize > 0)
+					{
+						bounds.height = Math.max(bounds.height, startSize);
+					}
+				}
+			}
+			
+			geo.alternateBounds = new mxRectangle(0, 0, bounds.width, bounds.height);
+		}
+		
+		if (geo.alternateBounds != null)
+		{
+			geo.alternateBounds.x = geo.x;
+			geo.alternateBounds.y = geo.y;
+			
+			var alpha = mxUtils.toRadians(style[mxConstants.STYLE_ROTATION] || 0);
+			
+			if (alpha != 0)
+			{
+				var dx = geo.alternateBounds.getCenterX() - geo.getCenterX();
+				var dy = geo.alternateBounds.getCenterY() - geo.getCenterY();
+	
+				var cos = Math.cos(alpha);
+				var sin = Math.sin(alpha);
+	
+				var dx2 = cos * dx - sin * dy;
+				var dy2 = sin * dx + cos * dy;
+				
+				geo.alternateBounds.x += dx2 - dx;
+				geo.alternateBounds.y += dy2 - dy;
+			}
+		}
+	}
+};
+
+/**
+ * Function: addAllEdges
+ * 
+ * Returns an array with the given cells and all edges that are connected
+ * to a cell or one of its descendants.
+ */
+mxGraph.prototype.addAllEdges = function(cells)
+{
+	var allCells = cells.slice();
+	
+	return mxUtils.removeDuplicates(allCells.concat(this.getAllEdges(cells)));
+};
+
+/**
+ * Function: getAllEdges
+ * 
+ * Returns all edges connected to the given cells or its descendants.
+ */
+mxGraph.prototype.getAllEdges = function(cells)
+{
+	var edges = [];
+	
+	if (cells != null)
+	{
+		for (var i = 0; i < cells.length; i++)
+		{
+			var edgeCount = this.model.getEdgeCount(cells[i]);
+			
+			for (var j = 0; j < edgeCount; j++)
+			{
+				edges.push(this.model.getEdgeAt(cells[i], j));
+			}
+
+			// Recurses
+			var children = this.model.getChildren(cells[i]);
+			edges = edges.concat(this.getAllEdges(children));
+		}
+	}
+	
+	return edges;
+};
+
+/**
+ * Group: Cell sizing
+ */
+
+/**
+ * Function: updateCellSize
+ * 
+ * Updates the size of the given cell in the model using <cellSizeUpdated>.
+ * This method fires <mxEvent.UPDATE_CELL_SIZE> while the transaction is in
+ * progress. Returns the cell whose size was updated.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose size should be updated.
+ */
+mxGraph.prototype.updateCellSize = function(cell, ignoreChildren)
+{
+	ignoreChildren = (ignoreChildren != null) ? ignoreChildren : false;
+	
+	this.model.beginUpdate();				
+	try
+	{
+		this.cellSizeUpdated(cell, ignoreChildren);
+		this.fireEvent(new mxEventObject(mxEvent.UPDATE_CELL_SIZE,
+				'cell', cell, 'ignoreChildren', ignoreChildren));
+	}
+	finally
+	{
+		this.model.endUpdate();
+	}
+	
+	return cell;
+};
+
+/**
+ * Function: cellSizeUpdated
+ * 
+ * Updates the size of the given cell in the model using
+ * <getPreferredSizeForCell> to get the new size.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> for which the size should be changed.
+ */
+mxGraph.prototype.cellSizeUpdated = function(cell, ignoreChildren)
+{
+	if (cell != null)
+	{
+		this.model.beginUpdate();				
+		try
+		{
+			var size = this.getPreferredSizeForCell(cell);
+			var geo = this.model.getGeometry(cell);
+			
+			if (size != null && geo != null)
+			{
+				var collapsed = this.isCellCollapsed(cell);
+				geo = geo.clone();
+
+				if (this.isSwimlane(cell))
+				{
+					var state = this.view.getState(cell);
+					var style = (state != null) ? state.style : this.getCellStyle(cell);
+					var cellStyle = this.model.getStyle(cell);
+
+					if (cellStyle == null)
+					{
+						cellStyle = '';
+					}
+
+					if (mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, true))
+					{
+						cellStyle = mxUtils.setStyle(cellStyle,
+								mxConstants.STYLE_STARTSIZE, size.height + 8);
+
+						if (collapsed)
+						{
+							geo.height = size.height + 8;
+						}
+
+						geo.width = size.width;
+					}
+					else
+					{
+						cellStyle = mxUtils.setStyle(cellStyle,
+								mxConstants.STYLE_STARTSIZE, size.width + 8);
+
+						if (collapsed)
+						{
+							geo.width = size.width + 8;
+						}
+
+						geo.height = size.height;
+					}
+
+					this.model.setStyle(cell, cellStyle);
+				}
+				else
+				{
+					geo.width = size.width;
+					geo.height = size.height;
+				}
+
+				if (!ignoreChildren && !collapsed)
+				{
+					var bounds = this.view.getBounds(this.model.getChildren(cell));
+
+					if (bounds != null)
+					{
+						var tr = this.view.translate;
+						var scale = this.view.scale;
+
+						var width = (bounds.x + bounds.width) / scale - geo.x - tr.x;
+						var height = (bounds.y + bounds.height) / scale - geo.y - tr.y;
+
+						geo.width = Math.max(geo.width, width);
+						geo.height = Math.max(geo.height, height);
+					}
+				}
+
+				this.cellsResized([cell], [geo], false);
+			}
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: getPreferredSizeForCell
+ * 
+ * Returns the preferred width and height of the given <mxCell> as an
+ * <mxRectangle>. To implement a minimum width, add a new style eg.
+ * minWidth in the vertex and override this method as follows.
+ * 
+ * (code)
+ * var graphGetPreferredSizeForCell = graph.getPreferredSizeForCell;
+ * graph.getPreferredSizeForCell = function(cell)
+ * {
+ *   var result = graphGetPreferredSizeForCell.apply(this, arguments);
+ *   var style = this.getCellStyle(cell);
+ *   
+ *   if (style['minWidth'] > 0)
+ *   {
+ *     result.width = Math.max(style['minWidth'], result.width);
+ *   }
+ * 
+ *   return result;
+ * };
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> for which the preferred size should be returned.
+ */
+mxGraph.prototype.getPreferredSizeForCell = function(cell)
+{
+	var result = null;
+	
+	if (cell != null)
+	{
+		var state = this.view.getState(cell) || this.view.createState(cell);
+		var style = state.style;
+
+		if (!this.model.isEdge(cell))
+		{
+			var fontSize = style[mxConstants.STYLE_FONTSIZE] || mxConstants.DEFAULT_FONTSIZE;
+			var dx = 0;
+			var dy = 0;
+			
+			// Adds dimension of image if shape is a label
+			if (this.getImage(state) != null || style[mxConstants.STYLE_IMAGE] != null)
+			{
+				if (style[mxConstants.STYLE_SHAPE] == mxConstants.SHAPE_LABEL)
+				{
+					if (style[mxConstants.STYLE_VERTICAL_ALIGN] == mxConstants.ALIGN_MIDDLE)
+					{
+						dx += parseFloat(style[mxConstants.STYLE_IMAGE_WIDTH]) || mxLabel.prototype.imageSize;
+					}
+					
+					if (style[mxConstants.STYLE_ALIGN] != mxConstants.ALIGN_CENTER)
+					{
+						dy += parseFloat(style[mxConstants.STYLE_IMAGE_HEIGHT]) || mxLabel.prototype.imageSize;
+					}
+				}
+			}
+
+			// Adds spacings
+			dx += 2 * (style[mxConstants.STYLE_SPACING] || 0);
+			dx += style[mxConstants.STYLE_SPACING_LEFT] || 0;
+			dx += style[mxConstants.STYLE_SPACING_RIGHT] || 0;
+
+			dy += 2 * (style[mxConstants.STYLE_SPACING] || 0);
+			dy += style[mxConstants.STYLE_SPACING_TOP] || 0;
+			dy += style[mxConstants.STYLE_SPACING_BOTTOM] || 0;
+			
+			// Add spacing for collapse/expand icon
+			// LATER: Check alignment and use constants
+			// for image spacing
+			var image = this.getFoldingImage(state);
+			
+			if (image != null)
+			{
+				dx += image.width + 8;
+			}
+
+			// Adds space for label
+			var value = this.cellRenderer.getLabelValue(state);
+
+			if (value != null && value.length > 0)
+			{
+				if (!this.isHtmlLabel(state.cell))
+				{
+					value = mxUtils.htmlEntities(value);
+				}
+				
+				value = value.replace(/\n/g, '<br>');
+				
+				var size = mxUtils.getSizeForString(value, fontSize, style[mxConstants.STYLE_FONTFAMILY]);
+				var width = size.width + dx;
+				var height = size.height + dy;
+				
+				if (!mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, true))
+				{
+					var tmp = height;
+					
+					height = width;
+					width = tmp;
+				}
+			
+				if (this.gridEnabled)
+				{
+					width = this.snap(width + this.gridSize / 2);
+					height = this.snap(height + this.gridSize / 2);
+				}
+
+				result = new mxRectangle(0, 0, width, height);
+			}
+			else
+			{
+				var gs2 = 4 * this.gridSize;
+				result = new mxRectangle(0, 0, gs2, gs2);
+			}
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: resizeCell
+ * 
+ * Sets the bounds of the given cell using <resizeCells>. Returns the
+ * cell which was passed to the function.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose bounds should be changed.
+ * bounds - <mxRectangle> that represents the new bounds.
+ */
+mxGraph.prototype.resizeCell = function(cell, bounds, recurse)
+{
+	return this.resizeCells([cell], [bounds], recurse)[0];
+};
+
+/**
+ * Function: resizeCells
+ * 
+ * Sets the bounds of the given cells and fires a <mxEvent.RESIZE_CELLS>
+ * event while the transaction is in progress. Returns the cells which
+ * have been passed to the function.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> whose bounds should be changed.
+ * bounds - Array of <mxRectangles> that represent the new bounds.
+ */
+mxGraph.prototype.resizeCells = function(cells, bounds, recurse)
+{
+	recurse = (recurse != null) ? recurse : this.isRecursiveResize();
+	
+	this.model.beginUpdate();
+	try
+	{
+		this.cellsResized(cells, bounds, recurse);
+		this.fireEvent(new mxEventObject(mxEvent.RESIZE_CELLS,
+				'cells', cells, 'bounds', bounds));
+	}
+	finally
+	{
+		this.model.endUpdate();
+	}
+
+	return cells;
+};
+
+/**
+ * Function: cellsResized
+ * 
+ * Sets the bounds of the given cells and fires a <mxEvent.CELLS_RESIZED>
+ * event. If <extendParents> is true, then the parent is extended if a
+ * child size is changed so that it overlaps with the parent.
+ * 
+ * The following example shows how to control group resizes to make sure
+ * that all child cells stay within the group.
+ * 
+ * (code)
+ * graph.addListener(mxEvent.CELLS_RESIZED, function(sender, evt)
+ * {
+ *   var cells = evt.getProperty('cells');
+ *   
+ *   if (cells != null)
+ *   {
+ *     for (var i = 0; i < cells.length; i++)
+ *     {
+ *       if (graph.getModel().getChildCount(cells[i]) > 0)
+ *       {
+ *         var geo = graph.getCellGeometry(cells[i]);
+ *         
+ *         if (geo != null)
+ *         {
+ *           var children = graph.getChildCells(cells[i], true, true);
+ *           var bounds = graph.getBoundingBoxFromGeometry(children, true);
+ *           
+ *           geo = geo.clone();
+ *           geo.width = Math.max(geo.width, bounds.width);
+ *           geo.height = Math.max(geo.height, bounds.height);
+ *           
+ *           graph.getModel().setGeometry(cells[i], geo);
+ *         }
+ *       }
+ *     }
+ *   }
+ * });
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> whose bounds should be changed.
+ * bounds - Array of <mxRectangles> that represent the new bounds.
+ * recurse - Optional boolean that specifies if the children should be resized.
+ */
+mxGraph.prototype.cellsResized = function(cells, bounds, recurse)
+{
+	recurse = (recurse != null) ? recurse : false;
+	
+	if (cells != null && bounds != null && cells.length == bounds.length)
+	{
+		this.model.beginUpdate();
+		try
+		{
+			for (var i = 0; i < cells.length; i++)
+			{
+				this.cellResized(cells[i], bounds[i], false, recurse);
+
+				if (this.isExtendParent(cells[i]))
+				{
+					this.extendParent(cells[i]);
+				}
+				
+				this.constrainChild(cells[i]);
+			}
+
+			if (this.resetEdgesOnResize)
+			{
+				this.resetEdges(cells);
+			}
+			
+			this.fireEvent(new mxEventObject(mxEvent.CELLS_RESIZED,
+					'cells', cells, 'bounds', bounds));
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: cellResized
+ * 
+ * Resizes the parents recursively so that they contain the complete area
+ * of the resized child cell.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose bounds should be changed.
+ * bounds - <mxRectangles> that represent the new bounds.
+ * ignoreRelative - Boolean that indicates if relative cells should be ignored.
+ * recurse - Optional boolean that specifies if the children should be resized.
+ */
+mxGraph.prototype.cellResized = function(cell, bounds, ignoreRelative, recurse)
+{
+	var geo = this.model.getGeometry(cell);
+
+	if (geo != null && (geo.x != bounds.x || geo.y != bounds.y ||
+		geo.width != bounds.width || geo.height != bounds.height))
+	{
+		geo = geo.clone();
+
+		if (!ignoreRelative && geo.relative)
+		{
+			var offset = geo.offset;
+
+			if (offset != null)
+			{
+				offset.x += bounds.x - geo.x;
+				offset.y += bounds.y - geo.y;
+			}
+		}
+		else
+		{
+			geo.x = bounds.x;
+			geo.y = bounds.y;
+		}
+
+		geo.width = bounds.width;
+		geo.height = bounds.height;
+
+		if (!geo.relative && this.model.isVertex(cell) && !this.isAllowNegativeCoordinates())
+		{
+			geo.x = Math.max(0, geo.x);
+			geo.y = Math.max(0, geo.y);
+		}
+
+		this.model.beginUpdate();
+		try
+		{
+			if (recurse)
+			{
+				this.resizeChildCells(cell, geo);
+			}
+						
+			this.model.setGeometry(cell, geo);
+			this.constrainChildCells(cell);
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: resizeChildCells
+ * 
+ * Resizes the child cells of the given cell for the given new geometry with
+ * respect to the current geometry of the cell.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that has been resized.
+ * newGeo - <mxGeometry> that represents the new bounds.
+ */
+mxGraph.prototype.resizeChildCells = function(cell, newGeo)
+{
+	var geo = this.model.getGeometry(cell);
+	var dx = newGeo.width / geo.width;
+	var dy = newGeo.height / geo.height;
+	var childCount = this.model.getChildCount(cell);
+	
+	for (var i = 0; i < childCount; i++)
+	{
+		this.scaleCell(this.model.getChildAt(cell, i), dx, dy, true);
+	}
+};
+
+/**
+ * Function: constrainChildCells
+ * 
+ * Constrains the children of the given cell using <constrainChild>.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that has been resized.
+ */
+mxGraph.prototype.constrainChildCells = function(cell)
+{
+	var childCount = this.model.getChildCount(cell);
+	
+	for (var i = 0; i < childCount; i++)
+	{
+		this.constrainChild(this.model.getChildAt(cell, i));
+	}
+};
+
+/**
+ * Function: scaleCell
+ * 
+ * Scales the points, position and size of the given cell according to the
+ * given vertical and horizontal scaling factors.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose geometry should be scaled.
+ * dx - Horizontal scaling factor.
+ * dy - Vertical scaling factor.
+ * recurse - Boolean indicating if the child cells should be scaled.
+ */
+mxGraph.prototype.scaleCell = function(cell, dx, dy, recurse)
+{
+	var geo = this.model.getGeometry(cell);
+	
+	if (geo != null)
+	{
+		var state = this.view.getState(cell);
+		var style = (state != null) ? state.style : this.getCellStyle(cell);
+		
+		geo = geo.clone();
+		
+		// Stores values for restoring based on style
+		var x = geo.x;
+		var y = geo.y
+		var w = geo.width;
+		var h = geo.height;
+		
+		geo.scale(dx, dy, style[mxConstants.STYLE_ASPECT] == 'fixed');
+		
+		if (style[mxConstants.STYLE_RESIZE_WIDTH] == '1')
+		{
+			geo.width = w * dx;
+		}
+		else if (style[mxConstants.STYLE_RESIZE_WIDTH] == '0')
+		{
+			geo.width = w;
+		}
+		
+		if (style[mxConstants.STYLE_RESIZE_HEIGHT] == '1')
+		{
+			geo.height = h * dy;
+		}
+		else if (style[mxConstants.STYLE_RESIZE_HEIGHT] == '0')
+		{
+			geo.height = h;
+		}
+		
+		if (!this.isCellMovable(cell))
+		{
+			geo.x = x;
+			geo.y = y;
+		}
+		
+		if (!this.isCellResizable(cell))
+		{
+			geo.width = w;
+			geo.height = h;
+		}
+
+		if (this.model.isVertex(cell))
+		{
+			this.cellResized(cell, geo, true, recurse);
+		}
+		else
+		{
+			this.model.setGeometry(cell, geo);
+		}
+	}
+};
+
+/**
+ * Function: extendParent
+ * 
+ * Resizes the parents recursively so that they contain the complete area
+ * of the resized child cell.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that has been resized.
+ */
+mxGraph.prototype.extendParent = function(cell)
+{
+	if (cell != null)
+	{
+		var parent = this.model.getParent(cell);
+		var p = this.getCellGeometry(parent);
+		
+		if (parent != null && p != null && !this.isCellCollapsed(parent))
+		{
+			var geo = this.getCellGeometry(cell);
+			
+			if (geo != null && !geo.relative &&
+				(p.width < geo.x + geo.width ||
+				p.height < geo.y + geo.height))
+			{
+				p = p.clone();
+				
+				p.width = Math.max(p.width, geo.x + geo.width);
+				p.height = Math.max(p.height, geo.y + geo.height);
+				
+				this.cellsResized([parent], [p], false);
+			}
+		}
+	}
+};
+
+/**
+ * Group: Cell moving
+ */
+
+/**
+ * Function: importCells
+ * 
+ * Clones and inserts the given cells into the graph using the move
+ * method and returns the inserted cells. This shortcut is used if
+ * cells are inserted via datatransfer.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> to be imported.
+ * dx - Integer that specifies the x-coordinate of the vector. Default is 0.
+ * dy - Integer that specifies the y-coordinate of the vector. Default is 0.
+ * target - <mxCell> that represents the new parent of the cells.
+ * evt - Mouseevent that triggered the invocation.
+ * mapping - Optional mapping for existing clones.
+ */
+mxGraph.prototype.importCells = function(cells, dx, dy, target, evt, mapping)
+{	
+	return this.moveCells(cells, dx, dy, true, target, evt, mapping);
+};
+
+/**
+ * Function: moveCells
+ * 
+ * Moves or clones the specified cells and moves the cells or clones by the
+ * given amount, adding them to the optional target cell. The evt is the
+ * mouse event as the mouse was released. The change is carried out using
+ * <cellsMoved>. This method fires <mxEvent.MOVE_CELLS> while the
+ * transaction is in progress. Returns the cells that were moved.
+ * 
+ * Use the following code to move all cells in the graph.
+ * 
+ * (code)
+ * graph.moveCells(graph.getChildCells(null, true, true), 10, 10);
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> to be moved, cloned or added to the target.
+ * dx - Integer that specifies the x-coordinate of the vector. Default is 0.
+ * dy - Integer that specifies the y-coordinate of the vector. Default is 0.
+ * clone - Boolean indicating if the cells should be cloned. Default is false.
+ * target - <mxCell> that represents the new parent of the cells.
+ * evt - Mouseevent that triggered the invocation.
+ * mapping - Optional mapping for existing clones.
+ */
+mxGraph.prototype.moveCells = function(cells, dx, dy, clone, target, evt, mapping)
+{
+	dx = (dx != null) ? dx : 0;
+	dy = (dy != null) ? dy : 0;
+	clone = (clone != null) ? clone : false;
+	
+	if (cells != null && (dx != 0 || dy != 0 || clone || target != null))
+	{
+		// Removes descandants with ancestors in cells to avoid multiple moving
+		cells = this.model.getTopmostCells(cells);
+
+		this.model.beginUpdate();
+		try
+		{
+			// Faster cell lookups to remove relative edge labels with selected
+			// terminals to avoid explicit and implicit move at same time
+			var dict = new mxDictionary();
+			
+			for (var i = 0; i < cells.length; i++)
+			{
+				dict.put(cells[i], true);
+			}
+			
+			var isSelected = mxUtils.bind(this, function(cell)
+			{
+				while (cell != null)
+				{
+					if (dict.get(cell))
+					{
+						return true;
+					}
+					
+					cell = this.model.getParent(cell);
+				}
+				
+				return false;
+			});
+			
+			// Removes relative edge labels with selected terminals
+			var checked = [];
+			
+			for (var i = 0; i < cells.length; i++)
+			{
+				var geo = this.getCellGeometry(cells[i]);
+				var parent = this.model.getParent(cells[i]);
+		
+				if ((geo == null || !geo.relative) || !this.model.isEdge(parent) ||
+					(!isSelected(this.model.getTerminal(parent, true)) &&
+					!isSelected(this.model.getTerminal(parent, false))))
+				{
+					checked.push(cells[i]);
+				}
+			}
+
+			cells = checked;
+			
+			if (clone)
+			{
+				cells = this.cloneCells(cells, this.isCloneInvalidEdges(), mapping);
+
+				if (target == null)
+				{
+					target = this.getDefaultParent();
+				}
+			}
+
+			// FIXME: Cells should always be inserted first before any other edit
+			// to avoid forward references in sessions.
+			// Need to disable allowNegativeCoordinates if target not null to
+			// allow for temporary negative numbers until cellsAdded is called.
+			var previous = this.isAllowNegativeCoordinates();
+			
+			if (target != null)
+			{
+				this.setAllowNegativeCoordinates(true);
+			}
+			
+			this.cellsMoved(cells, dx, dy, !clone && this.isDisconnectOnMove()
+					&& this.isAllowDanglingEdges(), target == null,
+					this.isExtendParentsOnMove() && target == null);
+			
+			this.setAllowNegativeCoordinates(previous);
+
+			if (target != null)
+			{
+				var index = this.model.getChildCount(target);
+				this.cellsAdded(cells, target, index, null, null, true);
+			}
+
+			// Dispatches a move event
+			this.fireEvent(new mxEventObject(mxEvent.MOVE_CELLS, 'cells', cells,
+				'dx', dx, 'dy', dy, 'clone', clone, 'target', target, 'event', evt));
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+
+	return cells;
+};
+
+/**
+ * Function: cellsMoved
+ * 
+ * Moves the specified cells by the given vector, disconnecting the cells
+ * using disconnectGraph is disconnect is true. This method fires
+ * <mxEvent.CELLS_MOVED> while the transaction is in progress.
+ */
+mxGraph.prototype.cellsMoved = function(cells, dx, dy, disconnect, constrain, extend)
+{
+	if (cells != null && (dx != 0 || dy != 0))
+	{
+		extend = (extend != null) ? extend : false;
+
+		this.model.beginUpdate();
+		try
+		{
+			if (disconnect)
+			{
+				this.disconnectGraph(cells);
+			}
+
+			for (var i = 0; i < cells.length; i++)
+			{
+				this.translateCell(cells[i], dx, dy);
+				
+				if (extend && this.isExtendParent(cells[i]))
+				{
+					this.extendParent(cells[i]);
+				}
+				else if (constrain)
+				{
+					this.constrainChild(cells[i]);
+				}
+			}
+
+			if (this.resetEdgesOnMove)
+			{
+				this.resetEdges(cells);
+			}
+			
+			this.fireEvent(new mxEventObject(mxEvent.CELLS_MOVED,
+				'cells', cells, 'dx', dx, 'dy', dy, 'disconnect', disconnect));
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: translateCell
+ * 
+ * Translates the geometry of the given cell and stores the new,
+ * translated geometry in the model as an atomic change.
+ */
+mxGraph.prototype.translateCell = function(cell, dx, dy)
+{
+	var geo = this.model.getGeometry(cell);
+
+	if (geo != null)
+	{
+		dx = parseFloat(dx);
+		dy = parseFloat(dy);
+		geo = geo.clone();
+		geo.translate(dx, dy);
+
+		if (!geo.relative && this.model.isVertex(cell) && !this.isAllowNegativeCoordinates())
+		{
+			geo.x = Math.max(0, parseFloat(geo.x));
+			geo.y = Math.max(0, parseFloat(geo.y));
+		}
+		
+		if (geo.relative && !this.model.isEdge(cell))
+		{
+			var parent = this.model.getParent(cell);
+			var angle = 0;
+			
+			if (this.model.isVertex(parent))
+			{
+				var state = this.view.getState(parent);
+				var style = (state != null) ? state.style : this.getCellStyle(parent);
+				
+				angle = mxUtils.getValue(style, mxConstants.STYLE_ROTATION, 0);
+			}
+			
+			if (angle != 0)
+			{
+				var rad = mxUtils.toRadians(-angle);
+				var cos = Math.cos(rad);
+				var sin = Math.sin(rad);
+				var pt = mxUtils.getRotatedPoint(new mxPoint(dx, dy), cos, sin, new mxPoint(0, 0));
+				dx = pt.x;
+				dy = pt.y;
+			}
+			
+			if (geo.offset == null)
+			{
+				geo.offset = new mxPoint(dx, dy);
+			}
+			else
+			{
+				geo.offset.x = parseFloat(geo.offset.x) + dx;
+				geo.offset.y = parseFloat(geo.offset.y) + dy;
+			}
+		}
+
+		this.model.setGeometry(cell, geo);
+	}
+};
+
+/**
+ * Function: getCellContainmentArea
+ * 
+ * Returns the <mxRectangle> inside which a cell is to be kept.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> for which the area should be returned.
+ */
+mxGraph.prototype.getCellContainmentArea = function(cell)
+{
+	if (cell != null && !this.model.isEdge(cell))
+	{
+		var parent = this.model.getParent(cell);
+		
+		if (parent != null && parent != this.getDefaultParent())
+		{
+			var g = this.model.getGeometry(parent);
+			
+			if (g != null)
+			{
+				var x = 0;
+				var y = 0;
+				var w = g.width;
+				var h = g.height;
+				
+				if (this.isSwimlane(parent))
+				{
+					var size = this.getStartSize(parent);
+					
+					var state = this.view.getState(parent);
+					var style = (state != null) ? state.style : this.getCellStyle(parent);
+					var dir = mxUtils.getValue(style, mxConstants.STYLE_DIRECTION, mxConstants.DIRECTION_EAST);
+					var flipH = mxUtils.getValue(style, mxConstants.STYLE_FLIPH, 0) == 1;
+					var flipV = mxUtils.getValue(style, mxConstants.STYLE_FLIPV, 0) == 1;
+					
+					if (dir == mxConstants.DIRECTION_SOUTH || dir == mxConstants.DIRECTION_NORTH)
+					{
+						var tmp = size.width;
+						size.width = size.height;
+						size.height = tmp;
+					}
+					
+					if ((dir == mxConstants.DIRECTION_EAST && !flipV) || (dir == mxConstants.DIRECTION_NORTH && !flipH) ||
+						(dir == mxConstants.DIRECTION_WEST && flipV) || (dir == mxConstants.DIRECTION_SOUTH && flipH))
+					{
+						x = size.width;
+						y = size.height;
+					}
+
+					w -= size.width;
+					h -= size.height;
+				}
+				
+				return new mxRectangle(x, y, w, h);
+			}
+		}
+	}
+	
+	return null;
+};
+
+/**
+ * Function: getMaximumGraphBounds
+ * 
+ * Returns the bounds inside which the diagram should be kept as an
+ * <mxRectangle>.
+ */
+mxGraph.prototype.getMaximumGraphBounds = function()
+{
+	return this.maximumGraphBounds;
+};
+
+/**
+ * Function: constrainChild
+ * 
+ * Keeps the given cell inside the bounds returned by
+ * <getCellContainmentArea> for its parent, according to the rules defined by
+ * <getOverlap> and <isConstrainChild>. This modifies the cell's geometry
+ * in-place and does not clone it.
+ * 
+ * Parameters:
+ * 
+ * cells - <mxCell> which should be constrained.
+ * sizeFirst - Specifies if the size should be changed first. Default is true.
+ */
+mxGraph.prototype.constrainChild = function(cell, sizeFirst)
+{
+	sizeFirst = (sizeFirst != null) ? sizeFirst : true;
+	
+	if (cell != null)
+	{
+		var geo = this.getCellGeometry(cell);
+		
+		if (geo != null && (this.isConstrainRelativeChildren() || !geo.relative))
+		{
+			var parent = this.model.getParent(cell);
+			var pgeo = this.getCellGeometry(parent);
+			var max = this.getMaximumGraphBounds();
+			
+			// Finds parent offset
+			if (max != null)
+			{
+				var off = this.getBoundingBoxFromGeometry([parent], false);
+				
+				if (off != null)
+				{
+					max = mxRectangle.fromRectangle(max);
+					
+					max.x -= off.x;
+					max.y -= off.y;
+				}
+			}
+			
+			if (this.isConstrainChild(cell))
+			{
+				var tmp = this.getCellContainmentArea(cell);
+				
+				if (tmp != null)
+				{
+					var overlap = this.getOverlap(cell);
+	
+					if (overlap > 0)
+					{
+						tmp = mxRectangle.fromRectangle(tmp);
+						
+						tmp.x -= tmp.width * overlap;
+						tmp.y -= tmp.height * overlap;
+						tmp.width += 2 * tmp.width * overlap;
+						tmp.height += 2 * tmp.height * overlap;
+					}
+					
+					// Find the intersection between max and tmp
+					if (max == null)
+					{
+						max = tmp;
+					}
+					else
+					{
+						max = mxRectangle.fromRectangle(max);
+						max.intersect(tmp);
+					}
+				}
+			}
+			
+			if (max != null)
+			{
+				var cells = [cell];
+				
+				if (!this.isCellCollapsed(cell))
+				{
+					var desc = this.model.getDescendants(cell);
+					
+					for (var i = 0; i < desc.length; i++)
+					{
+						if (this.isCellVisible(desc[i]))
+						{
+							cells.push(desc[i]);
+						}
+					}
+				}
+				
+				var bbox = this.getBoundingBoxFromGeometry(cells, false);
+				
+				if (bbox != null)
+				{
+					geo = geo.clone();
+					
+					// Cumulative horizontal movement
+					var dx = 0;
+					
+					if (geo.width > max.width)
+					{
+						dx = geo.width - max.width;
+						geo.width -= dx;
+					}
+					
+					if (bbox.x + bbox.width > max.x + max.width)
+					{
+						dx -= bbox.x + bbox.width - max.x - max.width - dx;
+					}
+					
+					// Cumulative vertical movement
+					var dy = 0;
+					
+					if (geo.height > max.height)
+					{
+						dy = geo.height - max.height;
+						geo.height -= dy;
+					}
+					
+					if (bbox.y + bbox.height > max.y + max.height)
+					{
+						dy -= bbox.y + bbox.height - max.y - max.height - dy;
+					}
+					
+					if (bbox.x < max.x)
+					{
+						dx -= bbox.x - max.x;
+					}
+					
+					if (bbox.y < max.y)
+					{
+						dy -= bbox.y - max.y;
+					}
+					
+					if (dx != 0 || dy != 0)
+					{
+						if (geo.relative)
+						{
+							// Relative geometries are moved via absolute offset
+							if (geo.offset == null)
+							{
+								geo.offset = new mxPoint();
+							}
+						
+							geo.offset.x += dx;
+							geo.offset.y += dy;
+						}
+						else
+						{
+							geo.x += dx;
+							geo.y += dy;
+						}
+					}
+					
+					this.model.setGeometry(cell, geo);
+				}
+			}
+		}
+	}
+};
+
+/**
+ * Function: resetEdges
+ * 
+ * Resets the control points of the edges that are connected to the given
+ * cells if not both ends of the edge are in the given cells array.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> for which the connected edges should be
+ * reset.
+ */
+mxGraph.prototype.resetEdges = function(cells)
+{
+	if (cells != null)
+	{
+		// Prepares faster cells lookup
+		var dict = new mxDictionary();
+		
+		for (var i = 0; i < cells.length; i++)
+		{
+			dict.put(cells[i], true);
+		}
+		
+		this.model.beginUpdate();
+		try
+		{
+			for (var i = 0; i < cells.length; i++)
+			{
+				var edges = this.model.getEdges(cells[i]);
+				
+				if (edges != null)
+				{
+					for (var j = 0; j < edges.length; j++)
+					{
+						var state = this.view.getState(edges[j]);
+						
+						var source = (state != null) ? state.getVisibleTerminal(true) : this.view.getVisibleTerminal(edges[j], true);
+						var target = (state != null) ? state.getVisibleTerminal(false) : this.view.getVisibleTerminal(edges[j], false);
+						
+						// Checks if one of the terminals is not in the given array
+						if (!dict.get(source) || !dict.get(target))
+						{
+							this.resetEdge(edges[j]);
+						}
+					}
+				}
+				
+				this.resetEdges(this.model.getChildren(cells[i]));
+			}
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: resetEdge
+ * 
+ * Resets the control points of the given edge.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> whose points should be reset.
+ */
+mxGraph.prototype.resetEdge = function(edge)
+{
+	var geo = this.model.getGeometry(edge);
+	
+	// Resets the control points
+	if (geo != null && geo.points != null && geo.points.length > 0)
+	{
+		geo = geo.clone();
+		geo.points = [];
+		this.model.setGeometry(edge, geo);
+	}
+	
+	return edge;
+};
+
+/**
+ * Group: Cell connecting and connection constraints
+ */
+
+/**
+ * Function: getOutlineConstraint
+ * 
+ * Returns the constraint used to connect to the outline of the given state.
+ */
+mxGraph.prototype.getOutlineConstraint = function(point, terminalState, me)
+{
+	if (terminalState.shape != null)
+	{
+		var bounds = this.view.getPerimeterBounds(terminalState);
+		var direction = terminalState.style[mxConstants.STYLE_DIRECTION];
+		
+		if (direction == mxConstants.DIRECTION_NORTH || direction == mxConstants.DIRECTION_SOUTH)
+		{
+			bounds.x += bounds.width / 2 - bounds.height / 2;
+			bounds.y += bounds.height / 2 - bounds.width / 2;
+			var tmp = bounds.width;
+			bounds.width = bounds.height;
+			bounds.height = tmp;
+		}
+	
+		var alpha = mxUtils.toRadians(terminalState.shape.getShapeRotation());
+		
+		if (alpha != 0)
+		{
+			var cos = Math.cos(-alpha);
+			var sin = Math.sin(-alpha);
+	
+			var ct = new mxPoint(bounds.getCenterX(), bounds.getCenterY());
+			point = mxUtils.getRotatedPoint(point, cos, sin, ct);
+		}
+
+		var sx = 1;
+		var sy = 1;
+		var dx = 0;
+		var dy = 0;
+		
+		// LATER: Add flipping support for image shapes
+		if (this.getModel().isVertex(terminalState.cell))
+		{
+			var flipH = terminalState.style[mxConstants.STYLE_FLIPH];
+			var flipV = terminalState.style[mxConstants.STYLE_FLIPV];
+			
+			// Legacy support for stencilFlipH/V
+			if (terminalState.shape != null && terminalState.shape.stencil != null)
+			{
+				flipH = mxUtils.getValue(terminalState.style, 'stencilFlipH', 0) == 1 || flipH;
+				flipV = mxUtils.getValue(terminalState.style, 'stencilFlipV', 0) == 1 || flipV;
+			}
+			
+			if (direction == mxConstants.DIRECTION_NORTH || direction == mxConstants.DIRECTION_SOUTH)
+			{
+				var tmp = flipH;
+				flipH = flipV;
+				flipV = tmp;
+			}
+			
+			if (flipH)
+			{
+				sx = -1;
+				dx = -bounds.width;
+			}
+			
+			if (flipV)
+			{
+				sy = -1;
+				dy = -bounds.height ;
+			}
+		}
+		
+		point = new mxPoint((point.x - bounds.x) * sx - dx + bounds.x, (point.y - bounds.y) * sy - dy + bounds.y);
+		
+		var x = Math.round((point.x - bounds.x) * 1000 / bounds.width) / 1000;
+		var y = Math.round((point.y - bounds.y) * 1000 / bounds.height) / 1000;
+		
+		return new mxConnectionConstraint(new mxPoint(x, y), false);
+	}
+	
+	return null;
+};
+
+/**
+ * Function: getAllConnectionConstraints
+ * 
+ * Returns an array of all <mxConnectionConstraints> for the given terminal. If
+ * the shape of the given terminal is a <mxStencilShape> then the constraints
+ * of the corresponding <mxStencil> are returned.
+ * 
+ * Parameters:
+ * 
+ * terminal - <mxCellState> that represents the terminal.
+ * source - Boolean that specifies if the terminal is the source or target.
+ */
+mxGraph.prototype.getAllConnectionConstraints = function(terminal, source)
+{
+	if (terminal != null && terminal.shape != null && terminal.shape.stencil != null)
+	{
+		return terminal.shape.stencil.constraints;
+	}
+
+	return null;
+};
+
+/**
+ * Function: getConnectionConstraint
+ * 
+ * Returns an <mxConnectionConstraint> that describes the given connection
+ * point. This result can then be passed to <getConnectionPoint>.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCellState> that represents the edge.
+ * terminal - <mxCellState> that represents the terminal.
+ * source - Boolean indicating if the terminal is the source or target.
+ */
+mxGraph.prototype.getConnectionConstraint = function(edge, terminal, source)
+{
+	var point = null;
+	var x = edge.style[(source) ? mxConstants.STYLE_EXIT_X : mxConstants.STYLE_ENTRY_X];
+
+	if (x != null)
+	{
+		var y = edge.style[(source) ? mxConstants.STYLE_EXIT_Y : mxConstants.STYLE_ENTRY_Y];
+		
+		if (y != null)
+		{
+			point = new mxPoint(parseFloat(x), parseFloat(y));
+		}
+	}
+	
+	var perimeter = false;
+	
+	if (point != null)
+	{
+		perimeter = mxUtils.getValue(edge.style, (source) ? mxConstants.STYLE_EXIT_PERIMETER :
+			mxConstants.STYLE_ENTRY_PERIMETER, true);
+	}
+	
+	return new mxConnectionConstraint(point, perimeter);
+};
+
+/**
+ * Function: setConnectionConstraint
+ * 
+ * Sets the <mxConnectionConstraint> that describes the given connection point.
+ * If no constraint is given then nothing is changed. To remove an existing
+ * constraint from the given edge, use an empty constraint instead.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> that represents the edge.
+ * terminal - <mxCell> that represents the terminal.
+ * source - Boolean indicating if the terminal is the source or target.
+ * constraint - Optional <mxConnectionConstraint> to be used for this
+ * connection.
+ */
+mxGraph.prototype.setConnectionConstraint = function(edge, terminal, source, constraint)
+{
+	if (constraint != null)
+	{
+		this.model.beginUpdate();
+		
+		try
+		{
+			if (constraint == null || constraint.point == null)
+			{
+				this.setCellStyles((source) ? mxConstants.STYLE_EXIT_X :
+					mxConstants.STYLE_ENTRY_X, null, [edge]);
+				this.setCellStyles((source) ? mxConstants.STYLE_EXIT_Y :
+					mxConstants.STYLE_ENTRY_Y, null, [edge]);
+				this.setCellStyles((source) ? mxConstants.STYLE_EXIT_PERIMETER :
+					mxConstants.STYLE_ENTRY_PERIMETER, null, [edge]);
+			}
+			else if (constraint.point != null)
+			{
+				this.setCellStyles((source) ? mxConstants.STYLE_EXIT_X :
+					mxConstants.STYLE_ENTRY_X, constraint.point.x, [edge]);
+				this.setCellStyles((source) ? mxConstants.STYLE_EXIT_Y :
+					mxConstants.STYLE_ENTRY_Y, constraint.point.y, [edge]);
+				
+				// Only writes 0 since 1 is default
+				if (!constraint.perimeter)
+				{
+					this.setCellStyles((source) ? mxConstants.STYLE_EXIT_PERIMETER :
+						mxConstants.STYLE_ENTRY_PERIMETER, '0', [edge]);
+				}
+				else
+				{
+					this.setCellStyles((source) ? mxConstants.STYLE_EXIT_PERIMETER :
+						mxConstants.STYLE_ENTRY_PERIMETER, null, [edge]);
+				}
+			}
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: getConnectionPoint
+ *
+ * Returns the nearest point in the list of absolute points or the center
+ * of the opposite terminal.
+ * 
+ * Parameters:
+ * 
+ * vertex - <mxCellState> that represents the vertex.
+ * constraint - <mxConnectionConstraint> that represents the connection point
+ * constraint as returned by <getConnectionConstraint>.
+ */
+mxGraph.prototype.getConnectionPoint = function(vertex, constraint)
+{
+	var point = null;
+	
+	if (vertex != null && constraint.point != null)
+	{
+		var bounds = this.view.getPerimeterBounds(vertex);
+        var cx = new mxPoint(bounds.getCenterX(), bounds.getCenterY());
+		var direction = vertex.style[mxConstants.STYLE_DIRECTION];
+		var r1 = 0;
+		
+		// Bounds need to be rotated by 90 degrees for further computation
+		if (direction != null)
+		{
+			if (direction == mxConstants.DIRECTION_NORTH)
+			{
+				r1 += 270;
+			}
+			else if (direction == mxConstants.DIRECTION_WEST)
+			{
+				r1 += 180;
+			}
+			else if (direction == mxConstants.DIRECTION_SOUTH)
+			{
+				r1 += 90;
+			}
+
+			// Bounds need to be rotated by 90 degrees for further computation
+			if (direction == mxConstants.DIRECTION_NORTH || direction == mxConstants.DIRECTION_SOUTH)
+			{
+				bounds.rotate90();
+			}
+		}
+
+		point = new mxPoint(bounds.x + constraint.point.x * bounds.width,
+				bounds.y + constraint.point.y * bounds.height);
+		
+		// Rotation for direction before projection on perimeter
+		var r2 = vertex.style[mxConstants.STYLE_ROTATION] || 0;
+		
+		if (constraint.perimeter)
+		{
+			if (r1 != 0)
+			{
+				// Only 90 degrees steps possible here so no trig needed
+				var cos = 0;
+				var sin = 0;
+				
+				if (r1 == 90)
+				{
+					sin = 1;
+				}
+				else if (r1 == 180)
+				{
+					cos = -1;
+				}
+				else if (r1 == 270)
+				{
+					sin = -1;
+				}
+				
+		        point = mxUtils.getRotatedPoint(point, cos, sin, cx);
+			}
+	
+			point = this.view.getPerimeterPoint(vertex, point, false);
+		}
+		else
+		{
+			r2 += r1;
+			
+			if (this.getModel().isVertex(vertex.cell))
+			{
+				var flipH = vertex.style[mxConstants.STYLE_FLIPH] == 1;
+				var flipV = vertex.style[mxConstants.STYLE_FLIPV] == 1;
+				
+				// Legacy support for stencilFlipH/V
+				if (vertex.shape != null && vertex.shape.stencil != null)
+				{
+					flipH = (mxUtils.getValue(vertex.style, 'stencilFlipH', 0) == 1) || flipH;
+					flipV = (mxUtils.getValue(vertex.style, 'stencilFlipV', 0) == 1) || flipV;
+				}
+				
+				if (flipH)
+				{
+					point.x = 2 * bounds.getCenterX() - point.x;
+				}
+				
+				if (flipV)
+				{
+					point.y = 2 * bounds.getCenterY() - point.y;
+				}
+			}
+		}
+
+		// Generic rotation after projection on perimeter
+		if (r2 != 0 && point != null)
+		{
+	        var rad = mxUtils.toRadians(r2);
+	        var cos = Math.cos(rad);
+	        var sin = Math.sin(rad);
+	        
+	        point = mxUtils.getRotatedPoint(point, cos, sin, cx);
+		}
+	}
+	
+	if (point != null)
+	{
+		point.x = Math.round(point.x);
+		point.y = Math.round(point.y);
+	}
+
+	return point;
+};
+
+/**
+ * Function: connectCell
+ * 
+ * Connects the specified end of the given edge to the given terminal
+ * using <cellConnected> and fires <mxEvent.CONNECT_CELL> while the
+ * transaction is in progress. Returns the updated edge.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> whose terminal should be updated.
+ * terminal - <mxCell> that represents the new terminal to be used.
+ * source - Boolean indicating if the new terminal is the source or target.
+ * constraint - Optional <mxConnectionConstraint> to be used for this
+ * connection.
+ */
+mxGraph.prototype.connectCell = function(edge, terminal, source, constraint)
+{
+	this.model.beginUpdate();
+	try
+	{
+		var previous = this.model.getTerminal(edge, source);
+		this.cellConnected(edge, terminal, source, constraint);
+		this.fireEvent(new mxEventObject(mxEvent.CONNECT_CELL,
+			'edge', edge, 'terminal', terminal, 'source', source,
+			'previous', previous));
+	}
+	finally
+	{
+		this.model.endUpdate();
+	}
+
+	return edge;
+};
+
+/**
+ * Function: cellConnected
+ * 
+ * Sets the new terminal for the given edge and resets the edge points if
+ * <resetEdgesOnConnect> is true. This method fires
+ * <mxEvent.CELL_CONNECTED> while the transaction is in progress.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> whose terminal should be updated.
+ * terminal - <mxCell> that represents the new terminal to be used.
+ * source - Boolean indicating if the new terminal is the source or target.
+ * constraint - <mxConnectionConstraint> to be used for this connection.
+ */
+mxGraph.prototype.cellConnected = function(edge, terminal, source, constraint)
+{
+	if (edge != null)
+	{
+		this.model.beginUpdate();
+		try
+		{
+			var previous = this.model.getTerminal(edge, source);
+
+			// Updates the constraint
+			this.setConnectionConstraint(edge, terminal, source, constraint);
+			
+			// Checks if the new terminal is a port, uses the ID of the port in the
+			// style and the parent of the port as the actual terminal of the edge.
+			if (this.isPortsEnabled())
+			{
+				var id = null;
+	
+				if (this.isPort(terminal))
+				{
+					id = terminal.getId();
+					terminal = this.getTerminalForPort(terminal, source);
+				}
+				
+				// Sets or resets all previous information for connecting to a child port
+				var key = (source) ? mxConstants.STYLE_SOURCE_PORT :
+					mxConstants.STYLE_TARGET_PORT;
+				this.setCellStyles(key, id, [edge]);
+			}
+			
+			this.model.setTerminal(edge, terminal, source);
+			
+			if (this.resetEdgesOnConnect)
+			{
+				this.resetEdge(edge);
+			}
+
+			this.fireEvent(new mxEventObject(mxEvent.CELL_CONNECTED,
+				'edge', edge, 'terminal', terminal, 'source', source,
+				'previous', previous));
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: disconnectGraph
+ * 
+ * Disconnects the given edges from the terminals which are not in the
+ * given array.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> to be disconnected.
+ */
+mxGraph.prototype.disconnectGraph = function(cells)
+{
+	if (cells != null)
+	{
+		this.model.beginUpdate();
+		try
+		{							
+			var scale = this.view.scale;
+			var tr = this.view.translate;
+			
+			// Fast lookup for finding cells in array
+			var dict = new mxDictionary();
+			
+			for (var i = 0; i < cells.length; i++)
+			{
+				dict.put(cells[i], true);
+			}
+			
+			for (var i = 0; i < cells.length; i++)
+			{
+				if (this.model.isEdge(cells[i]))
+				{
+					var geo = this.model.getGeometry(cells[i]);
+					
+					if (geo != null)
+					{
+						var state = this.view.getState(cells[i]);
+						var pstate = this.view.getState(
+							this.model.getParent(cells[i]));
+						
+						if (state != null &&
+							pstate != null)
+						{
+							geo = geo.clone();
+							
+							var dx = -pstate.origin.x;
+							var dy = -pstate.origin.y;
+							var pts = state.absolutePoints;
+
+							var src = this.model.getTerminal(cells[i], true);
+							
+							if (src != null && this.isCellDisconnectable(cells[i], src, true))
+							{
+								while (src != null && !dict.get(src))
+								{
+									src = this.model.getParent(src);
+								}
+								
+								if (src == null)
+								{
+									geo.setTerminalPoint(
+										new mxPoint(pts[0].x / scale - tr.x + dx,
+											pts[0].y / scale - tr.y + dy), true);
+									this.model.setTerminal(cells[i], null, true);
+								}
+							}
+							
+							var trg = this.model.getTerminal(cells[i], false);
+							
+							if (trg != null && this.isCellDisconnectable(cells[i], trg, false))
+							{
+								while (trg != null && !dict.get(trg))
+								{
+									trg = this.model.getParent(trg);
+								}
+								
+								if (trg == null)
+								{
+									var n = pts.length - 1;
+									geo.setTerminalPoint(
+										new mxPoint(pts[n].x / scale - tr.x + dx,
+											pts[n].y / scale - tr.y + dy), false);
+									this.model.setTerminal(cells[i], null, false);
+								}
+							}
+
+							this.model.setGeometry(cells[i], geo);
+						}
+					}
+				}
+			}
+		}
+		finally
+		{
+			this.model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Group: Drilldown
+ */
+
+/**
+ * Function: getCurrentRoot
+ * 
+ * Returns the current root of the displayed cell hierarchy. This is a
+ * shortcut to <mxGraphView.currentRoot> in <view>.
+ */
+mxGraph.prototype.getCurrentRoot = function()
+{
+	return this.view.currentRoot;
+};
+ 
+/**
+ * Function: getTranslateForRoot
+ * 
+ * Returns the translation to be used if the given cell is the root cell as
+ * an <mxPoint>. This implementation returns null.
+ * 
+ * Example:
+ * 
+ * To keep the children at their absolute position while stepping into groups,
+ * this function can be overridden as follows.
+ * 
+ * (code)
+ * var offset = new mxPoint(0, 0);
+ * 
+ * while (cell != null)
+ * {
+ *   var geo = this.model.getGeometry(cell);
+ * 
+ *   if (geo != null)
+ *   {
+ *     offset.x -= geo.x;
+ *     offset.y -= geo.y;
+ *   }
+ * 
+ *   cell = this.model.getParent(cell);
+ * }
+ * 
+ * return offset;
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the root.
+ */
+mxGraph.prototype.getTranslateForRoot = function(cell)
+{
+	return null;
+};
+
+/**
+ * Function: isPort
+ * 
+ * Returns true if the given cell is a "port", that is, when connecting to
+ * it, the cell returned by getTerminalForPort should be used as the
+ * terminal and the port should be referenced by the ID in either the
+ * mxConstants.STYLE_SOURCE_PORT or the or the
+ * mxConstants.STYLE_TARGET_PORT. Note that a port should not be movable.
+ * This implementation always returns false.
+ * 
+ * A typical implementation is the following:
+ * 
+ * (code)
+ * graph.isPort = function(cell)
+ * {
+ *   var geo = this.getCellGeometry(cell);
+ *   
+ *   return (geo != null) ? geo.relative : false;
+ * };
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the port.
+ */
+mxGraph.prototype.isPort = function(cell)
+{
+	return false;
+};
+
+/**
+ * Function: getTerminalForPort
+ * 
+ * Returns the terminal to be used for a given port. This implementation
+ * always returns the parent cell.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the port.
+ * source - If the cell is the source or target port.
+ */
+mxGraph.prototype.getTerminalForPort = function(cell, source)
+{
+	return this.model.getParent(cell);
+};
+
+/**
+ * Function: getChildOffsetForCell
+ * 
+ * Returns the offset to be used for the cells inside the given cell. The
+ * root and layer cells may be identified using <mxGraphModel.isRoot> and
+ * <mxGraphModel.isLayer>. For all other current roots, the
+ * <mxGraphView.currentRoot> field points to the respective cell, so that
+ * the following holds: cell == this.view.currentRoot. This implementation
+ * returns null.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose offset should be returned.
+ */
+mxGraph.prototype.getChildOffsetForCell = function(cell)
+{
+	return null;
+};
+
+/**
+ * Function: enterGroup
+ * 
+ * Uses the given cell as the root of the displayed cell hierarchy. If no
+ * cell is specified then the selection cell is used. The cell is only used
+ * if <isValidRoot> returns true.
+ * 
+ * Parameters:
+ * 
+ * cell - Optional <mxCell> to be used as the new root. Default is the
+ * selection cell.
+ */
+mxGraph.prototype.enterGroup = function(cell)
+{
+	cell = cell || this.getSelectionCell();
+	
+	if (cell != null && this.isValidRoot(cell))
+	{
+		this.view.setCurrentRoot(cell);
+		this.clearSelection();
+	}
+};
+
+/**
+ * Function: exitGroup
+ * 
+ * Changes the current root to the next valid root in the displayed cell
+ * hierarchy.
+ */
+mxGraph.prototype.exitGroup = function()
+{
+	var root = this.model.getRoot();
+	var current = this.getCurrentRoot();
+	
+	if (current != null)
+	{
+		var next = this.model.getParent(current);
+		
+		// Finds the next valid root in the hierarchy
+		while (next != root && !this.isValidRoot(next) &&
+				this.model.getParent(next) != root)
+		{
+			next = this.model.getParent(next);
+		}
+		
+		// Clears the current root if the new root is
+		// the model's root or one of the layers.
+		if (next == root || this.model.getParent(next) == root)
+		{
+			this.view.setCurrentRoot(null);
+		}
+		else
+		{
+			this.view.setCurrentRoot(next);
+		}
+		
+		var state = this.view.getState(current);
+		
+		// Selects the previous root in the graph
+		if (state != null)
+		{
+			this.setSelectionCell(current);
+		}
+	}
+};
+
+/**
+ * Function: home
+ * 
+ * Uses the root of the model as the root of the displayed cell hierarchy
+ * and selects the previous root.
+ */
+mxGraph.prototype.home = function()
+{
+	var current = this.getCurrentRoot();
+	
+	if (current != null)
+	{
+		this.view.setCurrentRoot(null);
+		var state = this.view.getState(current);
+		
+		if (state != null)
+		{
+			this.setSelectionCell(current);
+		}
+	}
+};
+
+/**
+ * Function: isValidRoot
+ * 
+ * Returns true if the given cell is a valid root for the cell display
+ * hierarchy. This implementation returns true for all non-null values.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> which should be checked as a possible root.
+ */
+mxGraph.prototype.isValidRoot = function(cell)
+{
+	return (cell != null);
+};
+
+/**
+ * Group: Graph display
+ */
+ 
+/**
+ * Function: getGraphBounds
+ * 
+ * Returns the bounds of the visible graph. Shortcut to
+ * <mxGraphView.getGraphBounds>. See also: <getBoundingBoxFromGeometry>.
+ */
+ mxGraph.prototype.getGraphBounds = function()
+ {
+ 	return this.view.getGraphBounds();
+ };
+
+/**
+ * Function: getCellBounds
+ * 
+ * Returns the scaled, translated bounds for the given cell. See
+ * <mxGraphView.getBounds> for arrays.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose bounds should be returned.
+ * includeEdge - Optional boolean that specifies if the bounds of
+ * the connected edges should be included. Default is false.
+ * includeDescendants - Optional boolean that specifies if the bounds
+ * of all descendants should be included. Default is false.
+ */
+mxGraph.prototype.getCellBounds = function(cell, includeEdges, includeDescendants)
+{
+	var cells = [cell];
+	
+	// Includes all connected edges
+	if (includeEdges)
+	{
+		cells = cells.concat(this.model.getEdges(cell));
+	}
+	
+	var result = this.view.getBounds(cells);
+	
+	// Recursively includes the bounds of the children
+	if (includeDescendants)
+	{
+		var childCount = this.model.getChildCount(cell);
+		
+		for (var i = 0; i < childCount; i++)
+		{
+			var tmp = this.getCellBounds(this.model.getChildAt(cell, i),
+				includeEdges, true);
+
+			if (result != null)
+			{
+				result.add(tmp);
+			}
+			else
+			{
+				result = tmp;
+			}
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: getBoundingBoxFromGeometry
+ * 
+ * Returns the bounding box for the geometries of the vertices in the
+ * given array of cells. This can be used to find the graph bounds during
+ * a layout operation (ie. before the last endUpdate) as follows:
+ * 
+ * (code)
+ * var cells = graph.getChildCells(graph.getDefaultParent(), true, true);
+ * var bounds = graph.getBoundingBoxFromGeometry(cells, true);
+ * (end)
+ * 
+ * This can then be used to move cells to the origin:
+ * 
+ * (code)
+ * if (bounds.x < 0 || bounds.y < 0)
+ * {
+ *   graph.moveCells(cells, -Math.min(bounds.x, 0), -Math.min(bounds.y, 0))
+ * }
+ * (end)
+ * 
+ * Or to translate the graph view:
+ * 
+ * (code)
+ * if (bounds.x < 0 || bounds.y < 0)
+ * {
+ *   graph.view.setTranslate(-Math.min(bounds.x, 0), -Math.min(bounds.y, 0));
+ * }
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> whose bounds should be returned.
+ * includeEdges - Specifies if edge bounds should be included by computing
+ * the bounding box for all points in geometry. Default is false.
+ */
+mxGraph.prototype.getBoundingBoxFromGeometry = function(cells, includeEdges)
+{
+	includeEdges = (includeEdges != null) ? includeEdges : false;
+	var result = null;
+	
+	if (cells != null)
+	{
+		for (var i = 0; i < cells.length; i++)
+		{
+			if (includeEdges || this.model.isVertex(cells[i]))
+			{
+				// Computes the bounding box for the points in the geometry
+				var geo = this.getCellGeometry(cells[i]);
+				
+				if (geo != null)
+				{
+					var bbox = null;
+					
+					if (this.model.isEdge(cells[i]))
+					{
+						var addPoint = function(pt)
+						{
+							if (pt != null)
+							{
+								if (tmp == null)
+								{
+									tmp = new mxRectangle(pt.x, pt.y, 0, 0);
+								}
+								else
+								{
+									tmp.add(new mxRectangle(pt.x, pt.y, 0, 0));
+								}
+							}
+						};
+						
+						if (this.model.getTerminal(cells[i], true) == null)
+						{
+							addPoint(geo.getTerminalPoint(true));
+						}
+						
+						if (this.model.getTerminal(cells[i], false) == null)
+						{
+							addPoint(geo.getTerminalPoint(false));
+						}
+												
+						var pts = geo.points;
+						
+						if (pts != null && pts.length > 0)
+						{
+							var tmp = new mxRectangle(pts[0].x, pts[0].y, 0, 0);
+
+							for (var j = 1; j < pts.length; j++)
+							{
+								addPoint(pts[j]);
+							}
+						}
+						
+						bbox = tmp;
+					}
+					else
+					{
+						var parent = this.model.getParent(cells[i]);
+						
+						if (geo.relative)
+						{
+							if (this.model.isVertex(parent) && parent != this.view.currentRoot)
+							{
+								var tmp = this.getBoundingBoxFromGeometry([parent], false);
+								
+								if (tmp != null)
+								{
+									bbox = new mxRectangle(geo.x * tmp.width, geo.y * tmp.height, geo.width, geo.height);
+									
+									if (mxUtils.indexOf(cells, parent) >= 0)
+									{
+										bbox.x += tmp.x;
+										bbox.y += tmp.y;
+									}
+								}
+							}
+						}
+						else
+						{
+							bbox = mxRectangle.fromRectangle(geo);
+							
+							if (this.model.isVertex(parent) && mxUtils.indexOf(cells, parent) >= 0)
+							{
+								var tmp = this.getBoundingBoxFromGeometry([parent], false);
+
+								if (tmp != null)
+								{
+									bbox.x += tmp.x;
+									bbox.y += tmp.y;
+								}
+							}
+						}
+						
+						if (bbox != null && geo.offset != null)
+						{
+							bbox.x += geo.offset.x;
+							bbox.y += geo.offset.y;
+						}
+					}
+					
+					if (bbox != null)
+					{
+						if (result == null)
+						{
+							result = mxRectangle.fromRectangle(bbox);
+						}
+						else
+						{
+							result.add(bbox);
+						}
+					}
+				}
+			}
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: refresh
+ * 
+ * Clears all cell states or the states for the hierarchy starting at the
+ * given cell and validates the graph. This fires a refresh event as the
+ * last step.
+ * 
+ * Parameters:
+ * 
+ * cell - Optional <mxCell> for which the cell states should be cleared.
+ */
+mxGraph.prototype.refresh = function(cell)
+{
+	this.view.clear(cell, cell == null);
+	this.view.validate();
+	this.sizeDidChange();
+	this.fireEvent(new mxEventObject(mxEvent.REFRESH));
+};
+
+/**
+ * Function: snap
+ * 
+ * Snaps the given numeric value to the grid if <gridEnabled> is true.
+ * 
+ * Parameters:
+ * 
+ * value - Numeric value to be snapped to the grid.
+ */
+mxGraph.prototype.snap = function(value)
+{
+	if (this.gridEnabled)
+	{
+		value = Math.round(value / this.gridSize ) * this.gridSize;
+	}
+	
+	return value;
+};
+
+/**
+ * Function: panGraph
+ * 
+ * Shifts the graph display by the given amount. This is used to preview
+ * panning operations, use <mxGraphView.setTranslate> to set a persistent
+ * translation of the view. Fires <mxEvent.PAN>.
+ * 
+ * Parameters:
+ * 
+ * dx - Amount to shift the graph along the x-axis.
+ * dy - Amount to shift the graph along the y-axis.
+ */
+mxGraph.prototype.panGraph = function(dx, dy)
+{
+	if (this.useScrollbarsForPanning && mxUtils.hasScrollbars(this.container))
+	{
+		this.container.scrollLeft = -dx;
+		this.container.scrollTop = -dy;
+	}
+	else
+	{
+		var canvas = this.view.getCanvas();
+		
+		if (this.dialect == mxConstants.DIALECT_SVG)
+		{
+			// Puts everything inside the container in a DIV so that it
+			// can be moved without changing the state of the container
+			if (dx == 0 && dy == 0)
+			{
+				// Workaround for ignored removeAttribute on SVG element in IE9 standards
+				if (mxClient.IS_IE)
+				{
+					canvas.setAttribute('transform', 'translate(' + dx + ',' + dy + ')');
+				}
+				else
+				{
+					canvas.removeAttribute('transform');
+				}
+				
+				if (this.shiftPreview1 != null)
+				{
+					var child = this.shiftPreview1.firstChild;
+					
+					while (child != null)
+					{
+						var next = child.nextSibling;
+						this.container.appendChild(child);
+						child = next;
+					}
+
+					if (this.shiftPreview1.parentNode != null)
+					{
+						this.shiftPreview1.parentNode.removeChild(this.shiftPreview1);
+					}
+					
+					this.shiftPreview1 = null;
+					
+					this.container.appendChild(canvas.parentNode);
+					
+					child = this.shiftPreview2.firstChild;
+					
+					while (child != null)
+					{
+						var next = child.nextSibling;
+						this.container.appendChild(child);
+						child = next;
+					}
+
+					if (this.shiftPreview2.parentNode != null)
+					{
+						this.shiftPreview2.parentNode.removeChild(this.shiftPreview2);
+					}
+					
+					this.shiftPreview2 = null;
+				}
+			}
+			else
+			{
+				canvas.setAttribute('transform', 'translate(' + dx + ',' + dy + ')');
+				
+				if (this.shiftPreview1 == null)
+				{
+					// Needs two divs for stuff before and after the SVG element
+					this.shiftPreview1 = document.createElement('div');
+					this.shiftPreview1.style.position = 'absolute';
+					this.shiftPreview1.style.overflow = 'visible';
+					
+					this.shiftPreview2 = document.createElement('div');
+					this.shiftPreview2.style.position = 'absolute';
+					this.shiftPreview2.style.overflow = 'visible';
+
+					var current = this.shiftPreview1;
+					var child = this.container.firstChild;
+					
+					while (child != null)
+					{
+						var next = child.nextSibling;
+						
+						// SVG element is moved via transform attribute
+						if (child != canvas.parentNode)
+						{
+							current.appendChild(child);
+						}
+						else
+						{
+							current = this.shiftPreview2;
+						}
+						
+						child = next;
+					}
+					
+					// Inserts elements only if not empty
+					if (this.shiftPreview1.firstChild != null)
+					{
+						this.container.insertBefore(this.shiftPreview1, canvas.parentNode);
+					}
+					
+					if (this.shiftPreview2.firstChild != null)
+					{
+						this.container.appendChild(this.shiftPreview2);
+					}
+				}
+				
+				this.shiftPreview1.style.left = dx + 'px';
+				this.shiftPreview1.style.top = dy + 'px';
+				this.shiftPreview2.style.left = dx + 'px';
+				this.shiftPreview2.style.top = dy + 'px';
+			}
+		}
+		else
+		{
+			canvas.style.left = dx + 'px';
+			canvas.style.top = dy + 'px';
+		}
+		
+		this.panDx = dx;
+		this.panDy = dy;
+
+		this.fireEvent(new mxEventObject(mxEvent.PAN));
+	}
+};
+
+/**
+ * Function: zoomIn
+ * 
+ * Zooms into the graph by <zoomFactor>.
+ */
+mxGraph.prototype.zoomIn = function()
+{
+	this.zoom(this.zoomFactor);
+};
+
+/**
+ * Function: zoomOut
+ * 
+ * Zooms out of the graph by <zoomFactor>.
+ */
+mxGraph.prototype.zoomOut = function()
+{
+	this.zoom(1 / this.zoomFactor);
+};
+
+/**
+ * Function: zoomActual
+ * 
+ * Resets the zoom and panning in the view.
+ */
+mxGraph.prototype.zoomActual = function()
+{
+	if (this.view.scale == 1)
+	{
+		this.view.setTranslate(0, 0);
+	}
+	else
+	{
+		this.view.translate.x = 0;
+		this.view.translate.y = 0;
+
+		this.view.setScale(1);
+	}
+};
+
+/**
+ * Function: zoomTo
+ * 
+ * Zooms the graph to the given scale with an optional boolean center
+ * argument, which is passd to <zoom>.
+ */
+mxGraph.prototype.zoomTo = function(scale, center)
+{
+	this.zoom(scale / this.view.scale, center);
+};
+
+/**
+ * Function: center
+ * 
+ * Centers the graph in the container.
+ * 
+ * Parameters:
+ * 
+ * horizontal - Optional boolean that specifies if the graph should be centered
+ * horizontally. Default is true.
+ * vertical - Optional boolean that specifies if the graph should be centered
+ * vertically. Default is true.
+ * cx - Optional float that specifies the horizontal center. Default is 0.5.
+ * cy - Optional float that specifies the vertical center. Default is 0.5.
+ */
+mxGraph.prototype.center = function(horizontal, vertical, cx, cy)
+{
+	horizontal = (horizontal != null) ? horizontal : true;
+	vertical = (vertical != null) ? vertical : true;
+	cx = (cx != null) ? cx : 0.5;
+	cy = (cy != null) ? cy : 0.5;
+	
+	var hasScrollbars = mxUtils.hasScrollbars(this.container);
+	var cw = this.container.clientWidth;
+	var ch = this.container.clientHeight;
+	var bounds = this.getGraphBounds();
+
+	var t = this.view.translate;
+	var s = this.view.scale;
+
+	var dx = (horizontal) ? cw - bounds.width : 0;
+	var dy = (vertical) ? ch - bounds.height : 0;
+	
+	if (!hasScrollbars)
+	{
+		this.view.setTranslate((horizontal) ? Math.floor(t.x - bounds.x * s + dx * cx / s) : t.x,
+			(vertical) ? Math.floor(t.y - bounds.y * s + dy * cy / s) : t.y);
+	}
+	else
+	{
+		bounds.x -= t.x;
+		bounds.y -= t.y;
+	
+		var sw = this.container.scrollWidth;
+		var sh = this.container.scrollHeight;
+		
+		if (sw > cw)
+		{
+			dx = 0;
+		}
+		
+		if (sh > ch)
+		{
+			dy = 0;
+		}
+
+		this.view.setTranslate(Math.floor(dx / 2 - bounds.x), Math.floor(dy / 2 - bounds.y));
+		this.container.scrollLeft = (sw - cw) / 2;
+		this.container.scrollTop = (sh - ch) / 2;
+	}
+};
+
+/**
+ * Function: zoom
+ * 
+ * Zooms the graph using the given factor. Center is an optional boolean
+ * argument that keeps the graph scrolled to the center. If the center argument
+ * is omitted, then <centerZoom> will be used as its value.
+ */
+mxGraph.prototype.zoom = function(factor, center)
+{
+	center = (center != null) ? center : this.centerZoom;
+	var scale = Math.round(this.view.scale * factor * 100) / 100;
+	var state = this.view.getState(this.getSelectionCell());
+	factor = scale / this.view.scale;
+	
+	if (this.keepSelectionVisibleOnZoom && state != null)
+	{
+		var rect = new mxRectangle(state.x * factor, state.y * factor,
+			state.width * factor, state.height * factor);
+		
+		// Refreshes the display only once if a scroll is carried out
+		this.view.scale = scale;
+		
+		if (!this.scrollRectToVisible(rect))
+		{
+			this.view.revalidate();
+			
+			// Forces an event to be fired but does not revalidate again
+			this.view.setScale(scale);
+		}
+	}
+	else
+	{
+		var hasScrollbars = mxUtils.hasScrollbars(this.container);
+		
+		if (center && !hasScrollbars)
+		{
+			var dx = this.container.offsetWidth;
+			var dy = this.container.offsetHeight;
+			
+			if (factor > 1)
+			{
+				var f = (factor - 1) / (scale * 2);
+				dx *= -f;
+				dy *= -f;
+			}
+			else
+			{
+				var f = (1 / factor - 1) / (this.view.scale * 2);
+				dx *= f;
+				dy *= f;
+			}
+
+			this.view.scaleAndTranslate(scale,
+				this.view.translate.x + dx,
+				this.view.translate.y + dy);
+		}
+		else
+		{
+			// Allows for changes of translate and scrollbars during setscale
+			var tx = this.view.translate.x;
+			var ty = this.view.translate.y;
+			var sl = this.container.scrollLeft;
+			var st = this.container.scrollTop;
+			
+			this.view.setScale(scale);
+			
+			if (hasScrollbars)
+			{
+				var dx = 0;
+				var dy = 0;
+				
+				if (center)
+				{
+					dx = this.container.offsetWidth * (factor - 1) / 2;
+					dy = this.container.offsetHeight * (factor - 1) / 2;
+				}
+				
+				this.container.scrollLeft = (this.view.translate.x - tx) * this.view.scale + Math.round(sl * factor + dx);
+				this.container.scrollTop = (this.view.translate.y - ty) * this.view.scale + Math.round(st * factor + dy);
+			}
+		}
+	}
+};
+
+/**
+ * Function: zoomToRect
+ * 
+ * Zooms the graph to the specified rectangle. If the rectangle does not have same aspect
+ * ratio as the display container, it is increased in the smaller relative dimension only
+ * until the aspect match. The original rectangle is centralised within this expanded one.
+ * 
+ * Note that the input rectangular must be un-scaled and un-translated.
+ * 
+ * Parameters:
+ * 
+ * rect - The un-scaled and un-translated rectangluar region that should be just visible 
+ * after the operation
+ */
+mxGraph.prototype.zoomToRect = function(rect)
+{
+	var scaleX = this.container.clientWidth / rect.width;
+	var scaleY = this.container.clientHeight / rect.height;
+	var aspectFactor = scaleX / scaleY;
+
+	// Remove any overlap of the rect outside the client area
+	rect.x = Math.max(0, rect.x);
+	rect.y = Math.max(0, rect.y);
+	var rectRight = Math.min(this.container.scrollWidth, rect.x + rect.width);
+	var rectBottom = Math.min(this.container.scrollHeight, rect.y + rect.height);
+	rect.width = rectRight - rect.x;
+	rect.height = rectBottom - rect.y;
+
+	// The selection area has to be increased to the same aspect
+	// ratio as the container, centred around the centre point of the 
+	// original rect passed in.
+	if (aspectFactor < 1.0)
+	{
+		// Height needs increasing
+		var newHeight = rect.height / aspectFactor;
+		var deltaHeightBuffer = (newHeight - rect.height) / 2.0;
+		rect.height = newHeight;
+		
+		// Assign up to half the buffer to the upper part of the rect, not crossing 0
+		// put the rest on the bottom
+		var upperBuffer = Math.min(rect.y , deltaHeightBuffer);
+		rect.y = rect.y - upperBuffer;
+		
+		// Check if the bottom has extended too far
+		rectBottom = Math.min(this.container.scrollHeight, rect.y + rect.height);
+		rect.height = rectBottom - rect.y;
+	}
+	else
+	{
+		// Width needs increasing
+		var newWidth = rect.width * aspectFactor;
+		var deltaWidthBuffer = (newWidth - rect.width) / 2.0;
+		rect.width = newWidth;
+		
+		// Assign up to half the buffer to the upper part of the rect, not crossing 0
+		// put the rest on the bottom
+		var leftBuffer = Math.min(rect.x , deltaWidthBuffer);
+		rect.x = rect.x - leftBuffer;
+		
+		// Check if the right hand side has extended too far
+		rectRight = Math.min(this.container.scrollWidth, rect.x + rect.width);
+		rect.width = rectRight - rect.x;
+	}
+
+	var scale = this.container.clientWidth / rect.width;
+	var newScale = this.view.scale * scale;
+
+	if (!mxUtils.hasScrollbars(this.container))
+	{
+		this.view.scaleAndTranslate(newScale, (this.view.translate.x - rect.x / this.view.scale), (this.view.translate.y - rect.y / this.view.scale));
+	}
+	else
+	{
+		this.view.setScale(newScale);
+		this.container.scrollLeft = Math.round(rect.x * scale);
+		this.container.scrollTop = Math.round(rect.y * scale);
+	}
+};
+
+/**
+ * Function: scrollCellToVisible
+ * 
+ * Pans the graph so that it shows the given cell. Optionally the cell may
+ * be centered in the container.
+ * 
+ * To center a given graph if the <container> has no scrollbars, use the following code.
+ * 
+ * [code]
+ * var bounds = graph.getGraphBounds();
+ * graph.view.setTranslate(-bounds.x - (bounds.width - container.clientWidth) / 2,
+ * 						   -bounds.y - (bounds.height - container.clientHeight) / 2);
+ * [/code]
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to be made visible.
+ * center - Optional boolean flag. Default is false.
+ */
+mxGraph.prototype.scrollCellToVisible = function(cell, center)
+{
+	var x = -this.view.translate.x;
+	var y = -this.view.translate.y;
+
+	var state = this.view.getState(cell);
+
+	if (state != null)
+	{
+		var bounds = new mxRectangle(x + state.x, y + state.y, state.width,
+			state.height);
+
+		if (center && this.container != null)
+		{
+			var w = this.container.clientWidth;
+			var h = this.container.clientHeight;
+
+			bounds.x = bounds.getCenterX() - w / 2;
+			bounds.width = w;
+			bounds.y = bounds.getCenterY() - h / 2;
+			bounds.height = h;
+		}
+		
+		var tr = new mxPoint(this.view.translate.x, this.view.translate.y);
+
+		if (this.scrollRectToVisible(bounds))
+		{
+			// Triggers an update via the view's event source
+			var tr2 = new mxPoint(this.view.translate.x, this.view.translate.y);
+			this.view.translate.x = tr.x;
+			this.view.translate.y = tr.y;
+			this.view.setTranslate(tr2.x, tr2.y);
+		}
+	}
+};
+
+/**
+ * Function: scrollRectToVisible
+ * 
+ * Pans the graph so that it shows the given rectangle.
+ * 
+ * Parameters:
+ * 
+ * rect - <mxRectangle> to be made visible.
+ */
+mxGraph.prototype.scrollRectToVisible = function(rect)
+{
+	var isChanged = false;
+	
+	if (rect != null)
+	{
+		var w = this.container.offsetWidth;
+		var h = this.container.offsetHeight;
+
+        var widthLimit = Math.min(w, rect.width);
+        var heightLimit = Math.min(h, rect.height);
+
+		if (mxUtils.hasScrollbars(this.container))
+		{
+			var c = this.container;
+			rect.x += this.view.translate.x;
+			rect.y += this.view.translate.y;
+			var dx = c.scrollLeft - rect.x;
+			var ddx = Math.max(dx - c.scrollLeft, 0);
+
+			if (dx > 0)
+			{
+				c.scrollLeft -= dx + 2;
+			}
+			else
+			{
+				dx = rect.x + widthLimit - c.scrollLeft - c.clientWidth;
+
+				if (dx > 0)
+				{
+					c.scrollLeft += dx + 2;
+				}
+			}
+
+			var dy = c.scrollTop - rect.y;
+			var ddy = Math.max(0, dy - c.scrollTop);
+
+			if (dy > 0)
+			{
+				c.scrollTop -= dy + 2;
+			}
+			else
+			{
+				dy = rect.y + heightLimit - c.scrollTop - c.clientHeight;
+
+				if (dy > 0)
+				{
+					c.scrollTop += dy + 2;
+				}
+			}
+
+			if (!this.useScrollbarsForPanning && (ddx != 0 || ddy != 0))
+			{
+				this.view.setTranslate(ddx, ddy);
+			}
+		}
+		else
+		{
+			var x = -this.view.translate.x;
+			var y = -this.view.translate.y;
+
+			var s = this.view.scale;
+
+			if (rect.x + widthLimit > x + w)
+			{
+				this.view.translate.x -= (rect.x + widthLimit - w - x) / s;
+				isChanged = true;
+			}
+
+			if (rect.y + heightLimit > y + h)
+			{
+				this.view.translate.y -= (rect.y + heightLimit - h - y) / s;
+				isChanged = true;
+			}
+
+			if (rect.x < x)
+			{
+				this.view.translate.x += (x - rect.x) / s;
+				isChanged = true;
+			}
+
+			if (rect.y  < y)
+			{
+				this.view.translate.y += (y - rect.y) / s;
+				isChanged = true;
+			}
+
+			if (isChanged)
+			{
+				this.view.refresh();
+				
+				// Repaints selection marker (ticket 18)
+				if (this.selectionCellsHandler != null)
+				{
+					this.selectionCellsHandler.refresh();
+				}
+			}
+		}
+	}
+
+	return isChanged;
+};
+
+/**
+ * Function: getCellGeometry
+ * 
+ * Returns the <mxGeometry> for the given cell. This implementation uses
+ * <mxGraphModel.getGeometry>. Subclasses can override this to implement
+ * specific geometries for cells in only one graph, that is, it can return
+ * geometries that depend on the current state of the view.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose geometry should be returned.
+ */
+mxGraph.prototype.getCellGeometry = function(cell)
+{
+	return this.model.getGeometry(cell);
+};
+
+/**
+ * Function: isCellVisible
+ * 
+ * Returns true if the given cell is visible in this graph. This
+ * implementation uses <mxGraphModel.isVisible>. Subclassers can override
+ * this to implement specific visibility for cells in only one graph, that
+ * is, without affecting the visible state of the cell.
+ * 
+ * When using dynamic filter expressions for cell visibility, then the
+ * graph should be revalidated after the filter expression has changed.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose visible state should be returned.
+ */
+mxGraph.prototype.isCellVisible = function(cell)
+{
+	return this.model.isVisible(cell);
+};
+
+/**
+ * Function: isCellCollapsed
+ * 
+ * Returns true if the given cell is collapsed in this graph. This
+ * implementation uses <mxGraphModel.isCollapsed>. Subclassers can override
+ * this to implement specific collapsed states for cells in only one graph,
+ * that is, without affecting the collapsed state of the cell.
+ * 
+ * When using dynamic filter expressions for the collapsed state, then the
+ * graph should be revalidated after the filter expression has changed.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose collapsed state should be returned.
+ */
+mxGraph.prototype.isCellCollapsed = function(cell)
+{
+	return this.model.isCollapsed(cell);
+};
+
+/**
+ * Function: isCellConnectable
+ * 
+ * Returns true if the given cell is connectable in this graph. This
+ * implementation uses <mxGraphModel.isConnectable>. Subclassers can override
+ * this to implement specific connectable states for cells in only one graph,
+ * that is, without affecting the connectable state of the cell in the model.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose connectable state should be returned.
+ */
+mxGraph.prototype.isCellConnectable = function(cell)
+{
+	return this.model.isConnectable(cell);
+};
+
+/**
+ * Function: isOrthogonal
+ * 
+ * Returns true if perimeter points should be computed such that the
+ * resulting edge has only horizontal or vertical segments.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCellState> that represents the edge.
+ */
+mxGraph.prototype.isOrthogonal = function(edge)
+{
+	var orthogonal = edge.style[mxConstants.STYLE_ORTHOGONAL];
+	
+	if (orthogonal != null)
+	{
+		return orthogonal;
+	}
+	
+	var tmp = this.view.getEdgeStyle(edge);
+	
+	return tmp == mxEdgeStyle.SegmentConnector ||
+		tmp == mxEdgeStyle.ElbowConnector ||
+		tmp == mxEdgeStyle.SideToSide ||
+		tmp == mxEdgeStyle.TopToBottom ||
+		tmp == mxEdgeStyle.EntityRelation ||
+		tmp == mxEdgeStyle.OrthConnector;
+};
+
+/**
+ * Function: isLoop
+ * 
+ * Returns true if the given cell state is a loop.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> that represents a potential loop.
+ */
+mxGraph.prototype.isLoop = function(state)
+{
+	var src = state.getVisibleTerminalState(true);
+	var trg = state.getVisibleTerminalState(false);
+	
+	return (src != null && src == trg);
+};
+
+/**
+ * Function: isCloneEvent
+ * 
+ * Returns true if the given event is a clone event. This implementation
+ * returns true if control is pressed.
+ */
+mxGraph.prototype.isCloneEvent = function(evt)
+{
+	return mxEvent.isControlDown(evt);
+};
+
+/**
+ * Function: isTransparentClickEvent
+ * 
+ * Hook for implementing click-through behaviour on selected cells. If this
+ * returns true the cell behind the selected cell will be selected. This
+ * implementation returns false;
+ */
+mxGraph.prototype.isTransparentClickEvent = function(evt)
+{
+	return false;
+};
+
+/**
+ * Function: isToggleEvent
+ * 
+ * Returns true if the given event is a toggle event. This implementation
+ * returns true if the meta key (Cmd) is pressed on Macs or if control is
+ * pressed on any other platform.
+ */
+mxGraph.prototype.isToggleEvent = function(evt)
+{
+	return (mxClient.IS_MAC) ? mxEvent.isMetaDown(evt) : mxEvent.isControlDown(evt);
+};
+
+/**
+ * Function: isGridEnabledEvent
+ * 
+ * Returns true if the given mouse event should be aligned to the grid.
+ */
+mxGraph.prototype.isGridEnabledEvent = function(evt)
+{
+	return evt != null && !mxEvent.isAltDown(evt);
+};
+
+/**
+ * Function: isConstrainedEvent
+ * 
+ * Returns true if the given mouse event should be aligned to the grid.
+ */
+mxGraph.prototype.isConstrainedEvent = function(evt)
+{
+	return mxEvent.isShiftDown(evt);
+};
+
+/**
+ * Function: isIgnoreTerminalEvent
+ * 
+ * Returns true if the given mouse event should not allow any connections to be
+ * made. This implementation returns false.
+ */
+mxGraph.prototype.isIgnoreTerminalEvent = function(evt)
+{
+	return false;
+};
+
+/**
+ * Group: Validation
+ */
+
+/**
+ * Function: validationAlert
+ * 
+ * Displays the given validation error in a dialog. This implementation uses
+ * mxUtils.alert.
+ */
+mxGraph.prototype.validationAlert = function(message)
+{
+	mxUtils.alert(message);
+};
+
+/**
+ * Function: isEdgeValid
+ * 
+ * Checks if the return value of <getEdgeValidationError> for the given
+ * arguments is null.
+ *  
+ * Parameters:
+ * 
+ * edge - <mxCell> that represents the edge to validate.
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ */
+mxGraph.prototype.isEdgeValid = function(edge, source, target)
+{
+	return this.getEdgeValidationError(edge, source, target) == null;
+};
+
+/**
+ * Function: getEdgeValidationError
+ * 
+ * Returns the validation error message to be displayed when inserting or
+ * changing an edges' connectivity. A return value of null means the edge
+ * is valid, a return value of '' means it's not valid, but do not display
+ * an error message. Any other (non-empty) string returned from this method
+ * is displayed as an error message when trying to connect an edge to a
+ * source and target. This implementation uses the <multiplicities>, and
+ * checks <multigraph>, <allowDanglingEdges> and <allowLoops> to generate
+ * validation errors.
+ * 
+ * For extending this method with specific checks for source/target cells,
+ * the method can be extended as follows. Returning an empty string means
+ * the edge is invalid with no error message, a non-null string specifies
+ * the error message, and null means the edge is valid.
+ * 
+ * (code)
+ * graph.getEdgeValidationError = function(edge, source, target)
+ * {
+ *   if (source != null && target != null &&
+ *     this.model.getValue(source) != null &&
+ *     this.model.getValue(target) != null)
+ *   {
+ *     if (target is not valid for source)
+ *     {
+ *       return 'Invalid Target';
+ *     }
+ *   }
+ *   
+ *   // "Supercall"
+ *   return mxGraph.prototype.getEdgeValidationError.apply(this, arguments);
+ * }
+ * (end)
+ *  
+ * Parameters:
+ * 
+ * edge - <mxCell> that represents the edge to validate.
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ */
+mxGraph.prototype.getEdgeValidationError = function(edge, source, target)
+{
+	if (edge != null && !this.isAllowDanglingEdges() && (source == null || target == null))
+	{
+		return '';
+	}
+	
+	if (edge != null && this.model.getTerminal(edge, true) == null &&
+		this.model.getTerminal(edge, false) == null)	
+	{
+		return null;
+	}
+	
+	// Checks if we're dealing with a loop
+	if (!this.allowLoops && source == target && source != null)
+	{
+		return '';
+	}
+	
+	// Checks if the connection is generally allowed
+	if (!this.isValidConnection(source, target))
+	{
+		return '';
+	}
+
+	if (source != null && target != null)
+	{
+		var error = '';
+
+		// Checks if the cells are already connected
+		// and adds an error message if required			
+		if (!this.multigraph)
+		{
+			var tmp = this.model.getEdgesBetween(source, target, true);
+			
+			// Checks if the source and target are not connected by another edge
+			if (tmp.length > 1 || (tmp.length == 1 && tmp[0] != edge))
+			{
+				error += (mxResources.get(this.alreadyConnectedResource) ||
+					this.alreadyConnectedResource)+'\n';
+			}
+		}
+
+		// Gets the number of outgoing edges from the source
+		// and the number of incoming edges from the target
+		// without counting the edge being currently changed.
+		var sourceOut = this.model.getDirectedEdgeCount(source, true, edge);
+		var targetIn = this.model.getDirectedEdgeCount(target, false, edge);
+
+		// Checks the change against each multiplicity rule
+		if (this.multiplicities != null)
+		{
+			for (var i = 0; i < this.multiplicities.length; i++)
+			{
+				var err = this.multiplicities[i].check(this, edge, source,
+					target, sourceOut, targetIn);
+				
+				if (err != null)
+				{
+					error += err;
+				}
+			}
+		}
+
+		// Validates the source and target terminals independently
+		var err = this.validateEdge(edge, source, target);
+		
+		if (err != null)
+		{
+			error += err;
+		}
+		
+		return (error.length > 0) ? error : null;
+	}
+	
+	return (this.allowDanglingEdges) ? null : '';
+};
+
+/**
+ * Function: validateEdge
+ * 
+ * Hook method for subclassers to return an error message for the given
+ * edge and terminals. This implementation returns null.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> that represents the edge to validate.
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ */
+mxGraph.prototype.validateEdge = function(edge, source, target)
+{
+	return null;
+};
+
+/**
+ * Function: validateGraph
+ * 
+ * Validates the graph by validating each descendant of the given cell or
+ * the root of the model. Context is an object that contains the validation
+ * state for the complete validation run. The validation errors are
+ * attached to their cells using <setCellWarning>. Returns null in the case of
+ * successful validation or an array of strings (warnings) in the case of
+ * failed validations.
+ * 
+ * Paramters:
+ * 
+ * cell - Optional <mxCell> to start the validation recursion. Default is
+ * the graph root.
+ * context - Object that represents the global validation state.
+ */
+mxGraph.prototype.validateGraph = function(cell, context)
+{
+	cell = (cell != null) ? cell : this.model.getRoot();
+	context = (context != null) ? context : new Object();
+	
+	var isValid = true;
+	var childCount = this.model.getChildCount(cell);
+	
+	for (var i = 0; i < childCount; i++)
+	{
+		var tmp = this.model.getChildAt(cell, i);
+		var ctx = context;
+		
+		if (this.isValidRoot(tmp))
+		{
+			ctx = new Object();
+		}
+		
+		var warn = this.validateGraph(tmp, ctx);
+		
+		if (warn != null)
+		{
+			this.setCellWarning(tmp, warn.replace(/\n/g, '<br>'));
+		}
+		else
+		{
+			this.setCellWarning(tmp, null);
+		}
+		
+		isValid = isValid && warn == null;
+	}
+	
+	var warning = '';
+	
+	// Adds error for invalid children if collapsed (children invisible)
+	if (this.isCellCollapsed(cell) && !isValid)
+	{
+		warning += (mxResources.get(this.containsValidationErrorsResource) ||
+			this.containsValidationErrorsResource) + '\n';
+	}
+	
+	// Checks edges and cells using the defined multiplicities
+	if (this.model.isEdge(cell))
+	{
+		warning += this.getEdgeValidationError(cell,
+		this.model.getTerminal(cell, true),
+		this.model.getTerminal(cell, false)) || '';
+	}
+	else
+	{
+		warning += this.getCellValidationError(cell) || '';
+	}
+	
+	// Checks custom validation rules
+	var err = this.validateCell(cell, context);
+	
+	if (err != null)
+	{
+		warning += err;
+	}
+	
+	// Updates the display with the warning icons
+	// before any potential alerts are displayed.
+	// LATER: Move this into addCellOverlay. Redraw
+	// should check if overlay was added or removed.
+	if (this.model.getParent(cell) == null)
+	{
+		this.view.validate();
+	}
+
+	return (warning.length > 0 || !isValid) ? warning : null;
+};
+
+/**
+ * Function: getCellValidationError
+ * 
+ * Checks all <multiplicities> that cannot be enforced while the graph is
+ * being modified, namely, all multiplicities that require a minimum of
+ * 1 edge.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> for which the multiplicities should be checked.
+ */
+mxGraph.prototype.getCellValidationError = function(cell)
+{
+	var outCount = this.model.getDirectedEdgeCount(cell, true);
+	var inCount = this.model.getDirectedEdgeCount(cell, false);
+	var value = this.model.getValue(cell);
+	var error = '';
+
+	if (this.multiplicities != null)
+	{
+		for (var i = 0; i < this.multiplicities.length; i++)
+		{
+			var rule = this.multiplicities[i];
+			
+			if (rule.source && mxUtils.isNode(value, rule.type,
+				rule.attr, rule.value) && (outCount > rule.max ||
+				outCount < rule.min))
+			{
+				error += rule.countError + '\n';
+			}
+			else if (!rule.source && mxUtils.isNode(value, rule.type,
+					rule.attr, rule.value) && (inCount > rule.max ||
+					inCount < rule.min))
+			{
+				error += rule.countError + '\n';
+			}
+		}
+	}
+
+	return (error.length > 0) ? error : null;
+};
+
+/**
+ * Function: validateCell
+ * 
+ * Hook method for subclassers to return an error message for the given
+ * cell and validation context. This implementation returns null. Any HTML
+ * breaks will be converted to linefeeds in the calling method.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the cell to validate.
+ * context - Object that represents the global validation state.
+ */
+mxGraph.prototype.validateCell = function(cell, context)
+{
+	return null;
+};
+
+/**
+ * Group: Graph appearance
+ */
+
+/**
+ * Function: getBackgroundImage
+ * 
+ * Returns the <backgroundImage> as an <mxImage>.
+ */
+mxGraph.prototype.getBackgroundImage = function()
+{
+	return this.backgroundImage;
+};
+
+/**
+ * Function: setBackgroundImage
+ * 
+ * Sets the new <backgroundImage>.
+ * 
+ * Parameters:
+ * 
+ * image - New <mxImage> to be used for the background.
+ */
+mxGraph.prototype.setBackgroundImage = function(image)
+{
+	this.backgroundImage = image;
+};
+
+/**
+ * Function: getFoldingImage
+ * 
+ * Returns the <mxImage> used to display the collapsed state of
+ * the specified cell state. This returns null for all edges.
+ */
+mxGraph.prototype.getFoldingImage = function(state)
+{
+	if (state != null && this.foldingEnabled && !this.getModel().isEdge(state.cell))
+	{
+		var tmp = this.isCellCollapsed(state.cell);
+		
+		if (this.isCellFoldable(state.cell, !tmp))
+		{
+			return (tmp) ? this.collapsedImage : this.expandedImage;
+		}
+	}
+	
+	return null;
+};
+
+/**
+ * Function: convertValueToString
+ * 
+ * Returns the textual representation for the given cell. This
+ * implementation returns the nodename or string-representation of the user
+ * object.
+ *
+ * Example:
+ * 
+ * The following returns the label attribute from the cells user
+ * object if it is an XML node.
+ * 
+ * (code)
+ * graph.convertValueToString = function(cell)
+ * {
+ * 	return cell.getAttribute('label');
+ * }
+ * (end)
+ * 
+ * See also: <cellLabelChanged>.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose textual representation should be returned.
+ */
+mxGraph.prototype.convertValueToString = function(cell)
+{
+	var value = this.model.getValue(cell);
+	
+	if (value != null)
+	{
+		if (mxUtils.isNode(value))
+		{
+			return value.nodeName;
+		}
+		else if (typeof(value.toString) == 'function')
+		{
+			return value.toString();
+		}
+	}
+	
+	return '';
+};
+
+/**
+ * Function: getLabel
+ * 
+ * Returns a string or DOM node that represents the label for the given
+ * cell. This implementation uses <convertValueToString> if <labelsVisible>
+ * is true. Otherwise it returns an empty string.
+ * 
+ * To truncate a label to match the size of the cell, the following code
+ * can be used.
+ * 
+ * (code)
+ * graph.getLabel = function(cell)
+ * {
+ *   var label = mxGraph.prototype.getLabel.apply(this, arguments);
+ * 
+ *   if (label != null && this.model.isVertex(cell))
+ *   {
+ *     var geo = this.getCellGeometry(cell);
+ * 
+ *     if (geo != null)
+ *     {
+ *       var max = parseInt(geo.width / 8);
+ * 
+ *       if (label.length > max)
+ *       {
+ *         label = label.substring(0, max)+'...';
+ *       }
+ *     }
+ *   } 
+ *   return mxUtils.htmlEntities(label);
+ * }
+ * (end)
+ * 
+ * A resize listener is needed in the graph to force a repaint of the label
+ * after a resize.
+ * 
+ * (code)
+ * graph.addListener(mxEvent.RESIZE_CELLS, function(sender, evt)
+ * {
+ *   var cells = evt.getProperty('cells');
+ * 
+ *   for (var i = 0; i < cells.length; i++)
+ *   {
+ *     this.view.removeState(cells[i]);
+ *   }
+ * });
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose label should be returned.
+ */
+mxGraph.prototype.getLabel = function(cell)
+{
+	var result = '';
+	
+	if (this.labelsVisible && cell != null)
+	{
+		var state = this.view.getState(cell);
+		var style = (state != null) ? state.style : this.getCellStyle(cell);
+		
+		if (!mxUtils.getValue(style, mxConstants.STYLE_NOLABEL, false))
+		{
+			result = this.convertValueToString(cell);
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: isHtmlLabel
+ * 
+ * Returns true if the label must be rendered as HTML markup. The default
+ * implementation returns <htmlLabels>.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose label should be displayed as HTML markup.
+ */
+mxGraph.prototype.isHtmlLabel = function(cell)
+{
+	return this.isHtmlLabels();
+};
+ 
+/**
+ * Function: isHtmlLabels
+ * 
+ * Returns <htmlLabels>.
+ */
+mxGraph.prototype.isHtmlLabels = function()
+{
+	return this.htmlLabels;
+};
+ 
+/**
+ * Function: setHtmlLabels
+ * 
+ * Sets <htmlLabels>.
+ */
+mxGraph.prototype.setHtmlLabels = function(value)
+{
+	this.htmlLabels = value;
+};
+
+/**
+ * Function: isWrapping
+ * 
+ * This enables wrapping for HTML labels.
+ * 
+ * Returns true if no white-space CSS style directive should be used for
+ * displaying the given cells label. This implementation returns true if
+ * <mxConstants.STYLE_WHITE_SPACE> in the style of the given cell is 'wrap'.
+ * 
+ * This is used as a workaround for IE ignoring the white-space directive
+ * of child elements if the directive appears in a parent element. It
+ * should be overridden to return true if a white-space directive is used
+ * in the HTML markup that represents the given cells label. In order for
+ * HTML markup to work in labels, <isHtmlLabel> must also return true
+ * for the given cell.
+ * 
+ * Example:
+ * 
+ * (code)
+ * graph.getLabel = function(cell)
+ * {
+ *   var tmp = mxGraph.prototype.getLabel.apply(this, arguments); // "supercall"
+ *   
+ *   if (this.model.isEdge(cell))
+ *   {
+ *     tmp = '<div style="width: 150px; white-space:normal;">'+tmp+'</div>';
+ *   }
+ *   
+ *   return tmp;
+ * }
+ * 
+ * graph.isWrapping = function(state)
+ * {
+ * 	 return this.model.isEdge(state.cell);
+ * }
+ * (end)
+ * 
+ * Makes sure no edge label is wider than 150 pixels, otherwise the content
+ * is wrapped. Note: No width must be specified for wrapped vertex labels as
+ * the vertex defines the width in its geometry.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCell> whose label should be wrapped.
+ */
+mxGraph.prototype.isWrapping = function(cell)
+{
+	var state = this.view.getState(cell);
+	var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+	return (style != null) ? style[mxConstants.STYLE_WHITE_SPACE] == 'wrap' : false;
+};
+
+/**
+ * Function: isLabelClipped
+ * 
+ * Returns true if the overflow portion of labels should be hidden. If this
+ * returns true then vertex labels will be clipped to the size of the vertices.
+ * This implementation returns true if <mxConstants.STYLE_OVERFLOW> in the
+ * style of the given cell is 'hidden'.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCell> whose label should be clipped.
+ */
+mxGraph.prototype.isLabelClipped = function(cell)
+{
+	var state = this.view.getState(cell);
+	var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+	return (style != null) ? style[mxConstants.STYLE_OVERFLOW] == 'hidden' : false;
+};
+
+/**
+ * Function: getTooltip
+ * 
+ * Returns the string or DOM node that represents the tooltip for the given
+ * state, node and coordinate pair. This implementation checks if the given
+ * node is a folding icon or overlay and returns the respective tooltip. If
+ * this does not result in a tooltip, the handler for the cell is retrieved
+ * from <selectionCellsHandler> and the optional getTooltipForNode method is
+ * called. If no special tooltip exists here then <getTooltipForCell> is used
+ * with the cell in the given state as the argument to return a tooltip for the
+ * given state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose tooltip should be returned.
+ * node - DOM node that is currently under the mouse.
+ * x - X-coordinate of the mouse.
+ * y - Y-coordinate of the mouse.
+ */
+mxGraph.prototype.getTooltip = function(state, node, x, y)
+{
+	var tip = null;
+	
+	if (state != null)
+	{
+		// Checks if the mouse is over the folding icon
+		if (state.control != null && (node == state.control.node ||
+			node.parentNode == state.control.node))
+		{
+			tip = this.collapseExpandResource;
+			tip = mxUtils.htmlEntities(mxResources.get(tip) || tip).replace(/\\n/g, '<br>');
+		}
+
+		if (tip == null && state.overlays != null)
+		{
+			state.overlays.visit(function(id, shape)
+			{
+				// LATER: Exit loop if tip is not null
+				if (tip == null && (node == shape.node || node.parentNode == shape.node))
+				{
+					tip = shape.overlay.toString();
+				}
+			});
+		}
+		
+		if (tip == null)
+		{
+			var handler = this.selectionCellsHandler.getHandler(state.cell);
+			
+			if (handler != null && typeof(handler.getTooltipForNode) == 'function')
+			{
+				tip = handler.getTooltipForNode(node);
+			}
+		}
+		
+		if (tip == null)
+		{
+			tip = this.getTooltipForCell(state.cell);
+		}
+	}
+	
+	return tip;
+};
+
+/**
+ * Function: getTooltipForCell
+ * 
+ * Returns the string or DOM node to be used as the tooltip for the given
+ * cell. This implementation uses the cells getTooltip function if it
+ * exists, or else it returns <convertValueToString> for the cell.
+ * 
+ * Example:
+ * 
+ * (code)
+ * graph.getTooltipForCell = function(cell)
+ * {
+ *   return 'Hello, World!';
+ * }
+ * (end)
+ * 
+ * Replaces all tooltips with the string Hello, World!
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose tooltip should be returned.
+ */
+mxGraph.prototype.getTooltipForCell = function(cell)
+{
+	var tip = null;
+	
+	if (cell != null && cell.getTooltip != null)
+	{
+		tip = cell.getTooltip();
+	}
+	else
+	{
+		tip = this.convertValueToString(cell);
+	}
+	
+	return tip;
+};
+
+/**
+ * Function: getCursorForMouseEvent
+ * 
+ * Returns the cursor value to be used for the CSS of the shape for the
+ * given event. This implementation calls <getCursorForCell>.
+ * 
+ * Parameters:
+ * 
+ * me - <mxMouseEvent> whose cursor should be returned.
+ */
+mxGraph.prototype.getCursorForMouseEvent = function(me)
+{
+	return this.getCursorForCell(me.getCell());
+};
+
+/**
+ * Function: getCursorForCell
+ * 
+ * Returns the cursor value to be used for the CSS of the shape for the
+ * given cell. This implementation returns null.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose cursor should be returned.
+ */
+mxGraph.prototype.getCursorForCell = function(cell)
+{
+	return null;
+};
+
+/**
+ * Function: getStartSize
+ * 
+ * Returns the start size of the given swimlane, that is, the width or
+ * height of the part that contains the title, depending on the
+ * horizontal style. The return value is an <mxRectangle> with either
+ * width or height set as appropriate.
+ * 
+ * Parameters:
+ * 
+ * swimlane - <mxCell> whose start size should be returned.
+ */
+mxGraph.prototype.getStartSize = function(swimlane)
+{
+	var result = new mxRectangle();
+	var state = this.view.getState(swimlane);
+	var style = (state != null) ? state.style : this.getCellStyle(swimlane);
+	
+	if (style != null)
+	{
+		var size = parseInt(mxUtils.getValue(style,
+			mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE));
+		
+		if (mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, true))
+		{
+			result.height = size;
+		}
+		else
+		{
+			result.width = size;
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: getImage
+ * 
+ * Returns the image URL for the given cell state. This implementation
+ * returns the value stored under <mxConstants.STYLE_IMAGE> in the cell
+ * style.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose image URL should be returned.
+ */
+mxGraph.prototype.getImage = function(state)
+{
+	return (state != null && state.style != null) ? state.style[mxConstants.STYLE_IMAGE] : null;
+};
+
+/**
+ * Function: getVerticalAlign
+ * 
+ * Returns the vertical alignment for the given cell state. This
+ * implementation returns the value stored under
+ * <mxConstants.STYLE_VERTICAL_ALIGN> in the cell style.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose vertical alignment should be
+ * returned.
+ */
+mxGraph.prototype.getVerticalAlign = function(state)
+{
+	return (state != null && state.style != null) ?
+		(state.style[mxConstants.STYLE_VERTICAL_ALIGN] ||
+		mxConstants.ALIGN_MIDDLE) : null;
+};
+
+/**
+ * Function: getIndicatorColor
+ * 
+ * Returns the indicator color for the given cell state. This
+ * implementation returns the value stored under
+ * <mxConstants.STYLE_INDICATOR_COLOR> in the cell style.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose indicator color should be
+ * returned.
+ */
+mxGraph.prototype.getIndicatorColor = function(state)
+{
+	return (state != null && state.style != null) ? state.style[mxConstants.STYLE_INDICATOR_COLOR] : null;
+};
+
+/**
+ * Function: getIndicatorGradientColor
+ * 
+ * Returns the indicator gradient color for the given cell state. This
+ * implementation returns the value stored under
+ * <mxConstants.STYLE_INDICATOR_GRADIENTCOLOR> in the cell style.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose indicator gradient color should be
+ * returned.
+ */
+mxGraph.prototype.getIndicatorGradientColor = function(state)
+{
+	return (state != null && state.style != null) ? state.style[mxConstants.STYLE_INDICATOR_GRADIENTCOLOR] : null;
+};
+
+/**
+ * Function: getIndicatorShape
+ * 
+ * Returns the indicator shape for the given cell state. This
+ * implementation returns the value stored under
+ * <mxConstants.STYLE_INDICATOR_SHAPE> in the cell style.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose indicator shape should be returned.
+ */
+mxGraph.prototype.getIndicatorShape = function(state)
+{
+	return (state != null && state.style != null) ? state.style[mxConstants.STYLE_INDICATOR_SHAPE] : null;
+};
+
+/**
+ * Function: getIndicatorImage
+ * 
+ * Returns the indicator image for the given cell state. This
+ * implementation returns the value stored under
+ * <mxConstants.STYLE_INDICATOR_IMAGE> in the cell style.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose indicator image should be returned.
+ */
+mxGraph.prototype.getIndicatorImage = function(state)
+{
+	return (state != null && state.style != null) ? state.style[mxConstants.STYLE_INDICATOR_IMAGE] : null;
+};
+
+/**
+ * Function: getBorder
+ * 
+ * Returns the value of <border>.
+ */
+mxGraph.prototype.getBorder = function()
+{
+	return this.border;
+};
+
+/**
+ * Function: setBorder
+ * 
+ * Sets the value of <border>.
+ * 
+ * Parameters:
+ * 
+ * value - Positive integer that represents the border to be used.
+ */
+mxGraph.prototype.setBorder = function(value)
+{
+	this.border = value;
+};
+
+/**
+ * Function: isSwimlane
+ * 
+ * Returns true if the given cell is a swimlane in the graph. A swimlane is
+ * a container cell with some specific behaviour. This implementation
+ * checks if the shape associated with the given cell is a <mxSwimlane>.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to be checked.
+ */
+mxGraph.prototype.isSwimlane = function (cell)
+{
+	if (cell != null)
+	{
+		if (this.model.getParent(cell) != this.model.getRoot())
+		{
+			var state = this.view.getState(cell);
+			var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+			if (style != null && !this.model.isEdge(cell))
+			{
+				return style[mxConstants.STYLE_SHAPE] == mxConstants.SHAPE_SWIMLANE;
+			}
+		}
+	}
+	
+	return false;
+};
+
+/**
+ * Group: Graph behaviour
+ */
+
+/**
+ * Function: isResizeContainer
+ * 
+ * Returns <resizeContainer>.
+ */
+mxGraph.prototype.isResizeContainer = function()
+{
+	return this.resizeContainer;
+};
+
+/**
+ * Function: setResizeContainer
+ * 
+ * Sets <resizeContainer>.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if the container should be resized.
+ */
+mxGraph.prototype.setResizeContainer = function(value)
+{
+	this.resizeContainer = value;
+};
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns true if the graph is <enabled>.
+ */
+mxGraph.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ * 
+ * Specifies if the graph should allow any interactions. This
+ * implementation updates <enabled>.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if the graph should be enabled.
+ */
+mxGraph.prototype.setEnabled = function(value)
+{
+	this.enabled = value;
+};
+
+/**
+ * Function: isEscapeEnabled
+ * 
+ * Returns <escapeEnabled>.
+ */
+mxGraph.prototype.isEscapeEnabled = function()
+{
+	return this.escapeEnabled;
+};
+
+/**
+ * Function: setEscapeEnabled
+ * 
+ * Sets <escapeEnabled>.
+ * 
+ * Parameters:
+ * 
+ * enabled - Boolean indicating if escape should be enabled.
+ */
+mxGraph.prototype.setEscapeEnabled = function(value)
+{
+	this.escapeEnabled = value;
+};
+
+/**
+ * Function: isInvokesStopCellEditing
+ * 
+ * Returns <invokesStopCellEditing>.
+ */
+mxGraph.prototype.isInvokesStopCellEditing = function()
+{
+	return this.invokesStopCellEditing;
+};
+
+/**
+ * Function: setInvokesStopCellEditing
+ * 
+ * Sets <invokesStopCellEditing>.
+ */
+mxGraph.prototype.setInvokesStopCellEditing = function(value)
+{
+	this.invokesStopCellEditing = value;
+};
+
+/**
+ * Function: isEnterStopsCellEditing
+ * 
+ * Returns <enterStopsCellEditing>.
+ */
+mxGraph.prototype.isEnterStopsCellEditing = function()
+{
+	return this.enterStopsCellEditing;
+};
+
+/**
+ * Function: setEnterStopsCellEditing
+ * 
+ * Sets <enterStopsCellEditing>.
+ */
+mxGraph.prototype.setEnterStopsCellEditing = function(value)
+{
+	this.enterStopsCellEditing = value;
+};
+
+/**
+ * Function: isCellLocked
+ * 
+ * Returns true if the given cell may not be moved, sized, bended,
+ * disconnected, edited or selected. This implementation returns true for
+ * all vertices with a relative geometry if <locked> is false.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose locked state should be returned.
+ */
+mxGraph.prototype.isCellLocked = function(cell)
+{
+	var geometry = this.model.getGeometry(cell);
+	
+	return this.isCellsLocked() || (geometry != null && this.model.isVertex(cell) && geometry.relative);
+};
+
+/**
+ * Function: isCellsLocked
+ * 
+ * Returns true if the given cell may not be moved, sized, bended,
+ * disconnected, edited or selected. This implementation returns true for
+ * all vertices with a relative geometry if <locked> is false.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose locked state should be returned.
+ */
+mxGraph.prototype.isCellsLocked = function()
+{
+	return this.cellsLocked;
+};
+
+/**
+ * Function: setLocked
+ * 
+ * Sets if any cell may be moved, sized, bended, disconnected, edited or
+ * selected.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean that defines the new value for <cellsLocked>.
+ */
+mxGraph.prototype.setCellsLocked = function(value)
+{
+	this.cellsLocked = value;
+};
+
+/**
+ * Function: getCloneableCells
+ * 
+ * Returns the cells which may be exported in the given array of cells.
+ */
+mxGraph.prototype.getCloneableCells = function(cells)
+{
+	return this.model.filterCells(cells, mxUtils.bind(this, function(cell)
+	{
+		return this.isCellCloneable(cell);
+	}));
+};
+
+/**
+ * Function: isCellCloneable
+ * 
+ * Returns true if the given cell is cloneable. This implementation returns
+ * <isCellsCloneable> for all cells unless a cell style specifies
+ * <mxConstants.STYLE_CLONEABLE> to be 0. 
+ * 
+ * Parameters:
+ * 
+ * cell - Optional <mxCell> whose cloneable state should be returned.
+ */
+mxGraph.prototype.isCellCloneable = function(cell)
+{
+	var state = this.view.getState(cell);
+	var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+	return this.isCellsCloneable() && style[mxConstants.STYLE_CLONEABLE] != 0;
+};
+
+/**
+ * Function: isCellsCloneable
+ * 
+ * Returns <cellsCloneable>, that is, if the graph allows cloning of cells
+ * by using control-drag.
+ */
+mxGraph.prototype.isCellsCloneable = function()
+{
+	return this.cellsCloneable;
+};
+
+/**
+ * Function: setCellsCloneable
+ * 
+ * Specifies if the graph should allow cloning of cells by holding down the
+ * control key while cells are being moved. This implementation updates
+ * <cellsCloneable>.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if the graph should be cloneable.
+ */
+mxGraph.prototype.setCellsCloneable = function(value)
+{
+	this.cellsCloneable = value;
+};
+
+/**
+ * Function: getExportableCells
+ * 
+ * Returns the cells which may be exported in the given array of cells.
+ */
+mxGraph.prototype.getExportableCells = function(cells)
+{
+	return this.model.filterCells(cells, mxUtils.bind(this, function(cell)
+	{
+		return this.canExportCell(cell);
+	}));
+};
+
+/**
+ * Function: canExportCell
+ * 
+ * Returns true if the given cell may be exported to the clipboard. This
+ * implementation returns <exportEnabled> for all cells.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the cell to be exported.
+ */
+mxGraph.prototype.canExportCell = function(cell)
+{
+	return this.exportEnabled;
+};
+
+/**
+ * Function: getImportableCells
+ * 
+ * Returns the cells which may be imported in the given array of cells.
+ */
+mxGraph.prototype.getImportableCells = function(cells)
+{
+	return this.model.filterCells(cells, mxUtils.bind(this, function(cell)
+	{
+		return this.canImportCell(cell);
+	}));
+};
+
+/**
+ * Function: canImportCell
+ * 
+ * Returns true if the given cell may be imported from the clipboard.
+ * This implementation returns <importEnabled> for all cells.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the cell to be imported.
+ */
+mxGraph.prototype.canImportCell = function(cell)
+{
+	return this.importEnabled;
+};
+
+/**
+ * Function: isCellSelectable
+ *
+ * Returns true if the given cell is selectable. This implementation
+ * returns <cellsSelectable>.
+ * 
+ * To add a new style for making cells (un)selectable, use the following code.
+ * 
+ * (code)
+ * mxGraph.prototype.isCellSelectable = function(cell)
+ * {
+ *   var state = this.view.getState(cell);
+ *   var style = (state != null) ? state.style : this.getCellStyle(cell);
+ *   
+ *   return this.isCellsSelectable() && !this.isCellLocked(cell) && style['selectable'] != 0;
+ * };
+ * (end)
+ * 
+ * You can then use the new style as shown in this example.
+ * 
+ * (code)
+ * graph.insertVertex(parent, null, 'Hello,', 20, 20, 80, 30, 'selectable=0');
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose selectable state should be returned.
+ */
+mxGraph.prototype.isCellSelectable = function(cell)
+{
+	return this.isCellsSelectable();
+};
+
+/**
+ * Function: isCellsSelectable
+ *
+ * Returns <cellsSelectable>.
+ */
+mxGraph.prototype.isCellsSelectable = function()
+{
+	return this.cellsSelectable;
+};
+
+/**
+ * Function: setCellsSelectable
+ *
+ * Sets <cellsSelectable>.
+ */
+mxGraph.prototype.setCellsSelectable = function(value)
+{
+	this.cellsSelectable = value;
+};
+
+/**
+ * Function: getDeletableCells
+ * 
+ * Returns the cells which may be exported in the given array of cells.
+ */
+mxGraph.prototype.getDeletableCells = function(cells)
+{
+	return this.model.filterCells(cells, mxUtils.bind(this, function(cell)
+	{
+		return this.isCellDeletable(cell);
+	}));
+};
+
+/**
+ * Function: isCellDeletable
+ *
+ * Returns true if the given cell is moveable. This returns
+ * <cellsDeletable> for all given cells if a cells style does not specify
+ * <mxConstants.STYLE_DELETABLE> to be 0.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose deletable state should be returned.
+ */
+mxGraph.prototype.isCellDeletable = function(cell)
+{
+	var state = this.view.getState(cell);
+	var style = (state != null) ? state.style : this.getCellStyle(cell);
+	
+	return this.isCellsDeletable() && style[mxConstants.STYLE_DELETABLE] != 0;
+};
+
+/**
+ * Function: isCellsDeletable
+ *
+ * Returns <cellsDeletable>.
+ */
+mxGraph.prototype.isCellsDeletable = function()
+{
+	return this.cellsDeletable;
+};
+
+/**
+ * Function: setCellsDeletable
+ * 
+ * Sets <cellsDeletable>.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if the graph should allow deletion of cells.
+ */
+mxGraph.prototype.setCellsDeletable = function(value)
+{
+	this.cellsDeletable = value;
+};
+
+/**
+ * Function: isLabelMovable
+ *
+ * Returns true if the given edges's label is moveable. This returns
+ * <movable> for all given cells if <isLocked> does not return true
+ * for the given cell.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose label should be moved.
+ */
+mxGraph.prototype.isLabelMovable = function(cell)
+{
+	return !this.isCellLocked(cell) &&
+		((this.model.isEdge(cell) && this.edgeLabelsMovable) ||
+		(this.model.isVertex(cell) && this.vertexLabelsMovable));
+};
+
+/**
+ * Function: isCellRotatable
+ *
+ * Returns true if the given cell is rotatable. This returns true for the given
+ * cell if its style does not specify <mxConstants.STYLE_ROTATABLE> to be 0.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose rotatable state should be returned.
+ */
+mxGraph.prototype.isCellRotatable = function(cell)
+{
+	var state = this.view.getState(cell);
+	var style = (state != null) ? state.style : this.getCellStyle(cell);
+	
+	return style[mxConstants.STYLE_ROTATABLE] != 0;
+};
+
+/**
+ * Function: getMovableCells
+ * 
+ * Returns the cells which are movable in the given array of cells.
+ */
+mxGraph.prototype.getMovableCells = function(cells)
+{
+	return this.model.filterCells(cells, mxUtils.bind(this, function(cell)
+	{
+		return this.isCellMovable(cell);
+	}));
+};
+
+/**
+ * Function: isCellMovable
+ *
+ * Returns true if the given cell is moveable. This returns <cellsMovable>
+ * for all given cells if <isCellLocked> does not return true for the given
+ * cell and its style does not specify <mxConstants.STYLE_MOVABLE> to be 0.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose movable state should be returned.
+ */
+mxGraph.prototype.isCellMovable = function(cell)
+{
+	var state = this.view.getState(cell);
+	var style = (state != null) ? state.style : this.getCellStyle(cell);
+	
+	return this.isCellsMovable() && !this.isCellLocked(cell) && style[mxConstants.STYLE_MOVABLE] != 0;
+};
+
+/**
+ * Function: isCellsMovable
+ *
+ * Returns <cellsMovable>.
+ */
+mxGraph.prototype.isCellsMovable = function()
+{
+	return this.cellsMovable;
+};
+
+/**
+ * Function: setCellsMovable
+ * 
+ * Specifies if the graph should allow moving of cells. This implementation
+ * updates <cellsMsovable>.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if the graph should allow moving of cells.
+ */
+mxGraph.prototype.setCellsMovable = function(value)
+{
+	this.cellsMovable = value;
+};
+
+/**
+ * Function: isGridEnabled
+ *
+ * Returns <gridEnabled> as a boolean.
+ */
+mxGraph.prototype.isGridEnabled = function()
+{
+	return this.gridEnabled;
+};
+
+/**
+ * Function: setGridEnabled
+ * 
+ * Specifies if the grid should be enabled.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if the grid should be enabled.
+ */
+mxGraph.prototype.setGridEnabled = function(value)
+{
+	this.gridEnabled = value;
+};
+
+/**
+ * Function: isPortsEnabled
+ *
+ * Returns <portsEnabled> as a boolean.
+ */
+mxGraph.prototype.isPortsEnabled = function()
+{
+	return this.portsEnabled;
+};
+
+/**
+ * Function: setPortsEnabled
+ * 
+ * Specifies if the ports should be enabled.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if the ports should be enabled.
+ */
+mxGraph.prototype.setPortsEnabled = function(value)
+{
+	this.portsEnabled = value;
+};
+
+/**
+ * Function: getGridSize
+ *
+ * Returns <gridSize>.
+ */
+mxGraph.prototype.getGridSize = function()
+{
+	return this.gridSize;
+};
+
+/**
+ * Function: setGridSize
+ * 
+ * Sets <gridSize>.
+ */
+mxGraph.prototype.setGridSize = function(value)
+{
+	this.gridSize = value;
+};
+
+/**
+ * Function: getTolerance
+ *
+ * Returns <tolerance>.
+ */
+mxGraph.prototype.getTolerance = function()
+{
+	return this.tolerance;
+};
+
+/**
+ * Function: setTolerance
+ * 
+ * Sets <tolerance>.
+ */
+mxGraph.prototype.setTolerance = function(value)
+{
+	this.tolerance = value;
+};
+
+/**
+ * Function: isVertexLabelsMovable
+ *
+ * Returns <vertexLabelsMovable>.
+ */
+mxGraph.prototype.isVertexLabelsMovable = function()
+{
+	return this.vertexLabelsMovable;
+};
+
+/**
+ * Function: setVertexLabelsMovable
+ * 
+ * Sets <vertexLabelsMovable>.
+ */
+mxGraph.prototype.setVertexLabelsMovable = function(value)
+{
+	this.vertexLabelsMovable = value;
+};
+
+/**
+ * Function: isEdgeLabelsMovable
+ *
+ * Returns <edgeLabelsMovable>.
+ */
+mxGraph.prototype.isEdgeLabelsMovable = function()
+{
+	return this.edgeLabelsMovable;
+};
+
+/**
+ * Function: isEdgeLabelsMovable
+ * 
+ * Sets <edgeLabelsMovable>.
+ */
+mxGraph.prototype.setEdgeLabelsMovable = function(value)
+{
+	this.edgeLabelsMovable = value;
+};
+
+/**
+ * Function: isSwimlaneNesting
+ *
+ * Returns <swimlaneNesting> as a boolean.
+ */
+mxGraph.prototype.isSwimlaneNesting = function()
+{
+	return this.swimlaneNesting;
+};
+
+/**
+ * Function: setSwimlaneNesting
+ * 
+ * Specifies if swimlanes can be nested by drag and drop. This is only
+ * taken into account if dropEnabled is true.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if swimlanes can be nested.
+ */
+mxGraph.prototype.setSwimlaneNesting = function(value)
+{
+	this.swimlaneNesting = value;
+};
+
+/**
+ * Function: isSwimlaneSelectionEnabled
+ *
+ * Returns <swimlaneSelectionEnabled> as a boolean.
+ */
+mxGraph.prototype.isSwimlaneSelectionEnabled = function()
+{
+	return this.swimlaneSelectionEnabled;
+};
+
+/**
+ * Function: setSwimlaneSelectionEnabled
+ * 
+ * Specifies if swimlanes should be selected if the mouse is released
+ * over their content area.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if swimlanes content areas
+ * should be selected when the mouse is released over them.
+ */
+mxGraph.prototype.setSwimlaneSelectionEnabled = function(value)
+{
+	this.swimlaneSelectionEnabled = value;
+};
+
+/**
+ * Function: isMultigraph
+ *
+ * Returns <multigraph> as a boolean.
+ */
+mxGraph.prototype.isMultigraph = function()
+{
+	return this.multigraph;
+};
+
+/**
+ * Function: setMultigraph
+ * 
+ * Specifies if the graph should allow multiple connections between the
+ * same pair of vertices.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if the graph allows multiple connections
+ * between the same pair of vertices.
+ */
+mxGraph.prototype.setMultigraph = function(value)
+{
+	this.multigraph = value;
+};
+
+/**
+ * Function: isAllowLoops
+ *
+ * Returns <allowLoops> as a boolean.
+ */
+mxGraph.prototype.isAllowLoops = function()
+{
+	return this.allowLoops;
+};
+
+/**
+ * Function: setAllowDanglingEdges
+ * 
+ * Specifies if dangling edges are allowed, that is, if edges are allowed
+ * that do not have a source and/or target terminal defined.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if dangling edges are allowed.
+ */
+mxGraph.prototype.setAllowDanglingEdges = function(value)
+{
+	this.allowDanglingEdges = value;
+};
+
+/**
+ * Function: isAllowDanglingEdges
+ *
+ * Returns <allowDanglingEdges> as a boolean.
+ */
+mxGraph.prototype.isAllowDanglingEdges = function()
+{
+	return this.allowDanglingEdges;
+};
+
+/**
+ * Function: setConnectableEdges
+ * 
+ * Specifies if edges should be connectable.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if edges should be connectable.
+ */
+mxGraph.prototype.setConnectableEdges = function(value)
+{
+	this.connectableEdges = value;
+};
+
+/**
+ * Function: isConnectableEdges
+ *
+ * Returns <connectableEdges> as a boolean.
+ */
+mxGraph.prototype.isConnectableEdges = function()
+{
+	return this.connectableEdges;
+};
+
+/**
+ * Function: setCloneInvalidEdges
+ * 
+ * Specifies if edges should be inserted when cloned but not valid wrt.
+ * <getEdgeValidationError>. If false such edges will be silently ignored.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if cloned invalid edges should be
+ * inserted into the graph or ignored.
+ */
+mxGraph.prototype.setCloneInvalidEdges = function(value)
+{
+	this.cloneInvalidEdges = value;
+};
+
+/**
+ * Function: isCloneInvalidEdges
+ *
+ * Returns <cloneInvalidEdges> as a boolean.
+ */
+mxGraph.prototype.isCloneInvalidEdges = function()
+{
+	return this.cloneInvalidEdges;
+};
+
+/**
+ * Function: setAllowLoops
+ * 
+ * Specifies if loops are allowed.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if loops are allowed.
+ */
+mxGraph.prototype.setAllowLoops = function(value)
+{
+	this.allowLoops = value;
+};
+
+/**
+ * Function: isDisconnectOnMove
+ *
+ * Returns <disconnectOnMove> as a boolean.
+ */
+mxGraph.prototype.isDisconnectOnMove = function()
+{
+	return this.disconnectOnMove;
+};
+
+/**
+ * Function: setDisconnectOnMove
+ * 
+ * Specifies if edges should be disconnected when moved. (Note: Cloned
+ * edges are always disconnected.)
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if edges should be disconnected
+ * when moved.
+ */
+mxGraph.prototype.setDisconnectOnMove = function(value)
+{
+	this.disconnectOnMove = value;
+};
+
+/**
+ * Function: isDropEnabled
+ *
+ * Returns <dropEnabled> as a boolean.
+ */
+mxGraph.prototype.isDropEnabled = function()
+{
+	return this.dropEnabled;
+};
+
+/**
+ * Function: setDropEnabled
+ * 
+ * Specifies if the graph should allow dropping of cells onto or into other
+ * cells.
+ * 
+ * Parameters:
+ * 
+ * dropEnabled - Boolean indicating if the graph should allow dropping
+ * of cells into other cells.
+ */
+mxGraph.prototype.setDropEnabled = function(value)
+{
+	this.dropEnabled = value;
+};
+
+/**
+ * Function: isSplitEnabled
+ *
+ * Returns <splitEnabled> as a boolean.
+ */
+mxGraph.prototype.isSplitEnabled = function()
+{
+	return this.splitEnabled;
+};
+
+/**
+ * Function: setSplitEnabled
+ * 
+ * Specifies if the graph should allow dropping of cells onto or into other
+ * cells.
+ * 
+ * Parameters:
+ * 
+ * dropEnabled - Boolean indicating if the graph should allow dropping
+ * of cells into other cells.
+ */
+mxGraph.prototype.setSplitEnabled = function(value)
+{
+	this.splitEnabled = value;
+};
+
+/**
+ * Function: isCellResizable
+ *
+ * Returns true if the given cell is resizable. This returns
+ * <cellsResizable> for all given cells if <isCellLocked> does not return
+ * true for the given cell and its style does not specify
+ * <mxConstants.STYLE_RESIZABLE> to be 0.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose resizable state should be returned.
+ */
+mxGraph.prototype.isCellResizable = function(cell)
+{
+	var state = this.view.getState(cell);
+	var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+	return this.isCellsResizable() && !this.isCellLocked(cell) &&
+		mxUtils.getValue(style, mxConstants.STYLE_RESIZABLE, '1') != '0';
+};
+
+/**
+ * Function: isCellsResizable
+ *
+ * Returns <cellsResizable>.
+ */
+mxGraph.prototype.isCellsResizable = function()
+{
+	return this.cellsResizable;
+};
+
+/**
+ * Function: setCellsResizable
+ * 
+ * Specifies if the graph should allow resizing of cells. This
+ * implementation updates <cellsResizable>.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if the graph should allow resizing of
+ * cells.
+ */
+mxGraph.prototype.setCellsResizable = function(value)
+{
+	this.cellsResizable = value;
+};
+
+/**
+ * Function: isTerminalPointMovable
+ *
+ * Returns true if the given terminal point is movable. This is independent
+ * from <isCellConnectable> and <isCellDisconnectable> and controls if terminal
+ * points can be moved in the graph if the edge is not connected. Note that it
+ * is required for this to return true to connect unconnected edges. This
+ * implementation returns true.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose terminal point should be moved.
+ * source - Boolean indicating if the source or target terminal should be moved.
+ */
+mxGraph.prototype.isTerminalPointMovable = function(cell, source)
+{
+	return true;
+};
+
+/**
+ * Function: isCellBendable
+ *
+ * Returns true if the given cell is bendable. This returns <cellsBendable>
+ * for all given cells if <isLocked> does not return true for the given
+ * cell and its style does not specify <mxConstants.STYLE_BENDABLE> to be 0.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose bendable state should be returned.
+ */
+mxGraph.prototype.isCellBendable = function(cell)
+{
+	var state = this.view.getState(cell);
+	var style = (state != null) ? state.style : this.getCellStyle(cell);
+	
+	return this.isCellsBendable() && !this.isCellLocked(cell) && style[mxConstants.STYLE_BENDABLE] != 0;
+};
+
+/**
+ * Function: isCellsBendable
+ *
+ * Returns <cellsBenadable>.
+ */
+mxGraph.prototype.isCellsBendable = function()
+{
+	return this.cellsBendable;
+};
+
+/**
+ * Function: setCellsBendable
+ * 
+ * Specifies if the graph should allow bending of edges. This
+ * implementation updates <bendable>.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if the graph should allow bending of
+ * edges.
+ */
+mxGraph.prototype.setCellsBendable = function(value)
+{
+	this.cellsBendable = value;
+};
+
+/**
+ * Function: isCellEditable
+ *
+ * Returns true if the given cell is editable. This returns <cellsEditable> for
+ * all given cells if <isCellLocked> does not return true for the given cell
+ * and its style does not specify <mxConstants.STYLE_EDITABLE> to be 0.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose editable state should be returned.
+ */
+mxGraph.prototype.isCellEditable = function(cell)
+{
+	var state = this.view.getState(cell);
+	var style = (state != null) ? state.style : this.getCellStyle(cell);
+	
+	return this.isCellsEditable() && !this.isCellLocked(cell) && style[mxConstants.STYLE_EDITABLE] != 0;
+};
+
+/**
+ * Function: isCellsEditable
+ *
+ * Returns <cellsEditable>.
+ */
+mxGraph.prototype.isCellsEditable = function()
+{
+	return this.cellsEditable;
+};
+
+/**
+ * Function: setCellsEditable
+ * 
+ * Specifies if the graph should allow in-place editing for cell labels.
+ * This implementation updates <cellsEditable>.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if the graph should allow in-place
+ * editing.
+ */
+mxGraph.prototype.setCellsEditable = function(value)
+{
+	this.cellsEditable = value;
+};
+
+/**
+ * Function: isCellDisconnectable
+ *
+ * Returns true if the given cell is disconnectable from the source or
+ * target terminal. This returns <isCellsDisconnectable> for all given
+ * cells if <isCellLocked> does not return true for the given cell.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose disconnectable state should be returned.
+ * terminal - <mxCell> that represents the source or target terminal.
+ * source - Boolean indicating if the source or target terminal is to be
+ * disconnected.
+ */
+mxGraph.prototype.isCellDisconnectable = function(cell, terminal, source)
+{
+	return this.isCellsDisconnectable() && !this.isCellLocked(cell);
+};
+
+/**
+ * Function: isCellsDisconnectable
+ *
+ * Returns <cellsDisconnectable>.
+ */
+mxGraph.prototype.isCellsDisconnectable = function()
+{
+	return this.cellsDisconnectable;
+};
+
+/**
+ * Function: setCellsDisconnectable
+ *
+ * Sets <cellsDisconnectable>.
+ */
+mxGraph.prototype.setCellsDisconnectable = function(value)
+{
+	this.cellsDisconnectable = value;
+};
+
+/**
+ * Function: isValidSource
+ * 
+ * Returns true if the given cell is a valid source for new connections.
+ * This implementation returns true for all non-null values and is
+ * called by is called by <isValidConnection>.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents a possible source or null.
+ */
+mxGraph.prototype.isValidSource = function(cell)
+{
+	return (cell == null && this.allowDanglingEdges) ||
+		(cell != null && (!this.model.isEdge(cell) ||
+		this.connectableEdges) && this.isCellConnectable(cell));
+};
+	
+/**
+ * Function: isValidTarget
+ * 
+ * Returns <isValidSource> for the given cell. This is called by
+ * <isValidConnection>.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents a possible target or null.
+ */
+mxGraph.prototype.isValidTarget = function(cell)
+{
+	return this.isValidSource(cell);
+};
+
+/**
+ * Function: isValidConnection
+ * 
+ * Returns true if the given target cell is a valid target for source.
+ * This is a boolean implementation for not allowing connections between
+ * certain pairs of vertices and is called by <getEdgeValidationError>.
+ * This implementation returns true if <isValidSource> returns true for
+ * the source and <isValidTarget> returns true for the target.
+ * 
+ * Parameters:
+ * 
+ * source - <mxCell> that represents the source cell.
+ * target - <mxCell> that represents the target cell.
+ */
+mxGraph.prototype.isValidConnection = function(source, target)
+{
+	return this.isValidSource(source) && this.isValidTarget(target);
+};
+
+/**
+ * Function: setConnectable
+ * 
+ * Specifies if the graph should allow new connections. This implementation
+ * updates <mxConnectionHandler.enabled> in <connectionHandler>.
+ * 
+ * Parameters:
+ * 
+ * connectable - Boolean indicating if new connections should be allowed.
+ */
+mxGraph.prototype.setConnectable = function(connectable)
+{
+	this.connectionHandler.setEnabled(connectable);
+};
+	
+/**
+ * Function: isConnectable
+ * 
+ * Returns true if the <connectionHandler> is enabled.
+ */
+mxGraph.prototype.isConnectable = function(connectable)
+{
+	return this.connectionHandler.isEnabled();
+};
+
+/**
+ * Function: setTooltips
+ * 
+ * Specifies if tooltips should be enabled. This implementation updates
+ * <mxTooltipHandler.enabled> in <tooltipHandler>.
+ * 
+ * Parameters:
+ * 
+ * enabled - Boolean indicating if tooltips should be enabled.
+ */
+mxGraph.prototype.setTooltips = function (enabled)
+{
+	this.tooltipHandler.setEnabled(enabled);
+};
+
+/**
+ * Function: setPanning
+ * 
+ * Specifies if panning should be enabled. This implementation updates
+ * <mxPanningHandler.panningEnabled> in <panningHandler>.
+ * 
+ * Parameters:
+ * 
+ * enabled - Boolean indicating if panning should be enabled.
+ */
+mxGraph.prototype.setPanning = function(enabled)
+{
+	this.panningHandler.panningEnabled = enabled;
+};
+
+/**
+ * Function: isEditing
+ * 
+ * Returns true if the given cell is currently being edited.
+ * If no cell is specified then this returns true if any
+ * cell is currently being edited.
+ *
+ * Parameters:
+ * 
+ * cell - <mxCell> that should be checked.
+ */
+mxGraph.prototype.isEditing = function(cell)
+{
+	if (this.cellEditor != null)
+	{
+		var editingCell = this.cellEditor.getEditingCell();
+		
+		return (cell == null) ? editingCell != null : cell == editingCell;
+	}
+	
+	return false;
+};
+
+/**
+ * Function: isAutoSizeCell
+ * 
+ * Returns true if the size of the given cell should automatically be
+ * updated after a change of the label. This implementation returns
+ * <autoSizeCells> or checks if the cell style does specify
+ * <mxConstants.STYLE_AUTOSIZE> to be 1.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that should be resized.
+ */
+mxGraph.prototype.isAutoSizeCell = function(cell)
+{
+	var state = this.view.getState(cell);
+	var style = (state != null) ? state.style : this.getCellStyle(cell);
+	
+	return this.isAutoSizeCells() || style[mxConstants.STYLE_AUTOSIZE] == 1;
+};
+
+/**
+ * Function: isAutoSizeCells
+ * 
+ * Returns <autoSizeCells>.
+ */
+mxGraph.prototype.isAutoSizeCells = function()
+{
+	return this.autoSizeCells;
+};
+
+/**
+ * Function: setAutoSizeCells
+ * 
+ * Specifies if cell sizes should be automatically updated after a label
+ * change. This implementation sets <autoSizeCells> to the given parameter.
+ * To update the size of cells when the cells are added, set
+ * <autoSizeCellsOnAdd> to true.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean indicating if cells should be resized
+ * automatically.
+ */
+mxGraph.prototype.setAutoSizeCells = function(value)
+{
+	this.autoSizeCells = value;
+};
+
+/**
+ * Function: isExtendParent
+ * 
+ * Returns true if the parent of the given cell should be extended if the
+ * child has been resized so that it overlaps the parent. This
+ * implementation returns <isExtendParents> if the cell is not an edge.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that has been resized.
+ */
+mxGraph.prototype.isExtendParent = function(cell)
+{
+	return !this.getModel().isEdge(cell) && this.isExtendParents();
+};
+
+/**
+ * Function: isExtendParents
+ * 
+ * Returns <extendParents>.
+ */
+mxGraph.prototype.isExtendParents = function()
+{
+	return this.extendParents;
+};
+
+/**
+ * Function: setExtendParents
+ * 
+ * Sets <extendParents>.
+ * 
+ * Parameters:
+ * 
+ * value - New boolean value for <extendParents>.
+ */
+mxGraph.prototype.setExtendParents = function(value)
+{
+	this.extendParents = value;
+};
+
+/**
+ * Function: isExtendParentsOnAdd
+ * 
+ * Returns <extendParentsOnAdd>.
+ */
+mxGraph.prototype.isExtendParentsOnAdd = function(cell)
+{
+	return this.extendParentsOnAdd;
+};
+
+/**
+ * Function: setExtendParentsOnAdd
+ * 
+ * Sets <extendParentsOnAdd>.
+ * 
+ * Parameters:
+ * 
+ * value - New boolean value for <extendParentsOnAdd>.
+ */
+mxGraph.prototype.setExtendParentsOnAdd = function(value)
+{
+	this.extendParentsOnAdd = value;
+};
+
+/**
+ * Function: isExtendParentsOnMove
+ * 
+ * Returns <extendParentsOnMove>.
+ */
+mxGraph.prototype.isExtendParentsOnMove = function()
+{
+	return this.extendParentsOnMove;
+};
+
+/**
+ * Function: setExtendParentsOnMove
+ * 
+ * Sets <extendParentsOnMove>.
+ * 
+ * Parameters:
+ * 
+ * value - New boolean value for <extendParentsOnAdd>.
+ */
+mxGraph.prototype.setExtendParentsOnMove = function(value)
+{
+	this.extendParentsOnMove = value;
+};
+
+/**
+ * Function: isRecursiveResize
+ * 
+ * Returns <recursiveResize>.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> that is being resized.
+ */
+mxGraph.prototype.isRecursiveResize = function(state)
+{
+	return this.recursiveResize;
+};
+
+/**
+ * Function: setRecursiveResize
+ * 
+ * Sets <recursiveResize>.
+ * 
+ * Parameters:
+ * 
+ * value - New boolean value for <recursiveResize>.
+ */
+mxGraph.prototype.setRecursiveResize = function(value)
+{
+	this.recursiveResize = value;
+};
+
+/**
+ * Function: isConstrainChild
+ * 
+ * Returns true if the given cell should be kept inside the bounds of its
+ * parent according to the rules defined by <getOverlap> and
+ * <isAllowOverlapParent>. This implementation returns false for all children
+ * of edges and <isConstrainChildren> otherwise.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that should be constrained.
+ */
+mxGraph.prototype.isConstrainChild = function(cell)
+{
+	return this.isConstrainChildren() && !this.getModel().isEdge(this.getModel().getParent(cell));
+};
+
+/**
+ * Function: isConstrainChildren
+ * 
+ * Returns <constrainChildren>.
+ */
+mxGraph.prototype.isConstrainChildren = function()
+{
+	return this.constrainChildren;
+};
+
+/**
+ * Function: setConstrainChildren
+ * 
+ * Sets <constrainChildren>.
+ */
+mxGraph.prototype.setConstrainChildren = function(value)
+{
+	this.constrainChildren = value;
+};
+
+/**
+ * Function: isConstrainRelativeChildren
+ * 
+ * Returns <constrainRelativeChildren>.
+ */
+mxGraph.prototype.isConstrainRelativeChildren = function()
+{
+	return this.constrainRelativeChildren;
+};
+
+/**
+ * Function: setConstrainRelativeChildren
+ * 
+ * Sets <constrainRelativeChildren>.
+ */
+mxGraph.prototype.setConstrainRelativeChildren = function(value)
+{
+	this.constrainRelativeChildren = value;
+};
+
+/**
+ * Function: isConstrainChildren
+ * 
+ * Returns <allowNegativeCoordinates>.
+ */
+mxGraph.prototype.isAllowNegativeCoordinates = function()
+{
+	return this.allowNegativeCoordinates;
+};
+
+/**
+ * Function: setConstrainChildren
+ * 
+ * Sets <allowNegativeCoordinates>.
+ */
+mxGraph.prototype.setAllowNegativeCoordinates = function(value)
+{
+	this.allowNegativeCoordinates = value;
+};
+
+/**
+ * Function: getOverlap
+ * 
+ * Returns a decimal number representing the amount of the width and height
+ * of the given cell that is allowed to overlap its parent. A value of 0
+ * means all children must stay inside the parent, 1 means the child is
+ * allowed to be placed outside of the parent such that it touches one of
+ * the parents sides. If <isAllowOverlapParent> returns false for the given
+ * cell, then this method returns 0.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> for which the overlap ratio should be returned.
+ */
+mxGraph.prototype.getOverlap = function(cell)
+{
+	return (this.isAllowOverlapParent(cell)) ? this.defaultOverlap : 0;
+};
+	
+/**
+ * Function: isAllowOverlapParent
+ * 
+ * Returns true if the given cell is allowed to be placed outside of the
+ * parents area.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the child to be checked.
+ */
+mxGraph.prototype.isAllowOverlapParent = function(cell)
+{
+	return false;
+};
+
+/**
+ * Function: getFoldableCells
+ * 
+ * Returns the cells which are movable in the given array of cells.
+ */
+mxGraph.prototype.getFoldableCells = function(cells, collapse)
+{
+	return this.model.filterCells(cells, mxUtils.bind(this, function(cell)
+	{
+		return this.isCellFoldable(cell, collapse);
+	}));
+};
+
+/**
+ * Function: isCellFoldable
+ * 
+ * Returns true if the given cell is foldable. This implementation
+ * returns true if the cell has at least one child and its style
+ * does not specify <mxConstants.STYLE_FOLDABLE> to be 0.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose foldable state should be returned.
+ */
+mxGraph.prototype.isCellFoldable = function(cell, collapse)
+{
+	var state = this.view.getState(cell);
+	var style = (state != null) ? state.style : this.getCellStyle(cell);
+	
+	return this.model.getChildCount(cell) > 0 && style[mxConstants.STYLE_FOLDABLE] != 0;
+};
+
+/**
+ * Function: isValidDropTarget
+ *
+ * Returns true if the given cell is a valid drop target for the specified
+ * cells. If <splitEnabled> is true then this returns <isSplitTarget> for
+ * the given arguments else it returns true if the cell is not collapsed
+ * and its child count is greater than 0.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> that represents the possible drop target.
+ * cells - <mxCells> that should be dropped into the target.
+ * evt - Mouseevent that triggered the invocation.
+ */
+mxGraph.prototype.isValidDropTarget = function(cell, cells, evt)
+{
+	return cell != null && ((this.isSplitEnabled() &&
+		this.isSplitTarget(cell, cells, evt)) || (!this.model.isEdge(cell) &&
+		(this.isSwimlane(cell) || (this.model.getChildCount(cell) > 0 &&
+		!this.isCellCollapsed(cell)))));
+};
+
+/**
+ * Function: isSplitTarget
+ *
+ * Returns true if the given edge may be splitted into two edges with the
+ * given cell as a new terminal between the two.
+ * 
+ * Parameters:
+ * 
+ * target - <mxCell> that represents the edge to be splitted.
+ * cells - <mxCells> that should split the edge.
+ * evt - Mouseevent that triggered the invocation.
+ */
+mxGraph.prototype.isSplitTarget = function(target, cells, evt)
+{
+	if (this.model.isEdge(target) && cells != null && cells.length == 1 &&
+		this.isCellConnectable(cells[0]) && this.getEdgeValidationError(target,
+			this.model.getTerminal(target, true), cells[0]) == null)
+	{
+		var src = this.model.getTerminal(target, true);
+		var trg = this.model.getTerminal(target, false);
+
+		return (!this.model.isAncestor(cells[0], src) &&
+				!this.model.isAncestor(cells[0], trg));
+	}
+
+	return false;
+};
+
+/**
+ * Function: getDropTarget
+ * 
+ * Returns the given cell if it is a drop target for the given cells or the
+ * nearest ancestor that may be used as a drop target for the given cells.
+ * If the given array contains a swimlane and <swimlaneNesting> is false
+ * then this always returns null. If no cell is given, then the bottommost
+ * swimlane at the location of the given event is returned.
+ * 
+ * This function should only be used if <isDropEnabled> returns true.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> which are to be dropped onto the target.
+ * evt - Mouseevent for the drag and drop.
+ * cell - <mxCell> that is under the mousepointer.
+ * clone - Optional boolean to indicate of cells will be cloned.
+ */
+mxGraph.prototype.getDropTarget = function(cells, evt, cell, clone)
+{
+	if (!this.isSwimlaneNesting())
+	{
+		for (var i = 0; i < cells.length; i++)
+		{
+			if (this.isSwimlane(cells[i]))
+			{
+				return null;
+			}
+		}
+	}
+
+	var pt = mxUtils.convertPoint(this.container,
+		mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+	pt.x -= this.panDx;
+	pt.y -= this.panDy;
+	var swimlane = this.getSwimlaneAt(pt.x, pt.y);
+	
+	if (cell == null)
+	{
+		cell = swimlane;
+	}
+	else if (swimlane != null)
+	{
+		// Checks if the cell is an ancestor of the swimlane
+		// under the mouse and uses the swimlane in that case
+		var tmp = this.model.getParent(swimlane);
+		
+		while (tmp != null && this.isSwimlane(tmp) && tmp != cell)
+		{
+			tmp = this.model.getParent(tmp);
+		}
+		
+		if (tmp == cell)
+		{
+			cell = swimlane;
+		}
+	}
+	
+	while (cell != null && !this.isValidDropTarget(cell, cells, evt) &&
+		!this.model.isLayer(cell))
+	{
+		cell = this.model.getParent(cell);
+	}
+	
+	// Checks if parent is dropped into child if not cloning
+	if (clone == null || !clone)
+	{
+		var parent = cell;
+		
+		while (parent != null && mxUtils.indexOf(cells, parent) < 0)
+		{
+			parent = this.model.getParent(parent);
+		}
+	}
+
+	return (!this.model.isLayer(cell) && parent == null) ? cell : null;
+};
+
+/**
+ * Group: Cell retrieval
+ */
+
+/**
+ * Function: getDefaultParent
+ * 
+ * Returns <defaultParent> or <mxGraphView.currentRoot> or the first child
+ * child of <mxGraphModel.root> if both are null. The value returned by
+ * this function should be used as the parent for new cells (aka default
+ * layer).
+ */
+mxGraph.prototype.getDefaultParent = function()
+{
+	var parent = this.getCurrentRoot();
+	
+	if (parent == null)
+	{
+		parent = this.defaultParent;
+		
+		if (parent == null)
+		{
+			var root = this.model.getRoot();
+			parent = this.model.getChildAt(root, 0);
+		}
+	}
+	
+	return parent;
+};
+
+/**
+ * Function: setDefaultParent
+ * 
+ * Sets the <defaultParent> to the given cell. Set this to null to return
+ * the first child of the root in getDefaultParent.
+ */
+mxGraph.prototype.setDefaultParent = function(cell)
+{
+	this.defaultParent = cell;
+};
+
+/**
+ * Function: getSwimlane
+ * 
+ * Returns the nearest ancestor of the given cell which is a swimlane, or
+ * the given cell, if it is itself a swimlane.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> for which the ancestor swimlane should be returned.
+ */
+mxGraph.prototype.getSwimlane = function(cell)
+{
+	while (cell != null && !this.isSwimlane(cell))
+	{
+		cell = this.model.getParent(cell);
+	}
+	
+	return cell;
+};
+
+/**
+ * Function: getSwimlaneAt
+ * 
+ * Returns the bottom-most swimlane that intersects the given point (x, y)
+ * in the cell hierarchy that starts at the given parent.
+ * 
+ * Parameters:
+ * 
+ * x - X-coordinate of the location to be checked.
+ * y - Y-coordinate of the location to be checked.
+ * parent - <mxCell> that should be used as the root of the recursion.
+ * Default is <defaultParent>.
+ */
+mxGraph.prototype.getSwimlaneAt = function (x, y, parent)
+{
+	parent = parent || this.getDefaultParent();
+	
+	if (parent != null)
+	{
+		var childCount = this.model.getChildCount(parent);
+		
+		for (var i = 0; i < childCount; i++)
+		{
+			var child = this.model.getChildAt(parent, i);
+			var result = this.getSwimlaneAt(x, y, child);
+			
+			if (result != null)
+			{
+				return result;
+			}
+			else if (this.isSwimlane(child))
+			{
+				var state = this.view.getState(child);
+				
+				if (this.intersects(state, x, y))
+				{
+					return child;
+				}
+			}
+		}
+	}
+	
+	return null;
+};
+
+/**
+ * Function: getCellAt
+ * 
+ * Returns the bottom-most cell that intersects the given point (x, y) in
+ * the cell hierarchy starting at the given parent. This will also return
+ * swimlanes if the given location intersects the content area of the
+ * swimlane. If this is not desired, then the <hitsSwimlaneContent> may be
+ * used if the returned cell is a swimlane to determine if the location
+ * is inside the content area or on the actual title of the swimlane.
+ * 
+ * Parameters:
+ * 
+ * x - X-coordinate of the location to be checked.
+ * y - Y-coordinate of the location to be checked.
+ * parent - <mxCell> that should be used as the root of the recursion.
+ * Default is current root of the view or the root of the model.
+ * vertices - Optional boolean indicating if vertices should be returned.
+ * Default is true.
+ * edges - Optional boolean indicating if edges should be returned. Default
+ * is true.
+ * ignoreFn - Optional function that returns true if cell should be ignored.
+ * The function is passed the cell state and the x and y parameter.
+ */
+mxGraph.prototype.getCellAt = function(x, y, parent, vertices, edges, ignoreFn)
+{
+	vertices = (vertices != null) ? vertices : true;
+	edges = (edges != null) ? edges : true;
+
+	if (parent == null)
+	{
+		parent = this.getCurrentRoot();
+		
+		if (parent == null)
+		{
+			parent = this.getModel().getRoot();
+		}
+	}
+
+	if (parent != null)
+	{
+		var childCount = this.model.getChildCount(parent);
+		
+		for (var i = childCount - 1; i >= 0; i--)
+		{
+			var cell = this.model.getChildAt(parent, i);
+			var result = this.getCellAt(x, y, cell, vertices, edges, ignoreFn);
+			
+			if (result != null)
+			{
+				return result;
+			}
+			else if (this.isCellVisible(cell) && (edges && this.model.isEdge(cell) ||
+				vertices && this.model.isVertex(cell)))
+			{
+				var state = this.view.getState(cell);
+
+				if (state != null && (ignoreFn == null || !ignoreFn(state, x, y)) &&
+					this.intersects(state, x, y))
+				{
+					return cell;
+				}
+			}
+		}
+	}
+	
+	return null;
+};
+
+/**
+ * Function: intersects
+ * 
+ * Returns the bottom-most cell that intersects the given point (x, y) in
+ * the cell hierarchy that starts at the given parent.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> that represents the cell state.
+ * x - X-coordinate of the location to be checked.
+ * y - Y-coordinate of the location to be checked.
+ */
+mxGraph.prototype.intersects = function(state, x, y)
+{
+	if (state != null)
+	{
+		var pts = state.absolutePoints;
+
+		if (pts != null)
+		{
+			var t2 = this.tolerance * this.tolerance;
+			var pt = pts[0];
+			
+			for (var i = 1; i < pts.length; i++)
+			{
+				var next = pts[i];
+				var dist = mxUtils.ptSegDistSq(pt.x, pt.y, next.x, next.y, x, y);
+				
+				if (dist <= t2)
+				{
+					return true;
+				}
+				
+				pt = next;
+			}
+		}
+		else
+		{
+			var alpha = mxUtils.toRadians(mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION) || 0);
+			
+			if (alpha != 0)
+			{
+				var cos = Math.cos(-alpha);
+				var sin = Math.sin(-alpha);
+				var cx = new mxPoint(state.getCenterX(), state.getCenterY());
+				var pt = mxUtils.getRotatedPoint(new mxPoint(x, y), cos, sin, cx);
+				x = pt.x;
+				y = pt.y;
+			}
+			
+			if (mxUtils.contains(state, x, y))
+			{
+				return true;
+			}
+		}
+	}
+	
+	return false;
+};
+
+/**
+ * Function: hitsSwimlaneContent
+ * 
+ * Returns true if the given coordinate pair is inside the content
+ * are of the given swimlane.
+ * 
+ * Parameters:
+ * 
+ * swimlane - <mxCell> that specifies the swimlane.
+ * x - X-coordinate of the mouse event.
+ * y - Y-coordinate of the mouse event.
+ */
+mxGraph.prototype.hitsSwimlaneContent = function(swimlane, x, y)
+{
+	var state = this.getView().getState(swimlane);
+	var size = this.getStartSize(swimlane);
+	
+	if (state != null)
+	{
+		var scale = this.getView().getScale();
+		x -= state.x;
+		y -= state.y;
+		
+		if (size.width > 0 && x > 0 && x > size.width * scale)
+		{
+			return true;
+		}
+		else if (size.height > 0 && y > 0 && y > size.height * scale)
+		{
+			return true;
+		}
+	}
+	
+	return false;
+};
+
+/**
+ * Function: getChildVertices
+ * 
+ * Returns the visible child vertices of the given parent.
+ * 
+ * Parameters:
+ * 
+ * parent - <mxCell> whose children should be returned.
+ */
+mxGraph.prototype.getChildVertices = function(parent)
+{
+	return this.getChildCells(parent, true, false);
+};
+	
+/**
+ * Function: getChildEdges
+ * 
+ * Returns the visible child edges of the given parent.
+ * 
+ * Parameters:
+ * 
+ * parent - <mxCell> whose child vertices should be returned.
+ */
+mxGraph.prototype.getChildEdges = function(parent)
+{
+	return this.getChildCells(parent, false, true);
+};
+
+/**
+ * Function: getChildCells
+ * 
+ * Returns the visible child vertices or edges in the given parent. If
+ * vertices and edges is false, then all children are returned.
+ * 
+ * Parameters:
+ * 
+ * parent - <mxCell> whose children should be returned.
+ * vertices - Optional boolean that specifies if child vertices should
+ * be returned. Default is false.
+ * edges - Optional boolean that specifies if child edges should
+ * be returned. Default is false.
+ */
+mxGraph.prototype.getChildCells = function(parent, vertices, edges)
+{
+	parent = (parent != null) ? parent : this.getDefaultParent();
+	vertices = (vertices != null) ? vertices : false;
+	edges = (edges != null) ? edges : false;
+
+	var cells = this.model.getChildCells(parent, vertices, edges);
+	var result = [];
+
+	// Filters out the non-visible child cells
+	for (var i = 0; i < cells.length; i++)
+	{
+		if (this.isCellVisible(cells[i]))
+		{
+			result.push(cells[i]);
+		}
+	}
+
+	return result;
+};
+	
+/**
+ * Function: getConnections
+ * 
+ * Returns all visible edges connected to the given cell without loops.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose connections should be returned.
+ * parent - Optional parent of the opposite end for a connection to be
+ * returned.
+ */
+mxGraph.prototype.getConnections = function(cell, parent)
+{
+	return this.getEdges(cell, parent, true, true, false);
+};
+	
+/**
+ * Function: getIncomingEdges
+ * 
+ * Returns the visible incoming edges for the given cell. If the optional
+ * parent argument is specified, then only child edges of the given parent
+ * are returned.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose incoming edges should be returned.
+ * parent - Optional parent of the opposite end for an edge to be
+ * returned.
+ */
+mxGraph.prototype.getIncomingEdges = function(cell, parent)
+{
+	return this.getEdges(cell, parent, true, false, false);
+};
+	
+/**
+ * Function: getOutgoingEdges
+ * 
+ * Returns the visible outgoing edges for the given cell. If the optional
+ * parent argument is specified, then only child edges of the given parent
+ * are returned.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose outgoing edges should be returned.
+ * parent - Optional parent of the opposite end for an edge to be
+ * returned.
+ */
+mxGraph.prototype.getOutgoingEdges = function(cell, parent)
+{
+	return this.getEdges(cell, parent, false, true, false);
+};
+	
+/**
+ * Function: getEdges
+ * 
+ * Returns the incoming and/or outgoing edges for the given cell.
+ * If the optional parent argument is specified, then only edges are returned
+ * where the opposite is in the given parent cell. If at least one of incoming
+ * or outgoing is true, then loops are ignored, if both are false, then all
+ * edges connected to the given cell are returned including loops.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose edges should be returned.
+ * parent - Optional parent of the opposite end for an edge to be
+ * returned.
+ * incoming - Optional boolean that specifies if incoming edges should
+ * be included in the result. Default is true.
+ * outgoing - Optional boolean that specifies if outgoing edges should
+ * be included in the result. Default is true.
+ * includeLoops - Optional boolean that specifies if loops should be
+ * included in the result. Default is true.
+ * recurse - Optional boolean the specifies if the parent specified only 
+ * need be an ancestral parent, true, or the direct parent, false.
+ * Default is false
+ */
+mxGraph.prototype.getEdges = function(cell, parent, incoming, outgoing, includeLoops, recurse)
+{
+	incoming = (incoming != null) ? incoming : true;
+	outgoing = (outgoing != null) ? outgoing : true;
+	includeLoops = (includeLoops != null) ? includeLoops : true;
+	recurse = (recurse != null) ? recurse : false;
+	
+	var edges = [];
+	var isCollapsed = this.isCellCollapsed(cell);
+	var childCount = this.model.getChildCount(cell);
+
+	for (var i = 0; i < childCount; i++)
+	{
+		var child = this.model.getChildAt(cell, i);
+
+		if (isCollapsed || !this.isCellVisible(child))
+		{
+			edges = edges.concat(this.model.getEdges(child, incoming, outgoing));
+		}
+	}
+
+	edges = edges.concat(this.model.getEdges(cell, incoming, outgoing));
+	var result = [];
+	
+	for (var i = 0; i < edges.length; i++)
+	{
+		var state = this.view.getState(edges[i]);
+		
+		var source = (state != null) ? state.getVisibleTerminal(true) : this.view.getVisibleTerminal(edges[i], true);
+		var target = (state != null) ? state.getVisibleTerminal(false) : this.view.getVisibleTerminal(edges[i], false);
+
+		if ((includeLoops && source == target) || ((source != target) && ((incoming &&
+			target == cell && (parent == null || this.isValidAncestor(source, parent, recurse))) ||
+			(outgoing && source == cell && (parent == null ||
+					this.isValidAncestor(target, parent, recurse))))))
+		{
+			result.push(edges[i]);
+		}
+	}
+
+	return result;
+};
+
+/**
+ * Function: isValidAncestor
+ * 
+ * Returns whether or not the specified parent is a valid
+ * ancestor of the specified cell, either direct or indirectly
+ * based on whether ancestor recursion is enabled.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> the possible child cell
+ * parent - <mxCell> the possible parent cell
+ * recurse - boolean whether or not to recurse the child ancestors
+ */
+mxGraph.prototype.isValidAncestor = function(cell, parent, recurse)
+{
+	return (recurse ? this.model.isAncestor(parent, cell) : this.model
+			.getParent(cell) == parent);
+};
+
+/**
+ * Function: getOpposites
+ * 
+ * Returns all distinct visible opposite cells for the specified terminal
+ * on the given edges.
+ * 
+ * Parameters:
+ * 
+ * edges - Array of <mxCells> that contains the edges whose opposite
+ * terminals should be returned.
+ * terminal - Terminal that specifies the end whose opposite should be
+ * returned.
+ * source - Optional boolean that specifies if source terminals should be
+ * included in the result. Default is true.
+ * targets - Optional boolean that specifies if targer terminals should be
+ * included in the result. Default is true.
+ */
+mxGraph.prototype.getOpposites = function(edges, terminal, sources, targets)
+{
+	sources = (sources != null) ? sources : true;
+	targets = (targets != null) ? targets : true;
+	
+	var terminals = [];
+	
+	// Fast lookup to avoid duplicates in terminals array
+	var dict = new mxDictionary();
+	
+	if (edges != null)
+	{
+		for (var i = 0; i < edges.length; i++)
+		{
+			var state = this.view.getState(edges[i]);
+			
+			var source = (state != null) ? state.getVisibleTerminal(true) : this.view.getVisibleTerminal(edges[i], true);
+			var target = (state != null) ? state.getVisibleTerminal(false) : this.view.getVisibleTerminal(edges[i], false);
+			
+			// Checks if the terminal is the source of the edge and if the
+			// target should be stored in the result
+			if (source == terminal && target != null && target != terminal && targets)
+			{
+				if (!dict.get(target))
+				{
+					dict.put(target, true);
+					terminals.push(target);
+				}
+			}
+			
+			// Checks if the terminal is the taget of the edge and if the
+			// source should be stored in the result
+			else if (target == terminal && source != null && source != terminal && sources)
+			{
+				if (!dict.get(source))
+				{
+					dict.put(source, true);
+					terminals.push(source);
+				}
+			}
+		}
+	}
+	
+	return terminals;
+};
+
+/**
+ * Function: getEdgesBetween
+ * 
+ * Returns the edges between the given source and target. This takes into
+ * account collapsed and invisible cells and returns the connected edges
+ * as displayed on the screen.
+ * 
+ * Parameters:
+ * 
+ * source -
+ * target -
+ * directed -
+ */
+mxGraph.prototype.getEdgesBetween = function(source, target, directed)
+{
+	directed = (directed != null) ? directed : false;
+	var edges = this.getEdges(source);
+	var result = [];
+
+	// Checks if the edge is connected to the correct
+	// cell and returns the first match
+	for (var i = 0; i < edges.length; i++)
+	{
+		var state = this.view.getState(edges[i]);
+		
+		var src = (state != null) ? state.getVisibleTerminal(true) : this.view.getVisibleTerminal(edges[i], true);
+		var trg = (state != null) ? state.getVisibleTerminal(false) : this.view.getVisibleTerminal(edges[i], false);
+
+		if ((src == source && trg == target) || (!directed && src == target && trg == source))
+		{
+			result.push(edges[i]);
+		}
+	}
+
+	return result;
+};
+
+/**
+ * Function: getPointForEvent
+ * 
+ * Returns an <mxPoint> representing the given event in the unscaled,
+ * non-translated coordinate space of <container> and applies the grid.
+ * 
+ * Parameters:
+ * 
+ * evt - Mousevent that contains the mouse pointer location.
+ * addOffset - Optional boolean that specifies if the position should be
+ * offset by half of the <gridSize>. Default is true.
+ */
+ mxGraph.prototype.getPointForEvent = function(evt, addOffset)
+ {
+	var p = mxUtils.convertPoint(this.container,
+		mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+	
+	var s = this.view.scale;
+	var tr = this.view.translate;
+	var off = (addOffset != false) ? this.gridSize / 2 : 0;
+	
+	p.x = this.snap(p.x / s - tr.x - off);
+	p.y = this.snap(p.y / s - tr.y - off);
+	
+	return p;
+ };
+
+/**
+ * Function: getCells
+ * 
+ * Returns the child vertices and edges of the given parent that are contained
+ * in the given rectangle. The result is added to the optional result array,
+ * which is returned. If no result array is specified then a new array is
+ * created and returned.
+ * 
+ * Parameters:
+ * 
+ * x - X-coordinate of the rectangle.
+ * y - Y-coordinate of the rectangle.
+ * width - Width of the rectangle.
+ * height - Height of the rectangle.
+ * parent - <mxCell> that should be used as the root of the recursion.
+ * Default is current root of the view or the root of the model.
+ * result - Optional array to store the result in.
+ */
+mxGraph.prototype.getCells = function(x, y, width, height, parent, result)
+{
+	result = (result != null) ? result : [];
+	
+	if (width > 0 || height > 0)
+	{
+		var model = this.getModel();
+		var right = x + width;
+		var bottom = y + height;
+
+		if (parent == null)
+		{
+			parent = this.getCurrentRoot();
+			
+			if (parent == null)
+			{
+				parent = model.getRoot();
+			}
+		}
+		
+		if (parent != null)
+		{
+			var childCount = model.getChildCount(parent);
+			
+			for (var i = 0; i < childCount; i++)
+			{
+				var cell = model.getChildAt(parent, i);
+				var state = this.view.getState(cell);
+				
+				if (state != null && this.isCellVisible(cell))
+				{
+					var deg = mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION) || 0;
+					var box = state;
+					
+					if (deg != 0)
+					{
+						box = mxUtils.getBoundingBox(box, deg);
+					}
+					
+					if ((model.isEdge(cell) || model.isVertex(cell)) &&
+						box.x >= x && box.y + box.height <= bottom &&
+						box.y >= y && box.x + box.width <= right)
+					{
+						result.push(cell);
+					}
+					else
+					{
+						this.getCells(x, y, width, height, cell, result);
+					}
+				}
+			}
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: getCellsBeyond
+ * 
+ * Returns the children of the given parent that are contained in the
+ * halfpane from the given point (x0, y0) rightwards or downwards
+ * depending on rightHalfpane and bottomHalfpane.
+ * 
+ * Parameters:
+ * 
+ * x0 - X-coordinate of the origin.
+ * y0 - Y-coordinate of the origin.
+ * parent - Optional <mxCell> whose children should be checked. Default is
+ * <defaultParent>.
+ * rightHalfpane - Boolean indicating if the cells in the right halfpane
+ * from the origin should be returned.
+ * bottomHalfpane - Boolean indicating if the cells in the bottom halfpane
+ * from the origin should be returned.
+ */
+mxGraph.prototype.getCellsBeyond = function(x0, y0, parent, rightHalfpane, bottomHalfpane)
+{
+	var result = [];
+	
+	if (rightHalfpane || bottomHalfpane)
+	{
+		if (parent == null)
+		{
+			parent = this.getDefaultParent();
+		}
+		
+		if (parent != null)
+		{
+			var childCount = this.model.getChildCount(parent);
+			
+			for (var i = 0; i < childCount; i++)
+			{
+				var child = this.model.getChildAt(parent, i);
+				var state = this.view.getState(child);
+				
+				if (this.isCellVisible(child) && state != null)
+				{
+					if ((!rightHalfpane || state.x >= x0) &&
+						(!bottomHalfpane || state.y >= y0))
+					{
+						result.push(child);
+					}
+				}
+			}
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: findTreeRoots
+ * 
+ * Returns all children in the given parent which do not have incoming
+ * edges. If the result is empty then the with the greatest difference
+ * between incoming and outgoing edges is returned.
+ * 
+ * Parameters:
+ * 
+ * parent - <mxCell> whose children should be checked.
+ * isolate - Optional boolean that specifies if edges should be ignored if
+ * the opposite end is not a child of the given parent cell. Default is
+ * false.
+ * invert - Optional boolean that specifies if outgoing or incoming edges
+ * should be counted for a tree root. If false then outgoing edges will be
+ * counted. Default is false.
+ */
+mxGraph.prototype.findTreeRoots = function(parent, isolate, invert)
+{
+	isolate = (isolate != null) ? isolate : false;
+	invert = (invert != null) ? invert : false;
+	var roots = [];
+	
+	if (parent != null)
+	{
+		var model = this.getModel();
+		var childCount = model.getChildCount(parent);
+		var best = null;
+		var maxDiff = 0;
+		
+		for (var i=0; i<childCount; i++)
+		{
+			var cell = model.getChildAt(parent, i);
+			
+			if (this.model.isVertex(cell) && this.isCellVisible(cell))
+			{
+				var conns = this.getConnections(cell, (isolate) ? parent : null);
+				var fanOut = 0;
+				var fanIn = 0;
+				
+				for (var j = 0; j < conns.length; j++)
+				{
+					var src = this.view.getVisibleTerminal(conns[j], true);
+
+                    if (src == cell)
+                    {
+                        fanOut++;
+                    }
+                    else
+                    {
+                        fanIn++;
+                    }
+				}
+				
+				if ((invert && fanOut == 0 && fanIn > 0) ||
+					(!invert && fanIn == 0 && fanOut > 0))
+				{
+					roots.push(cell);
+				}
+				
+				var diff = (invert) ? fanIn - fanOut : fanOut - fanIn;
+				
+				if (diff > maxDiff)
+				{
+					maxDiff = diff;
+					best = cell;
+				}
+			}
+		}
+		
+		if (roots.length == 0 && best != null)
+		{
+			roots.push(best);
+		}
+	}
+	
+	return roots;
+};
+
+/**
+ * Function: traverse
+ * 
+ * Traverses the (directed) graph invoking the given function for each
+ * visited vertex and edge. The function is invoked with the current vertex
+ * and the incoming edge as a parameter. This implementation makes sure
+ * each vertex is only visited once. The function may return false if the
+ * traversal should stop at the given vertex.
+ * 
+ * Example:
+ * 
+ * (code)
+ * mxLog.show();
+ * var cell = graph.getSelectionCell();
+ * graph.traverse(cell, false, function(vertex, edge)
+ * {
+ *   mxLog.debug(graph.getLabel(vertex));
+ * });
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * vertex - <mxCell> that represents the vertex where the traversal starts.
+ * directed - Optional boolean indicating if edges should only be traversed
+ * from source to target. Default is true.
+ * func - Visitor function that takes the current vertex and the incoming
+ * edge as arguments. The traversal stops if the function returns false.
+ * edge - Optional <mxCell> that represents the incoming edge. This is
+ * null for the first step of the traversal.
+ * visited - Optional <mxDictionary> from cells to true for the visited cells.
+ * inverse - Optional boolean to traverse in inverse direction. Default is false.
+ * This is ignored if directed is false.
+ */
+mxGraph.prototype.traverse = function(vertex, directed, func, edge, visited, inverse)
+{
+	if (func != null && vertex != null)
+	{
+		directed = (directed != null) ? directed : true;
+		inverse = (inverse != null) ? inverse : false;
+		visited = visited || new mxDictionary();
+		
+		if (!visited.get(vertex))
+		{
+			visited.put(vertex, true);
+			var result = func(vertex, edge);
+			
+			if (result == null || result)
+			{
+				var edgeCount = this.model.getEdgeCount(vertex);
+				
+				if (edgeCount > 0)
+				{
+					for (var i = 0; i < edgeCount; i++)
+					{
+						var e = this.model.getEdgeAt(vertex, i);
+						var isSource = this.model.getTerminal(e, true) == vertex;
+						
+						if (!directed || (!inverse == isSource))
+						{
+							var next = this.model.getTerminal(e, !isSource);
+							this.traverse(next, directed, func, e, visited, inverse);
+						}
+					}
+				}
+			}
+		}
+	}
+};
+
+/**
+ * Group: Selection
+ */
+
+/**
+ * Function: isCellSelected
+ * 
+ * Returns true if the given cell is selected.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> for which the selection state should be returned.
+ */
+mxGraph.prototype.isCellSelected = function(cell)
+{
+	return this.getSelectionModel().isSelected(cell);
+};
+
+/**
+ * Function: isSelectionEmpty
+ * 
+ * Returns true if the selection is empty.
+ */
+mxGraph.prototype.isSelectionEmpty = function()
+{
+	return this.getSelectionModel().isEmpty();
+};
+
+/**
+ * Function: clearSelection
+ * 
+ * Clears the selection using <mxGraphSelectionModel.clear>.
+ */
+mxGraph.prototype.clearSelection = function()
+{
+	return this.getSelectionModel().clear();
+};
+
+/**
+ * Function: getSelectionCount
+ * 
+ * Returns the number of selected cells.
+ */
+mxGraph.prototype.getSelectionCount = function()
+{
+	return this.getSelectionModel().cells.length;
+};
+	
+/**
+ * Function: getSelectionCell
+ * 
+ * Returns the first cell from the array of selected <mxCells>.
+ */
+mxGraph.prototype.getSelectionCell = function()
+{
+	return this.getSelectionModel().cells[0];
+};
+
+/**
+ * Function: getSelectionCells
+ * 
+ * Returns the array of selected <mxCells>.
+ */
+mxGraph.prototype.getSelectionCells = function()
+{
+	return this.getSelectionModel().cells.slice();
+};
+
+/**
+ * Function: setSelectionCell
+ * 
+ * Sets the selection cell.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to be selected.
+ */
+mxGraph.prototype.setSelectionCell = function(cell)
+{
+	this.getSelectionModel().setCell(cell);
+};
+
+/**
+ * Function: setSelectionCells
+ * 
+ * Sets the selection cell.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> to be selected.
+ */
+mxGraph.prototype.setSelectionCells = function(cells)
+{
+	this.getSelectionModel().setCells(cells);
+};
+
+/**
+ * Function: addSelectionCell
+ * 
+ * Adds the given cell to the selection.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to be add to the selection.
+ */
+mxGraph.prototype.addSelectionCell = function(cell)
+{
+	this.getSelectionModel().addCell(cell);
+};
+
+/**
+ * Function: addSelectionCells
+ * 
+ * Adds the given cells to the selection.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> to be added to the selection.
+ */
+mxGraph.prototype.addSelectionCells = function(cells)
+{
+	this.getSelectionModel().addCells(cells);
+};
+
+/**
+ * Function: removeSelectionCell
+ * 
+ * Removes the given cell from the selection.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to be removed from the selection.
+ */
+mxGraph.prototype.removeSelectionCell = function(cell)
+{
+	this.getSelectionModel().removeCell(cell);
+};
+
+/**
+ * Function: removeSelectionCells
+ * 
+ * Removes the given cells from the selection.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> to be removed from the selection.
+ */
+mxGraph.prototype.removeSelectionCells = function(cells)
+{
+	this.getSelectionModel().removeCells(cells);
+};
+
+/**
+ * Function: selectRegion
+ * 
+ * Selects and returns the cells inside the given rectangle for the
+ * specified event.
+ * 
+ * Parameters:
+ * 
+ * rect - <mxRectangle> that represents the region to be selected.
+ * evt - Mouseevent that triggered the selection.
+ */
+mxGraph.prototype.selectRegion = function(rect, evt)
+{
+	var cells = this.getCells(rect.x, rect.y, rect.width, rect.height);
+	this.selectCellsForEvent(cells, evt);
+	
+	return cells;
+};
+
+/**
+ * Function: selectNextCell
+ * 
+ * Selects the next cell.
+ */
+mxGraph.prototype.selectNextCell = function()
+{
+	this.selectCell(true);
+};
+
+/**
+ * Function: selectPreviousCell
+ * 
+ * Selects the previous cell.
+ */
+mxGraph.prototype.selectPreviousCell = function()
+{
+	this.selectCell();
+};
+
+/**
+ * Function: selectParentCell
+ * 
+ * Selects the parent cell.
+ */
+mxGraph.prototype.selectParentCell = function()
+{
+	this.selectCell(false, true);
+};
+
+/**
+ * Function: selectChildCell
+ * 
+ * Selects the first child cell.
+ */
+mxGraph.prototype.selectChildCell = function()
+{
+	this.selectCell(false, false, true);
+};
+
+/**
+ * Function: selectCell
+ * 
+ * Selects the next, parent, first child or previous cell, if all arguments
+ * are false.
+ * 
+ * Parameters:
+ * 
+ * isNext - Boolean indicating if the next cell should be selected.
+ * isParent - Boolean indicating if the parent cell should be selected.
+ * isChild - Boolean indicating if the first child cell should be selected.
+ */
+mxGraph.prototype.selectCell = function(isNext, isParent, isChild)
+{
+	var sel = this.selectionModel;
+	var cell = (sel.cells.length > 0) ? sel.cells[0] : null;
+	
+	if (sel.cells.length > 1)
+	{
+		sel.clear();
+	}
+	
+	var parent = (cell != null) ?
+		this.model.getParent(cell) :
+		this.getDefaultParent();
+	
+	var childCount = this.model.getChildCount(parent);
+	
+	if (cell == null && childCount > 0)
+	{
+		var child = this.model.getChildAt(parent, 0);
+		this.setSelectionCell(child);
+	}
+	else if ((cell == null || isParent) &&
+		this.view.getState(parent) != null &&
+		this.model.getGeometry(parent) != null)
+	{
+		if (this.getCurrentRoot() != parent)
+		{
+			this.setSelectionCell(parent);
+		}
+	}
+	else if (cell != null && isChild)
+	{
+		var tmp = this.model.getChildCount(cell);
+		
+		if (tmp > 0)
+		{
+			var child = this.model.getChildAt(cell, 0);
+			this.setSelectionCell(child);
+		}
+	}
+	else if (childCount > 0)
+	{
+		var i = parent.getIndex(cell);
+		
+		if (isNext)
+		{
+			i++;
+			var child = this.model.getChildAt(parent, i % childCount);
+			this.setSelectionCell(child);
+		}
+		else
+		{
+			i--;
+			var index =  (i < 0) ? childCount - 1 : i;
+			var child = this.model.getChildAt(parent, index);
+			this.setSelectionCell(child);
+		}
+	}
+};
+
+/**
+ * Function: selectAll
+ * 
+ * Selects all children of the given parent cell or the children of the
+ * default parent if no parent is specified. To select leaf vertices and/or
+ * edges use <selectCells>.
+ * 
+ * Parameters:
+ * 
+ * parent - Optional <mxCell> whose children should be selected.
+ * Default is <defaultParent>.
+ * descendants - Optional boolean specifying whether all descendants should be
+ * selected. Default is false.
+ */
+mxGraph.prototype.selectAll = function(parent, descendants)
+{
+	parent = parent || this.getDefaultParent();
+	
+	var cells = (descendants) ? this.model.filterDescendants(function(cell)
+	{
+		return cell != parent;
+	}, parent) : this.model.getChildren(parent);
+	
+	if (cells != null)
+	{
+		this.setSelectionCells(cells);
+	}
+};
+
+/**
+ * Function: selectVertices
+ * 
+ * Select all vertices inside the given parent or the default parent.
+ */
+mxGraph.prototype.selectVertices = function(parent)
+{
+	this.selectCells(true, false, parent);
+};
+
+/**
+ * Function: selectVertices
+ * 
+ * Select all vertices inside the given parent or the default parent.
+ */
+mxGraph.prototype.selectEdges = function(parent)
+{
+	this.selectCells(false, true, parent);
+};
+
+/**
+ * Function: selectCells
+ * 
+ * Selects all vertices and/or edges depending on the given boolean
+ * arguments recursively, starting at the given parent or the default
+ * parent if no parent is specified. Use <selectAll> to select all cells.
+ * For vertices, only cells with no children are selected.
+ * 
+ * Parameters:
+ * 
+ * vertices - Boolean indicating if vertices should be selected.
+ * edges - Boolean indicating if edges should be selected.
+ * parent - Optional <mxCell> that acts as the root of the recursion.
+ * Default is <defaultParent>.
+ */
+mxGraph.prototype.selectCells = function(vertices, edges, parent)
+{
+	parent = parent || this.getDefaultParent();
+	
+	var filter = mxUtils.bind(this, function(cell)
+	{
+		return this.view.getState(cell) != null &&
+			((this.model.getChildCount(cell) == 0 && this.model.isVertex(cell) && vertices
+			&& !this.model.isEdge(this.model.getParent(cell))) ||
+			(this.model.isEdge(cell) && edges));
+	});
+	
+	var cells = this.model.filterDescendants(filter, parent);
+	this.setSelectionCells(cells);
+};
+
+/**
+ * Function: selectCellForEvent
+ * 
+ * Selects the given cell by either adding it to the selection or
+ * replacing the selection depending on whether the given mouse event is a
+ * toggle event.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to be selected.
+ * evt - Optional mouseevent that triggered the selection.
+ */
+mxGraph.prototype.selectCellForEvent = function(cell, evt)
+{
+	var isSelected = this.isCellSelected(cell);
+	
+	if (this.isToggleEvent(evt))
+	{
+		if (isSelected)
+		{
+			this.removeSelectionCell(cell);
+		}
+		else
+		{
+			this.addSelectionCell(cell);
+		}
+	}
+	else if (!isSelected || this.getSelectionCount() != 1)
+	{
+		this.setSelectionCell(cell);
+	}
+};
+
+/**
+ * Function: selectCellsForEvent
+ * 
+ * Selects the given cells by either adding them to the selection or
+ * replacing the selection depending on whether the given mouse event is a
+ * toggle event.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> to be selected.
+ * evt - Optional mouseevent that triggered the selection.
+ */
+mxGraph.prototype.selectCellsForEvent = function(cells, evt)
+{
+	if (this.isToggleEvent(evt))
+	{
+		this.addSelectionCells(cells);
+	}
+	else
+	{
+		this.setSelectionCells(cells);
+	}
+};
+
+/**
+ * Group: Selection state
+ */
+
+/**
+ * Function: createHandler
+ * 
+ * Creates a new handler for the given cell state. This implementation
+ * returns a new <mxEdgeHandler> of the corresponding cell is an edge,
+ * otherwise it returns an <mxVertexHandler>.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose handler should be created.
+ */
+mxGraph.prototype.createHandler = function(state)
+{
+	var result = null;
+	
+	if (state != null)
+	{
+		if (this.model.isEdge(state.cell))
+		{
+			var source = state.getVisibleTerminalState(true);
+			var target = state.getVisibleTerminalState(false);
+			var geo = this.getCellGeometry(state.cell);
+			
+			var edgeStyle = this.view.getEdgeStyle(state, (geo != null) ? geo.points : null, source, target);
+			result = this.createEdgeHandler(state, edgeStyle);
+		}
+		else
+		{
+			result = this.createVertexHandler(state);
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: createVertexHandler
+ * 
+ * Hooks to create a new <mxVertexHandler> for the given <mxCellState>.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> to create the handler for.
+ */
+mxGraph.prototype.createVertexHandler = function(state)
+{
+	return new mxVertexHandler(state);
+};
+
+/**
+ * Function: createEdgeHandler
+ * 
+ * Hooks to create a new <mxEdgeHandler> for the given <mxCellState>.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> to create the handler for.
+ */
+mxGraph.prototype.createEdgeHandler = function(state, edgeStyle)
+{
+	var result = null;
+	
+	if (edgeStyle == mxEdgeStyle.Loop ||
+		edgeStyle == mxEdgeStyle.ElbowConnector ||
+		edgeStyle == mxEdgeStyle.SideToSide ||
+		edgeStyle == mxEdgeStyle.TopToBottom)
+	{
+		result = this.createElbowEdgeHandler(state);
+	}
+	else if (edgeStyle == mxEdgeStyle.SegmentConnector || 
+			edgeStyle == mxEdgeStyle.OrthConnector)
+	{
+		result = this.createEdgeSegmentHandler(state);
+	}
+	else
+	{
+		result = new mxEdgeHandler(state);
+	}
+	
+	return result;
+};
+
+/**
+ * Function: createEdgeSegmentHandler
+ * 
+ * Hooks to create a new <mxEdgeSegmentHandler> for the given <mxCellState>.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> to create the handler for.
+ */
+mxGraph.prototype.createEdgeSegmentHandler = function(state)
+{
+	return new mxEdgeSegmentHandler(state);
+};
+
+/**
+ * Function: createElbowEdgeHandler
+ * 
+ * Hooks to create a new <mxElbowEdgeHandler> for the given <mxCellState>.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> to create the handler for.
+ */
+mxGraph.prototype.createElbowEdgeHandler = function(state)
+{
+	return new mxElbowEdgeHandler(state);
+};
+
+/**
+ * Group: Graph events
+ */
+
+/**
+ * Function: addMouseListener
+ * 
+ * Adds a listener to the graph event dispatch loop. The listener
+ * must implement the mouseDown, mouseMove and mouseUp methods
+ * as shown in the <mxMouseEvent> class.
+ * 
+ * Parameters:
+ * 
+ * listener - Listener to be added to the graph event listeners.
+ */
+mxGraph.prototype.addMouseListener = function(listener)
+{
+	if (this.mouseListeners == null)
+	{
+		this.mouseListeners = [];
+	}
+	
+	this.mouseListeners.push(listener);
+};
+
+/**
+ * Function: removeMouseListener
+ * 
+ * Removes the specified graph listener.
+ * 
+ * Parameters:
+ * 
+ * listener - Listener to be removed from the graph event listeners.
+ */
+mxGraph.prototype.removeMouseListener = function(listener)
+{
+	if (this.mouseListeners != null)
+	{
+		for (var i = 0; i < this.mouseListeners.length; i++)
+		{
+			if (this.mouseListeners[i] == listener)
+			{
+				this.mouseListeners.splice(i, 1);
+				break;
+			}
+		}
+	}
+};
+
+/**
+ * Function: updateMouseEvent
+ * 
+ * Sets the graphX and graphY properties if the given <mxMouseEvent> if
+ * required and returned the event.
+ * 
+ * Parameters:
+ * 
+ * me - <mxMouseEvent> to be updated.
+ * evtName - Name of the mouse event.
+ */
+mxGraph.prototype.updateMouseEvent = function(me, evtName)
+{
+	if (me.graphX == null || me.graphY == null)
+	{
+		var pt = mxUtils.convertPoint(this.container, me.getX(), me.getY());
+		
+		me.graphX = pt.x - this.panDx;
+		me.graphY = pt.y - this.panDy;
+		
+		// Searches for rectangles using method if native hit detection is disabled on shape
+		if (me.getCell() == null && this.isMouseDown && evtName == mxEvent.MOUSE_MOVE)
+		{
+			me.state = this.view.getState(this.getCellAt(pt.x, pt.y, null, null, null, function(state)
+			{
+				return state.shape == null || state.shape.paintBackground != mxRectangleShape.prototype.paintBackground ||
+					mxUtils.getValue(state.style, mxConstants.STYLE_POINTER_EVENTS, '1') == '1' ||
+					(state.shape.fill != null && state.shape.fill != mxConstants.NONE);
+			}));
+		}
+	}
+	
+	return me;
+};
+
+/**
+ * Function: getStateForEvent
+ * 
+ * Returns the state for the given touch event.
+ */
+mxGraph.prototype.getStateForTouchEvent = function(evt)
+{
+	var x = mxEvent.getClientX(evt);
+	var y = mxEvent.getClientY(evt);
+	
+	// Dispatches the drop event to the graph which
+	// consumes and executes the source function
+	var pt = mxUtils.convertPoint(this.container, x, y);
+
+	return this.view.getState(this.getCellAt(pt.x, pt.y));
+};
+
+/**
+ * Function: isEventIgnored
+ * 
+ * Returns true if the event should be ignored in <fireMouseEvent>.
+ */
+mxGraph.prototype.isEventIgnored = function(evtName, me, sender)
+{
+	var mouseEvent = mxEvent.isMouseEvent(me.getEvent());
+	var result = false;
+
+	// Drops events that are fired more than once
+	if (me.getEvent() == this.lastEvent)
+	{
+		result = true;
+	}
+	else
+	{
+		this.lastEvent = me.getEvent();
+	}
+
+	// Installs event listeners to capture the complete gesture from the event source
+	// for non-MS touch events as a workaround for all events for the same geture being
+	// fired from the event source even if that was removed from the DOM.
+	if (this.eventSource != null && evtName != mxEvent.MOUSE_MOVE)
+	{
+		mxEvent.removeGestureListeners(this.eventSource, null, this.mouseMoveRedirect, this.mouseUpRedirect);
+		this.mouseMoveRedirect = null;
+		this.mouseUpRedirect = null;
+		this.eventSource = null;
+	}
+	else if (this.eventSource != null && me.getSource() != this.eventSource)
+	{
+		result = true;
+	}
+	else if (mxClient.IS_TOUCH && evtName == mxEvent.MOUSE_DOWN && !mouseEvent && !mxEvent.isPenEvent(me.getEvent()))
+	{
+		this.eventSource = me.getSource();
+
+		this.mouseMoveRedirect = mxUtils.bind(this, function(evt)
+		{
+			this.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, this.getStateForTouchEvent(evt)));
+		});
+		this.mouseUpRedirect = mxUtils.bind(this, function(evt)
+		{
+			this.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt, this.getStateForTouchEvent(evt)));
+		});
+		
+		mxEvent.addGestureListeners(this.eventSource, null, this.mouseMoveRedirect, this.mouseUpRedirect);
+	}
+
+	// Factored out the workarounds for FF to make it easier to override/remove
+	// Note this method has side-effects!
+	if (this.isSyntheticEventIgnored(evtName, me, sender))
+	{
+		result = true;
+	}
+
+	// Never fires mouseUp/-Down for double clicks
+	if (!mxEvent.isPopupTrigger(this.lastEvent) && evtName != mxEvent.MOUSE_MOVE && this.lastEvent.detail == 2)
+	{
+		return true;
+	}
+	
+	// Filters out of sequence events or mixed event types during a gesture
+	if (evtName == mxEvent.MOUSE_UP && this.isMouseDown)
+	{
+		this.isMouseDown = false;
+	}
+	else if (evtName == mxEvent.MOUSE_DOWN && !this.isMouseDown)
+	{
+		this.isMouseDown = true;
+		this.isMouseTrigger = mouseEvent;
+	}
+	// Drops mouse events that are fired during touch gestures as a workaround for Webkit
+	// and mouse events that are not in sync with the current internal button state
+	else if (!result && (((!mxClient.IS_FF || evtName != mxEvent.MOUSE_MOVE) &&
+		this.isMouseDown && this.isMouseTrigger != mouseEvent) ||
+		(evtName == mxEvent.MOUSE_DOWN && this.isMouseDown) ||
+		(evtName == mxEvent.MOUSE_UP && !this.isMouseDown)))
+	{
+		result = true;
+	}
+	
+	if (!result && evtName == mxEvent.MOUSE_DOWN)
+	{
+		this.lastMouseX = me.getX();
+		this.lastMouseY = me.getY();
+	}
+
+	return result;
+};
+
+/**
+ * Function: isSyntheticEventIgnored
+ * 
+ * Hook for ignoring synthetic mouse events after touchend in Firefox.
+ */
+mxGraph.prototype.isSyntheticEventIgnored = function(evtName, me, sender)
+{
+	var result = false;
+	var mouseEvent = mxEvent.isMouseEvent(me.getEvent());
+	
+	// LATER: This does not cover all possible cases that can go wrong in FF
+	if (this.ignoreMouseEvents && mouseEvent && evtName != mxEvent.MOUSE_MOVE)
+	{
+		this.ignoreMouseEvents = evtName != mxEvent.MOUSE_UP;
+		result = true;
+	}
+	else if (mxClient.IS_FF && !mouseEvent && evtName == mxEvent.MOUSE_UP)
+	{
+		this.ignoreMouseEvents = true;
+	}
+	
+	return result;
+};
+
+/**
+ * Function: isEventSourceIgnored
+ * 
+ * Returns true if the event should be ignored in <fireMouseEvent>. This
+ * implementation returns true for select, option and input (if not of type
+ * checkbox, radio, button, submit or file) event sources if the event is not
+ * a mouse event or a left mouse button press event.
+ * 
+ * Parameters:
+ * 
+ * evtName - The name of the event.
+ * me - <mxMouseEvent> that should be ignored.
+ */
+mxGraph.prototype.isEventSourceIgnored = function(evtName, me)
+{
+	var source = me.getSource();
+	var name = (source.nodeName != null) ? source.nodeName.toLowerCase() : '';
+	var candidate = !mxEvent.isMouseEvent(me.getEvent()) || mxEvent.isLeftMouseButton(me.getEvent());
+	
+	return evtName == mxEvent.MOUSE_DOWN && candidate && (name == 'select' || name == 'option' ||
+		(name == 'input' && source.type != 'checkbox' && source.type != 'radio' &&
+		source.type != 'button' && source.type != 'submit' && source.type != 'file'));
+};
+
+/**
+ * Function: getEventState
+ * 
+ * Returns the <mxCellState> to be used when firing the mouse event for the
+ * given state. This implementation returns the given state.
+ * 
+ * Parameters:
+ * 
+ * <mxCellState> - State whose event source should be returned.
+ */
+mxGraph.prototype.getEventState = function(state)
+{
+	return state;
+};
+
+/**
+ * Function: fireMouseEvent
+ * 
+ * Dispatches the given event in the graph event dispatch loop. Possible
+ * event names are <mxEvent.MOUSE_DOWN>, <mxEvent.MOUSE_MOVE> and
+ * <mxEvent.MOUSE_UP>. All listeners are invoked for all events regardless
+ * of the consumed state of the event.
+ * 
+ * Parameters:
+ * 
+ * evtName - String that specifies the type of event to be dispatched.
+ * me - <mxMouseEvent> to be fired.
+ * sender - Optional sender argument. Default is this.
+ */
+mxGraph.prototype.fireMouseEvent = function(evtName, me, sender)
+{
+	if (this.isEventSourceIgnored(evtName, me))
+	{
+		if (this.tooltipHandler != null)
+		{
+			this.tooltipHandler.hide();
+		}
+		
+		return;
+	}
+	
+	if (sender == null)
+	{
+		sender = this;
+	}
+
+	// Updates the graph coordinates in the event
+	me = this.updateMouseEvent(me, evtName);
+
+	// Detects and processes double taps for touch-based devices which do not have native double click events
+	// or where detection of double click is not always possible (quirks, IE10+). Note that this can only handle
+	// double clicks on cells because the sequence of events in IE prevents detection on the background, it fires
+	// two mouse ups, one of which without a cell but no mousedown for the second click which means we cannot
+	// detect which mouseup(s) are part of the first click, ie we do not know when the first click ends.
+	if ((!this.nativeDblClickEnabled && !mxEvent.isPopupTrigger(me.getEvent())) || (this.doubleTapEnabled &&
+		mxClient.IS_TOUCH && (mxEvent.isTouchEvent(me.getEvent()) || mxEvent.isPenEvent(me.getEvent()))))
+	{
+		var currentTime = new Date().getTime();
+		
+		// NOTE: Second mouseDown for double click missing in quirks mode
+		if ((!mxClient.IS_QUIRKS && evtName == mxEvent.MOUSE_DOWN) || (mxClient.IS_QUIRKS && evtName == mxEvent.MOUSE_UP && !this.fireDoubleClick))
+		{
+			if (this.lastTouchEvent != null && this.lastTouchEvent != me.getEvent() &&
+				currentTime - this.lastTouchTime < this.doubleTapTimeout &&
+				Math.abs(this.lastTouchX - me.getX()) < this.doubleTapTolerance &&
+				Math.abs(this.lastTouchY - me.getY()) < this.doubleTapTolerance &&
+				this.doubleClickCounter < 2)
+			{
+				this.doubleClickCounter++;
+				var doubleClickFired = false;
+				
+				if (evtName == mxEvent.MOUSE_UP)
+				{
+					if (me.getCell() == this.lastTouchCell && this.lastTouchCell != null)
+					{
+						this.lastTouchTime = 0;
+						var cell = this.lastTouchCell;
+						this.lastTouchCell = null;
+
+						// Fires native dblclick event via event source
+						// NOTE: This fires two double click events on edges in quirks mode. While
+						// trying to fix this, we realized that nativeDoubleClick can be disabled for
+						// quirks and IE10+ (or we didn't find the case mentioned above where it
+						// would not work), ie. all double clicks seem to be working without this.
+						if (mxClient.IS_QUIRKS)
+						{
+							me.getSource().fireEvent('ondblclick');
+						}
+						
+						this.dblClick(me.getEvent(), cell);
+						doubleClickFired = true;
+					}
+				}
+				else
+				{
+					this.fireDoubleClick = true;
+					this.lastTouchTime = 0;
+				}
+
+				// Do not ignore mouse up in quirks in this case
+				if (!mxClient.IS_QUIRKS || doubleClickFired)
+				{
+					mxEvent.consume(me.getEvent());
+					return;
+				}
+			}
+			else if (this.lastTouchEvent == null || this.lastTouchEvent != me.getEvent())
+			{
+				this.lastTouchCell = me.getCell();
+				this.lastTouchX = me.getX();
+				this.lastTouchY = me.getY();
+				this.lastTouchTime = currentTime;
+				this.lastTouchEvent = me.getEvent();
+				this.doubleClickCounter = 0;
+			}
+		}
+		else if ((this.isMouseDown || evtName == mxEvent.MOUSE_UP) && this.fireDoubleClick)
+		{
+			this.fireDoubleClick = false;
+			var cell = this.lastTouchCell;
+			this.lastTouchCell = null;
+			this.isMouseDown = false;
+			
+			// Workaround for Chrome/Safari not firing native double click events for double touch on background
+			var valid = (cell != null) || ((mxEvent.isTouchEvent(me.getEvent()) || mxEvent.isPenEvent(me.getEvent())) &&
+				(mxClient.IS_GC || mxClient.IS_SF));
+			
+			if (valid && Math.abs(this.lastTouchX - me.getX()) < this.doubleTapTolerance &&
+				Math.abs(this.lastTouchY - me.getY()) < this.doubleTapTolerance)
+			{
+				this.dblClick(me.getEvent(), cell);
+			}
+			else
+			{
+				mxEvent.consume(me.getEvent());
+			}
+			
+			return;
+		}
+	}
+
+	if (!this.isEventIgnored(evtName, me, sender))
+	{
+		// Updates the event state via getEventState
+		me.state = this.getEventState(me.getState());
+		this.fireEvent(new mxEventObject(mxEvent.FIRE_MOUSE_EVENT, 'eventName', evtName, 'event', me));
+		
+		if ((mxClient.IS_OP || mxClient.IS_SF || mxClient.IS_GC || mxClient.IS_IE11 ||
+			(mxClient.IS_IE && mxClient.IS_SVG) || me.getEvent().target != this.container))
+		{
+			if (evtName == mxEvent.MOUSE_MOVE && this.isMouseDown && this.autoScroll && !mxEvent.isMultiTouchEvent(me.getEvent))
+			{
+				this.scrollPointToVisible(me.getGraphX(), me.getGraphY(), this.autoExtend);
+			}
+			else if (evtName == mxEvent.MOUSE_UP && this.ignoreScrollbars && this.translateToScrollPosition &&
+					(this.container.scrollLeft != 0 || this.container.scrollTop != 0))
+			{
+				var s = this.view.scale;
+				var tr = this.view.translate;
+				this.view.setTranslate(tr.x - this.container.scrollLeft / s, tr.y - this.container.scrollTop / s);
+				this.container.scrollLeft = 0;
+				this.container.scrollTop = 0;
+			}
+			
+			if (this.mouseListeners != null)
+			{
+				var args = [sender, me];
+	
+				// Does not change returnValue in Opera
+				if (!me.getEvent().preventDefault)
+				{
+					me.getEvent().returnValue = true;
+				}
+				
+				for (var i = 0; i < this.mouseListeners.length; i++)
+				{
+					var l = this.mouseListeners[i];
+					
+					if (evtName == mxEvent.MOUSE_DOWN)
+					{
+						l.mouseDown.apply(l, args);
+					}
+					else if (evtName == mxEvent.MOUSE_MOVE)
+					{
+						l.mouseMove.apply(l, args);
+					}
+					else if (evtName == mxEvent.MOUSE_UP)
+					{
+						l.mouseUp.apply(l, args);
+					}
+				}
+			}
+			
+			// Invokes the click handler
+			if (evtName == mxEvent.MOUSE_UP)
+			{
+				this.click(me);
+			}
+		}
+		
+		// Detects tapAndHold events using a timer
+		if ((mxEvent.isTouchEvent(me.getEvent()) || mxEvent.isPenEvent(me.getEvent())) &&
+			evtName == mxEvent.MOUSE_DOWN && this.tapAndHoldEnabled && !this.tapAndHoldInProgress)
+		{
+			this.tapAndHoldInProgress = true;
+			this.initialTouchX = me.getGraphX();
+			this.initialTouchY = me.getGraphY();
+			
+			var handler = function()
+			{
+				if (this.tapAndHoldValid)
+				{
+					this.tapAndHold(me);
+				}
+				
+				this.tapAndHoldInProgress = false;
+				this.tapAndHoldValid = false;
+			};
+			
+			if (this.tapAndHoldThread)
+			{
+				window.clearTimeout(this.tapAndHoldThread);
+			}
+	
+			this.tapAndHoldThread = window.setTimeout(mxUtils.bind(this, handler), this.tapAndHoldDelay);
+			this.tapAndHoldValid = true;
+		}
+		else if (evtName == mxEvent.MOUSE_UP)
+		{
+			this.tapAndHoldInProgress = false;
+			this.tapAndHoldValid = false;
+		}
+		else if (this.tapAndHoldValid)
+		{
+			this.tapAndHoldValid =
+				Math.abs(this.initialTouchX - me.getGraphX()) < this.tolerance &&
+				Math.abs(this.initialTouchY - me.getGraphY()) < this.tolerance;
+		}
+
+		// Stops editing for all events other than from cellEditor
+		if (evtName == mxEvent.MOUSE_DOWN && this.isEditing() && !this.cellEditor.isEventSource(me.getEvent()))
+		{
+			this.stopEditing(!this.isInvokesStopCellEditing());
+		}
+
+		this.consumeMouseEvent(evtName, me, sender);
+	}
+};
+
+/**
+ * Function: consumeMouseEvent
+ * 
+ * Consumes the given <mxMouseEvent> if it's a touchStart event.
+ */
+mxGraph.prototype.consumeMouseEvent = function(evtName, me, sender)
+{
+	// Workaround for duplicate click in Windows 8 with Chrome/FF/Opera with touch
+	if (evtName == mxEvent.MOUSE_DOWN && mxEvent.isTouchEvent(me.getEvent()))
+	{
+		me.consume(false);
+	}
+};
+
+/**
+ * Function: fireGestureEvent
+ * 
+ * Dispatches a <mxEvent.GESTURE> event. The following example will resize the
+ * cell under the mouse based on the scale property of the native touch event.
+ * 
+ * (code)
+ * graph.addListener(mxEvent.GESTURE, function(sender, eo)
+ * {
+ *   var evt = eo.getProperty('event');
+ *   var state = graph.view.getState(eo.getProperty('cell'));
+ *   
+ *   if (graph.isEnabled() && graph.isCellResizable(state.cell) && Math.abs(1 - evt.scale) > 0.2)
+ *   {
+ *     var scale = graph.view.scale;
+ *     var tr = graph.view.translate;
+ *     
+ *     var w = state.width * evt.scale;
+ *     var h = state.height * evt.scale;
+ *     var x = state.x - (w - state.width) / 2;
+ *     var y = state.y - (h - state.height) / 2;
+ *     
+ *     var bounds = new mxRectangle(graph.snap(x / scale) - tr.x,
+ *     		graph.snap(y / scale) - tr.y, graph.snap(w / scale), graph.snap(h / scale));
+ *     graph.resizeCell(state.cell, bounds);
+ *     eo.consume();
+ *   }
+ * });
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * evt - Gestureend event that represents the gesture.
+ * cell - Optional <mxCell> associated with the gesture.
+ */
+mxGraph.prototype.fireGestureEvent = function(evt, cell)
+{
+	// Resets double tap event handling when gestures take place
+	this.lastTouchTime = 0;
+	this.fireEvent(new mxEventObject(mxEvent.GESTURE, 'event', evt, 'cell', cell));
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the graph and all its resources.
+ */
+mxGraph.prototype.destroy = function()
+{
+	if (!this.destroyed)
+	{
+		this.destroyed = true;
+		
+		if (this.tooltipHandler != null)
+		{
+			this.tooltipHandler.destroy();
+		}
+		
+		if (this.selectionCellsHandler != null)
+		{
+			this.selectionCellsHandler.destroy();
+		}
+
+		if (this.panningHandler != null)
+		{
+			this.panningHandler.destroy();
+		}
+
+		if (this.popupMenuHandler != null)
+		{
+			this.popupMenuHandler.destroy();
+		}
+		
+		if (this.connectionHandler != null)
+		{
+			this.connectionHandler.destroy();
+		}
+		
+		if (this.graphHandler != null)
+		{
+			this.graphHandler.destroy();
+		}
+		
+		if (this.cellEditor != null)
+		{
+			this.cellEditor.destroy();
+		}
+		
+		if (this.view != null)
+		{
+			this.view.destroy();
+		}
+
+		if (this.model != null && this.graphModelChangeListener != null)
+		{
+			this.model.removeListener(this.graphModelChangeListener);
+			this.graphModelChangeListener = null;
+		}
+
+		this.container = null;
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/view/mxGraphSelectionModel.js b/airavata-kubernetes/workflow-composer/src/js/view/mxGraphSelectionModel.js
new file mode 100644
index 0000000..db55424
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/view/mxGraphSelectionModel.js
@@ -0,0 +1,436 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxGraphSelectionModel
+ *
+ * Implements the selection model for a graph. Here is a listener that handles
+ * all removed selection cells.
+ * 
+ * (code)
+ * graph.getSelectionModel().addListener(mxEvent.CHANGE, function(sender, evt)
+ * {
+ *   var cells = evt.getProperty('added');
+ *   
+ *   for (var i = 0; i < cells.length; i++)
+ *   {
+ *     // Handle cells[i]...
+ *   }
+ * });
+ * (end)
+ * 
+ * Event: mxEvent.UNDO
+ * 
+ * Fires after the selection was changed in <changeSelection>. The
+ * <code>edit</code> property contains the <mxUndoableEdit> which contains the
+ * <mxSelectionChange>.
+ * 
+ * Event: mxEvent.CHANGE
+ * 
+ * Fires after the selection changes by executing an <mxSelectionChange>. The
+ * <code>added</code> and <code>removed</code> properties contain arrays of
+ * cells that have been added to or removed from the selection, respectively.
+ * The names are inverted due to historic reasons. This cannot be changed.
+ * 
+ * Constructor: mxGraphSelectionModel
+ *
+ * Constructs a new graph selection model for the given <mxGraph>.
+ * 
+ * Parameters:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ */
+function mxGraphSelectionModel(graph)
+{
+	this.graph = graph;
+	this.cells = [];
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxGraphSelectionModel.prototype = new mxEventSource();
+mxGraphSelectionModel.prototype.constructor = mxGraphSelectionModel;
+
+/**
+ * Variable: doneResource
+ * 
+ * Specifies the resource key for the status message after a long operation.
+ * If the resource for this key does not exist then the value is used as
+ * the status message. Default is 'done'.
+ */
+mxGraphSelectionModel.prototype.doneResource = (mxClient.language != 'none') ? 'done' : '';
+
+/**
+ * Variable: updatingSelectionResource
+ *
+ * Specifies the resource key for the status message while the selection is
+ * being updated. If the resource for this key does not exist then the
+ * value is used as the status message. Default is 'updatingSelection'.
+ */
+mxGraphSelectionModel.prototype.updatingSelectionResource = (mxClient.language != 'none') ? 'updatingSelection' : '';
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxGraphSelectionModel.prototype.graph = null;
+
+/**
+ * Variable: singleSelection
+ *
+ * Specifies if only one selected item at a time is allowed.
+ * Default is false.
+ */
+mxGraphSelectionModel.prototype.singleSelection = false;
+
+/**
+ * Function: isSingleSelection
+ *
+ * Returns <singleSelection> as a boolean.
+ */
+mxGraphSelectionModel.prototype.isSingleSelection = function()
+{
+	return this.singleSelection;
+};
+
+/**
+ * Function: setSingleSelection
+ *
+ * Sets the <singleSelection> flag.
+ * 
+ * Parameters:
+ * 
+ * singleSelection - Boolean that specifies the new value for
+ * <singleSelection>.
+ */
+mxGraphSelectionModel.prototype.setSingleSelection = function(singleSelection)
+{
+	this.singleSelection = singleSelection;
+};
+
+/**
+ * Function: isSelected
+ *
+ * Returns true if the given <mxCell> is selected.
+ */
+mxGraphSelectionModel.prototype.isSelected = function(cell)
+{
+	if (cell != null)
+	{
+		return mxUtils.indexOf(this.cells, cell) >= 0;
+	}
+	
+	return false;
+};
+
+/**
+ * Function: isEmpty
+ *
+ * Returns true if no cells are currently selected.
+ */
+mxGraphSelectionModel.prototype.isEmpty = function()
+{
+	return this.cells.length == 0;
+};
+
+/**
+ * Function: clear
+ *
+ * Clears the selection and fires a <change> event if the selection was not
+ * empty.
+ */
+mxGraphSelectionModel.prototype.clear = function()
+{
+	this.changeSelection(null, this.cells);
+};
+
+/**
+ * Function: setCell
+ *
+ * Selects the specified <mxCell> using <setCells>.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to be selected.
+ */
+mxGraphSelectionModel.prototype.setCell = function(cell)
+{
+	if (cell != null)
+	{
+		this.setCells([cell]);
+	}
+};
+
+/**
+ * Function: setCells
+ *
+ * Selects the given array of <mxCells> and fires a <change> event.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> to be selected.
+ */
+mxGraphSelectionModel.prototype.setCells = function(cells)
+{
+	if (cells != null)
+	{
+		if (this.singleSelection)
+		{
+			cells = [this.getFirstSelectableCell(cells)];
+		}
+	
+		var tmp = [];
+		
+		for (var i = 0; i < cells.length; i++)
+		{
+			if (this.graph.isCellSelectable(cells[i]))
+			{
+				tmp.push(cells[i]);
+			}	
+		}
+
+		this.changeSelection(tmp, this.cells);
+	}
+};
+
+/**
+ * Function: getFirstSelectableCell
+ *
+ * Returns the first selectable cell in the given array of cells.
+ */
+mxGraphSelectionModel.prototype.getFirstSelectableCell = function(cells)
+{
+	if (cells != null)
+	{
+		for (var i = 0; i < cells.length; i++)
+		{
+			if (this.graph.isCellSelectable(cells[i]))
+			{
+				return cells[i];
+			}
+		}
+	}
+	
+	return null;
+};
+
+/**
+ * Function: addCell
+ * 
+ * Adds the given <mxCell> to the selection and fires a <select> event.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to add to the selection.
+ */
+mxGraphSelectionModel.prototype.addCell = function(cell)
+{
+	if (cell != null)
+	{
+		this.addCells([cell]);
+	}
+};
+
+/**
+ * Function: addCells
+ * 
+ * Adds the given array of <mxCells> to the selection and fires a <select>
+ * event.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> to add to the selection.
+ */
+mxGraphSelectionModel.prototype.addCells = function(cells)
+{
+	if (cells != null)
+	{
+		var remove = null;
+		
+		if (this.singleSelection)
+		{
+			remove = this.cells;
+			cells = [this.getFirstSelectableCell(cells)];
+		}
+
+		var tmp = [];
+		
+		for (var i = 0; i < cells.length; i++)
+		{
+			if (!this.isSelected(cells[i]) &&
+				this.graph.isCellSelectable(cells[i]))
+			{
+				tmp.push(cells[i]);
+			}	
+		}
+
+		this.changeSelection(tmp, remove);
+	}
+};
+
+/**
+ * Function: removeCell
+ *
+ * Removes the specified <mxCell> from the selection and fires a <select>
+ * event for the remaining cells.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to remove from the selection.
+ */
+mxGraphSelectionModel.prototype.removeCell = function(cell)
+{
+	if (cell != null)
+	{
+		this.removeCells([cell]);
+	}
+};
+
+/**
+ * Function: removeCells
+ */
+mxGraphSelectionModel.prototype.removeCells = function(cells)
+{
+	if (cells != null)
+	{
+		var tmp = [];
+		
+		for (var i = 0; i < cells.length; i++)
+		{
+			if (this.isSelected(cells[i]))
+			{
+				tmp.push(cells[i]);
+			}
+		}
+		
+		this.changeSelection(null, tmp);	
+	}
+};
+
+/**
+ * Function: changeSelection
+ *
+ * Inner callback to add the specified <mxCell> to the selection. No event
+ * is fired in this implementation.
+ * 
+ * Paramters:
+ * 
+ * cell - <mxCell> to add to the selection.
+ */
+mxGraphSelectionModel.prototype.changeSelection = function(added, removed)
+{
+	if ((added != null &&
+		added.length > 0 &&
+		added[0] != null) ||
+		(removed != null &&
+		removed.length > 0 &&
+		removed[0] != null))
+	{
+		var change = new mxSelectionChange(this, added, removed);
+		change.execute();
+		var edit = new mxUndoableEdit(this, false);
+		edit.add(change);
+		this.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit));
+	}
+};
+
+/**
+ * Function: cellAdded
+ *
+ * Inner callback to add the specified <mxCell> to the selection. No event
+ * is fired in this implementation.
+ * 
+ * Paramters:
+ * 
+ * cell - <mxCell> to add to the selection.
+ */
+mxGraphSelectionModel.prototype.cellAdded = function(cell)
+{
+	if (cell != null &&
+		!this.isSelected(cell))
+	{
+		this.cells.push(cell);
+	}
+};
+
+/**
+ * Function: cellRemoved
+ *
+ * Inner callback to remove the specified <mxCell> from the selection. No
+ * event is fired in this implementation.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> to remove from the selection.
+ */
+mxGraphSelectionModel.prototype.cellRemoved = function(cell)
+{
+	if (cell != null)
+	{
+		var index = mxUtils.indexOf(this.cells, cell);
+		
+		if (index >= 0)
+		{
+			this.cells.splice(index, 1);
+		}
+	}
+};
+
+/**
+ * Class: mxSelectionChange
+ *
+ * Action to change the current root in a view.
+ *
+ * Constructor: mxCurrentRootChange
+ *
+ * Constructs a change of the current root in the given view.
+ */
+function mxSelectionChange(selectionModel, added, removed)
+{
+	this.selectionModel = selectionModel;
+	this.added = (added != null) ? added.slice() : null;
+	this.removed = (removed != null) ? removed.slice() : null;
+};
+
+/**
+ * Function: execute
+ *
+ * Changes the current root of the view.
+ */
+mxSelectionChange.prototype.execute = function()
+{
+	var t0 = mxLog.enter('mxSelectionChange.execute');
+	window.status = mxResources.get(
+		this.selectionModel.updatingSelectionResource) ||
+		this.selectionModel.updatingSelectionResource;
+
+	if (this.removed != null)
+	{
+		for (var i = 0; i < this.removed.length; i++)
+		{
+			this.selectionModel.cellRemoved(this.removed[i]);
+		}
+	}
+
+	if (this.added != null)
+	{
+		for (var i = 0; i < this.added.length; i++)
+		{
+			this.selectionModel.cellAdded(this.added[i]);
+		}
+	}
+	
+	var tmp = this.added;
+	this.added = this.removed;
+	this.removed = tmp;
+
+	window.status = mxResources.get(this.selectionModel.doneResource) ||
+		this.selectionModel.doneResource;
+	mxLog.leave('mxSelectionChange.execute', t0);
+	
+	this.selectionModel.fireEvent(new mxEventObject(mxEvent.CHANGE,
+			'added', this.added, 'removed', this.removed));
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/view/mxGraphView.js b/airavata-kubernetes/workflow-composer/src/js/view/mxGraphView.js
new file mode 100644
index 0000000..a81e5cd
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/view/mxGraphView.js
@@ -0,0 +1,3001 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxGraphView
+ *
+ * Extends <mxEventSource> to implement a view for a graph. This class is in
+ * charge of computing the absolute coordinates for the relative child
+ * geometries, the points for perimeters and edge styles and keeping them
+ * cached in <mxCellStates> for faster retrieval. The states are updated
+ * whenever the model or the view state (translate, scale) changes. The scale
+ * and translate are honoured in the bounds.
+ * 
+ * Event: mxEvent.UNDO
+ * 
+ * Fires after the root was changed in <setCurrentRoot>. The <code>edit</code>
+ * property contains the <mxUndoableEdit> which contains the
+ * <mxCurrentRootChange>.
+ * 
+ * Event: mxEvent.SCALE_AND_TRANSLATE
+ * 
+ * Fires after the scale and translate have been changed in <scaleAndTranslate>.
+ * The <code>scale</code>, <code>previousScale</code>, <code>translate</code>
+ * and <code>previousTranslate</code> properties contain the new and previous
+ * scale and translate, respectively.
+ * 
+ * Event: mxEvent.SCALE
+ * 
+ * Fires after the scale was changed in <setScale>. The <code>scale</code> and
+ * <code>previousScale</code> properties contain the new and previous scale.
+ * 
+ * Event: mxEvent.TRANSLATE
+ * 
+ * Fires after the translate was changed in <setTranslate>. The
+ * <code>translate</code> and <code>previousTranslate</code> properties contain
+ * the new and previous value for translate.
+ * 
+ * Event: mxEvent.DOWN and mxEvent.UP
+ * 
+ * Fire if the current root is changed by executing an <mxCurrentRootChange>.
+ * The event name depends on the location of the root in the cell hierarchy
+ * with respect to the current root. The <code>root</code> and
+ * <code>previous</code> properties contain the new and previous root,
+ * respectively.
+ * 
+ * Constructor: mxGraphView
+ *
+ * Constructs a new view for the given <mxGraph>.
+ * 
+ * Parameters:
+ * 
+ * graph - Reference to the enclosing <mxGraph>.
+ */
+function mxGraphView(graph)
+{
+	this.graph = graph;
+	this.translate = new mxPoint();
+	this.graphBounds = new mxRectangle();
+	this.states = new mxDictionary();
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxGraphView.prototype = new mxEventSource();
+mxGraphView.prototype.constructor = mxGraphView;
+
+/**
+ *
+ */
+mxGraphView.prototype.EMPTY_POINT = new mxPoint();
+
+/**
+ * Variable: doneResource
+ * 
+ * Specifies the resource key for the status message after a long operation.
+ * If the resource for this key does not exist then the value is used as
+ * the status message. Default is 'done'.
+ */
+mxGraphView.prototype.doneResource = (mxClient.language != 'none') ? 'done' : '';
+
+/**
+ * Function: updatingDocumentResource
+ *
+ * Specifies the resource key for the status message while the document is
+ * being updated. If the resource for this key does not exist then the
+ * value is used as the status message. Default is 'updatingDocument'.
+ */
+mxGraphView.prototype.updatingDocumentResource = (mxClient.language != 'none') ? 'updatingDocument' : '';
+
+/**
+ * Variable: allowEval
+ * 
+ * Specifies if string values in cell styles should be evaluated using
+ * <mxUtils.eval>. This will only be used if the string values can't be mapped
+ * to objects using <mxStyleRegistry>. Default is false. NOTE: Enabling this
+ * switch carries a possible security risk.
+ */
+mxGraphView.prototype.allowEval = false;
+
+/**
+ * Variable: captureDocumentGesture
+ * 
+ * Specifies if a gesture should be captured when it goes outside of the
+ * graph container. Default is true.
+ */
+mxGraphView.prototype.captureDocumentGesture = true;
+
+/**
+ * Variable: optimizeVmlReflows
+ * 
+ * Specifies if the <canvas> should be hidden while rendering in IE8 standards
+ * mode and quirks mode. This will significantly improve rendering performance.
+ * Default is true.
+ */
+mxGraphView.prototype.optimizeVmlReflows = true;
+
+/**
+ * Variable: rendering
+ * 
+ * Specifies if shapes should be created, updated and destroyed using the
+ * methods of <mxCellRenderer> in <graph>. Default is true.
+ */
+mxGraphView.prototype.rendering = true;
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxGraphView.prototype.graph = null;
+
+/**
+ * Variable: currentRoot
+ *
+ * <mxCell> that acts as the root of the displayed cell hierarchy.
+ */
+mxGraphView.prototype.currentRoot = null;
+
+/**
+ * Variable: graphBounds
+ *
+ * <mxRectangle> that caches the scales, translated bounds of the current view.
+ */
+mxGraphView.prototype.graphBounds = null;
+
+/**
+ * Variable: scale
+ * 
+ * Specifies the scale. Default is 1 (100%).
+ */
+mxGraphView.prototype.scale = 1;
+	
+/**
+ * Variable: translate
+ *
+ * <mxPoint> that specifies the current translation. Default is a new
+ * empty <mxPoint>.
+ */
+mxGraphView.prototype.translate = null;
+
+/**
+ * Variable: states
+ * 
+ * <mxDictionary> that maps from cell IDs to <mxCellStates>.
+ */
+mxGraphView.prototype.states = null;
+
+/**
+ * Variable: updateStyle
+ * 
+ * Specifies if the style should be updated in each validation step. If this
+ * is false then the style is only updated if the state is created or if the
+ * style of the cell was changed. Default is false.
+ */
+mxGraphView.prototype.updateStyle = false;
+
+/**
+ * Variable: lastNode
+ * 
+ * During validation, this contains the last DOM node that was processed.
+ */
+mxGraphView.prototype.lastNode = null;
+
+/**
+ * Variable: lastHtmlNode
+ * 
+ * During validation, this contains the last HTML DOM node that was processed.
+ */
+mxGraphView.prototype.lastHtmlNode = null;
+
+/**
+ * Variable: lastForegroundNode
+ * 
+ * During validation, this contains the last edge's DOM node that was processed.
+ */
+mxGraphView.prototype.lastForegroundNode = null;
+
+/**
+ * Variable: lastForegroundHtmlNode
+ * 
+ * During validation, this contains the last edge HTML DOM node that was processed.
+ */
+mxGraphView.prototype.lastForegroundHtmlNode = null;
+
+/**
+ * Function: getGraphBounds
+ *
+ * Returns <graphBounds>.
+ */
+mxGraphView.prototype.getGraphBounds = function()
+{
+	return this.graphBounds;
+};
+
+/**
+ * Function: setGraphBounds
+ *
+ * Sets <graphBounds>.
+ */
+mxGraphView.prototype.setGraphBounds = function(value)
+{
+	this.graphBounds = value;
+};
+
+/**
+ * Function: getBounds
+ * 
+ * Returns the union of all <mxCellStates> for the given array of <mxCells>.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> whose bounds should be returned.
+ */
+mxGraphView.prototype.getBounds = function(cells)
+{
+	var result = null;
+	
+	if (cells != null && cells.length > 0)
+	{
+		var model = this.graph.getModel();
+		
+		for (var i = 0; i < cells.length; i++)
+		{
+			if (model.isVertex(cells[i]) || model.isEdge(cells[i]))
+			{
+				var state = this.getState(cells[i]);
+			
+				if (state != null)
+				{
+					if (result == null)
+					{
+						result = mxRectangle.fromRectangle(state);
+					}
+					else
+					{
+						result.add(state);
+					}
+				}
+			}
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: setCurrentRoot
+ *
+ * Sets and returns the current root and fires an <undo> event before
+ * calling <mxGraph.sizeDidChange>.
+ *
+ * Parameters:
+ *
+ * root - <mxCell> that specifies the root of the displayed cell hierarchy.
+ */
+mxGraphView.prototype.setCurrentRoot = function(root)
+{
+	if (this.currentRoot != root)
+	{
+		var change = new mxCurrentRootChange(this, root);
+		change.execute();
+		var edit = new mxUndoableEdit(this, false);
+		edit.add(change);
+		this.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit));
+		this.graph.sizeDidChange();
+	}
+	
+	return root;
+};
+
+/**
+ * Function: scaleAndTranslate
+ *
+ * Sets the scale and translation and fires a <scale> and <translate> event
+ * before calling <revalidate> followed by <mxGraph.sizeDidChange>.
+ *
+ * Parameters:
+ *
+ * scale - Decimal value that specifies the new scale (1 is 100%).
+ * dx - X-coordinate of the translation.
+ * dy - Y-coordinate of the translation.
+ */
+mxGraphView.prototype.scaleAndTranslate = function(scale, dx, dy)
+{
+	var previousScale = this.scale;
+	var previousTranslate = new mxPoint(this.translate.x, this.translate.y);
+	
+	if (this.scale != scale || this.translate.x != dx || this.translate.y != dy)
+	{
+		this.scale = scale;
+		
+		this.translate.x = dx;
+		this.translate.y = dy;
+
+		if (this.isEventsEnabled())
+		{
+			this.revalidate();
+			this.graph.sizeDidChange();
+		}
+	}
+	
+	this.fireEvent(new mxEventObject(mxEvent.SCALE_AND_TRANSLATE,
+		'scale', scale, 'previousScale', previousScale,
+		'translate', this.translate, 'previousTranslate', previousTranslate));
+};
+
+/**
+ * Function: getScale
+ * 
+ * Returns the <scale>.
+ */
+mxGraphView.prototype.getScale = function()
+{
+	return this.scale;
+};
+
+/**
+ * Function: setScale
+ *
+ * Sets the scale and fires a <scale> event before calling <revalidate> followed
+ * by <mxGraph.sizeDidChange>.
+ *
+ * Parameters:
+ *
+ * value - Decimal value that specifies the new scale (1 is 100%).
+ */
+mxGraphView.prototype.setScale = function(value)
+{
+	var previousScale = this.scale;
+	
+	if (this.scale != value)
+	{
+		this.scale = value;
+
+		if (this.isEventsEnabled())
+		{
+			this.revalidate();
+			this.graph.sizeDidChange();
+		}
+	}
+	
+	this.fireEvent(new mxEventObject(mxEvent.SCALE,
+		'scale', value, 'previousScale', previousScale));
+};
+
+/**
+ * Function: getTranslate
+ * 
+ * Returns the <translate>.
+ */
+mxGraphView.prototype.getTranslate = function()
+{
+	return this.translate;
+};
+
+/**
+ * Function: setTranslate
+ *
+ * Sets the translation and fires a <translate> event before calling
+ * <revalidate> followed by <mxGraph.sizeDidChange>. The translation is the
+ * negative of the origin.
+ *
+ * Parameters:
+ *
+ * dx - X-coordinate of the translation.
+ * dy - Y-coordinate of the translation.
+ */
+mxGraphView.prototype.setTranslate = function(dx, dy)
+{
+	var previousTranslate = new mxPoint(this.translate.x, this.translate.y);
+	
+	if (this.translate.x != dx || this.translate.y != dy)
+	{
+		this.translate.x = dx;
+		this.translate.y = dy;
+
+		if (this.isEventsEnabled())
+		{
+			this.revalidate();
+			this.graph.sizeDidChange();
+		}
+	}
+	
+	this.fireEvent(new mxEventObject(mxEvent.TRANSLATE,
+		'translate', this.translate, 'previousTranslate', previousTranslate));
+};
+
+/**
+ * Function: refresh
+ *
+ * Clears the view if <currentRoot> is not null and revalidates.
+ */
+mxGraphView.prototype.refresh = function()
+{
+	if (this.currentRoot != null)
+	{
+		this.clear();
+	}
+	
+	this.revalidate();
+};
+
+/**
+ * Function: revalidate
+ *
+ * Revalidates the complete view with all cell states.
+ */
+mxGraphView.prototype.revalidate = function()
+{
+	this.invalidate();
+	this.validate();
+};
+
+/**
+ * Function: clear
+ *
+ * Removes the state of the given cell and all descendants if the given
+ * cell is not the current root.
+ * 
+ * Parameters:
+ * 
+ * cell - Optional <mxCell> for which the state should be removed. Default
+ * is the root of the model.
+ * force - Boolean indicating if the current root should be ignored for
+ * recursion.
+ */
+mxGraphView.prototype.clear = function(cell, force, recurse)
+{
+	var model = this.graph.getModel();
+	cell = cell || model.getRoot();
+	force = (force != null) ? force : false;
+	recurse = (recurse != null) ? recurse : true;
+	
+	this.removeState(cell);
+	
+	if (recurse && (force || cell != this.currentRoot))
+	{
+		var childCount = model.getChildCount(cell);
+		
+		for (var i = 0; i < childCount; i++)
+		{
+			this.clear(model.getChildAt(cell, i), force);
+		}
+	}
+	else
+	{
+		this.invalidate(cell);
+	}
+};
+
+/**
+ * Function: invalidate
+ * 
+ * Invalidates the state of the given cell, all its descendants and
+ * connected edges.
+ * 
+ * Parameters:
+ * 
+ * cell - Optional <mxCell> to be invalidated. Default is the root of the
+ * model.
+ */
+mxGraphView.prototype.invalidate = function(cell, recurse, includeEdges)
+{
+	var model = this.graph.getModel();
+	cell = cell || model.getRoot();
+	recurse = (recurse != null) ? recurse : true;
+	includeEdges = (includeEdges != null) ? includeEdges : true;
+	
+	var state = this.getState(cell);
+	
+	if (state != null)
+	{
+		state.invalid = true;
+	}
+	
+	// Avoids infinite loops for invalid graphs
+	if (!cell.invalidating)
+	{
+		cell.invalidating = true;
+		
+		// Recursively invalidates all descendants
+		if (recurse)
+		{
+			var childCount = model.getChildCount(cell);
+			
+			for (var i = 0; i < childCount; i++)
+			{
+				var child = model.getChildAt(cell, i);
+				this.invalidate(child, recurse, includeEdges);
+			}
+		}
+		
+		// Propagates invalidation to all connected edges
+		if (includeEdges)
+		{
+			var edgeCount = model.getEdgeCount(cell);
+			
+			for (var i = 0; i < edgeCount; i++)
+			{
+				this.invalidate(model.getEdgeAt(cell, i), recurse, includeEdges);
+			}
+		}
+		
+		delete cell.invalidating;
+	}
+};
+
+/**
+ * Function: validate
+ * 
+ * Calls <validateCell> and <validateCellState> and updates the <graphBounds>
+ * using <getBoundingBox>. Finally the background is validated using
+ * <validateBackground>.
+ * 
+ * Parameters:
+ * 
+ * cell - Optional <mxCell> to be used as the root of the validation.
+ * Default is <currentRoot> or the root of the model.
+ */
+mxGraphView.prototype.validate = function(cell)
+{
+	var t0 = mxLog.enter('mxGraphView.validate');
+	window.status = mxResources.get(this.updatingDocumentResource) ||
+		this.updatingDocumentResource;
+	
+	this.resetValidationState();
+	
+	// Improves IE rendering speed by minimizing reflows
+	var prevDisplay = null;
+	
+	if (this.optimizeVmlReflows && this.canvas != null && this.textDiv == null &&
+		((document.documentMode == 8 && !mxClient.IS_EM) || mxClient.IS_QUIRKS))
+	{
+		// Placeholder keeps scrollbar positions when canvas is hidden
+		this.placeholder = document.createElement('div');
+		this.placeholder.style.position = 'absolute';
+		this.placeholder.style.width = this.canvas.clientWidth + 'px';
+		this.placeholder.style.height = this.canvas.clientHeight + 'px';
+		this.canvas.parentNode.appendChild(this.placeholder);
+
+		prevDisplay = this.drawPane.style.display;
+		this.canvas.style.display = 'none';
+		
+		// Creates temporary DIV used for text measuring in mxText.updateBoundingBox
+		this.textDiv = document.createElement('div');
+		this.textDiv.style.position = 'absolute';
+		this.textDiv.style.whiteSpace = 'nowrap';
+		this.textDiv.style.visibility = 'hidden';
+		this.textDiv.style.display = (mxClient.IS_QUIRKS) ? 'inline' : 'inline-block';
+		this.textDiv.style.zoom = '1';
+		
+		document.body.appendChild(this.textDiv);
+	}
+	
+	var graphBounds = this.getBoundingBox(this.validateCellState(
+		this.validateCell(cell || ((this.currentRoot != null) ?
+			this.currentRoot : this.graph.getModel().getRoot()))));
+	this.setGraphBounds((graphBounds != null) ? graphBounds : this.getEmptyBounds());
+	this.validateBackground();
+	
+	if (prevDisplay != null)
+	{
+		this.canvas.style.display = prevDisplay;
+		this.textDiv.parentNode.removeChild(this.textDiv);
+		
+		if (this.placeholder != null)
+		{
+			this.placeholder.parentNode.removeChild(this.placeholder);
+		}
+				
+		// Textdiv cannot be reused
+		this.textDiv = null;
+	}
+	
+	this.resetValidationState();
+	
+	window.status = mxResources.get(this.doneResource) ||
+		this.doneResource;
+	mxLog.leave('mxGraphView.validate', t0);
+};
+
+/**
+ * Function: getEmptyBounds
+ * 
+ * Returns the bounds for an empty graph. This returns a rectangle at
+ * <translate> with the size of 0 x 0.
+ */
+mxGraphView.prototype.getEmptyBounds = function()
+{
+	return new mxRectangle(this.translate.x * this.scale, this.translate.y * this.scale);
+};
+
+/**
+ * Function: getBoundingBox
+ * 
+ * Returns the bounding box of the shape and the label for the given
+ * <mxCellState> and its children if recurse is true.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose bounding box should be returned.
+ * recurse - Optional boolean indicating if the children should be included.
+ * Default is true.
+ */
+mxGraphView.prototype.getBoundingBox = function(state, recurse)
+{
+	recurse = (recurse != null) ? recurse : true;
+	var bbox = null;
+	
+	if (state != null)
+	{
+		if (state.shape != null && state.shape.boundingBox != null)
+		{
+			bbox = state.shape.boundingBox.clone();
+		}
+		
+		// Adds label bounding box to graph bounds
+		if (state.text != null && state.text.boundingBox != null)
+		{
+			if (bbox != null)
+			{
+				bbox.add(state.text.boundingBox);
+			}
+			else
+			{
+				bbox = state.text.boundingBox.clone();
+			}
+		}
+		
+		if (recurse)
+		{
+			var model = this.graph.getModel();
+			var childCount = model.getChildCount(state.cell);
+			
+			for (var i = 0; i < childCount; i++)
+			{
+				var bounds = this.getBoundingBox(this.getState(model.getChildAt(state.cell, i)));
+				
+				if (bounds != null)
+				{
+					if (bbox == null)
+					{
+						bbox = bounds;
+					}
+					else
+					{
+						bbox.add(bounds);
+					}
+				}
+			}
+		}
+	}
+	
+	return bbox;
+};
+
+/**
+ * Function: createBackgroundPageShape
+ *
+ * Creates and returns the shape used as the background page.
+ * 
+ * Parameters:
+ * 
+ * bounds - <mxRectangle> that represents the bounds of the shape.
+ */
+mxGraphView.prototype.createBackgroundPageShape = function(bounds)
+{
+	return new mxRectangleShape(bounds, 'white', 'black');
+};
+
+/**
+ * Function: validateBackground
+ *
+ * Calls <validateBackgroundImage> and <validateBackgroundPage>.
+ */
+mxGraphView.prototype.validateBackground = function()
+{
+	this.validateBackgroundImage();
+	this.validateBackgroundPage();
+};
+
+/**
+ * Function: validateBackgroundImage
+ * 
+ * Validates the background image.
+ */
+mxGraphView.prototype.validateBackgroundImage = function()
+{
+	var bg = this.graph.getBackgroundImage();
+	
+	if (bg != null)
+	{
+		if (this.backgroundImage == null || this.backgroundImage.image != bg.src)
+		{
+			if (this.backgroundImage != null)
+			{
+				this.backgroundImage.destroy();
+			}
+			
+			var bounds = new mxRectangle(0, 0, 1, 1);
+			
+			this.backgroundImage = new mxImageShape(bounds, bg.src);
+			this.backgroundImage.dialect = this.graph.dialect;
+			this.backgroundImage.init(this.backgroundPane);
+			this.backgroundImage.redraw();
+
+			// Workaround for ignored event on background in IE8 standards mode
+			if (document.documentMode == 8 && !mxClient.IS_EM)
+			{
+				mxEvent.addGestureListeners(this.backgroundImage.node,
+					mxUtils.bind(this, function(evt)
+					{
+						this.graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt));
+					}),
+					mxUtils.bind(this, function(evt)
+					{
+						this.graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt));
+					}),
+					mxUtils.bind(this, function(evt)
+					{
+						this.graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt));
+					})
+				);
+			}
+		}
+		
+		this.redrawBackgroundImage(this.backgroundImage, bg);
+	}
+	else if (this.backgroundImage != null)
+	{
+		this.backgroundImage.destroy();
+		this.backgroundImage = null;
+	}
+};
+
+/**
+ * Function: validateBackgroundPage
+ * 
+ * Validates the background page.
+ */
+mxGraphView.prototype.validateBackgroundPage = function()
+{
+	if (this.graph.pageVisible)
+	{
+		var bounds = this.getBackgroundPageBounds();
+		
+		if (this.backgroundPageShape == null)
+		{
+			this.backgroundPageShape = this.createBackgroundPageShape(bounds);
+			this.backgroundPageShape.scale = this.scale;
+			this.backgroundPageShape.isShadow = true;
+			this.backgroundPageShape.dialect = this.graph.dialect;
+			this.backgroundPageShape.init(this.backgroundPane);
+			this.backgroundPageShape.redraw();
+			
+			// Adds listener for double click handling on background
+			if (this.graph.nativeDblClickEnabled)
+			{
+				mxEvent.addListener(this.backgroundPageShape.node, 'dblclick', mxUtils.bind(this, function(evt)
+				{
+					this.graph.dblClick(evt);
+				}));
+			}
+
+			// Adds basic listeners for graph event dispatching outside of the
+			// container and finishing the handling of a single gesture
+			mxEvent.addGestureListeners(this.backgroundPageShape.node,
+				mxUtils.bind(this, function(evt)
+				{
+					this.graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt));
+				}),
+				mxUtils.bind(this, function(evt)
+				{
+					// Hides the tooltip if mouse is outside container
+					if (this.graph.tooltipHandler != null && this.graph.tooltipHandler.isHideOnHover())
+					{
+						this.graph.tooltipHandler.hide();
+					}
+					
+					if (this.graph.isMouseDown && !mxEvent.isConsumed(evt))
+					{
+						this.graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt));
+					}
+				}),
+				mxUtils.bind(this, function(evt)
+				{
+					this.graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt));
+				})
+			);
+		}
+		else
+		{
+			this.backgroundPageShape.scale = this.scale;
+			this.backgroundPageShape.bounds = bounds;
+			this.backgroundPageShape.redraw();
+		}
+	}
+	else if (this.backgroundPageShape != null)
+	{
+		this.backgroundPageShape.destroy();
+		this.backgroundPageShape = null;
+	}
+};
+
+/**
+ * Function: getBackgroundPageBounds
+ * 
+ * Returns the bounds for the background page.
+ */
+mxGraphView.prototype.getBackgroundPageBounds = function()
+{
+	var fmt = this.graph.pageFormat;
+	var ps = this.scale * this.graph.pageScale;
+	var bounds = new mxRectangle(this.scale * this.translate.x, this.scale * this.translate.y,
+			fmt.width * ps, fmt.height * ps);
+	
+	return bounds;
+};
+
+/**
+ * Function: redrawBackgroundImage
+ *
+ * Updates the bounds and redraws the background image.
+ * 
+ * Example:
+ * 
+ * If the background image should not be scaled, this can be replaced with
+ * the following.
+ * 
+ * (code)
+ * mxGraphView.prototype.redrawBackground = function(backgroundImage, bg)
+ * {
+ *   backgroundImage.bounds.x = this.translate.x;
+ *   backgroundImage.bounds.y = this.translate.y;
+ *   backgroundImage.bounds.width = bg.width;
+ *   backgroundImage.bounds.height = bg.height;
+ *
+ *   backgroundImage.redraw();
+ * };
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * backgroundImage - <mxImageShape> that represents the background image.
+ * bg - <mxImage> that specifies the image and its dimensions.
+ */
+mxGraphView.prototype.redrawBackgroundImage = function(backgroundImage, bg)
+{
+	backgroundImage.scale = this.scale;
+	backgroundImage.bounds.x = this.scale * this.translate.x;
+	backgroundImage.bounds.y = this.scale * this.translate.y;
+	backgroundImage.bounds.width = this.scale * bg.width;
+	backgroundImage.bounds.height = this.scale * bg.height;
+
+	backgroundImage.redraw();
+};
+
+/**
+ * Function: validateCell
+ * 
+ * Recursively creates the cell state for the given cell if visible is true and
+ * the given cell is visible. If the cell is not visible but the state exists
+ * then it is removed using <removeState>.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose <mxCellState> should be created.
+ * visible - Optional boolean indicating if the cell should be visible. Default
+ * is true.
+ */
+mxGraphView.prototype.validateCell = function(cell, visible)
+{
+	visible = (visible != null) ? visible : true;
+	
+	if (cell != null)
+	{
+		visible = visible && this.graph.isCellVisible(cell);
+		var state = this.getState(cell, visible);
+		
+		if (state != null && !visible)
+		{
+			this.removeState(cell);
+		}
+		else
+		{
+			var model = this.graph.getModel();
+			var childCount = model.getChildCount(cell);
+			
+			for (var i = 0; i < childCount; i++)
+			{
+				this.validateCell(model.getChildAt(cell, i), visible &&
+					(!this.isCellCollapsed(cell) || cell == this.currentRoot));
+			}
+		}
+	}
+	
+	return cell;
+};
+
+/**
+ * Function: validateCellState
+ * 
+ * Validates and repaints the <mxCellState> for the given <mxCell>.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> whose <mxCellState> should be validated.
+ * recurse - Optional boolean indicating if the children of the cell should be
+ * validated. Default is true.
+ */
+mxGraphView.prototype.validateCellState = function(cell, recurse)
+{
+	recurse = (recurse != null) ? recurse : true;
+	var state = null;
+	
+	if (cell != null)
+	{
+		state = this.getState(cell);
+		
+		if (state != null)
+		{
+			var model = this.graph.getModel();
+			
+			if (state.invalid)
+			{
+				state.invalid = false;
+				
+				if (state.style == null)
+				{
+					state.style = this.graph.getCellStyle(state.cell);
+				}
+				
+				if (cell != this.currentRoot)
+				{
+					this.validateCellState(model.getParent(cell), false);
+				}
+
+				state.setVisibleTerminalState(this.validateCellState(this.getVisibleTerminal(cell, true), false), true);
+				state.setVisibleTerminalState(this.validateCellState(this.getVisibleTerminal(cell, false), false), false);
+				
+				this.updateCellState(state);
+				
+				// Repaint happens immediately after the cell is validated
+				if (cell != this.currentRoot && !state.invalid)
+				{
+					this.graph.cellRenderer.redraw(state, false, this.isRendering());
+
+					// Handles changes to invertex paintbounds after update of rendering shape
+					state.updateCachedBounds();
+				}
+			}
+
+			if (recurse && !state.invalid)
+			{
+				// Updates order in DOM if recursively traversing
+				if (state.shape != null)
+				{
+					this.stateValidated(state);
+				}
+			
+				var childCount = model.getChildCount(cell);
+				
+				for (var i = 0; i < childCount; i++)
+				{
+					this.validateCellState(model.getChildAt(cell, i));
+				}
+			}
+		}
+	}
+	
+	return state;
+};
+
+/**
+ * Function: updateCellState
+ * 
+ * Updates the given <mxCellState>.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> to be updated.
+ */
+mxGraphView.prototype.updateCellState = function(state)
+{
+	state.absoluteOffset.x = 0;
+	state.absoluteOffset.y = 0;
+	state.origin.x = 0;
+	state.origin.y = 0;
+	state.length = 0;
+	
+	if (state.cell != this.currentRoot)
+	{
+		var model = this.graph.getModel();
+		var pState = this.getState(model.getParent(state.cell)); 
+		
+		if (pState != null && pState.cell != this.currentRoot)
+		{
+			state.origin.x += pState.origin.x;
+			state.origin.y += pState.origin.y;
+		}
+		
+		var offset = this.graph.getChildOffsetForCell(state.cell);
+		
+		if (offset != null)
+		{
+			state.origin.x += offset.x;
+			state.origin.y += offset.y;
+		}
+		
+		var geo = this.graph.getCellGeometry(state.cell);				
+	
+		if (geo != null)
+		{
+			if (!model.isEdge(state.cell))
+			{
+				offset = geo.offset || this.EMPTY_POINT;
+	
+				if (geo.relative && pState != null)
+				{
+					if (model.isEdge(pState.cell))
+					{
+						var origin = this.getPoint(pState, geo);
+
+						if (origin != null)
+						{
+							state.origin.x += (origin.x / this.scale) - pState.origin.x - this.translate.x;
+							state.origin.y += (origin.y / this.scale) - pState.origin.y - this.translate.y;
+						}
+					}
+					else
+					{
+						state.origin.x += geo.x * pState.width / this.scale + offset.x;
+						state.origin.y += geo.y * pState.height / this.scale + offset.y;
+					}
+				}
+				else
+				{
+					state.absoluteOffset.x = this.scale * offset.x;
+					state.absoluteOffset.y = this.scale * offset.y;
+					state.origin.x += geo.x;
+					state.origin.y += geo.y;
+				}
+			}
+	
+			state.x = this.scale * (this.translate.x + state.origin.x);
+			state.y = this.scale * (this.translate.y + state.origin.y);
+			state.width = this.scale * geo.width;
+			state.unscaledWidth = geo.width;
+			state.height = this.scale * geo.height;
+			
+			if (model.isVertex(state.cell))
+			{
+				this.updateVertexState(state, geo);
+			}
+			
+			if (model.isEdge(state.cell))
+			{
+				this.updateEdgeState(state, geo);
+			}
+		}
+	}
+
+	state.updateCachedBounds();
+};
+
+/**
+ * Function: isCellCollapsed
+ * 
+ * Returns true if the children of the given cell should not be visible in the
+ * view. This implementation uses <mxGraph.isCellVisible> but it can be
+ * overidden to use a separate condition.
+ */
+mxGraphView.prototype.isCellCollapsed = function(cell)
+{
+	return this.graph.isCellCollapsed(cell);
+};
+
+/**
+ * Function: updateVertexState
+ * 
+ * Validates the given cell state.
+ */
+mxGraphView.prototype.updateVertexState = function(state, geo)
+{
+	var model = this.graph.getModel();
+	var pState = this.getState(model.getParent(state.cell));
+	
+	if (geo.relative && pState != null && !model.isEdge(pState.cell))
+	{
+		var alpha = mxUtils.toRadians(pState.style[mxConstants.STYLE_ROTATION] || '0');
+		
+		if (alpha != 0)
+		{
+			var cos = Math.cos(alpha);
+			var sin = Math.sin(alpha);
+
+			var ct = new mxPoint(state.getCenterX(), state.getCenterY());
+			var cx = new mxPoint(pState.getCenterX(), pState.getCenterY());
+			var pt = mxUtils.getRotatedPoint(ct, cos, sin, cx);
+			state.x = pt.x - state.width / 2;
+			state.y = pt.y - state.height / 2;
+		}
+	}
+	
+	this.updateVertexLabelOffset(state);
+};
+
+/**
+ * Function: updateEdgeState
+ * 
+ * Validates the given cell state.
+ */
+mxGraphView.prototype.updateEdgeState = function(state, geo)
+{
+	var source = state.getVisibleTerminalState(true);
+	var target = state.getVisibleTerminalState(false);
+	
+	// This will remove edges with no terminals and no terminal points
+	// as such edges are invalid and produce NPEs in the edge styles.
+	// Also removes connected edges that have no visible terminals.
+	if ((this.graph.model.getTerminal(state.cell, true) != null && source == null) ||
+		(source == null && geo.getTerminalPoint(true) == null) ||
+		(this.graph.model.getTerminal(state.cell, false) != null && target == null) ||
+		(target == null && geo.getTerminalPoint(false) == null))
+	{
+		this.clear(state.cell, true);
+	}
+	else
+	{
+		this.updateFixedTerminalPoints(state, source, target);
+		this.updatePoints(state, geo.points, source, target);
+		this.updateFloatingTerminalPoints(state, source, target);
+		
+		var pts = state.absolutePoints;
+		
+		if (state.cell != this.currentRoot && (pts == null || pts.length < 2 ||
+			pts[0] == null || pts[pts.length - 1] == null))
+		{
+			// This will remove edges with invalid points from the list of states in the view.
+			// Happens if the one of the terminals and the corresponding terminal point is null.
+			this.clear(state.cell, true);
+		}
+		else
+		{
+			this.updateEdgeBounds(state);
+			this.updateEdgeLabelOffset(state);
+		}
+	}
+};
+
+/**
+ * Function: updateVertexLabelOffset
+ * 
+ * Updates the absoluteOffset of the given vertex cell state. This takes
+ * into account the label position styles.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose absolute offset should be updated.
+ */
+mxGraphView.prototype.updateVertexLabelOffset = function(state)
+{
+	var h = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);
+
+	if (h == mxConstants.ALIGN_LEFT)
+	{
+		var lw = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_WIDTH, null);
+		
+		if (lw != null)
+		{
+			lw *= this.scale;
+		}
+		else
+		{
+			lw = state.width;
+		}
+		
+		state.absoluteOffset.x -= lw;
+	}
+	else if (h == mxConstants.ALIGN_RIGHT)
+	{
+		state.absoluteOffset.x += state.width;
+	}
+	else if (h == mxConstants.ALIGN_CENTER)
+	{
+		var lw = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_WIDTH, null);
+		
+		if (lw != null)
+		{
+			// Aligns text block with given width inside the vertex width
+			var align = mxUtils.getValue(state.style, mxConstants.STYLE_ALIGN, mxConstants.ALIGN_CENTER);
+			var dx = 0;
+			
+			if (align == mxConstants.ALIGN_CENTER)
+			{
+				dx = 0.5;
+			}
+			else if (align == mxConstants.ALIGN_RIGHT)
+			{
+				dx = 1;
+			}
+			
+			if (dx != 0)
+			{
+				state.absoluteOffset.x -= (lw * this.scale - state.width) * dx;
+			}
+		}
+	}
+	
+	var v = mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);
+	
+	if (v == mxConstants.ALIGN_TOP)
+	{
+		state.absoluteOffset.y -= state.height;
+	}
+	else if (v == mxConstants.ALIGN_BOTTOM)
+	{
+		state.absoluteOffset.y += state.height;
+	}
+};
+
+/**
+ * Function: resetValidationState
+ *
+ * Resets the current validation state.
+ */
+mxGraphView.prototype.resetValidationState = function()
+{
+	this.lastNode = null;
+	this.lastHtmlNode = null;
+	this.lastForegroundNode = null;
+	this.lastForegroundHtmlNode = null;
+};
+
+/**
+ * Function: stateValidated
+ * 
+ * Invoked when a state has been processed in <validatePoints>. This is used
+ * to update the order of the DOM nodes of the shape.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> that represents the cell state.
+ */
+mxGraphView.prototype.stateValidated = function(state)
+{
+	var fg = (this.graph.getModel().isEdge(state.cell) && this.graph.keepEdgesInForeground) ||
+		(this.graph.getModel().isVertex(state.cell) && this.graph.keepEdgesInBackground);
+	var htmlNode = (fg) ? this.lastForegroundHtmlNode || this.lastHtmlNode : this.lastHtmlNode;
+	var node = (fg) ? this.lastForegroundNode || this.lastNode : this.lastNode;
+	var result = this.graph.cellRenderer.insertStateAfter(state, node, htmlNode);
+
+	if (fg)
+	{
+		this.lastForegroundHtmlNode = result[1];
+		this.lastForegroundNode = result[0];
+	}
+	else
+	{
+		this.lastHtmlNode = result[1];
+		this.lastNode = result[0];
+	}
+};
+
+/**
+ * Function: updateFixedTerminalPoints
+ *
+ * Sets the initial absolute terminal points in the given state before the edge
+ * style is computed.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCellState> whose initial terminal points should be updated.
+ * source - <mxCellState> which represents the source terminal.
+ * target - <mxCellState> which represents the target terminal.
+ */
+mxGraphView.prototype.updateFixedTerminalPoints = function(edge, source, target)
+{
+	this.updateFixedTerminalPoint(edge, source, true,
+		this.graph.getConnectionConstraint(edge, source, true));
+	this.updateFixedTerminalPoint(edge, target, false,
+		this.graph.getConnectionConstraint(edge, target, false));
+};
+
+/**
+ * Function: updateFixedTerminalPoint
+ *
+ * Sets the fixed source or target terminal point on the given edge.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCellState> whose terminal point should be updated.
+ * terminal - <mxCellState> which represents the actual terminal.
+ * source - Boolean that specifies if the terminal is the source.
+ * constraint - <mxConnectionConstraint> that specifies the connection.
+ */
+mxGraphView.prototype.updateFixedTerminalPoint = function(edge, terminal, source, constraint)
+{
+	edge.setAbsoluteTerminalPoint(this.getFixedTerminalPoint(edge, terminal, source, constraint), source);
+};
+
+/**
+ * Function: getFixedTerminalPoint
+ *
+ * Returns the fixed source or target terminal point for the given edge.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCellState> whose terminal point should be returned.
+ * terminal - <mxCellState> which represents the actual terminal.
+ * source - Boolean that specifies if the terminal is the source.
+ * constraint - <mxConnectionConstraint> that specifies the connection.
+ */
+mxGraphView.prototype.getFixedTerminalPoint = function(edge, terminal, source, constraint)
+{
+	var pt = null;
+	
+	if (constraint != null)
+	{
+		pt = this.graph.getConnectionPoint(terminal, constraint);
+	}
+	
+	if (pt == null && terminal == null)
+	{
+		var s = this.scale;
+		var tr = this.translate;
+		var orig = edge.origin;
+		var geo = this.graph.getCellGeometry(edge.cell);
+		pt = geo.getTerminalPoint(source);
+		
+		if (pt != null)
+		{
+			pt = new mxPoint(s * (tr.x + pt.x + orig.x),
+							 s * (tr.y + pt.y + orig.y));
+		}
+	}
+	
+	return pt;
+};
+
+/**
+ * Function: updateBoundsFromStencil
+ * 
+ * Updates the bounds of the given cell state to reflect the bounds of the stencil
+ * if it has a fixed aspect and returns the previous bounds as an <mxRectangle> if
+ * the bounds have been modified or null otherwise.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCellState> whose bounds should be updated.
+ */
+mxGraphView.prototype.updateBoundsFromStencil = function(state)
+{
+	var previous = null;
+	
+	if (state != null && state.shape != null && state.shape.stencil != null && state.shape.stencil.aspect == 'fixed')
+	{
+		previous = mxRectangle.fromRectangle(state);
+		var asp = state.shape.stencil.computeAspect(state.style, state.x, state.y, state.width, state.height);
+		state.setRect(asp.x, asp.y, state.shape.stencil.w0 * asp.width, state.shape.stencil.h0 * asp.height);
+	}
+	
+	return previous;
+};
+
+/**
+ * Function: updatePoints
+ *
+ * Updates the absolute points in the given state using the specified array
+ * of <mxPoints> as the relative points.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCellState> whose absolute points should be updated.
+ * points - Array of <mxPoints> that constitute the relative points.
+ * source - <mxCellState> that represents the source terminal.
+ * target - <mxCellState> that represents the target terminal.
+ */
+mxGraphView.prototype.updatePoints = function(edge, points, source, target)
+{
+	if (edge != null)
+	{
+		var pts = [];
+		pts.push(edge.absolutePoints[0]);
+		var edgeStyle = this.getEdgeStyle(edge, points, source, target);
+		
+		if (edgeStyle != null)
+		{
+			var src = this.getTerminalPort(edge, source, true);
+			var trg = this.getTerminalPort(edge, target, false);
+			
+			// Uses the stencil bounds for routing and restores after routing
+			var srcBounds = this.updateBoundsFromStencil(src);
+			var trgBounds = this.updateBoundsFromStencil(trg);
+
+			edgeStyle(edge, src, trg, points, pts);
+			
+			// Restores previous bounds
+			if (srcBounds != null)
+			{
+				src.setRect(srcBounds.x, srcBounds.y, srcBounds.width, srcBounds.height);
+			}
+			
+			if (trgBounds != null)
+			{
+				trg.setRect(trgBounds.x, trgBounds.y, trgBounds.width, trgBounds.height);
+			}
+		}
+		else if (points != null)
+		{
+			for (var i = 0; i < points.length; i++)
+			{
+				if (points[i] != null)
+				{
+					var pt = mxUtils.clone(points[i]);
+					pts.push(this.transformControlPoint(edge, pt));
+				}
+			}
+		}
+		
+		var tmp = edge.absolutePoints;
+		pts.push(tmp[tmp.length-1]);
+
+		edge.absolutePoints = pts;
+	}
+};
+
+/**
+ * Function: transformControlPoint
+ *
+ * Transforms the given control point to an absolute point.
+ */
+mxGraphView.prototype.transformControlPoint = function(state, pt)
+{
+	if (state != null && pt != null)
+	{
+		var orig = state.origin;
+		
+	    return new mxPoint(this.scale * (pt.x + this.translate.x + orig.x),
+	    	this.scale * (pt.y + this.translate.y + orig.y));
+	}
+	
+	return null;
+};
+
+/**
+ * Function: isLoopStyleEnabled
+ * 
+ * Returns true if the given edge should be routed with <mxGraph.defaultLoopStyle>
+ * or the <mxConstants.STYLE_LOOP> defined for the given edge. This implementation
+ * returns true if the given edge is a loop and does not 
+ */
+mxGraphView.prototype.isLoopStyleEnabled = function(edge, points, source, target)
+{
+	var sc = this.graph.getConnectionConstraint(edge, source, true);
+	var tc = this.graph.getConnectionConstraint(edge, target, false);
+	
+	if (!mxUtils.getValue(edge.style, mxConstants.STYLE_ORTHOGONAL_LOOP, false) ||
+		((sc == null || sc.point == null) && (tc == null || tc.point == null)))
+	{
+		return source != null && source == target;
+	}
+	
+	return false;
+};
+
+/**
+ * Function: getEdgeStyle
+ * 
+ * Returns the edge style function to be used to render the given edge state.
+ */
+mxGraphView.prototype.getEdgeStyle = function(edge, points, source, target)
+{
+	var edgeStyle = this.isLoopStyleEnabled(edge, points, source, target) ?
+		mxUtils.getValue(edge.style, mxConstants.STYLE_LOOP, this.graph.defaultLoopStyle) :
+		(!mxUtils.getValue(edge.style, mxConstants.STYLE_NOEDGESTYLE, false) ?
+		edge.style[mxConstants.STYLE_EDGE] : null);
+
+	// Converts string values to objects
+	if (typeof(edgeStyle) == "string")
+	{
+		var tmp = mxStyleRegistry.getValue(edgeStyle);
+		
+		if (tmp == null && this.isAllowEval())
+		{
+ 			tmp = mxUtils.eval(edgeStyle);
+		}
+		
+		edgeStyle = tmp;
+	}
+	
+	if (typeof(edgeStyle) == "function")
+	{
+		return edgeStyle;
+	}
+	
+	return null;
+};
+
+/**
+ * Function: updateFloatingTerminalPoints
+ *
+ * Updates the terminal points in the given state after the edge style was
+ * computed for the edge.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose terminal points should be updated.
+ * source - <mxCellState> that represents the source terminal.
+ * target - <mxCellState> that represents the target terminal.
+ */
+mxGraphView.prototype.updateFloatingTerminalPoints = function(state, source, target)
+{
+	var pts = state.absolutePoints;
+	var p0 = pts[0];
+	var pe = pts[pts.length - 1];
+
+	if (pe == null && target != null)
+	{
+		this.updateFloatingTerminalPoint(state, target, source, false);
+	}
+	
+	if (p0 == null && source != null)
+	{
+		this.updateFloatingTerminalPoint(state, source, target, true);
+	}
+};
+
+/**
+ * Function: updateFloatingTerminalPoint
+ *
+ * Updates the absolute terminal point in the given state for the given
+ * start and end state, where start is the source if source is true.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCellState> whose terminal point should be updated.
+ * start - <mxCellState> for the terminal on "this" side of the edge.
+ * end - <mxCellState> for the terminal on the other side of the edge.
+ * source - Boolean indicating if start is the source terminal state.
+ */
+mxGraphView.prototype.updateFloatingTerminalPoint = function(edge, start, end, source)
+{
+	edge.setAbsoluteTerminalPoint(this.getFloatingTerminalPoint(edge, start, end, source), source);
+};
+
+/**
+ * Function: getFloatingTerminalPoint
+ * 
+ * Returns the floating terminal point for the given edge, start and end
+ * state, where start is the source if source is true.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCellState> whose terminal point should be returned.
+ * start - <mxCellState> for the terminal on "this" side of the edge.
+ * end - <mxCellState> for the terminal on the other side of the edge.
+ * source - Boolean indicating if start is the source terminal state.
+ */
+mxGraphView.prototype.getFloatingTerminalPoint = function(edge, start, end, source)
+{
+	start = this.getTerminalPort(edge, start, source);
+	var next = this.getNextPoint(edge, end, source);
+	
+	var orth = this.graph.isOrthogonal(edge);
+	var alpha = mxUtils.toRadians(Number(start.style[mxConstants.STYLE_ROTATION] || '0'));
+	var center = new mxPoint(start.getCenterX(), start.getCenterY());
+	
+	if (alpha != 0)
+	{
+		var cos = Math.cos(-alpha);
+		var sin = Math.sin(-alpha);
+		next = mxUtils.getRotatedPoint(next, cos, sin, center);
+	}
+	
+	var border = parseFloat(edge.style[mxConstants.STYLE_PERIMETER_SPACING] || 0);
+	border += parseFloat(edge.style[(source) ?
+		mxConstants.STYLE_SOURCE_PERIMETER_SPACING :
+		mxConstants.STYLE_TARGET_PERIMETER_SPACING] || 0);
+	var pt = this.getPerimeterPoint(start, next, alpha == 0 && orth, border);
+
+	if (alpha != 0)
+	{
+		var cos = Math.cos(alpha);
+		var sin = Math.sin(alpha);
+		pt = mxUtils.getRotatedPoint(pt, cos, sin, center);
+	}
+	
+	return pt;
+};
+
+/**
+ * Function: getTerminalPort
+ * 
+ * Returns an <mxCellState> that represents the source or target terminal or
+ * port for the given edge.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> that represents the state of the edge.
+ * terminal - <mxCellState> that represents the terminal.
+ * source - Boolean indicating if the given terminal is the source terminal.
+ */
+mxGraphView.prototype.getTerminalPort = function(state, terminal, source)
+{
+	var key = (source) ? mxConstants.STYLE_SOURCE_PORT :
+		mxConstants.STYLE_TARGET_PORT;
+	var id = mxUtils.getValue(state.style, key);
+	
+	if (id != null)
+	{
+		var tmp = this.getState(this.graph.getModel().getCell(id));
+		
+		// Only uses ports where a cell state exists
+		if (tmp != null)
+		{
+			terminal = tmp;
+		}
+	}
+	
+	return terminal;
+};
+
+/**
+ * Function: getPerimeterPoint
+ *
+ * Returns an <mxPoint> that defines the location of the intersection point between
+ * the perimeter and the line between the center of the shape and the given point.
+ * 
+ * Parameters:
+ * 
+ * terminal - <mxCellState> for the source or target terminal.
+ * next - <mxPoint> that lies outside of the given terminal.
+ * orthogonal - Boolean that specifies if the orthogonal projection onto
+ * the perimeter should be returned. If this is false then the intersection
+ * of the perimeter and the line between the next and the center point is
+ * returned.
+ * border - Optional border between the perimeter and the shape.
+ */
+mxGraphView.prototype.getPerimeterPoint = function(terminal, next, orthogonal, border)
+{
+	var point = null;
+	
+	if (terminal != null)
+	{
+		var perimeter = this.getPerimeterFunction(terminal);
+		
+		if (perimeter != null && next != null)
+		{
+			var bounds = this.getPerimeterBounds(terminal, border);
+
+			if (bounds.width > 0 || bounds.height > 0)
+			{
+				point = new mxPoint(next.x, next.y);
+				var flipH = false;
+				var flipV = false;	
+				
+				if (this.graph.model.isVertex(terminal.cell))
+				{
+					flipH = mxUtils.getValue(terminal.style, mxConstants.STYLE_FLIPH, 0) == 1;
+					flipV = mxUtils.getValue(terminal.style, mxConstants.STYLE_FLIPV, 0) == 1;	
+	
+					// Legacy support for stencilFlipH/V
+					if (terminal.shape != null && terminal.shape.stencil != null)
+					{
+						flipH = (mxUtils.getValue(terminal.style, 'stencilFlipH', 0) == 1) || flipH;
+						flipV = (mxUtils.getValue(terminal.style, 'stencilFlipV', 0) == 1) || flipV;
+					}
+	
+					if (flipH)
+					{
+						point.x = 2 * bounds.getCenterX() - point.x;
+					}
+					
+					if (flipV)
+					{
+						point.y = 2 * bounds.getCenterY() - point.y;
+					}
+				}
+				
+				point = perimeter(bounds, terminal, point, orthogonal);
+
+				if (point != null)
+				{
+					if (flipH)
+					{
+						point.x = 2 * bounds.getCenterX() - point.x;
+					}
+					
+					if (flipV)
+					{
+						point.y = 2 * bounds.getCenterY() - point.y;
+					}
+				}
+			}
+		}
+		
+		if (point == null)
+		{
+			point = this.getPoint(terminal);
+		}
+	}
+	
+	return point;
+};
+
+/**
+ * Function: getRoutingCenterX
+ * 
+ * Returns the x-coordinate of the center point for automatic routing.
+ */
+mxGraphView.prototype.getRoutingCenterX = function (state)
+{
+	var f = (state.style != null) ? parseFloat(state.style
+		[mxConstants.STYLE_ROUTING_CENTER_X]) || 0 : 0;
+
+	return state.getCenterX() + f * state.width;
+};
+
+/**
+ * Function: getRoutingCenterY
+ * 
+ * Returns the y-coordinate of the center point for automatic routing.
+ */
+mxGraphView.prototype.getRoutingCenterY = function (state)
+{
+	var f = (state.style != null) ? parseFloat(state.style
+		[mxConstants.STYLE_ROUTING_CENTER_Y]) || 0 : 0;
+
+	return state.getCenterY() + f * state.height;
+};
+
+/**
+ * Function: getPerimeterBounds
+ *
+ * Returns the perimeter bounds for the given terminal, edge pair as an
+ * <mxRectangle>.
+ * 
+ * If you have a model where each terminal has a relative child that should
+ * act as the graphical endpoint for a connection from/to the terminal, then
+ * this method can be replaced as follows:
+ * 
+ * (code)
+ * var oldGetPerimeterBounds = mxGraphView.prototype.getPerimeterBounds;
+ * mxGraphView.prototype.getPerimeterBounds = function(terminal, edge, isSource)
+ * {
+ *   var model = this.graph.getModel();
+ *   var childCount = model.getChildCount(terminal.cell);
+ * 
+ *   if (childCount > 0)
+ *   {
+ *     var child = model.getChildAt(terminal.cell, 0);
+ *     var geo = model.getGeometry(child);
+ *
+ *     if (geo != null &&
+ *         geo.relative)
+ *     {
+ *       var state = this.getState(child);
+ *       
+ *       if (state != null)
+ *       {
+ *         terminal = state;
+ *       }
+ *     }
+ *   }
+ *   
+ *   return oldGetPerimeterBounds.apply(this, arguments);
+ * };
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * terminal - <mxCellState> that represents the terminal.
+ * border - Number that adds a border between the shape and the perimeter.
+ */
+mxGraphView.prototype.getPerimeterBounds = function(terminal, border)
+{
+	border = (border != null) ? border : 0;
+
+	if (terminal != null)
+	{
+		border += parseFloat(terminal.style[mxConstants.STYLE_PERIMETER_SPACING] || 0);
+	}
+
+	return terminal.getPerimeterBounds(border * this.scale);
+};
+
+/**
+ * Function: getPerimeterFunction
+ *
+ * Returns the perimeter function for the given state.
+ */
+mxGraphView.prototype.getPerimeterFunction = function(state)
+{
+	var perimeter = state.style[mxConstants.STYLE_PERIMETER];
+
+	// Converts string values to objects
+	if (typeof(perimeter) == "string")
+	{
+		var tmp = mxStyleRegistry.getValue(perimeter);
+		
+		if (tmp == null && this.isAllowEval())
+		{
+ 			tmp = mxUtils.eval(perimeter);
+		}
+
+		perimeter = tmp;
+	}
+	
+	if (typeof(perimeter) == "function")
+	{
+		return perimeter;
+	}
+	
+	return null;
+};
+
+/**
+ * Function: getNextPoint
+ *
+ * Returns the nearest point in the list of absolute points or the center
+ * of the opposite terminal.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCellState> that represents the edge.
+ * opposite - <mxCellState> that represents the opposite terminal.
+ * source - Boolean indicating if the next point for the source or target
+ * should be returned.
+ */
+mxGraphView.prototype.getNextPoint = function(edge, opposite, source)
+{
+	var pts = edge.absolutePoints;
+	var point = null;
+	
+	if (pts != null && pts.length >= 2)
+	{
+		var count = pts.length;
+		point = pts[(source) ? Math.min(1, count - 1) : Math.max(0, count - 2)];
+	}
+	
+	if (point == null && opposite != null)
+	{
+		point = new mxPoint(opposite.getCenterX(), opposite.getCenterY());
+	}
+	
+	return point;
+};
+
+/**
+ * Function: getVisibleTerminal
+ *
+ * Returns the nearest ancestor terminal that is visible. The edge appears
+ * to be connected to this terminal on the display. The result of this method
+ * is cached in <mxCellState.getVisibleTerminalState>.
+ * 
+ * Parameters:
+ * 
+ * edge - <mxCell> whose visible terminal should be returned.
+ * source - Boolean that specifies if the source or target terminal
+ * should be returned.
+ */
+mxGraphView.prototype.getVisibleTerminal = function(edge, source)
+{
+	var model = this.graph.getModel();
+	var result = model.getTerminal(edge, source);
+	var best = result;
+	
+	while (result != null && result != this.currentRoot)
+	{
+		if (!this.graph.isCellVisible(best) || this.isCellCollapsed(result))
+		{
+			best = result;
+		}
+		
+		result = model.getParent(result);
+	}
+
+	// Checks if the result is not a layer
+	if (model.getParent(best) == model.getRoot())
+	{
+		best = null;
+	}
+	
+	return best;
+};
+
+/**
+ * Function: updateEdgeBounds
+ *
+ * Updates the given state using the bounding box of t
+ * he absolute points.
+ * Also updates <mxCellState.terminalDistance>, <mxCellState.length> and
+ * <mxCellState.segments>.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose bounds should be updated.
+ */
+mxGraphView.prototype.updateEdgeBounds = function(state)
+{
+	var points = state.absolutePoints;
+	var p0 = points[0];
+	var pe = points[points.length - 1];
+	
+	if (p0.x != pe.x || p0.y != pe.y)
+	{
+		var dx = pe.x - p0.x;
+		var dy = pe.y - p0.y;
+		state.terminalDistance = Math.sqrt(dx * dx + dy * dy);
+	}
+	else
+	{
+		state.terminalDistance = 0;
+	}
+	
+	var length = 0;
+	var segments = [];
+	var pt = p0;
+	
+	if (pt != null)
+	{
+		var minX = pt.x;
+		var minY = pt.y;
+		var maxX = minX;
+		var maxY = minY;
+		
+		for (var i = 1; i < points.length; i++)
+		{
+			var tmp = points[i];
+			
+			if (tmp != null)
+			{
+				var dx = pt.x - tmp.x;
+				var dy = pt.y - tmp.y;
+				
+				var segment = Math.sqrt(dx * dx + dy * dy);
+				segments.push(segment);
+				length += segment;
+				
+				pt = tmp;
+				
+				minX = Math.min(pt.x, minX);
+				minY = Math.min(pt.y, minY);
+				maxX = Math.max(pt.x, maxX);
+				maxY = Math.max(pt.y, maxY);
+			}
+		}
+		
+		state.length = length;
+		state.segments = segments;
+		
+		var markerSize = 1; // TODO: include marker size
+		
+		state.x = minX;
+		state.y = minY;
+		state.width = Math.max(markerSize, maxX - minX);
+		state.height = Math.max(markerSize, maxY - minY);
+	}
+};
+
+/**
+ * Function: getPoint
+ *
+ * Returns the absolute point on the edge for the given relative
+ * <mxGeometry> as an <mxPoint>. The edge is represented by the given
+ * <mxCellState>.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> that represents the state of the parent edge.
+ * geometry - <mxGeometry> that represents the relative location.
+ */
+mxGraphView.prototype.getPoint = function(state, geometry)
+{
+	var x = state.getCenterX();
+	var y = state.getCenterY();
+	
+	if (state.segments != null && (geometry == null || geometry.relative))
+	{
+		var gx = (geometry != null) ? geometry.x / 2 : 0;
+		var pointCount = state.absolutePoints.length;
+		var dist = Math.round((gx + 0.5) * state.length);
+		var segment = state.segments[0];
+		var length = 0;				
+		var index = 1;
+
+		while (dist >= Math.round(length + segment) && index < pointCount - 1)
+		{
+			length += segment;
+			segment = state.segments[index++];
+		}
+
+		var factor = (segment == 0) ? 0 : (dist - length) / segment;
+		var p0 = state.absolutePoints[index-1];
+		var pe = state.absolutePoints[index];
+
+		if (p0 != null && pe != null)
+		{
+			var gy = 0;
+			var offsetX = 0;
+			var offsetY = 0;
+
+			if (geometry != null)
+			{
+				gy = geometry.y;
+				var offset = geometry.offset;
+				
+				if (offset != null)
+				{
+					offsetX = offset.x;
+					offsetY = offset.y;
+				}
+			}
+
+			var dx = pe.x - p0.x;
+			var dy = pe.y - p0.y;
+			var nx = (segment == 0) ? 0 : dy / segment;
+			var ny = (segment == 0) ? 0 : dx / segment;
+			
+			x = p0.x + dx * factor + (nx * gy + offsetX) * this.scale;
+			y = p0.y + dy * factor - (ny * gy - offsetY) * this.scale;
+		}
+	}
+	else if (geometry != null)
+	{
+		var offset = geometry.offset;
+		
+		if (offset != null)
+		{
+			x += offset.x;
+			y += offset.y;
+		}
+	}
+	
+	return new mxPoint(x, y);		
+};
+
+/**
+ * Function: getRelativePoint
+ *
+ * Gets the relative point that describes the given, absolute label
+ * position for the given edge state.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> that represents the state of the parent edge.
+ * x - Specifies the x-coordinate of the absolute label location.
+ * y - Specifies the y-coordinate of the absolute label location.
+ */
+mxGraphView.prototype.getRelativePoint = function(edgeState, x, y)
+{
+	var model = this.graph.getModel();
+	var geometry = model.getGeometry(edgeState.cell);
+	
+	if (geometry != null)
+	{
+		var pointCount = edgeState.absolutePoints.length;
+		
+		if (geometry.relative && pointCount > 1)
+		{
+			var totalLength = edgeState.length;
+			var segments = edgeState.segments;
+
+			// Works which line segment the point of the label is closest to
+			var p0 = edgeState.absolutePoints[0];
+			var pe = edgeState.absolutePoints[1];
+			var minDist = mxUtils.ptSegDistSq(p0.x, p0.y, pe.x, pe.y, x, y);
+
+			var index = 0;
+			var tmp = 0;
+			var length = 0;
+			
+			for (var i = 2; i < pointCount; i++)
+			{
+				tmp += segments[i - 2];
+				pe = edgeState.absolutePoints[i];
+				var dist = mxUtils.ptSegDistSq(p0.x, p0.y, pe.x, pe.y, x, y);
+
+				if (dist <= minDist)
+				{
+					minDist = dist;
+					index = i - 1;
+					length = tmp;
+				}
+				
+				p0 = pe;
+			}
+			
+			var seg = segments[index];
+			p0 = edgeState.absolutePoints[index];
+			pe = edgeState.absolutePoints[index + 1];
+			
+			var x2 = p0.x;
+			var y2 = p0.y;
+			
+			var x1 = pe.x;
+			var y1 = pe.y;
+			
+			var px = x;
+			var py = y;
+			
+			var xSegment = x2 - x1;
+			var ySegment = y2 - y1;
+			
+			px -= x1;
+			py -= y1;
+			var projlenSq = 0;
+			
+			px = xSegment - px;
+			py = ySegment - py;
+			var dotprod = px * xSegment + py * ySegment;
+
+			if (dotprod <= 0.0)
+			{
+				projlenSq = 0;
+			}
+			else
+			{
+				projlenSq = dotprod * dotprod
+						/ (xSegment * xSegment + ySegment * ySegment);
+			}
+
+			var projlen = Math.sqrt(projlenSq);
+
+			if (projlen > seg)
+			{
+				projlen = seg;
+			}
+
+			var yDistance = Math.sqrt(mxUtils.ptSegDistSq(p0.x, p0.y, pe
+					.x, pe.y, x, y));
+			var direction = mxUtils.relativeCcw(p0.x, p0.y, pe.x, pe.y, x, y);
+
+			if (direction == -1)
+			{
+				yDistance = -yDistance;
+			}
+
+			// Constructs the relative point for the label
+			return new mxPoint(((totalLength / 2 - length - projlen) / totalLength) * -2,
+						yDistance / this.scale);
+		}
+	}
+	
+	return new mxPoint();
+};
+
+/**
+ * Function: updateEdgeLabelOffset
+ *
+ * Updates <mxCellState.absoluteOffset> for the given state. The absolute
+ * offset is normally used for the position of the edge label. Is is
+ * calculated from the geometry as an absolute offset from the center
+ * between the two endpoints if the geometry is absolute, or as the
+ * relative distance between the center along the line and the absolute
+ * orthogonal distance if the geometry is relative.
+ * 
+ * Parameters:
+ * 
+ * state - <mxCellState> whose absolute offset should be updated.
+ */
+mxGraphView.prototype.updateEdgeLabelOffset = function(state)
+{
+	var points = state.absolutePoints;
+	
+	state.absoluteOffset.x = state.getCenterX();
+	state.absoluteOffset.y = state.getCenterY();
+
+	if (points != null && points.length > 0 && state.segments != null)
+	{
+		var geometry = this.graph.getCellGeometry(state.cell);
+		
+		if (geometry.relative)
+		{
+			var offset = this.getPoint(state, geometry);
+			
+			if (offset != null)
+			{
+				state.absoluteOffset = offset;
+			}
+		}
+		else
+		{
+			var p0 = points[0];
+			var pe = points[points.length - 1];
+			
+			if (p0 != null && pe != null)
+			{
+				var dx = pe.x - p0.x;
+				var dy = pe.y - p0.y;
+				var x0 = 0;
+				var y0 = 0;
+
+				var off = geometry.offset;
+				
+				if (off != null)
+				{
+					x0 = off.x;
+					y0 = off.y;
+				}
+				
+				var x = p0.x + dx / 2 + x0 * this.scale;
+				var y = p0.y + dy / 2 + y0 * this.scale;
+				
+				state.absoluteOffset.x = x;
+				state.absoluteOffset.y = y;
+			}
+		}
+	}
+};
+
+/**
+ * Function: getState
+ *
+ * Returns the <mxCellState> for the given cell. If create is true, then
+ * the state is created if it does not yet exist.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> for which the <mxCellState> should be returned.
+ * create - Optional boolean indicating if a new state should be created
+ * if it does not yet exist. Default is false.
+ */
+mxGraphView.prototype.getState = function(cell, create)
+{
+	create = create || false;
+	var state = null;
+	
+	if (cell != null)
+	{
+		state = this.states.get(cell);
+		
+		if (create && (state == null || this.updateStyle) && this.graph.isCellVisible(cell))
+		{
+			if (state == null)
+			{
+				state = this.createState(cell);
+				this.states.put(cell, state);
+			}
+			else
+			{
+				state.style = this.graph.getCellStyle(cell);
+			}
+		}
+	}
+
+	return state;
+};
+
+/**
+ * Function: isRendering
+ *
+ * Returns <rendering>.
+ */
+mxGraphView.prototype.isRendering = function()
+{
+	return this.rendering;
+};
+
+/**
+ * Function: setRendering
+ *
+ * Sets <rendering>.
+ */
+mxGraphView.prototype.setRendering = function(value)
+{
+	this.rendering = value;
+};
+
+/**
+ * Function: isAllowEval
+ *
+ * Returns <allowEval>.
+ */
+mxGraphView.prototype.isAllowEval = function()
+{
+	return this.allowEval;
+};
+
+/**
+ * Function: setAllowEval
+ *
+ * Sets <allowEval>.
+ */
+mxGraphView.prototype.setAllowEval = function(value)
+{
+	this.allowEval = value;
+};
+
+/**
+ * Function: getStates
+ *
+ * Returns <states>.
+ */
+mxGraphView.prototype.getStates = function()
+{
+	return this.states;
+};
+
+/**
+ * Function: setStates
+ *
+ * Sets <states>.
+ */
+mxGraphView.prototype.setStates = function(value)
+{
+	this.states = value;
+};
+
+/**
+ * Function: getCellStates
+ *
+ * Returns the <mxCellStates> for the given array of <mxCells>. The array
+ * contains all states that are not null, that is, the returned array may
+ * have less elements than the given array. If no argument is given, then
+ * this returns <states>.
+ */
+mxGraphView.prototype.getCellStates = function(cells)
+{
+	if (cells == null)
+	{
+		return this.states;
+	}
+	else
+	{
+		var result = [];
+		
+		for (var i = 0; i < cells.length; i++)
+		{
+			var state = this.getState(cells[i]);
+			
+			if (state != null)
+			{
+				result.push(state);
+			}
+		}
+		
+		return result;
+	}
+};
+
+/**
+ * Function: removeState
+ *
+ * Removes and returns the <mxCellState> for the given cell.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> for which the <mxCellState> should be removed.
+ */
+mxGraphView.prototype.removeState = function(cell)
+{
+	var state = null;
+	
+	if (cell != null)
+	{
+		state = this.states.remove(cell);
+		
+		if (state != null)
+		{
+			this.graph.cellRenderer.destroy(state);
+			state.invalid = true;
+			state.destroy();
+		}
+	}
+	
+	return state;
+};
+
+/**
+ * Function: createState
+ *
+ * Creates and returns an <mxCellState> for the given cell and initializes
+ * it using <mxCellRenderer.initialize>.
+ * 
+ * Parameters:
+ * 
+ * cell - <mxCell> for which a new <mxCellState> should be created.
+ */
+mxGraphView.prototype.createState = function(cell)
+{
+	return new mxCellState(this, cell, this.graph.getCellStyle(cell));
+};
+
+/**
+ * Function: getCanvas
+ *
+ * Returns the DOM node that contains the background-, draw- and
+ * overlay- and decoratorpanes.
+ */
+mxGraphView.prototype.getCanvas = function()
+{
+	return this.canvas;
+};
+
+/**
+ * Function: getBackgroundPane
+ *
+ * Returns the DOM node that represents the background layer.
+ */
+mxGraphView.prototype.getBackgroundPane = function()
+{
+	return this.backgroundPane;
+};
+
+/**
+ * Function: getDrawPane
+ *
+ * Returns the DOM node that represents the main drawing layer.
+ */
+mxGraphView.prototype.getDrawPane = function()
+{
+	return this.drawPane;
+};
+
+/**
+ * Function: getOverlayPane
+ *
+ * Returns the DOM node that represents the layer above the drawing layer.
+ */
+mxGraphView.prototype.getOverlayPane = function()
+{
+	return this.overlayPane;
+};
+
+/**
+ * Function: getDecoratorPane
+ *
+ * Returns the DOM node that represents the topmost drawing layer.
+ */
+mxGraphView.prototype.getDecoratorPane = function()
+{
+	return this.decoratorPane;
+};
+
+/**
+ * Function: isContainerEvent
+ * 
+ * Returns true if the event origin is one of the drawing panes or
+ * containers of the view.
+ */
+mxGraphView.prototype.isContainerEvent = function(evt)
+{
+	var source = mxEvent.getSource(evt);
+
+	return (source == this.graph.container ||
+		source.parentNode == this.backgroundPane ||
+		(source.parentNode != null &&
+		source.parentNode.parentNode == this.backgroundPane) ||
+		source == this.canvas.parentNode ||
+		source == this.canvas ||
+		source == this.backgroundPane ||
+		source == this.drawPane ||
+		source == this.overlayPane ||
+		source == this.decoratorPane);
+};
+
+/**
+ * Function: isScrollEvent
+ * 
+ * Returns true if the event origin is one of the scrollbars of the
+ * container in IE. Such events are ignored.
+ */
+ mxGraphView.prototype.isScrollEvent = function(evt)
+{
+	var offset = mxUtils.getOffset(this.graph.container);
+	var pt = new mxPoint(evt.clientX - offset.x, evt.clientY - offset.y);
+
+	var outWidth = this.graph.container.offsetWidth;
+	var inWidth = this.graph.container.clientWidth;
+
+	if (outWidth > inWidth && pt.x > inWidth + 2 && pt.x <= outWidth)
+	{
+		return true;
+	}
+
+	var outHeight = this.graph.container.offsetHeight;
+	var inHeight = this.graph.container.clientHeight;
+	
+	if (outHeight > inHeight && pt.y > inHeight + 2 && pt.y <= outHeight)
+	{
+		return true;
+	}
+	
+	return false;
+};
+
+/**
+ * Function: init
+ *
+ * Initializes the graph event dispatch loop for the specified container
+ * and invokes <create> to create the required DOM nodes for the display.
+ */
+mxGraphView.prototype.init = function()
+{
+	this.installListeners();
+	
+	// Creates the DOM nodes for the respective display dialect
+	var graph = this.graph;
+	
+	if (graph.dialect == mxConstants.DIALECT_SVG)
+	{
+		this.createSvg();
+	}
+	else if (graph.dialect == mxConstants.DIALECT_VML)
+	{
+		this.createVml();
+	}
+	else
+	{
+		this.createHtml();
+	}
+};
+
+/**
+ * Function: installListeners
+ *
+ * Installs the required listeners in the container.
+ */
+mxGraphView.prototype.installListeners = function()
+{
+	var graph = this.graph;
+	var container = graph.container;
+	
+	if (container != null)
+	{
+		// Support for touch device gestures (eg. pinch to zoom)
+		// Double-tap handling is implemented in mxGraph.fireMouseEvent
+		if (mxClient.IS_TOUCH)
+		{
+			mxEvent.addListener(container, 'gesturestart', mxUtils.bind(this, function(evt)
+			{
+				graph.fireGestureEvent(evt);
+				mxEvent.consume(evt);
+			}));
+			
+			mxEvent.addListener(container, 'gesturechange', mxUtils.bind(this, function(evt)
+			{
+				graph.fireGestureEvent(evt);
+				mxEvent.consume(evt);
+			}));
+
+			mxEvent.addListener(container, 'gestureend', mxUtils.bind(this, function(evt)
+			{
+				graph.fireGestureEvent(evt);
+				mxEvent.consume(evt);
+			}));
+		}
+		
+		// Adds basic listeners for graph event dispatching
+		mxEvent.addGestureListeners(container, mxUtils.bind(this, function(evt)
+		{
+			// Condition to avoid scrollbar events starting a rubberband selection
+			if (this.isContainerEvent(evt) && ((!mxClient.IS_IE && !mxClient.IS_IE11 && !mxClient.IS_GC &&
+				!mxClient.IS_OP && !mxClient.IS_SF) || !this.isScrollEvent(evt)))
+			{
+				graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt));
+			}
+		}),
+		mxUtils.bind(this, function(evt)
+		{
+			if (this.isContainerEvent(evt))
+			{
+				graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt));
+			}
+		}),
+		mxUtils.bind(this, function(evt)
+		{
+			if (this.isContainerEvent(evt))
+			{
+				graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt));
+			}
+		}));
+		
+		// Adds listener for double click handling on background, this does always
+		// use native event handler, we assume that the DOM of the background
+		// does not change during the double click
+		mxEvent.addListener(container, 'dblclick', mxUtils.bind(this, function(evt)
+		{
+			if (this.isContainerEvent(evt))
+			{
+				graph.dblClick(evt);
+			}
+		}));
+
+		// Workaround for touch events which started on some DOM node
+		// on top of the container, in which case the cells under the
+		// mouse for the move and up events are not detected.
+		var getState = function(evt)
+		{
+			var state = null;
+			
+			// Workaround for touch events which started on some DOM node
+			// on top of the container, in which case the cells under the
+			// mouse for the move and up events are not detected.
+			if (mxClient.IS_TOUCH)
+			{
+				var x = mxEvent.getClientX(evt);
+				var y = mxEvent.getClientY(evt);
+				
+				// Dispatches the drop event to the graph which
+				// consumes and executes the source function
+				var pt = mxUtils.convertPoint(container, x, y);
+				state = graph.view.getState(graph.getCellAt(pt.x, pt.y));
+			}
+			
+			return state;
+		};
+		
+		// Adds basic listeners for graph event dispatching outside of the
+		// container and finishing the handling of a single gesture
+		// Implemented via graph event dispatch loop to avoid duplicate events
+		// in Firefox and Chrome
+		graph.addMouseListener(
+		{
+			mouseDown: function(sender, me)
+			{
+				graph.popupMenuHandler.hideMenu();
+			},
+			mouseMove: function() { },
+			mouseUp: function() { }
+		});
+		
+		this.moveHandler = mxUtils.bind(this, function(evt)
+		{
+			// Hides the tooltip if mouse is outside container
+			if (graph.tooltipHandler != null && graph.tooltipHandler.isHideOnHover())
+			{
+				graph.tooltipHandler.hide();
+			}
+
+			if (this.captureDocumentGesture && graph.isMouseDown && graph.container != null &&
+				!this.isContainerEvent(evt) && graph.container.style.display != 'none' &&
+				graph.container.style.visibility != 'hidden' && !mxEvent.isConsumed(evt))
+			{
+				graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, getState(evt)));
+			}
+		});
+		
+		this.endHandler = mxUtils.bind(this, function(evt)
+		{
+			if (this.captureDocumentGesture && graph.isMouseDown && graph.container != null &&
+				!this.isContainerEvent(evt) && graph.container.style.display != 'none' &&
+				graph.container.style.visibility != 'hidden')
+			{
+				graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt));
+			}
+		});
+		
+		mxEvent.addGestureListeners(document, null, this.moveHandler, this.endHandler);
+	}
+};
+
+/**
+ * Function: create
+ *
+ * Creates the DOM nodes for the HTML display.
+ */
+mxGraphView.prototype.createHtml = function()
+{
+	var container = this.graph.container;
+	
+	if (container != null)
+	{
+		this.canvas = this.createHtmlPane('100%', '100%');
+		this.canvas.style.overflow = 'hidden';
+	
+		// Uses minimal size for inner DIVs on Canvas. This is required
+		// for correct event processing in IE. If we have an overlapping
+		// DIV then the events on the cells are only fired for labels.
+		this.backgroundPane = this.createHtmlPane('1px', '1px');
+		this.drawPane = this.createHtmlPane('1px', '1px');
+		this.overlayPane = this.createHtmlPane('1px', '1px');
+		this.decoratorPane = this.createHtmlPane('1px', '1px');
+		
+		this.canvas.appendChild(this.backgroundPane);
+		this.canvas.appendChild(this.drawPane);
+		this.canvas.appendChild(this.overlayPane);
+		this.canvas.appendChild(this.decoratorPane);
+
+		container.appendChild(this.canvas);
+		this.updateContainerStyle(container);
+		
+		// Implements minWidth/minHeight in quirks mode
+		if (mxClient.IS_QUIRKS)
+		{
+			var onResize = mxUtils.bind(this, function(evt)
+			{
+				var bounds = this.getGraphBounds();
+				var width = bounds.x + bounds.width + this.graph.border;
+				var height = bounds.y + bounds.height + this.graph.border;
+				
+				this.updateHtmlCanvasSize(width, height);
+			});
+			
+			mxEvent.addListener(window, 'resize', onResize);
+		}
+	}
+};
+
+/**
+ * Function: updateHtmlCanvasSize
+ * 
+ * Updates the size of the HTML canvas.
+ */
+mxGraphView.prototype.updateHtmlCanvasSize = function(width, height)
+{
+	if (this.graph.container != null)
+	{
+		var ow = this.graph.container.offsetWidth;
+		var oh = this.graph.container.offsetHeight;
+
+		if (ow < width)
+		{
+			this.canvas.style.width = width + 'px';
+		}
+		else
+		{
+			this.canvas.style.width = '100%';
+		}
+
+		if (oh < height)
+		{
+			this.canvas.style.height = height + 'px';
+		}
+		else
+		{
+			this.canvas.style.height = '100%';
+		}
+	}
+};
+
+/**
+ * Function: createHtmlPane
+ * 
+ * Creates and returns a drawing pane in HTML (DIV).
+ */
+mxGraphView.prototype.createHtmlPane = function(width, height)
+{
+	var pane = document.createElement('DIV');
+	
+	if (width != null && height != null)
+	{
+		pane.style.position = 'absolute';
+		pane.style.left = '0px';
+		pane.style.top = '0px';
+
+		pane.style.width = width;
+		pane.style.height = height;
+	}
+	else
+	{
+		pane.style.position = 'relative';
+	}
+	
+	return pane;
+};
+
+/**
+ * Function: create
+ *
+ * Creates the DOM nodes for the VML display.
+ */
+mxGraphView.prototype.createVml = function()
+{
+	var container = this.graph.container;
+
+	if (container != null)
+	{
+		var width = container.offsetWidth;
+		var height = container.offsetHeight;
+		this.canvas = this.createVmlPane(width, height);
+		this.canvas.style.overflow = 'hidden';
+		
+		this.backgroundPane = this.createVmlPane(width, height);
+		this.drawPane = this.createVmlPane(width, height);
+		this.overlayPane = this.createVmlPane(width, height);
+		this.decoratorPane = this.createVmlPane(width, height);
+		
+		this.canvas.appendChild(this.backgroundPane);
+		this.canvas.appendChild(this.drawPane);
+		this.canvas.appendChild(this.overlayPane);
+		this.canvas.appendChild(this.decoratorPane);
+		
+		container.appendChild(this.canvas);
+	}
+};
+
+/**
+ * Function: createVmlPane
+ * 
+ * Creates a drawing pane in VML (group).
+ */
+mxGraphView.prototype.createVmlPane = function(width, height)
+{
+	var pane = document.createElement(mxClient.VML_PREFIX + ':group');
+	
+	// At this point the width and height are potentially
+	// uninitialized. That's OK.
+	pane.style.position = 'absolute';
+	pane.style.left = '0px';
+	pane.style.top = '0px';
+
+	pane.style.width = width + 'px';
+	pane.style.height = height + 'px';
+
+	pane.setAttribute('coordsize', width + ',' + height);
+	pane.setAttribute('coordorigin', '0,0');
+	
+	return pane;
+};
+
+/**
+ * Function: create
+ *
+ * Creates and returns the DOM nodes for the SVG display.
+ */
+mxGraphView.prototype.createSvg = function()
+{
+	var container = this.graph.container;
+	this.canvas = document.createElementNS(mxConstants.NS_SVG, 'g');
+	
+	// For background image
+	this.backgroundPane = document.createElementNS(mxConstants.NS_SVG, 'g');
+	this.canvas.appendChild(this.backgroundPane);
+
+	// Adds two layers (background is early feature)
+	this.drawPane = document.createElementNS(mxConstants.NS_SVG, 'g');
+	this.canvas.appendChild(this.drawPane);
+
+	this.overlayPane = document.createElementNS(mxConstants.NS_SVG, 'g');
+	this.canvas.appendChild(this.overlayPane);
+	
+	this.decoratorPane = document.createElementNS(mxConstants.NS_SVG, 'g');
+	this.canvas.appendChild(this.decoratorPane);
+	
+	var root = document.createElementNS(mxConstants.NS_SVG, 'svg');
+	root.style.width = '100%';
+	root.style.height = '100%';
+	
+	// NOTE: In standards mode, the SVG must have block layout
+	// in order for the container DIV to not show scrollbars.
+	root.style.display = 'block';
+	root.appendChild(this.canvas);
+	
+	// Workaround for scrollbars in IE11 and below
+	if (mxClient.IS_IE || mxClient.IS_IE11)
+	{
+		root.style.overflow = 'hidden';
+	}
+
+	if (container != null)
+	{
+		container.appendChild(root);
+		this.updateContainerStyle(container);
+	}
+};
+
+/**
+ * Function: updateContainerStyle
+ * 
+ * Updates the style of the container after installing the SVG DOM elements.
+ */
+mxGraphView.prototype.updateContainerStyle = function(container)
+{
+	// Workaround for offset of container
+	var style = mxUtils.getCurrentStyle(container);
+	
+	if (style != null && style.position == 'static')
+	{
+		container.style.position = 'relative';
+	}
+	
+	// Disables built-in pan and zoom in IE10 and later
+	if (mxClient.IS_POINTER)
+	{
+		container.style.touchAction = 'none';
+	}
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroys the view and all its resources.
+ */
+mxGraphView.prototype.destroy = function()
+{
+	var root = (this.canvas != null) ? this.canvas.ownerSVGElement : null;
+	
+	if (root == null)
+	{
+		root = this.canvas;
+	}
+	
+	if (root != null && root.parentNode != null)
+	{
+		this.clear(this.currentRoot, true);
+		mxEvent.removeGestureListeners(document, null, this.moveHandler, this.endHandler);
+		mxEvent.release(this.graph.container);
+		root.parentNode.removeChild(root);
+		
+		this.moveHandler = null;
+		this.endHandler = null;
+		this.canvas = null;
+		this.backgroundPane = null;
+		this.drawPane = null;
+		this.overlayPane = null;
+		this.decoratorPane = null;
+	}
+};
+
+/**
+ * Class: mxCurrentRootChange
+ *
+ * Action to change the current root in a view.
+ *
+ * Constructor: mxCurrentRootChange
+ *
+ * Constructs a change of the current root in the given view.
+ */
+function mxCurrentRootChange(view, root)
+{
+	this.view = view;
+	this.root = root;
+	this.previous = root;
+	this.isUp = root == null;
+	
+	if (!this.isUp)
+	{
+		var tmp = this.view.currentRoot;
+		var model = this.view.graph.getModel();
+		
+		while (tmp != null)
+		{
+			if (tmp == root)
+			{
+				this.isUp = true;
+				break;
+			}
+			
+			tmp = model.getParent(tmp);
+		}
+	}
+};
+
+/**
+ * Function: execute
+ *
+ * Changes the current root of the view.
+ */
+mxCurrentRootChange.prototype.execute = function()
+{
+	var tmp = this.view.currentRoot;
+	this.view.currentRoot = this.previous;
+	this.previous = tmp;
+
+	var translate = this.view.graph.getTranslateForRoot(this.view.currentRoot);
+	
+	if (translate != null)
+	{
+		this.view.translate = new mxPoint(-translate.x, -translate.y);
+	}
+
+	if (this.isUp)
+	{
+		this.view.clear(this.view.currentRoot, true);
+		this.view.validate();
+	}
+	else
+	{
+		this.view.refresh();
+	}
+	
+	var name = (this.isUp) ? mxEvent.UP : mxEvent.DOWN;
+	this.view.fireEvent(new mxEventObject(name,
+		'root', this.view.currentRoot, 'previous', this.previous));
+	this.isUp = !this.isUp;
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/view/mxLayoutManager.js b/airavata-kubernetes/workflow-composer/src/js/view/mxLayoutManager.js
new file mode 100644
index 0000000..346af84
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/view/mxLayoutManager.js
@@ -0,0 +1,409 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxLayoutManager
+ * 
+ * Implements a layout manager that runs a given layout after any changes to the graph:
+ * 
+ * Example:
+ * 
+ * (code)
+ * var layoutMgr = new mxLayoutManager(graph);
+ * layoutMgr.getLayout = function(cell)
+ * {
+ *   return layout;
+ * };
+ * (end)
+ * 
+ * Event: mxEvent.LAYOUT_CELLS
+ * 
+ * Fires between begin- and endUpdate after all cells have been layouted in
+ * <layoutCells>. The <code>cells</code> property contains all cells that have
+ * been passed to <layoutCells>.
+ * 
+ * Constructor: mxLayoutManager
+ *
+ * Constructs a new automatic layout for the given graph.
+ *
+ * Arguments:
+ * 
+ * graph - Reference to the enclosing graph. 
+ */
+function mxLayoutManager(graph)
+{
+	// Executes the layout before the changes are dispatched
+	this.undoHandler = mxUtils.bind(this, function(sender, evt)
+	{
+		if (this.isEnabled())
+		{
+			this.beforeUndo(evt.getProperty('edit'));
+		}
+	});
+	
+	// Notifies the layout of a move operation inside a parent
+	this.moveHandler = mxUtils.bind(this, function(sender, evt)
+	{
+		if (this.isEnabled())
+		{
+			this.cellsMoved(evt.getProperty('cells'), evt.getProperty('event'));
+		}
+	});
+	
+	this.setGraph(graph);
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxLayoutManager.prototype = new mxEventSource();
+mxLayoutManager.prototype.constructor = mxLayoutManager;
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxLayoutManager.prototype.graph = null;
+
+/**
+ * Variable: bubbling
+ * 
+ * Specifies if the layout should bubble along
+ * the cell hierarchy. Default is true.
+ */
+mxLayoutManager.prototype.bubbling = true;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies if event handling is enabled. Default is true.
+ */
+mxLayoutManager.prototype.enabled = true;
+
+/**
+ * Variable: updateHandler
+ * 
+ * Holds the function that handles the endUpdate event.
+ */
+mxLayoutManager.prototype.updateHandler = null;
+
+/**
+ * Variable: moveHandler
+ * 
+ * Holds the function that handles the move event.
+ */
+mxLayoutManager.prototype.moveHandler = null;
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxLayoutManager.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ * 
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ * 
+ * Parameters:
+ * 
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxLayoutManager.prototype.setEnabled = function(enabled)
+{
+	this.enabled = enabled;
+};
+
+/**
+ * Function: isBubbling
+ * 
+ * Returns true if a layout should bubble, that is, if the parent layout
+ * should be executed whenever a cell layout (layout of the children of
+ * a cell) has been executed. This implementation returns <bubbling>.
+ */
+mxLayoutManager.prototype.isBubbling = function()
+{
+	return this.bubbling;
+};
+
+/**
+ * Function: setBubbling
+ * 
+ * Sets <bubbling>.
+ */
+mxLayoutManager.prototype.setBubbling = function(value)
+{
+	this.bubbling = value;
+};
+
+/**
+ * Function: getGraph
+ * 
+ * Returns the graph that this layout operates on.
+ */
+mxLayoutManager.prototype.getGraph = function()
+{
+	return this.graph;
+};
+
+/**
+ * Function: setGraph
+ * 
+ * Sets the graph that the layouts operate on.
+ */
+mxLayoutManager.prototype.setGraph = function(graph)
+{
+	if (this.graph != null)
+	{
+		var model = this.graph.getModel();		
+		model.removeListener(this.undoHandler);
+		this.graph.removeListener(this.moveHandler);
+	}
+	
+	this.graph = graph;
+	
+	if (this.graph != null)
+	{
+		var model = this.graph.getModel();	
+		model.addListener(mxEvent.BEFORE_UNDO, this.undoHandler);
+		this.graph.addListener(mxEvent.MOVE_CELLS, this.moveHandler);
+	}
+};
+
+/**
+ * Function: getLayout
+ * 
+ * Returns the layout to be executed for the given graph and parent.
+ */
+mxLayoutManager.prototype.getLayout = function(parent)
+{
+	return null;
+};
+
+/**
+ * Function: beforeUndo
+ * 
+ * Called from the undoHandler.
+ *
+ * Parameters:
+ * 
+ * cell - Array of <mxCells> that have been moved.
+ * evt - Mouse event that represents the mousedown.
+ */
+mxLayoutManager.prototype.beforeUndo = function(undoableEdit)
+{
+	var cells = this.getCellsForChanges(undoableEdit.changes);
+	var model = this.getGraph().getModel();
+
+	// Adds all descendants
+	var tmp = [];
+	
+	for (var i = 0; i < cells.length; i++)
+	{
+		tmp = tmp.concat(model.getDescendants(cells[i]));
+	}
+	
+	cells = tmp;
+	
+	// Adds all parent ancestors
+	if (this.isBubbling())
+	{
+		tmp = model.getParents(cells);
+		
+		while (tmp.length > 0)
+		{
+			cells = cells.concat(tmp);
+			tmp = model.getParents(tmp);
+		}
+	}
+	
+	this.executeLayoutForCells(cells);
+};
+
+/**
+ * Function: executeLayout
+ * 
+ * Executes the given layout on the given parent.
+ */
+mxLayoutManager.prototype.executeLayoutForCells = function(cells)
+{
+	// Adds reverse to this array to avoid duplicate execution of leafes
+	// Works like capture/bubble for events, first executes all layout
+	// from top to bottom and in reverse order and removes duplicates.
+	var sorted = mxUtils.sortCells(cells, true);
+	sorted = sorted.concat(sorted.slice().reverse());
+	this.layoutCells(sorted);
+};
+
+/**
+ * Function: cellsMoved
+ * 
+ * Called from the moveHandler.
+ *
+ * Parameters:
+ * 
+ * cell - Array of <mxCells> that have been moved.
+ * evt - Mouse event that represents the mousedown.
+ */
+mxLayoutManager.prototype.cellsMoved = function(cells, evt)
+{
+	if (cells != null && evt != null)
+	{
+		var point = mxUtils.convertPoint(this.getGraph().container,
+			mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+		var model = this.getGraph().getModel();
+		
+		// Checks if a layout exists to take care of the moving if the
+		// parent itself is not being moved
+		for (var i = 0; i < cells.length; i++)
+		{
+			var parent = model.getParent(cells[i]);
+			
+			if (mxUtils.indexOf(cells, parent) < 0)
+			{
+				var layout = this.getLayout(parent);
+	
+				if (layout != null)
+				{
+					layout.moveCell(cells[i], point.x, point.y);
+				}
+			}
+		}
+	}
+};
+
+/**
+ * Function: getCellsForEdit
+ * 
+ * Returns the cells to be layouted for the given sequence of changes.
+ */
+mxLayoutManager.prototype.getCellsForChanges = function(changes)
+{
+	var dict = new mxDictionary();
+	var result = [];
+	
+	for (var i = 0; i < changes.length; i++)
+	{
+		var change = changes[i];
+		
+		if (change instanceof mxRootChange)
+		{
+			return [];
+		}
+		else
+		{
+			var cells = this.getCellsForChange(change);
+			
+			for (var j = 0; j < cells.length; j++)
+			{
+				if (cells[j] != null && !dict.get(cells[j]))
+				{
+					dict.put(cells[j], true);
+					result.push(cells[j]);
+				}
+			}
+		}
+	}
+	
+	return result;
+};
+
+/**
+ * Function: getCellsForChange
+ * 
+ * Executes all layouts which have been scheduled during the
+ * changes.
+ */
+mxLayoutManager.prototype.getCellsForChange = function(change)
+{
+	var model = this.getGraph().getModel();
+	
+	if (change instanceof mxChildChange)
+	{
+		return [change.child, change.previous, model.getParent(change.child)];
+	}
+	else if (change instanceof mxTerminalChange || change instanceof mxGeometryChange)
+	{
+		return [change.cell, model.getParent(change.cell)];
+	}
+	else if (change instanceof mxVisibleChange || change instanceof mxStyleChange)
+	{
+		return [change.cell];
+	}
+	
+	return [];
+};
+
+/**
+ * Function: layoutCells
+ * 
+ * Executes all layouts which have been scheduled during the
+ * changes.
+ */
+mxLayoutManager.prototype.layoutCells = function(cells)
+{
+	if (cells.length > 0)
+	{
+		// Invokes the layouts while removing duplicates
+		var model = this.getGraph().getModel();
+		
+		model.beginUpdate();
+		try 
+		{
+			var last = null;
+			
+			for (var i = 0; i < cells.length; i++)
+			{
+				if (cells[i] != model.getRoot() && cells[i] != last)
+				{
+					if (this.executeLayout(this.getLayout(cells[i]), cells[i]))
+					{
+						last = cells[i];
+					}
+				}
+			}
+			
+			this.fireEvent(new mxEventObject(mxEvent.LAYOUT_CELLS, 'cells', cells));
+		}
+		finally
+		{
+			model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: executeLayout
+ * 
+ * Executes the given layout on the given parent.
+ */
+mxLayoutManager.prototype.executeLayout = function(layout, parent)
+{
+	var result = false;
+	
+	if (layout != null && parent != null)
+	{
+		layout.execute(parent);
+		result = true;
+	}
+	
+	return result;
+};
+
+/**
+ * Function: destroy
+ * 
+ * Removes all handlers from the <graph> and deletes the reference to it.
+ */
+mxLayoutManager.prototype.destroy = function()
+{
+	this.setGraph(null);
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/view/mxMultiplicity.js b/airavata-kubernetes/workflow-composer/src/js/view/mxMultiplicity.js
new file mode 100644
index 0000000..aa0e0b9
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/view/mxMultiplicity.js
@@ -0,0 +1,257 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxMultiplicity
+ * 
+ * Defines invalid connections along with the error messages that they produce.
+ * To add or remove rules on a graph, you must add/remove instances of this
+ * class to <mxGraph.multiplicities>.
+ * 
+ * Example:
+ * 
+ * (code)
+ * graph.multiplicities.push(new mxMultiplicity(
+ *   true, 'rectangle', null, null, 0, 2, ['circle'],
+ *   'Only 2 targets allowed',
+ *   'Only circle targets allowed'));
+ * (end)
+ * 
+ * Defines a rule where each rectangle must be connected to no more than 2
+ * circles and no other types of targets are allowed.
+ * 
+ * Constructor: mxMultiplicity
+ * 
+ * Instantiate class mxMultiplicity in order to describe allowed
+ * connections in a graph. Not all constraints can be enforced while
+ * editing, some must be checked at validation time. The <countError> and
+ * <typeError> are treated as resource keys in <mxResources>.
+ * 
+ * Parameters:
+ * 
+ * source - Boolean indicating if this rule applies to the source or target
+ * terminal.
+ * type - Type of the source or target terminal that this rule applies to.
+ * See <type> for more information.
+ * attr - Optional attribute name to match the source or target terminal.
+ * value - Optional attribute value to match the source or target terminal.
+ * min - Minimum number of edges for this rule. Default is 1.
+ * max - Maximum number of edges for this rule. n means infinite. Default
+ * is n.
+ * validNeighbors - Array of types of the opposite terminal for which this
+ * rule applies.
+ * countError - Error to be displayed for invalid number of edges.
+ * typeError - Error to be displayed for invalid opposite terminals.
+ * validNeighborsAllowed - Optional boolean indicating if the array of
+ * opposite types should be valid or invalid.
+ */
+function mxMultiplicity(source, type, attr, value, min, max,
+	validNeighbors, countError, typeError, validNeighborsAllowed)
+{
+	this.source = source;
+	this.type = type;
+	this.attr = attr;
+	this.value = value;
+	this.min = (min != null) ? min : 0;
+	this.max = (max != null) ? max : 'n';
+	this.validNeighbors = validNeighbors;
+	this.countError = mxResources.get(countError) || countError;
+	this.typeError = mxResources.get(typeError) || typeError;
+	this.validNeighborsAllowed = (validNeighborsAllowed != null) ?
+		validNeighborsAllowed : true;
+};
+
+/**
+ * Variable: type
+ * 
+ * Defines the type of the source or target terminal. The type is a string
+ * passed to <mxUtils.isNode> together with the source or target vertex
+ * value as the first argument.
+ */
+mxMultiplicity.prototype.type = null;
+
+/**
+ * Variable: attr
+ * 
+ * Optional string that specifies the attributename to be passed to
+ * <mxUtils.isNode> to check if the rule applies to a cell.
+ */
+mxMultiplicity.prototype.attr = null;
+
+/**
+ * Variable: value
+ * 
+ * Optional string that specifies the value of the attribute to be passed
+ * to <mxUtils.isNode> to check if the rule applies to a cell.
+ */
+mxMultiplicity.prototype.value = null;
+
+/**
+ * Variable: source
+ * 
+ * Boolean that specifies if the rule is applied to the source or target
+ * terminal of an edge.
+ */
+mxMultiplicity.prototype.source = null;
+
+/**
+ * Variable: min
+ * 
+ * Defines the minimum number of connections for which this rule applies.
+ * Default is 0.
+ */
+mxMultiplicity.prototype.min = null;
+
+/**
+ * Variable: max
+ * 
+ * Defines the maximum number of connections for which this rule applies.
+ * A value of 'n' means unlimited times. Default is 'n'. 
+ */
+mxMultiplicity.prototype.max = null;
+
+/**
+ * Variable: validNeighbors
+ * 
+ * Holds an array of strings that specify the type of neighbor for which
+ * this rule applies. The strings are used in <mxCell.is> on the opposite
+ * terminal to check if the rule applies to the connection.
+ */
+mxMultiplicity.prototype.validNeighbors = null;
+
+/**
+ * Variable: validNeighborsAllowed
+ * 
+ * Boolean indicating if the list of validNeighbors are those that are allowed
+ * for this rule or those that are not allowed for this rule.
+ */
+mxMultiplicity.prototype.validNeighborsAllowed = true;
+
+/**
+ * Variable: countError
+ * 
+ * Holds the localized error message to be displayed if the number of
+ * connections for which the rule applies is smaller than <min> or greater
+ * than <max>.
+ */
+mxMultiplicity.prototype.countError = null;
+
+/**
+ * Variable: typeError
+ * 
+ * Holds the localized error message to be displayed if the type of the
+ * neighbor for a connection does not match the rule.
+ */
+mxMultiplicity.prototype.typeError = null;
+
+/**
+ * Function: check
+ * 
+ * Checks the multiplicity for the given arguments and returns the error
+ * for the given connection or null if the multiplicity does not apply.
+ *  
+ * Parameters:
+ * 
+ * graph - Reference to the enclosing <mxGraph> instance.
+ * edge - <mxCell> that represents the edge to validate.
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ * sourceOut - Number of outgoing edges from the source terminal.
+ * targetIn - Number of incoming edges for the target terminal.
+ */
+mxMultiplicity.prototype.check = function(graph, edge, source, target, sourceOut, targetIn)
+{
+	var error = '';
+
+	if ((this.source && this.checkTerminal(graph, source, edge)) ||
+		(!this.source && this.checkTerminal(graph, target, edge)))
+	{
+		if (this.countError != null && 
+			((this.source && (this.max == 0 || (sourceOut >= this.max))) ||
+			(!this.source && (this.max == 0 || (targetIn >= this.max)))))
+		{
+			error += this.countError + '\n';
+		}
+
+		if (this.validNeighbors != null && this.typeError != null && this.validNeighbors.length > 0)
+		{
+			var isValid = this.checkNeighbors(graph, edge, source, target);
+
+			if (!isValid)
+			{
+				error += this.typeError + '\n';
+			}
+		}
+	}
+	
+	return (error.length > 0) ? error : null;
+};
+
+/**
+ * Function: checkNeighbors
+ * 
+ * Checks if there are any valid neighbours in <validNeighbors>. This is only
+ * called if <validNeighbors> is a non-empty array.
+ */
+mxMultiplicity.prototype.checkNeighbors = function(graph, edge, source, target)
+{
+	var sourceValue = graph.model.getValue(source);
+	var targetValue = graph.model.getValue(target);
+	var isValid = !this.validNeighborsAllowed;
+	var valid = this.validNeighbors;
+	
+	for (var j = 0; j < valid.length; j++)
+	{
+		if (this.source &&
+			this.checkType(graph, targetValue, valid[j]))
+		{
+			isValid = this.validNeighborsAllowed;
+			break;
+		}
+		else if (!this.source && 
+			this.checkType(graph, sourceValue, valid[j]))
+		{
+			isValid = this.validNeighborsAllowed;
+			break;
+		}
+	}
+	
+	return isValid;
+};
+
+/**
+ * Function: checkTerminal
+ * 
+ * Checks the given terminal cell and returns true if this rule applies. The
+ * given cell is the source or target of the given edge, depending on
+ * <source>. This implementation uses <checkType> on the terminal's value.
+ */
+mxMultiplicity.prototype.checkTerminal = function(graph, terminal, edge)
+{
+	var value = graph.model.getValue(terminal);
+	
+	return this.checkType(graph, value, this.type, this.attr, this.value);
+};
+
+/**
+ * Function: checkType
+ * 
+ * Checks the type of the given value.
+ */
+mxMultiplicity.prototype.checkType = function(graph, value, type, attr, attrValue)
+{
+	if (value != null)
+	{
+		if (!isNaN(value.nodeType)) // Checks if value is a DOM node
+		{
+			return mxUtils.isNode(value, type, attr, attrValue);
+		}
+		else
+		{
+			return value == type;
+		}
+	}
+	
+	return false;
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/view/mxOutline.js b/airavata-kubernetes/workflow-composer/src/js/view/mxOutline.js
new file mode 100644
index 0000000..9819cec
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/view/mxOutline.js
@@ -0,0 +1,761 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxOutline
+ *
+ * Implements an outline (aka overview) for a graph. Set <updateOnPan> to true
+ * to enable updates while the source graph is panning.
+ * 
+ * Example:
+ * 
+ * (code)
+ * var outline = new mxOutline(graph, div);
+ * (end)
+ * 
+ * If an outline is used in an <mxWindow> in IE8 standards mode, the following
+ * code makes sure that the shadow filter is not inherited and that any
+ * transparent elements in the graph do not show the page background, but the
+ * background of the graph container.
+ * 
+ * (code)
+ * if (document.documentMode == 8)
+ * {
+ *   container.style.filter = 'progid:DXImageTransform.Microsoft.alpha(opacity=100)';
+ * }
+ * (end)
+ * 
+ * To move the graph to the top, left corner the following code can be used.
+ * 
+ * (code)
+ * var scale = graph.view.scale;
+ * var bounds = graph.getGraphBounds();
+ * graph.view.setTranslate(-bounds.x / scale, -bounds.y / scale);
+ * (end)
+ * 
+ * To toggle the suspended mode, the following can be used.
+ * 
+ * (code)
+ * outline.suspended = !outln.suspended;
+ * if (!outline.suspended)
+ * {
+ *   outline.update(true);
+ * }
+ * (end)
+ * 
+ * Constructor: mxOutline
+ *
+ * Constructs a new outline for the specified graph inside the given
+ * container.
+ * 
+ * Parameters:
+ * 
+ * source - <mxGraph> to create the outline for.
+ * container - DOM node that will contain the outline.
+ */
+function mxOutline(source, container)
+{
+	this.source = source;
+
+	if (container != null)
+	{
+		this.init(container);
+	}
+};
+
+/**
+ * Function: source
+ * 
+ * Reference to the source <mxGraph>.
+ */
+mxOutline.prototype.source = null;
+
+/**
+ * Function: outline
+ * 
+ * Reference to the <mxGraph> that renders the outline.
+ */
+mxOutline.prototype.outline = null;
+
+/**
+ * Function: graphRenderHint
+ * 
+ * Renderhint to be used for the outline graph. Default is faster.
+ */
+mxOutline.prototype.graphRenderHint = mxConstants.RENDERING_HINT_FASTER;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies if events are handled. Default is true.
+ */
+mxOutline.prototype.enabled = true;
+
+/**
+ * Variable: showViewport
+ * 
+ * Specifies a viewport rectangle should be shown. Default is true.
+ */
+mxOutline.prototype.showViewport = true;
+
+/**
+ * Variable: border
+ * 
+ * Border to be added at the bottom and right. Default is 10.
+ */
+mxOutline.prototype.border = 10;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies the size of the sizer handler. Default is 8.
+ */
+mxOutline.prototype.sizerSize = 8;
+
+/**
+ * Variable: labelsVisible
+ * 
+ * Specifies if labels should be visible in the outline. Default is false.
+ */
+mxOutline.prototype.labelsVisible = false;
+
+/**
+ * Variable: updateOnPan
+ * 
+ * Specifies if <update> should be called for <mxEvent.PAN> in the source
+ * graph. Default is false.
+ */
+mxOutline.prototype.updateOnPan = false;
+
+/**
+ * Variable: sizerImage
+ * 
+ * Optional <mxImage> to be used for the sizer. Default is null.
+ */
+mxOutline.prototype.sizerImage = null;
+
+/**
+ * Variable: minScale
+ * 
+ * Minimum scale to be used. Default is 0.001.
+ */
+mxOutline.prototype.minScale = 0.0001;
+
+/**
+ * Variable: suspended
+ * 
+ * Optional boolean flag to suspend updates. Default is false. This flag will
+ * also suspend repaints of the outline. To toggle this switch, use the
+ * following code.
+ * 
+ * (code)
+ * nav.suspended = !nav.suspended;
+ * 
+ * if (!nav.suspended)
+ * {
+ *   nav.update(true);
+ * }
+ * (end)
+ */
+mxOutline.prototype.suspended = false;
+
+/**
+ * Variable: forceVmlHandles
+ * 
+ * Specifies if VML should be used to render the handles in this control. This
+ * is true for IE8 standards mode and false for all other browsers and modes.
+ * This is a workaround for rendering issues of HTML elements over elements
+ * with filters in IE 8 standards mode.
+ */
+mxOutline.prototype.forceVmlHandles = document.documentMode == 8;
+
+/**
+ * Function: createGraph
+ * 
+ * Creates the <mxGraph> used in the outline.
+ */
+mxOutline.prototype.createGraph = function(container)
+{
+	var graph = new mxGraph(container, this.source.getModel(), this.graphRenderHint, this.source.getStylesheet());
+	graph.foldingEnabled = false;
+	graph.autoScroll = false;
+	
+	return graph;
+};
+
+/**
+ * Function: init
+ * 
+ * Initializes the outline inside the given container.
+ */
+mxOutline.prototype.init = function(container)
+{
+	this.outline = this.createGraph(container);
+	
+	// Do not repaint when suspended
+	var outlineGraphModelChanged = this.outline.graphModelChanged;
+	this.outline.graphModelChanged = mxUtils.bind(this, function(changes)
+	{
+		if (!this.suspended && this.outline != null)
+		{
+			outlineGraphModelChanged.apply(this.outline, arguments);
+		}
+	});
+
+	// Enables faster painting in SVG
+	if (mxClient.IS_SVG)
+	{
+		var node = this.outline.getView().getCanvas().parentNode;
+		node.setAttribute('shape-rendering', 'optimizeSpeed');
+		node.setAttribute('image-rendering', 'optimizeSpeed');
+	}
+	
+	// Hides cursors and labels
+	this.outline.labelsVisible = this.labelsVisible;
+	this.outline.setEnabled(false);
+	
+	this.updateHandler = mxUtils.bind(this, function(sender, evt)
+	{
+		if (!this.suspended && !this.active)
+		{
+			this.update();
+		}
+	});
+	
+	// Updates the scale of the outline after a change of the main graph
+	this.source.getModel().addListener(mxEvent.CHANGE, this.updateHandler);
+	this.outline.addMouseListener(this);
+	
+	// Adds listeners to keep the outline in sync with the source graph
+	var view = this.source.getView();
+	view.addListener(mxEvent.SCALE, this.updateHandler);
+	view.addListener(mxEvent.TRANSLATE, this.updateHandler);
+	view.addListener(mxEvent.SCALE_AND_TRANSLATE, this.updateHandler);
+	view.addListener(mxEvent.DOWN, this.updateHandler);
+	view.addListener(mxEvent.UP, this.updateHandler);
+
+	// Updates blue rectangle on scroll
+	mxEvent.addListener(this.source.container, 'scroll', this.updateHandler);
+	
+	this.panHandler = mxUtils.bind(this, function(sender)
+	{
+		if (this.updateOnPan)
+		{
+			this.updateHandler.apply(this, arguments);
+		}
+	});
+	this.source.addListener(mxEvent.PAN, this.panHandler);
+	
+	// Refreshes the graph in the outline after a refresh of the main graph
+	this.refreshHandler = mxUtils.bind(this, function(sender)
+	{
+		this.outline.setStylesheet(this.source.getStylesheet());
+		this.outline.refresh();
+	});
+	this.source.addListener(mxEvent.REFRESH, this.refreshHandler);
+
+	// Creates the blue rectangle for the viewport
+	this.bounds = new mxRectangle(0, 0, 0, 0);
+	this.selectionBorder = new mxRectangleShape(this.bounds, null,
+		mxConstants.OUTLINE_COLOR, mxConstants.OUTLINE_STROKEWIDTH);
+	this.selectionBorder.dialect = this.outline.dialect;
+
+	if (this.forceVmlHandles)
+	{
+		this.selectionBorder.isHtmlAllowed = function()
+		{
+			return false;
+		};
+	}
+	
+	this.selectionBorder.init(this.outline.getView().getOverlayPane());
+
+	// Handles event by catching the initial pointer start and then listening to the
+	// complete gesture on the event target. This is needed because all the events
+	// are routed via the initial element even if that element is removed from the
+	// DOM, which happens when we repaint the selection border and zoom handles.
+	var handler = mxUtils.bind(this, function(evt)
+	{
+		var t = mxEvent.getSource(evt);
+		
+		var redirect = mxUtils.bind(this, function(evt)
+		{
+			this.outline.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt));
+		});
+		
+		var redirect2 = mxUtils.bind(this, function(evt)
+		{
+			mxEvent.removeGestureListeners(t, null, redirect, redirect2);
+			this.outline.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt));
+		});
+		
+		mxEvent.addGestureListeners(t, null, redirect, redirect2);
+		this.outline.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt));
+	});
+	
+	mxEvent.addGestureListeners(this.selectionBorder.node, handler);
+
+	// Creates a small blue rectangle for sizing (sizer handle)
+	this.sizer = this.createSizer();
+	
+	if (this.forceVmlHandles)
+	{
+		this.sizer.isHtmlAllowed = function()
+		{
+			return false;
+		};
+	}
+	
+	this.sizer.init(this.outline.getView().getOverlayPane());
+	
+	if (this.enabled)
+	{
+		this.sizer.node.style.cursor = 'nwse-resize';
+	}
+	
+	mxEvent.addGestureListeners(this.sizer.node, handler);
+
+	this.selectionBorder.node.style.display = (this.showViewport) ? '' : 'none';
+	this.sizer.node.style.display = this.selectionBorder.node.style.display;
+	this.selectionBorder.node.style.cursor = 'move';
+
+	this.update(false);
+};
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxOutline.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ * 
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean that specifies the new enabled state.
+ */
+mxOutline.prototype.setEnabled = function(value)
+{
+	this.enabled = value;
+};
+
+/**
+ * Function: setZoomEnabled
+ * 
+ * Enables or disables the zoom handling by showing or hiding the respective
+ * handle.
+ * 
+ * Parameters:
+ * 
+ * value - Boolean that specifies the new enabled state.
+ */
+mxOutline.prototype.setZoomEnabled = function(value)
+{
+	this.sizer.node.style.visibility = (value) ? 'visible' : 'hidden';
+};
+
+/**
+ * Function: refresh
+ * 
+ * Invokes <update> and revalidate the outline. This method is deprecated.
+ */
+mxOutline.prototype.refresh = function()
+{
+	this.update(true);
+};
+
+/**
+ * Function: createSizer
+ * 
+ * Creates the shape used as the sizer.
+ */
+mxOutline.prototype.createSizer = function()
+{
+	if (this.sizerImage != null)
+	{
+		var sizer = new mxImageShape(new mxRectangle(0, 0, this.sizerImage.width, this.sizerImage.height), this.sizerImage.src);
+		sizer.dialect = this.outline.dialect;
+		
+		return sizer;
+	}
+	else
+	{
+		var sizer = new mxRectangleShape(new mxRectangle(0, 0, this.sizerSize, this.sizerSize),
+			mxConstants.OUTLINE_HANDLE_FILLCOLOR, mxConstants.OUTLINE_HANDLE_STROKECOLOR);
+		sizer.dialect = this.outline.dialect;
+	
+		return sizer;
+	}
+};
+
+/**
+ * Function: getSourceContainerSize
+ * 
+ * Returns the size of the source container.
+ */
+mxOutline.prototype.getSourceContainerSize = function()
+{
+	return new mxRectangle(0, 0, this.source.container.scrollWidth, this.source.container.scrollHeight);
+};
+
+/**
+ * Function: getOutlineOffset
+ * 
+ * Returns the offset for drawing the outline graph.
+ */
+mxOutline.prototype.getOutlineOffset = function(scale)
+{
+	return null;
+};
+
+/**
+ * Function: getOutlineOffset
+ * 
+ * Returns the offset for drawing the outline graph.
+ */
+mxOutline.prototype.getSourceGraphBounds = function()
+{
+	return this.source.getGraphBounds();
+};
+
+/**
+ * Function: update
+ * 
+ * Updates the outline.
+ */
+mxOutline.prototype.update = function(revalidate)
+{
+	if (this.source != null && this.outline != null)
+	{
+		var sourceScale = this.source.view.scale;
+		var scaledGraphBounds = this.getSourceGraphBounds();
+		var unscaledGraphBounds = new mxRectangle(scaledGraphBounds.x / sourceScale + this.source.panDx,
+				scaledGraphBounds.y / sourceScale + this.source.panDy, scaledGraphBounds.width / sourceScale,
+				scaledGraphBounds.height / sourceScale);
+
+		var unscaledFinderBounds = new mxRectangle(0, 0,
+			this.source.container.clientWidth / sourceScale,
+			this.source.container.clientHeight / sourceScale);
+		
+		var union = unscaledGraphBounds.clone();
+		union.add(unscaledFinderBounds);
+	
+		// Zooms to the scrollable area if that is bigger than the graph
+		var size = this.getSourceContainerSize();
+		var completeWidth = Math.max(size.width / sourceScale, union.width);
+		var completeHeight = Math.max(size.height / sourceScale, union.height);
+	
+		var availableWidth = Math.max(0, this.outline.container.clientWidth - this.border);
+		var availableHeight = Math.max(0, this.outline.container.clientHeight - this.border);
+		
+		var outlineScale = Math.min(availableWidth / completeWidth, availableHeight / completeHeight);
+		var scale = (isNaN(outlineScale)) ? this.minScale : Math.max(this.minScale, outlineScale);
+
+		if (scale > 0)
+		{
+			if (this.outline.getView().scale != scale)
+			{
+				this.outline.getView().scale = scale;
+				revalidate = true;
+			}
+		
+			var navView = this.outline.getView();
+			
+			if (navView.currentRoot != this.source.getView().currentRoot)
+			{
+				navView.setCurrentRoot(this.source.getView().currentRoot);
+			}
+
+			var t = this.source.view.translate;
+			var tx = t.x + this.source.panDx;
+			var ty = t.y + this.source.panDy;
+			
+			var off = this.getOutlineOffset(scale);
+			
+			if (off != null)
+			{
+				tx += off.x;
+				ty += off.y;
+			}
+			
+			if (unscaledGraphBounds.x < 0)
+			{
+				tx = tx - unscaledGraphBounds.x;
+			}
+			if (unscaledGraphBounds.y < 0)
+			{
+				ty = ty - unscaledGraphBounds.y;
+			}
+			
+			if (navView.translate.x != tx || navView.translate.y != ty)
+			{
+				navView.translate.x = tx;
+				navView.translate.y = ty;
+				revalidate = true;
+			}
+		
+			// Prepares local variables for computations
+			var t2 = navView.translate;
+			scale = this.source.getView().scale;
+			var scale2 = scale / navView.scale;
+			var scale3 = 1.0 / navView.scale;
+			var container = this.source.container;
+			
+			// Updates the bounds of the viewrect in the navigation
+			this.bounds = new mxRectangle(
+				(t2.x - t.x - this.source.panDx) / scale3,
+				(t2.y - t.y - this.source.panDy) / scale3,
+				(container.clientWidth / scale2),
+				(container.clientHeight / scale2));
+			
+			// Adds the scrollbar offset to the finder
+			this.bounds.x += this.source.container.scrollLeft * navView.scale / scale;
+			this.bounds.y += this.source.container.scrollTop * navView.scale / scale;
+			
+			var b = this.selectionBorder.bounds;
+			
+			if (b.x != this.bounds.x || b.y != this.bounds.y || b.width != this.bounds.width || b.height != this.bounds.height)
+			{
+				this.selectionBorder.bounds = this.bounds;
+				this.selectionBorder.redraw();
+			}
+		
+			// Updates the bounds of the zoom handle at the bottom right
+			var b = this.sizer.bounds;
+			var b2 = new mxRectangle(this.bounds.x + this.bounds.width - b.width / 2,
+					this.bounds.y + this.bounds.height - b.height / 2, b.width, b.height);
+
+			if (b.x != b2.x || b.y != b2.y || b.width != b2.width || b.height != b2.height)
+			{
+				this.sizer.bounds = b2;
+				
+				// Avoids update of visibility in redraw for VML
+				if (this.sizer.node.style.visibility != 'hidden')
+				{
+					this.sizer.redraw();
+				}
+			}
+
+			if (revalidate)
+			{
+				this.outline.view.revalidate();
+			}
+		}
+	}
+};
+
+/**
+ * Function: mouseDown
+ * 
+ * Handles the event by starting a translation or zoom.
+ */
+mxOutline.prototype.mouseDown = function(sender, me)
+{
+	if (this.enabled && this.showViewport)
+	{
+		var tol = (!mxEvent.isMouseEvent(me.getEvent())) ? this.source.tolerance : 0;
+		var hit = (this.source.allowHandleBoundsCheck && (mxClient.IS_IE || tol > 0)) ?
+				new mxRectangle(me.getGraphX() - tol, me.getGraphY() - tol, 2 * tol, 2 * tol) : null;
+		this.zoom = me.isSource(this.sizer) || (hit != null && mxUtils.intersects(shape.bounds, hit));
+		this.startX = me.getX();
+		this.startY = me.getY();
+		this.active = true;
+
+		if (this.source.useScrollbarsForPanning && mxUtils.hasScrollbars(this.source.container))
+		{
+			this.dx0 = this.source.container.scrollLeft;
+			this.dy0 = this.source.container.scrollTop;
+		}
+		else
+		{
+			this.dx0 = 0;
+			this.dy0 = 0;
+		}
+	}
+
+	me.consume();
+};
+
+/**
+ * Function: mouseMove
+ * 
+ * Handles the event by previewing the viewrect in <graph> and updating the
+ * rectangle that represents the viewrect in the outline.
+ */
+mxOutline.prototype.mouseMove = function(sender, me)
+{
+	if (this.active)
+	{
+		this.selectionBorder.node.style.display = (this.showViewport) ? '' : 'none';
+		this.sizer.node.style.display = this.selectionBorder.node.style.display; 
+
+		var delta = this.getTranslateForEvent(me);
+		var dx = delta.x;
+		var dy = delta.y;
+		var bounds = null;
+		
+		if (!this.zoom)
+		{
+			// Previews the panning on the source graph
+			var scale = this.outline.getView().scale;
+			bounds = new mxRectangle(this.bounds.x + dx,
+				this.bounds.y + dy, this.bounds.width, this.bounds.height);
+			this.selectionBorder.bounds = bounds;
+			this.selectionBorder.redraw();
+			dx /= scale;
+			dx *= this.source.getView().scale;
+			dy /= scale;
+			dy *= this.source.getView().scale;
+			this.source.panGraph(-dx - this.dx0, -dy - this.dy0);
+		}
+		else
+		{
+			// Does *not* preview zooming on the source graph
+			var container = this.source.container;
+			var viewRatio = container.clientWidth / container.clientHeight;
+			dy = dx / viewRatio;
+			bounds = new mxRectangle(this.bounds.x,
+				this.bounds.y,
+				Math.max(1, this.bounds.width + dx),
+				Math.max(1, this.bounds.height + dy));
+			this.selectionBorder.bounds = bounds;
+			this.selectionBorder.redraw();
+		}
+		
+		// Updates the zoom handle
+		var b = this.sizer.bounds;
+		this.sizer.bounds = new mxRectangle(
+			bounds.x + bounds.width - b.width / 2,
+			bounds.y + bounds.height - b.height / 2,
+			b.width, b.height);
+		
+		// Avoids update of visibility in redraw for VML
+		if (this.sizer.node.style.visibility != 'hidden')
+		{
+			this.sizer.redraw();
+		}
+		
+		me.consume();
+	}
+};
+
+/**
+ * Function: getTranslateForEvent
+ * 
+ * Gets the translate for the given mouse event. Here is an example to limit
+ * the outline to stay within positive coordinates:
+ * 
+ * (code)
+ * outline.getTranslateForEvent = function(me)
+ * {
+ *   var pt = new mxPoint(me.getX() - this.startX, me.getY() - this.startY);
+ *   
+ *   if (!this.zoom)
+ *   {
+ *     var tr = this.source.view.translate;
+ *     pt.x = Math.max(tr.x * this.outline.view.scale, pt.x);
+ *     pt.y = Math.max(tr.y * this.outline.view.scale, pt.y);
+ *   }
+ *   
+ *   return pt;
+ * };
+ * (end)
+ */
+mxOutline.prototype.getTranslateForEvent = function(me)
+{
+	return new mxPoint(me.getX() - this.startX, me.getY() - this.startY);
+};
+
+/**
+ * Function: mouseUp
+ * 
+ * Handles the event by applying the translation or zoom to <graph>.
+ */
+mxOutline.prototype.mouseUp = function(sender, me)
+{
+	if (this.active)
+	{
+		var delta = this.getTranslateForEvent(me);
+		var dx = delta.x;
+		var dy = delta.y;
+		
+		if (Math.abs(dx) > 0 || Math.abs(dy) > 0)
+		{
+			if (!this.zoom)
+			{
+				// Applies the new translation if the source
+				// has no scrollbars
+				if (!this.source.useScrollbarsForPanning ||
+					!mxUtils.hasScrollbars(this.source.container))
+				{
+					this.source.panGraph(0, 0);
+					dx /= this.outline.getView().scale;
+					dy /= this.outline.getView().scale;
+					var t = this.source.getView().translate;
+					this.source.getView().setTranslate(t.x - dx, t.y - dy);
+				}
+			}
+			else
+			{
+				// Applies the new zoom
+				var w = this.selectionBorder.bounds.width;
+				var scale = this.source.getView().scale;
+				this.source.zoomTo(Math.max(this.minScale, scale - (dx * scale) / w), false);
+			}
+
+			this.update();
+			me.consume();
+		}
+			
+		// Resets the state of the handler
+		this.index = null;
+		this.active = false;
+	}
+};
+
+/**
+ * Function: destroy
+ * 
+ * Destroy this outline and removes all listeners from <source>.
+ */
+mxOutline.prototype.destroy = function()
+{
+	if (this.source != null)
+	{
+		this.source.removeListener(this.panHandler);
+		this.source.removeListener(this.refreshHandler);
+		this.source.getModel().removeListener(this.updateHandler);
+		this.source.getView().removeListener(this.updateHandler);
+		mxEvent.addListener(this.source.container, 'scroll', this.updateHandler);
+		this.source = null;
+	}
+	
+	if (this.outline != null)
+	{
+		this.outline.removeMouseListener(this);
+		this.outline.destroy();
+		this.outline = null;
+	}
+
+	if (this.selectionBorder != null)
+	{
+		this.selectionBorder.destroy();
+		this.selectionBorder = null;
+	}
+	
+	if (this.sizer != null)
+	{
+		this.sizer.destroy();
+		this.sizer = null;
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/view/mxPerimeter.js b/airavata-kubernetes/workflow-composer/src/js/view/mxPerimeter.js
new file mode 100644
index 0000000..c1d5ffc
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/view/mxPerimeter.js
@@ -0,0 +1,921 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+var mxPerimeter =
+{
+	/**
+	 * Class: mxPerimeter
+	 * 
+	 * Provides various perimeter functions to be used in a style
+	 * as the value of <mxConstants.STYLE_PERIMETER>. Perimeters for
+	 * rectangle, circle, rhombus and triangle are available.
+	 *
+	 * Example:
+	 * 
+	 * (code)
+	 * <add as="perimeter">mxPerimeter.RectanglePerimeter</add>
+	 * (end)
+	 * 
+	 * Or programmatically:
+	 * 
+	 * (code)
+	 * style[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter;
+	 * (end)
+	 * 
+	 * When adding new perimeter functions, it is recommended to use the 
+	 * mxPerimeter-namespace as follows:
+	 * 
+	 * (code)
+	 * mxPerimeter.CustomPerimeter = function (bounds, vertex, next, orthogonal)
+	 * {
+	 *   var x = 0; // Calculate x-coordinate
+	 *   var y = 0; // Calculate y-coordainte
+	 *   
+	 *   return new mxPoint(x, y);
+	 * }
+	 * (end)
+	 * 
+	 * The new perimeter should then be registered in the <mxStyleRegistry> as follows:
+	 * (code)
+	 * mxStyleRegistry.putValue('customPerimeter', mxPerimeter.CustomPerimeter);
+	 * (end)
+	 * 
+	 * The custom perimeter above can now be used in a specific vertex as follows:
+	 * 
+	 * (code)
+	 * model.setStyle(vertex, 'perimeter=customPerimeter');
+	 * (end)
+	 * 
+	 * Note that the key of the <mxStyleRegistry> entry for the function should
+	 * be used in string values, unless <mxGraphView.allowEval> is true, in
+	 * which case you can also use mxPerimeter.CustomPerimeter for the value in
+	 * the cell style above.
+	 * 
+	 * Or it can be used for all vertices in the graph as follows:
+	 * 
+	 * (code)
+	 * var style = graph.getStylesheet().getDefaultVertexStyle();
+	 * style[mxConstants.STYLE_PERIMETER] = mxPerimeter.CustomPerimeter;
+	 * (end)
+	 * 
+	 * Note that the object can be used directly when programmatically setting
+	 * the value, but the key in the <mxStyleRegistry> should be used when
+	 * setting the value via a key, value pair in a cell style.
+	 * 
+	 * The parameters are explained in <RectanglePerimeter>.
+	 * 
+	 * Function: RectanglePerimeter
+	 * 
+	 * Describes a rectangular perimeter for the given bounds.
+	 *
+	 * Parameters:
+	 * 
+	 * bounds - <mxRectangle> that represents the absolute bounds of the
+	 * vertex.
+	 * vertex - <mxCellState> that represents the vertex.
+	 * next - <mxPoint> that represents the nearest neighbour point on the
+	 * given edge.
+	 * orthogonal - Boolean that specifies if the orthogonal projection onto
+	 * the perimeter should be returned. If this is false then the intersection
+	 * of the perimeter and the line between the next and the center point is
+	 * returned.
+	 */
+	RectanglePerimeter: function (bounds, vertex, next, orthogonal)
+	{
+		var cx = bounds.getCenterX();
+		var cy = bounds.getCenterY();
+		var dx = next.x - cx;
+		var dy = next.y - cy;
+		var alpha = Math.atan2(dy, dx);
+		var p = new mxPoint(0, 0);
+		var pi = Math.PI;
+		var pi2 = Math.PI/2;
+		var beta = pi2 - alpha;
+		var t = Math.atan2(bounds.height, bounds.width);
+		
+		if (alpha < -pi + t || alpha > pi - t)
+		{
+			// Left edge
+			p.x = bounds.x;
+			p.y = cy - bounds.width * Math.tan(alpha) / 2;
+		}
+		else if (alpha < -t)
+		{
+			// Top Edge
+			p.y = bounds.y;
+			p.x = cx - bounds.height * Math.tan(beta) / 2;
+		}
+		else if (alpha < t)
+		{
+			// Right Edge
+			p.x = bounds.x + bounds.width;
+			p.y = cy + bounds.width * Math.tan(alpha) / 2;
+		}
+		else
+		{
+			// Bottom Edge
+			p.y = bounds.y + bounds.height;
+			p.x = cx + bounds.height * Math.tan(beta) / 2;
+		}
+		
+		if (orthogonal)
+		{
+			if (next.x >= bounds.x &&
+				next.x <= bounds.x + bounds.width)
+			{
+				p.x = next.x;
+			}
+			else if (next.y >= bounds.y &&
+					   next.y <= bounds.y + bounds.height)
+			{
+				p.y = next.y;
+			}
+			if (next.x < bounds.x)
+			{
+				p.x = bounds.x;
+			}
+			else if (next.x > bounds.x + bounds.width)
+			{
+				p.x = bounds.x + bounds.width;
+			}
+			if (next.y < bounds.y)
+			{
+				p.y = bounds.y;
+			}
+			else if (next.y > bounds.y + bounds.height)
+			{
+				p.y = bounds.y + bounds.height;
+			}
+		}
+		
+		return p;
+	},
+
+	/**
+	 * Function: EllipsePerimeter
+	 * 
+	 * Describes an elliptic perimeter. See <RectanglePerimeter>
+	 * for a description of the parameters.
+	 */
+	EllipsePerimeter: function (bounds, vertex, next, orthogonal)
+	{
+		var x = bounds.x;
+		var y = bounds.y;
+		var a = bounds.width / 2;
+		var b = bounds.height / 2;
+		var cx = x + a;
+		var cy = y + b;
+		var px = next.x;
+		var py = next.y;
+		
+		// Calculates straight line equation through
+		// point and ellipse center y = d * x + h
+		var dx = parseInt(px - cx);
+		var dy = parseInt(py - cy);
+		
+		if (dx == 0 && dy != 0)
+		{
+			return new mxPoint(cx, cy + b * dy / Math.abs(dy));
+		}
+		else if (dx == 0 && dy == 0)
+		{
+			return new mxPoint(px, py);
+		}
+
+		if (orthogonal)
+		{
+			if (py >= y && py <= y + bounds.height)
+			{
+				var ty = py - cy;
+				var tx = Math.sqrt(a*a*(1-(ty*ty)/(b*b))) || 0;
+				
+				if (px <= x)
+				{
+					tx = -tx;
+				}
+				
+				return new mxPoint(cx+tx, py);
+			}
+			
+			if (px >= x && px <= x + bounds.width)
+			{
+				var tx = px - cx;
+				var ty = Math.sqrt(b*b*(1-(tx*tx)/(a*a))) || 0;
+				
+				if (py <= y)
+				{
+					ty = -ty;	
+				}
+				
+				return new mxPoint(px, cy+ty);
+			}
+		}
+		
+		// Calculates intersection
+		var d = dy / dx;
+		var h = cy - d * cx;
+		var e = a * a * d * d + b * b;
+		var f = -2 * cx * e;
+		var g = a * a * d * d * cx * cx +
+				b * b * cx * cx -
+				a * a * b * b;
+		var det = Math.sqrt(f * f - 4 * e * g);
+		
+		// Two solutions (perimeter points)
+		var xout1 = (-f + det) / (2 * e);
+		var xout2 = (-f - det) / (2 * e);
+		var yout1 = d * xout1 + h;
+		var yout2 = d * xout2 + h;
+		var dist1 = Math.sqrt(Math.pow((xout1 - px), 2)
+					+ Math.pow((yout1 - py), 2));
+		var dist2 = Math.sqrt(Math.pow((xout2 - px), 2)
+					+ Math.pow((yout2 - py), 2));
+					
+		// Correct solution
+		var xout = 0;
+		var yout = 0;
+		
+		if (dist1 < dist2)
+		{
+			xout = xout1;
+			yout = yout1;
+		}
+		else
+		{
+			xout = xout2;
+			yout = yout2;
+		}
+		
+		return new mxPoint(xout, yout);
+	},
+
+	/**
+	 * Function: RhombusPerimeter
+	 * 
+	 * Describes a rhombus (aka diamond) perimeter. See <RectanglePerimeter>
+	 * for a description of the parameters.
+	 */
+	RhombusPerimeter: function (bounds, vertex, next, orthogonal)
+	{
+		var x = bounds.x;
+		var y = bounds.y;
+		var w = bounds.width;
+		var h = bounds.height;
+		
+		var cx = x + w / 2;
+		var cy = y + h / 2;
+
+		var px = next.x;
+		var py = next.y;
+
+		// Special case for intersecting the diamond's corners
+		if (cx == px)
+		{
+			if (cy > py)
+			{
+				return new mxPoint(cx, y); // top
+			}
+			else
+			{
+				return new mxPoint(cx, y + h); // bottom
+			}
+		}
+		else if (cy == py)
+		{
+			if (cx > px)
+			{
+				return new mxPoint(x, cy); // left
+			}
+			else
+			{
+				return new mxPoint(x + w, cy); // right
+			}
+		}
+		
+		var tx = cx;
+		var ty = cy;
+		
+		if (orthogonal)
+		{
+			if (px >= x && px <= x + w)
+			{
+				tx = px;
+			}
+			else if (py >= y && py <= y + h)
+			{
+				ty = py;
+			}
+		}
+		
+		// In which quadrant will the intersection be?
+		// set the slope and offset of the border line accordingly
+		if (px < cx)
+		{
+			if (py < cy)
+			{
+				return mxUtils.intersection(px, py, tx, ty, cx, y, x, cy);
+			}
+			else
+			{
+				return mxUtils.intersection(px, py, tx, ty, cx, y + h, x, cy);
+			}
+		}
+		else if (py < cy)
+		{
+			return mxUtils.intersection(px, py, tx, ty, cx, y, x + w, cy);
+		}
+		else
+		{
+			return mxUtils.intersection(px, py, tx, ty, cx, y + h, x + w, cy);
+		}
+	},
+	
+	/**
+	 * Function: TrianglePerimeter
+	 * 
+	 * Describes a triangle perimeter. See <RectanglePerimeter>
+	 * for a description of the parameters.
+	 */
+	TrianglePerimeter: function (bounds, vertex, next, orthogonal)
+	{
+		var direction = (vertex != null) ?
+			vertex.style[mxConstants.STYLE_DIRECTION] : null;
+		var vertical = direction == mxConstants.DIRECTION_NORTH ||
+			direction == mxConstants.DIRECTION_SOUTH;
+
+		var x = bounds.x;
+		var y = bounds.y;
+		var w = bounds.width;
+		var h = bounds.height;
+		
+		var cx = x + w / 2;
+		var cy = y + h / 2;
+		
+		var start = new mxPoint(x, y);
+		var corner = new mxPoint(x + w, cy);
+		var end = new mxPoint(x, y + h);
+		
+		if (direction == mxConstants.DIRECTION_NORTH)
+		{
+			start = end;
+			corner = new mxPoint(cx, y);
+			end = new mxPoint(x + w, y + h);
+		}
+		else if (direction == mxConstants.DIRECTION_SOUTH)
+		{
+			corner = new mxPoint(cx, y + h);
+			end = new mxPoint(x + w, y);
+		}
+		else if (direction == mxConstants.DIRECTION_WEST)
+		{
+			start = new mxPoint(x + w, y);
+			corner = new mxPoint(x, cy);
+			end = new mxPoint(x + w, y + h);
+		}
+
+		var dx = next.x - cx;
+		var dy = next.y - cy;
+
+		var alpha = (vertical) ? Math.atan2(dx, dy) : Math.atan2(dy, dx);
+		var t = (vertical) ? Math.atan2(w, h) : Math.atan2(h, w);
+		
+		var base = false;
+		
+		if (direction == mxConstants.DIRECTION_NORTH ||
+			direction == mxConstants.DIRECTION_WEST)
+		{
+			base = alpha > -t && alpha < t;
+		}
+		else
+		{
+			base = alpha < -Math.PI + t || alpha > Math.PI - t;	
+		}
+
+		var result = null;			
+
+		if (base)
+		{
+			if (orthogonal && ((vertical && next.x >= start.x && next.x <= end.x) ||
+				(!vertical && next.y >= start.y && next.y <= end.y)))
+			{
+				if (vertical)
+				{
+					result = new mxPoint(next.x, start.y);
+				}
+				else
+				{
+					result = new mxPoint(start.x, next.y);
+				}
+			}
+			else
+			{
+				if (direction == mxConstants.DIRECTION_NORTH)
+				{
+					result = new mxPoint(x + w / 2 + h * Math.tan(alpha) / 2,
+						y + h);
+				}
+				else if (direction == mxConstants.DIRECTION_SOUTH)
+				{
+					result = new mxPoint(x + w / 2 - h * Math.tan(alpha) / 2,
+						y);
+				}
+				else if (direction == mxConstants.DIRECTION_WEST)
+				{
+					result = new mxPoint(x + w, y + h / 2 +
+						w * Math.tan(alpha) / 2);
+				}
+				else
+				{
+					result = new mxPoint(x, y + h / 2 -
+						w * Math.tan(alpha) / 2);
+				}
+			}
+		}
+		else
+		{
+			if (orthogonal)
+			{
+				var pt = new mxPoint(cx, cy);
+		
+				if (next.y >= y && next.y <= y + h)
+				{
+					pt.x = (vertical) ? cx : (
+						(direction == mxConstants.DIRECTION_WEST) ?
+							x + w : x);
+					pt.y = next.y;
+				}
+				else if (next.x >= x && next.x <= x + w)
+				{
+					pt.x = next.x;
+					pt.y = (!vertical) ? cy : (
+						(direction == mxConstants.DIRECTION_NORTH) ?
+							y + h : y);
+				}
+				
+				// Compute angle
+				dx = next.x - pt.x;
+				dy = next.y - pt.y;
+				
+				cx = pt.x;
+				cy = pt.y;
+			}
+
+			if ((vertical && next.x <= x + w / 2) ||
+				(!vertical && next.y <= y + h / 2))
+			{
+				result = mxUtils.intersection(next.x, next.y, cx, cy,
+					start.x, start.y, corner.x, corner.y);
+			}
+			else
+			{
+				result = mxUtils.intersection(next.x, next.y, cx, cy,
+					corner.x, corner.y, end.x, end.y);
+			}
+		}
+		
+		if (result == null)
+		{
+			result = new mxPoint(cx, cy);
+		}
+		
+		return result;
+	},
+	
+	/**
+	 * Function: HexagonPerimeter
+	 * 
+	 * Describes a hexagon perimeter. See <RectanglePerimeter>
+	 * for a description of the parameters.
+	 */
+	HexagonPerimeter: function (bounds, vertex, next, orthogonal)
+	{
+		var x = bounds.x;
+		var y = bounds.y;
+		var w = bounds.width;
+		var h = bounds.height;
+
+		var cx = bounds.getCenterX();
+		var cy = bounds.getCenterY();
+		var px = next.x;
+		var py = next.y;
+		var dx = px - cx;
+		var dy = py - cy;
+		var alpha = -Math.atan2(dy, dx);
+		var pi = Math.PI;
+		var pi2 = Math.PI / 2;
+
+		var result = new mxPoint(cx, cy);
+
+		var direction = (vertex != null) ? mxUtils.getValue(
+				vertex.style, mxConstants.STYLE_DIRECTION,
+				mxConstants.DIRECTION_EAST) : mxConstants.DIRECTION_EAST;
+		var vertical = direction == mxConstants.DIRECTION_NORTH
+				|| direction == mxConstants.DIRECTION_SOUTH;
+		var a = new mxPoint();
+		var b = new mxPoint();
+
+		//Only consider corrects quadrants for the orthogonal case.
+		if ((px < x) && (py < y) || (px < x) && (py > y + h)
+				|| (px > x + w) && (py < y) || (px > x + w) && (py > y + h))
+		{
+			orthogonal = false;
+		}
+
+		if (orthogonal)
+		{
+			if (vertical)
+			{
+				//Special cases where intersects with hexagon corners
+				if (px == cx)
+				{
+					if (py <= y)
+					{
+						return new mxPoint(cx, y);
+					}
+					else if (py >= y + h)
+					{
+						return new mxPoint(cx, y + h);
+					}
+				}
+				else if (px < x)
+				{
+					if (py == y + h / 4)
+					{
+						return new mxPoint(x, y + h / 4);
+					}
+					else if (py == y + 3 * h / 4)
+					{
+						return new mxPoint(x, y + 3 * h / 4);
+					}
+				}
+				else if (px > x + w)
+				{
+					if (py == y + h / 4)
+					{
+						return new mxPoint(x + w, y + h / 4);
+					}
+					else if (py == y + 3 * h / 4)
+					{
+						return new mxPoint(x + w, y + 3 * h / 4);
+					}
+				}
+				else if (px == x)
+				{
+					if (py < cy)
+					{
+						return new mxPoint(x, y + h / 4);
+					}
+					else if (py > cy)
+					{
+						return new mxPoint(x, y + 3 * h / 4);
+					}
+				}
+				else if (px == x + w)
+				{
+					if (py < cy)
+					{
+						return new mxPoint(x + w, y + h / 4);
+					}
+					else if (py > cy)
+					{
+						return new mxPoint(x + w, y + 3 * h / 4);
+					}
+				}
+				if (py == y)
+				{
+					return new mxPoint(cx, y);
+				}
+				else if (py == y + h)
+				{
+					return new mxPoint(cx, y + h);
+				}
+
+				if (px < cx)
+				{
+					if ((py > y + h / 4) && (py < y + 3 * h / 4))
+					{
+						a = new mxPoint(x, y);
+						b = new mxPoint(x, y + h);
+					}
+					else if (py < y + h / 4)
+					{
+						a = new mxPoint(x - Math.floor(0.5 * w), y
+								+ Math.floor(0.5 * h));
+						b = new mxPoint(x + w, y - Math.floor(0.25 * h));
+					}
+					else if (py > y + 3 * h / 4)
+					{
+						a = new mxPoint(x - Math.floor(0.5 * w), y
+								+ Math.floor(0.5 * h));
+						b = new mxPoint(x + w, y + Math.floor(1.25 * h));
+					}
+				}
+				else if (px > cx)
+				{
+					if ((py > y + h / 4) && (py < y + 3 * h / 4))
+					{
+						a = new mxPoint(x + w, y);
+						b = new mxPoint(x + w, y + h);
+					}
+					else if (py < y + h / 4)
+					{
+						a = new mxPoint(x, y - Math.floor(0.25 * h));
+						b = new mxPoint(x + Math.floor(1.5 * w), y
+								+ Math.floor(0.5 * h));
+					}
+					else if (py > y + 3 * h / 4)
+					{
+						a = new mxPoint(x + Math.floor(1.5 * w), y
+								+ Math.floor(0.5 * h));
+						b = new mxPoint(x, y + Math.floor(1.25 * h));
+					}
+				}
+
+			}
+			else
+			{
+				//Special cases where intersects with hexagon corners
+				if (py == cy)
+				{
+					if (px <= x)
+					{
+						return new mxPoint(x, y + h / 2);
+					}
+					else if (px >= x + w)
+					{
+						return new mxPoint(x + w, y + h / 2);
+					}
+				}
+				else if (py < y)
+				{
+					if (px == x + w / 4)
+					{
+						return new mxPoint(x + w / 4, y);
+					}
+					else if (px == x + 3 * w / 4)
+					{
+						return new mxPoint(x + 3 * w / 4, y);
+					}
+				}
+				else if (py > y + h)
+				{
+					if (px == x + w / 4)
+					{
+						return new mxPoint(x + w / 4, y + h);
+					}
+					else if (px == x + 3 * w / 4)
+					{
+						return new mxPoint(x + 3 * w / 4, y + h);
+					}
+				}
+				else if (py == y)
+				{
+					if (px < cx)
+					{
+						return new mxPoint(x + w / 4, y);
+					}
+					else if (px > cx)
+					{
+						return new mxPoint(x + 3 * w / 4, y);
+					}
+				}
+				else if (py == y + h)
+				{
+					if (px < cx)
+					{
+						return new mxPoint(x + w / 4, y + h);
+					}
+					else if (py > cy)
+					{
+						return new mxPoint(x + 3 * w / 4, y + h);
+					}
+				}
+				if (px == x)
+				{
+					return new mxPoint(x, cy);
+				}
+				else if (px == x + w)
+				{
+					return new mxPoint(x + w, cy);
+				}
+
+				if (py < cy)
+				{
+					if ((px > x + w / 4) && (px < x + 3 * w / 4))
+					{
+						a = new mxPoint(x, y);
+						b = new mxPoint(x + w, y);
+					}
+					else if (px < x + w / 4)
+					{
+						a = new mxPoint(x - Math.floor(0.25 * w), y + h);
+						b = new mxPoint(x + Math.floor(0.5 * w), y
+								- Math.floor(0.5 * h));
+					}
+					else if (px > x + 3 * w / 4)
+					{
+						a = new mxPoint(x + Math.floor(0.5 * w), y
+								- Math.floor(0.5 * h));
+						b = new mxPoint(x + Math.floor(1.25 * w), y + h);
+					}
+				}
+				else if (py > cy)
+				{
+					if ((px > x + w / 4) && (px < x + 3 * w / 4))
+					{
+						a = new mxPoint(x, y + h);
+						b = new mxPoint(x + w, y + h);
+					}
+					else if (px < x + w / 4)
+					{
+						a = new mxPoint(x - Math.floor(0.25 * w), y);
+						b = new mxPoint(x + Math.floor(0.5 * w), y
+								+ Math.floor(1.5 * h));
+					}
+					else if (px > x + 3 * w / 4)
+					{
+						a = new mxPoint(x + Math.floor(0.5 * w), y
+								+ Math.floor(1.5 * h));
+						b = new mxPoint(x + Math.floor(1.25 * w), y);
+					}
+				}
+			}
+
+			var tx = cx;
+			var ty = cy;
+
+			if (px >= x && px <= x + w)
+			{
+				tx = px;
+				
+				if (py < cy)
+				{
+					ty = y + h;
+				}
+				else
+				{
+					ty = y;
+				}
+			}
+			else if (py >= y && py <= y + h)
+			{
+				ty = py;
+				
+				if (px < cx)
+				{
+					tx = x + w;
+				}
+				else
+				{
+					tx = x;
+				}
+			}
+
+			result = mxUtils.intersection(tx, ty, next.x, next.y, a.x, a.y, b.x, b.y);
+		}
+		else
+		{
+			if (vertical)
+			{
+				var beta = Math.atan2(h / 4, w / 2);
+
+				//Special cases where intersects with hexagon corners
+				if (alpha == beta)
+				{
+					return new mxPoint(x + w, y + Math.floor(0.25 * h));
+				}
+				else if (alpha == pi2)
+				{
+					return new mxPoint(x + Math.floor(0.5 * w), y);
+				}
+				else if (alpha == (pi - beta))
+				{
+					return new mxPoint(x, y + Math.floor(0.25 * h));
+				}
+				else if (alpha == -beta)
+				{
+					return new mxPoint(x + w, y + Math.floor(0.75 * h));
+				}
+				else if (alpha == (-pi2))
+				{
+					return new mxPoint(x + Math.floor(0.5 * w), y + h);
+				}
+				else if (alpha == (-pi + beta))
+				{
+					return new mxPoint(x, y + Math.floor(0.75 * h));
+				}
+
+				if ((alpha < beta) && (alpha > -beta))
+				{
+					a = new mxPoint(x + w, y);
+					b = new mxPoint(x + w, y + h);
+				}
+				else if ((alpha > beta) && (alpha < pi2))
+				{
+					a = new mxPoint(x, y - Math.floor(0.25 * h));
+					b = new mxPoint(x + Math.floor(1.5 * w), y
+							+ Math.floor(0.5 * h));
+				}
+				else if ((alpha > pi2) && (alpha < (pi - beta)))
+				{
+					a = new mxPoint(x - Math.floor(0.5 * w), y
+							+ Math.floor(0.5 * h));
+					b = new mxPoint(x + w, y - Math.floor(0.25 * h));
+				}
+				else if (((alpha > (pi - beta)) && (alpha <= pi))
+						|| ((alpha < (-pi + beta)) && (alpha >= -pi)))
+				{
+					a = new mxPoint(x, y);
+					b = new mxPoint(x, y + h);
+				}
+				else if ((alpha < -beta) && (alpha > -pi2))
+				{
+					a = new mxPoint(x + Math.floor(1.5 * w), y
+							+ Math.floor(0.5 * h));
+					b = new mxPoint(x, y + Math.floor(1.25 * h));
+				}
+				else if ((alpha < -pi2) && (alpha > (-pi + beta)))
+				{
+					a = new mxPoint(x - Math.floor(0.5 * w), y
+							+ Math.floor(0.5 * h));
+					b = new mxPoint(x + w, y + Math.floor(1.25 * h));
+				}
+			}
+			else
+			{
+				var beta = Math.atan2(h / 2, w / 4);
+
+				//Special cases where intersects with hexagon corners
+				if (alpha == beta)
+				{
+					return new mxPoint(x + Math.floor(0.75 * w), y);
+				}
+				else if (alpha == (pi - beta))
+				{
+					return new mxPoint(x + Math.floor(0.25 * w), y);
+				}
+				else if ((alpha == pi) || (alpha == -pi))
+				{
+					return new mxPoint(x, y + Math.floor(0.5 * h));
+				}
+				else if (alpha == 0)
+				{
+					return new mxPoint(x + w, y + Math.floor(0.5 * h));
+				}
+				else if (alpha == -beta)
+				{
+					return new mxPoint(x + Math.floor(0.75 * w), y + h);
+				}
+				else if (alpha == (-pi + beta))
+				{
+					return new mxPoint(x + Math.floor(0.25 * w), y + h);
+				}
+
+				if ((alpha > 0) && (alpha < beta))
+				{
+					a = new mxPoint(x + Math.floor(0.5 * w), y
+							- Math.floor(0.5 * h));
+					b = new mxPoint(x + Math.floor(1.25 * w), y + h);
+				}
+				else if ((alpha > beta) && (alpha < (pi - beta)))
+				{
+					a = new mxPoint(x, y);
+					b = new mxPoint(x + w, y);
+				}
+				else if ((alpha > (pi - beta)) && (alpha < pi))
+				{
+					a = new mxPoint(x - Math.floor(0.25 * w), y + h);
+					b = new mxPoint(x + Math.floor(0.5 * w), y
+							- Math.floor(0.5 * h));
+				}
+				else if ((alpha < 0) && (alpha > -beta))
+				{
+					a = new mxPoint(x + Math.floor(0.5 * w), y
+							+ Math.floor(1.5 * h));
+					b = new mxPoint(x + Math.floor(1.25 * w), y);
+				}
+				else if ((alpha < -beta) && (alpha > (-pi + beta)))
+				{
+					a = new mxPoint(x, y + h);
+					b = new mxPoint(x + w, y + h);
+				}
+				else if ((alpha < (-pi + beta)) && (alpha > -pi))
+				{
+					a = new mxPoint(x - Math.floor(0.25 * w), y);
+					b = new mxPoint(x + Math.floor(0.5 * w), y
+							+ Math.floor(1.5 * h));
+				}
+			}
+
+			result = mxUtils.intersection(cx, cy, next.x, next.y, a.x, a.y, b.x, b.y);
+		}
+		
+		if (result == null)
+		{
+			return new mxPoint(cx, cy);
+		}
+		
+		return result;
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/view/mxPrintPreview.js b/airavata-kubernetes/workflow-composer/src/js/view/mxPrintPreview.js
new file mode 100644
index 0000000..14a6193
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/view/mxPrintPreview.js
@@ -0,0 +1,1175 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxPrintPreview
+ * 
+ * Implements printing of a diagram across multiple pages. The following opens
+ * a print preview for an existing graph:
+ * 
+ * (code)
+ * var preview = new mxPrintPreview(graph);
+ * preview.open();
+ * (end)
+ * 
+ * Use <mxUtils.getScaleForPageCount> as follows in order to print the graph
+ * across a given number of pages:
+ * 
+ * (code)
+ * var pageCount = mxUtils.prompt('Enter page count', '1');
+ * 
+ * if (pageCount != null)
+ * {
+ *   var scale = mxUtils.getScaleForPageCount(pageCount, graph);
+ *   var preview = new mxPrintPreview(graph, scale);
+ *   preview.open();
+ * }
+ * (end)
+ * 
+ * Additional pages:
+ * 
+ * To add additional pages before and after the output, <getCoverPages> and
+ * <getAppendices> can be used, respectively.
+ * 
+ * (code)
+ * var preview = new mxPrintPreview(graph, 1);
+ * 
+ * preview.getCoverPages = function(w, h)
+ * {
+ *   return [this.renderPage(w, h, 0, 0, mxUtils.bind(this, function(div)
+ *   {
+ *     div.innerHTML = '<div style="position:relative;margin:4px;">Cover Page</p>'
+ *   }))];
+ * };
+ * 
+ * preview.getAppendices = function(w, h)
+ * {
+ *   return [this.renderPage(w, h, 0, 0, mxUtils.bind(this, function(div)
+ *   {
+ *     div.innerHTML = '<div style="position:relative;margin:4px;">Appendix</p>'
+ *   }))];
+ * };
+ * 
+ * preview.open();
+ * (end)
+ * 
+ * CSS:
+ * 
+ * The CSS from the original page is not carried over to the print preview.
+ * To add CSS to the page, use the css argument in the <open> function or
+ * override <writeHead> to add the respective link tags as follows:
+ * 
+ * (code)
+ * var writeHead = preview.writeHead;
+ * preview.writeHead = function(doc, css)
+ * {
+ *   writeHead.apply(this, arguments);
+ *   doc.writeln('<link rel="stylesheet" type="text/css" href="style.css">');
+ * };
+ * (end)
+ * 
+ * Padding:
+ * 
+ * To add a padding to the page in the preview (but not the print output), use
+ * the following code:
+ * 
+ * (code)
+ * preview.writeHead = function(doc)
+ * {
+ *   writeHead.apply(this, arguments);
+ *   
+ *   doc.writeln('<style type="text/css">');
+ *   doc.writeln('@media screen {');
+ *   doc.writeln('  body > div { padding-top:30px;padding-left:40px;box-sizing:content-box; }');
+ *   doc.writeln('}');
+ *   doc.writeln('</style>');
+ * };
+ * (end)
+ * 
+ * Headers:
+ * 
+ * Apart from setting the title argument in the mxPrintPreview constructor you
+ * can override <renderPage> as follows to add a header to any page:
+ * 
+ * (code)
+ * var oldRenderPage = mxPrintPreview.prototype.renderPage;
+ * mxPrintPreview.prototype.renderPage = function(w, h, x, y, content, pageNumber)
+ * {
+ *   var div = oldRenderPage.apply(this, arguments);
+ *   
+ *   var header = document.createElement('div');
+ *   header.style.position = 'absolute';
+ *   header.style.top = '0px';
+ *   header.style.width = '100%';
+ *   header.style.textAlign = 'right';
+ *   mxUtils.write(header, 'Your header here');
+ *   div.firstChild.appendChild(header);
+ *   
+ *   return div;
+ * };
+ * (end)
+ * 
+ * The pageNumber argument contains the number of the current page, starting at
+ * 1. To display a header on the first page only, check pageNumber and add a
+ * vertical offset in the constructor call for the height of the header.
+ * 
+ * Page Format:
+ * 
+ * For landscape printing, use <mxConstants.PAGE_FORMAT_A4_LANDSCAPE> as
+ * the pageFormat in <mxUtils.getScaleForPageCount> and <mxPrintPreview>.
+ * Keep in mind that one can not set the defaults for the print dialog
+ * of the operating system from JavaScript so the user must manually choose
+ * a page format that matches this setting.
+ * 
+ * You can try passing the following CSS directive to <open> to set the
+ * page format in the print dialog to landscape. However, this CSS
+ * directive seems to be ignored in most major browsers, including IE.
+ * 
+ * (code)
+ * @page {
+ *   size: landscape;
+ * }
+ * (end)
+ * 
+ * Note that the print preview behaves differently in IE when used from the
+ * filesystem or via HTTP so printing should always be tested via HTTP.
+ * 
+ * If you are using a DOCTYPE in the source page you can override <getDoctype>
+ * and provide the same DOCTYPE for the print preview if required. Here is
+ * an example for IE8 standards mode.
+ * 
+ * (code)
+ * var preview = new mxPrintPreview(graph);
+ * preview.getDoctype = function()
+ * {
+ *   return '<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=5,IE=8" ><![endif]-->';
+ * };
+ * preview.open();
+ * (end)
+ * 
+ * Constructor: mxPrintPreview
+ *
+ * Constructs a new print preview for the given parameters.
+ * 
+ * Parameters:
+ * 
+ * graph - <mxGraph> to be previewed.
+ * scale - Optional scale of the output. Default is 1 / <mxGraph.pageScale>.
+ * border - Border in pixels along each side of every page. Note that the
+ * actual print function in the browser will add another border for
+ * printing.
+ * pageFormat - <mxRectangle> that specifies the page format (in pixels).
+ * This should match the page format of the printer. Default uses the
+ * <mxGraph.pageFormat> of the given graph.
+ * x0 - Optional left offset of the output. Default is 0.
+ * y0 - Optional top offset of the output. Default is 0.
+ * borderColor - Optional color of the page border. Default is no border.
+ * Note that a border is sometimes useful to highlight the printed page
+ * border in the print preview of the browser.
+ * title - Optional string that is used for the window title. Default
+ * is 'Printer-friendly version'.
+ * pageSelector - Optional boolean that specifies if the page selector
+ * should appear in the window with the print preview. Default is true.
+ */
+function mxPrintPreview(graph, scale, pageFormat, border, x0, y0, borderColor, title, pageSelector)
+{
+	this.graph = graph;
+	this.scale = (scale != null) ? scale : 1 / graph.pageScale;
+	this.border = (border != null) ? border : 0;
+	this.pageFormat = mxRectangle.fromRectangle((pageFormat != null) ? pageFormat : graph.pageFormat);
+	this.title = (title != null) ? title : 'Printer-friendly version';
+	this.x0 = (x0 != null) ? x0 : 0;
+	this.y0 = (y0 != null) ? y0 : 0;
+	this.borderColor = borderColor;
+	this.pageSelector = (pageSelector != null) ? pageSelector : true;
+};
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the <mxGraph> that should be previewed.
+ */
+mxPrintPreview.prototype.graph = null;
+
+/**
+ * Variable: pageFormat
+ *
+ * Holds the <mxRectangle> that defines the page format.
+ */
+mxPrintPreview.prototype.pageFormat = null;
+
+/**
+ * Variable: scale
+ * 
+ * Holds the scale of the print preview.
+ */
+mxPrintPreview.prototype.scale = null;
+
+/**
+ * Variable: border
+ * 
+ * The border inset around each side of every page in the preview. This is set
+ * to 0 if autoOrigin is false.
+ */
+mxPrintPreview.prototype.border = 0;
+
+/**
+ * Variable: marginTop
+ * 
+ * The margin at the top of the page (number). Default is 0.
+ */
+mxPrintPreview.prototype.marginTop = 0;
+
+/**
+ * Variable: marginBottom
+ * 
+ * The margin at the bottom of the page (number). Default is 0.
+ */
+mxPrintPreview.prototype.marginBottom = 0;
+
+/**
+ * Variable: x0
+ * 
+ * Holds the horizontal offset of the output.
+ */
+mxPrintPreview.prototype.x0 = 0;
+
+/**
+ * Variable: y0
+ *
+ * Holds the vertical offset of the output.
+ */
+mxPrintPreview.prototype.y0 = 0;
+
+/**
+ * Variable: autoOrigin
+ * 
+ * Specifies if the origin should be automatically computed based on the top,
+ * left corner of the actual diagram contents. The required offset will be added
+ * to <x0> and <y0> in <open>. Default is true.
+ */
+mxPrintPreview.prototype.autoOrigin = true;
+
+/**
+ * Variable: printOverlays
+ * 
+ * Specifies if overlays should be printed. Default is false.
+ */
+mxPrintPreview.prototype.printOverlays = false;
+
+/**
+ * Variable: printControls
+ * 
+ * Specifies if controls (such as folding icons) should be printed. Default is
+ * false.
+ */
+mxPrintPreview.prototype.printControls = false;
+
+/**
+ * Variable: printBackgroundImage
+ * 
+ * Specifies if the background image should be printed. Default is false.
+ */
+mxPrintPreview.prototype.printBackgroundImage = false;
+
+/**
+ * Variable: backgroundColor
+ * 
+ * Holds the color value for the page background color. Default is #ffffff.
+ */
+mxPrintPreview.prototype.backgroundColor = '#ffffff';
+
+/**
+ * Variable: borderColor
+ * 
+ * Holds the color value for the page border.
+ */
+mxPrintPreview.prototype.borderColor = null;
+
+/**
+ * Variable: title
+ * 
+ * Holds the title of the preview window.
+ */
+mxPrintPreview.prototype.title = null;
+
+/**
+ * Variable: pageSelector
+ * 
+ * Boolean that specifies if the page selector should be
+ * displayed. Default is true.
+ */
+mxPrintPreview.prototype.pageSelector = null;
+
+/**
+ * Variable: wnd
+ * 
+ * Reference to the preview window.
+ */
+mxPrintPreview.prototype.wnd = null;
+
+/**
+ * Variable: targetWindow
+ * 
+ * Assign any window here to redirect the rendering in <open>.
+ */
+mxPrintPreview.prototype.targetWindow = null;
+
+/**
+ * Variable: pageCount
+ * 
+ * Holds the actual number of pages in the preview.
+ */
+mxPrintPreview.prototype.pageCount = 0;
+
+/**
+ * Variable: clipping
+ * 
+ * Specifies is clipping should be used to avoid creating too many cell states
+ * in large diagrams. The bounding box of the cells in the original diagram is
+ * used if this is enabled. Default is true.
+ */
+mxPrintPreview.prototype.clipping = true;
+
+/**
+ * Function: getWindow
+ * 
+ * Returns <wnd>.
+ */
+mxPrintPreview.prototype.getWindow = function()
+{
+	return this.wnd;
+};
+
+/**
+ * Function: getDocType
+ * 
+ * Returns the string that should go before the HTML tag in the print preview
+ * page. This implementation returns an X-UA meta tag for IE5 in quirks mode,
+ * IE8 in IE8 standards mode and edge in IE9 standards mode.
+ */
+mxPrintPreview.prototype.getDoctype = function()
+{
+	var dt = '';
+	
+	if (document.documentMode == 5)
+	{
+		dt = '<meta http-equiv="X-UA-Compatible" content="IE=5">';
+	}
+	else if (document.documentMode == 8)
+	{
+		dt = '<meta http-equiv="X-UA-Compatible" content="IE=8">';
+	}
+	else if (document.documentMode > 8)
+	{
+		// Comment needed to make standards doctype apply in IE
+		dt = '<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=edge"><![endif]-->';
+	}
+	
+	return dt;
+};
+
+/**
+ * Function: appendGraph
+ * 
+ * Adds the given graph to the existing print preview.
+ * 
+ * Parameters:
+ * 
+ * css - Optional CSS string to be used in the head section.
+ * targetWindow - Optional window that should be used for rendering. If
+ * this is specified then no HEAD tag, CSS and BODY tag will be written.
+ */
+mxPrintPreview.prototype.appendGraph = function(graph, scale, x0, y0, forcePageBreaks, keepOpen)
+{
+	this.graph = graph;
+	this.scale = (scale != null) ? scale : 1 / graph.pageScale;
+	this.x0 = x0;
+	this.y0 = y0;
+	this.open(null, null, forcePageBreaks, keepOpen);
+};
+
+/**
+ * Function: open
+ * 
+ * Shows the print preview window. The window is created here if it does
+ * not exist.
+ * 
+ * Parameters:
+ * 
+ * css - Optional CSS string to be used in the head section.
+ * targetWindow - Optional window that should be used for rendering. If
+ * this is specified then no HEAD tag, CSS and BODY tag will be written.
+ */
+mxPrintPreview.prototype.open = function(css, targetWindow, forcePageBreaks, keepOpen)
+{
+	// Closing the window while the page is being rendered may cause an
+	// exception in IE. This and any other exceptions are simply ignored.
+	var previousInitializeOverlay = this.graph.cellRenderer.initializeOverlay;
+	var div = null;
+	
+	try
+	{
+		// Temporarily overrides the method to redirect rendering of overlays
+		// to the draw pane so that they are visible in the printout
+		if (this.printOverlays)
+		{
+			this.graph.cellRenderer.initializeOverlay = function(state, overlay)
+			{
+				overlay.init(state.view.getDrawPane());
+			};
+		}
+		
+		if (this.printControls)
+		{
+			this.graph.cellRenderer.initControl = function(state, control, handleEvents, clickHandler)
+			{
+				control.dialect = state.view.graph.dialect;
+				control.init(state.view.getDrawPane());
+			};
+		}
+		
+		this.wnd = (targetWindow != null) ? targetWindow : this.wnd;
+		var isNewWindow = false;
+		
+		if (this.wnd == null)
+		{
+			isNewWindow = true;
+			this.wnd = window.open();
+		}
+		
+		var doc = this.wnd.document;
+		
+		if (isNewWindow)
+		{
+			var dt = this.getDoctype();
+			
+			if (dt != null && dt.length > 0)
+			{
+				doc.writeln(dt);
+			}
+			
+			if (mxClient.IS_VML)
+			{
+				doc.writeln('<html xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">');
+			}
+			else
+			{
+				if (document.compatMode === 'CSS1Compat')
+				{
+					doc.writeln('<!DOCTYPE html>');
+				}
+				
+				doc.writeln('<html>');
+			}
+			
+			doc.writeln('<head>');
+			this.writeHead(doc, css);
+			doc.writeln('</head>');
+			doc.writeln('<body class="mxPage">');
+		}
+
+		// Computes the horizontal and vertical page count
+		var bounds = this.graph.getGraphBounds().clone();
+		var currentScale = this.graph.getView().getScale();
+		var sc = currentScale / this.scale;
+		var tr = this.graph.getView().getTranslate();
+		
+		// Uses the absolute origin with no offset for all printing
+		if (!this.autoOrigin)
+		{
+			this.x0 -= tr.x * this.scale;
+			this.y0 -= tr.y * this.scale;
+			bounds.width += bounds.x;
+			bounds.height += bounds.y;
+			bounds.x = 0;
+			bounds.y = 0;
+			this.border = 0;
+		}
+		
+		// Store the available page area
+		var availableWidth = this.pageFormat.width - (this.border * 2);
+		var availableHeight = this.pageFormat.height - (this.border * 2);
+	
+		// Adds margins to page format
+		this.pageFormat.height += this.marginTop + this.marginBottom;
+
+		// Compute the unscaled, untranslated bounds to find
+		// the number of vertical and horizontal pages
+		bounds.width /= sc;
+		bounds.height /= sc;
+
+		var hpages = Math.max(1, Math.ceil((bounds.width + this.x0) / availableWidth));
+		var vpages = Math.max(1, Math.ceil((bounds.height + this.y0) / availableHeight));
+		this.pageCount = hpages * vpages;
+		
+		var writePageSelector = mxUtils.bind(this, function()
+		{
+			if (this.pageSelector && (vpages > 1 || hpages > 1))
+			{
+				var table = this.createPageSelector(vpages, hpages);
+				doc.body.appendChild(table);
+				
+				// Implements position: fixed in IE quirks mode
+				if (mxClient.IS_IE && doc.documentMode == null || doc.documentMode == 5 || doc.documentMode == 8 || doc.documentMode == 7)
+				{
+					table.style.position = 'absolute';
+					
+					var update = function()
+					{
+						table.style.top = ((doc.body.scrollTop || doc.documentElement.scrollTop) + 10) + 'px';
+					};
+					
+					mxEvent.addListener(this.wnd, 'scroll', function(evt)
+					{
+						update();
+					});
+					
+					mxEvent.addListener(this.wnd, 'resize', function(evt)
+					{
+						update();
+					});
+				}
+			}
+		});
+		
+		var addPage = mxUtils.bind(this, function(div, addBreak)
+		{
+			// Border of the DIV (aka page) inside the document
+			if (this.borderColor != null)
+			{
+				div.style.borderColor = this.borderColor;
+				div.style.borderStyle = 'solid';
+				div.style.borderWidth = '1px';
+			}
+			
+			// Needs to be assigned directly because IE doesn't support
+			// child selectors, eg. body > div { background: white; }
+			div.style.background = this.backgroundColor;
+			
+			if (forcePageBreaks || addBreak)
+			{
+				div.style.pageBreakAfter = 'always';
+			}
+
+			// NOTE: We are dealing with cross-window DOM here, which
+			// is a problem in IE, so we copy the HTML markup instead.
+			// The underlying problem is that the graph display markup
+			// creation (in mxShape, mxGraphView) is hardwired to using
+			// document.createElement and hence we must use this document
+			// to create the complete page and then copy it over to the
+			// new window.document. This can be fixed later by using the
+			// ownerDocument of the container in mxShape and mxGraphView.
+			if (isNewWindow && (mxClient.IS_IE || document.documentMode >= 11 || mxClient.IS_EDGE))
+			{
+				// For some obscure reason, removing the DIV from the
+				// parent before fetching its outerHTML has missing
+				// fillcolor properties and fill children, so the div
+				// must be removed afterwards to keep the fillcolors.
+				doc.writeln(div.outerHTML);
+				div.parentNode.removeChild(div);
+			}
+			else
+			{
+				div.parentNode.removeChild(div);
+				doc.body.appendChild(div);
+			}
+
+			if (forcePageBreaks || addBreak)
+			{
+				this.addPageBreak(doc);
+			}
+		});
+		
+		var cov = this.getCoverPages(this.pageFormat.width, this.pageFormat.height);
+		
+		if (cov != null)
+		{
+			for (var i = 0; i < cov.length; i++)
+			{
+				addPage(cov[i], true);
+			}
+		}
+		
+		var apx = this.getAppendices(this.pageFormat.width, this.pageFormat.height);
+		
+		// Appends each page to the page output for printing, making
+		// sure there will be a page break after each page (ie. div)
+		for (var i = 0; i < vpages; i++)
+		{
+			var dy = i * availableHeight / this.scale - this.y0 / this.scale +
+					(bounds.y - tr.y * currentScale) / currentScale;
+			
+			for (var j = 0; j < hpages; j++)
+			{
+				if (this.wnd == null)
+				{
+					return null;
+				}
+				
+				var dx = j * availableWidth / this.scale - this.x0 / this.scale +
+						(bounds.x - tr.x * currentScale) / currentScale;
+				var pageNum = i * hpages + j + 1;
+				var clip = new mxRectangle(dx, dy, availableWidth, availableHeight);
+				div = this.renderPage(this.pageFormat.width, this.pageFormat.height, 0, 0, mxUtils.bind(this, function(div)
+				{
+					this.addGraphFragment(-dx, -dy, this.scale, pageNum, div, clip);
+					
+					if (this.printBackgroundImage)
+					{
+						this.insertBackgroundImage(div, -dx, -dy);
+					}
+				}), pageNum);
+
+				// Gives the page a unique ID for later accessing the page
+				div.setAttribute('id', 'mxPage-'+pageNum);
+
+				addPage(div, apx != null || i < vpages - 1 || j < hpages - 1);
+			}
+		}
+
+		if (apx != null)
+		{
+			for (var i = 0; i < apx.length; i++)
+			{
+				addPage(apx[i], i < apx.length - 1);
+			}
+		}
+
+		if (isNewWindow && !keepOpen)
+		{
+			this.closeDocument();
+			writePageSelector();
+		}
+		
+		this.wnd.focus();
+	}
+	catch (e)
+	{
+		// Removes the DIV from the document in case of an error
+		if (div != null && div.parentNode != null)
+		{
+			div.parentNode.removeChild(div);
+		}
+	}
+	finally
+	{
+		this.graph.cellRenderer.initializeOverlay = previousInitializeOverlay;
+	}
+
+	return this.wnd;
+};
+
+/**
+ * Function: addPageBreak
+ * 
+ * Adds a page break to the given document.
+ */
+mxPrintPreview.prototype.addPageBreak = function(doc)
+{
+	var hr = doc.createElement('hr');
+	hr.className = 'mxPageBreak';
+	doc.body.appendChild(hr);
+};
+
+/**
+ * Function: closeDocument
+ * 
+ * Writes the closing tags for body and page after calling <writePostfix>.
+ */
+mxPrintPreview.prototype.closeDocument = function()
+{
+	if (this.wnd != null && this.wnd.document != null)
+	{
+		var doc = this.wnd.document;
+		
+		this.writePostfix(doc);
+		doc.writeln('</body>');
+		doc.writeln('</html>');
+		doc.close();
+		
+		// Removes all event handlers in the print output
+		mxEvent.release(doc.body);
+	}
+};
+
+/**
+ * Function: writeHead
+ * 
+ * Writes the HEAD section into the given document, without the opening
+ * and closing HEAD tags.
+ */
+mxPrintPreview.prototype.writeHead = function(doc, css)
+{
+	if (this.title != null)
+	{
+		doc.writeln('<title>' + this.title + '</title>');
+	}
+	
+	// Adds required namespaces
+	if (mxClient.IS_VML)
+	{
+		doc.writeln('<style type="text/css">v\\:*{behavior:url(#default#VML)}o\\:*{behavior:url(#default#VML)}</style>');
+	}
+
+	// Adds all required stylesheets
+	mxClient.link('stylesheet', mxClient.basePath + '/css/common.css', doc);
+
+	// Removes horizontal rules and page selector from print output
+	doc.writeln('<style type="text/css">');
+	doc.writeln('@media print {');
+	doc.writeln('  table.mxPageSelector { display: none; }');
+	doc.writeln('  hr.mxPageBreak { display: none; }');
+	doc.writeln('}');
+	doc.writeln('@media screen {');
+	
+	// NOTE: position: fixed is not supported in IE, so the page selector
+	// position (absolute) needs to be updated in IE (see below)
+	doc.writeln('  table.mxPageSelector { position: fixed; right: 10px; top: 10px;' +
+			'font-family: Arial; font-size:10pt; border: solid 1px darkgray;' +
+			'background: white; border-collapse:collapse; }');
+	doc.writeln('  table.mxPageSelector td { border: solid 1px gray; padding:4px; }');
+	doc.writeln('  body.mxPage { background: gray; }');
+	doc.writeln('}');
+	
+	if (css != null)
+	{
+		doc.writeln(css);
+	}
+	
+	doc.writeln('</style>');
+};
+
+/**
+ * Function: writePostfix
+ * 
+ * Called before closing the body of the page. This implementation is empty.
+ */
+mxPrintPreview.prototype.writePostfix = function(doc)
+{
+	// empty
+};
+
+/**
+ * Function: createPageSelector
+ * 
+ * Creates the page selector table.
+ */
+mxPrintPreview.prototype.createPageSelector = function(vpages, hpages)
+{
+	var doc = this.wnd.document;
+	var table = doc.createElement('table');
+	table.className = 'mxPageSelector';
+	table.setAttribute('border', '0');
+
+	var tbody = doc.createElement('tbody');
+	
+	for (var i = 0; i < vpages; i++)
+	{
+		var row = doc.createElement('tr');
+		
+		for (var j = 0; j < hpages; j++)
+		{
+			var pageNum = i * hpages + j + 1;
+			var cell = doc.createElement('td');
+			var a = doc.createElement('a');
+			a.setAttribute('href', '#mxPage-' + pageNum);
+
+			// Workaround for FF where the anchor is appended to the URL of the original document
+			if (mxClient.IS_NS && !mxClient.IS_SF && !mxClient.IS_GC)
+			{
+				var js = 'var page = document.getElementById(\'mxPage-' + pageNum + '\');page.scrollIntoView(true);event.preventDefault();';
+				a.setAttribute('onclick', js);
+			}
+			
+			mxUtils.write(a, pageNum, doc);
+			cell.appendChild(a);
+			row.appendChild(cell);
+		}
+		
+		tbody.appendChild(row);
+	}
+	
+	table.appendChild(tbody);
+	
+	return table;
+};
+
+/**
+ * Function: renderPage
+ * 
+ * Creates a DIV that prints a single page of the given
+ * graph using the given scale and returns the DIV that
+ * represents the page.
+ * 
+ * Parameters:
+ * 
+ * w - Width of the page in pixels.
+ * h - Height of the page in pixels.
+ * dx - Optional horizontal page offset in pixels (used internally).
+ * dy - Optional vertical page offset in pixels (used internally).
+ * content - Callback that adds the HTML content to the inner div of a page.
+ * Takes the inner div as the argument.
+ * pageNumber - Integer representing the page number.
+ */
+mxPrintPreview.prototype.renderPage = function(w, h, dx, dy, content, pageNumber)
+{
+	var doc = this.wnd.document;
+	var div = document.createElement('div');
+	var arg = null;
+
+	try
+	{
+		// Workaround for ignored clipping in IE 9 standards
+		// when printing with page breaks and HTML labels.
+		if (dx != 0 || dy != 0)
+		{
+			div.style.position = 'relative';
+			div.style.width = w + 'px';
+			div.style.height = h + 'px';
+			div.style.pageBreakInside = 'avoid';
+			
+			var innerDiv = document.createElement('div');
+			innerDiv.style.position = 'relative';
+			innerDiv.style.top = this.border + 'px';
+			innerDiv.style.left = this.border + 'px';
+			innerDiv.style.width = (w - 2 * this.border) + 'px';
+			innerDiv.style.height = (h - 2 * this.border) + 'px';
+			innerDiv.style.overflow = 'hidden';
+			
+			var viewport = document.createElement('div');
+			viewport.style.position = 'relative';
+			viewport.style.marginLeft = dx + 'px';
+			viewport.style.marginTop = dy + 'px';
+
+			// FIXME: IE8 standards output problems
+			if (doc.documentMode == 8)
+			{
+				innerDiv.style.position = 'absolute';
+				viewport.style.position = 'absolute';
+			}
+		
+			if (doc.documentMode == 10)
+			{
+				viewport.style.width = '100%';
+				viewport.style.height = '100%';
+			}
+			
+			innerDiv.appendChild(viewport);
+			div.appendChild(innerDiv);
+			document.body.appendChild(div);
+			arg = viewport;
+		}
+		// FIXME: IE10/11 too many pages
+		else
+		{
+			div.style.width = w + 'px';
+			div.style.height = h + 'px';
+			div.style.overflow = 'hidden';
+			div.style.pageBreakInside = 'avoid';
+			
+			// IE8 uses above branch currently
+			if (doc.documentMode == 8)
+			{
+				div.style.position = 'relative';
+			}
+			
+			var innerDiv = document.createElement('div');
+			innerDiv.style.width = (w - 2 * this.border) + 'px';
+			innerDiv.style.height = (h - 2 * this.border) + 'px';
+			innerDiv.style.overflow = 'hidden';
+
+			if (mxClient.IS_IE && (doc.documentMode == null || doc.documentMode == 5 || doc.documentMode == 8 || doc.documentMode == 7))
+			{
+				innerDiv.style.marginTop = this.border + 'px';
+				innerDiv.style.marginLeft = this.border + 'px';	
+			}
+			else
+			{
+				innerDiv.style.top = this.border + 'px';
+				innerDiv.style.left = this.border + 'px';
+			}
+	
+			if (this.graph.dialect == mxConstants.DIALECT_VML)
+			{
+				innerDiv.style.position = 'absolute';
+			}
+
+			div.appendChild(innerDiv);
+			document.body.appendChild(div);
+			arg = innerDiv;
+		}
+	}
+	catch (e)
+	{
+		div.parentNode.removeChild(div);
+		div = null;
+		
+		throw e;
+	}
+
+	content(arg);
+	 
+	return div;
+};
+
+/**
+ * Function: getRoot
+ * 
+ * Returns the root cell for painting the graph.
+ */
+mxPrintPreview.prototype.getRoot = function()
+{
+	var root = this.graph.view.currentRoot;
+	
+	if (root == null)
+	{
+		root = this.graph.getModel().getRoot();
+	}
+	
+	return root;
+};
+
+/**
+ * Function: addGraphFragment
+ * 
+ * Adds a graph fragment to the given div.
+ * 
+ * Parameters:
+ * 
+ * dx - Horizontal translation for the diagram.
+ * dy - Vertical translation for the diagram.
+ * scale - Scale for the diagram.
+ * pageNumber - Number of the page to be rendered.
+ * div - Div that contains the output.
+ * clip - Contains the clipping rectangle as an <mxRectangle>.
+ */
+mxPrintPreview.prototype.addGraphFragment = function(dx, dy, scale, pageNumber, div, clip)
+{
+	var view = this.graph.getView();
+	var previousContainer = this.graph.container;
+	this.graph.container = div;
+	
+	var canvas = view.getCanvas();
+	var backgroundPane = view.getBackgroundPane();
+	var drawPane = view.getDrawPane();
+	var overlayPane = view.getOverlayPane();
+
+	if (this.graph.dialect == mxConstants.DIALECT_SVG)
+	{
+		view.createSvg();
+	}
+	else if (this.graph.dialect == mxConstants.DIALECT_VML)
+	{
+		view.createVml();
+	}
+	else
+	{
+		view.createHtml();
+	}
+	
+	// Disables events on the view
+	var eventsEnabled = view.isEventsEnabled();
+	view.setEventsEnabled(false);
+	
+	// Disables the graph to avoid cursors
+	var graphEnabled = this.graph.isEnabled();
+	this.graph.setEnabled(false);
+
+	// Resets the translation
+	var translate = view.getTranslate();
+	view.translate = new mxPoint(dx, dy);
+	
+	// Redraws only states that intersect the clip
+	var redraw = this.graph.cellRenderer.redraw;
+	var states = view.states;
+	var s = view.scale;
+	
+	// Gets the transformed clip for intersection check below
+	if (this.clipping)
+	{
+		var tempClip = new mxRectangle((clip.x + translate.x) * s, (clip.y + translate.y) * s,
+				clip.width * s / scale, clip.height * s / scale);
+		
+		// Checks clipping rectangle for speedup
+		// Must create terminal states for edge clipping even if terminal outside of clip
+		this.graph.cellRenderer.redraw = function(state, force, rendering)
+		{
+			if (state != null)
+			{
+				// Gets original state from graph to find bounding box
+				var orig = states.get(state.cell);
+				
+				if (orig != null)
+				{
+					var bbox = view.getBoundingBox(orig, false);
+					
+					// Stops rendering if outside clip for speedup
+					if (bbox != null && !mxUtils.intersects(tempClip, bbox))
+					{
+						return;
+					}
+				}
+			}
+			
+			redraw.apply(this, arguments);
+		};
+	}
+	
+	var temp = null;
+	
+	try
+	{
+		// Creates the temporary cell states in the view and
+		// draws them onto the temporary DOM nodes in the view
+		var cells = [this.getRoot()];
+		temp = new mxTemporaryCellStates(view, scale, cells);
+	}
+	finally
+	{
+		// Removes overlay pane with selection handles
+		// controls and icons from the print output
+		if (mxClient.IS_IE)
+		{
+			view.overlayPane.innerHTML = '';
+			view.canvas.style.overflow = 'hidden';
+			view.canvas.style.position = 'relative';
+			view.canvas.style.top = this.marginTop + 'px';
+			view.canvas.style.width = clip.width + 'px';
+			view.canvas.style.height = clip.height + 'px';
+		}
+		else
+		{
+			// Removes everything but the SVG node
+			var tmp = div.firstChild;
+
+			while (tmp != null)
+			{
+				var next = tmp.nextSibling;
+				var name = tmp.nodeName.toLowerCase();
+
+				// Note: Width and height are required in FF 11
+				if (name == 'svg')
+				{
+					tmp.style.overflow = 'hidden';
+					tmp.style.position = 'relative';
+					tmp.style.top = this.marginTop + 'px';
+					tmp.setAttribute('width', clip.width);
+					tmp.setAttribute('height', clip.height);
+					tmp.style.width = '';
+					tmp.style.height = '';
+				}
+				// Tries to fetch all text labels and only text labels
+				else if (tmp.style.cursor != 'default' && name != 'div')
+				{
+					tmp.parentNode.removeChild(tmp);
+				}
+				
+				tmp = next;
+			}
+		}
+		
+		// Puts background image behind SVG output
+		if (this.printBackgroundImage)
+		{
+			var svgs = div.getElementsByTagName('svg');
+			
+			if (svgs.length > 0)
+			{
+				svgs[0].style.position = 'absolute';
+			}
+		}
+		
+		// Completely removes the overlay pane to remove more handles
+		view.overlayPane.parentNode.removeChild(view.overlayPane);
+
+		// Restores the state of the view
+		this.graph.setEnabled(graphEnabled);
+		this.graph.container = previousContainer;
+		this.graph.cellRenderer.redraw = redraw;
+		view.canvas = canvas;
+		view.backgroundPane = backgroundPane;
+		view.drawPane = drawPane;
+		view.overlayPane = overlayPane;
+		view.translate = translate;
+		temp.destroy();
+		view.setEventsEnabled(eventsEnabled);
+	}
+};
+
+/**
+ * Function: insertBackgroundImage
+ * 
+ * Inserts the background image into the given div.
+ */
+mxPrintPreview.prototype.insertBackgroundImage = function(div, dx, dy)
+{
+	var bg = this.graph.backgroundImage;
+	
+	if (bg != null)
+	{
+		var img = document.createElement('img');
+		img.style.position = 'absolute';
+		img.style.marginLeft = Math.round(dx * this.scale) + 'px';
+		img.style.marginTop = Math.round(dy * this.scale) + 'px';
+		img.setAttribute('width', Math.round(this.scale * bg.width));
+		img.setAttribute('height', Math.round(this.scale * bg.height));
+		img.src = bg.src;
+		
+		div.insertBefore(img, div.firstChild);
+	}
+};
+
+/**
+ * Function: getCoverPages
+ * 
+ * Returns the pages to be added before the print output. This returns null.
+ */
+mxPrintPreview.prototype.getCoverPages = function()
+{
+	return null;
+};
+
+/**
+ * Function: getAppendices
+ * 
+ * Returns the pages to be added after the print output. This returns null.
+ */
+mxPrintPreview.prototype.getAppendices = function()
+{
+	return null;
+};
+
+/**
+ * Function: print
+ * 
+ * Opens the print preview and shows the print dialog.
+ * 
+ * Parameters:
+ * 
+ * css - Optional CSS string to be used in the head section.
+ */
+mxPrintPreview.prototype.print = function(css)
+{
+	var wnd = this.open(css);
+	
+	if (wnd != null)
+	{
+		wnd.print();
+	}
+};
+
+/**
+ * Function: close
+ * 
+ * Closes the print preview window.
+ */
+mxPrintPreview.prototype.close = function()
+{
+	if (this.wnd != null)
+	{
+		this.wnd.close();
+		this.wnd = null;
+	}
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/view/mxStyleRegistry.js b/airavata-kubernetes/workflow-composer/src/js/view/mxStyleRegistry.js
new file mode 100644
index 0000000..7d5d7af
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/view/mxStyleRegistry.js
@@ -0,0 +1,71 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+var mxStyleRegistry =
+{
+	/**
+	 * Class: mxStyleRegistry
+	 *
+	 * Singleton class that acts as a global converter from string to object values
+	 * in a style. This is currently only used to perimeters and edge styles.
+	 * 
+	 * Variable: values
+	 *
+	 * Maps from strings to objects.
+	 */
+	values: [],
+
+	/**
+	 * Function: putValue
+	 *
+	 * Puts the given object into the registry under the given name.
+	 */
+	putValue: function(name, obj)
+	{
+		mxStyleRegistry.values[name] = obj;
+	},
+
+	/**
+	 * Function: getValue
+	 *
+	 * Returns the value associated with the given name.
+	 */
+	getValue: function(name)
+	{
+		return mxStyleRegistry.values[name];
+	},
+	
+	/**
+	 * Function: getName
+	 * 
+	 * Returns the name for the given value.
+	 */
+	getName: function(value)
+	{
+		for (var key in mxStyleRegistry.values)
+		{
+			if (mxStyleRegistry.values[key] == value)
+			{
+				return key;
+			}
+		}
+		
+		return null;
+	}
+
+};
+
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_ELBOW, mxEdgeStyle.ElbowConnector);
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_ENTITY_RELATION, mxEdgeStyle.EntityRelation);
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_LOOP, mxEdgeStyle.Loop);
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_SIDETOSIDE, mxEdgeStyle.SideToSide);
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_TOPTOBOTTOM, mxEdgeStyle.TopToBottom);
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_ORTHOGONAL, mxEdgeStyle.OrthConnector);
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_SEGMENT, mxEdgeStyle.SegmentConnector);
+
+mxStyleRegistry.putValue(mxConstants.PERIMETER_ELLIPSE, mxPerimeter.EllipsePerimeter);
+mxStyleRegistry.putValue(mxConstants.PERIMETER_RECTANGLE, mxPerimeter.RectanglePerimeter);
+mxStyleRegistry.putValue(mxConstants.PERIMETER_RHOMBUS, mxPerimeter.RhombusPerimeter);
+mxStyleRegistry.putValue(mxConstants.PERIMETER_TRIANGLE, mxPerimeter.TrianglePerimeter);
+mxStyleRegistry.putValue(mxConstants.PERIMETER_HEXAGON, mxPerimeter.HexagonPerimeter);
diff --git a/airavata-kubernetes/workflow-composer/src/js/view/mxStylesheet.js b/airavata-kubernetes/workflow-composer/src/js/view/mxStylesheet.js
new file mode 100644
index 0000000..ed37cff
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/view/mxStylesheet.js
@@ -0,0 +1,266 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxStylesheet
+ * 
+ * Defines the appearance of the cells in a graph. See <putCellStyle> for an
+ * example of creating a new cell style. It is recommended to use objects, not
+ * arrays for holding cell styles. Existing styles can be cloned using
+ * <mxUtils.clone> and turned into a string for debugging using
+ * <mxUtils.toString>.
+ *
+ * Default Styles:
+ * 
+ * The stylesheet contains two built-in styles, which are used if no style is
+ * defined for a cell:
+ *
+ *   defaultVertex - Default style for vertices
+ *   defaultEdge - Default style for edges
+ * 
+ * Example:
+ * 
+ * (code)
+ * var vertexStyle = stylesheet.getDefaultVertexStyle();
+ * vertexStyle[mxConstants.ROUNDED] = true;
+ * var edgeStyle = stylesheet.getDefaultEdgeStyle();
+ * edgeStyle[mxConstants.STYLE_EDGE] = mxEdgeStyle.EntityRelation;
+ * (end)
+ * 
+ * Modifies the built-in default styles.
+ * 
+ * To avoid the default style for a cell, add a leading semicolon
+ * to the style definition, eg.
+ * 
+ * (code)
+ * ;shadow=1
+ * (end)
+ * 
+ * Removing keys:
+ * 
+ * For removing a key in a cell style of the form [stylename;|key=value;] the
+ * special value none can be used, eg. highlight;fillColor=none
+ * 
+ * See also the helper methods in mxUtils to modify strings of this format,
+ * namely <mxUtils.setStyle>, <mxUtils.indexOfStylename>,
+ * <mxUtils.addStylename>, <mxUtils.removeStylename>,
+ * <mxUtils.removeAllStylenames> and <mxUtils.setStyleFlag>.
+ * 
+ * Constructor: mxStylesheet
+ * 
+ * Constructs a new stylesheet and assigns default styles.
+ */
+function mxStylesheet()
+{
+	this.styles = new Object();
+	
+	this.putDefaultVertexStyle(this.createDefaultVertexStyle());
+	this.putDefaultEdgeStyle(this.createDefaultEdgeStyle());
+};
+
+/**
+ * Function: styles
+ * 
+ * Maps from names to cell styles. Each cell style is a map of key,
+ * value pairs.
+ */
+mxStylesheet.prototype.styles;
+
+/**
+ * Function: createDefaultVertexStyle
+ * 
+ * Creates and returns the default vertex style.
+ */
+mxStylesheet.prototype.createDefaultVertexStyle = function()
+{
+	var style = new Object();
+	
+	style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_RECTANGLE;
+	style[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter;
+	style[mxConstants.STYLE_VERTICAL_ALIGN] = mxConstants.ALIGN_MIDDLE;
+	style[mxConstants.STYLE_ALIGN] = mxConstants.ALIGN_CENTER;
+	style[mxConstants.STYLE_FILLCOLOR] = '#C3D9FF';
+	style[mxConstants.STYLE_STROKECOLOR] = '#6482B9';
+	style[mxConstants.STYLE_FONTCOLOR] = '#774400';
+	
+	return style;
+};
+
+/**
+ * Function: createDefaultEdgeStyle
+ * 
+ * Creates and returns the default edge style.
+ */
+mxStylesheet.prototype.createDefaultEdgeStyle = function()
+{
+	var style = new Object();
+	
+	style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_CONNECTOR;
+	style[mxConstants.STYLE_ENDARROW] = mxConstants.ARROW_CLASSIC;
+	style[mxConstants.STYLE_VERTICAL_ALIGN] = mxConstants.ALIGN_MIDDLE;
+	style[mxConstants.STYLE_ALIGN] = mxConstants.ALIGN_CENTER;
+	style[mxConstants.STYLE_STROKECOLOR] = '#6482B9';
+	style[mxConstants.STYLE_FONTCOLOR] = '#446299';
+	
+	return style;
+};
+
+/**
+ * Function: putDefaultVertexStyle
+ * 
+ * Sets the default style for vertices using defaultVertex as the
+ * stylename.
+ * 
+ * Parameters:
+ * style - Key, value pairs that define the style.
+ */
+mxStylesheet.prototype.putDefaultVertexStyle = function(style)
+{
+	this.putCellStyle('defaultVertex', style);
+};
+
+/**
+ * Function: putDefaultEdgeStyle
+ * 
+ * Sets the default style for edges using defaultEdge as the stylename.
+ */
+mxStylesheet.prototype.putDefaultEdgeStyle = function(style)
+{
+	this.putCellStyle('defaultEdge', style);
+};
+
+/**
+ * Function: getDefaultVertexStyle
+ * 
+ * Returns the default style for vertices.
+ */
+mxStylesheet.prototype.getDefaultVertexStyle = function()
+{
+	return this.styles['defaultVertex'];
+};
+
+/**
+ * Function: getDefaultEdgeStyle
+ * 
+ * Sets the default style for edges.
+ */
+mxStylesheet.prototype.getDefaultEdgeStyle = function()
+{
+	return this.styles['defaultEdge'];
+};
+
+/**
+ * Function: putCellStyle
+ * 
+ * Stores the given map of key, value pairs under the given name in
+ * <styles>.
+ *
+ * Example:
+ * 
+ * The following example adds a new style called 'rounded' into an
+ * existing stylesheet:
+ * 
+ * (code)
+ * var style = new Object();
+ * style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_RECTANGLE;
+ * style[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter;
+ * style[mxConstants.STYLE_ROUNDED] = true;
+ * graph.getStylesheet().putCellStyle('rounded', style);
+ * (end)
+ * 
+ * In the above example, the new style is an object. The possible keys of
+ * the object are all the constants in <mxConstants> that start with STYLE
+ * and the values are either JavaScript objects, such as
+ * <mxPerimeter.RightAngleRectanglePerimeter> (which is in fact a function)
+ * or expressions, such as true. Note that not all keys will be
+ * interpreted by all shapes (eg. the line shape ignores the fill color).
+ * The final call to this method associates the style with a name in the
+ * stylesheet. The style is used in a cell with the following code:
+ * 
+ * (code)
+ * model.setStyle(cell, 'rounded');
+ * (end)
+ * 
+ * Parameters:
+ * 
+ * name - Name for the style to be stored.
+ * style - Key, value pairs that define the style.
+ */
+mxStylesheet.prototype.putCellStyle = function(name, style)
+{
+	this.styles[name] = style;
+};
+
+/**
+ * Function: getCellStyle
+ * 
+ * Returns the cell style for the specified stylename or the given
+ * defaultStyle if no style can be found for the given stylename.
+ * 
+ * Parameters:
+ * 
+ * name - String of the form [(stylename|key=value);] that represents the
+ * style.
+ * defaultStyle - Default style to be returned if no style can be found.
+ */
+mxStylesheet.prototype.getCellStyle = function(name, defaultStyle)
+{
+	var style = defaultStyle;
+	
+	if (name != null && name.length > 0)
+	{
+		var pairs = name.split(';');
+
+		if (style != null &&
+			name.charAt(0) != ';')
+		{
+			style = mxUtils.clone(style);
+		}
+		else
+		{
+			style = new Object();
+		}
+
+		// Parses each key, value pair into the existing style
+	 	for (var i = 0; i < pairs.length; i++)
+	 	{
+	 		var tmp = pairs[i];
+	 		var pos = tmp.indexOf('=');
+	 		
+	 		if (pos >= 0)
+	 		{
+		 		var key = tmp.substring(0, pos);
+		 		var value = tmp.substring(pos + 1);
+
+		 		if (value == mxConstants.NONE)
+		 		{
+		 			delete style[key];
+		 		}
+		 		else if (mxUtils.isNumeric(value))
+		 		{
+		 			style[key] = parseFloat(value);
+		 		}
+		 		else
+		 		{
+			 		style[key] = value;
+		 		}
+			}
+	 		else
+	 		{
+	 			// Merges the entries from a named style
+				var tmpStyle = this.styles[tmp];
+				
+				if (tmpStyle != null)
+				{
+					for (var key in tmpStyle)
+					{
+						style[key] = tmpStyle[key];
+					}
+				}
+	 		}
+		}
+	}
+	
+	return style;
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/view/mxSwimlaneManager.js b/airavata-kubernetes/workflow-composer/src/js/view/mxSwimlaneManager.js
new file mode 100644
index 0000000..f02214f
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/view/mxSwimlaneManager.js
@@ -0,0 +1,450 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxSwimlaneManager
+ * 
+ * Manager for swimlanes and nested swimlanes that sets the size of newly added
+ * swimlanes to that of their siblings, and propagates changes to the size of a
+ * swimlane to its siblings, if <siblings> is true, and its ancestors, if
+ * <bubbling> is true.
+ * 
+ * Constructor: mxSwimlaneManager
+ *
+ * Constructs a new swimlane manager for the given graph.
+ *
+ * Arguments:
+ * 
+ * graph - Reference to the enclosing graph. 
+ */
+function mxSwimlaneManager(graph, horizontal, addEnabled, resizeEnabled)
+{
+	this.horizontal = (horizontal != null) ? horizontal : true;
+	this.addEnabled = (addEnabled != null) ? addEnabled : true;
+	this.resizeEnabled = (resizeEnabled != null) ? resizeEnabled : true;
+
+	this.addHandler = mxUtils.bind(this, function(sender, evt)
+	{
+		if (this.isEnabled() && this.isAddEnabled())
+		{
+			this.cellsAdded(evt.getProperty('cells'));
+		}
+	});
+	
+	this.resizeHandler = mxUtils.bind(this, function(sender, evt)
+	{
+		if (this.isEnabled() && this.isResizeEnabled())
+		{
+			this.cellsResized(evt.getProperty('cells'));
+		}
+	});
+	
+	this.setGraph(graph);
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxSwimlaneManager.prototype = new mxEventSource();
+mxSwimlaneManager.prototype.constructor = mxSwimlaneManager;
+
+/**
+ * Variable: graph
+ * 
+ * Reference to the enclosing <mxGraph>.
+ */
+mxSwimlaneManager.prototype.graph = null;
+
+/**
+ * Variable: enabled
+ * 
+ * Specifies if event handling is enabled. Default is true.
+ */
+mxSwimlaneManager.prototype.enabled = true;
+
+/**
+ * Variable: horizontal
+ * 
+ * Specifies the orientation of the swimlanes. Default is true.
+ */
+mxSwimlaneManager.prototype.horizontal = true;
+
+/**
+ * Variable: addEnabled
+ * 
+ * Specifies if newly added cells should be resized to match the size of their
+ * existing siblings. Default is true.
+ */
+mxSwimlaneManager.prototype.addEnabled = true;
+
+/**
+ * Variable: resizeEnabled
+ * 
+ * Specifies if resizing of swimlanes should be handled. Default is true.
+ */
+mxSwimlaneManager.prototype.resizeEnabled = true;
+
+/**
+ * Variable: moveHandler
+ * 
+ * Holds the function that handles the move event.
+ */
+mxSwimlaneManager.prototype.addHandler = null;
+
+/**
+ * Variable: moveHandler
+ * 
+ * Holds the function that handles the move event.
+ */
+mxSwimlaneManager.prototype.resizeHandler = null;
+
+/**
+ * Function: isEnabled
+ * 
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxSwimlaneManager.prototype.isEnabled = function()
+{
+	return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ * 
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ * 
+ * Parameters:
+ * 
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxSwimlaneManager.prototype.setEnabled = function(value)
+{
+	this.enabled = value;
+};
+
+/**
+ * Function: isHorizontal
+ * 
+ * Returns <horizontal>.
+ */
+mxSwimlaneManager.prototype.isHorizontal = function()
+{
+	return this.horizontal;
+};
+
+/**
+ * Function: setHorizontal
+ * 
+ * Sets <horizontal>.
+ */
+mxSwimlaneManager.prototype.setHorizontal = function(value)
+{
+	this.horizontal = value;
+};
+
+/**
+ * Function: isAddEnabled
+ * 
+ * Returns <addEnabled>.
+ */
+mxSwimlaneManager.prototype.isAddEnabled = function()
+{
+	return this.addEnabled;
+};
+
+/**
+ * Function: setAddEnabled
+ * 
+ * Sets <addEnabled>.
+ */
+mxSwimlaneManager.prototype.setAddEnabled = function(value)
+{
+	this.addEnabled = value;
+};
+
+/**
+ * Function: isResizeEnabled
+ * 
+ * Returns <resizeEnabled>.
+ */
+mxSwimlaneManager.prototype.isResizeEnabled = function()
+{
+	return this.resizeEnabled;
+};
+
+/**
+ * Function: setResizeEnabled
+ * 
+ * Sets <resizeEnabled>.
+ */
+mxSwimlaneManager.prototype.setResizeEnabled = function(value)
+{
+	this.resizeEnabled = value;
+};
+
+/**
+ * Function: getGraph
+ * 
+ * Returns the graph that this manager operates on.
+ */
+mxSwimlaneManager.prototype.getGraph = function()
+{
+	return this.graph;
+};
+
+/**
+ * Function: setGraph
+ * 
+ * Sets the graph that the manager operates on.
+ */
+mxSwimlaneManager.prototype.setGraph = function(graph)
+{
+	if (this.graph != null)
+	{
+		this.graph.removeListener(this.addHandler);
+		this.graph.removeListener(this.resizeHandler);
+	}
+	
+	this.graph = graph;
+	
+	if (this.graph != null)
+	{
+		this.graph.addListener(mxEvent.ADD_CELLS, this.addHandler);
+		this.graph.addListener(mxEvent.CELLS_RESIZED, this.resizeHandler);
+	}
+};
+
+/**
+ * Function: isSwimlaneIgnored
+ * 
+ * Returns true if the given swimlane should be ignored.
+ */
+mxSwimlaneManager.prototype.isSwimlaneIgnored = function(swimlane)
+{
+	return !this.getGraph().isSwimlane(swimlane);
+};
+
+/**
+ * Function: isCellHorizontal
+ * 
+ * Returns true if the given cell is horizontal. If the given cell is not a
+ * swimlane, then the global orientation is returned.
+ */
+mxSwimlaneManager.prototype.isCellHorizontal = function(cell)
+{
+	if (this.graph.isSwimlane(cell))
+	{
+		var style = this.graph.getCellStyle(cell);
+		
+		return mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, 1) == 1;
+	}
+	
+	return !this.isHorizontal();
+};
+
+/**
+ * Function: cellsAdded
+ * 
+ * Called if any cells have been added.
+ * 
+ * Parameters:
+ * 
+ * cell - Array of <mxCells> that have been added.
+ */
+mxSwimlaneManager.prototype.cellsAdded = function(cells)
+{
+	if (cells != null)
+	{
+		var model = this.getGraph().getModel();
+
+		model.beginUpdate();
+		try
+		{
+			for (var i = 0; i < cells.length; i++)
+			{
+				if (!this.isSwimlaneIgnored(cells[i]))
+				{
+					this.swimlaneAdded(cells[i]);
+				}
+			}
+		}
+		finally
+		{
+			model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: swimlaneAdded
+ * 
+ * Updates the size of the given swimlane to match that of any existing
+ * siblings swimlanes.
+ * 
+ * Parameters:
+ * 
+ * swimlane - <mxCell> that represents the new swimlane.
+ */
+mxSwimlaneManager.prototype.swimlaneAdded = function(swimlane)
+{
+	var model = this.getGraph().getModel();
+	var parent = model.getParent(swimlane);
+	var childCount = model.getChildCount(parent);
+	var geo = null;
+	
+	// Finds the first valid sibling swimlane as reference
+	for (var i = 0; i < childCount; i++)
+	{
+		var child = model.getChildAt(parent, i);
+		
+		if (child != swimlane && !this.isSwimlaneIgnored(child))
+		{
+			geo = model.getGeometry(child);
+			
+			if (geo != null)
+			{	
+				break;
+			}
+		}
+	}
+	
+	// Applies the size of the refernece to the newly added swimlane
+	if (geo != null)
+	{
+		var parentHorizontal = (parent != null) ? this.isCellHorizontal(parent) : this.horizontal;
+		this.resizeSwimlane(swimlane, geo.width, geo.height, parentHorizontal);
+	}
+};
+
+/**
+ * Function: cellsResized
+ * 
+ * Called if any cells have been resizes. Calls <swimlaneResized> for all
+ * swimlanes where <isSwimlaneIgnored> returns false.
+ * 
+ * Parameters:
+ * 
+ * cells - Array of <mxCells> whose size was changed.
+ */
+mxSwimlaneManager.prototype.cellsResized = function(cells)
+{
+	if (cells != null)
+	{
+		var model = this.getGraph().getModel();
+		
+		model.beginUpdate();
+		try
+		{
+			// Finds the top-level swimlanes and adds offsets
+			for (var i = 0; i < cells.length; i++)
+			{
+				if (!this.isSwimlaneIgnored(cells[i]))
+				{
+					var geo = model.getGeometry(cells[i]);
+
+					if (geo != null)
+					{
+						var size = new mxRectangle(0, 0, geo.width, geo.height);
+						var top = cells[i];
+						var current = top;
+						
+						while (current != null)
+						{
+							top = current;
+							current = model.getParent(current);
+							var tmp = (this.graph.isSwimlane(current)) ?
+									this.graph.getStartSize(current) :
+									new mxRectangle();
+							size.width += tmp.width;
+							size.height += tmp.height;
+						}
+						
+						var parentHorizontal = (current != null) ? this.isCellHorizontal(current) : this.horizontal;
+						this.resizeSwimlane(top, size.width, size.height, parentHorizontal);
+					}
+				}
+			}
+		}
+		finally
+		{
+			model.endUpdate();
+		}
+	}
+};
+
+/**
+ * Function: resizeSwimlane
+ * 
+ * Called from <cellsResized> for all swimlanes that are not ignored to update
+ * the size of the siblings and the size of the parent swimlanes, recursively,
+ * if <bubbling> is true.
+ * 
+ * Parameters:
+ * 
+ * swimlane - <mxCell> whose size has changed.
+ */
+mxSwimlaneManager.prototype.resizeSwimlane = function(swimlane, w, h, parentHorizontal)
+{
+	var model = this.getGraph().getModel();
+	
+	model.beginUpdate();
+	try
+	{
+		var horizontal = this.isCellHorizontal(swimlane);
+		
+		if (!this.isSwimlaneIgnored(swimlane))
+		{
+			var geo = model.getGeometry(swimlane);
+			
+			if (geo != null)
+			{
+				if ((parentHorizontal && geo.height != h) || (!parentHorizontal && geo.width != w))
+				{
+					geo = geo.clone();
+					
+					if (parentHorizontal)
+					{
+						geo.height = h;
+					}
+					else
+					{
+						geo.width = w;
+					}
+
+					model.setGeometry(swimlane, geo);
+				}
+			}
+		}
+
+		var tmp = (this.graph.isSwimlane(swimlane)) ?
+				this.graph.getStartSize(swimlane) :
+				new mxRectangle();
+		w -= tmp.width;
+		h -= tmp.height;
+		
+		var childCount = model.getChildCount(swimlane);
+		
+		for (var i = 0; i < childCount; i++)
+		{
+			var child = model.getChildAt(swimlane, i);
+			this.resizeSwimlane(child, w, h, horizontal);
+		}
+	}
+	finally
+	{
+		model.endUpdate();
+	}
+};
+
+/**
+ * Function: destroy
+ * 
+ * Removes all handlers from the <graph> and deletes the reference to it.
+ */
+mxSwimlaneManager.prototype.destroy = function()
+{
+	this.setGraph(null);
+};
diff --git a/airavata-kubernetes/workflow-composer/src/js/view/mxTemporaryCellStates.js b/airavata-kubernetes/workflow-composer/src/js/view/mxTemporaryCellStates.js
new file mode 100644
index 0000000..34bbfdd
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/js/view/mxTemporaryCellStates.js
@@ -0,0 +1,108 @@
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxTemporaryCellStates
+ *
+ * Extends <mxPoint> to implement a 2-dimensional rectangle with double
+ * precision coordinates.
+ * 
+ * Constructor: mxRectangle
+ *
+ * Constructs a new rectangle for the optional parameters. If no parameters
+ * are given then the respective default values are used.
+ */
+function mxTemporaryCellStates(view, scale, cells, isCellVisibleFn)
+{
+	scale = (scale != null) ? scale : 1;
+	this.view = view;
+	
+	// Stores the previous state
+	this.oldValidateCellState = view.validateCellState;
+	this.oldBounds = view.getGraphBounds();
+	this.oldStates = view.getStates();
+	this.oldScale = view.getScale();
+	
+	// Overrides validateCellState to ignore invisible cells
+	var self = this;
+	
+	view.validateCellState = function(cell, resurse)
+	{
+		if (cell == null || isCellVisibleFn == null || isCellVisibleFn(cell))
+		{
+			return self.oldValidateCellState.apply(view, arguments);
+		}
+		
+		return null;
+	};
+	
+	// Creates space for new states
+	view.setStates(new mxDictionary());
+	view.setScale(scale);
+	
+	if (cells != null)
+	{
+		view.resetValidationState();
+		var bbox = null;
+
+		// Validates the vertices and edges without adding them to
+		// the model so that the original cells are not modified
+		for (var i = 0; i < cells.length; i++)
+		{
+			var bounds = view.getBoundingBox(view.validateCellState(view.validateCell(cells[i])));
+			
+			if (bbox == null)
+			{
+				bbox = bounds;
+			}
+			else
+			{
+				bbox.add(bounds);
+			}
+		}
+
+		view.setGraphBounds(bbox || new mxRectangle());
+	}
+};
+
+/**
+ * Variable: view
+ *
+ * Holds the width of the rectangle. Default is 0.
+ */
+mxTemporaryCellStates.prototype.view = null;
+
+/**
+ * Variable: oldStates
+ *
+ * Holds the height of the rectangle. Default is 0.
+ */
+mxTemporaryCellStates.prototype.oldStates = null;
+
+/**
+ * Variable: oldBounds
+ *
+ * Holds the height of the rectangle. Default is 0.
+ */
+mxTemporaryCellStates.prototype.oldBounds = null;
+
+/**
+ * Variable: oldScale
+ *
+ * Holds the height of the rectangle. Default is 0.
+ */
+mxTemporaryCellStates.prototype.oldScale = null;
+
+/**
+ * Function: destroy
+ * 
+ * Returns the top, left corner as a new <mxPoint>.
+ */
+mxTemporaryCellStates.prototype.destroy = function()
+{
+	this.view.setScale(this.oldScale);
+	this.view.setStates(this.oldStates);
+	this.view.setGraphBounds(this.oldBounds);
+	this.view.validateCellState = this.oldValidateCellState;
+};
diff --git a/airavata-kubernetes/workflow-composer/src/resources/editor.txt b/airavata-kubernetes/workflow-composer/src/resources/editor.txt
new file mode 100644
index 0000000..53e8712
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/resources/editor.txt
@@ -0,0 +1,5 @@
+askZoom=Enter zoom (%)
+properties=Properties
+outline=Outline
+tasks=Tasks
+help=Help
diff --git a/airavata-kubernetes/workflow-composer/src/resources/editor_de.txt b/airavata-kubernetes/workflow-composer/src/resources/editor_de.txt
new file mode 100644
index 0000000..542f387
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/resources/editor_de.txt
@@ -0,0 +1,5 @@
+askZoom=Zoom eingeben (%)
+properties=Eigenschaften
+outline=Uebersicht
+tasks=Aufgaben
+help=Hilfe
diff --git a/airavata-kubernetes/workflow-composer/src/resources/editor_zh.txt b/airavata-kubernetes/workflow-composer/src/resources/editor_zh.txt
new file mode 100644
index 0000000..b37848b
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/resources/editor_zh.txt
@@ -0,0 +1,5 @@
+askZoom=进入缩放(%25)
+properties=属性
+outline=轮廓
+tasks=任务
+help=帮助
\ No newline at end of file
diff --git a/airavata-kubernetes/workflow-composer/src/resources/graph.txt b/airavata-kubernetes/workflow-composer/src/resources/graph.txt
new file mode 100644
index 0000000..baf61f8
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/resources/graph.txt
@@ -0,0 +1,11 @@
+alreadyConnected=Nodes already connected
+containsValidationErrors=Contains validation errors
+updatingDocument=Updating Document. Please wait...
+updatingSelection=Updating Selection. Please wait...
+collapse-expand=Collapse/Expand
+doubleClickOrientation=Doubleclick to change orientation
+close=Close
+error=Error
+done=Done
+cancel=Cancel
+ok=OK
diff --git a/airavata-kubernetes/workflow-composer/src/resources/graph_de.txt b/airavata-kubernetes/workflow-composer/src/resources/graph_de.txt
new file mode 100644
index 0000000..2999934
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/resources/graph_de.txt
@@ -0,0 +1,11 @@
+alreadyConnected=Knoten schon verbunden
+containsValidationErrors=Enthält Validierungsfehler
+updatingDocument=Aktualisiere Dokument. Bitte warten...
+updatingSelection=Aktualisiere Markierung. Bitte warten...
+collapse-expand=Einklappen/Ausklappen
+doubleClickOrientation=Doppelklicken um Orientierung zu ändern
+close=Schliessen
+error=Fehler
+done=Fertig
+cancel=Abbrechen
+ok=OK
diff --git a/airavata-kubernetes/workflow-composer/src/resources/graph_zh.txt b/airavata-kubernetes/workflow-composer/src/resources/graph_zh.txt
new file mode 100644
index 0000000..4958593
--- /dev/null
+++ b/airavata-kubernetes/workflow-composer/src/resources/graph_zh.txt
@@ -0,0 +1,11 @@
+alreadyConnected=节点已经连接
+containsValidationErrors=包含效验错误
+updatingDocument=更新文档。请等候......
+updatingSelection=更新所选项。请等候......
+collapse-expand=折叠/展开
+doubleClickOrientation=双击以改变方向
+close=关闭
+error=错误
+done=完成
+cancel=取消
+ok=确定
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
"commits@airavata.apache.org" <co...@airavata.apache.org>.